跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ Qt 摄像头视频采集实战:V4L2 与多线程

综述由AI生成介绍使用 C++ 和 Qt 框架在 Linux 环境下进行摄像头视频采集的实现方案。通过 V4L2 接口直接操作摄像头设备,利用 QThread 创建独立采集线程,结合信号槽机制实现跨线程通信。内容涵盖设备打开、参数配置、内存映射(mmap)、缓冲区管理以及图像格式转换。此外,还展示了如何将采集到的视频帧转换为 QImage 进行本地显示,并通过 UDP 协议进行网络广播发送。文章详细解析了线程生命周期管理及资源清理步骤,提供了完整的工程实践模板。

观心发布于 2026/3/25更新于 2026/5/3130 浏览

C++ Qt 摄像头视频采集实战:V4L2 与多线程

文件概述

本文主要讲解如何使用 Qt 的 QThread 编写视频采集线程类,实现从 Linux 摄像头设备采集视频、转换图像并通过信号发送的功能。

头文件结构

#ifndef CAPTURE_THREAD_H
#define CAPTURE_THREAD_H
// 各种头文件
// 宏定义
// 结构体
// CaptureThread 类
#endif

这是标准的 C/C++ 头文件保护,防止重复包含。

依赖库分类

1. Linux 底层相关(摄像头 / 显存)
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
头文件用途
fcntl.h打开设备文件
unistd.hread / write
videodev2.hV4L2 摄像头接口
sys/mman.h内存映射(mmap)

结论:这是直接操作 Linux 摄像头设备,而非使用 OpenCV 等高级接口。

2. Qt 相关(线程 / 图片 / 网络)
#include <QThread>
#include <QImage>
#include <QUdpSocket>
Qt 类用途
QThread创建线程
QImage保存一帧图像
QUdpSocketUDP 广播视频

宏定义

#define VIDEO_DEV "/dev/video1"
#define FB_DEV "/dev/fb0"
#define VIDEO_BUFFER_COUNT 3
  • /dev/video1:摄像头设备路径
  • /dev/fb0:LCD 显存
  • 3:摄像头缓冲区数量(V4L2 常规做法,循环缓冲)

结构体 buffer_info

struct buffer_info {
    void *start;
    unsigned int length;
};

这是 V4L2 的缓冲区描述结构:

  • start:缓冲区首地址
  • length:缓冲区大小

说明:一帧图像对应一个 buffer。


核心类 CaptureThread

继承关系

class CaptureThread : public QThread

这是一个线程类。在 Qt 中,QThread 代表独立执行的线程,实际线程代码写在 run() 函数中。

Q_OBJECT 宏

Q_OBJECT

启用 Qt 的信号 - 槽机制。没有它,signals 和 slots 关键字无法使用。

Signals(信号)

signals:
    void imageReady(QImage);
    void sendImage(QImage);
  • imageReady(QImage):通知界面有新图像数据
  • sendImage(QImage):用于网络发送

含义:线程完成任务后,通过信号告知外部。

Private 成员变量

bool startFlag = false;
bool startBroadcast = false;
bool startLocalDisplay = false;
  • startFlag:是否开启采集线程
  • startBroadcast:是否进行 UDP 广播
  • startLocalDisplay:是否本地显示

构造函数

CaptureThread(QObject *parent = nullptr) {
    Q_UNUSED(parent);
}

仅为了兼容 Qt 体系,未使用 parent 参数以避免警告。

Slots(槽函数)

1. 开启 / 关闭线程
void setThreadStart(bool start)
{
    startFlag = start;
    if (start) {
        if (!this->isRunning())
            this->start();
    } else {
        this->quit();
    }
}
  • true:开始采集(若未运行则启动)
  • false:停止采集(请求退出)
2. 开关广播
void setBroadcast(bool start)
{
    // 修改标志位
}
3. 开关本地显示
void setLocalDisplay(bool start)
{
    // 修改标志位
}

实现逻辑详解 (capture_thread.cpp)

run() 函数地位

void CaptureThread::run()

这是线程真正执行的入口函数。

  • start() 调用后系统自动执行 run()
  • run() 内部为子线程逻辑
  • 注意:不能在此处操作 UI,只能通过信号通信

平台判断

#ifdef linux
#ifndef __arm__
    return;
#endif
#endif
  • Linux + ARM:执行采集逻辑
  • Linux + x86 / Windows:跳过

原因:该代码针对嵌入式 Linux 环境设计,PC 端通常无 /dev/video1 或驱动不同。

第一阶段:打开摄像头设备

video_fd = open(VIDEO_DEV, O_RDWR);
  • VIDEO_DEV 即 /dev/video1
  • 成功返回文件描述符,失败打印错误并退出

第二阶段:设置摄像头参数

fmt.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 方式。

第四阶段: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);
}

关键点:

  • 摄像头数据为 RGB565,Qt 使用 QImage::Format_RGB16 包装,无额外拷贝。
  • 子线程不能直接操作 UI,需通过信号通知主线程。
  • 原始 RGB 数据较大,先压缩为 JPEG 再 Base64 编码以防乱码。

资源清理

munmap(...);
close(video_fd);

释放内存并关闭设备。


线程与信号槽交互机制

核心原则

Qt 中:run() 里的代码在子线程,UI 永远只能在主线程。子线程 → UI 只能靠信号槽。

角色区分

  1. 主线程 (UI):负责界面控件(QLabel, QPushButton 等)
  2. CaptureThread 对象:属于主线程,但 run() 运行在子线程
  3. 子线程执行体:负责摄像头采集逻辑

完整流程

  1. UI 点击按钮:触发 setThreadStart(true)(主线程)
  2. 启动线程:调用 start()(主线程),操作系统分配新线程
  3. 执行 run():进入子线程,开始循环采集
  4. 发送信号:emit imageReady(qImage)(子线程)
  5. 接收信号:主线程事件队列收到消息,调用槽函数更新 UI

跨线程连接机制

当信号发出线程与槽函数对象线程不同时,Qt 自动使用 Qt::QueuedConnection。

  • 子线程 emit 不会阻塞,也不会直接调用槽函数
  • 数据被放入主线程事件队列
  • 主线程空闲时处理槽函数

常见错误与正确做法

❌ 错误:在子线程直接操作 UI

// ui->label->setPixmap(...); // 会导致崩溃

✅ 正确:通过信号传递数据

emit imageReady(qImage); // 安全

总结

  1. CaptureThread 对象在主线程,但 run() 在子线程
  2. emit 只是投递消息,不是直接函数调用
  3. 跨线程信号槽自动使用队列机制,保证 UI 不卡死

目录

  1. C++ Qt 摄像头视频采集实战:V4L2 与多线程
  2. 文件概述
  3. 头文件结构
  4. 依赖库分类
  5. 1. Linux 底层相关(摄像头 / 显存)
  6. 2. Qt 相关(线程 / 图片 / 网络)
  7. 宏定义
  8. 结构体 buffer_info
  9. 核心类 CaptureThread
  10. 继承关系
  11. Q_OBJECT 宏
  12. Signals(信号)
  13. Private 成员变量
  14. 构造函数
  15. Slots(槽函数)
  16. 1. 开启 / 关闭线程
  17. 2. 开关广播
  18. 3. 开关本地显示
  19. 实现逻辑详解 (capture_thread.cpp)
  20. run() 函数地位
  21. 平台判断
  22. 第一阶段:打开摄像头设备
  23. 第二阶段:设置摄像头参数
  24. 第三阶段:申请缓冲区
  25. 第四阶段:mmap 映射缓冲区
  26. 第五阶段:提交缓冲区给驱动
  27. 第六阶段:启动采集
  28. 第七阶段:视频循环采集
  29. 资源清理
  30. 线程与信号槽交互机制
  31. 核心原则
  32. 角色区分
  33. 完整流程
  34. 跨线程连接机制
  35. 常见错误与正确做法
  36. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Kubernetes 中部署 Prometheus 实战指南
  • llama-cpp-python 本地大模型部署与安装实战
  • ThinkPHP 5 在 Windows IIS 上的部署与 PHP 版本兼容性配置
  • 2025 年 11 月 14 日全球 AI 前沿动态
  • Spring Web MVC 入门:从概念到实践
  • Python 爬虫实战:解析并下载百度文库内容
  • 增强现实(AR)技术全解析:从概念到应用
  • AI 产品经理转型指南:核心能力与实战框架
  • Vue 3 实战:10 个提升开发效率的常用技巧
  • Pixel Shuffle 与 Unshuffle 原理及算法流程
  • Apache Guacamole 远程桌面网关部署与配置指南
  • 利用 AIGC 技术生成古诗意境图的方法与实践
  • KaiwuDB 3.1.0 在 Ubuntu 22.04 部署实战:TLS 配置与性能基线
  • GitHub 生成 SSH 密钥配置指南
  • VSCode Python venv 环境加载失败排查与解决指南
  • QGroundControl 跨平台无人机地面站安装指南
  • Vue3 自定义 v-model 高级用法:从基础到实战
  • 软件开发并非易事:低代码与零基础的真相
  • Flutter EWS 组件适配鸿蒙实战:Exchange 邮件日历同步方案
  • Cursor AI 编程工具深度解析与技术架构

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online