热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

mediacodec压缩成h264数据_MediaCodec编码OpenGL速度和清晰度均衡

作者:VE视频引擎来源:https:blog.csdn.netweixin_41191739articledetails101210752概述在安卓平台

作者:VE视频引擎

来源:https://blog.csdn.net/weixin_41191739/article/details/101210752

概述

在安卓平台为了实现h264视频编码,我们通常可以使用libx264, ffmpeg等第三方视频编码库,但是如果对编码的速度有一定的要求,要实现实时甚至超实时的高速视频编码,我们并没有太多选项,只能使用Android提供的MediaCodec硬编码模块。

MediaCodec模块在实际使用中会遇到很多问题,本文主要讨论使用MediaCodec来对OpenGL渲染的画面进行编码视频时,如何达到速度快和画面清晰的均衡。

注意,本文将默认你已经熟悉使用MediaCodec,配合SurfaceTexture进行OpenGL画面编码的基本流程

分析影响编码速度的因素

除去设备硬件的因素,影响MediaCodec对视频画面进行编码的速度的其他因素并不多,我们实践探索下来主要发现以下几点:

  • 画面尺寸

画面尺寸就是要编码的视频画面的宽高了,它直接定义了每一帧要编码的画面数据的大小,所以它也主要决定了Mediacodec在进行每帧编码时的任务量。这个很容易理解。

  • 帧速率

帧速率指的是每一秒时长的视频所包含的静止画面的帧数。由于Mediacodec在编码时是以帧为单位,对每一个静止帧画面进行编码,所以,帧速率直接决定了MediaCodec在编码固定时长的视频时需要编码的画面数量。

比如,同样编码一段10秒钟的视频,在不考虑画面内容和其他因素的影响下,如果帧速率是30帧每秒,那么MediaCodec的编码工作量大致就是帧速率15帧每秒的情况的2倍,所以其耗时也大致是其2倍。

  • 同步模式下的Time Out设置

MediaCodec有两种工作模式,同步模式和异步模式,其中异步模式只有在Android 5.0以上的系统才支持。如果要兼容5.0以下的系统,那么就必须用到同步模式,而网上流传的众多对MediaCodec的编码流程讲解(比如经典的bigflake)也都是基于同步模式的。

那么在这些文档和案例指导下,大家在使用MediaCodec进行编码时,也都使用着大致相同的设置。

其中,同步模式下,在编码流程中,我们需要调用MediaCodec的dequeueOutputBuffer接口来从队列里获取编码输出缓冲区的内容。dequeueOutputBuffer接口的第二个参数,timeoutUs是一个以微妙为单位的时间值,它定义了从MediaCodec获取输出缓冲区内容时的超时期限。

目前流传的文档教程和案例中,大多将该值设置为10000微秒,也就是10毫秒。但是当我们把这个值降低到1毫秒甚至更低时,会发现,MediaCodec整个编码流程的速度会得到大幅度的提升,而基于我们的测试,编码结果也并未因此受到太多可视的影响。

当然,Android官方文档中也并未对timeoutUs的合适取值范围给出具体的建议,我这里得出的结论是在我们项目实际开发中测试得出,并且其结果满足我们的需求。

如果你的项目不需要保持对5.0以下系统的支持,我还是建议你使用异步模式来进行编码,这样可以直接规避这种同步等待的问题。

  • Encoder Profile

Profile是对视频压缩特性的描述,它主要是定义了编码工具的集合。Profile越高,就说明其采用了越高级的压缩特性。而通常,更高级的压缩方式通常需要耗费更多的压缩时间,也就是说,当profile设置的越高时,其编码耗费的时间往往更多。

例如,当我们在使用ffmpeg进行编码时,如果我们把编码配置设置为ultra-fast时,profile会被强制设置为baseline。

影响画面清晰度的因素

  • Encoder Profile Level

Profile是对视频压缩特性的描述,它主要是定义了编码工具的集合。Profile越高,就说明其采用了越高级的压缩特性,相应的,同样配置下所编码得到的视频文件的清晰度就越高,并且码流越小。

安卓系统支持的H264编码profile一共有3种

  1. Baseline Profile
  2. Main Profile
  3. High Profile

其中Baseline是从Android 3.0之后开始支持,据我们测试,Main Profile及更高的Profile的自定义设置时是从Android 7.0之后开始支持,之前版本Android系统调用相应接口对其设置均不起作用。而就算Android7.0以后的系统,也存在部分机型(比如部分OPPO和华为机型)不支持的情况。

所以如果你的产品需要保持对主流机型的支持,Profile的可靠选择也只有Baseline了。

  • Biterate

Biterate, 即视频码率,是视频数据(视频色彩量、亮度量、像素量)每秒输出的位数。一般用的单位是kbps。

通常情况下,码率越高则视频清晰度越高。但是由于人肉眼分辨的范围有限,码率设置过高所带来的画面清晰度替身也很难被人眼辨别,所以码率设置过高也没有意义。

通常情况下,我推荐一个公式来计算如何对编码器设置对应的码率:

Biterate = Width * Height * FrameRate * Factor

其中,Width、Height和FrameRate分别代表视频的宽度,高度和帧速率,而Factor则是一个系数,用来控制码率。

通常情况下,在网络流媒体使用场景中,可以将Factor设置为0.1~0.2,这样能在保证画面损失不严重的情况下生成出来的视频文件大小较小;

在普通本地浏览的使用场景中,可以将Factor设置为0.25~0.5,这样可以保证画面清晰度不会因为编码而造成过多肉眼可见的损失,这样生成出来的视频文件也相对较大;

在高清视频处理的使用场景中,可以将Factor设置为0.5以上。

不过,当视频画面颜色越丰富、画面变化越快时,视频编码需要的码率就更高,如果遇到这种视频画面场景,需要适当提高码率来保证清晰度。

需要注意的是,大多数安卓机型在对Bitrate的支持都有一个上限,如果设置的Bitrate值超过了上限,可能导致编码器抛出异常,进而编码流程失败。

所以在设计Bitrate的值时,需要通过MediaCodecInfo.CodecCapabilities提供的相关接口来检查系统支持的Bitrate上限。

  • Biterate Mode

虽然我们可以通过接口为编码器设置相关的码率,但是编码器在编码过程中如果处理我们设置的码率也是有几种不同的模式的。

  1. BITRATE_MODE_CQ

忽略用户设置的码率,由编码器自己控制码率,并尽可能保证画面清晰度和码率的均衡。

  1. BITRATE_MODE_CBR

无论视频的画面内容如果,尽可能遵守用户设置的码率

  1. BITRATE_MODE_VBR

尽可能遵守用户设置的码率,但是会根据帧画面之间运动矢量(通俗理解就是帧与帧之间的画面变化程度)来动态调整码率,如果运动矢量较大,则在该时间段将码率调高,如果画面变换很小,则码率降低。

所以,我们在设置码率的同时,也要注意对Bitrate Mode的设置。不同的设置对于生成出来的视频文件的大小和清晰度的影响都是不同的。

  1. 时间戳和帧速率

我们知道,在使用MediaCodec对OpenGL渲染内容进行编码的过程中,可以通过设置MediaFormat的MediaFormat.KEY_FRAME_RATE字段来设置编码器的帧速率;也可以通过设置MediaCodec.BufferInfo对象的presentationTimeUs值来设置每一帧画面的时间戳。

但是通常会忽略,我们在运行完OpenGL相关绘制命令,在调用swapbuffer之前需要调用eglPresentationTimeANDROID接口来设置当前帧的时间戳。

如果没有调用eglPresentationTimeANDROID来设置当前帧的时间戳,只设置了MediaCodec.BufferInfo对象的presentationTimeUs,编码产生的视频在播放时不会有任何时间上的异常,所以很多开发者往往忽略了eglPresentationTimeANDROID接口的调用。

但实际上,如果不调用eglPresentationTimeANDROID,编码出的视频在清晰度上会有额外的损失。

由于MediaCodec的设计是面向实时视频画面流编码的使用场景,所以MediaCodec会根据用户向其输入画面的速度来对编码的速度进行调节。如果我们不通过eglPresentationTimeANDROID来在编码之前对画面的时间戳进行设置,那么MediaCodec往往会将我们向其输入画面的速度默认为实时速度,来对编码速度进行调节。

这种调节会造成码率降低,视频画面清晰度降低。

当我们通过eglPresentationTimeANDROID在编码之前对画面的时间戳进行了正确的设置,MediaCodec会以实际每帧的时间戳为依据来对编码素材进行调节。

这样可以保证其不会对码率进行额外的降低,保证画面清晰度设置生效。

解决方案

结合以上的分析,为了当我们在使用MediaCodec进行视频编码时,为了达到编码速度和画面清晰度的平衡,我们需要在通过几个方面进行综合的优化。

  • Profile方法

综合上文的介绍,为了保证app在各个Android机型上的完美适配,其实我们在Profile方面能做的选择不多,最安全的情况是将Profile设置为Baseline。

  • Bitrate方法

Bitrate的设置我推荐使用Biterate = Width * Height * FrameRate * Factor的公式结合产品的使用场景进行设置。

  • Biterate Mode方法

Biterate Mode的默认设置是BITRATE_MODE_VBR,我推荐在系统和机型支持的情况下尽量将Biterate Mode设置为BITRATE_MODE_CQ。

在BITRATE_MODE_CQ情况下,编码器自身对码率和编码速度的调节往往能达到理想的效果,生成的视频文件不至于过多,但是画面清晰度优秀。

当然也要注意做好系统和机型的适配,进行异常处理,因为在某些机型上可能出现不支持BITRATE_MODE_CQ而导致MediaCodec在configure方法时失败,那么此时,我们需要回退到系统默认的Biterate Mode了。

  • 时间戳正确设置

我们在完成每帧OpenGL画面渲染,调用swapbuffer前一定要先调用eglPresentationTimeANDROID方法来正确设置该帧的时间戳。

eglPresentationTimeANDROID默认并不直接暴露,我们在包含相应头文件前先定义拓展宏来载入该函数。下面是载入eglPresentationTimeANDROID函数的相关代码:

#define EGL_EGLEXT_PROTOTYPES
#include 

eglPresentationTimeANDROID(mDisplay, mSurface, (EGLnsecsANDROID) time); // 在调用eglSwapBuffers先正确设置当前时间戳
eglSwapBuffers(mDisplay, mSurface);

086e195ac1caab178fafb0d2c5c79f4b.png

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

e8883ae6b3d12fb4a740fd27c6d97d8e.png

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

NDK 学习进阶免费视频来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

b08bedd30ae00f21d9c2a73033af7a34.gif



推荐阅读
  • Python多线程编程技巧与实战应用详解 ... [详细]
  • 本文介绍了Spring 2.0引入的TaskExecutor接口及其多种实现,包括同步和异步执行任务的方式。文章详细解释了如何在Spring应用中配置和使用这些线程池实现,以提高应用的性能和可管理性。 ... [详细]
  • PTArchiver工作原理详解与应用分析
    PTArchiver工作原理及其应用分析本文详细解析了PTArchiver的工作机制,探讨了其在数据归档和管理中的应用。PTArchiver通过高效的压缩算法和灵活的存储策略,实现了对大规模数据的高效管理和长期保存。文章还介绍了其在企业级数据备份、历史数据迁移等场景中的实际应用案例,为用户提供了实用的操作建议和技术支持。 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 应用链时代,详解 Avalanche 与 Cosmos 的差异 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Python错误重试让多少开发者头疼?高效解决方案出炉
    ### 优化后的摘要在处理 Python 开发中的错误重试问题时,许多开发者常常感到困扰。为了应对这一挑战,`tenacity` 库提供了一种高效的解决方案。首先,通过 `pip install tenacity` 安装该库。使用时,可以通过简单的规则配置重试策略。例如,可以设置多个重试条件,使用 `|`(或)和 `&`(与)操作符组合不同的参数,从而实现灵活的错误重试机制。此外,`tenacity` 还支持自定义等待时间、重试次数和异常处理,为开发者提供了强大的工具来提高代码的健壮性和可靠性。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
author-avatar
黄自安_725
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有