设置音频单元格式并渲染交错PCM音频的回调

时间:2013-01-22 17:00:13

标签: ios audio audiounit

我正在尝试播放我在一系列UDP数据包中收到的音频。它们被解码为具有以下属性的PCM帧:

  • 2个频道
  • interleaved
  • 单个通道中每个样本2个字节(所以4 每帧字节数)
  • ,采样率为48000。

每个UDP数据包包含480个帧,因此缓冲区的大小为480 * 2(通道)* 2(每个通道的字节数)。

我需要设置一个音频单元来播放这些数据包。所以,我的第一个问题是,我应该如何为音频单元设置AudioStreamBasicDescription结构?查看文档,我甚至不确定交错PCM是否是可接受的格式。

这是我到目前为止所得到的:

struct AudioStreamBasicDescription {
   Float64 mSampleRate;                 //48000
   UInt32  mFormatID;                   //?????
   UInt32  mFormatFlags;                //?????
   UInt32  mBytesPerPacket;             //Not sure what "packet" means here
   UInt32  mFramesPerPacket;            //Same as above
   UInt32  mBytesPerFrame;              //Same
   UInt32  mChannelsPerFrame;           //2?
   UInt32  mBitsPerChannel;             //16?
   UInt32  mReserved;                   //???
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

其次,在设置之后,我不确定如何将帧从UDP回调中获取到实际的音频单元渲染功能。

我目前有一个来自套接字侦听器的回调函数,我在其中生成包含我想要播放的音频的int16 *缓冲区。据我了解,我还必须为以下形式的音频单元实现渲染回调:

OSStatus RenderFrames(
    void                        *inRefCon,
    AudioUnitRenderActionFlags  *ioActionFlags,
    const AudioTimeStamp        *inTimeStamp,
    UInt32                      inBusNumber,
    UInt32                      inNumberFrames,
    AudioBufferList             *ioData)
{
    //No idea what I should do here.
    return noErr;
}

总而言之,我认为我的套接字接收回调应该做的是解码帧,并将它们放在缓冲区结构中,以便RenderFrames回调可以从该缓冲区中获取帧,并播放它们。 这是正确的吗?如果是这样,一旦我在RenderFrames函数中获取下一帧,我如何实际“提交”以进行播放

1 个答案:

答案 0 :(得分:6)

一次拍摄一部分

AudioStreamBasicDescriptor

Apple的ASBD文档是here。澄清:

  • 音频帧是时间一致的音频样本集。换句话说,每个通道一个样本。因此,对于立体声,这是2
  • 对于PCM格式,没有数据包。据说,mBytesPerPacket = mBytesPerFramemFramesPerPacket=1,但我不确定这是否真的被使用过。
  • mReserved未使用且必须为0
  • 有关mFormatIDmFormatFlags的信息,请参阅The documentation。 CoreAudioTypes.h中有一个方便的帮助函数CalculateLPCMFlags,用于在CoreAudioTypes.h中计算后者。
  • 多声道音频通常是交错的(如果您真的不想要它,可以在mFormatFlags设置一个位。)
  • 还有另一个辅助功能可以填写整个ASBD - FillOutASBDForLPCM()用于线性PCM的常见情况。
  • remoteIO设备不支持mFormatIDmFormatFlags的许多组合 - 我发现在iOS上需要进行实验。

以下是我的一个项目的一些工作代码:

AudioStreamBasicDescription inputASBL = {0}; 

inputASBL.mSampleRate =          static_cast<Float64>(sampleRate);
inputASBL.mFormatID =            kAudioFormatLinearPCM;
inputASBL.mFormatFlags =         kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger,
inputASBL.mFramesPerPacket =     1;
inputASBL.mChannelsPerFrame =    2;
inputASBL.mBitsPerChannel =      sizeof(short) * 8;
inputASBL.mBytesPerPacket =      sizeof(short) * 2;
inputASBL.mBytesPerFrame =       sizeof(short) * 2;
inputASBL.mReserved =            0;

渲染回调

CoreAudio运行Apple所描述的 pull 模型。也就是说,当CoreAudio需要缓冲区填充时,渲染回调被称为实时线程。根据您的问题,您似乎期待相反的情况 - 将数据推送到音频输出。

基本上有两种实现选择:

  1. 在渲染回调中从UDP套接字执行非阻塞读取(作为一般规则,您在此处执行的任何操作都应该是快速且无阻塞的)。
  2. 维护一个音频FIFO,当接收并由渲染回调消耗时,将插入样本。
  3. 第二种可能是更好的选择,但是您需要自己管理缓冲区过度和欠量运行。

    ioData参数指向分散 - 聚集控制结构。在最简单的情况下,它指向一个包含所有帧的缓冲区,但可以包含几个在它们之间具有足够帧以满足inNumberFrames的帧。通常,为inNumberFrames预先分配一个足够大的缓冲区,将样本复制到其中,然后修改指向buy AudioBufferList的{​​{1}}对象以指向它。

    在您的应用程序中,您可能对解码后的音频数据包采用分散 - 聚集方法,在解码时分配缓冲区。但是,您并不总是得到所需的延迟,并且可能无法安排ioData与解码的UDP音频帧相同。