OS X: respond to new audio device - macos

I need to be notified, when a new audio device appears on OS X. I'm not sure where to start. Can Core Audio do this for me, or do I need to get down to a lower level with for instance IO Kit?

You can do this by observing kAudioHardwarePropertyDevices. The code looks roughly like:
AudioObjectPropertyAddress propertyAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress, myAudioObjectPropertyListenerProc, NULL);
In myAudioObjectPropertyListenerProc you can determine what devices are currently available.

Related

Format of microphone audio passed to call back in mac OS X core audio example

I need access to audio data from microphone on macbook. I have the an example program for recording microphone data based on the one in "Learning Core Audio". When I run this program and break on the call back routine I see the inBuffer pointer and the mAudioData pointer. However I am having a heck of a time making sense of the data. I've tried casting the void* pointer to mAudioData to SInt16, to SInt32 and to float and tried a number of endian conversions all with nonsense looking results. What I need to know definitively is the number format for the data in the buffer. The example actually works writing microphone data to a file which I can play so I know that real audio is being recorded.
AudioStreamBasicDescription recordFormat;
memset(&recordFormat,0,sizeof(recordFormat));
//recordFormat.mFormatID = kAudioFormatMPEG4AAC;
recordFormat.mFormatID = kAudioFormatLinearPCM;
recordFormat.mChannelsPerFrame = 2;
recordFormat.mBitsPerChannel = 16;
recordFormat.mBytesPerPacket = recordFormat.mBytesPerFrame = recordFormat.mChannelsPerFrame * sizeof(SInt16);
recordFormat.mFramesPerPacket = 1;
MyGetDefaultInputDeviceSampleRate(&recordFormat.mSampleRate);
UInt32 propSize = sizeof(recordFormat);
CheckError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
0,
NULL,
&propSize,
&recordFormat),
"AudioFormatProperty failed");
//set up queue
AudioQueueRef queue = {0};
CheckError(AudioQueueNewInput(&recordFormat,
MyAQInputCallback,
&recorder,
NULL,
kCFRunLoopCommonModes,
0,
&queue),
"AudioQueueNewInput failed");
UInt32 size = sizeof(recordFormat);
CheckError(AudioQueueGetProperty(queue,
kAudioConverterCurrentOutputStreamDescription,
&recordFormat,
&size), "Couldn't get queue's format");

OSX: Detecting if audio is being played through the speakers using Core Audio

I'm learning how to build OSX applications, and I was wondering if there is a way to check if there is some audio being outputted by any application on the system? Thanks
I think this can be checked with the kAudioDevicePropertyDeviceIsRunningSomewhere property.
From the header doc:
A UInt32 where 1 means that the AudioDevice is running in at least one process on the system and 0 means that it isn't running at all.
Pseudo-y code:
bool isRunningSomewhere(AudioDeviceID deviceId) {
uint32 val;
uint32 size = sizeof(val);
AudioObjectPropertyAddress pa = { kAudioDevicePropertyDeviceIsRunningSomewhere, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
AudioObjectGetPropertyData(deviceId, &pa, 0, NULL, &size, &val);
return val == 1;
}
This should tell you if the device is being used (i.e. has an active IOProc.) But it won't tell you if that IOProc is just sending silence.
This can't be done at the user application level. It might be possible by installing an OS X kext (kernel extension) or a custom audio device driver, which requires sudo privileges and possibly a reboot.

Check if system volume is muted

I'm Currently working on a small project, in which I need to check if the system volume is muted from the App Delegate.
As sound as the user mute's/unmute's the volume an function needs to be called.
I've found some things about AudioToolbox, but I can't seem to find anything that works.
I know to look up if the default device is muted or not. First, you need to look up the 'default' audio device hardware ID. This can be done once and stored in your program.
var propAddr = AudioObjectPropertyAddress(
mSelector: AudioObjectPropertySelector(kAudioHardwarePropertyDefaultOutputDevice),
mScope: AudioObjectPropertyScope(kAudioObjectPropertyScopeGlobal),
mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMaster))
var defaultAudioHardwareID : AudioDeviceID = 0
var propSize = UInt32(sizeof(uint32))
let status = AudioHardwareServiceGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &propAddr, 0 , nil, &propSize, &defaultAudioHardwareID)
After that, you can look up if the device is muted.
var propAddr = AudioObjectPropertyAddress(
mSelector: AudioObjectPropertySelector(kAudioDevicePropertyMute),
mScope: AudioObjectPropertyScope(kAudioObjectPropertyScopeOutput),
mElement: AudioObjectPropertyElement(kAudioObjectPropertyElementMaster))
var isMuted: uint32 = 0
var propSize = UInt32(sizeof(uint32))
let status = AudioHardwareServiceGetPropertyData(defaultAudioHardwareID, &propAddr, 0, nil, &propSize, &isMuted)
if isMuted != 0 {
// Do stuff here
return;
}
I don't know if there's a way to get a notification when the mute state changes or not.

Streaming audio to multiple AirPlay devices

Anyone know how to stream audio to multiple AirPlay destinations? Apparently, this was possible through Core Audio at some point in the past, but on 10.9 and 10.10, this does not seem possible. iTunes does it, so what's the secret? Here is some code I tried to see if I could get this to work:
OSStatus err = 0;
UInt32 size = sizeof(UInt32);
SSAudioSource * targetSource = airplayDevice.airplaySources[0];
AudioDeviceID airPlayDeviceID = targetSource.deviceID;
SSAudioSource * source1 = airplayDevice.airplaySources[0];
SSAudioSource * source2 = airplayDevice.airplaySources[1];
SSAudioSource * source3 = airplayDevice.airplaySources[2];
AudioDeviceID alldevices[] = {source3.sourceID, source2.sourceID, source1.sourceID};
AudioObjectPropertyAddress addr;
addr.mSelector = kAudioDevicePropertyDataSource;
addr.mScope = kAudioDevicePropertyScopeOutput;
addr.mElement = kAudioObjectPropertyElementMaster;
// Set the 'AirPlay' device to point to all of its sources...
err = AudioObjectSetPropertyData(airPlayDeviceID, &addr, 0, nil, size, alldevices);
AudioObjectPropertyAddress audioDevicesAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
// ...now set the system output to point at the 'AirPlay' device
err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &audioDevicesAddress, 0, nil, size, &airPlayDeviceID);
No matter how I arrange the devices in the array, sound only comes out of the first device (index 0) of the array. So what's the secret?
Thanks
I raised a bug report with Apple for this back in July and got a reply in October:
Engineering has determined that there are no plans to address this
issue.
I've gone back to Apple asking why the functionality has been removed but not hopeful for a (timely) response.
For what it's worth I think your approach is correct, it's similar to the way I had it working in the past for an app. I suspect iTunes uses Audio Units or something similar to do multiple speakers.

Core Audio and the Phantom Device ID

So here's what is going on.
I am attempting to work with Core Audio, specifically input devices. I want to mute, change volume, etc, etc. I've encountered something absolutely bizarre that I cannot figure out. Thus far, google has been of no help.
When I query the system and ask for a list of all audio devices, I am returned an array of device IDs. In this case, 261, 259, 263, 257.
Using kAudioDevicePropertyDeviceName, I get the following:
261: Built-in Microphone
259: Built-in Input
263: Built-in Output
257: iPhoneSimulatorAudioDevice
This is all well and good.
// This method returns an NSArray of all the audio devices on the system, both input and
// On my system, it returns 261, 259, 263, 257
- (NSArray*)getAudioDevices
{
AudioObjectPropertyAddress propertyAddress = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 dataSize = 0;
OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
if(kAudioHardwareNoError != status)
{
MZLog(#"Unable to get number of audio devices. Error: %d",status);
return NULL;
}
UInt32 deviceCount = dataSize / sizeof(AudioDeviceID);
AudioDeviceID *audioDevices = malloc(dataSize);
status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, audioDevices);
if(kAudioHardwareNoError != status)
{
MZLog(#"AudioObjectGetPropertyData failed when getting device IDs. Error: %d",status);
free(audioDevices), audioDevices = NULL;
return NULL;
}
NSMutableArray* devices = [NSMutableArray array];
for(UInt32 i = 0; i < deviceCount; i++)
{
MZLog(#"device found: %d",audioDevices[i]);
[devices addObject:[NSNumber numberWithInt:audioDevices[i]]];
}
free(audioDevices);
return [NSArray arrayWithArray:devices];
}
The problem crops up when I then query the system and ask it for the ID of the default input device. This method returns an ID of 269, which is not listed in the array of all devices.
If I attempt to use kAudioDevicePropertyDeviceName to get the name of the device, I am returned an empty string. Although it doesn't appear to have a name, if I mute this device ID, my built-in microphone will mute. Conversely, if I mute the 261 ID, which is named "Built-In Microphone", my microphone does not mute.
// Gets the current default audio input device
// On my system, it returns 269, which is NOT LISTED in the array of ALL audio devices
- (AudioDeviceID)defaultInputDevice
{
AudioDeviceID defaultAudioDevice;
UInt32 propertySize = 0;
OSStatus status = noErr;
AudioObjectPropertyAddress propertyAOPA;
propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
propertyAOPA.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertySize = sizeof(AudioDeviceID);
status = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &propertyAOPA, 0, NULL, &propertySize, &defaultAudioDevice);
if(status)
{ //Error
NSLog(#"Error %d retreiving default input device",status);
return 0;
}
return defaultAudioDevice;
}
To further confuse things, if I manually switch my input to "Line In" and re-run the program, I get an ID of 259 when querying for the default input device, which is listed in the array of all devices.
So, to summarize:
I am attempting to interact with the input devices in my system. If I try to interact with device ID 261 which is my "Built-In Microphone", nothing happens. If I try to interact with device ID 269 which is, apparently, a phantom ID, my built-in microphone is affected. The 269 ID is returned when I query the system for the default input device, but it is not listed when I query the system for a list of all devices.
Does anyone know what is happening? Am I simply going insane?
Thanks in advance!
Fixed it.
First off, the phantom device ID was simply a virtual device the system was using.
Secondly, the reason I couldn't mute or do anything with the actual devices was because I was using AudioHardwareServiceSetPropertyData instead of AudioObjectSetPropertyData.
It all works now.

Resources