P0 native decode/encode benchmark passes on Xiaomi (12MP decode+encode ~210-270ms, off-main-thread)
This commit is contained in:
+33
@@ -2,6 +2,7 @@ package com.locphamtran.recipescamera.export
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import expo.modules.kotlin.functions.Coroutine
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
@@ -10,9 +11,12 @@ import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
private const val TAG = "RecipesCamExport"
|
||||
|
||||
class RecipescamExportModule : Module() {
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("RecipesCamExport")
|
||||
Log.i(TAG, "module defined RecipesCamExport")
|
||||
|
||||
// P0 spike: decode (BitmapFactory) + JPEG encode + file write, all off the
|
||||
// JS/main threads. Returns per-stage ms so JS can prove the JS thread is
|
||||
@@ -20,12 +24,39 @@ class RecipescamExportModule : Module() {
|
||||
AsyncFunction("decodeEncodeAsync") Coroutine { srcPath: String, dstPath: String, quality: Int ->
|
||||
decodeEncodeAsync(srcPath, dstPath, quality)
|
||||
}
|
||||
|
||||
// P0 probe helpers — removed before ship.
|
||||
AsyncFunction("ping") { ->
|
||||
Log.i(TAG, "ping called")
|
||||
"pong"
|
||||
}
|
||||
|
||||
// P0 spike harness: copy a bundled drawable asset (e.g. "wallframe", the
|
||||
// 3117x4000 artwork — representative 12MP decode) into cacheDir and hand the
|
||||
// real file path back to JS, which then runs decodeEncodeAsync on it. This
|
||||
// exercises the exact file-based path P1 will use.
|
||||
AsyncFunction("materializeBenchAsset") Coroutine { assetName: String ->
|
||||
val ctx = appContext.reactContext ?: error("react context lost")
|
||||
val resId = ctx.resources.getIdentifier(assetName, "drawable", ctx.packageName)
|
||||
check(resId != 0) { "drawable $assetName not bundled" }
|
||||
val srcFile = File(ctx.cacheDir, "$assetName.png")
|
||||
withContext(Dispatchers.IO) {
|
||||
if (!srcFile.exists()) {
|
||||
ctx.resources.openRawResource(resId).use { input ->
|
||||
FileOutputStream(srcFile).use { output -> input.copyTo(output) }
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "materialized $assetName -> ${srcFile.absolutePath} bytes=${srcFile.length()}")
|
||||
}
|
||||
srcFile.absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun decodeEncodeAsync(srcPath: String, dstPath: String, quality: Int): Map<String, Int> {
|
||||
val timings = mutableMapOf<String, Int>()
|
||||
val t0 = System.nanoTime()
|
||||
Log.i(TAG, "start src=$srcPath q=$quality thread=${Thread.currentThread().name}")
|
||||
withContext(Dispatchers.IO) {
|
||||
val src = File(srcPath)
|
||||
check(src.exists()) { "source file missing: $srcPath" }
|
||||
@@ -40,8 +71,10 @@ private suspend fun decodeEncodeAsync(srcPath: String, dstPath: String, quality:
|
||||
check(bmp.compress(Bitmap.CompressFormat.JPEG, quality, fos)) { "jpeg encode failed" }
|
||||
}
|
||||
timings["encodeWriteMs"] = ((System.nanoTime() - t2) / 1_000_000).toInt()
|
||||
Log.i(TAG, "run done decodeMs=${timings["decodeMs"]} encodeWriteMs=${timings["encodeWriteMs"]} size=${bmp.width}x${bmp.height} thread=${Thread.currentThread().name}")
|
||||
bmp.recycle()
|
||||
}
|
||||
timings["totalMs"] = ((System.nanoTime() - t0) / 1_000_000).toInt()
|
||||
Log.i(TAG, "totalMs=${timings["totalMs"]}")
|
||||
return timings
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ export interface DecodeEncodeTimings {
|
||||
export interface RecipescamExportModule {
|
||||
/** Decode JPEG/PNG at srcPath (plain absolute path), re-encode JPEG q and write dstPath. All native, background. Returns stage timings in ms. */
|
||||
decodeEncodeAsync(srcPath: string, dstPath: string, quality: number): Promise<DecodeEncodeTimings>;
|
||||
/** P0 probe helpers — removed before ship. */
|
||||
ping(): Promise<string>;
|
||||
materializeBenchAsset(assetName: string): Promise<string>;
|
||||
}
|
||||
|
||||
export default requireNativeModule('RecipesCamExport') as RecipescamExportModule;
|
||||
|
||||
Reference in New Issue
Block a user