標籤:連續 行資料 cookie tao ide get rtti ash start
- AudioQueue簡單介紹
- AudioStreamer說明
- AudioQueue具體解釋
- AudioQueue工作原理
- AudioQueue主要介面
- AudioQueueNewOutput
- AudioQueueAllocateBuffer
- AudioQueueEnqueueBuffer
- AudioQueueStart Pause Stop Flush Reset Dispose
- AudioQueueFreeBuffer
- AudioQueueGetProperty AudioQueueSetProperty
- 音頻播放LocalAudioPlayer
- 播放器的初始化
- 播放音頻
- LocalAudioPlayer相關屬性
- 讀取並開始解析音頻
- 解析音頻資訊
- kAudioFileStreamProperty_DataFormat
- kAudioFileStreamProperty_FileFormat
- kAudioFileStreamProperty_AudioDataByteCount
- kAudioFileStreamProperty_BitRate
- kAudioFileStreamProperty_DataOffset
- kAudioFileStreamProperty_AudioDataPacketCount
- kAudioFileStreamProperty_ReadyToProducePackets
- 解析音訊框架
- 播放音頻資料
- 清理相關資源
- 結束
iOS實現播放本地音樂,有非常多種方法。比如AVAudioPlayer,這些都能非常好的勝任。有人就奇怪了。為什麼要退而求其次。使用更複雜的AudioQueue來播放本地音樂呢?請繼續往下看
AudioQueue簡單介紹
AudioQueue,在蘋果的開發人員文檔上是這麼說的
"Audio Queue Services provides a straightforward, low overhead way to record and play audio in iOS and Mac OS X."
AudioQueue官方文檔
AudioQueue服務提供一種直接的,低開銷的方式以用於在iOS及Mac OS X上錄音和播放音樂。
使用AudioQueue播放音樂的長處就是開銷非常小並且支援流式播放(邊下邊播)。可是缺點就是開發難度大。所以有網路音頻庫AudioStreamer,網上有非常多講AudioQueue的。可是有執行個體代碼說明的,實在是少之又少。正好公司項目有音頻需求,儘管項目中使用的並不是我自己寫的音頻播放功能,但事後還是想自己來研究一下,這個在我看來比較奇妙也比較有趣的AudioQueue。
AudioStreamer說明
iOS上一個比較有名的流媒體音頻播放庫是AudioStreamer,該庫即使用了AudioQueue。只是該音頻庫並不支援本地音樂播放,我感覺非常奇怪,為什麼作者不支援。並且在使用過程中,我發現該庫還是有點問題,儘管我對音頻方面的知識並不怎麼瞭解,也並不能與大師媲美並論。但我也希望通過自己的學習,終於完成一個相似AudioStreamer的網路音樂庫,眼下或許僅僅是一個設想。無論最後自己有沒有那能力,起碼我以前也嘗試過。只是工作近期比較忙,加上自己知識的欠缺。不知何時才幹實現。本次就先來補上AudioStreamer沒有支援的,使用AudioQueue播放本地音樂。
AudioQueue具體解釋AudioQueue工作原理
我從Apple的官方文檔上截下下面該圖:
該圖非常好的說明了AudioQueue的工作原理,例如以下說明:
1. 使用者調用對應的方法,將音頻資料從硬碟中讀入到AudioQueue的緩衝區中,並將緩衝區送入音頻隊列。
2. 使用者App通過AudioQueue提供的介面。告訴外放裝置,緩衝區中已經有資料。能夠拿去播放。
3. 當一個緩衝區中的音頻資料播放完成之後,AudioQueue告訴使用者,當前有一個空的緩衝區能夠用來給你填充資料。
4. 反覆以上步驟。直至資料播放完成。
到這裡,肯定有不少同學發現了,AudioQueue事實上就是生產者-消費者模型的典型應用。
AudioQueue主要介面AudioQueueNewOutput
OSStatus AudioQueueNewOutput(const AudioStreamBasicDescription *inFormat, AudioQueueOutputCallback inCallbackProc, void *inUserData, CFRunLoopRef inCallbackRunLoop, CFStringRef inCallbackRunLoopMode, UInt32 inFlags, AudioQueueRef _Nullable *outAQ);
該方法用於建立一個用於輸出音訊AudioQueue
參數及返回說明例如以下:
1. inFormat:該參數指明了即將播放的音訊資料格式
2. inCallbackProc:該回調用於當AudioQueue已使用完一個緩衝區時通知使用者,使用者能夠繼續填充音頻資料
3. inUserData:由使用者傳入的資料指標,用於傳遞給回呼函數
4. inCallbackRunLoop:指明回調事件發生在哪個RunLoop之中,假設傳遞NULL,表示在AudioQueue所在的線程上運行該回調事件,普通情況下,傳遞NULL就可以。
5. inCallbackRunLoopMode:指明回調事件發生的RunLoop的模式,傳遞NULL相當於kCFRunLoopCommonModes,通常情況下傳遞NULL就可以
6. outAQ:該AudioQueue的引用執行個體,
返回OSStatus,假設值為noErr。則表示沒有錯誤。AudioQueue建立成功。
AudioQueueAllocateBuffer
OSStatus AudioQueueAllocateBuffer(AudioQueueRef inAQ, UInt32 inBufferByteSize, AudioQueueBufferRef _Nullable *outBuffer);
該方法的作用是為存放音頻資料的緩衝區開闢空間
參數及返回說明例如以下:
1. inAQ:AudioQueue的引用執行個體
2. inBufferByteSize:須要開闢的緩衝區的大小
3. outBuffer:開闢的緩衝區的引用執行個體
返回OSStatus,假設值為noErr,則表示緩衝區開闢成功。
AudioQueueEnqueueBuffer
OSStatus AudioQueueEnqueueBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, UInt32 inNumPacketDescs, const AudioStreamPacketDescription *inPacketDescs);
該方法用於將已經填充資料的AudioQueueBuffer入隊到AudioQueue
參數及返回說明例如以下:
1. inAQ:AudioQueue的引用執行個體
2. inBuffer:須要入隊的緩衝區執行個體
3. inNumPacketDescs:緩衝區中共存在有多少幀音頻資料
4. inPacketDescs:緩衝區中每一幀的相關資訊。使用者須要指明當中每一幀在緩衝區中資料的位移值,通過欄位mStartOffset來指定
返回OSStatus。假設值為noErr,則表示緩衝區已經成功入隊。等待播放
AudioQueueStart Pause Stop Flush Reset Dispose
OSStatus AudioQueueStart(AudioQueueRef inAQ, const AudioTimeStamp *inStartTime);OSStatus AudioQueuePause(AudioQueueRef inAQ);OSStatus AudioQueueStop(AudioQueueRef inAQ, Boolean inImmediate);OSStatus AudioQueueFlush(AudioQueueRef inAQ);OSStatus AudioQueueReset(AudioQueueRef inAQ);OSStatus AudioQueueDispose(AudioQueueRef inAQ, Boolean inImmediate);
顧名思義,前三個方法用於音訊播放,暫停及停止。
後兩個方法用於在最後清洗及重設音頻隊列,清洗確保隊列中的資料全然輸出。AudioQueuDispose用於清理AudioQueue所佔資源。
參數及返回說明例如以下:
1. inAQ:AudioQueue的引用執行個體
2. inStartTime:指明要開始播放音訊時間,假設要馬上開始。傳遞NULL
3. inImmediate:指明是否要馬上停止音頻播放,如是。傳遞true
返回OSStatus表示相關的操作是否成功運行。
AudioQueueFreeBuffer
OSStatus AudioQueueFreeBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);
該方法用於在播放結束時。釋放清理緩衝區時使用
AudioQueueGetProperty AudioQueueSetProperty
OSStatus AudioQueueGetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, void *outData, UInt32 *ioDataSize);OSStatus AudioQueueSetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, const void *inData, UInt32 inDataSize);
此GET/SET方法,用於設定擷取AudioQueue的相關屬性,請參看AudioQueue.h標頭檔裡的相關說明。
音頻播放(LocalAudioPlayer)
使用AudioQueue播放音樂,一般須要配合AudioFileStream一起,AudioFileStream負責解析音頻資料。AudioQueue負責播放解析到的音頻資料。
此次僅實現最主要的本地音頻播放功能。旨在為以後打下基礎,不處理不論什麼相關的狀態(如暫停、停止、SEEK),錯誤等。
播放方式相似流式播放,僅僅是音頻資料來源於本地檔案而非網路。需經過下面幾個步驟:
1. 持續不斷的從檔案裡讀取部分資料,直到資料所有讀取結束
2. 將檔案裡讀出的資料,交給AudioFileStream進行資料解析
3. 建立AudioQueue,當資料放入到AudioQueueBuffer中
4. 將緩衝區放到到AudioQueue中,開始播放音頻
5. 播放音頻結束,清理相關資源
播放器的初始化
播放器的init主要用於指定要播放的音頻檔案,例如以下所看到的:
讀取檔案操作,使用NSFileHandle類。audioInUseLock,是一個NSLock*類型,用於在AudioQueue通知我們有空的緩衝區能夠使用時做標記。
我們在使用者點擊playbutton的時候,初始化該播放器,並調用play方法進行播放
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="初始化播放器執行個體" title="">
播放音頻
播放音頻將分為下面步驟:
1. 讀取並開始解析音頻
2. 解析音頻資訊
3. 解析音訊框架
4. 播放音頻資料
5. 清理相關資源
我們先定義幾個宏,用於指定一些緩衝區的大小
#define kNumberOfBuffers 3 //AudioQueueBuffer數量。一般指明為3#define kAQBufSize 128 * 1024 //每一個AudioQueueBuffer的大小#define kAudioFileBufferSize 2048 //檔案讀取資料的緩衝區大小#define kMaxPacketDesc 512 //最大的AudioStreamPacketDescription個數
LocalAudioPlayer相關屬性
LocalAudioPlayer中定義的屬性例如以下所看到的:
讀取並開始解析音頻
我們使用AudioFileStream來解析音頻資訊。在使用者調用play方法之後。首先調用AudioFileStreamOpen,開啟AudioFileStream,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="開啟AudioFileStream" title="">
extern OSStatus AudioFileStreamOpen (void *inClientData, AudioFileStream_PropertyListenerProc inPropertyListenerProc, AudioFileStream_PacketsProc inPacketsProc, AudioFileTypeID inFileTypeHint, AudioFileStreamID * outAudioFileStream);
AudioFileStreamOpen的參數說明例如以下:
1. inClientData:使用者指定的資料,用於傳遞給回呼函數。這裡我們指定(__bridge LocalAudioPlayer*)self
2. inPropertyListenerProc:當解析到一個音頻資訊時。將回調該方法
3. inPacketsProc:當解析到一個音訊框架時,將回調該方法
4. inFileTypeHint:指明音頻資料的格式。假設你不知道音頻資料的格式,能夠傳0
5. outAudioFileStream:AudioFileStreamID執行個體,需儲存供興許使用
讀取到資料之後。調用AudioFileStreamParseBytes解析資料,其原型例如以下:
extern OSStatusAudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream, UInt32 inDataByteSize, const void * inData, AudioFileStreamParseFlags inFlags);
參數的說明例如以下:
1. inAudioFileStream:AudioFileStreamID執行個體,由AudioFileStreamOpen開啟
2. inDataByteSize:此次解析的資料位元組大小
3. inData:此次解析的資料大小
4. inFlags:資料解析標誌。當中僅僅有一個值kAudioFileStreamParseFlag_Discontinuity = 1,表示解析的資料是否是不連續的,眼下我們能夠傳0。
當檔案資料合部讀取結束的時候,此時便能夠關閉檔案。
解析音頻資訊
假設解析到音頻資訊,那麼將會調用之前指定的回呼函數,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="解析音頻資訊-1" title="">
每一個相關的屬性都能夠調用AudioFileStreamGetProperty來擷取到對應的值,原型例如以下:
extern OSStatusAudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioPropertyDataSize, void * outPropertyData);
參數說明:
1. inAudioFileStream:AudioFileStreamID執行個體,由AudioFileStreamOpen開啟
2. inPropertyID:要擷取的屬性名稱。參見AudioFileStream.h
3. ioPropertyDataSize:指明該屬性的大小
4. outPropertyData:用於存放該屬性值的空間
kAudioFileStreamProperty_DataFormat
該屬性指明了音頻資料的格式資訊,返回的資料是一個AudioStreamBasicDescription結構。需儲存用於AudioQueue的使用
kAudioFileStreamProperty_FileFormat
該屬性指明了音頻資料的編碼格式,如MPEG等。
kAudioFileStreamProperty_AudioDataByteCount
該屬性可擷取到音頻資料的長度,可用於計算音頻時間長度,計算公式為:
時間長度 = (音頻資料位元組大小 * 8) / 採樣率
kAudioFileStreamProperty_BitRate
該屬性可擷取到音訊採樣率。可用於計算音頻時間長度
kAudioFileStreamProperty_DataOffset
該屬性指明了音頻資料在整個音頻檔案裡的位移量:
音頻檔案總大小 = 位移量 + 音頻資料位元組大小
kAudioFileStreamProperty_AudioDataPacketCount
該屬性指明了音頻檔案裡共同擁有多少幀
kAudioFileStreamProperty_ReadyToProducePackets
該屬性告訴我們,已經解析到完整的音訊框架資料,準備產生音訊框架。之後會調用到另外一個回呼函數。我們在這裡建立音頻隊列AudioQueue,假設音頻資料中有Magic Cookie Data,則先調用AudioFileStreamGetPropertyInfo,擷取該資料是否可寫,假設可寫再取出該屬性值,並寫入到AudioQueue。之後便是音頻資料幀的解析。
解析音訊框架
音頻資訊解析完成之後,就應該解析音頻資料幀了,代碼例如以下所看到的:
在這裡。我們利用之前設定的inClientData,將該回呼函數,由C語言形式改為Objc的形式,解析到音頻資料之後的處理代碼例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="解析音頻資料幀-2" title="">
解析到音頻資料之後,我們要將資料寫入到AudioQueueBuffer中,首先,該回呼函數的原型例如以下所看到的:
typedef void (*AudioFileStream_PacketsProc)(void * inClientData, UInt32 inNumberBytes,UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);
參數說明:
1. inClientData:由AudioFileStreamOpen設定的使用者資料
2. inNumberBytes:音頻資料的位元組數
3. inNumberPackets:解析到的音訊框架數
4. inInputData:包括這些音頻資料幀的資料
5. inPacketDescriptions:AudioStreamPacketDescription數組。當中包括mStartOffset,指明了該幀相關資料的起始位置。mDataByteSize指明了該幀資料的大小。
此時我們首先建立一個音頻隊列。用以播放音頻。並為每一個緩衝區分配空間,例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2Fpcm8xMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="建立音頻隊列" title="">
之後我們遍曆每一幀。擷取每一幀的位移位置及資料大小,假設當前幀的資料大小,已經無法存放到當前的緩衝區當中,此時,我們應該將當前的緩衝區入隊。改動當前使用的緩衝區為下一個。並重設相關的資料填充資訊及已包括的資料幀資訊。
self.audioQueueCurrentBufferIndex = (++self.audioQueueCurrentBufferIndex) % kNumberOfBuffers;self.audioPacketsFilled = 0;self.audioDataBytesFilled = 0;
假設此時還沒有開始播放音樂,則能夠開始播放音樂
if(self.isPlaying == NO) { err = AudioQueueStart(audioQueueRef, NULL); self.isPlaying = YES;}
若下一個指定的緩衝區已經在使用。即所有緩衝區已滿且入隊。則應該進行等待
while(inuse[self.audioQueueCurrentBufferIndex]);
最後,假設緩衝區空間還能存放一幀資料,我們就使用memcpy將資料拷貝到緩衝區中對應的位置上去,儲存每一幀的相關資訊,設定每一幀在緩衝區中的位移量(mStartOffset),設定當前緩衝區已存方的資料大小。及已包括的幀量。
當一個緩衝區使用結束之後,AudioQueue將會調用之前由AudioQueueNewOutput設定的回呼函數,例如以下所看到的:
在該回調中,我們遍曆每一個緩衝區,找到空緩衝區。將該緩衝區的標記改動為未使用。
播放音頻資料
在處理資料幀的時候。假設緩衝區已經滿(指緩衝區的空間不足以存放下一幀資料),則此時能夠開始播放音頻
if(self.isPlaying == NO) { err = AudioQueueStart(audioQueueRef, NULL); self.isPlaying = YES;}
我們還能夠調用AudioQueuePause等相關方法來暫停和終止音頻播放。例如以下所看到的:
清理相關資源
終於。我們清理相關資源,在前面,我們利用了AudioQueueAddPropertyListener為kAudioQueueProperty_IsRunning屬性設定了一個監聽器,當AudioQueue啟動或者是終止的時候,會調用該函數:
該回呼函數,例如以下所看到的:
在該方法中,我們使用AudioQueueReset重設播放隊列,調用AudioQueueFreeBuffer釋放緩衝區空間,釋放AudioQueue所有資源,關閉AudioFileStream。
只是在實際使用過程中,我探索資料為空白的時候AudioQueue並不會主動終止,即不會主動調用該回調。所以我想,應該是要我們自己擷取到當前播放的進度,當播放完成的時候調用AudioQueueStop終止播放吧,這個問題留著待以後再繼續研究。
結束
終於完成了所有的代碼,使用者僅僅要點擊play,即能夠聽到《遙遠的她》這首美妙的音樂了。因為介面上僅僅有一個playbutton。不放示範圖了。戴上耳機。靜靜的享受自己的成果與這美妙的音樂。
。
iOS音頻播放之AudioQueue(一):播放本地音樂