You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

200 lines
21 KiB

Видеозвонки 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.