Files
EJM_Display/PublicFunctions/RtspPlayer.cpp

201 lines
5.2 KiB
C++
Raw Normal View History

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