diff --git a/BGMApp/BGMApp/BGMPlayThrough.cpp b/BGMApp/BGMApp/BGMPlayThrough.cpp index 574a8bf..b47ef18 100644 --- a/BGMApp/BGMApp/BGMPlayThrough.cpp +++ b/BGMApp/BGMApp/BGMPlayThrough.cpp @@ -459,41 +459,32 @@ OSStatus BGMPlayThrough::BGMDeviceListenerProc(AudioObjectID inObjectID, { DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Got kAudioDevicePropertyDeviceIsRunning notification"); - auto deviceIsRunningHandler = [refCon] { - // IsRunning doesn't always return true when IO is starting. Not sure why. But using - // RunningSomewhereOtherThanBGMApp instead seems to be working so far. - // - //if(refCon->mInputDevice->IsRunning()) - if(RunningSomewhereOtherThanBGMApp(refCon->mInputDevice)) + // This is dispatched because it can block and + // - we might be on a real-time thread, or + // - BGMXPCListener::waitForOutputDeviceToStartWithReply might get called on the same thread just + // before this and timeout waiting for this to run. + // + // TODO: We should find a way to do this without dispatching because dispatching isn't real-time safe. + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ + if(refCon->mActive) { -#if DEBUG - refCon->mToldOutputDeviceToStartAt = mach_absolute_time(); -#endif - refCon->Start(); - } - }; - - CAMutex::Tryer stateTrier(refCon->mStateMutex); - if(stateTrier.HasLock()) - { - // In the vast majority of cases (when we actually start playthrough here) we get the state lock - // and can invoke the handler directly - deviceIsRunningHandler(); - } - else - { - // TODO: This should be rare, but we still shouldn't dispatch on the IO thread because it isn't - // real-time safe. - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ - if(refCon->mActive) + DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Handling " + "kAudioDevicePropertyDeviceIsRunning notification in dispatched block"); + CAMutex::Locker stateLocker(refCon->mStateMutex); + + // IsRunning doesn't always return true when IO is starting. Not sure why. But using + // RunningSomewhereOtherThanBGMApp instead seems to be working so far. + // + //if(refCon->mInputDevice->IsRunning()) + if(RunningSomewhereOtherThanBGMApp(refCon->mInputDevice)) { - DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Handling " - "kAudioDevicePropertyDeviceIsRunning notification in dispatched block"); - CAMutex::Locker stateLocker(refCon->mStateMutex); - deviceIsRunningHandler(); +#if DEBUG + refCon->mToldOutputDeviceToStartAt = mach_absolute_time(); +#endif + refCon->Start(); } - }); - } + } + }); } break; diff --git a/BGMApp/BGMApp/BGMXPCListener.mm b/BGMApp/BGMApp/BGMXPCListener.mm index 2549955..a4c19f2 100644 --- a/BGMApp/BGMApp/BGMXPCListener.mm +++ b/BGMApp/BGMApp/BGMXPCListener.mm @@ -175,8 +175,22 @@ } - (void) waitForOutputDeviceToStartWithReply:(void (^)(NSError*))reply { - OSStatus err = [audioDevices waitForOutputDeviceToStart]; NSString* description; + OSStatus err; + + try { + err = [audioDevices waitForOutputDeviceToStart]; + } catch (CAException e) { + DebugMsg("BGMXPCListener::waitForOutputDeviceToStartWithReply: Caught CAException (%d). Replying kBGMXPC_HardwareError.", + e.GetError()); + err = kBGMXPC_HardwareError; + } catch (...) { + DebugMsg("BGMXPCListener::waitForOutputDeviceToStartWithReply: Caught unknown exception. Replying kBGMXPC_InternalError."); + err = kBGMXPC_InternalError; +#if DEBUG + throw; +#endif + } switch (err) { case noErr: diff --git a/BGMDriver/BGMDriver/BGM_Device.cpp b/BGMDriver/BGMDriver/BGM_Device.cpp index 9f25ef7..c26a200 100644 --- a/BGMDriver/BGMDriver/BGM_Device.cpp +++ b/BGMDriver/BGMDriver/BGM_Device.cpp @@ -1821,51 +1821,58 @@ void BGM_Device::Control_SetPropertyData(AudioObjectID inObjectID, pid_t inClien void BGM_Device::StartIO(UInt32 inClientID) { - CAMutex::Locker theStateLocker(mStateMutex); + bool clientIsBGMApp, bgmAppHasClientRegistered; - // An overview of the process this function is part of: - // - A client starts IO. - // - The plugin host (the HAL) calls the StartIO function in BGM_PluginInterface, which calls this function. - // - BGMDriver sends a message to BGMApp telling it to start the (real) audio hardware. - // - BGMApp starts the hardware and, after the hardware is ready, replies to BGMDriver's message. - // - BGMDriver lets the host know that it's ready to do IO by returning from StartIO. - - // Update our client data. - // - // We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation also - // add this task to the queue and the updates should be done in order. - bool didStartIO = mTaskQueue.QueueSync_StartClientIO(&mClients, inClientID); - - // We only tell the hardware to start if this is the first time IO has been started - if(didStartIO) - { - kern_return_t theError = _HW_StartIO(); - ThrowIfKernelError(theError, - CAException(theError), - "BGM_Device::StartIO: Failed to start because of an error calling down to the driver."); + { + CAMutex::Locker theStateLocker(mStateMutex); - // We only return from StartIO after BGMApp is ready to pass the audio through to the output device. That way - // the HAL doesn't start sending us data before BGMApp can play it, which would mean we'd have to either drop - // frames or increase latency. - if(!mClients.IsBGMApp(inClientID) && mClients.BGMAppHasClientRegistered()) + // An overview of the process this function is part of: + // - A client starts IO. + // - The plugin host (the HAL) calls the StartIO function in BGM_PluginInterface, which calls this function. + // - BGMDriver sends a message to BGMApp telling it to start the (real) audio hardware. + // - BGMApp starts the hardware and, after the hardware is ready, replies to BGMDriver's message. + // - BGMDriver lets the host know that it's ready to do IO by returning from StartIO. + + // Update our client data. + // + // We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation + // also add this task to the queue and the updates should be done in order. + bool didStartIO = mTaskQueue.QueueSync_StartClientIO(&mClients, inClientID); + + // We only tell the hardware to start if this is the first time IO has been started. + if(didStartIO) { - UInt64 theXPCError = WaitForBGMAppToStartOutputDevice(); - - if(theXPCError == kBGMXPC_Success) - { - DebugMsg("BGM_Device::StartIO: Ready for IO."); - } - else if(theXPCError == kBGMXPC_MessageFailure) - { - // This most likely means BGMXPCHelper isn't installed or has crashed. IO will probably still work, - // but we may drop frames while the audio hardware starts up. - DebugMsg("BGM_Device::StartIO: Couldn't reach BGMApp via XPC. Attempting to start IO anyway."); - } - else - { - DebugMsg("BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu", theXPCError); - Throw(CAException(kAudioHardwareUnspecifiedError)); - } + kern_return_t theError = _HW_StartIO(); + ThrowIfKernelError(theError, + CAException(theError), + "BGM_Device::StartIO: Failed to start because of an error calling down to the driver."); + } + + clientIsBGMApp = mClients.IsBGMApp(inClientID); + bgmAppHasClientRegistered = mClients.BGMAppHasClientRegistered(); + } + + // We only return from StartIO after BGMApp is ready to pass the audio through to the output device. That way + // the HAL doesn't start sending us data before BGMApp can play it, which would mean we'd have to either drop + // frames or increase latency. + if(!clientIsBGMApp && bgmAppHasClientRegistered) + { + UInt64 theXPCError = WaitForBGMAppToStartOutputDevice(); + + if(theXPCError == kBGMXPC_Success) + { + DebugMsg("BGM_Device::StartIO: Ready for IO."); + } + else if(theXPCError == kBGMXPC_MessageFailure) + { + // This most likely means BGMXPCHelper isn't installed or has crashed. IO will probably still work, + // but we may drop frames while the audio hardware starts up. + DebugMsg("BGM_Device::StartIO: Couldn't reach BGMApp via XPC. Attempting to start IO anyway."); + } + else + { + DebugMsg("BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu", theXPCError); + Throw(CAException(kAudioHardwareUnspecifiedError)); } } } diff --git a/BGMDriver/BGMDriver/BGM_XPCHelper.m b/BGMDriver/BGMDriver/BGM_XPCHelper.m index a56fee9..ce7ec1e 100644 --- a/BGMDriver/BGMDriver/BGM_XPCHelper.m +++ b/BGMDriver/BGMDriver/BGM_XPCHelper.m @@ -84,8 +84,8 @@ UInt64 WaitForBGMAppToStartOutputDevice() theConnection.interruptionHandler = failureHandler; theConnection.invalidationHandler = failureHandler; - // This remote call to BGMXPCHelper will send a reply when the output device is ready to receive IO. Note that we shouldn't trust - // the reply string. + // This remote call to BGMXPCHelper will send a reply when the output device is ready to receive IO. Note that, for security + // reasons, we shouldn't trust the reply object. [[theConnection remoteObjectProxyWithErrorHandler:^(NSError* error) { #if !DEBUG #pragma unused (error)