diff --git a/src/stream/stream_playback_audio.cpp b/src/stream/stream_playback_audio.cpp index 401f449d..8613b999 100644 --- a/src/stream/stream_playback_audio.cpp +++ b/src/stream/stream_playback_audio.cpp @@ -212,6 +212,17 @@ int64_t stream_playback_audio_pending_gain(struct stream_playback_audio* a, uint uint64_t stream_playback_audio_written(const struct stream_playback_audio* a) { return a ? a->written : 0; } +int stream_playback_audio_starved(const struct stream_playback_audio* a, uint64_t played) { + if (!a || a->finished) return 0; + if (played >= a->written || !a->outputCount) return 1; + for (unsigned i = 0; i < a->outputCount; i++) { + const auto& span = a->output[(a->outputHead + i) % mapCapacity]; + if (span.last <= played) continue; + return span.ptsLast == span.ptsFirst; + } + return 1; +} + void stream_playback_audio_finish(struct stream_playback_audio* a) { if (!a || a->finished) return; a->finished = true; a->stretch.flush(); diff --git a/src/stream/stream_playback_audio.h b/src/stream/stream_playback_audio.h index 1f2ecbe6..213d760a 100644 --- a/src/stream/stream_playback_audio.h +++ b/src/stream/stream_playback_audio.h @@ -26,6 +26,7 @@ int stream_playback_audio_pull(struct stream_playback_audio *audio, int16_t *pcm void stream_playback_audio_silence(struct stream_playback_audio *audio, unsigned frames); int64_t stream_playback_audio_position(struct stream_playback_audio *audio, uint64_t played_frames); int64_t stream_playback_audio_pending_gain(struct stream_playback_audio *audio, uint64_t played_frames); +int stream_playback_audio_starved(const struct stream_playback_audio *audio, uint64_t played_frames); uint64_t stream_playback_audio_written(const struct stream_playback_audio *audio); void stream_playback_audio_finish(struct stream_playback_audio *audio); int stream_playback_audio_empty(const struct stream_playback_audio *audio); diff --git a/tools/chatgui-android/jni_bridge/android_stream_jni.c b/tools/chatgui-android/jni_bridge/android_stream_jni.c index 99c871fc..2e8348f9 100644 --- a/tools/chatgui-android/jni_bridge/android_stream_jni.c +++ b/tools/chatgui-android/jni_bridge/android_stream_jni.c @@ -10,6 +10,7 @@ #include "../../../lib/audio_compressor.h" #include "../libutun_lite/stream_bridge.h" #include "../../../src/stream/stream_media.h" +#include "../../../src/stream/stream_media_buffer.h" #include "../../../src/stream/stream_playback_clock.h" #include "../../../src/stream/stream_playback_audio.h" @@ -88,6 +89,7 @@ struct android_stream_playback { struct stream_playback_state state; struct stream_playback_audio *audio; int64_t position, time_us; + int finished; }; JNIEXPORT jlong JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackCreate( @@ -136,6 +138,7 @@ JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlayb if (p->audio) { p->position = stream_playback_audio_position(p->audio, (uint64_t)played); pending = stream_playback_audio_pending_gain(p->audio, (uint64_t)played); + if (!p->finished && stream_playback_audio_starved(p->audio, (uint64_t)played)) starved = JNI_TRUE; } else if (p->time_us >= 0 && !starved) { int64_t elapsed = now - p->time_us; if (elapsed < 0) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "JNI playback wall clock regressed"); return NULL; } @@ -157,7 +160,7 @@ JNIEXPORT jlongArray JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlayb JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackFinish(JNIEnv *env, jobject obj, jlong handle) { (void)env; (void)obj; struct android_stream_playback *p = (struct android_stream_playback *)(intptr_t)handle; - if (p) stream_playback_audio_finish(p->audio); + if (p) { p->finished = 1; stream_playback_audio_finish(p->audio); } } JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStreamPlaybackEmpty(JNIEnv *env, jobject obj, jlong handle) { diff --git a/tools/chatgui-android/libutun_lite/stream_bridge.c b/tools/chatgui-android/libutun_lite/stream_bridge.c index 858f459b..3c77b4a0 100644 --- a/tools/chatgui-android/libutun_lite/stream_bridge.c +++ b/tools/chatgui-android/libutun_lite/stream_bridge.c @@ -264,6 +264,9 @@ static void media_process(void *arg) { if (packet.kind == STREAM_MEDIA_READ_END) media_close(&viewer, 5); else media_frame(&viewer, &packet.format, packet.kind == STREAM_MEDIA_READ_VIDEO ? &packet.video : NULL, packet.kind == STREAM_MEDIA_READ_AUDIO ? &packet.audio : NULL); + } else if (result == STREAM_MORE) { + viewer.want_read = 1; + if (media_schedule() < 0) DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media assembly scheduling failed"); } else if (result == STREAM_WAIT) viewer.waiting = 1; else if (result == STREAM_GAP || result == STREAM_EXPIRED) { DEBUG_INFO(DEBUG_CATEGORY_STREAM, "Android playback resync id=%llu result=%d", (unsigned long long)viewer.id, result); @@ -296,7 +299,7 @@ static void media_process(void *arg) { } } if (publisher.finished && now >= publisher.drain_until) media_close(&publisher, 5); - if (video_queue.head || audio_queue.head || publisher.finished) { + if (!media_post && (video_queue.head || audio_queue.head || publisher.finished)) { media_timer = uasync_set_timeout(media_inst->ua, publisher.finished ? 200 : 50, NULL, media_process, "android_stream"); if (!media_timer) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "Android media timer allocation failed"); media_close(&publisher, 6); } }