diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt index ffe3a3cc..69bd42f9 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamCaptureEngine.kt @@ -84,7 +84,8 @@ class StreamCaptureEngine( } check(NativeLib.streamOpen(id, group, width = size.width, height = size.height, rotation = rotation, audio = microphone)) originNs = System.nanoTime() - encoder = MediaCodec.createByCodecName(info.name).also { codec -> + encoder = MediaCodec.createByCodecName(info.name) + encoder!!.also { codec -> codec.setCallback(object : MediaCodec.Callback() { override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {} override fun onError(codec: MediaCodec, error: MediaCodec.CodecException) { failed("encoder: ${error.diagnosticInfo}") } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt index 98269070..f48e64c9 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt @@ -61,13 +61,19 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv var endDrainUntil = 0L fun resetVideo() { - try { decoder?.stop(); decoder?.release() } + try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder reset id=$id: ${e.message}") } + try { decoder?.release() } + catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder release id=$id: ${e.message}") } decoder = null; videos.clear(); videoBytes = 0; waitingKey = true mutableStatus.value = R.string.stream_waiting_key } fun resetAudio() { - track?.pause(); track?.flush(); track?.release(); track = null + try { track?.pause(); track?.flush() } + catch (e: Exception) { LogManager.addLog("WARN", "stream", "audio reset id=$id: ${e.message}") } + try { track?.release() } + catch (e: Exception) { LogManager.addLog("WARN", "stream", "audio release id=$id: ${e.message}") } + track = null if (opus != 0L) NativeLib.streamOpusClose(opus) opus = 0; audioBase = -1; written = 0; lastHead = -1; audios.clear() } @@ -85,6 +91,19 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv } return if (videoBase >= 0) videoBase + (now - videoStartNs) / 1000 else 0 } + fun writePcm(t: AudioTrack, pcm: ShortArray) { + val deadline = System.nanoTime() + 100_000_000 + var offset = 0 + while (running && offset < pcm.size) { + val count = t.write(pcm, offset, pcm.size - offset, AudioTrack.WRITE_NON_BLOCKING) + check(count >= 0) { "AudioTrack write=$count" } + if (count > 0) { offset += count; written += count } + else { + check(System.nanoTime() < deadline) { "AudioTrack stalled for 100ms" } + Thread.sleep(2) + } + } + } try { check(NativeLib.streamOpen(id, group, source)) { "Viewer open refused" } val openDeadline = System.nanoTime() + 10_000_000_000 @@ -159,10 +178,10 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv } else if (missing >= 0) { repeat(missing) { val conceal = NativeLib.streamOpusDecode(opus, byteArrayOf()) ?: error("Opus PLC failed") - check(t.write(conceal, 0, conceal.size, AudioTrack.WRITE_BLOCKING) == conceal.size); written += conceal.size + writePcm(t, conceal) } val pcm = NativeLib.streamOpusDecode(opus, f.data) ?: error("Opus decode failed") - check(t.write(pcm, 0, pcm.size, AudioTrack.WRITE_BLOCKING) == pcm.size); written += pcm.size + writePcm(t, pcm) } else LogManager.addLog("DEBUG", "stream", "late audio discarded id=$id pts=${f.pts}") } } @@ -178,7 +197,8 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv setByteBuffer("csd-1", ByteBuffer.wrap(nals.first { it.first == 8 }.second)) setInteger(MediaFormat.KEY_ROTATION, frame.rotation); setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, H264AnnexB.MAX_FRAME) } - decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC).also { it.configure(format, surface, null, 0); it.start() } + decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) + decoder!!.configure(format, surface, null, 0); decoder!!.start() LogManager.addLog("INFO", "video", "decoder started id=$id ${frame.width}x${frame.height} rotation=${frame.rotation}") } val index = decoder!!.dequeueInputBuffer(0) @@ -220,8 +240,10 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv if (running) { mutableStatus.value = R.string.stream_failed; LogManager.addLog("ERROR", "stream", "viewer failed id=$id: ${e.message}") } } finally { NativeLib.streamStop(id) - try { decoder?.stop(); decoder?.release() } catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder release id=$id: ${e.message}") } - try { track?.stop(); track?.release() } catch (e: Exception) { LogManager.addLog("WARN", "stream", "audio release id=$id: ${e.message}") } + try { decoder?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder stop id=$id: ${e.message}") } + try { decoder?.release() } catch (e: Exception) { LogManager.addLog("WARN", "video", "decoder release id=$id: ${e.message}") } + try { track?.stop() } catch (e: Exception) { LogManager.addLog("WARN", "stream", "audio stop id=$id: ${e.message}") } + try { track?.release() } catch (e: Exception) { LogManager.addLog("WARN", "stream", "audio release id=$id: ${e.message}") } if (opus != 0L) NativeLib.streamOpusClose(opus) val deadline = System.nanoTime() + 500_000_000 while (!ended && NativeLib.isReady() && System.nanoTime() < deadline) {