C++ Qt 摄像头视频采集实战:V4L2 与多线程
文件概述
本文主要讲解如何使用 Qt 的 QThread 编写视频采集线程类,实现从 Linux 摄像头设备采集视频、转换图像并通过信号发送的功能。
头文件结构
#ifndef CAPTURE_THREAD_H
#define CAPTURE_THREAD_H
// 各种头文件
介绍使用 C++ 和 Qt 框架在 Linux 环境下进行摄像头视频采集的实现方案。通过 V4L2 接口直接操作摄像头设备,利用 QThread 创建独立采集线程,结合信号槽机制实现跨线程通信。内容涵盖设备打开、参数配置、内存映射(mmap)、缓冲区管理以及图像格式转换。此外,还展示了如何将采集到的视频帧转换为 QImage 进行本地显示,并通过 UDP 协议进行网络广播发送。文章详细解析了线程生命周期管理及资源清理步骤,提供了完整的工程实践模板。
本文主要讲解如何使用 Qt 的 QThread 编写视频采集线程类,实现从 Linux 摄像头设备采集视频、转换图像并通过信号发送的功能。
#ifndef CAPTURE_THREAD_H
#define CAPTURE_THREAD_H
// 各种头文件
这是标准的 C/C++ 头文件保护,防止重复包含。
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
| 头文件 | 用途 |
|---|---|
fcntl.h | 打开设备文件 |
unistd.h | read / write |
videodev2.h | V4L2 摄像头接口 |
sys/mman.h | 内存映射(mmap) |
结论:这是直接操作 Linux 摄像头设备,而非使用 OpenCV 等高级接口。
#include <QThread>
#include <QImage>
#include <QUdpSocket>
| Qt 类 | 用途 |
|---|---|
QThread | 创建线程 |
QImage | 保存一帧图像 |
QUdpSocket | UDP 广播视频 |
#define VIDEO_DEV "/dev/video1"
#define FB_DEV "/dev/fb0"
#define VIDEO_BUFFER_COUNT 3
/dev/video1:摄像头设备路径/dev/fb0:LCD 显存3:摄像头缓冲区数量(V4L2 常规做法,循环缓冲)struct buffer_info {
void *start;
unsigned int length;
};
这是 V4L2 的缓冲区描述结构:
start:缓冲区首地址length:缓冲区大小说明:一帧图像对应一个 buffer。
class CaptureThread : public QThread
这是一个线程类。在 Qt 中,QThread 代表独立执行的线程,实际线程代码写在 run() 函数中。
Q_OBJECT
启用 Qt 的信号 - 槽机制。没有它,signals 和 slots 关键字无法使用。
signals:
void imageReady(QImage);
void sendImage(QImage);
imageReady(QImage):通知界面有新图像数据sendImage(QImage):用于网络发送含义:线程完成任务后,通过信号告知外部。
bool startFlag = false;
bool startBroadcast = false;
bool startLocalDisplay = false;
startFlag:是否开启采集线程startBroadcast:是否进行 UDP 广播startLocalDisplay:是否本地显示CaptureThread(QObject *parent = nullptr) {
Q_UNUSED(parent);
}
仅为了兼容 Qt 体系,未使用 parent 参数以避免警告。
void setThreadStart(bool start)
{
startFlag = start;
if (start) {
if (!this->isRunning())
this->start();
} else {
this->quit();
}
}
true:开始采集(若未运行则启动)false:停止采集(请求退出)void setBroadcast(bool start)
{
// 修改标志位
}
void setLocalDisplay(bool start)
{
// 修改标志位
}
void CaptureThread::run()
这是线程真正执行的入口函数。
start() 调用后系统自动执行 run()run() 内部为子线程逻辑#ifdef linux
#ifndef __arm__
return;
#endif
#endif
原因:该代码针对嵌入式 Linux 环境设计,PC 端通常无 /dev/video1 或驱动不同。
video_fd = open(VIDEO_DEV, O_RDWR);
VIDEO_DEV 即 /dev/video1fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;
ioctl(video_fd, VIDIOC_S_FMT, &fmt);
配置摄像头输出 640×480、RGB565 格式的图像。
req_bufs.count = 3;
req_bufs.memory = V4L2_MEMORY_MMAP;
ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs);
向驱动申请 3 个帧缓冲,使用 mmap 方式。
ioctl(video_fd, VIDIOC_QUERYBUF, &buf);
bufs_info[n_buf].start = mmap(...);
将摄像头内部缓冲区映射到用户空间。
ioctl(video_fd, VIDIOC_QBUF, &buf);
告诉驱动可以往该 buffer 写入数据。
ioctl(video_fd, VIDIOC_STREAMON, &type);
摄像头开始工作。
while (startFlag) {
// 1. 取出一帧
ioctl(video_fd, VIDIOC_DQBUF, &buf);
// 2. 转换为 QImage
QImage qImage((unsigned char*)bufs_info[n_buf].start, 640, 480, QImage::Format_RGB16);
// 3. 本地显示
if (startLocalDisplay)
emit imageReady(qImage);
// 4. UDP 广播
if (startBroadcast) {
QUdpSocket udpSocket;
QByteArray byte;
qImage.save(&byte, "JPEG");
QByteArray base64Byte = byte.toBase64();
udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888);
}
// 5. 归还缓冲区
ioctl(video_fd, VIDIOC_QBUF, &buf);
}
关键点:
QImage::Format_RGB16 包装,无额外拷贝。munmap(...);
close(video_fd);
释放内存并关闭设备。
Qt 中:
run()里的代码在子线程,UI 永远只能在主线程。子线程 → UI 只能靠信号槽。
run() 运行在子线程setThreadStart(true)(主线程)start()(主线程),操作系统分配新线程emit imageReady(qImage)(子线程)当信号发出线程与槽函数对象线程不同时,Qt 自动使用 Qt::QueuedConnection。
emit 不会阻塞,也不会直接调用槽函数❌ 错误:在子线程直接操作 UI
// ui->label->setPixmap(...); // 会导致崩溃
✅ 正确:通过信号传递数据
emit imageReady(qImage); // 安全
CaptureThread 对象在主线程,但 run() 在子线程emit 只是投递消息,不是直接函数调用
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online