mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-06 17:16:56 +08:00
Add raw audio decoder
* Add Int16Interleaved and Int16NonInterleaved formats * Add Float32Interleaved and Float32NonInterleaved formats * Add unit tests
This commit is contained in:
159
pkg/wave/decoder.go
Normal file
159
pkg/wave/decoder.go
Normal file
@@ -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
|
||||||
|
}
|
314
pkg/wave/decoder_test.go
Normal file
314
pkg/wave/decoder_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user