#include "DeviceDialog.h" #include "VideoWidget.h" // für kSharpenMin/Max (eine Quelle der Wahrheit) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int xioctl(int fd, unsigned long req, void* arg) { int r; do { r = ioctl(fd, req, arg); } while (r == -1 && errno == EINTR); return r; } DeviceDialog::DeviceDialog(QWidget* parent) : QDialog(parent) { setWindowTitle("HDMI Viewer – Gerät auswählen"); setMinimumWidth(520); auto* form = new QFormLayout; m_videoCombo = new QComboBox(this); m_audioCombo = new QComboBox(this); m_fpsCombo = new QComboBox(this); m_videoCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); m_audioCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); populateVideoDevices(); populateAudioDevices(); // FPS-Optionen: gängige Werte für HDMI-Grabber for (int fps : {10, 15, 20, 24, 25, 30, 50, 60}) m_fpsCombo->addItem(QString::number(fps) + " fps", fps); // Default: 30 setPreselectedFps(30); form->addRow("Video-Gerät (V4L2):", m_videoCombo); form->addRow("Audio-Gerät (ALSA):", m_audioCombo); form->addRow("Ziel-Framerate:", m_fpsCombo); m_smoothCheck = new QCheckBox("Bildglättung aktivieren (empfohlen)", this); m_smoothCheck->setChecked(true); m_smoothCheck->setToolTip( "Reduziert live Kompressions-/Scaler-Artefakte deines HDMI-Upscalers:\n" "Farbrauschen/Dot-Crawl wird weicher gezeichnet, harte blockige\n" "Pixelkanten werden geglättet und dezent gegengeschärft.\n" "Das ist keine Optik-Spielerei, sondern eine Bildverbesserung –\n" "empfohlen für Composite-Quellen wie PS1/PS2 an billigen Scalern.\n" "Umschaltbar im laufenden Betrieb mit [S]."); form->addRow("", m_smoothCheck); m_retroCheck = new QCheckBox("Retro-Optik (Scanlines/Maske) aktivieren", this); m_retroCheck->setToolTip( "Rein optischer Nostalgie-Look: Scanlines, Phosphor-Maske,\n" "leichte Wölbung wie bei einem alten Röhrenfernseher.\n" "Lässt sich mit der Bildglättung kombinieren.\n" "Umschaltbar im laufenden Betrieb mit [C]."); form->addRow("", m_retroCheck); m_sharpenSpin = new QDoubleSpinBox(this); m_sharpenSpin->setRange(VideoWidget::kSharpenMin, VideoWidget::kSharpenMax); m_sharpenSpin->setSingleStep(VideoWidget::kSharpenStep); m_sharpenSpin->setDecimals(1); m_sharpenSpin->setValue(0.0); m_sharpenSpin->setSpecialValueText("aus"); m_sharpenSpin->setToolTip( "Unsharp Mask übers gesamte Bild, unabhängig von der Bildglättung.\n" "0 = aus. Im laufenden Betrieb mit [X]/[+]/[-] änderbar."); form->addRow("Nachschärfen:", m_sharpenSpin); m_hwBrightnessSpin = new QSpinBox(this); m_hwBrightnessSpin->setRange(-1, 100); m_hwBrightnessSpin->setValue(-1); m_hwBrightnessSpin->setSpecialValueText("unverändert"); m_hwBrightnessSpin->setToolTip( "Hardware-Helligkeit direkt am Grabber-Chip, VOR der JPEG-Kompression\n" "(Wertebereich hängt vom jeweiligen Chip ab, hier 0-100 angenommen).\n" "\"unverändert\" lässt den aktuellen Chip-Wert einfach in Ruhe.\n" "Im laufenden Betrieb mit [,]/[.] änderbar. hdmi-viewer merkt sich\n" "beim Start den Ausgangswert und stellt ihn beim Beenden wieder her."); form->addRow("Hardware-Helligkeit:", m_hwBrightnessSpin); m_hwContrastSpin = new QSpinBox(this); m_hwContrastSpin->setRange(-1, 100); m_hwContrastSpin->setValue(-1); m_hwContrastSpin->setSpecialValueText("unverändert"); m_hwContrastSpin->setToolTip( "Hardware-Kontrast direkt am Grabber-Chip, VOR der JPEG-Kompression\n" "(Wertebereich hängt vom jeweiligen Chip ab, hier 0-100 angenommen).\n" "\"unverändert\" lässt den aktuellen Chip-Wert einfach in Ruhe.\n" "Im laufenden Betrieb mit [1]/[2] änderbar. hdmi-viewer merkt sich\n" "beim Start den Ausgangswert und stellt ihn beim Beenden wieder her."); form->addRow("Hardware-Kontrast:", m_hwContrastSpin); m_hwSaturationSpin = new QSpinBox(this); m_hwSaturationSpin->setRange(-1, 100); m_hwSaturationSpin->setValue(-1); m_hwSaturationSpin->setSpecialValueText("unverändert"); m_hwSaturationSpin->setToolTip( "Hardware-Sättigung direkt am Grabber-Chip, VOR der JPEG-Kompression.\n" "\"unverändert\" lässt den aktuellen Chip-Wert einfach in Ruhe.\n" "Im laufenden Betrieb mit [3]/[4] änderbar. hdmi-viewer merkt sich\n" "beim Start den Ausgangswert und stellt ihn beim Beenden wieder her."); form->addRow("Hardware-Sättigung:", m_hwSaturationSpin); auto* hint = new QLabel( "Hinweis: Der Grabber liefert die gewünschte FPS-Zahl nur wenn " "genug USB-Bandbreite vorhanden ist. Die tatsächliche Rate wird in der " "Titelleiste angezeigt.
" "F Vollbild  ·  S Glättung  ·  " "C Retro  ·  X/+/- Schärfen  ·  " ",/. HW-Helligkeit  ·  1/2 HW-Kontrast  ·  " "3/4 HW-Sättigung  ·  V Vergleich  ·  " "P Screenshot  ·  Esc zurück hierher
", this); hint->setWordWrap(true); auto* btnBox = new QHBoxLayout; auto* btnOk = new QPushButton("Starten", this); auto* btnClose = new QPushButton("Beenden", this); btnOk->setDefault(true); btnBox->addStretch(); btnBox->addWidget(btnClose); btnBox->addWidget(btnOk); connect(btnOk, &QPushButton::clicked, this, &QDialog::accept); connect(btnClose, &QPushButton::clicked, this, &QDialog::reject); auto* main = new QVBoxLayout(this); main->addLayout(form); main->addSpacing(8); main->addWidget(hint); main->addSpacing(12); main->addLayout(btnBox); } QString DeviceDialog::selectedVideoDevice() const { return m_videoCombo->currentData().toString(); } QString DeviceDialog::selectedAudioDevice() const { return m_audioCombo->currentData().toString(); } int DeviceDialog::selectedFps() const { return m_fpsCombo->currentData().toInt(); } bool DeviceDialog::selectedSmoothMode() const { return m_smoothCheck->isChecked(); } bool DeviceDialog::selectedRetroMode() const { return m_retroCheck->isChecked(); } float DeviceDialog::selectedSharpenAmount() const { return static_cast(m_sharpenSpin->value()); } int DeviceDialog::selectedHwBrightness() const { return m_hwBrightnessSpin->value(); } int DeviceDialog::selectedHwContrast() const { return m_hwContrastSpin->value(); } int DeviceDialog::selectedHwSaturation() const { return m_hwSaturationSpin->value(); } void DeviceDialog::setPreselectedVideo(const QString& device) { selectByData(m_videoCombo, device); } void DeviceDialog::setPreselectedAudio(const QString& device) { selectByData(m_audioCombo, device); } void DeviceDialog::setPreselectedFps(int fps) { selectByData(m_fpsCombo, QString::number(fps)); } void DeviceDialog::setPreselectedSmoothMode(bool enabled) { m_smoothCheck->setChecked(enabled); } void DeviceDialog::setPreselectedRetroMode(bool enabled) { m_retroCheck->setChecked(enabled); } void DeviceDialog::setPreselectedSharpenAmount(float amount) { m_sharpenSpin->setValue(amount); } void DeviceDialog::setPreselectedHwBrightness(int value) { m_hwBrightnessSpin->setValue(value); } void DeviceDialog::setPreselectedHwContrast(int value) { m_hwContrastSpin->setValue(value); } void DeviceDialog::setPreselectedHwSaturation(int value) { m_hwSaturationSpin->setValue(value); } void DeviceDialog::selectByData(QComboBox* combo, const QString& data) { for (int i = 0; i < combo->count(); ++i) if (combo->itemData(i).toString() == data) { combo->setCurrentIndex(i); return; } } void DeviceDialog::populateVideoDevices() { QDir dev("/dev"); const QStringList entries = dev.entryList({"video*"}, QDir::System, QDir::Name); for (const QString& entry : entries) { const QString path = "/dev/" + entry; int fd = ::open(path.toLocal8Bit().constData(), O_RDWR | O_NONBLOCK); if (fd == -1) continue; v4l2_capability cap{}; QString label = path; if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) { if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { ::close(fd); continue; } const QString card = QString::fromLatin1( reinterpret_cast(cap.card), 32).trimmed(); if (!card.isEmpty()) label = QString("%1 [%2]").arg(path, card); } ::close(fd); m_videoCombo->addItem(label, path); } if (m_videoCombo->count() == 0) m_videoCombo->addItem("Kein V4L2-Gerät gefunden", ""); } void DeviceDialog::populateAudioDevices() { m_audioCombo->addItem("default [System-Standard]", "default"); void** hints = nullptr; if (snd_device_name_hint(-1, "pcm", &hints) < 0) return; for (void** hint = hints; *hint != nullptr; ++hint) { char* ioid = snd_device_name_get_hint(*hint, "IOID"); const bool isCapture = (ioid == nullptr || strcmp(ioid, "Input") == 0); free(ioid); if (!isCapture) continue; char* name = snd_device_name_get_hint(*hint, "NAME"); char* desc = snd_device_name_get_hint(*hint, "DESC"); if (name) { const QString n = QString::fromLatin1(name); const QString d = desc ? QString::fromLatin1(desc).replace('\n', ' ') : n; if (n != "default") m_audioCombo->addItem(QString("%1 [%2]").arg(n, d.left(60)), n); free(name); } if (desc) free(desc); } snd_device_name_free_hint(hints); }