From e988ea99451cb487d7d6bda1a2ad3171ec04a597 Mon Sep 17 00:00:00 2001 From: Steve Checkoway Date: Fri, 17 Nov 2006 16:59:22 +0000 Subject: [PATCH] Survive device changes, (external) sample rate changes, etc. --- .../src/arch/Sound/RageSoundDriver_AU.cpp | 383 ++++++------------ stepmania/src/arch/Sound/RageSoundDriver_AU.h | 25 +- 2 files changed, 137 insertions(+), 271 deletions(-) diff --git a/stepmania/src/arch/Sound/RageSoundDriver_AU.cpp b/stepmania/src/arch/Sound/RageSoundDriver_AU.cpp index a8c42805db..486951a8d2 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_AU.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_AU.cpp @@ -13,11 +13,8 @@ static const UInt32 kBytesPerPacket = kChannelsPerFrame * kBitsPerChannel / 8; static const UInt32 kBytesPerFrame = kBytesPerPacket; static const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; - -RageSound_AU::RageSound_AU() : m_OutputUnit(NULL), m_OutputDevice(NULL), m_iSampleRate(0), m_fLatency(0.0f), - m_iLastSampleTime(0), m_iOffset(0), m_bDone(false), m_pIOThread(NULL), m_Semaphore("Sound shutdown") -{ -} +#define WERROR(str, num, extra...) str ": '%s' (%lu).", ## extra, FourCCToString(num).c_str(), (num) +#define ERROR(str, num, extra...) (ssprintf(WERROR(str, (num), ## extra))) static inline RString FourCCToString( uint32_t num ) { @@ -39,15 +36,27 @@ static inline RString FourCCToString( uint32_t num ) return s; } -#define WERROR(str, num, extra...) str ": '%s' (%lu).", ## extra, FourCCToString(num).c_str(), (num) -#define ERROR(str, num, extra...) (ssprintf(WERROR(str, (num), ## extra))) + +RageSound_AU::RageSound_AU() : m_OutputUnit(NULL), m_iSampleRate(0), m_bDone(false), m_bStarted(false), + m_pIOThread(NULL), m_pNotificationThread(NULL), m_Semaphore("Sound") +{ +} + + +void RageSound_AU::NameHALThread( CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *inRefCon ) +{ + RageSound_AU *This = (RageSound_AU *)inRefCon; + + This->m_pNotificationThread = new RageThreadRegister( "HAL notification thread" ); + This->m_Semaphore.Post(); +} RString RageSound_AU::Init() { ComponentDescription desc; desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; @@ -90,6 +99,9 @@ RString RageSound_AU::Init() if( streamFormat.mSampleRate <= 0 ) streamFormat.mSampleRate = 44100.0; m_iSampleRate = int( streamFormat.mSampleRate ); + + // XXX Try to set the hardware sample rate. + error = AudioUnitSetProperty( m_OutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, @@ -109,112 +121,55 @@ RString RageSound_AU::Init() if( error != noErr ) LOG->Warn( WERROR("Failed to set the maximum render quality", error) ); - - UInt32 data = 1; - error = AudioUnitSetProperty( m_OutputUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, - 0, - &data, - sizeof(data) ); - if( error != noErr ) - return ERROR( "Could not enable output I/O", error ); - - UInt32 size = sizeof(AudioDeviceID); - - error = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &size, &m_OutputDevice ); - if( error != noErr ) - return ERROR( "Could not get the default output device", error ); - - error = AudioUnitSetProperty( m_OutputUnit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &m_OutputDevice, - sizeof(AudioDeviceID) ); - if( error != noErr ) - return ERROR( "Could not set the default output device", error ); - - // XXX set channel map -#if 1 - data = 0; - error = AudioUnitSetProperty( m_OutputUnit, - 2007, //kAudioOutputUnitProperty_StartTimestampsAtZero, - kAudioUnitScope_Global, - 0, - &data, - sizeof(data) ); - if( error != noErr ) - return ERROR( "Could not set AudioUnit to use the device's time stamps", error ); -#endif - // Initialize the AU. error = AudioUnitInitialize( m_OutputUnit ); if( error != noErr ) return ERROR( "Could not initialize the AudioUnit", error ); - Float64 latency = 0.0; - - size = sizeof(latency); - error = AudioUnitGetProperty( m_OutputUnit, - kAudioUnitProperty_Latency, - kAudioUnitScope_Global, - 0, - &latency, - &size ); - if( error != noErr ) - LOG->Warn( WERROR("Could not get latency", error) ); - LOG->Info( "Latency: %lf", latency ); - m_fLatency = latency; - - // Add property listeners. - for( AudioUnitPropertyID prop = kAudioUnitProperty_ClassInfo; prop <= kAudioUnitProperty_PresentPreset; ++prop ) - AddAUPropertyListener( prop ); - for( AudioUnitPropertyID prop = kAudioOutputUnitProperty_CurrentDevice; prop <= kAudioOutputUnitProperty_HasIO; ++prop ) - AddAUPropertyListener( prop ); -#define ADD(x) AddADPropertyListener( kAudioDeviceProperty ## x ) -#ifdef DEBUG - AddADPropertyListener( kAudioPropertyWildcardPropertyID ); -#else - ADD( DeviceIsAlive ); - ADD( DeviceHasChanged ); - ADD( DeviceIsRunning ); - ADD( DeviceIsRunningSomewhere ); - AddADPropertyListener( kAudioDeviceProcessorOverload ); - ADD( Streams ); - ADD( StreamConfiguration ); - ADD( StreamFormat ); -#endif -#undef ADD - StartDecodeThread(); + + // Get the HAL's runloop and attach an observer. + CFRunLoopObserverRef observerRef; + CFRunLoopRef runLoopRef; + CFRunLoopObserverContext context = { 0, this, NULL, NULL, NULL }; + UInt32 size = sizeof( CFRunLoopRef ); + + if( (error = AudioHardwareGetProperty(kAudioHardwarePropertyRunLoop, &size, &runLoopRef)) ) + return ERROR( "Couldn't get the HAL's run loop", error); + + observerRef = CFRunLoopObserverCreate( kCFAllocatorDefault, kCFRunLoopAllActivities, false, 0, NameHALThread, &context ); + CFRunLoopAddObserver( runLoopRef, observerRef, kCFRunLoopDefaultMode ); + CFRunLoopWakeUp( runLoopRef ); + m_Semaphore.Wait(); + CFRunLoopObserverInvalidate( observerRef ); + CFRelease( observerRef ); + error = AudioOutputUnitStart( m_OutputUnit ); if( error != noErr ) return ERROR( "Could not start the AudioUnit", error ); + m_bStarted = true; return RString(); } RageSound_AU::~RageSound_AU() { - ShutDown(); + if( !m_OutputUnit ) + return; + if( m_bStarted ) + { + m_bDone = true; + m_Semaphore.Wait(); + } + AudioUnitUninitialize( m_OutputUnit ); + CloseComponent( m_OutputUnit ); delete m_pIOThread; + delete m_pNotificationThread; } int64_t RageSound_AU::GetPosition( const RageSoundBase *sound ) const { - AudioTimeStamp time; - OSStatus error; - - if( m_OutputDevice == NULL ) - return m_iLastSampleTime; - if( (error = AudioDeviceGetCurrentTime(m_OutputDevice, &time)) ) - { - if( error != kAudioHardwareNotRunningError ) - FAIL_M( ERROR("GetCurrentTime() failed", error) ); - return m_iLastSampleTime; - } - m_iLastSampleTime = int64_t( time.mSampleTime ) + m_iOffset; - return m_iLastSampleTime; + double scale = m_iSampleRate / AudioGetHostClockFrequency(); + return int64_t( scale * AudioGetCurrentHostTime() ); } @@ -224,46 +179,86 @@ void RageSound_AU::SetupDecodingThread() SetThreadPrecedence( 0.75f ); } -void RageSound_AU::ShutDown() +float RageSound_AU::GetPlayLatency() const { - if( !m_OutputUnit ) - return; - FOREACH_CONST( AudioUnitPropertyID, m_vAUProperties, prop ) - AudioUnitRemovePropertyListener( m_OutputUnit, *prop, PropertyChanged ); - FOREACH_CONST( AudioDevicePropertyID, m_vADProperties, prop ) - AudioDeviceRemovePropertyListener( m_OutputDevice, 0, false, *prop, DevicePropertyChanged ); - m_bDone = true; - m_Semaphore.Wait(); - m_vADProperties.clear(); - m_vAUProperties.clear(); - AudioUnitUninitialize( m_OutputUnit ); - CloseComponent( m_OutputUnit ); - m_OutputUnit = NULL; - m_OutputDevice = NULL; - m_iSampleRate = 0; -} - -void RageSound_AU::AddAUPropertyListener( AudioUnitPropertyID prop ) -{ - ComponentResult result = AudioUnitAddPropertyListener( m_OutputUnit, prop, PropertyChanged, this ); - - if( result ) - LOG->Warn( "Failed to add property listener for property %d.", int(prop) ); - else - m_vAUProperties.push_back( prop ); -} - -void RageSound_AU::AddADPropertyListener( AudioDevicePropertyID prop ) -{ - OSStatus error = AudioDeviceAddPropertyListener( m_OutputDevice, kAudioPropertyWildcardChannel, false, - prop, DevicePropertyChanged, this ); + OSStatus error; + UInt32 bufferSize; + AudioDeviceID OutputDevice; + UInt32 size = sizeof( AudioDeviceID ); + Float64 sampleRate; - if( error ) - LOG->Warn( WERROR("Failed to add device property listener for property %s", - error, FourCCToString(prop).c_str()) ); - else - m_vADProperties.push_back( prop ); + if( (error = AudioUnitGetProperty(m_OutputUnit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &OutputDevice, &size)) ) + { + LOG->Warn( WERROR("No output device", error) ); + return 0.0f; + } + + size = sizeof( Float64 ); + if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sampleRate)) ) + { + LOG->Warn( WERROR("Couldn't get the device sample rate", error) ); + return 0.0f; + } + + size = sizeof( UInt32 ); + if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyBufferFrameSize, &size, &bufferSize)) ) + { + LOG->Warn( WERROR("Couldn't determine buffer size", error) ); + bufferSize = 0; + } + + UInt32 frames; + + size = sizeof( UInt32 ); + if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyLatency, &size, &frames)) ) + { + LOG->Warn( WERROR( "Couldn't get device latency", error) ); + frames = 0; + } + + bufferSize += frames; + size = sizeof( UInt32 ); + if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertySafetyOffset, &size, &frames)) ) + { + LOG->Warn( WERROR("Couldn't get device safety offset", error) ); + frames = 0; + } + bufferSize += frames; + size = sizeof( UInt32 ); + + do { + if( (error = AudioDeviceGetPropertyInfo(OutputDevice, 0, false, kAudioDevicePropertyStreams, &size, NULL)) ) + { + LOG->Warn( WERROR("Device has no streams", error) ); + break; + } + int num = size / sizeof( AudioStreamID ); + if( num == 0 ) + { + LOG->Warn( "Device has no streams." ); + break; + } + AudioStreamID *streams = new AudioStreamID[num]; + + if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyStreams, &size, streams)) ) + { + LOG->Warn( WERROR("Cannot get device's streams", error) ); + delete[] streams; + break; + } + if( (error = AudioStreamGetProperty(streams[0], 0, kAudioDevicePropertyLatency, &size, &frames)) ) + { + LOG->Warn( WERROR("Stream does not report latency", error) ); + frames = 0; + } + delete[] streams; + bufferSize += frames; + } while( false ); + + return bufferSize / sampleRate; } + OSStatus RageSound_AU::Render( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, @@ -276,10 +271,13 @@ OSStatus RageSound_AU::Render( void *inRefCon, if( unlikely(This->m_pIOThread == NULL) ) This->m_pIOThread = new RageThreadRegister( "HAL I/O thread" ); + AudioBuffer &buf = ioData->mBuffers[0]; - int64_t decodePos = int64_t( inTimeStamp->mSampleTime ) + This->m_iOffset; + double scale = This->m_iSampleRate / AudioGetHostClockFrequency(); + int64_t now = int64_t( scale * AudioGetCurrentHostTime() ); + int64_t next = int64_t( scale * inTimeStamp->mHostTime ); - This->Mix( (int16_t *)buf.mData, inNumberFrames, decodePos, This->GetPosition(NULL) ); + This->Mix( (int16_t *)buf.mData, inNumberFrames, next, now ); if( unlikely(This->m_bDone) ) { AudioOutputUnitStop( This->m_OutputUnit ); @@ -288,123 +286,6 @@ OSStatus RageSound_AU::Render( void *inRefCon, return noErr; } -void RageSound_AU::PropertyChanged( void *inRefCon, - AudioUnit au, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement ) -{ - RageSound_AU *This = (RageSound_AU *)inRefCon; - OSStatus error; - UInt32 running; - UInt32 size = sizeof( running ); - AudioTimeStamp time; - - ASSERT( au == This->m_OutputUnit ); - - switch( inID ) - { - case kAudioOutputUnitProperty_CurrentDevice: - { - LOG->Info( "Audio device changed!" ); - This->ShutDown(); - RString result = This->Init(); - ASSERT_M( result.empty(), result ); - return; - } - case kAudioOutputUnitProperty_IsRunning: - if( (error = AudioUnitGetProperty(au, inID, inScope, inElement, &running, &size)) ) - FAIL_M( ERROR("Couldn't get running property", error) ); - LOG->Trace( "Audio device %s running.", running ? "started" : "stopped" ); - if( !running ) - return; - // Fall through. - case kAudioUnitProperty_StreamFormat: - if( inScope != kAudioUnitScope_Output ) - return; - if( (error = AudioDeviceGetCurrentTime(This->m_OutputDevice, &time)) ) - { - if( error != kAudioHardwareNotRunningError ) - FAIL_M( ERROR("Couldn't get the current time", error) ); - LOG->Trace( "Old offset %lld, last sample time %lld.", - This->m_iOffset, This->m_iLastSampleTime ); - This->m_iOffset = This->m_iLastSampleTime; - } - else - { - LOG->Trace( "Old offset %lld, last sample time %lld, current sample time %lld, new offset %lld.", - This->m_iOffset, This->m_iLastSampleTime, int64_t(time.mSampleTime), This->m_iLastSampleTime - int64_t(time.mSampleTime) ); - This->m_iOffset = This->m_iLastSampleTime - int64_t( time.mSampleTime ); - } - //return; - } - - const char *scope; - switch( inScope ) - { - case kAudioUnitScope_Global: scope = "global"; break; - case kAudioUnitScope_Input: scope = "input"; break; - case kAudioUnitScope_Output: scope = "output"; break; - case kAudioUnitScope_Group: scope = "group"; break; - case kAudioUnitScope_Part: scope = "part"; break; - DEFAULT_FAIL( int(inScope) ); - } - - LOG->Trace( "PropertyChanged: id = %d, scope = %s, element %d.", int(inID), scope, int(inElement) ); -} - -OSStatus RageSound_AU::DevicePropertyChanged( AudioDeviceID inDevice, - UInt32 inChannel, - Boolean isInput, - AudioDevicePropertyID inID, - void *inRefCon ) -{ - RageSound_AU *This = (RageSound_AU *)inRefCon; - UInt32 value; - UInt32 size = sizeof(value); - bool somewhere = true; - OSStatus error; - - switch( inID ) - { - case kAudioDevicePropertyDeviceIsAlive: - LOG->Warn( "Audio device died." ); - This->m_OutputDevice = NULL; // The AudioDeviceID is now invalid. - This->m_vADProperties.clear(); - break; - case kAudioDevicePropertyDeviceHasChanged: - // Hopefully this will be handled sanely by the AudioUnit. - LOG->Trace( "Audio device has changed in some way." ); - break; - case kAudioDevicePropertyDeviceIsRunning: - somewhere = false; - // fall through. - case kAudioDevicePropertyDeviceIsRunningSomewhere: - if( (error = AudioDeviceGetProperty(inDevice, inChannel, isInput, inID, &size, &value)) ) - FAIL_M( ERROR("AudioDeviceGetProperty('%s') failed", error, FourCCToString(inID).c_str()) ); - LOG->Trace( "Audio device has %s running%s.", value ? "started" : "stopped", somewhere ? " somewhere" : "" ); - break; - case kAudioDeviceProcessorOverload: - LOG->Trace( "Audio overload." ); // This is being called on the real time thread. hmm. - return 0; // Do nothing else. - case kAudioDevicePropertyStreams: - // Hopefully this will be handled sanely by the AudioUnit. - LOG->Trace( "Audio device's streams have changed." ); - break; - case kAudioDevicePropertyStreamConfiguration: - // Hopefully this will be handled sanely by the AudioUnit. - LOG->Trace( "Audio device's stream configuration has changed." ); - break; - case kAudioDevicePropertyStreamFormat: - // Hopefully this will be handled sanely by the AudioUnit. - LOG->Trace( "Audio device's stream format has changed." ); - break; - } - LOG->Trace( "Audio device property '%s' changed.", FourCCToString(inID).c_str() ); - return 0; -} - - /* * (c) 2004-2006 Steve Checkoway * All rights reserved. diff --git a/stepmania/src/arch/Sound/RageSoundDriver_AU.h b/stepmania/src/arch/Sound/RageSoundDriver_AU.h index f52f776502..407f9ea9a1 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_AU.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_AU.h @@ -11,7 +11,7 @@ public: RageSound_AU(); RString Init(); ~RageSound_AU(); - float GetPlayLatency() const { return m_fLatency; } + float GetPlayLatency() const; int GetSampleRate( int rate ) const { return m_iSampleRate; } int64_t GetPosition( const RageSoundBase *sound ) const; @@ -19,35 +19,20 @@ protected: void SetupDecodingThread(); private: - void ShutDown(); - void AddAUPropertyListener( AudioUnitPropertyID prop ); - void AddADPropertyListener( AudioDevicePropertyID prop ); static OSStatus Render( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData ); - static void PropertyChanged( void *inRefCon, - AudioUnit au, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement ); - static OSStatus DevicePropertyChanged( AudioDeviceID inDevice, - UInt32 inChannel, - Boolean isInput, - AudioDevicePropertyID inID, - void *inRefCon ); + static void NameHALThread( CFRunLoopObserverRef, CFRunLoopActivity activity, void *inRefCon ); + AudioUnit m_OutputUnit; - AudioDeviceID m_OutputDevice; int m_iSampleRate; - float m_fLatency; - mutable int64_t m_iLastSampleTime; - int64_t m_iOffset; - vector m_vAUProperties; - vector m_vADProperties; bool m_bDone; + bool m_bStarted; RageThreadRegister *m_pIOThread; + RageThreadRegister *m_pNotificationThread; RageSemaphore m_Semaphore; };