MP4·WebM·MKV 컨테이너 내부 구조 심화 | 뮤싱·시크·fMP4·메타데이터
이 글의 핵심
업로드한 영상이 앞부분만 재생되거나 탐색이 느린 문제는 컨테이너 인덱스가 어디에 어떻게 기록됐는지와 관련이 깊습니다. HTTP 범위 요청과 인덱스의 연결, fMP4·CMAF가 스트리밍에 유리한 이유와 대가, 메타데이터와 챕터 처리, 단계별로 컨테이너를 달리 쓰는 프로덕션 패턴과 QC 자동화를 다룹니다.
들어가며
이 글은 MP4 vs MKV vs WebM 비교에서 다룬 호환성과 사용 시나리오를 전제로, 컨테이너 파일이 실제로 어떻게 조립되고 분해되며 탐색되는지를 구현과 운영 관점에서 설명합니다. H.264, AAC, VP9 같은 코덱의 압축 이론보다는, 압축된 비트스트림을 파일 레이아웃과 타임라인에 어떻게 배치하는지에 초점을 둡니다.
뮤싱(Muxing)과 디먹싱(Demuxing)
파이프라인에서의 역할
인코딩은 원시 프레임이나 PCM 오디오를 코덱 규격에 맞는 압축 비트스트림으로 바꾸는 과정입니다. 뮤싱(muxing, multiplexing)은 그 비트스트림을 컨테이너가 다루는 단위(샘플, 프레임, 블록)로 나누고, 표시 시각(PTS), 디코딩 시각(DTS), 길이를 붙인 뒤 트랙 메타데이터와 함께 파일의 바이트 배치로 기록하는 과정입니다.
인코딩부터 뮤싱까지의 흐름은 대략 다음과 같습니다.
- 미디어 스트림 생성: 비디오, 오디오, 자막이 각각의 인코더에서 나옵니다.
- 타임베이스 정렬: 각 스트림의 타임스탬프를 공통 타임라인에 맞춥니다. 스트림마다 시간 단위(예: 비디오 90kHz, 오디오는 샘플레이트)가 달라도 같은 시각을 가리키도록 환산합니다.
- 인터리빙(interleaving): 재생할 때 디스크나 네트워크에서 한 번에 읽을 수 있도록 비디오와 오디오 데이터를 시간 순으로 번갈아 배치합니다. 간격은 muxer 설정으로 정합니다.
- 컨테이너 기록: MP4는 Box 트리로
moov와mdat를, Matroska 계열은 EBML 요소로Segment와Cluster등을 기록합니다.
디먹싱(demuxing)은 그 반대입니다. 파일 헤더와 인덱스를 읽어 트랙별 비트스트림을 꺼내고, 디코더에는 시간 순 패킷과 디코딩에 필요한 파라미터(H.264의 SPS/PPS, Matroska의 CodecPrivate 등)를 넘깁니다. 플레이어는 보통 demuxer, decoder, renderer 순으로 동작하며, 오디오와 비디오의 동기는 컨테이너에 기록된 PTS를 기준으로 맞춥니다.
MP4: 트랙·샘플·청크 모델
MP4(MPEG-4 Part 14)는 ISO Base Media File Format(ISOBMFF)을 기반으로 합니다. 각 트랙(trak)의 미디어는 샘플 단위로 관리되며, 샘플 테이블(stbl) 안의 여러 Box가 샘플의 크기(stsz), 시간 간격(stts), 샘플이 어느 청크에 속하는지(stsc), 청크가 파일의 어디에 있는지(stco/co64)를 기록합니다. 이 테이블들을 조합하면 “몇 번째 샘플이 몇 초에 재생되고 파일의 몇 바이트에 있는가”를 계산할 수 있습니다. muxer는 인코더가 낸 프레임을 mdat에 순서대로 쓰고, 다 쓴 뒤 moov 안에 이 인덱스를 완성합니다.
MKV·WebM: EBML·Cluster 모델
Matroska(MKV)는 이진 XML이라고 할 수 있는 EBML로 계층 구조를 표현합니다. 최상위 Segment 안에 코덱 정보를 담은 Tracks, 시간 순으로 미디어 블록을 담은 Cluster, 시크용 인덱스인 Cues, 그리고 Tags, Chapters 등이 들어갑니다. WebM은 Matroska의 부분집합으로, 허용하는 코덱(VP8, VP9, AV1, Vorbis, Opus)과 요소가 제한됩니다.
Cluster 안의 SimpleBlock은 블록마다 클러스터 기준 상대 타임스탬프와 키프레임 여부를 함께 담습니다. MP4가 인덱스를 moov에 모아 두는 것과 달리, Matroska는 이런 정보가 블록 가까이에 있어 인덱스가 없어도 순차적으로 읽으며 재생하기 쉽습니다.
실무에서의 함정
- 타임베이스 불일치: 소스가 가변 프레임레이트(VFR)이거나 타임스탬프가 중간에 끊겨 있으면 뮤싱 후 오디오와 비디오 싱크가 어긋납니다. 재인코딩 없이
-c copy로 컨테이너만 바꿀 때 특히 주의해야 합니다. - 인터리빙 간격: 너무 잘게 번갈아 배치하면 청크 수가 늘어 인덱스가 커지고, 너무 드물게 배치하면 재생할 때 파일의 먼 위치를 오가며 읽어야 합니다. HTTP로 재생하는 경우 이 차이가 추가 Range 요청으로 이어질 수 있습니다.
시크 테이블과 인덱싱
왜 인덱스가 필요한가
처음부터 끝까지 순서대로 재생만 한다면 비트스트림을 앞에서부터 읽기만 하면 됩니다. 하지만 사용자가 임의 시점으로 이동(seek)하면, 디코더는 그 시각 직전의 키프레임부터 읽어야 합니다. 키프레임이 아닌 프레임은 앞 프레임을 참조하기 때문입니다. 컨테이너의 시크 테이블(인덱스)은 “원하는 시각 근처의 키프레임이 파일의 몇 바이트에 있는가”를 빠르게 찾게 해 줍니다.
MP4의 핵심 테이블
비디오 트랙에서 시크와 관련된 Box는 다음과 같습니다.
stss: 키프레임(동기 샘플) 번호 목록입니다. 시크할 때 어느 샘플부터 디코딩을 시작할지 여기서 찾습니다. 이 Box가 없으면 모든 샘플이 동기 샘플로 간주됩니다.stco/co64: 각 청크의 파일 내 오프셋입니다. 4GB를 넘는 파일은 64비트 오프셋인co64를 씁니다.stsz: 각 샘플의 크기입니다.stts/ctts:stts는 디코딩 시간 간격을,ctts는 디코딩 시각과 표시 시각의 차이를 기록합니다. B-프레임이 있으면 디코딩 순서와 표시 순서가 달라지므로ctts가 필요합니다.
오디오는 대부분 모든 프레임이 독립적으로 디코딩되므로 시크가 단순합니다. 비디오는 stss가 키프레임을 제대로 가리키지 않으면 시크한 지점에서 디코딩이 깨지거나 화면이 뭉개질 수 있습니다.
MKV의 Cues
Matroska는 Cues 요소에 CuePoint들을 모아, 특정 타임코드의 키프레임이 파일의 어느 Cluster에 있는지 가리킵니다. Cues가 없거나 불완전하면 플레이어가 파일을 훑어가며 위치를 찾아야 해서 시크가 느리거나 부정확해집니다. 배포용 파일을 만들 때는 muxer가 Cues를 기록하는지, 가능하면 파일 앞쪽에 두는지 확인하는 것이 좋습니다(FFmpeg의 matroska muxer는 -reserve_index_space 옵션으로 앞쪽에 공간을 확보할 수 있습니다).
WebM도 Matroska 계열이므로 같은 Cues 기반 시크 모델을 씁니다.
HTTP와의 연결
로컬 파일에서는 디스크의 원하는 위치를 바로 읽지만, HTTP에서는 Range 요청으로 필요한 바이트 구간을 받아 옵니다. MP4에서 moov가 파일 앞쪽에 있으면(faststart) 첫 요청에서 바로 인덱스를 얻어 재생을 시작할 수 있습니다. moov가 파일 끝에 있으면 플레이어가 파일 끝을 따로 요청해 인덱스를 받아 와야 하므로 재생 시작이 늦어지고, Range 요청을 지원하지 않는 서버에서는 전체를 받을 때까지 재생하지 못합니다. 업로드한 영상이 한참 로딩되다가 재생되는 문제의 흔한 원인이 이것입니다.
단편(Fragmented) MP4와 일반 MP4
일반 MP4
일반 MP4는 하나의 moov(모든 트랙의 메타데이터와 샘플 테이블)와 하나 또는 몇 개의 mdat(실제 미디어 데이터)로 구성됩니다. 파일 하나로 완결되므로 편집이나 로컬 재생에 익숙한 구조입니다. 다만 moov는 모든 샘플을 기록한 뒤에야 완성되므로, 녹화 중에 프로그램이 비정상 종료되면 moov가 없는 재생 불가 파일이 남습니다.
FFmpeg의 -movflags +faststart는 인코딩이 끝난 뒤 moov를 mdat 앞으로 옮겨, 웹에서 순차 다운로드하며 재생을 바로 시작할 수 있게 합니다. 하지만 적응형 비트레이트(ABR) 스트리밍이 요구하는, 세그먼트 단위로 독립적인 구조와는 다릅니다.
단편 MP4(fMP4, CMAF 계열)
단편(fragmented) MP4는 moof(movie fragment)와 mdat 쌍이 시간 순으로 반복되는 구조입니다. 각 moof는 해당 구간의 트랙 프래그먼트(traf)를 담고, 샘플 정보는 moov의 전역 테이블 대신 tfhd, trun 같은 프래그먼트 로컬 Box에 기록됩니다. 파일 앞의 moov에는 코덱 정보와 mvex만 있고 샘플 테이블은 비어 있으며, 이 부분이 스트리밍에서 초기화 세그먼트(init segment)가 됩니다.
이 구조는 fMP4를 쓰는 HLS, MPEG-DASH, CMAF에서 널리 쓰이며, 세그먼트 단위로 독립적으로 전송하고 CDN에 캐시하기 좋습니다. 브라우저의 MSE(Media Source Extensions)가 초기화 세그먼트와 미디어 세그먼트를 차례로 버퍼에 붙이는 방식과도 잘 맞습니다. 조각마다 헤더가 붙으므로 같은 내용의 일반 MP4보다 파일이 조금 커지는 것이 대가입니다.
선택 시 트레이드오프
| 관점 | 일반 MP4 | 단편 fMP4 |
|---|---|---|
| 편집·단일 파일 배포 | 단순하고 도구 지원이 풍부 | 일부 편집 도구에서 불편할 수 있음 |
| 라이브·녹화 중단 | 끝까지 기록해야 moov 완성 | 조각 단위로 완결되어 중단에 강함 |
| ABR·CDN | 세그먼트 분할을 별도로 설계 | 세그먼트와 프래그먼트가 자연스럽게 대응 |
| 일반 플레이어 호환 | 넓음 | 오래되거나 제한된 환경은 확인 필요 |
웹에서 영상 하나를 제공한다면 faststart를 적용한 일반 MP4가 흔하고, HLS/DASH 파이프라인이라면 fMP4 세그먼트와 매니페스트 조합이 사실상 표준입니다.
메타데이터와 챕터
MP4
MP4는 udta, meta, ilst 같은 Box로 제목, 앨범, 언어 등의 메타데이터를 담습니다. 챕터는 도구와 플랫폼에 따라 QuickTime 챕터 트랙, Nero 방식의 chpl Box, 외부 파일 등 처리 방식이 달라서, 넣었는데 플레이어에서 안 보이는 문제가 종종 생깁니다. 배포 전에 대상 플레이어나 스토어의 스펙을 확인하는 것이 안전합니다.
MKV(Matroska)
Matroska는 키-값 메타데이터인 Tags, 에디션과 ChapterAtom 계층으로 된 Chapters, 폰트나 커버 이미지를 넣는 Attachments까지 한 파일 안에서 다룰 수 있게 설계되었습니다. 다국어 태그와 언어별 챕터 제목을 지원해 아카이브나 긴 영상에 유리합니다.
WebM
WebM은 Matroska의 제한된 프로파일이라 메타데이터와 챕터의 개념은 비슷하지만, 도구 지원은 MP4나 MKV보다 단순한 편입니다. 웹 배포에서는 자막을 HTML <track> 요소로 따로 붙이거나 매니페스트에 메타데이터를 넣어 보완하는 경우가 많습니다.
FFmpeg에서의 패턴
전역 메타데이터는 -metadata key=value로, 스트림별 메타데이터는 -metadata:s:s:0 language=kor처럼 지정합니다. 챕터는 FFMETADATA 형식의 텍스트 파일을 입력으로 넣어 추가할 수 있고, 이미 챕터가 있는 파일을 -c copy로 리먹스하면 기본적으로 유지됩니다. 다만 출력 컨테이너가 챕터를 어떤 방식으로 저장하느냐에 따라 대상 플레이어에서 보이지 않을 수 있으므로 결과를 직접 확인해야 합니다.
프로덕션에서의 컨테이너 선택 패턴
단계별 역할 분리
많은 파이프라인은 단계마다 다른 컨테이너를 씁니다.
- 촬영·중간 파일: 카메라나 레코더 고유 포맷(또는 MXF 등)을 쓰며, 편집 소프트웨어와의 호환이 우선입니다.
- 마스터·아카이브: 편집이 끝난 원본은 트랙, 챕터, 태그를 풍부하게 담을 수 있는 MKV나, 방송·영화 쪽이라면 MXF와 IMF 같은 업계 표준을 씁니다. 무손실 오디오나 여러 오디오 트랙을 그대로 두려면 컨테이너가 그 코덱을 수용해야 합니다.
- 메자닌(mezzanine): 배포용 인코딩의 입력으로 쓰는 고품질 중간본입니다. ProRes나 고비트레이트 H.264/HEVC를 MOV, MP4, MKV에 담는 경우가 많습니다.
- 배포(웹·모바일·OTT): MP4 단일 파일이나 fMP4와 HLS/DASH 조합을 플랫폼 정책, CDN, 필요하면 DRM 요구에 맞춰 고릅니다.
손실 압축을 여러 번 거치면 화질이 누적해서 떨어지므로, 중간 단계는 무손실에 가깝게 두고 최종 배포 단계에서만 목표 비트레이트로 인코딩하는 것이 일반적입니다.
웹·모바일
- 최대 호환 단일 파일: H.264와 AAC를 담은 MP4가 기본 후보입니다.
- 대역폭·로열티: VP9이나 AV1, Opus를 담은 WebM을 함께 제공하고 MP4를 폴백으로 두는 패턴이 흔합니다.
- 적응형 스트리밍: fMP4 세그먼트와 매니페스트를 쓰고, 플레이어는 MSE 기반 라이브러리나 네이티브 HLS로 재생합니다.
장편·다국어·자막이 많은 경우
- 내부 검수·보관: 여러 자막, 오디오, 챕터를 MKV 한 파일에서 관리합니다.
- 스토어·단말 제출: 제출 스펙이 MP4만 허용하는 경우가 많아 최종 단계에서만 리먹스하거나 인코딩합니다. 이때 자막은 별도 파일(sidecar)로 요구되기도 합니다.
QC 자동화
프로덕션에서는 ffprobe, MediaInfo, 전용 QC 도구로 코덱 프로파일, HDR 메타데이터, 오디오 채널 구성, 싱크 드리프트를 검사합니다. 같은 정보라도 컨테이너마다 기록되는 위치가 다릅니다. 예를 들어 HDR 색 정보는 MP4에서는 colr, mdcv Box에, MKV에서는 Colour 요소에 들어갑니다. 그래서 같은 영상인데 메타데이터만 달라지는 문제를 줄이려면, 마스터에서 배포본을 만드는 변환 스크립트를 표준화해 두는 편이 좋습니다.
포맷별로 이어서 읽을 글
기초 비교표와 시나리오는 MP4 vs MKV vs WebM 비교를, 포맷별 실무는 MP4 컨테이너 구조, MKV 실전 가이드, WebM 웹 표준을 참고하면 좋습니다.
자주 묻는 질문 (FAQ)
Q. 재인코딩 없이 -c copy로 리먹스했는데 음성과 영상 싱크가 어긋나는 이유는 무엇인가요?
A. 소스가 가변 프레임레이트(VFR)이거나 타임스탬프가 중간에 끊겨 있으면, 컨테이너를 바꾸는 과정에서 타임베이스가 맞지 않아 싱크가 어긋날 수 있습니다. -c copy는 스트림을 다시 만들지 않기 때문에 이런 타임스탬프 문제를 고쳐 주지 않습니다. 리먹스 전에 ffprobe로 타임스탬프와 프레임레이트를 확인하고, 문제가 있으면 해당 스트림만 재인코딩하는 방식을 검토해야 합니다.