Qt/C++音视频开发72-倍速推流/音视频同步倍速推流/不改变帧率和采样率/低倍速和高倍速

news/2024/10/10 8:19:59

一、前言

最近多了个新需求,需要倍速推流,推流界的扛把子obs也有倍速推流功能,最高支持到两倍速。这里所说的倍速,当然只限定在文件,只有文件才可能有倍速功能,因为也只有文件才能倍速解码播放。实时视频流是不可能倍速的,因为没有时长,有时长的才可以按照播放进度来。是否是文件也不能通过是不是本地文件等来判断,以为很多http/rtsp/m3u8等也可能是文件,具体最终的判断依据应该是有没有时长,能不能获取到时长,能获取到的就说明是文件。

倍速推流和倍速播放功能相通,在ffmpeg做音视频解码常识中,有个pts和dts就是用来控制显示时间和解码时间的,如果这两个值除以2就说明时间少了一半,就是2倍速,乘以2就表示时间多了2倍,就是0.5倍速,基本上的运算公式就是 packet.pts = packet.pts/speed,其中这个speed速度参数是float类型。倍速播放的时候其实就是将收到的packet的pts/dts更改后,再送入解码,而推流其实就是保存,保存到rtsp地址就是将数据推流到rtsp,所以将这个值经过同样的运算发出去,就形成了倍速推流。

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。

二、效果图

三、体验地址

  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。
  5. 视频主页:https://space.bilibili.com/687803542

四、功能特点

  1. 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。
  2. 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。
  3. 支持本地摄像头设备推流,可指定分辨率、帧率、格式等。
  4. 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。
  5. 可实时切换预览视频文件,可切换音视频文件播放进度,切换到哪里就推流到哪里。预览过程中可以切换静音状态和暂停推流。
  6. 可指定重新编码推流,任意源头格式可选强转264或265格式。
  7. 可转换分辨率推流,设置等比例缩放或者指定分辨率进行转换。
  8. 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。
  9. 音视频文件自动循环不间断推流。
  10. 音视频流有自动掉线重连机制,重连成功自动继续推流。
  11. 支持各种流媒体服务程序,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。
  12. 通过配置文件自动加载对应流媒体程序的协议和端口,自动生成推流地址和各种协议的拉流地址。可以通过配置文件自己增加流媒体程序。
  13. 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。
  14. 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。
  15. 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。
  16. 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。
  17. 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  18. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  19. 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。也支持自动按照指定标识后面加数字的方式递增命名。比如设置标识为字母v,策略为标识递增,则每添加一个对应的推流码命名依次是v1、v2、v3等。
  20. 根据推流协议自动转码格式,默认策略按照选择的推流协议,比如rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则自动转码成264格式再推流。
  21. 音视频同步推流,在拉流和采集的时候就会自动处理好同步,同步后的数据再推流。
  22. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  23. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
  24. 根据不同的流媒体服务器类型,自动生成对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器或者网页中预览查看。
  25. 添加的推流地址等信息自动存储到文件,可以手动打开进行修改,默认启动后自动加载历史记录。
  26. 可以指定生成的网页文件保存位置,方便作为网站网页发布,可以直接在浏览器中输入网址进行访问,发布后可以直接在局域网其他设备比如手机或者电脑打开对应网址访问。
  27. 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐藏地址中的用户信息。
  28. 自带设备推流模块,自动识别本地设备,包括本地的摄像头和桌面,可以手动选择不同的是视频和音频采集设备进行推流。
  29. 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种浏览器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开请求。
  30. 文件点播模块实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。
  31. 文件点播模块采用纯QTcpSocket通信,不依赖流媒体服务程序,核心源码不到500行,注释详细,功能完整。
  32. 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。

五、相关代码

#include "frmspeedpush.h"
#include "ui_frmspeedpush.h"
#include "qthelper.h"
#include "videoutil.h"frmSpeedPush::frmSpeedPush(QWidget *parent) : QWidget(parent), ui(new Ui::frmSpeedPush)
{ui->setupUi(this);this->initForm();this->initConfig();
}frmSpeedPush::~frmSpeedPush()
{AppConfig::SpeedPushStart = (ui->btnStart->text() == "停止推流");AppConfig::writeConfig();delete ui;
}void frmSpeedPush::initForm()
{VideoPara videoPara = ui->videoWidget->getVideoPara();videoPara.videoCore = VideoCore_FFmpeg;ui->videoWidget->setVideoPara(videoPara);VideoPara para = ui->videoWidget->getVideoPara();para.playRepeat = true;ui->videoWidget->setVideoPara(para);connect(ui->videoWidget, SIGNAL(sig_receivePlayStart(int)), this, SLOT(receivePlayStart(int)));connect(ui->videoWidget, SIGNAL(sig_receivePlayFinsh()), this, SLOT(receivePlayFinsh()));
}void frmSpeedPush::initConfig()
{VideoUtil::loadMediaUrl(ui->cboxMediaUrl, AppConfig::SpeedMediaUrl, 0x40);connect(ui->cboxMediaUrl->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtPushUrl->setText(AppConfig::SpeedPushUrl);connect(ui->txtPushUrl, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));VideoUtil::loadSpeed(ui->cboxSpeed);ui->cboxSpeed->setCurrentIndex(ui->cboxSpeed->findData(AppConfig::SpeedPushValue));connect(ui->cboxSpeed, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));ui->ckMuted->setChecked(AppConfig::SpeedPushMuted);connect(ui->ckMuted, SIGNAL(stateChanged(int)), this, SLOT(saveConfig()));if (AppConfig::SpeedPushStart) {on_btnStart_clicked();}
}void frmSpeedPush::saveConfig()
{AppConfig::SpeedMediaUrl = ui->cboxMediaUrl->currentText().trimmed();AppConfig::SpeedPushUrl = ui->txtPushUrl->text().trimmed();AppConfig::SpeedPushValue = ui->cboxSpeed->itemData(ui->cboxSpeed->currentIndex()).toFloat();AppConfig::SpeedPushMuted = ui->ckMuted->isChecked();AppConfig::writeConfig();
}void frmSpeedPush::receivePlayStart(int time)
{VideoThread *thread = ui->videoWidget->getVideoThread();thread->setMuted(AppConfig::SpeedPushMuted);thread->setSpeed(AppConfig::SpeedPushValue);thread->setEncodeSpeed(AppConfig::SpeedPushValue);thread->recordStart(AppConfig::SpeedPushUrl);connect(thread, SIGNAL(receivePosition(qint64)), this, SLOT(receivePosition(qint64)));ui->sliderPosition->setRange(0, thread->getDuration());ui->btnStart->setText("停止推流");ui->cboxSpeed->setEnabled(false);
}void frmSpeedPush::receivePlayFinsh()
{ui->sliderPosition->setRange(0, 0);ui->btnStart->setText("启动推流");ui->cboxSpeed->setEnabled(true);
}void frmSpeedPush::receivePosition(qint64 position)
{ui->sliderPosition->setValue(position);
}void frmSpeedPush::on_btnStart_clicked()
{if (ui->btnStart->text() == "启动推流") {ui->videoWidget->open(AppConfig::SpeedMediaUrl);} else {ui->videoWidget->stop();}
}void frmSpeedPush::on_sliderPosition_clicked()
{int value = ui->sliderPosition->value();on_sliderPosition_sliderMoved(value);
}void frmSpeedPush::on_sliderPosition_sliderMoved(int value)
{ui->videoWidget->setPosition(value);
}void frmSpeedPush::on_ckMuted_stateChanged(int arg1)
{ui->videoWidget->setMuted(ui->ckMuted->isChecked());
}void FFmpegSave::writePacket2(AVPacket *packet)
{//非音视频流不用处理int index = packet->stream_index;if (index != videoIndexOut && index != audioIndexOut) {return;}//转发数据包(可以设置仅仅转发数据包不用继续)if (sendPacket) {emit receivePacket(FFmpegHelper::creatPacket(packet));if (onlySendPacket) {return;}}//封装格式 https://blog.csdn.net/weixin_44520287/article/details/113435440 https://xilixili.net/2018/08/20/ffmpeg-got-raw-h264///测试发现部分文件如果是非编码保存也写入了/可能部分播放器不支持保存后的文件播放/比如安卓上if (index == videoIndexOut) {FFmpegSaveHelper::writeBsf(packet, videoStreamIn, bsfCtx);}if (saveVideoType == SaveVideoType_Stream) {//只需要写入视频数据if (index == videoIndexOut) {file.write((char *)packet->data, packet->size);}} else if (saveVideoType == SaveVideoType_Mp4) {//取出输入输出流的时间基AVStream *streamIn = (index == videoIndexOut ? videoStreamIn : audioStreamIn);AVStream *streamOut = formatCtx->streams[index];AVRational timeBaseIn = streamIn->time_base;AVRational timeBaseOut = streamOut->time_base;//转换时间基准if (index == videoIndexOut) {FFmpegSaveHelper::rescalePacket(packet, timeBaseIn, videoCount, frameRate);FFmpegSaveHelper::rescalePacket(packet, timeBaseIn, timeBaseOut);} else if (index == audioIndexOut) {if (audioEncode) {FFmpegSaveHelper::rescalePacket(packet, audioDuration);} else {FFmpegSaveHelper::rescalePacket(packet, timeBaseIn, timeBaseOut, audioDuration);}}//打印对应的信息方便查看/videoIndexOut/audioIndexOutif (index == -1) {qDebug() << TIMEMS << flag << index << packet->pts << packet->dts << packet->duration;}//倍速调整时间戳if (encodeSpeed != 1) {packet->pts = packet->pts / encodeSpeed;packet->dts = packet->dts / encodeSpeed;}//写入一帧数据/如果用 av_interleaved_write_frame 默认会缓存/可能导致音频越来越慢int result = av_write_frame(formatCtx, packet);if (result < 0) {errorCount++;debug(result, QString("写%1包").arg(index == audioIndexOut ? "音频" : "视频"), "");} else {errorCount = 0;}//推流超过错误次数需要重连if (errorCount >= 5 && saveMode != SaveMode_File) {isOk = false;errorCount = 0;emit receiveSaveError(VideoError_Save);}}
}

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

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

相关文章

Excel求解器使用教程

添加规则求解加载项创建excel文件,点击文件点击选项选择加载项->规则求解加载项->转到选择规则求解加载项->确定求解器所在位置---数据->规划求解在excel文档中填写相关的计算公式,用来求解点击规则求解,填写对应的目标,可变单元和约束,选择求解方法来求解通过…

虚拟机创建教程

虚拟机创建 创建虚拟机的时候,选择自定义,自己来创建虚拟机在虚拟机中,选择创建16.2.X版本的虚拟机,兼容性比较好在创建虚拟机的操作系统时,选择稍后安装操作系统,实测中如果选择其他的在安装过程中会跳过系统安装的部分阶段选择对应的系统和版本选择名称和安装位置,个人…

union 和union all 使用区别

union 和union all 把 查询user表前5条数据查询user表数据从第7条数据开始,查询两条 通过union来把两个sql中的数据合并到一张表中,只查询出一条数据,会把重复的数据去掉 通过union all查询 出现出了两条数据,不会去重

TypeError: Cannot read properties of undefined (reading trim)

运行时提示:TypeError: Cannot read properties of undefined (reading trim) 问题排查: 1、确认trim()属性是否存在,这个是js 去除字符串左右空格,属性是存在的 2、确认this.form.proxy_url是否存在 3、确认确认this.form.proxy_url的值是否为undefined和null 通过排查和打…

vue2 项目执行npm run serve 启动项目卡在24%一直不动

vue模板中添加了信息,应这样写:<div>接口管理</div>

博客园数据备份相关

OpenAPI文档 文档地址,使用前必须先申请权限,应用介绍要详细点。这种方式支持随笔和文章,不支持笔记。 管理后台备份只能在工作日18:00之后、8点之前或周六、周日进行操作,每天只允许备份一次。这种方式仅支持随笔。 使用爬虫 使用接口来模拟网页版的操作,使用 Jsoup 库来…

arduino uno+LCD12864(ST7735S)+蓝牙模块实现贪吃蛇

1.前言: 1.1本实验实现的贪吃蛇能穿越边界,结束游戏的唯一条件是贪吃蛇到达指定长度 1.2本实验所用LCD可能不是LCD12864,LCD12864所用库为u8glib,笔者在词库中并没有找到型号为ST77355的初始化函数,而是在ucglib中找到,其方法为 Ucglib_ST7735_18x128x160_SWSPI ucg(/*sc…