window QT使用opencv保存视频

库文件连接地址:https://download.csdn.net/download/yuchunhai321/93033363?spm=1001.2014.3001.5503

h文件

复制代码
#ifndef IMAGESAVEMANAGER_H
#define IMAGESAVEMANAGER_H

#include <QObject>
#include <QThread>
#include <QQueue>
#include <QImage>
#include <QDir>
#include <QDateTime>
#include <opencv2/opencv.hpp>

class SimpleVideoRecorder {
public:
    SimpleVideoRecorder();

    ~SimpleVideoRecorder();

    bool startRecording(const QString& fileName, int w, int h, int f = 25);

    void addFrame(const cv::Mat& image);

    void addFrame(const QImage& image);

    void stopRecording();

    bool isRecordingActive() const;

    int getQueueSize() const;

    void setMaxQueueSize(int size);

    // 添加一个方法来强制完成当前录制(等待队列清空)
    void flushAndStop();

private:
    cv::VideoWriter writer;
    QString currentFileName;
    bool isRecording = false;
    bool shouldStop = false;
    int width = 1920;
    int height = 1080;
    int fps = 25;
    int frameCount = 0;

    // 队列相关
    std::queue<cv::Mat> frameQueue;
    std::mutex queueMutex;
    std::condition_variable queueCV;
    std::thread writerThread;

    // 性能控制
    int maxQueueSize = 300;  // 最大队列大小,防止内存溢出
    std::atomic<int> queueSize{0};
    std::chrono::steady_clock::time_point lastLogTime;

    void writerThreadFunction();
};

#endif // IMAGESAVEMANAGER_H

cpp文件

复制代码
#include "imagesavemanager.h"

#include "logs.h"

SimpleVideoRecorder::SimpleVideoRecorder()
{
    lastLogTime = std::chrono::steady_clock::now();
    writerThread = std::thread(&SimpleVideoRecorder::writerThreadFunction, this);
}

SimpleVideoRecorder::~SimpleVideoRecorder()
{
    stopRecording();
    shouldStop = true;
    queueCV.notify_all();
    if (writerThread.joinable()) {
        writerThread.join();
    }
}

bool SimpleVideoRecorder::startRecording(const QString &fileName, int w, int h, int f)
{
    // 检查是否已经在录制
    if (isRecording) {
        ZLOG_INFO("Already recording, stopping current recording first");
        stopRecording();
    }

    // 清空队列
    {
        std::lock_guard<std::mutex> lock(queueMutex);
        while (!frameQueue.empty()) {
            frameQueue.pop();
        }
        queueSize = 0;
    }

    width = w;
    height = h;
    fps = f;
    currentFileName = fileName;
    isRecording = true;
    frameCount = 0;

    ZLOG_INFO(QString("Started recording to %1, size: %2x%3, fps: %4")
              .arg(fileName).arg(width).arg(height).arg(fps).toStdString().c_str());

    return true;
}

void SimpleVideoRecorder::addFrame(const cv::Mat &image)
{
    if (!isRecording) return;

    // 检查队列大小,避免内存溢出
    if (queueSize >= maxQueueSize) {
        ZLOG_INFO(QString("Queue is full (%1), dropping frame").arg(maxQueueSize).toStdString().c_str());
        return;
    }

    // 复制图像数据(因为原始图像可能很快被销毁)
    cv::Mat frameCopy = image.clone();

    {
        std::lock_guard<std::mutex> lock(queueMutex);
        frameQueue.push(frameCopy);
        queueSize++;
    }

    queueCV.notify_one();
}

void SimpleVideoRecorder::addFrame(const QImage &image)
{
    if (!isRecording) return;

    cv::Mat frame;

    // 转换 QImage 到 cv::Mat
    switch (image.format()) {
    case QImage::Format_RGB888:
    {
        cv::Mat temp(image.height(), image.width(), CV_8UC3,
                     const_cast<uchar*>(image.bits()), image.bytesPerLine());
        cv::cvtColor(temp, frame, cv::COLOR_RGB2BGR);
        break;
    }
    case QImage::Format_ARGB32:
    case QImage::Format_RGBA8888:
    {
        cv::Mat temp(image.height(), image.width(), CV_8UC4,
                     const_cast<uchar*>(image.bits()), image.bytesPerLine());
        cv::cvtColor(temp, frame, cv::COLOR_RGBA2BGR);
        break;
    }
    case QImage::Format_RGB32:
    {
        cv::Mat temp(image.height(), image.width(), CV_8UC4,
                     const_cast<uchar*>(image.bits()), image.bytesPerLine());
        cv::cvtColor(temp, frame, cv::COLOR_BGRA2BGR);
        break;
    }
    case QImage::Format_Grayscale8:
    {
        cv::Mat temp(image.height(), image.width(), CV_8UC1,
                     const_cast<uchar*>(image.bits()), image.bytesPerLine());
        cv::cvtColor(temp, frame, cv::COLOR_GRAY2BGR);
        break;
    }
    default:
    {
        // 其他格式,先转换为 RGB888
        QImage rgbImage = image.convertToFormat(QImage::Format_RGB888);
        cv::Mat temp(rgbImage.height(), rgbImage.width(), CV_8UC3,
                     const_cast<uchar*>(rgbImage.bits()), rgbImage.bytesPerLine());
        cv::cvtColor(temp, frame, cv::COLOR_RGB2BGR);
        break;
    }
    }

    addFrame(frame);
}

void SimpleVideoRecorder::stopRecording()
{
    if (!isRecording) return;

    isRecording = false;

    // 等待队列处理完成(可选,超时机制)
    int waitCount = 0;
    while (queueSize > 0 && waitCount < 300) {  // 最多等待3秒
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        waitCount++;
    }

    ZLOG_INFO("Recording stopped");
}

bool SimpleVideoRecorder::isRecordingActive() const
{
    return isRecording;
}

int SimpleVideoRecorder::getQueueSize() const
{
    return queueSize;
}

void SimpleVideoRecorder::setMaxQueueSize(int size)
{
    maxQueueSize = max(10, size);
}

void SimpleVideoRecorder::flushAndStop()
{
    if (!isRecording) return;

    isRecording = false;

    // 等待队列完全清空
    while (queueSize > 0) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    ZLOG_INFO("Recording flushed and stopped");
}

void SimpleVideoRecorder::writerThreadFunction()
{
    cv::VideoWriter localWriter;
    bool localRecording = false;
    int localFrameCount = 0;

    while (!shouldStop) {
        cv::Mat frame;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            queueCV.wait_for(lock, std::chrono::milliseconds(100), [this] {
                return !frameQueue.empty() || shouldStop;
            });

            if (!frameQueue.empty()) {
                frame = frameQueue.front();
                frameQueue.pop();
                queueSize--;
            }
        }

        if (!frame.empty()) {
            // 检查是否需要初始化或重新初始化 writer
            if (!localRecording && isRecording) {
                int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
                localWriter.open(currentFileName.toStdString(), fourcc,
                                 fps, cv::Size(width, height));
                if (localWriter.isOpened()) {
                    localRecording = true;
                    localFrameCount = 0;
                    ZLOG_INFO("Writer thread started successfully");
                } else {
                    ZLOG_ERROR("Failed to open video writer");
                    continue;
                }
            }

            // 写入帧
            if (localRecording && isRecording) {
                localWriter.write(frame);
                localFrameCount++;

                // 每30帧输出日志
                if (localFrameCount % 30 == 0) {
                    auto now = std::chrono::steady_clock::now();
                    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>
                            (now - lastLogTime).count();
                    ZLOG_INFO(QString("已录制帧数: %1, 队列大小: %2, 写入速度: %3 fps")
                              .arg(localFrameCount).arg(queueSize.load())
                              .arg(30000.0 / elapsed).toStdString().c_str());
                    lastLogTime = now;
                }
            }
        }

        // 停止录制的清理工作
        if (!isRecording && localRecording) {
            localWriter.release();
            localRecording = false;
            ZLOG_INFO(QString("录制完成,总帧数: %1,文件: %2")
                      .arg(localFrameCount).arg(currentFileName).toStdString().c_str());

            // 更新主线程的帧计数
            frameCount = localFrameCount;
        }
    }

    // 最终清理
    if (localRecording) {
        localWriter.release();
    }
}

使用

复制代码
   QString fileName = m_pathDirImage.filePath(
                        "video_" + QDateTime::currentDateTime().toString("yyyyMMddhhmmss_zzz") + ".mp4");

            recorder.setMaxQueueSize(500);  // 设置更大的队列
            if (!recorder.startRecording(fileName, m_recordingWidth, m_recordingHeight, 25)) {
                ZLOG_INFO("视频录制失败");
                recorder.flushAndStop();
                m_videoRecorderFlag = false;
                ui->pushButton_trackingMp4->setText(QString::fromLocal8Bit("开始视频录制"));
                return;
            }

            // 添加帧数据
            if (recorder.isRecordingActive())
                recorder.addFrame(image);

        if (recorder.isRecordingActive())
            recorder.flushAndStop();