왜 수동 YUV 비디오 인코딩이 Android 기기 전반에서 실패하는가 (그리고 해결 방법)

발행: (2026년 1월 14일 오전 02:00 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

The Problem

많은 개발자들이 단순한 방법을 사용합니다: Bitmap을 직접 YUV로 변환하고 인코더에 전달합니다. 간단해 보이지만 실제 서비스에서는 함정이 많습니다:

  • 스트라이드 가정이 기기마다 다릅니다 (MediaTek은 보통 16–64 바이트씩 패딩)
  • 크로마 플레인 순서가 다릅니다 (플래너 vs. 세미‑플래너)
  • Surface 잠금이 경쟁 상태가 될 수 있어 IllegalArgumentException이 발생합니다
  • 인코더 출력 스레드가 무한정 블록될 수 있어 앱이 멈춥니다

결과는? 충돌, 파일 손상, 그리고 기기마다 신뢰할 수 없는 비디오 파이프라인입니다.

Why Naive Approaches Fail Manual YUV Conversion

Manual YUV Conversion

fun bitmapToYuv420(bitmap: Bitmap, width: Int, height: Int): ByteArray {
    val yuv = ByteArray(width * height * 3 / 2)
    // Naive RGB → YUV conversion without stride handling
    return yuv
}

What goes wrong

  • stride == width이라고 가정 → MediaTek 기기에서 실패
  • 플래너/인터리브 레이아웃을 무시 → 색상 손상
  • 하드웨어 정렬을 놓침 → 인코더가 프레임을 거부

테스트 폰에서는 동작하더라도 다른 기기에서는 실패할 가능성이 높습니다.

Naive Surface Handling

fun recordFrame(bitmap: Bitmap) {
    val canvas = surface.lockCanvas(null)
    canvas.drawBitmap(bitmap, ...)
    surface.unlockCanvasAndPost(canvas)
}

Issues

  • 동시성 제어가 없음 → 이전 프레임이 아직 그려지는 중에 새 프레임이 오면 충돌
  • 메인 스레드 차단 → 프레임 손실 또는 카메라 정지
  • 오류 복구가 없음 → 전체 녹화가 실패

The Production Solution: Surface‑Based Encoding

벤더에 구애받지 않고 프로덕션 수준에서 사용할 수 있는 유일한 방법은 Surface‑based 인코딩(COLOR_FormatSurface)입니다. 하드웨어가 스트라이드, 색상 변환, 정렬을 내부적으로 처리합니다.

핵심 패턴

  • 프레임 드롭 – 인코더가 바쁠 경우 프레임을 건너뛰어 Surface 잠금 충돌 방지
  • 짧은 타임아웃dequeueOutputBuffer()에 ~100 ms를 사용해 빠른 종료 구현
  • 리소스 정리 순서MediaMuxer → MediaCodec → Surface
  • 스레드 안전 – 모든 작업을 전용 인코더 스레드에서 수행
val isEncodingFrame = AtomicBoolean(false)

fun recordFrame(bitmap: Bitmap) {
    if (!isEncodingFrame.compareAndSet(false, true)) return

    try {
        val canvas = encoderSurface.lockCanvas(null)
        canvas.drawBitmap(bitmap, null, dstRect, paint)
        encoderSurface.unlockCanvasAndPost(canvas)
    } finally {
        isEncodingFrame.set(false)
    }
}

이 방법은 Qualcomm, MediaTek, Exynos 기기 모두에서 Android 10–15, 장시간 녹화 및 고 FPS 환경에서도 정상적으로 동작합니다.

Trade‑offs and Lessons

  • 프레임 드롭 vs. 품질 – 약간 끊기는 영상이 충돌보다 낫다
  • 메모리 & CPU – Surface 인코딩은 변환을 GPU에 위임해 메모리 사용량을 줄인다
  • 백그라운드 처리 – Android 12+에서는 앱이 백그라운드로 전환될 경우 인코더 스레드가 종료될 수 있으니 포그라운드 서비스를 사용
  • 테스트 포커스 – MediaTek 기기(Vivo, Oppo, Xiaomi)와 고 FPS 시나리오를 우선적으로 검증

Key Takeaways

  • 수동 YUV 변환은 피하세요 — 깨지기 쉽고 기기마다 차이가 큽니다
  • Surface‑based 인코딩을 사용하세요 — 하드웨어가 자동으로 모든 quirks를 처리합니다
  • 다양한 칩셋과 Android 버전에서 테스트하세요 — 실제 환경이 이론보다 중요합니다

이 방식을 적용하면 비디오 파이프라인이 프로덕션 수준으로 안정적이고 유지보수하기 쉬워집니다 — 기기별 해킹이 전혀 필요 없습니다.

Back to Blog

관련 글

더 보기 »