Skip to content

Commit

Permalink
feat: Shutter sound and stabilization
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Feb 29, 2024
1 parent cb2f1c3 commit 7b46390
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 4 deletions.
2 changes: 1 addition & 1 deletion package/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ dependencies {
implementation "com.facebook.react:react-android:+"

// CameraX dependency
def camerax_version = "1.3.1"
def camerax_version = "1.4.0-alpha04"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.media.MediaActionSound
import android.util.Log
import android.util.Range
import android.util.Size
import androidx.annotation.OptIn
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraState
import androidx.camera.core.DynamicRange
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.MirrorMode
import androidx.camera.core.Preview
import androidx.camera.core.PreviewCapabilities
import androidx.camera.core.TorchState
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.extensions.ExtensionMode
Expand All @@ -38,6 +41,7 @@ import com.mrousavy.camera.extensions.await
import com.mrousavy.camera.extensions.byId
import com.mrousavy.camera.extensions.forSize
import com.mrousavy.camera.extensions.getCameraError
import com.mrousavy.camera.extensions.id
import com.mrousavy.camera.extensions.takePicture
import com.mrousavy.camera.extensions.toCameraError
import com.mrousavy.camera.extensions.withExtension
Expand All @@ -48,6 +52,7 @@ import com.mrousavy.camera.types.QualityBalance
import com.mrousavy.camera.types.RecordVideoOptions
import com.mrousavy.camera.types.Torch
import com.mrousavy.camera.types.Video
import com.mrousavy.camera.types.VideoStabilizationMode
import com.mrousavy.camera.utils.FileUtils
import com.mrousavy.camera.utils.runOnUiThread
import com.mrousavy.camera.utils.runOnUiThreadAndWait
Expand Down Expand Up @@ -179,18 +184,25 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
}
}

@Suppress("LiftReturnOrAssignment")
@SuppressLint("RestrictedApi")
private suspend fun configureOutputs(configuration: CameraConfiguration) {
Log.i(TAG, "Creating new Outputs for Camera #${configuration.cameraId}...")
val fpsRange = getTargetFpsRange(configuration)
val format = configuration.format

// TODO: Check if all of the values we set are supported with Video/Photo/Preview Capabilities from CameraInfo.

// 1. Preview
val previewConfig = configuration.preview as? CameraConfiguration.Output.Enabled<CameraConfiguration.Preview>
if (previewConfig != null) {
Log.i(TAG, "Creating Preview output...")
runOnUiThreadAndWait {
val preview = Preview.Builder().also { preview ->
// Configure Preview Output
if (configuration.videoStabilizationMode.isAtLeast(VideoStabilizationMode.CINEMATIC)) {
preview.setPreviewStabilizationEnabled(true)
}
if (fpsRange != null) {
preview.setTargetFrameRate(fpsRange)
}
Expand All @@ -207,6 +219,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
if (photoConfig != null) {
Log.i(TAG, "Creating Photo output...")
val photo = ImageCapture.Builder().also { photo ->
// Configure Photo Output
photo.setCaptureMode(photoConfig.config.photoQualityBalance.toCaptureMode())
if (format != null) {
Log.i(TAG, "Photo size: ${format.photoSize}")
Expand All @@ -231,8 +244,13 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
// video.setTargetVideoEncodingBitRate()
}.build()


val video = VideoCapture.Builder(recorder).also { video ->
// Configure Video Output
video.setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
if (configuration.videoStabilizationMode.isAtLeast(VideoStabilizationMode.STANDARD)) {
video.setVideoStabilizationEnabled(true)
}
if (fpsRange != null) {
video.setTargetFrameRate(fpsRange)
}
Expand Down Expand Up @@ -355,12 +373,13 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
val camera = camera ?: throw CameraNotReadyError()
val photoOutput = photoOutput ?: throw PhotoNotEnabledError()

// TODO: Add shutter sound, stabilization and quality prioritization support here?
// TODO: Add stabilization and quality prioritization support here?

photoOutput.flashMode = flash.toFlashMode()
photoOutput.targetRotation = outputOrientation.toDegrees()
val playSound = enableShutterSound || CameraInfo.mustPlayShutterSound()

val image = photoOutput.takePicture(CameraQueues.cameraQueue.executor)
val image = photoOutput.takePicture(playSound, CameraQueues.cameraQueue.executor)
val isMirrored = camera.cameraInfo.lensFacing == CameraSelector.LENS_FACING_FRONT
return Photo(image, isMirrored)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mrousavy.camera.extensions

import android.media.MediaActionSound
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
Expand All @@ -8,11 +9,21 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine

suspend inline fun ImageCapture.takePicture(executor: Executor): ImageProxy =
suspend inline fun ImageCapture.takePicture(enableShutterSound: Boolean, executor: Executor): ImageProxy =
suspendCancellableCoroutine { continuation ->
val shutterSound = if (enableShutterSound) MediaActionSound() else null
shutterSound?.load(MediaActionSound.SHUTTER_CLICK)

takePicture(
executor,
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureStarted() {
super.onCaptureStarted()
if (enableShutterSound) {
shutterSound?.play(MediaActionSound.SHUTTER_CLICK)
}
}

override fun onCaptureSuccess(image: ImageProxy) {
super.onCaptureSuccess(image)
if (continuation.isActive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ enum class VideoStabilizationMode(override val unionValue: String) : JSUnionValu
CINEMATIC("cinematic"),
CINEMATIC_EXTENDED("cinematic-extended");

private val score: Int
get() {
return when (this) {
OFF -> 0
STANDARD -> 1
CINEMATIC -> 2
CINEMATIC_EXTENDED -> 3
}
}

fun isAtLeast(mode: VideoStabilizationMode): Boolean {
return score >= mode.score
}

companion object : JSUnionValue.Companion<VideoStabilizationMode> {
override fun fromUnionValue(unionValue: String?): VideoStabilizationMode =
when (unionValue) {
Expand Down

0 comments on commit 7b46390

Please sign in to comment.