rtmp客户端h265/h264音视频数据推流
Posted while(null)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了rtmp客户端h265/h264音视频数据推流相关的知识,希望对你有一定的参考价值。
1.rtmp视频包格式
类型 | 长度 | 值 |
FrameType | 4bit | 1表示关键帧,2表示非关键帧,3表示一次性帧,4为服务器保留,5表示视频信息或命令帧 |
CodecID | 4bit | 1表示JPG,2表示Sorenson H263,3表示屏幕录像,4表示VP6 ON2,,5表示带alphat通道的VP6 ON2,6表示版本2的屏幕录像,7表示avc,即h264 |
VideoData | N 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视频包结构
类型 | 长度 | 值 |
AVCPacketType | 8bit | 0表示AVC Sequence头,1表示nalu, 2一般不支持较低level的h264 |
Composition time | 3 byte | 如果AVCPacketType为1表示Composition time offset, 其他值是全为0 |
Data | n 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):
-
客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。
-
服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。
-
服务器发送设置带宽协议消息到客户端。
-
客户端处理设置带宽协议消息后,发送确认窗口大小(Window Acknowledgement Size)
协议消息到服务器端。 -
服务端向客户端发送“流开始”(Stream Begin)。
-
服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。
建立网络流(Create Stream):
- 客户端发送命令消息中的“创建流”(CreateStream)命令到服务器端。
- 服务器端接收到“创建流”命令后,发送命令消息中的“结果”(_result),通知客户端流的状态。
播放(Play):
- 客户端发送命令“播放”给服务器
- 接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息给客户端
- 服务器发送“stream begin”给客户端,告诉客户端流的id
- 播放命令成功的话,服务器发送命令消息中的“响应状态” NetStream.Play.Start & NetStream.Play.Reset,告知客户端“播放”命令执行成功
- 服务器发送客户端要播放的音频和视频数据
以上是关于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有什么优势?