Audio Playback in iOS (Part 4) : AudioFile
前言
接着第三篇的
AudioStreamFile
這一篇要來聊一下
AudioFile
。和
AudioStreamFile
一樣
AudioFile
是
AudioToolBox
framework中的一員,它也能夠完成第一篇所述的第2步,讀取音頻格式資訊和進行幀分離,但事實上它的功能遠不止如此。
AudioFile介紹
按照官方文檔的描述:
a C programming interface that enables you to read or write a wide variety of audio data to or from disk or a memory buffer.With Audio File Services you can:
- Create, initialize, open, and close audio files
- Read and write audio files
- Optimize audio files
- Work with user data and global information
這個類可以用來建立、初始化音頻檔案;讀寫音頻資料;對音頻檔案進行優化;讀取和寫入音頻格式資訊等等,功能十分強大,可見它不但可以用來支援音頻播放,甚至可以用來生成音頻檔案。當然,在本篇文章中隻會涉及一些和音頻播放相關的内容(打開音頻檔案、讀取格式資訊、讀取音頻資料,其實我也隻對這些方法有一點了解,其餘的功能沒用過。。>_<).
AudioFile的打開“姿勢”
AudioFile
提供了兩個打開檔案的方法:
1、
AudioFileOpenURL
| |
從方法的定義上來看是用來讀取本地檔案的:
第一個參數,檔案路徑;
第二個參數,檔案的允許使用方式,是讀、寫還是讀寫,如果打開檔案後進行了允許使用方式以外的操作,就得到
kAudioFilePermissionsError
錯誤碼(比如Open時聲明是
kAudioFileReadPermission
但卻調用了
AudioFileWriteBytes
);
第三個參數,和
AudioFileStream
的open方法中一樣是一個幫助
AudioFile
解析檔案的類型提示,如果檔案類型确定的話應當傳入;
第四個參數,傳回AudioFile執行個體對應的
AudioFileID
,這個ID需要儲存起來作為後續一些方法的參數使用;
傳回值用來判斷是否成功打開檔案(OSSStatus == noErr)。
2、
AudioFileOpenWithCallbacks
| |
看過第一個Open方法後,這個方法乍看上去讓人有點迷茫,沒有URL的參數如何告訴AudioFile該打開哪個檔案?還是先來看一下參數的說明吧:
第一個參數,上下文資訊,不再多做解釋;
第二個參數,當
AudioFile
需要讀音頻資料時進行的回調(調用Open和Read方式後
同步
回調);
第三個參數,當
AudioFile
需要寫音頻資料時進行的回調(寫音頻檔案功能時使用,暫不讨論);
第四個參數,當
AudioFile
需要用到檔案的總大小時回調(調用Open和Read方式後
同步
回調);
第五個參數,當
AudioFile
需要設定檔案的大小時回調(寫音頻檔案功能時使用,暫不讨論);
第六、七個參數和傳回值同
AudioFileOpenURL
方法;
這個方法的重點在于
AudioFile_ReadProc
這個回調。換一個角度了解,這個方法相比于第一個方法自由度更高,AudioFile需要的隻是一個資料源,無論是磁盤上的檔案、記憶體裡的資料甚至是網絡流隻要能在
AudioFile
需要資料時(Open和Read時)通過
AudioFile_ReadProc
回調為AudioFile提供合适的資料就可以了,也就是說使用方法不僅僅可以讀取本地檔案也可以如
AudioFileStream
一樣以流的形式讀取資料。
下面來看一下
AudioFile_GetSizeProc
和
AudioFile_ReadProc
這兩個讀取功能相關的回調
| |
首先是
AudioFile_GetSizeProc
回調,這個回調很好了解,傳回檔案總長度即可,總長度的擷取途徑自然是檔案系統或者httpResponse等等。
接下來是
AudioFile_ReadProc
回調:
第一個參數,上下文對象,不再贅述;
第二個參數,需要讀取第幾個位元組開始的資料;
第三個參數,需要讀取的資料長度;
第四個參數,傳回參數,是一個資料指針并且其空間已經被配置設定,我們需要做的是把資料memcpy到buffer中;
第五個參數,實際提供的資料長度,即memcpy到buffer中的資料長度;
傳回值,如果沒有任何異常産生就傳回noErr,如果有異常可以根據異常類型選擇需要的error常量傳回(一般用不到其他傳回值,傳回noErr就足夠了);
這裡需要解釋一下這個回調方法的工作方式。
AudioFile
需要資料時會調用回調方法,需要資料的時間點有兩個:
- Open方法調用時,由于
的Open方法調用過程中就會對音頻格式資訊進行解析,隻有符合要求的音頻格式才能被成功打開否則Open方法就會傳回錯誤碼(換句話說,Open方法一旦調用成功就相當于AudioFile
在Parse後傳回AudioStreamFile
一樣,隻要Open成功就可以開始讀取音頻資料,詳見第三篇),是以在Open方法調用的過程中就需要提供一部分音頻資料來進行解析;ReadyToProducePackets
- Read相關方法調用時,這個不需要多說很好了解;
通過回調提供資料時需要注意inPosition和requestCount參數,這兩個參數指明了本次回調需要提供的資料範圍是從inPosition開始requestCount個位元組的資料。這裡又可以分為兩種情況:
- 有充足的資料:那麼我們需要把這個範圍内的資料拷貝到buffer中,并且給actualCount指派requestCount,最後傳回noError;
- 資料不足:沒有充足資料的話就隻能把手頭有的資料拷貝到buffer中,需要注意的是這部分被拷貝的資料必須是從inPosition開始的
,拷貝完成後給actualCount指派實際拷貝進buffer中的資料長度後傳回noErr,這個過程可以用下面的代碼來表示:連續資料
| |
說到這裡又需要分兩種情況:
2.1. Open方法調用時的回調資料不足:AudioFile的Open方法會根據檔案格式類型分幾步進行資料讀取以解析确定是否是一個合法的檔案格式,其中每一步的inPosition和requestCount都不一樣,如果某一步不成功就會直接進行下一步,如果幾部下來都失敗了,那麼Open方法就會失敗。簡單的說就是在調用Open之前首先需要保證音頻檔案的格式資訊完整,這就意味着
AudioFile
并不能獨立用于音頻流的讀取,在流播放時首先需要使用
AudioStreamFile
來得到
ReadyToProducePackets
标志位來保證資訊完整;
2.2. Read方法調用時的回調資料不足:這種情況下inPosition和requestCount的數值與Read方法調用時傳入的參數有關,資料不足對于Read方法本身沒有影響,隻要回調傳回noErr,Read就成功,隻是實際交給Read方法的調用方的資料會不足,那麼就把這個問題的處理交給了Read的調用方;
讀取音頻格式資訊
成功打開音頻檔案後就可以讀取其中的格式資訊了,讀取用到的方法如下:
| |
AudioFileGetPropertyInfo
方法用來擷取某個屬性對應的資料的大小(outDataSize)以及該屬性是否可以被write(isWritable),而
AudioFileGetProperty
則用來擷取屬性對應的資料。對于一些大小可變的屬性需要先使用
AudioFileGetPropertyInfo
擷取資料大小才能取擷取資料(例如formatList),而有些确定類型單個屬性則不必先調用
AudioFileGetPropertyInfo
直接調用
AudioFileGetProperty
即可(比如BitRate),例子如下:
| |
可以擷取的屬性有下面這些,大家可以參考文檔來擷取自己需要的資訊(注意到這裡有EstimatedDuration,可以得到Duration了):
| |
讀取音頻資料
讀取音頻資料的方法分為兩類:
1、直接讀取音頻資料:
| |
第一個參數,FileID;
第二個參數,是否需要cache,一般來說傳false;
第三個參數,從第幾個byte開始讀取資料
第四個參數,這個參數在調用時作為輸入參數表示需要讀取讀取多少資料,調用完成後作為輸出參數表示實際讀取了多少資料(即Read回調中的requestCount和actualCount);
第五個參數,buffer指針,需要事先配置設定好足夠大的記憶體(ioNumBytes大,即Read回調中的buffer,是以Read回調中不需要再配置設定記憶體);
傳回值表示是否讀取成功,EOF時會傳回
kAudioFileEndOfFileError
;
使用這個方法得到的資料都是沒有進行過幀分離的資料,如果想要用來播放或者解碼還必須通過
AudioFileStream
進行幀分離;
2、按幀(Packet)讀取音頻資料:
| |
按幀讀取的方法有兩個,這兩個方法看上去差不多,就連參數也幾乎相同,但使用場景和效率上卻有所不同,官方文檔中如此描述這兩個方法:
-
is memory efficient when reading variable bit-rate (VBR) audio data;AudioFileReadPacketData
-
is more efficient thanAudioFileReadPacketData
when reading compressed file formats that do not have packet tables, such as MP3 or ADTS. This function is a good choice for reading either CBR (constant bit-rate) or VBR data if you do not need to read a fixed duration of audio.AudioFileReadPackets
- Use
only when you need to read a fixed duration of audio data, or when you are reading only uncompressed audio.AudioFileReadPackets
隻有當需要讀取固定時長音頻或者非壓縮音頻時才會用到
AudioFileReadPackets
,其餘時候使用
AudioFileReadPacketData
會有更高的效率并且更省記憶體;
下面來看看這些參數:
第一、二個參數,同
AudioFileReadBytes
;
第三個參數,對于
AudioFileReadPacketData
來說ioNumBytes這個參數在輸入輸出時都要用到,在輸入時表示outBuffer的size,輸出時表示實際讀取了多少size的資料。而對
AudioFileReadPackets
來說outNumBytes隻在輸出時使用,表示實際讀取了多少size的資料;
第四個參數,幀資訊數組指針,在輸入前需要配置設定記憶體,大小必須足夠存在ioNumPackets個幀資訊(ioNumPackets * sizeof(AudioStreamPacketDescription));
第五個參數,在輸入時表示需要讀取多少個幀,在輸出時表示實際讀取了多少幀;
第六個參數,outBuffer資料指針,在輸入前就需要配置設定好空間,這個參數看上去兩個方法一樣但其實并非如此。對于
AudioFileReadPacketData
來說隻要配置設定
近似幀大小 * 幀數
的記憶體空間即可,方法本身會針對給定的記憶體空間大小來決定最後輸出多少個幀,如果空間不夠會适當減少出的幀數;而對于
AudioFileReadPackets
來說則需要配置設定
最大幀大小(或幀大小上界) * 幀數
的記憶體空間才行(最大幀大小和幀大小上界的差別等下會說);這也就是為何第三個參數一個是輸入輸出雙向使用的,而另一個隻是輸出時使用的原因。就這點來說兩個方法中前者在使用的過程中要比後者更省記憶體;
傳回值,同
AudioFileReadBytes
;
這兩個方法讀取後的資料為幀分離後的資料,可以直接用來播放或者解碼。
下面給出兩個方法的使用代碼(以MP3為例):
| |
| |
Seek
seek的思路和之前講
AudioFileStream
時講到的是一樣的,差別在于AudioFile沒有方法來幫助修正seek的offset和seek的時間:
- 使用
時需要計算出approximateSeekOffsetAudioFileReadBytes
- 使用
或者AudioFileReadPacketData
時需要計算出seekToPacketAudioFileReadPackets
approximateSeekOffset和seekToPacket的計算方法參見第三篇。
關閉AudioFile
AudioFile
使用完畢後需要調用
AudioFileClose
進行關閉,沒啥特别需要注意的。
小結
本篇針對
AudioFile
的音頻讀取功能做了介紹,小結一下:
-
有兩個Open方法,需要針對自身的使用場景選擇不同的方法;AudioFile
-
用來讀取本地檔案AudioFileOpenURL
-
的使用場景比前者要廣泛,使用時需要注意AudioFileOpenWithCallbacks
,這個回調方法在Open方法本身和Read方法被調用時會被AudioFile_ReadProc
調用同步
- 必須保證音頻檔案格式資訊可讀時才能使用
的Open方法,AudioFile并不能獨立用于音頻流的讀取,需要配合AudioFile
使用才能讀取流(需要用AudioStreamFile
來判斷檔案格式資訊可讀之後再調用Open方法);AudioStreamFile
- 使用
讀取格式資訊時需要判斷所讀取的資訊是否需要先調用AudioFileGetProperty
獲得資料大小後再進行讀取;AudioFileGetPropertyInfo
- 讀取音頻資料應該根據使用的場景選擇不同的音頻讀取方法,對于不同的讀取方法seek時需要計算的變量也不相同;
-
使用完畢後需要調用AudioFile
進行關閉;AudioFileClose
示例代碼
對于本地檔案用AudioFile讀取比較簡單就不在這裡提供demo了,對于流播放中的AudioFile使用推薦大家閱讀豆瓣的開源播放器代碼DOUAudioStreamer。
下篇預告
下一篇将講述如何使用
AudioQueue
。
參考資料
Audio File Services Reference
原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0