201 lines
5.2 KiB
C++
201 lines
5.2 KiB
C++
#include "RtspPlayer.h"
|
||
#if CONFIG_EN_RTSP //开启 RTSP 读取
|
||
|
||
|
||
#include <QDebug>
|
||
#include <QMutexLocker>
|
||
#include <opencv2/imgproc.hpp>
|
||
#endif
|
||
|
||
RtspPlayer::RtspPlayer(QObject *parent) : QThread(parent)
|
||
{
|
||
#if CONFIG_EN_RTSP //开启 RTSP 读取
|
||
// 设置线程退出时自动释放资源
|
||
setTerminationEnabled(true);
|
||
#endif
|
||
}
|
||
|
||
RtspPlayer::~RtspPlayer()
|
||
{
|
||
#if CONFIG_EN_RTSP //开启 RTSP 读取
|
||
stopPlay(); // 停止播放
|
||
wait(1000); // 等待线程退出(最多1秒)
|
||
if (isRunning()) {
|
||
terminate(); // 若仍未退出,强制终止(极端情况)
|
||
}
|
||
#endif
|
||
}
|
||
#if CONFIG_EN_RTSP //开启 RTSP 读取
|
||
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;
|
||
}
|
||
#endif
|