NSSound and Enumerating CoreAudio Output Devices NSSound and Enumerating CoreAudio Output Devices

Posted by smokris on 2008.08.12 @ 09:16

Filed under:

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 *)playbackDeviceIdentifier

Specifies 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.

Thanks, Chris. I corrected the article.

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

Hello, If I try this in a c++ file, and I add to xcode the coreaudio framework, my code runs into problems with:

for(j=0;jmNumberBuffers; and bufferList-&gt;mBuffers[j].mNumberChannels 

… 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.

#include 
#include 
#include

Thanks for your code!

Correction:

for(j=0;j<bufferList.mNumberBuffers;++j)
            outputChannelCount += bufferList.mBuffers[j].mNumberChannels;

Should be:

for(j=0;j<bufferList->mNumberBuffers;++j)
            outputChannelCount += bufferList->mBuffers[j].mNumberChannels;