Skip to content

Commit

Permalink
Revert "oto: remove Cgo on darwin (#178)"
Browse files Browse the repository at this point in the history
This reverts commit 28d09e3.

Reason: Test failures

Updates hajimehoshi/ebiten#2268
  • Loading branch information
hajimehoshi committed Aug 24, 2022
1 parent c5116a8 commit 66bc676
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 199 deletions.
117 changes: 0 additions & 117 deletions api_audio_darwin.go

This file was deleted.

84 changes: 49 additions & 35 deletions driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,62 @@

package oto

// #cgo LDFLAGS: -framework AudioToolbox
//
// #import <AudioToolbox/AudioToolbox.h>
//
// void oto_render(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
//
// void oto_setNotificationHandler();
import "C"

import (
"fmt"
"sync"
"time"
"unsafe"

"github.com/ebitengine/purego/objc"

"github.com/hajimehoshi/oto/v2/internal/mux"
)

const (
float32SizeInBytes = 4
)

noErr = 0
const (
avAudioSessionErrorCodeCannotStartPlaying = 0x21706c61 // '!pla'
avAudioSessionErrorCodeSiriIsRecording = 0x73697269 // 'siri'
)

func newAudioQueue(sampleRate, channelCount, bitDepthInBytes int) (_AudioQueueRef, []_AudioQueueBufferRef, error) {
desc := _AudioStreamBasicDescription{
mSampleRate: float64(sampleRate),
mFormatID: uint32(kAudioFormatLinearPCM),
mFormatFlags: uint32(kAudioFormatFlagIsFloat),
mBytesPerPacket: uint32(channelCount * float32SizeInBytes),
func newAudioQueue(sampleRate, channelCount, bitDepthInBytes int) (C.AudioQueueRef, []C.AudioQueueBufferRef, error) {
desc := C.AudioStreamBasicDescription{
mSampleRate: C.double(sampleRate),
mFormatID: C.kAudioFormatLinearPCM,
mFormatFlags: C.kAudioFormatFlagIsFloat,
mBytesPerPacket: C.UInt32(channelCount * float32SizeInBytes),
mFramesPerPacket: 1,
mBytesPerFrame: uint32(channelCount * float32SizeInBytes),
mChannelsPerFrame: uint32(channelCount),
mBitsPerChannel: uint32(8 * float32SizeInBytes),
mBytesPerFrame: C.UInt32(channelCount * float32SizeInBytes),
mChannelsPerFrame: C.UInt32(channelCount),
mBitsPerChannel: C.UInt32(8 * float32SizeInBytes),
}

var audioQueue _AudioQueueRef
if osstatus := _AudioQueueNewOutput(
var audioQueue C.AudioQueueRef
if osstatus := C.AudioQueueNewOutput(
&desc,
oto_render,
(C.AudioQueueOutputCallback)(C.oto_render),
nil,
0, //CFRunLoopRef
0, //CFStringRef
(C.CFRunLoopRef)(0),
(C.CFStringRef)(0),
0,
&audioQueue); osstatus != noErr {
return 0, nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
&audioQueue); osstatus != C.noErr {
return nil, nil, fmt.Errorf("oto: AudioQueueNewFormat with StreamFormat failed: %d", osstatus)
}

bufs := make([]_AudioQueueBufferRef, 0, 4)
bufs := make([]C.AudioQueueBufferRef, 0, 4)
for len(bufs) < cap(bufs) {
var buf _AudioQueueBufferRef
if osstatus := _AudioQueueAllocateBuffer(audioQueue, bufferSizeInBytes, &buf); osstatus != noErr {
return 0, nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
var buf C.AudioQueueBufferRef
if osstatus := C.AudioQueueAllocateBuffer(audioQueue, bufferSizeInBytes, &buf); osstatus != C.noErr {
return nil, nil, fmt.Errorf("oto: AudioQueueAllocateBuffer failed: %d", osstatus)
}
buf.mAudioDataByteSize = bufferSizeInBytes
bufs = append(bufs, buf)
Expand All @@ -69,16 +79,16 @@ func newAudioQueue(sampleRate, channelCount, bitDepthInBytes int) (_AudioQueueRe
}

type context struct {
audioQueue _AudioQueueRef
unqueuedBuffers []_AudioQueueBufferRef
audioQueue C.AudioQueueRef
unqueuedBuffers []C.AudioQueueBufferRef

cond *sync.Cond

mux *mux.Mux
err atomicError
}

// TODO: Convert the error code correctly.
// TOOD: Convert the error code correctly.
// See https://stackoverflow.com/questions/2196869/how-do-you-convert-an-iphone-osstatus-code-to-something-useful

var theContext *context
Expand All @@ -100,11 +110,11 @@ func newContext(sampleRate, channelCount, bitDepthInBytes int) (*context, chan s
c.audioQueue = q
c.unqueuedBuffers = bs

setNotificationHandler()
C.oto_setNotificationHandler()

var retryCount int
try:
if osstatus := _AudioQueueStart(c.audioQueue, nil); osstatus != noErr {
if osstatus := C.AudioQueueStart(c.audioQueue, nil); osstatus != C.noErr {
if osstatus == avAudioSessionErrorCodeCannotStartPlaying && retryCount < 100 {
time.Sleep(10 * time.Millisecond)
retryCount++
Expand Down Expand Up @@ -152,10 +162,10 @@ func (c *context) appendBuffer(buf32 []float32) {

c.mux.ReadFloat32s(buf32)
for i, f := range buf32 {
*(*float32)(unsafe.Pointer(buf.mAudioData + uintptr(i)*float32SizeInBytes)) = f
*(*float32)(unsafe.Pointer(uintptr(buf.mAudioData) + uintptr(i)*float32SizeInBytes)) = f
}

if osstatus := _AudioQueueEnqueueBuffer(c.audioQueue, buf, 0, nil); osstatus != noErr {
if osstatus := C.AudioQueueEnqueueBuffer(c.audioQueue, buf, 0, nil); osstatus != C.noErr {
c.err.TryStore(fmt.Errorf("oto: AudioQueueEnqueueBuffer failed: %d", osstatus))
}
}
Expand All @@ -167,7 +177,8 @@ func (c *context) Suspend() error {
if err := c.err.Load(); err != nil {
return err.(error)
}
if osstatus := _AudioQueuePause(c.audioQueue); osstatus != noErr {

if osstatus := C.AudioQueuePause(c.audioQueue); osstatus != C.noErr {
return fmt.Errorf("oto: AudioQueuePause failed: %d", osstatus)
}
return nil
Expand All @@ -183,7 +194,7 @@ func (c *context) Resume() error {

var retryCount int
try:
if osstatus := _AudioQueueStart(c.audioQueue, nil); osstatus != noErr {
if osstatus := C.AudioQueueStart(c.audioQueue, nil); osstatus != C.noErr {
if osstatus == avAudioSessionErrorCodeCannotStartPlaying && retryCount < 100 {
time.Sleep(10 * time.Millisecond)
retryCount++
Expand All @@ -205,17 +216,20 @@ func (c *context) Err() error {
return nil
}

func oto_render(inUserData unsafe.Pointer, inAQ _AudioQueueRef, inBuffer _AudioQueueBufferRef) {
//export oto_render
func oto_render(inUserData unsafe.Pointer, inAQ C.AudioQueueRef, inBuffer C.AudioQueueBufferRef) {
theContext.cond.L.Lock()
defer theContext.cond.L.Unlock()
theContext.unqueuedBuffers = append(theContext.unqueuedBuffers, inBuffer)
theContext.cond.Signal()
}

func oto_setGlobalPause(self objc.ID, _cmd objc.SEL, notification objc.ID) {
//export oto_setGlobalPause
func oto_setGlobalPause() {
theContext.Suspend()
}

func oto_setGlobalResume(self objc.ID, _cmd objc.SEL, notification objc.ID) {
//export oto_setGlobalResume
func oto_setGlobalResume() {
theContext.Resume()
}
6 changes: 0 additions & 6 deletions driver_ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,3 @@ package oto
// '4' is float32 size in bytes. '2' is a number of channels for stereo.

const bufferSizeInBytes = 12288

func setNotificationHandler() {
// AVAudioSessionInterruptionNotification is not reliable on iOS. Rely on
// applicationWillResignActive and applicationDidBecomeActive instead. See
// https://stackoverflow.com/questions/24404463/ios-siri-not-available-does-not-return-avaudiosessioninterruptionoptionshouldre
}
23 changes: 23 additions & 0 deletions driver_ios.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2021 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.

//go:build ios
// +build ios

void oto_setNotificationHandler() {
// AVAudioSessionInterruptionNotification is not reliable on iOS. Rely on
// applicationWillResignActive and applicationDidBecomeActive instead. See
// https://stackoverflow.com/questions/24404463/ios-siri-not-available-does-not-return-avaudiosessioninterruptionoptionshouldre
return;
}
37 changes: 2 additions & 35 deletions driver_macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,7 @@

package oto

import (
"unsafe"

"github.com/ebitengine/purego"
"github.com/ebitengine/purego/objc"
)
// #cgo LDFLAGS: -framework AppKit
import "C"

const bufferSizeInBytes = 2048

var appkit = purego.Dlopen("/System/Library/Frameworks/AppKit.framework/Versions/Current/AppKit", purego.RTLD_GLOBAL)

// setNotificationHandler sets a handler for sleep/wake notifications.
func setNotificationHandler() {
// Create the Observer object
class := objc.AllocateClassPair(objc.GetClass("NSObject\x00"), "OtoNotificationObserver\x00", 0)
class.AddMethod(objc.RegisterName("receiveSleepNote:\x00"), objc.IMP(oto_setGlobalPause), "v@:@\x00")
class.AddMethod(objc.RegisterName("receiveWakeNote:\x00"), objc.IMP(oto_setGlobalResume), "v@:@\x00")
class.Register()

observer := objc.ID(class).Send(objc.RegisterName("new\x00"))

notificationCenter := objc.ID(objc.GetClass("NSWorkspace\x00")).Send(objc.RegisterName("sharedWorkspace\x00")).Send(objc.RegisterName("notificationCenter\x00"))
notificationCenter.Send(objc.RegisterName("addObserver:selector:name:object:\x00"),
observer,
objc.RegisterName("receiveSleepNote:\x00"),
// Dlsym returns a pointer to the object so dereference it
*(*uintptr)(unsafe.Pointer(purego.Dlsym(appkit, "NSWorkspaceWillSleepNotification"))),
0,
)
notificationCenter.Send(objc.RegisterName("addObserver:selector:name:object:\x00"),
observer,
objc.RegisterName("receiveWakeNote:\x00"),
// Dlsym returns a pointer to the object so dereference it
*(*uintptr)(unsafe.Pointer(purego.Dlsym(appkit, "NSWorkspaceDidWakeNotification"))),
0,
)
}
Loading

0 comments on commit 66bc676

Please sign in to comment.