H.264(AVC) 실무: 프로파일·레벨 계산, NAL 구조, FFmpeg 인코딩과 재생 안 되는 파일 고치기

이 글의 핵심

H.264는 압축 효율로는 HEVC·AV1에 밀리지만, 거의 모든 기기에 하드웨어 디코더가 있다는 이유로 여전히 기본 트랙입니다. 이 글은 압축 원리와 프로파일·레벨 계산, NAL·SPS·PPS 비트스트림 구조를 먼저 정리하고, libx264 CRF·preset과 NVENC 설정, HLS 키프레임 정렬, faststart, 그리고 '내 PC에선 되는데 브라우저에선 안 나오는' 파일의 흔한 원인까지 FFmpeg 명령과 함께 다룹니다.

들어가며

H.264 / AVC(Advanced Video Coding)는 2003년 표준화 이후 가장 널리 배포된 손실 압축 비디오 코덱입니다. 방송, OTT, 스마트폰 카메라, 화상 회의, 게임 녹화까지 거의 모든 기기에 하드웨어 디코더가 들어 있어서, “한 번 인코딩해 두면 어디서든 재생된다”는 기준은 지금도 H.264를 가리킵니다.

압축 효율은 HEVC·AV1보다 낮고, 특허 라이선스도 고려해야 합니다. 그런데도 실무에서 H.264를 계속 다루게 되는 이유는 호환성 문제가 가장 적은 코덱이기 때문이고, 역설적으로 H.264 관련 문제의 대부분도 호환성에서 나옵니다. 제가 H.264 작업에서 시간을 가장 많이 쓴 건 화질 튜닝이 아니라 “이 파일이 왜 저 기기에서는 안 나오지”를 추적하는 일이었습니다. 그래서 이 글은 압축 원리를 짧게 정리한 뒤, 프로파일·레벨·비트스트림 구조처럼 호환성 문제를 진단하는 데 필요한 지식과 바로 쓸 수 있는 FFmpeg 설정에 비중을 둡니다.


코덱 개요

역사

H.264는 ITU-T VCEG와 ISO/IEC MPEG의 공동 팀 JVT(Joint Video Team)에서 만들었고, ITU-T H.264와 MPEG-4 Part 10이라는 두 이름으로 발행됐습니다. MPEG-2와 MPEG-4 Part 2(DivX/Xvid 계열) 대비 같은 화질에서 대략 절반의 비트레이트를 목표로 설계됐으며, 슬라이스·다중 참조 프레임·NAL 계층처럼 네트워크 전송과 저지연을 고려한 도구가 많이 들어 있습니다.

기술적 특징

  • 매크로블록 기반: 16×16 루마 블록(4:2:0이면 크로마는 8×8) 단위로 인코딩합니다.
  • 가변 블록 모션 보상: Inter 예측에서 16×16부터 4×4까지 파티션을 나눠 움직임에 맞춥니다.
  • 다중 참조·B 프레임: 여러 과거·미래 프레임을 참조해 시간 중복을 제거합니다.
  • 인트라 예측: 4×4, 16×16 모드(High 프로파일은 8×8 추가)로 같은 프레임 안의 공간 상관을 활용합니다.
  • 인루프 디블로킹 필터: 블록 경계 아티팩트를 줄이고, 필터링된 프레임을 다음 예측의 참조로 씁니다.

프로파일

프로파일은 인코더가 쓸 수 있는 코딩 도구를 제한합니다. 디코더는 자기가 지원하는 프로파일의 스트림만 보장하므로, 결국 “어떤 기기에서 재생할 것인가”의 문제입니다.

프로파일특징흔한 사용처
Constrained BaselineB 프레임·CABAC 없음화상 회의, WebRTC, 아주 오래된 모바일
MainB 프레임, CABAC, 인터레이스방송, 일반 VOD
HighMain + 8×8 변환·8×8 인트라 예측, 양자화 행렬Blu-ray, 오늘날 대부분의 VOD·웹 영상
High 10 / 4:2:2 / 4:4:410비트 이상, 크로마 서브샘플링 완화편집·납품용. 브라우저·대부분의 HW 디코더 미지원

FFmpeg의 -profile:v baseline은 실제로 Constrained Baseline을 만듭니다. 오늘날 Baseline이 필요한 경우는 WebRTC 호환 정도이고, 일반 배포는 High로 충분합니다.

표의 마지막 줄이 실무에서 가장 자주 사고가 나는 부분입니다. 카메라나 편집 툴에서 나온 4:2:2·10비트 원본을 -pix_fmt 없이 libx264로 인코딩하면, libx264는 원본 포맷을 유지하려고 High 4:2:2나 High 10으로 인코딩합니다. 편집 PC의 VLC에서는 잘 나오니 문제를 모르고 배포했다가, 브라우저에서는 소리만 나거나 검은 화면이 나오는 일을 겪었습니다. 배포용 H.264는 항상 -pix_fmt yuv420p를 명시하는 습관이 이 문제를 원천적으로 막습니다.

레벨과 MBPS 계산

레벨(Level)은 디코더가 감당해야 하는 처리량 상한입니다. 핵심 값은 초당 매크로블록 수(MaxMBPS), 프레임당 매크로블록 수(MaxFS), 최대 비트레이트(MaxBR)입니다.

레벨MaxMBPSMaxFSMaxBR (Main)대표 용도
3.1108,0003,60014 Mbps720p30
4.0245,7608,19220 Mbps1080p30
4.1245,7608,19250 Mbps1080p30 고비트레이트 (Blu-ray)
4.2522,2408,70450 Mbps1080p60
5.1983,04036,864240 Mbps4K30
5.22,073,60036,864240 Mbps4K60

(High 프로파일의 MaxBR은 표 값의 1.25배입니다.)

1080p는 높이가 16의 배수가 아니라 1088로 코딩되므로 120 × 68 = 8,160 매크로블록이고, 30fps면 초당 244,800개입니다. Level 4.0의 상한이 245,760이라 1080p30은 4.0에 겨우 들어가고, 1080p60은 4.2가 필요합니다. 4.1은 4.0과 처리량이 같고 비트레이트 상한만 높다는 점을 헷갈리기 쉬운데, “1080p60을 -level 4.1로 인코딩했더니 일부 셋톱박스에서 재생이 안 된다”는 문제는 대부분 이 계산에서 나옵니다. 인코더에 따라 레벨 제한을 넘는 설정을 받아도 경고만 내고 인코딩하는 경우가 있으니, 플래그 값만 믿지 말고 ffprobe로 결과 파일의 실제 레벨을 확인하는 것이 안전합니다.


압축 원리

Intra / Inter 예측

  • Intra(I 슬라이스): 같은 프레임 안에서만 예측합니다. 키프레임과 장면 전환 직후에 비트를 많이 씁니다.
  • Inter(P/B 슬라이스): 이미 디코딩된 프레임에서 블록 단위 모션 벡터로 예측하고, 예측과 실제의 차이(잔차)만 전송합니다. 움직임이 단순하고 예측 가능할수록 비트가 줄어듭니다.

변환과 양자화

잔차 블록은 DCT를 근사한 정수 변환(4×4, High는 8×8도)으로 주파수 영역으로 옮긴 뒤 양자화로 정밀도를 낮춥니다. 정수 변환이라 인코더와 디코더의 계산 결과가 정확히 일치하고, 예전 코덱에서 문제였던 역변환 오차 누적이 없습니다. 양자화 파라미터(QP)가 클수록 파일은 작아지지만 밴딩과 블러가 늘어납니다. H.264의 QP는 6 증가할 때마다 양자화 스텝이 두 배가 되는 구조입니다.

x264의 CRF(Constant Rate Factor)는 프레임마다 QP를 직접 고정하는 대신, 움직임이 많아 사람 눈이 디테일을 덜 느끼는 장면에서는 QP를 올리고 정적인 장면에서는 낮춰 “체감 화질이 일정하게” 비트를 배분하는 방식입니다. 그래서 같은 CRF라도 영상 내용에 따라 파일 크기가 크게 달라집니다.

엔트로피 코딩

H.264는 CAVLC(문맥 적응 가변 길이 부호)와 CABAC(문맥 적응 이진 산술 부호) 두 가지를 제공합니다. CABAC이 보통 10% 이상 비트를 절약하지만 디코딩 부하가 크고 병렬화가 어렵습니다. Constrained Baseline은 CAVLC만 씁니다.

flowchart LR
  subgraph input [입력]
    YUV[YUV 픽셀]
  end
  subgraph pred [예측]
    Intra[Intra 예측]
    Inter[Inter 예측]
  end
  subgraph residual [잔차 처리]
    TQ[정수 변환 + 양자화]
  end
  subgraph bits [비트스트림]
    EC[CAVLC / CABAC]
    BS[NAL 유닛]
  end
  YUV --> Intra
  YUV --> Inter
  Intra --> TQ
  Inter --> TQ
  TQ --> EC
  EC --> BS

비트스트림 구조: NAL·SPS·PPS

인코딩 옵션만 다룰 때는 몰라도 되지만, 스트림을 자르거나 붙이고 RTP·HLS로 보내거나 “재생이 안 되는 파일”을 디버깅할 때는 비트스트림 구조를 알아야 원인이 보입니다.

NAL 유닛과 두 가지 포장 방식

H.264 비트스트림은 NAL(Network Abstraction Layer) 유닛의 연속입니다. 같은 NAL 유닛을 포장하는 방식이 두 가지라 여기서 문제가 많이 생깁니다.

  • Annex B: NAL 유닛 앞에 시작 코드(00 00 00 01 또는 00 00 01)를 붙여 구분합니다. .h264 원시 파일, MPEG-TS(HLS), 많은 하드웨어 인코더 출력이 이 형식입니다.
  • AVCC: 시작 코드 대신 길이 필드(보통 4바이트, 빅 엔디안)를 붙이고, SPS/PPS는 스트림 안이 아니라 컨테이너 헤더(avcC 박스)에 따로 둡니다. MP4·MKV가 이 형식입니다.

MP4에서 뽑은 스트림을 그대로 TS muxer나 디코더 API에 넣으면 시작 코드가 없어 인식이 안 됩니다. FFmpeg에서는 비트스트림 필터로 변환합니다.

# MP4(AVCC) → 원시 Annex B 스트림
ffmpeg -i input.mp4 -c:v copy -bsf:v h264_mp4toannexb -an output.h264

최근 FFmpeg는 MP4에서 TS로 -c copy할 때 이 필터를 자동으로 넣어 주지만, 직접 디코더 API(예: Android MediaCodec, 하드웨어 디코더 SDK)에 패킷을 넘기는 코드를 짤 때는 여전히 직접 변환해야 합니다.

NAL 헤더와 자주 보는 타입

NAL 유닛의 첫 바이트가 헤더입니다. 하위 5비트가 타입이고, 그 위 2비트(nal_ref_idc)는 이 유닛이 참조에 쓰이는지를 나타냅니다. 헥스 덤프에서 67, 68, 65, 41이 보이면 각각 SPS, PPS, IDR 슬라이스, 일반 P/B 슬라이스입니다.

타입이름의미
1Non-IDR 슬라이스P/B 프레임 데이터
5IDR 슬라이스키프레임. 이 지점부터 이전 프레임 없이 디코딩 가능
6SEI부가 정보(타임코드, 캡션, 인코더 설정 문자열 등)
7SPS시퀀스 파라미터: 프로파일·레벨·해상도·참조 프레임 수
8PPS픽처 파라미터: 엔트로피 코딩(CAVLC/CABAC)·초기 QP 등
9AUD액세스 유닛(프레임) 경계 표시. TS 스트림에서 흔함

SPS·PPS가 없으면 디코딩이 시작되지 않는다

디코더는 SPS·PPS를 먼저 받아야 슬라이스를 해석할 수 있습니다. 그래서 스트림 중간에서 잘라 낸 파일이나 중간부터 접속한 라이브 스트림이 IDR + SPS/PPS를 만날 때까지 검은 화면이나 깨진 화면을 보여 줍니다. 라이브 송출에서는 키프레임마다 SPS/PPS를 반복해 넣는 설정(x264의 repeat-headers, 하드웨어 인코더의 “insert SPS/PPS” 옵션)을 켜 두면 늦게 접속한 시청자도 다음 키프레임부터 바로 볼 수 있습니다.

헤더 구조를 직접 보고 싶다면 FFmpeg의 trace_headers 필터가 SPS·PPS·슬라이스 헤더의 필드를 읽을 수 있게 출력해 줍니다.

ffmpeg -i input.mp4 -c:v copy -bsf:v trace_headers -f null - 2>&1 \
  | grep -E "nal_unit_type|profile_idc|level_idc|pic_width"

실전 인코딩

아래 예제는 FFmpeg가 설치되어 있다고 가정합니다(ffmpeg -version으로 확인).

웹·모바일 배포 기본형

ffmpeg -i input.mov \
  -c:v libx264 -crf 21 -preset slow -profile:v high -pix_fmt yuv420p \
  -c:a aac -b:a 160k \
  -movflags +faststart output.mp4

이 한 줄에 H.264 배포에서 자주 빠뜨리는 세 가지가 들어 있습니다. -pix_fmt yuv420p(위의 프로파일 문제), -c:a aac(브라우저 호환 오디오), -movflags +faststart(MP4 인덱스인 moov 박스를 파일 앞으로 옮겨 다운로드 중 재생 가능하게 함)입니다. faststart가 없으면 긴 영상이 끝까지 받아진 뒤에야 재생이 시작되는 것처럼 보이는데, 서버·CDN 문제로 오해하기 쉽습니다.

해상도를 바꿀 때 가로·세로가 홀수가 되면 4:2:0에서 인코딩이 실패합니다(width not divisible by 2). -vf scale=-2:720처럼 -2를 쓰면 비율을 유지하면서 짝수로 맞춰 줍니다.

CRF와 preset 고르기

  • CRF: 051, 기본값 23. 낮을수록 고화질이고, FFmpeg 문서의 경험칙으로는 CRF가 6 오르면 비트레이트가 대략 절반이 됩니다. 시각적으로 원본과 구분이 어려운 수준은 보통 1718, 웹 배포는 20~23이 흔한 출발점입니다.
  • preset: ultrafast부터 veryslow까지. 같은 CRF에서 preset을 느리게 하면 비슷한 화질에 파일이 더 작아집니다. 대신 인코딩 시간이 늘어납니다. 한 번 인코딩하고 오래 배포하는 VOD라면 slow, 대량 트랜스코딩이면 medium이나 fast가 현실적인 선택입니다. veryslow는 slow 대비 이득이 작습니다.
  • tune: film, animation, grain, stillimage, zerolatency 등. 그레인이 많은 영상에 grain을 안 쓰면 x264가 그레인을 뭉개서 비트를 아끼려다 화면이 흐려집니다.

CRF 값은 해상도·콘텐츠마다 결과가 크게 다르므로, 대표 클립 몇 개로 CRF 18/21/24를 뽑아 실제 시청 환경에서 비교해 정하는 것이 가장 확실합니다.

스트리밍: 목표 비트레이트와 키프레임 정렬

ffmpeg -i input.mov -c:v libx264 -preset medium -profile:v high -pix_fmt yuv420p \
  -b:v 5M -maxrate 5M -bufsize 10M \
  -g 60 -keyint_min 60 -sc_threshold 0 \
  -c:a aac -b:a 128k output.mp4

HLS·DASH의 여러 화질(ABR 사다리)을 만들 때는 모든 화질의 키프레임 위치가 같아야 플레이어가 세그먼트 경계에서 화질을 바꿀 수 있습니다. x264는 기본적으로 장면 전환을 감지해 키프레임을 추가로 넣기 때문에(sc_threshold), 화질마다 키프레임 위치가 달라집니다. 위처럼 -g(키프레임 간격, 30fps에 2초 세그먼트면 60)와 -sc_threshold 0으로 고정 간격을 강제하거나, -force_key_frames "expr:gte(t,n_forced*2)"로 시간 기준 키프레임을 강제합니다. 세그먼트 길이가 들쭉날쭉하거나 화질 전환 때 화면이 튀는 문제는 대부분 여기서 나옵니다. 자세한 패키징은 스트리밍 프로토콜 가이드에서 다룹니다.

-maxrate와 -bufsize는 VBV(비디오 버퍼 검증기) 제약입니다. -bufsize가 작을수록 비트레이트가 평평해지는 대신 복잡한 장면에서 화질이 떨어집니다. CRF와 함께 -crf 21 -maxrate 5M -bufsize 10M처럼 쓰면 평소엔 CRF로 동작하고 상한만 지키는 capped CRF가 됩니다.

2-pass (파일 크기를 정확히 맞춰야 할 때)

ffmpeg -y -i input.mov -c:v libx264 -b:v 4M -preset slow -pass 1 -an -f mp4 /dev/null
ffmpeg -i input.mov -c:v libx264 -b:v 4M -preset slow -pass 2 -c:a aac -b:a 160k output.mp4

Windows에서는 /dev/null 대신 NUL을 씁니다. 목표 크기가 정해진 업로드(용량 제한이 있는 플랫폼)가 아니면 CRF가 더 간단하고, 같은 평균 비트레이트라면 2-pass와 CRF의 화질은 비슷합니다.

저지연 라이브

ffmpeg -i input -c:v libx264 -preset veryfast -tune zerolatency \
  -b:v 3M -maxrate 3M -bufsize 3M -g 60 -pix_fmt yuv420p -f flv rtmp://...

zerolatency는 B 프레임과 lookahead를 끄고 프레임 단위 병렬을 슬라이스 병렬로 바꿔 인코더 지연을 줄입니다. 그 대가로 같은 비트레이트에서 화질이 떨어집니다. 화상 회의 수준이 아닌 일반 라이브 방송이라면 수 초의 지연은 어차피 HLS 세그먼트가 만들기 때문에, zerolatency를 꼭 켤 필요는 없습니다.

NVIDIA NVENC

ffmpeg -hwaccel cuda -i input.mov \
  -c:v h264_nvenc -preset p5 -rc vbr -cq 23 -b:v 0 \
  -profile:v high -pix_fmt yuv420p -c:a aac -b:a 160k output.mp4

하드웨어 인코더는 libx264와 옵션 체계가 다릅니다. NVENC는 -crf를 받지 않고, 품질 기준 인코딩은 -rc vbr -cq N으로 합니다. -crf를 넣으면 무시되고 기본 비트레이트로 인코딩되어 파일 크기가 예상과 전혀 다르게 나옵니다. preset은 p1(가장 빠름)~p7(가장 고품질) 체계입니다. 쓸 수 있는 옵션은 드라이버·FFmpeg 버전에 따라 달라서 ffmpeg -h encoder=h264_nvenc로 확인하는 것이 정확합니다. 같은 비트레이트에서 최신 NVENC는 libx264 medium과 비슷한 수준까지 올라왔지만, 비트레이트가 낮을수록 libx264 slow와의 차이가 드러나므로 품질 검수는 필요합니다.

그 밖의 하드웨어 인코더는 Intel Quick Sync(h264_qsv), Apple VideoToolbox(h264_videotoolbox), AMD AMF(h264_amf), 리눅스 VA-API(h264_vaapi)가 있고, 사용 가능 여부는 FFmpeg 빌드와 드라이버에 따라 다릅니다.


다른 코덱과의 위치

  • 압축 효율: 같은 화질 기준으로 대략 HEVC와 AV1이 H.264보다 효율적이고, 그 격차는 해상도가 높을수록 커집니다. 정확한 비율은 콘텐츠·인코더·설정에 따라 크게 달라서 일반화된 숫자는 참고용으로만 보세요.
  • 디코딩: H.264는 사실상 모든 기기에서 하드웨어 디코딩되어 전력 효율이 좋습니다. 저가 기기에서 AV1을 소프트웨어로 디코딩하면 배터리와 발열 문제가 생기는 것과 대비됩니다.
  • 인코딩: libx264는 오랜 최적화 덕분에 CPU 인코딩 속도 대비 품질이 매우 좋습니다. 같은 CPU 시간이면 x265·SVT-AV1보다 훨씬 빠르게 끝납니다.

실무 선택은 “대역폭 비용이 얼마나 큰가”와 “재생 기기가 상위 코덱을 하드웨어로 디코딩하는가”로 정해집니다. 코덱 간 비교는 H.264 vs HEVC vs AV1 비교에서 더 자세히 다룹니다.


실무 활용 사례

  • 업로드 원본: YouTube 같은 플랫폼은 업로드된 영상을 자체적으로 여러 코덱·해상도로 다시 트랜스코딩합니다. 업로드용으로는 최종 시청 비트레이트보다 훨씬 높은 비트레이트(또는 낮은 CRF)로 인코딩해야 재압축 손실이 덜 보입니다.
  • OTT: 대형 서비스는 H.264를 기본 호환 트랙으로 두고 HEVC·AV1 트랙을 추가하는 멀티 코덱 구성을 씁니다. DRM이 붙으면 암호화 방식까지 맞춰야 합니다(DRM 가이드).
  • 모바일 앱: iOS·Android 모두 H.264 High 프로파일 8비트 4:2:0을 하드웨어로 디코딩합니다. 오래된 저가 기기를 지원해야 한다면 레벨 상한(예: 4.0)을 확인하세요.
  • 웹 브라우저: MP4(H.264 + AAC)는 주요 브라우저에서 모두 재생됩니다. 리눅스용 일부 브라우저 빌드는 코덱 라이선스 때문에 H.264를 시스템 라이브러리에 의존하므로 환경에 따라 재생되지 않을 수 있습니다.
  • WebRTC: H.264(Constrained Baseline)는 VP8과 함께 필수 구현 코덱이라 기기 간 호환 코덱으로 자주 협상됩니다.

흔한 문제와 해결

재생이 안 될 때 확인 순서

ffprobe -v error -select_streams v:0 \
  -show_entries stream=codec_name,profile,level,pix_fmt,width,height,r_frame_rate \
  -of default=nw=1 input.mp4
  1. pix_fmt가 yuv420p인가? yuv422p, yuv444p, yuv420p10le이면 브라우저·하드웨어 디코더에서 실패할 가능성이 큽니다.
  2. profile이 High 이하인가? High 4:4:4 Predictive, High 10이면 1번과 같은 문제입니다.
  3. level(예: 41 = 4.1)이 대상 기기 상한 안인가? 해상도·프레임레이트로 위 MBPS 계산을 해 봅니다.
  4. 오디오가 AAC(또는 MP3)인가? PCM·FLAC 오디오가 든 MP4는 브라우저에서 영상까지 재생에 실패하는 경우가 있습니다.
  5. 웹에서 재생 시작이 느리면 faststart 여부를 확인합니다. 재인코딩 없이 ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4로 고칠 수 있습니다.

다시 인코딩했더니 원본보다 커짐

원본이 이미 낮은 비트레이트로 압축된 파일(스마트폰 녹화, 스트리밍 다운로드)이면, 낮은 CRF(18 등)로 다시 인코딩할 때 원본에 있던 압축 노이즈까지 충실히 보존하느라 비트를 더 씁니다. 화질은 원본보다 좋아지지 않으므로 CRF를 올리거나, 목적이 컨테이너 변경뿐이면 -c:v copy로 재인코딩 자체를 피합니다.

저비트레이트에서 블로킹·밴딩

빠른 움직임이 많은 장면을 낮은 비트레이트로 인코딩하면 블로킹이 생깁니다. preset을 느리게 하고 해상도를 한 단계 낮추는 것이 비트레이트를 조금 올리는 것보다 효과적인 경우가 많습니다. 하늘이나 어두운 그라데이션의 밴딩은 8비트의 한계이기도 해서, x264의 -tune film이나 약간의 노이즈를 남기는 설정이 도움이 됩니다.

영상과 소리가 점점 어긋남

가변 프레임레이트(VFR)로 녹화된 스마트폰·화면 녹화 파일을 편집 툴이나 일부 플레이어가 고정 프레임레이트로 가정하면서 생기는 문제가 흔합니다. -r 30(출력 옵션)이나 -fps_mode cfr로 고정 프레임레이트로 변환해 인코딩하면 해결되는 경우가 많습니다.

라이선스

H.264는 특허 풀로 라이선스가 관리됩니다. MPEG LA가 운영하던 AVC 풀은 2023년 Via Licensing과 합병한 Via LA로 넘어갔습니다. 무료 인터넷 방송용 콘텐츠에는 로열티를 부과하지 않는다는 정책이 알려져 있지만, 인코더·디코더를 포함한 제품을 배포하는 경우는 조건이 다릅니다. 주요 특허가 순차적으로 만료되고 있지만 국가·특허별로 상황이 다르고, 오픈소스 인코더를 쓴다고 특허 문제가 해결되는 것도 아니므로 상용 제품이라면 법무 검토를 거치세요.


공식 스펙과 도구

ITU-T H.264 | ISO/IEC 14496-10

스펙 전체는 수백 쪽이라 처음부터 읽기보다 필요한 부분을 찾아보는 용도가 맞습니다.

위치내용언제 보나
7장 Syntax and semanticsNAL 유닛, SPS/PPS, 슬라이스 헤더 필드비트스트림 파싱, trace_headers 출력 해석
8장 Decoding process참조 프레임 관리, 예측 과정디코더 구현·디버깅
Annex A프로파일·레벨, Table A-1(레벨 한도)호환성 문제 진단 (가장 자주 봄)
Annex B바이트 스트림(시작 코드) 형식Annex B/AVCC 변환
Annex DSEI 메시지타임코드·캡션 삽입

FFmpeg 옵션과 스펙 필드는 이렇게 대응합니다.

FFmpeg 옵션비트스트림에서비고
-profile:v highSPS profile_idc = 100Main 77, Constrained Baseline 66 + 제약 플래그
-level 4.1SPS level_idc = 41Annex A Table A-1 한도와 비교
-refs 3SPS max_num_ref_frames레벨의 MaxDpbMbs가 상한을 정함
-coder / presetPPS entropy_coding_mode_flag0 = CAVLC, 1 = CABAC
-g 60(스펙 필드 아님)IDR 간격은 인코더가 정하는 스트림 구조
-crf 23(스펙 필드 아님)x264의 레이트 컨트롤 방식

GOP 구조와 프레임별 크기는 ffprobe로 확인합니다.

# 프레임 타입 순서 (I/P/B)
ffprobe -v error -select_streams v:0 -show_entries frame=pict_type \
  -of csv=p=0 video.mp4 | head -n 30

# 키프레임 시각만 보기 (HLS 세그먼트 정렬 확인용)
ffprobe -v error -select_streams v:0 -skip_frame nokey \
  -show_entries frame=pts_time -of csv=p=0 video.mp4 | head

# 패킷 크기로 누적 용량 보기
ffprobe -v error -select_streams v:0 -show_entries packet=size \
  -of csv=p=0 video.mp4 | awk '{s+=$1; print s/1048576 " MB"}' | tail -n 1

참고할 구현으로는 x264 소스(code.videolan.org/videolan/x264)의 encoder/ratecontrol.c(CRF·VBV)와 encoder/analyse.c(모드 결정)가 스펙보다 실무적인 이해에 도움이 됩니다.


마무리

  • H.264는 호환성·하드웨어 디코딩 측면에서 여전히 기준 코덱이고, 문제의 대부분도 호환성에서 나옵니다.
  • 배포용은 -pix_fmt yuv420p, AAC 오디오, -movflags +faststart를 기본으로 넣고, 레벨은 해상도·프레임레이트로 계산해 확인하세요.
  • 스트리밍 ABR은 모든 화질의 키프레임 위치를 맞추는 것이 먼저입니다.
  • 대역폭 비용이 커지면 H.264를 기본 트랙으로 둔 채 HEVC·AV1 트랙을 추가하는 방향을 검토하세요.

같이 보면 좋은 글