在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播放音頻是一種生產(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的介紹文檔,關(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擁有以下功能:
- 支持多種音頻格式(mp3澡谭、flac愿题、wav、m4a...);
- 支持緩存功能;
- 支持定點(diǎn)播放蛙奖;
- 多倍率播放潘酗。
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地址
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)其他代碼:
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