diff --git a/doc/call_video.txt b/doc/call_video.txt index 891f1dd1..0549b869 100644 --- a/doc/call_video.txt +++ b/doc/call_video.txt @@ -36,11 +36,11 @@ call_id, video_generation и request_id; одно исполнение и пос Порядок реализации [+] История первых ACK 500мс/50мс и снимок payload send_q/inflight отдельного сервиса. [+] Unit счётчиков: повторные ACK, reset, отдельные сервисы; UDP/STCP регрессии пройдены. -[!] Проверка переноса аудиопозиции выявила дрейф feed/tempo; см. результаты ниже. +[+] Дрейф feed/tempo устранён картой фактических участков SoundTouch; см. ниже. [ ] H.265 runtime bitrate: поддерживаемый backend без циклического пересоздания. [+] Общий регулятор с детерминированными сценариями (пока не подключён к звонку). [ ] Протокол возможностей/формата, видеофрагменты, сборка, A/V scheduler. -[ ] Общие capture PTS и отображение позиции аудиоустройства в исходное время. +[+] Общие capture PTS и отображение позиции аудиоустройства в исходное время. [ ] ChatGUI: движок, окно и настройки; Android: движок, JNI, сервис и экран. [ ] Интеграционные проверки всех пар, перегрузки, смены формата и lifecycle. @@ -65,7 +65,7 @@ sequence=20ms, seek=10ms, overlap=4ms — параметры существую frames/tempo, записанные при feed, не описывают фактический выход. Поэтому копировать этот способ из stream_playback_audio в звонки нельзя. Изменений звука/буфера нет. -Варианты следующего шага: +Рассмотренные варианты (реализован вариант 2): 1. Минимальный: шкала по фактически потреблённому входу и произведённому выходу, с поправкой на seek-window. Тест показал <15мс на трёх сценариях, но это ещё не доказанная граница для речи/Opus/разных блоков. Нужны эти проверки и явный допуск. @@ -74,8 +74,32 @@ frames/tempo, записанные при feed, не описывают факт вычисления по косвенным счётчикам; потребует небольшого расширения 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 — успешно. diff --git a/tools/chatgui/src/voice_audio_io.cpp b/tools/chatgui/src/voice_audio_io.cpp index d882e691..b362b751 100644 --- a/tools/chatgui/src/voice_audio_io.cpp +++ b/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() { uint64_t epoch = m_playEpoch.load(); uint64_t position = 0; + int64_t lastClockReport = 0; call_clock_snapshot clock{}; 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); @@ -178,6 +179,17 @@ void VoiceAudioIo::playbackLoop() { unsigned lost = m_progressDropped.exchange(0); if (lost) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: playback progress overflow session=%llu dropped=%u", (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) { Playback block{}; block.epoch = epoch; block.position = position; if (m_kind == Kind::Call) {