MPEG-DASH 레퍼런스 — MPD(ISO 23009-1)·세그먼트·CENC/DRM·플레이어

이 글의 핵심

ISO/IEC 23009-1의 MPD·세그멘트 주소 지정(템플릿·리스트·베이스)부터 CENC/DRM, DASH-IF 프로파일, UHD, 라이브/VoD, 캐싱·바이트 범위, dash.js·Shaka·ExoPlayer·AVPlayer 구현까지 한 번에 짚는 DASH 실무 참조서입니다.

MPEG-DASH(Dynamic Adaptive Streaming over HTTP, 이하 DASH)는 ISO/IEC 23009-1이 정의한 적응형 HTTP 스트리밍 기술입니다. 콘텐츠를 세그멘트(보통 fMP4) 단위로 잘라 평범한 HTTP로 전송하고, MPD(Media Presentation Description)라는 XML 매니페스트에 비트레이트·해상도·시간 정렬 정보를 적어 두면 플레이어가 그것을 보고 ABR(Adaptive Bitrate)을 수행합니다. 이 글은 MPD와 세그멘트 전달 모델을 23009-1 관점에서 정리하고, FFmpeg·Bento4·Shaka Packager를 이용한 패키징, 다중 Period(광고)와 다국어 오디오·자막, CENC/DRM, DASH-IF 프로파일과 UHD, 정렬·Range·캐싱 최적화, dash.js·Shaka·ExoPlayer·AVPlayer 구현 요령을 한 문서에 묶었습니다. 키 길이, KID 관리, 라이선스 API 같은 세부는 벤더와 보안 정책에 따라 달라지므로 여기서는 프로토콜·패키징·플레이어 측면에 집중합니다.

규범·참고: ISO/IEC 23009-1(MPEG-DASH MPD, 세그멘트, 프로파일), CENC는 ISO/IEC 23001-7입니다. 라이선스와 키 교환은 EME 사양과 각 DRM 벤더 문서(Widevine, PlayReady 등)를 함께 보십시오.

MPD(미디어 프레젠테이션 설명)의 역할

MPD는 하나의 프레젠테이션을 Period가 시간 순으로 이어진 타임라인으로 표현하고, 각 Period 안에서 콘텐츠의 변형(Representation)과 초기화·미디어 세그멘트를 어떻게 가져올지(템플릿, 목록, 베이스)를 XML로 기술합니다. XML 네임스페이스는 urn:mpeg:dash:schema:mpd:2011이며, 루트 MPD 요소가 프로파일, 라이브 여부, 시간 관련 힌트를 담습니다. 그 아래로 Period → AdaptationSet → Representation → 세그멘트 정보(SegmentBase / SegmentList / SegmentTemplate)의 계층이 이어집니다.

MPD@type이 static이면 미리 만들어 둔 VoD(온디맨드) MPD이고, dynamic이면 라이브나 타임시프트용 MPD입니다. 동적 MPD에서는 플레이어가 MPD를 주기적으로 다시 가져오고, 라이브 엣지 근처의 세그멘트를 계산해 요청합니다. availabilityStartTime, timeShiftBufferDepth, suggestedPresentationDelay의 의미는 23009-1의 동적 MPD 절이 정의하고, 플레이어는 이 값들을 ABR, 시크, 라이브 지연 계산에 반영합니다.

핵심 루트 속성(개념 정리):

속성(예)용도
typestatic / dynamic — VoD와 라이브·타임시프트를 구분
profiles인터롭(예: DASH-IF) 프로파일 URN, 도구·플레이어 호환 힌트
minBufferTime최소 버퍼(미디어 타임) — 플레이어의 시작 버퍼·언더런 판단에 쓰는 힌트
mediaPresentationDuration전체 프레젠테이션 길이(필요 시)
availabilityStartTime / timeShiftBufferDepth / minimumUpdatePeriod동적 MPD의 기준 시각, 되감기 가능 범위, MPD 갱신 주기

ProgramInformation, BaseURL(serviceLocation, priority, weight), Location 같은 전역 요소는 메타데이터, MPD 이전, 다중 CDN·페일오버·지역 분산 전략에 쓰입니다. UTCTiming은 클라이언트 시계를 서버 시계에 맞추는 방법(HTTP 시간 서버, NTP 등)을 알려 주는데, 라이브에서는 “지금 어느 세그멘트까지 만들어졌는가”를 벽시계로 계산하기 때문에 이 값이 없거나 틀리면 존재하지 않는 세그멘트를 요청해 404가 나기 쉽습니다.

Period · AdaptationSet · Representation

Period

Period는 타임라인상의 한 구간입니다. 광고 삽입, 챕터, 권한이 다른 구간 등으로 프레젠테이션을 나눌 때 여러 Period를 씁니다. 인접한 Period가 끊김 없이 이어지게 하려면 start·duration과 미디어 내부 타임스탬프를 패키징 단계에서 정확히 맞춰야 하며, 실무에서 가장 자주 부딪히는 문제 중 하나가 바로 이 경계 불일치입니다. Period 수준에 BaseURL을 두어 그 구간만 다른 CDN이나 경로를 쓰게 할 수도 있습니다. EventStream / InbandEventStream과 SCTE-35 캐리지는 광고 마커와 동기 신호를 전달하는 데 쓰입니다.

AdaptationSet

AdaptationSet는 한 종류의 미디어(비디오, 오디오, 자막)를 논리적으로 묶습니다. 같은 종류라도 언어가 다른 오디오처럼 서로 대체 관계인 트랙은 보통 별도 AdaptationSet로 나눕니다. contentType(video·audio·text), mimeType, codecs(RFC 6381 코덱 문자열), lang, Label, Role(예: main, dub, commentary) 등은 클라이언트 UI와 초기 트랙 선택에 쓰입니다. 같은 AdaptationSet 안의 Representation들은 ABR이 서로 전환할 수 있는 후보이므로, 같은 콘텐츠이면서 세그멘트 경계가 시간상 맞아야 합니다. 다중 오디오는 언어·역할별로 AdaptationSet를 나누는 방식이 가장 명시적이고, 플레이어는 AdaptationSet 단위로 트랙을 고를 수 있어야 합니다.

Representation

Representation은 비트레이트, 해상도, 프레임레이트 조합으로 정해지는 구체적인 품질 하나입니다. id, bandwidth, width/height, frameRate, codecs가 ABR 판단, 디코더 설정, UI 표시에 쓰입니다. SegmentTemplate / SegmentList / SegmentBase는 대부분 여기나 상위 요소에 위치하며, 초기화 세그멘트(Initialization)와 미디어 세그멘트의 경로, 타임스케일(timescale), 세그멘트 길이를 정의합니다. DASH-IF 가이드라인과 주요 도구는 fMP4 세그멘트(moof/mdat) 구성을 기본으로 삼습니다.

세그멘트 지정: SegmentBase vs SegmentList vs SegmentTemplate

방식요약전형적 사용
SegmentBase단일 미디어 파일 + indexRange로 sidx(세그멘트 인덱스) 위치를 알려 주고, 플레이어가 Range 요청으로 구간을 가져옴단일 mp4 + sidx (on-demand 프로파일)
SegmentListSegmentURL 목록을 명시 + duration/timescale, 필요 시 InitializationURL이 불규칙하거나 개수가 적을 때, CMS가 목록을 생성할 때
SegmentTemplate$Number$ / $Time$ / $RepresentationID$ 등의 치환 규칙으로 세그멘트 이름을 기술대규모 VoD·라이브, fMP4, CDN 캐싱

SegmentTemplate@duration(timescale 기준)은 고정 세그멘트 길이를 나타냅니다. 라이브처럼 세그멘트 길이가 조금씩 달라지는 경우에는 SegmentTimeline과 $Time$ 기반 템플릿으로 실제 길이를 하나하나 기술할 수 있는데, 이때 MPD에 적은 값과 실제 미디어 타임스탬프가 일치해야 합니다.

Initialization / indexRange / BaseURL: 초기화 세그멘트(fMP4의 ftyp+moov)와 미디어 세그멘트(moof+mdat)를 분리하는 구성이 일반적입니다. 단일 파일 방식은 HTTP 바이트 범위 요청(206 Partial Content, Accept-Ranges)을 전제로 하며, CDN이 Range 요청을 잘 캐싱할 때 파일 수가 적다는 이점이 살아납니다(아래 “최적화” 절).

MPD·세그멘트 XML 주요 요소(참고 목록)

아래는 23009-1 MPD에서 자주 쓰이고 도구가 흔히 출력하는 요소·속성의 역할을 압축한 것입니다. 스키마의 모든 항목을 다루지는 않으므로, 식별자·확장·XLink 등은 표준 문서를 함께 보십시오.

MPD 하위 공통
ProgramInformation — 제목·저작 정보. BaseURL — 상대/절대 URL이며, 여러 개를 두면 로드밸런싱·페일오버에 쓸 수 있습니다. Location — MPD를 이후 갱신할 때 쓸 URL. UTCTiming — 시계 동기 방법. ServiceDescription — 목표 지연 등 서비스 설정(후속 에디션).

Period: id, start, duration은 타임라인상의 위치, BaseURL은 해당 Period 전용 베이스, EventStream은 MPD에 실린 이벤트, AssetIdentifier는 광고·본편 같은 자산 식별에 쓰입니다.

AdaptationSet: id, contentType, lang, mimeType, codecs, segmentAlignment, subsegmentAlignment, bitstreamSwitching은 ABR·트랙 전환 호환성을 나타냅니다. Accessibility·Role·AudioChannelConfiguration은 접근성과 역할, SupplementalProperty / EssentialProperty는 HDR 전달 특성이나 방송 확장 같은 부가 정보를 담고, ContentProtection은 DRM 정보(아래 CENC 절)를 담습니다.

Representation: id, bandwidth, width, height, frameRate, codecs는 ABR·디스플레이·디코딩에 쓰입니다. SubRepresentation은 한 Representation 안의 하위 품질(예: 트릭 모드용 일부 프레임)을 기술하며 드물게 쓰입니다.

세그멘트 체인
SegmentBase — @indexRange, 하위 Initialization, RepresentationIndex. SegmentList — SegmentURL, 필요 시 SegmentTimeline. SegmentTemplate — @media, @initialization, @duration 또는 SegmentTimeline, @timescale.
SegmentTimeline 안의 S 요소는 t(시작), d(길이), r(반복)로 가변 길이 세그멘트를 압축해 표현합니다.

MPD·Period·AdaptationSet·Representation 요소·속성(23009-1·실무 정렬)

표 1. MPD 루트(대표) — 필수/선택 여부는 에디션마다 다를 수 있으니 규범 문서를 확인하십시오.

요소/속성설명(요지)
MPD루트. type, profiles, minBufferTime, mediaPresentationDuration 등 프레젠테이션 전체 설정
profiles (URN 목록)DASH-IF·방송(예: HbbTV) 인터롭 프로파일 식별 — 플레이어·검증기가 어떤 제약을 기대할지 결정
minimumUpdatePeriod동적 MPD가 최소한 이 기간 동안은 바뀌지 않는다는 약속. 클라이언트는 대략 이 주기로 MPD를 다시 가져옴
minBufferTime최소 버퍼(미디어 타임) — ABR·언더런 판단 힌트
timeShiftBufferDepth라이브에서 되감을 수 있는 최대 범위
suggestedPresentationDelay라이브 엣지에서 얼마나 뒤에서 재생할지에 대한 권고 — 저지연과 안정성 사이의 절충
maxSegmentDuration가장 긴 세그멘트 길이 — 도구·검증기·플레이어 버퍼 계산 힌트
ProgramInformation제목, 저자, copyright 등 메타
BaseURL + @serviceLocation @priority @weight다중 베이스 — CDN, A/B, 지역, 페일오버
LocationMPD의 새 URL — 이후 갱신은 이 주소에서
UTCTiming + @value시계 동기 소스(HTTP·NTP 등) — 라이브에서 필수에 가까움
ServiceDescription목표 지연·재생 속도 범위 등 서비스 메타(LL-DASH에서 활용)

표 2. Period

요소/속성설명(요지)
id / start / durationPeriod 식별과 프레젠테이션 타임라인상의 시작·길이
BaseURL해당 Period만 다른 오리진을 쓸 때
EventStream / EventMPD에 실린 이벤트 — 광고 마커, 동기 신호 등
InbandEventStreamfMP4 안(emsg 박스)으로 전달되는 이벤트가 있음을 알림
SCTE-35 캐리지방송·광고 큐 시그널(DASH-IF·방송 확장)

표 3. AdaptationSet

요소/속성설명(요지)
contentTypevideo / audio / text / image 등 — 트랙 종류
segmentAlignment / subsegmentAlignmentRepresentation 간 세그멘트 경계가 겹치지 않고 맞는지(true 등) — ABR 전환 전제
bitstreamSwitching서로 다른 Representation의 세그멘트를 이어 붙여 디코더 재초기화 없이 넣을 수 있는지
mimeType / codecsRFC 6381 코덱 문자열 — avc1, hvc1, mp4a.40.2 등
lang / Label / Role / AccessibilityUI 표시, 접근성, 역할(main, subtitle, alternate 등)
ContentComponent / id다중화된 컴포넌트 구분
FramePacking / AudioChannelConfiguration3D 프레임 패킹, 오디오 채널 구성(URN·값)
EssentialProperty / SupplementalProperty필수/부가 속성 URN(예: HDR 전달 특성, AdaptationSet 간 전환 허용)
ContentProtection + @schemeIdUri + cenc:default_KID + cenc:pssh (Base64)DRM 시스템과 키 ID 연결
InbandEventStream이 세트의 세그멘트에 인밴드 이벤트가 들어 있음을 신호

표 4. Representation

요소/속성설명(요지)
id, bandwidthABR 선택의 기준. bandwidth는 bits/s
qualityRanking(선택) 품질 순위 힌트 — 값이 낮을수록 고품질
width / height / frameRate / sar해상도, 프레임레이트, 샘플 종횡비
codecs / mimeType상위에서 상속받은 값을 개별적으로 덮어쓸 때
BaseURL이 Representation 전용 URL
SegmentBase / SegmentList / SegmentTemplate세그멘트 주소 지정(위 절)
SubRepresentation하위 품질 기술(드묾)

표 5. SegmentBase / SegmentList / SegmentTemplate

요소/속성설명(요지)
SegmentBase@timescale미디어 클럭(초당 틱 수)
Initialization@sourceURL / @range초기화 세그멘트 URL 또는 바이트 범위
RepresentationIndex / SegmentBase@indexRangesidx 인덱스의 위치(바이트 구간)
SegmentList@duration / timescale균일한 세그멘트 길이(없으면 SegmentTimeline 사용)
SegmentURL@mediaRange / @indexRange개별 세그멘트의 바이트 구간
SegmentTemplate@media / initialization$Number$ $Time$ $RepresentationID$ $Bandwidth$ 등 치환
SegmentTemplate@duration고정 세그멘트 길이(timescale 단위)
SegmentTemplate.SegmentTimeline + S@t S@d S@r가변 길이·라이브용 타임라인. r은 추가 반복 횟수이고, -1이면 다음 S나 Period 끝(라이브는 MPD 갱신 시점)까지 반복

QoE 보고용 Metrics / Reporting, XLink로 원격 Period를 끼워 넣는 방식 등은 23009-1 후속 에디션과 DASH-IF 문서에 자세히 나옵니다. 이 표로 모든 요소를 대신하기보다는 스키마(XSD)와 23009-1 본문을 1차 근거로 삼으십시오.

정적 MPD(최소) 예: SegmentTemplate + fMP4

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
     type="static"
     minBufferTime="PT2S"
     profiles="urn:mpeg:dash:profile:isoff-live:2011"
     mediaPresentationDuration="PT0H10M0S">
  <Period>
    <AdaptationSet contentType="video" mimeType="video/mp4" segmentAlignment="true" par="16:9">
      <Representation id="v0" width="1280" height="720" bandwidth="3000000" codecs="avc1.4d401f" frameRate="30">
        <SegmentTemplate
          timescale="90000"
          media="v720_$Number$.m4s"
          initialization="v720_init.mp4"
          startNumber="1"
          duration="180000" />
        <!-- 180000/90000 = 2s @ 90k timescale; 실제 콘텐츠·GOP와 일치시킬 것 -->
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

프로파일 이름이 헷갈리기 쉬운데, isoff-on-demand 프로파일은 Representation마다 파일 하나와 SegmentBase(sidx)를 쓰는 구성을 전제로 합니다. 위처럼 SegmentTemplate으로 세그멘트 파일을 나누는 구성은 VoD라도 isoff-live 프로파일을 선언하는 것이 맞습니다.

타임라인 관리

미디어 타임은 timescale로 정수화됩니다(예: 90000, 48000, 1000). SegmentTemplate+$Time$, SegmentTimeline의 S@t·S@d·S@r은 라이브나 GOP 길이가 일정하지 않은 콘텐츠에 쓰입니다. 다중 Period나 SCTE-35 광고 구간에서는 각 Period 경계에서 인코더가 키프레임을 강제로 넣지 않으면 경계 부근에서 끊김이나 A/V 드리프트가 생깁니다. 정렬(alignment)도 중요합니다. 같은 시간 창에 속한 비디오·오디오·자막의 세그멘트 경계를 GOP와 맞추고, MPD에 적은 duration과 실제 fMP4 타임스탬프(tfdt)가 일치하는지 교차 검증하십시오.

DASH-IF: 상호운용을 위한 가이드라인(IOP)과 검증 도구를 제공하며, 해당 프로파일 URN을 MPD@profiles에 명시하는 경우가 많습니다.

실전: FFmpeg, Bento4, Shaka Packager(개요)

아래 명령의 옵션은 도구 버전과 빌드에 따라 다릅니다. --help와 공식 문서를 확인하고, 생성된 MPD는 반드시 검증기로 확인하십시오.

FFmpeg로 DASH(fMP4) 생성(예시)

FFmpeg의 dash 먹서는 대부분의 배포판 빌드에 포함되어 있습니다.

# 예: 비디오 2개 해상도 + 오디오를 DASH(fMP4)로
ffmpeg -y -i input.mp4 -filter_complex \
  "[0:v]split=2[v1][v2];[v1]scale=1280:-2[vout1];[v2]scale=854:-2[vout2]" \
  -map "[vout1]" -map "[vout2]" -map 0:a \
  -c:v libx264 -b:v:0 3000k -b:v:1 1200k -g 60 -keyint_min 60 -sc_threshold 0 \
  -c:a aac -b:a 128k \
  -f dash -use_timeline 1 -use_template 1 -seg_duration 2 \
  -init_seg_name "init-stream$RepresentationID$.$ext$" \
  -media_seg_name "chunk-stream$RepresentationID$-$Number%05d$.$ext$" \
  -adaptation_sets "id=0,streams=v id=1,streams=a" \
  ./out_dash/master.mpd

-f dash는 MPD와 세그멘트 파일을 함께 씁니다. -adaptation_sets로 비디오와 오디오를 각각 별도 AdaptationSet에 넣고, init_seg_name·media_seg_name이 MPD의 SegmentTemplate 이름 규칙이 됩니다. GOP 설정(-g, -keyint_min, 장면 전환 키프레임을 막는 -sc_threshold 0)은 세그멘트 길이와 Representation 간 정렬에 직접 영향을 줍니다. 세그멘트 길이는 2~6초가 흔한 출발점이며, 콘텐츠와 지연 요구에 맞춰 조정합니다. 라이브 옵션인 -window_size나 -remove_at_exit를 VoD 패키징에 섞어 쓰면 MPD에 일부 세그멘트만 남거나 종료 시 결과물이 지워지므로 주의해야 합니다.

Bento4 mp4dash(예시)

Bento4의 mp4dash는 조각화된(fragmented) MP4를 입력으로 받아 DASH를 패키징합니다.

# 예: 이미 fMP4로 변환한 트랙을 mp4dash로 (옵션은 버전에 따라 다름)
mp4dash -o out_dash video_1080p.mp4 video_720p.mp4 audio_128k.mp4

보통 mp4fragment로 먼저 조각화해 GOP·tfdt·세그멘트 경계를 맞추는 전처리를 거칩니다. Bento4 문서의 DASH 및 fragment 절을 확인하십시오. 참고로 MP4Box는 Bento4가 아니라 GPAC 프로젝트의 도구로, MP4Box -dash 명령으로 별도의 DASH 패키징 경로를 제공합니다.

Shaka Packager(예시)

packager는 CENC, 자막 등 프로덕션 기능을 갖춰 널리 쓰입니다.

# 예: ABR 비디오(대표) + 오디오 + VTT(도구/버전에 따라 --stream 옵션명 상이)
packager in=input.mp4,stream=video,init_segment=init_v.mp4,segment_template=seg_v_\$Number\$.m4s \
  in=input_en.mp4,stream=audio,init_segment=init_a_en.mp4,segment_template=seg_a_en_\$Number\$.m4s \
  --mpd_output master.mpd

암호화는 --enable_raw_key_encryption과 --keys(또는 --enable_widevine_encryption 같은 키 서버 연동 옵션)로 켜고, --protection_scheme으로 cenc/cbcs를 고르며, --protection_systems로 MPD와 init 세그멘트에 넣을 DRM 시스템 정보(PSSH)를 지정합니다. 키와 KID는 비밀로 다뤄야 합니다. 광고용 다중 Period는 SCTE-35 신호와 SSAI(서버 측 광고 삽입) 파이프라인이 함께 있어야 제대로 동작하는 경우가 많아, 패키저가 만든 MPD만으로는 부족합니다.

Bento4: fMP4 분할 후 DASH(전형)

원본이 progressive MP4 한 개라면, GOP와 세그멘트 경계를 맞추기 위해 먼저 조각화 단계를 둡니다(도구 버전과 매뉴얼을 우선하십시오).

# 예(개략): 비디오를 고정 duration fragment → DASH
mp4fragment --fragment-duration 2000 unfragmented_v.mp4 frag_v.m4f
# 오디오도 동일 정책(샘플레이트/프레임과 정렬)
mp4fragment --fragment-duration 2000 unfragmented_a.m4a frag_a.m4f
# 이후 mp4dash로 MPD+세그 출력 (Bento4 문서의 mp4dash 옵션 확인)
mp4dash -o ./dash_out frag_v.m4f frag_a.m4f

--fragment-duration(ms)은 인코딩 시 정한 GOP 길이(예: 2초)와 맞춰야 합니다. 조각은 키프레임에서만 시작할 수 있으므로 GOP가 맞지 않으면 실제 조각 길이가 들쭉날쭉해집니다. A/V 드리프트는 최종 MPD와 실제 재생으로 확인합니다.

FFmpeg: 다중 비디오 Representation(스케일 + DASH) 개략

한 입력에서 해상도 두 개를 만들고 오디오는 하나를 공유해 -f dash 한 번으로 묶는 형태입니다(-adaptation_sets 문법은 빌드 도움말을 확인하십시오).

ffmpeg -y -i input.mkv -filter_complex \
  "[0:v]split=2[v1][v2];[v1]scale=-2:1080[vo1];[v2]scale=-2:720[vo2]" \
  -map "[vo1]" -map "[vo2]" -map 0:a? \
  -c:v libx264 -b:v:0 5000k -b:v:1 2500k -g 60 -keyint_min 60 -sc_threshold 0 \
  -c:a aac -b:a 128k \
  -f dash -use_template 1 -use_timeline 1 \
  -init_seg_name "init-$RepresentationID$.$ext$" -media_seg_name "seg-$RepresentationID$-$Number%05d$.$ext$" \
  -adaptation_sets "id=0,streams=v id=1,streams=a" \
  out_multi/master.mpd

AdaptationSet 하나에는 한 종류의 미디어만 들어가야 하므로, 비디오 두 개는 id=0에, 오디오는 id=1에 넣습니다. 비디오와 오디오를 같은 AdaptationSet에 섞으면 플레이어가 둘을 ABR 전환 후보로 취급해 재생이 깨집니다. 두 비디오에 같은 -g·-keyint_min을 주는 것이 Representation 간 세그멘트 정렬의 핵심입니다.

Shaka Packager: WebVTT(경로) + CENC(개략, 키는 비밀)

# VTT(파일) + 비디오/오디오. 정확한 --stream, drm 플래그는 packager --help
packager \
  in=main.mp4,stream=video,init_segment=video_init.m4s,segment_template=video_$Number$.m4s \
  in=main_en.mp4,stream=audio,init_segment=audio_init.m4s,segment_template=audio_$Number$.m4s \
  in=captions.vtt,stream=text,init_segment=captions_init.m4s,segment_template=captions_$Number$.m4s \
  --mpd_output master.mpd
# CENC(예시형 — 실 키/KID/라이선스는 운영 환경에서만)
# packager ... --enable_raw_key_encryption --keys label=A:key_id=HEX:KEYHEX: ...

stream=text의 초기화·세그멘트 이름 규칙은 도구와 DASH-IF 자막 가이드라인에 맞게 조정합니다.

다중 Period(광고) · 다중 오디오 · 자막(WebVTT, TTML)

  • 다중 Period: 프리롤·미드롤을 인코딩이나 권한이 다른 Period로 나눌 때, 앞 Period의 끝과 다음 Period의 시작이 시간상 이어져야 시크와 ABR이 안정적입니다. 키프레임과 오디오 프레임 경계도 맞춰야 합니다. 이벤트와 SCTE-35 신호(인밴드/MPD)는 광고 삽입·동기와 함께 설계합니다.
  • 다중 오디오: AdaptationSet@lang, Role, Label로 언어와 보조 트랙을 구분하고, 플레이어의 오디오 트랙 선택 API로 전환합니다. 트랙 모델은 구현체마다 다릅니다.
  • 자막: 외부 WebVTT 파일 하나를 mimeType="text/vtt"로 두고 BaseURL로 가리키는 사이드카 방식과, 자막을 fMP4에 넣어 mimeType="application/mp4"에 codecs="wvtt"(WebVTT) 또는 codecs="stpp"(TTML/IMSC)로 신호하는 세그멘트 방식이 있습니다. 방송·규제 요구가 있으면 DASH-IF 가이드에 맞는 쪽을 고르고, 플레이어 지원 범위를 확인하십시오.

다중 Period MPD(개략): Period@start·duration으로 본편과 광고를 시간상 이어 붙입니다. 실제 서비스에서는 SSAI나 패키저가 광고 MPD와 세그멘트를 합성하는 경우가 많습니다.

<MPD type="static" minBufferTime="PT2S" mediaPresentationDuration="PT10M" xmlns="urn:mpeg:dash:schema:mpd:2011">
  <Period id="main" duration="PT8M">
    <AdaptationSet id="0" contentType="video" mimeType="video/mp4">...</AdaptationSet>
  </Period>
  <Period id="ad" duration="PT30S" start="PT8M">
    <AdaptationSet id="0" contentType="video" mimeType="video/mp4">...</AdaptationSet>
  </Period>
  <Period id="post" start="PT8M30S" duration="PT1M30S">
    <AdaptationSet id="0" contentType="video" mimeType="video/mp4">...</AdaptationSet>
  </Period>
</MPD>

start를 생략하면 앞 Period의 start+duration으로 암시되므로, 명시할 때는 이 규칙과 어긋나지 않게 일관되게 쓰십시오. 어긋나면 검증기 오류나 플레이어별 해석 차이가 생깁니다.

다국어 오디오 MPD(개략): lang이 다른 AdaptationSet를 따로 두는 방식이 가장 명시적입니다.

<Period>
  <AdaptationSet id="0" contentType="audio" lang="ko" mimeType="audio/mp4" codecs="mp4a.40.2">...</AdaptationSet>
  <AdaptationSet id="1" contentType="audio" lang="en" mimeType="audio/mp4" codecs="mp4a.40.2">...</AdaptationSet>
  <AdaptationSet id="2" contentType="video" mimeType="video/mp4">...</AdaptationSet>
</Period>

WebVTT vs TTML(in MP4): WebVTT는 웹 환경에서 다루기 가볍고, TTML/IMSC(stpp)는 스타일 표현력이 높아 방송·규제 요구가 있는 워크플로에서 많이 씁니다. 권장 방식은 DASH-IF의 자막 관련 IOP 문서에 정리되어 있습니다. 썸네일 스프라이트는 contentType="image" AdaptationSet로 제공하는 DASH-IF 확장이 있으니, 사용하는 플레이어의 지원 여부를 릴리스 노트에서 확인하십시오.

CENC(공통 암호화)와 DRM(개요)

CENC(Common Encryption, ISO/IEC 23001-7)는 fMP4 샘플을 암호화하는 공통 방식을 정의합니다. 암호화 모드는 AES-CTR 기반 cenc와 AES-CBC 패턴 암호화 기반 cbcs 두 가지가 주로 쓰이며, init 세그멘트의 tenc 박스에 기본 KID와 모드가, pssh 박스에 DRM 시스템별 데이터가, 미디어 세그멘트의 senc 박스에 샘플별 IV·서브샘플 정보가 담깁니다. 콘텐츠는 한 번만 암호화하고, 키 전달은 EME(Encrypted Media Extensions)와 CDM(Widevine, PlayReady, FairPlay 등)이 플랫폼별로 맡는 구조입니다. MPD의 ContentProtection@schemeIdUri(urn:uuid:...), cenc:default_KID, cenc:pssh(Base64)는 플레이어가 어떤 DRM 시스템으로 어떤 키를 요청할지 결정하는 단서입니다. 패키저와 키 서버를 잇는 SPEKE 같은 연동은 벤더 가이드를 따르십시오.

멀티 DRM: MPD에 ContentProtection을 DRM 시스템별로 여러 개 넣으면 하나의 암호화 콘텐츠로 다양한 클라이언트에 대응할 수 있습니다. 단 cbcs 하나로 Widevine·PlayReady·FairPlay를 모두 커버하려면 각 클라이언트 버전이 cbcs를 지원해야 합니다. Widevine의 L1/L3처럼 보안 레벨에 따라 허용 해상도를 제한하는 것은 라이선스 정책 영역입니다. Apple 기기는 HLS + FairPlay가 기본 경로입니다.

DASH-IF 프로파일 · UHD/4K · Live vs On-Demand

  • DASH-IF IOP: fMP4, profiles URN, 코덱·세그멘트·자막에 대한 최소 요구를 정리한 가이드라인입니다. 플랫폼 인증을 받거나 여러 플레이어와 호환시키려면 먼저 읽어야 합니다.
  • UHD/4K: HEVC/AV1 codecs, 10bit, HDR(PQ/HLG 전달 특성을 SupplementalProperty로 신호, Dolby Vision은 별도 코덱 문자열)을 다룹니다. 기기의 디코딩 성능, HDCP, DRM 보안 레벨에 따른 최대 해상도 제한을 함께 고려해야 합니다.
  • On-Demand: MPD@type="static". MPD와 세그멘트가 바뀌지 않으므로 캐시 수명을 길게 줄 수 있습니다.
  • Live: dynamic, minimumUpdatePeriod, availabilityStartTime, SegmentTimeline 갱신, suggestedPresentationDelay, timeShiftBufferDepth를 씁니다. 플레이어는 MPD와 세그멘트를 계속 가져옵니다. 스포츠 중계나 대화형 서비스처럼 지연이 중요하면 청크 전송을 활용하는 저지연 DASH(LL-DASH) 구성을 추가로 검토합니다.

최적화: 정렬, Range, 인덱스, 캐싱

  • 세그멘트 정렬: 모든 비트레이트가 같은 미디어 타임에서 세그멘트를 끊어야 ABR 전환이 매끄럽습니다. 인코딩 단계에서 고정 GOP와 키프레임 위치를 맞추고, 오디오 프레임 경계도 확인합니다.
  • Byte Range(HTTP 206): SegmentBase@indexRange와 sidx를 써서 파일 하나에서 필요한 구간만 Range로 가져오는 방식입니다. 파일 수가 적어 관리가 편하지만, CDN이 Range 요청을 어떻게 캐싱하는지에 따라 효율이 크게 달라집니다. 세그멘트를 여러 파일로 나누는 방식과의 트레이드오프입니다.
  • Index Segment / sidx: 세그멘트 인덱스(sidx)가 각 조각의 바이트 위치와 시간을 알려 주므로 시크 시 필요한 부분만 요청할 수 있습니다.
  • 캐싱: 라이브 MPD는 TTL을 짧게(또는 Cache-Control: no-cache) 두고, init 세그멘트는 길게, 미디어 세그멘트는 URL이 바뀌지 않는 한 내용도 바뀌지 않도록 버전이 담긴 URL($Number$ 증가 등)과 긴 캐시를 줍니다. 쿼리 스트링이 캐시 키에 포함되는지, 퍼지 정책은 어떤지 확인하십시오. 플레이어와 미디어 오리진이 다르면 CORS 헤더가 필요하고, Range 요청을 쓴다면 Range 요청 헤더와 Content-Range 등 응답 헤더 노출도 CORS 설정에 포함해야 합니다.

플레이어: dash.js, Shaka, ExoPlayer, AVPlayer(요지)

  • dash.js(브라우저): DASH-IF의 레퍼런스 플레이어로, MPD 파싱과 SegmentBase/List/Template 해석, ABR(처리량 기반 규칙, BOLA 등)을 내장합니다. MediaPlayer().create().initialize(video, src, true)로 시작하고, DRM은 EME 위에서 setProtectionData로 라이선스 서버를 지정합니다. 이벤트는 on(dashjs.MediaPlayer.events.*)로 받고, 버퍼·ABR 설정은 updateSettings()로 바꿉니다. 설정 구조가 메이저 버전마다 달라지므로 사용 중인 버전의 공식 문서를 기준으로 하십시오.
  • Shaka Player: DASH와 HLS를 모두 지원합니다. Player 생성 → attach → load 순서이며, configure()로 drm, abr, streaming, manifest 등을 설정합니다. shaka.offline.Storage로 오프라인 저장도 지원합니다. 실무에서 가장 먼저 부딪히는 문제는 대개 세그멘트·라이선스 서버의 CORS 설정입니다.
  • ExoPlayer / Media3(Android): MediaItem에 MIME 타입을 지정하거나 DashMediaSource를 직접 만들어 씁니다. DRM은 MediaItem.DrmConfiguration(Widevine/PlayReady UUID + 라이선스 URI)으로, HTTP 헤더는 DefaultHttpDataSource로, 언어·역할 우선순위는 DefaultTrackSelector의 파라미터로 지정합니다. ExoPlayer 2(com.google.android.exoplayer2)와 Media3(androidx.media3)는 패키지와 일부 API가 다르니 어느 쪽을 쓰는지 먼저 확인하십시오.
  • AVPlayer(iOS): AVPlayer는 DASH MPD를 재생하지 않고 HLS만 지원합니다. iOS에서 DASH를 재생하려면 서드파티 플레이어를 쓰거나, 같은 CMAF 세그멘트로 HLS 매니페스트를 함께 만드는 것이 일반적입니다. FairPlay는 AVContentKeySession에서 SPC를 만들어 키 서버로 보내고 CKC를 받아 오는 흐름입니다.

dash.js(예시, 개념)

<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
<video id="v" controls></video>
<script>
  const v = document.getElementById("v");
  const p = dashjs.MediaPlayer().create();
  p.initialize(v, "https://example.com/dash/master.mpd", true);
  p.on("error", (e) => console.error("dash error", e));
  // ABR/버퍼/품질: getSettings()·updateSettings(…) — dash.js 현재 메이저 API 문서
</script>

보호 콘텐츠(개념): setProtectionData로 키 시스템별 라이선스 서버를 지정하고, 요청을 가공해야 하면 registerLicenseRequestFilter를 씁니다. 브라우저는 W3C EME의 requestMediaKeySystemAccess로 키 시스템을 고르며, MPD ContentProtection의 시스템 UUID와 KID가 패키징 결과와 맞아야 합니다. 라이선스 서버가 요구하는 헤더, 요청 본문 형식, CORS 설정도 함께 확인하십시오.

// dash.js: Widevine 예(개략) — com.widevine.alpha / 서버·헤더는 환경값
p.setProtectionData({
  "com.widevine.alpha": { serverURL: "https://lic.example.com/wv", /* httpRequestHeaders, priority */ }
});
p.registerLicenseRequestFilter((req) => { /* req.uris, body 조작 */ return Promise.resolve() });

Shaka Player(예시, 개념)

<video id="v" controls></video>
<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.7.0/shaka-player.compiled.js"></script>
<script>
  (async function () {
    const video = document.getElementById("v");
    const p = new shaka.Player();
    await p.attach(video);
    p.configure({
      abr: { defaultBandwidthEstimate: 2e6, restrictions: { minHeight: 360 } },
      streaming: { rebufferingGoal: 2, bufferBehind: 30 },
      drm: { servers: { "com.widevine.alpha": "https://lic.example.com/widevine" } }
    });
    p.getNetworkingEngine().registerRequestFilter((type, request) => {
      if (type === shaka.net.NetworkingEngine.RequestType.LICENSE) {
        request.headers["X-My-Token"] = "bearer-…";
      }
    });
    await p.load("https://example.com/dash/master.mpd");
    p.selectAudioLanguage("ko", true);
    p.setTextTrackVisibility(true);
  })().catch(console.error);
</script>

자막 트랙은 getTextTracks·selectTextTrack으로 다룹니다. 재생이 안 될 때는 브라우저 콘솔의 Shaka 에러 코드, CORS 오류, 해당 브라우저의 MSE/EME 지원 여부를 먼저 봅니다. DRM 콘텐츠는 실제 키와 라이선스 없이는 재생되지 않으므로, 개발 중에는 암호화하지 않은(clear) 콘텐츠나 테스트용 키로 패키징한 스테이징 콘텐츠를 쓰는 편이 효율적입니다.

ExoPlayer / Media3(Kotlin, 개념)

신규 프로젝트는 ExoPlayer 2 대신 Media3를 쓰는 것이 권장됩니다. MediaItem에 MimeTypes.APPLICATION_MPD를 지정하는 방법과 DashMediaSource를 직접 만드는 방법 모두 가능합니다. DASH와 HLS를 함께 서비스한다면 URI와 MIME 타입으로 분기하면 됩니다.

// Media3: MediaItem + DASH + Widevine(개략) — com.google.android.exoplayer2:exoplayer, androidx.media3
import androidx.media3.common.MediaItem
import androidx.media3.common.C
import androidx.media3.common.MimeTypes

val item = MediaItem.Builder()
  .setUri("https://example.com/dash/master.mpd")
  .setMimeType(MimeTypes.APPLICATION_MPD)
  .setDrmConfiguration(
    MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
      .setLicenseUri("https://lic.example.com/widevine")
      .setLicenseRequestHeaders(mapOf("X-Token" to "…"))
      .setMultiSession(true)
      .build()
  )
  .build()
player.setMediaItem(item)
// TrackSelector: ParametersBuilder().setPreferredAudioLanguages("ko") …

HEVC/AV1, HDR 출력, 톤 매핑 지원은 기기의 SoC와 제조사 구현에 따라 크게 다릅니다. AnalyticsListener와 DecoderCounters로 드롭 프레임과 디코더 재사용 상황을 기록해 두면 기기별 문제를 좁히기 쉽습니다.

AVPlayer / FairPlay(개략, Swift)

// AVPlayer는 DASH MPD를 재생하지 않음 → HLS 매니페스트 사용
let url = URL(string: "https://example.com/hls/master.m3u8")! // iOS: HLS 1차
let player = AVPlayer(url: url)
// FairPlay: AVContentKeyRequest / AVContentKeySession + SPC(서버) → CKC(응답) — Apple 샘플·문서
// AVAssetResourceLoaderDelegate: m3u8/세그/키·인증서·사용자 인증(선택) 오버라이드

AVPlayer에 MPD URL을 넘기면 재생되지 않으므로, Apple 플랫폼에서는 HLS(fMP4/CMAF 세그멘트 가능)로 제공하고, FairPlay 키는 AVContentKeySession으로 처리합니다. 백그라운드 재생, AirPlay, PiP 같은 기능은 OS별 제약이 있으니 대상 OS 가이드를 확인하십시오.

크로스플랫폼 제품에서는 CMAF 세그멘트 한 벌에 DASH 매니페스트(Android·웹)와 HLS 매니페스트(iOS·tvOS)를 함께 만드는 이중 매니페스트 전략이 흔합니다.

본 가이드는 교육·기술 참조용이며, DRM·키·라이선스 운용은 보안·계약·지역 규정을 따릅니다.

같이 보면 좋은 글