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