package com.example.jnicpp.bowling import android.media.MediaMetadataRetriever import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.gms.tasks.Tasks import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.pose.PoseDetection import com.google.mlkit.vision.pose.accurate.AccuratePoseDetectorOptions import org.junit.Test import org.junit.runner.RunWith /** * Replays a pre-recorded reference video through the exact same * detection/smoothing/step-counting pipeline the live camera screen uses * (PoseAnalyzer's detector config -> PoseLandmarkSmoother -> * AnkleHipMovingAverageFilter -> buildPoseFrame -> LiveStepDetector), so the * algorithm can be validated against a video with a known, hand-counted * step count without needing a live device recording session each time. * * Not run as part of the normal test suite -- this is a diagnostic tool, * invoked directly via `connectedAndroidTest` with a specific video pushed * to the device first. */ @RunWith(AndroidJUnit4::class) class VideoStepReplayTest { @Test fun replayReferenceVideo() { val context = InstrumentationRegistry.getInstrumentation().targetContext val videoPath = context.getExternalFilesDir(null)!!.resolve("reference_test.mp4").absolutePath val retriever = MediaMetadataRetriever() retriever.setDataSource(videoPath) val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) ?.toLongOrNull() ?: 0L val detector = PoseDetection.getClient( AccuratePoseDetectorOptions.Builder() .setDetectorMode(AccuratePoseDetectorOptions.STREAM_MODE) .build() ) val landmarkSmoother = PoseLandmarkSmoother() val ankleHipSmoother = AnkleHipMovingAverageFilter() val liveStepDetector = LiveStepDetector() val logger = DebugSessionLogger(context) logger.start() val stepMs = 33L var t = 0L var finalStepCount = 0 var framesProcessed = 0 while (t < durationMs) { val bitmap = retriever.getFrameAtTime(t * 1000, MediaMetadataRetriever.OPTION_CLOSEST) if (bitmap != null) { val inputImage = InputImage.fromBitmap(bitmap, 0) val pose = Tasks.await(detector.process(inputImage)) val landmarks = landmarkSmoother.smooth(pose) val angles = PoseAngleCalculator.compute(landmarks) val smoothedAnkleHip = ankleHipSmoother.smooth(landmarks) val frame = buildPoseFrame(t, landmarks, smoothedAnkleHip, angles) val result = liveStepDetector.update(frame) finalStepCount = result.stepCount logger.log(landmarks, t, finalStepCount, result.handRaiseProgress) framesProcessed++ } t += stepMs } logger.stop() detector.close() retriever.release() println( "VideoStepReplayTest: processed $framesProcessed frames over ${durationMs}ms, " + "final step count = $finalStepCount" ) } }