使用srs_librtmp实现RTMP推流

1、背景 

    由于项目有需求在一个现有的产品上增加RTMP推流的功能,目前只推视频流。

2、方案选择

    由于是在现有的产品上新增功能,那么为了减少总的成本,故选择只动应用软件的来实现需求。

    现有的产品中的第三方库比较有限,连个ffmpeg都没,所以要选择可以直接集成代码进来的第三方库,最后选中了srs_librtmp。虽然它已经停止维护了,但是主要功能没问题,使用简单,且可以直接集成代码。

  

3、实现代码

step1:去github上面先把源码下下来。

GitHub - ossrs/srs-librtmp at master

step2:把对应的代码文件集成到项目里。

    这里只需要src/srs目录下的srs_librtmp.h和srs_librtmp.cpp就行了,如下图

step3:封装成工具类

封装过程中使用了另一个第三方库POCO,这个库只要用来实现线程,不想要的话可以直接改掉。

RTMPPusher.h

//
// Created by zhengqiuxu on 2023/8/5.
//#ifndef VIS_G3_SOFTWARE_RTMPPUSHER_H
#define VIS_G3_SOFTWARE_RTMPPUSHER_H#include <Poco/Runnable.h>
#include <Poco/Thread.h>
#include <mutex>
#include "srs_librtmp.h"class RTMPPusher : public Poco::Runnable{
public:// h264 nalustruct NaluHead{unsigned type : 5;unsigned nal_ref_idc : 2;unsigned forbidden_zero_bit : 1;};RTMPPusher();/*** 初始化** @param url : 推流地址* @return 0:成功  其他:失败*/int init(const std::string url);/*** 启动线程*/void start();/*** 设置一帧H264数据帧** @param h264Data : 一帧H264数据的指针* @param dataLen : 一帧H264数据的指针的长度*/void setH264Data(uint8_t *h264Data, const int dataLen);/*** 停止推流*/void stop();void run() override;int getCameraId() const;void setCameraId(int cameraId);const std::string &getRtmpUrl() const;void setRtmpUrl(const std::string &rtmpUrl);bool isInited() const;void setInited(bool inited);private:/*** 推送一帧H264数据帧(真实推送到RTMP)** @param h264Data : 一帧H264数据的指针* @param dataLen : 一帧H264数据的指针的长度*/void pushH264Data(char *h264Data, const int dataLen);/* 对应的相机ID */int cameraId = -1;/* RTMP的推送地址 */std::string rtmpUrl;/* 是不是需要停止推送 */bool isNeedStop = true;/* 是否初始化了 */bool inited = false;/* 是否可以发送了?需要第一帧是sps才行 */bool canSen = false;Poco::Thread pushThread;srs_rtmp_t rtmp;uint64_t pts = 0;uint64_t dts = 0;const int MAX_H264CACHE_SIZE = 1024*1024*4;/* 缓冲起来的h264数据 */char *h264DataCache;/* 缓冲起来的h264数据的长度 */int curH264DataLen = 0;/* 读写H264数据的互斥锁 */std::mutex h264DataLock;};#endif //VIS_G3_SOFTWARE_RTMPPUSHER_H

RTMPPusher.cpp

//
// Created by zhengqiuxu on 2023/8/5.
//#include "RTMPPusher.h"
#include <cstring>
#include <unistd.h>RTMPPusher::RTMPPusher() {}
/*** 初始化** @param url : 推流地址* @return 0:成功  其他:失败*/
int RTMPPusher::init(const std::string url) {int ret = -1;rtmpUrl = url;inited = true;h264DataCache = (char *)malloc(MAX_H264CACHE_SIZE);ret = 0;return ret;}
/*** 推送一帧H264数据帧(真实推送到RTMP)** @param h264Data : 一帧H264数据的指针* @param dataLen : 一帧H264数据的指针的长度*/
void RTMPPusher::pushH264Data(char *h264Data, const int dataLen) {try {printf("RTMPPusher::pushH264Data  size=%d \n",dataLen);/* 推流到RTMP */pts += 40;  /* 如果是B帧的话,PTS应该等于离它最近的P帧或者I帧的的PTS  一般都是选择填上一帧数据的PTS */dts = pts;int ret = srs_h264_write_raw_frames(rtmp, h264Data, dataLen, dts, pts);if (ret != 0) {if (srs_h264_is_dvbsp_error(ret)) {srs_human_trace("ignore drop video error, code=%d", ret);} else if (srs_h264_is_duplicated_sps_error(ret)) {srs_human_trace("ignore duplicated sps, code=%d", ret);} else if (srs_h264_is_duplicated_pps_error(ret)) {srs_human_trace("ignore duplicated pps, code=%d", ret);} else {srs_human_trace("send h264 raw data failed. ret=%d", ret);}}} catch (...) {printf("push H264Data to %s failed! %s \n",rtmpUrl.c_str(),strerror(errno));}}
/*** 停止推流*/
void RTMPPusher::stop() {isNeedStop = true;srs_human_trace("h264 raw data completed");srs_rtmp_destroy(rtmp);free(h264DataCache);inited = false;
}
/*** 启动线程*/
void RTMPPusher::start() {pushThread.start(*this);
}void RTMPPusher::run() {std::string pthreadName = "RTMPPusher_";pthreadName.append(rtmpUrl);pthread_setname_np(pthread_self(), pthreadName.c_str());isNeedStop = false;/* 创建一个RTMP客户端对象 */rtmp = srs_rtmp_create(rtmpUrl.c_str());/* 开始跟RTMP服务器握手 */if (srs_rtmp_handshake(rtmp) != 0) {srs_human_trace("simple handshake failed.");}else{srs_human_trace("simple handshake success");/* 连接RTMP流 */if (srs_rtmp_connect_app(rtmp) != 0) {srs_human_trace("connect vhost/app failed.");}else{srs_human_trace("connect vhost/app success");/* 看看RTMP流是否可以推流 */if (srs_rtmp_publish_stream(rtmp) != 0) {srs_human_trace("publish stream failed.");}else{srs_human_trace("publish stream success");canSen = false;/* 循环从内存里读出H264并推到RTMP服务器 */while (!isNeedStop){h264DataLock.lock();if(curH264DataLen > 0){if(canSen){pushH264Data(h264DataCache,curH264DataLen);curH264DataLen = 0;}else{/* 拿出NALU头用来后面判断NALU类型 */struct NaluHead curNaluHead = *(struct NaluHead *)(h264DataCache+4);/* 从SPSPPS开始推,有些服务器做的不好,不是从SPSPPS开始推的话会报错 */if(curNaluHead.type == 7){canSen = true;pushH264Data(h264DataCache,curH264DataLen);curH264DataLen = 0;}}}h264DataLock.unlock();usleep(10000);}}}}}
/*** 设置一帧H264数据帧** @param h264Data : 一帧H264数据的指针* @param dataLen : 一帧H264数据的指针的长度*/
void RTMPPusher::setH264Data(uint8_t *h264Data, const int dataLen) {if(dataLen > 0){h264DataLock.lock();memcpy(h264DataCache,h264Data,dataLen);curH264DataLen = dataLen;h264DataLock.unlock();}}int RTMPPusher::getCameraId() const {return cameraId;
}void RTMPPusher::setCameraId(int cameraId) {RTMPPusher::cameraId = cameraId;
}const std::string &RTMPPusher::getRtmpUrl() const {return rtmpUrl;
}void RTMPPusher::setRtmpUrl(const std::string &rtmpUrl) {RTMPPusher::rtmpUrl = rtmpUrl;
}bool RTMPPusher::isInited() const {return inited;
}void RTMPPusher::setInited(bool inited) {RTMPPusher::inited = inited;
}

    到这里我们就已经实现完成且封装成一个方便调用的工具类了。调用的时候只要需要先调用init()函数初始化,再调用start()函数,让发送线程跑起来,一有H264数据就通过setH264Data()函数设置给工具类的就行了。这样工具类就会循环读取设置过来的H264数据并推送到RTMP服务器了。

4、其他

    1、由于这个库的pts和dts是需要自己赋值的,所以有时候推送上去的数据要么是播放速度变快,要么是卡顿,都很有可能是pts和dts的问题。由于我这里是固定25帧的,所有我直接pts固定每帧都比上一帧+40ms。pts和dts还有很多研究空间,实际使用的时候具体情况具体分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/615164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux CentOS 7.6安装nginx详细保姆级教程

一、通过wget下载nginx压缩包 1、进入home文件并创建nginx文件夹用来存放nginx压缩包 cd /home //进入home文件夹 mkdir nginx //创建nginx文件夹 cd nginx //进入nginx文件夹2、下载nginx,我这里下载的是Nginx 1.24.0版本&#xff0c;如果要下载新版本可以去官网进行下载:…

堆排序(Java语言)

视频讲解地址&#xff1a;【手把手带你写十大排序】7.堆排序&#xff08;Java语言&#xff09;_哔哩哔哩_bilibili 代码&#xff1a; public class HeapSort {public void swap(int[] array, int index1, int index2) {array[index1] array[index1] ^ array[index2];array[i…

回归预测 | Matlab基于SMA+WOA+SFO-LSSVM多输入单输出回归预测

回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测 目录 回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SMAWOASFO-LSSVM回归预测 基于黏菌算法鲸鱼算法向日葵算法优化LSSVM回归预测 其中包含三种改进…

RSIC-V“一芯”学习笔记(一)——概述

考研的文章和资料之后想写的时候再写怕趴 文章目录 一、阶段设计二、环境、开发语言和工具三、最重要的两个观念四、处理器芯片设计五、处理器芯片设计包含很多软件问题六、处理器芯片的评价指标七、复杂系统的构建和维护八、专业世界观九&#xff0c;提问的艺术(提问模板)十、…

pandas创建一个新的dataframe对象

import pandas as pd df pd.DataFrame() print(df)

ddos攻击会让服务器受到什么影响?-速盾网络(sudun)

DDoS攻击是一种网络攻击手段&#xff0c;它通过利用大量的请求或恶意流量超过服务器的处理能力&#xff0c;从而导致服务器无法正常工作或服务质量显著下降。 首先&#xff0c;DDoS攻击会对服务器的带宽造成极大的压力。攻击者会利用大量的机器或网络资源发起攻击&#xff0c;…

Ubuntu下使用Virtual Box中显示没有可用的USB设备

Ubuntu中使用Virtual Box&#xff0c;但是使用到USB时只有USB1.1可以使用&#xff0c;并且提示没有可以使用的USB设备&#xff0c;解决方法如下 下载并安装Vitrual Box提供的功能扩展包 分别点击帮助->关于&#xff0c;查看当前使用的版本进入到Virtual Box官网下载链接根…

MATLAB中untrace函数用法

目录 语法 说明 untrace函数的功能是在仿真调试会话中移除跟踪点。 语法 untrace blk 说明 untrace blk 从当前仿真调试会话的跟踪点列表中移除块 blk 的跟踪点。每当在仿真调试会话中执行块时&#xff0c;软件会显示与跟踪点对应的块的信息。 当以编程方式启动仿真调试会…

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置!

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置&#xff01; 实际上&#xff0c;vue开发框架的时候&#xff0c;充分考虑到了前端开发人员可能会遇到的各种各样的情况&#xff0c;比如大家经常遇到的&#xff0c;数据类型的校验&#xff0c;再比如&#xff0c;默认…

Spring之整合Mybatis底层源码

文章目录 一、整体核心思路1 . 简介2. 整合思路 二、源码分析1. 环境准备2. 源码分析 一、整体核心思路 1 . 简介 有很多框架需要与Spring进行整合&#xff0c;而整合的核心思路就是把其他框架所产生的对象放到Spring容器中&#xff0c;让其成为一个bean。比如Mybatis&#x…

在Colab上测试Mamba

我们在前面的文章介绍了研究人员推出了一种挑战Transformer的新架构Mamba 他们的研究表明&#xff0c;Mamba是一种状态空间模型(SSM)&#xff0c;在不同的模式(如语言、音频和时间序列)中表现出卓越的性能。为了说明这一点&#xff0c;研究人员使用Mamba-3B模型进行了语言建模…

Oladance、南卡、Cleer开放式耳机怎么样?全方位测评大PK!

​开放式耳机作为新兴的音频设备领域中备受欢迎的选择&#xff0c;但市场上琳琅满目的产品汇集了质量千差万别的耳机&#xff0c;其中存在着一些粗制滥造的产品。身为一位音频设备测评博主&#xff0c;我经常收到有关哪个品牌的开放式耳机质量好的疑问。面对市面上众多选择&…

MFC结合GDI+

MFC结合GDI 创建一个空的MFC界面&#xff0c;在确定按钮函数里进行画图&#xff1a; 1、包含头文件与库 在stdafx.h中加入以下三行代码&#xff1a; #include "gdiplus.h" using namespace Gdiplus; #pragma comment(lib, "gdiplus.lib")2、安装GDI 在…

常州ipad签约 电子签约 战略签约 启动签约

ipad电子签约、签约上墙、多人签约、电子签约、签约仪式、签约软件、签军令状、签字上屏、屏幕签字等&#xff0c;并且本公司ipad签约可以实现 甲方对应N个乙方签约系统对多人&#xff1a;是指只有一个甲方&#xff0c;多个第三方。甲方只要签名一次就可以自动帖加指定的位置 …

基于Flask的高并发部署方案

在AI部署方案中,Flask是最常用的方案!本文列举几种最常用基于Flask的部署方案。 Flask方案 简介 Flask 是一个轻量级的 Python Web 框架,它非常适合构建小型到中型的应用程序。下面是对 Flask 的详细解释和简单示例: Flask 的特点: 轻量级:相比于 Django,Flask 更轻…

uni-app做A-Z排序通讯录、索引列表

上图是效果图&#xff0c;三个问题 访问电话通讯录&#xff0c;拿数据拿到用户的联系人数组对象&#xff0c;之后根据A-Z排序根据字母索引快速搜索 首先说数据怎么拿 - 社区有指导https://ask.dcloud.net.cn/question/64117 uniapp 调取通讯录 // #ifdef APP-PLUSplus.contac…

安谋科技“周易”NPU与飞桨完成II级兼容性测试,助力实现多样化AI部署

近日&#xff0c;安谋科技&#xff08;中国&#xff09;有限公司&#xff08;以下简称“安谋科技”&#xff09;“周易”NPU系列IP与飞桨已完成II级兼容性测试&#xff0c;测试结果显示&#xff0c;双方兼容性表现良好&#xff0c;整体运行稳定。这是安谋科技加入“硬件生态共创…

个人装机记录

配置单&#xff1a; CPU&#xff1a;13900K 主板&#xff1a;z790-a 吹雪 D5 显卡&#xff1a;华硕 tuf 4090 固态&#xff1a;三星990 pro 2TB 2 内存&#xff1a;金士顿 fury d5 6000频 32G2 散热&#xff1a;龙神三代360 电源&#xff1a;tuf 1200W 机箱&#xff1a;tuf 502…

【Node.js学习 day3——http模块】

创建HTTP服务端 //1.导入http模块 const http require(http);//2.创建服务对象 const server http.createServer((request, response) > {response.end(Hello HTTP Server);//设置响应体 });//3.监听端口&#xff0c;启动服务 server.listen(9000,()>{console.log(服务…

IMS中如何区分initial INVITE和re-INVITE?

这里就要先看下Dialog的定义。 dialog是两个UA之间持续一段时间的点对点 SIP关系。dialog通过SIP消息建立&#xff0c;例如对 INVITE request的 2xx response。dialog由Call-ID、local tag和remote tag来区分&#xff0c;也就是Call-ID 、from-tag和to-tag就可以确定一个dialog…