151 lines
14 KiB
Plaintext
151 lines
14 KiB
Plaintext
Адаптивная упаковка 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.
|