diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCameraModes.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCameraModes.kt index 4c6ffc4a..b5c4f681 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCameraModes.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCameraModes.kt @@ -11,7 +11,8 @@ import android.os.Build data class StreamCaptureMode(val camera: String, val width: Int, val height: Int, val fps: Int, val minimumFps: Int, val highSpeed: Boolean, val codec: String, val bitrate: Int, val videoCodec: StreamVideoCodec = StreamVideoCodec.H264, - val minimumBitrate: Int = 1, val maximumBitrate: Int = Int.MAX_VALUE) { + val minimumBitrate: Int = 1, val maximumBitrate: Int = Int.MAX_VALUE, + val encoderProfile: Int? = videoCodec.profile) { val label get() = "${width}×${height} · $fps fps" } data class StreamCamera(val id: String, val lens: LensOption, val modes: List) @@ -39,14 +40,14 @@ object StreamCameraModes { if (highSpeed && Build.VERSION.SDK_INT >= 29 && !codec.isHardwareAccelerated) continue val caps = codec.getCapabilitiesForType(type.mime) val video = caps.videoCapabilities + val profile = type.encoderProfile(caps.profileLevels.map { it.profile }.toIntArray()) if (!caps.colorFormats.contains(MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) || - (type.profile != null && caps.profileLevels.none { it.profile == type.profile }) || !video.areSizeAndRateSupported(width, height, fps.toDouble())) continue val bitrate = (width.toLong() * height * fps / 14).coerceIn(512_000L, 16_000_000L).toInt() modes.add(StreamCaptureMode(lens.cameraId, width, height, fps, lower, highSpeed, codec.name, - video.bitrateRange.clamp(bitrate), type, video.bitrateRange.lower, video.bitrateRange.upper)) + video.bitrateRange.clamp(bitrate), type, video.bitrateRange.lower, video.bitrateRange.upper, profile)) LogManager.addLog("INFO", "video", "camera mode camera=${lens.cameraId} ${width}x$height fps=$lower..$fps " + - "highSpeed=$highSpeed type=${type.label} encoder=${codec.name} bitrate=${modes.last().bitrate} " + + "highSpeed=$highSpeed type=${type.label} encoder=${codec.name} profile=$profile bitrate=${modes.last().bitrate} " + "range=${video.bitrateRange} achievable=${video.getAchievableFrameRatesFor(width, height)}") break } catch (e: Exception) { LogManager.addLog("WARN", "video", "codec probe ${codec.name}: ${e.message}") } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt index d72ab9a6..db705c71 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt @@ -138,7 +138,7 @@ class StreamCaptureEngine( if (switchStartedNs == 0L) switchStartedNs = reportNs val format = MediaFormat.createVideoFormat(mode.videoCodec.mime, mode.width, mode.height).apply { setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) - mode.videoCodec.profile?.let { setInteger(MediaFormat.KEY_PROFILE, it) } + mode.encoderProfile?.let { setInteger(MediaFormat.KEY_PROFILE, it) } setInteger(MediaFormat.KEY_BIT_RATE, config.bitrate) setInteger(MediaFormat.KEY_FRAME_RATE, mode.fps); setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) if (Build.VERSION.SDK_INT >= 29) setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt index 679f8aee..2b8709ab 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamVideoCodec.kt @@ -21,6 +21,7 @@ enum class StreamVideoCodec(val id: Int, val label: String, val mime: String, va require(bytes.isNotEmpty() && bytes.size <= H264AnnexB.MAX_FRAME) return if (annexB) H264AnnexB.normalize(bytes) else bytes.copyOf() } + fun encoderProfile(available: IntArray): Int? = profile?.takeIf { it in available } ?: available.firstOrNull() /** Parameter sets могут прийти отдельными CODEC_CONFIG callbacks; сохраняем полный набор текущей revision. */ fun mergeConfig(previous: ByteArray, update: ByteArray): ByteArray { val next = normalize(update)