Browse Source

Measure direct preview frame age using camera timestamp clocks

master
evgeny 5 days ago
parent
commit
78054b2c1d
  1. 11
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt
  2. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt
  3. 6
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt

11
tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt

@ -119,7 +119,7 @@ class StreamCaptureEngine(
if (previous != null && config.copy(videoBitrate = previous.videoBitrate) == previous) {
try {
encoder!!.setParameters(Bundle().apply { putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, config.bitrate) })
lastGood = config; ready(config, false)
lastGood = config; activeConfig = config; ready(config, false)
LogManager.addLog("INFO", "video", "bitrate changed id=$id revision=$generation ${previous.bitrate}->${config.bitrate}")
return@post
} catch (e: Exception) {
@ -256,7 +256,8 @@ class StreamCaptureEngine(
}
try {
if (output != null) {
val map = manager.getCameraCharacteristics(mode.camera).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val characteristics = manager.getCameraCharacteristics(mode.camera)
val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
?: error("Missing preview stream map")
val size = if (mode.highSpeed) android.util.Size(mode.width, mode.height) else
map.getOutputSizes(SurfaceTexture::class.java).orEmpty().filter {
@ -264,11 +265,11 @@ class StreamCaptureEngine(
&& it.width.toLong() * mode.height == it.height.toLong() * mode.width
}.maxByOrNull { it.width * it.height } ?: error("No compatible camera preview size")
output.texture.setDefaultBufferSize(size.width, size.height)
format = StreamPreviewFormat(size.width, size.height, cameraPose(mode.camera))
format = StreamPreviewFormat(size.width, size.height, cameraPose(mode.camera),
characteristics.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE) == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME)
preview.update(format)
} else if (withPreview) preview.update(preview.state.value.format)
val targets = listOfNotNull(input, output?.surface)
check(targets.isNotEmpty()) { "Missing encoder surface" }
val targets = listOfNotNull(checkNotNull(input) { "Missing encoder surface" }, output?.surface)
val callback = object : CameraCaptureSession.StateCallback() {
override fun onConfigured(configured: CameraCaptureSession) {
if (!valid()) { configured.close(); return }

2
tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPreview.kt

@ -10,7 +10,7 @@ data class StreamPreviewCamera(val sensor: Int, val front: Boolean) {
fun rotation(displayDegrees: Int): Int = (sensor + (if (front) displayDegrees else -displayDegrees) + 360) % 360
}
data class StreamPreviewFormat(val width: Int, val height: Int, val camera: StreamPreviewCamera) {
data class StreamPreviewFormat(val width: Int, val height: Int, val camera: StreamPreviewCamera, val realtimeTimestamp: Boolean = false) {
fun aspect(displayDegrees: Int): Float = if (camera.rotation(displayDegrees) % 180 == 0) width.toFloat() / height else height.toFloat() / width
/** Углы TextureView после поворота; размеры view уже учитывают поворот сенсора. */

6
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt

@ -6,6 +6,7 @@ import android.content.pm.PackageManager
import android.content.Context
import android.graphics.Matrix
import android.graphics.SurfaceTexture
import android.os.SystemClock
import android.view.Surface
import android.view.TextureView
import androidx.activity.compose.rememberLauncherForActivityResult
@ -389,13 +390,16 @@ private class CameraPreviewView(context: Context, private val preview: StreamPre
return false // Camera session удерживает свою ссылку до onClosed/onConfigureFailed.
}
override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {
val timestamp = texture.timestamp
if (timestamp == 0L) return // Очистка TextureView до первого кадра камеры.
frames++
val now = System.nanoTime()
if (frames == 1L || now - reportNs >= 1_000_000_000) {
val fps = (frames - reportFrames) * 1_000_000_000.0 / (now - reportNs).coerceAtLeast(1)
val age = if (format?.realtimeTimestamp == true) (SystemClock.elapsedRealtimeNanos() - timestamp) / 1000 else null
LogManager.addLog(if (frames == 1L) "INFO" else "DEBUG", "video", "direct preview frame " +
"target=${target?.let(System::identityHashCode)} frames=$frames fps=${"%.1f".format(fps)} " +
"ageUs=${(now - texture.timestamp) / 1000} timestampNs=${texture.timestamp} " +
"ageUs=$age timestampNs=$timestamp realtime=${format?.realtimeTimestamp} " +
"rotation=${format?.camera?.rotation(displayDegrees)}")
reportNs = now; reportFrames = frames
}

Loading…
Cancel
Save