From 7a2fc0112c6408a19df0293e8b24cc587782d82e Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 01:58:57 +0100 Subject: [PATCH] Model capture codec callbacks and Camera2 sessions for host fault tests --- .../android/hardware/camera2/HostCamera2.kt | 31 +++++++++++++--- .../kotlin/android/media/HostVideoCodec.kt | 36 +++++++++++++++++-- .../src/main/kotlin/android/os/HostVideoOs.kt | 1 + 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt b/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt index a74dd65e..8bdc59b3 100644 --- a/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt +++ b/tools/chatgui-android/video-tests/src/main/kotlin/android/hardware/camera2/HostCamera2.kt @@ -14,7 +14,7 @@ class CameraAccessException(val reason: Int) : Exception("camera access failure class HostStreamMap { fun getOutputSizes(format: Int) = arrayOf(Size(640, 480)) - fun getOutputSizes(type: Class) = arrayOf(Size(640, 480)) + fun getOutputSizes(type: Class<*>) = arrayOf(Size(640, 480)) fun getOutputMinFrameDuration(format: Int, size: Size) = 33_333_333L } class CameraCharacteristics { @@ -41,6 +41,14 @@ class CaptureRequest { fun build() = CaptureRequest() } } +object CaptureResult { + val CONTROL_AE_TARGET_FPS_RANGE = CameraCharacteristics.Key>("ae range") + val SENSOR_FRAME_DURATION = CameraCharacteristics.Key("frame duration") + val SENSOR_EXPOSURE_TIME = CameraCharacteristics.Key("exposure") + val SENSOR_TIMESTAMP = CameraCharacteristics.Key("timestamp") +} +class TotalCaptureResult { fun get(key: CameraCharacteristics.Key): T? = null } +class CaptureFailure { val frameNumber = 0L; val reason = 0; fun wasImageCaptured() = false } /** Реальные callback-поколения SDK воспроизводит тест; камера/сессия закрываются независимо. */ class CameraManager { @@ -86,9 +94,14 @@ class CameraDevice(val id: String, private val owner: CameraManager, private val var closeCalls = 0 private set fun createCaptureRequest(type: Int): CaptureRequest.Builder { check("request" !in failures); return CaptureRequest.Builder() } - fun createCaptureSession(targets: List, callback: CameraCaptureSession.StateCallback, delivery: Handler) { + fun createCaptureSession(targets: List, callback: CameraCaptureSession.StateCallback, delivery: Handler) = + createSession(targets, callback, delivery, highSpeed = false) + fun createConstrainedHighSpeedCaptureSession(targets: List, callback: CameraCaptureSession.StateCallback, delivery: Handler) = + createSession(targets, callback, delivery, highSpeed = true) + private fun createSession(targets: List, callback: CameraCaptureSession.StateCallback, delivery: Handler, highSpeed: Boolean) { check("session" !in failures) { "session creation failed" } - val session = CameraCaptureSession(this, callback, delivery).also { it.failures = owner.sessionFailures.poll() ?: owner.nextSessionFailures } + val session = (if (highSpeed) CameraConstrainedHighSpeedCaptureSession(this, callback, delivery) + else CameraCaptureSession(this, callback, delivery)).also { it.failures = owner.sessionFailures.poll() ?: owner.nextSessionFailures } owner.sessions.add(session) if (owner.autoConfigured) delivery.post { if ("configure" in session.failures || "session after" in failures) session.failConfiguration() @@ -106,7 +119,11 @@ class CameraDevice(val id: String, private val owner: CameraManager, private val check("close after" !in failures) { "camera close failed after effect" } } } -class CameraCaptureSession(val device: CameraDevice, val callback: StateCallback, private val handler: Handler) { +open class CameraCaptureSession(val device: CameraDevice, val callback: StateCallback, private val handler: Handler) { + open class CaptureCallback { + open fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {} + open fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failed: CaptureFailure) {} + } abstract class StateCallback { abstract fun onConfigured(session: CameraCaptureSession) abstract fun onConfigureFailed(session: CameraCaptureSession) @@ -127,3 +144,9 @@ class CameraCaptureSession(val device: CameraDevice, val callback: StateCallback closed = true; repeating = false; handler.post { callback.onClosed(this) } } } +class CameraConstrainedHighSpeedCaptureSession(device: CameraDevice, callback: CameraCaptureSession.StateCallback, handler: Handler) : + CameraCaptureSession(device, callback, handler) { + fun createHighSpeedRequestList(request: CaptureRequest) = listOf(request) + fun setRepeatingBurst(requests: List, callback: CaptureCallback, handler: Handler) = + setRepeatingRequest(requests.first(), callback, handler) +} diff --git a/tools/chatgui-android/video-tests/src/main/kotlin/android/media/HostVideoCodec.kt b/tools/chatgui-android/video-tests/src/main/kotlin/android/media/HostVideoCodec.kt index 0409e3d1..e7081ff3 100644 --- a/tools/chatgui-android/video-tests/src/main/kotlin/android/media/HostVideoCodec.kt +++ b/tools/chatgui-android/video-tests/src/main/kotlin/android/media/HostVideoCodec.kt @@ -10,18 +10,21 @@ class MediaFormat { companion object { const val KEY_ROTATION = "rotation"; const val KEY_MAX_INPUT_SIZE = "max-input-size" const val KEY_LOW_LATENCY = "low-latency" + const val KEY_COLOR_FORMAT = "color"; const val KEY_PROFILE = "profile"; const val KEY_BIT_RATE = "bitrate" + const val KEY_FRAME_RATE = "fps"; const val KEY_I_FRAME_INTERVAL = "interval"; const val KEY_MAX_B_FRAMES = "bframes" fun createVideoFormat(mime: String, width: Int, height: Int) = MediaFormat() } val values = mutableMapOf() fun setInteger(key: String, value: Int) { values[key] = value } fun setByteBuffer(key: String, value: ByteBuffer) { values[key] = value } + fun getByteBuffer(key: String) = values[key] as? ByteBuffer } class MediaCodecInfo(val name: String = "host.decoder") { val isEncoder = false val supportedTypes = arrayOf("video/avc", "video/hevc") fun getCapabilitiesForType(type: String) = CodecCapabilities() class CodecCapabilities { - companion object { const val FEATURE_LowLatency = "low-latency" } + companion object { const val FEATURE_LowLatency = "low-latency"; const val COLOR_FormatSurface = 0x7f000789 } fun isFormatSupported(format: MediaFormat) = true fun isFeatureSupported(name: String) = false } @@ -38,6 +41,8 @@ class MediaCodecList(kind: Int) { class MediaCodec private constructor(val name: String) { companion object { const val BUFFER_FLAG_END_OF_STREAM = 4; const val INFO_OUTPUT_FORMAT_CHANGED = -2 + const val BUFFER_FLAG_CODEC_CONFIG = 2; const val BUFFER_FLAG_PARTIAL_FRAME = 8; const val BUFFER_FLAG_KEY_FRAME = 1 + const val CONFIGURE_FLAG_ENCODE = 1; const val PARAMETER_KEY_VIDEO_BITRATE = "bitrate"; const val PARAMETER_KEY_REQUEST_SYNC_FRAME = "sync" val created = CopyOnWriteArrayList() @Volatile var nextFailures = emptySet() @Volatile var onInput: () -> Unit = {} @@ -46,7 +51,18 @@ class MediaCodec private constructor(val name: String) { return MediaCodec(name).also { created.add(it) } } } - class BufferInfo { var size = 0; var flags = 0; var presentationTimeUs = 0L } + class BufferInfo { var size = 0; var flags = 0; var presentationTimeUs = 0L; var offset = 0 } + class CodecException(val diagnosticInfo: String) : Exception(diagnosticInfo) + abstract class Callback { + abstract fun onInputBufferAvailable(codec: MediaCodec, index: Int) + abstract fun onError(codec: MediaCodec, error: CodecException) + abstract fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) + abstract fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: BufferInfo) + } + var callback: Callback? = null + private set + private var callbackHandler: android.os.Handler? = null + private val encoded = mutableMapOf() private data class Frame(val pts: Long, val size: Int, val flags: Int) private val queued = ConcurrentLinkedQueue() private val pending = mutableMapOf() @@ -63,6 +79,8 @@ class MediaCodec private constructor(val name: String) { private set var configuredSurface: Surface? = null private set + var inputSurface: Surface? = null + private set var renderedPts = ConcurrentLinkedQueue() val outputFormat = MediaFormat() private fun access(operation: String) { @@ -73,6 +91,19 @@ class MediaCodec private constructor(val name: String) { access("configure"); configuredSurface = surface; access("configure after") } fun start() { access("start"); started = true; access("start after") } + fun setCallback(value: Callback, handler: android.os.Handler) { + access("callback"); callback = value; callbackHandler = handler; access("callback after") + } + fun createInputSurface(): Surface { access("input surface"); return Surface().also { inputSurface = it } } + fun setParameters(bundle: android.os.Bundle) { access("parameters") } + fun emitFormat(format: MediaFormat) { callbackHandler!!.post { callback!!.onOutputFormatChanged(this, format) } } + fun emitFrame(data: ByteArray, pts: Long = System.nanoTime() / 1000, flags: Int = BUFFER_FLAG_KEY_FRAME) { + callbackHandler!!.post { + val index = ++index; encoded[index] = data + callback!!.onOutputBufferAvailable(this, index, BufferInfo().also { it.presentationTimeUs = pts; it.size = data.size; it.flags = flags }) + } + } + fun getOutputBuffer(index: Int): ByteBuffer = ByteBuffer.wrap(checkNotNull(encoded[index])) fun stop() { stopCalls++; access("stop"); started = false } fun release() { releaseCalls++; check("release" !in failures) { "decoder release failed" } @@ -94,6 +125,7 @@ class MediaCodec private constructor(val name: String) { } fun releaseOutputBuffer(index: Int, render: Boolean) { access("render") + if (encoded.remove(index) != null) return val frame = checkNotNull(pending.remove(index)) if (render) renderedPts.add(frame.pts) } diff --git a/tools/chatgui-android/video-tests/src/main/kotlin/android/os/HostVideoOs.kt b/tools/chatgui-android/video-tests/src/main/kotlin/android/os/HostVideoOs.kt index 5e9a4da1..18c01b3f 100644 --- a/tools/chatgui-android/video-tests/src/main/kotlin/android/os/HostVideoOs.kt +++ b/tools/chatgui-android/video-tests/src/main/kotlin/android/os/HostVideoOs.kt @@ -64,3 +64,4 @@ object SystemClock { } object Build { object VERSION { const val SDK_INT = 36 } } object Process { const val THREAD_PRIORITY_AUDIO = -16; fun setThreadPriority(value: Int) {} } +class Bundle { val values = mutableMapOf(); fun putInt(key: String, value: Int) { values[key] = value } }