版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.12.28 |
前言
ios系统中有很多方式可以播放音频文件,这里我们就详细的说明下播放音乐文件的原理和实例。感兴趣的可以看我写的上面几篇。
1. 几种播放音频文件的方式(一) —— 播放本地音乐
2. 几种播放音频文件的方式(二) —— 音效播放
3. 几种播放音频文件的方式(三) —— 网络音乐播放
4. 几种播放音频文件的方式(四) —— 音频队列服务(Audio Queue Services)(一)
5. 几种播放音频文件的方式(五) —— 音频队列服务(Audio Queue Services)简介(二)
6. 几种播放音频文件的方式(六) —— 音频队列服务(Audio Queue Services)之关于音频队列(三)
Recording Audio - 录制音频
使用音频队列服务进行录制时,存储目标可以是任何事物 —— 磁盘上的文件,网络连接,内存中的对象等等。 本章介绍最常见的情况:基本录制到磁盘文件。
注意:本章描述了一个基于
ANSI-C
的录制实现,并使用了Mac OS X Core Audio SDK
中的C ++类。 对于基于Objective-C的示例,请参阅iOS Dev Center的SpeakHere
示例代码。
要将录制功能添加到应用程序中,通常需要执行以下步骤:
- 定义一个自定义结构来管理状态,格式和路径信息。
- 编写一个音频队列callback回调函数来执行实际录制。
- 可以编写代码来确定音频队列缓冲区的大小。 如果您将以使用cookie的格式进行录制,编写代码与
magic cookies
一起配合。 - 填写自定义结构的字段。 这包括指定音频队列发送到正在记录的文件的数据流,以及该文件的路径。
- 创建一个录制音频队列,并要求它创建一组音频队列缓冲区。 还要创建一个文件来记录。
- 告诉音频队列开始录制。
- 完成后,告诉音频队列停止并销毁。 音频队列销毁其缓冲区。
本章的其余部分将详细介绍这些步骤。
Define a Custom Structure to Manage State - 定义一个自定义结构来管理状态
使用音频队列服务开发录音解决方案的第一步是定义一个自定义结构。 您将使用此结构来管理音频格式和音频队列状态信息。 Listing 2-1
说明了这样一个结构:
// Listing 2-1 A custom structure for a recording audio queue
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 X v10.5
上,改用kAudioConverterCurrentInputStreamDescription
属性。有关AudioStreamBasicDescription
结构的详细信息,请参阅Core Audio Data Types Reference。
- 表示要写入磁盘的音频数据格式的
- 由您的应用程序创建录制音频队列。
- 一个数组,指向由音频队列管理的音频队列缓冲区的指针。
- 音频文件对象,表示您的程序将音频数据记录到其中的文件。
每个音频队列缓冲区的大小(以字节为单位)。在创建音频队列之后并在启动之前,在DeriveBufferSize
函数的这些示例中计算此值。请参阅Write a Function to Derive Recording Audio Queue Buffer Size。
- 音频文件对象,表示您的程序将音频数据记录到其中的文件。
- 要从当前音频队列缓冲区写入的第一个数据包的数据包索引。
- 指示音频队列是否正在运行的布尔值。
Write a Recording Audio Queue Callback - 写一个录制音频队列回调函数
接下来,写一个录音音频队列回调函数。 这个回调做了两件事:
- 将新填充的音频队列缓冲区的内容写入到正在录制的音频文件中
- 将音频队列缓冲区(其内容刚刚写入磁盘)排入缓冲区队列
本节展示一个示例回调声明,然后分别描述这两个任务,最后展示一个完整的录制回调。 有关录制音频队列回调角色的说明,请参阅Figure 1-3。
1. The Recording Audio Queue Callback Declaration - 录制音频队列回调函数声明
Listing 2-2
显示了一个记录音频队列回调函数的示例声明,声明为AudioQueue.h
头文件中的AudioQueueInputCallback:
// Listing 2-2 The recording audio queue callback declaration
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
参数中的数据包描述数量。 值为0表示CBR数据。
-
- 对于需要数据包描述的压缩音频数据格式,编码器为缓冲区中数据包生成的数据包描述。
2. Writing an Audio Queue Buffer to Disk - 将音频队列缓冲区写入磁盘
录音的音频队列回调的第一项任务是将音频队列缓冲区写入磁盘。 这个缓冲区是回调的音频队列刚刚完成填充来自输入设备的新的音频数据的那个缓冲区。 回调使用AudioFile.h
头文件中的AudioFileWritePackets
函数,如Listing 2-3
所示。
// Listing 2-3 Writing an audio queue buffer to disk
AudioFileWritePackets ( // 1
pAqData->mAudioFile, // 2
false, // 3
inBuffer->mAudioDataByteSize, // 4
inPacketDesc, // 5
pAqData->mCurrentPacket, // 6
&inNumPackets, // 7
inBuffer->mAudioData // 8
);
以下是这段代码的工作原理:
- 在
AudioFile.h
头文件中声明的AudioFileWritePackets
函数将缓冲区的内容写入音频数据文件。
- 在
- 音频文件对象(类型
AudioFileID
),表示要写入的音频文件。pAqData
变量是一个指向Listing 2-1
中描述的数据结构的指针。
- 音频文件对象(类型
- 使用false值表示函数在写入时不应该缓存数据。
- 正在写入的音频数据的字节数。
inBuffer
变量表示由音频队列交给回调的音频队列缓冲区。
- 正在写入的音频数据的字节数。
- 音频数据的数据包描述数组。 值为
NULL
表示不需要数据包描述(例如用于CBR音频数据)。
- 音频数据的数据包描述数组。 值为
- 要写入的第一个数据包的数据包索引。
- 在输入时,要写入的数据包的数量。 输出时,实际写入的数据包数量。
- 将新的音频数据写入音频文件。
3. Enqueuing an Audio Queue Buffer - 将一个音频队列缓冲入队
现在来自音频队列缓冲区的音频数据已被写入音频文件,回调将缓冲区排入队列,如Listing 2-4
所示。 一旦回到缓冲区队列中,缓冲区就准备好接受更多的输入音频数据。
// Listing 2-4 Enqueuing an audio queue buffer after writing to disk
AudioQueueEnqueueBuffer ( // 1
pAqData->mQueue, // 2
inBuffer, // 3
0, // 4
NULL // 5
);
以下是这段代码的工作原理:
-
AudioQueueEnqueueBuffer
函数将音频队列缓冲区添加到音频队列的缓冲区队列中。
-
- 将音频队列添加到指定的音频队列缓冲区。
pAqData
变量是一个指向Listing 2-1
中描述的数据结构的指针。
- 将音频队列添加到指定的音频队列缓冲区。
- 要排队的音频队列缓冲区。
- 音频队列缓冲区数据中的数据包描述数量。 设置为0,因为此参数未用于记录。
- 描述音频队列缓冲区数据的数据包描述数组。 设置为
NULL
,因为此参数未用于记录。
- 描述音频队列缓冲区数据的数据包描述数组。 设置为
4. A Full Recording Audio Queue Callback - 一个完整的录制音频队列回调
Listing 2-5
显示了完整记录音频队列回调的基本版本。 与本文档中的其他代码示例一样,此列表不包括错误处理。
// Listing 2-5 A recording audio queue callback function
static void HandleInputBuffer (
void *aqData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc
) {
AQRecorderState *pAqData = (AQRecorderState *) aqData; // 1
if (inNumPackets == 0 && // 2
pAqData->mDataFormat.mBytesPerPacket != 0)
inNumPackets =
inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
if (AudioFileWritePackets ( // 3
pAqData->mAudioFile,
false,
inBuffer->mAudioDataByteSize,
inPacketDesc,
pAqData->mCurrentPacket,
&inNumPackets,
inBuffer->mAudioData
) == noErr) {
pAqData->mCurrentPacket += inNumPackets; // 4
}
if (pAqData->mIsRunning == 0) // 5
return;
AudioQueueEnqueueBuffer ( // 6
pAqData->mQueue,
inBuffer,
0,
NULL
);
}
以下是这段代码的工作原理:
- 实例化时提供给音频队列对象的自定义结构,包括表示要录制的音频文件的音频文件对象以及各种状态数据。请参阅Define a Custom Structure to Manage State。
- 如果音频队列缓冲区包含CBR数据,则计算缓冲区中的数据包数量。此数字等于缓冲区中的数据总字节数除以每个数据包的(恒定)字节数。对于VBR数据,音频队列在调用回调时提供缓冲区中的数据包数量。
- 将缓冲区的内容写入音频数据文件。有关详细说明,请参阅Writing an Audio Queue Buffer to Disk。
- 如果成功写入音频数据,则增加音频数据文件的包索引以准备好写入下一个缓冲器的音频数据。
- 如果音频队列已经停止,则返回。
- 将刚刚写入内容的音频队列缓冲区重新入队。有关详细说明,请参阅Enqueuing an Audio Queue Buffer。
Write a Function to Derive Recording Audio Queue Buffer Size - 编写一个函数来获取记录音频队列缓冲区大小
音频队列服务期望您的应用程序指定您使用的音频队列缓冲区的大小。 Listing 2-6
显示了一个这样做的方法。 它产生足够大的缓冲区大小来保存给定持续时间的音频数据。
这里的计算考虑了您正在记录的音频数据格式。 格式包括可能影响缓冲区大小的所有因素,例如音频通道的数量。
// Listing 2-6 Deriving a recording audio queue buffer size
void DeriveBufferSize (
AudioQueueRef audioQueue, // 1
AudioStreamBasicDescription &ASBDescription, // 2
Float64 seconds, // 3
UInt32 *outBufferSize // 4
) {
static const int maxBufferSize = 0x50000; // 5
int maxPacketSize = ASBDescription.mBytesPerPacket; // 6
if (maxPacketSize == 0) { // 7
UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
AudioQueueGetProperty (
audioQueue,
kAudioQueueProperty_MaximumOutputPacketSize,
// in Mac OS X v10.5, instead use
// kAudioConverterPropertyMaximumOutputPacketSize
&maxPacketSize,
&maxVBRPacketSize
);
}
Float64 numBytesForTime =
ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
*outBufferSize =
UInt32 (numBytesForTime < maxBufferSize ?
numBytesForTime : maxBufferSize); // 9
}
以下是这段代码的工作原理:
- 音频队列拥有您要指定大小的缓冲区。
- 音频队列的
AudioStreamBasicDescription结
构。
- 音频队列的
- 按照音频的秒数指定每个音频队列缓冲区的大小。
- 输出时,按字节计算每个音频队列缓冲区的大小。
- 音频队列缓冲区大小的上限(以字节为单位)。在这个例子中,上限设置为320 KB。这对应于大约五秒钟的立体声,采样率为96kHz的24位音频。
- 对于CBR音频数据,从
AudioStreamBasicDescription
结构获取(恒定)数据包大小。使用此值作为最大数据包大小。该做法具有确定要记录的音频数据是CBR还是VBR的副作用。如果是VBR,则音频队列的AudioStreamBasicDescription
结构将每个数据包的字节数值列为0。
- 对于CBR音频数据,从
- 对于VBR音频数据,查询音频队列以获取估计的最大数据包大小。
- 以字节为单位获取缓冲区大小。
- 将缓冲区大小(如果需要)限制到先前设置的上限。
Set a Magic Cookie for an Audio File - 为声音文件设置一个Magic Cookie
某些压缩音频格式(如MPEG 4 AAC
)利用包含音频元数据的结构。 这些结构被称为Magic Cookie
。 当您使用音频队列服务录制这种格式时,您必须从音频队列中获取Magic Cookie
并将其添加到音频文件,然后再开始录制。
Listing 2-7
显示了如何从音频队列中获取Magic Cookie并将其应用于音频文件。 在录制之前,您的代码会调用这样的函数,然后在录制之后再次调用一些函数 —— 一些编解码器在录制停止时更新Magic Cookie数据。
// Listing 2-7 Setting a magic cookie for an audio file
OSStatus SetMagicCookieForFile (
AudioQueueRef inQueue, // 1
AudioFileID inFile // 2
) {
OSStatus result = noErr; // 3
UInt32 cookieSize; // 4
if (
AudioQueueGetPropertySize ( // 5
inQueue,
kAudioQueueProperty_MagicCookie,
&cookieSize
) == noErr
) {
char* magicCookie =
(char *) malloc (cookieSize); // 6
if (
AudioQueueGetProperty ( // 7
inQueue,
kAudioQueueProperty_MagicCookie,
magicCookie,
&cookieSize
) == noErr
)
result = AudioFileSetProperty ( // 8
inFile,
kAudioFilePropertyMagicCookieData,
cookieSize,
magicCookie
);
free (magicCookie); // 9
}
return result; // 10
}
以下是这段代码的工作原理:
- 您用于录制的音频队列。
- 您正在录制写入的音频文件。
- 指示此函数成功或失败的结果变量。
- 一个变量来保存magic cooki的数据大小。
- 从音频队列中获取magic cooki的数据大小,并将其存储在
cookieSize
变量中。
- 从音频队列中获取magic cooki的数据大小,并将其存储在
- 分配一个字节数组来保存魔法cookie信息。
- 通过查询音频队列的
kAudioQueueProperty_MagicCookie
属性获取magic cooki。
- 通过查询音频队列的
- 为正在录制写入的音频文件设置
magic cookie
。AudioFileSetProperty
函数在AudioFile.h
头文件中声明。
- 为正在录制写入的音频文件设置
- 释放临时cookie变量的内存。
- 返回此函数的成功或失败。
Set Up an Audio Format for Recording - 设置录制的音频格式
本节介绍如何为音频队列设置音频数据格式。 音频队列使用这种格式来记录到一个文件。
要设置音频数据格式,请指定:
- 音频数据格式类型(如线性PCM,AAC等)
- 采样率(如44.1 kHz)
- 音频通道数量(如2,立体声)
- 比特深度(如16比特)
- 每个数据包的帧数(线性PCM,例如,每个数据包使用一个帧)
- 音频文件类型(如CAF,AIFF等)
- 文件类型所需的音频数据格式的详细信息
Listing 2-8
说明了如何为每个属性设置一个固定的选项来设置录制的音频格式。 在产品代码中,通常允许用户指定音频格式的一些或全部方面。 无论采取哪种方法,目标都是填充 AQRecorderState
自定义结构的mDataFormat
字段,如 Define a Custom Structure to Manage State中所述。
Listing 2-8 Specifying an audio queue’s audio data format
AQRecorderState aqData; // 1
aqData.mDataFormat.mFormatID = kAudioFormatLinearPCM; // 2
aqData.mDataFormat.mSampleRate = 44100.0; // 3
aqData.mDataFormat.mChannelsPerFrame = 2; // 4
aqData.mDataFormat.mBitsPerChannel = 16; // 5
aqData.mDataFormat.mBytesPerPacket = // 6
aqData.mDataFormat.mBytesPerFrame =
aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket = 1; // 7
AudioFileTypeID fileType = kAudioFileAIFFType; // 8
aqData.mDataFormat.mFormatFlags = // 9
kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsSignedInteger
| kLinearPCMFormatFlagIsPacked;
以下是这段代码的工作原理:
- 创建
AQRecorderState
自定义结构的一个实例。结构的mDataFormat
字段包含一个AudioStreamBasicDescription
结构。在mDataFormat
字段中设置的值为音频队列提供了音频格式的初始定义,这也是您录制文件的音频格式。在Listing 2-10中,您可以获得更完整的音频格式规范,Core Audio根据格式类型和文件类型为您提供了音频格式。
- 创建
- 将音频数据格式类型定义为线性PCM。请参阅Core Audio Data Types Reference以获取可用数据格式的完整列表。
- 定义采样率为44.1 kHz。
- 将通道数量定义为2。
- 将每个通道的位深度定义为16。
- 将每个数据包的字节数和每个字节的字节数定义为4(即每个采样2个通道乘以2个字节)。
- 将每个数据包的帧数定义为1。
- 将文件类型定义为AIFF。请参阅
AudioFile.h
头文件中的音频文件类型枚举以获取可用文件类型的完整列表。您可以指定已安装编解码器的任何文件类型,如Using Codecs and Audio Data Formats中所述。
- 将文件类型定义为AIFF。请参阅
- 设置指定文件类型所需的格式标志。
Create a Recording Audio Queue - 创建一个录制音频队列
现在,通过设置录制回调和音频数据格式,您可以创建并配置一个音频队列进行录制。
1. Creating a Recording Audio Queue - 创建一个录制音频队列
Listing 2-9
演示了如何创建一个记录音频队列。 请注意,AudioQueueNewInput
函数使用前面步骤中配置的回调,自定义结构和音频数据格式
// Listing 2-9 Creating a recording audio queue
AudioQueueNewInput ( // 1
&aqData.mDataFormat, // 2
HandleInputBuffer, // 3
&aqData, // 4
NULL, // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
以下是这段代码的工作原理:
-
AudioQueueNewInput
函数创建一个新的录音音频队列。
-
- 用于录制的音频数据格式。 请参阅Set Up an Audio Format for Recording。
- 用于录音音频队列的回调函数。 请参阅Write a Recording Audio Queue Callback。
- 记录音频队列的自定义数据结构。 请参阅Define a Custom Structure to Manage State。
- 将在其上调用回调的运行循环。 使用NULL来指定默认行为,在该行为中将在音频队列内部的线程上调用回调。 这是典型的用法 - 它允许音频队列在您的应用程序的用户界面线程等待用户输入停止录制时进行录制。
- 可以调用回调的运行循环模式。 通常,在这里使用
kCFRunLoopCommonModes
常量。
- 可以调用回调的运行循环模式。 通常,在这里使用
- 保留。 必须为0。
- 输出时,新分配录音音频队列。
2. Getting the Full Audio Format from an Audio Queue - 从音频队列中获取完整的音频格式
当音频队列存在时(请参阅Creating a Recording Audio Queue),它可能已经比您更完整地填充了AudioStreamBasicDescription
结构,特别是对于压缩格式。 要获得完整的格式描述,请调用AudioQueueGetProperty
函数,如代码Listing 2-10
所示。 创建要录制的音频文件时,请使用完整的音频格式(请参阅Create an Audio File)。
Listing 2-10 Getting the audio format from an audio queue
UInt32 dataFormatSize = sizeof (aqData.mDataFormat); // 1
AudioQueueGetProperty ( // 2
aqData.mQueue, // 3
kAudioQueueProperty_StreamDescription, // 4
// in Mac OS X, instead use
// kAudioConverterCurrentInputStreamDescription
&aqData.mDataFormat, // 5
&dataFormatSize // 6
);
以下是这段代码的工作原理:
- 获取预期的属性值大小,以便在查询有关其音频数据格式的音频队列时使用。
-
AudioQueueGetProperty
函数获取音频队列中指定属性的值。
-
- 音频队列从中获取音频数据格式。
- 用于获取音频队列数据格式值的属性ID。
- 在输出时,从音频队列中获得完整的音频数据格式,以
AudioStreamBasicDescription
结构的形式。
- 在输出时,从音频队列中获得完整的音频数据格式,以
- 在输入时,
AudioStreamBasicDescription
结构的预期大小。 在输出上,实际的大小。 您的录制应用程序不需要使用此值。
- 在输入时,
Create an Audio File - 创建一个音频文件
通过创建和配置音频队列,您可以创建将录制音频数据的音频文件,如Listing 2-11
所示。 音频文件使用先前存储在音频队列的自定义结构中的数据格式和文件格式规范。
// Listing 2-11 Creating an audio file for recording
CFURLRef audioFileURL =
CFURLCreateFromFileSystemRepresentation ( // 1
NULL, // 2
(const UInt8 *) filePath, // 3
strlen (filePath), // 4
false // 5
);
AudioFileCreateWithURL ( // 6
audioFileURL, // 7
fileType, // 8
&aqData.mDataFormat, // 9
kAudioFileFlags_EraseFile, // 10
&aqData.mAudioFile // 11
);
以下是这段代码的工作原理:
- 在
CFURL.h
头文件中声明的CFURLCreateFromFileSystemRepresentation
函数创建一个表示要记录的文件的CFURL对象。
- 在
- 使用
NULL
(或kCFAllocatorDefault
)来使用当前的默认内存分配器。
- 使用
- 您要转换为CFURL对象的文件系统路径。在生产代码中,通常会从用户获取filePath的值。
- 文件系统路径中的字节数。
- 值为false表示代表文件的
filePath
,而不是目录directory
。
- 值为false表示代表文件的
-
AudioFile.h
头文件中的AudioFileCreateWithURL
函数创建一个新的音频文件或初始化现有的文件。
-
- 用于创建新音频文件或者在现有文件的情况下进行初始化的URL,该URL是从步骤1中的
CFURLCreateFromFileSystemRepresentation
派生的。
- 用于创建新音频文件或者在现有文件的情况下进行初始化的URL,该URL是从步骤1中的
- 新文件的文件类型。在本章的示例代码中,这是以前通过
kAudioFileAIFFType
文件类型常量设置为AIFF
的。请参阅 Set Up an Audio Format for Recording。
- 新文件的文件类型。在本章的示例代码中,这是以前通过
- 将被记录到文件中的音频的数据格式,指定为
AudioStreamBasicDescription
结构。在本章的示例代码中,这也是在 Set Up an Audio Format for Recording中设置的。
- 将被记录到文件中的音频的数据格式,指定为
- 在文件已经存在的情况下擦除文件。
- 在输出上,表示要录制的音频文件的音频文件对象(类型
AudioFileID
)。
- 在输出上,表示要录制的音频文件的音频文件对象(类型
Set an Audio Queue Buffer Size - 设置音频队列的缓冲大小
在准备录制时使用的一组音频队列缓冲区之前,请使用您之前编写的DeriveBufferSize
函数(请参阅 Write a Function to Derive Recording Audio Queue Buffer Size)。 您将此大小分配给正在使用的录音音频队列。 Listing 2-12
说明了这一点。
// Listing 2-12 Setting an audio queue buffer size
DeriveBufferSize ( // 1
aqData.mQueue, // 2
aqData.mDataFormat, // 3
0.5, // 4
&aqData.bufferByteSize // 5
);
以下是这段代码的工作原理:
- 在Write a Function to Derive Recording Audio Queue Buffer Size中描述的
DeriveBufferSize
函数设置适当的音频队列缓冲区大小。
- 在Write a Function to Derive Recording Audio Queue Buffer Size中描述的
- 您正在设置缓冲区大小的音频队列。
- 您正在录制的文件的音频数据格式。 请参阅Set Up an Audio Format for Recording。
- 每个音频队列缓冲区应该容纳的音频的秒数。 半秒钟,如这里设置,通常是一个不错的选择。
- 输出时,每个音频队列缓冲区的大小(以字节为单位)。 该值放置在音频队列的自定义结构中。
Prepare a Set of Audio Queue Buffers - 准备一组音频队列缓冲
现在您可以询问您创建的音频队列(在Create a Recording Audio Queue中)以准备一组音频队列缓冲区。 Listing 2-13
演示了如何做到这一点。
Listing 2-13 Preparing a set of audio queue buffers
for (int i = 0; i < kNumberBuffers; ++i) { // 1
AudioQueueAllocateBuffer ( // 2
aqData.mQueue, // 3
aqData.bufferByteSize, // 4
&aqData.mBuffers[i] // 5
);
AudioQueueEnqueueBuffer ( // 6
aqData.mQueue, // 7
aqData.mBuffers[i], // 8
0, // 9
NULL // 10
);
}
以下是这段代码的工作原理:
- 迭代分配和排队每个音频队列缓冲区。
-
AudioQueueAllocateBuffer
函数为音频队列分配一个音频队列缓冲区。
-
- 执行分配并拥有缓冲区的音频队列。
- 新分配的音频队列缓冲区的大小(以字节为单位)。 请参阅Write a Function to Derive Recording Audio Queue Buffer Size。
- 输出时,新分配的音频队列缓冲区。 指向缓冲区的指针位于您正在使用音频队列的自定义结构中。
-
AudioQueueEnqueueBuffer
函数将音频队列缓冲区添加到缓冲区队列的末尾。
-
- 将缓冲添加到缓冲队列的音频队列。
- 您排队的音频队列缓冲区。
- 入队用于记录的缓冲区时,此参数未使用。
- 入队用于记录的缓冲区时,此参数未使用。
Record Audio - 录制音频
所有前面的代码都导致了非常简单的记录过程,如Listing 2-14
所示。
// Listing 2-14 Recording audio
aqData.mCurrentPacket = 0; // 1
aqData.mIsRunning = true; // 2
AudioQueueStart ( // 3
aqData.mQueue, // 4
NULL // 5
);
// Wait, on user interface thread, until user stops the recording
AudioQueueStop ( // 6
aqData.mQueue, // 7
true // 8
);
aqData.mIsRunning = false; // 9
以下是这段代码的工作原理:
- 将数据包索引初始化为0,开始在音频文件的开头进行录制。
- 在自定义结构中设置一个标志来指示音频队列正在运行。 记录音频队列回调callback使用此标志。
-
AudioQueueStart
函数在其自己的线程上启动音频队列。
-
- 音频队列开始。
- 使用
NULL
来指示音频队列应该立即开始记录。
- 使用
-
AudioQueueStop
函数停止并重置录音音频队列。
-
- 音频队列停止。
- 使用
true
来使用同步停止。 有关同步和异步停止的说明,请参阅Audio Queue Control and State。
- 使用
- 在自定义结构中设置一个标志来指示音频队列没有运行。
Clean Up After Recording - 录制后的清除工作
当您完成录制时,请销毁音频队列并关闭音频文件。 Listing 2-15
说明了这些步骤。
// Listing 2-15 Cleaning up after recording
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
以下是这段代码的工作原理:
-
AudioQueueDispose
函数销毁音频队列及其所有资源,包括其缓冲区。
-
- 您想要销毁的音频队列。
- 使用true来同步销毁音频队列(即立即)。
- 关闭用于录制的音频文件。
AudioFileClose
函数在AudioFile.h
头文件中声明。
- 关闭用于录制的音频文件。
后记
未完,待续~~~