fix: Fix Preview stretching on Android (#2377)
* fix: Fix Preview stretching on Android * fix: Simplify Preview size computation * fix: Catch `stopRepeating` error * fix: Fix preview size calculation * Format code * Update CameraSession.kt * Enable CodeScanner in example app * fix: Also update size on surface change * Format * fix: Flip sizes * Revert that stuff again * Update PreviewView.kt * fix: Swap width and height in SurfaceHolder::setFixedSize
This commit is contained in:
parent
2b10622559
commit
322b6fcbd6
@ -28,7 +28,6 @@ import com.mrousavy.camera.core.outputs.BarcodeScannerOutput
|
|||||||
import com.mrousavy.camera.core.outputs.PhotoOutput
|
import com.mrousavy.camera.core.outputs.PhotoOutput
|
||||||
import com.mrousavy.camera.core.outputs.SurfaceOutput
|
import com.mrousavy.camera.core.outputs.SurfaceOutput
|
||||||
import com.mrousavy.camera.core.outputs.VideoPipelineOutput
|
import com.mrousavy.camera.core.outputs.VideoPipelineOutput
|
||||||
import com.mrousavy.camera.extensions.bigger
|
|
||||||
import com.mrousavy.camera.extensions.capture
|
import com.mrousavy.camera.extensions.capture
|
||||||
import com.mrousavy.camera.extensions.closestToOrMax
|
import com.mrousavy.camera.extensions.closestToOrMax
|
||||||
import com.mrousavy.camera.extensions.createCaptureSession
|
import com.mrousavy.camera.extensions.createCaptureSession
|
||||||
@ -38,7 +37,6 @@ import com.mrousavy.camera.extensions.getPreviewTargetSize
|
|||||||
import com.mrousavy.camera.extensions.getVideoSizes
|
import com.mrousavy.camera.extensions.getVideoSizes
|
||||||
import com.mrousavy.camera.extensions.openCamera
|
import com.mrousavy.camera.extensions.openCamera
|
||||||
import com.mrousavy.camera.extensions.setZoom
|
import com.mrousavy.camera.extensions.setZoom
|
||||||
import com.mrousavy.camera.extensions.smaller
|
|
||||||
import com.mrousavy.camera.frameprocessor.FrameProcessor
|
import com.mrousavy.camera.frameprocessor.FrameProcessor
|
||||||
import com.mrousavy.camera.types.Flash
|
import com.mrousavy.camera.types.Flash
|
||||||
import com.mrousavy.camera.types.Orientation
|
import com.mrousavy.camera.types.Orientation
|
||||||
@ -48,6 +46,7 @@ import com.mrousavy.camera.types.Torch
|
|||||||
import com.mrousavy.camera.types.VideoStabilizationMode
|
import com.mrousavy.camera.types.VideoStabilizationMode
|
||||||
import com.mrousavy.camera.utils.ImageFormatUtils
|
import com.mrousavy.camera.utils.ImageFormatUtils
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -246,6 +245,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
|
|
||||||
private fun destroyPreviewOutputSync() {
|
private fun destroyPreviewOutputSync() {
|
||||||
Log.i(TAG, "Destroying Preview Output...")
|
Log.i(TAG, "Destroying Preview Output...")
|
||||||
|
// This needs to run synchronously because after this method returns, the Preview Surface is no longer valid,
|
||||||
|
// and trying to use it will crash. This might result in a short UI Thread freeze though.
|
||||||
runBlocking {
|
runBlocking {
|
||||||
configure { config ->
|
configure { config ->
|
||||||
config.preview = CameraConfiguration.Output.Disabled.create()
|
config.preview = CameraConfiguration.Output.Disabled.create()
|
||||||
@ -379,12 +380,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
if (preview != null) {
|
if (preview != null) {
|
||||||
// Compute Preview Size based on chosen video size
|
// Compute Preview Size based on chosen video size
|
||||||
val videoSize = videoOutput?.size ?: format?.videoSize
|
val videoSize = videoOutput?.size ?: format?.videoSize
|
||||||
val size = if (videoSize != null) {
|
val size = characteristics.getPreviewTargetSize(videoSize)
|
||||||
val formatAspectRatio = videoSize.bigger.toDouble() / videoSize.smaller
|
|
||||||
characteristics.getPreviewTargetSize(formatAspectRatio)
|
|
||||||
} else {
|
|
||||||
characteristics.getPreviewTargetSize(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val enableHdr = video?.config?.enableHdr ?: false
|
val enableHdr = video?.config?.enableHdr ?: false
|
||||||
|
|
||||||
@ -396,7 +392,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
enableHdr
|
enableHdr
|
||||||
)
|
)
|
||||||
outputs.add(output)
|
outputs.add(output)
|
||||||
previewView?.size = size
|
// Size is usually landscape, so we flip it here
|
||||||
|
previewView?.size = Size(size.height, size.width)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeScanner Output
|
// CodeScanner Output
|
||||||
@ -520,7 +517,11 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
|
|
||||||
if (!config.isActive) {
|
if (!config.isActive) {
|
||||||
isRunning = false
|
isRunning = false
|
||||||
captureSession?.stopRepeating()
|
try {
|
||||||
|
captureSession?.stopRepeating()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
// ignore - captureSession is already closed.
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (captureSession == null) {
|
if (captureSession == null) {
|
||||||
|
@ -19,8 +19,8 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
|
|||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
UiThreadUtil.runOnUiThread {
|
UiThreadUtil.runOnUiThread {
|
||||||
Log.i(TAG, "Resizing PreviewView to ${value.width} x ${value.height}...")
|
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
|
||||||
holder.setFixedSize(value.width, value.height)
|
holder.setFixedSize(value.height, value.width)
|
||||||
requestLayout()
|
requestLayout()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@ -44,20 +44,10 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
|
|||||||
holder.addCallback(callback)
|
holder.addCallback(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*fun resizeToInputCamera(cameraId: String, cameraManager: CameraManager, format: CameraDeviceFormat?) {
|
|
||||||
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
|
|
||||||
|
|
||||||
val targetPreviewSize = format?.videoSize
|
|
||||||
val formatAspectRatio = if (targetPreviewSize != null) targetPreviewSize.bigger.toDouble() / targetPreviewSize.smaller else null
|
|
||||||
size = characteristics.getPreviewTargetSize(formatAspectRatio)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
|
private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
|
||||||
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
|
val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
|
||||||
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
|
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
|
||||||
|
|
||||||
Log.i(TAG, "Content Size: $contentSize ($contentAspectRatio) | Container Size: $containerSize ($containerAspectRatio)")
|
|
||||||
|
|
||||||
val widthOverHeight = when (resizeMode) {
|
val widthOverHeight = when (resizeMode) {
|
||||||
ResizeMode.COVER -> contentAspectRatio > containerAspectRatio
|
ResizeMode.COVER -> contentAspectRatio > containerAspectRatio
|
||||||
ResizeMode.CONTAIN -> contentAspectRatio < containerAspectRatio
|
ResizeMode.CONTAIN -> contentAspectRatio < containerAspectRatio
|
||||||
|
@ -4,13 +4,12 @@ import android.content.res.Resources
|
|||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
fun getMaximumPreviewSize(): Size {
|
fun getMaximumPreviewSize(): Size {
|
||||||
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
|
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
|
||||||
// According to the Android Developer documentation, PREVIEW streams can have a resolution
|
// According to the Android Developer documentation, PREVIEW streams can have a resolution
|
||||||
// of up to the phone's display's resolution, with a maximum of 1920x1080.
|
// of up to the phone's display's resolution, with a maximum of 1920x1080.
|
||||||
val display1080p = Size(1920, 1080)
|
val display1080p = Size(1080, 1920)
|
||||||
val displaySize = Size(
|
val displaySize = Size(
|
||||||
Resources.getSystem().displayMetrics.widthPixels,
|
Resources.getSystem().displayMetrics.widthPixels,
|
||||||
Resources.getSystem().displayMetrics.heightPixels
|
Resources.getSystem().displayMetrics.heightPixels
|
||||||
@ -20,28 +19,11 @@ fun getMaximumPreviewSize(): Size {
|
|||||||
return if (isHighResScreen) display1080p else displaySize
|
return if (isHighResScreen) display1080p else displaySize
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CameraCharacteristics.getPreviewSizeFromAspectRatio(aspectRatio: Double): Size {
|
fun CameraCharacteristics.getPreviewTargetSize(targetSize: Size?): Size {
|
||||||
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
||||||
val maximumPreviewSize = getMaximumPreviewSize()
|
val maximumPreviewSize = getMaximumPreviewSize()
|
||||||
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
|
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
|
||||||
.sortedByDescending { it.width * it.height }
|
.filter { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
|
||||||
.sortedBy { abs(aspectRatio - (it.bigger.toDouble() / it.smaller)) }
|
|
||||||
|
|
||||||
return outputSizes.first { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
|
return outputSizes.closestToOrMax(targetSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CameraCharacteristics.getAutomaticPreviewSize(): Size {
|
|
||||||
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
|
|
||||||
val maximumPreviewSize = getMaximumPreviewSize()
|
|
||||||
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
|
|
||||||
.sortedByDescending { it.width * it.height }
|
|
||||||
|
|
||||||
return outputSizes.first { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun CameraCharacteristics.getPreviewTargetSize(aspectRatio: Double?): Size =
|
|
||||||
if (aspectRatio != null) {
|
|
||||||
getPreviewSizeFromAspectRatio(aspectRatio)
|
|
||||||
} else {
|
|
||||||
getAutomaticPreviewSize()
|
|
||||||
}
|
|
||||||
|
@ -9,7 +9,7 @@ import kotlin.math.min
|
|||||||
|
|
||||||
fun List<Size>.closestToOrMax(size: Size?): Size =
|
fun List<Size>.closestToOrMax(size: Size?): Size =
|
||||||
if (size != null) {
|
if (size != null) {
|
||||||
this.minBy { abs(it.width - size.width) + abs(it.height - size.height) }
|
this.minBy { abs((it.width * it.height) - (size.width * size.height)) }
|
||||||
} else {
|
} else {
|
||||||
this.maxBy { it.width * it.height }
|
this.maxBy { it.width * it.height }
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import com.facebook.proguard.annotations.DoNotStrip;
|
|||||||
/**
|
/**
|
||||||
* Represents a JS Frame Processor
|
* Represents a JS Frame Processor
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("JavaJniMissingFunction") // we're using fbjni.
|
|
||||||
public final class FrameProcessor {
|
public final class FrameProcessor {
|
||||||
/**
|
/**
|
||||||
* Call the JS Frame Processor function with the given Frame
|
* Call the JS Frame Processor function with the given Frame
|
||||||
|
@ -42,4 +42,4 @@ hermesEnabled=true
|
|||||||
# Can be set to true to disable the build setup
|
# Can be set to true to disable the build setup
|
||||||
#VisionCamera_disableFrameProcessors=true
|
#VisionCamera_disableFrameProcessors=true
|
||||||
# Can be set to true to include the full 2.4 MB MLKit dependency
|
# Can be set to true to include the full 2.4 MB MLKit dependency
|
||||||
#VisionCamera_enableCodeScanner=true
|
VisionCamera_enableCodeScanner=true
|
||||||
|
Loading…
Reference in New Issue
Block a user