Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
997 views
in Technique[技术] by (71.8m points)

macos - How to exclude input or output channels from an aggregate CoreAudio device?

I've got a CoreAudio-based MacOS/X program that allows the user to select an input-audio-device and an output-audio-device, and (if the user didn't choose the same device for both input and output) my program creates a private aggregate-audio-device and uses that to receive audio the audio, process it, and then send it out for playback.

That's all working great, but there is one minor problem -- if the selected input-device also has some outputs associated with its hardware, those outputs show up as part of the aggregate device's output-channels, which isn't the behavior I want. Similarly, if the selected output-device also has some inputs associated with its hardware, those inputs will show up as input channels in the aggregate device's inputs, which I also don't want.

My question is, is there any way to tell CoreAudio not to include the inputs or outputs of a sub-device in the aggregate device I'm constructing? (my fallback solution would be to modify my audio-rendering callback to ignore the unwanted audio channels, but that seems less than elegant, so I'm curious if there is a better way to handle it)

My function that creates the aggregate device is below, in case it is relevant:

// This code was adapted from the example code at :  https://web.archive.org/web/20140716012404/http://daveaddey.com/?p=51
ConstCoreAudioDeviceRef CoreAudioDevice :: CreateAggregateDevice(const ConstCoreAudioDeviceInfoRef & inputCadi, const ConstCoreAudioDeviceInfoRef & outputCadi, bool require96kHz, int32 optRequiredBufferSizeFrames)
{
   OSStatus osErr = noErr;
   UInt32 outSize;
   Boolean outWritable;

   //-----------------------
   // Start to create a new aggregate by getting the base audio hardware plugin
   //-----------------------

   osErr = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyPlugInForBundleID, &outSize, &outWritable);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   AudioValueTranslation pluginAVT;

   CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio");
   AudioObjectID pluginID;

   pluginAVT.mInputData      = &inBundleRef;
   pluginAVT.mInputDataSize  = sizeof(inBundleRef);
   pluginAVT.mOutputData     = &pluginID;
   pluginAVT.mOutputDataSize = sizeof(pluginID);

   osErr = AudioHardwareGetProperty(kAudioHardwarePropertyPlugInForBundleID, &outSize, &pluginAVT);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   //-----------------------
   // Create a CFDictionary for our aggregate device
   //-----------------------

   CFMutableDictionaryRef aggDeviceDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

   CFStringRef aggregateDeviceNameRef = CFSTR("My Aggregate Device");
   CFStringRef aggregateDeviceUIDRef  = CFSTR("com.mycomapany.myaggregatedevice");

   // add the name of the device to the dictionary
   CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceNameKey), aggregateDeviceNameRef);

   // add our choice of UID for the aggregate device to the dictionary
   CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceUIDKey), aggregateDeviceUIDRef);

   if (IsDebugFlagEnabled("public_cad_device") == false)
   {
      // make it private so that we don't have the user messing with it
      int value = 1;
      CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), CFNumberCreate(NULL, kCFNumberIntType, &value));
   }

   //-----------------------
   // Create a CFMutableArray for our sub-device list
   //-----------------------

   // we need to append the UID for each device to a CFMutableArray, so create one here
   CFMutableArrayRef subDevicesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

   // add the sub-devices to our aggregate device
   const CFStringRef  inputDeviceUID =  inputCadi()->GetPersistentUID().ToCFStringRef();
   const CFStringRef outputDeviceUID = outputCadi()->GetPersistentUID().ToCFStringRef();
   CFArrayAppendValue(subDevicesArray,  inputDeviceUID);
   CFArrayAppendValue(subDevicesArray, outputDeviceUID);

   //-----------------------
   // Feed the dictionary to the plugin, to create a blank aggregate device
   //-----------------------

   AudioObjectPropertyAddress pluginAOPA;
   pluginAOPA.mSelector = kAudioPlugInCreateAggregateDevice;
   pluginAOPA.mScope    = kAudioObjectPropertyScopeGlobal;
   pluginAOPA.mElement  = kAudioObjectPropertyElementMaster;
   UInt32 outDataSize;

   osErr = AudioObjectGetPropertyDataSize(pluginID, &pluginAOPA, 0, NULL, &outDataSize);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   AudioDeviceID outAggregateDevice;
   osErr = AudioObjectGetPropertyData(pluginID, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, &outAggregateDevice);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   //-----------------------
   // Set the sub-device list
   //-----------------------

   pluginAOPA.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList;
   pluginAOPA.mScope    = kAudioObjectPropertyScopeGlobal;
   pluginAOPA.mElement  = kAudioObjectPropertyElementMaster;
   outDataSize = sizeof(CFMutableArrayRef);
   osErr = AudioObjectSetPropertyData(outAggregateDevice, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   //-----------------------
   // Set the master device
   //-----------------------

   // set the master device manually (this is the device which will act as the master clock for the aggregate device)
   // pass in the UID of the device you want to use
   pluginAOPA.mSelector = kAudioAggregateDevicePropertyMasterSubDevice;
   pluginAOPA.mScope    = kAudioObjectPropertyScopeGlobal;
   pluginAOPA.mElement  = kAudioObjectPropertyElementMaster;

   outDataSize = sizeof(outputDeviceUID);
   osErr = AudioObjectSetPropertyData(outAggregateDevice, &pluginAOPA, 0, NULL, outDataSize, &outputDeviceUID);
   if (osErr != noErr) return ConstCoreAudioDeviceRef();

   //-----------------------
   // Clean up
   //-----------------------

   // release the CF objects we have created - we don't need them any more
   CFRelease(aggDeviceDict);
   CFRelease(subDevicesArray);

   // release the device UID CFStringRefs
   CFRelease(inputDeviceUID);
   CFRelease(outputDeviceUID);

   ConstCoreAudioDeviceInfoRef infoRef = CoreAudioDeviceInfo::GetAudioDeviceInfo(outAggregateDevice);
   if (infoRef())
   {
      ConstCoreAudioDeviceRef ret(new CoreAudioDevice(infoRef, true));
      return ((ret())&&(SetupSimpleCoreAudioDeviceAux(ret()->GetDeviceInfo(), require96kHz, optRequiredBufferSizeFrames, false).IsOK())) ? ret : ConstCoreAudioDeviceRef();
   }
   else return ConstCoreAudioDeviceRef();
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are ways to handle the channel mapping (which you're basically describing), but I doubt if it is a "better" way in your case.

Such functionality is covered in the AudioToolbox framework using Audio Units. Especially the kAudioUnitSubType_HALOutput AudioUnit (AUComponent.h) is interesting in this case.

Using this type of AudioUnit you can send and receive audio to and from a specific audio device in a specified channel format. When the desired channel layout doesn't match the channel layout of the device you can do channel mapping.

To get some technical details have a look at: https://developer.apple.com/library/archive/technotes/tn2091/_index.html

Please not that a lot of the AudioToolbox is in the process of being replaced by AVAudioEngine.

So, in your case I think it would be easier to do manual channel mapping by just ignoring the samples you don't need. Also, I'm not sure if CoreAudio provides 'slicenced' output buffers. To be sure consider silencing them yourself.

EDIT

Looking at the docs in AudioHardware.h there seems to be a way of enabling and disabling streams of a particular IOProc. When OS X creates an aggregate, it puts all the channels of the different subdevices in different streams, so in your case you should be able to disable the stream which contains the inputs of the output device and and vice versa disable the stream which contains the outputs of the input device.

For this have a look at AudioHardwareIOProcStreamUsage and kAudioDevicePropertyIOProcStreamUsage both in AudioHardware.h

I found the HALLab utility from Apple very useful in finding out about the actual streams. (https://developer.apple.com/download/more/ and search for "Audio Tools for Xcode")


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...