RTMP 推流增加对H265的支持-爱代码爱编程 (2024)

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 8bitbody[i++] = sps[1];// general_profile_compatibility_flags 32 bitbody[i++] = sps[2];body[i++] = sps[3];body[i++] = sps[4];body[i++] = sps[5]; // 48 bit NUll nothing deal in rtmpbody[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_idcbody[i++] = sps[12]; // 48 bit NUll nothing deal in rtmpbody[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 32body[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; // spsbody[i++] = 0x21; // sps 33body[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; // ppsbody[i++] = 0x22; // pps 34body[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

RTMP 推流增加对H265的支持-爱代码爱编程 (2024)

References

Top Articles
Latest Posts
Article information

Author: Edmund Hettinger DC

Last Updated:

Views: 5815

Rating: 4.8 / 5 (78 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: Edmund Hettinger DC

Birthday: 1994-08-17

Address: 2033 Gerhold Pine, Port Jocelyn, VA 12101-5654

Phone: +8524399971620

Job: Central Manufacturing Supervisor

Hobby: Jogging, Metalworking, Tai chi, Shopping, Puzzles, Rock climbing, Crocheting

Introduction: My name is Edmund Hettinger DC, I am a adventurous, colorful, gifted, determined, precious, open, colorful person who loves writing and wants to share my knowledge and understanding with you.