KVAudioStreamer - 基于AudioToolBox的開源音頻流媒體播放器

在iOS上,播放音頻一般使用AVAudioPlayer進(jìn)行音頻播放,但是AVAudioPlayer并不支持流媒體播放,換言之,AVAudioPlayer只能播放本地音頻,當(dāng)遇到網(wǎng)絡(luò)音頻的時(shí)候,都是先下載到整個(gè)音頻文件,然后再播放(微信語音就是先下載再播放),但是有些使用場(chǎng)景要求音頻使用流媒體播放,提高用戶體驗(yàn),像音樂軟件,在線音頻教育軟件的一些音頻課程都要求流媒體播放。
項(xiàng)目git地址

1 開發(fā)初衷

目前開源的流媒體播放器有很多,例如AudioStreamer、DOUAudioStreamer,但是這兩個(gè)開源庫(kù)或多或少都有點(diǎn)瑕疵,并且都是在很久以前開發(fā)的,并不滿足我的要求,所以決定自己開發(fā)一個(gè)流媒體播放器,并回饋一下開源社區(qū)。

2 KVAudioStreamer介紹

KVAudioStreamer采用AudioToolBox框架開發(fā),使用C接口開發(fā)將更容易自主定制,當(dāng)然,相對(duì)的也增加了開發(fā)難度。KVAudioStreamer內(nèi)部代碼結(jié)構(gòu)清晰,由于開源時(shí)間晚于AudioStreamer和DOUAudioStreamer,所以使用的API都是最新的。

2.1 AudioQueue介紹

我使用的是AudioQueue進(jìn)行音頻播放,AudioToolBox播放音頻主要涉及到以下API(僅僅列出,連參數(shù)都沒有放上去):

//AudioFileStreamID,用于音頻數(shù)據(jù)解析
extern OSStatus AudioFileStreamOpen ();  //打開文件流,獲取文件流id
extern OSStatus AudioFileStreamParseBytes();  //解析數(shù)據(jù),將會(huì)傳入一個(gè)C語言方法,獲取解析結(jié)果
extern OSStatus AudioFileStreamClose();  //關(guān)閉文件流
extern OSStatus AudioFileStreamGetProperty();  //獲取文件信息
//AudioQueueBufferRef,用于音頻緩存數(shù)據(jù)存儲(chǔ)
extern OSStatus AudioQueueEnqueueBuffer();  //放進(jìn)音頻隊(duì)列
extern OSStatus AudioQueueFreeBuffer();  //釋放緩存區(qū)數(shù)據(jù)
//AudioQueueRef,用于音頻播放
extern OSStatus AudioQueueStart();  //開始播放
extern OSStatus AudioQueuePause();  //暫停播放
extern OSStatus AudioQueueStop();  //停止播放
extern OSStatus AudioQueueDispose();  //釋放音頻隊(duì)列
//以下兩個(gè)方法配合使用,來控制播放速率
extern OSStatus AudioQueueSetProperty();  //設(shè)置屬性
extern OSStatus AudioQueueSetParameter();  //設(shè)置參數(shù)

AudioQueue的播放流程如下所示:

AudioQueue工作原理

從上圖可以看出,AudioQueue播放音頻是一種生產(chǎn)者-消費(fèi)者模式,所以KVAudioStreamer也采用生產(chǎn)者-消費(fèi)者的設(shè)計(jì)模式進(jìn)行框架搭建,內(nèi)部代碼邏輯清晰,充分解耦,方便開發(fā)者學(xué)習(xí)以及修改(這一點(diǎn)自認(rèn)為優(yōu)于AudioStreamer和DOUAudioStreamer)。
下圖是KVAudioStreamer的代碼結(jié)構(gòu),我已經(jīng)將實(shí)現(xiàn)代碼抽象成生產(chǎn)者和消費(fèi)者:
KVAudioStreamer代碼結(jié)構(gòu)

本篇文章僅為KVAudioStreamer的介紹文檔,關(guān)于AudioToolBox的使用問題將會(huì)在往后另一篇文章進(jìn)行說明,造輪子的過程是痛并快樂著的,踩過無數(shù)的坑,在填坑的過程中也在不斷成長(zhǎng),文章最后將會(huì)貼出當(dāng)時(shí)學(xué)習(xí)AudioToolBox的參考文章,同時(shí)也感謝這些作者的付出。

2.2 功能介紹

KVAudioStreamer擁有以下功能:

  1. 支持多種音頻格式(mp3、flac、wav、m4a...);
  2. 支持緩存功能;
  3. 支持定點(diǎn)播放;
  4. 多倍率播放。

KVAudioStreamer支持多種音頻格式,經(jīng)測(cè)試,目前音頻格式中僅ape格式文件無法播放,另外對(duì)m4a音頻文件只能做到流播放,無法使用seek操作,后續(xù)將會(huì)研究如何解決,如果開發(fā)者不需要播放m4a文件,那么KVAudioStreamer會(huì)是一個(gè)不錯(cuò)的選擇。
支持緩存功能,針對(duì)網(wǎng)絡(luò)文件,在完整緩存完畢將會(huì)通過代理事件通知開發(fā)者緩存成功,攜帶文件路徑供開發(fā)者下一步操作(注:僅在完整緩存后才會(huì)自動(dòng)緩存,如果播放網(wǎng)絡(luò)文件時(shí)還未緩存成功就使用了seek操作,那么就不算完整緩存,因?yàn)閮?nèi)部使用了斷點(diǎn)下載,如果seek后便無法保證文件的完整性,如果文件已經(jīng)完整緩存成功,重復(fù)seek不會(huì)產(chǎn)生重復(fù)的網(wǎng)絡(luò)請(qǐng)求,幫助用戶節(jié)省流量)。
定點(diǎn)播放也是KVAudioStreamer的一大特色,支持從音頻的某個(gè)位置開始播放,用于播放位置記憶功能。
多倍率播放,這也是音頻播放的一個(gè)常用功能,建議區(qū)間(0,5),其實(shí)2倍速度播放,出來的聲音就已經(jīng)很鬼畜了。

3 如何集成

該項(xiàng)目已提交到github開源社區(qū),并且提供cocoapod功能,可以直接通過git clone進(jìn)行項(xiàng)目下載,里面包含一個(gè)完整的demo演示,demo里面同時(shí)提供了音頻后臺(tái)播放鎖屏控制的解決方案。

3.1 git地址

git地址

3.2 cocoapod集成

使用以下pod命令集成

pod 'KVAudioStreamer', ' 1.0.0'

4 如何使用

KVAudioStreamer的API設(shè)計(jì)遵從命名規(guī)范,堅(jiān)持一切從簡(jiǎn)的設(shè)計(jì)原則,所以使用簡(jiǎn)單,上手快速。

4.1 初始化

self.streamer = [[KVAudioStreamer alloc] init];
self.streamer.delegate = self;
self.streamer.cacheEnable = YES;    //開啟緩存功能
//設(shè)置httpheader,音樂資源在阿里云OSS開啟了防盜鏈,需要在這里設(shè)置referer,如果沒有防盜鏈,那么不需要設(shè)置
self.streamer.httpHeaders = @{@"Referer" : @"kevinrefer"};

4.2 設(shè)置音頻路徑

[self.streamer resetAudioURL:self.filepath];  //音頻路徑需遵從以下規(guī)則

KVAudioStreamer通過音頻路徑來進(jìn)行本地以及網(wǎng)絡(luò)文件的區(qū)分,所以務(wù)必遵從該規(guī)則:如果是本地文件,需以file://開頭,網(wǎng)絡(luò)文件需以http開頭,如果音頻資源是https,開發(fā)者可以自行修改http請(qǐng)求文件中的代碼,KVAudioStreamer使用NSURLSession作為網(wǎng)絡(luò)請(qǐng)求框架,處理網(wǎng)絡(luò)請(qǐng)求的代碼全部封裝在這里,無需改動(dòng)其他代碼:

網(wǎng)絡(luò)請(qǐng)求文件

4.3 播放控制


  • 播放
[self.streamer play];
  • 定點(diǎn)播放
[self.streamer playAtTime:60];
  • 暫停
[self.streamer pause];
  • seek
[self.streamer seekToTime:60];
  • 停止
[self.streamer stop];
  • 設(shè)置音量
self.streamer.volume = 0.5;
  • 設(shè)置倍速
self.streamer.playRate = 0.5;

4.4 代理通知

KVAudioStreamer使用代理事件進(jìn)行事件通知,總共有六個(gè)代理方法。


  • 播放狀態(tài)改變通知,將會(huì)在這個(gè)代理方法里面接收到流媒體播放過程的各種狀態(tài)變化。
- (void)audioStreamer:(KVAudioStreamer*)streamer playStatusChange:(KVAudioStreamerPlayStatus)status;

所有的狀態(tài),如下所示:

typedef NS_ENUM(NSInteger, KVAudioStreamerPlayStatus) {
    KVAudioStreamerPlayStatusIdle,  //閑置狀態(tài)
    KVAudioStreamerPlayStatusBuffering, //緩沖中
    KVAudioStreamerPlayStatusPlaying,   //播放
    KVAudioStreamerPlayStatusPause, //暫停
    KVAudioStreamerPlayStatusFinish,  //完成播放
    KVAudioStreamerPlayStatusStop  //停止
};
  • 音頻時(shí)長(zhǎng)改變通知,KVAudioStreamer內(nèi)部計(jì)算時(shí)長(zhǎng)使用了三種方法,只有一種能夠拿到確切的時(shí)長(zhǎng),如果獲取不到將會(huì)使用另外兩種方法進(jìn)行計(jì)算,得出的為近似的音頻時(shí)長(zhǎng)。
- (void)audioStreamer:(KVAudioStreamer *)streamer durationChange:(float)duration;

近似時(shí)長(zhǎng)通知,注意:該方法有可能調(diào)用多次。

- (void)audioStreamer:(KVAudioStreamer *)streamer estimateDurationChange:(float)estimateDuration;
  • 播放進(jìn)度通知,內(nèi)部使用定時(shí)器監(jiān)聽播放進(jìn)度。
- (void)audioStreamer:(KVAudioStreamer *)streamer playAtTime:(long)location;
  • 緩存完成通知,如果開啟了緩存功能,并且文件完整緩存成功,將會(huì)回調(diào)這個(gè)方法,返回YES,將會(huì)刪除該緩存文件。
- (BOOL)audioStreamer:(KVAudioStreamer *)streamer cacheCompleteWithRelativePath:(NSString*)relativePath cachepath:(NSString*)cachepath;
  • 錯(cuò)誤通知,內(nèi)部報(bào)錯(cuò)將會(huì)回調(diào)該方法。
- (void)audioStreamer:(KVAudioStreamer *)streamer didFailWithErrorType:(KVAudioStreamerErrorType)errorType msg:(NSString*)msg error:(NSError*)error

4.5 使用注意事項(xiàng)

由于KVAudioStreamer使用了定時(shí)器進(jìn)行播放時(shí)長(zhǎng)監(jiān)聽,所以在適當(dāng)(不使用)的時(shí)候手動(dòng)釋放流媒體播放器。

- (void)dealloc {
    [self.streamer releaseStreamer];    //釋放流媒體
    self.streamer = nil;
}

5 寫在最后

造輪子的確很辛苦,過程中遇到了很多問題,撓破頭皮才一一解決,不過最后還是沒能解決m4a文件的播放seek問題,等待以后有空閑時(shí)間再慢慢研究。
KVAudioStreamer使用到的核心技術(shù):

  • AudioToolBox框架
  • GCD串行隊(duì)列,音頻數(shù)據(jù)解析都在串行隊(duì)列中順序執(zhí)行
  • 線程鎖(pthread_mutex_t),用于解決多線程資源共享問題
  • 線程條件變量(pthread_cond_t),由于音頻數(shù)據(jù)的解析后是在子線程連續(xù)填充緩存區(qū)的,在AudioQueue還未播放完成時(shí)緩存區(qū)是無法使用的,線程就必須等待,所以使用了條件變量進(jìn)行線程的等待以及喚醒,避免過多的CPU資源占用

以下文章為本人在學(xué)習(xí)AudioToolBox時(shí)的參考文章,當(dāng)然,里面或多或少有些坑,再次感謝這些作者的付出,往后有時(shí)間將會(huì)寫一篇文章完整講解AudioToolBox的使用。

http://www.cocoachina.com/ios/20170721/19969.html
http://www.lxweimin.com/p/05b6e9bc4060
http://blog.csdn.net/cairo123/article/details/53839980

項(xiàng)目git地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

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