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

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

- (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), который должен иметь значение null (согласно документации), но переключается между нулем и значением, отличным от нуля, при каждом втором обратном вызове. Есть ли в этом смысл для кого-то?

Кроме того, вызов 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;
}

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


person Alex Bollbach    schedule 13.02.2016    source источник
comment
Каким значениям назначены ваши kInputBus и kOutputBus? Вы установили что-нибудь для свойства kAudioUnitProperty_ShouldAllocateBuffer? Какую категорию AudioSession или AVAudioSession вы устанавливаете активной перед запуском RemoteIO? Вы тестируете это на новом iPhone 6s или 6s + или на более старом устройстве?   -  person hotpaw2    schedule 14.02.2016
comment
kOutputBus равен нулю 0 другой - 1. Я не устанавливал ...shouldAllocateBuffer и не видел того, что требуется для кода remoteIO, который я видел до сих пор. Я также не знал, что вам нужен сеанс не по умолчанию. Я взял AudioSession по умолчанию из AVFoundation и установил категорию PlayAndRecord, но это не помогло. Я тестирую это на iphone 6+.   -  person Alex Bollbach    schedule 14.02.2016
comment
Мои приложения установили ... shouldAllocateBuffer на false при выделении собственного AudioBufferList для RemoteIO. Вы активировали свой AVAudioSession?   -  person hotpaw2    schedule 14.02.2016


Ответы (1)


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

person hotpaw2    schedule 14.02.2016
comment
я предполагаю, что это опасность наименования переменной theSameName2 .. lol - person Alex Bollbach; 15.02.2016