H.264 Profile과 Level, 설정값과 실측값의 간극
개요
H.264 인코딩 설정 화면에는 Profile(Baseline/Main/High)과 Level(1.0부터 5.2까지) 두 항목이 나란히 있는 경우가 많습니다. Profile은 인코더가 어떤 압축 도구를 쓸 수 있는지를 정하고(B-frame, CABAC, 8x8 변환 등), Level은 해상도/프레임레이트/비트레이트/참조 프레임 개수 같은 수치가 넘을 수 없는 상한선을 정합니다.
두 값 모두 "디코더 호환성"과 직결된다는 건 알려져 있지만, 실무에서 막히는 지점은 조금 다릅니다. 소스가 이미 지정한 Level의 한계를 넘는 해상도나 비트레이트를 갖고 있을 때, 인코더가 그 사실을 알아서 처리해주는지, 아니면 잘못된 조합인 채로 그냥 인코딩해버리는지가 막연하다는 점입니다.
이 글에서는 1920x1080 소스를 Baseline Level 3.0, Main Level 4.0, High Level 4.2로 각각 FFmpeg(libx264)에 강제 지정해서 인코딩하고, 특히 Baseline Level 3.0처럼 애초에 1080p를 담을 수 없는 조합을 일부러 넣었을 때 libx264가 정확히 어떻게 반응하는지 stderr 로그와 ffprobe 결과로 확인합니다.
이어서 요청 비트레이트와 VBV 설정은 그대로 둔 채 Level만 1.0으로 극단적으로 낮췄을 때 실제 인코딩 결과가 어떻게 망가지는지도 실측하고, 마지막으로 Tencent Cloud MPS에서 같은 Profile/Level 조합을 API로 걸었을 때 결과가 어떻게 나오는지 비교합니다.
실무 시나리오
VOD 서비스에서 "구형 셋톱박스에서 특정 콘텐츠가 재생이 안 된다"는 문의가 들어왔다고 가정하겠습니다. 셋톱박스 제조사가 배포한 스펙 문서를 확인해보니 "H.264 Baseline Profile, Level 3.0까지 지원"이라고 적혀 있었고, 반면 해당 콘텐츠의 인코딩 프리셋은 High Profile Level 4.2로 잡혀 있었습니다.
원인을 찾은 것 같으니 프리셋을 Baseline Level 3.0으로 바꾸면 될 것 같은데, 여기서 두 가지 의문이 남습니다.
첫째, 이 서비스의 소스 영상은 1920x1080인데, Level 3.0은 원래 그보다 훨씬 작은 해상도(SD급)를 위한 스펙입니다. 인코더에 Baseline Level 3.0을 지정했을 때 1080p 소스를 그대로 밀어 넣으면 인코더가 알아서 해상도를 낮춰주는지, 아니면 규격을 벗어난 채로 그냥 인코딩해버리는지 확인해야 합니다.
후자라면 셋톱박스는 "Level 3.0"이라는 태그만 보고 재생을 시도했다가 여전히 디코딩에 실패할 수 있습니다.
둘째, 클라우드 트랜스코딩 서비스를 함께 쓰고 있다면 같은 Profile/Level 설정이 그 서비스에서도 동일하게 걸리는지, 혹시 API 스펙과 실제 동작이 다른 부분은 없는지도 미리 확인해두는 게 안전합니다. 감으로 설정을 바꾸기 전에, 실제 인코딩 결과의 메타데이터를 찍어보고 확정하는 절차가 필요합니다.
테스트 환경과 소스
- FFmpeg 8.0 (Homebrew 빌드, libx264 포함), macOS, Apple Silicon
- 소스: 1920x1080, 24fps, H.264 High Profile Level 4.0, 오디오 트랙 없음, 5.04초(121프레임)
$ ffprobe -v error -show_entries stream=codec_name,profile,level,width,height,r_frame_rate,bit_rate \
-show_entries format=duration -of default=noprint_wrappers=0 src.mp4
codec_name=h264
profile=High
width=1920
height=1080
level=40
r_frame_rate=24/1
bit_rate=6705527
duration=5.041667소스 자체가 이미 High Profile Level 4.0으로 인코딩돼 있으므로, 이 글에서 시험하는 조합들은 전부 소스를 낮은 Profile/Level로 재인코딩하는 방향입니다.
1. Baseline Profile + Level 3.0으로 1080p 인코딩
Level 3.0은 원래 1080p를 담을 수 있는 스펙이 아닙니다. 이걸 알면서도 일부러 지정해봤습니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 21 \
-profile:v baseline -level 3.0 -bf 0 -pix_fmt yuv420p baseline_l3_bf0.mp4인코딩은 실패하지 않았지만, libx264가 시작하자마자 경고 세 줄을 stderr에 찍었습니다.
[libx264] frame MB size (120x68) > level limit (1620)
[libx264] DPB size (1 frames, 8160 mbs) > level limit (0 frames, 8100 mbs)
[libx264] MB rate (195840) > level limit (40500)
[libx264] profile Constrained Baseline, level 3.0, 4:2:0, 8-bit1920x1080은 120x68 매크로블록(8160개)인데, Level 3.0이 허용하는 프레임당 매크로블록 수는 1620개뿐입니다. 5배 넘게 초과합니다.
더 눈에 띄는 건 두 번째 줄입니다. Level 3.0의 DPB 한도를 매크로블록 수 기준으로 환산하면 8100개인데, 1080p 프레임 하나가 이미 8160개를 차지하기 때문에 "이 Level에서는 참조 프레임을 0개까지만 허용한다"는 계산이 나옵니다.
즉 Level 3.0 스펙을 문자 그대로 지키려면 1080p 프레임은 단 한 장도 디코더 버퍼에 올릴 수 없다는 뜻입니다. MB 처리율(MB rate)도 195,840으로 한도 40,500의 약 4.8배입니다.
경고를 세 줄이나 찍었는데도 libx264는 인코딩을 멈추지 않았고, 거부하지도 않았습니다. 결과물을 ffprobe로 확인했습니다.
$ ffprobe -v error -show_entries stream=profile,level,codec_name,width,height,r_frame_rate,bit_rate \
-of default=noprint_wrappers=0 baseline_l3_bf0.mp4
codec_name=h264
profile=Constrained Baseline
width=1920
height=1080
level=30
r_frame_rate=24/1
bit_rate=8136418해상도는 그대로 1920x1080, 그런데 SPS에 박히는 level_idc는 요청한 그대로 30(Level 3.0)입니다. libx264는 "이 조합은 스펙 위반"이라고 경고만 할 뿐, Level 값을 실제 해상도에 맞게 올려주거나 해상도를 깎아주는 보정을 하지 않습니다.
결과물은 이 셋톱박스 스펙 문서 기준으로 보면 "Level 3.0 태그를 달고 있지만 실제로는 Level 3.0 디코더가 처리할 수 없는 스트림"이 됩니다. 앞서 실무 시나리오에서 나온 첫 번째 의문에 대한 답이 바로 이겁니다. 인코더는 알아서 맞춰주지 않고, 태그만 요청한 대로 찍힌 규격 위반 스트림을 그대로 내보냅니다.
B-frame을 명시적으로 끄지 않아도 Baseline은 B-frame을 안 씁니다
이번엔 -bf 0을 빼고 같은 조합을 다시 인코딩했습니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 21 \
-profile:v baseline -level 3.0 -pix_fmt yuv420p baseline_l3_nobf.mp4libx264 로그의 인코더 파라미터 줄을 보면 -bf를 지정하지 않았는데도 bframes=0, 그리고 cabac=0 weightp=0까지 함께 꺼져 있습니다.
options: cabac=0 ... bframes=0 weightp=0 keyint=250 ...프레임 타입을 직접 세어봐도 B-frame은 한 장도 없습니다.
$ ffprobe -v error -select_streams v:0 -show_entries frame=pict_type -of csv=p=0 baseline_l3_nobf.mp4 | sort | uniq -c
1 I
120 P-profile:v baseline을 주는 순간 libx264는 CABAC, B-frame, weighted prediction, 8x8 변환을 전부 강제로 끕니다. -bf는 이 결과에 영향을 주지 못합니다.
Baseline이 "B-frame을 지원하지 않는다"는 건 인코더가 옵션을 무시하고 넘어가는 게 아니라, Profile 파라미터 자체가 그 도구들에 접근하는 인코더 내부 스위치를 잠가버린다는 뜻입니다. 실제로 두 파일(bf 0 명시/미명시)의 크기는 5,129,045바이트로 완전히 동일했습니다.
2. Main Level 4.0, High Level 4.2: 스펙 내 조합
이번에는 1080p24를 실제로 담을 수 있는 Level로 지정했습니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 21 \
-profile:v main -level 4.0 -pix_fmt yuv420p main_l4.mp4
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 21 \
-profile:v high -level 4.2 -pix_fmt yuv420p high_l42.mp4이번에는 앞서 봤던 세 줄짜리 한도 초과 경고가 전혀 뜨지 않았습니다. Level 4.0/4.2 모두 1080p24의 매크로블록 수(8160)와 처리율(195,840)을 감당할 수 있는 스펙이기 때문입니다. ffprobe 결과도 요청한 그대로 나왔습니다.
| 설정 | profile 태그 | level 태그 | 비트레이트 |
|---|---|---|---|
| Main, Level 4.0 | Main | 40 | 6.20Mbps |
| High, Level 4.2 | High | 42 | 6.39Mbps |
두 인코딩의 x264 파라미터 줄을 비교하면 Profile 차이가 어디서 나는지 바로 보입니다.
# Main: 8x8dct=0 (Main은 8x8 변환을 지원하지 않음)
options: cabac=1 ref=3 ... bframes=3 ... weightp=2 ... 8x8dct=0 ...
# High: 8x8dct=1
options: cabac=1 ref=3 ... bframes=3 ... weightp=2 ... 8x8dct=1 ...Main과 High 둘 다 CABAC, B-frame, weighted prediction은 쓰지만 8x8 정수 변환(8x8dct)은 High에만 켜져 있습니다. 이게 Main과 High를 가르는 유일한 실질적 차이입니다.
여기에 앞서 만든 Baseline Level 3.0(규격 위반) 결과까지 같은 CRF 21 기준으로 비교해봤습니다.
| 설정 | 비트레이트 |
|---|---|
| Baseline, Level 3.0(규격 위반) | 8.14Mbps |
| Main, Level 4.0 | 6.20Mbps |
| High, Level 4.2 | 6.39Mbps |
같은 화질 목표(CRF 21)인데도 Baseline은 Main/High보다 약 28~31% 더 많은 비트레이트가 필요했습니다. CABAC 대신 CAVLC를 쓰고, B-frame과 8x8 변환도 없다 보니 같은 화질을 내는 데 더 많은 비트가 든 것으로 해석하는 게 맞습니다.
Main과 High 사이의 차이(6.20 대 6.39Mbps)는 오히려 Main이 근소하게 낮았는데, 이 소스가 5초 남짓의 짧고 단순한 클립이라 8x8 변환의 이득이 뚜렷하게 드러나지 않은 것으로 보입니다. 8x8dct의 효과는 소스의 텍스처 복잡도에 따라 달라지므로, 이 결과를 "Main이 항상 High보다 효율적이다"로 일반화할 수는 없습니다.
3. Level을 극단적으로 낮추면 태그만 틀리는 게 아니라 결과물도 망가집니다
앞서 Level 3.0 테스트는 CRF(품질 기반) 모드였습니다. 이번엔 비트레이트를 명시적으로 고정하는 CBR 모드에서, 소스 해상도(1080p)와 요청 비트레이트(8Mbps)는 그대로 두고 Level만 1.0으로 지정했습니다. Level 1.0은 원래 QCIF급(176x144 근방) 저해상도/저비트레이트 영상을 위한 스펙입니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium \
-b:v 8000k -maxrate 8000k -bufsize 16000k \
-profile:v high -level 1.0 -pix_fmt yuv420p high_l1_1080p.mp4경고가 이번엔 다섯 줄로 늘었습니다.
[libx264] frame MB size (120x68) > level limit (99)
[libx264] DPB size (4 frames, 32640 mbs) > level limit (0 frames, 396 mbs)
[libx264] VBV bitrate (8000) > level limit (80)
[libx264] VBV buffer (16000) > level limit (218)
[libx264] MB rate (195840) > level limit (1485)
[libx264] profile High, level 1.0, 4:2:0, 8-bitLevel 1.0의 프레임 크기 한도는 99매크로블록(대략 176x144)인데 1080p는 8160매크로블록이니 82배, MB 처리율은 1485 대 195,840으로 132배, VBV 비트레이트 한도는 80kbps인데 요청한 건 8,000kbps로 100배 차이가 납니다. 압도적으로 규격을 벗어난 조합입니다.
결과물을 ffprobe로 확인했습니다.
$ ffprobe -v error -show_entries stream=profile,level,width,height,bit_rate -of default=noprint_wrappers=0 high_l1_1080p.mp4
profile=High
level=10
width=1920
height=1080
bit_rate=1148551level 태그는 요청한 대로 10(Level 1.0)이 박혔습니다. 여기까지는 앞선 Baseline Level 3.0 사례와 같은 패턴입니다. 그런데 이번엔 다른 문제가 하나 더 있었습니다. -b:v 8000k로 8Mbps를 요청했는데 실제 결과물의 비트레이트는 1.15Mbps, 요청치의 약 14%밖에 안 나왔습니다.
이게 소스 자체의 특성 때문인지 확인하려고, Level만 4.2(유효한 값)로 바꾸고 나머지 설정은 전부 동일하게 컨트롤 테스트를 돌렸습니다.
$ ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium \
-b:v 8000k -maxrate 8000k -bufsize 16000k \
-profile:v high -level 4.2 -pix_fmt yuv420p control_l42.mp4$ ffprobe -v error -show_entries stream=bit_rate -of default=noprint_wrappers=0 control_l42.mp4
bit_rate=8055832Level 4.2로는 요청한 8Mbps에 근접한 8.06Mbps가 그대로 나왔습니다. 다른 조건은 전부 동일하고 -level 값 하나만 1.0에서 4.2로 바꿨을 뿐인데 실제 비트레이트가 7배 차이 났습니다.
숫자보다 화면으로 보면 더 확실합니다. 같은 소스, 같은 -b:v 8000k 요청인데 -level 값 하나만 다릅니다.
Level 1.0 강제 지정 (요청 8Mbps → 실제 1.14Mbps)
Level 4.2, 나머지 설정 동일 (요청 8Mbps → 실제 8.16Mbps)
Level 1.0 쪽은 첫 I-frame만 선명하고 그다음부터 블록 노이즈가 눈에 띄게 뭉개집니다. Level 1.0 인코딩의 프레임별 QP를 보면 무슨 일이 있었는지 짐작할 수 있습니다.
[libx264] frame I:1 Avg QP:13.81 size:146535
[libx264] frame P:37 Avg QP:37.36 size: 9626
[libx264] frame B:83 Avg QP:43.50 size: 2673첫 I-frame은 QP 13.81로 사실상 고화질로 찍혔는데, 뒤따르는 P/B-frame은 QP가 37~43대까지 급격히 뛰었습니다.
CBR 모드에서 인코더는 요청받은 VBV 파라미터(8000kbps/16000kb 버퍼)를 그대로 들고 있었지만(출력 side data에도 cpb: bitrate max/min/avg: 8000000/0/8000000이 그대로 찍혀 있었습니다), Level 1.0이라는 선언된 제약과 실제 요구되는 비트레이트 사이의 극단적인 불일치 속에서 레이트 컨트롤이 정상적으로 동작하지 못하고 이후 프레임들을 지나치게 압축한 것으로 보입니다.
정확한 내부 메커니즘까지 단정하기는 어렵지만, 확실한 사실은 이겁니다. Level을 실제 콘텐츠 요구사항과 맞지 않게 강제로 낮추면 메타데이터 태그만 틀리게 나오는 게 아니라, 실제 인코딩 품질과 비트레이트 자체가 예측 불가능하게 망가질 수 있습니다. Level 값은 "그냥 태그"가 아니라 인코더 내부 레이트 컨트롤이 실제로 참조하는 파라미터라는 뜻입니다.
4. Tencent Cloud MPS에서 Profile/Level 검증하기
로컬에서 확인한 내용이 클라우드 트랜스코딩 서비스에서도 같은 방식으로 동작하는지 확인하기 위해, 같은 소스를 COS에 올리고 MPS의 ProcessMedia API로 같은 조합을 시도했습니다.
미리 등록된 템플릿 대신 즉석 파라미터로 지정하려면 RawParameter 필드를 써야 하고, 오디오 트랙이 없는 소스는 RemoveAudio: 1을 명시해야 한다는 점은 이전에 GOP/B-frame을 검증할 때 이미 확인한 내용입니다. 이번에도 같은 패턴을 그대로 적용했습니다.
task = {
"OutputStorage": output_storage,
"OutputObjectPath": out_prefix + "{Definition}.{format}",
"Definition": 0,
"RawParameter": {
"Container": "mp4",
"RemoveAudio": 1,
"VideoTemplate": {
"Codec": "libx264",
"Fps": 24,
"Bitrate": 6000,
"ResolutionAdaptive": "close",
"Width": 1920,
"Height": 1080,
"VideoProfile": "baseline", # 또는 main, high
},
},
}VideoProfile은 요청한 대로 정확히 나옵니다
VideoProfile을 baseline/main/high로 각각 지정해서 세 번 돌렸습니다. 세 태스크 모두 SUCCESS로 끝났고, 결과물을 내려받아 ffprobe로 확인하니 요청한 Profile이 정확히 반영돼 있었습니다.
$ ffprobe -v error -show_entries stream=profile,level,width,height,bit_rate -of default=noprint_wrappers=0 mps_baseline.mp4
profile=Constrained Baseline
level=40
width=1920
height=1080
bit_rate=4546516
$ ffprobe -v error -show_entries stream=profile,level -of default=noprint_wrappers=0 mps_main.mp4
profile=Main
level=40
$ ffprobe -v error -show_entries stream=profile,level -of default=noprint_wrappers=0 mps_high.mp4
profile=High
level=40VideoLevel을 아예 지정하지 않았는데도(자동 결정) 세 결과물 모두 level=40(Level 4.0)으로 통일돼 있었습니다. MPS의 자동 Level 결정 로직이 Profile 종류와 무관하게 이 해상도/프레임레이트 조합에는 Level 4.0을 일괄 적용한 것으로 보입니다.
참고로 Baseline Profile로 Level 4.0을 쓰는 조합 자체는 표준상 허용되는 조합입니다. Baseline은 원래 저사양 기기를 겨냥한 Profile이라 실무에서는 낮은 Level과 함께 쓰는 경우가 많지만, Profile과 Level은 원칙적으로 독립적인 축입니다.
VideoProfile 필드가 잘못된 값을 정상적으로 거부하는지도 확인했습니다. 존재하지 않는 값(extended)을 넣었더니 태스크가 정확히 이 값을 지목하며 실패했습니다.
"Status": "FAIL", "ErrCode": 70000,
"ErrCodeExt": "InvalidParameterValue.VideoProfile(extended)"VideoLevel 값 포맷 인식 실패
VideoLevel을 명시적으로 지정해보려고 여러 표기법을 시도했습니다. FFmpeg에서 쓰는 것과 같은 "3.0", "4.0", "4.2", "1.0"부터 시작해서, 소수점을 뺀 "30", "3", 그리고 "Level3", "level3.0", "L3.0", "3_0"까지 총 아홉 가지 표기를 시도했습니다.
전부 요청 자체는 TaskId를 받으며 정상 접수됐지만, 태스크가 처리되는 과정에서 동일한 패턴으로 실패했습니다.
"ErrCodeExt": "InvalidParameterValue.VideoLevel(3.0)"
"ErrCodeExt": "InvalidParameterValue.VideoLevel(30)"
"ErrCodeExt": "InvalidParameterValue.VideoLevel(Level3)"
...VideoProfile이 잘못된 값(extended)을 줬을 때와 정확히 같은 형태의 에러 메시지입니다. 즉 MPS는 VideoLevel이라는 필드 이름 자체는 인식하고 있고(필드가 없다는 에러가 아니라 "이 값이 유효하지 않다"는 에러), 다만 이번에 시도한 아홉 가지 표기법 중 어느 것도 유효한 값으로 받아들이지 않았습니다.
로컬에 설치된 Tencent Cloud Python SDK(tencentcloud-sdk-python-mps 3.1.89)의 VideoTemplateInfo 모델 소스를 확인해봐도 VideoLevel은 "인코더 레벨, 기본값은 자동("")"이라는 설명만 있을 뿐, 허용되는 값 목록은 문서화돼 있지 않았습니다.
VideoLevel을 아예 비워두면(자동) 정상적으로 동작한다는 건 앞서 확인했으니, 이건 MPS가 Level 지정 자체를 지원하지 않는다는 뜻은 아닙니다. 다만 RawParameter 경로로 즉석 인코딩을 걸 때 이 필드에 어떤 문자열을 넣어야 하는지는 공개된 API 문서와 SDK 주석만으로는 확인할 수 없었고, 실측으로 시도한 아홉 가지 합리적인 표기법 모두 거부당했습니다.
특정 Level을 반드시 명시적으로 고정해야 하는 요구사항이 있다면, 콘솔에서 등록한 템플릿으로 지정하는 방법이 있는지, 혹은 RawParameter에서 이 필드에 기대하는 정확한 값 포맷이 무엇인지 담당 계정팀이나 기술지원팀에 문의해서 확인하는 걸 권합니다.
테스트에 쓴 COS 오브젝트(h264-profile-level-test/ 아래 소스 파일과 트랜스코딩 결과물)는 검증이 끝난 뒤 전부 삭제했습니다.
정리
- Level은 해상도/프레임레이트/비트레이트/DPB(참조 프레임 버퍼)가 넘을 수 없는 상한선을 정의하지만, libx264는 이 한도를 실제로 위반해도 인코딩을 막지 않습니다. 콘솔에 경고를 출력할 뿐, 결과물의 profile/level 태그는 사용자가 요청한 값 그대로 찍힙니다. 1080p 소스에 Baseline Level 3.0을 강제로 지정하면 "Level 3.0" 태그가 붙은, 실제로는 Level 3.0 디코더가 재생할 수 없는 스트림이 만들어집니다.
- Baseline Profile을 지정하면
-bf옵션을 명시하지 않아도 CABAC, B-frame, weighted prediction, 8x8 변환이 전부 강제로 꺼집니다. Baseline이 B-frame을 못 쓰는 건 사용자가 옵션을 안 줘서가 아니라 Profile 자체가 인코더의 해당 기능을 잠그기 때문입니다. - 같은 CRF에서 Baseline은 Main/High보다 약 28~31% 더 높은 비트레이트가 필요했습니다(8.14Mbps 대 6.20~6.39Mbps). CABAC/B-frame/8x8 변환 같은 압축 도구가 빠지는 대가입니다. Main과 High의 차이는 8x8 변환 지원 여부(
8x8dct)뿐이며, 이 소스처럼 짧고 단순한 클립에서는 그 효과가 뚜렷하게 드러나지 않았습니다. - Level을 콘텐츠 요구사항보다 극단적으로 낮게 강제 지정하면(Level 1.0 + 1080p + 8Mbps 요청) 단순히 잘못된 메타데이터 태그만 나오는 게 아니라, 실제 인코딩 결과의 비트레이트 자체가 예측 불가능하게 무너집니다. 동일 설정에서 Level만 4.2로 바꾼 컨트롤 테스트는 요청한 8Mbps에 근접했지만, Level 1.0에서는 그 14% 수준인 1.15Mbps밖에 나오지 않았습니다.
- Tencent Cloud MPS는
RawParameter.VideoTemplate.VideoProfile을 baseline/main/high로 정확히 반영했고, 잘못된 값은InvalidParameterValue.VideoProfile(...)로 명확히 거부했습니다. 같은 필드 그룹의VideoLevel은 아홉 가지 표기법을 실측했지만 전부InvalidParameterValue.VideoLevel(...)로 거부됐고, 공개 문서와 SDK에도 유효값 목록이 없었습니다.VideoLevel을 비워두면(자동) 정상 동작하며, 이번 테스트에서는 Profile 종류와 무관하게 Level 4.0이 일괄 적용됐습니다. - 셋톱박스 호환성 문제처럼 Profile/Level을 낮춰야 하는 상황에서는, 설정값을 바꾸는 것만으로 안심할 수 없습니다. 특히 소스 해상도가 지정하려는 Level의 스펙을 실제로 만족하는지, 그리고 인코더가 그 조합을 위반했을 때 조용히 경고만 남기고 넘어가는 건 아닌지를 ffprobe로 직접 확인하는 절차가 필요합니다. 태그가 요청한 값과 일치한다고 해서 그 스트림이 해당 Level 스펙을 실제로 만족한다는 뜻은 아닙니다.