블로그 목록
Media12분 읽기

Bitrate 완전 정복 — 같은 해상도인데 왜 화질이 다른가

해상도는 캔버스 크기, Bitrate는 물감의 양. Resolution×FPS×Bitrate 삼각관계, Cloud Recording 재인코딩 손실, HLS에서 ffprobe bitrate가 N/A인 이유, 화질 디버깅 5단계 체크리스트.

BitrateCloud RecordingHLSffprobe트러블슈팅

같은 720×1280인데 왜 한쪽은 선명하고 한쪽은 뭉개져 보이는가? 해상도가 같아도 bitrate가 다르면 화질은 완전히 달라집니다. 해상도는 캔버스 크기이고, bitrate는 그 캔버스 위에 얼마나 세밀하게 그릴 수 있는지를 결정합니다.

Cloud Recording 트러블슈팅을 하다 보면 "화질이 나빠요"라는 문의가 옵니다. 해상도를 확인하면 정상인데 체감 화질은 360p 수준. 이런 경우 십중팔구 bitrate 문제입니다.


Bitrate란 — 초당 데이터 예산

Bitrate는 1초 동안 영상을 표현하는 데 사용할 수 있는 데이터량입니다.

Bitrate = 초당 할당된 데이터 예산

1500 kbps = 초당 1,500,000 bits = 약 183 KB/초

같은 720×1280 영상이라도:
  500 kbps  → 예산 부족 → 디테일 포기 → 뭉개짐 (JPEG 품질 20)
  1500 kbps → 적정 예산 → 디테일 유지 → 선명함 (JPEG 품질 80)
  5000 kbps → 예산 과다 → 더 좋아지지 않음 → 용량만 증가

JPEG 품질 설정과 같은 원리입니다. 같은 1280×720 이미지라도 JPEG 품질 20으로 저장하면 뭉개지고, 80으로 저장하면 선명합니다. 해상도는 동일하지만 압축 품질이 다릅니다. 동영상에서 이 "압축 품질"을 결정하는 것이 bitrate입니다.


Resolution × FPS × Bitrate — 세 변수의 삼각관계

화질을 결정하는 세 변수는 서로 얽혀 있습니다. 하나를 올리면 다른 것도 따라 올려야 합니다.

                    Resolution (해상도)
                         /\
                        /  \
                       /    \
                      /  화질  \
                     /________\
              FPS              Bitrate
           (프레임 수)          (데이터 예산)

계산 직관

필요 bitrate ≈ 해상도(픽셀 수) × FPS × 압축 효율 계수

720×1280 @ 15fps → 기본 bitrate 필요량
720×1280 @ 30fps → 약 1.5~2배 bitrate 필요
                    (프레임이 2배 → 데이터도 더 필요)

세 변수 중 하나만 올리고 나머지를 고정하면 이런 일이 벌어집니다.

변경결과
해상도↑ bitrate 고정큰 캔버스에 적은 물감 → 뭉개짐
FPS↑ bitrate 고정프레임마다 예산 줄어듦 → 각 프레임 품질↓
Bitrate↑ 해상도·FPS 고정일정 수준까지 선명해지다가 수확 체감

해상도별 권장 bitrate

Agora의 setVideoEncoderConfiguration 기준으로 정리하면:

해상도FPS최소 bitrate권장 bitrate최대 bitrate
640×48015400 kbps500 kbps750 kbps
640×48030600 kbps750 kbps1000 kbps
1280×72015600 kbps1130 kbps1500 kbps
1280×720301000 kbps1500 kbps2000 kbps
1920×1080151000 kbps2000 kbps3000 kbps
1920×1080301500 kbps2500 kbps4000 kbps

이 표에서 핵심은 같은 해상도라도 FPS가 올라가면 권장 bitrate도 올라간다는 점입니다. 720×1280 @ 15fps에서 1130 kbps가 적정이지만, 30fps로 올리면 1500 kbps가 필요합니다.


Cloud Recording에서 Bitrate가 결정되는 흐름

녹화 화질은 단일 설정으로 결정되지 않습니다. 호스트 SDK에서 출발한 영상이 최종 녹화 파일이 되기까지 여러 단계를 거칩니다.

[호스트 디바이스]
    │
    │ ① setVideoEncoderConfiguration
    │    width: 720, height: 1280, fps: 30, bitrate: 1710
    │
    ▼
[Agora SD-RTN 네트워크]
    │
    │ ② 네트워크 적응 (Adaptive Bitrate)
    │    대역폭 부족 시 SDK가 자동으로 해상도/bitrate 낮춤
    │    설정이 720p여도 실제 송출은 360p일 수 있음
    │
    ▼
[Cloud Recording 서버]
    │
    │ ③ 스트림 수신
    │    videoStreamType: 0 (고화질) / 1 (저화질)
    │    channelType 불일치 시 수신 품질 저하 가능
    │
    │ ④ Mix 모드: 디코딩 → 캔버스 합성 → 재인코딩
    │    transcodingConfig의 width/height/fps/bitrate 적용
    │    재인코딩 과정에서 필연적 품질 손실 발생
    │
    ▼
[S3 저장소]
    녹화 파일 (M3U8 + TS)

각 단계에서 화질이 깎이는 방식

① 호스트 SDK 설정 — 출발점. 여기서 720×1280 / 30fps / 1710 kbps로 설정했다면 이것이 이론적 최대 화질입니다.

② 네트워크 적응 — 가장 예측 불가능한 구간. Agora SDK는 네트워크 상태를 실시간으로 측정하고, 대역폭이 부족하면 자동으로 해상도와 bitrate를 낮춥니다. 호스트가 Wi-Fi에서 셀룰러로 전환되거나, 네트워크 혼잡 시 360p까지 떨어질 수 있습니다. 설정값과 실제 송출값은 다를 수 있습니다.

③ Cloud Recording 수신videoStreamType: 0이면 고화질 스트림을 요청합니다. 하지만 channelType이 앱과 불일치하면 스트림 구독이 불안정해질 수 있습니다. Communication 모드로 Live Broadcasting 채널에 조인하면 호스트/청중 역할 라우팅이 맞지 않기 때문입니다.

④ Mix 모드 재인코딩 — 여기서 transcodingConfig의 bitrate가 적용됩니다. 호스트가 1710 kbps로 보내도 녹화 설정이 1130 kbps면 34% 적은 데이터로 다시 압축합니다. 재인코딩 자체도 generation loss(세대 손실)를 일으킵니다.


"같은 해상도인데 왜 뭉개지나" — 실제 사례

실제 트러블슈팅 사례로 설명합니다.

상황

호스트 SDK:  720×1280, 30fps, 1710 kbps
녹화 설정:   720×1280, 15fps, 1130 kbps
체감 화질:   360~480p 수준

ffprobe로 확인

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,avg_frame_rate,bit_rate \
  -of default=noprint_wrappers=1 recording.m3u8

# 결과:
width=720
height=1280
avg_frame_rate=15/1
bit_rate=N/A

해상도는 720×1280으로 정상. 그런데 bit_rate=N/A입니다.

HLS에서 bit_rate가 N/A인 이유

일반 MP4:
┌─────────────────────────────────────┐
│  moov atom에 전체 bitrate 메타데이터   │  ← ffprobe가 읽음
│  mdat (실제 데이터)                   │
└─────────────────────────────────────┘
→ bit_rate: 1130000  ✅

HLS (M3U8 + TS):
┌──────────┐
│ .m3u8    │  ← 텍스트 파일. 재생 순서만 있음. bitrate 정보 없음
└──────────┘
     ↓ 참조
┌──────┐ ┌──────┐ ┌──────┐
│ .ts  │ │ .ts  │ │ .ts  │  ← 각각 독립 파일. 전체 bitrate를 알려면
└──────┘ └──────┘ └──────┘     모든 세그먼트를 다 다운받아 계산해야 함
→ bit_rate: N/A  (ffprobe는 그렇게까지 안 함)

MP4는 moov atom에 전체 파일의 bitrate 메타데이터가 기록되어 있어서 ffprobe가 즉시 읽을 수 있습니다. 하지만 M3U8은 TS 세그먼트들의 목차일 뿐, bitrate 정보를 담고 있지 않습니다.

전체 bitrate를 알고 싶다면 개별 TS 세그먼트를 분석해야 합니다.

# 개별 TS 세그먼트의 bitrate 확인
ffprobe -v error -show_format \
  -of default=noprint_wrappers=1 segment_001.ts \
  | grep bit_rate

# 또는 전체 TS를 MP4로 변환 후 확인
ffmpeg -i recording.m3u8 -c copy output.mp4
ffprobe -v error -show_format output.mp4 | grep bit_rate

화질 저하의 원인 분석

해상도가 정상이라면 문제는 이 세 가지 조합입니다.

원인 1: Bitrate 부족 (1710 → 1130, -34%)
  ├─ 재인코딩 시 압축을 더 세게 함
  ├─ 디테일 손실, 블록 아티팩트
  └─ JPEG 품질 80 → 50으로 재저장하는 것과 동일

원인 2: FPS 절반 (30 → 15)
  ├─ 초당 프레임 15장 버림
  ├─ 움직임 많은 장면에서 모션 블러
  └─ 정지 화면은 차이 적음, 동작 장면에서 체감 큼

원인 3: 재인코딩 Generation Loss
  ├─ 디코딩 → 캔버스 합성 → 재인코딩
  ├─ 이 과정 자체가 품질 손실
  └─ Individual 모드(-c copy)와 달리 Mix는 필연적

CBR vs VBR — Bitrate 할당 전략

Bitrate를 할당하는 방식에는 두 가지가 있습니다.

CBR (Constant Bitrate) — 고정 비트레이트

시간 →  ████████████████████████████████
bitrate: 1500  1500  1500  1500  1500  1500
         정적   정적   동적   동적   정적   정적
         장면   장면   장면   장면   장면   장면

문제: 정적 장면에서 낭비, 동적 장면에서 부족

매 순간 동일한 bitrate를 사용합니다. 실시간 스트리밍에서 주로 사용됩니다. 네트워크 대역폭을 예측 가능하게 사용할 수 있지만, 장면 복잡도에 관계없이 같은 예산을 쓰기 때문에 비효율적입니다.

VBR (Variable Bitrate) — 가변 비트레이트

시간 →  ██░░██████████████░░░░██░░░░░░
bitrate: 800   800  2200  2500  1800  600
         정적   정적   동적   동적   정적   정적
         장면   장면   장면   장면   장면   장면

장점: 움직임 많으면 bitrate↑, 정적이면 bitrate↓
     → 같은 평균 bitrate로 더 높은 화질

장면 복잡도에 따라 bitrate를 유동적으로 할당합니다. 움직임이 많은 장면에는 더 많은 데이터를, 정적 장면에는 적은 데이터를 사용합니다. VOD(녹화 파일 재생)에 적합합니다.

Agora Cloud Recording은 어떤 방식?

Agora Cloud Recording의 transcodingConfig.bitrate목표(target) bitrate입니다. 내부 인코더가 이 값을 기준으로 VBR에 가까운 방식으로 인코딩합니다. 즉, 설정한 bitrate가 상한선에 가까운 가이드라인이지 매 프레임 고정값이 아닙니다.


호스트 Bitrate > 녹화 Bitrate — 무슨 일이 일어나는가

흔한 질문입니다. "호스트가 1710으로 보내는데 녹화를 2500으로 설정하면 더 좋아지나요?"

호스트 송출: 1710 kbps ──────┐
                              ▼
Cloud Recording 수신: 1710 kbps (원본 그대로)
                              │
                              ▼ 재인코딩
transcodingConfig bitrate: 2500 kbps (설정)
                              │
                              ▼
실제 인코딩 결과: ~1600-1800 kbps

원본(1710)보다 나아지지 않음.
인코더가 "더 쓸 데이터가 없다"고 판단.

녹화 bitrate를 호스트보다 높게 설정해도 화질은 좋아지지 않습니다. 원본에 없는 디테일을 만들어낼 수 없기 때문입니다. 인코더는 필요 이상의 bitrate를 강제로 채우지 않습니다.

반대로, 녹화 bitrate가 호스트보다 낮으면 확실히 나빠집니다. 원본의 디테일을 더 강하게 압축해서 버리기 때문입니다.

적정 설정: 호스트 bitrate와 같거나 약간 높게 (재인코딩 손실 보상). 호스트가 1710이면 녹화는 1800~2000이 적정입니다.


실전: 화질이 나쁠 때 디버깅 체크리스트

녹화 화질 문의가 왔을 때 순서대로 확인합니다.

Step 1: 실제 해상도 확인

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,avg_frame_rate \
  -of default=noprint_wrappers=1 recording.m3u8

설정한 해상도와 다르면 → 호스트 SDK가 네트워크 적응으로 다운그레이드했거나, 호스트와 녹화 캔버스의 방향(가로/세로)이 불일치.

Step 2: channelType 일치 확인

앱의 채널 모드:
  Communication → channelType: 0
  Live Broadcasting → channelType: 1

녹화 설정과 반드시 일치시킬 것.
생략하면 기본값 0 (Communication).

Step 3: FPS 일치 확인

호스트 FPS와 녹화 FPS가 다르면:
  호스트 30fps → 녹화 15fps = 프레임 절반 버림 → 체감 화질↓

권장: 호스트 FPS와 동일하게 설정

Step 4: Bitrate 적정성 확인

녹화 bitrate ≥ 호스트 bitrate (재인코딩 손실 보상)

호스트 1710 kbps → 녹화 최소 1800 kbps
호스트 1710 kbps → 녹화 1130 kbps ❌ (-34%, 뭉개짐 확정)

Step 5: 방향(Orientation) 일치 확인

호스트 송출:     720×1280 (세로)
녹화 캔버스:     720×1280 (세로)  ✅ 일치

호스트 송출:     1280×720 (가로)
녹화 캔버스:     720×1280 (세로)  ❌ 불일치
→ Best Fit이 가로 영상을 세로 캔버스에 축소 피팅
→ 유효 해상도 720×405 (≈ 480p)

핵심 요약

  • Bitrate는 초당 데이터 예산. 해상도가 같아도 bitrate가 낮으면 뭉개집니다. JPEG 품질 설정과 같은 원리.
  • Resolution × FPS × Bitrate는 삼각관계. 하나를 올리면 나머지도 맞춰야 합니다. 720p/30fps 기준 1500 kbps가 적정.
  • Cloud Recording Mix 모드는 재인코딩. 디코딩 → 합성 → 재인코딩 과정에서 필연적 품질 손실 발생. 녹화 bitrate를 호스트보다 약간 높게 설정해 보상.
  • 녹화 bitrate > 호스트 bitrate는 무의미. 원본 이상으로 좋아지지 않습니다. 스토리지 비용만 증가.
  • HLS(M3U8)에서 ffprobe bitrate가 N/A인 이유. M3U8은 세그먼트 목차일 뿐 bitrate 메타데이터가 없습니다. 개별 TS 또는 MP4 변환 후 확인.
  • 화질 디버깅 순서: 해상도 → channelType → FPS → Bitrate → 방향. 이 다섯 가지를 체크하면 대부분의 화질 문제를 잡을 수 있습니다.

시리즈 네비게이션

실시간 통화, 어떻게 녹화하는가 시리즈

  1. 실시간 통화, 왜 녹화해야 하는가 — Cloud Recording Architecture
  2. 내 서버에서 직접 녹화한다면 — On-Premise vs Cloud
  3. 녹화 모드 해부 — Individual, Mix, Web의 내부 파이프라인
  4. M3U8과 TS 파일의 모든 것 — HLS Deep Dive
  5. FFmpeg, 미디어의 스위스 아미 나이프
  6. FFmpeg 실전 파이프라인 — 녹화에서 VOD까지
  7. Bitrate 완전 정복 — 같은 해상도인데 왜 화질이 다른가 ← 현재 글

© 2026 Frank Kim. All rights reserved.