v4l2+Qt实战:Linux摄像头采集、显示、截图与录像全流程
简介面向Qt与V4L2开发的实践资源适合需要在Linux环境下完成USB/CSI摄像头采集、实时显示、截图与录像的嵌入式或桌面应用开发者。资源以v4l2框架拉取640×480视频流转换为QImage后在QLabel/QWidget中呈现并结合下位机串口通信可按外部命令触发截图和录像保存。压缩包共225个文件主体为136个h头文件、30个c源文件与8个cpp文件另含ffmpeg相关so动态库、pc配置及x264编码库可直接支撑Linux下的视频编码与封装流程。包体约68.74MB已有272人学习使用适合用来理解V4L2采集、Qt显示与串口联调的整体链路。借助目录中的readme、ui设计文件和工程pro配置可快速定位采集显示模块与录像保存逻辑节省从驱动层到界面层的对接时间。1. v4l2拉流 Qt 显示视频(640*480)并可截图 与保存视频(录像)先讲清楚这套方案解决什么接手过某公司一个桌面端模拟项目XLinux 工控机上要显示 USB 摄像头画面界面用 Qt 做640480 的分辨率除了实时预览还得支持随时截图留证、手动录像回放。需求拆开就是标题这句话——v4l2 负责从内核拿帧Qt 负责把帧画出来截图和录像在用户态各自落盘。这套组合在 Linux 下的桌面摄像头应用里几乎是最常见、最稳的起点因为 v4l2 是内核自带框架Qt 的 QImage 又能直接吃内存像素不需要引入重型播放链路。适合谁看手里有 Qt 基础、想在 Linux 下把摄像头采集到显示再到存盘走通一遍的嵌入式或桌面开发者。分辨率为什么不选 1080p因为 640480 是大多数 US 摄像头驱动的默认出图带宽占用小、调试成本低把这条链路跑通了换高分辨率只是改几个枚举值的问题。2. v4l2 设备初始化从打开 /dev/video0 到配好 640*480 参数2.1 先别急着写代码用 v4l2-ctl 摸清摄像头底细拿到一个新摄像头我的习惯是先用命令行工具确认它到底支持什么格式而不是直接往代码里写V4L2_PIX_FMT_YUYV。很多 USB 摄像头默认出 MJPEG你以为设置 YUYV 成功了实际驱动悄悄换成了别的格式或者只支持 320*240。先跑两条命令ls -l /dev/video* v4l2-ctl --device/dev/video0 --list-formats-ext逻辑说明v4l2-ctl来自 v4l-utils 软件包--list-formats-ext会列出设备支持的像素格式、分辨率区间和帧率。输出里能看到类似[0]: YUYV (YUYV 4:2:2) 640x480这样的行说明 640*480 下的原生格式是 YUYV如果只有MJPG那驱动层面无法直接给你 YUYV 原始帧后续要么接受 MJPEG 解压要么找驱动参数协商。参数含义-d指定设备节点--list-formats-ext是只读查询不会改变设备状态可以放心跑。2.2 打开设备并设置采集格式VIDIOC_S_FMT 的正确用法设置格式是整个链路里最容易出“玄学”问题的环节——你以为设置成功了摄像头其实没理你。v4l2 规范允许驱动修改你提交的格式参数所以必须读回实际结果。我一般这样写#include linux/videodev2.h #include sys/ioctl.h #include fcntl.h #include unistd.h #include cstring #include cstdio int fd open(/dev/video0, O_RDWR); if (fd 0) { perror(open /dev/video0); return -1; } struct v4l2_format fmt; memset(fmt, 0, sizeof(fmt)); fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width 640; fmt.fmt.pix.height 480; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field V4L2_FIELD_NONE; // 渐进扫描隔行采集这里会出问题 if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { perror(VIDIOC_S_FMT); close(fd); return -1; } // 读回实际协商后的格式驱动可能改尺寸和像素格式 ioctl(fd, VIDIOC_G_FMT, fmt); printf(actual: %ux%u, pixelfmt0x%08x\n, fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.pixelformat);逻辑说明VIDIOC_S_FMT是设置格式的 ioctlfmt.fmt.pix里的 width、height、pixelformat 三项是核心。设置完必须再用VIDIOC_G_FMT读一次对比是否真的拿到了 640*480。参数说明V4L2_PIX_FMT_YUYV表示每个像素 2 字节Y 分量和 UV 分量交错存放V4L2_FIELD_NONE表示不隔行现代 USB 摄像头基本都是这个值。2.3 申请帧缓冲并映射REQBUFS mmap 的完整配合v4l2 的采集模式里我常用 mmap 方式驱动在内核里准备几块缓冲区用户态通过 mmap 映射后直接读内存。这样做的好处是零拷贝采集循环里不需要 malloc 新内存。申请缓冲和映射的代码struct v4l2_requestbuffers req; memset(req, 0, sizeof(req)); req.count 4; // 环形缓冲数量4 足够在 640*480 下平滑采集 req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, req) 0) { perror(VIDIOC_REQBUFS); return -1; } // 每块缓冲都需要单独映射 void* buf_base[4] {nullptr}; size_t buf_len[4] {0}; struct v4l2_buffer buf; for (int i 0; i 4; i) { memset(buf, 0, sizeof(buf)); buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; buf.index i; ioctl(fd, VIDIOC_QUERYBUF, buf); buf_len[i] buf.length; // buf.m.offset 是驱动给出的页对齐偏移mmap 时必须用它 buf_base[i] mmap(nullptr, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); // 映射后立刻入队让驱动知道这块缓冲可以写入数据 ioctl(fd, VIDIOC_QBUF, buf); }逻辑说明VIDIOC_REQBUFS只是申请缓冲数量真正拿到地址要靠QUERYBUF查询m.offset再用mmap映射到用户态。buf.index是缓冲编号环形队列里靠它区分哪块缓冲准备好了。参数说明req.count在 3 到 5 之间比较合适太少容易在显示慢时丢帧太多占内存640*480 YUYV 每帧约 614KB4 块也就 2.4MB。这里的PROT_READ | PROT_WRITE表示映射区域可读可写MAP_SHARED让内核与用户态共享同一块物理内存。2.4 启动采集循环STREAMON 之后反复 DQBUF/QBUF缓冲全部入队后用VIDIOC_STREAMON开启数据流。采集循环就是个“取出→处理→放回”的闭环处理阶段就是后面 Qt 显示和截图录像的入口。核心代码enum v4l2_buf_type type V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMON, type); while (g_running) { struct v4l2_buffer buf; memset(buf, 0, sizeof(buf)); buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; // DQBUF 阻塞等待一帧就绪取出的缓冲被驱动标记为“用户态持有” if (ioctl(fd, VIDIOC_DQBUF, buf) 0) { if (errno EAGAIN) continue; // 非阻塞模式下无帧可用 break; } // buf.index 表示这次拿到了哪块缓冲 // buf.bytesused 是实际数据长度640*480 YUYV 应为 640*480*2 uint8_t* frame_data static_castuint8_t*(buf_base[buf.index]); process_frame(frame_data, buf.bytesused); // 处理完必须 QBUF 还回去否则几轮后系统无缓冲可用画面卡死 ioctl(fd, VIDIOC_QBUF, buf); } type V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMOFF, type);逻辑说明DQBUF是出队把驱动填好数据的缓冲交给用户态处理完用QBUF入队归还。这个循环的节奏决定了帧率如果process_frame耗时超过一帧间隔采集就会掉帧。我在 projectX 里把process_frame做得很轻——只拷贝一份 QImage 的内存并发出信号不在这里调用 Qt 的刷新操作。参数说明buf.bytesused在突发错误时可能变成 0处理前要判断EAGAIN只在非阻塞模式出现STREAMOFF是停止采集退出循环后必须调用否则再次打开设备会报设备忙。3. Qt 显示视频帧YUV 转 RGB 与刷新架构的两种写法3.1 架构选择采集线程 信号槽而不是在循环里刷 UI新手最容易踩的坑是在process_frame里直接调用ui-label-setPixmap()。这个操作会卡住采集循环UI 线程又被 Qt 事件循环占着两边互相等画面帧率直接下来。正确做法是把采集放进独立线程线程拿到帧后转换成QImage通过信号槽通知主线程刷新。我先定义一个采集线程类class CaptureThread : public QThread { Q_OBJECT public: CaptureThread(int fd, void** buf_base, size_t* buf_len) : m_fd(fd), m_buf_base(buf_base), m_buf_len(buf_len), m_running(true) {} void stop() { m_running false; } signals: void frameReady(const QImage frame); protected: void run() override { while (m_running) { // DQBUF、转换 QImage、调用 emit frameReady(img) // 发射信号时 Qt 会把 QImage 拷贝到主线程事件队列 } } };设置好信号槽连接后主线程只需要QLabel::setPixmap。信号参数用const QImage是常见做法Qt 的跨线程队列连接会做深拷贝避免消费者用完后生产者还在改内存。我自己习惯在信号参数前加const这是血泪经验——不加修饰的引用在跨线程队列里容易被误写成直连变成隐式共享画面出现半个帧的残影。3.2 YUYV 转 RGB32自己写转换函数还是用库v4l2 给出来的 YUYV 是 4:2:2 采样Qt 不认这种格式必须转成QImage::Format_RGB32或Format_RGB888。用 ffmpeg 的 swscale 代价太大为了 640*480 引入整个解码库不划算。我直接写一个精简转换每两个 Y 加一个 U、一个 V 组成四个像素点void convert_yuyv_to_rgb32(const uint8_t* yuyv, uint32_t* rgb, int width, int height) { // 640*480 满帧时 yuyv 长度 width * height * 2 for (int y 0; y height; y) { for (int x 0; x width; x 2) { int i (y * width x) * 2; int y0 yuyv[i] - 16; int y1 yuyv[i 2] - 16; int u yuyv[i 1] - 128; int v yuyv[i 3] - 128; // 标准 BT.601 公式注意符号处理 int r (298 * y0 409 * v 128) 8; int g (298 * y0 - 100 * u - 208 * v 128) 8; int b (298 * y0 516 * u 128) 8; rgb[y * width x] 0xFF000000 | clamp(r) 16 | clamp(g) 8 | clamp(b); r (298 * y1 409 * v 128) 8; g (298 * y1 - 100 * u - 208 * v 128) 8; b (298 * y1 516 * u 128) 8; rgb[y * width x 1] 0xFF000000 | clamp(r) 16 | clamp(g) 8 | clamp(b); } } }讲解y0/y1是两个亮度分量u/v是该像素对共用的色度分量。BT.601 的系数是给标清视频用的640480 正合适。这里的clamp()是自己写的限幅函数把值夹在 0~255 之间不做会出颜色溢出。性能上这个双层循环在 640480 下约 30 万次迭代现代桌面 CPU 单线程轻松跑满 30fps如果换 1080p这套朴素循环会吃力到时候再考虑 SIMD 或库。3.3 刷新节流控制 setPixmap 的频率采集线程按摄像头的帧率发信号比如 30fps但 UI 刷新不一定需要这么高频。如果主循环里同时跑录像、截图、按钮事件每秒 30 次setPixmap会让 Qt 的绘制线程很忙。我习惯做一层节流记录上次显示时间超过 33ms约 30fps才刷新或者把采集帧率固定到 25fps这样录像按 25fps 落盘时帧间隔均匀不会时快时慢。关键思路是采集、显示、录像三个频率互相解耦各自用各自的时间基准。// 主线程槽函数 void MainWindow::onFrameReady(const QImage img) { // 节流到 30fps避免高帧率摄像头拖累界面 qint64 now QDateTime::currentMSecsSinceEpoch(); if (now - m_lastShowMs 33) return; m_lastShowMs now; m_currentFrame img; // 深拷贝一份供截图按钮使用 ui-label-setPixmap(QPixmap::fromImage(img.scaled(ui-label-size(), Qt::KeepAspectRatio, Qt::SmoothTransformation))); }scaled是必要的因为标签控件往往不是正好 640*480。参数说明Qt::KeepAspectRatio防止画面拉伸变形Qt::SmoothTransformation会让缩放时走双线性插值显示效果比快速变换好代价是每帧多几毫秒 CPU。这里存储m_currentFrame时是深拷贝截图按钮拿到的是完整帧不受scaled影响。4. 截图与录像保存从帧缓存到 PNG/MP4 的完整链路4.1 截图从 QImage 到磁盘文件的两种路径截图最直接的方式是按钮槽函数里把m_currentFrame.save()写到磁盘因为m_currentFrame是我们自己维护的深拷贝安全可靠。另一种做法从QLabel的当前 pixmap 抠图ui-label-pixmap().toImage()但这个图是被scaled过的尺寸是控件大小不是原始 640*480除非你刻意要截图所见即所得否则不推荐。我保留两条路径默认走第一路void MainWindow::onSnapshotBtn() { // 取当前帧加时间戳做文件名避免覆盖 QImage snap m_currentFrame; // 引用计数浅拷贝但 m_currentFrame 不被修改安全 QString name QString::fromLocal8Bit(snap_%1.png) .arg(QDateTime::currentMSecsSinceEpoch()); if (snap.save(name, PNG)) { statusBar()-showMessage(QString(截图已保存: %1).arg(name)); } }save的第二参数指定格式不传的话 Qt 根据后缀推PNG 在 Linux 下默认由 Qt 的插件支持。场景里若对截图的实时性要求高可以给采集线程加一个captureRequested标志一旦收到按钮信号下一帧到达时直接保存原始转出的 QImage而不是等主线程槽函数慢慢深拷贝。640*480 的 PNG 大约 200~400KB生成速度在毫秒级普通按钮逻辑足够。4.2 录像方案选型原始帧直存 vs 压缩编码录像保存比截图多一层取舍存什么格式、压不压缩。我先给三种方案的对比这是在做之前一定要想清楚的成本账方案格式640*480 一帧大小1 分钟文件约CPU 占用实现复杂度原始 YUYV 写入 AVI无压缩~614KB~35GB极低低自己做容器帧写入OpenCV VideoWriter MJPGMJPG 压缩15~40KB约 1GB中低依赖开源视觉库ffmpeg/libx264 H.264H.2645~20KB300~600MB高高要管编码器状态原始帧直存在调试阶段很值因为帧内容和摄像头输出完全一致方便排除编码器带来的干扰但 35GB/分钟对磁盘压力太大我通常只用来验证采集链路完整性。MJPG 方案是 640*480 场景里最平衡的压缩率够、CPU 不高、代码量少。H.264 文件小但低码率下画面细节丢失多而且编码器初始化、PTS 管理都要自己处理对本需求来说属于“杀鸡用牛刀”。我的选择默认使用 MJPG 编码需要无损调试时切换原始帧直存。4.3 用 OpenCV VideoWriter 保存QImage 到 cv::Mat 的桥选 MJPG 的情况下常见做法是用开源视觉库的VideoWriter封装省掉自己写 AVI 容器的时间。关键是把 QImage 转成cv::Mat——注意 QImage 的内存行字节数可能是对齐的不能直接memcpy。我写一个转换函数cv::Mat qimage_to_mat(const QImage img) { // QImage::Format_RGB32 在内存里是 0xAARRGGBBOpenCV 默认 BGR 顺序不对 QImage converted img.convertToFormat(QImage::Format_RGB888); // convertToFormat 保证数据连续这里可以直接包 Mat return cv::Mat(converted.height(), converted.width(), CV_8UC3, const_castuchar*(converted.bits()), converted.bytesPerLine()).clone(); }录像写入的主循环里每帧采集信号到达时按需写入而不是在采集线程里同步阻塞写磁盘void MainWindow::startRecording() { m_writer.open(record.avi, cv::VideoWriter::fourcc(M,J,P,G), 25, cv::Size(640, 480)); if (!m_writer.isOpened()) { QMessageBox::warning(this, 录像, 打开 VideoWriter 失败检查编码器支持); } } void MainWindow::onFrameReady(const QImage img) { // ...显示逻辑... if (m_writer.isOpened()) { m_writer.write(qimage_to_mat(img)); } }参数说明fourcc(M,J,P,G)在 Linux 下依赖本地安装的 MJPG 编码器一些精简版系统没有isOpened()会返回 false帧率 25fps 要和实际采集帧率一致如果摄像头是 30fps 而你写 25fps播放速度会比真实时间快 20%。clone()是必需的因为converted一旦离开函数作用域内存就释放了直接返回 Mat 是悬垂指针我第一次写这里没 clone录像后半段全是绿条排查了一晚上。4.4 截图与录像并发互斥锁保护帧数据截图和录像同时进行时两个模块都要读m_currentFrame。我在主线程的事件循环里槽函数是串行执行的不真正并发但如果你把录像写入挪到录像线程就得加锁。稳妥做法是用一个QMutex保护所有对当前帧的读写void MainWindow::onFrameReady(const QImage img) { QMutexLocker locker(m_frameMutex); m_currentFrame img; } void MainWindow::onSnapshotBtn() { QMutexLocker locker(m_frameMutex); QImage snap m_currentFrame; locker.unlock(); // save 不需要锁文件 IO 不涉及共享数据 snap.save(...); }QMutexLocker是 RAII 风格构造函数加锁析构自动解锁。unlock()调用后手动释放锁是给 save 让路避免持锁做磁盘操作拖慢信号槽队列。这个并发场景在纯单线程下看不出来但我习惯先把锁加上因为后期一旦有人为了性能把录像挪到独立线程这个锁就是后悔药。5. v4l2 Qt 避坑排查五个高频翻车点与处理记录5.1 画面绿屏或严重花屏现象预览窗口出现满屏绿色或者画面像被撕裂一样横向条纹错位。原因像素格式对不上。摄像头实际输出 MJPEG你按 YUYV 解析数据完全不是同一套编码另一个常见原因是 YUYV 和 UYVY 的字节序搞混了两者只差 U/V 分量位置转换公式却完全不同。解决先跑v4l2-ctl --list-formats-ext确认默认格式在代码里VIDIOC_G_FMT读回实际 pixelformat打印出来对比。如果设备只支持 MJPG就先用libjpeg解压不要硬解。字节序问题就是调换取 u、v 的下标偏移。5.2 打开 /dev/video0 失败设备占用与权限现象open(/dev/video0, O_RDWR)返回 -1perror 显示Permission denied或Device or resource busy。原因当前用户不在 video 组或者另一个进程已占用摄像头——调试时最容易踩的是之前程序异常退出了没有 STREAMOFF驱动认为设备还在采集。解决权限问题执行usermod -aG video $USER后重新登录占用问题用fuser -v /dev/video0查是哪个进程占用的kill掉它程序里要注册信号处理函数收到 SIGINT/SIGTERM 时先 STREAMOFF 再退出防止下次打开卡死。5.3 预览卡顿帧率明显低于摄像头标称值现象30fps 的摄像头界面看着只有 15fps 左右鼠标拖动窗口时掉帧更严重。原因采集循环和 UI 刷新互相抢占主线程信号槽里频繁做 QImage 深拷贝scaled平滑缩放每次都触发完整重采样。解决采集线程保持独立信号槽参数不用QImage改用std::shared_ptrQImageQt 队列连接会复制智能指针不会复制像素数据或者主线程槽里只接收后做一次浅拷贝scaled结果缓存只有控件尺寸变化时才重新缩放。另外检查是否开了垂直同步Qt 在 Linux 下默认走 OpenGL 渲染垂直同步会卡刷新率。5.4 录像文件播放时后半段花屏或时长不对现象AVI 文件用播放器打开前几秒正常后面画面变成马赛克块或者总时长比实际录的时间少。原因MJPG 编码的关键帧间隔和写入频率不一致OpenCV 的VideoWriter内部缓冲没刷盘程序异常退出时文件尾部数据丢失QImage 转换cv::Mat时没有clone()导致写入的像素地址在帧消失后变成随机值这是最常见的“黑匣子”式损坏。解决录像停止时先调用m_writer.release()确保编码器 flush转换 Mat 时必须clone()关键帧间隔在VideoWriter::set(cv::VIDEOWRITER_PROP_QUALITY, 90)之外还可以用cv::imencode单独控制质量避免默认压缩过狠。5.5 程序退出崩溃QThread 析构与 mmap 未释放现象点击关闭窗口后程序卡住或者直接 segmentation fault终端里打印QThread: Destroyed while thread is still running。原因CaptureThread的run()里 while 循环还在 DQBUF 阻塞等待主线程关闭窗口直接把线程对象析构了而 fd 和 mmap 区域在退出前没有 munmap 和 close内核在进程退出时强制回收导致缓冲队列状态混乱。解决主窗口closeEvent里先调captureThread-stop()设置原子标志位让DQBUF超时退出随后captureThread-wait(500)等线程真正停止最后才释放 mmap 区域和 fd。注意DQBUF在阻塞模式下不会立刻返回需先STREAMOFF它才会被信号打断。6. 进阶调试技巧用丢帧计数和 PTS 校准帧率给方案兜底6.1 加一个轻量 FPS 计数器验证采集到显示的真实帧率光看画面“感觉流畅”不算数我做了一个简单的统计模块采集线程每次 DQBUF 成功就计数主线程每秒刷新标签显示实时帧率。核心逻辑就是一个滑动窗口class FpsCounter { public: void tick() { qint64 now QDateTime::currentMSecsSinceEpoch(); m_count; if (now - m_lastShowMs 1000) { double fps m_count * 1000.0 / (now - m_lastShowMs); qDebug() capture fps: fps dropped: m_dropped; m_count 0; m_lastShowMs now; } } private: qint64 m_lastShowMs 0; int m_count 0; int m_dropped 0; };把这个计数器放在 DQBUF 之后的process_frame入口画面卡不卡、实际采集帧率多少一眼就能看出来。我自己的使用习惯是接入新摄像头时先跑 2 分钟如果计数帧率比标称低 10% 以上优先排查是不是process_frame耗时超了帧间隔或者 USB 带宽被其他设备挤占。这个习惯帮我提前发现了两个标称 60fps 实际只能跑到 42fps 的摄像头避免录像时间轴偏快。6.2 用 v4l2_buffer 的 timestamp 做帧间隔统计比 FPS 计数器更准的是看相邻两帧的时间差。struct v4l2_buffer里的timestamp字段是内核打的时间戳单位随V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC标志精度是微秒。我在采集循环里记录上一帧的buf.timestamp.tv_usec相减后就能看到每帧间隔是否均匀。如果某段时间间隔忽大忽小说明 USB 传输不稳录像文件里对应的位置就会掉帧。这个数据比 FPS 平均值更直观平均值掩盖了掉帧的瞬间而逐帧间隔会把偶发卡顿暴露出来。6.3 断流重连摄像头掉线后的自恢复生产环境里摄像头偶尔会断流比如 USB 线虚接、驱动复位。我在process_frame里加了一个超时判断如果DQBUF连续 2 秒返回 EAGAIN就主动走一遍完整的停止流程——STREAMOFF、munmap、close然后sleep(1)重新 open、REQBUFS、STREAMON。注意重新打开时格式参数要重新设置一遍摄像头驱动复位后很多参数会回到默认值我之前漏了这步重连成功后画面颜色全偏。这个自恢复逻辑放在一个独立的管理线程里不阻塞 UI 刷新。调试链路走到这里回头总结一条规律v4l2 采集层的问题九成能通过v4l2-ctl命令先定位Qt 显示层的问题九成出在跨线程刷新和内存生命周期上而截图录像的坑集中在编码器和容器格式的选择上。每次接新摄像头我都会先用这套组合验证一遍再开始写业务逻辑目前为止翻车的概率很低。如果你正卡在某个环节建议先按上述顺序逐个排查——尤其先跑v4l2-ctl --list-formats-ext再动手写代码这条能帮你省下大半排查时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

自研Vue3上传视频组件:分片断点续传与封面截帧实战

自研Vue3上传视频组件:分片断点续传与封面截帧实战

前阵子做视频管理后台的时候,需要自己实现一个 Vue上传视频组件。一开始我也想着直接用现成的通用上传组件改一改,结果越改越不对劲——视频文件体积大、格式杂,光“选文件-传完-拿URL”这一套根本不够用。还需要校验时长、截封面帧、算整体进…

2026/10/15 3:28:40 阅读更多 →
SPFA算法详解:三种图存储方式的C++实现与负权环检测

SPFA算法详解:三种图存储方式的C++实现与负权环检测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 3:28:40 阅读更多 →
claude-desktop-debian 安装上手:5 分钟装进你的 Linux 桌面

claude-desktop-debian 安装上手:5 分钟装进你的 Linux 桌面

claude-desktop-debian 安装上手:5 分钟装进你的 Linux 桌面 【免费下载链接】claude-desktop-debian Claude Desktop for Linux 项目地址: https://gitcode.com/GitHub_Trending/cl/claude-desktop-debian claude-desktop-debian 把 Claude Desktop 装进 De…

2026/10/15 3:27:40 阅读更多 →

最新新闻

IDEA 2025.3 新功能实战:多光标、调试数据流与临时文件提升编码效率

IDEA 2025.3 新功能实战:多光标、调试数据流与临时文件提升编码效率

IDEA 2025.3 的更新日志刚出来那会儿,我真没当回事,毕竟每个大版本都爱说“更快、更智能、更好用”。直到上周把开发环境从旧版本升上来,试了一圈新功能,才意识到这波“骚操作”是真的多,甚至有点跟不上了。这篇文章不…

2026/10/15 4:27:50 阅读更多 →
贝叶斯网络学习指南:从结构、参数到推断的完整实践

贝叶斯网络学习指南:从结构、参数到推断的完整实践

简介:《贝叶斯网络学习指南》是一份面向机器学习、概率建模与数据科学初学者的PDF电子书,系统覆盖从概率论基础到贝叶斯网络建模、推理与参数学习的完整知识链。全书从概率函数与空间、条件概率与独立性、贝叶斯定理、随机变量及联合概率分布讲起&#x…

2026/10/15 4:27:50 阅读更多 →
STM32F072C8T6裸机开发实战:从寄存器到USB/CAN外设

STM32F072C8T6裸机开发实战:从寄存器到USB/CAN外设

简介:这份资料面向嵌入式初学者与有一定经验的开发者,围绕意法半导体STM32F072C8T6(ARM Cortex-M0内核、48MHz主频、32KB闪存与2KB RAM)学习板展开,帮助读者快速掌握该芯片的初始化配置、中断处理、内存管理及驱动编写…

2026/10/15 4:27:50 阅读更多 →
C/C++不同位数数据互转的进制转换实现与避坑指南

C/C++不同位数数据互转的进制转换实现与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 4:27:50 阅读更多 →
NC65销售订单批量导入全攻略:从模板准备到成功入库的避坑指南

NC65销售订单批量导入全攻略:从模板准备到成功入库的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 4:27:50 阅读更多 →
Linux下C/C++动静态库制作与使用实战指南

Linux下C/C++动静态库制作与使用实战指南

长期以来在Linux下做C/C开发,动静态库始终是绕不开的一个核心技能。无论是自己封装模块、分发二进制产物,还是处理第三方依赖,理解库的制作与使用直接决定了你在编译链接这块能不能少掉头发。我见过不少开发者写业务逻辑很熟练,一…

2026/10/15 4:26:50 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →