iOS,使用AudioQueue進行音頻開發(fā)

花了3天時間看AudioStream()源碼,總算對AudioQueue實現(xiàn)音頻流播放有了點了解.趁熱打鐵,寫下這篇日記,好記性不如爛筆頭!


首先對AudioStream整體思路做一個簡單分析:

1. 第一步是用CFNetwork進行網(wǎng)絡請求設置,通過CFReadStreamRef讀取流,分段讀取數(shù)據(jù);

2. 在請求到數(shù)據(jù)之后, 通過CFReadStreamClientCallBack進行函數(shù)回調, 在回調中進行音頻數(shù)據(jù)解析, 解析數(shù)據(jù)完成之后,將開始播放過程;

3. 創(chuàng)建一個AudioQueue,和一個Buffer數(shù)組;

4. 使用AudioQueueAllocateBuffer創(chuàng)建若干個AudioQueueBuffer實例,存放到創(chuàng)建的Buffer數(shù)組中;

5. 當緩沖到一定數(shù)據(jù)時, 從Buffer數(shù)組中取出一個buffer, memcpy數(shù)據(jù)后用AudioQueueEnqueueBuffer將buffer插入到AudioQueue中;

6. AudioQueue存在buffer后, 調用AudioQueueStart,開始播放;

7. AudioQueue播放消耗某個buffer后, 通過回調AudioQueueOutputCallback,將在另一個線程中buffer置為未使用狀態(tài),以供下次使用.重復步驟5,直到播放結束.


接下來直接上代碼:

初始化url,并添加一個打斷音頻的通知
開始播放流程
暫停操作

判斷當前的播放轉態(tài), 如果是暫停狀態(tài), 則開始播放;如果是初始化的狀態(tài), 將狀態(tài)置為AS_STARTING_FILE_THREAD. 同時創(chuàng)建一個異步線程,將所有的請求,數(shù)據(jù)解析,讀取流操作都放在此線程中.


// 一個容錯處理

// 下面三個方法在iOS7之后已經(jīng)被廢棄

// AudioSession的初始化, 前兩個參數(shù)設置為NULL表示AudioSession運行在主線程,第三個參數(shù)是音頻被打斷的回調函數(shù),第四個參數(shù)表示回調函數(shù)的附帶參數(shù)

// 注意點: AudioSessionInitialize會被多次調用, 但是回調函數(shù)只能被設置一次,因此必須使用靜態(tài)方法.當注冊成功后, 以后所有的打斷都會回調到該靜態(tài)方法上, 即使下次再調用AudioSessionInitialize并且把另一個靜態(tài)方法作為參數(shù)傳入, 當打斷到來時還是會回調到第一次設置的方法上。

// 函數(shù)原型

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

// 函數(shù)實現(xiàn)

AudioSessionInitialize (NULL, NULL, ASAudioSessionInterruptionListener, self );

// 設置類別, 第一個參數(shù)設置的功能類型,如果只是播放音頻可以直接設置kAudioSessionCategory_MediaPlayback,第二個參數(shù)是第三個參數(shù)的size, 第三個參數(shù)是想要實現(xiàn)功能的數(shù)據(jù)

// 函數(shù)原型

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

// 函數(shù)實現(xiàn)

UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;

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

// 啟動AudioSession

AudioSessionSetActive(true);

// 初始化互斥量(參數(shù)一是互斥量, 參數(shù)二是互斥鎖屬性,NULL默認為快速互斥鎖)

pthread_mutex_init(&queueBuffersMutex, NULL);

// 初始化條件變量

pthread_cond_init(&queueBufferReadyCondition, NULL);

// 當讀取流操作失敗, 進行清空操作, 否則進行數(shù)據(jù)請求操作

// 判斷當前線程是否有事件處理并且是否處于正常播放或是處于緩沖中,否則跳出循環(huán)

在internalThread線程中保持每0.25秒進行一次輪詢

當輪詢到當前線程中有處理事件, isRunning返回YES, 否則返回NO;

// 如果用戶手動拖拽了進度條, 則將seek到用戶拖拽的地方

if (seekWasRequested) {

[self internalSeekToTime:requestedSeekTime];

seekWasRequested = NO;

}

// 是否有緩存并且處于播放狀態(tài), 成立就暫停播放, 將狀態(tài)設置為緩沖狀態(tài)

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

err = AudioQueuePause(audioQueue);

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED];

return;

}

self.state = AS_BUFFERING;

}

創(chuàng)建讀取流

// 創(chuàng)建HTTP請求

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

// 設置請求頭, 實現(xiàn)分段加載

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

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

// 設置數(shù)據(jù)不連續(xù), 后面會用到

discontinuous = YES;

}

// 創(chuàng)建流請求

stream = CFReadStreamCreateForHTTPRequest(NULL, message);

CFRelease(message);

// 當使用CFReadStreamCreateForHTTPRequest創(chuàng)建讀取流時,流的重定向默認是被禁止的廓俭。如果請求連接被重定向,會導致一個錯誤箱残,它的狀態(tài)碼為300~307。如果收到一個重定向錯誤,需要關閉這個流被辑,然后重新創(chuàng)建一個流燎悍,啟用重定向并打開流

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

[self failWithErrorCode:AS_FILE_STREAM_SET_PROPERTY_FAILED];

return NO;

}

// HTTP代理設置(系統(tǒng)默認)

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);

}

// 設置狀態(tài)為加載數(shù)據(jù)狀態(tài)

self.state = AS_WAITING_FOR_DATA;

// 打開讀取流, 開始讀取數(shù)據(jù)

if (!CFReadStreamOpen(stream)) {

CFRelease(stream);

[self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED];

return NO;

}

// 調用CFReadStreamSetClient(可讀流)來登記要接收的流相關的事件

/**

監(jiān)聽回調事件

kCFStreamEventNone(沒有事件發(fā)生)

kCFStreamEventOpenCompleted(流被成功打開)

kCFStreamEventHasBytesAvailable(有數(shù)據(jù)可以讀取)

kCFStreamEventCanAcceptBytes(流可以接受寫入數(shù)據(jù)(用于寫入流))

kCFStreamEventErrorOccurred(在流上有錯誤發(fā)生)

kCFStreamEventEndEncountered(到達了流的結束位置)

在流有數(shù)據(jù)可以讀取, 發(fā)生錯誤,事件結束的情況下調用回調函數(shù)

*/

// CFStreamClientContext設置回調對象

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

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

// 添加到當前的RunLoop中

CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); ?

CFReadStreamSetClient的回調函數(shù)

// 如果aStream和以前的stream相等, 則跳過

if (aStream != stream) {

return;

}

// 流上有錯誤發(fā)生

if (eventType == kCFStreamEventErrorOccurred) ?{

[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];

}

// 到達了流的結束位置

if (eventType == kCFStreamEventEndEncountered) {

@synchronized(self)? {

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

if ([self isFinishing])? {

return;

}

}

// 如果有一部分緩沖數(shù)據(jù)盼理,則將其傳遞給用于處理的音頻隊列

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后重置解碼器狀態(tài)谈山,以防止當前的解碼器狀態(tài)影響到下一段音頻的解碼(比如切換播放的歌曲時)

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;

}

}

}

}

// 有可用數(shù)據(jù)時

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

第一個參數(shù)傳入一個上下文對象

第二個參數(shù)是歌曲信息解析的回調, 每解析出一首歌曲信息都會回調一次

第三個參數(shù)是分離幀的回調, 每解析出一部分幀數(shù)據(jù)都會回調一次

第四個參數(shù)是文件類型的提示, 這個參數(shù)來幫助AudioFileStream對文件格式進行解析.這個參數(shù)在文件信息不完整(例如信息有缺陷)時尤其有用,它可以給與AudioFileStream一定的提示宏怔,幫助其繞過文件中的錯誤或者缺失從而成功解析文件. 所以在確定文件類型的情況下建議各位還是填上這個參數(shù)奏路,如果無法確定可以傳入0

第五個參數(shù)是返回的AudioFileStream實例對應的AudioFileStreamID,這個ID需要保存起來作為后續(xù)一些方法的參數(shù)使用

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

*/

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;

}

// 從讀取流中獲取數(shù)據(jù), 返回數(shù)據(jù)大小

length = CFReadStreamRead(stream, bytes, kAQDefaultBufSize);

if (length == -1) {

[self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND];

return;

}

if (length == 0) {

return;

}

}

// 判斷數(shù)據(jù)是否是連續(xù)的, 是拖拽的時候discontinuous為YES

if (discontinuous) {

/**

AudioFileStream初始化完成之后,進行數(shù)據(jù)解析

第一個參數(shù)是AudioFileStream實例對應的AudioFileStreamID

第二個參數(shù)是本次要解析的數(shù)據(jù)長度

第三個參數(shù)是本次要解析的數(shù)據(jù)

第四個參數(shù)表示本次解析的數(shù)據(jù)和上次解析的數(shù)據(jù)是否是連續(xù)的, 如果是連續(xù)的則直接傳入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;

}

}

}

歌曲信息解析的回調

// 函數(shù)原型

// 第一個參數(shù)是Open方法中的上下文對象;

// 第二個參數(shù)表示AudioFileStream實例對應的AudioFileStreamID

// 第三個參數(shù)是此次回調解析的信息ID臊诊。表示當前PropertyID對應的信息已經(jīng)解析完成信息(例如數(shù)據(jù)格式鸽粉、音頻數(shù)據(jù)的偏移量等等),使用者可以通過AudioFileStreamGetProperty接口獲取PropertyID對應的值或者數(shù)據(jù)結構抓艳;

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


解析文件格式信息

// kAudioFileStreamProperty_ReadyToProducePackets一旦回調中出現(xiàn)這個PropertyID就代表解析完成触机,接下來可以對音頻數(shù)據(jù)進行幀分離了

if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) {

// 設置為YES是為了跳過頭信息數(shù)據(jù)

discontinuous = true;

}

// kAudioFileStreamProperty_DataOffset 表示音頻數(shù)據(jù)在整個音頻文件中的offset

if (inPropertyID == kAudioFileStreamProperty_DataOffset) {

// 獲取該幀數(shù)據(jù)在整個音頻文件中的偏移量

SInt64 offset;

UInt32 offsetSize = sizeof(offset);

/**

AudioFileStreamGetProperty

第四個參數(shù)ioFlags是一個返回參數(shù),表示這個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;

}

}

// 音頻文件中音頻數(shù)據(jù)的總量儡首。這個Property的作用一是用來計算音頻的總時長,二是可以在seek時用來計算時間對應的字節(jié)offset

if (inPropertyID == kAudioFileStreamProperty_AudioDataByteCount) {

UInt32 byteCountSize = sizeof(UInt64);

// 獲取音頻數(shù)據(jù)總量

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

if (err) {

[self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED];

return;

}

// 計算文件長度, 文件長度 = 音頻頭信息大小 + 音頻數(shù)據(jù)大小

fileLength = dataOffset + audioDataByteCount;

}

// 表示音頻文件結構信息偏友,是一個AudioStreamBasicDescription的結構

if (inPropertyID == kAudioFileStreamProperty_DataFormat) {

// asbd是一個AudioStreamBasicDescription結構體, mSampleRate是音頻的采樣率, 根據(jù)asbd.mSampleRate == 0判斷asbd是否已經(jīng)被初始化, 沒有被初始化則進行初始化

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是一樣的蔬胯,區(qū)別在于用這個PropertyID獲取到是一個AudioStreamBasicDescription的數(shù)組,這個參數(shù)是用來支持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);

}



分離音頻幀數(shù)據(jù)

/**

第一個參數(shù)是本次處理的所有數(shù)據(jù)

第二個參數(shù)是本次處理的數(shù)據(jù)大小

第三個參數(shù)是本次總共處理多少幀數(shù)據(jù)

第四個參數(shù)是個AudioStreamPacketDescription數(shù)組, 儲存了每一幀數(shù)據(jù)是從第幾個字節(jié)開始的,這一幀總共有多少個字節(jié)

*/

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

@synchronized(self) {

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

if ([self isFinishing]) {

return;

}

if (bitRate == 0) {

// m4a和其他一些格式不會去解析音頻數(shù)據(jù)的碼率,我們需要在這里設置一個“不能解析的”條件, 對于UInt32的~0 等于 (0x1 << 31) - 1

bitRate = ~0;

}

if (discontinuous) {

discontinuous = false;

}

// 創(chuàng)建音頻隊列

if (!audioQueue) {

[self createQueue];

}

}

// 判斷inPacketDescriptions是否有返回. 主要是為了區(qū)分VBR編碼和CBR編碼的數(shù)據(jù)(不是太懂, 百度了下, VBR動態(tài)碼率, CBR靜態(tài)碼率)

if (inPacketDescriptions) {

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

// 音頻幀的偏移量

SInt64 packetOffset = inPacketDescriptions[i].mStartOffset;

// 音頻幀的大小

SInt64 packetSize? = inPacketDescriptions[i].mDataByteSize;

// 緩存空間剩余大小

size_t bufSpaceRemaining;

// processedPacketsCount表示已下載音頻幀的總個數(shù),processedPacketsSizeTotal表示已下載音頻大小(在顯示已下載進度條的時候讀取此數(shù)), 這一步主要是為了計算平均碼率

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添加到播放隊列中, 否則繼續(xù)進行緩存

if (bufSpaceRemaining < packetSize) {

[self enqueueBuffer];

}

@synchronized(self) {

if ([self isFinishing]) return;

// 在緩沖隊列沒有為新的音頻數(shù)據(jù)騰出空間,那么就退出

if (bytesFilled + packetSize > packetBufferSize) return;

// 拷貝數(shù)據(jù)到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;

// 緩存的數(shù)據(jù)大小

bytesFilled += packetSize;

// 緩存的音頻幀個數(shù)

packetsFilled += 1;

}

// 當緩存的音頻幀的個數(shù)超過限定的最大值時, 將buffer添加到播放隊列中

size_t packetsDescsRemaining = kAQMaxPacketDescs - packetsFilled;

if (packetsDescsRemaining == 0) {

[self enqueueBuffer];

}

}

} else {

size_t offset = 0;

while (inNumberBytes) {

// 如果緩存空間剩余大小小于當前處理的數(shù)據(jù)大小,

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;

}

}

}

創(chuàng)建AudioQueue,進行音頻播放

// mSampleRate 采樣率,? mFramesPerPacket 每個Packet的幀數(shù)量

sampleRate ? ? ? = asbd.mSampleRate;

packetDuration = asbd.mFramesPerPacket / sampleRate;

/**

創(chuàng)建AudioQueue

第一個參數(shù)表示需要播放的音頻數(shù)據(jù)格式類型位他,是一個AudioStreamBasicDescription對象笔宿,是使用AudioFileStream或者AudioFile解析出來的數(shù)據(jù)格式信息

第二個參數(shù)AudioQueueOutputCallback是某塊Buffer被使用之后的回調

第三個參數(shù)為上下文對象

第四個參數(shù)inCallbackRunLoop為AudioQueueOutputCallback需要在的哪個RunLoop上被回調,如果傳入NULL的話就會再AudioQueue的內(nèi)部RunLoop中被回調棱诱,所以一般傳NULL就可以了

第五個參數(shù)inCallbackRunLoopMode為RunLoop模式,如果傳入NULL就相當于kCFRunLoopCommonModes涝动,也傳NULL就可以了

第六個參數(shù)inFlags是保留字段迈勋,目前沒作用,傳0

第七個參數(shù)醋粟,返回生成的AudioQueue實例

*/

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

if (err) {

[self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED];

return;

}

// kAudioQueueProperty_IsRunning監(jiān)聽當前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;

}

}

/**

創(chuàng)建自定的Buffer數(shù)組

*/

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

/**

第一個參數(shù)傳入AudioQueue實例

第二個參數(shù)傳入Buffer大小

第三個參數(shù)傳出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數(shù)據(jù),開始播放

// 設置此位置的已經(jīng)有buffer在使用

inuse[fillBufferIndex] = true;

// 使用的buffer的個數(shù)

buffersUsed++;

// 從自定義的Buffer數(shù)組中獲取buffer結構體

AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex];

// 將緩存的數(shù)據(jù)填充到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;

}

// 當前播放為正在緩沖或等待加載數(shù)據(jù)或緩沖結束或音頻被打斷的狀態(tài)時

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

// 當數(shù)據(jù)全部加載完成之后或者緩沖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的個數(shù)

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

// 重置buffer中的緩沖數(shù)據(jù)

bytesFilled = 0;?

packetsFilled = 0;?

// 等待buffer數(shù)組中的緩沖數(shù)據(jù)播放全部完成, 一旦完成, 開始解鎖互斥鎖, 否則線程一直等待解鎖( 互斥鎖, 等待條件信號量改變, 一旦條件信息量改變, 互斥鎖進行解鎖)

pthread_mutex_lock(&queueBuffersMutex);

while (inuse[fillBufferIndex]) {

pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex);

}

pthread_mutex_unlock(&queueBuffersMutex);

buffer中的數(shù)據(jù)被讀取完成以后的回調

// 釋放信號量

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的講解很詳細

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市米愿,隨后出現(xiàn)的幾起案子厦凤,更是在濱河造成了極大的恐慌,老刑警劉巖育苟,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件较鼓,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機博烂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門香椎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人禽篱,你說我怎么就攤上這事畜伐。” “怎么了躺率?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵玛界,是天一觀的道長。 經(jīng)常有香客問我悼吱,道長慎框,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任舆绎,我火速辦了婚禮鲤脏,結果婚禮上,老公的妹妹穿的比我還像新娘吕朵。我一直安慰自己猎醇,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布努溃。 她就那樣靜靜地躺著硫嘶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梧税。 梳的紋絲不亂的頭發(fā)上沦疾,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音第队,去河邊找鬼哮塞。 笑死,一個胖子當著我的面吹牛凳谦,可吹牛的內(nèi)容都是我干的忆畅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼尸执,長吁一口氣:“原來是場噩夢啊……” “哼家凯!你這毒婦竟也來了?” 一聲冷哼從身側響起如失,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绊诲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后褪贵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掂之,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了板惑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橄镜。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冯乘,靈堂內(nèi)的尸體忽然破棺而出洽胶,到底是詐尸還是另有隱情,我是刑警寧澤裆馒,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布姊氓,位于F島的核電站,受9級特大地震影響喷好,放射性物質發(fā)生泄漏翔横。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一梗搅、第九天 我趴在偏房一處隱蔽的房頂上張望禾唁。 院中可真熱鬧,春花似錦无切、人聲如沸荡短。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掘托。三九已至,卻和暖如春籍嘹,著一層夾襖步出監(jiān)牢的瞬間闪盔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工辱士, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泪掀,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓颂碘,卻偏偏與公主長得像异赫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凭涂,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android音頻系統(tǒng)詳解 參考好文: Android 音頻系統(tǒng):從 AudioTrack 到 AudioFlin...
    愛雨520閱讀 13,650評論 2 7
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)贴妻,斷路器切油,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • 對那些傷害我們的,最高境界的報復是:當他們走投無路時名惩,當他們友叛親離時澎胡,等待他的不是仇恨,而是帶著原諒的愛痹籍。
    神是舜的神閱讀 168評論 0 1
  • 石墨書院閱讀 205評論 0 1
  • 已記不得是什么情況下看到不寫就出局的邻奠,當初的想法也越來越模糊了 還好,有個機會讓自己靜靜地想想當初...
    張婷_amy閱讀 299評論 0 0