Model radio worker I/O faults and partial native capture effects

This commit is contained in:
evgeny committed 2026-10-29 13:34:23 +01:00
1 parent 128af68a89
commit 5d74bcd865
2 files changed
+65 -17

No files matched your search

@@ -1,6 +1,7 @@
package android.media
import android.os.Handler
import java.util.concurrent.LinkedBlockingDeque
interface AudioRouting {
fun interface OnRoutingChangedListener { fun onRoutingChanged(router: AudioRouting) }
@@ -22,8 +23,8 @@ class AudioTrack(val id: Int) : AudioRouting {
failures = nextFailures
created.add(this)
}
var playbackHeadPosition = 0
private set
private var head = 0
val playbackHeadPosition: Int get() { access("head"); return head }
fun play() { access("play") }
var interruptOnStop = false
fun stop() { access("stop"); if (interruptOnStop) Thread.currentThread().interrupt() }
@@ -36,12 +37,21 @@ class AudioTrack(val id: Int) : AudioRouting {
check("release after" !in failures) { "track release failed after effect" }
}
fun write(samples: ShortArray, offset: Int, count: Int): Int {
onWrite()
access("write")
check(offset >= 0 && offset + count <= samples.size)
playbackHeadPosition += count / 2
return count
val result = forcedWriteResult ?: minOf(count, maxWriteSamples)
val write = Write(offset, count, result, samples.copyOfRange(offset, offset + count))
if (!writes.offerLast(write)) { writes.pollFirst(); writes.offerLast(write) }
if (result > 0) head += result / 2
return result
}
var failures = emptySet<String>()
data class Write(val offset: Int, val count: Int, val result: Int, val pcm: ShortArray)
val writes = LinkedBlockingDeque<Write>(64)
@Volatile var onWrite: () -> Unit = {}
@Volatile var maxWriteSamples = Int.MAX_VALUE
@Volatile var forcedWriteResult: Int? = null
@Volatile var failures = emptySet<String>()
var acceptPreference = true
var autoRoute = true
var emitOnPreference = false
@@ -2,6 +2,7 @@ package com.utun.chat.data
import java.util.Collections
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.ConcurrentLinkedQueue
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,7 +41,12 @@ object NativeLib {
@Volatile private var radioGroup = 0L
@Volatile private var radioMode = 0
@Volatile private var radioTx = false
var radioFailures = emptySet<String>()
@Volatile var radioFailures = emptySet<String>()
@Volatile var radioCaptureAlive = false
@Volatile var radioPullResult = 0
val radioPullResults = ConcurrentLinkedQueue<Int>()
@Volatile var onRadioFeed: (Long) -> Unit = {}
val radioFeeds: MutableList<Long> = Collections.synchronizedList(mutableListOf())
fun radioSetActive(group: Long, active: Boolean) { radioCommands.add(RadioCommand("active=$active", group)) }
fun radioConfigure(request: Long, settings: RadioAutoSettings): Boolean { radioRequest = request; return settings.valid() }
fun radioAudioStart(group: Long, channels: Int): Boolean {
@@ -50,19 +56,51 @@ object NativeLib {
fun radioAudioStop() {
radioCommands.add(RadioCommand("audio stop", radioGroup))
check("stop" !in radioFailures) { "native audio stop failed" }
radioTx = false; radioCaptureAlive = false
}
fun radioAudioPull(group: Long, out: ShortArray, playedFrames: Long): Int {
check("pull" !in radioFailures) { "native pull failed" }
val result = radioPullResults.poll() ?: radioPullResult
if (result in 1..out.size) out.fill(7, 0, result)
return result
}
fun radioAudioFeed(group: Long, samples: ShortArray): Boolean {
onRadioFeed(group)
radioFeeds.add(group)
check("feed" !in radioFailures) { "native feed failed" }
return "feed reject" !in radioFailures && group == radioGroup && radioCaptureAlive && samples.isNotEmpty()
}
fun radioCaptureStart(group: Long, channels: Int, mode: Int): Boolean {
radioCommands.add(RadioCommand("capture start", group))
if ("capture reject" in radioFailures) return false
radioMode = if ("detector off" in radioFailures) 0 else mode
radioCaptureAlive = true
check("capture start" !in radioFailures) { "native capture start failed after effect" }
return true
}
fun radioCaptureStop() {
radioCommands.add(RadioCommand("capture stop", radioGroup))
check("capture stop" !in radioFailures) { "native capture stop failed" }
radioTx = false; radioCaptureAlive = false
}
fun radioCaptureDiscontinuity(group: Long) {
radioCommands.add(RadioCommand("boundary", group))
check("boundary" !in radioFailures) { "native boundary failed" }
}
fun radioAutoMode(): Int {
check("mode" !in radioFailures) { "native mode query failed" }
return if ("bad mode" in radioFailures) 99 else radioMode
}
fun radioTransmitting() = radioTx
fun radioTalkBegin(group: Long) {
radioCommands.add(RadioCommand("begin", group)); radioTx = true
check("begin" !in radioFailures) { "native begin failed after effect" }
}
fun radioTalkEnd(group: Long) {
radioCommands.add(RadioCommand("end", group))
check("end" !in radioFailures) { "native end failed" }
radioTx = false
}
fun radioAudioPull(group: Long, out: ShortArray, playedFrames: Long): Int = 0
fun radioAudioFeed(group: Long, samples: ShortArray): Boolean = group == radioGroup
fun radioCaptureStart(group: Long, channels: Int, mode: Int): Boolean {
radioMode = mode; radioCommands.add(RadioCommand("capture start", group)); return true
}
fun radioCaptureStop() { radioTx = false; radioCommands.add(RadioCommand("capture stop", radioGroup)) }
fun radioCaptureDiscontinuity(group: Long) { radioCommands.add(RadioCommand("boundary", group)) }
fun radioAutoMode() = radioMode
fun radioTransmitting() = radioTx
fun radioTalkBegin(group: Long) { radioTx = true; radioCommands.add(RadioCommand("begin", group)) }
fun radioTalkEnd(group: Long) { radioTx = false; radioCommands.add(RadioCommand("end", group)) }
data class LocalInvite(val peer: Long, val request: Long, val link: String)
val localInvites = mutableListOf<LocalInvite>()
var sendLocal: (LocalInvite) -> Boolean = { true }