使用srs_librtmp实现RTMP推流

news2024/12/22 13:39:26

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 nalu
    struct 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.coloradmin.cn/o/1376087.html

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

相关文章

2024深圳国际电池创新技术交流展览会

2024深圳国际电池创新技术交流展览会 2024 Shenzhen International Battery Technology Exchange Exhibition 时间&#xff1a;2024年08月28-30日 地点&#xff1a;深圳国际会展中心(国际) 详询主办方陆先生 I38&#xff08;前三位&#xff09; I82I&#xff08;中间四位…

【C语言】ipoib驱动 - ipoib_cm_post_receive_nonsrq_rss函数

一、ipoib_cm_post_receive_nonsrq_rss函数定义 static int ipoib_cm_post_receive_nonsrq_rss(struct net_device *dev,struct ipoib_cm_rx *rx, int id) {struct ipoib_dev_priv *priv ipoib_priv(dev);struct ipoib_recv_ring *recv_ring priv->recv_ring rx->ind…

首个云原生、分布式、全栈国产化银行核心业务系统投产上线丨TiDB × 杭州银行

日前&#xff0c;杭州银行新一代核心业务系统成功投产上线。 新核心系统是业内首个实际投产的云原生、分布式、全栈国产化的银行核心系统&#xff0c;是金融科技领域突破关键核心技术应用的重大实践。 新核心系统自上线以来运行安全稳定&#xff0c;大幅提升了业务处理效率&am…

基于JavaWeb+BS架构+SpringBoot+Vue基于hive旅游数据的分析与应用系统的设计和实现

基于JavaWebBS架构SpringBootVue基于hive旅游数据的分析与应用系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 1 概 述 5 1.1 研究背景 5 1.2 研究意义 5 1.3 研究内容…

香港Web3:Web3的新热土

相关推荐点击查看TechubNews 随着区块链技术的快速发展&#xff0c;Web3的概念逐渐在全球范围内受到关注。作为亚洲的金融中心&#xff0c;香港在Web3领域也展现出了极大的热情和潜力。本文将探讨香港在Web3领域的发展现状、机遇与挑战。 一、香港Web3的发展现状 香港在Web3…

使用python执行系统命令的五种方式

在日常开发中&#xff0c;有时需要在Python脚本中执行系统命令&#xff0c;Python有五种方式来执行系统命令&#xff0c;推荐使用第五种。 python执行系统命令的五种方式 方法1: os.system 这是最简单的方法&#xff0c;适合简单的业务场景&#xff0c;输入为完整命令字符串…

用Python做一个2048小游戏

文章目录 逻辑设计绘图逻辑主循环 逻辑设计 2048的逻辑无非是操作 4 4 4\times4 44的方格&#xff0c;每个方格中有一个数&#xff0c;这些数可以移动&#xff0c;如果两个相同的数字在移动时相撞了&#xff0c;就可以彼此合并。 而这个 4 4 4\times4 44的方格&#xff0c;…

scratch考试“画图”相关考点一网打尽

最近2023年12月17日举行的第15届蓝桥STEMA测评Scratch编程初/中级组编程第3题—六花阵图 在讲解这个题目&#xff0c;可以延申scratch所有的基础图形&#xff0c;学会这篇&#xff0c;碰到关于“图形”类题目基本上都可以搞定 六花阵图 编程实现&#xff1a; 六花阵图。 注…

C#,入门教程(15)——类(class)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(14)——字符串与其他数据类型的转换https://blog.csdn.net/beijinghorn/article/details/124004562 物以类聚&#xff0c;凡物必类。 类的使用&#xff0c;须遵循几个简单的原则&#xff1a; &#xff08;1&#xff09;能类则类&a…

selenium不自动关闭chrome,selenium hello world

selenium不自动关闭chrome 用visual studio的话&#xff0c;右键&#xff0c;在终端运行。 from selenium import webdriveroptions webdriver.ChromeOptions() options.add_experimental_option("detach", True) driver webdriver.Chrome(optionsoptions) url …

世微大功率 内置2.5A宽电压降压恒流 LED电源驱动车灯IC AP5193

AP5193是一款PWM工作模式,高效率、外围简单、 内置功率MOS管&#xff0c;适用于4.5-100V输入的高精度 降压LED恒流驱动芯片。电流2.5A。AP5193可实现线性调光和PWM调光&#xff0c;线性调光 脚有效电压范围0.55-2.6V. AP5193 工作频率可以通过RT 外部电阻编程来设定&#xff0c…

【Python】编程练习的解密与实战(三)

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《Python | 编程解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1fa90;1. 初识Python &a…

机器人制作开源方案 | 六足灾后探测机器人

作者&#xff1a;毕钟诚 施钥 范江龙 张莉曼 陈金凤 单位&#xff1a;中国石油大学&#xff08;北京&#xff09; 指导老师&#xff1a;许亚岚 孙琳 世纪全球自然灾害频发&#xff0c;灾后探测重建工作十分重要&#xff0c;极端恶劣的现场探测环境&#xff0c;频发的余震甚至…

Unity获取系统语言

大家好&#xff0c;我是阿赵。   在使用Unity引擎做多语言的游戏时&#xff0c;很有可能需要根据用户的手机或者电脑的当前语言来设置游戏的默认语言。   Unity的API里面默认就有可以获取系统语言的方法&#xff1a; Application.systemLanguageUnity的API例子&#xff1a…

Redis 为什么要分16个库

目录 一. 前言 二. 16 个数据库的由来 三. 正解 Redis 数据库概念 四. 集群环境下的 Redis 实例 五. 总结 一. 前言 在实际的项目中&#xff0c;Redis 常被用作缓存、分布式锁、消息队列等的解决方案。但是在搭建好Redis 服务后&#xff0c;Redis 默认创建了16个数据库&am…

基于知识图谱的健康知识问答系统

基于知识图谱的健康知识问答系统 引言数据集与技术选型数据集技术选型 系统功能与实现数据导入与图数据库构建问答任务设计与实现1. 实体提取2. 用户意图识别 前端聊天界面与问答系统 结语 引言 随着互联网的发展&#xff0c;人们对健康知识的需求逐渐增加。为了更方便地获取健…

Windows安全基础:认证基础知识

目录 Windows凭据 Windows访问控制模型 访问令牌&#xff1a; 安全标识符&#xff08;SID&#xff09;&#xff1a; 安全描述符&#xff1a; 令牌安全防御 1、禁止域管理员异机登录 2、开启“审核进程创建”策略 Windows凭据 SSPI&#xff08;Security Support Provide…

【PHP】PHP实现与硬件串口交互,接收硬件发送的实时数据

一、前言 目的&#xff1a;借助虚拟串口软件&#xff08;VSPD&#xff09;模拟硬件串口发送数据&#xff0c;使用PHP语言实现接收硬件发送的数据。 我这里的需求是连接天平&#xff0c;把天平的称量数据实时的传送到PHP使用。 使用工具&#xff1a;vspd串口调试工具 使用语…

第十三章Filter

第十三章Filter 1.什么是Filter过滤器2.Filter过滤器的基本使用示例3.完整的用户登录和权限检查4.Filter的生命周期5.FilterConfig类6.FilterChain多个过滤器执行的细节7.Filter的拦截路径 1.什么是Filter过滤器 2.Filter过滤器的基本使用示例 现在下面三个都是可以访问的&…

PPT插件-大珩助手-《提取选中的幻灯片》-选中新建

选中新建 提取选中的幻灯片到新的幻灯文稿中。PDF编辑器可以提取指定的页面到新的PDF文档中&#xff0c;PPT没有这个功能&#xff0c;因此开发。 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持Wps Wo…