KVAudioStreamer - 基于AudioToolBox的开源音频流媒体播放器

在iOS上,播放音频一般使用AVAudioPlayer进行音频播放,但是AVAudioPlayer并不支持流媒体播放,换言之,AVAudioPlayer只能播放本地音频,当遇到网络音频的时候,都是先下载到整个音频文件,然后再播放(微信语音就是先下载再播放),但是有些使用场景要求音频使用流媒体播放,提高用户体验,像音乐软件,在线音频教育软件的一些音频课程都要求流媒体播放。
项目git地址

1 开发初衷

目前开源的流媒体播放器有很多,例如AudioStreamer、DOUAudioStreamer,但是这两个开源库或多或少都有点瑕疵,并且都是在很久以前开发的,并不满足我的要求,所以决定自己开发一个流媒体播放器,并回馈一下开源社区。

2 KVAudioStreamer介绍

KVAudioStreamer采用AudioToolBox框架开发,使用C接口开发将更容易自主定制,当然,相对的也增加了开发难度。KVAudioStreamer内部代码结构清晰,由于开源时间晚于AudioStreamer和DOUAudioStreamer,所以使用的API都是最新的。

2.1 AudioQueue介绍

我使用的是AudioQueue进行音频播放,AudioToolBox播放音频主要涉及到以下API(仅仅列出,连参数都没有放上去):

//AudioFileStreamID,用于音频数据解析
extern OSStatus AudioFileStreamOpen ();  //打开文件流,获取文件流id
extern OSStatus AudioFileStreamParseBytes();  //解析数据,将会传入一个C语言方法,获取解析结果
extern OSStatus AudioFileStreamClose();  //关闭文件流
extern OSStatus AudioFileStreamGetProperty();  //获取文件信息
//AudioQueueBufferRef,用于音频缓存数据存储
extern OSStatus AudioQueueEnqueueBuffer();  //放进音频队列
extern OSStatus AudioQueueFreeBuffer();  //释放缓存区数据
//AudioQueueRef,用于音频播放
extern OSStatus AudioQueueStart();  //开始播放
extern OSStatus AudioQueuePause();  //暂停播放
extern OSStatus AudioQueueStop();  //停止播放
extern OSStatus AudioQueueDispose();  //释放音频队列
//以下两个方法配合使用,来控制播放速率
extern OSStatus AudioQueueSetProperty();  //设置属性
extern OSStatus AudioQueueSetParameter();  //设置参数

AudioQueue的播放流程如下所示:

AudioQueue工作原理

从上图可以看出,AudioQueue播放音频是一种生产者-消费者模式,所以KVAudioStreamer也采用生产者-消费者的设计模式进行框架搭建,内部代码逻辑清晰,充分解耦,方便开发者学习以及修改(这一点自认为优于AudioStreamer和DOUAudioStreamer)。
下图是KVAudioStreamer的代码结构,我已经将实现代码抽象成生产者和消费者:
KVAudioStreamer代码结构

本篇文章仅为KVAudioStreamer的介绍文档,关于AudioToolBox的使用问题将会在往后另一篇文章进行说明,造轮子的过程是痛并快乐着的,踩过无数的坑,在填坑的过程中也在不断成长,文章最后将会贴出当时学习AudioToolBox的参考文章,同时也感谢这些作者的付出。

2.2 功能介绍

KVAudioStreamer拥有以下功能:

  1. 支持多种音频格式(mp3、flac、wav、m4a...);
  2. 支持缓存功能;
  3. 支持定点播放;
  4. 多倍率播放。

KVAudioStreamer支持多种音频格式,经测试,目前音频格式中仅ape格式文件无法播放,另外对m4a音频文件只能做到流播放,无法使用seek操作,后续将会研究如何解决,如果开发者不需要播放m4a文件,那么KVAudioStreamer会是一个不错的选择。
支持缓存功能,针对网络文件,在完整缓存完毕将会通过代理事件通知开发者缓存成功,携带文件路径供开发者下一步操作(注:仅在完整缓存后才会自动缓存,如果播放网络文件时还未缓存成功就使用了seek操作,那么就不算完整缓存,因为内部使用了断点下载,如果seek后便无法保证文件的完整性,如果文件已经完整缓存成功,重复seek不会产生重复的网络请求,帮助用户节省流量)。
定点播放也是KVAudioStreamer的一大特色,支持从音频的某个位置开始播放,用于播放位置记忆功能。
多倍率播放,这也是音频播放的一个常用功能,建议区间(0,5),其实2倍速度播放,出来的声音就已经很鬼畜了。

3 如何集成

该项目已提交到github开源社区,并且提供cocoapod功能,可以直接通过git clone进行项目下载,里面包含一个完整的demo演示,demo里面同时提供了音频后台播放锁屏控制的解决方案。

3.1 git地址

git地址

3.2 cocoapod集成

使用以下pod命令集成

pod 'KVAudioStreamer', ' 1.0.0'

4 如何使用

KVAudioStreamer的API设计遵从命名规范,坚持一切从简的设计原则,所以使用简单,上手快速。

4.1 初始化

self.streamer = [[KVAudioStreamer alloc] init];
self.streamer.delegate = self;
self.streamer.cacheEnable = YES;    //开启缓存功能
//设置httpheader,音乐资源在阿里云OSS开启了防盗链,需要在这里设置referer,如果没有防盗链,那么不需要设置
self.streamer.httpHeaders = @{@"Referer" : @"kevinrefer"};

4.2 设置音频路径

[self.streamer resetAudioURL:self.filepath];  //音频路径需遵从以下规则

KVAudioStreamer通过音频路径来进行本地以及网络文件的区分,所以务必遵从该规则:如果是本地文件,需以file://开头,网络文件需以http开头,如果音频资源是https,开发者可以自行修改http请求文件中的代码,KVAudioStreamer使用NSURLSession作为网络请求框架,处理网络请求的代码全部封装在这里,无需改动其他代码:

网络请求文件

4.3 播放控制


  • 播放
[self.streamer play];
  • 定点播放
[self.streamer playAtTime:60];
  • 暂停
[self.streamer pause];
  • seek
[self.streamer seekToTime:60];
  • 停止
[self.streamer stop];
  • 设置音量
self.streamer.volume = 0.5;
  • 设置倍速
self.streamer.playRate = 0.5;

4.4 代理通知

KVAudioStreamer使用代理事件进行事件通知,总共有六个代理方法。


  • 播放状态改变通知,将会在这个代理方法里面接收到流媒体播放过程的各种状态变化。
- (void)audioStreamer:(KVAudioStreamer*)streamer playStatusChange:(KVAudioStreamerPlayStatus)status;

所有的状态,如下所示:

typedef NS_ENUM(NSInteger, KVAudioStreamerPlayStatus) {
    KVAudioStreamerPlayStatusIdle,  //闲置状态
    KVAudioStreamerPlayStatusBuffering, //缓冲中
    KVAudioStreamerPlayStatusPlaying,   //播放
    KVAudioStreamerPlayStatusPause, //暂停
    KVAudioStreamerPlayStatusFinish,  //完成播放
    KVAudioStreamerPlayStatusStop  //停止
};
  • 音频时长改变通知,KVAudioStreamer内部计算时长使用了三种方法,只有一种能够拿到确切的时长,如果获取不到将会使用另外两种方法进行计算,得出的为近似的音频时长。
- (void)audioStreamer:(KVAudioStreamer *)streamer durationChange:(float)duration;

近似时长通知,注意:该方法有可能调用多次。

- (void)audioStreamer:(KVAudioStreamer *)streamer estimateDurationChange:(float)estimateDuration;
  • 播放进度通知,内部使用定时器监听播放进度。
- (void)audioStreamer:(KVAudioStreamer *)streamer playAtTime:(long)location;
  • 缓存完成通知,如果开启了缓存功能,并且文件完整缓存成功,将会回调这个方法,返回YES,将会删除该缓存文件。
- (BOOL)audioStreamer:(KVAudioStreamer *)streamer cacheCompleteWithRelativePath:(NSString*)relativePath cachepath:(NSString*)cachepath;
  • 错误通知,内部报错将会回调该方法。
- (void)audioStreamer:(KVAudioStreamer *)streamer didFailWithErrorType:(KVAudioStreamerErrorType)errorType msg:(NSString*)msg error:(NSError*)error

4.5 使用注意事项

由于KVAudioStreamer使用了定时器进行播放时长监听,所以在适当(不使用)的时候手动释放流媒体播放器。

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

5 写在最后

造轮子的确很辛苦,过程中遇到了很多问题,挠破头皮才一一解决,不过最后还是没能解决m4a文件的播放seek问题,等待以后有空闲时间再慢慢研究。
KVAudioStreamer使用到的核心技术:

  • AudioToolBox框架
  • GCD串行队列,音频数据解析都在串行队列中顺序执行
  • 线程锁(pthread_mutex_t),用于解决多线程资源共享问题
  • 线程条件变量(pthread_cond_t),由于音频数据的解析后是在子线程连续填充缓存区的,在AudioQueue还未播放完成时缓存区是无法使用的,线程就必须等待,所以使用了条件变量进行线程的等待以及唤醒,避免过多的CPU资源占用

以下文章为本人在学习AudioToolBox时的参考文章,当然,里面或多或少有些坑,再次感谢这些作者的付出,往后有时间将会写一篇文章完整讲解AudioToolBox的使用。

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

项目git地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容