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;
    }

}

 

posted @ 2026-08-17 14:43  txwtech  阅读(2)  评论(0)    收藏  举报