#include "gstreamerpipelinewidget.h" #include #include #include #include #include #include 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( "Quick Start: Click 'Quick Start' to automatically configure and start streaming.
" "Manual: 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::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 - Click 'Start Viewer' on the left to view", 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"; }