clang optimizations and cleanup

This commit is contained in:
Maik Jurischka
2025-12-19 07:27:21 +01:00
parent 5fed3070de
commit bef80372b6
5 changed files with 78 additions and 116 deletions

View File

@@ -14,17 +14,15 @@ CameraControlWidget::CameraControlWidget(SocketClient* socketClient, QWidget *pa
void CameraControlWidget::setupUI() void CameraControlWidget::setupUI()
{ {
QVBoxLayout* mainLayout = new QVBoxLayout(this); auto* mainLayout = new QVBoxLayout(this);
// Create scroll area for all controls auto* scrollArea = new QScrollArea(this);
QScrollArea* scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true); scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QWidget* scrollWidget = new QWidget(); auto* scrollWidget = new QWidget();
QVBoxLayout* scrollLayout = new QVBoxLayout(scrollWidget); auto* scrollLayout = new QVBoxLayout(scrollWidget);
// Add all control groups
scrollLayout->addWidget(createFormatGroup()); scrollLayout->addWidget(createFormatGroup());
scrollLayout->addWidget(createExposureGroup()); scrollLayout->addWidget(createExposureGroup());
scrollLayout->addWidget(createWhiteBalanceGroup()); scrollLayout->addWidget(createWhiteBalanceGroup());
@@ -36,7 +34,6 @@ void CameraControlWidget::setupUI()
mainLayout->addWidget(scrollArea); mainLayout->addWidget(scrollArea);
// Status label at bottom
m_statusLabel = new QLabel("Status: Ready", this); m_statusLabel = new QLabel("Status: Ready", this);
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
mainLayout->addWidget(m_statusLabel); mainLayout->addWidget(m_statusLabel);
@@ -46,8 +43,8 @@ void CameraControlWidget::setupUI()
QGroupBox* CameraControlWidget::createFormatGroup() QGroupBox* CameraControlWidget::createFormatGroup()
{ {
QGroupBox* groupBox = new QGroupBox("Video Format", this); auto* groupBox = new QGroupBox("Video Format", this);
QVBoxLayout* layout = new QVBoxLayout(); auto* layout = new QVBoxLayout();
m_formatCombo = new QComboBox(this); m_formatCombo = new QComboBox(this);
m_formatCombo->addItem("1280x720@30fps UYVY (Supported)", "1280,720,30,UYVY"); m_formatCombo->addItem("1280x720@30fps UYVY (Supported)", "1280,720,30,UYVY");
@@ -69,10 +66,10 @@ QGroupBox* CameraControlWidget::createFormatGroup()
QGroupBox* CameraControlWidget::createExposureGroup() QGroupBox* CameraControlWidget::createExposureGroup()
{ {
QGroupBox* groupBox = new QGroupBox("Exposure", this); auto* groupBox = new QGroupBox("Exposure", this);
QVBoxLayout* layout = new QVBoxLayout(); auto* layout = new QVBoxLayout();
QButtonGroup* exposureGroup = new QButtonGroup(this); auto* exposureGroup = new QButtonGroup(this);
m_exposureAuto = new QRadioButton("Auto", this); m_exposureAuto = new QRadioButton("Auto", this);
m_exposureManual = new QRadioButton("Manual", this); m_exposureManual = new QRadioButton("Manual", this);
m_exposureAuto->setChecked(true); m_exposureAuto->setChecked(true);
@@ -82,7 +79,7 @@ QGroupBox* CameraControlWidget::createExposureGroup()
connect(m_exposureAuto, &QRadioButton::toggled, this, &CameraControlWidget::onExposureModeChanged); connect(m_exposureAuto, &QRadioButton::toggled, this, &CameraControlWidget::onExposureModeChanged);
QHBoxLayout* modeLayout = new QHBoxLayout(); auto* modeLayout = new QHBoxLayout();
modeLayout->addWidget(m_exposureAuto); modeLayout->addWidget(m_exposureAuto);
modeLayout->addWidget(m_exposureManual); modeLayout->addWidget(m_exposureManual);
@@ -94,7 +91,7 @@ QGroupBox* CameraControlWidget::createExposureGroup()
m_setExposureBtn = new QPushButton("Set Exposure", this); m_setExposureBtn = new QPushButton("Set Exposure", this);
connect(m_setExposureBtn, &QPushButton::clicked, this, &CameraControlWidget::onSetExposure); connect(m_setExposureBtn, &QPushButton::clicked, this, &CameraControlWidget::onSetExposure);
QFormLayout* formLayout = new QFormLayout(); auto* formLayout = new QFormLayout();
formLayout->addRow("Mode:", modeLayout); formLayout->addRow("Mode:", modeLayout);
formLayout->addRow("Value:", m_exposureValue); formLayout->addRow("Value:", m_exposureValue);
@@ -107,10 +104,10 @@ QGroupBox* CameraControlWidget::createExposureGroup()
QGroupBox* CameraControlWidget::createWhiteBalanceGroup() QGroupBox* CameraControlWidget::createWhiteBalanceGroup()
{ {
QGroupBox* groupBox = new QGroupBox("White Balance", this); auto* groupBox = new QGroupBox("White Balance", this);
QVBoxLayout* layout = new QVBoxLayout(); auto* layout = new QVBoxLayout();
QButtonGroup* wbGroup = new QButtonGroup(this); auto* wbGroup = new QButtonGroup(this);
m_whiteBalanceAuto = new QRadioButton("Auto", this); m_whiteBalanceAuto = new QRadioButton("Auto", this);
m_whiteBalanceManual = new QRadioButton("Manual", this); m_whiteBalanceManual = new QRadioButton("Manual", this);
m_whiteBalanceAuto->setChecked(true); m_whiteBalanceAuto->setChecked(true);
@@ -120,7 +117,7 @@ QGroupBox* CameraControlWidget::createWhiteBalanceGroup()
connect(m_whiteBalanceAuto, &QRadioButton::toggled, this, &CameraControlWidget::onWhiteBalanceModeChanged); connect(m_whiteBalanceAuto, &QRadioButton::toggled, this, &CameraControlWidget::onWhiteBalanceModeChanged);
QHBoxLayout* modeLayout = new QHBoxLayout(); auto* modeLayout = new QHBoxLayout();
modeLayout->addWidget(m_whiteBalanceAuto); modeLayout->addWidget(m_whiteBalanceAuto);
modeLayout->addWidget(m_whiteBalanceManual); modeLayout->addWidget(m_whiteBalanceManual);
@@ -133,7 +130,7 @@ QGroupBox* CameraControlWidget::createWhiteBalanceGroup()
m_setWhiteBalanceBtn = new QPushButton("Set White Balance", this); m_setWhiteBalanceBtn = new QPushButton("Set White Balance", this);
connect(m_setWhiteBalanceBtn, &QPushButton::clicked, this, &CameraControlWidget::onSetWhiteBalance); connect(m_setWhiteBalanceBtn, &QPushButton::clicked, this, &CameraControlWidget::onSetWhiteBalance);
QFormLayout* formLayout = new QFormLayout(); auto* formLayout = new QFormLayout();
formLayout->addRow("Mode:", modeLayout); formLayout->addRow("Mode:", modeLayout);
formLayout->addRow("Temperature:", m_whiteBalanceTemp); formLayout->addRow("Temperature:", m_whiteBalanceTemp);
@@ -146,8 +143,8 @@ QGroupBox* CameraControlWidget::createWhiteBalanceGroup()
QGroupBox* CameraControlWidget::createImageAdjustmentGroup() QGroupBox* CameraControlWidget::createImageAdjustmentGroup()
{ {
QGroupBox* groupBox = new QGroupBox("Image Adjustments", this); auto* groupBox = new QGroupBox("Image Adjustments", this);
QVBoxLayout* layout = new QVBoxLayout(); auto* layout = new QVBoxLayout();
layout->addWidget(createSliderControl("Brightness (0-255):", 0, 255, 128, layout->addWidget(createSliderControl("Brightness (0-255):", 0, 255, 128,
&m_brightnessSlider, &m_brightnessSpinBox)); &m_brightnessSlider, &m_brightnessSpinBox));
@@ -180,13 +177,13 @@ QGroupBox* CameraControlWidget::createImageAdjustmentGroup()
QWidget* CameraControlWidget::createSliderControl(const QString& label, int min, int max, int defaultValue, QWidget* CameraControlWidget::createSliderControl(const QString& label, int min, int max, int defaultValue,
QSlider** slider, QSpinBox** spinBox) QSlider** slider, QSpinBox** spinBox)
{ {
QWidget* widget = new QWidget(this); auto* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget); auto* layout = new QVBoxLayout(widget);
layout->setContentsMargins(0, 5, 0, 5); layout->setContentsMargins(0, 5, 0, 5);
QLabel* titleLabel = new QLabel(label, this); auto* titleLabel = new QLabel(label, this);
QHBoxLayout* controlLayout = new QHBoxLayout(); auto* controlLayout = new QHBoxLayout();
*slider = new QSlider(Qt::Horizontal, this); *slider = new QSlider(Qt::Horizontal, this);
(*slider)->setRange(min, max); (*slider)->setRange(min, max);
@@ -223,9 +220,8 @@ void CameraControlWidget::onGetFormats()
int fps = fmt["framerate"].toInt(); int fps = fmt["framerate"].toInt();
QString format = fmt["format"].toString(); QString format = fmt["format"].toString();
QString displayText = QString("%1x%2@%3fps %4") QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format);
.arg(width).arg(height).arg(fps).arg(format); QString data = QString("%1,%2,%3,%4").arg(width, height, fps).arg(format);
QString data = QString("%1,%2,%3,%4").arg(width).arg(height).arg(fps).arg(format);
m_formatCombo->addItem(displayText, data); m_formatCombo->addItem(displayText, data);
} }

View File

@@ -16,12 +16,11 @@ GStreamerPipelineWidget::GStreamerPipelineWidget(SocketClient* socketClient, QWi
void GStreamerPipelineWidget::setupUI() void GStreamerPipelineWidget::setupUI()
{ {
QVBoxLayout* mainLayout = new QVBoxLayout(this); auto* mainLayout = new QVBoxLayout(this);
QGroupBox* groupBox = new QGroupBox("GStreamer Pipeline", this); auto* groupBox = new QGroupBox("GStreamer Pipeline", this);
QVBoxLayout* groupLayout = new QVBoxLayout(groupBox); auto* groupLayout = new QVBoxLayout(groupBox);
// Info label with instructions
m_infoLabel = new QLabel( m_infoLabel = new QLabel(
"<b>Quick Start:</b> Click 'Quick Start' to automatically configure and start streaming.<br>" "<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); "<b>Manual:</b> 1. Set video format → 2. Set pipeline → 3. Start stream", this);
@@ -29,27 +28,23 @@ void GStreamerPipelineWidget::setupUI()
m_infoLabel->setWordWrap(true); m_infoLabel->setWordWrap(true);
groupLayout->addWidget(m_infoLabel); groupLayout->addWidget(m_infoLabel);
// Quick Start button (prominent)
m_quickStartBtn = new QPushButton("⚡ Quick Start (Auto Configure & Stream)", this); m_quickStartBtn = new QPushButton("⚡ Quick Start (Auto Configure & Stream)", this);
m_quickStartBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; }"); m_quickStartBtn->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; }");
connect(m_quickStartBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onQuickStart); connect(m_quickStartBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onQuickStart);
groupLayout->addWidget(m_quickStartBtn); groupLayout->addWidget(m_quickStartBtn);
// Separator auto* line = new QFrame(this);
QFrame* line = new QFrame(this);
line->setFrameShape(QFrame::HLine); line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken); line->setFrameShadow(QFrame::Sunken);
groupLayout->addWidget(line); groupLayout->addWidget(line);
// Format selection auto* formatLabel = new QLabel("Video Format:", this);
QLabel* formatLabel = new QLabel("Video Format:", this);
m_formatCombo = new QComboBox(this); m_formatCombo = new QComboBox(this);
m_formatCombo->addItem("1280x720@30fps UYVY (Default/Supported)", "1280,720,30,UYVY"); m_formatCombo->addItem("1280x720@30fps UYVY (Default/Supported)", "1280,720,30,UYVY");
groupLayout->addWidget(formatLabel); groupLayout->addWidget(formatLabel);
groupLayout->addWidget(m_formatCombo); groupLayout->addWidget(m_formatCombo);
// Pipeline presets auto* presetsLabel = new QLabel("Pipeline Presets:", this);
QLabel* presetsLabel = new QLabel("Pipeline Presets:", this);
m_pipelinePresets = new QComboBox(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("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("UDP H.264 Stream (Requires gst-libav)", "videoconvert ! x264enc tune=zerolatency ! rtph264pay ! udpsink host=127.0.0.1 port=5000");
@@ -66,8 +61,7 @@ void GStreamerPipelineWidget::setupUI()
groupLayout->addWidget(presetsLabel); groupLayout->addWidget(presetsLabel);
groupLayout->addWidget(m_pipelinePresets); groupLayout->addWidget(m_pipelinePresets);
// Pipeline editor auto* pipelineLabel = new QLabel("Pipeline:", this);
QLabel* pipelineLabel = new QLabel("Pipeline:", this);
m_pipelineEdit = new QTextEdit(this); m_pipelineEdit = new QTextEdit(this);
m_pipelineEdit->setMaximumHeight(80); m_pipelineEdit->setMaximumHeight(80);
m_pipelineEdit->setPlaceholderText("Enter GStreamer pipeline here...\nExample: videoconvert ! autovideosink"); m_pipelineEdit->setPlaceholderText("Enter GStreamer pipeline here...\nExample: videoconvert ! autovideosink");
@@ -75,13 +69,11 @@ void GStreamerPipelineWidget::setupUI()
groupLayout->addWidget(pipelineLabel); groupLayout->addWidget(pipelineLabel);
groupLayout->addWidget(m_pipelineEdit); groupLayout->addWidget(m_pipelineEdit);
// Set pipeline button
m_setPipelineBtn = new QPushButton("Set Pipeline", this); m_setPipelineBtn = new QPushButton("Set Pipeline", this);
connect(m_setPipelineBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onSetPipeline); connect(m_setPipelineBtn, &QPushButton::clicked, this, &GStreamerPipelineWidget::onSetPipeline);
groupLayout->addWidget(m_setPipelineBtn); groupLayout->addWidget(m_setPipelineBtn);
// Stream control buttons auto* buttonLayout = new QHBoxLayout();
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_startStreamBtn = new QPushButton("Start Stream", this); m_startStreamBtn = new QPushButton("Start Stream", this);
m_stopStreamBtn = new QPushButton("Stop Stream", this); m_stopStreamBtn = new QPushButton("Stop Stream", this);
m_getStatusBtn = new QPushButton("Get Status", this); m_getStatusBtn = new QPushButton("Get Status", this);
@@ -95,7 +87,6 @@ void GStreamerPipelineWidget::setupUI()
buttonLayout->addWidget(m_getStatusBtn); buttonLayout->addWidget(m_getStatusBtn);
groupLayout->addLayout(buttonLayout); groupLayout->addLayout(buttonLayout);
// Status label
m_statusLabel = new QLabel("Status: Unknown", this); m_statusLabel = new QLabel("Status: Unknown", this);
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
groupLayout->addWidget(m_statusLabel); groupLayout->addWidget(m_statusLabel);
@@ -148,7 +139,6 @@ void GStreamerPipelineWidget::onStartStream()
m_socketClient->sendCommand("set_format", formatParams, m_socketClient->sendCommand("set_format", formatParams,
[this](const QJsonObject& response) { [this](const QJsonObject& response) {
// Now start stream
m_socketClient->sendCommand("start_stream", QJsonObject(), m_socketClient->sendCommand("start_stream", QJsonObject(),
[this](const QJsonObject& response) { [this](const QJsonObject& response) {
updateStatus("Streaming started", true); updateStatus("Streaming started", true);
@@ -213,7 +203,6 @@ void GStreamerPipelineWidget::updateStatus(const QString& status, bool streaming
void GStreamerPipelineWidget::onQuickStart() void GStreamerPipelineWidget::onQuickStart()
{ {
// Disable button during process
m_quickStartBtn->setEnabled(false); m_quickStartBtn->setEnabled(false);
m_quickStartBtn->setText("Configuring..."); m_quickStartBtn->setText("Configuring...");
@@ -306,10 +295,8 @@ void GStreamerPipelineWidget::onFormatsReceived(const QJsonObject& response)
return; return;
} }
// Clear existing formats
m_formatCombo->clear(); m_formatCombo->clear();
// Add all available formats
for (const QJsonValue& val : formats) { for (const QJsonValue& val : formats) {
QJsonObject fmt = val.toObject(); QJsonObject fmt = val.toObject();
int width = fmt["width"].toInt(); int width = fmt["width"].toInt();
@@ -317,9 +304,8 @@ void GStreamerPipelineWidget::onFormatsReceived(const QJsonObject& response)
int fps = fmt["framerate"].toInt(); int fps = fmt["framerate"].toInt();
QString format = fmt["format"].toString(); QString format = fmt["format"].toString();
QString displayText = QString("%1x%2@%3fps %4") QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format);
.arg(width).arg(height).arg(fps).arg(format); QString data = QString("%1,%2,%3,%4").arg(width, height, fps).arg(format);
QString data = QString("%1,%2,%3,%4").arg(width).arg(height).arg(fps).arg(format);
m_formatCombo->addItem(displayText, data); m_formatCombo->addItem(displayText, data);
} }

View File

@@ -24,26 +24,21 @@ MainWindow::~MainWindow()
void MainWindow::setupUI() void MainWindow::setupUI()
{ {
// Create socket client
m_socketClient = new SocketClient("/tmp/vizion_control.sock", this); m_socketClient = new SocketClient("/tmp/vizion_control.sock", this);
// Create widgets
m_videoWidget = new VideoViewerWidget(this); m_videoWidget = new VideoViewerWidget(this);
m_pipelineWidget = new GStreamerPipelineWidget(m_socketClient, this); m_pipelineWidget = new GStreamerPipelineWidget(m_socketClient, this);
m_cameraWidget = new CameraControlWidget(m_socketClient, this); m_cameraWidget = new CameraControlWidget(m_socketClient, this);
// Create tab widget for controls on the right auto* controlTabs = new QTabWidget(this);
QTabWidget* controlTabs = new QTabWidget(this);
controlTabs->addTab(m_pipelineWidget, "Pipeline Control"); controlTabs->addTab(m_pipelineWidget, "Pipeline Control");
controlTabs->addTab(m_cameraWidget, "Camera Control"); controlTabs->addTab(m_cameraWidget, "Camera Control");
// Create horizontal splitter: video on left (full height), controls on right auto* mainSplitter = new QSplitter(Qt::Horizontal, this);
QSplitter* mainSplitter = new QSplitter(Qt::Horizontal, this);
mainSplitter->addWidget(m_videoWidget); mainSplitter->addWidget(m_videoWidget);
mainSplitter->addWidget(controlTabs); mainSplitter->addWidget(controlTabs);
mainSplitter->setStretchFactor(0, 2); // Video gets more space (2/3) mainSplitter->setStretchFactor(0, 2); // Video gets more space (2/3)
mainSplitter->setStretchFactor(1, 1); // Controls get less space (1/3) mainSplitter->setStretchFactor(1, 1); // Controls get less space (1/3)
// Set as central widget
setCentralWidget(mainSplitter); setCentralWidget(mainSplitter);
} }

View File

@@ -8,16 +8,25 @@
#include <gst/video/video.h> #include <gst/video/video.h>
VideoViewerWidget::VideoViewerWidget(QWidget *parent) VideoViewerWidget::VideoViewerWidget(QWidget *parent)
: QWidget(parent), m_pipeline(nullptr), m_appSink(nullptr), : QWidget(parent),
m_busWatchId(0), m_zoomFactor(1.0) m_scrollArea(nullptr),
m_videoDisplay(nullptr),
m_startBtn(nullptr),
m_stopBtn(nullptr),
m_sourceType(nullptr),
m_hostEdit(nullptr),
m_portEdit(nullptr),
m_statusLabel(nullptr),
m_zoomSlider(nullptr),
m_zoomLabel(nullptr),
m_pipeline(nullptr),
m_appSink(nullptr),
m_zoomFactor(1.0),
m_busWatchId(0)
{ {
// Register QImage as meta type for signal/slot across threads
qRegisterMetaType<QImage>("QImage");
initGStreamer(); initGStreamer();
setupUI(); setupUI();
// Connect signal for frame display
connect(this, &VideoViewerWidget::newFrameAvailable, connect(this, &VideoViewerWidget::newFrameAvailable,
this, &VideoViewerWidget::displayFrame, Qt::QueuedConnection); this, &VideoViewerWidget::displayFrame, Qt::QueuedConnection);
} }
@@ -34,11 +43,10 @@ void VideoViewerWidget::initGStreamer()
void VideoViewerWidget::setupUI() void VideoViewerWidget::setupUI()
{ {
QVBoxLayout* mainLayout = new QVBoxLayout(this); auto* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(5); mainLayout->setSpacing(5);
// Video display in scroll area for zoom support
m_scrollArea = new QScrollArea(this); m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(false); m_scrollArea->setWidgetResizable(false);
m_scrollArea->setAlignment(Qt::AlignCenter); m_scrollArea->setAlignment(Qt::AlignCenter);
@@ -52,8 +60,7 @@ void VideoViewerWidget::setupUI()
m_scrollArea->setWidget(m_videoDisplay); m_scrollArea->setWidget(m_videoDisplay);
// Zoom control auto* zoomLayout = new QHBoxLayout();
QHBoxLayout* zoomLayout = new QHBoxLayout();
zoomLayout->addWidget(new QLabel("Zoom:", this)); zoomLayout->addWidget(new QLabel("Zoom:", this));
m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider = new QSlider(Qt::Horizontal, this);
@@ -70,12 +77,10 @@ void VideoViewerWidget::setupUI()
zoomLayout->addWidget(m_zoomSlider); zoomLayout->addWidget(m_zoomSlider);
zoomLayout->addWidget(m_zoomLabel); zoomLayout->addWidget(m_zoomLabel);
// Controls auto* controlGroup = new QGroupBox("Viewer Controls", this);
QGroupBox* controlGroup = new QGroupBox("Viewer Controls", this); auto* controlLayout = new QVBoxLayout();
QVBoxLayout* controlLayout = new QVBoxLayout();
// Source type selection auto* sourceLayout = new QHBoxLayout();
QHBoxLayout* sourceLayout = new QHBoxLayout();
sourceLayout->addWidget(new QLabel("Source Type:", this)); sourceLayout->addWidget(new QLabel("Source Type:", this));
m_sourceType = new QComboBox(this); m_sourceType = new QComboBox(this);
m_sourceType->addItem("UDP MJPEG Stream (No plugins needed)", "udp-mjpeg"); m_sourceType->addItem("UDP MJPEG Stream (No plugins needed)", "udp-mjpeg");
@@ -87,15 +92,13 @@ void VideoViewerWidget::setupUI()
this, &VideoViewerWidget::onSourceTypeChanged); this, &VideoViewerWidget::onSourceTypeChanged);
sourceLayout->addWidget(m_sourceType); sourceLayout->addWidget(m_sourceType);
// Host and port auto* formLayout = new QFormLayout();
QFormLayout* formLayout = new QFormLayout();
m_hostEdit = new QLineEdit("127.0.0.1", this); m_hostEdit = new QLineEdit("127.0.0.1", this);
m_portEdit = new QLineEdit("5000", this); m_portEdit = new QLineEdit("5000", this);
formLayout->addRow("Host:", m_hostEdit); formLayout->addRow("Host:", m_hostEdit);
formLayout->addRow("Port:", m_portEdit); formLayout->addRow("Port:", m_portEdit);
// Control buttons auto* buttonLayout = new QHBoxLayout();
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_startBtn = new QPushButton("Start Viewer", this); m_startBtn = new QPushButton("Start Viewer", this);
m_stopBtn = new QPushButton("Stop Viewer", this); m_stopBtn = new QPushButton("Stop Viewer", this);
m_stopBtn->setEnabled(false); m_stopBtn->setEnabled(false);
@@ -106,7 +109,6 @@ void VideoViewerWidget::setupUI()
buttonLayout->addWidget(m_startBtn); buttonLayout->addWidget(m_startBtn);
buttonLayout->addWidget(m_stopBtn); buttonLayout->addWidget(m_stopBtn);
// Status label
m_statusLabel = new QLabel("Status: Stopped", this); m_statusLabel = new QLabel("Status: Stopped", this);
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
@@ -116,7 +118,6 @@ void VideoViewerWidget::setupUI()
controlLayout->addWidget(m_statusLabel); controlLayout->addWidget(m_statusLabel);
controlGroup->setLayout(controlLayout); controlGroup->setLayout(controlLayout);
// Add to main layout: video takes most space, zoom control, then viewer controls at bottom
mainLayout->addWidget(m_scrollArea, 1); mainLayout->addWidget(m_scrollArea, 1);
mainLayout->addLayout(zoomLayout); mainLayout->addLayout(zoomLayout);
mainLayout->addWidget(controlGroup); mainLayout->addWidget(controlGroup);
@@ -124,8 +125,7 @@ void VideoViewerWidget::setupUI()
setLayout(mainLayout); setLayout(mainLayout);
} }
QString VideoViewerWidget::buildPipelineString() QString VideoViewerWidget::buildPipelineString() const {
{
QString sourceType = m_sourceType->currentData().toString(); QString sourceType = m_sourceType->currentData().toString();
QString host = m_hostEdit->text(); QString host = m_hostEdit->text();
QString port = m_portEdit->text(); QString port = m_portEdit->text();
@@ -136,19 +136,17 @@ QString VideoViewerWidget::buildPipelineString()
QString sinkPipeline = "videoconvert ! video/x-raw,format=RGB ! appsink name=videosink emit-signals=true"; QString sinkPipeline = "videoconvert ! video/x-raw,format=RGB ! appsink name=videosink emit-signals=true";
if (sourceType == "udp-mjpeg") { if (sourceType == "udp-mjpeg") {
pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=JPEG,payload=26 ! " pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! %2")
"rtpjpegdepay ! jpegdec ! %2") .arg(port, sinkPipeline);
.arg(port).arg(sinkPipeline);
} else if (sourceType == "udp-h264") { } else if (sourceType == "udp-h264") {
pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=H264 ! " pipeline = QString("udpsrc port=%1 ! application/x-rtp,encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! %2")
"rtph264depay ! h264parse ! avdec_h264 ! %2") .arg(port, sinkPipeline);
.arg(port).arg(sinkPipeline);
} else if (sourceType == "tcp") { } else if (sourceType == "tcp") {
pipeline = QString("tcpclientsrc host=%1 port=%2 ! tsdemux ! h264parse ! avdec_h264 ! %3") pipeline = QString("tcpclientsrc host=%1 port=%2 ! tsdemux ! h264parse ! avdec_h264 ! %3")
.arg(host).arg(port).arg(sinkPipeline); .arg(host, port, sinkPipeline);
} else if (sourceType == "http") { } else if (sourceType == "http") {
pipeline = QString("souphttpsrc location=http://%1:%2 ! multipartdemux ! jpegdec ! %3") pipeline = QString("souphttpsrc location=http://%1:%2 ! multipartdemux ! jpegdec ! %3")
.arg(host).arg(port).arg(sinkPipeline); .arg(host, port, sinkPipeline);
} else if (sourceType == "test") { } else if (sourceType == "test") {
pipeline = QString("videotestsrc ! %1").arg(sinkPipeline); pipeline = QString("videotestsrc ! %1").arg(sinkPipeline);
} }
@@ -182,12 +180,10 @@ void VideoViewerWidget::startPipeline()
return; return;
} }
// Set up bus callback
GstBus* bus = gst_element_get_bus(m_pipeline); GstBus* bus = gst_element_get_bus(m_pipeline);
m_busWatchId = gst_bus_add_watch(bus, busCallback, this); m_busWatchId = gst_bus_add_watch(bus, busCallback, this);
gst_object_unref(bus); gst_object_unref(bus);
// Get appsink element and configure it
m_appSink = gst_bin_get_by_name(GST_BIN(m_pipeline), "videosink"); m_appSink = gst_bin_get_by_name(GST_BIN(m_pipeline), "videosink");
if (!m_appSink) { if (!m_appSink) {
m_statusLabel->setText("Status: Failed to get appsink element"); m_statusLabel->setText("Status: Failed to get appsink element");
@@ -196,10 +192,9 @@ void VideoViewerWidget::startPipeline()
return; return;
} }
// Configure appsink
g_object_set(m_appSink, "emit-signals", TRUE, "sync", FALSE, "max-buffers", 1, "drop", TRUE, nullptr); g_object_set(m_appSink, "emit-signals", TRUE, "sync", FALSE, "max-buffers", 1, "drop", TRUE, nullptr);
// Set callback for new samples - properly initialize all fields // Properly initialize all callback fields
GstAppSinkCallbacks callbacks = { 0 }; GstAppSinkCallbacks callbacks = { 0 };
callbacks.new_sample = newSampleCallback; callbacks.new_sample = newSampleCallback;
callbacks.eos = nullptr; callbacks.eos = nullptr;
@@ -209,7 +204,6 @@ void VideoViewerWidget::startPipeline()
#endif #endif
gst_app_sink_set_callbacks(GST_APP_SINK(m_appSink), &callbacks, this, nullptr); gst_app_sink_set_callbacks(GST_APP_SINK(m_appSink), &callbacks, this, nullptr);
// Start playing
GstStateChangeReturn ret = gst_element_set_state(m_pipeline, GST_STATE_PLAYING); GstStateChangeReturn ret = gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
qDebug() << "[VideoViewer] Pipeline state change return:" << ret; qDebug() << "[VideoViewer] Pipeline state change return:" << ret;
@@ -246,7 +240,6 @@ void VideoViewerWidget::stopPipeline()
m_busWatchId = 0; m_busWatchId = 0;
} }
// Clear video display and current frame
m_videoDisplay->clear(); m_videoDisplay->clear();
m_videoDisplay->setText(""); m_videoDisplay->setText("");
m_currentFrame = QImage(); m_currentFrame = QImage();
@@ -354,7 +347,6 @@ void VideoViewerWidget::onZoomChanged(int value)
m_zoomFactor = value / 100.0; m_zoomFactor = value / 100.0;
m_zoomLabel->setText(QString("%1%").arg(value)); m_zoomLabel->setText(QString("%1%").arg(value));
// Re-display the current frame with new zoom factor
if (!m_currentFrame.isNull()) { if (!m_currentFrame.isNull()) {
displayFrame(m_currentFrame); displayFrame(m_currentFrame);
} }
@@ -377,14 +369,12 @@ GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Pull the sample from appsink
GstSample* sample = gst_app_sink_pull_sample(appsink); GstSample* sample = gst_app_sink_pull_sample(appsink);
if (!sample) { if (!sample) {
qDebug() << "[VideoViewer] Callback: Failed to pull sample"; qDebug() << "[VideoViewer] Callback: Failed to pull sample";
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Get the buffer from the sample
GstBuffer* buffer = gst_sample_get_buffer(sample); GstBuffer* buffer = gst_sample_get_buffer(sample);
if (!buffer) { if (!buffer) {
qDebug() << "[VideoViewer] Callback: No buffer in sample"; qDebug() << "[VideoViewer] Callback: No buffer in sample";
@@ -392,7 +382,6 @@ GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Get the caps to extract width and height
GstCaps* caps = gst_sample_get_caps(sample); GstCaps* caps = gst_sample_get_caps(sample);
if (!caps) { if (!caps) {
qDebug() << "[VideoViewer] Callback: No caps in sample"; qDebug() << "[VideoViewer] Callback: No caps in sample";
@@ -409,7 +398,6 @@ GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Map the buffer to access the raw data
GstMapInfo map; GstMapInfo map;
if (!gst_buffer_map(buffer, &map, GST_MAP_READ)) { if (!gst_buffer_map(buffer, &map, GST_MAP_READ)) {
qDebug() << "[VideoViewer] Callback: Failed to map buffer"; qDebug() << "[VideoViewer] Callback: Failed to map buffer";
@@ -417,7 +405,7 @@ GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Calculate expected size for RGB888 format // RGB888 format: width * height * 3 bytes
gsize expected_size = width * height * 3; gsize expected_size = width * height * 3;
if (map.size < expected_size) { if (map.size < expected_size) {
qDebug() << "[VideoViewer] Callback: Buffer too small. Expected:" << expected_size << "Got:" << map.size; qDebug() << "[VideoViewer] Callback: Buffer too small. Expected:" << expected_size << "Got:" << map.size;
@@ -426,14 +414,12 @@ GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
// Create QImage from the RGB data with proper stride // QImage requires RGB data with proper stride (width * 3 bytes per row)
// QImage::Format_RGB888 expects RGB data
QImage frame(map.data, width, height, width * 3, QImage::Format_RGB888); QImage frame(map.data, width, height, width * 3, QImage::Format_RGB888);
// Make a deep copy since the buffer will be unmapped // Make a deep copy since the buffer will be unmapped
QImage frameCopy = frame.copy(); QImage frameCopy = frame.copy();
// Unmap and cleanup
gst_buffer_unmap(buffer, &map); gst_buffer_unmap(buffer, &map);
gst_sample_unref(sample); gst_sample_unref(sample);
@@ -465,20 +451,16 @@ void VideoViewerWidget::displayFrame(const QImage& frame)
qDebug() << "[VideoViewer] Frames received:" << frameCount; qDebug() << "[VideoViewer] Frames received:" << frameCount;
} }
// Store current frame for zoom changes
m_currentFrame = frame; m_currentFrame = frame;
// Convert QImage to QPixmap
QPixmap pixmap = QPixmap::fromImage(frame); QPixmap pixmap = QPixmap::fromImage(frame);
if (pixmap.isNull()) { if (pixmap.isNull()) {
qDebug() << "[VideoViewer] ERROR: Pixmap conversion failed!"; qDebug() << "[VideoViewer] ERROR: Pixmap conversion failed!";
return; return;
} }
// Calculate target size with zoom factor
QSize targetSize = frame.size() * m_zoomFactor; QSize targetSize = frame.size() * m_zoomFactor;
// Scale pixmap with zoom factor
QPixmap scaledPixmap = pixmap.scaled(targetSize, QPixmap scaledPixmap = pixmap.scaled(targetSize,
Qt::KeepAspectRatio, Qt::KeepAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
@@ -488,7 +470,6 @@ void VideoViewerWidget::displayFrame(const QImage& frame)
<< "Scaled pixmap:" << scaledPixmap.size(); << "Scaled pixmap:" << scaledPixmap.size();
} }
// Update the label size to match the scaled pixmap
m_videoDisplay->setPixmap(scaledPixmap); m_videoDisplay->setPixmap(scaledPixmap);
m_videoDisplay->resize(scaledPixmap.size()); m_videoDisplay->resize(scaledPixmap.size());
m_videoDisplay->update(); m_videoDisplay->update();

View File

@@ -18,7 +18,7 @@ class VideoViewerWidget : public QWidget
public: public:
explicit VideoViewerWidget(QWidget *parent = nullptr); explicit VideoViewerWidget(QWidget *parent = nullptr);
~VideoViewerWidget(); ~VideoViewerWidget() override;
signals: signals:
void newFrameAvailable(const QImage& frame); void newFrameAvailable(const QImage& frame);
@@ -36,12 +36,12 @@ private:
void cleanupGStreamer(); void cleanupGStreamer();
void startPipeline(); void startPipeline();
void stopPipeline(); void stopPipeline();
QString buildPipelineString(); QString buildPipelineString() const;
static gboolean busCallback(GstBus* bus, GstMessage* msg, gpointer data); static gboolean busCallback(GstBus* bus, GstMessage* msg, gpointer data);
static GstFlowReturn newSampleCallback(GstAppSink* appsink, gpointer user_data); static GstFlowReturn newSampleCallback(GstAppSink* appsink, gpointer user_data);
// UI elements // UI elements (8-byte pointers)
QScrollArea* m_scrollArea; QScrollArea* m_scrollArea;
QLabel* m_videoDisplay; QLabel* m_videoDisplay;
QPushButton* m_startBtn; QPushButton* m_startBtn;
@@ -53,14 +53,18 @@ private:
QSlider* m_zoomSlider; QSlider* m_zoomSlider;
QLabel* m_zoomLabel; QLabel* m_zoomLabel;
// GStreamer elements // GStreamer elements (8-byte pointers)
GstElement* m_pipeline; GstElement* m_pipeline;
GstElement* m_appSink; GstElement* m_appSink;
// Zoom factor (8-byte double)
double m_zoomFactor;
// Bus watch ID (4-byte unsigned int)
guint m_busWatchId; guint m_busWatchId;
// Video state // Video state (large object at end)
QImage m_currentFrame; QImage m_currentFrame;
double m_zoomFactor;
}; };
#endif // VIDEOVIEWERWIDGET_H #endif // VIDEOVIEWERWIDGET_H