1.前言

 

 微信团队分享:微信安卓版的小视频码填的那些坑_ timg.jpg。

 

 安卓的视频相关开发一直是安卓生态系统和安卓应用编程接口中最具争议和最突出的部分。与视频编码相关的摄像头和应用编程接口,谷歌在这方面一直控制不力,导致不同制造商在这两个应用编程接口的实现上存在诸多差异。此外,从应用编程接口设计的角度来看,优化是相当有限的,甚至有人认为这是“安卓系统中最困难的应用编程接口之一”

 

 以微信的小视频为例,我们录制了一个540p mp4文件。对于安卓来说,它基本上遵循这样一个过程:

 微信团队分享:微信安卓版的小视频码填在那些坑里_1.jpg

 

 一般来说,从摄像机输出的YOV帧被预处理并发送到编码器以获得编码的h264视频流。

 

 以上仅用于视频流的编码,此外,音频流需要单独记录,最后将视频流和音频流组合以产生最终的视频。

 

 本文将主要分析视频流编码中的两个常见问题:

 

 视频编码器的选择:硬编码和软编码;

 如何快速预处理摄像机输出的YOV帧:镜像、缩放和旋转。

 

 2.视频编码器的选择

 

 为了满足录制视频的需求,许多应用程序需要分别处理每一帧数据,因此很少使用MediaRecorder直接录制视频。

 

 一般来说,有两种选择:

 

 媒体编解码器。

 FFMpeg+x264/openh264 .

 

 让我们逐一分析它们。

 3、媒体编解码器

 

 3.1基本介绍

 

 MediaCodec是继API 16之后,谷歌推出的一款较低级的音视频编解码API,可以直接使用硬件加速视频编解码。调用时,您需要先将MediaCodec初始化为视频编码器,然后您可以直接输出编码后的h264流,只需将原始的YUV数据连续导入编码器即可。

 

 根据整个应用编程接口设计模型,有两个队列,包括输入和输出:

 微信团队分享:微信安卓版的小视频码填在那些坑里_2.png

 

 因此,作为编码器,输入队列存储原始YOV数据,输出队列输出编码的h264流,而作为解码器,情况正好相反。调用时,MediaCodec提供同步和异步调用模式,但异步回调模式是在API 21之后添加的。

 

 以同步调用为例,一般来说,调用模式大致是这样的(来自官方的例子):

 微信团队分享:微信安卓版的小视频码填在那些坑里_3.png

 

 简单地说,通过getInputBuffers获取输入队列,然后调用dequeueInputBuffer来获取输入队列的空闲数组的索引。请注意,出列输出缓冲区将有几个特殊的返回值,指示当前编解码器状态的变化,然后通过queueInputBuffer将原始的YUV数据发送到编码器。在输出队列端,输出h264流也通过getOutputBuffers和dequeueOutputBuffer获得。处理完输出数据后,需要通过释放输出缓冲区将输出缓冲区返回给系统,并将其放回输出队列。

 

 有关媒体编解码器的更复杂使用示例,请参考CTS测试中的使用模式:

 EncodeDecodeTest.java

 

 从上面的例子来看,它确实是一个非常原始的API。因为MediaCodec的底层直接调用手机平台硬件的编码和解码能力,所以速度非常快,但是因为谷歌对整个Android硬件生态系统的控制非常弱,所以这个API存在很多问题。

 

 3.2颜色格式问题

 

 媒体编解码器在初始化和配置时需要传入一个媒体格式对象。当它被用作编码器时,我们通常需要在MediaFormat中指定视频宽度和高度、帧速率、比特率和I帧间隔的基本信息。此外,另一个重要信息是指定编码器接受的YOV帧的颜色格式。这是因为YUV根据其采样率有许多不同的颜色格式,并且在onPreviewFrame中由安卓相机输出的YUV帧格式基本上是无任何参数的NV21格式,但是谷歌的媒体编解码器的应用编程接口在设计和标准化它时非常不友好。太接近安卓的HAL层,导致NV21格式,这不是所有机器的媒体编解码器都支持作为编码器的输入格式!

 

 因此,在初始化媒体编解码器时,我们需要通过codecinfo . getcapabilities for type查询机器上的媒体编解码器实现支持哪些YOV格式作为输入格式。一般来说,至少在4.4+系统上,大多数机器都支持这两种格式:

 微信团队分享:微信安卓版的小视频码填在那些坑里_4.jpg

 

 这两种格式分别是YUV420P和NV21。如果机器只支持YV420P格式,摄像机输出的NV21格式需要先转换成YV420P,然后送到编码器进行编码,否则最终的视频会被屏蔽或者颜色会混乱。

 

 这是一个很小的坑,基本上是在使用媒体编解码器进行视频编码时遇到的。

 

 3.3编码器支持功能非常有限

 

 如果我们使用媒体编解码器对H264视频流进行编码,对于H264格式,将会有一些与压缩率和比特率相关的视频质量设置,例如配置文件(基线、主要、高)、配置文件级别、比特率模式(CBR、CQ、VBR)。这些参数的合理配置可以使我们在相同的比特率下获得更高的压缩率,从而提高视频质量。

 

 安卓还提供了相应的设置接口,可以在媒体格式中设置为这些设置:

 微信团队分享:微信安卓版的小视频码填在那些坑里_5.png

 

 但是,问题是大多数手机都不支持配置文件、级别、比特率模式设置,即使设置了,最终也不会生效。例如,如果配置文件设置为高,最终视频仍将是基线....

 

 这个问题在7.0以下的机器中几乎是不可避免的。一个可能的原因是,安卓已经在源代码级别设置了配置文件:

 微信团队分享:微信安卓版的小视频码填在那些坑里_6.png

 

 安卓直到7.0之后才取消了这个地方的硬编码:

 微信团队分享:微信安卓版的小视频码填在那些坑里_7.png

 

 可以说,这个问题间接导致了由媒体编码解码器编码的低视频质量,并且难以获得与软编码相同的视频质量,甚至难以获得相同比特率的iOS。

 

 3.416位对齐要求

 

 如前所述,在设计时,应用编程接口媒体编码太接近HAL层。在许多Soc实现中,传递到媒体编解码器的缓冲区直接发送到Soc,无需任何预处理。在对h264视频流进行编码时,由于h264的编码块大小一般为16×16,所以在开始设置视频的宽度和高度时,如果在一些中央处理器上设置了不与16对齐的大小,如960×540,最终编码的视频将被直接播放!

 

 显然,这是由于制造商在实施该应用编程接口时缺乏对输入数据的验证和预处理。目前,这个问题在华为和三星的SOC中会频繁出现,其他厂商的一些早期SOC也存在这个问题。一般来说,解决方案是统一设置视频宽度和高度,使其与16位后的大小一致。

 4、FFMpeg+x264/openh264

 

 除了使用MediaCodec进行编码之外,另一种流行的方案是使用ffmpeg+x264/openh264进行软编码。ffmpeg用于预处理一些视频帧。X264/openh264主要用作视频编码器。

 

 X264基本上被认为是当今市场上最快的商用视频编码器,并且基本上支持h264的所有特性。通过合理配置各种参数,可以获得更好的压缩比和编码速度。

 

 由于空间有限,这里不再描述h264的参数配置。如果您感兴趣,可以在这两篇文章中查看x264编码参数的调整:

 https://www.nmm-hd.org/d/index.php? title = X264 % E4 % BD % BF % E7 % 94% A8 % E4 % BB % 8B % E7 % BB % 8D & variant = zh-cn

 http://www.cnblogs.com/wainiwann/p/5647521.html

 

 open h264(https://github . org)。是思科公司推出的另一款h264编码器。该项目于2013年启动,比x264稍年轻。然而,因为思科支付h264的年度专利费用,它可以直接和免费为外部用户使用。此外,火狐还直接内置了openh264。

 

 但是,与x264相比,openh264对h264的高级功能支持较差:

 

 配置文件仅支持基线,级别5.2;;

 多线程编码仅支持基于切片的编码,但不支持基于帧的多线程编码。

 就编码效率而言,openh264并不比x264快,但它最大的优势是可以直接免费使用。

 

 5.软编辑和硬编辑的比较

 

 从以上分析来看,硬编码的主要优势在于其速度快,系统不需要引入外部库,但其功能支持有限,并且硬编码的压缩率普遍较低。对于软编码,虽然速度较慢,但压缩率相对较高,支持的H264特性将比相对可控的硬编码多得多。就可用性而言,在4.4+系统上,媒体编解码器的可用性基本上可以得到保证,但是不同机器的编码器能力有许多不同。建议根据机器配置选择不同的编码器配置。

 6.YUV帧的预处理

 

 根据开头给出的流程,我们需要在将摄像机输出的YOV帧发送到编码器之前对其进行预处理。

 

 6.1缩放

 

 如果相机的预览大小设置为1080p,则onPreviewFrame中的YOV帧输出直接为1920x1080。如果我们需要对不同于这个尺寸的视频进行编码,我们需要在录制过程中实时缩放YOV帧。

 

 以微信为例,当摄像头预览1080p数据时,需要对960x540视频进行编码。

 

 最常见的方法是使用诸如ffmpeg之类的sws_scale函数进行直接缩放,为了获得更好的效果/性能,通常会选择SWS _ FAST _双线性算法:

 微信团队分享:微信安卓版的小视频码填在那些坑里_8.png

 

 在nexus 6p上,通过ffmpeg直接缩放需要40毫秒以上。对于那些需要记录30fps的人,每帧的处理时间最多约为30毫秒。如果仅仅变焦就要消耗这么多时间,那么录制的视频基本上只能达到15fps左右。

 

 显然,用ffmpeg直接缩放太慢了,必须说swsscale只是ffmpeg中的败类。

 

 在比较了行业中几种常用的算法后,我们最终考虑实现这种快速缩放算法:

 微信团队分享:微信安卓版的小视频码填在那些坑里_9.png

 

 我们选择一种称为局部均值算法的算法,它用前后行中的四个相邻点来计算最终图像的四个像素。对于源图像中的每一行像素,我们可以用氖来直接实现,以缩放Y分量为例:

 微信团队分享:微信安卓版的小视频码填在那些坑里_10.png

 

 上面使用的Neon指令一次只能读取和存储8或16位数据,额外的数据只需要用C语言实现,而不是用相同的算法。

 

 使用上述算法优化后,在Nexus 6p上缩放每一帧的时间不到5毫秒。对于缩放质量,将ffmpeg的SWS _ FAST _双线性算法与上述算法缩放的图像进行比较,在大多数场景中峰值信噪比(psnr)约为38-40,质量足够好。

 

 6.2轮换

 

 在安卓机器上,由于摄像头的安装角度不同,onPreviewFrame中的YOV框架通常会旋转90或270度。如果最终视频是垂直拍摄的,通常需要旋转YOV帧。

 

 至于旋转算法,如果它是一个纯C实现的代码,它通常是一个O (n 2)复杂性算法。如果要旋转960x540 yuv帧数据,则在nexus 6p上旋转每帧需要30毫秒以上,这显然是不可接受的。

 

 让我们改变我们的想法。我们能不能不旋转YOV帧?

 

 实际上,在mp4文件格式的头中,我们可以指定一个旋转矩阵,特别是在moov.trak.tkhdbox中。当视频播放器播放视频时,它将在这里读取矩阵信息,以确定视频本身的旋转角度、位移和缩放。请参考苹果公司的文件:http://developer . Apple . com/library/content/documentation/quick time/qtff/qtffchap 4/qtff 4 . html #//Apple _ ref/doc/uid/TP 4000939-ch 206-1889

 

 使用ffmpeg,我们可以轻松地将这个旋转角度放在合成mp4文件上:

 微信团队分享:微信安卓版的小视频码填在那些坑里_11.png

 

 所以你可以在录制时节省很多旋转开销,兴奋吧!

 

 6.3镜像

 

 用前置摄像头拍摄时,如果YOV帧未被处理,直接拍摄的视频将被镜像并翻转。这里的原则就像照镜子一样。从前置摄像头拍摄的YOV画面刚好反转,但有时镜像的视频镜头可能无法满足我们的需求,因此我们需要在此时镜像并翻转YOV画面。

 

 然而,因为摄像机的安装角度通常为90或270度,所以原始的YOV框架实际上是水平旋转的。因此,在进行镜像翻转时,只需以中间为中心轴上下交换每一行数据,并注意Y和UV的分开处理。

 

 该算法很容易用Neon实现:

 微信团队分享:微信安卓版的小视频码填在那些坑里_12.png

 

 同样,其余的数据可以用纯C代码实现。在nexus6p上,该图像翻转一帧1080x1920 YUV数据不到5毫秒。在对h264视频流进行编码之后,最后的处理是将音频流和视频流合并并打包成mp4文件。这部分可以通过系统的MediaMuxer、mp4v2或ffmpeg来实现,这相对简单,这里不再赘述。

 7.参考

 

 1)雷晓华的专栏:

 http://blog.csdn.net/leixiaohua1020

 著名的Thor博客包含了许多关于音频和视频编码/ffmpeg的学习材料,这是入门所必需的。愿他也安息在天堂。

 

 2)安卓媒体编解码器:

 http://bigflake.com/mediacodec/

 它包含了一些MediaCodec使用的示例代码,可以在这里首次引用。

 

 NEON编码:

 https://community . arm . com/processors/b/blog/post/coding-for-neon-part-1-load-and-store

 关于如何使用一些常用霓虹灯说明的系列教程。当上面引入缩放时,使用氖。事实上,大多数音频和视频处理过程将被使用。以YUV帧处理为例,Neon可以优化缩放、旋转和图像翻转。

 

 4)libyuv:

 https://chromium.googlesource.com/libyuv/libyuv/

 谷歌开源YEV处理库,它只针对1080p->540p视频帧缩放算法,但对于一般的压缩处理,这里的实现可以直接使用,比ffmpeg快得多。


----------------------------------------------------------------------------------

哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通

IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载

IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建

新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP

哇谷IM-JM沟通热门动态博客短视频娱乐生活

关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建

联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务

即时通讯IM融云世界

IM即时通讯钉钉技术:企业IM钉钉在后端架构上的优越之处

新的市场叫板环信、融云、腾讯云!开源版IM即使聊天工具

企业IM即时通讯聊天办公APP钉钉技术分析交流

哇谷云-怎么样正确认识海外云服务器

公有云和私有云之间有什么区别?类似融云、环信云、网易云、哇谷云?