Repository: pion/mediadevices
Pull request #179 (2020-06-06 23:43:52)

Add raw audio decoder
* Add Int16Interleaved and Int16NonInterleaved formats
* Add Float32Interleaved and Float32NonInterleaved formats
* Add unit tests


Patch:
diff --git a/pkg/wave/decoder.go b/pkg/wave/decoder.go
new file mode 100644
index 0000000..cbb5e2d
--- /dev/null
+++ b/pkg/wave/decoder.go
@@ -0,0 +1,159 @@
+package wave
+
+import (
+	"encoding/binary"
+	"fmt"
+	"math"
+)
+
+// Format represents how audio is formatted in memory
+type Format string
+
+const (
+	FormatInt16Interleaved      Format = "Int16Interleaved"
+	FormatInt16NonInterleaved          = "Int16NonInterleaved"
+	FormatFloat32Interleaved           = "Float32Interleaved"
+	FormatFloat32NonInterleaved        = "Float32NonInterleaved"
+)
+
+// Decoder decodes raw chunk to Audio
+type Decoder interface {
+	// Decode decodes raw chunk in endian byte order
+	Decode(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error)
+}
+
+// DecoderFunc is a proxy type for Decoder
+type DecoderFunc func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error)
+
+func (f DecoderFunc) Decode(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
+	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
+
+	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)
+	}
+
+	return decoder, nil
+}
+
+func calculateChunkInfo(chunk []byte, channels int, sampleSize int) (ChunkInfo, error) {
+	if channels <= 0 {
+		return ChunkInfo{}, fmt.Errorf("channels has to be greater than 0")
+	}
+
+	if sampleSize <= 0 {
+		return ChunkInfo{}, fmt.Errorf("sample size has to be greater than 0")
+	}
+
+	sampleLen := channels * sampleSize
+	if len(chunk)%sampleLen != 0 {
+		expectedLen := len(chunk) + (sampleLen - len(chunk)%sampleLen)
+		return ChunkInfo{}, fmt.Errorf("expected chunk to have a length of %d, but got %d", expectedLen, len(chunk))
+	}
+
+	return ChunkInfo{
+		Channels: channels,
+		Len:      len(chunk) / (channels * sampleSize),
+	}, nil
+}
+
+func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
+	sampleSize := 2
+	chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
+	if err != nil {
+		return nil, err
+	}
+
+	container := NewInt16Interleaved(chunkInfo)
+	sampleLen := sampleSize * channels
+	var i int
+	for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen {
+		for ch := 0; ch < channels; ch++ {
+			flatOffset := offset + ch*sampleSize
+			sample := endian.Uint16(chunk[flatOffset : flatOffset+sampleSize])
+			container.SetInt16(i, ch, Int16Sample(sample))
+		}
+		i++
+	}
+
+	return container, nil
+}
+
+func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
+	sampleSize := 2
+	chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
+	if err != nil {
+		return nil, err
+	}
+
+	container := NewInt16NonInterleaved(chunkInfo)
+	chunkLen := len(chunk) / channels
+	for ch := 0; ch < channels; ch++ {
+		offset := ch * chunkLen
+		for i := 0; i < chunkInfo.Len; i++ {
+			flatOffset := offset + i*sampleSize
+			sample := endian.Uint16(chunk[flatOffset : flatOffset+sampleSize])
+			container.SetInt16(i, ch, Int16Sample(sample))
+		}
+	}
+
+	return container, nil
+}
+
+func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
+	sampleSize := 4
+	chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
+	if err != nil {
+		return nil, err
+	}
+
+	container := NewFloat32Interleaved(chunkInfo)
+	sampleLen := sampleSize * channels
+	var i int
+	for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen {
+		for ch := 0; ch < channels; ch++ {
+			flatOffset := offset + ch*sampleSize
+			sample := endian.Uint32(chunk[flatOffset : flatOffset+sampleSize])
+			sampleF := math.Float32frombits(sample)
+			container.SetFloat32(i, ch, Float32Sample(sampleF))
+		}
+		i++
+	}
+
+	return container, nil
+}
+
+func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) {
+	sampleSize := 4
+	chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize)
+	if err != nil {
+		return nil, err
+	}
+
+	container := NewFloat32NonInterleaved(chunkInfo)
+	chunkLen := len(chunk) / channels
+	for ch := 0; ch < channels; ch++ {
+		offset := ch * chunkLen
+		for i := 0; i < chunkInfo.Len; i++ {
+			flatOffset := offset + i*sampleSize
+			sample := endian.Uint32(chunk[flatOffset : flatOffset+sampleSize])
+			sampleF := math.Float32frombits(sample)
+			container.SetFloat32(i, ch, Float32Sample(sampleF))
+		}
+	}
+
+	return container, nil
+}


Test patch:
diff --git a/pkg/wave/decoder_test.go b/pkg/wave/decoder_test.go
new file mode 100644
index 0000000..bda52db
--- /dev/null
+++ b/pkg/wave/decoder_test.go
@@ -0,0 +1,314 @@
+package wave
+
+import (
+	"encoding/binary"
+	"math"
+	"reflect"
+	"testing"
+)
+
+func TestCalculateChunkInfo(t *testing.T) {
+	testCases := map[string]struct {
+		chunk      []byte
+		channels   int
+		sampleSize int
+		expected   ChunkInfo
+		expectErr  bool
+	}{
+		"InvalidChunkSize1": {
+			chunk:      make([]byte, 3),
+			channels:   2,
+			sampleSize: 2,
+			expected:   ChunkInfo{},
+			expectErr:  true,
+		},
+		"InvalidChunkSize2": {
+			chunk:      make([]byte, 4),
+			channels:   2,
+			sampleSize: 4,
+			expected:   ChunkInfo{},
+			expectErr:  true,
+		},
+		"InvalidChannels": {
+			chunk:      nil,
+			channels:   0,
+			sampleSize: 2,
+			expected:   ChunkInfo{},
+			expectErr:  true,
+		},
+		"InvalidSampleSize": {
+			chunk:      nil,
+			channels:   2,
+			sampleSize: 0,
+			expected:   ChunkInfo{},
+			expectErr:  true,
+		},
+		"Valid1": {
+			chunk:      nil,
+			channels:   2,
+			sampleSize: 2,
+			expected: ChunkInfo{
+				Len:          0,
+				Channels:     2,
+				SamplingRate: 0,
+			},
+			expectErr: false,
+		},
+		"Valid2": {
+			chunk:      make([]byte, 8),
+			channels:   2,
+			sampleSize: 4,
+			expected: ChunkInfo{
+				Len:          1,
+				Channels:     2,
+				SamplingRate: 0,
+			},
+			expectErr: false,
+		},
+		"Valid3": {
+			chunk:      make([]byte, 4),
+			channels:   1,
+			sampleSize: 2,
+			expected: ChunkInfo{
+				Len:          2,
+				Channels:     1,
+				SamplingRate: 0,
+			},
+			expectErr: false,
+		},
+	}
+
+	for testCaseName, testCase := range testCases {
+		testCase := testCase
+		t.Run(testCaseName, func(t *testing.T) {
+			actual, err := calculateChunkInfo(testCase.chunk, testCase.channels, testCase.sampleSize)
+			if testCase.expectErr && err == nil {
+				t.Fatal("expected an error, but got nil")
+			} else if !testCase.expectErr && err != nil {
+				t.Fatalf("expected no error, but got %s", err)
+			} else if !testCase.expectErr && !reflect.DeepEqual(actual, testCase.expected) {
+				t.Errorf("Wrong chunk info calculation result,\nexpected:\n%+v\ngot:\n%+v", testCase.expected, actual)
+			}
+		})
+	}
+}
+
+func TestDecodeInt16Interleaved(t *testing.T) {
+	raw := []byte{
+		// 16 bits per channel
+		0x01, 0x02, 0x03, 0x04,
+		0x05, 0x06, 0x07, 0x08,
+	}
+
+	t.Run("BigEndian", func(t *testing.T) {
+		expected := &Int16Interleaved{
+			Data: []int16{
+				int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})),
+				int16(binary.BigEndian.Uint16([]byte{0x03, 0x04})),
+				int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})),
+				int16(binary.BigEndian.Uint16([]byte{0x07, 0x08})),
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeInt16Interleaved(binary.BigEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+
+	t.Run("LittleEndian", func(t *testing.T) {
+		expected := &Int16Interleaved{
+			Data: []int16{
+				int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})),
+				int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04})),
+				int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})),
+				int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08})),
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeInt16Interleaved(binary.LittleEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+}
+
+func TestDecodeInt16NonInterleaved(t *testing.T) {
+	raw := []byte{
+		// 16 bits per channel
+		0x01, 0x02, 0x03, 0x04,
+		0x05, 0x06, 0x07, 0x08,
+	}
+
+	t.Run("BigEndian", func(t *testing.T) {
+		expected := &Int16NonInterleaved{
+			Data: [][]int16{
+				{int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})), int16(binary.BigEndian.Uint16([]byte{0x03, 0x04}))},
+				{int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})), int16(binary.BigEndian.Uint16([]byte{0x07, 0x08}))},
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeInt16NonInterleaved(binary.BigEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+
+	t.Run("LittleEndian", func(t *testing.T) {
+		expected := &Int16NonInterleaved{
+			Data: [][]int16{
+				{int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})), int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04}))},
+				{int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})), int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08}))},
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeInt16NonInterleaved(binary.LittleEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+}
+
+func TestDecodeFloat32Interleaved(t *testing.T) {
+	raw := []byte{
+		// 32 bits per channel
+		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+		0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+	}
+
+	t.Run("BigEndian", func(t *testing.T) {
+		expected := &Float32Interleaved{
+			Data: []float32{
+				math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
+				math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
+				math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
+				math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeFloat32Interleaved(binary.BigEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+
+	t.Run("LittleEndian", func(t *testing.T) {
+		expected := &Float32Interleaved{
+			Data: []float32{
+				math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
+				math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
+				math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
+				math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeFloat32Interleaved(binary.LittleEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+}
+
+func TestDecodeFloat32NonInterleaved(t *testing.T) {
+	raw := []byte{
+		// 32 bits per channel
+		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+		0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+	}
+
+	t.Run("BigEndian", func(t *testing.T) {
+		expected := &Float32NonInterleaved{
+			Data: [][]float32{
+				{
+					math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
+					math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
+				},
+				{
+					math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
+					math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
+				},
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeFloat32NonInterleaved(binary.BigEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+
+	t.Run("LittleEndian", func(t *testing.T) {
+		expected := &Float32NonInterleaved{
+			Data: [][]float32{
+				{
+					math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
+					math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
+				},
+				{
+					math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
+					math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
+				},
+			},
+			Size: ChunkInfo{
+				Len:      2,
+				Channels: 2,
+			},
+		}
+		actual, err := decodeFloat32NonInterleaved(binary.LittleEndian, raw, 2)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !reflect.DeepEqual(expected, actual) {
+			t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual)
+		}
+	})
+}