QT6开发高性能企业视频会议-6 Linux Video采集和渲染

摘要

本文将介绍如何在 Linux 操作系统上使用 Qt 6.8 实现视频采集与渲染功能。我们将使用 Qt Multimedia 模块提供的 API 来访问摄像头设备,并使用 Qt Quick 进行视频帧的渲染。同时,我们还会详细讨论如何处理不同的视频格式,特别是在视频会议应用中,如何进行格式转换并将视频帧远程传输给其他参与者。

相关文章: 

Linux Audio开发

神旗视讯: 高性能的私有化音视频系统

神旗视讯: 从零搭建免费高性能信创会议系统

1. 环境准备

  • 操作系统: 统信Linux v20或者麒麟Linux v10 desktop  
  • 开发环境: Qt 6.8
  • 编译器: GCC
  • 依赖库: Qt Multimedia, Qt Quick

2. 项目创建

  1. 打开 Qt Creator,选择 "New Project"。
  2. 选择 "Application" -> "Qt Quick Application"。
  3. 设置项目名称和路径,点击 "Next"。
  4. 选择构建套件,确保选择支持 Qt 6.8 的套件。
  5. 点击 "Finish" 完成项目创建。

3. 摄像头初始化与设置

在开始视频采集之前,我们需要初始化摄像头并进行一些必要的设置。

void initCamera() {
    qDebug("[%s][%d]: ", __PRETTY_FUNCTION__, __LINE__);
    QCameraDevice m_camera_device = QMediaDevices::defaultVideoInput();
    QCamera * m_camera = new QCamera(m_camera_device);

    if (m_camera->cameraFormat().isNull())
    {
        auto formats = m_camera_device.videoFormats();

        if (!formats.isEmpty())
        {
            QCameraFormat bestFormat;
            for (const auto &fmt : formats)
            {
                qDebug("enum frame format, format is %d, width is %d, height is %d, max rate is %f", fmt.pixelFormat(), fmt.resolution().width(), fmt.resolution().height(), fmt.maxFrameRate());
                if (fmt.pixelFormat() == QVideoFrameFormat::Format_NV12
                    && fmt.resolution().width() > fmt.resolution().height()
                    && bestFormat.resolution().width() < fmt.resolution().width()
                    && bestFormat.resolution().height() < fmt.resolution().height())
                {
                    bestFormat = fmt;
                }
                else if(fmt.pixelFormat() == QVideoFrameFormat::Format_YUYV)
                {
                    if(bestFormat.isNull() || 
                        (fmt.resolution().width() * 9 == fmt.resolution().height() * 16
                        && fmt.resolution().width() > bestFormat.resolution().width())
                        )
                    {
                        if(fmt.maxFrameRate() >= 29.0f)
                            bestFormat = fmt;
                    }
                }
            }
            m_camera->setCameraFormat(bestFormat);
        }
    }

    m_captureSession.setCamera(m_camera);
    m_captureSession.setVideoSink(&m_videoSink);

    connect(&m_videoSink, &QVideoSink::videoFrameChanged, this, &UnifiedVideoCapture::slotProcessVideoFrame);
    qDebug("[%s][%d] Exit", __PRETTY_FUNCTION__, __LINE__);
}

在摄像头初始化过程中,我们首先检查设备的权限,并获取支持的摄像头格式。然后,通过对摄像头的支持格式进行筛选,选择最佳的视频格式,并进行设置。这些操作确保摄像头的设置符合视频会议需求,能够支持不同的视频输入格式。

4. 摄像头切换功能

在这段代码中,我们首先检查目标摄像头 ID 是否与当前摄像头相同。如果是,则无需切换。如果不同,我们会停止当前摄像头,删除并释放其资源。接着,通过查找所有可用的摄像头设备,匹配目标摄像头 ID 来进行切换。如果找到了目标摄像头,则重新初始化摄像头并启动它。

摄像头切换过程的关键步骤:

  1. 检查当前摄像头与目标摄像头是否相同:如果相同,则直接返回,不进行切换。
  2. 停止并释放当前摄像头:如果摄像头存在,首先停止摄像头的采集,释放相关资源。
  3. 查找目标摄像头:遍历可用的摄像头设备,找到符合目标 ID 的摄像头。
  4. 初始化并启动目标摄像头:如果找到了目标摄像头,进行初始化并启动新的摄像头。
void switchCamera(QString id) {
    if (m_camera_device.description().contains(id, Qt::CaseInsensitive)) {
        qDebug("[%s][%d]: Current camera is the same as target, no change needed.", __FUNCTION__, __LINE__);
        return;
    }

    if (m_camera != nullptr) {
        m_camera->stop();
        m_camera->deleteLater();
        m_camera = nullptr;
    }

    const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
    bool cameraFound = false;

    for (const QCameraDevice &cameraDevice : cameras)
    {
        if (cameraDevice.description().contains(id, Qt::CaseInsensitive))
        {
            m_camera_device = cameraDevice;
            m_camera = new QCamera(m_camera_device);
            cameraFound = true;
            break;
        }
    }

    if (!cameraFound) {
        qWarning("[%s][%d]: Camera with id '%s' not found.", __FUNCTION__, __LINE__, qPrintable(id));
        return;
    }

    initCamera();
    m_camera->start();
}

5. 视频格式转换与远程传输

在视频会议应用中,摄像头采集到的视频帧通常需要进行格式转换,以适应编解码器的输入需求。以下代码展示了如何处理不同的视频格式,并将其发送到编码器进行编码处理。

void slotProcessVideoFrame(const QVideoFrame &frame) {
    QVideoFrame videoFrame(frame);

    QVideoFrameFormat::PixelFormat pixelFormat = videoFrame.pixelFormat();
   
    uchar *pdata = nullptr;
    int len = 0;

    videoFrame.map(QVideoFrame::ReadOnly);

    if (pixelFormat == QVideoFrameFormat::Format_UYVY) {
        int width = frame.width();
        int height = frame.height();
        size_t frame_size = width * height * 3 / 2;
        char *nv12_content = (char *)malloc(sizeof(char) * frame_size);
        if (!nv12_content) {
            free(nv12_content);
            return ;
        }
        size_t y_size = width * height;
        size_t pixels_in_a_row = width * 2;
        char *nv12_y_ptr = nv12_content;
        char *nv12_uv_ptr = nv12_content + y_size;
        int lines = 0;

        char *uyvy_content = (char*)videoFrame.bits(0);
        size_t file_size = width * height * 2;
        for (int i = 0;i < file_size;i += 4) {
            // copy y channel
            *nv12_y_ptr++ = uyvy_content[i + 1];
            *nv12_y_ptr++ = uyvy_content[i + 3];
            if (0 == i % pixels_in_a_row) {
                ++lines;
            }
            if (lines % 2) {       // extract the UV value of odd rows
                // copy uv channel
                *nv12_uv_ptr++ = uyvy_content[i];
                *nv12_uv_ptr++ = uyvy_content[i + 2];
            }
        }
        
        send_local_video_frame(nv12_content,  width * height * 3 / 2, width, height, QVideoFrameFormat::PixelFormat::Format_NV12);
        free(nv12_content);

    }
    else if(pixelFormat == QVideoFrameFormat::Format_NV12) {

        int width = frame.width();
        int height = frame.height();
        len = width * height * 3 / 2;
               
       send_local_video_frame((unsigned char*)videoFrame.bits(0),  len, width, height, QVideoFrameFormat::PixelFormat::Format_NV12);
    }
    else if(pixelFormat == QVideoFrameFormat::Format_YUV420P)
    {
        int width = frame.width();
        int height = frame.height();
        len = width * height * 3 / 2;
        
        send_local_video_frame((unsigned char*)videoFrame.bits(0),  len, width, height, QVideoFrameFormat::PixelFormat::Format_YUV420P);
    }
    else if(pixelFormat == QVideoFrameFormat::Format_YUYV)
    {
        int width = frame.width();
        int height = frame.height();
        len = width * height * 2;
        
        send_local_video_frame((unsigned char*)videoFrame.bits(0), len, width, height, QVideoFrameFormat::PixelFormat::Format_YUYV);
    }
    else
    {
        qDebug("Unhandled camera video format %d", pixelFormat);
    }
    videoFrame.unmap();
}

视频格式转换的步骤

  1. 映射视频帧
    使用 videoFrame.map(QVideoFrame::ReadOnly) 将视频帧映射到内存中,以便进行处理。

  2. 检查像素格式
    根据视频帧的像素格式(如 NV12YUV420PYUYV 等),选择适当的处理方式。

  3. 格式转换并传输
    根据不同的像素格式,将视频帧转换为目标格式,并通过以下函数将其发送到编码器进行进一步处理:
    send_local_video_frame

  4. 取消映射视频帧
    使用 videoFrame.unmap() 释放资源。

6. 视频渲染

Qt Quick 提供了 VideoOutput 元素用于渲染视频帧。我们可以将 QVideoWidget 嵌入到 QML 场景中进行渲染。

  1. 在 main.qml 中引入 QtMultimedia 模块:
    import QtMultimedia
  2. 使用 VideoOutput 元素显示视频:
    VideoOutput{ anchors.fill:parent source: videoWidget // 将 QVideoWidget 对象绑定到 source 属性 }

7. 总结

本文介绍了如何在 Linux UOS 上使用 Qt 6.8.实现视频采集与渲染功能,并讨论了如何进行摄像头切换、视频格式转换。这些功能是实现高效视频会议系统的基础,能够确保视频采集、处理、传输的顺畅进行。

开源地址:

国内: https://gitee.com/sqmeeting

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值