2026/6/1 9:10:31
网站建设
项目流程
主机做网站服务器怎么设置,建设公共网站的目的,学校网站建设总结报告,百度商桥代码怎么加到网站上capture_thread.h 一、这个文件是干嘛的#xff1f;#xff08;一句话先懂#xff09;#x1f449; 这是一个用 Qt 的 QThread 写的“视频采集线程类”作用大致是#xff1a;从 Linux 摄像头设备 /dev/video1 采集视频把采集到的图像转换成 QImage通过 Qt 信号 发给界面显…capture_thread.h一、这个文件是干嘛的一句话先懂这是一个用 Qt 的QThread写的“视频采集线程类”作用大致是从Linux 摄像头设备/dev/video1采集视频把采集到的图像转换成QImage通过Qt 信号发给界面显示 / 通过 UDP 广播发送二、头文件结构总览先有整体#ifndef CAPTURE_THREAD_H #define CAPTURE_THREAD_H // 各种头文件 // 宏定义 // 结构体 // CaptureThread 类 #endif这是标准C/C 头文件保护防止重复包含不用纠结。三、头文件为什么这么多分三类① Linux 底层相关摄像头 / 显存#include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/videodev2.h 用来干这些事头文件用途fcntl.h打开设备文件unistd.hread / writevideodev2.hV4L2 摄像头接口sys/mman.h内存映射mmap结论这是直接操作 Linux 摄像头设备不是 OpenCV 那种高级接口。② 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/fb0LCD 显存3摄像头缓冲区数量V4L2 常规做法用 3 个 buffer 做循环缓冲五、结构体buffer_infostruct buffer_info { void *start; unsigned int length; }; 这是V4L2 的缓冲区描述结构成员含义start缓冲区首地址length缓冲区大小一帧图像 一个 buffer六、重点来了CaptureThread类1️⃣ 继承关系class CaptureThread : public QThread这是一个线程类 在 Qt 中QThread 一个独立执行的线程真正线程代码写在run()里2️⃣Q_OBJECT是干嘛的Q_OBJECT启用 Qt 的“信号-槽机制”没有它signalsslots全部不能用七、signals信号——线程往外“喊话”signals: void imageReady(QImage); void sendImage(QImage);怎么理解线程干完活 → 告诉 UI信号用途imageReady(QImage)通知界面有新图像sendImage(QImage)用于网络发送信号 ≈ “我这里准备好数据了”八、private 成员变量控制线程行为bool startFlag false; bool startBroadcast false; bool startLocalDisplay false;含义一看就懂版变量作用startFlag是否开启采集线程startBroadcast是否 UDP 广播startLocalDisplay是否本地显示线程内部会根据这些 flag 决定做不做事线程函数void run() override;重点重点重点这里面一般是while(startFlag) { 采集一帧; 转 QImage; emit imageReady(img); }九、public 构造函数很简单CaptureThread(QObject *parent nullptr) { Q_UNUSED(parent); } 只是为了和 Qt 体系兼容没用parent防止警告十、slots槽函数——外部“控制线程”1️⃣ 开启 / 关闭线程void setThreadStart(bool start)逻辑拆开看startFlag start;true→ 开始采集false→ 停止采集if (start) { if (!this-isRunning()) this-start(); }只有线程没跑才启动else { this-quit(); }请求线程退出2️⃣ 开关广播void setBroadcast(bool start) 只是改标志位3️⃣ 开关本地显示void setLocalDisplay(bool start) 同样只是改标志位十一、整张图一句话理解非常重要UI 线程 调用setThreadStart(true) 采集线程run()开始跑 摄像头采集一帧 转成QImageemit imageReady(img) UI 显示图像capture_thread.cpp好这个capture_thread.cpp我给你按“一步一步跑起来”的顺序讲你把它当成“Linux 摄像头 Qt 线程 信号槽 UDP 广播”的完整实战模板来看。你不用一次全懂看完这一遍至少知道每一段在干嘛。一、run()是什么地位先定性void CaptureThread::run()这是线程真正执行的函数在 Qt 里start()→ 系统自动调用run()run()里面就是子线程逻辑不能写 UI只能发信号二、最外层的宏判断为什么有#ifdef linux #ifndef __arm__ return; #endif含义条件结果Linux ARM✅ 真正执行Linux x86❌ 直接 returnWindows❌ 不编译原因这是给 正点原子 ARM 开发板 用的PC 上没有/dev/video1三、第一阶段打开摄像头设备video_fd open(VIDEO_DEV, O_RDWR);VIDEO_DEV/dev/video1成功 → 返回文件描述符失败 → 打印错误并退出线程Linux 里摄像头 文件四、第二阶段设置摄像头参数格式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;意思一句话我要一个640×480、RGB565 格式的摄像头图像然后ioctl(video_fd, VIDIOC_S_FMT, fmt);把参数“塞进”摄像头驱动五、第三阶段申请缓冲区V4L2 核心req_bufs.count 3; req_bufs.memory V4L2_MEMORY_MMAP;告诉驱动我要 3 个帧缓冲用 mmap 方式ioctl(video_fd, VIDIOC_REQBUFS, req_bufs);这一步只是“申请”还没拿到地址六、第四阶段mmap 映射缓冲区重点ioctl(video_fd, VIDIOC_QUERYBUF, buf); 查询第n_buf个 buffer 的信息bufs_info[n_buf].start mmap(...);把“摄像头内部缓冲区”映射到用户空间 结果是摄像头DMA → 内核buffer → mmap → 用户指针七、第五阶段把缓冲区“还给驱动”ioctl(video_fd, VIDIOC_QBUF, buf); 意思是“我准备好了你可以往这个 buffer 里放数据了”八、第六阶段启动摄像头采集ioctl(video_fd, VIDIOC_STREAMON, type);摄像头开始工作九、第七阶段真正的“视频循环”核心while (startFlag) 只要外部没关线程就一直采集1️⃣ 取出一帧DQBUFioctl(video_fd, VIDIOC_DQBUF, buf);驱动说这一帧你可以用了2️⃣ 把 raw 数据 → Qt 的 QImage关键桥梁QImage qImage( (unsigned char*)bufs_info[n_buf].start, 640, 480, QImage::Format_RGB16 );这一步非常重要摄像头给的是RGB565Qt 用QImage::Format_RGB16没有拷贝数据只是“包装”3️⃣ 本地显示信号槽if (startLocalDisplay) emit imageReady(qImage); 子线程不能直接操作 UI 用信号通知主线程emit imageReady → QLabel::setPixmap4️⃣ UDP 广播图像发网络if (startBroadcast)步骤拆解① 创建 UDP 套接字QUdpSocket udpSocket;② QImage → JPEG → QByteArrayqImage.save(buff, JPEG); 原始 RGB 太大 → 压缩成 JPEG③ Base64防止乱码QByteArray base64Byte byte.toBase64();④ 广播发送udpSocket.writeDatagram( base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888 );这就是“视频服务器”的核心5️⃣ 用完后再放回驱动QBUFioctl(video_fd, VIDIOC_QBUF, buf); 形成循环缓冲十、线程结束后的清理munmap(...) close(video_fd); 释放内存 关闭设备十一、整段代码一句话总结非常重要这个线程做的事就是 摄像头取一帧 包装成 QImage 要么发给 UI 显示 要么压缩后 UDP 广播 再把 buffer 还给驱动信号槽 线程调用关系图太好了 这一块一旦想通Qt 线程 信号槽你就算“过关”了。下面我给你不用 Qt 术语、用工程思维来讲再配一张“脑中必须有的调用关系图”。一、先给你一句“定理级结论”Qt 中run()里的代码在子线程UI 永远只能在主线程子线程 → UI 只能靠信号槽二、三种“角色”先分清非常重要┌────────────┐ │ 主线程(UI) │ ← QLabel / QPushButton / 界面 └─────┬──────┘ │ 信号槽 ┌─────▼──────┐ │ CaptureThread │ ← QThread 对象壳 └─────┬──────┘ │ run() ┌─────▼──────┐ │ 子线程执行体 │ ← 摄像头采集 └────────────┘重点CaptureThread 对象属于主线程run()里的代码运行在子线程三、从“按钮点击”开始看完整流程① 你在 UI 点了「开始采集」connect(button, QPushButton::clicked, captureThread, CaptureThread::setThreadStart);发生在主线程② 调用槽函数setThreadStart(true)void setThreadStart(bool start) { startFlag start; if (start) { if (!isRunning()) start(); // 关键 } }重点来了函数在哪个线程setThreadStart()主线程start()主线程run()子线程③start()干了什么很多人卡在这this-start();它不会执行 run() 它只是向操作系统申请一个新线程成功后OS 自动调用 run()四、run()才是真正的“子线程世界”void CaptureThread::run() { while (startFlag) { 采集一帧; emit imageReady(qImage); } }从这里开始不在 UI 线程不能碰 QLabel / QPushButton只能emit五、emit imageReady(qImage)到底发生了什么这是最关键的一步。1️⃣ 你在 main.cpp / widget.cpp 里一般会这样连connect(captureThread, CaptureThread::imageReady, this, Widget::updateImage); 连接时captureThread→ 子线程发信号this (Widget)→ 主线程对象2️⃣ Qt 自动判断“跨线程连接”信号发出线程 ≠ 槽函数对象线程Qt 自动使用Qt::QueuedConnection3️⃣ 这意味着什么非常重要子线程 emit imageReady ↓ Qt 把 QImage 放进 主线程事件队列 ↓ 主线程空闲时 ↓ 调用 updateImage(QImage)子线程不会等UI 不会卡死六、用一张“时间线图”彻底看懂时间 → ──────────────────────────────────────────── 主线程: [按钮点击] ↓ setThreadStart(true) ↓ start() ↓ (继续跑UI) 子线程: run()开始 ↓ 摄像头采集 ↓ emit imageReady ↓ (立刻继续采集) 主线程: Qt事件循环 ↓ updateImage() ↓ QLabel显示关键理解emit ≠ 直接调用emit “发消息”七、为什么 UI 不会崩考试必问❌ 错误做法会崩// 子线程里 ui-label-setPixmap(...); ❌✅ 正确做法你现在这个emit imageReady(qImage); ✅ UI 操作100% 在主线程八、你代码里的三种“信号控制关系”信号 / 槽控制方向setThreadStart()UI → 子线程setLocalDisplay()UI → 子线程imageReady()子线程 → UI非常标准的生产级写法九、用“单片机思维”类比给你兜底如果你把 Qt 当成 MCUQtMCU主线程main()子线程定时器中断signal中断置位slot中断回调中断不能直接改 LCD只能发标志位十、你现在应该“通”的 3 个点如果你点头说明真的懂了1️⃣CaptureThread 对象在主线程但 run 在子线程2️⃣emit 只是投递消息不是函数调用3️⃣跨线程信号槽 自动队列