Android MediaCodec硬解码、ffmpeg软解码,兼顾机型一致性和性能

MediaCodec硬解

首先考虑使用MediaCodec硬解码,硬解码的代码谷歌的文档很详细,主要分为异步模式、同步模式。至于解码的输出,如果是解码到文件中,可以提取outputBuffer后写入文件;如果是用于显示,推荐初始化MediaCodec的时候传入Surface:

decoder.configure(mediaFormat, surface, null, 0);

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

decoder.releaseOutputBuffer(outputBufferIndex, true);

这样Surface会与codec绑定起来,解码后的buffer直接在底层用于显示到Surface上,无需业务层数组拷贝,效率最高,同时这种情况下outputBuffer中获取到的buffer也为null。decoder.releaseOutputBuffer是解码器真正解码渲染的时候。

不过有遇到某些h264流在某些手机上,硬解码丢帧的情况,调试发现很多帧在decoder.dequeueOutputBuffer(bufferInfo, 0);会返回-2,也就是format changed,导致这些帧解码失败。故思考mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);这边将COLOR_FormatSurface换成COLOR_QCOM_FormatYUV420SemiPlanar,再将解码出来的yuv,通过OpenGLES2.0或者EGL,显示在Surface上,是否能规避这个问题。经过一番尝试,上述出现问题的摄像头和手机的配合,不管KEY_COLOR_FORMAT换成哪个,依然format changed造成丢帧。为了一致性的体验,决定转向软解。

ffmpeg软解

使用ffmpeg软解不需要我们在业务代码里拆帧,只要将流一股脑的给ffmpeg就行,ffmpeg自己有av_read_frame函数可以拆帧,我们主要做好队列工作。网上大部分ffmpeg软解的代码都是从文件读流,直接将文件的path传给ffmpeg最简单,但是要使用摄像头这样的buffer输入的话,需要用到avio_alloc_context、av_probe_input_buffer。

显示部分,可以将Surface传给ffmpeg,转为ANativeWindow,解码后的yuv通过sws_scale转为rgb,再逐行复制到ANativeWindow里就可以显示。

然而遇到了喜闻乐见的软解性能问题。摄像头码率提到10M后,解码成为瓶颈,每一帧的解码需要将近30ms,加上渲染时长,导致帧率低于摄像头的原始帧率30,出现画面延迟,帧率不足,很快解码buffer队列就满了,队列满了就要丢弃老数据,于是画面就会出现马赛克。这里使用三星S8加上帧率30,10M码率的摄像头做测试:解码后不渲染,帧率34-38;解码后同一个线程渲染,帧率22,CPU占用始终125%左右

可见光解码的话,还是可以保证帧率的,但是加上渲染就不行了。

ffmpeg软解,解码和渲染异步

于是尝试将解码线程和渲染线程独立出来,尽量榨干CPU。需要注意的是avcodec_send_packet和avcodec_receive_frame必须同步调用,就是send后必须马上receive,等receive返回不为0后,才能继续send,否则send会失败。

这两个函数名设计的让人容易产生误解,以为ffmpeg自己维护了一个帧队列,然后可以在两个线程中分别send和receive,其实是错的,ffmpeg应该只维护了一个数组,数组为空取完后才可以再次send。

所以需要send和receive在同一个线程同步执行,receive后将AVFrame放到队列里;另一个线程从队列里取帧,进行sws_scale后,绘制rgb到NativeWindow上。解码+渲染,CPU占用上升到180%,性能提升到帧率29,但是一会儿CPU会发热降频,解码耗时增大,帧率掉到20,这性能还是没达到要求。

ffmpeg硬解

于是尝试使用ffmpeg硬解。虽然测试过某些摄像头在某些手机上调用MediaCodec硬解会出现format changed导致丢帧的现象,并且ffmpeg实际上也是使用MediaCodec实现的硬解,但是本着不试一试怎么知道的精神,决定尝试ffmpeg硬解。

configure需要做如下配置:

--enable-jni

--enable-mediacodec

--enable-decoder=h264_mediacodec

--enable-hwaccel=h264_mediacodec

--target-os=android(这条如果没有,会报错jni not found)

由于我之前编译过ijkplayer,有一个中间步骤是编译ffmpeg,于是图方便使用ijk的工程来编译,发现加上上述configure后总是报jni not found,后来发现需要ijkplayer/android/contrib/tools/do-compile-ffmpeg.sh中将FF_CFG_FLAGS="$FF_CFG_FLAGS --target-os=linux"改为--target-os=android编译好新的ffmpeg后尝试,创建解码器的时候,需要使用AVCodec *pCodec = avcodec_find_decoder_by_name("h264_mediacodec");代替掉AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

硬解确实解码很快,每一帧的解码时间缩短为1ms左右,但是发现画面卡顿,CPU占用依然很高,一看log,发现瓶颈变为sws_scale,硬解后的yuv进行sws_scale计算,效率非常低,使用SWS_BILINEAR算法,一帧的scale需要68ms,很快渲染队列就满了。网上查到可以使用libyuv通过neon硬件加速替换掉sws_scale函数,也有使用OpenGL硬件加速渲染yuv到Surface的方案。原因是软解出来的yuv,是YUV420P,sws_scale效率高,一帧只需要12ms,硬解出来的yuv是NV12,sws_scale效率很低。

ffmpeg多线程软解

尝试多线程软解,继续榨干CPU,解决解码性能瓶颈。配置多线程解码:pCodecCtx->thread_count = 8;有个坑是设置pCodecCtx->thread_type = FF_THREAD_SLICE;后,反而多线程无效,注释掉后多线程解码生效,性能飙升,帧率直接取决于喂数据的速度

加快喂数据,能吃掉更多CPU资源

加快喂数据后的帧率直接上去了

总结

在取舍了机型一致性、性能后,最终方案:ffmpeg多线程软解,通过sws_scale转换yuv到rgb显示在ANativeWindow上。

优化空间:sws_scale替换为libyuv,或者OpenGL,进一步降低能耗

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

推荐阅读更多精彩内容