Pure go FLAC packetizer doing minimal stream decoding, allow offseting packets.
All checks were successful
continuous-integration/drone/push Build is passing

This removes the libflac / flacgo packetizers
This commit is contained in:
DataHoarder 2022-07-27 03:04:02 +02:00
parent 041e51b106
commit 4073f8f0d7
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
9 changed files with 1230 additions and 157 deletions

View file

@ -27,15 +27,15 @@ Collection of audio utilities for decoding/encoding/processing files and streams
Only output from Kirika's own encoders is supported.
| Container | Packetizer | Keep Mode | Sample Numbers | Offset | Notes |
|:---------:|:----------:|:---------:|:--------------:|:------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **FLAC** | ✅ | ✅ | ✅ | ❌ | Uses [libFLAC](https://github.com/xiph/flac) via [goflac](https://git.gammaspectra.live/S.O.N.G/goflac) for parsing streams.<br/>If not available, [flacgo](https://git.gammaspectra.live/S.O.N.G/flacgo) will be used. |
| **TTA** | ❌ | - | - | - | |
| **MP3** | ✅ | ✅ | ✅ | ✅ | Uses [sssgun/mp3](https://github.com/sssgun/mp3) as a frame parser. |
| **Ogg** | ✅ | ✅ | ✅* | ✅ | *Sample numbers (absolute granule position in Ogg) depend on underlying codec implementing it.<br/>Has been tested as working for Opus |
| **ADTS** | ✅ | ✅ | ✅ | ✅ | Uses [edgeware/mp4ff](https://github.com/edgeware/mp4ff) for its ADTS frame parser. |
| **MP4** | ❌ | - | - | - | |
| **ADIF** | ❌ | - | - | - | |
| Container | Packetizer | Keep Mode | Sample Numbers | Offset | Notes |
|:---------:|:----------:|:---------:|:--------------:|:------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **FLAC** | ✅ | ✅ | ✅ | ✅ | Uses custom Kirika packetizer. |
| **TTA** | ❌ | - | - | - | |
| **MP3** | ✅ | ✅ | ✅ | ✅ | Uses [sssgun/mp3](https://github.com/sssgun/mp3) as a frame parser. |
| **Ogg** | ✅ | ✅ | ✅* | ✅ | Uses custom Kirika packetizer.<br/>*Sample numbers (absolute granule position in Ogg) depend on underlying codec implementing it.<br/>Has been tested as working for Opus |
| **ADTS** | ✅ | ✅ | ✅ | ✅ | Uses [edgeware/mp4ff](https://github.com/edgeware/mp4ff) for its ADTS frame parser. |
| **MP4** | ❌ | - | - | - | |
| **ADIF** | ❌ | - | - | - | |
## Dependencies
### Go >= 1.18
@ -136,9 +136,9 @@ If this tag is enabled, yet `aac` support remains enabled, an AAC encoder (but n
### disable_codec_libflac
This tag disables the [libFLAC](https://gitlab.xiph.org/xiph/flac) support for decoding/encoding/packetizing FLAC.
This tag disables the [libFLAC](https://gitlab.xiph.org/xiph/flac) support for decoding/encoding FLAC.
If this tag is enabled, yet `flac` support remains enabled, [flacgo](https://git.gammaspectra.live/S.O.N.G/flacgo) FLAC decoder (but not encoder) will be used.
If this tag is enabled, yet `flac` support remains enabled, [flacgo](https://git.gammaspectra.live/S.O.N.G/flacgo) FLAC decoder/encoder will be used.
### disable_codec_lame
This tag disables the [LAME](https://github.com/viert/go-lame) support for encoding MP3. This is available for specific problems with the LGPL v2 license that conflicts with your needs or policy.

View file

@ -6,13 +6,11 @@ import (
"bytes"
"errors"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
libflac "git.gammaspectra.live/S.O.N.G/flacgo"
"git.gammaspectra.live/S.O.N.G/flacgo/frame"
"git.gammaspectra.live/S.O.N.G/flacgo/meta"
"io"
"log"
)
type Format struct {
@ -156,31 +154,61 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
}
}
for block := range filter.NewBlockSizeFilter(int(blockSize)).Process(source).ToInt32(int(bitsPerSample)).GetBlocks() {
buf := make([]int32, 0, blockSize)
minBlockSize := len(block) / channels.Count()
if uint16(minBlockSize) > blockSize {
log.Panicf("invalid block size %d > %d", minBlockSize, blockSize)
//Uses verbatim samples + constant. No savings will be had, but it's a proper FLAC
for block := range source.ToInt32(int(bitsPerSample)).GetBlocks() {
buf = append(buf, block...)
for len(buf) >= int(blockSize)*ccount {
for i := 0; i < ccount; i++ {
//De-interleave samples
for j := 0; j < int(blockSize); j++ {
subframes[i].Samples[j] = buf[j*ccount+i]
}
}
err = encoder.WriteFrame(&frame.Frame{
Header: frame.Header{
HasFixedBlockSize: true,
BlockSize: blockSize,
SampleRate: sampleRate,
Channels: channels,
BitsPerSample: bitsPerSample,
},
Subframes: subframes,
})
if err != nil {
return err
}
buf = buf[int(blockSize)*ccount:]
}
for i := 0; i < ccount; i++ {
//Uses verbatim samples + constant. No savings will be had, but it's a proper FLAC
if len(buf) == 0 {
//free
buf = nil
}
}
//TODO: blockSize will pad the end, is it allowed to make it smaller?
if len(buf) > 0 {
for i := 0; i < ccount; i++ {
//De-interleave samples
for j := 0; j < minBlockSize; j++ {
subframes[i].Samples[j] = block[j*ccount+i]
}
//zero remaining, if any
for j := uint16(minBlockSize); j < blockSize; j++ {
subframes[i].Samples[j] = 0
for j := 0; j < len(buf)/ccount; j++ {
subframes[i].Samples[j] = buf[j*ccount+i]
}
subframes[i].Samples = subframes[i].Samples[0 : len(buf)/ccount]
subframes[i].NSamples = len(subframes[i].Samples)
}
err = encoder.WriteFrame(&frame.Frame{
Header: frame.Header{
HasFixedBlockSize: true,
BlockSize: blockSize,
BlockSize: uint16(subframes[0].NSamples),
SampleRate: sampleRate,
Channels: channels,
BitsPerSample: bitsPerSample,

View file

@ -2,14 +2,651 @@ package packetizer
import (
"bufio"
"bytes"
"errors"
"fmt"
"github.com/icza/bitio"
"io"
)
func NewFLACPacketizer(reader io.Reader) *FLACPacketizer {
return &FLACPacketizer{br: bitio.NewReader(bufio.NewReaderSize(reader, 4096))}
}
type FLACHeaderPacket struct {
MinimumBlockSize uint16
MaximumBlockSize uint16
MinimumFrameSize uint32
MaximumFrameSize uint32
SampleRate uint32
ChannelCount uint8
BitsPerSample uint8
TotalSamples uint64
Hash [128 / 8]byte
MetadataBlocks [][]byte
buf []byte
}
func (p *FLACHeaderPacket) KeepMode() KeepMode {
return Keep
}
func (p *FLACHeaderPacket) GetStartSampleNumber() int64 {
return 0
}
func (p *FLACHeaderPacket) GetEndSampleNumber() int64 {
return 0
}
func (p *FLACHeaderPacket) GetData() []byte {
data := bytes.NewBuffer([]byte{})
buf := bitio.NewWriter(data)
if _, err := buf.Write([]byte("fLaC")); err != nil {
return nil
}
for _, b := range p.MetadataBlocks {
if buf.WriteByte(b[0]) != nil {
return nil
}
if err := buf.WriteBits(uint64(len(b)-1), 24); err != nil {
return nil
}
if _, err := buf.Write(b[1:]); err != nil {
return nil
}
}
buf.Align()
return data.Bytes()
}
func (p *FLACHeaderPacket) Category() int64 {
return 0
}
const invalidMetadataBlock = 127
func readFlacMetadataBlock(br *bitio.Reader) (block byte, data []byte) {
var err error
if block, err = br.ReadByte(); err != nil {
return invalidMetadataBlock, nil
}
var lengthInBytes uint64
if lengthInBytes, err = br.ReadBits(24); err != nil {
return invalidMetadataBlock, nil
}
data = make([]byte, lengthInBytes)
if _, err = io.ReadFull(br, data); err != nil {
return invalidMetadataBlock, nil
}
return
}
func readFlacHeader(br *bitio.Reader) *FLACHeaderPacket {
if n, err := br.ReadBits(32); err != nil || n != 0x664C6143 {
return nil
}
block, data := readFlacMetadataBlock(br)
if (block&0x7F) != 0 || len(data) < ((16+16+24+24+20+3+5+36+128)/8) { //StreamInfo
return nil
}
hdr := &FLACHeaderPacket{
buf: make([]byte, 0, 4096),
}
hdr.MetadataBlocks = append(hdr.MetadataBlocks, append([]byte{block}, data...))
sbr := bitio.NewReader(bytes.NewBuffer(data))
var n uint64
n, _ = sbr.ReadBits(16)
hdr.MinimumBlockSize = uint16(n)
n, _ = sbr.ReadBits(16)
hdr.MaximumBlockSize = uint16(n)
n, _ = sbr.ReadBits(24)
hdr.MinimumFrameSize = uint32(n)
n, _ = sbr.ReadBits(24)
hdr.MaximumFrameSize = uint32(n)
n, _ = sbr.ReadBits(20)
hdr.SampleRate = uint32(n)
n, _ = sbr.ReadBits(3)
hdr.ChannelCount = uint8(n) + 1
n, _ = sbr.ReadBits(5)
hdr.BitsPerSample = uint8(n) + 1
_, _ = sbr.Read(hdr.Hash[:])
for {
if (block & (1 << 7)) > 0 { //if last
break
}
block, data = readFlacMetadataBlock(br)
if block == invalidMetadataBlock {
return nil
}
hdr.MetadataBlocks = append(hdr.MetadataBlocks, append([]byte{block}, data...))
}
return hdr
}
func readFlacFrame(hdr *FLACHeaderPacket, br *bitio.Reader) *FLACPacket {
if sync, err := br.ReadBits(14); err != nil || sync != 0b11111111111110 {
return nil
}
if reserved, err := br.ReadBits(1); err != nil || reserved != 0 {
return nil
}
p := &FLACPacket{
hdr: hdr,
mode: Discard,
}
var err error
if p.BlockingStrategy, err = br.ReadBits(1); err != nil {
return nil
}
if p.BlockSizeRaw, err = br.ReadBits(4); err != nil {
return nil
}
if p.SampleRateRaw, err = br.ReadBits(4); err != nil {
return nil
}
if p.ChannelAssignment, err = br.ReadBits(4); err != nil {
return nil
}
if p.BitsPerSample, err = br.ReadBits(3); err != nil {
return nil
}
if reserved, err := br.ReadBits(1); err != nil || reserved != 0 {
return nil
}
if p.Number, err = func(r io.ByteReader) (x uint64, err error) {
const (
tx = 0x80 // 1000 0000
t2 = 0xC0 // 1100 0000
t3 = 0xE0 // 1110 0000
t4 = 0xF0 // 1111 0000
t5 = 0xF8 // 1111 1000
t6 = 0xFC // 1111 1100
t7 = 0xFE // 1111 1110
t8 = 0xFF // 1111 1111
maskx = 0x3F // 0011 1111
mask2 = 0x1F // 0001 1111
mask3 = 0x0F // 0000 1111
mask4 = 0x07 // 0000 0111
mask5 = 0x03 // 0000 0011
mask6 = 0x01 // 0000 0001
rune1Max = 1<<7 - 1
rune2Max = 1<<11 - 1
rune3Max = 1<<16 - 1
rune4Max = 1<<21 - 1
rune5Max = 1<<26 - 1
rune6Max = 1<<31 - 1
rune7Max = 1<<36 - 1
)
c0, err := r.ReadByte()
if err != nil {
return 0, err
}
// 1-byte, 7-bit sequence?
if c0 < tx {
// if c0 == 0xxxxxxx
// total: 7 bits (7)
return uint64(c0), nil
}
// unexpected continuation byte?
if c0 < t2 {
// if c0 == 10xxxxxx
return 0, errors.New("unexpected continuation byte")
}
// get number of continuation bytes and store bits from c0.
var l int
switch {
case c0 < t3:
// if c0 == 110xxxxx
// total: 11 bits (5 + 6)
l = 1
x = uint64(c0 & mask2)
case c0 < t4:
// if c0 == 1110xxxx
// total: 16 bits (4 + 6 + 6)
l = 2
x = uint64(c0 & mask3)
case c0 < t5:
// if c0 == 11110xxx
// total: 21 bits (3 + 6 + 6 + 6)
l = 3
x = uint64(c0 & mask4)
case c0 < t6:
// if c0 == 111110xx
// total: 26 bits (2 + 6 + 6 + 6 + 6)
l = 4
x = uint64(c0 & mask5)
case c0 < t7:
// if c0 == 1111110x
// total: 31 bits (1 + 6 + 6 + 6 + 6 + 6)
l = 5
x = uint64(c0 & mask6)
case c0 < t8:
// if c0 == 11111110
// total: 36 bits (0 + 6 + 6 + 6 + 6 + 6 + 6)
l = 6
x = 0
}
// store bits from continuation bytes.
for i := 0; i < l; i++ {
x <<= 6
c, err := r.ReadByte()
if err != nil {
if err == io.EOF {
return 0, io.ErrUnexpectedEOF
}
return 0, err
}
if c < tx || t2 <= c {
// if c != 10xxxxxx
return 0, errors.New("expected continuation byte")
}
x |= uint64(c & maskx)
}
// check if number representation is larger than necessary.
switch l {
case 1:
if x <= rune1Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
case 2:
if x <= rune2Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
case 3:
if x <= rune3Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
case 4:
if x <= rune4Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
case 5:
if x <= rune5Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
case 6:
if x <= rune6Max {
return 0, fmt.Errorf("larger number representation than necessary; x (%d) stored in %d bytes, could be stored in %d bytes", x, l+1, l)
}
}
return x, nil
}(br); err != nil {
return nil
}
if p.BlockSizeRaw == 0x6 {
if p.BlockSizeExtra, err = br.ReadBits(8); err != nil {
return nil
}
} else if p.BlockSizeRaw == 0x7 {
if p.BlockSizeExtra, err = br.ReadBits(16); err != nil {
return nil
}
}
if p.SampleRateRaw == 0xC {
if p.SampleRateExtra, err = br.ReadBits(8); err != nil {
return nil
}
} else if p.SampleRateRaw == 0xD || p.SampleRateRaw == 0xE {
if p.SampleRateExtra, err = br.ReadBits(16); err != nil {
return nil
}
}
if p.CRC8, err = br.ReadBits(8); err != nil {
return nil
}
channelCount := int(p.ChannelAssignment) + 1
if p.ChannelAssignment == 0x8 || p.ChannelAssignment == 0x9 || p.ChannelAssignment == 0xA {
channelCount = 2
}
blockSize := int(p.getBlockSize())
buf := bytes.NewBuffer(hdr.buf[:0])
bw := bitio.NewWriter(buf)
var padding, subframeType, wastedBitsFlag, wastedBits uint64
//TODO: remove error checking here, given we get proper input and a check will happen later
for channelNumber := 0; channelNumber < channelCount; channelNumber++ {
bps := p.getBitsPerSample()
if p.ChannelAssignment == 0x9 && channelNumber == 0 {
bps++
} else if (p.ChannelAssignment == 0x8 || p.ChannelAssignment == 0xA) && channelNumber == 1 {
bps++
}
if padding, err = br.ReadBits(1); err != nil {
return nil
}
if subframeType, err = br.ReadBits(6); err != nil {
return nil
}
if wastedBitsFlag, err = br.ReadBits(1); err != nil {
return nil
}
if wastedBitsFlag == 1 {
if wastedBits, err = readUnary(br); err != nil {
return nil
}
} else {
wastedBits = 0
}
_ = bw.WriteBits(padding, 1)
_ = bw.WriteBits(subframeType, 6)
_ = bw.WriteBits(wastedBitsFlag, 1)
if wastedBitsFlag == 1 {
_ = writeUnary(bw, wastedBits)
bps -= uint8(wastedBits) + 1
}
switch {
case subframeType == 0: //Constant
var constantSample uint64
if constantSample, err = br.ReadBits(bps); err != nil {
return nil
}
_ = bw.WriteBits(constantSample, bps)
case subframeType == 1: //Verbatim
var verbatimData []byte
if verbatimData, err = readManyBits(br, blockSize*int(bps)); err != nil {
return nil
}
_ = writeManyBits(bw, verbatimData, blockSize*int(bps))
case subframeType < 8: //reserved
return nil
case subframeType < 16: //Fixed
order := int(subframeType & 0x07)
if order > 4 {
return nil
}
var warmupData []byte
if warmupData, err = readManyBits(br, order*int(bps)); err != nil {
return nil
}
_ = writeManyBits(bw, warmupData, order*int(bps))
if err = decodeResidual(br, bw, blockSize, order); err != nil {
return nil
}
case subframeType < 32: //reserved
return nil
default: //FIR
order := int(subframeType&0x1F) + 1
var warmupData []byte
if warmupData, err = readManyBits(br, order*int(bps)); err != nil {
return nil
}
_ = writeManyBits(bw, warmupData, order*int(bps))
var coefficientPrecisionLen, quantizationLevel uint64
if coefficientPrecisionLen, err = br.ReadBits(4); err != nil {
return nil
}
_ = bw.WriteBits(coefficientPrecisionLen, 4)
if coefficientPrecisionLen == 0xF {
return nil
}
precision := int(coefficientPrecisionLen) + 1
if quantizationLevel, err = br.ReadBits(5); err != nil {
return nil
}
_ = bw.WriteBits(quantizationLevel, 5)
var coefficientData []byte
if coefficientData, err = readManyBits(br, order*precision); err != nil {
return nil
}
_ = writeManyBits(bw, coefficientData, order*precision)
if err = decodeResidual(br, bw, blockSize, order); err != nil {
return nil
}
}
}
if n := br.Align(); n > 0 {
_ = bw.WriteBits(0, n)
if n, err = bw.Align(); err != nil || n != 0 {
//Alignment mismatch!
return nil
}
}
p.FrameData = buf.Bytes()
if p.CRC16, err = br.ReadBits(16); err != nil {
return nil
}
return p
}
func decodeResidual(br *bitio.Reader, bw *bitio.Writer, blockSize, predictorOrder int) (err error) {
var entropyCodingMethod, partitionOrder uint64
if entropyCodingMethod, err = br.ReadBits(2); err != nil {
return err
}
_ = bw.WriteBits(entropyCodingMethod, 2)
paramSize := 0
switch entropyCodingMethod {
case 0x0:
paramSize = 4
case 0x1:
paramSize = 5
default:
return errors.New("wrong method")
}
if partitionOrder, err = br.ReadBits(4); err != nil {
return err
}
_ = bw.WriteBits(partitionOrder, 4)
partitions := 1 << partitionOrder
var riceParameter uint64
for partition := 0; partition < partitions; partition++ {
if riceParameter, err = br.ReadBits(uint8(paramSize)); err != nil {
return err
}
_ = bw.WriteBits(riceParameter, uint8(paramSize))
var nsamples int
if partition != 0 {
nsamples = blockSize / partitions
} else {
nsamples = blockSize/partitions - predictorOrder
}
if (paramSize == 4 && riceParameter == 0xF) || (paramSize == 5 && riceParameter == 0x1F) {
// 1111 or 11111: Escape code, meaning the partition is in unencoded
// binary form using riceParameter2 bits per sample; riceParameter2 follows as a 5-bit number.
var riceParameter2 uint64
if riceParameter2, err = br.ReadBits(5); err != nil {
return err
}
_ = bw.WriteBits(riceParameter2, 5)
if riceParameter2 > 0 {
var sampleData []byte
if sampleData, err = readManyBits(br, nsamples*int(riceParameter2)); err != nil {
return err
}
_ = writeManyBits(bw, sampleData, nsamples*int(riceParameter2))
}
} else {
//TODO: optimize this!
var high, low uint64
if riceParameter == 0 {
for j := 0; j < nsamples; j++ {
if high, err = readUnary(br); err != nil {
return err
}
_ = writeUnary(bw, high)
}
} else {
for j := 0; j < nsamples; j++ {
if high, err = readUnary(br); err != nil {
return err
}
_ = writeUnary(bw, high)
if low, err = br.ReadBits(uint8(riceParameter)); err != nil {
return err
}
_ = bw.WriteBits(low, uint8(riceParameter))
}
}
}
}
return nil
}
func decodeRicePart(br *bitio.Reader, paramSize int) {
}
func readManyBits(br *bitio.Reader, bits int) (data []byte, err error) {
dataLen := bits / 8
if (bits % 8) != 0 {
dataLen++
}
data = make([]byte, dataLen)
for i := 0; i < (bits / 8); i++ {
if data[i], err = br.ReadByte(); err != nil {
return nil, err
}
}
if (bits % 8) != 0 {
var b uint64
if b, err = br.ReadBits(uint8(bits % 8)); err != nil {
return nil, err
}
data[len(data)-1] = byte(b)
}
return
}
func writeManyBits(bw *bitio.Writer, data []byte, bits int) (err error) {
for i := 0; i < (bits / 8); i++ {
if err = bw.WriteByte(data[i]); err != nil {
return err
}
}
if (bits % 8) != 0 {
if err = bw.WriteBits(uint64(data[(bits/8)]), uint8(bits%8)); err != nil {
return err
}
}
return
}
func readUnary(br *bitio.Reader) (x uint64, err error) {
for {
bit, err := br.ReadBits(1)
if err != nil {
return 0, err
}
if bit == 1 {
break
}
x++
}
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
}
type FLACPacket struct {
mode KeepMode
sampleNumber int64
endSampleNumber int64
data []byte
mode KeepMode
hdr *FLACHeaderPacket
BlockingStrategy uint64
BlockSizeRaw uint64
SampleRateRaw uint64
ChannelAssignment uint64
BitsPerSample uint64
Number uint64
BlockSizeExtra uint64
SampleRateExtra uint64
CRC8 uint64
FrameData []byte
CRC16 uint64
}
func (p *FLACPacket) KeepMode() KeepMode {
@ -17,48 +654,310 @@ func (p *FLACPacket) KeepMode() KeepMode {
}
func (p *FLACPacket) GetStartSampleNumber() int64 {
return p.sampleNumber
if p.BlockingStrategy == 0 { //fixed-blocksize
return int64(p.Number * uint64(p.getBlockSize()))
}
return int64(p.Number)
}
func (p *FLACPacket) GetEndSampleNumber() int64 {
return p.endSampleNumber
return p.GetStartSampleNumber() + int64(p.getBlockSize())
}
func (p *FLACPacket) GetData() []byte {
return p.data
return p.GetDataOffset(0)
}
func (p *FLACPacket) Category() int64 {
return 0
}
func NewFLACPacketizer(reader io.Reader) *FLACPacketizer {
return &FLACPacketizer{reader: bufio.NewReader(reader)}
}
func (p *FLACPacket) getBlockSize() uint16 {
n := p.BlockSizeRaw
x := p.BlockSizeExtra
func (o *FLACPacketizer) Read(p []byte) (n int, err error) {
n, err = o.reader.Read(p)
if n > 0 {
o.buffer = append(o.buffer, p[:n]...)
switch {
case n == 0x0:
// 0000: reserved.
return 0
case n == 0x1:
// 0001: 192 samples.
return 192
case n >= 0x2 && n <= 0x5:
// 0010-0101: 576 * 2^(n-2) samples.
return 576 * (1 << (n - 2))
case n == 0x6:
// 0110: get 8 bit (block size)-1 from the end of the header.
return uint16(x + 1)
case n == 0x7:
// 0111: get 16 bit (block size)-1 from the end of the header.
return uint16(x + 1)
default:
// 1000-1111: 256 * 2^(n-8) samples.
return 256 * (1 << (n - 8))
}
o.bytesRead += int64(n)
return
}
func (o *FLACPacketizer) Seek(offset int64, whence int) (int64, error) {
if offset == 0 && whence == io.SeekCurrent {
return o.bytesRead, nil
func (p *FLACPacket) getSampleRate() uint32 {
switch p.SampleRateRaw {
case 0x0:
// 0000: unknown sample rate; get from StreamInfo.
return p.hdr.SampleRate
case 0x1:
// 0001: 88.2 kHz.
return 88200
case 0x2:
// 0010: 176.4 kHz.
return 176400
case 0x3:
// 0011: 192 kHz.
return 192000
case 0x4:
// 0100: 8 kHz.
return 8000
case 0x5:
// 0101: 16 kHz.
return 16000
case 0x6:
// 0110: 22.05 kHz.
return 22050
case 0x7:
// 0111: 24 kHz.
return 24000
case 0x8:
// 1000: 32 kHz.
return 32000
case 0x9:
// 1001: 44.1 kHz.
return 44100
case 0xA:
// 1010: 48 kHz.
return 48000
case 0xB:
// 1011: 96 kHz.
return 96000
case 0xC:
// 1100: get 8 bit sample rate (in kHz) from the end of the header.
return uint32(p.SampleRateExtra * 1000)
case 0xD:
// 1101: get 16 bit sample rate (in Hz) from the end of the header.
return uint32(p.SampleRateExtra)
case 0xE:
// 1110: get 16 bit sample rate (in daHz) from the end of the header.
return uint32(p.SampleRateExtra * 10)
default:
// 1111: invalid.
return 0
}
return 0, io.ErrNoProgress
}
func (o *FLACPacketizer) Close() error {
return nil
func (p *FLACPacket) getBitsPerSample() uint8 {
switch p.BitsPerSample {
case 0x0:
// 000: unknown bits-per-sample; get from StreamInfo.
return p.hdr.BitsPerSample
case 0x1:
// 001: 8 bits-per-sample.
return 8
case 0x2:
// 010: 12 bits-per-sample.
return 12
case 0x4:
// 100: 16 bits-per-sample.
return 16
case 0x5:
// 101: 20 bits-per-sample.
return 20
case 0x6:
// 110: 24 bits-per-sample.
return 24
default:
// 011: reserved.
// 111: reserved.
return 0
}
}
func (o *FLACPacketizer) getBuffer() (buf []byte) {
buf = make([]byte, len(o.buffer))
copy(buf, o.buffer)
o.buffer = o.buffer[:0]
return
func (p *FLACPacket) GetDataOffset(offset int64) []byte {
//TODO: reserve proper amount
buf := bytes.NewBuffer(make([]byte, 0, len(p.FrameData)+64))
bw := bitio.NewWriter(buf)
//sync
_ = bw.WriteBits(0b11111111111110, 14)
//reserved
_ = bw.WriteBits(0, 1)
_ = bw.WriteBits(p.BlockingStrategy, 1)
_ = bw.WriteBits(p.BlockSizeRaw, 4)
_ = bw.WriteBits(p.SampleRateRaw, 4)
_ = bw.WriteBits(p.ChannelAssignment, 4)
_ = bw.WriteBits(p.BitsPerSample, 3)
//reserved
_ = bw.WriteBits(0, 1)
number := int64(p.Number)
if offset != 0 {
if p.BlockingStrategy == 1 {
number += offset
if number < 0 {
return nil
}
} else {
number += offset / int64(p.getBlockSize())
if number < 0 {
return nil
}
}
}
var err error
if err = func(w io.ByteWriter, x uint64) error {
const (
tx = 0x80 // 1000 0000
t2 = 0xC0 // 1100 0000
t3 = 0xE0 // 1110 0000
t4 = 0xF0 // 1111 0000
t5 = 0xF8 // 1111 1000
t6 = 0xFC // 1111 1100
t7 = 0xFE // 1111 1110
t8 = 0xFF // 1111 1111
maskx = 0x3F // 0011 1111
mask2 = 0x1F // 0001 1111
mask3 = 0x0F // 0000 1111
mask4 = 0x07 // 0000 0111
mask5 = 0x03 // 0000 0011
mask6 = 0x01 // 0000 0001
rune1Max = 1<<7 - 1
rune2Max = 1<<11 - 1
rune3Max = 1<<16 - 1
rune4Max = 1<<21 - 1
rune5Max = 1<<26 - 1
rune6Max = 1<<31 - 1
rune7Max = 1<<36 - 1
)
// 1-byte, 7-bit sequence?
if x <= rune1Max {
if err := w.WriteByte(byte(x)); err != nil {
return 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 := w.WriteByte(byte(bits)); err != nil {
return err
}
// Store continuation bytes.
for i := l - 1; i >= 0; i-- {
bits = tx | (x>>uint(6*i))&maskx
if err := w.WriteByte(byte(bits)); err != nil {
return err
}
}
return nil
}(bw, uint64(number)); err != nil {
return nil
}
if p.BlockSizeRaw == 0x6 {
_ = bw.WriteBits(p.BlockSizeExtra, 8)
} else if p.BlockSizeRaw == 0x7 {
_ = bw.WriteBits(p.BlockSizeExtra, 16)
}
if p.SampleRateRaw == 0xC {
_ = bw.WriteBits(p.SampleRateExtra, 8)
} else if p.SampleRateRaw == 0xD || p.SampleRateRaw == 0xE {
_ = bw.WriteBits(p.SampleRateExtra, 16)
}
_, _ = bw.Align()
_ = bw.WriteBits(uint64(CRC8ChecksumATM(buf.Bytes())), 8)
_, _ = bw.Write(p.FrameData)
_, _ = bw.Align()
_ = bw.WriteBits(uint64(CRC16ChecksumIBM(buf.Bytes())), 16)
return buf.Bytes()
}
type FLACPacketizer struct {
br *bitio.Reader
hdr *FLACHeaderPacket
offset int64
samples int64
bytesRead int64
buffer []byte
}
func (o *FLACPacketizer) GetPacket() Packet {
if o.hdr == nil {
o.hdr = readFlacHeader(o.br)
if o.hdr == nil {
return nil
}
return o.hdr
}
packet := readFlacFrame(o.hdr, o.br)
if packet == nil {
return nil
}
return packet
}

View file

@ -1,50 +0,0 @@
//go:build !disable_format_flac && !disable_codec_libflac && cgo
package packetizer
import (
libflac "git.gammaspectra.live/S.O.N.G/goflac"
"io"
)
type FLACPacketizer struct {
reader io.Reader
stream *libflac.Decoder
offset int64
samples int64
bytesRead int64
buffer []byte
}
func (o *FLACPacketizer) GetPacket() Packet {
var err error
if o.stream == nil {
o.stream, err = libflac.NewDecoderReader(o)
if err != nil {
return nil
}
return &FLACPacket{
mode: Keep,
sampleNumber: 0,
endSampleNumber: 0,
data: o.getBuffer(),
}
}
frame, err := o.stream.ReadFrame()
if err != nil {
return nil
}
sampleNumber := o.samples
nsamples := int64(len(frame.Buffer)) / int64(frame.Channels)
o.samples += nsamples
return &FLACPacket{
mode: Discard,
sampleNumber: sampleNumber,
endSampleNumber: sampleNumber + nsamples,
data: o.getBuffer(),
}
}

View file

@ -0,0 +1,200 @@
package packetizer
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This implements the 8-bit cyclic redundancy check, or CRC-8,
// checksum. See http://en.wikipedia.org/wiki/Cyclic_redundancy_check and
// http://www.ross.net/crc/download/crc_v3.txt for information.
// CRC8Size of a CRC-8 checksum in bytes.
const CRC8Size = 1
// Predefined polynomials.
const (
CRC8ATM = 0x07 // x^8 + x^2 + x + 1
)
// CRC8Table is a 256-word table representing the polynomial for efficient
// processing.
type CRC8Table [256]uint8
// CRC8ATMTable is the table for the CRC8ATM polynomial.
var CRC8ATMTable = makeTableCRC8(CRC8ATM)
// makeTableCRC8 returns the CRC8Table constructed from the specified polynomial.
func makeTableCRC8(poly uint8) (table *CRC8Table) {
table = new(CRC8Table)
for i := range table {
crc := uint8(i)
for j := 0; j < 8; j++ {
if crc&0x80 != 0 {
crc = crc<<1 ^ poly
} else {
crc <<= 1
}
}
table[i] = crc
}
return table
}
// crc16Digest represents the partial evaluation of a checksum.
type crc8Digest struct {
crc uint8
table *CRC8Table
}
// NewCRC8 creates a new hash computing the CRC-8 checksum using the
// polynomial represented by the CRC8Table.
func NewCRC8(table *CRC8Table) *crc8Digest {
return &crc8Digest{0, table}
}
// NewCRC8ATM creates a new hashutil.Hash8 computing the CRC-8 checksum using the
// CRC8ATM polynomial.
func NewCRC8ATM() *crc8Digest {
return NewCRC8(CRC8ATMTable)
}
func (d *crc8Digest) Size() int {
return CRC8Size
}
func (d *crc8Digest) BlockSize() int {
return 1
}
func (d *crc8Digest) Reset() {
d.crc = 0
}
// CRC8Update returns the result of adding the bytes in p to the crc.
func CRC8Update(crc uint8, table *CRC8Table, p []byte) uint8 {
for _, v := range p {
crc = table[crc^v]
}
return crc
}
func (d *crc8Digest) Write(p []byte) (n int, err error) {
d.crc = CRC8Update(d.crc, d.table, p)
return len(p), nil
}
// Sum8 returns the 8-bit checksum of the hash.
func (d *crc8Digest) Sum8() uint8 {
return d.crc
}
func (d *crc8Digest) Sum(in []byte) []byte {
return append(in, d.crc)
}
// CRC8Checksum returns the CRC-8 checksum of data, using the polynomial represented
// by the CRC8Table.
func CRC8Checksum(data []byte, table *CRC8Table) uint8 {
return CRC8Update(0, table, data)
}
// CRC8ChecksumATM returns the CRC-8 checksum of data using the CRC8ATM polynomial.
func CRC8ChecksumATM(data []byte) uint8 {
return CRC8Update(0, CRC8ATMTable, data)
}
// CRC16Size of a CRC-16 checksum in bytes.
const CRC16Size = 2
// Predefined polynomials.
const (
CRC16IBM = 0x8005 // x^16 + x^15 + x^2 + x^0
)
// CRC16Table is a 256-word table representing the polynomial for efficient
// processing.
type CRC16Table [256]uint16
// CRC16IBMTable is the table for the CRC16IBM polynomial.
var CRC16IBMTable = makeTablecrc16(CRC16IBM)
// makeTablecrc16 returns the CRC16Table constructed from the specified polynomial.
func makeTablecrc16(poly uint16) (table *CRC16Table) {
table = new(CRC16Table)
for i := range table {
crc := uint16(i << 8)
for j := 0; j < 8; j++ {
if crc&0x8000 != 0 {
crc = crc<<1 ^ poly
} else {
crc <<= 1
}
}
table[i] = crc
}
return table
}
// crc16Digest represents the partial evaluation of a checksum.
type crc16Digest struct {
crc uint16
table *CRC16Table
}
// NewCRC16 creates a new hash computing the CRC-16 checksum using the
// polynomial represented by the CRC16Table.
func NewCRC16(table *CRC16Table) *crc16Digest {
return &crc16Digest{0, table}
}
// NewCRC16IBM creates a new hash computing the CRC-16 checksum using the
// CRC16IBM polynomial.
func NewCRC16IBM() *crc16Digest {
return NewCRC16(CRC16IBMTable)
}
func (d *crc16Digest) Size() int {
return CRC16Size
}
func (d *crc16Digest) BlockSize() int {
return 1
}
func (d *crc16Digest) Reset() {
d.crc = 0
}
// CRC16Update returns the result of adding the bytes in p to the crc.
func CRC16Update(crc uint16, table *CRC16Table, p []byte) uint16 {
for _, v := range p {
crc = crc<<8 ^ table[crc>>8^uint16(v)]
}
return crc
}
func (d *crc16Digest) Write(p []byte) (n int, err error) {
d.crc = CRC16Update(d.crc, d.table, p)
return len(p), nil
}
// Sum16 returns the 16-bit checksum of the hash.
func (d *crc16Digest) Sum16() uint16 {
return d.crc
}
func (d *crc16Digest) Sum(in []byte) []byte {
s := d.Sum16()
return append(in, byte(s>>8), byte(s))
}
// CRC16Checksum returns the CRC-16 checksum of data, using the polynomial
// represented by the CRC16Table.
func CRC16Checksum(data []byte, table *CRC16Table) uint16 {
return CRC16Update(0, table, data)
}
// CRC16ChecksumIBM returns the CRC-16 checksum of data using the CRC16IBM polynomial.
func CRC16ChecksumIBM(data []byte) uint16 {
return CRC16Update(0, CRC16IBMTable, data)
}

View file

@ -1,50 +0,0 @@
//go:build disable_format_flac || disable_codec_libflac || !cgo
package packetizer
import (
flac_parser "git.gammaspectra.live/S.O.N.G/flacgo"
"io"
)
type FLACPacketizer struct {
reader io.Reader
stream *flac_parser.Stream
offset int64
samples int64
bytesRead int64
buffer []byte
}
func (o *FLACPacketizer) GetPacket() Packet {
var err error
if o.stream == nil {
o.stream, err = flac_parser.NewSeek(o)
if err != nil {
return nil
}
return &FLACPacket{
mode: Keep,
sampleNumber: 0,
endSampleNumber: 0,
data: o.getBuffer(),
}
}
frame, err := o.stream.ParseNext()
if err != nil {
return nil
}
sampleNumber := int64(frame.Num)
if frame.HasFixedBlockSize {
sampleNumber *= int64(frame.BlockSize)
}
return &FLACPacket{
mode: Discard,
sampleNumber: sampleNumber,
endSampleNumber: sampleNumber + int64(frame.Subframes[0].NSamples),
data: o.getBuffer(),
}
}

View file

@ -1,4 +1,4 @@
//go:build !disable_format_flac && !disable_codec_libflac && cgo
//go:build !disable_format_flac
package packetizer
@ -63,3 +63,49 @@ func TestPacketizeFLAC(t *testing.T) {
t.Errorf("Mismatch on byte output %X != %X", shaPacketHasher.Sum([]byte{}), shaHasher.Sum([]byte{}))
}
}
func TestPacketizeFLACOffset(t *testing.T) {
t.Parallel()
fp, err := os.Open(test.TestSingleSample24)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, err := flac.NewFormat().Open(fp)
if err != nil {
t.Error(err)
return
}
reader, writer := io.Pipe()
go func() {
defer writer.Close()
err = flac.NewFormat().Encode(source, writer, nil)
if err != nil {
t.Error(err)
return
}
}()
pipePacketizer := NewFLACPacketizer(reader)
packetCount := 0
packetBytes := 0
for {
packet := pipePacketizer.GetPacket()
if packet == nil {
break
}
packetCount++
if offsetablePacket, ok := packet.(OffsetablePacket); ok {
packetBytes += len(offsetablePacket.GetDataOffset(4096))
}
}
if packetCount != 4231 {
t.Errorf("Wrong Packet Count %d != %d", packetCount, 4231)
}
if packetBytes < 50000000 {
t.Errorf("Wrong Packet Bytes %d < %d", packetBytes, 50000000)
}
}

View file

@ -93,7 +93,7 @@ func TestPacketizeOggFLACOffset(t *testing.T) {
}
packetCount++
if offsetablePacket, ok := packet.(OffsetablePacket); ok {
packetBytes += len(offsetablePacket.GetDataOffset(1000))
packetBytes += len(offsetablePacket.GetDataOffset(4096))
}
}
if packetCount != 1059 {

2
go.mod
View file

@ -14,6 +14,7 @@ require (
github.com/dh1tw/gosamplerate v0.1.2
github.com/edgeware/mp4ff v0.29.0
github.com/hajimehoshi/go-mp3 v0.3.3
github.com/icza/bitio v1.1.0
github.com/jfreymuth/oggvorbis v1.0.3
github.com/kvark128/minimp3 v0.0.0-20220408223524-dd428325fce7
github.com/minio/sha256-simd v1.0.0
@ -23,7 +24,6 @@ require (
)
require (
github.com/icza/bitio v1.1.0 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.4 // indirect