Practical Engineering with ffmpeg
ffmpeg 실전 — 컨테이너·스트리밍·하드웨어
코덱 이론을 손에 잡힌 도구로. 컨테이너 vs 코덱·CRF·하드웨어 인코더·ABR 스트리밍의 실전 매뉴얼.
Overview
이 코스의 마지막 장. 1~9장의 이론을 실전 도구로 묶는다. ffmpeg가 비디오 엔지니어의 스위스 아미 나이프. 이번 장은 ffmpeg의 주요 사용 패턴 — 트랜스코딩, 컨테이너 변환, 하드웨어 가속, ABR 스트리밍 — 을 매뉴얼처럼 다룬다.
이번 장의 명령들을 외우면 비디오 관련 일상 업무의 90%를 ffmpeg 한 줄로 해결할 수 있다.
- 컨테이너와 코덱을 정확히 구분하고 도메인별 선택 가능
- ffmpeg의 핵심 명령 5-7개를 외운다
- 하드웨어 인코더의 trade-off를 안다 (NVENC·VideoToolbox·QSV)
- HLS·DASH 스트리밍을 ffmpeg로 만들 수 있다
- Two-pass·multi-bitrate 같은 실전 파이프라인을 이해한다
Sections
10.1 컨테이너 vs 코덱 — 한 번 더 정리
비디오 파일은 두 층으로 구성된다.
컨테이너 (Container): 비디오·오디오·자막·메타데이터를 한 파일로 묶는 봉투. 코덱이 아니라 포맷.
- .mp4 (ISO BMFF) — 가장 호환성 좋음. H.264·H.265·AV1·AAC 모두 가능
- .mkv (Matroska) — 거의 모든 코덱 가능. 자막 다국어 잘 다룸
- .webm (서브셋 of Matroska) — VP9·AV1 + Opus 위주. 웹 친화
- .mov (QuickTime) — Apple. ProRes·H.264·H.265
- .ts (MPEG-TS) — 방송·HLS. 스트리밍에 적합
비디오 코덱 (Video Codec): 영상 자체의 압축 방식. H.264·H.265·AV1·VP9·ProRes 등.
오디오 코덱 (Audio Codec): 음향의 압축. AAC·Opus·MP3·FLAC.
한 .mp4 안에 H.264 + AAC, 또는 H.265 + AAC, 또는 AV1 + Opus 모두 가능. 파일 확장자만 보고 코덱을 추측하면 안 된다. ffprobe로 확인:
``bash
ffprobe -v error -show_streams video.mp4 | grep codec_name
# codec_name=h264
# codec_name=aac
``
엔지니어가 가장 자주 만지는 컨테이너는 MP4(호환성) 와 TS(스트리밍). 도메인별로 외우면 90% 커버.
10.2 ffmpeg 핵심 명령 — 외워두면 인생 편해짐
ffmpeg의 일상 명령. 외워두면 매일 쓴다.
```bash # 1) 정보 확인 ffprobe -v error -show_format -show_streams input.mp4
# 2) H.264 기본 트랜스코딩 (가장 자주 씀) ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -c:a aac out.mp4
# 3) H.265로 (절반 크기) ffmpeg -i input.mp4 -c:v libx265 -crf 28 -preset medium -c:a aac out.mp4
# 4) 다시 인코딩 없이 컨테이너만 변경 (빠르고 무손실) ffmpeg -i input.mkv -c copy out.mp4
# 5) 해상도 변경 (1080p → 720p) ffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -c:a copy out.mp4
# 6) 일부 잘라내기 (5초~25초) ffmpeg -ss 5 -t 20 -i input.mp4 -c copy clip.mp4
# 7) 키프레임 간격 강제 (스트리밍 준비) ffmpeg -i input.mp4 -c:v libx264 -g 60 -keyint_min 60 -sc_threshold 0 \ -force_key_frames 'expr:gte(t,n_forced*2)' out.mp4
# 8) 오디오만 추출 ffmpeg -i input.mp4 -vn -c:a copy audio.m4a
# 9) GIF로 변환 (작은 클립) ffmpeg -i input.mp4 -vf 'fps=15,scale=480:-1:flags=lanczos' -c:v gif out.gif ```
이 9개가 실무 명령의 80%. 나머지는 자주 안 씀.
10.3 하드웨어 인코더 — NVENC·VideoToolbox·QSV
ffmpeg는 하드웨어 가속 인코더도 지원. CPU 대신 전용 칩(GPU·ASIC)으로 인코딩 → 10~50배 빠름. 단, 화질은 SW 인코더(x264·x265) 보다 약간 떨어짐.
NVIDIA NVENC (RTX/GTX GPU):
``bash
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p4 -cq 23 -c:a aac out.mp4
ffmpeg -i input.mp4 -c:v hevc_nvenc -preset p4 -cq 28 -c:a aac out.mp4
``
Apple VideoToolbox (Mac):
``bash
ffmpeg -i input.mp4 -c:v h264_videotoolbox -b:v 4M -c:a aac out.mp4
ffmpeg -i input.mp4 -c:v hevc_videotoolbox -b:v 2M -c:a aac out.mp4
``
Intel Quick Sync (QSV) (Intel CPU 내장):
``bash
ffmpeg -i input.mp4 -c:v h264_qsv -global_quality 23 -c:a aac out.mp4
``
언제 HW vs SW: - 라이브 스트리밍 (1080p@60 이상) → HW 필수. 5-10% 화질 손해 감수. - 실시간 화상회의 → HW 필수. 저전력 + 짧은 지연. - VOD 라이브러리 (한 번 인코딩 → 영원히 재생) → SW가 정답. 화질 최우선. - 모바일 녹화 → 디바이스 HW가 알아서 (대부분 HEVC HW).
10.4 ABR 스트리밍 — HLS·DASH 만들기
스트리밍 서비스는 여러 비트레이트를 동시에 만들어 클라이언트가 네트워크 상태에 따라 자동 전환 → ABR(Adaptive Bit Rate).
ffmpeg로 HLS 생성:
``bash
ffmpeg -i input.mp4 \
-filter_complex '[0:v]split=3[v1][v2][v3]; \
[v1]scale=w=1280:h=720[v1out]; \
[v2]scale=w=854:h=480[v2out]; \
[v3]scale=w=640:h=360[v3out]' \
-map '[v1out]' -c:v:0 libx264 -b:v:0 3M -maxrate:0 3.5M -bufsize:0 6M \
-map '[v2out]' -c:v:1 libx264 -b:v:1 1.5M -maxrate:1 1.8M -bufsize:1 3M \
-map '[v3out]' -c:v:2 libx264 -b:v:2 800k -maxrate:2 1M -bufsize:2 1.6M \
-map a:0 -c:a aac -b:a 128k \
-g 60 -keyint_min 60 -sc_threshold 0 \
-f hls -hls_time 2 -hls_list_size 0 \
-hls_segment_filename 'stream_%v/seg_%03d.ts' \
-master_pl_name master.m3u8 \
-var_stream_map 'v:0,a:0 v:1,a:0 v:2,a:0' \
'stream_%v/playlist.m3u8'
``
결과:
``
master.m3u8 ← 메타: 3개 품질 선택지
stream_0/... ← 720p 3Mbps 세그먼트
stream_1/... ← 480p 1.5Mbps
stream_2/... ← 360p 800kbps
``
클라이언트(브라우저 HLS.js, iOS Safari, Android ExoPlayer)가 master.m3u8을 읽고 네트워크 상태에 따라 세 품질을 동적 전환.
핵심 옵션 정리:
- -g 60 -keyint_min 60 -sc_threshold 0: 키프레임 일정 간격 (세그먼트 경계 일치)
- -hls_time 2: 세그먼트 길이 2초
- -maxrate -bufsize: VBR 캡 — 네트워크 안정성
10.5 Two-pass와 트랜스코딩 파이프라인
Two-pass 인코딩: 1회차에 전체 영상을 분석해 비트레이트 분포를 미리 정한 다음, 2회차에 그 분포대로 인코딩. 목표 평균 비트레이트를 정확히 맞춰야 할 때 사용.
``bash
# pass 1
ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2M -pass 1 -an -f null /dev/null
# pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -pass 2 -c:a aac out.mp4
``
사용처: 정확한 파일 크기·비트레이트 목표가 있는 다운로드용. CRF로는 파일 크기를 미리 모르는 게 단점인데 two-pass가 해결.
전형적인 프로덕션 트랜스코딩 파이프라인: 1. Ingest: 원본 영상 받음 (보통 ProRes·H.264 고비트레이트) 2. Mezzanine 생성: 후반작업·QC용 중간 인코딩 (H.264 high bitrate) 3. ABR ladder 생성: 360p~4K 5~7단계 비트레이트로 인코딩 (코덱별·해상도별) 4. Segmenting: HLS/DASH로 분할 5. CDN 업로드: 세그먼트를 CDN에 푸시 6. 모니터링: VMAF·viewer drop rate 추적
Netflix·YouTube 같은 회사는 이 파이프라인을 수십 개 코덱 × 수십 개 비트레이트 × 수십 개 해상도 조합으로 운영. 한 영상이 200~300번 인코딩됨.
ffmpeg는 이 파이프라인의 핵심 도구. 직접 운영하든 매니지드 서비스(AWS Elemental·Mux·Cloudflare Stream)를 쓰든 내부는 ffmpeg.
공항에서 화물을 처리하는 흐름을 보자. (1) 짐 도착 (ingest), (2) X-ray 검사 (QC), (3) 항공기·차량·기차 각각 다른 포장 (multi-bitrate), (4) 컨테이너에 적재 (segmenting), (5) 운송 (CDN 업로드), (6) 도착지에서 모니터링.
각 단계가 비디오 트랜스코딩 파이프라인과 정확히 매핑된다. 그리고 컨테이너의 의미도 분명해진다 — 한 화물(영상)을 어떤 봉투(MP4·TS)에 담을지는 도착지(클라이언트)에 따라 다르다.
하드웨어 인코더는 "전용 화물 처리기". CPU(범용 직원)가 한 박스 포장하는 데 10분이면, 전용 기계는 30초. 화질이 살짝 떨어지지만 처리량 폭증.
ABR 스트리밍은 "고객 차량에 따라 다른 크기 박스 자동 선택". 작은 차에는 작은 박스(360p), 큰 트럭에는 큰 박스(4K). 클라이언트가 자기 능력을 보고 자동 선택.
Python으로 ffmpeg를 wrapping해서 ABR ladder를 자동 생성하는 미니 파이프라인. 실제 프로덕션에서 자주 쓰는 패턴.
import subprocess
from pathlib import Path
def generate_abr_ladder(input_path, output_dir, codec='h264'):
"""Generate HLS multi-bitrate ladder."""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# ABR ladder: (height, bitrate Kbps)
ladder = [
(360, 800),
(480, 1500),
(720, 3000),
(1080, 5000),
]
lib = {'h264': 'libx264', 'h265': 'libx265', 'av1': 'libsvtav1'}[codec]
# Build the filter complex
split = f'[0:v]split={len(ladder)}'
splits = ''.join(f'[v{i}]' for i in range(len(ladder)))
scales = ';'.join(
f'[v{i}]scale=-2:{h}[v{i}out]'
for i, (h, _) in enumerate(ladder)
)
filter_complex = f'{split}{splits}; {scales}'
cmd = ['ffmpeg', '-y', '-i', str(input_path),
'-filter_complex', filter_complex]
for i, (h, b) in enumerate(ladder):
cmd += ['-map', f'[v{i}out]',
f'-c:v:{i}', lib,
f'-b:v:{i}', f'{b}k',
f'-maxrate:{i}', f'{int(b*1.2)}k',
f'-bufsize:{i}', f'{b*2}k']
cmd += ['-map', 'a:0', '-c:a', 'aac', '-b:a', '128k',
'-g', '60', '-keyint_min', '60', '-sc_threshold', '0',
'-f', 'hls', '-hls_time', '2', '-hls_list_size', '0',
'-hls_segment_filename', f'{output_dir}/v%v/s%03d.ts',
'-master_pl_name', 'master.m3u8',
'-var_stream_map',
' '.join(f'v:{i},a:0' for i in range(len(ladder))),
f'{output_dir}/v%v/playlist.m3u8']
print('Running:', ' '.join(cmd[:8]), '...')
subprocess.run(cmd, check=True)
print(f'✓ ABR ladder ready at {output_dir}/master.m3u8')
# Usage
generate_abr_ladder('input.mp4', 'hls_out', codec='h264')
# Output:
# hls_out/
# master.m3u8 ← 클라이언트 진입점
# v0/playlist.m3u8 + s001.ts ... (360p 800k)
# v1/playlist.m3u8 + s001.ts ... (480p 1.5M)
# v2/playlist.m3u8 + s001.ts ... (720p 3M)
# v3/playlist.m3u8 + s001.ts ... (1080p 5M)
이 함수가 프로덕션 ABR 파이프라인의 미니어처. 입력 영상 하나에서 4단계 비트레이트의 HLS 세트를 생성. 클라이언트(웹·iOS·Android)가 master.m3u8을 받아 네트워크 상태에 따라 자동으로 품질 전환. 실제 대형 서비스는 이 ladder를 7~10단계로 늘리고, H.264·H.265·AV1 세 코덱 다 만들어서 디바이스별 fallback까지 처리한다.
✅ 시니어가 보는 것
- 컨테이너·코덱 구분과 ffprobe로 검증 가능
- ffmpeg 일상 명령 5-7개 외우고 있음
- HW vs SW 인코더의 trade-off 도메인별로 판단
- HLS/DASH ABR ladder 설계 가능
- Two-pass·single-pass 선택 기준 안다
⚠️ 레드 플래그
- MP4가 코덱이라고 답함
- 라이브에 x265 preset slow 쓰려고 함
- 키프레임 간격을 세그먼트 길이와 안 맞춰 HLS 깨뜨림
- ABR ladder를 7-10단계가 아니라 1-2단계만 만듦
🎤 예상 인터뷰 질문
- HLS와 DASH의 차이와 사용 사례를 설명해 주세요
- NVENC vs x264의 trade-off는?
- ABR ladder 설계 시 고려할 요소를 5가지 말해 주세요
Key Takeaways
컨테이너 ≠ 코덱
MP4 안에 H.264·H.265·AV1 다 가능.
ffprobe로 확인
확장자만 보고 추측 X.
9개 명령 외우기
일상 업무의 80%를 커버.
Stream copy
-c copy로 무인코딩 컨테이너 변환.
HW 인코더
10-50배 빠름, 화질 약간 손해. 라이브 필수.
ABR ladder
여러 비트레이트 동시 생성. ABR 스트리밍의 기본.
HLS = 2초 세그먼트 + 일정 키프레임
둘 안 맞추면 깨짐.
Two-pass
정확한 비트레이트 목표 시. CRF는 크기 예측 불가.