写点什么

去抖音面试被问到硬编码与软编码区别,如何选取硬编与软编?

用户头像
Android架构
关注
发布于: 刚刚

###一.MediaCodec(硬编)MediaCodec 是 API 16 之后 Google 推出的用于音视频编解码的一套偏底层的 API,可以直接利用硬件加速进行视频的编解码。调用的时候需要先初始化 MediaCodec 作为视频的编码器,然后只需要不停传入原始的 YUV 数据进入编码器就可以直接输出编码好的 h264 流,整个 API 设计模型来看,就是同时包含了输入端和输出端的两条队列:



因此,作为编码器,输入端队列存放的就是原始 YUV 数据,输出端队列输出的就是编码好的 h264 流,作为解码器则对应相反。在调用的时候,MediaCodec 提供了同步和异步两种调用方式,但是异步使用 Callback 的方式是在 API 21 之后才加入的,以同步调用为例,一般来说调用方式大概是这样(摘自官方例子):


MediaCodec codec = MediaCodec.createByCodecName(name);codec.configure(format, …);MediaFormat outputFormat = codec.getOutputFormat(); // option Bcodec.start();for (;;) {int inputBufferId = codec.dequeueInputBuffer(timeoutUs);if (inputBufferId >= 0) {ByteBuffer inputBuffer = codec.getInputBuffer(…);// fill inputBuffer with valid data…codec.queueInputBuffer(inputBufferId, …);}int outputBufferId = codec.dequeueOutputBuffer(…);if (outputBufferId >= 0) {ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A// bufferFormat is identical to outputFormat// outputBuffer is ready to be processed or rendered.…codec.releaseOutputBuffer(outputBufferId, …);} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// Subsequent data will conform to new format.// Can ignore if using getOutputFormat(outputBufferId)outputFormat = codec.getOutputFormat(); // option B}}codec.stop();codec.release();


简单解释一下,通过getInputBuffers获取输入队列,然后调用dequeueInputBuffer获取输入队列空闲数组下标,注意dequeueOutputBuffer会有几个特殊的返回值表示当前编解码状态的变化,


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


然后再通过queueInputBuffer把原始 YUV 数据送入编码器,而在输出队列端同样通过getOutputBuffersdequeueOutputBuffer获取输出的 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 的时候,我们需要通过codecInfo.getCapabilitiesForType来查询机器上的 MediaCodec 实现具体支持哪些 YUV 格式作为输入格式,一般来说,起码在 4.4+的系统上,这两种格式在大部分机器都有支持:


MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PlanarMediaCodecInfo.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_MODEMediaFormat.KEY_PROFILEMediaFormat.KEY_LEVEL


但问题是,对于 Profile,Level, Bitrate mode 这些设置,在大部分手机上都是不支持的,即使是设置了最终也不会生效,例如设置了 Profile 为 high,最后出来的视频依然还会是 Baseline,Shit....这个问题,在 7.0 以下的机器几乎是必现的,其中一个可能的原因是,Android 在源码层级 hardcode 了 profile 的的设置:


// XXXif (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) {.....}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
去抖音面试被问到硬编码与软编码区别,如何选取硬编与软编?