Expose viewer wake signals and complete audio independently on Android

This commit is contained in:
evgeny committed 2026-10-21 00:13:01 +02:00
1 parent 9fb4823df0
commit a538f99bf0
3 files changed
+42 -9

No files matched your search

@@ -215,10 +215,14 @@ object NativeLib {
fun streamTxStats(id: Long): LongArray? = nativeStreamTxStats(id)
fun streamSetAudioEnabled(id: Long, enabled: Boolean): Boolean = nativeStreamSetAudioEnabled(id, enabled)
fun streamStop(id: Long) { nativeStreamStop(id) }
fun streamPull(id: Long, positionUs: Long = -1, wantVideo: Boolean = true, audioAheadUs: Long = 80000): ByteArray? =
nativeStreamPull(id, positionUs, wantVideo, audioAheadUs)
fun streamPull(id: Long, positionUs: Long = -1, video: Int = 1, audioAheadUs: Long = 80000, records: Int = 1): ByteArray? =
nativeStreamPull(id, positionUs, video, audioAheadUs, records)
fun streamStatus(id: Long): LongArray? = nativeStreamStatus(id)
fun streamResync(id: Long): Boolean = nativeStreamResync(id)
external fun streamSignalCreate(id: Long): Long
external fun streamSignalNotify(handle: Long)
external fun streamSignalWait(handle: Long, timeoutMs: Int): Int
external fun streamSignalClose(handle: Long)
fun streamPlaybackCreate(id: Long, pts: Long, audio: Boolean): Long = nativeStreamPlaybackCreate(id, pts, audio)
fun streamPlaybackFeed(handle: Long, pts: Long, packet: ByteArray): Int = nativeStreamPlaybackFeed(handle, pts, packet)
fun streamPlaybackPull(handle: Long, frames: Int, limit: Long): ShortArray? = nativeStreamPlaybackPull(handle, frames, limit)
@@ -227,6 +231,7 @@ object NativeLib {
nativeStreamPlaybackUpdate(handle, now, played, ready, guard, starved)
fun streamPlaybackFinish(handle: Long) { nativeStreamPlaybackFinish(handle) }
fun streamPlaybackEmpty(handle: Long): Boolean = nativeStreamPlaybackEmpty(handle)
fun streamPlaybackNeedsInput(handle: Long): Boolean = nativeStreamPlaybackNeedsInput(handle)
fun streamPlaybackClose(handle: Long) { nativeStreamPlaybackClose(handle) }
fun streamOpusCreate(encode: Boolean): Long = nativeStreamOpusCreate(encode)
fun streamOpusEncode(handle: Long, pcm: ShortArray, compressor: RadioCompressorSettings): ByteArray? =
@@ -241,7 +246,7 @@ object NativeLib {
private external fun nativeStreamTxStats(id: Long): LongArray?
private external fun nativeStreamSetAudioEnabled(id: Long, enabled: Boolean): Boolean
private external fun nativeStreamStop(id: Long)
private external fun nativeStreamPull(id: Long, position: Long, wantVideo: Boolean, audioAhead: Long): ByteArray?
private external fun nativeStreamPull(id: Long, position: Long, video: Int, audioAhead: Long, records: Int): ByteArray?
private external fun nativeStreamStatus(id: Long): LongArray?
private external fun nativeStreamResync(id: Long): Boolean
private external fun nativeStreamPlaybackCreate(id: Long, pts: Long, audio: Boolean): Long
@@ -252,6 +257,7 @@ object NativeLib {
guard: Long, starved: Boolean): LongArray?
private external fun nativeStreamPlaybackFinish(handle: Long)
private external fun nativeStreamPlaybackEmpty(handle: Long): Boolean
private external fun nativeStreamPlaybackNeedsInput(handle: Long): Boolean
private external fun nativeStreamPlaybackClose(handle: Long)
private external fun nativeStreamOpusCreate(encode: Boolean): Long
private external fun nativeStreamOpusEncode(handle: Long, pcm: ShortArray, compressor: Boolean, maxGainDb: Int, riseRate: Int): ByteArray?
@@ -76,7 +76,7 @@ internal object StreamAudioMixer {
/** Независимые Opus/SoundTouch, темп и PTS. Video worker и mixer сериализуют только свой DSP.
* Каждый 10ms блок общего выхода отражён в своей шкале, включая тишину при starvation/hold.
* Reset создаёт новый Input с новой origin; AudioTrack и остальные источники продолжают играть. */
internal class StreamMixedPlayback(val id: Long, pts: Long, private val audioEnabled: Boolean) {
internal class StreamMixedPlayback(val id: Long, pts: Long, private val audioEnabled: Boolean, private val wake: () -> Unit) {
private val lock = Any()
private var handle = NativeLib.streamPlaybackCreate(id, pts, audioEnabled).also { check(it != 0L) }
private val timeline = StreamMixTimeline()
@@ -91,6 +91,7 @@ internal class StreamMixedPlayback(val id: Long, pts: Long, private val audioEna
val leadUs: Long get() = if (audioEnabled) StreamAudioMixer.output.leadUs else 0
val empty: Boolean get() = synchronized(lock) { handle == 0L || NativeLib.streamPlaybackEmpty(handle) }
val needsInput: Boolean get() = synchronized(lock) { handle != 0L && NativeLib.streamPlaybackNeedsInput(handle) }
val drained: Boolean get() = synchronized(lock) {
!audioEnabled || (ended && (endFrame?.let { timeline.played(StreamAudioMixer.output.played) >= it }
?: (timeline.frames == 0L && NativeLib.streamPlaybackEmpty(handle))))
@@ -123,9 +124,11 @@ internal class StreamMixedPlayback(val id: Long, pts: Long, private val audioEna
if (ended && endFrame == null && NativeLib.streamPlaybackEmpty(handle)) endFrame = timeline.frames + count
if (count < buffer.frames) NativeLib.streamPlaybackSilence(handle, buffer.frames - count)
timeline.append(firstOutputFrame, buffer.frames)
wake()
true
} catch (e: Exception) {
outputFailure = e.message ?: "Audio mixing failed"
wake()
LogManager.addLog("ERROR", "stream", "mixer input failed id=$id: ${e.message}")
false
}