nativeaudio

audio playback for Go
Log | Files | Refs | README | LICENSE

commit 10025629ef7bfecd4b0a0ed827d5390fddeac520
parent 7981a03eda14412c644a71b4cd9820fb5d097584
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Tue, 26 Oct 2021 21:53:09 +0800

nativeaudio: use built-in helpers at the cgo boundary

The performance gain is negligible so we shall prefer correctness.

BEFORE

goos: windows
goarch: amd64
pkg: git.sr.ht/~jackmordaunt/nativeaudio/internal/test
cpu: AMD Ryzen 5 3600 6-Core Processor
BenchmarkDecode/native-load-12               165           7272938 ns/op              88 B/op          3 allocs/op
BenchmarkDecode/native-decode-12             164           7075614 ns/op              72 B/op          2 allocs/op
BenchmarkDecode/ffmpeg-load-12                19          61365137 ns/op         2262458 B/op       1423 allocs/op

AFTER

goos: windows
goarch: amd64
pkg: git.sr.ht/~jackmordaunt/nativeaudio/internal/test
cpu: AMD Ryzen 5 3600 6-Core Processor
BenchmarkDecode/native-load-12               165           7371150 ns/op          606245 B/op          3 allocs/op
BenchmarkDecode/native-decode-12             159           7471619 ns/op          606250 B/op          3 allocs/op
BenchmarkDecode/ffmpeg-load-12                19          61339426 ns/op         2262489 B/op       1423 allocs/op

Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>

Diffstat:
Maudio_windows.go | 34+++++-----------------------------
1 file changed, 5 insertions(+), 29 deletions(-)

diff --git a/audio_windows.go b/audio_windows.go @@ -15,7 +15,6 @@ import "C" import ( "errors" "fmt" - "runtime" "strings" "unsafe" ) @@ -62,10 +61,8 @@ func load(path string) (uncompressed []byte, format Format, err error) { defer C.ErrorFree(r.Err) return nil, Format{}, collectErrors(r.Err) } - uncompressed = goBytes(unsafe.Pointer(r.Uncompressed.Data), int(r.Uncompressed.Len)) - runtime.SetFinalizer(&uncompressed, func(_ *[]byte) { - C.BufferFree(r.Uncompressed) - }) + defer C.BufferFree(r.Uncompressed) + uncompressed = C.GoBytes(unsafe.Pointer(r.Uncompressed.Data), C.int(r.Uncompressed.Len)) format = Format{ SampleRate: int(r.Format.SampleRate), BitDepth: int(r.Format.BitDepth), @@ -79,16 +76,13 @@ func load(path string) (uncompressed []byte, format Format, err error) { // // uncompressed is a read-only slice backed by a C buffer. Do not mutate. func decode(compressed []byte) (uncompressed []byte, format Format, err error) { - defer runtime.KeepAlive(compressed) - r := C.Decode(cBytes(compressed)) + r := C.Decode((*C.uchar)(C.CBytes(compressed)), C.uint(len(compressed))) if r.Err != nil && r.Err.Str != nil { defer C.ErrorFree(r.Err) return nil, format, collectErrors(r.Err) } - uncompressed = goBytes(unsafe.Pointer(r.Uncompressed.Data), int(r.Uncompressed.Len)) - runtime.SetFinalizer(&uncompressed, func(_ *[]byte) { - C.BufferFree(r.Uncompressed) - }) + defer C.BufferFree(r.Uncompressed) + uncompressed = C.GoBytes(unsafe.Pointer(r.Uncompressed.Data), C.int(r.Uncompressed.Len)) format = Format{ Channels: int(r.Format.Channels), BitDepth: int(r.Format.BitDepth), @@ -97,24 +91,6 @@ func decode(compressed []byte) (uncompressed []byte, format Format, err error) { return uncompressed, format, nil } -// goBytes returns a slice backed by a C byte array. -// -// [1 << 30] means assume backing array is 1GB, and then slice into it -// with length. -// -// If the data is larger than 1GB, allocate more memory. -func goBytes(ptr unsafe.Pointer, length int) []byte { - if length > 1<<30 { - return C.GoBytes(ptr, C.int(length)) - } - return (*[1 << 30]byte)(ptr)[:length:length] -} - -// cBytes returns a dynamic C byte array backed by a Go slice. -func cBytes(by []byte) (*C.uchar, C.uint) { - return (*C.uchar)(unsafe.Pointer(&by[0])), C.uint(len(by)) -} - // collectErrors unwraps all the errors in the chain and coalesces them // into a single Go error. func collectErrors(err *C.Error) error {