开发者社区> livevideostack> 正文

FFmpeg代码导读——HEVC在RTMP中的扩展

简介: 视频流媒体中视频数据的传输占据了绝大部分的带宽,如何提升编码效率、减小带宽使用、提升画面质量,成为音视频开发者努力的重点。
+关注继续查看

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1


视频流媒体中视频数据的传输占据了绝大部分的带宽,如何提升编码效率、减小带宽使用、提升画面质量,成为音视频开发者努力的重点。HEVC编码格式的推出为此带来了突破点。对于直播而言,大部分推拉流协议是基于RTMP的,因此本文将主要介绍如何在RTMP协议中增加对HEVC视频编码格式的支持。


本文来自金山云OBG事业部投稿,是《FFmpeg从入门到出家》系列的第一篇下半部分,由LiveVideoStack审校整理,希望能让大家对FFmpeg有更深入了解。阅读上半部分内容请点击【阅读原文】


文 / 施雪梅


4. HEVC在RTMP中的扩展


为推进HEVC视频编码格式在直播方案中的落地,经过CDN联盟讨论,并和主流云服务厂商达成一致,规范了HEVC在RTMP/FLV中的扩展,具体修改内容见下。


4.1 FLV规范扩展


HEVC为视频编码格式,因此对FLV规范的扩展,只集中在Video Tag,其它部分,无任何改动。


4.1.1 支持HEVC的VideoTagHeader


扩展后的VideoTagHeader如下图所示(红色字体为新增内容):

 

0?wx_fmt=png

图9. 支持HEVC的FLVTagHeader


修改点如下:


1.CodecID - 定义HEVC格式的值为12;

2.HEVCPacketType - 当CodecID == 12时,AVCPacketType为HEVCPacketType:

  • 如果HEVCPacketType为0,表示HEVCVIDEOPACKET中存放的是HEVC sequence header;

  • 如果HEVCPacketType为1,表示HEVCVIDEOPACKET中存放的是HEVC NALU;

  • 如果HEVCPacketType为2,表示HEVCVIDEPACKET中存放的是HEVC end of sequence,即HEVCDecoderConfigurationRecord;

3.CompositionTime - 当CodecID == 12时,同样需要CompositionTime。


4.1.2 支持HEVC的VideoTagBody


当CodecID为12时,VideoTagBody中存放的就是HEVC视频帧内容。

扩展后的VideoTagBody如下图所示(红色字体为HEVC新增内容):

 

0?wx_fmt=png

图10. 支持HEVC的VideoTagBody


4.2 FFmpeg中的修改


我们已在FFmpeg的各个版本上提供相关的完整修改,具体参见:https://github.com/ksvc/FFmpeg,完整patch获取及相关说明见:https://github.com/ksvc/FFmpeg/wiki。


由第二章节的阐述可知,FLV的解复用和复用功能代码分别在libavformt/flvdec.c和libavformat/flvenc.c中,扩展后的修改也都集中在这两个文件。本节将在FFmpeg3.3的基础上,说明修改的关键点。


4.2.1 编码类型定义


libavformat/flv.h中按照VideoTagHeader中的CodecID定义了一组视频编码格式的枚举值,扩展后的枚举定义如下:



enum {
   FLV_CODECID_H263    = 2,
   FLV_CODECID_SCREEN  = 3,
   FLV_CODECID_VP6     = 4,
   FLV_CODECID_VP6A    = 5,
   FLV_CODECID_SCREEN2 = 6,
   FLV_CODECID_H264    = 7,
   FLV_CODECID_REALH263= 8,
   FLV_CODECID_MPEG4   = 9,
   FLV_CODECID_HEVC   = 12,
};


4.2.2 FLV demux


在解复用过程中,flv_read_packet方法是整个过程的核心,它里面完成了对每个Tag的读取和解析。


4.1.1中提到,如果HEVCPacketType为0时,表示HEVCVIDEOPACKET中存放的是HEVC sequence header,也就是HEVCDecoderConfigurationRecord,解码时需设置HEVCDecoderConfigurationRecord方能正确解码。


HEVC与AVC视频帧在FLV中的存放格式相同,所以只需在读取Video Tag的地方增加AV_CODEC_ID_HEVC的判断条件即可,调整后的代码如下:



if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||
    st->codecpar->codec_id == AV_CODEC_ID_H264 ||
    st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
   st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {
   int type = avio_r8(s->pb);
   size--;
   if (st->codecpar->codec_id == AV_CODEC_ID_H264 ||
        st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
        st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {
        // sign extension
        int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;
        pts = dts + cts;
        if (cts< 0) { // dts might be wrong
            if (!flv->wrong_dts)
                av_log(s, AV_LOG_WARNING,
                       "Negative cts, previous timestamps might be wrong.\n");
           flv->wrong_dts = 1;
       } else if (FFABS(dts - pts) > 1000*60*15) {
               av_log(s, AV_LOG_WARNING,
                      "invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);
               dts = pts = AV_NOPTS_VALUE;
       }
  }
 
  if (type == 0 &&(!st->codecpar->extradata ||
      st->codecpar->codec_id == AV_CODEC_ID_AAC ||
      st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
      st->codecpar->codec_id == AV_CODEC_ID_H264)) {
      AVDictionaryEntry *t;
      if (st->codecpar->extradata) {
          if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)
              return ret;
          ret = FFERROR_REDO;
          goto leave;
      }
      if ((ret = flv_get_extradata(s, st, size)) < 0)
          return ret;
      ……
  }
}

AVCDecoderConfigurationRecord和HEVCDecoderConfigurationRecord都是存放在AVStream->AVCodecParameter->extradata中。

4.2.3 FLV mux


FLV mux的修改相对较多、header、packet、trailer中均有涉及。


4.2.3.1 write header


flv_write_header中主要完成了以下工作:


  1. 写入FLV Header;

  2. 写入Metadata;

  3. 如果音频编码格式为AAC,则写入第一个Audio Tag,其AudioTagBody中存放的是AAC sequence header;

  4. 如果视频编码格式为AVC,则写入第一个Video Tag,其中VideoTagBody中存放的是AVC sequence header。


同样,当视频编码格式HEVC时,也要写入第一个VideoTag,其中VideoTagBody中存放的是HEVCDecoderConfigurationRecord,修改点如下:



avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags
avio_w8(pb, 0); // AVC sequence header
avio_wb24(pb, 0); // composition time

if (par->codec_id == AV_CODEC_ID_HEVC)
   ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0);
else
   ff_isom_write_avcc(pb, par->extradata, par->extradata_size);


ff_isom_write_hvcc的作用是将extradata转为HEVCDecoderConfigurationRecord结构并写入。


4.2.3.2 write packet


flv_write_packet的作用是写入音视频帧,其中有关写入video数据的地方,都需要加上AV_CODEC_ID_HEVC的判断条件,修改内容如下:



   else if (par->codec_id == AV_CODEC_ID_HEVC ){
       if (par->extradata_size> 0 && *(uint8_t*)par->extradata != 1)
           if ((ret = ff_hevc_annexb2mp4_buf(pkt->data, &data, &size, 0, NULL)) < 0)
               return ret;
   }


ff_hevc_annexb2mp4_buf方法的作用是将Annex-B格式的HEVC视频帧转为HVCC格式。


AnnexB与AVCC/HVCC(ISO/IEC14496-15中所定义,通常也称为MPEG-4格式)的区别在于参数集与帧格式,AnnexB的参数集sps、pps以NAL的形式存在码流中(带内传输),以startcode分割NAL。而HVCC 的参数集存储在extradata中(带外传输),使用NALU长度(固定字节,通常为4字节,从extradata中解析)分隔NAL。


4.2.3.3 write trailer


结束时需要写入HEVC end of sequence,其格式与AVC end of sequence相同,直接复用即可,flv_write_trailer的修改内容如下:



if (par->codec_type == AVMEDIA_TYPE_VIDEO &&
          (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_HEVC || par->codec_id == AV_CODEC_ID_MPEG4))
     put_avc_eos_tag(pb, sc->last_ts); 


5. 结束语


本文简单介绍了如何在FFmpeg中扩展rtmp协议对HEVC编码格式的支持,而要将HEVC应用于直播整体方案,除推流端和播放端要提供相应能力外,源站、CDN、转码服务同样都需要提供这种能力。金山云的所有视频服务中,已完全支持HEVC视频编码格式,欢迎大家使用。


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
29523 0
VC图形绘制双缓存的代码复用性讨论
在前文中已经讨论了如何实现界面绘制双缓存的问题,前文网址如下: http://www.2cto.com/kf/201111/112429.html   双缓存的主要思路是:先把图形绘制到内存DC中,然后再把内存DC中的所有图形一次性的复制到屏幕DC中。
692 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
20894 0
只更新代码,然后发布版本:基于 Serverless Devs 原子化操作阿里云函数计算
本文以阿里云为例,通过在 Github 上使用 Servelress Devs 单纯对代码进行更新,并进行版本发布,该流程是比较常见的,也是比较通用的,希望读者可以发挥想象力,将这个流程应用到自己的项目中。
146 0
<转>VC编译的除法的一段汇编代码解释
int main(int argc, char* argv[]){      int i;      scanf("%d", &i);      int j = i / 2;      printf("%d", j);return 0;} 编译后: 10:         int j = i /...
509 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23609 0
<转>vc 用ado访问Oracle数据库的代码示例
#pragma once class CDBOp { public: bool ReConnect(); bool CloseConnect(); bool OpenConnect(CString hostName, CString dBName, CString us...
908 0
【软件测试】3、代码检查与Code Review
对于一家技术研发流程完善的技术公司来说,代码审查都是必不可少的一部分。虽然大部分代码审查工作都是研发团队的工程师完成,广义上讲,代码审查也是软件测试的一部分。
910 0
+关注
livevideostack
LiveVideoStack是专注在音视频领域的技术社区媒体,成立于2017年初,通过LiveVideoStackCon等技术大会、技术培训、高质量技术内容及咨询服务,推动相关开源项目与最佳实践普及和传播,帮助技术人成长,解决企业发展中的技术难点。
244
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载