시리즈: 실시간 통화, 어떻게 녹화하는가4/6
- 1.실시간 통화, 왜 녹화해야 하는가
- 2.내 서버에서 직접 녹화한다면
- 3.녹화 모드 해부
- 4.M3U8과 TS 파일의 모든 것 ← 현재 글
- 5.FFmpeg, 미디어의 스위스 아미 나이프
- 6.FFmpeg 실전 파이프라인
M3U8과 TS 파일의 모든 것 — HLS Deep Dive
녹화 파일을 열었더니 MP4가 아니라 m3u8 파일 하나와 잘게 쪼개진 ts 조각들이 나옵니다. 왜 이런 구조일까요? 답은 장애 내성에 있습니다. MP4는 재생에 필요한 메타데이터가 파일 끝에 몰려 있어 서버가 중간에 죽으면 녹화 전체를 잃지만, HLS는 영상을 짧은 세그먼트로 쪼개 저장하므로 이미 완성된 조각은 살아남습니다. 이 글에서는 그 차이가 생기는 원리부터 TS 패킷 구조, M3U8 태그 해석, 세그먼트를 I-frame에서 자르는 이유, Agora의 슬라이싱 규칙까지 실무에서 녹화 파일을 다룰 때 알아야 할 내용을 정리합니다.
녹화 파일을 열어보면 두 가지가 나옵니다. .m3u8 파일과 .ts 파일들. 처음 보면 생소합니다. MP4나 WebM을 예상했는데 왜 이런 구조인가?
이유는 하나입니다. 실시간 녹화에서 장애 내성(Fault Tolerance)이 최우선이기 때문입니다.
"서버가 비정상 종료되어도 지금까지 녹화된 내용은 살아남아야 한다" — 이 요구사항을 충족하려면 MP4 단일 파일 구조는 근본적으로 적합하지 않습니다. 왜 그런지부터 시작합니다.
MP4의 치명적 약점 — moov atom
MP4 파일을 열면 내부는 이렇게 생겼습니다.
moov atom은 MP4에서 사실상 목차(index)입니다. 트랙 정보, 타임스탬프, 각 샘플의 바이트 오프셋 — 플레이어가 영상을 재생하려면 이 정보가 반드시 필요합니다. 문제는 이 moov가 파일의 맨 끝에 위치한다는 것입니다.
녹화가 정상적으로 끝나면 moov가 파일 끝에 기록됩니다. 하지만 서버 크래시, 네트워크 중단, 강제 종료 — 이런 상황에서는 mdat만 쓰여진 채 moov가 없는 파일이 남습니다. 플레이어는 이 파일을 열지 못합니다. 1시간짜리 상담 녹화가 통째로 날아가는 겁니다.
ffmpeg -i input.mp4 -movflags faststart output.mp4로 moov를 파일 앞으로 옮길 수 있습니다. 하지만 이 작업은 녹화가 완전히 끝난 후에만 가능합니다. 진행 중인 녹화에는 적용할 수 없습니다.
HLS — HTTP Live Streaming
Apple이 2009년에 만든 스트리밍 프로토콜입니다. 핵심 아이디어는 단순합니다. 긴 영상을 통째로 다루는 대신, 짧은 조각들로 쪼개서 HTTP로 전달한다.
M3U8은 목차(playlist) 역할을 하는 텍스트 파일이고, TS(Transport Stream)는 실제 미디어가 담긴 조각들입니다. 플레이어는 M3U8을 읽고, 필요한 TS 세그먼트를 순서대로 다운로드해서 재생합니다.
녹화 관점에서 장점이 명확합니다. 세그먼트 단위로 파일이 완성되기 때문에, 중간에 서버가 죽어도 지금까지 완성된 세그먼트들은 온전히 살아있습니다. MP4처럼 마지막에 한 번에 메타데이터를 쓰는 구조가 아닙니다. 녹화 파일이 S3에 저장되고 업로드되는 흐름은 녹화 파일이 S3에 저장되는 흐름에서 다뤘습니다.
TS (Transport Stream) 패킷 구조
TS는 원래 위성방송과 디지털 방송을 위해 설계된 컨테이너 포맷입니다. 핵심 설계 철학은 손실 허용(loss-tolerant)입니다. 신뢰할 수 없는 전송 채널을 가정하고 만들었습니다.
모든 패킷이 고정 188 bytes입니다. 첫 번째 바이트는 항상 0x47 (sync byte)입니다. 패킷 경계를 찾기 위해 파일 전체를 파싱할 필요가 없습니다. 파일 오프셋 mod 188 == 0인 위치를 찾으면 됩니다.
PID(Packet Identifier)는 13-bit 값으로 어느 스트림인지를 식별합니다. PMT(Program Map Table)에 오디오 PID와 비디오 PID가 정의되어 있고, 플레이어는 이를 참고해 패킷을 분류합니다. 패킷 하나가 손상되어도 해당 패킷만 스킵하면 됩니다. 파일 전체가 망가지지 않습니다.
| MP4 | TS | |
|---|---|---|
| 구조 | moov atom이 파일 끝 | 각 패킷이 자기완결적 |
| 중간 손상 | 파일 전체 재생 불가 | 손상된 패킷만 스킵 |
| 실시간 녹화 | 비정상 종료 시 깨짐 | 쓴 세그먼트는 안전 |
| 스트리밍 | 전체 다운로드 필요 | 세그먼트 단위 즉시 재생 |
| 파일 크기 | 효율적 | 헤더 오버헤드 (~2%) |
M3U8 파일 해부
M3U8은 그냥 텍스트 파일입니다. #으로 시작하는 태그와 파일 경로로 구성됩니다. Agora Cloud Recording이 실제로 생성하는 M3U8은 이렇게 생겼습니다.
표준 HLS 태그:
#EXTM3U— M3U8 파일임을 선언하는 시작 태그. 반드시 첫 줄에 있어야 합니다.#EXT-X-VERSION:3— HLS 스펙 버전. Agora는 v3를 사용합니다.#EXT-X-TARGETDURATION:15— 세그먼트 최대 길이(초). 실제 세그먼트는 이보다 짧을 수 있지만, 길어서는 안 됩니다.#EXTINF:15.0,— 바로 다음에 오는 TS 파일의 실제 길이(초). 쉼표로 끝납니다.#EXT-X-ENDLIST— 녹화 완료를 나타냅니다. 이 태그가 없으면 플레이어는 세그먼트가 계속 추가될 것으로 기대합니다 (라이브 스트리밍 모드).
Agora 전용 확장 태그:
#EXT-X-AGORA-TRACK-EVENT—EVENT=START(현재 START만 존재),TRACK_TYPE=AUDIO또는VIDEO,TIME=UTC초. 스트림이 시작되거나, 중단 후 재개될 때 해당 세그먼트에 START가 기록됩니다. 네트워크 중단으로 스트림이 여러 번 끊겼다 재개된 경우 START가 여러 번 나타나며, 그 개수로 중단 횟수를 추정할 수 있습니다.#EXT-X-AGORA-ROTATE—WIDTH,HEIGHT,ROTATE(0/90/180/270). 해상도와 화면 회전 정보. 모바일에서 카메라 방향이 바뀌면 이 태그가 추가됩니다.
마지막 세그먼트의 길이가 12.3초인 것에 주목하세요. 15초 경계에서 녹화가 끝나지 않았기 때문입니다. 실제 녹화 데이터가 12.3초이면 그 길이 그대로 기록됩니다.
Agora 모드별 출력 파일 트리
모드에 따라 생성되는 파일 구조가 다릅니다.
Individual 모드 — 참여자별 분리 저장
UID마다, 트랙(audio/video)마다 별도의 M3U8과 TS 세트가 생성됩니다. 참여자가 10명이면 최대 20개의 M3U8 파일이 나옵니다.
Mix (Composite) 모드 — 합성 후 단일 파일
서버 사이드에서 모든 참여자 영상을 믹싱한 결과물입니다. 파일이 훨씬 단순합니다.
파일명 컨벤션 해부:
타임스탬프가 파일명에 들어가 있어서 파일 시스템 정렬만으로 시간순 순서를 알 수 있습니다.
avFileType 설정
Cloud Recording API 요청 시 출력 포맷을 지정할 수 있습니다.
avFileType에 mp4를 쓰려면 반드시 hls와 함께 ["hls", "mp4"]로 지정해야 합니다. ["mp4"] 단독은 error code 2를 반환합니다. 또한 MP4는 Mix(Composite) 모드와 Web 모드에서만 지원됩니다. Individual 모드에서는 사용할 수 없습니다.
MP4는 녹화가 끝난 뒤에 만들어지는 후처리 결과물이 아닙니다. 녹화 도중 실시간으로 생성되며, 파일이 일정 길이 또는 약 2GB 크기에 도달하면 새 MP4 파일로 분할(롤링)됩니다. 그래서 긴 녹화에서는 여러 개의 MP4 파일이 순차적으로 만들어집니다.
TS 세그먼트를 MP4로 병합하는 변환은 일반 녹화 흐름이 아니라, High Availability(고가용성) 복구 시나리오에 한정됩니다. 녹화 중 장애가 발생해 TS 조각만 남은 경우, 별도의 외부 transcoding 스크립트로 TS를 MP4로 합쳐 복구합니다.
I-frame / P-frame / B-frame — 세그먼트를 어디서 자르는가
TS 세그먼트를 임의로 자를 수는 없습니다. 비디오 압축의 기본 개념이 개입합니다.
- I-frame (Key Frame): 완전한 이미지 정보를 담습니다. 앞 프레임 없이도 독립 재생이 가능합니다. JPEG 한 장과 유사한 정보량을 가집니다.
- P-frame: 이전 I-frame 또는 P-frame과의 차이(delta)만 저장합니다. I-frame 없이는 재생할 수 없습니다.
- B-frame: 앞뒤 프레임 모두와의 차이를 저장합니다. 가장 압축률이 높지만 의존성이 가장 큽니다.
TS 세그먼트는 반드시 I-frame에서 시작해야 합니다. P-frame이나 B-frame에서 세그먼트가 시작되면, 참조할 이전 프레임이 없어서 화면이 깨집니다. 플레이어가 세그먼트를 독립적으로 재생할 수 없게 됩니다.
TS 슬라이싱 규칙
Agora가 TS 세그먼트를 자르는 조건은 다음과 같습니다.
비디오 슬라이싱 트리거:
오디오 슬라이싱:
오디오는 단순합니다. 비디오 슬라이싱과 동기화되거나, 독립적으로 매 15초마다 일정하게 잘립니다.
비디오와 오디오의 타임스탬프가 어긋나는 경우, Individual 모드에서는 M3U8의 #EXTINF 값을 통해 플레이어가 싱크를 맞춥니다. 나중에 FFmpeg으로 합칠 때 이 타임스탬프 정렬이 중요해집니다.
M3U8 파싱 스크립트
Agora M3U8을 파싱해서 유용한 정보를 빠르게 추출하는 Python 스크립트입니다.
녹화 파일을 받았을 때 가장 먼저 실행할 만한 스크립트입니다. 총 길이와 스트림 중단 여부를 빠르게 확인할 수 있습니다. 세그먼트 수가 예상보다 적거나, START 이벤트가 여러 번 나타나면 그 개수만큼 네트워크 중단이 있었음을 의미합니다.
핵심 요약
- MP4가 실시간 녹화에 부적합한 이유는 moov atom 구조 때문입니다. 비정상 종료 시 메타데이터가 기록되지 않아 파일 전체를 잃습니다.
- HLS는 영상을 짧은 TS 세그먼트로 쪼개 저장합니다. 완성된 세그먼트는 서버가 죽어도 안전합니다.
- TS 패킷은 고정 188 bytes로 자기완결적입니다. 중간 손상이 해당 패킷에만 영향을 미칩니다.
- M3U8은 플레인 텍스트 목차입니다. Agora 전용 태그(
#EXT-X-AGORA-TRACK-EVENT,#EXT-X-AGORA-ROTATE)를 통해 스트림 이벤트와 해상도 변경을 기록합니다. - TS 세그먼트는 반드시 I-frame에서 시작해야 합니다. Agora는 I-frame 도착 후 15초를 기준으로 슬라이싱하며, 코덱/해상도 변경, 스트림 중단, 강제 타임아웃 조건에서도 즉시 자릅니다.
- MP4 출력은
hls와 함께 지정해야 합니다.avFileType: ["mp4"]단독은 error code 2를 반환하며,["hls", "mp4"]로 지정하고 Mix/Web 모드에서만 사용할 수 있습니다. MP4는 녹화 중 실시간으로 생성되며 약 2GB(또는 일정 길이)마다 새 파일로 분할됩니다. TS → MP4 병합은 일반 흐름이 아니라 HA 복구 시나리오에 한정됩니다.
시리즈 네비게이션
실시간 통화, 어떻게 녹화하는가 시리즈
관련 글
- #14 녹화 모드 해부 — Individual/Mix/Web — 이 글의 M3U8/TS 출력이 어느 녹화 모드에서 어떻게 만들어지는지의 상위 맥락
- #33 Cloud Recording 점프 재생 — moov/ENDLIST/DISCONTINUITY —
#EXT-X-ENDLIST와 moov atom 이야기를 재생/탐색 관점에서 더 깊게 파고든 글 - #11 Cloud Recording 녹화 파일 저장 — S3 — 여기서 만들어진 TS/M3U8 세그먼트가 실제로 클라우드 스토리지에 올라가는 흐름
- #27 프레임의 모든 것 — Interlaced/I·P·B/GOP/PLI·FIR — "세그먼트는 I-frame에서 시작" 규칙의 근거인 GOP 구조 심화
- #40 라이브 스트리밍 프로토콜 선택 — HLS/LL-HLS/WebRTC — HLS의 세그먼트 모델이 지연·확장성 측면에서 갖는 트레이드오프
참고 자료
- RFC 8216 — HTTP Live Streaming —
#EXTM3U,#EXT-X-TARGETDURATION,#EXTINF,#EXT-X-ENDLIST등 M3U8 태그의 공식 정의 - ISO/IEC 13818-1 (MPEG-2 Systems / Transport Stream) — 188바이트 고정 패킷, sync byte 0x47, PID, PMT의 원본 표준
- Apple — HLS Authoring Specification for Apple Devices — 세그먼트 길이·키프레임 정렬 등 실무 권고사항
- Agora — Cloud Recording RESTful API (recordingFileConfig / avFileType) —
avFileType,recordingFileConfig등 출력 포맷 설정의 공식 레퍼런스 - ISO/IEC 14496-12 (ISO Base Media File Format) — MP4의
moov/mdatatom 구조를 정의하는 표준 - FFmpeg — movflags / faststart —
-movflags faststart로 moov atom을 파일 앞으로 옮기는 옵션 문서