1. 程式人生 > 實用技巧 >Qt音視訊開發22-通用GPU顯示

Qt音視訊開發22-通用GPU顯示

一、前言

採用GPU來繪製實時視訊一直以來都是個難點,如果是安防行業的做視訊監控開發這塊的人員,這個坎必須邁過去,本人一直從事的是安防行業的電子圍欄這個相當小眾的細分市場的開發,視訊監控這塊僅僅是周邊技術玩一玩探討一下,關於GPU繪製這塊著實走了不少的彎路。

之前用ffmpeg解碼的時候,已經做了硬解碼的處理,比如支援qsv、dxva2、d3d11va等方式進行硬解碼處理,但是當時解碼出來以後,還是重新轉成了QImage來繪製,這樣就大打折扣了,儘管可以看到GPU使用率有了,但是依然耗時的操作還是在CPU繪製顯示,這就顯得很尷尬了,Qt封裝了大部分的opengl的操作,直接做成了QOPenGLWidget,既支援ffmpeg解碼出來的yuyv格式的資料顯示,還支援硬解碼出來的nv12格式的資料顯示,很好很強大,這樣的話就大大減輕了CPU的壓力,專門交給GPU繪製,經過這麼一番徹底的改造,效率提升至少5倍,不要太牛逼!如果開啟了opengl繪製,則對應記憶體會增加不少,可能opengl繪製需要開闢很多的記憶體來交換資料吧。

採用GPU顯示需要同時支援yuyv格式和nv12格式,因為有些配置差的電腦,硬解碼很可能歇菜,此時就需要用opengl來直接繪製ffmpeg軟解碼出來的yuyv資料,做到自動切換,這樣就相容了所有的可能的情況。測試發現ffmpeg4的效能要優於ffmpeg3,64位的效能要優於32位的,在64位的作業系統上,UDP協議效能要優於TCP效能,但是可能會丟包。

方案 CPU 記憶體 GPU
none+none 12% 147MB 0%
dxva2+none 3% 360MB 38%
d3d11va+none 2% 277MB 62%
none+painter 30% 147MB 0%
dxva2+painter 30% 360MB 38%
d3d11va+painter 21% 277MB 62%
none+yuyv 17% 177MB 22%
dxva2+yuyv 25% 400MB 38%
d3d11va+yuyv 18% 30MB 65%
qsv+nv12 22% 970MB 40%
dxva2+nv12 20% 380MB 40%
d3d11va+nv12 15% 320MB 62%

二、功能特點

  1. 支援多畫面切換,全屏切換等,包括1+4+6+8+9+13+16+25+36+64畫面切換。
  2. 支援alt+enter全屏,esc退出全屏。
  3. 自定義資訊框+錯誤框+詢問框+右下角提示框(包含多種格式)。
  4. 17套面板樣式隨意更換,所有樣式全部統一,包括選單等。
  5. 雲臺儀表盤滑鼠移上去高亮,八個方位精準識別。
  6. 底部畫面工具欄(畫面分割切換+截圖聲音等設定)移上去高亮。
  7. 可在配置檔案更改左上角logo+中文軟體名稱+英文軟體名稱。
  8. 封裝了百度地圖,檢視切換,運動軌跡,裝置點位,滑鼠按下獲取經緯度等。
  9. 支援圖片地圖,裝置按鈕可以在圖片地圖上自由拖動自動儲存位置資訊。
  10. 在百度地圖和圖片地圖上,雙擊視訊可以預覽攝像頭實時視訊。
  11. 堆疊窗體,每個窗體都是個單獨的qwidget,方便編寫自己的程式碼。
  12. 頂部滑鼠右鍵選單,可動態控制時間CPU+左上角面板+左下角面板+右上角面板+右下角面板的顯示和隱藏,支援恢復預設佈局。
  13. 工具欄可以放置多個小圖示和關閉圖示。
  14. 左側右側可拖動拉伸,並自動記憶寬高位置,重啟後恢復。
  15. 雙擊攝像機節點自動播放視訊,雙擊節點自動依次新增視訊,會自動跳到下一個,雙擊父節點自動新增該節點下的所有視訊。
  16. 攝像機節點拖曳到對應窗體播放視訊,同時支援拖曳本地檔案直接播放。
  17. 視訊畫面窗體支援拖曳交換,瞬間響應。
  18. 雙擊節點+拖曳節點+拖曳窗體交換位置,均自動更新url.txt。
  19. 支援從url.txt中載入通道視訊播放,自動記憶最後通道對應的視訊,軟體啟動後自動開啟播放。
  20. 右下角音量條控制元件,失去焦點自動隱藏,音量條帶靜音圖示。
  21. 整合百度線上地圖和離線地圖,可以新增裝置對應位置,自動生成地圖,支援縮放和新增覆蓋物等。
  22. 視訊拖動到通道窗體外自動刪除視訊。
  23. 滑鼠右鍵可刪除當前+所有視訊,截圖當前+所有視訊。
  24. 錄影機管理、攝像機管理,可新增刪除修改匯入匯出列印資訊,立即應用新的裝置資訊生成樹狀列表,不需重啟。
  25. 在pro檔案中可以自由開啟是否載入地圖。
  26. 視訊播放可選2種核心自由切換,vlc+ffmpeg,均可在pro中設定。
  27. 可設定1+4+9+16畫面輪詢,可設定輪詢間隔以及輪詢碼流型別等,直接在主介面底部工具欄右側單擊啟動輪詢按鈕即可,再次單擊停止輪詢。
  28. 預設超過10秒鐘未操作自動隱藏滑鼠指標。
  29. 支援onvif搜素裝置,支援任意onvif攝像機,包括但不限於海康大華宇視天地偉業華為等。
  30. 支援onvif雲臺控制,可上下左右移動雲臺攝像機,包括復位和焦距調整等。
  31. 同時支援sqlite、mysql、postsql等資料庫。
  32. 可儲存視訊,可選定時儲存或者單檔案儲存,可選儲存間隔時間。
  33. 可設定視訊流通訊方式tcp+udp,可設定視訊解碼是速度優先、質量優先、均衡等。
  34. 可設定硬解碼型別,支援qsv、dxva2、d3d11va等。
  35. 預設採用opengl繪製視訊,超低的CPU資源佔用,支援yuyv和nv12兩種格式繪製,很牛逼。
  36. 高度可定製化,使用者可以很方便的在此基礎上衍生自己的功能,支援linux和mac系統。

三、效果圖

四、相關站點

  1. 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
  3. 個人主頁:https://blog.csdn.net/feiyangqingyun
  4. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
  5. 體驗地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心程式碼

void YUVOpenGLWidget2::initializeGL()
{
    initializeOpenGLFunctions();
    glDisable(GL_DEPTH_TEST);

    //傳遞頂點和紋理座標
    static const GLfloat ver[] = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
    static const GLfloat tex[] = {0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f};

    //設定頂點,紋理陣列並啟用
    glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, ver);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, tex);
    glEnableVertexAttribArray(1);

    //初始化shader
    this->initShader();
    //初始化textures
    this->initTextures();
    //初始化顏色
    this->initColor();
}

void YUVOpenGLWidget2::paintGL()
{
    if (!dataY || !dataU || !dataV || width == 0 || height == 0) {
        this->initColor();
        return;
    }

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureY);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeY);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, dataY);
    glUniform1i(textureUniformY, 0);

    glActiveTexture(GL_TEXTURE0 + 1);
    glBindTexture(GL_TEXTURE_2D, textureU);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeU);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width >> 1, height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, dataU);
    glUniform1i(textureUniformU, 1);

    glActiveTexture(GL_TEXTURE0 + 2);
    glBindTexture(GL_TEXTURE_2D, textureV);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizeV);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width >> 1, height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, dataV);
    glUniform1i(textureUniformV, 2);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

void YUVOpenGLWidget2::initColor()
{
    //取畫板背景顏色
    QColor color = palette().background().color();
    //設定背景清理色
    glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
    //清理顏色背景
    glClear(GL_COLOR_BUFFER_BIT);
}

void YUVOpenGLWidget2::initShader()
{
    //載入頂點和片元指令碼
    program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertShader);
    program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragShader);

    //設定頂點位置
    program.bindAttributeLocation("vertexIn", 0);
    //設定紋理位置
    program.bindAttributeLocation("textureIn", 1);
    //編譯shader
    program.link();
    program.bind();    

    //從shader獲取地址
    textureUniformY = program.uniformLocation("textureY");
    textureUniformU = program.uniformLocation("textureU");
    textureUniformV = program.uniformLocation("textureV");
}

void YUVOpenGLWidget2::initTextures()
{
    //建立紋理
    glGenTextures(1, &textureY);
    glBindTexture(GL_TEXTURE_2D, textureY);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenTextures(1, &textureU);
    glBindTexture(GL_TEXTURE_2D, textureU);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenTextures(1, &textureV);
    glBindTexture(GL_TEXTURE_2D, textureV);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}