純Qt結合ffmpeg實現本地攝像頭采集/桌面采集/應用程序窗口采集/指定采集幀率和分辨率等

一、前言說明

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底層架構的非常棒。采集桌面還可以指定采集區域以及不同的屏幕,不指定的話默認采集整個屏幕,有時候是雙屏多屏幕的,所以圍繞這塊,專門做了很多參數設置,可以指定哪個屏幕采集,指定屏幕對應的區域范圍,指定采集幀率,林林總總都搞定以后,又來個新的需求,能不能采集指定應用程序窗口呢?答案也是可以的,所以后面又做了個先枚舉本地所有應用程序窗口的函數,讓用戶下拉框選擇,然后指定對應窗口的標題進行采集,無論程序拖動到哪個位置,還能正常采集,目前還有個缺陷,那就是窗體大小發生變化后,不能正常識別新的大小,不知道各位大佬有沒有好的思路方案。

二、效果圖

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

三、相關地址

  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。

四、功能特點

  1. 同時支持多種解碼內核,包括qmedia內核(Qt4/Qt5/Qt6)、ffmpeg內核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6)、vlc內核(vlc2/vlc3)、mpv內核(mpv1/mp2)、mdk內核、海康sdk、easyplayer內核等。
  2. 非常完善的多重基類設計,新增一種解碼內核只需要實現極少的代碼量,就可以應用整套機制,極易拓展。
  3. 同時支持多種畫面顯示策略,自動調整(原始分辨率小于顯示控件尺寸則按照原始分辨率大小顯示,否則等比縮放)、等比縮放(永遠等比縮放)、拉伸填充(永遠拉伸填充)。所有內核和所有視頻顯示模式下都支持三種畫面顯示策略。
  4. 同時支持多種視頻顯示模式,句柄模式(傳入控件句柄交給對方繪制控制)、繪制模式(回調拿到數據后轉成QImage用QPainter繪制)、GPU模式(回調拿到數據后轉成yuv用QOpenglWidget繪制)。
  5. 支持多種硬件加速類型,ffmpeg可選dxva2、d3d11va等,vlc可選any、dxva2、d3d11va,mpv可選auto、dxva2、d3d11va,mdk可選dxva2、d3d11va、cuda、mft等。不同的系統環境有不同的類型選擇,比如linux系統有vaapi、vdpau,macos系統有videotoolbox。
  6. 解碼線程和顯示窗體分離,可指定任意解碼內核掛載到任意顯示窗體,動態切換。
  7. 支持共享解碼線程,默認開啟并且自動處理,當識別到相同的視頻地址,共享一個解碼線程,在網絡視頻環境中可以大大節約網絡流量以及對方設備的推流壓力。國內頂尖視頻廠商均采用此策略。這樣只要拉一路視頻流就可以共享到幾十個幾百個通道展示。
  8. 自動識別視頻旋轉角度并繪制,比如手機上拍攝的視頻一般是旋轉了90度的,播放的時候要自動旋轉處理,不然默認是倒著的。
  9. 自動識別視頻流播放過程中分辨率的變化,在視頻控件上自動調整尺寸。比如攝像機可以在使用過程中動態配置分辨率,當分辨率改動后對應視頻控件也要做出同步反應。
  10. 音視頻文件無感知自動切換循環播放,不會出現切換期間黑屏等肉眼可見的切換痕跡。
  11. 視頻控件同時支持任意解碼內核、任意畫面顯示策略、任意視頻顯示模式。
  12. 視頻控件懸浮條同時支持句柄、繪制、GPU三種模式,非絕對坐標移來移去。
  13. 本地攝像頭設備支持指定設備名稱、分辨率、幀率進行播放。
  14. 本地桌面采集支持設定采集區域、偏移值、指定桌面索引、幀率、多個桌面同時采集等。還支持指定窗口標題采集固定窗口。
  15. 錄像文件同時支持打開的視頻文件、本地攝像頭、本地桌面、網絡視頻流等。
  16. 瞬間響應打開和關閉,無論是打開不存在的視頻或者網絡流,探測設備是否存在,讀取中的超時等待,收到關閉指令立即中斷之前的操作并響應。
  17. 支持打開各種圖片文件,支持本地音視頻文件拖曳播放。
  18. 視頻流通信方式可選tcp/udp,有些設備可能只提供了某一種協議通信比如tcp,需要指定該種協議方式打開。
  19. 可設置連接超時時間(視頻流探測用的超時時間)、讀取超時時間(采集過程中的超時時間)。
  20. 支持逐幀播放,提供上一幀/下一幀函數接口,可以逐幀查閱采集到的圖像。
  21. 音頻文件自動提取專輯信息比如標題、藝術家、專輯、專輯封面,自動顯示專輯封面。
  22. 視頻響應極低延遲0.2s左右,極速響應打開視頻流0.5s左右,專門做了優化處理。
  23. 支持H264/H265編碼(現在越來越多的監控攝像頭是H265視頻流格式)生成視頻文件,內部自動識別切換編碼格式。
  24. 支持用戶信息中包含特殊字符(比如用戶信息中包含+#@等字符)的視頻流播放,內置解析轉義處理。
  25. 支持濾鏡,各種水印及圖形效果,支持多個水印和圖像,可以將OSD標簽信息和各種圖形信息寫入到MP4文件。
  26. 支持視頻流中的各種音頻格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推薦選擇AAC兼容性跨平臺性最好。
  27. 內核ffmpeg采用純qt+ffmpeg解碼,非sdl等第三方繪制播放依賴,gpu繪制采用qopenglwidget,音頻播放采用qaudiooutput。
  28. 內核ffmpeg和內核mdk支持安卓,其中mdk支持安卓硬解碼,性能非常兇殘。
  29. 可以切換音視頻軌道,也就是節目通道,可能ts文件帶了多個音視頻節目流,可以分別設置要播放哪一個,可以播放前設置好和播放過程中動態設置。
  30. 可以設置視頻旋轉角度,可以播放前設置好和播放過程中動態改變。
  31. 視頻控件懸浮條自帶開始和停止錄像切換、聲音靜音切換、抓拍截圖、關閉視頻等功能。
  32. 音頻組件支持聲音波形值數據解析,可以根據該值繪制波形曲線和柱狀聲音條,默認提供了聲音振幅信號。
  33. 標簽和圖形信息支持三種繪制方式,繪制到遮罩層、繪制到圖片、源頭繪制(對應信息可以存儲到文件)。
  34. 通過傳入一個url地址,該地址可以帶上通信協議、分辨率、幀率等信息,無需其他設置。
  35. 保存視頻到文件支持三種策略,自動處理、僅限文件、全部轉碼,轉碼策略支持自動識別、轉264、轉265,編碼保存支持指定分辨率縮放或者等比例縮放。比如對保存文件體積有要求可以指定縮放后再存儲。
  36. 支持加密保存文件和解密播放文件,可以指定秘鑰文本。
  37. 提供的監控布局類支持64通道同時顯示,還支持各種異型布局,比如13通道,手機上6行2列布局。各種布局可以自由定義。
  38. 支持電子放大,在懸浮條切換到電子放大模式,在畫面上選擇需要放大的區域,選取完畢后自動放大,再次切換放大模式可以復位。
  39. 各組件中極其詳細的打印信息提示,尤其是報錯信息提示,封裝的統一打印格式。針對現場復雜的設備環境測試極其方便有用,相當于精確定位到具體哪個通道哪個步驟出錯。
  40. 同時提供了簡單示例、視頻播放器、多畫面視頻監控、監控回放、逐幀播放、多屏渲染等單獨窗體示例,專門演示對應功能如何使用。
  41. 監控回放可選不同廠家類型、回放時間段、用戶信息、指定通道。支持切換回放進度。
  42. 可以從聲卡設備下拉框選擇聲卡播放聲音,提供對應的切換聲卡函數接口。
  43. 支持編譯到手機app使用,提供了專門的手機app布局界面,可以作為手機上的視頻監控使用。
  44. 代碼框架和結構優化到最優,性能強悍,注釋詳細,持續迭代更新升級。
  45. 源碼支持windows、linux、mac、android等,支持各種國產linux系統,包括但不限于統信UOS/中標麒麟/銀河麒麟等。還支持嵌入式linux。
  46. 源碼支持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 &para)
{if (count > index && !list.at(index).isEmpty()) {para = list.at(index).toInt();}
}void DeviceInfoHelper::setPara(const QStringList &list, int count, int index, float &para)
{if (count > index && !list.at(index).isEmpty()) {para = list.at(index).toFloat();}
}void DeviceInfoHelper::setPara(const QStringList &list, int count, int index, QString &para)
{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);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919355.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919355.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919355.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【論文筆記】Multi-Agent Based Character Simulation for Story Writing

論文信息 論文標題&#xff1a; Multi-Agent Based Character Simulation for Story Writing - In2Writing 2025 論文作者&#xff1a; Tian Yu, Ken Shi, Zixin Zhao, Gerald Penn 論文鏈接&#xff1a; https://aclanthology.org/2025.in2writing-1.9/ 論文領域&#xff1a;…

GaussianLSS

背景 BEV感知能夠聚合多個傳感器輸入得到統一的空間表征&#xff0c;在3D感知、下游應用以及跨模態融合中發揮重要作用。現有的BEV感知分為2D反投影&#xff08;LSS&#xff09;以及3D投影&#xff08;BEVFormer&#xff09;&#xff0c;其中3D投影依賴于3D體素的投影&#xff…

衛生許可證識別技術:通過OCR與NLP實現高效合規管理,提升審核準確性與效率

在食品安全、公共衛生管理日益重要的今天&#xff0c;衛生許可證成為企業合規經營的關鍵憑證。傳統人工審核方式效率低、易出錯&#xff0c;而衛生許可證識別技術應運而生&#xff0c;正逐步革新監管與合規流程。下面深入解析這項技術的核心要素。核心技術原理衛生許可證識別本…

【habitat學習一】Habitat-Lab 配置鍵文檔詳解(CONFIG_KEYS.md)

該文件是 Habitat-Lab&#xff08;Facebook Research 開發的 Embodied AI 模擬框架&#xff09;的核心配置文檔&#xff0c;詳細定義了 Habitat 配置系統&#xff08;基于 OmegaConf&#xff09;中所有可配置參數的層級結構、默認值、取值范圍及功能說明。以下內容嚴格按文件結…

AI-調查研究-55-機器人 百年進化史:從Unimate到人形智能體的技術躍遷

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持續更新中&#xff01;&#xff08;長期更新&#xff09; AI煉丹日志-31- 千呼萬喚始出來 GPT-5 發布&#xff01;“快的…

【python實用小腳本-190】Python一鍵刪除PDF任意頁:輸入頁碼秒出干凈文件——再也不用在線裁剪排隊

Python一鍵刪除PDF任意頁&#xff1a;輸入頁碼秒出干凈文件——再也不用在線裁剪排隊 PDF刪頁, 零依賴腳本, 命令行秒用, 小白神器, 瑞士軍刀 故事開場&#xff1a;一把瑞士軍刀救了你 周一早高峰&#xff0c;老板發來 200 頁 PDF&#xff1a; “把第 18、25、37 頁刪掉&#x…

RH134 運行容器知識點

1. 相比較虛擬機&#xff0c;容器有哪些技術優勢&#xff1f;答&#xff1a;輕量級&#xff1a;容器共享宿主操作系統的內核&#xff0c;比虛擬機更輕量級&#xff0c;啟動速度更快&#xff0c;資源占用更少。高效性&#xff1a;容器可以快速部署和擴展&#xff0c;適合于微服務…

亞矩陣云手機智能定位:助力Snapchat矩陣賬號的本地化內容運營穿透技術

——構建跨地理圍欄的智能社交營銷網絡**??一、技術架構&#xff1a;動態定位引擎與多維度穿透體系??動態定位引擎??多源信號融合定位?&#xff1a;集成GPS/基站/WiFi三角定位技術&#xff0c;通過亞矩陣云手機底層驅動注入真實坐標數據&#xff08;誤差<5米&#xf…

數據處理和統計分析 —— Pandas 基礎(附數據集)

目錄 一、概述 &#xff08;一&#xff09;series 1、組成 2、創建方式 3、Series常用屬性 &#xff08;二&#xff09;DataFrame 1、組成&#xff1a; 2、構建方式 &#xff08;三&#xff09;數據導入和導出 二、加載數據集 加載部分數據 loc / iloc 三、分組和聚合計算 需求…

github 如何在 readme 顯示Star History

在GitHub的README文件中顯示Star History&#xff08;星標歷史&#xff09;圖表&#xff0c;可以通過以下幾種方式實現&#xff1a; 使用Star History服務 你可以使用Star History服務來生成并嵌入星標歷史圖表 。具體步驟如下&#xff1a; 訪問 Star History 網站: https:/…

安全防范方案

一、基于地理位置的訪問控制&#xff08;核心方案&#xff09; 原理&#xff1a;通過內置GeoIP數據庫或第三方IP庫識別訪問源國家/地區&#xff0c;動態攔截非目標區域IP。 配置步驟&#xff1a; 啟用GeoIP模塊 登錄管理控制臺 → 安全策略 → 地理位置策略 → 加載MaxMind Ge…

Python爬蟲實戰:研究optimesh庫,構建Github網格數據采集系統

1. 引言 1.1 研究背景與意義 在計算機輔助設計(CAD)、有限元分析(FEA)、計算流體力學(CFD)等領域,網格作為離散化連續空間的基礎載體,其質量直接影響數值模擬的精度與效率。高質量的網格應具備單元形狀規則、分布均勻、邊界貼合度高等特征,而低質量網格可能導致計算…

利用Qwen大模型進行c++11并發庫的學習,與時俱進!!!!

文章目錄 1、學習新的東西可以借助ai和官方文檔 1.1 會問問題 異步編程教程 1. std::future 2. std::shared_future 3、std::promise 4、4. std::packaged_task 5. std::async 6. std::future_status 和等待函數 綜合代碼 總結 1、學習新的東西可以借助ai和官方文檔 因為別人寫…

曲面/線 擬合gnuplot

1. 下載gnuplot windows 版&#xff0c;安裝&#xff0c;&#xff08;別的綠色的可能下載即用&#xff0c;推薦下面鏈接這款&#xff09; gnuplot - Browse Files at SourceForge.net 2. 準備數據&#xff0c;存成data.dat或者其它文件名稱&#xff0c;放桌面 數據內容如下…

CentOS Linux 7 (Core)上部署Oracle 11g、19C RAC詳細圖文教程

一、部署參考鏈接&#xff1a; 墨天輪數據庫社區 - 樂知樂享&#xff0c;同心共濟 實戰篇&#xff1a;一步步教你 Linux 7 安裝 Oracle 11GR2 RAC 實戰篇&#xff1a;Linux7 安裝 Oracle 19C RAC 詳細圖文教程 Linux7 安裝 Oracle 19C RAC 詳細圖文教程

WebSocket DevTools 開發調試工具完全指南 – 實時監控、消息攔截、性能分析一站式解決方案

概述 WebSocket DevTools 是一款專為 WebSocket 連接調試而設計的瀏覽器開發者工具擴展。它提供了實時監控、消息攔截、連接管理等功能&#xff0c;幫助開發者快速定位和解決 WebSocket 相關問題。 主要特性 &#x1f50d; 實時監控&#xff1a;實時顯示 WebSocket 連接狀態…

常見的光源頻閃控制方式

一、光源頻閃的優勢消除運動模糊&#xff0c;提升成像質量 光源以極短脈沖&#xff08;微秒級&#xff09;與相機曝光嚴格同步&#xff0c;實現“瞬時凍結”高速運動物體。增強特征對比度&#xff0c;提高缺陷檢出率 頻閃模式下&#xff0c;LED可短時超負荷工作&#xff0c;峰值…

讀From GPT-2 to gpt-oss: Analyzing the Architectural Advances(續)

序 本篇由來&#xff0c;在COC上我當面感謝了組委會和姜寧老師&#xff0c;隨即被姜寧老師催稿&#xff0c;本來當天晚上寫了一個流水賬&#xff0c;感覺甚為不妥。于是決定慢慢寫&#xff0c;緩緩道來。要同時兼顧Show me the code&#xff0c;Show me the vide。希望能形成一…

6-7 TIM編碼器接口 [江科協STM32]

一、引入&#xff08;1&#xff09;編碼器接口的好處&#xff1a;節約軟件資源&#xff1a;外部中斷計次&#xff0c;程序頻繁進入中斷&#xff0c;則軟件資源會被簡單頻繁的工作給占用&#xff0c;效率不高使用定時器的編碼器接口&#xff0c;再配合編碼器&#xff0c;就可以測…

01數據結構-交換排序

01數據結構-交換排序1.冒泡排序1.1基礎冒泡排序1.1.1基礎冒泡排序代碼實現1.2冒泡排序的一次優化1.2.1冒泡排序的第一次優化代碼實現1.3冒泡排序的二次優化1.3.1 冒泡排序的二次優化代碼實現2.快速排序2.1雙邊循環法2.1.1雙邊循環法的代碼實現2.2單邊循環法2.2.1單邊循環法代碼…