首页 > 系统相关 >Qt/C++实现帧同步播放器/硬解码GPU绘制/超低资源占用/支持8K16K/支持win/linux/mac/嵌入式/国产OS等

Qt/C++实现帧同步播放器/硬解码GPU绘制/超低资源占用/支持8K16K/支持win/linux/mac/嵌入式/国产OS等

时间:2024-12-02 11:43:16浏览次数:5  
标签:widget 同步 Qt SyncLocal win void syncPosition position OS

一、前言

首先泼一盆冷水,在不同的电脑上实现完完全全的帧同步理论上是不可能的,市面上所有号称帧同步的播放器,同一台电脑不同拼接视频可以通过合并成一张图片来绘制实现完完全全的帧同步,不同电脑,受限于网络的延迟,命令交互的时间占用,不同硬件之间的主频偏差等,肯定会有些许的误差,只要误差控制在1帧以内,人的肉眼是完全看不出来的,比如误差5ms,看不出来的。这个和零延迟的推流软件道理一样,不可能零延迟的,只能够做到肉眼分不清的延迟,就已经可以了。

搞帧同步播放核心就两点,第一点保证帧序号一致,第二点保证刷新的时间一致。两者缺一不可,否则无法实现真正的帧同步。序号一致这个搞音视频开发的都能做到,可以先缓存也好,暂停也好,程序底层肯定是知道当前要播放的是第几帧。保证刷新时间一致这个也非常关键,哪怕是在同一台电脑,由于分时多任务操作系统是通过中断来并发执行指令的,指令的传递和最终的绘制都有时间偏差,尤其是在资源占用很多的时候,所以一个技巧就是,等待,等到所有视频帧全部解码完整就差绘制的时候,然后让多个界面同时绘制,这样就能将误差控制在极低极低范围,基本上控制在1帧以内比如5ms。在现在的多任务操作系统中,完全一致肯定是不可能的,一般可能会有1-2个中断的时间差,可能有5-10ms的差,不过没关系,一般25fps也要40ms才有一帧,哪怕是60fps的也要16.7ms一帧,这个误差几乎不影响。

二、效果图

三、相关地址

  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_sync。

四、功能特点

  1. 实时帧同步,本地无缝拼接多个视频。
  2. 支持网络同步,可选主控端和被控端,主控端将本地播放的进度实时同步到被控端。
  3. 网络同步支持组播、广播、单播三种模式,默认组播,既可以跨网段,也可以避免广播数据风暴。
  4. 默认开启自动同步,也可以手动同步和复位同步,手动同步是立即执行一次同步,将第一个视频的进度同步到其他视频文件,复位同步是将所有视频播放进度切换到最开始0的位置。
  5. 支持各种视音频文件,包括但不限于mp4/mov/mkv/rmvb/avi等格式。
  6. 硬解码和GPU绘制,最大化利用硬件资源,支持qsv/cuda/dxva2/d3d11va/vaapi等硬解码。
  7. 极低的CPU占用,8K30fps只占不到1%的CPU,解码和绘制全部交给GPU。
  8. 提供示例按照行列生成多个视频播放窗口,每个窗口可以选择不同的视频文件,在手动同步模式下,可以切换任意一个视频播放进度,会将所有的视频按照这个进度同步。
  9. 自动循环播放视频文件,无缝切换循环播放,看起来非常丝滑。
  10. 支持Qt4/Qt5/Qt6所有版本,支持各种操作系统包括国产OS和嵌入式OS。

五、相关代码

#include "synclocal.h"
#include "qthelper.h"
#include "frmplay.h"

SINGLETON_IMPL(SyncLocal)
QDateTime SyncLocal::SyncTime = QDateTime::currentDateTime().addDays(-1);
SyncLocal::SyncLocal(QObject *parent) : QThread(parent)
{
    isStop = false;
    this->reset();

    syncInterval = 5;
    syncOffset = 15;
    syncSleep = 500;
    updateInterval = 10;
}

SyncLocal::~SyncLocal()
{
    this->stop();
}

void SyncLocal::run()
{
    while (!isStop) {
        this->checkPosition();
        this->checkSync();
        this->checkPause();
        this->updateWidget();

        count++;
        msleep(updateInterval);
        //qDebug() << TIMEMS << "111" << updateInterval << count;
    }

    isStop = false;
    this->reset();
}

void SyncLocal::checkPosition()
{
    //同步间隔0表示不启用/至少要2个窗体才需要同步
    int size = frmPlay::widgets.size();
    if (size < 2 || isSync || isPasue) {
        count = 0;
        return;
    }

    //永远同步到到第一个窗体/处于非播放状态或者暂停状态不用继续
    frmPlay *widget = frmPlay::widgets.first();
    if (!widget->isPlaying() || widget->isPaused()) {
        return;
    }

    //优先执行手动同步指令/-1则同步到第一个窗体/>=0则同步到对应位置
    if (syncPosition >= -1) {
        position = (syncPosition == -1 ? widget->position() : syncPosition);
        count = 0;
        isSync = true;
        qDebug() << TIMEMS << "hand" << position;
        return;
    }

    //同步间隔0表示不启用
    if (syncInterval == 0) {
        count = 0;
        return;
    }

    //计算同步间隔需要循环多少次
    int maxCount = syncInterval * 1000 / updateInterval;
    //到了需要同步的时候执行同步
    if (count < maxCount) {
        return;
    }

    count = 0;
    position = widget->position();

    //刚开始或者快结束先不同步
    if (position < 1000 || qAbs(widget->duration() - position) < 1000) {
        return;
    }

    for (int i = 1; i < size; ++i) {
        offset = position - frmPlay::widgets.at(i)->position();
        qDebug() << TIMEMS << "posi" << position << "\t" << offset;
        if (qAbs(offset) >= syncOffset) {
            isSync = true;
            break;
        }
    }
}

void SyncLocal::checkSync()
{
    //同步标志位为真则先同步
    if (isSync) {
        count = 0;
        isSync = false;
        isPasue = true;
        SyncTime = QDateTime::currentDateTime();
        qDebug() << TIMEMS << "seek" << position;

        //先暂停再执行设置进度
        foreach (frmPlay *widget, frmPlay::widgets) {
            widget->pause();
            widget->seek(position);
        }
    }
}

void SyncLocal::checkPause()
{
    //暂停阶段说明刚才执行过同步/等待一段时间重新播放
    if (isPasue) {
        qint64 time = SyncTime.msecsTo(QDateTime::currentDateTime());
        if (time >= syncSleep) {
            foreach (frmPlay *widget, frmPlay::widgets) {
                widget->next();
            }

            count = 0;
            isPasue = false;
            syncPosition = -2;
            emit receiveSync(offset);
            qDebug() << TIMEMS << "play" << position;
        }
    }
}

void SyncLocal::updateWidget()
{
    //刷新界面用来触发绘制
    foreach (frmPlay *widget, frmPlay::widgets) {
        widget->updateVideo();
    }
}

void SyncLocal::setSyncInterval(int syncInterval)
{
    this->reset();
    this->syncInterval = syncInterval;
}

void SyncLocal::setSyncOffset(int syncOffset)
{
    this->syncOffset = syncOffset;
}

void SyncLocal::setSyncSleep(int syncSleep)
{
    this->syncSleep = syncSleep;
}

void SyncLocal::setUpdateInterval(int updateInterval)
{
    this->updateInterval = updateInterval;
}

void SyncLocal::stop()
{
    if (this->isRunning()) {
        this->isStop = true;
        this->wait();
    }
}

void SyncLocal::reset()
{
    this->count = 0;
    this->isSync = false;
    this->isPasue = false;
    this->syncPosition = -2;
}

//-1则同步到第一个窗体/>=0则同步到对应位置
void SyncLocal::sync(qint64 position)
{
    //至少要两个窗体才能同步/处于暂停阶段说明上一个同步还没执行完成
    if (frmPlay::widgets.size() >= 2 && !isPasue && syncPosition == -2) {
        this->syncPosition = position;
    }
}

标签:widget,同步,Qt,SyncLocal,win,void,syncPosition,position,OS
From: https://www.cnblogs.com/feiyangqingyun/p/18581553

相关文章

  • C语言qosrt排序问题
    在MDK中使用qsort排序时发现会卡死:#include<stdio.h>#include<stdlib.h>inttestdata[200]={0,6,3};inttestcnt;intcompare(constvoid*a,constvoid*b){testcnt++;if(*(int*)a<*(int*)b)return-1;elseif(*(int*)a>*(int......
  • 在 CentOS 上安装 Docker:构建容器化环境全攻略
    一、引言在当今的软件开发与运维领域,Docker无疑是一颗璀璨的明星。它以轻量级虚拟化的卓越特性,为应用程序的打包、分发和管理开辟了崭新的高效便捷之路。无论是开发环境的快速搭建,还是生产环境的稳定部署,Docker都展现出了无与伦比的优势。本文将带领您深入探索在CentOS系......
  • Centos7.9 安装mysql8.4.3-lts 记录过程
    1、下载并上传mysqlrpm安装包tar-xvfmysql-8.4.3-1.el7.x86_64.rpm-bundle.tar2、按照如下顺序执行安装;如果有依赖缺少,执行yum-yinstall依赖名称rpm-ivhmysql-community-common-8.4.3-1.el7.x86_64.rpmrpm-ivhmysql-community-client-plugins-8.4.3-1.el7.x86_64......
  • YOLOv8改进 | 损失函数篇:SlideLoss与FocalLoss的细节优化与应用【YOLOv8】
    本专栏专为AI视觉领域的爱好者和从业者打造。涵盖分类、检测、分割、追踪等多项技术,带你从入门到精通!后续更有实战项目,助你轻松应对面试挑战!立即订阅,开启你的YOLOv8之旅!专栏订阅地址:https://blog.csdn.net/mrdeam/category_12804295.html文章目录YOLOv8改进|损失函数......
  • axios相比原生ajax的优点有哪些呢?
    Axios比原生AJAX在前端开发中有很多优势:更简洁易用的API:Axios提供了更简洁、更易于使用的API,使得发送HTTP请求更加方便。例如,设置请求头、处理响应数据等操作都更加直观。原生AJAX需要手动处理XMLHttpRequest对象的各种状态和事件,比较繁琐。Promise支持:Axi......
  • Windows更改远程桌面端口
    为了远程安全,默认在3389改为别的端口。本示例为3389改为533891、步骤:打开“开始→运行”,输入“regedit”,打开注册表,进入以下路径:[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\Wds\rdpwd\Tds\tcp]修改PortNamber修改成所希望的端口即可,例如53389(......
  • OSI七层模型指的是哪七层?
    OSI七层模型指的是:物理层(PhysicalLayer):传输比特流,关注的是物理连接的建立和维护,例如网线、光纤、无线电波等。它定义了电压、电流、线缆规格等物理特性。数据链路层(DataLinkLayer):在物理层的基础上,建立相邻节点之间的连接,并进行错误检测和纠正,确保数据的可靠......
  • axios为什么能在浏览器中环境运行又能在node中环境运行?
    Axios之所以能在浏览器和Node.js环境中运行,是因为它使用了不同的适配器(adapters)来发送HTTP请求。它能够根据运行环境自动切换适配器。在浏览器中:Axios使用XMLHttpRequest(XHR)对象发送请求。这是浏览器内置的API,用于与服务器进行通信。在Node.js中:Axios使用http或ht......
  • axios如何一次发送多个并发请求?
    在前端开发中,Axios提供了几种方法来发送多个并发请求:Promise.all:这是最常用的方法,它接受一个Promise数组作为参数,并返回一个新的Promise。当所有传入的Promise都resolve时,新的Promise才会resolve,并将所有结果以数组形式返回。如果任何一个Promisereject,新的Prom......
  • 怎样去除iOS和Android中的输入URL地址的控件条呢?
    在iOS和Android中完全去除浏览器自带的地址栏是不可能的,除非你开发的是一个独立的应用程序,而不是在浏览器中运行的网页。如果你的目标是在Web应用中提供更沉浸式的体验,隐藏地址栏可以实现,但用户仍然可以通过下拉或其他操作重新显示地址栏。以下是一些方法可以尝试在你的......