rtmp客户端h265/h264音视频数据推流

Posted while(null)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了rtmp客户端h265/h264音视频数据推流相关的知识,希望对你有一定的参考价值。

1.rtmp视频包格式

类型长度
FrameType4bit1表示关键帧,2表示非关键帧,3表示一次性帧,4为服务器保留,5表示视频信息或命令帧
CodecID4bit1表示JPG,2表示Sorenson H263,3表示屏幕录像,4表示VP6 ON2,,5表示带alphat通道的VP6 ON2,6表示版本2的屏幕录像,7表示avc,即h264
VideoDataN byte视频数据,根据CodecID的不同,此处字段内容不同,例如CodecID为2时,此处为H263包结构,当CodecID为7时,此处为AVC视频包结

实际使用当中,经常用的是发送h264或者h265的包
对于h265, CodecID为7,对于h265,一般扩展的CodecID都是12
因此,对于h264视频包来说,第一个字节一般是0x17或0x27,
对于h265, 第一个字节一般是0x1c或0x2c

对于VideoData字段,当CodecID为7时,结构是AVC视频包

AVC视频包结构

类型长度
AVCPacketType8bit0表示AVC Sequence头,1表示nalu, 2一般不支持较低level的h264
Composition time3 byte如果AVCPacketType为1表示Composition time offset, 其他值是全为0
Datan byte如果AVCPacketType为0,此处是AVCDecoderConfigurationRecord,如果AVCPacketType,此处是视频数据,如果AVCPacketType为2, 此处为空

以上资料参考自:(2条消息) rtmp h264,h265包结构和推流组包_ybn187的专栏-CSDN博客

2.rtmp封装

H.264 rtmp头部信息封装
SPS PPS
AVCDecoderConfigurationRecord

frame type (1 byte)
fixed 0x00 0x00 0x00 0x00 (4 byte)
configurationVersion (1 byte)
AVCProfileIndication (1 byte)
profile_compatibility (1 byte)
AVCLevelIndication (1 byte)
lengthSizeMinusOne (1 byte)
SPS

sps number (1 byte)
sps data length (2 byte)
sps data
PPS

pps number (1 byte)
pps data length (2 byte)
pps data
1) frame type

高4位表示是否是关键帧,低4位表示编码类型。SPS和PPS是关键帧用1表示,H.264(AVC)对应的值是7,所以这个byte是0x17。

2) fixed

这4个byte的具体含义我也不清楚,但对于H.264 AVCDecoderConfigurationRecord来说是 0x00 0x00 0x00 0x00

3) configurationVersion

0x01

4) AVCProfileIndication

SPS的第2个byte,也就是去掉第一个byte的NALU type之后的那个byte。

5) profile_compatibility

SPS的第3个byte。

6) AVCLevelIndication

SPS的第4个byte。

7) lengthSizeMinusOne

0xff

8) sps number

SPS的序号,是0xE1

9) sps data length

这2个byte表示SPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:

packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;

packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;


10) sps data

最后是存放SPS数据,不包含开头的分隔符00000001。

11) pps number

PPS的序号,是0x01

12) pps data length

这2个byte表示PPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。

13) pps data

最后是存放PPS数据,不包含开头的分隔符00000001。

其他NALU
frame type (1 byte)
fixed 0x01 0x00 0x00 0x00 (4 byte)
NALU size (4 byte)
NALU data
1) frame type

如果关键帧就是0x17,如果非关键帧就是0x27。高4位表示是否是关键帧,低4位表示编码类型,H.264(AVC)对应的值是7。

2) fixed

这4个byte的具体含义我也不清楚,但对于H.264来说都是固定值 0x01 0x00 0x00 0x00

3) NALU size

这4个byte表示NALU数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:

packet[i++] = (data_size>>24)&0xff;
packet[i++] = (data_size>>16)&0xff;
packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;

packet[i++] = (data_size>>24)&0xff;
packet[i++] = (data_size>>16)&0xff;
packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;


4) NALU data

最后是存放长度为[NALU size]的NALU数据,不包含开头的分隔符00000001。

H.265 rtmp头部信息封装
rtmp协议中并没有H.265,这里讲的H.265封装是在H.264的基础上改进的。

SPS PPS VPS
HEVCDecoderConfigurationRecord

frame type (1 byte)
fixed 0x00 0x00 0x00 0x00 (4 byte)
configurationVersion (1 byte)
HEVCProfileIndication (1 byte)
profile_compatibility (1 byte)
HEVCLevelIndication (1 byte)
lengthSizeMinusOne (1 byte)
SPS

sps number (1 byte)
sps data length (2 byte)
sps data
PPS

pps number (1 byte)
pps data length (2 byte)
pps data
VPS

vps number (1 byte)
vps data length (2 byte)
vps data
1) frame type

高4位表示是否是关键帧,低4位表示编码类型。SPS、PPS、VPS是关键帧用1表示,H.265(HEVC)对应的值是12,所以这个byte是0x1C。

2) fixed

这4个byte的具体含义我也不清楚,但对于H.265 HEVCDecoderConfigurationRecord来说是 0x00 0x00 0x00 0x00

3) configurationVersion

0x01

4) HEVCProfileIndication

SPS的第2个byte,也就是去掉第一个byte的NALU type之后的那个byte。

5) profile_compatibility

SPS的第3个byte。

6) HEVCLevelIndication

SPS的第4个byte。

7) lengthSizeMinusOne

0x03

8) sps number

SPS的序号,是0xE1

9) sps data length

这2个byte表示SPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:

packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;

packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;


10) sps data

最后是存放SPS数据,不包含开头的分隔符00000001。

11) pps number

PPS的序号,是0x01

12) pps data length

这2个byte表示PPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。

13) pps data

最后是存放PPS数据,不包含开头的分隔符00000001。

14) vps number

VPS的序号我也不知道,我暂时写0x01

15) vps data length

这2个byte表示VPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。

16) vps data

最后是存放VPS数据,不包含开头的分隔符00000001。

其他NALU
frame type (1 byte)
fixed 0x01 0x00 0x00 0x00 (4 byte)
NALU size (4 byte)
NALU data
1) frame type

如果关键帧就是0x1C,如果非关键帧就是0x2C。高4位表示是否是关键帧,低4位表示编码类型,H.265(HEVC)对应的值是12。

2) fixed

这4个byte的具体含义我也不清楚,我暂时写 0x01 0x00 0x00 0x00

3) NALU size

这4个byte表示NALU数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:

packet[i++] = (data_size>>24)&0xff;
packet[i++] = (data_size>>16)&0xff;
packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;

packet[i++] = (data_size>>24)&0xff;
packet[i++] = (data_size>>16)&0xff;
packet[i++] = (data_size>>8)&0xff;
packet[i++] = data_size&0xff;


4) NALU data

最后是存放长度为[NALU size]的NALU数据,不包含开头的分隔符00000001。

以上资料参考自:https://blog.csdn.net/Jacob_job/article/details/81880445

3.示例代码

h264格式头部封装

static int rtmp_write_video_header_h264(unsigned char *sps, int sps_len,
										unsigned char *pps, int pps_len)
//264: index[2] + cts[3] + nalu.len[4] + nalu.index[1] + nalu.data[n]
    RTMPPacket packet;
	RTMPPacket_Reset(&packet);
	int ret = RTMPPacket_Alloc(&packet, 2 + 3 + 5 + 1 + 2 + sps_len + 1 + 2 + pps_len);

    packet.m_packetType 	= RTMP_PACKET_TYPE_VIDEO;
    packet.m_nBodySize  	= 2 + 3 + 5 + 1 + 2 + sps_len + 1 + 2 + pps_len;
    packet.m_nChannel   	= 0x04;
    packet.m_nTimeStamp 	= 0;
    packet.m_hasAbsTimestamp= 0;
    packet.m_headerType 	= RTMP_PACKET_SIZE_LARGE;
	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;
    packet.m_nInfoField2 	= rtmpctx->m_stream_id;

	unsigned char *body = (unsigned char *)packet.m_body;	
    int i = 0;	
    body[i++] = 0x17;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    //AVCDecoderConfigurationRecord
    body[i ++] = 0x01;
    body[i ++] = sps[1];
    body[i ++] = sps[2];
    body[i ++] = sps[3];
    body[i ++] = 0xff;
    //sps
    body[i ++] = 0xe1;
    body[i ++] = (sps_len >> 8) & 0xff;
    body[i ++] = (sps_len) & 0xff;
    memcpy(body + i, sps, sps_len); i += sps_len;
    
    //pps
    body[i ++] = 0x01;
    body[i ++] = (pps_len >> 8) & 0xff;
    body[i ++] = (pps_len) & 0xff;
    memcpy(body + i, pps, pps_len); i += pps_len;
	
	printf("rtmp_config_send: sps=%d, pps=%d\\n", sps_len, pps_len);
    int nRet = RTMP_SendPacket(rtmpctx, &packet, 0);
	RTMPPacket_Free(&packet);
	return nRet;

h265格式头部封装

static int rtmp_write_video_header_h265(unsigned char *vps, int vps_len,
										unsigned char *sps, int sps_len,
										unsigned char *pps, int pps_len)

    RTMPPacket packet;
	RTMPPacket_Reset(&packet);
	int ret = RTMPPacket_Alloc(&packet, 43 + vps_len + sps_len + pps_len);

	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;

	unsigned char *body = (unsigned char *)packet.m_body;	
    int i = 0;	
    body[i++] = 0x1c;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    body[i ++] = 0x00;
    body[i ++] = 0x01;

	body[i++] = sps[6];
	body[i++] = sps[7];
	body[i++] = sps[8];
	body[i++] = sps[9];

	body[i++] = sps[12];
	body[i++] = sps[13];
	body[i++] = sps[14];

	//48 bit nothing deal in rtmp
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;

	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	body[i ++] = 0x00;
	
	//bit(16) avgFrameRate
	/* bit(2) constantFrameRate; */
	/* bit(3) numTemporalLayers; */
	/* bit(1) temporalIdNested; */
	body[i ++] = 0x83;

	/*unsigned int(8) numOfArrays; 03*/
	body[i ++] = 0x03;

	//vps 32
	body[i ++] = 0x20;
	body[i ++] = 0x00;
	body[i ++] = 0x01;
	body[i ++] = (vps_len >> 8) & 0xff;
	body[i ++] = (vps_len) & 0xff;
	memcpy(&body[i], vps, vps_len);
	i += vps_len;
	
    //sps
    body[i ++] = 0x21;
	body[i ++] = 0x00;
	body[i ++] = 0x01;
    body[i ++] = (sps_len >> 8) & 0xff;
    body[i ++] = (sps_len) & 0xff;
    memcpy(&body[i], sps, sps_len); 
	i += sps_len;
    
    //pps
    body[i ++] = 0x22;
	body[i ++] = 0x00;
	body[i ++] = 0x01;
    body[i ++] = (pps_len >> 8) & 0xff;
    body[i ++] = (pps_len) & 0xff;
    memcpy(&body[i], pps, pps_len);
	i += pps_len;

	packet.m_packetType 	= RTMP_PACKET_TYPE_VIDEO;
    packet.m_nBodySize  	= i;
    packet.m_nChannel   	= 0x04;
    packet.m_nTimeStamp 	= 0;
    packet.m_hasAbsTimestamp= 0;
    packet.m_headerType 	= RTMP_PACKET_SIZE_LARGE;
	packet.m_nInfoField2 	= rtmpctx->m_stream_id;
	
	printf("rtmp_config_send: vps_lend=%d, sps_len=%d, pps_len=%d\\n", vps_len, sps_len, pps_len);
    int nRet = RTMP_SendPacket(rtmpctx, &packet, 0);
	RTMPPacket_Free(&packet);
	return nRet;

audio头部封装

static int rtmp_write_audio_header(void)

	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;
	RTMPPacket packet;  
	RTMPPacket_Reset(&packet);  
	RTMPPacket_Alloc(&packet, 4);

#if 1//8k
	packet.m_body[0] = 0xAA;
	packet.m_body[1] = 0x00;
	packet.m_body[2] = 0x15;
	packet.m_body[3] = 0x88;
#else //16k
	packet.m_body[0] = 0xAA;
	packet.m_body[1] = 0x00;
	packet.m_body[2] = 0x14;
	packet.m_body[3] = 0x08;
#endif

	packet.m_headerType  = RTMP_PACKET_SIZE_MEDIUM;
	packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;  
	packet.m_hasAbsTimestamp = 0;
	packet.m_nChannel   = 0x04;  
	packet.m_nTimeStamp = 0;  
	packet.m_nInfoField2 = rtmpctx->m_stream_id;
	packet.m_nBodySize  = 4;  

	//printf("audio header send....\\n");

	//调用发送接口  
	int nRet = RTMP_SendPacket(rtmpctx, &packet, TRUE);  
	RTMPPacket_Free(&packet);//释放内存  
	return nRet;  

h264帧数据封装发送

static int rtmp_packet_send_h264(unsigned char *frame, int length, unsigned long long pts)

	if( length < 0 ) 
		return 0;
	uint8_t *data = frame/*skip 0001*/; 
	int      size = length;

	unsigned long long rpts = pts;

	RTMPPacket packet;
	RTMPPacket_Alloc(&packet, 9 + size);

	packet.m_packetType 	= RTMP_PACKET_TYPE_VIDEO;
	packet.m_nBodySize  	= 9 + size;
	packet.m_nChannel   	= 0x04;
	packet.m_nTimeStamp 	= rpts/1000;
	packet.m_hasAbsTimestamp= 0;
	packet.m_headerType 	= RTMP_PACKET_SIZE_MEDIUM;
	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;
	packet.m_nInfoField2 	= rtmpctx->m_stream_id;

	memcpy(packet.m_body    , (data[0]&0x1f)!=1? "\\x17\\x01":"\\x27\\x01", 2);
	memcpy(packet.m_body + 2, "\\x00\\x00\\x00", 3);
	AMF_EncodeInt32(packet.m_body + 5, packet.m_body + 9, size);
	memcpy(packet.m_body + 9, data, size);

	int nRet = RTMP_SendPacket(rtmpctx, &packet, 1);
	RTMPPacket_Free(&packet);
	return nRet;

h265帧数据封装发送

static int rtmp_packet_send_h265(unsigned char *frame, int length, unsigned long long pts)

	if( length < 0 ) 
		return 0;
	uint8_t *data = frame/*skip 0001*/; 
	int      size = length;
	int i = 0;

	unsigned long long rpts = pts;

	RTMPPacket packet;
	RTMPPacket_Alloc(&packet, 9 + size);

	packet.m_packetType 	= RTMP_PACKET_TYPE_VIDEO;
	packet.m_nBodySize  	= 9 + size;
	packet.m_nChannel   	= 0x04;
	packet.m_nTimeStamp 	= rpts/1000;
	packet.m_hasAbsTimestamp= 0;
	packet.m_headerType 	= RTMP_PACKET_SIZE_MEDIUM;
	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;
	packet.m_nInfoField2 	= rtmpctx->m_stream_id;

	if (((data[0] & 0x7e) >> 1) == 19) 
		packet.m_body[i++] = 0x1c;
	 else 
		packet.m_body[i++] = 0x2c;
	
	packet.m_body[i++] = 0x01;//AVC NALU
	packet.m_body[i++] = 0x00;
	packet.m_body[i++] = 0x00;
	packet.m_body[i++] = 0x00;

	//NALU size
	packet.m_body[i++] = (size >> 24) & 0xff;
	packet.m_body[i++] = (size >> 16) & 0xff;
	packet.m_body[i++] = (size >> 8) & 0xff;
	packet.m_body[i++] = (size) & 0xff;
	
	memcpy(&packet.m_body[i], data, size);

	int nRet = RTMP_SendPacket(rtmpctx, &packet, 1);
	RTMPPacket_Free(&packet);

	//printf("send package body...\\n");
	
	return nRet;

audio数据封装发送

static int rtmp_write_audio_data(char *data, int data_len, unsigned long long pts)

	RTMP* rtmpctx = (RTMP*)rtmp_handle->rtmpctx;
	int size = data_len -7 + 2;  
	RTMPPacket packet;  
	RTMPPacket_Reset(&packet);  
	RTMPPacket_Alloc(&packet, size);  
	int i=0;
	unsigned long long rpts = pts;// - pusher->vref;
#if 1//8k

	packet.m_body[i++] = 0xAA; //8K
	packet.m_body[i++] = 0x01;
#else //16k
	packet.m_body[i++] = 0xAA; //16k
	packet.m_body[i++] = 0x01;
#endif
	memcpy(&packet.m_body[i], data + 7, data_len - 7);
	packet.m_headerType  = RTMP_PACKET_SIZE_MEDIUM;
	packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;
	packet.m_hasAbsTimestamp = 0;  
	packet.m_nChannel   = 0x04;  
	packet.m_nTimeStamp = rpts/1000;
	packet.m_nInfoField2 = rtmpctx->m_stream_id;
	packet.m_nBodySize  = size;  
	//调用发送接口 
	int nRet = RTMP_SendPacket(rtmpctx, &packet, TRUE);
	RTMPPacket_Free(&packet);//释放内存  
	return nRet;  

注意实项:头部封装发送只在最开始的时候发送一次,先发送h264/h265头部封装数据,再发送audio头部封装数据,最后循环发送video和audio帧数据

4.效果展示

vlc无法播放rtmp接收到的h265格式视频数据,我测试使用的是easyplayerPro

如有疑问,请留言交流,本人可以提供一些力所能及的帮助!

 

视频流媒体推流平台RTMP协议是如何进行网络连接并推送视频流的?

RTMP是基于TCP协议的多媒体传输协议,因此RTMP服务器本质上是一个TCP服务器,它的逻辑结构基本上和普通的TCP服务器是类似的。我们视频直播点播流媒体服务器能够通过RTMP协议进行推流分发。

技术图片

本文我来跟大家介绍一下RTMP服务器建立推流的步骤。首先看一下RTMP 握手(Handshake)过程:

技术图片

1.握手开始于客户端发送C0、C1块。服务器收到C0或C1后发送S0和S1。
2.当客户端收齐S0和S1后,开始发送C2。当服务器收齐C0和C1后,开始发送S2。
3.当客户端和服务器分别收到S2和C2后,握手完成。

在实际工程应用中,一般是客户端先将C0, C1块同时发出,服务器在收到C1 之后同时将S0, S1, S2发给客户端。之后客户端向服务器端发送C2块,简单握手完成。

建立网络连接(NetConnection):

技术图片

  1. 客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。

  2. 服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。

  3. 服务器发送设置带宽协议消息到客户端。

  4. 客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)
    协议消息到服务器端。

  5. 服务端向客户端发送“流开始”(Stream Begin)。

  6. 服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。

建立网络流(Create Stream):

技术图片

  1. 客户端发送命令消息中的“创建流”(CreateStream)命令到服务器端。
  2. 服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。

播放(Play):

技术图片

    1. 客户端发送命令“播放”给服务器
    2. 接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息给客户端
    3. 服务器发送“stream begin”给客户端,告诉客户端流的id
    4. 播放命令成功的话,服务器发送命令消息中的“响应状态” NetStream.Play.Start & NetStream.Play.Reset,告知客户端“播放”命令执行成功
    5. 服务器发送客户端要播放的音频和视频数据

以上是关于rtmp客户端h265/h264音视频数据推流的主要内容,如果未能解决你的问题,请参考以下文章

Android IOS WebRTC 音视频开发总结(七一)-- H265/H264有何不同

EasyRTMP获取H.264实时流并转化成为RTMP直播推流之EasyRTMP-iOS如何处理H264关键帧和SPSPPS数据的

利用FFMPEG以及EasyRTMP实现读取H.264文件推RTMP视频流的两种方式

国内AI大模型 VS chatgpt (20)-- H265/H264有何不同,h265有什么优势?

智勇电子最新H.265支持SRT协议编码器ZY-EDHV201发布

iOS:基于RTMP的视频推流