Browse Source

Preserve Opus preroll state across timestamp jitter and short gaps

master
evgeny 6 days ago
parent
commit
1d7f70b31c
  1. 26
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt

26
tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt

@ -94,14 +94,23 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
resetAudio(); resetVideo(); videoBase = -1; prefillUntil = 0
connectDeadline = System.nanoTime() + 10_000_000_000
}
// Отброшенный звук всё равно прогревает Opus; остающиеся пакеты будут декодированы один раз при воспроизведении.
fun warmAudio(frame: Frame) {
if (warmAudioPts >= 0 && frame.pts != warmAudioPts) {
LogManager.addLog("WARN", "stream", "preroll audio discontinuity id=$id expected=$warmAudioPts pts=${frame.pts}")
// Прогрев допускает джиттер PTS и короткий PLC; большой разрыв начинает новое состояние Opus.
fun prepareAudio(pts: Long) {
if (warmAudioPts >= 0 && (pts < warmAudioPts - 10000 || pts > warmAudioPts + 100000)) {
LogManager.addLog("WARN", "stream", "preroll audio discontinuity id=$id expected=$warmAudioPts pts=$pts")
if (opus != 0L) NativeLib.streamOpusClose(opus)
opus = 0
opus = 0; warmAudioPts = -1
}
if (opus == 0L) { opus = NativeLib.streamOpusCreate(false); check(opus != 0L) }
val missing = if (warmAudioPts < 0) 0 else ((pts - warmAudioPts + 10000) / 20000).toInt()
repeat(missing) {
check(NativeLib.streamOpusDecode(opus, byteArrayOf()) != null) { "Preroll Opus PLC failed" }
prerollFrames += 960
}
}
// Отброшенный звук всё равно прогревает Opus; остающиеся пакеты декодируются один раз при воспроизведении.
fun warmAudio(frame: Frame) {
prepareAudio(frame.pts)
check(NativeLib.streamOpusDecode(opus, frame.data) != null) { "Preroll Opus decode failed" }
warmAudioPts = frame.pts + 20000; prerollFrames += 960
}
@ -237,12 +246,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
val now = System.nanoTime()
if (prefillUntil != 0L && now >= prefillUntil) {
if (audios.isNotEmpty() && track == null) {
if (warmAudioPts >= 0 && audios.first.pts != warmAudioPts) {
LogManager.addLog("WARN", "stream", "audio startup discontinuity id=$id expected=$warmAudioPts pts=${audios.first.pts}")
if (opus != 0L) NativeLib.streamOpusClose(opus)
opus = 0
}
if (opus == 0L) { opus = NativeLib.streamOpusCreate(false); check(opus != 0L) }
prepareAudio(audios.first.pts)
track = AudioTrack.Builder().setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build()).setAudioFormat(AudioFormat.Builder()
.setSampleRate(48000).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())

Loading…
Cancel
Save