嵌入式 H264视频通过RTMP直播

时间:2021-09-06 05:20:29
前面的文章中提到了通过RTSP(Real Time Streaming Protocol)的方式来实现视频的直播,但RTSP方式的一个弊端是如果需要支持客户端通过网页来访问,就需要在在页面中嵌入一个ActiveX控件,而ActiveX一般都需要签名才能正常使用,否则用户在使用时还需要更改浏览器设置,并且ActiveX还只支持IE内核的浏览器,Chrome、FireFox需要IE插件才能运行,因此会特别影响用户体验。而RTMP(Real Time Messaging Protocol)很好的解决了这一个问题。由于RTMP是针对FLASH的流媒体协议,视频通过RTMP直播后,只需要在WEB上嵌入一个Web Player(如Jwplayer)即可观看,而且对平台也没什么限制,还可以方便的通过手机观看。

视频通过RTMP方式发布需要一个RTMP Server(常见的有FMS、Wowza Media Server, 开源的有CRtmpServer、Red5等),原始视频只要按照RTMP协议发送给RTMP Server就可以RTMP视频流的发布了。为了便于视频的打包发布,封装了一个RTMPStream,目前只支持发送H264的视频文件。可以直接发送H264数据帧或H264文件,RTMPStream提供的接口如下。

  1. class CRTMPStream
  2. {
  3. public:
  4. CRTMPStream(void);
  5. ~CRTMPStream(void);
  6. public:
  7. // 连接到RTMP Server
  8. bool Connect(const char* url);
  9. // 断开连接
  10. void Close();
  11. // 发送MetaData
  12. bool SendMetadata(LPRTMPMetadata lpMetaData);
  13. // 发送H264数据帧
  14. bool SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp);
  15. // 发送H264文件
  16. bool SendH264File(const char *pFileName);
  17. //...
  18. }

调用示例:

  1. #include <stdio.h>
  2. #include "RTMPStream\RTMPStream.h"
  3. int main(int argc,char* argv[])
  4. {
  5. CRTMPStream rtmpSender;
  6. bool bRet = rtmpSender.Connect("rtmp://192.168.1.104/live/test");
  7. rtmpSender.SendH264File("E:\\video\\test.264");
  8. rtmpSender.Close();
  9. }

通过JwPlayer播放效果如下:

嵌入式 H264视频通过RTMP直播

最后附上RTMPStream完整的代码:

  1. /********************************************************************
  2. filename:   RTMPStream.h
  3. created:    2013-04-3
  4. author:     firehood
  5. purpose:    发送H264视频到RTMP Server,使用libRtmp库
  6. *********************************************************************/
  7. #pragma once
  8. #include "rtmp.h"
  9. #include "rtmp_sys.h"
  10. #include "amf.h"
  11. #include <stdio.h>
  12. #define FILEBUFSIZE (1024 * 1024 * 10)       //  10M
  13. // NALU单元
  14. typedef struct _NaluUnit
  15. {
  16. int type;
  17. int size;
  18. unsigned char *data;
  19. }NaluUnit;
  20. typedef struct _RTMPMetadata
  21. {
  22. // video, must be h264 type
  23. unsigned int    nWidth;
  24. unsigned int    nHeight;
  25. unsigned int    nFrameRate;     // fps
  26. unsigned int    nVideoDataRate; // bps
  27. unsigned int    nSpsLen;
  28. unsigned char   Sps[1024];
  29. unsigned int    nPpsLen;
  30. unsigned char   Pps[1024];
  31. // audio, must be aac type
  32. bool            bHasAudio;
  33. unsigned int    nAudioSampleRate;
  34. unsigned int    nAudioSampleSize;
  35. unsigned int    nAudioChannels;
  36. char            pAudioSpecCfg;
  37. unsigned int    nAudioSpecCfgLen;
  38. } RTMPMetadata,*LPRTMPMetadata;
  39. class CRTMPStream
  40. {
  41. public:
  42. CRTMPStream(void);
  43. ~CRTMPStream(void);
  44. public:
  45. // 连接到RTMP Server
  46. bool Connect(const char* url);
  47. // 断开连接
  48. void Close();
  49. // 发送MetaData
  50. bool SendMetadata(LPRTMPMetadata lpMetaData);
  51. // 发送H264数据帧
  52. bool SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp);
  53. // 发送H264文件
  54. bool SendH264File(const char *pFileName);
  55. private:
  56. // 送缓存中读取一个NALU包
  57. bool ReadOneNaluFromBuf(NaluUnit &nalu);
  58. // 发送数据
  59. int SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp);
  60. private:
  61. RTMP* m_pRtmp;
  62. unsigned char* m_pFileBuf;
  63. unsigned int  m_nFileBufSize;
  64. unsigned int  m_nCurPos;
  65. };
  1. /********************************************************************
  2. filename:   RTMPStream.cpp
  3. created:    2013-04-3
  4. author:     firehood
  5. purpose:    发送H264视频到RTMP Server,使用libRtmp库
  6. *********************************************************************/
  7. #include "RTMPStream.h"
  8. #include "SpsDecode.h"
  9. #ifdef WIN32
  10. #include <windows.h>
  11. #endif
  12. #ifdef WIN32
  13. #pragma comment(lib,"WS2_32.lib")
  14. #pragma comment(lib,"winmm.lib")
  15. #endif
  16. enum
  17. {
  18. FLV_CODECID_H264 = 7,
  19. };
  20. int InitSockets()
  21. {
  22. #ifdef WIN32
  23. WORD version;
  24. WSADATA wsaData;
  25. version = MAKEWORD(1, 1);
  26. return (WSAStartup(version, &wsaData) == 0);
  27. #else
  28. return TRUE;
  29. #endif
  30. }
  31. inline void CleanupSockets()
  32. {
  33. #ifdef WIN32
  34. WSACleanup();
  35. #endif
  36. }
  37. char * put_byte( char *output, uint8_t nVal )
  38. {
  39. output[0] = nVal;
  40. return output+1;
  41. }
  42. char * put_be16(char *output, uint16_t nVal )
  43. {
  44. output[1] = nVal & 0xff;
  45. output[0] = nVal >> 8;
  46. return output+2;
  47. }
  48. char * put_be24(char *output,uint32_t nVal )
  49. {
  50. output[2] = nVal & 0xff;
  51. output[1] = nVal >> 8;
  52. output[0] = nVal >> 16;
  53. return output+3;
  54. }
  55. char * put_be32(char *output, uint32_t nVal )
  56. {
  57. output[3] = nVal & 0xff;
  58. output[2] = nVal >> 8;
  59. output[1] = nVal >> 16;
  60. output[0] = nVal >> 24;
  61. return output+4;
  62. }
  63. char *  put_be64( char *output, uint64_t nVal )
  64. {
  65. output=put_be32( output, nVal >> 32 );
  66. output=put_be32( output, nVal );
  67. return output;
  68. }
  69. char * put_amf_string( char *c, const char *str )
  70. {
  71. uint16_t len = strlen( str );
  72. c=put_be16( c, len );
  73. memcpy(c,str,len);
  74. return c+len;
  75. }
  76. char * put_amf_double( char *c, double d )
  77. {
  78. *c++ = AMF_NUMBER;  /* type: Number */
  79. {
  80. unsigned char *ci, *co;
  81. ci = (unsigned char *)&d;
  82. co = (unsigned char *)c;
  83. co[0] = ci[7];
  84. co[1] = ci[6];
  85. co[2] = ci[5];
  86. co[3] = ci[4];
  87. co[4] = ci[3];
  88. co[5] = ci[2];
  89. co[6] = ci[1];
  90. co[7] = ci[0];
  91. }
  92. return c+8;
  93. }
  94. CRTMPStream::CRTMPStream(void):
  95. m_pRtmp(NULL),
  96. m_nFileBufSize(0),
  97. m_nCurPos(0)
  98. {
  99. m_pFileBuf = new unsigned char[FILEBUFSIZE];
  100. memset(m_pFileBuf,0,FILEBUFSIZE);
  101. InitSockets();
  102. m_pRtmp = RTMP_Alloc();
  103. RTMP_Init(m_pRtmp);
  104. }
  105. CRTMPStream::~CRTMPStream(void)
  106. {
  107. Close();
  108. WSACleanup();
  109. delete[] m_pFileBuf;
  110. }
  111. bool CRTMPStream::Connect(const char* url)
  112. {
  113. if(RTMP_SetupURL(m_pRtmp, (char*)url)<0)
  114. {
  115. return FALSE;
  116. }
  117. RTMP_EnableWrite(m_pRtmp);
  118. if(RTMP_Connect(m_pRtmp, NULL)<0)
  119. {
  120. return FALSE;
  121. }
  122. if(RTMP_ConnectStream(m_pRtmp,0)<0)
  123. {
  124. return FALSE;
  125. }
  126. return TRUE;
  127. }
  128. void CRTMPStream::Close()
  129. {
  130. if(m_pRtmp)
  131. {
  132. RTMP_Close(m_pRtmp);
  133. RTMP_Free(m_pRtmp);
  134. m_pRtmp = NULL;
  135. }
  136. }
  137. int CRTMPStream::SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp)
  138. {
  139. if(m_pRtmp == NULL)
  140. {
  141. return FALSE;
  142. }
  143. RTMPPacket packet;
  144. RTMPPacket_Reset(&packet);
  145. RTMPPacket_Alloc(&packet,size);
  146. packet.m_packetType = nPacketType;
  147. packet.m_nChannel = 0x04;
  148. packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
  149. packet.m_nTimeStamp = nTimestamp;
  150. packet.m_nInfoField2 = m_pRtmp->m_stream_id;
  151. packet.m_nBodySize = size;
  152. memcpy(packet.m_body,data,size);
  153. int nRet = RTMP_SendPacket(m_pRtmp,&packet,0);
  154. RTMPPacket_Free(&packet);
  155. return nRet;
  156. }
  157. bool CRTMPStream::SendMetadata(LPRTMPMetadata lpMetaData)
  158. {
  159. if(lpMetaData == NULL)
  160. {
  161. return false;
  162. }
  163. char body[1024] = {0};;
  164. char * p = (char *)body;
  165. p = put_byte(p, AMF_STRING );
  166. p = put_amf_string(p , "@setDataFrame" );
  167. p = put_byte( p, AMF_STRING );
  168. p = put_amf_string( p, "onMetaData" );
  169. p = put_byte(p, AMF_OBJECT );
  170. p = put_amf_string( p, "copyright" );
  171. p = put_byte(p, AMF_STRING );
  172. p = put_amf_string( p, "firehood" );
  173. p =put_amf_string( p, "width");
  174. p =put_amf_double( p, lpMetaData->nWidth);
  175. p =put_amf_string( p, "height");
  176. p =put_amf_double( p, lpMetaData->nHeight);
  177. p =put_amf_string( p, "framerate" );
  178. p =put_amf_double( p, lpMetaData->nFrameRate);
  179. p =put_amf_string( p, "videocodecid" );
  180. p =put_amf_double( p, FLV_CODECID_H264 );
  181. p =put_amf_string( p, "" );
  182. p =put_byte( p, AMF_OBJECT_END  );
  183. int index = p-body;
  184. SendPacket(RTMP_PACKET_TYPE_INFO,(unsigned char*)body,p-body,0);
  185. int i = 0;
  186. body[i++] = 0x17; // 1:keyframe  7:AVC
  187. body[i++] = 0x00; // AVC sequence header
  188. body[i++] = 0x00;
  189. body[i++] = 0x00;
  190. body[i++] = 0x00; // fill in 0;
  191. // AVCDecoderConfigurationRecord.
  192. body[i++] = 0x01; // configurationVersion
  193. body[i++] = lpMetaData->Sps[1]; // AVCProfileIndication
  194. body[i++] = lpMetaData->Sps[2]; // profile_compatibility
  195. body[i++] = lpMetaData->Sps[3]; // AVCLevelIndication
  196. body[i++] = 0xff; // lengthSi*usOne
  197. // sps nums
  198. body[i++] = 0xE1; //&0x1f
  199. // sps data length
  200. body[i++] = lpMetaData->nSpsLen>>8;
  201. body[i++] = lpMetaData->nSpsLen&0xff;
  202. // sps data
  203. memcpy(&body[i],lpMetaData->Sps,lpMetaData->nSpsLen);
  204. i= i+lpMetaData->nSpsLen;
  205. // pps nums
  206. body[i++] = 0x01; //&0x1f
  207. // pps data length
  208. body[i++] = lpMetaData->nPpsLen>>8;
  209. body[i++] = lpMetaData->nPpsLen&0xff;
  210. // sps data
  211. memcpy(&body[i],lpMetaData->Pps,lpMetaData->nPpsLen);
  212. i= i+lpMetaData->nPpsLen;
  213. return SendPacket(RTMP_PACKET_TYPE_VIDEO,(unsigned char*)body,i,0);
  214. }
  215. bool CRTMPStream::SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp)
  216. {
  217. if(data == NULL && size<11)
  218. {
  219. return false;
  220. }
  221. unsigned char *body = new unsigned char[size+9];
  222. int i = 0;
  223. if(bIsKeyFrame)
  224. {
  225. body[i++] = 0x17;// 1:Iframe  7:AVC
  226. }
  227. else
  228. {
  229. body[i++] = 0x27;// 2:Pframe  7:AVC
  230. }
  231. body[i++] = 0x01;// AVC NALU
  232. body[i++] = 0x00;
  233. body[i++] = 0x00;
  234. body[i++] = 0x00;
  235. // NALU size
  236. body[i++] = size>>24;
  237. body[i++] = size>>16;
  238. body[i++] = size>>8;
  239. body[i++] = size&0xff;;
  240. // NALU data
  241. memcpy(&body[i],data,size);
  242. bool bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp);
  243. delete[] body;
  244. return bRet;
  245. }
  246. bool CRTMPStream::SendH264File(const char *pFileName)
  247. {
  248. if(pFileName == NULL)
  249. {
  250. return FALSE;
  251. }
  252. FILE *fp = fopen(pFileName, "rb");
  253. if(!fp)
  254. {
  255. printf("ERROR:open file %s failed!",pFileName);
  256. }
  257. fseek(fp, 0, SEEK_SET);
  258. m_nFileBufSize = fread(m_pFileBuf, sizeof(unsigned char), FILEBUFSIZE, fp);
  259. if(m_nFileBufSize >= FILEBUFSIZE)
  260. {
  261. printf("warning : File size is larger than BUFSIZE\n");
  262. }
  263. fclose(fp);
  264. RTMPMetadata metaData;
  265. memset(&metaData,0,sizeof(RTMPMetadata));
  266. NaluUnit naluUnit;
  267. // 读取SPS帧
  268. ReadOneNaluFromBuf(naluUnit);
  269. metaData.nSpsLen = naluUnit.size;
  270. memcpy(metaData.Sps,naluUnit.data,naluUnit.size);
  271. // 读取PPS帧
  272. ReadOneNaluFromBuf(naluUnit);
  273. metaData.nPpsLen = naluUnit.size;
  274. memcpy(metaData.Pps,naluUnit.data,naluUnit.size);
  275. // 解码SPS,获取视频图像宽、高信息
  276. int width = 0,height = 0;
  277. h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);
  278. metaData.nWidth = width;
  279. metaData.nHeight = height;
  280. metaData.nFrameRate = 25;
  281. // 发送MetaData
  282. SendMetadata(&metaData);
  283. unsigned int tick = 0;
  284. while(ReadOneNaluFromBuf(naluUnit))
  285. {
  286. bool bKeyframe  = (naluUnit.type == 0x05) ? TRUE : FALSE;
  287. // 发送H264数据帧
  288. SendH264Packet(naluUnit.data,naluUnit.size,bKeyframe,tick);
  289. msleep(40);
  290. tick +=40;
  291. }
  292. return TRUE;
  293. }
  294. bool CRTMPStream::ReadOneNaluFromBuf(NaluUnit &nalu)
  295. {
  296. int i = m_nCurPos;
  297. while(i<m_nFileBufSize)
  298. {
  299. if(m_pFileBuf[i++] == 0x00 &&
  300. m_pFileBuf[i++] == 0x00 &&
  301. m_pFileBuf[i++] == 0x00 &&
  302. m_pFileBuf[i++] == 0x01
  303. )
  304. {
  305. int pos = i;
  306. while (pos<m_nFileBufSize)
  307. {
  308. if(m_pFileBuf[pos++] == 0x00 &&
  309. m_pFileBuf[pos++] == 0x00 &&
  310. m_pFileBuf[pos++] == 0x00 &&
  311. m_pFileBuf[pos++] == 0x01
  312. )
  313. {
  314. break;
  315. }
  316. }
  317. if(pos == nBufferSize)
  318. {
  319. nalu.size = pos-i;
  320. }
  321. else
  322. {
  323. nalu.size = (pos-4)-i;
  324. }
  325. nalu.type = m_pFileBuf[i]&0x1f;
  326. nalu.data = &m_pFileBuf[i];
  327. m_nCurPos = pos-4;
  328. return TRUE;
  329. }
  330. }
  331. return FALSE;
  332. }

附上SpsDecode.h文件:

  1. #include <stdio.h>
  2. #include <math.h>
  3. UINT Ue(BYTE *pBuff, UINT nLen, UINT &nStartBit)
  4. {
  5. //计算0bit的个数
  6. UINT nZeroNum = 0;
  7. while (nStartBit < nLen * 8)
  8. {
  9. if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) //&:按位与,%取余
  10. {
  11. break;
  12. }
  13. nZeroNum++;
  14. nStartBit++;
  15. }
  16. nStartBit ++;
  17. //计算结果
  18. DWORD dwRet = 0;
  19. for (UINT i=0; i<nZeroNum; i++)
  20. {
  21. dwRet <<= 1;
  22. if (pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
  23. {
  24. dwRet += 1;
  25. }
  26. nStartBit++;
  27. }
  28. return (1 << nZeroNum) - 1 + dwRet;
  29. }
  30. int Se(BYTE *pBuff, UINT nLen, UINT &nStartBit)
  31. {
  32. int UeVal=Ue(pBuff,nLen,nStartBit);
  33. double k=UeVal;
  34. int nValue=ceil(k/2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
  35. if (UeVal % 2==0)
  36. nValue=-nValue;
  37. return nValue;
  38. }
  39. DWORD u(UINT BitCount,BYTE * buf,UINT &nStartBit)
  40. {
  41. DWORD dwRet = 0;
  42. for (UINT i=0; i<BitCount; i++)
  43. {
  44. dwRet <<= 1;
  45. if (buf[nStartBit / 8] & (0x80 >> (nStartBit % 8)))
  46. {
  47. dwRet += 1;
  48. }
  49. nStartBit++;
  50. }
  51. return dwRet;
  52. }
  53. bool h264_decode_sps(BYTE * buf,unsigned int nLen,int &width,int &height)
  54. {
  55. UINT StartBit=0;
  56. int forbidden_zero_bit=u(1,buf,StartBit);
  57. int nal_ref_idc=u(2,buf,StartBit);
  58. int nal_unit_type=u(5,buf,StartBit);
  59. if(nal_unit_type==7)
  60. {
  61. int profile_idc=u(8,buf,StartBit);
  62. int constraint_set0_flag=u(1,buf,StartBit);//(buf[1] & 0x80)>>7;
  63. int constraint_set1_flag=u(1,buf,StartBit);//(buf[1] & 0x40)>>6;
  64. int constraint_set2_flag=u(1,buf,StartBit);//(buf[1] & 0x20)>>5;
  65. int constraint_set3_flag=u(1,buf,StartBit);//(buf[1] & 0x10)>>4;
  66. int reserved_zero_4bits=u(4,buf,StartBit);
  67. int level_idc=u(8,buf,StartBit);
  68. int seq_parameter_set_id=Ue(buf,nLen,StartBit);
  69. if( profile_idc == 100 || profile_idc == 110 ||
  70. profile_idc == 122 || profile_idc == 144 )
  71. {
  72. int chroma_format_idc=Ue(buf,nLen,StartBit);
  73. if( chroma_format_idc == 3 )
  74. int residual_colour_transform_flag=u(1,buf,StartBit);
  75. int bit_depth_luma_minus8=Ue(buf,nLen,StartBit);
  76. int bit_depth_chroma_minus8=Ue(buf,nLen,StartBit);
  77. int qpprime_y_zero_transform_bypass_flag=u(1,buf,StartBit);
  78. int seq_scaling_matrix_present_flag=u(1,buf,StartBit);
  79. int seq_scaling_list_present_flag[8];
  80. if( seq_scaling_matrix_present_flag )
  81. {
  82. for( int i = 0; i < 8; i++ ) {
  83. seq_scaling_list_present_flag[i]=u(1,buf,StartBit);
  84. }
  85. }
  86. }
  87. int log2_max_frame_num_minus4=Ue(buf,nLen,StartBit);
  88. int pic_order_cnt_type=Ue(buf,nLen,StartBit);
  89. if( pic_order_cnt_type == 0 )
  90. int log2_max_pic_order_cnt_lsb_minus4=Ue(buf,nLen,StartBit);
  91. else if( pic_order_cnt_type == 1 )
  92. {
  93. int delta_pic_order_always_zero_flag=u(1,buf,StartBit);
  94. int offset_for_non_ref_pic=Se(buf,nLen,StartBit);
  95. int offset_for_top_to_bottom_field=Se(buf,nLen,StartBit);
  96. int num_ref_frames_in_pic_order_cnt_cycle=Ue(buf,nLen,StartBit);
  97. int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle];
  98. for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
  99. offset_for_ref_frame[i]=Se(buf,nLen,StartBit);
  100. delete [] offset_for_ref_frame;
  101. }
  102. int num_ref_frames=Ue(buf,nLen,StartBit);
  103. int gaps_in_frame_num_value_allowed_flag=u(1,buf,StartBit);
  104. int pic_width_in_mbs_minus1=Ue(buf,nLen,StartBit);
  105. int pic_height_in_map_units_minus1=Ue(buf,nLen,StartBit);
  106. width=(pic_width_in_mbs_minus1+1)*16;
  107. height=(pic_height_in_map_units_minus1+1)*16;
  108. return true;
  109. }
  110. else
  111. return false;
  112. }