FFmpeg 실전 가이드 | 동영상/오디오 처리
이 글의 핵심
FFmpeg 실전 가이드에 대해 정리한 개발 블로그 글입니다. > TL;DR: FFmpeg로 동영상/오디오를 자유자재로 다루는 방법을 배웁니다. 설치부터 변환, 인코딩, 스트리밍까지 실무에서 바로 쓸 수 있는 모든 것을 마스터합니다. 이 글을 읽으면: - ✅ FFmpeg 설치부터 기본… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드:…
🎯 이 글을 읽으면 (읽는 시간: 30분)
TL;DR: FFmpeg로 동영상/오디오를 자유자재로 다루는 방법을 배웁니다. 설치부터 변환, 인코딩, 스트리밍까지 실무에서 바로 쓸 수 있는 모든 것을 마스터합니다. 이 글을 읽으면:
- ✅ FFmpeg 설치부터 기본 사용법까지 완벽 이해
- ✅ 동영상 변환, 인코딩, 최적화 기법 실전 적용
- ✅ HLS 스트리밍과 라이브 방송 구축 능력 습득
- ✅ Python/Node.js에서 FFmpeg 프로그래밍 방식 활용 실무 활용:
- 🔥 동영상 플랫폼 (YouTube, Netflix 스타일)
- 🔥 라이브 스트리밍 서비스
- 🔥 썸네일 자동 생성 시스템
- 🔥 동영상 인코딩 파이프라인 난이도: 중급 | 실습 예제: 30개 | 프로덕션 레벨
이 글의 핵심
FFmpeg는 동영상과 오디오를 다루는 가장 강력한 오픈소스 도구입니다. 이 가이드는 설치부터 실전 활용까지, 실무에서 바로 써먹을 수 있는 패턴과 노하우를 담았습니다. 단순한 명령어 나열이 아닌, 왜 이렇게 쓰는지, 어떤 상황에서 쓰는지를 중심으로 정리했습니다.
사전 지식 (초보자를 위한 기초)
1. 동영상 파일 구조
동영상 파일을 이해하려면 컨테이너와 코덱의 차이를 알아야 합니다. 많은 사람들이 이 둘을 혼동하는데, 실제로는 완전히 다른 개념입니다.
동영상 파일 구조:
┌─────────────────────────────────┐
│ 컨테이너 (Container) │
│ 예: MP4, MKV, AVI, MOV │
│ │
│ ┌──────────────────────────┐ │
│ │ 비디오 스트림 │ │
│ │ 코덱: H.264, H.265 │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ 오디오 스트림 │ │
│ │ 코덱: AAC, MP3 │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ 자막 스트림 (선택) │ │
│ │ SRT, ASS │ │
│ └──────────────────────────┘ │
└─────────────────────────────────┘
컨테이너 (Container):
- 비디오, 오디오, 자막을 담는 “상자” 역할
- 예: MP4, MKV, AVI, MOV, WebM
- 같은 코덱이라도 컨테이너에 따라 호환성이 달라집니다
- MP4는 가장 범용적이고, MKV는 기능이 많지만 일부 기기에서 재생 안 될 수 있습니다 코덱 (Codec):
- 데이터를 압축/해제하는 “알고리즘”
- 비디오: H.264 (가장 보편적), H.265 (고효율), VP9 (웹용), AV1 (차세대)
- 오디오: AAC (범용), MP3 (레거시), Opus (고효율), FLAC (무손실)
- 같은 화질이라도 코덱에 따라 파일 크기가 2배 이상 차이날 수 있습니다 비유로 이해하기:
- 컨테이너 = 택배 상자
- 코덱 = 물건을 압축하는 방법
- 같은 물건(영상)이라도 어떻게 압축하고(코덱) 어떤 상자에 담느냐(컨테이너)에 따라 결과가 달라집니다
2. 비트레이트와 해상도
비트레이트 (Bitrate): 초당 전송되는 데이터 양입니다. 단위는 kbps(킬로비트), Mbps(메가비트)를 사용합니다.
- 높은 비트레이트: 화질은 좋지만 파일 크기가 크고 스트리밍 시 버퍼링 발생 가능
- 낮은 비트레이트: 파일은 작지만 화질 저하, 특히 빠른 움직임에서 블록 노이즈 발생
- 적정 비트레이트: 해상도와 프레임레이트, 콘텐츠 특성에 따라 결정됩니다
비디오 비트레이트 가이드:
4K (3840×2160): 20-50 Mbps
1080p (1920×1080): 5-10 Mbps
720p (1280×720): 2.5-5 Mbps
480p (854×480): 1-2 Mbps
360p (640×360): 0.5-1 Mbps
오디오 비트레이트:
320 kbps: 고음질 (음악)
192 kbps: 표준 (음악)
128 kbps: 일반 (팟캐스트)
64 kbps: 저음질 (통화)
해상도 (Resolution): 가로×세로 픽셀 수입니다.
- 4K (3840×2160): 영화관 수준, 파일 크기 매우 큼
- 1080p (1920×1080): Full HD, 가장 보편적인 고화질
- 720p (1280×720): HD, 모바일이나 웹 스트리밍에 적합
- 480p (854×480): SD, 저사양 기기나 느린 네트워크용 실전 팁: 같은 비트레이트라도 해상도가 높으면 화질이 떨어집니다. 4K를 5Mbps로 인코딩하는 것보다 1080p를 5Mbps로 인코딩하는 게 훨씬 깨끗합니다.
3. 프레임레이트
프레임레이트 (Frame Rate, FPS): 초당 몇 장의 화면을 보여주는지를 나타냅니다. 단위는 fps(frames per second)입니다. 프레임레이트가 높을수록 움직임이 부드럽지만, 파일 크기도 비례해서 커집니다. 콘텐츠 특성에 맞는 선택이 중요합니다.
일반적인 프레임레이트:
24 fps: 영화 (시네마틱 느낌, 영화관 표준)
30 fps: TV, 유튜브 (일반 영상, 가장 보편적)
60 fps: 게임, 스포츠 (부드러운 움직임, 액션 장면)
120 fps: 슬로우 모션 촬영용 (편집 시 24fps나 30fps로 느리게 재생)
실전 선택 가이드:
- 브이로그, 강의: 30fps (파일 크기와 품질 균형)
- 게임 플레이, 스포츠: 60fps (빠른 움직임 표현)
- 영화 느낌: 24fps (시네마틱 무드)
- 슬로우 모션 필요: 120fps 이상 촬영 후 편집
4. 코덱 선택 가이드
코덱 선택은 호환성, 파일 크기, 인코딩 속도, 화질의 균형을 맞추는 작업입니다. 상황별로 최적의 선택이 다릅니다.
비디오 코덱 비교:
H.264 (AVC) - 가장 안전한 선택
✅ 장점: 거의 모든 기기 지원, 빠른 인코딩, 하드웨어 가속 지원
❌ 단점: H.265 대비 파일 크기 큼
📌 추천: 일반 용도, 최대 호환성 필요, 빠른 처리 필요
H.265 (HEVC) - 용량 최적화
✅ 장점: H.264 대비 40-50% 작은 파일, 4K에 적합
❌ 단점: 느린 인코딩, 구형 기기 미지원, 라이선스 비용
📌 추천: 4K 영상, 저장 공간 부족, 최신 기기 타겟
VP9 - 웹 스트리밍용
✅ 장점: 로열티 프리, YouTube 지원, 웹 브라우저 호환
❌ 단점: 매우 느린 인코딩, 하드웨어 지원 제한적
📌 추천: YouTube 업로드, 웹 전용 콘텐츠
AV1 - 미래 대비
✅ 장점: 최고의 압축 효율, 로열티 프리
❌ 단점: 극도로 느린 인코딩, 제한적 재생 지원
📌 추천: 장기 보관용, 실험적 프로젝트
오디오 코덱 비교:
AAC - 범용 표준
✅ 장점: 우수한 음질/용량 비율, 광범위한 지원
❌ 단점: MP3보다 약간 느린 인코딩
📌 추천: 대부분의 경우 (기본 선택)
MP3 - 레거시 호환
✅ 장점: 100% 호환성, 빠른 인코딩
❌ 단점: AAC 대비 큰 파일 또는 낮은 음질
📌 추천: 구형 기기 지원 필수, 팟캐스트
Opus - 고효율
✅ 장점: 최고의 음질/용량 비율, 로열티 프리
❌ 단점: 제한적 기기 지원
📌 추천: 웹 스트리밍, 음성 통화, Discord
FLAC - 무손실
✅ 장점: 완벽한 음질 보존, 압축 지원
❌ 단점: 큰 파일 크기
📌 추천: 음악 아카이빙, 마스터링
실전 의사결정 트리:
1. 최대 호환성 필요? → H.264 + AAC
2. 파일 크기 최소화? → H.265 + AAC (또는 Opus)
3. YouTube 업로드? → VP9 + Opus (또는 H.264 + AAC)
4. 4K 영상? → H.265 + AAC
5. 웹 전용? → VP9 + Opus
6. 음악/오디오 보관? → FLAC
1. FFmpeg이란?
FFmpeg는 동영상과 오디오를 다루는 가장 강력한 오픈소스 멀티미디어 프레임워크입니다. 1999년부터 개발되어 현재까지 업계 표준으로 자리잡았으며, YouTube, Netflix, VLC 등 수많은 서비스와 프로그램이 FFmpeg를 기반으로 동작합니다.
주요 기능
FFmpeg로 할 수 있는 작업은 거의 무한합니다: 기본 기능:
- 동영상/오디오 포맷 변환 (MP4 ↔ AVI ↔ MKV 등)
- 인코딩/디코딩 (압축 및 압축 해제)
- 해상도 변경 및 품질 조정
- 썸네일 및 스크린샷 추출 고급 기능:
- 실시간 스트리밍 (RTMP, HLS, DASH)
- 필터 적용 (자르기, 회전, 워터마크, 색보정)
- 오디오 처리 (노이즈 제거, 볼륨 조절, 믹싱)
- 화면 녹화 및 웹캠 캡처
- 자막 추가 및 변환
- 여러 파일 병합 및 분할 실무 활용 예시:
- 유튜브 업로드용 영상 최적화
- 웹사이트용 다중 해상도 영상 생성
- 라이브 스트리밍 서버 구축
- 동영상 자동 처리 파이프라인 구축
- 모바일 앱용 영상 변환
FFmpeg 구성 요소
FFmpeg는 여러 도구와 라이브러리로 구성되어 있습니다. 각각의 역할을 이해하면 더 효과적으로 활용할 수 있습니다.
명령줄 도구:
ffmpeg
- 가장 많이 쓰는 핵심 도구
- 파일 변환, 인코딩, 필터 적용 등 모든 작업 수행
- 예: ffmpeg -i input.mp4 output.avi
ffprobe
- 미디어 파일 정보 분석 도구
- 코덱, 해상도, 비트레이트, 길이 등 메타데이터 추출
- 스크립트에서 파일 정보 확인 시 필수
- 예: ffprobe -show_format input.mp4
ffplay
- 간단한 미디어 재생기
- 인코딩 결과 빠르게 확인할 때 유용
- 테스트 및 디버깅용
- 예: ffplay output.mp4
라이브러리 (프로그래밍 시 사용):
libavcodec
- 코덱 인코딩/디코딩 라이브러리
- H.264, H.265, AAC, MP3 등 수백 개 코덱 지원
- 직접 코딩할 때 가장 핵심적인 라이브러리
libavformat
- 컨테이너 포맷 처리 라이브러리
- MP4, MKV, AVI 등 파일 읽기/쓰기
- 스트리밍 프로토콜 지원 (RTMP, HLS 등)
libavfilter
- 필터 그래프 처리 라이브러리
- 영상/오디오 효과 적용
- 크기 조정, 자르기, 워터마크, 색보정 등
libavutil
- 공통 유틸리티 라이브러리
- 수학 함수, 메모리 관리, 로깅 등
- 다른 라이브러리들의 기반
libswscale
- 이미지 스케일링 및 색공간 변환
- 해상도 변경, RGB ↔ YUV 변환
- 고품질 리사이징 알고리즘 제공
libswresample
- 오디오 리샘플링 라이브러리
- 샘플레이트 변환, 채널 믹싱
- 스테레오 ↔ 모노 변환 등
실전 팁:
- 일반 사용자: ffmpeg 명령어만 알면 충분
- 개발자: Python/Node.js 바인딩 사용 (ffmpeg-python, fluent-ffmpeg)
- 고급 개발자: libav 라이브러리 직접 사용 (C/C++)
2. 설치 및 기본 사용법
설치
macOS:
brew install ffmpeg
# 모든 옵션 포함
brew install ffmpeg --with-fdk-aac --with-libvpx --with-libvorbis
Ubuntu/Debian:
sudo apt update
sudo apt install ffmpeg
# 최신 버전 (PPA)
sudo add-apt-repository ppa:savoury1/ffmpeg4
sudo apt update
sudo apt install ffmpeg
Windows:
# Chocolatey
choco install ffmpeg
# 또는 수동 설치
# 1. https://ffmpeg.org/download.html
# 2. 압축 해제
# 3. 환경 변수 PATH에 bin 폴더 추가
Docker:
docker pull linuxserver/ffmpeg
docker run --rm -v $(pwd):/data linuxserver/ffmpeg \
-i /data/input.mp4 -c:v libx264 /data/output.mp4
버전 확인
ffmpeg -version
# 출력:
# ffmpeg version 6.1.1
# configuration: --enable-gpl --enable-libx264 --enable-libx265 ...
# libavcodec 60.31.102
# libavformat 60.16.100
# ...
기본 명령어 구조 - FFmpeg의 실행 원리
FFmpeg 명령어는 일정한 패턴을 따릅니다. 이 구조를 이해하면 복잡한 명령어도 쉽게 만들 수 있습니다.
ffmpeg [전역 옵션] [입력 옵션] -i [입력 파일] [출력 옵션] [출력 파일]
# 기본 예시
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
# ↑ ↑ ↑ ↑
# 입력 비디오 코덱 품질 출력
# 더 복잡한 예시
ffmpeg -y -i input.mp4 -i audio.mp3 -c:v libx264 -crf 20 -c:a aac -b:a 192k -shortest output.mp4
# ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
# 덮어쓰기 비디오입력 오디오입력 비디오코덱 비디오품질 오디오코덱 오디오비트 짧은쪽맞춤 출력
FFmpeg의 내부 처리 파이프라인 (상세):
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -vf scale=1280:720 output.mp4
1. Demuxing (디먹싱) - libavformat
┌────────────────────────────────┐
│ input.mp4 Container │
│ ┌──────────────────────────┐ │
│ │ Video Stream #0 (H.264) │ │ → Packet Queue
│ │ - Codec: H.264 │ │
│ │ - Bitrate: 5 Mbps │ │
│ │ - Resolution: 1920x1080 │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Audio Stream #1 (AAC) │ │ → Packet Queue
│ │ - Codec: AAC │ │
│ │ - Bitrate: 192 kbps │ │
│ │ - Sample Rate: 48000 Hz │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Metadata │ │
│ │ - Duration: 00:05:30 │ │
│ │ - Creation Time │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
Demuxer 동작:
- MP4 atom 파싱 (moov, mdat, ftyp)
- Stream 정보 추출
- Packet 단위로 데이터 읽기
- Seek 테이블 생성
2. Decoding (디코딩) - libavcodec
Compressed Packet → Raw Frame
Video Decoding:
[H.264 Packet: 50KB] → libavcodec/h264 decoder
내부 동작:
1) NAL Unit 분리
2) SPS/PPS 파싱 (해상도, 프로파일)
3) Slice 디코딩
4) Reference Frame 관리
5) Deblocking Filter 적용
결과: [YUV420p Frame: 3MB]
- Y plane: 1920x1080 (luma)
- U plane: 960x540 (chroma)
- V plane: 960x540 (chroma)
Audio Decoding:
[AAC Packet: 5KB] → libavcodec/aac decoder
내부 동작:
1) ADTS Header 파싱
2) MDCT (Modified DCT)
3) Psychoacoustic 모델 적용
결과: [PCM Samples: 96KB]
- Format: Float32
- Channels: 2 (Stereo)
- Samples: 1024 per channel
3. Filtering (필터링) - libavfilter
Raw Frame → Filter Graph → Processed Frame
-vf scale=1280:720 실행:
Filter Graph:
[in] → scale → [out]
scale 필터 내부:
1) swscale (libswscale) 호출
2) 리샘플링 알고리즘 선택:
- Bilinear (빠름)
- Bicubic (균형)
- Lanczos (고품질)
3) Y/U/V plane 개별 스케일링:
Y: 1920x1080 → 1280x720
U: 960x540 → 640x360
V: 960x540 → 640x360
결과: [YUV420p Frame: 1.3MB]
복잡한 Filter Graph 예시:
-vf "scale=1280:720,fps=30,eq=brightness=0.1"
[in] → scale → fps → eq → [out]
↓ ↓ ↓
swscale frame color
drop adjust
4. Encoding (인코딩) - libavcodec
Raw Frame → Compressed Packet
libx264 인코딩 과정:
1) Macroblock 분할 (16x16):
1280x720 → 80x45 macroblocks
2) Motion Estimation:
현재 프레임과 Reference Frame 비교
Motion Vector 계산
→ P-frame, B-frame 생성
3) Transform (DCT):
Spatial domain → Frequency domain
고주파 성분 제거 (손실 압축)
4) Quantization:
CRF 23 적용
→ Quantization Parameter (QP) 계산
→ 세부 정보 손실 (압축률 조정)
5) Entropy Coding (CABAC):
비트스트림 최종 압축
Huffman coding 기반
6) Deblocking Filter:
블록 노이즈 제거
결과: [H.264 Packet: 25KB]
- I-frame: 200KB (1초당 1개)
- P-frame: 30KB (대부분)
- B-frame: 10KB (optional)
Audio Encoding (AAC):
[PCM Samples: 96KB] → libavcodec/aac
1) MDCT (1024 samples)
2) Psychoacoustic 모델
→ 사람이 못 듣는 주파수 제거
3) Quantization
4) Huffman Coding
결과: [AAC Packet: 3KB]
- 압축률: 32:1 (96KB → 3KB)
5. Muxing (먹싱) - libavformat
Compressed Packets → Container
MP4 Muxer 동작:
1) Container 헤더 작성:
ftyp: 파일 타입
moov: 메타데이터
- mvhd: 영상 정보
- trak: 트랙 정보
- tkhd: 트랙 헤더
- mdia: 미디어 정보
2) 데이터 인터리빙:
Video Packet + Audio Packet 교차 배치
Timeline:
[V0] [A0] [V1] [A1] [V2] [A2] ...
이유: 순차 재생 최적화
3) mdat Atom 작성:
실제 압축 데이터 저장
4) Index 생성 (stco, stsc, stsz):
Seek 테이블
→ 빠른 탐색 가능
5) -movflags +faststart 옵션:
moov를 파일 앞으로 이동
→ 웹 스트리밍 즉시 시작 가능
최종 output.mp4 구조:
┌───────────────┐
│ ftyp (16B) │
├───────────────┤
│ moov (5KB) │ ← faststart: 앞으로
├───────────────┤
│ mdat (50MB) │ ← 실제 데이터
└───────────────┘
전체 파이프라인 메모리 사용:
- Demuxer Buffer: ~15MB
- Decoded Frames: ~10MB (2-3 프레임)
- Filter Buffer: ~5MB
- Encoder Buffer: ~20MB (Reference Frames)
- Muxer Buffer: ~5MB
총: ~55MB (1080p 기준)
병렬 처리:
-threads 4 사용 시:
Thread 1: Frame 1 → Decode → Filter → Encode
Thread 2: Frame 2 → Decode → Filter → Encode
Thread 3: Frame 3 → Decode → Filter → Encode
Thread 4: Frame 4 → Decode → Filter → Encode
→ 처리 속도: ~3-4배 향상
명령어 옵션의 위치가 중요한 이유:
# ❌ 잘못된 예: 입력 옵션을 -i 뒤에
ffmpeg -i input.mp4 -ss 00:01:00 -t 10 output.mp4
# 동작:
# 1. 전체 파일을 디코딩 (1분까지)
# 2. 1분부터 10초만 인코딩
# 문제: 불필요한 디코딩 (느림)
# ✅ 올바른 예: 입력 옵션을 -i 앞에
ffmpeg -ss 00:01:00 -t 10 -i input.mp4 output.mp4
# 동작:
# 1. 파일을 1분 위치로 seek (키프레임까지)
# 2. 10초만 디코딩
# 3. 인코딩
# 장점: 매우 빠름 (seek은 디코딩 불필요)
# 성능 차이:
# -i 뒤: 60초 소요 (1분까지 디코딩)
# -i 앞: 2초 소요 (seek + 10초 디코딩)
-c:v와 -c:a의 의미:
# -c:v libx264
# -c: codec (코덱 지정)
# :v: video stream (비디오 스트림)
# libx264: H.264 인코더 라이브러리
# -c:a aac
# :a: audio stream (오디오 스트림)
# 모든 스트림에 적용
-c copy # 비디오 + 오디오 모두 복사
# 개별 지정
-c:v copy -c:a aac # 비디오는 복사, 오디오만 재인코딩
# 여러 출력 파일
ffmpeg -i input.mp4 \
-c:v libx264 -crf 23 1080p.mp4 \
-c:v libx264 -crf 28 -vf scale=1280:720 720p.mp4
# 한 번의 디코딩으로 여러 해상도 생성 (효율적!)
-map의 강력한 기능:
# input.mp4: 비디오 2개 + 오디오 3개 (다국어)
ffprobe input.mp4
# Stream #0:0: Video (비디오 - 주 화면)
# Stream #0:1: Video (비디오 - 주석 화면)
# Stream #0:2: Audio (한국어)
# Stream #0:3: Audio (영어)
# Stream #0:4: Audio (일본어)
# 기본 동작 (자동 선택): 첫 번째 비디오 + 첫 번째 오디오
ffmpeg -i input.mp4 output.mp4
# Stream #0:0 (비디오) + Stream #0:2 (한국어)
# 수동 선택: 주 화면 + 영어 오디오
ffmpeg -i input.mp4 -map 0:0 -map 0:3 -c copy english.mp4
# -map 0:0: 입력 0번의 스트림 0번 (비디오)
# -map 0:3: 입력 0번의 스트림 3번 (영어 오디오)
# 여러 오디오 트랙 포함
ffmpeg -i input.mp4 -map 0:0 -map 0:2 -map 0:3 -c copy multi_audio.mp4
# 비디오 1개 + 오디오 2개 (한국어, 영어)
# 실전 예제: 비디오와 외부 오디오 결합
ffmpeg -i video.mp4 -i audio.mp3 -map 0:v -map 1:a -c copy output.mp4
# -map 0:v: 첫 번째 입력의 모든 비디오 스트림
# -map 1:a: 두 번째 입력의 모든 오디오 스트림
주요 옵션 설명:
# 전역 옵션 (입력 파일 앞에 위치)
-y # 출력 파일 덮어쓰기 (확인 없이)
-n # 출력 파일 덮어쓰기 금지
-v quiet # 로그 출력 최소화
-stats # 진행 상황 표시
# 입력 옵션 (-i 앞에 위치)
-ss 00:01:30 # 시작 시간 (1분 30초부터)
-t 10 # 지속 시간 (10초만)
-to 00:02:00 # 종료 시간 (2분까지)
# 비디오 출력 옵션
-c:v libx264 # 비디오 코덱 (H.264)
-c:v copy # 비디오 재인코딩 없이 복사
-crf 23 # 품질 (0=최고, 51=최저, 23=권장)
-preset slow # 인코딩 속도 (slow=고품질)
-b:v 5M # 비디오 비트레이트 (5Mbps)
-r 30 # 프레임레이트 (30fps)
-vf scale=1280:720 # 비디오 필터 (해상도 변경)
# 오디오 출력 옵션
-c:a aac # 오디오 코덱 (AAC)
-c:a copy # 오디오 재인코딩 없이 복사
-b:a 192k # 오디오 비트레이트 (192kbps)
-ar 48000 # 샘플레이트 (48kHz)
-ac 2 # 채널 수 (2=스테레오)
# 기타 유용한 옵션
-an # 오디오 제거
-vn # 비디오 제거
-shortest # 가장 짧은 스트림에 맞춤
-map 0:v:0 # 첫 번째 비디오 스트림 선택
-map 0:a:1 # 두 번째 오디오 스트림 선택
실전 팁:
- 옵션 순서가 중요합니다
- 입력 옵션은
-i앞에 - 출력 옵션은
-i뒤, 출력 파일 앞에
- 입력 옵션은
- -c:v와 -vcodec은 같습니다
-c:v libx264=-vcodec libx264-c:a aac=-acodec aac
- copy는 재인코딩을 건너뜁니다
- 매우 빠르지만 품질 조정 불가
- 컨테이너만 바꿀 때 유용
- 여러 입력 파일 사용 가능
ffmpeg -i video.mp4 -i audio.mp3 -c copy output.mp4 - 진행 상황 확인
ffmpeg -i input.mp4 -c:v libx264 output.mp4 # 실시간으로 frame=, fps=, time= 등이 표시됩니다
3. 동영상 변환 및 인코딩
동영상 변환은 FFmpeg의 가장 기본적이면서도 강력한 기능입니다. 단순 포맷 변환부터 고급 인코딩까지 다룹니다.
기본 변환
가장 간단한 형태의 변환입니다. FFmpeg가 자동으로 적절한 코덱을 선택합니다.
# MP4 → AVI (자동 코덱 선택)
ffmpeg -i input.mp4 output.avi
# MOV → MP4 (자동 코덱 선택)
ffmpeg -i input.mov output.mp4
# MKV → MP4 (재인코딩 없이 복사 - 가장 빠름)
ffmpeg -i input.mkv -c copy output.mp4
# -c copy: 비디오와 오디오를 재인코딩 없이 그대로 복사
# 장점: 매우 빠름 (1분 영상을 1초 만에 변환), 품질 손실 없음
# 단점: 해상도/품질 조정 불가, 코덱이 컨테이너와 호환되어야 함
# 비디오만 복사, 오디오는 재인코딩
ffmpeg -i input.mkv -c:v copy -c:a aac output.mp4
# MKV의 비디오는 그대로 두고 오디오만 AAC로 변환
# 실전 예시: iPhone 촬영 영상(MOV)을 웹용 MP4로
ffmpeg -i iphone_video.mov -c:v libx264 -crf 23 -c:a aac -b:a 128k web_video.mp4
언제 -c copy를 쓸까?
- 컨테이너만 바꾸고 싶을 때 (MKV → MP4)
- 영상 자르기만 할 때 (품질 유지)
- 빠른 처리가 필요할 때 언제 재인코딩이 필요할까?
- 해상도를 바꿀 때
- 파일 크기를 줄일 때
- 호환성 문제가 있을 때 (예: HEVC → H.264)
- 필터를 적용할 때 (워터마크, 색보정 등)
H.264 인코딩
H.264는 가장 보편적인 비디오 코덱입니다. 거의 모든 기기에서 재생되며, 품질과 파일 크기의 균형이 우수합니다. CRF (Constant Rate Factor) 방식 - 권장 CRF는 일정한 품질을 유지하면서 인코딩하는 방식입니다. 파일 크기는 영상의 복잡도에 따라 자동으로 조절됩니다.
# 기본 CRF 인코딩
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
# CRF 값 가이드:
# 0 = 무손실 (파일 크기 매우 큼, 거의 사용 안 함)
# 17-18 = 시각적으로 무손실 (전문가용, 편집 소스)
# 19-23 = 고품질 (일반 용도, YouTube 권장)
# 24-28 = 중간 품질 (웹 스트리밍, 모바일)
# 29-35 = 낮은 품질 (저사양 기기, 프리뷰용)
# 36-51 = 매우 낮은 품질 (거의 사용 안 함)
# 실전 예시들:
# YouTube 업로드용 (고품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k youtube.mp4
# 웹사이트 배경 영상 (중간 품질, 작은 파일)
ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset fast -c:a aac -b:a 128k web_bg.mp4
# 모바일 앱용 (균형잡힌 품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -vf scale=1280:720 -c:a aac -b:a 128k mobile.mp4
# 아카이브용 (최고 품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 17 -preset veryslow -c:a aac -b:a 320k archive.mp4
CRF 선택 가이드:
| 용도 | CRF 값 | 설명 |
|---|---|---|
| 편집 소스 | 17-18 | 재편집 시 품질 손실 최소화 |
| YouTube/Vimeo | 18-20 | 플랫폼 재인코딩 대비 |
| 일반 공유 | 21-23 | 대부분의 경우 적합 |
| 웹 스트리밍 | 24-26 | 빠른 로딩과 품질 균형 |
| 모바일 | 26-28 | 작은 화면, 데이터 절약 |
| 프리뷰/테스트 | 28-30 | 빠른 확인용 |
| 주의사항: |
- CRF 값이 낮을수록 품질은 좋지만 파일 크기가 커집니다
- 같은 CRF라도 영상의 복잡도에 따라 파일 크기가 다릅니다
- 정적인 장면: 작은 파일
- 빠른 움직임, 복잡한 장면: 큰 파일 프리셋 (Preset) - 속도 vs 압축률 프리셋은 인코딩 속도와 압축 효율의 균형을 조절합니다. 느린 프리셋일수록 같은 품질에서 파일 크기가 작아지지만, 인코딩 시간이 오래 걸립니다.
# 프리셋 기본 사용법
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 23 output.mp4
# 프리셋 종류 (빠름 → 느림):
ultrafast # 인코딩: 초고속 (실시간 가능), 파일: 매우 큼
superfast # 인코딩: 매우 빠름, 파일: 큼
veryfast # 인코딩: 빠름, 파일: 약간 큼
faster # 인코딩: 빠른 편, 파일: 보통
fast # 인코딩: 보통, 파일: 보통
medium # 인코딩: 보통 (기본값), 파일: 보통
slow # 인코딩: 느림, 파일: 작음 (★ 권장)
slower # 인코딩: 매우 느림, 파일: 매우 작음
veryslow # 인코딩: 극도로 느림, 파일: 최소
# 실전 예시:
# 빠른 테스트용 (품질 확인)
ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast -crf 23 test.mp4
# 1080p 1분 영상: 약 5초 소요
# 일반 용도 (균형)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 output.mp4
# 1080p 1분 영상: 약 30초 소요
# 최종 배포용 (최적 품질)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 20 final.mp4
# 1080p 1분 영상: 약 1-2분 소요
# 아카이브용 (최대 압축)
ffmpeg -i input.mp4 -c:v libx264 -preset veryslow -crf 18 archive.mp4
# 1080p 1분 영상: 약 5-10분 소요
프리셋 선택 가이드:
| 상황 | 추천 프리셋 | 이유 |
|---|---|---|
| 실시간 스트리밍 | ultrafast, veryfast | CPU 부하 최소화 |
| 빠른 프리뷰 | veryfast, fast | 빠른 확인 |
| 일반 작업 | medium | 기본 균형 |
| 최종 배포 | slow | 최적 품질/크기 비율 |
| 아카이브 | slower, veryslow | 장기 보관용 최소 크기 |
| 배치 처리 | fast, medium | 대량 파일 처리 |
| 성능 비교 (1080p 1분 영상 기준): |
프리셋 인코딩 시간 파일 크기 품질 (CRF 23)
ultrafast 5초 50MB 보통
veryfast 10초 40MB 좋음
medium 30초 30MB 매우 좋음
slow 1-2분 25MB 최고
veryslow 5-10분 23MB 최고+
실제 시간은 CPU 성능에 따라 다릅니다.
실전 팁:
- 개발/테스트 단계:
veryfast사용- 빠른 피드백으로 효율적인 작업
- 최종 배포:
slow사용- 시간을 들여도 최적 결과 확보
- 대량 처리:
medium사용- 시간과 품질의 균형
- 프리셋과 CRF 조합:
# 빠르게 + 높은 품질 = 큰 파일 ffmpeg -i input.mp4 -preset veryfast -crf 18 output.mp4 # 느리게 + 낮은 품질 = 작은 파일 ffmpeg -i input.mp4 -preset slow -crf 28 output.mp4 # 권장 조합 (일반 용도) ffmpeg -i input.mp4 -preset slow -crf 22 output.mp4
Preset이 내부적으로 바꾸는 파라미터:
Preset은 libx264의 수십 개 파라미터를 한 번에 조정합니다.
주요 파라미터 비교:
파라미터 ultrafast medium slow veryslow
────────────────────────────────────────────────────────────────────
ref (참조 프레임) 1 2 5 16
me (움직임 탐색) dia hex umh tesa
subme (서브픽셀) 1 6 8 11
me_range 16 16 16 24
b-adapt (B프레임) 0 1 2 2
rc_lookahead 10 40 50 60
trellis (최적화) 0 1 2 2
partitions none some most all
8x8dct no yes yes yes
direct spatial spatial auto auto
deblock yes(α=0) yes yes yes
weightp 0 2 2 2
weightb no no yes yes
mixed_refs no yes yes yes
──────────────────────────────────────────────────────────────────
각 파라미터 의미:
1. ref (참조 프레임 수):
- 이전 프레임을 몇 개까지 참조할지
- 많을수록 압축률 향상, 메모리 증가
- ultrafast: 1개 (직전 프레임만)
- slow: 5개
- veryslow: 16개 (최대)
실제 동작:
현재 프레임의 16x16 블록을:
- ref=1: 직전 1개 프레임에서 탐색
- ref=5: 최근 5개 프레임에서 최적 매칭 찾기
- ref=16: 최근 16개 프레임에서 최적 매칭 찾기
효과:
- 카메라 팬/틸트: 높은 ref에서 큰 효과
- 정적 장면: ref 증가 효과 미미
- 메모리: ref × 해상도 × 1.5 (YUV420p)
2. me (Motion Estimation - 움직임 추정):
알고리즘 순서 (빠름 → 느림 → 정확):
dia (Diamond):
┌───┬───┬───┐
│ │ ● │ │ 4방향만 탐색
├───┼───┼───┤
│ ● │ X │ ● │ X = 현재 위치
├───┼───┼───┤
│ │ ● │ │
└───┴───┴───┘
hex (Hexagon):
┌───┬───┬───┐
│ ● │ │ ● │ 6방향 탐색
├───┼───┼───┤
│ ● │ X │ ● │ 더 넓은 범위
├───┼───┼───┤
│ ● │ │ ● │
└───┴───┴───┘
umh (Uneven Multi-Hexagon):
나선형 + 다각형 + 전역 탐색
시작점 주변 정밀 탐색 후
점점 범위 확대
tesa (Transformed Exhaustive Search Algorithm):
모든 가능한 위치 철저히 탐색
가장 느리지만 가장 정확
탐색 범위:
- me_range: 탐색할 최대 픽셀 거리
- ultrafast: 16px (빠른 움직임 놓칠 수 있음)
- slow/veryslow: 24px (빠른 움직임도 캐치)
3. subme (Subpixel Motion Estimation):
레벨 0-11:
subme=1 (ultrafast):
정수 픽셀만 탐색
16x16 블록 단위
subme=6 (medium):
1/2 픽셀 정밀도
8x8, 4x4 블록도 분석
subme=8 (slow):
1/4 픽셀 정밀도
RD (Rate-Distortion) 최적화
subme=11 (veryslow):
1/8 픽셀 정밀도
완전 RD 최적화
모든 블록 크기 (16x16, 8x8, 4x4, 8x4, 4x8)
효과:
- 낮음: 블록 경계 눈에 띔 (blocky)
- 높음: 부드러운 움직임, 작은 파일
4. b-adapt (B프레임 적응 모드):
B프레임: 양방향 예측 프레임
(과거 + 미래 프레임 참조)
b-adapt=0 (ultrafast):
B프레임 사용 안 함
I-P-P-P-P... 구조만
b-adapt=1 (medium):
빠른 휴리스틱 알고리즘
장면 변화 감지 후 B프레임 배치
b-adapt=2 (slow/veryslow):
최적 B프레임 위치 계산
lookahead buffer 사용
예시:
b-adapt=0: I P P P P P (25% 압축)
b-adapt=2: I B B P B B P (40% 압축)
I = Intra (독립 프레임)
P = Predictive (이전 참조)
B = Bi-directional (양방향 참조)
5. rc_lookahead (Rate Control Lookahead):
미래 프레임을 몇 개 미리 볼지
lookahead=10 (ultrafast):
10프레임 미리 분석
장면 전환 감지 제한적
lookahead=60 (veryslow):
60프레임 (2초 @ 30fps) 미리 분석
장면 전환 완벽 감지
최적 비트 할당
효과:
- 높음: 장면 전환 전 비트 절약
- 높음: 복잡한 장면에 비트 집중
- 메모리: lookahead × 해상도 버퍼
6. trellis (Trellis 양자화):
0 = 비활성화
1 = 최종 인코딩 시만
2 = 전체 과정 (가장 느림)
동작:
- DCT 계수를 최적화
- Rate-Distortion 트레이드오프
- 비트 절약 vs 품질 유지
효과:
- trellis=0: 빠름, 큰 파일
- trellis=2: 5-10% 파일 크기 감소
7. partitions (매크로블록 분할):
H.264는 16x16 매크로블록을 더 작게 나눌 수 있음
none (ultrafast):
16x16만 사용
some (medium):
16x16, 8x8
most (slow):
16x16, 8x8, 4x4
all (veryslow):
16x16, 8x16, 16x8, 8x8, 8x4, 4x8, 4x4
효과:
- 작은 블록: 디테일 보존, 느린 인코딩
- 큰 블록: 빠른 인코딩, 블록 아티팩트
실제 명령어로 보는 차이:
# ultrafast (수동 재현)
ffmpeg -i input.mp4 -c:v libx264 \
-x264-params \
ref=1:\
me=dia:\
subme=1:\
me_range=16:\
b-adapt=0:\
rc_lookahead=10:\
trellis=0 \
output.mp4
# slow (수동 재현)
ffmpeg -i input.mp4 -c:v libx264 \
-x264-params \
ref=5:\
me=umh:\
subme=8:\
me_range=16:\
b-adapt=2:\
rc_lookahead=50:\
trellis=2:\
8x8dct=1:\
mixed_refs=1:\
weightb=1 \
output.mp4
인코딩 시간 vs 압축률 (1080p 1분):
ultrafast:
시간: 5초
크기: 50MB
CPU: 20%
메모리: 200MB
medium:
시간: 30초
크기: 30MB (-40%)
CPU: 80%
메모리: 400MB
slow:
시간: 90초
크기: 25MB (-50%)
CPU: 100%
메모리: 800MB
veryslow:
시간: 300초
크기: 23MB (-54%)
CPU: 100%
메모리: 1.5GB
실전 권장:
- 실시간 스트리밍: ultrafast/veryfast
- 빠른 변환: fast/medium
- 최종 배포: slow
- 아카이브: veryslow (시간 여유 시)
2-Pass 인코딩 - 목표 비트레이트 정확도 2-Pass 인코딩은 영상을 두 번 처리합니다. 첫 번째는 분석, 두 번째는 실제 인코딩입니다. 목표 비트레이트를 정확히 맞춰야 할 때 사용합니다.
# 1단계: 분석 (영상 전체를 스캔하여 통계 수집)
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 1 -f null /dev/null
# Windows에서는: -f null NUL
# 2단계: 실제 인코딩 (1단계 통계를 바탕으로 최적화)
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 2 output.mp4
# 완전한 예시 (오디오 포함)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 2 -c:a aac -b:a 192k output.mp4
2-Pass vs 1-Pass (CRF) 비교:
| 특성 | 1-Pass (CRF) | 2-Pass (Bitrate) |
|---|---|---|
| 인코딩 시간 | 1배 | 2배 |
| 품질 일관성 | 일정한 품질 | 일정한 비트레이트 |
| 파일 크기 | 가변적 | 정확히 목표치 |
| 사용 시기 | 일반 용도 | 스트리밍, 방송 |
| 복잡한 장면 | 비트레이트 증가 | 품질 약간 저하 |
| 단순한 장면 | 비트레이트 감소 | 품질 약간 향상 |
| 언제 2-Pass를 사용할까? | ||
| ✅ 사용해야 할 때: |
- 정확한 파일 크기가 필요할 때 (예: DVD, Blu-ray)
- 스트리밍 비트레이트 상한이 정해져 있을 때
- 방송 송출 규격을 맞춰야 할 때
- 여러 영상의 비트레이트를 동일하게 맞춰야 할 때 ❌ 사용하지 않아도 될 때:
- 일반 파일 변환 (CRF가 더 간단하고 효율적)
- 빠른 처리가 필요할 때
- 파일 크기가 유동적이어도 될 때
- YouTube 등 업로드 (플랫폼이 다시 인코딩함) 실전 예시:
# 웹 스트리밍용 (정확한 비트레이트 필요)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 4M \
-pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 4M \
-pass 2 -c:a aac -b:a 128k streaming.mp4
# DVD 제작용 (MPEG-2, 정확한 크기)
# Pass 1
ffmpeg -i input.mp4 -c:v mpeg2video -b:v 8M -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v mpeg2video -b:v 8M -pass 2 -c:a mp2 -b:a 192k dvd.mpg
# 모바일 스트리밍 (낮은 비트레이트, 정확한 제어)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 500k -maxrate 500k -bufsize 1M \
-pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 500k -maxrate 500k -bufsize 1M \
-pass 2 -c:a aac -b:a 64k mobile.mp4
2-Pass 최적화 팁:
- 첫 번째 Pass는 빠르게:
# preset을 빠르게 설정 (분석만 하므로) ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -preset veryfast -pass 1 -f null /dev/null ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -preset slow -pass 2 output.mp4 - 로그 파일 정리:
# 2-Pass는 ffmpeg2pass-0.log 파일을 생성합니다 # 작업 후 삭제: rm ffmpeg2pass-0.log - maxrate와 bufsize 함께 사용:
# 비트레이트 스파이크 방지 -b:v 5M -maxrate 5M -bufsize 10M
성능 비교:
1080p 10분 영상 기준:
1-Pass CRF:
- 시간: 5분
- 크기: 500MB (가변)
- 품질: 일정
2-Pass 5Mbps:
- 시간: 10분 (2배)
- 크기: 375MB (정확)
- 품질: 약간 가변
결론: 일반 용도는 1-Pass CRF 추천
H.265 (HEVC) 인코딩
# H.265 기본
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4
# H.265는 H.264보다 CRF 값을 4-6 높여도 비슷한 품질
# H.264 CRF 23 ≈ H.265 CRF 28
# 프리셋 포함
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 28 output.mp4
# 태그 추가 (호환성)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -tag:v hvc1 output.mp4
해상도 변경
해상도 변경은 모바일 최적화, 웹 스트리밍, 파일 크기 절감 등 다양한 목적으로 사용됩니다.
# 기본 해상도 변경 (1080p → 720p)
ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4
# 비율 유지 (너비 고정, 높이 자동 계산)
ffmpeg -i input.mp4 -vf scale=1280:-1 output.mp4
# -1: 원본 비율 유지하며 자동 계산
# 비율 유지 (높이 고정, 너비 자동 계산)
ffmpeg -i input.mp4 -vf scale=-1:720 output.mp4
# 짝수 픽셀 보장 (H.264 등 일부 코덱 요구사항)
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# -2: 2의 배수로 자동 조정 (홀수 픽셀 방지)
# 4K → 1080p (고품질 다운스케일)
ffmpeg -i input_4k.mp4 -vf scale=1920:1080 -c:v libx264 -preset slow -crf 18 output_1080p.mp4
실전 예시:
# 모바일 최적화 (720p, 작은 파일)
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 26 -preset fast -c:a aac -b:a 96k mobile.mp4
# 웹 스트리밍용 다중 해상도 생성
# 1080p
ffmpeg -i input.mp4 -vf scale=1920:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k output_1080p.mp4
# 720p
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output_720p.mp4
# 480p
ffmpeg -i input.mp4 -vf scale=854:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 96k output_480p.mp4
# Instagram 정사각형 (1:1 비율, 크롭)
ffmpeg -i input.mp4 -vf "scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080" \
-c:v libx264 -crf 23 instagram.mp4
# YouTube Shorts / TikTok (9:16 세로 비율)
ffmpeg -i input.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -crf 23 shorts.mp4
# 4K 다운스케일 (고품질 유지)
ffmpeg -i input_4k.mp4 -vf scale=1920:-2 -c:v libx264 -preset slow -crf 18 \
-c:a copy output_1080p.mp4
고급 스케일링 옵션:
# 고품질 스케일링 알고리즘 (lanczos)
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=lanczos output.mp4
# 스케일링 알고리즘 비교:
# bilinear: 빠름, 품질 보통
# bicubic: 중간 (기본값)
# lanczos: 느림, 품질 최고
# spline: 부드러움
# 예시: 각 알고리즘 비교
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=bilinear bilinear.mp4
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=bicubic bicubic.mp4
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=lanczos lanczos.mp4
# 업스케일링 (권장하지 않음, 품질 향상 없음)
ffmpeg -i 720p.mp4 -vf scale=1920:-2:flags=lanczos 1080p.mp4
# 주의: 해상도만 커지고 실제 화질은 개선되지 않음
비율 맞추기:
# 16:9 비율 강제 (패딩 추가)
ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" output.mp4
# 4:3 → 16:9 (좌우 검은 바 추가)
ffmpeg -i input_4_3.mp4 -vf "scale=1920:-2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" output_16_9.mp4
# 16:9 → 4:3 (상하 검은 바 추가)
ffmpeg -i input_16_9.mp4 -vf "scale=-2:1080,pad=1440:1080:(ow-iw)/2:(oh-ih)/2:black" output_4_3.mp4
일괄 변환 스크립트:
# 폴더 내 모든 영상을 720p로 변환
for file in *.mp4; do
ffmpeg -i "$file" -vf scale=1280:-2 -c:v libx264 -crf 23 "720p_$file"
done
# 다중 해상도 자동 생성
for file in *.mp4; do
base="${file%.mp4}"
ffmpeg -i "$file" -vf scale=1920:-2 -c:v libx264 -crf 23 "${base}_1080p.mp4"
ffmpeg -i "$file" -vf scale=1280:-2 -c:v libx264 -crf 23 "${base}_720p.mp4"
ffmpeg -i "$file" -vf scale=854:-2 -c:v libx264 -crf 23 "${base}_480p.mp4"
done
주의사항:
- 업스케일링은 피하세요
- 720p → 1080p로 키워도 화질은 개선되지 않습니다
- 오히려 파일 크기만 커집니다
- 홀수 픽셀 문제
- H.264 등은 짝수 픽셀을 요구합니다
-2사용으로 자동 조정
- 비율 왜곡 주의
scale=1280:720는 강제로 늘리거나 줄입니다- 비율 유지는
-1또는-2사용
- 성능 고려
- 다운스케일은 빠름
- 업스케일은 느리고 의미 없음
프레임레이트 변경
# 60fps → 30fps
ffmpeg -i input.mp4 -r 30 output.mp4
# 24fps → 60fps (보간)
ffmpeg -i input.mp4 -filter:v "minterpolate=fps=60" output.mp4
비트레이트 설정
# 비디오 비트레이트
ffmpeg -i input.mp4 -b:v 5M output.mp4
# 오디오 비트레이트
ffmpeg -i input.mp4 -b:a 192k output.mp4
# 둘 다
ffmpeg -i input.mp4 -b:v 5M -b:a 192k output.mp4
# 최대 비트레이트 제한
ffmpeg -i input.mp4 -b:v 5M -maxrate 5M -bufsize 10M output.mp4
4. 오디오 처리
오디오 추출
# 동영상에서 오디오만 추출
ffmpeg -i video.mp4 -vn -c:a copy audio.aac
# -vn: 비디오 제외
# MP3로 변환
ffmpeg -i video.mp4 -vn -c:a libmp3lame -b:a 192k audio.mp3
# WAV로 변환 (무손실)
ffmpeg -i video.mp4 -vn audio.wav
오디오 변환
# MP3 → AAC
ffmpeg -i input.mp3 -c:a aac -b:a 192k output.aac
# WAV → MP3
ffmpeg -i input.wav -c:a libmp3lame -b:a 320k output.mp3
# FLAC → MP3 (무손실 → 손실)
ffmpeg -i input.flac -c:a libmp3lame -q:a 0 output.mp3
# -q:a 0: 최고 품질 (VBR)
오디오 편집
# 볼륨 조절 (2배)
ffmpeg -i input.mp3 -af "volume=2.0" output.mp3
# 볼륨 조절 (dB)
ffmpeg -i input.mp3 -af "volume=10dB" output.mp3
# 페이드 인/아웃
ffmpeg -i input.mp3 -af "afade=t=in:st=0:d=3,afade=t=out:st=57:d=3" output.mp3
# t=in: 페이드 인, t=out: 페이드 아웃
# st: 시작 시간(초), d: 지속 시간(초)
# 노이즈 제거
ffmpeg -i input.mp3 -af "highpass=f=200,lowpass=f=3000" output.mp3
# 스테레오 → 모노
ffmpeg -i input.mp3 -ac 1 output.mp3
# 샘플레이트 변경
ffmpeg -i input.mp3 -ar 44100 output.mp3
5. 썸네일 및 이미지 추출
썸네일 추출
# 첫 프레임 추출
ffmpeg -i video.mp4 -vframes 1 thumbnail.jpg
# 특정 시간 (5초)
ffmpeg -i video.mp4 -ss 00:00:05 -vframes 1 thumbnail.jpg
# 고품질
ffmpeg -i video.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg
# -q:v 2: 품질 (1=최고, 31=최저)
# 특정 크기
ffmpeg -i video.mp4 -ss 00:00:05 -vf scale=1280:720 -vframes 1 thumbnail.jpg
여러 썸네일 추출
# 1초마다 추출
ffmpeg -i video.mp4 -vf fps=1 thumb_%04d.jpg
# 10초마다 추출
ffmpeg -i video.mp4 -vf fps=1/10 thumb_%04d.jpg
# 특정 구간 (10초~20초, 1초마다)
ffmpeg -i video.mp4 -ss 00:00:10 -to 00:00:20 -vf fps=1 thumb_%04d.jpg
GIF 생성
# 동영상 → GIF
ffmpeg -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos" output.gif
# 고품질 GIF (팔레트 생성)
# 1단계: 팔레트 생성
ffmpeg -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png
# 2단계: GIF 생성
ffmpeg -i video.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
# 특정 구간 (0~5초)
ffmpeg -ss 00:00:00 -t 00:00:05 -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -ss 00:00:00 -t 00:00:05 -i video.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
6. 동영상 편집
자르기 (Trim)
# 시간 기준 자르기
ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:30 output.mp4
# -ss: 시작 시간
# -to: 종료 시간
# 또는
ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:20 output.mp4
# -t: 지속 시간 (20초)
# 재인코딩 없이 (빠름, 정확도 낮음)
ffmpeg -ss 00:00:10 -i input.mp4 -to 00:00:30 -c copy output.mp4
# -ss를 -i 앞에 두면 더 빠름 (키프레임 탐색)
크롭 (Crop)
# 중앙 1280×720 크롭
ffmpeg -i input.mp4 -vf "crop=1280:720" output.mp4
# 특정 위치 크롭
ffmpeg -i input.mp4 -vf "crop=1280:720:0:0" output.mp4
# crop=width:height:x:y
# 16:9 → 1:1 (정사각형, 중앙)
ffmpeg -i input.mp4 -vf "crop=ih:ih" output.mp4
# ih: 입력 높이
회전
# 90도 시계방향
ffmpeg -i input.mp4 -vf "transpose=1" output.mp4
# 90도 반시계방향
ffmpeg -i input.mp4 -vf "transpose=2" output.mp4
# 180도
ffmpeg -i input.mp4 -vf "transpose=1,transpose=1" output.mp4
# 메타데이터로 회전 (재인코딩 없음)
ffmpeg -i input.mp4 -c copy -metadata:s:v:0 rotate=90 output.mp4
병합
동영상 이어붙이기:
# 1. 파일 목록 생성
cat > filelist.txt << EOF
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'
EOF
# 2. 병합
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
# 재인코딩 포함
ffmpeg -f concat -safe 0 -i filelist.txt -c:v libx264 -crf 23 output.mp4
오디오 추가:
# 동영상에 오디오 추가
ffmpeg -i video.mp4 -i audio.mp3 -c:v copy -c:a aac output.mp4
# 기존 오디오 교체
ffmpeg -i video.mp4 -i audio.mp3 -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 output.mp4
7. 스트리밍 및 HLS
HLS 생성
HLS (HTTP Live Streaming)는 Apple이 개발한 스트리밍 프로토콜이다.
# 기본 HLS
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-hls_time 10 \
-hls_playlist_type vod \
-hls_segment_filename "segment_%03d.ts" \
playlist.m3u8
# 옵션:
# -hls_time 10: 세그먼트 길이 (10초)
# -hls_playlist_type vod: VOD (주문형)
# -hls_segment_filename: 세그먼트 파일명
다중 해상도 (Adaptive Bitrate):
# 1080p
ffmpeg -i input.mp4 \
-vf scale=1920:1080 -c:v libx264 -b:v 5M -c:a aac -b:a 192k \
-hls_time 10 -hls_playlist_type vod \
-hls_segment_filename "1080p_%03d.ts" \
1080p.m3u8
# 720p
ffmpeg -i input.mp4 \
-vf scale=1280:720 -c:v libx264 -b:v 3M -c:a aac -b:a 128k \
-hls_time 10 -hls_playlist_type vod \
-hls_segment_filename "720p_%03d.ts" \
720p.m3u8
# 480p
ffmpeg -i input.mp4 \
-vf scale=854:480 -c:v libx264 -b:v 1.5M -c:a aac -b:a 128k \
-hls_time 10 -hls_playlist_type vod \
-hls_segment_filename "480p_%03d.ts" \
480p.m3u8
# Master Playlist 생성
cat > master.m3u8 << EOF
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=854x480
480p.m3u8
EOF
RTMP 스트리밍
# YouTube 라이브
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3M -maxrate 3M -bufsize 6M \
-pix_fmt yuv420p -g 60 -c:a aac -b:a 128k -ar 44100 \
-f flv rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY
# Twitch 라이브
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3M \
-c:a aac -b:a 128k \
-f flv rtmp://live.twitch.tv/app/YOUR_STREAM_KEY
# -re: 실시간 속도로 읽기
# -g 60: GOP 크기 (2초, 30fps 기준)
웹캠 스트리밍
# macOS (웹캠)
ffmpeg -f avfoundation -i "0:0" \
-c:v libx264 -preset veryfast -b:v 2M \
-c:a aac -b:a 128k \
-f flv rtmp://localhost/live/stream
# Linux (웹캠)
ffmpeg -f v4l2 -i /dev/video0 \
-c:v libx264 -preset veryfast -b:v 2M \
-f flv rtmp://localhost/live/stream
# Windows (웹캠)
ffmpeg -f dshow -i video="USB Video Device" \
-c:v libx264 -preset veryfast -b:v 2M \
-f flv rtmp://localhost/live/stream
8. 필터 및 효과
워터마크
# 이미지 워터마크 (우측 상단)
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "overlay=W-w-10:10" \
output.mp4
# 좌측 하단
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "overlay=10:H-h-10" \
output.mp4
# 중앙
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "overlay=(W-w)/2:(H-h)/2" \
output.mp4
# 투명도 조절
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[logo];[0:v][logo]overlay=W-w-10:10" \
output.mp4
텍스트 추가
# 간단한 텍스트
ffmpeg -i input.mp4 \
-vf "drawtext=text='Hello World':fontsize=48:fontcolor=white:x=10:y=10" \
output.mp4
# 시간 표시
ffmpeg -i input.mp4 \
-vf "drawtext=text='%{pts\:hms}':fontsize=32:fontcolor=white:x=10:y=10" \
output.mp4
# 한글 텍스트 (폰트 지정 필수)
ffmpeg -i input.mp4 \
-vf "drawtext=text='안녕하세요':fontfile=/System/Library/Fonts/AppleSDGothicNeo.ttc:fontsize=48:fontcolor=white:x=10:y=10" \
output.mp4
# 배경 추가
ffmpeg -i input.mp4 \
-vf "drawtext=text='Hello':fontsize=48:fontcolor=white:x=10:y=10:box=1:[email protected]:boxborderw=5" \
output.mp4
색상 조정
# 밝기 조절
ffmpeg -i input.mp4 -vf "eq=brightness=0.1" output.mp4
# 대비 조절
ffmpeg -i input.mp4 -vf "eq=contrast=1.5" output.mp4
# 채도 조절
ffmpeg -i input.mp4 -vf "eq=saturation=1.5" output.mp4
# 흑백
ffmpeg -i input.mp4 -vf "hue=s=0" output.mp4
# 세피아
ffmpeg -i input.mp4 -vf "colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131" output.mp4
블러 및 샤프닝
# 블러
ffmpeg -i input.mp4 -vf "boxblur=5:1" output.mp4
# 샤프닝
ffmpeg -i input.mp4 -vf "unsharp=5:5:1.0:5:5:0.0" output.mp4
# 특정 영역만 블러 (얼굴 가리기)
ffmpeg -i input.mp4 \
-vf "boxblur=10:enable='between(t,5,10)'" \
output.mp4
9. 미디어 정보 확인
ffprobe 사용
# 기본 정보
ffprobe input.mp4
# JSON 형식
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
# 비디오 정보만
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,bit_rate -of default=noprint_wrappers=1 input.mp4
# 오디오 정보만
ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,channels,bit_rate -of default=noprint_wrappers=1 input.mp4
# 길이 확인
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
실용적인 정보 추출
# 해상도
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 input.mp4
# 출력: 1920x1080
# 프레임레이트
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: 30/1
# 비트레이트
ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: 5000000 (5 Mbps)
# 코덱
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: h264
10. FFmpeg 라이브러리 (libav)
Python (ffmpeg-python)
설치:
pip install ffmpeg-python
기본 사용:
import ffmpeg
# 동영상 변환
(
ffmpeg
.input('input.mp4')
.output('output.mp4', vcodec='libx264', crf=23)
.run()
)
# 해상도 변경
(
ffmpeg
.input('input.mp4')
.filter('scale', 1280, 720)
.output('output.mp4')
.run()
)
# 썸네일 추출
(
ffmpeg
.input('input.mp4', ss=5)
.output('thumbnail.jpg', vframes=1)
.run()
)
# 오디오 추출
(
ffmpeg
.input('input.mp4')
.output('audio.mp3', vn=None, acodec='libmp3lame', audio_bitrate='192k')
.run()
)
고급 예제:
import ffmpeg
import sys
def convert_video(input_file, output_file, resolution='1280:720', crf=23):
"""동영상 변환"""
try:
(
ffmpeg
.input(input_file)
.filter('scale', *resolution.split(':'))
.output(
output_file,
vcodec='libx264',
preset='slow',
crf=crf,
acodec='aac',
audio_bitrate='192k'
)
.global_args('-loglevel', 'error')
.run(capture_stdout=True, capture_stderr=True)
)
print(f"✅ Conversion complete: {output_file}")
except ffmpeg.Error as e:
print(f"❌ Error: {e.stderr.decode()}", file=sys.stderr)
raise
def get_video_info(input_file):
"""동영상 정보 추출"""
try:
probe = ffmpeg.probe(input_file)
video_stream = next((s for s in probe['streams'] if s['codec_type'] == 'video'), None)
audio_stream = next((s for s in probe['streams'] if s['codec_type'] == 'audio'), None)
return {
'duration': float(probe['format']['duration']),
'size': int(probe['format']['size']),
'bitrate': int(probe['format']['bit_rate']),
'video': {
'codec': video_stream['codec_name'],
'width': video_stream['width'],
'height': video_stream['height'],
'fps': eval(video_stream['r_frame_rate'])
} if video_stream else None,
'audio': {
'codec': audio_stream['codec_name'],
'sample_rate': audio_stream['sample_rate'],
'channels': audio_stream['channels']
} if audio_stream else None
}
except ffmpeg.Error as e:
print(f"❌ Error: {e.stderr.decode()}", file=sys.stderr)
raise
# 사용
info = get_video_info('input.mp4')
print(f"Duration: {info['duration']}s")
print(f"Resolution: {info['video']['width']}x{info['video']['height']}")
print(f"FPS: {info['video']['fps']}")
진행률 표시:
import ffmpeg
import sys
def convert_with_progress(input_file, output_file):
"""진행률 표시"""
# 동영상 길이 확인
probe = ffmpeg.probe(input_file)
duration = float(probe['format']['duration'])
# 변환 시작
process = (
ffmpeg
.input(input_file)
.output(output_file, vcodec='libx264', crf=23)
.global_args('-progress', 'pipe:1')
.overwrite_output()
.run_async(pipe_stdout=True, pipe_stderr=True)
)
# 진행률 파싱
for line in process.stdout:
line = line.decode()
if line.startswith('out_time_ms='):
time_ms = int(line.split('=')[1])
time_s = time_ms / 1000000.0
progress = (time_s / duration) * 100
sys.stdout.write(f"\rProgress: {progress:.1f}%")
sys.stdout.flush()
process.wait()
print("\n✅ Complete!")
convert_with_progress('input.mp4', 'output.mp4')
Node.js (fluent-ffmpeg)
설치:
npm install fluent-ffmpeg
기본 사용:
const ffmpeg = require('fluent-ffmpeg');
// 동영상 변환
ffmpeg('input.mp4')
.output('output.mp4')
.videoCodec('libx264')
.audioCodec('aac')
.on('end', () => console.log('✅ Complete!'))
.on('error', (err) => console.error('❌ Error:', err))
.run();
// 해상도 변경
ffmpeg('input.mp4')
.size('1280x720')
.output('output.mp4')
.run();
// 썸네일 추출
ffmpeg('input.mp4')
.screenshots({
timestamps: ['00:00:05'],
filename: 'thumbnail.jpg',
folder: './thumbnails'
});
고급 예제:
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
// 동영상 정보 추출
function getVideoInfo(inputPath) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(inputPath, (err, metadata) => {
if (err) return reject(err);
const videoStream = metadata.streams.find(s => s.codec_type === 'video');
const audioStream = metadata.streams.find(s => s.codec_type === 'audio');
resolve({
duration: metadata.format.duration,
size: metadata.format.size,
bitrate: metadata.format.bit_rate,
video: videoStream ? {
codec: videoStream.codec_name,
width: videoStream.width,
height: videoStream.height,
fps: eval(videoStream.r_frame_rate)
} : null,
audio: audioStream ? {
codec: audioStream.codec_name,
sampleRate: audioStream.sample_rate,
channels: audioStream.channels
} : null
});
});
});
}
// 진행률 표시
function convertWithProgress(inputPath, outputPath) {
return new Promise((resolve, reject) => {
ffmpeg(inputPath)
.output(outputPath)
.videoCodec('libx264')
.videoBitrate('5M')
.audioCodec('aac')
.audioBitrate('192k')
.on('start', (cmd) => {
console.log('Starting:', cmd);
})
.on('progress', (progress) => {
console.log(`Progress: ${progress.percent?.toFixed(1)}%`);
})
.on('end', () => {
console.log('✅ Complete!');
resolve();
})
.on('error', (err) => {
console.error('❌ Error:', err);
reject(err);
})
.run();
});
}
// HLS 생성
function createHLS(inputPath, outputDir) {
return new Promise((resolve, reject) => {
ffmpeg(inputPath)
.outputOptions([
'-c:v libx264',
'-c:a aac',
'-hls_time 10',
'-hls_playlist_type vod',
'-hls_segment_filename', path.join(outputDir, 'segment_%03d.ts')
])
.output(path.join(outputDir, 'playlist.m3u8'))
.on('end', resolve)
.on('error', reject)
.run();
});
}
// 사용
(async () => {
const info = await getVideoInfo('input.mp4');
console.log('Video Info:', info);
await convertWithProgress('input.mp4', 'output.mp4');
await createHLS('input.mp4', './hls');
})();
Go (ffmpeg-go)
설치:
go get github.com/u2takey/ffmpeg-go
기본 사용:
package main
import (
"fmt"
ffmpeg "github.com/u2takey/ffmpeg-go"
)
func main() {
// 동영상 변환
err := ffmpeg.Input("input.mp4").
Output("output.mp4", ffmpeg.KwArgs{
"c:v": "libx264",
"crf": 23,
}).
Run()
if err != nil {
panic(err)
}
fmt.Println("✅ Complete!")
}
// 해상도 변경
func resizeVideo(input, output string) error {
return ffmpeg.Input(input).
Filter("scale", ffmpeg.Args{"1280:720"}).
Output(output).
Run()
}
// 썸네일 추출
func extractThumbnail(input, output string, timestamp float64) error {
return ffmpeg.Input(input, ffmpeg.KwArgs{"ss": timestamp}).
Output(output, ffmpeg.KwArgs{"vframes": 1}).
Run()
}
// 오디오 추출
func extractAudio(input, output string) error {
return ffmpeg.Input(input).
Output(output, ffmpeg.KwArgs{
"vn": "",
"c:a": "libmp3lame",
"b:a": "192k",
}).
Run()
}
11. 실전 프로젝트
프로젝트 1: 동영상 업로드 서버
Node.js + Express:
const express = require('express');
const multer = require('multer');
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
const fs = require('fs').promises;
const app = express();
const upload = multer({ dest: 'uploads/' });
// 동영상 업로드 및 처리
app.post('/upload', upload.single('video'), async (req, res) => {
const inputPath = req.file.path;
const outputDir = path.join('processed', req.file.filename);
try {
await fs.mkdir(outputDir, { recursive: true });
// 1. 썸네일 생성
await new Promise((resolve, reject) => {
ffmpeg(inputPath)
.screenshots({
timestamps: ['00:00:01'],
filename: 'thumbnail.jpg',
folder: outputDir
})
.on('end', resolve)
.on('error', reject);
});
// 2. 다중 해상도 변환
const resolutions = [
{ name: '1080p', size: '1920x1080', bitrate: '5M' },
{ name: '720p', size: '1280x720', bitrate: '3M' },
{ name: '480p', size: '854x480', bitrate: '1.5M' }
];
await Promise.all(resolutions.map(res => {
return new Promise((resolve, reject) => {
ffmpeg(inputPath)
.size(res.size)
.videoBitrate(res.bitrate)
.output(path.join(outputDir, `${res.name}.mp4`))
.on('end', resolve)
.on('error', reject)
.run();
});
}));
// 3. HLS 생성
await new Promise((resolve, reject) => {
ffmpeg(inputPath)
.outputOptions([
'-c:v libx264',
'-c:a aac',
'-hls_time 10',
'-hls_playlist_type vod',
'-hls_segment_filename', path.join(outputDir, 'segment_%03d.ts')
])
.output(path.join(outputDir, 'playlist.m3u8'))
.on('end', resolve)
.on('error', reject)
.run();
});
// 4. 원본 파일 삭제
await fs.unlink(inputPath);
res.json({
success: true,
thumbnail: `/processed/${req.file.filename}/thumbnail.jpg`,
videos: resolutions.map(r => ({
quality: r.name,
url: `/processed/${req.file.filename}/${r.name}.mp4`
})),
hls: `/processed/${req.file.filename}/playlist.m3u8`
});
} catch (error) {
console.error('Processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
프로젝트 2: 동영상 스트리밍 서버
Python + Flask:
from flask import Flask, request, send_file, jsonify
import ffmpeg
import os
import uuid
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
PROCESSED_FOLDER = 'processed'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(PROCESSED_FOLDER, exist_ok=True)
@app.route('/upload', methods=['POST'])
def upload_video():
if 'video' not in request.files:
return jsonify({'error': 'No video file'}), 400
file = request.files['video']
video_id = str(uuid.uuid4())
input_path = os.path.join(UPLOAD_FOLDER, f"{video_id}.mp4")
output_dir = os.path.join(PROCESSED_FOLDER, video_id)
os.makedirs(output_dir, exist_ok=True)
file.save(input_path)
try:
# 1. 동영상 정보 추출
probe = ffmpeg.probe(input_path)
duration = float(probe['format']['duration'])
# 2. 썸네일 생성 (3개)
timestamps = [duration * 0.25, duration * 0.5, duration * 0.75]
for i, ts in enumerate(timestamps):
(
ffmpeg
.input(input_path, ss=ts)
.output(os.path.join(output_dir, f'thumb_{i}.jpg'), vframes=1)
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
# 3. HLS 생성 (다중 해상도)
resolutions = [
{'name': '1080p', 'width': 1920, 'height': 1080, 'bitrate': '5M'},
{'name': '720p', 'width': 1280, 'height': 720, 'bitrate': '3M'},
{'name': '480p', 'width': 854, 'height': 480, 'bitrate': '1.5M'}
]
for res in resolutions:
res_dir = os.path.join(output_dir, res['name'])
os.makedirs(res_dir, exist_ok=True)
(
ffmpeg
.input(input_path)
.output(
os.path.join(res_dir, 'playlist.m3u8'),
**{
'c:v': 'libx264',
'b:v': res['bitrate'],
'c:a': 'aac',
'b:a': '128k',
'vf': f"scale={res['width']}:{res['height']}",
'hls_time': 10,
'hls_playlist_type': 'vod',
'hls_segment_filename': os.path.join(res_dir, 'segment_%03d.ts')
}
)
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
# 4. Master Playlist 생성
master_playlist = "#EXTM3U\n"
for res in resolutions:
bandwidth = int(res['bitrate'].replace('M', ')) * 1000000
master_playlist += f"#EXT-X-STREAM-INF:BANDWIDTH={bandwidth},RESOLUTION={res['width']}x{res['height']}\n"
master_playlist += f"{res['name']}/playlist.m3u8\n"
with open(os.path.join(output_dir, 'master.m3u8'), 'w') as f:
f.write(master_playlist)
# 5. 원본 삭제
os.remove(input_path)
return jsonify({
'video_id': video_id,
'duration': duration,
'thumbnails': [f'/processed/{video_id}/thumb_{i}.jpg' for i in range(3)],
'hls': f'/processed/{video_id}/master.m3u8'
})
except ffmpeg.Error as e:
return jsonify({'error': e.stderr.decode()}), 500
@app.route('/processed/<path:filename>')
def serve_processed(filename):
return send_file(os.path.join(PROCESSED_FOLDER, filename))
if __name__ == '__main__':
app.run(debug=True, port=5000)
프로젝트 3: 일괄 변환 도구
Python CLI:
#!/usr/bin/env python3
import ffmpeg
import argparse
import os
import sys
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
def convert_video(input_path, output_dir, resolution, crf, preset):
"""단일 동영상 변환"""
input_name = Path(input_path).stem
output_path = os.path.join(output_dir, f"{input_name}_{resolution}.mp4")
try:
width, height = resolution.split('x')
(
ffmpeg
.input(input_path)
.filter('scale', width, height)
.output(
output_path,
vcodec='libx264',
preset=preset,
crf=crf,
acodec='aac',
audio_bitrate='192k'
)
.global_args('-loglevel', 'error')
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
return f"✅ {input_name} → {output_path}"
except ffmpeg.Error as e:
return f"❌ {input_name}: {e.stderr.decode()}"
def batch_convert(input_dir, output_dir, resolution='1280x720', crf=23, preset='medium', workers=4):
"""일괄 변환"""
os.makedirs(output_dir, exist_ok=True)
# 동영상 파일 찾기
video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.flv', '.wmv']
video_files = []
for ext in video_extensions:
video_files.extend(Path(input_dir).glob(f'*{ext}'))
video_files.extend(Path(input_dir).glob(f'*{ext.upper()}'))
if not video_files:
print(f"No video files found in {input_dir}")
return
print(f"Found {len(video_files)} videos")
print(f"Resolution: {resolution}, CRF: {crf}, Preset: {preset}")
print(f"Workers: {workers}\n")
# 병렬 처리
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(convert_video, str(f), output_dir, resolution, crf, preset): f
for f in video_files
}
for future in as_completed(futures):
result = future.result()
print(result)
print(f"\n✅ All conversions complete!")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Batch video converter')
parser.add_argument('input_dir', help='Input directory')
parser.add_argument('output_dir', help='Output directory')
parser.add_argument('--resolution', default='1280x720', help='Output resolution (default: 1280x720)')
parser.add_argument('--crf', type=int, default=23, help='CRF value (default: 23)')
parser.add_argument('--preset', default='medium', help='Encoding preset (default: medium)')
parser.add_argument('--workers', type=int, default=4, help='Number of parallel workers (default: 4)')
args = parser.parse_args()
batch_convert(
args.input_dir,
args.output_dir,
resolution=args.resolution,
crf=args.crf,
preset=args.preset,
workers=args.workers
)
사용:
# 기본
python batch_convert.py ./videos ./output
# 720p, CRF 20, slow 프리셋
python batch_convert.py ./videos ./output --resolution 1280x720 --crf 20 --preset slow
# 8개 병렬 처리
python batch_convert.py ./videos ./output --workers 8
12. 고급 필터
복잡한 필터 체인
# 여러 필터 적용
ffmpeg -i input.mp4 \
-vf "scale=1280:720,eq=brightness=0.1:contrast=1.2,unsharp=5:5:1.0" \
output.mp4
# 필터 그래프
ffmpeg -i input.mp4 -i logo.png \
-filter_complex "\
[0:v]scale=1280:720[scaled];\
[scaled][1:v]overlay=W-w-10:10[watermarked];\
[watermarked]drawtext=text='Hello':fontsize=48:x=10:y=10\
" \
output.mp4
화면 분할
# 2개 동영상 나란히 (Side by Side)
ffmpeg -i left.mp4 -i right.mp4 \
-filter_complex "\
[0:v]scale=960:1080[left];\
[1:v]scale=960:1080[right];\
[left][right]hstack\
" \
output.mp4
# 4개 동영상 (2×2 그리드)
ffmpeg -i top_left.mp4 -i top_right.mp4 -i bottom_left.mp4 -i bottom_right.mp4 \
-filter_complex "\
[0:v]scale=960:540[tl];\
[1:v]scale=960:540[tr];\
[2:v]scale=960:540[bl];\
[3:v]scale=960:540[br];\
[tl][tr]hstack[top];\
[bl][br]hstack[bottom];\
[top][bottom]vstack\
" \
output.mp4
슬로우 모션 / 타임랩스
# 슬로우 모션 (0.5배 속도)
ffmpeg -i input.mp4 -filter:v "setpts=2.0*PTS" output.mp4
# 타임랩스 (2배 속도)
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
# 오디오 포함 (속도 동기화)
ffmpeg -i input.mp4 \
-filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \
-map "[v]" -map "[a]" \
output.mp4
트랜지션 효과
# 페이드 인/아웃
ffmpeg -i input.mp4 \
-vf "fade=t=in:st=0:d=3,fade=t=out:st=57:d=3" \
output.mp4
# 크로스페이드 (2개 동영상 전환)
ffmpeg -i video1.mp4 -i video2.mp4 \
-filter_complex "\
[0:v]fade=t=out:st=7:d=1[v0];\
[1:v]fade=t=in:st=0:d=1[v1];\
[v0][v1]concat=n=2:v=1:a=0\
" \
output.mp4
FFmpeg 심화 — libav·필터 그래프·HW·매핑·프로덕션
이 절에서는 명령줄 너머의 내부 데이터 흐름과, 서비스 환경에서 자주 맞닥뜨리는 필터 그래프·하드웨어 파이프라인·스트림 선택을 엔지니어링 관점에서 정리합니다. 앞선 «기본 명령 구조» 절의 파이프라인 설명을 보강하는 목적입니다.
Libav 계열 아키텍처: 디먹서 → 디코더 → 필터 → 인코더 → 먹서
FFmpeg 실행 파일은 libavformat, libavcodec, libavfilter, libavutil, 필요 시 libswscale·libswresample을 조합해 동작합니다. 한 줄의 ffmpeg 명령은 내부적으로 다음과 같은 책임 분리를 따릅니다.
디먹서(Demuxer, libavformat)는 컨테이너를 읽어 스트림 단위 메타데이터와 압축된 패킷(AVPacket)을 생성합니다. MP4의 moov/mdat, MPEG-TS의 PID·PAT/PMT처럼 포맷별 파서가 다르지만, 상위 파이프라인은 동일하게 «스트림 인덱스 → 패킷 큐»로 추상화됩니다.
디코더(Decoder, libavcodec)는 패킷을 디코딩된 프레임(AVFrame) 또는 오디오 샘플 버퍼로 바꿉니다. 여기서 픽셀 포맷(yuv420p, nv12 등)·색역·타임스탬프(PTS/DTS)가 확정되며, 이후 필터·인코더는 이 표현에 맞춰 동작합니다.
필터(Filter graph, libavfilter)는 디코더 출력을 그대로 인코더에 넣지 않고 가공할 때 개입합니다. -vf/-af는 단일 입력·단일 출력 선형 체인이고, -filter_complex는 여러 입력 패드와 분기·합성을 표현합니다. 필터는 프레임 단위로 연결되므로, 해상도·픽셀 포맷·샘플레이트가 어긋나면 FFmpeg가 자동으로 삽입하는 변환 필터(format, fps, aformat 등)가 끼어들 수 있습니다. 예상과 다른 그래프가 만들어졌을 때는 -loglevel debug로 «자동 삽입» 로그를 확인하는 것이 좋습니다.
인코더(Encoder)는 필터 출력 프레임을 다시 압축 패킷으로 만듭니다. 소프트웨어 인코더(libx264 등)와 하드웨어 인코더(NVENC/VAAPI 등)는 입력 버퍼의 메모리 종류(시스템 메모리 vs GPU 서페이스)에서부터 차이가 납니다.
먹서(Muxer)는 패킷을 타임스탬프 순으로 인터리브하고, 컨테이너별 인덱스·헤더를 기록합니다. 웹 배포에서 자주 쓰는 -movflags +faststart는 먹서 단계에서 moov 위치를 조정하는 전형적인 예입니다.
비트스트림 필터(Bitstream filter)는 «디코딩 없이」 바이트열을 변형할 때 씁니다. 예를 들어 MP4에 들어 있는 H.264/HEVC를 Annex B 형태로 꺼내 TS에 실을 때 h264_mp4toannexb, hevc_mp4toannexb가 등장합니다. 패킷 경계와 NAL 구조를 이해하고 있을 때만 안전하게 적용할 수 있습니다.
필터 그래프 처리: 패드, 스케줄링, 병렬
-filter_complex 문자열은 내부적으로 방향 그래프(DAG)로 컴파일됩니다. [0:v]scale=1280:720[v1]처럼 레이블된 패드는 노드의 입·출력 핀에 해당합니다. split, overlay, xfade, concat처럼 다입력·다출력 필터를 쓸 때는 패드 연결을 빠뜨리지 않았는지가 오류의 대부분입니다.
오디오·비디오 동시 처리에서는 [0:v]…[v], [0:a]…[a]를 만든 뒤 -map "[v]" -map "[a]"로 출력 스트림에 묶는 패턴이 표준적입니다. 단순 -vf만 쓰면 비디오 체인만 바뀌고 오디오는 입력 그대로라 싱크가 어긋난 것처럼 보이는 경우가 있으므로, 속도 변경·트림을 할 때는 오디오 atempo·asetpts까지 한 그래프에서 설계하는 것이 안전합니다.
-filter_threads: 일부 필터는 내부적으로 스레드를 쓰며, 입력 해상도가 크면 CPU 병목이 됩니다. 반대로 과도한 스레드는 캐시 미스를 유발할 수 있어 실측으로 조정하는 편이 좋습니다.
하드웨어와 필터의 접점: CPU 메모리에 있는 프레임을 GPU 인코더로 보내려면 hwupload/hwupload_cuda 등으로 업로드해야 하고, GPU 디코더가 만든 서페이스를 CPU 필터(scale 등)로 내리려면 hwdownload가 필요합니다. «전 구간 GPU»를 기대하고도 중간에 CPU scale이 끼면 다운로드·재업로드 비용으로 이득이 사라지는 경우가 많습니다.
# 예: CUDA 디코더 출력을 유지한 채 GPU 쪽에서 크기 조정 후 NVENC로 인코딩
# (빌드·드라이버에 따라 사용 가능한 필터 이름이 다를 수 있음)
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 \
-vf "scale_cuda=1280:720" \
-c:v h264_nvenc -preset p4 -cq 23 \
-c:a copy output.mp4
하드웨어 가속 API: VAAPI(리눅스)와 NVENC(NVIDIA)
공통 원칙: 하드웨어 가속의 이득은 «디코드·처리·인코드가 같은 메모리 공간에서 이어질 때» 극대화됩니다. 디코드만 GPU, 필터는 CPU, 인코드만 GPU처럼 섞이면 PCIe 대역·포맷 변환이 병목이 됩니다.
VAAPI (Video Acceleration API, 주로 Linux·Intel/AMD)
Intel·AMD GPU가 있는 리눅스 서버에서 흔히 쓰입니다. 디바이스 노드는 보통 /dev/dri/renderD128이며, 컨테이너·클라우드 VM에서는 render 노드 노출 여부가 먼저 확인되어야 합니다.
# 사용 가능한 VAAPI 디바이스 확인 (환경에 따라 패키지 필요)
vainfo
# VAAPI로 H.264 인코딩 (예시 — 드라이버·FFmpeg 빌드에 따라 옵션명이 다를 수 있음)
ffmpeg -init_hw_device vaapi=va:/dev/dri/renderD128 -filter_hw_device va \
-hwaccel vaapi -hwaccel_output_format vaapi -i input.mp4 \
-vf 'format=nv12|vaapi,hwupload' -c:v h264_vaapi -qp 23 \
-c:a copy output.mp4
실무 체크포인트: Wayland/X11과 무관하게 헤드리스 서버에서 VAAPI를 쓰려면 권한(video 그룹)·i965/iris/mesa 드라이버 버전·FFmpeg의 --enable-vaapi 빌드가 맞아야 합니다. 실패 시 -loglevel verbose에서 vaapi 초기화 오류 메시지를 우선 확인합니다.
NVENC (NVIDIA)
NVIDIA GPU의 전용 인코더입니다. 디코딩은 -hwaccel cuda 또는 구형 파이프라인의 cuvid(빌드에 포함된 경우)를 쓰고, 인코딩은 h264_nvenc/hevc_nvenc/av1_nvenc 등으로 지정합니다. 품질 모드는 세대별로 -cq, -rc vbr, -preset p1~p7 등 표기가 나뉘어 와서 드라이버 릴리스 노트와 ffmpeg -h encoder=h264_nvenc 출력을 기준으로 잡는 것이 안전합니다.
# NVENC VBR + 품질 상한 예시 (환경에 따라 인식되지 않는 옵션은 제거)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc -preset p4 -rc vbr -cq 23 -b:v 5M -maxrate 8M -bufsize 16M \
-c:a aac -b:a 192k output.mp4
NVENC와 VAAPI 선택: 데이터센터 GPU가 있으면 NVENC가 처리량에서 유리한 경우가 많고, CPU 내장 GPU·오픈소스 스택 위주면 VAAPI가 자연스럽습니다. 동시 세션 수는 NVENC 세대별 인코더 세션 제한(제품·드라이버에 따라 상이)이 병목이 되므로, 트랜스코딩 팜 설계 시 세션 수·지연·전력을 함께 적습니다.
스트림 매핑·메타데이터·챕터
-map은 «어떤 입력의 어떤 스트림이 출력의 몇 번째 트랙이 될지」를 명시합니다. 다국어 오디오·자막·여러 각도 영상이 있는 방송·블루레이 계열 소스에서 필수입니다. -map 0:v:0 -map 0:a:2처럼 인덱스는 0부터이며, 단순히 -c copy만 쓰면 «첫 번째 비디오+첫 오디오»가 기본이라 의도와 다르게 떨어질 수 있습니다.
# 모든 스트림을 최대한 보존해 복사 (포맷이 허용할 때)
ffmpeg -i input.mkv -map 0 -c copy output.mkv
# 메타데이터는 컨테이너·스트림 단위로 복사 또는 재작성
ffmpeg -i input.mp4 -map_metadata 0 -map_chapters 0 -c copy with_meta.mp4
ffmpeg -i input.mp4 -c copy \
-metadata title="제목" -metadata author="작성자" \
-metadata:s:a:0 language=kor \
tagged.mp4
-disposition은 트랙의 기본 역할(강제 자막·청각 장애인용 보조 등)을 지정할 때 씁니다. 플레이어·스트리밍 규격이 이 값을 해석하므로, 배포 전 타깃 플랫폼 요구사항과 맞추는 것이 좋습니다.
챕터: ffprobe -show_chapters로 확인한 뒤, -map_chapters로 복사하거나 외부 파일에서 주입할 수 있습니다. 전자는 빠르고, 후자는 편집 도구와의 워크플로에 맞춰 선택합니다.
ffprobe -v error -show_chapters -show_entries format_tags=title -of json input.mp4
프로덕션에서 통하는 FFmpeg 패턴
실패를 줄이는 습관: 배치·큐 워커에서는 -nostdin으로 표준 입력이 우연히 소비되는 상황을 막고, -hide_banner -loglevel error(필요 시 -stats)로 로그 노이즈를 제어합니다. 재현 가능한 로그를 남기려면 FFmpeg 버전 문자열·전체 명령줄·입력 파일 해시를 함께 저장합니다.
진행률·모니터링: -progress url 또는 파이프로 키·값 형태의 진행 정보를 뽑아 워커가 파싱하기 쉽게 합니다. 대규모 클러스터에서는 이 값을 메트릭으로 넘겨 스톨·큐 적체를 감시합니다.
리소스: HLS·DASH 세그먼트를 만드는 워커는 동시 오픈 파일 수와 디스크 fsync 정책에 걸리기 쉽습니다. 로컬 SSD·tmpfs·세그먼트별 디렉터리 샤딩이 자주 쓰입니다.
입력 검증: 인코딩 전 ffprobe로 코덱·해상도·HDR 메타·가변 프레임레이트 여부를 확인하고, 이상이 있으면 전처리 필터(fps 고정, 색공간 태그 보정)를 명시적으로 넣습니다. «한 번에 긴 명령»보다 프로브 → 조건부 인코더 선택이 운영에 유리합니다.
하드웨어 파이프라인 검증: GPU 인코딩은 동일 설정이라도 드라이버 버전에 따라 화질·파일 크기가 달라질 수 있습니다. 릴리스마다 소수의 골든 샘플에 대해 VMAF·SSIM 또는 육안 검사를 자동화해 두면 회귀를 빨리 잡을 수 있습니다.
보안·견고성: 사용자가 업로드한 임의 파일명·임의 경로를 그대로 쉘에 넣지 말고, 인자 배열으로 FFmpeg를 호출합니다. 서버에서는 입력 길이·해상도 상한, 무한 필터 체인 같은 DoS성 입력에 대한 제한을 두는 것이 좋습니다.
# 배치 워커에서 흔한 «조용한» 래퍼 예시
ffmpeg -nostdin -hide_banner -loglevel error -stats \
-i "$INPUT" -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 192k \
-movflags +faststart "$OUTPUT"
이 절의 내용은 아래 «성능 최적화» 절의 NVENC·QSV·AMF 실전 예제와 겹치지 않게 API·파이프라인 관점을 보강한 것입니다. 운영 환경(드라이버, FFmpeg 빌드 플래그)마다 최적 옵션이 달라지므로, 배포 전 ffmpeg -version과 -h encoder=… 출력을 기준선으로 삼으면 됩니다.
13. 성능 최적화
하드웨어 가속 - GPU의 힘 활용하기
왜 하드웨어 가속이 필요한가?
CPU 인코딩 (libx264):
- 범용 프로세서로 소프트웨어 연산
- 높은 품질, 최적 압축률
- 속도: 1080p 1분 영상 → 30-60초 소요
GPU 인코딩 (h264_nvenc):
- 전용 하드웨어 인코더 칩 사용
- 병렬 처리 (수천 개 코어)
- 속도: 1080p 1분 영상 → 5-10초 소요
- 품질: CPU 대비 약간 떨어짐 (CRF +2~3 수준)
- 파일 크기: 20-30% 더 큼
언제 GPU를 쓸까?
✅ 실시간 스트리밍 (Twitch, YouTube Live)
✅ 대량 파일 변환 (시간이 중요)
✅ 웹캠 녹화 (CPU 부하 최소화)
❌ 최종 아카이브 (최고 품질 필요)
❌ 저사양 서버 (GPU 없음)
NVIDIA GPU (NVENC) - 가장 널리 사용:
# GPU 사용 가능 여부 확인
ffmpeg -hwaccels
# 출력에 cuda가 있으면 사용 가능
ffmpeg -encoders | grep nvenc
# h264_nvenc: H.264 NVENC 인코더
# hevc_nvenc: H.265 NVENC 인코더
# H.264 GPU 인코딩
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc -preset fast -b:v 5M \
output.mp4
# 옵션 상세:
# -hwaccel cuda: CUDA 디코딩 가속 (디코딩도 GPU 사용)
# -c:v h264_nvenc: NVIDIA H.264 인코더
# -preset fast: GPU 프리셋
# - default: 균형 (기본)
# - fast: 빠름
# - slow: 느림, 품질 향상
# - lossless: 무손실 (파일 매우 큼)
# H.265 GPU 인코딩 (4K에 적합)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v hevc_nvenc -preset fast -b:v 5M \
output.mp4
# 품질 설정 (CQP - Constant Quantization Parameter)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc -preset slow -cq 23 \
output.mp4
# -cq: GPU의 CRF 상당 (값이 낮을수록 고품질)
# 범위: 0-51 (CPU의 -crf와 유사)
# 2-Pass GPU 인코딩 (최고 품질)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc -preset slow -b:v 5M -multipass fullres \
output.mp4
# -multipass fullres: GPU 2-pass (품질 향상)
Intel GPU (QSV - Quick Sync Video):
# QSV 지원 확인
ffmpeg -encoders | grep qsv
# h264_qsv: Intel H.264 인코더
# hevc_qsv: Intel H.265 인코더
# H.264 QSV 인코딩
ffmpeg -hwaccel qsv -i input.mp4 \
-c:v h264_qsv -preset fast -global_quality 23 \
output.mp4
# -global_quality: QSV의 품질 파라미터
# 값이 낮을수록 고품질 (CRF와 유사)
# 범위: 1-51
# H.265 QSV 인코딩
ffmpeg -hwaccel qsv -i input.mp4 \
-c:v hevc_qsv -preset veryslow -global_quality 23 \
output.mp4
# 해상도 변경 (GPU에서 스케일링)
ffmpeg -hwaccel qsv -i input.mp4 \
-vf scale_qsv=1280:720 -c:v h264_qsv -global_quality 23 \
output.mp4
# scale_qsv: GPU 스케일링 (CPU 스케일보다 빠름)
AMD GPU (AMF - Advanced Media Framework):
# AMF 지원 확인
ffmpeg -encoders | grep amf
# h264_amf: AMD H.264 인코더
# hevc_amf: AMD H.265 인코더
# H.264 AMF 인코딩
ffmpeg -i input.mp4 \
-c:v h264_amf -quality balanced -rc cqp -qp 23 \
output.mp4
# -quality: 인코딩 품질 모드
# - speed: 빠름, 낮은 품질
# - balanced: 균형 (권장)
# - quality: 느림, 높은 품질
# -rc: 레이트 컨트롤 모드
# - cqp: Constant QP (CRF와 유사)
# - vbr: Variable Bitrate
# - cbr: Constant Bitrate (스트리밍용)
# -qp: Quantization Parameter (0-51)
# H.265 AMF 인코딩
ffmpeg -i input.mp4 \
-c:v hevc_amf -quality balanced -rc cqp -qp 23 \
output.mp4
macOS (VideoToolbox) - Apple Silicon/Intel Mac:
# VideoToolbox 지원 확인
ffmpeg -encoders | grep videotoolbox
# h264_videotoolbox: Apple H.264 인코더
# hevc_videotoolbox: Apple H.265 인코더
# H.264 VideoToolbox 인코딩
ffmpeg -i input.mp4 \
-c:v h264_videotoolbox -b:v 5M \
output.mp4
# H.265 VideoToolbox 인코딩 (Apple Silicon 권장)
ffmpeg -i input.mp4 \
-c:v hevc_videotoolbox -b:v 5M \
output.mp4
# 품질 설정 (q:v)
ffmpeg -i input.mp4 \
-c:v h264_videotoolbox -q:v 65 \
output.mp4
# q:v 범위: 0-100 (높을수록 고품질)
# 권장: 60-75
하드웨어 가속 성능 비교:
테스트 환경: 1080p 60fps 1분 영상
libx264 (CPU):
preset ultrafast: 20초 (품질 낮음)
preset medium: 45초 (균형)
preset slow: 90초 (고품질)
h264_nvenc (NVIDIA GPU):
preset fast: 8초 (품질 중)
preset slow: 12초 (품질 상)
h264_qsv (Intel GPU):
preset fast: 15초 (품질 중)
preset slow: 20초 (품질 상)
h264_amf (AMD GPU):
quality speed: 10초 (품질 하)
quality balanced: 15초 (품질 중)
결론:
- GPU는 CPU 대비 3-10배 빠름
- 품질은 CPU가 약간 우수
- 실시간 용도는 GPU 필수
하드웨어 가속 주의사항:
# ❌ 잘못된 사용: 디코딩만 GPU, 인코딩은 CPU
ffmpeg -hwaccel cuda -i input.mp4 -c:v libx264 output.mp4
# 효과: 거의 없음 (디코딩은 빠른데 인코딩이 느림)
# ✅ 올바른 사용: 디코딩 + 인코딩 모두 GPU
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc output.mp4
# 효과: 최대 성능 (전체 파이프라인 GPU 사용)
# GPU 메모리 부족 에러
ffmpeg -hwaccel cuda -i 4k_video.mp4 -c:v h264_nvenc output.mp4
# Error: out of memory
# 해결: 해상도 낮추기 또는 CPU 사용
# GPU 드라이버 오래됨
# 해결: 최신 드라이버 업데이트
# NVIDIA: https://www.nvidia.com/drivers
# Intel: https://www.intel.com/content/www/us/en/download-center/home.html
하드웨어 인코더 선택 가이드:
어떤 GPU 인코더를 쓸까?
NVIDIA (h264_nvenc):
✅ 가장 빠름 (Turing, Ampere 아키텍처)
✅ 최고 품질 (GPU 인코더 중)
✅ 다양한 옵션
❌ NVIDIA GPU 필요 (GTX 1000 시리즈 이상)
Intel (h264_qsv):
✅ 대부분의 Intel CPU에 내장 (7세대 이상)
✅ 추가 비용 없음
✅ 낮은 전력 소비
❌ NVIDIA보다 품질 약간 떨어짐
AMD (h264_amf):
✅ AMD GPU에서 작동
✅ 괜찮은 성능
❌ 옵션이 제한적
❌ 드라이버 호환성 문제 가능
Apple (h264_videotoolbox):
✅ 모든 Mac에서 작동
✅ Apple Silicon에서 매우 빠름
✅ 전력 효율 최고
❌ macOS 전용
❌ 옵션이 제한적
권장:
- Windows/Linux + NVIDIA → h264_nvenc
- Windows/Linux + Intel → h264_qsv
- Windows + AMD → h264_amf
- macOS → h264_videotoolbox
# NVENC VBR 예시 (같은 절 상단 NVENC 예제와 옵션 계열이 같음)
ffmpeg -hwaccel cuda -i input.mp4 \
-c:v h264_nvenc -preset slow -rc vbr -cq 23 \
output.mp4
멀티스레딩
# 스레드 수 지정
ffmpeg -i input.mp4 -threads 8 -c:v libx264 -crf 23 output.mp4
# 자동 (CPU 코어 수)
ffmpeg -i input.mp4 -threads 0 -c:v libx264 -crf 23 output.mp4
# 타일 인코딩 (AV1)
ffmpeg -i input.mp4 -c:v libaom-av1 -tiles 4x4 -threads 16 output.mp4
메모리 최적화
# 버퍼 크기 조절
ffmpeg -i input.mp4 \
-bufsize 10M -maxrate 5M \
-c:v libx264 -crf 23 \
output.mp4
# 스트리밍 최적화 (faststart)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart output.mp4
# moov atom을 파일 앞으로 이동 (웹 스트리밍 필수)
14. 실전 명령어 모음
유튜브 최적화
# 유튜브 권장 설정
ffmpeg -i input.mp4 \
-c:v libx264 -preset slow -crf 18 \
-c:a aac -b:a 192k -ar 48000 \
-pix_fmt yuv420p \
-movflags +faststart \
youtube.mp4
# 4K 유튜브
ffmpeg -i input.mp4 \
-c:v libx264 -preset slow -crf 18 \
-vf scale=3840:2160 \
-c:a aac -b:a 192k \
-pix_fmt yuv420p \
-movflags +faststart \
youtube_4k.mp4
Instagram 최적화
# Instagram 피드 (1:1)
ffmpeg -i input.mp4 \
-vf "scale=1080:1080:force_original_aspect_ratio=decrease,pad=1080:1080:(ow-iw)/2:(oh-ih)/2" \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-t 60 \
instagram_feed.mp4
# Instagram 스토리 (9:16)
ffmpeg -i input.mp4 \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2" \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-t 15 \
instagram_story.mp4
웹 최적화
# 웹용 MP4 (빠른 시작)
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
web.mp4
# WebM (VP9)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 -b:v 2M \
-c:a libopus -b:a 128k \
web.webm
# 다중 포맷 (호환성)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart web.mp4
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 2M web.webm
15. 라이브 스트리밍
RTMP 서버 설정
Nginx-RTMP:
# nginx.conf
rtmp {
server {
listen 1935;
application live {
live on;
record off;
# HLS 생성
hls on;
hls_path /tmp/hls;
hls_fragment 3;
hls_playlist_length 60;
}
}
}
http {
server {
listen 8080;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}
스트리밍 시작:
# 파일 스트리밍
ffmpeg -re -i video.mp4 \
-c:v libx264 -preset veryfast -b:v 3M -maxrate 3M -bufsize 6M \
-pix_fmt yuv420p -g 60 \
-c:a aac -b:a 128k -ar 44100 \
-f flv rtmp://localhost/live/stream
# 웹캠 스트리밍
ffmpeg -f avfoundation -i "0:0" \
-c:v libx264 -preset veryfast -b:v 2M \
-c:a aac -b:a 128k \
-f flv rtmp://localhost/live/webcam
# 화면 녹화 + 스트리밍 (macOS)
ffmpeg -f avfoundation -capture_cursor 1 -i "1:0" \
-c:v libx264 -preset veryfast -b:v 3M \
-c:a aac -b:a 128k \
-f flv rtmp://localhost/live/screen
재스트리밍 (Restreaming)
# RTMP → HLS
ffmpeg -i rtmp://source/live/stream \
-c:v copy -c:a copy \
-hls_time 3 -hls_list_size 10 \
-hls_flags delete_segments \
output.m3u8
# 다중 플랫폼 동시 스트리밍
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3M -c:a aac -b:a 128k \
-f flv rtmp://a.rtmp.youtube.com/live2/YOUTUBE_KEY \
-c:v libx264 -preset veryfast -b:v 3M -c:a aac -b:a 128k \
-f flv rtmp://live.twitch.tv/app/TWITCH_KEY
16. 트러블슈팅
일반적인 문제
1) “Unknown encoder ‘libx264’”
# 원인: FFmpeg이 libx264 없이 컴파일됨
# 해결: 전체 버전 재설치
# macOS
brew reinstall ffmpeg
# Ubuntu (full 버전)
sudo apt install ffmpeg
2) “Conversion failed”
# 상세 로그 확인
ffmpeg -i input.mp4 -loglevel debug output.mp4
# 코덱 지원 확인
ffmpeg -codecs | grep h264
ffmpeg -encoders | grep h264
3) “Invalid data found when processing input”
# 원인: 손상된 파일
# 해결: 에러 무시 옵션
ffmpeg -err_detect ignore_err -i input.mp4 -c copy output.mp4
4) 오디오/비디오 동기화 문제
# 오디오 지연 (0.5초)
ffmpeg -i input.mp4 -itsoffset 0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4
# 오디오 앞당기기
ffmpeg -i input.mp4 -itsoffset -0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4
성능 문제
# 1. 하드웨어 가속 사용
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc output.mp4
# 2. 빠른 프리셋
ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast output.mp4
# 3. 재인코딩 없이 복사
ffmpeg -i input.mp4 -c copy output.mp4
# 4. 낮은 해상도
ffmpeg -i input.mp4 -vf scale=640:360 output.mp4
17. 실전 스크립트
자동 인코딩 워커
Python (Celery):
from celery import Celery
import ffmpeg
import os
app = Celery('video_worker', broker='redis://localhost:6379/0')
@app.task
def encode_video(input_path, output_path, resolution='1280x720', crf=23):
"""비디오 인코딩 작업"""
try:
width, height = resolution.split('x')
(
ffmpeg
.input(input_path)
.filter('scale', width, height)
.output(
output_path,
vcodec='libx264',
preset='slow',
crf=crf,
acodec='aac',
audio_bitrate='192k',
movflags='+faststart'
)
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
return {'status': 'success', 'output': output_path}
except ffmpeg.Error as e:
return {'status': 'error', 'message': e.stderr.decode()}
@app.task
def create_thumbnails(input_path, output_dir, count=3):
"""썸네일 생성 작업"""
try:
probe = ffmpeg.probe(input_path)
duration = float(probe['format']['duration'])
os.makedirs(output_dir, exist_ok=True)
thumbnails = []
for i in range(count):
timestamp = duration * (i + 1) / (count + 1)
output_path = os.path.join(output_dir, f'thumb_{i}.jpg')
(
ffmpeg
.input(input_path, ss=timestamp)
.output(output_path, vframes=1, q=2)
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
thumbnails.append(output_path)
return {'status': 'success', 'thumbnails': thumbnails}
except ffmpeg.Error as e:
return {'status': 'error', 'message': e.stderr.decode()}
# 사용
if __name__ == '__main__':
# 작업 큐에 추가
encode_video.delay('input.mp4', 'output.mp4', resolution='1920x1080', crf=20)
create_thumbnails.delay('input.mp4', './thumbnails', count=5)
진행률 웹소켓
Node.js + Socket.io:
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const ffmpeg = require('fluent-ffmpeg');
const multer = require('multer');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const upload = multer({ dest: 'uploads/' });
app.post('/convert', upload.single('video'), (req, res) => {
const inputPath = req.file.path;
const outputPath = `processed/${req.file.filename}.mp4`;
const socketId = req.body.socketId;
ffmpeg(inputPath)
.output(outputPath)
.videoCodec('libx264')
.audioCodec('aac')
.on('start', (cmd) => {
console.log('Started:', cmd);
})
.on('progress', (progress) => {
// 클라이언트에게 진행률 전송
io.to(socketId).emit('progress', {
percent: progress.percent,
currentTime: progress.timemark,
fps: progress.currentFps
});
})
.on('end', () => {
io.to(socketId).emit('complete', {
url: `/processed/${req.file.filename}.mp4`
});
})
.on('error', (err) => {
io.to(socketId).emit('error', {
message: err.message
});
})
.run();
res.json({ message: 'Processing started' });
});
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
클라이언트 (HTML):
<!DOCTYPE html>
<html>
<head>
<title>Video Converter</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Video Converter</h1>
<input type="file" id="videoFile" accept="video/*">
<button onclick="uploadVideo()">Upload & Convert</button>
<div id="progress" style="display: none;">
<p>Progress: <span id="percent">0</span>%</p>
<progress id="progressBar" value="0" max="100"></progress>
</div>
<div id="result" style="display: none;">
<p>✅ Conversion complete!</p>
<video id="outputVideo" controls width="640"></video>
</div>
<script>
const socket = io();
function uploadVideo() {
const fileInput = document.getElementById('videoFile');
const file = fileInput.files[0];
if (!file) {
alert('Please select a video file');
return;
}
const formData = new FormData();
formData.append('video', file);
formData.append('socketId', socket.id);
document.getElementById('progress').style.display = 'block';
fetch('/convert', {
method: 'POST',
body: formData
});
}
socket.on('progress', (data) => {
document.getElementById('percent').textContent = data.percent.toFixed(1);
document.getElementById('progressBar').value = data.percent;
});
socket.on('complete', (data) => {
document.getElementById('progress').style.display = 'none';
document.getElementById('result').style.display = 'block';
document.getElementById('outputVideo').src = data.url;
});
socket.on('error', (data) => {
alert('Error: ' + data.message);
});
</script>
</body>
</html>
18. 고급 활용
자막 처리
# 자막 추가 (하드서브)
ffmpeg -i video.mp4 -vf subtitles=subtitle.srt output.mp4
# 자막 추가 (소프트서브)
ffmpeg -i video.mp4 -i subtitle.srt -c copy -c:s mov_text output.mp4
# 자막 추출
ffmpeg -i video.mp4 -map 0:s:0 subtitle.srt
# 자막 스타일 변경
ffmpeg -i video.mp4 \
-vf "subtitles=subtitle.srt:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFF'" \
output.mp4
메타데이터 편집
# 메타데이터 확인
ffprobe -show_format input.mp4
# 메타데이터 추가
ffmpeg -i input.mp4 -c copy \
-metadata title="My Video" \
-metadata author="Alice" \
-metadata comment="Created with FFmpeg" \
output.mp4
# 메타데이터 제거
ffmpeg -i input.mp4 -c copy -map_metadata -1 output.mp4
화면 녹화
# macOS (화면 + 오디오)
ffmpeg -f avfoundation -capture_cursor 1 -i "1:0" \
-c:v libx264 -preset ultrafast -crf 23 \
-c:a aac -b:a 128k \
screen_recording.mp4
# Linux (X11)
ffmpeg -f x11grab -s 1920x1080 -i :0.0 \
-c:v libx264 -preset ultrafast -crf 23 \
screen_recording.mp4
# Windows (GDI)
ffmpeg -f gdigrab -i desktop \
-c:v libx264 -preset ultrafast -crf 23 \
screen_recording.mp4
19. 프로덕션 팁
에러 처리
import ffmpeg
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_convert(input_path, output_path, max_retries=3):
"""재시도 로직 포함 변환"""
for attempt in range(max_retries):
try:
logger.info(f"Attempt {attempt + 1}/{max_retries}")
(
ffmpeg
.input(input_path)
.output(output_path, vcodec='libx264', crf=23)
.global_args('-loglevel', 'error')
.overwrite_output()
.run(capture_stdout=True, capture_stderr=True)
)
logger.info("✅ Success!")
return True
except ffmpeg.Error as e:
logger.error(f"❌ Attempt {attempt + 1} failed: {e.stderr.decode()}")
if attempt == max_retries - 1:
logger.error("All attempts failed")
raise
return False
큐 시스템
import ffmpeg
import redis
import json
import time
from threading import Thread
class VideoQueue:
def __init__(self):
self.redis = redis.Redis(host='localhost', port=6379, db=0)
self.queue_key = 'video:queue'
self.processing_key = 'video:processing'
def add_job(self, input_path, output_path, options=None):
"""작업 추가"""
job = {
'input': input_path,
'output': output_path,
'options': options or {},
'status': 'queued',
'created_at': time.time()
}
job_id = f"job:{time.time()}"
self.redis.lpush(self.queue_key, json.dumps(job))
return job_id
def process_job(self, job_data):
"""작업 처리"""
job = json.loads(job_data)
try:
# FFmpeg 실행
stream = ffmpeg.input(job['input'])
# 옵션 적용
if 'scale' in job['options']:
stream = stream.filter('scale', *job['options']['scale'].split('x'))
stream.output(
job['output'],
vcodec='libx264',
crf=job['options'].get('crf', 23),
preset=job['options'].get('preset', 'medium')
).overwrite_output().run()
print(f"✅ Job complete: {job['output']}")
except ffmpeg.Error as e:
print(f"❌ Job failed: {e.stderr.decode()}")
def worker(self):
"""워커 스레드"""
print("Worker started")
while True:
# 큐에서 작업 가져오기
job_data = self.redis.brpop(self.queue_key, timeout=5)
if job_data:
_, job_json = job_data
self.redis.lpush(self.processing_key, job_json)
self.process_job(job_json)
self.redis.lrem(self.processing_key, 1, job_json)
def start_workers(self, num_workers=4):
"""워커 시작"""
threads = []
for i in range(num_workers):
t = Thread(target=self.worker, daemon=True)
t.start()
threads.append(t)
return threads
# 사용
queue = VideoQueue()
# 작업 추가
queue.add_job('input1.mp4', 'output1.mp4', {'scale': '1280x720', 'crf': 20})
queue.add_job('input2.mp4', 'output2.mp4', {'scale': '1920x1080', 'crf': 18})
# 워커 시작 (4개)
workers = queue.start_workers(num_workers=4)
# 메인 스레드 유지
for worker in workers:
worker.join()
20. 성능 벤치마크
인코딩 속도 비교
# 테스트 스크립트
#!/bin/bash
INPUT="test_4k.mp4"
echo "=== FFmpeg Encoding Benchmark ==="
echo
# H.264 프리셋 비교
for preset in ultrafast superfast veryfast faster fast medium slow slower veryslow; do
echo "Testing preset: $preset"
start=$(date +%s)
ffmpeg -i "$INPUT" -c:v libx264 -preset "$preset" -crf 23 -an -f null - 2>&1 | grep "frame="
end=$(date +%s)
duration=$((end - start))
echo "Time: ${duration}s"
echo
done
# 코덱 비교
for codec in libx264 libx265 libvpx-vp9; do
echo "Testing codec: $codec"
start=$(date +%s)
ffmpeg -i "$INPUT" -c:v "$codec" -b:v 5M -an -f null - 2>&1 | grep "frame="
end=$(date +%s)
duration=$((end - start))
echo "Time: ${duration}s"
echo
done
결과 예시 (4K 1분 영상):
프리셋 비교 (H.264):
ultrafast: 15초 (파일: 50MB)
veryfast: 25초 (파일: 40MB)
medium: 45초 (파일: 30MB)
slow: 90초 (파일: 25MB)
veryslow: 180초 (파일: 23MB)
코덱 비교 (medium 프리셋):
H.264: 45초 (파일: 30MB)
H.265: 120초 (파일: 15MB)
VP9: 180초 (파일: 18MB)
21. 유용한 원라이너
# 모든 MP4를 720p로 일괄 변환
for f in *.mp4; do ffmpeg -i "$f" -vf scale=1280:720 -c:v libx264 -crf 23 "720p_$f"; done
# 모든 동영상에서 오디오 추출
for f in *.mp4; do ffmpeg -i "$f" -vn -c:a libmp3lame -b:a 192k "${f%.mp4}.mp3"; done
# 모든 동영상의 첫 프레임 추출
for f in *.mp4; do ffmpeg -i "$f" -vframes 1 "${f%.mp4}.jpg"; done
# 모든 이미지를 동영상으로 (슬라이드쇼)
ffmpeg -framerate 1 -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p slideshow.mp4
# 오디오 파일들을 하나로 병합
ffmpeg -i "concat:audio1.mp3|audio2.mp3|audio3.mp3" -c copy output.mp3
# 동영상 정보 CSV로 저장
for f in *.mp4; do echo "$f,$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$f")"; done > videos.csv
22. Docker로 FFmpeg 사용
Dockerfile
FROM ubuntu:22.04
# FFmpeg 설치
RUN apt-get update && \
apt-get install -y ffmpeg && \
rm -rf /var/lib/apt/lists/*
WORKDIR /data
ENTRYPOINT [ffmpeg]
빌드 및 사용:
# 빌드
docker build -t my-ffmpeg .
# 사용
docker run --rm -v $(pwd):/data my-ffmpeg \
-i /data/input.mp4 \
-c:v libx264 -crf 23 \
/data/output.mp4
# 별칭 생성
alias dffmpeg='docker run --rm -v $(pwd):/data my-ffmpeg'
# 사용
dffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
트러블슈팅
실무에서 자주 마주치는 문제들과 해결 방법입니다.
문제 1: “height not divisible by 2” 에러
증상:
[libx264 @ 0x...] height not divisible by 2 (1281x721)
원인: H.264 코덱은 짝수 픽셀을 요구합니다. 해결:
# ❌ 잘못된 예
ffmpeg -i input.mp4 -vf scale=1281:721 output.mp4
# ✅ 올바른 예
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# -2는 2의 배수로 자동 조정
문제 2: 오디오/비디오 싱크 안 맞음
증상: 영상과 음성이 어긋남 원인: 프레임레이트 변환, 잘못된 타임스탬프 해결:
# 방법 1: 오디오 타임스탬프 재계산
ffmpeg -i input.mp4 -c:v copy -c:a copy -async 1 output.mp4
# 방법 2: 오디오/비디오 재동기화
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -vsync 1 -async 1 output.mp4
# 방법 3: 오디오 지연 수정 (0.5초 지연)
ffmpeg -i input.mp4 -itsoffset 0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4
문제 3: “moov atom not found” 에러
증상: 웹 브라우저에서 재생 안 됨, 다운로드 완료 후에만 재생 원인: MP4의 메타데이터(moov atom)가 파일 끝에 위치 해결:
# 인코딩 시 faststart 옵션 추가
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart output.mp4
# 이미 인코딩된 파일 수정 (빠름)
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
문제 4: 인코딩이 너무 느림
해결 방법:
# 1. 프리셋을 빠르게
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -crf 23 output.mp4
# 2. 해상도 낮추기
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 output.mp4
# 3. GPU 인코더 사용 (NVIDIA)
ffmpeg -i input.mp4 -c:v h264_nvenc -preset fast -crf 23 output.mp4
# 4. GPU 인코더 사용 (Intel QSV)
ffmpeg -i input.mp4 -c:v h264_qsv -preset fast -global_quality 23 output.mp4
# 5. GPU 인코더 사용 (AMD)
ffmpeg -i input.mp4 -c:v h264_amf -quality balanced -rc cqp -qp 23 output.mp4
# 6. 병렬 처리 (여러 파일)
parallel -j 4 ffmpeg -i {} -c:v libx264 -crf 23 {.}_output.mp4 ::: *.mp4
문제 5: 파일 크기가 너무 큼
해결:
# 1. CRF 값 높이기 (품질 낮추기)
ffmpeg -i input.mp4 -c:v libx264 -crf 28 output.mp4
# 2. 해상도 낮추기
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 output.mp4
# 3. 프레임레이트 낮추기
ffmpeg -i input.mp4 -r 24 -c:v libx264 -crf 23 output.mp4
# 4. H.265 사용 (더 작은 파일)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4
# 5. 2-Pass 인코딩으로 목표 크기 맞추기
# 목표: 100MB, 10분 영상 = 100MB / 600초 = 1.33Mbps
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k output.mp4
문제 6: “Conversion failed” 또는 “Invalid data found”
원인: 손상된 파일, 지원하지 않는 코덱 해결:
# 1. 파일 정보 확인
ffprobe input.mp4
# 2. 에러 무시하고 강제 변환
ffmpeg -err_detect ignore_err -i input.mp4 -c:v libx264 -crf 23 output.mp4
# 3. 손상된 부분 건너뛰기
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -max_muxing_queue_size 9999 output.mp4
# 4. 재인코딩 없이 복사 시도
ffmpeg -i input.mp4 -c copy output.mp4
문제 7: 메모리 부족 에러
해결:
# 1. 버퍼 크기 조정
ffmpeg -i input.mp4 -max_muxing_queue_size 9999 -c:v libx264 output.mp4
# 2. 파일을 나눠서 처리
ffmpeg -i input.mp4 -ss 0 -t 600 -c:v libx264 part1.mp4
ffmpeg -i input.mp4 -ss 600 -t 600 -c:v libx264 part2.mp4
# 나중에 병합
# 3. 스레드 수 제한
ffmpeg -i input.mp4 -threads 2 -c:v libx264 output.mp4
문제 8: 색상이 이상함 (회색빛, 채도 낮음)
원인: 색공간 변환 문제 해결:
# YUV 색공간 명시
ffmpeg -i input.mp4 -vf scale=1280:-2 -pix_fmt yuv420p -c:v libx264 output.mp4
# 색공간 정보 보존
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -color_primaries bt709 \
-color_trc bt709 -colorspace bt709 output.mp4
문제 9: 자막이 안 나옴
해결:
# 1. 자막 스트림 확인
ffprobe -show_streams input.mkv
# 2. 자막 포함하여 변환
ffmpeg -i input.mkv -c:v libx264 -c:a aac -c:s mov_text output.mp4
# 3. 자막 하드코딩 (영상에 구워넣기)
ffmpeg -i video.mp4 -vf subtitles=subtitle.srt output.mp4
# 4. 외부 자막 파일 추가
ffmpeg -i video.mp4 -i subtitle.srt -c:v copy -c:a copy -c:s mov_text output.mp4
문제 10: FFmpeg 설치 확인
# 버전 확인
ffmpeg -version
# 지원 코덱 확인
ffmpeg -codecs | grep h264
ffmpeg -codecs | grep hevc
# 지원 포맷 확인
ffmpeg -formats | grep mp4
# 하드웨어 인코더 확인
ffmpeg -encoders | grep nvenc # NVIDIA
ffmpeg -encoders | grep qsv # Intel
ffmpeg -encoders | grep amf # AMD
FAQ
Q1. FFmpeg와 HandBrake는 어떻게 나누면 되나? A:
- FFmpeg: 명령줄 기반, 자동화·스크립트·서버에 적합, 세밀한 제어 가능
- HandBrake: GUI 기반, 직관적, 빠른 설정, 개인 사용에 편리 선택 가이드:
- 한두 개 파일 변환: HandBrake
- 배치 처리, 자동화: FFmpeg
- 서버/CI/CD 파이프라인: FFmpeg
- 복잡한 필터, 스트리밍: FFmpeg
- 처음 시작하는 초보자: HandBrake Q2. 코덱은 무엇을 쓰면 되나? A: 상황에 따라 다릅니다: | 상황 | 추천 코덱 | 이유 | |------|----------|------| | 최대 호환성 | H.264 | 모든 기기 지원 | | 파일 크기 최소화 | H.265 | 40-50% 작음 | | 웹 스트리밍 | H.264 또는 VP9 | 브라우저 지원 | | YouTube 업로드 | H.264 | 플랫폼 권장 | | 4K 영상 | H.265 | 효율적 압축 | | 미래 대비 | AV1 | 차세대 표준 | 실전 팁:
- 일반 용도: H.264 (libx264)
- 저장 공간 부족: H.265 (libx265)
- 웹 전용: VP9 (libvpx-vp9) Q3. CRF와 비트레이트 중 무엇을 쓰나? A: 대부분의 경우 CRF를 권장합니다. CRF 사용 (권장):
- ✅ 일정한 품질 유지
- ✅ 간단한 사용법
- ✅ 대부분의 용도에 적합
- ❌ 파일 크기 예측 어려움
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
비트레이트 사용:
- ✅ 정확한 파일 크기
- ✅ 스트리밍 대역폭 제어
- ❌ 복잡한 장면에서 품질 저하
- ❌ 2-Pass 필요 시 시간 2배
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M output.mp4
선택 가이드:
- 일반 파일 변환: CRF 23
- YouTube 업로드: CRF 18-20
- 웹 스트리밍: 비트레이트 2-5M
- 방송 송출: 비트레이트 (2-Pass)
- 모바일: CRF 26-28 Q4. 인코딩이 너무 느리다 A: 여러 최적화 방법이 있습니다: 1. 프리셋 조정 (가장 쉬움)
# 느림 (현재)
ffmpeg -i input.mp4 -preset slow -crf 23 output.mp4
# 빠름 (권장)
ffmpeg -i input.mp4 -preset veryfast -crf 23 output.mp4
# 속도: 3-5배 향상, 파일 크기: 10-20% 증가
2. GPU 인코더 사용 (가장 효과적)
# NVIDIA GPU
ffmpeg -i input.mp4 -c:v h264_nvenc -preset fast -crf 23 output.mp4
# 속도: 5-10배 향상
# Intel GPU
ffmpeg -i input.mp4 -c:v h264_qsv -preset fast output.mp4
# AMD GPU
ffmpeg -i input.mp4 -c:v h264_amf -quality balanced output.mp4
3. 해상도 낮추기
ffmpeg -i input_4k.mp4 -vf scale=1920:-2 -crf 23 output_1080p.mp4
# 4K → 1080p: 4배 빠름
4. 병렬 처리 (여러 파일)
# GNU Parallel 사용
parallel -j 4 ffmpeg -i {} -crf 23 {.}_output.mp4 ::: *.mp4
# 4개 파일 동시 처리
5. 프레임레이트 낮추기
ffmpeg -i input.mp4 -r 24 -crf 23 output.mp4
# 60fps → 24fps: 2.5배 빠름
속도 vs 품질 비교:
| 방법 | 속도 향상 | 품질/크기 영향 |
|---|---|---|
| preset veryfast | 3-5배 | 크기 +10-20% |
| GPU 인코더 | 5-10배 | 크기 +20-30% |
| 해상도 감소 | 4배 (4K→1080p) | 해상도 감소 |
| 프레임레이트 감소 | 2배 (60→30fps) | 부드러움 감소 |
| Q5. 화질 손실을 최소화하려면? | ||
| A: 상황별 최적 전략: | ||
| 1. 재인코딩 없이 변환 (무손실) |
# 컨테이너만 변경
ffmpeg -i input.mkv -c copy output.mp4
# 장점: 초고속, 무손실
# 단점: 해상도/품질 조정 불가
2. 고품질 재인코딩 (거의 무손실)
# CRF 17-18 사용
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 17 output.mp4
# 시각적으로 무손실, 파일 크기 큼
3. 편집용 중간 코덱
# ProRes (macOS)
ffmpeg -i input.mp4 -c:v prores_ks -profile:v 3 output.mov
# DNxHD (크로스 플랫폼)
ffmpeg -i input.mp4 -c:v dnxhd -b:v 185M output.mov
# 무손실 H.264
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 0 output.mp4
4. 다단계 인코딩 최소화
# ❌ 나쁜 예: 3번 인코딩
원본 → 편집 → 효과 → 최종
(품질 손실 누적)
# ✅ 좋은 예: 1번 인코딩
원본 → [편집+효과 동시] → 최종
5. 적절한 코덱 선택
# 보관용: 무손실 또는 고품질
ffmpeg -i input.mp4 -c:v libx264 -crf 17 -preset slow archive.mp4
# 배포용: 균형잡힌 품질
ffmpeg -i input.mp4 -c:v libx264 -crf 20 -preset slow final.mp4
# 웹용: 적정 품질
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium web.mp4
품질 우선순위:
- 재인코딩 피하기 (
-c copy) - 낮은 CRF 값 (17-20)
- 느린 프리셋 (slow, slower)
- 적절한 해상도 유지
- 2-Pass 인코딩 (필요시) Q6. 웹 스트리밍용 최적 설정은? A:
# 웹 최적화 (progressive download)
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
-vf scale=1920:-2 \
web_optimized.mp4
# 핵심 옵션:
# -movflags +faststart: 메타데이터를 앞으로 (즉시 재생)
# -crf 23: 웹용 적정 품질
# -b:a 128k: 오디오 대역폭 절약
Q7. 여러 파일을 하나로 합치려면? A:
# 방법 1: concat 프로토콜 (같은 코덱)
ffmpeg -i "concat:file1.mp4|file2.mp4|file3.mp4" -c copy output.mp4
# 방법 2: concat demuxer (권장)
# 1. 파일 목록 생성
echo "file 'file1.mp4'" > list.txt
echo "file 'file2.mp4'" >> list.txt
echo "file 'file3.mp4'" >> list.txt
# 2. 병합
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
# 방법 3: filter_complex (다른 코덱/해상도)
ffmpeg -i file1.mp4 -i file2.mp4 -i file3.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" output.mp4
Q8. 특정 구간만 자르려면? A:
# 빠른 방법 (재인코딩 없음, 키프레임에서만 정확)
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c copy output.mp4
# 정확한 방법 (재인코딩, 느림)
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c:v libx264 -crf 23 output.mp4
# 시작 시간과 길이로 지정
ffmpeg -i input.mp4 -ss 00:01:30 -t 00:01:15 -c copy output.mp4
# -ss: 시작 시간
# -t: 길이
# -to: 종료 시간
내부 동작과 핵심 메커니즘
이 글의 주제는 「FFmpeg 실전 가이드 | 동영상/오디오 처리」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
경계에서의 지연·실패(시퀀스 관점)
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(프로세스·런타임·게이트웨이) participant D as 의존성(외부 API·DB·큐) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
알고리즘·프로토콜·리소스 관점 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
- 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.
프로덕션 운영 패턴
실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.
확장 예시: 엔드투엔드 미니 시나리오
「FFmpeg 실전 가이드 | 동영상/오디오 처리」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.
의사코드 스케치(프레임워크 무관)
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request) // 경계에서 거절
authorize(validated, ctx) // 권한·테넌트
result = domainCore(validated) // 순수에 가까운 규칙
persistOrEmit(result, idempotentKey) // I/O: 멱등·재시도 정책
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성 불안정, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정이 로컬과 다름 | 프로필·시크릿·기본값, 지역 리전 | 단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
요약
핵심 정리
FFmpeg란?
- 가장 강력한 오픈소스 멀티미디어 프레임워크
- 명령줄 도구 (
ffmpeg,ffprobe,ffplay) - 프로그래밍 라이브러리 (libav 계열)
- 거의 모든 비디오/오디오 포맷 지원
- YouTube, Netflix 등 산업 표준 핵심 명령어 치트시트:
# 기본 변환 (자동 코덱)
ffmpeg -i input.mp4 output.avi
# 고품질 H.264 인코딩 (권장)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 20 -c:a aac -b:a 192k output.mp4
# 빠른 변환 (재인코딩 없음)
ffmpeg -i input.mkv -c copy output.mp4
# 해상도 변경
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# 특정 구간 자르기
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c copy output.mp4
# 썸네일 추출
ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg
# 오디오 추출
ffmpeg -i input.mp4 -vn -c:a libmp3lame -b:a 192k audio.mp3
# HLS 스트리밍 생성
ffmpeg -i input.mp4 -hls_time 10 -hls_playlist_type vod playlist.m3u8
# 파일 정보 확인
ffprobe -show_format -show_streams input.mp4
# 여러 파일 병합
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
상황별 최적 설정:
# YouTube 업로드 (고품질)
ffmpeg -i input.mp4 \
-c:v libx264 -preset slow -crf 18 \
-c:a aac -b:a 192k \
-pix_fmt yuv420p \
youtube.mp4
# 웹 스트리밍 (즉시 재생)
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
-vf scale=1920:-2 \
web.mp4
# 모바일 최적화 (작은 파일)
ffmpeg -i input.mp4 \
-c:v libx264 -preset fast -crf 26 \
-c:a aac -b:a 96k \
-vf scale=1280:-2 \
mobile.mp4
# 빠른 변환 (테스트용)
ffmpeg -i input.mp4 \
-c:v libx264 -preset veryfast -crf 23 \
-c:a aac -b:a 128k \
quick.mp4
# 최소 파일 크기 (보관용)
ffmpeg -i input.mp4 \
-c:v libx265 -preset slow -crf 28 \
-c:a aac -b:a 128k \
compressed.mp4
# 최고 품질 (아카이브)
ffmpeg -i input.mp4 \
-c:v libx264 -preset veryslow -crf 17 \
-c:a aac -b:a 320k \
archive.mp4
CRF 값 선택 가이드:
| 용도 | CRF | 설명 |
|---|---|---|
| 편집 소스 | 17-18 | 시각적 무손실 |
| YouTube | 18-20 | 플랫폼 재인코딩 대비 |
| 일반 공유 | 21-23 | 균형잡힌 품질 |
| 웹 스트리밍 | 23-26 | 빠른 로딩 |
| 모바일 | 26-28 | 데이터 절약 |
| 프리셋 선택 가이드: | ||
| 상황 | 프리셋 | 이유 |
| ------ | -------- | ------ |
| 테스트/프리뷰 | veryfast | 빠른 확인 |
| 일반 작업 | medium | 기본 균형 |
| 최종 배포 | slow | 최적 품질 |
| 아카이브 | veryslow | 최소 크기 |
| 실시간 스트리밍 | ultrafast | CPU 부하 최소 |
학습 로드맵
1주차: 기초 다지기
- FFmpeg 설치 및 버전 확인
- 기본 명령어 구조 이해
- 간단한 포맷 변환 (
-c copy) - CRF와 프리셋 개념 파악
- 해상도 변경 실습 2주차: 실전 활용
- H.264 인코딩 마스터
- 오디오 처리 (추출, 변환, 편집)
- 썸네일 및 GIF 생성
- 영상 자르기 및 병합
- 필터 적용 (워터마크, 자막) 3주차: 고급 기능
- HLS 스트리밍 구축
- 2-Pass 인코딩
- GPU 가속 활용
- Python/Node.js 연동
- 배치 처리 스크립트 4주차: 최적화 및 트러블슈팅
- 성능 최적화 기법
- 일반적인 에러 해결
- 프로덕션 파이프라인 구축
- 모니터링 및 로깅
- 실전 프로젝트 적용 실무 적용 팁:
- 작게 시작하세요
- 짧은 테스트 영상으로 실험
- 다양한 설정 비교
- 결과를 눈으로 확인
- 자주 쓰는 명령어는 스크립트로
# convert_to_web.sh #!/bin/bash ffmpeg -i "$1" -c:v libx264 -preset medium -crf 23 \ -c:a aac -b:a 128k -movflags +faststart "web_$1" - GPU 가속 활용
- NVIDIA: h264_nvenc
- Intel: h264_qsv
- AMD: h264_amf
- 병렬 처리로 시간 절약
parallel -j 4 ffmpeg -i {} -crf 23 {.}_output.mp4 ::: *.mp4 - 품질 vs 속도 균형
- 개발: veryfast + crf 23
- 배포: slow + crf 20
- 테스트: ultrafast + crf 28
다음 단계
관련 심화 주제:
- H.264 코덱 완벽 가이드 - 코덱 내부 동작 원리
- AAC 오디오 코덱 가이드 - 오디오 인코딩 최적화
- [MKV 컨테이너 포맷 가이드](/en/blog/container-format-mkv-practical-guide/ - 컨테이너 구조 이해 실전 프로젝트:
- 동영상 스트리밍 서버 구축
- 자동 인코딩 파이프라인 개발
- 영상 편집 자동화 시스템
- 라이브 방송 플랫폼 구축 추가 학습 자료:
- FFmpeg 공식 문서
- FFmpeg Wiki
- VideoLAN Wiki
마치며
FFmpeg는 처음에는 복잡해 보이지만, 기본 패턴만 익히면 매우 강력한 도구입니다. 이 가이드의 예제들을 직접 실행해보면서 익숙해지세요. 기억해야 할 핵심:
- 대부분의 경우
-c:v libx264 -crf 23이면 충분합니다 - 재인코딩이 필요 없으면
-c copy를 사용하세요 - 웹용은
-movflags +faststart를 잊지 마세요 - 테스트는 짧은 영상으로, 최종본은 느린 프리셋으로
- 문제가 생기면
ffprobe로 먼저 확인하세요 질문이나 피드백은 댓글로 남겨주세요!
관련 글 (내부 링크)
FFmpeg와 함께 보면 좋은 비디오·오디오 관련 가이드입니다:
- H.264/AVC 비디오 코덱 완벽 가이드 | 프로파일·레벨·인코딩 최적화 - FFmpeg libx264 상세
- HEVC/H.265 실전 가이드 | 4K HDR·Main 10·인코딩 최적화 - FFmpeg libx265 활용
- AV1 코덱 차세대 표준 | 압축 효율·로열티 프리·실전 인코딩 - FFmpeg libaom-av1
- AAC 오디오 코덱 완전 가이드 | LC-AAC·HE-AAC·FFmpeg 실전 - FFmpeg 오디오 인코딩
- MP3 오디오 코덱 실전 활용 | LAME·CBR·VBR·FFmpeg 인코딩 - FFmpeg libmp3lame
- Opus 오디오 코덱 차세대 표준 | WebRTC·저지연·FFmpeg 실전 - FFmpeg libopus
- DRM 완벽 가이드 | Widevine·FairPlay·PlayReady - FFmpeg로 DRM 콘텐츠 준비
- HTTP 프로토콜 완벽 가이드 | HTTP/1.1·HTTP/2·HTTP/3 - HLS 스트리밍 이해
키워드: FFmpeg, Video, Audio, 동영상, 오디오, 인코딩, 스트리밍, HLS, RTMP, libav, Multimedia, 변환, H.264, H.265, VP9, AV1, AAC, MP3, 썸네일, GIF, 필터, 워터마크
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- AAC 오디오 코덱 완전 가이드 | LC-AAC·HE-AAC·FFmpeg 실전 인코딩
- MP3 오디오 코덱 실전 활용 | LAME·CBR·VBR·FFmpeg 인코딩 가이드
- Opus 오디오 코덱 차세대 표준 | WebRTC·저지연·FFmpeg 실전 가이드
이 글에서 다루는 키워드 (관련 검색어)
FFmpeg, Video, Audio, 동영상, 오디오, 인코딩, 스트리밍, libav, Multimedia 등으로 검색하시면 이 글이 도움이 됩니다.