Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

HLS 완전 이해 가이드

이 책에 대하여

이 책은 HLS 사용법이 아니라 HLS가 왜 이런 선택을 했는지, 그리고 실제로 어떻게 동작하는지를 따라가는 책이다.

모든 스트리밍 기술은 무언가를 얻기 위해 무언가를 포기한 트레이드오프의 결과물이다.

HLS는 잘 만들어진 기술이 아니라 안정성과 확장성을 위해 지연을 의도적으로 감수한 기술이다.

이 책은 두 가지를 모두 다룬다.

  • 개념과 철학
  • 구체적인 동작 (태그, 패킷, box 구조, API 수준)

이 책의 모든 장은 결국 다음 한 문장을 향한다.

HLS는 포맷이 아니라 설계 철학이며, 우리가 마주하는 모든 한계는 그 철학의 자연스러운 결과다.


이 책이 답하려는 질문

  • 왜 HLS는 라이브에서 몇 초씩 늦을까
  • m3u8 안의 각 태그는 무엇을 의미하는가
  • TS 패킷과 fMP4 box는 실제로 어떻게 생겼는가
  • 왜 LL-HLS는 데이터 포맷까지 바꿔야 했을까
  • Safari와 Chrome은 왜 같은 영상도 다르게 재생할까
  • MSE를 사용하면 실제 코드는 어떤 모습인가
  • Low Latency는 결국 무엇을 바꾸는 일인가

읽는 법

내용
1~2장HLS가 무엇이고 왜 등장했는가
3~4장HLS의 데이터 구조 (playlist와 segment)
5장그 구조가 만드는 한계 (지연)
6~7장한계를 줄이기 위한 변화 (LL-HLS, CMAF)
8장재생 제어권의 이동 (MSE)
9장콘텐츠 보호 (암호화, DRM)
10장트레이드오프로 본 스트리밍

각 장은 앞 장을 전제로 한다. 순서대로 읽기를 권장한다.


목차

시작하며. 스트리밍을 바라보는 세 가지 축

  • 안정성 ↔ 지연
  • 구조의 단순함 ↔ 성능
  • 제어권 ↔ 편의성
  • 이 책이 따라갈 흐름

1장. HLS의 정의와 등장 배경

  • 1.1 스트리밍의 본질과 초기 방식 (RTMP / RTSP)
  • 1.2 기존 스트리밍 구조의 한계
  • 1.3 Apple의 문제 정의
  • 1.4 HLS의 핵심 아이디어: 연결에서 요청으로
  • 1.5 HTTP 기반 설계의 전략적 의미
  • 1.6 시장과 기술의 교차점: Apple vs Adobe
  • 1.7 스트리밍 패러다임의 전환
  • 1.8 HLS의 설계 목표와 구조적 한계
  • 1.9 HLS vs MPEG-DASH: 같은 시기, 다른 길

2장. 왜 동영상을 잘게 나누는가

  • 2.1 동영상을 쪼개서 전달한다는 것
  • 2.2 잘게 나누는 이유: 전체 실패에서 부분 실패로
  • 2.3 HTTP 요청 구조와의 결합
  • 2.4 적응형 비트레이트(ABR)의 필요성
  • 2.5 ABR이 가능한 이유: 조각의 독립성
  • 2.6 ABR 알고리즘의 종류
    • throughput-based
    • buffer-based
    • hybrid
  • 2.7 ABR의 실제 동작 흐름

3장. HLS 구조와 Playlist(m3u8) 완전 해부

  • 3.1 HLS의 3대 구성요소
    • Playlist + Segment + Player
  • 3.2 실제 HLS 디렉토리 구조
  • 3.3 Master Playlist 완전 분석
    • 3.3.1 EXT-X-STREAM-INF의 속성들
    • 3.3.2 EXT-X-MEDIA로 정의하는 대체 렌디션
    • 3.3.3 GROUP-ID로 묶이는 트랙 구조
    • 3.3.4 EXT-X-I-FRAME-STREAM-INF
    • 3.3.5 EXT-X-INDEPENDENT-SEGMENTS
  • 3.4 Media Playlist의 주요 태그
    • 3.4.1 EXT-X-VERSION
    • 3.4.2 EXT-X-TARGETDURATION
    • 3.4.3 EXT-X-MEDIA-SEQUENCE
    • 3.4.4 EXTINF
    • 3.4.5 EXT-X-MAP (Initialization Segment)
    • 3.4.6 EXT-X-BYTERANGE
  • 3.5 VOD와 Live의 구분
    • 3.5.1 EXT-X-PLAYLIST-TYPE
    • 3.5.2 EXT-X-ENDLIST
    • 3.5.3 EXT-X-DISCONTINUITY
  • 3.6 플레이어 동작 흐름
  • 3.7 Live Streaming과 Sliding Window
  • 3.8 HLS 구조의 본질: pull 기반 클라이언트 주도
  • 3.9 이 구조가 만드는 장점과 단점

4장. 데이터의 실체 — Segment 내부 구조

  • 4.1 컨테이너와 코덱의 구분
  • 4.2 MPEG-TS 깊이 들여다보기
    • 4.2.1 188바이트 패킷의 구조
    • 4.2.2 PID, PAT, PMT
    • 4.2.3 PES (Packetized Elementary Stream)
    • 4.2.4 왜 TS는 중간부터 재생이 어려운가
  • 4.3 fMP4 깊이 들여다보기
    • 4.3.1 MP4의 “Box” 개념
    • 4.3.2 ftyp / moov / mvex
    • 4.3.3 moof / mdat
    • 4.3.4 sidx와 랜덤 액세스
    • 4.3.5 왜 fMP4는 독립 재생이 가능한가
  • 4.4 GOP과 Keyframe Alignment
    • 4.4.1 I / P / B 프레임의 차이
    • 4.4.2 왜 segment 경계는 keyframe이어야 하는가
    • 4.4.3 인코더의 segment 경계 처리
  • 4.5 코덱 호환성
    • 4.5.1 비디오: H.264 / H.265 / AV1
    • 4.5.2 오디오: AAC / AC-3 / Opus
    • 4.5.3 CODECS 문자열 읽는 법
  • 4.6 정리: 왜 TS와 fMP4가 공존하는가

5장. 왜 HLS는 느릴 수밖에 없는가

  • 5.1 라이브인데 왜 늦게 보일까
  • 5.2 전체 흐름 한눈에 보기
  • 5.3 인코더 측 지연
  • 5.4 서버 측 지연: Segment 완성 대기
  • 5.5 Playlist 구조에서 오는 간접 지연
  • 5.6 Polling 방식의 추가 지연
  • 5.7 Player Buffer 지연
  • 5.8 지연을 시간으로 다시 보기
  • 5.9 이 구조의 본질
  • 5.10 핵심 정리: Latency ≈ encoder + segment + polling + buffer

6장. HLS의 한계를 줄이기 위한 변화 — LL-HLS

  • 6.1 문제 재정의
  • 6.2 첫 번째 시도: Segment를 더 작게
  • 6.3 두 번째 변화: 생성 중 전송
    • 6.3.1 Segment와 Part의 역할 구분
    • 6.3.2 EXT-X-PART
    • 6.3.3 EXT-X-PART-INF
  • 6.4 세 번째 변화: Polling에서 Blocking으로
    • 6.4.1 EXT-X-SERVER-CONTROL
    • 6.4.2 CAN-BLOCK-RELOAD / HOLD-BACK / PART-HOLD-BACK
    • 6.4.3 Blocking Reload (_HLS_msn, _HLS_part)
    • 6.4.4 흐름 비교: polling vs blocking
  • 6.5 미래를 미리 알려주기
    • 6.5.1 EXT-X-PRELOAD-HINT
    • 6.5.2 EXT-X-RENDITION-REPORT
  • 6.6 HTTP Chunked Transfer Encoding
  • 6.7 LL-HLS의 한계
  • 6.8 한 문장 정리

7장. Low Latency를 가능하게 하는 데이터 구조 — CMAF

  • 7.1 왜 데이터 구조까지 바뀌어야 했는가
  • 7.2 TS는 왜 중간 전송이 어려운가
  • 7.3 fMP4가 LL-HLS를 가능하게 한 이유
    • 7.3.1 moof + mdat의 독립성
    • 7.3.2 init.mp4와 본체의 분리
  • 7.4 fMP4만으로 부족했던 이유
  • 7.5 CMAF가 해결한 것 ①: 쪼개는 단위
    • Segment / Fragment / Chunk
  • 7.6 CMAF가 해결한 것 ②: 전송 방식
  • 7.7 CMAF가 해결한 것 ③: 호환성
  • 7.8 Common Encryption (CENC)과 CMAF
  • 7.9 전체 흐름 정리

8장. 재생 제어권의 이동 — Flash에서 MSE까지

  • 8.1 시점 전환: 누가 재생을 통제하는가
  • 8.2 Flash 시대
  • 8.3 전환점: 애플의 선택과 HLS
  • 8.4 HTML5 video 시대
  • 8.5 Low Latency에서 드러난 한계
  • 8.6 MSE: 데이터를 직접 넣는 방식
  • 8.7 MSE API 실제 흐름
    • 8.7.1 MediaSource와 SourceBuffer
    • 8.7.2 addSourceBuffer / appendBuffer / updateend
    • 8.7.3 init segment를 먼저 넣는 이유
    • 8.7.4 SourceBuffer mode: segments vs sequence
    • 8.7.5 buffer 제거와 메모리 관리
  • 8.8 hls.js는 무엇을 해주는가
  • 8.9 Safari는 왜 다른 구조를 가지는가
  • 8.10 실무에서 발생하는 차이
  • 8.11 Managed Media Source (iOS 17+)
  • 8.12 핵심 정리

9장. 콘텐츠 보호 — 암호화와 DRM

  • 9.1 왜 스트리밍은 암호화가 필요한가
  • 9.2 EXT-X-KEY 태그 분석
  • 9.3 AES-128 (segment 단위 암호화)
  • 9.4 SAMPLE-AES (샘플 단위 암호화)
  • 9.5 DRM 시스템
    • FairPlay
    • Widevine
    • PlayReady
  • 9.6 Common Encryption (CENC)으로 가는 흐름

10장. 다시 처음으로 — 트레이드오프로 본 스트리밍

  • 10.1 우리가 따라온 질문
  • 10.2 세 가지 축으로 본 스트리밍
    • 안정성 ↔ 지연
    • 구조의 단순함 ↔ 성능
    • 제어권 ↔ 편의성
  • 10.3 HLS / LL-HLS / DASH / WebRTC의 위치
  • 10.4 어떤 기술을 언제 선택할 것인가
  • 10.5 이 책의 마지막 한 문장

부록

A. m3u8 태그 전체 레퍼런스

  • 모든 표준 태그를 한 페이지에서 조회
  • 카테고리별 정리
    • Basic
    • Media Segment
    • Media Playlist
    • Master Playlist
    • LL-HLS

B. 자주 쓰는 도구

  • ffmpeg로 HLS 만들기 (TS / fMP4)
  • hls.js / Shaka Player / Video.js 비교
  • 디버깅 도구
    • Wireshark
    • mediainfo
    • mp4box

C. 용어 정리

  • segment vs chunk vs part vs fragment
  • container vs codec
  • playlist vs manifest
  • 그 외 자주 헷갈리는 용어들

핵심 메시지

Low Latency는 “데이터를 어떻게 보내느냐“와 “누가 재생을 제어하느냐“가 함께 만들어낸 결과다.

HLS의 모든 선택은, 안정성·확장성·호환성을 위해 무엇을 포기했는지를 기억할 때 비로소 이해된다.

1장. HLS의 정의와 등장 배경

1.1 스트리밍의 본질과 초기 방식

스트리밍이란 데이터를 모두 다운로드하지 않고 전달받으면서 동시에 재생하는 방식이다.

초기 스트리밍 기술은 이 개념을 구현하기 위해 “지속적인 연결“을 기반으로 설계되었다.

대표적인 프로토콜은 다음과 같다.

프로토콜등장주된 용도
RTMPAdobe (2002)라이브 송출, Flash 재생
RTSPIETF (1998)IP 카메라, VoD 스트리밍
RTP/RTCPIETF (1996)실시간 음성/영상

이 방식의 핵심 구조는 이렇게 정리된다.

클라이언트와 서버가 연결을 유지한 상태에서 데이터를 끊임없이 전달한다

실시간성 측면에서는 효과적이었지만 인터넷 환경 전체를 고려하면 여러 한계를 가지고 있었다.


1.2 기존 스트리밍 구조의 한계

연결 기반 스트리밍은 다음과 같은 구조적 문제를 가진다.

1) 연결 상태에 대한 강한 의존성

스트리밍이 진행되는 동안 연결이 유지되어야 하므로 네트워크 변화에 매우 민감하다.

  • 네트워크 전환 (Wi-Fi → LTE)
  • 일시적인 패킷 손실
  • 연결 지연

이런 상황이 발생하면 스트리밍 품질이 즉시 영향을 받는다.


2) 재연결 비용과 복구 문제

연결이 끊어지면 단순히 이어서 재생되지 않는다.

복구를 위해 다음 과정이 필요하다.

  • 재연결 (handshake)
  • 스트림 위치 동기화
  • 버퍼 재구성

이 과정은 사용자에게 “영상이 멈추고 한참 뒤에 다시 시작되는 경험“으로 이어진다.


3) 모바일 환경에서의 비효율

연결을 계속 유지하는 구조는 다음과 같은 비용을 만든다.

  • 네트워크 인터페이스 지속 활성화
  • CPU 사용 증가
  • 배터리 소모 증가

특히 모바일 환경에서는 이 문제가 치명적이다.


4) 웹 인프라와의 비호환성

기존 스트리밍은 HTTP 기반이 아니다. 따라서 다음 문제가 발생한다.

  • CDN 캐싱 활용이 어렵다
  • 웹 트래픽과 분리된 별도 인프라가 필요하다
  • 방화벽 통과가 까다롭다

이는 대규모 서비스 확장에 큰 제약이 된다.


1.3 Apple의 문제 정의

이러한 한계를 해결하기 위해 Apple은 근본적인 질문을 던졌다.

“스트리밍은 반드시 연결을 유지해야 하는가?”

이 질문은 단순한 개선이 아니라 스트리밍의 전제를 뒤집는 접근이었다.


1.4 HLS의 핵심 아이디어

Apple이 제시한 해법은 이렇다.

스트리밍을 ’연결 기반’이 아니라 ’요청 기반’으로 바꾸자

기존 방식이 하나의 연결 위에서 데이터를 흘려보냈다면

HLS는 데이터를 여러 단위로 나누고 필요할 때마다 요청해서 가져오는 방식이다.

이 변화는 단순한 구현 차이가 아니라 시스템의 성격 자체를 바꾼다.


연결 기반 vs 요청 기반

구분연결 기반요청 기반 (HLS)
상태 관리StatefulStateless
데이터 전달지속적 스트림요청 단위
장애 대응취약유연
확장성제한적매우 높음
인프라전용 서버웹 서버 / CDN

이 구조를 한 문장으로 정리하면 이렇다.

HLS는 스트리밍을 “데이터 흐름“이 아니라 “리소스 요청“으로 재정의한 기술이다


1.5 HTTP 기반 설계의 의미

HLS는 HTTP/HTTPS 위에서 동작한다.

이 선택은 단순한 구현 편의가 아니라 전략적 결정이다.


1) 웹 인프라와의 완전한 통합

  • 기존 웹 서버 사용 가능
  • CDN 캐싱 활용 가능
  • 별도 프로토콜 불필요

스트리밍이 웹 트래픽의 일부가 된다.


2) Stateless 구조

각 요청은 독립적으로 처리된다.

  • 연결 유지 불필요
  • 실패 시 다음 요청으로 복구 가능
  • 서버 자원 효율적 사용

네트워크 환경 변화에 강한 구조가 된다.


3) 네트워크 및 방화벽 친화성

HLS는 일반 웹 요청과 동일하게 동작한다.

  • 포트 80 / 443 사용
  • HTTP GET 요청 기반

대부분의 네트워크 환경에서 자연스럽게 허용된다.

여기서 중요한 표현 하나를 기억하자.

HLS는 방화벽을 “우회하는 기술“이 아니라 애초에 차단되지 않는 방식으로 설계된 기술이다


1.6 시장과 기술의 교차점

HLS는 기술적 해결책이면서 동시에 플랫폼 전략의 일부였다.

당시 스트리밍 시장은 Adobe의 Flash 기반 기술이 지배하고 있었다.

Flash 기반 구조의 특징:

  • RTMP 중심
  • 플러그인 의존
  • 데스크탑 환경 중심

Apple의 전략

Apple은 다음 방향을 선택했다.

  • iPhone에서 Flash 미지원 선언 (2007)
  • HTML5 기반 웹 생태계 채택
  • HTTP 기반 스트리밍 도입 (HLS, 2009)

이 선택은 단순한 기술 교체가 아니라 스트리밍의 권력 구조를 바꾸는 결정이었다.

결과적으로 이렇게 정리된다.

스트리밍은 플러그인 기술에서 웹 표준 기술로 전환되었다


1.7 스트리밍 패러다임의 전환

HLS의 등장 이후 스트리밍은 다음과 같이 변화했다.

Before

  • 연결 중심
  • 특수 프로토콜
  • 환경 의존적

After

  • 요청 중심
  • HTTP 기반
  • 웹과 완전히 통합

이 변화의 핵심은 다음 한 문장으로 요약된다.

스트리밍은 더 이상 특수한 기술이 아니라, 웹 기술의 일부가 되었다


1.8 HLS의 설계 목표와 구조적 한계

HLS는 특정 목표를 기반으로 설계되었다.

설계 목표

  • 안정성
  • 확장성
  • 네트워크 호환성

그 결과 발생하는 한계

이 구조는 자연스럽게 다음 특성을 만든다.

  • 데이터가 준비된 이후 전달됨
  • 요청 단위로 처리됨
  • 클라이언트가 polling으로 갱신을 확인함

결과:

구조적으로 지연(Latency)이 발생한다

이 부분은 매우 중요하다.

HLS는 초저지연을 위해 만들어진 기술이 아니다. 안정성과 확장성을 우선한 기술이다.

이 지연이 왜 발생하는지는 5장에서 본격적으로 다룬다.


1.9 HLS vs MPEG-DASH: 같은 시기, 다른 길

HLS가 등장한 비슷한 시기에 또 다른 HTTP 기반 스트리밍이 표준화됐다.

MPEG-DASH다.

둘은 자주 비교되지만, 철학과 구조가 다르다.


같은 점

  • HTTP 위에서 동작
  • 영상을 segment로 나눠 전송
  • 적응형 비트레이트(ABR) 지원

다른 점

구분HLSMPEG-DASH
표준화 주체AppleMPEG (국제 표준)
매니페스트m3u8 (텍스트)MPD (XML)
코덱 의존성명세에 포함코덱 독립적
iOS/Safari네이티브 지원미지원
Android/Web라이브러리 필요라이브러리로 지원

왜 두 기술이 공존하는가

DASH는 기술적으로 더 유연하고 표준 친화적이다.

하지만 iOS/tvOS에서 HLS만 네이티브로 재생되기 때문에 글로벌 서비스는 HLS를 선택할 수밖에 없다.

결국 많은 서비스는 둘 다 만든다.

  • HLS → Apple 생태계용
  • DASH → Android / Web / Smart TV용

이 분기를 줄이기 위해 등장한 것이 7장에서 다룰 CMAF다.


1장 한 줄 정리

HLS는 스트리밍을 “연결“에서 “요청“으로 재정의했고, 그 대가로 지연을 감수했다.

2장. 왜 동영상을 잘게 나누는가

2.1 동영상을 “쪼개서” 전달한다는 것

HLS는 동영상을 하나의 파일로 다루지 않는다.

대신 이렇게 처리한다.

동영상을 짧은 시간 단위의 조각으로 나누고 이를 순차적으로 전달한다

이 개념을 쉽게 이해하려면 이렇게 생각하면 된다.

HLS는 영상을 한 편의 긴 영화 파일이 아니라 짧은 영상 클립들의 집합으로 다룬다.

예를 들어 10초짜리 영상이라면:

0~2초   → seg1
2~4초   → seg2
4~6초   → seg3
6~8초   → seg4
8~10초  → seg5

플레이어는 이 조각들을 순서대로 다운로드하면서 동시에 재생한다.

즉 전체 영상을 한 번에 받는 것이 아니라 조각 단위로 이어 붙이면서 재생하는 구조다.


2.2 잘게 나누는 이유: 전체 실패에서 부분 실패로

이 구조는 단순한 구현 방식이 아니라 스트리밍의 문제를 해결하기 위한 핵심 설계다.


네트워크는 항상 불안정하다

인터넷 환경은 항상 안정적이지 않다.

  • 속도가 순간적으로 떨어질 수 있다
  • 패킷 손실이 발생할 수 있다
  • 연결이 일시적으로 끊길 수 있다

큰 파일 vs 조각 단위

만약 하나의 큰 파일을 전송하는 구조라면:

  • 다운로드 중단 시 전체 흐름이 끊긴다
  • 재시작 비용이 매우 크다

반면 조각 단위로 나누면:

  • 일부 조각만 실패한다
  • 다음 조각부터 다시 요청할 수 있다

즉 HLS는 문제의 단위를 바꾼다.

“전체 실패“에서 “부분 실패“로 축소한다

이것이 HLS가 안정적인 이유다.


2.3 HTTP 요청 구조와의 결합

HLS는 HTTP 요청 기반 구조다.

이 구조의 흐름은 이렇다.

요청 → 응답 → 종료
다시 요청 → 응답 → 종료
...

동영상을 조각으로 나누면 이 흐름에 딱 맞는다.

  • 조각 하나 = 요청 하나
  • 요청 단위로 독립 처리 가능

즉 이렇게 표현할 수 있다.

조각 단위 구조는 HTTP의 특성과 정확히 맞물린다

이 매칭은 우연이 아니다. HLS는 처음부터 HTTP에 맞도록 영상을 재해석한 기술이다.


2.4 적응형 비트레이트(ABR)의 필요성

네트워크는 항상 일정하지 않다.

  • 어떤 순간에는 10 Mbps
  • 어떤 순간에는 2 Mbps

이 상황에서 항상 고화질 영상을 보내면:

  • 버퍼링 발생
  • 재생 끊김

반대로 항상 저화질이면:

  • 화질 낭비
  • 사용자 경험 저하

이 문제를 해결하기 위해 등장한 개념이 적응형 비트레이트 (Adaptive Bitrate, ABR)이다.


2.5 ABR이 가능한 이유

ABR은 HLS의 조각 구조 덕분에 가능하다.

핵심은 이것이다.

각 조각이 독립적인 영상 단위로 존재한다

같은 시간 구간이라도 여러 화질 버전이 존재할 수 있다.

시간 구간1080p720p480p
0~2초seg1_1080seg1_720seg1_480
2~4초seg2_1080seg2_720seg2_480
4~6초seg3_1080seg3_720seg3_480

플레이어는 다음과 같은 선택이 가능하다.

  • seg1은 1080p로 재생
  • seg2는 720p로 전환
  • seg3은 다시 1080p로 복귀

즉 이렇게 정리된다.

다음 조각부터 화질을 바꿀 수 있다

이것이 ABR의 핵심이다.


Keyframe Alignment

ABR이 자연스럽게 동작하려면 한 가지 조건이 필요하다.

모든 화질의 segment 경계가 같은 위치(시간)에 있어야 한다

이 정렬을 keyframe alignment라고 부른다.

자세한 내용은 4.4절에서 다룬다.

지금은 이것만 기억하면 된다.

같은 시점에서 화질을 바꿀 수 있는 이유는 모든 화질이 같은 위치에서 잘려 있기 때문이다.


2.6 ABR 알고리즘의 종류

플레이어는 단순히 랜덤으로 화질을 바꾸지 않는다. 정해진 알고리즘으로 판단한다.

대표적인 방식은 세 가지다.


① Throughput-based (대역폭 기반)

가장 단순한 방식.

플레이어가 segment를 다운로드하면서 다운로드 속도를 측정한다.

seg1: 2 MB / 0.5초 → 32 Mbps
seg2: 2 MB / 1.0초 → 16 Mbps
seg3: 2 MB / 2.0초 → 8 Mbps  ← 떨어짐

측정한 속도보다 낮은 비트레이트를 가진 화질을 다음 segment로 선택한다.

장점

  • 직관적이고 구현이 쉽다
  • 빠른 네트워크에서 잘 동작한다

단점

  • 네트워크가 출렁이면 화질도 출렁인다
  • 버퍼 상태를 고려하지 않는다

② Buffer-based (버퍼 기반)

다른 접근.

플레이어 내부 버퍼의 양을 기준으로 판단한다.

버퍼 < 5초   → 화질 낮춤 (안전 우선)
버퍼 10~20초 → 현재 화질 유지
버퍼 > 20초  → 화질 높임 (여유 있음)

장점

  • 네트워크 출렁임에 둔감하다
  • 화질 변화가 부드럽다

단점

  • 초기 재생(버퍼가 비어있을 때) 판단이 어렵다
  • 네트워크가 갑자기 좋아져도 반응이 느리다

③ Hybrid (혼합 방식)

throughput + buffer를 함께 본다.

  • 초기에는 throughput 기반으로 빠르게 결정
  • 안정화되면 buffer 기반으로 부드럽게 유지
  • 네트워크 급변 시 throughput 재반영

실무에서 쓰이는 대부분의 플레이어 (hls.js, Shaka Player, ExoPlayer 등)는 hybrid 방식을 사용한다.

대표적인 학술 알고리즘으로 BOLA, MPC, Pensieve 등이 있다.


2.7 ABR의 실제 동작 흐름

플레이어 내부에서는 다음 흐름이 반복된다.

1. 현재 화질로 segment 요청
2. 다운로드 시간 측정
3. 버퍼 잔량 확인
4. 알고리즘으로 다음 화질 결정
5. 결정된 화질의 segment 요청

이 과정을 통해 플레이어는 다음 목표를 추구한다.

끊김 없이 가능한 최고 화질을 유지한다

이 두 가지는 충돌하는 목표지만, ABR이 그 사이에서 균형을 잡는다.


2장 한 줄 정리

동영상을 쪼개는 것은 저장 방식이 아니라 안정성과 화질 적응을 동시에 얻기 위한 설계다.

3장. HLS 구조와 Playlist(m3u8) 완전 해부

3.1 HLS의 3대 구성요소

2장에서 HLS는 영상을 조각으로 나눈다고 했다.

하지만 단순히 파일을 나누는 것만으로는 스트리밍이 동작하지 않는다.

플레이어는 다음을 알아야 한다.

  • 어떤 화질이 존재하는가
  • 어떤 순서로 재생해야 하는가
  • 다음에 무엇을 가져와야 하는가

이를 해결하는 구조가 HLS의 3대 구성요소다.

Playlist  +  Segment  +  Player
   ↓           ↓           ↓
 지도        실 영상      엔진

이 중 가장 중요한 것은 Playlist (m3u8)이다.

왜냐하면

플레이어는 실제 영상이 아니라 “playlist를 기준으로” 동작하기 때문이다


3.2 실제 HLS 디렉토리 구조

이론보다 실제 구조를 보는 것이 이해가 빠르다.

hls/
├── master.m3u8
├── 1080p/
│   ├── index.m3u8
│   ├── init.mp4         (fMP4 사용 시)
│   ├── seg100.ts
│   ├── seg101.ts
│   └── ...
├── 720p/
│   ├── index.m3u8
│   ├── seg100.ts
│   └── ...
├── 480p/
│   ├── index.m3u8
│   └── ...
├── audio/               (오디오 트랙 분리 시)
│   ├── ko/index.m3u8
│   └── en/index.m3u8
└── subtitle/
    ├── ko.m3u8
    └── en.m3u8

구조 특징

  • 화질별로 디렉토리가 분리됨
  • 동일한 시간 구간이 여러 화질로 존재
  • 오디오/자막은 별도 트랙으로 분리 가능

같은 번호의 segment는 같은 시간 구간을 의미한다.

1080p/seg100.ts
720p/seg100.ts
480p/seg100.ts

이 구조 덕분에:

플레이어는 같은 시점에서 화질을 자유롭게 변경할 수 있다 (ABR)


3.3 Master Playlist 완전 분석

Master Playlist는 “이 영상에 어떤 화질/트랙이 있는가“를 알려주는 카탈로그 역할을 한다.

(공식 명칭은 Multivariant Playlist다. 실무에서는 master playlist라고 더 많이 부른다.)


기본 예시

#EXTM3U
#EXT-X-VERSION:7

#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
1080p/index.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
720p/index.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=640x360,CODECS="avc1.42c01e,mp4a.40.2"
480p/index.m3u8

플레이어는 이 파일을 보고 사용 가능한 화질 목록을 알게 된다.


3.3.1 EXT-X-STREAM-INF의 속성들

#EXT-X-STREAM-INF는 각 화질(variant)의 메타데이터를 담는다.

속성의미
BANDWIDTH최대 비트레이트 (bps), 필수
AVERAGE-BANDWIDTH평균 비트레이트
RESOLUTION해상도 (가로x세로)
CODECS코덱 식별자
FRAME-RATE프레임레이트
HDCP-LEVELDRM 출력 보호 레벨
VIDEO-RANGESDR / HDR10 / PQ
AUDIO연결된 오디오 GROUP-ID
SUBTITLES연결된 자막 GROUP-ID
CLOSED-CAPTIONS연결된 캡션 GROUP-ID

BANDWIDTH는 ABR이 화질을 고르는 핵심 기준이다.

플레이어는 현재 네트워크 속도와 BANDWIDTH를 비교해서 다음 화질을 선택한다.


3.3.2 EXT-X-MEDIA로 정의하는 대체 렌디션

영상에는 비디오 외에도 여러 오디오, 자막이 있을 수 있다.

이를 표현하는 태그가 #EXT-X-MEDIA다.

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="Korean",
  LANGUAGE="ko",DEFAULT=YES,AUTOSELECT=YES,
  URI="audio/ko/index.m3u8"

#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",NAME="English",
  LANGUAGE="en",DEFAULT=NO,AUTOSELECT=YES,
  URI="audio/en/index.m3u8"

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",NAME="Korean",
  LANGUAGE="ko",DEFAULT=YES,
  URI="subtitle/ko.m3u8"

주요 속성:

속성의미
TYPEAUDIO / VIDEO / SUBTITLES / CLOSED-CAPTIONS
GROUP-ID같은 그룹으로 묶이는 식별자
NAME사용자에게 보이는 이름
LANGUAGE언어 코드 (BCP 47)
DEFAULT기본 선택 여부
AUTOSELECT자동 선택 가능 여부
URI실제 playlist 경로

3.3.3 GROUP-ID로 묶이는 트랙 구조

GROUP-ID는 매우 중요한 개념이다.

비디오 variant는 자신과 연결될 오디오/자막 그룹을 명시한다.

#EXT-X-STREAM-INF:BANDWIDTH=5000000,
  RESOLUTION=1920x1080,
  AUDIO="aud1",       ← 오디오 그룹 연결
  SUBTITLES="sub1"    ← 자막 그룹 연결
1080p/index.m3u8

이 구조의 의미는 이렇다.

비디오는 한 줄, 오디오/자막은 별도 트랙으로 묶고, GROUP-ID로 연결한다.

이를 통해 같은 영상에 한국어/영어 오디오를 동시에 제공하거나 자막을 따로 토글할 수 있다.


3.3.4 EXT-X-I-FRAME-STREAM-INF

이 태그는 탐색/썸네일용 I-Frame 전용 playlist를 정의한다.

#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=200000,
  RESOLUTION=640x360,
  CODECS="avc1.42c01e",
  URI="iframe/index.m3u8"

왜 필요한가

영상에서 임의 위치로 점프할 때 플레이어는 keyframe(I-Frame)부터 디코딩해야 한다.

I-Frame만 모아둔 별도 playlist가 있으면:

  • 탐색이 빠르다
  • 썸네일 미리보기를 만들 수 있다
  • trick play (배속, 역재생)에 활용된다

3.3.5 EXT-X-INDEPENDENT-SEGMENTS

#EXT-X-INDEPENDENT-SEGMENTS

이 태그가 있으면 다음을 의미한다.

모든 segment는 자기 자신만으로 디코딩이 가능하다 (즉, 각 segment가 keyframe으로 시작한다)

이 보장이 있어야 ABR 전환이 어디서든 깨끗하게 일어난다.


3.4 Media Playlist의 주요 태그

Media Playlist는 실제 segment의 재생 순서를 정의하는 파일이다.

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:100
#EXT-X-MAP:URI="init.mp4"

#EXTINF:2.000,
seg100.m4s

#EXTINF:2.000,
seg101.m4s

#EXTINF:2.000,
seg102.m4s

3.4.1 EXT-X-VERSION

#EXT-X-VERSION:7

플레이어가 이 playlist를 해석하기 위해 필요한 최소 HLS 프로토콜 버전을 선언한다.

버전추가된 기능
3부동소수 EXTINF
4EXT-X-BYTERANGE
6EXT-X-MAP
7fMP4 (mp4 segment)
9EXT-X-PART (LL-HLS)

3.4.2 EXT-X-TARGETDURATION

#EXT-X-TARGETDURATION:2

segment 최대 길이 (초).

  • 짧을수록 지연 감소
  • 길수록 요청 횟수 감소

Latency vs 효율의 트레이드오프

플레이어는 이 값을 기준으로 playlist를 얼마나 자주 다시 가져올지를 결정한다.


3.4.3 EXT-X-MEDIA-SEQUENCE

#EXT-X-MEDIA-SEQUENCE:100

현재 playlist의 첫 segment 번호.

라이브 스트리밍에서는 시간이 지나면서 이 값이 증가한다.

T+0초    : MEDIA-SEQUENCE=100, segments [100,101,102]
T+2초    : MEDIA-SEQUENCE=101, segments [101,102,103]
T+4초    : MEDIA-SEQUENCE=102, segments [102,103,104]

플레이어는 이 번호로 현재 재생 위치를 추적한다.


3.4.4 EXTINF

#EXTINF:2.000,
seg100.m4s
  • 다음 segment의 재생 길이 (초)
  • 그 다음 줄에 segment URI

길이는 TARGETDURATION 이하여야 한다.


3.4.5 EXT-X-MAP (Initialization Segment)

#EXT-X-MAP:URI="init.mp4"

fMP4를 사용할 때 등장하는 태그.

init.mp4는 실제 영상 데이터를 담고 있지 않다.

대신:

  • 코덱 정보
  • 트랙 메타데이터
  • 디코더 초기화 데이터

가 들어있다.

플레이어는 init.mp4를 먼저 다운로드한 뒤 각 segment를 재생한다.

(TS 포맷은 init segment가 필요 없다. 각 TS segment에 PAT/PMT가 포함되어 있기 때문이다. 자세한 건 4.2절에서 다룬다.)


3.4.6 EXT-X-BYTERANGE

#EXTINF:2.000,
#EXT-X-BYTERANGE:1048576@0
big.mp4

#EXTINF:2.000,
#EXT-X-BYTERANGE:1048576@1048576
big.mp4

하나의 큰 파일을 바이트 범위로 잘라서 사용할 수 있다.

이렇게 하면:

  • 파일 수가 줄어든다
  • 서버 관리가 편해진다
  • HTTP Range 요청으로 부분만 가져온다

3.5 VOD와 Live의 구분

같은 m3u8 형식이지만 VODLive는 동작이 다르다.

이를 구분하는 태그들이 있다.


3.5.1 EXT-X-PLAYLIST-TYPE

#EXT-X-PLAYLIST-TYPE:VOD

값은 두 가지.

의미
VOD완성된 영상. playlist 변경 없음
EVENT추가만 가능 (라이브 녹화)

이 태그가 없으면 일반 라이브로 간주된다. (playlist가 sliding window로 갱신됨)


3.5.2 EXT-X-ENDLIST

#EXT-X-ENDLIST

더 이상 segment가 추가되지 않음을 의미한다.

  • VOD의 마지막
  • 종료된 라이브 (생방송 끝)

이 태그를 보면 플레이어는 playlist를 더 이상 다시 요청하지 않는다.


3.5.3 EXT-X-DISCONTINUITY

#EXTINF:2.000,
seg100.ts

#EXT-X-DISCONTINUITY
#EXTINF:2.000,
ad_001.ts

다음 segment가 이전 segment와 연속성이 끊긴다는 표시.

주로 다음 상황에서 사용된다.

  • 광고 삽입 (SSAI)
  • 코덱/해상도 변경
  • 타임스탬프 불연속

플레이어는 이 태그를 보면 디코더를 리셋한다.


3.6 플레이어 동작 흐름

플레이어는 다음 과정을 반복한다.

1. master.m3u8 요청
2. 화질/오디오/자막 선택
3. 각 트랙의 index.m3u8 요청
4. (필요시) init.mp4 요청
5. segment 다운로드
6. 디코딩 + 재생
7. playlist 재요청 (라이브의 경우)
8. 다음 segment 결정 → 5번으로 돌아감

핵심 특징:

플레이어는 playlist를 계속 다시 읽는다

이 polling 구조가 5장에서 다룰 지연의 근본 원인 중 하나다.


3.7 Live Streaming과 Sliding Window

라이브에서는 playlist가 계속 변경된다.


시점 T+0

#EXT-X-MEDIA-SEQUENCE:100
#EXTINF:2.0,
seg100.ts
#EXTINF:2.0,
seg101.ts
#EXTINF:2.0,
seg102.ts

시점 T+2초

#EXT-X-MEDIA-SEQUENCE:101
#EXTINF:2.0,
seg101.ts
#EXTINF:2.0,
seg102.ts
#EXTINF:2.0,
seg103.ts

특징

  • 오래된 segment는 목록에서 제거된다
  • 새로운 segment가 끝에 추가된다
  • 총 segment 개수는 일정하게 유지된다

이 구조를 Sliding Window라고 한다.

항상 최신 영상만 유지한다

뒤로 무한정 되감기는 불가능하다. (라이브 DVR 기능은 별도 설계가 필요하다)


3.8 HLS 구조의 본질

이제 전체를 정리하면 이렇다.

서버의 역할

  • playlist 제공
  • segment 제공
  • (필요시) init segment 제공

플레이어의 역할

  • playlist 분석
  • 다음 segment 계산
  • 직접 요청
  • 디코딩 및 재생

핵심:

HLS는 서버가 보내는 구조가 아니라 클라이언트가 가져오는 구조다 (pull-based)


3.9 이 구조가 만드는 장점과 단점

장점

  • ABR이 자연스럽게 가능
  • CDN 캐싱 활용 가능
  • 네트워크 안정성
  • 인프라 비용 절감

단점

  • polling 구조 → 지연 발생
  • segment 단위 → 즉시성 부족
  • playlist 갱신 주기에 의존

3장 한 줄 정리

HLS는 playlist라는 지도segment라는 조각으로 이루어지며, 플레이어는 그 지도를 보고 스스로 길을 찾아가는 구조다.

4장. 데이터의 실체 — Segment 내부 구조

4.0 이 장을 시작하며

3장까지 우리는 HLS를 “playlist + segment“라는 추상 구조로 다뤘다.

이제 한 단계 내려간다.

segment 파일 안에는 실제로 무엇이 들어있는가?

이 질문에 답하지 않으면 다음 두 가지를 이해할 수 없다.

  • 왜 TS와 fMP4가 함께 존재하는가
  • 왜 LL-HLS는 fMP4여야만 가능한가

이 장은 책에서 가장 “실물“에 가까운 장이다.


4.1 컨테이너와 코덱의 구분

먼저 두 용어를 분리해야 한다.

자주 헷갈리지만, 이 둘은 완전히 다른 계층이다.


코덱 (Codec)

영상/음성을 압축하고 푸는 방식.

  • H.264 / H.265 / AV1 — 비디오 코덱
  • AAC / AC-3 / Opus — 오디오 코덱

코덱은 픽셀/소리 데이터를 비트의 흐름으로 만든다.


컨테이너 (Container)

코덱이 만든 데이터를 담는 봉투.

  • MPEG-TS (.ts)
  • MP4 / fMP4 (.mp4, .m4s)
  • MKV, WebM, MOV

컨테이너는 다음 정보를 담는다.

  • 어떤 코덱을 썼는가
  • 각 데이터의 타임스탬프
  • 트랙 정보 (오디오/비디오 분리)

비유

코덱은 음식이고 컨테이너는 도시락통이다.

같은 음식(H.264 비디오)을 다른 도시락통(TS, fMP4)에 담을 수 있다.

HLS에서 포맷 논의는 컨테이너 논의다. 코덱은 보통 H.264 + AAC로 고정되어 있다.


4.2 MPEG-TS 깊이 들여다보기

HLS가 처음 등장할 때부터 사용된 컨테이너가 MPEG-TS (Transport Stream)이다.

원래 이건 디지털 방송(DVB, ATSC)을 위해 만들어진 포맷이다.


4.2.1 188바이트 패킷의 구조

TS의 가장 중요한 특징:

모든 데이터가 188바이트 패킷 단위로 흘러간다

┌─── 188 bytes ────────────────────┐
│ Header (4B) │ Payload (184B)     │
└─────────────────────────────────┘

Header (4 bytes)

0x47          - sync byte (모든 패킷 시작)
TEI/PUSI/TP   - 플래그 비트 (1.5바이트)
PID           - 패킷 식별자 (13비트)
TSC/AFC/CC    - 스크램블/적응/연속성 카운터

이 패킷이 끝없이 이어진 것이 TS 스트림이다.


4.2.2 PID, PAT, PMT

여러 트랙(비디오/오디오)이 한 흐름으로 섞여 있는데 어떻게 구분할까?

각 패킷의 PID (Packet Identifier)로 구분한다.

PID=0x0100 → 비디오 트랙
PID=0x0101 → 오디오 트랙
PID=0x0102 → 자막 트랙

그런데 플레이어는 “어떤 PID가 어떤 트랙“인지 어떻게 알까?

특수한 두 테이블을 통해서다.


PAT (Program Association Table)

PID=0x0000 (고정)
→ "프로그램 목록과 그 PMT의 PID를 알려준다"

PAT는 일종의 메뉴판이다.

프로그램 1 → PMT의 PID는 0x1000

PMT (Program Map Table)

PID=0x1000 (PAT가 지정)
→ "이 프로그램의 비디오/오디오 PID를 알려준다"

PMT는 상세 안내서다.

비디오: PID=0x0100, 코덱 H.264
오디오: PID=0x0101, 코덱 AAC

패킷 흐름 예시

[PAT] [PMT] [Video] [Video] [Audio] [Video] [Audio] ...

플레이어는 다음 순서로 동작한다.

1. 188바이트씩 읽는다
2. 첫 번째 PAT를 만나 → 프로그램 목록 확인
3. PMT를 만나 → 비디오/오디오 PID 학습
4. 각 PID에 해당하는 패킷을 모아 디코더로 전달

4.2.3 PES (Packetized Elementary Stream)

비디오/오디오 데이터는 바로 패킷에 들어가지 않는다.

한 번 더 포장된다.

Elementary Stream (코덱 출력 raw 비트)
  ↓
PES Packet (타임스탬프 + 헤더 + 데이터)
  ↓
TS Packet (188바이트로 잘려서)

PES 헤더에는 다음이 들어있다.

  • PTS (Presentation Timestamp) — 언제 보여줄지
  • DTS (Decoding Timestamp) — 언제 디코딩할지

이게 있어야 오디오와 비디오가 싱크가 맞춰진다.


4.2.4 왜 TS는 중간부터 재생이 어려운가

이제 구조적 이유가 보인다.

  • 한 프레임의 데이터가 여러 TS 패킷에 걸쳐 분산되어 있다
  • PAT/PMT는 주기적으로 반복되지만 랜덤 위치에서 만나려면 운이 좋아야 한다
  • PES 헤더와 본문이 떨어져 있을 수 있다

결과:

TS는 “흐름“으로 설계됐기 때문에 임의의 지점에서 잘라내기가 어렵다

이게 5장에서 다룰 latency 문제와 6장에서 fMP4가 필요해진 이유로 이어진다.


4.3 fMP4 깊이 들여다보기

fMP4 (fragmented MP4)는 MP4 컨테이너의 한 형태다.

먼저 MP4의 구조를 알아야 한다.


4.3.1 MP4의 “Box” 개념

MP4는 모든 데이터를 Box (또는 Atom) 단위로 담는다.

각 box의 구조:

┌─── Box ────────────────┐
│ size (4B)              │
│ type (4B, 예: "ftyp") │
│ data (...)             │
└────────────────────────┘

Box는 트리 구조다. 부모 box 안에 자식 box가 들어간다.


일반 MP4 구조

ftyp        ← 파일 타입
moov        ← 메타데이터 (재생 정보)
├── mvhd
├── trak (비디오)
│   └── mdia
│       └── minf
│           └── stbl
│               ├── stsd  (코덱 정보)
│               ├── stts  (타임스탬프)
│               └── stsz  (크기 테이블)
└── trak (오디오)
mdat        ← 실제 미디어 데이터

문제는 이 구조에 있다.

  • moov에는 모든 프레임의 위치 인덱스가 들어있다
  • 즉 영상이 끝나야 moov가 완성된다

라이브에서 사용할 수 없다는 뜻이다.


4.3.2 ftyp / moov / mvex

fMP4는 이 문제를 해결한다.

ftyp        ← 파일 타입
moov        ← 트랙 정의만 (인덱스 없음!)
├── mvhd
├── trak
└── mvex    ← "이후 fragment가 이어진다"는 선언

mvex (Movie Extends) box가 핵심이다.

이 box가 있으면 “moov는 미완성이고, 뒤에 fragment가 추가된다“는 뜻이다.

ftyp + moov + mvex 묶음을 Initialization Segment라 부른다. (HLS의 EXT-X-MAP이 가리키는 것)


4.3.3 moof / mdat

본체 segment는 moof + mdat 쌍의 반복이다.

moof (fragment 1)
mdat (fragment 1 data)

moof (fragment 2)
mdat (fragment 2 data)

moof (fragment 3)
mdat (fragment 3 data)

moof (Movie Fragment) 내부:

moof
├── mfhd      ← fragment 번호
└── traf      ← 트랙별 fragment 정보
    ├── tfhd  ← 트랙 헤더
    ├── tfdt  ← 타임스탬프
    └── trun  ← 샘플(프레임) 목록과 위치

mdat: 실제 인코딩된 비디오/오디오 데이터.


4.3.4 sidx와 랜덤 액세스

sidx (Segment Index)

VOD에서 자주 등장하는 box.

각 fragment의 위치와 시간 범위를 미리 인덱스로 제공한다.

플레이어는 sidx를 보고 “5분 30초 지점은 byte offset 12000000“이라는 식으로 즉시 점프할 수 있다.

라이브에서는 sidx가 없다. (어차피 끝이 정해지지 않았으므로)


4.3.5 왜 fMP4는 독립 재생이 가능한가

핵심을 보자.

  • moof는 그 자체로 fragment의 모든 메타데이터를 가진다
  • mdat은 keyframe부터 시작하는 독립 디코딩 가능한 영상을 담는다
  • 앞 fragment를 몰라도 init.mp4만 있으면 재생 가능하다

즉:

fMP4의 fragment는 그 자체로 “작은 완성품“이다

이게 LL-HLS가 가능해진 이유다.

TS:    [────────────────] 흐름 (자를 곳을 정하기 어려움)

fMP4:  [✓][✓][✓][✓][✓]    각각이 독립 조각

4.4 GOP과 Keyframe Alignment

4.4.1 I / P / B 프레임의 차이

영상 압축은 모든 프레임을 통째로 저장하지 않는다.

세 종류로 나눈다.

종류의미크기
I-Frame자기 자신만으로 완성된 프레임크다
P-Frame이전 프레임과의 차이만 저장중간
B-Frame양쪽 프레임을 참조 (앞/뒤)작다

GOP (Group of Pictures)

I-Frame 하나로 시작해서 다음 I-Frame이 나오기 전까지의 묶음.

[I, P, P, B, P, B, P, P, I, P, P, ...]
 └──────── GOP 1 ────┘└─ GOP 2 ─
  • I-Frame: keyframe이라고도 부른다
  • GOP 길이는 보통 1~5초

4.4.2 왜 segment 경계는 keyframe이어야 하는가

ABR을 다시 떠올려보자.

seg1: 1080p (GOP A)
seg2: 720p  (GOP B)  ← 화질 전환

만약 seg2가 P-Frame으로 시작한다면?

P-Frame은 이전 프레임이 있어야 디코딩 가능하다.

그런데 seg1은 1080p, seg2는 720p. 참조할 이전 프레임이 없다.

→ 디코딩 실패

그래서 규칙이 정해진다.

모든 segment는 I-Frame(keyframe)으로 시작해야 한다


4.4.3 인코더의 segment 경계 처리

이를 보장하려면 인코더는 GOP 경계와 segment 경계를 일치시켜야 한다.

GOP:     [I────][I────][I────][I────]
Segment: [────][────][────][────]
         ↑일치  ↑일치  ↑일치  ↑일치

이를 closed GOP + segment alignment라고 부른다.

추가로, 여러 화질 사이에서도 이 경계가 동일해야 한다.

1080p: [I────][I────][I────]
720p:  [I────][I────][I────]
480p:  [I────][I────][I────]
       ↑모두 같은 시점에 keyframe

이 정렬을 keyframe alignment라고 한다.

ffmpeg에서는 보통 이렇게 강제한다.

-g 60 -keyint_min 60 -force_key_frames "expr:gte(t,n_forced*2)"

(2초마다 강제 keyframe)


4.5 코덱 호환성

4.5.1 비디오 코덱

코덱별명특징
H.264AVC가장 호환성 높음, 디폴트
H.265HEVC압축 효율 ↑, 라이선스 복잡
AV1로열티 프리, 인코딩 느림
VP9YouTube가 주도, 브라우저 위주

HLS는 전통적으로 H.264. 4K HDR에는 HEVC를 쓰기 시작했다.


4.5.2 오디오 코덱

코덱특징
AACHLS의 표준
AC-3 / E-AC-35.1 채널, 영화/TV
Opus저지연, 음성 중심

4.5.3 CODECS 문자열 읽는 법

EXT-X-STREAM-INFCODECS 속성은 정해진 포맷이다.

예시:

CODECS="avc1.640028,mp4a.40.2"

비디오: avc1.640028

avc1     → H.264 (AVC)
64       → Profile (High)
0028     → Level (4.0)
Profile 코드이름
42Baseline
4DMain
64High
Level 코드 (10진 표기)의미
1F (31)Level 3.1 (720p30)
28 (40)Level 4.0 (1080p30)
33 (51)Level 5.1 (4K)

오디오: mp4a.40.2

mp4a     → MPEG-4 Audio
40       → AAC family
2        → AAC-LC
마지막 숫자의미
2AAC-LC
5HE-AAC
29HE-AAC v2

플레이어는 이 문자열을 보고 자신이 지원하는 코덱인지 판단한다.

지원하지 않으면 그 variant를 건너뛴다.


4.6 정리: 왜 TS와 fMP4가 공존하는가

이제 두 포맷의 위치가 명확해진다.

구분MPEG-TSfMP4
설계 목적방송 송출파일 + 스트리밍
데이터 형태188B 패킷 흐름Box 트리
독립 재생어려움각 fragment 가능
init 분리불필요 (PAT/PMT 반복)필요 (EXT-X-MAP)
라이브 친화성매우 높음매우 높음 (LL-HLS)
압축 효율보통좋음 (오버헤드 적음)
HEVC 지원제약 있음우수
HLS 버전v1부터v7 이후
호환성거의 모든 곳최신 플레이어

둘 다 살아남은 이유

  • TS는 호환성과 안정성
  • fMP4는 효율성과 LL-HLS

특히 fMP4는 DASH와 동일한 컨테이너라서 한 파일로 양쪽에서 재생할 수 있다.

이게 7장에서 다룰 CMAF의 핵심이다.


4장 한 줄 정리

같은 영상도 TS에서는 흐름으로 흩어지고, fMP4에서는 독립된 조각들의 묶음이 된다. 이 차이가 Low Latency를 가르는 출발점이다.

5장. 왜 HLS는 느릴 수밖에 없는가

5.1 라이브인데 왜 늦게 보일까

라이브 스트리밍을 보다 보면 이런 상황을 자주 겪는다.

방송자는 이미 어떤 말을 했는데, 시청자는 몇 초 뒤에야 그 장면을 보게 된다.

단순히 네트워크 문제나 서버 성능 문제로 생각하기 쉽지만 실제로는 더 근본적인 이유가 있다.

HLS는 구조적으로 지연이 발생하도록 설계되어 있다.

이 장에서는 그 이유를 “방송자 → 인코더 → 서버 → 시청자” 흐름으로 따라간다.


5.2 전체 흐름 한눈에 보기

먼저 흐름을 한 번 눈으로 보자.

sequenceDiagram
    participant P as 방송자
    participant E as 인코더
    participant S as 서버
    participant V as 시청자

    P->>E: 영상 캡처 (실시간)

    Note over E: GOP 단위로 인코딩
    E->>S: segment 업로드

    Note over S: playlist 업데이트

    V->>S: playlist 요청
    S-->>V: playlist 응답

    V->>S: segment 요청
    S-->>V: segment 전달

    Note over V: 버퍼링 진행
    V-->>V: 재생 시작

이 흐름에서 중요한 건 “누가 언제 기다리는가“다.

각 구간마다 발생하는 지연을 하나씩 분석한다.


5.3 인코더 측 지연

가장 앞단부터 보자.

카메라가 영상을 캡처하면 먼저 인코더가 처리해야 한다.

이 과정에서 두 종류의 지연이 발생한다.


① GOP 단위 처리

4장에서 다룬 GOP를 떠올려보자.

[I, P, P, B, P, B, P, P, I, P, P, ...]
 ←──────── GOP ────────→

B-Frame은 양쪽 프레임을 참조한다.

즉 어떤 프레임을 인코딩하려면 미래 프레임이 들어올 때까지 기다려야 한다.

시간 0   : 프레임 캡처
시간 0.05: 다음 프레임 캡처
시간 0.10: B-Frame 인코딩 가능

대략 GOP 길이의 절반 정도 지연이 발생한다. (보통 0.5~1초)


② 버퍼 + 비트레이트 제어

인코더는 품질 안정성을 위해 짧은 버퍼를 둔다.

  • VBV (Video Buffering Verifier)
  • lookahead (몇 프레임 미리 봄)

이로 인해 추가로 수백 ms가 더해진다.


인코더 측 총 지연

항목대략적 시간
GOP/B-Frame 대기0.5~1초
VBV/lookahead0.2~0.5초
합계약 1초 안팎

5.4 서버 측 지연: Segment 완성 대기

HLS의 두 번째 지연은 서버에서 segment를 완성하는 시간이다.

영상은 일정 시간 단위로 잘려서 segment가 된다.

예를 들어 2초 단위라면:

0~2초 → seg1
2~4초 → seg2

여기서 중요한 점.

segment는 실시간으로 쪼개지는 게 아니라 해당 구간의 영상이 모두 모인 뒤에 생성된다

즉 시간 흐름은 이렇다.

0초  : 촬영 시작
1초  : 아직 seg1 없음
2초  : seg1 생성 완료

이 순간 이미 최소 2초의 지연이 발생한다.


Segment 길이를 줄이면?

  • 2초 → 1초로 줄이면 → 1초 지연으로 감소
  • 0.5초로 줄이면 → 0.5초 지연

하지만 segment를 무한정 작게 만들 수는 없다.

  • 파일 수 폭증 → HTTP 요청 부담
  • CDN 캐싱 효율 저하
  • keyframe 강제로 인한 화질 손실

그래서 2초가 전통적인 sweet spot이었다. (LL-HLS는 이걸 200~500ms까지 내린다 — 6장)


5.5 Playlist 구조에서 오는 간접 지연

segment가 만들어졌다고 해서 바로 시청자가 받을 수 있는 것은 아니다.

HLS에서 플레이어는 segment를 직접 알지 못한다.

항상 playlist(m3u8)를 통해서만 정보를 얻는다.

segment 생성
  → playlist 업데이트
    → 플레이어가 playlist 다시 요청
      → 플레이어가 새 segment 인지
        → segment 다운로드 시작

이 흐름에서 중요한 구조적 특징이 나온다.

HLS는 push가 아니라 pull 방식이다.

서버가 “새 데이터 있어!“라고 알려주지 않는다. 플레이어가 직접 물어봐야 한다.


5.6 Polling 방식의 추가 지연

플레이어는 서버에게 계속 묻는다.

“새로운 segment 있어?”

이 요청은 일정 주기로 반복된다.

HLS 명세는 다음을 권장한다.

playlist는 TARGETDURATION / 2 주기로 갱신하라 (보통 1초)

이 경우 timing에 따라 이런 상황이 발생한다.

T=2.0초: segment 생성 완료
T=2.1초: 아직 다음 polling 안 옴 → 모름
T=3.0초: polling → 그제야 알게 됨

즉 segment가 준비됐어도 바로 전달되지 않는다.

요청 타이밍에 따라 추가로 0~TARGETDURATION 만큼의 지연이 발생한다.

평균적으로 TARGETDURATION의 절반 정도가 추가된다.


5.7 Player Buffer 지연

여기서 가장 큰 지연이 발생한다.

플레이어는 안정적인 재생을 위해 일부 데이터를 미리 쌓는다.

바로 재생하면 작은 네트워크 변동에도 끊긴다.

일반적인 동작:

segment 2~3개 확보 → 재생 시작

segment가 2초라면:

2초 × 3개 = 6초

즉 플레이어는 스스로 약 6초의 지연을 의도적으로 만든다.

이건 문제가 아니라 의도된 동작이다.

끊김을 막기 위해 일부러 늦게 시작한다.


왜 그렇게 많이 쌓을까

플레이어는 다음을 가정한다.

  • 네트워크는 출렁인다
  • 한 segment가 늦게 오더라도 다른 segment로 메워야 한다
  • 화질을 바꿀 여유도 있어야 한다

이 모든 안전 마진의 결과가 큰 버퍼다.

LL-HLS는 이 가정을 바꾼다. (6장에서 다룸)


5.8 지연을 시간으로 다시 보기

이제 전체를 하나로 묶어보자.

T=0초     : 촬영
T=1초     : 인코딩 완료
T=2초     : segment 생성 완료
T=2.5초   : playlist 업데이트
T=3초     : 시청자가 polling → 인지
T=3.2초   : segment 다운로드 완료
T=9초     : 버퍼 6초 쌓음 → 재생 시작

결과적으로 약 6~10초의 지연이 발생한다.

이게 우리가 일반적으로 보는 HLS 라이브 지연이다.


지연 구성 분해

구간대략적 시간
인코더약 1초
Segment 완성1~2초
Playlist 갱신 + Polling0~2초
Player Buffer4~6초
총합6~10초

대부분의 지연은 버퍼와 segment 완성에서 온다.


5.9 이 구조의 본질

지금까지 내용을 한 문장으로 정리하면 이렇다.

HLS는 “즉시 보여주는 구조“가 아니라 “안정적으로 보여주기 위해 기다리는 구조“다.

이 구조에서는

  • 인코더도 기다리고
  • 서버도 기다리고
  • 시청자도 기다리고
  • 플레이어도 기다린다

결국 모든 단계에 “대기“가 들어간다.


왜 이런 설계를 선택했을까

이건 HLS의 설계 목표와 연결된다.

HLS는 처음부터 초저지연을 목표로 만든 기술이 아니다.

대신 다음을 목표로 했다.

  • 끊김 없는 재생
  • 글로벌 확장성
  • CDN 활용
  • 네트워크 호환성

그래서 선택한 방향은 명확하다.

조금 늦더라도 안정적으로 보여주자


그래서 생긴 장단점

장점

  • 끊김이 적다
  • 네트워크 변화에 강하다
  • 대규모 서비스에 유리하다
  • 인프라 비용이 낮다

단점

  • 실시간성이 떨어진다
  • 구조적으로 지연이 발생한다
  • 인터랙티브 콘텐츠(쌍방향)에 부적합하다

5.10 핵심 정리

HLS의 지연은 다음 네 가지에서 발생한다.

  1. 인코더 측 지연 (GOP, lookahead)
  2. Segment 생성 대기
  3. Playlist polling 간격
  4. Player buffer

이걸 한 공식으로 표현하면:

Latency ≈ encoder + segment + polling + buffer

그리고 가장 중요한 문장은 이것이다.

HLS는 Low Latency 기술이 아니라 Stable Streaming 기술이다.

다음 장에서는 이 네 가지 지연 요소를 어떻게 줄였는가를 다룬다. LL-HLS의 등장이다.


5장 한 줄 정리

HLS의 지연은 버그가 아니라 설계다. 안정성을 얻기 위해 각 단계가 의도적으로 기다리도록 만들어졌다.

6장. HLS의 한계를 줄이기 위한 변화 — LL-HLS

6.1 문제 재정의

5장에서 우리는 지연의 원인을 정리했다.

Latency ≈ encoder + segment + polling + buffer

LL-HLS(Low-Latency HLS)는 이 중 인코더를 제외한 세 가지를 모두 손본 기술이다.

요소기존 HLSLL-HLS
Segment완성 후 전송생성 중 전송 (Part)
Polling일정 주기대기 + 즉시 응답 (Blocking)
Buffer6~10초1~3초
미래 정보없음Preload Hint로 미리 알림

LL-HLS는 2019년 Apple이 발표하고, 2020년 HLS 명세에 정식 편입되었다.


6.2 첫 번째 시도: Segment를 더 작게

가장 단순한 접근부터 보자.

기존 HLS에서는 보통 2~10초 단위로 segment를 만든다.

“그럼 segment를 작게 만들면 되지 않나?”

  • 2초 → 1초로 줄이면 → segment 대기 1초 감소
  • 0.5초로 줄이면 → 더 감소

이렇게 해도 일부 효과는 있다.

하지만 한계가 있다.


작게 만들기의 한계

  • 파일 수 폭증 → HTTP 요청 부담
  • 매 segment마다 keyframe → 화질/용량 손실
  • CDN 캐싱 효율 저하
  • playlist 자체가 거대해짐

무엇보다 중요한 건 이거다.

여전히 “완성된 뒤에 전송한다“는 구조는 그대로다.

근본 문제가 안 풀린다.

LL-HLS는 더 본질적인 변화를 시도한다.


6.3 두 번째 변화: 생성 중 전송

여기서 HLS 구조를 크게 바꾸는 아이디어가 나온다.

segment가 완성될 때까지 기다리지 말고, 만드는 중간부터 전달하자

기존 방식:

0~2초 데이터 수집 → seg1 완성 → 전송 시작
                  ↑ 2초 대기

LL-HLS:

0~0.2초 → 즉시 전달 (part 1)
0.2~0.4초 → 즉시 전달 (part 2)
0.4~0.6초 → 즉시 전달 (part 3)
...
2초까지 → segment 완성 (마지막 part 포함)

이 변화의 핵심 표현:

“완성 후 전송“에서 “생성 중 전송“으로


6.3.1 Segment와 Part의 역할 구분

여기서 헷갈리기 쉬운 부분을 정리하자.

  • Segment: 플레이어가 사용하는 재생 단위
  • Part: 그 안을 더 잘게 나눈 전송 단위

하나의 segment는 여러 part로 구성된다.

seg100 ┐
  ├── part 0  (0.0~0.2초)
  ├── part 1  (0.2~0.4초)
  ├── part 2  (0.4~0.6초)
  ...
  └── part 9  (1.8~2.0초)

플레이어는

  • 일반 시청자: segment를 받아서 재생
  • 라이브 최전선: part를 받아서 즉시 재생

이걸 동시에 지원한다.

(공식 용어로 part는 종종 chunk와 혼용되지만, HLS 명세는 Part라는 용어를 쓴다. DASH/CMAF에서는 chunk다.)


6.3.2 EXT-X-PART

각 part는 m3u8에 #EXT-X-PART 태그로 선언된다.

#EXT-X-PART:DURATION=0.200,URI="seg100.0.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.1.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.2.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.3.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.4.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.5.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.6.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.7.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.8.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.200,URI="seg100.9.m4s"

#EXTINF:2.000,
seg100.m4s

주요 속성:

속성의미
URIpart 파일 위치
DURATIONpart 길이 (초)
INDEPENDENT자기 자신만으로 디코딩 가능한가 (keyframe 시작)
BYTERANGE큰 파일에서 부분만 가져오는 경우
GAP누락된 part 표시

INDEPENDENT=YES인 part는 ABR 전환 지점으로 쓸 수 있다.


6.3.3 EXT-X-PART-INF

#EXT-X-PART-INF:PART-TARGET=0.200

이 playlist의 part 기본 길이.

플레이어는 이 값을 보고 polling 간격이나 버퍼 크기를 조정한다.


6.4 세 번째 변화: Polling에서 Blocking으로

5장에서 polling 지연을 다뤘다.

플레이어가 일정 주기로 묻기 때문에 새 데이터가 즉시 도착하지 않는다.

LL-HLS는 이 구조를 바꾼다.

기존:   요청 → 응답 → 종료 → 다시 요청
LL-HLS: 요청 → (서버가 기다림) → 새 데이터 생기면 즉시 응답

이를 Blocking Playlist Reload라고 한다.


6.4.1 EXT-X-SERVER-CONTROL

이 동작을 가능하게 하는 핵심 태그.

#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,
  PART-HOLD-BACK=0.6,
  HOLD-BACK=6.0,
  CAN-SKIP-UNTIL=12.0

서버가 클라이언트에게 “나는 이런 동작을 지원한다“고 알리는 태그다.


6.4.2 주요 속성

CAN-BLOCK-RELOAD=YES

서버가 blocking reload를 지원한다는 선언.

이게 있어야 클라이언트가 “새 데이터 나올 때까지 기다려“라고 요청할 수 있다.


HOLD-BACK

플레이어가 라이브 최전선에서 얼마만큼 떨어진 지점에서 재생해야 하는가.

HOLD-BACK=6.0  →  최신에서 6초 뒤에서 재생

이 값이 클수록 안정적이지만 지연이 늘어난다.

LL-HLS에서는 더 짧은 버퍼를 위해 PART-HOLD-BACK도 함께 쓴다.


PART-HOLD-BACK

part 단위 재생 시 최전선에서 얼마나 떨어질지.

PART-HOLD-BACK=0.6  →  최신 part에서 0.6초 뒤

보통 PART-TARGET × 3 정도 값을 쓴다.


CAN-SKIP-UNTIL

playlist 크기를 줄이기 위한 기능.

GET /playlist.m3u8?_HLS_skip=YES

오래된 segment 정보를 생략하고 최근 데이터만 전송한다.

playlist가 거대해지는 라이브 환경에서 유용하다.


6.4.3 Blocking Reload (_HLS_msn, _HLS_part)

플레이어는 특별한 쿼리 파라미터로 blocking을 요청한다.

GET /index.m3u8?_HLS_msn=101&_HLS_part=3

의미는 이렇다.

“media-sequence 101의 part 3이 playlist에 추가될 때까지 응답을 미뤄줘”

서버는 그 시점이 올 때까지 HTTP 응답을 보내지 않고 대기한다.

  • 새 데이터가 생기면 → 즉시 응답
  • 너무 오래 걸리면 → 타임아웃

플레이어는 응답을 받자마자 다음 part를 요청한다.


6.4.4 흐름 비교: Polling vs Blocking

기존 polling:

T=0.0  요청 → 응답 (데이터 없음)
T=1.0  요청 → 응답 (데이터 없음)
T=2.0  요청 → 응답 (seg100 추가!)  ← 1~2초 지연

LL-HLS blocking:

T=0.0  요청 (대기 시작)
T=2.0  서버에서 seg100 생성됨 → 즉시 응답
       플레이어: 다시 요청 (대기 시작)
T=2.2  part 1 생성 → 즉시 응답
T=2.4  part 2 생성 → 즉시 응답

차이는 분명하다.

정해진 주기로 묻지 않고, 새 데이터가 나오는 순간 받는다.


6.5 미래를 미리 알려주기

LL-HLS는 또 다른 카드를 꺼낸다.

“다음에 무엇이 올지 미리 알려주자”


6.5.1 EXT-X-PRELOAD-HINT

#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg101.0.m4s"

의미:

이 part는 아직 만들어지지 않았지만, 다음에 이 URL로 만들어질 것이다.

플레이어는 이 URL로 선행 요청을 보낼 수 있다.

서버는 데이터가 만들어지는 즉시 응답에 흘려보낸다. (HTTP Chunked Transfer로)

이를 통해 polling 자체를 건너뛴다.


6.5.2 EXT-X-RENDITION-REPORT

ABR을 위한 힌트.

각 화질의 m3u8에 들어간다.

#EXT-X-RENDITION-REPORT:URI="../720p/index.m3u8",
  LAST-MSN=101,
  LAST-PART=2

의미:

“현재 화질을 1080p로 보고 있다면, 720p의 최신 상태는 media-sequence=101, part=2다”

플레이어가 화질을 바꿀 때 다시 처음부터 따라잡을 필요가 없다.

이전에는 720p로 전환하려면:

  • 720p playlist 요청
  • 어디까지 왔는지 파악
  • 따라잡기 위해 시간 소요

LL-HLS에서는:

  • 미리 알려준 위치로 즉시 점프

6.6 HTTP Chunked Transfer Encoding

LL-HLS의 backbone 기술이다.

데이터가 다 만들어지기 전에 응답을 시작하고, 만들어지는 대로 전송한다.


일반 HTTP 응답

HTTP/1.1 200 OK
Content-Length: 1024000

[1024000 bytes 모두 한 번에]

서버는 데이터를 다 만든 뒤 보낸다.


Chunked Transfer

HTTP/1.1 200 OK
Transfer-Encoding: chunked

200\r\n
[512 bytes]\r\n      ← 1차 chunk

200\r\n
[512 bytes]\r\n      ← 2차 chunk

...

0\r\n\r\n            ← 종료

서버는 chunk 단위로 데이터를 흘려보낸다.

  • Content-Length 불필요
  • 응답이 시작되어도 끝이 정해지지 않음
  • 클라이언트는 받자마자 처리

LL-HLS와의 결합

T=0.0  서버: 응답 시작 (헤더 전송)
T=0.2  서버: part 1 chunk 전송 → 플레이어가 즉시 디코딩
T=0.4  서버: part 2 chunk 전송 → 즉시 디코딩
...
T=2.0  서버: 마지막 chunk + 종료

이 구조 덕분에 LL-HLS는 TCP 연결 하나로 part들이 흘러나오는 경험을 만든다.

push가 아닌데 push처럼 보인다.


6.7 LL-HLS의 한계

지금까지의 변화를 정리하면 이렇다.

LL-HLS는 세 방향으로 개선했다.

변화방법
Segment 대기Part로 잘게 나눠 즉시 전송
Playlist pollingBlocking Reload
Player bufferPart 단위로 줄임
전환 지연Preload Hint / Rendition Report

지연은 다음 수준으로 줄어든다.

구분일반적 지연
기존 HLS6~10초
LL-HLS2~3초
잘 튜닝된 LL-HLS1~2초

한계

하지만 중요한 사실이 남아 있다.

구조 자체는 여전히 HLS다.

다음 요소는 그대로 유지된다.

  • HTTP 요청 기반
  • playlist 중심 동작
  • player buffer 필요
  • CDN 캐싱 모델

그래서 1초 미만의 초저지연은 LL-HLS의 영역이 아니다.

  • 1~3초 지연 → LL-HLS / LL-DASH
  • 200~500ms 지연 → WebRTC
  • 비교: 일반 HLS → 6~10초

6.8 한 문장 정리

LL-HLS는 HLS의 정체성을 지키면서 지연을 현실적으로 줄이기 위한 개선이다.

다음 장에서는 이 모든 변화가 가능하게 한 데이터 구조의 변화 — fMP4와 CMAF를 다룬다.


6장 한 줄 정리

LL-HLS는 “기다림이 있던 모든 자리“에 부분 전송과 즉시 응답을 끼워 넣은 결과다.

7장. Low Latency를 가능하게 하는 데이터 구조 — CMAF

7.1 왜 데이터 구조까지 바뀌어야 했는가

6장에서 LL-HLS의 핵심은 “기다림을 줄이는 것“이었다.

  • segment를 part로 나누고
  • part는 만들어지는 즉시 전달
  • polling 대신 blocking reload

이 중 가장 본질적인 변화는 이것이다.

완성된 파일이 아니라, 생성 중인 데이터를 전달한다

여기서 문제가 생긴다.

기존 HLS에서 사용하던 .ts 구조로는 이걸 깔끔하게 구현할 수 없다.

동작 방식의 변화는 데이터 형식의 변화를 요구한다.


7.2 TS는 왜 중간 전송이 어려운가

4장에서 이미 다뤘지만 다시 떠올려보자.

TS는 본래 방송 송출을 위해 만들어진 포맷이다.

  • 188바이트 패킷이 끝없이 이어진다
  • 한 프레임이 여러 패킷에 분산된다
  • 디코딩 정보(PAT/PMT)는 주기적으로 반복된다
[PAT][PMT][V][V][A][V][A][PAT][PMT][V][V]...

이 구조에서 다음 시도를 한다고 해보자.

“지금 0.4초까지 만든 데이터를 일단 보내자”

문제는 이렇다.

  • 마지막 프레임이 잘려 있을 수 있다
  • PES 헤더와 본문이 분리되어 있을 수 있다
  • PAT/PMT가 아직 이 부분에 안 왔을 수 있다

결국 받는 쪽은 디코딩에 실패한다.

그래서 TS에서는 완성된 segment 단위로만 안전하게 전송할 수 있었다.


7.3 fMP4가 LL-HLS를 가능하게 한 이유

4장에서 본 fMP4 구조를 다시 보자.

init.mp4  (ftyp + moov + mvex)
  ↑ 초기 정보

moof + mdat   ← fragment 1
moof + mdat   ← fragment 2
moof + mdat   ← fragment 3
...

여기서 중요한 포인트가 두 개 있다.


7.3.1 moof + mdat의 독립성

moof + mdat 하나의 짝은 그 자체로 완성된 작은 영상이다.

각 fragment에는:

  • 어떤 시간 구간인지 (tfdt)
  • 몇 개의 샘플(프레임)이 들어있는지 (trun)
  • 각 샘플의 크기와 오프셋
  • 코덱은 init에서 받았으므로 별도 정보 불필요

즉 앞 fragment를 몰라도 이 짝만으로 재생이 가능하다.

이 구조 때문에 다음이 가능해진다.

  • 전체 segment를 기다릴 필요 없음
  • fragment 하나가 완성되면 즉시 전송 가능
  • 받는 쪽도 받자마자 즉시 디코딩

7.3.2 init.mp4와 본체의 분리

fMP4는 초기 정보와 본체를 분리한다.

init.mp4 (한 번만 다운로드)
  ↓
  moof + mdat
  moof + mdat
  moof + mdat   ← 계속 추가

이 분리가 가져오는 효과:

  • segment마다 코덱 정보 반복 불필요
  • fragment 크기가 작아진다
  • 새 segment를 처음부터 받아도 init만 있으면 즉시 재생

이건 HLS의 EXT-X-MAP 태그가 가리키는 것과 정확히 일치한다.


TS vs fMP4 한 줄 요약

TS: 전체가 하나의 흐름 fMP4: 완전한 조각들이 이어진 구조

이 차이 덕분에 “생성 중 전송“이 자연스럽게 가능해진다.


7.4 fMP4만으로 부족했던 이유

여기까지 보면 fMP4로 모든 문제가 풀린 것처럼 보인다.

하지만 실제 서비스에서는 또 다른 문제가 생긴다.


표준화의 부재

각자 방식대로 쪼개기 시작하면 호환이 깨진다.

  • 어떤 인코더는 0.5초 단위
  • 어떤 인코더는 1초 단위
  • 어떤 인코더는 비트레이트가 다름

게다가 같은 fMP4여도

  • HLS용 fMP4
  • DASH용 fMP4

가 서로 미묘하게 달랐다.

예:

항목HLS fMP4DASH fMP4
박스 순서약간 다름약간 다름
brand 코드mp42iso6
메타데이터 위치차이 있음차이 있음

이 차이 때문에

하나의 fMP4 파일을 두 프로토콜에서 그대로 쓸 수 없었다.

서비스 입장에서는 같은 영상을 두 번 인코딩하고 두 번 저장해야 했다.

저장 비용과 CDN 비용이 두 배가 된다.


7.5 CMAF가 해결한 것 ①: 쪼개는 단위

이 문제를 해결하기 위해 등장한 것이 CMAF (Common Media Application Format)이다.

ISO 표준으로 2018년 발표되었다.

CMAF는 새로운 구조가 아니다.

CMAF는 fMP4를 어떻게 사용할지 정의한 규칙이다.

세 가지를 표준화한다.

  • 쪼개는 단위
  • 전송 방식
  • 호환성

데이터의 계층

CMAF는 데이터를 다음 계층으로 정의한다.

Track       (예: 1080p 비디오 트랙)
  └── Segment       (플레이어가 인식하는 단위)
        └── Fragment    (내부 논리 단위)
              └── Chunk       (실제 전송 단위)
단위역할
SegmentURL로 요청되는 단위
Fragmentmoof+mdat 한 쌍
Chunk가장 작은 전송 단위

용어 정리 (헷갈리는 부분)

HLS 용어CMAF 용어
PartChunk
SegmentSegment
(없음)Fragment

HLS의 Part와 CMAF의 Chunk는 사실상 같은 것이다. 서로 다른 표준이라 이름이 다를 뿐이다.


핵심 규칙

CMAF는 chunk에 강한 제약을 둔다.

각 chunk는 독립적으로 디코딩 가능해야 한다.

즉 chunk는 자기 안에 완전한 moof + mdat 쌍을 포함한다.

이 규칙 덕분에 중간부터 받아도 재생이 가능하다.


7.6 CMAF가 해결한 것 ②: 전송 방식

과거에는 fMP4를 써도 segment 전체를 다 만든 뒤 보내는 경우가 많았다.

CMAF는 이렇게 정의한다.

chunk가 만들어지면 즉시 전송해야 한다.

이때 사용하는 방식이 6장에서 본 HTTP Chunked Transfer Encoding이다.

[CMAF chunk 1 만들어짐] → 즉시 chunk 응답
[CMAF chunk 2 만들어짐] → 즉시 chunk 응답
[CMAF chunk 3 만들어짐] → 즉시 chunk 응답
...
[Segment 종료]         → 응답 종료

데이터가 파일처럼 한 번에 내려오는 것이 아니라 스트림처럼 계속 흘러오게 된다.


LL-HLS와의 관계

LL-HLS는 CMAF의 chunk를 Part 단위로 m3u8에 노출한다.

#EXT-X-PART:DURATION=0.200,URI="seg100.0.m4s"
#EXT-X-PART:DURATION=0.200,URI="seg100.1.m4s"

같은 chunk를 DASH 측에서는 다른 방식으로 노출한다. (LL-DASH의 availabilityTimeOffset)

데이터는 같고, 안내 방식만 다르다.


7.7 CMAF가 해결한 것 ③: 호환성

가장 큰 효과는 여기서 나온다.

CMAF는 fMP4 구조를 HLS와 DASH가 공유하도록 통일한다.

[ CMAF segment ]
       ↓
   ┌───┴────┐
HLS     DASH
용     용

동일한 파일이 두 프로토콜에서 그대로 재생 가능하다.


서비스 입장에서의 이득

CMAF 이전:

원본 → HLS용 fMP4 (1080p, 720p, 480p)
     → DASH용 fMP4 (1080p, 720p, 480p)
     [저장 2배, CDN 2배]

CMAF 이후:

원본 → CMAF (1080p, 720p, 480p)
       ↑ 한 벌만 생성
     → HLS playlist (m3u8)
     → DASH playlist (MPD)
       ↑ 매니페스트만 두 종류

저장 비용과 CDN 비용이 거의 절반으로 줄어든다.


7.8 Common Encryption (CENC)과 CMAF

CMAF의 또 다른 중요한 측면은 암호화 호환성이다.

DRM은 9장에서 자세히 다루지만, 여기서는 CMAF와의 관계만 짚는다.


과거의 문제

DRM 시스템마다 암호화 방식이 달랐다.

시스템암호화 방식
FairPlay (Apple)cbcs (AES-CBC + samples)
Widevine (Google)cenc 또는 cbcs
PlayReady (Microsoft)cenc 또는 cbcs

암호화 방식이 다르면 동일한 파일을 여러 DRM에서 못 쓴다.


CENC의 등장

Common Encryption (CENC)는 암호화 방식을 표준화한 ISO 규격이다.

CENC가 정의하는 두 방식:

cenc  (AES-CTR 모드)
cbcs  (AES-CBC + sample 단위)

CMAF는 이 중 cbcs를 권장한다. 모든 주요 DRM이 cbcs를 지원하기 때문이다.


CMAF + CENC의 효과

같은 CMAF 파일을 FairPlay, Widevine, PlayReady에서 모두 재생 가능하다.

원본 영상
  → CMAF + cbcs 암호화 (한 벌)
  → 라이선스만 DRM별로 발급

이전에는 DRM마다 별도 인코딩 + 별도 저장. 이제는 한 벌 + 라이선스 분리.


7.9 전체 흐름 정리

지금까지의 흐름을 정리하면 이렇다.

[HLS]
  안정성을 위해 설계됨
  → 구조 때문에 지연 발생

[5장: 지연 분석]
  encoder + segment + polling + buffer

[6장: LL-HLS]
  → "만들면서 전달하자"
  → Part, Blocking Reload, Preload Hint

[하지만 TS로는 구현 불가]
  → 데이터가 흐름 형태

[7장: fMP4 도입]
  → 독립 디코딩 가능한 조각 구조
  → 그러나 호환성 문제

[CMAF 표준화]
  → 쪼개는 단위 통일
  → 전송 방식 표준화
  → HLS와 DASH 호환
  → CENC로 DRM까지 통합

이 흐름의 끝에 우리가 있다.


비교표

구분기존 HLSLL-HLS + CMAF
컨테이너MPEG-TSfMP4 (CMAF)
전송 단위Segment 전체Chunk 즉시
응답 방식일반 HTTPChunked Transfer
호환 표준HLS 전용HLS + DASH 공통
암호화AES-128CENC (cbcs)
일반적 지연6~10초1~3초

7장 한 줄 정리

CMAF는 새로운 기술이 아니라 fMP4를 “어떻게 쓸지“를 약속한 규칙이며, 그 덕분에 데이터가 HLS와 DASH의 경계를 넘는다.

8장. 재생 제어권의 이동 — Flash에서 MSE까지

8.1 시점 전환: 누가 재생을 통제하는가

지금까지 우리는 서버와 전송 구조를 중심으로 살펴봤다.

이제 시점을 바꿔야 한다.

브라우저는 이 데이터를 어떻게 재생하는가

여기서 중요한 관점은 하나다.

스트리밍 기술은 단순히 포맷이 바뀐 것이 아니라, “재생을 누가 통제하느냐“가 계속 바뀌어 온 과정이다

이 흐름은 Flash → HTML5 video → MSE로 이어진다.


8.2 Flash 시대: 모든 제어권은 어도비에

초기 웹 스트리밍은 Flash가 중심이었다.

브라우저는 Flash Player를 실행해주는 역할만 하고 실제 재생은 Flash 내부에서 이루어졌다.

브라우저 → Flash 실행
Flash → 서버와 통신 (RTMP)
Flash → 직접 재생

이 구조에서 제어권은 명확했다.

모든 제어권은 어도비(Flash)에 있었다.


Flash의 장점

  • 실시간 스트리밍 가능 (RTMP)
  • 플레이어를 자유롭게 구현
  • 강력한 영상 처리 기능

Flash의 단점

  • 플러그인 설치 필요
  • 보안 취약점 다수
  • CPU / 배터리 소모 큼
  • 모바일, 특히 iOS에서 지원 안 됨

특히 마지막이 결정타였다.


8.3 전환점: 애플의 선택과 HLS

애플은 iPhone에서 Flash를 지원하지 않기로 결정했다.

이건 단순한 기술 문제가 아니라 플랫폼 주도권, 보안, 효율성을 고려한 전략적 선택이었다.

Flash 대신 애플이 밀어붙인 것이 HLS다.

이 선택으로 스트리밍 방식이 바뀐다.

  • RTMP → HTTP 기반
  • 플러그인 → 브라우저 기본 기능

그리고 재생 구조도 바뀐다.

Flash 내부가 아니라, 브라우저가 직접 재생을 담당하게 된다.


8.4 HTML5 video 시대

Flash 이후 등장한 것이 HTML5 video 태그다.

<video src="stream.m3u8" controls></video>

매우 단순하다.

개발자는 영상의 위치(URL)만 넘겨주면 된다.

그러면 브라우저가 내부적으로:

  • m3u8 다운로드
  • Segment 목록 확인 및 순차 다운로드
  • 내부 버퍼 관리 및 디코딩
  • 화면 렌더링

을 모두 처리한다.

브라우저가 다운로드부터 재생까지 모든 것을 처리한다.

이 구조에서 제어권은 이렇게 바뀐다.

어도비 → 브라우저


새로 생긴 한계

이 방식은 표준 기반이고 모바일에서도 잘 동작했다.

하지만 새로운 한계가 생긴다.

  • 언제 데이터를 가져올지 제어 불가
  • buffer 크기 제어 불가
  • 지연 시간 제어 불가
  • Low Latency 구현 어려움

즉 개발자는 단순히

“이 영상 틀어줘”

라고 요청만 할 수 있고 재생 알고리즘에는 개입할 수 없다.


8.5 Low Latency에서 드러난 한계

LL-HLS 구조에서는 상황이 달라진다.

데이터가 더 이상 완성된 파일로 오지 않는다. 작은 chunk 단위로 계속 도착한다.

이걸 제대로 재생하려면:

  • chunk가 오면 바로 재생에 반영
  • buffer를 최소화
  • 지연을 계속 조절

하지만 HTML5 video 구조에서는 불가능하다.

왜냐하면

브라우저가 “파일 단위“로 처리하기 때문이다.

브라우저는 chunk 흐름을 인지하지 않는다. 이 모델로는 LL-HLS의 잠재력을 못 살린다.

새로운 방식이 필요해진다.


8.6 MSE: 데이터를 직접 넣는 방식

MSE (Media Source Extensions)는 이 문제를 해결하기 위해 등장했다.

기존 방식과 가장 큰 차이는 이것이다.

URL을 넘기는 방식에서 바이너리 데이터를 직접 넘기는 방식으로 바뀐다


기존 HTML5 video 방식

개발자 → URL 전달
브라우저 → 알아서 다운로드 + 버퍼링 + 재생

MSE 방식

JavaScript → 직접 데이터 다운로드 (fetch)
JavaScript → buffer에 데이터 직접 추가
브라우저 → 재생만 수행

즉 구조가 이렇게 바뀐다.

개발자가 데이터를 가져오고 브라우저는 그걸 재생만 한다.


비유

기존 방식:

“이 주소에 있는 영상 틀어줘”

MSE 방식:

“영상 데이터를 내가 가져왔으니까 이걸 그대로 틀어줘”

이 변화의 의미는 크다.

재생 제어권이 개발자에게 넘어간다.


8.7 MSE API 실제 흐름

여기서는 MSE를 실제로 어떻게 쓰는지 본다.

8.7.1 MediaSource와 SourceBuffer

MSE는 두 가지 핵심 객체로 구성된다.

const video = document.querySelector('video');
const mediaSource = new MediaSource();

video.src = URL.createObjectURL(mediaSource);
MediaSource         = 가상의 영상 소스
SourceBuffer        = 데이터를 담는 버퍼

MediaSource여러 SourceBuffer를 가질 수 있다. (보통 비디오용 1개, 오디오용 1개)


8.7.2 addSourceBuffer / appendBuffer / updateend

기본 사용 흐름:

mediaSource.addEventListener('sourceopen', () => {
  // 1) SourceBuffer 생성
  const sourceBuffer = mediaSource.addSourceBuffer(
    'video/mp4; codecs="avc1.640028, mp4a.40.2"'
  );

  // 2) init.mp4 다운로드 후 buffer에 추가
  fetch('init.mp4')
    .then(r => r.arrayBuffer())
    .then(data => sourceBuffer.appendBuffer(data));

  // 3) 각 segment를 차례로 추가
  sourceBuffer.addEventListener('updateend', () => {
    if (queue.length > 0) {
      const next = queue.shift();
      fetch(next)
        .then(r => r.arrayBuffer())
        .then(data => sourceBuffer.appendBuffer(data));
    }
  });
});

핵심 메서드:

메서드역할
addSourceBuffer(mimeType)새 SourceBuffer 생성
appendBuffer(data)바이너리 데이터 추가
remove(start, end)시간 범위 삭제 (메모리 관리)
endOfStream()더 이상 데이터 없음 알림

핵심 이벤트:

이벤트발생 시점
sourceopenMediaSource 사용 준비 완료
updateendappendBuffer/remove 작업 완료
error디코딩 실패 등

8.7.3 init segment를 먼저 넣어야 하는 이유

7장에서 본 fMP4 구조를 떠올려보자.

init.mp4    ← 코덱 정보, 트랙 메타데이터
moof + mdat ← 본체

브라우저(디코더)는 init이 없으면 본체를 해석할 수 없다.

따라서 MSE도 동일한 순서를 요구한다.

1. appendBuffer(init.mp4)      ← 반드시 먼저
2. appendBuffer(segment1.m4s)
3. appendBuffer(segment2.m4s)

순서를 바꾸면 error 이벤트가 발생한다.


8.7.4 SourceBuffer mode: segments vs sequence

SourceBuffer에는 mode라는 속성이 있다.

sourceBuffer.mode = 'segments';  // 기본값
// 또는
sourceBuffer.mode = 'sequence';
mode동작
segmentssegment에 담긴 타임스탬프를 그대로 사용
sequence들어온 순서대로 이어 붙임 (타임스탬프 무시)

라이브 스트리밍에서는 보통 segments. 임의의 클립을 이어 붙일 때는 sequence.


8.7.5 buffer 제거와 메모리 관리

오래 재생할수록 buffer가 쌓인다.

브라우저는 메모리 한계에 부딪히면 다음 에러를 던진다.

QuotaExceededError

이를 방지하려면 오래된 부분을 직접 삭제한다.

// 현재 시점에서 60초 이전 데이터를 삭제
const currentTime = video.currentTime;
sourceBuffer.remove(0, currentTime - 60);

라이브에서는 보통 현재 시점 ± 일정 범위만 유지한다.


MSE 코드 전체 흐름 (요약)

1. MediaSource 생성
2. video.src에 URL.createObjectURL로 연결
3. sourceopen 이벤트 대기
4. addSourceBuffer로 비디오/오디오 SourceBuffer 생성
5. init segment를 appendBuffer
6. 각 segment(또는 part)를 순서대로 appendBuffer
7. 필요하면 remove로 오래된 buffer 정리
8. 끝나면 endOfStream

8.8 hls.js는 무엇을 해주는가

위 코드를 직접 짜는 건 무겁다.

  • m3u8 파싱
  • segment URL 계산
  • TS → fMP4 변환 (브라우저는 TS 직접 재생 불가)
  • ABR 알고리즘
  • 에러 복구

이걸 모두 해주는 라이브러리가 hls.js다.


hls.js 내부 흐름 (요약)

1. m3u8 파일 다운로드 + 파싱
2. Master playlist → variant 선택
3. Media playlist 주기적 재요청
4. 각 segment fetch
5. TS인 경우 → 내부에서 fMP4로 변환 (demux + remux)
6. fMP4 chunk를 MSE SourceBuffer에 appendBuffer
7. ABR 알고리즘으로 다음 화질 결정
8. 에러 발생 시 재시도 / fallback

복잡한 부분은 모두 라이브러리가 처리한다.

개발자는 보통 이렇게만 쓴다.

if (Hls.isSupported()) {
  const hls = new Hls();
  hls.loadSource('master.m3u8');
  hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // Safari: 네이티브 HLS 재생
  video.src = 'master.m3u8';
}

비슷한 라이브러리들

라이브러리특징
hls.jsHLS 전용. 가장 많이 쓰임
Shaka PlayerHLS + DASH 모두 지원. Google이 만듦
Video.jsUI/UX 중심. 다양한 플러그인
dash.jsDASH 전용 레퍼런스 구현체

8.9 Safari는 왜 다른 구조를 가지는가

여기서 Safari가 자연스럽게 등장한다.

지금까지 흐름은 “개발자가 점점 더 많은 제어권을 가진다“는 방향이었다.

그런데 Safari는 이 흐름을 매우 늦게 수용했다.


이유: 애플의 철학

애플은 HLS의 종주국이다.

iOS / macOS 시스템 전체(AVFoundation)가 HLS 재생에 최적화되어 있다.

애플은 배터리 효율과 시스템 안정성을 위해 개발자가 하드웨어를 직접 제어하는 MSE 방식을 오랫동안 제한했다.

video 태그 → m3u8 URL
Safari 내부 플레이어가 처리

이 구조에서는 재생 제어권이 여전히 브라우저 내부에 있다.


다른 브라우저는 왜 MSE로 갔나

  • 네이티브 스트리밍 기능이 부족했다
  • HLS 네이티브 재생 미지원
  • 자유로운 플레이어 구현 필요

이런 사정 때문에 Chrome / Firefox / Edge는 MSE 기반으로 발전했다.

Safari는 이미 완성된 HLS 플레이어를 가지고 있었기에 그 구조를 유지했다.


8.10 실무에서 발생하는 차이

이 구조 차이는 실제 서비스에서 차이를 만든다.


Chrome / Firefox / Edge

  • MSE 기반
  • 개발자가 buffer / 지연 / 화질을 직접 제어

개발자가 버퍼를 2초로 강제하고 지연을 조절할 수 있다


Safari (iOS)

  • Native HLS 기반
  • 내부 로직에 따라 재생

개발자가 아무리 요청해도 iOS 시스템 플레이어가 “안정성을 위해 버퍼를 5초 이상 쌓겠다“라고 판단하면 개발자가 개입할 수 없다.

이런 차이가 만들어내는 결과:

  • 같은 LL-HLS 스트림인데 Chrome은 1.5초 지연, Safari는 4초 지연이 나온다
  • 개발자가 Safari의 지연을 줄일 직접적인 방법이 없다

따라서:

Safari는 “성능이 부족한 것“이 아니라, “시스템이 제어권을 꽉 쥐고 놓아주지 않는 것“이다.


8.11 Managed Media Source (iOS 17+)

최근 변화가 있다.

iOS 17 / macOS Sonoma부터 Safari는 Managed Media Source (MMS)를 도입했다.

이는 MSE의 변형이다.

개발자가 데이터를 직접 공급하지만, 메모리/배터리 관리는 시스템이 한다.


MSE vs MMS

항목MSEMMS
데이터 공급개발자개발자
메모리 관리개발자시스템
배터리 관리개발자시스템
가능한 곳Chrome 등Safari (iOS 17+)

이 변화로 Safari에서도 LL-HLS를 더 적극적으로 다룰 수 있게 되었다.

다만:

  • iOS 17 미만 기기는 여전히 native HLS만
  • MMS는 MSE의 1:1 대체가 아니라 제약형

완전한 통일까지는 시간이 걸린다.


8.12 핵심 정리

이 장의 핵심을 정리하면 이렇다.

재생 제어권의 이동

Flash       → 어도비가 통제
HTML5 video → 브라우저가 통제
MSE         → 개발자가 통제
MMS         → 개발자 + 시스템 협업

그리고 가장 중요한 한 문장:

Low Latency는 “데이터를 어떻게 보내느냐“와 “누가 재생을 제어하느냐“가 함께 만들어낸 결과다.

서버 측 구조(LL-HLS, CMAF)만 갖춰져도 재생 측 제어권이 없으면 초저지연을 만들 수 없다.

이 두 축이 함께 진화해 왔다는 사실이 이 책의 가장 큰 그림이다.


8장 한 줄 정리

스트리밍의 진화는 누구의 손에 재생이 있는가를 끊임없이 옮겨 온 역사다.

9장. 콘텐츠 보호 — 암호화와 DRM

9.1 왜 스트리밍은 암호화가 필요한가

HLS는 HTTP 기반이다.

이건 큰 장점이지만, 보안 측면에서는 약점이기도 하다.

  • segment 파일은 일반 HTTP 응답
  • URL만 알면 누구나 다운로드 가능
  • 다운로드 후 영상 추출이 너무 쉽다

OTT 서비스, 유료 콘텐츠, 라이선스가 있는 영상은 이 상태로 두면 안 된다.

누구나 받을 수 있지만, 받은 사람이 재생할 수는 없게 만들자.

이게 스트리밍 암호화의 목적이다.


두 가지 레이어

HLS의 보호는 크게 두 가지 레이어로 나뉜다.

레이어역할
암호화데이터 자체를 잠근다
DRM키를 안전하게 관리하고 배포한다

암호화는 자물쇠, DRM은 그 자물쇠의 키 관리 체계라고 보면 된다.


9.2 EXT-X-KEY 태그 분석

HLS는 m3u8 안에 암호화 방식과 키 위치를 명시한다.

#EXT-X-KEY:METHOD=AES-128,
  URI="https://example.com/keys/key1.bin",
  IV=0x9c7d8f8e1b6a4c5d2e8f9a0b1c3d4e5f

주요 속성:

속성의미
METHOD암호화 방식 (NONE, AES-128, SAMPLE-AES)
URI복호화 키를 받을 URL
IVInitialization Vector (필요 시)
KEYFORMAT키 포맷 (DRM 시스템 식별)
KEYFORMATVERSIONS포맷 버전

METHOD 값들

의미
NONE암호화 없음 (기본)
AES-128전체 segment를 AES-128로 암호화
SAMPLE-AES샘플(프레임) 단위로 부분 암호화

SAMPLE-AES는 fMP4 + CMAF 환경에서 SAMPLE-AES-CTR로 발전한다.


키 교체

EXT-X-KEY는 m3u8 중간에 다시 등장할 수 있다.

#EXT-X-KEY:METHOD=AES-128,URI="key1.bin"
seg100.ts
seg101.ts
seg102.ts

#EXT-X-KEY:METHOD=AES-128,URI="key2.bin"
seg103.ts
seg104.ts

이렇게 하면 주기적으로 키를 바꾸는 rotation이 가능하다. 하나의 키가 노출돼도 피해를 제한할 수 있다.


9.3 AES-128 (segment 단위 암호화)

가장 단순한 방식.

각 segment 파일 전체를 AES-128로 암호화한다.

seg100.ts (평문)
   ↓ AES-128 + IV
seg100.ts (암호문, 같은 이름)

플레이어 동작:

1. m3u8 다운로드
2. EXT-X-KEY에서 키 URI 확인
3. 키 다운로드 (HTTPS)
4. segment 다운로드
5. 키 + IV로 복호화
6. 디코딩 → 재생

장점

  • 구현이 매우 단순
  • 모든 HLS 플레이어가 지원
  • CDN 캐싱 가능 (암호문 그대로 캐싱)

단점

  • 키만 얻으면 전체 영상 복호화 가능
  • 키 배포 자체의 보안이 약함 (URI 노출 시 누구나 다운로드)
  • 실제 상용 서비스에는 불충분

AES-128은 개인 콘텐츠나 가벼운 보호 용도다.

상용 OTT는 DRM 기반을 쓴다.


9.4 SAMPLE-AES (샘플 단위 암호화)

좀 더 정교한 방식.

모든 데이터를 암호화하는 것이 아니라, 미디어 샘플(프레임)만 부분 암호화한다.

암호화 대상:

  • 비디오 프레임의 일부 영역
  • 오디오 샘플

암호화하지 않는 부분:

  • 컨테이너 헤더
  • 메타데이터
  • 일부 NAL 유닛

왜 부분만 암호화하는가

하드웨어 디코더와의 호환성

전체를 암호화하면 하드웨어 디코더가 데이터를 식별할 수 없다.

샘플 단위 암호화는 컨테이너는 평문으로 두고 실제 미디어 비트만 잠그는 방식.

성능

전체 암호화는 CPU 부담이 크다. 일부만 암호화하면 보호 효과는 비슷하면서 처리 비용 감소.


CMAF에서의 발전: cbcs

7장에서 본 Common Encryption (CENC)를 다시 떠올려보자.

CMAF + LL-HLS 환경에서는 주로 cbcs 모드를 사용한다.

cbcs = AES-CBC + sample 단위 + 패턴 암호화

특징:

  • sample 단위 (SAMPLE-AES 계열)
  • 패턴 적용 가능 (예: 10블록 중 1블록만 암호화)
  • 하드웨어 디코더 친화적

이게 현재 HLS + DRM의 표준이다.


9.5 DRM 시스템

DRM(Digital Rights Management)은 키를 안전하게 배포하고 재생 환경을 통제하는 시스템이다.

AES-128에서 키 URI 노출이 문제였다면, DRM은 그 키를 보호된 채널로 전달한다.

1. 플레이어가 콘텐츠를 요청
2. 라이선스 서버에 인증 요청
3. 라이선스 서버: 권한 확인
4. 디바이스의 CDM(Content Decryption Module)에
   암호화된 키를 전달
5. CDM이 OS/HW 보호 영역에서 복호화

키가 JavaScript나 메모리에 노출되지 않는다.


주요 DRM 세 가지

DRM회사주요 플랫폼
FairPlayAppleSafari / iOS / tvOS
WidevineGoogleChrome / Android / Firefox
PlayReadyMicrosoftEdge / Windows / Xbox

상용 서비스는 보통 세 가지를 모두 지원한다. 플랫폼별로 다른 DRM이 강제되기 때문이다.


FairPlay

Apple 생태계의 DRM.

  • iOS, tvOS, macOS Safari에서 사용
  • HLS와 결합 (fairplay keyformat)
  • cbcs 암호화 모드만 지원
#EXT-X-KEY:METHOD=SAMPLE-AES,
  URI="skd://license-server.com/getkey?asset=xyz",
  KEYFORMAT="com.apple.streamingkeydelivery",
  KEYFORMATVERSIONS="1"

skd://는 FairPlay 전용 스킴이다.


Widevine

Google이 만든 DRM.

  • 세 가지 보안 레벨 (L1, L2, L3)
    • L1: 하드웨어 보호 (4K 가능)
    • L2: 하드웨어 + 일부 SW
    • L3: 소프트웨어만 (저화질 한정)
  • Chrome, Firefox, Android
  • cenc 또는 cbcs 지원

EME (Encrypted Media Extensions) API로 호출된다.


PlayReady

Microsoft의 DRM.

  • Edge, Windows, Xbox
  • 일부 스마트 TV
  • cenc 또는 cbcs 지원

기업/방송 환경에서 많이 쓰인다.


멀티 DRM 구조

상용 서비스의 일반적 흐름:

원본 영상
  ↓
CMAF + cbcs 암호화 (한 벌)
  ↓
Manifest (HLS / DASH)
  ↓
플레이어 환경 감지
  ├─ Safari/iOS  → FairPlay 라이선스
  ├─ Chrome/Android → Widevine 라이선스
  └─ Edge/Windows → PlayReady 라이선스

영상 파일은 한 벌이고, 라이선스만 DRM별로 발급된다.

이게 가능한 이유는

세 DRM 모두 cbcs 모드를 지원하기 때문이다.

이 표준화가 없었다면 DRM마다 영상을 다시 인코딩해야 했다.


9.6 CENC로 가는 흐름

지금까지의 흐름을 정리하면 이렇다.

[HLS v1]
  AES-128 segment 암호화
  ↓
[HLS v4]
  SAMPLE-AES 도입
  하드웨어 친화적
  ↓
[CMAF + CENC]
  cbcs 표준화
  HLS + DASH 호환
  ↓
[멀티 DRM]
  한 파일 / 세 라이선스

스트리밍 보호는 암호화 형식 → 표준화 → DRM 통합의 순서로 진화했다.

이 흐름의 끝에서

  • 인프라는 공유
  • 보호는 각 플랫폼 DRM

이라는 형태가 자리잡았다.


실무에서 자주 보는 조합

콘텐츠 종류일반적 선택
개인 동영상 / 가벼운 보호AES-128
라이브 / 일반 OTTCMAF + Widevine/FairPlay/PlayReady (cbcs)
프리미엄 4K HDR위 + HDCP, L1 강제
학습/사내 콘텐츠AES-128 + 인증 토큰

9장 한 줄 정리

스트리밍 보호는 “파일을 잠그는 일“이 아니라 “키를 누구에게, 어떻게 줄 것인가“의 문제다.

10장. 다시 처음으로 — 트레이드오프로 본 스트리밍

10.1 우리가 따라온 질문

이 책의 도입부에서 다음 질문들로 시작했다.

  • 왜 HLS는 라이브에서 늦을까
  • m3u8 안의 각 태그는 무엇을 의미하는가
  • TS 패킷과 fMP4 box는 실제로 어떻게 생겼는가
  • 왜 LL-HLS는 데이터 포맷까지 바꿔야 했을까
  • Safari와 Chrome은 왜 다르게 동작할까
  • MSE 코드는 어떤 모습인가
  • Low Latency는 결국 무엇을 바꾸는 일인가

이제 답을 정리할 수 있다.


정리

질문
왜 HLS는 늦은가안정성을 위해 의도적으로 기다리도록 설계됨
m3u8 태그3장, 6장에서 다룸
TS / fMP4 내부4장에서 다룸
왜 LL-HLS는 fMP4인가독립 디코딩 가능한 조각이 필요했기 때문
Safari가 다른 이유시스템이 재생 제어권을 가지고 있음
MSE 코드데이터를 직접 buffer에 넣는 구조
Low Latency데이터 구조 + 재생 제어의 합작품

하지만 답만 모은다고 이 책의 메시지가 되지는 않는다.

진짜 메시지는 그 답들 뒤의 패턴이다.


10.2 세 가지 축으로 본 스트리밍

도입부에서 제시한 세 가지 축을 다시 보자.

지금 우리는 각 챕터를 거치며 이 축들이 어떻게 충돌하는지 모두 보았다.


축 ①: 안정성 ↔ 지연

챕터어떻게 등장했는가
1장HLS는 안정성을 위해 지연을 감수했다
5장그 지연은 4단계에 걸쳐 누적된다
6장LL-HLS는 각 단계의 대기를 잘게 쪼개 줄였다

이 축은 끝까지 사라지지 않는다.

버퍼를 줄이면 끊김의 위험이 늘어난다.

WebRTC조차 이 트레이드오프를 피해 가지 못한다. 다만 어느 쪽에 더 무게를 두느냐가 다를 뿐이다.


축 ②: 구조의 단순함 ↔ 성능

챕터어떻게 등장했는가
1장HTTP만 쓰는 단순함을 위해 RTMP를 버렸다
3장playlist + segment라는 단순 구조
6장단순함을 깨지 않으면서 성능을 끌어올리는 시도
7장CMAF로 HLS와 DASH의 단순한 통일

LL-HLS는 단순함을 완전히 버리지 않고 성능을 끌어올렸다는 점에서 의미가 있다.

그래서 1초 미만의 초저지연은 결국 다른 프로토콜(WebRTC)에 양보한다.

한 기술이 모든 것을 잘할 수는 없다.


축 ③: 제어권 ↔ 편의성

챕터어떻게 등장했는가
8장Flash → 브라우저 → 개발자
8장Safari는 시스템이 통제
8장MMS는 절충

이 축은 재생 측의 이야기다.

  • 제어권이 개발자에게 있으면 자유롭지만 복잡
  • 제어권이 시스템에 있으면 편하지만 제약

Low Latency가 가능했던 건 이 제어권이 개발자에게 넘어왔기 때문이다.


10.3 HLS / LL-HLS / DASH / WebRTC의 위치

세 가지 축 위에 주요 기술을 놓아보면 이렇다.


지연 vs 안정성

실시간성 (낮을수록 빠름)
←                          →
WebRTC        LL-HLS    DASH      HLS
~200ms        ~2s       ~5s       ~8s

인프라 친화도

기술HTTP/CDN 활용인프라 비용
HLS◎ 완전 활용낮음
LL-HLS○ 활용 (chunked)낮음~중간
DASH◎ 완전 활용낮음
WebRTC✕ 별도 인프라높음

종합 비교

항목HLSLL-HLSDASHWebRTC
일반 지연6~10초1~3초5~10초0.2~0.5초
컨테이너TS / fMP4fMP4fMP4RTP
매니페스트m3u8m3u8MPD(없음)
iOS 네이티브
Android 네이티브
인프라HTTPHTTPHTTPUDP + 시그널링
확장성매우 높음높음매우 높음보통
양방향
DRM

10.4 어떤 기술을 언제 선택할 것인가

이 책의 끝에서 가장 실용적인 질문은 이거다.

무엇을 골라야 하는가?

답은 단순하다.

무엇이 가장 중요한가에 따라 다르다.


일반적인 결정 기준

1. iOS/Apple TV 호환이 필수인가?
   ├─ YES → HLS 또는 LL-HLS
   └─ NO  → DASH도 고려

2. 1초 이하 초저지연이 필요한가?
   ├─ YES → WebRTC (또는 HESP, SRT)
   └─ NO  → HLS / DASH 계열

3. 라이브인가 VOD인가?
   ├─ 라이브 + 짧은 지연 → LL-HLS
   ├─ 라이브 + 일반 지연 → HLS
   └─ VOD → HLS / DASH

4. 글로벌 CDN 활용이 중요한가?
   ├─ YES → HTTP 기반 (HLS, DASH)
   └─ NO  → 다른 옵션 가능

5. 콘텐츠 보호가 중요한가?
   ├─ YES → CMAF + 멀티 DRM
   └─ NO  → AES-128 정도

시나리오별 추천

시나리오추천
일반 VOD 서비스HLS (CMAF, fMP4)
OTT 라이브 (스포츠 등)LL-HLS + CMAF + 멀티 DRM
1:1 비디오 통화WebRTC
게임/주식 라이브 (지연 민감)WebRTC, HESP, SRT
학습 콘텐츠HLS + AES-128 또는 토큰
사내 회의 녹화HLS (VOD)

10.5 이 책의 마지막 한 문장

10개 장을 거쳐 우리가 본 것은 사실 하나의 같은 이야기였다.

  • HLS의 등장은 → 연결을 포기하고 요청을 얻은 결과였고
  • HLS의 지연은 → 안정성을 얻기 위해 즉시성을 포기한 결과였고
  • LL-HLS의 등장은 → 그 포기를 최소화하기 위한 정교한 절충이었고
  • CMAF는 → 단순함과 효율을 동시에 잡기 위한 표준이었고
  • MSE의 등장은 → 제어권을 개발자에게 넘긴 결정이었다

모든 단계가 같은 패턴이다.

무언가를 얻기 위해 무언가를 포기한 결과.


한 줄로

이 책의 모든 장이 향했던 그 한 문장을 다시 적는다.

HLS는 포맷이 아니라 설계 철학이며, 우리가 마주하는 모든 한계는 그 철학의 자연스러운 결과다.

기술은 바뀐다. HLS는 결국 어딘가에서 새로운 기술로 대체될 것이다.

하지만 트레이드오프라는 관점은 바뀌지 않는다.

다음 기술을 만날 때도

  • 이 기술은 무엇을 얻기 위해
  • 무엇을 포기했는가

만 묻는다면, 이 책이 했던 일은 그 새 기술에서도 그대로 반복된다.


10장 한 줄 정리

좋은 기술이란 없다. 무엇을 골랐는지 알고 쓰는 기술이 있을 뿐이다.


책을 마치며

여기까지 함께한 여정을 정리하면 이렇다.

1장   HLS는 왜 만들어졌는가
2장   왜 영상을 잘게 나누는가
3장   playlist와 segment의 구조
4장   segment 내부의 실체
5장   왜 HLS는 느린가
6장   그 한계를 어떻게 줄였는가 (LL-HLS)
7장   그것을 가능하게 한 데이터 구조 (CMAF)
8장   브라우저는 어떻게 재생하는가 (MSE)
9장   콘텐츠를 어떻게 보호하는가
10장  이 모두를 어떻게 바라볼 것인가

이제 m3u8을 봐도 TS 패킷을 봐도 fMP4 box를 봐도 MSE 코드를 봐도

“이게 왜 이렇게 생겼지?”

라는 질문에 스스로 답할 수 있을 것이다.

그게 이 책이 하고 싶었던 일이다.

부록 A. m3u8 태그 레퍼런스

본문에서 다룬 m3u8 태그를 한 페이지에서 조회할 수 있도록 정리했다.

각 태그가 등장한 본문 위치도 함께 표기했다.


A.1 기본 태그 (Basic)

태그의미본문
#EXTM3Uplaylist 시작 표식3.3
#EXT-X-VERSION최소 프로토콜 버전3.4.1

A.2 Media Segment 태그

각 segment에 적용되는 태그.

태그의미본문
#EXTINFsegment 길이 + URI3.4.4
#EXT-X-BYTERANGE파일 내 바이트 범위3.4.6
#EXT-X-DISCONTINUITY시퀀스 불연속3.5.3
#EXT-X-KEY암호화 키 정보9.2
#EXT-X-MAPInitialization Segment3.4.5
#EXT-X-PROGRAM-DATE-TIME실제 시각 매핑

A.3 Media Playlist 태그

playlist 전체에 적용되는 태그.

태그의미본문
#EXT-X-TARGETDURATIONsegment 최대 길이3.4.2
#EXT-X-MEDIA-SEQUENCE첫 segment 번호3.4.3
#EXT-X-PLAYLIST-TYPEVOD / EVENT3.5.1
#EXT-X-ENDLISTplaylist 종료3.5.2
#EXT-X-I-FRAMES-ONLYI-Frame만 있는 playlist
#EXT-X-INDEPENDENT-SEGMENTS모든 segment 독립 디코딩3.3.5
#EXT-X-START재생 시작 위치

A.4 Master Playlist 태그

여러 variant를 묶는 playlist 전용 태그.

태그의미본문
#EXT-X-STREAM-INFvariant 정의3.3.1
#EXT-X-MEDIA대체 렌디션 (오디오/자막)3.3.2
#EXT-X-I-FRAME-STREAM-INFI-Frame 전용 playlist3.3.4
#EXT-X-SESSION-DATA세션 메타데이터
#EXT-X-SESSION-KEY세션 레벨 키

A.5 LL-HLS 태그

Low-Latency HLS 전용 태그.

태그의미본문
#EXT-X-PARTpartial segment 정의6.3.2
#EXT-X-PART-INFpart 기본 길이6.3.3
#EXT-X-SERVER-CONTROLblocking reload 등 서버 기능6.4.1
#EXT-X-PRELOAD-HINT다음 part 미리 알림6.5.1
#EXT-X-RENDITION-REPORT다른 화질 상태 알림6.5.2
#EXT-X-SKIPskip된 segment 정보

A.6 자주 쓰는 속성 모음

EXT-X-STREAM-INF

속성예시의미
BANDWIDTH5000000최대 bps
AVERAGE-BANDWIDTH4800000평균 bps
RESOLUTION1920x1080해상도
CODECS“avc1.640028,mp4a.40.2”코덱 식별
FRAME-RATE30.000fps
HDCP-LEVELNONE / TYPE-0 / TYPE-1출력 보호
VIDEO-RANGESDR / PQ / HLG색공간
AUDIO“aud1”오디오 그룹
SUBTITLES“sub1”자막 그룹

EXT-X-MEDIA

속성예시의미
TYPEAUDIO트랙 종류
GROUP-ID“aud1”그룹 식별자
NAME“Korean”표시명
LANGUAGE“ko”언어 (BCP 47)
DEFAULTYES / NO기본 선택
AUTOSELECTYES / NO자동 선택 가능
FORCEDYES / NO강제 선택 (자막)
CHANNELS“2”오디오 채널 수
URI“audio/ko.m3u8”playlist 위치

EXT-X-KEY

속성예시의미
METHODAES-128 / SAMPLE-AES / NONE암호화 방식
URI“https://…/key”키 위치
IV0x…초기 벡터
KEYFORMAT“identity” / “com.apple.streamingkeydelivery” 등DRM 식별
KEYFORMATVERSIONS“1”포맷 버전

EXT-X-PART

속성예시의미
URI“seg.0.m4s”part 위치
DURATION0.200part 길이 (초)
INDEPENDENTYES / NO독립 디코딩 가능 여부
BYTERANGE“1000@0”바이트 범위
GAPYES누락 표시

EXT-X-SERVER-CONTROL

속성예시의미
CAN-BLOCK-RELOADYESblocking reload 지원
HOLD-BACK6.0최전선 거리 (초)
PART-HOLD-BACK0.6part 단위 최전선 거리
CAN-SKIP-UNTIL12.0skip 기능 활성 임계값
CAN-SKIP-DATERANGESYESDATERANGE도 skip 가능

A.7 한눈에 보는 전체 흐름

[Master Playlist]
  #EXTM3U
  #EXT-X-VERSION
  #EXT-X-INDEPENDENT-SEGMENTS
  #EXT-X-MEDIA          (오디오/자막)
  #EXT-X-STREAM-INF     (각 화질)
  #EXT-X-I-FRAME-STREAM-INF (탐색용)

[Media Playlist]
  #EXTM3U
  #EXT-X-VERSION
  #EXT-X-TARGETDURATION
  #EXT-X-MEDIA-SEQUENCE
  #EXT-X-PLAYLIST-TYPE  (VOD / EVENT)
  #EXT-X-MAP            (fMP4)
  #EXT-X-KEY            (암호화)
  #EXT-X-SERVER-CONTROL (LL-HLS)
  #EXT-X-PART-INF       (LL-HLS)

  #EXT-X-PART (LL-HLS)
  #EXTINF + segment URI
  #EXT-X-DISCONTINUITY (필요 시)

  #EXT-X-PRELOAD-HINT (LL-HLS, 끝부분)
  #EXT-X-RENDITION-REPORT (LL-HLS)

  #EXT-X-ENDLIST (VOD/종료)

A.8 명세 위치

  • HLS 공식 명세: RFC 8216 (HLS Protocol) https://datatracker.ietf.org/doc/html/rfc8216
  • LL-HLS 확장: HLS 명세에 통합됨 (2nd Edition draft 등)
  • Apple의 공식 문서: https://developer.apple.com/streaming/

각 태그의 모든 속성과 제약은 원본 명세를 확인하는 것이 가장 정확하다.

부록 B. 자주 쓰는 도구

본문에서 다룬 개념을 실제로 만들고/확인하고/디버깅할 때 사용하는 도구들을 정리한다.


B.1 인코딩과 패키징: ffmpeg

가장 강력한 오픈소스 미디어 도구.

HLS / fMP4 / CMAF 생성에 모두 사용된다.


기본 HLS 생성 (TS)

ffmpeg -i input.mp4 \
  -c:v libx264 -c:a aac \
  -hls_time 2 \
  -hls_list_size 0 \
  -hls_segment_filename "seg%03d.ts" \
  output.m3u8
옵션의미
-hls_timesegment 길이 (초)
-hls_list_sizeplaylist에 유지할 segment 수 (0=전부)
-hls_segment_filenamesegment 파일 이름 패턴

fMP4 HLS 생성

ffmpeg -i input.mp4 \
  -c:v libx264 -c:a aac \
  -hls_time 2 \
  -hls_segment_type fmp4 \
  -hls_segment_filename "seg%03d.m4s" \
  output.m3u8

-hls_segment_type fmp4가 핵심. init.mp4가 자동으로 생성된다.


멀티 비트레이트 (ABR)

ffmpeg -i input.mp4 \
  -map 0:v:0 -map 0:v:0 -map 0:v:0 -map 0:a:0 \
  -c:v libx264 -c:a aac \
  -b:v:0 5M -s:v:0 1920x1080 \
  -b:v:1 3M -s:v:1 1280x720 \
  -b:v:2 1M -s:v:2 640x360 \
  -var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0" \
  -master_pl_name master.m3u8 \
  -f hls -hls_time 2 \
  "%v/index.m3u8"

Keyframe Alignment 강제

ffmpeg -i input.mp4 \
  -force_key_frames "expr:gte(t,n_forced*2)" \
  -g 60 -keyint_min 60 \
  -sc_threshold 0 \
  ...

2초마다 강제 I-Frame. ABR의 화질 전환을 깨끗하게 만든다.


B.2 플레이어 라이브러리

브라우저에서 HLS를 재생할 때 쓰는 라이브러리.


hls.js

  • HLS 전용
  • 가장 많이 쓰이는 오픈소스
  • MSE 기반 (Safari는 native 사용)
  • https://github.com/video-dev/hls.js
import Hls from 'hls.js';

const video = document.querySelector('video');

if (Hls.isSupported()) {
  const hls = new Hls();
  hls.loadSource('master.m3u8');
  hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  video.src = 'master.m3u8';
}

LL-HLS 지원: lowLatencyMode 옵션.


Shaka Player

  • HLS + DASH 모두 지원
  • Google이 만듦
  • DRM 통합이 강력
  • https://github.com/shaka-project/shaka-player
const player = new shaka.Player(video);
await player.load('master.m3u8');

OTT처럼 멀티 DRM이 필요한 환경에 적합.


Video.js

  • UI / 플러그인 중심
  • HLS 재생은 플러그인으로 (videojs-http-streaming)
  • https://github.com/videojs/video.js

UI 커스터마이징이 많은 서비스에 적합.


dash.js

  • DASH 전용 레퍼런스 구현체
  • DASH-IF가 직접 관리
  • https://github.com/Dash-Industry-Forum/dash.js

비교

라이브러리HLSDASHDRMLL-HLS
hls.js△ (EME)
Shaka Player
Video.js○ (플러그인)
dash.js

B.3 디버깅 도구

mediainfo

파일의 코덱/컨테이너 정보를 빠르게 확인.

mediainfo input.mp4
mediainfo seg100.ts

출력에서 확인할 수 있는 정보:

  • 컨테이너 종류
  • 비디오/오디오 코덱
  • 프로파일/레벨
  • 비트레이트
  • 프레임레이트
  • 트랙 정보

mp4box (GPAC)

fMP4 / MP4의 box 구조를 분석.

# 전체 구조 트리
mp4box -info init.mp4

# box 단위 dump
mp4box -dump-box seg100.m4s

4장에서 본 ftyp / moov / moof / mdat 등이 실제로 어떻게 들어있는지 확인 가능.


ffprobe

ffmpeg에 포함된 메타데이터 분석 도구.

ffprobe -v error -show_streams -show_format input.mp4
ffprobe -show_packets -select_streams v:0 seg100.ts
  • 각 프레임의 PTS/DTS 확인
  • keyframe 위치 확인
  • 코덱 파라미터 확인

Wireshark

네트워크 패킷 수준 분석.

  • HTTP 요청/응답 흐름 추적
  • Chunked Transfer Encoding 확인
  • LL-HLS의 blocking reload 동작 검증
  • TLS 환경에서는 SSLKEYLOGFILE로 복호화

Chrome DevTools

가장 가까운 도구.

  • Network 탭 → m3u8, segment, key 요청 흐름 → Transfer-Encoding: chunked 헤더 확인 → segment 다운로드 시간 측정
  • Media 탭 → 디코더 상태, buffer 잔량 → MSE 이벤트 흐름
  • Performance 탭 → 재생 중 CPU/GPU 사용량

Charles / mitmproxy

HTTPS 트래픽 가로채기 + 변조.

  • 다른 디바이스의 HLS 트래픽 캡처 (Apple TV, 스마트폰 등)
  • 응답을 수정해서 동작 검증
  • m3u8을 즉석에서 변조

B.4 라이브 스트리밍 송출 도구

OBS Studio

  • 가장 널리 쓰이는 무료 송출 도구
  • RTMP / SRT 출력
  • HLS 직접 출력은 안 됨 → 서버에서 변환

nginx-rtmp / nginx-vod-module

  • RTMP 수신 → HLS 변환
  • VOD 파일을 HLS로 즉시 서빙

Wowza / AWS MediaLive

  • 상용 라이브 인코더
  • 고가용성, 멀티 비트레이트 자동화

B.5 명세와 레퍼런스 자료

  • RFC 8216: HLS 공식 명세 https://datatracker.ietf.org/doc/html/rfc8216
  • Apple HLS 페이지: https://developer.apple.com/streaming/
  • DASH-IF: https://dashif.org/
  • CMAF (ISO/IEC 23000-19): ISO 사이트에서 유료 구매 또는 DASH-IF 문서 참고
  • W3C MSE 명세: https://www.w3.org/TR/media-source/
  • W3C EME 명세: https://www.w3.org/TR/encrypted-media/

B.6 어떤 도구부터 써야 하는가

추천 순서.

1. mediainfo / ffprobe로 파일 들여다보기
2. ffmpeg로 직접 HLS 만들어보기
3. hls.js로 웹에서 재생해보기
4. Chrome DevTools로 트래픽 관찰
5. mp4box로 fMP4 box 구조 확인
6. Wireshark로 LL-HLS의 chunked 응답 관찰

각 도구는 책의 다음 장과 짝지어진다.

도구가장 잘 어울리는 장
ffmpeg2~4장 (구조 만들기)
mediainfo / ffprobe4장 (코덱/컨테이너)
mp4box4장 (fMP4 box)
hls.js / Shaka8장 (재생)
Chrome DevTools5~6장 (지연/LL-HLS 검증)
Wireshark6장 (chunked transfer)

시작하며. 스트리밍을 바라보는 세 가지 축

이 책을 시작하기 전에

스트리밍 기술을 공부하다 보면 이런 의문이 자주 든다.

  • 왜 HLS는 라이브에서 늦을까
  • 왜 어떤 서비스는 1초인데 어떤 서비스는 10초나 늦을까
  • 왜 같은 영상이 브라우저마다 다르게 동작할까
  • 왜 LL-HLS, CMAF, MSE 같은 비슷한 이름의 기술이 계속 등장할까

이 책은 이 질문들에 답한다.

그런데 답을 찾으려면 먼저 관점이 필요하다.


스트리밍에는 정답이 없다

스트리밍 기술을 처음 공부할 때 가장 흔히 빠지는 함정은 이것이다.

“어떤 기술이 가장 좋은가?”

이 질문은 답을 가지지 않는다.

왜냐하면 모든 스트리밍 기술은 무엇을 얻기 위해 무엇을 포기할 것인가의 결과물이기 때문이다.

  • 안정성을 얻으려면 지연을 감수해야 하고
  • 지연을 줄이려면 구조가 복잡해지고
  • 구조를 단순하게 가져가려면 플랫폼 제약을 받아야 한다

즉 스트리밍은 “좋고 나쁨“이 아니라 “무엇을 골랐는가“의 문제다.


이 책의 세 가지 축

이 책은 스트리밍 기술을 세 가지 축으로 바라본다.

축 ①: 안정성 ↔ 지연

스트리밍은 끊김 없이 보이는 것즉시 보이는 것을 동시에 만족시킬 수 없다.

  • 끊김을 줄이려면 → 버퍼를 쌓아야 함
  • 지연을 줄이려면 → 버퍼를 줄여야 함

이 두 가지는 정면으로 충돌한다.

HLS는 이 축에서 안정성 쪽을 선택한 기술이다.


축 ②: 구조의 단순함 ↔ 성능

스트리밍은 구조를 단순하게 유지하는 것성능을 끌어올리는 것 사이에서도 충돌한다.

  • HTTP만으로 동작하면 → 단순하지만 느림
  • 전용 프로토콜을 쓰면 → 빠르지만 복잡함

HLS는 “기존 웹 인프라를 그대로 쓴다“는 구조의 단순함을 선택했다.

그 대가가 지연이다.


축 ③: 제어권 ↔ 편의성

스트리밍은 누가 재생을 통제하느냐에 따라 가능한 것이 달라진다.

  • 브라우저가 통제 → 개발자는 편하지만 제어 불가
  • 개발자가 통제 → 자유롭지만 복잡

Flash → HTML5 video → MSE로 이어지는 흐름은 제어권이 점점 개발자에게 넘어오는 과정이다.

Low Latency가 가능해진 것도 이 변화 덕분이다.


이 책이 따라갈 흐름

이 책은 다음 순서로 진행된다.

1~2장   HLS는 왜 등장했고, 왜 영상을 쪼개는가
3~4장   HLS의 데이터 구조 (playlist와 segment 내부)
5장     이 구조가 만드는 한계 (지연)
6~7장   한계를 줄이기 위한 변화 (LL-HLS, CMAF)
8장     재생 제어권의 이동 (MSE)
9장     콘텐츠 보호 (암호화, DRM)
10장    트레이드오프로 본 스트리밍

각 장은 앞 장의 내용을 전제로 한다.

순서대로 읽기를 권장한다.


한 가지만 기억하자

이 책을 끝까지 읽고 나면 다음 한 문장이 자연스럽게 이해될 것이다.

HLS는 포맷이 아니라 설계 철학이며, 우리가 마주하는 모든 한계는 그 철학의 자연스러운 결과다.

이제 시작하자.

부록 C. 용어 정리

스트리밍 분야는 비슷한 용어가 많다. 이 책에서 사용한 용어들을 자주 헷갈리는 짝으로 묶어 정리한다.


C.1 Segment vs Chunk vs Part vs Fragment

가장 자주 헷갈리는 네 단어.

각각 다른 표준에서 비슷한 개념을 부르는 이름이다.

용어출처의미
SegmentHLS / DASH 공통플레이어가 인식하는 재생 단위 (URL 단위)
FragmentMP4 / fMP4moof + mdat 한 쌍 (컨테이너 내부)
ChunkCMAF / DASH가장 작은 전송 단위 (CMAF chunk)
PartLL-HLSCMAF chunk와 동일한 개념의 HLS 용어

관계 정리

Segment (1~10초)
  └── Fragment (moof+mdat)
        └── Chunk (CMAF) = Part (HLS)
              ↑ 0.2~0.5초

핵심

  • HLS의 Part와 CMAF의 Chunk는 사실상 같은 것
  • MP4의 Fragment는 컨테이너 내부 구조 용어
  • Segment만 표준 간 공통

본문 위치: 4.3, 6.3, 7.5


C.2 Container vs Codec

용어의미
Codec영상/음성을 압축/해제하는 방식H.264, AAC, AV1
Container코덱 결과물을 담는 형식MP4, fMP4, TS

같은 코덱(H.264)을 여러 컨테이너에 담을 수 있다.

본문 위치: 4.1


C.3 Playlist vs Manifest

용어사용처파일
PlaylistHLSm3u8
ManifestDASHMPD (XML)

기능은 같다. 스트림 목록과 메타데이터를 제공한다.

본문 위치: 1.9, 3.3


C.4 Master / Multivariant Playlist

같은 파일을 가리키는 두 이름.

용어출처
Master Playlist실무 / 구버전
Multivariant PlaylistHLS 명세 정식 용어

본문에서는 실무에서 익숙한 Master Playlist로 부른다.

본문 위치: 3.3


C.5 VOD vs Live vs Event

라이브 스트리밍의 세 가지 모드.

모드특징매니페스트
VOD완성된 영상고정, ENDLIST 있음
Live실시간 송출sliding window, ENDLIST 없음
Event라이브 + 처음부터 재생 가능추가만 가능

EXT-X-PLAYLIST-TYPE이 이 모드를 표시한다.

본문 위치: 3.5


C.6 ABR / Adaptive Bitrate

용어풀네임의미
ABRAdaptive Bitrate네트워크 상태에 따라 화질을 자동 변경
VariantABR의 각 화질 옵션 (1080p, 720p 등)
Rendition각 트랙(비디오/오디오/자막)의 한 버전

Variant는 비디오 화질, Rendition은 더 일반적인 트랙 단위.

본문 위치: 2.4~2.6, 3.3


C.7 Push vs Pull

데이터 전송 모델.

모델누가 주도
Push서버RTMP, WebRTC, SSE
Pull클라이언트HLS, DASH

HLS는 명확하게 pull-based.

다만 LL-HLS의 blocking reload는 “클라이언트가 요청 → 서버가 보낼 때까지 잡고 있음” 형태라 pull + push의 절충으로 볼 수 있다.

본문 위치: 3.8, 6.4


C.8 Polling vs Long Polling vs Blocking

요청 응답 패턴.

방식동작
Polling일정 주기로 반복 요청
Long Polling요청 후 응답을 늦게 보냄
Blocking특정 조건 만족 시까지 응답 보류

HLS는 Polling. LL-HLS는 Blocking (사실상 long polling의 일종).

본문 위치: 5.6, 6.4


C.9 Encoding vs Transcoding vs Packaging

미디어 처리 단계.

용어의미
Encodingraw 신호 → 압축 (코덱 적용)
Transcoding한 코덱 → 다른 코덱 변환
Packaging압축된 데이터 → 컨테이너로 묶기
Transmuxing컨테이너만 변경 (재인코딩 없이)

ffmpeg는 이 모두를 한다.

ABR을 위한 멀티 비트레이트 생성은 encoding + packaging.

TS → fMP4 변환은 transmuxing.


C.10 GOP / Keyframe / I-Frame

용어의미
GOPGroup of Pictures. 한 keyframe부터 다음 keyframe 전까지
Keyframe독립 디코딩 가능한 프레임
I-FrameIntra-coded Frame. 통상 keyframe과 같음
P-FramePredicted. 이전 프레임 참조
B-FrameBidirectional. 양쪽 프레임 참조

엄밀히 “I-Frame이 항상 keyframe은 아니다“라는 세부 구분이 있지만, HLS 맥락에서는 거의 동의어다.

본문 위치: 4.4


C.11 PTS / DTS

타임스탬프 두 종류.

용어의미
PTSPresentation Timestamp. 언제 보여줄지
DTSDecoding Timestamp. 언제 디코딩할지

B-Frame이 있으면 PTS ≠ DTS. 참조 프레임을 먼저 디코딩하고 순서를 바꿔 보여줘야 하기 때문.

본문 위치: 4.2.3


C.12 CMAF / fMP4 / ISO BMFF

비슷한 듯 다른 세 용어.

용어의미
ISO BMFFBase Media File Format (ISO/IEC 14496-12). MP4의 기반
fMP4Fragmented MP4. ISO BMFF 위에서 fragment 사용
CMAFCommon Media Application Format. fMP4를 어떻게 쓸지 표준화
ISO BMFF (기반)
  ↓
fMP4 (fragment 구조)
  ↓
CMAF (사용 규칙 표준화)

본문 위치: 4.3, 7.5


C.13 AES-128 / SAMPLE-AES / cenc / cbcs

암호화 관련 용어.

용어분류특징
AES-128HLS 암호화segment 전체
SAMPLE-AESHLS 암호화샘플 단위
cencCENC 모드AES-CTR
cbcsCENC 모드AES-CBC + 샘플 + 패턴

현재 멀티 DRM 환경의 표준은 cbcs.

본문 위치: 9.3, 9.4


C.14 DRM 시스템 비교

시스템회사주 플랫폼
FairPlayAppleiOS / Safari / tvOS
WidevineGoogleChrome / Android
PlayReadyMicrosoftEdge / Windows / Xbox
ClearKeyW3C테스트용 (실서비스 X)

EME (Encrypted Media Extensions)는 이들을 브라우저에서 호출하는 W3C 표준 API다.

본문 위치: 9.5


C.15 MSE / EME / MMS

브라우저 미디어 API 세 가지.

API풀네임역할
MSEMedia Source Extensions데이터를 buffer에 직접 공급
EMEEncrypted Media ExtensionsDRM 키 관리
MMSManaged Media SourceMSE의 메모리/배터리 관리형

본문 위치: 8.6, 8.11, 9.5


C.16 HLS / LL-HLS / DASH / LL-DASH / WebRTC

스트리밍 프로토콜 비교.

프로토콜풀네임일반 지연비고
HLSHTTP Live Streaming6~10초Apple, HTTP 기반
LL-HLSLow-Latency HLS1~3초HLS 확장
DASHDynamic Adaptive Streaming over HTTP5~10초ISO 표준
LL-DASHLow-Latency DASH1~3초DASH 확장
WebRTCWeb Real-Time Communication0.2~0.5초UDP 기반
SRTSecure Reliable Transport0.2~1초UDP, 송출용
HESPHigh-Efficiency Streaming Protocol0.4~2초신규 표준

본문 위치: 1.9, 10.3