• 14阅读
  • 0回复

纯Qt结合ffmpeg实现本地摄像头采集/桌面采集/应用程序窗口采集/指定采集帧率和分辨率等 [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 倒序阅读 楼主  发表于: 5小时前


## 一、前言说明
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](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 文件地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](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,兼容所有版本。

## 五、相关代码
```cpp
#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"
#endif

QList<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;
    }
#else
    int screenCount = qApp->desktop()->screenCount();
    QDesktopWidget *desk = qApp->desktop();
    for (int i = 0; i < screenCount; ++i) {
        QRect rect = desk->screenGeometry(i);
        rects << rect;
    }
#endif
    return 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";
#endif
        device = 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=30
    QStringList 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 ffmpegdevice
        DeviceInfoHelper::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_WASM
    QProcess 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
#endif
    return 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
#endif
    return 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
#endif
    return names;
}

QStringList DeviceInfoHelper::getVideoDevices2()
{
    QStringList names;
#ifdef Q_OS_LINUX
#ifndef Q_OS_WASM
    QProcess 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
#endif
    return 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 ffmpegdevice
    FFmpegDevice::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);
}
```
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
快速回复
限100 字节
 
上一个 下一个