Add controlled native stream and Android audio test boundaries
This commit is contained in:
1 parent
43a749d8ab
commit
5dffa9917f
3 files changed
+107
No files matched your search
@@ -0,0 +1,59 @@
|
||||
package android.media
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class AudioAttributes {
|
||||
companion object { const val USAGE_MEDIA = 1; const val CONTENT_TYPE_MOVIE = 1 }
|
||||
class Builder {
|
||||
fun setUsage(value: Int) = this
|
||||
fun setContentType(value: Int) = this
|
||||
fun build() = AudioAttributes()
|
||||
}
|
||||
}
|
||||
class AudioFormat {
|
||||
companion object { const val CHANNEL_OUT_STEREO = 12; const val ENCODING_PCM_16BIT = 2 }
|
||||
class Builder {
|
||||
fun setSampleRate(value: Int) = this
|
||||
fun setChannelMask(value: Int) = this
|
||||
fun setEncoding(value: Int) = this
|
||||
fun build() = AudioFormat()
|
||||
}
|
||||
}
|
||||
/** Только JVM: SDK output остаётся наблюдаемым до подтверждённого release. */
|
||||
class AudioTrack {
|
||||
companion object {
|
||||
const val STATE_INITIALIZED = 1; const val MODE_STREAM = 1; const val WRITE_NON_BLOCKING = 1
|
||||
val created = CopyOnWriteArrayList<AudioTrack>()
|
||||
var nextFailures = emptySet<String>()
|
||||
fun getMinBufferSize(rate: Int, channels: Int, encoding: Int) = 3840
|
||||
}
|
||||
class Builder {
|
||||
fun setAudioAttributes(value: AudioAttributes) = this
|
||||
fun setAudioFormat(value: AudioFormat) = this
|
||||
fun setBufferSizeInBytes(value: Int) = this
|
||||
fun setTransferMode(value: Int) = this
|
||||
fun build() = AudioTrack().also { created.add(it) }
|
||||
}
|
||||
var failures = nextFailures
|
||||
var released = false
|
||||
private set
|
||||
val state get() = STATE_INITIALIZED
|
||||
var playState = 0
|
||||
private set
|
||||
val bufferSizeInFrames = 960
|
||||
var startThresholdInFrames = 1
|
||||
private set
|
||||
var playbackHeadPosition = 0
|
||||
private set
|
||||
val underrunCount = 0
|
||||
fun setStartThresholdInFrames(value: Int) { check("threshold" !in failures); startThresholdInFrames = value }
|
||||
fun write(pcm: ShortArray, offset: Int, count: Int, mode: Int): Int {
|
||||
check(!released); check("write" !in failures); playbackHeadPosition += count / 2; return count
|
||||
}
|
||||
fun pause() { check(!released); playState = 0 }
|
||||
fun play() { check(!released); check("play" !in failures); playState = 1 }
|
||||
fun release() {
|
||||
check("release" !in failures) { "audio release failed" }; released = true
|
||||
check("release after" !in failures) { "audio release failed after effect" }
|
||||
}
|
||||
}
|
||||
@@ -63,3 +63,4 @@ object SystemClock {
|
||||
fun elapsedRealtimeNanos(): Long = System.nanoTime()
|
||||
}
|
||||
object Build { object VERSION { const val SDK_INT = 36 } }
|
||||
object Process { const val THREAD_PRIORITY_AUDIO = -16; fun setThreadPriority(value: Int) {} }
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
import android.media.AudioTrack
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
object CallController { var blocked = false; fun blocksMedia() = blocked }
|
||||
object StreamController { private val ids = AtomicLong(100); fun nextId() = ids.incrementAndGet() }
|
||||
object MediaAudioController {
|
||||
data class State(val switching: Boolean = false)
|
||||
val state = MutableStateFlow(State())
|
||||
var failures = emptySet<String>()
|
||||
val outputs = ConcurrentHashMap.newKeySet<AudioTrack>()
|
||||
fun attachOutput(track: AudioTrack): AutoCloseable {
|
||||
check("attach" !in failures); outputs.add(track)
|
||||
return AutoCloseable { check("detach" !in failures); outputs.remove(track) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Native-границы viewer/mixer: fault injection до изменения владельца, никаких JNI/DSP/сети. */
|
||||
object HostStreams {
|
||||
val ports = ConcurrentHashMap.newKeySet<Long>()
|
||||
val signals = ConcurrentHashMap.newKeySet<Long>()
|
||||
val playbacks = ConcurrentHashMap.newKeySet<Long>()
|
||||
val records = ConcurrentLinkedQueue<ByteArray>()
|
||||
private val sequence = AtomicLong()
|
||||
@Volatile var failures = emptySet<String>()
|
||||
@Volatile var audioEnabled = false
|
||||
@Volatile var status = false
|
||||
@Volatile var onWait: () -> Unit = {}
|
||||
@Volatile var onPull: () -> Unit = {}
|
||||
fun access(name: String) { check(name !in failures) { "stream $name failed" } }
|
||||
fun open(id: Long): Boolean { access("open"); ports.add(id); return true }
|
||||
fun stop(id: Long) { access("stop"); ports.remove(id) }
|
||||
fun createSignal(): Long { access("signal create"); return sequence.incrementAndGet().also { signals.add(it) } }
|
||||
fun closeSignal(handle: Long) { access("signal close"); check(signals.remove(handle)) }
|
||||
fun notifySignal(handle: Long) { access("signal notify"); check(handle in signals) }
|
||||
fun waitSignal(handle: Long): Int { onWait(); access("signal wait"); check(handle in signals); Thread.sleep(2); return 0 }
|
||||
fun createPlayback(): Long { access("playback create"); return sequence.incrementAndGet().also { playbacks.add(it) } }
|
||||
fun closePlayback(handle: Long) { access("playback close"); check(playbacks.remove(handle)) }
|
||||
fun reset() {
|
||||
ports.clear(); signals.clear(); playbacks.clear(); records.clear(); failures = emptySet()
|
||||
audioEnabled = false; status = false; onWait = {}; onPull = {}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user