📁 File6. 스트리밍03 — MPEG-DASH (Dynamic Adaptive Streaming over HTTP)

03 — MPEG-DASH (Dynamic Adaptive Streaming over HTTP)

이 문서가 답하는 질문: DASH는 HLS와 무엇이 다르고, MPD라는 XML이 정확히 무엇을 표현하는가? 표준: ISO/IEC 23009-1 (MPEG-DASH). 핵심 한 줄: DASH는 XML 매니페스트(MPD) 하나Period → AdaptationSet → Representation → Segment의 4단 트리로 영상을 표현하는 벤더 중립 표준이다.


한 줄 답 (Pyramid Top)

DASH는 HLS와 같은 일을 한다 — 하지만 m3u8 두 단계 텍스트 대신 MPD 하나의 XML 트리로 푼다. 그 트리의 4단(Period > AdaptationSet > Representation > Segment)이 시간 분할 × 트랙 분할 × 품질 분할을 한 곳에 표현한다.


Why — DASH는 왜 만들어졌나

HLS가 2009년에 나오자, 다른 진영(Microsoft Smooth Streaming, Adobe HDS)도 자기 표준이 있었다. 표준이 셋이면 인코더는 같은 영상을 세 번 만들어야 한다.

MPEG는 2012년 ISO/IEC 23009-1로 벤더 중립 통합 표준을 만들었다 — 그게 DASH다. 동기:

  1. 벤더 락-인 회피 — Apple/MS/Adobe 어디에도 안 묶이는 표준.
  2. 유연성 — XML 기반으로 임의의 메타데이터(자막, ad-insertion, multi-period 등)를 깔끔하게 표현.
  3. codec-agnostic — H.264 외 HEVC/VP9/AV1을 원래부터 가정.
  4. DRM-agnostic — *Common Encryption (CENC)*과 결합해 Widevine/PlayReady/FairPlay 모두 동일 segment로 표현.

→ 결과적으로 유럽 방송·OTT(BBC iPlayer, YouTube)와 안드로이드 생태계에서 우선 채택되었다. iOS는 여전히 HLS만 native라 두 매니페스트(HLS + DASH)를 같이 만드는 경우가 많다 — CMAF 통합으로 segment는 공유.


How — MPD의 4단 트리

MPD (Manifest)

영상 전체에 대한 루트 메타데이터. 한 파일.

<?xml version="1.0" encoding="UTF-8"?>
<MPD
  xmlns="urn:mpeg:dash:schema:mpd:2011"
  type="static"
  mediaPresentationDuration="PT1H30M0S"
  minBufferTime="PT2S"
  profiles="urn:mpeg:dash:profile:isoff-live:2011">
  ...
</MPD>
속성의미
type="static"VOD (완결). live면 dynamic
mediaPresentationDuration전체 길이 (ISO 8601 duration: PT1H30M0S = 1h30m)
minBufferTime최소 버퍼링 권고
profilesDASH 프로파일 (ISO BMFF live, on-demand 등)

Period — 시간 분할

연속된 콘텐츠 구간. 광고 삽입(SCTE-35 marker)이나 콘텐츠 변경 지점에서 새 Period를 연다.

<Period id="main" start="PT0S" duration="PT30M0S">
  ...
</Period>
<Period id="midroll-ad" start="PT30M0S" duration="PT2M0S">
  ...
</Period>
<Period id="continuation" start="PT32M0S" duration="PT58M0S">
  ...
</Period>

Period 경계는 플레이어가 디코더를 리셋해도 되는 자연스러운 지점이다. 코덱이 바뀌어도, 해상도가 바뀌어도, ad가 다른 도메인의 segment를 가리켜도 OK.

AdaptationSet — 트랙 분할

같은 type동일 컨텍스트의 Representation 묶음. 보통:

  • 비디오 1개 (또는 SDR/HDR을 둘로 분리)
  • 오디오 N개 (언어별)
  • 자막 N개 (언어별)
<AdaptationSet
  id="1"
  contentType="video"
  mimeType="video/mp4"
  codecs="avc1.640028"
  segmentAlignment="true"
  startWithSAP="1"
  maxWidth="1920" maxHeight="1080" maxFrameRate="30000/1001">
  ...
</AdaptationSet>
 
<AdaptationSet
  id="2"
  contentType="audio"
  mimeType="audio/mp4"
  codecs="mp4a.40.2"
  lang="ko"
  segmentAlignment="true">
  <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
  ...
</AdaptationSet>
 
<AdaptationSet
  id="3"
  contentType="audio"
  codecs="mp4a.40.2"
  lang="en">
  <Role schemeIdUri="urn:mpeg:dash:role:2011" value="alternate"/>
  ...
</AdaptationSet>
속성의미
segmentAlignment="true"모든 Representation의 segment 경계가 정확히 일치 → ABR 전환이 매끄럽다
startWithSAP="1"모든 segment가 SAP type 1 (= IDR)으로 시작
Role트랙의 의미 (main / alternate / commentary / dub)

Representation — 품질 분할

비트레이트/해상도의 미디어. ABR이 고르는 단위.

<AdaptationSet contentType="video" ...>
  <Representation id="v1" bandwidth="5300000" width="1920" height="1080" frameRate="30000/1001">
    ...
  </Representation>
  <Representation id="v2" bandwidth="2800000" width="1280" height="720" frameRate="30000/1001">
    ...
  </Representation>
  <Representation id="v3" bandwidth="1400000" width="854" height="480" frameRate="30000/1001">
    ...
  </Representation>
  <Representation id="v4" bandwidth="800000" width="640" height="360" frameRate="30000/1001">
    ...
  </Representation>
</AdaptationSet>

bandwidthbps (= HLS의 BANDWIDTH). ABR 결정에 직접 들어가는 숫자.

Segment — 실제 미디어 (참조 방식 3가지)

(a) SegmentTemplate (가장 흔함) + SegmentTimeline

<Representation id="v1" bandwidth="5300000" width="1920" height="1080">
  <SegmentTemplate
    timescale="90000"
    initialization="$RepresentationID$/init.mp4"
    media="$RepresentationID$/seg_$Number%05d$.m4s"
    startNumber="1">
    <SegmentTimeline>
      <S t="0" d="540540" r="299"/>
    </SegmentTimeline>
  </SegmentTemplate>
</Representation>

$RepresentationID$v1로 치환 → URL은 v1/seg_00001.m4s, v1/seg_00002.m4s

토큰치환
$RepresentationID$Representation의 id
$Number$segment 번호 (startNumber부터)
$Number%05d$5자리 0-padded
$Time$누적 시간 (timescale 단위)
$Bandwidth$Representation의 bandwidth

<SegmentTimeline>은:

  • t — 시작 시간 (timescale=90000 → 90000 단위 = 1초)
  • d — 한 segment 길이 (540540 / 90000 = 6.006초)
  • r반복 횟수 (r=299 → 동일 길이 segment 300개 = 약 30분)

→ MPEG-TS PCR이 90kHz clock이라 timescale=90000이 전통.

(b) SegmentTemplate + duration (Timeline 없이)

<SegmentTemplate
  timescale="1000"
  duration="6000"
  initialization="$RepresentationID$/init.mp4"
  media="$RepresentationID$/seg_$Number%05d$.m4s"
  startNumber="1"/>

모든 segment가 정확히 6초인 경우 단축형. 라이브에서 흔하다 — segment N의 시간은 (startNumber + N) * duration / timescale로 계산 가능.

(c) SegmentList (가장 verbose, 거의 안 씀)

<Representation id="v1" bandwidth="5300000">
  <SegmentList timescale="90000" duration="540540">
    <Initialization sourceURL="v1/init.mp4"/>
    <SegmentURL media="v1/seg_00001.m4s"/>
    <SegmentURL media="v1/seg_00002.m4s"/>
    <SegmentURL media="v1/seg_00003.m4s"/>
    ...
  </SegmentList>
</Representation>

→ 모든 segment를 명시. 매니페스트가 거대해진다. 거의 항상 SegmentTemplate가 더 낫다.


What — 완성된 MPD 예시 (VOD, 비디오 4단 + 한국어/영어 오디오 + 자막)

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     type="static"
     mediaPresentationDuration="PT0H30M0.0S"
     minBufferTime="PT2.0S"
     profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">
 
  <BaseURL>https://cdn.example.com/movies/abc/</BaseURL>
 
  <Period id="0" start="PT0S">
 
    {/* ===== VIDEO ===== */}
    <AdaptationSet id="1" contentType="video" mimeType="video/mp4"
                   segmentAlignment="true" startWithSAP="1"
                   maxWidth="1920" maxHeight="1080" maxFrameRate="30000/1001"
                   par="16:9">
 
      <SegmentTemplate timescale="90000"
                       initialization="$RepresentationID$/init.mp4"
                       media="$RepresentationID$/seg_$Number%05d$.m4s"
                       startNumber="1">
        <SegmentTimeline>
          <S t="0" d="540540" r="299"/>
        </SegmentTimeline>
      </SegmentTemplate>
 
      <Representation id="v1080" codecs="avc1.640028" width="1920" height="1080" bandwidth="5300000"/>
      <Representation id="v720"  codecs="avc1.640020" width="1280" height="720"  bandwidth="2800000"/>
      <Representation id="v480"  codecs="avc1.64001f" width="854"  height="480"  bandwidth="1400000"/>
      <Representation id="v360"  codecs="avc1.64001e" width="640"  height="360"  bandwidth="800000"/>
    </AdaptationSet>
 
    {/* ===== AUDIO (Korean) ===== */}
    <AdaptationSet id="2" contentType="audio" mimeType="audio/mp4"
                   codecs="mp4a.40.2" lang="ko" segmentAlignment="true">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
      <SegmentTemplate timescale="48000"
                       initialization="$RepresentationID$/init.mp4"
                       media="$RepresentationID$/seg_$Number%05d$.m4s"
                       startNumber="1" duration="288000"/>
      <Representation id="ako" bandwidth="128000" audioSamplingRate="48000">
        <AudioChannelConfiguration
          schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
          value="2"/>
      </Representation>
    </AdaptationSet>
 
    {/* ===== AUDIO (English, alternate) ===== */}
    <AdaptationSet id="3" contentType="audio" mimeType="audio/mp4"
                   codecs="mp4a.40.2" lang="en" segmentAlignment="true">
      <Role schemeIdUri="urn:mpeg:dash:role:2011" value="alternate"/>
      <SegmentTemplate timescale="48000"
                       initialization="$RepresentationID$/init.mp4"
                       media="$RepresentationID$/seg_$Number%05d$.m4s"
                       startNumber="1" duration="288000"/>
      <Representation id="aen" bandwidth="128000" audioSamplingRate="48000">
        <AudioChannelConfiguration
          schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
          value="2"/>
      </Representation>
    </AdaptationSet>
 
    {/* ===== SUBTITLES ===== */}
    <AdaptationSet id="4" contentType="text" mimeType="text/vtt" lang="ko">
      <Representation id="sko" bandwidth="500">
        <BaseURL>subs/ko.vtt</BaseURL>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

이 한 파일이 HLS의 master.m3u8 + variant_*.m3u8 × 4 + audio/{ko,en}.m3u8 + subs/ko.m3u8 전부에 해당.


What — Live MPD (type="dynamic")

<MPD
  type="dynamic"
  availabilityStartTime="2026-05-10T10:00:00Z"
  publishTime="2026-05-10T10:30:15Z"
  minimumUpdatePeriod="PT4S"
  timeShiftBufferDepth="PT5M"
  suggestedPresentationDelay="PT10S"
  minBufferTime="PT2S"
  profiles="urn:mpeg:dash:profile:isoff-live:2011">
속성의미
availabilityStartTimewall-clock 기준 프로그램 시작 시각 — 모든 segment 시간이 여기서부터
publishTime이 MPD가 갱신된 시각
minimumUpdatePeriod플레이어가 MPD를 다시 GET할 최소 주기
timeShiftBufferDepthDVR 윈도우 (이 시간 이내로는 시크 가능)
suggestedPresentationDelay라이브 엣지로부터의 권장 지연 (지연 ↑ 안정성 ↑)

라이브의 segment URL 계산:

현재 wall-clock 시각 - availabilityStartTime = T
segment 번호 N = startNumber + floor(T / segmentDuration)

→ MPD를 매번 안 받아도 시각 기반으로 url 추론 가능. HLS의 sliding window 폴링과 다른 모델.


What — BaseURL과 redundancy

<MPD ...>
  <BaseURL>https://cdn1.example.com/abc/</BaseURL>
  <BaseURL>https://cdn2.example.com/abc/</BaseURL>
 
  <Period>
    <AdaptationSet>
      <BaseURL>video/</BaseURL>
      <Representation id="v1080">
        <BaseURL>1080p/</BaseURL>
        <SegmentTemplate media="seg_$Number%05d$.m4s" .../>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

여러 BaseURL이 있으면 CDN failover:

  • 한 CDN이 5xx를 내면 같은 path를 다른 BaseURL로 재시도.
  • 상대 경로 결합 — cdn1.example.com/abc/video/1080p/seg_00001.m4s.

dash.js는 failoverContent/baseUrlController 옵션으로 이걸 활용한다.


What — DRM (ContentProtection 박스)

<AdaptationSet contentType="video">
  <ContentProtection
    schemeIdUri="urn:mpeg:dash:mp4protection:2011"
    value="cenc"
    cenc:default_KID="34e5db32-8625-47cd-ba06-68fca0655a72"/>
 
  <ContentProtection
    schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
    value="Widevine">
    <cenc:pssh>AAAAVnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADYIARIQNOXbMoYlR826BmjgB...</cenc:pssh>
  </ContentProtection>
 
  <ContentProtection
    schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"
    value="PlayReady">
    <cenc:pssh>AAAA...</cenc:pssh>
  </ContentProtection>
</AdaptationSet>
식별자DRM
urn:mpeg:dash:mp4protection:2011CENC (공통 protection scheme 표시)
urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21edWidevine
urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95PlayReady
urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2FairPlay (DASH는 보통 안 씀, HLS 별도)

cenc:pssh(Protection System Specific Header)에 DRM별 라이선스 요청 메타데이터가 base64로 들어 있다. 같은 segment를 세 DRM이 동시에 풀 수 있도록 *공통 키 ID(default_KID)*를 공유한다 — 이게 04-cmaf.md의 핵심.


What-if — 잘못 만들면 어떻게 깨지는가

증상원인처방
mediaPresentationDuration이 실제와 다름인코딩 후 trim 안 함매니페스트 재생성
시크 끝부분에서 멈춤마지막 segment의 timeline S 누락<S> repeat가 정확한 segment 수와 일치
ABR이 작동 안 함segmentAlignment="false"true로 — 같은 시점에 다른 quality로 전환 가능
라이브가 미래 segment 요청 → 404클라이언트 시계 오차UTCTiming element 추가 (NTP HEAD 등)
Multi-period 경계에서 깜빡디코더 reset 미발생Period 경계는 codec change OK지만 adaptation reset가 작동하는지 확인
BaseURL 상대 경로 망가짐매니페스트 자체 URL이 redirect됨<Location> 명시 또는 절대 URL
MPD 갱신 폭주minimumUpdatePeriod가 너무 짧음live segment duration 정도로

Insight — 흥미로운 이야기

“왜 timescale=90000인가”

MPEG-TS의 PCR (Program Clock Reference)이 27MHz / 300 = 90kHz로 동작하기 때문이다. 1990년대 MPEG-2 시절부터 이어진 관습. fMP4는 임의 timescale이 가능하지만 비디오 트랙은 90000을 그대로 쓰는 경우가 압도적이다 — 다른 시스템과의 호환성. 오디오는 보통 샘플링 레이트를 그대로 (44100 / 48000) 쓴다.

“DASH는 사실 HLS보다 더 늦게 표준이 되었다”

HLS: 2009년 IETF Internet-Draft → 2017년 RFC 8216. DASH: 2011년 ISO/IEC 23009-1 (FDIS) → 2014년 amendment. 표준화 기간은 DASH가 더 길다 — 더 형식적이다. 그게 ISO 표준의 장점이자 단점.

“YouTube는 사실 DASH로만 보낸다”

모바일/데스크톱 모두. iOS Safari에서도 YouTube는 Service Worker + MSE(iPad는 native, iPhone은 polyfill 또는 fallback)로 DASH를 받는다. YouTube는 자기 클라이언트를 컨트롤하므로 iOS native HLS의 제약에서 벗어날 수 있다. 일반 OTT 서비스는 그게 어려워 HLS를 항상 같이 만든다.

*“DASH는 .mpd 가 아니라 .json 으로도 가능했을 것”

XML은 verbose하다. 파싱도 무겁다. 이론상 같은 트리를 JSON으로 표현해도 된다. 실제로 DASH-IF에서 JSON 매니페스트 제안이 나왔지만 ISO 표준은 XML로 굳었다 — 2010년대 초의 분위기가 XML이었기 때문. 지금이라면 JSON이 자연스러웠을 것이다.


한 단락 요약 + Mermaid

DASH는 영상의 모든 메타데이터를 MPD라는 한 XML에 4단 트리로 표현한다 — Period(시간) > AdaptationSet(트랙) > Representation(품질) > Segment(미디어). SegmentTemplate으로 segment URL을 공식처럼 압축하고, BaseURL로 CDN failover를 만들고, ContentProtection으로 다중 DRM을 같은 segment에 얹는다. HLS와 다른 점은 형식뿐, 푸는 문제는 같다.

→ 다음 파일 04-cmaf.md: HLS와 DASH가 segment를 공유하기로 합의한 결과.