블로그 목록
Media18분 읽기

모바일 영상 파이프라인의 발열 — 메모리 복사, GPU Texture, Zero-Copy, 하드웨어 코덱

"영상 통화를 좀 하면 폰이 뜨끈해지고 배터리가 훅 닳는데, 코덱 설정이 문제일까요?" 답은 코덱만이 아니라, 카메라 센서에서 화면까지 가는 동안 프레임 버퍼를 몇 번이나 복사하느냐, 그리고 인코딩을 CPU가 떠안느냐 전용 칩이 처리하느냐에서 갈립니다. 이 글은 모바일 영상 파이프라인을 따라가며 메모리 복사가 왜 생기고 Zero-Copy가 이를 어떻게 없애는지, 안드로이드와 iOS가 텍스처 핸들 공유로 같은 철학을 어떻게 구현하는지, 하드웨어 인코딩이 전력에서 왜 압도적인지를 짚습니다. 모바일에 별도 VRAM이 있다는 통념과 통합 메모리의 실제도 함께 정리합니다.

Zero-CopyGPUTextureSurfaceTextureCVPixelBufferMediaCodecVideoToolbox하드웨어 인코딩unified memory발열

"영상 통화를 좀 하면 폰이 뜨끈해지고 배터리가 훅 닳는데, 코덱 설정 문제인가요?" 라는 질문을 자주 받습니다. 답은 "코덱만의 문제가 아니다" 입니다. 발열은 카메라 센서, 디스플레이, 그리고 무엇보다 데이터가 메모리를 몇 번이나 옮겨 다니는가의 합산 결과입니다.

이 글은 모바일 영상 파이프라인(카메라 센서 → 메모리 → 앱 엔진 → 인코더/필터 → 화면)을 따라가며, 메모리 복사가 왜 생기는지, Zero-Copy(제로카피)가 이를 어떻게 없애는지, 그리고 하드웨어 코덱이 전력에서 왜 압도적인지를 정리합니다. 중간에 흔히 도는 오해 세 가지 — "모바일에도 별도 VRAM이 있다", "복사는 어쩔 수 없다", "발열의 주범은 메모리 복사다" — 도 정정합니다.

0. 핵심 명제

모바일 영상의 발열·배터리 문제는 "어떤 코덱을 쓰느냐"보다 "프레임 버퍼를 몇 번 복사하느냐 + 인코딩을 CPU가 하느냐 전용 ASIC이 하느냐"에서 갈린다. 잘 설계된 파이프라인은 카메라부터 인코더까지 단 한 번도 픽셀을 복사하지 않는다(Zero-Copy) 는 것을 목표로 한다.

세 줄 요약 카드부터 보겠습니다.

레버나쁨 🔴좋음 🟢
프레임 이동매 프레임 CPU 메모리 복사(memcpy)GPU 텍스처 핸들만 전달(Zero-Copy)
인코딩 주체소프트웨어(CPU 풀로드)하드웨어 ASIC(MediaCodec/VideoToolbox)
메모리 모델 가정"VRAM과 RAM이 따로 있다" (PC식 오해)Unified Memory(통합 메모리) 전제로 설계

1. 데이터 흐름: 카메라에서 화면까지 무슨 일이 일어나는가

먼저 "정상적인" 영상 파이프라인의 단계를 그려 봅니다. 영상 통화 앱 하나에서 프레임 하나가 거쳐야 하는 경로입니다.

[1] 카메라 센서        빛 → 전기신호 → RAW/YUV 픽셀
        │  (ISP가 노이즈 제거·화이트밸런스·YUV 변환)
        ▼
[2] 캡처 버퍼          메모리에 프레임 도착 (Android: ImageReader/SurfaceTexture,
        │              iOS: CMSampleBuffer/CVPixelBuffer)
        ▼
[3] 앱 엔진/처리       미리보기 + 필터(미용보정, 배경흐림) → 보통 GPU 셰이더
        │
        ▼
   ┌────┴────┐
   ▼         ▼
[4a] 렌더    [4b] 인코더    화면 표시  /  H.264·HEVC·AV1 비트스트림 생성
   (미리보기)  (전송용)
              │
              ▼
        [5] 네트워크      RTP/SRTP 패킷 → SD-RTN/WebRTC 전송

여기서 핵심 질문은 각 화살표(│)마다 픽셀 데이터를 복사하느냐, 아니면 "어디에 있는지"만 가리키느냐 입니다.

  • 픽셀을 복사한다 = 1080p 프레임이면 약 3MB(YUV420 기준)를 초당 30번 옮긴다 = 초당 ~90MB의 memcpy.
  • 가리키기만 한다 = 핸들(포인터/텍스처 ID) 몇 바이트만 전달한다.

이 차이가 발열의 큰 축입니다.

이 글은 [1]~4에 집중하고, [5] 네트워크/전송은 블로그 23·블로그 43에서 다룹니다.

비유: 한 부서에서 다음 부서로 서류를 넘길 때, 매번 전체 문서를 복사기로 복사해서 넘기는 것과, "서류함 3번 칸에 있다"는 쪽지 만 넘기는 것의 차이입니다. 기술적 실체로 환원하면 — 전자는 CPU가 메모리 대역폭을 태우며 memcpy를 도는 것이고, 후자는 GPU 텍스처 핸들(정수 ID) 하나를 전달하는 것입니다.


2. 복사(Copy)는 왜 발생하는가 — 경계가 만든 비용

"그냥 안 복사하면 되잖아?" 싶지만, 복사가 생기는 데는 구조적 이유가 있습니다. 데이터를 가진 쪽과 쓰려는 쪽 사이에 경계(boundary) 가 있기 때문입니다.

경계 종류왜 복사가 생기나
프로세스 경계카메라 서비스(시스템 프로세스)와 앱(별도 프로세스)은 메모리를 직접 못 본다. 공유 메커니즘이 없으면 IPC로 복사
보안/권한 경계카메라 HAL이 만든 버퍼를 앱이 임의로 못 만진다. 안전한 핸드오프가 없으면 복사로 우회
포맷 경계센서는 NV12/YUV, 인코더는 다른 정렬(stride/tiling)을 원함. 맞추려면 변환=복사
CPU↔GPU 경계CPU 메모리에 있는 데이터를 GPU가 쓰려면 업로드(복사)가 필요 — 단, 통합 메모리에서는 이게 회피 가능 (4번 참조)

순진하게 짜면 한 프레임이 이렇게 흐릅니다.

🔴 복사가 난무하는 (나쁜) 파이프라인

카메라 버퍼 ──memcpy──▶ 앱 CPU 버퍼 ──upload──▶ GPU 텍스처(필터)
                                                    │
                                          ──readback(복사)──▶ CPU 버퍼
                                                    │
                                          ──memcpy──▶ 인코더 입력 버퍼

1080p·30fps 기준으로 위 경로엔 프레임당 3~4번의 풀프레임 이동이 있습니다. 초당으로 환산하면 수백 MB의 메모리 트래픽이고, 이는 CPU를 고클럭 상태로 붙잡아 둡니다. 발열의 직접 원인 중 하나입니다.

정정 (자주 보는 과장): "모바일 발열의 주범은 메모리 복사다" 는 부분적으로만 맞습니다(PARTLY_TRUE). 발열은 카메라 센서 동작, 디스플레이 백라이트, CPU/GPU 고주파 구동, 네트워크 무선(모뎀) 등 여러 요인의 합 입니다. 메모리 복사는 그중 하나일 뿐입니다. 다만 현대 안드로이드/iOS는 하드웨어 버퍼 공유와 제로카피를 기본 설계로 채택해 불필요한 복사를 최소화하므로, "복사를 피하는 것이 파이프라인 설계의 핵심"이라는 명제는 유효합니다.


3. Zero-Copy: 픽셀 대신 "텍스처 핸들"을 넘긴다

해결책은 단순합니다. 데이터를 옮기지 말고, 데이터를 가리키는 핸들을 공유한다. 모바일에서 이를 가능케 하는 건 OS가 제공하는 공유 버퍼 메커니즘입니다.

정정: "복사는 어쩔 수 없다"는 옛 PC 직관입니다 — 통합 메모리에선 핸들 공유로 회피 가능합니다.

🟢 Zero-Copy (좋은) 파이프라인

카메라 ──▶ [공유 버퍼] ◀── GPU 필터가 이 버퍼를 직접 읽음
              │              (텍스처로 "바인딩"만, 복사 없음)
              └──▶ 인코더가 같은 버퍼를 직접 입력으로 사용

   픽셀은 한 곳에 그대로. 각 단계는 "핸들"만 받는다.

3-1. Android: SurfaceTexture + GraphicBuffer + 외부 텍스처

안드로이드의 그래픽 스택은 BufferQueue라는 생산자-소비자 큐 위에 서 있습니다. 카메라가 프레임을 생산하면, 소비자(GPU/디스플레이)가 그 버퍼를 복사 없이 받아 갑니다.

구성요소역할
GraphicBuffer실제 픽셀이 담긴 공유 메모리 버퍼(하드웨어 버퍼)
BufferQueue생산자(카메라)↔소비자(GPU) 사이의 버퍼 핸들 큐
SurfaceTexture큐에서 받은 버퍼를 OpenGL 텍스처로 노출
GL_TEXTURE_EXTERNAL_OES일반 GL_TEXTURE_2D가 아닌 외부 텍스처 타깃. YUV/특수 포맷을 GPU가 직접 샘플링
EGLImageKHR버퍼를 텍스처 내용으로 "연결"하는 핸들. updateTexImage()가 이걸로 갱신

핵심은 SurfaceTexture.updateTexImage() 입니다. 이 호출은 픽셀을 복사하지 않습니다. 가장 최근 카메라 버퍼를 EGLImageKHR로 잡아 GL_TEXTURE_EXTERNAL_OES 텍스처에 연결할 뿐입니다. 셰이더는 이 외부 텍스처를 그대로 샘플링해 필터를 적용하고, 결과를 인코더 입력 Surface로 다시 흘려보냅니다.

// 외부 텍스처는 전용 확장과 샘플러를 쓴다
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES sTexture;   // GL_TEXTURE_2D 가 아님
varying vec2 vTexCoord;
void main() {
    gl_FragColor = texture2D(sTexture, vTexCoord);  // 카메라 버퍼를 복사 없이 샘플
}
// Java/Kotlin 측: 새 프레임이 오면 텍스처 내용만 갱신(복사 X)
surfaceTexture.setOnFrameAvailableListener(st -> {
    // GL 스레드에서:
    surfaceTexture.updateTexImage();          // EGLImage로 텍스처 콘텐츠 갱신
    surfaceTexture.getTransformMatrix(mtx);    // 좌표 변환 행렬만 받아옴
    // 이후 sTexture 를 그려서 미리보기 + 인코더 Surface로 전달
});

3-2. iOS: CVPixelBuffer + IOSurface + CVMetalTextureCache

iOS는 IOSurface 백업 덕분에 텍스처 변환 경로가 더 단순합니다. 카메라가 주는 CVPixelBufferIOSurface로 백업되어 있으면(권장 설정), 이를 Metal 텍스처로 바꾸는 비용이 제로(zero cost) 입니다. 같은 IOSurface의 GPU 메모리를 여러 텍스처가 공유하기 때문입니다.

구성요소역할
CVPixelBuffer픽셀 버퍼 추상화
IOSurface프로세스/엔진 간 공유 가능한 실제 GPU 접근 가능 버퍼
CVMetalTextureCacheIOSurface 백업 버퍼를 Metal 텍스처로 복사 없이 래핑
kCVPixelBufferIOSurfacePropertiesKey버퍼를 IOSurface 백업으로 만들라는 생성 플래그(필수 조건)
// CVMetalTextureCache 는 IOSurface 백업 픽셀버퍼에만 zero-copy 동작
var cvTexture: CVMetalTexture?
CVMetalTextureCacheCreateTextureFromImage(
    kCFAllocatorDefault,
    textureCache,            // CVMetalTextureCacheCreate 로 미리 만든 캐시
    pixelBuffer,             // 카메라가 준 CVPixelBuffer (IOSurface 백업)
    nil,
    .bgra8Unorm,
    width, height,
    0,
    &cvTexture
)
let metalTexture = CVMetalTextureGetTexture(cvTexture!)  // 복사 없이 MTLTexture 획득

⚠️ 주의: CVMetalTextureCache의 zero-copy는 kCVPixelBufferIOSurfacePropertiesKey로 생성된(즉 IOSurface 백업) 픽셀 버퍼에만 적용됩니다. 일반 malloc 백업 버퍼는 여전히 복사가 필요합니다. 캡처 세션의 픽셀 버퍼 속성을 잘못 설정하면 zero-copy가 조용히 깨집니다.

3-3. 양 플랫폼 비교

AndroidiOS
공유 버퍼GraphicBuffer / HardwareBufferIOSurface
카메라 프레임 타입SurfaceTexture 텍스처CVPixelBuffer
GPU 텍스처화GL_TEXTURE_EXTERNAL_OES + EGLImageKHRCVMetalTextureCache
갱신 호출updateTexImage()CVMetalTextureCacheCreateTextureFromImage()
복사 비용없음(핸들 갱신)없음(zero cost)

✅ 두 플랫폼 모두 "픽셀은 한 곳에 두고 핸들만 공유" 라는 동일한 철학을 다른 API로 구현합니다. 이것이 현대 모바일 미디어 파이프라인의 핵심 최적화입니다.


4. ★정정: 모바일에는 "별도 VRAM"이 없다 — Unified Memory

여기서 PC 그래픽 카드의 직관을 그대로 옮기면 틀립니다.

정정: "스마트폰 GPU에도 별도 VRAM이 물리적으로 분리되어 있다" 는 부정확합니다. 모바일 기기(스마트폰/태블릿)는 CPU와 GPU가 같은 메모리 풀을 공유하는 통합 메모리(Unified Memory) 아키텍처가 표준입니다. 안드로이드/iOS 모두 LPDDR 기반 통합 메모리를 쓰며, CPU와 GPU가 동일한 물리 메모리에 접근합니다. Apple Silicon(M1/M2/M3 등)도 명시적으로 "Unified Memory"를 표방합니다.

PC (분리 메모리)                    모바일/Apple Silicon (통합 메모리)
┌─────────┐   PCIe    ┌─────────┐    ┌──────────────────────────┐
│  CPU    │◀────────▶│  GPU    │    │   CPU        GPU         │
│  + DDR  │  복사 필요 │  + VRAM │    │     \       /            │
└─────────┘          └─────────┘    │   같은 LPDDR 메모리 풀    │
  RAM과 VRAM이 물리적으로 분리         │   (복사 없이 포인터 공유) │
                                     └──────────────────────────┘

이게 왜 중요하냐면 — CPU↔GPU "업로드/다운로드 복사"가 원리적으로 불필요하기 때문입니다. 같은 메모리를 가리키므로, 올바른 핸드오프(IOSurface/GraphicBuffer)만 쓰면 데이터를 옮길 이유가 없습니다. Zero-Copy가 모바일에서 특히 자연스러운 이유가 바로 이 통합 메모리입니다.

⚠️ 단서: 일부 고급 안드로이드 SoC는 여러 LPDDR 채널을 노출하고 GPU 트래픽을 우선순위화할 수 있습니다. 그래도 물리적으로는 통합 메모리입니다. 별도 VRAM은 NVIDIA Jetson 같은 일부 고급 임베디드에는 있으나, 스마트폰에는 표준이 아닙니다. [NEEDS VERIFICATION: 특정 기종별 채널 구성은 SoC 데이터시트 확인 필요]


5. 하드웨어 인코딩 vs 소프트웨어 인코딩 — 전력의 결정적 차이

복사를 다 없애도, 인코딩을 누가 하느냐가 남습니다. H.264/HEVC/AV1 인코딩은 연산량이 큽니다. 이걸 CPU(소프트웨어)로 돌리면 CPU가 고전력 상태에 묶이고, 전용 하드웨어 블록(ASIC)으로 돌리면 훨씬 적은 전력으로 끝납니다.

현대 모바일 SoC(Qualcomm Snapdragon, Apple A-시리즈, MediaTek 등)는 CPU/GPU와 별도로 H.264·HEVC·VP8·VP9·AV1 등을 처리하는 전용 비디오 인코더/디코더 블록을 통합하고 있습니다. 하드웨어 비디오 블록은 2010년대 초부터 보급됐고, Android에서는 MediaCodec(API 16, 2012)부터 앱이 표준 API로 접근하게 됐습니다. 예컨대 Google Tensor G3(2023)가 모바일 최초로 AV1 하드웨어 인코딩(최대 4K60)을 지원하기 시작했습니다. 반면 Qualcomm은 AV1 인코딩을 건너뛰고 VVC로 가겠다는 입장이라, Snapdragon 8 Gen 3는 AV1 디코딩(최대 8K60)만 지원하고 인코딩은 지원하지 않습니다.

항목소프트웨어 인코딩 🔴하드웨어 인코딩(ASIC) 🟢
연산 주체범용 CPU 코어전용 고정기능 블록
전력 효율낮음(CPU 고클럭 유지)높음(목적 최적화 회로)
발열작음
유연성높음(임의 코덱/옵션)낮음(지원 코덱·프로파일 한정)
화질 튜닝세밀(x264 옵션 등)제한적

연구 결과를 보면 차이가 큽니다. 인코딩 직접 측정치는 드물어 같은 ASIC을 쓰는 디코딩 연구로 경향을 봅니다.

  • HEVC 소프트웨어 디코딩은 하드웨어 디코딩 대비 최대 4배 이상의 에너지를 소비합니다.
  • 절대 전력값은 측정 환경마다 다르므로 단정하기 어렵고, 연구가 제시하는 것은 상대 비율입니다 — 하드웨어 디코딩이 소프트웨어 대비 한 자릿수 %(HEVC의 경우 약 4~5%) 수준으로 에너지를 낮춥니다.
  • 일부 측정에선 하드웨어 디코더가 소프트웨어 대비 에너지 소비를 9% 수준 이하로 낮춥니다.

⚠️ 정확히 말하면: 절감 배수는 코덱·해상도·비트레이트·SoC 구현에 따라 다릅니다. 위 수치는 주로 디코딩 측정이며, 인코딩은 더 무겁습니다. 일반적으로 하드웨어 가속이 대략 5~10배 이상 더 효율적이라고 보면 안전합니다. 정밀한 수치는 기기별 실측이 필요합니다. [NEEDS VERIFICATION: 인코딩 전력은 기종별 실측 권장]

트레이드오프는 분명합니다. 소프트웨어 인코더는 화질 튜닝(B-frame 구조, ref 프레임, psy-tune 등 — 블로그 30 참조)에서 자유롭지만 배터리에 치명적입니다. 모바일 실시간 영상에서는 거의 항상 하드웨어 인코딩이 정답이고, 화질 손해는 비트레이트/해상도 조정으로 메웁니다.

5-1. 접근 API

플랫폼API하드웨어 확인 방법
Androidandroid.media.MediaCodecAPI 29+ MediaCodecInfo.isHardwareAccelerated()
iOSVideoToolbox 프레임워크VTIsHardwareDecodeSupported()

Android에서는 MediaCodecInfogetSupportedPerformancePoints()로 특정 해상도·프레임레이트 조합을 하드웨어가 감당하는지 조회할 수 있습니다. iOS의 VideoToolbox는 헤더 파일과 WWDC 세션 문서가 주된 레퍼런스입니다.

// Android: 하드웨어 가속 코덱인지 확인 (API 29+)
val codec = MediaCodec.createByCodecName(codecName)
val isHw = codec.codecInfo.isHardwareAccelerated   // false면 소프트웨어 폴백

6. 기기 파편화와 코덱 화이트리스트 — 안드로이드의 현실

하드웨어 인코딩이 정답이라고 했지만, 안드로이드에는 함정이 있습니다. 기기마다 하드웨어 코덱 구현 품질이 천차만별이고 일부는 버그가 있습니다. 그래서 대형 멀티미디어 프로젝트는 기기/칩셋 기반 화이트리스트·블랙리스트를 실제로 운영합니다.

사례내용
구형 Snapdragon일부 H.264 하드웨어 인코더가 키프레임을 잘못 처리해 비활성화된 적 있음
OMX.MARVELL 프리픽스인코딩 오류로 회피 대상이 된 코덱군
Spreadtrum/MediaTek/HUAWEI칩셋별 특정 이슈 보고 다수
Mozilla FirefoxWebRTC 초기에 Nexus 5만 화이트리스트했던 시기 존재

이는 추측이 아니라 WebRTC·Chrome·Firefox·React Native WebRTC 등에서 문서화된 실제 산업 사례입니다. 대규모 영상 SDK(예: Agora 같은 RTC 플랫폼 — 블로그 43 참조)가 자체 코덱/디바이스 처리 로직을 갖는 이유 중 하나도 이 파편화 흡수입니다.

⚠️ 시사점: 모바일 영상 앱을 직접 만든다면 "하드웨어 코덱 = 항상 켠다"가 아니라 "기기별로 신뢰할 수 있을 때만 켠다" 가 현실적인 정책입니다. 신뢰 안 되는 기기는 소프트웨어 폴백(전력 손해를 감수)하거나 해상도를 낮춥니다.


7. "렌더(Render)"라는 단어 정리

마지막으로 자주 혼동되는 용어 하나를 짚습니다. 영상 파이프라인에서 "렌더"는 두 가지 다른 작업을 동시에 가리킬 수 있습니다.

맥락"렌더"의 의미
미리보기/표시GPU가 텍스처를 화면(디스플레이)에 그리는
필터 처리GPU가 셰이더로 텍스처를 다른 텍스처로 변환(오프스크린 렌더)하는 것

둘 다 GPU의 같은 그리기 능력을 쓰지만 출력 대상이 다릅니다. Zero-Copy 파이프라인에서는 이 두 렌더가 같은 입력 텍스처(카메라 버퍼) 를 공유하고, 인코더 입력 역시 같은 버퍼를 가리킵니다. 그래서 "캡처 → 필터(렌더) → 미리보기(렌더) + 인코딩"이 단일 복사 없이 굴러갑니다.

            ┌──▶ [필터 렌더] ──▶ [미리보기 렌더 → 화면]
[카메라 버퍼]┤
            └──▶ [같은 버퍼 → 인코더 입력]
        (이 버퍼는 처음부터 끝까지 한 곳에 그대로 있음)

8. 한 줄 결론

모바일 영상의 발열·배터리는 코덱 선택보다 "프레임을 몇 번 복사했나 + 인코딩을 CPU가 했나 ASIC이 했나" 로 결정된다. 통합 메모리 위에서 텍스처 핸들만 공유하는 Zero-Copy + 하드웨어 코덱이 정석이고, 안드로이드에서는 기기 파편화 때문에 "하드웨어를 신뢰할 수 있을 때만 켜는" 화이트리스트 전략이 현실이다.

관련 글

  • /frank/blog/23 — 오디오 파이프라인 (Opus/RTP/Jitter Buffer): 영상의 자매편
  • /frank/blog/27 — 프레임 I/P/B, GOP, PLI/FIR: 인코더가 뭘 만드는지
  • /frank/blog/28 — H.264 Profile·인코더·비트레이트: 코덱 설정의 기초
  • /frank/blog/30 — x264 옵션 ref/tune/keyint: 소프트웨어 인코딩의 튜닝 자유도
  • /frank/blog/43 — Agora 자체 코덱 vs Web SDK, SD-RTN: 플랫폼이 파편화를 흡수하는 방식

참고 자료

© 2026 Frank Kim. All rights reserved.