库文件连接地址: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();