花了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,直到播放結束.
接下來直接上代碼:
判斷當前的播放轉態(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)
當輪詢到當前線程中有處理事件, 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)建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); ?
// 如果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ù)是本次處理的所有數(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;
}
}
}
// 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;
}
// 設置此位置的已經(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);
// 釋放信號量
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);
水平有限,暫時先寫到這了,有機會再進行補充吧!
參考:?