329 lines
13 KiB
C++
329 lines
13 KiB
C++
#include "gstreamerpipelinewidget.h"
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QGroupBox>
|
|
#include <QFrame>
|
|
#include <QTimer>
|
|
#include <QJsonArray>
|
|
|
|
GStreamerPipelineWidget::GStreamerPipelineWidget(SocketClient* socketClient, QWidget *parent)
|
|
: QWidget(parent), m_socketClient(socketClient)
|
|
{
|
|
setupUI();
|
|
onGetStatus();
|
|
fetchAvailableFormats();
|
|
}
|
|
|
|
void GStreamerPipelineWidget::setupUI()
|
|
{
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
|
|
|
QGroupBox* groupBox = new QGroupBox("GStreamer Pipeline", this);
|
|
QVBoxLayout* groupLayout = new QVBoxLayout(groupBox);
|
|
|
|
// Info label with instructions
|
|
m_infoLabel = new QLabel(
|
|
"<b>Quick Start:</b> Click 'Quick Start' to automatically configure and start streaming.<br>"
|
|
"<b>Manual:</b> 1. Set video format → 2. Set pipeline → 3. Start stream", this);
|
|
m_infoLabel->setStyleSheet("QLabel { background-color: #e3f2fd; padding: 8px; border-radius: 4px; }");
|
|
m_infoLabel->setWordWrap(true);
|
|
groupLayout->addWidget(m_infoLabel);
|
|
|
|
// Quick Start button (prominent)
|
|
m_quickStartBtn = new QPushButton("⚡ Quick Start (Auto Configure & Stream)", this);
|
|
m_quickStartBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; }");
|
|
connect(m_quickStartBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onQuickStart);
|
|
groupLayout->addWidget(m_quickStartBtn);
|
|
|
|
// Separator
|
|
QFrame* line = new QFrame(this);
|
|
line->setFrameShape(QFrame::HLine);
|
|
line->setFrameShadow(QFrame::Sunken);
|
|
groupLayout->addWidget(line);
|
|
|
|
// Format selection
|
|
QLabel* formatLabel = new QLabel("Video Format:", this);
|
|
m_formatCombo = new QComboBox(this);
|
|
m_formatCombo->addItem("1280x720@30fps UYVY (Default/Supported)", "1280,720,30,UYVY");
|
|
groupLayout->addWidget(formatLabel);
|
|
groupLayout->addWidget(m_formatCombo);
|
|
|
|
// Pipeline presets
|
|
QLabel* presetsLabel = new QLabel("Pipeline Presets:", this);
|
|
m_pipelinePresets = new QComboBox(this);
|
|
m_pipelinePresets->addItem("MJPEG UDP Stream (Best for raw formats)", "videoconvert ! jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=5000");
|
|
m_pipelinePresets->addItem("UDP H.264 Stream (Requires gst-libav)", "videoconvert ! x264enc tune=zerolatency ! rtph264pay ! udpsink host=127.0.0.1 port=5000");
|
|
m_pipelinePresets->addItem("Custom", "");
|
|
m_pipelinePresets->addItem("Test - Fake Sink (No Output)", "fakesink");
|
|
m_pipelinePresets->addItem("Local Display", "videoconvert ! autovideosink");
|
|
m_pipelinePresets->addItem("TCP H.264 Stream", "videoconvert ! x264enc ! h264parse ! mpegtsmux ! tcpserversink host=0.0.0.0 port=5000");
|
|
m_pipelinePresets->addItem("MJPEG HTTP Stream", "videoconvert ! jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=8080");
|
|
m_pipelinePresets->addItem("Save to File", "videoconvert ! x264enc ! mp4mux ! filesink location=/tmp/output.mp4");
|
|
|
|
connect(m_pipelinePresets, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
this, &GStreamerPipelineWidget::onPipelinePresetChanged);
|
|
|
|
groupLayout->addWidget(presetsLabel);
|
|
groupLayout->addWidget(m_pipelinePresets);
|
|
|
|
// Pipeline editor
|
|
QLabel* pipelineLabel = new QLabel("Pipeline:", this);
|
|
m_pipelineEdit = new QTextEdit(this);
|
|
m_pipelineEdit->setMaximumHeight(80);
|
|
m_pipelineEdit->setPlaceholderText("Enter GStreamer pipeline here...\nExample: videoconvert ! autovideosink");
|
|
|
|
groupLayout->addWidget(pipelineLabel);
|
|
groupLayout->addWidget(m_pipelineEdit);
|
|
|
|
// Set pipeline button
|
|
m_setPipelineBtn = new QPushButton("Set Pipeline", this);
|
|
connect(m_setPipelineBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onSetPipeline);
|
|
groupLayout->addWidget(m_setPipelineBtn);
|
|
|
|
// Stream control buttons
|
|
QHBoxLayout* buttonLayout = new QHBoxLayout();
|
|
m_startStreamBtn = new QPushButton("Start Stream", this);
|
|
m_stopStreamBtn = new QPushButton("Stop Stream", this);
|
|
m_getStatusBtn = new QPushButton("Get Status", this);
|
|
|
|
connect(m_startStreamBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onStartStream);
|
|
connect(m_stopStreamBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onStopStream);
|
|
connect(m_getStatusBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onGetStatus);
|
|
|
|
buttonLayout->addWidget(m_startStreamBtn);
|
|
buttonLayout->addWidget(m_stopStreamBtn);
|
|
buttonLayout->addWidget(m_getStatusBtn);
|
|
groupLayout->addLayout(buttonLayout);
|
|
|
|
// Status label
|
|
m_statusLabel = new QLabel("Status: Unknown", this);
|
|
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
|
|
groupLayout->addWidget(m_statusLabel);
|
|
|
|
mainLayout->addWidget(groupBox);
|
|
mainLayout->addStretch();
|
|
|
|
setLayout(mainLayout);
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onPipelinePresetChanged(int index)
|
|
{
|
|
QString pipeline = m_pipelinePresets->currentData().toString();
|
|
if (!pipeline.isEmpty()) {
|
|
m_pipelineEdit->setPlainText(pipeline);
|
|
}
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onSetPipeline()
|
|
{
|
|
QString pipeline = m_pipelineEdit->toPlainText().trimmed();
|
|
if (pipeline.isEmpty()) {
|
|
updateStatus("Error: Empty pipeline", false);
|
|
return;
|
|
}
|
|
|
|
QJsonObject params;
|
|
params["pipeline"] = pipeline;
|
|
|
|
m_socketClient->sendCommand("set_pipeline", params,
|
|
[this](const QJsonObject& response) {
|
|
updateStatus("Pipeline set successfully", false);
|
|
},
|
|
[this](const QString& error) {
|
|
updateStatus("Error: " + error, false);
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onStartStream()
|
|
{
|
|
// First ensure format is set, then start stream
|
|
QString formatData = m_formatCombo->currentData().toString();
|
|
QStringList parts = formatData.split(',');
|
|
|
|
QJsonObject formatParams;
|
|
formatParams["width"] = parts[0];
|
|
formatParams["height"] = parts[1];
|
|
formatParams["framerate"] = parts[2];
|
|
formatParams["format"] = parts[3];
|
|
|
|
m_socketClient->sendCommand("set_format", formatParams,
|
|
[this](const QJsonObject& response) {
|
|
// Now start stream
|
|
m_socketClient->sendCommand("start_stream", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
updateStatus("Streaming started", true);
|
|
},
|
|
[this](const QString& error) {
|
|
updateStatus("Error: Failed to start stream: " + error, false);
|
|
});
|
|
},
|
|
[this](const QString& error) {
|
|
// Format setting failed, but maybe it was already set - try starting anyway
|
|
m_socketClient->sendCommand("start_stream", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
updateStatus("Streaming started", true);
|
|
},
|
|
[this](const QString& error) {
|
|
updateStatus("Error: Failed to start stream: " + error, false);
|
|
});
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onStopStream()
|
|
{
|
|
m_socketClient->sendCommand("stop_stream", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
updateStatus("Streaming stopped", false);
|
|
},
|
|
[this](const QString& error) {
|
|
updateStatus("Error: Failed to stop stream: " + error, false);
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onGetStatus()
|
|
{
|
|
m_socketClient->sendCommand("get_status", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
bool streaming = response["streaming"].toBool();
|
|
QString pipeline = response["pipeline"].toString();
|
|
|
|
updateStatus(streaming ? "Streaming" : "Stopped", streaming);
|
|
|
|
if (!pipeline.isEmpty() && m_pipelineEdit->toPlainText().isEmpty()) {
|
|
m_pipelineEdit->setPlainText(pipeline);
|
|
}
|
|
},
|
|
[this](const QString& error) {
|
|
updateStatus("Connection Error", false);
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::updateStatus(const QString& status, bool streaming)
|
|
{
|
|
m_statusLabel->setText("Status: " + status);
|
|
|
|
if (streaming) {
|
|
m_statusLabel->setStyleSheet("QLabel { background-color: #90EE90; padding: 5px; border-radius: 3px; }");
|
|
} else if (status.contains("Error")) {
|
|
m_statusLabel->setStyleSheet("QLabel { background-color: #FFB6C1; padding: 5px; border-radius: 3px; }");
|
|
} else {
|
|
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
|
|
}
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onQuickStart()
|
|
{
|
|
// Disable button during process
|
|
m_quickStartBtn->setEnabled(false);
|
|
m_quickStartBtn->setText("Configuring...");
|
|
|
|
// Step 1: Set format
|
|
QString formatData = m_formatCombo->currentData().toString();
|
|
QStringList parts = formatData.split(',');
|
|
|
|
QJsonObject formatParams;
|
|
formatParams["width"] = parts[0];
|
|
formatParams["height"] = parts[1];
|
|
formatParams["framerate"] = parts[2];
|
|
formatParams["format"] = parts[3];
|
|
|
|
m_socketClient->sendCommand("set_format", formatParams,
|
|
[this](const QJsonObject& response) {
|
|
// Step 2: Use selected preset pipeline or default to MJPEG
|
|
QString pipeline = m_pipelinePresets->currentData().toString();
|
|
if (pipeline.isEmpty()) {
|
|
pipeline = "videoconvert ! jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=5000";
|
|
}
|
|
m_pipelineEdit->setPlainText(pipeline);
|
|
|
|
QJsonObject pipelineParams;
|
|
pipelineParams["pipeline"] = pipeline;
|
|
|
|
m_socketClient->sendCommand("set_pipeline", pipelineParams,
|
|
[this](const QJsonObject& response) {
|
|
// Step 3: Start stream
|
|
m_socketClient->sendCommand("start_stream", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
updateStatus("Streaming started - Switch to Video Viewer tab and click 'Start Viewer'", true);
|
|
m_quickStartBtn->setEnabled(true);
|
|
m_quickStartBtn->setText("⚡ Quick Start (Auto Configure & Stream)");
|
|
},
|
|
[this](const QString& error) {
|
|
m_quickStartBtn->setEnabled(true);
|
|
m_quickStartBtn->setText("⚡ Quick Start (Auto Configure & Stream)");
|
|
updateStatus("Error: Failed to start stream: " + error, false);
|
|
});
|
|
},
|
|
[this](const QString& error) {
|
|
m_quickStartBtn->setEnabled(true);
|
|
m_quickStartBtn->setText("⚡ Quick Start (Auto Configure & Stream)");
|
|
updateStatus("Error: Failed to set pipeline: " + error, false);
|
|
});
|
|
},
|
|
[this](const QString& error) {
|
|
m_quickStartBtn->setEnabled(true);
|
|
m_quickStartBtn->setText("⚡ Quick Start (Auto Configure & Stream)");
|
|
updateStatus("Error: Failed to set format: " + error, false);
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::setFormatAndPipeline()
|
|
{
|
|
// Called before manual stream start to ensure format is set
|
|
QString formatData = m_formatCombo->currentData().toString();
|
|
QStringList parts = formatData.split(',');
|
|
|
|
QJsonObject formatParams;
|
|
formatParams["width"] = parts[0];
|
|
formatParams["height"] = parts[1];
|
|
formatParams["framerate"] = parts[2];
|
|
formatParams["format"] = parts[3];
|
|
|
|
m_socketClient->sendCommand("set_format", formatParams,
|
|
[](const QJsonObject& response) {},
|
|
[](const QString& error) {});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::fetchAvailableFormats()
|
|
{
|
|
m_socketClient->sendCommand("get_formats", QJsonObject(),
|
|
[this](const QJsonObject& response) {
|
|
onFormatsReceived(response);
|
|
},
|
|
[this](const QString& error) {
|
|
qDebug() << "Failed to fetch formats:" << error;
|
|
});
|
|
}
|
|
|
|
void GStreamerPipelineWidget::onFormatsReceived(const QJsonObject& response)
|
|
{
|
|
if (!response.contains("formats")) {
|
|
return;
|
|
}
|
|
|
|
QJsonArray formats = response["formats"].toArray();
|
|
if (formats.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Clear existing formats
|
|
m_formatCombo->clear();
|
|
|
|
// Add all available formats
|
|
for (const QJsonValue& val : formats) {
|
|
QJsonObject fmt = val.toObject();
|
|
int width = fmt["width"].toInt();
|
|
int height = fmt["height"].toInt();
|
|
int fps = fmt["framerate"].toInt();
|
|
QString format = fmt["format"].toString();
|
|
|
|
QString displayText = QString("%1x%2@%3fps %4")
|
|
.arg(width).arg(height).arg(fps).arg(format);
|
|
QString data = QString("%1,%2,%3,%4").arg(width).arg(height).arg(fps).arg(format);
|
|
|
|
m_formatCombo->addItem(displayText, data);
|
|
}
|
|
|
|
qDebug() << "Loaded" << formats.size() << "available formats from camera";
|
|
}
|