LL-DASH и low-latency CMAF: chunked encoding на практике

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

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

Последняя сверка: 2026-05-21 со стандартом ISO/IEC 23009-1:2022 (MPEG-DASH, пятая редакция, август 2022), ISO/IEC 23000-19:2024 (CMAF, третья редакция), DASH-IF Implementation Guidelines: Low-Latency Modes for DASH v1.2 (2024), DASH-IF Implementation Guidelines: Restricted Timing Model (post-community-review, 2025), IETF RFC 9112 (HTTP/1.1, июнь 2022), IETF RFC 9114 (HTTP/3, июнь 2022), исходниками референсного плеера dash.js v5 и Bitmovin Video Developer Report 2025/26.

TL;DR

Low-Latency DASH – LL-DASH – это профиль MPEG-DASH, который сочетает chunked-encoded CMAF-сегменты с HTTP chunked transfer, чтобы опустить glass-to-glass-задержку с двадцати-тридцати секунд обычного DASH до двух-четырёх секунд. Фокус в том, что наименьшая единица, которую может получить плеер, – это уже не полный сегмент, а отдельный CMAF chunk: 200–500-миллисекундный кусок сегмента, который packager публикует в тот момент, когда энкодер его произвёл, и который origin отдаёт плееру кадр за кадром через открытый HTTP-ответ, не закрывая соединение до конца сегмента. Четыре сигнала в MPD делают этот трюк понятным плееру: @availabilityTimeOffset объявляет, на сколько раньше завершения сегмента chunk становится доступен для скачивания; @availabilityTimeComplete="false" объявляет, что chunks производятся прогрессивно, а не как один атомарный файл; <ServiceDescription> несёт целевую задержку; а profile URN DASH-IF low-latency-профиля (urn:mpeg:dash:profile:cmaf-extended:2018) объявляет, что весь low-latency-контракт в силе. В сочетании с HTTP/1.1 chunked transfer encoding, HTTP/2 stream framing или HTTP/3 stream framing – и с плеером, который делает ABR на частично прибывших chunk’ах, а не на завершённых сегментах – LL-DASH достигает той же полосы 2–4 секунды, что и LL-HLS, на не-Apple-половине браузеров и устройств. Production-стек 2026 года – это один набор CMAF-chunk’ов, отдаваемый как LL-HLS для iOS / Safari и как LL-DASH для Chrome / Edge / Firefox / Android из одного origin.

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

Если вы строите что-либо, что стримит live-видео в Chromium-браузер, на Android-устройство, на Smart TV (кроме Apple TV) или на любое из полумиллиарда устройств с ExoPlayer или Shaka Player, то LL-DASH – это то, что вы фактически отгружаете для низкой задержки. LL-HLS на слуху, потому что Apple владеет инфоповодом и потому что каждая статья «как сделать low latency» начинается с iOS, – но реальность развёртывания в 2026 году такова: LL-HLS покрывает экосистему Apple, LL-DASH покрывает всё остальное, а CMAF даёт им общий набор файлов под капотом. Механика тонкая – chunked transfer и CMAF chunk это не одно и то же; @availabilityTimeOffset не то же самое, что @suggestedPresentationDelay; ABR-алгоритм в плеере должен уметь оценивать throughput на полу-прибывшем chunk’е – а production-режимы отказа всегда находятся на стыках. Цель статьи – сделать каждую строчку LL-DASH MPD читаемой, сделать каждый узел chunked-CMAF-цепочки видимым, и сделать компромиссы между LL-DASH, LL-HLS и WebRTC чем-то, о чём вы можете спорить цифрами.

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

LL-DASH – это обычный MPEG-DASH с тремя изменениями. Первое: энкодер производит CMAF-chunk’и (200–500-миллисекундные куски сегмента) вместо ожидания, чтобы опубликовать полный 2–6-секундный сегмент. Второе: origin отдаёт эти chunk’и плееру через HTTP chunked transfer encoding (HTTP/1.1) или stream framing (HTTP/2, HTTP/3) одним ответом, который остаётся открытым, пока сегмент ещё производится; плеер потребляет байты по мере прибытия, не дожидаясь закрытия ответа. Третье: MPD несёт четыре сигнала – profile URN cmaf-extended, @availabilityTimeOffset на каждом SegmentTemplate, @availabilityTimeComplete="false" и блок <ServiceDescription> с целевой задержкой, – которые вместе говорят плееру: «ты смотришь low-latency-поток; вот как планировать запросы, вот где live edge, вот сколько буферизовать». DASH-IF-совместимый low-latency-плеер (dash.js с v3.0, Shaka Player с v3.2, ExoPlayer с 2.16, Bitmovin Player с 8.41, THEOplayer с 4.0) читает эти сигналы, планирует первый запрос так, чтобы он попал в момент появления следующего chunk’а, запускает ABR-алгоритм на bytes-per-second, а не chunks-per-second, и держится целевой задержки, заявленной в MPD. Чистый эффект – пол задержки 2–4 секунды на тех же CDN-экономике, формате файлов, packager’ах и DRM-инфраструктуре, что у обычного DASH: ни нового серверного ПО, ни нового ingest-протокола, ни нового плеерного движка – только новые сигналы в манифесте и новый тюнинг ABR.

История в трёх вехах

Low-latency DASH старше LL-HLS, и это стандарт, из которого дизайнеры LL-HLS явно черпали идеи. Три вехи определяют, как мы оказались в 2026 году.

Первая – публикация в 2017 году ISO/IEC 23000-19Common Media Application Format (CMAF). CMAF определяет fragmented-MP4-упаковку, в которой явно поддерживается chunk – фрагмент-фрагмента – как наименьшая адресуемая медиа-единица. CMAF chunk – это один или несколько кадров, упакованных внутри пары moof + mdat, которые можно разобрать и декодировать независимо от остального родительского сегмента. Стандарт зафиксировал то, что некоторые энкодеры уже начали делать в 2016 году (Akamai-предложения «low-latency HLS over CMAF» ещё до Apple-LL-HLS), и дал HLS и DASH общий строительный блок.

Вторая – публикация в 2019 году DASH-IF Low-Latency Modes for DASH на community review, формализованного как DASH-IF Implementation Guidelines в 2020 году и пересмотренного до v1.2 в 2024 году. Этот документ – фактическая спецификация LL-DASH, потому что ISO/IEC 23009-1 определяет только сигналы на уровне манифеста; DASH-IF определяет, как их собрать в согласованный low-latency-профиль и как должен вести себя плеер. Релиз 2020 года совпал с релизом dash.js v3.0, который отгрузил первую референсную реализацию low-latency-режима, и с первым коммерческим LL-DASH-поведением origin от Akamai.

Третья – DASH-IF Restricted Timing Model 2025 года, закрывшая последний крупный interop-разрыв в LL-DASH: как @availabilityTimeOffset, @suggestedPresentationDelay и оценка clock-skew в плеере вместе формируют стабильную целевую wall-clock-задержку у разнородных зрителей. Документ 2025 года определяет детерминированный алгоритм подстройки playback-rate в плеере (тот самый ±5%-нудж, который абсорбирует clock skew без слышимого audio-pitch-shift), и сейчас это эталон для каждого production LL-DASH-развёртывания.

В 2026 году production-стек зрелый. Сигналы MPD общеизвестны, dash.js и Shaka spec-совместимы, основные packager’ы (Akamai LL-CMAF, AWS MediaPackage v2, Bitmovin Live, Mux Live, Norsk, Unified Streaming, Wowza, Shaka Packager) корректно эмитят chunked-CMAF из коробки, и основные CDN (Akamai, Cloudflare, Fastly, CloudFront) поддерживают конфигурацию cache-key, нужную LL-DASH.

Бюджет задержки – до и после

Самый чистый способ понять, что делает LL-DASH, – это положить бюджет задержки рядом с обычным DASH.

У типичного plain-DASH live-потока те же три контрибьютора, что у plain HLS, в чуть других единицах. Энкодер производит сегменты по 2–6 секунд; для канонического четырёхсекундного сегмента четыре секунды реального времени проходят до того, как первый байт сегмента N доходит до origin. Segment availability-задержка – это одно сегментное время ожидания между тем, как энкодер закончил сегмент, и тем, как плеер узнал, что сегмент доступен, потому что плеер опрашивает MPD по расписанию, заданному @minimumUpdatePeriod. Playback buffer традиционно глубиной в три сегмента, чтобы плеер абсорбировал джиттер, что добавляет двенадцать секунд. Прибавьте около секунды HTTP-оверхеда – получите двадцать одну секунду glass-to-glass. Production-развёртывания измеряют восемнадцать-тридцать секунд.

LL-DASH переписывает каждую строку.

Контрибьютор задержкиPlain DASH (4 с сегменты)LL-DASH (4 с сегменты, 333 мс chunks)
Энкодер4.0 с (полный сегмент)0.33 с (один chunk)
Segment availability2.0 с (половина @minimumUpdatePeriod)0.0 с (template-адресация; перезагрузка MPD не нужна)
Playback buffer12.0 с (3 сегмента)1.0–2.0 с (3–6 chunks)
HTTP-оверхед1.0 с0.4 с (один открытый ответ)
Итого glass-to-glass~19 с~1.8–2.8 с

Энкодерная строка падает с четырёх секунд до трети секунды, потому что packager публикует chunk сразу, как только энкодер его произвёл, а не после полного сегмента. Строка segment availability падает до нуля, потому что LL-DASH использует template-адресацию (SegmentTemplate$Number$ или $Time$): плеер вычисляет URL сегмента N+1 без выкачивания нового MPD и просит его в тот момент, когда @availabilityTimeOffset говорит, что он должен стать доступен. Playback buffer сжимается с трёх сегментов до трёх-шести chunks, потому что наименьшая единица, которую может буферизовать плеер, теперь chunk, а не сегмент. HTTP-оверхед падает вдвое, потому что плеер держит chunked-transfer-ответ открытым на весь сегмент вместо одного запроса на сегмент.

Рисунок 1. Бюджет задержки glass-to-glass для plain DASH (верхняя полоса) и LL-DASH (нижняя полоса). Каждый сегмент подписан своим контрибьютором; LL-DASH сжимает каждую строку примерно на порядок.

Четыре механизма по одному

LL-DASH – это одна новая идея упаковки (CMAF chunk), одна новая идея транспорта (chunked transfer) и два новых сигнала в манифесте (@availabilityTimeOffset и <ServiceDescription>). Все четыре части дают полную пользу только вместе. Пропусти любую – получишь более медленную версию LL-DASH, которая всё ещё называется LL-DASH в конфигурационном файле.

Механизм 1 – CMAF chunk

CMAF chunk – наименьшая независимо парсимая единица, на которую можно разрезать CMAF-сегмент. Это одна пара moof + mdat, содержащая один или несколько видео- или аудио-кадров, упакованная внутри того же fragmented-MP4-обёрта, что и родительский сегмент. В четырёхсекундном сегменте с 333-мс chunk’ами файл сегмента содержит двенадцать пар moof/mdat подряд. CMAF chunk соотносится с CMAF-сегментом так же, как #EXT-X-PART с HLS-сегментом: та же единица на уровне провода, под другим именем.

Для плеера важны два свойства. Первое: chunk можно разобрать и декодировать в момент, когда его байты прибыли, не дожидаясь окончания сегмента. Бокс moof в начале chunk’а несёт sample timing и offsets; mdat несёт кадры; SourceBuffer.appendBuffer() плеера принимает их как валидный фрагмент. Второе: только некоторые chunk’и независимы – те, что начинаются с I-кадра (IDR-сэмпла в терминах H.264 / HEVC / AV1). Не-независимый chunk (P- или B-кадры) можно декодировать только после chunk’ов, которые ему предшествовали. DASH-IF guidelines требуют как минимум одного независимого chunk’а на сегмент; production-развёртывания делают один такой в секунду реального времени, чтобы дать плееру частые ABR-возможности.

Длительность chunk’а – главный регулятор задержки. 200-мс chunk сдвигает пол задержки к 1.8 с ценой более частого moof-оверхеда и более агрессивного ABR-каданса. 500-мс chunk поднимает пол до 2.5 с и снижает packaging-оверхед. DASH-IF рекомендует 200–500 мс; 333 мс – production-дефолт 2026 года, потому что он ровно ложится на 30 fps (10 кадров на chunk) и 60 fps (20 кадров на chunk).

Арифметика прямая. Четырёхсекундный сегмент с 333-мс chunk’ами – это двенадцать chunks. Пять-рендиционный multivariant-поток даёт шестьдесят событий публикации chunk’а на сегмент, но плеер не делает шестьдесят запросов: он делает один запрос на сегмент и потребляет все двенадцать chunks через один открытый chunked-transfer-ответ. Нагрузка на провод для плеера такая же, как у plain DASH; нагрузка на packager и origin растёт, потому что им обоим приходится производить и стримить контент в двенадцать раз чаще.

Механизм 2 – HTTP chunked transfer

Механика на уровне провода, которая заставляет LL-DASH работать, – это HTTP chunked transfer encoding (HTTP/1.1, RFC 9112 §7.1) и его эквиваленты в HTTP/2 и HTTP/3. С chunked transfer сервер может начать слать тело ответа до того, как узнает его общую длину; он шлёт каждый чанк по мере доступности, терминируя его длиной в hex, и сигнализирует конец тела нулевой длиной.

Для LL-DASH запрос выглядит обычно:

GET /video/720p/segment-42.m4s HTTP/1.1
Host: edge.example.com

Ответ, однако, необычный: он начинает стримить ещё до того, как сегмент 42 полностью произведён.

HTTP/1.1 200 OK
Content-Type: video/mp4
Transfer-Encoding: chunked

10F0                              <- длина первого CMAF chunk в hex
[moof + mdat chunk 1, ~4 КБ]
10C5                              <- длина второго CMAF chunk
[moof + mdat chunk 2, ~4.3 КБ]
…
0                                 <- chunk нулевой длины: конец тела

Сервер продолжает писать по мере производства энкодером; плеер потребляет байты по мере прибытия. Ключевое свойство: TCP-receive-buffer плеера (или QUIC-stream) ни разу не опустошается между chunk’ами после начала ответа – нет request-response round-trip на каждый CMAF chunk, нет TLS-handshake на chunk, нет DNS-резолва на chunk, нет CDN cache-lookup на chunk. Один запрос открывает один ответ, который длится длительность одного сегмента, и байты текут через него по мере появления.

В HTTP/2 и HTTP/3 семантика идентична, а проводной формат чуть другой: сервер открывает stream (DATA-фреймы) вместо Transfer-Encoding: chunked и пишет фреймы по мере производства chunk’ов энкодером. И HTTP/2, и HTTP/3 мультиплексируют несколько segment-запросов через одно соединение, что чистая победа, когда плеер тянет видео, аудио и субтитры параллельно; HTTP/3 дополнительно избегает head-of-line-блокировки на уровне QUIC, что важно на потерянных сетях, но редко на проводных или 5G-соединениях, где смотрят большинство low-latency-контента. Развёрнутый микс 2026 года – примерно 60% HTTP/1.1 chunked transfer, 25% HTTP/2, 15% HTTP/3, причём HTTP/3 растёт быстрее всех, потому что тот же QUIC-стек лежит под MoQ.

Важнее всего усвоить, что chunked transfer и CMAF chunk – независимые идеи, у которых случайно общее слово. Chunked transfer – это механизм HTTP-уровня для стриминга тела ответа. CMAF chunk – единица медиа-уровня. LL-DASH использует обе, и термин «chunk» в одном или другом смысле определяется контекстом.

Механизм 3 – `@availabilityTimeOffset` и `@availabilityTimeComplete`

MPD должен сказать плееру две вещи, которых не говорит обычный DASH: насколько раньше завершения сегмента chunk становится доступен для скачивания, и что сегменты производятся прогрессивно, а не как атомарные файлы. Два атрибута на SegmentTemplate (или SegmentBase) несут этот контракт.

@availabilityTimeOffset – десятичное число секунд. Он объявляет, на сколько секунд раньше номинального segment-availability time первый байт сегмента становится доступен с origin. Для четырёхсекундного сегмента с 333-мс chunk’ами @availabilityTimeOffset="3.667" говорит «ты можешь начать тянуть сегмент N в момент, когда энкодер закончил первый chunk – это 3.667 секунды раньше номинального segment-N-complete time».

@availabilityTimeComplete="false" объявляет, что сегмент не атомарно доступен в объявленный момент – он формируется поверх @availabilityTimeOffset секунд. Плеер, который не понимает @availabilityTimeComplete, воспринимает сегмент как обычный файл и ждёт, пока тот будет готов; LL-DASH-совместимый плеер планирует запрос на early-availability-time и потребляет chunked-transfer-ответ.

В MPD это выглядит так:

<AdaptationSet mimeType="video/mp4" segmentAlignment="true" startWithSAP="1">
  <SegmentTemplate
      timescale="90000"
      duration="360000"
      media="$RepresentationID$/segment-$Number$.m4s"
      initialization="$RepresentationID$/init.mp4"
      startNumber="1"
      availabilityTimeOffset="3.667"
      availabilityTimeComplete="false"/>
  <Representation id="720p" bandwidth="2500000" width="1280" height="720" codecs="avc1.4d401f"/>
  <Representation id="1080p" bandwidth="5000000" width="1920" height="1080" codecs="avc1.640028"/>
</AdaptationSet>

Четырёхсекундная длительность сегмента – duration / timescale = 360000 / 90000 = 4.0 с. 333-мс chunk даёт @availabilityTimeOffset = 4.0 - 0.333 = 3.667, потому что первый chunk становится доступен через 333 мс после начала кодирования сегмента. Плеер вычисляет availability-time сегмента N как availabilityStartTime + N × segmentDuration - @availabilityTimeOffset и шлёт запрос в момент, когда его часы пересекут это время.

Тонкое, но важное следствие: значение @availabilityTimeOffset отсчитывается относительно длительности сегмента, а не длительности chunk’а. Это offset, на котором первый байт становится доступен, а не последний. Сервер обязан соблюдать контракт – если он рекламирует @availabilityTimeOffset="3.667", а плеер запрашивает в этот точный момент, ответ обязан начать стримить немедленно. Серверы, которые рекламируют offset, но фактически ждут завершения сегмента перед ответом, – самая частая мисконфигурация LL-DASH; в MPD они выглядят compliant, а на проводе ведут себя как plain DASH.

Механизм 4 – `<ServiceDescription>` и целевая задержка

MPD объявляет желаемую playback-задержку в блоке <ServiceDescription> в начале документа. Блок несёт три куска информации: целевую задержку в миллисекундах, допустимый диапазон playback-rate (±5%-нудж, которым плеер абсорбирует clock skew), и опциональный диапазон min/max-quality.

<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     profiles="urn:mpeg:dash:profile:isoff-live:2011,urn:mpeg:dash:profile:cmaf-extended:2018"
     type="dynamic"
     availabilityStartTime="2026-05-21T08:00:00Z"
     minimumUpdatePeriod="PT4S"
     timeShiftBufferDepth="PT60S"
     minBufferTime="PT2S"
     suggestedPresentationDelay="PT3S">

  <ServiceDescription id="0">
    <Latency target="3000" max="6000" min="2000" referenceId="0"/>
    <PlaybackRate min="0.95" max="1.05"/>
  </ServiceDescription>

  <Period id="p0" start="PT0S">
    <AdaptationSet ...>...</AdaptationSet>
  </Period>
</MPD>

<Latency target="3000" max="6000" min="2000"/> – целевая glass-to-glass-задержка, которую продюсер хочет, чтобы плеер держал: в этом MPD три секунды, с жёстким полом в две секунды (ниже плеер должен ребуферить, а не рендерить) и мягким потолком в шесть секунд (выше плеер должен ускоряться, чтобы догнать). <PlaybackRate min="0.95" max="1.05"/> говорит, что плеер может нуджить playback-скорость между 95% и 105%, чтобы догнать целевую задержку, что достаточно мало, чтобы быть неслышимым.

suggestedPresentationDelay="PT3S" – legacy-атрибут DASH, делающий часть той же работы. DASH-IF Restricted Timing Model (2025) определяет, как именно плеер должен комбинировать @suggestedPresentationDelay, <Latency target=""> и фактически измеренную задержку, чтобы вычислить подстройку playback-rate; в production для LL-DASH-совместимых плееров выигрывает блок <ServiceDescription>, а @suggestedPresentationDelay – fallback для старых плееров, не понимающих новый сигнал.

Profile URN cmaf-extended:2018 – указанный рядом с isoff-live:2011 в атрибуте @profiles манифеста – это явное объявление, что весь low-latency-контракт в силе. Плеер, который видит URN и сконфигурирован в low-latency-режим, читает @availabilityTimeOffset, @availabilityTimeComplete и <ServiceDescription> и переключает своё расписание и ABR-алгоритм соответственно.

Рисунок 2. Цикл chunked-transfer в LL-DASH. Плеер просит сегмент 42 один раз в момент, когда `@availabilityTimeOffset` говорит, что его первый байт доступен; сервер стримит CMAF-chunk’и обратно через один открытый ответ по мере производства энкодером; ответ закрывается, когда сегмент 42 заканчивается. Никаких long-poll, никаких per-chunk-запросов.

Live LL-DASH MPD построчно

Вот реалистичный LL-DASH MPD для четырёхсекундного-сегментного, 333-мс-chunk’ового live-потока с тремя видеорендициями и одной аудиорендицией.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     profiles="urn:mpeg:dash:profile:isoff-live:2011,urn:mpeg:dash:profile:cmaf-extended:2018"
     type="dynamic"
     availabilityStartTime="2026-05-21T08:00:00Z"
     publishTime="2026-05-21T08:03:14Z"
     minimumUpdatePeriod="PT4S"
     timeShiftBufferDepth="PT60S"
     minBufferTime="PT2S"
     suggestedPresentationDelay="PT3S">

  <ServiceDescription id="0">
    <Latency target="3000" max="6000" min="2000" referenceId="0"/>
    <PlaybackRate min="0.95" max="1.05"/>
  </ServiceDescription>

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

  <Period id="p0" start="PT0S">

    <AdaptationSet contentType="video" mimeType="video/mp4"
                   segmentAlignment="true" startWithSAP="1">
      <SegmentTemplate
          timescale="90000"
          duration="360000"
          media="$RepresentationID$/seg-$Number$.m4s"
          initialization="$RepresentationID$/init.mp4"
          startNumber="1"
          availabilityTimeOffset="3.667"
          availabilityTimeComplete="false"/>
      <Representation id="360p"  bandwidth="800000"  width="640"  height="360"  codecs="avc1.4d401e" frameRate="30"/>
      <Representation id="720p"  bandwidth="2500000" width="1280" height="720"  codecs="avc1.4d401f" frameRate="30"/>
      <Representation id="1080p" bandwidth="5000000" width="1920" height="1080" codecs="avc1.640028" frameRate="30"/>
    </AdaptationSet>

    <AdaptationSet contentType="audio" mimeType="audio/mp4" lang="en"
                   segmentAlignment="true" startWithSAP="1">
      <SegmentTemplate
          timescale="48000"
          duration="192000"
          media="$RepresentationID$/seg-$Number$.m4s"
          initialization="$RepresentationID$/init.mp4"
          startNumber="1"
          availabilityTimeOffset="3.667"
          availabilityTimeComplete="false"/>
      <Representation id="aac128" bandwidth="128000" codecs="mp4a.40.2" audioSamplingRate="48000"/>
    </AdaptationSet>

  </Period>
</MPD>

Читаем сверху. Элемент <MPD> рекламирует два профиля: isoff-live:2011 (базовый live-профиль) и cmaf-extended:2018 (low-latency-профиль URN). type="dynamic" объявляет, что это live-поток. availabilityStartTime – wall-clock-якорь для арифметики segment availability. publishTime – момент генерации этого MPD сервером; плеер может сравнить его с текущим временем, чтобы детектить устаревший манифест. minimumUpdatePeriod="PT4S" говорит плееру перечитывать MPD каждые четыре секунды – хотя в установившемся low-latency-режиме template-адресация сегментов означает, что новый MPD редко нужен. suggestedPresentationDelay="PT3S" – legacy-таргет трёх секунд от live edge.

<ServiceDescription> несёт современные low-latency-сигналы: target 3 секунды, hard floor 2 секунды, soft ceiling 6 секунд, диапазон playback-rate 0.95–1.05.

<UTCTiming> указывает плееру на авторитетный источник времени – в этом примере Akamai well-known time service. Low-latency-плеер не может полагаться на часы клиентского устройства, потому что потребительские устройства дрейфуют на десятки миллисекунд, а live-edge-расписание зависит от sub-100-ms-точных wall-clock; плеер забирает time service на старте, вычисляет offset между serverверным и клиентским временем, и использует этот offset для каждого вычисления segment-availability. Restricted Timing Model 2025 года стандартизировала это; production LL-DASH-развёртывания без <UTCTiming> сломаны на большинстве клиентов.

Видео <AdaptationSet> несёт один <SegmentTemplate>, общий для всех трёх рендиций. timescale="90000" и duration="360000" дают 4-секундный сегмент (360000 / 90000 = 4.0). media="$RepresentationID$/seg-$Number$.m4s" – URL-шаблон: сегмент 42 рендиции 720p живёт по 720p/seg-42.m4s. startNumber="1" говорит, что сегменты нумеруются с 1. availabilityTimeOffset="3.667" объявляет, что первый байт каждого сегмента доступен на 3.667 секунды раньше его номинального availability-time – то есть в момент, когда энкодер произвёл первый chunk. availabilityTimeComplete="false" объявляет, что сегмент производится прогрессивно.

Каждый <Representation> объявляет рендицию с фиксированным битрейтом, разрешением, кодеком и framerate. Плеер выбирает одну Representation на AdaptationSet в любой момент и может переключаться между ними на границах chunk’ов (при условии независимости chunk’а).

Аудио <AdaptationSet> зеркалит видео, с одной Representation (128 кбит/с AAC LC на 48 кГц).

Когда сегмент 42 производится, плеер вычисляет:

  • segment-42-availability-time = availabilityStartTime + 42 × 4.0 - 3.667 = 08:02:48.333 UTC
  • Скорректированные wall-clock плеера пересекают 08:02:48.333.
  • Плеер шлёт GET /720p/seg-42.m4s на origin.
  • Origin сразу начинает стримить chunked-transfer-ответ.
  • Первый CMAF chunk прибывает плееру примерно через 80 мс (сетевой RTT + origin TTFB).
  • Плеер декодирует chunk и рендерит первый кадр через ~20 мс.
  • Ещё одиннадцать chunks прибывают за следующие 3.667 секунды, по одному каждые 333 мс.
  • В 08:02:52.000 ответ закрывается, а сегмент 43 уже 333 мс в производстве; плеер шлёт GET /720p/seg-43.m4s и цикл продолжается.

Glass-to-glass-задержка в этом цикле примерно:

  • 0.333 с энкодер (один chunk-pipeline)
  • 0.080 с сетевой RTT + origin TTFB
  • 0.020 с decoder pre-roll
  • 1.5 с playback buffer (target latency 3.0 с; suggestedPresentationDelay 3.0 с; буфер глубиной 4.5 chunk’а)
  • ≈ 2.0 с glass-to-glass на рендиции 720p в типичных условиях фиксированного broadband.
Рисунок 3. Тот же MPD, с аннотацией каждого low-latency-специфичного элемента. Подсвечены: профиль `cmaf-extended`, `<ServiceDescription>`, `<UTCTiming>`, `@availabilityTimeOffset` и `@availabilityTimeComplete`.

ABR на chunked-transfer-ответе

ABR-переключение – это место, где LL-DASH-плееры наиболее заметно отличаются от plain-DASH-плееров. Plain-DASH ABR простой: скачать сегмент N, разделить размер сегмента на время скачивания – получить throughput, выбрать самую высокую рендицию, чья bandwidth ниже throughput минус safety, переключиться на следующей границе сегмента. С четырёхсекундными сегментами и одной секундой safety плеер имеет хорошее ABR-разрешение: каждые четыре секунды свежий замер throughput и чистая возможность для переключения.

LL-DASH ломает обе половины. Плеер больше не ждёт завершения сегмента, чтобы вычислить throughput – к моменту завершения сегмента N плеер уже в середине декода и уже на десять секунд опаздывает с решением о переключении. Поэтому плеер измеряет throughput на частичных ответах: байты, полученные за последние 500 мс или 1 секунду, делённые на затраченное время, дают непрерывно-обновляемую оценку throughput. DASH-IF Low-Latency Modes guidelines называют это low-latency throughput estimator; dash.js v3.0 был первой референсной реализацией, и алгоритм теперь стандарт у Shaka v3.2+, ExoPlayer 2.16+ и основных коммерческих плееров.

Оценщик throughput должен быть умнее обычного скользящего среднего, потому что сам ответ темпирован: сервер пишет байты со скоростью производства энкодером, что составляет ~1/N полной ёмкости линка для N-рендиционного потока на максимальном качестве. Наивная оценка throughput заключила бы «это соединение ограничено 1.5 Мбит/с» и упала бы на нижнюю рендицию; фактически у линка ёмкости с запасом, просто энкодер не заполняет его быстрее, чем 333 мс × bitrate на chunk. Скорректированный оценщик измеряет время между прибытиями chunk’ов и вычисляет эффективный сетевой throughput из того, насколько простаивает соединение между chunk’ами – если плеер получает 60-КБ chunk каждые 333 мс и chunk прибывает за 30 мс, линк простаивает 303 мс из каждых 333 мс, а эффективная ёмкость линка – примерно 60 КБ / 30 мс = 2 МБ/с. Это та метрика, которую плеер фактически использует для ABR.

Логика переключения тоже другая. В plain DASH переключение происходит на границе сегмента (один сегмент на 4 секунды – то есть до одного переключения каждые 4 секунды). В LL-DASH плеер в принципе может переключаться на любом независимом chunk’е – примерно раз в секунду. Компромисс: переключение в середине сегмента требует, чтобы плеер скачал init-сегмент новой рендиции (если ещё не имел), бросил остаток летящего chunked-transfer-ответа и начал тянуть chunks новой рендиции с ближайшей независимой границы. dash.js v5 (текущий референс 2026 года) реализует это с консервативной политикой переключения: переключаться только на независимых chunk’ах, никогда в первой половине сегмента после предыдущего переключения, и никогда если задержка упадёт ниже <Latency min="">-пола. Большинство production-развёртываний упрощают, переключаясь только на границах сегментов и соглашаясь на четырёхсекундное ABR-разрешение; более глубокая возможность mid-segment-переключения зарезервирована для самых агрессивных low-latency-развёртываний.

Где фактический пол задержки в 2026 году

Mux, Bitmovin, Akamai, AWS Elemental и Wowza все публикуют LL-DASH-benchmark’и. Согласованный вывод – пол glass-to-glass 2–4 секунды в production, с публичными цифрами Mux 2024 года: среднее 3.6 с по 38 000 сессий, медиана 2.9 с, p95 5.5 с, p99 8.2 с – прямо сопоставимо с LL-HLS в тех же условиях. Развёртывания Bitmovin 2025 года рапортуют 2.5–4.0 с в нормальных условиях, с нижней границей 1.5 с, достижимой только когда энкодер pipeline затюнен (нет B-кадров, GOP в одну секунду, длительность сегмента две секунды с 200-мс chunk’ами). База клиентов Akamai видит среднее 4.0 с с длинным хвостом, обусловленным Wi-Fi-джиттером и buffer-bloat на потребительских роутерах.

Стена в две секунды – та же стена, в которую упирается LL-HLS, и по тем же причинам. LL-DASH не может сделать энкодер быстрее его pipeline (буфер на 1 GOP для B-frame-референса + CABAC-entropy + chunk-muxing – минимум 500 мс задержки со стороны энкодера; снижение GOP до одной секунды и отключение B-кадров – стандартная pre-broadcast-оптимизация), не может сделать decoder pre-roll быстрее, чем intake-latency аппаратного декодера устройства (200–400 мс на современных телефонах и TV), не может сделать сетевой RTT меньше законов физики (60–120 мс через континент по оптоволокну, больше по Wi-Fi и мобильной сети), и не может убрать HTTP-оверхед CDN (50–200 мс TTFB на edge). Ниже двух секунд вы уже не в HTTP-based streaming – вы в WebRTC или MoQ-зоне, и платите за это другим scaling-профилем.

LL-DASH и LL-HLS вместе – унифицированный CMAF-стек

Самое важное архитектурное озарение 2026 года – что LL-DASH и LL-HLS делят базовые медиа-файлы. CMAF chunks – это единица на уровне провода для обоих протоколов; разница только на уровне манифеста. Современный packager (Shaka Packager, AWS MediaPackage v2, Unified Streaming, Bitmovin Live, Norsk, Mux Live) эмитит один набор CMAF chunks и два параллельных манифеста – HLS multivariant playlist с #EXT-X-PART и DASH MPD с @availabilityTimeOffset. Те же chunks на обе стороны. Тот же origin. Тот же CDN. DRM такой же (Common Encryption – CENC, определённый в ISO/IEC 23001-7, с Widevine PSSH для Chrome / Android, PlayReady PSSH для Edge / Xbox, FairPlay key delivery для Safari).

Паттерн развёртывания:

  • iOS / iPadOS / macOS Safari / Apple TV → LL-HLS over chunked-CMAF (потому что Safari не поддерживает MSE-based DASH-воспроизведение, а HLS – единственный протокол, который он умеет нативно).
  • Android (Chrome, ExoPlayer) / Windows (Chrome, Edge) / macOS (Chrome, Firefox) / Linux / Smart TV (Tizen, webOS, Android TV, Roku в некоторых конфигурациях) → LL-DASH через Shaka Player, dash.js, ExoPlayer или нативный DASH-стек устройства.
  • Один набор CMAF chunks под обоими.

Это паттерн «unified CMAF», и это то, что отгружает каждый современный OTT-продукт в 2026 году – Netflix, Disney+, Prime Video, YouTube TV, Hulu, DAZN, HLS-over-CMAF-live-путь Twitch и большинство live-e-commerce-стеков. Формат CMAF полностью задокументирован в CMAF: формат упаковки, объединивший HLS и DASH; LL-HLS-половина – в LL-HLS подробный разбор; эта статья покрывает LL-DASH-половину.

Экономия конкретная. Один набор CMAF-файлов вместо двух наборов сегментов вдвое сокращает origin-storage и CDN-cache-footprint, упрощает invalidation и снижает CPU-нагрузку на packager. Гайды DASH-IF и HLS-IF 2026 года оба рекомендуют unified-CMAF-развёртывание как default-стартовую точку для любого нового live-OTT-стека.

Распространённые подводные камни

Четыре механизма описывают, что такое LL-DASH. Подводные камни описывают, что идёт не так на практике, когда один из них мисконфигурирован. Каждая команда, отгружающая LL-DASH, попадает в подмножество этих ловушек.

«Ловушка – сервер рекламирует @availabilityTimeOffset, но ждёт завершения сегмента. MPD говорит @availabilityTimeOffset="3.667", плеер шлёт запрос в early-availability-time, но origin держит ответ до тех пор, пока сегмент 42 полностью не записан на диск. Плеер видит 4-секундный TTFB на каждом сегменте, и бюджет задержки коллапсирует в plain-DASH-уровень. Валидируйте, шля запрос за 3.5 с до segment-complete time и тайминг how long until first byte. Первый байт должен прийти в пределах ~100 мс от запроса, а не в пределах ~4 секунд.»
«Ловушка – packager эмитит один moof на сегмент вместо одного moof на chunk. CMAF-сегмент с одним большим moof в начале и одним большим mdat после него технически валидный CMAF, но он не chunked – плеер не может ничего декодировать, пока весь mdat не прибудет. Проверьте CMAF-парсером (Bento4 mp4dump, verification-режим Shaka Packager или MP4Box -info -diso), что каждый сегмент содержит несколько пар moof + mdat. Production-цель: одна пара на 200–500 мс.»
«Ловушка – <UTCTiming> отсутствует или указывает на недоступный сервер. Без авторитетного источника времени плеер падает на часы устройства, которые дрейфуют на десятки миллисекунд и могут быть off на минуты, если NTP отключён. Плеер вычисляет неправильное segment-availability-time, запросы приходят рано (404) или поздно (задержка ползёт), и развёртывание выглядит периодически сломанным. Всегда включайте <UTCTiming> и валидируйте, что URL возвращает осмысленный ответ.»
«Ловушка – CDN cache TTL на манифесте слишком длинный. MPD меняется каждые @minimumUpdatePeriod (обычно 2–4 секунды в live-режиме). CDN, кэширующий MPD на 60 секунд, отдаёт устаревший манифест новым зрителям, которые вычисляют segment-номера, устаревшие на минуты, и ребуферят до догона. Поставьте Cache-Control: max-age=2 (или даже max-age=1) на MPD-ответ и убедитесь, что CDN это уважает. Origin shielding помогает амортизировать нагрузку на origin.»
«Ловушка – CDN не уважает chunked transfer encoding. Некоторые legacy-конфигурации CDN буферизуют весь ответ на edge до пересылки клиенту – режим «store-and-forward» – что побеждает chunked transfer. Зритель видит один большой ответ после завершения сегмента вместо потока chunks. Все основные CDN (Akamai, Cloudflare, Fastly, CloudFront, Bunny) в 2026 году поддерживают pass-through chunked transfer, но конфигурация opt-in на property. Валидируйте, перехватывая response-headers и тайминг прибытия байтов.»
«Ловушка – <Latency target=""> выставлен слишком агрессивно. Целевая задержка 1.5 с при размере chunk’а 333 мс оставляет плееру 1.0 с playback-buffer (3 chunks), что ниже безопасного rebuffer-порога для большинства сетей. Плеер гонится за live edge, ускоряясь до 1.05x, ребуферит, падает на нижнюю рендицию, и зритель видит видимые осцилляции качества. Production-рекомендация: целевая задержка ≥ 2.5 с при 333-мс chunks; ≥ 2.0 с при 200-мс chunks.»
«Ловушка – не-независимые chunks слишком редкие. Тот же режим отказа, что у LL-HLS. Некоторые энкодеры по умолчанию ставят один независимый (I-frame) chunk на сегмент (один в 4 секунды), что заставляет ABR-переключения ждать границы сегмента. Сконфигурируйте энкодер на один I-frame в секунду (keyint=fps), проверьте через ffprobe -show_packets, и поставьте packager выравнивать границы CMAF chunks по каденсу I-кадров.»
«Ловушка – fallback для non-low-latency-плееров сломан. Старые плееры, не понимающие @availabilityTimeOffset, должны всё равно играть поток – они увидят его как plain-DASH-поток с чуть более высокой задержкой. Fallback работает только если сам файл сегмента валидный и парсимый как обычный non-chunked CMAF-сегмент, когда энкодер закончил его писать. Некоторые конфигурации packager оставляют границы moof/mdat целыми, но эмитят кривой mvex-бокс, который путает non-low-latency-плееры. Валидируйте через DASH-IF-референс-плеер в non-low-latency-режиме на том же MPD.»

Когда LL-DASH – правильный выбор, а когда нет

LL-DASH – правильный протокол, когда вы хотите HTTP-based-доставку, CDN-экономику и полосу задержки 2–5 секунд, и отгружаете на не-Apple-устройства. Это покрывает большую часть рынка live-OTT 2026 года для браузера и Android: live-спорт без sub-second-ставок, breaking news, концертные стримы, live-e-learning, live-shopping, где чат – основное взаимодействие, surveillance-обзор, telemedicine-triage, где пациенту просто нужно видеть врача, а не оперировать синхронным инструментом.

LL-DASH – неправильный выбор в трёх направлениях. Вверх, когда важна задержка ниже двух секунд – спортивные ставки с realtime-ставками, live-аукционы, realtime-gaming, telemedicine-консультации с синхронными интерактивными инструментами – ответ дают WebRTC delivery и Media over QUIC. Вниз, когда задержка выше десяти секунд устраивает – длинный VOD, повторы, podcast-видео, архив – обычный MPEG-DASH проще, дешевле и cache-дружелюбнее. Вбок, когда вы отгружаете только на Apple-устройства – отгружайте LL-HLS на тот же chunked-CMAF-origin.

Интересное недавнее сравнение – LL-DASH против HESP. HESP достигает пола задержки 400 мс с двух-track-архитектурой (initialization track + continuation track) ценой примерно 2x storage. LL-DASH не может матчить задержку HESP, но матчит его CDN-совместимость и побеждает по storage. Для большинства use-case’ов LL-DASH выигрывает на кривой cost-vs-latency; HESP выигрывает, когда задержка ниже одной секунды критична, а WebRTC операционно слишком дорог.

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

Мы отгружали LL-DASH в live-e-learning-платформы, в OTT-сервисы на Smart TV и Android, в live-shopping-платформы и в telemedicine-triage-системы, где воспроизведение в браузере – основная поверхность. Наша команда streaming-инженеров рассматривает LL-DASH и LL-HLS как одну систему – один packager, один chunked-CMAF-origin, два параллельных манифеста, один DRM-стек – потому что production-режимы отказа всегда на стыках между энкодером, packager’ом, origin’ом, CDN и плеером, и унифицированный стек сжимает количество стыков с десяти до четырёх. Мы также делали обратное: помогали клиентам понять, что их sub-second-use-case на самом деле требует WebRTC, и избегали отгрузки LL-DASH для продукта, где он никогда не достиг бы целевой задержки. Честный scoping-разговор в начале экономит три месяца «почему оно всё ещё буферит» в конце.

Ключевые выводы

  • LL-DASH – это plain MPEG-DASH плюс четыре механизма: CMAF chunks, HTTP chunked transfer, @availabilityTimeOffset и <ServiceDescription> с целевой задержкой.
  • Production-пол задержки в 2026 году – 2–4 секунды glass-to-glass, прямо сопоставимо с LL-HLS в тех же условиях.
  • Chunked transfer (стриминг ответа на HTTP-уровне) и CMAF chunk (фрагмент-фрагмента сегмента на медиа-уровне) – независимые идеи с общим словом; LL-DASH использует обе.
  • Unified-CMAF-стек – один packager, один набор chunked-CMAF-файлов, LL-HLS для Apple, LL-DASH для всего остального – default 2026 года для нового live-OTT.
  • Самый частый production-отказ – origin, рекламирующий @availabilityTimeOffset, но ждущий завершения сегмента перед ответом; валидируйте end-to-end до объявления готовности.
  • Задержка ниже 2 секунд требует WebRTC или MoQ, а не LL-DASH.

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

CTA

  • Поговорить со streaming-инженером – о том, какая форма (LL-DASH, LL-HLS, WebRTC или MoQ) подходит вашей целевой задержке и CDN-бюджету.
  • Посмотреть наши кейсы – live-e-learning, OTT, telemedicine и live-shopping-развёртывания.
  • СкачатьLL-DASH Readiness Checklist (2026) – двадцать четыре пункта, которые каждая команда должна проверить перед объявлением production-готовности LL-DASH.

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

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