192 lines
5.0 KiB
C++
192 lines
5.0 KiB
C++
|
|
#include "RtspPlayer.h"
|
|||
|
|
#include <QDebug>
|
|||
|
|
#include <QMutexLocker>
|
|||
|
|
#include <opencv2/imgproc.hpp>
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
}
|