热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RTMP推流增加对H265的支持

RTMP协议本身是不支持H265的。但现在的设备越来越追求更高的压缩比和更高的图形质量。H265相对其他的媒体格式更多受到厂家的重视。rtmp协议要支持H265首先要定义一个ID。

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
#include
//#include
#include
#include
#include
#include
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_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 return GetWord(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 {
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 {
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




推荐阅读
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 配置IPv4静态路由实现企业网内不同网段用户互访
    本文介绍了通过配置IPv4静态路由实现企业网内不同网段用户互访的方法。首先需要配置接口的链路层协议参数和IP地址,使相邻节点网络层可达。然后按照静态路由组网图的操作步骤,配置静态路由。这样任意两台主机之间都能够互通。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
author-avatar
他的一个号码_616
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有