mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-28 18:51:48 +08:00
Make raw audio decoder more practical
This commit is contained in:
@@ -9,16 +9,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Format represents how audio is formatted in memory
|
// Format represents how audio is formatted in memory
|
||||||
type Format string
|
type Format fmt.Stringer
|
||||||
|
|
||||||
const (
|
type RawFormat struct {
|
||||||
FormatInt16Interleaved Format = "Int16Interleaved"
|
SampleSize int
|
||||||
FormatInt16NonInterleaved = "Int16NonInterleaved"
|
IsFloat bool
|
||||||
FormatFloat32Interleaved = "Float32Interleaved"
|
Interleaved bool
|
||||||
FormatFloat32NonInterleaved = "Float32NonInterleaved"
|
}
|
||||||
)
|
|
||||||
|
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 hostEndian binary.ByteOrder
|
||||||
|
var registeredDecoders = map[string]Decoder{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
switch v := *(*uint16)(unsafe.Pointer(&([]byte{0x12, 0x34}[0]))); v {
|
switch v := *(*uint16)(unsafe.Pointer(&([]byte{0x12, 0x34}[0]))); v {
|
||||||
@@ -29,6 +43,20 @@ func init() {
|
|||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("failed to determine host endianness: %x", v))
|
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
|
// 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)
|
return f(endian, chunk, channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDecoder creates a decoder to decode raw audio data in the given format
|
// DecoderBuilder builds raw audio decoder
|
||||||
func NewDecoder(f Format) (Decoder, error) {
|
type DecoderBuilder interface {
|
||||||
var decoder DecoderFunc
|
// NewDecoder creates a new decoder for specified format
|
||||||
|
NewDecoder() (Decoder, Format)
|
||||||
|
}
|
||||||
|
|
||||||
switch f {
|
// DecoderBuilderFunc is a proxy type for DecoderBuilder
|
||||||
case FormatInt16Interleaved:
|
type DecoderBuilderFunc func() (Decoder, Format)
|
||||||
decoder = decodeInt16Interleaved
|
|
||||||
case FormatInt16NonInterleaved:
|
func (builderFunc DecoderBuilderFunc) NewDecoder() (Decoder, Format) {
|
||||||
decoder = decodeInt16NonInterleaved
|
return builderFunc()
|
||||||
case FormatFloat32Interleaved:
|
}
|
||||||
decoder = decodeFloat32Interleaved
|
|
||||||
case FormatFloat32NonInterleaved:
|
func RegisterDecoder(builder DecoderBuilder) error {
|
||||||
decoder = decodeFloat32NonInterleaved
|
decoder, format := builder.NewDecoder()
|
||||||
default:
|
formatStr := format.String()
|
||||||
return nil, fmt.Errorf("%s is not supported", f)
|
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
|
return decoder, nil
|
||||||
@@ -85,8 +127,15 @@ func calculateChunkInfo(chunk []byte, channels int, sampleSize int) (ChunkInfo,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
|
func newInt16InterleavedDecoder() (Decoder, Format) {
|
||||||
sampleSize := 2
|
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)
|
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -114,10 +163,21 @@ func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return container, nil
|
return container, nil
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return decoder, format
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
|
func newInt16NonInterleavedDecoder() (Decoder, Format) {
|
||||||
sampleSize := 2
|
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)
|
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -146,10 +206,20 @@ func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels i
|
|||||||
}
|
}
|
||||||
|
|
||||||
return container, nil
|
return container, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return decoder, format
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
|
func newFloat32InterleavedDecoder() (Decoder, Format) {
|
||||||
sampleSize := 4
|
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)
|
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -178,10 +248,20 @@ func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels in
|
|||||||
}
|
}
|
||||||
|
|
||||||
return container, nil
|
return container, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return decoder, format
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
|
func newFloat32NonInterleavedDecoder() (Decoder, Format) {
|
||||||
sampleSize := 4
|
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)
|
chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -211,4 +291,7 @@ func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels
|
|||||||
}
|
}
|
||||||
|
|
||||||
return container, nil
|
return container, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return decoder, format
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkDecoder(b *testing.B) {
|
func BenchmarkDecoder(b *testing.B) {
|
||||||
formats := []Format{
|
|
||||||
FormatInt16Interleaved,
|
|
||||||
FormatInt16NonInterleaved,
|
|
||||||
FormatFloat32Interleaved,
|
|
||||||
FormatFloat32NonInterleaved,
|
|
||||||
}
|
|
||||||
|
|
||||||
var nonHostEndian binary.ByteOrder
|
var nonHostEndian binary.ByteOrder
|
||||||
if hostEndian == binary.BigEndian {
|
if hostEndian == binary.BigEndian {
|
||||||
nonHostEndian = binary.LittleEndian
|
nonHostEndian = binary.LittleEndian
|
||||||
@@ -21,12 +14,9 @@ func BenchmarkDecoder(b *testing.B) {
|
|||||||
nonHostEndian = binary.BigEndian
|
nonHostEndian = binary.BigEndian
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, format := range formats {
|
for format, decoder := range registeredDecoders {
|
||||||
format := format
|
format := format
|
||||||
decoder, err := NewDecoder(format)
|
decoder := decoder
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Run(fmt.Sprintf("%sHostEndian", format), func(b *testing.B) {
|
b.Run(fmt.Sprintf("%sHostEndian", format), func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
@@ -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) {
|
func TestDecodeInt16Interleaved(t *testing.T) {
|
||||||
raw := []byte{
|
raw := []byte{
|
||||||
// 16 bits per channel
|
// 16 bits per channel
|
||||||
0x01, 0x02, 0x03, 0x04,
|
0x01, 0x02, 0x03, 0x04,
|
||||||
0x05, 0x06, 0x07, 0x08,
|
0x05, 0x06, 0x07, 0x08,
|
||||||
}
|
}
|
||||||
|
decoder, _ := newInt16InterleavedDecoder()
|
||||||
|
|
||||||
t.Run("BigEndian", func(t *testing.T) {
|
t.Run("BigEndian", func(t *testing.T) {
|
||||||
expected := &Int16Interleaved{
|
expected := &Int16Interleaved{
|
||||||
@@ -113,7 +146,7 @@ func TestDecodeInt16Interleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeInt16Interleaved(binary.BigEndian, raw, 2)
|
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -136,7 +169,7 @@ func TestDecodeInt16Interleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeInt16Interleaved(binary.LittleEndian, raw, 2)
|
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -154,6 +187,8 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
|
|||||||
0x05, 0x06, 0x07, 0x08,
|
0x05, 0x06, 0x07, 0x08,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decoder, _ := newInt16NonInterleavedDecoder()
|
||||||
|
|
||||||
t.Run("BigEndian", func(t *testing.T) {
|
t.Run("BigEndian", func(t *testing.T) {
|
||||||
expected := &Int16NonInterleaved{
|
expected := &Int16NonInterleaved{
|
||||||
Data: [][]int16{
|
Data: [][]int16{
|
||||||
@@ -165,7 +200,7 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeInt16NonInterleaved(binary.BigEndian, raw, 2)
|
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -186,7 +221,7 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeInt16NonInterleaved(binary.LittleEndian, raw, 2)
|
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -204,6 +239,8 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
|
|||||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decoder, _ := newFloat32InterleavedDecoder()
|
||||||
|
|
||||||
t.Run("BigEndian", func(t *testing.T) {
|
t.Run("BigEndian", func(t *testing.T) {
|
||||||
expected := &Float32Interleaved{
|
expected := &Float32Interleaved{
|
||||||
Data: []float32{
|
Data: []float32{
|
||||||
@@ -217,7 +254,7 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeFloat32Interleaved(binary.BigEndian, raw, 2)
|
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -240,7 +277,7 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeFloat32Interleaved(binary.LittleEndian, raw, 2)
|
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -258,6 +295,8 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
|
|||||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decoder, _ := newFloat32NonInterleavedDecoder()
|
||||||
|
|
||||||
t.Run("BigEndian", func(t *testing.T) {
|
t.Run("BigEndian", func(t *testing.T) {
|
||||||
expected := &Float32NonInterleaved{
|
expected := &Float32NonInterleaved{
|
||||||
Data: [][]float32{
|
Data: [][]float32{
|
||||||
@@ -275,7 +314,7 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeFloat32NonInterleaved(binary.BigEndian, raw, 2)
|
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -302,7 +341,7 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
|
|||||||
Channels: 2,
|
Channels: 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual, err := decodeFloat32NonInterleaved(binary.LittleEndian, raw, 2)
|
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user