简介
Audio �Queue 服务提供了一种更低开销,更简单直接的方式,用于iOS和MacOSX中录制和播放音频。这是一种相对会更好用的更值得推荐的音频播放、录制方式。
Audio Queue Services 可以让你录制和播放以下的格式:
1、PCM 格式;
2、苹果平台所支持的所有音频格式;
这里大概包括
kAudioFormatLinearPCM = 'lpcm',
kAudioFormatAC3 = 'ac-3',
kAudioFormat60958AC3 = 'cac3',
kAudioFormatAppleIMA4 = 'ima4',
kAudioFormatMPEG4AAC = 'aac ',
kAudioFormatMPEG4CELP = 'celp',
kAudioFormatMPEG4HVXC = 'hvxc',
kAudioFormatMPEG4TwinVQ = 'twvq',
kAudioFormatMACE3 = 'MAC3',
kAudioFormatMACE6 = 'MAC6',
kAudioFormatULaw = 'ulaw',
kAudioFormatALaw = 'alaw',
kAudioFormatQDesign = 'QDMC',
kAudioFormatQDesign2 = 'QDM2',
kAudioFormatQUALCOMM = 'Qclp',
kAudioFormatMPEGLayer1 = '.mp1',
kAudioFormatMPEGLayer2 = '.mp2',
kAudioFormatMPEGLayer3 = '.mp3',
kAudioFormatTimeCode = 'time',
kAudioFormatMIDIStream = 'midi',
kAudioFormatParameterValueStream = 'apvs',
kAudioFormatAppleLossless = 'alac',
kAudioFormatMPEG4AAC_HE = 'aach',
kAudioFormatMPEG4AAC_LD = 'aacl',
kAudioFormatMPEG4AAC_ELD = 'aace',
kAudioFormatMPEG4AAC_ELD_SBR = 'aacf',
kAudioFormatMPEG4AAC_ELD_V2 = 'aacg',
kAudioFormatMPEG4AAC_HE_V2 = 'aacp',
kAudioFormatMPEG4AAC_Spatial = 'aacs',
kAudioFormatAMR = 'samr',
kAudioFormatAMR_WB = 'sawb',
kAudioFormatAudible = 'AUDB',
kAudioFormatiLBC = 'ilbc',
kAudioFormatDVIIntelIMA = 0x6D730011,
kAudioFormatMicrosoftGSM = 0x6D730031,
kAudioFormatAES3 = 'aes3',
kAudioFormatEnhancedAC3 = 'ec-3'
Audio Queue Services是比较高级的框架,它可以让你的程序直接使用硬件录制或播放音频,不需要更多的了解硬件接口,也可以让你在使用编解码器的过程中,无须再去理解编解码器的工作原理。
Audio Queue 会支持一些比较高级的特性,它提供了精确的的时间控制,用来支持定时播放和同步。这就可以让多组的音频队列同时播放,并实现音视频同步。(这个在做游戏声效的时候非常好用)
Note: Audio Queue Services 是基于M�ac OSX 的Sound Manager 优化出来的,它增加了一些新的特性,比方说:同步。因为Sound Manager 已经在Mac OSXv10.5 被弃用了,而且他不支持64位应用程序,所以苹果才有意的推出Audio Queue Services 给开发人员使用以及为SoundManager做替换。
Audio Queue Services 是一个纯C的接口,你可以在Cocoa 应用以及在Mac OSX上一样的使用,为了帮助Audio Queue Service类的使用,可通过Core Audio 框架中的C++类来简化实现,但是并非一定得使用这两个东西的。
Audio Queue Services包括了以下的内容:
About Audio Queues 描述了音频队列的功能,架构以及内部的工作原理。
Recording Audio 介绍如何进行录音。
Playing Audio 介绍了如何进行播放。
关于Audio Queues
这里会讲述关于音频队列的功能、架构以及内部工作过程。接下来会引入音频队列、音频队列缓冲以及音频队列的回调方法来进行录音或播放音频。你需要了解音频队列的状态以及相关的设置参数。
Audio queue 是iOS和Mac OS X通用的一个录音和播放音频的软件对象,它由AudioQueue.h
头文件中声明的AudioQueueRef
数据类型表示。
一个AudioQueue会执行以下的内容:
1、连接音频采集硬件模块
2.、管理音频采集所需的内存
3、根据需求采用压缩音频格式以及选择相关编解码器
4、录音和音频播放的中转站
你可以通过使用Core Audio 相关接口,以及少量的代码来创建相关的Audio Queue 来进行录音或者音频回放。
音频队列的架构
所有的音频队列都具有相同的通用架构,主要包括以下内容:
1、一组音频队列缓冲区audio queue buffers
,作为音频数据的临时存储;
2、一个音频队列buffer queue
,用于控制音频队列缓冲的数据;
3、一个音频队列回调函数,可用于写入、发送等操作。
这个架构决定了音频队列是用于录制还是播放,会体现音频队列的输入、输出以及回调函数的差异。
用于录音的音频队列
使用AudioQueueNewInput可创建一个用于录音的音频队列,其工作过程如下图所示:
录音音频队列的输出端是通过回调函数实现的,当回调从音频队列接收到新的音频数据时,就可以从缓冲区读取音频数据写到磁盘中,或者发送出去。
总的来说就是通过麦克风采集音频数据,通过队列的形式,回调给上层使用。
用于播放的音频队列
使用AudioQueueNewOutput可创建一个用于播放的音频队列,其工作过程如下图所示:
在播放队列中,回调函数是作为一个输入端,它从磁盘或者其他地方获取到音频数据,然后将其闯入到音频队列,通过队列缓冲播放。
音频队列缓冲区
Audio queue buffer 是AudioQueue.h
类里面声明的AudioQueueBuffer类型的结构体。
typedef struct AudioQueueBuffer {
const UInt32 mAudioDataBytesCapacity;
void *const mAudioData;
UInt32 mAudioDataByteSize;
void *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;
其中mAudioData
属性是指向缓冲区本身(一个正在播放或者录音的音频数据的临时缓存内存块)。其他字段中的信息是用于Audio Queue管理Buffer的。
开发者可以随意定制音频队列的缓冲区数量,一般我们使用三个就满足了。这样就可以让一个用来读,一个用来写入,另一个就可以用来缓存新的音频数据。
音频队列对其缓冲区的内存管理
1、当调用AudioQueueAllocateBuffer函数时,音频队列会分配一个缓冲区。
2、当通过调用AudioQueueDispose函数释放音频队列时,队列释放其缓冲区。
这里值得一提的是,无论录音操作还是音频播放操作,音频队列的入队和出队都是在回调函数实现的。
设置音频队列缓冲区大小
DeriveBufferSize (
aqData.mQueue, // 当前设置缓冲区大小的音频队列
aqData.mDataFormat, // 当前录制的文件的音频数据格式。
0.5, // 每个音频队列缓冲区应该保持的音频的秒数
&aqData.bufferByteSize // 在输出时,每个音频队列缓冲区的大小(以字节为单位)。
);
录音过程
1、开始录音后,音频数据从设备麦克风采集音频数据然后填入音频队列缓冲器中;
2、当第一个缓冲区满了,音频队列就回调给上层;
3、把回调的音频数据写入磁盘中;
4、回调完成后的缓冲区会继续被音频队列重新使用;
5、音频队列传递完整缓冲区然后回调并填充另一个缓冲区;
整体过程如下图所示:
录音音频队列的回调声明
static void HandleInputBuffer (
void *aqData, // 1
AudioQueueRef inAQ, // 2
AudioQueueBufferRef inBuffer, // 3
const AudioTimeStamp *inStartTime, // 4
UInt32 inNumPackets, // 5
const AudioStreamPacketDescription *inPacketDesc // 6
)
1、通常aqData时报喊音频队列的状态数据的自定义数据;
2、拥有当前回调的音频队列。
3、音频队列缓冲区包含要录制的传入音频数据。
4、音频队列缓冲区中第一个采样的采样时间(简单记录是不需要的)。
5、AudioStreamPacketDescription 参数中的数据包数,当值是0时表示CBR数据。
6、编码器为缓冲区中的数据包生成对应的压缩音频数据格式描述内容。
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 如果音频队列缓冲区包含CBR数据,则计算缓冲区中的数据包数。 该数字等于缓冲区中数据的总字节数除以(常数)每个数据包的字节数。 对于VBR数据,音频队列在调用回调时提供缓冲区中的数据包数。
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
);
}
设置用于录制的音频格式
AQRecorderState aqData; // 1创建AQRecorderState自定义结构的实例
aqData.mDataFormat.mFormatID = kAudioFormatLinearPCM; // 2 将音频数据格式类型定义为线性PCM
aqData.mDataFormat.mSampleRate = 44100.0; // 3 将采样率定义为44.1 kHz。
aqData.mDataFormat.mChannelsPerFrame = 2; // 4将通道数定义为2。
aqData.mDataFormat.mBitsPerChannel = 16; // 5 将每个通道的位深度定义为16。
aqData.mDataFormat.mBytesPerPacket = // 6将每个数据包的字节数和每帧的字节数定义为4(即每个通道2个byte)
aqData.mDataFormat.mBytesPerFrame =
aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket = 1; // 7 将每个数据包的帧数定义为1
AudioFileTypeID fileType = kAudioFileAIFFType; // 8 将文件类型定义为AIFF,请参阅AudioFile.h头文件中的音频文件类型枚举,以获取可用文件类型的完整列表。
aqData.mDataFormat.mFormatFlags = // 9 设置指定文件类型所需的格式标志。
kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsSignedInteger
| kLinearPCMFormatFlagIsPacked;
开始录音、停止录音
aqData.mCurrentPacket = 0; // 1
aqData.mIsRunning = true; // 2
//开始录音
AudioQueueStart ( // 3
aqData.mQueue, // 4
NULL // 5
);
// 停止录音
AudioQueueStop ( // 6
aqData.mQueue, // 7
true // 8
);
aqData.mIsRunning = false;
后续会继续更新音频播放相关的内容以及音频转码内容