Files
utun2/doc/call_video.txt
T

1437 lines
163 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 Baseline/Constrained Baseline: пересечение encoder отправителя и decoder получателя.
Encode/decode проверяются независимо, профили AVC согласуются явной маской.
HEVC Main с Main/High tier (High начиная с level 4), 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.
Камера, разрешение, частота до 60 fps, кодек и диапазон битрейта меняются во время звонка.
Автоматический диапазон по умолчанию 300–4000 кбит/с (десятичные единицы).
Время
Общая монотонная шкала захвата аудио и видео на каждом отправителе. PTS первого
аудиосэмпла/кадра сохраняется до вывода; время сетевой отправки учитывается отдельно.
Приёмное видео следует исходной позиции реально воспроизводимого аудио, включая
SoundTouch и очередь устройства. Алгоритм регулирования аудиобуфера сохраняется.
Показ имеет собственное абсолютное расписание с плавным темпом по суммарному запасу до 1.6x;
audio clock ограничивает опережение videoPTS-audioPTS до +40мс. Устойчивый долг при ограничении
частоты экрана снимается пропуском только показа уже декодированных кадров.
Подробности и границы измерения — в разделе «Независимое расписание показа» ниже.
Искусственная тишина/тоны не продвигают исходную позицию. Формат видео меняется по 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мс; с 15.10.2026 заменён общим playout на 50мс (см. ниже).
Подтверждено на подключённых устройствах последней 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 не мог открыть сокеты;
успешный полный прогон выполнен с разрешённым сетевым доступом.
15.10.2026, повторная диагностика задержки desktop -> Samsung после внедрения AAudio
Звонок 14610053360084326214 (Kotlin Long: -3836690713625225402), Samsung на громкой связи,
AAudio output ~23мс, очередь устройства 20мс, стабильный JB reserve=20мс. Оба направления около 30fps.
Выборка всех этапов по pts % 1000000 < 34000: примерно один кадр/сек, категории debug=debug.
Desktop: encode (включая select/convert), send, receive, decode, select, publish.
Android: receive, pull (MONOTONIC+BOOTTIME), queue, decode, release, surface, texture_update, commit.
Поля pts принадлежат отправителю; mono_us — локальному CLOCK_MONOTONIC, boot_us — CLOCK_BOOTTIME.
Сравнение разных устройств напрямую запрещено. Для анализа использовано ограничение send <= receive
в обоих направлениях, по совпадающим PTS: phone_boot-host_boot в [-796330256933, -796330252177]мкс.
Погрешность межмашинных оценок в этой выборке +/-2.378мс; локальные интервалы этой ошибки не имеют.
Первый замер ошибочно считал texture_update временем готовности текущего кадра: по исходникам Android
TextureView.applyUpdate() передаёт работу RenderThread, а callback вызывается до фактического dequeue.
Чтение SurfaceTexture.timestamp здесь часто показывает предыдущий кадр и добавляет ложные ~33мс.
MediaCodec.OnFrameRenderedListener на этом backend возвращает исходный PTS в nanoTime, что также
не является измерением локального времени показа. В логе поле переименовано в reported_us.
Контроль commit читает timestamp после registerFrameCommitCallback (API29+): кадр уже обработан
RenderThread и передан в swapchain. Callback может опоздать; физический scanout не измерен.
Эти данные не обосновывают компенсацию постоянными 40мс или замену TextureView.
51 полная выборка desktop -> Samsung: медиана encode 25.72мс, от mapped capture PTS до выхода
encoder 46.19мс, сеть 4.09мс, ожидание в RX-порту 32.05мс, decode 6.47мс,
ожидание audio clock после decode 24.19мс, release -> commit 19.50мс.
Медиана mapped PTS -> release 115.88мс, -> commit 137.90мс.
В последних 20 выборках без большого сетевого выброса: PTS -> release 100.4мс,
PTS -> commit 133.8мс; сеть 4.4мс, decode 5.7мс. Медианы отдельных этапов не складываются
в точную медиану полного пути. В общей записи были сетевые выбросы и увеличение ожидания JB;
на стабильном участке резерв вернулся к 20мс, аппаратных underrun нет.
Обратный путь, последние 20 выборок: sensor PTS -> send 124.7мс (камера и encoder вместе),
сеть 5.2мс, software decode 2.1мс, ожидание audio clock <0.1мс, select -> Qt publish 12.0мс.
От sensor PTS до publish 147.5мс; publish ещё не означает физический показ на мониторе.
Desktop capture PTS привязан к arrival-upper-bound Qt, Android PTS — к аппаратному sensor timestamp:
сравнивать эти числа как точное glass-to-glass измерение разных камер нельзя.
Тем не менее асимметрия после получения кадра ясна: быстрый desktop encoder обгоняет аудиопозицию,
Samsung удерживает кадр/следующие encoded frames до неё. Видео телефона приходит к desktop уже позже
соответствующей аудиопозиции и показывается практически сразу. Локальный desktop preview аудио не ждёт.
Ускорение только encoder освободит CPU, но при раннем приходе увеличит ожидание аудио на столько же.
Для дальнейшего снижения общей A/V-задержки нужно оптимизировать аудиотракт и отдельно проверять
планирование презентации с учётом измеренного render latency; JB20/адаптация и привязка к звуку сохранены.
Снимки: /tmp/video-timing-committed-desktop.log, /tmp/video-timing-committed-sm.log;
расчёт: /tmp/video-timing-committed-analysis.json. Исходники Android для проверки порядка callbacks:
/tmp/video-TextureView.java, /tmp/video-DeferredLayerUpdater.cpp, /tmp/video-ImageConsumer.cpp.
Сборки /tmp/video-timing-*-build.log и /tmp/video-commit-*-build.log; 103 Android unit PASS,
test_call_video_clock exit=0, test_call_video_encoder 13/13 PASS. Диагностика установлена на Samsung,
ChatGUI пересобран и перезапущен. Поведение кодеков, очередей, аудиосинхронизации не изменено.
Независимое расписание показа, 15.10.2026 (история; заменено восстановлением без пропусков ниже)
Общий C-модуль call_video_playout принадлежит RX worker, не владеет изображениями/decoder.
Последовательные сроки: D[i] = D[i-1] + (PTS[i]-PTS[i-1])*25/26.
Локальная received_us ставится на входе RX port в CLOCK_MONOTONIC до ожидания worker;
это дополнительное локальное поле, wire format не изменён. Desktop использует тот же источник
времени, Android System.nanoTime соответствует MONOTONIC. BOOTTIME из транспортных логов сюда не подмешивается.
Если received_us > D[i], срок переносится на received_us. Поздний pull, decode или poll не переносят
его на фактическое время показа. Поэтому ожидание данных постепенно съедается ускорением,
а опоздание CPU не добавляется к последующим срокам. На полностью разгруженном входе скорость
естественно ограничена поступлением кадров; дополнительных кадров и фиксированного запаса не создаём.
Свежий audio clock ограничивает фазу: ранний кадр ждёт, поздний более 50мс не показывается.
Проекция аудиометки на 1x ограничивает плановый срок, но решение SHOW всегда проверяет текущие часы;
проверка не реже 5мс, поэтому изменение темпа аудио не требует отдельного rate estimator.
При WAIT кадр не потребляется, следующая проверка не накапливает сдвиги. При длинной паузе CPU
готовый следующий кадр с уже наступившим сроком вытесняет устаревший показ; codec references не удаляются.
После невалидных аудиочасов снимается привязка расписания, но сохраняются decoder и монотонный порядок PTS.
Изменение source_epoch само по себе не сбрасывает видео. Долг таймера после outage не отрабатывается пачкой.
Обе платформы держат до двух готовых изображений для lookahead; ждать второго для показа первого не нужно.
Android продолжает подачу input во время ожидания output, суммарно до восьми кадров в decoder/готовой очереди.
Метка received_us переносится через input FIFO по PTS. EOS закрывает decoder лишь после показа/пропуска
всех выходов, включая непустой последний EOS output. Переход формата сохраняет расписание.
На desktop публикация remote image больше не зависит от UI-таймера 20мс: одно coalesced queued notification,
последний выбранный кадр и проверка поколения публикации при stop/restart. Preview/status сохраняют свой таймер.
Сводка call/debug раз в секунду: shown, drop_late/drop_schedule/drop_order, cadence_us,
av_us, dispatch_max_us, data_shift_us, audio_wait/clock_wait. Для самого большого промежутка
gap_pts_us/gap_rx_us/gap_dispatch_us различают нерегулярность исходных кадров, доступности входа и исполнения.
data_shift_us — сумма сдвигов расписания по доступности входа, не накопленная задержка сети:
даже ровный вход 30fps ограничивает 1.04x примерно на 40мс за секунду.
Задержку uasync/его backpressure эта метка не отличает от задержки доставки; это не физический socket timestamp.
Граница A/V относится к выдаче на отображение. UI/render/scanout измеряются отдельно, строгой аппаратной
гарантии в условиях остановки CPU или отсутствия новых кадров нет. Для большой сетевой паузы одновременно
сохранить все кадры, только плавно догнать и сразу соблюдать 50мс невозможно — действует drop_late.
Проверки: test_call_video_playout — абсолютные сроки при CPU+9мс, задержка данных+20мс,
пропуск backlog, верхняя граница A/V, outage/recovery, порядок PTS и 400 кадров с jitter/FPS/темпом аудио.
Комбинированный сценарий: 389 показано, 11 пропущено, все SHOW внутри A/V-границ.
Qt test_call_video_clock использует настоящий decoder: ровный вход 90/90; доставка+20мс,
RX CPU+80мс и UI+60мс — 87/90, без key requests/rejections и без публикаций после stop.
Повторный start того же call_id и 32 source_epoch за сценарий не разрывают декодирование.
test_call_video, test_call_video_rate, test_call (2/2), Qt encoder (13/13), Android unit (103) прошли.
Ядро, ChatGUI, APK собраны. Логи /tmp/video-playout-*-test.log и /tmp/video-playout-*-build.log.
Пробный живой звонок 11342339676311998023: Samsung 3719 показов за 127.374с (~29.2fps),
drop_late/drop_schedule=0, av=-43819..39982мкс. Desktop 5737 показов за 217.560с (~26.4fps),
646 drop_late, 3 drop_schedule, av=-49934..39988мкс. Окна выборок различаются, это не парный glass-to-glass замер.
На Samsung остаются отдельные промежутки 50–70мс; на desktop обратное направление теряет около 10%
кадров по строгой границе отставания. Полностью ровный живой показ пока не подтверждён.
Снимки /tmp/video-playout-live-{desktop,sm}.log. После этого замера добавлен контекст максимального
промежутка и уточнён абсолютный срок сна Android; финальная сборка установлена на Samsung и Q8.
Следующая проверка: по gap_* установить источник остаточных пауз и late drops, не расширяя A/V-границы вслепую.
Проверка финальной сборки, звонок 9002196988226353494, 15.10.2026
Снимки /tmp/video-playout-final-live-{desktop,sm}.log, сводка /tmp/video-playout-final-live-summary.json.
Последние 20 секунд: Samsung 29.94fps, ноль drop_late/drop_schedule; videoPTS-audioPTS на release
+14.664..+40.000мс. Медиана секундных максимумов интервала 43.14мс; отдельные длинные интервалы остаются.
Desktop 29.61fps, 8 drop_late за это окно, без drop_schedule; A/V -49.740..+2.763мс.
Исходный период Android-камеры около 33.308мс, но для самых длинных промежутков на desktop
медианный gap_rx=52.91мс при gap_dispatch=3.95мс. Нерегулярность видна уже на входе RX port.
На Samsung: gap_pts обычно ~36мс, gap_rx медиана 56.34мс, gap_dispatch 7.06мс;
был исходный промежуток 68мс. Поэтому все остаточные паузы нельзя приписать приёмному таймеру.
По согласованным send/receive PTS двух направлений phone_boot-host_boot ограничен
[-796330268513,-796330265026]мкс: погрешность оценки +/-1.744мс.
После завершения постановки кадра отправителем до полной сборки получателем: медианы
3.03мс desktop->SM (42 выборки) и 3.37мс SM->desktop (33). Это не включает queue_age отправителя.
На SM sensorPTS->send медиана 124.08мс, максимум 163.26мс. Для desktop mappedPTS->send
медиана 30.28мс, encode 23.11мс. Разные способы получения capture PTS не дают точного
сравнения стекло-стекло. На SM receive->decode 11.78мс, decode->release 20.68мс,
release->commit 23.29мс по имеющимся совпавшим выборкам; commit не означает физический scanout.
Выявлен отдельный механизм искусственного ожидания TX: flush выдаёт до 16 фрагментов по 1200 байт,
затем продолжение вызывается лишь новым видео, отправкой audio или таймером 50мс.
Это граница 19200 байт на один вызов, хотя канал может быть свободен. В выборке Samsung:
31 кадр <=19200 байт имел queue_age 0..0.2мс; кадры 45309/46333 байт — 1.9/39.9мс.
Снимки непустой TX-очереди показывают held=0, audio_busy=transport_busy=0, то есть ожидания
аудиопозиции в этих снимках нет. Покадрового счётчика выхода по budget/debt сейчас нет;
39.9мс нельзя целиком объявить только budget, но само ожидание внешнего события после budget
подтверждено кодом. Следующее исправление: ограниченное отменяемое продолжение отправки при
исчерпании budget и готовых данных, с сохранением проверок аудиопозиции, сетевого долга и fairness uasync.
Не увеличивать очереди/допуск A/V для маскировки этой паузы. В этом диагностическом проходе код не менялся.
Продолжение TX после исчерпания budget, 15.10.2026
После успешной порции из 16 фрагментов flush ставит один uasync_call_soon, если остался кадр,
разрешённый audio_end_pts и подтверждёнными CAPS. Новый callback выдаёт следующую ограниченную порцию.
uasync обрабатывает только исходный batch очереди immediate, поэтому самопостановка не захватывает
цикл целиком; между batch остаётся обработка сокетов/таймеров. Новый поток или периодический таймер не нужен.
Каждый фрагмент по-прежнему проверяет capabilities, deadline, media debt и место в router send_q.
Выход по backpressure не ставит новое immediate-продолжение; возобновление остаётся за штатными событиями/tick.
Другой flush (например, новый audio block) отменяет прежнее ожидающее продолжение. Callback перед работой
снимает собственный handle; stop/discard отменяют pending до освобождения v/данных. Все операции в uasync-потоке.
Без новых покадровых логов: секундный video TX flow содержит flush_yields (постановки), flush_runs
(выполненные callback), flush_wait_max_us (наибольшее ожидание продолжения, включая опережающий audio flush),
flush_net_wait (выходы по debt/send_q), flush_audio_wait (выходы по audio/CAPS-барьеру).
Выборочный stage=send дополнен budget_yields текущего кадра; queue_age_us сохраняет полный срок в TX.
Эти счётчики различают исчерпание порции, задержку исполнения и ограничение транспорта; они не являются
отдельным измерением физической сети и не должны складываться с queue_age.
test_call_video_flush использует настоящий transport, сериализацию фрагментов и uasync, управляемый router.
45001 байт отправляются тремя порциями (16/16/6), без audio/50мс tick, с проверкой каждого байта и offset.
Проверены остановка при отсутствии места/большом inflight, возобновление без дублей, audio/CAPS-барьеры,
опережающий audio flush, stop/discard с pending callback, равенство alloc/free immediate handles.
Тест прошёл; test_call_video/rate/playout, сетевой test_call (2/2), Qt encoder (13/13), Qt clock
(90/90 ровный вход, 87/90 с задержками, ноль key requests/rejections) также прошли.
Ядро, ChatGUI, APK собраны; Gradle unit-задача актуальна, 103 теста без ошибок (up-to-date).
Артефакты проверки: /tmp/video-flush-*-build.log, /tmp/video-flush-*-test.log,
/tmp/video-flush-regression.log. APK установлен на Samsung и Q8, ChatGUI перезапущен.
Непрерывные журналы пишутся; эффект на живой queue_age/равномерность ещё требует повторного звонка.
Живая проверка продолжения TX, звонок 12694689407894286842
Снимки /tmp/video-flush-live-{desktop,sm}.log; /tmp/video-flush-live-summary.json и -stages.json.
На Samsung два выборочных крупных кадра 42429/43309 байт: budget_yields=2, queue_age=600/500мкс.
В предыдущем звонке кадр 46333 байт ждал 39900мкс. Это разные кадры/звонки, но текущие выборки
и счётчики подтверждают работу немедленного продолжения. Desktop крупных кадров в выборочной трассе
не было; секундные счётчики показывают 26 постановок/26 callback за последние 20 окон.
На SM 83 постановки/78 callback: оставшиеся продолжения опережены обычными вызовами flush.
flush_net_wait=0, непустых TX-очередей в этих снимках нет. Максимальное ожидание продолжения:
desktop 201мкс, SM 5877мкс. Ожидание исполнения не считается задержкой сети.
Последние 20 отчётов playout: SM 599 показов/20.052998с=29.87fps, ноль drops,
videoPTS-audioPTS -1287..39997мкс; промежутки показа 6931..63810мкс.
Desktop 600/20.057555с=29.91fps, 3 late drops, ноль schedule drops, A/V -49290..1469мкс;
промежутки 22672..83153мкс. Полностью ровного cadence ещё нет.
Медиана gap_rx для самого длинного секундного промежутка: SM 51560.5мкс, desktop 56718.5мкс;
соответствующая медиана gap_pts 32046/33308мкс. Максимальный dispatch SM 24404мкс, desktop 9044мкс.
Парные выборки только этого call_id дают phone_boot-host_boot [-796330272225,-796330268949]мкс
(+/-1638мкс). Send->complete receive: медиана desktop->SM 3469мкс (47), SM->desktop 3948мкс (46).
В последних 20сек выборочного SM timing: receive->decode 11661мкс, queue->decode 7424.5мкс,
decode->release 4199.5мкс; release->commit 23007мкс (15 совпавших выборок).
Медианы не складываются в точный end-to-end; commit по-прежнему не является физическим scanout.
Есть краткие переходы audio clock valid=0->1. В последних 20сек именно clock-трассы 37 пар,
медиана длительности 2173мкс, максимум 6540мкс. Это не количество потерянных кадров:
poll работает и без готового изображения. При invalid общий playout сбрасывает anchored.
CallTimeline::clock имеет несколько причин отказа (в том числе nowUs<anchorReceived), но логирует
только итоговый valid. nowUs передаётся в call_audio_get_clock до захвата mutex; это потенциальная
гонка времени снимка с progress, ещё не доказанная причина текущих переходов.
Следующий шаг: агрегировать причины invalid и длительность получения снимка/возраст anchor,
с контекстом максимального gap; не ослаблять valid и не менять часы без подтверждающих измерений.
GPU-кодирование desktop-звонка, 18.10.2026
В основном пути CallVideoEncoder выбирает только hardware: NVENC, QSV, AMF, VAAPI.
Software доступен только при явно указанном backend в проверках; автоматического CPU fallback нет.
Общий LiveVideoEncoder задаёт минимальный настроенный конвейер, без B-кадров/lookahead,
проверяет профиль/level/первый IDR. QSV работает внутри в 90kHz, capture PTS восстанавливается
по точному соответствию codec PTS; порядок выходов проверяется. До восьми входов, без неограниченной очереди.
NVENC использует delay=0, QSV/VAAPI async_depth=1, AMF ultralowlatency и async_depth=1 при наличии опции.
В старом FFmpeg AMF не поддерживает forced_idr; общий путь сохраняет периодические IDR,
как в трансляции экрана. Это видно в forced_keys/forced_idr и не выдаётся за поддержку принудительного IDR.
Worker сразу отдаёт все готовые кадры. Видеопорт атомарно копирует готовую пачку до восьми кадров
с общим пределом 2MiB в один mailbox и ставит один post; ожидания накопления пачки нет.
Busy возвращается до принятия любого кадра. После отказа транспорта зависимый хвост освобождается,
запрашивается KEY. Stop/detach освобождают всю ожидающую пачку. Android передаёт count=1.
При смене bitrate новый GPU готовится в фоне; до его первого проверенного IDR продолжают выходить
старые кадры с PTS раньше границы замены. Applied выдаётся после IDR, при отказе сохраняется старый GPU.
Диагностика: debug=trace,video=trace,call=debug. call GPU probe показывает pipeline_frames,
video encoder flow — pending, bitrate committed — pipeline_frames/pipeline_pts_us/retire_us.
test_call_video_gpu проверяет шесть Windows backend/codec сочетаний на настоящих bitstream и decoder,
с подменой GPU-границы: задержка, пачка из трёх кадров, точные PTS и отказ замены; это не тест драйвера.
Сетевой test_call проверяет атомарную пачку, busy, неверную пачку и teardown.
Фактическую глубину, время GPU и задержку показа необходимо подтвердить живым звонком на Windows.
AMF HEVC PPS, исправление 18.10.2026
В Windows-логе GPU выдавал IDR, но call verifier отвергал разрешённые PPS-флаги
dependent_slice_segments_enabled_flag/output_flag_present_flag ещё до отправки. encode оставался нулевым.
Общий verifier поддерживает эти флаги: первый IDR-слайс независим, последующие segments
проверяются с учётом CTB геометрии SPS и optional dependent flag; явный pic_output_flag должен быть 1.
Обе стартовые KEY-пробы проходят тот же call_video_check_key, что и live-путь, затем FFmpeg decoder.
Несовместимый backend не попадает в capabilities; выбор продолжает перебор GPU, без CPU fallback.
test_call_video проверяет сочетания PPS flags/extra header bits, independent/dependent segments,
отказ при выходе адреса за CTB и при отключённом picture output. test_call_video_gpu меняет PPS
на настоящем HEVC IDR и проверяет полный pipeline с decoder, а также отказ несовместимой стартовой пробы.
Общий verifier используется и на Android: для HEVC обновить обе стороны звонка. Буферизация не меняется.
Аудит восстановления после коротких пауз, 19.10.2026
Новое требование: сохранять последовательность encoded кадров, быстро декодировать пришедший
burst и плавно догонять показом. Короткая пауза не должна вызывать пропуск входного P-кадра,
ожидание следующего KEY или перескок на более свежее изображение.
На момент аудита реализация этому требованию не соответствовала; сначала добавлена диагностика,
алгоритм буферизации/показа и действующие ограничения не изменены.
1. Router сохраняет порядок, при заполнении RX port приостанавливает доставку/ACK и возобновляет
её после pull. RX port имеет 8 кадров/4MiB; это предел ёмкости, не обязательный стартовый запас.
2. Desktop декодирует последовательно, держит до двух готовых изображений. Android ограничивает
decoder inputs + готовые outputs восемью кадрами, готовые outputs — двумя. Подача/декодирование
в итоге ограничиваются освобождением готовых кадров, отдельного быстрого drain всего burst нет.
3. Playout имеет номинальное расписание 1.04x. При срыве поставки срок двигается по received_us,
но позднее выполнение worker само по себе срок не переносит. DROP_LATE отбрасывает показ при
отставании от audio PTS больше 50мс; DROP_SCHEDULE пропускает изображение, если следующий
кадр тоже уже пора показать. Оба пути работают после decode, codec references сохраняются.
4. Desktop GUI хранит последнее выбранное изображение вместо FIFO. Пока queued notification
не исполнено, новые publishImage заменяют предыдущее. Это ещё один пропуск показа после
decode. Прежний ui_wait_us считался от последней замены, поэтому мог скрыть паузу GUI;
диагностика теперь отдельно считает ожидание notification, возраст последней замены и overwritten.
5. Потеря до decoder тоже возможна. TX mailbox освобождает всю пачку после попытки передачи,
включая кадр с результатом busy=0 и оставшийся хвост. Producer получает запрос KEY, а не retry.
Admission закрывается при receive_busy или достижении предела долга; transport также имеет
предел 32 кадров/500мс PTS/2MiB и deadline головы 2с. Отказ готовому encoded кадру разрывает
цепочку references; последующие P-кадры ждут KEY. Это отличается от DROP_LATE/SCHEDULE.
Контрольная симуляция настоящего call_video_playout (90 кадров, 30fps, пауза 200мс):
пауза доставки при непрерывном аудио — показано 86, late=4;
пауза CPU при непрерывном аудио — показано 84, late=5, schedule=1;
пауза CPU и удержание audio PTS с последующим догоном 1.1x — показано 77, late=12, schedule=1.
Шаг poll в этих сценариях 1мс; при шаге 15.625мс последний сценарий даёт 74 показа и 15 late.
Шаг 15.625мс — параметр модели, не утверждение о фактическом Windows timer resolution.
Даже при идеальном исполнении 1.04x убирает дополнительные 200мс примерно за 5с.
Существующий test_backlog явно требует DROP_LATE и DROP_SCHEDULE после паузы 102мс;
его успешность подтверждает старую политику, а не новое требование сохранения кадров.
Для нового поведения нужны согласованные изменения:
- encoded FIFO с переносом backpressure до producer и сохранением неотправленного хвоста;
- последовательная подача decoder без ожидания срока показа, пока хватает выходной ёмкости;
- упорядоченный показ с адаптивным ускорением по накопленной длительности, без late/schedule
пропусков при короткой паузе. Возобновление от первого сохранённого кадра, без мгновенного burst;
- сохранение выбранного кадра до фактического потребления GUI/render, вместо overwrite последним;
- временное A/V-отставание во время восстановления либо согласованная задержка аудио и видео.
Сохранить все кадры после 200мс паузы и одновременно требовать A/V <=50мс невозможно.
Предел короткой паузы, память и политика длительного переполнения должны задаваться явно.
Обычный догон не должен запрашивать KEY или сбрасывать decoder.
Windows-диагностика: AudioDiag теперь работает через тот же bounded reporter, что и Linux.
При call=debug WASAPI queue показывает padding и stream latency отдельно, callback/write —
время исполнения/интервалы; video RX worker показывает decode и опоздание пробуждения.
video late drop содержит точные PTS/AV/возраст первого позднего кадра за окно, playout —
диапазоны всех late кадров. Измерения не корректируют Windows callback-estimate аудиочасы.
Восстановление без пропусков, 19.10.2026 — действующее поведение
Encoded-кадр после принятия сохраняет ID/PTS и владельца до передачи дальше. TX port
удерживает непринятый хвост пачки и повторяет его при уведомлении writable. Новая пачка
не принимается во время обработки старой. Busy=0 не меняет references и не запрашивает KEY;
ошибка цепочки/формата/отказ decoder возвращает -1 и диагностируется отдельно.
Admission ограничивает новые raw-входы encoder, но не запрещает передачу уже готовых кадров.
Router send_q будит flush через отменяемый waiter; audio progress, CAPS ACK и timer — другие источники wake.
Mailbox: до восьми кадров, каждый <=2MiB; максимальная пачка <=16MiB без предварительного выделения.
Transport сохраняет прежние пределы 32 кадра/500мс PTS/2MiB; RX — 8 кадров/4MiB.
Это верхние границы ёмкости, заполнение не является условием начала передачи/показа.
Полный RX FIFO удерживает BUSY до разгрузки, создавая паузу production для устранения долга.
Обычный поток не проходит этот hysteresis. Длительная перегрузка/обрыв ограничены deadline головы TX 2с:
после него цепочка явно сбрасывается с причиной deadline и запросом KEY, память не растёт бесконечно.
GPU/MediaCodec отдают готовые выходы независимо от новых кадров камеры. При backpressure
подача raw останавливается до encode; уже принятые GPU-входы продолжают drain в ограниченную очередь.
При смене bitrate сначала полностью забираются выходы старого encoder, затем запускается новый KEY.
Фоновая подготовка desktop сохраняется. Некорректный первый KEY — настоящая ошибка encoder,
а не busy; выводится ошибка и отключается подача вместо бесконечного повторения неисправного режима.
Явные stop/смена режима/отсоединение Surface отменяют соответствующие старые очереди.
Decoder получает кадры строго последовательно, без искусственных интервалов 30fps между decode.
В актуальной схеме ниже три decoded места, включая ожидающий подтверждения GUI/Surface кадр;
входы decoder ограничены отдельно восемью, чтобы output lookahead не блокировал запуск GPU.
Основной backlog хранится encoded, а не большим RGB FIFO или удержанием всех аппаратных outputs.
Playout больше не содержит DROP_LATE/DROP_SCHEDULE. Invalid/order остаётся диагностируемой ошибкой.
CallTimeline публикует rate_ppm из фактического SourceSpan SoundTouch -> PCM на текущей позиции устройства.
Первоначальный вариант управлял темпом по audioPTS-framePTS. Его заменила описанная ниже схема
с подтверждённым временем показа, общей глубиной конвейера и небольшим рабочим запасом.
Видео может временно отставать от звука более 50мс, затем догоняет; опережение ограничено 40мс.
Невалидные аудиочасы приостанавливают показ, сохраняют decoder и порядок кадров.
Desktop сохраняет один queued кадр до paintEvent acknowledgement; serial защищает stop/restart.
Скрытое/свёрнутое окно принимает поток без ожидания paint, физический показ там не обещается.
Android выпускает следующий кадр только после чтения соответствующего SurfaceTexture timestamp
после RenderThread commit (API29+); на API26–28 проверка выполняется через postOnAnimation.
Проверяется Surface identity; detach/stop отменяет ожидание. Commit/paint не измеряют scanout панели.
Диагностика call=debug: playout speed_ppm/AV/cadence, RX decode/wake, GUI notification/paint wait,
Android awaitingSurfacePts/surfaceWaitMs. TX paused/resumed и RX full/drained показывают границы давления.
Windows WASAPI padding/latency остаются наблюдениями; аппаратная привязка аудио Windows не изменялась.
Для проверки реального Windows тракта нужны обновлённая сборка и новый звонок с call=debug.
Проверки: test_call_video_playout сохраняет 480/480 кадров при паузах сети/CPU 50/100/200/500мс,
аудиодогоне и недостоверных часах; отдельно моделируется poll с шагом 15.625мс (не замер Windows).
test_call_video_port проверяет частично принятую пачку, повторный busy, отсутствие KEY/spin и RX drain.
test_call_video_flush проверяет bounded burst, событие send_ready, аудиогейт и отмену callbacks.
Qt clock требует 90/90 при RX/GUI-паузах; GPU-тест проверяет каждый исходный PTS через смену bitrate
и равенство decoded + pending входам, без настоящего Windows драйвера.
Windows 1.log, повторный прогон 19.10.2026
В 19:16:31–19:16:41 decoder занимает 1.7–4.4мс, но notification ждёт до 113мс,
а подтверждение paint — до 227мс. Показ падает до 3–6fps, RX FIFO заполняется,
ожидание уже принятого кадра до decode достигает 2.66с. Аудио в этот период работает:
WASAPI padding 20мс, callback около 10мс, аудиочасы valid=1. Это локальное ограничение GUI;
длительность самого paint и причина задержки event loop в старой диагностике не различались.
После 19:16:41 notification обычно <1.8мс, paint acknowledgement <9мс, показ 29–32fps.
Остаточное опоздание RX sleep достигает 19.2мс; точность Windows timer отдельно не исправлялась.
Отсутствие кадров около 19:17:04–19:17:10 заканчивается generation=2; пользователь подтвердил один перезапуск.
Выявлена дополнительная искусственная пауза playout: gap_pts_us=2264933 при готовом lookahead
превращался в cadence_us=1424602 из-за deltaPTS/1.6. Захват мог быть приостановлен upstream
backpressure, но эта пауза проходилась второй раз на показе. Теперь интервал ограничен FPS текущего
формата, реальное опережение по-прежнему удерживается audio PTS. Encoded/decoded кадры не удаляются.
source_gaps/source_gap_max_us и первый video source gap за секунду показывают устранённый лишний интервал.
Тест queued_capture_gap моделирует готовые соседние кадры с разрывом 0.5/2.265/10с на 15/30/60fps;
следующий кадр выходит за один обычный интервал, а граница опережения аудио 40мс сохраняется.
Для установления причины стартовой задержки GUI добавлены call=debug сводки:
- video GUI loop: частота timer 20мс и максимальный разрыв event loop, первая задержка >50мс — WARN;
- video GUI delivery image_slot_max_us: время синхронных slots imageChanged, отдельно от notification wait;
- video GUI paint: ожидание UpdateRequest -> paintEvent, prepare/draw/overlay/painter.end,
размеры изображения/окна, DPR при первом замедлении, число move/resize событий.
Замена renderer или изменение Windows system timer без этих измерений не выполнялись.
Часы показа и сквозная очередь, 19.10.2026 (актуальная схема)
N = полные encoded в port + pending input worker + принятые decoder inputs без output
+ decoded FIFO + один выбранный, но ещё не подтверждённый GUI/Surface кадр.
Перемещение между стадиями не меняет N; исключение кадра происходит после подтверждения показа
или решения DROP_LATE для уже декодированного кадра (дополнение 20.10.2026 ниже).
Снимок port.rx_frames берётся под mutex после локальных перемещений RX worker. Сетевая частичная
сборка не считается полным кадром. Очереди отправителя не видны этому счётчику.
RX admission возобновляется при появлении места, без искусственного ожидания полного drain FIFO.
Уведомление port будит desktop RX немедленно; callback снимается до освобождения владельца.
Декодирование не следует FPS показа. Оно запускается при готовых данных и свободном decoded месте.
Три decoded места включают GUI/Surface pending; внутренний pipeline GPU ограничен восемью входами отдельно.
submit/receive независимы: EAGAIN сохраняет непринятый packet. Metadata PTS/frame_id/generation/received_us
переходит в выход ровно один раз. Перед новым format старый decoder завершается EOS и отдаёт все принятые входы.
Уже готовые изображения старого поколения остаются в FIFO до последовательного показа.
SHOW резервирует кадр, presented(PTS, MONOTONIC) подтверждает его потребление и продвигает часы.
Desktop ACK приходит после paintEvent; Android — после SurfaceTexture/RenderThread commit, не по OnFrameRendered.
Это подтверждения платформенного потребления, а не измерение физического scanout экрана.
Срок следующей головы фиксируется один раз: последнее ACK + период format / speed - оценка dispatch-to-ACK.
Оценка lead сглаживается и принимает обычный ACK за целый цикл экрана; выбросы больше 1.25 периода +1мс игнорируются.
Пустая очередь сохраняет привязку: готовый через 12мс следующий кадр не показывается раньше своего обычного срока.
Поздно пришедшая голова выходит сразу после готовности; разрыв capture PTS не повторяется ожиданием.
Целевой N: текущая голова + один будущий кадр + необходимый pipeline выбранного decoder.
Избыток каждого кадра добавляет 0.1x к базовому max(1x, audio rate), предел 1.6x.
Недостаток рабочего запаса временно уменьшает темп на 0.02x; обязательного старта с тремя кадрами нет.
Изменение темпа сглаживается с постоянной 200мс и применяется к следующему назначаемому сроку.
Слабая A/V-коррекция (до 0.05x) видит скрытый сетевой долг сверх рабочего запаса +100мс.
Опережение аудио по-прежнему ограничено 40мс; невалидные аудиочасы запрещают новый показ без очистки FIFO.
Windows ожидает абсолютный срок через high-resolution waitable timer + event, без spin/timeBeginPeriod.
Отказ high-resolution API явно логируется, остаётся regular timer; полный отказ handles использует condition_variable.
Stop и данные прерывают ожидание. Фактический overshoot остаётся в video RX worker wake_late_max_us.
CPU encoder: libx264 threads=4/sliced-threads=1; libx265 pools=4/wpp=1/frame-threads=1.
Это четыре вычислительных worker, не обещание ровно четырёх OS threads вместе со служебными.
GPU encoder сохраняет приоритет NVENC/QSV/AMF/VAAPI, после отказа кандидатов разрешён CPU fallback.
CPU decoder: thread_count=4, FF_THREAD_SLICE, без кадровой многопоточности.
Реальный параллелизм зависит от slices/WPP bitstream; H.264 с единственным slice не даёт четыре независимые задачи.
Desktop GPU decode: D3D11VA на Windows, VAAPI на Linux, затем CPU. Backend проверяется paced-пробой
восьми независимых KEY с точными PTS, выдачей до EOS и чтением всех восьми выходов.
Минимальная сборка FFmpeg v5 явно включает h264/hevc_d3d11va2 либо h264/hevc_vaapi.
Для Windows DLL старого профиля необходимо запустить tools/chatgui/ffmpeg/build_ffmpeg.sh и пересобрать GUI.
Android предпочитает аппаратные MediaCodec, проверяет pipeline при paced input и включает заявленный FEATURE_LowLatency.
Числом внутренних CPU threads MediaCodec управляет платформа; параметры FFmpeg к нему не применяются.
Обычная пауза/EAGAIN не теряет кадры и не запрашивает KEY. Реальный отказ decoder/device логируется
с числом потерянных аппаратных inputs и сохранённых ready кадров; после GPU fault используется CPU и новый KEY.
Ещё не принятый KEY при отказе открытия GPU повторяется на CPU без потери. Отказ самого устройства
может уничтожить уже принятые им references/inputs; гарантии короткого backpressure к такому отказу не относятся.
Watchdog 2с проверяет output progress только без готового/ожидающего показа и при достаточном числе decoder inputs.
Диагностика call=debug: playout queued/reserve/awaiting/lead/speed/cadence, RX submitted/presented/encoded/inflight/total;
video decoder pipeline показывает backend, threads/active_thread_type, submit/decode/GPU transfer/RGB conversion.
Тесты: 16 сценариев 480/480 с паузами 50–500мс, фиксированный deadline после starvation, снижение 1.596x -> 1.004x;
отложенные outputs и EOS между поколениями, CPU fallback, настоящие H.264/H.265 bitstream и rate switches.
Linux Qt RX+GUI: 90/90 без KEY/reject, normal ACK p50=33.4мс/p95=37.7мс, stop=2.1мс;
с RX80мс/GUI60мс: 90/90, p95=39.1мс, максимальная пауза94.4мс, stop=5.0мс.
Эти числа относятся к локальному тесту с имитацией paint ACK, не Windows драйверу или панели.
Собраны Linux GUI и Android APK; Android unit: 108/108. Call transport, RX port и bounded flush проходят.
В заключительном общем CTest три call-video набора и stream_publish прошли; stream_view один раз
зафиксировал рост silenceGaps 2 -> 4 в realH264RenderingAndClose. Повтор того же бинарника без изменений
прошёл (15/15); stream_view/его audio clock этим изменением не редактировались. Эту временную
нестабильность не скрываем изменением допусков теста. Живой Windows GPU/timer/scanout пока не проверен.
Android: компоновка звонка и поворот передачи, 19.10.2026
Ник находится рядом со стрелкой. В обычном режиме миниатюры занимают место аватара;
TextureView локального и удалённого видео сохраняются при разворачивании. Их положение
берётся из измеренного слота; заполнение экрана не добавляет поворот на 90 градусов.
Панель камеры/кодека/диапазона битрейта/разрешения использует общие CaptureControls со стримом.
Она появляется при любом нажатии кнопки видео, в том числе во время проверки кодеков,
при отказе запуска и отказе в разрешении камеры; сохраняется после выключения передачи.
Первый настоящий TX-кадр также открывает панель. Недоступные опции заблокированы, причина
отказа показана под строкой; доступные настройки можно менять до повторной попытки.
Открытое меню приостанавливает автоскрытие. Показ панели сохраняется при пересоздании экрана звонка.
CallVideoEngine владеет OrientationEventListener вместе с foreground-сервисом; сворачивание
экрана не прекращает отслеживание. Углы датчика направлены противоположно display rotation:
поворот пикселей = sensor + (front ? -deviceAngle : deviceAngle), нормализованный к 0/90/180/270.
Первый достоверный угол принимается сразу; следующие — с гистерезисом 60 градусов и выдержкой
250мс. Неопределённое положение сохраняет последний угол. Экран и локальное превью не следуют
датчику: геометрия превью использует SENSOR_ORIENTATION и фиксированный display rotation.
ImageReader сохраняет размер режима камеры. CallVideoCodec поворачивает Y/U/V непосредственно
при копировании в flexible input MediaCodec, с учётом crop/rowStride/pixelStride и доступных
границ. В потоке rotation=0: пиксели уже ориентированы. lib/video_plane.c собирается с -O2
также в debug APK; без оптимизации на Q8 портретный 720p вход занимал около 48мс вместо 27мс.
Это замер синтетического YUV -> encoder, не гарантия FPS живого звонка.
Поворот на 180 градусов при прежних выходных размерах сохраняет codec/generation и запрашивает
IDR. Перестановка ширины/высоты меняет поколение: raw входы приостановлены, старый encoder
полностью drained, готовые выходы сохранены до принятия портом, затем ожидается tx_idle.
Только после этого публикуется новый формат; подача ждёт configured_generation. tx_idle
означает свободный mailbox независимо от can_encode; уже принятый transport FIFO сохраняет порядок.
Камера/preview при одном повороте не перезапускаются. Откат настроек получает новое поколение,
PTS и frame_id продолжаются; неверный режим диагностируется, при полном отказе останавливается TX.
CAPS сохраняют формат wire. Общий call_video_limit_orientations расширяет обе осевые границы
только после реальной landscape+portrait пробы; предел pixels_per_second остаётся прежним.
Android encoder проверяет обе ориентации каждого предлагаемого размера; desktop encoder —
1280x720 и 720x1280 перед объявлением возможностей. Независимые Android/desktop decoder probes используют
настоящие 1280x720 и 720x1280 KEY; портретные fixtures воспроизводит tools/generate_call_video_portrait.py.
Проверки: test_video_plane (четыре поворота, stride 1/2, interleaved U/V, смещения и точные границы),
test_call_video (портретные CAPS/KEY), test_call_video_port (tx_idle отдельно от can_encode),
CallVideoOrientationTest (направление углов, гистерезис, выдержка, flat/отмена).
Аппаратный debug probe: adb -s SERIAL shell am instrument -w -e rotations 1
com.utun.chat/com.utun.chat.data.CallVideoProbe. Он проверяет цветные квадранты Y/U/V после
настоящего encode/decode, все PTS, четыре поворота и 180 градусов внутри одного encoder.
Результат — INSTRUMENTATION_CODE: 0; подробности и wait/copy/queue timings — tag CallVideoProbe.
В живом звонке call=debug выводит submitMaxUs/inputWaitMaxUs/copyMaxUs/queueMaxUs раз в секунду.
Согласование возможностей и показ опций после отказа проверены сборками Android/core/chatgui.
Android unit: 111/111; шесть native video наборов: 6/6; Qt encoder/clock CTest: 2/2.
Qt bothOrientations кодирует и декодирует H.264/H.265 при переходах 1280x720 -> 720x1280 -> 1280x720,
проверяя поколения и PTS. Аппаратный probe на SM и Q8 проходит с INSTRUMENTATION_CODE: 0;
дополнительно выбирает реальные проверенные режимы в обеих ориентациях через общий C CAPS/JNI.
Это локальные codec-проверки, живой межплатформенный звонок новой пары сборок ещё не проверен.
Windows 2.log, неравномерность показа, 19.10.2026
На 136 стабильных секундных интервалах shown/interval даёт 30.09fps; drop_order=0,
обычный общий запас — 2-3 кадра. В живом потоке выбран D3D11VA, а threads=1 относится
к аппаратному backend. Это не однопоточный программный decoder.
Сравнение устойчивых участков 21:40:51-21:41:37 (480x640) и 21:41:55-21:42:31 (720x1280):
медиана секундных decode_max_us растёт 4450 -> 10111, convert_max_us — 2229 -> 6744,
dispatch_max_us — 3079 -> 7311. При этом wake_late_max_us — 1076 -> 1045,
GUI paint_max_us — 3505 -> 2947, notification_wait_max_us — 434 -> 422.
Это медианы максимумов окон, а не средние длительности отдельных кадров.
RX worker выполняет decoder.submit/receive (включая GPU transfer/RGB/rotation) в том же
потоке, что и playout.poll/publishImage. Готовая голова может пропустить назначенный срок
во время подготовки следующего кадра. Для прямой проверки добавлена секундная диагностика:
decode_deadline_misses — число операций decoder, начавшихся до срока готовой головы и
завершившихся после него; decode_deadline_max_us — наибольшее такое опоздание;
decode_deadline_stage=input/output, work_us и margin_us описывают именно эту операцию.
Ожидание аудио, GUI ACK и отсутствие назначенной готовой головы исключены из счётчика.
Фильтр — call=debug; алгоритм, лимиты очередей и порядок кадров не менялись.
Есть отдельная граница достоверности: desktop ACK отправляется в конце paintEvent,
до подтверждённой презентации окна. По этим метрикам нельзя измерить scanout или признать
доказанной потерю кадров композитором. Текущая формула срока следующего кадра использует
предыдущий ACK плюс шаг минус сглаженный dispatch lead; индивидуальные задержки рисования
тем самым влияют на расписание. Для устранения зависимости нужны независимое исполнение
decoder/планировщика и синхронизация presentation с обновлением дисплея; обычный таймер
и ранний ACK из QPainter не дают такой гарантии.
Независимый desktop-планировщик, 19.10.2026 — реализовано
TX по-прежнему владеет encoder. RX worker теперь единолично владеет decoder, всеми
submit/receive/EOS/open/close, GPU transfer, RGB и rotation. Отдельный display worker
владеет call_video_playout и не вызывает decoder, port_status/pull, Qt post/invokeMethod,
не ждёт GUI и не форматирует/записывает логи.
Передача RX -> display: SPSC кольцо из трёх неизменяемых Picture. Producer заполняет слот
до release-публикации; consumer освобождает его только после ACK выбранного кадра.
Голова во время GUI ACK остаётся в кольце и входит в его ёмкость. При полном кольце
останавливается подача decoder; до восьми входов разрешают проверенный hardware pipeline.
Старый QImage в слоте заменяет decoder, освобождение его буфера не попадает в display.
Смена поколения сначала выводит принятые decoder-входы; старые готовые изображения
сохраняют собственные PTS/формат и показываются до новых.
GUI читает один опубликованный слот по QWinEventNotifier (Windows) или QSocketNotifier
(POSIX pipe). Таймер записывает metadata и serial, затем сигналит событие; аллокации
queued Qt lambda в нём больше нет. Только GUI копирует shared QImage и вызывает imageChanged.
ACK имеет глобально возрастающий serial; дубликаты и ACK предыдущего start не освобождают
слот новой сессии. stop запрещает публикации, будит и присоединяет display/RX/TX независимо
от GUI ACK, затем закрывает порт. Notifier удаляется до закрытия native handles.
Общий запас: cumulative port_received - completed - decoder_discarded; переносы между
encoded/decoder/ready/GUI не меняют счётчик. port_received читается атомарно, без mutex
порта, который может быть занят копированием нового encoded кадра. Decoder-discard
меняется только при настоящем отказе цепочки/codec; короткая пауза ничего не отбрасывает.
call_audio_try_clock использует try_lock и на call_audio, и на jitter mutex. BUSY вызывает
повтор через 500мкс без сброса назначенной головы; экстраполяции устаревшего audio PTS нет.
У display/RX отдельные wake: Windows event + high-resolution timer, POSIX nonblocking pipe
и pselect. notify не берёт пользовательский mutex. Runtime ошибки wake передаются RX
для вывода и остановки workers. Логи playout копируются в bounded SPSC очередь; форматирует
их RX. Переполнение диагностической очереди считается и явно логируется, кадры не вытесняются.
Фаза опирается на запланированную презентацию и корректируется по ACK на error/8,
не более 1мс за кадр. Настоящая пауза более половины периода привязывает фазу к новому ACK;
минимальный плановый интервал ограничивает мгновенный burst темпом 1.6x. Большие разрывы
capture PTS не проигрываются повторно. Это не VSync: ACK текущего окна всё ещё paintEvent,
а фактическая презентация Windows-композитором требует отдельного render backend.
Проверки: C playout — 480/480 при сетевых/CPU паузах 50/100/200/500мс, возврат темпа,
смена render latency 3->9мс без накопленного сдвига (phase=11625мкс).
Qt clock — три сценария 90/90, смена поколения на 45-м кадре, RX80/GUI60мс, audio BUSY,
decoder output+12мс, stop без ACK, запоздалый ACK после start, 100000 конкурентных публикаций.
В последнем Linux-прогоне p95 показа: обычный 34.7мс, с медленным decoder 35.2мс;
максимумы работы display в секундных окнах 26-143мкс. Это локальный замер, не Windows scanout.
Qt encoder/GPU/clock/jitter: 4/4 PASS, core и vibechat собраны.
Полный check.sh вне sandbox: 163/164; известный test_encrypted_attachment снова phase=5,
heartbeats=12001 (описан в doc/tasks.md). Лог: /tmp/video-independent-check-full.log.
Full HD и режимы камеры, 19.10.2026
Предел интерфейса звонка — 1920x1080 либо 1080x1920, 30fps. Начальный режим и диапазон
300k..4M не повышаются автоматически. Общие wire CAPS уже допускают эти геометрии.
Для Full HD@30 согласуются AVC level_idc=40 и HEVC general_level_idc=120, Main tier;
нижние размеры по-прежнему получают минимальный допустимый уровень через call_video_choose.
Desktop camera worker опрашивает QCameraDevice::videoFormats и использует те же
stream_video_add_camera_modes/stream_video_camera_supports, что publisher трансляций.
В звонок попадают только чётные аппаратные размеры до Full HD при 30/29.97fps.
Дубликаты pixel format объединяются; список передаётся в GUI вместе с id/именем камеры
и обновляется по videoInputsChanged. Драйвер опрашивается только в отдельном процессе.
GUI сохраняет разрешение при смене камеры, если оно поддерживается; иначе выбирает 640x480
либо минимальный доступный размер. Отсутствие режимов не превращается в запрос размера -1.
При старте требуется точное совпадение аппаратной геометрии; другой размер готового
QImage диагностируется как ошибка вместо скрытого масштабирования.
Shared mapping FRAME/ACK расширен до 1920*1080*4 байт; обе стороны проверяют геометрию.
Worker копирует RGB32 построчно с учётом stride, GUI получает собственный QImage.
Владение mapping, поколения и ограничение единственного кадра в IPC сохранены.
Android выбирает 640x480/1280x720/1920x1080 из YUV_420_888 камеры, учитывая min frame
duration и диапазон AE с upper=30. Каждая геометрия кодера проверяется в двух ориентациях;
для разных размеров могут использоваться разные аппаратные/программные реализации.
Decoder также хранит реализацию и измеренный pipeline отдельно для 720p и Full HD.
Это сохраняет работающий GPU на 720p, если Full HD проверен только через CPU.
CAPS содержат реальные проверенные encode/decode пределы; выбранный режим дополнительно
проверяется открытием codec и bitstream, как требует контракт независимых верхних границ.
Общие KEY fixtures дополнены 1920x1080 и 1080x1920, AVC Baseline/CB и HEVC Main.
call_video_probe_key принимает width/height; генератор tools/generate_call_video_portrait.py
--fullhd явно задаёт high-tier=0. Нельзя заменять Full HD-пробу одной лишь проверкой 720p.
Desktop capability probes отменяются при stop, включая ожидание другого probe;
незавершённый результат не кэшируется. Codec/драйверные вызовы выполняются вне GUI/playout.
Проверки: native bitstream/геометрия/профили/DPB — PASS; Qt encoder/GPU/clock/camera/audio
5/5 PASS, включая настоящие Full HD encode/decode в обеих ориентациях, поколения,
границу shared buffer и stop во время проб. Общие camera modes publisher — 3/3 PASS.
Ядро, все desktop targets и Android APK собраны; Android unit 111/111 PASS.
Логи: /tmp/fullhd-native-video-test.log, /tmp/fullhd-qt-verified-tests.log,
/tmp/fullhd-shared-camera-modes.log, /tmp/fullhd-android-final-build.log,
/tmp/fullhd-desktop-final-build.log. Полный check.sh — 163/164, прежний отказ
test_encrypted_attachment phase=5/heartbeats=12001: /tmp/fullhd-core-check.log.
Это codec/IPC-проверки; живой Full HD звонок новой сборки на Windows/Android ещё не проверен.
Samsung HEVC: сравнение звонка и трансляции, 19.10.2026
Свежий звонок на SM_A525F/RZ8R72MG64B: c2.qti.hevc.encoder и его OMX alias
на 640x480/1280x720/1920x1080 возвращают profile=1, tier=1 вместо запрошенного
Main tier. Отказ происходит в hevc_ptl общего C validator, до публикации encoder CAPS.
Приёмный c2.qti.hevc.decoder независимо прошёл реальные пробы до Full HD.
Лог: /tmp/android-sm-hevc-recall.log, 00:05:08.731..00:05:09.307 времени телефона.
В Android publisher трансляции используется Surface, задаются profile/bitrate/fps,
но не KEY_LEVEL, KEY_BITRATE_MODE или KEY_LATENCY. StreamVideoCodec проверяет
envelope/NAL-набор, затем viewer передаёт VPS/SPS/PPS настоящему decoder. Строгого
Main-tier verifier звонка на этом пути нет. Это объясняет доступность HEVC в
трансляции; отдельной нормализации Qualcomm SPS/VPS в трансляции нет.
Добавлен debug-only CallVideoHevcProbe: отдельный app_process без Camera2, сетевого
ядра, установки APK или перезапуска живого приложения. Он сравнивает 54 варианта:
640x480/480x640/1280x720/720x1280/1920x1080/1080x1920, YUV/Surface,
явный/автоматический level, CBR/режим по умолчанию, плюс точные настройки publisher.
Каждый вариант кодирует восемь кадров с проверкой порядка PTS, сохраняет исходный
KEY и Annex B, отдельно вызывает общий validator и аппаратный decoder.
Отказ validator не отменяет эксперимент decode. Параметр parameters вместо матрицы
опрашивает supportedVendorParameters и типы параметров (API 31+).
Запуск после assembleDebug: скопировать APK и все lib/arm64-v8a/*.so из этого APK
в отдельный /data/local/tmp/utun-hevc-probe на телефоне; сам APK сделать chmod 444.
Пример команды adb shell (OUTPUT — отдельный каталог результатов):
CLASSPATH=/data/local/tmp/utun-hevc-probe/probe.apk app_process \
-Djava.library.path=/data/local/tmp/utun-hevc-probe / \
com.utun.chat.data.CallVideoHevcProbe OUTPUT [parameters]
Зависимость libonnxruntime.so необходима для загрузки JNI validator, хотя сама проба
ONNX не использует. Инструмент требует Android API 29+ и аппаратные HEVC encode/decode.
Результат Samsung: 54/54 encode и hardware decode, по восемь кадров, без потерь PTS;
общий validator отвергает все 54 потока. Независимый FFmpeg decode с -xerror также
вернул восемь кадров для всех 54 файлов без ошибок. trace_headers подтверждает:
всегда tier=1 и reorder=0; 16 явных режимов имеют level=93, восемь Full HD — 120,
все 30 режимов без явного KEY_LEVEL имеют level=30, включая Full HD.
Следовательно, снятие KEY_LEVEL, CBR или переход на Surface не исправляют HEVC
звонка. Автоматический level дополнительно не соответствует большой геометрии.
Опрос vendor-параметров обнаружил только bitrate-mode, preprocess-rotate,
timestamp-source-avtimer и raw.blurinfo; переключателя tier среди них нет.
Это короткая синтетическая проверка совместимости, не проверка bitrate/VBV на камере.
Артефакты: /tmp/hevc-probe-sm/ (54 KEY, 54 Annex B, results.log),
/tmp/hevc-probe-sm-ffmpeg.log, /tmp/hevc-probe-sm-headers.log,
/tmp/hevc-probe-sm-parameters.log. Сборки ядра и assembleDebug прошли:
/tmp/hevc-probe-core-build.log, /tmp/hevc-probe-android-build.log.
Рабочий контракт звонка пока сохранён: Main tier; HEVC TX Samsung не включён.
Предлагаемый следующий этап исправления: явный HEVC level 4 для Qualcomm,
поддержка High tier с отдельным согласованием encode/decode возможностей и
настоящими High-tier decode fixtures на обеих платформах, сохранением общих
ограничений bitrate/геометрии/DPB/reorder. Простое снятие запрета tier оставляет
непроверенный приём и ошибочные low-level заголовки. Модификация VPS/SPS не выполнялась.
HEVC High tier для Samsung, 19.10.2026
Реализовано явное согласование двух режимов HEVC Main: бит 1 — Main tier,
бит 2 — High tier. Это значения call_video profiles/format.profile, не HEVC Main 10.
High tier принимается только с level_idc>=120 (level 4), Main tier предпочтителен,
когда у обеих сторон есть оба режима. AVC Baseline/CB и размеры wire структур сохранены.
Бюджеты bitrate/HRD/CPB остаются консервативными Main-tier, максимум target — 4M.
Нормативное ограничение tier/level: ITU-T H.265, Annex A, general_tier_flag:
https://www.itu.int/rec/dologin_pub.asp?id=T-REC-H.265-201304-S%21%21PDF-E&lang=e&type=items
Общий KEY validator различает tier и проверяет согласованный режим по VPS/SPS.
High-tier decoder fixtures получают декларацию High tier/level 4 из известных
Main-tier fixtures: их coding tools, DPB и BR/CPB укладываются в более высокий уровень.
Меняются только PTL эталонных проб; производственные VPS/SPS не переписываются.
Android и desktop проверяют декодирование обоих режимов отдельно, в обеих ориентациях,
на 720p и Full HD; отказ одного режима не снимает уже проверенную поддержку другого.
Desktop TX продолжает объявлять только реально проверенный Main tier.
Android сначала пробует прежний level, затем HEVC level 4; найденные profile/tier/level
сохраняются для конкретного размера. Live выбирает именно режим проверенного encoder.
Запрос platform Main tier одинаков в probe и live: Samsung при MainTierLevel4 выдаёт
High tier, а при HighTierLevel4 в эксперименте выдал Main tier. Поэтому нельзя строить
Android KEY_LEVEL из полученного wire profile/tier и считать его обратным отображением.
prepare проверяет реальный поток, а production-проба требует точного совпадения с mode.
CallVideoHevcProbe OUTPUT call проверяет настоящий CallVideoCodec: probe/prepare,
восемь кадров, точные PTS, in-place 1M -> 4M -> 300k -> 4M и decode каждой фазы.
На SM_A525F прошли все 24 фазы: 640x480/1280x720/1920x1080 и обратные ориентации,
c2.qti.hevc.encoder, Main/High tier, level 4. Это синтетический roundtrip, не замер
среднего bitrate на сложной сцене. Все 24 настоящих KEY дополнительно прошли
production CallVideoDecoder на desktop (четыре CPU slice threads) и c2.unisoc.hevc.decoder
второго Android; режим receive берёт *.key из OUTPUT и проверяет восемь PTS на файл.
Артефакты: /tmp/hevc-high-sm-production/, /tmp/hevc-high-desktop-cross.log,
/tmp/hevc-high-q8-receive.log. Живой звонок камеры новой сборки ещё не проверен.
Проверки: native call_video — PASS; Android assembleDebug и unit — PASS;
desktop собран целиком; encoder/GPU/camera — 3/3 PASS, clock — PASS.
Camera IPC проверен вне sandbox, внутри него QLocalServer::listen запрещён.
Полный check.sh: 162/164; прежний test_encrypted_attachment и нестабильный
test_local_transport (отдельный повтор PASS). Логи: /tmp/hevc-high-core-check.log,
/tmp/hevc-high-local-transport-retry.log, /tmp/hevc-high-native-test.log,
/tmp/hevc-high-android-final-build.log, /tmp/hevc-high-qt-final-tests.log.
Для согласования High tier необходимо обновить обе стороны звонка. Установленные
приложения в ходе проверки не заменялись: app_process выполнялся отдельно.
До 60 fps и восстановление presentation, 20.10.2026
Причина «нет подходящих режимов» в Windows 1.log: камера объявила фиксированные
640x360@330, 1280x720@100, 1920x1080@50, а прежний фильтр требовал диапазон с ровно 30 fps.
Теперь FPS пользовательского режима означает частоту передачи. Capture остаётся аппаратным;
общий со стримами отбор разрешает более быстрый источник. На desktop в IPC и combo передаются
пары размер/FPS; выбирается наименее быстрый подходящий аппаратный формат без изменения его частоты.
До RGB-конвертации/IPC/кодирования работает фазовый отбор raw кадров с допуском четверти периода.
Фаза не сбрасывается на обычном рассогласовании частот (например 60 -> 50), только при отставании
более одного периода. Долгая пауза не создаёт немедленный догоняющий burst исходных кадров.
Android пересекает пары размер/FPS камеры с проверенными encoder modes для 30/50/60 fps.
Учитываются YUV minFrameDuration и AE ranges; выбирается минимальный верхний AE предел >= FPS.
Preview использует вторую поверхность той же сессии. Изменение размера/FPS перезапускает capture;
поворот и bitrate продолжают прежний жизненный цикл. Недоступная пара отклоняется с notice/логом.
Частота передачи не понижается регулятором задержки. Backpressure отбирает только ещё не закодированный raw.
Encoder/decoder проходят отдельные реальные пробы 30/50/60 fps в обеих ориентациях.
Для AVC 720p50/60 нужен level 3.2, Full HD50/60 — 4.2; для HEVC — 4 и 4.1 соответственно.
Android probe/live используют проверенный level, Main-tier platform request и фактический wire tier.
FPS и PPS в CAPS агрегируются по настоящим пробам независимо: 1080p30 +720p60 не объявляют 1080p60.
GPU decoder допускается только по подтверждённому размеру/FPS/профилю, CPU сохраняет четыре slice threads.
Эталонные KEY 60 fps: call_video_fast_fixtures.h, генератор --fast. High tier не понижает level 4.1 до 4.
Android подготовка отменяется при завершении звонка; незавершённые режимы не публикуются.
Все encoded кадры последовательно подаются в decoder; готовая очередь вместе с ожидающим ACK <=3.
Сначала общий playout пытается сократить долг ускоренным показом. При входе 60 fps и экране 60 Hz
ускориться физически нельзя. Если лаг сверх рабочего запаса не сокращается за 500мс хотя бы на
четверть периода, DROP_LATE освобождает только decoded presentation до возврата к запасу.
Рабочая граница — (reserve+1)*период +50мс, где reserve включает обязательный pipeline decoder.
Сохраняются минимум голова/будущие кадры. DROP_LATE не очищает decoder, не запрашивает KEY и не меняет
last_show/last_deadline: часы продвигает только ACK. На desktop решение и освобождение SPSC-слота
выполняются отдельным timer без codec, GUI, блокирующих mutex или синхронного форматирования логов.
Текущая desktop версия пропускает показ после GPU readback/RGB; перенос этих операций из RX сюда не выполнялся.
Диагностика call=debug: camera capability/selected capture_fps/output_fps, camera flow frames/paced,
decoder probe size/fps/backend, video playout drop_display; INFO video display catchup объясняет
lag/recovered/window/queued/reserve. Статус desktop и Android меню показывают выбранный FPS.
Проверки: Android APK и 114 unit — PASS; desktop build, Qt encoder/GPU/clock/camera/stream modes — 5/5 PASS.
Native playout: малые/большие паузы с возможностью ускорения сохраняют все кадры; на модели 60Hz после
пауз 50/100/500мс декодированы все 600, пропущен только показ 1/4/29 кадров, лаг возвращается к ~67мс.
Настоящий desktop RX: 180/180 decoded при GUI freeze 500мс, 149 показано, KEY/reject=0; timer не вызывает codec/port mutex.
Samsung: 72 production encode/decode фазы (три размера x30/50/60 xдве ориентации xчетыре bitrate), PASS.
Все 72 настоящих KEY прошли production desktop decoder с точными FPS/PTS. Unisoc проверил 24 собственные
encode/decode фазы 640x480/480x640 при 30/50/60; более высокие TX режимы не объявлены.
Тот же Unisoc аппаратно принял все 72 Samsung KEY, включая Full HD60/High tier/level 4.1,
по восемь кадров с точными PTS; /tmp/video60-q8-receive-results.log, remote finished files=72.
Полный check.sh: 163/164, только прежний test_encrypted_attachment phase=5/heartbeats=12001;
артефакт /tmp/utun_encrypted_attachment_25Uyql. test_local_transport на этот раз PASS.
Логи: /tmp/video60-full-check.log, /tmp/video60-playout-test.log, /tmp/video60-qt-tests.log,
/tmp/video60-android-final-build.log, /tmp/video60-desktop-cross.log; артефакты Samsung /tmp/video60-sm-results/.
Живой звонок новой камеры на Windows не проверен. Установленные приложения не заменялись;
аппаратные проверки выполнялись отдельным app_process. Обе стороны нужно обновить для новых режимов.
Android Surface ACK: устранение фризов 1–2 fps на Samsung, 20.10.2026
Свежий logcat /tmp/video-freeze-sm.log, звонок 8563820959416855278, 01:31:23–01:32:39:
77 playout сводок, медиана 1.99 показанных fps, медиана dispatch-to-ACK 538мс, максимум 1019мс.
Очередь ready=2, awaiting=1; decode останавливается за ожидающим Surface ACK. Смена размера/FPS
не устраняет паузы. Это конкретное ожидание подтверждения GUI, не доказательство медленного decoder.
ViewRootImpl в performDraw захватывает список frame commit callbacks ДО draw TextureView.
Прежний onSurfaceTextureUpdated вызывался уже во время draw и регистрировал callback слишком поздно.
Он оставался до следующего постороннего UI draw (статистика каждые 500мс). RX ждал ACK,
не отправлял следующий кадр на Surface, а без нового кадра TextureView сам не создавал следующий draw.
Отсюда цикл ожидания и накопление задержки; пропуск decoded presentation не мог устранить причину.
CallVideoTexture теперь регистрирует frame commit в OnPreDrawListener, до захвата списка ViewRoot.
Timestamp SurfaceTexture читается после commit того же кадра и сверяется с единственным ожидающим PTS.
Посторонний draw со старым timestamp не подтверждает новый кадр: настоящий TextureView update сам
назначит следующий draw/commit. На API<29 сохраняется postOnAnimation с ожиданием правильного timestamp.
Listener снимается при detach; старые callback проверяют закрытие и идентичность Surface.
Нет постоянного invalidate-цикла, дополнительного draw после каждого кадра и ACK по таймауту.
Encoded порядок, три decoded места, playout/rate/protocol не менялись.
Диагностика call=debug: video surface updates/commits/updateToCommitMaxUs/pts и
video playback acknowledged/surfaceAckMaxUs/surfaceWaitMs разделяют texture update, commit и RX ACK.
Документация API: https://developer.android.com/reference/android/view/ViewTreeObserver#registerFrameCommitCallback(java.lang.Runnable)
VideoSurfaceProbeActivity в отдельном audio-probe APK проверяет настоящий TextureView/EGL:
один EGL-кадр до ACK, точные presentation timestamps, посторонний UI draw раз в 500мс.
Команда: adb shell am start -n com.utun.audioprobe/.VideoSurfaceProbeActivity --ei fps 60 --ez legacy false
legacy=true воспроизводит прежнюю регистрацию в onSurfaceTextureUpdated.
На Samsung старая схема: 15 ACK /8.028с, максимальное ожидание 516414мкс — дефект воспроизведён.
Исправленная схема 60fps: 483 ACK /8.050с, максимум 21391мкс; все ACK соответствуют ожидающим PTS.
30fps: 241 ACK /8.033с, максимум 31071мкс. Три посторонних draw увидели старый timestamp
и правильно дождались настоящего обновления; это не потеря кадра и не неверный ACK.
Тест проверяет FPS и число подтверждений/входов, а не отсутствие законных промежуточных UI draw.
Лог: /tmp/video-surface-sm-probe.log. Проба второго телефона не получила Surface при Dozing;
её остановили, устройство не разблокировали, результатом проверки показа это не считается.
C-сборка, Android assembleDebug и 114 unit — PASS. Полный check.sh: 162/164, прежние сбои:
local_transport peers():95 после TTL age_ms=30335 (отдельный повтор PASS), encrypted_attachment
phase=5/heartbeats=12001, артефакт /tmp/utun_encrypted_attachment_3XZGxj.
Логи: /tmp/video-surface-freeze-core-build.log, /tmp/video-surface-freeze-android-final.log,
/tmp/video-surface-freeze-full-check.log, /tmp/video-surface-freeze-local-retry.log.
Основное приложение на телефонах в этой проверке не заменялось; готовый исправленный APK —
tools/chatgui-android/app/build/outputs/apk/debug/app-debug.apk. Сетевой звонок с новым APK ещё не проверен.