Как исключить входные или выходные каналы из совокупного устройства CoreAudio? - PullRequest
2 голосов
/ 28 февраля 2020

У меня есть программа MacOS / X на основе CoreAudio, которая позволяет пользователю выбирать аудиоустройство ввода и аудиоустройство вывода, и (если пользователь не выбрал одно и то же устройство для обоих входов и вывод) моя программа создает частное агрегатное аудиоустройство и использует его для получения аудио, аудио, обработки и отправки его на воспроизведение.

Все работает отлично, но есть одна небольшая проблема - если выбранное устройство ввода также имеет несколько выходов, связанных с его оборудованием, эти выходы отображаются как часть выходных каналов агрегированного устройства, а это не то поведение, которое мне нужно. Точно так же, если выбранное выходное устройство также имеет некоторые входы, связанные с его оборудованием, эти входы будут отображаться как входные каналы на входах агрегатного устройства, что я тоже не хочу.

Мой вопрос таков: Есть ли способ сказать CoreAudio не включать входы или выходы дополнительного устройства в агрегатное устройство, которое я создаю? (Мое резервное решение было бы изменить мой обратный вызов аудио-рендеринга, чтобы игнорировать нежелательные аудиоканалы, но это кажется не элегантным, поэтому мне любопытно, есть ли лучший способ справиться с этим)

Моя функция Это создает совокупное устройство ниже, в случае, если оно актуально:

// 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();
}

1 Ответ

2 голосов
/ 28 февраля 2020

Существуют способы обработки сопоставления каналов (которые вы в основном описываете), но я сомневаюсь, что это «лучший» способ в вашем случае.

Такая функциональность рассматривается в платформе AudioToolbox с использованием Аудиоустройства. Особенно интересен в этом случае kAudioUnitSubType_HALOutput AudioUnit (AUComponent.h).

Используя этот тип AudioUnit, вы можете отправлять и получать аудио на определенное c аудиоустройство и из него в указанном формате канала. Если желаемое расположение каналов не соответствует расположению каналов устройства, вы можете выполнить сопоставление каналов.

Чтобы получить некоторые технические детали, взгляните на: https://developer.apple.com/library/archive/technotes/tn2091/_index.html

Обратите внимание, что большая часть AudioToolbox находится в процессе замены на AVAudioEngine.

Итак, в вашем случае я думаю, что было бы проще выполнить ручное сопоставление каналов, просто игнорируя сэмплы, которые вы не делаете. не нужно. Кроме того, я не уверен, что CoreAudio предоставляет «срезанные» выходные буферы. Чтобы быть уверенным, стоит замолчать их самостоятельно.

РЕДАКТИРОВАТЬ

Глядя на документы в AudioHardware.h, кажется, есть способ включения и отключения потоков определенного IOPro. c. Когда OS X создает агрегат, он помещает все каналы различных подустройств в разные потоки, поэтому в вашем случае вы сможете отключить поток, который содержит входы устройства вывода, и, наоборот, отключить поток, который содержит выходы устройства ввода.

Для этого взгляните на AudioHardwareIOProcStreamUsage и kAudioDevicePropertyIOProcStreamUsage на AudioHardware.h

. Я нашел утилиту HALLab от Apple, очень полезную для выяснения о фактические потоки. (https://developer.apple.com/download/more/ и поиск "Audio Tools for Xcode")

...