FFmpeg RTMP推HEVC/H265流
阿新 • • 發佈:2019-01-09
直播流媒體協議中,HLS和RTMP協議是兩大主流協議。而眾所周知的原因,RTMP在許多年前就已經停止拓展和更新,因此標準一直無法支援HEVC的編碼格式。目前國內的CDN還有金山雲等已經對RTMP進行了標準擴充套件,播放器上ijkplayer也擴充套件了該修改。
具體FFmpeg的修改程式碼如下:
From e40fcb1113cb1c93c48b8ef74b8aec6437f23d84 Mon Sep 17 00:00:00 2001 From: Franken Zeng <[email protected]> Date: Wed, 14 Jun 2017 21:33:54 +0800 Subject: [PATCH] The RTMP protocol extensions for H.265/HEVC --- libavformat/flv.h | 1 + libavformat/flvdec.c | 16 +++++++++++++--- libavformat/flvenc.c | 29 ++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/libavformat/flv.h b/libavformat/flv.h index df5ce3d17f8..089bc76972d 100644 --- a/libavformat/flv.h +++ b/libavformat/flv.h @@ -109,6 +109,7 @@ enum { FLV_CODECID_H264 = 7, FLV_CODECID_REALH263= 8, FLV_CODECID_MPEG4 = 9, + FLV_CODECID_HEVC = 12, }; enum { diff --git a/libavformat/flvdec.c b/libavformat/flvdec.c index 2d89bef15f2..c8073b88a92 100644 --- a/libavformat/flvdec.c +++ b/libavformat/flvdec.c @@ -36,6 +36,7 @@ #include "internal.h" #include "avio_internal.h" #include "flv.h" +#include "hevc.h" #define VALIDATE_INDEX_TS_THRESH 2500 @@ -291,6 +292,8 @@ static int flv_same_video_codec(AVCodecParameters *vpar, int flags) return vpar->codec_id == AV_CODEC_ID_VP6A; case FLV_CODECID_H264: return vpar->codec_id == AV_CODEC_ID_H264; + case FLV_CODECID_HEVC: + return vpar->codec_id == AV_CODEC_ID_HEVC; default: return vpar->codec_tag == flv_codecid; } @@ -340,6 +343,11 @@ static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, par->codec_id = AV_CODEC_ID_MPEG4; ret = 3; break; + case FLV_CODECID_HEVC: + par->codec_id = AV_CODEC_ID_HEVC; + vstream->need_parsing = AVSTREAM_PARSE_NONE; + ret = 3; // not 4, reading packet type will consume one byte + break; default: avpriv_request_sample(s, "Video codec (%x)", flv_codecid); par->codec_tag = flv_codecid; @@ -1149,10 +1157,12 @@ static int flv_read_packet(AVFormatContext *s, AVPacket *pkt) if (st->codecpar->codec_id == AV_CODEC_ID_AAC || st->codecpar->codec_id == AV_CODEC_ID_H264 || - st->codecpar->codec_id == AV_CODEC_ID_MPEG4) { + st->codecpar->codec_id == AV_CODEC_ID_MPEG4 || + st->codecpar->codec_id == AV_CODEC_ID_HEVC) { int type = avio_r8(s->pb); size--; - if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4) { + if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4 + || st->codecpar->codec_id == AV_CODEC_ID_HEVC) { // sign extension int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000; pts = dts + cts; @@ -1168,7 +1178,7 @@ static int flv_read_packet(AVFormatContext *s, AVPacket *pkt) } } if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC || - st->codecpar->codec_id == AV_CODEC_ID_H264)) { + st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_HEVC)) { AVDictionaryEntry *t; if (st->codecpar->extradata) { diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c index 899b07ea7be..eb955e5b451 100644 --- a/libavformat/flvenc.c +++ b/libavformat/flvenc.c @@ -34,6 +34,7 @@ #include "libavutil/opt.h" #include "libavcodec/put_bits.h" #include "libavcodec/aacenctab.h" +#include "hevc.h" static const AVCodecTag flv_video_codec_ids[] = { @@ -46,6 +47,7 @@ static const AVCodecTag flv_video_codec_ids[] = { { AV_CODEC_ID_VP6, FLV_CODECID_VP6 }, { AV_CODEC_ID_VP6A, FLV_CODECID_VP6A }, { AV_CODEC_ID_H264, FLV_CODECID_H264 }, + { AV_CODEC_ID_HEVC, FLV_CODECID_HEVC }, { AV_CODEC_ID_NONE, 0 } }; @@ -486,7 +488,7 @@ static void flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par) { FLVContext *flv = s->priv_data; if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264 - || par->codec_id == AV_CODEC_ID_MPEG4) { + || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { int64_t pos; avio_w8(pb, par->codec_type == AVMEDIA_TYPE_VIDEO ? @@ -533,7 +535,11 @@ static void flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par) { avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags avio_w8(pb, 0); // AVC sequence header avio_wb24(pb, 0); // composition time - ff_isom_write_avcc(pb, par->extradata, par->extradata_size); + 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); + } } data_size = avio_tell(pb) - pos; avio_seek(pb, -data_size - 10, SEEK_CUR); @@ -836,7 +842,7 @@ static int flv_write_trailer(AVFormatContext *s) AVCodecParameters *par = s->streams[i]->codecpar; FLVStreamContext *sc = s->streams[i]->priv_data; if (par->codec_type == AVMEDIA_TYPE_VIDEO && - (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)) + (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC)) put_avc_eos_tag(pb, sc->last_ts); } } @@ -880,15 +886,16 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) int64_t cur_offset = avio_tell(pb); if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A || - par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC) + par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC) { flags_size = 2; - else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) + } else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { flags_size = 5; - else + } else { flags_size = 1; + } if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264 - || par->codec_id == AV_CODEC_ID_MPEG4) { + || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { int side_size = 0; uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { @@ -951,6 +958,10 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1) if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0) return ret; + } 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; } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { if (!s->streams[pkt->stream_index]->nb_frames) { @@ -1021,9 +1032,9 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) else avio_w8(pb, ((FFALIGN(par->width, 16) - par->width) << 4) | (FFALIGN(par->height, 16) - par->height)); - } else if (par->codec_id == AV_CODEC_ID_AAC) + } else if (par->codec_id == AV_CODEC_ID_AAC) { avio_w8(pb, 1); // AAC raw - else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) { + } else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { avio_w8(pb, 1); // AVC NALU avio_wb24(pb, pkt->pts - pkt->dts); }
通過該項修改即可拓展FFmpeg中RTMP對於HEVC的支援。
我的上一篇文章,《FFmpeg+Nginx搭建RTMP直播推流服務》記錄了FFmpeg+Nginx的RTMP的搭建方式。
但是由於服務端Nginx並沒有新增HEVC下重發flv的header頭部,因此播放器端只有在從流的最開始才能夠成功的解碼,否則會出現缺少解碼引數集導致無法成功解碼。解決辦法有兩個:
1. 在FFmpeg程式碼中每一包都手動新增解碼引數集於flv的header中,修改如下:
ff_write_packet中
if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC) { int side_size = 0; uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size); if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) { av_free(par->extradata); par->extradata = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE); if (!par->extradata) { par->extradata_size = 0; return AVERROR(ENOMEM); } memcpy(par->extradata, side, side_size); par->extradata_size = side_size; flv_write_codec_header(s, par, pkt->dts); } else { flv_write_codec_header(s, par, pkt->dts); } }
2. 修改Nginx,新增HEVC的支援。這部分暫無時間修改。