关于鸣潮启动器450张图片杂谈—从代码分析为何使用帧动画

news2024/12/22 22:29:11

关于鸣潮启动器450张图片杂谈—从代码分析为何使用帧动画

前言

在鸣潮启动器的目录下

Wuthering Waves\kr_game_cache\animate_bg\99de27ae82e3c370286fba14c4fcb699

打开该目录发现有450张图片,不难看出启动器的背景动画是由这450张图片不断切换实现的

在这里插入图片描述

qt框架

在这里插入图片描述

从动态库能很明显的看出启动器是用qt5写的,而使用qt实现动态背景图的方式主要有以下几种:1.帧动画,也是官方启动器选择的方式 2.使用ffmpeg等开源音视频解码库对视频文件进行解码,3.使用外部解码软件,4.使用gif动图

帧动画

首先来看第一个解决方案,也是最简单,效果也不错的方案,以下是代码,非常简单一共也就十几行,直接一个定时器不断切换背景图片路径就行了

static int index = 0;
AnimatedBackground::AnimatedBackground(QWidget *parent)
    : QWidget{parent}
{
    this->setFixedSize(1280,760);
    m_timer = new QTimer(this);
    connect(m_timer,&QTimer::timeout,this,[&]()mutable{
        index%=450;
        index++;
        QString path = "D:\\Wuthering Waves\\kr_game_cache\\animate_bg\\99de27ae82e3c370286fba14c4fcb699\\home_"+QString::number(index)+".jpg";
        m_currentBackground.load(path);
        update();
    });
    m_timer->start(1000/33);
}

void AnimatedBackground::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawPixmap(rect(),m_currentBackground);
}

来看效果:
在这里插入图片描述

使用ffmpeg软解码视频

qt框架自己并没有附带解码器,要实现播放视频需要解码库或者解码软件,这里使用开源的ffmpeg对视频进行解码

首先要将ffmpeg添加到自己的项目:

  • 下载源码并编译(略)
  • 将编译好的库文件和头文件添加到项目(略)
  • 编写Cmake/qmake 文件将库链接到项目(略)

实现方案:

由于解码视频是需要时间的,如果等视频流所有的帧都解码完才显示会有几秒左右的延迟,要想实现第一个方案打开就有动画效果,需要解码和显示同时进行

需要一个队列对帧数据进行缓存,所以选择基于生产者-消费者的设计模式的线程模型 ,以下是原理图:

在这里插入图片描述
涉及多线程的的话,当然需要锁啦 先封装个锁:
semaphore.h

#ifndef SEMAPHORE_H
#define SEMAPHORE_H

#include <atomic>
#include <condition_variable>
#include <mutex>

class Semaphore
{
public:
    explicit Semaphore(int i = 0) {
        m_semaphore.store(i < 0 ? 0 : i);
    }

    Semaphore(const Semaphore &) = delete;
    Semaphore& operator=(const Semaphore &) = delete;

    void acquire(int i = 1) {
        if (i <= 0) return;

        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_semaphore.load() < i) {
            m_conditionVar.wait(lock);
        }
        m_semaphore.fetch_sub(i);
    }

    bool tryAcquire(int i = 1) {
        if (i <= 0) return false;

        if (m_semaphore.load() >= i) {
            m_semaphore.fetch_sub(i);
            return true;
        } else return false;
    }

    void release(int i = 1) {
        if (i <= 0) return;

        m_semaphore.fetch_add(i);
        m_conditionVar.notify_one();
    }

    int available() const {
        return m_semaphore.load();
    }

private:
    std::condition_variable m_conditionVar;
    std::atomic_int m_semaphore;
    std::mutex m_mutex;
};

#endif

实现缓存队列bufferqueue.h

#ifndef BUFFERQUEUE_H
#define BUFFERQUEUE_H

#ifdef DEBUG_OUTPUT
#include <iostream>
#endif

#include "semaphore.h"
#include <vector>

template <class T> class BufferQueue
{
public:
    BufferQueue(int bufferSize = 100) {
        setBufferSize(bufferSize);
    }

    ~BufferQueue() {
        init();
        std::vector<T>().swap(m_bufferQueue);
    }

    void setBufferSize(int bufferSize) {
        m_bufferSize = bufferSize;
        m_bufferQueue = std::vector<T>(bufferSize);
        m_useableSpace.acquire(m_useableSpace.available());
        m_freeSpace.release(m_bufferSize - m_freeSpace.available());
        m_front = m_rear = 0;
    }

    void enqueue(const T &element) {
#ifdef DEBUG_OUTPUT
        std::cout << "[freespace " << m_freeSpace.available()
                  << "] --- [useablespace " << m_useableSpace.available() << "]" << std::endl;
#endif
        m_freeSpace.acquire();
        m_bufferQueue[m_front++ % m_bufferSize] = element;
        m_useableSpace.release();
    }

    T dequeue() {
#ifdef DEBUG_OUTPUT
        std::cout << "[freespace " << m_freeSpace.available()
                  << "] --- [useablespace " << m_useableSpace.available() << "]" << std::endl;
#endif
        m_useableSpace.acquire();
        T element = m_bufferQueue[m_rear++ % m_bufferSize];
        m_freeSpace.release();

        return element;
    }

    /**
     * @brief tryDequeue
     * @note 尝试获取一个元素,并且在失败时不会阻塞调用线程
     * @return 成功返回对应T元素,失败返回默认构造的T元素
     */
    T tryDequeue() {
        T element;
        bool success = m_useableSpace.tryAcquire();
        if (success) {
            element = m_bufferQueue[m_rear++ % m_bufferSize];
            m_freeSpace.release();
        }

        return element;
    }

    void init() {
        m_useableSpace.acquire(m_useableSpace.available());
        m_freeSpace.release(m_bufferSize - m_freeSpace.available());
        m_front.store(0);
        m_rear.store(0);
    }

private:
    //         -1               +1
    //   [free space] -> [useable space]
    Semaphore m_freeSpace;
    Semaphore m_useableSpace;
    std::atomic_int m_rear;
    std::atomic_int m_front;
    std::vector<T> m_bufferQueue;
    int m_bufferSize;
};

#endif

封装视频解码类:

videodecoder.h

class VideoDecoder : public QThread
{
    Q_OBJECT

public:
    VideoDecoder(QObject *parent = nullptr);
    ~VideoDecoder();

    void stop();
    void open(const QString &filename);

    int fps() const { return m_fps; }
    int width() const { return m_width; }
    int height() const { return m_height; }
    QImage currentFrame();

signals:
    void resolved();
    void finish();

protected:
    void run();

private:
    void demuxing_decoding();

    bool m_runnable = true;
    QMutex m_mutex;
    QString m_filename;
    BufferQueue<QImage> m_frameQueue;
    int m_fps, m_width, m_height;
};

videodecoder.cpp

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#include <QApplication>
#include <QHBoxLayout>
#include <QMimeData>
#include <QPushButton>
#include <QPainter>
#include <QTimer>
#include <QDebug>

VideoDecoder::VideoDecoder(QObject *parent)
    : QThread (parent)
{

}

VideoDecoder::~VideoDecoder()
{
    stop();
}

void VideoDecoder::stop()
{
    //必须先重置信号量
    m_frameQueue.init();
    m_runnable = false;
    wait();
}

void VideoDecoder::open(const QString &filename)
{
    stop();

    m_mutex.lock();
    m_filename = filename;
    m_runnable = true;
    m_mutex.unlock();

    start();
}

QImage VideoDecoder::currentFrame()
{
    static QImage image = QImage();
    image = m_frameQueue.tryDequeue();

    return image;
}

void VideoDecoder::run()
{
    demuxing_decoding();
}

void VideoDecoder::demuxing_decoding()
{
    AVFormatContext *formatContext = nullptr;
    AVCodecContext *codecContext = nullptr;
    AVCodec *videoDecoder = nullptr;
    AVStream *videoStream = nullptr;
    int videoIndex = -1;

    //打开输入文件,并分配格式上下文
    avformat_open_input(&formatContext, m_filename.toStdString().c_str(), nullptr, nullptr);
    avformat_find_stream_info(formatContext, nullptr);

    //找到视频流的索引
    videoIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

    if (videoIndex < 0) {
        qDebug() << "Has Error: line =" << __LINE__;
        return;
    }
    videoStream = formatContext->streams[videoIndex];

    if (!videoStream) {
        qDebug() << "Has Error: line =" << __LINE__;
        return;
    }
    videoDecoder = avcodec_find_decoder(videoStream->codecpar->codec_id);

    if (!videoDecoder) {
        qDebug() << "Has Error: line =" << __LINE__;
        return;
    }
    codecContext = avcodec_alloc_context3(videoDecoder);

    if (!codecContext) {
        qDebug() << "Has Error: line =" << __LINE__;
        return;
    }
    avcodec_parameters_to_context(codecContext, videoStream->codecpar);

    if (!codecContext) {
        qDebug() << "Has Error: line =" << __LINE__;
        return;
    }
    avcodec_open2(codecContext, videoDecoder, nullptr);

    //打印相关信息
    av_dump_format(formatContext, 0, "format", 0);
    fflush(stderr);

    m_fps = videoStream->avg_frame_rate.num / videoStream->avg_frame_rate.den;
    m_width = codecContext->width;
    m_height = codecContext->height;

    emit resolved();

    SwsContext *swsContext = sws_getContext(m_width, m_height, codecContext->pix_fmt, m_width, m_height, AV_PIX_FMT_RGB24,
                                            SWS_BILINEAR, nullptr, nullptr, nullptr);
    //分配并初始化一个临时的帧和包
    AVPacket *packet = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    packet->data = nullptr;
    packet->size = 0;

    //读取下一帧
    while (m_runnable && av_read_frame(formatContext, packet) >= 0) {

        if (packet->stream_index == videoIndex) {
            //发送给解码器
            int ret = avcodec_send_packet(codecContext, packet);

            while (ret >= 0) {
                //从解码器接收解码后的帧
                ret = avcodec_receive_frame(codecContext, frame);

                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
                else if (ret < 0) goto Run_End;

                int dst_linesize[4];
                uint8_t *dst_data[4];
                av_image_alloc(dst_data, dst_linesize, m_width, m_height, AV_PIX_FMT_RGB24, 1);
                sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, dst_data, dst_linesize);
                QImage image = QImage(dst_data[0], m_width, m_height, QImage::Format_RGB888).copy();
                av_freep(&dst_data[0]);

                m_frameQueue.enqueue(image);

                av_frame_unref(frame);
            }
        }

        av_packet_unref(packet);
    }

Run_End:
    m_fps = m_width = m_height = 0;

    if (frame) av_frame_free(&frame);
    if (packet) av_packet_free(&packet);
    if (swsContext) sws_freeContext(swsContext);
    if (codecContext) avcodec_free_context(&codecContext);
    if (formatContext) avformat_close_input(&formatContext);
}

显示:

AnimatedBackground::AnimatedBackground(QWidget *parent)
    : QWidget(parent)
{
    this->setFixedSize(1280,760);
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, [this](){
        m_currentFrame = m_decoder->currentFrame();
        update();
    });
    m_decoder = new VideoDecoder(this);
    connect(m_decoder, &VideoDecoder::resolved, this, [this]() {
        m_timer->start(1000 / m_decoder->fps());
    });
    m_decoder->open(":/video/1.mp4");
}
void AnimatedBackground::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    if (!m_currentFrame.isNull())
       painter.drawImage(rect(), m_currentFrame);
}

效果和第一个方案是一模一样的这里就不做展示了,总之,软解码视频方案比帧动画多了几百行代码不说,ffmpeg这个库也是比较难写的,由于是c语言风格,写之前还需要一定的时间费脑子去阅读文档,实现效果和帧动画没什么区别,内存上也没减少多少虽然图片经压缩视频后大小减小,但难蹦的是库文件的大小比450张图片还大,总之就是十分吃力不讨好。

在这里插入图片描述

使用外部解码软件

qt的QMediaPlayer可以使用外部的解码器进行解码,从而实现视频播放,但是不能保证用户是否下载了解码器,要绑定安装的话是十分流氓的行为,而且不开源的软件商用也是要钱的,直接用也是会有商业纠纷,这种方案不必多说

播放gif

效果差,糊的一批的同时帧率还低

总之

​ 综合看下来,帧动画是最优的解决方案,简单且高效,软解码不说前期可能遇到的环境问题不说,代码也是多了几百行,给自己多加了一两天的工作量,内存空间上不但没有因为图片压缩成视频减小空间,反而因为添加动态库比原先还大,是十分吃力且不讨好的行为。代码的最终目的是为了服务于产品的,不管哪种代码,你只要能达到最终的效果,那就是好代码

另外分享一个有趣的:windows的开机动画也是用图标字体一帧一帧拼起来的

在这里插入图片描述

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

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

相关文章

小白公式量化--用Python指标公式模块做量化策略研究

《小白公式量化系统》是纯Python开发的&#xff0c;因此我们可以借用《小白公式量化系统》的金融模块&#xff0c;来实现自己的Python量化框架&#xff0c;以及用Python实现量化研究、选股、人工智能机器学习&#xff0c;以及打造自己的行情软件和量化平台。我们后面文章讲介绍…

【大模型从入门到精通27】开源库框架LangChain 语义搜索:高级检索策略1

这里写目录标题 语义搜索&#xff1a;高级检索策略简介最大边际相关性 (MMR)自我查询检索上下文压缩增强语义搜索的高级文档检索技术引言设置环境导入必要的库初始化向量数据库以进行相似性搜索导入 Chroma 向量存储库和 OpenAI 嵌入 填充向量数据库定义文本列表以填充数据库创…

基于YOLOv8-pose的手部关键点检测(3)- 实现实时手部关键点检测

目录 前言 1.扩大检测框区域 2.先检测手部&#xff0c;后检测手部关键点 3.正面视角检测 4.侧面视角检测 5.摄像头视角检测 6.遮挡视角检测 7.结论 前言 使用YOLOv8-m对图像进行手部检测&#xff0c;然后扩大检测框区域&#xff0c;并对该区域使用YOLOv8-s-pose使用关键…

达梦数据库版本介绍

达梦数据库根据不同用户的不同需求&#xff0c;提供了三种版本的数据库&#xff1a;DM Standard Edition 标准版、DM Enterprise Edition 企业版、DM Security Edition 安全版。那么这三种版本有什么区别&#xff0c;我们该如何选择合适的版本&#xff1f;下面先介绍三种版本各…

【笔记】泰山派环境配置遇到E: Unable to locate package repo

答案来自通义千问&#xff0c;解决了我的问题&#xff0c;做一些记录 你尝试在Ubuntu或Debian系统上使用apt命令安装repo工具&#xff0c;但是遇到了问题&#xff0c;因为repo不是直接在软件源中作为一个独立的包提供的。repo是Google的一个Git仓库管理工具&#xff0c;通常用…

【系统架构设计】系统性能评价(二)

【系统架构设计】系统性能评价&#xff08;二&#xff09; 性能指标性能计算性能设计阿姆达尔解决方案负载均衡 性能评估基准测试程序Web 服务器的性能评估 性能指标 性能计算 性能设计 阿姆达尔解决方案 阿姆达尔定律&#xff1a; 系统中对某部件采用某种更快的执行方式&a…

Centos8和stream 9防火墙基本使用

查发行版&#xff1a; [rootlocalhost nps-0.26.10]# cat /etc/redhat-releaseCentOS Stream release 9查看防火墙状态 systemctl status firewalld firewall-cmd --state开启/关闭/重启防火墙 systemctl start firewalld[rootlocalhost conf]# systemctl start firewalld …

Eureka原理与实践:深入探索微服务架构的核心组件

在微服务架构日益盛行的今天&#xff0c;服务之间的注册与发现成为了保证系统高可用性和灵活性的关键。Eureka&#xff0c;作为Netflix开源的服务注册与发现框架&#xff0c;凭借其简单、健壮的特性&#xff0c;在微服务领域占据了举足轻重的地位。本文将深入剖析Eureka的原理&…

ubuntu 24.04 安装 Nvidia 显卡驱动 + CUDA + cuDNN,配置 AI 深度学习训练环境,简单易懂,一看就会!

ubuntu 24.04 安装 Nvidia 显卡驱动 CUDA cuDNN&#xff0c;配置 AI 深度学习训练环境&#xff0c;简单易懂&#xff0c;一看就会&#xff01; 1.查看本机显卡型号 lspci | grep -i nvidia输出如下&#xff1a; 01:00.0 3D controller: NVIDIA Corporation GM108M [GeForc…

焦虑迷雾中的幻觉挑战?专家教你如何拨云见日!

在这个快节奏、高压力的时代&#xff0c;焦虑症已成为许多人难以言说的秘密。它不仅让人心情沉重&#xff0c;更有可能在极端情况下引发幻觉&#xff0c;仿佛置身于一个光怪陆离、难以分辨真假的世界。面对这样的困境&#xff0c;我们该如何自救&#xff0c;如何找到那束穿透焦…

ansible【自动化配置】(thirty day)

回顾 1、mysql和python &#xff08;1&#xff09;不需要执行mysql_ssl_rsa_setup &#xff08;2&#xff09;Change_master_to.不需要get public key 2、可以使用pymysql非交互的管理mysql &#xff08;1&#xff09;connpymysql.connect(host,user,password,database,prot) …

OpenCV图像处理——积分图像计算(C++/Python)

概述 积分图像是一种高效的图像处理技术&#xff0c;最初由Crow在1984年提出&#xff0c;目的是为了提高多尺度透视投影的渲染速度。它通过构建一个积分图&#xff0c;使得图像中任意矩形区域的像素和能够在常数时间内快速计算出来&#xff0c;极大地减少了在图像模糊、边缘提…

WARNING: There was an error checking the latest version of pip. 解决方案

WARNING: There was an error checking the latest version of pip. 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开…

【单片机】51单片机入门教程(二):定时器的模式详解与中断应用实例

文章目录 51单片机定时器教程:模式详解与中断应用实例1. 介绍2. 51单片机定时器/计数器概述3. 定时器控制寄存器与中断入口4. 模式0:13位定时器/计数器5. 模式1:16位定时器/计数器6. 模式2:8位自动重装载定时器/计数器7. 模式3:分割两个独立的8位定时器/计数器8. 总结51单…

Vue.js入门系列(十):深入理解Vue指令及自定义指令的使用

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

【技术前沿】MetaGPT入门安装部署——用多个大语言模型解决任务!一键安装,只需填写OpenAI API

项目简介 MetaGPT 是一个多智能体框架&#xff0c;旨在构建全球首家 “AI 软件公司”。该项目通过为 GPT 分配不同的角色&#xff0c;模拟产品经理、架构师、工程师等职业&#xff0c;协同完成复杂的软件开发任务。MetaGPT 将一个简单的需求转化为完整的软件开发流程&#xff…

点接触导致Fluent Meshing网格划分出错的处理

问题概述 在CFD建模中&#xff0c;几何处理有时会出现两个面是单点接触的情形。 举例如下图所示&#xff0c;在球形计算域内部&#xff0c;存在分别用黄色和粉红色标记的内部悬浮面&#xff0c;两者接触区域为一个点&#xff0c;而非一条曲线。 上述几何模型可正确的导入Flue…

从零搭建xxl-job(四):xxljob进行一些性能优化

之前的代码这部分并没有补充完毕&#xff0c;假如调度中心如果判断有定时任务要执行了&#xff0c;该怎么远程通知给执行定时任务的程序呢&#xff1f;当定时任务要把自己的信息发送给调度中心时&#xff0c;是通过一个RegistryParam对象发送的。该对象内部封装了定时任务相关的…

鸿蒙自定义Tab,可居左显示

最近写鸿蒙项目时&#xff0c;需要用到类似Android的TabLayout控件&#xff0c;鸿蒙官方也有提供类似实现的组件Tabs。但是官方Tabs组件&#xff0c;实在有点鸡肋&#xff0c;首先 TabContent和 TabBar是绑定在一起的放在Tabs里面的&#xff0c;如果UI是TabBar的背景是一个整体…

可视化大屏入口界面,炫酷科技又不失简洁时尚。

可视化大屏界面&#xff0c;大家见到很多了&#xff0c;当可视化大屏是多个系统的融合&#xff0c;而且彼此又相互独立&#xff0c;就需要设计一个入口页面&#xff0c;便于分流客户&#xff0c;这次我给大家分享一批。 设计可视化大屏入口界面时&#xff0c;可以结合炫酷科技…