一、前言

首先泼一盆冷水,在不同的电脑上实现完完全全的帧同步理论上是不可能的,市面上所有号称帧同步的播放器,同一台电脑不同拼接视频可以通过合并成一张图片来绘制实现完完全全的帧同步,不同电脑,受限于网络的延迟,命令交互的时间占用,不同硬件之间的主频偏差等,肯定会有些许的误差,只要误差控制在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;
}
}

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

  1. HTML5实践之歌词同步播放器

    歌曲播放我们会发现他的兼容性不是很好,譬如IE上能播放的flash播放器,再firfox或者chrome上就不是很好的应用了,因为有插件的阻碍!HTML5的出现让这一切成为了可能,但是播放器虽然播放了 ...

  2. 基于Qt Phonon模块实现音乐播放器

    这次使用Qt实现的是一个本地音乐播放器,可以播放下载在计算机本地的音乐,提供了添加歌曲,歌曲列表,清空列表的功能.默认歌曲列表循环播放.音乐播放的实现主要依赖的是Qt 的多媒体框架phonon.该音乐 ...

  3. VLC和Qt结合编写流媒体rtsp播放器

            VLC播放器是一款功能强大且小巧的播放器,它支持多种多样的音视频格式,比如MPEG1,2以及mp3等等.并且通过Qt和VLC的结合能让每个开发者写出自己的视频流媒体播放器.     Q ...

  4. Android平台RTMP/RTSP播放器开发系列--解码和绘制

    本文主要抛砖引玉,粗略介绍下Android平台RTMP/RTSP播放器中解码和绘制相关的部分(Github). 解码 提到解码,大家都知道软硬解,甚至一些公司觉得硬解码已经足够通用,慢慢抛弃软解了,如 ...

  5. 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  6. 驳Linux不娱乐 堪比Win平台中十款播放器

    播放器在我们日常生活中扮演着非常重要的角色,在Windows操作系统中,播放器被应用的非常广泛,不但我们可以听音乐,甚至还可以听广播,制作铃声,下载音乐等等.而在Linux发行版中,缺少娱乐性一直性W ...

  7. 零基础读懂视频播放器控制原理——ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  8. 视频播放器控制原理:ffmpeg之ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  9. libEasyPlayer RTSP windows播放器SDK API接口设计说明

    概述 libEasyPlayer实现对RTSP直播流进行实时采集和解码显示,稳定,高效,低延时:解码可采用intel硬件解码和软件解码两种方式,能实时进行录像和快照抓图,OSD叠加等功能. API接口 ...

  10. 基于FFMPEG的跨平台播放器实现(二)

    基于FFMPEG的跨平台播放器实现(二) 上一节讲到了在Android平台下采用FFmpeg+surface组合打造播放器的方法,这一节讲一下Windows平台FFmpeg + D3D.Linux平台 ...

随机推荐

  1. 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(3)

    1.问题描述: compatibleSdkVersion升级到5.0.0(12)之后,调用坐标系转换API:map.convertCoordinate(mapCommon.CoordinateType ...

  2. Power BI 通过输入数据新建表后重新进入编辑状态

    在使用Power BI时,有时候我们会直接通过输入数据构建一些简单的表,但是构建好后我们可能还需要对表格进行增删改的操作,这时候我们需要怎么才会恢复到表格的编辑状态呢?其实很简单,我们回到PQ里面,双 ...

  3. NOIP2015 提高组 子串

    NOIP2015 提高组 子串 感觉是最长公共子序列模型的变式. 容易想到记 \(f[i][j][k]\) 表示 \(A\) 走到了第 \(i\) 位,\(B\) 匹配上了 \(1 \sim j\), ...

  4. Qt Creator快捷键记录

    Ctrl + K:代码格式化(先选中要格式化的代码) F4:在头文件和源文件之间切换 Ctrl+/:注释和取消注释 F2:在声明和实现之间切换 Ctrl+K:打开定位器(locator),搜索文件名 ...

  5. UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态

    我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来注册我们玩家的快捷栏. 1.Types.h:定于ShortcutContainer类,定义快捷栏的单个容器结构 ...

  6. rpmbuild命令RPM包制作kafka示例SPEC

    kafka的自定义安装路径RPM包制作 # 编写SPEC cat kafka.spec BuildArch: noarch Name: kafka_2.11 Version: 2.1.0 Releas ...

  7. RAG三件套运行的新选择 - GPUStack

    GPUStack 是一个开源的大模型即服务平台,可以高效整合并利用 Nvidia.Apple Metal.华为昇腾和摩尔线程等各种异构的 GPU/NPU 资源,提供本地私有部署大模型解决方案. GPU ...

  8. Modbus调试、Modbus Slave、ModScan、Modbus Ploll、串口调试

    记录一下昨天调试Modbus调试. 上位机往下位机发送modbus指令.发送过去之后没有反应.后来才调试出来原来是下位机错一个位. 调试过程:用modScan 往modbus slave 发送modb ...

  9. The 2024 ICPC Asia East Continent Online Contest (II) K.Match

    题面 K.Match 给定长度为 \(n\) 的两个序列 \(a\) 和 \(b\),当且仅当 \(a_i ⊕ b_j ≥ k\) 时,\(a_i\) 与 \(b_j\) 连一条双向边,其中 \(⊕\ ...

  10. CommonsCollections7(基于ysoserial)

    环境准备 JDK1.8(8u421)我以本地的JDK8版本为准.commons-collections(3.x 4.x均可这里使用3.2版本) cc3.2: <dependency> &l ...