对应RTMP推流,业界有很多开源方案。如使用FFMPEG推流,librtmp(rtmp-dump),gstream推流。由于ffmpeg和gstreamer比较庞大,仅仅用来推流,有大炮打蚊子之嫌。针对客户端特别是瘦客户端,使用librtmp(rtmp-dump)方案更加精简,更加高效。
本方案基本思路:
- 下载并编译librtmp。
下载地址:http://rtmpdump.mplayerhq.hu/download/
编译成功后产生一个librtmp.so 库
2.调用librtmp,封装一个视频层Wrapper_RtmpLib.cpp,该类定义如下:
代码语言:javascript复制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推流接口。
基本使用步骤:
- 定义一个Wrapper_RtmpLib对象test
- Test.open(),与服务器建立rtmp信令相关连接
- int SendData(char * data,int dataLength, unsigned int timeStamp,int debug = -1);发送RTMP数据
注意data,必须是一个完整的NAL单元。所以应用程序调该接口前必须解析出NAL单元。
下面是一个h264裸文件推送RTMP过程。
代码语言:javascript复制#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 1111n",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 failedn");
return 0;
}
if (test.IsConnect() < 0)
{
printf("connect is failedn");
return 0;
}
else
printf("connect is okn");
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 failedn");
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 start1n");
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 errorn");
}
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 failedn");
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 start2n");
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 errorn");
}
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 failedn");
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 streamn");
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
使用该方案注意:
- SendData 必须是一个完整的NAL单元。如果是文件需要解析或网络流必须解析出NAL单元。
- 时间戳采用间隔时间。即时间戳按每帧时间间隔递增,可能因为网络抖动或者1000/帧率不是帧率会存在累计误差。该demo因为不存在音视频同步,时间戳影响不大。
3.如果是云主机,在云主机内不能推公网IP,而要推内网IP 192.168.1.226,客户端访问需要外网IP。
客户端播放效果如下:。