Browse Source

android radio: preserve output route on reconfigure, link input/output settings

master
evgeny 2 days ago
parent
commit
745ec3dbd2
  1. 17
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt
  2. 29
      tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

17
tools/chatgui-android/app/src/main/java/com/utun/chat/data/RadioAudioEngine.kt

@ -126,6 +126,7 @@ class RadioAudioEngine {
active = false active = false
releaseLocked(false) releaseLocked(false)
active = true active = true
router.setAutoHeadset(true) /* дефолт «авто» только на свежий старт/смену группы */
setupAudio() setupAudio()
} }
@ -157,7 +158,6 @@ class RadioAudioEngine {
/* Маршрут выхода: автоследование за гарнитурой (воткнули → HEADSET, вынули → SPEAKER). */ /* Маршрут выхода: автоследование за гарнитурой (воткнули → HEADSET, вынули → SPEAKER). */
router.fallbackRoute = AudioRoute.SPEAKER router.fallbackRoute = AudioRoute.SPEAKER
router.setAutoHeadset(true)
router.start() router.start()
// ── AudioRecord (захват) с откатом стерео→моно ── // ── AudioRecord (захват) с откатом стерео→моно ──
@ -382,6 +382,7 @@ class RadioAudioEngine {
val ch = captureChannels val ch = captureChannels
val frame = FRAME_SAMPLES * ch val frame = FRAME_SAMPLES * ch
val buf = ShortArray(frame) val buf = ShortArray(frame)
var frameN = 0L
while (active) { while (active) {
val rec = audioRecord ?: break val rec = audioRecord ?: break
if (rec.recordingState != AudioRecord.RECORDSTATE_RECORDING) { if (rec.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
@ -404,6 +405,12 @@ class RadioAudioEngine {
} }
read += n read += n
} }
/* диагностика крос-девайс входа/выхода: куда реально роутится захват + уровень сигнала */
if (frameN < 10 || frameN % 100L == 0L) {
LogManager.addLog("DEBUG", "RadioAudio",
"cap routed=${rec.routedDevice?.type ?: "-"} frame=$frameN rmsDb=${rmsDb(buf)} input=${inputSource()} route=${route()}")
}
frameN++
if ((talking || vadMode) && !NativeLib.radioAudioFeed(groupId, buf)) { if ((talking || vadMode) && !NativeLib.radioAudioFeed(groupId, buf)) {
LogManager.addLog("WARN", "RadioAudio", "radioAudioFeed returned false") LogManager.addLog("WARN", "RadioAudio", "radioAudioFeed returned false")
} }
@ -411,6 +418,14 @@ class RadioAudioEngine {
LogManager.addLog("DEBUG", "RadioAudio", "capture loop done") LogManager.addLog("DEBUG", "RadioAudio", "capture loop done")
} }
/** RMS захваченного кадра в dBFS (0 = полная шкала; тишина ~ -90). */
private fun rmsDb(buf: ShortArray): Int {
var sum = 0L
for (s in buf) sum += s.toLong() * s.toLong()
val rms = Math.sqrt(sum.toDouble() / buf.size)
return if (rms < 1.0) -120 else (20.0 * Math.log10(rms / 32768.0)).toInt()
}
private fun playLoop() { private fun playLoop() {
val frame = FRAME_SAMPLES * PLAY_CHANNELS val frame = FRAME_SAMPLES * PLAY_CHANNELS
val buf = ShortArray(frame) val buf = ShortArray(frame)

29
tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt

@ -860,22 +860,47 @@ class ChatViewModel : ViewModel() {
radioAudio.talkEnd() radioAudio.talkEnd()
} }
/** Ручной выбор выхода рации (динамик/гарнитура). */ /** Ручной выбор выхода рации (динамик/гарнитура) — синхронизирует вход. */
fun setRadioRoute(r: AudioRoute) { fun setRadioRoute(r: AudioRoute) {
if (!_radioActive.value) return if (!_radioActive.value) return
_radioAuto.value = false _radioAuto.value = false
radioAudio.setRoute(r) radioAudio.setRoute(r)
_radioRoute.value = radioAudio.route() _radioRoute.value = radioAudio.route()
val linkedInput = when (r) {
AudioRoute.SPEAKER -> RadioAudioEngine.INPUT_BUILTIN
AudioRoute.HEADSET -> when {
_wiredInputAvailable.value -> RadioAudioEngine.INPUT_WIRED
_btInputAvailable.value -> RadioAudioEngine.INPUT_BT
else -> null
}
else -> null
}
if (linkedInput != null && linkedInput != _radioInputSource.value) {
_radioInputSource.value = linkedInput
viewModelScope.launch {
ChatApplication.instance.configProvider.setRadioInputSource(linkedInput)
radioAudio.reconfigure()
}
}
LogManager.addLog("INFO", "VM", "setRadioRoute -> ${radioAudio.route()}") LogManager.addLog("INFO", "VM", "setRadioRoute -> ${radioAudio.route()}")
} }
/** Ручной выбор входного устройства рации (выключает авто). */ /** Ручной выбор входного устройства рации — синхронизирует выход. */
fun setRadioInputSource(src: Int) { fun setRadioInputSource(src: Int) {
if (!_radioActive.value) return if (!_radioActive.value) return
val clamped = src.coerceIn(RadioAudioEngine.INPUT_AUTO, RadioAudioEngine.INPUT_BT) val clamped = src.coerceIn(RadioAudioEngine.INPUT_AUTO, RadioAudioEngine.INPUT_BT)
if (clamped == _radioInputSource.value) return if (clamped == _radioInputSource.value) return
_radioAuto.value = false _radioAuto.value = false
_radioInputSource.value = clamped _radioInputSource.value = clamped
val linkedRoute = when (clamped) {
RadioAudioEngine.INPUT_BUILTIN -> AudioRoute.SPEAKER
RadioAudioEngine.INPUT_WIRED, RadioAudioEngine.INPUT_BT -> AudioRoute.HEADSET
else -> null
}
if (linkedRoute != null) {
radioAudio.setRoute(linkedRoute)
_radioRoute.value = radioAudio.route()
}
viewModelScope.launch { viewModelScope.launch {
ChatApplication.instance.configProvider.setRadioInputSource(clamped) ChatApplication.instance.configProvider.setRadioInputSource(clamped)
radioAudio.reconfigure() radioAudio.reconfigure()

Loading…
Cancel
Save