시리즈: 실시간 통화, 어떻게 녹화하는가5/6
- 1.실시간 통화, 왜 녹화해야 하는가
- 2.내 서버에서 직접 녹화한다면
- 3.Agora 녹화 모드 비교
- 4.M3U8과 TS 구조
- 5.FFmpeg 미디어 처리 ← 현재 글
- 6.FFmpeg 실전 파이프라인
FFmpeg 미디어 처리 — 컨테이너, 코덱, 스트림
FFmpeg에서 컨테이너, 코덱, 스트림을 구분하고 probe·demux·decode·filter·encode·mux 단계가 어떻게 연결되는지 설명합니다. `-c copy`는 입력 코덱과 출력 컨테이너, timestamp와 keyframe 조건이 맞을 때만 안전합니다. remux, transcoding, stream mapping, seek, concat을 선택하는 기준을 실제 명령으로 정리했습니다.
FFmpeg은 media container 분석, remux, transcoding, filter 처리를 한 CLI에서 제공한다.
녹화 파일이 생성된 순간부터 사용자에게 실제로 전달되기까지, 그 사이에는 수많은 후처리 과정이 존재한다. TS 파일을 MP4로 변환하고, 개별 트랙을 합치고, 구간을 자르고, 썸네일을 뽑아내고. 이 모든 작업의 중심에 FFmpeg이 있다.
이 글에서는 FFmpeg을 이해하는 세 가지 핵심 개념인 Container, Codec, Stream과 자주 쓰는 명령어 패턴을 정리한다.
핵심 개념 1: Container vs Codec
가장 많이 혼동하는 개념부터 시작한다.
택배 상자(MP4)와 포장 방식(H.264)은 별개다. 같은 물건을 다른 상자에 옮겨 담을 수 있고, 같은 상자에 다른 방식으로 포장된 물건을 넣을 수도 있다.
컨테이너별 특성:
| 컨테이너 | 특징 | 주요 용도 |
|---|---|---|
| MP4 | 지원 codec일 때 폭넓게 재생 | VOD, 다운로드 |
| TS | 188-byte packet 기반 전송 container | HLS 스트리밍, 실시간 녹화 |
| MKV | 거의 모든 코덱 지원, 다중 트랙 | 아카이브, 자막 포함 영상 |
| WebM | VP8/VP9/AV1 + Vorbis/Opus | 웹 최적화 |
| FLV | 레거시 Flash 포맷 | RTMP 스트리밍 (레거시) |
TS는 sync byte, PID, continuity counter를 이용해 packet 경계와 손실을 감지하기 쉽다. 하지만 하나의 PES packet이나 video frame은 여러 TS packet에 걸칠 수 있으므로 각 packet이 독립 재생 가능한 완결 단위는 아니다.
핵심 개념 2: Stream (스트림)
컨테이너 내부에는 하나 이상의 스트림(트랙)이 존재한다.
각 스트림은 독립적으로 처리할 수 있다. 비디오 스트림만 추출하거나, 오디오 스트림만 교체하거나, 자막 스트림을 추가하는 것이 모두 가능하다.
ffprobe로 파일의 스트림 구성을 확인할 수 있다:
출력 결과에서 codec_type, codec_name, width, height, sample_rate 등을 확인할 수 있다. 후처리 파이프라인을 작성하기 전에 항상 먼저 실행해보는 습관을 들이면 좋다. "왜 오디오가 없지?" 같은 문제를 미리 발견할 수 있다.
핵심 개념 3: Muxing / Demuxing
스트림을 컨테이너에 넣고 빼는 작업에도 이름이 있다.
- Demuxing: 컨테이너에서 스트림을 꺼내는 작업
- Muxing: 스트림들을 컨테이너에 담는 작업
- Remuxing: 스트림 변경 없이 컨테이너만 바꾸는 것 (TS → MP4)
Remuxing은 media를 decode/encode하지 않으므로 codec data의 세대 손실이 없고 일반적으로 I/O 중심으로 처리된다. 다만 입력 codec이 출력 container에서 지원되고 timestamp·bitstream 형식이 호환되어야 한다.
실전 예시 — 리패키징 서버 (SRS 등)
디먹서/먹서 개념이 실제로 어떻게 조합되는지 가장 흔한 사례가 리패키징 서버입니다. OBS 같은 송출 도구가 RTMP로 푸시하면, 서버 한 곳에서 받아서 여러 프로토콜로 동시에 재배포하는 구조.
대표 오픈소스: SRS (Simple Realtime Server).
플로우:
- RTMP 수신 — OBS가 푸시한 RTMP 스트림을 TCP 1935에서 수신
- 디먹서 — RTMP 컨테이너에서 H.264 video와 AAC audio packet 추출
- 분기 먹서
- HLS 먹서: 추출한 H.264/AAC를 MPEG-TS 세그먼트로 포장 →
.m3u8플레이리스트 갱신 - WebRTC 출력: WebRTC에서 협상된 codec으로 RTP 전송. AAC는 WebRTC 필수 audio codec이 아니므로 일반적으로 Opus/G.711 계열로 transcoding이 필요
- HLS 먹서: 추출한 H.264/AAC를 MPEG-TS 세그먼트로 포장 →
- 재배포 — 한 입력이 두 가지 프로토콜로 동시 제공
왜 이 구조가 효율적인가
| 측면 | 설명 |
|---|---|
| 선택적 stream copy | 목적지와 codec이 호환되는 stream만 재인코딩 없이 재사용 가능 |
| 프로토콜 다양성 | 한 입력으로 HLS(대중 호환) + WebRTC(저지연) 동시 |
| 확장성 | 디먹서 출력을 캐시해서 N개 먹서에 공급 가능 |
주의: 코덱 호환성
디먹서는 container만 분리하므로 codec 자체가 목적지와 호환돼야 합니다. WebRTC endpoint가 지원하는 H.264 profile과 packetization mode, audio codec을 offer/answer에서 협상해야 합니다. RTMP의 AAC audio를 그대로 WebRTC 필수 codec처럼 RTP에 넣을 수는 없습니다.
RTMP→WebRTC 경로는 송출 설정과 수신 endpoint가 실제로 협상할 수 있는 codec/profile을 먼저 확인하고, 맞지 않는 audio/video stream만 transcoding해야 합니다. B-frame timestamp 처리 사례는 RTMP 라이브 송출의 B-frame이 만든 PTS rollback에서 다룹니다.
FFmpeg 명령어 구조
FFmpeg 명령어는 처음 보면 복잡해 보이지만, 구조를 이해하면 패턴이 보인다.
주요 옵션 정리:
| 옵션 | 의미 | 예시 |
|---|---|---|
-i | 입력 파일 지정 | -i input.mp4 |
-c:v | 비디오 코덱 지정 | -c:v libx264 |
-c:a | 오디오 코덱 지정 | -c:a aac |
-c copy | 모든 스트림 복사 (remux) | -c copy |
-ss | 시작 시점 | -ss 00:01:00 |
-t | 출력 길이 | -t 30 |
-to | 종료 시점 | -to 00:02:00 |
-vf | 비디오 필터 | -vf scale=1280:720 |
-an | 오디오 스트림 제거 | -an |
-vn | 비디오 스트림 제거 | -vn |
-y | 출력 파일 덮어쓰기 | -y |
-c copy vs Transcoding
FFmpeg을 쓸 때 가장 중요한 판단 중 하나다.
| -c copy (Remuxing) | Transcoding | |
|---|---|---|
| 속도 | 매우 빠름 (I/O bound) | 느림 (CPU bound) |
| 품질 | 무손실 (원본 그대로) | CRF/비트레이트로 제어 |
| CPU | 거의 안 씀 | 집약적 |
| 용도 | 컨테이너 변환, 스트림 추출 | 해상도 변경, 레이아웃 합성, 코덱 변환 |
| 필터 사용 | 불가 | 가능 |
codec을 바꾸거나 filter를 적용하지 않고 입력 stream이 출력 container와 호환된다면 -c copy를 우선 검토할 수 있다. timestamp, bitstream 형식, keyframe과 concat 입력 호환성이 맞지 않으면 remux가 실패하거나 재생 문제가 생길 수 있다.
-map: 스트림 선택
입력이 여러 개일 때, 또는 특정 스트림만 선택해야 할 때 -map을 사용한다.
Individual 모드로 녹화한 파일을 합칠 때 이 패턴을 자주 쓰게 된다. 비디오 트랙과 오디오 트랙이 별도 파일로 저장되어 있을 경우, -map으로 각각 지정해서 하나의 파일로 mux한다.
FFmpeg 내부 5단계 파이프라인
FFmpeg이 내부적으로 어떻게 동작하는지 이해하면, 에러 메시지를 해석하고 옵션을 조합하는 데 도움이 된다.
트랜스코딩은 Decode → Filter → Encode 단계에서 압축을 푼 media를 처리한다. 실제 처리 시간은 codec, preset, filter, hardware acceleration, 입력 복잡도와 장비 성능에 따라 달라진다.
기본 명령어 10가지
명령어 8번에서 filelist.txt 형식은 다음과 같다:
HLS 녹화 후 여러 TS 세그먼트를 하나의 MP4로 합칠 때 이 패턴을 쓴다.
"언제 -c copy, 언제 트랜스코딩?" 판단 플로우차트
TS→MP4, M3U8→MP4, stream 추출과 concat은 호환 조건이 맞으면 -c copy로 처리할 수 있다. 해상도·codec 변경이나 여러 영상을 한 layout으로 합성할 때는 transcoding이 필요하다.
핵심 요약
- Container는 포맷(상자), Codec은 압축 방식(포장법): 같은 코덱이 여러 컨테이너에 담길 수 있다
- Stream은 컨테이너 내부의 개별 트랙: 하나의 파일에 비디오/오디오/자막 스트림이 공존한다
- Remuxing은 codec을 재인코딩하지 않음: 입력 stream과 출력 container가 호환될 때
-c copy를 사용한다 - Transcoding은 필터가 필요할 때만: 디코딩 → 처리 → 인코딩을 거치므로 CPU 집약적이다
- -map으로 스트림을 정밀 선택: 멀티 입력이나 특정 트랙 추출 시 필수 옵션이다
- ffprobe를 습관처럼 먼저 실행하라: 파일 구조를 모르고 명령어를 작성하면 예상치 못한 결과가 나온다
시리즈 네비게이션
실시간 통화, 어떻게 녹화하는가 시리즈
관련 글
- #17 FFmpeg 실전 파이프라인 — Agora 녹화에서 프로덕션 VOD까지 — 이 글의 Container/Codec/
-c copy개념을 실제 녹화→VOD 파이프라인에 적용한 후속 편 - #15 M3U8과 TS 구조 — HLS 녹화 파일 읽기 — TS 세그먼트와 M3U8 플레이리스트의 구조. 본문의 TS→MP4 remux가 어디서 출발하는지
- #14 Agora 녹화 모드 비교 — Individual, Composite, Web page — 모드별로 어떤 파일이 생성되고 왜
-map이 필요한지 - #26 RTMP B-frame PTS rollback — WebRTC 변환 — 본문 "코덱 호환성" 주의에서 언급한 B-frame + RTP 단일 타임스탬프 충돌의 상세 분석
- #22 PCM vs WAV — 컨테이너와 코덱 — 컨테이너와 코덱의 분리라는 같은 원리를 오디오(PCM/WAV) 관점에서 본 글
참고 자료
- FFmpeg Documentation —
ffmpeg/ffprobeCLI 옵션, 필터, 먹서/디먹서 공식 레퍼런스 - FFmpeg Formats Documentation — concat demuxer와 MP4/HLS muxer·demuxer 옵션.
- FFmpeg Documentation —
-ss, stream copy, mapping 동작. - FFmpeg Codecs Documentation — encoder/decoder와 codec option.
- RFC 7874 — WebRTC Audio Codec and Processing Requirements — WebRTC endpoint의 필수 audio codec.
- The WebM Project: Container Guidelines — WebM이 VP8/VP9/AV1 + Vorbis/Opus를 담는 Matroska 기반 컨테이너임을 명시한 1차 출처
- MDN: Media container formats — MP4/WebM/MKV 등 컨테이너와 코덱의 관계 정리