nativeaudio

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

commit 91ddb316f6d25fff5af17efb71809a147905397d
parent c79f169bf107942fba9ce8208fc7d1673764af13
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Fri, 18 Sep 2026 13:15:44 -0400

audio: [macOS] report the buffer length, not its capacity

The size callback told AudioToolbox the file was cap(buf) bytes. A slice
built by append or returned by io.ReadAll usually has room to spare, so
the decoder was told there was more audio than there was, kept asking
for data past the end, and got short reads reported as success. It never
stopped: a generated WAV hung the decode until the test timeout.

The fixture this package has always tested with is embedded, where
capacity equals length, so the bug never showed. Every generated one has
spare capacity, and so does anything read with io.ReadAll, which is how
the file entry point builds its buffer.

The read callback now clamps the start as well as the end. Clamping only
the end left a start past the finish, which panics inside a C callback
and takes the process with it.

Diffstat:
Maudio_macos.go | 26++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/audio_macos.go b/audio_macos.go @@ -550,12 +550,23 @@ func AudioFileReadProcImpl( dst := outBuf[:req] - // It seems like the requested amount is allowed to exceed the - // actual size of the audio data. In that case we need to bound - // it by the length of the audio data. + // The requested amount is allowed to exceed the actual size of the + // audio data, and a read can start beyond the end of it, so clamp + // both ends. Clamping only the end would slice with the start past + // the finish and panic inside a C callback, which takes the process + // with it. + if pos < 0 { + pos = 0 + } + if pos > len(inBuf) { + pos = len(inBuf) + } if end > len(inBuf) { end = len(inBuf) } + if end < pos { + end = pos + } src := inBuf[pos:end] @@ -571,7 +582,14 @@ func AudioFileGetSizeProcImpl( inClientData unsafe.Pointer, ) C.SInt64 { inBuf := cgo.Handle(uintptr(inClientData)).Value().([]byte) - return C.SInt64(cap(inBuf)) + + // The length, not the capacity. A slice built by append or returned + // by io.ReadAll usually has room to spare, and reporting that as the + // file size tells AudioToolbox there is more audio than there is. It + // then keeps asking for data past the end, gets short reads reported + // as success, and never stops: a generated WAV hung the decode for + // five minutes before this was found. + return C.SInt64(len(inBuf)) } // unwrapOSStatus extracts the [OSStatus] from an [error] for conforming to C