Qt/C++音視頻開發54-視頻監控控件的極致設計

一、前言

跌跌撞撞摸爬滾打一步步迭代完善到今天,這個視頻監控控件的設計,在現階段水平上個人認爲是做的最棒的(稍微自戀一下),理論上來說應該可以用5年不用推翻重寫,推翻重寫當然也是程序員愛乾的事情,這個就要考驗個人的功底,設計的好框架搭建的好,可以很多年不用變,只需要在現有框架小修小補即可,最多就是繼承基類實現一些特殊性的功能,設計的不好,可能每個月都要重寫,這種不斷的迭代也是無法避免的,畢竟需求一直在變化,當現有的框架無法滿足老闆或者用戶的需求的時候,就必須大動干戈的推翻重來了,縱觀Qt的發展史,基本上也是這樣的,Qt4時代一個大版本,Qt5時代又是一個大版本,到了Qt6又是一個大版本,互相不兼容,而且很多模塊的結構都變了,甚至類名都改了,可能是爲了表達的更貼切。不過近期Qt的版本彪的着實厲害,這點需要批評一下。

極致設計功能點:

  • 支持多種解碼內核,比如ffmpeg/vlc/mpv/qtav/qmedia/各種廠家sdk等。
  • 參數靈活多變,兩大類結構體,一種窗體相關參數WidgetPara,一種視頻處理相關參數VideoPara。
  • 同時支持句柄(vlc/mpv/廠家SDK都提供了句柄方式)、繪製(回調拿到視頻原始數據通過painter繪製)、GPU(採集到視頻數據用opengl繪製yuv/nv12/rgb)三種視頻顯示模式。
  • 具備常規基礎接口,開始播放、暫停播放、繼續播放、停止播放、音量大小、靜音切換、抓拍圖片、開始錄像、暫停錄像、停止錄像。
  • 具備常規信號接口,文件時長、播放進度、音量變化、靜音狀態、抓圖完成、收到圖片、播放開始、播放結束等信號。
  • 支持音視頻文件(mp3、wav、mp4、asf、rm、rmvb、mkv等)、視頻流(rtsp、rtmp、http、rtp等)、本地攝像機、桌面等。
  • 共享解碼線程,打開同地址直接複用解碼,不用重複解碼,極大降低CPU佔用,多路完全實時同步。
  • 使用極其簡單,定義結構體參數後直接open打開即可,幾行代碼就能正常使用。

二、效果圖

三、體驗地址

  1. 國內站點:https://gitee.com/feiyangqingyun
  2. 國際站點:https://github.com/feiyangqingyun
  3. 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 體驗地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_push。

四、功能特點

1. 基礎功能

  1. 支持各種音頻視頻文件格式,比如mp3、wav、mp4、asf、rm、rmvb、mkv等。
  2. 支持本地攝像頭設備,可指定分辨率、幀率。
  3. 支持各種視頻流格式,比如rtp、rtsp、rtmp、http等。
  4. 本地音視頻文件和網絡音視頻文件,自動識別文件長度、播放進度、音量大小、靜音狀態等。
  5. 文件可以指定播放位置、調節音量大小、設置靜音狀態等。
  6. 支持倍速播放文件,可選0.5倍、1.0倍、2.5倍、5.0倍等速度,相當於慢放和快放。
  7. 支持開始播放、停止播放、暫停播放、繼續播放。
  8. 支持抓拍截圖,可指定文件路徑,可選抓拍完成是否自動顯示預覽。
  9. 支持錄像存儲,手動開始錄像、停止錄像,部分內核支持暫停錄像後繼續錄像,跳過不需要錄像的部分。
  10. 支持無感知切換循環播放、自動重連等機制。
  11. 提供播放成功、播放完成、收到解碼圖片、收到抓拍圖片、視頻尺寸變化、錄像狀態變化等信號。
  12. 多線程處理,一個解碼一個線程,不卡主界面。

2. 特色功能

  1. 同時支持多種解碼內核,包括qmedia內核(Qt4/Qt5/Qt6)、ffmpeg內核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5)、vlc內核(vlc2/vlc3)、mpv內核(mpv1/mp2)、海康sdk、easyplayer內核等。
  2. 非常完善的多重基類設計,新增一種解碼內核只需要實現極少的代碼量,就可以應用整套機制。
  3. 同時支持多種畫面顯示策略,自動調整(原始分辨率小於顯示控件尺寸則按照原始分辨率大小顯示,否則等比縮放)、等比縮放(永遠等比縮放)、拉伸填充(永遠拉伸填充)。所有內核和所有視頻顯示模式下都支持三種畫面顯示策略。
  4. 同時支持多種視頻顯示模式,句柄模式(傳入控件句柄交給對方繪製控制)、繪製模式(回調拿到數據後轉成QImage用QPainter繪製)、GPU模式(回調拿到數據後轉成yuv用QOpenglWidget繪製)。
  5. 支持多種硬件加速類型,ffmpeg可選dxva2、d3d11va等,mpv可選auto、dxva2、d3d11va,vlc可選any、dxva2、d3d11va。不同的系統環境有不同的類型選擇,比如linux系統有vaapi、vdpau,macos系統有videotoolbox。
  6. 解碼線程和顯示窗體分離,可指定任意解碼內核掛載到任意顯示窗體,動態切換。
  7. 支持共享解碼線程,默認開啓並且自動處理,當識別到相同的視頻地址,共享一個解碼線程,在網絡視頻環境中可以大大節約網絡流量以及對方設備的推流壓力。國內頂尖視頻廠商均採用此策略。這樣只要拉一路視頻流就可以共享到幾十個幾百個通道展示。
  8. 自動識別視頻旋轉角度並繪製,比如手機上拍攝的視頻一般是旋轉了90度的,播放的時候要自動旋轉處理,不然默認是倒着的。
  9. 自動識別視頻流播放過程中分辨率的變化,在視頻控件上自動調整尺寸。比如攝像機可以在使用過程中動態配置分辨率,當分辨率改動後對應視頻控件也要做出同步反應。
  10. 音視頻文件無感知自動切換循環播放,不會出現切換期間黑屏等肉眼可見的切換痕跡。
  11. 視頻控件同時支持任意解碼內核、任意畫面顯示策略、任意視頻顯示模式。
  12. 視頻控件懸浮條同時支持句柄、繪製、GPU三種模式,非絕對座標移來移去。
  13. 本地攝像頭設備支持指定設備名稱、分辨率、幀率進行播放。
  14. 錄像文件同時支持打開的視頻文件、本地攝像頭、網絡視頻流等。
  15. 瞬間響應打開和關閉,無論是打開不存在的視頻或者網絡流,探測設備是否存在,讀取中的超時等待,收到關閉指令立即中斷之前的操作並響應。
  16. 支持打開各種圖片文件,支持本地音視頻文件拖曳播放。
  17. 視頻控件懸浮條自帶開始和停止錄像切換、聲音靜音切換、抓拍截圖、關閉視頻等功能。
  18. 音頻組件支持聲音波形值數據解析,可以根據該值繪製波形曲線和柱狀聲音條,默認提供了聲音振幅信號。
  19. 標籤和圖形信息支持三種繪製方式,繪製到遮罩層、繪製到圖片、源頭繪製(對應信息可以存儲到文件)。
  20. 各組件中極其詳細的打印信息提示,尤其是報錯信息提示,封裝的統一打印格式。針對現場複雜的設備環境測試極其方便有用,相當於精確定位到具體哪個通道哪個步驟出錯。
  21. 代碼框架和結構優化到最優,性能強悍,持續迭代更新升級。
  22. 源碼支持Qt4、Qt5、Qt6,兼容所有版本。

3. 視頻控件

  1. 可動態添加任意多個osd標籤信息,標籤信息包括名字、是否可見、字號大小、文本文字、文本顏色、標籤圖片、標籤座標、標籤格式(文本、日期、時間、日期時間、圖片)、標籤位置(左上角、左下角、右上角、右下角、居中、自定義座標)。
  2. 可動態添加任意多個圖形信息,這個非常有用,比如人工智能算法解析後的圖形區域信息直接發給視頻控件即可。圖形信息支持任意形狀,直接繪製在原始圖片上,採用絕對座標。
  3. 圖形信息包括名字、邊框大小、邊框顏色、背景顏色、矩形區域、路徑集合、點座標集合等。
  4. 每個圖形信息都可指定三種區域中的一種或者多種,指定了的都會繪製。
  5. 內置懸浮條控件,懸浮條位置支持頂部、底部、左側、右側。
  6. 懸浮條控件參數包括邊距、間距、背景透明度、背景顏色、文本顏色、按下顏色、位置、按鈕圖標代碼集合、按鈕名稱標識集合、按鈕提示信息集合。
  7. 懸浮條控件一排工具按鈕可自定義,通過結構體參數設置,圖標可選圖形字體還是自定義圖片。
  8. 懸浮條按鈕內部實現了錄像切換、抓拍截圖、靜音切換、關閉視頻等功能,也可以自行在源碼中增加自己對應的功能。
  9. 懸浮條按鈕對應實現了功能的按鈕,有對應圖標切換處理,比如錄像按鈕按下後會切換到正在錄像中的圖標,聲音按鈕切換後變成靜音圖標,再次切換還原。
  10. 懸浮條按鈕單擊後都用名稱唯一標識作爲信號發出,可以自行關聯響應處理。
  11. 懸浮條空白區域可以顯示提示信息,默認顯示當前視頻分辨率大小,可以增加幀率、碼流大小等信息。
  12. 視頻控件參數包括邊框大小、邊框顏色、焦點顏色、背景顏色(默認透明)、文字顏色(默認全局文字顏色)、填充顏色(視頻外的空白處填充黑色)、背景文字、背景圖片(如果設置了圖片優先取圖片)、是否拷貝圖片、縮放顯示模式(自動調整、等比縮放、拉伸填充)、視頻顯示模式(句柄、繪製、GPU)、啓用懸浮條、懸浮條尺寸(橫向爲高度、縱向爲寬度)、懸浮條位置(頂部、底部、左側、右側)。

五、相關代碼

1. 將內核相關文件 core_audio、core_video、core_videobase、core_videoffmpeg 拷貝放到對應目錄。

2. 在你的項目的pro文件引入上面的組件並增加對應內核定義。../表示上級目錄。
DEFINES += ffmpeg videoffmpeg ffmpeg4
include($$PWD/../core_audio/core_audio.pri)
include($$PWD/../core_video/core_video.pri)
include($$PWD/../core_videobase/core_videobase.pri)
include($$PWD/../core_videoffmpeg/core_videoffmpeg.pri)

3. 使用代碼
#include "mainwindow.h"
#include "videowidgetx.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    VideoWidget w;
    w.resize(800, 600);
    VideoPara para = w.getVideoPara();
    para.videoCore = VideoCore_FFmpeg;
    w.setVideoPara(para);
    w.show();
    w.open("f:/mp4/push/1.mp4");

    return a.exec();
}

public slots:
    //開始播放
    virtual void play();
    //停止播放
    virtual void stop();

    //暫停播放
    virtual void pause();
    //繼續播放
    virtual void next();

    //抓拍截圖
    virtual void snap(const QString &snapName = QString());
    //截圖完成
    virtual void snapFinsh(const QImage &image);

    //開始錄製
    virtual void recordStart(const QString &fileName);
    //暫停錄製
    virtual void recordPause();
    //停止錄製
    virtual void recordStop();

    //寫入視頻數據到文件
    void writeVideoData(int width, int height, quint8 *dataY, quint8 *dataU, quint8 *dataV);
    //寫入音頻數據到文件
    void writeAudioData(const char *data, qint64 len);
    void writeAudioData(const QByteArray &data);

    //設置標籤信息集合
    virtual void setOsdInfo(const QList<OsdInfo> &listOsd);
    //設置圖形信息集合
    virtual void setGraphInfo(const QList<GraphInfo> &listGraph);

signals:
    //播放成功
    void receivePlayStart(int time);
    //播放結束
    void receivePlayFinsh();
    //播放失敗
    void receivePlayError(int error);

    //收到一張圖片
    void receiveImage(const QImage &image, int time);
    //抓拍一張圖片
    void snapImage(const QImage &image, const QString &snapName);

    //視頻尺寸變化
    void receiveSizeChanged();
    //錄製狀態變化
    void recorderStateChanged(const RecorderState &state, const QString &file);

    //收到一幀rgb視頻數據
    void receiveFrame(int width, int height, quint8 *dataRGB, int type);
    //收到一幀yuv視頻數據
    void receiveFrame(int width, int height, quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV);
    //收到一幀nv12視頻數據
    void receiveFrame(int width, int height, quint8 *dataY, quint8 *dataUV, quint32 linesizeY, quint32 linesizeUV);

    //音量大小
    void receiveVolume(int volume);
    //靜音狀態
    void receiveMuted(bool muted);
    //音頻數據振幅
    void receiveLevel(qreal leftLevel, qreal rightLevel);

    //視頻實時碼率
    void receiveKbps(qreal kbps, int frameRate);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章