MP4 컨테이너 구조: ISO BMFF 박스, moov·mdat, fMP4와 FFmpeg 실전
이 글의 핵심
웹에서 영상 재생이 늦게 시작되거나 아예 안 되는 문제는 moov 박스 위치나 코덱 프로필 같은 컨테이너 세부 사항에서 출발하는 경우가 많습니다. Progressive와 Fragmented MP4의 차이, YouTube 업로드와 오프라인 다운로드처럼 목적에 따른 설정, 자막 동기화 문제 해결까지 다뤄 상황에 맞는 MP4를 만들 수 있게 합니다.
들어가며
MP4는 파일 확장자로 익숙하지만, 규격상 이름은 ISO Base Media File Format(ISO BMFF, ISO/IEC 14496-12) 위에 MPEG-4 시스템(14496-14 등) 관례를 얹은 형태로 이해하는 것이 정확합니다. H.264/H.265/AV1 비디오와 AAC 오디오를 같은 파일에 담는 가장 보편적인 상자(container)이며, 모바일·OTT·웹·편집 툴이 모두 기대하는 “기본 포맷”입니다.
MP4를 다룰 때 생기는 문제 대부분은 한 가지 사실에서 출발합니다. 미디어 데이터(mdat)를 해석하려면 목차(moov)가 먼저 있어야 한다는 점입니다. moov에는 각 프레임이 파일의 몇 번째 바이트에 있고, 크기가 얼마이며, 언제 재생해야 하는지가 담긴 표가 들어 있습니다. 이 표가 없으면 mdat은 경계를 알 수 없는 바이트 덩어리일 뿐입니다. 그래서 moov가 파일 끝에 있으면 플레이어는 끝까지 받아야 재생을 시작할 수 있고, 녹화 도중 프로그램이 죽어 moov를 쓰지 못하면 수 GB의 파일이 통째로 재생 불가가 됩니다. 뒤에 나오는 faststart와 fragmented MP4는 모두 이 문제를 서로 다른 방식으로 푸는 방법입니다.
컨테이너 개요
역사 및 개발 배경
ISO BMFF는 QuickTime 파일 포맷의 정리·일반화를 거쳐 MPEG-4 Part 12로 표준화되었으며, 이후 MP4 파일은 주로 14496-14(MPEG-4 파일 포맷)의 프로파일 관점에서 오디오·비디오 트랙 조합을 규정합니다.
주요 연표는 다음과 같습니다.
- 2001: MPEG-4 Systems에 QuickTime 기반 MP4 파일 포맷 포함
- 2003~2004: MP4 파일 포맷(Part 14)과 ISO BMFF(Part 12)로 분리·정리
- 2016: Apple HLS가 MPEG-TS 외에 fMP4 세그먼트 지원
- 2018: CMAF(ISO/IEC 23000-19) 표준화로 HLS·DASH가 같은 fMP4 세그먼트를 공유
기술적 특징
| 항목 | 설명 |
|---|---|
| 구조 | Box/Atom 기반 계층 구조 |
| 코덱 | H.264, HEVC, AV1, AAC, AC3 등 |
| 메타데이터 | moov 박스 내 트랙·타임라인 정보 |
| 스트리밍 | Progressive (faststart), Fragmented (fMP4) |
| 확장자 | .mp4 (비디오), .m4v (비디오), .m4a (오디오) |
내부 구조
Box/Atom 구조
MP4는 Box 단위로 구성됩니다. 각 Box는 4바이트 크기, 4바이트 타입(fourcc), 그리고 나머지 데이터로 이루어집니다.
크기 필드는 헤더를 포함한 박스 전체 길이입니다. 4바이트로는 4GB를 넘는 박스를 표현할 수 없기 때문에, 크기 값이 1이면 타입 뒤에 8바이트 확장 크기(largesize)가 따라오고, 0이면 “파일 끝까지”를 뜻합니다. 긴 녹화 파일의 mdat이 4GB를 넘는 경우가 흔해서, 직접 파서를 만들다 이 규칙을 빠뜨리면 큰 파일에서만 구조를 잘못 읽습니다. 모르는 타입의 박스는 크기만큼 건너뛰면 되므로, 새로운 박스가 추가되어도 오래된 플레이어가 망가지지 않는 확장성이 이 구조에서 나옵니다.
주요 Box의 계층은 다음과 같습니다.
MP4 파일
├─ ftyp (File Type)
│ ├─ major_brand: isom
│ └─ compatible_brands: isom, iso2, mp41
├─ moov (Movie)
│ ├─ mvhd (Movie Header)
│ ├─ trak (Track 1: Video)
│ │ ├─ tkhd (Track Header)
│ │ ├─ mdia (Media)
│ │ │ ├─ mdhd (Media Header)
│ │ │ ├─ hdlr (Handler: vide)
│ │ │ └─ minf (Media Info)
│ │ │ ├─ vmhd (Video Media Header)
│ │ │ ├─ dinf (Data Info)
│ │ │ └─ stbl (Sample Table)
│ │ │ ├─ stsd (Sample Description: avc1)
│ │ │ ├─ stts (Time-to-Sample)
│ │ │ ├─ stsc (Sample-to-Chunk)
│ │ │ ├─ stsz (Sample Size)
│ │ │ └─ stco (Chunk Offset)
│ ├─ trak (Track 2: Audio)
│ │ └─ ... (유사 구조, hdlr: soun, stsd: mp4a)
│ └─ udta (User Data: 메타데이터)
└─ mdat (Media Data: 실제 압축 샘플)
Progressive vs Fragmented
Progressive MP4
[ftyp][moov][mdat]
moov 하나에 전체 타임라인의 샘플 표가 들어가고, mdat 하나에 모든 미디어 데이터가 들어갑니다. faststart는 이 moov를 mdat 앞으로 옮긴 형태입니다.
Fragmented MP4 (fMP4)
[ftyp][moov][moof][mdat][moof][mdat]...
moov에는 코덱 초기화 정보만 들어가고, 각 조각의 샘플 정보는 moof에, 그 조각의 미디어 데이터는 바로 뒤의 mdat에 들어갑니다. 조각 단위로 독립적으로 전송·캐시할 수 있으므로 라이브 스트리밍과 적응형 비트레이트(ABR) 스트리밍에 쓰입니다.
두 방식의 차이는 “목차를 언제 쓰는가”입니다. Progressive MP4는 인코딩이 끝나야 전체 프레임 목록을 알 수 있어서, 인코더는 보통 mdat을 먼저 쓰고 moov를 마지막에 붙입니다. 그래서 faststart 없이 만든 파일은 moov가 끝에 있습니다. Fragmented MP4는 moov에 코덱 초기화 정보만 두고, 몇 초 분량마다 그 구간의 목차(moof)와 데이터(mdat)를 한 쌍으로 씁니다. 목차가 조각마다 있으니 파일이 끝나지 않아도 앞부분을 재생할 수 있고, 녹화 중 중단되어도 마지막 조각 전까지는 살아남습니다. 대신 조각마다 moof가 붙어 파일이 조금 커지고, 오래된 일부 편집 도구나 플레이어는 fMP4의 탐색을 잘 지원하지 않습니다.
실전 사용
기본 명령
1) 구조 확인
# 스트림 정보
ffprobe -hide_banner -show_format -show_streams input.mp4
# Box 구조 (mp4dump - Bento4)
mp4dump input.mp4 | head -50
2) 무손실 리먹스
# MKV → MP4 (코덱 복사)
ffmpeg -i input.mkv -c copy -movflags +faststart output.mp4
# AVI → MP4
ffmpeg -i input.avi -c copy output.mp4
-c copy 리먹스는 재인코딩 없이 몇 초 만에 끝나지만, 원본 코덱이 MP4에 들어갈 수 있을 때만 성공합니다. AVI의 PCM 오디오나 MKV의 ASS 자막처럼 MP4가 담지 못하는 스트림이 있으면 Could not find tag for codec pcm_s16le in stream #1, codec not currently supported in container 같은 오류로 멈춥니다. 이때는 그 스트림만 -c:a aac처럼 재인코딩하고 나머지는 복사하면 됩니다. MKV의 텍스트 자막(SRT)은 -c:s mov_text로 변환해야 MP4에 들어가고, 그래픽 자막(PGS)은 MP4에 넣을 수 없어 번인하거나 별도 파일로 둬야 합니다. 또 오래된 AVI의 DivX/Xvid 비디오는 MP4에 들어가긴 하지만 브라우저가 재생하지 못하므로, 웹용이라면 결국 H.264로 재인코딩해야 합니다.
3) 인코딩 + faststart
# H.264 + AAC (웹 최적화)
ffmpeg -i input.mov \
-c:v libx264 \
-preset medium \
-crf 23 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 192k \
-movflags +faststart \
output.mp4
고급 옵션
Fragmented MP4 생성
# fMP4 (HLS/DASH용)
ffmpeg -i input.mp4 \
-c copy \
-f mp4 \
-movflags frag_keyframe+empty_moov+default_base_moof \
fragmented.mp4
default_base_moof는 조각 안의 데이터 오프셋을 파일 시작이 아니라 해당 moof 기준으로 기록하게 하는 플래그로, 조각을 떼어 내 따로 전송해도 오프셋이 맞도록 해 줍니다.
frag_keyframe는 키프레임마다 조각을 자르므로 조각 길이가 원본의 키프레임 간격을 그대로 따릅니다. -c copy로 만들면 원본 GOP가 10초라면 조각도 10초가 되므로, HLS/DASH용으로 일정한 세그먼트 길이가 필요하다면 인코딩 단계에서 -g나 -force_key_frames로 키프레임 간격을 먼저 맞춰야 합니다. empty_moov는 moov에 샘플 정보를 전혀 넣지 않아 인코딩을 시작하자마자 파일 앞부분을 내보낼 수 있게 하는 옵션으로, 라이브 전송이나 MSE(Media Source Extensions)로 브라우저에 조각을 밀어 넣을 때 필요합니다.
메타데이터 추가
# 기본 태그
ffmpeg -i input.mp4 \
-c copy \
-metadata title="영화 제목" \
-metadata artist="감독" \
-metadata date="2026" \
-metadata comment="설명" \
tagged.mp4
다중 오디오 트랙
# 비디오 + 다중 오디오
ffmpeg -i video.mp4 \
-i audio_kor.wav \
-i audio_eng.wav \
-map 0:v:0 \
-map 1:a:0 \
-map 2:a:0 \
-c:v copy \
-c:a aac -b:a 192k \
-metadata:s:a:0 language=kor -metadata:s:a:0 title="한국어" \
-metadata:s:a:1 language=eng -metadata:s:a:1 title="English" \
-disposition:a:0 default \
multi_audio.mp4
HLS 세그먼트 생성
# HLS fMP4 세그먼트
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename segment_%03d.m4s \
playlist.m3u8
성능 비교
컨테이너 오버헤드
MP4·MKV·WebM 모두 박스나 요소 헤더에 드는 바이트가 미디어 데이터에 비하면 아주 작아서, 일반적인 영상에서 컨테이너 선택이 파일 크기를 눈에 띄게 바꾸지는 않습니다. 다만 moov의 크기는 예외일 수 있습니다. moov 안의 샘플 표는 프레임 수에 비례해 커지므로, 몇 시간짜리 영상이면 수 MB가 되기도 합니다. faststart 파일에서는 플레이어가 재생 전에 이 moov를 전부 받아야 하므로, 아주 긴 VOD에서 첫 재생이 늦다면 moov 크기를 확인해 보고 세그먼트 방식으로 바꾸는 것을 검토할 만합니다.
스트리밍 비교
| 프로토콜 | Progressive MP4 | Fragmented MP4 |
|---|---|---|
| HTTP 단일 파일 | 우수 (faststart) | 가능 |
| HLS | 가능 (TS 대체) | 표준 (fMP4) |
| DASH | 가능 | 표준 (fMP4) |
| 라이브 | 부적합 | 적합 |
실무 활용 사례
사례 1: YouTube 업로드 최적화
YouTube는 업로드된 영상을 반드시 다시 인코딩하므로, 업로드 파일은 크기보다 화질 손실을 줄이는 쪽으로 만드는 것이 유리합니다.
# YouTube 권장 설정
ffmpeg -i input.mov \
-c:v libx264 \
-preset slow \
-crf 18 \
-pix_fmt yuv420p \
-profile:v high \
-level 4.2 \
-c:a aac \
-b:a 192k \
-ar 48000 \
-movflags +faststart \
youtube_upload.mp4
-crf 18은 시각적으로 원본과 거의 구분되지 않는 수준의 품질로, 두 번째 손실 압축(YouTube 재인코딩)에서 열화가 누적되는 것을 줄이기 위한 값입니다. 대신 파일이 커지므로 업로드 시간은 늘어납니다. -preset slow는 같은 품질에서 파일 크기를 줄이기 위해 인코딩 시간을 더 쓰는 설정이고, High 프로파일·레벨 4.2는 1080p60까지 담을 수 있는 조합입니다.
사례 2: 웹 비디오 플레이어
단일 파일 플레이어에서 화질 선택 메뉴를 제공하려면 해상도별 파일을 따로 만듭니다. 높이만 지정하고 너비는 -2로 두면 원본 비율을 유지하면서 짝수 너비로 맞춰집니다.
다중 품질 생성
# 1080p
ffmpeg -i input.mp4 \
-vf "scale=-2:1080" \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 192k \
-movflags +faststart \
1080p.mp4
# 720p
ffmpeg -i input.mp4 \
-vf "scale=-2:720" \
-c:v libx264 -preset medium -crf 24 \
-c:a aac -b:a 128k \
-movflags +faststart \
720p.mp4
# 480p
ffmpeg -i input.mp4 \
-vf "scale=-2:480" \
-c:v libx264 -preset medium -crf 26 \
-c:a aac -b:a 96k \
-movflags +faststart \
480p.mp4
Python 자동화
import subprocess
from pathlib import Path
def create_multi_quality(input_file, output_dir):
"""
다중 품질 MP4 생성
"""
qualities = [
('1080p', '-2:1080', 23, '192k'),
('720p', '-2:720', 24, '128k'),
('480p', '-2:480', 26, '96k')
]
Path(output_dir).mkdir(parents=True, exist_ok=True)
stem = Path(input_file).stem
for name, scale, crf, audio_br in qualities:
output_file = Path(output_dir) / f"{stem}_{name}.mp4"
cmd = [
'ffmpeg', '-y',
'-i', input_file,
'-vf', f'scale={scale}',
'-c:v', 'libx264',
'-preset', 'medium',
'-crf', str(crf),
'-c:a', 'aac',
'-b:a', audio_br,
'-movflags', '+faststart',
str(output_file)
]
print(f"Creating {name}...")
subprocess.run(cmd, check=True)
# 사용
create_multi_quality('master.mov', 'output')
사례 3: HLS 적응형 스트리밍
플레이어가 네트워크 상황에 따라 화질을 바꾸는 적응형 스트리밍은 여러 비트레이트의 세그먼트와 이를 묶는 마스터 플레이리스트가 필요합니다.
HLS 생성
# 마스터 플레이리스트 + 다중 variant
ffmpeg -i input.mp4 \
-filter_complex \
"[0:v]split=3[v1][v2][v3]; \
[v1]scale=-2:1080[v1out]; \
[v2]scale=-2:720[v2out]; \
[v3]scale=-2:480[v3out]" \
-map "[v1out]" -c:v:0 libx264 -b:v:0 5000k \
-map "[v2out]" -c:v:1 libx264 -b:v:1 2800k \
-map "[v3out]" -c:v:2 libx264 -b:v:2 1400k \
-force_key_frames "expr:gte(t,n_forced*6)" \
-map 0:a -map 0:a -map 0:a -c:a aac -b:a 128k \
-f hls \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_type fmp4 \
-hls_fmp4_init_filename init.mp4 \
-hls_segment_filename "stream_%v/segment_%03d.m4s" \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
stream_%v/playlist.m3u8
-var_stream_map에서 같은 오디오 스트림을 여러 variant가 함께 참조하면 FFmpeg가 오류를 내므로, 오디오를 variant 수만큼 -map 0:a로 반복해 각 variant에 하나씩 붙입니다. -force_key_frames는 6초마다 키프레임을 강제해, 모든 화질의 세그먼트 경계가 같은 시점에 맞도록 합니다. 경계가 어긋나면 플레이어가 화질을 바꿀 때 끊김이 생길 수 있습니다. 실행 결과는 다음과 같습니다.
.
├─ master.m3u8
├─ stream_0/
│ ├─ playlist.m3u8
│ ├─ init.mp4
│ ├─ segment_000.m4s
│ └─ segment_001.m4s
├─ stream_1/
│ └─ ...
└─ stream_2/
└─ ...
사례 4: 모바일 앱 - 오프라인 다운로드
오프라인 저장용 파일은 기기 저장 공간을 아끼면서도 하드웨어 디코더로 재생되도록 만드는 것이 목표입니다.
# 모바일 최적화
ffmpeg -i input.mp4 \
-vf "scale=-2:720" \
-c:v libx264 \
-preset fast \
-crf 26 \
-profile:v main \
-level 3.1 \
-c:a aac \
-b:a 96k \
-movflags +faststart \
mobile.mp4
Main 프로파일은 대부분의 모바일 하드웨어 디코더가 지원하고, 레벨 3.1은 720p30까지를 담는 레벨이라 오래된 기기에서도 재생됩니다. CRF 26은 화질을 조금 양보하고 파일 크기를 줄이는 선택이며, 하드웨어 디코딩이 되면 소프트웨어 디코딩보다 배터리 소모도 적습니다.
최적화 팁
프로그레시브 재생 (faststart)
# 인코딩 시 적용
ffmpeg -i input.avi \
-c:v libx264 -c:a aac \
-movflags +faststart \
output.mp4
# 기존 파일에 적용
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
moov가 파일 앞으로 오므로 플레이어가 파일 앞부분만 받고 바로 재생을 시작할 수 있습니다.
faststart는 공짜가 아닙니다. FFmpeg는 먼저 moov를 끝에 둔 파일을 다 쓴 뒤, 두 번째 패스로 파일 전체를 다시 쓰면서 moov를 앞으로 옮깁니다. 그래서 출력 파일 크기만큼의 추가 디스크 I/O가 들고, 인코딩 로그 마지막에 Starting second pass: moving the moov atom to the beginning of the file 메시지가 찍힌 뒤 한동안 멈춘 것처럼 보입니다. 이미 만들어진 파일에도 -c copy -movflags +faststart로 적용할 수 있으니, 대량 인코딩 파이프라인에서는 이 단계의 시간과 임시 공간을 계산에 넣어야 합니다. 또 moov 앞 이동은 저장이 끝난 파일에만 의미가 있고, 파이프(-f mp4 pipe:1)로 내보내는 출력에는 적용할 수 없어서 그런 경우에는 fMP4를 써야 합니다.
파일 크기 최소화
# 불필요한 트랙 제거
ffmpeg -i input.mp4 \
-map 0:v:0 \
-map 0:a:0 \
-c copy \
slim.mp4
# 메타데이터 제거
ffmpeg -i input.mp4 \
-c copy \
-map_metadata -1 \
no_metadata.mp4
호환성 개선
# 최대 호환 설정
ffmpeg -i input.mp4 \
-c:v libx264 \
-preset medium \
-crf 23 \
-vf "scale=-2:480" \
-profile:v baseline \
-level 3.0 \
-pix_fmt yuv420p \
-c:a aac \
-b:a 128k \
-ar 44100 \
-movflags +faststart \
compatible.mp4
레벨 3.0이 허용하는 최대 프레임 크기는 720×576 정도이므로, 720p나 1080p 원본을 그대로 두고 -level 3.0만 지정하면 레벨 표기와 실제 스트림이 맞지 않는 파일이 됩니다. 그래서 위 명령은 480p로 줄여 함께 인코딩합니다.
트러블슈팅
문제 1: 웹에서 재생 안 됨
<video> 태그로 넣은 MP4가 재생되지 않는 경우입니다.
<video src="video.mp4" controls></video>
<!-- 재생 안 됨 -->
첫 번째 원인은 moov 위치입니다.
# faststart 적용
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
두 번째 원인은 브라우저가 지원하지 않는 코덱입니다.
# 코덱 확인
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name input.mp4
# HEVC → H.264 변환
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a copy \
-movflags +faststart \
output.mp4
원인 1과 2를 구분하는 가장 빠른 방법은 브라우저 개발자 도구의 네트워크 탭입니다. moov가 끝에 있는 파일은 브라우저가 파일 앞부분을 받은 뒤 끝부분을 따로 요청하는 Range 요청이 보이고, 서버가 Range 요청을 지원하지 않으면 전체를 받을 때까지 기다리거나 재생에 실패합니다. 코덱 문제라면 콘솔에 MEDIA_ERR_SRC_NOT_SUPPORTED 계열 오류가 나거나, 소리만 나오고 화면이 검게 나옵니다. HEVC는 브라우저와 운영체제, 하드웨어 조합에 따라 지원 여부가 달라서 “내 PC에서는 되는데”가 가장 자주 나오는 코덱입니다.
문제 2: 재생 시작 느림
다운로드가 끝나야 재생이 시작된다면 moov가 파일 끝에 있는 것입니다. faststart로 옮기면 됩니다.
# faststart 적용
ffmpeg -i slow.mp4 -c copy -movflags +faststart fast.mp4
적용 여부는 박스 순서로 확인합니다.
# moov 위치 확인 (mp4dump)
mp4dump slow.mp4 | grep -A 5 "moov"
Bento4가 없다면 FFmpeg만으로도 확인할 수 있습니다. ffprobe -v trace slow.mp4 2>&1 | grep -E "type:'(moov|mdat)'"를 실행하면 박스가 파일에 등장하는 순서대로 출력되므로, moov가 mdat보다 먼저 나오면 faststart가 적용된 파일입니다.
문제 2-1: moov atom not found
녹화나 인코딩이 중간에 끊긴 파일을 열면 moov atom not found와 함께 Invalid data found when processing input 오류가 납니다. 앞에서 설명한 대로 Progressive MP4는 moov를 마지막에 쓰기 때문에, 프로세스가 강제 종료되거나 디스크가 가득 차면 목차 없는 mdat만 남습니다. -c copy로 다시 먹싱해도 moov가 없어서 해결되지 않고, 같은 장비·설정으로 녹화한 정상 파일을 참조로 삼아 목차를 재구성하는 복구 도구(untrunc 등)를 써야 할 수도 있습니다. 예방이 훨씬 쉬우므로, 긴 녹화는 처음부터 fMP4(-movflags +frag_keyframe+empty_moov)나 MKV로 기록하고 끝난 뒤 필요하면 일반 MP4로 리먹스하는 방식을 권합니다. OBS Studio가 일반 MP4 대신 MKV나 중단에 강한 MP4 방식으로 녹화하라고 안내하는 이유도 이것입니다.
문제 3: 자막 동기화 문제
자막이 영상보다 일정하게 앞서거나 뒤처진다면 SRT 파일의 타임스탬프 자체가 어긋난 경우가 대부분입니다. 단순히 MP4에 넣는 것만으로는 고쳐지지 않으므로, 자막 입력 앞에 -itsoffset을 붙여 타임스탬프를 옮긴 뒤 mov_text로 넣습니다(아래는 자막을 0.5초 늦추는 예).
# 자막을 0.5초 뒤로 밀어서 넣기
ffmpeg -i video.mp4 -itsoffset 0.5 -i subtitle.srt \
-map 0:v -map 0:a -map 1:s \
-c:v copy -c:a copy \
-c:s mov_text \
-metadata:s:s:0 language=kor \
synced.mp4
처음에는 맞다가 갈수록 벌어진다면 오프셋이 아니라 프레임레이트 차이(예: 23.976fps 영상에 25fps 기준으로 만든 자막)이므로, Subtitle Edit 같은 자막 도구에서 속도 비율을 바꿔 맞춰야 합니다.
문제 4: 모바일 재생 안 됨
특정 기기에서만 재생되지 않는다면 컨테이너가 아니라 H.264 프로파일·레벨이나 픽셀 포맷을 기기의 하드웨어 디코더가 지원하지 않는 경우가 많습니다. 가장 보수적인 설정은 다음과 같습니다.
# Baseline Profile (최대 호환)
ffmpeg -i input.mp4 \
-c:v libx264 \
-preset medium \
-crf 23 \
-vf "scale=-2:480" \
-profile:v baseline \
-level 3.0 \
-pix_fmt yuv420p \
-c:a aac -b:a 128k \
-movflags +faststart \
mobile_compatible.mp4
다음 단계
- MKV 비교: MKV 실전 가이드
- WebM 비교: WebM 웹 표준
- H.264 코덱: H.264 코덱
참고 자료
- ISO BMFF: ISO/IEC 14496-12
- MP4: ISO/IEC 14496-14
- CMAF: ISO/IEC 23000-19
- FFmpeg: https://ffmpeg.org/ffmpeg-formats.html#mov_002c-mp4_002c-ismv
같이 보면 좋은 글
- 영상 스트리밍 프로토콜 | RTMP·RTSP·HLS·DASH·CMAF 비교
- Inside the MP4 Container: ftyp/moov/mdat Boxes, faststart and Fragmented MP4 for Streaming
- MKV(Matroska) 컨테이너 실전 활용 | EBML·다중 자막·FFmpeg 리먹스