对应RTMP推流,业界有很多开源方案。如使用FFMPEG推流,librtmp(rtmp-dump),gstream推流。由于ffmpeg和gstreamer比较庞大,仅仅用来推流,有大炮打蚊子之嫌。针对客户端特别是瘦客户端,使用librtmp(rtmp-dump)方案更加精简,更加高效。
本方案基本思路:
下载地址:http://rtmpdump.mplayerhq.hu/download/
编译成功后产生一个librtmp.so 库
2.调用librtmp,封装一个视频层Wrapper_RtmpLib.cpp,该类定义如下:
class Wrapper_RtmpLib{public: Wrapper_RtmpLib(char * url); ~Wrapper_RtmpLib(); int Open(); int SendData(char * data,int dataLength, unsigned int timeStamp,int debug = -1); int IsConnect(); int Close();private: int InitSockets(); void CleanupSockets(); int pushSPSPPS(char *sps, int spsLen, char *pps, int ppsLen, int m_stream_id,unsigned int timeStamp); int pushVideoData(char *data, int dataLen, bool keyFrame, int m_stream_id,unsigned int timeStamp); int GetStartPrixLen(char *Pack, int offest); char * rtmpUrl = NULL; RTMP * m_pRtmp = NULL; NALU * CopyNALU(NALU * src); void FreeNALU(NALU * nalu);};
Wrapper_RtmpLib对外提供RTMP推流接口。
基本使用步骤:
注意data,必须是一个完整的NAL单元。所以应用程序调该接口前必须解析出NAL单元。
下面是一个h264裸文件推送RTMP过程。
粉丝福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~
#include <cstdio>#include"Wrapper_RtmpLib.hpp"#include <unistd.h>#include<string.h>#include <signal.h>#include<time.h>#define LEN_R 1400//检测启动码,并获取启动码的长度int GetStartCode(char *Pack, int offest){ int iStartPrexLen = 0; if (Pack[offest] == '\x00' && Pack[offest + 1] == '\x00'&&Pack[offest + 2] == '\x00'&&Pack[offest + 3] == '\x01') { iStartPrexLen = 4; return iStartPrexLen; } else if (Pack[offest] == '\x00' && Pack[offest + 1] == '\x00'&&Pack[offest + 2] == '\x01') { iStartPrexLen = 3; return iStartPrexLen; } else { return iStartPrexLen; }}#include <time.h>void delaytime(int ms){// return; struct timespec tvUec; clock_gettime(CLOCK_MONOTONIC, &tvUec); long long pretime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000; long long nowtime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000; while (1) { clock_gettime(CLOCK_MONOTONIC, &tvUec); nowtime = tvUec.tv_nsec / 1000000 + tvUec.tv_sec * 1000; if (nowtime - pretime > ms-10) //程序自身耗时,预估耗时10ms。实际网络流不要延时,仅供测试 { return; } }}void help(char *p){ printf("Use:"); printf("%s h264_File RTMP_URL FRate 1111\n",p);}char NALBuff[1080 * 1920 * 8]={0};int NALLen = 0;int lastPos = 0;int pretime = 0;int NALCount = 0;int main(int argc,char*argv[]){ if(argc<4) { help(argv[0]); return 1; } signal(SIGPIPE, SIG_IGN); Wrapper_RtmpLib test(argv[2]); if (test.Open() < 0) { printf("open is failed\n"); return 0; } if (test.IsConnect() < 0) { printf("connect is failed\n"); return 0; } else printf("connect is ok\n"); char Pack[1500] = { 0 }; int ret = 0; char *pStart = NULL; char *pEnd = NULL; char *pNALbuff = NALBuff; unsigned int timestamp = 0; int ioffset = 1000 /atoi(argv[3]); char *pPack = Pack; int iCurrentStartLen = 0; int iPreStartLen = 0; FILE *fp = NULL; fp = fopen(argv[1], "rb+"); if (fp == NULL) { printf("open file is failed\n"); return -1; } while (1) { pStart = NULL; pEnd = NULL; ret = fread(Pack, LEN_R, 1, fp); if (ret == 1) { //如果头4个字节恰好为 00 00 00 01 或者00 00 01 iCurrentStartLen = GetStartCode(Pack, 0); if(iCurrentStartLen>0) { iPreStartLen = iCurrentStartLen; pStart=&Pack[0]; pEnd = &Pack[0]; for (int i = 2; i < LEN_R; i++) { iCurrentStartLen = GetStartCode(Pack, i); if (iCurrentStartLen > 0) { printf("##find nal start1\n"); pEnd = &Pack[i]; memmove(NALBuff+ NALLen, pStart, pEnd - pStart);//分离NAL拷贝到buffer NALLen += pEnd - pStart; NALCount++; } if (NALLen != 0) { int StartCodeLen = GetStartCode(NALBuff, 0); if (StartCodeLen <= 0) { printf("NAL buffer data error\n"); } if ((NALBuff[StartCodeLen] & 0x1F) == 7) //sps需要等pps一起发 { if (NALCount == 1) { iPreStartLen = iCurrentStartLen; i++; pStart = pEnd; continue; } } else if (( NALBuff[StartCodeLen] & 0x1F)== 5|| (NALBuff[StartCodeLen] & 0x1F) == 1) { timestamp = timestamp + ioffset; i++; } else { //不是我所关注的NAL类型,可以不往下送 NALLen = 0; NALCount = 0; memset(NALBuff, 0, sizeof(NALBuff)); iPreStartLen = iCurrentStartLen; i++; pStart = pEnd; continue; } ret =test.SendData(NALBuff, NALLen, timestamp, -1); if (ret < 0) printf("send is failed\n"); NALLen = 0; NALCount = 0; memset(NALBuff, 0, sizeof(NALBuff)); iPreStartLen = iCurrentStartLen; delaytime(ioffset); } pStart = pEnd; } //一个包中遗留半个NAL单元,找不到下一个头 //剩余的不完整NAL单元拷贝到临时buffer,后面凑齐一个NAL单元再发 memmove(NALBuff + NALLen, pStart, (&Pack[LEN_R - 1] - pStart) + 1);//sps pps idr non-idr,拷贝到buffer NALLen += (&Pack[LEN_R - 1] - pStart + 1); } else //如果头4个字节不是启动码 { for (int i = 1; i < LEN_R; i++) //必须从2开始,因为可能存在00 00 01或00 00 00 01相邻出现 { // pStart = &Pack[0]; iCurrentStartLen = GetStartCode(Pack, i); if (iCurrentStartLen > 0) { printf("##find nal start2\n"); pEnd = &Pack[i]; if (pStart == NULL) pStart = &Pack[0]; memmove(NALBuff + NALLen, pStart, pEnd - pStart);//sps pps idr non-idr,拷贝到buffer NALLen += pEnd - pStart; NALCount++; if (NALLen != 0) { int StartCodeLen = GetStartCode(NALBuff, 0); if (StartCodeLen <= 0) { printf("NAL buffer data error\n"); } if ((NALBuff[StartCodeLen] & 0x1F) == 7) //sps需要等pps一起发 { if (NALCount == 1) { iPreStartLen = iCurrentStartLen; i++; pStart = pEnd; continue; } } else if ((NALBuff[StartCodeLen] & 0x1F) == 5 || (NALBuff[StartCodeLen] & 0x1F) == 1) { timestamp = timestamp + ioffset; } else { //不是我所关注的NAL类型,可以不往下送 NALLen = 0; NALCount = 0; memset(NALBuff, 0, sizeof(NALBuff)); iPreStartLen = iCurrentStartLen; i++; pStart = pEnd; continue; } ret = test.SendData(NALBuff, NALLen, timestamp, -1); if (ret < 0) printf("send data is failed\n"); NALLen = 0; NALCount = 0; memset(NALBuff, 0, sizeof(NALBuff)); delaytime(ioffset); } i++; iPreStartLen = iCurrentStartLen; } pStart = pEnd; } //整个包都不足一个NAL单元 if (pStart == pEnd) { if (pStart == NULL) { pStart = &Pack[0]; } pEnd = &Pack[LEN_R - 1]; memmove(NALBuff + NALLen, pStart, pEnd - pStart + 1); // //lastPos = NALLen; NALLen += (pEnd - pStart + 1); } } } else { printf("read is failed endof stream\n"); break; } } getchar(); printf("hello from rtmp!\n"); return 0;}
基本思路如下:
读文件----解析NAL单元---利用 SendData发送一个完成的NAL单元完成推流
编译main.cpp Wrapper_RtmpLib.cpp 并链接librtmp.so生成可执行文件h2642rtmp.
运行可执行程序推流
./h264tortmp avc.h264 rtmp://192.168.1.226:8085/live/1830562240700540100 25
使用该方案注意:
3.如果是云主机,在云主机内不能推公网IP,而要推内网IP 192.168.1.226,客户端访问需要外网IP。
客户端播放效果如下:。