From ea46c56bc1c6e29003c260c63cf8ea300803985b Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 16 Oct 2026 13:50:03 +0200 Subject: [PATCH] Define source-specific desktop stream video modes --- tools/chatgui/src/stream_publish_engine.h | 7 +- tools/chatgui/src/stream_video_modes.h | 83 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tools/chatgui/src/stream_video_modes.h diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index ef4a0c0f..7dcdf30d 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -9,6 +9,7 @@ #include #include #include "live_video_encoder.h" +#include "stream_video_modes.h" #include "audio_capture.h" #include "audio_capture_lease.h" #include "../../../src/stream/stream_publisher.h" @@ -19,12 +20,15 @@ struct StreamVideoSource { QString name; QByteArray id; QVariant window; + QSize size; + QList modes; }; struct StreamPublishConfig { StreamVideoSource source; LiveVideoConfig video; QString microphone; bool audio = true; + bool nativeSize = false, automaticBitrate = false; }; class StreamPublishEngine : public QObject { Q_OBJECT @@ -45,6 +49,7 @@ public: bool muted() const; signals: void codecsChanged(); + void sourcesChanged(); void stateChanged(const QString& state); void configurationChanged(); void previewVideoChanged(const QVariant& frame); @@ -64,7 +69,7 @@ private: void sourceFailed(uint64_t generation, const QString& error); void publicationFailed(uint64_t id, const QString& error); void tick(); - void videoLoop(); + void videoLoop(StreamPublishConfig initial); void audioLoop(); void captureAudio(const AudioCapture::Frame& frame); }; diff --git a/tools/chatgui/src/stream_video_modes.h b/tools/chatgui/src/stream_video_modes.h new file mode 100644 index 00000000..e6ed70b6 --- /dev/null +++ b/tools/chatgui/src/stream_video_modes.h @@ -0,0 +1,83 @@ +/* Режимы трансляции: экран масштабируется пропорционально, камера сохраняет аппаратные размеры. + * Пустой размер экрана означает ожидание первого кадра системного выбора Wayland. + * FPS — предел частоты трансляции; Qt захватывает с максимальной частотой выбранного формата. */ +#pragma once +#include +#include +#include +#include + +struct StreamVideoMode { + QSize size; + int fps = 30; + bool operator==(const StreamVideoMode&) const = default; +}; + +/* Ограничиваем длинную сторону и, при необходимости, короткую; не увеличиваем исходник. */ +inline QSize stream_video_screen_size(QSize source, int shortSide = 4096) { + if (source.width() < 16 || source.height() < 16 || shortSide < 16) return {}; + double scale = std::min({1.0, 4096.0 / std::max(source.width(), source.height()), + double(shortSide) / std::min(source.width(), source.height())}); + QSize size(int(std::floor(source.width() * scale)) & ~1, int(std::floor(source.height() * scale)) & ~1); + return size.width() >= 16 && size.height() >= 16 ? size : QSize(); +} + +/* Стандартные ступени короткой стороны дают одинаковые пропорции в обеих ориентациях. */ +inline QList stream_video_screen_modes(QSize source) { + QList modes; + if (source.isEmpty()) { + for (int fps : {15, 30, 60}) modes.append({QSize(), fps}); + return modes; + } + QSize maximum = stream_video_screen_size(source); + if (maximum.isEmpty()) return modes; + QList sizes{maximum}; + for (int side : {2160, 1440, 1080, 720, 480, 360}) { + QSize size = stream_video_screen_size(source, side); + if (!size.isEmpty() && !sizes.contains(size)) sizes.append(size); + } + for (QSize size : sizes) for (int fps : {15, 30, 60}) modes.append({size, fps}); + return modes; +} + +/* 29.97/59.94 соответствуют пределам 30/60; фактический диапазон камеры не округляется. */ +inline bool stream_video_camera_supports(QSize size, double minimum, double maximum, const StreamVideoMode& mode) { + return size == mode.size && size.width() >= 16 && size.height() >= 16 && size.width() <= 4096 && size.height() <= 4096 + && !(size.width() % 2) && !(size.height() % 2) && std::isfinite(minimum) && std::isfinite(maximum) + && minimum >= 0 && maximum > 0 && minimum <= maximum && mode.fps >= 1 && mode.fps <= 60 + && mode.fps + 0.1 >= minimum && mode.fps <= maximum + 0.1; +} + +/* Объединяем одинаковые режимы разных pixel format, сохраняя границы необычных диапазонов FPS. */ +inline void stream_video_add_camera_modes(QList& modes, QSize size, double minimum, double maximum) { + QList rates{15, 24, 25, 30, 50, 60}; + if (std::isfinite(minimum) && minimum >= 1 && minimum <= 60.1) rates.append(int(std::ceil(minimum - 0.1))); + if (std::isfinite(maximum) && maximum >= 0.9 && maximum <= 60.1) rates.append(int(std::floor(maximum + 0.1))); + for (int fps : rates) { + StreamVideoMode mode{size, fps}; + if (stream_video_camera_supports(size, minimum, maximum, mode) && !modes.contains(mode)) modes.append(mode); + } + std::sort(modes.begin(), modes.end(), [](const auto& left, const auto& right) { + if (left.size != right.size) { + int area = left.size.width() * left.size.height(), other = right.size.width() * right.size.height(); + return area != other ? area > other : left.size.width() > right.size.width(); + } + return left.fps < right.fps; + }); +} + +/* При явном выборе экрана берём максимум; обновление списка сохраняет допустимый ручной выбор. */ +inline int stream_video_select_mode(const QList& modes, StreamVideoMode previous, bool maximum) { + if (modes.isEmpty()) return -1; + if (!maximum) { + int index = modes.indexOf(previous); + if (index >= 0) return index; + } + QSize size = maximum ? modes.front().size : QSize(1280, 720); + for (int fps : {previous.fps, 30}) { + int index = modes.indexOf({size, fps}); + if (index >= 0) return index; + } + for (int i = 0; i < modes.size(); i++) if (modes[i].fps == 30) return i; + return 0; +}