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 ae4290c3..c4ca3b11 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 @@ -238,66 +238,66 @@ internal class CallVideoCodec(requested: CallVideoFormat, bitrate: Int, val name fun probe(): Map { val result = mutableMapOf() for (type in listOf(StreamVideoCodec.H265, StreamVideoCodec.H264)) { - val modes = mutableListOf() - for (size in listOf(Size(640, 480), Size(1280, 720), Size(1920, 1080))) { - for (entry in candidates(true)) { - if (entry.supportedTypes.none { it == type.mime }) continue - try { - val caps = entry.getCapabilitiesForType(type.mime) - if (caps.encoderCapabilities?.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) != true) continue - var foundProfile = 0; var foundLevel = 0 - for ((w, h) in listOf(size.width to size.height, size.height to size.width)) { - check(caps.videoCapabilities?.areSizeAndRateSupported(w, h, 30.0) == true) { "Geometry unsupported: ${w}x$h" } - val fullhd = size.width > 1280 - 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) - } + val modes = mutableListOf() + for (size in listOf(Size(640, 480), Size(1280, 720), Size(1920, 1080))) { + for (entry in candidates(true)) { + if (entry.supportedTypes.none { it == type.mime }) continue + try { + val caps = entry.getCapabilitiesForType(type.mime) + if (caps.encoderCapabilities?.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) != true) continue + var foundProfile = 0; var foundLevel = 0 + for ((w, h) in listOf(size.width to size.height, size.height to size.width)) { + check(caps.videoCapabilities?.areSizeAndRateSupported(w, h, 30.0) == true) { "Geometry unsupported: ${w}x$h" } + val fullhd = size.width > 1280 + 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) } - modes += EncoderMode(size, entry.name, foundProfile, foundLevel) - LogManager.addLog("INFO", "call", "video codec probe passed type=${type.label} encoder=${entry.name} size=$size"); break - } catch (e: Exception) { LogManager.addLog("WARN", "call", "video codec probe rejected ${entry.name} size=$size: ${e.message}") } + } + modes += EncoderMode(size, entry.name, foundProfile, foundLevel) + LogManager.addLog("INFO", "call", "video codec probe passed type=${type.label} encoder=${entry.name} size=$size"); break + } catch (e: Exception) { LogManager.addLog("WARN", "call", "video codec probe rejected ${entry.name} size=$size: ${e.message}") } + } } - } - if (modes.isNotEmpty()) result[type.id] = Encoder(modes) + if (modes.isNotEmpty()) result[type.id] = Encoder(modes) } return result } fun probeDecoders(): Map { val result = mutableMapOf() for (type in listOf(StreamVideoCodec.H265, StreamVideoCodec.H264)) { - val modes = mutableListOf() - for (size in listOf(Size(1280, 720), Size(1920, 1080))) { - for (entry in candidates(false)) { - if (entry.supportedTypes.none { it == type.mime }) continue - 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)) { - 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) }, - entry.name, true)) + val modes = mutableListOf() + for (size in listOf(Size(1280, 720), Size(1920, 1080))) { + for (entry in candidates(false)) { + if (entry.supportedTypes.none { it == type.mime }) continue + 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)) { + 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) }, + entry.name, true)) + } + profiles = profiles or profile } - profiles = profiles or profile - } - modes += DecoderMode(size, entry.name, profiles, level, 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}") } + modes += DecoderMode(size, entry.name, profiles, level, 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}") } + } } - } - if (modes.isNotEmpty()) result[type.id] = Decoder(modes) + if (modes.isNotEmpty()) result[type.id] = Decoder(modes) } return result } 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 468bcb59..b5273def 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 @@ -137,14 +137,19 @@ class CallVideoEngine(private val context: Context, private val id: Long) { if (stopped) return@post val cameras = CameraLensManager.enumerate(manager).mapNotNull { lens -> val c = manager.getCameraCharacteristics(lens.cameraId) - val map = c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) + val map = c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: run { + LogManager.addLog("WARN", "call", "video camera has no stream configuration id=${lens.cameraId}") + return@mapNotNull null + } val rates = c.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES).orEmpty() - val sizes = map?.getOutputSizes(ImageFormat.YUV_420_888).orEmpty().filter { + val sizes = map.getOutputSizes(ImageFormat.YUV_420_888).orEmpty().filter { (it == Size(640, 480) || it == Size(1280, 720) || it == Size(1920, 1080)) && rates.any { range -> range.upper == 30 } && - map!!.getOutputMinFrameDuration(ImageFormat.YUV_420_888, it) <= 33_366_700L + map.getOutputMinFrameDuration(ImageFormat.YUV_420_888, it) <= 33_366_700L } val supported = sizes.filter { size -> encoders.values.any { size in it.sizes } } + LogManager.addLog(if (supported.isEmpty()) "WARN" else "INFO", "call", + "video camera id=${lens.cameraId} 30fpsYuv=$sizes verified=$supported") if (supported.isEmpty()) null else CallVideoCamera(lens.cameraId, lens.label, supported, c.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0, c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) diff --git a/tools/chatgui/src/call_camera_worker.cpp b/tools/chatgui/src/call_camera_worker.cpp index 83983c2e..38e18bb8 100644 --- a/tools/chatgui/src/call_camera_worker.cpp +++ b/tools/chatgui/src/call_camera_worker.cpp @@ -117,6 +117,7 @@ class CameraWorker : public QObject { } void stage(const QString& name) { send(packet(Stage, generation, [&](QDataStream& out) { out << name; })); } void enumerate() { + if (socket.state() != QLocalSocket::ConnectedState) return; // Первичный опрос повторит connected. stage(QStringLiteral("enumerate")); const auto devices = source->devices(); send(packet(Devices, 0, [&](QDataStream& out) {