Files
utun2/doc/call_video.txt
T

276 lines
28 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 получателя.
Камера, разрешение, кодек и диапазон битрейта меняются во время звонка.
Автоматический диапазон по умолчанию 300–4000 кбит/с (десятичные единицы).
Время
Общая монотонная шкала захвата аудио и видео на каждом отправителе. 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.
Настройка диапазона, 14.10.2026
ChatGUI: Настройки -> аудио, поля минимального/максимального битрейта видеозвонка.
Android: Настройки -> аудио, ползунок диапазона. По умолчанию 300–4000 кбит/с;
пользователь может сузить диапазон внутри этих границ, включая фиксированный bitrate.
Одна атомарная настройка call_video_bitrate_range_kbps=300:4000 хранится в конфиге
desktop / DataStore Android и передаётся в общий chat_setting. min > max отвергается
без частичного изменения. Android включает настройку в конфиг при каждом запуске.
Во время звонка transport читает диапазон каждые 50мс, не сбрасывая pending-команду.
Верхняя граница ограничена проверенным codec cap текущего generation. Если новый
минимум выше codec cap, admission приостановлен до совместимой настройки/режима;
это событие логируется. Смена диапазона сама по себе не расширяет возможности codec.
При сети слабее 300 кбит/с target не уходит ниже нижней границы; bounded admission
не позволяет очереди бесконечно расти. Аудиоканал продолжает работать отдельно.
Порог 5% при снижении не мешает достигнуть точной нижней границы диапазона.
Проверено: модель полосы 150 кбит/с -> 6 Мбит/с достигает target 300 кбит/с,
затем 4 Мбит/с, очередь остаётся ограниченной. Сетевой test_call меняет диапазон
при pending-команде, принимает прежний ACK и затем получает target нового диапазона;
также проверяет отказ несовместимого codec cap. Логи /tmp/call-range-rate-test.log
и /tmp/call-range-integration-test.log. ChatGUI собран; Android assembleDebug и
81 unit-тест прошли, /tmp/call-range-android-ui-final.log.
Полный ./check.sh вне sandbox: 154/154, proxy 9/9, burst/load PASS;
/tmp/call-range-check-final.log. Первый запуск в sandbox блокировался запретом сокетов.
Настройки и регулятор готовы; подключение камеры/codec/render остаётся отдельным этапом.
Проверка реального x265 перед подключением backend, 14.10.2026
Добавлена ручная цель probe_call_x265 (EXCLUDE_FROM_ALL, не входит в ctest), если
найден x265 через pkg-config. Сборка/запуск:
cmake --build tools/chatgui/build --target probe_call_x265 -j4
./tools/chatgui/build/probe_call_x265
./tools/chatgui/build/probe_call_x265 --frame-rc
Один encoder, 640x360@30, Main 8-bit/Main tier/level 3.1, reference=1,
B=0/lookahead=0, ABR+VBV, буфер 1000 кбит. Три участка по 120 кадров:
4 Мбит/с -> 300 кбит/с -> 4 Мбит/с. Детерминированная шумовая сцена требует полосы;
измерение каждого участка исключает первые 30 кадров переходного процесса.
Код 0 — измерение прошло; 1 — ошибка API/потеря PTS; 2 — непригодный rate control.
Допуск измерения 0.5..1.5 target — диагностический, не гарантия CBR для любой сцены.
Установленная библиотека 4.1+1-1d117be (пакет 4.1-4) не проходит пробу:
target 4000000: output 4016866 бит/с;
target 300000: output 4006669 бит/с, несмотря на getter bitrate=300/vbv=300 кбит/с;
target 4000000: output 3945016 бит/с.
Лог: /tmp/call-x265-system.log. Return reconfigure=0 и getter недостаточны.
frame-rc также непригоден для этого zerolatency-профиля: output около 40 кбит/с
на всех трёх участках; /tmp/call-x265-frame-rc.log.
Причина проверена по исходникам именно установленного выпуска:
https://deb.debian.org/debian/pool/main/x/x265/x265_4.1.orig.tar.xz
SHA256 85abb8a850000bf693947459c42a666a3ecd6de10a2ac6b3c6647554b14d3270
Encoder::configure(p) теперь присваивает this->m_param=p. Вызов из
x265_encoder_reconfig с m_latestParam переключает encoder на новый объект, но
RateControl::m_param остаётся прежним адресом. Последующий x265_copy_params
копирует latest в него самого; reconfigureRC читает старые параметры rate control.
Для frame-rc обнаружен отдельный дефект: tuneQscaleToUpdatedBitrate делит
frameBitsTotal на i=0 при пустом lookahead, что в этой пробе приводит к QP=69.
Проверенный минимальный патч сохраняет активный m_param до configure(latest)
и восстанавливает его после. Файл для дальнейшего подключения:
tools/chatgui/ffmpeg/patches/x265-4.1-reconfigure-param-owner.patch
Применён только к отдельной копии исходников в /tmp/call-x265-source;
библиотека собрана в /tmp/call-x265-build без установки в систему.
С тем же тестом и frame-rc=off output=4016866 / 298165 / 4213269 бит/с.
Логи: /tmp/call_x265_probe_fixed.log и /tmp/call-x265-fixed.log.
Дополнительная проверка ffprobe: 360/360 кадров декодируются, Main, level=93
(HEVC level 3.1), has_b_frames=0. Системная библиотека и приложение не заменены.
ChatGUI успешно собирается; /tmp/call-x265-desktop-build.log.
Рекомендуемое продолжение: закрепить проверенную версию x265 и патч в локальной
сборке зависимости, повторить codec-пробу для рабочих разрешений и decoder,
затем подключить direct API к backend звонка. Непрошедший проверку backend
не объявлять в capabilities; H.264 остаётся альтернативой, когда H.265 недоступен.
Патч пока не подключён к build_ffmpeg.sh; изменение поставки зависимости и
интеграция backend остановлены по правилу AGENTS.md о выявленной нестыковке.