234 lines
10 KiB
C++
234 lines
10 KiB
C++
#include "DeviceDialog.h"
|
||
#include "VideoWidget.h" // für 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_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(
|
||
"<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 · <b>S</b> Glättung · "
|
||
"<b>C</b> Retro · <b>X/+/-</b> Schärfen · "
|
||
"<b>,/.</b> HW-Helligkeit · <b>1/2</b> HW-Kontrast · "
|
||
"<b>3/4</b> HW-Sättigung · <b>V</b> Vergleich · "
|
||
"<b>P</b> Screenshot · <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()); }
|
||
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<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);
|
||
}
|