Restore video call modes and remember the selected bitrate range
This commit is contained in:
1 parent
76f81bc14e
commit
c9f8f17858
2 files changed
+84
-11
No files matched your search
@@ -17,6 +17,7 @@ import android.util.Size
|
||||
import android.view.Surface
|
||||
import android.view.OrientationEventListener
|
||||
import com.utun.chat.R
|
||||
import com.utun.chat.ChatApplication
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.nio.ByteBuffer
|
||||
@@ -26,7 +27,22 @@ data class CallVideoMode(val width: Int, val height: Int, val fps: Int)
|
||||
data class CallVideoCamera(val id: String, val name: String, val modes: List<CallVideoMode>, val sensor: Int, val front: Boolean,
|
||||
val focal35: Float)
|
||||
data class CallVideoSettings(val enabled: Boolean = false, val camera: String = "", val width: Int = 640,
|
||||
val height: Int = 480, val codec: Int = 0, val fps: Int = 30)
|
||||
val height: Int = 480, val codec: Int = 0, val fps: Int = 30) {
|
||||
/** Восстановить поддерживаемый режим; включение передачи относится только к текущему звонку. */
|
||||
fun restore(state: CallVideoState): CallVideoSettings {
|
||||
val format = codec.takeIf { it == 0 || (it in state.codecs && state.cameras.any { camera ->
|
||||
state.modes(camera.id, it).isNotEmpty()
|
||||
}) } ?: 0
|
||||
val selected = state.cameras.firstOrNull { it.id == camera && state.modes(it.id, format).isNotEmpty() }
|
||||
?: state.cameras.firstOrNull { state.modes(it.id, format).isNotEmpty() }
|
||||
val modes = selected?.let { state.modes(it.id, format) }.orEmpty()
|
||||
val mode = modes.firstOrNull { it.width == width && it.height == height && it.fps == fps }
|
||||
?: modes.filter { it.width == width && it.height == height && it.fps <= fps }.maxByOrNull { it.fps }
|
||||
?: modes.minWithOrNull(compareBy<CallVideoMode> { it.width * it.height }.thenBy { it.fps })
|
||||
return copy(enabled = false, camera = selected?.id.orEmpty(), codec = format,
|
||||
width = mode?.width ?: 640, height = mode?.height ?: 480, fps = mode?.fps ?: 30)
|
||||
}
|
||||
}
|
||||
data class CallVideoState(val ready: Boolean = false, val settings: CallVideoSettings = CallVideoSettings(),
|
||||
val cameras: List<CallVideoCamera> = emptyList(), val codecs: List<Int> = emptyList(), val bitrate: Int = 0,
|
||||
val range: CallVideoBitrateRange = CallVideoBitrateRange(), val status: String = "", val transmitting: Boolean = false,
|
||||
@@ -186,13 +202,22 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
|
||||
c.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0,
|
||||
c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT, lens.eqFocal)
|
||||
}
|
||||
val firstCamera = cameras.firstOrNull()
|
||||
val firstMode = firstCamera?.modes?.firstOrNull { it.width == wanted.width && it.height == wanted.height && it.fps == wanted.fps }
|
||||
?: firstCamera?.modes?.minWithOrNull(compareBy<CallVideoMode> { it.width * it.height }.thenBy { it.fps })
|
||||
wanted = wanted.copy(camera = firstCamera?.id.orEmpty(), width = firstMode?.width ?: 640,
|
||||
height = firstMode?.height ?: 480, fps = firstMode?.fps ?: 30)
|
||||
mutable.value = mutable.value.copy(ready = true, cameras = cameras, codecs = encoders.keys.toList(), settings = wanted,
|
||||
val available = mutable.value.copy(ready = true, cameras = cameras, codecs = encoders.keys.toList(),
|
||||
codecModes = encoders.mapValues { entry -> entry.value.modes.map { CallVideoMode(it.size.width, it.size.height, it.fps) } })
|
||||
val preferences = ChatApplication.instance.configProvider
|
||||
val saved = try { preferences.getCallVideoCaptureSettings() }
|
||||
catch (e: Exception) { LogManager.addLog("ERROR", "call", "Video settings load failed id=$id: $e"); CallVideoSettings() }
|
||||
wanted = saved.restore(available)
|
||||
if (saved.camera.isNotEmpty() && saved != wanted)
|
||||
LogManager.addLog("WARN", "call", "Saved video settings adjusted id=$id saved=$saved restored=$wanted")
|
||||
mutable.value = available.copy(settings = wanted)
|
||||
val savedRange = try { preferences.getCallVideoBitrateRange() }
|
||||
catch (e: Exception) { LogManager.addLog("ERROR", "call", "Video bitrate settings load failed id=$id: $e"); CallVideoBitrateRange() }
|
||||
if (stopped) { LogManager.addLog("INFO", "call", "Video settings restore cancelled id=$id"); return@post }
|
||||
if (CallVideoNative.range(id, savedRange.minimum * 1000, savedRange.maximum * 1000))
|
||||
mutable.value = mutable.value.copy(range = savedRange)
|
||||
else LogManager.addLog("WARN", "call", "Saved video bitrate range rejected id=$id range=$savedRange")
|
||||
LogManager.addLog("INFO", "call", "Video settings restored id=$id settings=$wanted range=${mutable.value.range}")
|
||||
report(if (encoders.isEmpty()) "Receive-only video" else "Video ready")
|
||||
tick()
|
||||
} catch (e: Exception) {
|
||||
@@ -222,13 +247,21 @@ class CallVideoEngine(private val context: Context, private val id: Long) {
|
||||
wanted = selected; excluded = emptySet(); observeOrientation(selected.enabled)
|
||||
mutable.value = current.copy(settings = selected, transmitting = selected.enabled && current.transmitting,
|
||||
activeCodec = if (selected.enabled) current.activeCodec else 0, notice = null)
|
||||
ChatApplication.instance.configProvider.saveCallVideoCaptureSettings(selected)
|
||||
LogManager.addLog("INFO", "call", "video selection accepted id=$id requested=$settings resolved=$selected ready=${current.ready}")
|
||||
}) LogManager.addLog("WARN", "call", "video selection rejected by stopped worker id=$id settings=$settings")
|
||||
}
|
||||
fun range(value: CallVideoBitrateRange) { handler.post {
|
||||
if (CallVideoNative.range(id, value.minimum * 1000, value.maximum * 1000)) mutable.value = mutable.value.copy(range = value)
|
||||
else report("Bitrate range rejected")
|
||||
} }
|
||||
fun range(value: CallVideoBitrateRange) {
|
||||
if (!handler.post {
|
||||
if (stopped) { LogManager.addLog("WARN", "call", "Bitrate selection after stop id=$id range=$value"); return@post }
|
||||
if (CallVideoNative.range(id, value.minimum * 1000, value.maximum * 1000)) {
|
||||
mutable.value = mutable.value.copy(range = value)
|
||||
ChatApplication.instance.configProvider.saveCallVideoBitrateRange(value)
|
||||
NativeLib.setChatSetting("call_video_bitrate_range_kbps", value.encode())
|
||||
LogManager.addLog("INFO", "call", "Video bitrate selected id=$id range=$value")
|
||||
} else LogManager.addLog("WARN", "call", "Bitrate range rejected id=$id range=$value")
|
||||
}) LogManager.addLog("WARN", "call", "Bitrate selection rejected by stopped worker id=$id range=$value")
|
||||
}
|
||||
private fun report(message: String) {
|
||||
mutable.value = mutable.value.copy(status = message)
|
||||
LogManager.addLog("INFO", "call", "video call=$id $message")
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class CallVideoSettingsTest {
|
||||
private val sd = CallVideoMode(640, 480, 30)
|
||||
private val hd = CallVideoMode(1280, 720, 60)
|
||||
private val fullHd = CallVideoMode(1920, 1080, 30)
|
||||
private val state = CallVideoState(ready = true, cameras = listOf(
|
||||
CallVideoCamera("front", "Front", listOf(sd, fullHd), 90, true, 25f),
|
||||
CallVideoCamera("back", "Back", listOf(sd, hd, fullHd), 90, false, 24f)),
|
||||
codecs = listOf(1, 2), codecModes = mapOf(1 to listOf(sd, fullHd), 2 to listOf(sd, hd)))
|
||||
|
||||
@Test fun savedCameraCodecAndCadenceRestoreWithoutEnablingTransmission() {
|
||||
val saved = CallVideoSettings(true, "back", 1280, 720, 2, 60)
|
||||
assertEquals(saved.copy(enabled = false), saved.restore(state))
|
||||
}
|
||||
|
||||
@Test fun unsupportedCadenceKeepsResolutionWithAvailableLowerRate() {
|
||||
val saved = CallVideoSettings(camera = "front", width = 1920, height = 1080, codec = 1, fps = 60)
|
||||
assertEquals(saved.copy(fps = 30), saved.restore(state))
|
||||
}
|
||||
|
||||
@Test fun missingCameraAndCodecChooseUsableAutoMode() {
|
||||
val saved = CallVideoSettings(camera = "gone", width = 1920, height = 1080, codec = 5)
|
||||
assertEquals(saved.copy(camera = "front", codec = 0), saved.restore(state))
|
||||
}
|
||||
|
||||
@Test fun listedCodecWithoutCameraModesFallsBackToAuto() {
|
||||
val saved = CallVideoSettings(camera = "back", codec = 2)
|
||||
val available = state.copy(codecModes = mapOf(1 to listOf(sd, fullHd), 2 to emptyList()))
|
||||
assertEquals(saved.copy(codec = 0), saved.restore(available))
|
||||
}
|
||||
|
||||
@Test fun missingModesProduceDisabledReceiveOnlySettings() {
|
||||
val restored = CallVideoSettings(true, "gone", 1920, 1080, 2, 60).restore(CallVideoState())
|
||||
assertEquals(CallVideoSettings(), restored)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user