Cocoa’s NSSound provides a blindingly simple way to play back audio asynchronously, and even provides some metadata and control over how the audio is played back.
It uses the default sound device, by default. It gives you the ability to change the output device.
According to the documentation:
- (void)setPlaybackDeviceIdentifier:(NSString *)playbackDeviceIdentifierSpecifies the receiver’s output device.
playbackDeviceIdentifier Unique identifier of a sound output device.
That’s it. What is the “Unique identifier of a sound output device”? What format is this “Unique identifier”? How do I get a list of the “Unique identifiers” of the available output devices on my system?
No documentation exists for that part. Anywhere. Which makes this method kinda useless.
Maybe we can use the corresponding method, - (NSString *)playbackDeviceIdentifier? Nope, that just returns nil.
So, then, how do I get a list of output devices?
Turns out I need to detour from Cocoa and use CoreAudio’s C API directly. And even then it’s not documented very well. There was a request a few years ago to update the archaic, pre-OS X-era KB article, but that request has gone unanswered.
Can I get a list of output devices directly? Apparently not. But I can get a list of ALL devices, and then interrogate each device and get a list of each device’s buffers. Then I can interrogate each buffer and add up the number of output channels the device has in total. If there are more than zero output channels, this device is an output device!
I was able to piece together the following enumerator:
UInt32 sz; AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,&sz,NULL); AudioDeviceID *audioDevices=(AudioDeviceID *)malloc(sz); AudioHardwareGetProperty(kAudioHardwarePropertyDevices,&sz,audioDevices); UInt32 deviceCount = (sz / sizeof(AudioDeviceID));UInt32 i; for(i=0;i<deviceCount;++i) { NSString *s;
// get buffer list UInt32 outputChannelCount=0; { AudioDeviceGetPropertyInfo( audioDevices[i],0,false, kAudioDevicePropertyStreamConfiguration, &sz,NULL ); AudioBufferList *bufferList=(AudioBufferList *)malloc(sz); AudioDeviceGetProperty( audioDevices[i],0,false, kAudioDevicePropertyStreamConfiguration, &sz,&bufferList ); UInt32 j; for(j=0;j<bufferList.mNumberBuffers;++j) outputChannelCount += bufferList.mBuffers[j].mNumberChannels; free(bufferList); } // skip devices without any output channels if(outputChannelCount==0) continue; // output some device info { sz=sizeof(CFStringRef); AudioDeviceGetProperty( audioDevices[i],0,false, kAudioDevicePropertyDeviceUID, &sz,&s ); NSLog(@"DeviceUID: [%@]",s); [s release]; AudioDeviceGetProperty( audioDevices[i],0,false, kAudioObjectPropertyName, &sz,&s ); NSLog(@" Name: [%@]",s); [s release]; NSLog(@" OutputChannels: %d",outputChannelCount); }}
On my MacBook Pro, this currently outputs:
DeviceUID: [AppleFWAudioEngineGUID:18202415010545664]
Name: [EDIROL FA-101 (0000)]
OutputChannels: 10
DeviceUID: [AppleHDAEngineOutput:0]
Name: [Built-in Output]
OutputChannels: 2
Why couldn’t this have been as easy as NSSound?

minor nit, but CoreAudio (and all its C functions) is not Carbon. CoreAudio is located in /System/Library/Frameworks/CoreAudio.framework, and is entirely independent of /System/Library/Frameworks/Carbon.framework.
The sample code posted appears to have no carbon dependencies.
Submitted by cwright (not verified) on 2008.08.24 @ 04:27. |
Thanks, Chris. I corrected the article.
Submitted by smokris on 2008.08.24 @ 08:33. |
There is another mistake in the code, in the function call AudioDeviceGetProperty( audioDevices[i],0,false, kAudioDevicePropertyStreamConfiguration, &sz,&bufferList ); it should be bufferList that’s being passed into it, not &bufferList. Thanks
Submitted by Scott (not verified) on 2009.04.13 @ 19:19. |
Hello, If I try this in a c++ file, and I add to xcode the coreaudio framework, my code runs into problems with:
… but the program, when executed, goes into ‘dbg’ mode but it doesn’t tell what is wrong with “for(j=0;jmNumberBuffers;++j)”
What #include statements am I missing? Here is what I have.
Submitted by Salvador Aguinaga (not verified) on 2009.10.06 @ 06:24. | Re:
Post new comment