블로그 목록
Media12분 읽기

Bitrate와 화질 — 같은 해상도인데 왜 결과가 다른가

Bitrate는 해상도·frame rate·codec·장면 복잡도와 함께 압축 화질을 좌우합니다. 이 글은 송출 설정, 네트워크 적응, Cloud Recording 출력 설정을 구분하고 `ffprobe`로 실제 결과를 측정하는 절차를 설명합니다. 출력 bitrate를 높여도 원본에서 이미 잃은 디테일은 복원되지 않으므로 실제 콘텐츠로 품질을 비교해야 합니다.

BitrateCloud RecordingHLSffprobe트러블슈팅

같은 720×1280인데 왜 한쪽은 선명하고 한쪽은 뭉개져 보이는가? 해상도가 같아도 bitrate와 입력·인코딩 조건이 다르면 화질이 달라집니다. 해상도가 캔버스 크기라면 bitrate는 encoder가 초당 사용할 수 있는 데이터 예산입니다.

Cloud Recording에서 해상도는 설정값과 같지만 디테일이 무너지는 경우가 있습니다. 이때 bitrate는 우선 확인할 항목이지만, 입력 화질, 프레임률, codec, 재인코딩과 네트워크 적응도 함께 봐야 합니다.


Bitrate란 — 초당 데이터 예산

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

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

1500 kbps = 초당 1,500,000 bits = 187.5 kB/초
                                      (약 183.1 KiB/초)

같은 720×1280 영상이라도:
  bitrate가 낮으면  → encoder가 더 많은 정보를 버려야 함
  bitrate가 높으면  → 보존할 수 있는 정보가 늘어남
  충분히 높은 뒤에는 → 입력과 codec 한계 때문에 개선 폭이 줄어듦

정지 이미지의 압축 품질을 떠올리면 직관을 잡는 데 도움이 되지만, 영상 codec은 시간축의 프레임 간 예측까지 사용합니다. 따라서 특정 bitrate를 JPEG 품질 값과 일대일로 대응시킬 수는 없습니다.


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

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

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

계산 직관

필요 bitrate는 해상도, FPS, 장면 복잡도, codec과 encoder 설정에 좌우됨

720×1280 @ 15fps → 기본 bitrate 필요량
720×1280 @ 30fps → 같은 프레임당 품질을 유지하려면 보통 더 많은 bitrate 필요

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

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

권장 bitrate를 정하는 방법

Agora SDK는 해상도와 FPS 조합별 standard bitrate를 제공하며, SDK와 플랫폼에 따라 사용할 수 있는 preset과 범위가 다릅니다. 오래된 FAQ의 base/live 표를 고정값으로 복사하지 말고 현재 사용 중인 SDK의 video encoding 문서를 기준으로 설정합니다.

같은 해상도에서도 FPS가 높거나 움직임이 복잡하면 일반적으로 더 많은 bitrate가 필요합니다. 최종 값은 대표 장면에서 실제 송출 통계와 녹화 결과를 측정해 정합니다.


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

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

[호스트 디바이스]
    │
    │ ① setVideoEncoderConfiguration
    │    width: 720, height: 1280, fps: 30, bitrate: 1710
    │
    ▼
[Agora SD-RTN 네트워크]
    │
    │ ② 네트워크 적응 (Adaptive Bitrate)
    │    대역폭 부족 시 실제 송출 bitrate·품질이 낮아질 수 있음
    │
    ▼
[Cloud Recording 서버]
    │
    │ ③ 스트림 수신
    │    videoStreamType: 0 (고화질) / 1 (저화질)
    │    channelType 등 구독 설정 확인
    │
    │ ④ Composite 모드: 디코딩 → 캔버스 합성 → 재인코딩
    │    transcodingConfig의 width/height/fps/bitrate 적용
    │    재인코딩 과정에서 필연적 품질 손실 발생
    │
    ▼
[S3 저장소]
    녹화 파일 (M3U8 + TS)

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

① 호스트 SDK 설정 — 송출 목표값의 출발점입니다. 실제 입력 품질과 network adaptation 때문에 설정값이 결과의 품질을 보장하지는 않습니다.

② 네트워크 적응 — SDK는 네트워크 상태에 맞춰 실제 송출 품질을 조정할 수 있습니다. 어떤 속성이 어느 순서로 바뀌는지는 SDK와 degradation preference 설정에 따라 다릅니다. 설정값과 실제 송출값은 다를 수 있습니다.

③ Cloud Recording 수신 — videoStreamType: 0은 high-quality stream, 1은 low-quality stream을 구독하도록 지정합니다. channelType은 앱의 channel profile과 맞춰야 하지만, 이 값만으로 특정 화질 저하를 진단할 수는 없습니다.

④ Composite 모드 재인코딩 — transcodingConfig의 출력 해상도, FPS와 bitrate가 적용됩니다. 입력보다 낮은 bitrate로 다시 인코딩하면 압축 손실이 커질 수 있습니다. 결과는 장면과 encoder 동작을 함께 측정해야 합니다.


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

아래는 진단 방법을 보여주기 위한 예시 설정입니다.

상황

호스트 SDK:  720×1280, 30fps, 1710 kbps
녹화 설정:   720×1280, 15fps, 1130 kbps
관찰 결과:   움직이는 구간에서 블록 아티팩트

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인 이유

먼저 짚어야 할 구분이 있습니다. HLS(RFC 8216)에는 두 종류의 플레이리스트가 있습니다.

Master(Multivariant) 플레이리스트:
┌──────────────────────────────────────────────┐
│ #EXT-X-STREAM-INF:BANDWIDTH=1130000,...       │  ← BANDWIDTH 필수 (RFC 8216 §4.3.4.2)
│ stream_720p.m3u8                              │     bits/sec 단위 bitrate 명시
│ #EXT-X-STREAM-INF:BANDWIDTH=600000,...        │
│ stream_360p.m3u8                              │
└──────────────────────────────────────────────┘
→ 여기에는 화질별 bitrate가 들어 있음

Media(Variant) 플레이리스트:
┌──────────┐
│ .m3u8    │  ← 세그먼트 목차. EXTINF(재생 시간)만 있고 전체 bitrate 필드 없음
└──────────┘
     ↓ 참조
┌──────┐ ┌──────┐ ┌──────┐
│ .ts  │ │ .ts  │ │ .ts  │  ← 각각 독립 파일. 전체 bitrate를 알려면
└──────┘ └──────┘ └──────┘     모든 세그먼트를 다 다운받아 계산해야 함
→ bit_rate: N/A  (ffprobe는 그렇게까지 안 함)

즉 M3U8에 bitrate 정보가 전혀 없는 것은 아닙니다. Master 플레이리스트의 EXT-X-STREAM-INF는 BANDWIDTH(bits/sec) 속성을 필수로 가집니다(RFC 8216 §4.3.4.2). 다만 이 값은 여러 화질 variant를 묶을 때 클라이언트가 대역폭에 맞는 variant를 고르라고 제공하는 메타데이터일 뿐, 개별 media 플레이리스트 자체에는 전체 bitrate 필드가 없습니다.

Cloud Recording의 녹화 M3U8은 media playlist이므로 EXT-X-STREAM-INF:BANDWIDTH가 없습니다. 이 입력에서 ffprobe가 stream bitrate를 추정하지 못하면 N/A를 표시할 수 있습니다. MP4도 bitrate metadata가 항상 존재하는 것은 아니며, 도구가 파일 크기와 재생 시간으로 평균 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%)
  ├─ 재인코딩 시 압축을 더 세게 함
  ├─ 디테일 손실, 블록 아티팩트
  └─ 움직임과 디테일이 많은 구간에서 손실이 두드러질 수 있음

원인 2: FPS 절반 (30 → 15)
  ├─ 출력 시간축에 포함되는 프레임 수가 줄어듦
  ├─ 움직임이 덜 부드러워질 수 있음
  └─ 정지 화면은 차이 적음, 동작 장면에서 체감 큼

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

CBR vs VBR — Bitrate 할당 개념

CBR과 VBR은 자주 쓰는 두 개념입니다. 실제 encoder에는 capped VBR, average bitrate, CRF 등 더 다양한 rate-control 방식이 있습니다.

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

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

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

CBR은 설정한 시간 구간과 buffer 제약 안에서 bitrate를 일정하게 유지하려는 방식입니다. 개별 frame이나 순간 bitrate까지 정확히 같다는 뜻은 아닙니다. 대역폭 예측이 중요한 실시간 전송에 사용됩니다.

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

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

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

VBR은 장면 복잡도에 따라 bitrate를 다르게 할당합니다. 목표 평균값이나 상한을 함께 둘 수 있으며 VOD encoding에 널리 사용됩니다.

Cloud Recording의 bitrate 설정

transcodingConfig.bitrate는 Composite 출력 encoder에 전달되는 bitrate 설정입니다. 공개 문서만으로 내부 rate-control 구현을 CBR 또는 VBR로 단정할 수 없으므로, 실제 결과의 평균·peak bitrate와 품질을 측정해야 합니다.


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

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

호스트 송출: 1710 kbps ──────┐
                              ▼
Cloud Recording 수신: 1710 kbps (원본 그대로)
                              │
                              ▼ 재인코딩
transcodingConfig bitrate: 2500 kbps (설정)
                              │
                              ▼
실제 인코딩 결과: 입력과 encoder 동작에 따라 달라짐

원본(1710)보다 나아지지 않음.
높은 출력 bitrate가 손실된 입력 디테일을 복원하지는 못함.

녹화 bitrate를 호스트보다 높게 설정해도 원본에 없는 디테일을 복원할 수는 없습니다. 다만 재인코딩 과정에서 추가 손실을 줄이는 데 필요한 bitrate는 입력 bitrate와 같지 않을 수 있습니다.

출력 bitrate가 너무 낮으면 압축 아티팩트가 늘어날 가능성이 큽니다. 적정값은 고정 비율로 정하지 말고 입력 통계, 출력 해상도·FPS, 장면 복잡도와 목표 품질로 검증합니다.


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

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

Step 1: 실제 해상도 확인

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

설정한 해상도와 다르면 실제 송출 통계, subscription stream type, transcoding canvas와 layout 설정을 차례로 확인합니다.

Step 2: channelType 일치 확인

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

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

Step 3: FPS 일치 확인

호스트 FPS와 녹화 FPS가 다르면:
  호스트 30fps → 녹화 15fps = 출력 움직임이 덜 부드러울 수 있음

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

Step 4: Bitrate 적정성 확인

고정 비율 대신 다음을 함께 확인:
  실제 입력 bitrate, 출력 해상도·FPS, 장면 복잡도, codec, 목표 품질

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

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

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

핵심 요약

  • Bitrate는 초당 데이터 예산입니다. 해상도·FPS·장면 복잡도·codec과 함께 판단합니다.
  • SDK preset은 현재 버전 문서를 확인합니다. 오래된 base/live 표를 공통 기준으로 쓰지 않습니다.
  • Composite 모드는 출력 설정으로 재인코딩하므로 입력보다 낮은 bitrate에서 손실이 커질 수 있습니다.
  • 높은 출력 bitrate는 입력 디테일을 복원하지 못하지만, 재인코딩 손실을 줄일 여지는 측정해야 합니다.
  • media playlist에는 variant BANDWIDTH가 없습니다. ffprobe가 N/A를 표시하면 segment 크기와 실제 duration으로 평균값을 계산합니다.
  • 화질 진단은 실제 송출 통계부터 시작해 subscription, 출력 해상도·FPS·bitrate, layout과 codec을 확인합니다.

시리즈 네비게이션

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

  1. 실시간 통화, 왜 녹화해야 하는가 — Cloud Recording Architecture
  2. 내 서버에서 직접 녹화한다면 — On-Premise vs Cloud Recording
  3. Agora 녹화 모드 비교 — Individual, Composite, Web page
  4. M3U8과 TS 구조 — HLS 녹화 파일 읽기
  5. FFmpeg 미디어 처리 — 컨테이너, 코덱, 스트림
  6. FFmpeg 실전 파이프라인 — Agora 녹화에서 프로덕션 VOD까지
  7. Bitrate 완전 정복 — 같은 해상도인데 왜 화질이 다른가 ← 현재 글

관련 글


참고 자료

© 2026 Frank Kim. All rights reserved.