Enhance rtmp-flv协议开始支持多码流
小编之前在LiveVideoStack写过博文详解Enhanced-RTMP支持H.265,详解了Enhance-Rtmp如何支持H265编码。
后面也写过ffmpeg7.0 flv支持hdr,Enhanced-Rtmp支持Hdr。
最近Enhance-Rtmp协议再次更新到v2版本,开始支持多码流方式,也就是我们常说的推送多份不同分辨率的视频,方便观众根据需要来观看。
协议原文github地址:
https://github.com/veovera/enhanced-rtmp/blob/main/docs/enhanced/enhanced-rtmp-v2.md
本文主要内容:
-
介绍应用场景:Enhance Rtmp-Flv V2协议支持哪几种多码率解决方案
-
详解协议内容:Enhance Rtmp-Flv V2协议如何支持多码率
文章有点长,可以先关注或收藏,有空再看。
1 Enhance Rtmp-Flv V2支持多码率
该V2协议支持多种应用的场景,如下:
新rtmp-flv支持多种码流了,4种不同类型的应用方式:
-
自适应比特率流式传输:多轨支持允许客户端发送自适应比特率 (ABR) 阶梯,从而避免服务器端转码的需要并减少质量损失。这也有助于发送具有多种编解码器(如 AV1、HEVC 和 VP9)的内容。
-
设备特定流式传输:该功能允许流式传输不同的宽高比,针对各种设备配置文件进行量身定制,从而实现更动态、更灵活的演示。
-
帧级同步:例如,您可以在音乐会中同步多个摄像机视图。
-
多语言支持:现在支持单个 [FLV] 文件中的多个音轨,无需多个文件版本。
1.1 自适应比特率流式传输
在新兴的视频编码,如AV1、HEVC 和 VP9,是支持SVC,多空间层和多时间层的,也就是有多个Spatial Layers(空间层), 多个Temporal Layers(时间层)。
常规H264(不支持SVC),I帧,P帧和B帧都是前后依赖:

P帧和B帧少了前面I帧或P帧或B帧后,都会解码失败。
而SVC克服这个问题,SVC编码帧的依赖:

T0的帧之间前后依赖,T1层的帧依赖T0的帧,T2的帧依赖T1和T0。举例,如果整视频为30帧/s:
-
T2层的帧就为30帧/s,连续性最好,但是需要解码全部帧:T0,T1,T2;
-
T1层只有15帧/s,只需要解码T0, T1;
-
T0层只有7.5帧/s,只需要解码T0帧,虽然连续性最差,但是基本视频可用;
这样SVC保证接收端在网络不好的情况下,如果只接收到T0帧,也能基本可用。
Enhance-Rtmp2.0支持对SVC多层的多码率特性,实现:
推流SVC多层,拉流可以根据情况生成多种码率的拉流。
1.2 设备特定流式传输
该功能允许流式传输不同的宽高比,针对各种设备配置文件进行量身定制,从而实现更动态、更灵活的演示
常规的H264/H265(不支持SVC的),可以推流多个分辨率的流,放在一个rtmp推流中,通过flv的扩展头来区分不同的分辨率。
1.3 帧级同步
多摄像头,多视频源的同步推送,可以把不同视频源放在一个rtmp流中进行推送,通过flv的扩展头来区分不同的摄像头。
举例一些应用:
-
大型会议不同摄像头的拍摄,如音乐会等
-
安放鱼眼摄像头(多个摄像头的拼接)
-
VR应用(两个重叠的左右眼视频推送)
1.4 多语言支持
现在支持单个 [FLV] 文件中的多个音轨,无需多个文件版本。也就是支持多个audio track,支持多语言的音频,实现单文件多语音语言的存储。
方便用户选择不同的语言进行听取。
2 Enhande Rtmp-Flv具体细节
在博文详解Enhanced-RTMP支持H.265中,详细介绍了Enhance Rtmp如何支持H265编码,本章介绍Enhanced rtmp如何支持多码流。
在Rtmp流中承载video和audio,首先是Rtmp Message Header:
0 1 2 3+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|MessageType ID | Payload length || (1 byte) | (3 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Timestamp || (4 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Stream ID || (3 bytes) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
如上Rtmp Message Header,关键信息:MessageType ID: 8是音频,9是视频。
在这个头后面就是Flv的video tag或audio tag数据。
每个flv video tag或audio tag数据,在Enhance Rtmp中都有extesion的定义,用来支持更多的内容:
-
更多的编码器:h265/av1/opus等
-
更多的能力:多码率等
2.1 Extended AudioTagHeader
这里介绍如何支持多audio track,用协议伪码进行详解,下图中UB单位为bit,UI8为一个字节。
soundFormat = UB[4] as SoundFormat/*soundFormat为4bits,常规的值:enum SoundFormat { ¦ LPcmPlatformEndian = 0, ¦ AdPcm = 1, ¦ Mp3 = 2, ¦ LPcmLittleEndian = 3, ¦ Nellymoser16KMono = 4, ¦ Nellymoser8KMono = 5, ¦ Nellymoser = 6, ¦ G711ALaw = 7, ¦ G711MuLaw = 8, ¦ ExHeader = 9, // new, used to signal FOURCC mode ¦ Aac = 10, ¦ Speex = 11, ¦ ¦ Mp3_8K = 14, ¦ Native = 15, // Device specific sound ¦}原来常规flv协议规定audio codec有:Aac(10), Mp3(15)。但是9元来是reverve保留的,在Enhance RtmpFlv中变成ExHeader标志。*/ //如果soundFormat的值不是ExHeader(9),//就是传统的Rtmp audio tag信息if (soundFormat != SoundFormat.ExHeader) { //flv audioi tag传统信息 soundRate = UB[2] soundSize = UB[1] soundType = UB[1] ¦} processAudioBody = false//如果soundFormat的值是ExHeader(9),//就是Rtmp audio ExHeader信息 if (soundFormat == SoundFormat.ExHeader) { processAudioBody = true /* audioPacketType含义: enum AudioPacketType { SequenceStart = 0, CodedFrames = 1, SequenceEnd = 2, MultichannelConfig = 4, Multitrack = 5, ModEx = 7, } 这里关注Multitrack,就是多audio track标志位 */ audioPacketType = UB[4] as AudioPacketType // Process each ModEx data packet while (audioPacketType == AudioPacketType.ModEx) { //省略 } //如果是Multitrack,就是多audio track标志位 if (audioPacketType == AudioPacketType.Multitrack) { isAudioMultitrack = true; /* enum AvMultitrackType { OneTrack = 0, ManyTracks = 1, ManyTracksManyCodecs = 2, } */ //如果是1,就是多audio track; //如果是2,就是多audio track,且每个track的codec不同 audioMultitrackType = UB[4] as AvMultitrackType audioPacketType = UB[4] as AudioPacketType //如果audioMultitrackType不是多audio track多codec模式 if (audioMultitrackType != AvMultitrackType.ManyTracksManyCodecs) { //得到该audio的编码FourCC信息,如Mp4a, Opus等 audioFourCc = FOURCC as AudioFourCc } } else { //得到该audio的编码FourCC信息,如Mp4a, Opus等 audioFourCc = FOURCC as AudioFourCc } } //如果扩展audio使能while (processAudioBody) { //如果是音频多track模式 if (isAudioMultitrack) { //如果audioMultitrackType是多track多codec模式 if (audioMultitrackType == AvMultitrackType.ManyTracksManyCodecs) { //因为是多track,每个track有独立的codec; //得到当前的audio codec FourCc信息 audioFourCc = FOURCC as AudioFourCc } //当前的audio trackId audioTrackId = UI8 if (audioMultitrackType != AvMultitrackType.OneTrack) { //如果不是单track模式,sizeOfAudioTrack用来表示本track的大小, //在while循环结束前,判断是否继续下一个track的检测 sizeOfAudioTrack = UI24 } } if (audioPacketType == AudioPacketType.MultichannelConfig) { //多声道配置,略 audioChannelOrder = UI8 as AudioChannelOrder channelCount = UI8 if (audioChannelOrder == AudioChannelOrder.Custom) { audioChannelMapping = UI8[channelCount] as AudioChannel } if (audioChannelOrder == AudioChannelOrder.Native) { audioChannelFlags = UI32 } } if (audioPacketType == AudioPacketType.SequenceEnd) { // signals end of sequence } /** 如果是SequenceStart,表示内容为audio sequence header **/ /** 一般第一个音频报文为audio sequence header **/ if (audioPacketType == AudioPacketType.SequenceStart) { if (audioFourCc == AudioFourCc.Aac) { // Aac的Sequence header aacHeader = [AacSequenceHeader] } if (audioFourCc == AudioFourCc.Flac) { // Flac的Sequence header flacHeader = [FlacSequenceHeader] } if (audioFourCc == AudioFourCc.Opus) { // Opus的Sequence header opusHeader = [OpusSequenceHeader] } } if (audioPacketType == AudioPacketType.CodedFrames) { if (audioFourCc == AudioFourCc.Ac3 || audioFourCc == AudioFourCc.Eac3) { //ac3音频编码数据 ac3Data = [Ac3CodedData] } if (audioFourCc == AudioFourCc.Opus) { //opus编码数据 opusData = [OpusCodedData] } if (audioFourCc == AudioFourCc.Mp3) { //mp3编码数据 mp3Data = [Mp3CodedData] } if (audioFourCc == AudioFourCc.Aac) { //aac编码数据 aacData = [AacCodedData] } if (audioFourCc == AudioFourCc.Flac) { //Flac音频数据 flacData = [FlacCodedData] } } //如果isAudioMultitrack使能,且是多track模式, //且sizeOfAudioTrack没有完结, //本报文contiue去寻找下一个audio track的信息。 //否则结束循环 if ( isAudioMultitrack && audioMultitrackType != AvMultitrackType.OneTrack && positionDataPtrToNextAudioTrack(sizeOfAudioTrack) ) { // TODO: need to implement positionDataPtrToNextVideoTrack() continue } // 否则,结束while循环 break }
如上所示,通过audio type是否为9,判断后面是否是ext audio header,再判断audioPacketType是否为Multitrack,得知是否为多audio track模式;
如果是多audio track模式,最后通过while循环,分析各个track的信息,得到对应各个track的codec类型,codec数据。
2.2 Extended VideoTagHeader
传统的rtmp flv video tag header:
- 如果视频数据是H264的sequence header(也就是包含sps/pps的Avcc Header),就应该是0x17 00;
-
如果视频数据是H264的Iframe,就应该是0x17 01;
-
如果视频数据是H264的非Iframe,就应该是0x27 01
在Extended video tag header中,第一个字节的第一个bit表示ExVideoHeader使能位,如果该位使能,带有更多的信息,如更多的codec类型,或多video track信息。
具体如下:
//第一个bit表示ExVideoHeader,为1表示使能扩展信息isExVideoHeader = UB[1]//后3个bits为传统的video codec类型,如7为h264videoFrameType = UB[3] as VideoFrameType //如果没有使能isExVideoHeader,就是传统的flv video tagif (isExVideoHeader == 0) { videoCodecId = UB[4] as VideoCodecId if (videoFrameType == VideoFrameType.Command) { videoCommand = UI8 as VideoCommand } } processVideoBody = false//如果使能isExVideoHeader,//开始分析Ext Video Headerif (isExVideoHeader == 1) { processVideoBody = true /*enum VideoPacketType { SequenceStart = 0, CodedFrames = 1, SequenceEnd = 2, CodedFramesX = 3, Metadata = 4, MPEG2TSSequenceStart = 5, Multitrack = 6,//video多track使能 ModEx = 7,}*/ //这里关注videoPacketType是否为VideoPacketType.Multitrack videoPacketType = UB[4] as VideoPacketType // Process each ModEx data packet while (videoPacketType == VideoPacketType.ModEx) { //ModEx类型,分析:略 modExDataSize = UI8 + 1 if (modExDataSize == 256) { modExDataSize = UI16 + 1; } modExData = UI8[modExDataSize] videoPacketModExType = UB[4] as VideoPacketModExType videoPacketType = UB[4] as VideoPacketType // at byte boundary after this read if (videoPacketModExType == VideoPacketModExType.TimestampOffsetNano) { videoTimestampNanoOffset = bytesToUI24(modExData) } } if ( videoPacketType != VideoPacketType.Metadata && videoFrameType == VideoFrameType.Command ) { videoCommand = UI8 as VideoCommand processVideoBody = false } /* 关注videoPacketType == VideoPacketType.Multitrack */ else if (videoPacketType == VideoPacketType.Multitrack) { isVideoMultitrack = true; /* enum AvMultitrackType { OneTrack = 0, ManyTracks = 1, ManyTracksManyCodecs = 2, } track类型为单track,多track,或多track多codec类型 */ videoMultitrackType = UB[4] as AvMultitrackType videoPacketType = UB[4] as VideoPacketType /** 如果不是多track多codec类型,得到codecFourCc信息**/ if (videoMultitrackType != AvMultitrackType.ManyTracksManyCodecs) { //得到video codec FourCc信息,如"vp08", "avc1", "hvc1" videoFourCc = FOURCC as VideoFourCc } } else { //如果不是多video track,得到video codec FourCc信息 videoFourCc = FOURCC as VideoFourCc } } /** 如果扩展video tag header使能 **//** 进入分析循环体 **/while (processVideoBody) { //如果video多track使能 if (isVideoMultitrack) { if (videoMultitrackType == AvMultitrackType.ManyTracksManyCodecs) { //如果是多track多codec类型 //得到本track的codec FourCc信息,如"vp08", "avc1", "hvc1" videoFourCc = FOURCC as VideoFourCc } //得到但字节的video trackId videoTrackId = UI8 if (videoMultitrackType != AvMultitrackType.OneTrack) { //如果不是单track类型,得到本track的size大小 sizeOfVideoTrack = UI24 } } if (videoPacketType == VideoPacketType.Metadata) { videoMetadata = [VideoMetadata] } if (videoPacketType == VideoPacketType.SequenceEnd) { // signals end of sequence } if (videoPacketType == VideoPacketType.SequenceStart) { if (videoFourCc == VideoFourCc.Vp8) { //vp8的codec sequece信息 vp8Header = [VPCodecConfigurationRecord] } if (videoFourCc == VideoFourCc.Vp9) { // vp9的codec sequece信息 vp9Header = [VPCodecConfigurationRecord] } if (videoFourCc == VideoFourCc.Av1) { //av1的codec sequece信息 av1Header = [AV1CodecConfigurationRecord] } if (videoFourCc == VideoFourCc.Avc) { //h264的codec sequece信息 avcHeader = [AVCDecoderConfigurationRecord] } if (videoFourCc == VideoFourCc.Hevc) { //h265的codec sequece信息 hevcHeader = [HEVCDecoderConfigurationRecord] } } //mpegts的sequence信息 if (videoPacketType == VideoPacketType.MPEG2TSSequenceStart) { if (videoFourCc == VideoFourCc.Av1) { // body contains a video descriptor to start the sequence av1Header = [AV1VideoDescriptor] } } //videoPacketType为CodedFrames //表示内容为codec的编码数据 if (videoPacketType == VideoPacketType.CodedFrames) { if (videoFourCc == VideoFourCc.Vp8) { //vp8的编码数据 vp8CodedData = [Vp8CodedData] } if (videoFourCc == VideoFourCc.Vp9) { //vp9的编码数据 vp9CodedData = [Vp9CodedData] } if (videoFourCc == VideoFourCc.Av1) { //av1的编码数据 av1CodedData = [Av1CodedData] } if (videoFourCc == VideoFourCc.Avc) { //3字节的时间戳偏移(为由dts计算pts时间) compositionTimeOffset = SI24 //h264的编码数据 avcCodedData = [AvcCodedData] } if (videoFourCc == VideoFourCc.Hevc) { //3字节的时间戳偏移(为由dts计算pts时间) compositionTimeOffset = SI24 //h265的编码数据 hevcData = [HevcCodedData] } } //若videoPacketType为CodedFramesX //没有偏移时间的字段,节省3个字节 if (videoPacketType == VideoPacketType.CodedFramesX) { if (videoFourCc == VideoFourCc.Avc) { //h264的编码数据,没有偏移时间的字段,节省3个字节 avcCodedData = [AvcCodedData] } if (videoFourCc == VideoFourCc.Hevc) { //h265的编码数据,没有偏移时间的字段,节省3个字节 hevcData = [HevcCodedData] } } if ( isVideoMultitrack && videoMultitrackType != AvMultitrackType.OneTrack && positionDataPtrToNextVideoTrack(sizeOfVideoTrack) ) { //如果是video多track模式下,且sizeOfVideoTrack后还有数据, //则继续分析后续的数据 continue } //否则,结束while循环的数据分析 break }
3 总结
Enhance-Rtmp协议再次更新到v2版本,开始支持多码流方式。
本文主要内容:
- 应用场景:Enhance Rtmp-Flv V2协议支持哪几种多码率解决方案;
- 自适应比特率流式传输:多轨支持允许客户端发送自适应比特率 (ABR) 阶梯,从而避免服务器端转码的需要并减少质量损失。这也有助于发送具有多种编解码器(如 AV1、HEVC 和 VP9)的内容。
-
设备特定流式传输:该功能允许流式传输不同的宽高比,针对各种设备配置文件进行量身定制,从而实现更动态、更灵活的演示。
-
帧级同步:例如,您可以在音乐会中同步多个摄像机视图。
-
多语言支持:现在支持单个 [FLV] 文件中的多个音轨,无需多个文件版本。
- 协议内容:Enhance Rtmp-Flv V2协议如何支持多码率
扩展flv tag header如何定义多码率方案。