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다. 동기:
- 벤더 락-인 회피 — Apple/MS/Adobe 어디에도 안 묶이는 표준.
- 유연성 — XML 기반으로 임의의 메타데이터(자막, ad-insertion, multi-period 등)를 깔끔하게 표현.
- codec-agnostic — H.264 외 HEVC/VP9/AV1을 원래부터 가정.
- 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 | 최소 버퍼링 권고 |
profiles | DASH 프로파일 (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>bandwidth는 bps (= 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">| 속성 | 의미 |
|---|---|
availabilityStartTime | wall-clock 기준 프로그램 시작 시각 — 모든 segment 시간이 여기서부터 |
publishTime | 이 MPD가 갱신된 시각 |
minimumUpdatePeriod | 플레이어가 MPD를 다시 GET할 최소 주기 |
timeShiftBufferDepth | DVR 윈도우 (이 시간 이내로는 시크 가능) |
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:2011 | CENC (공통 protection scheme 표시) |
urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed | Widevine |
urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95 | PlayReady |
urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2 | FairPlay (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를 공유하기로 합의한 결과.