Skip to content

Commit

Permalink
Camera fragments refactored into single class.
Browse files Browse the repository at this point in the history
  • Loading branch information
zegkljan committed Sep 10, 2022
1 parent 98303f5 commit 1b6718f
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 518 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,19 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

class HighSpeedCameraFragment : Fragment() {
class CameraFragment : Fragment() {

/** Android ViewBinding */
private var _fragmentCameraBinding: FragmentCameraBinding? = null

private val fragmentCameraBinding get() = _fragmentCameraBinding!!

/** AndroidX navigation arguments */
private val args: HighSpeedCameraFragmentArgs by navArgs()
private val args: CameraFragmentArgs by navArgs()

private val isHighSpeed: Boolean by lazy {
args.fps >= 120
}

/** Host's navigation controller */
private val navController: NavController by lazy {
Expand Down Expand Up @@ -106,7 +110,10 @@ class HighSpeedCameraFragment : Fragment() {
private val cameraHandler = Handler(cameraThread.looper)

/** Captures high speed frames from a [CameraDevice] for our slow motion video recording */
private lateinit var session: CameraConstrainedHighSpeedCaptureSession
private lateinit var highSpeedSession: CameraConstrainedHighSpeedCaptureSession

/** Captures frames from a [CameraDevice] for our video recording */
private lateinit var session: CameraCaptureSession

/** The [CameraDevice] that will be opened in this fragment */
private lateinit var camera: CameraDevice
Expand All @@ -115,37 +122,66 @@ class HighSpeedCameraFragment : Fragment() {

/** Requests used for preview only in the [CameraConstrainedHighSpeedCaptureSession] */
private val previewRequestList: List<CaptureRequest> by lazy {
// Log.d(TAG, "previewRequestList lazy")
// Capture request holds references to target surfaces
session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
highSpeedSession.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
// Add the preview surface target
addTarget(fragmentCameraBinding.viewFinder.holder.surface)
// High speed capture session requires a target FPS range, even for preview only
set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(FPS_PREVIEW_ONLY, args.fps))
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW)
}.let {
// Creates a list of highly optimized capture requests sent to the camera for a high
// speed video session. Important note: Must use repeating burst request type
session.createHighSpeedRequestList(it.build())
highSpeedSession.createHighSpeedRequestList(it.build())
}
}

/** Request used for preview only in the [CameraCaptureSession] */
private val previewRequest: CaptureRequest by lazy {
// Capture request holds references to target surfaces
session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
// Add the preview surface target
addTarget(fragmentCameraBinding.viewFinder.holder.surface)
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW)
}.build()
}

/** Requests used for preview and recording in the [CameraConstrainedHighSpeedCaptureSession] */
private val recordRequestList: List<CaptureRequest> by lazy {
// Log.d(TAG, "recordRequestList lazy")
// Capture request holds references to target surfaces
session.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
highSpeedSession.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
// Add the preview and recording surface targets
addTarget(fragmentCameraBinding.viewFinder.holder.surface)
addTarget(recorderSurface)
// Sets user requested FPS for all targets
set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(args.fps, args.fps))
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD)
}.let {
// Creates a list of highly optimized capture requests sent to the camera for a high
// speed video session. Important note: Must use repeating burst request type
session.createHighSpeedRequestList(it.build())
highSpeedSession.createHighSpeedRequestList(it.build())
}
}

/** Request used for preview and recording in the [CameraCaptureSession] */
private val recordRequest: CaptureRequest by lazy {
// Log.d(TAG, "recordRequest lazy")
// Capture request holds references to target surfaces
session.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
// Add the preview and recording surface targets
addTarget(fragmentCameraBinding.viewFinder.holder.surface)
addTarget(recorderSurface)
// Sets user requested FPS for all targets
set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range(args.fps, args.fps))
set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO)
set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD)
}.build()
}

private var recordingStartMillis: Long = 0L

/** Live data listener for changes in the device orientation relative to the camera */
Expand Down Expand Up @@ -226,18 +262,29 @@ class HighSpeedCameraFragment : Fragment() {
val targets = listOf(fragmentCameraBinding.viewFinder.holder.surface, recorderSurface)

// Start a capture session using our open camera and list of Surfaces where frames will go
session = createCaptureSession(camera, targets, cameraHandler)
if (isHighSpeed) {
highSpeedSession = createHighSpeedCaptureSession(camera, targets, cameraHandler)
} else {
session = createCaptureSession(camera, targets, cameraHandler)
}

// Ensures the requested size and FPS are compatible with this camera
val fpsRange = Range(args.fps, args.fps)
assert(
true == characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
?.getHighSpeedVideoFpsRangesFor(Size(args.width, args.height))?.contains(fpsRange)
)
if (isHighSpeed) {
// Ensures the requested size and FPS are compatible with this camera
val fpsRange = Range(args.fps, args.fps)
assert(
true == characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
?.getHighSpeedVideoFpsRangesFor(Size(args.width, args.height))
?.contains(fpsRange)
)
}

// Sends the capture request as frequently as possible until the session is torn down or
// session.stopRepeating() is called
session.setRepeatingBurst(previewRequestList, null, cameraHandler)
if (isHighSpeed) {
highSpeedSession.setRepeatingBurst(previewRequestList, null, cameraHandler)
} else {
session.setRepeatingRequest(previewRequest, null, cameraHandler)
}

// Listen to the capture button
fragmentCameraBinding.captureButton.setOnCheckedChangeListener { _, isChecked ->
Expand All @@ -249,8 +296,13 @@ class HighSpeedCameraFragment : Fragment() {
ActivityInfo.SCREEN_ORIENTATION_LOCKED

// Stops preview requests, and start record requests
session.stopRepeating()
session.setRepeatingBurst(recordRequestList, null, cameraHandler)
if (isHighSpeed) {
highSpeedSession.stopRepeating()
highSpeedSession.setRepeatingBurst(recordRequestList, null, cameraHandler)
} else {
session.stopRepeating()
session.setRepeatingRequest(recordRequest, null, cameraHandler)
}

// Finalizes recorder setup and starts recording
recorder.apply {
Expand Down Expand Up @@ -299,13 +351,12 @@ class HighSpeedCameraFragment : Fragment() {
// navigate to player
requireActivity().runOnUiThread {
navController.navigate(
HighSpeedCameraFragmentDirections.actionHighSpeedCameraToPlayer(
CameraFragmentDirections.actionCameraToPlayer(
medium!!.getUriString(),
args.cameraId,
args.width,
args.height,
args.fps,
true
args.fps
)
)
}
Expand All @@ -321,7 +372,6 @@ class HighSpeedCameraFragment : Fragment() {
cameraId: String,
handler: Handler? = null
): CameraDevice = suspendCancellableCoroutine { cont ->
// Log.d(TAG, "openCamera")
manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
// Log.d(TAG, "openCamera/onOpened")
Expand Down Expand Up @@ -352,10 +402,10 @@ class HighSpeedCameraFragment : Fragment() {
}

/**
* Creates a [CameraCaptureSession] and returns the configured session (as the result of the
* Creates a [CameraConstrainedHighSpeedCaptureSession] and returns the configured session (as the result of the
* suspend coroutine)
*/
private suspend fun createCaptureSession(
private suspend fun createHighSpeedCaptureSession(
device: CameraDevice,
targets: List<Surface>,
handler: Handler? = null
Expand All @@ -381,6 +431,36 @@ class HighSpeedCameraFragment : Fragment() {
)
}

/**
* Creates a [CameraCaptureSession] and returns the configured session (as the result of the
* suspend coroutine)
*/
private suspend fun createCaptureSession(
device: CameraDevice,
targets: List<Surface>,
handler: Handler? = null
): CameraCaptureSession = suspendCoroutine { cont ->
// Log.d(TAG, "createCaptureSession")
// Creates a capture session using the predefined targets, and defines a session state
// callback which resumes the coroutine once the session is configured
device.createCaptureSession(
targets, object : CameraCaptureSession.StateCallback() {

override fun onConfigured(session: CameraCaptureSession) {
// Log.d(TAG, "createCaptureSession/onConfigured")
cont.resume(session as CameraCaptureSession)
}

override fun onConfigureFailed(session: CameraCaptureSession) {
// Log.d(TAG, "createCaptureSession/onConfigureFailed")
val exc = RuntimeException("Camera ${device.id} session configuration failed")
// Log.e(TAG, exc.message, exc)
cont.resumeWithException(exc)
}
}, handler
)
}

override fun onStop() {
// Log.d(TAG, "onStop")
super.onStop()
Expand All @@ -406,7 +486,7 @@ class HighSpeedCameraFragment : Fragment() {
}

companion object {
private val TAG = HighSpeedCameraFragment::class.java.simpleName
private val TAG = CameraFragment::class.java.simpleName

private const val RECORDER_VIDEO_BITRATE: Int = 10000000
private const val MIN_REQUIRED_RECORDING_TIME_MILLIS: Long = 1000L
Expand Down
Loading

0 comments on commit 1b6718f

Please sign in to comment.