Provide video engine capability, encoder and native port test boundaries
This commit is contained in:
1 parent
3a4e1c6a7c
commit
63f393c615
3 files changed
+77
-2
No files matched your search
@@ -0,0 +1,10 @@
|
||||
package com.utun.chat
|
||||
|
||||
import android.content.Context
|
||||
import com.utun.chat.data.VideoHostConfig
|
||||
|
||||
object ChatApplication { val instance = VideoHostApplication() }
|
||||
class VideoHostApplication {
|
||||
val configProvider = VideoHostConfig()
|
||||
}
|
||||
object R { object string { const val call_video_unavailable = 1; const val call_video_mode_restored = 2 } }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
import android.hardware.camera2.CameraManager
|
||||
|
||||
/** Границы сохранённых настроек и списка объективов; реальные Camera2 callbacks остаются у engine. */
|
||||
class VideoHostConfig {
|
||||
var failures = emptySet<String>()
|
||||
fun getCallVideoCaptureSettings(): CallVideoSettings { check("settings load" !in failures); return CallVideoSettings() }
|
||||
fun getCallVideoBitrateRange(): CallVideoBitrateRange { check("range load" !in failures); return CallVideoBitrateRange() }
|
||||
fun saveCallVideoCaptureSettings(settings: CallVideoSettings) { check("settings save" !in failures) }
|
||||
fun saveCallVideoBitrateRange(range: CallVideoBitrateRange) { check("range save" !in failures) }
|
||||
}
|
||||
class VideoHostLens(val cameraId: String = "0", val label: String = "host", val mm: String = "26mm", val eqFocal: Float = 26f)
|
||||
object CameraLensManager { fun enumerate(manager: CameraManager) = listOf(VideoHostLens()) }
|
||||
@@ -2,6 +2,8 @@ package com.utun.chat.data
|
||||
|
||||
import android.media.MediaCodec
|
||||
import android.media.MediaFormat
|
||||
import android.media.Image
|
||||
import android.util.Size
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
@@ -18,11 +20,42 @@ object LogManager {
|
||||
}
|
||||
|
||||
/** Граница аппаратной пробы; сам RX и разбор uAV2 KEY компилируются из приложения. */
|
||||
internal class CallVideoCodec { companion object {
|
||||
internal class CallVideoCodec(val format: CallVideoFormat, var bitrate: Int, val name: String) : AutoCloseable {
|
||||
data class Packet(val pts: Long, val key: Boolean, val bytes: ByteArray)
|
||||
var closed = false
|
||||
private set
|
||||
var failures = nextFailures
|
||||
val pendingFrames get() = packets.size
|
||||
val inputWaitUs = 0L; val copyUs = 0L; val queueUs = 0L
|
||||
private val packets = ConcurrentLinkedQueue<Packet>()
|
||||
init { check("create" !in failures); created.add(this) }
|
||||
fun prepare(keepRunning: () -> Boolean = { true }) { check(keepRunning() && "prepare" !in failures) }
|
||||
fun submit(image: Image, pts: Long, key: Boolean, turn: Int): Boolean {
|
||||
check("submit" !in failures); packets.add(Packet(pts, key, byteArrayOf(1))); return true
|
||||
}
|
||||
fun drain(): List<Packet> { check("drain" !in failures); return buildList { while (true) add(packets.poll() ?: break) } }
|
||||
fun finish() = drain()
|
||||
fun setBitrate(value: Int): Boolean { if ("bitrate" in failures) return false; bitrate = value; return true }
|
||||
override fun close() { check("close" !in failures); closed = true; check("close after" !in failures) }
|
||||
companion object {
|
||||
val created = CopyOnWriteArrayList<CallVideoCodec>()
|
||||
@Volatile var nextFailures = emptySet<String>()
|
||||
@Volatile var onProbe: () -> Unit = {}
|
||||
data class EncoderMode(val size: Size = Size(640, 480), val name: String = "host.encoder", val profile: Int = 1,
|
||||
val level: Int = 31, val fps: Int = 30)
|
||||
data class Encoder(val modes: List<EncoderMode> = listOf(EncoderMode())) {
|
||||
val sizes get() = modes.map { it.size }
|
||||
val fps get() = 30; val pixelsPerSecond get() = 640 * 480 * 30; val profile get() = 1; val level get() = 31
|
||||
}
|
||||
data class DecoderMode(val name: String = "host.decoder", val pipeline: Int = 0) {
|
||||
fun supports(format: CallVideoFormat) = format.codec == 1
|
||||
}
|
||||
data class Decoder(val modes: List<DecoderMode> = listOf(DecoderMode()))
|
||||
data class Decoder(val modes: List<DecoderMode> = listOf(DecoderMode())) {
|
||||
val size get() = Size(640, 480); val fps get() = 30; val pixelsPerSecond get() = 640 * 480 * 30
|
||||
val profiles get() = 1; val level get() = 31
|
||||
}
|
||||
fun probe(keepRunning: () -> Boolean): Map<Int, Encoder> { onProbe(); return if (keepRunning()) mapOf(1 to Encoder()) else emptyMap() }
|
||||
fun probeDecoders(keepRunning: () -> Boolean): Map<Int, Decoder> = if (keepRunning()) mapOf(1 to Decoder()) else emptyMap()
|
||||
fun validateKey(bytes: ByteArray, format: CallVideoFormat) { StreamVideoCodec.fromId(format.codec).unpackKey(bytes) }
|
||||
fun decoderFormat(format: CallVideoFormat, decoder: MediaCodec, config: ByteArray): MediaFormat =
|
||||
StreamVideoCodec.fromId(format.codec).decoderFormat(format.width, format.height, format.rotation, config)
|
||||
@@ -30,6 +63,7 @@ internal class CallVideoCodec { companion object {
|
||||
|
||||
object NativeLib {
|
||||
fun callAudioClock(id: Long, now: Long, out: LongArray): Boolean { out[5] = 1; return true }
|
||||
fun setChatSetting(name: String, value: String) {}
|
||||
}
|
||||
|
||||
/** Native playout/encoded FIFO: только управляемые границы, без загрузки JNI/декодирования/сетевого ядра. */
|
||||
@@ -46,9 +80,15 @@ internal object CallVideoNative {
|
||||
@Volatile var onPull: () -> Unit = {}
|
||||
val destroys = CopyOnWriteArrayList<Long>()
|
||||
val keyRequests = AtomicLong()
|
||||
val ports = ConcurrentHashMap.newKeySet<Long>()
|
||||
val portOperations = CopyOnWriteArrayList<String>()
|
||||
@Volatile var configured = 0L
|
||||
@Volatile var onOpen: () -> Unit = {}
|
||||
@Volatile var onClose: () -> Unit = {}
|
||||
fun reset() {
|
||||
frames.clear(); live.clear(); rejected.clear(); accepted.clear(); presented.clear(); destroys.clear()
|
||||
failures = emptySet(); onCreate = {}; onPull = {}; keyRequests.set(0)
|
||||
ports.clear(); portOperations.clear(); configured = 0; onOpen = {}; onClose = {}
|
||||
}
|
||||
private fun access(operation: String) { check(operation !in failures) { "native $operation failed" } }
|
||||
fun playoutCreate(id: Long): Long {
|
||||
@@ -73,4 +113,15 @@ internal object CallVideoNative {
|
||||
fun decoded(id: Long, generation: Long, success: Boolean) {
|
||||
access("decoded"); (if (success) accepted else rejected).add(generation)
|
||||
}
|
||||
fun rotatableCaps(bytes: ByteArray): ByteArray? { access("caps"); return bytes.copyOf() }
|
||||
fun open(id: Long, caps: ByteArray): Boolean { onOpen(); access("open"); ports.add(id); portOperations.add("open:$id"); return true }
|
||||
fun close(id: Long) { onClose(); access("close"); ports.remove(id); portOperations.add("close:$id") }
|
||||
fun range(id: Long, minimum: Int, maximum: Int): Boolean { access("range"); return "range reject" !in failures }
|
||||
fun peer(id: Long): ByteArray? = if (id in ports) ByteArray(64) else null
|
||||
fun status(id: Long): LongArray? = if (id in ports) longArrayOf(1, 0, 0, 1_000_000, configured, 0, 300_000, 4_000_000, 0, 0, 1, 1) else null
|
||||
fun choose(local: ByteArray, peer: ByteArray, desired: LongArray): LongArray? = desired.copyOf()
|
||||
fun supported(local: ByteArray, peer: ByteArray, desired: LongArray): Boolean = true
|
||||
fun format(id: Long, format: LongArray, bitrate: Int): Boolean { access("format"); configured = format[0]; return true }
|
||||
fun applied(id: Long, generation: Long, request: Long, bitrate: Int) { access("applied") }
|
||||
fun send(id: Long, metadata: LongArray, bytes: ByteArray): Int { access("send"); return 1 }
|
||||
}
|
||||
Reference in new issue
Block a user