Browse Source

Verify hardware rate changes on continuous noisy video

master
evgeny 4 days ago
parent
commit
607952b709
  1. 61
      tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoProbe.kt

61
tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoProbe.kt

@ -1,6 +1,10 @@
package com.utun.chat.data
import android.app.Instrumentation
import android.graphics.ImageFormat
import android.media.Image
import android.media.ImageReader
import android.media.ImageWriter
import android.os.Bundle
/** Запускается только явно через adb am instrument; камера и реальные звонки не затрагиваются. */
@ -26,9 +30,7 @@ class CallVideoProbe : Instrumentation() {
check(codecs.isNotEmpty() && decoders.isNotEmpty()) { "No verified encoders or decoders" }
for ((codec, entry) in codecs) {
val f = CallVideoFormat(1, 640, 480, codec = codec, level = entry.level, profile = entry.profile)
for (rate in listOf(4_000_000, 300_000, 4_000_000)) {
CallVideoCodec(f, rate, entry.name).use { it.prepare() }
}
verifyRateChanges(f, entry.name)
CallVideoCodec(f, 1_000_000, entry.name).use { encoder ->
encoder.prepare(); var key: ByteArray? = null; var input = 0
val deadline = System.nanoTime() + 3_000_000_000
@ -45,4 +47,57 @@ class CallVideoProbe : Instrumentation() {
finish(1, Bundle().apply { putString("error", e.toString()) })
}
}
/** Шум требует заданную полосу: на однотонных пробах нельзя обнаружить проигнорированный bitrate. */
private fun verifyRateChanges(format: CallVideoFormat, name: String) {
ImageReader.newInstance(format.width, format.height, ImageFormat.YUV_420_888, 2).use { reader ->
ImageWriter.newInstance(reader.surface, 1).use { writer ->
writer.dequeueInputImage().use { image ->
CallVideoCodec(format, 4_000_000, name).use { encoder ->
encoder.prepare(); var random = 1
for ((phase, rate) in listOf(4_000_000, 300_000, 4_000_000).withIndex()) {
check(encoder.setBitrate(rate)) { "In-place bitrate unsupported: $name" }
var input = 0; var output = 0; var bytes = 0L; var prepared = false; var key = false
val base = 10_000_000L + phase * 4_000_000L
val deadline = System.nanoTime() + 15_000_000_000L
while (output < 120 && System.nanoTime() < deadline) {
if (input < 120) {
if (!prepared) { random = fillNoise(image, random); prepared = true }
if (encoder.submit(image, base + input * 1_000_000L / 30, input % 30 == 0)) {
input++; prepared = false
}
}
for (packet in encoder.drain()) {
check(packet.pts == base + output * 1_000_000L / 30) { "Lost/reordered rate-change frame" }
if (output == 0) key = packet.key
if (output >= 60) bytes += packet.bytes.size
output++
}
Thread.sleep(2)
}
val measured = bytes * 8 / 2
val result = "name=$name phase=$phase target=$rate measured=$measured input=$input output=$output key=$key"
android.util.Log.i("CallVideoProbe", "rate check $result")
check(input == 120 && output == 120 && key) { "Incomplete rate check: $result" }
check(measured > rate / 2 && measured < rate.toLong() * 3 / 2) { "Bitrate change ineffective: $result" }
}
}
}
}
}
}
private fun fillNoise(image: Image, seed: Int): Int {
var random = seed
for ((index, plane) in image.planes.withIndex()) {
val width = image.width shr (if (index == 0) 0 else 1)
val height = image.height shr (if (index == 0) 0 else 1)
val buffer = plane.buffer.duplicate(); val start = buffer.position()
for (y in 0 until height) for (x in 0 until width) {
random = random * 1664525 + 1013904223
buffer.put(start + y * plane.rowStride + x * plane.pixelStride, if (index == 0) (random ushr 24).toByte() else 128.toByte())
}
}
return random
}
}

Loading…
Cancel
Save