Recover stream playback stalls with bounded proportional catchup
This commit is contained in:
1 parent
58c0fc6bba
commit
6c619d5f2a
3 files changed
+58
-58
No files matched your search
@@ -3,13 +3,13 @@
|
||||
#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);
|
||||
for (unsigned i = 0; i < STREAM_PLAYBACK_HISTORY; i++) c->history[i].period = -1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "playback clock reset id=%llu history=200ms tempo=1.0..1.6", (unsigned long long)id);
|
||||
}
|
||||
|
||||
/* Смещение обновляется по реально воспроизведённому материалу, а не по запрошенному темпу. */
|
||||
@@ -35,7 +35,7 @@ int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, i
|
||||
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",
|
||||
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "playback clock sampling gap id=%llu interval_us=%lld; restart observation window",
|
||||
(unsigned long long)c->id, (long long)elapsed);
|
||||
}
|
||||
}
|
||||
@@ -53,34 +53,35 @@ int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, i
|
||||
(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 period = now / STREAM_PLAYBACK_BUCKET_US, reserve = ready - position;
|
||||
struct stream_playback_bucket *bucket = &c->history[period % STREAM_PLAYBACK_HISTORY];
|
||||
int64_t normalized = reserve - c->correction_us;
|
||||
if (bucket->second != second) { bucket->second = second; bucket->minimum_us = normalized; }
|
||||
if (bucket->period != period) { bucket->period = period; 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->period < 0 || b->period > period || period - b->period >= 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;
|
||||
int complete = samples == STREAM_PLAYBACK_HISTORY && now - c->first_us >= STREAM_PLAYBACK_SETTLE_US;
|
||||
int64_t removable = minimum - guard - pending_gain;
|
||||
if (removable < 0) removable = 0;
|
||||
/* Торможение начинается с учётом будущего расхода при плавном спаде 1.1 -> 1.0. */
|
||||
/* Уже подготовленный PCM и тормозной путь не должны съесть защищённый запас. */
|
||||
int excess = c->tempo_milli - 1000;
|
||||
int64_t braking = (int64_t)excess * excess * STREAM_PLAYBACK_RAMP_US / 200000;
|
||||
int range = STREAM_PLAYBACK_MAX_TEMPO - 1000;
|
||||
int64_t braking = (int64_t)excess * excess * STREAM_PLAYBACK_RAMP_US / (2 * range * 1000);
|
||||
int target = 1000;
|
||||
if (complete && stable >= 60000000 && !c->waiting && removable > braking) {
|
||||
if (complete && stable >= STREAM_PLAYBACK_SETTLE_US && !c->waiting && removable > braking) {
|
||||
int64_t allowance = removable - braking;
|
||||
int increase = allowance >= 50000 ? 100 : (int)(allowance * 100 / 50000);
|
||||
int increase = allowance >= range * 2000 ? range : (int)(allowance / 2000);
|
||||
target += increase;
|
||||
}
|
||||
c->ramp_remainder += (elapsed > STREAM_PLAYBACK_RAMP_US ? STREAM_PLAYBACK_RAMP_US : elapsed) * 100;
|
||||
c->ramp_remainder += (elapsed > STREAM_PLAYBACK_RAMP_US ? STREAM_PLAYBACK_RAMP_US : elapsed) * range;
|
||||
int step = (int)(c->ramp_remainder / STREAM_PLAYBACK_RAMP_US);
|
||||
c->ramp_remainder %= STREAM_PLAYBACK_RAMP_US;
|
||||
if (target > c->tempo_milli) c->tempo_milli += target - c->tempo_milli < step ? target - c->tempo_milli : step;
|
||||
@@ -90,9 +91,9 @@ int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, i
|
||||
.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) {
|
||||
const char *reason = c->waiting ? "starved" : !complete ? "history" : stable < 60000000 ? "cooldown"
|
||||
const char *reason = c->waiting ? "starved" : !complete ? "history" : stable < STREAM_PLAYBACK_SETTLE_US ? "cooldown"
|
||||
: removable <= braking ? "reserve" : "catchup";
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM,
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
|
||||
"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 "
|
||||
"guard_us=%lld removable_us=%lld braking_us=%lld reason=%s",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* Общая адаптация воспроизведения desktop/Android, без сетевых ресурсов и очередей медиа.
|
||||
* Один поток либо внешняя блокировка. position_us — фактические общие часы исходного материала;
|
||||
* ready_us — непрерывная готовая граница, включая данные в кольце/кодеке/устройстве.
|
||||
* Шестьдесят секундных минимумов нормализованы по смещению wall-clock относительно media-clock:
|
||||
* Минимумы короткого окна нормализованы по смещению wall-clock относительно media-clock:
|
||||
* простой увеличивает все прежние запасы, догон уменьшает их без переписывания истории.
|
||||
* Ускорение до 1.1 разрешено после полной минуты без нехватки/пропуска наблюдений.
|
||||
* После 200 мс без фактической недостачи избыток погашается плавным ускорением до 1.6.
|
||||
* guard_us и уже поставленный устройству pending_gain_us защищают остаток при снижении темпа.
|
||||
* reset нужен при новой эпохе/реальном GAP/смене позиции; временная нехватка историю сохраняет. */
|
||||
#ifndef STREAM_PLAYBACK_CLOCK_H
|
||||
@@ -15,11 +15,13 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define STREAM_PLAYBACK_HISTORY 60
|
||||
#define STREAM_PLAYBACK_MAX_TEMPO 1100
|
||||
#define STREAM_PLAYBACK_HISTORY 4
|
||||
#define STREAM_PLAYBACK_BUCKET_US 50000
|
||||
#define STREAM_PLAYBACK_SETTLE_US 200000
|
||||
#define STREAM_PLAYBACK_MAX_TEMPO 1600
|
||||
#define STREAM_PLAYBACK_RAMP_US 500000
|
||||
|
||||
struct stream_playback_bucket { int64_t second, minimum_us; };
|
||||
struct stream_playback_bucket { int64_t period, minimum_us; };
|
||||
struct stream_playback_clock {
|
||||
struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY];
|
||||
uint64_t id, pauses;
|
||||
|
||||
Reference in new issue
Block a user