iOS,使用AudioQueue进行音频开发

花了3天时间看AudioStream()源码,总算对AudioQueue实现音频流播放有了点了解.趁热打铁,写下这篇日记,好记性不如烂笔头!


首先对AudioStream整体思路做一个简单分析:

1. 第一步是用CFNetwork进行网络请求设置,通过CFReadStreamRef读取流,分段读取数据;

2. 在请求到数据之后, 通过CFReadStreamClientCallBack进行函数回调, 在回调中进行音频数据解析, 解析数据完成之后,将开始播放过程;

3. 创建一个AudioQueue,和一个Buffer数组;

4. 使用AudioQueueAllocateBuffer创建若干个AudioQueueBuffer实例,存放到创建的Buffer数组中;

5. 当缓冲到一定数据时, 从Buffer数组中取出一个buffer, memcpy数据后用AudioQueueEnqueueBuffer将buffer插入到AudioQueue中;

6. AudioQueue存在buffer后, 调用AudioQueueStart,开始播放;

7. AudioQueue播放消耗某个buffer后, 通过回调AudioQueueOutputCallback,将在另一个线程中buffer置为未使用状态,以供下次使用.重复步骤5,直到播放结束.


接下来直接上代码:

初始化url,并添加一个打断音频的通知
开始播放流程
暂停操作

判断当前的播放转态, 如果是暂停状态, 则开始播放;如果是初始化的状态, 将状态置为AS_STARTING_FILE_THREAD. 同时创建一个异步线程,将所有的请求,数据解析,读取流操作都放在此线程中.


// 一个容错处理

// 下面三个方法在iOS7之后已经被废弃

// AudioSession的初始化, 前两个参数设置为NULL表示AudioSession运行在主线程,第三个参数是音频被打断的回调函数,第四个参数表示回调函数的附带参数

// 注意点: AudioSessionInitialize会被多次调用, 但是回调函数只能被设置一次,因此必须使用静态方法.当注册成功后, 以后所有的打断都会回调到该静态方法上, 即使下次再调用AudioSessionInitialize并且把另一个静态方法作为参数传入, 当打断到来时还是会回调到第一次设置的方法上。

// 函数原型

AudioSessionInitialize(  CFRunLoopRef  inRunLoop, CFStringRef  inRunLoopMode, AudioSessionInterruptionListener  inInterruptionListener, void *inClientData);

// 函数实现

AudioSessionInitialize (NULL, NULL, ASAudioSessionInterruptionListener, self );

// 设置类别, 第一个参数设置的功能类型,如果只是播放音频可以直接设置kAudioSessionCategory_MediaPlayback,第二个参数是第三个参数的size, 第三个参数是想要实现功能的数据

// 函数原型

AudioSessionSetProperty( AudioSessionPropertyID  inID, UInt32  inDataSize, const void  *inData);

// 函数实现

UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;

AudioSessionSetProperty ( kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory );

// 启动AudioSession

AudioSessionSetActive(true);

// 初始化互斥量(参数一是互斥量, 参数二是互斥锁属性,NULL默认为快速互斥锁)

pthread_mutex_init(&queueBuffersMutex, NULL);

// 初始化条件变量

pthread_cond_init(&queueBufferReadyCondition, NULL);

// 当读取流操作失败, 进行清空操作, 否则进行数据请求操作

// 判断当前线程是否有事件处理并且是否处于正常播放或是处于缓冲中,否则跳出循环

在internalThread线程中保持每0.25秒进行一次轮询

当轮询到当前线程中有处理事件, isRunning返回YES, 否则返回NO;

// 如果用户手动拖拽了进度条, 则将seek到用户拖拽的地方

if (seekWasRequested) {

[self internalSeekToTime:requestedSeekTime];

seekWasRequested = NO;

}

// 是否有缓存并且处于播放状态, 成立就暂停播放, 将状态设置为缓冲状态

if (buffersUsed == 0 && self.state == AS_PLAYING) {

err = AudioQueuePause(audioQueue);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED];

return;

}

self.state = AS_BUFFERING;

}

创建读取流

// 创建HTTP请求

CFHTTPMessageRef message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1);

// 设置请求头, 实现分段加载

if (fileLength > 0 && seekByteOffset > 0) {

CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"),(CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", (long)seekByteOffset, (long)fileLength]);

// 设置数据不连续, 后面会用到

discontinuous = YES;

}

// 创建流请求

stream = CFReadStreamCreateForHTTPRequest(NULL, message);

CFRelease(message);

// 当使用CFReadStreamCreateForHTTPRequest创建读取流时,流的重定向默认是被禁止的。如果请求连接被重定向,会导致一个错误,它的状态码为300~307。如果收到一个重定向错误,需要关闭这个流,然后重新创建一个流,启用重定向并打开流

if (CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) {

[self failWithErrorCode:AS_FILE_STREAM_SET_PROPERTY_FAILED];

return NO;

}

// HTTP代理设置(系统默认)

CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();

CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxySettings);

CFRelease(proxySettings);

// HTTPS代理设置

if([[url scheme] isEqualToString:@"https"]) {

NSDictionary *sslSettings =

[NSDictionary dictionaryWithObjectsAndKeys:

(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel,

[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,

[NSNull null], kCFStreamSSLPeerName,nil];

CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, sslSettings);

}

// 设置状态为加载数据状态

self.state = AS_WAITING_FOR_DATA;

// 打开读取流, 开始读取数据

if (!CFReadStreamOpen(stream)) {

CFRelease(stream);

[self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED];

return NO;

}

// 调用CFReadStreamSetClient(可读流)来登记要接收的流相关的事件

/**

监听回调事件

kCFStreamEventNone(没有事件发生)

kCFStreamEventOpenCompleted(流被成功打开)

kCFStreamEventHasBytesAvailable(有数据可以读取)

kCFStreamEventCanAcceptBytes(流可以接受写入数据(用于写入流))

kCFStreamEventErrorOccurred(在流上有错误发生)

kCFStreamEventEndEncountered(到达了流的结束位置)

在流有数据可以读取, 发生错误,事件结束的情况下调用回调函数

*/

// CFStreamClientContext设置回调对象

CFStreamClientContext context = {0, self, NULL, NULL, NULL};

CFReadStreamSetClient(stream,kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered,ASReadStreamCallBack,&context);

// 添加到当前的RunLoop中

CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  

CFReadStreamSetClient的回调函数

// 如果aStream和以前的stream相等, 则跳过

if (aStream != stream) {

return;

}

// 流上有错误发生

if (eventType == kCFStreamEventErrorOccurred)  {

[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];

}

// 到达了流的结束位置

if (eventType == kCFStreamEventEndEncountered) {

@synchronized(self)  {

// 播放是否完成, 完成则直接返回

if ([self isFinishing])  {

return;

}

}

// 如果有一部分缓冲数据,则将其传递给用于处理的音频队列

if (bytesFilled) {

if (self.state == AS_WAITING_FOR_DATA) {

self.state = AS_FLUSHING_EOF;

}

[self enqueueBuffer];

}

@synchronized(self) {

if (state == AS_WAITING_FOR_DATA) {

[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];

} else if (![self isFinishing]) {

if (audioQueue) {

// 调用后会在播放完Enqueue的所有buffer后重置解码器状态,以防止当前的解码器状态影响到下一段音频的解码(比如切换播放的歌曲时)

err = AudioQueueFlush(audioQueue);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED];

return;

}

self.state = AS_STOPPING;

stopReason = AS_STOPPING_EOF;

err = AudioQueueStop(audioQueue, false);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED];

return;

}

} else {

self.state = AS_STOPPED;

stopReason = AS_STOPPING_EOF;

}

}

}

}

// 有可用数据时

if (eventType == kCFStreamEventHasBytesAvailable) {

// 获取请求头, 从中获取fileLength

if (!httpHeaders) {

CFTypeRef message = CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader);

httpHeaders = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message);

CFRelease(message);

if (seekByteOffset == 0) {

fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue];

}

}

}

// 通过url,获取音频文件后缀名

if (!audioFileStream) {

if (!self.fileExtension) {

self.fileExtension = [[url path] pathExtension];

}

/**

初始化AudioFileStream

第一个参数传入一个上下文对象

第二个参数是歌曲信息解析的回调, 每解析出一首歌曲信息都会回调一次

第三个参数是分离帧的回调, 每解析出一部分帧数据都会回调一次

第四个参数是文件类型的提示, 这个参数来帮助AudioFileStream对文件格式进行解析.这个参数在文件信息不完整(例如信息有缺陷)时尤其有用,它可以给与AudioFileStream一定的提示,帮助其绕过文件中的错误或者缺失从而成功解析文件. 所以在确定文件类型的情况下建议各位还是填上这个参数,如果无法确定可以传入0

第五个参数是返回的AudioFileStream实例对应的AudioFileStreamID,这个ID需要保存起来作为后续一些方法的参数使用

返回值表示是否初始化成功

*/

err = AudioFileStreamOpen(self, ASPropertyListenerProc, ASPacketsProc, fileTypeHint, &audioFileStream);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED];

return;

}

}

UInt8 bytes[kAQDefaultBufSize];

CFIndex length;

@synchronized(self) {

if ([self isFinishing] || !CFReadStreamHasBytesAvailable(stream)) {

return;

}

// 从读取流中获取数据, 返回数据大小

length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize);

if (length == -1) {

[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];

return;

}

if (length == 0) {

return;

}

}

// 判断数据是否是连续的, 是拖拽的时候discontinuous为YES

if (discontinuous) {

/**

AudioFileStream初始化完成之后,进行数据解析

第一个参数是AudioFileStream实例对应的AudioFileStreamID

第二个参数是本次要解析的数据长度

第三个参数是本次要解析的数据

第四个参数表示本次解析的数据和上次解析的数据是否是连续的, 如果是连续的则直接传入0,否则传入kAudioFileStreamParseFlag_Discontinuity

*/

err = AudioFileStreamParseBytes(audioFileStream, (UInt32)length, bytes, kAudioFileStreamParseFlag_Discontinuity);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED];

return;

}

} else {

err = AudioFileStreamParseBytes(audioFileStream, (UInt32)length, bytes, 0);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED];

return;

}

}

}

歌曲信息解析的回调

// 函数原型

// 第一个参数是Open方法中的上下文对象;

// 第二个参数表示AudioFileStream实例对应的AudioFileStreamID

// 第三个参数是此次回调解析的信息ID。表示当前PropertyID对应的信息已经解析完成信息(例如数据格式、音频数据的偏移量等等),使用者可以通过AudioFileStreamGetProperty接口获取PropertyID对应的值或者数据结构;

(*AudioFileStream_PropertyListenerProc)(void * inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, AudioFileStreamPropertyFlags * ioFlags);


解析文件格式信息

// kAudioFileStreamProperty_ReadyToProducePackets一旦回调中出现这个PropertyID就代表解析完成,接下来可以对音频数据进行帧分离了

if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) {

// 设置为YES是为了跳过头信息数据

discontinuous = true;

}

// kAudioFileStreamProperty_DataOffset 表示音频数据在整个音频文件中的offset

if (inPropertyID == kAudioFileStreamProperty_DataOffset) {

// 获取该帧数据在整个音频文件中的偏移量

SInt64 offset;

UInt32 offsetSize = sizeof(offset);

/**

AudioFileStreamGetProperty

第四个参数ioFlags是一个返回参数,表示这个property是否需要被缓存,如果需要赋值kAudioFileStreamPropertyFlag_PropertyIsCached否则不赋值

*/

err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

dataOffset = offset;

if (audioDataByteCount) {

fileLength = dataOffset + audioDataByteCount;

}

}

// 音频文件中音频数据的总量。这个Property的作用一是用来计算音频的总时长,二是可以在seek时用来计算时间对应的字节offset

if (inPropertyID == kAudioFileStreamProperty_AudioDataByteCount) {

UInt32 byteCountSize = sizeof(UInt64);

// 获取音频数据总量

err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

// 计算文件长度, 文件长度 = 音频头信息大小 + 音频数据大小

fileLength = dataOffset + audioDataByteCount;

}

// 表示音频文件结构信息,是一个AudioStreamBasicDescription的结构

if (inPropertyID == kAudioFileStreamProperty_DataFormat) {

// asbd是一个AudioStreamBasicDescription结构体, mSampleRate是音频的采样率, 根据asbd.mSampleRate == 0判断asbd是否已经被初始化, 没有被初始化则进行初始化

if (asbd.mSampleRate == 0) {

UInt32 asbdSize = sizeof(asbd);

err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

}

}

// 作用和kAudioFileStreamProperty_DataFormat是一样的,区别在于用这个PropertyID获取到是一个AudioStreamBasicDescription的数组,这个参数是用来支持AAC SBR这样的包含多个文件类型的音频格式

if (inPropertyID == kAudioFileStreamProperty_FormatList) {

Boolean outWriteable;

UInt32 formatListSize;

err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

AudioFormatListItem *formatList = malloc(formatListSize);

err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);

if (err) {

free(formatList);

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem)) {

AudioStreamBasicDescription pasbd = formatList[i].mASBD;

if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2) {

#if !TARGET_IPHONE_SIMULATOR

asbd = pasbd;

#endif

break;

}

}

free(formatList);

}



分离音频帧数据

/**

第一个参数是本次处理的所有数据

第二个参数是本次处理的数据大小

第三个参数是本次总共处理多少帧数据

第四个参数是个AudioStreamPacketDescription数组, 储存了每一帧数据是从第几个字节开始的,这一帧总共有多少个字节

*/

- (void)handleAudioPackets:(const void *)inInputData numberBytes:(UInt32)inNumberBytes numberPackets:(UInt32)inNumberPackets packetDescriptions:(AudioStreamPacketDescription *)inPacketDescriptions

@synchronized(self) {

// 音频播放完成,直接返回

if ([self isFinishing]) {

return;

}

if (bitRate == 0) {

// m4a和其他一些格式不会去解析音频数据的码率,我们需要在这里设置一个“不能解析的”条件, 对于UInt32的~0 等于 (0x1 << 31) - 1

bitRate = ~0;

}

if (discontinuous) {

discontinuous = false;

}

// 创建音频队列

if (!audioQueue) {

[self createQueue];

}

}

// 判断inPacketDescriptions是否有返回. 主要是为了区分VBR编码和CBR编码的数据(不是太懂, 百度了下, VBR动态码率, CBR静态码率)

if (inPacketDescriptions) {

for (int i = 0; i < inNumberPackets; ++i) {

// 音频帧的偏移量

SInt64 packetOffset = inPacketDescriptions[i].mStartOffset;

// 音频帧的大小

SInt64 packetSize  = inPacketDescriptions[i].mDataByteSize;

// 缓存空间剩余大小

size_t bufSpaceRemaining;

// processedPacketsCount表示已下载音频帧的总个数,processedPacketsSizeTotal表示已下载音频大小(在显示已下载进度条的时候读取此数), 这一步主要是为了计算平均码率

if (processedPacketsCount < BitRateEstimationMaxPackets) {

processedPacketsSizeTotal += packetSize;

processedPacketsCount += 1;

}

@synchronized(self) {

if ([self isFinishing]) {

return;

}

// packetBufferSize是在createQueue方法中进行的赋值, 表示缓存音频帧的最大值

if (packetSize > packetBufferSize) {

[self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL];

}

// 缓存剩余空间

bufSpaceRemaining = packetBufferSize - bytesFilled;

}

// 如果缓冲空间小于当前音频帧的大小, 则将buffer添加到播放队列中, 否则继续进行缓存

if (bufSpaceRemaining < packetSize) {

[self enqueueBuffer];

}

@synchronized(self) {

if ([self isFinishing]) return;

// 在缓冲队列没有为新的音频数据腾出空间,那么就退出

if (bytesFilled + packetSize > packetBufferSize) return;

// 拷贝数据到buffer中

AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];

memcpy((char *)fillBuf->mAudioData + bytesFilled, (const char *)inInputData + packetOffset, packetSize);

// packetDescs中缓存的AudioStreamPacketDescription对象

packetDescs[packetsFilled] = inPacketDescriptions[i];

packetDescs[packetsFilled].mStartOffset = bytesFilled;

// 缓存的数据大小

bytesFilled += packetSize;

// 缓存的音频帧个数

packetsFilled += 1;

}

// 当缓存的音频帧的个数超过限定的最大值时, 将buffer添加到播放队列中

size_t packetsDescsRemaining = kAQMaxPacketDescs - packetsFilled;

if (packetsDescsRemaining == 0) {

[self enqueueBuffer];

}

}

} else {

size_t offset = 0;

while (inNumberBytes) {

// 如果缓存空间剩余大小小于当前处理的数据大小,

size_t bufSpaceRemaining = kAQDefaultBufSize - bytesFilled;

if (bufSpaceRemaining < inNumberBytes) {

[self enqueueBuffer];

}

@synchronized(self) {

if ([self isFinishing]) return;

bufSpaceRemaining = kAQDefaultBufSize - bytesFilled;

size_t copySize;

if (bufSpaceRemaining < inNumberBytes) {

copySize = bufSpaceRemaining;

} else {

copySize = inNumberBytes;

}

if (bytesFilled > packetBufferSize) return;

AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];

memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inInputData + offset), copySize);

bytesFilled += copySize;

packetsFilled = 0;

inNumberBytes -= copySize;

offset += copySize;

}

}

}

创建AudioQueue,进行音频播放

// mSampleRate 采样率,  mFramesPerPacket 每个Packet的帧数量

sampleRate       = asbd.mSampleRate;

packetDuration = asbd.mFramesPerPacket / sampleRate;

/**

创建AudioQueue

第一个参数表示需要播放的音频数据格式类型,是一个AudioStreamBasicDescription对象,是使用AudioFileStream或者AudioFile解析出来的数据格式信息

第二个参数AudioQueueOutputCallback是某块Buffer被使用之后的回调

第三个参数为上下文对象

第四个参数inCallbackRunLoop为AudioQueueOutputCallback需要在的哪个RunLoop上被回调,如果传入NULL的话就会再AudioQueue的内部RunLoop中被回调,所以一般传NULL就可以了

第五个参数inCallbackRunLoopMode为RunLoop模式,如果传入NULL就相当于kCFRunLoopCommonModes,也传NULL就可以了

第六个参数inFlags是保留字段,目前没作用,传0

第七个参数,返回生成的AudioQueue实例

*/

err = AudioQueueNewOutput(&asbd, ASAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED];

return;

}

// kAudioQueueProperty_IsRunning监听当前AudioQueue是否在运行

err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, ASAudioQueueIsRunningCallback, self);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_ADD_LISTENER_FAILED];

return;

}

// 设置音频帧的最大值

UInt32 sizeOfUInt32 = sizeof(UInt32);

err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize);

if (err || packetBufferSize == 0) {

err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize);

if (err || packetBufferSize == 0) {

packetBufferSize = kAQDefaultBufSize;

}

}

/**

创建自定的Buffer数组

*/

for (unsigned int i = 0; i < kNumAQBufs; ++i) {

/**

第一个参数传入AudioQueue实例

第二个参数传入Buffer大小

第三个参数传出Buffer实例

*/

err = AudioQueueAllocateBuffer(audioQueue, packetBufferSize, &audioQueueBuffer[i]);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED];

return;

}

}

/**

kAudioQueueProperty_MagicCookie

部分音频格式需要设置magicCookie,这个cookie可以从AudioFileStream和AudioFile中获取

以下就是获取方法,并设置:

*/

UInt32 cookieSize;

Boolean writable;

OSStatus ignorableError;

ignorableError = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable);

if (ignorableError) {

return;

}

void *cookieData = calloc(1, cookieSize);

ignorableError = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData);

if (ignorableError) {

return;

}

ignorableError = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize);

free(cookieData);

if (ignorableError) {

return;

}

将插入buffer数据,开始播放

// 设置此位置的已经有buffer在使用

inuse[fillBufferIndex] = true;

// 使用的buffer的个数

buffersUsed++;

// 从自定义的Buffer数组中获取buffer结构体

AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];

// 将缓存的数据填充到buffer中

fillBuf->mAudioDataByteSize = bytesFilled;

// 插入Buffer

if (packetsFilled) {

err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, packetsFilled, packetDescs);

} else {

err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, 0, NULL);

}

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_ENQUEUE_FAILED];

return;

}

// 当前播放为正在缓冲或等待加载数据或缓冲结束或音频被打断的状态时

if (state == AS_BUFFERING || state == AS_WAITING_FOR_DATA || state == AS_FLUSHING_EOF || (state == AS_STOPPED && stopReason == AS_STOPPING_TEMPORARILY)) {

// 当数据全部加载完成之后或者缓冲buffer被全部填充,开始播放

if (state == AS_FLUSHING_EOF || buffersUsed == kNumAQBufs - 1) {

if (self.state == AS_BUFFERING) {

err = AudioQueueStart(audioQueue, NULL);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED];

return;

}

self.state = AS_PLAYING;

} else {

self.state = AS_WAITING_FOR_QUEUE_TO_START;

err = AudioQueueStart(audioQueue, NULL);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED];

return;

}

}

}

}

// 设置以缓冲buffer的个数

if (++fillBufferIndex >= kNumAQBufs) fillBufferIndex = 0;

// 重置buffer中的缓冲数据

bytesFilled = 0; 

packetsFilled = 0; 

// 等待buffer数组中的缓冲数据播放全部完成, 一旦完成, 开始解锁互斥锁, 否则线程一直等待解锁( 互斥锁, 等待条件信号量改变, 一旦条件信息量改变, 互斥锁进行解锁)

pthread_mutex_lock(&queueBuffersMutex);

while (inuse[fillBufferIndex]) {

pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex);

}

pthread_mutex_unlock(&queueBuffersMutex);

buffer中的数据被读取完成以后的回调

// 释放信号量

pthread_mutex_lock(&queueBuffersMutex);

inuse[bufIndex] = false;

buffersUsed--;

#if LOG_QUEUED_BUFFERS

NSLog(@"Queued buffers: %ld", buffersUsed);

#endif

pthread_cond_signal(&queueBufferReadyCondition);

pthread_mutex_unlock(&queueBuffersMutex);

水平有限,暂时先写到这了,有机会再进行补充吧!

参考: 

里面对AudioQueue的讲解很详细

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android音频系统详解 参考好文: Android 音频系统:从 AudioTrack 到 AudioFlin...
    爱雨520阅读 13,382评论 2 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,587评论 18 139
  • 对那些伤害我们的,最高境界的报复是:当他们走投无路时,当他们友叛亲离时,等待他的不是仇恨,而是带着原谅的爱。
    神是舜的神阅读 163评论 0 1
  • 石墨书院阅读 201评论 0 1
  • 已记不得是什么情况下看到不写就出局的,当初的想法也越来越模糊了 还好,有个机会让自己静静地想想当初...
    张婷_amy阅读 290评论 0 0