Qt编写跨平台的推流工具(支持win/linux/mac/嵌入式linux/安卓等)

news2024/9/22 1:38:45

一、前言

跨平台的推流工具当属OBS最牛逼,功能也是最强大的,唯一的遗憾就是多路推流需要用到插件,而且CPU占用比较高,默认OBS的规则是将对应画布中的视频画面和设定的音频一起重新编码再推流,意味着肯定占用不少CPU资源,而很多时候尤其是视频监控行业,往往源头的摄像头的视频流就是标准的264/265/aac格式,其实无需重新编码就可以推流,就相当写文件一样,只需要极低极低的CPU运算资源,一路1080P基本上占用在0.1%以下,而OBS可以直接干到5%,这就差距有点大了,几十倍的差距,毕竟重新编码哪怕是硬编码,肯定需要占用不少的CPU。而用户的真实需求可能是要求500路摄像头推流,用OBS肯定是达不到的,10路都卡成屎,毕竟OBS面对的不是视频监控行业的应用场景,而是视频直播行业,视频直播行业要的是各种特效,画面的自定义,音频的自定义,要求在一路上面可以各种各样的自定义。为此本工具应运而生,主要针对极低资源占用的多路推流,同时能够跨平台运行,包括但不限于windows/linux/mac/安卓/嵌入式linux/树莓派等。

既然要求跨平台,那用Qt开发是首选,OBS本身也是Qt开发的,在各个平台上也能正常编译运行。只要代码中没有特别依赖对应操作系统的API接口函数的,源码都可以直接在其他平台编译运行。其实用ffmpeg做推流,核心就是保存文件,只需要将保存文件的地址改成推流地址即可,是不是很简单,最开始没有做过的时候还以为有多复杂,吓得都不敢开工,估计也有不少人和我一样。

二、效果图

在这里插入图片描述
在这里插入图片描述

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_push。

四、功能特点

  1. 支持各种本地视频文件和网络视频文件。
  2. 支持各种网络视频流,网络摄像头,协议包括rtsp、rtmp、http。
  3. 支持将本地摄像头设备推流,可指定分辨率和帧率等。
  4. 支持将本地桌面推流,可指定屏幕区域和帧率等。
  5. 自动启动流媒体服务程序,默认mediamtx(原rtsp-simple-server),可选用srs、EasyDarwin、LiveQing、ZLMediaKit等。
  6. 可实时切换预览视频文件。
  7. 推流的清晰度和质量可调。
  8. 可动态添加文件、目录、地址。
  9. 视频文件自动循环推流,如果视频源是视频流,在掉线后会自动重连。
  10. 网络视频流自动重连,重连成功自动继续推流。
  11. 网络视频流实时性极高,延迟极低,延迟时间大概在100ms左右。
  12. 极低CPU占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  13. 推流可选推流到rtsp/rtmp两种,推流后的数据支持直接rtsp/rtmp/hls/webrtc四种方式访问,可以直接浏览器打开看实时画面。
  14. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  15. 每个推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。
  16. 自动生成测试网页直接打开播放,可以看到实时效果,自动按照数量对应宫格显示。
  17. 推流过程中可以在表格中切换对应推流项,实时预览正在推流的视频,并可以切换视频文件的播放进度。
  18. 音视频同步推流,符合264/265/aac格式的自动原数据推流,不符合的自动转码再推流(会占用一定CPU)。
  19. 转码策略支持三种,自动处理(符合要求的原数据/不符合的转码),仅限文件(文件类型的转码视频),所有转码。
  20. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  21. 支持Qt4/Qt5/Qt6任意版本,支持任意系统(windows/linux/macos/android/嵌入式linux等)。

五、相关代码

#include "ffmpegsavesimple.h"

//用法示例(保存文件/推流)
#if 0
FFmpegSaveSimple *f = new FFmpegSaveSimple(this);
f->setUrl("f:/mp4/1.mp4", "f:/1.mp4");
f->setUrl("f:/mp4/1.mp4", "rtmp://127.0.0.1:6908/stream");
f->start();
#endif

FFmpegSaveSimple::FFmpegSaveSimple(QObject *parent) : QThread(parent)
{
    stopped = false;
    audioIndex = -1;
    videoIndex = -1;

    formatCtxIn = NULL;
    formatCtxOut = NULL;

    //初始化ffmpeg的库
    FFmpegHelper::initLib();
}

FFmpegSaveSimple::~FFmpegSaveSimple()
{
    this->stop();
    this->close();
}

void FFmpegSaveSimple::run()
{
    if (!this->open()) {
        this->close();
        return;
    }

    int ret;
    AVPacket packet;
    qint64 videoCount = 0;
    qint64 startTime = av_gettime();

    while (!stopped) {
        //读取一帧
        ret = av_read_frame(formatCtxIn, &packet);
        int index = packet.stream_index;
        if (ret < 0) {
            if (ret == AVERROR_EOF || ret == AVERROR_EXIT) {
                debug(ret, "文件结束");
                break;
            } else {
                debug(ret, "读取出错");
                continue;
            }
        }

        //取出输入输出流的时间基
        AVStream *streamIn = formatCtxIn->streams[index];
        AVStream *streamOut = formatCtxOut->streams[index];
        AVRational timeBaseIn = streamIn->time_base;
        AVRational timeBaseOut = streamOut->time_base;

        if (index == videoIndex) {
            videoCount++;
        }

        //纠正有些文件比如h264格式的没有pts
        if (packet.pts == AV_NOPTS_VALUE) {
            qreal fps = av_q2d(formatCtxIn->streams[videoIndex]->r_frame_rate);
            FFmpegHelper::rescalePacket(&packet, timeBaseIn, videoCount, fps);
        }

        //延时(防止数据太快给缓存造成太大压力)
        if (index == videoIndex) {
            AVRational timeBase = {1, AV_TIME_BASE};
            int64_t ptsTime = av_rescale_q(packet.dts, timeBaseIn, timeBase);
            int64_t nowTime = av_gettime() - startTime;
            if (ptsTime > nowTime) {
                av_usleep(ptsTime - nowTime);
            }
        }

        //重新调整时间基准
        FFmpegHelper::rescalePacket(&packet, timeBaseIn, timeBaseOut);

        qDebug() << TIMEMS << "发送一帧" << videoCount << packet.flags << packet.pts << packet.dts;
        //ret = av_write_frame(formatCtxOut, &packet);
        ret = av_interleaved_write_frame(formatCtxOut, &packet);
        if (ret < 0) {
            debug(ret, "写数据包");
            break;
        }

        av_packet_unref(&packet);
    }

    //写文件尾
    av_write_trailer(formatCtxOut);
    this->close();
}

bool FFmpegSaveSimple::open()
{
    if (urlIn.isEmpty() || urlOut.isEmpty()) {
        return false;
    }

    int ret = -1;
    if ((ret = avformat_open_input(&formatCtxIn, urlIn.toUtf8().constData(), 0, NULL)) < 0) {
        debug(ret, "打开输入");
        return false;
    }
    if ((ret = avformat_find_stream_info(formatCtxIn, 0)) < 0) {
        debug(ret, "无流信息");
        return false;
    }

    videoIndex = av_find_best_stream(formatCtxIn, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoIndex < 0) {
        debug(ret, "无视频流");
        return false;
    }

    //既可以是保存到文件也可以是推流(对应格式要区分)
    const char *format = "mp4";
    if (urlOut.startsWith("rtmp://")) {
        format = "flv";
    } else if (urlOut.startsWith("rtsp://")) {
        format = "rtsp";
    }

    QByteArray urlData = urlOut.toUtf8();
    const char *url = urlData.constData();
    ret = avformat_alloc_output_context2(&formatCtxOut, NULL, format, url);
    if (!formatCtxOut) {
        debug(ret, "创建输出");
        return false;
    }

    //根据输入流创建输出流
    for (int i = 0; i < formatCtxIn->nb_streams; i++) {
        AVStream *streamIn = formatCtxIn->streams[i];
        AVStream *streamOut = avformat_new_stream(formatCtxOut, NULL);
        if (!streamOut) {
            return false;
        }

        //复制各种参数
        ret = FFmpegHelper::copyContext(streamIn, streamOut);
        if (ret < 0) {
            debug(ret, "复制参数");
            return false;
        }        
    }

    //打开输出
    if (!(formatCtxOut->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&formatCtxOut->pb, url, AVIO_FLAG_WRITE);
        if (ret < 0) {
            debug(ret, "打开输出");
            return false;
        }
    }

    //写文件头
    ret = avformat_write_header(formatCtxOut, NULL);
    if (ret < 0) {
        debug(ret, "写文件头");
        return false;
    }

    debug(0, QString("打开成功: %1").arg(urlIn));
    if (format != "mp4") {
        debug(0, QString("开始推流: %1").arg(urlOut));
    } else {
        debug(0, QString("开始录像: %1").arg(urlOut));
    }

    return true;
}

void FFmpegSaveSimple::close()
{
    stopped = false;
    if (formatCtxOut) {
        debug(0, QString("关闭成功: %1").arg(urlIn));
    }

    if (formatCtxIn) {
        avformat_close_input(&formatCtxIn);
        formatCtxIn = NULL;
    }

    if (formatCtxOut && !(formatCtxOut->oformat->flags & AVFMT_NOFILE)) {
        avio_close(formatCtxOut->pb);
    }

    if (formatCtxOut) {
        avformat_free_context(formatCtxOut);
        formatCtxOut = NULL;
    }
}

void FFmpegSaveSimple::debug(int ret, const QString &msg)
{
    QString text = (ret < 0 ? QString("%1 错误: %2").arg(msg).arg(FFmpegHelper::getError(ret)) : msg);
    qDebug() << TIMEMS << text;
}

void FFmpegSaveSimple::setUrl(const QString &urlIn, const QString &urlOut)
{
    this->urlIn = urlIn;
    this->urlOut = urlOut;
}

void FFmpegSaveSimple::stop()
{
    this->stopped = true;
    this->wait();
}

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

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

相关文章

【解决】笔记本电脑wifi无法访问网站的各种情况

本文适用于 mac 以及 windows 等操作系统 场景 手机可以连接wifi&#xff0c;电脑连接却无法访问左下角wifi图标为小地球可以使用app&#xff0c;无法访问网站其他 方式一 Win10任务栏中的网络变成小地球&#xff0c;无WIFI列表显示&#xff0c;应该&#xff1a; winR , 输入…

MATLAB计算程序运行所需的时间

在使用MATLAB进行计算时&#xff0c;用户有时需要了解程序运行所花费的四件&#xff0c;以此来评价程序的执行效率&#xff0c;从而对代码进行优化 可以使用3种方法来得到程序运行所需要的时间 1.使用tic和toc命令 将tic和toc命令相结合来得到程序的运行时间 ①tic命令&…

SAP CAP篇六:为CAP添加Fiori Elements程序(3)

本文目录 本系列之前的文章本篇之前的运行效果修改pom.xml添加新的Dependency更新plugin 添加新的Controller创建index目录添加UiIndexContentProviderFactory.java 添加resources运行结果更新UiIndexContentProviderFactory 代码库 (Gitcode) 本系列之前的文章 本系列之前的文…

文学创作的数学原理——5年文学阅读的思考

作者 | gongyouliu 编辑 | gongyouliu 我从2019年初开始阅读文学作品&#xff0c;到今年差不多5年了&#xff0c;至少也阅读了50本以上的世界名著了&#xff08;如果算「得到」听书上的文学作品&#xff0c;那么至少也有200本以上了&#xff09;&#xff0c;这里面绝大多数都是…

python(10):批量修改文件名

文章目录 1. 使用Python的字符串操作来去除文件名高位的多余零说明代码示例 2. 将文件夹中的文件重命名为递增的数字加上原始文件名的后缀代码运行前后&#xff1a; 1. 使用Python的字符串操作来去除文件名高位的多余零 说明 一个目录下的文件名是6位数字序号加上后缀的格式&…

PaddleSeg在Windows上的配置及人像分隔使用

PaddleSeg是基于飞桨PaddlePaddle开源的端到端图像分割套件&#xff0c;提供语义分割、交互式分割、Matting、全景分割四大分割能力。源代码地址&#xff1a;https://github.com/PaddlePaddle/PaddleSeg&#xff0c;license为Apache-2.0&#xff0c;最新发布版本为2.8。 Window…

借用jQuery发送Http请求的实现(内附源码)

文章目录 一、前言二、jQuery 介绍三、jQuery 下载四、jQuery 使用五、Http客户端DEMO实现 一、前言 最近在解决项目上前同事开发的视频播放器问题&#xff0c;视频播放器是用Qt开发&#xff0c;作为播放插件供Web调用。 播放器与Web的通信方式采用的是Http&#xff0c;数据格…

libevent(1)windows下安装libevent

Socket通信库libevent成熟、稳定、性能高&#xff0c;在unix和windows下都能使用&#xff0c;在证券交易领域也有不少成功的应用&#xff0c;已经用事实证明是非常棒的socket通信库。对我们目前交易系统的unix重构来说&#xff0c;是比较合适的选择 –– 坑少、在证券交易项目中…

Colors颜色代码对应表 | 常见颜色值的中英文对照及颜色值代码表Hex代码RGB代码

颜色代码对应表_颜色代码表大全——最全的颜色代码以及对应图片 Hex代码 RGB代码 关于颜色的英文&#xff0c;不仅仅有七个...... 一、常用颜色的英文名称&#xff1a; redcrimsonfirebrickdarkredbrownmaroonsiennasaddlebrownindianredrosybrownlightcoralsalmondarksalm…

壳牌小程序笔记

壳牌加油站 uni-app-基础-day01 概览 为什么要学uni-app&#xff1f; 现在很多中小型公司&#xff0c;都有自己的小程序项目&#xff0c;然后开发小程序就会用到uni-app。 uni-app没有诞生之前&#xff0c;怎么写小程序 使用原生微信小程序这个框架去开发&#xff1f; 只…

matplotlib---绘制折线图

1. 折线图 绘制一条线&#xff1a; plot函数来绘制折线图。下面是对各个参数的解释&#xff1a; x&#xff1a;要绘制折线图的x轴数据。y&#xff1a;要绘制折线图的y轴数据。c&#xff08;可选&#xff09;&#xff1a;指定折线的颜色。默认为蓝色&#xff08;"b"…

三模块七电平级联H桥整流器电压平衡控制策略仿真

三模块七电平级联H桥整流器电压平衡控制策略MATLAB仿真资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87934753模型简介&#xff1a; MATLAB2021b 采用三模块七电平级联H桥整流器仿真&#xff0c;采用电压电流双闭环控制&#xff0c;电流环采用PR控制&am…

「团建活动」无锡某企业2023拓展培训

如何让新员工快速融入集体&#xff0c;自发成长&#xff0c;一次次有效团建。 ➡️明确团队目标&#xff0c;提高处理问题的效率和能力&#xff0c;互相配合&#xff0c;更好完成任务 &#xff1b; ➡️协调个人与团队的关系&#xff0c;增进情感&#xff1b; ➡️激励团队&…

深度学习时间序列预测项目案例数据集介绍

💥项目专栏:【深度学习时间序列预测案例】零基础入门经典深度学习时间序列预测项目实战(附代码+数据集+原理介绍) 一、数据集介绍 🌈 本专栏使用的数据集为 风速预测的时间序列数据,该数据集包含一个气象站内嵌入的5个天气变量传感器阵列的 6574 个每日平均样本。该设备…

rabbitmq第一课-rabbitmq的快速安装以及核心概念

一、MQ介绍 1.什么是MQ&#xff1f;为什么要用MQ&#xff1f; 消息队列是一种在应用程序之间传递消息的技术。它提供了一种异步通信模式&#xff0c;允许应用程序在不同的时间处理消息。消息队列通常用于解耦应用程序&#xff0c;以便它们可以独立地扩展和修改。在消息队列中…

LNMP架构搭建实操

目录 一、安装Nginx服务 1.安装依赖包 2.创建Nginx运行用户 3.编译安装Nginx源码包 4.优化路径便于使用 5、添加 Nginx 系统服务 二、安装Mysql服务 1.安装Mysql环境依赖包 2.创建Mysql运行用户 3.编译安装 4.修改mysql配置文件 5.更改mysql安装目录和配置文件的属…

ArduPilot开源代码之AP_Logger

ArduPilot开源代码之AP_Logger 1. 源由2. Logger类2.1 Copter初始化2.2 Copter任务 3. 实例理解3.1 Copter设备应用任务3.1.1 Copter::ten_hz_logging_loop3.1.2 Copter::twentyfive_hz_logging 3.2 AP_Logger应用类任务3.2.1 AP_Logger::periodic_tasks3.2.2 AP_Logger::io_th…

ORB_SLAM3 IMU预积分PreintegrateIMU

这篇博文主要分享ORB_SLAM3中Tracking::PreintegrateIMU()&#xff0c;其主要包括几个部分&#xff1a; 获得两帧之间的IMU数据中值积分IMU状态更新 关于IMU的理论推导参考&#xff1a; ORB_SLAM3_IMU预积分理论推导(预积分项)ORB_SLAM3_IMU预积分理论推导(噪声分析)ORB_SLA…

第五章 Linux的文件权限与目录配置

Linux最好的地方之一就在于他的多用户多任务环境。为了让各个使用者具有较保密的文件数据&#xff0c;因此文件的权限管理就变得很重要了。Linux一般将文件可存取的身份分为三个类别&#xff0c;分别是owner/group/others,且三种身份各有read/write/execute等权限。 5.1 使用者…

【Servlet学习一】认识Servlet 创建第一个Servlet项目

目录 &#x1f31f;需要知道&#xff1a; &#x1f308;1、Tomcat是什么&#xff1f; &#x1f308; 2、Maven &#x1f31f;一、认识Servlet &#x1f308;1、Servlet是什么&#xff1f; &#x1f308;2、实现第一个Servlet项目。 &#x1f308;3、简单了解Postman工具…