|
|
#include "camera_streamer.h"
|
|
|
|
|
|
#include <QDebug>
|
|
|
#include <QHostAddress>
|
|
|
#include <opencv2/imgproc.hpp>
|
|
|
#include <opencv2/imgcodecs.hpp>
|
|
|
|
|
|
CameraStreamer::CameraStreamer(QObject *parent)
|
|
|
: QObject(parent)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
CameraStreamer::~CameraStreamer()
|
|
|
{
|
|
|
stopStreaming();
|
|
|
}
|
|
|
|
|
|
bool CameraStreamer::startStreaming(const QString &remoteUser,
|
|
|
const QString &remoteHost,
|
|
|
const QString &remoteCommand,
|
|
|
int localPort)
|
|
|
{
|
|
|
if (m_running.load())
|
|
|
return true; // already running
|
|
|
|
|
|
// 启动 ssh 进程,在远端开始推流
|
|
|
if (!m_sshProcess) {
|
|
|
m_sshProcess = new QProcess(this);
|
|
|
// 使 ssh 保持会话 & 不读取stdin,-T 禁用伪终端
|
|
|
QStringList args;
|
|
|
args << QString("%1@%2").arg(remoteUser, remoteHost)
|
|
|
<< "-T" << remoteCommand;
|
|
|
m_sshProcess->start("ssh", args);
|
|
|
if (!m_sshProcess->waitForStarted(3000)) {
|
|
|
qWarning() << "Failed to start ssh process:" << m_sshProcess->errorString();
|
|
|
delete m_sshProcess;
|
|
|
m_sshProcess = nullptr;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 启动本地接收线程
|
|
|
m_running = true;
|
|
|
m_captureThread = std::thread(&CameraStreamer::captureLoop, this, localPort);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void CameraStreamer::stopStreaming()
|
|
|
{
|
|
|
if (!m_running.load())
|
|
|
return;
|
|
|
|
|
|
// 停止读取线程
|
|
|
m_running = false;
|
|
|
if (m_captureThread.joinable())
|
|
|
m_captureThread.join();
|
|
|
|
|
|
// 结束远端 ssh 进程
|
|
|
if (m_sshProcess) {
|
|
|
m_sshProcess->terminate();
|
|
|
if (!m_sshProcess->waitForFinished(3000)) {
|
|
|
m_sshProcess->kill();
|
|
|
m_sshProcess->waitForFinished();
|
|
|
}
|
|
|
delete m_sshProcess;
|
|
|
m_sshProcess = nullptr;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void CameraStreamer::captureLoop(int localPort)
|
|
|
{
|
|
|
// GStreamer UDP pipeline
|
|
|
QString pipeline = QString("udpsrc address=0.0.0.0 port=%1 ! application/x-rtp,media=video,encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! appsink max-buffers=1 drop=true").arg(localPort);
|
|
|
|
|
|
cv::VideoCapture cap(pipeline.toStdString(), cv::CAP_GSTREAMER);
|
|
|
if (!cap.isOpened()) {
|
|
|
qWarning() << "Failed to open capture pipeline" << pipeline;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
cv::Mat frame;
|
|
|
while (m_running.load()) {
|
|
|
if (!cap.read(frame) || frame.empty()) {
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
cv::Mat rgb;
|
|
|
if (frame.channels() == 3) {
|
|
|
cv::cvtColor(frame, rgb, cv::COLOR_BGR2RGB);
|
|
|
} else if (frame.channels() == 4) {
|
|
|
cv::cvtColor(frame, rgb, cv::COLOR_BGRA2RGB);
|
|
|
} else {
|
|
|
rgb = frame;
|
|
|
}
|
|
|
|
|
|
QImage image(rgb.data, rgb.cols, rgb.rows, static_cast<int>(rgb.step), QImage::Format_RGB888);
|
|
|
emit newFrame(image.copy()); // copy to detach from cv::Mat memory
|
|
|
|
|
|
// 控制帧率:根据需要调整
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
}
|
|
|
|
|
|
cap.release();
|
|
|
}
|