Files
EJM_Display/PublicFunctions/RtspPlayer.cpp
2025-09-28 17:14:34 +08:00

201 lines
5.2 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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转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;
}
#endif