1
1
mirror of https://github.com/wader/fq.git synced 2024-11-27 14:14:58 +03:00
fq/format/flac/flac_frame.go
Mattias Wadman 7c5215347d bitio,decode: Refactor bitio usage and make buffer slicing more correct
Remove bitio.Buffer layer. bitio.Buffer was a kitchen sink layer with helpers
now it's just a buffer and most functions have been moved to decode instead.

bitio package now only have primitive types and functions simialar to standard
library io and bytes packages.

Make nearly eveything internally use bitio.Bit* interfaces so that slicing work
correctly this will also make it possible to start experimenting with more
complicated silcing helpers, ex things like:
breplace(.header.bitrate; 123) to get a new buffer with bitrate changed.
2022-02-04 21:41:53 +01:00

651 lines
22 KiB
Go

package flac
import (
"encoding/binary"
"math/bits"
"github.com/wader/fq/format"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/internal/mathextra"
"github.com/wader/fq/pkg/checksum"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
func init() {
registry.MustRegister(decode.Format{
Name: format.FLAC_FRAME,
Description: "FLAC frame",
DecodeFn: frameDecode,
})
}
const (
SampleRateStreaminfo = 0b0000
)
const (
SampleSizeStreaminfo = 0b000
)
const (
BlockingStrategyFixed = 0
BlockingStrategyVariable = 1
)
var BlockingStrategyNames = scalar.UToSymStr{
BlockingStrategyFixed: "fixed",
BlockingStrategyVariable: "variable",
}
const (
BlockSizeEndOfHeader8 = 0b0110
BlockSizeEndOfHeader16 = 0b0111
)
const (
SampeleRateEndOfHeader8 = 0b1100
SampeleRateEndOfHeader16 = 0b1101
SampeleRateEndOfHeader160 = 0b1110
)
const (
SubframeConstant = "constant"
SubframeVerbatim = "verbatim"
SubframeFixed = "fixed"
SubframeLPC = "lpc"
SubframeReserved = "reserved"
)
const (
ChannelLeftSide = 0b1000
ChannelSideRight = 0b1001
ChannelMidSide = 0b1010
)
const (
ResidualCodingMethodRice = 0b00
ResidualCodingMethodRice2 = 0b01
)
var ResidualCodingMethodMap = scalar.UToScalar{
ResidualCodingMethodRice: scalar.S{Sym: uint64(4), Description: "rice"},
ResidualCodingMethodRice2: scalar.S{Sym: uint64(5), Description: "rice2"},
}
// TODO: generic enough?
func utf8Uint(d *decode.D) uint64 {
n := d.U8()
// leading ones, bit negate and count zeroes
c := bits.LeadingZeros8(^uint8(n))
// 0b0xxxxxxx 1 byte
// 0b110xxxxx 2 byte
// 0b1110xxxx 3 byte
// 0b11110xxx 4 byte
switch c {
case 0:
// nop
case 2, 3, 4:
n = n & ((1 << (8 - c - 1)) - 1)
for i := 1; i < c; i++ {
n = n<<6 | d.U8()&0x3f
}
default:
d.Errorf("invalid UTF8Uint")
}
return n
}
// in argument is an optional FlacFrameIn struct with stream info
func frameDecode(d *decode.D, in interface{}) interface{} {
var inStreamInfo *format.FlacStreamInfo
ffi, ok := in.(format.FlacFrameIn)
if ok {
inStreamInfo = &ffi.StreamInfo
}
frameStart := d.Pos()
blockSize := 0
channelAssignment := uint64(0)
channels := 0
sampleSize := 0
sideChannelIndex := -1
d.FieldStruct("header", func(d *decode.D) {
// <14> 11111111111110
d.FieldU14("sync", d.AssertU(0b11111111111110), scalar.Bin)
// <1> Reserved
// 0 : mandatory value
// 1 : reserved for future use
d.FieldU1("reserved0", d.AssertU(0))
// <1> Blocking strategy:
// 0 : fixed-blocksize stream; frame header encodes the frame number
// 1 : variable-blocksize stream; frame header encodes the sample number
blockingStrategy := d.FieldU1("blocking_strategy", BlockingStrategyNames)
// <4> 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 blockSizeMap = scalar.UToScalar{
0b0000: {Description: "reserved"},
0b0001: {Sym: uint64(192)},
0b0010: {Sym: uint64(576)},
0b0011: {Sym: uint64(1152)},
0b0100: {Sym: uint64(2304)},
0b0101: {Sym: uint64(4608)},
0b0110: {Description: "end of header (8 bit)"},
0b0111: {Description: "end of header (16 bit)"},
0b1000: {Sym: uint64(256)},
0b1001: {Sym: uint64(512)},
0b1010: {Sym: uint64(1024)},
0b1011: {Sym: uint64(2048)},
0b1100: {Sym: uint64(4096)},
0b1101: {Sym: uint64(8192)},
0b1110: {Sym: uint64(16384)},
0b1111: {Sym: uint64(32768)},
}
blockSizeS := d.FieldScalarU4("block_size", blockSizeMap, scalar.Bin)
if blockSizeS.Sym != nil {
blockSize = int(blockSizeS.SymU())
}
// <4> 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 sampleRateMap = scalar.UToScalar{
0b0000: {Description: "from streaminfo"},
0b0001: {Sym: uint64(88200)},
0b0010: {Sym: uint64(176400)},
0b0011: {Sym: uint64(192000)},
0b0100: {Sym: uint64(8000)},
0b0101: {Sym: uint64(16000)},
0b0110: {Sym: uint64(22050)},
0b0111: {Sym: uint64(24000)},
0b1000: {Sym: uint64(32000)},
0b1001: {Sym: uint64(44100)},
0b1010: {Sym: uint64(48000)},
0b1011: {Sym: uint64(96000)},
0b1100: {Description: "end of header (8 bit*1000)"},
0b1101: {Description: "end of header (16 bit)"},
0b1110: {Description: "end of header (16 bit*10)"},
0b1111: {Description: "invalid"},
}
sampleRateS := d.FieldScalarU4("sample_rate", sampleRateMap, scalar.Bin)
switch sampleRateS.ActualU() {
case SampleRateStreaminfo:
if inStreamInfo == nil {
d.Fatalf("streaminfo required for sample rate")
}
}
// <4> 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
// TODO: extract to tables and cleanup
var channelAssignmentMap = scalar.UToScalar{
0: {Sym: uint64(1), Description: "mono"},
1: {Sym: uint64(2), Description: "lr"},
2: {Sym: uint64(3), Description: "lrc"},
3: {Sym: uint64(4), Description: "fl,fr,bl,br"},
4: {Sym: uint64(5), Description: "fl,fr,fc,back/surround left,back/surround right"},
5: {Sym: uint64(6), Description: "fl,fr,fc,lfe,back/surround left,back/surround right"},
6: {Sym: uint64(7), Description: "fl,fr,fc,lfe,back center,sl,sr"},
7: {Sym: uint64(8), Description: "fl,fr,fc,lfe,back left,br,sl,sr"},
0b1000: {Sym: uint64(2), Description: "left/side stereo"},
0b1001: {Sym: uint64(2), Description: "right/side stereo"},
0b1010: {Sym: uint64(2), Description: "mid/side stereo"},
0b1011: {Sym: nil, Description: "reserved"},
0b1100: {Sym: nil, Description: "reserved"},
0b1101: {Sym: nil, Description: "reserved"},
0b1111: {Sym: nil, Description: "reserved"},
}
channelAssignmentS := d.FieldScalarU4("channel_assignment", channelAssignmentMap)
if channelAssignmentS.Sym == nil {
d.Fatalf("unknown number of channels")
}
channelAssignment = channelAssignmentS.ActualU()
channels = int(channelAssignmentS.SymU())
switch channelAssignmentS.ActualU() {
case ChannelLeftSide:
sideChannelIndex = 1
case ChannelSideRight:
sideChannelIndex = 0
case ChannelMidSide:
sideChannelIndex = 1
}
if sideChannelIndex != -1 {
d.FieldValueU("side_channel_index", uint64(sideChannelIndex))
}
// <3> 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 sampleSizeMap = scalar.UToScalar{
0b000: {Description: "from streaminfo"},
0b001: {Sym: uint64(8)},
0b010: {Sym: uint64(12)},
0b011: {Description: "reserved"},
0b100: {Sym: uint64(16)},
0b101: {Sym: uint64(20)},
0b110: {Sym: uint64(24)},
0b111: {Description: "reserved"},
}
sampleSizeS := d.FieldScalarU3("sample_size", sampleSizeMap, scalar.Bin)
switch sampleSizeS.ActualU() {
case SampleSizeStreaminfo:
if inStreamInfo == nil {
d.Fatalf("streaminfo required for sample size")
}
sampleSize = int(inStreamInfo.BitPerSample)
default:
if sampleSizeS.Sym != nil {
sampleSize = int(sampleSizeS.SymU())
}
}
// <1> Reserved:
// 0 : mandatory value
// 1 : reserved for future use
d.FieldU1("reserved1", d.AssertU(0))
d.FieldStruct("end_of_header", func(d *decode.D) {
// if(variable blocksize)
// <8-56>:"UTF-8" coded sample number (decoded number is 36 bits) [4]
// else
// <8-48>:"UTF-8" coded frame number (decoded number is 31 bits) [4]
// 0 : fixed-blocksize stream; frame header encodes the frame number
// 1 : variable-blocksize stream; frame header encodes the sample number
switch blockingStrategy {
case BlockingStrategyVariable:
d.FieldUFn("sample_number", utf8Uint)
case BlockingStrategyFixed:
d.FieldUFn("frame_number", utf8Uint)
}
// if(blocksize bits == 011x)
// 8/16 bit (blocksize-1)
// 0110 : get 8 bit (blocksize-1) from end of header
// 0111 : get 16 bit (blocksize-1) from end of header
switch blockSizeS.ActualU() {
case BlockSizeEndOfHeader8:
blockSize = int(d.FieldU8("block_size", scalar.UAdd(1)))
case BlockSizeEndOfHeader16:
blockSize = int(d.FieldU16("block_size", scalar.UAdd(1)))
}
// if(sample rate bits == 11xx)
// 8/16 bit sample rate
// 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
switch sampleRateS.ActualU() {
case SampeleRateEndOfHeader8:
d.FieldUFn("sample_rate", func(d *decode.D) uint64 { return d.U8() * 1000 })
case SampeleRateEndOfHeader16:
d.FieldU16("sample_rate")
case SampeleRateEndOfHeader160:
d.FieldUFn("sample_rate", func(d *decode.D) uint64 { return d.U16() * 10 })
}
})
headerCRC := &checksum.CRC{Bits: 8, Table: checksum.ATM8Table}
d.MustCopyBits(headerCRC, d.BitBufRange(frameStart, d.Pos()-frameStart))
d.FieldU8("crc", d.ValidateUBytes(headerCRC.Sum(nil)), scalar.Hex)
})
var channelSamples [][]int64
d.FieldArray("subframes", func(d *decode.D) {
for channelIndex := 0; channelIndex < channels; channelIndex++ {
d.FieldStruct("subframe", func(d *decode.D) {
// <1> Zero bit padding, to prevent sync-fooling string of 1s
d.FieldU1("zero_bit", d.AssertU(0))
// <6> 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
lpcOrder := -1
var subframeTypeRangeMap = scalar.URangeToScalar{
{Range: [2]uint64{0b000000, 0b000000}, S: scalar.S{Sym: SubframeConstant}},
{Range: [2]uint64{0b000001, 0b000001}, S: scalar.S{Sym: SubframeVerbatim}},
{Range: [2]uint64{0b000010, 0b000011}, S: scalar.S{Sym: SubframeReserved}},
{Range: [2]uint64{0b000100, 0b000111}, S: scalar.S{Sym: SubframeReserved}},
{Range: [2]uint64{0b001000, 0b001100}, S: scalar.S{Sym: SubframeFixed}},
{Range: [2]uint64{0b001101, 0b001111}, S: scalar.S{Sym: SubframeReserved}},
{Range: [2]uint64{0b010000, 0b011111}, S: scalar.S{Sym: SubframeReserved}},
{Range: [2]uint64{0b100000, 0b111111}, S: scalar.S{Sym: SubframeLPC}},
}
subframeTypeS := d.FieldScalarU6("subframe_type", subframeTypeRangeMap, scalar.Bin)
switch subframeTypeS.SymStr() {
case SubframeFixed:
lpcOrder = int(subframeTypeS.ActualU() & 0b111)
case SubframeLPC:
lpcOrder = int((subframeTypeS.ActualU() & 0b11111) + 1)
}
if lpcOrder != -1 {
d.FieldValueU("lpc_order", uint64(lpcOrder))
}
// '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.
wastedBitsFlag := d.FieldU1("wasted_bits_flag")
var wastedBitsK int
if wastedBitsFlag != 0 {
wastedBitsK = int(d.FieldUnary("wasted_bits_k", 0, scalar.UAdd(1)))
}
subframeSampleSize := sampleSize - wastedBitsK
if subframeSampleSize < 1 {
d.Fatalf("subframeSampleSize %d < 1", subframeSampleSize)
}
// if channel is side, add en extra sample bit
// https://github.com/xiph/flac/blob/37e675b777d4e0de53ac9ff69e2aea10d92e729c/src/libFLAC/stream_decoder.c#L2040
if channelIndex == sideChannelIndex {
subframeSampleSize++
}
d.FieldValueU("subframe_sample_size", uint64(subframeSampleSize))
decodeWarmupSamples := func(samples []int64, n int, sampleSize int) {
if len(samples) < n {
d.Fatalf("decodeWarmupSamples outside block size")
}
d.FieldArray("warmup_samples", func(d *decode.D) {
for i := 0; i < n; i++ {
samples[i] = d.FieldS("value", sampleSize)
}
})
}
decodeResiduals := func(samples []int64) {
samplesLen := len(samples)
n := 0
// <2> Residual coding method:
// 00 : partitioned Rice coding with 4-bit Rice parameter; RESIDUAL_CODING_METHOD_PARTITIONED_RICE follows
// 01 : partitioned Rice coding with 5-bit Rice parameter; RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 follows
// 10-11 : reserved
var riceEscape int
var riceBits int
residualCodingMethod := d.FieldU2("residual_coding_method", scalar.UToScalar{
0b00: scalar.S{Sym: uint64(4), Description: "rice"},
0b01: scalar.S{Sym: uint64(5), Description: "rice2"},
})
switch residualCodingMethod {
case ResidualCodingMethodRice:
riceEscape = 0b1111
riceBits = 4
case ResidualCodingMethodRice2:
riceEscape = 0b11111
riceBits = 5
}
// <4> Partition order.
partitionOrder := int(d.FieldU4("partition_order"))
// There will be 2^order partitions.
ricePartitions := 1 << partitionOrder
d.FieldValueU("rice_partitions", uint64(ricePartitions))
d.FieldArray("partitions", func(d *decode.D) {
for i := 0; i < ricePartitions; i++ {
d.FieldStruct("partition", func(d *decode.D) {
// Encoding parameter:
// <4(+5)> Encoding parameter:
// 0000-1110 : Rice parameter.
// 1111 : Escape code, meaning the partition is in unencoded binary form using n bits per sample; n follows as a 5-bit number.
// Or:
// <5(+5)> Encoding parameter:
// 00000-11110 : Rice parameter.
// 11111 : Escape code, meaning the partition is in unencoded binary form using n bits per sample; n follows as a 5-bit number.
// Encoded residual. The number of samples (n) in the partition is determined as follows:
// if the partition order is zero, n = frame's blocksize - predictor order
// else if this is not the first partition of the subframe, n = (frame's blocksize / (2^partition order))
// else n = (frame's blocksize / (2^partition order)) - predictor order
var count int
if partitionOrder == 0 {
count = blockSize - lpcOrder
} else if i != 0 {
count = blockSize / ricePartitions
} else {
count = (blockSize / ricePartitions) - lpcOrder
}
d.FieldValueU("count", uint64(count))
riceParameter := int(d.FieldU("rice_parameter", riceBits))
if samplesLen < n+count {
d.Fatalf("decodeResiduals outside block size")
}
if riceParameter == riceEscape {
escapeSampleSize := int(d.FieldU5("escape_sample_size"))
d.FieldRawLen("samples", int64(count*escapeSampleSize))
} else {
samplesStart := d.Pos()
for j := 0; j < count; j++ {
high := d.Unary(0)
_ = high
low := d.U(riceParameter)
_ = low
samples[n] = mathextra.ZigZag(high<<riceParameter | low)
n++
}
samplesStop := d.Pos()
d.RangeFn(samplesStart, samplesStop-samplesStart, func(d *decode.D) {
d.FieldRawLen("samples", d.BitsLeft())
})
}
})
}
})
}
// modifies input samples slice and returns it
decodeLPC := func(lpcOrder int, samples []int64, coeffs []int64, shift int64) {
for i := lpcOrder; i < len(samples); i++ {
r := int64(0)
for j := 0; j < len(coeffs); j++ {
c := coeffs[j]
s := samples[i-j-1]
r += c * s
}
samples[i] = samples[i] + (r >> shift)
}
}
var samples []int64
switch subframeTypeS.SymStr() {
case SubframeConstant:
samples = make([]int64, blockSize)
// <n> Unencoded constant value of the subblock, n = frame's bits-per-sample.
v := d.FieldS("value", subframeSampleSize)
for i := 0; i < blockSize; i++ {
samples[i] = v
}
case SubframeVerbatim:
samples = make([]int64, blockSize)
// <n*i> Unencoded subblock; n = frame's bits-per-sample, i = frame's blocksize.
// TODO: refactor into some kind of FieldBitBufLenFn?
d.RangeFn(d.Pos(), int64(blockSize*subframeSampleSize), func(d *decode.D) {
d.FieldRawLen("samples", d.BitsLeft())
})
for i := 0; i < blockSize; i++ {
samples[i] = d.S(subframeSampleSize)
}
case SubframeFixed:
samples = make([]int64, blockSize)
// <n> Unencoded warm-up samples (n = frame's bits-per-sample * predictor order).
decodeWarmupSamples(samples, lpcOrder, subframeSampleSize)
// Encoded residual
decodeResiduals(samples[lpcOrder:])
// http://www.hpl.hp.com/techreports/1999/HPL-1999-144.pdf
fixedCoeffs := [][]int64{
{},
{1},
{2, -1},
{3, -3, 1},
{4, -6, 4, -1},
}
coeffs := fixedCoeffs[lpcOrder]
decodeLPC(lpcOrder, samples, coeffs, 0)
case SubframeLPC:
samples = make([]int64, blockSize)
// <n> Unencoded warm-up samples (n = frame's bits-per-sample * lpc order).
decodeWarmupSamples(samples, lpcOrder, subframeSampleSize)
// <4> (Quantized linear predictor coefficients' precision in bits)-1 (1111 = invalid).
precision := int(d.FieldU4("precision", scalar.UAdd(1)))
// <5> Quantized linear predictor coefficient shift needed in bits (NOTE: this number is signed two's-complement).
shift := d.FieldS5("shift")
if shift < 0 {
d.Fatalf("negative LPC shift %d", shift)
}
// <n> Unencoded predictor coefficients (n = qlp coeff precision * lpc order) (NOTE: the coefficients are signed two's-complement).
var coeffs []int64
d.FieldArray("coefficients", func(d *decode.D) {
for i := 0; i < lpcOrder; i++ {
coeffs = append(coeffs, d.FieldS("value", precision))
}
})
// Encoded residual
decodeResiduals(samples[lpcOrder:])
decodeLPC(lpcOrder, samples, coeffs, shift)
}
if wastedBitsK != 0 {
for i := 0; i < len(samples); i++ {
samples[i] <<= wastedBitsK
}
}
channelSamples = append(channelSamples, samples)
})
}
})
// <?> Zero-padding to byte alignment.
d.FieldU("byte_align", d.ByteAlignBits(), d.AssertU(0))
// <16> 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
footerCRC := &checksum.CRC{Bits: 16, Table: checksum.ANSI16Table}
d.MustCopyBits(footerCRC, d.BitBufRange(frameStart, d.Pos()-frameStart))
d.FieldRawLen("footer_crc", 16, d.ValidateBitBuf(footerCRC.Sum(nil)), scalar.RawHex)
streamSamples := len(channelSamples[0])
for j := 0; j < len(channelSamples); j++ {
if streamSamples > len(channelSamples[j]) {
d.Fatalf("different amount of samples in channels %d != %d", streamSamples, len(channelSamples[j]))
}
}
// Transform mid/side channels into left, right
// mid = (left + right)/2
// side = left - right
switch channelAssignment {
case ChannelLeftSide:
for i := 0; i < len(channelSamples[0]); i++ {
channelSamples[1][i] = channelSamples[0][i] - channelSamples[1][i]
}
case ChannelSideRight:
for i := 0; i < len(channelSamples[0]); i++ {
channelSamples[0][i] = channelSamples[1][i] + channelSamples[0][i]
}
case ChannelMidSide:
for i := 0; i < len(channelSamples[0]); i++ {
m := channelSamples[0][i]
s := channelSamples[1][i]
m = m<<1 | s&1
channelSamples[0][i] = (m + s) >> 1
channelSamples[1][i] = (m - s) >> 1
}
default:
// not stereo or no side channel
}
outSampleSize := sampleSize + (sampleSize % 8)
bytesPerSample := outSampleSize / 8
p := 0
le := binary.LittleEndian
interleavedSamplesBuf := ffi.SamplesBuf
interleavedSamplesBufLen := len(channelSamples) * streamSamples * bytesPerSample
// TODO: decode read buffer?
// reuse buffer if possible
if interleavedSamplesBuf == nil || len(interleavedSamplesBuf) < interleavedSamplesBufLen {
interleavedSamplesBuf = make([]byte, interleavedSamplesBufLen)
}
// TODO: speedup by using more cache friendly memory layout for samples
for i := 0; i < streamSamples; i++ {
for j := 0; j < len(channelSamples); j++ {
s := channelSamples[j][i]
switch outSampleSize {
case 8:
interleavedSamplesBuf[p] = byte(s)
case 16:
le.PutUint16(interleavedSamplesBuf[p:], uint16(s))
case 24:
interleavedSamplesBuf[p] = byte(s)
le.PutUint16(interleavedSamplesBuf[p+1:], uint16(s>>8))
case 32:
le.PutUint32(interleavedSamplesBuf[p:], uint32(s))
}
p += bytesPerSample
}
}
return format.FlacFrameOut{
SamplesBuf: interleavedSamplesBuf,
Samples: uint64(streamSamples),
Channels: channels,
BitsPerSample: outSampleSize,
}
}