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.SurfaceOutput
|
||||
import com.mrousavy.camera.core.outputs.VideoPipelineOutput
|
||||
import com.mrousavy.camera.extensions.bigger
|
||||
import com.mrousavy.camera.extensions.capture
|
||||
import com.mrousavy.camera.extensions.closestToOrMax
|
||||
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.openCamera
|
||||
import com.mrousavy.camera.extensions.setZoom
|
||||
import com.mrousavy.camera.extensions.smaller
|
||||
import com.mrousavy.camera.frameprocessor.FrameProcessor
|
||||
import com.mrousavy.camera.types.Flash
|
||||
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.utils.ImageFormatUtils
|
||||
import java.io.Closeable
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.concurrent.CancellationException
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -246,6 +245,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
||||
|
||||
private fun destroyPreviewOutputSync() {
|
||||
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 {
|
||||
configure { config ->
|
||||
config.preview = CameraConfiguration.Output.Disabled.create()
|
||||
@ -379,12 +380,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
||||
if (preview != null) {
|
||||
// Compute Preview Size based on chosen video size
|
||||
val videoSize = videoOutput?.size ?: format?.videoSize
|
||||
val size = if (videoSize != null) {
|
||||
val formatAspectRatio = videoSize.bigger.toDouble() / videoSize.smaller
|
||||
characteristics.getPreviewTargetSize(formatAspectRatio)
|
||||
} else {
|
||||
characteristics.getPreviewTargetSize(null)
|
||||
}
|
||||
val size = characteristics.getPreviewTargetSize(videoSize)
|
||||
|
||||
val enableHdr = video?.config?.enableHdr ?: false
|
||||
|
||||
@ -396,7 +392,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
||||
enableHdr
|
||||
)
|
||||
outputs.add(output)
|
||||
previewView?.size = size
|
||||
// Size is usually landscape, so we flip it here
|
||||
previewView?.size = Size(size.height, size.width)
|
||||
}
|
||||
|
||||
// CodeScanner Output
|
||||
@ -520,7 +517,11 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
||||
|
||||
if (!config.isActive) {
|
||||
isRunning = false
|
||||
try {
|
||||
captureSession?.stopRepeating()
|
||||
} catch (e: IllegalStateException) {
|
||||
// ignore - captureSession is already closed.
|
||||
}
|
||||
return
|
||||
}
|
||||
if (captureSession == null) {
|
||||
|
@ -19,8 +19,8 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
|
||||
set(value) {
|
||||
field = value
|
||||
UiThreadUtil.runOnUiThread {
|
||||
Log.i(TAG, "Resizing PreviewView to ${value.width} x ${value.height}...")
|
||||
holder.setFixedSize(value.width, value.height)
|
||||
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
|
||||
holder.setFixedSize(value.height, value.width)
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
@ -44,20 +44,10 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
|
||||
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 {
|
||||
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
|
||||
val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
|
||||
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
|
||||
|
||||
Log.i(TAG, "Content Size: $contentSize ($contentAspectRatio) | Container Size: $containerSize ($containerAspectRatio)")
|
||||
|
||||
val widthOverHeight = when (resizeMode) {
|
||||
ResizeMode.COVER -> contentAspectRatio > containerAspectRatio
|
||||
ResizeMode.CONTAIN -> contentAspectRatio < containerAspectRatio
|
||||
|
@ -4,13 +4,12 @@ import android.content.res.Resources
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.util.Size
|
||||
import android.view.SurfaceHolder
|
||||
import kotlin.math.abs
|
||||
|
||||
fun getMaximumPreviewSize(): Size {
|
||||
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
|
||||
// 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.
|
||||
val display1080p = Size(1920, 1080)
|
||||
val display1080p = Size(1080, 1920)
|
||||
val displaySize = Size(
|
||||
Resources.getSystem().displayMetrics.widthPixels,
|
||||
Resources.getSystem().displayMetrics.heightPixels
|
||||
@ -20,28 +19,11 @@ fun getMaximumPreviewSize(): Size {
|
||||
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 maximumPreviewSize = getMaximumPreviewSize()
|
||||
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
|
||||
.sortedByDescending { it.width * it.height }
|
||||
.sortedBy { abs(aspectRatio - (it.bigger.toDouble() / it.smaller)) }
|
||||
.filter { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.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 =
|
||||
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 {
|
||||
this.maxBy { it.width * it.height }
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import com.facebook.proguard.annotations.DoNotStrip;
|
||||
/**
|
||||
* Represents a JS Frame Processor
|
||||
*/
|
||||
@SuppressWarnings("JavaJniMissingFunction") // we're using fbjni.
|
||||
public final class FrameProcessor {
|
||||
/**
|
||||
* 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
|
||||
#VisionCamera_disableFrameProcessors=true
|
||||
# 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