音频录制
当你使用音频队列服务进行录制的时候,你可以将音频录制到任何地方——磁盘文件、网络连接或内存对象等等。本章将介绍中最常见的一种情况——将音频录制到磁盘文件中。
注意: 本章介绍了基于ANSI-C的录制的实现,并且使用了MAC OS X中Core Audio SDK中了一些C++类,如果想了解基于Objective-C的例子,请参考iOS Dev Center中的SpeakHere 例子。 |
要将录制功能添加到你的应用程序中,一般都要进行以下几个步骤:
- 定义一个自定义的结构来管理状态、格式以及路径信息等。
- 编写音频队列回调函数来执行实际的录制工作。
- 可选的,你可以编写代码来为音频队列缓冲区选择一个合适的大小。如果你将要录制的格式使用了magic cookies,你需要编写相应的代码来配合使用。
- 填充自定义结构中的各个域,包括制定音频队列将要录制到的文件的数据流、还有这个文件的路径。
- 创建一个录制用的音频队列并且让音频队列创建一系列的音频队列缓冲区,同时创建一个它将要写入的文件。
- 通知音频队列开始录制音频。
- 录制完毕之后,通知音频队列停止录制,然后释放掉它,同时它会释放掉它所拥有的缓冲区。
定义一个用来管理状态的自定义结构
使用音频队列服务来开发一个音频录制解决方案的时候,第一步就是定义一个自定义的结构。你将使用这个结构来管理音频格式和音频队列状态信息。清单2-1演示了这个这样的一个结构。
一个录制用音频队列的自定义结构
- static const int kNumberBuffers = 3; // 1
- struct AQRecorderState {
- AudioStreamBasicDescription mDataFormat; // 2
- AudioQueueRef mQueue; // 3
- AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4
- AudioFileID mAudioFile; // 5
- UInt32 bufferByteSize; // 6
- SInt64 mCurrentPacket; // 7
- bool mIsRunning; // 8
- };
下面是这个结构体中每个域的说明:
- 设置要使用的音频队列缓冲区的数量。
- 一个AudioStreamBasicDescription结构(来自CoreAudioTypes.h头文件),它表示将要写入磁盘的音频数据的格式,音频队列缓冲区使用这个格式来指定它的mQueue域。mDataFormat域是由你的应用程序初始化的,就像“Set Up an Audio Format for Recording.”中说的一样。通过查询音频都列的kAudioQueueProperty_StreamDescription属性来更新这个域的值是一个很好的做法,就像 “Getting the Full Audio Format from an Audio Queue.”中描述的一样。在MAC OS X10.5中要使用the
kAudioConverterCurrentInputStreamDescription。
关于AudioStreamBasicDescription结构体的详细信息,请参考Core Audio Data Types Reference.
- 由你的应用程序创建的音频队列
- 一个指向由你的音频队列所管理的音频队列缓冲区的指针数组。
- 一个表示你的程序录制音频时写入的文件的音频文件对象。
- 每个音频队列缓冲区的字节大小。它的值在随后的例子中的DeriveBufferSize函数中计算出来,它在音频队列创建之后,开始录制音频之前计算出来,参考“Write a Function to Derive Recording Audio Queue Buffer Size.”。
- 从当前音频队列缓冲区写入文件的第一个包(packet)的索引。
- 一个布尔值,用来指示音频队列是否在运行中。
编写录制用音频队列回调函数
接下来,编写一个录制用回调函数,这个函数主要做两个事情:
- 将新填充进音频队列缓冲区的内容写入你正在录制的文件中
- 将刚才已经将内容写入文件的音频队列缓冲区入队到缓冲区队列
本段展示了一个回调函数声明的列子,然后分别描述这两个任务,最后展示一个完整的录制用回调函数。关于录制用音频队列回调函数所扮演的角色,可以参考图示 1-3
录制用音频队列回调函数的声明
列表2-2是一个录制用音频队列回调函数的声明的例子,是在AudioQueue.h头文件中声明的AudioQueueInputCallback
。
录制用音频队列回调函数声明
- static void HandleInputBuffer (
- void *aqData, // 1
- AudioQueueRef inAQ, // 2
- AudioQueueBufferRef inBuffer, // 3
- const AudioTimeStamp *inStartTime, // 4
- UInt32 inNumPackets, // 5
- const AudioStreamPacketDescription *inPacketDesc // 6
- )
下面就是这段代码如何工作的:
- 一般来说,
aqData
是一个自定义的数据结构,他包含了音频队列的状态信息,就像“Define a Custom Structure to Manage State.”中的一样。 - 拥有这个回调函数的音频队列
- 包含录制数据的音频队列缓冲区
- 音频队列缓冲区中第一个采样的的时间(对于简单的录制,这个是不需要的)
- inPacketDesc域中packet descriptions的数量,如果是0,表明这是个CBR数据
- 对于压缩数据格式如果需要packet descriptions,这个packet descriptions是由编码器产生的
将音频队列缓冲区中的数据写入磁盘
录制用音频队列回调函数要做的第一件事情就是讲音频队列缓冲区中的内容写入磁盘。这个缓冲区就是音频队列从输入设备最新输入的音频数据。这个回调函数使用AudioFile.h头文件中声明的AudioFileWritePackets
函。如列表2-3所示
将音频队列缓冲区数据写入磁盘
- AudioFileWritePackets ( // 1
- pAqData->mAudioFile, // 2
- false, // 3
- inBuffer->mAudioDataByteSize, // 4
- inPacketDesc, // 5
- pAqData->mCurrentPacket, // 6
- &inNumPackets, // 7
- inBuffer->mAudioData // 8
- );