시리즈: 실시간 통화, 어떻게 녹화하는가4/6
- 1.실시간 통화, 왜 녹화해야 하는가
- 2.내 서버에서 직접 녹화한다면
- 3.Agora 녹화 모드 비교
- 4.M3U8과 TS 구조 ← 현재 글
- 5.FFmpeg 미디어 처리
- 6.FFmpeg 실전 파이프라인
M3U8과 TS 구조 — HLS 녹화 파일 읽기
Cloud Recording 결과에 포함된 M3U8 재생목록과 MPEG-TS 세그먼트를 읽는 방법을 설명합니다. MP4의 `moov` 위치와 fragmented MP4를 구분하고, TS packet·PES·codec frame의 관계, HLS 태그, target duration 규칙을 살펴봅니다. Agora의 track event와 slice 파일명은 공식 형식에 맞춰 해석합니다.
녹화 파일을 열어보면 두 가지가 나옵니다. .m3u8 파일과 .ts 파일들. 처음 보면 생소합니다. MP4나 WebM을 예상했는데 왜 이런 구조인가?
HLS 조각과 playlist 구조는 녹화 중 파일을 순차 업로드하고 장애 뒤 남은 구간을 처리하는 데 유리합니다.
단일 일반 MP4와 HLS가 장애·재생·후처리 측면에서 어떻게 다른지부터 살펴봅니다.
MP4의 moov atom
MP4 파일을 열면 내부는 이렇게 생겼습니다.
moov atom은 MP4의 track 정보, timestamp, sample 위치 같은 metadata를 담습니다. moov가 파일 끝에 놓이는 파일도 있지만 항상 그런 것은 아닙니다. faststart는 완성된 일반 MP4의 moov를 앞으로 옮기고, fragmented MP4는 여러 fragment의 metadata를 나눠 기록할 수 있습니다.
일반 MP4 writer가 metadata를 마감하기 전에 종료되면 player가 파일을 열지 못할 수 있습니다. 복구 가능성은 writer와 fragment 구조, 남은 metadata에 따라 달라지므로 전체 녹화가 항상 손실된다고 단정할 수는 없습니다.
-movflags +faststart는 완성된 일반 MP4의 index를 앞쪽으로 재배치해 progressive download를 돕는 옵션입니다. 녹화 중 장애 복구 기능 자체는 아닙니다.
HLS — HTTP Live Streaming
Apple이 2009년에 만든 스트리밍 프로토콜입니다. 핵심 아이디어는 단순합니다. 긴 영상을 통째로 다루는 대신, 짧은 조각들로 쪼개서 HTTP로 전달한다.
M3U8은 목차(playlist) 역할을 하는 텍스트 파일이고, TS(Transport Stream)는 실제 미디어가 담긴 조각들입니다. 플레이어는 M3U8을 읽고, 필요한 TS 세그먼트를 순서대로 다운로드해서 재생합니다.
녹화 관점에서는 업로드가 완료된 segment를 개별 객체로 보존할 수 있다는 장점이 있습니다. 다만 녹화 서버 failover가 발생하면 bak<n> playlist가 추가될 수 있어 복구 시 여러 playlist를 함께 처리해야 합니다. 저장 흐름은 녹화 파일이 S3에 저장되는 흐름에서 다뤘습니다.
TS (Transport Stream) 패킷 구조
TS는 원래 위성방송과 디지털 방송을 위해 설계된 컨테이너 포맷입니다. 핵심 설계 철학은 손실 허용(loss-tolerant)입니다. 신뢰할 수 없는 전송 채널을 가정하고 만들었습니다.
TS packet은 188 bytes이고 header 첫 byte는 0x47 sync byte입니다. 파일 시작점이 packet 경계로 정렬되었다는 보장이 없거나 앞부분이 손상되었다면 여러 packet 간격에서 반복되는 sync byte를 확인해 경계를 찾아야 합니다.
PID(Packet Identifier)는 13-bit 값으로 어느 stream에 속하는지 식별합니다. PMT(Program Map Table)에 audio/video PID가 정의됩니다. 하나의 PES packet이나 video frame은 여러 TS packet에 걸칠 수 있으므로 TS packet 하나가 완결된 media unit은 아니며, 손실 영향도 codec의 참조 구조에 따라 뒤 구간으로 이어질 수 있습니다.
| MP4 | TS | |
|---|---|---|
| 구조 | 일반/fragmented 구성 가능 | 188-byte packet과 playlist segment |
| 중간 손상 | metadata·fragment 구조에 따라 영향이 다름 | 손실 감지가 쉽지만 media dependency 영향은 남음 |
| 실시간 녹화 | writer와 fragmentation 방식에 따라 다름 | 완료·업로드된 segment를 개별 보존 가능 |
| 스트리밍 | range request/progressive/fragmented 방식 지원 | playlist와 segment 단위 재생 |
| 파일 크기 | 효율적 | 헤더 오버헤드 (~2%) |
M3U8 파일 구조
M3U8은 그냥 텍스트 파일입니다. #으로 시작하는 태그와 파일 경로로 구성됩니다. Agora Cloud Recording이 실제로 생성하는 M3U8은 이렇게 생겼습니다.
표준 HLS 태그:
#EXTM3U— M3U8 파일임을 선언하는 시작 태그. 반드시 첫 줄에 있어야 합니다.#EXT-X-VERSION:3— 위 Agora 예제 playlist가 요구하는 HLS compatibility version입니다.#EXT-X-TARGETDURATION:34— RFC 8216의 target duration입니다. 각EXTINFduration을 가장 가까운 정수로 반올림한 값이 이를 넘지 않아야 합니다.#EXTINF:6.332000— 바로 다음 TS 파일의 실제 길이입니다. RFC 문법은 뒤에 comma를 요구하지만 Agora 출력은 comma를 생략할 수 있습니다. player 호환 문제가 있으면privateParams의correctEXTINF를 사용합니다.#EXT-X-ENDLIST— 녹화 완료를 나타냅니다. 이 태그가 없으면 플레이어는 세그먼트가 계속 추가될 것으로 기대합니다 (라이브 스트리밍 모드).
Agora 전용 확장 태그:
#EXT-X-AGORA-TRACK-EVENT—EVENT=START,TRACK_TYPE=AUDIO|VIDEO,TIME=<17자리 UTC 시각>. 최초 시작과 중단 후 재시작에 기록되므로 한 track의 재시작 횟수는START수에서 최초 시작을 뺀 값으로 추정합니다.#EXT-X-AGORA-ROTATE—WIDTH,HEIGHT,ROTATE(0/90/180/270),TIME=<17자리 UTC 시각>을 기록합니다.
예제처럼 video segment 길이는 고정 15초가 아닙니다. I-frame, codec·해상도 변경, stream 중단과 강제 분할 조건에 따라 달라집니다.
Agora 모드별 출력 파일 트리
모드에 따라 생성되는 파일 구조가 다릅니다.
Individual 모드 — 참여자별 분리 저장
streamMode=default에서는 UID마다 audio/video M3U8이 생성됩니다. streamMode=standard에서 audio와 video를 함께 녹화하면 UID별 merged M3U8도 추가되므로 파일 수가 달라집니다.
Composite 모드 — 합성 후 단일 output
서버 사이드에서 모든 참여자 영상을 믹싱한 결과물입니다. 파일이 훨씬 단순합니다.
파일명 컨벤션 해부:
동일한 prefix 안에서는 17자리 UTC 시각을 문자열로 정렬해 segment 시작 순서를 확인할 수 있습니다. 이 값은 Unix epoch가 아닙니다.
avFileType 설정
Cloud Recording API 요청 시 출력 포맷을 지정할 수 있습니다.
HLS만 요청하는 설정입니다.
Composite/Web page recording에서 HLS와 MP4를 함께 요청하는 설정입니다.
avFileType에 mp4를 쓰려면 hls와 함께 지정해야 합니다. mp4 단독 설정은 유효하지 않습니다. MP4는 Composite와 Web page recording에서만 지원되며 Individual recording에서는 사용할 수 없습니다.
Composite/Web page recording에서 현재 MP4 길이가 약 3시간 또는 크기가 약 2GB에 도달하면 새 MP4가 생성됩니다. Web page recording은 maxVideoDuration으로 길이 기준을 별도 설정할 수 있습니다.
HA 전환으로 여러 bak<n> playlist가 생긴 경우 Agora의 HA transcoder로 원본과 failover 이후 playlist를 합칠 수 있습니다. 일반적인 HLS→MP4 remux도 가능하지만 codec·timestamp·segment 존재 여부를 먼저 확인해야 합니다.
I-frame / P-frame / B-frame — 세그먼트를 어디서 자르는가
TS 바이트를 임의 위치에서 잘라도 독립 재생 가능한 segment가 된다고 보장할 수 없습니다. 비디오 frame의 참조 관계를 고려해야 합니다.
- I-frame (Key Frame): 완전한 이미지 정보를 담습니다. 앞 프레임 없이도 독립 재생이 가능합니다. JPEG 한 장과 유사한 정보량을 가집니다.
- P-frame: 이전 I-frame 또는 P-frame과의 차이(delta)만 저장합니다. I-frame 없이는 재생할 수 없습니다.
- B-frame: 이전·이후 reference frame을 사용할 수 있습니다. 구체적인 참조 구조와 압축 효율은 encoder 설정에 따라 달라집니다.
segment를 독립 재생하려면 시작 frame이 필요한 참조 정보를 갖춰야 하므로 I-frame 경계가 유리합니다. 그러나 Agora의 browser H.264 강제 분할에서는 새 segment가 I-frame으로 시작하지 않을 수 있고, 이 경우 해당 segment를 단독 decode·재생할 수 없습니다.
TS 슬라이싱 규칙
Agora가 TS 세그먼트를 자르는 조건은 다음과 같습니다.
비디오 슬라이싱 트리거:
오디오 슬라이싱:
오디오 slice는 15초에 도달하면 분할됩니다. 비디오 slice와 항상 같은 경계로 잘린다고 가정하면 안 됩니다.
비디오와 오디오의 타임스탬프가 어긋나는 경우, Individual 모드에서는 M3U8의 #EXTINF 값을 통해 플레이어가 싱크를 맞춥니다. 나중에 FFmpeg으로 합칠 때 이 타임스탬프 정렬이 중요해집니다.
M3U8 파싱 스크립트
Agora M3U8을 파싱해서 유용한 정보를 빠르게 추출하는 Python 스크립트입니다.
이 스크립트는 playlist 기준 총 길이와 START event를 확인합니다. 최초 START 이후 추가 START는 stream 재시작을 나타내지만, 원인이 반드시 사용자 네트워크 단절이라고 단정할 수는 없습니다.
핵심 요약
- 일반 MP4는 metadata 마감 전 종료 시 재생이 어려울 수 있지만 moov 위치와 fragmentation 방식은 구현에 따라 다릅니다.
- HLS는 media를 segment와 playlist로 나눕니다. 업로드된 객체는 보존되지만 failover 시 여러 playlist를 함께 처리해야 할 수 있습니다.
- TS packet은 188 bytes이지만 하나의 완결된 frame은 아니며 손실 영향은 media dependency에 따라 이어질 수 있습니다.
- M3U8은 플레인 텍스트 목차입니다. Agora 전용 태그(
#EXT-X-AGORA-TRACK-EVENT,#EXT-X-AGORA-ROTATE)를 통해 스트림 이벤트와 해상도 변경을 기록합니다. - 비디오 slice는 15초 도달 후 I-frame, codec·해상도 변경, stream 중단 등에 분할됩니다. browser H.264 강제 분할은 새 slice가 I-frame으로 시작하지 않을 수 있습니다.
- MP4 출력은
hls와 함께 지정하고 Composite/Web page recording에서 사용합니다. 약 3시간 또는 약 2GB에 도달하면 다음 MP4로 분할됩니다.
시리즈 네비게이션
실시간 통화, 어떻게 녹화하는가 시리즈
관련 글
- #14 Agora 녹화 모드 비교 — Individual, Composite, Web page — 이 글의 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 — Cloud Recording API의 현행 레퍼런스.
- Agora — Manage recorded files — 파일명, M3U8 tag, slicing, HA 처리 규칙.
- ISO/IEC 14496-12 (ISO Base Media File Format) — MP4의
moov/mdatatom 구조를 정의하는 표준 - FFmpeg — movflags / faststart —
-movflags faststart로 moov atom을 파일 앞으로 옮기는 옵션 문서