HLS and ABR

HLS(HTTP Live Streaming)는 HTTP로 .m3u8, .m3u 플레이리스트와 짧은 미디어 세그먼트를 주고받으며 영상을 이어 재생하는 방식입니다. 네트워크나 기기 상황에 맞춰 ABR(Adaptive Bitrate, 적응형 비트레이트)로 화질(Variant)을 바꿔 끊김을 줄입니다.

인코딩을 구분하면 다음과 같습니다.

  • CBR(Constant Bit Rate): 구간마다 비트레이트가 거의 일정한 인코딩
  • VBR(Variable Bit Rate): 장면 복잡도에 따라 비트레이트가 달라지는 인코딩
  • ABR(Adaptive Bitrate): 서버가 여러 화질(Variant)을 제공하고, 클라이언트가 대역폭, 버퍼, CPU 등을 보고 그중 하나를 선택하고 전환하는 재생 방식 (HLS Master playlist와 연결) HLS는 ABR Streaming을 제공하는 프로토콜로 동영상 스트리밍에 가장 많이 쓰이는 방식 중 하나입니다.

이 문서(RFC 8216)는 HTTP 위에서 동작하는 미디어 전송 프로토콜(HLS)을 정의해 구현 간 상호 운용성을 맞춥니다. 이 프로토콜을 사용하면 클라이언트는 서버로부터 연속적인 미디어 스트림을 수신하여 표현할 수 있습니다.

이 문서에서는 프로토콜 버전 7을 다룹니다.

Media playlist and segments

HLS에서 Media playlist(미디어 플레이리스트, 보통 .m3u8,.m3u)는 media segment URI와 재생 메타데이터가 담긴 인덱스 파일입니다. 플레이어가 이 파일을 읽고, 나열된 순서대로 세그먼트를 HTTP로 요청해 이어 붙여 영상을 제공하죠.

  • VOD: 플레이리스트에 세그먼트가 모두 있고, 끝에는 대부분 #EXT-X-ENDLIST로 종료를 표시하는 경우가 많습니다.
  • Live: 같은 Media playlist URL을 반복해서 갱신함 (#EXT-X-MEDIA-SEQUENCE 등).
    • Sliding: 최근 N개 세그먼트만 유지하고 예전 항목은 삭제해 윈도우가 짧아져 지연 부담이 줄어듦
    • Event: 세그먼트를 계속 누적해서 이벤트 시작 이후 되감기 가능, DVR처럼 사용 가능
      • DVR(Digital Video Recorder): 카메라가 촬영한 영상 신호를 디지털로 변환해 하드디스크에 저장하는 영상 녹화 장치

아래가 그 Media playlist의 구성입니다.

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:9.009,
http://media.example.com/first.ts
#EXTINF:9.009,
http://media.example.com/second.ts
#EXTINF:3.003,
http://media.example.com/third.ts
#EXT-X-ENDLIST

#EXT-X-TARGETDURATION은 세그먼트 최대 길이(초)의 상한을 나타내고, #EXTINF와 URI로 각 세그먼트의 재생 시간과 가져올 미디어를 적습니다. 그보다 복잡한 태그도 같은 미디어 플레이리스트에 넣을 수 있습니다.

  • Segment: 전체 영상을 2~10초 길이의 작은 파일(.ts 포맷)으로 쪼갠 단위, 주로 다음 형식을 지원
    • MPEG-2 TS (.ts): 영상과 오디오가 mux된 본편 스트림에 많음.
    • Fragmented MPEG-4 (fMP4): #EXT-X-MAP으로 init 세그먼트를 두는 경우가 많음.
    • Packed Audio: 오디오만 담은 세그먼트(분리 오디오 Rendition용).
    • WebVTT: 자막 등 텍스트 트랙용(본편 영상 세그먼트와는 용도가 다름).

세그먼트를 암호화할 때는 #EXT-X-KEY로 복호화 방법(METHOD)과 키 URI를 선언하고, 필요하면 IV를 함께 둡니다. Master playlist에서는 #EXT-X-SESSION-KEY로 키를 미리 알려 두기도 합니다 (자세한 내용은 아래 Encryption and keys 참고)

Master playlist and variants

이전에 언급한 Media playlist는 실제 영상과 오디오 데이터 조각을 가리키는 인덱스였습니다. 하지만 우리가 VOD나 OTT를 보면 다양한 화질과 다양한 국가의 오디오를 제공하는 걸 볼 수 있죠. 이를 가능하게 하는 이유는 각 화질과 오디오를 또 색인해 주는 index가 존재하고 이게 Master playlist입니다.

그림으로 표현하면 아래와 같죠. (Master → Media → Segment) playlist

사용자가 콘텐츠를 재생하면 플레이어가 Master playlist를 받고, 선택한 VariantMedia playlist URI를 GET한 뒤, 그 안에 적힌 세그먼트를 순서대로 요청해 재생합니다. 화질 변경(ABR)은 다른 VariantMedia playlist로 넘어가는 것입니다.

Master playlist는 대략 두 종류를 중심으로 구성합니다.

  • Variant (#EXT-X-STREAM-INF): 같은 콘텐츠의 다른 화질과 비트레이트(ABR 전환 대상)를 담고, URI가 하나의 Media playlist를 가리킵니다.
  • Alternative Rendition (#EXT-X-MEDIA + GROUP-ID): 같은 콘텐츠의 다른 오디오, 자막 등 (Variant에 붙는 보조 트랙)을 제공하며, 선택 시 별도 Media playlist URI를 가리킬 수 있습니다.

그 외 Master에는 #EXT-X-SESSION-KEY, #EXT-X-I-FRAME-STREAM-INF(빠른 탐색용) 등이 더 있을 수 있습니다. Rendition 규칙은 Alternative renditions에서 다룹니다.

직접 동영상 플레이어를 만든다면 대략 다음 순서입니다.

  1. 스트림 URL로 Master playlist GET (단순 스트림은 Media playlist만 있을 수 있음)
  2. Master에서 Variant와 Rendition을 고르고, 각각이 가리키는 Media playlist URI, 대체 스트림, 키(#EXT-X-SESSION-KEY 등) 정보를 확인
  3. 선택한 Media playlist를 GET한 뒤, 안에 적힌 세그먼트 URI를 순서대로 GET (주로 .ts이고, 라이브면 playlist를 주기적으로 다시 GET)
  4. 분리 오디오 Rendition을 쓰면 영상 Media와 오디오 Media를 타임스탬프에 맞춰 같이 재생
  5. 세그먼트를 충분히 버퍼한 뒤 (암호화면 복호화 후) 연속 재생

Alternative renditions

앞의 Variant가 화질·비트레이트 줄기라면, Alternative Rendition은 같은 프로그램에 붙는 다른 오디오와 자막을 나타내는 다른 트랙입니다. Master playlist에서 Rendition은 #EXT-X-MEDIA로 선언합니다.

  • TYPE: AUDIO, VIDEO, SUBTITLES, CLOSED-CAPTIONS
  • GROUP-ID: 같은 종류 Rendition을 묶는 이름
  • NAME, LANGUAGE, URI: Rendition용 Media playlist
  • DEFAULT, AUTOSELECT: 기본 값과 자동 선택 여부

#EXT-X-STREAM-INFAUDIO="그룹ID", SUBTITLES="그룹ID" 등은 Rendition 정의가 아니라, 이 Variant와 함께 쓸 GROUP-ID를 연결하는 속성입니다. 플레이어는 Variant 하나를 고른 뒤, 연결된 그룹에서 오디오와 자막 Rendition을 고릅니다.

아래는 Master playlist 예시입니다(#EXT-X-MEDIA#EXT-X-STREAM-INF).

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="한국어",LANGUAGE="ko",URI="audio-ko.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",URI="audio-en.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="한국어",URI="subs-ko.m3u8"

#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1280x720,AUDIO="audio",SUBTITLES="subs"
https://example.com/720p.m3u8

본편 Variant의 Media playlist에 오디오가 이미 mux되어 있으면 Rendition 없이도 재생됩니다. 분리 오디오(Packed Audio)나 자막(WebVTT)을 쓰면, Variant의 영상 Media와 Rendition Media playlist 세그먼트를 타임스탬프에 맞춰 같이 재생합니다(위 플레이어 순서에서 단계 4번).

Encryption and keys

HLS는 Media Segment 본문을 암호화할 수 있습니다. 복호화에 필요한 정보는 playlist 태그로 전달됩니다. 클라이언트(플레이어)는 Media playlist의 #EXT-X-KEY를 읽고, URI로 Key file을 HTTP GET한 뒤 해당 구간의 세그먼트를 복호화해 재생합니다. Master playlist의 #EXT-X-SESSION-KEY는 여러 Variant와 Rendition이 같은 키를 쓸 때, Variant를 고르기 전에 키 정보를 미리 알려 두는 용도이며, 실제 복호화는 Media playlist의 #EXT-X-KEY를 따릅니다.

#EXT-X-KEY는 playlist에서 다음 #EXT-X-KEY까지의 Media Segment와 그 사이의 #EXT-X-MAP init 구간에 적용됩니다. 태그가 없으면 해당 구간은 평문이고, 키를 바꿀 때는 #EXT-X-KEY 줄을 추가하면 됩니다.

암호화 스트림에서 클라이언트가 하는 일은 앞 절 플레이어 순서의 3, 5번과 같습니다.

  1. Media playlist(또는 Master의 #EXT-X-SESSION-KEY)에서 METHOD, 키 URI, 필요 시 IV 확인
  2. URI로 Key file GET
  3. 세그먼트 URI로 미디어를 GET한 뒤 METHOD에 맞게 복호화하고 버퍼 후 재생

키와 세그먼트는 playlist에 적힌 HTTP URI로만 가져옵니다. RFC는 복호화 절차만 명시했을 뿐, 키를 누구에게 줄지(DRM, 로그인)는 서비스가 정합니다.

  • DRM(Digital Rights Management): 디지털 콘텐츠의 저작권을 보호하고 권한 없는 복제·무단 사용을 막기 위한 기술·관리 체계

METHOD는 다음 중 하나입니다.

  • NONE: 평문. 암호화 구간 뒤에 평문이 오면 #EXT-X-KEY:METHOD=NONE이 필요합니다.
  • AES-128: 세그먼트 통째 암호화. VOD·라이브에서 가장 흔합니다.
  • SAMPLE-AES: 샘플 단위 암호화. DRM·fMP4에서 자주 씁니다.

URI는 Key file 주소입니다. KEYFORMAT을 생략하면 identity로 간주되며, Key file은 DRM 래퍼 없이 128비트 cipher key 바이너리입니다. identity가 아닌 KEYFORMAT은 FairPlay·Widevine 등 DRM용 키 표현이고, 플레이어는 지원하는 형식만 씁니다.

IV(Initialization Vector, 초기화 벡터)는 AES-128 CBC에서 세그먼트마다 쓰는 128비트 값입니다. playlist에 IV를 적으면 그 값을 쓰고, 생략하면 identity일 때 해당 세그먼트의 Media Sequence Number를 big-endian 16바이트 IV로 씁니다

아래는 예시를 분석해보죠.

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:7794
#EXT-X-TARGETDURATION:15

#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52",KEYFORMAT="identity",IV=0x00000000000000000000000000001E72

#EXTINF:2.833,
http://media.example.com/fileSequence52-A.ts
#EXTINF:15.0,
http://media.example.com/fileSequence52-B.ts

#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=53",KEYFORMAT="identity"

#EXTINF:15.0,
http://media.example.com/fileSequence53-A.ts

위 예시를 읽으면 다음과 같습니다.

  • #EXTM3U: Extended M3U, Media playlist임을 나타냅니다.
  • #EXT-X-VERSION:3: 이 playlist가 따르는 HLS 프로토콜 버전입니다.
  • #EXT-X-MEDIA-SEQUENCE:7794: 이 playlist에 나열된 첫 세그먼트의 시퀀스 번호입니다. 이어지는 세그먼트는 7795, 7796가 옵니다.
  • #EXT-X-TARGETDURATION:15: 세그먼트 재생 길이(#EXTINF)의 상한이 15초임을 뜻합니다.
  • #EXTINF와 그 아래 URI: 각 세그먼트의 재생 시간(초)과 가져올 .ts 주소입니다.
  • #EXT-X-KEY: METHOD=AES-128, KEYFORMAT="identity", Key file URI, IV를 명시합니다. IV 끝의 1E72는 첫 시퀀스 7794(0x1E72)에 맞춘 예시 값입니다. 이 태그 아래 fileSequence52-A.ts, fileSequence52-B.ts는 키 r=52와 같은 IV 속성 값으로 복호화합니다(IV를 적었을 때는 세그먼트마다 시퀀스 번호 IV를 쓰지 않음).
  • 두 번째 #EXT-X-KEY: METHOD는 여전히 AES-128이고, 바뀐 것은 Key file URI(r=53)입니다. IV를 적지 않았으므로 fileSequence53-A.ts(시퀀스 7796)는 Media Sequence Number를 IV로 씁니다.

Server and client responsibilities

이 섹션에서는 서버가 Playlist를 생성하고 클라이언트가 미디어 재생을 위해 세그먼트를 다운로드 하는 방법을 다룹니다. (작성 중)