Forráskód Böngészése

V2.1.2

1.使用OpenGL渲染视频图像
2.修复电脑没有音频设备的时候,视频播放几十秒就卡准的问题。
3.将信号槽封装,简化将代码移入主函数运行的操作。
huihui 5 éve
szülő
commit
7ca4352054

+ 11 - 5
VideoPlayer.pro

@@ -8,6 +8,7 @@ QT       += core gui
 
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
+CONFIG += c++11
 
 UI_DIR  = obj/Gui
 MOC_DIR = obj/Moc
@@ -33,19 +34,24 @@ include(module/VideoPlayer/VideoPlayer.pri)
 #°üº¬¿ÉÍ϶¯´°ÌåµÄ´úÂë
 include(module/DragAbleWidget/DragAbleWidget.pri)
 
-SOURCES += src/main.cpp \
-    src/Widget/VideoPlayerWidget.cpp \
+SOURCES += \
+    src/main.cpp \
+    src/AppConfig.cpp \
+    src/Base/FunctionTransfer.cpp \
+    src/MainWindow.cpp \
     src/Widget/ShowVideoWidget.cpp \
     src/Widget/VideoSlider.cpp
 
-
 HEADERS  += \
-    src/Widget/VideoPlayerWidget.h \
+    src/AppConfig.h \
+    src/Base/FunctionTransfer.h \
+    src/MainWindow.h \
     src/Widget/ShowVideoWidget.h \
     src/Widget/VideoSlider.h
 
+
 FORMS    += \
-    src/Widget/VideoPlayerWidget.ui \
+    src/MainWindow.ui \
     src/Widget/ShowVideoWidget.ui
 
 RESOURCES += \

BIN
bin32/VideoPlayer.exe


+ 5 - 3
module/VideoPlayer/VideoPlayer.pri

@@ -1,22 +1,24 @@
+CONFIG += c++11
 QMAKE_CXXFLAGS += -std=c++11
 
 SOURCES +=  \
-    $$PWD/src/AppConfig.cpp \
     $$PWD/src/Mutex/Cond.cpp \
     $$PWD/src/Mutex/Mutex.cpp \
     $$PWD/src/LogWriter/LogWriter.cpp \
     $$PWD/src/VideoPlayer/VideoPlayer.cpp \
+    $$PWD/src/VideoPlayer/Video/VideoFrame.cpp \
     $$PWD/src/VideoPlayer/Video/VideoPlayer_VideoThread.cpp \
     $$PWD/src/VideoPlayer/Audio/VideoPlayer_AudioThread.cpp \
     $$PWD/src/VideoPlayer/Audio/PcmVolumeControl.cpp \
-    $$PWD/src/EventHandle/VideoPlayerEventHandle.cpp
+    $$PWD/src/EventHandle/VideoPlayerEventHandle.cpp \
+    $$PWD/src/types.cpp
 
 HEADERS  += \
-    $$PWD/src/AppConfig.h \
     $$PWD/src/Mutex/Cond.h \
     $$PWD/src/Mutex/Mutex.h \
     $$PWD/src/LogWriter/LogWriter.h \
     $$PWD/src/VideoPlayer/VideoPlayer.h \
+    $$PWD/src/VideoPlayer/Video/VideoFrame.h \
     $$PWD/src/VideoPlayer/Audio/PcmVolumeControl.h \
     $$PWD/src/EventHandle/VideoPlayerEventHandle.h \
     $$PWD/src/types.h

+ 0 - 197
module/VideoPlayer/src/AppConfig.cpp

@@ -1,197 +0,0 @@
-#include "AppConfig.h"
-
-#if defined(WIN32)
-#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
-
-int AppConfig::VERSION = 1;
-char AppConfig::VERSION_NAME[32] = "2.1.1";
-
-LogWriter* AppConfig::gLogWriter = new LogWriter;
-
-AppConfig::AppConfig()
-{
-
-}
-
-void AppConfig::mkdir(char *dirName)
-{
-#if defined(WIN32)
-    ///如果目录不存在 则创建
-    if (access(dirName, 0)!=0)
-    {
-        _mkdir(dirName);
-    }
-#else
-    ///如果目录不存在 则创建
-    if (access(dirName, R_OK)!=0)
-    {
-        char cmd[128] = {0};
-        sprintf(cmd,"mkdir %s", dirName);
-        system(cmd);
-    }
-#endif
-}
-
-void AppConfig::mkpath(char *path)
-{
-#if defined(WIN32)
-        ///windows创建文件夹命令 路径得是反斜杠 因此这里需要替换一下
-        char dirPath[128] = {0};
-        strcpy(dirPath, path);
-
-        AppConfig::replaceChar(dirPath, '/', '\\');
-
-        ///如果目录不存在 则创建它
-        if (access(dirPath, 0)!=0)
-        {
-    //        _mkdir(dirPath);
-            char cmd[128];
-            sprintf(cmd,"mkdir %s", dirPath);
-            system(cmd);
-        }
-
-#else
-    ///如果目录不存在 则创建它
-    if (access(path,R_OK)!=0)
-    {
-        char cmd[128];
-        sprintf(cmd,"mkdir %s -p",path);
-        system(cmd);
-    }
-#endif
-}
-
-void AppConfig::removeDir(char *dirName)
-{
-    if (strlen(dirName) <= 0) return;
-
-    if (access(dirName, 0) != 0 ) //文件夹不存在
-    {
-        return;
-    }
-
-#if defined(WIN32)
-
-    ///删除本地文件
-    char cmd[128];
-    sprintf(cmd,"rd /s /q \"%s\"", dirName);
-    system(cmd);
-
-#else
-
-    char cmd[128];
-    sprintf(cmd,"rm -rf \"%s\"",dirName);
-    system(cmd);
-
-#endif
-}
-
-void AppConfig::removeFile(const char *filePath)
-{
-    if (filePath == NULL || strlen(filePath) <= 0) return;
-
-#if defined(WIN32)
-
-        ///删除本地文件
-        remove(filePath);
-
-#else
-        ///删除本地文件
-        char cmd[128] = {0};
-        sprintf(cmd,"rm -rf \"%s\"",filePath);
-        system(cmd);
-#endif
-}
-
-void AppConfig::copyFile(const char *srcFile, const char *destFile)
-{
-
-#if defined(WIN32)
-        CopyFileA(srcFile, destFile, FALSE);
-#else
-
-        ///将文件拷贝到远端服务器
-        char copyfilecmd[512];
-        sprintf(copyfilecmd,"cp \"%s\" \"%s\"", srcFile, destFile);
-        system(copyfilecmd);
-
-#endif
-}
-
-void AppConfig::replaceChar(char *string, char oldChar, char newChar)
-{
-    int len = strlen(string);
-    int i;
-    for (i = 0; i < len; i++){
-        if (string[i] == oldChar){
-            string[i] = newChar;
-        }
-    }
-}
-
-
-std::string AppConfig::removeFirstAndLastSpace(std::string &s)
-{
-    if (s.empty())
-    {
-        return s;
-    }
-    s.erase(0,s.find_first_not_of(" "));
-    s.erase(s.find_last_not_of(" ") + 1);
-    return s;
-}
-
-void AppConfig::mSleep(int mSecond)
-{
-#if defined(WIN32)
-    Sleep(mSecond);
-#else
-    usleep(mSecond * 1000);
-#endif
-}
-
-int64_t AppConfig::getTimeStamp_MilliSecond()
-{
-
-    int mSecond = 0; //当前毫秒数
-
-#if defined(WIN32)
-
-    SYSTEMTIME sys;
-    GetLocalTime( &sys );
-
-    mSecond = sys.wMilliseconds;
-
-#else
-
-    struct timeval    tv;
-    struct timezone tz;
-
-    struct tm         *p;
-
-    gettimeofday(&tv, &tz);
-    p = localtime(&tv.tv_sec);
-
-    mSecond = tv.tv_usec / 1000;
-
-
-#endif
-
-    int64_t timeStamp = ((int64_t)time(NULL)) * 1000 + mSecond;
-
-    return timeStamp;
-
-}

+ 0 - 58
module/VideoPlayer/src/AppConfig.h

@@ -1,58 +0,0 @@
-#ifndef APPCONFIG_H
-#define APPCONFIG_H
-
-#include <stdio.h>
-#include <string>
-
-#include "LogWriter/LogWriter.h"
-
-#if defined(WIN32)
-
-#define PRINT_INT64_FORMAT "%I64d"
-
-#else
-#include <sys/time.h>
-#include <stdio.h>
-#include <unistd.h>
-
-void Sleep(long mSeconds);
-
-#define PRINT_INT64_FORMAT "%lld"
-
-#endif
-
-#include <QDebug>
-#define OUTPUT qDebug
-
-typedef unsigned char uchar;
-
-class AppConfig
-{
-public:
-    AppConfig();
-
-    static int VERSION;
-    static char VERSION_NAME[32];
-
-    static LogWriter* gLogWriter;
-
-    static void mkdir(char *dirName); //创建文件夹
-    static void mkpath(char *path);   //创建多级文件夹
-
-    static void removeDir(char *dirName);
-    static void removeFile(const char *filePath);
-
-    static void copyFile(const char *srcFile, const char *destFile);
-
-    static void replaceChar(char *string, char oldChar, char newChar); //字符串替换字符
-    static std::string removeFirstAndLastSpace(std::string &s); //移除开始和结束的空格
-
-    static std::string getIpFromRtspUrl(std::string rtspUrl); //从rtsp地址中获取ip地址
-
-    static void mSleep(int mSecond);
-
-    static int64_t getTimeStamp_MilliSecond(); //获取时间戳(毫秒)
-
-};
-
-#endif // APPCONFIG_H

+ 4 - 0
module/VideoPlayer/src/EventHandle/VideoPlayerEventHandle.cpp

@@ -1,2 +1,6 @@
 #include "VideoPlayerEventHandle.h"
 
+VideoPlayerCallBack::~VideoPlayerCallBack()
+{
+
+}

+ 5 - 2
module/VideoPlayer/src/EventHandle/VideoPlayerEventHandle.h

@@ -2,10 +2,13 @@
 #define VIDEOPLAYEREVENTHANDLE_H
 
 #include "types.h"
+#include "VideoPlayer/Video/VideoFrame.h"
 
 class VideoPlayerCallBack
 {
 public:
+    ~VideoPlayerCallBack();
+
     ///打开文件失败
     virtual void onOpenVideoFileFailed(const int &code = 0) = 0;
 
@@ -18,8 +21,8 @@ public:
     ///播放器状态改变的时候回调此函数
     virtual void onPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio) = 0;
 
-    ///显示rgb数据,此函数不宜做耗时操作,否则会影响播放的流畅性,传入的brgb32Buffer,在函数返回后既失效
-    virtual void onDisplayVideo(const uint8_t *brgb32Buffer, const int &width, const int &height) = 0;
+    ///播放视频,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    virtual void onDisplayVideo(VideoFramePtr videoFrame) = 0;
 
 };
 

+ 11 - 10
module/VideoPlayer/src/LogWriter/LogWriter.cpp

@@ -4,7 +4,7 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "AppConfig.h"
+#include "types.h"
 
 #ifdef WIN32
 #include <direct.h>
@@ -15,14 +15,15 @@
 #endif
 
 #if defined(WIN32)
-#include <windows.h>
+    #include <WinSock2.h>
+    #include <Windows.h>
 static DWORD WINAPI thread_Func(LPVOID pM)
 #else
-#include <sys/time.h>
-#include <stdio.h>
-#include <time.h>
-#include <stdlib.h>
-#include <unistd.h>
+    #include <sys/time.h>
+    #include <stdio.h>
+    #include <time.h>
+    #include <stdlib.h>
+    #include <unistd.h>
 static void *thread_Func(void *pM)
 #endif
 {
@@ -76,7 +77,7 @@ void LogWriter::writeLog(int cameraId, const std::string &str)
 
     LogInfoNode node;
     node.cameraId = cameraId;
-    node.mCreateTime =  AppConfig::getTimeStamp_MilliSecond();
+    node.mCreateTime =  getTimeStamp_MilliSecond();
 
 #if defined(WIN32)
     SYSTEMTIME sys;
@@ -153,7 +154,7 @@ void LogWriter::run()
         else
         {
             uint64_t startTime = mLogNodeList.front().mCreateTime;
-            uint64_t currentTime = AppConfig::getTimeStamp_MilliSecond();
+            uint64_t currentTime = getTimeStamp_MilliSecond();
 
             if ((currentTime - startTime) > (10000)) //日志数据最迟10秒写入文件
             {
@@ -235,7 +236,7 @@ void LogWriter::run()
         else
         {
             mCondition->Unlock();
-            AppConfig::mSleep(5000);
+            mSleep(5000);
             continue;
         }
     }

+ 2 - 2
module/VideoPlayer/src/Mutex/Cond.h

@@ -9,8 +9,8 @@
 #endif
 
 #if defined(WIN32) && !defined(MINGW)
-    #include <winsock2.h>
-    #include <windows.h>
+    #include <WinSock2.h>
+    #include <Windows.h>
 #else
     #include <pthread.h>
     #include <time.h>

+ 2 - 2
module/VideoPlayer/src/Mutex/Mutex.h

@@ -3,8 +3,8 @@
 
 
 #if defined(WIN32)
-    #include <winsock2.h>
-    #include <windows.h>
+    #include <WinSock2.h>
+    #include <Windows.h>
 //#elif defined(Q_OS_LINUX)
 #else
     #include <pthread.h>

+ 55 - 0
module/VideoPlayer/src/VideoPlayer/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
module/VideoPlayer/src/VideoPlayer/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

+ 20 - 28
module/VideoPlayer/src/VideoPlayer/Video/VideoPlayer_VideoThread.cpp

@@ -8,7 +8,7 @@
 
 void VideoPlayer::decodeVideoThread()
 {
-OUTPUT("%s start \n", __FUNCTION__);
+    fprintf(stderr, "%s start \n", __FUNCTION__);
 
     mIsVideoThreadFinished = false;
 
@@ -19,24 +19,24 @@ OUTPUT("%s start \n", __FUNCTION__);
     double audio_pts = 0; //音频pts
 
     ///解码视频相关
-    AVFrame *pFrame, *pFrameRGB;
-    uint8_t *out_buffer_rgb; //解码后的rgb数据
+    AVFrame *pFrame, *pFrameYUV;
+    uint8_t *out_buffer_yuv; //解码后的yuv数据
     struct SwsContext *img_convert_ctx;  //用于解码后的视频格式转换
 
     AVCodecContext *pCodecCtx = mVideoStream->codec; //视频解码器
 
     pFrame = av_frame_alloc();
-    pFrameRGB = av_frame_alloc();
+    pFrameYUV = av_frame_alloc();
 
-    ///这里我们改成了 将解码后的YUV数据转换成RGB32
+    ///由于解码后的数据不一定都是yuv420p,因此需要将解码后的数据统一转换成YUV420P
     img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
             pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
-            AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
+            AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
 
-    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
+    numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,pCodecCtx->height);
 
-    out_buffer_rgb = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
-    avpicture_fill((AVPicture *) pFrameRGB, out_buffer_rgb, AV_PIX_FMT_RGB32,
+    out_buffer_yuv = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
+    avpicture_fill((AVPicture *) pFrameYUV, out_buffer_yuv, AV_PIX_FMT_YUV420P,
             pCodecCtx->width, pCodecCtx->height);
 
     while(1)
@@ -49,7 +49,7 @@ OUTPUT("%s start \n", __FUNCTION__);
 
         if (mIsPause == true) //判断暂停
         {
-            AppConfig::mSleep(10);
+            mSleep(10);
             continue;
         }
 
@@ -65,7 +65,7 @@ OUTPUT("%s start \n", __FUNCTION__);
             }
             else
             {
-                AppConfig::mSleep(1); //队列只是暂时没有数据而已
+                mSleep(1); //队列只是暂时没有数据而已
                 continue;
             }
         }
@@ -85,16 +85,6 @@ OUTPUT("%s start \n", __FUNCTION__);
             continue;
         }
 
-        ///avcodec_decode_video2 是ffmpeg2中的用法
-//        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
-//        if (ret < 0)
-//        {
-//            av_packet_unref(packet);
-//            continue;
-//        }
-
-
-        ///ffmpeg4.1解码视频使用新的api 如下:
         if (avcodec_send_packet(pCodecCtx, packet) != 0)
         {
            qDebug("input AVPacket to decoder failed!\n");
@@ -174,16 +164,16 @@ OUTPUT("%s start \n", __FUNCTION__);
 
                 if (!mIsNeedPause)
                 {
-                    AppConfig::mSleep(delayTime);
+                    mSleep(delayTime);
                 }
             }
 
             sws_scale(img_convert_ctx,
                     (uint8_t const * const *) pFrame->data,
-                    pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
-                    pFrameRGB->linesize);
+                    pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data,
+                    pFrameYUV->linesize);
 
-            doDisplayVideo(out_buffer_rgb, pCodecCtx->width, pCodecCtx->height);
+            doDisplayVideo(out_buffer_yuv, pCodecCtx->width, pCodecCtx->height);
 
             if (mIsNeedPause)
             {
@@ -196,8 +186,8 @@ OUTPUT("%s start \n", __FUNCTION__);
     }
 
     av_free(pFrame);
-    av_free(pFrameRGB);
-    av_free(out_buffer_rgb);
+    av_free(pFrameYUV);
+    av_free(out_buffer_yuv);
 
     sws_freeContext(img_convert_ctx);
 
@@ -207,6 +197,8 @@ OUTPUT("%s start \n", __FUNCTION__);
     }
 
     mIsVideoThreadFinished = true;
-OUTPUT("%s finished \n", __FUNCTION__);
+
+    fprintf(stderr, "%s finished \n", __FUNCTION__);
+
     return;
 }

+ 43 - 29
module/VideoPlayer/src/VideoPlayer/VideoPlayer.cpp

@@ -37,7 +37,7 @@ bool VideoPlayer::initPlayer()
 
     if (SDL_Init(SDL_INIT_AUDIO))
     {
-        OUTPUT("Could not initialize SDL - %s. \n", SDL_GetError());
+        fprintf(stderr, "Could not initialize SDL - %s. \n", SDL_GetError());
         return false;
     }
 
@@ -127,7 +127,7 @@ bool VideoPlayer::stop(bool isWait)
     {
         while(!mIsReadThreadFinished)
         {
-            AppConfig::mSleep(3);
+            mSleep(3);
         }
     }
 
@@ -191,7 +191,7 @@ int VideoPlayer::openSDL()
 
         return -1;
     }
-OUTPUT("mAudioID=%d\n\n\n\n\n\n", mAudioID);
+fprintf(stderr, "mAudioID=%d\n\n\n\n\n\n", mAudioID);
     return 0;
 }
 
@@ -237,14 +237,14 @@ void VideoPlayer::readVideoFile()
 
     if (avformat_open_input(&pFormatCtx, file_path, nullptr, nullptr) != 0)
     {
-        OUTPUT("can't open the file. \n");
+        fprintf(stderr, "can't open the file. \n");
         doOpenVideoFileFailed();
         goto end;
     }
 
     if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
     {
-        OUTPUT("Could't find stream infomation.\n");
+        fprintf(stderr, "Could't find stream infomation.\n");
         doOpenVideoFileFailed();
         goto end;
     }
@@ -276,7 +276,7 @@ void VideoPlayer::readVideoFile()
 
         if (pCodec == nullptr)
         {
-            OUTPUT("PCodec not found.\n");
+            fprintf(stderr, "PCodec not found.\n");
             doOpenVideoFileFailed();
             goto end;
         }
@@ -284,7 +284,7 @@ void VideoPlayer::readVideoFile()
         ///打开视频解码器
         if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
         {
-            OUTPUT("Could not open video codec.\n");
+            fprintf(stderr, "Could not open video codec.\n");
             doOpenVideoFileFailed();
             goto end;
         }
@@ -308,7 +308,7 @@ void VideoPlayer::readVideoFile()
 
         if (aCodec == NULL)
         {
-            OUTPUT("ACodec not found.\n");
+            fprintf(stderr, "ACodec not found.\n");
             audioStream = -1;
         }
         else
@@ -316,7 +316,7 @@ void VideoPlayer::readVideoFile()
             ///打开音频解码器
             if (avcodec_open2(aCodecCtx, aCodec, nullptr) < 0)
             {
-                OUTPUT("Could not open audio codec.\n");
+                fprintf(stderr, "Could not open audio codec.\n");
                 doOpenVideoFileFailed();
                 goto end;
             }
@@ -378,7 +378,7 @@ void VideoPlayer::readVideoFile()
                 char buff[128]={0};
                 av_strerror(ret, buff, 128);
 
-                OUTPUT("Could not open resample context %s\n", buff);
+                fprintf(stderr, "Could not open resample context %s\n", buff);
                 swr_free(&swrCtx);
                 swrCtx = nullptr;
                 doOpenVideoFileFailed();
@@ -419,7 +419,7 @@ void VideoPlayer::readVideoFile()
     doPlayerStateChanged(VideoPlayer_Playing, mVideoStream != nullptr, mAudioStream != nullptr);
 
     mVideoStartTime = av_gettime();
-OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
+fprintf(stderr, "%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
     while (1)
     {
         if (mIsQuit)
@@ -446,7 +446,7 @@ OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
 
             if (av_seek_frame(pFormatCtx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0)
             {
-                OUTPUT("%s: error while seeking\n",pFormatCtx->filename);
+                fprintf(stderr, "%s: error while seeking\n",pFormatCtx->filename);
             }
             else
             {
@@ -489,13 +489,13 @@ OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
         //这个值可以稍微写大一些
         if (mAudioPacktList.size() > MAX_AUDIO_SIZE || mVideoPacktList.size() > MAX_VIDEO_SIZE)
         {
-            AppConfig::mSleep(10);
+            mSleep(10);
             continue;
         }
 
         if (mIsPause == true)
         {
-            AppConfig::mSleep(10);
+            mSleep(10);
             continue;
         }
 
@@ -509,7 +509,7 @@ OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
                 break; //解码线程也执行完了 可以退出了
             }
 
-            AppConfig::mSleep(10);
+            mSleep(10);
             continue;
         }
 
@@ -520,8 +520,16 @@ OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
         }
         else if( packet.stream_index == audioStream )
         {
-            inputAudioQuene(packet);
-            //这里我们将数据存入队列 因此不调用 av_free_packet 释放
+            if (mIsAudioThreadFinished)
+            { ///SDL没有打开,则音频数据直接释放
+                av_packet_unref(&packet);
+            }
+            else
+            {
+                inputAudioQuene(packet);
+                //这里我们将数据存入队列 因此不调用 av_free_packet 释放
+            }
+
         }
         else
         {
@@ -534,7 +542,7 @@ OUTPUT("%s mIsQuit=%d \n", __FUNCTION__, mIsQuit);
     ///等待播放完毕
     while (!mIsQuit)
     {
-        AppConfig::mSleep(100);
+        mSleep(100);
     }
 
 end:
@@ -549,7 +557,7 @@ end:
 
     while((mVideoStream != nullptr && !mIsVideoThreadFinished) || (mAudioStream != nullptr && !mIsAudioThreadFinished))
     {
-        AppConfig::mSleep(10);
+        mSleep(10);
     } //确保视频线程结束后 再销毁队列
 
     closeSDL();
@@ -591,7 +599,7 @@ end:
 
     mIsReadThreadFinished = true;
 
-OUTPUT("%s finished \n", __FUNCTION__);
+fprintf(stderr, "%s finished \n", __FUNCTION__);
 }
 
 bool VideoPlayer::inputVideoQuene(const AVPacket &pkt)
@@ -653,7 +661,7 @@ void VideoPlayer::clearAudioQuene()
 ///打开文件失败
 void VideoPlayer::doOpenVideoFileFailed(const int &code)
 {
-    OUTPUT("%s \n", __FUNCTION__);
+    fprintf(stderr, "%s \n", __FUNCTION__);
 
     if (mVideoPlayerCallBack != nullptr)
     {
@@ -665,7 +673,7 @@ void VideoPlayer::doOpenVideoFileFailed(const int &code)
 ///打开sdl失败的时候回调此函数
 void VideoPlayer::doOpenSdlFailed(const int &code)
 {
-    OUTPUT("%s \n", __FUNCTION__);
+    fprintf(stderr, "%s \n", __FUNCTION__);
 
     if (mVideoPlayerCallBack != nullptr)
     {
@@ -676,7 +684,7 @@ void VideoPlayer::doOpenSdlFailed(const int &code)
 ///获取到视频时长的时候调用此函数
 void VideoPlayer::doTotalTimeChanged(const int64_t &uSec)
 {
-    OUTPUT("%s \n", __FUNCTION__);
+    fprintf(stderr, "%s \n", __FUNCTION__);
 
     if (mVideoPlayerCallBack != nullptr)
     {
@@ -687,7 +695,7 @@ void VideoPlayer::doTotalTimeChanged(const int64_t &uSec)
 ///播放器状态改变的时候回调此函数
 void VideoPlayer::doPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio)
 {
-    OUTPUT("%s \n", __FUNCTION__);
+    fprintf(stderr, "%s \n", __FUNCTION__);
 
     if (mVideoPlayerCallBack != nullptr)
     {
@@ -696,13 +704,19 @@ void VideoPlayer::doPlayerStateChanged(const VideoPlayerState &state, const bool
 
 }
 
-///显示rgb数据,此函数不宜做耗时操作,否则会影响播放的流畅性,传入的brgb32Buffer,在函数返回后既失效
-void VideoPlayer::doDisplayVideo(const uint8_t *brgb32Buffer, const int &width, const int &height)
+///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+void VideoPlayer::doDisplayVideo(const uint8_t *yuv420Buffer, const int &width, const int &height)
 {
-//    OUTPUT("%s \n", __FUNCTION__);
-
+//    fprintf(stderr, "%s \n", __FUNCTION__);
     if (mVideoPlayerCallBack != nullptr)
     {
-        mVideoPlayerCallBack->onDisplayVideo(brgb32Buffer, width, height);
+        VideoFramePtr videoFrame = std::make_shared<VideoFrame>();
+
+        VideoFrame * ptr = videoFrame.get();
+
+        ptr->initBuffer(width, height);
+        ptr->setYUVbuf(yuv420Buffer);
+
+        mVideoPlayerCallBack->onDisplayVideo(videoFrame);
     }
 }

+ 3 - 3
module/VideoPlayer/src/VideoPlayer/VideoPlayer.h

@@ -29,8 +29,8 @@ extern "C"
     #include <SDL_config.h>
 }
 
-#include "AppConfig.h"
 #include "types.h"
+#include "Mutex/Cond.h"
 #include "EventHandle/VideoPlayerEventHandle.h"
 
 #define SDL_AUDIO_BUFFER_SIZE 1024
@@ -182,8 +182,8 @@ private:
     ///播放器状态改变的时候回调此函数
     void doPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio);
 
-    ///显示rgb数据,此函数不宜做耗时操作,否则会影响播放的流畅性,传入的brgb32Buffer,在函数返回后既失效
-    void doDisplayVideo(const uint8_t *brgb32Buffer, const int &width, const int &height);
+    ///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    void doDisplayVideo(const uint8_t *yuv420Buffer, const int &width, const int &height);
 
 };
 

+ 62 - 0
module/VideoPlayer/src/types.cpp

@@ -0,0 +1,62 @@
+#include "types.h"
+
+#include <time.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
+
+void mSleep(int mSecond)
+{
+#if defined(WIN32)
+    Sleep(mSecond);
+#else
+    usleep(mSecond * 1000);
+#endif
+}
+
+
+int64_t getTimeStamp_MilliSecond()
+{
+
+    int mSecond = 0; //µ±Ç°ºÁÃëÊý
+
+#if defined(WIN32)
+
+    SYSTEMTIME sys;
+    GetLocalTime( &sys );
+
+    mSecond = sys.wMilliseconds;
+
+#else
+
+    struct timeval    tv;
+    struct timezone tz;
+
+    struct tm         *p;
+
+    gettimeofday(&tv, &tz);
+    p = localtime(&tv.tv_sec);
+
+    mSecond = tv.tv_usec / 1000;
+
+
+#endif
+
+    int64_t timeStamp = ((int64_t)time(NULL)) * 1000 + mSecond;
+
+    return timeStamp;
+}

+ 20 - 0
module/VideoPlayer/src/types.h

@@ -4,6 +4,17 @@
 #include <stdio.h>
 #include <stdint.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>
+#endif
+
 enum VideoPlayerState
 {
     VideoPlayer_Playing,
@@ -11,4 +22,13 @@ enum VideoPlayerState
     VideoPlayer_Stop
 };
 
+#if defined(WIN32)
+#else
+    void Sleep(long mSeconds);
+#endif
+
+void mSleep(int mSecond);
+
+int64_t getTimeStamp_MilliSecond(); //»ñȡʱ¼ä´Á£¨ºÁÃ룩
+
 #endif // TYPES_H

+ 605 - 0
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.2";
+
+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
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

+ 60 - 0
src/Base/FunctionTransfer.cpp

@@ -0,0 +1,60 @@
+#include "FunctionTransfer.h"
+
+#include <QThread>
+#include <QDebug>
+
+Qt::HANDLE FunctionTransfer::gMainThreadId = nullptr;
+FunctionTransfer *FunctionTransfer::main_thread_forward = nullptr;
+
+FunctionTransfer::FunctionTransfer(QObject *parent) :
+    QObject(parent)
+{
+    connect(this, SIGNAL(comming(std::function<void()>)), this, SLOT(exec(std::function<void()>)), Qt::BlockingQueuedConnection);
+}
+
+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)
+{
+    FunctionTransfer::main_thread_forward->exec(f);
+}
+
+void FunctionTransfer::exec(std::function<void()> f)
+{
+    if(FunctionTransfer::isMainThread())
+    {
+        f();
+    }
+    else
+    {
+        Q_EMIT this->comming(f);
+    }
+
+}

+ 47 - 0
src/Base/FunctionTransfer.h

@@ -0,0 +1,47 @@
+#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);
+
+private:
+    static Qt::HANDLE gMainThreadId;
+
+    //在全局数据区实例化一个FunctionTransfer的实例,该实例所在的线程就是主线程。
+    static FunctionTransfer *main_thread_forward;
+
+Q_SIGNALS:
+    ///@brief 在别的线程有函数对象传来
+    void comming(std::function<void()> f);
+
+public Q_SLOTS:
+    ///@brief 执行函数对象
+    void exec(std::function<void()> f);
+
+};
+
+#endif // FUNCTIONTRANSFER_H

+ 87 - 104
src/Widget/VideoPlayerWidget.cpp → src/MainWindow.cpp

@@ -4,8 +4,8 @@
  * http://blog.yundiantech.com/
  */
 
-#include "VideoPlayerWidget.h"
-#include "ui_VideoPlayerWidget.h"
+#include "MainWindow.h"
+#include "ui_MainWindow.h"
 
 #include <QPainter>
 #include <QPaintEvent>
@@ -16,14 +16,19 @@
 #include <QMouseEvent>
 #include <QMessageBox>
 
+#include "AppConfig.h"
+#include "Base/FunctionTransfer.h"
+
 Q_DECLARE_METATYPE(VideoPlayerState)
 
-VideoPlayerWidget::VideoPlayerWidget(QWidget *parent) :
+MainWindow::MainWindow(QWidget *parent) :
     DragAbleWidget(parent),
-    ui(new Ui::VideoPlayerWidget)
+    ui(new Ui::MainWindow)
 {
     ui->setupUi(this->getContainWidget());
 
+    FunctionTransfer::init(QThread::currentThreadId());
+
     ///初始化播放器
     VideoPlayer::initPlayer();
 
@@ -33,18 +38,12 @@ VideoPlayerWidget::VideoPlayerWidget(QWidget *parent) :
     //因为VideoPlayer::PlayerState是自定义的类型 要跨线程传递需要先注册一下
     qRegisterMetaType<VideoPlayerState>();
 
-    connect(this, &VideoPlayerWidget::sig_OpenVideoFileFailed, this, &VideoPlayerWidget::slotOpenVideoFileFailed);
-    connect(this, &VideoPlayerWidget::sig_OpenSdlFailed, this, &VideoPlayerWidget::slotOpenSdlFailed);
-    connect(this, &VideoPlayerWidget::sig_TotalTimeChanged, this, &VideoPlayerWidget::slotTotalTimeChanged);
-    connect(this, &VideoPlayerWidget::sig_PlayerStateChanged, this, &VideoPlayerWidget::slotStateChanged);
-    connect(this, &VideoPlayerWidget::sig_DisplayVideo, this, &VideoPlayerWidget::slotDisplayVideo);
-
-    connect(ui->pushButton_open, &QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
-    connect(ui->toolButton_open, &QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
-    connect(ui->pushButton_play, &QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
-    connect(ui->pushButton_pause,&QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
-    connect(ui->pushButton_stop, &QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
-    connect(ui->pushButton_volume, &QPushButton::clicked, this, &VideoPlayerWidget::slotBtnClick);
+    connect(ui->pushButton_open, &QPushButton::clicked, this, &MainWindow::slotBtnClick);
+    connect(ui->toolButton_open, &QPushButton::clicked, this, &MainWindow::slotBtnClick);
+    connect(ui->pushButton_play, &QPushButton::clicked, this, &MainWindow::slotBtnClick);
+    connect(ui->pushButton_pause,&QPushButton::clicked, this, &MainWindow::slotBtnClick);
+    connect(ui->pushButton_stop, &QPushButton::clicked, this, &MainWindow::slotBtnClick);
+    connect(ui->pushButton_volume, &QPushButton::clicked, this, &MainWindow::slotBtnClick);
 
     connect(ui->horizontalSlider, SIGNAL(sig_valueChanged(int)), this, SLOT(slotSliderMoved(int)));
     connect(ui->horizontalSlider_volume, SIGNAL(valueChanged(int)), this, SLOT(slotSliderMoved(int)));
@@ -57,11 +56,11 @@ VideoPlayerWidget::VideoPlayerWidget(QWidget *parent) :
     mPlayer->setVideoPlayerCallBack(this);
 
     mTimer = new QTimer; //定时器-获取当前视频时间
-    connect(mTimer, &QTimer::timeout, this, &VideoPlayerWidget::slotTimerTimeOut);
+    connect(mTimer, &QTimer::timeout, this, &MainWindow::slotTimerTimeOut);
     mTimer->setInterval(500);
 
     mTimer_CheckControlWidget = new QTimer; //用于控制控制界面的出现和隐藏
-    connect(mTimer_CheckControlWidget, &QTimer::timeout, this, &VideoPlayerWidget::slotTimerTimeOut);
+    connect(mTimer_CheckControlWidget, &QTimer::timeout, this, &MainWindow::slotTimerTimeOut);
     mTimer_CheckControlWidget->setInterval(1500);
 
     mAnimation_ControlWidget  = new QPropertyAnimation(ui->widget_controller, "geometry");
@@ -76,12 +75,15 @@ VideoPlayerWidget::VideoPlayerWidget(QWidget *parent) :
 
 }
 
-VideoPlayerWidget::~VideoPlayerWidget()
+MainWindow::~MainWindow()
 {
+    AppConfig::saveConfigInfoToFile();
+    AppConfig::removeDirectory(AppConfig::AppDataPath_Tmp);
+
     delete ui;
 }
 
-void VideoPlayerWidget::showOutControlWidget()
+void MainWindow::showOutControlWidget()
 {
 
     mAnimation_ControlWidget->setDuration(800);
@@ -112,7 +114,7 @@ void VideoPlayerWidget::showOutControlWidget()
 
 }
 
-void VideoPlayerWidget::hideControlWidget()
+void MainWindow::hideControlWidget()
 {
     mAnimation_ControlWidget->setTargetObject(ui->widget_controller);
 
@@ -131,7 +133,7 @@ void VideoPlayerWidget::hideControlWidget()
 }
 
 
-void VideoPlayerWidget::slotSliderMoved(int value)
+void MainWindow::slotSliderMoved(int value)
 {
     if (QObject::sender() == ui->horizontalSlider)
     {
@@ -144,7 +146,7 @@ void VideoPlayerWidget::slotSliderMoved(int value)
     }
 }
 
-void VideoPlayerWidget::slotTimerTimeOut()
+void MainWindow::slotTimerTimeOut()
 {
     if (QObject::sender() == mTimer)
     {
@@ -166,7 +168,7 @@ void VideoPlayerWidget::slotTimerTimeOut()
     }
 }
 
-void VideoPlayerWidget::slotBtnClick(bool isChecked)
+void MainWindow::slotBtnClick(bool isChecked)
 {
     if (QObject::sender() == ui->pushButton_play)
     {
@@ -184,16 +186,19 @@ void VideoPlayerWidget::slotBtnClick(bool isChecked)
     {
         QString s = QFileDialog::getOpenFileName(
                    this, QStringLiteral("选择要播放的文件"),
-                    "/",//初始目录
+                    AppConfig::gVideoFilePath,//初始目录
                     QStringLiteral("视频文件 (*.flv *.rmvb *.avi *.MP4 *.mkv);;")
                     +QStringLiteral("音频文件 (*.mp3 *.wma *.wav);;")
                     +QStringLiteral("所有文件 (*.*)"));
         if (!s.isEmpty())
         {
-            s.replace("/","\\");
+//            s.replace("/","\\");
 
             mPlayer->stop(true); //如果在播放则先停止
             mPlayer->startPlay(s.toStdString());
+
+            AppConfig::gVideoFilePath = s;
+            AppConfig::saveConfigInfoToFile();
         }
     }
     else if (QObject::sender() == ui->pushButton_volume)
@@ -224,114 +229,92 @@ void VideoPlayerWidget::slotBtnClick(bool isChecked)
 }
 
 ///打开文件失败
-void VideoPlayerWidget::onOpenVideoFileFailed(const int &code)
+void MainWindow::onOpenVideoFileFailed(const int &code)
 {
-    emit sig_OpenVideoFileFailed(code);
+    FunctionTransfer::runInMainThread([&]()
+    {
+        QMessageBox::critical(NULL, "tips", QString("open file failed %1").arg(code));
+    });
 }
 
 ///打开SDL失败的时候回调此函数
-void VideoPlayerWidget::onOpenSdlFailed(const int &code)
+void MainWindow::onOpenSdlFailed(const int &code)
 {
-    emit sig_OpenSdlFailed(code);
+    FunctionTransfer::runInMainThread([&]()
+    {
+        QMessageBox::critical(NULL, "tips", QString("open Sdl failed %1").arg(code));
+    });
 }
 
 ///获取到视频时长的时候调用此函数
-void VideoPlayerWidget::onTotalTimeChanged(const int64_t &uSec)
-{
-    emit sig_TotalTimeChanged(uSec);
-}
-
-///播放器状态改变的时候回调此函数
-void VideoPlayerWidget::onPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio)
-{
-    emit sig_PlayerStateChanged(state, hasVideo, hasAudio);
-}
-
-void VideoPlayerWidget::slotOpenVideoFileFailed(const int &code)
-{
-    QMessageBox::critical(NULL, "tips", QString("open file failed %1").arg(code));
-}
-
-void VideoPlayerWidget::slotOpenSdlFailed(const int &code)
-{
-    QMessageBox::critical(NULL, "tips", QString("open Sdl failed %1").arg(code));
-}
-
-void VideoPlayerWidget::slotTotalTimeChanged(const qint64 &uSec)
+void MainWindow::onTotalTimeChanged(const int64_t &uSec)
 {
-    qint64 Sec = uSec/1000000;
-
-    ui->horizontalSlider->setRange(0,Sec);
+    FunctionTransfer::runInMainThread([&]()
+    {
+        qint64 Sec = uSec/1000000;
 
-//    QString hStr = QString("00%1").arg(Sec/3600);
-    QString mStr = QString("00%1").arg(Sec/60);
-    QString sStr = QString("00%1").arg(Sec%60);
+        ui->horizontalSlider->setRange(0,Sec);
 
-    QString str = QString("%1:%2").arg(mStr.right(2)).arg(sStr.right(2));
-    ui->label_totaltime->setText(str);
+    //    QString hStr = QString("00%1").arg(Sec/3600);
+        QString mStr = QString("00%1").arg(Sec/60);
+        QString sStr = QString("00%1").arg(Sec%60);
 
+        QString str = QString("%1:%2").arg(mStr.right(2)).arg(sStr.right(2));
+        ui->label_totaltime->setText(str);
+    });
 }
 
-void VideoPlayerWidget::slotStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio)
+///播放器状态改变的时候回调此函数
+void MainWindow::onPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio)
 {
-    if (state == VideoPlayer_Stop)
+    FunctionTransfer::runInMainThread([&]()
     {
-        ui->stackedWidget->setCurrentWidget(ui->page_open);
+        if (state == VideoPlayer_Stop)
+        {
+            ui->stackedWidget->setCurrentWidget(ui->page_open);
 
-        ui->pushButton_pause->hide();
-        ui->widget_videoPlayer->clear();
+            ui->pushButton_pause->hide();
+            ui->widget_videoPlayer->clear();
 
-        ui->horizontalSlider->setValue(0);
-        ui->label_currenttime->setText("00:00");
-        ui->label_totaltime->setText("00:00");
+            ui->horizontalSlider->setValue(0);
+            ui->label_currenttime->setText("00:00");
+            ui->label_totaltime->setText("00:00");
 
-        mTimer->stop();
+            mTimer->stop();
 
-    }
-    else if (state == VideoPlayer_Playing)
-    {
-        if (hasVideo)
-        {
-            ui->stackedWidget->setCurrentWidget(ui->page_video);
         }
-        else
+        else if (state == VideoPlayer_Playing)
         {
-            ui->stackedWidget->setCurrentWidget(ui->page_audio);
-        }
-
-        ui->pushButton_play->hide();
-        ui->pushButton_pause->show();
-
-        mTimer->start();
-    }
-    else if (state == VideoPlayer_Pause)
-    {
-        ui->pushButton_pause->hide();
-        ui->pushButton_play->show();
-    }
-}
-
-///显示rgb数据,此函数不宜做耗时操作,否则会影响播放的流畅性,传入的brgb32Buffer,在函数返回后既失效。
-void VideoPlayerWidget::onDisplayVideo(const uint8_t *brgb32Buffer, const int &width, const int &height)
-{
-//    qDebug()<<__FUNCTION__<<width<<height;
+            if (hasVideo)
+            {
+                ui->stackedWidget->setCurrentWidget(ui->page_video);
+            }
+            else
+            {
+                ui->stackedWidget->setCurrentWidget(ui->page_audio);
+            }
 
-    //把这个RGB数据 用QImage加载
-    QImage tmpImg((uchar *)brgb32Buffer, width, height, QImage::Format_RGB32);
-//  QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
-    QImage image = tmpImg.convertToFormat(QImage::Format_RGB888,Qt::NoAlpha); //去掉透明的部分 有些奇葩的视频会透明
+            ui->pushButton_play->hide();
+            ui->pushButton_pause->show();
 
-    emit sig_DisplayVideo(image);
+            mTimer->start();
+        }
+        else if (state == VideoPlayer_Pause)
+        {
+            ui->pushButton_pause->hide();
+            ui->pushButton_play->show();
+        }
+    });
 }
 
-void VideoPlayerWidget::slotDisplayVideo(const QImage &image)
+///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+void MainWindow::onDisplayVideo(std::shared_ptr<VideoFrame> videoFrame)
 {
-//    qDebug()<<__FUNCTION__<<image;
-    ui->widget_videoPlayer->inputOneFrame(image);
+    ui->widget_videoPlayer->inputOneFrame(videoFrame);
 }
 
 //图片显示部件时间过滤器处理
-bool VideoPlayerWidget::eventFilter(QObject *target, QEvent *event)
+bool MainWindow::eventFilter(QObject *target, QEvent *event)
 {
     if(target == ui->widget_container)
     {

+ 11 - 26
src/Widget/VideoPlayerWidget.h → src/MainWindow.h

@@ -4,8 +4,8 @@
  * http://blog.yundiantech.com/
  */
 
-#ifndef VIDEOPLAYERWIDGET_H
-#define VIDEOPLAYERWIDGET_H
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
 
 #include <QWidget>
 
@@ -19,23 +19,23 @@
 #include "DragAbleWidget.h"
 
 namespace Ui {
-class VideoPlayerWidget;
+class MainWindow;
 }
 
 ///这个是播放器的主界面 包括那些按钮和进度条之类的
-class VideoPlayerWidget : public DragAbleWidget, public VideoPlayerCallBack
+class MainWindow : public DragAbleWidget, public VideoPlayerCallBack
 {
     Q_OBJECT
 
 public:
-    explicit VideoPlayerWidget(QWidget *parent = 0);
-    ~VideoPlayerWidget();
+    explicit MainWindow(QWidget *parent = 0);
+    ~MainWindow();
 
 protected:
     bool eventFilter(QObject *target, QEvent *event);
 
 private:
-    Ui::VideoPlayerWidget *ui;
+    Ui::MainWindow *ui;
 
     VideoPlayer *mPlayer; //播放线程
     QTimer *mTimer; //定时器-获取当前视频时间
@@ -53,7 +53,7 @@ private slots:
     void slotBtnClick(bool isChecked);
 
 
-    ///以下函数,用于输出信息给界面
+    ///以下函数,是播放器的回调函数,用于输出信息给界面
 protected:
     ///打开文件失败
     void onOpenVideoFileFailed(const int &code);
@@ -67,24 +67,9 @@ protected:
     ///播放器状态改变的时候回调此函数
     void onPlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio);
 
-    ///显示rgb数据,此函数不宜做耗时操作,否则会影响播放的流畅性,传入的brgb32Buffer,在函数返回后既失效。
-    void onDisplayVideo(const uint8_t *brgb32Buffer, const int &width, const int &height);
-
-    ///由于不能在子线程中操作界面,因此需要通过信号槽的方式,将上面函数转移到主线程
-signals:
-    void sig_OpenVideoFileFailed(const int &code);
-    void sig_OpenSdlFailed(const int &code);
-    void sig_TotalTimeChanged(const qint64 &Usec);
-    void sig_PlayerStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio);
-    void sig_DisplayVideo(const QImage &image);
-
-private slots:
-    void slotOpenVideoFileFailed(const int &code);
-    void slotOpenSdlFailed(const int &code);
-    void slotTotalTimeChanged(const qint64 &uSec);
-    void slotStateChanged(const VideoPlayerState &state, const bool &hasVideo, const bool &hasAudio);
-    void slotDisplayVideo(const QImage &image);
+    ///显示视频数据,此函数不宜做耗时操作,否则会影响播放的流畅性。
+    void onDisplayVideo(VideoFramePtr videoFrame);
 
 };
 
-#endif // VIDEOPLAYERWIDGET_H
+#endif // MAINWINDOW_H

+ 2 - 2
src/Widget/VideoPlayerWidget.ui → src/MainWindow.ui

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>VideoPlayerWidget</class>
- <widget class="QWidget" name="VideoPlayerWidget">
+ <class>MainWindow</class>
+ <widget class="QWidget" name="MainWindow">
   <property name="geometry">
    <rect>
     <x>0</x>

+ 667 - 32
src/Widget/ShowVideoWidget.cpp

@@ -1,19 +1,103 @@
-/**
- * 叶海辉
- * QQ群121376426
- * http://blog.yundiantech.com/
- */
-
-#include "ShowVideoWidget.h"
-#include "ui_ShowVideoWidget.h"
+
+#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) :
-    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()
@@ -21,41 +105,592 @@ ShowVideoWidget::~ShowVideoWidget()
     delete ui;
 }
 
-void ShowVideoWidget::paintEvent(QPaintEvent *event)
+void ShowVideoWidget::setIsPlaying(bool value)
 {
-    QPainter painter(this);
-	
-	painter.setRenderHint(QPainter::Antialiasing);
-    painter.setRenderHint(QPainter::TextAntialiasing);
-    painter.setRenderHint(QPainter::SmoothPixmapTransform);
-    painter.setRenderHint(QPainter::HighQualityAntialiasing);
-	
-    painter.setBrush(Qt::black);
-    painter.drawRect(0,0,this->width(),this->height()); //先画成黑色
+    mIsPlaying = value;
 
-    if (mImage.size().width() <= 0) return;
+    FunctionTransfer::runInMainThread([&]()
+    {
+        if (!mIsPlaying)
+        {
+            ui->pushButton_close->hide();
+        }
+        update();
+    });
 
-    ///将图像按比例缩放成和窗口一样大小
-    QImage img = mImage.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); //这里效率比较低下  还不知道如何优化
+}
 
-    int x = this->width() - img.width();
-    int y = this->height() - img.height();
+void ShowVideoWidget::setPlayFailed(bool value)
+{
+    mPlayFailed = value;
+    FunctionTransfer::runInMainThread([&]()
+    {
+        update();
+    });
+}
+
+void ShowVideoWidget::setCameraName(QString name)
+{
+    mCameraName = name;
+    FunctionTransfer::runInMainThread([&]()
+    {
+        update();
+    });
+}
 
-    x /= 2;
-    y /= 2;
+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();
 
-    painter.drawImage(QPoint(x,y),img); //画出图像
+    if (mIsOpenGLInited)
+    {
+        FunctionTransfer::runInMainThread([&]()
+        {
+            resetGLVertex(this->width(), this->height());
+        });
+    }
 
 }
 
-void ShowVideoWidget::inputOneFrame(const QImage &img)
+void ShowVideoWidget::setCloseAble(bool isCloseAble)
 {
-    mImage = img;
-    update(); //调用update将执行 paintEvent函数
+    mIsCloseAble = isCloseAble;
 }
 
 void ShowVideoWidget::clear()
 {
-    mImage = QImage(0, 0);
-    update();
+    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();
+
+        }
+    }
+
 }

+ 92 - 12
src/Widget/ShowVideoWidget.h

@@ -1,23 +1,32 @@
-/**
- * 叶海辉
- * QQ群121376426
- * http://blog.yundiantech.com/
- */
-
-#ifndef SHOWVIDEOWIDGET_H
+#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 "VideoPlayer/Video/VideoFrame.h"
 
 namespace Ui {
 class ShowVideoWidget;
 }
 
-///显示视频用的widget
+struct FaceInfoNode
+{
+    QRect faceRect;
+};
+
+///显示视频用的widget(使用OPENGL绘制YUV420P数据)
 ///这个仅仅是显示视频画面的控件
 
-class ShowVideoWidget : public QWidget
+class ShowVideoWidget : public QOpenGLWidget,protected QOpenGLFunctions
 {
     Q_OBJECT
 
@@ -25,13 +34,84 @@ public:
     explicit ShowVideoWidget(QWidget *parent = 0);
     ~ShowVideoWidget();
 
-    void inputOneFrame(const QImage &img);
+    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 paintEvent(QPaintEvent *event);
+    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矩阵
 
-    QImage mImage; //记录当前的图像
+    QString mPlayerId;
 
 private:
     Ui::ShowVideoWidget *ui;

+ 201 - 2
src/Widget/ShowVideoWidget.ui

@@ -6,13 +6,212 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>300</height>
+    <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/>

+ 42 - 2
src/main.cpp

@@ -8,17 +8,57 @@
 #include <QApplication>
 #include <QTextCodec>
 
-#include "Widget/VideoPlayerWidget.h"
+#include <QDebug>
+
+#include "AppConfig.h"
+#include "MainWindow.h"
 
 #undef main
 int main(int argc, char *argv[])
 {
+//    bool userSoftWareOpenGL = false; //使用软件模拟openGL
+
+//    for (int i=0;i<argc;i++)
+//    {
+//        QString str = QString(argv[i]);
+
+//        if (str == "usesoftopengl")
+//        {
+//            userSoftWareOpenGL = true;
+//        }
+//        else if (str == "harddecoder")
+//        {
+//            AppConfig::gVideoHardDecoder = true;
+//        }
+//        qDebug()<<__FUNCTION__<<argv[i]<<str;
+//    }
+
+//    if (userSoftWareOpenGL)
+//    {
+//        qDebug()<<__FUNCTION__<<"\n\n !!! userSoftWareOpenGL !!! \n\n";
+//        ///没有安装显卡驱动的系统需要使用软件模拟的openGL,一般是linux系统下会存在这种情况
+//        QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
+//    }
+//    else
+//    {
+//#if defined(WIN32)
+//        QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); //解决windows下使用独立显卡的时候 切换全屏 闪烁问题
+//#else
+
+//#endif
+//    }
+
     QApplication a(argc, argv);
 
     QTextCodec *codec = QTextCodec::codecForName("GBK");
     QTextCodec::setCodecForLocale(codec);
 
-    VideoPlayerWidget w;
+    AppConfig::InitAllDataPath(); //初始化一些变量和路径信息
+    AppConfig::loadConfigInfoFromFile();
+
+    AppConfig::WriteLog(QString( "\n #############\n Version = %1 \n ##############").arg(AppConfig::VERSION_NAME));
+
+    MainWindow w;
     w.show();
 
     return a.exec();