c++ 通过 enhance rtmp 发送 h265 视频流

  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;

 然后是视频帧数据

四、使用ffmpeg播放

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Topaz Video Enhance AI是一款备受赞誉的视频增强软件,它可以通过人工智能技术提高视频的质量和分辨率。这个软件可以通过智能算法来提升模糊、噪声严重、低分辨率的视频画质,并将其转化为高清晰度的视频。 Topaz Video Enhance AI采用了无监督学习的方法,可以自动学习和提升视频的细节,改善锐度和清晰度,消除噪声,并增强颜色和对比度。在处理视频时,用户只需选择视频文件,点击“增强”按钮,软件会自动识别并应用最佳的算法。 除了画质的提升,Topaz Video Enhance AI还可以进行更多的视频处理,比如插帧技术可以将低帧率的视频转化为高帧率的视频,使得动作更加畅。软件还提供了图像稳定功能,可以平稳地去除视频中的抖动和摇晃,使得观看体验更好。 Topaz Video Enhance AI的使用简单直观,对于用户来说很友好。它可以在几分钟内处理完成一个视频,并能够导出为各种常见格式,方便用户分享和播放。然而,需要注意的是,该软件在处理视频时需要较大的计算能力,同时也需要一定的时间去处理,因此较长的视频可能需要较长的处理时间。 总的来说,Topaz Video Enhance AI是视频处理领域中一款非常优秀的软件,通过其强大的人工智能技术,用户可以轻松地提升视频的画质和细节,并实现更好的观看体验。无论是专业用户还是普通用户,都可以从中受益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值