Skip to content

FFmpeg 직접 제어 vs MPS 매니지드 트랜스코딩 ​

개요 ​

이 시리즈에서는 코덱, 비트레이트 컨트롤, GOP와 B-frame, Profile과 Level, preset, chroma subsampling과 bit depth, HDR 색공간 메타데이터를 하나씩 붙잡고 FFmpeg와 Tencent Cloud MPS 양쪽에서 실제로 인코딩을 돌려봤습니다. 매번 질문의 형태는 비슷했습니다. 사용자가 입력한 값이 결과물에 그대로 찍히는지, 아니면 어딘가에서 다른 값으로 바뀌는지, 그리고 바뀐다면 어떤 조건에서 바뀌는지입니다.

여덟 편을 따로 읽으면 각자의 파라미터 이야기로 끝나지만, 나란히 놓고 보면 반복되는 패턴이 보입니다. FFmpeg는 libx264나 libx265 같은 인코더 라이브러리에 커맨드라인 옵션을 거의 그대로 전달하는 경로이고, 그래서 사용자가 지정한 값이 인코더 내부까지 왜곡 없이 전달됩니다. 값이 스펙을 벗어나도 인코더가 경고만 찍고 그대로 밀어붙이는 경우도 실측으로 여러 번 확인했습니다.

반면 MPS는 사용자 요청을 받아 내부 트랜스코딩 파이프라인에 넘기는 매니지드 서비스라서, 요청한 값이 결과물에 반영되는 과정에 서비스 자체의 기본 정책과 검증 로직이 한 겹 끼어듭니다. 이 글은 그 반복되는 패턴을 파라미터별로 정리하고, 왜 이런 차이가 구조적으로 자연스러운지, 그리고 실무에서 어느 경로를 언제 고르면 되는지를 다룹니다.

파라미터별 종합 표 ​

각 행의 근거는 시리즈의 해당 글에서 실측한 내용입니다. 숫자와 필드명은 실제로 확인한 값을 그대로 인용했습니다.

파라미터FFmpeg 직접 제어MPS 매니지드 트랜스코딩
코덱/압축 효율같은 CRF 숫자를 넣어도 코덱마다 정의가 달라 압축 강도가 다르게 나옵니다. 화질 지표(VMAF)를 먼저 맞추고 크기를 비교해야 공정합니다표준 프리셋은 CRF 같은 고정 화질 목표가 아니라 별도의 전송 대역폭 기준으로 설계돼 있어서, 같은 코덱이라도 로컬 CRF 인코딩과는 다른 지점에서 최적화됩니다
비트레이트 컨트롤minrate/maxrate/bufsize로 VBV 버퍼 창을 직접 좁히거나 넓혀서 CBR에 가까운 동작부터 완전 가변까지 세밀하게 조절됩니다VideoTemplate.Bitrate는 정확한 상한이 아니라 레이트 컨트롤이 참고하는 기준점처럼 동작합니다. 같은 800이라는 값이 720p에서는 114%(909kbps), 480p 다운스케일에서는 62%(494kbps)로 나왔습니다
GOP-g/-keyint_min으로 프레임 단위까지 정확하게 키프레임 위치를 고정합니다. -force_key_frames로 특정 시점만 따로 지정하는 것도 가능합니다RawParameter.VideoTemplate.Gop로 지정한 값이 로컬과 동일한 규칙(프레임 수를 프레임레이트로 나눈 정확한 초 간격)으로 반영됩니다. 다만 스트림 시작 구간만 짧게 잡는 별도 필드는 없습니다
Startup GOP-force_key_frames에 시점을 나열하고 -g를 매우 크게 올려 자동 삽입을 무력화하면, 지정한 시점에 소수점까지 정확히 키프레임이 찍힙니다콘솔에 "GOP Duration at Startup"이라는 개념이 노출돼 있지만, DescribeTranscodeTemplates가 돌려주는 VideoTemplate 스키마에는 시작 구간과 안정화 구간을 따로 지정하는 필드가 없습니다
B-frame-bf는 개수를 고정하는 값이 아니라 상한입니다. 적응형 배치(b_strategy)를 끄고 고정 패턴을 만들려면 -b_strategy 0을 같이 줘야 합니다즉석 RawParameter 인코딩에서 서버가 되돌려주는 VideoTemplate 스키마에는 B-frame 개수 필드가 없습니다. 다만 계정에 등록된 프리셋 템플릿을 조회하면 Bframes 필드 자체는 스키마에 존재하는 것으로 확인됩니다. 템플릿 등록 경로와 즉석 커스텀 경로 사이에 노출되는 필드 범위가 다르다는 뜻입니다
Profile-profile:v가 CABAC, B-frame, weighted prediction, 8x8 변환 같은 인코더 내부 도구 접근 자체를 잠급니다. Baseline은 -bf를 명시하지 않아도 B-frame이 강제로 꺼집니다VideoProfile은 baseline/main/high 값이 그대로 반영되고, 잘못된 값(extended)은 InvalidParameterValue.VideoProfile(extended)로 명확히 거부됩니다
Level스펙을 벗어난 조합(1080p에 Baseline Level 3.0 등)을 줘도 인코딩을 막지 않고 경고만 찍은 뒤 요청한 태그를 그대로 씁니다. 극단적으로 낮추면 태그만 틀리는 게 아니라 실제 비트레이트와 화질도 무너집니다VideoLevel은 아홉 가지 표기법("3.0", "30", "Level3" 등)을 전부 InvalidParameterValue.VideoLevel(...)로 거부했습니다. 값을 비워 자동 결정에 맡기면 Profile 종류와 무관하게 Level 4.0이 일괄 적용됐습니다
Preset(속도/화질 트레이드오프)ultrafast~veryslow가 움직임 추정 범위와 RDO 탐색 강도를 조절합니다. CRF 고정 시엔 파일 크기가, 비트레이트 고정 시엔 화질(VMAF)이 벌어집니다x264 preset과 동일한 필드는 없습니다. 가장 가까운 대체재는 TEHD(극속고화질) 기능에 딸린 CompressType(ultra/standard/high/low_compress)이고, ScenarioBased: 1을 TEHD 없이 지정하면 Only supports TEHD로 거부됩니다
Chroma subsamplinglibx264/265 모두 4:2:2, 4:4:4를 지원하지만 프로파일(high422, high444)을 정확히 맞춰야 합니다. 이미 4:2:0인 소스는 4:4:4로 재인코딩해도 손실이 되살아나지 않습니다391개 기본 프리셋 중 VideoProfile이 high422/high444인 템플릿은 0개였고, RawParameter로 high444를 직접 요청해도 InvalidParameterValue.VideoProfile(high444)로 거부됐습니다
Bit depth-pix_fmt yuv420p10le로 8bit/10bit를 자유롭게 오갈 수 있고, 실제로 bits_per_raw_sample=10까지 정직하게 찍힙니다VideoTemplate.BitDepth를 10으로 지정하면 요청이 받아들여지고, 결과물도 bits_per_raw_sample=10인 진짜 10bit로 나옵니다
색공간 메타데이터(HDR)사용자가 명시적으로 덮어쓰지 않는 한, 코덱이 바뀌거나 비트 심도가 바뀌어도 입력의 color_primaries/color_transfer/color_space 태그가 그대로 출력까지 전달됩니다입력 파일을 분석해 HdrType: hdr10까지는 정확히 판정하지만, 기본 RawParameter 트랜스코딩 경로를 거치면 BitDepth: 10으로 비트 심도를 유지한 결과물에서도 색공간 태그가 전부 unknown으로 초기화됩니다

차이가 나는 이유 ​

여덟 편의 실측을 겹쳐보면 이 차이가 어느 한쪽의 완성도 문제가 아니라 두 도구가 애초에 다른 층위에서 동작하도록 설계됐다는 사실에서 나온다는 게 분명해집니다.

FFmpeg는 libx264, libx265 같은 인코더 라이브러리를 커맨드라인 옵션 하나로 직접 호출하는 도구입니다. -profile:v, -level, -g, -bf 같은 옵션은 인코더 내부 파라미터 구조체에 거의 1대1로 매핑되고, 그래서 값이 스펙을 벗어나도 인코더가 그 값을 그대로 받아 처리합니다.

이번 시리즈에서 Level 1.0에 1080p를 강제로 밀어 넣었을 때 인코딩이 실패하지 않고 대신 결과물의 화질이 예측 불가능하게 무너진 것도 이 구조 때문입니다. 인코더는 "이 조합은 위험하다"는 경고를 콘솔에 남길 뿐, 사용자의 의도를 넘겨짚어 값을 대신 고쳐주지 않습니다. 이건 결함이 아니라 인코더 라이브러리 자체의 설계 철학입니다. 최종 판단은 호출하는 쪽이 진다는 전제로 만들어져 있습니다.

MPS는 이런 인코더 위에 트랜스코딩 파이프라인을 얹은 매니지드 서비스입니다. 이 파이프라인의 첫 번째 우선순위는 배포 호환성과 운영 안정성입니다.

이 시리즈에서 반복해서 나타난 세 가지 동작, Bitrate가 상한이 아니라 화질 기준점으로 동작하는 것, VideoLevel을 비워두면 해상도/프레임레이트 조합에 맞춰 자동으로 적당한 값(이번 시리즈 실측 기준 Level 4.0)을 골라주는 것, 기본 트랜스코딩 경로에서 색공간 태그를 매번 초기화하는 것은 모두 같은 방향을 가리킵니다. 사용자가 세부 값을 하나하나 정확히 맞추지 않아도 재생 가능하고 호환성 있는 결과물이 나오도록, 서비스가 값을 검증하고 필요하면 자체 판단을 얹는 구조입니다.

VideoProfile처럼 표준화된 값 집합(baseline/main/high)은 정확히 검증하고 반영하면서, VideoLevel처럼 표기법이 통일돼 있지 않고 잘못 지정하면 재생 실패로 직결되는 값은 아예 자동 결정에 맡기는 쪽을 택한 것도 같은 맥락으로 읽힙니다. 색공간 태그를 초기화하는 것도, 표준 트랜스코딩 경로를 거친 산출물에 원본과 다른 인코딩 파라미터가 적용된 이상 원본의 색공간 메타데이터를 그대로 물려주는 것보다는 명확히 리셋하는 편이 다운스트림 재생기 입장에서 더 안전하다는 판단으로 볼 수 있습니다.

RawParameter와 OverrideParameter를 구분해서 받는 API 구조도 같은 설계 방향의 연장선입니다. Definition이 실제 템플릿 ID를 가리킬 때는 OverrideParameter로 일부 필드만 덮어쓰고, Definition: 0으로 완전히 새로운 커스텀 인코딩을 요청할 때는 RawParameter를 쓰도록 나뉘어 있습니다.

이 시리즈 곳곳에서 이 구분을 놓쳐 Container is null 에러를 겪었는데, 이건 API가 불친절해서가 아니라 등록된 템플릿 기반 운영과 즉석 커스텀 인코딩이라는 서로 다른 두 경로를 명확히 분리해두고 있다는 뜻입니다. 매니지드 서비스가 대규모 트랜스코딩 큐를 안정적으로 운영하려면 이런 경로 분리가 필요합니다.

실측: 같은 소스에 최대한 동일한 파라미터로 두 경로 비교하기 ​

지금까지는 이전 여덟 편에서 나온 결과를 종합한 것이고, 이번 글을 위해 하나의 소스에 가능한 한 동일한 파라미터 세트를 FFmpeg와 MPS 양쪽에 한 번씩 실제로 적용해봤습니다.

소스는 Startup GOP 글에서 쓴 것과 같은 1280x720, 24fps, H.264 8bit, 오디오 트랙 없음, 5.04초(121프레임) 클립입니다. 두 경로 모두에 H.264, 화질 기준값 23(FFmpeg는 -crf 23, MPS는 Vcrf: 23인 VCRF 모드), GOP 48프레임(2초), Profile High를 지정했습니다.

# FFmpeg
ffmpeg -y -i src.mp4 -an -c:v libx264 -preset medium -crf 23 \
    -profile:v high -g 48 -keyint_min 48 -sc_threshold 0 \
    -pix_fmt yuv420p out_local_ffmpeg.mp4
python
# MPS RawParameter (Definition=0, RemoveAudio=1)
video_template = {
    "Codec": "libx264", "Fps": 24,
    "Mode": "VCRF", "Vcrf": 23, "Bitrate": 8000,
    "ResolutionAdaptive": "close", "Width": 1280, "Height": 720,
    "Gop": 48, "VideoProfile": "high",
}

MPS 쪽은 VCRF 모드에서도 Bitrate가 필수 입력이라, CBR/VBR/ABR/CRF 글에서 확인한 대로 넉넉한 상한(8000kbps)을 얹어 사실상 화질 기준값(Vcrf)이 결과를 결정하도록 뒀습니다.

두 결과물을 ffprobe로 나란히 확인한 결과입니다.

항목FFmpeg (-crf 23)MPS (Vcrf: 23)
profileHighHigh
level(자동 결정)3.13.1
pix_fmtyuv420pyuv420p
bits_per_raw_sample88
키프레임 위치0.0s, 2.0s, 4.0s0.0s, 2.0s, 4.0s
프레임 타입 분포I 3 / P 33 / B 85I 3 / P 33 / B 85
총 프레임 수121121
파일 크기1,472,353 bytes1,336,567 bytes
평균 비트레이트2,332,609 bps2,117,009 bps
color_primaries/transfer/spacebt709 / bt709 / bt709bt709 / bt709 / bt709

Profile, Level(자동 결정 값), pix_fmt, bit depth, GOP 위치, 프레임 타입 분포, 색공간 태그까지 사실상 완전히 일치했습니다.

이 소스는 원래 BT.709 SDR이고 두 경로 모두 색공간 옵션을 별도로 건드리지 않았기 때문에, HDR 색공간 메타데이터 글에서 확인했던 "MPS 기본 경로는 색공간 태그를 초기화한다"는 동작은 이번처럼 원본이 이미 SDR/BT.709인 경우에는 겉으로 드러나지 않습니다. 그 차이는 BT.2020/PQ로 태깅된 소스를 트랜스코딩할 때만 나타납니다.

두 결과물 사이에 실제로 벌어진 차이는 파일 크기와 비트레이트입니다. 같은 화질 기준값(23) 아래에서 MPS 쪽이 파일 크기로 약 9.2%, 비트레이트로 약 9.2% 더 작게 나왔습니다.

FFmpeg의 -crf와 MPS의 Vcrf는 둘 다 "화질을 고정하고 필요한 비트를 그때그때 쓴다"는 개념은 같지만, 같은 숫자 23이 두 인코딩 경로 안에서 정확히 같은 양자화 강도를 의미한다는 보장은 없습니다. 코덱 비교 글에서 코덱 간 CRF 스케일이 다르다는 걸 확인했던 것과 같은 이유로, 화질 기준값 이름과 숫자가 같다고 해서 내부 레이트-왜곡 최적화 경로까지 동일하다고 볼 수는 없습니다.

이 정도의 차이는 두 경로가 "같은 파라미터 이름 아래 서로 다른 인코딩 구현체"라는 걸 보여주는 자연스러운 결과로 읽는 게 맞고, 어느 한쪽이 틀렸다고 볼 근거는 아닙니다.

이번 실험에 쓴 COS 오브젝트(ffmpeg-vs-mps-test/ 아래 소스 파일과 트랜스코딩 결과물)는 검증이 끝난 뒤 전부 삭제했습니다.

실무 가이드: 언제 FFmpeg를, 언제 MPS를 쓸지 ​

이 시리즈 전체를 관통하는 결론은 두 도구 중 하나가 우월하다는 게 아니라, 요구사항이 놓인 층위에 따라 맞는 도구가 다르다는 것입니다.

FFmpeg로 직접 제어하는 편이 맞는 경우는 이렇습니다. 방송 송출망처럼 순간 비트레이트 상한을 하드 캡으로 지켜야 해서 VBV 버퍼(minrate/maxrate/bufsize)를 세밀하게 설계해야 할 때, 구형 셋톱박스처럼 특정 Profile/Level 조합을 정확히 맞추고 그 결과를 ffprobe로 검증해야 할 때, HDR 마스터의 색공간 태그를 파이프라인 전 구간에서 한 글자도 바뀌지 않게 유지해야 할 때, 라이브 스트림 인제스트 단계에서 -force_key_frames로 Startup GOP 구조를 직접 만들어야 할 때입니다.

이런 요구사항은 인코더 내부 파라미터를 있는 그대로 노출해주는 경로가 아니면 애초에 표현할 방법이 없습니다.

MPS로 매니지드 트랜스코딩을 쓰는 편이 맞는 경우는 이렇습니다. 대량의 콘텐츠를 서버 인프라 관리 부담 없이 안정적으로 처리해야 할 때, Profile처럼 표준화된 값으로 기기 호환성을 맞추면 되고 Level까지 프레임 단위로 미세 조정할 필요는 없을 때, 화질 기준값 하나로 비트레이트를 자동으로 맞춰주는 편이 오히려 예측 가능한 운영에 유리할 때입니다.

이번 실측에서 본 것처럼 VideoProfile, Gop, BitDepth 같은 표준화된 파라미터는 요청한 값이 정확하게 반영되기 때문에, 기본 옵션 범위 안에 있는 요구사항이라면 인코더 내부를 직접 만지지 않고도 원하는 결과를 얻을 수 있습니다.

두 도구 중 하나만으로 부족한 지점도 분명히 있습니다. MPS는 표준 VideoTemplate 필드 범위 밖에서도 TEHD(극속고화질) 기능, HEVC 10bit HDR10 MOV, ProRes+MOV 같은 고급 커스텀 규격을 별도로 지원합니다. 이 시리즈에서 4:2:2/4:4:4 chroma subsampling, Startup GOP 전용 필드, x264 preset과 동일한 탐색 강도 옵션이 기본 스키마에 없다고 확인한 것들도, 표준 API 범위 밖에서는 담당 계정팀이나 기술지원팀과 협의해 커스텀 트랜스코딩 파라미터로 풀 수 있는 여지가 있습니다.

반대로 FFmpeg 쪽에서 아쉬운 지점은 인프라입니다. 인코딩 로직은 완전히 통제할 수 있지만, 그 로직을 대규모 큐로 얼마나 안정적으로 운영할지는 별도로 만들어야 합니다.

결국 이 선택은 "어느 도구가 더 나은가"가 아니라 "이 요구사항이 인코더 내부 파라미터 수준의 정밀 제어를 요구하는가, 아니면 검증된 표준값과 운영 안정성을 요구하는가"를 먼저 구분하는 문제입니다. 이 시리즈에서 여덟 개 파라미터를 하나씩 실측해온 이유도 그 구분선이 어디에 있는지를 감이 아니라 숫자로 확인하기 위해서였습니다.

基于 VitePress 构建 · 部署于腾讯云 EdgeOne Pages