IT

영상은 어떻게 인터넷을 타고 흐르는가 — HTTP 스트리밍 기술 해부

zinok 2026. 3. 9. 15:12

유튜브를 켰을 때 영상이 곧바로 뜨는 게 당연하게 느껴지지만, 그 뒤에는 수십 년에 걸쳐 쌓인 꽤 정교한 기술들이 있습니다. m3u8, HLS, MPEG-DASH, LL-HLS — 이름은 들어봤는데 막상 설명하려면 막막한 그 기술들을, 오늘 천천히 뜯어보겠습니다.

Background왜 HTTP인가 — RTMP는 어디 갔나

한때 라이브 스트리밍의 왕은 RTMP(Real-Time Messaging Protocol)였습니다. Adobe Flash 시대의 산물로, TCP 위에 자체 프로토콜을 얹은 구조였죠. OBS로 방송을 설정해 본 분이라면 rtmp://로 시작하는 스트림 서버 주소가 익숙하실 겁니다.

그런데 RTMP에는 치명적인 문제들이 있었습니다. 기업 방화벽을 통과하지 못하는 경우가 많았고, CDN과 궁합이 나빴으며, Flash의 몰락과 함께 브라우저 지원이 끊겼습니다. 그래서 업계가 선택한 해법은 의외로 단순했습니다 — "그냥 HTTP 써버리자."

왜 HTTP가 이상적인가

HTTP는 이미 전 세계 CDN 인프라가 최적화되어 있고, 방화벽의 80/443 포트는 항상 열려 있습니다. 캐싱 인프라도 완벽하게 구축되어 있죠. 기존 웹 인프라를 그대로 재활용할 수 있다는 건 엔지니어링 관점에서 굉장히 매력적인 조건입니다.

HTTP 스트리밍의 핵심 아이디어는 단순합니다. 영상을 잘게 쪼개서 정적 파일처럼 서빙한다. 클라이언트가 순서대로 파일을 요청하면 CDN이 캐시해서 빠르게 내려줍니다. 복잡한 상태 유지가 필요 없고, 수평 확장도 쉽습니다.

///

Core ConceptABR — 화질이 알아서 바뀌는 이유

유튜브에서 4K 영상을 보다가 지하철에서 데이터가 약해지면 자동으로 화질이 내려가는 경험, 해보셨죠? 이게 바로 ABR(Adaptive Bitrate Streaming)입니다.

원리는 이렇습니다. 인코딩 단계에서 같은 영상을 여러 화질로 미리 만들어둡니다. 플레이어는 네트워크 상태와 버퍼 여유를 실시간으로 측정하면서, 지금 받을 수 있는 최고의 화질을 선택해 세그먼트를 요청합니다.

ABR — 다중 화질 구성도
Origin (원본 영상) │ ├──▶ 1080p 8 Mbps ──▶ seg_1080_001.ts seg_1080_002.ts ... ├──▶ 720p 4 Mbps ──▶ seg_720_001.ts seg_720_002.ts ... ├──▶ 480p 2 Mbps ──▶ seg_480_001.ts seg_480_002.ts ... └──▶ 360p 1 Mbps ──▶ seg_360_001.ts seg_360_002.ts ... │ ▼ Manifest 파일 (.m3u8 / .mpd) "어떤 화질이 있고, 각 세그먼트 URL은 뭔지" │ ▼ Player (HLS.js / Video.js / Native) 네트워크 측정 → 화질 결정 → 세그먼트 순차 요청
Buffer 기반 적응 전략

단순히 현재 다운로드 속도만 보는 게 아닙니다. 플레이어는 버퍼에 얼마나 쌓여 있는지를 기준으로 화질을 결정합니다. 버퍼가 넉넉하면 올리고, 위험 수준이면 내립니다. BOLA, SQUAD 같은 알고리즘이 이 역할을 담당합니다.

///

File Formatm3u8 — 그 파일의 정체

개발자라면 한 번쯤 네트워크 탭에서 .m3u8 요청을 본 적 있을 겁니다. 그게 뭔지 클릭해봤더니 텍스트 파일이라서 당황했다는 분도 있죠. 맞습니다. m3u8은 그냥 텍스트 파일입니다.

정확히는 UTF-8 인코딩된 M3U 재생목록 파일입니다. HLS의 핵심 메타데이터로, "어떤 화질이 있고, 각 세그먼트 파일이 어디에 있는지"를 기술합니다. 구조는 두 종류로 나뉩니다.

master.m3u8// ① Master Playlist — 전체 화질 목록을 정의하는 '메뉴판'
#EXTM3U
#EXT-X-VERSION:6

# 1080p 옵션
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080,
  CODECS="avc1.640028,mp4a.40.2",FRAME-RATE=30
https://cdn.example.com/hls/1080p/index.m3u8

# 720p 옵션
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1280x720,
  CODECS="avc1.4d401f,mp4a.40.2",FRAME-RATE=30
https://cdn.example.com/hls/720p/index.m3u8
720p/index.m3u8// ② Media Playlist — 실제 세그먼트 목록. 이게 진짜 '재생목록'
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:6       ← 세그먼트 최대 길이(초)
#EXT-X-MEDIA-SEQUENCE:1024    ← Live의 핵심! 계속 증가함

#EXTINF:6.000,
https://cdn.example.com/hls/720p/seg1024.ts
#EXTINF:6.000,
https://cdn.example.com/hls/720p/seg1025.ts

# VOD는 끝에 이게 붙음. Live는 절대 안 붙음.
#EXT-X-ENDLIST
Live vs VOD 핵심 차이

Live#EXT-X-ENDLIST가 없습니다. 플레이어는 주기적으로 m3u8을 다시 요청해서 새 세그먼트가 추가됐는지 확인합니다. VOD는 이 태그가 있어 "더 이상 세그먼트 없음"을 알려줍니다. 태그 하나로 Live/VOD가 구분됩니다.

///

Segment Format.ts vs .m4s — 세그먼트의 두 얼굴

m3u8이 목차라면, 세그먼트 파일이 실제 영상 데이터입니다. 형식은 크게 두 가지인데, 역사적 배경이 다릅니다.

MPEG-2 TS (.ts) — 방송 표준에서 온 포맷
188 bytes 패킷들의 연속 ┌────────────┬────────────┬────────────┬────────────┐ │ TS PacketTS PacketTS Packet │ ... │ │ 188 bytes │ 188 bytes │ 188 bytes │ │ │ sync: 0x47 │ │ │ │ └────────────┴────────────┴────────────┴────────────┘ 특징 - 방송 업계(DVB, ATSC) 표준에서 출발 - 각 패킷이 독립적 → 네트워크 손실에 강함 - 구형 장비/플레이어 호환성 최고 - 파일 크기는 .m4s 대비 약 10~15% 큼
fMP4 Fragmented MP4 (.m4s) — 현대적 포맷
MP4 Box 구조 ┌────────┬──────────────────────────┬──────────────────────────┐ │ ftypmoofmdat │ │ │ Movie Fragment Header │ 실제 영상/음성 데이터 │ │ 포맷 │ (타임스탬프, 오프셋, │ │ │ 선언 │ 시퀀스 번호) │ │ └────────┴──────────────────────────┴──────────────────────────┘ 특징 - HLS v6+ 및 MPEG-DASH 공통 포맷 (CMAF 기반) - .ts 대비 파일 크기 10~15% 작음 - AES-128, CBCS 암호화 처리 효율적 - 하나의 세그먼트로 HLS + DASH 동시 서빙 가능!
CMAF — 한 벌로 두 프로토콜 서빙

CMAF(Common Media Application Format)은 .m4s 세그먼트 하나를 HLS와 DASH에서 모두 사용하는 방식입니다. 예전에는 프로토콜별로 인코딩을 따로 해야 했지만, CMAF 덕분에 스토리지와 CDN 비용을 크게 줄일 수 있게 됐습니다. Netflix와 Apple이 공동 표준화한 포맷입니다.

///

Protocol ComparisonHLS vs MPEG-DASH — 진영 싸움의 역사

HTTP 스트리밍에는 두 진영이 있습니다. Apple이 만든 HLS업계 표준인 MPEG-DASH. 비슷한 개념이지만 세부 구현이 달라서, 오랫동안 스트리밍 개발자들의 공공의 적이었습니다.

항목HLSMPEG-DASH
개발사Apple (2009)MPEG 표준 (2012)
Manifest.m3u8 텍스트.mpd XML
세그먼트.ts / .m4s.m4s 전용
기본 지연6~30초2~10초
DRMFairPlay (Apple)Widevine + PlayReady
Safari 기본 지원네이티브없음
Chrome/FirefoxJS 라이브러리 필요네이티브
주요 사용Apple 생태계, 범용YouTube, Netflix
현실에서의 선택

Safari는 HLS만 지원하고, 대부분의 OTT는 Widevine DRM 때문에 DASH를 씁니다. 그래서 실제 서비스에서는 둘 다 지원하거나, CMAF 기반으로 통합하는 방식을 씁니다. HLS.js, dash.js 같은 JavaScript 라이브러리가 그 간극을 메워주고 있습니다.

///

Low LatencyLL-HLS — 지연을 1초대로 낮추는 법

HLS의 고질적인 약점은 지연시간입니다. 6초짜리 세그먼트 기준으로 CDN 전파 시간까지 더하면 시청자는 방송보다 15~30초 느리게 보게 됩니다. 스포츠 중계에서 옆집 환호성을 먼저 듣고 골을 나중에 보는 그 상황 말이죠.

프로토콜별 End-to-End 지연시간 비교
RTMP
3~5초
HLS
15~30초
MPEG-DASH
4~8초
LL-HLS
1~3초
WebRTC
<0.5초

Apple이 2019년에 내놓은 해법이 LL-HLS(Low-Latency HLS)입니다. 핵심 아이디어는 세그먼트를 완성하기 전에 200ms 단위 조각으로 나눠서 즉시 전달하는 것입니다.

세그먼트 전달 방식 비교
일반 HLS
seg N-2
seg N-1
seg N ← 완성 후
LL-HLS
p1
p2
p3
p4
p5
p6
p7
p8
p9
p10
p11
p12
🔴

여기에 HTTP/2의 Server PushBlocking Playlist Reload를 조합하면, 플레이어가 새 조각을 거의 실시간으로 받을 수 있습니다.

LL-HLS m3u8#EXT-X-PART-INF:PART-TARGET=0.2      ← 부분 세그먼트 목표 길이(200ms)
#EXT-X-SERVER-CONTROL:
    CAN-BLOCK-RELOAD=YES,           ← 새 세그먼트 생기면 응답
    PART-HOLD-BACK=0.6

# 완성된 이전 세그먼트
#EXTINF:6.00000,
seg004.m4s

# 현재 생성 중인 세그먼트의 부분들 — 만들어지는 즉시 서빙
#EXT-X-PART:DURATION=0.20000,URI="seg005_p0.m4s"
#EXT-X-PART:DURATION=0.20000,URI="seg005_p1.m4s"
#EXT-X-PART:DURATION=0.20000,URI="seg005_p2.m4s"
# ... 아직 생성 중 ...
///

File Containerm4v는 뭐가 다른가

여기서 잠깐 혼란스러운 파일 확장자를 정리하고 가겠습니다. .m4v는 스트리밍 세그먼트가 아닙니다. Apple이 정의한 완성형 비디오 컨테이너입니다.

MP4 계열 파일 포맷 비교
.mp4 ─ 업계 표준 ISO 컨테이너 │ ├─ .m4v Apple 확장판 │ ├─ iTunes FairPlay DRM 지원 │ ├─ 챕터 마커 │ ├─ 자막 트랙 (SRT/SSA) │ └─ 사실상 .mp4와 동일 구조 (대부분 플레이어 재생 가능) │ ├─ .m4a 오디오 전용 │ └─ .m4s Fragmented MP4 스트리밍 세그먼트 └─ 완성된 파일 아님. 다른 조각과 합쳐야 재생 가능
한 줄 정리

.m4v는 "파일 하나로 완결되는 영상" / .m4s는 "스트리밍용 조각 파일". 둘 다 .mp4 기반이지만 용도가 전혀 다릅니다. 실무에서 .m4v는 원본 소스 파일로 존재하다가 트랜스코딩을 거쳐 스트리밍 세그먼트로 변환됩니다.

///

InfrastructureCache 전략 — CDN이 스트리밍의 핵심인 이유

HTTP 스트리밍이 이렇게까지 확산된 이유 중 하나는 CDN 캐싱과 완벽하게 궁합이 맞는다는 점입니다. 세그먼트 파일은 한 번 만들어지면 내용이 바뀌지 않으니까요. 다만 m3u8과 세그먼트의 캐시 정책은 완전히 다르게 가져가야 합니다.

파일Cache-Control이유
*.m3u8 (Live) no-cache, no-store 항상 최신 세그먼트 목록 필요
*.m3u8 (VOD) max-age=3600 변경 없으므로 캐시 가능
*.ts / *.m4s max-age=86400+ 시퀀스 번호로 파일명 고정, 불변
*.m4s (LL-HLS partial) max-age=86400+ 생성 후 절대 변경 없음
흔한 실수

Live m3u8에 캐시를 걸면 시청자들이 오래된 세그먼트 목록을 받아 재생이 끊깁니다. 반대로 세그먼트에 캐시를 안 걸면 모든 요청이 Origin으로 가서 서버가 터집니다. m3u8 = no-cache, 세그먼트 = long TTL. 이 원칙 하나가 스트리밍 인프라 안정성의 절반을 책임집니다.

///

Wrap-up정리 — 어떤 걸 써야 하나

사용 케이스별 권장 기술 스택
VOD 서비스 └─ 원본 .mp4 / .m4v └─ 트랜스코딩 → 다중 화질 .m4s + HLS m3u8 + DASH mpd (CMAF) └─ DRM: FairPlay (iOS) + Widevine (Android/Chrome) 라이브 스트리밍 (지연 10초 이내 허용) └─ 인코더(OBS/FFmpeg) → RTMP/SRT → Nginx/Wowza └─ .ts 세그먼트(2~6초) + m3u8 갱신 → CDN └─ HLS.js 플레이어 라이브 스트리밍 (스포츠/경매, 3초 이내 목표) └─ 인코더 → SRT → 인제스트 서버 └─ .m4s 부분 세그먼트(200ms) + LL-HLS m3u8 → CDN (HTTP/2 필수) └─ LL-HLS 지원 플레이어 초저지연 (500ms 이하, 화상회의/게임 스트리밍) └─ HTTP 스트리밍 포기 → WebRTC (완전히 다른 기술 스택)

스트리밍 기술은 결국 "빠르고, 끊기지 않고, 많은 사람이 동시에 볼 수 있게"라는 단순한 목표를 향해 달려온 수십 년의 결과물입니다.

다음에 유튜브나 트위치를 볼 때 브라우저 개발자 도구 네트워크 탭을 한번 열어보세요. .m3u8 파일이 주기적으로 요청되고, 그 뒤를 이어 세그먼트 파일들이 줄줄이 내려오는 모습이 보일 겁니다. 조금은 다르게 보이지 않나요?