From 306014abefa53bb5349971972cf50662010bea16 Mon Sep 17 00:00:00 2001 From: mewmew Date: Wed, 20 Jul 2016 06:38:19 +0200 Subject: [PATCH] flac: Add preliminary encoding support. Support for encoding metadata has been added. Proper support for encoding audio samples is yet to be implemented. For now, simply copy the original encoded audio data from the source FLAC file. The API mirrors that of image.Encode. Updates #14. --- enc.go | 425 ++++++++++++++++++++++++++++++++++++++++ internal/bits/reader.go | 4 +- meta/cuesheet.go | 2 +- meta/meta.go | 14 +- meta/picture.go | 2 +- meta/seektable.go | 2 +- meta/streaminfo.go | 2 +- meta/vorbiscomment.go | 2 +- 8 files changed, 439 insertions(+), 14 deletions(-) create mode 100644 enc.go diff --git a/enc.go b/enc.go new file mode 100644 index 0000000..322f6a2 --- /dev/null +++ b/enc.go @@ -0,0 +1,425 @@ +package flac + +import ( + "bytes" + "encoding/binary" + "io" + + "github.com/icza/bitio" + "github.com/mewkiz/flac/meta" + "github.com/mewkiz/pkg/errutil" +) + +// Encode writes the FLAC audio stream to w. +func Encode(w io.Writer, stream *Stream) error { + // Create a bit writer to the output stream. + buf := new(bytes.Buffer) + bw := bitio.NewWriter(buf) + enc := &encoder{bw: bw} + + // Store FLAC signature. + if _, err := bw.Write(signature); err != nil { + return errutil.Err(err) + } + + // Store StreamInfo metadata block header. + infoHdr := meta.Header{ + IsLast: len(stream.Blocks) == 0, + Type: meta.TypeStreamInfo, + // The StreamInfo metadata block body is 34 bytes in length. + // + // 34 = (16+16+24+24+20+3+5+36+8*16)/8 + Length: 34, + } + if err := enc.writeBlockHeader(infoHdr); err != nil { + return errutil.Err(err) + } + + // Store the StreamInfo metadata block. + if err := enc.writeStreamInfo(stream.Info); err != nil { + return errutil.Err(err) + } + + // Store metadata blocks. + for _, block := range stream.Blocks { + if block.Type > meta.TypePicture { + return errutil.Newf("flac.Encode: support for encoding %T of block type %d not yet implemented", block, block.Type) + } + + if err := enc.writeBlockHeader(block.Header); err != nil { + return errutil.Err(err) + } + var err error + switch body := block.Body.(type) { + case *meta.Application: + err = enc.writeApplication(body) + case *meta.SeekTable: + err = enc.writeSeekTable(body) + case *meta.VorbisComment: + err = enc.writeVorbisComment(body) + case *meta.CueSheet: + err = enc.writeCueSheet(body) + case *meta.Picture: + err = enc.writePicture(body) + default: + err = enc.writePadding(int(block.Length)) + } + if err != nil { + return errutil.Err(err) + } + } + + // Flush pending bit writes. + if err := bw.Close(); err != nil { + return errutil.Err(err) + } + + // Copy buffer to output stream. + if _, err := io.Copy(w, buf); err != nil { + return errutil.Err(err) + } + + // TODO: Implement proper encoding support for audio samples. For now, copy + // the audio sample stream verbatim from the source file. + if _, err := io.Copy(w, stream.r); err != nil { + return errutil.Err(err) + } + + return nil +} + +// An encoder represents a FLAC encoder. +type encoder struct { + // Bit writer to the output stream. + bw bitio.Writer +} + +// TODO: Consider moving metadata related encoding to the meta package. + +// writeBlockHeader writes the header of a metadata block. +func (enc *encoder) writeBlockHeader(hdr meta.Header) error { + // 1 bit: IsLast. + x := uint64(0) + if hdr.IsLast { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 7 bits: Type. + if err := enc.bw.WriteBits(uint64(hdr.Type), 7); err != nil { + return errutil.Err(err) + } + + // 24 bits: Length. + if err := enc.bw.WriteBits(uint64(hdr.Length), 24); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writeStreamInfo stores the body of a StreamInfo metadata block. +func (enc *encoder) writeStreamInfo(si *meta.StreamInfo) error { + // 16 bits: BlockSizeMin. + if err := enc.bw.WriteBits(uint64(si.BlockSizeMin), 16); err != nil { + return errutil.Err(err) + } + + // 16 bits: BlockSizeMax. + if err := enc.bw.WriteBits(uint64(si.BlockSizeMax), 16); err != nil { + return errutil.Err(err) + } + + // 24 bits: FrameSizeMin. + if err := enc.bw.WriteBits(uint64(si.FrameSizeMin), 24); err != nil { + return errutil.Err(err) + } + + // 24 bits: FrameSizeMax. + if err := enc.bw.WriteBits(uint64(si.FrameSizeMax), 24); err != nil { + return errutil.Err(err) + } + + // 20 bits: SampleRate. + if err := enc.bw.WriteBits(uint64(si.SampleRate), 20); err != nil { + return errutil.Err(err) + } + + // 3 bits: NChannels; stored as (number of channels) - 1. + if err := enc.bw.WriteBits(uint64(si.NChannels-1), 3); err != nil { + return errutil.Err(err) + } + + // 5 bits: BitsPerSample; stored as (bits-per-sample) - 1. + if err := enc.bw.WriteBits(uint64(si.BitsPerSample-1), 5); err != nil { + return errutil.Err(err) + } + + // 36 bits: NSamples. + if err := enc.bw.WriteBits(uint64(si.NSamples), 36); err != nil { + return errutil.Err(err) + } + + // 16 bytes: MD5sum. + if _, err := enc.bw.Write(si.MD5sum[:]); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writePadding writes the body of a Padding metadata block. +func (enc *encoder) writePadding(n int) error { + for i := 0; i < n; i++ { + if err := enc.bw.WriteByte(0); err != nil { + return errutil.Err(err) + } + } + return nil +} + +// writeApplication writes the body of an Application metadata block. +func (enc *encoder) writeApplication(app *meta.Application) error { + // 32 bits: ID. + if err := enc.bw.WriteBits(uint64(app.ID), 32); err != nil { + return errutil.Err(err) + } + + // Check if the Application block only contains an ID. + if _, err := enc.bw.Write(app.Data); err != nil { + return errutil.Err(err) + } + + return nil +} + +// writeSeekTable writes the body of a SeekTable metadata block. +func (enc *encoder) writeSeekTable(table *meta.SeekTable) error { + for _, point := range table.Points { + if err := binary.Write(enc.bw, binary.BigEndian, point); err != nil { + return errutil.Err(err) + } + } + return nil +} + +// writeVorbisComment writes the body of a VorbisComment metadata block. +func (enc *encoder) writeVorbisComment(comment *meta.VorbisComment) error { + // 32 bits: vendor length. + x := uint32(len(comment.Vendor)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + + // (vendor length) bits: Vendor. + if _, err := enc.bw.Write([]byte(comment.Vendor)); err != nil { + return errutil.Err(err) + } + + // Store tags. + // 32 bits: number of tags. + x = uint32(len(comment.Tags)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + for _, tag := range comment.Tags { + // Store tag, which has the following format: + // NAME=VALUE + buf := []byte(tag[0] + "=" + tag[1]) + + // 32 bits: vector length + x = uint32(len(buf)) + if err := binary.Write(enc.bw, binary.LittleEndian, x); err != nil { + return errutil.Err(err) + } + + // (vector length): vector. + if _, err := enc.bw.Write(buf); err != nil { + return errutil.Err(err) + } + } + + return nil +} + +// writeCueSheet writes the body of a CueSheet metadata block. +func (enc *encoder) writeCueSheet(cs *meta.CueSheet) error { + // Parse cue sheet. + // 128 bytes: MCN. + mcn := make([]byte, 128) + copy(mcn, cs.MCN) + if _, err := enc.bw.Write(mcn); err != nil { + return errutil.Err(err) + } + + // 64 bits: NLeadInSamples. + if err := enc.bw.WriteBits(cs.NLeadInSamples, 64); err != nil { + return errutil.Err(err) + } + + // 1 bit: IsCompactDisc. + x := uint64(0) + if cs.IsCompactDisc { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 7 bits and 258 bytes: reserved. + if err := enc.bw.WriteBits(0, 7); err != nil { + return errutil.Err(err) + } + // TODO: Remove unnecessary allocation. + padding := make([]byte, 258) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + + // Parse cue sheet tracks. + // 8 bits: (number of tracks) + x = uint64(len(cs.Tracks)) + if err := enc.bw.WriteBits(x, 8); err != nil { + return errutil.Err(err) + } + for _, track := range cs.Tracks { + // 64 bits: Offset. + if err := enc.bw.WriteBits(track.Offset, 64); err != nil { + return errutil.Err(err) + } + + // 8 bits: Num. + if err := enc.bw.WriteBits(uint64(track.Num), 8); err != nil { + return errutil.Err(err) + } + + // 12 bytes: ISRC. + isrc := make([]byte, 12) + copy(isrc, track.ISRC) + if _, err := enc.bw.Write(isrc); err != nil { + return errutil.Err(err) + } + + // 1 bit: IsAudio. + x := uint64(0) + if !track.IsAudio { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 1 bit: HasPreEmphasis. + // mask = 01000000 + x = 0 + if track.HasPreEmphasis { + x = 1 + } + if err := enc.bw.WriteBits(x, 1); err != nil { + return errutil.Err(err) + } + + // 6 bits and 13 bytes: reserved. + // mask = 00111111 + if err := enc.bw.WriteBits(0, 6); err != nil { + return errutil.Err(err) + } + // TODO: Remove unnecessary allocation. + padding := make([]byte, 13) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + + // Parse indicies. + // 8 bits: (number of indicies) + x = uint64(len(track.Indicies)) + if err := enc.bw.WriteBits(x, 8); err != nil { + return errutil.Err(err) + } + for _, index := range track.Indicies { + // 64 bits: Offset. + if err := enc.bw.WriteBits(index.Offset, 64); err != nil { + return errutil.Err(err) + } + + // 8 bits: Num. + if err := enc.bw.WriteBits(uint64(index.Num), 8); err != nil { + return errutil.Err(err) + } + + // 3 bytes: reserved. + // TODO: Remove unnecessary allocation. + padding := make([]byte, 3) + if _, err := enc.bw.Write(padding); err != nil { + return errutil.Err(err) + } + } + } + + return nil +} + +// writePicture writes the body of a Picture metadata block. +func (enc *encoder) writePicture(pic *meta.Picture) error { + // 32 bits: Type. + if err := enc.bw.WriteBits(uint64(pic.Type), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: (MIME type length). + x := uint64(len(pic.MIME)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (MIME type length) bytes: MIME. + if _, err := enc.bw.Write([]byte(pic.MIME)); err != nil { + return errutil.Err(err) + } + + // 32 bits: (description length). + x = uint64(len(pic.Desc)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (description length) bytes: Desc. + if _, err := enc.bw.Write([]byte(pic.Desc)); err != nil { + return errutil.Err(err) + } + + // 32 bits: Width. + if err := enc.bw.WriteBits(uint64(pic.Width), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: Height. + if err := enc.bw.WriteBits(uint64(pic.Height), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: Depth. + if err := enc.bw.WriteBits(uint64(pic.Depth), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: NPalColors. + if err := enc.bw.WriteBits(uint64(pic.NPalColors), 32); err != nil { + return errutil.Err(err) + } + + // 32 bits: (data length). + x = uint64(len(pic.Data)) + if err := enc.bw.WriteBits(x, 32); err != nil { + return errutil.Err(err) + } + + // (data length) bytes: Data. + if _, err := enc.bw.Write([]byte(pic.Data)); err != nil { + return errutil.Err(err) + } + + return nil +} diff --git a/internal/bits/reader.go b/internal/bits/reader.go index 272f3c1..85027bc 100644 --- a/internal/bits/reader.go +++ b/internal/bits/reader.go @@ -1,4 +1,4 @@ -// Package bits provides bit reading operations and binary decoding algorithms. +// Package bits provides bit access operations and binary decoding algorithms. package bits import ( @@ -20,7 +20,7 @@ type Reader struct { } // NewReader returns a new Reader that reads bits from r. -func NewReader(r io.Reader) (br *Reader) { +func NewReader(r io.Reader) *Reader { return &Reader{r: r} } diff --git a/meta/cuesheet.go b/meta/cuesheet.go index df6c89d..14a0f1e 100644 --- a/meta/cuesheet.go +++ b/meta/cuesheet.go @@ -26,7 +26,7 @@ type CueSheet struct { Tracks []CueSheetTrack } -// parseCueSheet reads and parses the body of an CueSheet metadata block. +// parseCueSheet reads and parses the body of a CueSheet metadata block. func (block *Block) parseCueSheet() error { // Parse cue sheet. // 128 bytes: MCN. diff --git a/meta/meta.go b/meta/meta.go index 593120f..7a922d2 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -172,13 +172,13 @@ type Type uint8 // Metadata block body types. const ( - TypeStreamInfo Type = iota - TypePadding - TypeApplication - TypeSeekTable - TypeVorbisComment - TypeCueSheet - TypePicture + TypeStreamInfo Type = 0 + TypePadding Type = 1 + TypeApplication Type = 2 + TypeSeekTable Type = 3 + TypeVorbisComment Type = 4 + TypeCueSheet Type = 5 + TypePicture Type = 6 ) func (t Type) String() string { diff --git a/meta/picture.go b/meta/picture.go index efd5c3b..022e169 100644 --- a/meta/picture.go +++ b/meta/picture.go @@ -50,7 +50,7 @@ type Picture struct { Data []byte } -// parsePicture reads and parses the body of an Picture metadata block. +// parsePicture reads and parses the body of a Picture metadata block. func (block *Block) parsePicture() error { // 32 bits: Type. pic := new(Picture) diff --git a/meta/seektable.go b/meta/seektable.go index 06da3e7..c0b96ce 100644 --- a/meta/seektable.go +++ b/meta/seektable.go @@ -14,7 +14,7 @@ type SeekTable struct { Points []SeekPoint } -// parseSeekTable reads and parses the body of an SeekTable metadata block. +// parseSeekTable reads and parses the body of a SeekTable metadata block. func (block *Block) parseSeekTable() error { // The number of seek points is derived from the header length, divided by // the size of a SeekPoint; which is 18 bytes. diff --git a/meta/streaminfo.go b/meta/streaminfo.go index 007bbe6..f72fbfc 100644 --- a/meta/streaminfo.go +++ b/meta/streaminfo.go @@ -39,7 +39,7 @@ type StreamInfo struct { MD5sum [md5.Size]uint8 } -// parseStreamInfo reads and parses the body of an StreamInfo metadata block. +// parseStreamInfo reads and parses the body of a StreamInfo metadata block. func (block *Block) parseStreamInfo() error { // 16 bits: BlockSizeMin. br := bits.NewReader(block.lr) diff --git a/meta/vorbiscomment.go b/meta/vorbiscomment.go index 673d626..0642868 100644 --- a/meta/vorbiscomment.go +++ b/meta/vorbiscomment.go @@ -16,7 +16,7 @@ type VorbisComment struct { Tags [][2]string } -// parseVorbisComment reads and parses the body of an VorbisComment metadata +// parseVorbisComment reads and parses the body of a VorbisComment metadata // block. func (block *Block) parseVorbisComment() error { // 32 bits: vendor length.