Core Audio - путаница с удаленным вводом-выводом

У меня проблемы с интерпретацией поведения обратных вызовов audioIO для удаленного входа в iOS. Я настраиваю удаленный модуль с двумя обратными вызовами, один как обратный вызов ввода, а другой как обратный вызов рендеринга. Я следую очень похожей настройке удаленного доступа, как рекомендовано в этом вкусном уроке по пикселям. Это довольно длинный метод настройки:

- (void)setup {


   AudioUnit ioUnit;

   AudioComponentDescription audioCompDesc;
   audioCompDesc.componentType = kAudioUnitType_Output;
   audioCompDesc.componentSubType = kAudioUnitSubType_RemoteIO;
   audioCompDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
   audioCompDesc.componentFlags = 0;
   audioCompDesc.componentFlagsMask = 0;

   AudioComponent rioComponent = AudioComponentFindNext(NULL, &audioCompDesc);
   CheckError(AudioComponentInstanceNew(rioComponent, &ioUnit), "Couldn't get RIO unit instance");

   // i/o
   UInt32 oneFlag = 1;
   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioOutputUnitProperty_EnableIO,
                                   kAudioUnitScope_Output,
                                   kOutputBus,
                                   &oneFlag,
                                   sizeof(oneFlag)), "Couldn't enable RIO output");

   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioOutputUnitProperty_EnableIO,
                                   kAudioUnitScope_Input,
                                   kInputBus,
                                   &oneFlag,
                                   sizeof(oneFlag)), "Couldn't enable RIO input");

   AudioStreamBasicDescription myASBD;
   memset (&myASBD, 0, sizeof(myASBD));
   myASBD.mSampleRate = 44100;
   myASBD.mFormatID = kAudioFormatLinearPCM;
   myASBD.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
   myASBD.mFramesPerPacket = 1;
   myASBD.mChannelsPerFrame = 1;
   myASBD.mBitsPerChannel = 16;
   myASBD.mBytesPerPacket = 2 * myASBD.mChannelsPerFrame;
   myASBD.mBytesPerFrame = 2 *  myASBD.mChannelsPerFrame;


   // set stream format for both busses
   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioUnitProperty_StreamFormat,
                                   kAudioUnitScope_Input,
                                   kOutputBus,
                                   &myASBD,
                                   sizeof(myASBD)), "Couldn't set ASBD for RIO on input scope / bus 0");

   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioUnitProperty_StreamFormat,
                                   kAudioUnitScope_Output,
                                   kInputBus,
                                   &myASBD,
                                   sizeof(myASBD)), "Couldn't set ASBD for RIO on output scope / bus 1");

   // set arbitrarily high for now
   UInt32 bufferSizeBytes = 10000 * sizeof(int);



   int offset = offsetof(AudioBufferList, mBuffers[0]);

   int bufferListSizeInBytes = offset + (sizeof(AudioBuffer) * myASBD.mChannelsPerFrame);

   // why need to cast to audioBufferList * ?
   self.inputBuffer = (AudioBufferList *)malloc(bufferListSizeInBytes);
   self.inputBuffer->mNumberBuffers = myASBD.mChannelsPerFrame;

   for (UInt32 i = 0; i < myASBD.mChannelsPerFrame; i++) {
      self.inputBuffer->mBuffers[i].mNumberChannels = 1;
      self.inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes;
      self.inputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes);
   }


   self.remoteIOUnit = ioUnit;


   /////////////////////////////////////////////// callback setup
   AURenderCallbackStruct callbackStruct;
   callbackStruct.inputProc = inputCallback;
   callbackStruct.inputProcRefCon = (__bridge void * _Nullable)self;
   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioOutputUnitProperty_SetInputCallback,
                                   kAudioUnitScope_Global,
                                   kInputBus,
                                   &callbackStruct,
                                   sizeof(callbackStruct)), "Couldn't set input callback");


   AURenderCallbackStruct callbackStruct2;
   callbackStruct2.inputProc = playbackCallback;
   callbackStruct2.inputProcRefCon = (__bridge void * _Nullable)self;
   CheckError(AudioUnitSetProperty(ioUnit,
                                   kAudioUnitProperty_SetRenderCallback,
                                   kAudioUnitScope_Global,
                                   kOutputBus,
                                   &callbackStruct,
                                   sizeof(callbackStruct)), "Couldn't set input callback");


   CheckError(AudioUnitInitialize(ioUnit), "Couldn't initialize input unit");
   CheckError(AudioOutputUnitStart(ioUnit), "AudioOutputUnitStart failed");


}

Я испытываю странное поведение в обратных вызовах. Во-первых, playbackCallback функция не вызывается вообще, несмотря на то, что ее свойство устанавливается идентично описанному в учебном пособии (учебное пособие принадлежит парню, написавшему приложение Loopy).

Во-вторых, входной обратный вызов имеет параметр ioData (audioBufferList), который должен быть нулевым (согласно документации), но переключается между нулевым и имеет ненулевое значение при каждом втором обратном вызове. Имеет ли это смысл для кого-либо?

Дополнительно звоню audiounitRender во входном обратном вызове (семантика которого я до сих пор не понимаю с точки зрения логики API и жизненного цикла и т. д.) приводит к ошибке -50, которая является очень общим "плохим параметром". Скорее всего, это связано с неправильной "топологией" audiobufferlist то есть чередование / удаление чередования, число каналов и т. д. Однако я пробовал различные топологии, и ни одна из них не привела к ошибке. И это также не объясняет странное поведение ioData. ЗДЕСЬ это функция для справки:

OSStatus inputCallback(void *inRefCon,
                       AudioUnitRenderActionFlags *ioActionFlags,
                       const AudioTimeStamp *inTimeStamp,
                       UInt32 inBusNumber,
                       UInt32 inNumberFrames,
                       AudioBufferList * ioData)
{

   MicController *myRefCon = (__bridge MicController *)inRefCon;


   CheckError(AudioUnitRender(myRefCon.remoteIOUnit,
                              ioActionFlags,
                              inTimeStamp,
                              inBusNumber,
                              inNumberFrames,
                              myRefCon.inputBuffer), "audio unit render");



   return noErr;
}

Я полагаю, что мой опыт может быть связан с некоторыми простыми ошибками в форматировании или, возможно, использованием неправильной шины в неправильной области или каким-то другим тривиальным (и легко сделать в основной ошибке аудио контекста). Однако, поскольку у меня принципиально нет интуиции для семантики и потока жизненного цикла (схема? Я даже не знаю, какое слово использовать), я не могу адекватно отладить это. Я был бы очень признателен за помощь более опытного программиста ядра аудио, который может пролить свет на эту ситуацию.

1 ответ

Решение

Ваш установщик свойства kAudioUnitProperty_SetRenderCallback использует callbackStruct вместо callbackStruct2. Таким образом, ваш аудиоустройство RemoteIO дважды вызывает inputCallback(), а не PlayCallback().

Другие вопросы по тегам