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.264 | AVC | 가장 호환성 높음, 디폴트 |
| H.265 | HEVC | 압축 효율 ↑, 라이선스 복잡 |
| AV1 | — | 로열티 프리, 인코딩 느림 |
| VP9 | — | YouTube가 주도, 브라우저 위주 |
HLS는 전통적으로 H.264. 4K HDR에는 HEVC를 쓰기 시작했다.
4.5.2 오디오 코덱
| 코덱 | 특징 |
|---|---|
| AAC | HLS의 표준 |
| AC-3 / E-AC-3 | 5.1 채널, 영화/TV |
| Opus | 저지연, 음성 중심 |
4.5.3 CODECS 문자열 읽는 법
EXT-X-STREAM-INF의 CODECS 속성은
정해진 포맷이다.
예시:
CODECS="avc1.640028,mp4a.40.2"
비디오: avc1.640028
avc1 → H.264 (AVC)
64 → Profile (High)
0028 → Level (4.0)
| Profile 코드 | 이름 |
|---|---|
| 42 | Baseline |
| 4D | Main |
| 64 | High |
| 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
| 마지막 숫자 | 의미 |
|---|---|
| 2 | AAC-LC |
| 5 | HE-AAC |
| 29 | HE-AAC v2 |
플레이어는 이 문자열을 보고 자신이 지원하는 코덱인지 판단한다.
지원하지 않으면 그 variant를 건너뛴다.
4.6 정리: 왜 TS와 fMP4가 공존하는가
이제 두 포맷의 위치가 명확해진다.
| 구분 | MPEG-TS | fMP4 |
|---|---|---|
| 설계 목적 | 방송 송출 | 파일 + 스트리밍 |
| 데이터 형태 | 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를 가르는 출발점이다.