diff --git a/src/stream/stream_media_buffer.c b/src/stream/stream_media_buffer.c index 17bf364d..d72c896c 100644 --- a/src/stream/stream_media_buffer.c +++ b/src/stream/stream_media_buffer.c @@ -34,6 +34,8 @@ struct stream_media_cursor { int initialized; unsigned tracks; int64_t start_us; + int audio_started; + int64_t audio_blocked_pts; uint8_t *scratch[2], *frame; size_t scratch_capacity, frame_capacity; uint64_t scratch_seq[2]; @@ -258,6 +260,7 @@ void stream_media_cursor_destroy(struct stream_media_cursor *c) { void stream_media_cursor_reset(struct stream_media_cursor *c) { if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor reset: null cursor"); return; } c->initialized = 0; c->video_revision = 0; c->pending_used = 0; + c->audio_started = 0; c->audio_blocked_pts = -1; c->pending = (struct stream_media_video){0}; c->pending_format = (struct stream_media_format){0}; c->scratch_valid[0] = c->scratch_valid[1] = 0; c->video = (struct cursor_track){0}; c->audio = (struct cursor_track){0}; @@ -431,10 +434,22 @@ int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, in result = cursor_block(c, c->audio.seq, &audio); if (result != STREAM_OK) return result; struct stream_media_audio a = audio.audio[c->audio.entry++]; + if (!c->audio_started) { + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "media cursor first audio source=%llx seq=%llu pts=%llu start=%lld lead_us=%lld", + (unsigned long long)c->store->descriptor.source_node_id, (unsigned long long)c->audio.seq, + (unsigned long long)a.pts_us, (long long)c->start_us, (long long)((int64_t)a.pts_us - c->start_us)); + c->audio_started = 1; + } memcpy(c->opus, a.data, a.length); a.data = c->opus; *packet = (struct stream_media_packet){.kind = STREAM_MEDIA_READ_AUDIO, .format = audio.format, .audio = a}; return STREAM_OK; } if (video_allowed) return cursor_video(c, packet); + if (ar == STREAM_OK && audio_ahead >= 0 && audio_pts > audio_limit && c->audio_blocked_pts != audio_pts) { + c->audio_blocked_pts = audio_pts; + DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "media cursor audio waits source=%llx seq=%llu pts=%lld position=%lld limit=%lld first=%d video_requested=%d", + (unsigned long long)c->store->descriptor.source_node_id, (unsigned long long)c->audio.seq, + (long long)audio_pts, (long long)position, (long long)audio_limit, !c->audio_started, want_video); + } return ar == STREAM_MORE || vr == STREAM_MORE ? STREAM_MORE : STREAM_WAIT; } 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 6f41fced..067d0279 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 @@ -52,6 +52,8 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv var position = -1L var ready = -1L var needsAudio = true + var audioInputs = 0L + var lastAudioPts = -1L var generation = 0L var playbackStarted = false var eosQueued = false @@ -178,6 +180,7 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv } } 2 -> if (audioEnabled && playback != 0L) { + audioInputs++; lastAudioPts = pts val result = NativeLib.streamPlaybackFeed(playback, pts, ByteArray(size).also { b.get(it) }) if (result == -2) resetTimeline(true) else check(result >= 0) { "Opus feed failed=$result" } } @@ -235,7 +238,8 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv LogManager.addLog("DEBUG", "stream", "viewer id=$id revision=$videoRevision inputs=${video.inputCount} " + "decoded=${video.outputCount} shown=${video.shown} discarded=${video.discarded} input=${video.input != null} " + "output=${video.output?.pts} inflight=${video.inflight} decoderLeadUs=${video.decoderLeadUs} " + - "clock=$position reserve=${clockState?.get(1)} minimum=${clockState?.get(2)} " + + "clock=$position ready=$ready audio=$audioEnabled audioInputs=$audioInputs audioPTS=$lastAudioPts needsAudio=$needsAudio " + + "reserve=${clockState?.get(1)} minimum=${clockState?.get(2)} " + "tempo=${clockState?.get(3)} ${audio.description} waitingKey=$waitingKey") } Thread.sleep(5) diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index d66cecd7..50d0d235 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -657,6 +657,13 @@ target_include_directories(test_voice_file PRIVATE ${UTUN_INCLUDE_DIRS}) target_link_libraries(test_voice_file PRIVATE ${QT_LIBS} utun_voice utun pthread ${CMAKE_DL_LIBS}) add_test(NAME test_voice_file COMMAND test_voice_file) +if(WIN32) + # AudioDevice закрывает неисправный WASAPI в worker с собственным COM apartment. + foreach(audio_target vibechat test_file_drop test_invite_links test_video_engine test_stream_view test_stream_publish test_voice_file) + target_link_libraries(${audio_target} PRIVATE ole32) + endforeach() +endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_executable(test_audio_recovery tests/test_audio_recovery.cpp src/audio_device.cpp src/sound_manager.cpp src/voice_audio_io.cpp src/audiorecorder.cpp src/call_audio_engine.cpp src/radio_audio_engine.cpp