AudioQueue理论学习
AudioQueue是iOS提供的又一套实现音频播放和录制的框架,怎么说呢,使用起来其实也不比AduioUnit方便很多,特别是在buffer的管理上,新手理解起来还是有点费劲的,在被她折磨了一周后,现在终于把她征服了,这里做一下总结。
首先看一下官方给的AudioQueue工作流程图
这里给出的是播放本地路径下的音频文件,流程总结如下:
- 读取音频文件,在音频文件的回调中给buffers填充数据
- 将填充满的buffers给AudioQueue播放
- AudioQueue播放完一个Buffer后,把这个buffer还给AudioQueue的回调继续填充
- 循环2和3直到音频播放完
主要的api:
AudioQueueNewOutput
|
|
该方法用于创建一个用于输出音频的AudioQueue
參数及返回说明例如以下:
- inFormat:该參数指明了即将播放的音频的数据格式
- inCallbackProc:该回调用于当AudioQueue已使用完一个缓冲区时通知用户,用户能够继续填充音频数据
- inUserData:由用户传入的数据指针,用于传递给回调函数
- inCallbackRunLoop:指明回调事件发生在哪个RunLoop之中,假设传递NULL,表示在AudioQueue所在的线程上运行该回调事件,普通情况下,传递NULL就可以。
- inCallbackRunLoopMode:指明回调事件发生的RunLoop的模式,传递NULL相当于kCFRunLoopCommonModes,通常情况下传递NULL就可以
- outAQ:该AudioQueue的引用实例,
AudioQueueOutput_Callback
|
|
这个是AudioQueue的回调函数,会将已经播放完的buffer还回来。
AudioQueueAllocateBuffer
|
|
该方法的作用是为存放音频数据的缓冲区开辟空间
參数及返回说明例如以下:
- inAQ:AudioQueue的引用实例
- inBufferByteSize:须要开辟的缓冲区的大小
- outBuffer:开辟的缓冲区的引用实例
AudioQueueEnqueueBuffer
|
|
该方法用于将已经填充数据的AudioQueueBuffer入队到AudioQueue
參数及返回说明例如以下:
- inAQ:AudioQueue的引用实例
- inBuffer:须要入队的缓冲区实例
- inNumPacketDescs:缓冲区中共存在有多少帧音频数据
- inPacketDescs:缓冲区中每一帧的相关信息。用户须要指明当中每一帧在缓冲区中数据的偏移值,通过字段mStartOffset来指定
控制相关
|
|
AudioFileStream
数据的相关内容都和它相关,所以还是很重要的,其实AudioQueue使用起来比较简单,复杂的部分都在这个数据的处理上了。。。
AudioFileStreamOpen
|
|
这个函数会创建一个AudioFileStreamID,之后所有的操作都是基于这个ID来的,然后还是创建2个回调 inPropertyListenerProc 和 inPacketsProc,这2个回调函数比较重要,下面详说。
AudioFileStreamParseBytes
|
|
只有对数据进行了解析,才会进到上面的2个回调函数里面。
AudioFileStreamPropertyListenerProc
|
|
在这个回调中,你可以拿到你想要的音频相关信息,比如音频结构(AudioStreamBasicDescription),码率(BitRate),MagicCookie等等,通过这些信息,你还可以计算其他数据,比如音频总时长。
这里分享下音频时长的2种计算方式:
总时长 = 总帧数*单帧的时长
单帧的时长 = 单帧的采样个数*每帧的时长
每帧的时长 = 1/采样率
采样率:单位时间内的采样个数
- 总时长 = 文件总的字节数/码率
码率:单位时间内的文件字节数
AudioFileStreamPacketsProc
|
|
在这个回调中,你能够拿到每一个packet的数据,然后数据的填充都在这里完成。
实战
这里只讲几个比较重要的细节,其他的可以参考demo中的代码。
- AudioQueueNewOutput在创建的时候有2个runloop相关的参数,这里直接传NULL就行,不要取当前的runloop和model
- AudioQueueOutput_Callback里面在标记可使用的buffer时要加锁,不然音频无法正常播放
- 记得设置AVAudioSession的category
- 读取音频数据时使用while循环,比使用计时器优雅
- kAudioFileStreamProperty_DataFormat这个属性是必须要获取到的,在创建AudioQueue的时候需要传入
- 填装数据的时候要判断对当前buffer的可用填装空间,如果装不下了就别再装啦。。。
- AudioQueueEnqueueBuffer给AudioQueue塞完数据后,需要判断下一个buffer是否可用,不可用的话得一直等着,知道可用为止。