Android我还可以相信你多少系列文章二之音视频播放

音频视频播放在现在的应用里面很常见,传统应用发展到一定阶段多少会引入音视频资源,特别是现在短视频被看作下一个增长爆发点,和之相关的创业层出不穷,作为开发者如何进行音视频技术选型非常关键

MediaPlayer和VideoView给我们提供了非常方便的播放音视频的能力,几乎不需要要写几行代码就可以完成。我们也可以使用MediaPlayer结合SurfaceView或者TextureView来实现视频播放,本质和VideoView是一样的,不过有更多的灵活性。

正因为封装性太强,意味着定制化变弱。MediaPlayer提供的setDataSource方法支持http,file,content等协议,但仍然无法应对复杂的需求。所以更灵活的AudioTrack的出现,可以让我们直接传送解码后的byte[]给他,带来的问题就是自己要做解码。解码不是件简单的事情,往往我们利用MediaCodec(Android4.1增加)或者外部解码库(比如ffmpeg)来实现。自己来实现解码要特别注意不要丢失了硬件加速,音频软解码还好,视频解码软解码对CPU压力会大很多。

在做音视频业务的时候,经常会遇到这样几个问题需要设置代理,或者边播边缓存,缓存加密,失败重试,网络优化等等

因为我们无法干涉MediaPlayer的网络请求部分,所以一般会将原始的播放地址http://xxx.com/playurl转换成本机代理地址http://127.0.0.1:port?url=htt...,这样MediaPlayer就会来请求本机port端口上面起的一个代理服务,在这个代理端可以做很多优化逻辑,比如给真正发往服务端的请求加上代理;将请求到的数据写入磁盘缓存,这个代理端可以根据磁盘缓存来按需请求服务端(使用http的Range参数);还有一些失败重试等网络优化手段。这个代理层还有个特别的意义甚至可以接管webview里面的audio和video标签请求。

这种实现方式在实际运行中偶尔会出现本机代理无法启动的情况,原因是Socket无法bind到指定端口,往往我们会在bind的时候指定让系统来分配一个可用端口,所以这种失败情况很有可能是root手机或者一些安全管理软件禁用了权限。

特别再说下边播边缓存的实现,缓存文件允许空洞,每个缓存文件配备另外一个内容索引文件,MediaPlayer本身会根据解码情况发出多个带Range的请求,根据内容索引文件来确定当前请求从文件哪个位置读,接下去多少字节从文件读,多少字节从网络读,网络读的部分同时写回文件以保证下次请求可以复用,这样就实现了一个边播边缓存的逻辑,甚至我们还可以给本地缓存文件进行加密。同时这个缓存文件的加载百分比可以用来做UI界面上面的缓冲进度,监控下载速度进行网络请求优化。

2.MediaPlayer的Looper。新手往往可能不关心MediaPlayer的实现,打开它的构造器前面几行代码我们就会看到他默认使用的是当前线程的Looper,如果当前线程不是个Looper线程则使用MainLooper。这一点比较重要,因为我们知道即使MediaPlayer运行在Service里面,实际上还在跑在主线程,这样的结果导致后续所有的MediaPlayer回调操作都跑在主线程,这可能是隐藏的一个定时炸弹。


更优雅的设计我们建议将MediaPlayer的回调和主动操作(stop,reset等操作)都放入work线程,操作的串行化是种最简单的设计,也是最有效的设计。大概的代码形式是这样的:


MediaPlayer在PlayHandlerThread里面初始化,就保证了他里面使用的Looper也是这个PlayHandlerThread的,这样回调就都会在这个线程触发,同时我们也在这个线程里面做setDataSource等主动操作。

3.视频播放本质上也是用MediaPlayer实现的,所以读取数据上面没有特别差异。现在比较热的小视频需要显示在列表页面支持滚动播放一个视频,点击在新页面继续观看,一般采用MediaPlayer+TextureView来实现,MediaPlayer可以采用全局定义唯一一个,只是不同时刻把内容绑定显示在不同的TextureView上而已。

4.MediaPlayer最大的问题还是在于其兼容性。从我们的经验来看可能会有这些问题:音频格式支持不全(ape,wma等原生系统不支持),未缓冲完不开始播放,播放过程中突然没有声音,播放存在跳帧,mediaserver

died;视频播放只有声音没有画面,视频格式兼容性差无法播放等。这些问题在系统基础上基本无法解决。

最头疼的问题是MediaPlayer返回的errorcode很多都是厂家扩展出来的,文档上面提供的几个值基本也是表意不清到底什么问题。这给排查问题带来很大麻烦。最最头疼的是MediaPlayer的EventHandler里面处理异常直接导致程序崩溃,比如像这样:

11-0413:43:08.966: E/AndroidRuntime(26482): java.lang.RuntimeException: failurecode: -3211-0413:43:08.966: E/AndroidRuntime(26482):    at android.media.MediaPlayer.invoke(MediaPlayer.java:664)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.media.MediaPlayer.getInbandTrackInfo(MediaPlayer.java:1692)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.media.MediaPlayer.scanInternalSubtitleTracks(MediaPlayer.java:1851)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.media.MediaPlayer.access$600(MediaPlayer.java:529)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:2198)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.os.Handler.dispatchMessage(Handler.java:102)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.os.Looper.loop(Looper.java:137)11-0413:43:08.966: E/AndroidRuntime(26482):    at android.app.ActivityThread.main(ActivityThread.java:4998)11-0413:43:08.966: E/AndroidRuntime(26482):    at java.lang.reflect.Method.invokeNative(Native Method)11-0413:43:08.966: E/AndroidRuntime(26482):    at java.lang.reflect.Method.invoke(Method.java:515)11-0413:43:08.966: E/AndroidRuntime(26482):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)11-0413:43:08.966: E/AndroidRuntime(26482):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)11-0413:43:08.966: E/AndroidRuntime(26482):    at dalvik.system.NativeStart.main(Native Method)

除了反射替换MediaPlayer里面的EventHandler来抓住异常,其他没啥特别好的办法。

遇到这么多问题开发者只能另投他路。市面上采用自解码的方案也很多,比较主流的是使用MediaCodec和ffmpeg,ffmpeg更是因为MediaCodec版本限制原因,加上本来就闻名遐迩,被很多开发者青睐。主流的音视频播放器大部分都是在这个上面进行改造的。

ExoPlayer:https://github.com/google/Exo...,作为google在MediaCodec的封装也是不错的推荐,相比自己要去抽取ffmpeg代码进行android适配编译来得容易得多

ffmepg:当然也有一些现成的实现:https://github.com/search?o=d...,最出名的当是ijkplayer,哔哩哔哩出品,跨平台还有弹幕。做视频弹幕真是开箱即用。ffmpeg功能强大,唯一的缺点就是软解码,这也是他兼容性好的原因,我们知道硬解码依赖各个厂家硬件实现兼容性自然就下降了。

在使用自解码的时候,我们建议将自己的MediaPlayer封装成Android高版本上面添加的接口一样:

/** * Sets the data source (MediaDataSource) to use. * *@paramdataSource the MediaDataSource for the media you want to play *@throwsIllegalStateException if it is called in an invalid state *@throwsIllegalArgumentException if dataSource is not a valid MediaDataSource */publicvoidsetDataSource(MediaDataSource dataSource)throwsIllegalArgumentException, IllegalStateException{    _setDataSource(dataSource);}

这样做的好处是所有实现都对MediaPlayer透明,我们只需要定义好MediaDataSource接口,后面只需要专注于实现就可以了,比如HttpDataSource,FileDataSource,MemoryDataSource等。

或许自解码会引入更多的不确定性,但是这一步迟早都要迈出去。推荐小型app或者需求不强的产品使用系统解码,在我上面提到的一些解决思路上进行改进应该能满足绝大部分场景。而那些音视频作为主业务的产品则不得不面对自解码来提高兼容性。

于是我们又在造轮子了;)

更多文章请关注微信公众号:anzhuozhimei

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

推荐阅读更多精彩内容