Сквозная диаграмма меток времени: от захвата до рендеринга

Автор: Николай СапуновОбновлено: август 202618 мин чтения
Содержание статьи +

Коротко

Между микрофоном и динамиком аудио и видео проходят через шесть–семь разных временных доменов, и синхронизация губ (lip-sync) сохраняется только в том случае, если каждый из них передаёт следующему корректную метку времени. Эта статья описывает весь конвейер целиком – от захвата до рендеринга: кодер, мультиплексор, сеть, демультиплексор, декодер, рендерер – и подписывает каждый временной домен, каждую метку времени, а также указывает точные места, где возникает дрейф и где он исправляется. Вы узнаете, что измеряют эти метки (такты дискретизации, а не время суток), почему в вещательном мире используются программные часы с частотой 27 МГц, а в WebRTC – сетевые часы NTP, и как приёмник восстанавливает единую шкалу времени из потоков, стартовавших с произвольных значений. Сопроводительный постер в PDF умещает всю диаграмму на одной странице – её можно повесить над столом.

Почему это важно

Если вы работаете с видеопродуктами – конференциями, стримингом, OTT, видеонаблюдением, телемедициной – вопрос «почему звук рассинхронизирован?» рано или поздно окажется на вашем столе, и честный ответ почти никогда не сводится к одному багу. Проблема – в часах где-то в цепочке из семи компонентов, которые передали неверную или отсутствующую метку времени. Эта статья – карта. Продакт-менеджер прочитает её, чтобы понять, о чём спорят инженеры; инженер – чтобы найти, на каком именно этапе теряется синхронизация. Весь смысл Блока 5 в том, что нигде в интернете нет полной такой диаграммы – поэтому мы её нарисовали.

Рисунок 1. Полный конвейер от захвата до рендеринга. Каждый блок находится в своём домене часов; каждая стрелка, пересекающая границу домена, – это место, где может нарушиться синхронизация.

Главная идея: метка времени – это счётчик тактов, а не время суток

Прежде чем диаграмма обретёт смысл, зафиксируйте одну мысль. Метка времени медиа не сообщает, который сейчас час. Она показывает, сколько тактов прошло с момента запуска часов. Точка отсчёта – часто случайное число, выбранное при старте потока. Часы идут с фиксированной частотой – например, 48 000 тактов в секунду для аудио Opus или 90 000 тактов в секунду для видео.

Поэтому, когда пакет сообщает, что его метка времени равна 1 440 000, это не означает 14:44 пополудни. Для аудио-часов с частотой 48 кГц это значит: «с начала потока прошло 1 440 000 отсчётов дискретизации», то есть ровно 30 секунд звука (1 440 000 ÷ 48 000 = 30). Это число не имеет смысла, пока вы не знаете двух вещей: частоты часов и того, какому реальному моменту времени соответствует ноль потока.

Этот второй факт – привязка счётчика тактов к реальному времени – и есть суть синхронизации. Аудио и видео – это два независимых счётчика тактов с разными частотами и случайными начальными моментами. Lip-sync – это процесс приведения обоих счётчиков к одной общей временной шкале, чтобы сэмпл и кадр, возникшие одновременно, воспроизводились одновременно.

Семь этапов и часы в каждом

Пройдём по конвейеру слева направо. На каждом этапе задайте один вопрос: какие часы здесь главные и какую метку времени они фиксируют?

Этап 1 – Захват: появляются часы дискретизации

Микрофон выдаёт непрерывное напряжение. Аналого-цифровой преобразователь (ADC) измеряет это напряжение с фиксированной частотой – для работы с видео стандарт составляет 48 000 раз в секунду. Эта частота, называемая частотой дискретизации, задаётся крошечным кварцевым генератором в оборудовании захвата. Это первые часы в цепочке, и они – главный хронометр для аудио: каждый последующий этап наследует своё представление о «прошедшем аудиовремени» из количества сэмплов, выданных этим преобразователем.

У камеры есть свои часы захвата, отсчитывающие кадры – 30 или 60 в секунду. Уже на первом этапе у вас два независимых временных источника: часы дискретизации аудио и часы кадров видео. Это не один и тот же кварц, и идеально синхронизированными они не останутся. Запомните это – здесь и кроется причина всего дрейфа.

Этап 2 – Кодер: такты превращаются в временные метки

Кодер (Opus, AAC, H.264, AV1) берёт сырые сэмплы или кадры и сжимает их в пакеты, добавляя к каждому пакету метку времени – счётчик тактов на медиа-часах. Для аудио медиа-часы обычно соответствуют частоте дискретизации (48 кГц для Opus). В случае видео по давней традиции используется частота 90 000 Гц, независимо от частоты кадров, поскольку 90 000 нацело делится на любые распространённые значения: 90 000 ÷ 30 = 3000 тактов на кадр, ÷ 25 = 3600, ÷ 24 = 3750.

Здесь же для видео возникает тонкое усложнение: порядок декодирования пакетов не всегда совпадает с порядком их отображения. Современное видео использует двунаправленные кадры (B-кадры), которые зависят от будущих кадров, поэтому кодер добавляет к каждому видео-пакету две временные метки – метку времени декодирования (DTS, «декодируй меня сейчас») и метку времени отображения (PTS, «покажи меня в этот момент»). У аудио такой перестановки нет, поэтому аудио-пакеты содержат только метку отображения. Полная история PTS и DTS живёт в отдельной статье; здесь просто обратите внимание, что обе метки генерируются кодером.

Этап 3 – Мультиплексор или пакетизатор: на первый план выходят транспортные часы

Теперь потоки нужно объединить для транспортировки, и здесь вещательный и реал-тайм-миры расходятся в совершенно разные направления.

В мире вещания и файловых форматов (MPEG-TS, MP4) мультиплексор объединяет аудио- и видеопакеты в единый контейнер и привязывает к каждому из них временные метки – PTS и DTS – к общей временной шкале, называемой системными часами времени (STC). В формате MPEG-TS эти часы работают на частоте 27 МГц, и мультиплексор периодически вставляет в поток их значение – опорные программные часы (PCR), чтобы приёмник мог точно восстановить ту же временную шкалу. STC на 27 МГц организованы так: 33-битный счётчик с частотой 90 кГц (та же, что и у PTS/DTs) плюс 9-битное расширение, фиксирующее остаток от деления на 300, поскольку 27 000 000 ÷ 90 000 = 300. Все элементы программы синхронизированы по одной общей временной шкале.

В реальном времени (WebRTC) мультиплексор не использует общие часы. Аудио и видео передаются как два независимых RTP-потока – у каждого своя метка времени и случайный старт. Пакетизатор просто оборачивает каждый медиа-пакет в RTP-заголовок и отправляет. Общая шкала времени устанавливается отдельно – протоколом управления, к которому мы подойдём на этапе 5.

Этап 4 – Сеть: этап вообще без часов

У сети нет часов. У неё есть задержка, и она непостоянна: один пакет приходит за 20 миллисекунд, следующий – за 45, третий – за 18. Эту вариацию времени прибытия называют джиттером, и из-за него конвейер не может просто воспроизводить пакеты по мере их поступления. Кроме того, сеть может менять порядок пакетов и полностью терять часть из них.

Важно: сеть не изменяет метки времени. Метка, с которой пакет покидает мультиплексор, остаётся неизменной при поступлении в демультиплексор. В этом и заключается гениальность меток времени: зафиксировав временные метки в источнике и считав их на приёмной стороне, можно сделать ненадёжную промежуточную часть сети неважной для синхронизации. Сеть может произвольно задерживать и переупорядочивать пакеты – метки времени всё равно точно указывают, что и когда произошло.

Этап 5 – Демультиплексор и буфер джиттера: восстановить тактовую частоту и поглотить джиттер

Приёмник теперь должен устранить последствия сбоя в сети и восстановить временные метки отправителя. Происходит два действия – это первая точка коррекции в конвейере.

Во-первых, восстановление часов. В вещании приёмник подаёт входящие образцы PCR в систему фазовой автоподстройки (PLL) – схему, подкручивающую локальный генератор 27 МГц, пока он не совпадёт с генератором отправителя. PCR приходит примерно каждые 40 миллисекунд или чаще, и стандарт разрешает ему дрожать не более чем на ±500 наносекунд джиттера (ISO/IEC 13818-1). PLL усредняет это дрожание и выдаёт гладкую, верную копию часов отправителя. В WebRTC PCR нет; вместо этого приёмник ждёт RTCP-отчёт отправителя (sender report, SR) – маленький управляющий пакет, несущий согласованную пару: одно показание RTP-метки времени потока рядом с одним показанием стенных часов отправителя в формате NTP (RFC 3550, §6.4.1). Эта пара – якорь. Одна пара на поток позволяет приёмнику перевести любую RTP-метку в стенное время, и как только и аудио, и видео выражены в одних стенных часах, у них общая шкала. Это и есть восстановленный lip-sync.

Во-вторых, буфер джиттера. Восстановленные временные метки указывают приёмнику, когда каждый пакет должен быть воспроизведён, но сами пакеты приходят с неравномерными интервалами. Буфер джиттера – это небольшая очередь-накопитель, своего рода зал ожидания в аэропорту для пакетов. Он намеренно задерживает воспроизведение на небольшой запас времени (обычно 30–200 мс), чтобы опоздавшие пакеты успели прийти до момента своего показа. Благодаря буферу задержка становится более плавной, а воспроизведение – упорядоченным и своевременным. В WebRTC такой буфер реализован как NetEQ, и он достаточно «умён», чтобы слегка растягивать или сжимать аудио, не допуская опустошения или переполнения буфера.

Этап 6 – Декодер: временные метки проходят дальше без изменений

Декодер преобразует сжатые пакеты обратно в исходные сэмплы и кадры. Он учитывает DTS (декодируй это сейчас) и сохраняет PTS (этот сэмпл относится к определённому моменту), чтобы рендерер знал, куда поместить результат. Декодер не изменяет и не выдумывает временные метки – он передаёт PTS без изменений. Если видеопакет был декодирован не в порядке отображения из-за B-кадров, декодер переставляет выходные данные в правильный порядок на основе PTS. Контракт тайминга, заданный кодером, наконец реализуется здесь.

Этап 7 – Рендерер: часы воспроизведения и вторая точка коррекции

Рендерер – это цифрово-аналоговый преобразователь звуковой карты и устройство обновления экрана. У него есть свои тактовые часы – часы дискретизации воспроизведения, и вот в чём заключается главная проблема, вызывающая дрейф в длительных звонках: часы рендерера – это отдельный кварц, отличный от кварца на стороне захвата. Если сторона захвата работает на частоте 48 000,5 Гц, а сторона воспроизведения – на 47 999,5 Гц, то разница в одну часть на миллион приводит к тому, что рендерер постепенно отстаёт или опережает входящий аудиосигнал. За часовой звонок даже небольшое рассогласование в 50 частей на миллион накапливается примерно до 180 миллисекунд – что уже значительно превышает порог, при котором синхронизация губ (lip-sync) становится заметно нарушенной.

Поэтому рендерер – вторая точка коррекции. Хорошо построенный рендерер непрерывно отслеживает уровень заполнения буфера и применяет минимальную пересэмплирование – добавляя или убирая доли сэмпла то здесь, то там – чтобы синхронизировать часы воспроизведения с восстановленными медиа-часами. Коррекция настолько мала, что остаётся неслышимой. Плохо построенный рендерер пропускает этот шаг и вместо этого пропускает или дублирует целые кадры, когда буфер опустошается или переполняется, и слушатель воспринимает это как щелчок или заикание. Разница между продуктом, звучащим профессионально, и тем, что «заикается», почти полностью определяется тем, как последний этап обрабатывает рассогласование часов.

Два мира бок о бок: программные часы 27 МГц против стенных часов NTP

Самое полезное, что стоит усвоить, – это то, что существует две разные философии синхронизации, и от того, к какой из них вы относитесь, зависит, где находится якорь.

АспектВещание / файл (MPEG-TS, MP4)Реал-тайм (WebRTC, RTP)
Главные часыОдни системные часы 27 МГц (STC)Общих нет; каждый поток независим
Якорь в потокеPCR (образец STC 27 МГц)RTCP Sender Report (пара NTP + RTP)
Восстановление часовPLL по PCRЛинейное отображение по одной паре SR
Метки аудио/видеоPTS/DTS на 90 кГц, обе от STCРаздельные RTP-метки, раздельные частоты
Частота якоряPCR каждые ≤40 мсSR раз в несколько секунд
Lip-sync устанавливаетсяОбщие STC делают PTS сравнимыми напрямуюПереводом обоих RTP-часов в стенное время NTP
Спецификация джиттераДжиттер PCR ≤ ±500 нс (ISO/IEC 13818-1)Спец. нет; джиттер гасит буфер

Читайте таблицу по одной фразе на строку. В вещании одни часы правят всем, поэтому две метки времени от этих часов можно сравнить простым вычитанием. В WebRTC два независимых источника времени нужно каждый привязывать к нейтральному третьему эталону – стенным часам NTP – прежде чем их вообще можно будет сравнить. Оба подхода решают одну и ту же задачу; они просто размещают общий эталон в разных местах.

Разбор примера: сколько накапливается дрейфа?

Дрейф – это режим отказа, который часто недооценивают, поэтому подставим реальные цифры. Допустим, аудиочасы устройства захвата и аудиочасы устройства воспроизведения отличаются на 50 частей на миллион – вполне реалистичная величина для двух потребительских устройств с обычными кварцевыми генераторами.

Шаг первый – что такое 50 ppm как доля? 50 частей на миллион = 50 ÷ 1 000 000 = 0,00005.

Шаг второй – сколько времени накапливается или теряется за секунду? 0,00005 × 1 секунда = 0,00005 секунды = 0,05 миллисекунды в секунду.

Шаг третий – накопим за час: 0,05 мс/с × 3600 с = 180 миллисекунд.

Теперь сравним 180 мс с допуском lip-sync, который реально навязывает человеческий глаз. Стандарт ITU-R BT.1359-1 устанавливает порог приемлемости: аудио может опережать видео на 90 мс или отставать на 185 мс – при превышении этого значения синхронизация считается неприемлемой. Таким образом, нескомпенсированное рассогласование в 50 ppm к концу часа доведёт синхронизацию до границы «неприемлемо». Вот почему непрерывный ресэмплинг рендерера – не просто удобная опция, а то, что стоит между вашим продуктом и потоком обращений в службу поддержки.

Частые ошибки, ломающие синхронизацию

За большинством проблем с синхронизацией данных стоит всего несколько типичных ошибок, и каждая из них – это часы, которым присвоили неверную временную метку.

Ошибка «PTS – это стенное время». Новички в медиа полагают, что метка показа – это время суток, которое можно сопоставить с now(). Это не так: это счётчик тактов на часах, чей ноль выбран произвольно. Сравнивать PTS с системным временем без предварительной привязки потока к якорю бессмысленно.

Отсутствующий или устаревший якорь. В WebRTC, если первый RTCP-отчёт отправителя потерян, а приёмник не ожидает следующего, оба потока воспроизводятся гладко, но никогда не синхронизируются по времени – они постоянно, молча рассинхронизированы. Решение – не начинать рендеринг второго потока, пока хотя бы один отчёт отправителя не привяжет оба к общей временной шкале. RFC 6051 и расширение заголовка abs-capture-time в WebRTC решают более медленную версию этой проблемы, передавая отображение времени внутри потока.

Перегенерирующий промежуточный узел. Реле или SFU, который переписывает метки времени на основе своих часов, а не передаёт их без изменений, нарушает тайминг источника. Корректно построенный SFU передаёт RTP- и RTCP-метки без изменений – он работает как труба, а не как редактор.

Проигнорированное рассогласование часов. Рендерер, работающий на собственной частоте и никогда не ресэмплирующий относительно восстановленных медиа-часов, будет дрейфовать ровно так, как показывает приведённая выше арифметика. Симптом проявляется так: синхронизация, нормальная в начале звонка, но заметно нарушенная через двадцать минут, – всегда свидетельствует о дрейфе, а не о разовом сбое.

Где здесь Фора Софт

Мы реализуем синхронизацию аудио и видео в платформах видеоконференций, OTT- и интернет-ТВ-сервисах, системах онлайн-обучения, телемедицинских приложениях и решениях видеонаблюдения с 2005 года. Конвейер меток времени, показанный на диаграмме, – это тот слой, который мы проверяем, когда клиент сообщает: «звук уехал». Причина почти всегда сводится к одному конкретному переходу, а не к расплывчатой проблеме качества. В реальном времени мы отслеживаем путь RTP и RTCP; в стриминговых и OTT-продуктах – PTS, DTS и PCR через контейнер. Именно поэтому рисование полного конвейера, как это делает данная статья, позволяет быстро находить сбои в синхронизации, а не гадать наугад.

Главное

  • Метка времени – это счётчик тактов на часах с фиксированной частотой, а не время суток.
  • Аудио и видео используют отдельные часы с независимыми случайными стартами.
  • Вещание работает на часах с частотой 27 МГц; WebRTC привязывает каждый поток к сетевому времени NTP.
  • Сеть не имеет собственных часов – она вносит джиттер, но никогда не изменяет временные метки.
  • Ключевые точки коррекции – буфер джиттера и ресэмплинг рендерера.
  • Рассогласование часов в 50 ppm накапливается до ~180 мс за час – это превышает допустимые пределы.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.