3 changed files with 144 additions and 0 deletions
@ -0,0 +1,102 @@ |
|||||||
|
#include "call_video_playout.h" |
||||||
|
#include "../../lib/debug_config.h" |
||||||
|
#include "../../lib/u_async.h" |
||||||
|
#include <string.h> |
||||||
|
#include <time.h> |
||||||
|
#include <limits.h> |
||||||
|
|
||||||
|
#define VIDEO_LATE_US 50000 |
||||||
|
#define VIDEO_AHEAD_US 40000 |
||||||
|
|
||||||
|
int64_t call_video_playout_now_us(void) { |
||||||
|
#ifdef _WIN32 |
||||||
|
return (int64_t)get_time_us(); |
||||||
|
#else |
||||||
|
struct timespec now; |
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now); |
||||||
|
return (int64_t)now.tv_sec * 1000000 + now.tv_nsec / 1000; |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void call_video_playout_init(struct call_video_playout *p, uint64_t call_id) { |
||||||
|
memset(p, 0, sizeof(*p)); p->call_id = call_id; p->last_pts = -1; |
||||||
|
p->interval_min = p->av_min = INT64_MAX; p->av_max = INT64_MIN; |
||||||
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video playout init call=%llu speed=1.04 av_us=-50000..40000", (unsigned long long)call_id); |
||||||
|
} |
||||||
|
|
||||||
|
/* 25/26 без накопления времени поздних вызовов и без переполнения delta*25. */ |
||||||
|
static int64_t interval(int64_t delta) { return delta / 26 * 25 + delta % 26 * 25 / 26; } |
||||||
|
|
||||||
|
/* Аудио ограничивает фазу, а не квантует каждый кадр на 20мс. Проекция на 1x лишь
|
||||||
|
* назначает срок следующей проверки; при SHOW границы проверяются по свежему audio PTS. */ |
||||||
|
static int64_t bounded(int64_t deadline, int64_t pts, int64_t now, int64_t audio) { |
||||||
|
int64_t earliest = now + (pts - audio) - VIDEO_AHEAD_US, latest = now + (pts - audio) + VIDEO_LATE_US; |
||||||
|
if (deadline < earliest) return earliest; |
||||||
|
if (deadline > latest) return latest; |
||||||
|
return deadline; |
||||||
|
} |
||||||
|
|
||||||
|
/* Сводка агрегирует штатные ожидания и пропуски; per-frame spam не нужен. */ |
||||||
|
static void report(struct call_video_playout *p, int64_t now) { |
||||||
|
if (!p->report_us) p->report_us = now; |
||||||
|
if (now - p->report_us < 1000000) return; |
||||||
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, |
||||||
|
"video playout call=%llu interval_us=%lld shown=%u drop_late=%u drop_schedule=%u drop_order=%u " |
||||||
|
"cadence_us=%lld..%lld av_us=%lld..%lld dispatch_max_us=%lld data_shift_us=%lld audio_wait=%u clock_wait=%u", |
||||||
|
(unsigned long long)p->call_id, (long long)(now - p->report_us), p->shown, p->late, p->skipped, p->order, |
||||||
|
(long long)(p->interval_min == INT64_MAX ? 0 : p->interval_min), (long long)p->interval_max, |
||||||
|
(long long)(p->shown ? p->av_min : 0), (long long)(p->shown ? p->av_max : 0), |
||||||
|
(long long)p->dispatch_max, (long long)p->data_shift, p->audio_wait, p->clock_wait); |
||||||
|
p->report_us = now; p->shown = p->late = p->skipped = p->order = p->audio_wait = p->clock_wait = 0; |
||||||
|
p->interval_min = p->av_min = INT64_MAX; p->av_max = INT64_MIN; |
||||||
|
p->interval_max = p->dispatch_max = p->data_shift = 0; |
||||||
|
} |
||||||
|
|
||||||
|
enum call_video_playout_action call_video_playout_poll(struct call_video_playout *p, |
||||||
|
const struct call_video_playout_frame *f, const struct call_video_playout_frame *next, |
||||||
|
int64_t now, int audio_valid, int64_t audio) { |
||||||
|
report(p, now); p->deadline_us = now + 5000; |
||||||
|
if (p->audio_valid != !!audio_valid) { |
||||||
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video playout clock call=%llu valid=%d pts=%lld", |
||||||
|
(unsigned long long)p->call_id, !!audio_valid, (long long)audio); |
||||||
|
p->audio_valid = !!audio_valid; |
||||||
|
} |
||||||
|
if (!audio_valid) { p->anchored = 0; p->clock_wait++; return CALL_VIDEO_PLAYOUT_WAIT; } |
||||||
|
if (!f) return CALL_VIDEO_PLAYOUT_WAIT; |
||||||
|
if (f->pts_us <= p->last_pts || f->received_us < 0 || f->received_us > now) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video playout invalid/order call=%llu pts=%lld last=%lld received=%lld now=%lld", |
||||||
|
(unsigned long long)p->call_id, (long long)f->pts_us, (long long)p->last_pts, |
||||||
|
(long long)f->received_us, (long long)now); |
||||||
|
p->order++; return CALL_VIDEO_PLAYOUT_DROP_ORDER; |
||||||
|
} |
||||||
|
int64_t planned = p->anchored ? p->last_deadline + interval(f->pts_us - p->last_pts) : f->received_us; |
||||||
|
int64_t available = planned > f->received_us ? planned : f->received_us; |
||||||
|
int64_t deadline = bounded(available, f->pts_us, now, audio), av = f->pts_us - audio; |
||||||
|
p->deadline_us = deadline; |
||||||
|
enum call_video_playout_action action = CALL_VIDEO_PLAYOUT_SHOW; |
||||||
|
if (av < -VIDEO_LATE_US) action = CALL_VIDEO_PLAYOUT_DROP_LATE; |
||||||
|
else if (deadline > now) { |
||||||
|
if (av > VIDEO_AHEAD_US) p->audio_wait++; |
||||||
|
return CALL_VIDEO_PLAYOUT_WAIT; |
||||||
|
} else if (next && next->pts_us > f->pts_us && next->received_us <= now) { |
||||||
|
int64_t due = deadline + interval(next->pts_us - f->pts_us); |
||||||
|
if (due < next->received_us) due = next->received_us; |
||||||
|
if (bounded(due, next->pts_us, now, audio) <= now) action = CALL_VIDEO_PLAYOUT_DROP_SCHEDULE; |
||||||
|
} |
||||||
|
p->last_pts = f->pts_us; p->last_deadline = deadline; p->anchored = 1; |
||||||
|
if (available > planned) p->data_shift += available - planned; |
||||||
|
if (action == CALL_VIDEO_PLAYOUT_DROP_LATE) p->late++; |
||||||
|
else if (action == CALL_VIDEO_PLAYOUT_DROP_SCHEDULE) p->skipped++; |
||||||
|
else { |
||||||
|
if (now - deadline > p->dispatch_max) p->dispatch_max = now - deadline; |
||||||
|
if (av < p->av_min) p->av_min = av; |
||||||
|
if (av > p->av_max) p->av_max = av; |
||||||
|
if (p->last_show_us) { |
||||||
|
int64_t gap = now - p->last_show_us; |
||||||
|
if (gap < p->interval_min) p->interval_min = gap; |
||||||
|
if (gap > p->interval_max) p->interval_max = gap; |
||||||
|
} |
||||||
|
p->last_show_us = now; p->shown++; |
||||||
|
} |
||||||
|
return action; |
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
/* Общий таймер показа видео, один владелец — RX worker. Изображениями не владеет.
|
||||||
|
* poll вызывается для первого готового decoded кадра; next — следующий готовый кадр или NULL. |
||||||
|
* SHOW/DROP потребляют первый кадр, WAIT сохраняет его. Подача decoder продолжается независимо от WAIT. |
||||||
|
* received_us ставится на входе RX port, до ожидания worker; все локальные времена — MONOTONIC, не BOOTTIME. |
||||||
|
* Поздний poll не сдвигает расписание. Сдвиги разрешены только доступностью данных и границами A/V. |
||||||
|
* Невалидные аудиочасы снимают привязку, но не очищают decoder/очередь и не меняют порядок PTS. */ |
||||||
|
#ifndef CALL_VIDEO_PLAYOUT_H |
||||||
|
#define CALL_VIDEO_PLAYOUT_H |
||||||
|
#include <stdint.h> |
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
struct call_video_playout_frame { int64_t pts_us, received_us; }; |
||||||
|
enum call_video_playout_action { |
||||||
|
CALL_VIDEO_PLAYOUT_WAIT, CALL_VIDEO_PLAYOUT_SHOW, CALL_VIDEO_PLAYOUT_DROP_LATE, |
||||||
|
CALL_VIDEO_PLAYOUT_DROP_SCHEDULE, CALL_VIDEO_PLAYOUT_DROP_ORDER |
||||||
|
}; |
||||||
|
struct call_video_playout { |
||||||
|
uint64_t call_id; |
||||||
|
int anchored, audio_valid; |
||||||
|
int64_t last_pts, last_deadline, deadline_us; |
||||||
|
int64_t report_us, last_show_us, interval_min, interval_max, av_min, av_max, dispatch_max, data_shift; |
||||||
|
unsigned shown, late, skipped, order, audio_wait, clock_wait; |
||||||
|
}; |
||||||
|
|
||||||
|
int64_t call_video_playout_now_us(void); |
||||||
|
void call_video_playout_init(struct call_video_playout *p, uint64_t call_id); |
||||||
|
/* frame==NULL позволяет обновлять состояние аудиочасов/сводку даже без готовых кадров.
|
||||||
|
* deadline_us — абсолютный срок при WAIT, проверять аудиочасы не реже чем раз в 5мс. |
||||||
|
* source_epoch не участвует: коррекция аудио не означает разрыв видеокодека. */ |
||||||
|
enum call_video_playout_action call_video_playout_poll(struct call_video_playout *p, |
||||||
|
const struct call_video_playout_frame *frame, const struct call_video_playout_frame *next, |
||||||
|
int64_t now_us, int audio_valid, int64_t audio_pts_us); |
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
|
#endif |
||||||
Loading…
Reference in new issue