视频RTMP推流实践
对应RTMP推流,业界有很多开源方案。如使用FFMPEG推流,librtmp(rtmp-dump),gstream推流。由于ffmpeg和gstreamer比较庞大,仅仅用来推流,有大炮打蚊子之嫌。针对客户端特别是瘦客户端,使用librtmp(rtmp-dump)方案更加精简,更加高效。
本方案基本思路:
- 下载并编译librtmp。
下载地址: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推流接口。
基本使用步骤:
- 定义一个Wrapper_RtmpLib对象test
- Test.open(),与服务器建立rtmp信令相关连接
- int SendData(char * data,int dataLength, unsigned int timeStamp,int debug = -1);发送RTMP数据
注意data,必须是一个完整的NAL单元。所以应用程序调该接口前必须解析出NAL单元。
下面是一个h264裸文件推送RTMP过程。
#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;}elseprintf("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 01iCurrentStartLen = 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拷贝到bufferNALLen += 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,拷贝到bufferNALLen += (&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,拷贝到bufferNALLen += 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
使用该方案注意:
- SendData 必须是一个完整的NAL单元。如果是文件需要解析或网络流必须解析出NAL单元。
- 时间戳采用间隔时间。即时间戳按每帧时间间隔递增,可能因为网络抖动或者1000/帧率不是帧率会存在累计误差。该demo因为不存在音视频同步,时间戳影响不大。
3.如果是云主机,在云主机内不能推公网IP,而要推内网IP 192.168.1.226,客户端访问需要外网IP。
客户端播放效果如下:。
更多更详细资源请关注公众号:AV_Chat
视频RTMP推流实践相关推荐
- rtmp断线重连_rtsp转rtmp rtsp2rtmp 同时16路视频 rtmp推流器 支持ipc dvr nvr
本公司提供各种音视频方案定制,详情请咨询 283 邮件:zyy6569@163.com : (咨询特价) 1.拉取网络摄像机RTSP的码流,通过rtmp推送到指定的rtmp服务器,支持h264视频, ...
- 【解决方案】TSINGSEE青犀视频RTMP推流网关+公有云直播远程监控系统,让“小饭桌”变成“放心桌”
近几年来,学校附近的小饭桌如雨后春笋般出现,选择一个好的小饭桌,让各个家长花费了很大的精力.为此,很多小饭桌为了提高学生数量,在厨房和学习地方安装监控,并将视频公开给家长. 安装监控不仅解除了学生家长 ...
- 【解决方案】“小饭桌”变成“放心桌”,TSINGSEE青犀视频RTMP推流网关+公有云直播远程监控
近几年来,学校附近的小饭桌如雨后春笋般出现,选择一个好的小饭桌,让各个家长花费了很大的精力.为此,很多小饭桌为了提高学生数量,在厨房和学习地方安装监控,并将视频公开给家长. 安装监控不仅解除了学生家长 ...
- TSINGSEE青犀视频RTMP推流摄像头焦距与监控距离存在什么关系?
很多项目团队都因为推流的需求,咨询了解TSINGSEE青犀视频和海康合作研发的RTMP摄像头.该RTMP摄像头支持语音对讲.主动推流,虽然摄像头没有自行存储的功能,但是可以通过插入TF内存卡,对视频进 ...
- linux人脸识别视频推流,RTMP推流协议视频智能分析/人脸识别/直播点播平台EasyDSS接口调用注意事项介绍...
TSINGSEE青犀视频目前推出了前端支持不同协议设备接入的视频智能分析平台,包括RTSP协议的EasyNVR.GB28181协议的EasyGBS,RTMP推流协议的EasyDSS,还有能够进行人脸识 ...
- iOS之一个超赞的视频直播、第三方库,直播看这个就够了,支持RTMP推流,美颜直播
GitHub地址: 点击打开链接 包含一下功能: 1, 提供IOS苹果手机的RTMP推流: 填写RTMP服务地址,直接就可以进行推流. 2,美颜直播 美不美都能装的直播,IOS OPENGL美艳加速, ...
- 基于librtmp的安卓小项目:投屏摄像头视频:推流rtmp到服务器上并显示在其它设备上(比如电脑或者其它直播平台)
首先这个项目并未实现音频的传输,后面有时间再实现音频的传输后更新博文.这里如果是自己部署流媒体服务器,可以参考搭建nginx的相关博文,这里需要注意的是如果是搭建在linux系统下面,那么网络最好选用 ...
- 安卓rtmp推流app_视频直播app开发只需三步就可完成
不知道从什么时候开始,主播这个职业已经成为了一种大众职业,不在偏向于颜值,才艺也是重要的考量.从直播.玩游戏.唱歌.吃饭睡觉都会有主播在进行日常生活的直播同步,不少的用户也愿意去关注. 直播App的火 ...
- 新版RTMP推流协议视频直播点播平台EasyDSS在进行视频直播/录像回看时如何创建视频录像计划?
EasyDSS是TSINGSEE青犀视频开发的可支持接入RTMP推流摄像头的视频流媒体平台,新版EasyDSS互联网直播点播平台支持创建录像计划,用户可以设定周一至周日中,某天某个时间段内开启录像,其 ...
最新文章
- HDU.3177Crixalis's Equipment(贪心)
- C# Task的用法
- 【BZOJ2245】[SDOI2011]工作安排 拆边费用流
- 开源分布式中间件 DBLE Schema.xml 配置解析
- 【H2 Database】shell
- php的修改数据库语句怎么写,php的数据库修改语句是什么
- 各视频、各音频之间格式任意玩弄(图文详解)
- 4 指针运算_C++用指针访问数组元素(学习笔记:第6章 08)
- ThinkPHP5模型操作中的自动时间戳总结
- 无代码编程时代下,程序员要失业了?
- 第 11 章 直接内存
- oracle registers,【案例】Oracle RAC强制删除node节点过程的详细笔记
- 记忆化搜索(DFS+DP) URAL 1223 Chernobyl’ Eagle on a Roof
- Java面试题之 static执行顺序
- string.join用法
- 软件测试员200题(练习)
- 《金字塔原理》要点汇总
- 软件分享:Everthing
- 【转】将安全证书导入到java的cacerts证书库
- 国外的大龄程序员在干什么?