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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PPT模板,免费下载

找PPT模板、素材&#xff0c;就上这几个网站&#xff0c;免费下载。 1、菜鸟图库 https://www.sucai999.com/search/ppt/0_0_0_1.html?vNTYxMjky 菜鸟图库素材非常齐全&#xff0c;设计、办公、图片、视频等素材这里都能找到&#xff0c;PPT模板数量很可观&#xff0c;模板样…

《AI基本原理和python实现》栏目介绍

一、说明 栏目《AI基本原理和python实现》的设计目的是为了实现相关算法的python编程。因为用python实现AI需对相关的python库进行全方位了解&#xff0c;本栏目基本包含了【机器学习】相关的经典算法&#xff0c;除此之外还包括了数据分析、时间序列等一些概念和相关python代码…

斯坦福Mobile ALOHA提到的ACT之外的另两项技术:Diffusion Policy、VINN

前言 本文接上一篇文章《斯坦福机器人Mobile ALOHA的关键技术&#xff1a;动作分块ACT的算法原理与代码剖析》而来&#xff0c;当然最开始本文是作为上一篇文章的第二、第三部分的 但因为ACT太过关键&#xff0c;除了在上一篇文章中写清楚其算法原理之外&#xff0c;还得再剖…

借助文档控件Aspose.Words,使用 Java 在 Word 文档中创建表格

Microsoft Word 是一种流行的文字处理应用程序&#xff0c;用于创建各种类型的文档。这些文档可能包含多种类型的元素&#xff0c;包括文本、图像、表格和图表。当涉及到用 Java 自动创建和操作文档时&#xff0c;您可能需要一个轻松的解决方案来在 Word 文档中创建表格。因此&…

操作系统课程设计:常用页面置换算法(OPT、FIFO、LRU)的实现及缺页率的计算(C语言)

名人说&#xff1a;莫听穿林打叶声&#xff0c;何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#xff09; 目录 一、效果图二、代码&#xff08;带注释&#xff09;三、说明 一、效果图 二、代码&#xff08;带…

@RequestParam

在我们写接口的时候&#xff0c;经常会用到这个注解来标记参数&#xff0c;通过这个注解我们可以把请求的url中的参数名和值映射到被标记的参数上。 比如下方&#xff0c;这个接口是通过传入的参数来查询相关信息的 我们定义这样一个接口&#xff0c;设置了8个参数&#xff0c;…

接口测试工具:Postman的高级用法

Postman 是一款功能强大的 API 开发和测试工具&#xff0c;以下是一些高级用法的详细介绍和操作步骤。【文末有配套视频教程和免费的资料文档领取】 一、环境和全局变量 环境变量允许你设置特定于环境&#xff08;如开发、测试、生产&#xff09;的变量&#xff0c;全局变量则…

C++ Primer 6.1 函数基础

函数的形参列表 int func(int v,int v2) {int v,v2;//&#xff01;错误 } 函数返回类型 不能是数组和函数&#xff08;两者都不接受对拷&#xff09;&#xff0c;但可以是指针 局部对象 形参和函数体内部的变量称为局部变量&#xff0c;仅在函数内部可见&#xff0c;隐藏外部…

四川天蝶电子商务有限公司助力商家飞向电商蓝海

随着互联网的飞速发展&#xff0c;电商行业已经成为一个不可忽视的经济增长点。在这个大背景下&#xff0c;四川天蝶电子商务有限公司凭借其独特的抖音电商服务&#xff0c;迅速崭露头角&#xff0c;成为了众多商家在电商领域的得力助手。今天&#xff0c;我们将深入了解这家公…

关于markdown文件插入图片变成相对路径

两种方式 第一种方式 ![](绝对路径)变成下面这种相对路径 也就是说每次插入的时候&#xff0c;都得修改一下。 第二种方式 在Typora中&#xff0c;文件——偏好设置——图像——优先选择相对路径 这样问题就解决了。 如果想了解更多的方式&#xff0c;附上链接。 Typora…

Groove闭包

Groovy闭包 - 简书# 闭包 闭包的基础知识 闭包的使用 闭包 this,owner,delegate 的理解 总结 ## 闭包的基础知识 闭包就是一段可以使用参数的代码片段&#xff0c;每个闭包会被编译成...https://www.jianshu.com/p/c73b03cdf986

Android中两种选择联系人方式

1.在选择联系人方式网上也有很多案例 有的说是使用ContactsContract.CommonDataKinds.Phone.CONTENT_URI也有的说是使用ContactsContract.Contacts.CONTENT_URI其实这两种方式都可以使用 只不过ContactsContract.Contacts.CONTENT_URI这种方式需要多查询一遍 一、使用Contacts…

矿山无人驾驶方案

矿山无人驾驶运输系统&#xff0c;可实现露天矿采煤装载运输的无人化&#xff0c;满足智能矿山安全、高效、绿色、环保等目标。 无人驾驶应用的总体技术架构包括“车端、场端、云端”三个层面以及相应的安全保障体系&#xff0c;其中车端的智能矿卡具备车辆感知、通信、决策和执…

[NOIP2006 提高组] 作业调度方案(修改)

题目&#xff1a; 这里对于之前的题目进行修改记录。果然还是受不了等待&#xff0c;利用晚饭时间又看了这个题目。于是发现了问题。 之前的博客&#xff1a;https://blog.csdn.net/KLSZM/article/details/135522867?spm1001.2014.3001.5501 问题修改描述 上午书写的代码中是…

Mongodb Replica Sets 副本集群搭建

Replica Sets 复制集搭建 MongoDB 有三种集群架构模式&#xff0c;分别为主从复制&#xff08;Master-Slaver&#xff09;、副本集&#xff08;Replica Set&#xff09;和分片&#xff08;Sharding&#xff09;模式。 Master-Slaver 是一种主从复制的模式&#xff0c;目前已经…

Spring MVC 的RequestMapping注解

RequestMapping注解 使用说明 作用&#xff1a;用于建立请求URL和处理请求方法之间的对应关系。 出现位置&#xff1a; 类上&#xff1a; 请求 URL的第一级访问目录。此处不写的话&#xff0c;就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的 URL 可以…

解决:TypeError: ‘dict_keys’ object does not support indexing

解决&#xff1a;TypeError: ‘dict_keys’ object does not support indexing 文章目录 解决&#xff1a;TypeError: dict_keys object does not support indexing背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff1a;方法二&#xff1a;方法三&#xff1a;今…

CES2024:智能戒指、全息技术、AI家居机器人等有趣的小工具

在CES2024的展会上上&#xff0c;我们见证了一系列充满创意和未来感的科技产品。从智能戒指到全息技术&#xff0c;再到AI家居机器人&#xff0c;这些有趣的小工具不仅展现了技术的进步&#xff0c;更预示着未来生活的可能性。现在就来给大家介绍九个实用有趣的小工具。 1、华…

单因素方差分析--R

任务说明 三个剂量水平的药物处理受试者&#xff0c;每个剂量水平十个受试者&#xff0c;现在收集到数据后&#xff0c;问&#xff1a; 药物剂量水平显著影响受试者的response&#xff1f; 或者不同剂量药物处理受试者有显著效果的差异吗&#xff1f; 数据 library(tidyvers…

stable diffusion代码学习笔记

前言&#xff1a;本文没有太多公式推理&#xff0c;只有一些简单的公式&#xff0c;以及公式和代码的对应关系。本文仅做个人学习笔记&#xff0c;如有理解错误的地方&#xff0c;请指出。 资源 本文学习的代码&#xff1b;相关文献&#xff1a; Denoising Diffusion Probab…