MPEG-DASH: подробный разбор MPD, Periods, AdaptationSets, Representations

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

Опубликовано: 2026-05-21 · Время чтения: 25 мин · Автор: Николай Сапунов, CEO Фора Софт

Последняя сверка: 2026-05-21 со стандартом ISO/IEC 23009-1:2022 (Dynamic adaptive streaming over HTTP – Part 1, пятая редакция, август 2022), DASH-IF Implementation Guidelines: Restricted Timing Model (post-community-review, 2025), DASH-IF Implementation Guidelines v4.3, ISO/IEC 23000-19:2024 (CMAF, третья редакция), ISO/IEC 23001-7:2023 (Common Encryption, четвёртая редакция), исходниками референсного плеера dash.js v5 и Bitmovin Video Developer Report 2025/26.

TL;DR

MPEG-DASH – Dynamic Adaptive Streaming over HTTP, формальное обозначение ISO/IEC 23009-1 – это международно-стандартизованный родственник Apple HLS и протокол доставки по умолчанию везде за пределами экосистемы Apple. Его манифест – это XML-документ под названием Media Presentation Description (MPD), который описывает поток как четырёхуровневую иерархию: Презентация содержит один или несколько Periods (отрезков времени), каждый Period содержит один или несколько AdaptationSets (групп взаимозаменяемых дорожек одного и того же контента – например, всех видеорендиций фильма или одной аудиодорожки), каждый AdaptationSet содержит один или несколько Representations (по одной закодированной версии с фиксированным битрейтом, разрешением, кодеком и языком), а каждое Representation доставляется как последовательность Segments, чьи URL либо перечислены явно (SegmentTimeline), либо генерируются из шаблона по номеру (SegmentTemplate$Number$), либо из шаблона по времени (SegmentTemplate$Time$). DASH не зависит от кодека, контейнера и DRM – один и тот же MPD может отдавать H.264 в MPEG-TS, H.265 во fragmented MP4, AV1 в CMAF-чанках, с ключами Widevine, PlayReady и FairPlay, мультиплексированными через Common Encryption. В 2026 году доминирующий не-Apple стек – это DASH-over-CMAF с CENC, на котором работают Netflix, YouTube, Disney+, Prime Video и каждый крупный OTT за пределами iOS Safari.

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

Половина стримингового трафика в публичном интернете идёт на MPEG-DASH, и нельзя принять обоснованное архитектурное решение по видеопродукту, не понимая его. HLS чаще на слуху, потому что Apple отгружает его как единственный протокол, который iOS Safari умеет нативно, но каждое Android-устройство, каждый Smart TV на ExoPlayer или Shaka, каждый браузер с поддержкой Media Source Extensions, каждый премиальный OTT-сервис, которому нужны три параллельные DRM-системы (Widevine для Chrome и Android, PlayReady для Edge и Xbox, FairPlay для Safari и Apple TV) – отгружает DASH. Формат MPD плотнее и гибче, чем m3u8-плейлист, и именно эта гибкость даёт DASH SCTE-35-маркеры рекламы, многопериодные live-to-VOD-окна, мульти-DRM-сигнализацию и trick-mode-дорожки, которые реально нужны broadcast и премиальному OTT. Цель статьи – превратить MPD из непрозрачного куска XML в документ, который читается как проза, поведение которого можно предсказать, и в котором можно разобраться, когда плеер вдруг падает на нижнюю рендицию без видимой причины.

Что такое DASH в одном абзаце

MPEG-DASH – это HTTP-based adaptive streaming-протокол, стандартизированный как ISO/IEC 23009-1 в ISO/IEC JTC 1/SC 29/WG 11 (Moving Picture Experts Group). DASH-поток состоит из двух артефактов: единственного XML-манифеста под названием Media Presentation Description (MPD) и набора медиафайлов под названием Segments, отдаваемых по обычному HTTP. MPD описывает таймлайн потока (где он начинается, сколько длится, live это или on-demand), все доступные кодированные версии аудио, видео и текста (разрешение, битрейт, кодек, язык, accessibility), как эти версии адресуются (URL-шаблоны или явные списки) и как они защищены (идентификаторы DRM-схем и метаданные шифрования). DASH-плеер скачивает MPD один раз, парсит его, решает, какую комбинацию видео-Representation + аудио-Representation + текст-Representation проигрывать в данный момент, скачивает соответствующие Segments и непрерывно пересматривает это решение по мере изменения сетевых условий и характеристик устройства. Поскольку всё работает по обычному HTTP и протоколу всё равно, что внутри Segments, DASH наследует все свойства HTTP-доставки – CDN-кэширование, простой проход через файрволы, бесплатное использование HTTP/2 и HTTP/3 – и не добавляет никакой кастомной серверной механики, которая требуется RTMP, RTSP или WebRTC.

Краткая история, с датами

MPEG-DASH не появился из ниоткуда. Это была конвергенция трёх более ранних проприетарных HTTP-стриминговых протоколов – Apple HLS (2009), Microsoft Smooth Streaming (2008) и Adobe HDS (2010) – в один вендор-нейтральный международный стандарт.

Работа началась в 3GPP в 2009 году как Adaptive HTTP Streaming (AHS) с явной целью заменить RTSP/RTP-стриминг, который тогда несли 3G-сети. В 2010 MPEG выпустил call for proposals на HTTP-стриминговый стандарт, получил восемьдесят семь заявок и включил работу 3GPP в результат. Draft international standard был одобрен в январе 2011 года. Первая редакция ISO/IEC 23009-1 вышла в апреле 2012 года; протокол получил бренд MPEG-DASH. Первое live-внедрение – трансляция VRT летних Олимпийских игр в Лондоне в августе 2012. В том же году был сформирован DASH Industry Forum (DASH-IF) – он публикует implementation-профили и поддерживает референсный плеер.

С первой публикации стандарт пересматривался четыре раза. Вторая редакция – 2014 год, третья – 2019, четвёртая – 2020, текущая пятая (ISO/IEC 23009-1:2022) – август 2022 года. Редакция 2022 года добавила дескриптор минимально требуемой защиты вывода устройства, более гибкую сигнализацию полосы пропускания для VBR-кодирования, фреймворк Service Description, на котором построен low-latency-профиль DASH-IF, и уточнила механизм MPD Patch, превращающий длинные live-MPD в инкрементальные обновления. ISO/IEC 23009 сегодня – это многочастный стандарт: Part 1 – MPD и формат сегментов; Part 4 – шифрование и аутентификация сегментов; Part 5 – Server and Network Assisted DASH (SAND); Part 8 – session-based DASH operations.

В 2017 году Common Media Application Format (ISO/IEC 23000-19, CMAF) стандартизировал fragmented MP4-упаковку, которой могут пользоваться и HLS, и DASH, – поэтому одни и те же медиабайты теперь обслуживают оба протокола. Третья редакция CMAF 2024 года добавила нативную поддержку AV1. Связка DASH-over-CMAF с Common Encryption – фактически стандартный стек премиальной OTT-доставки в 2026 году.

Четырёхуровневая иерархия MPD

Структурная модель MPD – это четырёхуровневое дерево, и каждый другой элемент в манифесте висит на одном из узлов этого дерева. Чтение любого DASH-манифеста начинается с одной и той же картинки в голове.

MPD (вся презентация)
└── Period (отрезок времени — одна реклама, одна программа, весь VOD-актив)
    └── AdaptationSet (один тип медиа в этом Period — видео, одна аудиодорожка, субтитры)
        └── Representation (одна кодированная версия с фиксированным битрейтом / разрешением / кодеком)
            └── Segment (скачиваемый файл или byte-range, покрывающий отрезок времени)

Задача каждого уровня – ограничить следующий.

MPD – презентация

Корневой элемент. Несёт атрибуты документа в целом: @type ("static" для VOD, "dynamic" для live), @profiles (идентификаторы DASH-профилей, которым соответствует манифест), @mediaPresentationDuration для статичного MPD или @availabilityStartTime для динамического, @minBufferTime (минимальный буфер плеера для непрерывного воспроизведения на максимальной заявленной полосе), @suggestedPresentationDelay для live (на сколько игроку отступать от live edge) и @minimumUpdatePeriod для live (как часто плеер должен перечитывать MPD). Содержит один или несколько <Period> и, опционально, элемент <ServiceDescription> с low-latency-подсказками, целевой задержкой и диапазоном допустимой скорости воспроизведения.

<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     type="dynamic"
     profiles="urn:mpeg:dash:profile:isoff-live:2011,urn:mpeg:dash:profile:cmaf:2019"
     availabilityStartTime="2026-05-21T08:00:00Z"
     minimumUpdatePeriod="PT2S"
     timeShiftBufferDepth="PT60S"
     minBufferTime="PT2S"
     suggestedPresentationDelay="PT4S">
  <Period id="p0" start="PT0S">
    …
  </Period>
</MPD>

Эта преамбула уже сообщает плееру шесть вещей: это live-поток; он начался в 08:00:00 UTC; плеер должен перечитывать MPD каждые 2 секунды; DVR-окно – 60 секунд; нужен буфер минимум на 2 секунды на максимальном битрейте; рекомендуемое расстояние от live edge – 4 секунды. Плеер может спланировать сессию ещё до того, как тронет хотя бы один сегмент.

Period – отрезок времени

Period – это непрерывный отрезок времени внутри презентации. Все остальные элементы манифеста привязаны к Period. VOD-актив – обычно один Period от start="PT0S" до start + duration = mediaPresentationDuration. Live-поток – обычно один Period, начинающийся с availabilityStartTime и не заканчивающийся явно. Интересный случай – multi-period: live-канал, переключающийся между основной программой и рекламной паузой и обратно; VOD-фильм с пре-, мид- и пост-роллом; 24/7-канал, который меняет параметры кодирования каждый час. Каждый из таких отрезков – отдельный Period со своим набором AdaptationSets, и плеер обрабатывает границу, сбрасывая текущие SourceBuffer и пересоздавая их под новый Period – обычно с разрывом в один сегмент, во время которого пользователь видит короткую паузу.

<Period id="programme" start="PT0S" duration="PT30M">
  <!-- основная программа: видео + аудио + субтитры -->
</Period>
<Period id="ad-break-1" start="PT30M" duration="PT60S">
  <!-- 60-секундный рекламный блок, отдельно закодированный, возможно с другого origin -->
</Period>
<Period id="programme-2" start="PT31M" duration="PT30M">
  <!-- программа возобновляется -->
</Period>

В Period может быть <BaseURL>, который префиксует все URL внутри, – именно так Server-Side Ad Insertion (SSAI) подставляет рекламу со стороннего ad-origin, не меняя реальные URL рекламных сегментов. В Period может быть и <EventStream> для сигнализации SCTE-35 cue-tone, границ программ или out-of-band-метаданных. Атрибут @duration опционален для последнего Period в статичном MPD и запрещён для бесконечного live-Period; плеер выводит длительность из start следующего Period или из mediaPresentationDuration.

AdaptationSet – один тип медиа в этом Period

AdaptationSet группирует взаимозаменяемые Representations одного и того же контента. Определяющий критерий: любое Representation внутри AdaptationSet должно быть допустимой заменой любого другого в середине воспроизведения, без переинициализации decoder pipeline. Классические группировки: один AdaptationSet под видео (все видеорендиции одного источника – разные разрешения и битрейты, но одна семья кодеков, одно соотношение сторон, одно цветовое пространство), по одному на каждый аудиоязык (все битрейтовые лестницы английского аудио в одном AdaptationSet, испанского – в другом) и по одному на каждый язык субтитров. Поток с несколькими аудиокодеками – скажем, AAC-LC ради совместимости плюс xHE-AAC для новых клиентов – это два AdaptationSet, не один, потому что один decoder pipeline не может переключаться между ними.

AdaptationSet несёт атрибуты, общие для всех Representations внутри: @contentType ("video", "audio", "text"), @mimeType ("video/mp4", "audio/mp4", "application/mp4" для fragmented MP4-субтитров), @codecs (codec-строка по RFC 6381 – "avc1.640028" для H.264 High profile level 4.0, "hev1.2.4.L150.B0" для H.265 Main10 level 5.0, "av01.0.05M.08" для AV1), @width, @height, @frameRate, @audioSamplingRate, @lang (BCP 47 – "en", "es-MX", "ja") и один или несколько дескрипторов <Role> со schemeIdUri="urn:mpeg:dash:role:2011" и значениями "main", "alternate", "caption", "subtitle", "sign", "description". Дескриптор Role – это то, как плеер понимает, что один из трёх английских AdaptationSet – это descriptive-audio для людей с нарушениями зрения, а не дубляж.

<AdaptationSet id="video" contentType="video" mimeType="video/mp4"
               codecs="avc1.640028" startWithSAP="1" segmentAlignment="true"
               maxWidth="1920" maxHeight="1080" maxFrameRate="30" par="16:9">
  <Representation id="v0" width="1920" height="1080" bandwidth="6000000" />
  <Representation id="v1" width="1280" height="720"  bandwidth="3000000" />
  <Representation id="v2" width="960"  height="540"  bandwidth="1500000" />
  <Representation id="v3" width="640"  height="360"  bandwidth="800000"  />
  <Representation id="v4" width="426"  height="240"  bandwidth="400000"  />
</AdaptationSet>

@segmentAlignment="true" – это контракт: границы сегментов выровнены во всех Representations набора. Если вы переключаетесь с v0 на v3 в конце сегмента 42, следующий сегмент 43 в обеих рендициях начинается на одном и том же wall-clock-мгновении, и плеер может склеить без зазора. Каждый современный DASH-упаковщик по умолчанию делает выровненные сегменты, потому что альтернатива – четверть секунды чёрного на каждом переключении.

Representation – одна кодированная версия

Representation – это одна кодированная версия контента AdaptationSet с фиксированным битрейтом, разрешением (для видео), частотой дискретизации (для аудио) и конфигурацией кодека. Обязательные атрибуты: @id (уникальная строка внутри AdaptationSet, используется для переключения), @bandwidth (максимальный битрейт потока в битах в секунду – плеер использует это, чтобы прикинуть, тянет ли его текущая throughput-оценка эту рендицию), @codecs (переопределяет codec из AdaptationSet, если они различаются – редкий случай), @width / @height для видео и способ адресовать Segments (об этом – в следующей секции).

Набор <Representation> внутри AdaptationSet – это то, что неформально называют bitrate ladder. Классическая лестница в стиле Apple – пять видеорендиций (240p / 360p / 540p / 720p / 1080p) с прогрессивно растущим битрейтом; современные лестницы – per-title (своя лестница на каждый тайтл по результатам анализа сложности контента) или per-shot (разные шоты внутри тайтла получают лестницы разной формы). DASH всё равно, какой философии вы придерживаетесь, – каждое Representation описывается одинаково.

Segment – скачиваемый файл

Segment – это единица медиа, которую плеер реально скачивает. Это чанк fragmented MP4 (или, редко, MPEG-TS), покрывающий отрезок презентационного времени – обычно 2–6 секунд для VOD, 1–2 секунды для live. Каждый Segment – либо целый отдельно адресуемый файл со своим URL (segments/v0/seg-42.m4s), либо byte-range внутри большего Indexed Single Segment, описанный в <SegmentBase>. Плеер делает обычный HTTP GET (или HTTP Range GET) на каждый Segment и скармливает байты в SourceBuffer браузера через Media Source Extensions.

Первый байт payload Segment – это Stream Access Point (SAP), кадр, с которого декодирование можно начать без ссылок на более ранние кадры (IDR-кадр в H.264 или его эквивалент в других кодеках). Атрибут @startWithSAP="1" на AdaptationSet обещает, что каждый Segment каждого Representation начинается с SAP, – именно это делает легальным ABR-переключение внутри сегмента.

Рис. 1. Четырёхуровневая иерархия MPD с проработанным примером: 90-минутный VOD-фильм, разбитый на pre-roll-рекламный Period и основной программный Period, видео и три аудиоязыка как AdaptationSets, пять видеорендиций и по одному Representation на язык, и выровненные 4-секундные сегменты снизу.

Адресация сегментов – три режима, которые нужно различать

DASH-манифест может адресовать свои Segments одним из трёх способов, и выбор напрямую сказывается на live-задержке, сложности упаковщика и поведении CDN. Каждая статья про DASH, которая смешивает эти три режима, неверна со второго абзаца.

Режим 1 – SegmentList

Самый простой. MPD перечисляет каждый URL Segment явно, по одному <SegmentURL> на строку, внутри <SegmentList>. Для 90-минутного фильма с 4-секундными сегментами это 1350 строк на каждое Representation, 6750 на пятирендиционную лестницу, 27 000 на фильм с четырьмя аудиоязыками. Манифест огромный, live-сценарий невозможен (пришлось бы пушить новый MPD с добавленными строками каждые 4 секунды), и только устаревшие VOD-энкодеры им ещё пользуются. Упоминаем один раз и забываем.

Режим 2 – SegmentTemplate с $Number$

Доминирующий режим для VOD и более простой из двух live-режимов. MPD объявляет URL-шаблон, содержащий литеральный токен $Number$, плюс стартовый номер и интер-сегментную длительность. Плеер строит каждый URL, подставляя в $Number$ следующее целое.

<SegmentTemplate
    media="$RepresentationID$/segment-$Number$.m4s"
    initialization="$RepresentationID$/init.mp4"
    timescale="48000"
    duration="192000"
    startNumber="1" />

Плеер видит: длительность сегмента – 192000 / 48000 = 4.0 s; сегмент 1 – по адресу v0/segment-1.m4s, сегмент 2 – v0/segment-2.m4s и так далее. Для 90-минутного фильма в манифесте нет ни одного перечисления URL – плеер их вычисляет. Для live-потока плеер добавляет номер сегмента, выведенный из (wall_clock_now − availabilityStartTime) / segment_duration, – поэтому availabilityStartTime – обязательный атрибут динамического MPD.

Шаблоны по номеру удобны для CDN: URL-пространство маленькое, URL предсказуемые, cache key тривиальный. Они нетерпимы к переменной длительности сегментов – каждый сегмент должен быть строго заявленной длительности, иначе плеер вычислит неверное wall-clock-to-segment-соответствие и начнёт запрашивать ещё не существующие сегменты. Большинство live-энкодеров решает это, жёстко прибивая длительность сегмента и принимая вытекающие ограничения по GOP-выравниванию.

Режим 3 – SegmentTemplate с $Time$ (и SegmentTimeline)

Гибкий режим. MPD объявляет шаблон с $Time$, а элемент <SegmentTimeline> перечисляет записи <S>, описывающие start time и длительность каждого сегмента в media-timescale.

<SegmentTemplate
    media="$RepresentationID$/segment-$Time$.m4s"
    initialization="$RepresentationID$/init.mp4"
    timescale="48000">
  <SegmentTimeline>
    <S t="0"      d="192000" r="2" />   <!-- 3 сегмента по 4.000 s, начиная с t=0 -->
    <S            d="240000"      />   <!-- 1 сегмент 5.000 s -->
    <S            d="192000" r="9" />   <!-- ещё 10 сегментов по 4.000 s -->
  </SegmentTimeline>
</SegmentTemplate>

У <S> три атрибута: @t (start time в media-timescale, опционален после первой записи – вычисляется как previous @t + previous @d), @d (длительность в media-timescale) и @r (счётчик повторов – r="2" означает эта запись плюс два повторения, то есть три одинаковых сегмента). r="-1" означает повторять бесконечно до следующего @t.

SegmentTimeline принимает переменные длительности сегментов – энкодер может укоротить сегмент, чтобы выровняться на ad-insertion-границу, удлинить, чтобы сохранить GOP-on-IDR-выравнивание, и манифест точно опишет и то, и другое. Цена – манифест растёт по мере жизни live-потока (каждый новый сегмент добавляет запись <S> или расширяет @r существующей), и поэтому существует MPD Patch (ISO/IEC 23009-1:2022, Annex M, развёрнуто в DASH-IF guidelines): вместо того чтобы перечитывать весь MPD каждый @minimumUpdatePeriod, плеер забирает крошечный XML-диф, добавляющий только новые записи <S>.

DASH-IF Implementation Guidelines: Restricted Timing Model рекомендует SegmentTemplate с SegmentTimeline для любого live-внедрения, которому нужно точное time-to-Segment-соответствие (catch-up DVR, точный seek, границы multi-period). SegmentTemplate с $Number$ остаётся приемлемым для VOD и для live, где каждый сегмент действительно одинаковой длительности. Чистый SegmentList приемлем только для статичного VOD и фактически устарел.

Режим адресацииЛучше всего дляПоддержка liveПеременная длительностьCDN cache keyРазмер манифеста
SegmentListLegacy VODНетДаТривиальный (URL per segment)Большой
SegmentTemplate + $Number$VOD и live с фиксированным шагомДаНетТривиальныйКрошечный
SegmentTemplate + $Time$ + SegmentTimelineDVR live, multi-period, ad-insertionДаДаТривиальныйРастёт со временем (использовать MPD Patch)
Рис. 2. Три режима адресации сегментов рядом. MPD-источник – слева; URL, которые конструирует плеер, – справа. Обратите внимание, как размер коллапсирует от SegmentList (одна строка на сегмент) до SegmentTemplate+$Number$ (одна строка-шаблон на всю презентацию).

Live vs VOD – два типа презентации

Один атрибут на корне MPD – @type – переключает протокол между двумя режимами, которые плеер реализует совершенно разной логикой.

Static (VOD)

type="static" объявляет записанный актив известной длины. MPD несёт @mediaPresentationDuration (общая длина презентации как XML-duration – "PT1H30M" для девяноста минут, "PT4500S" – та же длительность в секундах). MPD никогда не меняется. Каждый Segment существует с момента публикации MPD. Плеер может seek-нуть в любую точку таймлайна, может запросить любой Segment в любом порядке, может пред-загрузить весь поток, если хочет. Нет @availabilityStartTime, нет @minimumUpdatePeriod, нет @timeShiftBufferDepth. Субтитры, аудиодорожки и главы могут быть богаче, потому что всё финализировано.

<SegmentTimeline> или <SegmentTemplate> статичного MPD описывает всю презентацию. Плеер вычисляет полный список URL во время парсинга и может больше не общаться с manifest-сервером, пока пользователь не уйдёт со страницы.

Dynamic (live)

type="dynamic" объявляет поток, производимый в реальном времени. MPD теперь несёт @availabilityStartTime (wall-clock-момент, когда первый Segment потока стал доступен для скачивания; всё остальное в presentation-таймлайне отсчитывается от него), @minimumUpdatePeriod (как часто плеер должен перечитывать MPD), @timeShiftBufferDepth (DVR-окно – на сколько плеер может отмотать назад от live edge, прежде чем Segments выпадут из доступности) и @suggestedPresentationDelay (на сколько отступать от live edge – обычно 2–4× длительности сегмента, не меньше 4 секунд).

Соответствие wall-clock и номера сегмента – сердце live-DASH. Для манифеста на номерном шаблоне с availabilityStartTime="2026-05-21T08:00:00Z", startNumber="1", timescale="48000", duration="192000":

seconds_into_presentation = (now_utc - availabilityStartTime).total_seconds()
segment_duration_seconds  = 192000 / 48000 = 4.0
current_segment_number    = startNumber + floor(seconds_into_presentation / segment_duration_seconds)

В 08:00:40 UTC seconds_into_presentation = 40, current_segment_number = 1 + floor(40/4) = 11. Сегмент 11 только что стал доступен (его первый байт существует на 08:00:40, последний – примерно на 08:00:44 после задержки в одну длительность сегмента в энкодере). Плеер, уважающий suggestedPresentationDelay="PT4S", начал бы с сегмента 10, не 11, оставив один сегмент буфера между собой и live edge.

Динамический MPD также обычно несёт дескрипторы <UTCTiming>, чтобы плеер мог синхронизировать свои часы с серверными. Без авторитетного UTCTiming плееры на устройствах со сбитыми часами неправильно считают current_segment_number и либо ребуфферят (запрашивая несуществующие сегменты), либо отстают от live edge.

<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-iso:2014"
           value="https://time.akamai.com/?iso" />
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-head:2014"
           value="https://time.akamai.com/" />

DASH-IF guidelines настаивают, что каждый динамический MPD должен нести хотя бы один <UTCTiming>. В продакшене его отсутствие – самая частая причина ситуации «у одних пользователей live-поток подвисает, у других – нет»: часы у этих пользователей дрейфуют.

Рис. 3. Модель live-тайминга в DASH. Wall-clock now – справа; availabilityStartTime – слева. suggestedPresentationDelay – расстояние от now назад до точки, с которой стартует плеер. timeShiftBufferDepth – DVR-окно. Сегменты старше левого края timeShiftBufferDepth выпадают и становятся 404.

XML-преамбула – что объявляет каждый MPD

Каждый MPD начинается с одних и тех же четырёх объявлений, которые сообщают парсеру, какой документ он читает.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"
     type="static"
     mediaPresentationDuration="PT1H30M"
     minBufferTime="PT2S"
     profiles="urn:mpeg:dash:profile:isoff-on-demand:2011,urn:mpeg:dash:profile:cmaf:2019">

xmlns="urn:mpeg:dash:schema:mpd:2011" – namespace-URI MPD-схемы. Он не менялся с момента первой публикации ISO/IEC 23009-1 в 2012 году – пятая редакция 2022 года полностью обратно совместима на уровне XML, хотя добавила новые атрибуты и элементы. 2011 в URI – год, когда MPEG зафиксировал namespace; не путайте с годом редакции стандарта.

@profiles – список идентификаторов профилей через запятую, которым MPD заявляет соответствие. Два важнейших в 2026 году:

  • urn:mpeg:dash:profile:isoff-live:2011 – ISOBMFF (fragmented MP4) live-профиль. Обязателен для любого live.
  • urn:mpeg:dash:profile:isoff-on-demand:2011 – ISOBMFF on-demand-профиль. Обязателен для VOD.
  • urn:mpeg:dash:profile:cmaf:2019 – CMAF-профиль, ратифицированный поправкой 2019 года к DASH-стандарту. Манифест с этим профилем гарантирует, что каждый Segment соответствует ISO/IEC 23000-19 (CMAF), – именно это позволяет одним и тем же медиабайтам работать и в HLS-, и в DASH-плеере.

@minBufferTime="PT2S" – контракт между энкодером и плеером: если плеер держит буфер минимум на 2 секунды на максимальной заявленной полосе, он не ребуфферит из-за всплесков coded-content. Атрибут обязателен на MPD. DASH-IF guidelines рекомендуют minBufferTime в одну-три длительности сегмента; меньше – заставляет плеер играть на мгновенной throughput, больше – без нужды съедает latency-бюджет.

XML-синтаксис длительностей – PT1H30M, PT2S, PT0.5S – это ISO 8601. P открывает duration, T переключает с date-части на time-часть, H / M / S – часы / минуты / секунды. Десятичные секунды допустимы. Формат сбивает инженеров, привыкших к целым миллисекундам; чтение PT1.5S как «полторы секунды» быстро тренирует глаз.

ContentProtection – мульти-DRM в одном манифесте

Killer-feature DASH для премиального OTT – Common Encryption (CENC), определённое в ISO/IEC 23001-7:2023. Одни и те же зашифрованные медиабайты могут быть расшифрованы Widevine, PlayReady или FairPlay, в зависимости от того, какой key system поддерживает устройство, с единственным набором хранимых Segments и одним content-ключом. MPD сигнализирует это, складывая элементы <ContentProtection> внутри AdaptationSet – один для базовой схемы шифрования, по одному на каждую поддерживаемую DRM-систему.

<AdaptationSet contentType="video" mimeType="video/mp4" codecs="avc1.640028" segmentAlignment="true">
  <!-- Схема шифрования: AES-128 CTR, объявляется один раз -->
  <ContentProtection
      schemeIdUri="urn:mpeg:dash:mp4protection:2011"
      value="cenc"
      cenc:default_KID="34e5db32-8625-47cd-ba06-68fca0655a72"
      xmlns:cenc="urn:mpeg:cenc:2013" />
  <!-- Widevine: UUID DRM-системы — опубликованное Google значение -->
  <ContentProtection
      schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
    <cenc:pssh>AAAAW3Bzc2gAAAAA…(Base64 PSSH box)…</cenc:pssh>
  </ContentProtection>
  <!-- PlayReady: UUID DRM-системы — опубликованное Microsoft значение -->
  <ContentProtection
      schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
    <cenc:pssh>AAAAlHBzc2gAAAAA…(Base64 PSSH box)…</cenc:pssh>
    <mspr:pro xmlns:mspr="urn:microsoft:playready">…(Base64 PlayReady Object)…</mspr:pro>
  </ContentProtection>
  <Representation id="v0" width="1920" height="1080" bandwidth="6000000" />
  …
</AdaptationSet>

Первый <ContentProtection> говорит: «эти Representations зашифрованы AES-128 CTR по ISO/IEC 23001-7, и default Key Identifier – GUID 34e5db32-…». Следующие два объявляют две DRM-системы, у которых есть лицензия на этот ключ, – Widevine и PlayReady – и включают Protection System Specific Header (PSSH) каждой системы в Base64. Плеер в Chrome выбирает блок Widevine, постит PSSH на лицензионный сервер Widevine, получает ключ, расшифровывает. Плеер в Microsoft Edge выбирает блок PlayReady, постит на лицензионный сервер PlayReady. Те же Segments работают для обоих.

FairPlay – исключение: Apple не использует in-MPD PSSH-box-паттерн, и FairPlay-контент доставляется через HLS, не DASH. Премиальный OTT, обслуживающий каждое устройство, обычно отгружает DASH для Chrome / Edge / Firefox / Android / Smart TV (Widevine + PlayReady, иногда ClearKey для диагностики) и HLS для Safari и Apple TV (FairPlay). CMAF-профиль – это то, что заставляет одни и те же зашифрованные медиабайты обслуживать оба; меняется только манифест.

Официальные UUID DRM-систем публикует DASH-IF (https://dashif.org/identifiers/content_protection/). Самые распространённые:

DRM-системаUUID
Widevine (Google)edef8ba9-79d6-4ace-a3c8-27dcd51d21ed
PlayReady (Microsoft)9a04f079-9840-4286-ab92-e65be0885f95
FairPlay (Apple – только HLS)94ce86fb-07ff-4f43-adb8-93d2fa968ca2
Marlin5e629af5-38da-4063-8977-97ffbd9902d4
ClearKey (W3C, диагностика)e2719d58-a985-b3c9-781a-b030af78d30e

urn:mpeg:dash:mp4protection:2011 – это то, как MPD говорит «здесь CENC»; @value – это CENC-схема: "cenc" для AES-128 CTR (чаще всего для HEVC/AVC/AV1) или "cbcs" для AES-128 CBC sub-sample-pattern (требуется Apple FairPlay и рекомендованная схема с 2018 года).

EssentialProperty и SupplementalProperty – механизм расширения

Оба элемента имеют тип DescriptorType в схеме и оба несут @schemeIdUri плюс опциональный @value. Разница – в семантике обязательности: EssentialProperty с незнакомым @schemeIdUri обязан заставить плеер проигнорировать охватывающий элемент; SupplementalProperty с незнакомым @schemeIdUri может быть тихо проигнорирован. Этот паттерн – то, как DASH развивается, не ломая старых клиентов.

Реальные примеры EssentialProperty: сигнализация того, что Representation – часть стереоскопической пары, что AdaptationSet – это trick-mode-дорожка (превьюшки для перемотки), что Period – это SCTE-35-рекламный блок, который клиент должен передать отдельному ad-серверу. Реальные примеры SupplementalProperty: объявление override соотношения сторон, блока метаданных colour-space или схемы frame packing, которая помогает умному плееру, но не блокирует более простой.

<!-- AdaptationSet с trick-mode-превьюшками — плееры, не понимающие схемы, обязаны его пропустить -->
<AdaptationSet contentType="image" mimeType="image/jpeg">
  <EssentialProperty schemeIdUri="http://dashif.org/guidelines/trickmode" value="0"/>
  <Representation id="tm0" bandwidth="32000" width="320" height="180">
    <SegmentTemplate media="trick/$Number$.jpg" duration="20" startNumber="1" />
  </Representation>
</AdaptationSet>

CMAF, fragmented MP4 и Segment изнутри

Тело каждого современного DASH-Segment – это fragmented MP4 – формально ISO Base Media File Format, ISO/IEC 14496-12, та же семья контейнеров, что и .mp4 и .mov. Fragmented MP4 разбивает файл на последовательность movie fragments (moof-боксов), за которыми идут их payload-данные (mdat-боксы), по одному фрагменту на Segment. Бокс styp (segment type) в начале каждого Segment несёт идентификатор бренда и позволяет упаковщику пометить Segment как соответствующий конкретному профилю.

CMAF – ISO/IEC 23000-19:2024, третья редакция – это ограниченный профиль fragmented MP4, который умеют потреблять и HLS, и DASH. CMAF-Segment – это styp + sidx (segment index, для byte-range-адресации) + moof + mdat; CMAF-chunk – это один moof + один mdat внутри Segment, и именно эту единицу скачивают low-latency-DASH- и LL-HLS-плееры по chunked HTTP transfer. CMAF Initialisation Segment – это пара ftyp + moov, содержащая конфигурацию кодека (SPS/PPS для H.264, VPS/SPS/PPS для H.265, OBU sequence header для AV1) без медиаданных. Плеер качает Init Segment один раз на Representation, парсит его, конфигурирует декодер, потом качает Segments и скармливает их.

Обещание CMAF – один набор медиабайтов, два манифеста – определило индустрию. Netflix, YouTube, Disney+, Prime Video, Hulu, HBO Max отгружают одни и те же CMAF-совместимые fragmented-MP4-файлы, с двумя тонкими манифестами поверх: HLS-m3u8 для аудитории Safari и Apple TV, DASH-MPD для всех остальных. Стоимость хранения уменьшилась вдвое, стоимость кодирования – вдвое, стоимость QA – более чем вдвое. CMAF – это то, почему «HLS vs DASH» перестало быть решением об упаковке и стало решением о формате манифеста.

Проработанный пример – читаем реальный MPD строка за строкой

30-секундный кусок проработанного MPD делает четырёхуровневую иерархию конкретной. Числа ниже – настоящие; URL-фрагменты резолвились бы относительно того же origin.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     type="static"
     mediaPresentationDuration="PT1H30M"
     minBufferTime="PT2S"
     profiles="urn:mpeg:dash:profile:isoff-on-demand:2011,urn:mpeg:dash:profile:cmaf:2019">
  <BaseURL>https://cdn.example.com/movie-42/</BaseURL>
  <Period id="main" duration="PT1H30M">
    <!-- 5-рендиционная H.264 видеолестница, CMAF-чанки, 4-секундные сегменты -->
    <AdaptationSet id="video" contentType="video" mimeType="video/mp4"
                   codecs="avc1.640028" segmentAlignment="true" startWithSAP="1"
                   maxWidth="1920" maxHeight="1080" frameRate="24" par="16:9">
      <SegmentTemplate
          media="video/$RepresentationID$/seg-$Number$.m4s"
          initialization="video/$RepresentationID$/init.mp4"
          timescale="12288" duration="49152" startNumber="1" />
      <Representation id="v0" width="1920" height="1080" bandwidth="6000000" />
      <Representation id="v1" width="1280" height="720"  bandwidth="3000000" />
      <Representation id="v2" width="960"  height="540"  bandwidth="1500000" />
      <Representation id="v3" width="640"  height="360"  bandwidth="800000"  />
      <Representation id="v4" width="426"  height="240"  bandwidth="400000"  />
    </AdaptationSet>
    <!-- Английское стерео-аудио 128 kbps AAC-LC -->
    <AdaptationSet id="audio-en" contentType="audio" mimeType="audio/mp4"
                   codecs="mp4a.40.2" lang="en" audioSamplingRate="48000">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main" />
      <SegmentTemplate
          media="audio/en/seg-$Number$.m4s"
          initialization="audio/en/init.mp4"
          timescale="48000" duration="192000" startNumber="1" />
      <Representation id="a-en-0" bandwidth="128000" />
    </AdaptationSet>
    <!-- Испанское аудио -->
    <AdaptationSet id="audio-es" contentType="audio" mimeType="audio/mp4"
                   codecs="mp4a.40.2" lang="es" audioSamplingRate="48000">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="alternate" />
      <SegmentTemplate
          media="audio/es/seg-$Number$.m4s"
          initialization="audio/es/init.mp4"
          timescale="48000" duration="192000" startNumber="1" />
      <Representation id="a-es-0" bandwidth="128000" />
    </AdaptationSet>
    <!-- Английские субтитры, WebVTT в fMP4 -->
    <AdaptationSet id="subs-en" contentType="text" mimeType="application/mp4"
                   codecs="wvtt" lang="en">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle" />
      <SegmentTemplate
          media="subs/en/seg-$Number$.m4s"
          initialization="subs/en/init.mp4"
          timescale="1000" duration="4000" startNumber="1" />
      <Representation id="s-en-0" bandwidth="2000" />
    </AdaptationSet>
  </Period>
</MPD>

Математика, строка за строкой. minBufferTime="PT2S" означает, что плеер буферит минимум две секунды на верхней полосе – 6 Mbps для видео плюс 128 kbps для аудио. Буфер в байтах – минимум (6_000_000 + 128_000) × 2 / 8 = 1.53 MB. timescale="12288" на видеошаблоне и duration="49152" дают 49152 / 12288 = 4.0 s на сегмент; 90 минут – это 5400 секунд – это 1350 сегментов. Английское аудио использует timescale="48000" (audio sample rate) и duration="192000" для 192000 / 48000 = 4.0 s; границы аудио- и видеосегментов выровнены. Испанское аудио – те же параметры. Английский subtitle-track использует timescale="1000", значит длительности в миллисекундах; duration="4000" – тот же 4-секундный шаг в субтитровых единицах timescale. Плеер, выбирающий v2 (540p), a-en-0 (английское аудио) и s-en-0 (английские субтитры), делает три HTTP-запроса каждые 4 секунды в течение 90 минут – итого 1350 × 3 = 4050 segment fetch за сессию.

CDN cache key для этих запросов – только URL; ничего не зависит от query string. CDN hit rate для популярного VOD на edge – 95–99 %. Сам MPD – небольшой файл (пример выше ~2 KB; продакшен-MPD – 3–30 KB) и кешируется недолго (60 секунд типично для VOD, @minimumUpdatePeriod для live).

DASH vs HLS – сравнение, которое важно в 2026

Два протокола решают одну задачу. Различия – в углах.

ИзмерениеDASHHLS
СтандартизаторISO/IEC (open)IETF (RFC 8216 → draft-pantos-hls-rfc8216bis) + Apple Authoring Spec
Формат манифестаXML (MPD)Текст (m3u8)
Охват кодековКодек-агностиченКодек-агностичен с 2017 (изначально MPEG-TS-only)
Охват контейнеровКонтейнер-агностичен; fMP4 / CMAF доминируютИзначально MPEG-TS; CMAF с 2017
Нативная поддержка в браузерахЧерез MSE (Chrome, Edge, Firefox, Android-браузеры)iOS Safari (нативно); macOS Safari (нативно и через MSE); Android (через Exo/Shaka MSE)
Поддержка устройств AppleНе нативно; нужен Shaka/dash.js + MSE; работает в macOS Safari, не работает в iOS SafariНативно везде
Мульти-DRMНативно через CENC ContentProtection (Widevine + PlayReady + ClearKey, всё в одном MPD)Нативно FairPlay через EXT-X-KEY и HLS Authoring Spec; CENC cbcs тоже мостит HLS с Widevine и PlayReady
Low-latencyLL-DASH + DASH-IF Low-Latency Profile (CMAF chunked)LL-HLS (parts + preload hints + blocking reload)
Вставка рекламыSCTE-35 <EventStream>; multi-period SSAI зрелыйSCTE-35 EXT-X-DATERANGE; SSAI зрелый
Рост размера манифеста в liveРастёт, пока не используется MPD PatchРастёт, пока не используются playlist delta updates
Форматы субтитровWebVTT в fMP4, TTML/IMSC в fMP4, SRTWebVTT (.vtt), TTML/IMSC (с 2018)
Trick-mode (FF/RW)Нативно через EssentialProperty:trickmode и image-AdaptationSetНативно через EXT-X-IMAGE-STREAM-INF (I-frame playlists)
Reference implementationsdash.js (DASH-IF), Shaka Player (Google), ExoPlayer (Google для Android)Apple AVPlayer (нативно), hls.js (open), Shaka Player (multi-protocol)
Крупнейшие внедренияNetflix, YouTube, Disney+, Prime Video, Hulu, TwitchApple TV+, каждое iOS-приложение, live в экосистеме Apple

В 2026 практический ответ на «DASH или HLS» – оба, с одним набором CMAF-Segments снизу. Два манифеста дешёвые; медиа – то, что стоит денег. Bitmovin Video Developer Report 2025/26 сообщает, что 64 % опрошенных разработчиков отгружают DASH и 82 % – HLS, и пересечение – это сервисы, отгружающие оба с одного CMAF-origin.

Low-Latency DASH – краткий набросок (полный разбор в 4.5)

DASH-IF Low-Latency Profile (формализованный в DASH-IF IOP v4.3 и Restricted Timing Model) достигает 2–4 секунды glass-to-glass поверх обычного HTTP/1.1 chunked transfer. Четыре механизма:

  1. CMAF-чанки (меньше Segments – типично 200 ms) становятся единицей, которую плеер скачивает по HTTP/1.1 chunked transfer.
  2. Элемент <ServiceDescription> в MPD объявляет target latency и диапазон playback rate, к которому плеер может подкручивать скорость, чтобы удерживать таргет.
  3. availabilityTimeOffset на <SegmentTemplate> сообщает, что Segments становятся доступны availabilityTimeOffset секунд раньше, чем подсказывает строгая формула availabilityStartTime + segmentNumber × segmentDuration, – потому что энкодер публикует чанки по мере их производства, не дожидаясь закрытия сегмента.
  4. MPD Patch держит манифест маленьким через часы стрима.

Результат сопоставим с LL-HLS – 2–5 секунд glass-to-glass – на протоколе, который работает на любом HTTP/1.1-CDN. LL-DASH получит свой глубокий разбор в статье 4.5.

Типичные ошибки

Краткая экскурсия по failure modes, через которые Фора Софт прошла в продакшене.

Нет <UTCTiming> или он неправильный. Динамический MPD без UTC-источника просит плеер пользоваться локальными часами. Локальные часы на Smart TV и embedded-устройствах дрейфуют на десятки секунд. Плееры считают current_segment_number неверно, запрашивают несуществующие сегменты, фоллбекаются, повторяют – и получается прерывистый ребуферинг, который QA не воспроизводит на чистом лабораторном устройстве. Фикс: добавить минимум один <UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-iso:2014" value="…"> в каждый динамический MPD, направить на надёжный HTTP-time-источник (Akamai, Cloudflare, ваш собственный ntpd-фронт).

Расхождение между реальной длительностью сегмента и @duration на шаблоне. Пирамида боли: упаковщик, настроенный на 4-секундные сегменты, на практике производит сегменты по 3.997 s (потому что GOP закрывается на 3 кадра раньше, чтобы выровняться на следующий IDR). MPD при этом всё ещё объявляет duration="49152" (4.0 s). Через 10 минут live плеер и энкодер расходятся на ~600 ms; через час – на ~3.6 s; плеер начинает запрашивать ещё не существующие сегменты. Фикс: либо прибить энкодер к строго GOP-выровненным 4-секундным сегментам (closed-gop + gop-size = framerate × duration), либо переключить манифест на SegmentTemplate с SegmentTimeline, где описывается реальная длительность каждого сегмента.

ContentProtection в неправильной области видимости. <ContentProtection> легален внутри <AdaptationSet> (применяется ко всем Representations набора) или внутри <Representation> (override для одного). Положить его внутрь <Period> – частая ошибка copy-paste; одни лояльные плееры это принимают, другие молча отвергают. Фикс: держать ContentProtection на уровне AdaptationSet для типичного случая (одно шифрование применяется ко всей лестнице) и опускать на уровень Representation только тогда, когда у отдельных рендиций разное шифрование.

@startWithSAP не равен 1. Некоторые энкодеры выдают Segments, начинающиеся с P-кадра вместо IDR. MPD-шный @startWithSAP="1" лжёт, и плеер либо ребуферится на каждом ABR-переключении, либо – что хуже – выдаёт блочное декодированное видео на границе. Фикс: валидировать кодированный выход CMAF-conformance-инструментом (shaka-packager, Bento4 mp4info); отвергать любой Segment, не начинающийся с SAP type 1.

Смешивание $Number$ и $Time$ шаблонов в одном AdaptationSet. Спецификация это допускает; ни один плеер с этим хорошо не справляется. Фикс: один режим адресации на AdaptationSet – и держаться его.

Ротация init.mp4 на границе Period без сигнализации <SupplementalProperty> continuity. Если два соседних Period используют разные Init Segments (разная конфигурация кодека), часть плееров считает границу фатальной ошибкой и останавливается. Фикс: сигнализировать Period continuity через <SupplementalProperty schemeIdUri="urn:mpeg:dash:period-continuity:2015">, когда следующий Period продолжает дорожку предыдущего на той же конфигурации кодека; плеер тогда знает, что decoder pipeline можно не пересобирать.

MPD слишком большой, чтобы перечитывать его каждый @minimumUpdatePeriod. Live-MPD на SegmentTimeline, растущий часами, может занять сотни килобайт. Перечитывание его каждые 2 секунды жжёт трафик и CPU на каждом подключённом плеере. Фикс: включить MPD Patch в упаковщике (Unified Streaming, AWS MediaPackage, Wowza, Bitmovin Live Server – все поддерживают). Плеер тогда забирает ~/manifest_patch.mpd (несколько сотен байт) на каждом refresh и сливает его в свой in-memory MPD.

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

Фора Софт отгружает DASH-стриминг для видеоконференций, OTT, e-learning, телемедицины, видеонаблюдения и AR/VR с тех пор, когда протокол был ещё Working Draft. Мы обычно разворачиваем DASH-over-CMAF с Common Encryption как основной стек доставки для любого клиента вне экосистемы Apple, с параллельным HLS-манифестом поверх тех же CMAF-Segments – для iOS Safari. Наши инженеры знают, какой продакшен-рычаг дёрнуть, когда MPD не грузится на Smart TV Tizen, когда LL-DASH-поток подвисает за hyperscaler-load-balancer или когда мульти-DRM-ротация должна выкатиться, не сломав уже работающую базу Widevine-клиентов. Технология – половина работы; операционная дисциплина запуска её на сорока классах устройств – остальное. Если в вашем roadmap есть и то и другое – поговорите с нами раньше, чем фиксировать стек.

Ключевые тезисы

  • MPEG-DASH = ISO/IEC 23009-1; текущая редакция – пятая, 2022.
  • MPD – это четырёхуровневое дерево: Презентация → Period → AdaptationSet → Representation → Segment.
  • Три режима адресации сегментов: SegmentList (legacy), SegmentTemplate+$Number$ (default VOD), SegmentTemplate+$Time$+SegmentTimeline (variable-duration / DVR / ad-aware live).
  • Live требует availabilityStartTime, minimumUpdatePeriod, timeShiftBufferDepth, suggestedPresentationDelay и минимум одного <UTCTiming>.
  • Мульти-DRM через Common Encryption (CENC, ISO/IEC 23001-7) – killer-feature DASH: один набор медиа, Widevine + PlayReady + ClearKey в одном MPD.
  • CMAF-профиль (urn:mpeg:dash:profile:cmaf:2019) позволяет одним и тем же медиабайтам обслуживать и DASH, и HLS.

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

CTA

  • Поговорить с инженером по стримингу – забронируйте 30-минутный скоупинг-колл и пройдёмся по вашей архитектуре DASH или гибридной DASH/HLS.
  • Посмотреть наши кейсы – OTT, телемедицина и e-learning продукты, которые Фора Софт отгрузила на DASH-стеке доставки.
  • Скачать DASH MPD anatomy cheat sheet (PDF) – одностраничный справочник по каждому элементу, атрибуту и значению MPD, которые реально встречаются в продакшене.

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

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