Files
utun2/doc/call_video.txt
T

201 lines
21 KiB
Plaintext
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.
Видеозвонки Android и ChatGUI
Контракт
Каждая сторона независимо включает видео. Автовыбор H.265 Main 8-bit, затем
H.264 Constrained Baseline: пересечение encoder отправителя и decoder получателя.
Камера, разрешение, кодек и верхняя граница битрейта меняются во время звонка.
Время
Общая монотонная шкала захвата аудио и видео на каждом отправителе. PTS первого
аудиосэмпла/кадра сохраняется до вывода; время сетевой отправки учитывается отдельно.
Приёмное видео следует исходной позиции реально воспроизводимого аудио, включая
SoundTouch и очередь устройства. Алгоритм регулирования аудиобуфера сохраняется.
Искусственная тишина/тоны не продвигают исходную позицию. Формат видео меняется по PTS;
новое поколение не уничтожает старые кадры, соответствующие ещё не прозвучавшей речи.
Передача и регулирование
Один A/V-планировщик, независимые логические каналы управления/аудио/видео.
Звук продолжает работу при отказе камеры. Ограничены ожидание encode, очереди,
размер сборки кадра и память получателя; ACK не равен успешному decode/render.
Роутер считает исходный payload и пакеты, видеомодуль вычитает фиксированный
заголовок фрагмента. Физические очереди повторно не суммируются с router inflight.
Окно ACK: 10 завершённых интервалов по 50мс, текущая ячейка отдельно.
Регулятор общий C, решения проверяются управляемым временем. Снижение учитывает
подтверждённую скорость и разгрузку задолженности; повышение — малыми пробами.
Нет отдельного неограниченного интегратора: задолженность уже интегрирует дефицит.
Учитываются прогрев, RTT, minrtt_probe, отсутствие маршрута, локальное удержание
планировщиком, применение encoder-команды и смена эпохи.
Владение
Состояние звонка/регулятора/роутера — uasync. Камера и encoder/decoder — платформенные
workers. Ограниченный mailbox, одно ожидающее уведомление. Команды bitrate имеют
call_id, video_generation и request_id; одно исполнение и последнее желаемое значение.
Принятая настройка не заменяет измерение фактического выхода. Shutdown отменяет
уведомления/таймеры, останавливает producers, затем освобождает очереди и codecs.
Порядок реализации
[+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса.
[+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены.
[+] Дрейф feed/tempo устранён картой фактических участков SoundTouch; см. ниже.
[ ] H.265 runtime bitrate: поддерживаемый backend без циклического пересоздания.
[+] Общий регулятор с детерминированными сценариями и подключением к router/encoder-командам.
[+] Видеофрагменты, ограниченная сборка, обмен возможностями с подтверждением.
[ ] Дополнить возможности ограничениями profile/level/tier и диапазоном битрейта.
[ ] Завершить A/V scheduler с платформенными очередями захвата/encode.
[+] Общие capture PTS и отображение позиции аудиоустройства в исходное время.
[ ] ChatGUI: движок, окно и настройки; Android: движок, JNI, сервис и экран.
[ ] Интеграционные проверки всех пар, перегрузки, смены формата и lifecycle.
Приёмка
Нет двойного учёта байтов: принято = подтверждено + отменено + задолженность.
Повторные ACK/передачи не увеличивают delivered. Reset не считается доставкой.
Свободный канал не вызывает снижение на статичной сцене; рост полосы обнаруживается
пробами. Очереди остаются ограниченными при отсутствии ACK и остановке decoder.
Видео следует аудио при догоне, underrun, смене устройства и формате видеотрека.
Завершение звонка безопасно во время переключения камеры, bitrate и decoder.
Проверка SoundTouch, 14.10.2026
test_soundtouch_clock сравнивает положение реальных четырёхсэмпловых фрагментов
в выходном сигнале с двумя вариантами временной шкалы. 60с входа, mono 48kHz,
sequence=20ms, seek=10ms, overlap=4ms — параметры существующего call_jitter.
Максимальная ошибка feed/tempo / consumed-input, мс:
постоянный темп 1.6: 14.208 / 14.271
плавный цикл 1.0..1.6 (шаг 0.012/20ms): 29.550 / 13.723
стресс, переключение 1.0/1.6 каждые 20ms: 938.104 / 14.462
Стресс не воспроизводит ограничение темпа звонка, а проверяет отсутствие накопления.
Проблема: setTempo меняет обработку ещё не потреблённого входа; номинальные интервалы
frames/tempo, записанные при feed, не описывают фактический выход. Поэтому копировать
этот способ из stream_playback_audio в звонки нельзя. Изменений звука/буфера нет.
Рассмотренные варианты (реализован вариант 2):
1. Минимальный: шкала по фактически потреблённому входу и произведённому выходу,
с поправкой на seek-window. Тест показал <15мс на трёх сценариях, но это ещё не
доказанная граница для речи/Opus/разных блоков. Нужны эти проверки и явный допуск.
2. Более строгий: SoundTouch публикует соответствие исходных участков выходным
непосредственно в TDStretch. Позволяет учитывать выбранный WSOLA offset без
вычисления по косвенным счётчикам; потребует небольшого расширения bundled API.
Overlap смешивает два участка, поэтому даже здесь нужно определить правило PTS.
Рекомендуется для общего A/V clock: не меняет звук, исключает накопительный дрейф.
Реализованная шкала
SoundTouch сообщает Copy/Mix с фактическими индексами входа/выхода после WSOLA.
Mix интерполирует PTS двумя теми же линейными весами, которыми смешивается PCM.
Это определение времени смешанного сигнала, а не утверждение, что у смеси один источник.
Поддержан профиль звонка: float PCM, mono/stereo, rate=pitch=1, AA выключен.
Неподдержанная смена профиля/flush отключает карту до reset, сохраняя обработку звука.
CallTimeline переносит участки через частичные receiveSamples и локальные тоны/тишину.
Кольца ограничены; переполнение или противоречивый progress запрещают valid до reset.
Краткое движение исходного PTS назад удерживается, held_back_us видно в диагностике.
Разрыв более 100мс и смешивание разных source_epoch не дают valid-PTS.
Аудиопакет несёт capture_pts_us, capture_epoch и codec_delay_samples (Opus lookahead).
Метка неизвестного источника — INT64_MIN. Старого формата протокола больше нет.
После разрыва захвата хвост прежнего Opus не получает вымышленную новую метку.
Desktop передаёт исходную метку callback захвата; playback использует позицию PCM
и время вывода backend. measured показывает аппаратную отметку либо оценку backend.
Android использует AudioRecord timestamp MONOTONIC; до появления timestamp оценка
привязана один раз к окончанию первого read и далее идёт по сэмплам. После исчезновения
timestamp продолжается последний якорь. Это ограничение точности, а не аппаратное измерение.
AudioTrack playbackHeadPosition разворачивается из unsigned32 и ограничивается реально
записанным PCM. Опрос head — estimated (measured=0), write не подтверждает воспроизведение.
Локальные часы API — MONOTONIC, не get_time_us ядра (на Linux он использует BOOTTIME).
Снимок без progress более 250мс недостоверен; экстраполяции за подтверждённый участок нет.
Логи call DEBUG показывают epoch/source/played/written/PTS/valid/measured/held раз в секунду.
Видеотранспорт подключён к ядру; codecs, полный scheduler и UI ещё не подключены. Проверка A/V на устройствах
остаётся следующим интеграционным этапом; unit-тесты не измеряют физическую задержку динамика.
Проверки текущего этапа
./build.sh --no-gui -j4 — успешно.
./check.sh — 153/153, proxy 9/9, burst и load успешно; /tmp/call-video-check.log.
test_etcp_router_unit — 36/36; /tmp/call-video-router-unit.log.
UDP и STCP — по 440 пакетов, 5 циклов backpressure, без потерь/дублей/повреждений;
/tmp/call-video-router-udp.log, /tmp/call-video-router-tcp.log.
Регулятор: модель 120с с полосой 1.5→3Mbps, max debt=118559 bytes;
статичная сцена — 0 ложных снижений. Pending, cap, no route, warmup, локальное
удержание, minrtt probe, RTT=600ms проверены. Интеграция encoder ещё отсутствует.
test_soundtouch_clock — три сценария проходят предел consumed-input <20ms;
этот предел теста не является гарантией для всех входных сигналов.
Проверки реализованного варианта 2
Сравнение SoundTouch с callback и без него: PCM побитно одинаков; Copy/Mix
восстанавливаются по фактическому входу, включая mono/stereo, partial read и clear.
CallTimeline: 200с реального SoundTouch, смена темпа, границы участков, переполнение,
неверная/устаревшая позиция устройства и восстановление через reset.
CallAudio: 40с реального Opus+jitter, burst/догон, тоны/тишина, эпоха устройства;
capture metadata проверены через очередь uasync и реальный сетевой звонок.
Desktop: шесть профильных + пять проверок общего аудиостека прошли, vibechat собран.
Android: assembleDebug и все 79 unit-тестов прошли (arm64-v8a и x86_64 native).
./check.sh: 153/153, proxy 9/9, burst/load PASS; /tmp/call-clock-check-final.log.
Логи: /tmp/call-clock-desktop-final-tests.log, /tmp/call-clock-shared-tests.log
(test_radio_member_mute повторён вне sandbox: /tmp/call-clock-radio-mute-test.log),
/tmp/call-clock-android-final.log. Физические устройства в этом этапе не проверялись.
Реализованный видеотранспорт, 14.10.2026
call_video.c задаёт общий wire и сборку; call_video_transport.c владеет состоянием
видеоканала внутри call_session. Сигнализация звонка, video control, audio и video
имеют отдельные router service IDs. Приёмная очередь видео не блокирует аудио.
CAPS повторяется до прикладного CAPS_ACK: получение транспортного ACK не гарантирует,
что получатель уже обработал ACCEPT и установил capabilities. Кодек выбирается
направленно: encoder отправителя пересекается с decoder получателя, H.265 перед H.264.
Текущие limits проверяют размеры, FPS и pixels/second; контракт профиля ещё неполон.
Заголовок фрагмента 52 байта, payload до 1200 байт; полный кадр до 2MiB.
KEY содержит config_length:4BE + AnnexB config + access unit. Ядро проверяет
границы envelope; содержимое NAL обязан проверять платформенный decoder.
frame_id и capture PTS возрастают на всём протяжении звонка, включая смену generation.
Новый формат требует нового generation и KEY. После пропуска кадра нужен KEY.
На TX один незавершённый кадр, на RX одна ограниченная сборка; deadline 2с.
receive_ready резервирует возможность принять целый кадр до ACK первого фрагмента.
Callback копирует данные до возврата; платформенная очередь также должна быть ограничена.
Старые кадры другого generation сохраняются до соответствующей позиции аудиоряда.
Видео пока удерживается до передачи соответствующего аудио в router. Аудио никогда
не ждёт камеру. Это первая часть scheduler: ограничение ожидания encode, pacing
на максимальном битрейте и политика остановки видеотрека остаются следующими задачами.
flush ограничен 16 фрагментами за вызов; пропускную способность этого бюджета нужно
проверить вместе с частотой вызовов от платформенного producer и аудио.
Регулятор получает endpoint ACK 500мс/50мс и queued+inflight отдельного видеосервиса.
Фиксированные заголовки вычитаются; добавляется ещё не переданный остаток кадра.
Удержание по аудио не трактуется как нехватка сети. Смена эпохи сбрасывает окно.
Ограничение admission: задолженность + новый кадр не превышают 2MiB.
Одна команда bitrate в работе, подтверждение проверяет generation/request;
устаревшее подтверждение отвергается. Timeout 2с или отказ encoder останавливает
admission до явной повторной настройки; аудиозвонок продолжает работу.
Платформенные callbacks вызываются в uasync и не меняют call API реентерабельно.
При hangup отменяются таймеры и освобождаются pending TX/RX, callbacks снимаются.
Проверки видеотранспорта
./check.sh: 154/154, proxy 9/9, burst/load PASS; /tmp/call-video-core-check.log.
test_call_video проверяет capabilities, wire, фрагментацию, replay, разрыв,
timeout, смену generation и некорректные размеры/config.
test_call через два реальных UDP-ядра проверяет обмен CAPS, удержание видео по аудио,
backpressure получателя без остановки аудио, H.265 -> H.264 с новым generation,
bitrate command/ACK, устаревшие ACK и hangup с ожидающим кадром.
Payload в этом тесте синтетический: тест не доказывает работу HEVC/AVC decoder.
Лог: /tmp/call-video-rate-integration.log.
ChatGUI vibechat собран; test_voice_tx и test_call_jitter прошли.
Android assembleDebug/testDebugUnitTest успешны; /tmp/call-video-android-core-build.log.
Камера, декодирование/показ и физическая A/V-синхронизация пока не проверены.
Выявленная нестыковка перед подключением codecs
Размеров/FPS/pixels-per-second недостаточно: допустимый битрейт зависит также от
profile/level/tier и ограничений конкретного decoder. Текущий StreamVideoEncoder
не умеет runtime bitrate для H.265, а прямой x265 требует дополнительной проверки.
В upstream x265_encoder_reconfig при смене profile/level/tier отменяет изменение
rate-control, но эта ветка не меняет ret=0. Успешный return не доказывает применение.
Источник: https://github.com/videolan/x265/blob/master/source/encoder/api.cpp
Предлагаемое продолжение:
1. Зафиксировать Main 8-bit / Constrained Baseline и согласовать level/tier,
верхний bitrate/VBV и ограничения reference frames для каждого направления.
Выбранный режим должен проверяться фактическим открытием encoder/decoder.
2. Открывать encoder в согласованных пределах. Автоадаптация работает внутри них
без пересоздания. ACK команды означает проверенное фактическое применение,
а не только возврат setter; отдельно измеряется реальный выход encoder.
3. Пользовательское изменение за пределами режима создаёт новый generation,
config и KEY после проверки возможностей получателя; звук продолжается.
4. Проверить установленный x265 и Android MediaCodec тестом encode/decode с
понижением/повышением битрейта и отказом настройки. Непригодный H.265 backend
не объявляется доступным; в auto используется H.264.
Подключение платформенных codecs остановлено на этой границе согласно AGENTS.md.