Step counter test1
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file CameraXController.kt
|
||||
* @brief Owns CameraX use-case binding, recording control, and pose-overlay compositing.
|
||||
*/
|
||||
package com.example.jnicpp.bowling
|
||||
|
||||
import android.Manifest
|
||||
@@ -36,25 +40,37 @@ import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Owns all CameraX use-case binding and recording control. Deliberately not
|
||||
* an Activity/Fragment/View: it only needs a [Context], a [LifecycleOwner]
|
||||
* and a couple of Android views handed to it, which keeps the CameraX wiring
|
||||
* isolated from Android component lifecycle boilerplate and easy to reason
|
||||
* about (and to fake out behind [Callback] in tests that don't need a real
|
||||
* camera).
|
||||
* @brief Owns all CameraX use-case binding and recording control.
|
||||
*
|
||||
* Deliberately not an Activity/Fragment/View: it only needs a [Context], a
|
||||
* [LifecycleOwner] and a couple of Android views handed to it, which keeps
|
||||
* the CameraX wiring isolated from Android component lifecycle boilerplate
|
||||
* and easy to reason about (and to fake out behind [Callback] in tests that
|
||||
* don't need a real camera).
|
||||
*
|
||||
* @param appContext Application context used for camera/provider/content-resolver access.
|
||||
* @param cameraExecutor Background executor the pose analyzer runs inference on.
|
||||
*/
|
||||
class CameraXController(
|
||||
private val appContext: Context,
|
||||
private val cameraExecutor: java.util.concurrent.Executor
|
||||
) {
|
||||
|
||||
/** @brief Callbacks through which [CameraXController] reports camera, recording, and pose-detection events. */
|
||||
interface Callback {
|
||||
/** @brief Called once the camera use cases are bound and the preview is live. */
|
||||
fun onCameraReady() {}
|
||||
/** @brief Called if binding the camera use cases fails. @param message Human-readable failure reason. */
|
||||
fun onCameraError(message: String)
|
||||
/** @brief Called once an in-progress recording has actually started writing. */
|
||||
fun onRecordingStarted() {}
|
||||
/** @brief Called once a recording finishes successfully. @param outputUri Location of the saved video. */
|
||||
fun onRecordingFinalized(outputUri: Uri) {}
|
||||
/** @brief Called if starting or finishing a recording fails. @param message Human-readable failure reason. */
|
||||
fun onRecordingError(message: String)
|
||||
/** @brief Called if the pose detector fails on a given frame. @param message Human-readable failure reason. */
|
||||
fun onPoseDetectorError(message: String) {}
|
||||
/** @brief Called with each frame's pose detection result while pose detection is enabled. @param result The frame's landmarks, angles, and image metadata. */
|
||||
fun onPoseResult(result: PoseAnalyzer.PoseFrameResult)
|
||||
}
|
||||
|
||||
@@ -100,16 +116,25 @@ class CameraXController(
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Whether a video recording is currently in progress. */
|
||||
val isRecording: Boolean
|
||||
get() = activeRecording != null
|
||||
|
||||
/**
|
||||
* Binds Preview + VideoCapture (Recorder) + ImageAnalysis to a single
|
||||
* camera lifecycle, all at once, so the same camera stream feeds the
|
||||
* on-screen preview, the video file being recorded, and the pose
|
||||
* detector simultaneously. Also attaches an [OverlayEffect] to the
|
||||
* VideoCapture output so [setPoseDetectionEnabled] can bake the
|
||||
* skeleton into the recorded file, not just the live preview.
|
||||
* @brief Binds Preview + VideoCapture (Recorder) + ImageAnalysis to a
|
||||
* single camera lifecycle, all at once, so the same camera
|
||||
* stream feeds the on-screen preview, the video file being
|
||||
* recorded, and the pose detector simultaneously.
|
||||
*
|
||||
* Also attaches an `OverlayEffect` to the VideoCapture output so
|
||||
* [setPoseDetectionEnabled] can bake the skeleton into the recorded
|
||||
* file, not just the live preview.
|
||||
*
|
||||
* @param lifecycleOwner Lifecycle the camera use cases are bound to;
|
||||
* they're torn down automatically when it's destroyed.
|
||||
* @param previewView View the live camera preview is drawn into.
|
||||
* @param callback Receives camera/recording/pose-detection events from this point on.
|
||||
* @param lensFacing Which physical camera to bind, e.g. `CameraSelector.LENS_FACING_BACK`.
|
||||
*/
|
||||
fun bindToLifecycle(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
@@ -193,6 +218,12 @@ class CameraXController(
|
||||
}, ContextCompat.getMainExecutor(appContext))
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the lazily-created [OverlayEffect] that composites the
|
||||
* pose skeleton into recorded video frames, creating it (and its
|
||||
* backing [HandlerThread]) on first use.
|
||||
* @return The overlay effect to attach to the VideoCapture use case.
|
||||
*/
|
||||
private fun getOrCreateOverlayEffect(): OverlayEffect {
|
||||
overlayEffect?.let { return it }
|
||||
|
||||
@@ -244,14 +275,17 @@ class CameraXController(
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches/detaches the pose analyzer from the analysis stream, and
|
||||
* turns skeleton compositing into the recorded video on/off, without
|
||||
* needing a full unbind/rebind of the camera use cases. Cheap and
|
||||
* synchronous, so it's safe to call live from a preview-time toggle;
|
||||
* whatever this is set to when [startRecording] is called becomes that
|
||||
* recording's pose mode (plain video vs. with pose baked in) and stays
|
||||
* fixed for its duration -- callers are expected to stop offering the
|
||||
* toggle while a recording is in progress.
|
||||
* @brief Attaches/detaches the pose analyzer from the analysis stream,
|
||||
* and turns skeleton compositing into the recorded video on/off,
|
||||
* without needing a full unbind/rebind of the camera use cases.
|
||||
*
|
||||
* Cheap and synchronous, so it's safe to call live from a preview-time
|
||||
* toggle; whatever this is set to when [startRecording] is called
|
||||
* becomes that recording's pose mode (plain video vs. with pose baked
|
||||
* in) and stays fixed for its duration -- callers are expected to stop
|
||||
* offering the toggle while a recording is in progress.
|
||||
*
|
||||
* @param enabled true to run pose detection and draw/bake the skeleton, false to stop.
|
||||
*/
|
||||
fun setPoseDetectionEnabled(enabled: Boolean) {
|
||||
poseDetectionEnabled = enabled
|
||||
@@ -261,6 +295,7 @@ class CameraXController(
|
||||
applyPoseDetectionEnabled()
|
||||
}
|
||||
|
||||
/** @brief Attaches or clears the pose analyzer on [imageAnalysis] to match [poseDetectionEnabled]. */
|
||||
private fun applyPoseDetectionEnabled() {
|
||||
val analysis = imageAnalysis ?: return
|
||||
val analyzer = poseAnalyzer ?: return
|
||||
@@ -271,6 +306,14 @@ class CameraXController(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Starts recording video (with audio, if permission is granted)
|
||||
* to a new file in the shared Movies/bowling collection.
|
||||
*
|
||||
* No-op if the camera isn't bound yet or a recording is already in
|
||||
* progress. Reports [Callback.onRecordingStarted]/[Callback.onRecordingFinalized]/
|
||||
* [Callback.onRecordingError] asynchronously as the recording progresses.
|
||||
*/
|
||||
fun startRecording() {
|
||||
val cb = callback ?: return
|
||||
val capture = videoCapture
|
||||
@@ -326,12 +369,13 @@ class CameraXController(
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Stops the in-progress recording, if any; [Callback.onRecordingFinalized] follows asynchronously. */
|
||||
fun stopRecording() {
|
||||
activeRecording?.stop()
|
||||
activeRecording = null
|
||||
}
|
||||
|
||||
/** Unbinds all use cases and releases the pose detector. Call from onDestroy. */
|
||||
/** @brief Unbinds all use cases and releases the pose detector. Call from onDestroy. */
|
||||
fun release() {
|
||||
activeRecording?.stop()
|
||||
activeRecording = null
|
||||
|
||||
Reference in New Issue
Block a user