瀏覽代碼

ffmpeg4.1解码并播放h264和h265

huihui 5 年之前
父節點
當前提交
14ee1c017a
共有 32 個文件被更改,包括 3331 次插入0 次删除
  1. 51 0
      source/VideoDecode/VideoDecode.pro
  2. 65 0
      source/VideoDecode/VideoDecoder/VideoDecoder.pri
  3. 98 0
      source/VideoDecode/VideoDecoder/src/Mutex/Cond.cpp
  4. 55 0
      source/VideoDecode/VideoDecoder/src/Mutex/Cond.h
  5. 44 0
      source/VideoDecode/VideoDecoder/src/Mutex/Mutex.cpp
  6. 35 0
      source/VideoDecode/VideoDecoder/src/Mutex/Mutex.h
  7. 6 0
      source/VideoDecode/VideoDecoder/src/Video/VideoEventHandle.cpp
  8. 16 0
      source/VideoDecode/VideoDecoder/src/Video/VideoEventHandle.h
  9. 55 0
      source/VideoDecode/VideoDecoder/src/Video/VideoFrame.cpp
  10. 35 0
      source/VideoDecode/VideoDecoder/src/Video/VideoFrame.h
  11. 141 0
      source/VideoDecode/VideoDecoder/src/VideoDecoder/VideoDecoder.cpp
  12. 46 0
      source/VideoDecode/VideoDecoder/src/VideoDecoder/VideoDecoder.h
  13. 182 0
      source/VideoDecode/VideoDecoder/src/VideoReader/NALUParsing.cpp
  14. 57 0
      source/VideoDecode/VideoDecoder/src/VideoReader/NALUParsing.h
  15. 141 0
      source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.cpp
  16. 48 0
      source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.h
  17. 34 0
      source/VideoDecode/VideoDecoder/src/VideoReader/h264.h
  18. 76 0
      source/VideoDecode/VideoDecoder/src/VideoReader/h265.h
  19. 二進制
      source/VideoDecode/data/test.h264
  20. 二進制
      source/VideoDecode/data/test.h265
  21. 605 0
      source/VideoDecode/src/AppConfig.cpp
  22. 75 0
      source/VideoDecode/src/AppConfig.h
  23. 88 0
      source/VideoDecode/src/Base/FunctionTransfer.cpp
  24. 48 0
      source/VideoDecode/src/Base/FunctionTransfer.h
  25. 696 0
      source/VideoDecode/src/Video/ShowVideoWidget.cpp
  26. 121 0
      source/VideoDecode/src/Video/ShowVideoWidget.h
  27. 218 0
      source/VideoDecode/src/Video/ShowVideoWidget.ui
  28. 22 0
      source/VideoDecode/src/main.cpp
  29. 84 0
      source/VideoDecode/src/mainwindow.cpp
  30. 41 0
      source/VideoDecode/src/mainwindow.h
  31. 137 0
      source/VideoDecode/src/mainwindow.ui
  32. 11 0
      source/VideoDecode/说明.txt

+ 51 - 0
source/VideoDecode/VideoDecode.pro

@@ -0,0 +1,51 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2019-10-17T14:42:56
+#
+#-------------------------------------------------
+ 
+QT       += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = VideoDecode
+TEMPLATE = app
+
+CONFIG += c++11
+
+UI_DIR  = obj/Gui
+MOC_DIR = obj/Moc
+OBJECTS_DIR = obj/Obj
+
+
+#将输出文件直接放到源码目录下的bin目录下,将dll都放在了次目录中,用以解决运行后找不到dll的问
+#DESTDIR=$$PWD/bin/
+contains(QT_ARCH, i386) {
+    message("32-bit")
+    DESTDIR = $${PWD}/bin32
+} else {
+    message("64-bit")
+    DESTDIR = $${PWD}/bin64
+}
+
+#包含视频解码的代码
+include(VideoDecoder/VideoDecoder.pri)
+
+SOURCES += \
+    src/Video/ShowVideoWidget.cpp \
+    src/main.cpp \
+    src/Base/FunctionTransfer.cpp \
+    src/AppConfig.cpp \
+    src/mainwindow.cpp
+
+HEADERS += \
+    src/AppConfig.h \
+    src/Base/FunctionTransfer.h \
+    src/Video/ShowVideoWidget.h \
+    src/mainwindow.h
+
+FORMS += \
+    src/Video/ShowVideoWidget.ui \
+    src/mainwindow.ui
+
+INCLUDEPATH += $$PWD/src

+ 65 - 0
source/VideoDecode/VideoDecoder/VideoDecoder.pri

@@ -0,0 +1,65 @@
+CONFIG += c++11
+QMAKE_CXXFLAGS += -std=c++11
+
+SOURCES += \
+    $$PWD/src/Video/VideoEventHandle.cpp \
+    $$PWD/src/Video/VideoFrame.cpp \
+    $$PWD/src/Mutex/Cond.cpp \
+    $$PWD/src/Mutex/Mutex.cpp \
+    $$PWD/src/VideoDecoder/VideoDecoder.cpp \
+    $$PWD/src/VideoReader/NALUParsing.cpp \
+    $$PWD/src/VideoReader/ReadVideoFileThread.cpp
+
+HEADERS += \
+    $$PWD/src/Mutex/Cond.h \
+    $$PWD/src/Mutex/Mutex.h \
+    $$PWD/src/Video/VideoEventHandle.h \
+    $$PWD/src/Video/VideoFrame.h \
+    $$PWD/src/VideoDecoder/VideoDecoder.h \
+    $$PWD/src/VideoReader/h264.h \
+    $$PWD/src/VideoReader/h265.h \
+    $$PWD/src/VideoReader/NALUParsing.h \
+    $$PWD/src/VideoReader/ReadVideoFileThread.h
+
+win32{
+
+    contains(QT_ARCH, i386) {
+        message("32-bit")
+        INCLUDEPATH += $$PWD/lib/win32/ffmpeg/include \
+                       $$PWD/src
+
+        LIBS += -L$$PWD/lib/win32/ffmpeg/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
+
+    } else {
+        message("64-bit")
+        INCLUDEPATH += $$PWD/lib/win64/ffmpeg/include \
+                       $$PWD/src
+
+        LIBS += -L$$PWD/lib/win64/ffmpeg/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
+
+    }
+
+}
+
+
+unix{
+    contains(QT_ARCH, i386) {
+        message("32-bit, 请自行编译32位库!")
+    } else {
+        message("64-bit")
+        INCLUDEPATH += $$PWD/lib/linux/ffmpeg/include \
+                       $$PWD/src
+
+        LIBS += -L$$PWD/lib/linux/ffmpeg/lib  -lavformat  -lavcodec -lavdevice -lavfilter -lavutil -lswresample -lswscale
+
+        LIBS += -lpthread -ldl
+    }
+
+#QMAKE_POST_LINK 表示编译后执行内容
+#QMAKE_PRE_LINK 表示编译前执行内容
+
+#解压库文件
+#QMAKE_PRE_LINK += "cd $$PWD/lib/linux && tar xvzf ffmpeg.tar.gz "
+system("cd $$PWD/lib/linux && tar xvzf ffmpeg.tar.gz")
+
+}

+ 98 - 0
source/VideoDecode/VideoDecoder/src/Mutex/Cond.cpp

@@ -0,0 +1,98 @@
+#include "Cond.h"
+
+Cond::Cond()
+{
+#if defined(WIN32) && !defined(MINGW)
+    InitializeCriticalSection(&m_mutex);
+    InitializeConditionVariable(&m_cond);
+#else
+    pthread_mutex_init(&m_mutex, NULL);
+    pthread_cond_init(&m_cond, NULL);
+#endif
+
+}
+
+Cond::~Cond()
+{
+#if defined(WIN32) && !defined(MINGW)
+    DeleteCriticalSection(&m_mutex);
+#else
+    pthread_mutex_destroy(&m_mutex);
+    pthread_cond_destroy(&m_cond);
+#endif
+
+}
+
+//加锁
+int Cond::Lock()
+{
+#if defined(WIN32) && !defined(MINGW)
+    EnterCriticalSection(&m_mutex);
+    return 0;
+#else
+    return pthread_mutex_lock(&m_mutex);
+#endif
+
+}
+
+//解锁
+int Cond::Unlock()
+{
+#if defined(WIN32) && !defined(MINGW)
+    LeaveCriticalSection(&m_mutex);
+    return 0;
+#else
+    return pthread_mutex_unlock(&m_mutex);
+#endif
+}
+
+int Cond::Wait()
+{
+#if defined(WIN32) && !defined(MINGW)
+    DWORD ret = SleepConditionVariableCS((PCONDITION_VARIABLE)&m_cond, &m_mutex, INFINITE);
+#else
+    int ret = pthread_cond_wait(&m_cond, &m_mutex);
+#endif
+
+    return ret;
+
+}
+
+//固定时间等待
+int Cond::TimedWait(int second)
+{
+#if defined(WIN32) && !defined(MINGW)
+    SleepConditionVariableCS((PCONDITION_VARIABLE)&m_cond, &m_mutex, second*1000);
+    return 0;
+#else
+    struct timespec abstime;
+    //获取从当前时间,并加上等待时间, 设置进程的超时睡眠时间
+    clock_gettime(CLOCK_REALTIME, &abstime);
+    abstime.tv_sec += second;
+    return pthread_cond_timedwait(&m_cond, &m_mutex, &abstime);
+#endif
+
+}
+
+int Cond::Signal()
+{
+#if defined(WIN32) && !defined(MINGW)
+    int ret = 0;
+    WakeConditionVariable((PCONDITION_VARIABLE)&m_cond);
+#else
+    int ret = pthread_cond_signal(&m_cond);
+#endif
+    return ret;
+}
+
+//唤醒所有睡眠线程
+int Cond::Broadcast()
+{
+#if defined(WIN32) && !defined(MINGW)
+    WakeAllConditionVariable((PCONDITION_VARIABLE)&m_cond);
+    return 0;
+#else
+    return pthread_cond_broadcast(&m_cond);
+#endif
+
+}

+ 55 - 0
source/VideoDecode/VideoDecoder/src/Mutex/Cond.h

@@ -0,0 +1,55 @@
+#ifndef COND_H
+#define COND_H
+
+/// 注意Mingw的话使用的是linux下的api pthread
+/// 没有_MSC_VER这个宏 我们就认为他用的是mingw编译器
+
+#ifndef _MSC_VER
+#define MINGW
+#endif
+
+#if defined(WIN32) && !defined(MINGW)
+    #include <WinSock2.h>
+    #include <Windows.h>
+#else
+    #include <pthread.h>
+    #include <time.h>
+#endif
+
+class Cond
+{
+public:
+    Cond();
+    ~Cond();
+
+    //上锁
+    int Lock();
+
+    //解锁
+    int Unlock();
+
+    //
+    int Wait();
+
+    //固定时间等待
+    int TimedWait(int second);
+
+    //
+    int Signal();
+
+    //唤醒所有睡眠线程
+    int Broadcast();
+
+private:
+
+#if defined(WIN32) && !defined(MINGW)
+    CRITICAL_SECTION m_mutex;
+    RTL_CONDITION_VARIABLE m_cond;
+#else
+    pthread_mutex_t m_mutex;
+    pthread_cond_t m_cond;
+#endif
+
+};
+
+#endif // MUTEX_H

+ 44 - 0
source/VideoDecode/VideoDecoder/src/Mutex/Mutex.cpp

@@ -0,0 +1,44 @@
+#include "Mutex.h"
+
+Mutex::Mutex()
+{
+#if defined(WIN32)
+    m_mutex = ::CreateMutex(NULL, FALSE, NULL);
+#else
+    pthread_mutex_init(&mutex, NULL);
+#endif
+
+}
+
+Mutex::~Mutex()
+{
+#if defined(WIN32)
+    ::CloseHandle(m_mutex);
+#else
+     pthread_mutex_destroy(&mutex);
+#endif
+
+}
+
+int Mutex::Lock() const
+{
+#if defined(WIN32)
+    DWORD ret = WaitForSingleObject(m_mutex, INFINITE);
+#else
+    int ret = pthread_mutex_lock((pthread_mutex_t*)&mutex);
+#endif
+
+    return ret;
+
+}
+
+int Mutex::Unlock() const
+{
+#if defined(WIN32)
+    bool ret = ::ReleaseMutex(m_mutex);
+#else
+    int ret = pthread_mutex_unlock((pthread_mutex_t*)&mutex);
+#endif
+    return ret;
+}
+

+ 35 - 0
source/VideoDecode/VideoDecoder/src/Mutex/Mutex.h

@@ -0,0 +1,35 @@
+#ifndef MUTEX_H
+#define MUTEX_H
+
+
+#if defined(WIN32)
+    #include <WinSock2.h>
+    #include <Windows.h>
+//#elif defined(Q_OS_LINUX)
+#else
+    #include <pthread.h>
+#endif
+
+class Mutex
+{
+public:
+    Mutex();
+    ~Mutex();
+
+    //确保拥有互斥对象的线程对被保护资源的独自访问
+    int Lock() const;
+
+    //释放当前线程拥有的互斥对象,以使其它线程可以拥有互斥对象,对被保护资源进行访问
+    int Unlock() const;
+
+private:
+
+#if defined(WIN32)
+     HANDLE m_mutex;
+#else
+    pthread_mutex_t mutex;
+#endif
+
+};
+
+#endif // MUTEX_H

+ 6 - 0
source/VideoDecode/VideoDecoder/src/Video/VideoEventHandle.cpp

@@ -0,0 +1,6 @@
+#include "VideoEventHandle.h"
+
+VideoCallBack::~VideoCallBack()
+{
+
+}

+ 16 - 0
source/VideoDecode/VideoDecoder/src/Video/VideoEventHandle.h

@@ -0,0 +1,16 @@
+#ifndef VIDEOEVENTHANDLE_H
+#define VIDEOEVENTHANDLE_H
+
+#include "Video/VideoFrame.h"
+
+class VideoCallBack
+{
+public:
+    ~VideoCallBack();
+
+    ///播放视频,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    virtual void onDisplayVideo(VideoFramePtr videoFrame, int frameNum) = 0;
+
+};
+
+#endif // VIDEOYEVENTHANDLE_H

+ 55 - 0
source/VideoDecode/VideoDecoder/src/Video/VideoFrame.cpp

@@ -0,0 +1,55 @@
+#include "VideoFrame.h"
+
+VideoFrame::VideoFrame()
+{
+    mYuv420Buffer = nullptr;
+}
+
+VideoFrame::~VideoFrame()
+{
+    if (mYuv420Buffer != nullptr)
+    {
+        free(mYuv420Buffer);
+        mYuv420Buffer = nullptr;
+    }
+}
+
+void VideoFrame::initBuffer(const int &width, const int &height)
+{
+    if (mYuv420Buffer != nullptr)
+    {
+        free(mYuv420Buffer);
+        mYuv420Buffer = nullptr;
+    }
+
+    mWidth  = width;
+    mHegiht = height;
+
+    mYuv420Buffer = (uint8_t*)malloc(width * height * 3 / 2);
+
+}
+
+void VideoFrame::setYUVbuf(const uint8_t *buf)
+{
+    int Ysize = mWidth * mHegiht;
+    memcpy(mYuv420Buffer, buf, Ysize * 3 / 2);
+}
+
+void VideoFrame::setYbuf(const uint8_t *buf)
+{
+    int Ysize = mWidth * mHegiht;
+    memcpy(mYuv420Buffer, buf, Ysize);
+}
+
+void VideoFrame::setUbuf(const uint8_t *buf)
+{
+    int Ysize = mWidth * mHegiht;
+    memcpy(mYuv420Buffer + Ysize, buf, Ysize / 4);
+}
+
+void VideoFrame::setVbuf(const uint8_t *buf)
+{
+    int Ysize = mWidth * mHegiht;
+    memcpy(mYuv420Buffer + Ysize + Ysize / 4, buf, Ysize / 4);
+}
+

+ 35 - 0
source/VideoDecode/VideoDecoder/src/Video/VideoFrame.h

@@ -0,0 +1,35 @@
+#ifndef VIDEOFRAME_H
+#define VIDEOFRAME_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <memory>
+
+#define VideoFramePtr std::shared_ptr<VideoFrame>
+
+class VideoFrame
+{
+public:
+    VideoFrame();
+    ~VideoFrame();
+
+    void initBuffer(const int &width, const int &height);
+
+    void setYUVbuf(const uint8_t *buf);
+    void setYbuf(const uint8_t *buf);
+    void setUbuf(const uint8_t *buf);
+    void setVbuf(const uint8_t *buf);
+
+    uint8_t * buffer(){return mYuv420Buffer;}
+    int width(){return mWidth;}
+    int height(){return mHegiht;}
+
+protected:
+    uint8_t *mYuv420Buffer;
+
+    int mWidth;
+    int mHegiht;
+};
+
+#endif // VIDEOFRAME_H

+ 141 - 0
source/VideoDecode/VideoDecoder/src/VideoDecoder/VideoDecoder.cpp

@@ -0,0 +1,141 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "VideoDecoder.h"
+
+VideoDecoder::VideoDecoder()
+{
+    pCodec = NULL;
+    pCodecCtx = NULL;
+    img_convert_ctx = NULL;
+    pFrame = NULL;
+
+    pFrameYUV = NULL;
+    bufferYUV = NULL;
+}
+
+void VideoDecoder::initWidth(int width, int height)
+{
+    int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, width,height);
+    bufferYUV = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
+    avpicture_fill((AVPicture *)pFrameYUV, bufferYUV, AV_PIX_FMT_YUV420P,width, height);
+
+    img_convert_ctx = sws_getContext(width, height, pCodecCtx->pix_fmt,
+                                     width, height, AV_PIX_FMT_YUV420P,
+                                     SWS_BICUBIC, NULL,NULL,NULL);
+
+}
+
+bool VideoDecoder::openDecoder(AVCodecID id)
+{
+    /* find the h264 video decoder */
+    pCodec = avcodec_find_decoder(id);
+    if (!pCodec)
+    {
+        fprintf(stderr, "codec not found\n");
+        return false;
+    }
+    pCodecCtx = avcodec_alloc_context3(pCodec);// avcodec_alloc_context2();
+
+    /* open the coderc */
+    if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0)
+    {
+        fprintf(stderr, "could not open codec\n");
+        return false;
+    }
+
+    // Allocate video frame
+    pFrame = av_frame_alloc();
+
+    if(pFrame == NULL)
+        return false;
+
+    pFrameYUV = av_frame_alloc();
+
+    if(pFrameYUV == NULL)
+        return false;
+
+    return true;
+}
+
+void VideoDecoder::closeDecoder()
+{
+    avcodec_close(pCodecCtx);
+    av_free(pCodecCtx);
+    av_free(pFrame);
+    av_free(pFrameYUV);
+
+    if (bufferYUV != NULL)
+    {
+        av_free(bufferYUV);
+    }
+
+    pCodec = NULL;
+    pCodecCtx = NULL;
+    img_convert_ctx = NULL;
+    pFrame = NULL;
+
+    pFrameYUV = NULL;
+    bufferYUV = NULL;
+}
+
+bool VideoDecoder::decode(uint8_t *inputbuf, int frame_size, uint8_t *&outBuf, int &outWidth, int &outHeight)
+{
+    AVPacket pkt;
+    av_init_packet(&pkt);
+    pkt.data = inputbuf;
+    pkt.size = frame_size;
+
+    if (avcodec_send_packet(pCodecCtx, &pkt) != 0)
+    {
+       fprintf(stderr, "input AVPacket to decoder failed!\n");
+       av_packet_unref(&pkt);
+       return 0;
+    }
+
+    while (0 == avcodec_receive_frame(pCodecCtx, pFrame))
+    {
+        //前面初始化解码器的时候 并没有设置视频的宽高信息,
+        //因为h264的每一帧数据都带有编码的信息,当然也包括这些宽高信息了,因此解码完之后,便可以知道视频的宽高是多少
+        //这就是为什么 初始化编码器的时候 需要初始化高度,而初始化解码器却不需要。
+        //解码器可以直接从需要解码的数据中获得宽高信息,这样也才会符合道理。
+        //所以一开始没有为bufferYUV分配空间 因为没办法知道 视频宽高
+        //一旦解码了一帧之后 就可以知道宽高了  这时候就可以分配了
+        if (bufferYUV == NULL)
+        {
+            initWidth(pCodecCtx->width, pCodecCtx->height);
+        }
+
+        //格式转换 解码之后的数据是yuv420p的 把她转换成 rgb的图像数据
+        sws_scale(img_convert_ctx,
+                 (uint8_t const * const *) pFrame->data, pFrame->linesize,
+                  0, pCodecCtx->height,
+                  pFrameYUV->data, pFrameYUV->linesize);
+
+        outBuf = bufferYUV;
+        outWidth = pCodecCtx->width;
+        outHeight = pCodecCtx->height;
+    }
+
+    av_packet_unref(&pkt);
+
+    return true;
+}
+
+int VideoDecoder::getFrameRate()
+{
+    int den = pCodecCtx->framerate.den;
+    int num = pCodecCtx->framerate.num;
+
+    int frameRate = 0;
+
+    if (den > 0)
+    {
+        frameRate = num / den;
+    }
+
+    return frameRate;
+}

+ 46 - 0
source/VideoDecode/VideoDecoder/src/VideoDecoder/VideoDecoder.h

@@ -0,0 +1,46 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef VIDEODECODER_H
+#define VIDEODECODER_H
+
+extern "C"
+{
+    #include <libavcodec/avcodec.h>
+    #include <libavdevice/avdevice.h>
+    #include <libavformat/avformat.h>
+    #include <libavformat/avformat.h>
+    #include <libswscale/swscale.h>
+}
+
+typedef unsigned char uchar;
+
+class VideoDecoder
+{
+public:
+    VideoDecoder();
+
+    bool openDecoder(enum AVCodecID id = AV_CODEC_ID_H264);
+    void closeDecoder();
+
+    bool decode(uint8_t *inputbuf, int frame_size, uint8_t * &outBuf, int &outWidth, int &outHeight);
+    int getFrameRate(); //获取帧率
+
+private:
+
+    AVCodec         *pCodec;
+    AVCodecContext  *pCodecCtx;
+    SwsContext      *img_convert_ctx;
+    AVFrame         *pFrame;
+
+    AVFrame *pFrameYUV;
+    uint8_t *bufferYUV;
+
+    void initWidth(int width, int height);
+
+};
+
+#endif // H264DECORDER_H

+ 182 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/NALUParsing.cpp

@@ -0,0 +1,182 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "NALUParsing.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+NALUParsing::NALUParsing()
+{
+    ///初始化一段内存 用于临时存放h264数据
+    mH264Buffer = (uint8_t*)malloc(1024*1024*10);
+    mBufferSize = 0;
+}
+
+int NALUParsing::inputH264Data(uint8_t *buf, int len)
+{
+    memcpy(mH264Buffer + mBufferSize, buf, len);
+    mBufferSize += len;
+
+    return mBufferSize;
+}
+
+T_NALU *NALUParsing::getNextFrame()
+{
+
+    /*根据h264文件的特性  逐个字节搜索 直到遇到h264的帧头 视为获取到了完整的一帧h264视频数据*/
+
+///    关于起始码startcode的两种形式:3字节的0x000001和4字节的0x00000001
+///    3字节的0x000001只有一种场合下使用,就是一个完整的帧被编为多个slice的时候,
+///    包含这些slice的nalu使用3字节起始码。其余场合都是4字节的。
+///    因此查找一帧的话,只需要查找四字节的起始码即可。
+
+    ///首先查找第一个起始码
+
+    int pos = 0; //记录当前处理的数据偏移量
+    int StartCode = 0;
+
+    while(1)
+    {
+        unsigned char* Buf = mH264Buffer + pos;
+        int lenth = mBufferSize - pos; //剩余没有处理的数据长度
+        if (lenth <= 4)
+        {
+            return NULL;
+        }
+
+        ///查找起始码(0x00000001)
+        if(Buf[0]==0 && Buf[1]==0 && Buf[2] ==0 && Buf[3] ==1)
+         //Check whether buf is 0x00000001
+        {
+            StartCode = 4;
+            break;
+        }
+        else
+        {
+            //否则 往后查找一个字节
+            pos++;
+        }
+    }
+
+
+    ///然后查找下一个起始码查找第一个起始码
+
+    int pos_2 = pos + StartCode; //记录当前处理的数据偏移量
+    int StartCode_2 = 0;
+
+    while(1)
+    {
+        unsigned char* Buf = mH264Buffer + pos_2;
+        int lenth = mBufferSize - pos_2; //剩余没有处理的数据长度
+        if (lenth <= 4)
+        {
+            return NULL;
+        }
+
+        ///查找起始码(0x00000001)
+        if(Buf[0]==0 && Buf[1]==0 && Buf[2] ==0 && Buf[3] ==1)
+         //Check whether buf is 0x00000001
+        {
+            StartCode_2 = 4;
+            break;
+        }
+        else
+        {
+            //否则 往后查找一个字节
+            pos_2++;
+        }
+    }
+
+    /// 现在 pos和pos_2之间的数据就是一帧数据了
+    /// 把他取出来
+
+    ///由于传递给ffmpeg解码的数据 需要带上起始码 因此这里的nalu带上了起始码
+    unsigned char* Buf = mH264Buffer + pos; //这帧数据的起始数据(包含起始码)
+    int naluSize = pos_2 - pos; //nalu数据大小 包含起始码
+
+    T_NALU * nalu = AllocNALU(naluSize, mVideoType);//分配nal 资源
+
+    if (mVideoType == T_NALU_H264)
+    {
+        T_H264_NALU_HEADER *nalu_header = (T_H264_NALU_HEADER *)(Buf + StartCode);
+
+        nalu->nalu.h264Nalu.startcodeprefix_len = StartCode;      //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
+        nalu->nalu.h264Nalu.len = naluSize;                 //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
+        nalu->nalu.h264Nalu.forbidden_bit = 0;            //! should be always FALSE
+        nalu->nalu.h264Nalu.nal_reference_idc = nalu_header->NRI;        //! NALU_PRIORITY_xxxx
+        nalu->nalu.h264Nalu.nal_unit_type = nalu_header->TYPE;            //! NALU_TYPE_xxxx
+        nalu->nalu.h264Nalu.lost_packets = false;  //! true, if packet loss is detected
+        memcpy(nalu->nalu.h264Nalu.buf, Buf, naluSize);  //! contains the first byte followed by the EBSP
+
+//        {
+//            char *bufTmp = (char*)(Buf + StartCode);
+//            char s[10];
+//            itoa(bufTmp[0], s, 2);
+//            fprintf(stderr, "%s %08s %x %d\n", __FUNCTION__, s, bufTmp[0] , nalu_header->TYPE);
+//        }
+    }
+    else
+    {
+        T_H265_NALU_HEADER *nalu_header = (T_H265_NALU_HEADER *)(Buf + StartCode);
+
+        nalu->nalu.h265Nalu.startCodeLen = StartCode;      //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
+        nalu->nalu.h265Nalu.len = naluSize;                 //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
+        nalu->nalu.h265Nalu.h265NaluHeader = *nalu_header;
+        memcpy(nalu->nalu.h265Nalu.buf, Buf, naluSize);  //! contains the first byte followed by the EBSP
+
+        {
+            char *bufTmp = (char*)(Buf);
+            fprintf(stderr, "%s %02x%02x%02x%02x%02x%02x %d %d\n", __FUNCTION__, bufTmp[0], bufTmp[1], bufTmp[2], bufTmp[3], bufTmp[4], bufTmp[5], nalu->nalu.h265Nalu.h265NaluHeader.nal_unit_type, nalu_header->nal_unit_type);
+        }
+    }
+
+    /// 将这一帧数据去掉
+    /// 把后一帧数据覆盖上来
+    int leftSize = mBufferSize - pos_2;
+    memcpy(mH264Buffer, mH264Buffer + pos_2, leftSize);
+    mBufferSize = leftSize;
+
+    return nalu;
+}
+
+T_NALU *NALUParsing::AllocNALU(int buffersize, T_NALU_TYPE type)
+{
+    T_NALU *n = NULL;
+
+    n = (T_NALU*)malloc (sizeof(T_NALU));
+
+    n->type = type;
+
+    if (type == T_NALU_H264)
+    {
+        n->nalu.h264Nalu.max_size = buffersize;	//Assign buffer size
+        n->nalu.h264Nalu.buf = (unsigned char*)malloc (buffersize);
+        n->nalu.h264Nalu.len = buffersize;
+    }
+    else
+    {
+        n->nalu.h265Nalu.buf = (unsigned char*)malloc (buffersize);
+        n->nalu.h265Nalu.len  = buffersize;
+    }
+
+    return n;
+}
+
+void NALUParsing::FreeNALU(T_NALU *n)
+{
+    if (n == nullptr) return;
+
+    if (n->type == T_NALU_H264)
+    {
+        free(n->nalu.h264Nalu.buf);
+    }
+    else
+    {
+        free(n->nalu.h265Nalu.buf);
+    }
+}

+ 57 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/NALUParsing.h

@@ -0,0 +1,57 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef NALUPARSING_H
+#define NALUPARSING_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "h264.h"
+#include "h265.h"
+
+enum T_NALU_TYPE
+{
+    T_NALU_H264 = 0,
+    T_NALU_H265,
+};
+
+typedef struct
+{
+    T_NALU_TYPE type;
+
+    union{
+        T_H264_NALU h264Nalu;
+        T_H265_NALU h265Nalu;
+    }nalu;
+
+} T_NALU;
+
+class NALUParsing
+{
+public:
+    NALUParsing();
+
+    void setVideoType(T_NALU_TYPE type){mVideoType = type;}
+
+    int inputH264Data(uint8_t *buf,int len); //输入h264数据
+
+    ///从H264数据中查找出一帧数据
+    T_NALU* getNextFrame();
+
+private:
+    uint8_t *mH264Buffer;
+    int mBufferSize;
+
+    T_NALU_TYPE mVideoType; //类型 区分是264还是265
+
+public:
+    static T_NALU *AllocNALU(int buffersize, T_NALU_TYPE type);
+
+    static void FreeNALU(T_NALU *n);
+};
+
+#endif // H264READER_H

+ 141 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.cpp

@@ -0,0 +1,141 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include <thread>
+
+
+#include "ReadVideoFileThread.h"
+
+#if defined(WIN32)
+#include <WinSock2.h>
+#include <Windows.h>
+#include <direct.h>
+#include <io.h> //C (Windows)    access
+#else
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+
+void Sleep(long mSeconds)
+{
+    usleep(mSeconds * 1000);
+}
+
+#endif
+
+
+ReadVideoFileThread::ReadVideoFileThread()
+{
+    mVideoCallBack = nullptr;
+
+    mNaluParsing  = new NALUParsing();
+    mVideoDecoder = new VideoDecoder();
+}
+
+ReadVideoFileThread::~ReadVideoFileThread()
+{
+
+}
+
+void ReadVideoFileThread::startRead(char* filePath, AVCodecID id)
+{
+    strcpy(mFileName, filePath);
+
+    //启动新的线程实现读取视频文件
+    std::thread([&](ReadVideoFileThread *pointer, AVCodecID id)
+    {
+        pointer->run(id);
+
+    }, this, id).detach();
+}
+
+void ReadVideoFileThread::run(AVCodecID id)
+{
+    mVideoDecoder->openDecoder(id);
+
+    if (id == AV_CODEC_ID_H264)
+    {
+        mNaluParsing->setVideoType(T_NALU_H264);
+    }
+    else
+    {
+        mNaluParsing->setVideoType(T_NALU_H265);
+    }
+
+    char *fileName = mFileName;
+    FILE *fp = fopen(fileName, "rb");
+    if (fp == NULL)
+    {
+        fprintf(stderr, "H264 file not exist! \n");
+        return;
+    }
+
+    int frameNum = 0; //当前播放的帧序号
+
+    while(!feof(fp))
+    {
+        char buf[10240];
+        int size = fread(buf, 1, 1024, fp);//从h264文件读1024个字节 (模拟从网络收到h264流)
+        int nCount = mNaluParsing->inputH264Data((uchar*)buf,size);
+
+        while(1)
+        {
+            //从前面读到的数据中获取一个nalu
+            T_NALU* nalu = mNaluParsing->getNextFrame();
+            if (nalu == NULL) break;
+
+            uint8_t *bufferYUV = nullptr;
+            int width;
+            int height;
+
+            if (nalu->type == T_NALU_H264)
+            {
+                mVideoDecoder->decode(nalu->nalu.h264Nalu.buf, nalu->nalu.h264Nalu.len, bufferYUV, width, height);
+            }
+            else if (nalu->type == T_NALU_H265)
+            {
+                mVideoDecoder->decode(nalu->nalu.h265Nalu.buf, nalu->nalu.h265Nalu.len, bufferYUV, width, height);
+            }
+
+            if (bufferYUV != nullptr)
+            {
+                int frameRate = mVideoDecoder->getFrameRate(); //获取帧率
+
+                /// h264裸数据不包含时间戳信息  因此只能根据帧率做同步
+                /// 需要成功解码一帧后 才能获取到帧率
+                /// 为0说明还没获取到 则直接显示
+                if (frameRate != 0)
+                {
+                    Sleep(1000 / frameRate);
+                }
+
+                //然后传给主线程显示
+                doDisplayVideo(bufferYUV, width, height, ++frameNum);
+            }
+
+            NALUParsing::FreeNALU(nalu);
+        }
+    }
+
+    mVideoDecoder->closeDecoder();
+}
+
+///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+void ReadVideoFileThread::doDisplayVideo(const uint8_t *yuv420Buffer, const int &width, const int &height, const int &frameNum)
+{
+//    fprintf(stderr, "%s \n", __FUNCTION__);
+    if (mVideoCallBack != nullptr)
+    {
+        VideoFramePtr videoFrame = std::make_shared<VideoFrame>();
+
+        VideoFrame * ptr = videoFrame.get();
+
+        ptr->initBuffer(width, height);
+        ptr->setYUVbuf(yuv420Buffer);
+
+        mVideoCallBack->onDisplayVideo(videoFrame, frameNum);
+    }
+}

+ 48 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/ReadVideoFileThread.h

@@ -0,0 +1,48 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef READVIDEOFILETHREAD_H
+#define READVIDEOFILETHREAD_H
+
+#include "VideoDecoder/VideoDecoder.h"
+#include "VideoReader/NALUParsing.h"
+
+#include "Video/VideoEventHandle.h"
+
+class ReadVideoFileThread
+{
+public:
+    ReadVideoFileThread();
+    ~ReadVideoFileThread();
+
+    /**
+     * @brief setVideoCallBack 设置视频回调函数
+     * @param pointer
+     */
+    void setVideoCallBack(VideoCallBack *pointer){mVideoCallBack=pointer;}
+
+    void startRead(char* filePath, AVCodecID id = AV_CODEC_ID_H264);
+
+protected:
+    void run(AVCodecID id);
+
+private:
+    NALUParsing *mNaluParsing;
+    VideoDecoder *mVideoDecoder;
+
+    char mFileName[256];
+
+    ///回调函数相关,主要用于输出信息给界面
+private:
+    ///回调函数
+    VideoCallBack *mVideoCallBack;
+
+    ///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    void doDisplayVideo(const uint8_t *yuv420Buffer, const int &width, const int &height, const int &frameNum);
+
+};
+
+#endif // READH264FILETHREAD_H

+ 34 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/h264.h

@@ -0,0 +1,34 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef H264_H
+#define H264_H
+
+#include <stdlib.h>
+
+typedef struct
+{
+  int startcodeprefix_len;      //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
+  unsigned len;                 //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
+  unsigned max_size;            //! Nal Unit Buffer size
+  int forbidden_bit;            //! should be always FALSE
+  int nal_reference_idc;        //! NALU_PRIORITY_xxxx
+  int nal_unit_type;            //! NALU_TYPE_xxxx
+  unsigned char *buf;                    //! contains the first byte followed by the EBSP
+  unsigned short lost_packets;  //! true, if packet loss is detected
+} T_H264_NALU;
+
+#pragma pack (1)
+typedef struct {
+    //byte 0
+    unsigned char TYPE:5;
+    unsigned char NRI:2;
+    unsigned char F:1;
+
+} T_H264_NALU_HEADER; /**//* 1 BYTES */
+#pragma pack ()
+
+#endif // H264_H

+ 76 - 0
source/VideoDecode/VideoDecoder/src/VideoReader/h265.h

@@ -0,0 +1,76 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef H265_H
+#define H265_H
+
+#include <stdlib.h>
+
+typedef enum e_hevc_nalu_type
+{
+    HEVC_NAL_TRAIL_N    = 0,
+    HEVC_NAL_TRAIL_R    = 1,
+    HEVC_NAL_TSA_N      = 2,
+    HEVC_NAL_TSA_R      = 3,
+    HEVC_NAL_STSA_N     = 4,
+    HEVC_NAL_STSA_R     = 5,
+    HEVC_NAL_RADL_N     = 6,
+    HEVC_NAL_RADL_R     = 7,
+    HEVC_NAL_RASL_N     = 8,
+    HEVC_NAL_RASL_R     = 9,
+    HEVC_NAL_VCL_N10    = 10,
+    HEVC_NAL_VCL_R11    = 11,
+    HEVC_NAL_VCL_N12    = 12,
+    HEVC_NAL_VCL_R13    = 13,
+    HEVC_NAL_VCL_N14    = 14,
+    HEVC_NAL_VCL_R15    = 15,
+    HEVC_NAL_BLA_W_LP   = 16,
+    HEVC_NAL_BLA_W_RADL = 17,
+    HEVC_NAL_BLA_N_LP   = 18,
+    HEVC_NAL_IDR_W_RADL = 19,
+    HEVC_NAL_IDR_N_LP   = 20,
+    HEVC_NAL_CRA_NUT    = 21,
+    HEVC_NAL_IRAP_VCL22 = 22,
+    HEVC_NAL_IRAP_VCL23 = 23,
+    HEVC_NAL_RSV_VCL24  = 24,
+    HEVC_NAL_RSV_VCL25  = 25,
+    HEVC_NAL_RSV_VCL26  = 26,
+    HEVC_NAL_RSV_VCL27  = 27,
+    HEVC_NAL_RSV_VCL28  = 28,
+    HEVC_NAL_RSV_VCL29  = 29,
+    HEVC_NAL_RSV_VCL30  = 30,
+    HEVC_NAL_RSV_VCL31  = 31,
+    HEVC_NAL_VPS        = 32,
+    HEVC_NAL_SPS        = 33,
+    HEVC_NAL_PPS        = 34,
+    HEVC_NAL_AUD        = 35,
+    HEVC_NAL_EOS_NUT    = 36,
+    HEVC_NAL_EOB_NUT    = 37,
+    HEVC_NAL_FD_NUT     = 38,
+    HEVC_NAL_SEI_PREFIX = 39,
+    HEVC_NAL_SEI_SUFFIX = 40
+} E_HEVC_NALU_TYPE;
+
+#pragma pack (1)
+typedef struct t_h265_nalu_header
+{
+    unsigned char forbidden_zero_bit:1;
+    unsigned char nal_unit_type:6;
+    unsigned char nuh_layer_id:6;
+    unsigned char nuh_temporal_id_plus1:3;
+} T_H265_NALU_HEADER;
+
+typedef struct t_h265_nalu
+{
+    int startCodeLen;
+    T_H265_NALU_HEADER h265NaluHeader;
+    unsigned int len;
+    unsigned char *buf;
+} T_H265_NALU;
+
+#pragma pack ()
+
+#endif // H264_H

二進制
source/VideoDecode/data/test.h264


二進制
source/VideoDecode/data/test.h265


+ 605 - 0
source/VideoDecode/src/AppConfig.cpp

@@ -0,0 +1,605 @@
+#include "AppConfig.h"
+
+#include <QProcess>
+#include <QDesktopWidget>
+#include <QDesktopServices>
+
+#include <QByteArray>
+#include <QCryptographicHash>
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QCoreApplication>
+#include <QTranslator>
+#include <QDateTime>
+#include <QApplication>
+#include <QMessageBox>
+
+#include <QJsonParseError>
+#include <QJsonObject>
+#include <QJsonArray>
+
+#include <QJsonDocument>
+#include <QJsonObject>
+
+#include <QDebug>
+
+#if defined(WIN32)
+#include <WinSock2.h>
+#include <Windows.h>
+#include <direct.h>
+#include <io.h> //C (Windows)    access
+#else
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+
+void Sleep(long mSeconds);
+//{
+//    usleep(mSeconds * 1000);
+//}
+
+#endif
+
+QString AppConfig::APPID = "{a1db97ad-b8ed-11e9-a297-0235d2b38928}";
+int AppConfig::VERSION = 1;
+QString AppConfig::VERSION_NAME = "2.1.3";
+
+MainWindow *AppConfig::gMainWindow = NULL;
+QRect AppConfig::gMainWindowRect;
+
+QRect AppConfig::gScreenRect;
+
+bool AppConfig::gVideoKeepAspectRatio = false; //按比例显示
+bool AppConfig::gVideoHardDecoder = false; //硬解解码
+QString AppConfig::gVideoFilePath;
+
+QString AppConfig::AppDataPath_Main;
+QString AppConfig::AppDataPath_Data;
+QString AppConfig::AppDataPath_Tmp;
+QString AppConfig::AppDataPath_TmpFile;
+QString AppConfig::AppFilePath_Log;
+QString AppConfig::AppFilePath_LogFile;
+
+QString AppConfig::AppFilePath_EtcFile;
+
+AppConfig::AppConfig()
+{
+
+}
+
+void AppConfig::MakeDir(QString dirName)
+{
+    QDir dir;
+    dir.mkpath(dirName);
+}
+
+void AppConfig::InitAllDataPath()
+{
+
+#if defined(WIN32)
+    ///windows数据存储在C盘的数据目录下
+    QFileInfo fileInfo(QCoreApplication::applicationFilePath());
+    QString exeFileName = fileInfo.baseName(); //当前程序名字
+
+    QString dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+    if (dataPath.right(exeFileName.length()) == exeFileName)
+    {
+        dataPath = dataPath.left(dataPath.length() - exeFileName.length());
+    }
+
+    if (!dataPath.endsWith("/"))
+    {
+        dataPath += "/";
+    }
+
+#else
+    ///Linux则放在程序所在目录下的data目录下
+    QFileInfo fileInfo(QCoreApplication::applicationFilePath());
+
+    QString dataPath = fileInfo.absoluteDir().path();
+
+    if (!dataPath.endsWith("/"))
+    {
+        dataPath += "/";
+    }
+
+#endif
+
+qDebug()<<__FUNCTION__<<dataPath;
+
+    AppDataPath_Main = dataPath;
+
+    AppDataPath_Data = AppDataPath_Main + "/data";
+
+    QString dirName = AppDataPath_Data + "/etc";
+    MakeDir(dirName);
+
+    AppFilePath_EtcFile = dirName + "/main.conf";
+
+    AppDataPath_Tmp = AppDataPath_Data + "/tmp";
+    AppFilePath_Log = AppDataPath_Data + "/log";
+
+    AppDataPath_TmpFile = AppDataPath_Tmp + "/tmp.txt";
+
+    MakeDir(AppDataPath_Data);
+    MakeDir(AppFilePath_Log);
+    MakeDir(AppDataPath_Tmp);
+
+    InitLogFile();
+}
+
+QString AppConfig::bufferToString(QByteArray sendbuf)
+{
+    QString tmp;
+    for (int k = 0; k < sendbuf.size(); k++)
+    {
+        QString str = QString("%1").arg(sendbuf[k] & 0xff, 2, 16, QLatin1Char('0'));
+//        tmp += str + " ";
+        tmp += str;
+    }
+    tmp = tmp.toUpper();
+    return tmp;
+}
+
+QByteArray AppConfig::StringToBuffer(QString str)
+{
+    QString text = str.remove(" ");
+    QByteArray sendbuf;
+    while(!text.isEmpty())
+    {
+        QString str = text.left(2);
+
+        if (str.length() == 1)
+        {
+            str = "0" + str;
+        }
+
+        text.remove(0,2);
+        int x = str.left(1).toInt(0,16) << 4;
+        x += str.right(1).toInt(0,16);
+        QByteArray buf;
+        buf.resize(1);
+        buf[0] = x;
+        sendbuf.append(buf);
+    }
+
+    return sendbuf;
+}
+
+QString AppConfig::getFileMd5(QString filePath,qint64 size)
+{
+    QFile localFile(filePath);
+
+    if (!localFile.open(QFile::ReadOnly))
+    {
+//        qDebug() << "file open error.";
+        return "";
+    }
+
+    QCryptographicHash ch(QCryptographicHash::Md5);
+
+    quint64 totalBytes = 0;
+    quint64 bytesWritten = 0;
+    quint64 bytesToWrite = 0;
+    quint64 loadSize = 1024 * 4;
+    QByteArray buf;
+
+    totalBytes = localFile.size();
+
+    if (size > 0)
+    {
+        bytesToWrite = size;
+    }
+    else
+    {
+        bytesToWrite = totalBytes;
+    }
+
+    if (bytesToWrite > totalBytes)
+    {
+        bytesToWrite = totalBytes;
+    }
+
+    while (1)
+    {
+        if(bytesToWrite > 0)
+        {
+            buf = localFile.read(qMin(bytesToWrite, loadSize));
+            ch.addData(buf);
+            bytesWritten += buf.length();
+            bytesToWrite -= buf.length();
+            buf.resize(0);
+        }
+        else
+        {
+            break;
+        }
+
+        if (bytesToWrite <= 0)
+        {
+            break;
+        }
+
+//        if(bytesWritten == totalBytes)
+//        {
+//            break;
+//        }
+    }
+
+    localFile.close();
+    QByteArray md5 = ch.result();
+    return AppConfig::bufferToString(md5).toLower();
+}
+
+void AppConfig::loadConfigInfoFromFile()
+{
+
+    QFile file(AppConfig::AppFilePath_EtcFile);
+    bool ret = file.open(QIODevice::ReadOnly);
+
+    if (!ret) return;
+
+    QString text(file.readAll());
+
+    if (!text.isEmpty())
+    {
+        QJsonParseError json_error;
+        QJsonDocument jsonDoc(QJsonDocument::fromJson(text.toUtf8(), &json_error));
+
+        if(json_error.error == QJsonParseError::NoError)
+        {
+            QJsonObject object = jsonDoc.object();
+            QJsonValue VideoKeepAspectRatioValue = object.value("VideoKeepAspectRatio");
+            QJsonValue VideoFilePathValue = object.value("VideoFilePath");
+
+            AppConfig::gVideoKeepAspectRatio = VideoKeepAspectRatioValue.toBool();
+            AppConfig::gVideoFilePath = VideoFilePathValue.toString();
+        }
+    }
+
+    file.close();
+
+}
+
+void AppConfig::saveConfigInfoToFile()
+{
+    QFile file(AppConfig::AppFilePath_EtcFile);
+    if (file.open(QIODevice::WriteOnly))
+    {
+        QTextStream fileOut(&file);
+        fileOut.setCodec("UTF-8");  //unicode UTF-8  ANSI
+
+
+        QJsonObject dataObject;
+        dataObject.insert("appid", AppConfig::APPID);
+        dataObject.insert("VideoKeepAspectRatio", AppConfig::gVideoKeepAspectRatio);
+        dataObject.insert("VideoFilePath", AppConfig::gVideoFilePath);
+
+        QJsonDocument json;
+//        QJsonObject object;
+//        object.insert("config", dataObject);
+
+        //最外层是大括号所以是object
+        json.setObject(dataObject);
+
+        QString jsonStr = json.toJson(QJsonDocument::Compact);
+
+        fileOut<<jsonStr;
+
+//        std::string str = base64::base64_encode((unsigned char*)jsonStr.toUtf8().data(),jsonStr.toUtf8().size());
+//        fileOut<<QString::fromStdString(str);
+
+        file.close();
+    }
+}
+
+void AppConfig::InitLogFile()
+{
+//    QString logFilePath = AppFilePath_Log + "\\log.txt";
+//    gLogFile.setFileName(logFilePath);
+
+
+    QDir dir(AppFilePath_Log);
+
+    QFileInfoList fileInfoList = dir.entryInfoList();
+    foreach(QFileInfo fileInfo, fileInfoList)
+    {
+        if(fileInfo.fileName() == "." || fileInfo.fileName() == "..")
+            continue;
+
+        if(fileInfo.isFile())
+        {
+            qint64 t1 = fileInfo.created().toMSecsSinceEpoch();
+            qint64 t2 = QDateTime::currentMSecsSinceEpoch();
+
+            qint64 t = (t2 - t1) / 1000; //文件创建到现在的时间(单位:秒)
+
+            if (t >= (24*3600*3)) //删除3天前的日志文件
+//            if (t >= (60*20))
+            {
+                QFile::remove(fileInfo.absoluteFilePath());
+            }
+        }
+    }
+
+    AppFilePath_LogFile = AppFilePath_Log + QString("/log_%1.txt").arg(QDate::currentDate().toString("yyyy-MM-dd"));
+    WriteLog("\r\n=======================================\r\n=======================================\r\n[App Start...]\r\n\r\n");
+
+}
+
+#if 0
+bool AppConfig::WriteLog(QString str)
+{
+    bool IsFileOpened = gLogFile.isOpen();
+
+    if (!IsFileOpened)
+    {
+        IsFileOpened = gLogFile.open(QIODevice::ReadWrite);
+        gLogFile.seek(gLogFile.size());
+    }
+
+    if (IsFileOpened)
+    {
+        QString tmpStr = QString("[%1] %2 \r\n")
+                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
+                .arg(str);
+
+        QTextStream fileOut(&gLogFile);
+        fileOut.setCodec("UTF-8");  //unicode UTF-8  ANSI
+        fileOut<<tmpStr;
+    }
+
+    return IsFileOpened;
+}
+#else
+void AppConfig::WriteLog(QString str)
+{
+//    qDebug()<<__FUNCTION__<<str;
+
+    QFile file(AppFilePath_LogFile);
+    if (file.open(QIODevice::ReadWrite))
+    {
+        file.seek(file.size());
+        QString tmpStr = QString("[%1] %2 \r\n")
+                .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
+                .arg(str);
+
+        QTextStream fileOut(&file);
+        fileOut.setCodec("UTF-8");  //unicode UTF-8  ANSI
+        fileOut<<tmpStr;
+        file.close();
+    }
+}
+#endif
+
+QString AppConfig::getSizeInfo(qint64 size)
+{
+    int pee = 1024;
+
+    char ch[10]={0};
+    if (size > (pee*pee*pee)) //大于1G
+    {
+        sprintf(ch,"%dGB",(int)(size*1.0/pee/pee/pee+0.5));
+    }
+    else if (size > (pee*pee)) //大于1M
+    {
+        sprintf(ch,"%dMB",size/pee/pee);
+    }
+    else if (size > pee) //大于1K
+    {
+        sprintf(ch,"%dKB",size/pee);
+    }
+    else //小于1KB
+    {
+        sprintf(ch,"%dB",size);
+    }
+
+    QString str = QString(ch);
+
+    return str;
+}
+
+QImage AppConfig::ImagetoGray( QImage image)
+{
+    int height = image.height();
+    int width = image.width();
+    QImage ret(width, height, QImage::Format_Indexed8);
+    ret.setColorCount(256);
+    for(int i = 0; i < 256; i++)
+    {
+        ret.setColor(i, qRgb(i, i, i));
+    }
+    switch(image.format())
+    {
+    case QImage::Format_Indexed8:
+        for(int i = 0; i < height; i ++)
+        {
+            const uchar *pSrc = (uchar *)image.constScanLine(i);
+            uchar *pDest = (uchar *)ret.scanLine(i);
+            memcpy(pDest, pSrc, width);
+        }
+        break;
+    case QImage::Format_RGB32:
+    case QImage::Format_ARGB32:
+    case QImage::Format_ARGB32_Premultiplied:
+        for(int i = 0; i < height; i ++)
+        {
+            const QRgb *pSrc = (QRgb *)image.constScanLine(i);
+            uchar *pDest = (uchar *)ret.scanLine(i);
+
+            for( int j = 0; j < width; j ++)
+            {
+                 pDest[j] = qGray(pSrc[j]);
+            }
+        }
+        break;
+    }
+    return ret;
+}
+
+//拷贝文件夹:
+bool AppConfig::copyDirectoryFiles(const QString &fromDir, const QString &toDir, bool coverFileIfExist)
+{
+    QDir sourceDir(fromDir);
+    QDir targetDir(toDir);
+    if(!targetDir.exists()){    /**< 如果目标目录不存在,则进行创建 */
+        if(!targetDir.mkdir(targetDir.absolutePath()))
+            return false;
+    }
+
+    QFileInfoList fileInfoList = sourceDir.entryInfoList();
+    foreach(QFileInfo fileInfo, fileInfoList){
+        if(fileInfo.fileName() == "." || fileInfo.fileName() == "..")
+            continue;
+
+        if(fileInfo.isDir()){    /**< 当为目录时,递归的进行copy */
+            if(!copyDirectoryFiles(fileInfo.filePath(),
+                targetDir.filePath(fileInfo.fileName()),
+                coverFileIfExist))
+                return false;
+        }
+        else{            /**< 当允许覆盖操作时,将旧文件进行删除操作 */
+            if(coverFileIfExist && targetDir.exists(fileInfo.fileName())){
+                targetDir.remove(fileInfo.fileName());
+            }
+
+            /// 进行文件copy
+            if(!QFile::copy(fileInfo.filePath(),
+                targetDir.filePath(fileInfo.fileName()))){
+                    return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool AppConfig::removeDirectory(QString dirName)
+{
+  QDir dir(dirName);
+  QString tmpdir = "";
+
+  if (!QFile(dirName).exists()) return false;
+
+  if(!dir.exists()){
+    return false;
+  }
+
+  QFileInfoList fileInfoList = dir.entryInfoList();
+  foreach(QFileInfo fileInfo, fileInfoList){
+    if(fileInfo.fileName() == "." || fileInfo.fileName() == "..")
+      continue;
+
+    if(fileInfo.isDir()){
+      tmpdir = dirName + ("/") + fileInfo.fileName();
+      removeDirectory(tmpdir);
+      dir.rmdir(fileInfo.fileName()); /**< 移除子目录 */
+    }
+    else if(fileInfo.isFile()){
+      QFile tmpFile(fileInfo.fileName());
+      dir.remove(tmpFile.fileName()); /**< 删除临时文件 */
+    }
+    else{
+      ;
+    }
+  }
+
+  dir.cdUp();            /**< 返回上级目录,因为只有返回上级目录,才可以删除这个目录 */
+  if(dir.exists(dirName)){
+    if(!dir.rmdir(dirName))
+      return false;
+  }
+  return true;
+}
+
+#if defined(Q_OS_WIN32)
+    #include <ShlObj.h>
+    #include <ShellAPI.h>
+
+    bool AppConfig::restartSelf()
+    {
+        SHELLEXECUTEINFO sei;
+        TCHAR szModule [MAX_PATH],szComspec[MAX_PATH],szParams [MAX_PATH];
+
+        // 获得文件名.
+        if((GetModuleFileName(0,szModule,MAX_PATH)!=0) &&
+            (GetShortPathName(szModule,szModule,MAX_PATH)!=0) &&
+            (GetEnvironmentVariable(L"COMSPEC",szComspec,MAX_PATH)!=0))
+        {
+    //        QString dirPath = QCoreApplication::applicationDirPath();
+            QString dirPath = QCoreApplication::applicationFilePath();
+            dirPath.replace("/","\\");
+            dirPath = "\"" + dirPath + "\"";
+            // 设置命令参数.
+            lstrcpy(szParams, L"/c ");
+            lstrcat(szParams, (WCHAR*)dirPath.utf16());
+            lstrcat(szParams, L" > nul");
+
+    //        lstrcpy(szParams, L"/c del ");
+    //        lstrcat(szParams, szModule);
+    //        lstrcat(szParams, L" > nul");
+
+            // 设置结构成员.
+            sei.cbSize = sizeof(sei);
+            sei.hwnd = 0;
+            sei.lpVerb = L"Open";
+            sei.lpFile = szComspec;
+            sei.lpParameters = szParams;
+            sei.lpDirectory = 0;
+            sei.nShow = SW_HIDE;
+            sei.fMask = SEE_MASK_NOCLOSEPROCESS;
+
+            // 执行shell命令.
+            if(ShellExecuteEx(&sei))
+            {
+                // 设置命令行进程的执行级别为空闲执行,使本程序有足够的时间从内存中退出.
+                SetPriorityClass(sei.hProcess,IDLE_PRIORITY_CLASS);
+                SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);
+                SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
+
+               // 通知Windows资源浏览器,本程序文件已经被删除.
+                SHChangeNotify(SHCNE_DELETE,SHCNF_PATH,(WCHAR*)dirPath.utf16(),0);
+                return TRUE;
+            }
+        }
+        return FALSE;
+    }
+#elif defined(Q_OS_MAC)
+    bool Appconfig::restartSelf()
+    {
+        QString bashFilePath = QString("%1/run.sh").arg(Appconfig::AppDataPath_Data);
+        QFile file(bashFilePath);
+        if (file.open(QIODevice::WriteOnly))
+        {
+            QTextStream fileOut(&file);
+            fileOut.setCodec("UTF-8");  //unicode UTF-8  ANSI
+
+
+            QString dirPath = QApplication::applicationDirPath();
+            QString filePath = QString("%1/%2").arg(dirPath).arg(Appconfig::AppExeName);
+            QString runAppCmd = QString("open -a \""+filePath+"\" \n");
+
+            fileOut<<QString("ping -c 4 -t 2 baidu.com \n"); //延时2秒
+            fileOut<<runAppCmd; //启动程序
+
+            file.close();
+        }
+
+    qDebug()<<bashFilePath;
+        QProcess p(0);
+        p.start("bash");
+        p.waitForStarted();
+        p.write(QString("chmod a+x \""+bashFilePath+"\" \n").toUtf8());
+        p.write(QString("\""+bashFilePath+"\" &\n").toUtf8()); //后台运行
+//        p.write(QString("open -a \""+bashFilePath+"\" \n").toUtf8());
+        p.closeWriteChannel();
+        p.waitForFinished();
+
+        return true;
+    }
+#endif
+
+void AppConfig::mSleep(int mSecond)
+{
+    Sleep(mSecond);
+}

+ 75 - 0
source/VideoDecode/src/AppConfig.h

@@ -0,0 +1,75 @@
+#ifndef APPCONFIG_H
+#define APPCONFIG_H
+
+#include <QFile>
+#include <QString>
+#include <QTranslator>
+#include <QDateTime>
+
+#define CURRENT_TIME QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss]")
+
+#ifdef QT_NO_KEYWORDS
+#define foreach Q_FOREACH
+#endif
+
+class MainWindow;
+
+class AppConfig
+{
+public:
+    AppConfig();
+
+    static QString APPID;
+    static int VERSION;
+    static QString VERSION_NAME;
+
+    /// 本地全局变量
+    static QString AppDataPath_Main; //程序数据主目录
+    static QString AppDataPath_Data; //程序数据的data目录
+    static QString AppDataPath_Tmp; //临时目录(程序退出时会清空此目录)
+    static QString AppDataPath_TmpFile; //程序运行时 创建次文件,退出时删除此文件,用来判断程序是否正常退出
+    static QString AppFilePath_Log; //日志目录
+    static QString AppFilePath_LogFile; //日志文件
+    static QString AppFilePath_EtcFile; //配置信息
+
+    static MainWindow *gMainWindow;
+    static QRect gMainWindowRect; //主窗口的位置 - 用于标记在非全屏模式下的弹窗大小
+    static QRect gScreenRect;
+
+    static bool gVideoKeepAspectRatio; //视频按比例播放
+    static bool gVideoHardDecoder; //硬解解码
+    static QString gVideoFilePath; //打开视频文件的默认位置
+
+
+    static void MakeDir(QString dirName);
+    static void InitAllDataPath(); //初始化所有数据保存的路径
+
+    static QString bufferToString(QByteArray sendbuf);
+    static QByteArray StringToBuffer(QString);
+    static QString getFileMd5(QString filePath,qint64 size=-1);
+
+    ///配置文件
+    static void loadConfigInfoFromFile();
+    static void saveConfigInfoToFile();
+
+    ///写日志
+    static void WriteLog(QString str);
+    static void InitLogFile();
+    static QString getSizeInfo(qint64 size);
+
+    static QImage ImagetoGray( QImage image); //生成灰度图
+
+    ///拷贝文件夹
+    static bool copyDirectoryFiles(const QString &fromDir, const QString &toDir, bool coverFileIfExist);
+
+    ///删除目录
+    static bool removeDirectory(QString dirName);
+
+    ///重启软件
+    static bool restartSelf();
+
+    ///休眠函数(毫秒)
+    static void mSleep(int mSecond);
+};
+
+#endif // APPCONFIG_H

+ 88 - 0
source/VideoDecode/src/Base/FunctionTransfer.cpp

@@ -0,0 +1,88 @@
+#include "FunctionTransfer.h"
+
+#include <QThread>
+#include <QDebug>
+
+Qt::HANDLE FunctionTransfer::gMainThreadId = nullptr;
+FunctionTransfer *FunctionTransfer::main_thread_forward = nullptr;
+
+Q_DECLARE_METATYPE(std::function<void()>)
+
+FunctionTransfer::FunctionTransfer(QObject *parent) :
+    QObject(parent)
+{
+    //因为std::function<void()>是自定义的类型 要跨线程传递需要先注册一下
+    qRegisterMetaType<std::function<void()>>();
+
+    connect(this, SIGNAL(comming(std::function<void()>)), this, SLOT(slotExec(std::function<void()>)), Qt::BlockingQueuedConnection);
+    connect(this, SIGNAL(comming_noBlock(std::function<void()>)), this, SLOT(slotExec(std::function<void()>)), Qt::QueuedConnection);
+}
+
+FunctionTransfer::~FunctionTransfer()
+{
+
+}
+
+void FunctionTransfer::init(Qt::HANDLE id)
+{
+    gMainThreadId = id;
+    FunctionTransfer::main_thread_forward = new FunctionTransfer();
+}
+
+bool FunctionTransfer::isMainThread()
+{
+    if (gMainThreadId == nullptr)
+    {
+        qDebug()<<__FILE__<<__LINE__<<__FUNCTION__<<"the main thread id is not set!";
+        return false;
+    }
+
+    if (QThread::currentThreadId() == gMainThreadId)
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+void FunctionTransfer::runInMainThread(std::function<void()> f, bool isBlock)
+{
+//    FunctionTransfer::main_thread_forward->exec(f, isBlock);
+    if(FunctionTransfer::isMainThread())
+    {
+        f();
+    }
+    else
+    {
+        if (isBlock)
+        {
+            Q_EMIT FunctionTransfer::main_thread_forward->comming(f);
+        }
+        else
+        {
+            Q_EMIT FunctionTransfer::main_thread_forward->comming_noBlock(f);
+        }
+    }
+}
+
+void FunctionTransfer::slotExec(std::function<void()> f)
+{
+    f();
+//    if(FunctionTransfer::isMainThread())
+//    {
+//        f();
+//    }
+//    else
+//    {
+//        if (isBlock)
+//        {
+//            Q_EMIT this->comming(f);
+//        }
+//        else
+//        {
+//            Q_EMIT this->comming_noBlock(f);
+//        }
+//    }
+}

+ 48 - 0
source/VideoDecode/src/Base/FunctionTransfer.h

@@ -0,0 +1,48 @@
+#ifndef FUNCTIONTRANSFER_H
+#define FUNCTIONTRANSFER_H
+
+#include  <functional>
+
+#include <QThread>
+#include <QObject>
+
+//#ifdef QT_NO_KEYWORDS
+//#define signals Q_SIGNALS
+//#define slots Q_SLOTS
+//#define emit Q_EMIT
+//#endif
+
+class FunctionTransfer : public QObject
+{
+    Q_OBJECT
+public:
+
+    ///@brief 构造函数
+    explicit FunctionTransfer(QObject *parent = 0);
+    ~FunctionTransfer();
+
+    static void init(Qt::HANDLE id);
+    static bool isMainThread();
+
+public:
+    ///@brief 制定函数f在main中执行
+    static void runInMainThread(std::function<void()> f, bool isBlock = false);
+
+private:
+    static Qt::HANDLE gMainThreadId;
+
+    //在全局数据区实例化一个FunctionTransfer的实例,该实例所在的线程就是主线程。
+    static FunctionTransfer *main_thread_forward;
+
+Q_SIGNALS:
+    ///@brief 在别的线程有函数对象传来
+    void comming(std::function<void()> f);
+    void comming_noBlock(std::function<void()> f);
+
+private Q_SLOTS:
+    ///@brief 执行函数对象
+    void slotExec(std::function<void()> f);
+
+};
+
+#endif // FUNCTIONTRANSFER_H

+ 696 - 0
source/VideoDecode/src/Video/ShowVideoWidget.cpp

@@ -0,0 +1,696 @@
+
+#include "ShowVideoWidget.h"
+#include "ui_ShowVideoWidget.h"
+
+#include <QPainter>
+#include <QDebug>
+#include <QOpenGLWidget>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
+#include <QOpenGLTexture>
+#include <QFile>
+#include <QOpenGLTexture>
+#include <QOpenGLBuffer>
+#include <QMouseEvent>
+
+#include <QTimer>
+#include <QDrag>
+#include <QMimeData>
+
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QScreen>
+#include <QDateTime>
+
+#include "AppConfig.h"
+
+#define ATTRIB_VERTEX 3
+#define ATTRIB_TEXTURE 4
+
+///用于绘制矩形
+//! [3]
+static const char *vertexShaderSource =
+    "attribute highp vec4 posAttr;\n"
+    "attribute lowp vec4 colAttr;\n"
+    "varying lowp vec4 col;\n"
+    "uniform highp mat4 matrix;\n"
+    "void main() {\n"
+    "   col = colAttr;\n"
+    "   gl_Position = posAttr;\n"
+    "}\n";
+
+static const char *fragmentShaderSource =
+    "varying lowp vec4 col;\n"
+    "void main() {\n"
+    "   gl_FragColor = col;\n"
+    "}\n";
+//! [3]
+
+
+ShowVideoWidget::ShowVideoWidget(QWidget *parent) :
+    QOpenGLWidget(parent),
+    ui(new Ui::ShowVideoWidget)
+{
+    ui->setupUi(this);
+
+    connect(ui->pushButton_close, &QPushButton::clicked, this, &ShowVideoWidget::sig_CloseBtnClick);
+
+    ui->pushButton_close->hide();
+
+    mIsPlaying = false;
+    mPlayFailed = false;
+
+    ui->widget_erro->hide();
+    ui->widget_name->hide();
+
+    textureUniformY = 0;
+    textureUniformU = 0;
+    textureUniformV = 0;
+    id_y = 0;
+    id_u = 0;
+    id_v = 0;
+
+    m_pVSHader = NULL;
+    m_pFSHader = NULL;
+    m_pShaderProgram = NULL;
+    m_pTextureY = NULL;
+    m_pTextureU = NULL;
+    m_pTextureV = NULL;
+
+    m_vertexVertices = new GLfloat[8];
+
+    mVideoFrame.reset();
+
+    m_nVideoH = 0;
+    m_nVideoW = 0;
+
+    mPicIndex_X = 0;
+    mPicIndex_Y = 0;
+
+    setAcceptDrops(true);
+
+    mCurrentVideoKeepAspectRatio = AppConfig::gVideoKeepAspectRatio;
+    mIsShowFaceRect = false;
+
+    mIsCloseAble = true;
+
+    mIsOpenGLInited = false;
+
+    mLastGetFrameTime = 0;
+
+}
+
+ShowVideoWidget::~ShowVideoWidget()
+{
+    delete ui;
+}
+
+void ShowVideoWidget::setIsPlaying(bool value)
+{
+    mIsPlaying = value;
+
+    FunctionTransfer::runInMainThread([=]()
+    {
+        if (!mIsPlaying)
+        {
+            ui->pushButton_close->hide();
+        }
+        update();
+    });
+
+}
+
+void ShowVideoWidget::setPlayFailed(bool value)
+{
+    mPlayFailed = value;
+    FunctionTransfer::runInMainThread([=]()
+    {
+        update();
+    });
+}
+
+void ShowVideoWidget::setCameraName(QString name)
+{
+    mCameraName = name;
+    FunctionTransfer::runInMainThread([=]()
+    {
+        update();
+    });
+}
+
+void ShowVideoWidget::setVideoWidth(int w, int h)
+{
+    if (w <= 0 || h <= 0) return;
+
+    m_nVideoW = w;
+    m_nVideoH = h;
+qDebug()<<__FUNCTION__<<w<<h<<this->isHidden();
+
+    if (mIsOpenGLInited)
+    {
+        FunctionTransfer::runInMainThread([=]()
+        {
+            resetGLVertex(this->width(), this->height());
+        });
+    }
+
+}
+
+void ShowVideoWidget::setCloseAble(bool isCloseAble)
+{
+    mIsCloseAble = isCloseAble;
+}
+
+void ShowVideoWidget::clear()
+{
+    FunctionTransfer::runInMainThread([=]()
+    {
+        mVideoFrame.reset();
+
+        mFaceInfoList.clear();
+
+        update();
+    });
+}
+
+void ShowVideoWidget::enterEvent(QEvent *event)
+{
+//    qDebug()<<__FUNCTION__;
+    if (mIsPlaying && mIsCloseAble)
+        ui->pushButton_close->show();
+}
+
+void ShowVideoWidget::leaveEvent(QEvent *event)
+{
+//    qDebug()<<__FUNCTION__;
+    ui->pushButton_close->hide();
+}
+
+void ShowVideoWidget::mouseMoveEvent(QMouseEvent *event)
+{
+    if ((event->buttons() & Qt::LeftButton) && mIsPlaying)
+    {
+        QDrag *drag = new QDrag(this);
+        QMimeData *mimeData = new QMimeData;
+
+        ///为拖动的鼠标设置一个图片
+//        QPixmap pixMap = QPixmap::grabWindow(this->winId());
+        QPixmap pixMap  = this->grab();
+//        QPixmap fullScreenPixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
+//        QPixmap pixMap = fullScreenPixmap.copy(200,100,600,400);
+        QString name = mCameraName;
+
+        if (name.isEmpty())
+        {
+            name = "drag";
+        }
+        QString filePath = QString("%1/%2.png").arg(AppConfig::AppDataPath_Tmp).arg(name);
+        pixMap.save(filePath);
+
+        QList<QUrl> list;
+        QUrl url = "file:///" + filePath;
+        list.append(url);
+        mimeData->setUrls(list);
+        drag->setPixmap(pixMap);
+
+        ///实现视频画面拖动,激发拖动事件
+        mimeData->setData("playerid", mPlayerId.toUtf8());
+        drag->setMimeData(mimeData);
+
+//        qDebug()<<__FUNCTION__<<"11111";
+        drag->start(Qt::CopyAction| Qt::MoveAction);
+//        qDebug()<<__FUNCTION__<<"99999";
+    }
+    else
+    {
+        QWidget::mouseMoveEvent(event);
+    }
+}
+
+void ShowVideoWidget::inputOneFrame(VideoFramePtr videoFrame)
+{
+    FunctionTransfer::runInMainThread([=]()
+    {
+        int width = videoFrame.get()->width();
+        int height = videoFrame.get()->height();
+
+        if (m_nVideoW <= 0 || m_nVideoH <= 0 || m_nVideoW != width || m_nVideoH != height)
+        {
+            setVideoWidth(width, height);
+        }
+
+        mLastGetFrameTime = QDateTime::currentMSecsSinceEpoch();
+
+        mVideoFrame.reset();
+        mVideoFrame = videoFrame;
+
+        update(); //调用update将执行 paintEvent函数
+    });
+
+}
+
+
+void ShowVideoWidget::initializeGL()
+{
+    qDebug()<<__FUNCTION__<<mVideoFrame.get();
+
+    mIsOpenGLInited = true;
+
+    initializeOpenGLFunctions();
+    glEnable(GL_DEPTH_TEST);
+    //现代opengl渲染管线依赖着色器来处理传入的数据
+    //着色器:就是使用openGL着色语言(OpenGL Shading Language, GLSL)编写的一个小函数,
+    //       GLSL是构成所有OpenGL着色器的语言,具体的GLSL语言的语法需要读者查找相关资料
+    //初始化顶点着色器 对象
+    m_pVSHader = new QOpenGLShader(QOpenGLShader::Vertex, this);
+    //顶点着色器源码
+    const char *vsrc = "attribute vec4 vertexIn; \
+    attribute vec2 textureIn; \
+    varying vec2 textureOut;  \
+    void main(void)           \
+    {                         \
+        gl_Position = vertexIn; \
+        textureOut = textureIn; \
+    }";
+    //编译顶点着色器程序
+    bool bCompile = m_pVSHader->compileSourceCode(vsrc);
+    if(!bCompile)
+    {
+    }
+    //初始化片段着色器 功能gpu中yuv转换成rgb
+    m_pFSHader = new QOpenGLShader(QOpenGLShader::Fragment, this);
+    //片段着色器源码(windows下opengl es 需要加上float这句话)
+        const char *fsrc =
+    #if defined(WIN32)
+        "#ifdef GL_ES\n"
+        "precision mediump float;\n"
+        "#endif\n"
+    #else
+    #endif
+    "varying vec2 textureOut; \
+    uniform sampler2D tex_y; \
+    uniform sampler2D tex_u; \
+    uniform sampler2D tex_v; \
+    void main(void) \
+    { \
+        vec3 yuv; \
+        vec3 rgb; \
+        yuv.x = texture2D(tex_y, textureOut).r; \
+        yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
+        yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
+        rgb = mat3( 1,       1,         1, \
+                    0,       -0.39465,  2.03211, \
+                    1.13983, -0.58060,  0) * yuv; \
+        gl_FragColor = vec4(rgb, 1); \
+    }";
+    //将glsl源码送入编译器编译着色器程序
+    bCompile = m_pFSHader->compileSourceCode(fsrc);
+    if(!bCompile)
+    {
+    }
+
+
+    ///用于绘制矩形
+    m_program = new QOpenGLShaderProgram(this);
+    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
+    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
+    m_program->link();
+    m_posAttr = m_program->attributeLocation("posAttr");
+    m_colAttr = m_program->attributeLocation("colAttr");
+
+
+#define PROGRAM_VERTEX_ATTRIBUTE 0
+#define PROGRAM_TEXCOORD_ATTRIBUTE 1
+    //创建着色器程序容器
+    m_pShaderProgram = new QOpenGLShaderProgram;
+    //将片段着色器添加到程序容器
+    m_pShaderProgram->addShader(m_pFSHader);
+    //将顶点着色器添加到程序容器
+    m_pShaderProgram->addShader(m_pVSHader);
+    //绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明
+    m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX);
+    //绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明
+    m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE);
+    //链接所有所有添入到的着色器程序
+    m_pShaderProgram->link();
+    //激活所有链接
+    m_pShaderProgram->bind();
+    //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在
+    //片段着色器源码中可以看到
+    textureUniformY = m_pShaderProgram->uniformLocation("tex_y");
+    textureUniformU =  m_pShaderProgram->uniformLocation("tex_u");
+    textureUniformV =  m_pShaderProgram->uniformLocation("tex_v");
+
+    // 顶点矩阵
+    const GLfloat vertexVertices[] = {
+        -1.0f, -1.0f,
+         1.0f, -1.0f,
+         -1.0f, 1.0f,
+         1.0f, 1.0f,
+    };
+
+    memcpy(m_vertexVertices, vertexVertices, sizeof(vertexVertices));
+
+    //纹理矩阵
+    static const GLfloat textureVertices[] = {
+        0.0f,  1.0f,
+        1.0f,  1.0f,
+        0.0f,  0.0f,
+        1.0f,  0.0f,
+    };
+    //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
+    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, m_vertexVertices);
+    //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
+    glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
+    //启用ATTRIB_VERTEX属性的数据,默认是关闭的
+    glEnableVertexAttribArray(ATTRIB_VERTEX);
+    //启用ATTRIB_TEXTURE属性的数据,默认是关闭的
+    glEnableVertexAttribArray(ATTRIB_TEXTURE);
+    //分别创建y,u,v纹理对象
+    m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
+    m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
+    m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
+    m_pTextureY->create();
+    m_pTextureU->create();
+    m_pTextureV->create();
+    //获取返回y分量的纹理索引值
+    id_y = m_pTextureY->textureId();
+    //获取返回u分量的纹理索引值
+    id_u = m_pTextureU->textureId();
+    //获取返回v分量的纹理索引值
+    id_v = m_pTextureV->textureId();
+    glClearColor(0.0,0.0,0.0,0.0);//设置背景色-黑色
+    //qDebug("addr=%x id_y = %d id_u=%d id_v=%d\n", this, id_y, id_u, id_v);
+}
+
+void ShowVideoWidget::resetGLVertex(int window_W, int window_H)
+{
+    if (m_nVideoW <= 0 || m_nVideoH <= 0 || !AppConfig::gVideoKeepAspectRatio) //铺满
+    {
+        mPicIndex_X = 0.0;
+        mPicIndex_Y = 0.0;
+
+        // 顶点矩阵
+        const GLfloat vertexVertices[] = {
+            -1.0f, -1.0f,
+             1.0f, -1.0f,
+             -1.0f, 1.0f,
+             1.0f, 1.0f,
+        };
+
+        memcpy(m_vertexVertices, vertexVertices, sizeof(vertexVertices));
+
+        //纹理矩阵
+        static const GLfloat textureVertices[] = {
+            0.0f,  1.0f,
+            1.0f,  1.0f,
+            0.0f,  0.0f,
+            1.0f,  0.0f,
+        };
+        //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
+        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, m_vertexVertices);
+        //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
+        glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
+        //启用ATTRIB_VERTEX属性的数据,默认是关闭的
+        glEnableVertexAttribArray(ATTRIB_VERTEX);
+        //启用ATTRIB_TEXTURE属性的数据,默认是关闭的
+        glEnableVertexAttribArray(ATTRIB_TEXTURE);
+    }
+    else //按比例
+    {
+        int pix_W = window_W;
+        int pix_H = m_nVideoH * pix_W / m_nVideoW;
+
+        int x = this->width() - pix_W;
+        int y = this->height() - pix_H;
+
+        x /= 2;
+        y /= 2;
+
+        if (y < 0)
+        {
+            pix_H = window_H;
+            pix_W = m_nVideoW * pix_H / m_nVideoH;
+
+            x = this->width() - pix_W;
+            y = this->height() - pix_H;
+
+            x /= 2;
+            y /= 2;
+
+        }
+
+        mPicIndex_X = x * 1.0 / window_W;
+        mPicIndex_Y = y * 1.0 / window_H;
+
+    //qDebug()<<window_W<<window_H<<pix_W<<pix_H<<x<<y;
+        float index_y = y *1.0 / window_H * 2.0 -1.0;
+        float index_y_1 = index_y * -1.0;
+        float index_y_2 = index_y;
+
+        float index_x = x *1.0 / window_W * 2.0 -1.0;
+        float index_x_1 = index_x * -1.0;
+        float index_x_2 = index_x;
+
+        const GLfloat vertexVertices[] = {
+            index_x_2, index_y_2,
+            index_x_1,  index_y_2,
+            index_x_2, index_y_1,
+            index_x_1,  index_y_1,
+        };
+
+        memcpy(m_vertexVertices, vertexVertices, sizeof(vertexVertices));
+
+    #if TEXTURE_HALF
+        static const GLfloat textureVertices[] = {
+            0.0f,  1.0f,
+            0.5f,  1.0f,
+            0.0f,  0.0f,
+            0.5f,  0.0f,
+        };
+    #else
+        static const GLfloat textureVertices[] = {
+            0.0f,  1.0f,
+            1.0f,  1.0f,
+            0.0f,  0.0f,
+            1.0f,  0.0f,
+        };
+    #endif
+        //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式
+        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, m_vertexVertices);
+        //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式
+        glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);
+        //启用ATTRIB_VERTEX属性的数据,默认是关闭的
+        glEnableVertexAttribArray(ATTRIB_VERTEX);
+        //启用ATTRIB_TEXTURE属性的数据,默认是关闭的
+        glEnableVertexAttribArray(ATTRIB_TEXTURE);
+    }
+
+}
+
+void ShowVideoWidget::resizeGL(int window_W, int window_H)
+{
+    mLastGetFrameTime = QDateTime::currentMSecsSinceEpoch();
+
+//    qDebug()<<__FUNCTION__<<m_pBufYuv420p<<window_W<<window_H;
+    if(window_H == 0)// 防止被零除
+    {
+        window_H = 1;// 将高设为1
+    }
+    //设置视口
+    glViewport(0, 0, window_W, window_H);
+
+    int x = window_W - ui->pushButton_close->width() - 22;
+    int y = 22;
+    ui->pushButton_close->move(x, y);
+
+    x = 0;
+    y = window_H / 2 - ui->widget_erro->height() / 2;
+    ui->widget_erro->move(x, y);
+    ui->widget_erro->resize(window_W, ui->widget_erro->height());
+
+    x = 0;
+    y = window_H - ui->widget_name->height() - 6;
+    ui->widget_name->move(x, y);
+    ui->widget_name->resize(window_W, ui->widget_name->height());
+
+
+    resetGLVertex(window_W, window_H);
+
+}
+
+ void ShowVideoWidget::paintGL()
+ {
+//     qDebug()<<__FUNCTION__<<mCameraName<<m_pBufYuv420p;
+
+    mLastGetFrameTime = QDateTime::currentMSecsSinceEpoch();
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    if (ui->pushButton_close->isVisible())
+        if (!mIsPlaying || !mIsCloseAble)
+        {
+         ui->pushButton_close->hide();
+        }
+
+    if (!mCameraName.isEmpty() && mIsPlaying)
+    {
+        ui->widget_name->show();
+
+        QFontMetrics fontMetrics(ui->label_name->font());
+        int fontSize = fontMetrics.width(mCameraName);//获取之前设置的字符串的像素大小
+        QString str = mCameraName;
+        if(fontSize > (this->width() / 2))
+        {
+           str = fontMetrics.elidedText(mCameraName, Qt::ElideRight, (this->width() / 2));//返回一个带有省略号的字符串
+        }
+        ui->label_name->setText(str);
+        ui->label_name->setToolTip(mCameraName);
+    }
+    else
+    {
+        ui->widget_name->hide();
+    }
+
+    if (mIsPlaying && mPlayFailed)
+    {
+        ui->widget_erro->show();
+    }
+    else
+    {
+        ui->widget_erro->hide();
+    }
+
+    ///设置中按比例发生改变 则需要重置x y偏量
+    if (mCurrentVideoKeepAspectRatio != AppConfig::gVideoKeepAspectRatio)
+    {
+        mCurrentVideoKeepAspectRatio = AppConfig::gVideoKeepAspectRatio;
+        resetGLVertex(this->width(), this->height());
+    }
+
+    ///绘制矩形框
+    if (mIsShowFaceRect && !mFaceInfoList.isEmpty())
+    {
+        m_program->bind();
+
+        glEnableVertexAttribArray(0);
+        glEnableVertexAttribArray(1);
+
+        for (int i = 0; i < mFaceInfoList.size(); i++)
+        {
+            FaceInfoNode faceNode = mFaceInfoList.at(i);
+            QRect rect = faceNode.faceRect;
+
+            int window_W = this->width();
+            int window_H = this->height();
+
+            int pix_W = rect.width();
+            int pix_H = rect.height();
+
+            int x = rect.x();
+            int y = rect.y();
+
+            float index_x_1 = x *1.0 / m_nVideoW * 2.0 -1.0;
+            float index_y_1 = 1.0 - (y *1.0 / m_nVideoH * 2.0);
+
+            float index_x_2 = (x + pix_W) * 1.0 / m_nVideoW * 2.0 - 1.0;
+            float index_y_2 = index_y_1;
+
+            float index_x_3 = index_x_2;
+            float index_y_3 = 1.0 - ((y + pix_H) * 1.0 / m_nVideoH * 2.0);
+
+            float index_x_4 = index_x_1;
+            float index_y_4 = index_y_3;
+
+            index_x_1 += mPicIndex_X;
+            index_x_2 += mPicIndex_X;
+            index_x_3 += mPicIndex_X;
+            index_x_4 += mPicIndex_X;
+
+            index_y_1 -= mPicIndex_Y;
+            index_y_2 -= mPicIndex_Y;
+            index_y_3 -= mPicIndex_Y;
+            index_y_4 -= mPicIndex_Y;
+
+            const GLfloat vertices[] = {
+                index_x_1, index_y_1,
+                index_x_2,  index_y_2,
+                index_x_3, index_y_3,
+                index_x_4,  index_y_4,
+            };
+
+//            #7AC451 - 122, 196, 81 - 0.47843f, 0.768627f, 0.317647f
+            const GLfloat colors[] = {
+                0.47843f, 0.768627f, 0.317647f,
+                0.47843f, 0.768627f, 0.317647f,
+                0.47843f, 0.768627f, 0.317647f,
+                0.47843f, 0.768627f, 0.317647f
+            };
+
+            glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
+            glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);
+
+            glLineWidth(2.2f); //设置画笔宽度
+
+            glDrawArrays(GL_LINE_LOOP, 0, 4);
+        }
+
+        glDisableVertexAttribArray(1);
+        glDisableVertexAttribArray(0);
+
+        m_program->release();
+    }
+
+    VideoFrame * videoFrame = mVideoFrame.get();
+
+    if (videoFrame != nullptr)
+    {
+        uint8_t *m_pBufYuv420p = videoFrame->buffer();
+
+        if (m_pBufYuv420p != NULL)
+        {
+            m_pShaderProgram->bind();
+
+            //加载y数据纹理
+            //激活纹理单元GL_TEXTURE0
+            glActiveTexture(GL_TEXTURE0);
+            //使用来自y数据生成纹理
+            glBindTexture(GL_TEXTURE_2D, id_y);
+            //使用内存中m_pBufYuv420p数据创建真正的y数据纹理
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW, m_nVideoH, 0, GL_RED, GL_UNSIGNED_BYTE, m_pBufYuv420p);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+            //加载u数据纹理
+            glActiveTexture(GL_TEXTURE1);//激活纹理单元GL_TEXTURE1
+            glBindTexture(GL_TEXTURE_2D, id_u);
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+            //加载v数据纹理
+            glActiveTexture(GL_TEXTURE2);//激活纹理单元GL_TEXTURE2
+            glBindTexture(GL_TEXTURE_2D, id_v);
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_nVideoW/2, m_nVideoH/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pBufYuv420p+m_nVideoW*m_nVideoH*5/4);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+            //指定y纹理要使用新值 只能用0,1,2等表示纹理单元的索引,这是opengl不人性化的地方
+            //0对应纹理单元GL_TEXTURE0 1对应纹理单元GL_TEXTURE1 2对应纹理的单元
+            glUniform1i(textureUniformY, 0);
+            //指定u纹理要使用新值
+            glUniform1i(textureUniformU, 1);
+            //指定v纹理要使用新值
+            glUniform1i(textureUniformV, 2);
+            //使用顶点数组方式绘制图形
+            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+            m_pShaderProgram->release();
+
+        }
+    }
+
+}

+ 121 - 0
source/VideoDecode/src/Video/ShowVideoWidget.h

@@ -0,0 +1,121 @@
+#ifndef SHOWVIDEOWIDGET_H
+#define SHOWVIDEOWIDGET_H
+
+#include <QWidget>
+#include <QPaintEvent>
+#include <QResizeEvent>
+
+#include <QOpenGLWidget>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
+#include <QOpenGLTexture>
+#include <QFile>
+
+#include "Base/FunctionTransfer.h"
+#include "Video/VideoFrame.h"
+
+namespace Ui {
+class ShowVideoWidget;
+}
+
+struct FaceInfoNode
+{
+    QRect faceRect;
+};
+
+///显示视频用的widget(使用OPENGL绘制YUV420P数据)
+///这个仅仅是显示视频画面的控件
+
+class ShowVideoWidget : public QOpenGLWidget,protected QOpenGLFunctions
+{
+    Q_OBJECT
+
+public:
+    explicit ShowVideoWidget(QWidget *parent = 0);
+    ~ShowVideoWidget();
+
+    void setPlayerId(QString id){mPlayerId=id;} //用于协助拖拽 区分是哪个窗口
+    QString getPlayerId(){return mPlayerId;}
+
+    void setCloseAble(bool isCloseAble);
+
+    void clear();
+    
+    void setIsPlaying(bool value);
+    void setPlayFailed(bool value);
+    
+    void setCameraName(QString name);
+
+    void setVideoWidth(int w, int h);
+
+    void setShowFaceRect(bool value){mIsShowFaceRect = value;}
+
+    qint64 getLastGetFrameTime(){return mLastGetFrameTime;}
+
+    void inputOneFrame(VideoFramePtr videoFrame);
+
+signals:
+    void sig_CloseBtnClick();
+    void sig_Drag(QString id_from, QString id_to);
+
+protected:
+    void enterEvent(QEvent *event);
+    void leaveEvent(QEvent *event);
+    void mouseMoveEvent(QMouseEvent *event);
+
+private:
+    bool mIsPlaying;
+    bool mPlayFailed; //播放失败
+    bool mIsCloseAble; //是否显示关闭按钮
+
+    QString mCameraName;
+    qint64 mLastGetFrameTime; //上一次获取到帧的时间戳
+    void resetGLVertex(int window_W, int window_H);
+
+protected:
+    void initializeGL() Q_DECL_OVERRIDE;
+    void resizeGL(int window_W, int window_H) Q_DECL_OVERRIDE;
+    void paintGL() Q_DECL_OVERRIDE;
+
+private:
+    ///OPenGL用于绘制图像
+    GLuint textureUniformY; //y纹理数据位置
+    GLuint textureUniformU; //u纹理数据位置
+    GLuint textureUniformV; //v纹理数据位置
+    GLuint id_y; //y纹理对象ID
+    GLuint id_u; //u纹理对象ID
+    GLuint id_v; //v纹理对象ID
+    QOpenGLTexture* m_pTextureY;  //y纹理对象
+    QOpenGLTexture* m_pTextureU;  //u纹理对象
+    QOpenGLTexture* m_pTextureV;  //v纹理对象
+    QOpenGLShader *m_pVSHader;  //顶点着色器程序对象
+    QOpenGLShader *m_pFSHader;  //片段着色器对象
+    QOpenGLShaderProgram *m_pShaderProgram; //着色器程序容器
+    GLfloat *m_vertexVertices; // 顶点矩阵
+
+    float mPicIndex_X; //按比例显示情况下 图像偏移量百分比 (相对于窗口大小的)
+    float mPicIndex_Y; //
+    int m_nVideoW; //视频分辨率宽
+    int m_nVideoH; //视频分辨率高
+
+    VideoFramePtr mVideoFrame;
+    QList<FaceInfoNode> mFaceInfoList;
+
+    bool mIsOpenGLInited; //openGL初始化函数是否执行过了
+
+    ///OpenGL用于绘制矩形
+    bool mIsShowFaceRect;
+    GLuint m_posAttr;
+    GLuint m_colAttr;
+    QOpenGLShaderProgram *m_program;
+
+    bool mCurrentVideoKeepAspectRatio; //当前模式是否是按比例 当检测到与全局变量不一致的时候 则重新设置openGL矩阵
+
+    QString mPlayerId;
+
+private:
+    Ui::ShowVideoWidget *ui;
+
+};
+
+#endif // SHOWVIDEOWIDGET_H

+ 218 - 0
source/VideoDecode/src/Video/ShowVideoWidget.ui

@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowVideoWidget</class>
+ <widget class="QWidget" name="ShowVideoWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>412</width>
+    <height>304</height>
+   </rect>
+  </property>
+  <property name="mouseTracking">
+   <bool>true</bool>
+  </property>
+  <property name="focusPolicy">
+   <enum>Qt::ClickFocus</enum>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QWidget" name="widget_back" native="true">
+     <property name="styleSheet">
+      <string notr="true">QWidget#widget_back
+{
+border:1px solid rgba(74, 108, 155, 0);
+border-radius:2px;
+	background-color: rgba(74, 108, 155, 0);
+}
+
+</string>
+     </property>
+     <widget class="QWidget" name="widget_name" native="true">
+      <property name="geometry">
+       <rect>
+        <x>20</x>
+        <y>250</y>
+        <width>371</width>
+        <height>22</height>
+       </rect>
+      </property>
+      <property name="styleSheet">
+       <string notr="true">QWidget#widget_name
+{
+	background-color: rgba(220, 255, 193, 0);
+}
+</string>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>6</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>62</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_name">
+         <property name="maximumSize">
+          <size>
+           <width>200</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="styleSheet">
+          <string notr="true">	font-family: &quot;微软雅黑&quot;;
+	color:rgb(33,33,33);
+	font-size: 13px;
+border:1px solid rgba(102, 102, 102, 92);
+	border-radius:1px;
+	
+background-color: rgb(255, 255, 255);
+	
+	</string>
+         </property>
+         <property name="text">
+          <string>TextLabel</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="widget_erro" native="true">
+      <property name="geometry">
+       <rect>
+        <x>20</x>
+        <y>20</y>
+        <width>331</width>
+        <height>49</height>
+       </rect>
+      </property>
+      <property name="styleSheet">
+       <string notr="true">QWidget#widget_erro
+{
+	background-color: rgba(220, 255, 193, 0);
+}
+</string>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <spacer name="horizontalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>63</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_erro">
+         <property name="styleSheet">
+          <string notr="true">	font-family: &quot;微软雅黑&quot;;
+	color:rgb(233,233,233);
+	font-size: 16px;
+border:1px solid rgba(102, 102, 102, 92);
+	border-radius:1px;
+	padding-left:6px;
+padding-right:6px;
+background-color: rgb(255, 25, 25);
+	
+	</string>
+         </property>
+         <property name="text">
+          <string>播放失败,重试中...</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>63</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QPushButton" name="pushButton_close">
+      <property name="geometry">
+       <rect>
+        <x>240</x>
+        <y>120</y>
+        <width>36</width>
+        <height>36</height>
+       </rect>
+      </property>
+      <property name="cursor">
+       <cursorShape>PointingHandCursor</cursorShape>
+      </property>
+      <property name="styleSheet">
+       <string notr="true">QPushButton{ 
+image: url(:/image/closebtn_3.png);
+background-color:rgba(200,80,6,0);  
+border-radius:18px; 
+color: rgb(255, 255, 255);
+font-size:16px;
+font-family:&quot;微软雅黑&quot;
+} 
+QPushButton:hover{ 
+background-color:rgb(200,80,6); 
+} 
+QPushButton:pressed{ 
+background-color:rgb(200,80,6);  
+}
+</string>
+      </property>
+      <property name="text">
+       <string/>
+      </property>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 22 - 0
source/VideoDecode/src/main.cpp

@@ -0,0 +1,22 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+#include "mainwindow.h"
+#include <QApplication>
+
+#include <QTextCodec>
+
+int main(int argc, char *argv[])
+{
+    QApplication a(argc, argv);
+
+    QTextCodec *codec = QTextCodec::codecForName("GBK");
+    QTextCodec::setCodecForLocale(codec);
+
+    MainWindow w;
+    w.show();
+
+    return a.exec();
+}

+ 84 - 0
source/VideoDecode/src/mainwindow.cpp

@@ -0,0 +1,84 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QDebug>
+#include <QMessageBox>
+
+#include <QFileDialog>
+
+MainWindow::MainWindow(QWidget *parent) :
+    QMainWindow(parent),
+    ui(new Ui::MainWindow)
+{
+    ui->setupUi(this);
+
+    FunctionTransfer::init(QThread::currentThreadId());
+
+    av_register_all();
+    avformat_network_init();
+    avdevice_register_all();
+
+    mReadVideoFileThread = new ReadVideoFileThread();
+    mReadVideoFileThread->setVideoCallBack(this);
+//    mReadH264FileThread->startRead((char*)"../data/test.h264");
+
+    connect(ui->pushButton_open, SIGNAL(clicked(bool)), this, SLOT(slotBtnClick(bool)));
+    connect(ui->pushButton_play, SIGNAL(clicked(bool)), this, SLOT(slotBtnClick(bool)));
+
+}
+
+MainWindow::~MainWindow()
+{
+    delete ui;
+}
+
+void MainWindow::slotBtnClick(bool isChecked)
+{
+    if (QObject::sender() == ui->pushButton_open)
+    {
+        QString s = QFileDialog::getOpenFileName(
+                   this, "选择要播放的文件",
+                    QCoreApplication::applicationDirPath() + "/../data",//初始目录
+                    QStringLiteral("H264/H265 Files (*.264 *.265 *.h264 *.h265);;")
+                    +QStringLiteral("All Files (*.*)"));
+        if (!s.isEmpty())
+        {
+            s.replace("/","\\");
+            ui->lineEdit->setText(s);
+        }
+    }
+    else if (QObject::sender() == ui->pushButton_play)
+    {
+        QString filePath = ui->lineEdit->text();
+
+        QString suffix = QFileInfo(filePath).suffix();
+
+        AVCodecID id;
+
+        ///根据后缀判断是264还是265
+        if (suffix.toLower() == "h265" || suffix.toLower() == "265")
+        {
+            id = AV_CODEC_ID_H265;
+        }
+        else if (suffix.toLower() == "h264" || suffix.toLower() == "264")
+        {
+            id = AV_CODEC_ID_H264;
+        }
+
+        mReadVideoFileThread->startRead(filePath.toUtf8().data(), id);
+    }
+
+}
+
+///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+void MainWindow::onDisplayVideo(std::shared_ptr<VideoFrame> videoFrame, int frameNum)
+{
+    ui->label_frameNum->setText(QString("%1").arg(frameNum));
+    ui->widget_videoPlayer->inputOneFrame(videoFrame);
+}

+ 41 - 0
source/VideoDecode/src/mainwindow.h

@@ -0,0 +1,41 @@
+/**
+ * 叶海辉
+ * QQ群121376426
+ * http://blog.yundiantech.com/
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QTimer>
+#include <QMainWindow>
+
+#include "VideoReader/ReadVideoFileThread.h"
+
+namespace Ui {
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow, public VideoCallBack
+{
+    Q_OBJECT
+
+public:
+    explicit MainWindow(QWidget *parent = 0);
+    ~MainWindow();
+
+private:
+    Ui::MainWindow *ui;
+
+    ReadVideoFileThread *mReadVideoFileThread;
+
+private slots:
+    void slotBtnClick(bool isChecked);
+
+    ///以下函数,是播放器的回调函数,用于输出信息给界面
+protected:
+    ///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    void onDisplayVideo(VideoFramePtr videoFrame, int frameNum);
+};
+
+#endif // MAINWINDOW_H

+ 137 - 0
source/VideoDecode/src/mainwindow.ui

@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>452</width>
+    <height>305</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralWidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="ShowVideoWidget" name="widget_videoPlayer" native="true">
+      <property name="enabled">
+       <bool>true</bool>
+      </property>
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="mouseTracking">
+       <bool>true</bool>
+      </property>
+      <property name="styleSheet">
+       <string notr="true">QWidget#widget_videoPlayer
+{
+	background-color: rgb(255, 55, 195);
+}</string>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label_frameNum_2">
+        <property name="text">
+         <string>帧序号:</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_frameNum">
+        <property name="text">
+         <string>0</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QWidget" name="widget" native="true">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>32</height>
+       </size>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <widget class="QLineEdit" name="lineEdit">
+         <property name="text">
+          <string>../data/test.h265</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="pushButton_open">
+         <property name="maximumSize">
+          <size>
+           <width>36</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="text">
+          <string>...</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="pushButton_play">
+         <property name="text">
+          <string>开始播放</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menuBar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>452</width>
+     <height>25</height>
+    </rect>
+   </property>
+  </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+  <customwidget>
+   <class>ShowVideoWidget</class>
+   <extends>QWidget</extends>
+   <header>Video/ShowVideoWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

+ 11 - 0
source/VideoDecode/说明.txt

@@ -0,0 +1,11 @@
+这是Qt的工程,建议使用Qt Creator 打开
+
+从零开始学习音视频编程技术(41) H.264播放器
+
+FFMPEG的版本是4.1
+
+学习音视频技术欢迎访问 http://blog.yundiantech.com  
+音视频技术交流讨论欢迎加 QQ群 121376426  
+
+
+