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 c4ca3b11..fba5abc9 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 @@ -43,7 +43,7 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name else MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedBaseline } else MediaCodecInfo.CodecProfileLevel.HEVCProfileMain) - setInteger(MediaFormat.KEY_LEVEL, platformLevel(format.codec, format.level)) + setInteger(MediaFormat.KEY_LEVEL, platformLevel(format.codec, format.level, format.profile)) setInteger(MediaFormat.KEY_BIT_RATE, bitrate); setInteger(MediaFormat.KEY_FRAME_RATE, format.fps) setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) @@ -192,11 +192,14 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name } companion object { - fun platformLevel(codec: Int, level: Int): Int = if (codec == 1) when (level) { + fun platformLevel(codec: Int, level: Int, profile: Int = 1): Int = if (codec == 1) when (level) { 30 -> MediaCodecInfo.CodecProfileLevel.AVCLevel3 31 -> MediaCodecInfo.CodecProfileLevel.AVCLevel31 40 -> MediaCodecInfo.CodecProfileLevel.AVCLevel4 else -> error("Unsupported AVC level $level") + } else if (profile == 2) { + check(level == 120) { "Unsupported HEVC High tier level $level" } + MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel4 } else when (level) { 90 -> MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel3 93 -> MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31 @@ -252,11 +255,20 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name val f = CallVideoFormat(1, w, h, codec = type.id, level = if (type.id == 1) { if (fullhd) 40 else 31 } else { if (fullhd) 120 else 93 }, profile = if (type.id == 1) 2 else 1) - CallVideoCodec(f, 1_000_000, entry.name).use { encoder -> - encoder.prepare() - check(foundProfile == 0 || foundProfile == encoder.format.profile) { "Geometry changes profile" } - foundProfile = encoder.format.profile; foundLevel = maxOf(foundLevel, encoder.format.level) + var verified: CallVideoFormat? = null + for (level in (if (type.id == 2) listOf(f.level, 120) else listOf(f.level)).distinct()) { + try { + CallVideoCodec(f.copy(level = level), 1_000_000, entry.name).use { encoder -> + encoder.prepare(); verified = encoder.format + } + break + } catch (e: Exception) { + LogManager.addLog("WARN", "call", "video encoder level probe rejected name=${entry.name} size=${w}x$h level=$level: ${e.message}") + } } + val actual = checkNotNull(verified) { "No valid encoder level: ${entry.name} ${w}x$h" } + check(foundProfile == 0 || foundProfile == actual.profile) { "Geometry changes profile/tier" } + foundProfile = actual.profile; foundLevel = maxOf(foundLevel, actual.level) } modes += EncoderMode(size, entry.name, foundProfile, foundLevel) LogManager.addLog("INFO", "call", "video codec probe passed type=${type.label} encoder=${entry.name} size=$size"); break @@ -277,21 +289,29 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name try { val caps = entry.getCapabilitiesForType(type.mime) if (caps.videoCapabilities?.areSizeAndRateSupported(size.width, size.height, 30.0) != true) continue - var profiles = 0; var pipeline = 0 - val level = if (type.id == 1) { if (size.width > 1280) 40 else 31 } else { if (size.width > 1280) 120 else 93 } - for (profile in if (type.id == 1) listOf(1, 2) else listOf(1)) { + 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) - pipeline = maxOf(pipeline, verifyDecode(f, List(8) { Packet(1_000_000L + it * 33333, true, key) }, + profilePipeline = maxOf(profilePipeline, verifyDecode(f, List(8) { Packet(1_000_000L + it * 33333, true, key) }, entry.name, true)) } - profiles = profiles or profile + 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}") + } } - modes += DecoderMode(size, entry.name, profiles, level, pipeline) + check(profiles != 0) { "No verified decoder profiles" } + modes += DecoderMode(size, entry.name, profiles, maximumLevel, pipeline) LogManager.addLog("INFO", "call", "video decoder verified codec=${type.id} name=${entry.name} size=$size profiles=$profiles pipeline=$pipeline") break } catch (e: Exception) { LogManager.addLog("WARN", "call", "video decoder probe rejected ${entry.name} size=$size: ${e.message}") } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt index f81e5d2c..9f13c875 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoEngine.kt @@ -271,7 +271,7 @@ class CallVideoEngine(private val context: Context, private val id: Long) { for (codec in if (settings.codec == 0) listOf(2, 1) else listOf(settings.codec)) { if (codec in excluded) continue val implementation = encoders[codec]?.modes?.firstOrNull { it.size == Size(settings.width, settings.height) } ?: continue - val desired = CallVideoFormat(++generation, width, height, codec = codec) + val desired = CallVideoFormat(++generation, width, height, codec = codec, profile = implementation.profile) val selected = CallVideoNative.choose(caps, peer, desired.array()) ?: continue var candidate: CallVideoCodec? = null try { diff --git a/tools/chatgui/src/call_video_decoder.cpp b/tools/chatgui/src/call_video_decoder.cpp index 283defcc..35c52064 100644 --- a/tools/chatgui/src/call_video_decoder.cpp +++ b/tools/chatgui/src/call_video_decoder.cpp @@ -244,7 +244,7 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic* running) call_video_caps probed{}; for (unsigned codec = 1; codec <= 2; ++codec) { unsigned profiles = 0, maximumTier = 0; - for (unsigned profile = 1; profile <= (codec == 1 ? 2u : 1u); ++profile) { + for (unsigned profile = 1; profile <= 2; ++profile) { for (unsigned tier = 0; tier < 2; ++tier) { const unsigned width = tier ? 1920 : 1280, height = tier ? 1080 : 720; for (Backend backend : {Backend::HardwareOnly, Backend::SoftwareOnly}) { @@ -260,7 +260,8 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic* running) call_video_frame input{}; input.call_id = 1; input.flags = CALL_VIDEO_KEY; input.data = bytes; input.size = input.length = size; input.format = {1, (uint16_t)w, (uint16_t)h, 30, 0, - (uint8_t)codec, (uint8_t)(codec == 1 ? (tier ? 40 : 31) : (tier ? 120 : 93)), 4000000, (uint8_t)profile}; + (uint8_t)codec, (uint8_t)(codec == 1 ? (tier ? 40 : 31) : (tier || profile == 2 ? 120 : 93)), + 4000000, (uint8_t)profile}; unsigned sent = 0, received = 0; bool valid = true, outputBeforeEos = false; const int64_t start = call_video_playout_now_us(); while (valid && received < 8 && call_video_playout_now_us() - start < 1500000) { @@ -303,7 +304,7 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic* running) if (profiles) { const uint16_t width = maximumTier ? 1920 : 1280, height = maximumTier ? 1080 : 720; call_video_limit limit{width, height, 30, uint32_t(width) * height * 30, 4000000, (uint8_t)profiles, - (uint8_t)(codec == 1 ? (maximumTier ? 40 : 31) : (maximumTier ? 120 : 93))}; + (uint8_t)(codec == 1 ? (maximumTier ? 40 : 31) : (maximumTier || (profiles & CALL_VIDEO_MAIN_HIGH_TIER) ? 120 : 93))}; if (!call_video_limit_orientations(&limit, codec)) probed.decode[codec - 1] = limit; } }