From c03fe3da71013261487fe65880c6605dfc9c9021 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 16 Oct 2026 17:24:25 +0200 Subject: [PATCH] Prime late stream audio without waiting for its playback clock --- src/stream/stream_media_buffer.c | 24 ++++++++++++++---------- src/stream/stream_media_buffer.h | 2 ++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/stream/stream_media_buffer.c b/src/stream/stream_media_buffer.c index 28f38c31..0349d068 100644 --- a/src/stream/stream_media_buffer.c +++ b/src/stream/stream_media_buffer.c @@ -35,7 +35,7 @@ struct stream_media_cursor { unsigned tracks; int64_t start_us; int audio_started; - int64_t audio_blocked_pts; + int64_t audio_start_us; uint8_t *scratch[2], *frame; size_t scratch_capacity, frame_capacity; uint64_t scratch_seq[2]; @@ -260,7 +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->audio_started = 0; c->audio_start_us = -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}; @@ -274,7 +274,7 @@ static int cursor_start(struct stream_media_cursor *c) { if (s->failed) return STREAM_ERR_FORMAT; if (c->initialized) return STREAM_OK; if (!s->count || s->key_seq == UINT64_MAX || !media_find(s, s->key_seq)) return s->finished ? STREAM_GAP : STREAM_WAIT; - c->generation = s->generation; c->start_us = s->key_us; + c->generation = s->generation; c->start_us = s->key_us; c->audio_start_us = -1; c->video.seq = s->key_seq; c->audio.seq = s->key_seq; for (unsigned i = 0; i < s->count; i++) { struct media_position *p = media_at(s, i); @@ -426,8 +426,18 @@ int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, in if (c->audio.ended && c->video.ended) { *packet = (struct stream_media_packet){.kind = STREAM_MEDIA_READ_END}; return STREAM_OK; } + /* Поздний первый звук должен прогреть decoder до запуска audio clock. Основание фиксировано, + * поэтому неподвижные часы разрешают только один lookahead, а не всё оставшееся аудио. */ + if (ar == STREAM_OK && c->audio_start_us < 0) { + c->audio_start_us = audio_pts > c->start_us ? audio_pts : c->start_us; + if (audio_pts > c->start_us) DEBUG_INFO(DEBUG_CATEGORY_STREAM, + "media cursor audio startup source=%llx video_pts=%lld audio_pts=%lld gap_us=%lld", + (unsigned long long)c->store->descriptor.source_node_id, (long long)c->start_us, + (long long)audio_pts, (long long)(audio_pts - c->start_us)); + } + int64_t audio_position = position > c->audio_start_us ? position : c->audio_start_us; int64_t audio_limit = audio_ahead < 0 ? -1 : position < 0 ? c->start_us - : position > INT64_MAX - audio_ahead ? INT64_MAX : position + audio_ahead; + : audio_position > INT64_MAX - audio_ahead ? INT64_MAX : audio_position + audio_ahead; int video_allowed = want_video && vr == STREAM_OK; if (ar == STREAM_OK && audio_ahead >= 0 && audio_pts <= audio_limit && (audio_pts < video_pts || !video_allowed)) { /* video peek мог переиспользовать scratch аудио: заново взять ту же пачку из двухэлементного cache. */ @@ -445,11 +455,5 @@ int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, in return STREAM_OK; } if (video_allowed) return cursor_video(c, packet); - if (!c->audio_started && 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_DEBUG, "media cursor first audio waits source=%llx seq=%llu pts=%lld position=%lld limit=%lld 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, want_video); - } return ar == STREAM_MORE || vr == STREAM_MORE ? STREAM_MORE : STREAM_WAIT; } diff --git a/src/stream/stream_media_buffer.h b/src/stream/stream_media_buffer.h index 00363f8d..e7bd0ee0 100644 --- a/src/stream/stream_media_buffer.h +++ b/src/stream/stream_media_buffer.h @@ -50,6 +50,8 @@ void stream_media_cursor_reset(struct stream_media_cursor *cursor); int stream_media_cursor_status(struct stream_media_cursor *cursor, struct stream_media_status *status); /* want_video=1 запрашивает следующий кадр независимо от часов показа; 0 оставляет видео в кольце. * position_us<0 прогревает аудио перед KEY; затем звук выдаётся до position_us+audio_ahead_us, -1 отключает звук. + * Если звук начинается позже KEY, lookahead отсчитывается минимум от первого аудиопакета: + * decoder получает стартовый PCM при неподвижных часах; размер разрешённого окна остаётся ограниченным. * При GAP/EXPIRED cursor сбрасывают отдельно: это позволяет frontend сначала обработать разрыв. * Бюджет одного вызова ограничен; MORE требует отложенного продолжения, WAIT — новых данных/позиции. */ int stream_media_cursor_read(struct stream_media_cursor *cursor, int64_t position_us, int want_video, int64_t audio_ahead_us,