#include "videoviewerwidget.h" #include #include #include #include #include #include #include VideoViewerWidget::VideoViewerWidget(QWidget *parent) : QWidget(parent), m_pipeline(nullptr), m_videoSink(nullptr), m_busWatchId(0), m_windowId(0) { initGStreamer(); setupUI(); } VideoViewerWidget::~VideoViewerWidget() { cleanupGStreamer(); } void VideoViewerWidget::initGStreamer() { gst_init(nullptr, nullptr); } void VideoViewerWidget::setupUI() { QVBoxLayout* mainLayout = new QVBoxLayout(this); // Video display container QGroupBox* videoGroup = new QGroupBox("Video Display", this); QVBoxLayout* videoLayout = new QVBoxLayout(); m_videoContainer = new QWidget(this); m_videoContainer->setMinimumSize(640, 480); m_videoContainer->setStyleSheet("background-color: black;"); m_videoContainer->setAttribute(Qt::WA_NativeWindow); videoLayout->addWidget(m_videoContainer); videoGroup->setLayout(videoLayout); // Controls QGroupBox* controlGroup = new QGroupBox("Viewer Controls", this); QVBoxLayout* controlLayout = new QVBoxLayout(); // Source type selection QHBoxLayout* sourceLayout = new QHBoxLayout(); sourceLayout->addWidget(new QLabel("Source Type:", this)); m_sourceType = new QComboBox(this); m_sourceType->addItem("UDP MJPEG Stream (No plugins needed)", "udp-mjpeg"); m_sourceType->addItem("UDP H.264 Stream (Requires gst-libav)", "udp-h264"); m_sourceType->addItem("TCP H.264 Stream", "tcp"); m_sourceType->addItem("MJPEG HTTP Stream", "http"); m_sourceType->addItem("Test Pattern", "test"); connect(m_sourceType, QOverload::of(&QComboBox::currentIndexChanged), this, &VideoViewerWidget::onSourceTypeChanged); sourceLayout->addWidget(m_sourceType); // Host and port QFormLayout* formLayout = new QFormLayout(); m_hostEdit = new QLineEdit("127.0.0.1", this); m_portEdit = new QLineEdit("5000", this); formLayout->addRow("Host:", m_hostEdit); formLayout->addRow("Port:", m_portEdit); // Control buttons QHBoxLayout* buttonLayout = new QHBoxLayout(); m_startBtn = new QPushButton("Start Viewer", this); m_stopBtn = new QPushButton("Stop Viewer", this); m_stopBtn->setEnabled(false); connect(m_startBtn, &QPushButton::clicked, this, &VideoViewerWidget::onStartViewer); connect(m_stopBtn, &QPushButton::clicked, this, &VideoViewerWidget::onStopViewer); buttonLayout->addWidget(m_startBtn); buttonLayout->addWidget(m_stopBtn); // Status label m_statusLabel = new QLabel("Status: Stopped", this); m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); controlLayout->addLayout(sourceLayout); controlLayout->addLayout(formLayout); controlLayout->addLayout(buttonLayout); controlLayout->addWidget(m_statusLabel); controlGroup->setLayout(controlLayout); mainLayout->addWidget(videoGroup, 1); mainLayout->addWidget(controlGroup); setLayout(mainLayout); } void VideoViewerWidget::showEvent(QShowEvent* event) { QWidget::showEvent(event); if (!m_windowId) { m_videoContainer->winId(); // Force window creation QTimer::singleShot(100, this, [this]() { m_windowId = m_videoContainer->winId(); qDebug() << "[VideoViewer] Window ID initialized:" << m_windowId; }); } } QString VideoViewerWidget::buildPipelineString() { QString sourceType = m_sourceType->currentData().toString(); QString host = m_hostEdit->text(); QString port = m_portEdit->text(); QString pipeline; // Note: Using autovideosink which opens a separate window // VideoOverlay with Qt widgets doesn't work reliably on this system if (sourceType == "udp-mjpeg") { pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=JPEG,payload=26 ! " "rtpjpegdepay ! jpegdec ! autovideosink") .arg(port); } else if (sourceType == "udp-h264") { pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=H264 ! " "rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! autovideosink") .arg(port); } else if (sourceType == "tcp") { pipeline = QString("tcpclientsrc host=%1 port=%2 ! tsdemux ! h264parse ! avdec_h264 ! " "videoconvert ! autovideosink") .arg(host).arg(port); } else if (sourceType == "http") { pipeline = QString("souphttpsrc location=http://%1:%2 ! multipartdemux ! jpegdec ! " "videoconvert ! autovideosink") .arg(host).arg(port); } else if (sourceType == "test") { pipeline = "videotestsrc ! autovideosink"; } return pipeline; } void VideoViewerWidget::startPipeline() { if (m_pipeline) { stopPipeline(); } QString pipelineStr = buildPipelineString(); qDebug() << "[VideoViewer] Starting pipeline:" << pipelineStr; GError* error = nullptr; m_pipeline = gst_parse_launch(pipelineStr.toUtf8().constData(), &error); if (error) { m_statusLabel->setText(QString("Status: Pipeline Error - %1").arg(error->message)); m_statusLabel->setStyleSheet("QLabel { background-color: #FFB6C1; padding: 5px; border-radius: 3px; }"); g_error_free(error); return; } if (!m_pipeline) { m_statusLabel->setText("Status: Failed to create pipeline"); m_statusLabel->setStyleSheet("QLabel { background-color: #FFB6C1; padding: 5px; border-radius: 3px; }"); return; } // Set up bus callback GstBus* bus = gst_element_get_bus(m_pipeline); m_busWatchId = gst_bus_add_watch(bus, busCallback, this); gst_object_unref(bus); // Note: VideoOverlay disabled - using autovideosink with separate window instead // Start playing GstStateChangeReturn ret = gst_element_set_state(m_pipeline, GST_STATE_PLAYING); qDebug() << "[VideoViewer] Pipeline state change return:" << ret; if (ret == GST_STATE_CHANGE_FAILURE) { m_statusLabel->setText("Status: Failed to start pipeline"); m_statusLabel->setStyleSheet("QLabel { background-color: #FFB6C1; padding: 5px; border-radius: 3px; }"); cleanupGStreamer(); return; } qDebug() << "[VideoViewer] Pipeline started successfully"; m_statusLabel->setText("Status: Playing"); m_statusLabel->setStyleSheet("QLabel { background-color: #90EE90; padding: 5px; border-radius: 3px; }"); m_startBtn->setEnabled(false); m_stopBtn->setEnabled(true); } void VideoViewerWidget::stopPipeline() { if (m_pipeline) { gst_element_set_state(m_pipeline, GST_STATE_NULL); gst_object_unref(m_pipeline); m_pipeline = nullptr; } if (m_videoSink) { gst_object_unref(m_videoSink); m_videoSink = nullptr; } if (m_busWatchId > 0) { g_source_remove(m_busWatchId); m_busWatchId = 0; } m_statusLabel->setText("Status: Stopped"); m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); m_startBtn->setEnabled(true); m_stopBtn->setEnabled(false); } void VideoViewerWidget::cleanupGStreamer() { stopPipeline(); } gboolean VideoViewerWidget::busCallback(GstBus* bus, GstMessage* msg, gpointer data) { VideoViewerWidget* viewer = static_cast(data); switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_ERROR: { GError* err; gchar* debug_info; gst_message_parse_error(msg, &err, &debug_info); QString errorMsg = QString("GStreamer Error: %1\nDebug: %2") .arg(err->message) .arg(debug_info ? debug_info : "none"); qDebug() << "[VideoViewer] ERROR:" << errorMsg; QMetaObject::invokeMethod(viewer, [viewer, errorMsg]() { viewer->m_statusLabel->setText("Status: Stream Error - " + errorMsg); viewer->m_statusLabel->setStyleSheet("QLabel { background-color: #FFB6C1; padding: 5px; border-radius: 3px; }"); viewer->stopPipeline(); }, Qt::QueuedConnection); g_error_free(err); g_free(debug_info); break; } case GST_MESSAGE_EOS: qDebug() << "[VideoViewer] End of stream"; QMetaObject::invokeMethod(viewer, [viewer]() { viewer->m_statusLabel->setText("Status: End of Stream"); viewer->stopPipeline(); }, Qt::QueuedConnection); break; case GST_MESSAGE_STATE_CHANGED: if (GST_MESSAGE_SRC(msg) == GST_OBJECT(viewer->m_pipeline)) { GstState oldState, newState, pendingState; gst_message_parse_state_changed(msg, &oldState, &newState, &pendingState); qDebug() << "[VideoViewer] State changed:" << gst_element_state_get_name(oldState) << "->" << gst_element_state_get_name(newState); } break; case GST_MESSAGE_WARNING: { GError* err; gchar* debug_info; gst_message_parse_warning(msg, &err, &debug_info); qDebug() << "[VideoViewer] WARNING:" << err->message; g_error_free(err); g_free(debug_info); break; } case GST_MESSAGE_INFO: { GError* err; gchar* debug_info; gst_message_parse_info(msg, &err, &debug_info); qDebug() << "[VideoViewer] INFO:" << err->message; g_error_free(err); g_free(debug_info); break; } default: break; } return TRUE; } void VideoViewerWidget::onStartViewer() { startPipeline(); } void VideoViewerWidget::onStopViewer() { stopPipeline(); } void VideoViewerWidget::onSourceTypeChanged(int index) { QString sourceType = m_sourceType->currentData().toString(); bool needsNetwork = (sourceType != "test"); bool isUdp = (sourceType == "udp-mjpeg" || sourceType == "udp-h264"); m_hostEdit->setEnabled(needsNetwork && !isUdp); m_portEdit->setEnabled(needsNetwork); } void VideoViewerWidget::onPrepareWindowHandle(GstBus* bus, GstMessage* msg, gpointer data) { if (!gst_is_video_overlay_prepare_window_handle_message(msg)) { return; } VideoViewerWidget* viewer = static_cast(data); if (viewer->m_windowId) { GstElement* sink = GST_ELEMENT(GST_MESSAGE_SRC(msg)); qDebug() << "[VideoViewer] prepare-window-handle: Setting window ID" << viewer->m_windowId; gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), viewer->m_windowId); } else { qDebug() << "[VideoViewer] prepare-window-handle: No window ID available yet"; } }