비트맵 모드에서 미디어 개선 수명 주기 이해

미디어 개선 API는 하드웨어 가속을 활용하여 APK 블로트 없이 고품질 미디어 개선을 제공하는 지연 시간이 짧고 개인 정보 보호를 준수하는 온디바이스 AI 솔루션을 제공합니다. 자세한 내용은 미디어 개선 기능 이해를 참고하세요.

정적 비트맵 모드 (EnhancementMode.BITMAP)는 정적 디코딩된 이미지 처리를 위해 설계되었습니다.

비트맵 모드에서 작동하려면 OS가 압축되지 않은 픽셀 데이터를 직렬화하고 시스템 버스를 통해 CPU 메모리에서 GPU 메모리로 복사한 후 역방향 복사를 통해 처리된 프레임을 반환해야 합니다. 단일 프레임 실행에 최적화되어 있으며 실시간 동영상 스트리밍과 호환되지 않습니다.

이 워크플로에는 세션을 만들고, 세션으로 단일 비트맵을 처리한 후 결과를 처리하는 작업이 포함됩니다. 이전 섹션의 코드 예시에서는 이 사용 사례를 자세히 다룹니다.

  1. 옵션 구성: EnhancementOptions 객체를 만들고 enhancementModeEnhancementMode.BITMAP으로 설정되어 있는지 확인합니다.
  2. 세션 만들기: 이전에 정의한 createSessionAsync 래퍼를 사용하여 EnhancementSession을 만듭니다. 이 객체는 무거운 객체이므로 필요한 경우에만 만듭니다.
  3. 이미지 처리: 세션, 입력 비트맵, 원하는 옵션 을 호출합니다.
  4. 결과 처리: 일시중단 함수는 성공 시 새로운 개선된 비트맵을 반환하고 실패 시 예외를 발생시킵니다.
  5. 세션 해제: 완료되면 session.release()를 호출하여 GPU 리소스를 확보하는 것이 중요합니다.

EnhancementSession은 영구 GPU 또는 NPU 메모리 파이프라인을 유지하는 무거운 컨텍스트 객체입니다. 전용 동영상 RAM (VRAM)과 네이티브 시스템 핸들을 할당합니다. 심각한 메모리 누수와 잠재적인 OutOfMemoryError 비정상 종료를 방지하려면 다음 수명 주기 원칙을 준수하세요.

  1. 지연 인스턴스화: 사용자가 개선 작업을 시작할 때까지 세션을 만들지 마세요.
  2. 전략적 재사용: 동일한 구성 (크기 및 전환된 옵션)으로 여러 이미지를 처리할 때 단일 세션 인스턴스를 유지하고 재사용합니다.
  3. 즉시 삭제: 시각적 작업이 종료되면 즉시 session.release()를 호출하여 공유 하드웨어 리소스를 확보합니다.

개선 엔진 초기화

이 메서드는 2단계 검사를 오케스트레이션합니다. 기기의 하드웨어가 가속을 지원하는지 확인한 후 필요한 머신러닝 모듈이 있는지 확인합니다.

이 메서드를 필수 단계로 실행하면 앱이 미디어를 처리하기 전에 기능을 검증하여 런타임 초기화 실패를 방지할 수 있습니다.

class MediaSetupViewModel(application: Application) : AndroidViewModel(application) {
    private val enhancementClient = Enhancement.getClient(application)
    fun initializeEnhancementEngine() {
        viewModelScope.launch {
            try {
                // 1. Verify hardware capability
                val isSupported = enhancementClient.isDeviceSupportedAsync()
                if (!isSupported) {
                    notifyUiDeviceIncompatible()
                    return@launch
                }
                // 2. Verify and download the Google Play services ML modules
                val isInstalled = enhancementClient.isModuleInstalledAsync()
                if (!isInstalled) {
                    notifyUiDownloadingModels()
                    enhancementClient.installModule().await() 
                }
                notifyUiEngineReady()
            } catch (e: Exception) {

                // Handle potential errors during session creation or image processing.
                handleInitializationError(e)
            }
        }
    }
}

세션 및 비트맵 프로세스 래퍼 만들기

이러한 Kotlin 코루틴 래퍼를 사용하여 작업 기반 클라이언트 콜백을 표준 일시중단 함수로 변환하여 더 깔끔하고 순차적인 실행을 지원합니다.

// Wraps the task-based createSession callback into a suspending function.
suspend fun EnhancementClient.createSessionAsync(
    options: EnhancementOptions,
    executor: Executor
): EnhancementSession = withContext(Dispatchers.Main) {
    suspendCancellableCoroutine { continuation ->

        // EnhancementSessionCallback handles session success or failure.
        val callback = object : EnhancementSessionCallback {
            override fun onSessionCreated(session: EnhancementSession) {
                continuation.resume(session)
            }
            override fun onSessionCreationFailed(status: Status) {
                continuation.resumeWithException(
                    Exception("Session creation failed: ${status.statusMessage} (${status.statusCode})")
                )
            }
            override fun onSessionDestroyed() {}
            override fun onSessionDisconnected(status: Status) {}
        }

        // Handles errors during the initial request trigger.
        this.createSession(options, callback).addOnFailureListener(executor) { e ->
            if (continuation.isActive) {
                continuation.resumeWithException(e)
            }
        }
    }
}

// Wraps this process in a suspending function for cleaner execution.
suspend fun EnhancementSession.processBitmapAsync(
    bitmap: Bitmap,
    options: EnhancementOptions
): Bitmap = suspendCancellableCoroutine { continuation ->

    // EnhancementCallback returns the processed bitmap or an error code.
    val callback = object : EnhancementCallback {
        override fun onBitmapProcessed(enhancedBitmap: Bitmap) {
            continuation.resume(enhancedBitmap)
        }
        override fun onError(statusCode: Int) {
            continuation.resumeWithException(
                Exception("Bitmap processing failed with status code: $statusCode")
            )
        }
        override fun onSurfaceProcessed(timestamp: Long) {}
    }
    this.process(bitmap, options, callback)
}

ViewModel에서 비트맵 파이프라인 실행

개선 파이프라인을 애플리케이션 아키텍처에 통합하려면 ViewModel을 사용하여 세션 수명 주기를 관리하세요. 이 접근 방식을 사용하면 ViewModel이 삭제될 때 무거운 GPU 리소스가 해제됩니다.

// Define a data class to hold image information.
data class ImageInfo(val bitmap: Bitmap)
// Define a UI state class to hold loading status, errors, and enhanced image.
data class EnhancementUiState(
    val isLoading: Boolean = false,
    val enhancementError: String? = null,
    val enhancedImage: ImageInfo? = null
)

class EnhancementViewModel(application: Application) : AndroidViewModel(application) {

    // Backing field for UI state, initialized with default values.
    private val _uiState = MutableStateFlow(EnhancementUiState())
    // Publicly exposed UI state flow for observation.
    val uiState: StateFlow<EnhancementUiState> = _uiState.asStateFlow()

// Initialize client to interact with the Media Enhancement service.
    private val enhancementClient: EnhancementClient = Enhancement.getClient(application)

// Single-thread executor for processing background enhancement tasks.
    private val enhancementExecutor = Executors.newSingleThreadExecutor()

// Track session state to enable reuse across multiple processing calls.
    private var enhancementSession: EnhancementSession? = null

// Primary function to trigger the enhancement workflow for a provided bitmap.
    fun enhanceImage(bitmap: Bitmap) {
        viewModelScope.launch(Dispatchers.IO) {
            _uiState.update { it.copy(isLoading = true, enhancementError = null) }
            try {
                // 1. Establish the session lazily on demand

// Define enhancement options (for example, enable upscale, tonemapping) based
// on bitmap dimensions.
                if (enhancementSession == null) {
                    val options = EnhancementOptions(
                        bitmap.width,
                        bitmap.height,
                        EnhancementMode.BITMAP,
                        enableTonemap = true,
                        enableDeblurDenoise = true,
                        enableDenoiseOnly = false,
                        enableUpscale = false,
                    )
                    enhancementSession = enhancementClient.createSessionAsync(options, enhancementExecutor)
                }
                val session = enhancementSession ?: throw IllegalStateException("Session unavailable.")
                // 2. Dispatch image through the neural pipeline
                val enhancedBitmap = session.processBitmapAsync(bitmap, session.defaultOptions)
                // 3. Render output to UI
                _uiState.update {
                    it.copy(enhancedImage = ImageInfo(bitmap = enhancedBitmap))
                }
            } catch (e: Exception) {
                _uiState.update { it.copy(enhancementError = e.message) }
            } finally {

// Ensure loading state is reset regardless of the outcome.
                _uiState.update { it.copy(isLoading = false) }
            }
        }
    }
    override fun onCleared() {
        // 4. Critical: Release native GPU hardware resources
        enhancementSession?.release()
        enhancementSession = null
        enhancementExecutor.shutdown()
        super.onCleared()
    }
}