[go: nahoru, domu]

blob: 1d811a0bea37c07fe0bc79e157d285df67b5f287 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.integration.view
import android.annotation.SuppressLint
import android.content.ContentValues
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Toast
import androidx.annotation.VisibleForTesting
import androidx.camera.core.CameraEffect
import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraSelector.LENS_FACING_BACK
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.OutputFileOptions
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
import androidx.camera.video.MediaStoreOutputOptions
import androidx.camera.video.Recording
import androidx.camera.video.VideoRecordEvent
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
import androidx.camera.view.video.AudioConfig
import androidx.fragment.app.Fragment
/**
* Fragment for testing effects integration.
*/
class EffectsFragment : Fragment() {
private lateinit var cameraController: LifecycleCameraController
lateinit var previewView: PreviewView
private lateinit var surfaceEffectForPreviewVideo: RadioButton
lateinit var surfaceEffectForImageCapture: RadioButton
private lateinit var imageEffectForImageCapture: RadioButton
private lateinit var previewVideoGroup: RadioGroup
private lateinit var imageGroup: RadioGroup
private lateinit var capture: Button
private lateinit var record: Button
private lateinit var flip: Button
private var recording: Recording? = null
private lateinit var surfaceProcessor: ToneMappingSurfaceProcessor
private var imageEffect: ToneMappingImageEffect? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment.
val view = inflater.inflate(R.layout.effects_view, container, false)
previewView = view.findViewById(R.id.preview_view)
surfaceEffectForPreviewVideo = view.findViewById(R.id.surface_effect_for_preview_video)
surfaceEffectForImageCapture = view.findViewById(R.id.surface_effect_for_image_capture)
imageEffectForImageCapture = view.findViewById(R.id.image_effect_for_image_capture)
previewVideoGroup = view.findViewById(R.id.preview_and_video_effect_group)
imageGroup = view.findViewById(R.id.image_effect_group)
capture = view.findViewById(R.id.capture)
record = view.findViewById(R.id.record)
flip = view.findViewById(R.id.flip)
// Set up UI events.
previewView.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
previewVideoGroup.setOnCheckedChangeListener { _, _ -> updateEffects() }
imageGroup.setOnCheckedChangeListener { _, _ -> updateEffects() }
capture.setOnClickListener { takePicture() }
record.setOnClickListener {
if (recording == null) {
startRecording()
} else {
stopRecording()
}
}
flip.setOnClickListener {
if (cameraController.cameraSelector.lensFacing == LENS_FACING_BACK) {
cameraController.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
} else {
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
}
// Set up the surface processor.
surfaceProcessor = ToneMappingSurfaceProcessor()
// Set up the camera controller.
cameraController = LifecycleCameraController(requireContext())
cameraController.setEnabledUseCases(
CameraController.IMAGE_CAPTURE or CameraController.VIDEO_CAPTURE
)
previewView.controller = cameraController
updateEffects()
cameraController.bindToLifecycle(viewLifecycleOwner)
return view
}
private fun updateEffects() {
try {
val effects = mutableSetOf<CameraEffect>()
var surfaceEffectTarget = 0
if (surfaceEffectForPreviewVideo.isChecked) {
surfaceEffectTarget = surfaceEffectTarget or PREVIEW or VIDEO_CAPTURE
}
if (surfaceEffectForImageCapture.isChecked) {
surfaceEffectTarget = surfaceEffectTarget or IMAGE_CAPTURE
}
if (surfaceEffectTarget != 0) {
effects.add(
ToneMappingSurfaceEffect(
surfaceEffectTarget,
surfaceProcessor
)
)
}
if (imageEffectForImageCapture.isChecked) {
// Use ImageEffect for image capture
imageEffect = ToneMappingImageEffect()
effects.add(imageEffect!!)
} else {
imageEffect = null
}
cameraController.setEffects(effects)
} catch (e: RuntimeException) {
toast("Failed to set effects: $e")
}
}
override fun onDestroyView() {
super.onDestroyView()
surfaceProcessor.release()
}
private fun toast(message: String?) {
requireActivity().runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
private fun takePicture() {
takePicture(
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
toast("Image saved successfully.")
}
override fun onError(exception: ImageCaptureException) {
toast("Image capture failed. $exception")
}
}
)
}
fun takePicture(onImageSavedCallback: ImageCapture.OnImageSavedCallback) {
createDefaultPictureFolderIfNotExist()
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
val outputFileOptions = OutputFileOptions.Builder(
requireContext().contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
).build()
cameraController.takePicture(
outputFileOptions,
directExecutor(),
onImageSavedCallback
)
}
@SuppressLint("MissingPermission")
private fun startRecording() {
record.text = "Stop recording"
val outputOptions: MediaStoreOutputOptions = getNewVideoOutputMediaStoreOptions()
val audioConfig = AudioConfig.create(true)
recording = cameraController.startRecording(
outputOptions, audioConfig,
directExecutor()
) {
if (it is VideoRecordEvent.Finalize) {
val uri = it.outputResults.outputUri
when (it.error) {
VideoRecordEvent.Finalize.ERROR_NONE,
ERROR_FILE_SIZE_LIMIT_REACHED,
ERROR_DURATION_LIMIT_REACHED,
ERROR_INSUFFICIENT_STORAGE,
ERROR_SOURCE_INACTIVE -> toast("Video saved to: $uri")
else -> toast("Failed to save video: uri $uri with code (${it.error})")
}
}
}
}
private fun stopRecording() {
record.text = "Record"
recording?.stop()
}
private fun getNewVideoOutputMediaStoreOptions(): MediaStoreOutputOptions {
val videoFileName = "video_" + System.currentTimeMillis()
val resolver = requireContext().contentResolver
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
contentValues.put(MediaStore.Video.Media.TITLE, videoFileName)
contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName)
return MediaStoreOutputOptions.Builder(
resolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
).setContentValues(contentValues)
.build()
}
private fun createDefaultPictureFolderIfNotExist() {
val pictureFolder = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
)
if (!pictureFolder.exists()) {
if (!pictureFolder.mkdir()) {
toast("Failed to create directory: $pictureFolder")
}
}
}
@VisibleForTesting
fun getImageEffect(): ToneMappingImageEffect? {
return imageEffect
}
@VisibleForTesting
fun getSurfaceProcessor(): ToneMappingSurfaceProcessor {
return surfaceProcessor
}
}