去抖⾳⾯试被问到硬编码与软编码区别,如何选取硬编与软编?
Android的视频相关的开发,⼤概⼀直是整个Android⽣态,以及Android API中,最为分裂以及兼容性问题最为突出的⼀部分。摄像头,以及视频编码相关的API,Google⼀直对这⽅⾯的控制⼒⾮常差,导致不同⼚商对这两个API的实现有不少差异,⽽且从API的设计来看,⼀直以来优化也相当有限,甚⾄有⼈认为这是“Android上最难⽤的API之⼀”
以为例,我们录制⼀个540p的mp4⽂件,对于Android来说,⼤体上是遵循这么⼀个流程:
上⾯只是针对视频流的编码,另外还需要对⾳频流单独录制,最后再将视频流和⾳频流进⾏合成出最终视频。
这篇⽂章主要将会对视频流的编码中两个常见问题进⾏分析:
1.视频编码器的选择(硬编 or 软编)?
2.如何对摄像头输出的YUV帧进⾏快速预处理(镜像,缩放,旋转)?
视频编码器的选择
世界奇景对于录制视频的需求,不少app都需要对每⼀帧数据进⾏单独处理,因此很少会直接⽤到MediaRecorder来直接录取视频,⼀般来说,会有这么两个选择:
1.MediaCodec
2.FFMpeg+x264/openh264
我们来逐个解析⼀下
⼀.MediaCodec(硬编)
MediaCodec是API 16之后Google推出的⽤于⾳视频编解码的⼀套偏底层的API,可以直接利⽤硬件加速进⾏视频的编解码。调⽤的时候需要先初始化MediaCodec作为视频的编码器,然后只需要不停传⼊原始的YUV数据进⼊编码器就可以直接输出编码好的h264流,整个API设计模型来看,就是同时包含了输⼊端和输出端的两条队列:
因此,作为编码器,输⼊端队列存放的就是原始YUV数据,输出端队列输出的就是编码好的h264流,作为解码器则对应相反。在调⽤的时候,MediaCodec提供了同步和异步两种调⽤⽅式,但是异步使⽤Callback的⽅式是在API 21之后才加⼊的,以同步调⽤为例,⼀般来说调⽤⽅式⼤概是这样(摘⾃官⽅例⼦):
MediaCodec codec = ateByCodecName(name);
MediaFormat outputFormat = OutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = InputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);林宥嘉前女友
}
int outputBufferId = codec.dequeueOutputBuffer(…);
明星人体艺术图片if (outputBufferId >= 0) {
ByteBuffer outputBuffer = OutputBuffer(outputBufferId);
MediaFormat bufferFormat = OutputFormat(outputBufferId); // option A
/
/ bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
新警察故事演员// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = OutputFormat(); // option B
}
}
codec.stop();
简单解释⼀下,通过getInputBuffers获取输⼊队列,然后调⽤dequeueInputBuffer获取输⼊队列空闲数组下标,注意dequeueOutputBuffer会有⼏个特殊的返回值表⽰当前编解码状态的变化,然后再通过queueInputBuffer把原始YUV数据送⼊编码器,⽽在输出队列端同样通
过getOutputBuffers和dequeueOutputBuffer获取输出的h264流,处理完输出数据之后,需要通过releaseOutputBuffer把输出buffer还给系统,重新放到输出队列中。
从上⾯例⼦来看的确是⾮常原始的API,由于MediaCodec底层是直接调⽤了⼿机平台硬件的编解码能⼒,所以速度⾮常快,但是因为Google对整个Android硬件⽣态的掌控⼒⾮常弱,所以这个API有很多问题:
1.颜⾊格式问题
MediaCodec在初始化的时候,在configure的时候,需要传⼊⼀个MediaFormat对象,当作为编码器使⽤的时候,我们⼀般需要在MediaFormat中指定视频的宽⾼,帧率,码率,I帧间隔等基本信息,除此之外,还有⼀个重要的信息就是,指定编码器接受的YUV帧的颜⾊格式。这个是因为由于YUV根据其采样⽐例,UV分量的排列顺序有很多种不同的颜⾊格式,⽽对于Android的摄像头
在onPreviewFrame输出的YUV帧格式,如果没有配置任何参数的情况下,基本上都是NV21格式,但Google对MediaCodec的API在设计和规范的时候,显得很不厚道,过于贴近Android的HAL层了,导致了NV21格式并不是所有机器的MediaCodec都⽀持这种格式作为编码器的输⼊格式! 因此,在初始化MediaCodec的时候,我们需要通过CapabilitiesForType来查询机器上的MediaCodec实现具体⽀持哪些YUV格式作为输⼊格式,⼀般来说,起码在4.4+的系统上,这两种格式在⼤部分机器都有⽀持:
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
描写老师的句子MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
两种格式分别是YUV420P和NV21,如果机器上只⽀持YUV420P格式的情况下,则需要先将摄像头输出的NV21格式先转换成
YUV420P,才能送⼊编码器进⾏编码,否则最终出来的视频就会花屏,或者颜⾊出现错乱
这个算是⼀个不⼤不⼩的坑,基本上⽤上了MediaCodec进⾏视频编码都会遇上这个问题
2.编码器⽀持特性相当有限
如果使⽤MediaCodec来编码H264视频流,对于H264格式来说,会有⼀些针对压缩率以及码率相关的视频质量设置,典型的诸如
Profile(baseline, main, high),Profile Level, Bitrate mode(CBR, CQ, VBR),合理配置这些参数可以让我们在同等的码率下,获得更⾼的压缩率,从⽽提升视频的质量,Android也提供了对应的API进⾏设置,可以设置到MediaFormat中这些设置项:
张柏芝照艳原图MediaFormat.KEY_BITRATE_MODE
MediaFormat.KEY_PROFILE
MediaFormat.KEY_LEVEL
但问题是,对于Profile,Level, Bitrate mode这些设置,在⼤部分⼿机上都是不⽀持的,即使是设置了最终也不会⽣效,例如设置了Profile为high,最后出来的视频依然还会是Baseline,Shit…
这个问题,在7.0以下的机器⼏乎是必现的,其中⼀个可能的原因是,Android在源码层级hardcode了profile的的设置:
// XXX
if (h264type.eProfile != OMX_VIDEO_AVCProfileBaseline) {
ALOGW("Use baseline profile instead of %d for AVC recording",
h264type.eProfile);
h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
}
Android直到7.0之后才取消了这段地⽅的Hardcode
if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) {
....
} else if (h264type.eProfile == OMX_VIDEO_AVCProfileMain ||
h264type.eProfile == OMX_VIDEO_AVCProfileHigh) {
.....
}
这个问题可以说间接导致了MediaCodec编码出来的视频质量偏低,同等码率下,难以获得跟软编码甚⾄iOS那样的视频质量。
3.16位对齐要求
前⾯说到,MediaCodec这个API在设计的时候,过于贴近HAL层,这在很多Soc的实现上,是直接把传⼊MediaCodec的buffer,在不经过任何前置处理的情况下就直接送⼊了Soc中。⽽在编码h264视频流的时候,由于h264的编码块⼤⼩⼀般是16x16,于是乎在⼀开始设置视频的宽⾼的时候,如果设置了⼀个没有对齐16的⼤⼩,例如960x540,在某些cpu上,最终编码出来的视频就会直接花屏!
很明显这还是因为⼚商在实现这个API的时候,对传⼊的数据缺少校验以及前置处理导致的,⽬前来看,华为,三星的Soc出现这个问题会⽐较频繁,其他⼚商的⼀些早期Soc也有这种问题,⼀般来说解决⽅法还是在设置视频宽⾼的时候,统⼀设置成对齐16位之后的⼤⼩就好了。
⼆.FFMpeg+x264/openh264(软编)
除了使⽤MediaCodec进⾏编码之外,另外⼀种⽐较流⾏的⽅案就是使⽤ffmpeg+x264/openh264进⾏软编码,ffmpeg是⽤于⼀些视频帧的预处理。这⾥主要是使⽤x264/openh264作为视频的编码器。
x264基本上被认为是当今市⾯上最快的商⽤视频编码器,⽽且基本上所有h264的特性都⽀持,通过合理配置各种参数还是能够得到较好的压缩率和编码速度的,限于篇幅,这⾥不再阐述h264的参数配置
openh264则是由思科开源的另外⼀个h264编码器,项⽬在2013年开源,对⽐起x264来说略显年轻,不过由于思科⽀付满了h264的年度专利费,所以对于外部⽤户来说,相当于可以直接免费使⽤了,另外,firefox直接内置了openh264,作为其在webRTC中的视频的编解码器使⽤。
但对⽐起x264,openh264在h264⾼级特性的⽀持⽐较差:
Profile只⽀持到baseline, level 5.2
多线程编码只⽀持slice based,不⽀持frame based的多线程编码
从编码效率上来看,openh264的速度也并不会⽐x264快,不过其最⼤的好处,还是能够直接免费使⽤吧。
软硬编对⽐
从上⾯的分析来看,
1.硬编的好处主要在于速度快,⽽且系统⾃带不需要引⼊外部的库,但是特性⽀持有限,⽽且硬编的压缩率⼀般偏低,
2.⽽对于软编码来说,虽然速度较慢,但是压缩率⽐较⾼,⽽且⽀持的H264特性也会⽐硬编码多很多,相对来说⽐较可控。就可⽤性⽽⾔,
3.在
4.4+的系统上,MediaCodec的可⽤性是能够基本保证的,但是不同等级的机器的编码器能⼒会有不少差别,建议可以根据机器的配置,选择不同的编码器配置。视频流合流然后包装到mp4⽂件,这部分我们可以通过
⾯试精选。进阶。视频教程~更多⾯试详情请关注我的主页查询。
发布评论