Media Enhancement API הוא פתרון AI במכשיר ששומר על הפרטיות, עם זמן טעינה נמוך, שמנצל האצת חומרה כדי לספק שיפורים באיכות המדיה ללא ניפוח של קובץ ה-APK. מידע נוסף זמין במאמר בנושא הסבר על היכולות של שיפור המדיה.
מצב Bitmap סטטי (EnhancementMode.BITMAP) מיועד לעיבוד תמונות סטטיות מפוענחות.
כדי לפעול במצב Bitmap, מערכת ההפעלה צריכה לבצע סריאליזציה של נתוני פיקסלים לא דחוסים ולהעתיק אותם מזיכרון המעבד דרך אפיק המערכת לזיכרון המעבד הגרפי, ואז להחזיר את הפריים המעובד באמצעות העתקה הפוכה. הוא מותאם לביצוע של פריים יחיד ולא תואם לסטרימינג של סרטוני וידאו בזמן אמת.
תהליך העבודה הזה כולל יצירת סשן, עיבוד של מפת סיביות אחת באמצעות הסשן ואז טיפול בתוצאה. דוגמאות הקוד מהקטעים הקודמים כוללות הסבר מפורט על תרחיש השימוש הזה.
- הגדרת אפשרויות: יוצרים אובייקט
EnhancementOptionsומוודאים שהערך שלenhancementModeמוגדר ל-EnhancementMode.BITMAP. - יצירת סשן: משתמשים ב-wrapper
createSessionAsyncשהגדרנו קודם כדי ליצורEnhancementSession. זהו אובייקט כבד, ולכן צריך ליצור אותו רק כשצריך. - Process image: קוראים להפעלה, למפת הסיביות של הקלט ולאפשרויות הרצויות.
- הטיפול בתוצאה: פונקציית ההשעיה מחזירה Bitmap חדש ומשופר אם הפעולה הצליחה, או מעלה חריגה אם היא נכשלה.
- שחרור הסשן: חשוב מאוד לקרוא ל-
session.release()כשמסיימים כדי לפנות משאבי GPU.
EnhancementSession הוא אובייקט הקשר כבד ששומר על צינור זיכרון מתמשך של GPU או NPU. הוא מקצה זיכרון RAM ייעודי לווידאו (VRAM)
ומטפל במערכת באופן מקומי. כדי למנוע דליפות זיכרון חמורות וקריסות פוטנציאליות של OutOfMemoryError, חשוב לפעול לפי העקרונות הבאים של מחזור החיים:
- יצירת מופע עצלנית: לא נוצר סשן עד שהמשתמש מתחיל פעולת שיפור.
- שימוש חוזר אסטרטגי: שמירה של מופע סשן יחיד ושימוש חוזר בו כשמעבדים כמה תמונות עם הגדרות זהות (מימדים ואפשרויות מופעלות).
- ניתוח של ההנחיה: הפעלת
session.release()באופן מיידי כשמשימות ויזואליות מסתיימות, כדי לפנות משאבי חומרה משותפים.
הפעלת מנוע השיפור
בשיטה הזו מתבצעת בדיקה דו-שלבית. הוא בודק אם החומרה של המכשיר תומכת בהאצה, ואז מוודא שקיימים המודולים הנדרשים של למידת מכונה.
הפעלת הפקודה הזו כשלב מקדים מונעת כשלים בהפעלת האפליקציה בזמן ריצה, כי היא מאמתת את היכולות לפני שהאפליקציה מנסה לעבד מדיה.
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 coroutine כדי להמיר קריאות חוזרות (callback) של לקוח מבוסס-משימות לפונקציות השהיה רגילות, וכך לאפשר ביצוע נקי ורציף.
// 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 כדי לנהל את מחזור החיים של הסשן. הגישה הזו מבטיחה שמשאבי ה-GPU הכבדים ישוחררו כשמנקים את ViewModel.
// 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()
}
}