使用srs_librtmp实现RTMP推流

2024-01-12 09:28

本文主要是介绍使用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还有很多研究空间,实际使用的时候具体情况具体分析。

这篇关于使用srs_librtmp实现RTMP推流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st

SpringBoot集成MyBatis实现SQL拦截器的实战指南

《SpringBoot集成MyBatis实现SQL拦截器的实战指南》这篇文章主要为大家详细介绍了SpringBoot集成MyBatis实现SQL拦截器的相关知识,文中的示例代码讲解详细,有需要的小伙伴... 目录一、为什么需要SQL拦截器?二、MyBATis拦截器基础2.1 核心接口:Interceptor

Python使用openpyxl读取Excel的操作详解

《Python使用openpyxl读取Excel的操作详解》本文介绍了使用Python的openpyxl库进行Excel文件的创建、读写、数据操作、工作簿与工作表管理,包括创建工作簿、加载工作簿、操作... 目录1 概述1.1 图示1.2 安装第三方库2 工作簿 workbook2.1 创建:Workboo

SpringBoot集成EasyPoi实现Excel模板导出成PDF文件

《SpringBoot集成EasyPoi实现Excel模板导出成PDF文件》在日常工作中,我们经常需要将数据导出成Excel表格或PDF文件,本文将介绍如何在SpringBoot项目中集成EasyPo... 目录前言摘要简介源代码解析应用场景案例优缺点分析类代码方法介绍测试用例小结前言在日常工作中,我们经

基于Python实现简易视频剪辑工具

《基于Python实现简易视频剪辑工具》这篇文章主要为大家详细介绍了如何用Python打造一个功能完备的简易视频剪辑工具,包括视频文件导入与格式转换,基础剪辑操作,音频处理等功能,感兴趣的小伙伴可以了... 目录一、技术选型与环境搭建二、核心功能模块实现1. 视频基础操作2. 音频处理3. 特效与转场三、高

Python实现中文文本处理与分析程序的示例详解

《Python实现中文文本处理与分析程序的示例详解》在当今信息爆炸的时代,文本数据的处理与分析成为了数据科学领域的重要课题,本文将使用Python开发一款基于Python的中文文本处理与分析程序,希望... 目录一、程序概述二、主要功能解析2.1 文件操作2.2 基础分析2.3 高级分析2.4 可视化2.5

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张

使用Go实现文件复制的完整流程

《使用Go实现文件复制的完整流程》本案例将实现一个实用的文件操作工具:将一个文件的内容完整复制到另一个文件中,这是文件处理中的常见任务,比如配置文件备份、日志迁移、用户上传文件转存等,文中通过代码示例... 目录案例说明涉及China编程知识点示例代码代码解析示例运行练习扩展小结案例说明我们将通过标准库 os

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数