flac: add Encoder API to encode audio samples and metadata blocks (#32)
* flac: encode frame header * flac: calculate CRC-8 when encoding frame headers * flac: fix encoding of frame header * flac: add preliminary subframe encoder * flac: fix UTF-8 encoding of frame number * frame: add sanity check for sample count in decodeLPC Updates #31. * flac: update flac encoding API, depricate flac.Encode Encode has been removed in favour of using NewEncoder. The Encode function was temporarily added to support re-encoding FLAC streams to update the metadata, but it had no support for encoding audio samples. The added flac.Encoder has support for encoding both metadata and audio samples. It also does not require that you first decode a FLAC file to later re-encode it by calling Encode (as was the previous behaviour). * flac: add MD5 running hash of unencoded audio samples to StreamInfo * flac: remove unused encodePadding Reported by golangci * flac: fix golangci lint issues frame/utf8.go:57:6: `decodeUTF8Int` is unused (deadcode) func decodeUTF8Int(r io.Reader) (n uint64, err error) { ^ internal/utf8/encode.go:32:16: unnecessary conversion (unconvert) bits = uint64(t2 | (x>>6)&mask2) ^ internal/utf8/encode.go:37:16: unnecessary conversion (unconvert) bits = uint64(t3 | (x>>(6*2))&mask3) ^ internal/utf8/encode.go:42:16: unnecessary conversion (unconvert) bits = uint64(t4 | (x>>(6*3))&mask4) ^ * flac: fix golangci lint issues encode_frame.go:89:1: cyclomatic complexity 52 of func `(*Encoder).encodeFrameHeader` is high (> 30) (gocyclo) func (enc *Encoder) encodeFrameHeader(w io.Writer, hdr frame.Header) error { ^ internal/utf8/encode.go:66:17: unnecessary conversion (unconvert) bits := uint64(tx | (x>>uint(6*i))&maskx) ^ encode_subframe.go:105:46: unnecessary conversion (unconvert) if err := bw.WriteBits(uint64(sample), byte(hdr.BitsPerSample)); err != nil { ^ * flac: clarify that frame.Header.Num is calculated by the encoder * flac: minor re-phrasing
This commit is contained in:
parent
4309906bb8
commit
3e3f4b5fcf
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.flac
|
||||
*.wav
|
209
cmd/wav2flac/main.go
Normal file
209
cmd/wav2flac/main.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/go-audio/audio"
|
||||
"github.com/go-audio/wav"
|
||||
"github.com/mewkiz/flac"
|
||||
"github.com/mewkiz/flac/frame"
|
||||
"github.com/mewkiz/flac/meta"
|
||||
"github.com/mewkiz/pkg/osutil"
|
||||
"github.com/mewkiz/pkg/pathutil"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse command line arguments.
|
||||
var (
|
||||
// force overwrite FLAC file if already present.
|
||||
force bool
|
||||
)
|
||||
flag.BoolVar(&force, "f", false, "force overwrite")
|
||||
flag.Parse()
|
||||
for _, wavPath := range flag.Args() {
|
||||
if err := wav2flac(wavPath, force); err != nil {
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wav2flac(wavPath string, force bool) error {
|
||||
// Create WAV decoder.
|
||||
r, err := os.Open(wavPath)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer r.Close()
|
||||
dec := wav.NewDecoder(r)
|
||||
if !dec.IsValidFile() {
|
||||
return errors.Errorf("invalid WAV file %q", wavPath)
|
||||
}
|
||||
sampleRate, nchannels, bps := int(dec.SampleRate), int(dec.NumChans), int(dec.BitDepth)
|
||||
|
||||
// Create FLAC encoder.
|
||||
flacPath := pathutil.TrimExt(wavPath) + ".flac"
|
||||
if !force && osutil.Exists(flacPath) {
|
||||
return errors.Errorf("FLAC file %q already present; use -f flag to force overwrite", flacPath)
|
||||
}
|
||||
w, err := os.Create(flacPath)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
info := &meta.StreamInfo{
|
||||
// Minimum block size (in samples) used in the stream; between 16 and
|
||||
// 65535 samples.
|
||||
BlockSizeMin: 16, // adjusted by encoder.
|
||||
// Maximum block size (in samples) used in the stream; between 16 and
|
||||
// 65535 samples.
|
||||
BlockSizeMax: 65535, // adjusted by encoder.
|
||||
// Minimum frame size in bytes; a 0 value implies unknown.
|
||||
//FrameSizeMin // set by encoder.
|
||||
// Maximum frame size in bytes; a 0 value implies unknown.
|
||||
//FrameSizeMax // set by encoder.
|
||||
// Sample rate in Hz; between 1 and 655350 Hz.
|
||||
SampleRate: uint32(sampleRate),
|
||||
// Number of channels; between 1 and 8 channels.
|
||||
NChannels: uint8(nchannels),
|
||||
// Sample size in bits-per-sample; between 4 and 32 bits.
|
||||
BitsPerSample: uint8(bps),
|
||||
// Total number of inter-channel samples in the stream. One second of
|
||||
// 44.1 KHz audio will have 44100 samples regardless of the number of
|
||||
// channels. A 0 value implies unknown.
|
||||
//NSamples // set by encoder.
|
||||
// MD5 checksum of the unencoded audio data.
|
||||
//MD5sum // set by encoder.
|
||||
}
|
||||
enc, err := flac.NewEncoder(w, info)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer enc.Close()
|
||||
|
||||
// Encode samples.
|
||||
if err := dec.FwdToPCM(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
// Number of samples per channel and block.
|
||||
const nsamplesPerChannel = 16
|
||||
nsamplesPerBlock := nchannels * nsamplesPerChannel
|
||||
buf := &audio.IntBuffer{
|
||||
Format: &audio.Format{
|
||||
NumChannels: nchannels,
|
||||
SampleRate: sampleRate,
|
||||
},
|
||||
Data: make([]int, nsamplesPerBlock),
|
||||
SourceBitDepth: bps,
|
||||
}
|
||||
|
||||
subframes := make([]*frame.Subframe, nchannels)
|
||||
subHdr := frame.SubHeader{
|
||||
// Specifies the prediction method used to encode the audio sample of the
|
||||
// subframe.
|
||||
Pred: frame.PredVerbatim,
|
||||
// Prediction order used by fixed and FIR linear prediction decoding.
|
||||
Order: 0,
|
||||
// Wasted bits-per-sample.
|
||||
Wasted: 0,
|
||||
}
|
||||
for i := range subframes {
|
||||
subframe := &frame.Subframe{
|
||||
SubHeader: subHdr,
|
||||
Samples: make([]int32, nsamplesPerChannel),
|
||||
}
|
||||
subframes[i] = subframe
|
||||
}
|
||||
for j := 0; !dec.EOF(); j++ {
|
||||
// Decode WAV samples.
|
||||
n, err := dec.PCMBuffer(buf)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
for _, subframe := range subframes {
|
||||
subframe.NSamples = n / nchannels
|
||||
subframe.Samples = subframe.Samples[:subframe.NSamples]
|
||||
}
|
||||
for i, sample := range buf.Data {
|
||||
subframe := subframes[i%nchannels]
|
||||
subframe.Samples[i/nchannels] = int32(sample)
|
||||
}
|
||||
fmt.Println("j:", j)
|
||||
|
||||
// Encode FLAC frame.
|
||||
channels, err := getChannels(nchannels)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
hdr := frame.Header{
|
||||
// Specifies if the block size is fixed or variable.
|
||||
HasFixedBlockSize: false,
|
||||
// Block size in inter-channel samples, i.e. the number of audio samples
|
||||
// in each subframe.
|
||||
BlockSize: uint16(nsamplesPerChannel),
|
||||
// Sample rate in Hz; a 0 value implies unknown, get sample rate from
|
||||
// StreamInfo.
|
||||
SampleRate: uint32(sampleRate),
|
||||
// Specifies the number of channels (subframes) that exist in the frame,
|
||||
// their order and possible inter-channel decorrelation.
|
||||
Channels: channels,
|
||||
// Sample size in bits-per-sample; a 0 value implies unknown, get sample
|
||||
// size from StreamInfo.
|
||||
BitsPerSample: uint8(bps),
|
||||
// Specifies the frame number if the block size is fixed, and the first
|
||||
// sample number in the frame otherwise. When using fixed block size, the
|
||||
// first sample number in the frame can be derived by multiplying the
|
||||
// frame number with the block size (in samples).
|
||||
//Num // set by encoder.
|
||||
}
|
||||
f := &frame.Frame{
|
||||
Header: hdr,
|
||||
Subframes: subframes,
|
||||
}
|
||||
if err := enc.WriteFrame(f); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChannels returns the channels assignment matching the given number of
|
||||
// channels.
|
||||
func getChannels(nchannels int) (frame.Channels, error) {
|
||||
switch nchannels {
|
||||
case 1:
|
||||
// 1 channel: mono.
|
||||
return frame.ChannelsMono, nil
|
||||
case 2:
|
||||
// 2 channels: left, right.
|
||||
return frame.ChannelsLR, nil
|
||||
//return frame.ChannelsLeftSide, nil // 2 channels: left, side; using inter-channel decorrelation.
|
||||
//return frame.ChannelsSideRight, nil // 2 channels: side, right; using inter-channel decorrelation.
|
||||
//return frame.ChannelsMidSide, nil // 2 channels: mid, side; using inter-channel decorrelation.
|
||||
case 3:
|
||||
// 3 channels: left, right, center.
|
||||
return frame.ChannelsLRC, nil
|
||||
case 4:
|
||||
// 4 channels: left, right, left surround, right surround.
|
||||
return frame.ChannelsLRLsRs, nil
|
||||
case 5:
|
||||
// 5 channels: left, right, center, left surround, right surround.
|
||||
return frame.ChannelsLRCLsRs, nil
|
||||
case 6:
|
||||
// 6 channels: left, right, center, LFE, left surround, right surround.
|
||||
return frame.ChannelsLRCLfeLsRs, nil
|
||||
case 7:
|
||||
// 7 channels: left, right, center, LFE, center surround, side left, side right.
|
||||
return frame.ChannelsLRCLfeCsSlSr, nil
|
||||
case 8:
|
||||
// 8 channels: left, right, center, LFE, left surround, right surround, side left, side right.
|
||||
return frame.ChannelsLRCLfeLsRsSlSr, nil
|
||||
default:
|
||||
return 0, errors.Errorf("support for %d number of channels not yet implemented", nchannels)
|
||||
}
|
||||
}
|
548
enc.go
548
enc.go
|
@ -1,548 +0,0 @@
|
|||
package flac
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"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.
|
||||
|
||||
// TODO: Remove buf when me manage to find a way to flush bits without
|
||||
// closing the underlying writer.
|
||||
|
||||
// Use a temporary buffer to avoid closing the underlying writer when calling
|
||||
// `Close` on the bit writer to flushing pending bits.
|
||||
buf := new(bytes.Buffer)
|
||||
enc := &encoder{bw: bitio.NewWriter(buf)}
|
||||
|
||||
// Store FLAC signature.
|
||||
if _, err := enc.bw.Write(flacSignature); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store the StreamInfo metadata block.
|
||||
infoHdr := meta.Header{
|
||||
IsLast: len(stream.Blocks) == 0,
|
||||
Type: meta.TypeStreamInfo,
|
||||
}
|
||||
if err := enc.writeStreamInfo(infoHdr, stream.Info); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata blocks.
|
||||
for i, block := range stream.Blocks {
|
||||
if block.Type > meta.TypePicture {
|
||||
log.Printf("ignoring metadata block of unknown block type %d", block.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
var err error
|
||||
hdr := block.Header
|
||||
hdr.IsLast = i == len(stream.Blocks)-1
|
||||
switch body := block.Body.(type) {
|
||||
case *meta.Application:
|
||||
err = enc.writeApplication(hdr, body)
|
||||
case *meta.SeekTable:
|
||||
err = enc.writeSeekTable(hdr, body)
|
||||
case *meta.VorbisComment:
|
||||
err = enc.writeVorbisComment(hdr, body)
|
||||
case *meta.CueSheet:
|
||||
err = enc.writeCueSheet(hdr, body)
|
||||
case *meta.Picture:
|
||||
err = enc.writePicture(hdr, body)
|
||||
default:
|
||||
err = enc.writePadding(hdr)
|
||||
}
|
||||
if err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush pending bit writes.
|
||||
if err := enc.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
|
||||
}
|
||||
|
||||
// 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(hdr meta.Header, si *meta.StreamInfo) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
BlockSizeMinBits = 16
|
||||
BlockSizeMaxBits = 16
|
||||
FrameSizeMinBits = 24
|
||||
FrameSizeMaxBits = 24
|
||||
SampleRateBits = 20
|
||||
NChannelsBits = 3
|
||||
BitsPerSampleBits = 5
|
||||
NSamplesBits = 36
|
||||
MD5sumBits = 8 * 16
|
||||
)
|
||||
nbits := int64(BlockSizeMinBits + BlockSizeMaxBits + FrameSizeMinBits +
|
||||
FrameSizeMaxBits + SampleRateBits + NChannelsBits + BitsPerSampleBits +
|
||||
NSamplesBits + MD5sumBits)
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 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(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(hdr meta.Header) error {
|
||||
// Store metadata block header.
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
for i := 0; i < int(hdr.Length); 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(hdr meta.Header, app *meta.Application) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
IDBits = 32
|
||||
)
|
||||
nbits := int64(IDBits + 8*len(app.Data))
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 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(hdr meta.Header, table *meta.SeekTable) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
SampleNumBits = 64
|
||||
OffsetBits = 64
|
||||
NSamplesBits = 16
|
||||
PointBits = SampleNumBits + OffsetBits + NSamplesBits
|
||||
)
|
||||
nbits := int64(PointBits * len(table.Points))
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
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(hdr meta.Header, comment *meta.VorbisComment) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
VendorLenBits = 32
|
||||
NTagsBits = 32
|
||||
)
|
||||
nbits := int64(VendorLenBits + 8*len(comment.Vendor) + NTagsBits)
|
||||
for _, tag := range comment.Tags {
|
||||
const (
|
||||
VectorLenBits = 32
|
||||
EqualBits = 8 * 1
|
||||
)
|
||||
nbits += int64(VectorLenBits + 8*len(tag[0]) + EqualBits + 8*len(tag[1]))
|
||||
}
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 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(hdr meta.Header, cs *meta.CueSheet) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
MCNBits = 8 * 128
|
||||
NLeadInSamplesBits = 64
|
||||
IsCompactDiscBits = 1
|
||||
Reserved1Bits = 7 + 8*258
|
||||
NTracksBits = 8
|
||||
)
|
||||
nbits := int64(MCNBits + NLeadInSamplesBits + IsCompactDiscBits +
|
||||
Reserved1Bits + NTracksBits)
|
||||
for _, track := range cs.Tracks {
|
||||
const (
|
||||
OffsetBits = 64
|
||||
NumBits = 8
|
||||
ISRCBits = 8 * 12
|
||||
IsAudioBits = 1
|
||||
HasPreEmphasisBits = 1
|
||||
Reserved2Bits = 6 + 8*13
|
||||
NIndicesBits = 8
|
||||
)
|
||||
nbits += OffsetBits + NumBits + ISRCBits + IsAudioBits + HasPreEmphasisBits + Reserved2Bits + NIndicesBits
|
||||
for range track.Indicies {
|
||||
const (
|
||||
OffsetBits = 64
|
||||
NumBits = 8
|
||||
Reserved3Bits = 8 * 3
|
||||
)
|
||||
nbits += OffsetBits + NumBits + Reserved3Bits
|
||||
}
|
||||
}
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 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(hdr meta.Header, pic *meta.Picture) error {
|
||||
// Store metadata block header.
|
||||
const (
|
||||
TypeBits = 32
|
||||
MIMELenBits = 32
|
||||
DescLenBits = 32
|
||||
WidthBits = 32
|
||||
HeightBits = 32
|
||||
DepthBits = 32
|
||||
NPalColorsBits = 32
|
||||
DataLenBits = 32
|
||||
)
|
||||
nbits := int64(TypeBits + MIMELenBits + 8*len(pic.MIME) + DescLenBits +
|
||||
8*len(pic.Desc) + WidthBits + HeightBits + DepthBits + NPalColorsBits +
|
||||
DataLenBits + 8*len(pic.Data))
|
||||
hdr.Length = nbits / 8
|
||||
if err := enc.writeBlockHeader(hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 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(pic.Data); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//+build ignore
|
||||
|
||||
package flac_test
|
||||
|
||||
import (
|
||||
|
|
109
encode.go
Normal file
109
encode.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package flac
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/icza/bitio"
|
||||
"github.com/mewkiz/flac/meta"
|
||||
"github.com/mewkiz/pkg/errutil"
|
||||
)
|
||||
|
||||
// An Encoder represents a FLAC encoder.
|
||||
type Encoder struct {
|
||||
// FLAC stream of encoder.
|
||||
*Stream
|
||||
// Underlying io.Writer to the output stream.
|
||||
w io.Writer
|
||||
// io.Closer to flush pending writes to output stream.
|
||||
c io.Closer
|
||||
// Minimum and maximum block size (in samples) of frames written by encoder.
|
||||
blockSizeMin, blockSizeMax uint16
|
||||
// Minimum and maximum frame size (in bytes) of frames written by encoder.
|
||||
frameSizeMin, frameSizeMax uint32
|
||||
// MD5 running hash of unencoded audio samples.
|
||||
md5sum hash.Hash
|
||||
// Total number of samples (per channel) written by encoder.
|
||||
nsamples uint64
|
||||
// Current frame number if block size is fixed, and the first sample number
|
||||
// of the current frame otherwise.
|
||||
curNum uint64
|
||||
}
|
||||
|
||||
// NewEncoder returns a new FLAC encoder for the given metadata StreamInfo block
|
||||
// and optional metadata blocks.
|
||||
func NewEncoder(w io.Writer, info *meta.StreamInfo, blocks ...*meta.Block) (*Encoder, error) {
|
||||
// Store FLAC signature.
|
||||
enc := &Encoder{
|
||||
Stream: &Stream{
|
||||
Info: info,
|
||||
Blocks: blocks,
|
||||
},
|
||||
w: w,
|
||||
md5sum: md5.New(),
|
||||
}
|
||||
if c, ok := w.(io.Closer); ok {
|
||||
enc.c = c
|
||||
}
|
||||
bw := bitio.NewWriter(w)
|
||||
if _, err := bw.Write(flacSignature); err != nil {
|
||||
return nil, errutil.Err(err)
|
||||
}
|
||||
// Encode metadata blocks.
|
||||
// TODO: consider using bufio.NewWriter.
|
||||
if err := encodeStreamInfo(bw, info, len(blocks) == 0); err != nil {
|
||||
return nil, errutil.Err(err)
|
||||
}
|
||||
for i, block := range blocks {
|
||||
if err := encodeBlock(bw, block.Body, i == len(blocks)-1); err != nil {
|
||||
return nil, errutil.Err(err)
|
||||
}
|
||||
}
|
||||
// Flush pending writes of metadata blocks.
|
||||
if _, err := bw.Align(); err != nil {
|
||||
return nil, errutil.Err(err)
|
||||
}
|
||||
// Return encoder to be used for encoding audio samples.
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying io.Writer of the encoder and flushes any pending
|
||||
// writes. If the io.Writer implements io.Seeker, the encoder will update the
|
||||
// StreamInfo metadata block with the MD5 checksum of the unencoded audio
|
||||
// samples, the number of samples, and the minimum and maximum frame size and
|
||||
// block size.
|
||||
func (enc *Encoder) Close() error {
|
||||
// TODO: check if bit writer should be flushed before seeking on enc.w.
|
||||
// Update StreamInfo metadata block.
|
||||
if ws, ok := enc.w.(io.WriteSeeker); ok {
|
||||
if _, err := ws.Seek(int64(len(flacSignature)), io.SeekStart); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// Update minimum and maximum block size (in samples) of FLAC stream.
|
||||
enc.Info.BlockSizeMin = enc.blockSizeMin
|
||||
enc.Info.BlockSizeMax = enc.blockSizeMax
|
||||
// Update minimum and maximum frame size (in bytes) of FLAC stream.
|
||||
enc.Info.FrameSizeMin = enc.frameSizeMin
|
||||
enc.Info.FrameSizeMax = enc.frameSizeMax
|
||||
// Update total number of samples (per channel) of FLAC stream.
|
||||
enc.Info.NSamples = enc.nsamples
|
||||
// Update MD5 checksum of the unencoded audio samples.
|
||||
sum := enc.md5sum.Sum(nil)
|
||||
for i := range sum {
|
||||
enc.Info.MD5sum[i] = sum[i]
|
||||
}
|
||||
bw := bitio.NewWriter(ws)
|
||||
// Write updated StreamInfo metadata block to output stream.
|
||||
if err := encodeStreamInfo(bw, enc.Info, len(enc.Blocks) == 0); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
if _, err := bw.Align(); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
if enc.c != nil {
|
||||
return enc.c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
407
encode_frame.go
Normal file
407
encode_frame.go
Normal file
|
@ -0,0 +1,407 @@
|
|||
package flac
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/icza/bitio"
|
||||
"github.com/mewkiz/flac/frame"
|
||||
"github.com/mewkiz/flac/internal/hashutil/crc16"
|
||||
"github.com/mewkiz/flac/internal/hashutil/crc8"
|
||||
"github.com/mewkiz/flac/internal/utf8"
|
||||
"github.com/mewkiz/pkg/errutil"
|
||||
)
|
||||
|
||||
// --- [ Frame ] ---------------------------------------------------------------
|
||||
|
||||
// WriteFrame encodes the given audio frame to the output stream. The Num field
|
||||
// of the frame header is automatically calculated by the encoder.
|
||||
func (enc *Encoder) WriteFrame(f *frame.Frame) error {
|
||||
// Sanity checks.
|
||||
nchannels := int(enc.Info.NChannels)
|
||||
if nchannels != len(f.Subframes) {
|
||||
return errutil.Newf("subframe and channel count mismatch; expected %d, got %d", nchannels, len(f.Subframes))
|
||||
}
|
||||
nsamplesPerChannel := f.Subframes[0].NSamples
|
||||
if !(16 <= nsamplesPerChannel && nsamplesPerChannel <= 65535) {
|
||||
return errutil.Newf("invalid number of samples per channel; expected >= 16 && <= 65535, got %d", nsamplesPerChannel)
|
||||
}
|
||||
for i, subframe := range f.Subframes {
|
||||
if nsamplesPerChannel != len(subframe.Samples) {
|
||||
return errutil.Newf("invalid number of samples in channel %d; expected %d, got %d", i, nsamplesPerChannel, len(subframe.Samples))
|
||||
}
|
||||
}
|
||||
if nchannels != f.Channels.Count() {
|
||||
return errutil.Newf("channel count mismatch; expected %d, got %d", nchannels, f.Channels.Count())
|
||||
}
|
||||
|
||||
// Create a new CRC-16 hash writer which adds the data from all write
|
||||
// operations to a running hash.
|
||||
h := crc16.NewIBM()
|
||||
hw := io.MultiWriter(h, enc.w)
|
||||
|
||||
// Encode frame header.
|
||||
f.Num = enc.curNum
|
||||
if f.HasFixedBlockSize {
|
||||
enc.curNum++
|
||||
} else {
|
||||
enc.curNum += uint64(nsamplesPerChannel)
|
||||
}
|
||||
enc.nsamples += uint64(nsamplesPerChannel)
|
||||
blockSize := uint16(nsamplesPerChannel)
|
||||
if enc.blockSizeMin == 0 || blockSize < enc.blockSizeMin {
|
||||
enc.blockSizeMin = blockSize
|
||||
}
|
||||
if enc.blockSizeMax == 0 || blockSize > enc.blockSizeMax {
|
||||
enc.blockSizeMax = blockSize
|
||||
}
|
||||
// TODO: track number of bytes written to hw, to update values of
|
||||
// frameSizeMin and frameSizeMax.
|
||||
// Add unencoded audio samples to running MD5 hash.
|
||||
f.Hash(enc.md5sum)
|
||||
if err := enc.encodeFrameHeader(hw, f.Header); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode subframes.
|
||||
bw := bitio.NewWriter(hw)
|
||||
for _, subframe := range f.Subframes {
|
||||
if err := encodeSubframe(bw, f.Header, subframe); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Zero-padding to byte alignment.
|
||||
// Flush pending writes to subframe.
|
||||
if _, err := bw.Align(); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// CRC-16 (polynomial = x^16 + x^15 + x^2 + x^0, initialized with 0) of
|
||||
// everything before the crc, back to and including the frame header sync
|
||||
// code.
|
||||
crc := h.Sum16()
|
||||
if err := binary.Write(enc.w, binary.BigEndian, crc); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ Frame header ] --------------------------------------------------------
|
||||
|
||||
// encodeFrameHeader encodes the given frame header, writing to w.
|
||||
func (enc *Encoder) encodeFrameHeader(w io.Writer, hdr frame.Header) error {
|
||||
// Create a new CRC-8 hash writer which adds the data from all write
|
||||
// operations to a running hash.
|
||||
h := crc8.NewATM()
|
||||
hw := io.MultiWriter(h, w)
|
||||
bw := bitio.NewWriter(hw)
|
||||
enc.c = bw
|
||||
|
||||
// Sync code: 11111111111110
|
||||
if err := bw.WriteBits(0x3FFE, 14); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Reserved: 0
|
||||
if err := bw.WriteBits(0x0, 1); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Blocking strategy:
|
||||
// 0 : fixed-blocksize stream; frame header encodes the frame number
|
||||
// 1 : variable-blocksize stream; frame header encodes the sample number
|
||||
if err := bw.WriteBool(!hdr.HasFixedBlockSize); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode block size.
|
||||
nblockSizeSuffixBits, err := encodeFrameHeaderBlockSize(bw, hdr.BlockSize)
|
||||
if err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode sample rate.
|
||||
sampleRateSuffixBits, nsampleRateSuffixBits, err := encodeFrameHeaderSampleRate(bw, hdr.SampleRate)
|
||||
if err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode channels assignment.
|
||||
if err := encodeFrameHeaderChannels(bw, hdr.Channels); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode bits-per-sample.
|
||||
if err := encodeFrameHeaderBitsPerSample(bw, hdr.BitsPerSample); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Reserved: 0
|
||||
if err := bw.WriteBits(0x0, 1); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// if (variable blocksize)
|
||||
// <8-56>:"UTF-8" coded sample number (decoded number is 36 bits)
|
||||
// else
|
||||
// <8-48>:"UTF-8" coded frame number (decoded number is 31 bits)
|
||||
if err := utf8.Encode(bw, hdr.Num); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Write block size after the frame header (used for uncommon block sizes).
|
||||
if nblockSizeSuffixBits > 0 {
|
||||
// 0110 : get 8 bit (blocksize-1) from end of header
|
||||
// 0111 : get 16 bit (blocksize-1) from end of header
|
||||
if err := bw.WriteBits(uint64(hdr.BlockSize-1), nblockSizeSuffixBits); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write sample rate after the frame header (used for uncommon sample rates).
|
||||
if nsampleRateSuffixBits > 0 {
|
||||
if err := bw.WriteBits(sampleRateSuffixBits, nsampleRateSuffixBits); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush pending writes to frame header.
|
||||
if _, err := bw.Align(); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// CRC-8 (polynomial = x^8 + x^2 + x^1 + x^0, initialized with 0) of
|
||||
// everything before the crc, including the sync code.
|
||||
crc := h.Sum8()
|
||||
if err := binary.Write(w, binary.BigEndian, crc); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ~~~ [ Block size ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// encodeFrameHeaderBlockSize encodes the block size of the frame header,
|
||||
// writing to bw. It returns the number of bits used to store block size after
|
||||
// the frame header.
|
||||
func encodeFrameHeaderBlockSize(bw bitio.Writer, blockSize uint16) (nblockSizeSuffixBits byte, err error) {
|
||||
// Block size in inter-channel samples:
|
||||
// 0000 : reserved
|
||||
// 0001 : 192 samples
|
||||
// 0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608
|
||||
// 0110 : get 8 bit (blocksize-1) from end of header
|
||||
// 0111 : get 16 bit (blocksize-1) from end of header
|
||||
// 1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768
|
||||
var bits uint64
|
||||
switch blockSize {
|
||||
case 192:
|
||||
// 0001
|
||||
bits = 0x1
|
||||
case 576, 1152, 2304, 4608:
|
||||
// 0010-0101 : 576 * (2^(n-2)) samples, i.e. 576/1152/2304/4608
|
||||
bits = 0x2 + uint64(blockSize/576) - 1
|
||||
case 256, 512, 1024, 2048, 4096, 8192, 16384, 32768:
|
||||
// 1000-1111 : 256 * (2^(n-8)) samples, i.e. 256/512/1024/2048/4096/8192/16384/32768
|
||||
bits = 0x8 + uint64(blockSize/256) - 1
|
||||
default:
|
||||
if blockSize <= 256 {
|
||||
// 0110 : get 8 bit (blocksize-1) from end of header
|
||||
bits = 0x6
|
||||
nblockSizeSuffixBits = 8
|
||||
} else {
|
||||
// 0111 : get 16 bit (blocksize-1) from end of header
|
||||
bits = 0x7
|
||||
nblockSizeSuffixBits = 16
|
||||
}
|
||||
}
|
||||
if err := bw.WriteBits(bits, 4); err != nil {
|
||||
return 0, errutil.Err(err)
|
||||
}
|
||||
return nblockSizeSuffixBits, nil
|
||||
}
|
||||
|
||||
// ~~~ [ Sample rate ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// encodeFrameHeaderSampleRate encodes the sample rate of the frame header,
|
||||
// writing to bw. It returns the bits and the number of bits used to store
|
||||
// sample rate after the frame header.
|
||||
func encodeFrameHeaderSampleRate(bw bitio.Writer, sampleRate uint32) (sampleRateSuffixBits uint64, nsampleRateSuffixBits byte, err error) {
|
||||
// Sample rate:
|
||||
// 0000 : get from STREAMINFO metadata block
|
||||
// 0001 : 88.2kHz
|
||||
// 0010 : 176.4kHz
|
||||
// 0011 : 192kHz
|
||||
// 0100 : 8kHz
|
||||
// 0101 : 16kHz
|
||||
// 0110 : 22.05kHz
|
||||
// 0111 : 24kHz
|
||||
// 1000 : 32kHz
|
||||
// 1001 : 44.1kHz
|
||||
// 1010 : 48kHz
|
||||
// 1011 : 96kHz
|
||||
// 1100 : get 8 bit sample rate (in kHz) from end of header
|
||||
// 1101 : get 16 bit sample rate (in Hz) from end of header
|
||||
// 1110 : get 16 bit sample rate (in tens of Hz) from end of header
|
||||
// 1111 : invalid, to prevent sync-fooling string of 1s
|
||||
var bits uint64
|
||||
switch sampleRate {
|
||||
case 0:
|
||||
// 0000 : get from STREAMINFO metadata block
|
||||
bits = 0
|
||||
case 88200:
|
||||
// 0001 : 88.2kHz
|
||||
bits = 0x1
|
||||
case 176400:
|
||||
// 0010 : 176.4kHz
|
||||
bits = 0x2
|
||||
case 192000:
|
||||
// 0011 : 192kHz
|
||||
bits = 0x3
|
||||
case 8000:
|
||||
// 0100 : 8kHz
|
||||
bits = 0x4
|
||||
case 16000:
|
||||
// 0101 : 16kHz
|
||||
bits = 0x5
|
||||
case 22050:
|
||||
// 0110 : 22.05kHz
|
||||
bits = 0x6
|
||||
case 24000:
|
||||
// 0111 : 24kHz
|
||||
bits = 0x7
|
||||
case 32000:
|
||||
// 1000 : 32kHz
|
||||
bits = 0x8
|
||||
case 44100:
|
||||
// 1001 : 44.1kHz
|
||||
bits = 0x9
|
||||
case 48000:
|
||||
// 1010 : 48kHz
|
||||
bits = 0xA
|
||||
case 96000:
|
||||
// 1011 : 96kHz
|
||||
bits = 0xB
|
||||
default:
|
||||
switch {
|
||||
case sampleRate <= 255000 && sampleRate%1000 == 0:
|
||||
// 1100 : get 8 bit sample rate (in kHz) from end of header
|
||||
bits = 0xC
|
||||
sampleRateSuffixBits = uint64(sampleRate / 1000)
|
||||
nsampleRateSuffixBits = 8
|
||||
case sampleRate <= 65535:
|
||||
// 1101 : get 16 bit sample rate (in Hz) from end of header
|
||||
bits = 0xD
|
||||
sampleRateSuffixBits = uint64(sampleRate)
|
||||
nsampleRateSuffixBits = 16
|
||||
case sampleRate <= 655350 && sampleRate%10 == 0:
|
||||
// 1110 : get 16 bit sample rate (in tens of Hz) from end of header
|
||||
bits = 0xE
|
||||
sampleRateSuffixBits = uint64(sampleRate / 10)
|
||||
nsampleRateSuffixBits = 16
|
||||
default:
|
||||
return 0, 0, errutil.Newf("unable to encode sample rate %v", sampleRate)
|
||||
}
|
||||
}
|
||||
if err := bw.WriteBits(bits, 4); err != nil {
|
||||
return 0, 0, errutil.Err(err)
|
||||
}
|
||||
return sampleRateSuffixBits, nsampleRateSuffixBits, nil
|
||||
}
|
||||
|
||||
// ~~~ [ Channels assignment ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// encodeFrameHeaderChannels encodes the channels assignment of the frame
|
||||
// header, writing to bw.
|
||||
func encodeFrameHeaderChannels(bw bitio.Writer, channels frame.Channels) error {
|
||||
// Channel assignment.
|
||||
// 0000-0111 : (number of independent channels)-1. Where defined, the channel order follows SMPTE/ITU-R recommendations. The assignments are as follows:
|
||||
// 1 channel: mono
|
||||
// 2 channels: left, right
|
||||
// 3 channels: left, right, center
|
||||
// 4 channels: front left, front right, back left, back right
|
||||
// 5 channels: front left, front right, front center, back/surround left, back/surround right
|
||||
// 6 channels: front left, front right, front center, LFE, back/surround left, back/surround right
|
||||
// 7 channels: front left, front right, front center, LFE, back center, side left, side right
|
||||
// 8 channels: front left, front right, front center, LFE, back left, back right, side left, side right
|
||||
// 1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel
|
||||
// 1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel
|
||||
// 1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel
|
||||
// 1011-1111 : reserved
|
||||
var bits uint64
|
||||
switch channels {
|
||||
case frame.ChannelsMono, frame.ChannelsLR, frame.ChannelsLRC, frame.ChannelsLRLsRs, frame.ChannelsLRCLsRs, frame.ChannelsLRCLfeLsRs, frame.ChannelsLRCLfeCsSlSr, frame.ChannelsLRCLfeLsRsSlSr:
|
||||
// 1 channel: mono.
|
||||
// 2 channels: left, right.
|
||||
// 3 channels: left, right, center.
|
||||
// 4 channels: left, right, left surround, right surround.
|
||||
// 5 channels: left, right, center, left surround, right surround.
|
||||
// 6 channels: left, right, center, LFE, left surround, right surround.
|
||||
// 7 channels: left, right, center, LFE, center surround, side left, side right.
|
||||
// 8 channels: left, right, center, LFE, left surround, right surround, side left, side right.
|
||||
bits = uint64(channels.Count() - 1)
|
||||
case frame.ChannelsLeftSide:
|
||||
// 2 channels: left, side; using inter-channel decorrelation.
|
||||
// 1000 : left/side stereo: channel 0 is the left channel, channel 1 is the side(difference) channel
|
||||
bits = 0x8
|
||||
case frame.ChannelsSideRight:
|
||||
// 2 channels: side, right; using inter-channel decorrelation.
|
||||
// 1001 : right/side stereo: channel 0 is the side(difference) channel, channel 1 is the right channel
|
||||
bits = 0x9
|
||||
case frame.ChannelsMidSide:
|
||||
// 2 channels: mid, side; using inter-channel decorrelation.
|
||||
// 1010 : mid/side stereo: channel 0 is the mid(average) channel, channel 1 is the side(difference) channel
|
||||
bits = 0xA
|
||||
default:
|
||||
return errutil.Newf("support for channel assignment %v not yet implemented", channels)
|
||||
}
|
||||
if err := bw.WriteBits(bits, 4); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ~~~ [ Bits-per-sample ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// encodeFrameHeaderBitsPerSample encodes the bits-per-sample of the frame
|
||||
// header, writing to bw.
|
||||
func encodeFrameHeaderBitsPerSample(bw bitio.Writer, bps uint8) error {
|
||||
// Sample size in bits:
|
||||
// 000 : get from STREAMINFO metadata block
|
||||
// 001 : 8 bits per sample
|
||||
// 010 : 12 bits per sample
|
||||
// 011 : reserved
|
||||
// 100 : 16 bits per sample
|
||||
// 101 : 20 bits per sample
|
||||
// 110 : 24 bits per sample
|
||||
// 111 : reserved
|
||||
var bits uint64
|
||||
switch bps {
|
||||
case 0:
|
||||
// 000 : get from STREAMINFO metadata block
|
||||
bits = 0x0
|
||||
case 8:
|
||||
// 001 : 8 bits per sample
|
||||
bits = 0x1
|
||||
case 12:
|
||||
// 010 : 12 bits per sample
|
||||
bits = 0x2
|
||||
case 16:
|
||||
// 100 : 16 bits per sample
|
||||
bits = 0x4
|
||||
case 20:
|
||||
// 101 : 20 bits per sample
|
||||
bits = 0x5
|
||||
case 24:
|
||||
// 110 : 24 bits per sample
|
||||
bits = 0x6
|
||||
default:
|
||||
return errutil.Newf("support for sample size %v not yet implemented", bps)
|
||||
}
|
||||
if err := bw.WriteBits(bits, 3); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
376
encode_meta.go
Normal file
376
encode_meta.go
Normal file
|
@ -0,0 +1,376 @@
|
|||
package flac
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/icza/bitio"
|
||||
"github.com/mewkiz/flac/internal/ioutilx"
|
||||
"github.com/mewkiz/flac/meta"
|
||||
"github.com/mewkiz/pkg/errutil"
|
||||
)
|
||||
|
||||
// --- [ Metadata block ] ------------------------------------------------------
|
||||
|
||||
// encodeBlock encodes the metadata block, writing to bw.
|
||||
func encodeBlock(bw bitio.Writer, body interface{}, last bool) error {
|
||||
switch body := body.(type) {
|
||||
case *meta.StreamInfo:
|
||||
return encodeStreamInfo(bw, body, last)
|
||||
case *meta.Application:
|
||||
return encodeApplication(bw, body, last)
|
||||
case *meta.SeekTable:
|
||||
return encodeSeekTable(bw, body, last)
|
||||
case *meta.VorbisComment:
|
||||
return encodeVorbisComment(bw, body, last)
|
||||
case *meta.CueSheet:
|
||||
return encodeCueSheet(bw, body, last)
|
||||
case *meta.Picture:
|
||||
return encodePicture(bw, body, last)
|
||||
default:
|
||||
panic(fmt.Errorf("support for metadata block body type %T not yet implemented", body))
|
||||
}
|
||||
}
|
||||
|
||||
// --- [ Metadata block header ] -----------------------------------------------
|
||||
|
||||
// encodeBlockHeader encodes the metadata block header, writing to bw.
|
||||
func encodeBlockHeader(bw bitio.Writer, hdr *meta.Header) error {
|
||||
// 1 bit: IsLast.
|
||||
if err := bw.WriteBool(hdr.IsLast); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 7 bits: Type.
|
||||
if err := bw.WriteBits(uint64(hdr.Type), 7); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 24 bits: Length.
|
||||
if err := bw.WriteBits(uint64(hdr.Length), 24); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ StreamInfo ] ----------------------------------------------------------
|
||||
|
||||
// encodeStreamInfo encodes the StreamInfo metadata block, writing to bw.
|
||||
func encodeStreamInfo(bw bitio.Writer, info *meta.StreamInfo, last bool) error {
|
||||
// Store metadata block header.
|
||||
const nbits = 16 + 16 + 24 + 24 + 20 + 3 + 5 + 36 + 8*16
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeStreamInfo,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 16 bits: BlockSizeMin.
|
||||
if err := bw.WriteBits(uint64(info.BlockSizeMin), 16); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 16 bits: BlockSizeMax.
|
||||
if err := bw.WriteBits(uint64(info.BlockSizeMax), 16); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 24 bits: FrameSizeMin.
|
||||
if err := bw.WriteBits(uint64(info.FrameSizeMin), 24); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 24 bits: FrameSizeMax.
|
||||
if err := bw.WriteBits(uint64(info.FrameSizeMax), 24); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 20 bits: SampleRate.
|
||||
if err := bw.WriteBits(uint64(info.SampleRate), 20); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 3 bits: NChannels; stored as (number of channels) - 1.
|
||||
if err := bw.WriteBits(uint64(info.NChannels-1), 3); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 5 bits: BitsPerSample; stored as (bits-per-sample) - 1.
|
||||
if err := bw.WriteBits(uint64(info.BitsPerSample-1), 5); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 36 bits: NSamples.
|
||||
if err := bw.WriteBits(info.NSamples, 36); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 16 bytes: MD5sum.
|
||||
if _, err := bw.Write(info.MD5sum[:]); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ Application ] ---------------------------------------------------------
|
||||
|
||||
// encodeApplication encodes the Application metadata block, writing to bw.
|
||||
func encodeApplication(bw bitio.Writer, app *meta.Application, last bool) error {
|
||||
// Store metadata block header.
|
||||
nbits := int64(32 + 8*len(app.Data))
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeApplication,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 32 bits: ID.
|
||||
if err := bw.WriteBits(uint64(app.ID), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// TODO: check if the Application block may contain only an ID.
|
||||
if _, err := bw.Write(app.Data); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ SeekTable ] -----------------------------------------------------------
|
||||
|
||||
// encodeSeekTable encodes the SeekTable metadata block, writing to bw.
|
||||
func encodeSeekTable(bw bitio.Writer, table *meta.SeekTable, last bool) error {
|
||||
// Store metadata block header.
|
||||
nbits := int64((64 + 64 + 16) * len(table.Points))
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeSeekTable,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
for _, point := range table.Points {
|
||||
if err := binary.Write(bw, binary.BigEndian, point); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ VorbisComment ] -------------------------------------------------------
|
||||
|
||||
// encodeVorbisComment encodes the VorbisComment metadata block, writing to bw.
|
||||
func encodeVorbisComment(bw bitio.Writer, comment *meta.VorbisComment, last bool) error {
|
||||
// Store metadata block header.
|
||||
nbits := int64(32 + 8*len(comment.Vendor) + 32)
|
||||
for _, tag := range comment.Tags {
|
||||
nbits += int64(32 + 8*(len(tag[0])+1+len(tag[1])))
|
||||
}
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeVorbisComment,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 32 bits: vendor length.
|
||||
// TODO: verify that little-endian encoding is used; otherwise, switch to
|
||||
// using bw.WriteBits.
|
||||
if err := binary.Write(bw, binary.LittleEndian, uint32(len(comment.Vendor))); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// (vendor length) bits: Vendor.
|
||||
if _, err := bw.Write([]byte(comment.Vendor)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// Store tags.
|
||||
// 32 bits: number of tags.
|
||||
if err := binary.Write(bw, binary.LittleEndian, uint32(len(comment.Tags))); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
for _, tag := range comment.Tags {
|
||||
// Store tag, which has the following format:
|
||||
// NAME=VALUE
|
||||
buf := []byte(fmt.Sprintf("%s=%s", tag[0], tag[1]))
|
||||
// 32 bits: vector length
|
||||
if err := binary.Write(bw, binary.LittleEndian, uint32(len(buf))); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// (vector length): vector.
|
||||
if _, err := bw.Write(buf); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ CueSheet ] ------------------------------------------------------------
|
||||
|
||||
// encodeCueSheet encodes the CueSheet metadata block, writing to bw.
|
||||
func encodeCueSheet(bw bitio.Writer, cs *meta.CueSheet, last bool) error {
|
||||
// Store metadata block header.
|
||||
nbits := int64(8*128 + 64 + 1 + 7 + 8*258 + 8)
|
||||
for _, track := range cs.Tracks {
|
||||
nbits += 64 + 8 + 8*12 + 1 + 1 + 6 + 8*13 + 8
|
||||
for range track.Indicies {
|
||||
nbits += 64 + 8 + 8*3
|
||||
}
|
||||
}
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeCueSheet,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// Store cue sheet.
|
||||
// 128 bytes: MCN.
|
||||
var mcn [128]byte
|
||||
copy(mcn[:], cs.MCN)
|
||||
if _, err := bw.Write(mcn[:]); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 64 bits: NLeadInSamples.
|
||||
if err := bw.WriteBits(cs.NLeadInSamples, 64); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 1 bit: IsCompactDisc.
|
||||
if err := bw.WriteBool(cs.IsCompactDisc); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 7 bits and 258 bytes: reserved.
|
||||
if err := bw.WriteBits(0, 7); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
if _, err := io.CopyN(bw, ioutilx.Zero, 258); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// Store cue sheet tracks.
|
||||
// 8 bits: (number of tracks)
|
||||
if err := bw.WriteBits(uint64(len(cs.Tracks)), 8); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
for _, track := range cs.Tracks {
|
||||
// 64 bits: Offset.
|
||||
if err := bw.WriteBits(track.Offset, 64); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 8 bits: Num.
|
||||
if err := bw.WriteBits(uint64(track.Num), 8); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 12 bytes: ISRC.
|
||||
var isrc [12]byte
|
||||
copy(isrc[:], track.ISRC)
|
||||
if _, err := bw.Write(isrc[:]); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 1 bit: IsAudio.
|
||||
if err := bw.WriteBool(!track.IsAudio); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 1 bit: HasPreEmphasis.
|
||||
// mask = 01000000
|
||||
if err := bw.WriteBool(track.HasPreEmphasis); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 6 bits and 13 bytes: reserved.
|
||||
// mask = 00111111
|
||||
if err := bw.WriteBits(0, 6); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
if _, err := io.CopyN(bw, ioutilx.Zero, 13); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// Store indicies.
|
||||
// 8 bits: (number of indicies)
|
||||
if err := bw.WriteBits(uint64(len(track.Indicies)), 8); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
for _, index := range track.Indicies {
|
||||
// 64 bits: Offset.
|
||||
if err := bw.WriteBits(index.Offset, 64); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 8 bits: Num.
|
||||
if err := bw.WriteBits(uint64(index.Num), 8); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 3 bytes: reserved.
|
||||
if _, err := io.CopyN(bw, ioutilx.Zero, 3); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ Picture ] -------------------------------------------------------------
|
||||
|
||||
// encodePicture encodes the Picture metadata block, writing to bw.
|
||||
func encodePicture(bw bitio.Writer, pic *meta.Picture, last bool) error {
|
||||
// Store metadata block header.
|
||||
nbits := int64(32 + 32 + 8*len(pic.MIME) + 32 + 8*len(pic.Desc) + 32 + 32 + 32 + 32 + 32 + 8*len(pic.Data))
|
||||
hdr := &meta.Header{
|
||||
IsLast: last,
|
||||
Type: meta.TypeCueSheet,
|
||||
Length: nbits / 8,
|
||||
}
|
||||
if err := encodeBlockHeader(bw, hdr); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store metadata block body.
|
||||
// 32 bits: Type.
|
||||
if err := bw.WriteBits(uint64(pic.Type), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: (MIME type length).
|
||||
if err := bw.WriteBits(uint64(len(pic.MIME)), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// (MIME type length) bytes: MIME.
|
||||
if _, err := bw.Write([]byte(pic.MIME)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: (description length).
|
||||
if err := bw.WriteBits(uint64(len(pic.Desc)), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// (description length) bytes: Desc.
|
||||
if _, err := bw.Write([]byte(pic.Desc)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: Width.
|
||||
if err := bw.WriteBits(uint64(pic.Width), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: Height.
|
||||
if err := bw.WriteBits(uint64(pic.Height), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: Depth.
|
||||
if err := bw.WriteBits(uint64(pic.Depth), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: NPalColors.
|
||||
if err := bw.WriteBits(uint64(pic.NPalColors), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// 32 bits: (data length).
|
||||
if err := bw.WriteBits(uint64(len(pic.Data)), 32); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
// (data length) bytes: Data.
|
||||
if _, err := bw.Write(pic.Data); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
110
encode_subframe.go
Normal file
110
encode_subframe.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package flac
|
||||
|
||||
import (
|
||||
"github.com/icza/bitio"
|
||||
"github.com/mewkiz/flac/frame"
|
||||
iobits "github.com/mewkiz/flac/internal/bits"
|
||||
"github.com/mewkiz/pkg/errutil"
|
||||
)
|
||||
|
||||
// --- [ Subframe ] ------------------------------------------------------------
|
||||
|
||||
// encodeSubframe encodes the given subframe, writing to bw.
|
||||
func encodeSubframe(bw bitio.Writer, hdr frame.Header, subframe *frame.Subframe) error {
|
||||
// Encode subframe header.
|
||||
if err := encodeSubframeHeader(bw, subframe.SubHeader); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Encode audio samples.
|
||||
switch subframe.Pred {
|
||||
//case frame.PredConstant:
|
||||
// if err := encodeConstantSamples(bw, samples); err != nil {
|
||||
// return errutil.Err(err)
|
||||
// }
|
||||
case frame.PredVerbatim:
|
||||
if err := encodeVerbatimSamples(bw, hdr, subframe.Samples); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
//case frame.PredFixed:
|
||||
// if err := encodeFixedSamples(bw, samples, subframe.Order); err != nil {
|
||||
// return errutil.Err(err)
|
||||
// }
|
||||
//case frame.PredFIR:
|
||||
// if err := encodeFIRSamples(bw, samples, subframe.Order); err != nil {
|
||||
// return errutil.Err(err)
|
||||
// }
|
||||
default:
|
||||
return errutil.Newf("support for prediction method %v not yet implemented", subframe.Pred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ Subframe header ] -----------------------------------------------------
|
||||
|
||||
// encodeSubframeHeader encodes the given subframe header, writing to bw.
|
||||
func encodeSubframeHeader(bw bitio.Writer, hdr frame.SubHeader) error {
|
||||
// Zero bit padding, to prevent sync-fooling string of 1s.
|
||||
if err := bw.WriteBits(0x0, 1); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Subframe type:
|
||||
// 000000 : SUBFRAME_CONSTANT
|
||||
// 000001 : SUBFRAME_VERBATIM
|
||||
// 00001x : reserved
|
||||
// 0001xx : reserved
|
||||
// 001xxx : if(xxx <= 4) SUBFRAME_FIXED, xxx=order ; else reserved
|
||||
// 01xxxx : reserved
|
||||
// 1xxxxx : SUBFRAME_LPC, xxxxx=order-1
|
||||
var bits uint64
|
||||
switch hdr.Pred {
|
||||
case frame.PredConstant:
|
||||
// 000000 : SUBFRAME_CONSTANT
|
||||
bits = 0x00
|
||||
case frame.PredVerbatim:
|
||||
// 000001 : SUBFRAME_VERBATIM
|
||||
bits = 0x01
|
||||
case frame.PredFixed:
|
||||
// 001xxx : if(xxx <= 4) SUBFRAME_FIXED, xxx=order ; else reserved
|
||||
bits = 0x08 | uint64(hdr.Order)
|
||||
case frame.PredFIR:
|
||||
// 1xxxxx : SUBFRAME_LPC, xxxxx=order-1
|
||||
bits = 0x20 | uint64(hdr.Order-1)
|
||||
}
|
||||
if err := bw.WriteBits(bits, 6); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// <1+k> 'Wasted bits-per-sample' flag:
|
||||
//
|
||||
// 0 : no wasted bits-per-sample in source subblock, k=0
|
||||
// 1 : k wasted bits-per-sample in source subblock, k-1 follows, unary coded; e.g. k=3 => 001 follows, k=7 => 0000001 follows.
|
||||
hasWastedBits := hdr.Wasted > 0
|
||||
if err := bw.WriteBool(hasWastedBits); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
if hasWastedBits {
|
||||
if err := iobits.WriteUnary(bw, uint64(hdr.Wasted)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- [ Verbatim samples ] ----------------------------------------------------
|
||||
|
||||
// encodeVerbatimSamples stores the given samples verbatim (uncompressed),
|
||||
// writing to bw.
|
||||
func encodeVerbatimSamples(bw bitio.Writer, hdr frame.Header, samples []int32) error {
|
||||
// Unencoded subblock; n = frame's bits-per-sample, i = frame's blocksize.
|
||||
if int(hdr.BlockSize) != len(samples) {
|
||||
return errutil.Newf("block size and sample count mismatch; expected %d, got %d", hdr.BlockSize, len(samples))
|
||||
}
|
||||
for _, sample := range samples {
|
||||
if err := bw.WriteBits(uint64(sample), hdr.BitsPerSample); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/mewkiz/flac/internal/hashutil"
|
||||
"github.com/mewkiz/flac/internal/hashutil/crc16"
|
||||
"github.com/mewkiz/flac/internal/hashutil/crc8"
|
||||
"github.com/mewkiz/flac/internal/utf8"
|
||||
)
|
||||
|
||||
// A Frame contains the header and subframes of an audio frame. It holds the
|
||||
|
@ -137,7 +138,7 @@ func (frame *Frame) Parse() error {
|
|||
}
|
||||
got := frame.crc.Sum16()
|
||||
if got != want {
|
||||
log.Printf("frame.Frame.Parse: CRC-16 checksum mismatch; expected 0x%04X, got 0x%04X", want, got)
|
||||
return fmt.Errorf("frame.Frame.Parse: CRC-16 checksum mismatch; expected 0x%04X, got 0x%04X", want, got)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -285,7 +286,7 @@ func (frame *Frame) parseHeader() error {
|
|||
// 1-6 bytes: UTF-8 encoded frame number.
|
||||
// else
|
||||
// 1-7 bytes: UTF-8 encoded sample number.
|
||||
frame.Num, err = decodeUTF8Int(hr)
|
||||
frame.Num, err = utf8.Decode(hr)
|
||||
if err != nil {
|
||||
return unexpected(err)
|
||||
}
|
||||
|
@ -307,7 +308,7 @@ func (frame *Frame) parseHeader() error {
|
|||
}
|
||||
got := h.Sum8()
|
||||
if want != got {
|
||||
log.Printf("frame.Frame.parseHeader: CRC-8 checksum mismatch; expected 0x%02X, got 0x%02X", want, got)
|
||||
return fmt.Errorf("frame.Frame.parseHeader: CRC-8 checksum mismatch; expected 0x%02X, got 0x%02X", want, got)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -55,7 +55,6 @@ func (frame *Frame) parseSubframe(br *bits.Reader, bps uint) (subframe *Subframe
|
|||
for i, sample := range subframe.Samples {
|
||||
subframe.Samples[i] = sample << subframe.Wasted
|
||||
}
|
||||
|
||||
return subframe, err
|
||||
}
|
||||
|
||||
|
@ -461,6 +460,9 @@ func (subframe *Subframe) decodeLPC(coeffs []int32, shift int32) error {
|
|||
if shift < 0 {
|
||||
return fmt.Errorf("frame.Subframe.decodeLPC: invalid negative shift")
|
||||
}
|
||||
if subframe.NSamples != len(subframe.Samples) {
|
||||
return fmt.Errorf("frame.Subframe.decodeLPC: subframe sample count mismatch; expected %d, got %d", subframe.NSamples, len(subframe.Samples))
|
||||
}
|
||||
for i := subframe.Order; i < subframe.NSamples; i++ {
|
||||
var sample int64
|
||||
for j, c := range coeffs {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package bits
|
||||
|
||||
import (
|
||||
"github.com/icza/bitio"
|
||||
)
|
||||
|
||||
// ReadUnary decodes and returns an unary coded integer, whose value is
|
||||
// represented by the number of leading zeros before a one.
|
||||
//
|
||||
|
@ -25,3 +29,27 @@ func (br *Reader) ReadUnary() (x uint64, err error) {
|
|||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// WriteUnary encodes x as an unary coded integer, whose value is represented by
|
||||
// the number of leading zeros before a one.
|
||||
//
|
||||
// Examples of unary coded binary on the left and decoded decimal on the right:
|
||||
//
|
||||
// 0 => 1
|
||||
// 1 => 01
|
||||
// 2 => 001
|
||||
// 3 => 0001
|
||||
// 4 => 00001
|
||||
// 5 => 000001
|
||||
// 6 => 0000001
|
||||
func WriteUnary(bw bitio.Writer, x uint64) error {
|
||||
bits := uint64(1)
|
||||
n := byte(1)
|
||||
for ; x > 0; x-- {
|
||||
n++
|
||||
}
|
||||
if err := bw.WriteBits(bits, n); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
24
internal/ioutilx/byte.go
Normal file
24
internal/ioutilx/byte.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Package ioutilx implements extended input/output utility functions.
|
||||
package ioutilx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadByte reads and returns the next byte from r.
|
||||
func ReadByte(r io.Reader) (byte, error) {
|
||||
var buf [1]byte
|
||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return buf[0], nil
|
||||
}
|
||||
|
||||
// WriteByte writes the given byte to w.
|
||||
func WriteByte(w io.Writer, b byte) error {
|
||||
buf := [1]byte{b}
|
||||
if _, err := w.Write(buf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
17
internal/ioutilx/zero.go
Normal file
17
internal/ioutilx/zero.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package ioutilx
|
||||
|
||||
// Zero is an io.Reader which always reads zero bytes.
|
||||
var Zero zero
|
||||
|
||||
// zero is an io.Reader which always reads zero bytes.
|
||||
type zero struct {
|
||||
}
|
||||
|
||||
// Read reads len(b) zero bytes into b. It returns the number of bytes read and
|
||||
// a nil error value.
|
||||
func (zero) Read(b []byte) (n int, err error) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
package frame
|
||||
// Package utf8 implements encoding and decoding of UTF-8 coded numbers.
|
||||
package utf8
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mewkiz/flac/internal/ioutilx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,9 +32,10 @@ const (
|
|||
rune4Max = 1<<21 - 1
|
||||
rune5Max = 1<<26 - 1
|
||||
rune6Max = 1<<31 - 1
|
||||
rune7Max = 1<<36 - 1
|
||||
)
|
||||
|
||||
// decodeUTF8Int decodes a "UTF-8" coded number and returns it.
|
||||
// Decode decodes a "UTF-8" coded number and returns it.
|
||||
//
|
||||
// ref: http://permalink.gmane.org/gmane.comp.audio.compression.flac.devel/3033
|
||||
//
|
||||
|
@ -54,8 +58,8 @@ const (
|
|||
// - if B does not match 10xxxxxx, the encoding is invalid
|
||||
// - set R = R or <the lower 6 bits from B>
|
||||
// - the read value is R
|
||||
func decodeUTF8Int(r io.Reader) (n uint64, err error) {
|
||||
c0, err := readByte(r)
|
||||
func Decode(r io.Reader) (x uint64, err error) {
|
||||
c0, err := ioutilx.ReadByte(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -80,38 +84,38 @@ func decodeUTF8Int(r io.Reader) (n uint64, err error) {
|
|||
// if c0 == 110xxxxx
|
||||
// total: 11 bits (5 + 6)
|
||||
l = 1
|
||||
n = uint64(c0 & mask2)
|
||||
x = uint64(c0 & mask2)
|
||||
case c0 < t4:
|
||||
// if c0 == 1110xxxx
|
||||
// total: 16 bits (4 + 6 + 6)
|
||||
l = 2
|
||||
n = uint64(c0 & mask3)
|
||||
x = uint64(c0 & mask3)
|
||||
case c0 < t5:
|
||||
// if c0 == 11110xxx
|
||||
// total: 21 bits (3 + 6 + 6 + 6)
|
||||
l = 3
|
||||
n = uint64(c0 & mask4)
|
||||
x = uint64(c0 & mask4)
|
||||
case c0 < t6:
|
||||
// if c0 == 111110xx
|
||||
// total: 26 bits (2 + 6 + 6 + 6 + 6)
|
||||
l = 4
|
||||
n = uint64(c0 & mask5)
|
||||
x = uint64(c0 & mask5)
|
||||
case c0 < t7:
|
||||
// if c0 == 1111110x
|
||||
// total: 31 bits (1 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 5
|
||||
n = uint64(c0 & mask6)
|
||||
x = uint64(c0 & mask6)
|
||||
case c0 < t8:
|
||||
// if c0 == 11111110
|
||||
// total: 36 bits (0 + 6 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 6
|
||||
n = 0
|
||||
x = 0
|
||||
}
|
||||
|
||||
// store bits from continuation bytes.
|
||||
for i := 0; i < l; i++ {
|
||||
n <<= 6
|
||||
c, err := readByte(r)
|
||||
x <<= 6
|
||||
c, err := ioutilx.ReadByte(r)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
|
@ -122,46 +126,35 @@ func decodeUTF8Int(r io.Reader) (n uint64, err error) {
|
|||
// if c != 10xxxxxx
|
||||
return 0, errors.New("frame.decodeUTF8Int: expected continuation byte")
|
||||
}
|
||||
n |= uint64(c & maskx)
|
||||
x |= uint64(c & maskx)
|
||||
}
|
||||
|
||||
// check if number representation is larger than necessary.
|
||||
switch l {
|
||||
case 1:
|
||||
if n <= rune1Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune1Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
case 2:
|
||||
if n <= rune2Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune2Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
case 3:
|
||||
if n <= rune3Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune3Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
case 4:
|
||||
if n <= rune4Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune4Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
case 5:
|
||||
if n <= rune5Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune5Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
case 6:
|
||||
if n <= rune6Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; n (%d) stored in %d bytes, could be stored in %d bytes", n, l+1, l)
|
||||
if x <= rune6Max {
|
||||
return 0, fmt.Errorf("frame.decodeUTF8Int: larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// readByte reads and returns the next byte from the provided io.Reader.
|
||||
func readByte(r io.Reader) (c byte, err error) {
|
||||
buf := make([]byte, 1)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return buf[0], nil
|
||||
return x, nil
|
||||
}
|
72
internal/utf8/encode.go
Normal file
72
internal/utf8/encode.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package utf8
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/mewkiz/flac/internal/ioutilx"
|
||||
"github.com/mewkiz/pkg/errutil"
|
||||
)
|
||||
|
||||
// Encode encodes x as a "UTF-8" coded number.
|
||||
func Encode(w io.Writer, x uint64) error {
|
||||
// 1-byte, 7-bit sequence?
|
||||
if x <= rune1Max {
|
||||
if err := ioutilx.WriteByte(w, byte(x)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get number of continuation bytes and store bits of c0.
|
||||
var (
|
||||
// number of continuation bytes.,
|
||||
l int
|
||||
// bits of c0.
|
||||
bits uint64
|
||||
)
|
||||
switch {
|
||||
case x <= rune2Max:
|
||||
// if c0 == 110xxxxx
|
||||
// total: 11 bits (5 + 6)
|
||||
l = 1
|
||||
bits = t2 | (x>>6)&mask2
|
||||
case x <= rune3Max:
|
||||
// if c0 == 1110xxxx
|
||||
// total: 16 bits (4 + 6 + 6)
|
||||
l = 2
|
||||
bits = t3 | (x>>(6*2))&mask3
|
||||
case x <= rune4Max:
|
||||
// if c0 == 11110xxx
|
||||
// total: 21 bits (3 + 6 + 6 + 6)
|
||||
l = 3
|
||||
bits = t4 | (x>>(6*3))&mask4
|
||||
case x <= rune5Max:
|
||||
// if c0 == 111110xx
|
||||
// total: 26 bits (2 + 6 + 6 + 6 + 6)
|
||||
l = 4
|
||||
bits = t5 | (x>>(6*4))&mask5
|
||||
case x <= rune6Max:
|
||||
// if c0 == 1111110x
|
||||
// total: 31 bits (1 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 5
|
||||
bits = t6 | (x>>(6*5))&mask6
|
||||
case x <= rune7Max:
|
||||
// if c0 == 11111110
|
||||
// total: 36 bits (0 + 6 + 6 + 6 + 6 + 6 + 6)
|
||||
l = 6
|
||||
bits = 0
|
||||
}
|
||||
// Store bits of c0.
|
||||
if err := ioutilx.WriteByte(w, byte(bits)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
|
||||
// Store continuation bytes.
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
bits := tx | (x>>uint(6*i))&maskx
|
||||
if err := ioutilx.WriteByte(w, byte(bits)); err != nil {
|
||||
return errutil.Err(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue