Pārlūkot izejas kodu

V0.2.2
1、完善了一些其他库

Apple 6 mēneši atpakaļ
vecāks
revīzija
6b77b72b92

+ 1 - 1
Libraries/sm_dll/FindSM.cmake

@@ -56,7 +56,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Windows")
 #添加Linux版本
 elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
     if(CMAKE_BUILD_TYPE MATCHES "Debug")
-        list(APPEND SM_LIB_LIBRARY ${CMAKE_CURRENT_LIST_DIR}/libs/linux/x64/with_debug_info/libSM_Dll.so)
+        list(APPEND SM_LIB_LIBRARY ${CMAKE_CURRENT_LIST_DIR}/libs/linux/x64/libSM_Dll.so)
     else()
         list(APPEND SM_LIB_LIBRARY ${CMAKE_CURRENT_LIST_DIR}/libs/linux/x64/libSM_Dll.so)
     endif()

BIN
Libraries/sm_dll/libs/linux/x64/SM_Dll


BIN
Libraries/sm_dll/libs/linux/x64/libSM_Dll.so


+ 2 - 0
UI/Resource/Standard.qrc

@@ -3,6 +3,8 @@
         <file>Standard_ICON/Dialog_close.png</file>
         <file>Standard_ICON/Dialog_close2.png</file>
         <file>Standard_ICON/DownArrow.png</file>
+        <file>Standard_ICON/Radio_Checked.png</file>
+        <file>Standard_ICON/Radio_Unchecked.png</file>
         <file>qss/selectDialog.css</file>
     </qresource>
 </RCC>

+ 0 - 0
UI/Resource/Standard_ICON/Checked.png → UI/Resource/Standard_ICON/Radio_Checked.png


+ 0 - 0
UI/Resource/Standard_ICON/Unchaecked.png → UI/Resource/Standard_ICON/Radio_Unchecked.png


+ 33 - 1
UI/Resource/qss/selectDialog.css

@@ -132,6 +132,15 @@ QComboBox
     background:#FFFFFF;
     padding-left: 12px;
 }
+QComboBox:disabled
+{
+    background: rgba(0,0,0,0.04);
+    border-radius: 4px;
+    border: 1px solid #E6E9F4;
+    padding-left: 12px;
+}
+
+
 QComboBox[Warn=true]
 {
     border: 1px solid #D21F21;
@@ -212,7 +221,30 @@ QComboBox QScrollBar::sub-line::vertical{/*下箭头*/
     border:none;
 }
     
+/*****************  QRadioButton  *****************/
+QRadioButton::indicator
+{
+    width: 16px;
+    height: 16px;
     
+}
 
-    
+QRadioButton::indicator::unchecked
+{
+    image: url(:/Standard_ICON/Radio_Unchecked.png);
+}
+
+QRadioButton::indicator::checked
+{
+    image: url(:/Standard_ICON/Radio_Checked.png);
+}
+
+/****************** QLineEdit ******************/
+QLineEdit
+{
+    border: 1px solid #E6E9F4;
+    border-radius: 4px;
+    background:#FFFFFF;
+    padding-left: 12px;
+}
     

+ 608 - 0
common/VedioPlayer_/DecodeVedio.cpp

@@ -0,0 +1,608 @@
+#include "DecodeVedio.h"
+#include "spdlog/spdlog.h"
+
+#include <QImage>
+#include <QThread>
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libavutil/imgutils.h>
+}
+
+
+DecodeVedio::DecodeVedio(QThread* thread, QObject* parent) : QObject(parent) , m_thread(thread)
+{
+    /* 在连接之前调用,移动到新的线程 */
+    this->moveToThread(thread);
+    m_startThread.setSingleShot(true);
+    connect(&m_startThread, &QTimer::timeout, this, &DecodeVedio::do_startDecodeVedio);
+    thread->start();
+}
+
+DecodeVedio::~DecodeVedio()
+{
+    exitThread();
+    if(m_thread != nullptr)
+    {
+        if(m_thread->isRunning())
+        {
+            m_thread->quit();
+            m_thread->wait();
+        }
+    }
+    if(m_initFFmpeg)
+    {
+        freeFFmpeg();
+    }
+    if(!m_queueImage.isEmpty())
+    {
+        int size = m_queueImage.count();
+        for(int i = 0; i < size; i++)
+        {
+            auto image = m_queueImage.dequeue();
+            if (image)
+            {
+                delete image;
+            }
+            
+        }
+        
+    }
+}
+
+/* 开始解码视频 */
+void DecodeVedio::startDecodeVedio()
+{
+    if(m_isRunning)
+    {
+        return;
+    }
+    if(!m_initFFmpeg)
+    {
+        SPDLOG_WARN("未初始化FFMPEG...");
+        return;
+    }
+    /* 开启定时器,进入槽函数,就进入了新的线程 */
+    m_startThread.start(0);
+}
+
+/* 停止解码视频,退出工作函数,线程未停止 */
+void DecodeVedio::stopDecodeVedio()
+{
+    if(!m_isRunning)
+    {
+        return;
+    }
+    m_isRunning = false;
+    m_fileName = QString();
+    // /* 等待线程执行结束 */
+    // while(true)
+    // {
+    //     if(m_threadStopped)
+    //     {
+    //         break;
+    //     }
+    //     /* 睡眠10ms */
+    //     std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    // }
+    
+}
+
+/* 设置当前播放位置,单位是微秒 */
+void DecodeVedio::setCurrentPos(quint64 pos)
+{
+    // AV_TIME_BASE
+    qint64 target_pos = m_startPos + pos;
+    SPDLOG_DEBUG("设置播放位置:{}",target_pos);
+    /* 暂停解码 */
+    m_pauseDecode = true;
+    
+    /* 第二个参数设置为-1,表示所有流都跳转 */
+    int ret = av_seek_frame(m_pFormatContext, -1, target_pos, AVSEEK_FLAG_ANY);
+    if(ret < 0)
+    {
+        SPDLOG_WARN("跳转失败...");
+    }
+    /* 清空队列 */
+    m_mutexQueue.lock();
+    int size = m_queueImage.count();
+    for(int i = 0; i < size; i++)
+    {
+        auto image = m_queueImage.dequeue();
+        if (image)
+        {
+            delete image;
+        }
+            
+    }
+    m_mutexQueue.unlock();
+    SPDLOG_INFO("跳转完成 , queue {}", m_queueImage.count());
+    /* 继续解码 */
+    m_pauseDecode = false;
+    
+}
+
+/* 获取当前播放位置 */
+qint64 DecodeVedio::getCurrentPos()
+{
+    
+    return m_currentPos - m_startPos;
+}
+
+/* 获取视频时长 */
+qint64 DecodeVedio::getDuration()
+{
+    return m_duration;
+
+}
+
+/* 唤醒队列不满条件变量 */
+void DecodeVedio::wakeUpCondQueueNoEmpty()
+{
+    m_condQueueNoFull.wakeAll();    
+}
+
+/**
+ * @brief 获取一帧图像,队列为空就返回nullptr,这个函数应该是运行在UI线程中的
+ * @warning 传出这个指针后,队列就出队了,内存需要外面获取的实例释放
+ * @return QImage* 一帧图像的指针
+ */
+QImage* DecodeVedio::getOneImage()
+{
+    if(m_queueImage.count() == 0)
+    {
+        // SPDLOG_TRACE("队列为空...");
+        return nullptr;
+    }
+    // SPDLOG_TRACE("******************************** 队列中图片个数:{} ",m_queueImage.count());
+    m_mutexQueue.lock();
+    auto image = m_queueImage.dequeue();
+    m_mutexQueue.unlock();
+    /* 唤醒可能阻塞住的解码线程,队列中的图片低于20之后再唤醒 */
+    if(m_queueImage.count() < 20)
+    {
+        m_condQueueNoFull.wakeAll();
+    }
+    return image;
+}
+
+/* 获取一帧图像,直到有图像为止 */
+QImage* DecodeVedio::getOneImageUntilHave()
+{
+    QImage* image = nullptr;
+    if(m_queueImage.count() == 0)
+    {
+        m_mutexQueue.lock();
+        m_condQueueNoEmpty.wait(&m_mutexQueue);
+        image = m_queueImage.dequeue();
+        m_mutexQueue.unlock();
+    }
+
+    return image;
+}
+
+/**
+ * @brief 获取每秒的帧数
+ * 
+ * @return int 正值表示帧数,-1表示未知,-2表示HEVC
+
+ */
+int DecodeVedio::getFrameCount()
+{
+    AVFormatContext *pFormatCtx = NULL;
+    avformat_open_input(&pFormatCtx, m_fileName.toStdString().c_str(), NULL, NULL);
+    avformat_find_stream_info(pFormatCtx, NULL);
+    /* 找到视频流 */
+    int videoStreamIndex = -1;
+    for (int i = 0; i < pFormatCtx->nb_streams; i++) 
+    {
+        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+            videoStreamIndex = i;
+            break;
+        }
+    }
+    int fps = 0;
+    /* 获取视频格式是hevc还是h.264 */
+    enum AVCodecID codecID = pFormatCtx->streams[videoStreamIndex]->codecpar->codec_id;
+    if (codecID == AV_CODEC_ID_HEVC) 
+    {
+        SPDLOG_DEBUG("视频格式是HEVC");
+        fps = -2;
+        // AVPacket packet;
+        // int frameCount = 0;
+        // double timeBase = av_q2d(pFormatCtx->streams[videoStreamIndex]->time_base);
+        // double duration = 2.0;  // Duration in seconds
+
+        // while (av_read_frame(pFormatCtx, &packet) >= 0) 
+        // {
+        //     if (packet.stream_index == videoStreamIndex) 
+        //     {
+        //         double packetTime = packet.pts * timeBase;
+        //         if (packetTime <= duration) {
+        //             frameCount++;
+        //         } else {
+        //             break;
+        //         }
+        //     }
+        //     av_packet_unref(&packet);
+        // }
+        // fps = frameCount / duration;
+    }
+    else if(codecID == AV_CODEC_ID_H264)
+    {
+        SPDLOG_DEBUG("视频格式是H.264");
+        /* 获取到分子和分母,一除就是帧率 */
+        AVRational frameRate = pFormatCtx->streams[videoStreamIndex]->avg_frame_rate;
+        fps = frameRate.num*1.0 / frameRate.den;
+        // SPDLOG_DEBUG("分子:{} 分母:{} 帧率:{}",frameRate.num, frameRate.den, fps);
+    }else
+    {
+        fps = 30;
+    }
+
+    avformat_close_input(&pFormatCtx);
+
+    return fps;
+}
+
+void DecodeVedio::setVideoSize(int width, int height)
+{
+    m_width = width;
+    m_height = height;
+
+    /* 重新初始化缩放参数 */
+    // sws_freeContext(m_sws_ctx);
+    /* 初始化Sws Context,这是转换规则,转换成RGB */
+    m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt,    /* 原图像大小和格式 */
+                                m_width, m_height, AV_PIX_FMT_RGB24,      /* 目标图像的大小和格式 */
+                                SWS_BICUBIC, 
+                                nullptr, 
+                                nullptr, 
+                                nullptr);
+}
+
+/* 初始化函数 */
+void DecodeVedio::initFFmpeg(const QString& fileName)
+{
+    if(fileName != m_fileName)
+    {
+        m_fileName = fileName;
+        if(m_initFFmpeg)
+        {
+            freeFFmpeg();
+        }
+    }else 
+    {
+        /* 清空队列 */
+        int size = m_queueImage.count();
+        for(int i = 0; i < size; i++)
+        {
+            auto image = m_queueImage.dequeue();
+            if (image)
+            {
+                delete image;
+            }
+                
+        }
+        m_queueImage.clear();
+        return;
+    }
+    /* 清空队列 */
+    int size = m_queueImage.count();
+    for(int i = 0; i < size; i++)
+    {
+        auto image = m_queueImage.dequeue();
+        if (image)
+        {
+            delete image;
+        }
+            
+    }
+    m_queueImage.clear();
+
+    SPDLOG_DEBUG("开始初始化FFMPEG");
+    /* 注册所有的解码器,从4.0开始就不需要这个初始化了 */
+    // av_register_all();
+
+    /* 存储文件格式信息 */
+    /* 打开文件,读取视频文件的头信息,放在第一个参数的结构体中 */
+    int ret = avformat_open_input(&m_pFormatContext, m_fileName.toStdString().c_str(), nullptr, nullptr);
+    if(ret != 0)
+    {
+        SPDLOG_WARN("打开视频文件错误,错误代码:{}",ret);
+        return;
+    }
+
+    ret = 0;
+    /* 检查视频容器内部的流信息,将流存储到了pFormatContext->streams中
+     * 这个会补充avformat_open_input()没有获取到的信息 */
+    ret = avformat_find_stream_info(m_pFormatContext, nullptr);
+    if(ret < 0)
+    {
+        SPDLOG_WARN("获取视频流错误,错误代码:{}",ret);
+        return;
+    }
+    m_duration = m_pFormatContext->duration;
+    m_currentPos = 0;
+    m_startPos = m_pFormatContext->start_time;
+    // SPDLOG_DEBUG("开始时间:{} 时长:{}",m_startPos, m_duration);
+    
+    /* 一个调试函数,将流信息输出到控制台 */
+    av_dump_format(m_pFormatContext, 0, m_fileName.toStdString().c_str(), 0);
+
+    /************ 找到视频流 ************/
+    int i = 0;
+    AVCodecParameters *pCodecCtxOrig = nullptr;
+    
+    for(i = 0;i < m_pFormatContext->nb_streams; i++)
+    {
+        if(m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+        {
+            m_videoStream = i;
+            break;
+        }
+    }
+    if(m_videoStream == -1)
+    {
+        SPDLOG_WARN("没有找到视频流");
+        return;
+    }
+    SPDLOG_INFO("找到视频流");
+    
+    pCodecCtxOrig = m_pFormatContext->streams[m_videoStream]->codecpar;
+    SPDLOG_INFO("获取视频流参数成功!");
+    /* 使用编码器指向流 */
+    AVCodec *pCodec = nullptr;
+    /* 找到解码器 */
+    pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
+    if(pCodec == nullptr)
+    {
+        SPDLOG_WARN("没有找到解码器");
+        return;
+    }
+    SPDLOG_TRACE("找到解码器");
+    /* 复制上下文,先分配空间,后面记得释放空间 */
+    m_pCodecCtx = avcodec_alloc_context3(pCodec);
+    SPDLOG_TRACE("分配空间成功!");
+    /* 将视频流中的编码器参数拷贝下来,这个函数不是线程安全的 */
+    if(avcodec_parameters_to_context(m_pCodecCtx, pCodecCtxOrig) != 0)
+    {
+        SPDLOG_WARN("复制上下文错误");
+        return;
+    }
+    SPDLOG_INFO("复制上下文成功!");
+    if(avcodec_open2(m_pCodecCtx, pCodec, nullptr) < 0)
+    {
+        SPDLOG_ERROR("打开解码器错误");
+        return;
+    }
+    SPDLOG_TRACE("打开编码器成功!");
+
+    /******** 读取数据(解码数据) ********/
+    m_packet = av_packet_alloc();
+    av_init_packet(m_packet);
+
+    /********* 创建两个pFrame,一个存放原始数据,一个存放转换后的RGB数据 **********/
+    m_pFrame = av_frame_alloc();
+    if(m_pFrame == nullptr)
+    {
+        SPDLOG_ERROR("创建pFrame错误");
+        return;
+    }
+    m_pFrameRGB = av_frame_alloc();
+    if(m_pFrameRGB == nullptr)
+    {
+        SPDLOG_ERROR("创建pFrameRGB错误");
+        return;
+    }
+    
+    int numBytes = 0;
+    /* 初始化pFrameRGB */
+    numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
+    m_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
+    /* 获取视频相关信息 */
+    m_width = m_pCodecCtx->width;
+    m_height = m_pCodecCtx->height;
+    m_frameCount = m_pFormatContext->streams[m_videoStream]->nb_frames;
+    
+    /* 这个函数的实际作用是将buffer设置给pFrameRGB作为原始数据的内存区域 */
+    av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_buffer, AV_PIX_FMT_RGB24, m_pCodecCtx->width, m_pCodecCtx->height, 1);
+
+    /********** 创建一个SwsContext结构体,主要为了格式转化的时候使用 ***********/
+    
+    /* 初始化Sws Context,这是转换规则,转换成RGB */
+    m_sws_ctx = sws_getContext( m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt,    /* 原图像大小和格式 */
+                                m_width, m_height, AV_PIX_FMT_RGB24,      /* 目标图像的大小和格式 */
+                                SWS_BILINEAR,           /* 双线性 */
+                                nullptr, 
+                                nullptr, 
+                                nullptr);
+
+    m_initFFmpeg = true;
+    SPDLOG_INFO("FFMPEG初始化完成!");
+    /* 这里不能释放,否则会出问题 */
+    // avcodec_parameters_free(&pCodecCtxOrig);
+}
+
+/* 取消ffmpeg初始化函数 */
+void DecodeVedio::freeFFmpeg()
+{
+    /************** 清理缓冲区,释放空间 *********************/
+    av_free(m_buffer);
+    m_buffer = nullptr;
+    av_free(m_pFrameRGB);
+    m_pFrameRGB = nullptr;
+    /* 释放pFrame */
+    av_free(m_pFrame);
+    m_pFrame = nullptr;
+    av_packet_free(&m_packet);
+    // sws_freeContext(m_sws_ctx);
+    /* 关闭解码器 */
+    avcodec_close(m_pCodecCtx);
+    /* 关闭文件 */
+    avformat_close_input(&m_pFormatContext);
+
+    QString m_fileName = QString();
+    int m_videoStream = -1;                         /* 记录视频流是第几个流 */
+    bool m_initFFmpeg = false;
+    
+}
+
+/**
+ * @brief 进入之后就会一直解码,完成一个帧就会发送新一个信号
+ * 
+ */
+void DecodeVedio::decodeVedio()
+{
+    int ret = 0;
+    int retFrame = 0;
+    int retPacket = 0;
+    while(m_isRunning)
+    {
+        /* 暂停解码 */
+        while(m_pauseDecode)
+        {
+            std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        }
+        std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
+        /* 读取一帧压缩码流,如果使用多线程解码,就从这里分发数据流的包 */
+        retPacket = av_read_frame(m_pFormatContext, m_packet);
+        std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
+        // SPDLOG_TRACE("======================================= 读取数据包耗时(us):{}",std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count());
+        if(retPacket == AVERROR_EOF)
+        {
+            SPDLOG_INFO("读取到文件末尾...");
+            break;
+        }
+        if(retPacket < 0)
+        {
+            SPDLOG_WARN("读取帧错误...");
+            break;
+        }
+        /* 判断是否是视频帧 */
+        if(m_packet->stream_index == m_videoStream)
+        {
+            std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
+            /* 解码视频帧,现在使用新的API */
+            ret  = avcodec_send_packet(m_pCodecCtx, m_packet);
+            std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
+            if(ret < 0)
+            {
+                SPDLOG_ERROR("发送数据包错误...");
+                continue;
+            }
+            // SPDLOG_TRACE("======================================= 发送数据包耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count());
+            while (m_isRunning)
+            {
+                /* 从解码器接收解码后的帧的函数。*/
+                retFrame = avcodec_receive_frame(m_pCodecCtx, m_pFrame);
+                std::chrono::steady_clock::time_point t5 = std::chrono::steady_clock::now();
+                // SPDLOG_TRACE("======================================= 接收帧耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count());
+                if(retFrame == AVERROR(EAGAIN) || retFrame == AVERROR_EOF)
+                {
+                    // SPDLOG_TRACE("没有更多的帧可以输出,跳出循环。");
+                    break;
+                }
+                if(retFrame < 0)
+                {
+                    SPDLOG_ERROR("解码错误...");
+                    break;
+                }
+                if (retFrame == 0)
+                {
+                    /* 现在的位置,转换成时间,乘上AV_TIME_BASE后,单位是微秒 */
+                    m_currentPos = m_pFrame->pts * av_q2d(m_pFormatContext->streams[m_videoStream]->time_base) * AV_TIME_BASE;
+                    SPDLOG_DEBUG("======== 当前位置:{} ========",m_currentPos);
+                    /* 成功接收到一帧,处理解码后的帧 */
+                    std::chrono::steady_clock::time_point t6 = std::chrono::steady_clock::now();
+                    /* 将帧转换为RGB */
+                    sws_scale(m_sws_ctx, (uint8_t const * const *)m_pFrame->data, m_pFrame->linesize, 0, m_pCodecCtx->height, 
+                                        m_pFrameRGB->data, m_pFrameRGB->linesize);
+                    std::chrono::steady_clock::time_point t7 = std::chrono::steady_clock::now();
+                    // SPDLOG_TRACE("======================================= 转换RGB耗时:{}",std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count());
+                    /* 一帧图像入队 */
+                    auto image = new QImage(m_pFrameRGB->data[0], m_width, m_height, QImage::Format_RGB888);
+                    m_mutexQueue.lock();
+                    if(m_queueImage.count() >= m_queueMaxNum)
+                    {
+                        // SPDLOG_TRACE("队列满了...");
+                        m_condQueueNoFull.wait(&m_mutexQueue);
+                    }
+                    m_queueImage.enqueue(image);
+                    m_mutexQueue.unlock();
+                    /* 队列有数据,唤醒阻塞函数 */
+                    m_condQueueNoEmpty.wakeAll();
+                    /* 同时发送信号 */
+                    emit signal_oneImage();
+                } 
+                else if (retFrame == AVERROR(EAGAIN) || ret == AVERROR_EOF) 
+                {
+                    // 没有更多的帧可以输出,跳出循环
+                    break;
+                } 
+                else if (retFrame < 0)
+                {
+                    SPDLOG_TRACE("解码错误...");
+                    av_packet_unref(m_packet);
+                    break;
+                }
+                av_frame_unref(m_pFrame);
+                retFrame = -1;
+            }
+            
+        }else
+        {
+            // SPDLOG_TRACE("不是视频帧...");
+        }
+        // av_free_packet(AVPacket *pkt)
+        av_packet_unref(m_packet);
+    }
+    
+    freeFFmpeg();
+    /* 清空队列 */
+    m_mutexQueue.lock();
+    int count = m_queueImage.count();
+    for (int i = 0; i < count; i++)
+    {
+        auto image = m_queueImage.dequeue();
+        if (image)
+        {
+            delete image;
+            image = nullptr;
+        }
+    }
+    m_mutexQueue.unlock();
+    /* 线程已停止 */
+    m_threadStopped = true;
+}
+
+/* 退出线程,将所有可能暂停线程运行的条件全部唤醒 */
+void DecodeVedio::exitThread()
+{
+    if(m_isRunning)
+    {
+        m_isRunning = false;
+    }
+    m_pauseDecode = false;
+    m_condQueueNoFull.wakeAll();
+}
+
+
+/* 开启解码 */
+void DecodeVedio::do_startDecodeVedio()
+{
+    SPDLOG_DEBUG("线程ID:{}",QThread::currentThreadId());
+    // if(!m_initFFmpeg)
+    // {
+    //     initFFmpeg();
+    // }
+    m_isRunning = true;
+    m_threadStopped = false;
+    m_pauseDecode = false;
+    /* 进入解码,直到播放完成或者手动退出 */
+    decodeVedio();
+    SPDLOG_TRACE("播放完成...");
+}

+ 102 - 0
common/VedioPlayer_/DecodeVedio.h

@@ -0,0 +1,102 @@
+#ifndef DECODEVEDIO_H
+#define DECODEVEDIO_H
+
+#include <QObject>
+#include <QQueue>
+#include <QTimer>
+#include <QMutex>
+#include <QWaitCondition>
+
+// #include "threadcontroller.h"
+
+extern "C"
+{
+// #include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+// #include <libswscale/swscale.h>
+// #include <libavutil/imgutils.h>
+}
+
+/**
+ * 使用方式:
+ *      1. 初始化FFmpeg:initFFmpeg()
+ *      2. 开启解码线程:startDecodeVedio()
+ *      3. 获取一帧图像:getOneImage()
+ *      4. 停止解码线程:stopDecodeVedio()
+ *      5. 在初始化完成后,未进入第二步的之前,可以获取视频的宽高信息,也可以设置宽高信息
+ *      
+ */
+class DecodeVedio : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit DecodeVedio(QThread* thread, QObject* parent = nullptr);
+    ~DecodeVedio();
+
+    // void setFileName(const QString& fileName) { m_fileName = fileName; }
+    void initFFmpeg(const QString& fileName);       /* 初始化函数 */
+    void startDecodeVedio();                        /* 开始解码视频,开始前先设置视频名称 */
+    void stopDecodeVedio();                         /* 停止解码视频,也是停止线程 */
+    void setCurrentPos(quint64 pos);                /* 设置当前播放位置 */
+    qint64 getCurrentPos();                         /* 获取当前播放位置 */
+    qint64 getDuration();                           /* 获取视频时长 */
+    bool isRunning() { return m_isRunning; }        /* 获取解码线程是否运行 */
+
+    void wakeUpCondQueueNoEmpty();                  /* 唤醒队列非空条件变量 */
+    QImage* getOneImage();                          /* 获取一帧图像 */
+    QImage* getOneImageUntilHave();                 /* 获取一帧图像,直到有图像为止 */
+
+    int getFrameCount();                            /* 获取帧数 */
+    int getVideoWidth() const {return m_width;}     /* 获取图像宽度 */
+    int getVideoHeight() const {return m_height;}   /* 获取图像高度 */
+    void setVideoSize(int width,int height);        /* 设置图像宽度和高度 */
+
+signals:
+    void signal_oneImage();                         /* 一帧图像信号 */
+private:
+    void freeFFmpeg();                              /* 取消ffmpeg初始化函数 */
+    void decodeVedio();                             /* 解码视频的工作函数,这个就是新线程 */
+    void exitThread();                              /* 退出线程 */
+
+private slots:
+    void do_startDecodeVedio();                     /* 开启解码 */
+
+private:
+    QThread* m_thread = nullptr;                    /* 解码线程 */
+    std::atomic_bool m_isRunning = false;           /* 解码线程是否运行 */
+    QTimer m_startThread;                           /* 开启线程的定时器 */
+    std::atomic_bool m_initFFmpeg = false;          /* ffmpeg初始化标志 */
+    std::atomic_bool m_threadStopped = false;       /* 停止线程 */
+    std::atomic_bool m_pauseDecode = false;         /* 暂停解码 */
+
+    QString m_fileName;                             /* 解码的视频文件名称 */
+    AVFormatContext *m_pFormatContext = nullptr;    /* 格式上下文,贯穿全局 */
+    AVCodecContext *m_pCodecCtx = nullptr;          /* 解码器上下文 */
+    AVPacket* m_packet = nullptr;                   /* 存储解码前的数据,一个数据包 */
+    AVFrame* m_pFrame = nullptr;                    /* 存储解码后的一帧数据原始视频编码 */
+    AVFrame* m_pFrameRGB = nullptr;                 /* 存储解码后的一帧数据,RGB格式 */
+    struct SwsContext *m_sws_ctx = nullptr;         /* 视频转换上下文 */
+    uint8_t *m_buffer = nullptr;                    /* 存储解码后的一帧数据,RGB格式 */
+    int m_videoStream = -1;                         /* 记录视频流是第几个流 */
+
+    int m_width = 0;                                /* 图像宽度 */
+    int m_height = 0;                               /* 图像高度 */
+    int m_frameCount = 0;                           /* 帧数 */
+    qint64 m_duration = 0;                          /* 视频时长 */
+    qint64 m_currentPos = 0;                        /* 当前播放位置 */
+    qint64 m_startPos = 0;                          /* 起始位置,后续操作都需要在这各基础上操作 */
+
+    // QImage *m_image = nullptr;
+    int m_queueMaxNum = 30;                         /* 这里不是环形队列,最大设置为30帧图像 */
+    QQueue<QImage*> m_queueImage;                   /* 视频帧队列 */
+    QMutex m_mutexQueue;                            /* 队列互斥锁 */
+    QWaitCondition m_condQueueNoFull;               /* 队列不满,可以入队 */
+    QWaitCondition m_condQueueNoEmpty;              /* 队列非空,可以读取 */
+};
+
+
+
+
+
+#endif /* DECODEVEDIO_H */

+ 290 - 0
common/VedioPlayer_/VideoPlayer.cpp

@@ -0,0 +1,290 @@
+#include "VideoPlayer.h"
+
+#include "DecodeVedio.h"
+
+#include <QPainter>
+#include <QResizeEvent>
+#include <QEventLoop>
+
+#include "spdlog/spdlog.h"
+
+
+
+VideoPlayer::VideoPlayer(QWidget *parent) : QWidget(parent)
+{
+    /* 初始化解码线程 */
+    m_threadDecode = new QThread(this);
+    m_decodeVedio = new DecodeVedio(m_threadDecode);
+
+    m_semRefresh = new QSemaphore(30);
+    m_semRefresh->release(30);
+
+    m_timerRefreshUI.setSingleShot(false);
+    /* 设置精度毫秒级 */
+    m_timerRefreshUI.setTimerType(Qt::PreciseTimer);
+    connect(&m_timerRefreshUI, &QTimer::timeout, this, &VideoPlayer::do_refreshUI);
+
+    connect(m_decodeVedio, &DecodeVedio::signal_oneImage, this, &VideoPlayer::do_refreshOneUI);
+    SPDLOG_TRACE("UI线程ID:{}", QThread::currentThreadId());
+}
+
+VideoPlayer::~VideoPlayer()
+{
+    if(m_timerRefreshUI.isActive())
+    {
+        m_timerRefreshUI.stop();
+    }
+    delete m_decodeVedio;
+    if(m_image)
+    {
+        delete m_image;
+    }
+}
+
+/**
+ * @brief 设置播放视频,启动定时器,定时器间隔决定播放的速度
+ *        视频的宽和高使用QImage进行缩放
+ *        视频大小在直接设置这个类的resize即可,有最小大小限制
+ * 
+ * @param fileName 
+ */
+void VideoPlayer::setPlayVedio(const QString& fileName)
+{
+    m_fileName = fileName;
+    isSetVedioFile = true;
+    m_decodeVedio->initFFmpeg(m_fileName);
+    /* 获取原始视频信息 */
+    m_srcWidth = m_decodeVedio->getVideoWidth();
+    m_srcHeight = m_decodeVedio->getVideoHeight();
+    m_frameCount = m_decodeVedio->getFrameCount();
+    SPDLOG_DEBUG("视频宽:{} 高:{} 帧数:{}", m_srcWidth, m_srcHeight, m_frameCount);
+    /* 设置视频宽和高的最小大小 */
+    this->setMinimumSize(160,90);
+
+    /* 开启定时器刷新 */
+    if(m_frameCount < 0)
+    {
+        /* HEVC帧率,获取不到,就按照24帧来刷新 */
+        if(m_frameCount == -2)
+        {
+            m_frameCount = 24;
+        }
+        else {
+            m_frameCount = 25;
+        }
+    }
+    else if(m_frameCount == 0)
+    {
+        m_frameCount = 25;
+    }
+    SPDLOG_INFO("帧率:{}", m_frameCount);
+
+    /* 开启解码,手动刷新第一帧 */
+    m_decodeVedio->startDecodeVedio();
+    m_semRefresh->release(1);
+    // m_timerRefreshUI.setSingleShot(true);
+    // m_timerRefreshUI.start(500);
+}
+
+/* 播放视频 */
+bool VideoPlayer::play()
+{
+    if(!isSetVedioFile)
+    {
+        SPDLOG_ERROR("文件名为空");
+        return false;
+    }
+    if(m_playStatus)
+    {
+        return false;
+    }
+    
+    /* 设置刷新时间 */
+    m_timerRefreshUI.setSingleShot(false);
+    m_interval = 1000/m_frameCount;
+    if(1000 % m_frameCount > 5)
+    {
+        m_frameCount += 1;
+    }
+    SPDLOG_TRACE("刷新UI的定时间隔:{}",m_interval);
+    m_timerRefreshUI.start(m_interval);
+    m_playStatus = true;
+    
+    return true;
+}
+
+/* 暂停播放 */
+void VideoPlayer::pause()
+{
+    if(!m_playStatus)
+    {
+        return;
+    }
+    m_timerRefreshUI.stop();
+    m_playStatus = false;
+}
+
+/* 停止播放,停止后停止解码,清空队列,渲染一帧黑屏 */
+void VideoPlayer::stop()
+{
+    SPDLOG_DEBUG("...停止播放...");
+    m_fileName = QString();
+    if(m_timerRefreshUI.isActive())
+    {
+        m_timerRefreshUI.stop();
+    }
+    SPDLOG_DEBUG("...停止解码...");
+    m_decodeVedio->stopDecodeVedio();
+    /* 绘制黑帧 */
+    SPDLOG_DEBUG("绘制黑帧");
+    m_image = new QImage(m_nowWidth, m_nowHeight, QImage::Format_RGB32);
+    m_image->fill(Qt::black);
+    update();
+    m_playStatus = false;
+    isSetVedioFile = false;
+}
+
+/* 获取视频时长 */
+qint64 VideoPlayer::getDuration()
+{
+    return m_decodeVedio->getDuration();
+}
+
+/* 获取当前播放位置 */
+qint64 VideoPlayer::getCurrentPos()
+{
+    return m_decodeVedio->getCurrentPos();
+}
+
+/* 设置当前播放位置 */
+void VideoPlayer::setCurrentPos(quint64 pos)
+{
+    /* 先停止播放 */
+    bool temp = m_playStatus;
+    if(m_playStatus)
+    {
+        m_timerRefreshUI.stop();
+        m_playStatus = false;
+    }
+    m_decodeVedio->setCurrentPos(pos);
+    /* 继续播放 */
+    if(temp)
+    {
+        m_timerRefreshUI.start(m_interval);
+        m_playStatus = true;
+    }else
+    {
+        /* 刷新三张照片,防止第一张是跳转前的时间段 */
+        m_semRefresh->release(3);
+    }
+}
+
+
+void VideoPlayer::paintEvent(QPaintEvent *event)
+{
+    if(m_image)
+    {
+        // SPDLOG_TRACE("开始绘制画面...");
+        QPainter painter(this);
+        painter.drawImage(0, 0, *m_image);
+    }
+}
+
+void VideoPlayer::resizeEvent(QResizeEvent *event)
+{
+    SPDLOG_TRACE("窗口大小改变...");
+    m_nowWidth = event->size().width();
+    m_nowHeight = event->size().height();
+
+    QWidget::resizeEvent(event);
+}
+
+/* 刷新一张图片,直到有图片为止 */
+void VideoPlayer::refreshOneUIUntilHave()
+{
+    if(m_decodeVedio != nullptr)
+    {
+        // SPDLOG_DEBUG("取出一帧图片...");
+        /* 删除上一帧图片 */
+        if(m_image != nullptr)
+        {
+            delete m_image;
+            m_image = nullptr;
+        }
+        /* 如果没有图片,这个函数会阻塞 */
+        m_image = m_decodeVedio->getOneImageUntilHave();
+        
+        if(m_image)
+        {
+            if(m_srcWidth != m_nowWidth || m_srcHeight != m_nowHeight)
+            {
+                *m_image = m_image->scaled(m_nowWidth, m_nowHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+            }
+            // SPDLOG_DEBUG("绘制画面...");
+            update();
+        }
+        m_decodeVedio->wakeUpCondQueueNoEmpty();
+    }
+}
+
+/* 取出画面,刷新UI */
+void VideoPlayer::do_refreshUI()
+{
+    if(m_decodeVedio != nullptr)
+    {
+        // SPDLOG_DEBUG("取出一帧图片...");
+        /* 删除上一帧图片 */
+        if(m_image != nullptr)
+        {
+            delete m_image;
+            m_image = nullptr;
+        }
+        m_image = m_decodeVedio->getOneImage();
+        
+        if(m_image)
+        {
+            if(m_srcWidth != m_nowWidth || m_srcHeight != m_nowHeight)
+            {
+                *m_image = m_image->scaled(m_nowWidth, m_nowHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+            }
+            // SPDLOG_DEBUG("绘制画面...");
+            update();
+        }
+        m_decodeVedio->wakeUpCondQueueNoEmpty();
+    }
+}
+
+
+/* 通过信号刷新第一张图片 */
+void VideoPlayer::do_refreshOneUI()
+{
+    if(!m_semRefresh->tryAcquire(1))
+    {
+        return;
+    }
+    /* 取出第一张 */
+    if(m_decodeVedio != nullptr)
+    {
+        // SPDLOG_DEBUG("取出一帧图片...");
+        /* 删除上一帧图片 */
+        if(m_image != nullptr)
+        {
+            delete m_image;
+            m_image = nullptr;
+        }
+        m_image = m_decodeVedio->getOneImage();
+        
+        if(m_image)
+        {
+            if(m_srcWidth != m_nowWidth || m_srcHeight != m_nowHeight)
+            {
+                *m_image = m_image->scaled(m_nowWidth, m_nowHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+            }
+            // SPDLOG_DEBUG("绘制画面...");
+            update();
+        }
+        m_decodeVedio->wakeUpCondQueueNoEmpty();
+    }
+}
+
+

+ 57 - 0
common/VedioPlayer_/VideoPlayer.h

@@ -0,0 +1,57 @@
+#ifndef VideoPlayer_H
+#define VideoPlayer_H
+
+#include <QWidget>
+#include <QThread>
+#include <QTimer>
+#include <QSemaphore>
+
+class DecodeVedio;
+
+class VideoPlayer : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit VideoPlayer(QWidget *parent = nullptr);
+    ~VideoPlayer();
+
+    void setPlayVedio(const QString& fileName);     /* 设置播放视频 */
+    bool play();                                    /* 播放视频 */
+    void pause();                                   /* 暂停播放 */
+    void stop();                                    /* 停止播放 */
+    bool getPlayStatus() { return m_playStatus; }   /* 获取播放状态 */
+    qint64 getDuration();                           /* 获取视频时长 */
+    qint64 getCurrentPos();                         /* 获取当前播放位置 */
+    void setCurrentPos(quint64 pos);                /* 设置当前播放位置 */
+
+
+protected:
+    void paintEvent(QPaintEvent *event) override;
+    void resizeEvent(QResizeEvent *event) override;
+    void refreshOneUIUntilHave();                   /* 刷新一张图片,直到有图片为止 */
+
+private slots:
+    void do_refreshUI();                            /* 取出画面,刷新UI */
+    void do_refreshOneUI();                         /* 通过信号刷新第一张图片 */
+
+private:
+    QString m_fileName;
+    QTimer m_timerRefreshUI;                        /* 定时器,用于刷新界面 */
+    int m_srcWidth = 0;                             /* 图片原本大小 */
+    int m_srcHeight = 0;
+    int m_nowWidth = 0;                             /* 现在大小 */
+    int m_nowHeight = 0;
+    int m_frameCount = 0;                           /* 帧数 */
+    int m_interval = 0;                             /* 间隔 */
+
+    DecodeVedio* m_decodeVedio = nullptr;
+    QThread* m_threadDecode = nullptr;              /* 解码器所在的线程 */
+    QImage* m_image = nullptr;                      /* 画面 */
+    bool m_playStatus = false;                      /* 是否正在播放 */
+    bool isSetVedioFile = false;                    /* 是否设置了视频文件 */
+    QSemaphore* m_semRefresh = nullptr;             /* 刷新信号量 */
+};
+
+
+
+#endif /* VideoPlayer_H */