一、前言說明
ffmpeg的功能真的是包羅萬象,除了基本的編解碼,還有個專門的avdevice模塊用來對本地設備的采集支持,最開始用到ffmpeg采集本地攝像頭的緣由,還不是因為Qt不給力,Qt5開始有個qcamera類,但是只能在windows或者部分linux系統才有用,而且功能非常有限,尤其是在嵌入式linux上使用非常雞肋,幾乎是無用狀態,后面也嘗試用v4l2這個萬能方案采集,這個是通用的linux多媒體框架,只能在linux上使用,所有后面慢慢發展成了win上使用qcamera采集,linux使用v4l2采集的方式,依然不夠滿足,后面自從用了ffmpeg做音視頻的解碼后,發現他居然也支持本地設備采集,所以用ffmpeg實現了對應的功能,顯示效果比v4l2好很多,畢竟ffmpeg這邊采集后解碼出來的yuv數據可以直接用qopenglwidget繪制,而v4l2采集后轉成了qimage然后用painter繪制,性能上大打折扣,何不直接用ffmpeg方案做跨平臺的采集呢。
實現完采集本地USB攝像頭,后面又有不少用戶希望能夠采集本地桌面,這些好了,只能用ffmpeg做,而且也是最方便的,只需要在之前采集本地攝像頭的框架代碼基礎上,改動幾行代碼即可,非常的方便快速,不得不說ffmpeg底層架構的非常棒。采集桌面還可以指定采集區域以及不同的屏幕,不指定的話默認采集整個屏幕,有時候是雙屏多屏幕的,所以圍繞這塊,專門做了很多參數設置,可以指定哪個屏幕采集,指定屏幕對應的區域范圍,指定采集幀率,林林總總都搞定以后,又來個新的需求,能不能采集指定應用程序窗口呢?答案也是可以的,所以后面又做了個先枚舉本地所有應用程序窗口的函數,讓用戶下拉框選擇,然后指定對應窗口的標題進行采集,無論程序拖動到哪個位置,還能正常采集,目前還有個缺陷,那就是窗體大小發生變化后,不能正常識別新的大小,不知道各位大佬有沒有好的思路方案。
二、效果圖



三、相關地址
- 國內站點:https://gitee.com/feiyangqingyun
- 國際站點:https://github.com/feiyangqingyun
- 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_demo。
四、功能特點
- 同時支持多種解碼內核,包括qmedia內核(Qt4/Qt5/Qt6)、ffmpeg內核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6)、vlc內核(vlc2/vlc3)、mpv內核(mpv1/mp2)、mdk內核、海康sdk、easyplayer內核等。
- 非常完善的多重基類設計,新增一種解碼內核只需要實現極少的代碼量,就可以應用整套機制,極易拓展。
- 同時支持多種畫面顯示策略,自動調整(原始分辨率小于顯示控件尺寸則按照原始分辨率大小顯示,否則等比縮放)、等比縮放(永遠等比縮放)、拉伸填充(永遠拉伸填充)。所有內核和所有視頻顯示模式下都支持三種畫面顯示策略。
- 同時支持多種視頻顯示模式,句柄模式(傳入控件句柄交給對方繪制控制)、繪制模式(回調拿到數據后轉成QImage用QPainter繪制)、GPU模式(回調拿到數據后轉成yuv用QOpenglWidget繪制)。
- 支持多種硬件加速類型,ffmpeg可選dxva2、d3d11va等,vlc可選any、dxva2、d3d11va,mpv可選auto、dxva2、d3d11va,mdk可選dxva2、d3d11va、cuda、mft等。不同的系統環境有不同的類型選擇,比如linux系統有vaapi、vdpau,macos系統有videotoolbox。
- 解碼線程和顯示窗體分離,可指定任意解碼內核掛載到任意顯示窗體,動態切換。
- 支持共享解碼線程,默認開啟并且自動處理,當識別到相同的視頻地址,共享一個解碼線程,在網絡視頻環境中可以大大節約網絡流量以及對方設備的推流壓力。國內頂尖視頻廠商均采用此策略。這樣只要拉一路視頻流就可以共享到幾十個幾百個通道展示。
- 自動識別視頻旋轉角度并繪制,比如手機上拍攝的視頻一般是旋轉了90度的,播放的時候要自動旋轉處理,不然默認是倒著的。
- 自動識別視頻流播放過程中分辨率的變化,在視頻控件上自動調整尺寸。比如攝像機可以在使用過程中動態配置分辨率,當分辨率改動后對應視頻控件也要做出同步反應。
- 音視頻文件無感知自動切換循環播放,不會出現切換期間黑屏等肉眼可見的切換痕跡。
- 視頻控件同時支持任意解碼內核、任意畫面顯示策略、任意視頻顯示模式。
- 視頻控件懸浮條同時支持句柄、繪制、GPU三種模式,非絕對坐標移來移去。
- 本地攝像頭設備支持指定設備名稱、分辨率、幀率進行播放。
- 本地桌面采集支持設定采集區域、偏移值、指定桌面索引、幀率、多個桌面同時采集等。還支持指定窗口標題采集固定窗口。
- 錄像文件同時支持打開的視頻文件、本地攝像頭、本地桌面、網絡視頻流等。
- 瞬間響應打開和關閉,無論是打開不存在的視頻或者網絡流,探測設備是否存在,讀取中的超時等待,收到關閉指令立即中斷之前的操作并響應。
- 支持打開各種圖片文件,支持本地音視頻文件拖曳播放。
- 視頻流通信方式可選tcp/udp,有些設備可能只提供了某一種協議通信比如tcp,需要指定該種協議方式打開。
- 可設置連接超時時間(視頻流探測用的超時時間)、讀取超時時間(采集過程中的超時時間)。
- 支持逐幀播放,提供上一幀/下一幀函數接口,可以逐幀查閱采集到的圖像。
- 音頻文件自動提取專輯信息比如標題、藝術家、專輯、專輯封面,自動顯示專輯封面。
- 視頻響應極低延遲0.2s左右,極速響應打開視頻流0.5s左右,專門做了優化處理。
- 支持H264/H265編碼(現在越來越多的監控攝像頭是H265視頻流格式)生成視頻文件,內部自動識別切換編碼格式。
- 支持用戶信息中包含特殊字符(比如用戶信息中包含+#@等字符)的視頻流播放,內置解析轉義處理。
- 支持濾鏡,各種水印及圖形效果,支持多個水印和圖像,可以將OSD標簽信息和各種圖形信息寫入到MP4文件。
- 支持視頻流中的各種音頻格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推薦選擇AAC兼容性跨平臺性最好。
- 內核ffmpeg采用純qt+ffmpeg解碼,非sdl等第三方繪制播放依賴,gpu繪制采用qopenglwidget,音頻播放采用qaudiooutput。
- 內核ffmpeg和內核mdk支持安卓,其中mdk支持安卓硬解碼,性能非常兇殘。
- 可以切換音視頻軌道,也就是節目通道,可能ts文件帶了多個音視頻節目流,可以分別設置要播放哪一個,可以播放前設置好和播放過程中動態設置。
- 可以設置視頻旋轉角度,可以播放前設置好和播放過程中動態改變。
- 視頻控件懸浮條自帶開始和停止錄像切換、聲音靜音切換、抓拍截圖、關閉視頻等功能。
- 音頻組件支持聲音波形值數據解析,可以根據該值繪制波形曲線和柱狀聲音條,默認提供了聲音振幅信號。
- 標簽和圖形信息支持三種繪制方式,繪制到遮罩層、繪制到圖片、源頭繪制(對應信息可以存儲到文件)。
- 通過傳入一個url地址,該地址可以帶上通信協議、分辨率、幀率等信息,無需其他設置。
- 保存視頻到文件支持三種策略,自動處理、僅限文件、全部轉碼,轉碼策略支持自動識別、轉264、轉265,編碼保存支持指定分辨率縮放或者等比例縮放。比如對保存文件體積有要求可以指定縮放后再存儲。
- 支持加密保存文件和解密播放文件,可以指定秘鑰文本。
- 提供的監控布局類支持64通道同時顯示,還支持各種異型布局,比如13通道,手機上6行2列布局。各種布局可以自由定義。
- 支持電子放大,在懸浮條切換到電子放大模式,在畫面上選擇需要放大的區域,選取完畢后自動放大,再次切換放大模式可以復位。
- 各組件中極其詳細的打印信息提示,尤其是報錯信息提示,封裝的統一打印格式。針對現場復雜的設備環境測試極其方便有用,相當于精確定位到具體哪個通道哪個步驟出錯。
- 同時提供了簡單示例、視頻播放器、多畫面視頻監控、監控回放、逐幀播放、多屏渲染等單獨窗體示例,專門演示對應功能如何使用。
- 監控回放可選不同廠家類型、回放時間段、用戶信息、指定通道。支持切換回放進度。
- 可以從聲卡設備下拉框選擇聲卡播放聲音,提供對應的切換聲卡函數接口。
- 支持編譯到手機app使用,提供了專門的手機app布局界面,可以作為手機上的視頻監控使用。
- 代碼框架和結構優化到最優,性能強悍,注釋詳細,持續迭代更新升級。
- 源碼支持windows、linux、mac、android等,支持各種國產linux系統,包括但不限于統信UOS/中標麒麟/銀河麒麟等。還支持嵌入式linux。
- 源碼支持Qt4、Qt5、Qt6,兼容所有版本。
五、相關代碼
#include "deviceinfohelper.h"
#include "applicationwindow.h"
#include "qapplication.h"
#include "qprocess.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#endif#ifdef multimedia
#include <QtMultimedia>
#endif
#ifdef ffmpegdevice
#include "ffmpegdevice.h"
#endifQList<QRect> DeviceInfoHelper::getScreenRects()
{QList<QRect> rects;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))int screenCount = qApp->screens().count();QList<QScreen *> screens = qApp->screens();for (int i = 0; i < screenCount; ++i) {QScreen *screen = screens.at(i);//需要根據縮放比來重新調整寬高qreal ratio = screen->devicePixelRatio();QRect rect = screen->geometry();rect.setWidth(rect.width() * ratio);rect.setHeight(rect.height() * ratio);rects << rect;}
#elseint screenCount = qApp->desktop()->screenCount();QDesktopWidget *desk = qApp->desktop();for (int i = 0; i < screenCount; ++i) {QRect rect = desk->screenGeometry(i);rects << rect;}
#endifreturn rects;
}QRect DeviceInfoHelper::getScreenRect(int screenIndex)
{//指定了屏幕索引則取指定的(沒有指定則取當前鼠標所在屏幕)QRect rect;QList<QRect> rects = DeviceInfoHelper::getScreenRects();if (screenIndex >= 0 && screenIndex < rects.count()) {rect = rects.at(screenIndex);} else {//當前屏幕區域包含當前鼠標所在坐標則說明是當前屏幕QPoint pos = QCursor::pos();foreach (QRect r, rects) {if (r.contains(pos)) {rect = r;break;}}}return rect;
}QStringList DeviceInfoHelper::getScreenInfo()
{QStringList infos;QList<QRect> rects = DeviceInfoHelper::getScreenRects();for (int i = 0; i < rects.count(); ++i) {QRect rect = rects.at(i);infos << QString("DISPLAY%1 (%2 x %3)").arg(i).arg(rect.width()).arg(rect.height());}return infos;
}QString DeviceInfoHelper::getScreenUrl(const QString &url)
{QString device = url;int index = device.mid(7, 1).toInt();device.replace("(", "");device.replace(")", "");device.replace("x", "");QStringList list = device.split(" ");if (list.count() == 4) {
#if defined(Q_OS_WIN)QString flag = "desktop";
#elif defined(Q_OS_LINUX)QString flag = ":0.0";
#elif defined(Q_OS_MAC)QString flag = "0";
#endifdevice = QString("screen=%1|%2x%3|25|||%4").arg(flag).arg(list.at(1)).arg(list.at(3)).arg(index);}return device;
}void DeviceInfoHelper::checkSize(int &width, int &height)
{//ffmpeg默認只支持偶數/虛擬機中很可能是奇數的分辨率if (width % 2 != 0) {width--;}if (height % 2 != 0) {height--;}
}QString DeviceInfoHelper::getResolution(int width, int height)
{DeviceInfoHelper::checkSize(width, height);return QString("%1x%2").arg(width).arg(height);
}QString DeviceInfoHelper::getResolution(const QString &resolution)
{QStringList sizes = DeviceInfoHelper::getSizes(resolution);return getResolution(sizes.at(0).toInt(), sizes.at(1).toInt());
}QStringList DeviceInfoHelper::getSizes(const QString &size)
{QStringList sizes;if (size.contains("*")) {sizes = size.split("*");} else if (size.contains("x")) {sizes = size.split("x");}return sizes;
}void DeviceInfoHelper::checkRect(int screenIndex, QString &resolution, int &offsetX, int &offsetY)
{QRect rect = DeviceInfoHelper::getScreenRect(screenIndex);if (resolution == "0x0") {resolution = DeviceInfoHelper::getResolution(rect.width(), rect.height());} else {resolution = DeviceInfoHelper::getResolution(resolution);}//偏移值必須小于分辨率否則重置if (offsetX > rect.width()) {offsetX = 0;}if (offsetY > rect.height()) {offsetY = 0;}//判斷設定的偏移值加上設定的分辨率是否超出了真實的分辨率QStringList sizes = DeviceInfoHelper::getSizes(resolution);int width = sizes.at(0).toInt();int height = sizes.at(1).toInt();if (offsetX + width > rect.width()) {width = rect.width() - offsetX;}if (offsetY + height > rect.height()) {height = rect.height() - offsetY;}//如果超出了分辨率則重新設置采集的分辨率resolution = DeviceInfoHelper::getResolution(width, height);//多個屏幕需要加上屏幕起始坐標if (offsetX == 0) {offsetX = rect.x();} else {offsetX += rect.x();}if (offsetY == 0) {offsetY = rect.y();} else {offsetY += rect.y();}//qDebug() << TIMEMS << screenIndex << offsetX << offsetY << resolution;
}void DeviceInfoHelper::checkOption(const QString &option, QString &format, QString &resolution, int &minFps, int &maxFps)
{//vcodec=mjpeg min s=1920x1080 fps=25 max s=1920x1080 fps=30QStringList list = option.split(" ");if (list.count() == 7) {format = list.at(0).split("=").last();resolution = list.at(5).split("=").last();//這里為什么轉float/因為有些時候是60.001這種幀率/轉int會失敗minFps = list.at(3).split("=").last().toFloat();maxFps = list.at(6).split("=").last().toFloat();}
}QStringList DeviceInfoHelper::getOptions(const QString &audioDevice, const QString &videoDevice)
{if (videoDevice == "none" && audioDevice == "none") {return QStringList();}//優先獲取視頻設備的參數QString url;if (videoDevice != "none") {url = "video=" + videoDevice;} else {url = "audio=" + audioDevice;}DeviceInfoHelper::deviceOption.clear();DeviceInfoHelper::getDeviceOption(url);return DeviceInfoHelper::deviceOption;
}QString DeviceInfoHelper::getOptionUrl(const QString &audioDevice, const QString &videoDevice, const QString &titleDevice, const QString &option)
{QString url = DeviceInfoHelper::getDeviceUrl(audioDevice, videoDevice, titleDevice);if (!url.isEmpty() && !option.isEmpty() && !url.startsWith("audio=")) {QString format, resolution;int minFps, maxFps;DeviceInfoHelper::checkOption(option, format, resolution, minFps, maxFps);url = QString("%1|%2|%3|%4").arg(url).arg(resolution).arg(maxFps).arg(format);}return url;
}QStringList DeviceInfoHelper::deviceOption = QStringList();
void DeviceInfoHelper::getDeviceOption(const QString &url)
{//為空則重新獲取if (DeviceInfoHelper::deviceOption.isEmpty()) {
#ifdef ffmpegdeviceDeviceInfoHelper::deviceOption = FFmpegDevice::getDeviceOption(url);
#endif}//linux上還需要用命令行獲取對應的參數if (DeviceInfoHelper::deviceOption.isEmpty()) {DeviceInfoHelper::deviceOption = DeviceInfoHelper::getDeviceOption2(url);}
}//安裝工具命令 sudo apt-get install v4l-utils
//獲取所有設備 v4l2-ctl --list-devices
//獲取所有格式 v4l2-ctl --list-formats -d /dev/video0
//獲取所有參數 v4l2-ctl --list-formats-ext -d /dev/video0
QStringList DeviceInfoHelper::getDeviceOption2(const QString &url)
{QStringList options;
#ifdef Q_OS_LINUX
#ifndef Q_OS_WASMQProcess process;QString devName = url;devName.replace("video=", "");process.start("v4l2-ctl", QStringList() << "--list-formats-ext" << "-d" << devName);process.waitForFinished();QString result = process.readAll();QStringList list = result.split("\t");static QString pixel_format;static QString video_size;static int min_fps = 200;static int max_fps = 0;foreach (QString line, list) {line = line.trimmed();line.replace("\r", "");line.replace("\n", "");if (line.startsWith("Pixel Format") || line.contains("]:")) {QString format = "yuyv422";if (line.contains("MJPG")) {format = "mjpeg";} else if (line.contains("H264")) {format = "h264";}//不同的格式說明是新的/要if (format != pixel_format) {//存在格式說明當前是新的一個格式/需要把上一個的末尾的信息添加if (!pixel_format.isEmpty()) {options << QString("pixel_format=%1 min s=%2 fps=%3 max s=%2 fps=%4").arg(pixel_format).arg(video_size).arg(min_fps).arg(max_fps);}//復位分辨率和幀率pixel_format = format;video_size.clear();min_fps = 200;max_fps = 0;}} else if (line.startsWith("Size")) {//新的分辨率則把上一個分辨率帶幀率的信息添加QString size = line.split(" ").last();if (size != video_size) {if (!video_size.isEmpty()) {options << QString("pixel_format=%1 min s=%2 fps=%3 max s=%2 fps=%4").arg(pixel_format).arg(video_size).arg(min_fps).arg(max_fps);}//復位幀率video_size = size;min_fps = 200;max_fps = 0;}} else if (line.startsWith("Interval")) {//取出當前分辨率分組中最大最小幀率line = line.split("(").last();line = line.split(".").first();int fps = line.toInt();if (fps > max_fps) {max_fps = fps;}if (fps < min_fps) {min_fps = fps;}}}//添加末尾結束的一個if (min_fps != 200 && max_fps != 0) {options << QString("pixel_format=%1 min s=%2 fps=%3 max s=%2 fps=%4").arg(pixel_format).arg(video_size).arg(min_fps).arg(max_fps);}//搞完后需要復位pixel_format.clear();video_size.clear();min_fps = 200;max_fps = 0;
#endif
#endifreturn options;
}QStringList DeviceInfoHelper::getDeviceFormat(const QString &url)
{QStringList formats;DeviceInfoHelper::getDeviceOption(url);foreach (QString option, DeviceInfoHelper::deviceOption) {QStringList list = option.split(" ");QString format = list.at(0).split("=").at(1);if (!formats.contains(format)) {formats << format;}}return formats;
}QStringList DeviceInfoHelper::getDeviceResolution(const QString &url, const QString &format)
{QStringList resolutions;DeviceInfoHelper::getDeviceOption(url);foreach (QString option, DeviceInfoHelper::deviceOption) {QStringList list = option.split(" ");QString formatx = list.at(0).split("=").at(1);if (format == formatx) {QString resolution = list.at(2).split("=").at(1);resolutions << resolution;}}return resolutions;
}QStringList DeviceInfoHelper::getDeviceFrameRate(const QString &url, const QString &format, const QString &resolution)
{QStringList frameRates;DeviceInfoHelper::getDeviceOption(url);foreach (QString option, DeviceInfoHelper::deviceOption) {QStringList list = option.split(" ");QString formatx = list.at(0).split("=").at(1);if (format == formatx) {QString resolutionx = list.at(2).split("=").at(1);if (resolution == resolutionx) {QString minFps = list.at(3).split("=").at(1);QString maxFps = list.at(6).split("=").at(1);frameRates << minFps << maxFps;break;}}}return frameRates;
}QStringList DeviceInfoHelper::getAudioDevices()
{QStringList names;
#ifdef multimedia
#if (QT_VERSION >= QT_VERSION_CHECK(6,2,0))QList<QAudioDevice> devices = QMediaDevices::audioInputs();foreach (QAudioDevice device, devices) {names << device.description();}
#else//目前發現Qt4.8會不全/尾部字符串被暴力裁剪/Qt4.7中文亂碼/一般建議Qt5使用QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);foreach (QAudioDeviceInfo device, devices) {//部分版本會重復比如Qt5.9會輸出兩次/需要去重QString name = device.deviceName();if (!names.contains(name)) {names << name;}}
#endif
#endifreturn names;
}QStringList DeviceInfoHelper::getVideoDevices()
{QStringList names;
#ifdef multimedia
#if (QT_VERSION >= QT_VERSION_CHECK(6,2,0))QList<QCameraDevice>cameras = QMediaDevices::videoInputs();foreach (QCameraDevice camera, cameras) {names << camera.description();}
#elif (QT_VERSION >= QT_VERSION_CHECK(5,0,0))QList<QCameraInfo> cameras = QCameraInfo::availableCameras();foreach (QCameraInfo camera, cameras) {names << camera.description();}
#endif
#endifreturn names;
}QStringList DeviceInfoHelper::getVideoDevices2()
{QStringList names;
#ifdef Q_OS_LINUX
#ifndef Q_OS_WASMQProcess process;process.start("ls", QStringList() << "/dev/");process.waitForFinished();QString result = process.readAll();QStringList list = result.split("\n");foreach (QString line, list) {if (line.startsWith("video")) {names << QString("/dev/%1").arg(line);}}
#endif
#endifreturn names;
}void DeviceInfoHelper::getInputDevices(QStringList &audioDevices, QStringList &videoDevices, bool screen)
{//優先用Qt自帶的方式獲取audioDevices << DeviceInfoHelper::getAudioDevices();videoDevices << DeviceInfoHelper::getVideoDevices();//這里假設如果存在虛擬屏幕則假設虛擬聲卡if (!audioDevices.contains("virtual-audio-capturer") && videoDevices.contains("screen-capture-recorder")) {audioDevices << "virtual-audio-capturer";}//上面沒有獲取到則用ffmpeg內置函數再獲取
#ifdef ffmpegdeviceFFmpegDevice::getInputDevices(false, audioDevices);FFmpegDevice::getInputDevices(true, videoDevices);
#endif//如果為空還需要通過命令行獲取if (videoDevices.isEmpty()) {videoDevices << DeviceInfoHelper::getVideoDevices2();}//將屏幕信息加到后面if (screen) {videoDevices << DeviceInfoHelper::getScreenInfo();}
}QString DeviceInfoHelper::getDeviceUrl(const QString &audioDevice, const QString &videoDevice, const QString &titleDevice)
{QString url;if (audioDevice.isEmpty() && videoDevice.isEmpty()) {return url;}//優先添加桌面采集if (videoDevice.startsWith("DISPLAY")) {url = DeviceInfoHelper::getScreenUrl(videoDevice);return url;}//其次添加程序窗口if (!titleDevice.isEmpty() && titleDevice != "none") {url = "screen=title=" + titleDevice;return url;}QString audioName;if (!audioDevice.isEmpty() && audioDevice != "none") {audioName = "audio=" + audioDevice;}QString videoName;if (!videoDevice.isEmpty() && videoDevice != "none") {videoName = "video=" + videoDevice;}if (audioName.isEmpty() && videoName.isEmpty()) {return url;}QStringList list;if (!videoName.isEmpty()) {list << videoName;}if (!audioName.isEmpty()) {list << audioName;}url = list.join(":");return url;
}void DeviceInfoHelper::initInputDevices(QComboBox *cboxAudioDevice, QComboBox *cboxVideoDevice, QComboBox *cboxTitleDevice, bool none, bool screen)
{QStringList audioDevices, videoDevices;DeviceInfoHelper::getInputDevices(audioDevices, videoDevices, screen);if (cboxAudioDevice) {cboxAudioDevice->clear();if (none) {cboxAudioDevice->addItem("none");}cboxAudioDevice->addItems(audioDevices);}if (cboxVideoDevice) {cboxVideoDevice->clear();if (none) {cboxVideoDevice->addItem("none");}cboxVideoDevice->addItems(videoDevices);}if (cboxTitleDevice) {cboxTitleDevice->clear();if (none) {cboxTitleDevice->addItem("none");}ApplicationWindow::getApplicationWindow(cboxTitleDevice);}
}void DeviceInfoHelper::initInputOptions(QComboBox *cboxAudioDevice, QComboBox *cboxVideoDevice, QComboBox *cboxDeviceOption)
{QString audioDevice = cboxAudioDevice->currentText();QString videoDevice = cboxVideoDevice->currentText();QStringList options = DeviceInfoHelper::getOptions(audioDevice, videoDevice);cboxDeviceOption->clear();cboxDeviceOption->addItems(options);
}void DeviceInfoHelper::setPara(const QStringList &list, int count, int index, int ¶)
{if (count > index && !list.at(index).isEmpty()) {para = list.at(index).toInt();}
}void DeviceInfoHelper::setPara(const QStringList &list, int count, int index, float ¶)
{if (count > index && !list.at(index).isEmpty()) {para = list.at(index).toFloat();}
}void DeviceInfoHelper::setPara(const QStringList &list, int count, int index, QString ¶)
{if (count > index && !list.at(index).isEmpty()) {para = list.at(index);}
}void DeviceInfoHelper::getDevicePara(const QString &url, QString &resolution, int &frameRate, QString &codecName)
{int offsetX, offsetY;float encodeVideoRatio;QString encodeVideoScale;DeviceInfoHelper::getDevicePara(url, resolution, frameRate, codecName, offsetX, offsetY, encodeVideoRatio, encodeVideoScale);
}void DeviceInfoHelper::getDevicePara(const QString &url, QString &resolution, int &frameRate, QString &codecName, int &offsetX, int &offsetY, float &encodeVideoRatio, QString &encodeVideoScale)
{//無論是否帶分隔符第一個約定是設備名稱QStringList list = url.split("|");int count = list.count();//默認不指定屏幕索引int screenIndex = -1;//判斷是否是屏幕采集bool isScreen = url.startsWith("screen=");//帶分隔符說明還指定了分辨率或幀率if (count > 1) {QStringList sizes = DeviceInfoHelper::getSizes(list.at(1));if (sizes.count() == 2) {int width = sizes.at(0).toInt();int height = sizes.at(1).toInt();resolution = QString("%1x%2").arg(width).arg(height);} else {resolution = "0x0";}//第三個參數是幀率DeviceInfoHelper::setPara(list, count, 2, frameRate);//取出其他幾個參數if (isScreen) {//XY坐標偏移值DeviceInfoHelper::setPara(list, count, 3, offsetX);DeviceInfoHelper::setPara(list, count, 4, offsetY);//屏幕索引DeviceInfoHelper::setPara(list, count, 5, screenIndex);//壓縮比率DeviceInfoHelper::setPara(list, count, 6, encodeVideoRatio);//視頻縮放DeviceInfoHelper::setPara(list, count, 7, encodeVideoScale);//約定屏幕索引255則取用戶自定義的區域if (screenIndex != 255) {DeviceInfoHelper::checkRect(screenIndex, resolution, offsetX, offsetY);}} else {//指定解碼器DeviceInfoHelper::setPara(list, count, 3, codecName);//壓縮比率DeviceInfoHelper::setPara(list, count, 4, encodeVideoRatio);//視頻縮放DeviceInfoHelper::setPara(list, count, 5, encodeVideoScale);}}//沒有設置分辨率則重新處理if (resolution == "0x0") {if (!isScreen) {resolution = "640x480";} else {DeviceInfoHelper::checkRect(screenIndex, resolution, offsetX, offsetY);}}//采用第三方錄屏只能主屏幕/不能設置分辨率/而且不能指定區域/優點就是效率高而且光標不閃爍if (url.contains("video=screen-capture-recorder")) {resolution = "0x0";codecName = "";if (frameRate <= 0 || frameRate >= 30) {frameRate = 30;}}
}void DeviceInfoHelper::getNormalPara(const QString &url, QString &transport, int &decodeType, int &encodeVideo, int &encodeVideoFps, float &encodeVideoRatio, QString &encodeVideoScale)
{QStringList list = url.split("|");int count = list.count();//通信協議DeviceInfoHelper::setPara(list, count, 1, transport);//解碼策略DeviceInfoHelper::setPara(list, count, 2, decodeType);//編碼視頻格式DeviceInfoHelper::setPara(list, count, 3, encodeVideo);//編碼視頻幀率DeviceInfoHelper::setPara(list, count, 4, encodeVideoFps);//視頻壓縮比率DeviceInfoHelper::setPara(list, count, 5, encodeVideoRatio);//視頻縮放比例DeviceInfoHelper::setPara(list, count, 6, encodeVideoScale);
}