@[TOC](IOS音视频(四十四)AVFoundation 之 Audio Queue Services)
1. Audio Queue Services简介
Audio Queue Services,这是Core Audio的Audio Toolbox框架中的一个C编程接口。
- 那么什么是音频队列服务呢?
Audio Queue Services提供了一种简单、低开销的方式来在iOS和Mac OS X中录制和播放音频。
音频队列服务让您记录和播放音频的任何下列格式:
- 线性PCM。
- 您正在开发的Apple平台上原生支持的任何压缩格式。
- 用户已安装编解码器的任何其他格式。
音频队列服务是高级的。它允许您的应用程序在不了解硬件接口的情况下使用硬件记录和回放设备(如麦克风和扬声器)。它还允许您在不了解编解码器工作原理的情况下使用复杂的编解码器。
同时,音频队列服务支持一些高级功能。它提供了细粒度的定时控制,以支持预定的回放和同步。您可以使用它来同步多个音频队列的回放,并同步音频和视频。
注意:Audio Queue Services提供的特性与之前Mac OS x中的声音管理器提供的特性类似。声音管理器在Mac OS X v10.5中是不支持的,并且不能用于64位应用程序。苹果推荐所有新开发的音频队列服务,并将其作为现有Mac OS X应用程序中的声音管理器的替代品。
Audio Queue Services是一个纯C接口,可以在Cocoa应用程序中使用,也可以在Mac OS X命令行工具中使用。为了保持对音频队列服务的关注,本文中的代码示例有时通过使用Core Audio SDK中的c++类进行了简化。但是,无论是SDK还是c++语言都不需要使用音频队列服务。
1.1 音频队列
- 什么是音频队列呢?
音频队列是在iOS或Mac OS x中用于录制或播放音频的软件对象。它由AudioQueueRef不透明数据类型表示,在AudioQueue.h头文件中声明。您可以使用音频队列和其他核心音频接口,以及相对少量的自定义代码,在应用程序中创建完整的数字音频录制或回放解决方案。
一个音频队列做的工作如下:
- 连接到音频硬件
- 管理内存
- 根据需要对压缩的音频格式使用编解码器
- 调用录音和回放
1.1.1 音频队列架构
所有的音频队列具有相同的一般结构,由以下部分组成:
- 一组音频队列缓冲区,每个缓冲区都是一些音频数据的临时存储库
- 一个缓冲区队列,一个音频队列缓冲区的有序列表
- 你写的一个音频队列回调函数
根据音频队列是用于录制还是用于回放,架构会有所不同。区别在于音频队列如何连接其输入和输出,以及回调函数的角色。
1.1.2 用于录音的音频队列
使用AudioQueueNewInput函数创建的录制音频队列的结构如图1-1所示。
录音音频队列创建一个新的录制音频队列对象。方法如下:
func AudioQueueNewInput(_ inFormat: UnsafePointer<AudioStreamBasicDescription>,
_ inCallbackProc: AudioQueueInputCallback,
_ inUserData: UnsafeMutableRawPointer?,
_ inCallbackRunLoop: CFRunLoop?,
_ inCallbackRunLoopMode: CFString?,
_ inFlags: UInt32,
_ outAQ: UnsafeMutablePointer<AudioQueueRef?>) -> OSStatus
上面AudioQueueNewInput的参数详解如下:
inFormat
: 要记录到的压缩或未压缩音频数据格式。当记录到线性PCM时,只支持交错格式。inCallbackProc
: 一个回调函数,用于记录音频队列。当音频队列完成填充缓冲区时,音频队列调用此函数。看到AudioQueueInputCallback。inUserData
: 与回调函数一起使用的自定义数据结构。inCallbackRunLoop
: 要调用inCallbackProc参数所指向的回调函数的事件循环。如果指定NULL,则在音频队列的内部线程上调用回调。inCallbackRunLoopMode
: 调用在inCallbackProc参数中指定的回调函数的运行循环模式。通常,您传递kCFRunLoopCommonModes或使用NULL,这是等价的。您可以选择用自己的run循环创建自己的线程。有关运行循环的更多信息,请参见运行循环和CFRunLoop。inFlags
: 保留供将来使用。必须是0。outAQ
: 在输出端,新创建的录制音频队列。
还有一个音频队列输入回调方法:AudioQueueInputCallback,这个回调是当录制音频队列完成填充音频队列缓冲区时,系统调用。
在调用AudioQueueNewInput(::::::_: ::)函数时,指定一个录制音频队列回调。每次其记录音频队列用新音频数据填充音频队列缓冲区时,都会调用回调。通常,回调将数据写入文件或其他缓冲区,然后重新对音频队列缓冲区进行排队以接收更多数据。
typealias AudioQueueInputCallback = (UnsafeMutableRawPointer?, AudioQueueRef, AudioQueueBufferRef, UnsafePointer<AudioTimeStamp>, UInt32, UnsafePointer<AudioStreamPacketDescription>?) -> Void
参数详解如下:
inUserData
:您在AudioQueueNewInput(::::::_: ::)函数的inUserData参数中指定的定制数据。通常,这包括音频队列的格式和状态信息。inAQ
:调用回调的录制音频队列。inBuffer
: 一个音频队列缓冲区,由记录音频队列新填充,包含回调需要写入的新音频数据。inStartTime
: 音频队列缓冲区启动的示例时间。此参数不用于基本记录。inNumberPacketDescriptions
: 在inBuffer参数中发送回调的音频数据包的数量。当以恒定比特率(CBR)格式录制时,音频队列将此参数设置为NULL。inPacketDescs
: 对于需要包描述的压缩格式,编码器为inBuffer参数中的音频数据生成的包描述集。当以CBR格式录制时,音频队列将此参数设置为NULL。
- 录音音频队列的输入端通常连接到外部音频硬件,如麦克风。例如,在iOS中,音频来自用户内置麦克风或耳机麦克风连接的设备。在Mac OS X的默认情况下,音频来自系统的默认音频输入设备,由用户在系统首选项中设置。
- 录制音频队列的输出端使用您编写的回调函数。当录制到磁盘时,回调将从其音频队列接收到的新音频数据的缓冲区写入音频文件。然而,录制音频队列可以以其他方式使用。例如,回调可以直接向应用程序提供音频数据,而不是将其写入磁盘。
- 如想了解关于此回调的更多信息请参考:录制音频队列回调函数。
- 每个音频队列—无论是录制还是播放—都有一个或多个音频队列缓冲区。这些缓冲区按照称为缓冲区队列的特定顺序排列。在图中,音频队列缓冲区根据它们被填入的顺序进行编号——这与它们被传递给回调的顺序相同。
1.1.3 用于回放的音频队列
回放音频队列(使用AudioQueueNewOutput函数创建)的结构如图1-2所示。
在回放音频队列中,回调位于输入端。回调负责从磁盘(或其他源)获取音频数据并将其传递给音频队列。回放回调也告诉它们的音频队列在没有更多数据播放时停止。
回放音频队列的输出通常连接到外部音频硬件,如扬声器。在iOS系统中,音频会传送到用户选择的设备上,例如,接收器或耳机。在Mac OS X的默认情况下,音频将按照用户在系统首选项中设置的方式进入系统的默认音频输出设备。
接下来认识一下AudioQueueNewOutput函数,该函数用来创建一个新的播放音频队列对象。函数定义如下:
func AudioQueueNewOutput(_ inFormat: UnsafePointer<AudioStreamBasicDescription>,
_ inCallbackProc: AudioQueueOutputCallback,
_ inUserData: UnsafeMutableRawPointer?,
_ inCallbackRunLoop: CFRunLoop?,
_ inCallbackRunLoopMode: CFString?,
_ inFlags: UInt32,
_ outAQ: UnsafeMutablePointer<AudioQueueRef?>) -> OSStatus
它的参数详解如下:
inFormat
: 要记录到的压缩或未压缩音频数据格式。当记录到线性PCM时,只支持交错格式。- 一个回调函数,用于回放音频队列。音频队列在音频队列完成获取缓冲区后调用回调。参考:AudioQueueOutputCallback。
inUserData
: 与回调函数一起使用的自定义数据结构。inCallbackRunLoop
: 要调用inCallbackProc参数所指向的回调函数的事件循环。如果指定NULL,则在音频队列的内部线程上调用回调。inCallbackRunLoopMode
: 调用在inCallbackProc参数中指定的回调函数的运行循环模式。通常,您传递kCFRunLoopCommonModes或使用NULL,这是等价的。您可以选择用自己的run循环创建自己的线程。有关运行循环的更多信息,请参见运行循环和CFRunLoop。inFlags
: 保留供将来使用。必须是0。outAQ
: 在输出时,新创建的回放音频队列对象。
- 用于回放音频队列。音频队列在音频队列完成获取缓冲区后调用回调。这个回调函数当音频队列缓冲区可重用时,由系统调用。AudioQueueOutputCallback的定义如下:
typealias AudioQueueOutputCallback = (UnsafeMutableRawPointer?, AudioQueueRef, AudioQueueBufferRef) -> Void
参数详解如下:
inUserData
:您在AudioQueueNewOutput(::::::: :::)函数的inUserData参数中指定的定制数据。通常,这包括音频队列的数据格式和状态信息。inAQ
:调用回调的回放音频队列。inBuffer
: 一个音频队列缓冲区,最近可用来填充,因为回放音频队列已经获取了它的内容。
- 如果你将你的回调函数命名为MyAudioQueueOutputCallback,你会这样声明它:
此回调函数在其关联的回放音频队列从音频队列缓冲区获取数据时调用,此时该缓冲区可用于重用。新可用的缓冲区在inBuffer参数中被发送到这个回调。通常,你写这个回调到:
- 用文件或其他缓冲区中的下一组音频数据填充新可用的缓冲区。
- 重新排队播放缓冲区。要对缓冲区重新排队,可以使用AudioQueueEnqueueBuffer(::::)或AudioQueueEnqueueBufferWithParameters(:::::::::: _:)函数。
要将此回调与回放音频队列关联,请在创建音频队列时提供对回调的引用。查看AudioQueueNewOutput(::::::_: _:)函数的inCallbackProc参数。
当系统调用此回调时,您不能假定已播放了来自新可用缓冲区的音频数据。有关如何检查声音是否已完成播放的说明,请参阅AudioQueuePropertyListenerProc回调函数的讨论。
1.1.4 音频队列缓冲区
音频队列缓冲区是一种数据结构,类型为AudioQueueBuffer,在AudioQueue.h头文件中声明:
typedef struct AudioQueueBuffer {
const UInt32 mAudioDataBytesCapacity;
void *const mAudioData;
UInt32 mAudioDataByteSize;
void *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;
上面代码中突出显示的mAudioData字段指向缓冲区本身:一块内存,用作正在播放或录制的音频数据的暂态块的容器。其他字段中的信息有助于音频队列管理缓冲区。
一个音频队列可以使用任意数量的缓冲区。您的应用程序指定了数量。一个典型的数字是3。这使得一个人可以忙于写磁盘,而另一个人则忙于填充新的音频数据。如果需要补偿磁盘I/O延迟等问题,可以使用第三个缓冲区。图1-3说明了这一点。
音频队列为其缓冲区执行内存管理:
- 当您调用AudioQueueAllocateBuffer函数时,音频队列将分配一个缓冲区。
- 当您通过调用AudioQueueDispose函数来释放音频队列时,队列将释放其缓冲区。
AudioQueueAllocateBuffer函数一旦分配,指向音频队列缓冲区的指针和缓冲区的容量就不能更改。缓冲区的size字段mAudioDataByteSize最初设置为0,表示有效数据的数量。其中AudioQueueAllocateBuffer函数定义如下:
func AudioQueueAllocateBuffer(_ inAQ: AudioQueueRef,
_ inBufferByteSize: UInt32,
_ outBuffer: UnsafeMutablePointer<AudioQueueBufferRef?>) -> OSStatus
函数参数解析:
inAQ
:要分配缓冲区的音频队列。inBufferByteSize
: 新缓冲区所需的容量,以字节为单位。适当的容量取决于对数据和音频数据格式的处理。outBuffer
: 在输出时,指向新分配的音频队列缓冲区。
AudioQueueDispose函数处理音频队列也会处理它的资源,包括它的缓冲区。调用此函数后,您将不能再与音频队列进行交互。此外,音频队列不再调用任何回调。 AudioQueueDispose函数定义如下:
func AudioQueueDispose(_ inAQ: AudioQueueRef,
_ inImmediate: Bool) -> OSStatus
函数参数解析:
inAQ
:要处理的音频队列。inImmediate
: 如果传递true,则立即(即同步地)处理音频队列。如果传递false,则在处理所有进入队列的缓冲区(即异步处理)之前不会执行处理。
使用音频队列缓冲提高了添加到应用程序的录制和回放功能的健壮性。它还有助于优化资源使用。
有关AudioQueueBuffer数据结构的完整描述,请参见AudioQueue Services参考资料。
1.1.5 音频队列服务
- 音频队列服务允许您在线性PCM、压缩格式(如Apple无损和AAC)和其他用户已安装编解码器的格式中录制和播放音频。音频队列服务还支持多个音频队列的预定回放和同步以及音频与视频的同步。
- 音频队列是用于录制或播放音频的软件对象。一个音频队列做的工作:
- 连接到音频硬件
- 管理内存
- 根据需要对压缩的音频格式使用编解码器
- 调用录音和回放
注意:
音频队列服务提供了与之前由声音管理器和macOS提供的类似的功能。它增加了额外的功能,比如同步。声音管理器在OS X v10.5中是不支持的,并且不能用于64位应用程序。建议所有新开发的应用程序都使用音频队列服务,以替代现有Mac应用程序中的声音管理器。
- 提供了控制音频队列的接口如下:
//开始播放或录制音频。
func AudioQueueStart(AudioQueueRef, UnsafePointer<AudioTimeStamp>?) -> OSStatus
//解码进入队列的缓冲区,为回放做准备。
func AudioQueuePrime(AudioQueueRef, UInt32, UnsafeMutablePointer<UInt32>?) -> OSStatus
//重置音频队列的解码器状态。
func AudioQueueFlush(AudioQueueRef) -> OSStatus
//停止播放或录制音频。
func AudioQueueStop(AudioQueueRef, Bool) -> OSStatus
//暂停音频播放或录音。
func AudioQueuePause(AudioQueueRef) -> OSStatus
//重置音频队列。
func AudioQueueReset(AudioQueueRef) -> OSStatus
- 提供了创建和处理音频队列的接口如下:
//创建一个新的播放音频队列对象。
func AudioQueueNewOutput(UnsafePointer<AudioStreamBasicDescription>, AudioQueueOutputCallback, UnsafeMutableRawPointer?, CFRunLoop?, CFString?, UInt32, UnsafeMutablePointer<AudioQueueRef?>) -> OSStatus
//创建一个新的录制音频队列对象。
func AudioQueueNewInput(UnsafePointer<AudioStreamBasicDescription>, AudioQueueInputCallback, UnsafeMutableRawPointer?, CFRunLoop?, CFString?, UInt32, UnsafeMutablePointer<AudioQueueRef?>) -> OSStatus
////析构一个音频队列。
func AudioQueueDispose(AudioQueueRef, Bool) -> OSStatus
- 提供了处理音频队列缓冲区的接口如下:
//请求音频队列对象分配音频队列缓冲区。
func AudioQueueAllocateBuffer(AudioQueueRef, UInt32, UnsafeMutablePointer<AudioQueueBufferRef?>) -> OSStatus
//请求一个音频队列对象分配一个带有数据包描述空间的音频队列缓冲区。
func AudioQueueAllocateBufferWithPacketDescriptions(AudioQueueRef, UInt32, UInt32, UnsafeMutablePointer<AudioQueueBufferRef?>) -> OSStatus
//请求音频队列处理音频队列缓冲区。
func AudioQueueFreeBuffer(AudioQueueRef, AudioQueueBufferRef) -> OSStatus
//将缓冲区添加到录制或回放音频队列的缓冲区队列中。
func AudioQueueEnqueueBuffer(AudioQueueRef, AudioQueueBufferRef, UInt32, UnsafePointer<AudioStreamPacketDescription>?) -> OSStatus
//将缓冲区添加到回放音频队列对象的缓冲区队列中,指定开始时间和其他设置。
func AudioQueueEnqueueBufferWithParameters(AudioQueueRef, AudioQueueBufferRef, UInt32, UnsafePointer<AudioStreamPacketDescription>?, UInt32, UInt32, UInt32, UnsafePointer<AudioQueueParameterEvent>?, UnsafePointer<AudioTimeStamp>?, UnsafeMutablePointer<AudioTimeStamp>?) -> OSStatus
- 提供了操作音频队列参数的接口如下:
//获取音频队列参数值.
func AudioQueueGetParameter(AudioQueueRef, AudioQueueParameterID, UnsafeMutablePointer<AudioQueueParameterValue>) -> OSStatus
//设置播放音频队列参数值。
func AudioQueueSetParameter(AudioQueueRef, AudioQueueParameterID, AudioQueueParameterValue) -> OSStatus
- 提供了操作音频队列属性的接口如下:
//获取音频队列属性值。
func AudioQueueGetProperty(AudioQueueRef, AudioQueuePropertyID, UnsafeMutableRawPointer, UnsafeMutablePointer<UInt32>) -> OSStatus
//设置一个音频队列属性值。
func AudioQueueSetProperty(AudioQueueRef, AudioQueuePropertyID, UnsafeRawPointer, UInt32) -> OSStatus
//获取音频队列属性值的大小。
func AudioQueueGetPropertySize(AudioQueueRef, AudioQueuePropertyID, UnsafeMutablePointer<UInt32>) -> OSStatus
//将属性侦听器回调添加到音频队列。
func AudioQueueAddPropertyListener(AudioQueueRef, AudioQueuePropertyID, AudioQueuePropertyListenerProc, UnsafeMutableRawPointer?) -> OSStatus
//从音频队列中移除一个属性侦听器回调。
func AudioQueueRemovePropertyListener(AudioQueueRef, AudioQueuePropertyID, AudioQueuePropertyListenerProc, UnsafeMutableRawPointer?) -> OSStatus
- 提供了处理时间的接口如下:
//为音频队列创建一个时间轴对象。
func AudioQueueCreateTimeline(AudioQueueRef, UnsafeMutablePointer<AudioQueueTimelineRef?>) -> OSStatus
//析构释放音频队列的时间轴对象。
func AudioQueueDisposeTimeline(AudioQueueRef, AudioQueueTimelineRef) -> OSStatus
//获取与音频队列关联的音频硬件设备的当前时间。
func AudioQueueDeviceGetCurrentTime(AudioQueueRef, UnsafeMutablePointer<AudioTimeStamp>) -> OSStatus
//获取音频硬件设备最接近请求的启动时间的启动时间。
func AudioQueueDeviceGetNearestStartTime(AudioQueueRef, UnsafeMutablePointer<AudioTimeStamp>, UInt32) -> OSStatus
//将音频队列的相关音频硬件设备的时间从一种时基表示形式转换为另一种时基表示形式。
func AudioQueueDeviceTranslateTime(AudioQueueRef, UnsafePointer<AudioTimeStamp>, UnsafeMutablePointer<AudioTimeStamp>) -> OSStatus
//获取当前音频队列时间。
func AudioQueueGetCurrentTime(AudioQueueRef, AudioQueueTimelineRef?, UnsafeMutablePointer<AudioTimeStamp>?, UnsafeMutablePointer<DarwinBoolean>?) -> OSStatus
- 提供了执行离线渲染的接口如下:
//设置播放音频队列的呈现模式和音频格式。
func AudioQueueSetOfflineRenderFormat(AudioQueueRef, UnsafePointer<AudioStreamBasicDescription>?, UnsafePointer<AudioChannelLayout>?) -> OSStatus
//使用回放音频队列将音频导出到缓冲区,而不是设备。
func AudioQueueOfflineRender(AudioQueueRef, UnsafePointer<AudioTimeStamp>, AudioQueueBufferRef, UInt32) -> OSStatus
- 提供了回调函数如下:
//当一个录制音频队列完成填充音频队列缓冲区时,系统调用。
typealias AudioQueueInputCallback
//当一个音频队列缓冲区可以重用时,系统调用。
typealias AudioQueueOutputCallback
//当指定的音频队列属性改变值时,系统调用。
typealias AudioQueuePropertyListenerProc
1.1.6 缓存入队和排队
缓冲区队列提供音频队列(实际上是音频队列服务)及其名称。您在音频队列体系结构中遇到了缓冲队列(一个按顺序排列的缓冲区列表)。在这里,您将了解音频队列对象以及回调函数如何在录制或回放期间管理缓冲区队列。特别是,您将了解排队,即将音频队列缓冲区添加到缓冲区队列。无论您是实现录制还是回放,排队都是回调执行的任务
1.1.6.1 录音过程
在录制时,一个音频队列缓冲区将填充从输入设备(如麦克风)获取的音频数据。缓冲区队列中的其余缓冲区排在当前缓冲区后面,等待依次填充音频数据。
音频队列按获取音频数据的顺序将填满的缓冲区传递给回调。图1-3说明了在使用音频队列时如何进行录制。
使用音频队列时如何进行录制-
在图1-3的步骤1中,开始记录。音频队列用获取的数据填充缓冲区。
-
在步骤2中,第一个缓冲区已被填充。音频队列调用回调,将完整的缓冲区(缓冲区1)交给它。回调(步骤3)将缓冲区的内容写入音频文件。同时,音频队列用新获得的数据填充另一个缓冲区(缓冲区2)。
-
在第4步中,回调将它刚刚写到磁盘的缓冲区(缓冲区1)放入队列中,使其再次被填满。音频队列再次调用回调(步骤5),并将下一个完整的缓冲区(缓冲区2)交给它。这种循环稳定状态一直持续到用户停止录制为止。
1.1.6.2 回放过程
播放时,一个音频队列缓冲区被发送到输出设备,如扬声器。缓冲区队列中剩余的缓冲区排在当前缓冲区后面,等待依次播放。
音频队列将音频数据的播放缓冲区按播放的顺序传递给回调。回调将新的音频数据读入缓冲区,然后对其进行排队。图1-4演示了在使用音频队列时回放是如何工作的。
图1-4演示了在使用音频队列时回放是如何工作的在图1-4的步骤1中,应用程序启动回放音频队列。应用程序为每个音频队列缓冲区调用一次回调,填充它们并将它们添加到缓冲区队列中。启动确保回放可以在应用程序调用AudioQueueStart函数时立即启动(步骤2)。
在步骤3中,音频队列将第一个缓冲区(缓冲区1)发送到输出。
一旦播放了第一个缓冲区,播放音频队列就进入循环稳定状态。音频队列开始播放下一个缓冲区(缓冲区2,步骤4)并调用回调(步骤5),将刚刚播放的缓冲区(缓冲区1)交给它。
1.1.6.3 控制回放过程
音频队列缓冲区总是按照它们进入队列的顺序播放。然而,音频队列服务通过AudioQueueEnqueueBufferWithParameters函数为您提供了对回放过程的一些控制。这个功能让你:
- 为缓冲区设置精确的回放时间。这使您能够支持同步。
- 在音频队列缓冲区的开始或结束处修剪帧。这可以让您移除引导或尾随的静默。
- 在缓冲区的粒度上设置回放增益
您可以使用此函数对缓冲区队列施加一些控制。您可以分配音频队列设置,这些设置实际上是由音频队列缓冲区携带的。因此,设置在音频队列缓冲区开始播放时生效。
这个函数只适用于回放。录制音频队列不接受参数,也不支持可变比特率(VBR)格式(这可能需要调整)。
AudioQueueEnqueueBufferWithParameters函数的完整定义如下:
func AudioQueueEnqueueBufferWithParameters(_ inAQ: AudioQueueRef,
_ inBuffer: AudioQueueBufferRef,
_ inNumPacketDescs: UInt32,
_ inPacketDescs: UnsafePointer<AudioStreamPacketDescription>?,
_ inTrimFramesAtStart: UInt32,
_ inTrimFramesAtEnd: UInt32,
_ inNumParamValues: UInt32,
_ inParamValues: UnsafePointer<AudioQueueParameterEvent>?,
_ inStartTime: UnsafePointer<AudioTimeStamp>?,
_ outActualStartTime: UnsafeMutablePointer<AudioTimeStamp>?) -> OSStatus
函数参数详解如下:
inAQ
: 拥有音频队列缓冲区的音频队列对象。inBuffer
: 要添加到缓冲区队列中的音频队列缓冲区。在调用此函数之前,缓冲区必须包含要播放的音频数据。inNumPacketDescs
: inBuffer参数中音频数据的包数。对下列任何一种情况使用0值:(1)当播放恒定比特率(CBR)格式时。(2)当使用audioqueueallocatebufferwithpacketdescription(:::: _:)函数分配正在重新排队的缓冲区时。在这种情况下,回调应该描述缓冲区的mpacketdescription和mPacketDescriptionCount字段中的缓冲区包。inPacketDescs
: 包描述的数组。在下列任何一种情况下使用NULL值:(1)当播放恒定比特率(CBR)格式时。(2)当使用audioqueueallocatebufferwithpacketdescription(:::: _:)函数分配正在重新排队的缓冲区时。在这种情况下,回调应该描述缓冲区的mpacketdescription和mPacketDescriptionCount字段中的缓冲区包。inTrimFramesAtStart
: 在缓冲区开始时要跳过的启动帧数。inTrimFramesAtEnd
: 缓冲区结束时要跳过的帧数。inNumParamValues
: inParamValues参数指向的音频队列参数值的数量。如果不设置参数,则使用0。inParamValues
: 要应用于音频队列缓冲区的参数数组。(在OS X v10.5中,只有一个音频队列参数kAudioQueueParam_Volume。)如果没有设置缓冲区的参数,则使用NULL。
在播放之前分配参数值——在缓冲区播放时不能更改参数值。音频队列缓冲区参数的更改将在缓冲区开始播放时生效。inStartTime
: 播放缓冲区所需的开始时间。要指定与音频队列启动时间相关的时间,请使用AudioTimeStamp结构的mSampleTime字段。使用NULL来指示缓冲区应该尽快播放—这可能是在前面排队的缓冲区完成播放之后。
缓冲区按它们进入队列的顺序运行(先入先出)。如果有多个缓冲区在排队,则开始时间必须是升序或NULL;否则,将发生错误。此参数指定音频数据何时开始播放,忽略在mframesatstart参数中指定的任何修剪帧。outActualStartTime
: 在输出时,缓冲区实际开始播放的时间。
1.1.7 使用编解码器和音频数据格式
音频队列服务根据需要使用编解码器(音频数据编码/解码组件)在音频格式之间进行转换。您的录音或播放应用程序可以使用任何音频格式,其中安装了一个编解码器。您不需要编写自定义代码来处理各种音频格式。具体来说,回调不需要知道数据格式。
事情是这样的。每个音频队列都有一个音频数据格式,用AudioStreamBasicDescription结构表示。当您在结构的mFormatID字段中指定格式时,音频队列将使用适当的编解码器。然后指定采样率和通道计数,这就是全部内容。您将在录制音频和播放音频中看到设置音频数据格式的示例。
录音音频队列使用已安装的编解码器,如图1-5所示。
录音期间的音频格式转换在图1-5的步骤1中,应用程序告诉音频队列开始录制,并告诉它要使用的数据格式。在步骤2中,音频队列获取新的音频数据并根据指定的格式使用编解码器进行转换。然后,音频队列调用回调,将一个包含适当格式的音频数据的缓冲区交给它。在步骤3中,回调将格式化的音频数据写入磁盘。同样,您的回调不需要知道数据格式。
回放音频队列使用已安装的编解码器,如图1-6所示。
播放期间的音频格式转换在图1-6的步骤1中,应用程序告诉音频队列开始播放,并告诉它要播放的音频文件中包含的数据格式。在步骤2中,音频队列调用您的回调,它从音频文件中读取数据。回调函数将原始格式的数据传递给音频队列。在步骤3中,音频队列使用适当的编解码器,然后将音频发送到目的地。
音频队列可以使用任何已安装的编解码器,无论是Mac OS X本地的还是由第三方提供的。要指定要使用的编解码器,需要向音频队列的AudioStreamBasicDescription结构提供其四字符代码ID。你会在录制音频中看到一个这样的例子。
Mac OS X包含广泛的音频编解码器,如CoreAudioTypes.h头文件中的格式IDs枚举所列,并如Core audio Data Types Reference所述。您可以使用Audio Toolbox框架中AudioFormat.h头文件中的接口来确定系统上可用的编解码器。您可以使用Fiendishthngs应用程序在系统上显示编解码器,可以通过http://developer.apple.com/samplecode/Fiendishthngs/获得样例代码。
1.1.8 音频队列控制和状态
音频队列在创建和处理之间有一个生命周期。你的应用程序管理这个生命周期,并控制音频队列的状态——使用AudioQueue.h头文件中声明的六个函数:
- 开始(
AudioQueueStart
)。呼叫以启动录制或回放。 - (
AudioQueuePrime
)。对于回放,在调用AudioQueueStart之前调用,以确保音频队列可以立即播放数据。此功能与记录无关。 - 停止(
AudioQueueStop
)。调用重置音频队列(请参阅下面的AudioQueueReset描述),然后停止录制或回放。播放音频队列回调在没有更多数据播放时调用此函数。 - 暂停(
AudioQueuePause
)。调用暂停录制或回放而不影响缓冲区或重置音频队列。要继续,请调用AudioQueueStart函数。 - 冲洗(
AudioQueueFlush
)。进入最后一个音频队列缓冲区后调用,以确保所有缓冲的数据以及处理中的所有音频数据都被录制或播放。 - 重置(
AudioQueueReset
)。调用立即静音一个音频队列,从先前预定的使用中删除所有缓冲区,并重置所有解码器和DSP状态。
您可以在同步或异步模式下使用AudioQueueStop函数:
- 同步停止立即发生,而不考虑先前缓冲的音频数据。
- 异步停止发生在播放或记录所有排队的缓冲区之后。
有关这些函数的完整描述,请参阅Audio Queue Services参考资料,其中包括关于同步和异步停止音频队列的更多信息。
1.1.9 音频队列参数
音频队列有可调的设置,称为参数。每个参数都有一个枚举常量作为其键,一个浮点数作为其值。参数通常用于回放,而不是录制。
在Mac OS X v10.5中,唯一可用的音频队列参数是for gain。该参数的值是使用kAudioQueueParam_Volume常量设置或检索的,可用范围为0.0(静默)到1.0(统一增益)。
你的应用程序可以设置音频队列参数的两种方式:
- 每个音频队列,使用AudioQueueSetParameter函数。这允许您直接更改音频队列的设置。这些变化立即生效。
- 每个音频队列缓冲区,使用AudioQueueEnqueueBufferWithParameters函数。这使您可以分配音频队列设置,这些设置实际上是由音频队列缓冲区携带的。这些更改将在音频队列缓冲区开始播放时生效。
在这两种情况下,音频队列的参数设置将一直有效,直到您更改它们。
可以使用AudioQueueGetParameter函数在任何时候访问音频队列的当前参数值。有关获取和设置参数值的函数的完整描述,请参阅音频队列服务参考资料。
参考苹果官方文档:https://developer.apple.com/documentation/audiotoolbox/audio_queue_services
网友评论