Erster Teststream absolviert. Positives Ergebnis

This commit is contained in:
2026-09-05 18:55:56 +02:00
parent 953c37d8d1
commit cc3527f86a
11 changed files with 1198 additions and 46 deletions

View File

@@ -9,18 +9,28 @@
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QSettings>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QFrame>
#include <QTimer>
#include <QDir>
#include <QDateTime>
MainWindow::MainWindow(const QString& videoDevice,
const QString& audioDevice,
int targetFps,
bool smoothModeEnabled,
bool retroModeEnabled,
float brightness,
float sharpenAmount,
float lastSharpenAmount,
QWidget* parent)
: QMainWindow(parent)
, m_lastSharpenAmount(lastSharpenAmount)
, m_videoDevice(videoDevice)
, m_audioDevice(audioDevice)
{
@@ -28,8 +38,20 @@ MainWindow::MainWindow(const QString& videoDevice,
resize(1280, 720);
m_video = new VideoWidget(this);
m_video->setSmoothModeEnabled(smoothModeEnabled);
m_video->setRetroModeEnabled(retroModeEnabled);
m_video->setSharpenAmount(sharpenAmount);
m_video->setBrightness(brightness);
setCentralWidget(m_video);
// Kurzzeitige Einblendung unten links (funktioniert auch im Vollbild,
// wo Menü- und Statusleiste ausgeblendet sind).
m_overlayLabel = new QLabel(m_video);
m_overlayLabel->setStyleSheet(
"background-color: rgba(20, 20, 20, 200); color: white;"
"padding: 6px 12px; border-radius: 4px; font-size: 13px;");
m_overlayLabel->hide();
setupMenuBar();
updateStatusBar();
@@ -45,6 +67,12 @@ MainWindow::MainWindow(const QString& videoDevice,
connect(m_capture, &CaptureThread::captureInfo,
this, &MainWindow::onCaptureInfo,
Qt::QueuedConnection);
connect(m_capture, &CaptureThread::frameDumped,
this, &MainWindow::onFrameDumped,
Qt::QueuedConnection);
connect(m_capture, &CaptureThread::hardwareControlChanged,
this, &MainWindow::onHardwareControlChanged,
Qt::QueuedConnection);
m_capture->start();
@@ -65,6 +93,54 @@ MainWindow::~MainWindow()
void MainWindow::setupMenuBar()
{
QMenu* viewMenu = menuBar()->addMenu("&Anzeige");
m_smoothAction = viewMenu->addAction("Bildglättung\tS");
m_smoothAction->setCheckable(true);
m_smoothAction->setChecked(m_video->smoothModeEnabled());
connect(m_smoothAction, &QAction::triggered, this, &MainWindow::toggleSmoothMode);
m_retroAction = viewMenu->addAction("Retro-Optik (Scanlines/Maske)\tC");
m_retroAction->setCheckable(true);
m_retroAction->setChecked(m_video->retroModeEnabled());
connect(m_retroAction, &QAction::triggered, this, &MainWindow::toggleRetroMode);
m_sharpenAction = viewMenu->addAction("Nachschärfen\tX");
m_sharpenAction->setCheckable(true);
m_sharpenAction->setChecked(m_video->sharpenAmount() > 0.0f);
connect(m_sharpenAction, &QAction::triggered, this, &MainWindow::toggleSharpen);
QAction* sharpenUpAct = viewMenu->addAction("Schärfen +\t+");
connect(sharpenUpAct, &QAction::triggered, this, &MainWindow::increaseSharpen);
QAction* sharpenDownAct = viewMenu->addAction("Schärfen -\t-");
connect(sharpenDownAct, &QAction::triggered, this, &MainWindow::decreaseSharpen);
viewMenu->addSeparator();
QAction* brighterAct = viewMenu->addAction("Heller\t.");
connect(brighterAct, &QAction::triggered, this, &MainWindow::increaseBrightness);
QAction* darkerAct = viewMenu->addAction("Dunkler\t,");
connect(darkerAct, &QAction::triggered, this, &MainWindow::decreaseBrightness);
viewMenu->addSeparator();
QAction* rawAct = viewMenu->addAction("Roh-Modus (Diagnose)\tR");
rawAct->setCheckable(true);
rawAct->setChecked(m_video->rawEnabled());
connect(rawAct, &QAction::triggered, this, &MainWindow::toggleRaw);
QAction* compareAct = viewMenu->addAction("Live-Vergleich (roh/bearbeitet)\tV");
compareAct->setCheckable(true);
compareAct->setChecked(m_video->compareEnabled());
connect(compareAct, &QAction::triggered, this, &MainWindow::toggleCompare);
viewMenu->addSeparator();
QAction* screenshotAct = viewMenu->addAction("Screenshot speichern\tP");
connect(screenshotAct, &QAction::triggered, this, &MainWindow::saveScreenshot);
QMenu* helpMenu = menuBar()->addMenu("&Hilfe");
QAction* aboutAct = helpMenu->addAction("Über HDMI Viewer");
@@ -72,11 +148,155 @@ void MainWindow::setupMenuBar()
connect(aboutAct, &QAction::triggered, this, &MainWindow::showAbout);
}
void MainWindow::showOverlayMessage(const QString& text)
{
if (!m_overlayLabel) return;
m_overlayLabel->setText(text);
m_overlayLabel->adjustSize();
const int margin = 16;
m_overlayLabel->move(margin, m_video->height() - m_overlayLabel->height() - margin);
m_overlayLabel->show();
m_overlayLabel->raise();
QTimer::singleShot(1500, m_overlayLabel, &QLabel::hide);
}
void MainWindow::toggleSmoothMode()
{
const bool enabled = !m_video->smoothModeEnabled();
m_video->setSmoothModeEnabled(enabled);
if (m_smoothAction) m_smoothAction->setChecked(enabled);
QSettings settings;
settings.setValue("display/smooth", enabled);
statusBar()->showMessage(
enabled ? "Bildglättung aktiviert" : "Bildglättung deaktiviert", 2000);
showOverlayMessage(enabled ? "Bildglättung: an" : "Bildglättung: aus");
}
void MainWindow::toggleRetroMode()
{
const bool enabled = !m_video->retroModeEnabled();
m_video->setRetroModeEnabled(enabled);
if (m_retroAction) m_retroAction->setChecked(enabled);
QSettings settings;
settings.setValue("display/retro", enabled);
statusBar()->showMessage(
enabled ? "Retro-Optik aktiviert" : "Retro-Optik deaktiviert", 2000);
showOverlayMessage(enabled ? "Retro-Optik: an" : "Retro-Optik: aus");
}
void MainWindow::toggleSharpen()
{
// Quick-Toggle: aus (0) ↔ zuletzt benutzter Wert (nicht der feste
// Standardwert!) merkt sich also z. B. 7.0, nicht immer 2.5.
if (m_video->sharpenAmount() > 0.0f) {
m_lastSharpenAmount = m_video->sharpenAmount();
applySharpen(0.0f);
} else {
applySharpen(m_lastSharpenAmount > 0.0f ? m_lastSharpenAmount : VideoWidget::kSharpenDefault);
}
}
void MainWindow::increaseSharpen()
{
applySharpen(m_video->sharpenAmount() + VideoWidget::kSharpenStep);
}
void MainWindow::decreaseSharpen()
{
applySharpen(m_video->sharpenAmount() - VideoWidget::kSharpenStep);
}
void MainWindow::applySharpen(float value)
{
value = qBound(VideoWidget::kSharpenMin, value, VideoWidget::kSharpenMax);
m_video->setSharpenAmount(value);
if (m_sharpenAction) m_sharpenAction->setChecked(value > 0.0f);
QSettings settings;
settings.setValue("display/sharpen", static_cast<double>(value));
if (value > 0.0f) {
m_lastSharpenAmount = value;
settings.setValue("display/sharpen_last", static_cast<double>(value));
}
statusBar()->showMessage(QString("Nachschärfen: %1").arg(value, 0, 'f', 1), 2000);
showOverlayMessage(QString("Nachschärfen: %1").arg(value, 0, 'f', 1));
}
void MainWindow::increaseBrightness()
{
applyBrightness(m_video->brightness() + VideoWidget::kBrightnessStep);
}
void MainWindow::decreaseBrightness()
{
applyBrightness(m_video->brightness() - VideoWidget::kBrightnessStep);
}
void MainWindow::toggleRaw()
{
const bool enabled = !m_video->rawEnabled();
m_video->setRawEnabled(enabled);
const QString msg = enabled ? "Roh-Modus: an (alle Effekte umgangen)" : "Roh-Modus: aus";
statusBar()->showMessage(msg, 2000);
showOverlayMessage(msg);
}
void MainWindow::toggleCompare()
{
const bool enabled = !m_video->compareEnabled();
m_video->setCompareEnabled(enabled);
const QString msg = enabled ? "Vergleich: an (links roh, rechts bearbeitet)" : "Vergleich: aus";
statusBar()->showMessage(msg, 2000);
showOverlayMessage(msg);
}
void MainWindow::saveScreenshot()
{
const QImage img = m_video->grabProcessedFrame();
if (img.isNull()) {
statusBar()->showMessage("Screenshot fehlgeschlagen (kein Bild)", 3000);
return;
}
const QString path = QDir::homePath() + "/hdmiviewer_screenshot_" +
QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss") + ".png";
if (img.save(path, "PNG")) {
statusBar()->showMessage("Screenshot gespeichert: " + path, 5000);
showOverlayMessage("Screenshot gespeichert");
} else {
statusBar()->showMessage("Screenshot konnte nicht gespeichert werden: " + path, 5000);
}
}
void MainWindow::applyBrightness(float value)
{
value = qBound(VideoWidget::kBrightnessMin, value, VideoWidget::kBrightnessMax);
m_video->setBrightness(value);
QSettings settings;
settings.setValue("display/brightness", static_cast<double>(value));
statusBar()->showMessage(
QString("Helligkeit: %1 %").arg(qRound(value * 100.0f)), 2000);
showOverlayMessage(QString("Helligkeit: %1 %").arg(qRound(value * 100.0f)));
}
void MainWindow::showAbout()
{
auto* dlg = new QDialog(this);
dlg->setWindowTitle("Über HDMI Viewer");
dlg->setFixedSize(420, 340);
dlg->setFixedSize(460, 470);
dlg->setModal(true);
auto* root = new QVBoxLayout(dlg);
@@ -125,26 +345,37 @@ void MainWindow::showAbout()
grid->addWidget(v, row, 1);
};
addRow(0, "Version", "1.3.0");
addRow(0, "Version", "1.4.0");
addRow(1, "Entwickler", "Dany Thinnes");
addRow(2, "Projekt", "Projekt Hirnfrei");
addRow(3, "Framework", QString("Qt %1").arg(QT_VERSION_STR));
addRow(4, "Sprache", "C++17");
addRow(5, "Video-API", "V4L2 · MJPEG / YUYV / NV12");
addRow(6, "Audio-API", "ALSA");
addRow(7, "Rendering", "OpenGL 3.3 · Bildglättung / Retro-Optik");
// ── Tastenkürzel-Hinweis ──────────────────────────────────────────────────
auto* sep2 = new QFrame(body);
sep2->setFrameShape(QFrame::HLine);
sep2->setStyleSheet("color: #313244;");
grid->addWidget(sep2, 7, 0, 1, 2);
grid->addWidget(sep2, 8, 0, 1, 2);
auto* keysLabel = new QLabel(
"<small><b>F</b> &nbsp;Vollbild ein/aus &nbsp;&nbsp; "
"<b>Esc</b> &nbsp;Zurück zu Einstellungen</small>", body);
"<small><b>F</b> Vollbild &nbsp;&nbsp; "
"<b>S</b> Glättung &nbsp;&nbsp; "
"<b>C</b> Retro &nbsp;&nbsp; "
"<b>X</b>/<b>+</b>/<b>-</b> Schärfen &nbsp;&nbsp; "
"<b>,</b>/<b>.</b> Helligkeit<br>"
"<b>1</b>/<b>2</b> HW-Kontrast &nbsp;&nbsp; "
"<b>3</b>/<b>4</b> HW-Sättigung &nbsp;&nbsp; "
"<b>R</b> Roh-Diagnose<br>"
"<b>V</b> Vergleich &nbsp;&nbsp; "
"<b>P</b> Screenshot &nbsp;&nbsp; "
"<b>D</b> Frame dumpen &nbsp;&nbsp; "
"<b>Esc</b> Zurück</small>", body);
keysLabel->setStyleSheet("color: #6c7086;");
keysLabel->setAlignment(Qt::AlignCenter);
grid->addWidget(keysLabel, 8, 0, 1, 2);
grid->addWidget(keysLabel, 9, 0, 1, 2);
// ── Schließen-Button ──────────────────────────────────────────────────────
auto* btnRow = new QHBoxLayout;
@@ -194,6 +425,52 @@ void MainWindow::keyPressEvent(QKeyEvent* event)
close();
}
break;
case Qt::Key_S:
toggleSmoothMode();
break;
case Qt::Key_C:
toggleRetroMode();
break;
case Qt::Key_X:
toggleSharpen();
break;
case Qt::Key_Plus:
case Qt::Key_Equal: // '+' liegt auf vielen Layouts ohne Shift auf dieser Taste
increaseSharpen();
break;
case Qt::Key_Minus:
decreaseSharpen();
break;
case Qt::Key_Period:
increaseBrightness();
break;
case Qt::Key_Comma:
decreaseBrightness();
break;
case Qt::Key_R:
toggleRaw();
break;
case Qt::Key_D:
if (m_capture) m_capture->requestFrameDump();
break;
case Qt::Key_V:
toggleCompare();
break;
case Qt::Key_P:
saveScreenshot();
break;
case Qt::Key_1:
if (m_capture) m_capture->adjustHardwareContrast(-5);
break;
case Qt::Key_2:
if (m_capture) m_capture->adjustHardwareContrast(5);
break;
case Qt::Key_3:
if (m_capture) m_capture->adjustHardwareSaturation(-5);
break;
case Qt::Key_4:
if (m_capture) m_capture->adjustHardwareSaturation(5);
break;
default:
QMainWindow::keyPressEvent(event);
}
@@ -226,7 +503,7 @@ void MainWindow::updateStatusBar()
info = QString("%1×%2 @ %3fps %4 ")
.arg(m_capWidth).arg(m_capHeight).arg(m_capFps).arg(m_capFmt);
info += QString("Video: %1 Audio: %2 [F] Vollbild [Esc] Einstellungen")
info += QString("Video: %1 Audio: %2 [F] Vollbild [S] Glättung [C] Retro [X/+/-] Schärfen [,/.] Helligkeit [1/2] HW-Kontrast [3/4] HW-Sättigung [R] Roh [V] Vergleich [P] Screenshot [D] Frame dumpen [Esc] Einstellungen")
.arg(m_videoDevice,
m_audioDevice.isEmpty() ? QStringLiteral("") : m_audioDevice);
@@ -244,6 +521,20 @@ void MainWindow::onCaptureInfo(int width, int height, int fps, const QString& fo
.arg(width).arg(height).arg(fps).arg(format));
}
void MainWindow::onFrameDumped(const QString& path)
{
const QString msg = "Frame gespeichert: " + path;
statusBar()->showMessage(msg, 5000);
showOverlayMessage("Frame gespeichert (siehe Home-Ordner)");
}
void MainWindow::onHardwareControlChanged(const QString& label, int value, int minVal, int maxVal)
{
const QString msg = QString("%1: %2 (%3..%4)").arg(label).arg(value).arg(minVal).arg(maxVal);
statusBar()->showMessage(msg, 2000);
showOverlayMessage(msg);
}
void MainWindow::onError(const QString& msg)
{
statusBar()->showMessage("" + msg, 8000);