Код: Выделить всё
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? = 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()
}
}
, который говорит о создании createLlbSession, но я не уверен, как передать выходную поверхностьForLlb, как показано в документе, а также я не уверен, что вызываю его в нужный момент, поскольку единственный пример кода относится к Camera2, а не к CameraX.
Вот пример кодовой базы.>
Подробнее здесь: https://stackoverflow.com/questions/798 ... n-requires
Мобильная версия