first commit

This commit is contained in:
Maik Jurischka
2025-12-18 16:10:55 +01:00
commit 69e2f3ae1d
19 changed files with 2707 additions and 0 deletions

328
gstreamerpipelinewidget.cpp Normal file
View File

@@ -0,0 +1,328 @@
#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";
}