#include "RtspPlayer.h" #include #include #include RtspPlayer::RtspPlayer(QObject *parent) : QThread(parent) { // 设置线程退出时自动释放资源 setTerminationEnabled(true); } RtspPlayer::~RtspPlayer() { stopPlay(); // 停止播放 wait(1000); // 等待线程退出(最多1秒) if (isRunning()) { terminate(); // 若仍未退出,强制终止(极端情况) } } bool RtspPlayer::init(const QString &rtspUrl) { QMutexLocker locker(&m_mutex); // 加锁保护资源 // 释放已有连接 if (m_cap.isOpened()) { m_cap.release(); } m_rtspUrl = rtspUrl; m_needReconnect = false; // 强制使用FFmpeg后端(兼容性最佳) bool opened = m_cap.open(rtspUrl.toStdString(), cv::CAP_FFMPEG); // 若失败,尝试GStreamer后端(Linux常用) if (!opened) { opened = m_cap.open(rtspUrl.toStdString(), cv::CAP_GSTREAMER); } if (!opened) { emit errorOccurred("初始化失败:无法打开RTSP流"); return false; } // 配置低延迟参数 m_cap.set(cv::CAP_PROP_BUFFERSIZE, 1); // 缓冲区仅1帧(最低延迟) m_cap.set(cv::CAP_PROP_FPS, 25); // 固定帧率 return true; } void RtspPlayer::startPlay() { QMutexLocker locker(&m_mutex); m_isPaused = false; m_isPlaying = true; // 若线程未运行,则启动 if (!m_isRunning) { m_isRunning = true; start(NormalPriority); // 正常优先级启动线程 } } void RtspPlayer::pausePlay() { QMutexLocker locker(&m_mutex); m_isPaused = true; m_isPlaying = false; } void RtspPlayer::stopPlay() { QMutexLocker locker(&m_mutex); m_isPlaying = false; m_isPaused = false; m_isRunning = false; m_needReconnect = false; // 强制释放OpenCV资源 if (m_cap.isOpened()) { m_cap.release(); } } bool RtspPlayer::isPlaying() const { QMutexLocker locker(&m_mutex); // const函数加锁需用mutable mutex return m_isPlaying; } void RtspPlayer::run() { cv::Mat frame; int reconnectCount = 0; // 重连计数器,避免无限重试 while (m_isRunning) { // 检查是否需要暂停 { QMutexLocker locker(&m_mutex); if (m_isPaused) { locker.unlock(); // 解锁后再休眠,避免阻塞其他操作 msleep(50); continue; } } // 检查连接是否有效,无效则重连 if (!m_cap.isOpened() || m_needReconnect) { emit errorOccurred(QString("连接断开,尝试重连(%1次)...").arg(reconnectCount)); // 重连逻辑(加锁保护) { QMutexLocker locker(&m_mutex); m_cap.release(); // 彻底释放旧连接 m_cap.open(m_rtspUrl.toStdString(), cv::CAP_FFMPEG); m_needReconnect = !m_cap.isOpened(); } if (m_cap.isOpened()) { emit errorOccurred("重连成功"); reconnectCount = 0; // 重置计数器 } else { reconnectCount++; if (reconnectCount >= 5) { // 最多重试5次 emit errorOccurred("重连失败次数过多,请检查设备"); stopPlay(); // 停止播放 break; } msleep(1000); // 1秒后重试 continue; } } // 读取一帧(非阻塞方式) bool readSuccess = m_cap.grab(); // 先抓取帧(快速) if (readSuccess) { readSuccess = m_cap.retrieve(frame); // 再解码(耗时) } if (!readSuccess || frame.empty()) { m_needReconnect = true; // 标记需要重连 msleep(100); continue; } // 转换为QImage并发送 QImage image = cvMatToQImage(frame); if (!image.isNull()) { emit frameReady(image); } // 控制帧率(约25fps) msleep(40); } // 线程退出前释放资源 frame.release(); m_cap.release(); qDebug() << "RtspPlayer线程已退出"; } QImage RtspPlayer::cvMatToQImage(const cv::Mat &mat) { if (mat.empty()) { return QImage(); } QImage image; if (mat.channels() == 3) { // BGR转RGB(OpenCV默认BGR格式) cv::Mat rgbMat; cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB); // 复制数据,避免Mat释放后QImage访问无效内存 image = QImage( rgbMat.data, rgbMat.cols, rgbMat.rows, rgbMat.step, QImage::Format_RGB888 ).copy(); } else if (mat.channels() == 1) { // 灰度图 image = QImage( mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8 ).copy(); } else { emit errorOccurred(QString("不支持的图像格式(通道数:%1)").arg(mat.channels())); } return image; }