Document isolated stream capture shutdown and diagnostics
This commit is contained in:
1 parent
8c2289b188
commit
caf4ab406c
8 files changed
+41
-29
No files matched your search
@@ -827,11 +827,11 @@ cmake .. && make -j4
|
||||
| `call_video_engine.h/cpp` | Desktop-видеозвонок: камера, TX/RX workers, обмен с ядром и показ кадров по аудиочасам |
|
||||
| `call_video_encoder.h/cpp` | Кодер звонка: согласованный формат, смена битрейта с drain прежнего кодера и подтверждение настройки |
|
||||
| `call_video_decoder.h/cpp` | FFmpeg-декодер H.264/H.265 с выбором GPU/CPU и сохранением PTS при задержанном выходе |
|
||||
| `call_camera_capture.h/cpp` | Управление отдельным процессом камеры, каталог устройств/режимов и получение кадров |
|
||||
| `call_camera_worker.cpp` | Процесс Qt-захвата камеры: настройка устройства, отбор кадров и выдача пикселей через shared memory |
|
||||
| `call_camera_source.h` | Интерфейс backend камеры для процесса захвата и подстановки устройств в тестах |
|
||||
| `call_camera_protocol.h` | Локальный протокол управляющих команд, каталога устройств и FRAME/ACK процесса камеры |
|
||||
| `call_camera_buffer.h` | Владение shared-memory буфером пикселей между процессами захвата и GUI |
|
||||
| `video_capture.h/cpp` | Асинхронный capture-процесс, каталог источников, watchdog и независимое от GUI закрытие |
|
||||
| `video_capture_worker.cpp` | Камера/экран/окно, преобразование в CPU RGB32 и диагностика этапов аппаратного закрытия |
|
||||
| `video_capture_source.h` | Контракт backend внутри worker и точка входа для тестовых источников |
|
||||
| `video_capture_protocol.h` | Поколения, FRAME/ACK, этапы/heartbeat и ограничения IPC видеозахвата |
|
||||
| `video_capture_buffer.h` | Однокадровый shared-memory буфер согласованной ёмкости до 4096×4096 RGB32 |
|
||||
| `call_camera_rate.h` | Временное отключение снижения FPS из-за автоэкспозиции V4L2 с восстановлением настройки камеры |
|
||||
| `call_video_cadence.h` | Отбор исходных кадров под заданный FPS без накопления ошибки дробного периода |
|
||||
| `call_video_capture_clock.h` | Привязка времени камеры к монотонным часам с коррекцией задержки первого callback |
|
||||
|
||||
@@ -406,19 +406,24 @@ stats timer и stream handle освобождаются до контекста
|
||||
|
||||
Desktop `MainWindow` владеет `StreamPublishEngine`, а окно управления лишь отображает
|
||||
его состояние. Закрытие окна сохраняет эфир, новый запуск запрещён до конца FIN drain.
|
||||
Qt capture callback сохраняет последний `QVideoFrame` для preview и ограниченно ставит кадр
|
||||
в очередь отдельного долгоживущего image worker. Он конвертирует `QVideoFrame` в `QImage`;
|
||||
video worker получает готовый image и не создаёт Qt Multimedia TLS, которое могло задержать его exit на Windows.
|
||||
Общий `VideoCapture` запускает отдельный процесс для камеры/экрана/окна и преобразования кадров.
|
||||
Аппаратные Qt-объекты и все `QVideoFrame` остаются в дочернем процессе. GUI копирует RGB32 из
|
||||
однокадрового shared memory до FRAME/ACK и передаёт собственный CPU `QImage` в preview и video worker.
|
||||
Размеры Call ограничены Full HD, Stream — 4096 по каждой стороне. Поздние кадры отсекаются поколением.
|
||||
Video/audio workers выполняют encode/DSP вне GUI и hardware callbacks. Отдельные поколения источника и encoder позволяют менять
|
||||
видеокодек/bitrate/rotation без повторного выбора экрана в портале. Ошибки Qt capture
|
||||
обрабатываются через queued callback, чтобы не уничтожать backend из собственного callback.
|
||||
Stop запрещает producers, закрывает Qt capture и подписку на аудиозахват. Отдельный join worker
|
||||
дожидается полного выхода video/audio threads, включая native TLS Qt; GUI продолжает обрабатывать события.
|
||||
После join отправляется FIN с drain 2s; закрытие последнего микрофона может продолжаться параллельно.
|
||||
Microphone lease и owner сохраняются до завершения обоих путей. Новый start ждёт также FIN drain.
|
||||
Stop запрещает producers и сразу закрывает admission C publisher: принятые access units дренируются,
|
||||
FIN с drain 2s не ждёт capture или encoder. Состояние engine сохраняется до полного выхода video/audio
|
||||
threads, закрытия микрофона и capture-процесса. Новый start ждёт также FIN drain.
|
||||
Независимый QObject-supervisor переживает владельца GUI; только QProcess::finished подтверждает
|
||||
освобождение native/TLS capture. Deadline выхода 1.5s проверяется каждые 250ms и не продлевается
|
||||
этапами/heartbeat. При таймауте supervisor пишет PID, поколение, последний этап, время и завершает процесс.
|
||||
Таймаут активности камеры учитывает свежие кадры; неподвижный экран остаётся жив по heartbeat.
|
||||
При завершении приложения engine уничтожается до SoundManager и ядра; detach в uasync
|
||||
закрывает оставшийся publisher. Destructor ждёт workers в локальном event loop без пользовательского ввода,
|
||||
чтобы thread cleanup мог обратиться к GUI; start и внешние сигналы engine в это время запрещены.
|
||||
чтобы encoder thread cleanup мог обратиться к GUI; capture-supervisor освобождается независимо.
|
||||
Start и внешние сигналы engine в это время запрещены; деструктор живого QProcess в GUI не вызывается.
|
||||
Workers никогда не держат borrowed instance/handle.
|
||||
Microphone lease публикации исключает call/recorder; рация согласует вход через `AudioCapture`.
|
||||
Одинаковый выбранный вход имеет одну аппаратную сессию и профиль трансляции. Разные входы независимы.
|
||||
|
||||
+6
-1
@@ -1093,7 +1093,12 @@ Desktop:
|
||||
- transport/stream_bridge владеет независимым stream_publisher; shared C actor выполняет uAV2/RS,
|
||||
admission, KEY recovery, FIN и TX snapshot в uasync. Очереди/pressure/drain совпадают с Android.
|
||||
UI показывает video queue, суммарный inflight, send buffer и ACK_1s каждые 100ms.
|
||||
Stop закрывает источники и audio callback, join обоих workers, затем FIN и ограниченный drain 2s.
|
||||
Захват и преобразование кадров выполняет общий VideoCapture в отдельном процессе; аппаратные
|
||||
QVideoFrame никогда не передаются GUI. Preview и encoder получают собственные CPU QImage через FRAME/ACK.
|
||||
Stop закрывает admission publisher и сразу начинает FIN/drain 2s независимо от физического capture close.
|
||||
Новый start ждёт workers, микрофон, фактический выход capture-процесса и завершение drain.
|
||||
Capture-supervisor сохраняет этапы закрытия, PID/поколение/длительность; после deadline 1.5s завершает
|
||||
зависший процесс. Неподвижный экран проверяется по heartbeat, без требования новых кадров.
|
||||
Удаление engine происходит до SoundManager и ядра; detach отменяет posts/timers и закрывает publisher
|
||||
в uasync. Общий publisher использует те же uAV2 и текущий wire v7 на обеих платформах.
|
||||
- Одиночный StreamPlayerWindow либо StreamGridWindow с отдельным player на источник.
|
||||
|
||||
@@ -41,7 +41,7 @@ private:
|
||||
QLabel *m_status, *m_stats, *m_inputChannels; // Статус/ошибка, bitrate/QP/очереди/ACK и фактические входные каналы.
|
||||
QPushButton* m_start; // Start/Stop; отключена во время завершения actor либо при отсутствии доступных source/mode/codec.
|
||||
StreamBufferIndicator* m_buffer; // Диапазоны получателей по истории source: oldest → live; tooltip содержит peer/subflows.
|
||||
QWidget* m_preview; // QVideoWidget при HAVE_STREAM_CAPTURE, иначе текст о недоступности capture.
|
||||
QWidget* m_preview; // VideoRenderWidget с CPU QImage при HAVE_STREAM_CAPTURE, иначе текст о недоступности capture.
|
||||
bool m_updating = false; // Защита от применения/сохранения промежуточного состояния при перестроении controls.
|
||||
bool m_audioMuted = false; // Сохранённый mute для следующего запуска; во время трансляции синхронизируется с engine.
|
||||
QJsonObject m_settings; // Последний сохранённый выбор; source id в base64, окно по имени, режим/codec/audio/cursor/QP отдельно.
|
||||
|
||||
@@ -37,10 +37,9 @@ public:
|
||||
explicit VideoCapture(QObject* parent = nullptr, Usage usage = Usage::Call);
|
||||
/* Отвязывает GUI-владельца и запрашивает асинхронный Quit; сессия сама дождётся выхода/убьёт зависший worker. */
|
||||
~VideoCapture() override;
|
||||
/* enabled включает/выключает захват; device — точный id из devicesChanged, пустой id не выбирает камеру автоматически.
|
||||
* width/height должны пройти VideoCaptureProtocol::validSize, fps=1..60 — предел выдачи, не команда драйверу.
|
||||
* Параметры проверяются и при enabled=false; повтор тех же параметров без ошибки ничего не меняет.
|
||||
* Смена конфигурации асинхронна; после failed явный configure(true, ...) допускает перезапуск процесса. */
|
||||
/* Call принимает камеры Full HD, Stream — камеры/экраны/окна до 4096. FPS 1..60 ограничивает выдачу.
|
||||
* Параметры проверяются и при enabled=false; повтор конфигурации без ошибки ничего не меняет.
|
||||
* После failed enabled=true допускает перезапуск; во время stop конфигурация отклоняется с WARN. */
|
||||
void configure(const VideoCaptureConfig& config);
|
||||
void stop(); // Запросить выход процесса; deadline 1.5 с не продлевается сообщениями worker. Повтор безопасен.
|
||||
bool closing() const; // true до подтверждённого выхода процесса после stop; GUI никогда не ждёт его синхронно.
|
||||
@@ -53,7 +52,8 @@ signals:
|
||||
* уменьшение опережения исходных PTS. Ack worker отправлен до сигнала, mapping больше не удерживается. */
|
||||
void frameReady(const QImage& image, qint64 pts, qint64 correction);
|
||||
/* Ошибка запуска/IPC/драйвера/параметров или watchdog: процесс останавливается, захват отключается.
|
||||
* Watchdog: отсутствие прогресса/свежих кадров около 5 с, остановка — около 1.5 с, проверка каждые 250 мс. */
|
||||
* Watchdog: отсутствие прогресса или кадров камеры около 5 с; неподвижному экрану достаточно heartbeat.
|
||||
* stop контролирует фактический выход процесса с deadline 1.5 с, проверка каждые 250 мс. */
|
||||
void failed(const QString& error);
|
||||
void stopped(); // Процесс фактически вышел, включая уничтожение Qt/TLS; перед этим повторный start запрещён.
|
||||
private:
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
Session* m; // Независимый QObject с QPointer на владельца; после shutdown живёт до завершения worker/event loop.
|
||||
};
|
||||
|
||||
/* Entry point дочернего процесса: argc=3, argv[2] — endpoint, создаёт QGuiApplication и запускает worker event loop.
|
||||
/* Entry point дочернего процесса: argc=4, argv[2] — endpoint, argv[3] — call/stream; создаёт QGuiApplication.
|
||||
* Вызывается до обычной инициализации приложения, конфига, БД и single-instance lock.
|
||||
* Возвращает код event loop; без HAVE_STREAM_CAPTURE или с неверным argc — 2. */
|
||||
int video_capture_worker_main(int argc, char** argv);
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Однокадровый mapping VideoCaptureProtocol::FrameBytes: GUI создаёт, capture worker подключается по name().
|
||||
/* Однокадровый mapping согласованной ёмкости до MaxFrameBytes: GUI создаёт, capture worker подключается по name().
|
||||
* Объект единолично владеет локальным mapping/handle, не копируется; вызовы не синхронизированы.
|
||||
* Доступ к общим пикселям согласуется через Frame/Ack, а не через этот класс. close не ждёт другой процесс. */
|
||||
#pragma once
|
||||
@@ -23,7 +23,7 @@ class VideoCaptureBuffer {
|
||||
#endif
|
||||
/* Запоминает причину, освобождает локальные ресурсы и возвращает false. */
|
||||
bool fail(const QString& error) { m_error = error; close(); return false; }
|
||||
/* Отображает FrameBytes текущего handle/файла; false освобождает ресурсы через fail. */
|
||||
/* Отображает согласованную ёмкость handle/файла; false освобождает ресурсы через fail. */
|
||||
bool map() {
|
||||
#ifdef Q_OS_WIN
|
||||
m_data = static_cast<uchar*>(MapViewOfFile(m_mapping, FILE_MAP_WRITE, 0, 0, m_capacity));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* IPC GUI <-> camera worker: QLocalSocket передаёт QDataStream Qt_5_15, пиксели лежат в VideoCaptureBuffer.
|
||||
/* IPC GUI <-> video capture worker: QLocalSocket передаёт QDataStream Qt_5_15, пиксели лежат в VideoCaptureBuffer.
|
||||
* Пакет: quint32 длины тела, quint8 Type, quint64 поколения конфигурации, затем поля конкретного типа.
|
||||
* Worker пишет один RGB32-кадр и посылает Frame; до Ack его пиксели не меняются, в том числе при Configure.
|
||||
* Получатель копирует/отбрасывает кадр и подтверждает его поколение; shared mutex между процессами нет. */
|
||||
@@ -9,8 +9,10 @@
|
||||
#include <functional>
|
||||
|
||||
namespace VideoCaptureProtocol {
|
||||
/* GUI -> worker: Configure(bool,QByteArray device,qint32 w/h/fps), Quit(), Ack(), Buffer(QString name).
|
||||
* Worker -> GUI: Devices(count,[name,id,modes(w,h,fps)]), Frame(qint64 pts/correction,qint32 w/h), Stage/Error(QString).
|
||||
/* GUI -> worker: Configure(bool,QByteArray device,qint32 w/h/fps,quint8 kind,bool cursor), Quit(), Ack(),
|
||||
* Buffer(QString name,quint32 capacity), Refresh(). Worker -> GUI:
|
||||
* Devices(count,[name,id,count_modes,quint8 kind,QSize,modes(w,h,fps)]), Frame(qint64 pts/correction,qint32 w/h),
|
||||
* Stage/Error(QString), Heartbeat(). Usage call/stream выбирается аргументом запуска.
|
||||
* Buffer/Devices используют поколение 0; остальные — поколение конфигурации/подтверждаемого кадра. */
|
||||
enum Type : quint8 { Configure = 1, Quit, Ack, Devices, Frame, Stage, Error, Buffer, Heartbeat, Refresh };
|
||||
constexpr quint32 FrameBytes = 1920 * 1080 * 4; /* Буфер Call; Stream согласует буфер до MaxFrameBytes. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Backend камеры внутри изолированного worker-процесса; интерфейс для Qt и тестовых источников.
|
||||
/* Backend видео внутри изолированного worker-процесса; интерфейс для Qt и тестовых источников.
|
||||
* Управление/опрос идут в потоке Qt worker event loop и могут блокироваться в драйвере: watchdog GUI завершит процесс.
|
||||
* Сигнал frame может приходить из потока backend; worker забирает последний кадр и конвертирует вне callback. */
|
||||
#pragma once
|
||||
@@ -11,11 +11,11 @@
|
||||
class VideoCaptureSource : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/* Возвращает текущий полный список с аппаратными размерами/FPS передачи, допустимыми для звонка. */
|
||||
/* Список: stream=false ограничивает камеры до Full HD; true добавляет камеры 4096, экраны и окна. */
|
||||
virtual VideoCaptureDevices devices(bool stream) = 0;
|
||||
/* Запрашивает точный device/размер и предел FPS выдачи; worker вызывает stop перед каждым start.
|
||||
* true — запуск принят, готовность наступает с frame; отказ/асинхронная ошибка сообщаются также сигналом error.
|
||||
* Сам return false не создаёт IPC-ошибку. Qt backend выбирает подходящий формат без масштабирования кадров. */
|
||||
* Сам return false не создаёт IPC-ошибку. Камера выбирает аппаратный формат; экран вписывается в 4096 перед IPC. */
|
||||
virtual bool start(const VideoCaptureConfig& config) = 0;
|
||||
virtual bool prepareImage(QImage&) { return true; } // Наложение курсора до IPC; только worker event loop.
|
||||
/* Освобождает захват; должен допускать вызов без успешного start и повтор при уничтожении worker. */
|
||||
|
||||
Reference in new issue
Block a user