Lebenszyklus der Medienoptimierung im Bitmap-Modus

Die Media Enhancement API bietet eine datenschutzfreundliche On-Device-KI-Lösung mit niedriger Latenz, die Hardwarebeschleunigung nutzt, um hochwertige Medienverbesserungen ohne APK-Bloat zu ermöglichen. Weitere Informationen zu den Funktionen zur Medienoptimierung

Der statische Bitmap-Modus (EnhancementMode.BITMAP) ist für die Verarbeitung statischer, decodierter Bilder vorgesehen.

Im Bitmap-Modus muss das Betriebssystem unkomprimierte Pixeldaten serialisieren und aus dem CPU-Arbeitsspeicher über den Systembus in den GPU-Arbeitsspeicher kopieren. Der verarbeitete Frame wird dann durch ein umgekehrtes Kopieren zurückgegeben. Sie ist für die Ausführung einzelner Frames optimiert und nicht mit Echtzeit-Videostreaming kompatibel.

Bei diesem Workflow wird eine Sitzung erstellt, ein einzelnes Bitmap damit verarbeitet und dann das Ergebnis verarbeitet. Die Codebeispiele aus den vorherigen Abschnitten decken diesen Anwendungsfall im Detail ab.

  1. Optionen konfigurieren: Erstellen Sie ein EnhancementOptions-Objekt und achten Sie darauf, dass enhancementMode auf EnhancementMode.BITMAP festgelegt ist.
  2. Sitzung erstellen: Verwenden Sie den zuvor definierten createSessionAsync-Wrapper, um ein EnhancementSession zu erstellen. Dies ist ein ressourcenintensives Objekt. Erstellen Sie es daher nur bei Bedarf.
  3. Bild verarbeiten: Rufen Sie die Sitzung, die Eingabe-Bitmap und die gewünschten Optionen auf.
  4. Ergebnis verarbeiten: Die Suspend-Funktion gibt bei Erfolg eine neue, verbesserte Bitmap zurück oder löst bei einem Fehler eine Ausnahme aus.
  5. Sitzung beenden: Rufen Sie unbedingt session.release() auf, wenn Sie fertig sind, um GPU-Ressourcen freizugeben.

Das EnhancementSession ist ein schwergewichtiges Kontextobjekt, das eine persistente GPU- oder NPU-Speicherpipeline verwaltet. Es weist dedizierten Video-RAM (VRAM) und native Systemhandles zu. Um schwerwiegende Speicherlecks und potenzielle OutOfMemoryError-Abstürze zu vermeiden, sollten Sie die folgenden Lebenszyklusprinzipien beachten:

  1. Lazy Instantiation: Erstellen Sie erst dann eine Sitzung, wenn der Nutzer eine Optimierungsaktion startet.
  2. Strategische Wiederverwendung: Behalten Sie eine einzelne Sitzungsinstanz bei und verwenden Sie sie wieder, wenn Sie mehrere Bilder mit identischen Konfigurationen (Abmessungen und aktivierte Optionen) verarbeiten.
  3. Prompt-Teardown: Rufen Sie session.release() sofort auf, wenn visuelle Aufgaben beendet werden, um freigegebene Hardwareressourcen freizugeben.

Engine für die Optimierung initialisieren

Diese Methode führt eine zweistufige Prüfung durch. Es wird geprüft, ob die Hardware des Geräts die Beschleunigung unterstützt, und dann wird dafür gesorgt, dass die erforderlichen Module für maschinelles Lernen vorhanden sind.

Wenn Sie diesen Schritt als Voraussetzung ausführen, werden Laufzeitinitialisierungsfehler vermieden, da die Funktionen validiert werden, bevor Ihre App versucht, Medien zu verarbeiten.

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

Wrapper für Sitzungs- und Bitmap-Prozesse erstellen

Mit diesen Kotlin-Coroutine-Wrappern können Sie aufgabenbasierte Client-Callbacks in standardmäßige suspend-Funktionen umwandeln, um eine übersichtlichere und sequenzielle Ausführung zu ermöglichen.

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

Bitmap-Pipeline in einem ViewModel ausführen

Wenn Sie die Pipeline für die Optimierung in Ihre Anwendungsarchitektur einbinden möchten, verwenden Sie eine ViewModel, um den Sitzungslebenszyklus zu verwalten. So wird sichergestellt, dass die GPU-Ressourcen freigegeben werden, wenn der ViewModel gelöscht wird.

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