블로그 목록
Media20분 읽기

영상 압축과 HLS 장애 분석 — I/P/B-frame, GOP, Segment

I·P·B frame과 GOP가 압축과 random access에 맡는 역할을 설명하고, frame 기반 keyint 설정이 실제 시간 길이로 어떻게 환산되는지 살펴봅니다. 입력 frame rate 저하, keyframe 간격, HLS segment duration이 함께 변하는 장애를 log와 ffprobe로 구분하는 절차도 다룹니다.

영상 압축I-frameP-frameB-frameGOPkeyintHLSsegmentFFmpegx264라이브 스트리밍디버깅
목차(36개 항목)
  1. 0. `#EXTINF:18`은 원인이 아니라 관찰값이다
  2. 1. 영상의 본질 — 사진 × N장
  3. 2. I-frame vs P-frame vs B-frame — 3종류의 프레임
    1. 정의
    2. 직관 — 공이 날아가는 장면
    3. 압축 효율
  4. 3. GOP — Group Of Pictures
    1. 정의
    2. keyint — 가장 헷갈리는 파라미터
    3. "GOP=2초"를 프레임 수로 바꾸는 구성
    4. Open GOP vs Closed GOP
  5. 4. 정상 상태 — 30fps + keyint=60
  6. 5. 처리량 부족 가설 — fps가 무너지는 경우
  7. 6. 가능한 인과 사슬
  8. 6-1. 같은 증상, 다른 원인 — `#EXTINF:18` 대안 경로
    1. A. Frame drop 모드 — fps는 유지, 화질이 떨어짐
    2. B. Packager 설정 오류 — 인코더는 무죄
    3. C. 입력 소스 burst / PTS 불연속
    4. D. Clock drift — 인코더와 시스템 시계 불일치
    5. 진단 우선순위
  9. 7. HLS 세그먼트 경계와 랜덤 액세스
    1. HLS Split 규칙
    2. 정상 vs 사고
  10. 8. 플레이어 영향은 구현과 버퍼 정책에 따라 다르다
  11. 9. B-frame Deep Dive — 지연과 압축 효율의 교환
    1. 동작 원리
    2. 디코딩 순서 ≠ 재생 순서
    3. 단점 3가지
    4. 실무 설정 — 서비스 목표에 따라 선택
    5. HLS Segment와 B-frame의 상호작용
  12. 10. 실무 디버깅 역추적 🔍
  13. 11. 한 장 요약 — 최종 암기 카드
  14. 12. SA 의사결정 체크리스트
  15. 13. 한 줄 결론
  16. 관련 글
  17. 참고 자료

"라이브 송출 중에 화면이 멈춰요. 로그를 보니 #EXTINF:18이 찍혀 있습니다." EXTINF가 길다는 사실만으로 원인을 확정할 수는 없습니다. 실제 세그먼트 경계와 키프레임, 타임스탬프, 패키저 설정, 인코더 처리량을 함께 확인해야 합니다. 이 글은 I/P/B-frame, GOP, keyint을 바탕으로 그 진단 순서를 정리합니다.


0. #EXTINF:18은 원인이 아니라 관찰값이다

긴 HLS 세그먼트는 키프레임 간격, 입력 PTS, 패키저의 절단 정책, 처리 지연 가운데 하나 이상을 점검하라는 신호다. keyint이 프레임 수로 지정되는 인코더도 있지만, EXTINF 한 줄만으로 CPU 과부하나 GOP 이상을 확정해서는 안 된다.

흔한 오해와 정정:

오해정확한 이해
keyint=2 = 2초마다 keyframe2 프레임마다 keyframe입니다. 시간 아니라 프레임 개수입니다.
keyint=60 = 정확히 60프레임마다 I-framekeyint은 최대값입니다. scenecut이 켜져 있으면 더 일찍 삽입될 수 있습니다.
HLS segment 길이는 플레이리스트 옵션만 정한다패키저 목표 시간과 실제 키프레임·타임스탬프·절단 정책이 함께 결정
라이브에서는 B-frame을 반드시 꺼야 한다지연·압축 효율·디코더 호환성을 보고 결정. Apple·YouTube 권장안도 B-frame을 일률적으로 금지하지 않음
#EXTINF:18이면 인코더가 문제다관찰값만으로는 확정 불가. 패킷·프레임 타임스탬프와 패키저 설정부터 대조

1. 영상의 본질 — 사진 × N장

영상 = 사진 × N장
항목수치
1초30장 (30fps)
1080p 8-bit YUV 4:2:0 한 프레임약 3.11MB
1초 원본약 93MB
1시간 원본약 336GB

결론: 압축 없이는 인터넷으로 영상을 보낼 방법이 없습니다. 영상 코덱(H.264/H.265/AV1)이 푸는 문제가 정확히 이것 — "사진 × N장을 어떻게 작게 만들 것인가".


2. I-frame vs P-frame vs B-frame — 3종류의 프레임

정의

종류풀네임저장 내용참조크기
I-frameIntra frame프레임 내부 정보로 부호화다른 프레임 참조 없음대체로 큼
P-framePredicted frame앞 프레임과의 변화앞만 참조중간
B-frameBidirectionally predicted frame다른 참조 프레임과의 예측 정보과거·미래 참조 그림을 사용할 수 있음대체로 작음

직관 — 공이 날아가는 장면

Frame1 (I):  공이 왼쪽에 있음     ●
Frame2 (B):  공이 중간에 있음        ●
Frame3 (P):  공이 오른쪽에 있음         ●

I-frame 저장: "공이 왼쪽 (x=100, y=50)에 있다" — 전체 픽셀 기록
P-frame 저장: "앞 프레임 대비 공이 +200px 이동" — 차분만 기록
B-frame 저장: "앞(I)이랑 뒤(P) 평균쯤" — 보간 정보만 기록

P-frame은 앞만 봐서 변화량을 적고, B-frame은 앞뒤를 다 봐서 더 작게 줄입니다.

압축 효율

프레임 종류별 크기와 효율은 영상 내용·참조 구조·인코더 설정에 따라 달라진다.

B-frame이 가장 효율적이지만, 공짜는 아닙니다 (뒤에서 다룸).


3. GOP — Group Of Pictures

정의

GOP는 독립적으로 복호를 시작할 수 있는 그림과 그에 종속된 그림을 묶어 부르는 실무 용어다. 정확한 경계는 코덱의 랜덤 액세스 구조와 인코더 설정에 따라 달라진다.

I P P P P P P P P I P P P P P P P P I
|<--- GOP1 --->|  |<--- GOP2 --->|

B-frame을 포함한 전형적인 GOP는 이렇게 생깁니다:

I B B P B B P B B P B B P B B P I
|<------------- GOP ------------->|

keyint — 가장 헷갈리는 파라미터

흔한 착각: keyint=60 = "60초마다 keyframe" 실제: "60 프레임마다 keyframe (최대값)"

정확히 말하면 — keyint은 "최대" GOP 길이

x264에서 --keyint N은 maximum GOP length입니다. 두 가지 옵션이 함께 영향을 줍니다:

옵션동작
--keyint최대 키프레임 간격
--min-keyint최소 키프레임 간격
--scenecut장면 전환에 따른 조기 키프레임 삽입 제어
일반 영상:  --scenecut 40 켜짐
  장면 전환 감지 → keyint 도달 전에도 I-frame 삽입
  → GOP 길이가 가변적

고정 경계를 요구하는 멀티 렌디션 구성:
  장면 전환 키프레임을 제한하거나 렌디션 간 키프레임을 강제 정렬
  → 세그먼트 경계를 맞추기 쉬움

--scenecut 0은 일부 FFmpeg/x264 파이프라인에서 경계를 고정하기 위한 선택이지 HLS 규격의 필수 조건은 아닙니다. keyint=60도 최대 간격이므로 다른 키프레임 강제 옵션과 함께 해석해야 합니다.

학교 출석부 비유 🏫

선생님이 말합니다: "60번째 학생마다 출석 불러".

근데 학생이 하루에 몇 명 오느냐에 따라 시간이 달라집니다.

  • 하루에 30명씩 오면 → 2일 만에 60번째
  • 하루에 5명씩 오면 → 12일 만에 60번째

keyint은 시간이 아니라 "몇 번째 프레임마다" 라는 카운트 규칙입니다.

keyint=60의 일반적인 뜻:
"키프레임 사이를 최대 60프레임으로 제한"

프레임 번호:
1  2  3  ...  59  60  61  ...  119  120  ...
                   ↑                    ↑
              I-frame              I-frame

fps에 따라 실제 시간이 달라진다

30fps 정상 케이스

1초: [1][2][3]...[30]
2초: [31][32]...[60]  ← 60번째 도달 → I-frame!
3초: [61]...[90]
4초: [91]...[120]     ← 120번째 도달 → I-frame!

결과: keyint=60, fps=30 → 2초마다 I-frame

🔴 fps 떨어진 케이스

keyint=60, fps=30: 60 ÷ 30 = 2초마다 I-frame
keyint=60, fps=60: 60 ÷ 60 = 1초마다 I-frame
keyint=60, fps=15: 60 ÷ 15 = 4초마다 I-frame
keyint=60, fps=5:  60 ÷ 5  = 12초마다 I-frame ← GOP 폭증!

택배 비유 📦

keyint=60 = "60번째 택배마다 도장 찍어"

정상: 하루에 30개 택배 옴 → 60개 = 2일 만에 도장 CPU 터짐: 하루에 5개밖에 못 처리 → 60개 = 12일 만에 도장

도장 찍는 규칙(keyint=60)은 안 바뀝니다. 그냥 택배가 느리게 오는 것뿐.

시간이 아니라 프레임 개수. fps가 바뀌면 GOP의 실제 시간 길이도 같이 바뀝니다. 이게 뒤에 나올 "CPU 폭주 → segment 폭증"의 핵심 메커니즘 — 인코더에 설정된 규칙은 그대로지만, fps가 떨어지면 같은 keyint이 더 긴 시간으로 펼쳐집니다.

"GOP=2초"를 프레임 수로 바꾸는 구성

이 헷갈림의 진짜 원인은 사람이 설정하는 단위와 인코더 내부 단위가 다르다는 점입니다.

사람이 설정하는 값

GOP = 2초
fps = 30

사람 입장에서는 "2초마다 keyframe 넣어"가 가장 직관적입니다.

인코더 내부 변환

구성 계층이 2초 × 30fps = 60 frame으로 변환
       → 내부 파라미터: keyint = 60
       → 최대 키프레임 간격 60 frame

x264의 keyint처럼 프레임 수를 받는 옵션이 있는 반면, FFmpeg의 -force_key_frames처럼 타임스탬프 기반 표현식도 있습니다. 어떤 계층이 초를 프레임 수로 변환하는지 설정 파일과 실제 명령에서 확인해야 합니다.

정상일 땐 둘이 같아 보임

30fps 유지 시:
60 frame = 2초

I─────────I
   2초

→ 사람들은 "GOP=2초"라고 부름
→ 인코더는 keyint=60으로 작동
→ 결과 동일 → 차이를 인지 못함

CPU 망하면 차이 발생

원래: 30fps
망함: 5fps

근데 인코더 규칙은 안 바뀜:
"60 frame 모이면 I-frame"

60개 모으려면? 60 ÷ 5 = 12초

→ "GOP=2초"로 설정했지만 실제로는 12초
→ 사람의 표현(시간)과 실제 동작(frame)이 어긋남

진실 한 장 정리

사람이 말하는 것실제 인코더 내부
GOP = 2초keyint = 60
시간 단위frame 개수 단위

한 줄: "2초 GOP는 설정값이고, 인코더는 종종 그걸 60 프레임으로 변환해서 처리한다."

평소엔 둘이 같아 보여서 헷갈리지만, fps가 흔들리는 순간 정체가 드러납니다.

Open GOP vs Closed GOP

종류B-frame이 이전 GOP 참조 가능?용도
Open GOP가능VOD, 압축률 우선
Closed GOP불가 (GOP 경계에서 차단)독립 세그먼트·랜덤 액세스에 유리

독립 세그먼트를 만들려면 경계에서 이전 세그먼트의 참조가 필요하지 않도록 구성해야 합니다. Closed GOP가 흔한 해법이지만, HLS RFC 자체가 인코더 옵션 이름인 Closed GOP를 의무화하지는 않습니다.


4. 정상 상태 — 30fps + keyint=60

30fps 정상 동작

1초 = 30 frame
2초 = 60 frame  ← keyint=60

타임라인:
0s   I ──────────── 2s   I ──────────── 4s   I
     |<-- GOP 2초 -->|    |<-- GOP 2초 -->|

HLS segment:
[seg1: 2초][seg2: 2초][seg3: 2초]...
#EXTINF:2.0
#EXTINF:2.0
#EXTINF:2.0

깔끔하게 2초마다 I-frame이 박히고, HLS도 2초마다 자릅니다. #EXT-X-TARGETDURATION:2 약속과 실제가 일치.


5. 처리량 부족 가설 — fps가 무너지는 경우

아래는 인코더가 프레임을 드롭하지 않고 지연된 입력을 계속 처리하며, 출력 프레임률이 실제로 5fps까지 낮아진 경우의 계산 예입니다.

원래 fps: 30
실제 fps: 5   ← 6배 느림 (인코더 헉헉)

keyint=60을 채우려면?
60 frame ÷ 5 fps = 12초 걸림

타임라인:
0s   I ──────────────────────── 12s   I
     |<-------- GOP 12초 ------->|

이 조건에서는 같은 keyint=60도 12초에 해당합니다. 실제 시스템이 프레임을 드롭하는지, 타임스탬프를 유지한 채 늦게 처리하는지에 따라 결과는 달라집니다.


6. 가능한 인과 사슬

1. CPU 과부하 (100%)
       ↓
2. fps 하락 (30 → 5)
       ↓
3. 프레임 처리 지연
       ↓
4. I-frame 생성 지연 (keyint=60을 채우는 데 12초)
       ↓
5. GOP 길어짐 (2초 → 12~18초)
       ↓
6. HLS: I-frame 기다리며 못 자름
       ↓
7. segment 18초짜리 생성
       ↓
8. #EXTINF:11 / :13 / :18 로그 출현

이 흐름은 가능한 가설 중 하나입니다. 패키저와 타임스탬프를 확인하기 전에는 인코더를 원인으로 확정할 수 없습니다.

전제 조건: 이 인과 사슬은 인코더가 프레임을 드롭하지 않고 큐잉하는 경우에 발현됩니다. OBS의 "Frames missed due to rendering lag"이 늘어나는 모드, 또는 ffmpeg에서 -re 없이 캡처 디바이스를 직접 받는 케이스. 인코더가 frame drop 정책이면 GOP 시간은 유지되지만 화질이 떨어집니다 (다음 섹션 참조).


6-1. 같은 증상, 다른 원인 — #EXTINF:18 대안 경로

동일한 관찰값이 다른 경로로도 발생합니다. 디버깅 시 함께 확인할 항목은 다음과 같습니다.

A. Frame drop 모드 — fps는 유지, 화질이 떨어짐

실시간 파이프라인이 frame drop 정책을 사용한다면:
  처리량 부족 → 일부 프레임 드롭
  → 출력 fps는 30 유지
  → 입력 30프레임 중 5프레임만 인코딩, 25프레임 버림
  → GOP 시간은 정상 (2초)
  → 화질만 급격히 저하

증상: #EXTINF는 정상, 화면이 뚝뚝 끊김, "blocking" 증가
진단: ffmpeg -progress의 drop_frames, OBS의 "missed frames"

B. Packager 설정 오류 — 인코더는 무죄

ffmpeg -hls_time 2 인데 인코더 GOP가 5초:
  Packager는 다음 keyframe까지 기다림
  → #EXTINF:5 발생

Bento4/Shaka Packager의 segment_duration이 GOP와 불일치:
  → #EXTINF가 들쭉날쭉

진단: 인코더 설정 vs packager 설정 양쪽 동시 확인
처방: segment_length가 GOP_length의 정수배여야 함 ([#37](/frank/blog/37))

C. 입력 소스 burst / PTS 불연속

RTMP push 소스가 burst로 도착:
  잠시 멈췄다가 한 번에 여러 프레임 전송
  → 인코더 timestamp 기준 GOP가 늘어남

PTS discontinuity (캡처 디바이스 reconnect, 멀티 소스 전환):
  → packager가 segment boundary 오판
  → #EXTINF 비정상

진단: ffprobe -show_packets로 PTS gap 확인

D. Clock drift — 인코더와 시스템 시계 불일치

wall-clock을 직접 참조하는 애플리케이션 로직이라면 시스템 시계 변화가 영향을 줄 수 있습니다. 다만 FFmpeg 표현식의 `t`는 일반적으로 프레임 타임스탬프를 사용하므로, 먼저 PTS와 time base를 확인합니다.

진단 우선순위

순서확인 항목명령
1Frame drop 카운터OBS UI, ffmpeg -progress drop_frames
2CPU 사용률top, htop, nvidia-smi (HW 인코더)
3인코더 설정 vs packager 설정양쪽 config diff
4입력 소스 PTS 연속성ffprobe -show_packets
5시스템 시계chronyc tracking, timedatectl

#EXTINF:18을 보면 playlist만 보지 말고 실제 세그먼트의 첫 프레임, 키프레임 간격, PTS gap, frame drop, 패키저 설정을 같은 시간대에 대조합니다.


7. HLS 세그먼트 경계와 랜덤 액세스

HLS Split 규칙

HLS 비디오 세그먼트는 독립적으로 디코더를 초기화하고 재생을 시작할 수 있게 만드는 것이 권장된다. H.264 세그먼트는 IDR을 포함하는 것이 권장되지만, RFC의 표현은 MUST가 아니라 SHOULD다.

이유는 디코딩 의존성 때문:

I → P → P → P → P → P → P → P
         ↑
         여기서 자르면? → 앞 I가 없음 → 💀 재생 불가

P-frame은 앞 frame이 있어야 복구 가능.
B-frame은 앞뒤 둘 다 있어야 복구 가능.
→ 독립 세그먼트는 랜덤 액세스 가능한 경계에서 시작하도록 구성한다.

정상 vs 사고

상태I-frame 주기HLS segment 길이로그
정상2초2초#EXTINF:2.0
🟡 살짝 부하4~6초4~6초#EXTINF:4.5
🔴 CPU 터짐12~18초12~18초#EXTINF:18

RFC 8216은 각 EXTINF 값을 가장 가까운 정수로 반올림했을 때 EXT-X-TARGETDURATION 이하가 되도록 요구합니다. 위반 여부는 원본 소수 값을 단순 비교하지 말고 이 규칙으로 판단합니다.


8. 플레이어 영향은 구현과 버퍼 정책에 따라 다르다

플레이어는 EXT-X-TARGETDURATION을 재요청 주기와 live edge 계산 등에 사용합니다. 잘못된 값은 재생 동작을 불안정하게 만들 수 있지만, 특정 버퍼 크기나 실패 형태는 구현별로 다릅니다.

HLS playlist:
#EXT-X-TARGETDURATION:2  ← "2초씩 올게요" 약속

실제 도착:
#EXTINF:18  ← 18초짜리 옴

가능한 영향:
→ playlist 재요청 타이밍 불일치
→ live edge 계산 오차
→ 긴 세그먼트 다운로드·디코딩 지연
증상원인
seek 이상타임라인 계산이 segment 길이 기반인데 빗나감
버퍼 증가긴 세그먼트의 다운로드·디코딩 시간이 늘어남
화면 멈춤다음 segment 못 받음 (또는 받았는데 디코딩 지연)
AV sync 이상오디오/비디오 PTS 계산 꼬임 (특히 B-frame 환경)

RFC 8216: EXT-X-TARGETDURATION 검증은 각 EXTINF를 가장 가까운 정수로 반올림해 수행합니다. 특정 iOS 내부 버퍼 동작은 공개 규격만으로 단정하지 않습니다.


9. B-frame Deep Dive — 지연과 압축 효율의 교환

동작 원리

P-frame은 과거만 봅니다:

[I] → [P] → [P]
             ↑
          앞만 참조

B-frame은 과거 + 미래를 동시에 봅니다:

[I] → [B] → [P]
       ↑
   앞뒤 모두 참조

디코딩 순서 ≠ 재생 순서

B-frame의 핵심 함정: 저장/전송 순서와 화면 재생 순서가 다릅니다. 이유를 단계별로 풀어봅시다.

Step 1 — 문제 제기

B-frame은 미래 프레임을 참조합니다.

재생 순서:  I  B  B  P  B  B  P
시간 →      0  1  2  3  4  5  6

B(1번)을 디코딩하려면?

  • 과거 I(0번) 필요 → 이미 있음
  • 미래 P(3번) 필요 → 아직 안 왔음

B-frame을 재생하려면 미래의 P-frame을 먼저 디코딩해놔야 한다.

Step 2 — 그래서 저장 순서를 바꾼다

재생 순서 (Display order):  I  B  B  P  B  B  P
                            0  1  2  3  4  5  6

디코딩 순서 (Decode order): I  P  B  B  P  B  B
                            0  3  1  2  6  4  5
                            ↑  ↑
                       I 다음에 P가 먼저 옴 (B보다 앞)

Step 3 — 디코더 내부 흐름

① I(0) 디코딩 → 버퍼에 저장
② P(3) 디코딩 → I(0) 참조 → 버퍼에 저장 (아직 화면 출력 X)
③ B(1) 디코딩 → I(0) + P(3) 둘 다 준비됨 → 화면 출력 (t=1)
④ B(2) 디코딩 → I(0) + P(3) 참조 → 화면 출력 (t=2)
⑤ P(3) 화면 출력 (t=3)  ← 이미 ②에서 디코딩만 해둠
⑥ P(6) 디코딩 → P(3) 참조 → 버퍼에 저장
⑦ B(4), B(5) 디코딩 → P(3) + P(6) 참조 → 차례로 화면 출력

디코딩 시점과 화면 출력 시점이 분리되어 있다는 게 핵심. 디코더는 미래 프레임을 먼저 디코딩한 뒤 버퍼에 저장해두고, 재생 타이밍이 되면 꺼내서 출력합니다.

비유 — 영화 자막 번역가

영화 자막 번역가가 있다고 해봅시다. B-자막은 앞뒤 문맥을 모두 봐야 번역할 수 있습니다. 그래서 뒤 장면을 먼저 읽고, 다시 돌아와서 번역합니다.

→ 파일에는 "뒤 장면"이 먼저 저장되어 있어야 한다.

한 줄 요약

B-frame은 미래를 참조하므로, 그 미래(P)를 먼저 디코딩해야 한다. 그래서 파일 저장 순서 ≠ 화면 재생 순서가 된다.

이걸 처리하기 위해 디코더는 B-frame 직전의 P-frame을 미리 받아둘 만큼의 버퍼가 필요하고, 그 버퍼가 곧 지연(latency)으로 이어집니다. 실시간 통화에서 B-frame을 끄는 이유가 여기 있습니다.

PTS vs DTS — 컨테이너가 두 순서를 관리하는 법

디코딩 순서와 재생 순서가 다르다는 건 컨테이너 레벨에서 두 개의 타임스탬프를 따로 들고 있어야 한다는 뜻입니다.

타임스탬프풀네임의미
PTSPresentation Time Stamp화면에 보여줄 시각
DTSDecode Time Stamp디코더가 처리할 시각
B-frame이 없으면 PTS와 DTS가 같을 수 있음
프레임 재정렬이 있으면 PTS와 DTS가 달라질 수 있음

예: I B B P
PTS 순서:  I(0)  B(1)  B(2)  P(3)
DTS 순서:  I(0)  P(3)  B(1)  B(2)
            ↑    ↑
        같음   P가 B보다 먼저 디코딩되어야 함

실무 영향:

  • 프레임 구조 확인: PTS/DTS 차이만으로 B-frame 유무를 단정하지 말고 ffprobe -show_frames의 pict_type, 키프레임 플래그와 비트스트림을 함께 확인합니다.
  • HLS segment 경계: B-frame이 포함된 GOP를 segment로 자를 때, 마지막 B-frame의 DTS가 다음 segment의 첫 프레임 DTS보다 클 수 있어 sample dependency가 꼬일 위험이 있음 → Closed GOP 필수의 또 다른 이유.
  • iOS AV sync 증상: 비정상 segment에서 PTS/DTS 정렬이 깨지면 오디오 PTS와 비디오 PTS 매칭이 어긋남 → 입술 동기 오차.

단점 3가지

단점메커니즘영향
디코더 지연뒤 P-frame 먼저 받아 버퍼링실시간 스트리밍에서 치명적
디코딩 복잡도 ↑앞뒤 둘 다 참조해서 합성CPU/GPU 부하 증가
에러 전파앞뒤 frame 중 하나만 손상돼도 B 깨짐손실 환경에서 화질 불안

실무 설정 — 서비스 목표에 따라 선택

B-frame을 줄이거나 끄는 경우

- 실시간 방송 (OBS, 라이브 스트리밍)
- 초저지연 화상통화 (WebRTC, SIP)
- 인코딩 속도 최우선

ffmpeg 설정:
  -bf 0
x264 직접:
  --bframes 0

B-frame을 사용하는 경우

- VOD (다시보기, 영화, 강의 녹화)
- 파일 크기 최소화 우선
- 화질 최대화 (같은 비트레이트에서)
- 인코딩 시간 여유 있음

ffmpeg 설정:
  -bf 2   ← B-frame 2개 연속 허용
  -bf 3   ← 더 높은 압축, 더 큰 지연

초저지연 서비스에서는 재정렬 지연 때문에 B-frame을 줄이거나 끌 수 있습니다. 반면 일반 라이브 HLS에서도 인코더·플랫폼 권장안에 따라 B-frame을 사용할 수 있습니다. YouTube의 공식 가이드는 라이브 H.264에 B-frame 2개를 권장합니다.

HLS Segment와 B-frame의 상호작용

일반적인 독립 세그먼트 구성: IDR 등 랜덤 액세스 경계에서 자름.
B-frame이 있으면 세그먼트 간 참조가 남지 않도록 설정.

I B B P B B P ... I
↑                 ↑
여기서만 자름     여기서만 자름

추가 주의:
B-frame 때문에 디코딩 순서 ≠ 재생 순서
→ segment 경계의 PTS(재생시간) 계산 꼬일 위험
→ iOS 플레이어에서 AV sync 어긋나는 원인

라이브에서 B-frame을 쓸지는 목표 지연, 플랫폼 호환성, 압축 효율을 함께 보고 정합니다.


10. 실무 디버깅 역추적 🔍

#EXTINF:13 한 줄에서 시작해 CPU 모니터까지 거꾸로 따라갑니다.

1. HLS 로그에서 #EXTINF:13 발견
        ↓
2. "EXT-X-TARGETDURATION:2 인데 왜 13?"
        ↓
3. 패키저가 어디서 잘랐지?
   → 실제 키프레임·절단 옵션 확인
        ↓
4. I-frame이 늦게 왔나?
   → keyint=60인데 13초가 걸렸다 = fps가 ~5
        ↓
5. fps가 왜 떨어졌나?
   → 인코더 출력 fps 확인 (ffmpeg -progress)
        ↓
6. 인코더가 왜 느린가?
   → 입력 fps OK? 캡처 카드 정상?
   → CPU/GPU 사용률 확인 (top / htop / nvidia-smi)
        ↓
7. CPU 100% 확인 → 인코더 thread starvation
        ↓
8. 처방:
   - preset 다운 (slow → fast / veryfast)
   - 해상도/비트레이트 다운
   - HW 인코더 사용 (NVENC/QSV/VideoToolbox)
   - B-frame 끄기 (-bf 0)
   - 인코더 노드 스케일아웃

이 순서는 가설을 좁히는 예시입니다. 한 줄 로그만으로 8단계의 인과를 확정할 수는 없습니다.


11. 한 장 요약 — 최종 암기 카드

개념한 줄 요약
I-frame다른 프레임을 참조하지 않는 intra-coded picture. 모든 I-frame이 랜덤 액세스 지점인 것은 아님
P-frame"앞이랑 이만큼 달라" (중간)
B-frame"앞아 뒤야, 나 너네 사이 어딘가야" (가장 작음)
GOPI-frame ~ 다음 I-frame 직전 묶음
keyint=60최대 키프레임 간격 60프레임(인코더별 의미 확인)
Closed GOP세그먼트 독립성과 랜덤 액세스에 유리
HLS split패키저 목표와 랜덤 액세스 경계를 함께 확인
#EXTINF:18긴 세그먼트라는 관찰값. 키프레임·PTS·패키저·처리량을 함께 점검
B-frame지연·압축 효율·호환성에 따라 개수 결정
압축 효율:      B  >  P  >  I
디코딩 난이도:   B  >  P  >  I
실시간 지연:    B  >  P  >  I

12. SA 의사결정 체크리스트

라이브 송출 셋업 전에 답해야 할 8가지:

질문영향
1. 목표 fps와 keyint은?GOP의 실제 시간 = keyint ÷ fps
2. Closed GOP인가?HLS segment 깨짐 방지
3. 렌디션 간 키프레임이 정렬되는가?ABR 전환과 세그먼트 경계 안정성
4. rate control은 서비스 요구에 맞는가?대역폭 변동·품질·지연의 교환
5. 인코더 선택은? (SW/HW)품질·처리량·전력은 코덱과 기기별 실측 필요
6. 인코더 preset과 CPU 여유는?preset이 너무 무거우면 fps 미달
7. B-frame 사용 여부?목표 지연·압축 효율·플랫폼 권장안
8. HLS EXT-X-TARGETDURATION 설정값?반올림한 모든 EXTINF가 이 값 이하인지 검증

이 항목을 사전에 확인하면 긴 세그먼트의 원인을 더 빨리 좁힐 수 있습니다.


13. 한 줄 결론

#EXTINF:18은 긴 세그먼트가 만들어졌다는 관찰값이다. 실제 키프레임 간격, PTS 연속성, 패키저 절단 정책, frame drop과 처리량을 같은 시간대에 대조해야 원인을 확정할 수 있다.


관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.