Make raw audio decoder more practical

This commit is contained in:
Lukas Herman
2020-06-09 09:29:30 -04:00
parent c3c1177455
commit 122aec0536
3 changed files with 251 additions and 139 deletions

View File

@@ -9,16 +9,30 @@ import (
)
// Format represents how audio is formatted in memory
type Format string
type Format fmt.Stringer
const (
FormatInt16Interleaved Format = "Int16Interleaved"
FormatInt16NonInterleaved = "Int16NonInterleaved"
FormatFloat32Interleaved = "Float32Interleaved"
FormatFloat32NonInterleaved = "Float32NonInterleaved"
)
type RawFormat struct {
SampleSize int
IsFloat bool
Interleaved bool
}
func (f *RawFormat) String() string {
sampleSizeInBits := f.SampleSize * 8
dataTypeStr := "Int"
if f.IsFloat {
dataTypeStr = "Float"
}
interleavedStr := "NonInterleaved"
if f.Interleaved {
interleavedStr = "Interleaved"
}
return fmt.Sprintf("%s%d%s", dataTypeStr, sampleSizeInBits, interleavedStr)
}
var hostEndian binary.ByteOrder
var registeredDecoders = map[string]Decoder{}
func init() {
switch v := *(*uint16)(unsafe.Pointer(&([]byte{0x12, 0x34}[0]))); v {
@@ -29,6 +43,20 @@ func init() {
default:
panic(fmt.Sprintf("failed to determine host endianness: %x", v))
}
decoderBuilders := []DecoderBuilderFunc{
newInt16InterleavedDecoder,
newInt16NonInterleavedDecoder,
newFloat32InterleavedDecoder,
newFloat32NonInterleavedDecoder,
}
for _, decoderBuilder := range decoderBuilders {
err := RegisterDecoder(decoderBuilder)
if err != nil {
panic(err)
}
}
}
// Decoder decodes raw chunk to Audio
@@ -44,21 +72,35 @@ func (f DecoderFunc) Decode(endian binary.ByteOrder, chunk []byte, channels int)
return f(endian, chunk, channels)
}
// NewDecoder creates a decoder to decode raw audio data in the given format
func NewDecoder(f Format) (Decoder, error) {
var decoder DecoderFunc
// DecoderBuilder builds raw audio decoder
type DecoderBuilder interface {
// NewDecoder creates a new decoder for specified format
NewDecoder() (Decoder, Format)
}
switch f {
case FormatInt16Interleaved:
decoder = decodeInt16Interleaved
case FormatInt16NonInterleaved:
decoder = decodeInt16NonInterleaved
case FormatFloat32Interleaved:
decoder = decodeFloat32Interleaved
case FormatFloat32NonInterleaved:
decoder = decodeFloat32NonInterleaved
default:
return nil, fmt.Errorf("%s is not supported", f)
// DecoderBuilderFunc is a proxy type for DecoderBuilder
type DecoderBuilderFunc func() (Decoder, Format)
func (builderFunc DecoderBuilderFunc) NewDecoder() (Decoder, Format) {
return builderFunc()
}
func RegisterDecoder(builder DecoderBuilder) error {
decoder, format := builder.NewDecoder()
formatStr := format.String()
if _, ok := registeredDecoders[formatStr]; ok {
return fmt.Errorf("%v has already been registered", format)
}
registeredDecoders[formatStr] = decoder
return nil
}
// NewDecoder creates a decoder to decode raw audio data in the given format
func NewDecoder(format Format) (Decoder, error) {
decoder, ok := registeredDecoders[format.String()]
if !ok {
return nil, fmt.Errorf("%s format is not supported", format)
}
return decoder, nil
@@ -85,8 +127,15 @@ func calculateChunkInfo(chunk []byte, channels int, sampleSize int) (ChunkInfo,
}, nil
}
func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := 2
func newInt16InterleavedDecoder() (Decoder, Format) {
format := &RawFormat{
SampleSize: 2,
IsFloat: false,
Interleaved: true,
}
decoder := DecoderFunc(func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := format.SampleSize
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
if err != nil {
return nil, err
@@ -114,10 +163,21 @@ func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int)
}
return container, nil
})
return decoder, format
}
func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := 2
func newInt16NonInterleavedDecoder() (Decoder, Format) {
format := &RawFormat{
SampleSize: 2,
IsFloat: false,
Interleaved: false,
}
decoder := DecoderFunc(func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := format.SampleSize
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
if err != nil {
return nil, err
@@ -146,10 +206,20 @@ func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels i
}
return container, nil
})
return decoder, format
}
func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := 4
func newFloat32InterleavedDecoder() (Decoder, Format) {
format := &RawFormat{
SampleSize: 4,
IsFloat: true,
Interleaved: true,
}
decoder := DecoderFunc(func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := format.SampleSize
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
if err != nil {
return nil, err
@@ -178,10 +248,20 @@ func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels in
}
return container, nil
})
return decoder, format
}
func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := 4
func newFloat32NonInterleavedDecoder() (Decoder, Format) {
format := &RawFormat{
SampleSize: 4,
IsFloat: true,
Interleaved: false,
}
decoder := DecoderFunc(func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
sampleSize := format.SampleSize
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
if err != nil {
return nil, err
@@ -211,4 +291,7 @@ func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels
}
return container, nil
})
return decoder, format
}

View File

@@ -7,13 +7,6 @@ import (
)
func BenchmarkDecoder(b *testing.B) {
formats := []Format{
FormatInt16Interleaved,
FormatInt16NonInterleaved,
FormatFloat32Interleaved,
FormatFloat32NonInterleaved,
}
var nonHostEndian binary.ByteOrder
if hostEndian == binary.BigEndian {
nonHostEndian = binary.LittleEndian
@@ -21,12 +14,9 @@ func BenchmarkDecoder(b *testing.B) {
nonHostEndian = binary.BigEndian
}
for _, format := range formats {
for format, decoder := range registeredDecoders {
format := format
decoder, err := NewDecoder(format)
if err != nil {
b.Fatal(err)
}
decoder := decoder
b.Run(fmt.Sprintf("%sHostEndian", format), func(b *testing.B) {
for i := 0; i < b.N; i++ {

View File

@@ -93,12 +93,45 @@ func TestCalculateChunkInfo(t *testing.T) {
}
}
func TestNewDecoder(t *testing.T) {
rawFormats := []RawFormat{
{
SampleSize: 2,
IsFloat: false,
Interleaved: false,
},
{
SampleSize: 4,
IsFloat: true,
Interleaved: false,
},
{
SampleSize: 2,
IsFloat: false,
Interleaved: true,
},
{
SampleSize: 4,
IsFloat: true,
Interleaved: true,
},
}
for _, rawFormat := range rawFormats {
_, err := NewDecoder(&rawFormat)
if err != nil {
t.Fatal(err)
}
}
}
func TestDecodeInt16Interleaved(t *testing.T) {
raw := []byte{
// 16 bits per channel
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
}
decoder, _ := newInt16InterleavedDecoder()
t.Run("BigEndian", func(t *testing.T) {
expected := &Int16Interleaved{
@@ -113,7 +146,7 @@ func TestDecodeInt16Interleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeInt16Interleaved(binary.BigEndian, raw, 2)
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -136,7 +169,7 @@ func TestDecodeInt16Interleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeInt16Interleaved(binary.LittleEndian, raw, 2)
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -154,6 +187,8 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
0x05, 0x06, 0x07, 0x08,
}
decoder, _ := newInt16NonInterleavedDecoder()
t.Run("BigEndian", func(t *testing.T) {
expected := &Int16NonInterleaved{
Data: [][]int16{
@@ -165,7 +200,7 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeInt16NonInterleaved(binary.BigEndian, raw, 2)
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -186,7 +221,7 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeInt16NonInterleaved(binary.LittleEndian, raw, 2)
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -204,6 +239,8 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
decoder, _ := newFloat32InterleavedDecoder()
t.Run("BigEndian", func(t *testing.T) {
expected := &Float32Interleaved{
Data: []float32{
@@ -217,7 +254,7 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeFloat32Interleaved(binary.BigEndian, raw, 2)
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -240,7 +277,7 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeFloat32Interleaved(binary.LittleEndian, raw, 2)
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -258,6 +295,8 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
}
decoder, _ := newFloat32NonInterleavedDecoder()
t.Run("BigEndian", func(t *testing.T) {
expected := &Float32NonInterleaved{
Data: [][]float32{
@@ -275,7 +314,7 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeFloat32NonInterleaved(binary.BigEndian, raw, 2)
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
if err != nil {
t.Fatal(err)
}
@@ -302,7 +341,7 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
Channels: 2,
},
}
actual, err := decodeFloat32NonInterleaved(binary.LittleEndian, raw, 2)
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
if err != nil {
t.Fatal(err)
}