From 866ca0923dee2884458c07593f1910a4744e0311 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 13 Oct 2026 22:30:47 +0200 Subject: [PATCH] Give audio device lead and video decode lead independent read limits --- src/stream/stream_media_buffer.c | 35 +++++++++++++++++++++----------- src/stream/stream_media_buffer.h | 8 +++++--- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/stream/stream_media_buffer.c b/src/stream/stream_media_buffer.c index 14d2e477..d5d441d8 100644 --- a/src/stream/stream_media_buffer.c +++ b/src/stream/stream_media_buffer.c @@ -32,6 +32,7 @@ struct stream_media_cursor { struct cursor_track video, audio; uint32_t generation; int initialized, key_delivered; + unsigned tracks; int64_t start_us; uint8_t *scratch[2], *frame; size_t scratch_capacity, frame_capacity; @@ -201,11 +202,14 @@ void stream_media_store_finish(struct stream_media_store *s) { } /* Курсор хранит только собственные рабочие буферы, не копию накопленного медиапотока. */ -int stream_media_cursor_create(struct stream_media_cursor **out, struct stream_media_store *s) { - if (!out || *out || !s) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor create invalid arguments"); return STREAM_ERR_ARGUMENT; } +int stream_media_cursor_create(struct stream_media_cursor **out, struct stream_media_store *s, unsigned tracks) { + if (!out || *out || !s || !(tracks & STREAM_MEDIA_TRACK_VIDEO) || (tracks & ~3u)) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor create invalid arguments tracks=%u", tracks); return STREAM_ERR_ARGUMENT; + } struct stream_media_cursor *c = u_calloc(1, sizeof(*c)); if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor allocation failed"); return STREAM_ERR_MEMORY; } - c->store = s; c->scratch_capacity = stream_input_size(&s->descriptor); + c->store = s; c->tracks = tracks; c->scratch_capacity = stream_input_size(&s->descriptor); + c->audio.ended = !(tracks & STREAM_MEDIA_TRACK_AUDIO); if (c->scratch_capacity > STREAM_MEDIA_MAX_BLOCK) c->scratch_capacity = STREAM_MEDIA_MAX_BLOCK; for (unsigned i = 0; i < 2; i++) c->scratch[i] = u_malloc((uint32_t)c->scratch_capacity); if (!c->scratch[0] || !c->scratch[1]) { @@ -228,6 +232,7 @@ void stream_media_cursor_reset(struct stream_media_cursor *c) { c->initialized = c->key_delivered = 0; c->pending_used = 0; c->scratch_valid[0] = c->scratch_valid[1] = 0; c->video = (struct cursor_track){0}; c->audio = (struct cursor_track){0}; + c->audio.ended = !(c->tracks & STREAM_MEDIA_TRACK_AUDIO); } /* Выбирается последний полный KEY; тёплая история аудио перед ним остаётся в том же RS-кольце. */ @@ -258,7 +263,7 @@ int stream_media_cursor_status(struct stream_media_cursor *c, struct stream_medi struct stream_media_store *s = c->store; int result = cursor_start(c); struct stream_media_status status = {.ready_us = -1, .video_us = -1, .audio_us = -1, .start_us = -1, - .audio_enabled = s->configured && s->format.audio_codec, .finished = s->finished}; + .audio_enabled = s->configured && s->format.audio_codec && (c->tracks & STREAM_MEDIA_TRACK_AUDIO), .finished = s->finished}; if (result < 0) return result; if (c->initialized) { for (unsigned i = s->count; i-- > 0;) { @@ -268,6 +273,8 @@ int stream_media_cursor_status(struct stream_media_cursor *c, struct stream_medi } status.ready_us = status.video_us; if (status.audio_enabled && status.audio_us < status.ready_us) status.ready_us = status.audio_us; + if (s->finished && c->video.ended && status.audio_enabled) status.ready_us = status.audio_us; + status.video_ended = c->video.ended; status.audio_ended = c->audio.ended; status.start_us = c->start_us; status.generation = c->generation; status.initialized = 1; } *out = status; @@ -351,9 +358,11 @@ static int cursor_video(struct stream_media_cursor *c, struct stream_media_packe } /* Выдача по времени потребителя оставляет накопленный запас в общем кольце. */ -int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, int64_t lookahead, struct stream_media_packet *packet) { - if (!c || !packet || lookahead < 0 || lookahead > 1000000) { - DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor read invalid arguments ahead=%lld", (long long)lookahead); return STREAM_ERR_ARGUMENT; +int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, int64_t video_ahead, int64_t audio_ahead, + struct stream_media_packet *packet) { + if (!c || !packet || video_ahead < -1 || video_ahead > 60000000 || audio_ahead < -1 || audio_ahead > 60000000) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media cursor read invalid arguments ahead=%lld/%lld", + (long long)video_ahead, (long long)audio_ahead); return STREAM_ERR_ARGUMENT; } int result = cursor_start(c); if (result != STREAM_OK) { @@ -363,17 +372,19 @@ int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, in return result; } struct stream_media_block audio, video; int64_t audio_pts = INT64_MAX, video_pts = INT64_MAX; - int ar = c->store->format.audio_codec ? cursor_peek(c, &c->audio, 0, &audio, &audio_pts) : STREAM_GAP; if (!c->store->format.audio_codec) c->audio.ended = 1; - int vr = cursor_peek(c, &c->video, 1, &video, &video_pts); + int ar = c->audio.ended ? STREAM_GAP : audio_ahead < 0 ? STREAM_WAIT : cursor_peek(c, &c->audio, 0, &audio, &audio_pts); + int vr = video_ahead < 0 ? STREAM_WAIT : cursor_peek(c, &c->video, 1, &video, &video_pts); if ((ar == STREAM_GAP && !c->audio.ended) || (vr == STREAM_GAP && !c->video.ended)) return STREAM_GAP; if (ar == STREAM_EXPIRED || vr == STREAM_EXPIRED) return STREAM_EXPIRED; if (ar < 0 || vr < 0) return ar < 0 ? ar : vr; if (c->audio.ended && c->video.ended) { *packet = (struct stream_media_packet){.kind = STREAM_MEDIA_READ_END}; return STREAM_OK; } - int64_t limit = position < 0 ? c->start_us : position > INT64_MAX - lookahead ? INT64_MAX : position + lookahead; - if (ar == STREAM_OK && audio_pts <= limit && (audio_pts < video_pts || vr != STREAM_OK)) { + int64_t video_limit = position < 0 ? c->start_us : position > INT64_MAX - video_ahead ? INT64_MAX : position + video_ahead; + int64_t audio_limit = position < 0 ? c->start_us : position > INT64_MAX - audio_ahead ? INT64_MAX : position + audio_ahead; + int video_allowed = vr == STREAM_OK && video_ahead >= 0 && video_pts <= video_limit; + if (ar == STREAM_OK && audio_ahead >= 0 && audio_pts <= audio_limit && (audio_pts < video_pts || !video_allowed)) { /* video peek мог переиспользовать scratch аудио: заново взять ту же пачку из двухэлементного cache. */ result = cursor_block(c, c->audio.seq, &audio); if (result != STREAM_OK) return result; @@ -382,6 +393,6 @@ int stream_media_cursor_read(struct stream_media_cursor *c, int64_t position, in *packet = (struct stream_media_packet){.kind = STREAM_MEDIA_READ_AUDIO, .format = audio.format, .audio = a}; return STREAM_OK; } - if (vr == STREAM_OK && video_pts <= limit) return cursor_video(c, packet); + if (video_allowed) return cursor_video(c, packet); return STREAM_WAIT; } diff --git a/src/stream/stream_media_buffer.h b/src/stream/stream_media_buffer.h index 0e22681c..2d107fd5 100644 --- a/src/stream/stream_media_buffer.h +++ b/src/stream/stream_media_buffer.h @@ -18,6 +18,8 @@ extern "C" { #define STREAM_MEDIA_INDEX_BYTES (4u * 1024u * 1024u) #define STREAM_MEDIA_READ_AHEAD_US 80000 #define STREAM_MEDIA_PREROLL_US 500000 +#define STREAM_MEDIA_TRACK_VIDEO 1 +#define STREAM_MEDIA_TRACK_AUDIO 2 struct stream_media_store; struct stream_media_cursor; @@ -31,7 +33,7 @@ struct stream_media_packet { struct stream_media_status { int64_t ready_us, video_us, audio_us, start_us; uint64_t generation; - int initialized, audio_enabled, finished; + int initialized, audio_enabled, finished, video_ended, audio_ended; }; int stream_media_store_create(struct stream_media_store **out, struct stream_buffer *buffer, const struct stream_descriptor *descriptor); @@ -40,14 +42,14 @@ int stream_media_store_feed(struct stream_media_store *store, uint64_t seq, cons void stream_media_store_gap(struct stream_media_store *store, uint64_t first, uint64_t last); void stream_media_store_finish(struct stream_media_store *store); void stream_media_store_trim(struct stream_media_store *store); -int stream_media_cursor_create(struct stream_media_cursor **out, struct stream_media_store *store); +int stream_media_cursor_create(struct stream_media_cursor **out, struct stream_media_store *store, unsigned tracks); void stream_media_cursor_destroy(struct stream_media_cursor *cursor); 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); /* position_us<0 — старт: прогреть аудио перед выбранным KEY. Затем отдавать только до position_us+lookahead_us. * При GAP/EXPIRED cursor сбрасывают отдельно: это позволяет frontend сначала обработать разрыв. * Бюджет одного вызова ограничен; WAIT при сборке длинного кадра допускает повтор сразу/по следующему событию. */ -int stream_media_cursor_read(struct stream_media_cursor *cursor, int64_t position_us, int64_t lookahead_us, +int stream_media_cursor_read(struct stream_media_cursor *cursor, int64_t position_us, int64_t video_ahead_us, int64_t audio_ahead_us, struct stream_media_packet *packet); #ifdef __cplusplus