播放器开发(四):多线程解复用与解码模块实现

news2024/9/22 15:43:33

学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】

前言

根据第一章内容,我们首先可以先把解复用和解码模块完成,其中需要使用到多线程以及队列,还需要使用FFmpeg进行解复用和解码动作的实现。

创建BaseQueue基类

BaseQueue.h

#include <condition_variable>
#include <mutex>
#include <queue>

using namespace std;

template<class T>
class BaseQueue {
public:
    /**
     * 唤醒所有等待线程,设置异常标识为1
     */
    void abort() {
        m_abort = 1;
        m_cond.notify_all();
    }
    /**
     * push进m_queue
     *
     * @param val 需要push的值
     * @return  1是成功 or -1是m_abort==1可能有异常
     */
    int push(T val) {
        lock_guard<mutex> lock(m_mutex);
        if (m_abort == 1) {
            return -1;
        }
        m_queue.push(val);
        m_cond.notify_one();
        return 0;
    }
    /**
     * 从基类 front 到 val 并执行基类std::queue.pop
     *
     * @param val 存放front值的地址引用
     * @param timeout 持有锁的上限时间(ms)
     * @return 1是成功 or -1是m_abort==1可能有异常 or 是m_queue为空
     */
    int pop(T &val, int timeout = 0) {
        unique_lock<mutex> lock(m_mutex);
        if (m_queue.empty()) {
            m_cond.wait_for(lock, chrono::microseconds(timeout), [this] {
                return !m_queue.empty() | m_abort;
            });
        }
        if (m_abort == 1) {
            return -1;
        }
        if (m_queue.empty()) {
            return -2;
        }
        // there is address reference
        val = m_queue.front();
        m_queue.pop();
        return 0;
    }
    /**
     * 从基类std::queue.front 获取值保存到引用地址val
     *
     * @param val 存放front值的地址引用
     * @return 1是成功 or -1是m_abort==1可能有异常 or 是m_queue为空
     */
    int front(T &val) {
        lock_guard<mutex> lock(m_mutex);
        if (m_abort == 1) {
            return -1;
        }
        if (m_queue.empty()) {
            return -2;
        }
        val = m_queue.front();
        return 0;
    }
    int size() {
        lock_guard<mutex> lock(m_mutex);
        return m_queue.size();
    }

private:
    int m_abort = 0;// 是否中止
    mutex m_mutex;  // 锁
    condition_variable m_cond;
    queue<T> m_queue;
};

创建AVPacketQueue包队列类和AVFrameQueue帧队列类

在AVPacketQueue和AVFrameQueue中分别实现模版BaseQueue基类,并且可以添加一些错误的信息打印。


我们可以新建一个头文件FFmpegHeader.h用来存放导入ffmpeg的一些代码,后面用的时候导入这个文件就可以了

FFmpegHeader.h

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avassert.h"
#include "libavutil/ffversion.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"

#ifdef ffmpegdevice
#include "libavdevice/avdevice.h"
#endif
}

#include "qdatetime.h"
#pragma execution_character_set("utf-8")

#define TIMEMS qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))
#define TIME qPrintable(QTime::currentTime().toString("HH:mm:ss"))
#define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define QTIME qPrintable(QTime::currentTime().toString("HH-mm-ss"))
#define DATETIME qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))
#define STRDATETIME qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"))
#define STRDATETIMEMS qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss-zzz"))

AVPacketQueue

//AVPacketQueue.h
#include "BaseQueue.h"
#include "FFmpegHeader.h"
#include <cstdio>

class AVPacketQueue {
public:
    ~AVPacketQueue();
    int push(AVPacket *val);
    AVPacket *pop(int timeout);
    void release();
    int size();

private:
    BaseQueue<AVPacket *> m_queue;
};



//AVPacketQueue.cpp
#include "AVPacketQueue.h"

AVPacketQueue::~AVPacketQueue() {
    release();
    m_queue.abort();
}

int AVPacketQueue::push(AVPacket *val) {
    AVPacket *tmp_pkt = av_packet_alloc();
    av_packet_move_ref(tmp_pkt, val);
    return m_queue.push(tmp_pkt);
}
AVPacket *AVPacketQueue::pop(int timeout) {
    AVPacket *tmp_pkt = nullptr;
    int ret = m_queue.pop(tmp_pkt, timeout);
    if (ret == -1) {
        printf("AVPacketQueue::pop -->> m_abort==1可能有异常");
    }
    if (ret == -2) {
        printf("AVPacketQueue::pop -->> 队列为空");
    }
    return tmp_pkt;
}
void AVPacketQueue::release() {
    while (true) {
        AVPacket *pkt = nullptr;
        int ret = m_queue.pop(pkt, 1);
        if (ret < 0) {
            break;
        } else {
            av_packet_free(&pkt);
            continue;
        }
    }
}
int AVPacketQueue::size() {
    return m_queue.size();
}

AVFrameQueue

//AVFrameQueue.h
#include "BaseQueue.h"
#include "FFmpegHeader.h"
#include <cstdio>


class AVFrameQueue {
public:
    ~AVFrameQueue();
    int push(AVFrame *val);
    AVFrame *pop(int timeout);
    void release();
    int size();

private:
    BaseQueue<AVFrame *> m_queue;
};



//AVFrameQueue.cpp
#include "AVFrameQueue.h"
AVFrameQueue::~AVFrameQueue() {
    release();
    m_queue.abort();
}

int AVFrameQueue::push(AVFrame *val) {
    AVFrame *tmp_frame = av_frame_alloc();
    av_frame_move_ref(tmp_frame, val);
    return m_queue.push(tmp_frame);
}
AVFrame *AVFrameQueue::pop(int timeout) {
    AVFrame *tmp_frame = nullptr;
    int ret = m_queue.pop(tmp_frame, timeout);
    if (ret == -1) {
        printf("AVFrameQueue::pop -->> m_abort==1可能有异常");
    }
    if (ret == -2) {
        printf("AVFrameQueue::pop -->> 队列为空");
    }
    return tmp_frame;
}
void AVFrameQueue::release() {
    while (true) {
        AVFrame *pkt = nullptr;
        int ret = m_queue.pop(pkt, 1);
        if (ret < 0) {
            break;
        } else {
            av_frame_free(&pkt);
            continue;
        }
    }
}
int AVFrameQueue::size() {
    return m_queue.size();
}

创建BaseThread抽象类

#include <QThread>

/**
 * 因为我们后面可能需要用到qt信号传递是否暂停,所以使用QThread
 */
class BaseThread : public QThread {
    Q_OBJECT
public:
    // 初始化
    virtual int init() = 0;

    // 创建线程 开始run工作
    virtual int start() = 0;

    // 停止线程 释放资源
    virtual void stop() {
        isStopped = true;
        if (m_thread) {
            if (m_thread->isRunning()) {
                m_thread->wait(-1);
            }
            delete m_thread;
            m_thread = nullptr;
        }
    };

protected:
    bool isStopped = true; // 是否已经停止 停止时退出线程
    bool isPlaying = false;// 是否正在播放
    bool isPause = false;  // 是否暂停
    QThread *m_thread = nullptr;
};

创建DemuxThread解复用线程模块和DecodeThread解码线程模块

DemuxThread解复用线程:

1、打开视频文件,解封装操作。

2、读取流信息,并添加打印信息。

3、解复用(循环分离视频流和音频流)。

DemuxThread

//DemuxThread.h
enum class MediaType {
    Audio,
    Video
};

class DemuxThread : public BaseThread {
private:
    QString m_url = nullptr;
    AVFormatContext *ic = nullptr;
    int m_videoStreamIndex = -1;
    int m_audioStreamIndex = -1;
    const AVCodec *m_videoCodec;
    const AVCodec *m_audioCodec;
    AVPacketQueue *m_audioQueue;
    AVPacketQueue *m_videoQueue;

public:
    DemuxThread(AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue);
    DemuxThread(const QString &url, AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue);
    ~DemuxThread() override;
    // 打开视频文件,读取信息
    int init() override;
    int start() override;
    void stop() override;
    void run() override;

    void setUrl(const QString &url);


    AVCodecParameters *getCodecParameters(MediaType type);
    AVRational *getStreamTimeBase(MediaType type);
    const AVCodec *getCodec(MediaType type);
};



//DemuxThread.cpp
#include "DemuxThread.h"
DemuxThread::DemuxThread(AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue)
    : m_audioQueue(mAudioQueue), m_videoQueue(mVideoQueue) {
}
DemuxThread::DemuxThread(const QString &url, AVPacketQueue *mAudioQueue, AVPacketQueue *mVideoQueue)
    : m_url(url), m_audioQueue(mAudioQueue), m_videoQueue(mVideoQueue) {
}

DemuxThread::~DemuxThread() {
    if (m_thread) {
        this->stop();
    }
}
int DemuxThread::init() {
    if (m_url == nullptr) {
        qDebug() << "没有设置文件链接";
        return -1;
    }

    ic = avformat_alloc_context();
    int ret;
    ret = avformat_open_input(&ic, m_url.toUtf8(), nullptr, nullptr);
    if (ret < 0) {
        qDebug() << "avformat_open_input 函数发送错误";
        return -1;
    }

    ret = avformat_find_stream_info(ic, nullptr);
    if (ret < 0) {
        qDebug() << "avformat_find_stream_info 函数发送错误";
        return -1;
    }

    m_videoStreamIndex = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &m_videoCodec, 0);
    if (m_videoStreamIndex < 0) {
        qDebug() << "没有找到视频流索引 av_find_best_stream error";
        return -1;
    }
    AVCodecParameters *codecParameters_video = ic->streams[m_videoStreamIndex]->codecpar;
    QString codecNameVideo = avcodec_get_name(codecParameters_video->codec_id);
    qDebug() << "视频流:" << codecNameVideo;

    m_audioStreamIndex = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &m_audioCodec, 0);
    if (m_audioStreamIndex < 0) {
        qDebug() << "没有找到音频流索引 av_find_best_stream error";
        return -1;
    }
    AVCodecParameters *codecParameters_audio = ic->streams[m_audioStreamIndex]->codecpar;
    QString codecNameAudio = avcodec_get_name(codecParameters_audio->codec_id);
    qDebug() << "音频流:" << codecNameAudio;

    return 1;
}
int DemuxThread::start() {
    if (init() != 1) {
        qDebug() << "打开文件失败,停止创建线程";
        return -1;
    }
    isStopped = false;
    isPlaying = true;
    QThread::start();
    if (!currentThread()) {
        qDebug() << "线程创建失败";
        return -1;
    }

    return 0;
}
void DemuxThread::stop() {
    BaseThread::stop();
    if (ic) {
        avformat_close_input(&ic);
        ic = nullptr;
    }
    if (m_videoCodec) {
        m_videoCodec = nullptr;
    }

    if (m_audioCodec) {
        m_audioCodec = nullptr;
    }
}
void DemuxThread::run() {
    int ret;
    AVPacket pkt;
    while (!isStopped) {
        //
        if (m_audioQueue->size() > 10 || m_videoQueue->size() > 10) {
            //                        qDebug()<<"解复用线程等待 " <<"videoSize "<< m_videoQueue->size()<<"audioSize "<<m_audioQueue->size();
            msleep(10);
            //            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }
        ret = av_read_frame(ic, &pkt);
        if (ret < 0) {
            qDebug() << "帧读完";
            break;
        }
        if (pkt.stream_index == m_audioStreamIndex) {
            m_audioQueue->push(&pkt);
            //            qDebug() << "audio pkt queue size:" << m_audioQueue->size();
        } else if (pkt.stream_index == m_videoStreamIndex) {
            m_videoQueue->push(&pkt);
            //            qDebug() << "video pkt queue size:" << m_videoQueue->size();
        } else {
            av_packet_unref(&pkt);
        }
    }
}

void DemuxThread::setUrl(const QString &url) {
    m_url = url;
}
AVCodecParameters *DemuxThread::getCodecParameters(MediaType type) {
    switch (type) {
        case MediaType::Audio:
            if (m_audioStreamIndex != -1) {
                return ic->streams[m_audioStreamIndex]->codecpar;
            } else {
                return nullptr;
            }
        case MediaType::Video:
            if (m_videoStreamIndex != -1) {
                return ic->streams[m_videoStreamIndex]->codecpar;
            } else {
                return nullptr;
            }
    }
}
AVRational *DemuxThread::getStreamTimeBase(MediaType type) {
    switch (type) {
        case MediaType::Audio:
            if (m_audioStreamIndex != -1) {
                return &ic->streams[m_audioStreamIndex]->time_base;
            } else {
                return nullptr;
            }
        case MediaType::Video:
            if (m_videoStreamIndex != -1) {
                return &ic->streams[m_videoStreamIndex]->time_base;
            } else {
                return nullptr;
            }
    }
}
const AVCodec *DemuxThread::getCodec(MediaType type) {
    switch (type) {
        case MediaType::Audio:
            return m_audioCodec;
        case MediaType::Video:
            return m_videoCodec;
    }
}

DecodeThread

//DecodeThread.h
#include "BaseThread.h"
#include "FFmpegHeader.h"
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include <QDebug>

class DecodeThread : public BaseThread {
private:
    const AVCodec *m_codec = nullptr;
    AVCodecParameters *m_par = nullptr;
    AVPacketQueue *m_packetQueue = nullptr;
    AVFrameQueue *m_frameQueue = nullptr;

public:
    AVCodecContext *dec_ctx = nullptr;

    DecodeThread(const AVCodec *mCodec, AVCodecParameters *mPar, AVPacketQueue *mPacketQueue, AVFrameQueue *mFrameQueue);
    ~DecodeThread() override;
    int init() override;
    int start() override;
    void stop() override;
    void run() override;
};



//DecodeThread.cpp
#include "DecodeThread.h"
DecodeThread::DecodeThread(const AVCodec *mCodec, AVCodecParameters *mPar, AVPacketQueue *mPacketQueue, AVFrameQueue *mFrameQueue)
    : m_codec(mCodec), m_par(mPar), m_packetQueue(mPacketQueue), m_frameQueue(mFrameQueue) {
}
DecodeThread::~DecodeThread() {
    stop();
}
int DecodeThread::init() {
    if (!m_par) {
        qDebug() << "AVCodecParameters 为空";
        return -1;
    }
    dec_ctx = avcodec_alloc_context3(nullptr);

    int ret = avcodec_parameters_to_context(dec_ctx, m_par);
    if (ret < 0) {
        qDebug() << "avcodec_parameters_to_context error";
    }

    ret = avcodec_open2(dec_ctx, m_codec, nullptr);
    if (ret < 0) {
        qDebug() << "avcodec_open2 error";
    }

    return 0;
}
void DecodeThread::run() {
    AVFrame *frame = av_frame_alloc();
    while (!isStopped) {
        if (m_frameQueue->size() > 10) {
            //                        qDebug()<<"解码线程等待";
            msleep(10);
            //            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }

        AVPacket *pkt = m_packetQueue->pop(5);
        if (pkt) {
            int ret = avcodec_send_packet(dec_ctx, pkt);
            av_packet_free(&pkt);
            if (ret < 0) {
                qDebug() << "avcodec_send_packet error";
                break;
            }
            while (true) {
                ret = avcodec_receive_frame(dec_ctx, frame);
                if (ret == 0) {
                    m_frameQueue->push(frame);
                    //                    qDebug()<<"m_frameQueue size:"<<m_frameQueue->size();
                    continue;
                } else if (AVERROR(EAGAIN) == ret) {
                    break;
                } else {
                    isStopped = true;
                    qDebug() << "avcodec_receive_frame error";
                    break;
                }
            }
        } else {
            break;
        }
    }
}
int DecodeThread::start() {
    isStopped = false;
    isPlaying = true;
    QThread::start();
    if (!currentThread()) {
        qDebug() << "线程创建失败";
        return -1;
    }
    return 0;
}
void DecodeThread::stop() {
    BaseThread::stop();
    if (dec_ctx)
        avcodec_close(dec_ctx);
}

测试是否能够正常运行

现在解复用线程模块和解码线程模块都已经完成了,测试一下是否正常运行

main.cpp

#include <QApplication>
#include <QPushButton>
//-----------
#include "queue/AVFrameQueue.h"
#include "queue/AVPacketQueue.h"
#include "thread/DecodeThread.h"
#include "thread/DemuxThread.h"


int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QPushButton button("Hello world!", nullptr);
    button.resize(200, 100);
    button.show();

    // 解复用
    DemuxThread *demuxThread;
    DecodeThread *audioDecodeThread;
    DecodeThread *videoDecodeThread;

    // 解码-音频
    AVPacketQueue audioPacketQueue;
    AVFrameQueue audioFrameQueue;
    // 解码-视频
    AVPacketQueue videoPacketQueue;
    AVFrameQueue videoFrameQueue;

    demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);
    demuxThread->setUrl("/Users/mac/Downloads/23.mp4");
    demuxThread->start();
    int ret;
    audioDecodeThread = new DecodeThread(
            demuxThread->getCodec(MediaType::Audio),
            demuxThread->getCodecParameters(MediaType::Audio),
            &audioPacketQueue,
            &audioFrameQueue);
    audioDecodeThread->init();
    audioDecodeThread->start();

    videoDecodeThread = new DecodeThread(
            demuxThread->getCodec(MediaType::Video),
            demuxThread->getCodecParameters(MediaType::Video),
            &videoPacketQueue,
            &videoFrameQueue);
    videoDecodeThread->init();
    videoDecodeThread->start();

    return QApplication::exec();
}

测试完成运行正常

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

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

相关文章

技术分享 | 在 IDE 插件开发中接入 JCEF 框架

项目背景 当前的开发环境存在多种不同语言的 IDE&#xff0c;如 JetBrains 全家桶、Eclipse、Android Studio 和 VS Code 等等。由于每个 IDE 各有其特定的语言和平台要求&#xff0c;因此开发 IDE 插件时&#xff0c;需要投入大量资源才能尽可能覆盖大部分工具。同时&#xf…

java中的String.format()方法详解

介绍 String.format() 是 Java 中的一个字符串格式化方法&#xff0c;它用于生成指定格式的字符串。这个方法可以接受一个或多个参数&#xff0c;并将它们按照指定的格式插入到字符串中。它使用了类似于 C 语言中的 printf 函数的语法。 String.format() 方法的使用格式如下&…

Linux技能篇-非交互式修改密码

今天的文章没有格式&#xff0c;简单分享一个小技能&#xff0c;就是标题所说–非交互式修改密码。 一、普通方式修改用户密码 最普通的修改密码的命令就是passwd命令 [rootlocalhost ~]# passwd root Changing password for user root. New password: Retype new password:…

【经典小练习】修改文件中的数据

文章目录 &#x1f339;例子&#x1f33a;思路&#x1f6f8;方法一✨报错解决 &#x1f6f8;方法二 &#x1f339;例子 文本文件中有下面的数据 2-1-9-4-7-8 将文件中的数据进行排序&#xff0c;变成下面的数据 1-2-4-7-8-9 &#x1f33a;思路 要对这些数据进行排序&#xf…

发送一个网络数据包的过程解析

在 ip_queue_xmit 中&#xff0c;也即 IP 层的发送函数里面&#xff0c;有三部分逻辑。第一部分&#xff0c;选取路由&#xff0c;也即我要发送这个包应该从哪个网卡出去。 这件事情主要由 ip_route_output_ports 函数完成。接下来的调用链为&#xff1a;ip_route_output_port…

Python报错:AttributeError(类属性、实例属性)

Python报错&#xff1a;AttributeError&#xff08;类属性、实例属性&#xff09; Python报错&#xff1a;AttributeError 这个错误就是说python找不到对应的对象的属性&#xff0c;百度后才发现竟然是初始化类的时候函数名写错了 __init__应该有2条下划线&#xff0c;如果只有…

【JavaEE初阶】Thread 类及常见方法、线程的状态

目录 1、Thread 类及常见方法 1.1 Thread 的常见构造方法 1.2 Thread 的几个常见属性 1.3 启动⼀个线程 - start() 1.4 中断⼀个线程 1.5 等待⼀个线程 - join() 1.6 获取当前线程引用 1.7 休眠当前线程 2、线程的状态 2.1 观察线程的所有状态 2.2 线程状态和状…

黑马点评笔记 分布式锁

文章目录 分布式锁基本原理和实现方式对比Redis分布式锁的实现核心思路实现分布式锁版本一Redis分布式锁误删情况说明解决Redis分布式锁误删问题分布式锁的原子性问题分布式锁-Redission分布式锁-redission可重入锁原理分布式锁-redission锁重试和WatchDog机制分布式锁-redissi…

RocketMQ 消息中间件 知识点汇总

目录 RocketMQ1、什么是RocketMQ?常用术语:2、为什么需要消息队列3、什么是异步处理4、什么是服务解耦5、什么是流量控制6、消息队列两种模型队列模型:发布/订阅模型:总结:7、怎么保证消息不丢失8、如何处理消息被重复消费**出现消息重复的情况:****解决方法:**9、如何保…

RocketMQ消息的一生

这篇文章我准备来聊一聊RocketMQ消息的一生。 不知你是否跟我一样&#xff0c;在使用RocketMQ的时候也有很多的疑惑&#xff1a; 消息是如何发送的&#xff0c;队列是如何选择的&#xff1f; 消息是如何存储的&#xff0c;是如何保证读写的高性能&#xff1f; RocketMQ是如何…

Dreamview底层实现原理

1. Dreamview底层实现原理(3个模块) (1) HMI--可视化人机交互 a. HMIConfig: 1) 支持哪些模式&#xff1b;2)支持哪些地图&#xff1b;3)支持哪些车辆&#xff1b;4)HMIAction HMIMode: b.HMIStatus (2) SimControl (3) Monitor--监视自动驾驶行驶过程中软硬件状态 Referenc…

CentOS 7 安装 Weblogic 14 版本

安装JDK程序 注意&#xff1a;安装weblogic前&#xff0c;先安装JDK&#xff01;&#xff08;要求jdk(1.7以上)&#xff09;&#xff1a; 一、创建用户组weblogic及用户weblogic groupadd weblogic useradd -g weblogic weblogic二、将下载好的jdk及weblogic上传至/home/webl…

2021年12月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行下列程序,屏幕上可以看到几只小猫? A:1 B:3 C:4 D:0 答案:B 第2题 下列程序哪个可以实现:按下空格键,播放完音乐后说“你好!”2秒? A: B: C:

【Linux】yum -- 软件包管理器

目录 一、Linux中是如何安装软件的 1.1 安装的方法 1.2 安装的本质(基本理解) 二、软件包 2.1 软件包的概念 2.2 为什么要有软件包 三、yum--软件包管理器 3.1 yum的概念 3.2 yum的使用 3.2.1 搜索一个软件 3.2.2 安装一个软件 3.2.3 卸载一个软件 3.3 yum源更新 …

前端vue导出PPT,使用pptxgen.js

前言 公司新需求需要导出ppt给业务用&#xff0c;查阅资料后发现也挺简单的&#xff0c;记录一下。 如有不懂的可以留言&#xff01;&#xff01;&#xff01; 1.安装包 npm install pptxgenjs --save2.引入包 在需要使用的文件中引入 import Pptxgenfrom "pptxgenjs&…

java学习part10 this

90-面向对象(进阶)-关键字this调用属性、方法、构造器_哔哩哔哩_bilibili 1.java的this java的this性质类似cpp的this&#xff0c; 但它是一种引用&#xff0c;所以用 this. xxx来调用。 this代表当前的类的实例&#xff0c;所以必须和某个对象结合起来使用&#xff0c;不能…

rk3588配置uac功能,android13使能uac及adb的复合设备

最近&#xff0c;因新增需求需要在现有产品上增加UAC的功能&#xff0c;查阅并学习相关知识后&#xff0c;在rk3588 SOC硬件平台搭载android13系统平台上成功配置了uac及uac&adb的复合设备。基于开源共享精神希望给大家提供些参考。 1.技术可行性预研 &#xff08;1&#…

nodejs+vue+python+PHP+微信小程序-留学信息查询系统的设计与实现-安卓-计算机毕业设计

1、用户模块&#xff1a; 1&#xff09;登录&#xff1a;用户注册登录账号。 2&#xff09;留学查询模块&#xff1a;查询学校的入学申请条件、申请日期、政策变动等。 3&#xff09;院校排名&#xff1a;查询国外各院校的实力排名。 4&#xff09;测试功能&#xff1a;通过入学…

LabVIEW中将SMU信号连接到PXI背板触发线

LabVIEW中将SMU信号连接到PXI背板触发线 本文介绍如何将信号从PXI&#xff08;e&#xff09;SMU卡路由到PXI&#xff08;e&#xff09;机箱上的背板触发线。该过程涉及使用NI-DCPowerVI将SMU信号导出到PXI_TRIG线上。 在继续操作之前&#xff0c;请确保在开发PC上安装了兼容版…

防止应用程序截屏(容器式,防止极域电子教室和录屏软件录制)

核心原理、实现目的 1、使用Panel容器将外部窗口嵌入自己写的程序 2、使用防止截屏的函数来对窗口透明&#xff0c;这可以使本窗口内所有窗口在录屏软件上消失 3、解放&#xff0c;抓取&#xff0c;存储句柄&#xff0c;实现摆脱录屏&#xff08;极域监控&#xff09; 程序…