直播框架圖

引入HEVC編碼,涉及到的變動部分,上圖中的紅色字體已標(biāo)注:

1.編碼模塊:需要支持HEVC格式的編解碼,該部分不屬于本文的介紹范疇,可自行查閱;

2.封裝/傳輸模塊:RTMP、HTTP-FLV流媒體協(xié)議需要增加對HEVC視頻編碼格式的支持,這一部分是本文介紹的重點(diǎn)。

廣大音視頻開發(fā)者對于FFmpeg并不陌生,由于它有著能在多媒體處理上提供強(qiáng)大功能、開源易于修改維護(hù)的特性,使得其被廣泛應(yīng)用于各類音視頻軟件中。由于Adobe暫停了對RTMP/FLV標(biāo)準(zhǔn)的更新,所以目前標(biāo)準(zhǔn)中沒有支持HEVC視頻編碼格式。為避免各終端和服務(wù)器間的兼容性問題,F(xiàn)Fmpeg也沒有在RTMP/FLV的協(xié)議實現(xiàn)中進(jìn)行HEVC的相關(guān)擴(kuò)展。CDN聯(lián)盟制定了相關(guān)的協(xié)議擴(kuò)展規(guī)范,并在FFmpeg中完成了相關(guān)代碼實現(xiàn)。

FFmpeg簡析

開源程序FFmpeg發(fā)展至今,功能日益強(qiáng)大,很多初學(xué)者都被其眾多的源文件、龐大的結(jié)構(gòu)體和復(fù)雜的算法打消了繼續(xù)學(xué)習(xí)的念頭。我們先從總體上對FFmpeg進(jìn)行解析。

FFmpeg包含如下類庫:

libavformat – 用于各種音視頻封裝格式的生成和解析,包括獲取解碼所需信息、讀取音視頻數(shù)據(jù)等功能。各種流媒體協(xié)議代碼(如rtmpproto.c等)以及音視頻格式的(解)復(fù)用代碼(如flvdec.c、flvenc.c等)都位于該目錄下。

libavcodec – 音視頻各種格式的編解碼。各種格式的編解碼代碼(如aacenc.c、aacdec.c等)都位于該目錄下。

libavutil – 包含一些公共的工具函數(shù)的使用庫,包括算數(shù)運(yùn)算,字符操作等。

libswscale – 提供原始視頻的比例縮放、色彩映射轉(zhuǎn)換、圖像顏色空間或格式轉(zhuǎn)換的功能。

libswresample – 提供音頻重采樣,采樣格式轉(zhuǎn)換和混合等功能。

libavfilter – 各種音視頻濾波器。

libpostproc – 用于后期效果處理,如圖像的去塊效應(yīng)等。

libavdevice – 用于硬件的音視頻采集、加速和顯示。

如果之前沒有閱讀FFmpeg代碼的經(jīng)驗,建議優(yōu)先閱讀libavformat、libavcodec以及l(fā)ibavutil下面的代碼,它們提供了音視頻開發(fā)的基本功能,應(yīng)用范圍也最廣。

常用結(jié)構(gòu)

FFmpeg里面最常用的數(shù)據(jù)結(jié)構(gòu),按功能可大致分為以下幾類(以下代碼行數(shù),以branch: origin/release/3.4為準(zhǔn)):

1.封裝格式

AVFormatContext – 描述了媒體文件的構(gòu)成及基本信息,是統(tǒng)領(lǐng)全局的基本結(jié)構(gòu)體,貫穿程序始終,很多函數(shù)都要用它作為參數(shù);

AVInputFormat – 解復(fù)用器對象,每種作為輸入的封裝格式(例如FLV、MP4、TS等)對應(yīng)一個該結(jié)構(gòu)體,如libavformat/flvdec.c的ff_flv_demuxer;

AVOutputFormat – 復(fù)用器對象,每種作為輸出的封裝格式(例如FLV,?MP4、TS等)對應(yīng)一個該結(jié)構(gòu)體,如libavformat/flvenc.c的ff_flv_muxer;

AVStream – 用于描述一個視頻/音頻流的相關(guān)數(shù)據(jù)信息。

2.編解碼

AVCodecContext – 描述編解碼器上下文的數(shù)據(jù)結(jié)構(gòu),包含了眾多編解碼器需要的參數(shù)信息;

AVCodec – 編解碼器對象,每種編解碼格式(例如H.264、AAC等)對應(yīng)一個該結(jié)構(gòu)體,如libavcodec/aacdec.c的ff_aac_decoder。每個AVCodecContext中含有一個AVCodec;

AVCodecParameters – 編解碼參數(shù),每個AVStream中都含有一個AVCodecParameters,用來存放當(dāng)前流的編解碼參數(shù)。

3.網(wǎng)絡(luò)協(xié)議

AVIOContext – 管理輸入輸出數(shù)據(jù)的結(jié)構(gòu)體;

URLProtocol – 描述了音視頻數(shù)據(jù)傳輸所使用的協(xié)議,每種傳輸協(xié)議(例如HTTP、RTMP)等,都會對應(yīng)一個URLProtocol結(jié)構(gòu),如libavformat/http.c中的ff_http_protocol;

URLContext – 封裝了協(xié)議對象及協(xié)議操作對象。

4.數(shù)據(jù)存放

AVPacket – 存放編碼后、解碼前的壓縮數(shù)據(jù),即ES數(shù)據(jù);

AVFrame – 存放編碼前、解碼后的原始數(shù)據(jù),如YUV格式的視頻數(shù)據(jù)或PCM格式的音頻數(shù)據(jù)等;

上述結(jié)構(gòu)體的關(guān)系圖如下所示(箭頭表示派生出):

  FFmpeg結(jié)構(gòu)體關(guān)系圖

代碼結(jié)構(gòu)

下面這段代碼完成了讀取媒體文件中音視頻數(shù)據(jù)的基本功能,本節(jié)以此為例,分析FFmpeg內(nèi)部代碼的調(diào)用邏輯。

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

AVPacket pkt;

int ret = 0;

//注冊復(fù)用器、編碼器等

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);

//讀取音視頻數(shù)據(jù)

while(ret >= 0)

{

ret = av_read_frame(s, &pkt);

}

1.注冊

av_register_all函數(shù)的作用是注冊一系列的(解)復(fù)用器、編/解碼器等。它在所有基于FFmpeg的應(yīng)用程序中幾乎都是第一個被調(diào)用的,只有調(diào)用了該函數(shù),才能使用復(fù)用器、編碼器等。

static void register_all(void)

{

avcodec_register_all();

/* (de)muxers */

……

REGISTER_MUXDEMUX(FLV, flv);

……

}

REGISTER_MUXDEMUX實際上調(diào)用的是av_register_input_format和av_register_output_format,通過這兩個方法,將(解)復(fù)用器分別添加到了全局變量first_iformat與first_oformat鏈表的最后位置。

編/解碼其注冊過程相同,此處不再贅述。

2.文件打開

FFmpeg讀取媒體數(shù)據(jù)的過程始于avformat_open_input,該方法中完成了媒體文件的打開和格式探測的功能。但FFmpeg是如何找到正確的流媒體協(xié)議和解復(fù)用器呢?可以看到avformat_open_input方法中調(diào)用了init_input函數(shù),在這里面完成了查找流媒體協(xié)議和解復(fù)用器的工作。

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實際上調(diào)用的就是io_open_default,它最終調(diào)用到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可以得到當(dāng)前編譯的FFmpeg支持的所有流媒體協(xié)議,通過url的scheme和protocol->name相比較,得到正確的protocol。例如本例中URLProtocol最終指向了libavformat/http.c中的ff_http_protocol。

(2)av_probe_input_buffer2最終調(diào)用到av_probe_input_format3,該方法遍歷所有的解復(fù)用器,即first_iformat鏈表中的所有節(jié)點(diǎn),調(diào)用它們的read_probe()函數(shù)計算匹配得分,函數(shù)最終返回計算找到的最匹配的解復(fù)用器。本例中AVInputFormat最終指向了libavformat/flvdec.c中的ff_flv_demuxer。

3.數(shù)據(jù)讀取

av_read_frame作用是讀取媒體數(shù)據(jù)中的每個音視頻幀,該方法中最關(guān)鍵的地方就是調(diào)用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一個函數(shù)指針,指向當(dāng)前的AVInputFormat的讀取數(shù)據(jù)的函數(shù)。在本例中,AVInputFormat為ff_flv_demuxer,也就是說read_packet最終指向了flv_read_packet。

FLV文件結(jié)構(gòu)解析

FLV(FLASH VIDEO),是一種常用的文件封裝格式,目前國內(nèi)外大部分視頻分享網(wǎng)站都是采用的這種格式。其標(biāo)準(zhǔn)定義為《Adobe Flash Video File Format Specification》。RTMP協(xié)議也是基于FLV視頻格式的。

FLV的文件格式在該規(guī)范中已闡述清楚,本章節(jié)不再重復(fù)描述,而是結(jié)合下面的示例具體闡述如何分析FLV文件。

  FLV文件結(jié)構(gòu)示例1

  FLV文件結(jié)構(gòu)示例2

FLV文件的分析工具有很多,這里給大家推薦FLV Parser這個小軟件,通過它可以很容易的看到文件的組成結(jié)構(gòu)。

文件結(jié)構(gòu)

從整個文件上看,F(xiàn)LV是由Header和File Body組成,如下圖所示:

  FLV文件總體結(jié)構(gòu)

1.FLV Header – 長度為9,其結(jié)構(gòu)的標(biāo)準(zhǔn)定義參見標(biāo)準(zhǔn)定義見E.2 The FLV header;

2.FLV File Body – 由一連串的PreviousTagSize + Tag構(gòu)成。previousTagSize是4個字節(jié)的數(shù)據(jù),表示前一個tag的size。標(biāo)準(zhǔn)定義參見E.3 The FLV File Body。

以FLV文件結(jié)構(gòu)示例1這張圖為例,分析整體結(jié)構(gòu):

1.位置0x00000000 – 0x00000008, 共9個字節(jié),為FLV Header,其中:

0x00000000 – 0x00000002 : 0x46 0x4C 0x56分別表示字符’F”L”V’,用來標(biāo)識這個文件是FLV格式的。在做格式探測的時候,如果發(fā)現(xiàn)前3個字節(jié)為“FLV”,就認(rèn)為它是FLV文件;

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

0x00000004 : 0x05, 轉(zhuǎn)換為2進(jìn)制是0000 0101,其中第0位為1,表示存在video,第2位為1,表示存在audio;

0x00000005 – 0x00000008 : 0x00 0x00 0x00 0x09,轉(zhuǎn)十進(jìn)制為9,表示FLV header的長度,當(dāng)FLV 版本號為1時,該值通常為9。

2.位置0x00000009 – ,為FLV File Body:

0x00000009 – 0x0000000C : 0x00 0x00 0x00 0x00 PreviousTagSize0,轉(zhuǎn)十進(jìn)制為0,該值永遠(yuǎn)為0;

0x0000000D – 0x00000209 : 0x12 … 0x09,共509個字節(jié),為Tag1的具體內(nèi)容;

0x0000020A – 0x0000020D : 0x00 0x00 0x01 0xFD,轉(zhuǎn)十進(jìn)制為509,表示它前面的Tag,即Tag1的長度為509;

0x0000020E – :按照Tag + PreviousTagSize的結(jié)構(gòu)依次遞推,此處不再舉例說明。

Tag定義

FLV File Body是由一系列的PreviousTagSize + Tag組成,其中PreviousTagSize的長度為4個字節(jié),用來表示前一個Tag的長度;Tag里面的數(shù)據(jù)可能是video、audio或者scripts,其定義參見E.4.1 FLV Tag,結(jié)構(gòu)如下:

  FLV Tag 結(jié)構(gòu)

以圖3. FLV文件結(jié)構(gòu)示例1為例分析Tag結(jié)構(gòu):

1.位置0x0000020E : 0x08, 二進(jìn)制為0000 1000,第5位為0, 表示為非加擾文件;低5位01000為8,說明這個Tag包含的數(shù)據(jù)類型為Audio;

2.位置0x0000020F – 0x00000211 : 0x00 0x00 0x04,轉(zhuǎn)十進(jìn)制為4,說明Tag的內(nèi)容長度為4,與該tag后面的previousTagSize(15) – 11相同;

3.位置0x00000212 – 0x00000214 : 0x00 0x00 0x00,轉(zhuǎn)十進(jìn)制為0,說明當(dāng)前Audio數(shù)據(jù)的時間戳為0;

4.位置0x00000215 : 0x00,擴(kuò)展時間戳為0,如果擴(kuò)展時間戳不為0,那么該Tag的時間戳應(yīng)為:Timestamp | TimestampExtended<<24;

5.位置0x00000216 – 0x00000218 : 0x00 0x00 0x00,StreamID,總是0;

6.StreamID之后的數(shù)據(jù)每種格式的情況都不一樣,下面會依次進(jìn)行詳細(xì)解讀。

Audio Tags

如果TAG包中的TagType等于8,表示該Tag中包含的數(shù)據(jù)類型為Audio。StreamID之后的數(shù)據(jù)就是AudioTagHeader,其定義詳見E.4.2.1?AUDIODATA。結(jié)構(gòu)如下:

  FLV Audio Tag結(jié)構(gòu)

需要說明的是,通常情況下AudioTagHeader之后跟著的就是AUDIODATA數(shù)據(jù)了,但有個特例,如果音頻編碼格式為AAC,AudioTagHeader中會多出1個字節(jié)的數(shù)據(jù)AACPacketType,這個字段來表示AACAUDIODATA的類型:

0 = AAC sequence header

1 = AAC raw。

以FLV文件結(jié)構(gòu)示例這張圖為例,分析AudioTag結(jié)構(gòu):

1.位置0x00000219 : 0xAF, 二進(jìn)制表示為1010 1111:

高4位為1010,轉(zhuǎn)十進(jìn)制為10,表示Audio的編碼格式為AAC;

第3、2位為11,轉(zhuǎn)十進(jìn)制為3,表示該音頻的采樣率為44KHZ;

第1位為1,表示該音頻采樣點(diǎn)位寬為16bits;

第0位為1,表示該音頻為立體聲。

2.位置0x0000021A : 0x00,十進(jìn)制為0,并且Audio的編碼格式為AAC,說明AACAUDIODATA中存放的是AAC sequence header;

3.位置0x0000021B – 0x0000021C : AUDIODATA數(shù)據(jù),即AAC sequence header。

AudioSpecificConfig

AAC sequence header中存放的是AudioSpecificConfig,該結(jié)構(gòu)包含了更加詳細(xì)的音頻信息,《ISO-14496-3 Audio》中的1.6.2.1 章節(jié)對此作了詳細(xì)定義。

通常情況下,AAC sequence header這種Tag在FLV文件中只出現(xiàn)1次,并且是第一個Audio Tag,它存放了解碼AAC音頻所需要的詳細(xì)信息。

有關(guān)AudioSpecificConfig結(jié)構(gòu)的代碼解析,可以參考ffmpeg/libavcodec/mpeg4audio.c中的avpriv_mpeg4audio_get_config方法。

為什么AudioTagHeader中定義了音頻的相關(guān)參數(shù),我們還需要傳遞AudioSpecificConfig呢?

因為當(dāng)SoundFormat為AAC時,SoundType須設(shè)置為1(立體聲),SoundRate須設(shè)置為3(44KHZ),但這并不意味著FLV文件中AAC編碼的音頻必須是44KHZ的立體聲。播放器在播放AAC音頻時,應(yīng)忽略AudioTagHeader中的參數(shù),并根據(jù)AudioSpecificConfig來配置正確的解碼參數(shù)。

Video Tag

如果TAG包中的TagType等于9,表示該Tag中包含的數(shù)據(jù)類型為Video。StreamID之后的數(shù)據(jù)就是VideoTagHeader,其定義詳見E.4.3.1 VIDEODATA,結(jié)構(gòu)如下:

  FLV Video Tag結(jié)構(gòu)

VideoTagHeader之后跟著的就是VIDEODATA數(shù)據(jù)了,但是和AAC音頻一樣,它也存在一個特例,就是當(dāng)視頻編碼格式為H.264的時候,VideoTagHeader會多出4個字節(jié)的信息,AVCPacketType和CompositionTime。

AVCPacketType用來表示VIDEODATA的內(nèi)容。

CompositonTime相對時間戳,如果AVCPacketType=0x01,為相對時間戳,其它均為0;

以FLV文件結(jié)構(gòu)示例2這張圖為例,分析VideoTagHeader結(jié)構(gòu):

1.位置0x0000022C : 0x17, 二進(jìn)制表示為0001 0111:

高4位為0001,轉(zhuǎn)十進(jìn)制為1,表示當(dāng)前幀為關(guān)鍵幀;

低4位為0111,轉(zhuǎn)十進(jìn)制為7,說明當(dāng)前視頻的編碼格式為AVC。

2.位置0x0000022D : 0x00,十進(jìn)制為0,并且Video的編碼格式為AVC,說明VideoTagBody中存放的是AVC sequence header;

3.位置0x0000022E – 0x00000230 : 轉(zhuǎn)十進(jìn)制為0,表示相對時間戳為0;

4.位置0x00000231 – 0x0000021C : VIDEODATA數(shù)據(jù),即AVC sequence header。

AVCDecoderConfigurationRecord

AVC sequence header中存放的是AVCDecoderConfigurationRecord,《ISO-14496-15 AVC file format》對此作了詳細(xì)定義。它存放的是AVC的編碼參數(shù),解碼時需設(shè)置給解碼器后方可正確解碼。

通常情況下,AVC sequence header這種Tag在FLV文件中只出現(xiàn)1次,并且是第一個Video Tag。

有關(guān)AVCDecoderConfigurationRecord結(jié)構(gòu)的代碼解析,可以參考中的ff_isom_write_avcc方法。

CompositionTime(相對時間戳)

相對時間戳的概念需要和PTS、DTS一起理解:

DTS : Decode Time Stamp,解碼時間戳,用于告知解碼器該視頻幀的解碼時間;

PTS : Presentation Time Stamp,顯示時間戳,用于告知播放器該視頻幀的顯示時間;

CTS : Composition Time Stamp,相對時間戳,用來表示PTS與DTS的差值。

如果視頻里各幀的編碼是按輸入順序依次進(jìn)行的,則解碼和顯示時間相同,應(yīng)該是一致的。但在編碼后的視頻類型中,如果存在B幀,輸入順序和編碼順序并不一致,所以才需要PTS和DTS這兩種時間戳。視頻幀的解碼一定是發(fā)生在顯示前,所以視頻幀的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中包含的數(shù)據(jù)類型為SCRIPT。

SCRIPTDATA 結(jié)構(gòu)十分復(fù)雜,定義了很多格式類型,每個類型對應(yīng)一種結(jié)構(gòu),詳細(xì)可參考E.4.4 Data Tags

onMetaData是SCRIPTDATA中一個非常重要的信息,其結(jié)構(gòu)定義可參考E.5 onMetaData。它通常是FLV文件中的第一個Tag,用來表示當(dāng)前文件的一些基本信息: 比如視音頻的編碼類型id、視頻的寬和高、文件大小、視頻長度、創(chuàng)建日期等。

HEVC在RTMP中的擴(kuò)展

為推進(jìn)HEVC視頻編碼格式在直播方案中的落地,經(jīng)過CDN聯(lián)盟討論,并和主流云服務(wù)廠商達(dá)成一致,規(guī)范了HEVC在RTMP/FLV中的擴(kuò)展,具體修改內(nèi)容見下。

FLV規(guī)范擴(kuò)展

HEVC為視頻編碼格式,因此對FLV規(guī)范的擴(kuò)展,只集中在Video Tag,其它部分,無任何改動。

支持HEVC的VideoTagHeader

擴(kuò)展后的VideoTagHeader如下圖所示(紅色字體為新增內(nèi)容):

  支持HEVC的FLVTagHeader

修改點(diǎn)如下:

1.CodecID – 定義HEVC格式的值為12;

2.HEVCPacketType – 當(dāng)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 – 當(dāng)CodecID == 12時,同樣需要CompositionTime。

支持HEVC的VideoTagBody

當(dāng)CodecID為12時,VideoTagBody中存放的就是HEVC視頻幀內(nèi)容。

擴(kuò)展后的VideoTagBody如下圖所示(紅色字體為HEVC新增內(nèi)容):

  支持HEVC的VideoTagBody

FFmpeg中的修改

我們已在FFmpeg的各個版本上提供相關(guān)的完整修改,具體參見:https://github.com/ksvc/FFmpeg,完整patch獲取及相關(guān)說明見:https://github.com/ksvc/FFmpeg/wiki。

由第二章節(jié)的闡述可知,F(xiàn)LV的解復(fù)用和復(fù)用功能代碼分別在libavformt/flvdec.c和libavformat/flvenc.c中,擴(kuò)展后的修改也都集中在這兩個文件。本節(jié)將在FFmpeg3.3的基礎(chǔ)上,說明修改的關(guān)鍵點(diǎn)。

編碼類型定義

libavformat/flv.h中按照VideoTagHeader中的CodecID定義了一組視頻編碼格式的枚舉值,擴(kuò)展后的枚舉定義如下:

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

在解復(fù)用過程中,flv_read_packet方法是整個過程的核心,它里面完成了對每個Tag的讀取和解析。

上面提到,如果HEVCPacketType為0時,表示HEVCVIDEOPACKET中存放的是HEVC sequence header,也就是HEVCDecoderConfigurationRecord,解碼時需設(shè)置HEVCDecoderConfigurationRecord方能正確解碼。

HEVC與AVC視頻幀在FLV中的存放格式相同,所以只需在讀取Video Tag的地方增加AV_CODEC_ID_HEVC的判斷條件即可,調(diào)整后的代碼如下:

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。

同樣,當(dāng)視頻編碼格式HEVC時,也要寫入第一個VideoTag,其中VideoTagBody中存放的是HEVCDecoderConfigurationRecord,修改點(diǎn)如下:

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轉(zhuǎn)為HEVCDecoderConfigurationRecord結(jié)構(gòu)并寫入。

flv_write_packet的作用是寫入音視頻幀,其中有關(guān)寫入video數(shù)據(jù)的地方,都需要加上AV_CODEC_ID_HEVC的判斷條件,修改內(nèi)容如下:

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視頻幀轉(zhuǎn)為HVCC格式。

AnnexB與AVCC/HVCC(ISO/IEC14496-15中所定義,通常也稱為MPEG-4格式)的區(qū)別在于參數(shù)集與幀格式,AnnexB的參數(shù)集sps、pps以NAL的形式存在碼流中(帶內(nèi)傳輸),以startcode分割NAL。而HVCC 的參數(shù)集存儲在extradata中(帶外傳輸),使用NALU長度(固定字節(jié),通常為4字節(jié),從extradata中解析)分隔NAL。

結(jié)束時需要寫入HEVC end of sequence,其格式與AVC end of sequence相同,直接復(fù)用即可,flv_write_trailer的修改內(nèi)容如下:

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);

總結(jié)

本文對如何在FFmpeg中擴(kuò)展rtmp協(xié)議,從而支持HEVC編碼格式進(jìn)行了介紹。如果將HEVC應(yīng)用于直播整體方案,除了推流端和播放端需要提供相應(yīng)能力以外,包括源站、CDN、轉(zhuǎn)碼服務(wù),均需要提供這種能力。目前,作為視頻云行業(yè)的領(lǐng)跑者,金山云的所有視頻服務(wù)已完全支持HEVC視頻編碼格式,歡迎大家使用。對于直播、短視頻與HEVC的更多技術(shù)問題,我們會在接下來的系列文章中與大家繼續(xù)探討。

分享到

songjy

相關(guān)推薦