NSSound and Enumerating CoreAudio Output Devices

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?


Steve Mokris is a developer at Kosada, Inc.