CMAF-упаковка: HLS и DASH из одного мастера

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

TL;DR

Упаковка (packaging) – это шаг, который превращает закодированное видео в точные файлы и плейлисты, которые скачивает плеер, и современная цель – сделать его один раз: закодировать рендишны в единый высококачественный мастер, а затем упаковать этот мастер в один общий набор сегментов, который умеют читать оба основных формата доставки. Этот общий набор собирают в формате Common Media Application Format (CMAF) – контейнере, на который могут ссылаться и HTTP Live Streaming (HLS), и MPEG-DASH, поэтому вы отдаёте одну кучу медиафайлов и поверх неё всего два небольших текстовых манифеста. Выигрыш прямой и денежный: вместо того чтобы хранить, кэшировать и платить за доставку двух отдельных копий каждой единицы контента – одной для мира Apple и одной для всех остальных – вы храните и кэшируете одну, что примерно вдвое сокращает упаковочно-складской след и поднимает cache-hit ratio вашего CDN. Эта статья объясняет, что такое mezzanine, почему HLS и DASH раньше требовали две копии, как CMAF свёл их к одной, какое решение по длительности сегмента тихо задаёт вашу задержку и качество переключения, и какие ошибки возвращают вас к старому «налогу на два стека».

Зачем это важно

Если вы основатель, продакт-менеджер или CTO стриминга впервые, упаковка – наименее заметный шаг в конвейере и одно из самых лёгких мест, где можно молча удвоить счёт. Каждое устройство зрителя ждёт видео в определённой форме – устройства Apple исторически хотели один контейнер, остальной мир – другой – и команда, не спланировавшая это, в итоге кодирует и хранит две полные копии всего каталога, а потом платит CDN за кэширование и доставку обеих. Эта статья даёт ментальную модель, чтобы этого избежать: что на самом деле делает упаковка, почему один CMAF-мастер теперь обслуживает и HLS, и DASH, как выбор длины сегмента меняет баланс задержки и эффективности, и как прочитать настройку упаковки и понять, собрана она один раз или дважды. К концу вы сможете задать три вопроса, которые решают стоимость: упаковываем мы в один контейнер или в два, какой длины наши сегменты и почему, и шифруем мы один раз или один раз на каждый формат.

Кодирование делает качество; упаковка делает файлы

Между исходником и экраном зрителя стоят два шага, и команды постоянно их путают. Первый – кодирование (encoding): сжатие видео в набор версий качества, который называют encoding ladder, где каждая версия (рендишн) сочетает разрешение и битрейт. Это решение мы полностью разбираем в статье encoding ladder простыми словами. Кодирование решает, насколько хорошо выглядит картинка и сколько байт она занимает.

Второй шаг – упаковка (packaging), и именно о нём эта статья. Упаковка берёт эти закодированные рендишны и записывает их в точную файловую структуру, которую плеер может скачать и воспроизвести: режет каждый рендишн на маленькие отрезки по времени, называемые сегментами, оборачивает их в контейнерный формат, понятный плееру, и пишет небольшой текстовый индекс – манифест – который перечисляет каждый рендишн и каждый сегмент, чтобы плеер знал, что и когда скачивать. Кодирование – это готовка; упаковка – сервировка и меню. Еда та же; то, как вы её подаёте, решает, кто сможет её съесть.

У единственной высококачественной версии, с которой начинается упаковка, есть имя: mezzanine. Заимствованный из вещания, mezzanine – это чистая высокобитрейтная мастер-копия единицы контента, промежуток между оригинальным исходником и сжатыми рендишнами, которые вы доставляете. В современном конвейере вы кодируете mezzanine один раз в свою ladder из рендишнов, а затем упаковываете эти рендишны для доставки. Фраза «из одного mezzanine» в заголовке статьи – и есть вся суть: один мастер, упакованный один раз, отданный каждому устройству.

Проблема, которую упаковка создавала: две копии всего

Бóльшую часть истории стриминга упаковка навязывала болезненный выбор, и понимание причины – ключ к пониманию CMAF.

Контейнер – это файловая обёртка, которая держит сжатые видео и аудио плюс данные тайминга, нужные плееру; думайте о ней как о коробке, а не о содержимом. Содержимое (собственно сжатые пиксели, произведённые кодеком вроде H.264) может быть одинаковым; коробка вокруг них различалась по формату доставки. HLS – формат, созданный Apple и понятный каждому устройству Apple, – изначально требовал свои сегменты в одном виде коробки: MPEG-2 Transport Stream (расширение .ts), контейнер, унаследованный из цифрового вещания. Это закреплено в стандарте HLS, IETF RFC 8216 §3.2. MPEG-DASH – формат, используемый почти везде вне экосистемы Apple, – использовал другую коробку: fragmented MP4 (fMP4), основанный на ISO Base Media File Format (ISO/IEC 14496-12).

Те же пиксели, две коробки. Чтобы достать и устройства Apple, и всех остальных, приходилось упаковывать каждый рендишн каждой единицы контента дважды – один раз как .ts для HLS, один раз как fMP4 для DASH. Это означало дублирование работы по упаковке, две полные копии в хранилище и CDN, кэширующий обе, что дробило ваш кэш и чаще отдавало байты каждой единицы из origin. Мы называем это налогом на два стека: вы платили примерно вдвое за упаковку и хранение, чтобы покрыть деление устройств, которого не создавали. Хуже того, контейнер transport stream сам по себе менее эффективен, чем fMP4 – он несёт больше структурных накладных расходов, поэтому HLS-копия была даже чуть крупнее DASH-копии, которую дублировала.

Рис. 1. Раньше каждый рендишн упаковывали дважды – один контейнер для HLS, другой для DASH. CMAF заменяет две кучи медиа одной и оставляет раздельными только два маленьких текстовых манифеста.

CMAF: одна коробка, которую согласны читать оба формата

Решение появилось в 2016 году, когда Apple и Microsoft совместно предложили единый стандартный контейнер, который могли бы использовать оба формата доставки, опубликованный в 2018-м как Common Media Application Format (CMAF), ISO/IEC 23000-19 (текущая редакция 2024). CMAF – не новый протокол и не конкурент HLS или DASH; он лежит под ними. Это стандартизированный способ упаковать медиа как fragmented-MP4-сегменты, построенный на том же ISO Base Media File Format, который DASH уже использовал. Полезная картинка: HLS и DASH – два языка, а CMAF – единый алфавит, на котором теперь можно записать оба языка. Вы пишете медиа один раз этим алфавитом, а затем добавляете короткую «сопроводительную записку» HLS и короткую «сопроводительную записку» DASH сверху.

Причина, по которой это работает, записана в самом стандарте HLS, и её стоит процитировать, потому что большинство статей её пропускают. Наряду с легаси transport stream, RFC 8216 §3.3 определяет fragmented MP4 как поддерживаемый формат сегментов HLS и прямо заявляет, что «заголовок Common Media Application Format (CMAF) удовлетворяет всем этим требованиям» и «сегмент CMAF удовлетворяет этим требованиям». Иными словами, официальная спецификация HLS явно принимает CMAF-сегменты. Apple анонсировала эту поддержку fMP4 для HLS на своей конференции разработчиков в 2016 году – в том же году, когда был предложен CMAF. Поскольку DASH уже был форматом ISO-BMFF/fMP4, те же CMAF-сегменты удовлетворяют и DASH. Один набор сегментов, два формата, которые оба его принимают.

Итак, современный выход упаковки выглядит так: одна папка CMAF-сегментов на рендишн (плюс крошечный init-файл на рендишн) и два манифеста, индексирующие те же сегменты – HLS multivariant playlist (.m3u8) и DASH media presentation description (.mpd). Манифесты – маленькие текстовые файлы; медиа – большое. Вы дублируете маленькое и делите большое.

Урезанный HLS-манифест, указывающий на fMP4/CMAF-сегменты – обратите внимание на тег EXT-X-MAP, который RFC 8216 §4.3.2.5 требует для каждого fMP4-сегмента, чтобы назвать его init-файл:

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-MAP:URI="init_1080p.mp4"
#EXTINF:6.000,
seg_1080p_00001.m4s
#EXTINF:6.000,
seg_1080p_00002.m4s

DASH-манифест индексирует те же .m4s-сегменты другими словами – Representation внутри AdaptationSet, модель, определённая в ISO/IEC 23009-1:

<AdaptationSet mimeType="video/mp4" segmentAlignment="true">
  <Representation id="1080p" bandwidth="6000000" width="1920" height="1080" codecs="avc1.640028">
    <SegmentTemplate media="seg_1080p_$Number$.m4s" initialization="init_1080p.mp4"
                     duration="6" startNumber="1"/>
  </Representation>
</AdaptationSet>

Два манифеста, один набор файлов-сегментов. Это и есть «упаковать один раз» конкретно. Байтовая анатомия этих сегментов – боксы ftyp, moov, moof и mdat, из которых состоит fMP4-файл, и иерархия CMAF track-fragment-chunk – это внутренности протокола доставки, и мы держим их там, где им место: см. CMAF: формат упаковки, объединивший HLS и DASH в нашем разделе Video Streaming, рядом с подробными разборами HLS и MPEG-DASH. Для продуктового решения по OTT анатомия коробки важна лишь настолько, насколько она задаёт стоимость и охват – и именно там мы остаёмся.

Рис. 2. Один mezzanine, закодированный в ladder, упакованный один раз в CMAF, зашифрованный один раз, затем проиндексированный двумя маленькими манифестами. Дорогое медиа – общее; различается только дешёвый текст.

Что такое CMAF-сегмент – ровно столько, чтобы решить

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

Во-первых, каждый рендишн доставляется как два вида файлов: один маленький init-сегмент, который говорит плееру, как настроить декодер (разрешение, кодек, тайминг – и ничего воспроизводимого), и серия медиа-сегментов, которые несут собственно отрезки видео по несколько секунд каждый. В HLS init-сегмент называется тем самым тегом EXT-X-MAP; в DASH – атрибутом initialization. Тот же файл, два указателя. Поэтому «упаковать один раз» буквально верно на уровне файлов: плеер скачивает тот же init_1080p.mp4 и тот же seg_1080p_00001.m4s, нашёл он их через .m3u8 или через .mpd.

Во-вторых, сегмент можно ещё раз разделить на chunks – ещё более мелкие отрезки, обычно доля секунды, – которые существуют, чтобы live-стрим мог начать отдавать начало сегмента до того, как весь сегмент закодирован. Chunks – основа low-latency-стриминга, и они позволяют держать длинные сегменты (хорошо для эффективности), всё ещё доставляя быстро (хорошо для задержки). Механика chunked transfer и low-latency-профили живут в LL-DASH и low-latency CMAF; для упаковки chunk – это единица, которая отвязывает вашу задержку от длины сегмента, к чему мы переходим дальше.

Решение по длительности сегмента: ваш тихий рычаг задержки и стоимости

Вот решение упаковки, которое команды чаще всего принимают случайно: какой длины каждый сегмент? Это звучит пустяком и задаёт сразу три вещи – качество переключения, эффективность доставки и live-задержку – так что оно заслуживает осознанного выбора.

Сегмент должен начинаться с keyframe (полного самостоятельного кадра, с которого декодер может стартовать, не нуждаясь в предыдущем кадре). Keyframe'ы крупны по сравнению с кадрами между ними, поэтому границы сегментов дороги. Этот один факт и двигает весь баланс.

Длинные сегменты (скажем, 6–10 секунд) ставят в поток меньше keyframe'ов, что лучше сжимается и означает меньше файлов и меньше сетевых запросов, поэтому CDN хорошо их кэширует, а нагрузка на origin падает. Цена: плеер может переключать рендишны только на границе сегмента, так что длинные сегменты делают адаптивное переключение грубым и медленным, а для live поднимают задержку, потому что плеер обычно ждёт целые сегменты. Короткие сегменты (1–2 секунды) дают плееру быстро переключать качество и срезают live-задержку, но умножают keyframe'ы и число запросов, что снижает эффективность сжатия и сильнее грузит origin.

HLS Authoring Specification от Apple рекомендует целевую длительность сегмента 6 секунд как общий дефолт, и это разумная стартовая точка для video-on-demand. Для low-latency live это решают не уменьшением сегментов – так keyframe-налог платится снова и снова – а сохранением разумных сегментов и использованием chunks внутри них, чтобы плеер забирал sub-секундные chunks, пока сегменты остаются эффективными. Индустриальная low-latency-практика держит CMAF-chunks в диапазоне 200–500 миллисекунд и может достичь glass-to-glass-задержки порядка двух длительностей chunk, против многих длительностей сегмента без chunking.

Рис. 3. Длина сегмента меняет баланс задержки и скорости переключения против сжатия и эффективности кэша. Chunks позволяют держать эффективные длинные сегменты, всё ещё доставляя live быстро.

Шифруйте один раз, а не один раз на формат

Упаковка – это ещё и место, где применяется защита контента, и логика «упаковать один раз» распространяется на неё прямо – именно поэтому правильный контейнер важен не только ради хранения. Система, которая не даёт копировать премиум-контент, называется digital rights management (DRM), и она опирается на шифрование сегментов так, чтобы их мог расшифровать только авторизованный плеер. Стандарт, позволяющий одному набору зашифрованных сегментов обслуживать разные DRM-системы, – Common Encryption (CENC), ISO/IEC 23001-7. Он определяет две схемы шифрования: cenc (использует AES в режиме counter) и cbcs (AES в режиме cipher-block-chaining). Деталь, которая решает ваш workflow: FairPlay от Apple требует схему cbcs, а cbcs принимают и остальные крупные DRM-системы, так что шифрование ваших CMAF-сегментов один раз схемой cbcs позволяет выдавать лицензии для FairPlay (Apple), Widevine (Google) и PlayReady (Microsoft) из одних и тех же файлов.

Рис. 4. Зашифруйте CMAF-набор один раз схемой `cbcs` – и каждый DRM обслуживается из тех же файлов. Зашифруйте только `cenc` – и FairPlay (каждое устройство Apple) гаснет.

Это та же форма, что и история с контейнером. Как один CMAF-контейнер избегает двух стеков упаковки, так одно cbcs-шифрование избегает шифрования каталога отдельно под каждый DRM. Сделайте неправильно – зашифруйте только старой схемой cenc – и устройства FairPlay вообще не смогут воспроизвести ваш контент, тихо отрезав каждый iPhone и Apple TV. Поскольку DRM – решение со своей глубиной и своими режимами отказа, мы разбираем его полностью в Блоке 4: см. CENC, CTR и CBCS: common encryption простыми словами и multi-DRM: один workflow, все устройства. Для упаковки держите одно правило: и контейнер, и шифрование – решения «один раз», и именно CMAF плюс cbcs делают это «один раз» возможным.

Static против just-in-time: где живёт одна копия

Ещё один выбор упаковки влияет на хранение: когда вы на самом деле создаёте сегменты и манифесты? Есть две модели.

Static (пред-) упаковка пишет все CMAF-сегменты и оба манифеста в хранилище заранее, так что CDN забирает готовые файлы. Это просто и быстро отдавать, но хранит готовую упаковку для каждой единицы контента независимо от того, смотрит ли её кто-нибудь.

Just-in-time (динамическая) упаковка хранит только mezzanine-рендишны и генерирует сегменты и запрошенный манифест на лету, когда просит плеер, кэшируя результат на CDN. Она минимизирует то, что вы храните – один набор исходных файлов, без заранее собранных вариантов форматов – ценой некоторого компьюта в момент запроса. Для большого каталога с длинным хвостом редко смотримых единиц just-in-time держит хранение маленьким; для небольшого, активно смотримого каталога static часто проще и дешевле отдавать. Этот баланс и архитектуру origin за ним мы разбираем в just-in-time packaging против pre-packaged origin. В любом случае именно CMAF держит это в одной копии вместо двух: вы выбираете, когда делать единую общую упаковку, а не делать ли две.

От упаковки к счёту: арифметика, которая важна

Свяжем упаковку обратно с деньгами, потому что именно поэтому «один раз» побеждает. Возьмём каталог из 1 000 двухчасовых фильмов, каждый закодирован в ladder из семи rung'ов, чьи рендишны суммируются примерно в 17,5 Mbps хранимого битрейта (арифметика ladder разобрана в статье про encoding ladder). Одна упакованная копия одного фильма:

суммарный битрейт  = 17,5 Mbit/s
длина фильма       = 2 ч × 3 600 с          = 7 200 с
одна копия         = 17,5 Mbit/s × 7 200 с ÷ 8 = 15 750 МБ ≈ 15,75 ГБ
каталог, 1 копия   = 15,75 ГБ × 1 000 фильмов ≈ 15,75 ТБ

Упакуйте по-старому – отдельно .ts для HLS и fMP4 для DASH – и вы храните две копии, около 31,5 ТБ, причём transport-stream-копия крупнее из-за накладных расходов. Упакуйте один раз с CMAF – и вы храните примерно 15,75 ТБ. Экономия хранения близка к половине; отчёты вендоров оценивают экономию на кодировании-упаковке-хранении от перехода на единый fMP4/CMAF-выход в том же диапазоне – Bitmovin сообщает примерно 50% против поддержки HLS-TS плюс DASH. Эффективность контейнера добавляет ещё немного, так как transport stream несёт примерно на 10% больше накладных расходов, чем fMP4.

Хранение – меньший приз. Бóльший – доставка и кэширование. CDN кэширует то, что у него просят; если зрители Apple тянут .ts-файлы, а все остальные – fMP4, edge держит две копии каждого популярного сегмента и отдаёт больше запросов из origin. С одним CMAF-набором каждый зритель данного рендишна тянет тот же файл-сегмент, так что кэш держит одну копию и cache-hit ratio растёт – на практике сообщают о примерно двукратном росте для общего контента – что напрямую снижает egress, который вы платите. Поскольку доставка – доминирующая регулярная статья расходов стриминговой платформы, эффект кэша от упаковки один раз обычно важнее строки хранения. Полную модель стоимости доставки см. в стоимость CDN: egress, коммиты и 95-й перцентиль, а взгляд на платформу целиком – в модели стоимости OTT.

Контейнер и поддержка форматов – с одного взгляда

Таблица показывает, какой контейнер принимает каждый формат доставки и где каждый ложится на решения выше. Колонки «HLS?» и «DASH?» – это вид покрытия, который говорит, может ли одна упаковка обслужить оба.

Выбор упаковкиHLS?DASH?Шифровать один раз (cbcs)?Low latency через chunks?Заметки
MPEG-2 TS (.ts)Да (легаси)НетНетНетСтарый HLS-only путь; ~10% больше накладных, чем fMP4
fMP4 / CMAF (.m4s)ДаДаДаДаСовременный общий контейнер – одна копия на оба
CMAF + cbcs CENCДаДаДаДаmulti-DRM из одного шифрования; совместим с FairPlay
Два отдельных стекаДаДаНет (на формат)ЧастичноНалог на два стека – двойное хранение и дроблёный кэш

Табл. 1. Выборы упаковки и что они покрывают. Один CMAF-набор с cbcs-шифрованием – единственная строка, которая обслуживает и HLS, и DASH, шифрует один раз и поддерживает low latency без хранения двух копий.

Частая ошибка: отдавать два стека в 2026-м

Самая дорогая ошибка упаковки – та, которую команда так и не замечает: поднять HLS-конвейер, выдающий transport-stream-сегменты, и отдельный DASH-конвейер, выдающий fMP4, потому что так пришли настроены оба инструмента. Это работает – зрители видят видео – поэтому никто не поднимает флаг. А тем временем платформа хранит две копии каталога, CDN кэширует две копии каждого популярного сегмента, и счёт за egress идёт выше, чем должен, навсегда. Лечение – упаковывать оба формата из одного CMAF-набора; экономия складывается каждый месяц, пока каталог растёт.

С ней ходят три родственные ошибки. Первая – шифровать на формат или только cenc, что либо удваивает работу по шифрованию, либо тихо отрезает каждое устройство FairPlay (Apple); лекарство – одно cbcs-шифрование, разобранное в Блоке 4. Вторая – длительность сегмента, выбранная по умолчанию – обычно слишком длинная для обещанной live-задержки или слишком короткая для нужной эффективности кэша; выбирайте её осознанно под свой сценарий. Третья – забыть про хвост легаси-устройств: небольшому числу старых устройств всё ещё нужен transport-stream HLS, так что если ваша аудитория включает их, спланируйте узкий .ts-fallback, а не открывайте пробел в саппорт-тикетах – но сделайте CMAF дефолтом, который делят все остальные, а не исключением.

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

Упаковка – это место, где тихо решаются складской след и эффективность кэша стриминговой платформы, и сделать её правильно в масштабе – один CMAF-мастер на единицу контента, длительности сегментов под сценарий, одно cbcs-шифрование, питающее каждый DRM, и верный выбор static-против-just-in-time под форму каталога – это разница между счётом за egress, который растёт линейно, и тем, что растёт вдвое быстрее. Фора Софт строит ПО для видеостриминга, OTT/Internet TV, e-learning, телемедицины и видеонаблюдения с 2005 года – 250+ выпущенных проектов для 400+ клиентов – и эта работа центрирована именно на такой инженерии масштаба и стоимости: проектировании упаковочных и origin-воркфлоу так, чтобы один набор файлов обслуживал каждое устройство, был защищён один раз и хорошо кэшировался. Когда медиакомпании нужна стриминговая платформа, чья экономика доставки переживёт реальную растущую аудиторию, именно эту инженерию упаковки и origin мы приносим.

Главное

  • Упаковка превращает закодированные рендишны в сегменты и манифесты, которые скачивает плеер.
  • HLS когда-то требовал MPEG-TS, а DASH – fMP4, навязывая две хранимые копии всего.
  • CMAF – один контейнер на базе fMP4, который принимают и HLS, и DASH; RFC 8216 допускает CMAF-сегменты.
  • Вы отдаёте один набор медиа-сегментов плюс два маленьких текстовых манифеста – упаковка один раз.
  • Длина сегмента меняет баланс live-задержки и скорости переключения против сжатия и кэша.
  • Одно cbcs-шифрование защищает те же CMAF-сегменты для FairPlay, Widevine и PlayReady.

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

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

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