Files
hdmiviewer/src/DeviceDialog.cpp

233 lines
9.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "DeviceDialog.h"
#include "VideoWidget.h" // für kBrightnessMin/Max, kSharpenMin/Max (eine Quelle der Wahrheit)
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QComboBox>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QSpinBox>
#include <QLabel>
#include <QPushButton>
#include <QDir>
#include <QDebug>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <alsa/asoundlib.h>
#include <cerrno>
#include <cstring>
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_brightnessSpin = new QDoubleSpinBox(this);
m_brightnessSpin->setRange(VideoWidget::kBrightnessMin * 100.0, VideoWidget::kBrightnessMax * 100.0);
m_brightnessSpin->setSingleStep(VideoWidget::kBrightnessStep * 100.0);
m_brightnessSpin->setDecimals(0);
m_brightnessSpin->setSuffix(" %");
m_brightnessSpin->setValue(100.0);
m_brightnessSpin->setToolTip(
"Gamma-Helligkeit, 100 % = unverändert. Im laufenden Betrieb\n"
"mit [,]/[.] änderbar.");
form->addRow("Helligkeit:", m_brightnessSpin);
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(
"<small><b>Hinweis:</b> Der Grabber liefert die gewünschte FPS-Zahl nur wenn "
"genug USB-Bandbreite vorhanden ist. Die tatsächliche Rate wird in der "
"Titelleiste angezeigt.<br>"
"<b>F</b> Vollbild &nbsp;·&nbsp; <b>S</b> Glättung &nbsp;·&nbsp; "
"<b>C</b> Retro &nbsp;·&nbsp; <b>X/+/-</b> Schärfen &nbsp;·&nbsp; "
"<b>,/.</b> Helligkeit &nbsp;·&nbsp; <b>1/2</b> HW-Kontrast &nbsp;·&nbsp; "
"<b>3/4</b> HW-Sättigung &nbsp;·&nbsp; <b>V</b> Vergleich &nbsp;·&nbsp; "
"<b>P</b> Screenshot &nbsp;·&nbsp; <b>Esc</b> zurück hierher</small>",
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<float>(m_sharpenSpin->value()); }
float DeviceDialog::selectedBrightness() const { return static_cast<float>(m_brightnessSpin->value() / 100.0); }
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::setPreselectedBrightness(float gamma) { m_brightnessSpin->setValue(gamma * 100.0); }
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<const char*>(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);
}