瀏覽代碼

V0.7.17
1、完成了使用ffmpeg解码,使用OpenGL刷新视频的功能

apple 6 天之前
父節點
當前提交
239f0c8853

+ 0 - 7
.vscode/launch.json

@@ -1,7 +0,0 @@
-{
-    // 使用 IntelliSense 了解相关属性。 
-    // 悬停以查看现有属性的描述。
-    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": []
-}

+ 1 - 1
CMakeLists.txt

@@ -192,7 +192,7 @@ file(GLOB GLOBAL_SRC
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/OneThread)
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/timer)
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/time)
-# add_subdirectory(${CMAKE_SOURCE_DIR}/demo/VideoPlayer)
+add_subdirectory(${CMAKE_SOURCE_DIR}/demo/VideoPlayer)
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/xlsx)
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/DesignerPattern)
 # add_subdirectory(${CMAKE_SOURCE_DIR}/demo/ViewModel)

+ 1 - 1
External

@@ -1 +1 @@
-Subproject commit fe6dd8a6f8ca766c91e76b962e51deaf4e515c69
+Subproject commit 09efc215b49654fa52fdcbd0422fb8abf63ce274

+ 7 - 4
demo/OpenGLWidget/CMakeLists.txt

@@ -57,6 +57,13 @@ target_link_libraries(${libName} PRIVATE
 
 )
 
+target_link_libraries(${libName} PRIVATE
+    # ${CURL_LIBRARY}
+    ${FFMPEG_LIBRARY}
+    ${spdlog_LIBRARY}
+
+)
+
 if(QT_VERSION_MAJOR EQUAL 5)
     target_link_libraries(${libName} PRIVATE
         Qt5::OpenGL
@@ -69,14 +76,10 @@ endif(QT_VERSION_MAJOR EQUAL 5)
 
 if(CMAKE_SYSTEM_NAME MATCHES "Windows")
     target_link_libraries(${libName} PRIVATE 
-        # ${CURL_LIBRARY}
-        # ${FFMPEG_LIBRARY}
-        ${spdlog_LIBRARY}
         opengl32
     )
 elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
     target_link_libraries(${libName} PRIVATE 
-        ${spdlog_LIBRARY}
         GL
     )
 endif()

+ 1 - 1
demo/OpenGLWidgetTest/OpenGLWidgetAPI/OpenGLWidgetAPI.cpp

@@ -78,7 +78,7 @@ bool loadOpenGLWidgetLibrary()
 
     QString libPath = QApplication::applicationDirPath();
 #ifdef Q_OS_WIN
-    libPath += "/OpenGLWidget.dll";
+    libPath += "/libOpenGLWidget.dll";
 #elif defined(Q_OS_MACOS)
     libPath += "/libOpenGLWidget.dylib";
 #elif defined(Q_OS_LINUX)

+ 246 - 153
demo/OpenGLWidgetTest/VideoPlayer/DecodeVedio.cpp

@@ -1,17 +1,20 @@
 #include "DecodeVedio.h"
+#include "FrameFormat.h"
 #include "spdlog/spdlog.h"
-#include "FmtLog/fmtlog.h"
 
 #include <QThread>
 
 extern "C"
 {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
+// #include <libavcodec/avcodec.h>
+// #include <libavformat/avformat.h>
 #include <libswscale/swscale.h>
 #include <libavutil/imgutils.h>
 }
 
+#include "OpenGLWidgetAPI.h"
+
+
 /*=================================================================================================
 * @brief  FFmpeg获取GPU硬件解码帧格式的回调函数
 ===================================================================================================*/
@@ -136,7 +139,7 @@ void DecodeVedio::setCurrentPos(qint64 pos)
     
     SPDLOG_DEBUG("跳转到:{}ms",pos);
     /*在环形队列中有已解码的视频帧数,需要去掉已有的帧数所占的时间 */
-    pos = pos - m_queueImage.QueueSize() * (1000 / m_fps);
+    pos = pos - m_queueFrame.QueueSize() * (1000 / m_fps);
     if(pos < 0) {
         pos = 0;
     }
@@ -156,16 +159,7 @@ void DecodeVedio::setCurrentPos(qint64 pos)
     m_flushDecoder.store(true);
     /* 清空环形队列中的视频 */
     SPDLOG_DEBUG("清空环形队列中的视频。");
-    QImage* image = 0;
-    while (m_queueImage.QueueSize() > 0)
-    {
-        image = nullptr;
-        m_queueImage.front_pop_NoBlock(image);
-        if(image != nullptr)
-        {
-            delete image;
-        }
-    }
+    clearQueueFrame();
     
     /* 继续解码 */
     continueDecode();
@@ -188,18 +182,18 @@ qint64 DecodeVedio::getDuration()
  * @warning 传出这个指针后,队列就出队了,内存需要外面获取的实例释放
  * @return QImage* 一帧图像的指针
  */
-QImage* DecodeVedio::getOneImage()
+FrameBase* DecodeVedio::getOneImage()
 {
     if(!m_threadRuning)
     {
         return nullptr;
     }
-    QImage* image = nullptr;
-    if(!m_queueImage.front_pop_NoBlock(image))
-    {
-        return nullptr;
-    }
-    return image;
+    // FrameBase* frame = nullptr;
+    // if(!m_queueFrame.front_pop_NoBlock(frame))
+    // {
+    //     return nullptr;
+    // }
+    return m_queueFrame.front_pop_NoBlock();
 }
 
 /**
@@ -208,7 +202,7 @@ QImage* DecodeVedio::getOneImage()
  * @param timeOut 设为-1是一直等待,设置正整数是等待的时间,单位ms
  * @return QImage* 
  */
-QImage* DecodeVedio::getOneImageUntilHave(int timeOut)
+FrameBase* DecodeVedio::getOneImageUntilHave(int timeOut)
 {
     if(!m_threadRuning)
     {
@@ -216,15 +210,15 @@ QImage* DecodeVedio::getOneImageUntilHave(int timeOut)
     }
     if(timeOut < 0)
     {
-        QImage* image = m_queueImage.front_pop();
-        return image;
+        auto frame = m_queueFrame.front_pop();
+        return frame;
     }
+    FrameBase* frame = nullptr;
     for(int i = 0; i < timeOut; i++)
     {
-        QImage* image = nullptr;
-        if(m_queueImage.front_pop_NoBlock(image))
+        if(m_queueFrame.front_pop_NoBlock(frame))
         {
-            return image;
+            return frame;
         }
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
@@ -248,6 +242,28 @@ void DecodeVedio::findHWDecoder(QStringList& listDecoderName)
     }
 }
 
+/* 清空环形队列 */
+void DecodeVedio::clearQueueFrame()
+{
+    while(m_queueFrame.QueueSize() > 0)
+    {
+        FrameBase* frame = nullptr;
+        m_queueFrame.front_pop_NoBlock(frame);
+        deleteOneFrame(frame);
+    }
+    
+}
+
+/* 删除一个图片 */
+void DecodeVedio::deleteOneImage()
+{
+    if(m_queueFrame.QueueSize() > 0)
+    {
+        auto frame = m_queueFrame.front_pop_NoBlock();
+        deleteOneFrame(frame);
+    }
+}
+
 
 /* 获取硬件解码器 */
 void DecodeVedio::findHWDecoder()
@@ -271,6 +287,8 @@ void DecodeVedio::findHWDecoder()
     }else {
         m_supportHWDecoder = true;
     }
+
+    m_supportHWDecoder = false;
 }
 
 
@@ -357,19 +375,9 @@ bool DecodeVedio::openVedio(const QString& fileName)
     }
 
     /* 清空队列 */
-    if(m_queueImage.QueueSize() > 0)
-    {
-        for(int i = 0; i < m_queueImage.QueueSize(); i++)
-        {
-            QImage* image = nullptr;
-            if (m_queueImage.front_pop_NoBlock(image))
-            {
-                delete image;
-            }
-        }
-        m_queueImage.clearQueue();
-    }
-    m_queueImage.setQueueCapacity(30);
+    clearQueueFrame();
+    m_queueFrame.setQueueCapacity(30);
+    m_queueFrame.setDefaultValue(nullptr);
 
     SPDLOG_DEBUG("开始初始化FFMPEG");
     /* 设置网络缓冲区大小和错误恢复选项 */
@@ -549,9 +557,9 @@ void DecodeVedio::threadDecodeUsingCPU()
 {
     /******** 初始化局部变量 ********/
     bool isEnd = false;
-    int ret = 0;
-    int retFrame = 0;
-    int retPacket = 0;
+    // int ret = 0;
+    // int retFrame = 0;
+    // int retPacket = 0;
     m_pauseDecode = false;
     m_decodeStatus = true;
     m_decodeState.store(DecodeState::DecodeRun);
@@ -664,48 +672,52 @@ void DecodeVedio::threadDecodeUsingCPU()
             }
             // SPDLOG_DEBUG("当前帧的pts:{}", m_pts.load());
 
-            /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
-            if(m_sws_ctx == nullptr)
-            {
-                /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
-                m_sws_ctx = sws_getCachedContext(m_sws_ctx, 
-                                                m_pFrameSRC->width, m_pFrameSRC->height,    /* 原图像大小和格式 */
-                                                m_pCodecContext->pix_fmt,                   /* 输入图像的像素格式 */
-                                                m_srcSize.width(), m_srcSize.height(),      /* 目标图像的大小 */
-                                                AV_PIX_FMT_RGBA,                            /* 目标图像的格式 */
-                                                SWS_BILINEAR,                               /* 图像缩放算法,双线性 */
-                                                nullptr,                                    /* 输入图像的滤波器信息,不需要传NULL */
-                                                nullptr,                                    /* 输出图像的滤波器信息,不需要传NULL */
-                                                nullptr);                                   /* 特定缩放算法需要的参数,不需要传NULL */
-                if(m_sws_ctx == nullptr)
-                {
-                    SPDLOG_ERROR("创建SwsContext错误...");
-                    goto label_ThreadDecodeExit;
-                }
-                SPDLOG_INFO("创建SwsContext成功...");
-            }
-            /* 转换成RGBA格式 */
-            // uint8_t* data[1] = { m_buffer };
-            int lines[4];
-            /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
-            av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameSRC->width);
-
-            sws_scale(  m_sws_ctx,              /* 缩放的上下文 */
-                        m_pFrameSRC->data,      /* 源图像数组 */
-                        m_pFrameSRC->linesize,  /* 包含源图像每个平面步幅的数组 */
-                        0,                      /* 开始位置 */
-                        m_pFrameSRC->height,    /* 行数 */
-                        &m_buffer,                   /* 目标图像数组 */
-                        lines); /* 目标图像行数 */
-            if(m_buffer != nullptr)
-            {
-                /* 将数据拷贝到QImage中 */
-                auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
-                /* 如果队列满,线程会阻塞在这里 */
-                m_queueImage.push(image);
-                // av_frame_unref(m_pFrameRGB);
-                // SPDLOG_DEBUG("一帧视频入队");
-            }
+            // /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
+            // if(m_sws_ctx == nullptr)
+            // {
+            //     /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
+            //     m_sws_ctx = sws_getCachedContext(m_sws_ctx, 
+            //                                     m_pFrameSRC->width, m_pFrameSRC->height,    /* 原图像大小和格式 */
+            //                                     m_pCodecContext->pix_fmt,                   /* 输入图像的像素格式 */
+            //                                     m_srcSize.width(), m_srcSize.height(),      /* 目标图像的大小 */
+            //                                     AV_PIX_FMT_RGBA,                            /* 目标图像的格式 */
+            //                                     SWS_BILINEAR,                               /* 图像缩放算法,双线性 */
+            //                                     nullptr,                                    /* 输入图像的滤波器信息,不需要传NULL */
+            //                                     nullptr,                                    /* 输出图像的滤波器信息,不需要传NULL */
+            //                                     nullptr);                                   /* 特定缩放算法需要的参数,不需要传NULL */
+            //     if(m_sws_ctx == nullptr)
+            //     {
+            //         SPDLOG_ERROR("创建SwsContext错误...");
+            //         goto label_ThreadDecodeExit;
+            //     }
+            //     SPDLOG_INFO("创建SwsContext成功...");
+            // }
+            // /* 转换成RGBA格式 */
+            // // uint8_t* data[1] = { m_buffer };
+            // int lines[4];
+            // /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
+            // av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameSRC->width);
+
+            // sws_scale(  m_sws_ctx,              /* 转换上下文 */
+            //             m_pFrameSRC->data,      /* 源图像数据指针数组 */
+            //             m_pFrameSRC->linesize,  /* 源图像每行字节数数组 */
+            //             0,                      /* 源图像起始行(通常为0) */
+            //             m_pFrameSRC->height,    /* 处理的行数(通常为图像高度) */
+            //             &m_buffer,              /* 目标图像数据指针数组(packed格式只用[0]) */
+            //             lines);                 /* 目标图像每行字节数数组 */
+            // if(m_buffer != nullptr)
+            // {
+            //     /* 将数据拷贝到QImage中 */
+            //     auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
+            //     /* 如果队列满,线程会阻塞在这里 */
+            //     m_queueRGBA.push(image);
+            //     // av_frame_unref(m_pFrameRGB);
+            //     // SPDLOG_DEBUG("一帧视频入队");
+            // }
+
+            /* 对生成的图像进行格式转换,并添加到环形队列中 */
+            convertImageFormatAndPushToQueue(m_pFrameSRC);
+
             av_frame_unref(m_pFrameSRC);
             /* 如果在跳转过程中,直接退出,防止一个packet中有多个视频帧,再次阻塞在上面 */
             if(m_isSeek)
@@ -744,7 +756,7 @@ void DecodeVedio::exitThread()
     m_decodeState.store(DecodeState::DecodeRun);
     m_pauseDecode = false;
     /* 先退出可能阻塞住的解码线程 */
-    m_queueImage.exit();
+    m_queueFrame.exit();
 }
 
 /**
@@ -755,9 +767,9 @@ void DecodeVedio::threadDecodeUsingGPU()
 {
     /******** 初始化局部变量 ********/
     bool isEnd = false;
-    int ret = 0;
-    int retFrame = 0;
-    int retPacket = 0;
+    // int ret = 0;
+    // int retFrame = 0;
+    // int retPacket = 0;
     m_pauseDecode = false;
     m_decodeStatus = true;
     m_decodeState.store(DecodeState::DecodeRun);
@@ -879,47 +891,49 @@ void DecodeVedio::threadDecodeUsingGPU()
             // SPDLOG_DEBUG("当前帧的pts:{}", m_pts.load());
 
             /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
-            if(m_sws_ctx == nullptr)
-            {
-                /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
-                m_sws_ctx = sws_getCachedContext(m_sws_ctx, 
-                                                m_pFrameHW->width, m_pFrameHW->height,    /* 原图像大小和格式 */
-                                                m_pCodecContext->pix_fmt,                   /* 输入图像的像素格式 */
-                                                m_srcSize.width(), m_srcSize.height(),      /* 目标图像的大小 */
-                                                AV_PIX_FMT_RGBA,                            /* 目标图像的格式 */
-                                                SWS_BILINEAR,                               /* 图像缩放算法,双线性 */
-                                                nullptr,                                    /* 输入图像的滤波器信息,不需要传NULL */
-                                                nullptr,                                    /* 输出图像的滤波器信息,不需要传NULL */
-                                                nullptr);                                   /* 特定缩放算法需要的参数,不需要传NULL */
-                if(m_sws_ctx == nullptr)
-                {
-                    SPDLOG_ERROR("创建SwsContext错误...");
-                    goto label_ThreadDecodeExit;
-                }
-                SPDLOG_INFO("创建SwsContext成功...");
-            }
-            /* 转换成RGBA格式 */
-            // uint8_t* data[1] = { m_buffer };
-            int lines[4];
-            /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
-            av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameHW->width);
-
-            sws_scale(  m_sws_ctx,              /* 缩放的上下文 */
-                        m_pFrameHW->data,      /* 源图像数组 */
-                        m_pFrameHW->linesize,  /* 包含源图像每个平面步幅的数组 */
-                        0,                      /* 开始位置 */
-                        m_pFrameHW->height,    /* 行数 */
-                        &m_buffer,                   /* 目标图像数组 */
-                        lines); /* 目标图像行数 */
-            if(m_buffer != nullptr)
-            {
-                /* 将数据拷贝到QImage中 */
-                auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
-                /* 如果队列满,线程会阻塞在这里 */
-                m_queueImage.push(image);
-                // av_frame_unref(m_pFrameRGB);
-                // SPDLOG_DEBUG("一帧视频入队");
-            }
+            // if(m_sws_ctx == nullptr)
+            // {
+            //     /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
+            //     m_sws_ctx = sws_getCachedContext(m_sws_ctx, 
+            //                                     m_pFrameHW->width, m_pFrameHW->height,    /* 原图像大小和格式 */
+            //                                     m_pCodecContext->pix_fmt,                   /* 输入图像的像素格式 */
+            //                                     m_srcSize.width(), m_srcSize.height(),      /* 目标图像的大小 */
+            //                                     AV_PIX_FMT_RGBA,                            /* 目标图像的格式 */
+            //                                     SWS_BILINEAR,                               /* 图像缩放算法,双线性 */
+            //                                     nullptr,                                    /* 输入图像的滤波器信息,不需要传NULL */
+            //                                     nullptr,                                    /* 输出图像的滤波器信息,不需要传NULL */
+            //                                     nullptr);                                   /* 特定缩放算法需要的参数,不需要传NULL */
+            //     if(m_sws_ctx == nullptr)
+            //     {
+            //         SPDLOG_ERROR("创建SwsContext错误...");
+            //         goto label_ThreadDecodeExit;
+            //     }
+            //     SPDLOG_INFO("创建SwsContext成功...");
+            // }
+            // /* 转换成RGBA格式 */
+            // // uint8_t* data[1] = { m_buffer };
+            // int lines[4];
+            // /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
+            // av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, m_pFrameHW->width);
+
+            // sws_scale(  m_sws_ctx,              /* 缩放的上下文 */
+            //             m_pFrameHW->data,      /* 源图像数组 */
+            //             m_pFrameHW->linesize,  /* 包含源图像每个平面步幅的数组 */
+            //             0,                      /* 开始位置 */
+            //             m_pFrameHW->height,    /* 行数 */
+            //             &m_buffer,                   /* 目标图像数组 */
+            //             lines); /* 目标图像行数 */
+            // if(m_buffer != nullptr)
+            // {
+            //     /* 将数据拷贝到QImage中 */
+            //     auto image = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
+            //     /* 如果队列满,线程会阻塞在这里 */
+            //     m_queueRGBA.push(image);
+            //     // av_frame_unref(m_pFrameRGB);
+            //     // SPDLOG_DEBUG("一帧视频入队");
+            // }
+            convertImageFormatAndPushToQueue(m_pFrameHW);
+
             av_frame_unref(m_pFrameSRC);
             /* 如果在跳转过程中,直接退出,防止一个packet中有多个视频帧,再次阻塞在上面 */
             if(m_isSeek)
@@ -947,6 +961,94 @@ label_ThreadDecodeExit:
     m_threadRuning = false;
 }
 
+
+/* 对生成的图像进行格式转换,并添加到环形队列中 */
+void DecodeVedio::convertImageFormatAndPushToQueue(AVFrame* pFrame)
+{
+    /* 判断视频帧格式 */
+    if(pFrame->format == AV_PIX_FMT_YUV420P)
+    {
+        m_frameFormat = EFrameFormat::Frame_YUV420P;
+        /* 是yuv420p格式, */
+        Frame_YUV420P* frame = new Frame_YUV420P();
+        convertYUV420PToImage(pFrame, frame->frameData);
+        m_queueFrame.push(frame);
+    } else
+    {
+        /* 不是420p,转换成RGBA格式 */
+        m_frameFormat = EFrameFormat::Frame_RGBA8888;
+        convertToRGBA(pFrame);
+        if(m_buffer != nullptr)
+        {
+            Frame_RGBA8888* frame = new Frame_RGBA8888();
+            /* 将数据拷贝到QImage中 */
+            frame->frameData = new QImage(m_buffer, m_srcSize.width(), m_srcSize.height(), QImage::Format_RGBA8888);
+            /* 如果队列满,线程会阻塞在这里 */
+            m_queueFrame.push(frame);
+        }
+    }
+}
+
+/* 将yuv420p的uint8_t数组缓存转换成Image_YUV420P结构体 */
+void DecodeVedio::convertYUV420PToImage(AVFrame* pFrame, Image_YUV420P* image)
+{
+    if(pFrame == nullptr || image == nullptr)
+    {
+        return;
+    }
+    if(pFrame->data[0] == nullptr || pFrame->data[1] == nullptr || pFrame->data[2] == nullptr)
+    {
+        return;
+    }
+
+    image->width = pFrame->width;
+    image->height = pFrame->height;
+    uint32_t ySzie = pFrame->width * pFrame->height;  /* Y分量的大小 */
+    image->yData.append(reinterpret_cast<const char*>(pFrame->data[0]), ySzie);
+    image->uData.append(reinterpret_cast<const char*>(pFrame->data[1]), ySzie / 4);
+    image->vData.append(reinterpret_cast<const char*>(pFrame->data[2]), ySzie / 4);
+}
+
+/* 转换成RGBA格式 */
+void DecodeVedio::convertToRGBA(AVFrame* pFrame)
+{
+    /* 转换解码后的帧格式,转换成RGBA格式,Qt可以识别 */
+    if(m_sws_ctx == nullptr)
+    {
+        /* 选择在这里创建,为了兼容硬件解码,硬件解码出来的格式可能和解码器传出的不一样 */
+        m_sws_ctx = sws_getCachedContext(m_sws_ctx, 
+                                        pFrame->width, pFrame->height,              /* 原图像大小和格式 */
+                                        m_pCodecContext->pix_fmt,                   /* 输入图像的像素格式 */
+                                        m_srcSize.width(), m_srcSize.height(),      /* 目标图像的大小 */
+                                        AV_PIX_FMT_RGBA,                            /* 目标图像的格式 */
+                                        SWS_BILINEAR,                               /* 图像缩放算法,双线性 */
+                                        nullptr,                                    /* 输入图像的滤波器信息,不需要传NULL */
+                                        nullptr,                                    /* 输出图像的滤波器信息,不需要传NULL */
+                                        nullptr);                                   /* 特定缩放算法需要的参数,不需要传NULL */
+        if(m_sws_ctx == nullptr)
+        {
+            SPDLOG_ERROR("创建SwsContext错误...");
+            /* 释放空间 */
+            av_packet_free(&m_packet);
+            m_decodeState.store(DecodeState::DecodeExit);
+            m_threadRuning = false;
+        }
+        SPDLOG_INFO("创建SwsContext成功...");
+    }
+    /* 转换成RGBA格式 */
+    // uint8_t* data[1] = { m_buffer };
+    int lines[4];
+    /* 使用像素格式pix_fmt和宽度填充图像的平面线条的大小(一行大小?) */
+    av_image_fill_linesizes(lines, AV_PIX_FMT_RGBA, pFrame->width);
+    sws_scale(  m_sws_ctx,              /* 转换上下文 */
+                pFrame->data,           /* 源图像数据指针数组 */
+                pFrame->linesize,       /* 源图像每行字节数数组 */
+                0,                      /* 源图像起始行(通常为0) */
+                pFrame->height,         /* 处理的行数(通常为图像高度) */
+                &m_buffer,              /* 目标图像数据指针数组(packed格式只用[0]) */
+                lines);                 /* 目标图像每行字节数数组 */
+}
+
 /* 暂停解码,会阻塞到线程暂停为止 */
 void DecodeVedio::pauseDecode()
 {
@@ -963,19 +1065,8 @@ void DecodeVedio::pauseDecode()
     m_decodeState.store(DecodeState::DecodeRun);
     m_pauseDecode = true;
     /* 队列出队两张图片,防止解码线程阻塞到环形队列满上面 */
-    QImage* image = nullptr;
-    m_queueImage.front_pop_NoBlock(image);
-    if(image != nullptr)
-    {
-        delete image;
-        image = nullptr;
-    }
-    m_queueImage.front_pop_NoBlock(image);
-    if(image != nullptr)
-    {
-        delete image;
-        image = nullptr;
-    }
+    deleteOneImage();
+    deleteOneImage();
     /* 等待线程状态变为暂停为止 */
     while (m_decodeState.load() != DecodeState::DecodePause)
     {
@@ -1013,7 +1104,16 @@ void DecodeVedio::do_startDecodeVedio()
     m_threadRuning = true;
     m_pauseDecode = false;
     /* 进入解码,直到播放完成或者手动退出 */
-    threadDecodeUsingCPU();
+    if(m_supportHWDecoder)
+    {
+        SPDLOG_TRACE("使用硬件解码器进行解码...");
+        threadDecodeUsingGPU();
+    }else 
+    {
+        SPDLOG_INFO("使用CPU进行解码...");
+        threadDecodeUsingCPU();
+    }
+    
     SPDLOG_TRACE("Decode解码结束。");
 
 }
@@ -1060,15 +1160,8 @@ void DecodeVedio::freeAll()
         av_buffer_unref(&m_hw_device_ctx);
     }
 
-    for(int i = 0; i < m_queueImage.QueueSize(); i++)
-    {
-        QImage* image = nullptr;
-        if (m_queueImage.front_pop_NoBlock(image))
-        {
-            delete image;
-        }
-    }
-    m_queueImage.clearQueue();
+    clearQueueFrame();
+    m_queueFrame.clearQueue();
 }
 
 

+ 22 - 7
demo/OpenGLWidgetTest/VideoPlayer/DecodeVedio.h

@@ -9,12 +9,12 @@
 #include <QImage>
 
 #include "RingQueue/RingQueue.hpp"
+#include "FrameFormat.h"
 
-// #include "threadcontroller.h"
 
 extern "C"
 {
-// #include <libavcodec/avcodec.h>
+#include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 // #include <libswscale/swscale.h>
 // #include <libavutil/imgutils.h>
@@ -22,6 +22,8 @@ extern "C"
 
 
 
+
+
 /**
  * 使用方式:
  *      1. 初始化FFmpeg:initFFmpeg()
@@ -69,9 +71,9 @@ public:
     
 
     /* 获取一帧图像 */
-    QImage* getOneImage();
+    FrameBase* getOneImage();
     /* 获取一帧图像,直到有图像为止,可以设置超时时间 */
-    QImage* getOneImageUntilHave(int timeOut = -1);
+    FrameBase* getOneImageUntilHave(int timeOut = -1);
 
     /* 获取帧数 */
     int getFPS() const { return m_fps; }
@@ -90,7 +92,14 @@ signals:
     void signal_oneImage();                         /* 一帧图像信号 */
     void signal_playCompleted();                    /* 播放完成信号 */
     void signal_startDecode();                      /* 开始解码信号 */
+    
 private:
+    /* 清空环形队列 */
+    void clearQueueFrame();
+    /* 删除一帧图片 */
+    void deleteOneImage();
+
+    /*----------------------- 解码相关的函数 ------------------------*/
     /* 查找硬件解码器 */
     void findHWDecoder();
     /* 初始化硬件解码器 */
@@ -99,9 +108,15 @@ private:
     bool copyDataFromGPU(AVFrame* pFrameHW, AVFrame* pFrameSRC);
                 
     /* 软解码线程 */       
-    void threadDecodeUsingCPU();     
+    void threadDecodeUsingCPU();
     /* 硬件解码线程 */
     void threadDecodeUsingGPU();
+    /* 对生成的图像进行格式转换,并添加到环形队列中 */
+    inline void convertImageFormatAndPushToQueue(AVFrame* pFrame);
+    /* 将yuv420p的uint8_t数组缓存转换成Image_YUV420P结构体 */
+    inline void convertYUV420PToImage(AVFrame* pFrame, Image_YUV420P* image);
+    /* 转换成RGBA格式 */
+    inline void convertToRGBA(AVFrame* pFrame);
     /* 退出线程 */      
     void exitThread();
     /* 暂停解码 */
@@ -157,8 +172,8 @@ private:
     qint64 m_currentFrame = 0;                      /* 当前已播放的帧数 */
     QString m_decoderName;                          /* 解码器名称 */
 
-    RingQueue<QImage*> m_queueImage;                /* 环形队列,存储生成的图像 */
-    
+    EFrameFormat m_frameFormat = EFrameFormat::Frame_YUV420P; /* 视频帧格式,默认是YUV420P */
+    RingQueue<FrameBase*> m_queueFrame;             /* 环形队列,存储生成的图像 */
 };
 
 

+ 61 - 0
demo/OpenGLWidgetTest/VideoPlayer/FrameFormat.cpp

@@ -0,0 +1,61 @@
+#include "FrameFormat.h"
+
+#include "OpenGLWidgetAPI.h"
+
+#include "spdlog/spdlog.h"
+
+
+
+Frame_YUV420P::Frame_YUV420P() : FrameBase(EFrameFormat::Frame_YUV420P)
+{
+    frameData = new Image_YUV420P();
+}
+
+Frame_YUV420P::~Frame_YUV420P()
+{
+    if(frameData != nullptr)
+    {
+        delete frameData;
+        frameData = nullptr;
+    }
+}
+
+Frame_RGBA8888::Frame_RGBA8888()
+{
+    frameData = new QImage();
+}
+
+Frame_RGBA8888::~Frame_RGBA8888()
+{
+    if(frameData != nullptr)
+    {
+        delete frameData;
+        frameData = nullptr;
+    }
+}
+
+/* 删除一个图片 */
+bool deleteOneFrame(FrameBase* frame)
+{
+    if(frame == nullptr)
+    {
+        SPDLOG_WARN("传入的帧指针为空,无法删除");
+        return false;
+    }
+    if(frame->frameFormat() == EFrameFormat::Frame_YUV420P)
+    {
+        Frame_YUV420P* yuvFrame = static_cast<Frame_YUV420P*>(frame);
+        delete yuvFrame;
+    }else if(frame->frameFormat() == EFrameFormat::Frame_RGBA8888)
+    {
+        Frame_RGBA8888* rgbaFrame = static_cast<Frame_RGBA8888*>(frame);
+        delete rgbaFrame;
+    } else {
+        SPDLOG_WARN("未知的帧格式,无法删除。");
+        return false;
+    }
+
+    return true;
+}
+
+

+ 61 - 0
demo/OpenGLWidgetTest/VideoPlayer/FrameFormat.h

@@ -0,0 +1,61 @@
+#ifndef _FRAMEFORMAT_H_
+#define _FRAMEFORMAT_H_
+
+#include <QImage>
+
+
+struct Image_YUV420P;
+
+/**
+ * @brief 支持的帧格式枚举
+ * 
+ */
+enum class EFrameFormat
+{
+    Frame_YUV420P = 0,  /* YUV420P格式 */
+    Frame_RGBA8888,     /* RGBA格式 */
+};
+
+/**
+ * @brief 帧数据基础类
+ * 
+ */
+struct FrameBase
+{
+protected:
+    EFrameFormat format; /* 帧格式 */
+
+public:
+    FrameBase(EFrameFormat fmt = EFrameFormat::Frame_YUV420P) : format(fmt) { }
+    virtual ~FrameBase() { } /* 虚析构函数,确保派生类的析构函数被调用 */
+
+    EFrameFormat frameFormat() const { return format; } /* 获取帧格式 */
+};
+
+/**
+ * @brief YUV420P格式的帧数据结构
+ * 
+ */
+struct Frame_YUV420P : public FrameBase
+{
+    Image_YUV420P* frameData = nullptr;     /* YUV420P格式的图像数据 */
+
+    Frame_YUV420P();
+    ~Frame_YUV420P() override;
+};
+
+struct Frame_RGBA8888 : public FrameBase
+{
+    QImage* frameData = nullptr;            /* RGBA格式的图像数据 */
+
+    Frame_RGBA8888();
+    ~Frame_RGBA8888() override;
+};
+
+
+/* 删除一个图片 */
+bool deleteOneFrame(FrameBase* frame);
+
+
+
+#endif /* _FRAMEFORMAT_H_ */

+ 97 - 75
demo/OpenGLWidgetTest/VideoPlayer/VideoPlayer.cpp

@@ -7,6 +7,7 @@
 #include <QEventLoop>
 #include <QVBoxLayout>
 
+#include "FrameFormat.h"
 #include "OpenGLWidgetAPI.h"
 
 #include "spdlog/spdlog.h"
@@ -211,11 +212,11 @@ void VideoPlayer::stop()
     m_isOpenFile = false;
     /* 绘制黑帧 */
     SPDLOG_DEBUG("绘制黑帧");
-    m_image = new QImage(m_nowWidth, m_nowHeight, QImage::Format_RGB32);
-    m_image->fill(Qt::black);
+    QImage image(m_nowWidth, m_nowHeight, QImage::Format_RGBA8888);
+    image.fill(Qt::black);
     // update();
     
-    refreshRGBAImage(m_playWidget, *m_image);
+    refreshRGBAImage(m_playWidget, image);
 }
 
 /* 后退,单位ms */
@@ -480,24 +481,61 @@ void VideoPlayer::refreshOneUIUntilHave()
     if(m_decodeVedio != nullptr)
     {
         // SPDLOG_DEBUG("取出一帧图片...");
-        /* 删除上一帧图片 */
-        if(m_image != nullptr)
+        // /* 删除上一帧图片 */
+        // if(m_image != nullptr)
+        // {
+        //     delete m_image;
+        //     m_image = nullptr;
+        // }
+        // /* 如果没有图片,这个函数会阻塞 */
+        // m_image = m_decodeVedio->getOneImageUntilHave();
+        
+        // if(m_image)
+        // {
+        //     if(m_image->isNull())
+        //     {
+        //         SPDLOG_WARN("取出的图片为空...");
+        //         return;
+        //     }
+        //     // SPDLOG_DEBUG("绘制画面...");
+        //     refreshRGBAImage(m_playWidget, *m_image);
+        // }
+        refreshFrame(m_decodeVedio->getOneImageUntilHave());
+    }
+}
+
+/* 刷新一帧 */
+void VideoPlayer::refreshFrame(FrameBase* frame)
+{
+    if(frame == nullptr)
+    {
+        SPDLOG_WARN("刷新一帧,传入的帧为空");
+        return;
+    }
+    /* 删除上一帧图片 */
+    deleteOneFrame(m_image);
+    m_image = frame;
+    frame = nullptr;  // 防止外部继续使用这个帧
+
+    if(m_image->frameFormat() == EFrameFormat::Frame_RGBA8888)
+    {
+        QImage* rgbaImage = static_cast<Frame_RGBA8888*>(m_image)->frameData;
+        if(rgbaImage != nullptr && !rgbaImage->isNull())
         {
-            delete m_image;
-            m_image = nullptr;
+            refreshRGBAImage(m_playWidget, *rgbaImage);
+        }else {
+            SPDLOG_WARN("刷新一帧,RGBA图片为空");
         }
-        /* 如果没有图片,这个函数会阻塞 */
-        m_image = m_decodeVedio->getOneImageUntilHave();
-        
-        if(m_image)
+    }
+    else if(m_image->frameFormat() == EFrameFormat::Frame_YUV420P)
+    {
+        /* YUV420P格式的图片 */
+        Frame_YUV420P* yuvImage = static_cast<Frame_YUV420P*>(m_image);
+        if(yuvImage != nullptr && yuvImage->frameData != nullptr)
         {
-            if(m_image->isNull())
-            {
-                SPDLOG_WARN("取出的图片为空...");
-                return;
-            }
-            // SPDLOG_DEBUG("绘制画面...");
-            refreshRGBAImage(m_playWidget, *m_image);
+            refreshYUV420Image(m_playWidget, *yuvImage->frameData);
+        }else {
+            SPDLOG_WARN("刷新一帧,YUV420P图片为空");
         }
     }
 }
@@ -524,25 +562,7 @@ 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_image->isNull())
-            {
-                SPDLOG_WARN("取出的图片为空...");
-                return;
-            }
-            // SPDLOG_DEBUG("绘制画面...");
-            refreshRGBAImage(m_playWidget, *m_image);
-        }
-        // m_decodeVedio->wakeUpCondQueueNoEmpty();
+        refreshFrame(m_decodeVedio->getOneImage());
     }
 }
 
@@ -560,24 +580,25 @@ void VideoPlayer::do_refreshSamImage()
         if(m_decodeVedio != nullptr)
         {
             // SPDLOG_DEBUG("取出一帧图片...");
-            /* 删除上一帧图片 */
-            if(m_image != nullptr)
-            {
-                delete m_image;
-                m_image = nullptr;
-            }
-            /* 等待图片,最多等待50ms */
-            m_image = m_decodeVedio->getOneImageUntilHave(100);
-            if(m_image)
-            {
-                if(m_image->isNull())
-                {
-                    SPDLOG_WARN("取出的图片为空...");
-                    return;
-                }
-                SPDLOG_DEBUG("绘制预览画面。");
-                refreshRGBAImage(m_playWidget, *m_image);
-            }
+            // /* 删除上一帧图片 */
+            // if(m_image != nullptr)
+            // {
+            //     delete m_image;
+            //     m_image = nullptr;
+            // }
+            // /* 等待图片,最多等待50ms */
+            // m_image = m_decodeVedio->getOneImageUntilHave(100);
+            // if(m_image)
+            // {
+            //     if(m_image->isNull())
+            //     {
+            //         SPDLOG_WARN("取出的图片为空...");
+            //         return;
+            //     }
+            //     SPDLOG_DEBUG("绘制预览画面。");
+            //     refreshRGBAImage(m_playWidget, *m_image);
+            // }
+            refreshFrame(m_decodeVedio->getOneImageUntilHave(100));
         }
     }
 }
@@ -592,27 +613,28 @@ void VideoPlayer::do_playCompleted()
     {
         if(m_decodeVedio != nullptr)
         {
-            QImage* image = nullptr;
-            image = m_decodeVedio->getOneImage();
-            if(image == nullptr)
-            {
-                break;
-            }
-            /* 删除上一帧图片 */
-            if(m_image != nullptr)
-            {
-                delete m_image;
-                m_image = nullptr;
-            }
-            m_image = image;
+            // QImage* image = nullptr;
+            // image = m_decodeVedio->getOneImage();
+            // if(image == nullptr)
+            // {
+            //     break;
+            // }
+            // /* 删除上一帧图片 */
+            // if(m_image != nullptr)
+            // {
+            //     delete m_image;
+            //     m_image = nullptr;
+            // }
+            // m_image = image;
             
-            if(m_image->isNull())
-            {
-                SPDLOG_WARN("取出的图片为空...");
-                return;
-            }
-            // SPDLOG_DEBUG("绘制画面...");
-            refreshRGBAImage(m_playWidget, *m_image);
+            // if(m_image->isNull())
+            // {
+            //     SPDLOG_WARN("取出的图片为空...");
+            //     return;
+            // }
+            // // SPDLOG_DEBUG("绘制画面...");
+            // refreshRGBAImage(m_playWidget, *m_image);
+            refreshFrame(m_decodeVedio->getOneImage());
         }
     }
     m_playStatus = false;

+ 6 - 2
demo/OpenGLWidgetTest/VideoPlayer/VideoPlayer.h

@@ -1,6 +1,7 @@
 #ifndef VideoPlayer_H
 #define VideoPlayer_H
 
+#include "FrameFormat.h"
 #include <QObject>
 #include <QThread>
 #include <QTimer>
@@ -41,7 +42,10 @@ signals:
 protected:
     // void paintEvent(QPaintEvent *event) override;
     // void resizeEvent(QResizeEvent *event) override;
-    void refreshOneUIUntilHave();                   /* 刷新一张图片,直到有图片为止 */
+    /* 刷新一张图片,直到有图片为止 */
+    void refreshOneUIUntilHave();
+    /* 刷新一帧 */
+    inline void refreshFrame(FrameBase* frame);
     /* 双击事件函数 */
     // void mouseDoubleClickEvent(QMouseEvent *event) override;
 
@@ -68,7 +72,7 @@ private:
 
     DecodeVedio* m_decodeVedio = nullptr;
     QThread* m_threadDecode = nullptr;              /* 解码器所在的线程 */
-    QImage* m_image = nullptr;                      /* 画面 */
+    FrameBase* m_image = nullptr;                   /* 一帧画面 */
     bool m_playStatus = false;                      /* 是否正在播放 */
     bool m_isOpenFile = false;                      /* 是否打开了视频文件,未打开视频文件也就是未初始化解码线程 */
     QSemaphore* m_semRefresh = nullptr;             /* 刷新信号量 */

+ 5 - 3
demo/VideoPlayer/CMakeLists.txt

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.10)
 
 set(this_exe VideoPlayer)
 
@@ -44,9 +44,10 @@ target_include_directories(${this_exe} PRIVATE
     ${CMAKE_SOURCE_DIR}/External/module/RingQueue
     ${CMAKE_SOURCE_DIR}/External/module/VideoPlayer
     
+    ${spdlog_INCLUDE_DIR}
     ${CURL_INCLUDE_DIR}
     ${FFMPEG_INCLUDE_DIR}
-    ${spdlog_INCLUDE_DIR}
+    
 )
 
 target_link_libraries(${this_exe} PRIVATE
@@ -61,9 +62,10 @@ target_link_libraries(${this_exe} PRIVATE
 target_link_libraries(${this_exe} PRIVATE 
     # fmt::fmt
     # spdlog::spdlog
+    ${spdlog_LIBRARY}
     ${CURL_LIBRARY}
     ${FFMPEG_LIBRARY}
-    ${spdlog_LIBRARY}
+    
 )
 
 if(CMAKE_CXX_COMPILER_VERSION LESS 9.0)

+ 1 - 1
demo/VideoPlayerGL/GLWidget/PlayerGLWidget.cpp

@@ -37,7 +37,7 @@ void PlayerGLWidget::showOneRGBAImage(const QImage& image)
 }
 
 /* 显示一张YUV420的图片 */
-void PlayerGLWidget::showOneYUV420Image(const Image_YUV420& yuvData)
+void PlayerGLWidget::showOneYUV420Image(const Image_YUV420P& yuvData)
 {
     if(!yuvData.isValid()) {
         SPDLOG_WARN("YUV420数据无效");