RTMP协议本身是不支持H265的。但现在的设备越来越追求更高的压缩比和更高的图形质量。H265相对其他的媒体格式更多受到厂家的重视。rtmp协议要支持H265首先要定义一个ID。按照大家的约定来看,基本使用12(0xc)作为ID. 同时相对H264对NALU的分析要进行改变。并对发送的Metadata数据进行修改。
先看下发送metadata:
-
int
SendVideoSpsPpsVps(RTMP*
r,
unsigned
char*
pps,
int
pps_len,
unsigned
char*
sps,
int
sps_len,
unsigned
char*
vps,
-
int
vps_len,
uint32_t
dts)
-
{
-
char
tBuffer[
RTMP_HEAD_SIZE
+
1024]
= {
0 }
;
-
RTMPPacket*
packet
=
(RTMPPacket*)tBuffer;
-
packet->m_body
=
(char*)packet
+
RTMP_HEAD_SIZE;
-
unsigned
char*
body
=
(unsigned
char*)packet->m_body;
-
//
http://ffmpeg.org/doxygen/trunk/hevc_8c_source.html#l00040
hvcc_write
函数
-
//
在nginx-rtmp中会跳过48位不去处理
我们在表示后面补0
-
//
skip
tag
header
and
configurationVersion(1
byte)
-
int
i
=
0
;
-
body[
i++]
=
0x1C
;
-
body[
i++]
=
0x0
;
-
body[
i++]
=
0x0
;
-
body[
i++]
=
0x0
;
-
body[
i++]
=
0x0
;
-
body[
i++]
=
0x1
;
-
//
general_profile_idc
8bit
-
body[
i++]
=
sps[
1]
;
-
//
general_profile_compatibility_flags
32
bit
-
body[
i++]
=
sps[
2]
;
-
body[
i++]
=
sps[
3]
;
-
body[
i++]
=
sps[
4]
;
-
body[
i++]
=
sps[
5]
;
-
-
//
48
bit
NUll
nothing
deal
in
rtmp
-
body[
i++]
=
sps[
6]
;
-
body[
i++]
=
sps[
7]
;
-
body[
i++]
=
sps[
8]
;
-
body[
i++]
=
sps[
9]
;
-
body[
i++]
=
sps[
10]
;
-
body[
i++]
=
sps[
11]
;
-
-
//
general_level_idc
-
body[
i++]
=
sps[
12]
;
-
-
//
48
bit
NUll
nothing
deal
in
rtmp
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
body[
i++]
=
0
;
-
//
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++]
=
(1
>>
8
)
&
0xff
;
-
body[
i++]
=
1
&
0xff
;
-
body[
i++]
=
(vps_len
>>
8
)
&
0xff
;
-
body[
i++]
=
(vps_len)
&
0xff
;
-
memcpy(&body[
i],
vps,
vps_len);
-
i
+=
vps_len;
-
-
//
sps
-
body[
i++]
=
0x21
;
//
sps
33
-
body[
i++]
=
0
;
-
body[
i++]
=
1
;
-
body[
i++]
=
(sps_len
>>
8
)
&
0xff
;
-
body[
i++]
=
sps_len
&
0xff
;
-
memcpy(&body[
i],
sps,
sps_len);
-
i
+=
sps_len;
-
-
//
pps
-
body[
i++]
=
0x22
;
//
pps
34
-
body[
i++]
=
(1
>>
8
)
&
0xff
;
-
body[
i++]
=
1
&
0xff
;
-
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
=
dts;
-
packet->m_hasAbsTimestamp
=
0
;
-
packet->m_headerType
=
RTMP_PACKET_SIZE_LARGE;
-
packet->m_nInfoField2
=
r->m_stream_id;
-
int
nRet
=
0
;
-
if
(RTMP_IsConnected(r))
-
nRet
=
RTMP_SendPacket(r,
packet,
0
);
//
1
为放进发送队列,
0
是不放进发送队列,
直接发送
-
return
nRet;
-
}
上面中需要注意// bit(16) avgFrameRate;
/* bit(2) constantFrameRate; */
/* bit(3) numTemporalLayers; */
/* bit(1) temporalIdNested; */
body[i++] = 0x83;
这个地方在一些网站上写的是0,或者不处理,可能造成一些服务器,不工作。
其他,在发送媒体信息的时候需要解释sps。对H265的解释跟H264不一样。
-
#ifndef h265_decode_info_h__
-
#define h265_decode_info_h__
-
-
-
#include <iostream>
-
#include <sstream>
-
//#include <unistd.h>
-
#include <stdint.h>
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
struct vc_params_t
-
{
-
LONG width, height;
-
DWORD profile, level;
-
DWORD nal_length_size;
-
void clear()
-
{
-
memset(
this,
0,
sizeof(*
this));
-
}
-
};
-
-
class NALBitstream
-
{
-
public:
-
NALBitstream() : m_data(
NULL), m_len(
0), m_idx(
0), m_bits(
0), m_byte(
0), m_zeros(
0) {};
-
NALBitstream(
void * data,
int len) { Init(data, len); };
-
void Init(void * data, int len) { m_data = (LPBYTE)data; m_len = len; m_idx =
0; m_bits =
0; m_byte =
0; m_zeros =
0; };
-
-
-
BYTE GetBYTE()
-
{
-
//printf("m_idx=%d,m_len=%d\n", m_idx, m_len);
-
if (m_idx >= m_len)
-
return
0;
-
BYTE b = m_data[m_idx++];
-
-
// to avoid start-code emulation, a byte 0x03 is inserted
-
// after any 00 00 pair. Discard that here.
-
if (b ==
0)
-
{
-
m_zeros++;
-
if ((m_idx < m_len) && (m_zeros ==
2) && (m_data[m_idx] ==
0x03))
-
{
-
-
m_idx++;
-
m_zeros =
0;
-
}
-
}
-
-
else
-
{
-
m_zeros =
0;
-
-
}
-
return b;
-
};
-
-
-
UINT32 GetBit()
-
{
-
-
if (m_bits ==
0)
-
{
-
m_byte = GetBYTE();
-
m_bits =
8;
-
-
}
-
m_bits--;
-
return (m_byte >> m_bits) &
0x1;
-
};
-
-
UINT32 GetWord(int bits)
-
{
-
-
UINT32 u =
0;
-
while (bits >
0)
-
-
{
-
u <<=
1;
-
u |= GetBit();
-
bits--;
-
}
-
return u;
-
};
-
UINT32 GetUE()
-
{
-
-
// Exp-Golomb entropy coding: leading zeros, then a one, then
-
// the data bits. The number of leading zeros is the number of
-
// data bits, counting up from that number of 1s as the base.
-
// That is, if you see
-
// 0001010
-
// You have three leading zeros, so there are three data bits (010)
-
// counting up from a base of 111: thus 111 + 010 = 1001 = 9
-
int zeros =
0;
-
while (m_idx < m_len && GetBit() ==
0) zeros++;
-
return GetWord(zeros) + ((
1 << zeros) -
1);
-
};
-
-
-
INT32 GetSE()
-
{
-
-
// same as UE but signed.
-
// basically the unsigned numbers are used as codes to indicate signed numbers in pairs
-
// in increasing value. Thus the encoded values
-
// 0, 1, 2, 3, 4
-
// mean
-
// 0, 1, -1, 2, -2 etc
-
UINT32 UE = GetUE();
-
bool positive = UE &
1;
-
INT32 SE = (UE +
1) >>
1;
-
if (!positive)
-
{
-
SE = -SE;
-
}
-
return SE;
-
};
-
-
-
private:
-
LPBYTE m_data;
-
int m_len;
-
int m_idx;
-
int m_bits;
-
BYTE m_byte;
-
int m_zeros;
-
};
-
-
-
bool ParseSequenceParameterSet(BYTE* data, int size, vc_params_t& params)
-
{
-
if (size <
20)
-
{
-
return
false;
-
}
-
-
NALBitstream bs(data, size);
-
-
// seq_parameter_set_rbsp()
-
bs.GetWord(
4);
// sps_video_parameter_set_id
-
int sps_max_sub_layers_minus1 = bs.GetWord(
3);
// "The value of sps_max_sub_layers_minus1 shall be in the range of 0 to 6, inclusive."
-
if (sps_max_sub_layers_minus1 >
6)
-
{
-
return
false;
-
}
-
bs.GetWord(
1);
// sps_temporal_id_nesting_flag
-
// profile_tier_level( sps_max_sub_layers_minus1 )
-
{
-
bs.GetWord(
2);
// general_profile_space
-
bs.GetWord(
1);
// general_tier_flag
-
params.profile = bs.GetWord(
5);
// general_profile_idc
-
bs.GetWord(
32);
// general_profile_compatibility_flag[32]
-
bs.GetWord(
1);
// general_progressive_source_flag
-
bs.GetWord(
1);
// general_interlaced_source_flag
-
bs.GetWord(
1);
// general_non_packed_constraint_flag
-
bs.GetWord(
1);
// general_frame_only_constraint_flag
-
bs.GetWord(
44);
// general_reserved_zero_44bits
-
params.level = bs.GetWord(
8);
// general_level_idc
-
unsigned
char sub_layer_profile_present_flag[
6] = {
0 };
-
unsigned
char sub_layer_level_present_flag[
6] = {
0 };
-
for (
int i =
0; i < sps_max_sub_layers_minus1; i++)
-
{
-
sub_layer_profile_present_flag[i] = bs.GetWord(
1);
-
sub_layer_level_present_flag[i] = bs.GetWord(
1);
-
}
-
if (sps_max_sub_layers_minus1 >
0)
-
{
-
for (
int i = sps_max_sub_layers_minus1; i <
8; i++)
-
{
-
unsigned
char reserved_zero_2bits = bs.GetWord(
2);
-
}
-
}
-
for (
int i =
0; i < sps_max_sub_layers_minus1; i++)
-
{
-
if (sub_layer_profile_present_flag[i])
-
{
-
bs.GetWord(
2);
// sub_layer_profile_space[i]
-
bs.GetWord(
1);
// sub_layer_tier_flag[i]
-
bs.GetWord(
5);
// sub_layer_profile_idc[i]
-
bs.GetWord(
32);
// sub_layer_profile_compatibility_flag[i][32]
-
bs.GetWord(
1);
// sub_layer_progressive_source_flag[i]
-
bs.GetWord(
1);
// sub_layer_interlaced_source_flag[i]
-
bs.GetWord(
1);
// sub_layer_non_packed_constraint_flag[i]
-
bs.GetWord(
1);
// sub_layer_frame_only_constraint_flag[i]
-
bs.GetWord(
44);
// sub_layer_reserved_zero_44bits[i]
-
}
-
if (sub_layer_level_present_flag[i])
-
{
-
bs.GetWord(
8);
// sub_layer_level_idc[i]
-
}
-
}
-
}
-
unsigned
long sps_seq_parameter_set_id = bs.GetUE();
// "The value of sps_seq_parameter_set_id shall be in the range of 0 to 15, inclusive."
-
/*if (sps_seq_parameter_set_id > 15)
-
{
-
printf("enter2\r\n");
-
return false;
-
}*/
-
unsigned
long chroma_format_idc = bs.GetUE();
// "The value of chroma_format_idc shall be in the range of 0 to 3, inclusive."
-
/*if (sps_seq_parameter_set_id > 3)
-
{
-
printf("enter3\r\n");
-
return false;
-
}*/
-
if (chroma_format_idc ==
3)
-
{
-
bs.GetWord(
1);
// separate_colour_plane_flag
-
}
-
params.width = bs.GetUE();
// pic_width_in_luma_samples
-
params.height = bs.GetUE();
// pic_height_in_luma_samples
-
if (bs.GetWord(
1))
-
{
// conformance_window_flag
-
bs.GetUE();
// conf_win_left_offset
-
bs.GetUE();
// conf_win_right_offset
-
bs.GetUE();
// conf_win_top_offset
-
bs.GetUE();
// conf_win_bottom_offset
-
}
-
unsigned
long bit_depth_luma_minus8 = bs.GetUE();
-
unsigned
long bit_depth_chroma_minus8 = bs.GetUE();
-
/*if (bit_depth_luma_minus8 != bit_depth_chroma_minus8)
-
{
-
printf("enter4\r\n");
-
return false;
-
}*/
-
//...
-
-
-
return
true;
-
}
-
-
#endif // h265_decode_info_h__
这样可以发送根据媒体格式进行头信息填写了。
-
if(lpMetaData =
= NULL)
-
{
-
return
-1;
-
}
-
char
buffer[1024] = {0};
-
char
*body = buffer+RTMP_MAX_HEADER_SIZE;
-
-
char
* p = (char *)body;
-
p =
put_byte(p, AMF_STRING );
-
p =
put_amf_string(p , "@setDataFrame" );
-
-
p =
put_byte( p, AMF_STRING );
-
p =
put_amf_string( p, "onMetaData" );
-
-
p =
put_byte(p, AMF_OBJECT );
-
p =
put_amf_string( p, "copyright" );
-
p =
put_byte(p, AMF_STRING );
-
p =
put_amf_string( p, "CarEyeRTMP" );
-
-
if
(type == 1)
-
{
-
p =
put_amf_string(p, "width");
-
p =
put_amf_double(p, lpMetaData->Width);
-
-
p =
put_amf_string(p, "height");
-
p =
put_amf_double(p, lpMetaData->Height);
-
-
p =
put_amf_string(p, "framerate");
-
p =
put_amf_double(p, lpMetaData->FrameRate);
-
-
p =
put_amf_string(p, "videocodecid");
-
if
(lpMetaData->VCodec == CAREYE_VCODE_H264)
-
{
-
p =
put_amf_double(p, FLV_CODECID_H264);
-
}
-
else
-
{
-
p =
put_amf_double(p, FLV_CODECID_H265);
-
}
-
}
-
-
p =
put_amf_string( p, "audiosamplerate");
-
p =
put_amf_double( p, lpMetaData->SampleRate);
-
-
p =
put_amf_string( p, "audiocodecid");
-
p =
put_amf_double( p, 10);
-
-
p =
put_amf_string( p, "" );
-
p =
put_byte( p, AMF_OBJECT_END );
car-eye RTMP推流是将GB28181或者GT1078协议的数据的音视频数据推送到RTMP拉流服务器。以实现客户端对RTMP,http,websocket,HLS等多种方式的拉取和播放。
car-eye流媒体服务器实现了对监控和车载移动设备多种场景的支持。相关的开源源码地址:https://github.com/Car-eye-team/
https://gitee.com/careye_open_source_platform_group
本文章参考:https://blog.csdn.net/qq_33795447/article/details/89457581