Document audited call video and session contracts

This commit is contained in:
evgeny committed 2026-10-26 21:54:18 +01:00
1 parent 1899b7f8cf
commit 07f63c3047
13 files changed
+350 -92

No files matched your search

+107
View File
@@ -1659,3 +1659,110 @@ Check запущен от user в отдельном network namespace с IPv6/l
Логи /tmp/call-rebase-{native-build,gui-configure,gui-build,android-build,voice-tests,full-check,netns,validation}.log;
проверка сохранности /tmp/call-rebase-validate.py, runner /tmp/call-rebase-{check,netns}.sh.
Общий аудит остаётся частичным; статусы чек-листа аудиоконтрактов не менялись.
22. Сверка контрактов call_video — 26.10.2026, utun4, base 1899b7f8
По запросу пользователя продолжен внешний utun-doc-audit-checklist.txt.
Прочитаны полностью шесть реализаций src/call/call_video{,_bitstream,_playout,
_port,_rate,_transport}.c и соответствующие заголовки. Четыре fixtures-заголовка
проверены извлечением всех массивов и настоящим декодированием. Сверены семь
desktop call_video*.h с encoder/decoder/engine и inline queue/wake/capture clock/cadence;
для doc/call_video.txt проверены также Android JNI, CallVideoCodec/Engine/Player,
Surface ACK и запуск фоновых codec-проб в обоих приложениях.
Исправлены комментарии шести C- и трёх C++-заголовков, дополнен текущий контракт
doc/call_video.txt. Pure validators/serializers доступны codec workers, receiver
имеет одного владельца; утверждение «все операции в uasync» относилось к transport.
Уточнены единицы/границы format/limits/frame, владение fragment view и полного RX,
срок частичной сборки по следующему push, возвраты и неизменность выходов при отказе.
KEY envelope/разбор нужных NAL-полей/настоящее decode описаны как разные проверки;
общий verifier не декодирует entropy payload. HRD High tier консервативно ограничен Main-tier таблицей.
Зафиксированы CAPS64/header56, точные поля/смещения/BE; video control uint64
по текущему коду native endian, cross-endian совместимость не подтверждается.
Configure может изменить локальные настройки до ошибки отправки CAPS; port-команды
подтверждают постановку, send=1 — копирование, transport может отвергнуть кадр позже.
TX mailbox8x2МиБ, transport32/500мс/2МиБ, RX8/4МиБ; retry сохраняет зависимый хвост.
Отражены CAPS_ACK, bounded flush16, send_ready, audio gate, timeout команды12с,
порядок close/detach и различия завершения workers desktop/Android.
Чистый rate_step допускает100Мбит/с, диапазон звонка ограничивают transport/parser.
Устранены устаревшие текущие формулировки о полном отсутствии DROP_LATE,
port.rx_frames как единственном desktop счётчике, фазе расписания прямо от каждого ACK
и condition_variable fallback при полном отказе Windows timer handles.
Desktop при недоступных handles отклоняет start; runtime ошибки передаёт RX для остановки.
Исторические wire/VBV/DPB/GPU-only/очереди и результаты прогонов оставлены с явной датированной областью;
ссылки из начала документа ведут к поздним действующим этапам, а не старому playout1.04x.
Уточнено: пользовательская смена режима desktop допускает отмену старого worker batch,
а Android геометрия ждёт drain/tx_idle; смена bitrate desktop выводит старый encoder полностью.
Новых runtime-дефектов в рамках этой сверки не подтверждено; реализация не менялась.
Проверки и границы
Профиль собран штатными целями tests/Makefile: пять наборов test_call_video,
test_call_video_rate, test_call_video_playout, test_call_video_port, test_call_video_flush
PASS, каждый exit0. Шестой набор test_call —2/2 PASS после отдельной сборки.
Первый test_call в sandbox не создал сокеты (Operation not permitted); разрешённый
повтор вне sandbox успешен, проверяет настоящие два ядра и видеотранспорт, без камер.
Логи /tmp/call-video-doc-audit-{build,call-build,native-results}.log,
/tmp/call-video-doc-audit-test_call*.log и соответствующие test_call_video*.log.
Все16 встроенных KEY прошли системный FFmpeg -xerror: ровно один decoded frame,
ожидаемые geometry/profile/level, has_b_frames0. FPS30/60 проверен по VUI timing,
для AVC time_scale/(2*num_units_in_tick), а не по ffprobe r_frame_rate одиночного IDR.
Образцы не регенерировались. Runner /tmp/call-video-doc-audit-fixtures.py,
данные/лог /tmp/call-video-doc-audit-fixtures.{json,log}.
Снимок17 заголовков до правок /tmp/call-video-doc-audit-before.json.
Токены C/C++ до/после совпадают17/17; объявления/константы/структуры сохранены.
Общий include всех10 C-заголовков проходит C99, всех17 — C++20/Qt6,
-Wall -Wextra -Werror -fsyntax-only; /tmp/call-video-doc-audit-headers.{c,cpp},
/tmp/call-video-doc-audit-validation.log. Git diff --check PASS.
В чек-листе закрыты10 src/call/call_video*.h,7 desktop call_video*.h и doc/call_video.txt;
прочие статусы сохранены, src/call/call_priv.h и общий аудит call/архитектуры остаются частичными.
Это завершение сверки перечисленных контрактов с кодом, не новая аппаратная приёмка видеозвонка.
Qt runtime/GPU/камеры, APK/JVM, Windows/FreeBSD/Android на устройствах, sanitizers
и полный check.sh в этом проходе не запускались. Исторические замеры не перепроверялись.
Runtime/wire/тесты не менялись; коммит и push не выполнялись.
23. Закрытие внутреннего call_priv.h — 26.10.2026
По уточнению пользователя закрыта оставшаяся частичная отметка внутреннего состояния.
Причиной прежней [~] была незавершённая сверка video lifetime (раздел20); контракты
видеоядра/порта уже проверены в разделе22. Повторно прочитаны call.c целиком,
call_priv.h, места доступа video_transport/port, реестр ll_queue и штатные проверки admission.
В заголовке описаны единственный uasync-владелец, sessions с индексом call_id,
заимствованный find (возвращает и ENDED), внешняя аллокация entry->data и запрет
переноса raw session/ctx в workers/отложенные команды. Поле session.ll не используется
для реестра; это отмечено явно, его layout не менялся. Started_tb — начало отсчёта60с,
а не готовое значение deadline; PHONE_BUSY/continue не перезапускают попытку.
Отражены реальное значение ACTIVE без обязательной готовности CM, немедленная очистка
audio TX/video при ENDED, ожидание END_ACK/таймаута с удержанием CM/записи,
отложенное освобождение обычного remove и синхронное освобождение оставшегося реестра при destroy.
Уточнены все таймеры, watcher пути и ACTIVE stats, аудио FIFO750/drop-head,
sequence до выделения entry, одноразовый send waiter и интервал агрегатов ожидания.
Counters: started считает только созданные исходящие, ended — переходы обеих ролей;
TX/RX — audio frames, c_tx_dropped считает overflow, c_rx_dropped не ведётся.
Last_media_tb обновляется на RX; watchdog управляется traffic_timer. Headless callback
хранится в ctx, а audio_ops — отдельно у instance и переживает пересоздание ctx.
Новые функциональные дефекты не подтверждены; изменены только комментарии и статус аудита.
Проверки: snapshot /tmp/call-priv-doc-audit-before.h; C/C++ токены совпадают,
объявления/структуры/константы сохранены. Include заголовка C99 и C++20 с
-Wall -Wextra -Werror -fsyntax-only PASS; /tmp/call-priv-doc-audit-validation.log.
Штатные цели test_call_admission/test_call_video_port/test_call_video_flush собраны;
новые прогоны3/3 PASS. Admission:547 checks,BUSY22,no_event18,allocations0->0,
проверены сохранение сессии/CM/таймеров при отказе, ACTIVE после маршрута,
ENDED и потерянный END_ACK. Первый запуск sandbox заблокировал сокеты,
успешный admission выполнен с разрешением вне sandbox.
Логи /tmp/call-priv-doc-audit-{build,admission,test_call_video_port,test_call_video_flush}.log.
Git diff --check PASS; предыдущие изменения video/doc сохранены.
Call_priv.h [~]→[V], остальные отметки сохранены. Все21 заголовок src/call теперь[V];
doc/chat_call_arch.md и общий аудит call/платформ остаются[~]. Текущий статус архитектурного
документа уточнён; исторические результаты разделов20–22 сохранены.
Новые fullcheck/sanitizers/APK/Qt runtime/аппаратный звонок не выполнялись.
Коммит/push не выполнялись.
+83 -11
View File
@@ -9,6 +9,9 @@ HEVC Main с Main/High tier (High начиная с level 4), 8-bit 4:2:0, progr
HRD при наличии, геометрию и независимый IDR. Фактический level проверяется после
открытия encoder. CAPS — 64 байта, header — 56 байт; старый формат не поддерживается.
VBV является локальной настройкой software encoder, в сетевом контракте его нет.
Проверка KEY в call_video.c ограничена envelope; разбор нужных полей VPS/SPS/PPS/IDR
выполняет call_video_bitstream. Он не декодирует entropy payload и не заменяет decoder.
У HEVC High tier также действуют консервативные Main-tier бюджеты HRD/CPB.
Результат запуска decoder (generation + accepted) передаётся отдельно от router ACK:
неподдержанный формат вызывает fallback в Auto, потеря references — запрос KEY.
Камера, разрешение, частота до 60 fps, кодек и диапазон битрейта меняются во время звонка.
@@ -22,7 +25,8 @@ SoundTouch и очередь устройства. Алгоритм регули
Показ имеет собственное абсолютное расписание с плавным темпом по суммарному запасу до 1.6x;
audio clock ограничивает опережение videoPTS-audioPTS до +40мс. Устойчивый долг при ограничении
частоты экрана снимается пропуском только показа уже декодированных кадров.
Подробности и границы измерения — в разделе «Независимое расписание показа» ниже.
Подробности — в разделах «Часы показа и сквозная очередь», «Независимый desktop-планировщик»
и «До 60 fps и восстановление presentation»; раннее «Независимое расписание показа» историческое.
Искусственная тишина/тоны не продвигают исходную позицию. Формат видео меняется по PTS;
новое поколение не уничтожает старые кадры, соответствующие ещё не прозвучавшей речи.
@@ -43,8 +47,69 @@ audio clock ограничивает опережение videoPTS-audioPTS до
Состояние звонка/регулятора/роутера — uasync. Камера и encoder/decoder — платформенные
workers. Ограниченный mailbox, одно ожидающее уведомление. Команды bitrate имеют
call_id, video_generation и request_id; одно исполнение и последнее желаемое значение.
Принятая настройка не заменяет измерение фактического выхода. Shutdown отменяет
уведомления/таймеры, останавливает producers, затем освобождает очереди и codecs.
Принятая настройка не заменяет измерение фактического выхода. Остановка освобождает
уведомления/таймеры, очереди и codecs в порядке владельцев: desktop присоединяет workers
перед close port; Android ставит stopped, присоединяет RX, закрывает port, затем TX handler
закрывает камеру/encoder и завершает свой looper. Это разные контракты завершения.
Границы модулей и wire (сверено с кодом 26.10.2026)
call_video.h: чистые проверки/сериализация доступны workers, receiver имеет одного владельца.
Limits — независимые границы, полностью нулевой limit означает отсутствие
проверенного профиля. Format: ненулевой generation, чётные размеры 16..4096, 1..60 fps,
rotation=0/90/180/270, target ceiling 300000..4000000 бит/с, один profile=1/2.
Поля формата, включая maximum_bps, неизменны внутри generation; текущий bitrate — отдельно.
frame_id и capture PTS строго возрастают сквозь поколения; пропуск frame_id требует KEY.
CAPS: четыре limits по 16 байт: encode AVC, encode HEVC, decode AVC, decode HEVC.
Внутри limit: width:2, height:2, fps:2, pixels_per_second:4, maximum_bps:4, level:1, profiles:1.
Header фрагмента (смещения в байтах):
0 call_id:8 8 generation:8 16 frame_id:8 24 capture_pts_us:8
32 width:2 34 height:2 36 fps:2 38 rotation:2
40 codec:1 41 flags:1 42 level:1 43 profile:1
44 frame_size:4 48 offset:4 52 maximum_bps:4
Все многобайтовые поля CAPS/header — BE. Длина payload 1..1200, полный кадр <=2 МиБ.
Parse возвращает view внутри входного пакета; fragment.data уже указывает на payload
данного offset. Локальный received_us в wire не входит. Control имеет отдельный формат:
type:1 + call_id:8 + revision/generation:8, для CAPS ещё 64 байта; оба uint64 — native endian,
как в текущей сигнализации call. Это описание действующего кода, не обещание cross-endian совместимости.
call_video_receiver хранит одну частичную сборку. Offset идёт непрерывно; повтор/перестановка,
разрыв frame_id, регресс PTS/поколения и смена формата без нового generation нарушают цепочку.
Возраст сборки >2с проверяется на следующем push; отдельного таймера сборки нет.
Готовый буфер заимствован до следующего push/reset/destroy; очередь готовых кадров — у владельца.
call_video_transport принадлежит uasync: control/audio/video используют отдельные router services,
control и video требуют подпись и шифрование. CAPS повторяются каждые 500мс до прикладного ACK.
Configure может применить локальное состояние и затем вернуть -1 при отказе отправки CAPS.
TX FIFO: 32 кадра, размах PTS <=500мс, суммарно <=2 МиБ; общий admission учитывает router debt.
Голова ждёт audio_end_pts и CAPS_ACK. Flush выдаёт до 16 фрагментов, продолжает через call_soon;
send_q/full debt ждут send_ready/audio/tick. Возраст головы >2с сбрасывает зависимую цепочку.
Tick 50мс обновляет регулятор; timeout encoder-команды 12с выключает admission до configure_rate.
Callback заимствует payload до возврата и не вызывает изменяющий call API реентерабельно.
call_video_port — единственный процессный mutex/mailbox. Attach/detach — uasync, workers используют
потокобезопасные команды. Open того же id сохраняет RX/TX/peer/wake/received counter;
другой id очищает их. Open/format/applied/range/decoded подтверждают приём команды,
проверка активной сессии и исполнение происходят позже в uasync.
Send=1 означает копирование пачки в mailbox, transport может позже отвергнуть её с key_serial.
Send=0 не принимает ничего; transport busy сохраняет непринятый хвост без нового KEY.
TX mailbox: до 8 кадров по 2 МиБ (до 16 МиБ); RX: до 8 кадров/4 МиБ.
Pull передаёт владение data, освобождение u_free; RX wake вызывается под port mutex, без входа в port API.
До detach останавливают пользователей и снимают transport callbacks через close с обработкой команды
либо завершение call session. Сам detach не заменяет остановку транспорта.
call_video_rate — чистая модель 500мс окон с 50мс шагом; единицы — уникальные encoded bytes,
без fragment headers. Invalid sample не меняет состояние/результат. Чистый step допускает до
100 Мбит/с; диапазон звонка 300..4000 кбит/с ограничивают parser и transport.
call_video_playout не владеет изображениями: WAIT сохраняет голову, SHOW резервирует до presented,
DROP_ORDER/DROP_LATE освобождают только decoded presentation силами владельца.
Desktop считает общий запас как received-completed-discarded без mutex порта;
Android складывает снимок port.rx_frames с локальными pending/inputs/ready/awaiting.
Desktop смена пользовательского режима отменяет ещё не переданную worker-пачку и заменяет encoder;
это отличается от смены bitrate с полным drain старого encoder. Android при смене геометрии
сначала drain, отправляет pendingFrames и ждёт tx_idle перед format. Готовые RX-изображения
старого поколения сохраняются до своего показа на обеих платформах.
Применение bitrate на ключевом кадре (уточнение пользователя)
Worker хранит pending-команду и применяет её перед кодированием ближайшего
@@ -99,8 +164,11 @@ GOP продолжается. Android сначала меняет bitrate чер
Видео следует аудио при догоне, underrun, смене устройства и формате видеотрека.
Завершение звонка безопасно во время переключения камеры, bitrate и decoder.
Далее журнал этапов разработки. Старые описания wire/profile/VBV ниже исторические;
актуальный контракт приведён в начале, последние проверки — в конце документа.
Далее журнал этапов разработки. Формулировки «текущий», «действующий», «пока» и результаты
проверок относятся к дате соответствующего этапа; старые wire/profile/VBV/буферы/алгоритмы
не задают нынешний контракт. Актуальная сводка приведена выше, подробности поздних этапов
уточняют расписание, High tier, 60 fps и прогрев. Исторические аппаратные результаты и логи
при сверке документации 26.10.2026 заново не воспроизводились.
Проверка SoundTouch, 14.10.2026
test_soundtouch_clock сравнивает положение реальных четырёхсэмпловых фрагментов
@@ -922,7 +990,7 @@ Windows-диагностика: AudioDiag теперь работает чере
video late drop содержит точные PTS/AV/возраст первого позднего кадра за окно, playout —
диапазоны всех late кадров. Измерения не корректируют Windows callback-estimate аудиочасы.
Восстановление без пропусков, 19.10.2026 — действующее поведение
Восстановление без пропусков, 19.10.2026 — базовый этап; уточнения 19–20.10 ниже
Encoded-кадр после принятия сохраняет ID/PTS и владельца до передачи дальше. TX port
удерживает непринятый хвост пачки и повторяет его при уведомлении writable. Новая пачка
не принимается во время обработки старой. Busy=0 не меняет references и не запрашивает KEY;
@@ -949,7 +1017,8 @@ Decoder получает кадры строго последовательно,
входы decoder ограничены отдельно восемью, чтобы output lookahead не блокировал запуск GPU.
Основной backlog хранится encoded, а не большим RGB FIFO или удержанием всех аппаратных outputs.
Playout больше не содержит DROP_LATE/DROP_SCHEDULE. Invalid/order остаётся диагностируемой ошибкой.
На этом этапе playout не содержал DROP_LATE/DROP_SCHEDULE; 20.10 возвращён только DROP_LATE
для устойчивого долга при ограничении частоты экрана. Invalid/order остаётся диагностируемой ошибкой.
CallTimeline публикует rate_ppm из фактического SourceSpan SoundTouch -> PCM на текущей позиции устройства.
Первоначальный вариант управлял темпом по audioPTS-framePTS. Его заменила описанная ниже схема
с подтверждённым временем показа, общей глубиной конвейера и небольшим рабочим запасом.
@@ -1004,8 +1073,9 @@ N = полные encoded в port + pending input worker + принятые decod
+ decoded FIFO + один выбранный, но ещё не подтверждённый GUI/Surface кадр.
Перемещение между стадиями не меняет N; исключение кадра происходит после подтверждения показа
или решения DROP_LATE для уже декодированного кадра (дополнение 20.10.2026 ниже).
Снимок port.rx_frames берётся под mutex после локальных перемещений RX worker. Сетевая частичная
сборка не считается полным кадром. Очереди отправителя не видны этому счётчику.
На Android снимок port.rx_frames берётся под mutex после локальных перемещений RX worker.
Desktop использует атомарный cumulative received-completed-discarded (см. независимый планировщик ниже).
Сетевая частичная сборка не считается полным кадром. Очереди отправителя не видны этому счётчику.
RX admission возобновляется при появлении места, без искусственного ожидания полного drain FIFO.
Уведомление port будит desktop RX немедленно; callback снимается до освобождения владельца.
@@ -1018,7 +1088,8 @@ submit/receive независимы: EAGAIN сохраняет непринят
SHOW резервирует кадр, presented(PTS, MONOTONIC) подтверждает его потребление и продвигает часы.
Desktop ACK приходит после paintEvent; Android — после SurfaceTexture/RenderThread commit, не по OnFrameRendered.
Это подтверждения платформенного потребления, а не измерение физического scanout экрана.
Срок следующей головы фиксируется один раз: последнее ACK + период format / speed - оценка dispatch-to-ACK.
Срок следующей головы фиксируется один раз: фаза последнего показа + период format / speed
минус оценка dispatch-to-ACK. Фаза плавно корректируется по ACK; большая пауза привязывает её к ACK.
Оценка lead сглаживается и принимает обычный ACK за целый цикл экрана; выбросы больше 1.25 периода +1мс игнорируются.
Пустая очередь сохраняет привязку: готовый через 12мс следующий кадр не показывается раньше своего обычного срока.
Поздно пришедшая голова выходит сразу после готовности; разрыв capture PTS не повторяется ожиданием.
@@ -1031,7 +1102,8 @@ Desktop ACK приходит после paintEvent; Android — после Surfa
Опережение аудио по-прежнему ограничено 40мс; невалидные аудиочасы запрещают новый показ без очистки FIFO.
Windows ожидает абсолютный срок через high-resolution waitable timer + event, без spin/timeBeginPeriod.
Отказ high-resolution API явно логируется, остаётся regular timer; полный отказ handles использует condition_variable.
Отказ high-resolution API явно логируется, остаётся regular timer; при отсутствии необходимых handles
desktop start отклоняет запуск видео. Runtime ошибка ожидания передаётся RX для лога и остановки workers.
Stop и данные прерывают ожидание. Фактический overshoot остаётся в video RX worker wake_late_max_us.
CPU encoder: libx264 threads=4/sliced-threads=1; libx265 pools=4/wpp=1/frame-threads=1.
+6 -1
View File
@@ -65,7 +65,12 @@ Android timing/trace/buffer/recovery26/26, voice/desktop30/30 и assembleDebug
Следующая сверка закрыла публичные аудиоконтракты call.h/call_audio.h/call_headless.h/call_proto.h
и desktop call_audio_engine.h. Уточнены сроки жизни callback/аргументов, фиксированный wire-формат,
завершение и восстановление устройств. Изменения только в комментариях/doc; voice38/38 и четыре
набора сигналинга/headless PASS. Подробности — раздел20 отчёта; call_priv и видеоконтракты пока частичны.
набора сигналинга/headless PASS. Подробности — раздел20 отчёта.
Сверка видеоконтрактов и внутреннего `call_priv.h` завершена: все21 заголовок `src/call/` закрыты,
также проверены17 C/C++ видеозаголовков и `doc/call_video.txt` (разделы22–23 отчёта).
Уточнены владение сессией/видеотреком, асинхронное завершение и смысл счётчиков; реализации не менялись.
Общий аудит архитектуры и платформенных разделов остаётся частичным, новая аппаратная приёмка не выполнялась.
## 1. Область
+39 -27
View File
@@ -1,42 +1,54 @@
/* Внутреннее состояние call.c/call_video_transport.c; доступ только из uasync. */
/* Внутренний реестр звонков: call.c владеет ctx/сессиями, video_transport и process
* видеопорта обращаются к ним только из uasync. Workers используют публичный port API.
* Сессия размещена в data внешнего ll_entry; sessions индексирует call_id, а не пару group/peer.
* Find возвращает заимствованный указатель, в том числе на ENDED до удаления записи.
* При ENDED аудио-TX/video останавливаются сразу; CM/запись могут ждать END_ACK/таймаут.
* Обычное удаление снимает запись из реестра и освобождает entry через call_soon;
* call_destroy отменяет callbacks и освобождает оставшиеся в реестре записи синхронно.
* Не переносить указатели ctx/session в другой поток или отложенную команду: передавать call_id
* и повторять find в uasync. Описание публичных действий/событий — call.h/call_proto.h. */
#ifndef CALL_PRIV_H
#define CALL_PRIV_H
#include "call.h"
#include "../../lib/ll_queue.h"
/* IDLE — только создание; RINGING бывает у обеих ролей, WAIT_DECISION — ожидание force
* при телефонной занятости; ENDED в реестре уже не занимает линию и не принимает медиа. */
enum call_state { CALL_ST_IDLE, CALL_ST_OUTGOING, CALL_ST_RINGING, CALL_ST_ACTIVE, CALL_ST_ENDED, CALL_ST_WAIT_DECISION };
enum call_role { CALL_ROLE_CALLER, CALL_ROLE_CALLEE };
struct call_video_transport;
struct call_session {
struct ll_entry ll;
uint64_t call_id, group_id, peer_node_id;
uint8_t role, state, reason;
struct ll_entry ll; /* Не используется; реальный queue entry находится перед s == entry->data. */
uint64_t call_id, group_id, peer_node_id; /* Неизменные ID попытки, группы маршрутизации и собеседника. */
uint8_t role, state, reason; /* call_role/call_state; reason — CALL_REASON_* после перехода в ENDED. */
uint8_t force; /* вызывающий подтвердил обход телефонной занятости */
uint64_t started_tb; /* неизменный deadline попытки, включая выбор продолжения */
uint8_t end_subcmd;
uint64_t end_started_tb;
struct call_ctx *ctx;
struct CONN_MGR_HANDLE *cm_handle;
void *ring_timer, *invite_timer;
uint8_t no_route_strikes;
void *traffic_timer, *teardown_timer, *stats_timer;
uint8_t connection_reported, connection_type;
uint64_t connection_next_hop, last_media_tb;
uint16_t tx_media_seq;
struct ll_queue *tx_q;
struct queue_waiter_handle send_waiter;
uint32_t c_tx_frames, c_rx_frames, c_tx_dropped, c_rx_dropped;
uint64_t tx_wait_total_ms;
uint32_t tx_wait_frames, tx_wait_max_ms;
struct call_video_transport *video;
uint64_t started_tb; /* Начало попытки get_time_tb (100мкс); 60с отсчитываются отсюда, включая WAIT_DECISION. */
uint8_t end_subcmd; /* Финальная команда для повтора; 0 — нет своей команды либо уже принят END_ACK. */
uint64_t end_started_tb; /* Начало ENDED в tb; предел повтора своей команды — 20с. */
struct call_ctx *ctx; /* Заимствованный владелец, живёт до удаления сессии из реестра. */
struct CONN_MGR_HANDLE *cm_handle; /* Собственный запрос оптимизации пути; ACTIVE не требует готового CM. */
void *ring_timer, *invite_timer; /* Caller: 60с/повтор INVITE 300мс; callee: ring_timer — watchdog INVITE до 2с. */
uint8_t no_route_strikes; /* Последовательные INVITE ticks без маршрута; сбрасывается при наличии маршрута. */
void *traffic_timer, *teardown_timer, *stats_timer; /* RX watchdog 20с, завершение/END retry, путь и ACTIVE-статистика 1с. */
uint8_t connection_reported, connection_type; /* Было событие CONNECTION / последний router path.type (0..3). */
uint64_t connection_next_hop, last_media_tb; /* Кэш next hop и время последнего RX; watchdog использует traffic_timer. */
uint16_t tx_media_seq; /* Следующий sequence аудиопакета с wrap uint16; расходуется до выделения TX entry. */
struct ll_queue *tx_q; /* Собственная FIFO готовых audio packets [svc][media]; до 750, переполнение удаляет голову. */
struct queue_waiter_handle send_waiter; /* Отменяемое одноразовое ожидание места в router audio send_q. */
uint32_t c_tx_frames, c_rx_frames, c_tx_dropped, c_rx_dropped; /* TX принят router / валидный RX / TX overflow / RX не ведётся. */
uint64_t tx_wait_total_ms; /* Сумма времени audio enqueue->drain, включая отказ router; сброс при отчёте stats. */
uint32_t tx_wait_frames, tx_wait_max_ms; /* Число drained кадров/максимальное ожидание за тот же отчётный интервал. */
struct call_video_transport *video; /* Собственное ленивое состояние видеотрека; cleanup обнуляет и освобождает его. */
};
struct call_ctx {
struct UTUN_INSTANCE *inst;
int initialized;
struct ll_queue *sessions;
uint32_t c_calls_started, c_calls_ended;
call_media_recv_fn media_recv_fn;
void *media_recv_arg;
struct UTUN_INSTANCE *inst; /* Заимствованное ядро, владеющее ctx в inst->call; audio_ops хранится отдельно у ядра. */
int initialized; /* Сигналинг привязан; 1 также во время регистрации media services/отката call_init. */
struct ll_queue *sessions; /* Собственные queue entries с call_session в data и 8-байтовым индексом call_id. */
uint32_t c_calls_started, c_calls_ended; /* Созданные исходящие / все переходы в ENDED; это не парные счётчики. */
call_media_recv_fn media_recv_fn; /* Один необязательный headless RX callback, вызываемый в uasync. */
void *media_recv_arg; /* Заимствованный arg до замены callback/destroy; пересоздание ctx сбрасывает привязку. */
};
/* Найти call_id, включая ENDED: заимствованная сессия либо NULL (также при NULL ctx/sessions).
* Только uasync; не проверяет state/group/peer. После удаления/destroy указатель использовать нельзя. */
struct call_session *call_session_find(struct call_ctx *ctx, uint64_t call_id);
#endif
+36 -18
View File
@@ -1,7 +1,10 @@
/* Общий контракт видеотрека. Все операции выполняет владелец в uasync-потоке.
/* Общий контракт видеотрека: чистые проверки/сериализация доступны любому потоку,
* один receiver принадлежит одному владельцу, без внутренней синхронизации.
* Сетевой transport использует receiver из uasync; codec workers используют проверки формата.
* Кодеки: H.264 Baseline/Constrained Baseline, H.265 Main (Main/High tier), 8 bit,
* 4:2:0, progressive, без B-кадров/reorder. DPB ограничен уровнем потока.
* Поколение меняется только при смене формата/камеры; bitrate не меняет поколение.
* Поля format неизменны внутри generation; его увеличивают при смене формата/камеры.
* Текущий bitrate encoder не входит в format и не меняет generation.
* KEY: config_length:4BE + Annex B configuration + Annex B access unit (как uAV2).
* Приём хранит один частичный кадр. Готовый view заимствован до следующего push/reset.
* Очередью готовых кадров, decoder и показом по audio clock владеет вызывающий. */
@@ -31,29 +34,31 @@ extern "C" {
* Поля — независимые верхние границы, не обещание поддержки их сочетания:
* выбранный режим дополнительно проверяется открытием платформенного codec. */
struct call_video_limit {
uint16_t width, height, fps;
uint32_t pixels_per_second, maximum_bps;
uint16_t width, height, fps; // Верхние границы осей (16..4096) и частоты (1..60); нулевой профиль — все поля нулевые.
uint32_t pixels_per_second, maximum_bps; // Ограничение width*height*fps и целевого bitrate (300000..4000000 бит/с).
uint8_t profiles; // Маска режимов: AVC Baseline/CB; HEVC Main с Main/High tier. Приём каждого проверяется отдельно.
uint8_t level;
};
struct call_video_caps { struct call_video_limit encode[2], decode[2]; };
struct call_video_format {
uint64_t generation;
uint16_t width, height, fps, rotation;
uint8_t codec, level;
uint64_t generation; // Ненулевой, монотонно возрастающий номер формата в одном направлении звонка.
uint16_t width, height, fps, rotation; // Чётные размеры 16..4096, 1..60 fps, поворот отображения 0/90/180/270 градусов.
uint8_t codec, level; // CALL_VIDEO_H264/H265 и AVC level_idc / HEVC general_level_idc.
uint32_t maximum_bps; // Предел целевого bitrate; не обещание мгновенного размера выхода/CPB.
uint8_t profile;
uint8_t profile; // Один режим 1/2, не маска: AVC Baseline/CB либо HEVC Main с Main/High tier.
};
struct call_video_frame {
uint64_t call_id, frame_id;
int64_t pts_us;
uint64_t call_id, frame_id; // Ненулевые ID; frame_id возрастает сквозь поколения, пропуск требует KEY.
int64_t pts_us; // Capture MONOTONIC отправителя: 0..INT64_MAX/2, строго возрастает сквозь поколения.
struct call_video_format format;
uint32_t size, offset, length;
uint8_t flags;
const uint8_t *data;
uint32_t size, offset, length; // Размер всего кадра, смещение и размер данного фрагмента; полный кадр: offset=0, length=size.
uint8_t flags; // Только CALL_VIDEO_KEY либо 0.
const uint8_t *data; // Начало payload данного фрагмента, уже со смещением offset; владение задаёт вызывающий API.
int64_t received_us; // Только локальная RX port метка MONOTONIC; по сети не передаётся.
};
/* 1 — все limits структурно корректны, включая полностью нулевые CAPS; 0 — ошибка/NULL.
* Не проверяет реальные codec и совместимость сочетания независимых границ. */
int call_video_caps_valid(const struct call_video_caps *caps);
/* После успешных платформенных проб width×height и height×width расширяет обе осевые
* границы limit до длинной стороны, сохраняя pixel-rate/FPS/bitrate/profile/level.
@@ -61,34 +66,47 @@ int call_video_caps_valid(const struct call_video_caps *caps);
* 0 — успех, -1 — неверный limit/codec (limit не меняется). */
int call_video_limit_orientations(struct call_video_limit *limit, int codec);
/* format задаёт generation/размер/FPS/rotation/maximum_bps, level=0, profile=0 для auto.
* codec=0: H.265, затем H.264. Успех: codec (1/2), заполняются codec и минимальный level.
* codec=0: H.265, затем H.264. Успех: codec (1/2), заполняются codec/profile и минимальный level.
* 0 — нет совместимого режима, -1 — неверные аргументы; на отказе format не меняется. */
int call_video_choose(const struct call_video_caps *sender, const struct call_video_caps *receiver,
struct call_video_format *format);
/* Проверка уже выбранного/полученного режима, включая оба направленных CAPS и level. */
/* 1 — формат допустим для sender.encode и receiver.decode, 0 — нет/неверный аргумент. */
int call_video_format_supported(const struct call_video_caps *sender, const struct call_video_caps *receiver,
const struct call_video_format *format);
/* 1/0: проверка полей и таблиц level по padded geometry/FPS/bitrate, без открытия codec. */
int call_video_format_valid(const struct call_video_format *format);
/* Нормативный предел DPB в кадрах по coded geometry и level; 0 при неверном уровне. */
unsigned call_video_dpb_limit(int codec, unsigned level, unsigned width, unsigned height);
/* 1/0: ненулевые HRD bitrate (бит/с) и cpb (биты) в пределах таблицы уровня.
* nal!=0 включает NAL-множитель; для HEVC используется консервативная таблица Main tier. */
int call_video_hrd_valid(int codec, unsigned level, int nal, uint64_t bitrate, uint64_t cpb);
/* Сравнение всех полей format, включая generation; оба указателя обязательны. */
int call_video_same_format(const struct call_video_format *a, const struct call_video_format *b);
/* CAPS: четыре 16-байтовых limits (encode AVC/HEVC, decode AVC/HEVC), integers BE.
* 0/-1; при ошибке выход не меняется. Parse требует ровно CALL_VIDEO_CAPS_BYTES. */
int call_video_caps_encode(const struct call_video_caps *caps, uint8_t out[CALL_VIDEO_CAPS_BYTES]);
int call_video_caps_parse(const uint8_t *data, size_t length, struct call_video_caps *out);
/* Весь header проверяется до записи/публикации view. Отказ: -1, выход не меняется. */
/* Header 56 байт BE + 1..1200 байт payload; size всего кадра <=2 МиБ.
* Encode копирует fragment.data[0..length); parse возвращает view внутри data.
* Успех 0; весь header проверяется до записи/публикации view. Отказ -1, выход не меняется. */
int call_video_fragment_encode(const struct call_video_frame *frame, uint8_t *out, size_t capacity, size_t *length);
int call_video_fragment_parse(const uint8_t *data, size_t length, struct call_video_frame *out);
struct call_video_receiver;
/* Создать пустую сборку, изначально требующую KEY; NULL — ошибка выделения. */
struct call_video_receiver *call_video_receiver_create(void);
/* Destroy/reset допускают NULL. Reset освобождает буфер и забывает ID/PTS/формат. */
void call_video_receiver_destroy(struct call_video_receiver *receiver);
void call_video_receiver_reset(struct call_video_receiver *receiver);
/* 1 — полный кадр, 0 — частичный/пропущенный, -1 — неверный поток; после пропуска нужен KEY.
* bytes — строго один пакет, уже проверенный на принадлежность call_id/peer/group владельцем.
* now_us — монотонное время для ограничения жизни частичной сборки (2с).
* fragment — валидный заголовок и payload одного фрагмента, call_id/peer/group проверяет владелец.
* now_us — неубывающее локальное время; возраст частичной сборки >2с проверяется при push.
* Фрагменты одного кадра идут без разрывов offset; повтор/перестановка сбрасывают сборку.
* KEY здесь проверяется только по envelope; NAL проверяет call_video_bitstream/платформа.
* Новый формат принимается только с KEY. Старые готовые кадры вызывающий сохраняет до их PTS. */
int call_video_receiver_push(struct call_video_receiver *receiver, const struct call_video_frame *fragment,
uint64_t now_us, struct call_video_frame *complete);
/* 1 — нужен независимый KEY (также для NULL), 0 — цепочка сборки непрерывна. */
int call_video_receiver_needs_key(const struct call_video_receiver *receiver);
#ifdef __cplusplus
}
+7 -2
View File
@@ -3,7 +3,8 @@
* progressive 8-bit 4:2:0, один spatial/temporal layer, без reorder. DPB — по Annex A.
* HEVC допускает dependent segments внутри IDR и явный pic_output_flag=1;
* первый слайс независимый, адреса остальных — в границах CTB геометрии SPS.
* Результат публикуется целиком только после успешной проверки VPS/SPS/PPS и IDR.
* Это проверка нужных полей parameter sets и заголовков IDR, без полного разбора entropy payload;
* она не заменяет проверку реальным decoder. Результат публикуется только при успехе.
* Все функции доступны worker-потокам; состояния и платформенных объектов нет. */
#ifndef CALL_VIDEO_BITSTREAM_H
#define CALL_VIDEO_BITSTREAM_H
@@ -12,9 +13,13 @@
extern "C" {
#endif
struct call_video_bitstream {
unsigned width, height, level, profile, dpb;
unsigned width, height, level, profile, dpb; // Видимая геометрия после crop, wire level/profile и SPS DPB в кадрах.
};
/* codec=1/2, data — полный KEY envelope размером 9..CALL_VIDEO_MAX_FRAME.
* 0 — заполнен out, -1 — отказ с логом; out на ошибке не меняется, data заимствован на время вызова. */
int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct call_video_bitstream *out);
/* Inspect + сравнение геометрии/profile и actual level <= format.level; 0/-1.
* AVC CB принимается и в Baseline envelope. Сам format предварительно валидирует владелец. */
int call_video_check_key(const struct call_video_format *format, const uint8_t *data, size_t length);
/* KEY-проба 1280x720 либо 1920x1080, включая обратную ориентацию, fps=30 либо 60.
* Буфер 8192 байт достаточен; возвращает размер или -1 с диагностикой. */
+13 -8
View File
@@ -1,6 +1,7 @@
/* Общий таймер показа видео, один владелец — RX worker. Изображениями не владеет.
/* Общий таймер показа видео, один владелец — worker показа. Изображениями не владеет.
* poll вызывается для первого готового decoded кадра. queued — все ещё не показанные полные кадры.
* SHOW/DROP_ORDER/DROP_LATE потребляют decoded кадр, WAIT сохраняет его. Encoded входы не пропускаются.
* DROP_ORDER/DROP_LATE освобождают decoded кадр, SHOW удерживает его до presented; WAIT сохраняет голову.
* Сам модуль очереди не меняет. Encoded входы не пропускаются.
* received_us ставится на входе RX port, до ожидания worker; все локальные времена — MONOTONIC, не BOOTTIME.
* SHOW резервирует один кадр до presented(pts, фактическое время); только ACK продвигает часы.
* Короткое опустошение сохраняет расписание. Темп плавно зависит от суммарного запаса кадров.
@@ -14,7 +15,7 @@ extern "C" {
#endif
struct call_video_playout_frame {
int64_t pts_us, received_us;
int64_t pts_us, received_us; // Capture PTS отправителя и локальное время полной RX-сборки, мкс.
uint32_t interval_us; // 1000000/format.fps; разрыв capture PTS не создаёт повторную паузу.
};
enum call_video_playout_action {
@@ -22,9 +23,9 @@ enum call_video_playout_action {
};
struct call_video_playout_event;
struct call_video_playout {
uint64_t call_id;
int anchored, audio_valid;
int64_t last_pts, last_deadline, deadline_us;
uint64_t call_id; // Для диагностики; не фильтрует входные кадры по звонку.
int anchored, audio_valid; // Есть фаза подтверждённого показа / последний clock был valid.
int64_t last_pts, last_deadline, deadline_us; // Последний выбранный/пропущенный PTS, фаза и срок следующего poll.
int64_t report_us, last_show_us, interval_min, interval_max, av_min, av_max, dispatch_max, data_shift;
int64_t last_show_pts, last_received, gap_pts, gap_received, gap_dispatch;
uint32_t speed_ppm; // Темп аудио + коррекция отставания, максимум 1.6x.
@@ -34,7 +35,7 @@ struct call_video_playout {
unsigned source_gaps;
int64_t source_gap_max; // Большие разрывы capture PTS, уже прошедшие до показа.
struct call_video_playout_frame pending; // Единственный выбранный кадр до подтверждения потребителем.
int awaiting;
int awaiting; // Есть SHOW без соответствующего presented; новые SHOW запрещены.
int64_t selected_us, pending_av, head_pts, head_due_us, lead_us, rate_us;
int64_t head_show_us; // Плановая презентация головы; GUI ACK корректирует фазу плавно.
unsigned queued, reserve; // reserve — будущие кадры сверх головы, включая необходимый pipeline backend.
@@ -54,16 +55,20 @@ struct call_video_playout_event {
/* Reporter вне timer: форматирует и выводит сохранённое событие через DEBUG категории call. */
void call_video_playout_log_event(const struct call_video_playout_event *event);
/* Локальное MONOTONIC время, мкс (Windows использует get_time_us); не capture PTS удалённого узла. */
int64_t call_video_playout_now_us(void);
/* Обнулить состояние и logger, задать id. p обязателен; вызывать до первого poll/после остановки владельца. */
void call_video_playout_init(struct call_video_playout *p, uint64_t call_id);
/* frame==NULL обновляет состояние без снятия временной привязки.
* deadline_us — абсолютный срок при WAIT, проверять аудиочасы не реже чем раз в 5мс.
* queued учитывает encoded, decoder, decoded и GUI каждый ровно один раз; reserve обычно 1.
* p обязателен, now_us — неубывающий MONOTONIC; NULL/invalid audio означает WAIT.
* Некорректный PTS/received/interval даёт DROP_ORDER только при valid audio и отсутствии awaiting.
* source_epoch не участвует: коррекция аудио не означает разрыв видеокодека. */
enum call_video_playout_action call_video_playout_poll(struct call_video_playout *p,
const struct call_video_playout_frame *frame, int64_t now_us, const struct call_clock_snapshot *audio,
unsigned queued, unsigned reserve);
/* Подтверждение выбранного PTS в MONOTONIC. Повторный/чужой ACK не меняет расписание. */
/* Подтверждение выбранного PTS в MONOTONIC, не раньше выбора. Повторный/чужой ACK логируется и игнорируется. */
void call_video_playout_presented(struct call_video_playout *p, int64_t pts_us, int64_t now_us);
#ifdef __cplusplus
}
+13 -6
View File
@@ -1,6 +1,7 @@
/* call_video_port — обмен готовыми видеокадрами между платформенными workers и ядром звонка.
* Attach подключает ядро; для активного call_id: open → send/pull/status → close. Перед отправкой настроить encoder через format.
* Attach/detach — uasync; остальные API потокобезопасны. До detach остановить пользователей порта.
* Attach/detach — uasync; остальные API потокобезопасны. До detach остановить пользователей порта
* и снять transport callbacks через close с обработкой команды либо остановку сессии звонка.
* Send копирует кадры; pull передаёт буфер вызывающему, освобождение — u_free(). Команды исполняются в uasync.
* В процессе один порт: TX до восьми кадров по 2 МиБ, RX до восьми кадров и 4 МиБ суммарно. */
#ifndef CALL_VIDEO_PORT_H
@@ -24,7 +25,7 @@ struct call_video_port_status {
};
// ── Привязка к ядру ──
// Подключить единственное ядро к порту; 0/-1.
// Подключить единственное ядро к порту; 0/-1. Повторный attach, даже того же instance, — ошибка.
int call_video_port_attach(struct UTUN_INSTANCE *inst);
// Отменить ожидающий post и освободить очереди; выполнять до уничтожения ядра.
@@ -32,7 +33,8 @@ void call_video_port_detach(struct UTUN_INSTANCE *inst);
// ── Открытие и закрытие видеотрека ──
// Скопировать локальные capabilities и запланировать настройку видеотрека; 0/-1, не подтверждение готовности пира.
// Скопировать CAPS и запланировать настройку; 0/-1. Active-call проверяется асинхронно.
// Повтор open того же id сохраняет RX/TX/счётчики/wake; другой id очищает их.
int call_video_port_open(uint64_t id, const struct call_video_caps *caps);
// Закрыть текущий id и очистить его очереди; чужой id игнорируется.
@@ -54,7 +56,9 @@ int call_video_port_set_wake(uint64_t id, void (*wake)(void *), void *arg);
// ── Настройка кодера и исходящие кадры ──
// Передать фактический формат и битрейт открытого encoder; формат копируется. 0 — команда принята, -1 — отказ.
// Передать формат/битрейт открытого encoder; 0 — команда принята, -1 — отказ.
// Перед сменой формата владелец при необходимости ждёт tx_idle; сама команда не ждёт drain.
// Итог проверять по status.error/configured_generation; 0 не подтверждает настройку транспорта.
int call_video_port_format(uint64_t id, const struct call_video_format *format, uint32_t bps);
// Подтвердить команду status.rate_generation/rate_request: bps — применённый битрейт, 0 — отказ encoder. 0/-1.
@@ -62,9 +66,12 @@ int call_video_port_applied(uint64_t id, uint64_t generation, uint64_t request,
#define CALL_VIDEO_PORT_TX_FRAMES 8u
#define CALL_VIDEO_PORT_TX_BYTES (CALL_VIDEO_PORT_TX_FRAMES * CALL_VIDEO_MAX_FRAME)
/* Атомарно копирует готовую пачку одного звонка. 1 — принята целиком, 0 — mailbox занят,
/* Атомарно копирует готовую пачку одного звонка/формата с возрастающими frame_id/PTS.
* 1 — принята целиком, 0 — mailbox занят,
* -1 — ошибка; на 0/-1 ни один кадр не принят. count: 1..CALL_VIDEO_PORT_TX_FRAMES.
* Входные кадры всегда остаются у вызывающего. При 0 повторить ту же пачку после освобождения порта. */
* Входные кадры всегда остаются у вызывающего. При 0 повторить ту же пачку после освобождения порта.
* 1 подтверждает копирование в mailbox; transport проверяет формат/цепочку позже.
* Transport busy сохраняет непринятый хвост, ошибка освобождает его и увеличивает key_serial. */
int call_video_port_send(const struct call_video_frame *frames, unsigned count);
// ── Входящие кадры ──
+14 -10
View File
@@ -1,7 +1,9 @@
/* Регулятор исходящего видеотрека: чистое состояние без таймеров, транспорта и encoder.
* Владелец вызывает step каждые 50мс с согласованными окнами доставки/производства 500мс.
* Все размеры — уникальные байты сжатого видео без заголовков. Held A/V не входит в debt.
* После смены транспортной эпохи нужен init. Команды encoder сериализует вызывающий. */
* После смены транспортной эпохи нужен init. Команды encoder сериализует вызывающий.
* Step допускает ненулевые границы/applied до 100 Мбит/с; диапазон звонка
* 300..4000 кбит/с проверяют parse_range и transport. */
#ifndef CALL_VIDEO_RATE_H
#define CALL_VIDEO_RATE_H
#include <stdint.h>
@@ -18,32 +20,34 @@ enum call_video_rate_reason {
};
struct call_video_rate_sample {
uint64_t now_us, window_end_us;
uint64_t delivered_bytes, produced_bytes, debt_bytes;
uint32_t applied_bps, minimum_bps, maximum_bps;
uint32_t baseline_rtt_us, rtt_us, frame_interval_us;
uint64_t now_us, window_end_us; // Неубывающее время и граница завершённого 50мс интервала: 0 <= now-end <50мс.
uint64_t delivered_bytes, produced_bytes, debt_bytes; // ACK/выход за 500мс и текущий долг; каждый <=UINT32_MAX.
uint32_t applied_bps, minimum_bps, maximum_bps; // Фактически применённый bitrate и желаемые границы, бит/с.
uint32_t baseline_rtt_us, rtt_us, frame_interval_us; // RTT и период кадра 1..1000000мкс для допуска штатного inflight.
uint64_t debt_limit_bytes; /* Жёсткий предел admission, независимо от ACK-оценки. */
int valid, ready, no_route, receive_busy, probing, locally_held, command_pending;
int valid, ready, no_route, receive_busy, probing, locally_held, command_pending; // Флаги окна, канала и encoder-команды.
};
struct call_video_rate_result {
uint32_t target_bps;
enum call_video_rate_reason reason;
uint32_t target_bps; // Рекомендация; при pending остаётся applied, даже если диапазон изменился.
enum call_video_rate_reason reason; // Причина рекомендации либо удержания/блокировки.
int admit; /* Уже принятые кадры продолжают drain; новые запрещены при переполнении. */
uint64_t delivery_bps, excess_bytes;
uint64_t delivery_bps, excess_bytes; // delivered_bytes*16 и долг сверх допуска RTT + два периода кадра.
};
struct call_video_rate {
// Внутренняя история; изменяется только init/step у одного владельца.
uint64_t last_us, next_change_us, previous_debt, high_since_us, last_log_us;
uint32_t applied_bps;
int initialized, high;
};
/* Сбросить историю к now_us; NULL диагностируется и игнорируется. */
void call_video_rate_init(struct call_video_rate *rate, uint64_t now_us);
/* Атомарная настройка min:max в десятичных кбит/с, 300 <= min <= max <= 4000.
* На ошибке выходные значения не меняются; результат 0/-1. */
int call_video_rate_parse_range(const char *text, uint32_t *minimum_bps, uint32_t *maximum_bps);
/* 0 — результат получен, -1 — недопустимый снимок (состояние не меняется).
/* 0 — результат получен, -1 — недопустимый снимок (состояние и result не меняются).
* target != applied — рекомендация, не подтверждение применения. Pending запрещает новую команду. */
int call_video_rate_step(struct call_video_rate *rate, const struct call_video_rate_sample *sample,
struct call_video_rate_result *result);
+14 -4
View File
@@ -11,7 +11,9 @@ struct call_session;
extern "C" {
#endif
struct call_video_ops {
// peer/frame заимствованы на время callback; frame — полный собранный encoded кадр.
void (*capabilities)(void *arg, const struct call_video_caps *peer);
// 1 — есть место для полного кадра bytes, 0 — router удерживает первый фрагмент/ACK до resume.
int (*receive_ready)(void *arg, uint32_t bytes);
void (*frame)(void *arg, const struct call_video_frame *frame);
void (*need_key)(void *arg);
@@ -19,8 +21,11 @@ struct call_video_ops {
void (*decoded)(void *arg, uint64_t generation, int accepted);
void (*writable)(void *arg, int admit); // Разрешение новых входов encoder; также будит retry сохранённой пачки.
};
/* Настройка активного звонка, включая receive-only. NULL ops снимает платформенные callbacks.
* capabilities отражают только реально проверенные профили. 0 — успешно, -1 — ошибка. */
/* Настройка активного звонка, включая receive-only. При ops обязательны frame/receive_ready;
* NULL ops снимает callbacks, обнуляет локальные CAPS/регулятор и очищает TX/частичную сборку RX.
* caps обязательны даже при NULL ops; отражают только реально проверенные профили.
* 0 — CAPS поставлены в router, -1 — ошибка; при отказе отправки настройка уже может быть применена.
* CAPS повторяются до прикладного CAPS_ACK; успешный configure ещё не разрешает TX. */
int call_video_configure(struct UTUN_INSTANCE *inst, uint64_t call_id, const struct call_video_caps *caps,
const struct call_video_ops *ops, void *arg);
/* Копирует один полный кадр. 1 принят, 0 временная занятость, -1 неверный формат/сессия/цепочка.
@@ -29,10 +34,12 @@ int call_video_configure(struct UTUN_INSTANCE *inst, uint64_t call_id, const str
* Каждый кадр выходит только после соответствующего аудио; deadline головы 2с.
* При истечении deadline сбрасывается вся зависимая цепочка и запрашивается KEY. */
int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_frame *frame);
/* Повторить доставку router RX после освобождения места платформой; чужой/закрытый id игнорируется. */
void call_video_resume_receive(struct UTUN_INSTANCE *inst, uint64_t call_id);
/* Decoder потерял references. Запрос идемпотентный, отправляется не чаще раза в 500мс. */
void call_video_request_key(struct UTUN_INSTANCE *inst, uint64_t call_id);
/* Результат запуска decoder конкретного поколения, отдельно от потери references. */
/* Результат последнего полностью полученного поколения, accepted=0/1, 0/-1.
* Отправка control при необходимости повторяется timer, отдельно от потери references. */
int call_video_decode_result(struct UTUN_INSTANCE *inst, uint64_t call_id, uint64_t generation, int accepted);
/* После открытия encoder; format — согласованный и фактически проверенный режим.
* maximum_bps — дополнительный предел регулятора, не выше format.maximum_bps;
@@ -44,10 +51,13 @@ int call_video_decode_result(struct UTUN_INSTANCE *inst, uint64_t call_id, uint6
* applied=0 в rate_applied означает отказ encoder, выключает регулятор до configure_rate. */
int call_video_configure_rate(struct UTUN_INSTANCE *inst, uint64_t call_id, const struct call_video_format *format,
uint32_t applied_bps, uint32_t maximum_bps);
/* 0 — ненулевой результат принят; -1 — stale/неверный результат либо обработанный отказ applied_bps=0. */
int call_video_rate_applied(struct UTUN_INSTANCE *inst, uint64_t call_id, uint64_t generation,
uint64_t request, uint32_t applied_bps);
/* Только call.c: управление общей сессией и граница уже отправленного аудио. */
/* Только call.c: bind 0/-1 регистрирует два router-сервиса; unbind снимает их.
* Stop отменяет timer/продолжение/send waiter и освобождает transport сессии.
* Audio_sent обновляет границу уже отправленного аудио и повторяет flush; end_pts_us<0 игнорируется. */
int call_video_transport_bind(struct UTUN_INSTANCE *inst);
void call_video_transport_unbind(struct UTUN_INSTANCE *inst);
void call_video_transport_stop(struct call_session *session);
+8 -3
View File
@@ -1,3 +1,7 @@
/* Decoder видеотрека: один RX worker владеет всем экземпляром, submit/receive не потокобезопасны.
* Полный KEY проверяется общим C verifier; FFmpeg дополнительно проверяет PTS/profile/level/геометрию выхода.
* Новый формат сначала завершает старый через EOS. Готовые изображения сохраняет владелец.
* capabilities — отдельная блокирующая worker-проба с процессным кэшем, mutex и отменой. */
#pragma once
#include "../../../src/call/call_video.h"
#include <QImage>
@@ -12,7 +16,8 @@ public:
explicit CallVideoDecoder(Backend backend = Backend::Automatic);
~CallVideoDecoder();
// 1 — принят/получен, 0 — занят/ещё не готов, -1 — ошибка с подробностями в логе.
// При submit=0 владелец сохраняет исходный frame. receive отдаёт metadata без data.
// При submit=0 владелец сохраняет исходный frame. При 1 encoded bytes скопированы в FFmpeg,
// metadata сохранена до receive; очередь до восьми входов. receive отдаёт metadata без data и владеющий QImage.
int submit(const call_video_frame& frame);
int receive(call_video_frame& frame, QImage& image);
int finish(); // EOS: 1 принят, 0 нужно сначала получить выход, -1 ошибка.
@@ -21,8 +26,8 @@ public:
bool hardware() const;
QString error() const;
void useSoftware(); // После реального отказа устройства: следующий KEY открывает CPU, без повторной GPU пробы.
void close();
// running отменяет длительную пробу при stop; незавершённый результат не кэшируется.
void close(); // Отбрасывает pending inputs, освобождает FFmpeg/GPU; выбор backend и текст ошибки сохраняются.
// running отменяет ожидание lock/пробу; завершённый кэш может вернуться сразу, его не очищает close().
static call_video_caps capabilities(const std::atomic<bool>* running = nullptr);
private:
struct Impl;
+4 -1
View File
@@ -17,8 +17,11 @@ struct CallVideoRateCompletion {
class CallVideoEncoder {
public:
// true — открыт проверенный кандидат; при отказе прежний encoder остаётся, причина в error().
// HEVC TX — только Main tier; format заранее сверяет с CAPS владелец.
bool open(const call_video_format& format, uint32_t bitrate, const QString& backend = {});
void close();
void close(); // Ждёт фоновой подготовки, отбрасывает буферизованный выход/pending-команду и очищает error().
// true — одна команда принята; actual completion приходит из encode на границе KEY.
bool requestBitrate(uint64_t generation, uint64_t request, uint32_t bitrate);
// Пустое image забирает готовые кадры без нового input (pts игнорируется).
// На false output пуст; completion нужно обработать и на ошибке. PTS готового выхода сохраняет capture PTS.
+6 -1
View File
@@ -1,3 +1,7 @@
/* Desktop-видеозвонок: управление/camera/signals — GUI, encoder — TX, decoder — RX,
* playout — отдельный display worker. start запускает workers; stop будит и присоединяет их,
* закрывает port и очищает изображение. Нельзя вызывать stop из этих workers.
* Между RX/display ограниченная SPSC очередь из трёх кадров, включая ожидающий GUI ACK. */
#pragma once
#include <QObject>
#include <QImage>
@@ -16,8 +20,9 @@ public:
// Блокирующая подготовка только в worker: один общий кэш encoder/decoder на процесс, без камеры и сети.
// running отменяет ожидание/пробу; частичный результат не кэшируется. nullptr — без отмены.
static call_video_caps capabilities(const std::atomic<bool>* running = nullptr);
void start(quint64 id);
void start(quint64 id); // Тот же работающий id — без изменений; другой id сначала останавливает прежний.
void stop();
// codec=0 — Auto; изменение режима асинхронно, результат/отказ сообщается statusChanged.
void setVideo(bool enabled, const QByteArray& camera, int width, int height, int codec, int fps = 30);
void setPresentationDriven(bool enabled); // GUI-поток: окно подтверждает рисование, иначе достаточно доставки signal.
void imagePresented(quint64 serial); // GUI-поток; повторный/старый ACK безопасно игнорируется.