在2019年的一个大项目中,有一个功能模块让笔者感触颇深,那就是实时音视频的预览,当然这不是普通的开开直播,画面出来了就完了那么简单,如果你是一个开发者,那么你肯定知道同样大小的一张图片里,色彩丰富的图片的像素点肯定要比颜色单调的像素点要更多且更复杂,这就涉及到了编码算法对图像内冗余数据的宏观计算,颜色丰富的肯定要比色彩单调的处理起来更复杂,原理就不做阐述了,有兴趣的小伙伴可以去某度一下,帧内预测与视频编码原理等。
在这个项目中呢,需求是监管平台在后台可以实时的预览车辆上设备的前后两路摄像头,是同时开启两路摄像头,并且能实时进行对讲和监听,视频的质量也是要求非常的高,预计最少在1-2M/s码率及以上,这里跟大家说一下这个需求与普通室内的某些直播软件的区别。
先说普通室内的直播,这一类直播通常是在一个房间内,因为没有那些蓝天、白云、绿树、过往的车辆等,它所捕捉到的画面是非常的单一的,因为一般只有主播在活动,其他是相对静止的,在这种环境下计算帧内冗余数据是非常快得,且颜色复杂程度不高,所以其帧本身质量也相对较小,自然处理速度也稍快,如果排除网络问题,那他们的延时肯定是与实际动作相接近的,当然如果观看的是蓝光画质,那对网络也是非常依赖的。
接下来说笔者项目中的预览画面情况,首先大家应该可以想象,在车上肯定是常有颠簸并且广角摄像头所捕捉的画面是非常的大的,因为汽车行驶的过程中,其他静止的物体都相对运动了,比如天上的白云、高楼大厦、过往的行人车辆等,这就导致了单帧数据的质量是非常高的,且颜色程度非常复杂,也变相表示着处理它们将耗费更多的机器性能与时间,如果对质量有苛刻的要求,要达到实际需求的水平,这将是对工程师本身技术水平的考验,也将是对机器、硬件性能的考验。
刚开始笔者采用的是MediaCodeC硬编码,基于MT8735的芯片,单开其中一路最高只能达到1000kpb/s,室内跑起来问题不大,画面很流畅且画质也还行,于是装车去实际路上跑,结果只要是汽车颠簸或经过阳光与阴暗交替的地方就会出现超级多的马赛克,但加大码率没有实际效果,这个是MediaCodeC硬编码的坑,后面会为小伙伴们介绍。加大码率后不仅没有效果,反倒画面会变卡,单一路就这样了,更别说直接开两路了。
于是我在X264官网上下载了开源编码器的代码,简单的熟悉了一下API,就开始上手,因为笔者有C++基础,所以整个过程非常顺利,笔者也顺利把库编译好,并且从NDK C++里把接口透出来。使用X264软编码加大码率是有非常明显的效果的,但是个人感觉开一路还凑合,两路同时开的话必炸!于是我把cpu的使用情况查了一下,发现这个X264软编码也是异常的消耗性能,然后我去MT查了下关于芯片的性能方面,发现MT好像有专门提供一套用于这类实时媒体要求较高的芯片,MT8665芯片,因为价格问题公司也没有多说什么,然后整个项目几乎都已经没什么大问题了,主要就卡在视频这一块,无法推动下去。
个人感觉普通的芯片是无法满足这类高性能要求的,尤其是这个项目的要求和标准也都是当地政府定的,所以这是个很纠结的问题,它高要求的视频质量暂且不说,两路视频同时编码运算处理,那是相当耗费性能的,更别说机器发烫啥的。虽然项目进入了僵局,笔者也有换个上班环境的打算,但笔者觉得还是要写一篇心得,分享给遇到类似问题的小伙伴们,让你们也有个底,不要因为用错了东西,导致后续耗费更大的人力物力!本篇内容纯手打,要是觉得笔者写得不错的,给个赞支持一下原创作品!
1.出处介绍
先简单的介绍一下MediaCodeC以及X264开源编码器
MediaCodeC由Google Android 4.1, API16 版本首次推出
X264由X264 Team的10名左右的成员在2003年发起,经过几年的开发,x264逐渐成为了最好的视频编码器
2.参数介绍
MediaCodeC硬编码下常用的几个参数
MediaFormat.KEY_BIT_RATE 码率
MediaFormat.KEY_FRAME_RATE 帧率
MediaFormat.KEY_COLOR_FORMAT 输入格式
MediaFormat.KEY_I_FRAME_INTERVAL 关键帧间隔
MediaFormat.KEY_BITRATE_MODE 码率模式 (在MT8735上没有实际作用)
MediaFormat.KEY_PROFILE 视频品质 (这个KEY个人觉得没什么用,问题还多,后面会介绍具体原因)
MediaFormat.KEY_LEVEL 视频复杂程度,该值越大相对的码率、分辨率等也会较高
X264软编码下常用的几个参数
b_repeat_headers 是否在每个关键帧前面添加SPS/PPS,0不加,1加
b_sliced_threads 并行或切片编码,为1时单帧图像被切分成多片,为0并行且x264自动计算编码线程
i_width 编码后的输出画面分辨率宽
i_height 编码后的输出画面分辨率高
i_bitrate 输出视频码率
i_rc_method 码率模式选择,ABR模式(平均码率)、CQP(恒定质量)、CRF(恒定码率)
i_fps_num 视频帧率分子
i_fps_den 视频帧率分母
i_keyint_max GOP,关键帧间隔*视频帧率分子,假设2S一个I帧,帧率24,那GOP为48
i_threads 编码线程数,默认数为 i_threads = x264_cpu_num_processors() * (h->param.b_sliced_threads ? 2 : 3) / 2
b_vfr_input ABR模式(平均码率)下达不到设置码率时,将此值置为0
i_qp_constant CQP模式(恒定质量)下,0代表无损压缩,QP值越小编码质量越好,默认23
f_rf_constant CRF模式(恒定码率)下,值越大图像越花,值越小越清晰
f_rf_constant_max CRF模式(恒定码率)下,恒定码率最大值
3.方法与函数介绍
MediaCodeC硬编码常用方法介绍
getInputBuffers 获取输入缓冲区
getOutputBuffers 获取输出缓冲区
dequeueInputBuffer 从输入缓冲区获取有效数据的索引,此方法相同值不同设备表现也不同,问题特别多
queueInputBuffer 向输入缓冲区队列传递具体的长度、偏移等参数
dequeueOutputBuffer 从输出缓冲区获取有效数据的索引
releaseOutputBuffer 释放输出缓冲区
X264软编码常用函数介绍
x264_param_default_preset 设置X264编码器的tune,一共有8种,直播的话默认填zerolatency
x264_param_apply_profile 设置X264编码器的质量,实际项目中baseline、main、high运用较多
x264_encoder_open 打开X264编码器
x264_encoder_encode 进行编码
x264_encoder_close 关闭X264编码器
4.简单的优缺点对比
MediaCodeC硬编码优点:使用非常方便Google已经封装好的现成接口,对于直播质量与门槛要求较低的需求可以从容不迫的处理数据且延时相对较低
MediaCodeC硬编码缺点:同一分API在不同的硬件、芯片上使用时兼容性差,会有各种千奇百怪的问题爆出来极大的增加了开发难度,同时也表现出了他的可移植性差
X264软编码优点:同一份代码可以放在不同的Android芯片上使用,降低了后期开发的成本,对于视频的质量等参数可以有效的把控,维护起来也只有一套代码非常的nice,可移植性非常强
X264软编码缺点:对于新手上手难度过大,需要花很多时间理解其中具体某个函数乃至变量的用途,即使看API也得耗费大量的时间去阅读,编码运算依赖CPU,如果是高强度的运算会导致机器的耗电量明显加大,机器也会发烫等
5.实际运用中踩过的坑
先说说MediaCodeC硬编码,这个API看似Google都已经把该做的都已经做好了,但实际却不尽人意,为何这么说呢?因为使用过的小伙伴们都知道这里面的坑有多少,乃至现在Android都出到10几的版本了,这些问题我估计依然存在,因为Android机型繁杂,所以很多小伙伴抱怨在某个机器上没问题但在另一些机器上直接crash。就拿dequeueInputBuffer这个方法来说,相信被坑的不在少数吧?笔者觉得视频质量一般,于是看了一下API,发现MediaFormat.KEY_PROFILE可以控制视频的质量,于是我把质量设置到high,发现根本没什么用,于是找了一下资料发现这也是个坑爹货,Android7.0以下默认baseline,不管你怎么设置都是默认baseline,于是又找来7.0以上的机器,无济于事,这也是前面提到的为什么加大码率没有实际效果!这让我彻底放弃了MediaCodeC硬编码,继而转向X264、FFMPEG、openH264等这些成熟的开源编码器,因为FFMPEG H264编码最终也是调X264的库,所以笔者直接打算找X264看。
使用X264的过程也很顺利,在clone了源代码之后,我按照X264编译命令写了3个编译架构支持的shell脚本,分别是arm64-v8a、armeabi-v7a、x86三个架构,然后把源码和脚本一同打包到我的Linux服务器,安装完编译插件跟NDK后,执行脚本后编译出了三个架构的点a静态库,在copy到Androidstudio进行so集合编译,都完美通过了。笔者把相关的编码参数全部从NDK透出来,以便后续调试时不用再次编译so库了。关于X264的坑笔者只遇到了一个,就是频繁的开关会导致编译器内存紊乱回收不及时,某些变量已经被回收,其他逻辑块却还持有它的引用,导致NDK直接报错进而引发机器ANR,最后通过一个pause接口完美规避了这个问题,还有就是看issue上有小伙伴说长时间开直播会出现黑屏现象,这个笔者倒是没有遇到过,因为我测的时候都是一两小时的开着。
6.入手推荐
笔者个人觉得要入手的话建议入手X264,因为毕竟是H264编码的老大哥了!等你玩熟悉了,去面试的时候,在你的简历上写上一条有音视频直播开发经验或开源X264二次开发难道它不香嘛,那真的是加分不少!因为你能玩转这个,也侧面说明你有NDK开发经验,无疑为你增加很多加分项,加上目前抖音、快手、西瓜视频、各类直播等东西的火爆,你也应该知道音视频在未来的5年乃至10年都是相当重要的!如果小伙伴没有C/C++基础,建议也多看看这类入门的学习视频,毕竟时间不等人,别让自己过了30了,还在为自己跳槽是否找不找得到工作而发愁,为了房贷车贷而不敢跟领导说NO!
7.使用推荐
说了那么多,那到底用MediaCodeC还是用X264呢?其实笔者前面已经给出答案,就是优缺点嘛!对于直播画面质量要求较低的需求,我们完全可以采用MediaCodeC进行编码,省时省力!如果直播画面要求相对较高时,并且机器性能给力的前提下,当然推荐你用X264,FFMPEG太过庞大,且其h264编码实质也是调用的X246,当然市面上还有其他优秀的编码器,看小伙伴们自己怎么选择。如果既需要高标椎的直播画面,又没有强劲的硬件性能,碰到这类需求,该放手时就放手,你搞不定的,相信我!好了,学习道路千万条,走向捷径就一条,那就是勤奋!谢谢你阅读到这里,感谢小伙伴们一直以来对我的支持!再次感谢你们!