Browse Source

Reseek on media discontinuities and bound disabled track timestamps

master
evgeny 7 days ago
parent
commit
3649e45087
  1. 54
      src/stream/stream_media_buffer.c
  2. 43
      tests/test_stream_playback.c

54
src/stream/stream_media_buffer.c

@ -123,6 +123,17 @@ void stream_media_store_destroy(struct stream_media_store *s) {
u_free(s->positions); u_free(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. */ /* Окончательный пропуск завершает старый непрерывный участок; следующая цепочка начинается с KEY. */
void stream_media_store_gap(struct stream_media_store *s, uint64_t first, uint64_t last) { 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) { 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, 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}); .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; media_discontinuity(s);
s->video_us = s->audio_us = s->key_us = s->last_audio_us = -1; s->key_seq = UINT64_MAX;
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "media index GAP source=%llx first=%llu last=%llu generation=%u", 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); (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; struct stream_media_block b;
int result = stream_media_parse(data, length, &b); int result = stream_media_parse(data, length, &b);
if (result != STREAM_OK) { s->failed = 1; return result; } 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", 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, (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; 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; } if (s->configured && b.format.revision != s->format.revision) { s->pending_used = 0; s->waiting_key = 1; }
s->format = b.format; s->configured = 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++) { for (unsigned i = 0; i < b.audio_count; i++) {
const struct stream_media_audio *a = &b.audio[i]; int64_t pts = (int64_t)b.audio[i].pts_us;
if (s->last_audio_us >= 0 && (int64_t)a->pts_us <= s->last_audio_us) { if (pts > INT64_MAX - 20000) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index stale audio seq=%llu pts=%llu", (unsigned long long)seq, (unsigned long long)a->pts_us); DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "media audio end overflow seq=%llu pts=%lld", (unsigned long long)seq, (long long)pts);
continue; 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++) { for (unsigned i = 0; i < b.video_count; i++) {
const struct stream_media_video *v = &b.video[i]; const struct stream_media_video *v = &b.video[i];
if (s->waiting_key && !s->pending_used && v->offset) continue;
if (!v->offset) { if (!v->offset) {
if (s->video_us >= 0 && ((int64_t)v->pts_us <= s->video_us || v->frame_id <= s->last_frame_id)) { 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", 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) { if (s->pending_used) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "media index incomplete frame=%llu before frame=%llu; require KEY", 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); (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; if (s->waiting_key && !(v->flags & STREAM_MEDIA_KEY)) continue;
s->pending = *v; s->pending.data = NULL; s->pending_seq = seq; 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) { || 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", 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); (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; s->pending_used += v->length;
if (s->pending_used != s->pending.frame_size) continue; 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; 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; } 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, 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}); .video_us = s->video_us, .audio_us = s->audio_us, .key_seq = s->key_seq, .key_us = s->key_us});
return STREAM_OK; 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) { if (c->audio.ended && c->video.ended) {
*packet = (struct stream_media_packet){.kind = STREAM_MEDIA_READ_END}; return STREAM_OK; *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 video_limit = video_ahead < 0 ? -1 : position < 0 ? c->start_us
int64_t audio_limit = position < 0 ? c->start_us : position > INT64_MAX - audio_ahead ? INT64_MAX : position + audio_ahead; : 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; 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)) { if (ar == STREAM_OK && audio_ahead >= 0 && audio_pts <= audio_limit && (audio_pts < video_pts || !video_allowed)) {
/* video peek мог переиспользовать scratch аудио: заново взять ту же пачку из двухэлементного cache. */ /* video peek мог переиспользовать scratch аудио: заново взять ту же пачку из двухэлементного cache. */

43
tests/test_stream_playback.c

@ -192,13 +192,54 @@ static void test_large_frame_budget(void) {
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] large frame: bounded MORE work, complete existing-ring assembly and video-only EOF"); DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] large frame: bounded MORE work, complete existing-ring assembly and video-only EOF");
} }
/* Повреждённая последовательность кадров не оставляет старую границу готовности и decoder references. */
static void test_media_discontinuity(void) {
uint8_t key[32] = {3}; struct stream_encoder *encoder = NULL; struct stream_buffer *buffer = NULL;
struct stream_media_store *store = NULL; struct stream_media_cursor *cursor = NULL, *fresh = NULL;
CHECK(stream_encoder_create(&encoder, &descriptor, key) == STREAM_OK);
CHECK(stream_buffer_create(&buffer, &descriptor, stream_encoder_public_key(encoder), 64 * 1300) == STREAM_OK);
CHECK(stream_media_store_create(&store, buffer, &descriptor) == STREAM_OK);
CHECK(stream_media_cursor_create(&cursor, store, 3) == STREAM_OK);
CHECK(stream_media_cursor_create(&fresh, store, 3) == STREAM_OK);
append_media(encoder, buffer, store, 0, 0, STREAM_MEDIA_KEY, 0);
struct stream_media_packet packet; struct stream_media_status status;
CHECK(stream_media_cursor_read(cursor, -1, 0, 0, &packet) == STREAM_OK);
struct stream_media_block media = {.format = {64, 64, 0, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, 1}, .video_count = 1};
media.video[0] = (struct stream_media_video){2, 40000, 256, 0, 128, 0, video_bytes};
uint8_t wire[1024]; size_t length = 0;
CHECK(stream_media_encode(&media, wire, sizeof(wire), &length) == STREAM_OK);
CHECK(stream_media_store_feed(store, 1, wire, length) == STREAM_OK);
append_media(encoder, buffer, store, 2, 80000, 0, 0);
CHECK(stream_media_cursor_status(cursor, &status) == STREAM_OK && status.video_us == 0);
CHECK(stream_media_cursor_status(fresh, &status) == STREAM_OK && !status.initialized);
stream_media_cursor_reset(cursor);
append_media(encoder, buffer, store, 3, 120000, STREAM_MEDIA_KEY, 0);
CHECK(stream_media_cursor_status(cursor, &status) == STREAM_OK && status.generation == 2 && status.start_us == 120000);
append_media(encoder, buffer, store, 4, 400000, 0, 0);
stream_media_cursor_reset(cursor);
CHECK(stream_media_cursor_status(cursor, &status) == STREAM_OK && !status.initialized);
append_media(encoder, buffer, store, 5, 440000, STREAM_MEDIA_KEY, 0);
CHECK(stream_media_cursor_status(cursor, &status) == STREAM_OK && status.generation == 3 && status.start_us == 440000);
media.format.rotation = 90; media.video[0].frame_id = 7; media.video[0].pts_us = 480000;
CHECK(stream_media_encode(&media, wire, sizeof(wire), &length) == STREAM_OK);
CHECK(stream_media_store_feed(store, 6, wire, length) == STREAM_ERR_CONFLICT);
stream_media_cursor_destroy(cursor); stream_media_cursor_destroy(fresh); stream_media_store_destroy(store); store = NULL;
CHECK(stream_media_store_create(&store, buffer, &descriptor) == STREAM_OK);
media.video_count = 0; media.audio_count = 1;
media.audio[0] = (struct stream_media_audio){INT64_MAX, sizeof(opus_bytes), 960, opus_bytes};
CHECK(stream_media_encode(&media, wire, sizeof(wire), &length) == STREAM_OK);
CHECK(stream_media_store_feed(store, 0, wire, length) == STREAM_ERR_FORMAT);
stream_media_store_destroy(store); stream_buffer_destroy(buffer); stream_encoder_destroy(encoder);
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] media discontinuity: incomplete video and audio gap require KEY; format and PTS bounds enforced");
}
int main(void) { int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_INFO);
size_t allocations = u_get_allocated_count(); size_t allocations = u_get_allocated_count();
for (unsigned i = 0; i < sizeof(video_bytes); i++) video_bytes[i] = (uint8_t)(i * 71 + 3); for (unsigned i = 0; i < sizeof(video_bytes); i++) video_bytes[i] = (uint8_t)(i * 71 + 3);
test_history_correction(); test_catchup_device_queue(); test_invalid_and_sampling_gap(); test_shared_media_ring(); test_history_correction(); test_catchup_device_queue(); test_invalid_and_sampling_gap(); test_shared_media_ring();
test_large_frame_budget(); test_large_frame_budget(); test_media_discontinuity();
CHECK(u_get_allocated_count() == allocations); CHECK(u_get_allocated_count() == allocations);
return 0; return 0;
} }

Loading…
Cancel
Save