当前位置: 首页 > news >正文

Linux和Windows基于V4L2和TCP的QT监控

最近工作需要用QT做一个网络摄像头测试,简单记录:

服务端,主机配置为Ubuntu,通过端口12345采集传输MJPEG格式图片

windows客户端,QT Creator通过ip地址连接访问

提前准备

服务端需要安装QT5

sudo apt-get install qt5-default

g++

qmake

客户端需要安装Qt Creator

服务端代码

cameraserver.cpp

#include "cameraserver.h"CameraServer::CameraServer(QObject *parent) : QObject(parent), camera_fd(-1), buffer(nullptr), buffer_length(0)
{server = new QTcpServer(this);connect(server, &QTcpServer::newConnection, this, &CameraServer::newConnection);if (!server->listen(QHostAddress::Any, 12345)) {qDebug() << "Server could not start!";} else {qDebug() << "Server started on port 12345";}// 打开摄像头camera_fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);if (camera_fd == -1) {qDebug() << "Failed to open camera:" << strerror(errno);return;}// 设置摄像头格式struct v4l2_format format;memset(&format, 0, sizeof(format));format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;format.fmt.pix.width = 640;format.fmt.pix.height = 480;format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;format.fmt.pix.field = V4L2_FIELD_NONE;if (ioctl(camera_fd, VIDIOC_S_FMT, &format) == -1) {qDebug() << "Failed to set camera format:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}// 请求缓冲区struct v4l2_requestbuffers req;memset(&req, 0, sizeof(req));req.count = 1;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_MMAP;if (ioctl(camera_fd, VIDIOC_REQBUFS, &req) == -1) {qDebug() << "Failed to request buffers:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}// 映射缓冲区struct v4l2_buffer buf;memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0;if (ioctl(camera_fd, VIDIOC_QUERYBUF, &buf) == -1) {qDebug() << "Failed to query buffer:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camera_fd, buf.m.offset);if (buffer == MAP_FAILED) {qDebug() << "Failed to mmap buffer:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}buffer_length = buf.length;// 将缓冲区加入队列if (ioctl(camera_fd, VIDIOC_QBUF, &buf) == -1) {qDebug() << "Failed to queue buffer:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}// 开启视频流enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(camera_fd, VIDIOC_STREAMON, &type) == -1) {qDebug() << "Failed to start streaming:" << strerror(errno);close(camera_fd);camera_fd = -1;return;}// 设置定时器timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &CameraServer::captureFrame);timer->start(33); // ~30 FPS
}CameraServer::~CameraServer()
{if (camera_fd != -1) {// 停止视频流enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(camera_fd, VIDIOC_STREAMOFF, &type);if (buffer != MAP_FAILED && buffer != nullptr) {munmap(buffer, buffer_length);}close(camera_fd);}
}void CameraServer::newConnection()
{QTcpSocket *socket = server->nextPendingConnection();qDebug() << "Client connected:" << socket->peerAddress().toString();clients.append(socket);connect(socket, &QTcpSocket::disconnected, this, [this, socket]() {qDebug() << "Client disconnected";clients.removeOne(socket);socket->deleteLater();});
}void CameraServer::captureFrame()
{if (camera_fd == -1 || clients.isEmpty()) return;struct v4l2_buffer buf;memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;// 出队缓冲区if (ioctl(camera_fd, VIDIOC_DQBUF, &buf) == -1) {if (errno != EAGAIN) {qDebug() << "Failed to dequeue buffer:" << strerror(errno);}return;}// 发送帧数据for (QTcpSocket *client : clients) {if (client->state() == QAbstractSocket::ConnectedState) {// 发送帧大小quint32 size = buf.bytesused;client->write(reinterpret_cast<const char*>(&size), sizeof(size));// 发送帧数据client->write(reinterpret_cast<const char*>(buffer), size);}}// 重新入队缓冲区if (ioctl(camera_fd, VIDIOC_QBUF, &buf) == -1) {qDebug() << "Failed to queue buffer:" << strerror(errno);}
}

cameraserver.h

#ifndef CAMERASERVER_H
#define CAMERASERVER_H#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <QList>
#include <QDebug>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <cstring>
#include <cerrno>class CameraServer : public QObject
{Q_OBJECT
public:explicit CameraServer(QObject *parent = nullptr);~CameraServer();private slots:void newConnection();void captureFrame();private:QTcpServer *server;QList<QTcpSocket*> clients;int camera_fd;QTimer *timer;void *buffer;size_t buffer_length;
};#endif // CAMERASERVER_H

缓冲区增加声明

客户端QT代码

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QBuffer>
#include <QMessageBox>
#include <QHostAddress> // 添加头文件MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("Camera Stream Client");socket = new QTcpSocket(this);connect(socket, &QTcpSocket::connected, this, &MainWindow::on_socketConnected);connect(socket, &QTcpSocket::disconnected, this, &MainWindow::on_socketDisconnected);// 修复1: 使用兼容Qt 5.14的错误处理方式connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),this, &MainWindow::on_socketError);connect(socket, &QTcpSocket::readyRead, this, &MainWindow::on_socketReadyRead);// Set placeholder imageQImage placeholder(640, 480, QImage::Format_RGB888);placeholder.fill(Qt::darkGray);ui->imageLabel->setPixmap(QPixmap::fromImage(placeholder));ui->imageLabel->setScaledContents(true);ui->ipLineEdit->setText("192.168.1.100"); // Default IP
}MainWindow::~MainWindow() {delete ui;
}void MainWindow::on_connectButton_clicked() {if (socket->state() == QAbstractSocket::ConnectedState) {socket->disconnectFromHost();ui->connectButton->setText("Connect");ui->statusLabel->setText("Disconnected");return;}QString ip = ui->ipLineEdit->text();if (ip.isEmpty()) {QMessageBox::warning(this, "Error", "Please enter server IP address");return;}ui->connectButton->setText("Connecting...");ui->connectButton->setEnabled(false);socket->connectToHost(ip, 12345);
}void MainWindow::on_socketConnected() {ui->connectButton->setText("Disconnect");ui->connectButton->setEnabled(true);// 修复2: 使用正确的QHostAddress方法ui->statusLabel->setText("Connected to " + socket->peerAddress().toString());
}void MainWindow::on_socketDisconnected() {ui->connectButton->setText("Connect");ui->statusLabel->setText("Disconnected");
}// 修改错误处理函数的签名
void MainWindow::on_socketError(QAbstractSocket::SocketError error) {Q_UNUSED(error);ui->connectButton->setText("Connect");ui->connectButton->setEnabled(true);ui->statusLabel->setText("Error: " + socket->errorString());
}void MainWindow::on_socketReadyRead() {while (socket->bytesAvailable() > 0) {if (!readingFrame) {// Start reading a new frameif (socket->bytesAvailable() < static_cast<qint64>(sizeof(quint32))) {return; // Wait for more data}socket->read(reinterpret_cast<char*>(&frameSize), sizeof(quint32));readingFrame = true;}if (socket->bytesAvailable() < frameSize) {return; // Wait for the complete frame}// Read the complete frameQByteArray frameData = socket->read(frameSize);readingFrame = false;// Decode JPEG to QImagecurrentFrame = QImage::fromData(frameData, "JPEG");if (!currentFrame.isNull()) {ui->imageLabel->setPixmap(QPixmap::fromImage(currentFrame));ui->statusLabel->setText(QString("Received frame: %1x%2").arg(currentFrame.width()).arg(currentFrame.height()));}}
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QTcpSocket>
#include <QImage>
#include <QLabel>
#include <QHostAddress> // 添加头文件QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_connectButton_clicked();void on_socketConnected();void on_socketDisconnected();void on_socketError(QAbstractSocket::SocketError error); // 保持原签名void on_socketReadyRead();private:Ui::MainWindow *ui;QTcpSocket *socket;QImage currentFrame;bool readingFrame = false;quint32 frameSize = 0;
};
#endif // MAINWINDOW_H

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"><class>MainWindow</class><widget class="QMainWindow" name="MainWindow"><property name="geometry"><rect><x>0</x><y>0</y><width>800</width><height>600</height></rect></property><property name="windowTitle"><string>Camera Stream Client</string></property><widget class="QWidget" name="centralwidget"><layout class="QVBoxLayout" name="verticalLayout"><item><widget class="QFrame" name="frame"><property name="frameShape"><enum>QFrame::StyledPanel</enum></property><property name="frameShadow"><enum>QFrame::Raised</enum></property><layout class="QHBoxLayout" name="horizontalLayout"><item><widget class="QLabel" name="label"><property name="text"><string>Server IP:</string></property></widget></item><item><widget class="QLineEdit" name="ipLineEdit"/></item><item><widget class="QPushButton" name="connectButton"><property name="text"><string>Connect</string></property></widget></item></layout></widget></item><item><widget class="QLabel" name="imageLabel"><property name="minimumSize"><size><width>640</width><height>480</height></size></property><property name="frameShape"><enum>QFrame::Box</enum></property><property name="text"><string>No Image</string></property><property name="alignment"><set>Qt::AlignCenter</set></property></widget></item><item><widget class="QLabel" name="statusLabel"><property name="text"><string>Disconnected</string></property></widget></item></layout></widget></widget><resources/><connections/>
</ui>

整体框架基于V4L2

演示效果

后续打算在RK3576上面跑一下,虽说arch64架构的Ubuntu,移植QT应该会遇到一些阻力,不过可以期待一下

http://www.lryc.cn/news/597934.html

相关文章:

  • 【MAC电脑系统变量管理】
  • 进程调度的艺术:从概念本质到 Linux 内核实现
  • n8n AI资讯聚合与分发自动化教程:从数据获取到微信与Notion集成
  • RabbitMQ--消息顺序性
  • 【华为】笔试真题训练_20250611
  • go下载包
  • 数据库常用DDL语言
  • 文件管理困境如何破?ZFile+cpolar打造随身云盘新体验
  • M²IV:面向大型视觉-语言模型中高效且细粒度的多模态上下文学习
  • RabbitMQ简述
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-16,(知识点:电平转换电路)
  • RabbitMQ—仲裁队列
  • <论文>(斯坦福)DSPy:将声明式语言模型调用编译为自我优化的pipeline
  • 等保二级、三级配置表(阿里云)
  • RuoYi-Vue 项目 Docker 全流程部署实战教程
  • 【LeetCode数据结构】二叉树的应用(一)——单值二叉树问题、相同的树问题、对称二叉树问题、另一棵树的子树问题详解
  • [数据结构]#6 树
  • JVM Java虚拟机
  • 【Qt开发】信号与槽(一)
  • 机器学习入门指南它来了
  • LeetCodeOJ题:回文链表
  • Java学习----原型模式
  • vant-field 显示radio
  • 【Java】空指针(NullPointerException)异常深度攻坚:从底层原理到架构级防御,老司机的实战经验
  • 高级 JAVA 工程师卷 1
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-20,(知识点:热阻的概念,散热)
  • 专题:2025微短剧行业生态构建与跨界融合研究报告|附100+份报告PDF汇总下载
  • Python 使用环境下编译 FFmpeg 及 PyAV 源码(英特尔篇)
  • 第4章唯一ID生成器——4.1 分布式唯一ID
  • 中小企业安全落地:低成本漏洞管理与攻击防御方案