WebM 컨테이너 웹 표준 | VP9·AV1·Opus·HTML5·FFmpeg 입문
이 글의 핵심
WebM은 웹에 맞춘 Matroska 계열 컨테이너—로열티 프리 코덱 조합과 HTML5·MSE 관점에서 기본기를 묶었습니다.
들어가며
WebM은 웹 배포를 염두에 둔 미디어 컨테이너로, Matroska의 구조를 따르되 허용 코덱·요소를 제한한 프로파일입니다. VP8/VP9/AV1 같은 로열티 프리에 가까운 비디오 코덱과 Opus/Vorbis 오디오를 묶어 HTML5 <video>, MSE(Media Source Extensions), WebRTC 생태계에서 라이선스 부담을 줄이려는 선택지로 자주 등장합니다.
이 글을 읽으면
- WebM과 일반 MKV의 차이를 한 문장으로 설명할 수 있습니다
- VP9/AV1 + Opus 조합으로 파일을 만드는 명령을 복사해 쓸 수 있습니다
- 브라우저·모바일 적합성을 MP4와 비교해 선택 근거를 갖습니다
- 자주 막히는 재생 문제를 빠르게 좁힐 수 있습니다
목차
컨테이너 개요
역사 및 개발 배경
WebM은 2010년 전후 구글 주도로 VP8과 함께 공개되며 널리 알려졌고, 이후 VP9, AV1이 같은 컨테이너 철학 아래 웹 친화적 비디오로 자리 잡았습니다.
주요 마일스톤:
- 2010: WebM 프로젝트 발표 (Google)
- 2010: VP8 + Vorbis 조합 공개
- 2013: VP9 출시
- 2018: AV1 1.0 릴리스
- 2026: Chrome, Firefox, Edge 완전 지원
기술적 특징
| 항목 | 설명 |
|---|---|
| 기반 | Matroska (EBML) 부분집합 |
| 허용 비디오 | VP8, VP9, AV1 |
| 허용 오디오 | Vorbis, Opus |
| 자막 | WebVTT (외부 권장) |
| 라이선스 | BSD 유사 (로열티 프리) |
| 스트리밍 | MSE, DASH 일부 지원 |
WebM vs MKV
| 특징 | WebM | MKV |
|---|---|---|
| 코덱 제한 | VP8/VP9/AV1 + Opus/Vorbis | 거의 모든 코덱 |
| 용도 | 웹 배포 | 범용 아카이브 |
| 복잡도 | 단순 | 복잡 (다중 트랙, 챕터) |
| 브라우저 지원 | 높음 | 낮음 |
내부 구조
EBML·Matroska 관계
WebM은 Matroska의 부분집합입니다. Segment → Cluster → SimpleBlock 흐름은 MKV와 같지만, 비디오는 주로 VP8/VP9/AV1, 오디오는 Vorbis/Opus로 실무에서 쓰는 조합이 고정되어 있습니다.
구조 다이어그램
WebM 파일
├─ EBML Header
├─ Segment
│ ├─ SeekHead (인덱스)
│ ├─ Info (메타데이터)
│ ├─ Tracks
│ │ ├─ Video Track (VP8/VP9/AV1)
│ │ └─ Audio Track (Opus/Vorbis)
│ ├─ Cluster 1 (타임스탬프 0-2s)
│ ├─ Cluster 2 (타임스탬프 2-4s)
│ └─ Cues (시크 인덱스)
메타데이터
지원 태그:
- TITLE: 제목
- LANGUAGE: 언어 코드
- DATE_RELEASED: 출시일
제한사항:
- MP4보다 메타데이터 지원 제한적
- 커버 아트는 별도 처리 권장
실전 사용
FFmpeg 기본 예제
1) VP9 + Opus (일반 웹 배포)
# CRF 모드 (품질 우선)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-b:v 0 \
-row-mt 1 \
-c:a libopus \
-b:a 128k \
output.webm
파라미터 설명:
-crf 32: 품질 (18-40, 낮을수록 고품질)-b:v 0: CRF 모드 활성화-row-mt 1: 멀티스레드 (VP9)
2) AV1 + Opus (최신 브라우저)
# SVT-AV1 인코더
ffmpeg -i input.mp4 \
-c:v libsvtav1 \
-crf 28 \
-preset 6 \
-c:a libopus \
-b:a 128k \
output.webm
프리셋:
- 0-3: 느림, 고품질
- 4-6: 중간
- 7-10: 빠름, 낮은 품질
3) 오디오만 (Opus)
# 음악
ffmpeg -i music.wav \
-c:a libopus \
-b:a 128k \
-application audio \
music.webm
# 음성
ffmpeg -i voice.wav \
-c:a libopus \
-b:a 32k \
-ac 1 \
-application voip \
voice.webm
고급 옵션
2-pass 인코딩 (VP9)
# 1st pass
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-b:v 1M \
-pass 1 \
-an \
-f webm \
/dev/null
# 2nd pass
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-b:v 1M \
-pass 2 \
-c:a libopus \
-b:a 128k \
output.webm
키프레임 간격 조정
# 2초마다 키프레임 (탐색 개선)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-g 96 \
-keyint_min 96 \
-c:a libopus \
-b:a 128k \
output.webm
배치 처리
import subprocess
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
def convert_to_webm(input_file, output_file, video_crf=32, audio_bitrate='128k'):
"""
MP4 → WebM 변환
"""
cmd = [
'ffmpeg', '-y',
'-i', str(input_file),
'-c:v', 'libvpx-vp9',
'-crf', str(video_crf),
'-b:v', '0',
'-row-mt', '1',
'-c:a', 'libopus',
'-b:a', audio_bitrate,
str(output_file)
]
subprocess.run(cmd, check=True)
return output_file
def batch_convert(input_dir, output_dir, max_workers=2):
"""
병렬 배치 변환
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
mp4_files = list(Path(input_dir).glob('*.mp4'))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for mp4_file in mp4_files:
output_file = Path(output_dir) / f"{mp4_file.stem}.webm"
future = executor.submit(convert_to_webm, mp4_file, output_file)
futures.append((mp4_file.name, future))
for name, future in futures:
try:
result = future.result()
print(f"✓ {name} → {result.name}")
except Exception as e:
print(f"✗ {name}: {e}")
# 사용
batch_convert('input_videos', 'output_webm', max_workers=2)
성능 비교
컨테이너 오버헤드
| 컨테이너 | 오버헤드 | 특징 |
|---|---|---|
| WebM | 매우 낮음 | Matroska 계열, 간단한 구조 |
| MP4 | 낮음 | 최적화됨, 스트리밍 친화 |
| MKV | 낮음 | 유연하지만 복잡 |
결론: 컨테이너 오버헤드는 거의 무시 가능
코덱별 파일 크기 비교
테스트: 1080p 5분 영상
| 조합 | 파일 크기 | 인코딩 시간 | 품질 |
|---|---|---|---|
| H.264 (MP4) + AAC | 45MB | 2분 | 기준 |
| VP9 (WebM) + Opus | 35MB | 8분 | 비슷 |
| AV1 (WebM) + Opus | 25MB | 30분 | 비슷 |
결론: AV1이 가장 작지만 인코딩 느림
브라우저 호환성
| 브라우저 | VP8 | VP9 | AV1 | Opus |
|---|---|---|---|---|
| Chrome | ✅ | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ | ✅ |
| Edge | ✅ | ✅ | ✅ | ✅ |
| Safari | ❌ | 부분 | 부분 | ✅ |
| Opera | ✅ | ✅ | ✅ | ✅ |
결론: Safari는 MP4 폴백 필수
실무 활용 사례
사례 1: YouTube 스타일 다중 품질
요구사항:
- 다양한 해상도 제공
- 적응형 스트리밍
- 대역폭 최적화
다중 품질 생성
# 1080p (고품질)
ffmpeg -i input.mp4 \
-vf "scale=1920:1080" \
-c:v libvpx-vp9 -crf 30 -b:v 0 \
-c:a libopus -b:a 128k \
1080p.webm
# 720p (중품질)
ffmpeg -i input.mp4 \
-vf "scale=1280:720" \
-c:v libvpx-vp9 -crf 32 -b:v 0 \
-c:a libopus -b:a 96k \
720p.webm
# 480p (저품질)
ffmpeg -i input.mp4 \
-vf "scale=854:480" \
-c:v libvpx-vp9 -crf 35 -b:v 0 \
-c:a libopus -b:a 64k \
480p.webm
HTML5 적응형 플레이어
<!DOCTYPE html>
<html>
<head>
<title>WebM 적응형 플레이어</title>
</head>
<body>
<video id="player" controls width="1280"></video>
<div>
<button onclick="changeQuality('1080p')">1080p</button>
<button onclick="changeQuality('720p')">720p</button>
<button onclick="changeQuality('480p')">480p</button>
</div>
<script>
const player = document.getElementById('player');
const qualities = {
'1080p': 'videos/1080p.webm',
'720p': 'videos/720p.webm',
'480p': 'videos/480p.webm'
};
function changeQuality(quality) {
const currentTime = player.currentTime;
player.src = qualities[quality];
player.currentTime = currentTime;
player.play();
}
// 자동 품질 선택
function selectQuality() {
const connection = navigator.connection || navigator.mozConnection;
if (!connection) {
return '720p';
}
const effectiveType = connection.effectiveType;
if (effectiveType === '4g') {
return '1080p';
} else if (effectiveType === '3g') {
return '480p';
} else {
return '720p';
}
}
// 초기 로드
player.src = qualities[selectQuality()];
</script>
</body>
</html>
사례 2: 웹 강의 플랫폼
요구사항:
- 긴 영상 (1-2시간)
- 파일 크기 최소화
- 화면 녹화 (슬라이드)
설정
# 화면 녹화 최적화 (낮은 프레임레이트)
ffmpeg -i lecture.mp4 \
-vf "scale=1280:720" \
-r 15 \
-c:v libvpx-vp9 \
-crf 35 \
-b:v 0 \
-c:a libopus \
-b:a 64k \
-ac 1 \
lecture.webm
결과:
- 1시간 강의: 약 200MB (MP4 대비 40% 절약)
사례 3: 게임 트레일러
요구사항:
- 고품질
- 빠른 로딩
- 웹 임베드
설정
# 고품질 VP9
ffmpeg -i trailer.mp4 \
-c:v libvpx-vp9 \
-crf 24 \
-b:v 0 \
-row-mt 1 \
-c:a libopus \
-b:a 192k \
trailer.webm
HTML5 임베드
<video autoplay loop muted playsinline>
<source src="trailer.webm" type="video/webm">
<source src="trailer.mp4" type="video/mp4">
</video>
사례 4: 라이브 스트리밍 (WebRTC)
요구사항:
- 초저지연
- 실시간 인코딩
- P2P 전송
WebRTC 설정
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// Opus 오디오 설정
const audioConfig = {
codec: 'opus',
bitrate: 32000,
sampleRate: 48000,
channels: 1
};
// VP9 비디오 설정
const videoConfig = {
codec: 'VP9',
width: 1280,
height: 720,
framerate: 30,
bitrate: 1000000
};
// MediaStream 추가
navigator.mediaDevices.getUserMedia({
video: videoConfig,
audio: audioConfig
}).then(stream => {
stream.getTracks().forEach(track => {
peerConnection.addTrack(track, stream);
});
});
최적화 팁
1) 인코딩 속도 개선
VP9 멀티스레드
# 타일 인코딩 (멀티코어 활용)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-b:v 0 \
-row-mt 1 \
-tile-columns 2 \
-tile-rows 1 \
-threads 8 \
-c:a libopus \
-b:a 128k \
output.webm
AV1 프리셋 조정
# 빠른 인코딩 (품질 약간 저하)
ffmpeg -i input.mp4 \
-c:v libsvtav1 \
-crf 30 \
-preset 8 \
-c:a libopus \
-b:a 128k \
fast.webm
2) 파일 크기 최소화
# 해상도 + 프레임레이트 감소
ffmpeg -i input.mp4 \
-vf "scale=854:480" \
-r 24 \
-c:v libvpx-vp9 \
-crf 35 \
-b:v 0 \
-c:a libopus \
-b:a 64k \
small.webm
3) 스트리밍 최적화
# 키프레임 간격 조정 (2초)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-b:v 0 \
-g 60 \
-keyint_min 60 \
-c:a libopus \
-b:a 128k \
streaming.webm
트러블슈팅
문제 1: Safari 재생 안 됨
증상: Safari에서 WebM 재생 불가
<video src="video.webm" controls></video>
<!-- Safari: 재생 불가 -->
해결: MP4 폴백 추가
<video controls>
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
</video>
# MP4 버전 생성
ffmpeg -i input.mp4 \
-c:v libx264 \
-preset medium \
-crf 23 \
-c:a aac \
-b:a 128k \
fallback.mp4
문제 2: 인코딩 너무 느림
증상: AV1 인코딩이 몇 시간 소요
해결 1: VP9 사용
# AV1 대신 VP9 (10배 빠름)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-b:v 0 \
-c:a libopus \
-b:a 128k \
output.webm
해결 2: 프리셋 조정
# AV1 빠른 프리셋
ffmpeg -i input.mp4 \
-c:v libsvtav1 \
-crf 30 \
-preset 10 \
-c:a libopus \
-b:a 128k \
fast.webm
문제 3: 재생 시 끊김
증상: 웹 플레이어에서 버퍼링 발생
원인: 키프레임 간격 너무 김
해결:
# 키프레임 간격 줄이기 (2초)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-g 60 \
-c:a libopus \
-b:a 128k \
output.webm
문제 4: 오디오 코덱 미지원
증상: WebM에 AAC 넣었더니 재생 안 됨
# 잘못된 예
ffmpeg -i input.mp4 -c:v libvpx-vp9 -c:a aac output.webm
# WebM은 AAC 지원 안 함
해결: Opus 또는 Vorbis 사용
# 올바른 예
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-crf 32 \
-c:a libopus \
-b:a 128k \
output.webm
마무리
WebM은 웹에 특화된 Matroska 계열로, VP9/AV1 + Opus 같은 로열티 프리 조합에 잘 맞습니다.
핵심 요약
-
WebM = Matroska 부분집합
- 허용 코덱: VP8/VP9/AV1 + Opus/Vorbis
- 웹 배포 최적화
-
브라우저 지원
- Chrome, Firefox, Edge: 완전 지원
- Safari: 제한적 (MP4 폴백 필수)
-
로열티 프리
- 오픈 코덱 조합
- 상용 배포 자유
선택 가이드
| 상황 | 추천 |
|---|---|
| 최대 호환 | MP4 (H.264 + AAC) |
| 대역폭 절약 | WebM (VP9/AV1 + Opus) |
| 오픈 코덱 | WebM |
| Safari 필수 | MP4 + WebM 병행 |
| 편집 워크플로 | MP4/MOV |
FFmpeg 명령 치트시트
# VP9 + Opus (일반)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 -c:a libopus -b:a 128k output.webm
# AV1 + Opus (최신)
ffmpeg -i input.mp4 -c:v libsvtav1 -crf 28 -preset 6 -c:a libopus -b:a 128k output.webm
# 오디오만
ffmpeg -i audio.wav -c:a libopus -b:a 128k audio.webm
# 2-pass VP9
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 1M -pass 1 -an -f webm /dev/null
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 1M -pass 2 -c:a libopus -b:a 128k output.webm
# 폴백 MP4 생성
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -b:a 128k fallback.mp4
HTML5 폴백 패턴
<!-- 권장 패턴 -->
<video controls>
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
다음 단계
- MP4 비교: MP4 완벽 가이드
- MKV 비교: MKV 실전 가이드
- AV1 코덱: AV1 차세대 비디오
참고 자료
- WebM 프로젝트: https://www.webmproject.org/
- VP9 가이드: https://developers.google.com/media/vp9
- FFmpeg VP9:
ffmpeg -h encoder=libvpx-vp9 - FFmpeg AV1:
ffmpeg -h encoder=libsvtav1
한 줄 정리: 오픈 웹 스택·대역폭 절약이 중요하면 WebM(VP9/AV1+Opus), 최대 호환이 필요하면 MP4 폴백을 병행한다.