// Copyright 2019 The Oto Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !js package oto // #cgo LDFLAGS: -framework AudioToolbox // // #import // // void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer); // // void oto_setNotificationHandler(); // bool oto_isBackground(void); import "C" import ( "fmt" "runtime" "sync" "time" "unsafe" ) const baseQueueBufferSize = 1024 type audioInfo struct { channelNum int bitDepthInBytes int } type driver struct { audioQueue C.AudioQueueRef buf []byte bufSize int sampleRate int audioInfo *audioInfo buffers []C.AudioQueueBufferRef paused bool lastPauseTime time.Time err error chWrite chan []byte chWritten chan int m sync.Mutex } var ( theDriver *driver driverM sync.Mutex ) func setDriver(d *driver) { driverM.Lock() defer driverM.Unlock() if theDriver != nil && d != nil { panic("oto: at most one driver object can exist") } theDriver = d if d != nil { setNotificationHandler(d) } } func getDriver() *driver { driverM.Lock() defer driverM.Unlock() return theDriver } // TOOD: Convert the error code correctly. // See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (tryWriteCloser, error) { flags := C.kAudioFormatFlagIsPacked if bitDepthInBytes != 1 { flags |= C.kAudioFormatFlagIsSignedInteger } desc := C.AudioStreamBasicDescription{ mSampleRate: C.double(sampleRate), mFormatID: C.kAudioFormatLinearPCM, mFormatFlags: C.UInt32(flags), mBytesPerPacket: C.UInt32(channelNum * bitDepthInBytes), mFramesPerPacket: 1, mBytesPerFrame: C.UInt32(channelNum * bitDepthInBytes), mChannelsPerFrame: C.UInt32(channelNum), mBitsPerChannel: C.UInt32(8 * bitDepthInBytes), } audioInfo := &audioInfo{ channelNum: channelNum, bitDepthInBytes: bitDepthInBytes, } var audioQueue C.AudioQueueRef if osstatus := C.AudioQueueNewOutput( &desc, (C.AudioQueueOutputCallback)(C.oto_render), unsafe.Pointer(audioInfo), (C.CFRunLoopRef)(0), (C.CFStringRef)(0), 0, &audioQueue); osstatus != C.noErr { return nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus) } queueBufferSize := baseQueueBufferSize * channelNum * bitDepthInBytes nbuf := bufferSizeInBytes / queueBufferSize if nbuf <= 1 { nbuf = 2 } d := &driver{ audioQueue: audioQueue, sampleRate: sampleRate, audioInfo: audioInfo, bufSize: nbuf * queueBufferSize, buffers: make([]C.AudioQueueBufferRef, nbuf), chWrite: make(chan []byte), chWritten: make(chan int), } runtime.SetFinalizer(d, (*driver).Close) // Set the driver before setting the rendering callback. setDriver(d) for i := 0; i < len(d.buffers); i++ { var buf C.AudioQueueBufferRef if osstatus := C.AudioQueueAllocateBuffer(audioQueue, C.UInt32(queueBufferSize), &buf); osstatus != C.noErr { return nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus) } d.buffers[i] = buf d.buffers[i].mAudioDataByteSize = C.UInt32(queueBufferSize) for j := 0; j < queueBufferSize; j++ { *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(d.buffers[i].mAudioData)) + uintptr(j))) = 0 } if osstatus := C.AudioQueueEnqueueBuffer(audioQueue, d.buffers[i], 0, nil); osstatus != C.noErr { return nil, fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus) } } for C.oto_isBackground() { time.Sleep(time.Second) } if osstatus := C.AudioQueueStart(audioQueue, nil); osstatus != C.noErr { return nil, fmt.Errorf("oto: AudioQueueStart failed: %d", osstatus) } return d, nil } //export oto_render func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) { audioInfo := (*audioInfo)(inUserData) queueBufferSize := baseQueueBufferSize * audioInfo.channelNum * audioInfo.bitDepthInBytes d := getDriver() var buf []byte // Set the timer. When the input does not come, the audio must be paused. s := time.Second * time.Duration(queueBufferSize) / time.Duration(d.sampleRate*d.audioInfo.channelNum*d.audioInfo.bitDepthInBytes) t := time.NewTicker(s) defer t.Stop() ch := t.C for len(buf) < queueBufferSize { select { case dbuf := <-d.chWrite: for !d.resume(false) { d.m.Lock() err := d.err d.m.Unlock() if err != nil { return } time.Sleep(time.Second) } n := queueBufferSize - len(buf) if n > len(dbuf) { n = len(dbuf) } buf = append(buf, dbuf[:n]...) d.chWritten <- n case <-ch: d.pause() ch = nil } } for i := 0; i < queueBufferSize; i++ { *(*byte)(unsafe.Pointer(uintptr(inBuffer.mAudioData) + uintptr(i))) = buf[i] } // Do not update mAudioDataByteSize, or the buffer is not used correctly any more. d.enqueueBuffer(inBuffer) } func (d *driver) TryWrite(data []byte) (int, error) { d.m.Lock() err := d.err d.m.Unlock() if err != nil { return 0, err } n := d.bufSize - len(d.buf) if n > len(data) { n = len(data) } d.buf = append(d.buf, data[:n]...) // Use the buffer only when the buffer length is enough to avoid choppy sound. queueBufferSize := baseQueueBufferSize * d.audioInfo.channelNum * d.audioInfo.bitDepthInBytes for len(d.buf) >= queueBufferSize { d.chWrite <- d.buf n := <-d.chWritten d.buf = d.buf[n:] } return n, nil } func (d *driver) Close() error { d.m.Lock() defer d.m.Unlock() runtime.SetFinalizer(d, nil) if osstatus := C.AudioQueueStop(d.audioQueue, C.false); osstatus != C.noErr { return fmt.Errorf("oto: AudioQueueStop failed: %d", osstatus) } if osstatus := C.AudioQueueDispose(d.audioQueue, C.false); osstatus != C.noErr { return fmt.Errorf("oto: AudioQueueDispose failed: %d", osstatus) } d.audioQueue = nil setDriver(nil) return nil } func (d *driver) enqueueBuffer(buffer C.AudioQueueBufferRef) { d.m.Lock() defer d.m.Unlock() if osstatus := C.AudioQueueEnqueueBuffer(d.audioQueue, buffer, 0, nil); osstatus != C.noErr && d.err == nil { d.err = fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus) return } } func (d *driver) resume(afterSleep bool) bool { d.m.Lock() defer d.m.Unlock() // Audio doesn't work soon after recovering from sleeping. Wait for a while // (hajimehoshi/ebiten#1259). if afterSleep { // After short-time sleeping, 500ms more sleeping is enough. However, after long-time sleeping, it // looks like 1 second more sleeping are required (hajimehoshi/ebiten#1280). // This is tested on MacBook Pro 2020 macOS 10.15.6. if time.Now().Sub(d.lastPauseTime) < 30*time.Second { time.Sleep(500 * time.Millisecond) } else { time.Sleep(time.Second) } } if C.oto_isBackground() { return false } if osstatus := C.AudioQueueStart(d.audioQueue, nil); osstatus != C.noErr && d.err == nil { d.err = fmt.Errorf("oto: AudioQueueStart for resuming failed: %d", osstatus) return false } d.paused = false return true } func (d *driver) pause() { d.m.Lock() defer d.m.Unlock() if d.paused { return } if osstatus := C.AudioQueuePause(d.audioQueue); osstatus != C.noErr && d.err == nil { d.err = fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus) return } d.paused = true d.lastPauseTime = time.Now() } func (d *driver) setError(err error) { d.m.Lock() defer d.m.Unlock() if theDriver.err != nil { return } theDriver.err = err } func (d *driver) tryWriteCanReturnWithoutWaiting() bool { return true } func setNotificationHandler(driver *driver) { C.oto_setNotificationHandler() } //export oto_setGlobalPause func oto_setGlobalPause() { theDriver.pause() } //export oto_setGlobalResume func oto_setGlobalResume() { theDriver.resume(true) } //export oto_setErrorByNotification func oto_setErrorByNotification(s C.OSStatus, from *C.char) { gofrom := C.GoString(from) theDriver.setError(fmt.Errorf("oto: %s at notification failed: %d", gofrom, s)) }