AUHAL 单元的 AudioBufferList,其输出流格式被压缩。

AudioBufferList for AUHAL unit whose output stream format is compressed.

致谢

我知道这个 post 很长,但我尽量将我的问题放在上下文中,因为我认为它非常独特(除了 this one. The final question is at the very end of the post and here's the complete code 之外找不到任何相关问题。

首先,了解一下上下文。我正在使用 CoreAudioAudioToolbox 库,更准确地说是 Audio Units。我在 macOS 上。我的最终目标是从任何输入设备录制音频(因此在简单的 AudioQueueBuffer 上使用音频单元)并将其写入音频文件。我认为我的程序中最棘手的部分是在 单个音频单元 内从 LPCM 转换为 AAC(在我的例子中),因此没有使用 AUGraph。

我的程序基本上只是一个音频单元,封装在一个 class、AudioUnit mInputUnit 中,这是一个 AUHAL 单元。于是,我就按照这个this technical note来设置了。基本上,我 link 输入元素的输入范围(因为输出元素被禁用)到音频设备,即我的内置麦克风。

然后我相应地更新单元输出范围的AudioFormat。

  ...
  inputStream.mFormatID = kAudioFormatMPEG4AAC;
  inputStream.mFormatFlags = 0;
  inputStream.mBitsPerChannel = 0;
  checkError(
    AudioUnitSetProperty(
      mInputUnit,
      kAudioUnitProperty_StreamFormat,
      kAudioUnitScope_Output,
      1,
      &inputStream,
      propertySize
    ),
    "Couldn't set output stream format."
  );

因此,此时音频单元应该工作如下:

从 LPCM 中的输入设备记录 [INPUT SCOPE] ==> 从 LPCM 转换为 ==> AAC 渲染。

请注意,每种流格式(输入和输出)使用 2 个通道。输入流和输出流都没有将其 mFormatFlags 设置为 kAudioFormatIsNonInterleaved,因此它们都是交错的。 事实上,我认为这就是问题的来源,但不明白为什么。

此时,似乎一切正常。当我在设置输入回调后尝试渲染音频单元时出现问题。

我发现了一条说明如下的注释:

“By convention, AUHAL deinterleaves multichannel audio. This means that you set up two AudioBuffers of one channel each instead of setting up one AudioBuffer with mNumberChannels==2. A common cause of paramErr (-50) problems in AudioUnitRender() calls is having AudioBufferLists whose topology (or arrangement of buffers) doesn’t match what the unit is prepared to produce. When dealing at the unit level, you almost always want to do noninterleaved like this.”

摘自:克里斯·亚当森和凯文·阿维拉。 “学习核心音频:Mac 和 iOS 的音频编程实践指南。” iBooks.

因此,我遵循了适当的代码结构来呈现音频。

OSStatus Recorder::inputProc(
  void *inRefCon,
  AudioUnitRenderActionFlags *ioActionFlags,
  const AudioTimeStamp *inTimeStamp,
  UInt32 inBusNumber,
  UInt32 inNumberFrames,
  AudioBufferList *ioData
)
{
  Recorder *This = (Recorder *) inRefCon;
  CAStreamBasicDescription outputStream;
  This->getStreamBasicDescription(kAudioUnitScope_Output, outputStream);

  UInt32 bufferSizeBytes = inNumberFrames * sizeof(Float32);
  UInt32 propertySize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * outputStream.mChannelsPerFrame);
  auto bufferList = (AudioBufferList*) malloc(propertySize);
  bufferList->mNumberBuffers = outputStream.mChannelsPerFrame;

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

  checkError(
    AudioUnitRender(
      This->mInputUnit,
      ioActionFlags,
      inTimeStamp,
      inBusNumber,
      inNumberFrames,
      bufferList
    ),
    "Couldn't render audio unit."
  );
  free(bufferList);
}

然后,当我尝试渲染音频时,我遇到了以下错误 Error: Couldn't render audio unit. (-50),这实际上应该是 已修复 的错误,这让我更加困惑。

问题

在这一点上,我不知道这是否与我的整体架构有关,即我是否应该使用 AUGraph 并添加一个输出单元而不是尝试在单个 AUHAL 单元内从规范格式转换为压缩格式? 还是这与我预先分配 AudioBufferList 的方式有关?

我已经通过重新设计整个流程来解决这个问题。简而言之,我仍然有一个独特的 AUHAL 单元,但我不是在 AUHAL 单元内进行格式转换,而是在渲染回调中进行,使用扩展音频文件,它采用源格式和目标格式。 整个挑战是找到正确的格式描述,基本上只是测试 mFormatIDmFormatFlags 等的不同值...