From b2645aed9d22e3560b49d3a50b99d8b3c66eb477 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 13 Oct 2026 21:56:10 +0200 Subject: [PATCH] Add a shared playback reserve clock with corrected minute history --- src/stream/stream_playback_clock.c | 101 +++++++++++++++++++++++++++++ src/stream/stream_playback_clock.h | 46 +++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/stream/stream_playback_clock.c create mode 100644 src/stream/stream_playback_clock.h diff --git a/src/stream/stream_playback_clock.c b/src/stream/stream_playback_clock.c new file mode 100644 index 00000000..8b2d51e3 --- /dev/null +++ b/src/stream/stream_playback_clock.c @@ -0,0 +1,101 @@ +#include + +#include "../../lib/debug_config.h" +#include "stream_playback_clock.h" + +/* Новая временная шкала не наследует минутные минимумы прежней позиции. */ +void stream_playback_clock_reset(struct stream_playback_clock *c, uint64_t id) { + if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback clock reset: null clock id=%llu", (unsigned long long)id); return; } + memset(c, 0, sizeof(*c)); + c->id = id; c->tempo_milli = 1000; + for (unsigned i = 0; i < STREAM_PLAYBACK_HISTORY; i++) c->history[i].second = -1; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "playback clock reset id=%llu history=60s tempo=1.0..1.1", (unsigned long long)id); +} + +/* Смещение обновляется по реально воспроизведённому материалу, а не по запрошенному темпу. */ +int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, int64_t position, + int64_t ready, int64_t guard, int64_t pending_gain, + int starved, struct stream_playback_state *state) { + if (!c || !state || now < 0 || position < 0 || ready < 0 || guard < 0 || guard > 1000000 + || pending_gain < 0 || pending_gain > 1000000 || (c->initialized && (now < c->last_us || position < c->last_position_us))) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback clock invalid id=%llu now=%lld pts=%lld ready=%lld guard=%lld pending=%lld", + c ? (unsigned long long)c->id : 0, (long long)now, (long long)position, (long long)ready, + (long long)guard, (long long)pending_gain); + return -1; + } + int64_t elapsed = c->initialized ? now - c->last_us : 0; + if (!c->initialized) { c->initialized = 1; c->first_us = c->last_bad_us = now; } + else { + int64_t advanced = position - c->last_position_us; + if (advanced > elapsed * 2 + 1000000) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback clock position discontinuity id=%llu wall=%lld media=%lld; reset required", + (unsigned long long)c->id, (long long)elapsed, (long long)advanced); + return -1; + } + c->correction_us += elapsed - advanced; + if (elapsed > 1000000) { + c->last_bad_us = now; + DEBUG_WARN(DEBUG_CATEGORY_STREAM, "playback clock sampling gap id=%llu interval_us=%lld; restart healthy minute", + (unsigned long long)c->id, (long long)elapsed); + } + } + if (starved || ready < position) { + c->last_bad_us = now; + if (!c->waiting) { + c->pause_us = now; c->pauses++; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "playback waits id=%llu pts=%lld ready=%lld pauses=%llu", + (unsigned long long)c->id, (long long)position, (long long)ready, (unsigned long long)c->pauses); + } + c->waiting = 1; + } else if (c->waiting) { + c->last_bad_us = now; c->waiting = 0; + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "playback resumes id=%llu pause_us=%lld reserve_us=%lld correction_us=%lld", + (unsigned long long)c->id, (long long)(now - c->pause_us), + (long long)(ready - position), (long long)c->correction_us); + } + int64_t second = now / 1000000, reserve = ready - position; + struct stream_playback_bucket *bucket = &c->history[second % STREAM_PLAYBACK_HISTORY]; + int64_t normalized = reserve - c->correction_us; + if (bucket->second != second) { bucket->second = second; bucket->minimum_us = normalized; } + else if (normalized < bucket->minimum_us) bucket->minimum_us = normalized; + int64_t minimum = normalized; + unsigned samples = 0; + for (unsigned i = 0; i < STREAM_PLAYBACK_HISTORY; i++) { + const struct stream_playback_bucket *b = &c->history[i]; + if (b->second < 0 || b->second > second || second - b->second >= STREAM_PLAYBACK_HISTORY) continue; + if (b->minimum_us < minimum) minimum = b->minimum_us; + samples++; + } + minimum += c->correction_us; + int64_t stable = now - c->last_bad_us; + int complete = samples == STREAM_PLAYBACK_HISTORY && now - c->first_us >= 60000000; + int64_t removable = minimum - guard - pending_gain; + if (removable < 0) removable = 0; + /* Торможение начинается с учётом будущего расхода при плавном спаде 1.1 -> 1.0. */ + int excess = c->tempo_milli - 1000; + int64_t braking = (int64_t)excess * excess * STREAM_PLAYBACK_RAMP_US / 200000; + int target = 1000; + if (complete && stable >= 60000000 && !c->waiting && removable > braking) { + int64_t allowance = removable - braking; + int increase = allowance >= 50000 ? 100 : (int)(allowance * 100 / 50000); + target += increase; + } + int step = (int)(elapsed > STREAM_PLAYBACK_RAMP_US ? 100 : elapsed * 100 / STREAM_PLAYBACK_RAMP_US); + if (target > c->tempo_milli) c->tempo_milli += target - c->tempo_milli < step ? target - c->tempo_milli : step; + else c->tempo_milli -= c->tempo_milli - target < step ? c->tempo_milli - target : step; + if (c->waiting) c->tempo_milli = 1000; + struct stream_playback_state result = {.position_us = position, .reserve_us = reserve, .minimum_us = minimum, + .correction_us = c->correction_us, .stable_us = stable, .removable_us = removable, + .tempo_milli = c->tempo_milli, .waiting = c->waiting, .history_ready = complete}; + if (!c->report_us || now - c->report_us >= 1000000) { + DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, + "playback reserve id=%llu pts=%lld ready=%lld reserve=%lld minimum=%lld correction=%lld " + "stable_ms=%lld history=%u tempo=%d pending_gain=%lld waiting=%d", + (unsigned long long)c->id, (long long)position, (long long)ready, (long long)reserve, + (long long)minimum, (long long)c->correction_us, (long long)(stable / 1000), samples, + c->tempo_milli, (long long)pending_gain, c->waiting); + c->report_us = now; + } + c->last_us = now; c->last_position_us = position; *state = result; + return 0; +} diff --git a/src/stream/stream_playback_clock.h b/src/stream/stream_playback_clock.h new file mode 100644 index 00000000..21741678 --- /dev/null +++ b/src/stream/stream_playback_clock.h @@ -0,0 +1,46 @@ +/* Общая адаптация воспроизведения desktop/Android, без сетевых ресурсов и очередей медиа. + * Один поток либо внешняя блокировка. position_us — фактические общие часы исходного материала; + * ready_us — непрерывная готовая граница, включая данные в кольце/кодеке/устройстве. + * Шестьдесят секундных минимумов нормализованы по смещению wall-clock относительно media-clock: + * простой увеличивает все прежние запасы, догон уменьшает их без переписывания истории. + * Ускорение до 1.1 разрешено после полной минуты без нехватки/пропуска наблюдений. + * guard_us и уже поставленный устройству pending_gain_us защищают остаток при снижении темпа. + * reset нужен при новой эпохе/реальном GAP/смене позиции; временная нехватка историю сохраняет. */ +#ifndef STREAM_PLAYBACK_CLOCK_H +#define STREAM_PLAYBACK_CLOCK_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define STREAM_PLAYBACK_HISTORY 60 +#define STREAM_PLAYBACK_MAX_TEMPO 1100 +#define STREAM_PLAYBACK_RAMP_US 500000 + +struct stream_playback_bucket { int64_t second, minimum_us; }; +struct stream_playback_clock { + struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY]; + uint64_t id, pauses; + int64_t first_us, last_us, last_position_us, correction_us, last_bad_us, pause_us, report_us; + int tempo_milli, initialized, waiting; +}; + +struct stream_playback_state { + int64_t position_us, reserve_us, minimum_us, correction_us, stable_us, removable_us; + int tempo_milli, waiting, history_ready; +}; + +void stream_playback_clock_reset(struct stream_playback_clock *clock, uint64_t id); +/* Результат не меняется при ошибке. starved отмечает фактическую остановку подготовки/вывода. + * Временное уменьшение фактического position_us запрещено; после перемотки вызывают reset. + * Возврат 0 — успех, отрицательный — неверные аргументы/часы. */ +int stream_playback_clock_update(struct stream_playback_clock *clock, int64_t now_us, int64_t position_us, + int64_t ready_us, int64_t guard_us, int64_t pending_gain_us, + int starved, struct stream_playback_state *state); + +#ifdef __cplusplus +} +#endif +#endif