Files
utun2/tools/chatgui/src/stream_publish_window.cpp
T

367 lines
24 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 "stream_publish_window.h"
#include "sound_manager.h"
#include "debug_config.h"
#include <QVBoxLayout>
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QComboBox>
#include <QSpinBox>
#include <QCheckBox>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QButtonGroup>
#include <QPainter>
#include <QScrollArea>
#include <QSignalBlocker>
#include <QJsonDocument>
#include <QScopedValueRollback>
#ifdef HAVE_STREAM_CAPTURE
#include <QVideoWidget>
#include <QVideoSink>
#include <QVideoFrame>
#endif
namespace {
constexpr int MaximumManualBitrate = 12000000;
QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); }
QString bytes(uint64_t value) { return QString::number(value / 1024.0, 'f', 1) + " KiB"; }
}
StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t group, QWidget* parent, const QString& saved)
: QWidget(parent, Qt::Window), m_engine(engine), m_group(group) {
if (!saved.isEmpty()) {
QJsonParseError error; auto document = QJsonDocument::fromJson(saved.toUtf8(), &error);
if (error.error != QJsonParseError::NoError || !document.isObject())
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop capture settings invalid: %s", qPrintable(error.errorString()));
else m_settings = document.object();
}
setWindowTitle(text("Broadcast stream")); resize(720, 720); setMinimumSize(460, 480);
auto* layout = new QVBoxLayout(this);
#ifdef HAVE_STREAM_CAPTURE
auto* preview = new QVideoWidget(this); m_preview = preview;
preview->setMinimumHeight(160); preview->setAspectRatioMode(Qt::KeepAspectRatio);
preview->videoSink()->setVideoFrame(engine->previewFrame().value<QVideoFrame>());
connect(engine, &StreamPublishEngine::previewVideoChanged, this, [this, preview](const QVariant& frame) {
auto video = frame.value<QVideoFrame>();
if (!video.isValid() || isVisible()) preview->videoSink()->setVideoFrame(video);
});
#else
m_preview = new QLabel(text("Video capture requires Qt Multimedia and FFmpeg"), this);
#endif
layout->addWidget(m_preview, 1);
auto* scroll = new QScrollArea(this); scroll->setWidgetResizable(true);
auto* settings = new QWidget(scroll); auto* form = new QFormLayout(settings);
form->setRowWrapPolicy(QFormLayout::WrapLongRows);
scroll->setWidget(settings); scroll->setMinimumHeight(220); layout->addWidget(scroll);
m_source = new QComboBox(settings); m_source->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_source->setMinimumContentsLength(16); m_source->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
auto* sourceRow = new QWidget(settings); auto* sourceLayout = new QHBoxLayout(sourceRow); sourceLayout->setContentsMargins(0, 0, 0, 0);
sourceLayout->addWidget(m_source, 1); auto* refresh = new QPushButton(text("Refresh"), settings); sourceLayout->addWidget(refresh);
connect(refresh, &QPushButton::clicked, this, &StreamPublishWindow::refreshSources); form->addRow(text("Video source"), sourceRow);
connect(m_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] { m_source->setToolTip(m_source->currentText()); });
m_mode = new QComboBox(settings);
m_mode->setObjectName(QStringLiteral("streamVideoMode")); m_source->setObjectName(QStringLiteral("streamVideoSource"));
form->addRow(text("Video mode"), m_mode);
m_codec = new QComboBox(settings); form->addRow(text("Video codec"), m_codec);
m_bitrate = new QComboBox(settings); m_bitrate->addItem(text("Automatic"), 0);
for (int bitrate : {250000, 500000, 1000000, 2000000, 4000000, 8000000, MaximumManualBitrate})
m_bitrate->addItem(QStringLiteral("%1 Mbit/s").arg(bitrate / 1000000.0, 0, 'f', 2), bitrate);
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "desktop manual stream bitrate selection maximum_bps=%d", MaximumManualBitrate);
form->addRow(text("Video bitrate"), m_bitrate);
m_rotation = new QButtonGroup(this); auto* rotationRow = new QWidget(settings); auto* angles = new QHBoxLayout(rotationRow);
angles->setContentsMargins(0, 0, 0, 0);
for (int angle : {0, 90, 270}) {
auto* button = new QPushButton(QStringLiteral("%1°").arg(angle), settings); button->setCheckable(true); button->setFixedSize(66, 34);
QPixmap icon(20, 20); icon.fill(Qt::transparent); QPainter painter(&icon);
painter.translate(10, 10); painter.rotate(-angle); painter.setPen(QPen(QColor("#e53935"), 3)); painter.drawLine(-6, -6, 6, -6);
painter.setPen(QPen(palette().color(QPalette::Text), 1)); painter.drawLine(-6, -4, -6, 6); painter.drawLine(6, -4, 6, 6); painter.drawLine(-6, 6, 6, 6);
painter.end(); button->setIcon(QIcon(icon)); button->setToolTip(text("The red edge marks the top for viewers"));
button->setStyleSheet("QPushButton:checked { background: #1976d2; color: white; }");
m_rotation->addButton(button, angle); angles->addWidget(button);
}
m_rotation->button(0)->setChecked(true); angles->addStretch(); form->addRow(text("Rotation"), rotationRow);
m_audio = new QCheckBox(text("Microphone enabled"), settings); m_audio->setChecked(true); form->addRow(m_audio);
m_cursor = new QCheckBox(text("Show mouse cursor"), settings); m_cursor->setChecked(true);
m_cursor->setObjectName(QStringLiteral("streamShowCursor")); form->insertRow(2, m_cursor);
m_microphone = new QComboBox(settings); m_microphone->addItem(text("Default microphone"), QString());
for (const auto& device : SoundManager::enumerateCaptureDevices()) m_microphone->addItem(device.name, device.id);
form->addRow(text("Microphone"), m_microphone);
m_inputChannels = new QLabel(settings); form->addRow(text("Audio input"), m_inputChannels);
m_mute = new QCheckBox(text("Mute stream audio"), settings); form->addRow(m_mute);
m_maxQp = new QSpinBox(settings); m_maxQp->setObjectName(QStringLiteral("streamMaxQp"));
m_maxQp->setRange(0, 51); m_maxQp->setSpecialValueText(text("Automatic")); m_maxQp->setValue(28);
m_maxQp->setToolTip(text("Lower QP preserves more detail and may require a higher bitrate. Applies to H.264 and H.265."));
m_adaptiveQp = new QCheckBox(text("Adapt QP to the target bitrate"), settings);
m_adaptiveQp->setObjectName(QStringLiteral("streamAdaptiveQp"));
m_adaptiveQp->setToolTip(text(
"Gradually raise the QP ceiling above the selected value when bitrate is too high, then return it. Requires live QP adjustment."));
int bitrateRow = 0; QFormLayout::ItemRole bitrateRole;
form->getWidgetPosition(m_bitrate, &bitrateRow, &bitrateRole);
form->insertRow(bitrateRow + 1, text("Maximum QP"), m_maxQp); form->insertRow(bitrateRow + 2, m_adaptiveQp);
connect(m_mute, &QCheckBox::toggled, engine, &StreamPublishEngine::setMuted);
m_status = new QLabel(this); m_status->setWordWrap(true); layout->addWidget(m_status);
m_stats = new QLabel(this); m_stats->setObjectName(QStringLiteral("streamPublishStats"));
m_stats->setWordWrap(true); m_stats->setMinimumHeight(66); layout->addWidget(m_stats);
m_start = new QPushButton(text("Start stream"), this); layout->addWidget(m_start);
connect(m_start, &QPushButton::clicked, this, [this] {
if (!m_engine->active()) m_status->setProperty("streamError", QString());
if (m_engine->active()) m_engine->stop(); else m_engine->start(m_group, selection()); synchronize();
});
connect(m_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] {
if (m_updating) return;
auto previous = selection();
refreshModes(previous, previous.source.kind == StreamVideoSource::Screen); apply();
if (!m_engine->active()) synchronize();
});
for (auto* box : {m_mode, m_codec, m_bitrate}) connect(box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &StreamPublishWindow::apply);
connect(m_codec, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this] { if (!m_updating && !m_engine->active()) synchronize(); });
connect(m_microphone, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &StreamPublishWindow::apply);
connect(m_audio, &QCheckBox::toggled, this, &StreamPublishWindow::apply);
connect(m_cursor, &QCheckBox::toggled, this, &StreamPublishWindow::apply);
connect(m_maxQp, QOverload<int>::of(&QSpinBox::valueChanged), this, [this] { apply(); if (!m_engine->active()) synchronize(); });
connect(m_adaptiveQp, &QCheckBox::toggled, this, &StreamPublishWindow::apply);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(m_rotation, &QButtonGroup::idClicked, this, &StreamPublishWindow::apply);
#else
connect(m_rotation, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &StreamPublishWindow::apply);
#endif
connect(engine, &StreamPublishEngine::codecsChanged, this, &StreamPublishWindow::refreshCodecs);
connect(engine, &StreamPublishEngine::sourcesChanged, this, &StreamPublishWindow::refreshSources);
connect(engine, &StreamPublishEngine::configurationChanged, this, [this] { synchronize(); saveSettings(); });
connect(engine, &StreamPublishEngine::stateChanged, this, [this](const QString& value) {
auto error = m_status->property("streamError").toString();
m_status->setText(error.isEmpty() ? value : value + "\n" + error); synchronize();
});
connect(engine, &StreamPublishEngine::errorOccurred, this, [this](const QString& value) {
m_status->setProperty("streamError", value); m_status->setText(value);
});
connect(engine, &StreamPublishEngine::softwareEncodingRequested, this,
[this](uint64_t generation, const QString& codec, const QString& details) {
auto* question = new QMessageBox(QMessageBox::Warning, text("Hardware encoding is unavailable"),
text("Hardware encoding for %1 is unavailable in the selected video mode. Check the graphics driver or choose another codec.\n\n"
"Use CPU encoding instead? This may cause high CPU usage and slow down the computer.").arg(codec),
QMessageBox::Yes | QMessageBox::No, this);
question->setObjectName(QStringLiteral("streamCpuConfirmation"));
question->setDefaultButton(QMessageBox::No); question->setEscapeButton(QMessageBox::No);
question->setDetailedText(details); question->setAttribute(Qt::WA_DeleteOnClose);
auto* engine = m_engine;
connect(question, &QMessageBox::finished, engine, [engine, generation](int result) {
engine->answerSoftwareEncoding(generation, result == QMessageBox::Yes);
});
connect(question, &QObject::destroyed, engine,
[engine, generation] { engine->answerSoftwareEncoding(generation, false); }, Qt::QueuedConnection);
connect(engine, &StreamPublishEngine::stateChanged, question, [engine, generation, question] {
if (engine->softwareEncodingRequest() != generation) question->reject();
});
question->open();
});
connect(engine, &StreamPublishEngine::audioChannelsChanged, this, [this](int channels) { m_inputChannels->setText(channels == 2 ? text("Stereo") : text("Mono")); });
connect(engine, &StreamPublishEngine::telemetryChanged, this, [this](const stream_publisher_status& stats) {
m_stats->setText(text("Current video bitrate: %1 Mbit/s").arg(m_engine->videoBitrate() / 1000000.0, 0, 'f', 2) + '\n' +
(m_engine->videoMaxQp() >= 0 ? text("Current QP ceiling: %1").arg(m_engine->videoMaxQp()) + '\n' : QString()) +
text("Video queue: %1 · Inflight: %2 · Send buffer: %3\nACK, all connections, last second: %4")
.arg(bytes(stats.video_bytes), bytes(stats.inflight_bytes), bytes(stats.total_bytes), bytes(stats.acked_1s)));
});
refreshSources(); refreshCodecs(); restoreSettings(); synchronize();
}
/* В БД храним пользовательский выбор, а не runtime handles источника и рассчитанный auto-битрейт. */
void StreamPublishWindow::restoreSettings() {
if (m_settings.isEmpty() || m_engine->active()) return;
m_updating = true;
auto select = [](QComboBox* box, const QVariant& value, const char* field) {
int index = box->findData(value);
if (index >= 0) box->setCurrentIndex(index);
else DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop capture setting unavailable field=%s value=%s", field, qPrintable(value.toString()));
};
int sourceIndex = -1;
for (int i = 0; i < m_sources.size(); i++) {
const auto& source = m_sources[i];
if (source.kind != m_settings.value("source_kind").toInt()) continue;
bool matches = source.kind == StreamVideoSource::Window ? source.name == m_settings.value("source_name").toString()
: source.id == QByteArray::fromBase64(m_settings.value("source_id").toString().toLatin1());
if (matches) { sourceIndex = i; break; }
}
if (sourceIndex >= 0) m_source->setCurrentIndex(sourceIndex);
else DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop saved capture source unavailable; using current source");
auto restored = selection();
restored.video.width = m_settings.value("width").toInt(1280); restored.video.height = m_settings.value("height").toInt(720);
restored.video.fps = m_settings.value("fps").toInt(30); restored.nativeSize = m_settings.value("native_size").toBool();
refreshModes(restored, sourceIndex < 0 && restored.source.kind == StreamVideoSource::Screen);
int bitrate = m_settings.value("bitrate").toInt();
if (bitrate > MaximumManualBitrate) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop saved stream bitrate adjusted old_bps=%d maximum_bps=%d", bitrate, MaximumManualBitrate);
bitrate = MaximumManualBitrate;
}
if (bitrate > 0 && m_bitrate->findData(bitrate) < 0)
m_bitrate->addItem(QStringLiteral("%1 Mbit/s").arg(bitrate / 1000000.0, 0, 'f', 2), bitrate);
select(m_bitrate, bitrate, "bitrate");
int maxQp = m_settings.value("max_qp").toInt(28);
if (maxQp < 0 || maxQp > 51) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop saved QP ceiling invalid value=%d", maxQp);
m_maxQp->setValue(std::clamp(maxQp, 0, 51)); m_adaptiveQp->setChecked(m_settings.value("adaptive_qp").toBool());
if (auto* button = m_rotation->button(m_settings.value("rotation").toInt())) button->setChecked(true);
else DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop saved capture rotation invalid");
m_audio->setChecked(m_settings.value("audio").toBool(true));
m_cursor->setChecked(m_settings.value("show_cursor").toBool(true));
select(m_microphone, m_settings.value("microphone").toString(), "microphone");
m_updating = false;
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop capture settings restored: %s", QJsonDocument(m_settings).toJson(QJsonDocument::Compact).constData());
}
void StreamPublishWindow::saveSettings() {
if (m_updating) return;
auto config = selection(); auto saved = m_settings;
if (m_source->currentIndex() >= 0) {
saved["source_kind"] = config.source.kind;
saved["source_id"] = QString::fromLatin1(config.source.id.toBase64());
saved["source_name"] = config.source.kind == StreamVideoSource::Window ? config.source.name : QString();
}
if (m_mode->currentIndex() >= 0) {
saved["width"] = config.video.width; saved["height"] = config.video.height; saved["fps"] = config.video.fps;
saved["native_size"] = config.nativeSize;
}
if (m_codec->currentIndex() >= 0) saved["codec"] = config.video.codec;
saved["bitrate"] = m_bitrate->currentData().toInt(); saved["rotation"] = config.video.rotation;
saved["audio"] = config.audio; saved["microphone"] = config.microphone;
saved["show_cursor"] = config.showCursor;
saved["max_qp"] = m_maxQp->value(); saved["adaptive_qp"] = m_adaptiveQp->isChecked();
if (saved == m_settings) return;
m_settings = saved;
auto json = QJsonDocument(saved).toJson(QJsonDocument::Compact);
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop capture settings selected: %s", json.constData());
emit settingsChanged(QString::fromUtf8(json));
}
void StreamPublishWindow::refreshSources() {
QScopedValueRollback<bool> updating(m_updating, true);
auto config = m_engine->active() ? m_engine->config() : selection();
m_sources = StreamPublishEngine::sources(); m_source->clear();
int selected = -1;
for (int i = 0; i < m_sources.size(); i++) {
auto& source = m_sources[i];
if (source.sameSource(config.source)) {
selected = i;
if (m_engine->active() && !config.source.size.isEmpty()) source.size = config.source.size;
}
QString kind = source.kind == StreamVideoSource::Camera ? text("Camera") : source.kind == StreamVideoSource::Screen ? text("Screen") : text("Window");
QString label = kind + ": " + source.name;
if (source.kind == StreamVideoSource::Screen && !source.size.isEmpty())
label += QStringLiteral(" (%1 × %2)").arg(source.size.width()).arg(source.size.height());
m_source->addItem(label);
}
bool changed = selected < 0;
if (changed && !m_engine->active() && !m_sources.isEmpty()) selected = 0;
m_source->setCurrentIndex(selected);
refreshModes(config, changed && selected >= 0 && m_sources[selected].kind == StreamVideoSource::Screen);
synchronize();
}
/* Перестраиваем весь список под блокировкой, чтобы в engine не ушёл промежуточный режим другого источника. */
void StreamPublishWindow::refreshModes(const StreamPublishConfig& previous, bool maximum) {
QScopedValueRollback<bool> updating(m_updating, true);
m_mode->clear(); m_modes.clear();
int sourceIndex = m_source->currentIndex();
if (sourceIndex < 0 || sourceIndex >= m_sources.size()) return;
const auto& source = m_sources[sourceIndex];
if (source.kind == StreamVideoSource::Camera) m_modes = source.modes;
else if (source.kind == StreamVideoSource::Screen) m_modes = stream_video_screen_modes(source.size);
else {
for (QSize size : {QSize(1920, 1080), QSize(1280, 720), QSize(640, 360)})
for (int fps : {15, 30, 60}) m_modes.append({size, fps});
}
for (const auto& mode : m_modes) {
QString label = mode.size.isEmpty() ? text("Original resolution · %1 fps").arg(mode.fps)
: QStringLiteral("%1 × %2 · %3 fps").arg(mode.size.width()).arg(mode.size.height()).arg(mode.fps);
m_mode->addItem(label, QVariantList{mode.size.width(), mode.size.height(), mode.fps});
m_mode->setItemData(m_mode->count() - 1, source.kind == StreamVideoSource::Screen && mode.size == m_modes.front().size, Qt::UserRole + 1);
}
maximum |= source.kind == StreamVideoSource::Screen && previous.nativeSize;
StreamVideoMode wanted{QSize(previous.video.width, previous.video.height), previous.video.fps};
int selected = stream_video_select_mode(m_modes, wanted, maximum);
m_mode->setCurrentIndex(selected);
if (selected < 0) {
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop source has no selectable modes source=%s", qPrintable(source.name));
m_mode->setToolTip(text("No supported video modes")); return;
}
const auto& mode = m_modes[selected];
if (!maximum && wanted != mode) DEBUG_WARN(DEBUG_CATEGORY_VIDEO,
"desktop video mode adjusted source=%s requested=%dx%d/%d selected=%dx%d/%d",
qPrintable(source.name), wanted.size.width(), wanted.size.height(), wanted.fps, mode.size.width(), mode.size.height(), mode.fps);
m_mode->setToolTip(source.kind == StreamVideoSource::Camera ? text("Frame rate is limited by the camera and the selected stream mode") : QString());
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop video modes source=%s count=%lld selected=%dx%d/%d native=%d",
qPrintable(source.name), (long long)m_modes.size(), mode.size.width(), mode.size.height(), mode.fps,
m_mode->currentData(Qt::UserRole + 1).toBool());
}
void StreamPublishWindow::refreshCodecs() {
m_updating = true;
int previous = m_codec->currentIndex() >= 0 ? m_codec->currentData().toInt() : m_settings.value("codec").toInt();
m_codec->clear();
for (int codec : m_engine->codecs()) m_codec->addItem(LiveVideoEncoder::codecName(codec), codec);
int index = m_codec->findData(previous); if (index >= 0) m_codec->setCurrentIndex(index);
else if (previous && m_codec->count()) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop saved codec=%d unavailable; using codec=%d",
previous, m_codec->currentData().toInt());
m_updating = false; synchronize();
}
StreamPublishConfig StreamPublishWindow::selection() const {
StreamPublishConfig config;
if (m_source->currentIndex() >= 0 && m_source->currentIndex() < m_sources.size()) config.source = m_sources[m_source->currentIndex()];
auto mode = m_mode->currentData().toList();
if (mode.size() == 3) {
if (mode[0].toInt() > 0 && mode[1].toInt() > 0) { config.video.width = mode[0].toInt(); config.video.height = mode[1].toInt(); }
config.video.fps = mode[2].toInt();
}
config.nativeSize = config.source.kind == StreamVideoSource::Screen && m_mode->currentData(Qt::UserRole + 1).toBool();
config.video.codec = m_codec->currentData().toInt(); config.video.rotation = m_rotation->checkedId();
config.video.bitrate = m_bitrate->currentData().toInt();
config.automaticBitrate = !config.video.bitrate;
if (config.automaticBitrate) config.video.bitrate = std::clamp(config.video.width * config.video.height * config.video.fps / 11, 250000, 16000000);
if (config.video.codec == 1 || config.video.codec == 2) {
config.video.maxQp = m_maxQp->value() ? m_maxQp->value() : -1;
config.adaptiveQp = config.video.maxQp >= 0 && m_adaptiveQp->isChecked();
}
config.showCursor = m_cursor->isChecked();
config.microphone = m_microphone->currentData().toString(); config.audio = m_audio->isChecked(); return config;
}
void StreamPublishWindow::apply() {
if (m_updating || m_mode->currentIndex() < 0) return;
if (m_engine->active()) m_engine->reconfigure(selection());
saveSettings();
}
void StreamPublishWindow::synchronize() {
QScopedValueRollback<bool> updating(m_updating, true); bool active = m_engine->active();
m_start->setText(active ? text("Stop stream") : text("Start stream"));
m_start->setEnabled((active && m_engine->running()) || (!active && StreamPublishEngine::supported()
&& m_source->currentIndex() >= 0 && m_mode->currentIndex() >= 0 && m_codec->count()));
for (auto* box : {m_source, m_mode, m_codec, m_bitrate}) box->setEnabled(!active || m_engine->running());
m_audio->setEnabled(!active); m_microphone->setEnabled(!active); m_mute->setEnabled(m_engine->running() && m_engine->config().audio);
{ QSignalBlocker blocked(m_mute); m_mute->setChecked(m_engine->muted()); }
if (active) {
auto config = m_engine->config();
for (int i = 0; i < m_sources.size(); i++) if (m_sources[i].sameSource(config.source)) {
m_source->setCurrentIndex(i); if (!config.source.size.isEmpty()) m_sources[i].size = config.source.size;
}
refreshModes(config);
m_codec->setCurrentIndex(m_codec->findData(config.video.codec));
int bitrateIndex = config.automaticBitrate ? 0 : m_bitrate->findData(config.video.bitrate);
if (bitrateIndex < 0) {
m_bitrate->addItem(QStringLiteral("%1 Mbit/s").arg(config.video.bitrate / 1000000.0, 0, 'f', 2), config.video.bitrate);
bitrateIndex = m_bitrate->count() - 1;
}
m_bitrate->setCurrentIndex(bitrateIndex);
if (config.video.codec == 1 || config.video.codec == 2) {
m_maxQp->setValue(config.video.maxQp < 0 ? 0 : config.video.maxQp); m_adaptiveQp->setChecked(config.adaptiveQp);
}
if (auto* button = m_rotation->button(config.video.rotation)) button->setChecked(true);
m_audio->setChecked(config.audio); m_microphone->setCurrentIndex(m_microphone->findData(config.microphone));
m_cursor->setChecked(config.showCursor);
}
m_cursor->setVisible(StreamPublishEngine::cursorSupported(selection().source));
bool qpSupported = m_codec->currentData().toInt() == 1 || m_codec->currentData().toInt() == 2;
m_maxQp->setEnabled(qpSupported && (!active || m_engine->running()));
m_adaptiveQp->setEnabled(m_maxQp->isEnabled() && m_maxQp->value() > 0);
bool portal = selection().source.id == "portal";
m_cursor->setEnabled(!active || (m_engine->running() && !portal));
m_cursor->setToolTip(portal ? text("Change cursor visibility before starting screen sharing") : QString());
}