Use available encoder profiles when the preferred video profile is absent

This commit is contained in:
evgeny committed 2026-10-14 02:14:24 +02:00
1 parent 643420b644
commit 499ce21cfc
3 files changed
+7 -5

No files matched your search

@@ -11,7 +11,8 @@ import android.os.Build
data class StreamCaptureMode(val camera: String, val width: Int, val height: Int, val fps: Int, 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 minimumFps: Int, val highSpeed: Boolean, val codec: String, val bitrate: Int,
val videoCodec: StreamVideoCodec = StreamVideoCodec.H264, 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" val label get() = "${width}×${height} · $fps fps"
} }
data class StreamCamera(val id: String, val lens: LensOption, val modes: List<StreamCaptureMode>) data class StreamCamera(val id: String, val lens: LensOption, val modes: List<StreamCaptureMode>)
@@ -39,14 +40,14 @@ object StreamCameraModes {
if (highSpeed && Build.VERSION.SDK_INT >= 29 && !codec.isHardwareAccelerated) continue if (highSpeed && Build.VERSION.SDK_INT >= 29 && !codec.isHardwareAccelerated) continue
val caps = codec.getCapabilitiesForType(type.mime) val caps = codec.getCapabilitiesForType(type.mime)
val video = caps.videoCapabilities val video = caps.videoCapabilities
val profile = type.encoderProfile(caps.profileLevels.map { it.profile }.toIntArray())
if (!caps.colorFormats.contains(MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) || 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 !video.areSizeAndRateSupported(width, height, fps.toDouble())) continue
val bitrate = (width.toLong() * height * fps / 14).coerceIn(512_000L, 16_000_000L).toInt() 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, 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 " + 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)}") "range=${video.bitrateRange} achievable=${video.getAchievableFrameRatesFor(width, height)}")
break break
} catch (e: Exception) { LogManager.addLog("WARN", "video", "codec probe ${codec.name}: ${e.message}") } } catch (e: Exception) { LogManager.addLog("WARN", "video", "codec probe ${codec.name}: ${e.message}") }
@@ -138,7 +138,7 @@ class StreamCaptureEngine(
if (switchStartedNs == 0L) switchStartedNs = reportNs if (switchStartedNs == 0L) switchStartedNs = reportNs
val format = MediaFormat.createVideoFormat(mode.videoCodec.mime, mode.width, mode.height).apply { val format = MediaFormat.createVideoFormat(mode.videoCodec.mime, mode.width, mode.height).apply {
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) 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_BIT_RATE, config.bitrate)
setInteger(MediaFormat.KEY_FRAME_RATE, mode.fps); setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) 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) if (Build.VERSION.SDK_INT >= 29) setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0)
@@ -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) require(bytes.isNotEmpty() && bytes.size <= H264AnnexB.MAX_FRAME)
return if (annexB) H264AnnexB.normalize(bytes) else bytes.copyOf() 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. */ /** Parameter sets могут прийти отдельными CODEC_CONFIG callbacks; сохраняем полный набор текущей revision. */
fun mergeConfig(previous: ByteArray, update: ByteArray): ByteArray { fun mergeConfig(previous: ByteArray, update: ByteArray): ByteArray {
val next = normalize(update) val next = normalize(update)