6

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

  • 2个频道
  • 交错的
  • 单个通道中每个样本 2 个字节(因此每帧 4 个字节)
  • 采样率为 48000。

每个 UDP 数据包包含 480 帧,因此缓冲区的大小为 480 * 2(channels) * 2(bytes per channel)。

我需要设置一个音频单元来播放这些数据包。所以,我的第一个问题是,我应该如何为音频单元设置 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 函数中获取下一帧,我如何实际“提交”以进行播放

4

1 回答 1

11

一次拿一个部分

音频流基本描述符

Apple 的 ASBD 文档在此处。澄清:

  • 一帧音频是一组时间一致的音频样本。换句话说,每个通道一个样本。因此,对于 Stereo,这是2.
  • 对于 PCM 格式,没有打包。据说,,mBytesPerPacket = mBytesPerFramemFramesPerPacket=1我不确定这是否真的被使用过。
  • mReserved未使用且必须使用0
  • 请参阅和的文档。CoreAudioTypes.h 中有一个方便的辅助函数,用于在.mFormatIDmFormatFlagsCalculateLPCMFlagsCoreAudioTypes.h
  • 多声道音频通常是交错的(mFormatFlags如果你真的不希望这样,你可以设置一点)。
  • 还有另一个辅助函数可以填写整个 ASBD -FillOutASBDForLPCM()用于线性 PCM 的常见情况。
  • mFormatIDremoteIO 单元不支持和mFormatFlags不支持的许多组合- 我发现在 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 描述的拉式模型。也就是说,当CoreAudio需要填充缓冲区时,会从一个实时线程中调用render回调。从您的问题来看,您似乎期望相反 - 将数据推送到音频输出。

基本上有两种实现选择:

  1. 在渲染回调中从 UDP 套接字执行非阻塞读取(作为一般规则,您在此处执行的任何操作都应该是快速且非阻塞的)。
  2. 维护一个音频 FIFO,在接收和渲染回调使用时将样本插入其中。

第二个可能是更好的选择,但您将需要自己管理缓冲区过度和不足。

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

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

于 2013-01-23T01:13:37.763 回答