Files
utun2/doc/call_audio_batch.txt
T

151 lines
14 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.
Адаптивная упаковка Opus в звонке
================================
Сервис CALL_AUDIO использует одну Router-кодограмму для 1..10 Opus-кадров.
Кодек и длительность кадра (20мс) не меняются. Новый формат должен быть
на обеих сторонах; старый одиночный CALL_MEDIA не поддерживается.
Wire, packed, порядок байтов хоста как у остального CALL:
Общий header, 27 байт:
subcmd:u8, call_id:u64, track_id:u8, capture_epoch:u64,
codec_delay_samples:u32, sent_ms:u32, count:u8.
Далее count раз:
seq:u16, capture_pts_us:i64, length:u16, opus[length].
Накладные расходы: 27 + 12*N байт CALL на N кадров, плюс один Router.
count=1..10, весь CALL_MEDIA <=1024 байт. Пачка может быть меньше target из-за
ограничения размера или смены capture epoch/codec delay. PTS/seq каждого
кадра сохраняются; PTS может быть INT64_MIN. Парсер проверяет всю кодограмму
до выдачи первого кадра, отвергает хвостовые байты и незавершённые записи.
Headless callback по-прежнему получает отдельные Opus-кадры.
Пример для Opus по 37 байт, прямой Router с SIGN|ENCRYPT (+163 байта):
N кадров CALL payload Router wire байт Router на кадр
1 76 239 239
2 125 288 144
3 174 337 112,33
5 272 435 87
10 517 680 68
Обрамление ETCP/BLE и ACK не включены. Normalizer может объединять/разбивать
кодограммы. Старый расход: 236 байт Router на каждый такой Opus-кадр;
историческое разложение — call_audio_packet_overhead.txt.
Источник обратной связи
-----------------------
etcp_router_tx_stats(group_id, peer_node_id, ETCP_RT_ID_CALL_AUDIO).
Это конкретный логический аудиоканал, не общая сумма очередей ядра.
Интерфейс уже используется видеотреком звонка; stream_publisher использует
другой, общий снимок ETCP и для данного регулятора не подходит.
Router сохраняет исходное монотонное enqueued_us в send_q и переносит его
в inflight. Ретрансмит не меняет эту метку. При первом подтверждении:
sample = ACK time - enqueued_us;
delivery_rtt_us = EWMA sample с весом 1/8 (первый sample без сглаживания).
Снимок также отдаёт queued_age_us/inflight_age_us головы от исходного enqueue
и last_ack_us. Возраст пустой очереди равен нулю. При reset история очищается.
Прежние rtt_us/baseline_rtt_us и алгоритм видеорегулятора не меняются.
Полный delivery_rtt_us включает исходную очередь Router, нижние очереди,
путь до приёмника и возврат ACK. Этот показатель сам по себе не доказывает перегрузку. Это не время
доставки в динамик и не измерение односторонней задержки.
Регулятор
---------
Снимок каждые50мс в uasync; управление целиком в call_session.
D = max(delivery_rtt_us, queued_age_us, inflight_age_us).
Собственное намеренное накопление пачки в D не добавляется.
Начальное target=4. При смене local/peer Router epoch или исчезновении канала
история сбрасывается. Для изменения target нужно пригодное окно статистики.
До получения ACK сохраняется стартовое число кадров; при no_route,
receive_busy, minRTT probe или несвежих ACK target не растёт.
При queued_age_us>100мс или inflight_age_us>delivery_rtt_us+100мс непрерывно200мс
target увеличивается на1 до10. Между изменениями clamp(delivery_rtt_us,250мс,750мс)
и хотя бы одно новое подтверждение. Постоянный RTT сам по себе не увеличивает пачку.
При пустом Router send_q и inflight_age_us<=delivery_rtt_us+40мс в течение5с
target уменьшается на1; тоже требуется новое подтверждение после изменения.
Промежуточные значения сбрасывают отсчёты high/low, сохраняя target.
Если ACK не продвигался дольше max(2с, 2*delivery_rtt_us, 500мс), рост
выключается; это состояние не считается свободной полосой.
Отправка по готовности
----------------------
Требуются оба условия: в локальной FIFO не менее target кадров И Router готов
передать следующую кодограмму в ETCP. Таймера неполной пачки нет: даже при
свободном транспорте target-1 кадров остаются в FIFO. Захват продолжается.
При росте target уже после регистрации готовности callback повторно проверяет
число кадров. При снижении target очередной 50мс tick сразу проверяет отправку.
etcp_router_request_send регистрирует отменяемый одноразовый запрос на канал.
Повторная регистрация того же запроса не сбрасывает ожидание. Payload ещё не
создан, поэтому 64 маленьких неизменяемых пакета в send_q не накапливаются.
Router запускает handshake даже без готового payload, затем ждёт маршрут,
отсутствие RECEIVE_BUSY, свободное окно inflight и готовность send_input_q ETCP.
Запрос использует существующий round-robin; ретрансмит имеет прежний приоритет.
Перед callback Router снимает регистрацию. Сервис формирует одну пачку до
целевого числа кадров, не более1024 байт и без смешивания capture epoch/delay.
Лимит байт/граница capture могут разделить накопленные target кадров на меньшую
кодограмму: причина явно пишется как bytes/capture-change. Срок ожидания и
джиттер сами по себе никогда не разрешают отправку до накопления target.
В callback conn_send только копирует и принимает кодограмму; рекурсивная
отправка запрещена внутренним send_producing. Лишь после успешного принятия
сервис удаляет исходные кадры. При отказе они остаются в FIFO для повтора.
После возврата Router кодирует и передаёт пакет в нижний слой. При ошибке
дальнейшей отправки принятая кодограмма сохраняется в Router. Переданный пакет
не изменяется; подпись, шифрование, seq и надёжная доставка сохраняются.
Для следующей пачки можно снова запросить слот, не ожидая ACK предыдущей:
число пакетов в полёте по-прежнему ограничивает окно Router.
Закрытие/reset автоматически снимает запрос и обнуляет handle; callback может
закрыть/перезапустить канал. Смена маршрута переносит ожидание на новую очередь.
Сервис отменяет запрос до освобождения контекста. Кадры при no_route/BUSY
остаются в прежней защитной FIFO750, overflow удаляет старейший с логом.
Если полезная нагрузка устойчиво превышает полосу, упаковка не гарантирует
ограниченную задержку: потребуется отдельное управление битрейтом.
sent_ms ставится перед подачей пачки Router. Приёмник передаёт его всем кадрам,
но обновляет оценку сетевого jitter только по первому. Capture PTS остаётся
отдельной временной шкалой каждого кадра; учитываются реальные сетевые паузы,
а собственное накопление пачки не создаёт N фиктивных измерений jitter.
После полной проверки кадры идут в прежние decoder/jitter/headless callbacks.
Архитектура и владение описаны рядом с кодом: src/call/call_playout_policy.h,
контракт непрерывного/очередного вывода — src/call/call_audio.h.
Начальные200мс — защищённый нижний запас, сохраняемый после запуска. Порог
старта/восстановления: запас + max(длительность пачки, запрос PCM). Период пачки
учитывается один раз: CallDelay сравнивает с запасом нижнюю огибающую, не пик FIFO.
Запас растёт сразу по оценке доставки, снижается на20мс в секунду после5с
устойчивого воспроизведения и минимум20 пачек. При underrun/смене устройства
уверенность сбрасывается, запас увеличивается на20мс и составляет не менее200мс.
Оценка доставки получает20мс запаса на границу кадра. Окно нижней огибающей
CallDelay покрывает период пачки и вариацию доставки. При недостатке запаса
SoundTouch временно замедляет речь до0.98x без изменения высоты голоса;
одного изменения целевой глубины недостаточно для реального пополнения FIFO.
Устройство отдельно готовит PCM под размер callback; desktop начинает потреблять
речь только после публикации не менее40мс PCM (или полного callback, если больше).
Непрерывный Android AudioTrack получает начальную тишину с учётом аппаратной шкалы.
Захват/передача не ждут готовности вывода. Начальное ожидание не является underrun.
Это политика качества, не ограничение полной сквозной задержки величиной200мс.
cleanup/ENDED/destroy отменяют регулятор50мс и Router-запрос до освобождения FIFO.
Доступ к ядру только в uasync; audio capture worker API не меняется.
Диагностика
-----------
call=info: старт mode=full-and-router-ready и target; изменение target с причиной.
call=debug: раз в секунду локальные queued/oldest/wait и Router queued/inflight,
возраст enqueue->ACK, submit->ACK, ack_bytes_500ms, число кадров и пачек.
wait=frames/handshake/no-route/peer-busy/window/transport/unregistered показывает
причину ожидания. TX каждой пачки: frames/target/bytes/oldest_us/seq/reason/remaining.
etcp_route=debug: выдача/отмена слота с waited_us, подтверждение enqueue->ACK и
число попыток. Ошибки формата, выделения, регистрации и отправки логируются.
Проверки: test_call_audio_batch (формат, очереди/RTT/свежесть ACK), test_call (накопление
полного target на свободном транспорте, задержка при backpressure, доставка и
teardown), test_etcp_router_faults (handshake без payload, BUSY, окно, смена пути,
отмена ожидающего/отложенного запроса, reset и закрытие внутри callback).
Аппаратный эффект проверяется отдельным звонком и сравнением логов Windows/Android.