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.
This commit is contained in:
mewmew 2016-07-20 06:38:19 +02:00
parent 7164e11639
commit 306014abef
8 changed files with 439 additions and 14 deletions

425
enc.go Normal file
View file

@ -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
}

View file

@ -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}
}

View file

@ -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.

View file

@ -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 {

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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.