Enhancing rtmp, FLV 2023年7月31号正式发布,主要支持了HEVC(H.265)、VP9、AV1视频编码。
一、rtmp 头
发送数据一般有basic header + message header + Extend timeStamp + 数据
// +---------------------------+---------------------------+---------------------------+---------------------------+
// | Basic Header | Message Header | Extended Timestamp | Chunk Data |
// +---------------------------+---------------------------+---------------------------+---------------------------+
// | |
// |<------------------------------ Chunk Header ------------------------------------->|
1.1 basic header
//-------------------------------------------------------------------------------------------------------------------------------
// chunk basic header(大部分情况是一个字节)
// csid = 2-63
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+
// |fmt| cs id |
// +---+---+---+---+---+---+---+
//或
// CSID占14bit,此时协议将于chunk type所在字节的其他bit都置为0,剩下的一个字节表示CSID - 64,这样共有8个bit来存储 CSID,8 bit 可以表示 [0,255] 个数,因此这种情况下 CSID 在 [64,319],其中 319 = 255 + 64。
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// |fmt| 0 | cs id - 64 |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
//或
// CSID占22bit,此时协议将第一个字节的[2,8]bit置1,余下的16个bit表示CSID - 64,这样共有16个bit来存储CSID,16bit可以表示[0,65535]共 65536 个数,因此这种情况下 CSID 在 [64,65599],其中65599 = 65535 + 64,需要注意的是,Basic Header是采用小端存储的方式,越往后的字节数量级越高,因此通过3个字节的每一个bit的值来计算CSID时,应该是 : <第三个字节的值> * 256 + <第二个字节的值> +64
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// |fmt| 1 | cs id - 64 |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1.2 message header
//-------------------------------------------------------------------------------------------------------------------------------
// Message Header
// Chunk Type(fmt) = 0:总共 11 个 bytes. type=0时Message Header占用11个字节,其他三种能表示的数据它都能表示,但在chunk stream 的开始第一个chunk和头信息中的时间戳后退(即值与上一个chunk相比减小,通常在回退播放的时候会出现这种情况)的时候必须采用这种格式
// timestamp(时间戳):占用3个字节,因此它最多能表示到16777215=0xFFFFFF=2^24-1,当它的值超过这个最大值时,这三个字节都置为1,这样实际的timestamp会转存到 ExtendedTimestamp 字段中,接收端在判断timestamp字段24个位都为1时就会去Extended Timestamp中解析实际的时间戳。
// message length(消息数据长度):占用3个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总长度,而不是chunk本身data的长度。
// message type id(消息的类型id):1个字节,表示实际发送的数据的类型,如8代表音频数据,9代表视频数据。
// message stream id(消息的流id):4个字节,表示该chunk所在的流的ID,和Basic Header的CSID一样,它采用小端存储方式。
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | timestamp | message length | message type id | message stream id |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
//或
// Chunk Type(fmt) = 1:总共 7 个 bytes. type为1时占用7个字节,省去了表示message stream id的4个字节,表示此chunk和上一次发的 chunk 所在的流相同,如果在发送端和对端有一个流链接的时候可以尽量采取这种格式。
// timestamp delta:3 bytes,这里和type = 0时不同,存储的是和上一个chunk的时间差。类似上面提到的timestamp,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际的时间戳差值就会转存到Extended Timestamp字段中,接收端在判断timestamp delta字段24个bit都为1时就会去Extended Timestamp 中解析实际的与上次时间戳的差值。其他字段与上面的解释相同
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | timestamp | message length | message type id |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
//或
// Chunk Type(fmt) = 2:总共 3 个 bytes. type 为 2 时占用 3 个字节,相对于 type = 1 格式又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此 chunk和上一次发送的 chunk 所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示 timestamp delta,使用同type=1。
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | timestamp |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
//或
// Chunk Type(fmt) = 3: 总共 0 个 byte
// type=3时,为0字节,表示这个chunk的Message Header和上一个是完全相同的。当它跟在type=0的chunk后面时,表示和前一个 chunk 的时间戳都是相同。
// 什么时候连时间戳都是相同呢?就是一个 Message 拆分成多个 chunk,这个 chunk 和上一个 chunk 同属于一个 Message。而当它跟在 type = 1或 type = 2 的chunk后面时的chunk后面时,表示和前一个 chunk的时间戳的差是相同的。
// 比如第一个 chunk 的 type = 0,timestamp = 100,第二个 chunk 的 type = 2,timestamp delta = 20,表示时间戳为 100 + 20 = 120,第三个 chunk 的 type = 3,表示 timestamp delta = 20, 时间戳为 120 + 20 = 140。
1.3 Extend timeStamp
//-------------------------------------------------------------------------------------------------------------------------------
// Extend timeStamp
// Extended Timestamp(扩展时间戳), 在 chunk 中会有时间戳 timestamp 和时间戳差 timestamp delta,并且它们不会同时存在,只有这两者之一大于3字节能表示的最大数值 0xFFFFFF = 16777215 时,才会用这个字段来表示真正的时间戳,否则这个字段为 0。
// 扩展时间戳占 4 个字节,能表示的最大数值就是 0xFFFFFFFF = 4294967295。当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,而不是减去时间戳或者时间戳差的值。
1.4 rtmp 头wireshark抓包截图
1.4.1 basic header
- 通常只有一个字节
-
这里为0x06,转为二进制为 0 0 0 0 0 1 1 0
-
根据 “1.1 basic header”看出 fmt为0(前2个bit)
-
根据 “1.2 message header” 得出message header的大小为11个字节
- 剩余6个bit 0 0 0 1 1 0 二进制转十进制为6,其含义如下:
enum CHUNK_STREAM_ID
{
RTMP_CONTROL_CHANNEL = 2,//控制流通道
RTMP_COMMAND_CHANNEL = 3,//命令通道
RTMP_STREAM_CHANNEL = 5,//数据流通道
RTMP_VIDEO_CHANNEL = 6,//视频信道
RTMP_AUDIO_CHANNEL = 7,//音频信道
};
1.4.2 message header
上述已说,message header的大小为11个字节。消息体:
timestamp + message length + message type id + message stream id
字段说明参考 “1.2 message header”。
- timestamp(时间戳):占用3个字节
这里为0,表示帧开始,假如每秒30帧,每次叠加 nTime += 33 发送数据为16进制
btPacket[1] = (nTime >> 16) & 0xff;
btPacket[2] = (nTime >> 8) & 0xff;
btPacket[3] = nTime & 0xff;
- message length(消息数据长度):占用3个字节。
- message type id(消息的类型id):1个字节。视频流一般为0x9,音频流一般为0x8
// Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)
// 消息类型定义如下所示:
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Message Type | Msg Type id | MessageStreamID | 作用 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Set Chunk Size | 1 | 0 | 通知对端,更新最大可接受的Chunk大小,默认为128 (单位: Bytes) ,最小为1 (单位: Bytes) |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | About Message | 2 | | 通知另一方,终止处理指定cs_ id信道后续的其他消息 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Acknowledgement | 3 | | 由数据接收方(receiver) 发送,当首次收到有效数据大小等于Window Ack Size消息设置的窗口大小时,发送此消息给数据发送方(sender)以表示链接稳定。 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | User Control Message | 4 | 0 | 用户控制消息,是-系列用于控制消息流的消息的总称。用来作为控制对端用户操作事件的一种手段 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Window Ack Size | 5 | 0 | 用户控制消息,是-系列用于控制消息流的消息的总称。用来作为控制对端用户操作事件的一种手段 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Set Peer Bandwidth | 6 | 0 | 由数据接收方(receiver) 发送,根据已收到但未确认的消息的数据量,来通知约束发送方(sender) 的输出带宽(单位: Bytes),收到消息需要发送Window AckSize做应答 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Audio | 8 | 1 | RTMP音频数据包 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Video | 9 | 1 | RTMP视频数据包 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Data AMF3 | 15 | | AMF3编码,音视频MetaData风格|配置 详情包 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Shared Object AMF3 | 16 | | AMF3编码,共享对象消息(携带用户详情) |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Command AMF3 | 17 | | AMF3编码,RTMP 命令消息,可能涉及用户数据 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Data AMFO | 18 | | AMFO编码,音视频MetaData风格| 配置详情包 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Shared Object AMFO | 19 | | AMFO编码,共享对象消息(携带用户详情) |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Command AMFO | 20(0x14 | Connect 0,play 1 | AMFO编码,RTMP 命令消息,可能涉及用户数据 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Aggregate Message | 22 | | 整合消息 |
// +---------------------------+---------------------------+---------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------+
- message stream id(消息的流id):4个字节,表示该chunk所在的流的ID,和Basic Header的CSID一样,它采用小端存储方式.
//stream id
btPacket[8] = 1;//message stream id. 就是指该次媒体流的唯一标识,用于指明属于哪个流,用于区分不同流(NetConnection网络连接流或者是(某个)NetStream网络流)的消息。一个StreamId通常用以完成某些特定的工作. 如使用Id为0的Stream来完成客户端和服务器的连接和控制,用Id为1的Stream来完成Stream的控制和播放等工作.
btPacket[9] = 0;
btPacket[10] = 0;
btPacket[11] = 0;
二、建立网络连接
2.1 发送C0C1
2.1.1 C0协议
C0 和 S0 包由一个字节组成,下面是 C0/S0 包内的字段:
0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | version | +-+-+-+-+-+-+-+-+ C0 and S0 bits |
version(1 byte):RTMP 的版本,一般为 3。
2.1.2 C1 和 S1 格式
C1和S1包含两部分数据:key和digest,分别为如下:
key 和 digest 的顺序是不确定的,也有可能是:(nginx-rtmp中是如下的顺序):
764 bytes key 结构:
- random-data: (offset) bytes
- key-data: 128 bytes
- random-data: (764 - offset - 128 - 4) bytes
- offset: 4 bytes
764 bytes digest 结构:
- offset: 4 bytes
- random-data: (offset) bytes
- digest-data: 32 bytes
- random-data: (764 - 4 - offset - 32) bytes
2.1.3 截图
2.1.4 代码
bool SendC0C1(long nType)
{
char cInfo[RTMP_SIG_SIZE + 1] = {0};
//版本
cInfo[0] = 0x03;
//time
uint32_t nTime = htonl(RTMP_GetTime());
memcpy(cInfo + 1, &nTime, 4);
//version
memset(&cInfo[4], 0, 4);
//key、digest
for (int i = 8; i < RTMP_SIG_SIZE/*1536*/; i++) {
cInfo[i] = 0;
}
//
return SendInfo(cInfo, RTMP_SIG_SIZE + 1);//send
}
2.2 发送C2
2.2.1 协议
2.2.2 截图
2.3 发送connect
2.4 发送chunk size、releaseStream、FCPublish、createStream、_checkbw
2.5 发送publish
2.6 发送@setDataFrame
三、视频帧
此部署流媒体服务器srs必须超过6.0以上版本,ffmpeg必须6.0以上(实际使用7.0)
启动srs (安装docker不再赘述):
sudo docker run --rm -it -p 1935:1935 -p 8080:8080 registry.cn-hangzhou.aliyuncs.com/ossrs/srs:6 ./objs/srs -c conf/hevc.flv.conf
fmpeg播放:
ffplay rtmp://192.168.3.127:1935/live/livestream
3.1 第一帧vps、sps、pps、sei
由于是通过ffmpeg取流后推流后抓包分析出来的结果,h265 通常包含vps、sps、pps、sei等信息。推送指令:
ffmpeg -i "rtsp://admin:ncgxy123@192.168.3.5:554/ch1/main/av_stream" -vcodec libx265 -acodec aac -f flv rtmp://192.168.3.127:1935/live/livestream
3.1.1 数据标识(1个字节)
enhance rtmp 第一位表示是否是扩展rtmp,只有是扩展协议才能发送h265
//1 0 0 1 0 0 0 0
//第一个bit表示是否为 1,Check if isExVideoHeader flag is set to 1, signaling enhanced RTMP video mode
//0 0 1 第2个bit到第4个bit(这里表示关键帧) 表示VideoFrameType:
// enum VideoFrameType {
// // 0 - reserved
// KeyFrame = 1, // a seekable frame
// InterFrame = 2, // a non - seekable frame
// DisposableInterFrame = 3, // H.263 only
//
// GeneratedKeyFrame = 4, //reserved for server use only If videoFrameType is not ignored and is set to VideoFrameType.Command,
// //the payload will not contain video data. Instead, (Ex)VideoTagHeader will be followed by a UI8,
// //representing the following meanings:
// //0 = Start of client-side seeking video frame sequence
// //1 = End of client-side seeking video frame sequence
//
// // frameType is ignored if videoPacketType is VideoPacketType.MetaData
// Command = 5, // video info / command frame
// // 6 = reserved
// // 7 = reserved
// };
//0 0 0 0 第5到第8个bit表示VideoPacketType(因为第一个bit是1,表示扩展类型), 此时数据为0,表示视频序列开始:
// enum VideoPacketType {
// SequenceStart = 0,
// CodedFrames = 1,
// SequenceEnd = 2,
//
// // CompositionTime Offset is implicitly set to zero. This optimization
// // avoids transmitting an SI24 composition time value of zero over the wire.
// // See the ExVideoTagBody section below for corresponding pseudocode.
// CodedFramesX = 3,
//
// // ExVideoTagBody does not contain video data. Instead, it contains
// // an AMF-encoded metadata. Refer to the Metadata Frame section for
// // an illustration of its usage. For example, the metadata might include
// // HDR information. This also enables future possibilities for expressing
// // additional metadata meant for subsequent video sequences.
// // If VideoPacketType.Metadata is present, the FrameType flags
// // at the top of this table should be ignored.
// Metadata = 4,
//
// // Carriage of bitstream in MPEG-2 TS format
// // PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart
// // are mutually exclusive
// MPEG2TSSequenceStart = 5,
//
// // Turns on video multitrack mode
// Multitrack = 6,
//
// // 7 - Reserved
// // ...
// // 14 - reserved
// // 15 - reserved
// };
3.1.2 h265 标识(4个字节)
btData[nIndex++] = 'h';
btData[nIndex++] = 'v';
btData[nIndex++] = 'c';
btData[nIndex++] = '1';
3.1.3 HEVCDecoderConfigurationRecord 结构体
参考 ISO_IEC14496-15:2022.pdf
抓图:
开始分析红色框框部分
3.1.3.1 解析vps数据
从这里看出 数据内容有几个0x03,需要去掉,这个过程叫做Extract Rbsp,去掉后的内容为:
3.1.3.1.1 解析NaluType
前两个字节为40 01 ====》二进制 0100 0000 0000 0001
F : 0
NalType:100 000 ==》32 =》VPS
LayerID:0 0000 0==》0
TID:001 ==》1
3.1.3.1.2 解析general_profile_space、general_tier_flag、general_profile_idc
在跟踪obs代码时发现这三个值在解析vps赋值了一次,在解析sps时又赋值了一次。我是直接解析vps后直接赋值了,未二次赋值。在跟踪obs代码后,直接说答案,这三个值是根据第7个字节解析出来的
可以看出这个值为0x01,转为二进制为0 0 0 0 0 0 0 1 ,前2个bit为general_profile_space,第三个bit为general_tier_flag,后面5个bit为general_profile_idc
nGeneralProfileSpace = btData[6] & 0xC0;
nGeneralTierFlag = btData[6] & 0x20;
nGeneralProfileIdc = btData[6] & 0x1f;
故configurationVersion 后面为(0x01):
btData[nIndex++] = (m_nGeneralProfileSpace << 6) | (m_nGeneralTierFlag << 5) | m_nGeneralProfileIdc;
对应wireshark抓捕截图中的
3.1.3.1.3 解析general_profile_compatibility_flags
同样的将Extract Rbsp后的数据8到11位拷贝到缓存
对应wireshark抓包截图中
3.1.3.1.4 解析general_constraint_indicator_flags
同样的将Extract Rbsp后的数据12到17位拷贝到缓存
对应wireshark抓图:
3.1.3.1.5 解析general_level_idc
对应Extract Rbsp后的数据18位
对应wireshark抓图:
3.1.3.1.6 解析min_spatial_segmentation_idc
根据obs代码跟踪发现可以写死
btData[nIndex++] = 0xf0;
btData[nIndex++] = 0x00;
截图如下:
3.1.3.1.7 parallelismType
根据obs代码跟踪发现可以写死
btData[nIndex++] = 0xfc;
3.1.3.2 解析sps数据
sps 结果Extract Rbsp后的数据为
3.1.3.2.1 解析numTemporalLayers、temporalIdNested
根据跟踪obs代码,发现第三个字节用来解析的
uint64_t nData = pFrameData[2];
int nSpsMaxSubLayersMinus1 = nData & 0x0E;
m_nNumTemporalLayers = nSpsMaxSubLayersMinus1 + 1;
//0 0 0 1, 取最后一位, unsigned int(1) temporalIdNested;
m_nTemporalIdNested = nData & 0x01;
3.1.3.2.2 解析chroma_format_idc
后面0XA00280802D则需要根据指数-哥伦布解码
//sps_seq_parameter_set_id
m_nSpsSeqParameterSetId = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//chroma_format_idc
m_nChromaFormatIdc = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//separate_colour_plane_flag
if (m_nChromaFormatIdc == 3) {
get_bits(&stContext, 1); //
}
//pic_width_in_luma_samples
m_nCameraWidth = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//pic_height_in_luma_samples
m_nCameraHeight = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//
if (get_bits(&stContext, 1)) {// conformance_window_flag
SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext); // conf_win_left_offset
SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext); // conf_win_right_offset
SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext); // conf_win_top_offset
SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext); // conf_win_bottom_offset
}
//unsigned int(3) bit_depth_luma_minus8;
m_nBitDepthLumaMinus8 = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//unsigned int(3) bit_depth_chroma_minus8;
m_nBitDepthChromaMinus8 = SingleTon<Exp_Golomb>::GetInstance()->get_ue_golomb_long(&stContext);
//
//... 剩余的丢掉了
视频宽度为1280,
视频高度720,
chroma_format_idc 为 1
bit_depth_luma_minus8 为 0
bit_depth_chroma_minus8 为 0
3.1.3.3 组chroma_format_idc
根据上述所说chroma_format_idc 为 1(变量名为m_nChromaFormatIdc)
btData[nIndex++] = 0xfc | m_nChromaFormatIdc;
截图如下:
3.1.3.4 组bit_depth_luma_minus8、bit_depth_chroma_minus8
//bit(5) reserved = '11111'b;
//unsigned int(3) bit_depth_luma_minus8
btData[nIndex++] = 0xf8 | m_nBitDepthLumaMinus8;
//bit(5) reserved = '11111'b;
//unsigned int(3) bit_depth_chroma_minus8;
btData[nIndex++] = 0xf8 | m_nBitDepthChromaMinus8;
截图如下:
3.1.3.5 avgFrameRate
根据obs抓包为0
3.1.3.6 组constantFrameRate、numTemporalLayers、temporalIdNested、lengthSizeMinusOne
m_nNumTemporalLayers和m_nTemporalIdNested在上面已经获取到值了,参考 “3.1.3.2.1 解析numTemporalLayers、temporalIdNested”
#define ConstantFrameRate 0 //obs写死的值
#define LengthSizeMinusOne 3 //obs写死的值
btData[nIndex++] = (ConstantFrameRate << 6) | (m_nNumTemporalLayers << 3) | (m_nTemporalIdNested << 2) | LengthSizeMinusOne;
截图:
3.1.3.7 拷贝vps、sps、pps、sei数据
0x04 表示vps、sps、pps、sei的数量,各有1个
3.1.3.7.1 vps 结构填充
0x20 NaluType, 参考“3.1.3.1.1 解析NaluType”
0x00 0x01 vps个数
0x00 0x18 vps数据长度(未extract rbsp之前的数据,即原始数据长度)
vps 数据
截图:
3.1.3.7.2 sps 结构填充
0x21 NaluType, 参考“3.1.3.1.1 解析NaluType”
0x00 0x01 sps个数
0x00 0x2a sps数据长度(未extract rbsp之前的数据,即原始数据长度)
sps 数据
截图:
3.1.3.7.3 pps 结构填充
0x22 NaluType, 参考“3.1.3.1.1 解析NaluType”
0x00 0x01 pps个数
0x00 0x07 pps数据长度(未extract rbsp之前的数据,即原始数据长度)
pps 数据
截图:
3.1.3.7.4 sei 结构填充
0x27 NaluType, 参考“3.1.3.1.1 解析NaluType”
0x00 0x01 sei个数
0x09 0x39 sei数据长度(未extract rbsp之前的数据,即原始数据长度)
sei 数据
截图:
3.2 第一帧中的关键帧
第一个字节0x91,参考 “3.1.1 数据标识(1个字节)”:
然后:
m_pVideoData[nIndex++] = 'h';
m_pVideoData[nIndex++] = 'v';
m_pVideoData[nIndex++] = 'c';
m_pVideoData[nIndex++] = '1';
然后composition time (3字节):
m_pVideoData[nIndex++] = (nCusteomData >> 16) & 0xff;
m_pVideoData[nIndex++] = (nCusteomData >> 8) & 0xff;
m_pVideoData[nIndex++] = nCusteomData & 0xff;
然后视频帧长度(4字节):
m_pVideoData[nIndex++] = (nVideoSize >> 24) & 0xff;
m_pVideoData[nIndex++] = (nVideoSize >> 16) & 0xff;
m_pVideoData[nIndex++] = (nVideoSize >> 8) & 0xff;
m_pVideoData[nIndex++] = nVideoSize & 0xff;
然后关键帧数据
3.3 非关键帧
第一个字节0xa3,参考 “3.1.1 数据标识(1个字节)”
然后:
m_pVideoData[nIndex++] = 'h';
m_pVideoData[nIndex++] = 'v';
m_pVideoData[nIndex++] = 'c';
m_pVideoData[nIndex++] = '1';
然后是视频帧长度(4字节):
m_pVideoData[nIndex++] = (nVideoSize >> 24) & 0xff;
m_pVideoData[nIndex++] = (nVideoSize >> 16) & 0xff;
m_pVideoData[nIndex++] = (nVideoSize >> 8) & 0xff;
m_pVideoData[nIndex++] = nVideoSize & 0xff;
然后是视频帧数据