Audio Queue Services是官方推荐的方式以一种直接的,低开销的方式在iOS与Mac OS X中完成录制与播放的操做.不像上层的API,它能够经过回调拿到音频帧数据,以完成更加精细的操做.html
比上层API而言,能够直接获取每一帧音频数据,所以能够对音频帧作一些须要的处理. 可是没法对声音作一些更加精细的处理,如回声消除,混音,降噪等等,若是须要作更底层的操做,须要使用Audio Unit.数组
Audio Queue Service是Core Audio的Audio Toolbox框架中的基于C语言的一套接口.缓存
Audio Queue Services是一套高级的API. 它不只能够在无需了解硬件的基础上使程序与音频硬件(麦克风,扬声器等)之间完成交互,也在无需了解编解码器的原理状况下让咱们使用复杂的编解码器.bash
同时,Audio Queue Services还提供了更加精细的定时控制以支持预约的播放与同步任务.可使用它同步多个音频播放队列或者音视频间进行同步.cookie
支持如下格式网络
注意: Audio Queue Services是一套纯C的接口,因此基础的C,C++须要有必定了解.数据结构
在iOS, Mac OS X中audio queue是一个软件层面的对象,能够用来作录制与播放操做.使用AudioQueueRef
表明其数据结构.架构
做用app
若是要使用audio queue的录制功能,经过AudioQueueNewInput
建立录音队列.框架
录制使用的audio queue的输入端一般是当前设备链接的音频设备,如内置的麦克风,或外置的带麦克风功能的输入设备.输出端是咱们定义的回调函数.若是将音频数据录制成文件,能够在回调函数中将从audio queue中取出的音频数据写入文件.固然录制的音频数据也能够直接送给当前App以实现边录制边播放的功能.
每一个audio queue,不论是用于录制或播放,都至少有一个或多个音频数据.全部的音频数据被放在一个被称为音频队列buffer特殊的数据结构中,能够理解成队列中的结点.如上图所示,指定数量的buffer按顺序依次被放入音频队列中,它们最终也将在回调函数中按顺序取出.
若是要使用audio queue的播放功能,经过AudioQueueNewOutput
建立播放队列对象.
播放使用的音频队列,回调函数在输入端.该回调函数将从本地或其余音频数据源获取到的数据交给音频队列中.当没有数据装入播放回调函数也会告诉音频队列中止播放.
用于播放的音频队列的输出端则链接着音频输出硬件,如扬声器或外接的具备扬声器功能的音频设备(如:耳机,音响等).
AudioQueueBuffer
用于存放音频队列数据.
typedef struct AudioQueueBuffer {
const UInt32 mAudioDataBytesCapacity;
void *const mAudioData;
UInt32 mAudioDataByteSize;
void *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;
复制代码
mAudioData
: 当前取出的队列中存放的即时的音频数据指针,它指向真正存放音频数据的内存地址.mAudioDataBytesCapacity
: 当前音频数据最大存储空间mAudioDataByteSize
: 当前存储的音频数据实际的大小mUserData
: 开发者能够存放一些自定义的数据音频队列可使用任意数量的音频数据结点,但通常建议用3个便可.由于若是太少则存取过于频繁,太多则增长应用程序内存消耗,正常状况下两个便可,咱们可使用第三个当有延迟状况出现时做为补偿数据.
由于Audio Queue的纯C函数,内存须要咱们手动管理.
AudioQueueAllocateBuffer
分配内存AudioQueueDispose
回收内存经过内存管理,可使录制播放更加稳定,同时优化App资源使用.
audio queue: 音频队列, 即Audio Queue Services的名字
audio queue buffer : 音频队列中存放一个或多个结点数据
作录制操做时,一个audio queue buffer将被从输入设备(如:麦克风)采集的音频数据填充.音频队列中剩余的buffer按顺序排列在当前填充数据的buffer以后,依次等待被填充数据.在输出端,回调函数将按照指定时间间隔依次接收音频队列中按顺序排列好的音频数据.工做原理以下图:
图一: 录制开始,音频队列中填充须要的音频数据.
图二: 第一个buffer被填充,对调函数取出buffer 1并将其写入文件,同时buffer2也被填充完数据.
图三: 在第4步,回调函数将用完的buffer 1从新放回音频队列,随后第五步回调函数再次取出音频数据buffer2,最终将其写入文件然后从新放回音频队列此后循环往复直到录制中止.
作播放操做时,一个audio queue buffer须要交给输出设备(如:扬声器).剩余的音频数据也将按顺序排列在当前取出播放的音频数据以后,等待播放.回调函数将按顺序取出音频队列中的数据交给扬声器,随后将用完的audio queue buffer从新放入音频队列.
图1: 应用程序启动音频播放队列,每调用依次回调函数填充一个audio queue buffers,填充完后将其放入音频队列. 当应用程序调用AudioQueueStart
当即开始播放.
图2: 音频队列输出第一个音频数据
图3: 用完的audio queue buffer从新放入音频队列.一旦播放了第一个音频数据,音频队列会进入一个循环稳定的状态,即开始播放下一个buffer2(第4步)而后调用回调函数准备填充数据(第5步),最后(第6步)buffer1从新被填充并装入音频队列依次循环直到音频队列中止.
Audio queue buffers始终按照入队顺序进行播放.然而可使用AudioQueueEnqueueBufferWithParameters
函数作一些额外控制
a. 设置缓冲区精确的播放时间,用于同步
b. 能够裁剪开始或结尾的audio queue buffer,这使咱们能够作到开始或结尾的静音效果.
c. 增长播放的声音
后文播放章节中将具体介绍.
不管录制仍是播放,一旦注册好回调函数,它将频繁的被调用.调用时间取决于咱们的设置.回调函数的一个重要职责是将用完的数据从新交给音频队列.使用AudioQueueEnqueueBuffer
入队.
AudioQueueInputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription *inPacketDescs
);
复制代码
当输入端采集到音频数据时就会触发回调,能够从回调函数中取出装有音频数据的audio queue buffer.
AudioFileWritePackets
函数.CBR格式不使用此参数.AudioFileWritePackets
函数AudioQueueOutputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer
);
复制代码
在回调函数中将读取音频数据以用来播放
若是应用程序正在播放VBR格式数据,这个回调函数须要经过
AudioFileReadPackets
获取音频数据包信息.而后,回调将数据包信息放入自定义数据结构中,以使其可用于播放音频队列。
Audio Queue Services使音频编解码器用于转换音频数据格式.你的录制或播放可使用编解码器支持的任意格式.
每一个audio queue有一个本身的音频数据格式,被封装在AudioStreamBasicDescription
中,经过mFormatID
能够指定音频数据格式,audio queue会自动选择适当编解码器对其压缩.开发者能够指定采样率,声道数等等参数自定义音频数据.
如上图,应用程序告诉音频队列使用指定格式开始录制,音频队列在获取到原生的PCM数据后使用编码器将其转换为AAC类型数据,而后音频队列通知回调函数,将转换好的数据放入audio queue buffer中传给回调函数.最后,回调函数拿到转换好的AAC数据进行使用.
如上图,应用程序告诉音频队列播放指定的格式(AAC)的文件,音频队列调用回调函数从音频文件中读取音频数据,回调函数将原始格式的数据传给音频队列.最后,音频队列使用合适的解码器将音频数据(PCM)交给扬声器.
音频队列能够利用任何编解码器不管是系统自带的仍是第三方安装的(仅Mac OS)
音频队列在建立与销毁间的活动范围称为它的声明周期.
AudioQueueStart
前调用它确保当有可用的音频数据时可以当即播放.AudioQueueStart
恢复.AudioQueueStop
能够选择以同步或异步的方式中止.
音频队列有一个能够调节的设置称为参数,每一个参数都有一个枚举常量做为其键,一个浮点型做为其值,该值仅用于播放.
如下有两种方式设置参数
AudioQueueSetParameter
:当即改变AudioQueueEnqueueBufferWithParameters
,在入队时进行设置,播放时,此类更改将生效。使用
kAudioQueueParam_Volume
能够调节播放音量(0.0~1.0)
使用Audio Queue Services进行录制,输出端能够是一个文件,网络协议传输,拷贝给一个对象等等.这里仅介绍输出到文件.
流程
第一步是自定义一个结构体管理音频格式及状态信息.
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
};
复制代码
static void HandleInputBuffer (
void *aqData, // 1
AudioQueueRef inAQ, // 2
AudioQueueBufferRef inBuffer, // 3
const AudioTimeStamp *inStartTime, // 4
UInt32 inNumPackets, // 5
const AudioStreamPacketDescription *inPacketDesc // 6
)
复制代码
AudioFileWritePackets
函数.CBR格式不使用此参数(值为0).AudioFileWritePackets
函数使用AudioFileWritePackets
将数据写入音频文件.
AudioFileWritePackets ( // 1
pAqData->mAudioFile, // 2
false, // 3
inBuffer->mAudioDataByteSize, // 4
inPacketDesc, // 5
pAqData->mCurrentPacket, // 6
&inNumPackets, // 7
inBuffer->mAudioData // 8
);
复制代码
inPacketDesc
参数.当音频数据在回调函数中用完后,须要从新放回音频队列以便存储新的音频数据
AudioQueueEnqueueBuffer ( // 1
pAqData->mQueue, // 2
inBuffer, // 3
0, // 4
NULL // 5
);
复制代码
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
);
}
复制代码
inNumPackets
参数获取.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
}
复制代码
对于一些压缩音频数据格式,如AAC,MPEG 4 AAC等,必须包含音频元数据.包含该元数据信息的数据结构称为magic cookies.当你录制压缩音频数据格式的音频文件时,必须从audio queue中获取元数据并将其设置给音频文件.
注意: 咱们在录制前与中止录制后两个时间点都设置一次magin cookie,由于有的编码器须要在中止录制后更新magin cookie.
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
}
复制代码
主要关注如下参数
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;
复制代码
AudioQueueNewInput ( // 1
&aqData.mDataFormat, // 2
HandleInputBuffer, // 3
&aqData, // 4
NULL, // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
复制代码
当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
);
复制代码
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
类型的对象表明录制文件路径使用2.6.章节中的函数设置音频队列数据的大小以便后续使用.
DeriveBufferSize ( // 1
aqData.mQueue, // 2
aqData.mDataFormat, // 3
0.5, // 4
&aqData.bufferByteSize // 5
);
复制代码
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
);
}
复制代码
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
复制代码
录制完成后,回收音频队列数据,关闭音频文件.
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
复制代码
使用 Audio Queue Services播放音频时,源数据能够是本地文件, 内存中的对象或者其余音频存储方式.本章中仅介绍经过本地文件播放.
static const int kNumberBuffers = 3; // 1
struct AQPlayerState {
AudioStreamBasicDescription mDataFormat; // 2
AudioQueueRef mQueue; // 3
AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4
AudioFileID mAudioFile; // 5
UInt32 bufferByteSize; // 6
SInt64 mCurrentPacket; // 7
UInt32 mNumPacketsToRead; // 8
AudioStreamPacketDescription *mPacketDescs; // 9
bool mIsRunning; // 10
};
复制代码
此结构体中的数据基本与录制时相同.
做用
static void HandleOutputBuffer (
void *aqData, // 1
AudioQueueRef inAQ, // 2
AudioQueueBufferRef inBuffer // 3
)
复制代码
AudioFileReadPackets ( // 1
pAqData->mAudioFile, // 2
false, // 3
&numBytesReadFromFile, // 4
pAqData->mPacketDescs, // 5
pAqData->mCurrentPacket, // 6
&numPackets, // 7
inBuffer->mAudioData // 8
);
复制代码
读取完音频数据后,执行入队操做.
AudioQueueEnqueueBuffer ( // 1
pAqData->mQueue, // 2
inBuffer, // 3
(pAqData->mPacketDescs ? numPackets : 0), // 4
pAqData->mPacketDescs // 5
);
复制代码
若是检查到当前音频文件读取完毕,应该中止音频队列.
if (numPackets == 0) { // 1
AudioQueueStop ( // 2
pAqData->mQueue, // 3
false // 4
);
pAqData->mIsRunning = false; // 5
}
复制代码
AudioFileReadPackets
检查数据包是否为0static void HandleOutputBuffer (
void *aqData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer
) {
AQPlayerState *pAqData = (AQPlayerState *) aqData; // 1
if (pAqData->mIsRunning == 0) return; // 2
UInt32 numBytesReadFromFile; // 3
UInt32 numPackets = pAqData->mNumPacketsToRead; // 4
AudioFileReadPackets (
pAqData->mAudioFile,
false,
&numBytesReadFromFile,
pAqData->mPacketDescs,
pAqData->mCurrentPacket,
&numPackets,
inBuffer->mAudioData
);
if (numPackets > 0) { // 5
inBuffer->mAudioDataByteSize = numBytesReadFromFile; // 6
AudioQueueEnqueueBuffer (
pAqData->mQueue,
inBuffer,
(pAqData->mPacketDescs ? numPackets : 0),
pAqData->mPacketDescs
);
pAqData->mCurrentPacket += numPackets; // 7
} else {
AudioQueueStop (
pAqData->mQueue,
false
);
pAqData->mIsRunning = false;
}
}
复制代码
咱们须要指定一个音频队列buffer的大小.根据计算出来的大小为音频队列数据分配内存.
AudioFileReadPackets
获取读取到的包数void DeriveBufferSize (
AudioStreamBasicDescription &ASBDesc, // 1
UInt32 maxPacketSize, // 2
Float64 seconds, // 3
UInt32 *outBufferSize, // 4
UInt32 *outNumPacketsToRead // 5
) {
static const int maxBufferSize = 0x50000; // 6
static const int minBufferSize = 0x4000; // 7
if (ASBDesc.mFramesPerPacket != 0) { // 8
Float64 numPacketsForTime =
ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;
*outBufferSize = numPacketsForTime * maxPacketSize;
} else { // 9
*outBufferSize =
maxBufferSize > maxPacketSize ?
maxBufferSize : maxPacketSize;
}
if ( // 10
*outBufferSize > maxBufferSize &&
*outBufferSize > maxPacketSize
)
*outBufferSize = maxBufferSize;
else { // 11
if (*outBufferSize < minBufferSize)
*outBufferSize = minBufferSize;
}
*outNumPacketsToRead = *outBufferSize / maxPacketSize; // 12
}
复制代码
AudioFileGetProperty
查询kAudioFilePropertyPacketSizeUpperBound
属性可得CFURL
对象表示音频文件路径CFURL
对象表示音频文件路径CFURLRef audioFileURL =
CFURLCreateFromFileSystemRepresentation ( // 1
NULL, // 2
(const UInt8 *) filePath, // 3
strlen (filePath), // 4
false // 5
);
复制代码
CFURL
类型的对象表明录制文件路径AQPlayerState aqData; // 1
OSStatus result =
AudioFileOpenURL ( // 2
audioFileURL, // 3
fsRdPerm, // 4
0, // 5
&aqData.mAudioFile // 6
);
CFRelease (audioFileURL); // 7
复制代码
UInt32 dataFormatSize = sizeof (aqData.mDataFormat); // 1
AudioFileGetProperty ( // 2
aqData.mAudioFile, // 3
kAudioFilePropertyDataFormat, // 4
&dataFormatSize, // 5
&aqData.mDataFormat // 6
);
复制代码
AudioQueueNewOutput ( // 1
&aqData.mDataFormat, // 2
HandleOutputBuffer, // 3
&aqData, // 4
CFRunLoopGetCurrent (), // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
复制代码
UInt32 maxPacketSize;
UInt32 propertySize = sizeof (maxPacketSize);
AudioFileGetProperty ( // 1
aqData.mAudioFile, // 2
kAudioFilePropertyPacketSizeUpperBound, // 3
&propertySize, // 4
&maxPacketSize // 5
);
DeriveBufferSize ( // 6
aqData.mDataFormat, // 7
maxPacketSize, // 8
0.5, // 9
&aqData.bufferByteSize, // 10
&aqData.mNumPacketsToRead // 11
);
复制代码
bool isFormatVBR = ( // 1
aqData.mDataFormat.mBytesPerPacket == 0 ||
aqData.mDataFormat.mFramesPerPacket == 0
);
if (isFormatVBR) { // 2
aqData.mPacketDescs =
(AudioStreamPacketDescription*) malloc (
aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)
);
} else { // 3
aqData.mPacketDescs = NULL;
}
复制代码
对于压缩的音频数据格式(AAC...),咱们在播放前必须为音频队列设置magic cookies,即元数据信息.
UInt32 cookieSize = sizeof (UInt32); // 1
bool couldNotGetProperty = // 2
AudioFileGetPropertyInfo ( // 3
aqData.mAudioFile, // 4
kAudioFilePropertyMagicCookieData, // 5
&cookieSize, // 6
NULL // 7
);
if (!couldNotGetProperty && cookieSize) { // 8
char* magicCookie =
(char *) malloc (cookieSize);
AudioFileGetProperty ( // 9
aqData.mAudioFile, // 10
kAudioFilePropertyMagicCookieData, // 11
&cookieSize, // 12
magicCookie // 13
);
AudioQueueSetProperty ( // 14
aqData.mQueue, // 15
kAudioQueueProperty_MagicCookie, // 16
magicCookie, // 17
cookieSize // 18
);
free (magicCookie); // 19
}
复制代码
aqData.mCurrentPacket = 0; // 1
for (int i = 0; i < kNumberBuffers; ++i) { // 2
AudioQueueAllocateBuffer ( // 3
aqData.mQueue, // 4
aqData.bufferByteSize, // 5
&aqData.mBuffers[i] // 6
);
HandleOutputBuffer ( // 7
&aqData, // 8
aqData.mQueue, // 9
aqData.mBuffers[i] // 10
);
}
复制代码
开始播放前,能够设置音量(0~1)
Float32 gain = 1.0; // 1
// Optionally, allow user to override gain setting here
AudioQueueSetParameter ( // 2
aqData.mQueue, // 3
kAudioQueueParam_Volume, // 4
gain // 5
);
复制代码
aqData.mIsRunning = true; // 1
AudioQueueStart ( // 2
aqData.mQueue, // 3
NULL // 4
);
do { // 5
CFRunLoopRunInMode ( // 6
kCFRunLoopDefaultMode, // 7
0.25, // 8
false // 9
);
} while (aqData.mIsRunning);
CFRunLoopRunInMode ( // 10
kCFRunLoopDefaultMode,
1,
false
);
复制代码
播放完成后应该回收音频队列,关闭音频文件,释放全部相关资源
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
free (aqData.mPacketDescs); // 5
复制代码