HLS는 왜 스트리밍의 기본값이 됐나
HLS는 Apple이 2009년에 제안한 HTTP 기반 적응형 스트리밍 프로토콜입니다. m3u8 매니페스트 파일이 미디어 세그먼트 목록을 담고 있고, 클라이언트가 이 목록을 주기적으로 갱신하며 세그먼트를 순차 다운로드해서 재생합니다. 현재 사실상 라이브/VOD를 불문하고 가장 널리 쓰이는 스트리밍 프로토콜입니다.
들어가기 전에
2009년 iPhone 3GS 시절, Flash가 모바일에서 안 돌아가는 게 문제였습니다. Apple은 "HTTP 위에서 동작하는 스트리밍"이라는 발상으로 HLS를 만들었습니다. 기존 HTTP 인프라(웹서버, CDN, 캐시)를 그대로 활용할 수 있어서 별도 스트리밍 서버가 필요 없습니다. 이 단순함이 HLS가 지배적 프로토콜이 된 핵심 이유입니다.
DASH(MPEG-DASH)가 국제 표준으로 제안됐지만, Apple 생태계가 HLS만 지원하는 바람에 실질적으로 HLS가 업계 표준이 됐습니다. iOS/Safari에서 DASH를 네이티브로 재생할 수 없어서, 모바일 사용자를 포기할 수 없는 이상 HLS는 필수입니다.
핵심 구조: 매니페스트 + 세그먼트
HLS는 크게 Master Playlist, Media Playlist, Segment 세 가지로 움직입니다. 구조도는 실제 글 발행 전에 이미지로 넣는 편이 낫습니다. 코드블럭으로 그려놓으면 모바일에서 금방 무너집니다.
| 파일 | 역할 |
|---|---|
| Master Playlist | 사용 가능한 Variant Stream 목록. 해상도, 비트레이트, 코덱 정보 |
| Media Playlist | 실제 미디어 세그먼트 URL 목록. 재생 순서대로 나열 |
| Segment | 미디어 데이터 파일. 보통 .ts (MPEG-2 TS) 또는 .m4s (fMP4) |
m3u8 주요 태그
기본 태그
| 태그 | 설명 |
|---|---|
#EXTM3U | 파일 시작 식별자. 반드시 첫 줄 |
#EXT-X-VERSION:<n> | 프로토콜 버전. 현재 최신 7 |
Media Playlist 태그
| 태그 | 설명 | 필수 |
|---|---|---|
#EXT-X-TARGETDURATION:<s> | 세그먼트 최대 길이 (초). 모든 세그먼트는 이 값을 초과할 수 없음 | ✓ |
#EXTINF:<duration>,[title] | 다음 세그먼트의 길이. 소수점 포함 가능 (v3+) | ✓ |
#EXT-X-MEDIA-SEQUENCE:<n> | 첫 세그먼트의 시퀀스 번호. 라이브에서 증가 | |
#EXT-X-DISCONTINUITY-SEQUENCE:<n> | Discontinuity 카운터. 동기화에 사용 | |
#EXT-X-ENDLIST | 더 이상 세그먼트가 추가되지 않음 (VOD 표시) | |
#EXT-X-PLAYLIST-TYPE:EVENT | 세그먼트 삭제 없이 뒤에만 추가. DVR에 활용 | |
#EXT-X-PLAYLIST-TYPE:VOD | 완결된 재생목록. 변경 없음 |
Master Playlist 태그
| 태그 | 설명 |
|---|---|
#EXT-X-STREAM-INF | Variant Stream 정의. BANDWIDTH, RESOLUTION, CODECS 등 포함 |
#EXT-X-MEDIA | 대체 렌디션 (오디오 언어, 자막 등) 정의 |
#EXT-X-I-FRAME-STREAM-INF | I-Frame 전용 스트림. 트릭 플레이(빨리감기) 용도 |
#EXT-X-SESSION-DATA | 세션 레벨 메타데이터 전달 |
#EXT-X-SESSION-KEY | Master Playlist에서 암호화 키 선언 |
세그먼트 관련 태그
| 태그 | 설명 |
|---|---|
#EXT-X-KEY | 암호화 방식 (AES-128, SAMPLE-AES) 및 키 URL |
#EXT-X-MAP | 초기화 세그먼트 (fMP4의 init segment) |
#EXT-X-BYTERANGE | 하나의 파일에서 바이트 범위로 세그먼트 분리 |
#EXT-X-DISCONTINUITY | 인코딩 불연속점 (코덱, 해상도, 타임스탬프 변경) |
#EXT-X-PROGRAM-DATE-TIME | 세그먼트와 절대 시각 매핑 (ISO 8601) |
#EXT-X-DATERANGE | 시간 범위 기반 메타데이터. SCTE-35 광고 신호 전달에 사용 |
실제 예시
Master Playlist
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Korean",LANGUAGE="ko",DEFAULT=YES,URI="audio_ko.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",DEFAULT=NO,URI="audio_en.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",AUDIO="audio"
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2",AUDIO="audio"
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=854x480,CODECS="avc1.4d401e,mp4a.40.2",AUDIO="audio"
480p.m3u8Media Playlist (라이브)
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1024
#EXTINF:6.006,
https://cdn.example.com/live/seg_1024.ts
#EXTINF:6.006,
https://cdn.example.com/live/seg_1025.ts
#EXTINF:6.006,
https://cdn.example.com/live/seg_1026.ts#EXT-X-ENDLIST가 없으면 라이브입니다. 클라이언트는 이 매니페스트를 주기적으로 다시 가져와서 새 세그먼트를 확인합니다.
라이브 동작 메커니즘
매니페스트 갱신 주기
클라이언트는 Media Playlist를 주기적으로 폴링해서 새 세그먼트를 확인합니다.
- 갱신 간격: Target Duration의 0.5~1배 (RFC 권장)
- 서버는 이전 버전 게시 후 1.5 × Target Duration 이내에 새 버전을 반드시 올려야 함
#EXT-X-ENDLIST가 없는 한 클라이언트는 계속 폴링
슬라이딩 윈도우
라이브에서 Media Playlist는 무한히 길어지지 않습니다. 오래된 세그먼트는 앞에서부터 제거되고 EXT-X-MEDIA-SEQUENCE가 증가합니다.
시점 T: seq 1024, 1025, 1026
시점 T+6s: seq 1025, 1026, 1027 ← 1024 제거, 1027 추가
시점 T+12s: seq 1026, 1027, 1028RFC 규정: #EXT-X-ENDLIST 없는 라이브 플레이리스트의 총 duration은 3 × Target Duration 이상을 유지해야 합니다.
지연 시간 분석
HLS의 지연 시간은 구조적으로 발생합니다:
지연 = 인코딩 지연 + 세그먼트 길이 + 매니페스트 갱신 간격 + CDN 전파 + 클라이언트 버퍼일반적인 지연 구성 (6초 세그먼트 기준)
| 구간 | 지연 |
|---|---|
| 인코딩 + 세그먼트 생성 | ~6초 (1 세그먼트 길이) |
| 매니페스트 갱신 대기 | ~3초 (0.5 × Target Duration) |
| CDN 전파 | ~1초 |
| 클라이언트 버퍼 (3 세그먼트) | ~18초 |
| 총 지연 | 약 20~30초 |
실무에서 HLS 기본 지연은 10~30초 범위입니다. 세그먼트 길이를 줄이면 지연이 줄지만, 그만큼 매니페스트 갱신 빈도와 HTTP 요청 수가 늘어납니다.
지연을 줄이는 방법
| 방법 | 효과 | 트레이드오프 |
|---|---|---|
| 세그먼트 길이 축소 (6s → 2s) | 지연 ~50% 감소 | HTTP 오버헤드 증가, CDN 부하 증가 |
EXT-X-PLAYLIST-TYPE:EVENT + 작은 윈도우 | DVR 유지하면서 작은 윈도우 | 매니페스트 크기 증가 |
| LL-HLS (Low-Latency HLS) | 지연 2~4초 | 별도 규격 필요 (다른 글에서 상세 설명) |
일반 HLS로는 10초 벽을 깨기 어렵습니다. 구조적 한계입니다 — 세그먼트가 "완성"되어야 클라이언트가 받을 수 있으니까요. 이 한계를 깬 게 LL-HLS이고, 다른 글에서 더 자세히 작성했습니다.
세그먼트 포맷
HLS는 두 가지 컨테이너를 지원합니다:
| 포맷 | 파일 확장자 | 특징 |
|---|---|---|
| MPEG-2 TS | .ts | 전통 방식. 세그먼트마다 PAT/PMT 포함. 단독 디코딩 가능 |
| Fragmented MP4 | .m4s + init.mp4 | CMAF 호환. 초기화 세그먼트 + 미디어 세그먼트 분리. 저장 효율 좋음 |
fMP4(CMAF)를 쓰면 HLS와 DASH가 같은 세그먼트 파일을 공유할 수 있습니다. 하나의 인코딩으로 두 프로토콜을 동시에 서빙하는 구조가 가능해집니다.
암호화와 DRM
HLS는 세그먼트 레벨 암호화를 지원합니다:
| 방식 | 설명 |
|---|---|
| AES-128 | 전체 세그먼트를 AES-128-CBC로 암호화. 키 URL에서 키 다운로드 |
| SAMPLE-AES | 미디어 샘플 단위 암호화. FairPlay DRM에 사용 |
Apple FairPlay DRM은 HLS + SAMPLE-AES 조합으로 동작합니다. #EXT-X-KEY 태그에 KEYFORMAT="com.apple.streamingkeydelivery" 형태로 선언합니다.
프로토콜 버전 히스토리
| 버전 | 추가된 기능 |
|---|---|
| 1 | 기본 HLS |
| 2 | IV 속성 지원 |
| 3 | 부동소수점 EXTINF duration |
| 4 | EXT-X-BYTERANGE, I-Frames-Only |
| 5 | KEYFORMAT, EXT-X-MAP (I-Frame 전용) |
| 6 | EXT-X-MAP (일반 사용) |
| 7 | EXT-X-SESSION-DATA, EXT-X-SESSION-KEY |
현재 대부분의 서비스는 v3~v7을 사용합니다.
마치며
HLS의 핵심을 한 문장으로: "미디어를 HTTP 위의 파일 목록으로 추상화한 것."
이 단순함이 HLS를 지배적 프로토콜로 만들었습니다. 웹서버가 파일을 서빙할 수 있으면 스트리밍이 되고, CDN이 파일을 캐시할 수 있으면 확장이 됩니다. 별도의 스트리밍 프로토콜 서버가 필요 없습니다.
대신 구조적으로 지연이 발생하는 건 감수해야 합니다. 세그먼트가 완성되어야 서빙할 수 있고, 클라이언트가 매니페스트를 폴링해야 새 세그먼트를 알 수 있으니까요. 이 문제를 해결하기 위해 LL-HLS가 나왔고, 지연 시간을 2초대까지 끌어내리는 데 성공했습니다.
Apple이 사실상 "HLS 아니면 iOS에서 재생 안 됨"을 관철시킨 것이 HLS 지배의 진짜 이유입니다. 기술적 우월성보다 생태계 락인이 표준을 만든 케이스입니다.
참고 자료
- RFC 8216 - HTTP Live Streaming: https://datatracker.ietf.org/doc/html/rfc8216
- Apple HLS Authoring Specification: https://developer.apple.com/documentation/http-live-streaming