Add video bitrate regulator with bounded debt feedback
This commit is contained in:
1 parent
0bbd4d6c34
commit
f24a8ca6d3
2 files changed
+147
No files matched your search
@@ -0,0 +1,100 @@
|
||||
#include "call_video_rate.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
|
||||
#define WINDOW_US 500000u
|
||||
#define MAX_BITRATE 100000000u
|
||||
|
||||
void call_video_rate_init(struct call_video_rate *rate, uint64_t now_us) {
|
||||
if (!rate) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video rate init without state"); return; }
|
||||
memset(rate, 0, sizeof(*rate));
|
||||
rate->last_us = rate->next_change_us = rate->last_log_us = now_us;
|
||||
rate->initialized = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video rate reset time=%llu", (unsigned long long)now_us);
|
||||
}
|
||||
|
||||
/* Период реакции не короче измерительного окна и двух RTT; uint64 исключает overflow RTT*2. */
|
||||
static uint64_t feedback_interval(const struct call_video_rate_sample *s) {
|
||||
uint64_t rtt = (uint64_t)s->rtt_us * 2;
|
||||
return rtt > WINDOW_US ? rtt : WINDOW_US;
|
||||
}
|
||||
|
||||
/* Снижение ограничено вдвое за один feedback; рост — проба 5% с повторным измерением. */
|
||||
int call_video_rate_step(struct call_video_rate *rate, const struct call_video_rate_sample *s,
|
||||
struct call_video_rate_result *out) {
|
||||
if (!rate || !rate->initialized || !s || !out || s->now_us < rate->last_us
|
||||
|| s->now_us > UINT64_MAX - (uint64_t)UINT32_MAX * 2 - WINDOW_US
|
||||
|| s->window_end_us > s->now_us || s->now_us - s->window_end_us >= 50000
|
||||
|| s->window_end_us % 50000 || !s->minimum_bps || s->minimum_bps > s->maximum_bps
|
||||
|| s->maximum_bps > MAX_BITRATE || !s->applied_bps || s->applied_bps > MAX_BITRATE
|
||||
|| !s->debt_limit_bytes || s->debt_limit_bytes > UINT32_MAX || s->debt_bytes > UINT32_MAX
|
||||
|| s->delivered_bytes > UINT32_MAX || s->produced_bytes > UINT32_MAX
|
||||
|| !s->frame_interval_us || s->frame_interval_us > 1000000) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video rate invalid sample time=%llu window=%llu applied=%u min=%u max=%u",
|
||||
(unsigned long long)(s ? s->now_us : 0), (unsigned long long)(s ? s->window_end_us : 0),
|
||||
s ? s->applied_bps : 0, s ? s->minimum_bps : 0, s ? s->maximum_bps : 0);
|
||||
return -1;
|
||||
}
|
||||
struct call_video_rate_result result = {.target_bps = s->applied_bps, .reason = CALL_VIDEO_RATE_HOLD,
|
||||
.admit = s->ready && !s->no_route && !s->receive_busy && s->debt_bytes < s->debt_limit_bytes,
|
||||
.delivery_bps = s->delivered_bytes * 16};
|
||||
uint64_t interval = feedback_interval(s);
|
||||
if (rate->applied_bps != s->applied_bps) {
|
||||
rate->applied_bps = s->applied_bps;
|
||||
rate->next_change_us = s->now_us + interval;
|
||||
}
|
||||
/* Запас RTT относится к inflight, два кадра — к пакетному выходу encoder. */
|
||||
uint64_t normal = result.delivery_bps * ((uint64_t)s->baseline_rtt_us + 50000) / 8000000;
|
||||
normal += (uint64_t)s->applied_bps * s->frame_interval_us * 2 / 8000000;
|
||||
result.excess_bytes = s->debt_bytes > normal ? s->debt_bytes - normal : 0;
|
||||
if (!result.excess_bytes || !s->valid || s->locally_held || s->probing) rate->high = 0;
|
||||
else if (!rate->high) { rate->high = 1; rate->high_since_us = s->now_us; }
|
||||
|
||||
if (!s->ready || s->no_route || s->receive_busy) result.reason = CALL_VIDEO_RATE_BLOCKED;
|
||||
else if (!s->command_pending) {
|
||||
if (s->applied_bps > s->maximum_bps || s->applied_bps < s->minimum_bps) {
|
||||
result.target_bps = s->applied_bps > s->maximum_bps ? s->maximum_bps : s->minimum_bps;
|
||||
result.reason = CALL_VIDEO_RATE_LIMIT;
|
||||
} else if (s->valid && !s->probing && !s->locally_held && s->now_us >= rate->next_change_us) {
|
||||
int growing = s->debt_bytes > rate->previous_debt && s->produced_bytes > s->delivered_bytes * 105 / 100;
|
||||
int persistent = rate->high && s->now_us - rate->high_since_us >= WINDOW_US;
|
||||
if (result.excess_bytes && (growing || persistent)) {
|
||||
uint64_t capacity = result.delivery_bps * 95 / 100;
|
||||
/* Долг — интеграл дефицита. Целевой срок разгрузки 1с, без второго интегратора. */
|
||||
uint64_t drain = result.excess_bytes * 8;
|
||||
uint64_t candidate = capacity > drain ? capacity - drain : 0;
|
||||
uint32_t floor = s->applied_bps / 2;
|
||||
if (floor < s->minimum_bps) floor = s->minimum_bps;
|
||||
if (candidate < floor) candidate = floor;
|
||||
if (candidate < s->applied_bps * 95ull / 100) {
|
||||
result.target_bps = (uint32_t)candidate; result.reason = CALL_VIDEO_RATE_DRAIN;
|
||||
}
|
||||
} else if (!result.excess_bytes && result.admit && s->produced_bytes && s->delivered_bytes
|
||||
&& s->debt_bytes <= rate->previous_debt + s->applied_bps / 160) {
|
||||
/* Source-limited сцена не доказывает предел канала: допускаем небольшую пробу. */
|
||||
uint64_t candidate = s->applied_bps * 105ull / 100;
|
||||
if (candidate > s->maximum_bps) candidate = s->maximum_bps;
|
||||
if (candidate > s->applied_bps) {
|
||||
result.target_bps = (uint32_t)candidate; result.reason = CALL_VIDEO_RATE_PROBE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.target_bps != s->applied_bps) {
|
||||
rate->next_change_us = s->now_us + interval;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video rate request reason=%d from=%u to=%u ack_bps=%llu debt=%llu excess=%llu",
|
||||
result.reason, s->applied_bps, result.target_bps, (unsigned long long)result.delivery_bps,
|
||||
(unsigned long long)s->debt_bytes, (unsigned long long)result.excess_bytes);
|
||||
}
|
||||
if (s->now_us - rate->last_log_us >= 1000000) {
|
||||
rate->last_log_us = s->now_us;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video rate sample applied=%u ack_bps=%llu produced=%llu debt=%llu excess=%llu"
|
||||
" valid=%d admit=%d pending=%d probe=%d held=%d reason=%d",
|
||||
s->applied_bps, (unsigned long long)result.delivery_bps, (unsigned long long)s->produced_bytes,
|
||||
(unsigned long long)s->debt_bytes, (unsigned long long)result.excess_bytes, s->valid, result.admit,
|
||||
s->command_pending, s->probing, s->locally_held, result.reason);
|
||||
}
|
||||
rate->previous_debt = s->debt_bytes; rate->last_us = s->now_us;
|
||||
*out = result;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Регулятор исходящего видеотрека: чистое состояние без таймеров, транспорта и encoder.
|
||||
* Владелец вызывает step каждые 50мс с согласованными окнами доставки/производства 500мс.
|
||||
* Все размеры — уникальные байты сжатого видео без заголовков. Held A/V не входит в debt.
|
||||
* После смены транспортной эпохи нужен init. Команды encoder сериализует вызывающий. */
|
||||
#ifndef CALL_VIDEO_RATE_H
|
||||
#define CALL_VIDEO_RATE_H
|
||||
#include <stdint.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum call_video_rate_reason {
|
||||
CALL_VIDEO_RATE_HOLD, CALL_VIDEO_RATE_LIMIT, CALL_VIDEO_RATE_DRAIN,
|
||||
CALL_VIDEO_RATE_PROBE, CALL_VIDEO_RATE_BLOCKED
|
||||
};
|
||||
|
||||
struct call_video_rate_sample {
|
||||
uint64_t now_us, window_end_us;
|
||||
uint64_t delivered_bytes, produced_bytes, debt_bytes;
|
||||
uint32_t applied_bps, minimum_bps, maximum_bps;
|
||||
uint32_t baseline_rtt_us, rtt_us, frame_interval_us;
|
||||
uint64_t debt_limit_bytes; /* Жёсткий предел admission, независимо от ACK-оценки. */
|
||||
int valid, ready, no_route, receive_busy, probing, locally_held, command_pending;
|
||||
};
|
||||
|
||||
struct call_video_rate_result {
|
||||
uint32_t target_bps;
|
||||
enum call_video_rate_reason reason;
|
||||
int admit; /* Уже принятые кадры продолжают drain; новые запрещены при переполнении. */
|
||||
uint64_t delivery_bps, excess_bytes;
|
||||
};
|
||||
|
||||
struct call_video_rate {
|
||||
uint64_t last_us, next_change_us, previous_debt, high_since_us, last_log_us;
|
||||
uint32_t applied_bps;
|
||||
int initialized, high;
|
||||
};
|
||||
|
||||
void call_video_rate_init(struct call_video_rate *rate, uint64_t now_us);
|
||||
/* 0 — результат получен, -1 — недопустимый снимок (состояние не меняется).
|
||||
* target != applied — рекомендация, не подтверждение применения. Pending запрещает новую команду. */
|
||||
int call_video_rate_step(struct call_video_rate *rate, const struct call_video_rate_sample *sample,
|
||||
struct call_video_rate_result *result);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
Reference in new issue
Block a user