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

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는 “기다림이 있던 모든 자리“에 부분 전송과 즉시 응답을 끼워 넣은 결과다.