📁 File6. 스트리밍05 — Low Latency Streaming (LL-HLS, LL-DASH, vs WebRTC)

05 — Low Latency Streaming (LL-HLS, LL-DASH, vs WebRTC)

이 문서가 답하는 질문: 라이브 지연을 30초 → 3초 → 0.5초로 줄이려면 무엇이 바뀌어야 하나? 표준: HLS v9 (LL-HLS, 2020), DASH-IF Low-Latency Live (LL-DASH). 핵심 한 줄: 지연을 줄이는 길은 셋이다 — segment를 짧게 (전통), segment를 part로 쪼개고 push (LL-HLS/DASH), 또는 세그먼트 자체를 버린다 (WebRTC).


한 줄 답 (Pyramid Top)

전통 HLS의 라이브 지연(2030초)은 *세그먼트 길이 × 23*에서 온다. 줄이는 방법은: (1) 세그먼트를 더 짧게 (1초) — 한계 68초 지연. (2) 세그먼트를 part로 쪼개고 chunked push (LL-HLS, LL-DASH) — 한계 25초 지연. (3) 세그먼트를 포기하고 RTP/SRTP를 쓴다 (WebRTC) — 0.1~0.5초.


Why — 왜 지연이 생기는가 (지연 예산 분해)

전통 HLS의 30초 지연을 어디서 잃는지 분해하면:

단계지연 (초)이유
카메라 → 인코더0.1~0.5캡처 + GOP 1개분 인코딩
인코더 → packager0.1한 GOP 인코딩 후 컨테이너 작성
Segment 완성 대기66초 segment를 완성해야 발행 가능
Origin → CDN 전파1~3첫 fetch 전엔 캐시 없음
Player 매니페스트 폴링3~6TARGETDURATION/2 주기
Player 버퍼 (rebuffer 회피)6~12보통 2 segment 분량
디코드 + 디스플레이0.1~0.3거의 무시
합계~30s

굵은 항목 셋(segment 완성 6초 + 폴링 36초 + 버퍼 612초)이 절반 이상. LL의 핵심은 이 셋을 다 줄이는 것.


How — LL-HLS의 4가지 변경점 (Apple, 2019/2020 도입)

1) Partial Segment (#EXT-X-PART)

전통 HLS는 6초 segment를 완성해야 매니페스트에 추가했다. LL-HLS는 segment를 작은 part(보통 0.2~1초)로 쪼개 생성 즉시 노출한다.

#EXTM3U
#EXT-X-VERSION:9
#EXT-X-TARGETDURATION:6
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.5,HOLD-BACK=12

#EXT-X-MEDIA-SEQUENCE:1240

#EXTINF:6.0,
seg_01240.m4s
#EXTINF:6.0,
seg_01241.m4s
#EXTINF:6.0,
seg_01242.m4s

# 진행 중인 segment의 part들:
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.1.m4s"
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.2.m4s"
#EXT-X-PART:DURATION=0.33334,URI="seg_01243.3.m4s"

#EXT-X-PRELOAD-HINT:TYPE=PART,URI="seg_01243.4.m4s"
태그의미
#EXT-X-VERSION:9LL-HLS는 v9+
#EXT-X-PART-INF:PART-TARGETpart의 목표 길이 (초)
#EXT-X-PART:DURATION=...,URI="...",INDEPENDENT=YES한 part. INDEPENDENT=YES는 IDR로 시작 (시크 가능 지점)
#EXT-X-PRELOAD-HINT아직 publishing 안 된 다음 part의 URI를 미리 안내 → 플레이어가 blocking GET 가능
#EXT-X-SERVER-CONTROLLL 동작 파라미터
CAN-BLOCK-RELOAD=YES서버가 blocking GET을 지원함을 광고
PART-HOLD-BACK=1.5라이브 엣지로부터 1.5초 뒤를 권장 시청 위치
HOLD-BACK=12비-LL 클라이언트의 권장 위치 (= 12초 뒤)

→ 한 segment(6초)가 part 18개로 쪼개진다 (0.33초 × 18). 18배 더 자주 매니페스트가 갱신된다.

2) Blocking Playlist Reload

전통: 매니페스트를 polling. 새 segment가 없으면 빈손으로 돌아옴. LL-HLS: 플레이어가 _HLS_msn=N&_HLS_part=K라는 기대값을 쿼리에 박아 GET. 서버는 그 시점이 올 때까지 응답을 미룬다.

GET /media.m3u8?_HLS_msn=1243&_HLS_part=5
   ← 서버: msn=1243, part=5가 publishing 될 때까지 hold (long polling)
   ← publishing 즉시 응답 200

→ 폴링 오버헤드 0. 새 part 등장 즉시 클라이언트에 도달.

3) Delta Update

매니페스트가 매 part마다 갱신되면 텍스트가 비대해진다. LL-HLS는 delta만 보내는 옵션.

GET /media.m3u8?_HLS_skip=YES
   → 응답에 #EXT-X-SKIP:SKIPPED-SEGMENTS=N (앞부분 N개 segment 생략)
#EXTM3U
#EXT-X-VERSION:9
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:1240
#EXT-X-SKIP:SKIPPED-SEGMENTS=240   ← 1000~1239의 240개 생략

#EXTINF:6.0,
seg_01240.m4s
...

플레이어는 이전 매니페스트와 합쳐 전체 상태를 재구성. 텍스트 전송량 ↓.

4) Rendition Reports

플레이어가 ABR 전환 시 다른 variant의 매니페스트도 polling해야 했다. LL-HLS는 같은 매니페스트 안에 다른 variant의 최신 msn/part 정보를 동봉.

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

→ ABR 전환 시 상대 매니페스트의 위치를 이미 알고 blocking GET → 더 빠른 전환.


How — LL-DASH (chunked CMAF)

DASH는 CMAF chunked transfer를 표준화했다 — 별도 매니페스트 변경 없이.

MPD에 LL 메타 추가

<MPD type="dynamic"
     availabilityStartTime="2026-05-10T10:00:00Z"
     minimumUpdatePeriod="PT2S"
     suggestedPresentationDelay="PT2S"
     ...>
  <ServiceDescription>
    <Latency target="2000" min="1000" max="5000"/>
    <PlaybackRate min="0.95" max="1.05"/>
  </ServiceDescription>
  ...
  <SegmentTemplate
    timescale="90000"
    duration="540540"   {/* 6초 segment */}
    initialization="$RepresentationID$/init.mp4"
    media="$RepresentationID$/seg_$Number%05d$.m4s"
    availabilityTimeOffset="5.7"
    availabilityTimeComplete="false"
    startNumber="1"/>
  ...
</MPD>
속성의미
availabilityTimeComplete="false"segment가 완성 전부분을 받을 수 있음을 광고
availabilityTimeOffset="5.7"segment 시작 시각 0.3초 후부터 부분이 나오기 시작 (6 - 5.7 = 0.3)
Latency target="2000"권장 지연 2초
PlaybackRate min/maxlatency 따라잡기용 재생 속도 미세 조정 (0.95~1.05x)

인코더의 chunked HTTP transfer

PUT /seg_00501.m4s HTTP/1.1
Transfer-Encoding: chunked
 
200
[moof][mdat]   ← chunk 1 (200ms)
 
200
[moof][mdat]   ← chunk 2 (200ms)
 
...
0   ← end

CDN은 chunk를 받는 즉시 forward (HTTP/1.1 chunked passthrough). 플레이어는 한 segment의 일부를 받아 디코딩 시작.

→ HLS의 partial segment와 기능은 동등. 차이: HLS는 part마다 별도 URL, DASH는 같은 URL에 chunked 응답.


What — 지연 비교 표

방식Segment 길이Glass-to-Glass채택
전통 HLS (TS, 10s)10s25~45s전통 OTT
HLS (fMP4, 6s)6s18~30s일반 OTT (Netflix VOD 라이브)
HLS short segment (2s)2s6~10s빠른 라이브
LL-HLS6s segment + 0.33s part2~5sYouTube Live, Twitch (실험)
LL-DASH6s segment + chunked2~5sDAZN, BBC iPlayer
WebRTC패킷 단위0.1~0.5s화상회의, Twitch interactive
SRT/RIST (전송 only)패킷 단위0.5~2s방송 contribution

What — WebRTC와 LL-HLS는 어떤 분기인가

같은 “라이브” 도메인이지만 풀려는 문제가 다르다.

LL-HLS / LL-DASHWebRTC
모델HTTP pull (CDN-friendly)UDP push (peer-to-peer 또는 SFU)
확장성1억 명 OK (CDN edge)SFU별 ~수만 명
지연2~5s0.1~0.5s
방화벽80/443 통과UDP 차단되면 fallback (TCP relay)
DRM정상 (CENC/CBCS)별도 E2E (SFRAME 등 실험)
왕복단방향양방향 (인터랙티브)
품질 제어ABR (variant 사전 인코딩)simulcast/SVC + 서버 선택
사용처스포츠/뉴스 라이브화상회의, 강사-수강생, 라이브 인터랙션

규칙: 대규모 일방향 라이브 → LL-HLS/DASH. 인터랙션 → WebRTC. 둘 사이의 절충은 거의 없다 (양립 불가).


What — Glass-to-Glass 지연 측정

운영에서 LL을 검증하려면 실측이 필요하다. 일반적인 두 방법:

NTP 시각 합성 (권장)

화면에 NTP 시계를 오버레이로 합성하고, 시청 측 클라이언트도 NTP로 시각을 알면 영상에 보이는 시각 vs 현재 시각의 차이가 latency.

EXT-X-PROGRAM-DATE-TIME / availabilityStartTime

매니페스트 자체에 wall-clock이 박혀 있어 대략적 측정 가능.

#EXT-X-PROGRAM-DATE-TIME:2026-05-10T10:30:15.123Z
#EXTINF:6.0,
seg_01243.m4s

플레이어가 이 segment를 디스플레이하는 wall-clock과 PDT를 비교.


What-if — 잘못 쓰면 어떻게 깨지는가

증상원인처방
LL인데 지연이 그대로CDN이 chunked transfer 미지원CloudFront/Akamai LL 호환 origin 설정
Player가 LL 매니페스트 거부플레이어 버전이 v9 미만hls.js 1.x+, Shaka 4.x+ 사용
INDEPENDENT=YES part가 없음인코더가 part 경계에 IDR 미배치g=part_size 또는 force_key_frames per part
ABR이 part 단위로 폭주 진동switch 빈도가 너무 잦음switchInterval 조정 (≥ 4초)
Blocking GET가 timeout네트워크 NAT의 idle timeout매니페스트 응답을 keep-alive로
한 사용자만 6초 지연그 클라이언트가 LL 미지원HOLD-BACK 값으로 graceful degrade
라이브 시작 후 첫 1분 unstable서버가 part-target 못 맞춤인코더 GOP 설정 검증

Insight — 흥미로운 이야기

“LL-HLS는 사실 기존 CDN을 안 바꾸려고 만든 것”

WebRTC가 이미 0.5초 지연을 푼 시점이었지만, 기존 HLS 인프라(Akamai 같은 거대 CDN)를 그대로 쓸 수 있어야 했다. LL-HLS의 blocking GET은 사실 long-polling이고, part는 짧은 segment의 변형이다 — 모두 HTTP 위에 동작 가능. 인프라 변경 비용을 최소화한 것이 핵심.

“Apple과 LHLS의 분쟁”

2018~2019년 Periscope/Twitch가 unofficial LHLS(Low-latency HLS)를 먼저 제안했다 — chunked transfer 기반. Apple이 이를 받아들이지 않고 별도 표준(LL-HLS, partial segment)을 제안. 한동안 두 표준이 경쟁했고, 결국 Apple의 표준이 채택되었다. 표준의 정치학.

“PlaybackRate ±5%의 비밀”

LL 라이브에서 클라이언트가 라이브 엣지로부터 너무 멀어지면, 재생 속도를 0.95~1.05배로 미세 조정해 다시 따라잡는다. 시청자는 눈치 못 챈다 — 사람의 청지각은 ±5% pitch 변화에 둔감. 이 트릭이 Latency target을 지키기 위한 마지막 무기다.

“WebRTC는 사실 일방향 라이브에 잘 안 맞다”

WebRTC는 peer-to-peer가 본질이다. 1만 명 라이브를 하려면 SFU(Selective Forwarding Unit)가 필요한데, SFU는 서버 클러스터고 비싸다. 같은 1만 명을 LL-HLS로 하면 CDN edge가 공짜로 분산해준다. 그래서 대규모 라이브는 LL-HLS, 소규모 인터랙션은 WebRTC로 자연스럽게 나뉜다.


한 단락 요약 + Mermaid

Low-latency 스트리밍은 segment를 part로 쪼개 push하면서 매니페스트 폴링을 long-polling으로 바꾼 결과다. LL-HLS의 #EXT-X-PART와 LL-DASH의 chunked transfer는 같은 메커니즘의 다른 표현. 0.5초 미만이 필요하면 segment 자체를 포기하고 WebRTC로 가야 한다. 둘 사이에는 절충이 거의 없다.

→ 다음 파일 06-segmentation-and-manifest.md: segment 길이 자체의 trade-off — 2/4/6/10초의 trade off.