|
|
|
|
@ -123,6 +123,17 @@ void stream_media_store_destroy(struct stream_media_store *s) {
|
|
|
|
|
u_free(s->positions); u_free(s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Новая цепочка начинается с KEY; старые reader увидят смену generation до выдачи новых данных. */ |
|
|
|
|
static int media_discontinuity(struct stream_media_store *s) { |
|
|
|
|
if (s->generation == UINT32_MAX) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media generation exhausted source=%llx", (unsigned long long)s->descriptor.source_node_id); |
|
|
|
|
s->failed = 1; return STREAM_ERR_FORMAT; |
|
|
|
|
} |
|
|
|
|
s->generation++; s->pending_used = 0; s->waiting_key = 1; |
|
|
|
|
s->video_us = s->audio_us = s->key_us = s->last_audio_us = -1; s->key_seq = UINT64_MAX; |
|
|
|
|
return STREAM_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Окончательный пропуск завершает старый непрерывный участок; следующая цепочка начинается с KEY. */ |
|
|
|
|
void stream_media_store_gap(struct stream_media_store *s, uint64_t first, uint64_t last) { |
|
|
|
|
if (!s || first > last || last == UINT64_MAX || s->generation == UINT32_MAX) { |
|
|
|
|
@ -132,8 +143,7 @@ void stream_media_store_gap(struct stream_media_store *s, uint64_t first, uint64
|
|
|
|
|
} |
|
|
|
|
media_append(s, (struct media_position){.first = first, .last = last, .generation = s->generation, .gap = 1, |
|
|
|
|
.video_us = s->video_us, .audio_us = s->audio_us, .key_seq = s->key_seq, .key_us = s->key_us}); |
|
|
|
|
s->generation++; s->pending_used = 0; s->waiting_key = 1; |
|
|
|
|
s->video_us = s->audio_us = s->key_us = s->last_audio_us = -1; s->key_seq = UINT64_MAX; |
|
|
|
|
media_discontinuity(s); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "media index GAP source=%llx first=%llu last=%llu generation=%u", |
|
|
|
|
(unsigned long long)s->descriptor.source_node_id, (unsigned long long)first, (unsigned long long)last, s->generation); |
|
|
|
|
} |
|
|
|
|
@ -148,23 +158,32 @@ int stream_media_store_feed(struct stream_media_store *s, uint64_t seq, const ui
|
|
|
|
|
struct stream_media_block b; |
|
|
|
|
int result = stream_media_parse(data, length, &b); |
|
|
|
|
if (result != STREAM_OK) { s->failed = 1; return result; } |
|
|
|
|
if (s->configured && (b.format.audio_codec != s->format.audio_codec || b.format.revision < s->format.revision)) { |
|
|
|
|
if (s->configured && (b.format.audio_codec != s->format.audio_codec || b.format.revision < s->format.revision |
|
|
|
|
|| (b.format.revision == s->format.revision && (b.format.width != s->format.width |
|
|
|
|
|| b.format.height != s->format.height || b.format.rotation != s->format.rotation)))) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media index conflicting format source=%llx revision=%u/%u audio=%u/%u", |
|
|
|
|
(unsigned long long)s->descriptor.source_node_id, s->format.revision, b.format.revision, |
|
|
|
|
s->format.audio_codec, b.format.audio_codec); s->failed = 1; return STREAM_ERR_CONFLICT; |
|
|
|
|
} |
|
|
|
|
if (s->configured && b.format.revision != s->format.revision) { s->pending_used = 0; s->waiting_key = 1; } |
|
|
|
|
s->format = b.format; s->configured = 1; |
|
|
|
|
int64_t audio_previous = s->last_audio_us; |
|
|
|
|
for (unsigned i = 0; i < b.audio_count; i++) { |
|
|
|
|
const struct stream_media_audio *a = &b.audio[i]; |
|
|
|
|
if (s->last_audio_us >= 0 && (int64_t)a->pts_us <= s->last_audio_us) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index stale audio seq=%llu pts=%llu", (unsigned long long)seq, (unsigned long long)a->pts_us); |
|
|
|
|
continue; |
|
|
|
|
int64_t pts = (int64_t)b.audio[i].pts_us; |
|
|
|
|
if (pts > INT64_MAX - 20000) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media audio end overflow seq=%llu pts=%lld", (unsigned long long)seq, (long long)pts); |
|
|
|
|
s->failed = 1; return STREAM_ERR_FORMAT; |
|
|
|
|
} |
|
|
|
|
s->last_audio_us = (int64_t)a->pts_us; s->audio_us = s->last_audio_us + 20000; |
|
|
|
|
if (audio_previous >= 0 && pts - audio_previous > 120000) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media audio discontinuity seq=%llu previous=%lld pts=%lld; require new KEY", |
|
|
|
|
(unsigned long long)seq, (long long)audio_previous, (long long)pts); |
|
|
|
|
if (media_discontinuity(s) != STREAM_OK) return STREAM_ERR_FORMAT; |
|
|
|
|
} |
|
|
|
|
if (pts > audio_previous) audio_previous = pts; |
|
|
|
|
} |
|
|
|
|
for (unsigned i = 0; i < b.video_count; i++) { |
|
|
|
|
const struct stream_media_video *v = &b.video[i]; |
|
|
|
|
if (s->waiting_key && !s->pending_used && v->offset) continue; |
|
|
|
|
if (!v->offset) { |
|
|
|
|
if (s->video_us >= 0 && ((int64_t)v->pts_us <= s->video_us || v->frame_id <= s->last_frame_id)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index stale video seq=%llu frame=%llu pts=%llu", |
|
|
|
|
@ -174,7 +193,7 @@ int stream_media_store_feed(struct stream_media_store *s, uint64_t seq, const ui
|
|
|
|
|
if (s->pending_used) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index incomplete frame=%llu before frame=%llu; require KEY", |
|
|
|
|
(unsigned long long)s->pending.frame_id, (unsigned long long)v->frame_id); |
|
|
|
|
s->pending_used = 0; s->waiting_key = 1; |
|
|
|
|
if (media_discontinuity(s) != STREAM_OK) return STREAM_ERR_FORMAT; |
|
|
|
|
} |
|
|
|
|
if (s->waiting_key && !(v->flags & STREAM_MEDIA_KEY)) continue; |
|
|
|
|
s->pending = *v; s->pending.data = NULL; s->pending_seq = seq; |
|
|
|
|
@ -184,13 +203,22 @@ int stream_media_store_feed(struct stream_media_store *s, uint64_t seq, const ui
|
|
|
|
|
|| v->frame_size != s->pending.frame_size || v->flags != s->pending.flags) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index frame discontinuity seq=%llu frame=%llu offset=%u expected=%u", |
|
|
|
|
(unsigned long long)seq, (unsigned long long)v->frame_id, v->offset, s->pending_used); |
|
|
|
|
s->pending_used = 0; s->waiting_key = 1; continue; |
|
|
|
|
if (media_discontinuity(s) != STREAM_OK) return STREAM_ERR_FORMAT; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
s->pending_used += v->length; |
|
|
|
|
if (s->pending_used != s->pending.frame_size) continue; |
|
|
|
|
s->video_us = (int64_t)v->pts_us; s->last_frame_id = v->frame_id; s->waiting_key = 0; s->pending_used = 0; |
|
|
|
|
if (v->flags & STREAM_MEDIA_KEY) { s->key_seq = s->pending_seq; s->key_us = (int64_t)v->pts_us; } |
|
|
|
|
} |
|
|
|
|
for (unsigned i = 0; i < b.audio_count; i++) { |
|
|
|
|
const struct stream_media_audio *a = &b.audio[i]; |
|
|
|
|
if (s->last_audio_us >= 0 && (int64_t)a->pts_us <= s->last_audio_us) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index stale audio seq=%llu pts=%llu", (unsigned long long)seq, (unsigned long long)a->pts_us); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
s->last_audio_us = (int64_t)a->pts_us; s->audio_us = s->last_audio_us + 20000; |
|
|
|
|
} |
|
|
|
|
media_append(s, (struct media_position){.first = seq, .last = seq, .generation = s->generation, |
|
|
|
|
.video_us = s->video_us, .audio_us = s->audio_us, .key_seq = s->key_seq, .key_us = s->key_us}); |
|
|
|
|
return STREAM_OK; |
|
|
|
|
@ -381,8 +409,10 @@ 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; |
|
|
|
|
} |
|
|
|
|
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; |
|
|
|
|
int64_t video_limit = video_ahead < 0 ? -1 : position < 0 ? c->start_us |
|
|
|
|
: position > INT64_MAX - video_ahead ? INT64_MAX : position + video_ahead; |
|
|
|
|
int64_t audio_limit = audio_ahead < 0 ? -1 : 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. */ |
|
|
|
|
|