摘要
本文将介绍如何在 Linux 操作系统上使用 Qt 6.8 实现视频采集与渲染功能。我们将使用 Qt Multimedia 模块提供的 API 来访问摄像头设备,并使用 Qt Quick 进行视频帧的渲染。同时,我们还会详细讨论如何处理不同的视频格式,特别是在视频会议应用中,如何进行格式转换并将视频帧远程传输给其他参与者。
相关文章:
1. 环境准备
- 操作系统: 统信Linux v20或者麒麟Linux v10 desktop
- 开发环境: Qt 6.8
- 编译器: GCC
- 依赖库: Qt Multimedia, Qt Quick
2. 项目创建
- 打开 Qt Creator,选择 "New Project"。
- 选择 "Application" -> "Qt Quick Application"。
- 设置项目名称和路径,点击 "Next"。
- 选择构建套件,确保选择支持 Qt 6.8 的套件。
- 点击 "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 来进行切换。如果找到了目标摄像头,则重新初始化摄像头并启动它。
摄像头切换过程的关键步骤:
- 检查当前摄像头与目标摄像头是否相同:如果相同,则直接返回,不进行切换。
- 停止并释放当前摄像头:如果摄像头存在,首先停止摄像头的采集,释放相关资源。
- 查找目标摄像头:遍历可用的摄像头设备,找到符合目标 ID 的摄像头。
- 初始化并启动目标摄像头:如果找到了目标摄像头,进行初始化并启动新的摄像头。
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();
}
视频格式转换的步骤
-
映射视频帧
使用videoFrame.map(QVideoFrame::ReadOnly)
将视频帧映射到内存中,以便进行处理。 -
检查像素格式
根据视频帧的像素格式(如NV12
、YUV420P
、YUYV
等),选择适当的处理方式。 -
格式转换并传输
根据不同的像素格式,将视频帧转换为目标格式,并通过以下函数将其发送到编码器进行进一步处理:
send_local_video_frame
-
取消映射视频帧
使用 videoFrame.unmap() 释放资源。
6. 视频渲染
Qt Quick 提供了 VideoOutput 元素用于渲染视频帧。我们可以将 QVideoWidget 嵌入到 QML 场景中进行渲染。
- 在 main.qml 中引入 QtMultimedia 模块:
import QtMultimedia
- 使用 VideoOutput 元素显示视频:
VideoOutput
{ anchors.fill:parent source: videoWidget // 将 QVideoWidget 对象绑定到 source 属性 }
7. 总结
本文介绍了如何在 Linux UOS 上使用 Qt 6.8.实现视频采集与渲染功能,并讨论了如何进行摄像头切换、视频格式转换。这些功能是实现高效视频会议系统的基础,能够确保视频采集、处理、传输的顺畅进行。