首页 » 软件优化 » 音视频开发:视频RTMP推流实践(单元音视频是一个老舅视频)

音视频开发:视频RTMP推流实践(单元音视频是一个老舅视频)

落叶飘零 2024-11-24 03:23:33 0

扫一扫用手机浏览

文章目录 [+]

本方案基本思路:

下载并编译librtmp。

下载地址:http://rtmpdump.mplayerhq.hu/download/

编译成功后产生一个librtmp.so 库

音视频开发:视频RTMP推流实践(单元音视频是一个老舅视频) 软件优化
(图片来自网络侵删)

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对象testTest.open(),与服务器建立rtmp信令相关连接int SendData(char data,int dataLength, unsigned int timeStamp,int debug = -1);发送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,charargv[]){ 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

使用该方案注意:

SendData 必须是一个完整的NAL单元。
如果是文件需要解析或网络流必须解析出NAL单元。
时间戳采用间隔时间。
即时间戳按每帧时间间隔递增,可能因为网络抖动或者1000/帧率不是帧率会存在累计误差。
该demo因为不存在音视频同步,时间戳影响不大。

3.如果是云主机,在云主机内不能推公网IP,而要推内网IP 192.168.1.226,客户端访问需要外网IP。

客户端播放效果如下:。

标签:

相关文章