Comprendre le cycle de vie de l'amélioration du contenu multimédia en mode Bitmap

L'API Media Enhancement fournit une solution d'IA sur l'appareil à faible latence et protégeant la confidentialité qui exploite l'accélération matérielle pour offrir des améliorations multimédias de haute qualité sans gonflement de l'APK. Pour en savoir plus, consultez Comprendre les fonctionnalités d'amélioration multimédia.

Le mode Bitmap statique (EnhancementMode.BITMAP) est conçu pour le traitement d'images statiques décodées.

Le fonctionnement en mode Bitmap nécessite que le système d'exploitation sérialise les données de pixels non compressées et les copie de la mémoire du processeur vers la mémoire du GPU via le bus système, en renvoyant la frame traitée par une copie inverse. Il est optimisé pour l'exécution d'une seule frame et n'est pas compatible avec le streaming vidéo en temps réel.

Ce workflow implique la création d'une session, le traitement d'un seul bitmap avec celle-ci, puis la gestion du résultat. Les exemples de code des sections précédentes couvrent ce cas d'utilisation en détail.

  1. Configurer les options : créez un objet EnhancementOptions, en vous assurant que enhancementMode est défini sur EnhancementMode.BITMAP.
  2. Créer une session : utilisez le wrapper createSessionAsync que nous avons défini précédemment pour créer un EnhancementSession. Il s'agit d'un objet lourd. Ne le créez donc que lorsque cela est nécessaire.
  3. Traiter l'image : appelez la session, votre bitmap d'entrée et les options souhaitées.
  4. Gérer le résultat : la fonction suspendue renvoie un nouveau bitmap amélioré en cas de succès ou génère une exception en cas d’échec.
  5. Libérer la session : il est essentiel d'appeler session.release() lorsque vous avez terminé pour libérer les ressources du GPU.

EnhancementSession est un objet de contexte lourd qui gère un pipeline de mémoire GPU ou NPU persistant. Il alloue de la RAM vidéo (VRAM) dédiée et des handles de système natifs. Pour éviter les fuites de mémoire graves et les plantages potentiels OutOfMemoryError, respectez les principes de cycle de vie suivants :

  1. Instanciation différée : ne créez pas de session tant que l'utilisateur n'a pas lancé d'action d'amélioration.
  2. Réutilisation stratégique : conservez et réutilisez une seule instance de session lorsque vous traitez plusieurs images avec des configurations identiques (dimensions et options activées/désactivées).
  3. Démontage rapide : appelez session.release() immédiatement lorsque les tâches visuelles se terminent pour libérer les ressources matérielles partagées.

Initialiser le moteur d'amélioration

Cette méthode orchestre une vérification en deux étapes. Elle vérifie si le matériel de l'appareil est compatible avec l'accélération, puis s'assure que les modules de machine learning requis sont présents.

L'exécution de cette étape préalable empêche les échecs d'initialisation au moment de l'exécution en validant les capacités avant que votre application ne tente de traiter des contenus multimédias.

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)
            }
        }
    }
}

Créer des wrappers de processus de session et de bitmap

Utilisez ces wrappers de coroutine Kotlin pour convertir les rappels client basés sur des tâches en fonctions de suspension standards, ce qui permet une exécution plus propre et séquentielle.

// 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)
}

Exécuter le pipeline bitmap dans un ViewModel

Pour intégrer le pipeline d'amélioration à l'architecture de votre application, utilisez un ViewModel pour gérer le cycle de vie de la session. Cette approche garantit que les ressources GPU lourdes sont libérées lorsque le ViewModel est effacé.

// 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()
    }
}