Browse Source

Give audio device lead and video decode lead independent read limits

master
evgeny 6 days ago
parent
commit
866ca0923d
  1. 35
      src/stream/stream_media_buffer.c
  2. 8
      src/stream/stream_media_buffer.h

35
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;
}

8
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

Loading…
Cancel
Save