diff --git a/tools/chatgui/src/live_video_encoder.h b/tools/chatgui/src/live_video_encoder.h index 20520f10..fecdaa49 100644 --- a/tools/chatgui/src/live_video_encoder.h +++ b/tools/chatgui/src/live_video_encoder.h @@ -1,9 +1,10 @@ -/* FFmpeg live encoder: один worker владеет контекстом, конвертером и decoder configuration. - * open/encode/close последовательны; каждый KEY содержит uAV2 envelope. Пакеты имеют capture PTS. - * Backend ограничивает перебор: HardwareOnly никогда не открывает software encoder. - * Профили звонка проверяются по bitstream; допустим задержанный выход GPU без B-кадров. - * encode выдаёт все готовые кадры сразу. До восьми входных PTS сохраняются до выхода; - * QSV использует 90kHz внутри codec, наружу возвращается точный исходный capture PTS. */ +/* FFmpeg live encoder для звонков/трансляций: один worker последовательно вызывает все методы экземпляра. + * open синхронно пробует реальный KEY и принудительный KEY (если backend умеет), затем открывает чистый контекст. + * Профили звонка дополнительно проверяются по bitstream/декодированию, включая уровень и отсутствие B-кадров. + * encode добавляет весь готовый выход в список; задержка допустима. Для профилей звонка сохраняет не более восьми + * входных PTS и проверяет порядок выхода; QSV использует 90 kHz внутри codec, наружу возвращается capture PTS. + * KEY data: config_length:4BE + decoder configuration + access unit; остальные — access unit (без заголовка uAV2-блока). + * close/деструктор отбрасывают задержанный выход; для его получения сначала вызвать encode с пустым image и finish=true. */ #pragma once #include #include @@ -12,34 +13,61 @@ #include #include +/* Automatic — обычная трансляция; два явных профиля включают строгий bitstream-контракт звонка. */ enum class LiveVideoProfile { Automatic, H264ConstrainedBaseline, H265Main }; struct LiveVideoConfig { - int width = 1280, height = 720, fps = 30, bitrate = 2500000, codec = 1, rotation = 0; - LiveVideoProfile profile = LiveVideoProfile::Automatic; - int level = 0, references = 0, vbvBits = 0; // level: AVC level_idc / HEVC general_level_idc; 0 — auto. - int maxQp = -1; // Потолок QP для H.264/H.265: 0..51, -1 — настройки encoder. Профили звонка не используют. - QString backend; // Пусто — проба доступных; непусто — только уже проверенный encoder. + int width = 1280, height = 720, /* Чётные размеры кодированного кадра, каждая сторона 16..4096. */ + fps = 30, /* Номинальная частота 1..60; encode сам не ограничивает подачу кадров. */ + bitrate = 2500000, /* Целевой битрейт 64000..16000000 бит/с. */ + codec = 1, /* STREAM_MEDIA_H264/H265/VP9/AV1/H266, значения 1..5. */ + rotation = 0; /* Метаданные проверки профиля звонка; пиксели здесь не поворачиваются. */ + LiveVideoProfile profile = LiveVideoProfile::Automatic; /* Совместим с codec; явные профили только для H.264/H.265. */ + int level = 0, references = 0, vbvBits = 0; /* AVC/HEVC level_idc, число refs, VBV в битах; для звонка: level>0, refs=1, VBV>0. + * В Automatic: level определяет encoder, refs=0 — его выбор, VBV=0 — bitrate/4. */ + int maxQp = -1; /* Потолок QP 0..51 либо -1 (выбор encoder); только H.264/H.265 Automatic. */ + QString backend; /* Пусто — перебор доступных; иначе точное имя FFmpeg encoder с учётом Backend. */ + /* Сравнивает все поля, включая выбранный backend. */ bool operator==(const LiveVideoConfig&) const = default; }; -struct LiveEncodedFrame { QByteArray data; uint64_t pts = 0; bool key = false; }; +struct LiveEncodedFrame { + QByteArray data; /* Владеет KEY payload или обычным access unit, максимум STREAM_MEDIA_MAX_FRAME. */ + uint64_t pts = 0; /* Исходный capture PTS в микросекундах, <=INT64_MAX. */ + bool key = false; /* Фактический KEY по выходному пакету encoder, не копия запроса key. */ +}; class LiveVideoEncoder { public: + /* Ограничивает список имён encoder: Automatic — GPU/CPU, HardwareOnly — только GPU, SoftwareOnly — только CPU. */ enum class Backend { Automatic, HardwareOnly, SoftwareOnly }; LiveVideoEncoder(); ~LiveVideoEncoder(); + /* Сначала close прежнего контекста, затем валидация config и пробы encoder. true — готовый чистый контекст. + * false — смотреть error; продолжать encode после неудачного open нельзя. Без FFmpeg всегда false. */ bool open(const LiveVideoConfig& config, Backend backend = Backend::Automatic); - bool setBitrate(int bitrate); // true — изменение на следующем кадре; false — нужна смена видеоконтекста - bool canSetMaxQp() const; // Поддерживается изменение потолка без пересоздания encoder. - bool setMaxQp(int qp); // 0..51, применяется при следующей подаче кадра; false — ошибка/нет поддержки. + /* 64000..16000000 бит/с; true — обновлено для следующего кадра (только libx264 Automatic). + * false — неверный битрейт либо требуется замена контекста; прежний контекст сохраняется. */ + bool setBitrate(int bitrate); + /* Динамический QP доступен для H.264/H.265 Automatic: QSV либо AMF с опцией utun_qp_max. */ + bool canSetMaxQp() const; + /* Запрашивает потолок 0..51 на следующей подаче кадра; false — ошибка/нет поддержки, причина записывается в error. */ + bool setMaxQp(int qp); + /* Освобождает контекст без flush, очищает имя/ошибку/PTS; повтор безопасен. */ void close(); - // Пустое image забирает готовый выход без нового input; finish завершает старый поток перед заменой. + /* Масштабирует image с сохранением пропорций/чёрными полями под config, подаёт capture pts<=INT64_MAX и забирает выход. + * key — запрос KEY; backend без принудительных KEY (AMF) после первого кадра использует периодический GOP. + * Пустое image только забирает выход; finish=true требует пустое image и завершает подачу потока (после него close/open). + * output дополняется, не очищается; true допускает пустой выход. При false вход/часть выхода уже могли быть приняты. */ bool encode(const QImage& image, uint64_t pts, bool key, QList& output, bool finish = false); + /* Число принятых, но ещё не выданных входов профиля звонка; Automatic не ведёт эту таблицу и возвращает 0. */ unsigned pendingFrames() const; + /* Последняя записанная ошибка; успешный encode/setMaxQp не обязан очищать её. */ QString error() const; + /* Имя FFmpeg encoder текущей/последней пробы; после close пусто, само по себе не означает успешный open. */ QString name() const; - static QList available(Backend backend = Backend::Automatic); // Установленные кодеки, без открытия и кодирования. + /* ID кодеков с зарегистрированным encoder указанного Backend; без открытия/проверки GPU, без FFmpeg — пусто. */ + static QList available(Backend backend = Backend::Automatic); + /* Имя протокольного codec 1..5; неизвестный ID возвращает пустую строку. */ static QString codecName(int codec); private: struct Impl; - std::unique_ptr m; + std::unique_ptr m; /* Единственный владелец codec, кадров, конвертера и таблицы ожидаемых PTS. */ }; diff --git a/tools/chatgui/src/stream_video_modes.h b/tools/chatgui/src/stream_video_modes.h index 577cc33b..4c3dd702 100644 --- a/tools/chatgui/src/stream_video_modes.h +++ b/tools/chatgui/src/stream_video_modes.h @@ -1,6 +1,8 @@ /* Режимы трансляции: экран масштабируется пропорционально, камера сохраняет аппаратные размеры. * Пустой размер экрана означает ожидание первого кадра системного выбора Wayland. - * FPS — предел частоты трансляции; Qt захватывает с максимальной частотой выбранного формата. */ + * FPS — предел частоты трансляции; Qt захватывает с максимальной частотой выбранного формата. + * Функции работают только с переданными значениями/списками; сами не опрашивают устройства и не меняют захват. + * Для звонка CallCameraProtocol::validSize дополнительно ограничивает камеру до Full HD. */ #pragma once #include #include @@ -8,12 +10,14 @@ #include struct StreamVideoMode { - QSize size; - int fps = 30; + QSize size; /* Размер выходного кадра; пустой допустим для ещё не выбранного системного экрана. */ + int fps = 30; /* Предел выдачи 1..60; конструктор структуры сам его не проверяет. */ + /* Совпадают оба поля; используется для дедупликации и сохранения ручного выбора. */ bool operator==(const StreamVideoMode&) const = default; }; -/* Ограничиваем длинную сторону и, при необходимости, короткую; не увеличиваем исходник. */ +/* Вписывает source в длинную сторону 4096 и короткую shortSide, не увеличивая исходник, округляет размеры вниз до чётных. + * Возвращает пустой QSize при source/shortSide <16 либо если после уменьшения одна из сторон <16. */ 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()), @@ -22,7 +26,8 @@ inline QSize stream_video_screen_size(QSize source, int shortSide = 4096) { return size.width() >= 16 && size.height() >= 16 ? size : QSize(); } -/* Стандартные ступени короткой стороны дают одинаковые пропорции в обеих ориентациях. */ +/* Строит максимум и ступени короткой стороны 2160/1440/1080/720/480/360, убирая дубли размеров, с FPS 15/30/60. + * Первый размер максимальный; для пустого source возвращает три режима с пустым размером, для слишком малого — пустой список. */ inline QList stream_video_screen_modes(QSize source) { QList modes; if (source.isEmpty()) { @@ -41,7 +46,8 @@ inline QList stream_video_screen_modes(QSize source) { } /* FPS режима — предел передачи, не команда драйверу. Быстрый capture ограничивается до encode. - * 29.97/59.94 соответствуют пределам 30/60; фактический диапазон камеры не округляется. */ + * Проверяет точный size, чётные стороны 16..4096, корректный диапазон minimum..maximum и FPS 1..60 <=maximum+0.1. + * FPS может быть ниже minimum, поскольку лишние кадры отбрасываются. 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) @@ -49,7 +55,9 @@ inline bool stream_video_camera_supports(QSize size, double minimum, double maxi && mode.fps <= maximum + 0.1; } -/* Объединяем одинаковые режимы разных pixel format, сохраняя границы необычных диапазонов FPS. */ +/* Добавляет в modes допустимые FPS из 15/24/25/30/50/60 и округлённых с допуском 0.1 границ minimum/maximum. + * Объединяет дубли разных pixel format; сортирует весь modes по убыванию площади/ширины, затем возрастанию FPS. + * Невалидный аппаратный формат ничего не добавляет; сама функция не ограничивает камеры до Full HD. */ 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))); @@ -67,7 +75,10 @@ inline void stream_video_add_camera_modes(QList& modes, QSize s }); } -/* При явном выборе экрана берём максимум; обновление списка сохраняет допустимый ручной выбор. */ +/* Возвращает индекс режима или -1 для пустого списка, сам список не меняет. + * При !maximum сначала сохраняет точный previous; иначе/при отсутствии ищет FPS previous/30 с размером + * modes.front().size (maximum) либо 1280x720. Затем берёт первый режим 30 FPS, в крайнем случае индекс 0. + * Для выбора максимума вызывающий должен передать список с максимальным размером в начале. */ inline int stream_video_select_mode(const QList& modes, StreamVideoMode previous, bool maximum) { if (modes.isEmpty()) return -1; if (!maximum) {