Repository: pion/mediadevices
Pull request #181 (2020-06-07 13:55:19)

Add fast wave decoder
* Add a minor fix for non interleaved formats
* Add decoder benchmark for host vs non-host endian

lherman-cs: Performance different is about 4x-6x!

```
goos: darwin
goarch: amd64
pkg: github.com/pion/mediadevices/pkg/wave
BenchmarkDecoder
BenchmarkDecoder/Int16InterleavedHostEndian
BenchmarkDecoder/Int16InterleavedHostEndian-4            2160073               553 ns/op
BenchmarkDecoder/Int16InterleavedNonHostEndian
BenchmarkDecoder/Int16InterleavedNonHostEndian-4          379941              2860 ns/op
BenchmarkDecoder/Int16NonInterleavedHostEndian
BenchmarkDecoder/Int16NonInterleavedHostEndian-4         2685244               448 ns/op
BenchmarkDecoder/Int16NonInterleavedNonHostEndian
BenchmarkDecoder/Int16NonInterleavedNonHostEndian-4       466608              2349 ns/op
BenchmarkDecoder/Float32InterleavedHostEndian
BenchmarkDecoder/Float32InterleavedHostEndian-4          3347197               359 ns/op
BenchmarkDecoder/Float32InterleavedNonHostEndian
BenchmarkDecoder/Float32InterleavedNonHostEndian-4        789805              1512 ns/op
BenchmarkDecoder/Float32NonInterleavedHostEndian
BenchmarkDecoder/Float32NonInterleavedHostEndian-4       2681362               444 ns/op
BenchmarkDecoder/Float32NonInterleavedNonHostEndian
BenchmarkDecoder/Float32NonInterleavedNonHostEndian-4     667420              1534 ns/op
```

Patch:
diff --git a/pkg/wave/decoder.go b/pkg/wave/decoder.go
index cbb5e2d..f33debe 100644
--- a/pkg/wave/decoder.go
+++ b/pkg/wave/decoder.go
@@ -4,6 +4,8 @@ import (
 	"encoding/binary"
 	"fmt"
 	"math"
+	"reflect"
+	"unsafe"
 )
 
 // Format represents how audio is formatted in memory
@@ -16,6 +18,19 @@ const (
 	FormatFloat32NonInterleaved        = "Float32NonInterleaved"
 )
 
+var hostEndian binary.ByteOrder
+
+func init() {
+	switch v := *(*uint16)(unsafe.Pointer(&([]byte{0x12, 0x34}[0]))); v {
+	case 0x1234:
+		hostEndian = binary.BigEndian
+	case 0x3412:
+		hostEndian = binary.LittleEndian
+	default:
+		panic(fmt.Sprintf("failed to determine host endianness: %x", v))
+	}
+}
+
 // Decoder decodes raw chunk to Audio
 type Decoder interface {
 	// Decode decodes raw chunk in endian byte order
@@ -78,6 +93,15 @@ func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int)
 	}
 
 	container := NewInt16Interleaved(chunkInfo)
+
+	if endian == hostEndian {
+		n := len(chunk)
+		h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[0])), Len: n, Cap: n}
+		dst := *(*[]byte)(unsafe.Pointer(&h))
+		copy(dst, chunk)
+		return container, nil
+	}
+
 	sampleLen := sampleSize * channels
 	var i int
 	for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen {
@@ -101,6 +125,17 @@ func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels i
 
 	container := NewInt16NonInterleaved(chunkInfo)
 	chunkLen := len(chunk) / channels
+
+	if endian == hostEndian {
+		for ch := 0; ch < channels; ch++ {
+			offset := ch * chunkLen
+			h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[ch][0])), Len: chunkLen, Cap: chunkLen}
+			dst := *(*[]byte)(unsafe.Pointer(&h))
+			copy(dst, chunk[offset:offset+chunkLen])
+		}
+		return container, nil
+	}
+
 	for ch := 0; ch < channels; ch++ {
 		offset := ch * chunkLen
 		for i := 0; i < chunkInfo.Len; i++ {
@@ -121,6 +156,15 @@ func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels in
 	}
 
 	container := NewFloat32Interleaved(chunkInfo)
+
+	if endian == hostEndian {
+		n := len(chunk)
+		h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[0])), Len: n, Cap: n}
+		dst := *(*[]byte)(unsafe.Pointer(&h))
+		copy(dst, chunk)
+		return container, nil
+	}
+
 	sampleLen := sampleSize * channels
 	var i int
 	for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen {
@@ -145,6 +189,17 @@ func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels
 
 	container := NewFloat32NonInterleaved(chunkInfo)
 	chunkLen := len(chunk) / channels
+
+	if endian == hostEndian {
+		for ch := 0; ch < channels; ch++ {
+			offset := ch * chunkLen
+			h := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&container.Data[ch][0])), Len: chunkLen, Cap: chunkLen}
+			dst := *(*[]byte)(unsafe.Pointer(&h))
+			copy(dst, chunk[offset:offset+chunkLen])
+		}
+		return container, nil
+	}
+
 	for ch := 0; ch < channels; ch++ {
 		offset := ch * chunkLen
 		for i := 0; i < chunkInfo.Len; i++ {


Test patch:
diff --git a/pkg/wave/decoder_bench_test.go b/pkg/wave/decoder_bench_test.go
new file mode 100644
index 0000000..b86c936
--- /dev/null
+++ b/pkg/wave/decoder_bench_test.go
@@ -0,0 +1,49 @@
+package wave
+
+import (
+	"encoding/binary"
+	"fmt"
+	"testing"
+)
+
+func BenchmarkDecoder(b *testing.B) {
+	formats := []Format{
+		FormatInt16Interleaved,
+		FormatInt16NonInterleaved,
+		FormatFloat32Interleaved,
+		FormatFloat32NonInterleaved,
+	}
+
+	var nonHostEndian binary.ByteOrder
+	if hostEndian == binary.BigEndian {
+		nonHostEndian = binary.LittleEndian
+	} else {
+		nonHostEndian = binary.BigEndian
+	}
+
+	for _, format := range formats {
+		format := format
+		decoder, err := NewDecoder(format)
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		b.Run(fmt.Sprintf("%sHostEndian", format), func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				_, err := decoder.Decode(hostEndian, make([]byte, 800), 2)
+				if err != nil {
+					b.Fatal(err)
+				}
+			}
+		})
+
+		b.Run(fmt.Sprintf("%sNonHostEndian", format), func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				_, err := decoder.Decode(nonHostEndian, make([]byte, 800), 2)
+				if err != nil {
+					b.Fatal(err)
+				}
+			}
+		})
+	}
+}