블로그 목록
시리즈: 실시간 통화, 어떻게 녹화하는가5/6
  1. 1.실시간 통화, 왜 녹화해야 하는가
  2. 2.내 서버에서 직접 녹화한다면
  3. 3.Agora 녹화 모드 비교
  4. 4.M3U8과 TS 구조
  5. 5.FFmpeg 미디어 처리 ← 현재 글
  6. 6.FFmpeg 실전 파이프라인
Backend시리즈14분 읽기

FFmpeg 미디어 처리 — 컨테이너, 코덱, 스트림

FFmpeg에서 컨테이너, 코덱, 스트림을 구분하고 probe·demux·decode·filter·encode·mux 단계가 어떻게 연결되는지 설명합니다. `-c copy`는 입력 코덱과 출력 컨테이너, timestamp와 keyframe 조건이 맞을 때만 안전합니다. remux, transcoding, stream mapping, seek, concat을 선택하는 기준을 실제 명령으로 정리했습니다.

FFmpegCodecContainerMuxingTranscoding

FFmpeg은 media container 분석, remux, transcoding, filter 처리를 한 CLI에서 제공한다.

녹화 파일이 생성된 순간부터 사용자에게 실제로 전달되기까지, 그 사이에는 수많은 후처리 과정이 존재한다. TS 파일을 MP4로 변환하고, 개별 트랙을 합치고, 구간을 자르고, 썸네일을 뽑아내고. 이 모든 작업의 중심에 FFmpeg이 있다.

이 글에서는 FFmpeg을 이해하는 세 가지 핵심 개념인 Container, Codec, Stream과 자주 쓰는 명령어 패턴을 정리한다.


핵심 개념 1: Container vs Codec

가장 많이 혼동하는 개념부터 시작한다.

Container (컨테이너) = 택배 상자
  파일 포맷 자체를 의미. 내부 데이터를 어떻게 담을지 정의.
  MP4, TS, MKV, WebM, FLV, AVI ...

Codec (코덱) = 상자 안의 물건을 포장하는 방식
  실제 미디어 데이터를 어떻게 압축/복원할지 정의.
  비디오: H.264, H.265(HEVC), VP8, VP9, AV1
  오디오: AAC, Opus, MP3, FLAC

핵심: 같은 코덱을 다른 컨테이너에 담을 수 있다

  H.264 + AAC → MP4  (일반적인 영상 파일)
  H.264 + AAC → TS   (HLS 스트리밍 세그먼트)
  H.264 + AAC → MKV  (아카이브용 만능 컨테이너)
  VP9 + Opus  → WebM (웹 브라우저 최적화)

택배 상자(MP4)와 포장 방식(H.264)은 별개다. 같은 물건을 다른 상자에 옮겨 담을 수 있고, 같은 상자에 다른 방식으로 포장된 물건을 넣을 수도 있다.

컨테이너별 특성:

컨테이너특징주요 용도
MP4지원 codec일 때 폭넓게 재생VOD, 다운로드
TS188-byte packet 기반 전송 containerHLS 스트리밍, 실시간 녹화
MKV거의 모든 코덱 지원, 다중 트랙아카이브, 자막 포함 영상
WebMVP8/VP9/AV1 + Vorbis/Opus웹 최적화
FLV레거시 Flash 포맷RTMP 스트리밍 (레거시)

TS는 sync byte, PID, continuity counter를 이용해 packet 경계와 손실을 감지하기 쉽다. 하지만 하나의 PES packet이나 video frame은 여러 TS packet에 걸칠 수 있으므로 각 packet이 독립 재생 가능한 완결 단위는 아니다.


핵심 개념 2: Stream (스트림)

컨테이너 내부에는 하나 이상의 스트림(트랙)이 존재한다.

하나의 MP4 파일 내부:
┌──────────────────────────────────┐
│  MP4 Container                   │
│                                  │
│  ┌──────────────────────────┐    │
│  │ Stream 0: Video (H.264)  │    │
│  ├──────────────────────────┤    │
│  │ Stream 1: Audio (AAC)    │    │
│  ├──────────────────────────┤    │
│  │ Stream 2: Subtitle (mov_text) │ │
│  └──────────────────────────┘    │
└──────────────────────────────────┘

각 스트림은 독립적으로 처리할 수 있다. 비디오 스트림만 추출하거나, 오디오 스트림만 교체하거나, 자막 스트림을 추가하는 것이 모두 가능하다.

ffprobe로 파일의 스트림 구성을 확인할 수 있다:

ffprobe -v quiet -show_streams -of json input.mp4

출력 결과에서 codec_type, codec_name, width, height, sample_rate 등을 확인할 수 있다. 후처리 파이프라인을 작성하기 전에 항상 먼저 실행해보는 습관을 들이면 좋다. "왜 오디오가 없지?" 같은 문제를 미리 발견할 수 있다.


핵심 개념 3: Muxing / Demuxing

스트림을 컨테이너에 넣고 빼는 작업에도 이름이 있다.

Demuxing (분해):
┌──────────┐              ┌─ Video stream (H.264)
│  MP4     │ ─── Demux ─→ ├─ Audio stream (AAC)
│  파일    │              └─ Subtitle stream
└──────────┘

Muxing (조립):
Video stream  ─┐
Audio stream  ─┼─── Mux ───→ ┌──────────┐
Subtitle      ─┘             │  MKV     │
                              │  파일    │
                              └──────────┘

Remuxing (재조립):
┌──────┐         ┌─ Video stream ─┐         ┌──────┐
│  TS  │ Demux ─→│               │─→ Mux ─→ │  MP4 │
└──────┘         └─ Audio stream ─┘         └──────┘
스트림은 그대로, 컨테이너만 교체
  • Demuxing: 컨테이너에서 스트림을 꺼내는 작업
  • Muxing: 스트림들을 컨테이너에 담는 작업
  • Remuxing: 스트림 변경 없이 컨테이너만 바꾸는 것 (TS → MP4)

Remuxing은 media를 decode/encode하지 않으므로 codec data의 세대 손실이 없고 일반적으로 I/O 중심으로 처리된다. 다만 입력 codec이 출력 container에서 지원되고 timestamp·bitstream 형식이 호환되어야 한다.

실전 예시 — 리패키징 서버 (SRS 등)

디먹서/먹서 개념이 실제로 어떻게 조합되는지 가장 흔한 사례가 리패키징 서버입니다. OBS 같은 송출 도구가 RTMP로 푸시하면, 서버 한 곳에서 받아서 여러 프로토콜로 동시에 재배포하는 구조.

대표 오픈소스: SRS (Simple Realtime Server).

OBS 송출 RTMP push tcp :1935 RTMP 리패키징 서버 (예: SRS) RTMP 수신 모듈 포트 1935 스트림 관리자 채널별 라우팅 디먹서 (H.264 / AAC 추출) 입력 codec과 목적지 codec 호환성 확인 HLS 먹서 MPEG-TS 세그먼트 + .m3u8 WebRTC 출력 협상된 codec으로 RTP 전송 HLS 시청자 모바일 · 브라우저 범용 WebRTC 시청자 저지연 필요시

플로우:

  1. RTMP 수신 — OBS가 푸시한 RTMP 스트림을 TCP 1935에서 수신
  2. 디먹서 — RTMP 컨테이너에서 H.264 video와 AAC audio packet 추출
  3. 분기 먹서
    • HLS 먹서: 추출한 H.264/AAC를 MPEG-TS 세그먼트로 포장 → .m3u8 플레이리스트 갱신
    • WebRTC 출력: WebRTC에서 협상된 codec으로 RTP 전송. AAC는 WebRTC 필수 audio codec이 아니므로 일반적으로 Opus/G.711 계열로 transcoding이 필요
  4. 재배포 — 한 입력이 두 가지 프로토콜로 동시 제공

왜 이 구조가 효율적인가

측면설명
선택적 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 명령어는 처음 보면 복잡해 보이지만, 구조를 이해하면 패턴이 보인다.

ffmpeg [global options] [input options] -i input [output options] output

예시 해부:
ffmpeg -y -ss 00:01:00 -i input.mp4 -t 30 -c:v libx264 -crf 23 -c:a aac output.mp4
  │     │      │          │          │       │            │        │       │
  │     │      │          │          │       │            │        │       └─ 출력 파일
  │     │      │          │          │       │            │        └─ 오디오 코덱: AAC
  │     │      │          │          │       │            └─ 품질: CRF 23 (낮을수록 고품질)
  │     │      │          │          │       └─ 비디오 코덱: H.264
  │     │      │          │          └─ 출력 길이: 30초
  │     │      │          └─ 입력 파일
  │     │      └─ 시작 지점: 1분 (입력 옵션으로 seek)
  │     └─ 기존 파일 덮어쓰기 (yes)
  └─ 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을 쓸 때 가장 중요한 판단 중 하나다.

Remuxing (-c copy):
┌──────┐                           ┌──────┐
│  TS  │ ─→ Demux ─→ 스트림 그대로 ─→ Mux ─→ │  MP4 │
└──────┘                           └──────┘
처리 특성: I/O 중심   codec 세대 손실: 없음

Transcoding (-c:v libx264):
┌──────┐                                          ┌──────┐
│  TS  │ ─→ Demux ─→ Decode ─→ Filter ─→ Encode ─→ Mux ─→ │  MP4 │
└──────┘                                          └──────┘
처리 특성: decode/filter/encode 수행, 환경과 codec에 따라 처리량이 달라짐
-c copy (Remuxing)Transcoding
속도매우 빠름 (I/O bound)느림 (CPU bound)
품질무손실 (원본 그대로)CRF/비트레이트로 제어
CPU거의 안 씀집약적
용도컨테이너 변환, 스트림 추출해상도 변경, 레이아웃 합성, 코덱 변환
필터 사용불가가능

codec을 바꾸거나 filter를 적용하지 않고 입력 stream이 출력 container와 호환된다면 -c copy를 우선 검토할 수 있다. timestamp, bitstream 형식, keyframe과 concat 입력 호환성이 맞지 않으면 remux가 실패하거나 재생 문제가 생길 수 있다.


-map: 스트림 선택

입력이 여러 개일 때, 또는 특정 스트림만 선택해야 할 때 -map을 사용한다.

# 입력이 2개일 때: 영상은 첫 번째 파일, 오디오는 두 번째 파일에서
ffmpeg -i video.mp4 -i audio.m4a -map 0:v:0 -map 1:a:0 -c copy output.mp4
#                                       │        │
#                          입력 0의 비디오  입력 1의 오디오

# 문법: -map 입력번호:스트림타입
#   0:v = 첫 번째 입력의 비디오 스트림
#   1:a = 두 번째 입력의 오디오 스트림
#   0:s = 첫 번째 입력의 자막 스트림
#   0:0 = 첫 번째 입력의 첫 번째 스트림 (인덱스 직접 지정)

Individual 모드로 녹화한 파일을 합칠 때 이 패턴을 자주 쓰게 된다. 비디오 트랙과 오디오 트랙이 별도 파일로 저장되어 있을 경우, -map으로 각각 지정해서 하나의 파일로 mux한다.


FFmpeg 내부 5단계 파이프라인

FFmpeg이 내부적으로 어떻게 동작하는지 이해하면, 에러 메시지를 해석하고 옵션을 조합하는 데 도움이 된다.

┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
│ Demuxer  │→ │ Decoder  │→ │  Filter  │→ │ Encoder  │→ │  Muxer   │
│          │  │          │  │          │  │          │  │          │
│컨테이너  │  │코덱→Raw  │  │변환/합성 │  │Raw→코덱  │  │컨테이너  │
│열기/파싱 │  │데이터    │  │처리      │  │압축      │  │쓰기      │
└──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘

-c copy 사용 시 (Decoder/Filter/Encoder 단계 스킵):
┌──────────┐                                              ┌──────────┐
│ Demuxer  │──────────── 인코딩된 패킷 그대로 ──────────→  │  Muxer   │
└──────────┘                                              └──────────┘

트랜스코딩은 Decode → Filter → Encode 단계에서 압축을 푼 media를 처리한다. 실제 처리 시간은 codec, preset, filter, hardware acceleration, 입력 복잡도와 장비 성능에 따라 달라진다.


기본 명령어 10가지

# 1. 파일 스트림 정보 확인 (항상 먼저 실행)
ffprobe -v quiet -show_streams -of json input.mp4

# 2. 컨테이너 변환 (TS → MP4, 무손실 remux)
ffmpeg -i input.ts -c copy output.mp4

# 3. M3U8 → MP4 (HLS 플레이리스트를 단일 파일로)
ffmpeg -i recording.m3u8 -c copy output.mp4

# 4. 오디오만 추출 (비디오 제거)
ffmpeg -i input.mp4 -vn -c:a copy audio.m4a

# 5. 비디오만 추출 (오디오 제거)
ffmpeg -i input.mp4 -an -c:v copy video.mp4

# 6. 구간 잘라내기 (stream copy이므로 시작점은 keyframe 제약을 받음)
ffmpeg -ss 00:01:00 -to 00:02:00 -i input.mp4 -c copy clip.mp4

# 7. 해상도 변경 (720p로 다운스케일)
ffmpeg -i input.mp4 -vf scale=1280:720 -c:a copy resized.mp4

# 8. codec, stream layout, time base가 호환되는 파일 이어붙이기
# filelist.txt: 각 줄에 "file '파일명'" 형식으로 작성
ffmpeg -f concat -safe 0 -i filelist.txt -c copy merged.mp4

# 9. GIF 생성 (10초 지점부터 5초 구간)
ffmpeg -ss 00:00:10 -t 5 -i input.mp4 -vf "fps=10,scale=480:-1" output.gif

# 10. 썸네일 추출 (30초 지점 프레임 1장)
ffmpeg -ss 00:00:30 -i input.mp4 -frames:v 1 thumbnail.jpg

명령어 8번에서 filelist.txt 형식은 다음과 같다:

file 'segment_001.ts'
file 'segment_002.ts'
file 'segment_003.ts'

HLS 녹화 후 여러 TS 세그먼트를 하나의 MP4로 합칠 때 이 패턴을 쓴다.


"언제 -c copy, 언제 트랜스코딩?" 판단 플로우차트

              내가 하려는 작업은?
                      │
        ┌─────────────┼─────────────┐
        │             │             │
   컨테이너만      스트림만        필터/변환
   바꾸기          추출           적용
        │             │             │
     -c copy       -c copy      트랜스코딩 필요
     (무손실)      (무손실)           │
                              ┌──────┼──────┐
                              │      │      │
                         해상도   레이아웃  코덱
                         변경     합성     변환
                              │      │      │
                         -vf scale  -filter_complex  -c:v libx264
                                    (복수 입력)      -c:v libx265

TS→MP4, M3U8→MP4, stream 추출과 concat은 호환 조건이 맞으면 -c copy로 처리할 수 있다. 해상도·codec 변경이나 여러 영상을 한 layout으로 합성할 때는 transcoding이 필요하다.


핵심 요약

  • Container는 포맷(상자), Codec은 압축 방식(포장법): 같은 코덱이 여러 컨테이너에 담길 수 있다
  • Stream은 컨테이너 내부의 개별 트랙: 하나의 파일에 비디오/오디오/자막 스트림이 공존한다
  • Remuxing은 codec을 재인코딩하지 않음: 입력 stream과 출력 container가 호환될 때 -c copy를 사용한다
  • Transcoding은 필터가 필요할 때만: 디코딩 → 처리 → 인코딩을 거치므로 CPU 집약적이다
  • -map으로 스트림을 정밀 선택: 멀티 입력이나 특정 트랙 추출 시 필수 옵션이다
  • ffprobe를 습관처럼 먼저 실행하라: 파일 구조를 모르고 명령어를 작성하면 예상치 못한 결과가 나온다

시리즈 네비게이션

실시간 통화, 어떻게 녹화하는가 시리즈

  1. 실시간 통화, 왜 녹화해야 하는가 — Cloud Recording Architecture
  2. 내 서버에서 직접 녹화한다면 — On-Premise vs Cloud Recording
  3. Agora 녹화 모드 비교 — Individual, Composite, Web page
  4. M3U8과 TS 구조 — HLS 녹화 파일 읽기
  5. FFmpeg의 Container·Codec·Stream ← 현재 글
  6. FFmpeg 실전 파이프라인 — Agora 녹화에서 프로덕션 VOD까지

관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.