RTMP 推流增加对H265的支持

RTMP协议本身是不支持H265的。但现在的设备越来越追求更高的压缩比和更高的图形质量。H265相对其他的媒体格式更多受到厂家的重视。rtmp协议要支持H265首先要定义一个ID。按照大家的约定来看,基本使用12(0xc)作为ID. 同时相对H264对NALU的分析要进行改变。并对发送的Metadata数据进行修改。

先看下发送metadata:


   
   
  1. int SendVideoSpsPpsVps(RTMP* r, unsigned char* pps, int pps_len, unsigned char* sps, int sps_len, unsigned char* vps,
  2. int vps_len, uint32_t dts)
  3. {
  4. char tBuffer[ RTMP_HEAD_SIZE + 1024] = { 0 } ;
  5. RTMPPacket* packet = (RTMPPacket*)tBuffer;
  6. packet->m_body = (char*)packet + RTMP_HEAD_SIZE;
  7. unsigned char* body = (unsigned char*)packet->m_body;
  8. // http://ffmpeg.org/doxygen/trunk/hevc_8c_source.html#l00040 hvcc_write 函数
  9. // 在nginx-rtmp中会跳过48位不去处理 我们在表示后面补0
  10. // skip tag header and configurationVersion(1 byte)
  11. int i = 0 ;
  12. body[ i++] = 0x1C ;
  13. body[ i++] = 0x0 ;
  14. body[ i++] = 0x0 ;
  15. body[ i++] = 0x0 ;
  16. body[ i++] = 0x0 ;
  17. body[ i++] = 0x1 ;
  18. // general_profile_idc 8bit
  19. body[ i++] = sps[ 1] ;
  20. // general_profile_compatibility_flags 32 bit
  21. body[ i++] = sps[ 2] ;
  22. body[ i++] = sps[ 3] ;
  23. body[ i++] = sps[ 4] ;
  24. body[ i++] = sps[ 5] ;
  25. // 48 bit NUll nothing deal in rtmp
  26. body[ i++] = sps[ 6] ;
  27. body[ i++] = sps[ 7] ;
  28. body[ i++] = sps[ 8] ;
  29. body[ i++] = sps[ 9] ;
  30. body[ i++] = sps[ 10] ;
  31. body[ i++] = sps[ 11] ;
  32. // general_level_idc
  33. body[ i++] = sps[ 12] ;
  34. // 48 bit NUll nothing deal in rtmp
  35. body[ i++] = 0 ;
  36. body[ i++] = 0 ;
  37. body[ i++] = 0 ;
  38. body[ i++] = 0 ;
  39. body[ i++] = 0 ;
  40. body[ i++] = 0 ;
  41. body[ i++] = 0 ;
  42. body[ i++] = 0 ;
  43. // bit(16) avgFrameRate;
  44. /* bit(2) constantFrameRate; */
  45. /* bit(3) numTemporalLayers; */
  46. /* bit(1) temporalIdNested; */
  47. body[ i++] = 0x83 ;
  48. /* unsigned int(8) numOfArrays; 03 */
  49. body[ i++] = 0x03 ;
  50. // vps 32
  51. body[ i++] = 0x20 ;
  52. body[ i++] = (1 >> 8 ) & 0xff ;
  53. body[ i++] = 1 & 0xff ;
  54. body[ i++] = (vps_len >> 8 ) & 0xff ;
  55. body[ i++] = (vps_len) & 0xff ;
  56. memcpy(&body[ i], vps, vps_len);
  57. i += vps_len;
  58. // sps
  59. body[ i++] = 0x21 ; // sps 33
  60. body[ i++] = 0 ;
  61. body[ i++] = 1 ;
  62. body[ i++] = (sps_len >> 8 ) & 0xff ;
  63. body[ i++] = sps_len & 0xff ;
  64. memcpy(&body[ i], sps, sps_len);
  65. i += sps_len;
  66. // pps
  67. body[ i++] = 0x22 ; // pps 34
  68. body[ i++] = (1 >> 8 ) & 0xff ;
  69. body[ i++] = 1 & 0xff ;
  70. body[ i++] = (pps_len >> 8 ) & 0xff ;
  71. body[ i++] = (pps_len) & 0xff ;
  72. memcpy(&body[ i], pps, pps_len);
  73. i += pps_len;
  74. packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
  75. packet->m_nBodySize = i;
  76. packet->m_nChannel = 0x04 ;
  77. packet->m_nTimeStamp = dts;
  78. packet->m_hasAbsTimestamp = 0 ;
  79. packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
  80. packet->m_nInfoField2 = r->m_stream_id;
  81. int nRet = 0 ;
  82. if (RTMP_IsConnected(r))
  83. nRet = RTMP_SendPacket(r, packet, 0 ); // 1 为放进发送队列, 0 是不放进发送队列, 直接发送
  84. return nRet;
  85. }

上面中需要注意// bit(16) avgFrameRate;
    /* bit(2) constantFrameRate; */
    /* bit(3) numTemporalLayers; */
    /* bit(1) temporalIdNested; */
    body[i++] = 0x83;

这个地方在一些网站上写的是0,或者不处理,可能造成一些服务器,不工作。

 

其他,在发送媒体信息的时候需要解释sps。对H265的解释跟H264不一样。


   
   
  1. #ifndef h265_decode_info_h__
  2. #define h265_decode_info_h__
  3. #include <iostream>
  4. #include <sstream>
  5. //#include <unistd.h>
  6. #include <stdint.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <stdlib.h>
  10. struct vc_params_t
  11. {
  12. LONG width, height;
  13. DWORD profile, level;
  14. DWORD nal_length_size;
  15. void clear()
  16. {
  17. memset( this, 0, sizeof(* this));
  18. }
  19. };
  20. class NALBitstream
  21. {
  22. public:
  23. NALBitstream() : m_data( NULL), m_len( 0), m_idx( 0), m_bits( 0), m_byte( 0), m_zeros( 0) {};
  24. NALBitstream( void * data, int len) { Init(data, len); };
  25. 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; };
  26. BYTE GetBYTE()
  27. {
  28. //printf("m_idx=%d,m_len=%d\n", m_idx, m_len);
  29. if (m_idx >= m_len)
  30. return 0;
  31. BYTE b = m_data[m_idx++];
  32. // to avoid start-code emulation, a byte 0x03 is inserted
  33. // after any 00 00 pair. Discard that here.
  34. if (b == 0)
  35. {
  36. m_zeros++;
  37. if ((m_idx < m_len) && (m_zeros == 2) && (m_data[m_idx] == 0x03))
  38. {
  39. m_idx++;
  40. m_zeros = 0;
  41. }
  42. }
  43. else
  44. {
  45. m_zeros = 0;
  46. }
  47. return b;
  48. };
  49. UINT32 GetBit()
  50. {
  51. if (m_bits == 0)
  52. {
  53. m_byte = GetBYTE();
  54. m_bits = 8;
  55. }
  56. m_bits--;
  57. return (m_byte >> m_bits) & 0x1;
  58. };
  59. UINT32 GetWord(int bits)
  60. {
  61. UINT32 u = 0;
  62. while (bits > 0)
  63. {
  64. u <<= 1;
  65. u |= GetBit();
  66. bits--;
  67. }
  68. return u;
  69. };
  70. UINT32 GetUE()
  71. {
  72. // Exp-Golomb entropy coding: leading zeros, then a one, then
  73. // the data bits. The number of leading zeros is the number of
  74. // data bits, counting up from that number of 1s as the base.
  75. // That is, if you see
  76. // 0001010
  77. // You have three leading zeros, so there are three data bits (010)
  78. // counting up from a base of 111: thus 111 + 010 = 1001 = 9
  79. int zeros = 0;
  80. while (m_idx < m_len && GetBit() == 0) zeros++;
  81. return GetWord(zeros) + (( 1 << zeros) - 1);
  82. };
  83. INT32 GetSE()
  84. {
  85. // same as UE but signed.
  86. // basically the unsigned numbers are used as codes to indicate signed numbers in pairs
  87. // in increasing value. Thus the encoded values
  88. // 0, 1, 2, 3, 4
  89. // mean
  90. // 0, 1, -1, 2, -2 etc
  91. UINT32 UE = GetUE();
  92. bool positive = UE & 1;
  93. INT32 SE = (UE + 1) >> 1;
  94. if (!positive)
  95. {
  96. SE = -SE;
  97. }
  98. return SE;
  99. };
  100. private:
  101. LPBYTE m_data;
  102. int m_len;
  103. int m_idx;
  104. int m_bits;
  105. BYTE m_byte;
  106. int m_zeros;
  107. };
  108. bool ParseSequenceParameterSet(BYTE* data, int size, vc_params_t& params)
  109. {
  110. if (size < 20)
  111. {
  112. return false;
  113. }
  114. NALBitstream bs(data, size);
  115. // seq_parameter_set_rbsp()
  116. bs.GetWord( 4); // sps_video_parameter_set_id
  117. 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."
  118. if (sps_max_sub_layers_minus1 > 6)
  119. {
  120. return false;
  121. }
  122. bs.GetWord( 1); // sps_temporal_id_nesting_flag
  123. // profile_tier_level( sps_max_sub_layers_minus1 )
  124. {
  125. bs.GetWord( 2); // general_profile_space
  126. bs.GetWord( 1); // general_tier_flag
  127. params.profile = bs.GetWord( 5); // general_profile_idc
  128. bs.GetWord( 32); // general_profile_compatibility_flag[32]
  129. bs.GetWord( 1); // general_progressive_source_flag
  130. bs.GetWord( 1); // general_interlaced_source_flag
  131. bs.GetWord( 1); // general_non_packed_constraint_flag
  132. bs.GetWord( 1); // general_frame_only_constraint_flag
  133. bs.GetWord( 44); // general_reserved_zero_44bits
  134. params.level = bs.GetWord( 8); // general_level_idc
  135. unsigned char sub_layer_profile_present_flag[ 6] = { 0 };
  136. unsigned char sub_layer_level_present_flag[ 6] = { 0 };
  137. for ( int i = 0; i < sps_max_sub_layers_minus1; i++)
  138. {
  139. sub_layer_profile_present_flag[i] = bs.GetWord( 1);
  140. sub_layer_level_present_flag[i] = bs.GetWord( 1);
  141. }
  142. if (sps_max_sub_layers_minus1 > 0)
  143. {
  144. for ( int i = sps_max_sub_layers_minus1; i < 8; i++)
  145. {
  146. unsigned char reserved_zero_2bits = bs.GetWord( 2);
  147. }
  148. }
  149. for ( int i = 0; i < sps_max_sub_layers_minus1; i++)
  150. {
  151. if (sub_layer_profile_present_flag[i])
  152. {
  153. bs.GetWord( 2); // sub_layer_profile_space[i]
  154. bs.GetWord( 1); // sub_layer_tier_flag[i]
  155. bs.GetWord( 5); // sub_layer_profile_idc[i]
  156. bs.GetWord( 32); // sub_layer_profile_compatibility_flag[i][32]
  157. bs.GetWord( 1); // sub_layer_progressive_source_flag[i]
  158. bs.GetWord( 1); // sub_layer_interlaced_source_flag[i]
  159. bs.GetWord( 1); // sub_layer_non_packed_constraint_flag[i]
  160. bs.GetWord( 1); // sub_layer_frame_only_constraint_flag[i]
  161. bs.GetWord( 44); // sub_layer_reserved_zero_44bits[i]
  162. }
  163. if (sub_layer_level_present_flag[i])
  164. {
  165. bs.GetWord( 8); // sub_layer_level_idc[i]
  166. }
  167. }
  168. }
  169. 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."
  170. /*if (sps_seq_parameter_set_id > 15)
  171. {
  172. printf("enter2\r\n");
  173. return false;
  174. }*/
  175. unsigned long chroma_format_idc = bs.GetUE(); // "The value of chroma_format_idc shall be in the range of 0 to 3, inclusive."
  176. /*if (sps_seq_parameter_set_id > 3)
  177. {
  178. printf("enter3\r\n");
  179. return false;
  180. }*/
  181. if (chroma_format_idc == 3)
  182. {
  183. bs.GetWord( 1); // separate_colour_plane_flag
  184. }
  185. params.width = bs.GetUE(); // pic_width_in_luma_samples
  186. params.height = bs.GetUE(); // pic_height_in_luma_samples
  187. if (bs.GetWord( 1))
  188. { // conformance_window_flag
  189. bs.GetUE(); // conf_win_left_offset
  190. bs.GetUE(); // conf_win_right_offset
  191. bs.GetUE(); // conf_win_top_offset
  192. bs.GetUE(); // conf_win_bottom_offset
  193. }
  194. unsigned long bit_depth_luma_minus8 = bs.GetUE();
  195. unsigned long bit_depth_chroma_minus8 = bs.GetUE();
  196. /*if (bit_depth_luma_minus8 != bit_depth_chroma_minus8)
  197. {
  198. printf("enter4\r\n");
  199. return false;
  200. }*/
  201. //...
  202. return true;
  203. }
  204. #endif // h265_decode_info_h__

这样可以发送根据媒体格式进行头信息填写了。


   
   
  1. if(lpMetaData = = NULL)
  2. {
  3. return -1;
  4. }
  5. char buffer[1024] = {0};
  6. char *body = buffer+RTMP_MAX_HEADER_SIZE;
  7. char * p = (char *)body;
  8. p = put_byte(p, AMF_STRING );
  9. p = put_amf_string(p , "@setDataFrame" );
  10. p = put_byte( p, AMF_STRING );
  11. p = put_amf_string( p, "onMetaData" );
  12. p = put_byte(p, AMF_OBJECT );
  13. p = put_amf_string( p, "copyright" );
  14. p = put_byte(p, AMF_STRING );
  15. p = put_amf_string( p, "CarEyeRTMP" );
  16. if (type == 1)
  17. {
  18. p = put_amf_string(p, "width");
  19. p = put_amf_double(p, lpMetaData->Width);
  20. p = put_amf_string(p, "height");
  21. p = put_amf_double(p, lpMetaData->Height);
  22. p = put_amf_string(p, "framerate");
  23. p = put_amf_double(p, lpMetaData->FrameRate);
  24. p = put_amf_string(p, "videocodecid");
  25. if (lpMetaData->VCodec == CAREYE_VCODE_H264)
  26. {
  27. p = put_amf_double(p, FLV_CODECID_H264);
  28. }
  29. else
  30. {
  31. p = put_amf_double(p, FLV_CODECID_H265);
  32. }
  33. }
  34. p = put_amf_string( p, "audiosamplerate");
  35. p = put_amf_double( p, lpMetaData->SampleRate);
  36. p = put_amf_string( p, "audiocodecid");
  37. p = put_amf_double( p, 10);
  38. p = put_amf_string( p, "" );
  39. 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

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值