블로그 목록
Media22분 읽기

ABR Ladder의 Layer B 정렬 — 화질 전환 시 키프레임 동기화

"720p로 보다가 1080p로 자동 전환되는 순간 화면이 깨져요." 화질을 바꿀 때 잠깐 멈추거나 검은 화면이 뜨는 신고는 대부분 ABR ladder의 모든 화질이 같은 시간점에 키프레임을 갖지 못해서 생깁니다. 이 글은 화질 전환이 결국 다른 스트림으로 갈아타는 일이라는 점에서 출발해, 왜 모든 화질의 I-frame이 같은 시각에 박혀야 하는지, 그리고 단일 인코더 동시 출력과 force_key_frames, scene-cut 끄기로 그 정렬을 보장하고 ffprobe로 검증하는 방법까지 풀어냅니다.

ABRHLS키프레임 정렬Layer Bforce_key_framesFFmpegladdermediastreamvalidatorAgora Media Push
목차(37개 항목)
  1. 0. 핵심 명제 — 두 개의 정렬
  2. 1. SA 의사결정 흐름 — 한 장 요약
    1. 시나리오 인터뷰 — 고객사와 만났을 때 7가지 질문
  3. 2. ABR이 필요한 이유
    1. 시청자마다 다른 환경
    2. ABR — 같은 영상, 여러 화질
    3. Master Playlist (m3u8)
  4. 3. 화질 전환의 본질 — 시간축에서 스트림을 갈아탐
    1. 왜 P-frame으로 시작하면 안 되나
  5. 4. Layer B 정렬 위반 시나리오
    1. 시나리오 A: 정렬됨 (정상)
    2. 시나리오 B: Layer B 위반 (각 ladder가 독립적으로 인코딩됨)
    3. 시나리오 C: GOP 길이는 같은데 시작점이 다름
  6. 5. Layer B를 보장하는 4가지 방법
    1. 방법 1: 단일 인코더에서 ABR ladder 동시 출력
    2. 방법 2: `-force_key_frames`로 절대 시간 강제
    3. 방법 3: 클라우드 트랜스코더 사용
    4. 방법 4: 프록시 인코더 + 멀티 RTMP push (커스텀 파이프라인)
  7. 6. ABR Ladder 설계 — 일반 권장값
    1. 권장 ladder (16:9 비디오)
    2. 60fps 송출 (게임/스포츠)
    3. 비트레이트 간격 — 1.5~2배 룰
  8. 7. 검증 — 실제로 정렬됐는지 확인
    1. 각 ladder의 키프레임 timestamp 추출
    2. 한 줄 헬스체크 스크립트
    3. Apple `mediastreamvalidator`
  9. 8. 흔한 실수 패턴
    1. 실수 1: 각 화질을 독립 ffmpeg 프로세스로
    2. 실수 2: scene-cut 비활성화 누락
    3. 실수 3: VFR(가변 프레임레이트)
    4. 실수 4: GOP는 같은데 force_key_frames 누락
    5. 실수 5: 코덱/프로파일이 화질별로 다름
  10. 9. SA 체크리스트 — ABR Ladder 검증
  11. 10. 한 줄 결론
    1. 두 Layer 한눈에
  12. 관련 글
  13. 참고 자료

"720p로 보다가 1080p로 자동 전환되는 순간 화면이 깨져요". "시청자가 화질 바꿀 때 0.5초 멈춤이 생긴다는데 왜요?" — 두 신고 다 같은 원인입니다. ABR 화질 간 키프레임이 같은 시간점에 안 박혀 있을 때 일어나는 현상.

#37에서 GOP가 세그먼트 길이와 정렬되어야 한다는 Layer A (시간축 정렬) 를 다뤘다면, 이 글은 Layer B (스트림 간 정렬) — ABR ladder의 모든 화질이 같은 시간점에 I-frame을 가져야 하는 이유와 그 구현 방법을 정리합니다.


0. 핵심 명제 — 두 개의 정렬

라이브 송출에는 두 가지 정렬이 동시에 성립해야 한다. ① GOP가 세그먼트 길이의 정수 약수여야 하고(Layer A), ② 모든 ABR 화질이 같은 시간점에 I-frame을 가져야 한다(Layer B). 어느 한쪽만 맞으면 화질 전환 시 깨짐 또는 멈춤이 발생한다.

Layer정렬위반 시 증상
A (시간축)GOP 길이 ↔ HLS 세그먼트 길이패키저가 세그먼트 늘이거나 재인코딩 (#37)
B (스트림 간)모든 ABR 화질의 키프레임 시간점이 일치화질 전환 시 디코딩 실패 / 검은 화면 / 멈춤

1. SA 의사결정 흐름 — 한 장 요약

라이브 송출 설계는 비즈니스 요구사항에서 시작해 검증으로 끝나는 단방향 결정 트리입니다.

[비즈니스 요구사항]
       │
       ▼
[허용 지연]                  ← 시청자가 몇 초까지 견딜 수 있나
       │
       ▼
[프로토콜]                   ← HLS / LL-HLS / WebRTC / RTMP
       │
       ▼
[세그먼트 길이]              ← 플랫폼이 결정 (#37 Layer A)
       │
       ▼
[GOP = framerate × 세그먼트초]
       │
       ▼
[모든 ABR 화질에 동일 GOP + 동일 시간점 키프레임]  ← Layer B
       │
       ▼
[Closed GOP, Fixed GOP, Scene-cut OFF, CFR]
       │
       ▼
[송출 체인 전체에서 일관성 유지]
       │
       ▼
[검증: ffprobe + 플랫폼 헬스 + 시청자 테스트]

시나리오 인터뷰 — 고객사와 만났을 때 7가지 질문

1. "무엇을 송출하시려는 거예요?"           → 콘텐츠 종류 (강의/스포츠/게임)
2. "시청자 지연은 얼마까지 허용 가능해요?" → 프로토콜 선택
3. "송출 대상은 어디예요?"                 → 세그먼트 길이
4. "몇 fps로 캡처하실 거예요?"             → GOP 계산
5. "몇 가지 화질로 보내실 거예요?"         → ABR Ladder 설계
6. "인코더는 어디서 돌아가나요?"           → Agora Media Push? 자체? 클라우드 트랜스코더?
7. (설정 작성 후) "같이 ffprobe로 검증해보시죠"

이 7가지를 순서대로 묻고 답변을 채우면 자연스럽게 ladder 설정이 나옵니다.


2. ABR이 필요한 이유

시청자마다 다른 환경

5G에서 보는 사람, 지하철 와이파이, 해외 셀룰러 — 같은 라이브 방송이라도 받을 수 있는 비트레이트가 천차만별.

시청자 A (5G):       다운로드 속도 50 Mbps → 1080p 4.5 Mbps 여유
시청자 B (LTE):      8 Mbps → 720p 2.5 Mbps 가능, 1080p는 버벅
시청자 C (지하철):   1.2 Mbps → 360p 0.5 Mbps만 안정적

단일 화질로 송출하면:

  • 1080p만: 시청자 C는 끊김
  • 360p만: 시청자 A는 화질 불만

ABR — 같은 영상, 여러 화질

같은 라이브 방송을 여러 화질로 동시 인코딩해서 발행. 시청자 디바이스가 자기 네트워크 상태에 맞게 매 세그먼트마다 판단해 골라 받음.

같은 라이브 방송, 4가지 화질로 동시 인코딩:

1080p (4500 kbps):  seg1_1080.ts  seg2_1080.ts  seg3_1080.ts  ...
720p  (2500 kbps):  seg1_720.ts   seg2_720.ts   seg3_720.ts   ...
480p  (1000 kbps):  seg1_480.ts   seg2_480.ts   seg3_480.ts   ...
360p  (500 kbps):   seg1_360.ts   seg2_360.ts   seg3_360.ts   ...

시청자 디바이스의 ABR 알고리즘:

"지금 다운로드 속도 빠름 → 다음 세그먼트는 1080p로 받자"
"지금 느려짐 → 720p로 다운그레이드"
"끊겼다 회복 → 360p부터 시작해서 단계적 업그레이드"

이게 YouTube/Twitch에서 자동 화질 전환되는 원리. 사용자 개입 없이 매 세그먼트 경계에서 판단.

Master Playlist (m3u8)

ABR ladder는 master playlist 한 장에 표현됩니다.

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=4500000,RESOLUTION=1920x1080
1080p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=854x480
480p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=640x360
360p/index.m3u8

플레이어는 master를 먼저 받고, 처음에 적당한 화질을 골라 그 child playlist를 받음. 이후 매 세그먼트마다 평가 → 필요 시 다른 child로 전환.


3. 화질 전환의 본질 — 시간축에서 스트림을 갈아탐

ABR에서 "화질을 바꾼다"는 건 시청자 디바이스가 다음 세그먼트를 다른 ladder에서 받는다는 뜻입니다.

시청자가 720p로 보는 중:
  seg1_720 → seg2_720 → seg3_720 → seg4_720
                                        ↓
시점 t=8s에서 1080p로 전환 결정:
  seg1_720 → seg2_720 → seg3_720 → seg4_720 → seg5_1080 → seg6_1080
                                                  ↑
                                          여기가 I-frame이어야
                                          디코딩 가능

제약: 새 ladder의 첫 세그먼트는 반드시 I-frame으로 시작해야 함. 안 그러면 디코딩 실패.

왜 P-frame으로 시작하면 안 되나

#37 Layer 4에서 다룬 것과 같은 이유. P-frame은 이전 P 또는 I를 메모리에 갖고 있어야 디코딩 가능. 시청자는 720p의 P를 갖고 있는데, 1080p의 P는 720p의 P를 참조 못 함. 해상도도 다르고 인코딩 컨텍스트도 다름.

새 화질의 첫 세그먼트가 I-frame으로 시작 = ABR 전환의 필수 조건.

이걸 만족하려면 모든 ladder가 같은 시간점에 I-frame을 박아야 함.


4. Layer B 정렬 위반 시나리오

시나리오 A: 정렬됨 (정상)

시간축:  0s    2s    4s    6s    8s    10s

1080p:   I-----I-----I-----I-----I-----I
720p:    I-----I-----I-----I-----I-----I
480p:    I-----I-----I-----I-----I-----I
360p:    I-----I-----I-----I-----I-----I

→ 어느 시점이든 모든 ladder의 세그먼트 경계 = I-frame
→ ABR 전환 자유

시나리오 B: Layer B 위반 (각 ladder가 독립적으로 인코딩됨)

시간축:  0s    1s    2s    3s    4s    5s    6s

1080p:   I-----------I-----------I-----------I
720p:    I-----I-----------I-----------I
480p:    I-----------I-----I-----------I
360p:    I-----I-----------I-----I

시청자가 t=3s 시점에서 720p → 1080p 전환 시도:
  1080p의 t=3s 위치는? I-frame이 아님 (P-frame 한가운데)
  → 다음 1080p 세그먼트는 t=2 또는 t=4의 I-frame부터 시작
  → 시청자는 t=3을 못 받고 t=4까지 1초 멈춤 (또는 깨짐)

이런 패턴은 보통 각 화질을 독립된 인코더로 따로 인코딩할 때 발생합니다. 인코더마다 scene-cut 감지가 다르게 작동하거나, GOP 시작점이 어긋나거나.

시나리오 C: GOP 길이는 같은데 시작점이 다름

시간축:  0s    2s    4s    6s    8s    10s

1080p:   I-----I-----I-----I-----I-----I    (t=0부터 시작)
720p:     I-----I-----I-----I-----I-----I   (t=0.5부터 시작)

t=2 시점에서 1080p → 720p 전환:
  720p의 t=2 위치는 I-frame이 아님 (0.5초 시프트되어 있음)
  → 디코딩 실패

각 화질의 GOP 간격은 같아도 위상(phase)이 다르면 정렬 안 됨. 절대 시간점이 같아야 함.


5. Layer B를 보장하는 4가지 방법

방법 1: 단일 인코더에서 ABR ladder 동시 출력

가장 안전. 하나의 인코더가 timestamp 기반으로 모든 화질의 키프레임을 동기화.

# FFmpeg 단일 명령으로 4개 화질 동시 출력
ffmpeg -i input.mp4 \
  -filter_complex "[0:v]split=4[v1][v2][v3][v4]; \
                   [v1]scale=1920:1080[1080p]; \
                   [v2]scale=1280:720[720p]; \
                   [v3]scale=854:480[480p]; \
                   [v4]scale=640:360[360p]" \
  -map "[1080p]" -c:v:0 libx264 -b:v:0 4500k -g 60 -keyint_min 60 -sc_threshold 0 \
  -map "[720p]"  -c:v:1 libx264 -b:v:1 2500k -g 60 -keyint_min 60 -sc_threshold 0 \
  -map "[480p]"  -c:v:2 libx264 -b:v:2 1000k -g 60 -keyint_min 60 -sc_threshold 0 \
  -map "[360p]"  -c:v:3 libx264 -b:v:3 500k  -g 60 -keyint_min 60 -sc_threshold 0 \
  -map a:0 -c:a aac -b:a 128k \
  -f hls -hls_time 2 -hls_playlist_type event \
  -master_pl_name master.m3u8 \
  -hls_segment_filename "stream_%v/seg%d.ts" \
  -var_stream_map "v:0,a:0,name:1080p v:1,a:0,name:720p v:2,a:0,name:480p v:3,a:0,name:360p" \
  master.m3u8

핵심 옵션:

  • -g 60 -keyint_min 60 모든 출력에 동일하게 → 60프레임마다 강제 I-frame
  • -sc_threshold 0 scene-cut OFF → 가변 GOP 방지
  • 단일 입력 timestamp가 모든 출력에 동기 전파됨

방법 2: -force_key_frames로 절대 시간 강제

ffmpeg -i input.mp4 \
  -force_key_frames "expr:gte(t,n_forced*2)" \
  ... 각 ladder ...

expr:gte(t,n_forced*2) = "직전 강제 키프레임 시점 + 2초 도달 시 키프레임 강제". 절대 시간 기반이라 stream restart 후에도 정렬 유지.

방법 3: 클라우드 트랜스코더 사용

AWS MediaLive, Wowza Streaming Engine, Agora Cloud Recording의 transcoding 옵션 등 — 입력 단일 → ladder 동시 출력 자동 동기화.

Agora Media Push 한정: Media Push는 RTMP 단일 출력 채널입니다. ABR ladder를 직접 만들지 않으므로, Layer B 정렬은 송출 받는 쪽 플랫폼(YouTube/Twitch)이 책임집니다. YouTube가 ladder를 자체 트랜스코딩으로 생성하면 동기화도 그쪽이 처리.

방법 4: 프록시 인코더 + 멀티 RTMP push (커스텀 파이프라인)

자체 CDN이나 멀티 플랫폼 동시 송출 시 — 클라이언트 인코더에서 ladder를 만들고 각각 RTMP push.

이 경우 Layer B 보장이 가장 까다로움. 모든 인코더 인스턴스가 동일 timestamp 기준 + 동일 GOP + scene-cut OFF + force_key_frames 동기 사용 필수.


6. ABR Ladder 설계 — 일반 권장값

플랫폼/콘텐츠 종류에 따라 다르지만 일반적인 시작점:

권장 ladder (16:9 비디오)

화질해상도비트레이트 (H.264)fps용도
1080p1920×10804500~6000 kbps30데스크톱/대화면
720p1280×7202500~4000 kbps30모바일 와이파이
480p854×4801000~1500 kbps30모바일 LTE
360p640×360500~750 kbps30저속 셀룰러
240p426×240250~400 kbps30비상용

[NEEDS VERIFICATION] 비트레이트는 콘텐츠 종류(움직임 많은 스포츠 vs 정적인 강의)에 따라 ±30% 변동. 실제 측정 필수.

60fps 송출 (게임/스포츠)

화질해상도비트레이트fps
1080p601920×10806000~9000 kbps60
720p601280×7204500~6000 kbps60
720p301280×7202500 kbps30 (저속용 폴백)

60fps + 2초 세그먼트 → GOP = 120 프레임으로 모든 ladder 동일.

비트레이트 간격 — 1.5~2배 룰

연속한 화질 간 비트레이트가 1.5~2배 차이가 안 나면 ABR 알고리즘이 의미 있게 구분 못 함.

좋은 ladder:  500 → 1000 → 2500 → 4500   (각 단계 1.8~2.5배)
나쁜 ladder: 500 → 700 →  900 → 1100    (간격 너무 좁음)

[NEEDS VERIFICATION] Apple HLS Authoring Spec은 비트레이트 단계가 너무 좁거나 너무 넓으면 경고. 자세한 비율 가이드는 Apple mediastreamvalidator로 검증.


7. 검증 — 실제로 정렬됐는지 확인

ladder를 인코딩한 뒤 정렬 여부는 ffprobe로 확인합니다.

각 ladder의 키프레임 timestamp 추출

# 1080p stream의 키프레임 시점만 추출
ffprobe -select_streams v:0 -show_packets \
  -show_entries packet=pts_time,flags \
  -of csv=p=0 \
  1080p/index.m3u8 \
  | grep "K" | cut -d',' -f1 > 1080p_keyframes.txt

ffprobe ... 720p/index.m3u8 ... > 720p_keyframes.txt
ffprobe ... 480p/index.m3u8 ... > 480p_keyframes.txt
ffprobe ... 360p/index.m3u8 ... > 360p_keyframes.txt

# 모두 동일해야 함
diff 1080p_keyframes.txt 720p_keyframes.txt
diff 720p_keyframes.txt 480p_keyframes.txt

한 줄 헬스체크 스크립트

#!/bin/bash
# abr-align-check.sh
LADDERS=("1080p" "720p" "480p" "360p")
REF_FILE=""

for ladder in "${LADDERS[@]}"; do
  TMP="/tmp/${ladder}_kf.txt"
  ffprobe -v error -select_streams v:0 \
    -show_packets -show_entries packet=pts_time,flags \
    -of csv=p=0 "${ladder}/index.m3u8" 2>/dev/null \
    | grep "K" | cut -d',' -f1 > "$TMP"
  
  if [ -z "$REF_FILE" ]; then
    REF_FILE="$TMP"
  else
    if ! diff -q "$REF_FILE" "$TMP" > /dev/null; then
      echo "MISALIGNED: $ladder differs from reference"
      exit 1
    fi
  fi
done
echo "OK: all ladders aligned"

Apple mediastreamvalidator

Apple 공식 도구로 HLS Authoring Spec 준수 여부 검증:

mediastreamvalidator -v https://example.com/master.m3u8

키프레임 정렬, 비트레이트 단계, 세그먼트 길이 변동, 코덱 일관성까지 한 번에 체크.


8. 흔한 실수 패턴

실수 1: 각 화질을 독립 ffmpeg 프로세스로

# ❌ 위험
ffmpeg -i input.mp4 -c:v libx264 -b:v 4500k 1080p.m3u8 &
ffmpeg -i input.mp4 -c:v libx264 -b:v 2500k 720p.m3u8 &
ffmpeg -i input.mp4 -c:v libx264 -b:v 1000k 480p.m3u8 &

각 프로세스가 자기 timestamp로 독립 인코딩 → scene-cut 위치, 첫 키프레임 시점이 어긋남.

해결: 단일 ffmpeg에서 -filter_complex split + 다중 출력.

실수 2: scene-cut 비활성화 누락

# ❌ scene-cut 켜진 채로 ladder 만들면
-c:v libx264 -g 60   # 일부 화질에선 t=1.5에 강제 I, 다른 화질에선 안 박힘

해결: 모든 ladder에 -sc_threshold 0 또는 -x264-params scenecut=0:no-scenecut.

실수 3: VFR(가변 프레임레이트)

입력이 가변 fps면 출력 timestamp도 흔들림 → ladder 간 불일치.

해결: -vsync cfr 또는 -r 30 명시로 CFR 강제.

실수 4: GOP는 같은데 force_key_frames 누락

-g 60만으로는 인코더 내부 상태(rate control 등)에 따라 약간 어긋날 수 있음.

해결: -force_key_frames "expr:gte(t,n_forced*2)"로 절대 시간 강제.

실수 5: 코덱/프로파일이 화질별로 다름

1080p: H.264 High profile
720p:  H.264 Main profile
480p:  H.264 Baseline profile

이러면 디코더 재초기화 비용 발생 → 전환 시 멈춤. profile은 일관되게 (보통 Main + bframes=0, #28 참고).


9. SA 체크리스트 — ABR Ladder 검증

□ 1. ladder 화질 수 결정 (보통 3~5개)
□ 2. 각 화질의 해상도/비트레이트 — 1.5~2배 간격
□ 3. 모든 ladder 동일 framerate (다르면 60fps→30fps 폴백 명시)
□ 4. 모든 ladder 동일 GOP 값 (framerate × 세그먼트초)
□ 5. 모든 ladder Closed GOP + Scene-cut OFF + CFR
□ 6. 단일 인코더 / 클라우드 트랜스코더로 동시 출력
□ 7. -force_key_frames "expr:gte(t,n_forced*2)" 적용
□ 8. ffprobe로 키프레임 timestamp 일치 확인
□ 9. Apple mediastreamvalidator 통과
□ 10. 실제 시청자 시뮬레이션 — 화질 전환 시 멈춤/깨짐 없음

10. 한 줄 결론

Layer A는 시간축 정렬, Layer B는 스트림 간 정렬. 둘 다 만족해야 ABR이 깨끗하게 동작한다. 핵심 처방은 "단일 인코더에서 동일 GOP + force_key_frames + scene-cut OFF로 ladder를 동시 출력하는 것".

두 Layer 한눈에

┌────────────────────────────────────────────────────────┐
│                  Layer A — 시간축 정렬                  │
│   GOP 길이 ↔ 세그먼트 길이                              │
│   "GOP=2초, 세그먼트=2초 → 1:1 정렬"                    │
│   → #37에서 다룸                                        │
├────────────────────────────────────────────────────────┤
│                Layer B — 스트림 간 정렬                  │
│   1080p I-frame ↔ 720p I-frame ↔ 480p I-frame          │
│   "모든 ladder가 t=0,2,4,6초에 동시에 키프레임"          │
│   → 이 글의 주제                                        │
└────────────────────────────────────────────────────────┘

둘 다 만족 = 시청자는 어느 시점, 어느 화질로도 깨끗히 진입 / 전환 가능

관련 글


참고 자료

© 2026 Frank Kim. All rights reserved.