文章目录

  • 前言
  • 类改造
  • 可能遇到的问题
    • Simulcast
    • 两个QP阈值的含义
    • 动态码率/帧率
    • 反馈每一帧的QP非常重要
    • RTPFragmentationHeader是个什么玩意儿
    • H264编码数据的排列格式要求
    • Streams with pred_weight_table unsupported

前言

众所周知,所有基于Chromium内核开发的“标准”浏览器架构的项目,如CEF、Electron甚至是Google Chrome,默认提供的H.264软编码器都是Cisco的OpenH264。那么,如果我们想使用其他H.264编码器,例如x264,甚至是结合特殊芯片进行硬编码,该如何做呢?关于OpenH264和x264的差异,网上有很多相关文章,这里就不赘述了。

OpenH264的具体实现位于WebRTC项目中,本文是基于 CEF 76.3809.132 这个分支的源码基础上进行介绍。因Chromium以及WebRTC代码变化比较频繁,可能在不久的将来,代码就对应不上了,所以本文尽可能只说一下通用性的适合未来版本的思路。不过从git log看,H264编码器的实现变化的不频繁,比较频繁的还是WebRTC的VideoSendStream,里面有不少关于控制编码器行为的代码,并且这部分写的非常有意思,有时间我写篇文章简单说一下……跑题了,言归正传。

H264软编码器代码位于:

third_party\webrtc\modules\video_coding\codecs\h264\h264_encoder_impl.h/.cc

类改造

下面是H264EncoderImpl这个类的继承关系:
因为懒,所以上图中的函数参数、以及部分成员变量就不写了。

类关系还是很清楚的,也比较简单。H264EncoderImpl实现了VideoEncoder的5个纯虚函数(InitEncode、Encode、RegisterEncodeCompleteCallback、Release、SetRates),1个虚函数(GetEncoderInfo)。并且添加了若干private方法和成员变量。

H264EncoderImpl实现的这几个虚函数是最核心的几个,我们添加 x264编码器,就是要围绕着这几个虚函数展开。至于其他的部分,大家看到,基本上都是OpenH264的实现,并非通用的。所以,可以这么说,H264EncoderImpl 应该叫 OpenH264EncoderImpl 更合适一点。

OK,接下来我们对H264EncoderImpl进行一下改造。改造后的类结构如下:

我新建了一个基类 H264BaseEncoder,它用来存储及实现一些通用性的方法,而真正有差异的部分,继续保留虚函数的形式,派生出来2个子类:OpenH264EncoderImplX264EncoderImpl 去实现它们。

改造后,原来H264EncoderImpl中只适用于OpenH264的代码被全部转移到OpenH264EncoderImpl,公共的部分(方法、变量)保留在H264BaseEncoder。最终,X264EncoderImpl是我们着重要编写的类。

OK,下面讲一下在实现X264EncoderImpl这个类中可能会遇到的问题。

可能遇到的问题

Simulcast

WebRTC的OpenH264支持Simulcast,这个通过阅读源码可以看到:

std::vector<ISVCEncoder*> encoders_;

OpenH264是有一个最大数量为4的vector来存储每一层对应的编码器的。x264实现是否支持,看你的实际使用情况而定,你可以只实现一个编码器,相应地,上层初始化PeerConnection的时候就不要设置simulcast。

两个QP阈值的含义

OpenH264的源码中,定义了两个静态常量:kLowH264QpThresholdkHighH264QpThreshold,取值分别是 24 和 37。这里要注意,这两个值并不是OpenH264编码参数中设置用于编码的QP范围,而是用于判断是否动态升降发送分辨率或帧率的阈值。 WebRTC会根据一段时间内的编码平均QP,和这两个值进行比较。小等于kLowH264QpThreshold,认为需要升分辨率(或帧率)。而大于kHighH264QpThreshold,则会下调分辨率(或帧率)。平均QP计算的这部分实现也很有意思,它的源码位于 \webrtc\modules\video_coding\utility\quality_scaler.cc。

至于24和37是否适用于x264,这个也需要结合你使用的x264编码参数与实际应用情况来定。我做了一点微小的调整(26, 40)。

动态码率/帧率

上面大家有看到那几个重要的纯虚函数中,有一个 SetRates (这个函数以前叫SetRateAllocation),这个函数的调用堆栈大概是这样的:

→ VideoEncoder::SetRates()
↑ VideoStreamEncoder::SetEncoderRates()
↑ VideoStreamEncoder::OnBitrateUpdated()
↑ VideoSendStreamImpl::OnBitrateUpdated()
↑ BitrateAllocator::OnNetworkChanged()

其中VideoEncoder::SetRates()就会来到具体编码器的SetRates里来。它的参数主要包含了期望调整的目标码率、帧率等。SetRates会调用的非常频繁,所以在这里,我们要不断地重设编码器的参数,以适应WebRTC的请求。

这里要特别说明的一点是,x264似乎并不支持动态帧率。也就是说,如果在InitEncode的时候我们设置了一个60fps的帧率,在SetRates中使用 x264_encoder_reconfig() 尝试改为30fps,似乎是无效的。而动态调整码率则没有这个问题。我不太确定是因为我选用了ABR编码方式引起的,还是其他什么原因。有知道的朋友可以告诉我。

所以,针对这个问题,我的做法是:如果当前编码帧率和目标帧率相差某个阈值(如5)或以上,则关闭当前 x264编码器,重新创建一次(帧率采用本次的目标帧率,其他编码参数使用关闭前的)

反馈每一帧的QP非常重要

这一条,不得不多说两句。

其实x264编码器我很快就写完了,并且使用了常见的几档分辨率/帧率参数,使用一款罗技摄像头进行了试验,工作的比较良好。但当我把视频源从摄像头换成了一个1080P的mp4文件时,发生一件非常诡异的问题、踩了一个坑:

起初的发送分辨率是1920x1080,但通过webrtc-internals看到,很快发送分辨率下调至1440x810,之后又下降到960x540,在若干秒后,迅速下降到720x405,然后你猜怎么着,x264就出错了。原因是什么?输入了一个奇数分辨率。

那么到底是什么原因引起分辨率在短时间内迅速按照一个阶梯状下降呢?通过阅读源码后才发现里面的奥秘:WebRTC会根据一定时间内的编码平均QP,来决定是否要升降发送分辨率!也就是说,发送分辨率在不断下调的根本原因,是目前的平均QP一直居高不下(超过我们上面提到的QP阈值上限),所以一直在不断地请求下调分辨率。如果不是x264因为输入奇数分辨率出错了,下一个可能到达的就是480x270。(注,下调分辨率是按照输入分辨率 3/4 和 2/3 交替计算的)

OK,原因找到了,接下来问题就好解决了。最后问题的原因是因为我没有在编码后,通过 webrtc::H264BitstreamParserParseBitstreamGetLastSliceQp 这两个方法计算编码帧的正确QP送出去。(这两个方法的调用在OpenH264编码后的代码你可以找到)

RTPFragmentationHeader是个什么玩意儿

RTPFragmentationHeader的作用其实就是标识一帧编码后的H264数据各个NALU去掉开始码后的数据起始位置和长度的。举个例子:比如编码出来的一段H264数据是下面的格式:

00 00 00 01 67 aa aa aa aa aa aa 00 00 00 01 68 bb bb bb 00 00 00 01 65 cc cc cc

那么, RTPFragmentationHeader将会存储3个元素:

元素1的offset指针指向67,长度是67开始到下一个开始码之间的字节数
元素2的offset指针指向68,长度是68开始到下一个开始码之间的字节数
元素3的offset指针指向65,长度是65开始到下一个开始码之间的字节数

注,老版本的WebRTC源码中还有 fragmentationPlTypefragmentationTimeDiff这两个字段,新版本已经删掉了。

OK,最终,RTPFragmentationHeader以及编码后的数据,将通过 EncodedImageCallback->OnEncodedImage() 送到外面去进行后续处理流程。

H264编码数据的排列格式要求

OpenH264编码出来的数据,一般都是比较规整的“4字节开始码+NALU类型+编码数据”组成的,如下:

而x264编码器编码输出的数据中,有一些是无用的,需要跳过,如下是x264编码出来的起始部分:

这里还要注意的是,x264编码器送出来的NALU开始码长度有4个的,也有3个的,在填充RTPFragmentationHeader的时候需要注意别搞错了。另外就是有些NALU类型(如SEI、AUD)需要跳过不要。

这里插一句,起初我在填充 RTPFragmentationHeader 的时候,去掉了NALU的开始码。我想着, RTPFragmentationHeader 反正是按照偏移和长度来获取数据的,开始码有没有不重要。但后来我发现我错了。这个开始码不是外面要用的,而是webrtc::H264BitstreamParser在分析流和QP值的时候要读取的,缺少了开始码,将无法读取到正确的QP值,所以万不可去掉。这部分可以自行阅读以下H264BitstreamParser的源码。

Streams with pred_weight_table unsupported

在替换了x264以后,我发现debug级别的日志里面有很多这个错误。它来自

third_party\webrtc\common_video\h264\h264_bitstream_parser.cc

我的解决方法是通过以下两句代码:

[x264_param_t].analyse.i_weighted_pred = X264_WEIGHTP_NONE;
[x264_param_t].analyse.b_weighted_bipred = X264_WEIGHTP_NONE;

讲实话我不太清楚关闭它的影响是什么,如果你知道,欢迎告诉我。

OK,差不多就是这些了。本文对改造WebRTC的H264Encoder模块,添加 x264编码器进行了主要思路和注意事项的说明,里面其实有很多小的知识点,都可以专门展开写一篇文章。例如:

  • WebRTC对视频的发送策略,有保帧率、保分辨率、平衡模式,它们的区别是什么?
  • 在某种发送策略下,影响调高调低(分辨率/帧率)的因素有哪些?
  • 实时码率是从何而来?
  • 平均QP的计算方法,以及上调下调的极限
  • WebRTC是怎么检测CPU过载的(软编码、硬编码下不同的检测策略)

我强烈建议大家有时间多看看WebRTC的实现代码,里面有非常多值得我们学习的地方。

为 CEF/Chromium 添加 x264 编码器相关推荐

  1. 【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )

    文章目录 安卓直播推流专栏博客总结 一. x264 编码器参数设置引入 二. 获取 x264 编码器参数 三. 设置 x264 编码器编码规格 四. 设置 x264 编码器编码图像数据格式 五. 设置 ...

  2. ffmpeg使用x264编码的配置+ ffmpeg与 x264编码器参数完整对照表

    ffmpeg使用x264编码的配置+ ffmpeg与 x264编码器参数完整对照表 分类: 多媒体 2010-07-13 11:31 1072人阅读 评论(0) 收藏 举报 转载自:扶凯[http:/ ...

  3. x264 编码器选项分析 (x264 Codec Strong and Weak Points) 2

    文章目录: x264 编码器选项分析 (x264 Codec Strong and Weak Points) 1 x264 编码器选项分析 (x264 Codec Strong and Weak Po ...

  4. x264 编码器选项分析 (x264 Codec Strong and Weak Points) 1

    文章目录: x264 编码器选项分析 (x264 Codec Strong and Weak Points) 1 x264 编码器选项分析 (x264 Codec Strong and Weak Po ...

  5. 商鼎云|为Chromium添加星际文件系统协议支持

    ProtocolLabs和Igalia很高兴地宣布我们为网络和浏览器带来更多可扩展性和用户代理的一个重要里程碑: 支持Chromium中预定义的自定义协议处理程序! 简而言之:您现在可以构建Chrom ...

  6. x264源代码简单分析:编码器主干部分-2

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  7. x264源代码简单分析:编码器主干部分-1

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  8. 关于在Android音视频开发中,Google API的MediaCodeC与成熟开源编码器X264的应用对比及使用场景

    在2019年的一个大项目中,有一个功能模块让笔者感触颇深,那就是实时音视频的预览,当然这不是普通的开开直播,画面出来了就完了那么简单,如果你是一个开发者,那么你肯定知道同样大小的一张图片里,色彩丰富的 ...

  9. x265 (HEVC编码器,基于x264) 介绍

    x265要出来了.简单翻译了一下项目网站首页的介绍. x265是一个开源项目,是一个将视频编码为h.265/高效率的视频编码(HEVC)格式的免费的库,在GNU GPL条款下发布. 它的源代码是免费提 ...

最新文章

  1. Java基础之多线程详细分析
  2. vss2005与vs2005绑定问题解决
  3. 3种设计模式java小程序_Java设计模式之单例模式(3种实现方式)
  4. Java Web中的中文编码问题分析
  5. 香港印象:维多利亚港湾·张学友的手印
  6. 演练:在组件设计器中创建 Windows 服务应用程序
  7. Ubuntu出现没有正确安装GNOME电源管理器的默认配置
  8. jsp内置对象--session
  9. MySQL笔记(三)常用系统函数
  10. GraphicsMagick+im4java图片处理
  11. 关于忘记MySQL的root用户密码的问题
  12. 子程序入口参数是什么_三菱FX PLC | 什么是中断服务?没事多看几遍
  13. 利尔达芯智行智能BMS系统方案,让电池的“大脑”更聪明
  14. 3A算法—自动对焦(AF)
  15. Chrome 浏览器翻译停服!改Hosts也失效!还有这些解决方案
  16. android 六边形简书,六边形RecyclerView
  17. 哈罗单车再获10亿融资,摩拜、ofo难合并!
  18. 3D角色硬表面建模技巧与思路分享【案例解析】
  19. 再没时间 创业时也要读下这十本经典书
  20. python-requests请求超时解决方案

热门文章

  1. Hololens 简体中文版的安装步骤(2018.11)
  2. 电驴资源 凡尔纳“八十天环球地球”小说英文全文朗读plusPHD
  3. 李野默:《平凡的世界》 (最后三集)
  4. Whonix - Things NOT to do 匿名上网的时候千万不要做的事情——摘要
  5. 【随手记】滑板——(1)滑行
  6. EAIDK-610 板卡目录
  7. love2d教程27--菜单
  8. 如何爬取王者荣耀高清壁纸(详细分析教程)
  9. elementU中table表格模板(拿来即用)
  10. [每日App一]QQ主题要逆天!轻轻松松月入30万!