FFmpeg代码导读系列:HEVC在RTMP中的扩展

随着直播、短视频的持续火爆和产品功能的不断升级,音视频开发人员也面临着诸多的机遇与挑战。例如,在视频流媒体的传输过程中,视频数据的传输占据了绝大部分带宽,如何提升编码效率,占用更少带宽,提供更优质的画面质量,就成为了音视频开发人员努力的目标。HEVC(High Efficiency Video Coding,即H.265)编码格式的推出,为这一问题带来了解决的思路,但由于其算法复杂度较高,因此前期并未普遍应用,而随着移动设备计算能力的提升以及越来越多的设备对HEVC硬件编/解码的支持,直播平台也逐渐引入了HEVC这一视频格式。

HEVC属于视频编码标准,如果应用在视频流媒体中,需要相应的封装格式和流媒体协议的支持。鉴于直播的大部分推拉流协议基于RTMP(real time messaging protocol,实时消息传送协议,是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的私有协议),本文以金山云目前的做法为例,主要介绍如何在RTMP协议中增加对HEVC视频编码格式的支持。

直播框架解析

典型的直播框架通常包括三大部分,如下图所示:

1.推流端:负责音视频数据的采集、处理、编码及封装后将数据推送至源站;

2.服务端:涵盖源站和CDN,接收来自推流端的音视频数据,然后将数据分发至各播放端;

3.播放端:从CDN拉取直播数据,解复用、解码后渲染音视频数据;

FFmpeg代码导读系列:HEVC在RTMP中的扩展

直播框架图

引入HEVC编码,涉及到的变动部分,上图中的红色字体已标注:

1.编码模块:需要支持HEVC格式的编解码,该部分不属于本文的介绍范畴,可自行查阅;

2.封装/传输模块:RTMP、HTTP-FLV流媒体协议需要增加对HEVC视频编码格式的支持,这一部分是本文介绍的重点。

广大音视频开发者对于FFmpeg并不陌生,由于它有着能在多媒体处理上提供强大功能、开源易于修改维护的特性,使得其被广泛应用于各类音视频软件中。由于Adobe暂停了对RTMP/FLV标准的更新,所以目前标准中没有支持HEVC视频编码格式。为避免各终端和服务器间的兼容性问题,FFmpeg也没有在RTMP/FLV的协议实现中进行HEVC的相关扩展。CDN联盟制定了相关的协议扩展规范,并在FFmpeg中完成了相关代码实现。

FFmpeg简析

开源程序FFmpeg发展至今,功能日益强大,很多初学者都被其众多的源文件、庞大的结构体和复杂的算法打消了继续学习的念头。我们先从总体上对FFmpeg进行解析。

FFmpeg包含如下类库:

libavformat - 用于各种音视频封装格式的生成和解析,包括获取解码所需信息、读取音视频数据等功能。各种流媒体协议代码(如rtmpproto.c等)以及音视频格式的(解)复用代码(如flvdec.c、flvenc.c等)都位于该目录下。

libavcodec - 音视频各种格式的编解码。各种格式的编解码代码(如aacenc.c、aacdec.c等)都位于该目录下。

libavutil - 包含一些公共的工具函数的使用库,包括算数运算,字符操作等。

libswscale - 提供原始视频的比例缩放、色彩映射转换、图像颜色空间或格式转换的功能。

libswresample - 提供音频重采样,采样格式转换和混合等功能。

libavfilter - 各种音视频滤波器。

libpostproc - 用于后期效果处理,如图像的去块效应等。

libavdevice - 用于硬件的音视频采集、加速和显示。

如果之前没有阅读FFmpeg代码的经验,建议优先阅读libavformat、libavcodec以及libavutil下面的代码,它们提供了音视频开发的基本功能,应用范围也最广。

常用结构

FFmpeg里面最常用的数据结构,按功能可大致分为以下几类(以下代码行数,以branch: origin/release/3. 4 为准):

1.封装格式

AVFormatContext - 描述了媒体文件的构成及基本信息,是统领全局的基本结构体,贯穿程序始终,很多函数都要用它作为参数;

AVInputFormat - 解复用器对象,每种作为输入的封装格式(例如FLV、MP4、TS等)对应一个该结构体,如libavformat/flvdec.c的ff_flv_demuxer;

AVOutputFormat - 复用器对象,每种作为输出的封装格式(例如FLV, MP4、TS等)对应一个该结构体,如libavformat/flvenc.c的ff_flv_muxer;

AVStream - 用于描述一个视频/音频流的相关数据信息。

2.编解码

AVCodecContext - 描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息;

AVCodec - 编解码器对象,每种编解码格式(例如H.264、AAC等)对应一个该结构体,如libavcodec/aacdec.c的ff_aac_decoder。每个AVCodecContext中含有一个AVCodec;

AVCodecParameters - 编解码参数,每个AVStream中都含有一个AVCodecParameters,用来存放当前流的编解码参数。

3.网络协议

AVIOContext - 管理输入输出数据的结构体;

URLProtocol - 描述了音视频数据传输所使用的协议,每种传输协议(例如HTTP、RTMP)等,都会对应一个URLProtocol结构,如libavformat/http.c中的ff_http_protocol;

URLContext - 封装了协议对象及协议操作对象。

4.数据存放

AVPacket - 存放编码后、解码前的压缩数据,即ES数据;

AVFrame - 存放编码前、解码后的原始数据,如YUV格式的视频数据或PCM格式的音频数据等;

上述结构体的关系图如下所示(箭头表示派生出):

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FFmpeg结构体关系图

代码结构

下面这段代码完成了读取媒体文件中音视频数据的基本功能,本节以此为例,分析FFmpeg内部代码的调用逻辑。

const char *url = "http://192.168.1.105/test.flv";

AVPacket pkt;

int ret = 0;

//注册复用器、编码器等

av_register_all();

avformat_network_init();

//打开文件

AVFormatContext *fmtCtx = avformat_alloc_context();

ret = avformat_open_input(&fmtCtx, url, NULL, NULL);

ret = avformat_find_stream_info(fmtCtx, NULL);

//读取音视频数据

while(ret >= 0)

{

ret = av_read_frame(s, &pkt);

}

1.注册

av_register_all函数的作用是注册一系列的(解)复用器、编/解码器等。它在所有基于FFmpeg的应用程序中几乎都是第一个被调用的,只有调用了该函数,才能使用复用器、编码器等。

static void register_all(void)

{

avcodec_register_all();

/* (de)muxers */

……

REGISTER_MUXDEMUX(FLV, flv);

……

}

REGISTER_MUXDEMUX实际上调用的是av_register_input_format和av_register_output_format,通过这两个方法,将(解)复用器分别添加到了全局变量first_iformat与first_oformat链表的最后位置。

编/解码其注册过程相同,此处不再赘述。

2.文件打开

FFmpeg读取媒体数据的过程始于avformat_open_input,该方法中完成了媒体文件的打开和格式探测的功能。但FFmpeg是如何找到正确的流媒体协议和解复用器呢?可以看到avformat_open_input方法中调用了init_input函数,在这里面完成了查找流媒体协议和解复用器的工作。

static int init_input(AVFormatContext *s, const char *filename, AVDictionary **options)

{

int ret;

……

if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)

return ret;

if (s->iformat)

return 0;

return av_probe_input_buffer2(s->pb, &s->iformat, filename,

s, 0, s->format_probesize);

}

(1)s->io_open实际上调用的就是io_open_default,它最终调用到url_find_protocol方法。

static const struct URLProtocol *url_find_protocol(const char *filename)

{

const URLProtocol **protocols;

……

protocols = ffurl_get_protocols(NULL, NULL);

if (!protocols)

return NULL;

for (i = 0; protocols[i]; i++) {

constURLProtocol *up = protocols[i];

if (!strcmp(proto_str, up->name)) {

av_freep(&protocols);

return up;

}

if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) {

av_freep(&protocols);

return up;

}

}

av_freep(&protocols);

return NULL;

}

ffurl_get_protocols可以得到当前编译的FFmpeg支持的所有流媒体协议,通过url的scheme和protocol->name相比较,得到正确的protocol。例如本例中URLProtocol最终指向了libavformat/http.c中的ff_http_protocol。

(2)av_probe_input_buffer2 最终调用到av_probe_input_format3,该方法遍历所有的解复用器,即first_iformat链表中的所有节点,调用它们的read_probe()函数计算匹配得分,函数最终返回计算找到的最匹配的解复用器。本例中AVInputFormat最终指向了libavformat/flvdec.c中的ff_flv_demuxer。

3.数据读取

av_read_frame作用是读取媒体数据中的每个音视频帧,该方法中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。在本例中,AVInputFormat为ff_flv_demuxer,也就是说read_packet最终指向了flv_read_packet。

FLV文件结构解析

FLV(FLASH VIDEO),是一种常用的文件封装格式,目前国内外大部分视频分享网站都是采用的这种格式。其标准定义为《Adobe Flash Video File Format Specification》。RTMP协议也是基于FLV视频格式的。

FLV的文件格式在该规范中已阐述清楚,本章节不再重复描述,而是结合下面的示例具体阐述如何分析FLV文件。

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV文件结构示例1

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV文件结构示例2

FLV文件的分析工具有很多,这里给大家推荐FLV Parser这个小软件,通过它可以很容易的看到文件的组成结构。

文件结构

从整个文件上看,FLV是由Header和File Body组成,如下图所示:

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV文件总体结构

1.FLV Header - 长度为9,其结构的标准定义参见标准定义见E.2 The FLV header;

2.FLV File Body - 由一连串的PreviousTagSize + Tag构成。previousTagSize是 4 个字节的数据,表示前一个tag的size。标准定义参见E.3 The FLV File Body。

以FLV文件结构示例 1 这张图为例,分析整体结构:

1.位置0x00000000 - 0x00000008, 共 9 个字节,为FLV Header,其中:

0x00000000 - 0x00000002 : 0x46 0x4C 0x56 分别表示字符'F''L''V',用来标识这个文件是FLV格式的。在做格式探测的时候,如果发现前 3 个字节为“FLV”,就认为它是FLV文件;

0x00000003 : 0x01, 表示FLV版本号;

0x00000004 : 0x05, 转换为 2 进制是0000 0101,其中第 0 位为1,表示存在video,第 2 位为1,表示存在audio;

0x00000005 - 0x00000008 : 0x00 0x00 0x00 0x09,转十进制为9,表示FLV header的长度,当FLV 版本号为 1 时,该值通常为9。

2.位置0x00000009 - ,为FLV File Body:

0x00000009 - 0x0000000C : 0x00 0x00 0x00 0x00 PreviousTagSize0,转十进制为0,该值永远为0;

0x0000000D - 0x00000209 : 0x12 ... 0x09,共 509 个字节,为Tag1 的具体内容;

0x0000020A - 0x0000020D : 0x00 0x00 0x01 0xFD,转十进制为509,表示它前面的Tag,即Tag1 的长度为509;

0x0000020E - :按照Tag + PreviousTagSize的结构依次递推,此处不再举例说明。

Tag定义

FLV File Body是由一系列的PreviousTagSize + Tag组成,其中PreviousTagSize的长度为 4 个字节,用来表示前一个Tag的长度;Tag里面的数据可能是video、audio或者scripts,其定义参见E.4.1 FLV Tag,结构如下:

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV Tag 结构

以图3. FLV文件结构示例 1 为例分析Tag结构:

1.位置0x0000020E : 0x08, 二进制为0000 1000,第 5 位为0, 表示为非加扰文件;低 5 位 01000 为8,说明这个Tag包含的数据类型为Audio;

2.位置0x0000020F - 0x00000211 : 0x00 0x00 0x04,转十进制为4,说明Tag的内容长度为4,与该tag后面的previousTagSize(15) -11 相同;

3.位置0x00000212 - 0x00000214 : 0x00 0x00 0x00,转十进制为0,说明当前Audio数据的时间戳为0;

4.位置0x00000215 : 0x00,扩展时间戳为0,如果扩展时间戳不为0,那么该Tag的时间戳应为:Timestamp | TimestampExtended<<24;

5.位置0x00000216 - 0x00000218 : 0x00 0x00 0x00,StreamID,总是0;

6.StreamID之后的数据每种格式的情况都不一样,下面会依次进行详细解读。

Audio Tags

如果TAG包中的TagType等于8,表示该Tag中包含的数据类型为Audio。StreamID之后的数据就是AudioTagHeader,其定义详见E.4.2.1 AUDIODATA。结构如下:

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV Audio Tag结构

需要说明的是,通常情况下AudioTagHeader之后跟着的就是AUDIODATA数据了,但有个特例,如果音频编码格式为AAC,AudioTagHeader中会多出 1 个字节的数据AACPacketType,这个字段来表示AACAUDIODATA的类型:

0 = AAC sequence header

1 = AAC raw。

以FLV文件结构示例这张图为例,分析AudioTag结构:

1.位置0x00000219 : 0xAF, 二进制表示为1010 1111:

高 4 位为1010,转十进制为10,表示Audio的编码格式为AAC;

第3、 2 位为11,转十进制为3,表示该音频的采样率为44KHZ;

第 1 位为1,表示该音频采样点位宽为16bits;

第 0 位为1,表示该音频为立体声。

2.位置0x0000021A : 0x00,十进制为0,并且Audio的编码格式为AAC,说明AACAUDIODATA中存放的是AAC sequence header;

3.位置0x0000021B - 0x0000021C : AUDIODATA数据,即AAC sequence header。

AudioSpecificConfig

AAC sequence header中存放的是AudioSpecificConfig,该结构包含了更加详细的音频信息,《ISO-14496-3 Audio》中的1.6.2.1 章节对此作了详细定义。

通常情况下,AAC sequence header这种Tag在FLV文件中只出现 1 次,并且是第一个Audio Tag,它存放了解码AAC音频所需要的详细信息。

有关AudioSpecificConfig结构的代码解析,可以参考ffmpeg/libavcodec/mpeg4audio.c中的avpriv_mpeg4audio_get_config方法。

为什么AudioTagHeader中定义了音频的相关参数,我们还需要传递AudioSpecificConfig呢?

因为当SoundFormat为AAC时,SoundType须设置为1(立体声),SoundRate须设置为3(44KHZ),但这并不意味着FLV文件中AAC编码的音频必须是44KHZ的立体声。播放器在播放AAC音频时,应忽略AudioTagHeader中的参数,并根据AudioSpecificConfig来配置正确的解码参数。

Video Tag

如果TAG包中的TagType等于9,表示该Tag中包含的数据类型为Video。StreamID之后的数据就是VideoTagHeader,其定义详见E.4.3.1 VIDEODATA,结构如下:

FFmpeg代码导读系列:HEVC在RTMP中的扩展

FLV Video Tag结构

VideoTagHeader之后跟着的就是VIDEODATA数据了,但是和AAC音频一样,它也存在一个特例,就是当视频编码格式为H. 264 的时候,VideoTagHeader会多出 4 个字节的信息,AVCPacketType和CompositionTime。

AVCPacketType用来表示VIDEODATA的内容。

CompositonTime相对时间戳,如果AVCPacketType=0x01,为相对时间戳,其它均为0;

以FLV文件结构示例 2 这张图为例,分析VideoTagHeader结构:

1.位置0x0000022C : 0x17, 二进制表示为0001 0111:

高 4 位为0001,转十进制为1,表示当前帧为关键帧;

低 4 位为0111,转十进制为7,说明当前视频的编码格式为AVC。

2.位置0x0000022D : 0x00,十进制为0,并且Video的编码格式为AVC,说明VideoTagBody中存放的是AVC sequence header;

3.位置0x0000022E - 0x00000230 : 转十进制为0,表示相对时间戳为0;

4.位置0x00000231 - 0x0000021C : VIDEODATA数据,即AVC sequence header。

AVCDecoderConfigurationRecord

AVC sequence header中存放的是AVCDecoderConfigurationRecord,《ISO-14496-15 AVC file format》对此作了详细定义。它存放的是AVC的编码参数,解码时需设置给解码器后方可正确解码。

通常情况下,AVC sequence header这种Tag在FLV文件中只出现 1 次,并且是第一个Video Tag。

有关AVCDecoderConfigurationRecord结构的代码解析,可以参考中的ff_isom_write_avcc方法。

CompositionTime(相对时间戳)

相对时间戳的概念需要和PTS、DTS一起理解:

DTS : Decode Time Stamp,解码时间戳,用于告知解码器该视频帧的解码时间;

PTS : Presentation Time Stamp,显示时间戳,用于告知播放器该视频帧的显示时间;

CTS : Composition Time Stamp,相对时间戳,用来表示PTS与DTS的差值。

如果视频里各帧的编码是按输入顺序依次进行的,则解码和显示时间相同,应该是一致的。但在编码后的视频类型中,如果存在B帧,输入顺序和编码顺序并不一致,所以才需要PTS和DTS这两种时间戳。视频帧的解码一定是发生在显示前,所以视频帧的PTS,一定是大于等于DTS的,因此CTS=PTS-DTS。

FLV Video Tag中的TimeStamp,不是PTS,而是DTS,视频帧的PTS需要我们通过DTS + CTS计算得到。

为什么Audio Tag不需要CompositionTime呢?

因为Audio的编码顺序和输入顺序一致,即PTS=DTS,所以它没有CompositionTime的概念。

Script Data Tags

如果TAG包中的TagType等于18,表示该Tag中包含的数据类型为SCRIPT。

SCRIPTDATA 结构十分复杂,定义了很多格式类型,每个类型对应一种结构,详细可参考E.4.4 Data Tags

onMetaData是SCRIPTDATA中一个非常重要的信息,其结构定义可参考E.5 onMetaData。它通常是FLV文件中的第一个Tag,用来表示当前文件的一些基本信息: 比如视音频的编码类型id、视频的宽和高、文件大小、视频长度、创建日期等。

HEVC在RTMP中的扩展

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

FLV规范扩展

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

支持HEVC的VideoTagHeader

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

FFmpeg代码导读系列:HEVC在RTMP中的扩展

支持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。

支持HEVC的VideoTagBody

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

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

FFmpeg代码导读系列:HEVC在RTMP中的扩展

支持HEVC的VideoTagBody

FFmpeg中的修改

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

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

编码类型定义

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,

};

FLV demux

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

上面提到,如果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中。

FLV mux

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

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结构并写入。

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。

结束时需要写入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);

总结

本文对如何在FFmpeg中扩展rtmp协议,从而支持HEVC编码格式进行了介绍。如果将HEVC应用于直播整体方案,除了推流端和播放端需要提供相应能力以外,包括源站、CDN、转码服务,均需要提供这种能力。目前,作为视频云行业的领跑者,金山云的所有视频服务已完全支持HEVC视频编码格式,欢迎大家使用。对于直播、短视频与HEVC的更多技术问题,我们会在接下来的系列文章中与大家继续探讨。

免责声明:本文为厂商推广稿件,企业发布本文的目的在于推广其产品或服务,安科网发布此文仅为传递信息,不代表安科网赞同其观点,不对对内容真实性负责,仅供用户参考之用,不构成任何投资、使用等行为的建议。请读者使用之前核实真实性,以及可能存在的风险,任何后果均由读者自行承担。

相关推荐