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

在iOS上颤练,播放音頻一般使用AVAudioPlayer進(jìn)行音頻播放她倘,但是AVAudioPlayer并不支持流媒體播放脯厨,換言之铅祸,AVAudioPlayer只能播放本地音頻,當(dāng)遇到網(wǎng)絡(luò)音頻的時(shí)候合武,都是先下載到整個(gè)音頻文件临梗,然后再播放(微信語(yǔ)音就是先下載再播放),但是有些使用場(chǎng)景要求音頻使用流媒體播放稼跳,提高用戶體驗(yàn)盟庞,像音樂(lè)軟件,在線音頻教育軟件的一些音頻課程都要求流媒體播放汤善。
項(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ù)都沒(méi)有放上去):

//AudioFileStreamID竹揍,用于音頻數(shù)據(jù)解析
extern OSStatus AudioFileStreamOpen ();  //打開文件流敬飒,獲取文件流id
extern OSStatus AudioFileStreamParseBytes();  //解析數(shù)據(jù),將會(huì)傳入一個(gè)C語(yǔ)言方法芬位,獲取解析結(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è)方法配合使用昧碉,來(lái)控制播放速率
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的使用問(wèn)題將會(huì)在往后另一篇文章進(jìn)行說(shuō)明哥牍,造輪子的過(guò)程是痛并快樂(lè)著的毕泌,踩過(guò)無(wú)數(shù)的坑,在填坑的過(guò)程中也在不斷成長(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格式文件無(wú)法播放仔夺,另外對(duì)m4a音頻文件只能做到流播放,無(wú)法使用seek操作攒砖,后續(xù)將會(huì)研究如何解決缸兔,如果開發(fā)者不需要播放m4a文件,那么KVAudioStreamer會(huì)是一個(gè)不錯(cuò)的選擇吹艇。
支持緩存功能惰蜜,針對(duì)網(wǎng)絡(luò)文件,在完整緩存完畢將會(huì)通過(guò)代理事件通知開發(fā)者緩存成功受神,攜帶文件路徑供開發(fā)者下一步操作(注:僅在完整緩存后才會(huì)自動(dòng)緩存抛猖,如果播放網(wǎng)絡(luò)文件時(shí)還未緩存成功就使用了seek操作,那么就不算完整緩存,因?yàn)閮?nèi)部使用了斷點(diǎn)下載财著,如果seek后便無(wú)法保證文件的完整性养交,如果文件已經(jīng)完整緩存成功,重復(fù)seek不會(huì)產(chǎn)生重復(fù)的網(wǎng)絡(luò)請(qǐng)求瓢宦,幫助用戶節(jié)省流量)碎连。
定點(diǎn)播放也是KVAudioStreamer的一大特色,支持從音頻的某個(gè)位置開始播放驮履,用于播放位置記憶功能鱼辙。
多倍率播放,這也是音頻播放的一個(gè)常用功能玫镐,建議區(qū)間(0,5)倒戏,其實(shí)2倍速度播放,出來(lái)的聲音就已經(jīng)很鬼畜了恐似。

3 如何集成

該項(xiàng)目已提交到github開源社區(qū)杜跷,并且提供cocoapod功能,可以直接通過(guò)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扣泊,音樂(lè)資源在阿里云OSS開啟了防盜鏈,需要在這里設(shè)置referer嘶摊,如果沒(méi)有防盜鏈延蟹,那么不需要設(shè)置
self.streamer.httpHeaders = @{@"Referer" : @"kevinrefer"};

4.2 設(shè)置音頻路徑

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

KVAudioStreamer通過(guò)音頻路徑來(lái)進(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)求的代碼全部封裝在這里上枕,無(wú)需改動(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è)代理方法里面接收到流媒體播放過(guò)程的各種狀態(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 寫在最后

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

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

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

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

項(xiàng)目git地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末映皆,一起剝皮案震驚了整個(gè)濱河市租幕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖拾酝,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕少,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蒿囤,警方通過(guò)查閱死者的電腦和手機(jī)客们,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)材诽,“玉大人底挫,你說(shuō)我怎么就攤上這事≡朗兀” “怎么了凄敢?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)湿痢。 經(jīng)常有香客問(wèn)我涝缝,道長(zhǎng),這世上最難降的妖魔是什么譬重? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任拒逮,我火速辦了婚禮,結(jié)果婚禮上臀规,老公的妹妹穿的比我還像新娘滩援。我一直安慰自己,他們只是感情好塔嬉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布玩徊。 她就那樣靜靜地躺著,像睡著了一般谨究。 火紅的嫁衣襯著肌膚如雪恩袱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天胶哲,我揣著相機(jī)與錄音畔塔,去河邊找鬼。 笑死鸯屿,一個(gè)胖子當(dāng)著我的面吹牛澈吨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寄摆,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谅辣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了冰肴?” 一聲冷哼從身側(cè)響起屈藐,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤榔组,失蹤者是張志新(化名)和其女友劉穎熙尉,沒(méi)想到半個(gè)月后联逻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡检痰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年包归,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铅歼。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡公壤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出椎椰,到底是詐尸還是另有隱情厦幅,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布慨飘,位于F島的核電站确憨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瓤的。R本人自食惡果不足惜休弃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圈膏。 院中可真熱鬧塔猾,春花似錦、人聲如沸稽坤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尿褪。三九已至睦擂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茫多,已是汗流浹背祈匙。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留天揖,地道東北人夺欲。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像今膊,于是被迫代替她去往敵國(guó)和親些阅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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