108 lines
6.5 KiB
C
108 lines
6.5 KiB
C
#include <string.h>
|
|
|
|
#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 > 60000000
|
|
|| pending_gain < 0 || pending_gain > 60000000 || (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;
|
|
}
|
|
c->ramp_remainder += (elapsed > STREAM_PLAYBACK_RAMP_US ? STREAM_PLAYBACK_RAMP_US : elapsed) * 100;
|
|
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;
|
|
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) {
|
|
const char *reason = c->waiting ? "starved" : !complete ? "history" : stable < 60000000 ? "cooldown"
|
|
: removable <= braking ? "reserve" : "catchup";
|
|
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 "
|
|
"guard_us=%lld removable_us=%lld braking_us=%lld reason=%s",
|
|
(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,
|
|
(long long)guard, (long long)removable, (long long)braking, reason);
|
|
c->report_us = now;
|
|
}
|
|
c->last_us = now; c->last_position_us = position; *state = result;
|
|
return 0;
|
|
}
|