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

99 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 публикует желаемую конфигурацию; ошибка видео откатывает к последней рабочей, повторная ошибка останавливает.
* Захват/конвертация изолированы в video capture процессе; GUI и encoder получают только собственные CPU QImage.
* Audio callback копирует PCM; Opus/video кодируются в отдельных workers, данные уходят через общий C publisher actor.
* stop сразу закрывает admission publisher и асинхронно запрашивает выход capture, FIN дренируется независимо.
* active остаётся true до окончания actor, workers и capture; только после этого разрешён следующий start.
* Деструктор передаёт закрытие capture независимому supervisor, ожидая лишь encoder/audio с обработкой Qt events. */
#pragma once
#include <QObject>
#include <QTimer>
#include <QVariant>
#include <QImage>
#include <QList>
#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 }; // Соответствует VideoCaptureKind; native handles остаются в worker.
Kind kind = Camera; // Backend захвата.
QString name; // Отображаемое имя; не часть идентичности источника.
QByteArray id; // Camera device-id, дескриптор screen либо "portal" для системного выбора Wayland.
QSize size; // Физический размер screen; может быть неизвестен до первого кадра portal.
QList<StreamVideoMode> modes; // Режимы камеры из объявленных возможностей; screen-режимы строятся отдельно.
bool sameSource(const StreamVideoSource& other) const { // Сравнивает backend/handles, без имени, разрешения и списка режимов.
return kind == other.kind && id == other.id;
}
};
struct StreamPublishConfig {
StreamVideoSource source; // Выбранный capture; camera-режим должен поддерживать video.width/height/fps.
LiveVideoConfig video; // Параметры encoder; поддерживаемый codec, fps 1..60, размеры в допустимом диапазоне encoder.
bool audio = true; // Микрофон и stereo Opus 48кГц/128кбит/с; отказ открытия/нет PCM 300мс отключает аудио, видео продолжается.
bool startMuted = false; // Начальный mute применяется до запуска capture; дальнейшее переключение через setMuted().
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; // Собственный CPU QImage; пустой QVariant без кадра, lifetime не связан с capture.
QList<int> codecs() const; // Кодеки LiveVideoEncoder, найденные при создании; пусто без поддержки capture.
QList<StreamVideoSource> sources() const; // Последний асинхронный snapshot worker; до sourcesChanged список может быть пуст.
void refreshSources(); // Запросить новый snapshot через IPC; GUI не опрашивает аппаратный backend.
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 setAudioEnabled(bool enabled); // Live: открыть общий микрофон или закрыть захват/worker; false при отказе, видео сохраняется.
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); // CPU QImage до ~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.
QString m_captureId; // Общий с рацией выбор SoundManager; devicesChanged переоткрывает только аудиозахват.
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-процессу; ошибки приходят асинхронно.
void closeSource(); // Меняет sourceGeneration, очищает кадры, отключает callbacks и закрывает backend.
bool openMicrophone(); // true — открыт вход; нормализует каналы и подключает captureAudio.
// Только текущее поколение: rollback к good либо stop; cameraError добавляет пояснение об ошибке подключения камеры.
void sourceFailed(uint64_t generation, const QString& error, bool cameraError = false);
void publicationFailed(uint64_t id, const QString& error); // Ошибка всей текущей публикации, без video rollback: errorOccurred и stop.
void tick(); // GUI-проверка состояния actor/остановки, capture/audio timeout и статистики.
bool finishStop(); // true — workers, микрофон и capture завершились; FIN уже дренируется независимо в actor.
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.
};