Browse Source

Document and log the call source clock contract

master
evgeny 6 days ago
parent
commit
610773fa90
  1. 34
      doc/call_video.txt
  2. 12
      tools/chatgui/src/voice_audio_io.cpp

34
doc/call_video.txt

@ -36,11 +36,11 @@ call_id, video_generation и request_id; одно исполнение и пос
Порядок реализации Порядок реализации
[+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса. [+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса.
[+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены. [+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены.
[!] Проверка переноса аудиопозиции выявила дрейф feed/tempo; см. результаты ниже. [+] Дрейф feed/tempo устранён картой фактических участков SoundTouch; см. ниже.
[ ] H.265 runtime bitrate: поддерживаемый backend без циклического пересоздания. [ ] H.265 runtime bitrate: поддерживаемый backend без циклического пересоздания.
[+] Общий регулятор с детерминированными сценариями (пока не подключён к звонку). [+] Общий регулятор с детерминированными сценариями (пока не подключён к звонку).
[ ] Протокол возможностей/формата, видеофрагменты, сборка, A/V scheduler. [ ] Протокол возможностей/формата, видеофрагменты, сборка, A/V scheduler.
[ ] Общие capture PTS и отображение позиции аудиоустройства в исходное время. [+] Общие capture PTS и отображение позиции аудиоустройства в исходное время.
[ ] ChatGUI: движок, окно и настройки; Android: движок, JNI, сервис и экран. [ ] ChatGUI: движок, окно и настройки; Android: движок, JNI, сервис и экран.
[ ] Интеграционные проверки всех пар, перегрузки, смены формата и lifecycle. [ ] Интеграционные проверки всех пар, перегрузки, смены формата и lifecycle.
@ -65,7 +65,7 @@ sequence=20ms, seek=10ms, overlap=4ms — параметры существую
frames/tempo, записанные при feed, не описывают фактический выход. Поэтому копировать frames/tempo, записанные при feed, не описывают фактический выход. Поэтому копировать
этот способ из stream_playback_audio в звонки нельзя. Изменений звука/буфера нет. этот способ из stream_playback_audio в звонки нельзя. Изменений звука/буфера нет.
Варианты следующего шага: Рассмотренные варианты (реализован вариант 2):
1. Минимальный: шкала по фактически потреблённому входу и произведённому выходу, 1. Минимальный: шкала по фактически потреблённому входу и произведённому выходу,
с поправкой на seek-window. Тест показал <15мс на трёх сценариях, но это ещё не с поправкой на seek-window. Тест показал <15мс на трёх сценариях, но это ещё не
доказанная граница для речи/Opus/разных блоков. Нужны эти проверки и явный допуск. доказанная граница для речи/Opus/разных блоков. Нужны эти проверки и явный допуск.
@ -74,8 +74,32 @@ frames/tempo, записанные при feed, не описывают факт
вычисления по косвенным счётчикам; потребует небольшого расширения bundled API. вычисления по косвенным счётчикам; потребует небольшого расширения bundled API.
Overlap смешивает два участка, поэтому даже здесь нужно определить правило PTS. Overlap смешивает два участка, поэтому даже здесь нужно определить правило PTS.
Рекомендуется для общего A/V clock: не меняет звук, исключает накопительный дрейф. Рекомендуется для общего 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 — успешно. ./build.sh --no-gui -j4 — успешно.

12
tools/chatgui/src/voice_audio_io.cpp

@ -151,6 +151,7 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool
void VoiceAudioIo::playbackLoop() { void VoiceAudioIo::playbackLoop() {
uint64_t epoch = m_playEpoch.load(); uint64_t epoch = m_playEpoch.load();
uint64_t position = 0; uint64_t position = 0;
int64_t lastClockReport = 0;
call_clock_snapshot clock{}; call_clock_snapshot clock{};
if (m_kind == Kind::Call && call_audio_get_clock(m_session, voice_time_us(), &clock) != 0) if (m_kind == Kind::Call && call_audio_get_clock(m_session, voice_time_us(), &clock) != 0)
DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: call clock unavailable session=%llu", (unsigned long long)m_session); DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: call clock unavailable session=%llu", (unsigned long long)m_session);
@ -178,6 +179,17 @@ void VoiceAudioIo::playbackLoop() {
unsigned lost = m_progressDropped.exchange(0); unsigned lost = m_progressDropped.exchange(0);
if (lost) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: playback progress overflow session=%llu dropped=%u", if (lost) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: playback progress overflow session=%llu dropped=%u",
(unsigned long long)m_session, lost); (unsigned long long)m_session, lost);
int64_t reportTime = voice_time_us();
if (m_kind == Kind::Call && reportTime - lastClockReport >= 1000000) {
lastClockReport = reportTime;
call_clock_snapshot snapshot{};
if (call_audio_get_clock(m_session, reportTime, &snapshot) == 0)
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "audio clock session=%llu epoch=%llu source=%llu played=%llu written=%llu "
"pts=%lld valid=%d measured=%d held=%uus", (unsigned long long)m_session,
(unsigned long long)snapshot.epoch, (unsigned long long)snapshot.source_epoch,
(unsigned long long)snapshot.played_samples, (unsigned long long)snapshot.written_samples,
(long long)snapshot.pts_us, snapshot.valid, snapshot.measured, snapshot.held_back_us);
}
if (m_playback.size() < 2) { if (m_playback.size() < 2) {
Playback block{}; block.epoch = epoch; block.position = position; Playback block{}; block.epoch = epoch; block.position = position;
if (m_kind == Kind::Call) { if (m_kind == Kind::Call) {

Loading…
Cancel
Save