Files
utun2/tools/chatgui/src/stream_publish_engine.h
T

100 lines
11 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.
/* Desktop publisher принадлежит MainWindow и переживает закрытие окна управления.
* Публичные методы — GUI-поток: start открывает actor/capture, но первый KEY и Live приходят асинхронно.
* reconfigure публикует желаемую конфигурацию; ошибка видео откатывает к последней рабочей, повторная ошибка останавливает.
* Audio callback только копирует PCM; Opus/video кодируются в отдельных workers, QVideoFrame преобразует долгоживущий image worker.
* Capture/preview хранят последний кадр, данные уходят через общий C publisher actor.
* stop закрывает capture, оставляя GUI доступным; отдельный join ждёт завершения workers/TLS, затем finishStop отправляет FIN.
* active остаётся true до окончания actor; новый start разрешён после active=false. Деструктор ждёт также image worker, обрабатывая Qt events. */
#pragma once
#include <QObject>
#include <QTimer>
#include <QVariant>
#include <QImage>
#include <QList>
#include <QPointer>
#include <QScreen>
#include <memory>
#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"
struct StreamVideoSource {
enum Kind { Camera, Screen, Window }; // Window поддерживается openSource, но sources() перечисляет только camera/screen.
Kind kind = Camera; // Backend захвата.
QString name; // Отображаемое имя; не часть идентичности источника.
QByteArray id; // Camera device-id, дескриптор screen либо "portal" для системного выбора Wayland.
QVariant window; // QCapturableWindow для Kind::Window; пустой у camera/screen.
QSize size; // Физический размер screen; может быть неизвестен до первого кадра portal.
QList<StreamVideoMode> modes; // Режимы камеры из объявленных возможностей; screen-режимы строятся отдельно.
QPointer<QScreen> screen; // Живой монитор; удаление устройства не должно переключать захват на его одноимённого соседа.
bool sameSource(const StreamVideoSource& other) const { // Сравнивает backend/handles, без имени, разрешения и списка режимов.
return kind == other.kind && id == other.id && window == other.window && screen == other.screen;
}
};
struct StreamPublishConfig {
StreamVideoSource source; // Выбранный capture; camera-режим должен поддерживать video.width/height/fps.
LiveVideoConfig video; // Параметры encoder; поддерживаемый codec, fps 1..60, размеры в допустимом диапазоне encoder.
QString microphone; // Стабильный id устройства SoundManager; пустой — default. Меняется только до start.
bool audio = true; // Открыть микрофон и stereo Opus 48кГц/128кбит/с; во время публикации меняется только mute.
bool showCursor = true; // Учитывается поддерживаемым screen backend; portal разрешает выбор только до открытия сессии.
bool nativeSize = false, automaticBitrate = false; // Screen: исходные размеры до 4096 и пересчёт bitrate при уточнении размера.
bool adaptiveQp = false; // При перерасходе битрейта плавно повышать потолок выше video.maxQp, затем возвращать.
};
class StreamPublishEngine : public QObject {
Q_OBJECT
public:
explicit StreamPublishEngine(QObject* parent = nullptr);
~StreamPublishEngine() override;
bool start(uint64_t group, const StreamPublishConfig& config); // true — запуск принят; результат/ошибки ещё асинхронны. false может начать stop.
bool reconfigure(const StreamPublishConfig& config); // true — принято, не подтверждение KEY; микрофон/audio и cursor сессии portal менять нельзя.
void stop(); // Идемпотентный запрос остановки; освобождение workers/microphone и доотправка actor завершаются через tick.
bool active() const; // Actor-id ещё принадлежит engine, включая запуск и остановку/дренирование.
bool running() const; // Capture/encoder ещё работают либо ждут выбора portal/разрешения CPU; stop сразу даёт false.
uint64_t group() const; // Группа текущего actor, 0 после его окончательного завершения.
StreamPublishConfig config() const; // Копия wanted под lock; может ещё ожидать применения/отката.
uint64_t videoBitrate() const; // Фактический выход video encoder за последний интервал около секунды, бит/с; читать в GUI-потоке.
int videoMaxQp() const; // Текущий потолок QP с учётом адаптации; -1 — настройки encoder.
QVariant previewFrame() const; // Shared QVideoFrame последнего capture, пустой/невалидный без кадра; сетевое кодирование не требуется.
QList<int> codecs() const; // Кодеки LiveVideoEncoder, найденные при создании; пусто без поддержки capture.
static QList<StreamVideoSource> sources(); // Текущие камеры и screen: Wayland даёт один portal, остальные платформы — QScreen handles.
static bool supported(); // Возможности сборки HAVE_STREAM_CAPTURE; не гарантия наличия устройства или рабочего encoder.
static bool cursorSupported(const StreamVideoSource& source); // Наличие cursor backend для выбранного screen/платформы.
void setMuted(bool muted); // Только running+audio: меняет флаг actor и обнуляет исходящий PCM; ошибку смены трактует как отказ публикации.
bool muted() const; // Текущее mute; сбрасывается при start.
uint64_t softwareEncodingRequest() const; // Поколение ожидающего запроса CPU либо 0.
// Ответ только текущему запросу; устаревший игнорируется, отказ вызывает rollback/stop.
void answerSoftwareEncoding(uint64_t generation, bool allow);
signals:
void codecsChanged(); // Сигнал перечня codec; текущая реализация определяет список один раз в конструкторе.
void sourcesChanged(); // Камеры/мониторы изменились либо уточнён размер захваченного screen; GUI перечитывает sources/config.
void stateChanged(const QString& state); // Пользовательский текст запуска, Live, применения, остановки или ожидания CPU/portal.
void configurationChanged(); // Настройки подтверждены либо изменилось состояние start/stop/mute; перечитать config/active/running.
void previewVideoChanged(const QVariant& frame); // GUI публикует новый QVideoFrame до ~30fps; пустой QVariant очищает preview при closeSource.
void telemetryChanged(const stream_publisher_status& status); // Снимок actor, примерно каждые 100мс вне ожидания workers.
void errorOccurred(const QString& error); // Ошибка может привести к rollback либо stop; сама не означает active=false.
void audioChannelsChanged(int channels); // Фактический mono/stereo вход; mono дублируется в stereo-транспорт.
// HW не открылся; требуется явный ответ для CPU.
void softwareEncodingRequested(uint64_t generation, const QString& codec, const QString& details);
private:
struct Impl;
std::unique_ptr<Impl> m; // Workers, Qt capture backend, wanted/good generations, очередь PCM и последний video/preview.
AudioCapture m_capture; // Микрофон 48кГц, callback копирует PCM без DSP/codec.
AudioCaptureLease m_microphone{AudioCaptureLease::Mode::Exclusive}; // Удерживается до закрытия устройства и завершения workers.
QTimer m_timer; // GUI 100мс: finishStop, состояние actor, telemetry, bitrate и таймауты capture.
QTimer m_previewTimer; // GUI 33мс: отправляет только изменившийся previewFrame.
bool openSource(const StreamPublishConfig& config); // Пересоздаёт capture backend в GUI, true — принят запуск; ошибки возможны позже.
void closeSource(); // Меняет sourceGeneration, очищает кадры, отключает callbacks и закрывает backend.
bool openMicrophone(); // true — открыт вход; нормализует каналы и подключает captureAudio.
void sourceFailed(uint64_t generation, const QString& error); // Только текущее поколение: однократный rollback к good либо stop.
void publicationFailed(uint64_t id, const QString& error); // Ошибка всей текущей публикации, без video rollback: errorOccurred и stop.
void tick(); // GUI-проверка состояния actor/остановки, capture/audio timeout и статистики.
bool finishStop(); // true — workers join и закрытие микрофона завершены, FIN уже отправлен; actor ещё может дренироваться.
bool finishImageStop(); // Неблокирующий wait(0) image QThread при уничтожении; true — native TLS уже освобождён.
void imageLoop(); // Долгоживущий QThread: последний QVideoFrame → QImage, rotation/mirror и cursor Windows.
void videoLoop(StreamPublishConfig initial); // Video worker: encoder/revision, HW→CPU consent, KEY, bitrate/QP и feed actor.
void audioLoop(); // Audio worker: 20мс stereo, compressor/Opus, пропуск устаревших blocks, feed actor.
void captureAudio(const AudioCapture::Frame& frame); // Hardware callback: до 960 frames на элемент ограниченной очереди, без allocations/codec.
};