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提供的接口如下。
最后附上RTMPStream完整的代码:
/******************************************************************** filename: RTMPStream.cppcreated: 2013-04-3author: firehood purpose: 发送H264视频到RTMP Server,使用libRtmp库*********************************************************************/ #include "RTMPStream.h"#include "SpsDecode.h"#ifdef WIN32 #include <windows.h>#endifenum{FLV_CODECID_H264 = 7,};int InitSockets() { #ifdef WIN32 WORD version; WSADATA wsaData; version = MAKEWORD(1, 1); return (WSAStartup(version, &wsaData) == 0); #else return TRUE; #endif } inline void CleanupSockets() { #ifdef WIN32 WSACleanup(); #endif } char * put_byte( char *output, uint8_t nVal ) { output[0] = nVal; return output+1; } char * put_be16(char *output, uint16_t nVal ) { output[1] = nVal & 0xff; output[0] = nVal >> 8; return output+2; } char * put_be24(char *output,uint32_t nVal ) { output[2] = nVal & 0xff; output[1] = nVal >> 8; output[0] = nVal >> 16; return output+3; } char * put_be32(char *output, uint32_t nVal ) { output[3] = nVal & 0xff; output[2] = nVal >> 8; output[1] = nVal >> 16; output[0] = nVal >> 24; return output+4; } char * put_be64( char *output, uint64_t nVal ) { output=put_be32( output, nVal >> 32 ); output=put_be32( output, nVal ); return output; } char * put_amf_string( char *c, const char *str ) { uint16_t len = strlen( str ); c=put_be16( c, len ); memcpy(c,str,len); return c+len; } char * put_amf_double( char *c, double d ) { *c++ = AMF_NUMBER; /* type: Number */ { unsigned char *ci, *co; ci = (unsigned char *)&d; co = (unsigned char *)c; co[0] = ci[7]; co[1] = ci[6]; co[2] = ci[5]; co[3] = ci[4]; co[4] = ci[3]; co[5] = ci[2]; co[6] = ci[1]; co[7] = ci[0]; } return c+8; }CRTMPStream::CRTMPStream(void):m_pRtmp(NULL),m_nFileBufSize(0),m_nCurPos(0){m_pFileBuf = new unsigned char[FILEBUFSIZE];memset(m_pFileBuf,0,FILEBUFSIZE);InitSockets();m_pRtmp = RTMP_Alloc(); RTMP_Init(m_pRtmp); }CRTMPStream::~CRTMPStream(void){Close();WSACleanup(); delete[] m_pFileBuf;}bool CRTMPStream::Connect(const char* url){if(RTMP_SetupURL(m_pRtmp, (char*)url)<0){return FALSE;}RTMP_EnableWrite(m_pRtmp);if(RTMP_Connect(m_pRtmp, NULL)<0){return FALSE;}if(RTMP_ConnectStream(m_pRtmp,0)<0){return FALSE;}return TRUE;}void CRTMPStream::Close(){if(m_pRtmp){RTMP_Close(m_pRtmp);RTMP_Free(m_pRtmp);m_pRtmp = NULL;}}int CRTMPStream::SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp){if(m_pRtmp == NULL){return FALSE;}RTMPPacket packet;RTMPPacket_Reset(&packet);RTMPPacket_Alloc(&packet,size);packet.m_packetType = nPacketType;packet.m_nChannel = 0x04; packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_nTimeStamp = nTimestamp; packet.m_nInfoField2 = m_pRtmp->m_stream_id;packet.m_nBodySize = size;memcpy(packet.m_body,data,size);int nRet = RTMP_SendPacket(m_pRtmp,&packet,0);RTMPPacket_Free(&packet);return nRet;}bool CRTMPStream::SendMetadata(LPRTMPMetadata lpMetaData){if(lpMetaData == NULL){return false;}char body[1024] = {0};; 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, "firehood" ); p =put_amf_string( p, "width");p =put_amf_double( p, lpMetaData->nWidth);p =put_amf_string( p, "height");p =put_amf_double( p, lpMetaData->nHeight);p =put_amf_string( p, "framerate" );p =put_amf_double( p, lpMetaData->nFrameRate); p =put_amf_string( p, "videocodecid" );p =put_amf_double( p, FLV_CODECID_H264 );p =put_amf_string( p, "" );p =put_byte( p, AMF_OBJECT_END );int index = p-body;SendPacket(RTMP_PACKET_TYPE_INFO,(unsigned char*)body,p-body,0);int i = 0;body[i++] = 0x17; // 1:keyframe 7:AVCbody[i++] = 0x00; // AVC sequence headerbody[i++] = 0x00;body[i++] = 0x00;body[i++] = 0x00; // fill in 0;// AVCDecoderConfigurationRecord.body[i++] = 0x01; // configurationVersionbody[i++] = lpMetaData->Sps[1]; // AVCProfileIndicationbody[i++] = lpMetaData->Sps[2]; // profile_compatibilitybody[i++] = lpMetaData->Sps[3]; // AVCLevelIndication body[i++] = 0xff; // lengthSizeMinusOne // sps numsbody[i++] = 0xE1; //&0x1f// sps data lengthbody[i++] = lpMetaData->nSpsLen>>8;body[i++] = lpMetaData->nSpsLen&0xff;// sps datamemcpy(&body[i],lpMetaData->Sps,lpMetaData->nSpsLen);i= i+lpMetaData->nSpsLen;// pps numsbody[i++] = 0x01; //&0x1f// pps data length body[i++] = lpMetaData->nPpsLen>>8;body[i++] = lpMetaData->nPpsLen&0xff;// sps datamemcpy(&body[i],lpMetaData->Pps,lpMetaData->nPpsLen);i= i+lpMetaData->nPpsLen;return SendPacket(RTMP_PACKET_TYPE_VIDEO,(unsigned char*)body,i,0);}bool CRTMPStream::SendH264Packet(unsigned char *data,unsigned int size,bool bIsKeyFrame,unsigned int nTimeStamp){if(data == NULL && size<11){return false;}unsigned char *body = new unsigned char[size+9];int i = 0;if(bIsKeyFrame){body[i++] = 0x17;// 1:Iframe 7:AVC}else{body[i++] = 0x27;// 2:Pframe 7:AVC}body[i++] = 0x01;// AVC NALUbody[i++] = 0x00;body[i++] = 0x00;body[i++] = 0x00;// NALU sizebody[i++] = size>>24;body[i++] = size>>16;body[i++] = size>>8;body[i++] = size&0xff;;// NALU datamemcpy(&body[i],data,size);bool bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp);delete[] body;return bRet;}bool CRTMPStream::SendH264File(const char *pFileName){if(pFileName == NULL){return FALSE;}FILE *fp = fopen(pFileName, "rb"); if(!fp) { printf("ERROR:open file %s failed!",pFileName);} fseek(fp, 0, SEEK_SET);m_nFileBufSize = fread(m_pFileBuf, sizeof(unsigned char), FILEBUFSIZE, fp);if(m_nFileBufSize >= FILEBUFSIZE){printf("warning : File size is larger than BUFSIZE\n");}fclose(fp); RTMPMetadata metaData;memset(&metaData,0,sizeof(RTMPMetadata)); NaluUnit naluUnit;// 读取SPS帧 ReadOneNaluFromBuf(naluUnit);metaData.nSpsLen = naluUnit.size;memcpy(metaData.Sps,naluUnit.data,naluUnit.size);// 读取PPS帧ReadOneNaluFromBuf(naluUnit);metaData.nPpsLen = naluUnit.size;memcpy(metaData.Pps,naluUnit.data,naluUnit.size);// 解码SPS,获取视频图像宽、高信息int width = 0,height = 0; h264_decode_sps(metaData.Sps,metaData.nSpsLen,width,height);metaData.nWidth = width; metaData.nHeight = height;metaData.nFrameRate = 25; // 发送MetaData SendMetadata(&metaData);unsigned int tick = 0;while(ReadOneNaluFromBuf(naluUnit)){bool bKeyframe = (naluUnit.type == 0x05) ? TRUE : FALSE;// 发送H264数据帧SendH264Packet(naluUnit.data,naluUnit.size,bKeyframe,tick);msleep(40);tick +=40;}return TRUE;}bool CRTMPStream::ReadOneNaluFromBuf(NaluUnit &nalu){int i = m_nCurPos;while(i<m_nFileBufSize-4){if(m_pFileBuf[i++] == 0x00 &&m_pFileBuf[i++] == 0x00 &&m_pFileBuf[i++] == 0x00 &&m_pFileBuf[i++] == 0x01){int pos = i;while (pos<m_nFileBufSize-4){if(m_pFileBuf[pos++] == 0x00 &&m_pFileBuf[pos++] == 0x00 &&m_pFileBuf[pos++] == 0x00 &&m_pFileBuf[pos++] == 0x01){break;}}nalu.type = m_pFileBuf[i]&0x1f;nalu.size = (pos-4)-i;nalu.data = &m_pFileBuf[i];m_nCurPos = pos-4;return TRUE;}}return FALSE;}