Содержание статьи +
- Коротко
- Почему это важно
- Главная идея: метка времени – это счётчик тактов, а не время суток
- Семь этапов и часы в каждом
- Два мира бок о бок: программные часы 27 МГц против стенных часов NTP
- Разбор примера: сколько накапливается дрейфа?
- Частые ошибки, ломающие синхронизацию
- Где здесь Фора Софт
- Главное
- Что почитать дальше
Коротко
Между микрофоном и динамиком аудио и видео проходят через шесть-семь разных часов, и lip-sync выживает только если каждые часы передают следующим корректную метку времени. Эта статья рисует весь конвейер целиком – захват, кодер, мультиплексор, сеть, демультиплексор, декодер, рендерер – и подписывает каждый домен часов, каждую метку времени и точное место, где появляется дрейф и где он исправляется. Вы узнаете, что считает каждая метка (такты дискретизации, а не время суток), почему вещательный мир использует программные часы на 27 МГц, а мир WebRTC – стенные часы NTP, и как приёмник восстанавливает одну общую шкалу времени из потоков, стартовавших со случайных чисел. Сопроводительный постер в PDF умещает всю диаграмму на одной странице, которую можно повесить над столом.
Почему это важно
Если вы строите видеопродукты – конференции, стриминг, OTT, видеонаблюдение, телемедицину – вопрос «почему звук рассинхронизирован?» рано или поздно попадёт к вам на стол, и честный ответ почти никогда не «один баг». Это какие-то часы где-то в цепочке из семи часов, которые передали неверную или отсутствующую метку времени. Эта статья – карта. Продакт-менеджер прочитает её, чтобы понять, о чём спорят инженеры; инженер использует её, чтобы найти, какой именно переход роняет мяч. Весь смысл Блока 5 в том, что никто в интернете не рисует эту диаграмму целиком – поэтому мы её нарисовали.
Главная идея: метка времени – это счётчик тактов, а не время суток
Прежде чем любая диаграмма обретёт смысл, зафиксируйте одну мысль. Метка времени медиа не говорит вам, который сейчас час. Она говорит, сколько тактов неких часов прошло с собственной точки отсчёта этих часов. Точка отсчёта часто – случайное число, выбранное при старте потока. Часы тикают с фиксированной частотой – скажем, 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 кГц нацело делится на любую распространённую частоту кадров (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 кГц (те же 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 мс за час – за пределами допуска.