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.
 
 
 
 
 
 

638 lines
69 KiB

Видеозвонки Android и ChatGUI
Контракт
Каждая сторона независимо включает видео. Автовыбор H.265 Main 8-bit, затем
H.264 Baseline/Constrained Baseline: пересечение encoder отправителя и decoder получателя.
Encode/decode проверяются независимо, профили AVC согласуются явной маской.
HEVC Main/Main tier, 8-bit 4:2:0, progressive, без reorder; DPB ограничен Annex A,
не фиксированным числом references. Общий call_video_bitstream проверяет VPS/SPS/PPS,
HRD при наличии, геометрию и независимый IDR. Фактический level проверяется после
открытия encoder. CAPS — 64 байта, header — 56 байт; старый формат не поддерживается.
VBV является локальной настройкой software encoder, в сетевом контракте его нет.
Результат запуска decoder (generation + accepted) передаётся отдельно от router ACK:
неподдержанный формат вызывает fallback в Auto, потеря references — запрос KEY.
Камера, разрешение, кодек и диапазон битрейта меняются во время звонка.
Автоматический диапазон по умолчанию 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.
Применение bitrate на ключевом кадре (уточнение пользователя)
Worker хранит pending-команду и применяет её перед кодированием ближайшего
планового IDR. GOP ограничен одной секундой исходной шкалы capture PTS, а не
числом принятых сетью кадров: backpressure не должен блокировать переключение.
Для x265 используется новый encoder с нужным bitrate; обычный reconfigure даже
на IDR не обходит найденный дефект 4.1. Исправленная внешняя библиотека не нужна.
Первый выход нового encoder обязан содержать IDR и актуальные VPS/SPS/PPS.
Камера, audio epoch, capture PTS и последовательность frame_id не перезапускаются.
При неизменном формате bitrate не меняет video_generation; новые parameter sets
идут с KEY. Смена codec/разрешения/камеры сохраняет отдельный протокол generation.
Приём применяет конфигурацию перед декодированием соответствующего KEY, не удаляя
уже декодированные старые кадры, ожидающие своего времени по audio clock.
Подтверждение rate_applied отправляется после проверки первого нового IDR,
независимо от сетевого admission; иначе закрытый admission создаст взаимное ожидание.
Успех означает применение настройки, не гарантию бит-в-бит постоянного выхода.
До IDR регулятор видит старый applied bitrate и одну pending-команду; его текущий
таймаут 12с включает ожидание IDR, проверку hardware encoder (до 5с) и возможное
восстановление предыдущей конфигурации (до 5с). При отсутствии кадров/ошибке
команда завершается отказом; нельзя бесконечно продлевать её таймаут.
Ограничитель очереди работает и во время ожидания. Если новый IDR не принят,
producer запрашивает следующий IDR по существующему контракту send_frame.
Desktop готовит кандидата в отдельном потоке, продолжая кодирование старым encoder.
Готовый кандидат применяется только на IDR; при незавершённой подготовке текущий
GOP продолжается. Android сначала меняет bitrate через MediaCodec.setParameters
без остановки; подтверждает только проверенный KEY с PTS не раньше команды.
Уже закодированные P-кадры перед этим KEY сохраняются: runtime bitrate не разрушает references.
При отказе runtime API Android переоткрывает encoder последовательно (аппаратный
ресурс может быть единственным), при ошибке восстанавливает прежнюю конфигурацию
и подтверждает фактически восстановленный bitrate. В этой резервной ветке возможна
пауза картинки; аудио и capture timeline продолжаются.
Порядок реализации
[+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса.
[+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены.
[+] Дрейф feed/tempo устранён картой фактических участков SoundTouch; см. ниже.
[+] ChatGUI codec component: замена H.264/H.265 encoder на IDR, с непрерывными PTS.
[+] Android MediaCodec: применение bitrate на ближайшем IDR, проверка фактического формата.
[+] Общий регулятор с детерминированными сценариями и подключением к router/encoder-командам.
[+] Видеофрагменты, ограниченная сборка, обмен возможностями с подтверждением.
[+] Согласование profiles/level и целевого bitrate; проверка transport/encoder, независимый decoder probe.
[+] A/V scheduler с платформенными очередями захвата/encode.
[+] Общие capture PTS и отображение позиции аудиоустройства в исходное время.
[+] ChatGUI: движок, окно и настройки; Android: движок, JNI, сервис и экран.
[ ] Интеграционные проверки всех пар, перегрузки, смены формата и lifecycle.
Приёмка
Нет двойного учёта байтов: принято = подтверждено + отменено + задолженность.
Повторные ACK/передачи не увеличивают delivered. Reset не считается доставкой.
Свободный канал не вызывает снижение на статичной сцене; рост полосы обнаруживается
пробами. Очереди остаются ограниченными при отсутствии ACK и остановке decoder.
Видео следует аудио при догоне, underrun, смене устройства и формате видеотрека.
Завершение звонка безопасно во время переключения камеры, bitrate и decoder.
Далее журнал этапов разработки. Старые описания wire/profile/VBV ниже исторические;
актуальный контракт приведён в начале, последние проверки — в конце документа.
Проверка 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 ограниченная FIFO: до 32 кадров, 500мс по PTS и 2MiB суммарно; на RX одна ограниченная сборка.
Deadline головы TX и сборки RX — 2с. Каждый TX кадр ждёт соответствующего audio_end_pts;
следующие P-кадры сохраняются в FIFO, а не теряются на обычной задержке аудиообработки.
При потере головы отбрасывается вся зависимая цепочка и запрашивается KEY. В сетевой долг
входят только готовые к отправке кадры FIFO; ожидание аудио само по себе не перегружает канал.
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.
Первоначальное предложение (заменено решением переключать encoder на IDR ниже):
закрепить проверенную версию x265 и патч в локальной
сборке зависимости, повторить codec-пробу для рабочих разрешений и decoder,
затем подключить direct API к backend звонка. Непрошедший проверку backend
не объявлять в capabilities; H.264 остаётся альтернативой, когда H.265 недоступен.
Патч пока не подключён к build_ffmpeg.sh; изменение поставки зависимости и
интеграция backend остановлены по правилу AGENTS.md о выявленной нестыковке.
Проверка переключения на плановом IDR, 14.10.2026
По уточнению пользователя вместо поставки изменённой x265 проверено пересоздание
encoder на очередном IDR. Добавлены режимы probe_call_x265:
--keyframe-reconfig
--keyframe-reopen --output /tmp/call-x265-key-reopen.hevc
Запрос приходит на кадре 7 очередного участка; применение перед кадром 30,
через 23 кадра (767мс при 30fps). Плановый IDR задаётся каждые 30 кадров,
проверяются его NAL-тип и наличие VPS/SPS/PPS. PTS всех 360 кадров проверяются
на точное соответствие исходным, включая обе границы замены encoder.
Измеряются последние 60 кадров каждого 120-кадрового участка, после перехода.
На системной x265 4.1 без патча:
reconfigure перед IDR: 4008828 / 4001940 / 3977828 бит/с — отказ, код 2;
новый encoder на IDR: 4008828 / 309564 / 4019812 бит/с — PASS, код 0.
Логи: /tmp/call-x265-key-reconfig.log, /tmp/call-x265-key-reopen.log.
ffmpeg -v error -xerror декодирует объединённый поток без ошибок;
ffprobe подтверждает Main/level 3.1, B=0 и 360 кадров.
Это проверка 640x360@30 на синтетической сложной сцене, не готовый тракт звонка:
worker/camera/decoder UI и Android MediaCodec ещё предстоит подключить и проверить.
Патч x265 остаётся диагностическим материалом и не требуется выбранному способу.
Компонент кодирования ChatGUI, 14.10.2026
StreamVideoEncoder переименован в LiveVideoEncoder и выделен в chatgui_live_video;
трансляции используют тот же код, без дублирования захвата/конвертации/FFmpeg.
Явный профиль H.264 Constrained Baseline / H.265 Main задаёт level, reference=1,
VBV и 8-bit 4:2:0. Для этого режима пока проверены software libx264/libx265.
Открытие проверяет независимый IDR настоящим decoder: profile, level, Main tier,
pixel format, отсутствие B-кадров и размер. Проверка принятого option не заменяет
эту проверку bitstream. Automatic-профили трансляций сохраняют прежний выбор GPU/SW.
Опции encoder сверены с https://ffmpeg.org/ffmpeg-codecs.html#libx265 и разделом libx264.
CallVideoEncoder принадлежит video worker. Он хранит единственную pending
команду generation/request/bitrate, отклоняет replay, чужой generation и выход за
согласованный maximumBitrate. После команды кандидат открывается в фоне;
на ближайшем IDR после готовности проверяется полный KEY с точным исходным PTS.
Старый encoder сохраняется до успешной проверки; stop ждёт завершения подготовки.
При отказе кандидата возвращается completion с bitrate=0 и используется прежний
контекст. Неизменный target также подтверждается на KEY без пересоздания.
Completion отделён от отправки кадра: даже при закрытом admission его нужно
передать в call_video_rate_applied. Периодический IDR задаётся по capture PTS,
поэтому пропуски кадров/закрытая сеть не сдвигают применение на дополнительные GOP.
Физический захват камеры, audio origin и сетевые frame_id компонентом не меняются.
test_call_video_encoder прошёл: 5 QtTest cases (включая init/cleanup), без skip.
Для обоих codecs: 640x360@30, level 3.1, VBV 1 Мбит, шумовая сцена;
запрос на кадре 7, переход на кадре 30, один decoder на все 360 кадров.
Проверены PTS каждого decoded frame, профиль/level/размер/B=0 и средний bitrate:
H.264: 3996916 / 296904 / 3989748 бит/с;
H.265: 3993720 / 302956 / 3999788 бит/с.
Отдельно проверены отказ новой конфигурации с сохранением старого encoder,
предел bitrate, pending/replay/generation, force-key и разрыв capture PTS вперёд.
Логи: /tmp/call-live-video-test-final.log, /tmp/call-live-video-build-final.log.
test_stream_publish: 11 passed, 1 skip (capture под Xvfb), 0 failed;
/tmp/call-live-stream-regression.log. После уточнения VBV профильная проверка
liveSoftwareBitrateKeepsDecoder повторена успешно, /tmp/call-live-stream-bitrate-final.log.
ChatGUI собран; ./check.sh: 154/154, proxy 9/9, burst/load PASS за 118с;
/tmp/call-live-core-check.log. Android в этом этапе не менялся.
Граница этого этапа: рабочий codec component, ещё не камера/worker/видеозвонок.
Следующим этапом реализовано согласование ограничений, описанное ниже.
Согласование ограничений codec, 14.10.2026
Общий C-протокол фиксирует H.264 Constrained Baseline / H.265 Main, Main tier,
8-bit 4:2:0, progressive, reference=1, B=0. Другие профили этим протоколом не объявляются.
CAPS отдельно для encode/decode каждого codec передаёт width/height/FPS,
pixels_per_second, maximum_bps, maximum_vbv_bits и максимальный level.
Размер CAPS теперь 80 байт, заголовка фрагмента 60 байт; старый формат не поддерживается.
call_video_choose пересекает encode отправителя с decode получателя и выбирает
H.265, затем H.264. Явный codec запрещает автоматическую замену. Выбирается минимальный
level, вмещающий весь запрошенный режим, включая максимальный bitrate и VBV.
Неподходящий запрос не меняется и не сокращается молча; более узкий диапазон требует
нового явного выбора владельцем. Таблицы проверяют размер, частоту, геометрию и BR/CPB.
AVC учитывает дополнение до макроблоков 16x16; HEVC консервативно резервирует дополнение
до CTU 64x64 независимо от backend. Это может выбрать level выше минимального для
конкретного encoder. Пределы BR/CPB взяты консервативно для VCL без повышающих NAL-факторов.
Сверено с таблицами FFmpeg и исходниками x265 4.1:
https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/libavcodec/h264_levels.c
https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/libavcodec/h265_profile_level.c
CAPS задаёт независимые верхние границы: не гарантируется работоспособность всех
их сочетаний на конкретном устройстве. Перед объявлением и использованием нужны
платформенные пробы encoder/decoder. Проверка структуры формата не заменяет проверку
фактических parameter sets и кадров декодером.
call_video_format содержит level, maximum_bps и vbv_bits. Эти поля, codec и геометрия
неизменны внутри generation; изменение требует нового generation и KEY.
Текущий bitrate меняется на IDR в пределах этого формата без нового generation.
call_video_configure_rate получает весь формат, проверяет обе CAPS и сохраняет его;
TX обязан передавать именно этот формат, RX также проверяет согласованные ограничения.
Дополнительный потолок регулятора может быть ниже уже применённого bitrate, чтобы
дождаться его снижения, но applied не может превышать maximum_bps самого формата.
Изменение пользовательского диапазона не переписывает согласованный формат.
Если новые CAPS перестали вмещать текущий формат, admission закрывается, ожидающий
TX-кадр отбрасывается с причиной capabilities_changed и запрашивается новый KEY.
CallVideoEncoder открывается прямо из call_video_format; дублирующего описания
режима между общим протоколом и desktop encoder больше нет. Проверка кандидата
реальным decoder и сохранение старого контекста при отказе остаются обязательными.
Диагностика CAPS выводит level/bitrate/VBV каждой стороны; отклонённый TX показывает
frame_id/PTS относительно предыдущего кадра, размер/offset и валидность формата.
Проверки: test_call_video покрывает направленный выбор, fallback, границы level,
bitrate/VBV, атомарность отказа и смену generation. test_call: 2/2 PASS, включая
сужение CAPS при ожидающем кадре и завершение звонка; /tmp/call-envelope-network-fixed.log.
Реальный codec-тест: 5/5 QtTest cases PASS. Для 640x360@30 общий выбор даёт level 3.0
(AVC 30 / HEVC 90), максимум 4 Мбит/с, VBV 1 Мбит. На 360 кадрах с одним decoder:
H.264: 3996048 / 296904 / 3989748 бит/с;
H.265: 3993720 / 302956 / 3999788 бит/с.
PTS, профиль и level проверены на всех участках; /tmp/call-envelope-codec-test.log.
Итоговый ./check.sh: 154/154, proxy 9/9, burst/load PASS за 128с;
/tmp/call-envelope-full-check-final.log. ChatGUI и Android assembleDebug собраны:
/tmp/call-envelope-gui-build-verified.log, /tmp/call-envelope-android-build-final.log.
Интеграция платформ и аппаратная проверка, 14.10.2026
Добавлен общий call_video_port: один TX mailbox, RX до восьми кадров/4MiB,
команды формата/bitrate с ID звонка, отменяемый post, безопасный detach ядра.
Сетевой test_call проверяет обе стороны port, владение RX, backpressure TX,
изменение диапазона и отмену ожидающего post: 2/2 PASS; /tmp/call-video-port-network.log.
ChatGUI: отдельные TX/RX workers, Qt camera, FFmpeg decoder с проверкой выхода,
очередь показа по call_audio_get_clock. Окно содержит камеру, разрешение, codec,
диапазон bitrate и видео. Сборка успешна; production decoder проверен на прежних
360 кадрах каждого codec с переходами bitrate, 5/5 QtTest cases PASS:
/tmp/call-video-desktop-integration-build.log, /tmp/call-video-decoder-test.log.
Android: JNI к общему port, Camera2/ImageReader YUV, смена MediaCodec на IDR без
перезапуска камеры при смене bitrate, RX Surface decoder по аудиочасам с drain
старого generation. CallService владеет видео и camera foreground; CallScreen
содержит управление/разрешение камеры/Surface. AssembleDebug успешен, установлено
на D2AR10P3600820 и RZ8R72MG64B. Полный видеозвонок ещё НЕ принят и не проверен.
Фактический VBV аппаратного encoder также пока не подтверждён: MediaCodec не даёт
универсального управления CPB, принятие configure не доказывает этот предел.
Аппаратный блокер подтверждён явной debug instrumentation CallVideoProbe:
adb -s SERIAL shell am instrument -w com.utun.chat/com.utun.chat.data.CallVideoProbe
Samsung RZ8R72MG64B: c2.qti.avc.encoder проходит реальный encode/decode при явном
AVCProfileConstrainedBaseline. c2.qti.hevc.encoder/OMX.qcom.video.encoder.hevc:
SPS profile=1, tier=1, level=93 при запросе Main tier (первые байты 42 01 01 21).
D2AR10P3600820: c2.unisoc.avc.encoder/OMX.sprd.h264.encoder возвращают SPS
profile=66, constraints=128, level=31, а не Constrained Baseline; явный запрос
AVCProfileConstrainedBaseline этого не меняет. HEVC Main/Main tier/level=93
имеет sps_max_dec_pic_buffering_minus1=2 (три decoded pictures), reorder=0.
Текущий Android validator требует DPB<=2; это более жёсткое требование, чем
reference=1: размер DPB нельзя автоматически приравнивать числу активных references.
Логи: /tmp/call-video-probe2-D2AR10P3600820.log,
/tmp/call-video-probe2-RZ8R72MG64B.log. Первый телефон не объявляет ни одного codec,
Samsung объявляет только AVC. Нельзя считать такое состояние готовыми видеозвонками.
Дополнительно нужно разделить probe encode/decode: сейчас Android и desktop
консервативно объявляют decoder только после успешного собственного encoder.
Это лишает совместимого приёма устройства с неподходящим передатчиком.
Приёмный decoder следует проверять независимо эталонными потоками и общими limits.
Нужно проверить нормализацию HEVC tier по стандарту: High tier для level 3.1
нельзя просто разрешить как ещё один корректный режим.
Рекомендуемая доработка: H.264 Baseline (включая Constrained Baseline), HEVC Main,
отдельные проверенные encode/decode, DPB по уровню и фактическим SPS при reorder=0.
Сохранить проверку реального потока, ограниченные очереди и передачу bitrate на IDR;
не исправлять маркировку SPS без доказательства соответствия. Другой вариант —
поставлять на Android свои software codecs, сохранив прежний строгий контракт.
Дальнейшая реализация остановлена по правилу AGENTS.md о выявленной нестыковке.
После решения нужны проверки CPB/bitrate, камеры/смены Surface/lifecycle, полный
check.sh, Kotlin unit-тесты и реальные звонки Android↔Android↔ChatGUI.
Устранение аппаратных блокеров и проверка совместимости, 14.10.2026
Реализован согласованный план: Baseline/CB как отдельные AVC profiles, общий C-анализатор
VPS/SPS/PPS/HRD/IDR, нормативный DPB, независимые encode/decode probes с эталонными
потоками. Убрано неподтверждённое обещание VBV MediaCodec. Фактические profile/level
проверяются против CAPS после подготовки encoder; смена bitrate подтверждается
только настоящим IDR. Android допускает один экземпляр encoder и восстанавливает
прежний режим при отказе. Встречные направления выбирают codec независимо.
VIDEO_DECODE_OK/REJECT передают результат decoder; Auto исключает отвергнутый codec
для текущего выбора и пробует fallback. Старые generations не влияют на текущий.
Сетевой тест проверяет acceptance, rejection, stale result и сохранение запрета
отправки после очередного tick регулятора. При смене звонка очищаются pending команды port.
Сетевой долг ограничен полусекундой целевого потока плюс базовый RTT, с минимумом
64 KiB для IDR и абсолютным пределом 2 MiB. Частичная отправка по-прежнему ограничена
2с. Обе платформы отбрасывают показ кадров, отставших от аудио более чем на 500мс.
Подтверждено на подключённых устройствах последней debug instrumentation:
- D2AR10P3600820: c2.unisoc.hevc.encoder Main/level 93/DPB=3; AVC Baseline/level 31.
Независимо работают c2.unisoc.hevc.decoder и AVC decoder (Baseline + CB).
- RZ8R72MG64B: c2.qti.avc.encoder CB/level 31. HEVC encoder отвергнут за tier=1,
level=93; независимо работает c2.qti.hevc.decoder и AVC decoder (Baseline + CB).
- На обоих телефонах последовательные открытия 4M -> 300k -> 4M прошли проверку
реального IDR/PTS; это проверка применения конфигурации, не измерение среднего CBR
на сложной сцене (синтетический серый кадр не требует целевой полосы).
- Фактический HEVC Unisoc декодирован Samsung; AVC Samsung декодирован Unisoc.
AVC Baseline Unisoc -> Samsung вернул 3/4 кадров: этот remote probe пока НЕ прошёл;
требуется диагностика задержанного выхода конечного потока. Каждый probe подаёт 4 IDR.
- Все три аппаратных потока декодированы production CallVideoDecoder ChatGUI.
Экспортные кадры синтетические, камера не включалась; /tmp/call-video-cross/.
Проверки: ./check.sh — 154/154, proxy 9/9, burst/load PASS (131с),
/tmp/call-profile-full-check.log. ChatGUI vibechat собран; codec QtTest 6/6 PASS,
/tmp/call-profile-codec-final.log. Перекрёстный production decode PASS,
/tmp/call-profile-cross-desktop.log. Android assembleDebug + 81/81 unit PASS,
/tmp/call-profile-cross-build.log; APK установлен на оба устройства.
Полная ручная приёмка звонка с реальными камерами/Surface, A/V и сменой режимов
остаётся отдельной проверкой; на рабочем Linux-хосте отсутствуют /dev/video*.
15.10.2026 — исправление desktop encoder при русской числовой локали
По логу звонка 23:58 USB-камера уже отдаёт кадры, но оба encoder probe отказали:
libx264 неверно разобрал level=3.1 и выдал level 30; libx265 отклонил level-idc=3.1.
Воспроизведено на production CallVideoEncoder: 1280x720/30, C — оба успешны,
ru_RU.UTF-8 — оба отказали. Параметры level теперь целочисленные (31 для уровня 3.1);
HEVC wire level 93 преобразуется делением на 3. Глобальная локаль приложения не меняется.
Строгая проверка фактического профиля/уровня сохранена; параметр encoder виден при video=debug.
Регрессия проверяет оба codec в C/ru_RU.UTF-8, декодирование 720p и смену 1M -> 300k -> 4M.
ChatGUI пересобран; test_call_video_encoder — 10/10 PASS в каждой локали, без skipped.
Логи: /tmp/call-camera-locale-test-c.log, /tmp/call-camera-locale-test-ru.log.
Работающий GUI автоматически не перезапускался; проверка полного звонка после исправления остаётся.
UI и периодические фризы (15.10.2026)
Android: принимаемое видео сначала занимает весь экран, своя камера — миниатюрой поверх него.
Назад возвращает к управлению звонком с двумя доступными миниатюрами (своей и собеседника).
Нажатие миниатюры открывает выбранное видео на весь экран; второе остаётся в углу и позволяет
поменять их местами. Выход в управление не выключает передачу/приём и не пересоздаёт TextureView/Surface.
Ориентация интерфейса фиксируется на время экрана звонка. Только большое видео дополнительно
поворачивается на 90 градусов, если так кадр занимает большую площадь; миниатюры не поворачиваются.
Весь кадр вписывается с исходными пропорциями, без crop и растяжения; при несовпадении пропорций остаются поля.
Выход с экрана звонка восстанавливает прежнюю политику ориентации Activity.
При видео экран остаётся включённым, proximity-overlay отключён. Если выбран EARPIECE,
переход на SPEAKER ждёт подтверждённого FAR; UNKNOWN/NEAR переключение запрещают.
Выключение видео, конец звонка и ручной выбор выхода отменяют ожидание. HEADSET не меняется,
автоследование за гарнитурой сохраняется. Отсутствующий датчик означает отсутствие proximity;
при отказе регистрации состояние остаётся UNKNOWN с предупреждением, автоматического переключения нет.
Без RX локальная камера сначала видна миниатюрой на экране аудиозвонка; без кадров
место под видео не резервируется. Касание показывает кнопки, через 5с они скрываются.
Имя/статус/диагностика не рисуются поверх видео; параметры вынесены в «Настройки звонка».
Кнопки и миниатюры учитывают вырез и systemBarsIgnoringVisibility, включая боковую панель
навигации и временно скрытые системные кнопки. Видео по-прежнему использует весь экран.
TextureView и Surface сохраняются при показе кнопок; decoder освобождает старый Surface
до его release. Запоздалый detach предыдущего Surface не отключает новый.
Preview получает прямой output той же Camera2 через общий StreamPreview/CameraPreviewView,
без Bitmap-конвертации и ограничения 5 fps. Surface удерживается до закрытия CameraCaptureSession/CameraDevice;
разворачивание/миниатюра меняют только геометрию TextureView. На SM подтверждено 29–30 fps.
Отказ preview output логируется и оставляет YUV-output передачи; encoder при attach/detach не перезапускается.
Desktop: первый RX кадр открывает отдельное изменяемое окно видео, без автоматического fullscreen.
Окно звонка с управлением и всеми настройками сохраняет свою геометрию и состояние настроек.
Видео сохраняет пропорции, своя миниатюра — в углу окна видео. F11/двойной щелчок переключают
fullscreen только для видео, Esc возвращает его обычную геометрию. Закрытие видеоокна
не завершает звонок; «Показать видео» открывает его повторно. Новые кадры сами не отменяют закрытие.
Preview берётся из того же QVideoFrame при появлении нового кадра, без прежнего ограничения 10 fps.
Только при отсутствии RX своя миниатюра
показывается в окне звонка; без кадров пустого видеоблока нет. После 5с без показанных кадров RX скрывается:
отдельного VIDEO_OFF в протоколе пока нет.
Desktop UI smoke: отдельные окна, сохранение геометрии/настроек звонка, resize видео,
F11/Esc, закрытие/повторное открытие без hangup, local-only и завершение — PASS
(/tmp/call-video-separate-preview.log). ChatGUI собран; Android assembleDebug и 81 unit-тест — PASS.
Автоматическое ожидание FAR и независимый поворот видео требуют проверки в следующем реальном звонке.
15.10.2026, диагностика низкого FPS desktop -> Samsung:
До исправления capture=30/s, encoded=20/s, paced=10/s; TX accepted=5/s, audio_busy=15/s,
audio_ahead=140–180ms. RX pulled/decoded/rendered=5/s, late=0: кадры терялись до отправки.
Вместо порога 33333мкс от прошлого кадра desktop использует бюджет 30 fps с запасом одного кадра
и минимальным интервалом 25мс. Бюджет сохраняет дробную часть при пропуске кадра; фиксированная сетка
теряла серии кадров при уходе фазы камеры. Тест охватывает 10 минут с дрожанием и квантованием PTS,
а также ограничение входных 40/60 fps. FIFO TX сохраняет кадры во время ожидания аудио.
test_call проверяет семь последовательных KEY/P кадров за audio barrier, порядок после release и предел PTS.
Диагностика: /tmp/call-fps-desktop-live.log, /tmp/call-fps-sm-live.log; unit/core/Qt проверки прошли.
Повторный звонок после FIFO: desktop offered/accepted около 30 fps, audio_busy=0;
Samsung pulled/decoded/rendered около 30 fps, late=0. Исчезновение обратного видео вызвано
тайм-аутом команды bitrate на SM: 03:07:05.899 timeout request=1, 03:07:05.992 запоздалый applied.
Автоматические IDR аппаратного кодека постоянно переносили программную границу следующего KEY.
Теперь период KEY отсчитывается от успешной подачи нашего запроса, а не от выходного IDR;
смена bitrate остаётся на границе KEY. Логи: /tmp/call-fps-sm-after.log, /tmp/call-fps-desktop-later.log.
Следующий живой звонок: RX desktop сохраняется несколько минут, около 30 fps. Остаточные 16–17 fps
desktop -> SM уже возникали в самой USB-камере: V4L2 interval=1/30, Exposure Dynamic Framerate=1.
Отключение этого флага во время звонка сразу дало capture/encode/accepted=30 fps, paced=0, audio_busy=0
(/tmp/call-video-fixed-camera-rate.log). На Linux звонок теперь отключает этот флаг после активации
QCamera и восстанавливает его после остановки; автоэкспозиция не меняется. Неподдерживаемый control
не мешает работе камеры. Лог video local preview отдельно показывает частоту и время обновления миниатюры.
Общий check.sh: 154/154, proxy 9/9, burst/load PASS; Qt codec/clock и Android 81 unit PASS.
Диагностика задержки desktop -> SM: локальный preview быстрый, но capture_age_us=-180000..-216000,
TX queue=6–8, queue_age_us=184000..248000, tail_ahead_us около 200000.
Первый запоздавший callback камеры привязывал относительный PTS к слишком позднему now;
следующие кадры оказывались в будущем и корректно ожидали audio barrier.
CallVideoCaptureClock теперь ограничивает mapping временем прихода каждого кадра и корректирует
offset только назад; поздний callback не увеличивает offset. Регрессия исходного PTS начинает новую привязку.
Тест моделирует 220мс задержки первого callback, её постепенное исчезновение, поздний callback и перезапуск PTS.
capture_age_us/clock_correction_us показывают привязку; queue_age_us использует только BOOTTIME,
tail_ahead_us сравнивает media PTS в MONOTONIC. Разность BOOTTIME и MONOTONIC не является задержкой.
Qt encoder/clock PASS; изменение требует повторного живого звонка после перезапуска ChatGUI.
Лог исходного дефекта: /tmp/call-latency-new-sample.log.
Подтверждённая причина фризов раз в 1–2с: коррекция capture timestamp меняла audio source_epoch,
а обе платформы сбрасывали video decoder и ждали KEY. Теперь source_epoch обновляет
аудиопривязку без потери video references, готовых кадров и последнего показанного PTS.
Отдельный production RX тест: 90/90 кадров, 32 смены аудиоэпохи, 0 дополнительных KEY,
0 decoder rejection (/tmp/call-video-clock-final.log).
Android больше не отбрасывает кадр из-за интервала чуть меньше 33333мкс: Camera2 уже
ограничивает FPS; повторная фильтрация превращала дрожание timestamps в падение до 15fps.
Desktop rate test: 10/10 PASS, непрерывные H.264/H.265 4M -> 300k -> 4M, оба decoder.
H.264 commit 5–7мс; H.265 51–76мс включая кодирование первого кадра, подготовка — в фоне.
Измерение /tmp/call-video-background-test.log; для H.265 стоимость кодирования остаётся существенной.
Android assembleDebug и 81 unit-тест проходят. Полный звонок с камерами после этих правок
требует ручной приёмки; автоматические проверки не подтверждают его целиком.
Аппаратная смена bitrate без перезапуска подтверждена на шумовом YUV с ограниченным контрастом:
Samsung AVC: 3.985M -> 306.8k -> 3.990M; Unisoc HEVC: 4.031M -> 291.9k -> 4.042M;
Unisoc AVC: 3.993M -> 29.3k -> 3.994M бит/с. В каждой пробе получены все 360 кадров
с точными PTS и проверенными KEY. При сильной квантизации шум пропадает; фактический
выход ниже target допустим. Проверяются верхняя граница low-rate и восстановление high-rate,
а не наличие filler до target. Полностью случайный 8-bit шум в первой пробе Samsung
превысил 4M: аппаратный CBR не является жёстким ограничителем выходного потока.
Сетевой admission/ограничитель очереди остаётся необходимым.
Логи: /tmp/call-video-dynamic-sm-detail2.log, /tmp/call-video-dynamic-sp-final.log,
/tmp/call-video-sp-rates-final.log. CallVideoProbe принимает -e codec 1/2 для отдельной пробы.
Итоговый APK установлен на оба телефона. check.sh: 154/154, proxy 9/9, burst/load PASS (119с),
/tmp/call-video-core-final-check-unsandboxed.log. Первый запуск в sandbox не мог открыть сокеты;
успешный полный прогон выполнен с разрешённым сетевым доступом.