flacgo/encode.go

110 lines
3.3 KiB
Go
Raw Normal View History

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
2018-08-18 18:18:12 +00:00
package flac
import (
"crypto/md5"
"hash"
"io"
2022-07-16 22:07:10 +00:00
"git.gammaspectra.live/S.O.N.G/flacgo/meta"
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
2018-08-18 18:18:12 +00:00
"github.com/icza/bitio"
"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
}