一、前言

跨平台的推流工具当属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();
}

Qt/C++编写跨平台的推流工具(支持win/linux/mac/嵌入式linux/安卓等)的更多相关文章

  1. 亲测有效 Hyper V3.4.0 终端美化工具 支持win/mac

    亲测有效 Hyper V3.4.0 终端美化工具 支持win/mac Hyper 是一款终端美化工具 基于Web技术,JS/HTML/CSS ,支持扩展增强,很不错! 且支持win,mac 下载地址 ...

  2. LiveBlox无需代码的开发工具--支持win macos ubuntu等开发环境--

    LiveBlox无需代码的开发工具-支持windows macos ubuntu. 强大 灵活 易于使用 视频简介:LiveBlox Develop Technology Without Coding ...

  3. WinSetupFromUSB - 超简单制作多合一系统安装启动U盘的工具 (支持Win/PE/Linux启动盘)

    很多同学都喜欢将电脑凌乱不堪的系统彻底重装以获得一个"全新的开始",但你会发现如今很多电脑都已经没有光驱了,因此制作一个U盘版的系统安装启动盘备用是非常必要的. 我们之前推荐过 I ...

  4. 在Quick-cocos2dx中使用云风pbc解析Protocol Buffers,支持win、mac、ios、android

    本例主要介绍 如何将 pbc 集成到quick-cocos2dx框架中,让我们的cocos2dx客户端Lua拥有编解码Protocol Buffers能力. 参考: 云风pbc的用法: http:// ...

  5. 使用Qt installer framework制作安装包(不知道是否适合Mac和Linux?)

    一.介绍 使用Qt库开发的应用程序,一般有两种发布方式:(1)静态编译发布.这种方式使得程序在编译的时候会将Qt核心库全部编译到一个可执行文件中.其优势是简单单一,所有的依赖库都集中在一起,其缺点也很 ...

  6. Navicat15 For Mysql最新版完美破解图文教程(支持Win和Mac)

    Navicat15 For Mysql最新版完美破解 欢迎关注博主公众号[跟着Mic学架构],专注于分享Java领域技术干货,回复关键字 [面试资料] 可以获得海量面试资料. 申明,本教程 Navic ...

  7. 跨平台自动构建工具v1.0.2 发布

    XMake是一个跨平台自动构建工具,支持在各种主流平台上构建项目,类似cmake.automake.premake,但是更加的方便易用,工程描述语法更简洁直观,支持平台更多,并且集创建.配置.编译.打 ...

  8. 几款抓包工具在windows,mac,linux下的支持分析

    抓包工具的使用 几款抓包工具在windows,mac,linux下的支持分析 抓包工具简介 Chrome/Firefox 开发者工具: 浏览器内置,方便易用 Fiddler/Charles: 基于代理 ...

  9. 新成员!Visual Studio Code --跨平台的开发工具(支持OSX, Linux 和 Windows)

    原文出处:新成员!Visual Studio Code --跨平台的开发工具(支持OSX, Linux 和 Windows) 这是我的文章备份  http://www.dotblogs.com.tw/ ...

  10. Sublime Text 2 - 性感无比的代码编辑器!程序员必备神器!跨平台支持Win/Mac/Linux

    我用过的编辑器不少,真不少- 但却没有哪款让我特别心仪的,直到我遇到了 Sublime Text 2 !如果说“神器”是我能给予一款软件最高的评价,那么我很乐意为它封上这么一个称号.它小巧绿色且速度非 ...

随机推荐

  1. 键盘事件 key keyCode

    keyCode 8 = BackSpace BackSpace keyCode 9 = Tab Tab keyCode 12 = Clear keyCode 13 = Enter keyCode 16 ...

  2. SaaS架构:中央库存系统架构设计

    大家好,我是汤师爷~ 近年来,越来越多的零售企业大力发展全渠道业务.在销售额增长上,通过线上的小程序.直播.平台渠道等方式,拓展流量变现渠道.在会员增长方面,通过多样的互动方式,全渠道触达消费者,扩大 ...

  3. 云原生爱好者周刊:Chaos Mesh 升级成为 CNCF 孵化项目

    云原生一周动态要闻: Chaos Mesh 升级成为 CNCF 孵化项目 Zabbix Web Frontend 中发现安全漏洞 2021 年 Rust 调查报告出炉 Sysdig 2022 云原生安 ...

  4. node.js安装及环境配置基于Windows系统

    node.js安装及环境配置-Windows系统 1. 下载安装包 https://nodejs.org/zh-cn/download/ 根据自己电脑系统及位数选择,我的电脑是Windows系统.64 ...

  5. 自建家庭 KTV,在家想嗨就嗨

    现在用户最多.曲库最多的 K 歌软件是全民K歌,基本上想唱的歌都有,而且基本上每首歌都有 MV 或视频,使用体验也还不错,但是收费太贵了,对于一个月唱不了几次的打工人来说,唱一首歌就是"天价 ...

  6. jetpack1

    组合函数 package com.example.myapplication1 import android.os.Bundle import androidx.activity.ComponentA ...

  7. 使用 FastGPT 工作流实现 AI 赛博算卦,一键生成卦象图

    最近那个男人写的汉语新解火遍了全网,那个男人叫李继刚,国内玩 AI 的同学如果不知道这个名字,可以去面壁思过了. 这个汉语新解的神奇之处就在于它只是一段几百字的提示词,效果却顶得上几千行代码写出来的应 ...

  8. Java基础完结

    花费了大概一周的时间,看完了韩顺平的Java基础课,不得不说韩老师讲的java基础还是相当仔细的,细节满满,我认为这既是优点也是缺点吧23333333-- 接下来准备看老杜的MySQL,因为没有配套的 ...

  9. 6.Kubernetes集群管理工具kubectl

    Kubernetes集群管理工具kubectl 概述 kubectl是Kubernetes集群的命令行工具,通过kubectl能够对集群本身进行管理,并能够在集群上进行容器化应用的安装和部署 命令格式 ...

  10. 低功耗4G模组:RSA算法示例

    ​ 今天我们学习合宙低功耗4G模组Air780EP_LuatOS_rsa示例,文末[阅读原文]获取最新资料. 一.简介 RSA算法的安全性基于:将两个大质数相乘很容易,但是想要将其乘积分解成原始的质数 ...