Survive device changes, (external) sample rate changes, etc.

This commit is contained in:
Steve Checkoway
2006-11-17 16:59:22 +00:00
parent dee3db204f
commit e988ea9945
2 changed files with 137 additions and 271 deletions
+128 -247
View File
@@ -13,11 +13,8 @@ static const UInt32 kBytesPerPacket = kChannelsPerFrame * kBitsPerChannel / 8;
static const UInt32 kBytesPerFrame = kBytesPerPacket; static const UInt32 kBytesPerFrame = kBytesPerPacket;
static const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; static const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger;
#define WERROR(str, num, extra...) str ": '%s' (%lu).", ## extra, FourCCToString(num).c_str(), (num)
RageSound_AU::RageSound_AU() : m_OutputUnit(NULL), m_OutputDevice(NULL), m_iSampleRate(0), m_fLatency(0.0f), #define ERROR(str, num, extra...) (ssprintf(WERROR(str, (num), ## extra)))
m_iLastSampleTime(0), m_iOffset(0), m_bDone(false), m_pIOThread(NULL), m_Semaphore("Sound shutdown")
{
}
static inline RString FourCCToString( uint32_t num ) static inline RString FourCCToString( uint32_t num )
{ {
@@ -39,15 +36,27 @@ static inline RString FourCCToString( uint32_t num )
return s; 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() RString RageSound_AU::Init()
{ {
ComponentDescription desc; ComponentDescription desc;
desc.componentType = kAudioUnitType_Output; desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_HALOutput; desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0; desc.componentFlags = 0;
desc.componentFlagsMask = 0; desc.componentFlagsMask = 0;
@@ -90,6 +99,9 @@ RString RageSound_AU::Init()
if( streamFormat.mSampleRate <= 0 ) if( streamFormat.mSampleRate <= 0 )
streamFormat.mSampleRate = 44100.0; streamFormat.mSampleRate = 44100.0;
m_iSampleRate = int( streamFormat.mSampleRate ); m_iSampleRate = int( streamFormat.mSampleRate );
// XXX Try to set the hardware sample rate.
error = AudioUnitSetProperty( m_OutputUnit, error = AudioUnitSetProperty( m_OutputUnit,
kAudioUnitProperty_StreamFormat, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, kAudioUnitScope_Input,
@@ -109,112 +121,55 @@ RString RageSound_AU::Init()
if( error != noErr ) if( error != noErr )
LOG->Warn( WERROR("Failed to set the maximum render quality", error) ); 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. // Initialize the AU.
error = AudioUnitInitialize( m_OutputUnit ); error = AudioUnitInitialize( m_OutputUnit );
if( error != noErr ) if( error != noErr )
return ERROR( "Could not initialize the AudioUnit", error ); 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(); 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 ); error = AudioOutputUnitStart( m_OutputUnit );
if( error != noErr ) if( error != noErr )
return ERROR( "Could not start the AudioUnit", error ); return ERROR( "Could not start the AudioUnit", error );
m_bStarted = true;
return RString(); return RString();
} }
RageSound_AU::~RageSound_AU() 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_pIOThread;
delete m_pNotificationThread;
} }
int64_t RageSound_AU::GetPosition( const RageSoundBase *sound ) const int64_t RageSound_AU::GetPosition( const RageSoundBase *sound ) const
{ {
AudioTimeStamp time; double scale = m_iSampleRate / AudioGetHostClockFrequency();
OSStatus error; return int64_t( scale * AudioGetCurrentHostTime() );
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;
} }
@@ -224,47 +179,87 @@ void RageSound_AU::SetupDecodingThread()
SetThreadPrecedence( 0.75f ); SetThreadPrecedence( 0.75f );
} }
void RageSound_AU::ShutDown() float RageSound_AU::GetPlayLatency() const
{ {
if( !m_OutputUnit ) OSStatus error;
return; UInt32 bufferSize;
FOREACH_CONST( AudioUnitPropertyID, m_vAUProperties, prop ) AudioDeviceID OutputDevice;
AudioUnitRemovePropertyListener( m_OutputUnit, *prop, PropertyChanged ); UInt32 size = sizeof( AudioDeviceID );
FOREACH_CONST( AudioDevicePropertyID, m_vADProperties, prop ) Float64 sampleRate;
AudioDeviceRemovePropertyListener( m_OutputDevice, 0, false, *prop, DevicePropertyChanged );
m_bDone = true; if( (error = AudioUnitGetProperty(m_OutputUnit, kAudioOutputUnitProperty_CurrentDevice,
m_Semaphore.Wait(); kAudioUnitScope_Global, 0, &OutputDevice, &size)) )
m_vADProperties.clear(); {
m_vAUProperties.clear(); LOG->Warn( WERROR("No output device", error) );
AudioUnitUninitialize( m_OutputUnit ); return 0.0f;
CloseComponent( m_OutputUnit );
m_OutputUnit = NULL;
m_OutputDevice = NULL;
m_iSampleRate = 0;
} }
void RageSound_AU::AddAUPropertyListener( AudioUnitPropertyID prop ) size = sizeof( Float64 );
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sampleRate)) )
{ {
ComponentResult result = AudioUnitAddPropertyListener( m_OutputUnit, prop, PropertyChanged, this ); LOG->Warn( WERROR("Couldn't get the device sample rate", error) );
return 0.0f;
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 ) size = sizeof( UInt32 );
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyBufferFrameSize, &size, &bufferSize)) )
{ {
OSStatus error = AudioDeviceAddPropertyListener( m_OutputDevice, kAudioPropertyWildcardChannel, false, LOG->Warn( WERROR("Couldn't determine buffer size", error) );
prop, DevicePropertyChanged, this ); bufferSize = 0;
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 );
} }
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, OSStatus RageSound_AU::Render( void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, const AudioTimeStamp *inTimeStamp,
@@ -276,10 +271,13 @@ OSStatus RageSound_AU::Render( void *inRefCon,
if( unlikely(This->m_pIOThread == NULL) ) if( unlikely(This->m_pIOThread == NULL) )
This->m_pIOThread = new RageThreadRegister( "HAL I/O thread" ); This->m_pIOThread = new RageThreadRegister( "HAL I/O thread" );
AudioBuffer &buf = ioData->mBuffers[0];
int64_t decodePos = int64_t( inTimeStamp->mSampleTime ) + This->m_iOffset;
This->Mix( (int16_t *)buf.mData, inNumberFrames, decodePos, This->GetPosition(NULL) ); AudioBuffer &buf = ioData->mBuffers[0];
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, next, now );
if( unlikely(This->m_bDone) ) if( unlikely(This->m_bDone) )
{ {
AudioOutputUnitStop( This->m_OutputUnit ); AudioOutputUnitStop( This->m_OutputUnit );
@@ -288,123 +286,6 @@ OSStatus RageSound_AU::Render( void *inRefCon,
return noErr; 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 * (c) 2004-2006 Steve Checkoway
* All rights reserved. * All rights reserved.
+5 -20
View File
@@ -11,7 +11,7 @@ public:
RageSound_AU(); RageSound_AU();
RString Init(); RString Init();
~RageSound_AU(); ~RageSound_AU();
float GetPlayLatency() const { return m_fLatency; } float GetPlayLatency() const;
int GetSampleRate( int rate ) const { return m_iSampleRate; } int GetSampleRate( int rate ) const { return m_iSampleRate; }
int64_t GetPosition( const RageSoundBase *sound ) const; int64_t GetPosition( const RageSoundBase *sound ) const;
@@ -19,35 +19,20 @@ protected:
void SetupDecodingThread(); void SetupDecodingThread();
private: private:
void ShutDown();
void AddAUPropertyListener( AudioUnitPropertyID prop );
void AddADPropertyListener( AudioDevicePropertyID prop );
static OSStatus Render( void *inRefCon, static OSStatus Render( void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inBusNumber,
UInt32 inNumberFrames, UInt32 inNumberFrames,
AudioBufferList *ioData ); AudioBufferList *ioData );
static void PropertyChanged( void *inRefCon, static void NameHALThread( CFRunLoopObserverRef, CFRunLoopActivity activity, void *inRefCon );
AudioUnit au,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement );
static OSStatus DevicePropertyChanged( AudioDeviceID inDevice,
UInt32 inChannel,
Boolean isInput,
AudioDevicePropertyID inID,
void *inRefCon );
AudioUnit m_OutputUnit; AudioUnit m_OutputUnit;
AudioDeviceID m_OutputDevice;
int m_iSampleRate; int m_iSampleRate;
float m_fLatency;
mutable int64_t m_iLastSampleTime;
int64_t m_iOffset;
vector<AudioUnitPropertyID> m_vAUProperties;
vector<AudioDevicePropertyID> m_vADProperties;
bool m_bDone; bool m_bDone;
bool m_bStarted;
RageThreadRegister *m_pIOThread; RageThreadRegister *m_pIOThread;
RageThreadRegister *m_pNotificationThread;
RageSemaphore m_Semaphore; RageSemaphore m_Semaphore;
}; };