mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-23 00:50:30 +02:00
feat: Add first working non-stretched camera preview
This commit is contained in:
parent
20b466872b
commit
da1438c989
@ -8,6 +8,7 @@ import android.hardware.camera2.CameraCharacteristics
|
|||||||
import android.hardware.camera2.CameraDevice
|
import android.hardware.camera2.CameraDevice
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import android.media.ImageReader
|
import android.media.ImageReader
|
||||||
|
import android.net.wifi.aware.Characteristics
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
@ -26,9 +27,13 @@ class CameraHandler(
|
|||||||
private val device: CameraDevice,
|
private val device: CameraDevice,
|
||||||
private val handler: Handler,
|
private val handler: Handler,
|
||||||
private val thread: HandlerThread,
|
private val thread: HandlerThread,
|
||||||
|
private val lens: Int = CameraCharacteristics.LENS_FACING_BACK,
|
||||||
) {
|
) {
|
||||||
private lateinit var imageReader: ImageReader
|
private lateinit var imageReader: ImageReader
|
||||||
|
|
||||||
|
val characteristics: CameraCharacteristics
|
||||||
|
get() = manager.getCameraCharacteristics(lens.toString())
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.P)
|
@RequiresApi(Build.VERSION_CODES.P)
|
||||||
private suspend fun createCaptureSession(
|
private suspend fun createCaptureSession(
|
||||||
outputs: List<Surface>,
|
outputs: List<Surface>,
|
||||||
@ -75,7 +80,7 @@ class CameraHandler(
|
|||||||
Log.d("Alibi", "Taking photo")
|
Log.d("Alibi", "Taking photo")
|
||||||
|
|
||||||
Log.d("Alibi", "Creating Camera Characteristics")
|
Log.d("Alibi", "Creating Camera Characteristics")
|
||||||
val characteristics = manager.getCameraCharacteristics(CameraCharacteristics.LENS_FACING_BACK.toString())
|
val characteristics = manager.getCameraCharacteristics(lens.toString())
|
||||||
Log.d("Alibi", "Creating size")
|
Log.d("Alibi", "Creating size")
|
||||||
val size = characteristics.get(
|
val size = characteristics.get(
|
||||||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
||||||
@ -148,7 +153,6 @@ class CameraHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getPreviewSize(): Size {
|
fun getPreviewSize(): Size {
|
||||||
val characteristics = manager.getCameraCharacteristics(CameraCharacteristics.LENS_FACING_BACK.toString())
|
|
||||||
return characteristics
|
return characteristics
|
||||||
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
||||||
.getOutputSizes(ImageFormat.JPEG)
|
.getOutputSizes(ImageFormat.JPEG)
|
||||||
|
@ -1,66 +1,25 @@
|
|||||||
package app.myzel394.alibi.ui.screens
|
package app.myzel394.alibi.ui.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.ImageFormat
|
|
||||||
import android.hardware.camera2.CameraCaptureSession
|
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraDevice
|
import android.util.Size
|
||||||
import android.hardware.camera2.CameraManager
|
|
||||||
import android.hardware.camera2.params.SessionConfiguration
|
|
||||||
import android.media.Image
|
|
||||||
import android.media.ImageReader
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
import androidx.camera.core.ImageCapture
|
|
||||||
import androidx.camera.core.ImageCaptureException
|
|
||||||
import androidx.camera.core.ImageProxy
|
|
||||||
import androidx.camera.view.CameraController.VIDEO_CAPTURE
|
|
||||||
import androidx.camera.view.LifecycleCameraController
|
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import app.myzel394.alibi.CameraHandler
|
import app.myzel394.alibi.CameraHandler
|
||||||
import app.myzel394.alibi.R
|
|
||||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.AudioRecordingStatus
|
|
||||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.StartRecording
|
|
||||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.VideoRecordingStatus
|
|
||||||
import app.myzel394.alibi.ui.enums.Screen
|
|
||||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||||
import app.myzel394.alibi.ui.models.VideoRecorderModel
|
import app.myzel394.alibi.ui.models.VideoRecorderModel
|
||||||
import com.ujizin.camposer.CameraPreview
|
import app.myzel394.alibi.ui.utils.getOptimalPreviewSize
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@SuppressLint("MissingPermission", "NewApi")
|
@SuppressLint("MissingPermission", "NewApi")
|
||||||
@ -72,16 +31,55 @@ fun RecorderScreen(
|
|||||||
videoRecorder: VideoRecorderModel,
|
videoRecorder: VideoRecorderModel,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val lifecycle = LocalLifecycleOwner.current
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var camera by remember { mutableStateOf<CameraHandler?>(null) }
|
var camera by remember { mutableStateOf<CameraHandler?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
camera = CameraHandler.openCamera(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera == null) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {context ->
|
modifier = Modifier.fillMaxSize(),
|
||||||
val surfaceView = SurfaceView(context).apply {
|
factory = { context ->
|
||||||
|
val surface = object : SurfaceView(context) {
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
val width = resolveSize(suggestedMinimumWidth, widthMeasureSpec);
|
||||||
|
val height = resolveSize(suggestedMinimumHeight, heightMeasureSpec);
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
|
||||||
|
val supportedPreviewSizes = camera!!
|
||||||
|
.characteristics
|
||||||
|
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
||||||
|
.getOutputSizes(SurfaceHolder::class.java)
|
||||||
|
|
||||||
|
if (supportedPreviewSizes != null) {
|
||||||
|
val previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height);
|
||||||
|
|
||||||
|
val ratio = if (previewSize.height >= previewSize.width)
|
||||||
|
(previewSize.height / previewSize.width).toFloat()
|
||||||
|
else (previewSize.width / previewSize.height).toFloat()
|
||||||
|
|
||||||
|
val optimalWidth = width
|
||||||
|
val optimalHeight = (width * ratio).toInt()
|
||||||
|
|
||||||
|
// Make sure the camera preview uses the whole screen
|
||||||
|
|
||||||
|
val metrics = context.resources.displayMetrics
|
||||||
|
layoutParams.width = metrics.widthPixels
|
||||||
|
layoutParams.height = metrics.heightPixels
|
||||||
|
|
||||||
|
setMeasuredDimension(optimalWidth, optimalHeight)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val surfaceView = surface.apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
@ -91,15 +89,6 @@ fun RecorderScreen(
|
|||||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
camera = CameraHandler.openCamera(context)
|
|
||||||
|
|
||||||
val previewSize = camera!!.getPreviewSize()
|
|
||||||
|
|
||||||
holder.setFixedSize(
|
|
||||||
1080,
|
|
||||||
1920,
|
|
||||||
)
|
|
||||||
|
|
||||||
camera!!.startPreview(holder.surface)
|
camera!!.startPreview(holder.surface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,10 +115,6 @@ fun RecorderScreen(
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (camera == null) {
|
|
||||||
camera = CameraHandler.openCamera(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
camera!!.takePhoto(
|
camera!!.takePhoto(
|
||||||
File(
|
File(
|
||||||
context.externalCacheDir!!.absolutePath,
|
context.externalCacheDir!!.absolutePath,
|
||||||
@ -140,6 +125,7 @@ fun RecorderScreen(
|
|||||||
}) {
|
}) {
|
||||||
Text("Take photo")
|
Text("Take photo")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
57
app/src/main/java/app/myzel394/alibi/ui/utils/camera.kt
Normal file
57
app/src/main/java/app/myzel394/alibi/ui/utils/camera.kt
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package app.myzel394.alibi.ui.utils
|
||||||
|
|
||||||
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
|
import android.util.Size
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes rotation required to transform the camera sensor output orientation to the
|
||||||
|
* device's current orientation in degrees.
|
||||||
|
*
|
||||||
|
* @param characteristics The CameraCharacteristics to query for the sensor orientation.
|
||||||
|
* @param surfaceRotationDegrees The current device orientation as a Surface constant.
|
||||||
|
* @return Relative rotation of the camera sensor output.
|
||||||
|
*/
|
||||||
|
public fun computeRelativeRotation(
|
||||||
|
characteristics: CameraCharacteristics,
|
||||||
|
surfaceRotationDegrees: Int
|
||||||
|
): Int {
|
||||||
|
val sensorOrientationDegrees =
|
||||||
|
characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
|
||||||
|
|
||||||
|
// Reverse device orientation for back-facing cameras.
|
||||||
|
val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
|
||||||
|
CameraCharacteristics.LENS_FACING_FRONT
|
||||||
|
) 1 else -1
|
||||||
|
|
||||||
|
// Calculate desired orientation relative to camera orientation to make
|
||||||
|
// the image upright relative to the device orientation.
|
||||||
|
return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOptimalPreviewSize(sizes: Array<Size>, w: Int, h: Int): Size {
|
||||||
|
val ASPECT_TOLERANCE = 0.1
|
||||||
|
val targetRatio = h.toDouble() / w
|
||||||
|
var optimalSize: Size? = null
|
||||||
|
var minDiff = Double.MAX_VALUE
|
||||||
|
|
||||||
|
for (size in sizes) {
|
||||||
|
val ratio = size.width / size.height
|
||||||
|
if (abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue
|
||||||
|
if (abs(size.height - h) < minDiff) {
|
||||||
|
optimalSize = size
|
||||||
|
minDiff = abs(size.height - h).toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optimalSize == null) {
|
||||||
|
minDiff = Double.MAX_VALUE
|
||||||
|
for (size in sizes) {
|
||||||
|
if (abs(size.height - h) < minDiff) {
|
||||||
|
optimalSize = size
|
||||||
|
minDiff = abs(size.height - h).toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optimalSize!!
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user