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


    4. HEVC在RTMP中的扩展

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

    4.1 FLV规范扩展

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

    4.1.1 支持HEVC的VideoTagHeader

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

    图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新增内容):

    图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视频编码格式,欢迎大家使用。

  • 相关阅读:
    信号
    序列化数据的两种方式
    ModelForm的使用
    分页模板
    Django中间件进行用户登陆验证
    Flask 笔记一
    pipenv 使用基本命令
    git本地文件 上传 远程仓库
    service "$service" status 返回的状态
    &>/dev/null 的作用
  • 原文地址:https://www.cnblogs.com/lidabo/p/15752250.html
Copyright © 2020-2023  润新知