From c0d5db318f650f1f72053b282f220eddfe5d38e1 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 19 Oct 2026 23:38:53 +0200 Subject: [PATCH] Keep codec mode probes consistent with live encoding --- .../com/utun/chat/data/CallVideoHevcProbe.kt | 20 +++++++++++-- .../java/com/utun/chat/data/CallVideoCodec.kt | 30 ++++++++++--------- tools/chatgui/src/call_video_encoder.cpp | 3 +- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoHevcProbe.kt b/tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoHevcProbe.kt index b7be4bf3..6e0b76ac 100644 --- a/tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoHevcProbe.kt +++ b/tools/chatgui-android/app/src/debug/java/com/utun/chat/data/CallVideoHevcProbe.kt @@ -25,8 +25,8 @@ object CallVideoHevcProbe { } @JvmStatic fun main(args: Array) { - require(args.size in 1..2 && (args.size == 1 || args[1] in listOf("parameters", "call"))) { - "Expected output directory [parameters|call]" + require(args.size in 1..2 && (args.size == 1 || args[1] in listOf("parameters", "call", "receive"))) { + "Expected output directory [parameters|call|receive]" } check(Build.VERSION.SDK_INT >= 29) { "Hardware codec enumeration requires API 29" } val directory = File(args[0]); check(directory.isDirectory || directory.mkdirs()) @@ -39,6 +39,22 @@ object CallVideoHevcProbe { !it.isEncoder && it.isHardwareAccelerated && "video/hevc" in it.supportedTypes } report("start encoder=${encoder.name} decoder=${decoder.name} frames=$FRAMES") + if (args.getOrNull(1) == "receive") { + val decoders = checkNotNull(CallVideoCodec.probeDecoders()[2]) { "No verified HEVC decoder" } + val files = directory.listFiles()!!.filter { it.extension == "key" }.sortedBy { it.name } + check(files.isNotEmpty()) { "No remote KEY files in $directory" } + for (file in files) { + val bytes = file.readBytes(); val inspected = checkNotNull(CallVideoNative.inspect(2, bytes)) + val format = CallVideoFormat(1, inspected[0].toInt(), inspected[1].toInt(), codec = 2, + level = inspected[2].toInt(), profile = inspected[3].toInt()) + val implementation = checkNotNull(decoders.modes.firstOrNull { it.supports(format) }) + CallVideoCodec.verifyDecode(format, List(FRAMES) { CallVideoCodec.Packet(1_000_000L + it * 33333, true, bytes) }, + implementation.name) + report("remote PASSED file=${file.name} format=$format decoder=${implementation.name}") + } + report("remote finished files=${files.size}") + return + } if (args.getOrNull(1) == "call") { val encoders = CallVideoCodec.probe() val decoders = CallVideoCodec.probeDecoders() diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt index 9e9637f5..be8019fc 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoCodec.kt @@ -290,20 +290,22 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name var profiles = 0; var pipeline = 0; var maximumLevel = 0 for (profile in listOf(1, 2)) { try { - val level = if (type.id == 1) { if (size.width > 1280) 40 else 31 } - else if (size.width > 1280 || profile == 2) 120 else 93 - var profilePipeline = 0 - for (portrait in listOf(false, true)) { - val w = if (portrait) size.height else size.width; val h = if (portrait) size.width else size.height - check(caps.videoCapabilities?.areSizeAndRateSupported(w, h, 30.0) == true) { "Decoder geometry unsupported: ${w}x$h" } - val f = CallVideoFormat(1, w, h, codec = type.id, level = level, profile = profile) - val key = checkNotNull(CallVideoNative.probeKey(type.id, profile, w, h)) - validateKey(key, f) - profilePipeline = maxOf(profilePipeline, verifyDecode(f, List(8) { Packet(1_000_000L + it * 33333, true, key) }, - entry.name, true)) - } - profiles = profiles or profile; maximumLevel = maxOf(maximumLevel, level) - pipeline = maxOf(pipeline, profilePipeline) + val level = if (type.id == 1) { if (size.width > 1280) 40 else 31 } + else if (size.width > 1280 || profile == 2) 120 else 93 + var profilePipeline = 0 + for (portrait in listOf(false, true)) { + val w = if (portrait) size.height else size.width; val h = if (portrait) size.width else size.height + check(caps.videoCapabilities?.areSizeAndRateSupported(w, h, 30.0) == true) { + "Decoder geometry unsupported: ${w}x$h" + } + val f = CallVideoFormat(1, w, h, codec = type.id, level = level, profile = profile) + val key = checkNotNull(CallVideoNative.probeKey(type.id, profile, w, h)) + validateKey(key, f) + profilePipeline = maxOf(profilePipeline, + verifyDecode(f, List(8) { Packet(1_000_000L + it * 33333, true, key) }, entry.name, true)) + } + profiles = profiles or profile; maximumLevel = maxOf(maximumLevel, level) + pipeline = maxOf(pipeline, profilePipeline) } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder profile probe rejected name=${entry.name} " + "codec=${type.id} profile=$profile size=$size: ${e.message}") diff --git a/tools/chatgui/src/call_video_encoder.cpp b/tools/chatgui/src/call_video_encoder.cpp index 7d98b9bf..b849c616 100644 --- a/tools/chatgui/src/call_video_encoder.cpp +++ b/tools/chatgui/src/call_video_encoder.cpp @@ -6,7 +6,8 @@ // Профиль и envelope задаются явно: автоматический выбор level при смене bitrate запрещён. bool CallVideoEncoder::open(const call_video_format& format, uint32_t bitrate, const QString& backend) { - if (!call_video_format_valid(&format) || bitrate < CALL_VIDEO_RATE_MIN_BPS || bitrate > format.maximum_bps) { + if (!call_video_format_valid(&format) || bitrate < CALL_VIDEO_RATE_MIN_BPS || bitrate > format.maximum_bps + || (format.codec == CALL_VIDEO_H265 && format.profile != CALL_VIDEO_MAIN)) { m_error = "Invalid call video encoder configuration"; DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video open invalid generation=%llu codec=%u level=%u bitrate=%u cap=%u profile=%u", (unsigned long long)format.generation, format.codec, format.level, bitrate, format.maximum_bps, format.profile); return false;