Qt/C++ Audio and Video Development 52-The ultimate design for collecting local screen desktop

I. Introduction

When we first started designing, we only considered one screen. This is of course the most ideal situation. In fact, there are many users with dual or multiple screens. For example, for my two screens, screen 1 is 1080P and screen 2 is 1080P. It is a 2K resolution. The area for printing the two screens is QRect(0,0 1920x1030), QRect(1920,-208 2560x1390). You can see that there is a negative value (you can drag and adjust the display arrangement in the operating system). And if the order of the left and right sides of the screen is changed, for example, the 2K resolution is in the front, and the printed screen area is QRect(0,0 1920x1030), QRect(-2560,-185 2560x1390), you can see that the XY coordinates of the 2K screen are all It's a negative number. Do you think this is all the cases? Then you are wrong. It is possible that the screens are arranged up and down. The 2K screen prints the area below QRect(0,0 1920x1030), QRect(-639,1080 2560x1390). The 2K screen prints the area above QRect(0,0). 1920x1030), QRect(-270,-1440 2560x1390), this also supports the case of two screens. If there are 4 or more, if you want the user to obtain the area of ​​the corresponding screen and fill in the desktop recording parameters, it is no different. This is definitely impossible, and the default parameters of ffmpeg are to pass in the real offset value coordinates and resolution, and users are accustomed to the resolution of the current screen when the program is opened on which screen. The rate is based on the offset value, and the offset value is based on the upper left corner (0, 0). Therefore, it is agreed that the user only needs to fill in the resolution and relative offset value. If not filled in, the overall resolution of the current screen will prevail. This requires Create a special conversion function to obtain the current screen area and calculate various situations.

After the troublesome calculation above, I thought I could turn off the phone and go home to eat with chicken drumsticks. But I thought too much. The user may have entered an area that exceeds the current resolution, or the offset value plus the acquisition resolution exceeds the resolution of the current screen. , it cannot be opened, cannot be collected normally, and the program will not be executed. In order to enhance robustness and compatibility, it is necessary to make some adjustments. For example, after calculation, it is found that the set collection area size exceeds the real resolution size of the screen. Crop from the set offset value to the lower right corner. In this way, no matter how the user makes mistakes, the program will not make mistakes and can collect normally and make adjustments in a reasonable way. This is a good program design.

Demo video: https://www.bilibili.com/video/BV1D8411B7eP

The functions of collecting the desktop that have been implemented so far:

  • Supports multiple screens and can specify screen index.
  • Supports left and right arrangement, up and down arrangement and free adjustment of screen position.
  • Supports designated collection area.
  • Automatically correct parameter settings that exceed the screen area.
  • Specify relative offset value collection, taking the upper left corner of the desktop as the base.
  • Supports specifying the acquisition frame rate.
  • Do not fill in the resolution and various parameters, and the default values ​​will be automatically calculated.
  • If the screen is not specified, the current screen where the mouse is located shall prevail.
  • There are more details reflected in the code.

Format description:

  1. URL address format description: desktop=desktop|800x600|25|0|0|0.
  2. Parameters are separated by English vertical bars, where desktop= is a fixed prefix, used to distinguish whether the current address is used to collect the desktop.
  3. The first parameter represents the device identification. For example, fill in desktop for win, fill in: 0.0+0,0 for linux, and fill in 0:0 for mac.
  4. The second parameter indicates the resolution of the collection. If not filled in, the screen resolution will be used by default.
  5. The third parameter represents the frame rate, which is basically between 2-30. If not filled in, ffmpeg will set a value by default, sometimes 30.
  6. The fourth/fifth parameter represents the offset value XY coordinates, starting from the upper left corner of the screen (0,0).
  7. The sixth parameter represents the screen index. If not filled in, the screen where the current mouse is located will be used by default.
  8. Writing method 1: desktop=desktop, the current screen is captured in full screen.
  9. Writing method 2: desktop=desktop||15|0|0|1, screen 2 full screen capture, frame rate 15.
  10. Writing method 3: desktop=desktop|800x600|10|50|100, the current screen where the mouse is located is collected, the collection area is rect (50,100,800,600), and the frame rate is 10.

2. Effect drawing

Insert image description here

3. Experience address

  1. Domestic site: https://gitee.com/feiyangqingyun
  2. International site: https://github.com/feiyangqingyun
  3. Personal work: https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. Experience address: https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g Extraction code: 01jf File name: bin_video_push.

4. Functional features

  1. Supports various local video files and network video files.
  2. Supports various network video streams, web cameras, protocols including rtsp, rtmp, http.
  3. Supports streaming from local camera devices, and can specify resolution, frame rate, etc.
  4. Supports streaming of local desktop, and can specify screen area and frame rate, etc.
  5. Automatically start the streaming media service program, the default is mediamtx (formerly rtsp-simple-server), srs, EasyDarwin, LiveQing, ZLMediaKit, etc. can be selected.
  6. You can switch preview video files in real time, switch the video file playback progress, and push the stream wherever you switch.
  7. The clarity and quality of the push stream are adjustable.
  8. Files, directories, and addresses can be added dynamically.
  9. Video files are automatically pushed in a loop. If the video source is a video stream, it will automatically reconnect after being disconnected.
  10. The network video stream will automatically reconnect, and the stream will continue to be pushed if the reconnection is successful.
  11. The network video stream has extremely high real-time performance and extremely low delay, with the delay time being about 100ms.
  12. Extremely low CPU usage, 4-channel main stream push only takes up 0.2% of the CPU. Theoretically, a regular ordinary PC machine can push 100 channels without any pressure, and the main performance bottleneck is the network.
  13. There are two options for streaming: rtsp/rtmp. The pushed data supports four direct access methods: rtsp/rtmp/hls/webrtc, and can be opened directly in a browser to view real-time images.
  14. You can push the stream to an external network server, and then play the corresponding video stream through mobile phones, computers, tablets and other devices.
  15. Each push stream can be manually specified with a unique identifier (to facilitate streaming/users do not need to remember complex addresses). If not specified, a hash value will be randomly generated according to the strategy.
  16. Automatically generate a test webpage and open it directly for playback. You can see the real-time effect and automatically display it in a grid according to the number.
  17. During the streaming process, you can switch the corresponding streaming items in the table, preview the video being pushed in real time, and switch the playback progress of the video file.
  18. Audio and video are pushed simultaneously, and the original data that conforms to the 264/265/aac format is automatically pushed, and the data that does not conform is automatically transcoded and then pushed (it will occupy a certain amount of CPU).
  19. The transcoding strategy supports three types, automatic processing (original data that meets the requirements/transcoding that does not meet the requirements), file only (transcoded video of file type), and all transcoding.
  20. The table displays the resolution and audio and video data status of each stream in real time. Gray represents no input stream, black represents no output stream, green represents the original data stream, and red represents the transcoded data stream.
  21. Automatically reconnect to the video source and the streaming media server to ensure that after startup, the push address and the open address are reconnected in real time. As long as they are restored, they will be connected immediately to continue collecting and pushing.
  22. An example of loop push is provided. A video source is pushed to multiple streaming media servers at the same time. For example, a video is opened and pushed to Douyin/Kuaishou/Bilibili at the same time. It can be used as a recording and playback push, and the list is looped, which is very convenient and practical.
  23. According to different streaming media server types, the corresponding rtsp/rtmp/hls/flv/ws-flv/webrtc address is automatically generated. Users can directly copy the address to the player or web page for preview.
  24. The encoded video format can be automatically processed (if the source is 264, then 264/the source is 265, then 265), converted to H264 (forced conversion to 264), or converted to H265 (forced conversion to 265).
  25. Supports any version of Qt4/Qt5/Qt6 and any system (windows/linux/macos/android/embedded linux, etc.).

5. Related codes

void AbstractVideoThread::checkDeviceUrl(const QString &url, QString &deviceName, QString &resolution, int &frameRate, int &offsetX, int &offsetY, QString &encodeScale)
{
    
    
    //无论是否带分隔符第一个约定是设备名称
    QStringList list = url.split("|");
    int count = list.count();
    deviceName = list.at(0);

    //默认不指定屏幕索引
    int screenIndex = -1;
    //用一个无用的参数作为是否是本地摄像头的标志位
    bool isCamera = (encodeScale == "camera");

    //带分隔符说明还指定了分辨率或帧率
    if (count > 1) {
    
    
        QStringList sizes = WidgetHelper::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";
        }

        //第三个参数是帧率
        if (count >= 3) {
    
    
            frameRate = list.at(2).toInt();
        }

        //桌面采集还需要取出其他几个参数
        if (!isCamera) {
    
    
            //XY坐标偏移值
            if (count >= 5) {
    
    
                offsetX = list.at(3).toInt();
                offsetY = list.at(4).toInt();
            }

            //屏幕索引
            if (count >= 6) {
    
    
                screenIndex = list.at(5).toInt();
            }

            //视频缩放
            if (count >= 7) {
    
    
                encodeScale = list.at(6);
            }

            WidgetHelper::checkRect(screenIndex, resolution, offsetX, offsetY);
        }
    }

    //没有设置分辨率则重新处理
    if (resolution == "0x0") {
    
    
        if (isCamera) {
    
    
            resolution = "640x480";
        } else {
    
    
            WidgetHelper::checkRect(screenIndex, resolution, offsetX, offsetY);
        }
    }
}

QList<QRect> WidgetHelper::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);
        rects << screen->geometry();
    }
#else
    int screenCount = qApp->desktop()->screenCount();
    QDesktopWidget *desk = qApp->desktop();
    for (int i = 0; i < screenCount; ++i) {
    
    
        rects << desk->screenGeometry(i);
    }
#endif
    return rects;
}

QRect WidgetHelper::getScreenRect(int screenIndex)
{
    
    
    //指定了屏幕索引则取指定的(没有指定则取当前鼠标所在屏幕)
    QList<QRect> rects = WidgetHelper::getScreenRects();
    if (screenIndex >= 0 && screenIndex < rects.count()) {
    
    
        return rects.at(screenIndex);
    } else {
    
    
        //当前屏幕区域包含当前鼠标所在坐标则说明是当前屏幕
        QPoint pos = QCursor::pos();
        foreach (QRect rect, rects) {
    
    
            if (rect.contains(pos)) {
    
    
                return rect;
            }
        }
    }
}

QString WidgetHelper::getResolution(int width, int height)
{
    
    
    //取偶数(虚拟机中很可能是奇数的分辨率)
    if (width % 2 != 0) {
    
    
        width--;
    }

    if (height % 2 != 0) {
    
    
        height--;
    }

    return QString("%1x%2").arg(width).arg(height);
}

QString WidgetHelper::getResolution(const QString &resolution)
{
    
    
    QStringList sizes = WidgetHelper::getSizes(resolution);
    return getResolution(sizes.at(0).toInt(), sizes.at(1).toInt());
}

void WidgetHelper::checkRect(int screenIndex, QString &resolution, int &offsetX, int &offsetY)
{
    
    
    QRect rect = WidgetHelper::getScreenRect(screenIndex);
    if (resolution == "0x0") {
    
    
        resolution = WidgetHelper::getResolution(rect.width(), rect.height());
    } else {
    
    
        resolution = WidgetHelper::getResolution(resolution);
    }

    //偏移值必须小于分辨率否则重置
    if (offsetX > rect.width()) {
    
    
        offsetX = 0;
    }
    if (offsetY > rect.height()) {
    
    
        offsetY = 0;
    }

    //判断设定的偏移值加上设定的分辨率是否超出了真实的分辨率
    QStringList sizes = WidgetHelper::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 = WidgetHelper::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;
}

Guess you like

Origin blog.csdn.net/feiyangqingyun/article/details/132823102