🟣 파일 다운로드가 어떻게 스트리밍이 되나
앞 장에서 HLS의 발상을 "영상을 파일 조각으로 잘라 HTTP로 하나씩 받는 것"이라고 정리했다. 이 문장을 처음 들으면 의아하다. 파일을 하나씩 받는 게 어떻게 끊김 없는 재생이 되는가. 다운로드가 끝나야 트는 것 아닌가.
핵심은 플레이어가 다음 조각을 미리 받아 두는 데 있다. 지금 조각을 재생하는 동안 뒤이어 올 조각들을 백그라운드로 내려받아 버퍼에 쌓는다. 한 조각의 재생이 끝나면 이미 받아 둔 다음 조각으로 끊김 없이 넘어간다. 이 구조가 성립하려면 플레이어가 두 가지를 알아야 한다. 지금 어떤 조각들이 있고, 어떤 순서로 이어지는가. 그 목록을 담은 것이 .m3u8 플레이리스트다.
이 장은 그 플레이리스트가 실제로 어떻게 생겼는지 열어 보고, HLS의 진짜 강점인 적응형 비트레이트가 이 구조 위에서 어떻게 얹히는지 본다.
🟣 m3u8: 조각의 목록이자 재생의 설계도
.m3u8은 사실 평범한 텍스트 파일이다. 영상 조각(세그먼트)들의 목록과, 각 조각의 길이 같은 메타 정보가 줄줄이 적혀 있다. 실제 미디어 조각 파일(보통 .ts)은 따로 있고, 플레이리스트는 그 조각들을 가리키는 목차 역할만 한다.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:120
#EXTINF:6.000,
segment120.ts
#EXTINF:6.000,
segment121.ts
#EXTINF:6.000,
segment122.ts읽는 법은 간단하다. #EXT-X-TARGETDURATION은 조각 하나의 대략적 길이가 6초라는 뜻이고, #EXTINF 뒤의 숫자가 바로 다음 조각의 실제 재생 시간이다. #EXT-X-MEDIA-SEQUENCE는 지금 목록의 첫 조각이 전체에서 몇 번째인지를 알려 준다.
라이브 방송에서는 이 플레이리스트가 계속 갱신된다. 새 조각이 만들어지면 목록 맨 아래에 추가되고, 오래된 조각은 위에서 빠진다. 시청자의 플레이어는 몇 초마다 이 플레이리스트를 다시 받아 새 조각이 생겼는지 확인하고, 생겼으면 받아 재생한다. 여기서 앞 장에서 말한 지연의 정체가 드러난다. 서버가 6초짜리 조각을 다 모아 파일로 만들어야 목록에 올릴 수 있고, 플레이어는 그걸 받아 버퍼에 몇 개 쌓은 뒤에야 튼다. 조각 길이와 버퍼 개수가 그대로 지연으로 쌓인다.
그래서 조각을 짧게 하면 지연이 줄어든다. 대신 짧은 조각은 파일 개수와 요청 수를 늘려 오버헤드가 커진다. 앞 장의 지연-호환 트레이드오프가 조각 길이라는 숫자 하나에서 다시 나타나는 셈이다.
🟣 마스터 플레이리스트: 화질을 골라 담다
HLS의 진짜 힘은 여기서 나온다. 시청자의 네트워크는 제각각이다. 광랜으로 보는 사람도 있고, 지하철에서 흔들리는 LTE로 보는 사람도 있다. 모두에게 같은 고화질을 밀어붙이면 느린 쪽은 계속 끊긴다. 반대로 모두에게 저화질을 주면 빠른 쪽이 손해다.
HLS는 이 문제를 같은 영상을 여러 화질로 미리 인코딩해 두고, 플레이어가 그중 하나를 고르게 해서 푼다. 이때 화질별 플레이리스트들의 목록을 담은 상위 파일이 마스터 플레이리스트다.
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p/index.m3u8BANDWIDTH는 그 화질을 끊김 없이 보려면 대략 어느 정도 대역폭이 필요한지를 알려 주는 값이다. 플레이어는 이 마스터 플레이리스트를 먼저 받아, 지금 자기 네트워크로 감당할 수 있는 화질의 하위 플레이리스트를 골라 재생을 시작한다.
🟣 적응형 비트레이트: 도중에 화질을 바꾼다
화질을 한 번 고르고 끝이라면 그냥 "화질 선택"이지 적응형은 아니다. ABR(Adaptive Bitrate)의 핵심은 재생 도중에 네트워크 상황에 맞춰 화질을 계속 갈아탄다는 데 있다.
동작 원리는 앞서 본 세그먼트 구조 덕에 자연스럽다. 모든 화질이 같은 시점에 맞춰 같은 길이의 조각으로 잘려 있다. 그래서 플레이어는 다음 조각을 받을 때 다른 화질의 목록에서 같은 순번의 조각을 가져오면, 화질만 바뀌고 재생은 이어진다. 조각과 조각 사이의 경계가 곧 화질을 바꿀 수 있는 지점이 된다.
판단 기준은 대체로 두 가지다. 방금 조각을 얼마나 빨리 받았는지로 실제 대역폭을 추정하고, 버퍼에 남은 분량이 얼마나 되는지로 여유를 본다. 조각을 받는 속도가 처지고 버퍼가 말라 가면 한 단계 낮은 화질로 내려 끊김을 피하고, 여유가 생기면 다시 올린다. 화질이 조금 낮아지는 것과 아예 멈추는 것 중에 시청자가 무엇을 덜 싫어하는지에 대한 판단이 이 로직에 담겨 있다.
이 구조가 앞 장에서 HLS를 "확장 잘 되는 다운링크"라고 부른 이유를 완성한다. 서버는 화질별 조각을 그냥 파일로 만들어 CDN에 올려 둘 뿐이고, 누가 어떤 화질을 언제 고를지는 전적으로 플레이어가 알아서 정한다. 서버는 시청자 개개인의 상태를 추적하지 않는다. 의사 결정을 클라이언트로 밀어낸 덕에 서버가 단순해지고, 그래서 대규모로 확장된다.
🟣 iOS는 왜 HLS를 기본으로 삼나
여기서 iOS 이야기로 넘어온다. 애플 생태계에서 HLS는 선택지 중 하나가 아니라 사실상 기본값이다. 이유는 두 가지가 겹친다.
첫째, HLS를 만든 게 애플이다. 그래서 iOS와 macOS, tvOS의 AVPlayer가 HLS를 네이티브로 재생한다. 개발자 입장에서는 마스터 플레이리스트 주소 하나만 AVPlayer에 넘기면 세그먼트 다운로드, 버퍼 관리, ABR 화질 전환까지 시스템이 알아서 처리한다. 이 장에서 정리한 로직 대부분이 프레임워크 안에 이미 들어 있는 셈이다.
let url = URL(string: "https://cdn.example.com/live/master.m3u8")!
let player = AVPlayer(url: url)
player.play()둘째, App Store 심사 정책이 이를 밀어붙인다. 애플은 셀룰러 네트워크에서 일정 길이나 용량을 넘는 영상은 HLS로 제공하도록 요구해 왔다. 긴 영상을 통짜 파일로 다운로드하게 두면 셀룰러 데이터를 크게 낭비하고, 네트워크가 흔들릴 때 대응할 방법이 없기 때문이다. 세그먼트와 ABR을 강제하는 이 정책 덕에, iOS에서 라이브나 긴 VOD를 다루는 앱은 자연스럽게 HLS로 수렴한다.
클라이언트만 만질 때는 AVPlayer(url:) 한 줄이 전부라 그 뒤가 안 보였다. 서버 쪽을 정리하고 나니, 그 한 줄이 마스터 플레이리스트를 받아 화질을 고르고 세그먼트를 이어 붙이는 이 장의 모든 과정을 대신 해 주고 있었다는 게 보였다. AVPlayer가 감춰 준 복잡성의 목록이 곧 이 장의 목차였던 셈이다.
🟣 정리
.m3u8은 영상 조각들의 목록을 담은 텍스트 플레이리스트다. 실제 미디어는 별도의 세그먼트 파일에 있고, 플레이어는 이 목록을 보고 조각을 순서대로 받아 이어 붙인다.- 라이브에서는 플레이리스트가 계속 갱신된다. 서버가 조각을 다 모아 목록에 올리고 플레이어가 버퍼를 쌓는 시간이 그대로 HLS 지연의 정체다.
- 마스터 플레이리스트는 같은 영상의 여러 화질 목록을 담는다. 플레이어가 자기 대역폭에 맞는 화질을 골라 재생을 시작한다.
- ABR은 재생 도중에도 화질을 갈아탄다. 모든 화질이 같은 경계로 잘려 있어 조각 사이에서 화질만 바꿔 이어 갈 수 있다. 판단은 대역폭 추정과 버퍼 잔량으로 한다.
- 화질 결정을 클라이언트로 밀어낸 덕에 서버는 조각을 CDN에 올리기만 하면 되고, 그래서 대규모로 확장된다. iOS의
AVPlayer는 이 모든 과정을 네이티브로 대신 처리한다.
면접에서 HLS를 물으면 세그먼트와 m3u8까지는 흔히 답한다. 한 단계 더 내려가는 지점은 "왜 지연이 생기는가"와 "ABR의 화질 전환이 어떻게 끊김 없이 되는가"다. 둘 다 세그먼트 경계라는 하나의 구조에서 나온다는 걸 연결하면 이해의 깊이가 드러난다. 실무에서 재생 이슈를 디버깅할 때도 결국 플레이리스트를 직접 열어 세그먼트가 제때 갱신되는지 보는 데서 시작한다.
지금까지는 10초 지연을 감수하고 대규모로 뿌리는 쪽이었다. 다음 장은 그 지연을 도저히 못 견디는 실시간 소통의 영역, WebRTC로 넘어간다. 파일 조각이라는 발상 자체를 버리고 기기끼리 직접 연결을 뚫는 구조를 본다.