CAMERAGST qt采集图像
#ifndef _H #define CAMERAGST_H #include <QObject> #include <QThread> class CameraGst : public QObject { Q_OBJECT public: explicit CameraGst(QObject *parent = nullptr); ~CameraGst() override; // 启动摄像头 bool startCamera(const QString &dev = "/dev/video11", int w=800, int h=600); void stopCamera(); // 获取一帧拷贝 QByteArray getFrameJpeg(); signals: // 新帧到来,UI线程接收 void newFrameReady(); void errorOccurred(QString errMsg); private: // 全部不透明指针,头文件看不到gst/opencv类型 void* m_pipeline = nullptr; void* m_appsink = nullptr; // 帧缓冲互斥,帧数据放到cpp内部 void* m_globalFrame = nullptr; void* m_frameMutex = nullptr; bool m_running = false; // gst静态C回调,不能是成员函数 static void gstNotifyNewSample(void* sink, void* userData); }; #endif // CAMERAGST_H
#include "cameragst.h" #include <gst/gst.h> #include <gst/app/gstappsink.h> #include <opencv2/opencv.hpp> #include <mutex> #include <QDebug> #include <QByteArray> CameraGst::CameraGst(QObject *parent) : QObject(parent) { m_frameMutex = new std::mutex(); m_globalFrame = new cv::Mat(); gst_init(nullptr, nullptr); } CameraGst::~CameraGst() { stopCamera(); delete reinterpret_cast<std::mutex*>(m_frameMutex); delete reinterpret_cast<cv::Mat*>(m_globalFrame); gst_deinit(); } // 静态C回调,GStreamer调用 void CameraGst::gstNotifyNewSample(void* sinkPtr, void* userData) { CameraGst* camObj = static_cast<CameraGst*>(userData); if(!camObj || !camObj->m_running) return; GstAppSink* appsink = static_cast<GstAppSink*>(sinkPtr); GstSample* sample = gst_app_sink_pull_sample(appsink); if (!sample) return; GstBuffer* buf = gst_sample_get_buffer(sample); GstCaps* caps = gst_sample_get_caps(sample); gint w{0}, h{0}; GstStructure* st = gst_caps_get_structure(caps,0); gst_structure_get_int(st, "width", &w); gst_structure_get_int(st, "height", &h); GstMapInfo map; if(gst_buffer_map(buf, &map, GST_MAP_READ)) { cv::Mat tempMat(h,w,CV_8UC3, map.data); std::mutex* mtx = reinterpret_cast<std::mutex*>(camObj->m_frameMutex); cv::Mat* dstMat = reinterpret_cast<cv::Mat*>(camObj->m_globalFrame); std::lock_guard<std::mutex> lock(*mtx); *dstMat = tempMat.clone(); gst_buffer_unmap(buf,&map); } gst_sample_unref(sample); // 投递信号,跨线程,回到Qt主线程 emit camObj->newFrameReady(); } bool CameraGst::startCamera(const QString &dev, int w, int h) { if(m_running) { qWarning()<<"摄像头已经启动"; return true; } std::string pipeStr = "v4l2src device="+dev.toStdString()+" ! " "queue max-size-buffers=4 ! " "video/x-raw,format=NV12,width="+std::to_string(w)+",height="+std::to_string(h)+" ! " "videoconvert ! " "queue max-size-buffers=4 ! " "video/x-raw,format=BGR ! " "appsink name=sink sync=false drop=true"; GstElement* pipeline = gst_parse_launch(pipeStr.c_str(), nullptr); if(!pipeline) { emit errorOccurred("gstreamer管道创建失败"); return false; } GstElement* appsink = gst_bin_get_by_name(GST_BIN(pipeline),"sink"); if(!appsink) { gst_object_unref(pipeline); emit errorOccurred("找不到appsink"); return false; } GstAppSinkCallbacks cb{}; cb.new_sample = reinterpret_cast<GstFlowReturn (*)(GstAppSink*,gpointer)>(gstNotifyNewSample); gst_app_sink_set_callbacks(GST_APP_SINK(appsink), &cb, this, nullptr); auto ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); if(ret == GST_STATE_CHANGE_FAILURE) { gst_object_unref(appsink); gst_object_unref(pipeline); emit errorOccurred("管道PLAYING启动失败"); return false; } m_pipeline = pipeline; m_appsink = appsink; m_running = true; qDebug()<<"摄像头启动成功 "<<dev; return true; } void CameraGst::stopCamera() { if(!m_running) return; m_running = false; GstElement* pipeline = static_cast<GstElement*>(m_pipeline); GstElement* appsink = static_cast<GstElement*>(m_appsink); if(pipeline) { gst_element_set_state(pipeline, GST_STATE_NULL); } if(appsink) gst_object_unref(appsink); if(pipeline) gst_object_unref(pipeline); m_pipeline = nullptr; m_appsink = nullptr; qDebug()<<"摄像头已停止"; } QByteArray CameraGst::getFrameJpeg() { std::mutex* mtx = reinterpret_cast<std::mutex*>(m_frameMutex); cv::Mat* mat = reinterpret_cast<cv::Mat*>(m_globalFrame); std::lock_guard<std::mutex> lock(*mtx); if(mat->empty()) return {}; std::vector<uchar> buf; cv::imencode(".jpg",*mat,buf); return QByteArray((const char*)buf.data(), (int)buf.size()); }
调用方法: #include "mainwindow.h" #include "ui_mainwindow.h" #include <QFileDialog> #include <QByteArray> #include <QMessageBox> #include <QHostAddress> #include <QBuffer> #include <QFile> #include <QPixmap> #include "RkImageServer.h" #include <QDateTime> #include <QScrollBar> #include "cfile_operation.h" #include <QTimer> #include <QtNetwork> #pragma execution_character_set("utf-8") //QWidget: Must construct a QApplication before a QWidget,提示表示只能运行一个exe,不能重复运行 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); //初始化服务端 cfile_operation = new CFile_Operation(); RkImageServer *rkServer = new RkImageServer(this); local_port ="15100"; rkServer->startServer(local_port.toInt()); connect(rkServer, &RkImageServer::sig_notify_to_mainpage, this, &MainWindow::AddToMainLog);// connect(this, &MainWindow::SigToMainLog, this, &MainWindow::AddToMainLog);// QTimer* timer2 = new QTimer(this); connect(timer2, &QTimer::timeout, this, &MainWindow::TimerUpdate); timer2->start(1000); emit SigToMainLog(QString::fromLocal8Bit("app is started启动")); local_ip = GetLocalIP(); emit SigToMainLog("local IP:"+local_ip+",port:"+local_port); ui->lineEdit_local_IP->setText(local_ip); ui->lineEdit_local_port->setReadOnly(true); ui->lineEdit_local_IP->setReadOnly(true); // 信号绑定:新帧到来更新label显示 connect(&m_cam, &CameraGst::newFrameReady, this, [&](){ QByteArray jpegData = m_cam.getFrameJpeg(); if(!jpegData.isEmpty()) { QImage img; img.loadFromData(jpegData,"JPG"); ui->label_img->setPixmap(QPixmap::fromImage(img)); } }); connect(&m_cam,&CameraGst::errorOccurred,[](QString err){ qCritical()<<"相机错误:"<<err; }); ui->label_img->setGeometry(20,20,800,600); } //【开始按钮】点击触发 void MainWindow::on_btnStart_clicked() { bool ok = m_cam.startCamera("/dev/video11",800,600); if(ok) { qDebug()<<"点击按钮,摄像头启动"; } } //【停止按钮】点击触发 void MainWindow::on_btnStop_clicked() { m_cam.stopCamera(); ui->label_img->clear(); } QString MainWindow::ToCN(QString& msg) { //return QString::fromLocal8Bit(msg); } void MainWindow::AddToMainLog(QString msg) { qMutexMainLog.lock(); //QThread::msleep(300); QDateTime time = QDateTime::currentDateTime(); QString date_str = time.toString("yyyy-MM-dd hh:mm:ss.zzz "); QString add_info = date_str + msg; ui->plainTextEdit_log_msg->appendPlainText(add_info); cfile_operation->AddToLoginfo(msg); //ui.plainTextEdit_log_msg->verticalScrollBar()->setValue(0); //顶部 if (ui->plainTextEdit_log_msg->verticalScrollBar()->maximum() > 0) { ui->plainTextEdit_log_msg->verticalScrollBar()->setValue(ui->plainTextEdit_log_msg->verticalScrollBar()->maximum());//底部 } if (ui->plainTextEdit_log_msg->toPlainText().length() > 5000) { ui->plainTextEdit_log_msg->clear(); ui->plainTextEdit_log_msg->setPlainText("______"); } qMutexMainLog.unlock(); } QString MainWindow::GetLocalIP() { QList<QHostAddress> list = QNetworkInterface::allAddresses(); foreach(QHostAddress address, list) { if (address.protocol() == QAbstractSocket::IPv4Protocol) { return address.toString(); } } return ""; } void MainWindow::TimerUpdate() { //return; //QTime time_show = QTime::currentTime(); //显示时间 // QTime time_show = QTime::currentDateTime(); QDateTime time_show = QDateTime::currentDateTime();//显示日期和时间 // i_timer_counter++; //QString time_str = time_show.toString("yyyy-MM-dd HH:mm:ss.zzz"); QString time_str = time_show.toString("yyyy-MM-dd HH:mm:ss"); ui->lineEdit_current_time->setText(time_str); if(false) { if (false)//tcp_client_modbus->IsConnected() == false) { // if (i_timer_counter % 5 == 0) // { // emit SigPlcState("FALSE"); // emit SigToMainLog("#PLC502已断开连接,自动重接"); // tcp_client_modbus->Connect(c_op->plc1_ip, c_op->plc1_port); //PLC IP // } // return; } else { //ReadAllSensors(); //emit SigPlcState("TRUE"); } } } MainWindow::~MainWindow() { delete ui; } //服务端:有新客户端接入 void MainWindow::onNewConnection() { m_serverSocket = m_tcpServer->nextPendingConnection(); qDebug()<<"客户端连接进来"; connect(m_serverSocket,&QTcpSocket::readyRead,this,&MainWindow::onReadyRead); connect(m_serverSocket,&QTcpSocket::disconnected,this,&MainWindow::onSocketDisconnect); m_fileTotalSize = 0; m_recvBuffer.clear(); } //服务端收到数据 void MainWindow::onReadyRead() { parseFileData(m_serverSocket); } //解析数据包:文本指令 + jpg原始文件二进制 void MainWindow::parseFileData(QTcpSocket *sock) { if(!sock) return; m_recvBuffer.append(sock->readAll()); // ==========优先处理文本指令,以 \r\n 作为指令结束标记========== int idx = m_recvBuffer.indexOf("\r\n"); while(idx != -1) { QByteArray cmd = m_recvBuffer.left(idx).trimmed(); m_recvBuffer = m_recvBuffer.mid(idx + 2); //移除指令+\r\n qDebug()<<"收到客户端指令:"<<cmd; if(cmd == "get_rk_img") { //收到get_rk_img,读取D盘rk.jpg原始文件发送 QString filePath = "D:/rk.jpg"; if(!QFile::exists(filePath)) { qDebug()<<"D:/rk.jpg 打开失败,请检查路径!"; sock->write("error:img not found\r\n"); } else { qDebug()<<"响应get_rk_img,发送D:/rk.jpg原始文件"; sendJpgFile(sock, filePath); } } idx = m_recvBuffer.indexOf("\r\n"); } // ==========接收jpg文件二进制========== if(m_fileTotalSize == 0) { if(m_recvBuffer.size() >= 8) // 使用qint64,8字节大端长度 { // 解析8字节大端文件长度 uchar *p = (uchar*)m_recvBuffer.data(); m_fileTotalSize = (qint64)p[0] << 56 | (qint64)p[1] << 48 | (qint64)p[2] << 40 | (qint64)p[3] << 32 | (qint64)p[4] << 24 | (qint64)p[5] << 16 | (qint64)p[6] << 8 | (qint64)p[7]; m_recvBuffer = m_recvBuffer.mid(8); qDebug()<<"待接收jpg文件大小:"<<m_fileTotalSize<<"字节"; } } if(m_fileTotalSize > 0 && m_recvBuffer.size() >= m_fileTotalSize) { QByteArray fileByte = m_recvBuffer.left(m_fileTotalSize); m_recvBuffer = m_recvBuffer.mid(m_fileTotalSize); // 保存为本地jpg文件 QFile outFile("./recv_test.jpg"); if(outFile.open(QIODevice::WriteOnly)) { outFile.write(fileByte); outFile.close(); qDebug()<<"jpg文件接收保存成功 ./recv_test.jpg"; //加载显示到label(仅用于预览,不参与传输) QPixmap pix; pix.loadFromData(fileByte); if(!pix.isNull()) { // ui->label_recv->setPixmap(pix.scaled(ui->label_recv->size(),Qt::KeepAspectRatio)); } }else{ QMessageBox::warning(nullptr,"错误","保存接收文件失败"); } m_fileTotalSize = 0; if(m_recvBuffer.size()>0){ parseFileData(sock); } } } void MainWindow::closeEvent(QCloseEvent* e) { if (QMessageBox::question(this, tr("Tips"), (QString::fromUtf8("will minimux the window, click the X to quit the App\n将执行最小化\n点击X退出程序")), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { setWindowState(Qt::WindowMinimized); emit SigToMainLog(("已执行最小化exec minimum")); // e->accept(); e->ignore(); } else { e->ignore(); // return; } } /** * @brief sendJpgFile 直接发送jpg原始文件,不经过QImage * @param sock socket * @param filePath 文件路径 * @return 成功true */ bool MainWindow::sendJpgFile(QTcpSocket *sock, const QString &filePath) { if(!sock) return false; QFile file(filePath); if(!file.open(QIODevice::ReadOnly)) { qDebug()<<"文件打开失败:"<<file.errorString(); return false; } qint64 fileLen = file.size(); qDebug()<<"发送原始jpg文件,大小:"<<fileLen<<" bytes"; // 包头:8字节大端 qint64 文件长度 QByteArray head; head.resize(8); uchar* p = (uchar*)head.data(); p[0] = (fileLen >> 56) & 0xFF; p[1] = (fileLen >> 48) & 0xFF; p[2] = (fileLen >> 40) & 0xFF; p[3] = (fileLen >> 32) & 0xFF; p[4] = (fileLen >> 24) & 0xFF; p[5] = (fileLen >> 16) & 0xFF; p[6] = (fileLen >> 8) & 0xFF; p[7] = fileLen & 0xFF; sock->write(head); //分片发送,避免一次性读入大文件占内存 const int chunkSize = 8192; while(!file.atEnd()) { QByteArray chunk = file.read(chunkSize); sock->write(chunk); } sock->flush(); file.close(); return true; } //客户端按钮:选择jpg文件发送 void MainWindow::on_btnSendImg_clicked() { return; QString filePath = QFileDialog::getOpenFileName(this,"选择要发送的jpg文件",".","JPG(*.jpg *.jpeg)"); if(filePath.isEmpty()) return; //预览显示到label_send,仅UI预览,不参与网络传输 QPixmap pix(filePath); if(pix.isNull()){ QMessageBox::warning(this,"错误","打开图片预览失败"); return; } // ui->label_send->setPixmap(pix.scaled(ui->label_send->size(),Qt::KeepAspectRatio)); if(m_clientSocket->state() == QTcpSocket::ConnectedState){ sendJpgFile(m_clientSocket, filePath); }else{ QMessageBox::warning(this,"提示","客户端未连接服务器"); } } void MainWindow::onConnected() { qDebug()<<"客户端成功连接服务端"; } void MainWindow::onClientReadyRead() { // 如果客户端这边也要接收文件,打开下面注释 //parseFileData(m_clientSocket); } void MainWindow::onSocketDisconnect() { m_serverSocket = nullptr; m_fileTotalSize = 0; m_recvBuffer.clear(); qDebug()<<"客户端断开连接"; } void MainWindow::on_pushButton_exit_clicked() { if (QMessageBox::question(this, tr("Tips"), (QString::fromLocal8Bit("Are you sure to quit the App?\n确认退出吗?")), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { QApplication::exit(); } else { // return; } }
欢迎讨论,相互学习。
cdtxw@foxmail.com

浙公网安备 33010602011771号