Document Android radio automatic capture and background window contracts

This commit is contained in:
evgeny committed 2026-10-27 21:58:02 +01:00
1 parent d6dc6234d5
commit f71aff3e11
2 files changed
+27 -4

No files matched your search

@@ -1,6 +1,14 @@
package com.utun.chat.data
/** Один снимок режима и связанных порогов; значения режимов совпадают с radio_audio.h. */
/** Сохранённые параметры автопередачи; enabled отделён от выбранного детектора (VAD/LEVEL).
* OFF используется только для захвата без автоматики и не является сохраняемым mode.
* valid проверяет весь снимок, включая параметры невыбранного детектора; nativeValues сам проверку не выполняет.
* Диапазоны совпадают с реестром chat_setting.c, номера режимов — с radio_audio.h.
* @param levelOn порог включения по уровню, -90..0 dBFS.
* @param levelOff порог выключения по уровню, -90..0 dBFS, строго ниже levelOn.
* @param hangoverMs тишина до автоматического отбоя, 50..2000 мс.
* @param vadThreshold порог вероятности речи Silero, 10..95 процентов.
* @param vadGain предел усиления отдельного компрессора копии PCM для VAD, 1..60 dB; не усиление передаваемого PCM. */
data class RadioAutoSettings(
val enabled: Boolean = false,
val mode: Int = VAD,
@@ -14,11 +22,14 @@ data class RadioAutoSettings(
fun valid(): Boolean = mode in VAD..LEVEL && levelOn in -90..0 && levelOff in -90..0 && levelOff < levelOn &&
hangoverMs in 50..2000 && vadThreshold in 10..95 && vadGain in 1..60
// Порядок фиксирован контрактом nativeRadioConfigure; вся группа применяется одним заданием uasync.
/** Новая копия [enabled, mode, levelOn, levelOff, hangoverMs, vadThreshold, vadGain] для nativeRadioConfigure.
* Native проверяет и применяет снимок целиком в uasync, результат приходит отдельно по request ID. */
fun nativeValues(): IntArray = intArrayOf(if (enabled) 1 else 0, mode, levelOn, levelOff, hangoverMs, vadThreshold, vadGain)
}
/** Решение о захвате одинаково для VAD/уровня; измеритель не включает автопередачу. */
/** Для уже проверенных settings: null — захват не нужен, OFF — только PTT/измеритель, VAD/LEVEL — с автоматикой.
* Неактивная рация исключает любой захват. Автоматика требует включённый экран и разрешение фонового окна;
* ручной PTT обходит эти два ограничения, измеритель работает только при включённом экране и не включает автоматику. */
internal fun radioCaptureMode(active: Boolean, screenOn: Boolean, autoAllowed: Boolean,
settings: RadioAutoSettings, manual: Boolean, monitor: Boolean): Int? {
if (!active) return null
@@ -1,7 +1,12 @@
package com.utun.chat.data
/** Окно VAD расходуется только в фоне; все изменения выполняются в radio-audio-io. */
/** Фоновое окно обоих детекторов автопередачи (VAD и уровня), длительность задаёт durationMs.
* В foreground открытое окно не ограничено временем; возвращение в приложение восстанавливает полный бюджет,
* следующий уход в фон даёт новое окно целиком. Повторная установка того же foreground/enabled его не продлевает.
* Вызывающий передаёт nowMs в монотонной шкале SystemClock.elapsedRealtime, отдельно учитывает экран и готовность аудио.
* Изменения и timeLeftMs — только в radio-audio-io; capture-поток может читать volatile deadlineMs и isAllowed. */
class RadioVadWindow(private val durationMs: Long) {
/** 0 — закрыто, Long.MAX_VALUE — открыто без ограничения foreground, иначе исключающая граница в elapsedRealtime. */
@Volatile var deadlineMs = 0L
private set
var foreground = false
@@ -9,6 +14,7 @@ class RadioVadWindow(private val durationMs: Long) {
private var enabled = false
private var remainingMs = durationMs
/** Смена видимости приложения: вход в foreground восстанавливает бюджет, выход запускает его отсчёт. */
fun setForeground(value: Boolean, nowMs: Long) {
if (foreground == value) return
if (value) remainingMs = durationMs
@@ -16,24 +22,30 @@ class RadioVadWindow(private val durationMs: Long) {
updateDeadline(nowMs)
}
/** Только переход false→true открывает новое окно; true→false закрывает его. */
fun setEnabled(value: Boolean, nowMs: Long) {
if (enabled == value) return
enabled = value
if (value) restart(nowMs) else close()
}
/** Восстановить полный бюджет (например, экран включился); выключенную настройку enabled не включает. */
fun restart(nowMs: Long) {
remainingMs = durationMs
updateDeadline(nowMs)
}
/** Немедленно закрыть окно (например, экран погас), сохранив enabled и foreground.
* Повторный setEnabled(true) не откроет его; нужны restart либо новый переход в foreground. */
fun close() {
remainingMs = 0L
deadlineMs = 0L
}
/** На самой границе deadline окно уже истекло; разрешение экрана/PTT проверяется отдельно. */
fun isAllowed(nowMs: Long): Boolean = nowMs < deadlineMs
/** Остаток в фоне или полный бюджет открытого foreground; для закрытого/истёкшего окна — 0. Только io-поток. */
fun timeLeftMs(nowMs: Long): Long =
if (deadlineMs == Long.MAX_VALUE) remainingMs else (deadlineMs - nowMs).coerceAtLeast(0L)