Содержание статьи +
- Коротко
- Почему это важно
- Одна идея – за каждой меткой времени
- Часы 90 kHz: сердцебиение тайминга в MPEG
- PTS: когда показывать кадр
- DTS: когда декодировать кадр – и почему он отличается
- Где метки физически живут
- Timescale: гибкие часы MP4
- Частые ошибки, которые приводят к реальным багам в синхроне
- Разбор на примере: проверка синхрона в реальном файле
- Где здесь Фора Софт
- Главное
- Что почитать дальше
Коротко
Каждый кадр аудио и видео содержит две временные метки: метку отображения (PTS), указывающую, когда его нужно показать, и метку декодирования (DTS), определяющую, когда его следует декодировать. Эти метки различаются только потому, что часть видеокадров кодируется не в том порядке, в котором они отображаются – декодеру приходится обрабатывать их в одной последовательности, а выводить на экран – в другой. В формате MPEG-2 (transport stream и program stream) обе метки представляют собой 33-битные счётчики, тикающие 90 000 раз в секунду; в формате MP4 та же идея реализуется через таблицу длительностей каждого сэмпла плюс смещение для каждого сэмпла. Если эти метки заданы правильно – звук и изображение идеально синхронизированы; если же ошиблись – получите одну из самых частых причин рассинхрона в записанном медиа.
Почему это важно
Если вы разрабатываете стриминговый сервис, OTT-приложение, функцию записи или любой другой сервис, который объединяет звук и видео в один файл, метка времени – это ключевой элемент, обеспечивающий их синхронизацию. Когда пользователи жалуются, что «звук отстаёт» или «в записи звук запоздал на полсекунды», почти всегда причина – в ошибке при генерации, копировании или чтении метки времени где-то в цепочке обработки. Продакт-менеджеру важно знать о существовании этого механизма, чтобы задавать правильные вопросы; инженеру – понимать, как он работает, чтобы проанализировать дамп потока и найти проблемное значение. Эта статья предлагает единую модель для обеих аудиторий: метка времени – это показание часов, прикреплённое к каждому фрагменту медиа, а синхронизация – это простая арифметика над этими значениями.
Одна идея – за каждой меткой времени
Начнём с задачи, которую решают метки времени. Медиафайл или поток – это не единая сущность, а как минимум две независимые: аудиодорожка и видеодорожка, записанные разными устройствами, сжатые разными кодеками, и теперь их нужно воспроизвести так, будто они – одно целое. Единственный способ для плеера правильно их синхронизировать – чтобы каждый фрагмент содержал указание: в какой именно момент по общим часам этот фрагмент должен прозвучать или появиться. Это указание и есть метка времени.
Подпись отвечает на один вопрос: в какой момент, отсчитанный по согласованным часам, этот фрагмент медиа должен появиться перед зрителем? Видеокадр с подписью «1,000 секунды» и аудиоблок с подписью «1,000 секунды» должны одновременно попасть в глаза и в уши. Вся дальнейшая работа плеера – прочитать эти подписи и выпустить каждый фрагмент ровно в тот момент, который указана в ней. Синхронизация на этом уровне – не магия, а простое чтение чисел и следование им.
Прежде чем говорить о двух видах меток, нужна единица измерения. У фрагмента медиа, к которому привязывается метка, есть формальное название – access unit (единица доступа). Access unit – это наименьший самостоятельно воспроизводимый фрагмент потока: один сжатый видеокадр или один сжатый блок аудиосэмплов. Метки привязываются к access unit, а не к отдельным байтам. Как поток разбивается на такие единицы, подробно объясняется в статье Фреймы, пакеты, гранулы: почему звук нарезается; здесь важно лишь понимать, что каждая метка указывает на конкретный access unit.
Часы 90 kHz: сердцебиение тайминга в MPEG
Каждая метка – это показание часов, поэтому первый вопрос: какие часы и с какой скоростью они тикают? В мире MPEG-2 systems – мире transport stream (вещание, HLS, файлы .ts) и program stream (DVD) – ответ даёт стандарт. Часы тикают 90 000 раз в секунду. Метка времени – это просто счётчик, сколько таких тиков прошло.
Почему 90 000, а не круглый миллион? Потому что 90 kHz нацело делится на частоты кадров, важные при разработке MPEG-2. При 25 кадрах в секунду один кадр длится ровно 90 000 ÷ 25 = 3 600 тиков. При 30 кадрах – ровно 90 000 ÷ 30 = 3 000 тиков. При 24 кадрах – ровно 90 000 ÷ 24 = 3 750 тиков. Часы, дающие целое число тиков на каждую распространённую частоту кадров, не накапливают ошибку округления кадр за кадром. Вот и вся причина странного на вид числа.
Под этим лежат более глубокие часы. Главные системные часы в MPEG-2 работают на частоте 27 МГц – 27 миллионов тактов в секунду, – а часы меток с частотой 90 кГц получаются делением главных ровно на 300 (27 000 000 ÷ 300 = 90 000). Часы на 27 МГц используются в program clock reference – значении, с помощью которого вещатели синхронизируют часы декодера с часами кодера; подробнее об этом – в статье PCR в MPEG-TS: программные часы, которые ведут вещание. Для временных меток отдельных кадров читается и записывается именно значение в 90 кГц.
Сама метка – это 33-битное число. Тридцать три бита позволяют отсчитать до 2³³ − 1 = 8 589 934 591 тика. Делим это число на 90 000 тиков в секунду и получаем период переполнения:
максимум счётчика = 2^33 − 1 = 8 589 934 591 тиков
частота часов = 90 000 тиков в секунду
время переполнения = 8 589 934 591 ÷ 90 000 ≈ 95 443 с ≈ 26,5 часаТо есть 33-битный PTS отсчитывает примерно 26,5 часа, после чего сбрасывается в ноль и начинает отсчёт заново. Для двухчасового фильма это не имеет значения; для круглосуточного вещательного канала – очень важно, и декодеры умеют корректно обрабатывать переполнение. Это первая ловушка из раздела «Частые ошибки» ниже.
PTS: когда показывать кадр
Первая из двух меток – метка презентации, PTS. Она отвечает на простой вопрос: в какое время по часам этот access unit нужно показать зрителю – вывести на экран или передать в динамики? Видеокадр с PTS = 90 000 должен появиться ровно через секунду после нулевой точки отсчёта, потому что 90 000 тиков ÷ 90 000 тиков в секунду = 1 секунда.
Для формата PTS это почти вся история. Аудио декодируется в том же порядке, в котором воспроизводится: первый блок сэмплов – это и первый блок, который вы слышите. Поэтому для аудиодорожки порядок представления и порядок декодирования совпадают. Аудио access unit должен лишь указать: «воспроизведи меня в этот момент» – и всё. Вот почему чисто аудиопотоки редко содержат отдельную метку декодирования: переставлять нечего.
Сделаем PTS конкретным на примере. Пусть аудиопоток с частотой дискретизации 48 кГц кодируется в формате AAC, упаковывающем 1 024 сэмпла в каждый access unit (в терминах AAC – фрейм). Сколько длится один фрейм AAC и каков шаг PTS между соседними фреймами?
длительность фрейма (с) = сэмплов на фрейм ÷ частота дискретизации
= 1 024 ÷ 48 000
= 0,021333… с (около 21,3 мс)
шаг PTS на фрейм = длительность фрейма × 90 000 тиков в секунду
= 0,021333… × 90 000
= ровно 1 920 тиковТо есть у потока фреймов AAC на 48 кГц значения PTS равны 0, 1920, 3840, 5760 и так далее – каждый следующий фрейм имеет метку времени на 1920 тиков больше предыдущей. Если вы выгрузите PTS чистого потока AAC и увидите, что они увеличиваются ровно на 1920 – тайминг аудио в порядке. Если шаг отличается или изменяется неравномерно – вы нашли баг.
DTS: когда декодировать кадр – и почему он отличается
Если бы порядок представления кадров совпадал с порядком их декодирования, одной метки было бы достаточно. Но это не так – причина в особенностях современного видеосжатия.
Чтобы сжать видео, кодеры используют тот факт, что соседние кадры почти одинаковы. Вместо хранения каждого кадра целиком кодер сохраняет несколько полных кадров, а остальные описывает как разницу с соседними. Существует три типа кадров, и их названия имеют значение:
- I-кадр (intra-coded) – полный кадр, который декодируется самостоятельно и не ссылается ни на какие другие кадры.
- P-кадр (predicted) – описан как разница по сравнению с предыдущим кадром.
- B-кадр (bi-directionally predicted) – описан как разница как с предыдущим, так и с последующим кадром.
Именно последний тип – вся проблема. B-кадр зависит от кадра, который идёт после него в порядке воспроизведения. Декодер не может восстановить B-кадр, пока не обработает тот будущий кадр, на который он ссылается. Поэтому кодер переставляет порядок данных в потоке: он помещает опорный кадр раньше в файле, чем B-кадр, которому он нужен, хотя этот опорный кадр показывается позже. Данные поступают в порядке декодирования, а зрителю нужен порядок презентации. Эти два порядка не совпадают.
Вот почему меток ровно две. Метка декодирования, DTS, указывает, когда декодер должен обработать access unit. Метка презентации, PTS, указывает, когда результат нужно показать. Для I- и P-кадров, которые не зависят от будущих кадров, DTS и PTS совпадают. Для опорных кадров, необходимых B-кадрам, DTS идёт раньше PTS, потому что такие кадры нужно декодировать заранее – чтобы они были готовы к моменту обработки B-кадров.
Пройдёмся по небольшой группе кадров. Пусть порядок их отображения – I B B P: I-кадр, два B-кадра, затем P-кадр, от которого зависят B-кадры. Поскольку B-кадры зависят от P-кадра, кодер должен поместить P-кадр в поток раньше B-кадров. В результате порядок на диске становится I P B B. Теперь рассмотрим две метки для каждого кадра (в единицах кадров для наглядности; умножьте на количество тиков на кадр, чтобы получить реальные PTS):
| Кадр | Позиция показа (PTS) | Позиция декодирования (DTS) | DTS = PTS? |
|---|---|---|---|
| I | 0 | 0 | да |
| P | 3 | 1 | нет – декодирован раньше |
| B | 1 | 2 | нет – декодирован после опорного |
| B | 2 | 3 | нет – декодирован после опорного |
Таблица 1. Группа из четырёх кадров с порядком показа I B B P. Декодер получает их в порядке DTS (I, P, B, B) и переставляет в порядок PTS (I, B, B, P) перед отображением. P-кадр – главная улика: он декодируется на позиции 1, но отображается на позиции 3.
Прочитайте таблицу медленно. Декодер извлекает кадры из потока в порядке DTS: сначала I, затем P и два B. Он сохраняет P-кадр в буфере, использует его для декодирования B-кадров, а затем выводит все четыре кадра на экран в порядке PTS – I, B, B, P. DTS обеспечивает правильную последовательность подачи данных в декодер, а PTS – корректную последовательность отображения на экране. Без DTS декодер попытался бы обработать B-кадр до появления опорного кадра и бы сломался.
Чистое правило на вынос: DTS ≤ PTS – всегда. Кадр никогда не показывается раньше, чем декодируется. Для звука и видео без B-кадров эти две метки совпадают. Разрыв между ними возникает только тогда, когда B-кадры вынуждают декодировать кадры не по порядку.
Где метки физически живут
Идея двух меток универсальна, но место хранения чисел зависит от контейнера. Два формата, с которыми вы столкнётесь, – это MPEG-2 systems (transport и program stream) и ISO base media file format (MP4 и его родственники).
В потоках MPEG-2: заголовок PES
В transport stream или program stream элементарные потоки сначала оборачиваются в пакеты packetized elementary stream – PES-пакеты. У каждого PES-пакета есть заголовок, а метки находятся в нём. Двухбитное поле PTS_DTS_flags сообщает декодеру, что присутствует: значение 10 означает, что далее идёт только PTS; значение 11 – что далее идут и PTS, и DTS. Значение 00 означает, что ни того, ни другого нет – обычно это используется для продолжения большого кадра, разбитого на несколько пакетов.
33-битная метка хранится не в виде чистого 33-битного поля. Стандарт MPEG-2 systems растягивает её на 5 байт (40 бит), разделяя 33 бита данных на три группы (3, 15 и 15 бит) и вставляя 1 – «маркерный бит» – после каждой группы, а также добавляя 4-битный префикс в начале. Значение префикса: 0010 – если присутствует только PTS, 0011 – для первой половины пары PTS+DTS, 0001 – для второй (DTS). Маркерные биты нужны, чтобы декодер, работающий с повреждённым потоком, мог определить, что читает именно временную метку, а не случайные данные. Вручную такую структуру почти никогда не разбирают – инструменты вроде ffprobe делают это автоматически, – но понимание её устройства объясняет, почему «33-битное» значение занимает на линии 5 байт.
В MP4 / ISOBMFF: таблицы, а не заголовки
Формат MP4 идёт совершенно иным путём, что сбивает с толку инженеров, впервые столкнувшихся с метками в transport stream. В файле MP4 каждый кадр не снабжается абсолютными значениями PTS и DTS. Вместо этого в боксе sample table хранятся таблицы, а плеер вычисляет временные метки, проходя по этим таблицам.
Тайминг содержит два бокса. Бокс stts («decoding time to sample») перечисляет длительность каждого сэмпла. Декодер начинает с нуля и последовательно суммирует длительности, чтобы вычислить DTS каждого сэмпла – то есть DTS не хранится напрямую, а вычисляется накоплением. Бокс ctts («composition time to sample») хранит для каждого сэмпла смещение между временем декодирования и временем композиции (презентации). Это смещение и есть PTS − DTS. У файла без B-кадров бокса ctts вообще нет, потому что все смещения были бы нулевыми. У файла с B-кадрами присутствует бокс ctts, чьи ненулевые записи соответствуют тому же разрыву перестановки, что мы видели в Таблице 1, но выраженному в сэмплах.
Есть ещё один бокс, который тихо переписывает тайминг – edit list, бокс elst. Он отображает внутреннюю шкалу медиа на шкалу презентации и именно так MP4 выражает такие вещи, как «начать воспроизведение с 40-й миллисекунды аудиодорожки» или «задержать видео на два кадра». Удивительно большая доля реальных багов синхронизации возникает из-за edit list, который один инструмент записал, а другой проигнорировал. Мы отмечаем это как ловушку ниже.
| Контейнер | Метки хранятся как | Источник DTS | Источник PTS | Признак B-кадров |
|---|---|---|---|---|
| MPEG-2 TS / PS | Явные значения в заголовке PES | Читается напрямую (если PTS_DTS_flags = 11) | Читается напрямую | Присутствуют и PTS, и DTS |
| MP4 / ISOBMFF | Таблицы в боксе sample table | Накапливается из длительностей stts | DTS + смещение ctts | Есть бокс ctts с ненулевыми смещениями |
Таблица 2. Где живут PTS и DTS в двух мирах контейнеров. Числа означают одно и то же, различаются лишь способ хранения и извлечение. Подробности о переносе данных для каждого контейнера рассмотрены в Аудио в контейнерах: как MP4, MKV, fMP4, MPEG-TS несут звук.
Timescale: гибкие часы MP4
Часы 90 kHz зафиксированы в MPEG-2, но формат MP4 позволяет каждой дорожке указывать свою частоту часов – timescale. Timescale – это количество тиков в секунду для дорожки, указанное в заголовке медиа. Аудиодорожка с частотой дискретизации 48 kHz обычно использует timescale 48 000, так что один тик соответствует одному аудиосэмплу, а длительности выражаются целыми числами. Видеодорожка может использовать значение 30 000 (или более удобное для вещания 30 000 с длительностями сэмплов по 1 001, чтобы точно выразить частоту 29,97 fps).
Timescale – причина, по которой нельзя прочитать метку MP4, не зная сначала частоты часов дорожки. Длительность stts в 1 024 означает 1 024 тика, а сколько это в секундах – зависит исключительно от timescale. При timescale 48 000 это как раз тот самый фрейм AAC на 21,3 мс из примера выше; при timescale 90 000 то же число означало бы иное. Всегда делите на timescale, чтобы получить секунды:
время в секундах = значение в тиках ÷ timescale
пример: длительность stts 1 024 при timescale 48 000
= 1 024 ÷ 48 000
= 0,021333… с (фрейм AAC на 21,3 мс)Эта гибкость на уровне каждой дорожки – мощное преимущество и одновременно причина второй распространённой ошибки: предполагать, что видеодорожка и аудиодорожка используют один и тот же timescale. Обычно это не так, и любой код, сравнивающий «сырые» тики видео и аудио без перевода их в секунды, выдаст бессмыслицу.
Частые ошибки, которые приводят к реальным багам в синхроне
Метки – это простая арифметика, но режимы отказа конкретны и повторяются почти в каждой команде, работающей с медиа. Четыре из них стоит назвать поимённо.
Принимать PTS за время по настенным часам. Самая частая концептуальная ошибка. PTS = 90 000 не означает «9 утра» или «90 секунд после нажатия кнопки play». Это счётчик собственных часов потока, нулевая точка которого произвольна – многие разработчики начинают первый PTS с ненулевого значения. PTS отражает соотношение кадров, а не время суток. Чтобы привязать его к настенным часам, нужен внешний ориентир (program clock reference в вещании или RTCP sender report в WebRTC) – отдельный механизм, описанный в Метки времени RTP, отчёты отправителя RTCP и синхронизация по NTP.
Игнорировать переполнение каждые 26,5 часа. Поскольку 33-битные часы сбрасываются примерно через 26,5 часа, наивное сравнение if (pts_a > pts_b) даёт ошибку в момент переполнения счётчика: кадр сразу после сброса имеет очень маленький PTS, хотя он позже, чем кадр непосредственно перед сбросом с большим PTS. Длительно работающие каналы и записи должны обнаруживать переполнение и учитывать его, иначе они могут переставить или потерять кадры в районе точки сброса.
Терять бокс ctts или edit list при ремуксе. Инструменты, копирующие поток из одного контейнера в другой, иногда сохраняют длительности stts, но выбрасывают смещения композиции ctts или edit list elst. В результате получается видео, в котором B-кадры теперь отображаются в порядке декодирования (каждый второй кадр явно смещён), либо звук, сдвинутый ровно на ту величину, которую должен был исправить edit list. Когда при ремуксе появляется ошибка синхронизации, которой не было в оригинале, потерянный ctts или elst – главный подозреваемый.
Сравнивать тики при несовпадающих часах. Как уже отмечалось, аудиодорожку с timescale 48 000 и видеодорожку с timescale 30 000 нельзя сравнивать по «сырым» тикам напрямую. Сначала нужно перевести обе дорожки в секунды. Пропуск этого перевода приводит к смещению синхронизации, которое увеличивается по мере продвижения воспроизведения – незначительное в начале, но значительное к концу. Именно это и является признаком несовпадения timescale, а не фиксированной задержки.
Разбор на примере: проверка синхрона в реальном файле
Соберём всё вместе. Пусть ffprobe сообщает о видеодорожке с timescale 90 000 и аудиодорожке с timescale 48 000, и вы хотите понять, должны ли первый аудиокадр и первый видеокадр воспроизводиться одновременно.
Видео: первый кадр DTS = 0, PTS = 3 750 тиков (timescale 90 000)
Аудио: первый кадр PTS = 0, DTS нет (timescale 48 000)
PTS первого видеокадра в секундах = 3 750 ÷ 90 000 = 0,041667 с (≈ 41,7 мс)
PTS первого аудиокадра в секундах = 0 ÷ 48 000 = 0,000000 сПервый показанный кадр видео отображается на 41,7 миллисекунды позже звука. DTS = 0 при PTS = 3 750 указывает на наличие B-кадров: первый кадр в порядке декодирования – опорный, он отображается на кадр позже (3 750 тиков = ровно один кадр при 90 000 ÷ 24 = 3 750, то есть это контент с частотой 24 кадра в секунду). Edit list мог намеренно обрезать эти 41,7 мс, чтобы привести оба значения к нулю – именно поэтому перед выводом о рассинхронизации файла нужно проверить бокс elst. В любом случае это смещение находится глубоко внутри допустимого окна lip-sync; почему задержка видео в 41,7 мс остаётся незаметной – см. Что значит «в синхроне»: ITU-R BT.1359, окна lip-sync, заметность.
Смысл упражнения – в методе, а не в числах: прочитайте timescale каждой дорожки, переведите каждый тик в секунды, учтите перестановку DTS-против-PTS, а затем проверьте edit list, прежде чем винить кодер. Эта последовательность позволяет выявить почти любой баг синхронизации в записанном медиа.
Где здесь Фора Софт
Мы разрабатываем функции записи, транскодирования и стриминга для видеоконференций, OTT, e-learning, телемедицины и видеонаблюдения с 2005 года, и обработка временных меток – это как раз то место, где синхронизация записанного медиа либо удаётся, либо терпит крах. Повторяющаяся реальная проблема – не экзотическая: конвейер записи, мультиплексирующий WebRTC-звонок в MP4, должен корректно преобразовывать временные метки RTP в длительности stts и смещения ctts. Даже одна утерянная запись edit list или несовпадение timescale позже проявляется как рассинхронизация звука и видео на длительных записях. Когда клиент сообщает, что сохранённая сессия оказалась рассинхронизированной, хотя живой звонок проходил без проблем, первым делом мы проверяем именно этап преобразования временных меток – ведь именно здесь стыкуются две системы отсчёта времени.
Главное
- Каждый access unit содержит PTS (время отображения) и, при необходимости, DTS (время декодирования).
- DTS отличается от PTS только из-за B-кадров, которые требуют декодирования в непоследовательном порядке; при этом DTS всегда ≤ PTS.
- В MPEG-2 используются фиксированные 33-битные часы с частотой 90 кГц; их значение хранится в заголовке PES.
- В MP4 тайминг хранится в виде таблиц: длительности stts задают DTS, а смещения ctts – PTS.
- Timescale в MP4 задаётся для каждой дорожки отдельно; перед сравнением всегда переводите тики в секунды.
- PTS – это не реальное время, а отсчёт 33-битных часов, которые переполняются примерно каждые 26,5 часа.