Model capture codec callbacks and Camera2 sessions for host fault tests

This commit is contained in:
evgeny committed 2026-10-29 13:34:24 +01:00
1 parent 59d1839d8d
commit 7a2fc0112c
3 files changed
+62 -6

No files matched your search

@@ -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<SurfaceTexture>) = 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<Range<Int>>("ae range")
val SENSOR_FRAME_DURATION = CameraCharacteristics.Key<Long>("frame duration")
val SENSOR_EXPOSURE_TIME = CameraCharacteristics.Key<Long>("exposure")
val SENSOR_TIMESTAMP = CameraCharacteristics.Key<Long>("timestamp")
}
class TotalCaptureResult { fun <T> get(key: CameraCharacteristics.Key<T>): 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<Surface>, callback: CameraCaptureSession.StateCallback, delivery: Handler) {
fun createCaptureSession(targets: List<Surface>, callback: CameraCaptureSession.StateCallback, delivery: Handler) =
createSession(targets, callback, delivery, highSpeed = false)
fun createConstrainedHighSpeedCaptureSession(targets: List<Surface>, callback: CameraCaptureSession.StateCallback, delivery: Handler) =
createSession(targets, callback, delivery, highSpeed = true)
private fun createSession(targets: List<Surface>, 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<CaptureRequest>, callback: CaptureCallback, handler: Handler) =
setRepeatingRequest(requests.first(), callback, handler)
}
@@ -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<String, Any>()
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<MediaCodec>()
@Volatile var nextFailures = emptySet<String>()
@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<Int, ByteArray>()
private data class Frame(val pts: Long, val size: Int, val flags: Int)
private val queued = ConcurrentLinkedQueue<Frame>()
private val pending = mutableMapOf<Int, Frame>()
@@ -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<Long>()
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)
}
@@ -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<String, Int>(); fun putInt(key: String, value: Int) { values[key] = value } }