1. 程式人生 > >H264視訊通過RTMP直播

H264視訊通過RTMP直播

       前面的文章中提到了通過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播放效果如下:

最後附上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; // lengthSizeMinusOne

  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. }