ARTICLE AD BOX
I have this code
import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import android.Manifest import android.content.ContentValues import android.content.pm.PackageManager import android.hardware.camera2.CameraCaptureSession import android.hardware.camera2.CaptureRequest import android.hardware.camera2.TotalCaptureResult import android.os.Build import android.provider.MediaStore import androidx.camera.video.Recorder import androidx.camera.video.Recording import androidx.camera.video.VideoCapture import androidx.core.content.ContextCompat import java.util.concurrent.ExecutorService import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.core.Preview import androidx.camera.core.CameraSelector import android.util.Log import android.view.Surface import android.widget.Button import androidx.annotation.OptIn import androidx.camera.camera2.interop.Camera2Interop import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.SurfaceRequest import androidx.camera.core.UseCaseGroup import androidx.camera.video.MediaStoreOutputOptions import androidx.camera.video.Quality import androidx.camera.video.QualitySelector import androidx.camera.video.VideoRecordEvent import androidx.camera.view.PreviewView import androidx.core.content.PermissionChecker import androidx.lifecycle.lifecycleScope import com.google.android.gms.cameralowlight.LowLightBoost import com.google.android.gms.cameralowlight.LowLightBoostCallback import com.google.android.gms.cameralowlight.LowLightBoostClient import com.google.android.gms.cameralowlight.LowLightBoostOptions import com.google.android.gms.cameralowlight.LowLightBoostSession import com.google.android.gms.cameralowlight.SceneDetectorCallback import com.google.android.gms.common.api.Status import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.Executor class MainActivity : AppCompatActivity() { private var videoCapture: VideoCapture<Recorder>? = null private var recording: Recording? = null private var llbSession: LowLightBoostSession? = null private lateinit var cameraExecutor: ExecutorService private lateinit var viewFinder: PreviewView private lateinit var button: Button private val activityResultLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> // Handle Permission granted/rejected var permissionGranted = true permissions.entries.forEach { if (it.key in REQUIRED_PERMISSIONS && !it.value) permissionGranted = false } if (!permissionGranted) { Toast.makeText( baseContext, "Permission request denied", Toast.LENGTH_SHORT ).show() } else { startCamera() } } val cameraId = "12" var isInstalled = false private lateinit var llbClient: LowLightBoostClient private lateinit var executor: Executor val callback = object : LowLightBoostClient.InstallStatusCallback { override fun onError(description: String) { } override fun onCancelled() { } override fun onDownloadProgressUpdate(progress: Int) { } override fun onDownloadPending() { } override fun onDownloadStart() { } override fun onDownloadPaused() { } override fun onDownloadComplete() { } override fun onInstalled() { isInstalled = true } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } viewFinder = findViewById(R.id.viewFinder) button = findViewById(R.id.video_capture_button) executor = ContextCompat.getMainExecutor(this) lifecycleScope.launch { llbClient = LowLightBoost.getClient(this@MainActivity) val isSupported = llbClient.isCameraSupported(cameraId).await() if (isSupported && !isInstalled) { // Trigger installation llbClient.installModule(callback).await() requestCameraPermission() } } button.setOnClickListener { captureVideo() } } suspend fun createLlbSession(surfaceRequest: SurfaceRequest, outputSurfaceForLlb: Surface) { // 1. Create the LLB Session configuration val options = LowLightBoostOptions( outputSurfaceForLlb, cameraId, surfaceRequest.resolution.width, surfaceRequest.resolution.height, true // Start enabled ) // 2. Create the session. llbSession = llbClient.createSession(options, object : LowLightBoostCallback { override fun onSessionDisconnected(status: Status) { } override fun onSessionDestroyed() { } }).await() // 3. Get the surface to use. val llbInputSurface = llbSession?.getCameraSurface() if (llbInputSurface != null) { // 4. Provide the surface to the CameraX Preview UseCase. surfaceRequest.provideSurface(llbInputSurface, executor, resultListener) // 5. Set the scene detector callback to monitor how much boost is being applied. val onSceneBrightnessChanged = object : SceneDetectorCallback { override fun onSceneBrightnessChanged( session: LowLightBoostSession, boostStrength: Float ) { // Monitor the boostStrength from 0 (no boosting) to 1 (maximum boosting) } } llbSession?.setSceneDetectorCallback(onSceneBrightnessChanged, null) } } val resultListener: (SurfaceRequest.Result) -> Unit = { result -> when (result.resultCode) { SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY -> { // Surface accepted } SurfaceRequest.Result.RESULT_REQUEST_CANCELLED -> { // Camera closed } SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE -> { // UseCase stopped } SurfaceRequest.Result.RESULT_INVALID_SURFACE -> { // Invalid surface } SurfaceRequest.Result.RESULT_SURFACE_ALREADY_PROVIDED -> { // Surface already provided } } } private fun requestCameraPermission() { if (allPermissionsGranted()) { startCamera() } else { requestPermissions() } } private fun captureVideo() { val videoCapture = this.videoCapture ?: return button.isEnabled = false val curRecording = recording if (curRecording != null) { // Stop the current recording session. curRecording.stop() recording = null return } // create and start a new recording session val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .apply { if (PermissionChecker.checkSelfPermission( this@MainActivity, Manifest.permission.RECORD_AUDIO ) == PermissionChecker.PERMISSION_GRANTED ) { withAudioEnabled() } } .start(executor) { recordEvent -> when (recordEvent) { is VideoRecordEvent.Start -> { button.apply { text = "Stop" isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT) .show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e( TAG, "Video capture ends with error: " + "${recordEvent.error}" ) } button.apply { text = "Start" isEnabled = true } } } } } @OptIn(ExperimentalCamera2Interop::class) private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview val previewBuilder = Preview.Builder() val extender = Camera2Interop.Extender(previewBuilder) extender.setSessionCaptureCallback( object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) llbSession?.processCaptureResult(result) } } ) val preview = previewBuilder.build() .also { it.surfaceProvider = viewFinder.surfaceProvider } preview.setSurfaceProvider { surfaceRequest -> lifecycleScope.launch { if (::llbClient.isInitialized && isInstalled) { createLlbSession(surfaceRequest, someSurface) } } } val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.HIGHEST)) .build() videoCapture = VideoCapture.withOutput(recorder) // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA val cameraInfo = cameraProvider.getCameraInfo(cameraSelector) val useCaseGroupBuilder = UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(videoCapture!!) try { // Unbind use cases before rebinding cameraProvider.unbindAll() val camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCaseGroupBuilder.build() ) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } private fun requestPermissions() { activityResultLauncher.launch(REQUIRED_PERMISSIONS) } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it ) == PackageManager.PERMISSION_GRANTED } override fun onDestroy() { super.onDestroy() cameraExecutor.shutdown() } companion object { private const val TAG = "CameraXApp" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private val REQUIRED_PERMISSIONS = mutableListOf( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO ).apply { }.toTypedArray() } }Now I am following this doc, and using Google Low Light Boost
which tells to create createLlbSession but I am not sure how to pass outputSurfaceForLlb as shown in the doc, also I am not sure I am calling it at the right point of time as the only code sample is with Camera2 and not CameraX
Here is the sample codebase, any help would be appreciated
