H.264, H.265, VP9, AV1 인코딩 효율을 실측으로 비교하기
개요
동영상 인코딩 설정 화면을 열면 코덱을 고르는 항목이 있습니다. H.264는 거의 모든 기기에서 재생되는 오래된 표준이고, H.265(HEVC)는 그보다 압축 효율이 좋다고 알려져 있고, VP9는 유튜브가 많이 쓰는 로열티 프리 코덱이고, AV1은 최근 몇 년 사이 가장 많이 언급되는 차세대 코덱입니다.
문제는 "압축 효율이 좋다"는 말이 실제로 어느 정도의 차이를 의미하는지, 그리고 그 차이가 모든 종류의 영상에서 똑같이 나타나는지가 막연하다는 점입니다.
실무에서는 이 선택이 구체적인 트레이드오프로 다가옵니다. 스트리밍 서비스에서 대역폭 비용을 줄이려고 코덱을 H.264에서 AV1로 바꾸는 안이 올라왔다고 가정해보겠습니다.
검토해야 할 질문은 최소 세 가지입니다. 같은 화질을 유지하면서 파일 크기를 얼마나 줄일 수 있는지, 그 절감폭이 스포츠 중계처럼 움직임이 많은 콘텐츠와 뷰티 클로즈업처럼 정적이지만 텍스처가 복잡한 콘텐츠에서 같은 폭으로 나타나는지, 그리고 인코딩 시간이 늘어나는 만큼 서버 비용이 오히려 늘어나지는 않는지입니다. 이 질문들은 코덱 이름과 압축 효율의 순위표만 봐서는 답이 나오지 않고, 실제로 같은 소스를 여러 코덱으로 인코딩해보고 숫자로 확인해야 합니다.
이 글에서는 두 개의 실제 영상 소스를 FFmpeg로 H.264, H.265, VP9, AV1 네 코덱으로 인코딩하고, ffprobe로 결과물을 검증하고, libvmaf 필터로 원본 대비 화질 손실을 측정합니다.
이어서 같은 소스 하나를 Tencent Cloud의 미디어 트랜스코딩 서비스 MPS에 올려 API로 트랜스코딩한 결과와 로컬 인코딩 결과를 비교합니다.
테스트 환경과 소스
- FFmpeg 8.0 (Homebrew 빌드, libx264/libx265/libvpx-vp9/libsvtav1/libaom-av1/libvmaf 포함)
- macOS, Apple Silicon. 아래 결과는 모두 소프트웨어 인코더 기준이고, VideoToolbox 같은 하드웨어 가속 경로는 쓰지 않았습니다.
소스는 움직임과 텍스처 복잡도가 대비되는 두 개를 준비했습니다.
$ ffprobe -v error -show_entries format=duration,size,bit_rate \
-show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt \
-of default=noprint_wrappers=0 d4e71456f0244493.mp4
[STREAM]
codec_name=h264
width=1920
height=1080
pix_fmt=yuv420p
r_frame_rate=24/1
[/STREAM]
[FORMAT]
duration=5.041667
size=14308932
bit_rate=22705080
[/FORMAT]
$ ffprobe -v error -show_entries format=duration,size,bit_rate \
-show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt \
-of default=noprint_wrappers=0 cb78283a1a7c4e4f.mp4
[STREAM]
codec_name=h264
width=1280
height=720
pix_fmt=yuv420p
r_frame_rate=24/1
[/STREAM]
[FORMAT]
duration=8.000000
size=4913491
bit_rate=4913491
[/FORMAT]- 고움직임 소스: 1920x1080, 24fps, 약 5초. 카메라와 피사체가 빠르게 움직이는 체조 동작 장면이라 프레임 간 변화가 크고, 원본 비트레이트 자체도 22.7Mbps로 상당히 높습니다. 아래에서는 "HI"로 표기합니다.
- 저움직임/고복잡도 소스: 1280x720, 24fps, 8초. 카메라가 거의 고정된 클로즈업 장면인데 피부 질감, 머리카락, 세밀한 텍스처가 프레임 전체를 채우고 있어서 움직임은 적어도 공간 주파수(spatial detail)가 높습니다. 아래에서는 "LO"로 표기합니다.
이 두 소스를 고른 이유는 코덱별 압축 효율이 콘텐츠 특성에 따라 다르게 나타난다는 걸 확인하기 위해서입니다. 움직임 예측(motion estimation)이 중요한 장면과, 프레임 내 압축(intra prediction)과 양자화 품질이 더 중요한 장면에서 코덱 간 격차가 달라질 것으로 예상하고 시작했습니다.
1. 동일 CRF 근사값으로 네 코덱 인코딩하기
먼저 오디오를 빼고 네 코덱 각각의 기본값에 가까운 CRF로 인코딩했습니다. CRF 스케일은 코덱마다 정의가 달라서 같은 숫자를 넣어도 같은 화질이 나오지 않습니다. 그래서 숫자를 그대로 맞추는 대신, 각 코덱에서 "적당히 고화질" 구간으로 흔히 쓰이는 값을 골랐습니다.
# H.264 (libx264)
ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 23 -pix_fmt yuv420p out_h264.mp4
# H.265 (libx265)
ffmpeg -y -i src.mp4 -an -c:v libx265 -preset medium -crf 28 -pix_fmt yuv420p out_h265.mp4
# VP9 (libvpx-vp9), 정적 CRF 모드는 -b:v 0을 같이 줘야 합니다
ffmpeg -y -i src.mp4 -an -c:v libvpx-vp9 -b:v 0 -crf 34 -row-mt 1 -pix_fmt yuv420p out_vp9.mp4
# AV1 (libsvtav1)
ffmpeg -y -i src.mp4 -an -c:v libsvtav1 -crf 35 -preset 6 -pix_fmt yuv420p out_av1.mp4인코딩 시간은 /usr/bin/time -p로 실측했습니다.
$ /usr/bin/time -p ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 23 -pix_fmt yuv420p out_h264.mp4
...
real 1.88
user 13.82
sys 0.41결과물은 ffprobe로 codec_name, 해상도, 픽셀 포맷, 파일 크기, 비트레이트를 전부 확인했습니다.
$ ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,pix_fmt \
-show_entries format=duration,size,bit_rate \
-of default=noprint_wrappers=1 out_h265.mp4
codec_name=hevc
width=1920
height=1080
pix_fmt=yuv420p
duration=5.041667
size=1339866
bit_rate=2126068실측 결과: 파일 크기와 비트레이트
| 코덱 | 설정 | HI 파일 크기 | HI 비트레이트 | LO 파일 크기 | LO 비트레이트 |
|---|---|---|---|---|---|
| H.264 | libx264 crf23 | 3.28MB | 5455kbps | 2.04MB | 2136kbps |
| H.265 | libx265 crf28 | 1.28MB | 2126kbps | 0.95MB | 999kbps |
| VP9 | libvpx-vp9 crf34 | 2.18MB | 3626kbps | 1.71MB | 1791kbps |
| AV1 | libsvtav1 crf35 | 1.43MB | 2373kbps | 1.09MB | 1144kbps |
실제 결과물을 눈으로 비교하면 이렇습니다. 고움직임 소스를 각 코덱의 동일 CRF 근사값으로 인코딩한 5초 클립 네 개입니다.
H.264 (libx264, crf23, 3.28MB)
H.265 (libx265, crf28, 1.28MB)
VP9 (libvpx-vp9, crf34, 2.18MB)
AV1 (libsvtav1, crf35, 1.43MB)
두 소스 모두에서 파일 크기 순위는 H.264가 가장 크고, VP9가 그다음, AV1과 H.265가 가장 작은 순서로 나왔습니다.
다만 이 표만 보고 "H.265가 제일 효율적이다"라고 결론 내리면 안 됩니다. CRF는 코덱 내부의 양자화 강도를 정하는 값이지, 코덱 간에 같은 지각 품질을 보장하는 값이 아니기 때문입니다. 실제로 화질이 얼마나 유지됐는지는 VMAF로 따로 확인해야 합니다.
실측 결과: 인코딩 시간
| 코덱 | HI 인코딩 시간(5.04초 클립) | LO 인코딩 시간(8.00초 클립) |
|---|---|---|
| H.264 | 1.88초 | 0.94초 |
| H.265 | 3.09초 | 1.78초 |
| VP9 | 6.64초 | 4.94초 |
| AV1(SVT, preset 6) | 3.43초 | 1.94초 |
VP9가 가장 느렸는데, 여기서는 -cpu-used(속도/품질 트레이드오프 옵션)를 지정하지 않아서 libvpx-vp9가 기본값인 가장 느리고 품질 우선인 모드로 동작했습니다. 실무에서 VP9를 쓴다면 -cpu-used 2나 4 정도로 속도를 올리는 게 일반적이고, 그 경우 이 표의 VP9 인코딩 시간은 훨씬 짧아질 것으로 예상됩니다.
AV1도 preset 6이 SVT-AV1의 가장 빠른 설정은 아니라서, 이 시간들은 "각 인코더의 절대적인 속도 한계"가 아니라 "이번에 고른 설정을 기준으로 한 상대적인 소요 시간"으로 읽는 게 맞습니다. 클립 길이도 5~8초로 짧아서 인코더 초기화 오버헤드 비중이 실제 서비스 환경보다 크게 잡혔을 가능성도 있습니다.
2. VMAF로 실제 화질 손실 측정하기
파일 크기만으로는 화질을 판단할 수 없으므로, 각 결과물을 원본과 libvmaf 필터로 비교했습니다.
$ ffmpeg -i out_h264.mp4 -i src.mp4 -lavfi libvmaf -f null -
...
[Parsed_libvmaf_0] VMAF score: 99.804115여덟 개 결과물 전부에 같은 방식으로 VMAF를 측정했습니다.
| 코덱 | 설정 | HI VMAF | LO VMAF |
|---|---|---|---|
| H.264 | crf23 | 99.80 | 94.59 |
| H.265 | crf28 | 95.62 | 89.68 |
| VP9 | crf34 | 99.82 | 94.55 |
| AV1 | crf35 | 98.90 | 93.54 |
이 표에서 눈에 띄는 부분은 H.265입니다. 파일 크기는 네 코덱 중 가장 작거나 AV1과 비슷한 수준인데, VMAF는 두 소스 모두에서 가장 낮게 나왔습니다.
H.264와 VP9는 crf23/crf34에서 두 소스 모두 VMAF 99점대(HI)와 94점대(LO)로 거의 원본에 가까운 수준을 유지한 반면, H.265는 crf28에서 HI가 95.62, LO가 89.68로 눈에 띄게 낮습니다.
이건 H.265가 실제로 화질이 나쁜 코덱이라는 뜻이 아니라, 이번에 고른 crf28이라는 값이 다른 코덱의 crf23/34/35보다 상대적으로 더 공격적인 압축 지점에 해당한다는 뜻입니다. 즉 "같은 CRF 근사값"이라는 전제 자체가 코덱 간 직접 비교의 기준으로는 부정확하다는 게 이번 실측에서 드러난 셈입니다.
코덱을 공정하게 비교하려면 CRF 숫자를 맞추는 게 아니라, VMAF 같은 품질 지표를 먼저 맞추고 그 지점에서 파일 크기나 비트레이트를 비교해야 합니다. 이어지는 절에서 이 방식으로 다시 비교해봤습니다.
3. VMAF를 먼저 맞추고 파일 크기를 비교하기
고움직임(HI) 소스를 기준으로, H.265가 crf28에서 기록한 VMAF 95.62에 맞춰 나머지 세 코덱의 CRF를 조정했습니다. 자동화된 탐색 없이 몇 차례 값을 바꿔가며 직접 확인하는 방식으로 진행했습니다.
H.264는 처음에 crf32로 시도했는데 VMAF가 85.23까지 떨어져서 목표보다 훨씬 낮았습니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 32 -pix_fmt yuv420p trial.mp4
$ ffmpeg -i trial.mp4 -i src.mp4 -lavfi libvmaf -f null -
[Parsed_libvmaf_0] VMAF score: 85.231033crf27로 다시 인코딩하자 VMAF 96.23으로 목표 구간에 들어왔습니다. VP9와 AV1도 같은 방식으로 몇 차례 조정한 끝에 아래 값에서 VMAF가 서로 근접한 구간(95.6~97.0)에 도달했습니다.
| 코덱 | CRF | VMAF | 파일 크기 | 비트레이트 |
|---|---|---|---|---|
| H.264 | 27 | 96.23 | 1.96MB | 3254kbps |
| H.265 | 28 | 95.62 | 1.28MB | 2126kbps |
| VP9 | 40 | 96.99 | 1.33MB | 2210kbps |
| AV1 | 40 | 96.16 | 1.01MB | 1675kbps |
품질을 먼저 맞추고 나니 그림이 달라졌습니다. AV1이 가장 작은 파일 크기(1.01MB)를 기록했고, H.265가 근소한 차이로 뒤를 이었습니다(1.28MB). VP9는 H.265보다 살짝 컸고(1.33MB), H.264는 같은 품질을 내는 데 거의 두 배에 가까운 용량(1.96MB)이 필요했습니다.
앞 절의 "같은 CRF 숫자" 비교에서 H.265가 보여준 극단적인 크기 우위는 상당 부분 화질을 더 많이 희생한 결과였고, 화질을 맞추고 나면 AV1이 근소하게 앞서고 H.265와 VP9가 비슷한 수준으로 따라오는 그림이 더 정확합니다. 다만 이 비교는 CRF를 손으로 몇 번 조정해서 맞춘 단일 지점 비교이지, 여러 비트레이트 구간을 스캔한 정식 BD-rate 곡선 비교는 아니라는 점은 감안해야 합니다.
4. Tencent Cloud MPS로 같은 소스 트랜스코딩하기
로컬 FFmpeg 결과를 클라우드 매니지드 트랜스코딩 서비스와 비교하기 위해, 저움직임/고복잡도(LO) 소스를 COS에 올리고 MPS의 ProcessMedia API로 H.264와 H.265 트랜스코딩을 각각 실행했습니다.
먼저 계정에 등록된 트랜스코딩 프리셋을 DescribeTranscodeTemplates로 훑어서 720p H.265 표준 프리셋을 찾았습니다.
resp = call_tc3("mps", "DescribeTranscodeTemplates", "2019-06-12", {"Limit": 100, "Offset": offset})391개 프리셋 중 TEHD-MP4-H265-720P-FollowSource-FPS라는 이름의 공식 프리셋(Definition 328012)을 확인했습니다. "FollowSource"는 원본의 해상도나 프레임레이트를 그대로 따라간다는 의미입니다. H.264 쪽은 이미 알고 있던 720p 표준 프리셋(Definition 100030)을 그대로 썼습니다.
status, body = process_media(
"codec-compare-test/source-cb78283a1a7c4e4f.mp4",
"codec-compare-test/out-h264/",
transcode_definition=100030,
)
# status, body = process_media(..., transcode_definition=328012) # H.265두 태스크 모두 DescribeTaskDetail로 완료를 확인한 뒤 결과 파일을 내려받아 ffprobe로 검증했습니다.
$ ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,profile,width,height,r_frame_rate,pix_fmt \
-show_entries format=duration,size,bit_rate -of default=noprint_wrappers=1 mps_h265.mp4
codec_name=hevc
profile=Main
width=1280
height=720
pix_fmt=yuv420p
r_frame_rate=24/1
duration=8.000000
size=373367
bit_rate=373367여기서 실제로 겪은 함정이 하나 있었습니다. 태스크 요청 시 출력 경로를 codec-compare-test/out-h264/로 지정했는데, 완료된 태스크 상세의 Output.Path를 확인해보니 실제 저장 위치는 codec-compare-test/codec-compare-test/out-h264/100030.mp4로, 요청한 prefix 앞에 입력 파일이 있던 디렉터리 경로가 한 번 더 붙어 있었습니다.
요청한 경로를 그대로 믿고 다운로드를 시도했다면 실패했을 것이고, 폴링 응답에 찍힌 실제 경로를 확인하고 나서야 정확한 위치를 알 수 있었습니다.
또 하나는 프레임레이트입니다. H.264 프리셋(100030)으로 나온 결과물은 원본의 24fps가 25fps로 바뀌어 있었습니다. 표준 프리셋이 고정 프레임레이트를 강제하는 경우가 있다는 걸 ffprobe로 확인한 것인데, 이 때문에 VMAF를 측정할 때 원본과 프레임 수가 어긋나서 그대로 비교하면 왜곡된 점수가 나옵니다.
결과물 쪽에 fps=24 필터를 먼저 적용해서 프레임레이트를 원본과 맞춘 뒤에 비교했습니다.
$ ffmpeg -i mps_h264.mp4 -i src.mp4 -filter_complex "[0:v]fps=24[dist];[dist][1:v]libvmaf" -f null -
[Parsed_libvmaf_1] VMAF score: 91.380256H.265 프리셋(328012)은 원본과 같은 24fps를 그대로 유지해서 별도 보정 없이 측정했습니다.
$ ffmpeg -i mps_h265.mp4 -i src.mp4 -lavfi libvmaf -f null -
[Parsed_libvmaf_0] VMAF score: 87.950937로컬 인코딩과 MPS 프리셋 비교
| 구분 | 코덱 | 파일 크기 | 비트레이트 | VMAF |
|---|---|---|---|---|
| 로컬 FFmpeg (crf23) | H.264 | 2.04MB | 2136kbps | 94.59 |
| MPS 프리셋 (100030) | H.264 | 1.61MB | 1691kbps | 91.38 |
| 로컬 FFmpeg (crf28) | H.265 | 0.95MB | 999kbps | 89.68 |
| MPS 프리셋 (328012) | H.265 | 0.36MB | 373kbps | 87.95 |
두 코덱 모두 MPS 프리셋 쪽 결과물이 로컬 CRF 인코딩보다 크게 작고, VMAF도 소폭 낮게 나왔습니다. H.265는 차이가 특히 컸는데, MPS 프리셋이 로컬 crf28 결과보다 파일 크기는 3분의 1 이하이면서 VMAF는 89.68에서 87.95로 2점가량만 낮아졌습니다.
이 결과를 "MPS가 압도적으로 효율적이다"로 단순화하면 안 됩니다. 로컬 인코딩은 CRF라는 고정 화질 목표로 돌린 것이고, MPS 표준 프리셋은 특정 화질 목표가 아니라 일반적인 전송 대역폭에 맞춘 목표 비트레이트로 설계되어 있을 가능성이 높습니다.
즉 서로 다른 최적화 기준으로 만들어진 결과물을 비교한 것이라, "더 효율적이다"보다는 "프리셋마다 지향하는 지점이 다르다"는 결론이 더 정확합니다. 프리셋이 목표로 하는 화질 수준이 서비스 요구사항과 맞는지는 이렇게 실제로 트랜스코딩해서 VMAF로 확인하는 절차가 필요합니다.
작업에 사용한 COS 오브젝트(codec-compare-test/ 아래 소스 파일과 두 트랜스코딩 결과물)는 확인이 끝난 뒤 전부 삭제했습니다.
정리
이번 실측에서 확인한 것을 정리하면 다음과 같습니다.
- 같은 CRF 근사값으로 인코딩하면 H.265와 AV1이 H.264와 VP9보다 파일이 확연히 작아지지만, 이 비교에는 함정이 있습니다. CRF 스케일은 코덱마다 다르게 정의되어 있어서, 같은 "23 근처" 숫자가 코덱마다 다른 압축 강도를 의미합니다. 실제로 이번 실험에서 H.265의 VMAF가 다른 세 코덱보다 뚜렷하게 낮게 나온 건 H.265가 열등해서가 아니라 고른 CRF 값이 상대적으로 더 공격적이었기 때문입니다.
- VMAF를 먼저 비슷한 수준으로 맞추고 파일 크기를 비교하면 순위가 달라집니다. 고움직임 소스 기준으로 AV1이 가장 작았고, H.265와 VP9가 비슷한 수준, H.264가 가장 컸습니다. 코덱 효율을 공정하게 비교하려면 CRF 숫자가 아니라 품질 지표를 기준으로 맞춰야 한다는 게 이번 실측의 핵심입니다.
- 저움직임/고복잡도 소스에서는 모든 코덱의 VMAF가 고움직임 소스보다 낮게 나왔습니다. 움직임이 적어도 텍스처가 촘촘한 장면은 공간 방향 압축 부담이 커서, 코덱 입장에서는 오히려 압축하기 까다로운 소스라는 걸 실측으로 확인했습니다.
- 인코딩 시간은 VP9가 가장 오래 걸렸는데, 이는 코덱 자체의 한계라기보다 이번에 쓴 기본 속도 설정의 영향이 큽니다. 인코딩 속도를 비교할 때는 각 인코더의 속도/품질 트레이드오프 옵션을 맞추지 않으면 왜곡된 결론에 이르기 쉽습니다.
- 클라우드 매니지드 트랜스코딩 서비스의 표준 프리셋은 로컬에서 특정 CRF로 인코딩한 결과와 다른 기준으로 최적화되어 있을 수 있습니다. 파일 크기만 보고 우열을 판단하지 말고, 실제로 트랜스코딩을 돌려서 화질 지표까지 확인하는 절차가 필요합니다.
코덱 선택은 결국 "어떤 화질을 최소 기준으로 정할 것인가"를 먼저 정하고, 그 기준에서 각 코덱이 어떤 비용(파일 크기, 인코딩 시간, 클라이언트 호환성)을 요구하는지 실측으로 비교하는 문제입니다.
이름값이나 일반적인 압축 효율 순위표만으로 결정하면, 이번 실험에서 본 것처럼 CRF 숫자를 맞췄을 뿐인데 실제로는 화질이 다른 지점을 비교하는 실수를 하기 쉽습니다.