Core Audio - 远程 IO 混乱

Posted

技术标签:

【中文标题】Core Audio - 远程 IO 混乱【英文标题】:Core Audio - Remote IO confusion 【发布时间】:2016-02-13 21:55:43 【问题描述】:

我无法解释 ios 中 remoteIO audiounit 回调的行为。我正在设置一个具有两个回调的 remoteIO 单元,一个作为输入回调,一个作为“渲染”回调。我正在遵循与this tasty pixel 教程中推荐的非常相似的 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(根据文档),但在每个第二个回调中都在 null 和具有非 nil 值之间翻转。这对任何人都有意义吗?

此外,在输入回调中调用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;

我相信我的经历可能是由于一些简单的格式错误或可能在错误的示波器上使用了错误的总线或其他一些琐碎的事情(并且很容易在核心音频上下文错误中产生)。但是,因为我基本上对语义和生命周期流(方案?,我什至不知道该用什么词)没有直觉,所以我无法充分调试它。我将非常感谢一位更有经验的核心音频程序员的帮助,他们可能会对这种情况有所了解。

【问题讨论】:

您的 kInputBus 和 kOutputBus 分配了哪些值?您是否将 kAudioUnitProperty_ShouldAllocateBuffer 属性设置为任何值?在启动 RemoteIO 之前,您将哪个 AudioSession 或 AVAudioSession 类别设置为活动状态?您是在新的 iPhone 6s 或 6s+ 还是旧设备上进行测试? kOutputBus 是零 0 另一个是 1。我没有设置 ...shouldAllocateBuffer 并且没有看到到目前为止我看到的 remoteIO 代码所需的。我也不知道您需要一个非默认会话。我确实从 AVFoundation 中获取了默认的 AudioSession 并将类别设置为 PlayAndRecord 但这没有帮助。我正在 iphone 6+ 上测试这个。 在为 RemoteIO 分配自己的 AudioBufferList 时,我的应用程序将 ...shouldAllocateBuffer 设置为 false。你激活你的 AVAudioSession 了吗? 【参考方案1】:

您的 kAudioUnitProperty_SetRenderCallback 属性设置器正在使用 callbackStruct 而不是 callbackStruct2。因此,您的 RemoteIO 音频单元调用 inputCallback() 两次,而不是 PlaybackCallback()。

【讨论】:

我猜这是命名变量 theSameName"2" 的危险 .. 哈哈

以上是关于Core Audio - 远程 IO 混乱的主要内容,如果未能解决你的问题,请参考以下文章

多个文件播放器上的Core Audio声音测量

Core Audio 大(压缩)文件播放和内存占用

如何在 iPhone 的 Core Audio (Audio Unit/ Remote IO) 中将录制的声音更改为男人的声音

Airplay 远程控制 HTTP 请求

Jenkins 流水线远程部署 .NET Core/Framework 到 IIS

使用 Web Audio API 和 Wrtc 进行远程音频处理