Qt音視頻開發16-通用懸浮按鈕工具欄的設計

qt自定義控件 發佈 2024-05-09T20:08:24.261148+00:00

通用懸浮按鈕工具欄這個功能經過了好幾個版本的疊代,一開始設計的時候是寫在視頻控制項widget窗體中,當時功能簡單就放一排按鈕在頂部懸浮widget中就好,隨著用戶需求的變化,用戶需要自定義懸浮條的要求越發強烈,而且部分用戶還希望懸浮條的位置能夠指定,比如可以在頂部、底部、左側、右側位置。

一、前言

通用懸浮按鈕工具欄這個功能經過了好幾個版本的疊代,一開始設計的時候是寫在視頻控制項widget窗體中,當時功能簡單就放一排按鈕在頂部懸浮widget中就好,隨著用戶需求的變化,用戶需要自定義懸浮條的要求越發強烈,而且部分用戶還希望懸浮條的位置能夠指定,比如可以在頂部、底部、左側、右側位置。為了滿足各種需求,特意將通用懸浮按鈕工具欄單獨成類BannerWidget,將所有懸浮條參數放到結構體BannerPara中,可以設置按鈕的間距、邊距、背景透明度、背景顏色、文本顏色、按下顏色、懸浮條位置等,每個按鈕都對應有圖標代碼、名稱標識、提示信息。這些信息都可以動態設置並立即應用,在最外層的視頻控制項窗體就提供了設置接口。

//懸浮條位置
enum BannerPosition {
    BannerPosition_Top = 0,     //頂部
    BannerPosition_Bottom = 1,  //底部
    BannerPosition_Left = 2,    //左側
    BannerPosition_Right = 3    //右側
};

//懸浮條參數
#include <Qmargins>
struct BannerPara {
    QMargins margin;            //邊距
    int spacing;                //間距
    int bgAlpha;                //背景透明度

    QColor bgColor;             //背景顏色
    QColor textColor;           //文本顏色
    QColor pressColor;          //按下顏色
    BannerPosition position;    //懸浮條位置

    QList<int> icons;           //按鈕圖標代碼集合
    QList<QString> names;       //按鈕名稱標識集合
    QList<QString> tips;        //按鈕提示信息集合

    BannerPara() {
        margin = QMargins(5, 0, 3, 0);
        spacing = 2;
        bgAlpha = 200;

        bgColor = "#333333";
        textColor = "#FFFFFF";
        pressColor = "#5EC7D9";
        position = BannerPosition_Top;

        //採用iconfont圖形字體
        icons = QList<int>() << 0xea1b << 0xeb15 << 0xe674 << 0xea36 << 0xe74c;
        //為了避免和其他控制項重名建議前面加上前綴用來區分
        names = QList<QString>() << "banner_btnRecord" << "banner_btnSnap" << "banner_btnSound" << "banner_btnAlarm" << "banner_btnClose";
        tips = QList<QString>() << "錄製" << "抓圖" << "聲音" << "警情" << "關閉";
    }
};

二、效果圖

三、體驗地址

  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_demo/bin_linux_video。

四、相關代碼

void BannerWidget::btnClicked()
{
    QPushButton *btn = (QPushButton *)sender();
    QString objName = btn->objectName();
    emit btnClicked(objName);

    //根據需要切換圖標以及標識
    if (objName.endsWith("btnRecord")) {
        btn->setText((QChar)0xea1c);
        btn->setObjectName(objName.replace("Record", "Stop"));
    } else if (objName.endsWith("btnStop")) {
        btn->setText((QChar)0xea1b);
        btn->setObjectName(objName.replace("Stop", "Record"));
    } else if (objName.endsWith("btnSound")) {
        btn->setText((QChar)0xe667);
        btn->setObjectName(objName.replace("Sound", "Muted"));
    } else if (objName.endsWith("btnMuted")) {
        btn->setText((QChar)0xe674);
        btn->setObjectName(objName.replace("Muted", "Sound"));
    }
}

void BannerWidget::receivePlayFinsh()
{
    this->changeStatus("Stop", "Record", 0xea1b);
    this->changeStatus("Muted", "Sound", 0xe674);
}

void BannerWidget::receiveMuted(bool muted)
{
    if (muted) {
        this->changeStatus("Sound", "Muted", 0xe667);
    } else {
        this->changeStatus("Muted", "Sound", 0xe674);
    }
}

void BannerWidget::recorderStateChanged(const RecorderState &state, const QString &file)
{
    if (state == RecorderState_Recording) {
        this->isRecord = true;
        this->changeStatus("Record", "Stop", 0xea1c);
    } else if (state == RecorderState_Stopped) {
        this->isRecord = false;
        this->changeStatus("Stop", "Record", 0xea1b);
    }

    this->showInfo(text);
}

void BannerWidget::setBannerPara(const BannerPara &bannerPara)
{
    this->bannerPara = bannerPara;
}

void BannerWidget::initButton()
{
    //檢查數量是否一致
    int iconCount = bannerPara.icons.size();
    int nameCount = bannerPara.names.size();
    if (iconCount == 0 || iconCount != nameCount) {
        return;
    }

    //清空之前的按鈕對象
    qDeleteAll(btns);
    btns.clear();

    //如果之前存在布局則刪除布局(居然只能用delete而不是deleteLater)
    if (this->layout()) {
        delete this->layout();
    }

    //識別當前用哪個布局
    bool vertical = (bannerPara.position == BannerPosition_Left || bannerPara.position == BannerPosition_Right);
    //實例化布局
    QBoxLayout *layout = 0;
    if (vertical) {
        layout = new QVBoxLayout;
        //插入彈簧並設置布局的邊距間距
        layout->addStretch();
    } else {
        layout = new QHBoxLayout;
        //插入標籤放置各種信息
        layout->addWidget(label);
    }

    layout->setContentsMargins(bannerPara.margin);
    layout->setSpacing(bannerPara.spacing);
    this->setLayout(layout);

    //有多種辦法來設置圖片,qt內置的圖標+自定義的圖標+圖形字體
    //既可以設置圖標形式,也可以直接圖形字體設置文本
#if 0
    QList<QIcon> icons;
    icons << QApplication::style()->standardIcon(QStyle::SP_ComputerIcon);
    icons << QApplication::style()->standardIcon(QStyle::SP_FileIcon);
    icons << QApplication::style()->standardIcon(QStyle::SP_DirIcon);
    icons << QApplication::style()->standardIcon(QStyle::SP_DialogOkButton);
    icons << QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton);
#endif

    //根據位置設置布局以及添加按鈕(如果不需要按鈕則只需要加一行 iconCount = 0)
    for (int i = 0; i < iconCount; ++i) {
        QPushButton *btn = new QPushButton;
        //綁定按鈕單擊事件,用來發出信號通知
        connect(btn, SIGNAL(clicked(bool)), this, SLOT(btnClicked()));
        //設置標識,用來區別按鈕
        btn->setObjectName(bannerPara.names.at(i));
        //設置提示文字信息
        btn->setToolTip(bannerPara.tips.at(i));

        if (vertical) {
            //設置固定高度
            btn->setFixedHeight(20);
            //設置拉伸策略使得填充
            btn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
        } else {
            //設置固定寬度
            btn->setFixedWidth(20);
            //設置拉伸策略使得填充
            btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
        }

        //設置焦點策略為無焦點,避免單擊後焦點跑到按鈕上
        btn->setFocusPolicy(Qt::NoFocus);

#if 0
        //設置圖標大小和圖標
        btn->setIconSize(QSize(16, 16));
        btn->setIcon(icons.at(i));
#else
        btn->setFont(iconFont);
        btn->setText((QChar)bannerPara.icons.at(i));
#endif

        //將按鈕加到布局中
        layout->addWidget(btn);
        btns << btn;
    }
}

void BannerWidget::initStyle()
{
    //應用樣式表
    QStringList list;
    QColor bgColor = bannerPara.bgColor;
    QString rgba = QString("rgba(%1,%2,%3,%4)").arg(bgColor.red()).arg(bgColor.green()).arg(bgColor.blue()).arg(bannerPara.bgAlpha);
    list << QString("#bannerWidget{background:%1;border:none;}").arg(rgba);
    list << QString("QLabel{margin:0px;padding:0px;}");
    list << QString("QPushButton,QLabel{color:%1;}").arg(bannerPara.textColor.name());
    list << QString("QPushButton:pressed{color:%1;}").arg(bannerPara.pressColor.name());
    list << QString("QPushButton{border:none;padding:0px;background:rgba(0,0,0,0);}");
    this->setStyleSheet(list.join(""));
}

void BannerWidget::showInfo(const QString &text)
{
    this->text = text;
    if (isRecord) {
        label->setText(text + " 錄製中...");
    } else {
        label->setText(text);
    }
}

void BannerWidget::changeStatus(const QString &objNameSrc, const QString &objNameDst, int icon)
{
    foreach (QPushButton *btn, btns) {
        QString objName = btn->objectName();
        if (objName.endsWith(objNameSrc)) {
            btn->setObjectName(objName.replace(objNameSrc, objNameDst));
            btn->setText((QChar)icon);
            break;
        }
    }
}

五、功能特點

5.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. 多線程處理,一個解碼一個線程,不卡主界面。

5.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. 源碼支持Qt4、Qt5、Qt6,兼容所有版本。

5.3 視頻控制項

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

5.4 內核ffmpeg

  1. 支持各種音視頻文件、本地攝像頭設備,各種視頻流網絡流。
  2. 支持開始播放、暫停播放、繼續播放、停止播放、設置播放進度、倍速播放。
  3. 可設置音量、靜音切換、抓拍圖片、錄像存儲。
  4. 自動提取專輯信息比如標題、藝術家、專輯、專輯封面,自動顯示專輯封面。
  5. 完美支持音視頻同步和倍速播放。
  6. 解碼策略支持速度優先、質量優先、均衡處理、最快速度。
  7. 支持手機視頻旋轉角度顯示,比如一般手機拍攝的視頻是旋轉了90度的,解碼顯示的時候需要重新旋轉90度才是正的。
  8. 自動轉換yuv420格式,比如本地攝像頭是yuyv422格式,有些視頻文件是xx格式,統一將非yuv420格式轉換,然後再進行處理。
  9. 支持硬解碼dxva2、d3d11va等,性能極高尤其是大解析度比如4K視頻。
  10. 視頻響應極低延遲0.2s左右,極速響應打開視頻流0.5s左右,專門做了優化處理。
  11. 硬解碼和GPU繪製組合,極低CPU占用,比海康大華等客戶端更優。
  12. 支持視頻流中的各種音頻格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推薦選擇AAC兼容性跨平台性最好。
  13. 視頻存儲支持yuv、h264、mp4多種格式,音頻存儲支持pcm、wav、aac多種格式。默認視頻mp4格式、音頻aac格式。
  14. 支持分開存儲音頻視頻文件,也支持合併到一個mp4文件,默認策略是無論何種音視頻文件格式存儲,最終都轉成mp4及aac格式,然後合併成音視頻一起的mp4文件。
  15. 支持本地攝像頭實時視頻顯示帶音頻輸入輸出,音視頻錄製合併到一個mp4文件。
  16. 支持H264/H265編碼(現在越來越多的監控攝像頭是H265視頻流格式)生成視頻文件,內部自動識別切換編碼格式。
  17. 自動識別視頻流動態解析度改動,重新打開視頻流。
  18. 支持用戶信息中包含特殊字符(比如用戶信息中包含+#@等字符)的視頻流播放,內置解析轉義處理。
  19. 純qt+ffmpeg解碼,非sdl等第三方繪製播放依賴,gpu繪製採用qopenglwidget,音頻播放採用qaudiooutput。
  20. 同時支持ffmpeg2、ffmpeg3、ffmpeg4、ffmpeg5版本,全部做了兼容處理。如果需要支持xp需要選用ffmpeg3及以下。
關鍵字: