Preserve adaptive audio batch target without partial-packet deadlines

This commit is contained in:
evgeny committed 2026-10-30 19:28:21 +01:00
1 parent e598568652
commit ee5c258b3c
2 files changed
+63 -2

No files matched your search

+49
View File
@@ -1,4 +1,5 @@
#include "call_audio_batch.h"
#include "../routing_layer/etcp_router.h"
#include "../../lib/debug_config.h"
#include <string.h>
@@ -49,3 +50,51 @@ int call_audio_batch_parse(const uint8_t *wire, size_t length, struct call_audio
invalid:
DEBUG_WARN(DEBUG_CATEGORY_CALL, "audio batch RX malformed bytes=%zu", length); return -1;
}
void call_audio_batch_init(struct call_audio_batch_control *c, uint64_t now) {
if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "audio batch reset without state"); return; }
*c = (struct call_audio_batch_control){.target = 4, .last_us = now};
}
int call_audio_batch_step(struct call_audio_batch_control *c, uint64_t now, const struct ETCP_ROUTER_TX_STATS *net) {
if (!c || !c->target || now < c->last_us || (net && net->last_ack_us > now)) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "audio batch invalid control sample time=%llu", (unsigned long long)now); return -1;
}
unsigned previous = c->target;
if ((!net && (c->local_epoch || c->peer_epoch || c->feedback_received))
|| (net && (net->local_epoch != c->local_epoch || net->peer_epoch != c->peer_epoch))) {
call_audio_batch_init(c, now);
if (net) { c->local_epoch = net->local_epoch; c->peer_epoch = net->peer_epoch; }
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "audio batch reset router epoch=%llx/%llx previous=%u",
(unsigned long long)c->local_epoch, (unsigned long long)c->peer_epoch, previous);
}
c->last_us = now;
c->delay_us = net ? net->delivery_rtt_us : 0;
if (net && net->queued_age_us > c->delay_us) c->delay_us = net->queued_age_us;
if (net && net->inflight_age_us > c->delay_us) c->delay_us = net->inflight_age_us;
uint64_t interval = net && net->delivery_rtt_us > 250000 ? net->delivery_rtt_us * 2 : 500000;
uint64_t fresh_us = interval > 2000000 ? interval : 2000000;
int measured = net && net->ready && !net->no_route && !net->receive_busy && !net->probing
&& net->acked_packets && now - net->last_ack_us <= fresh_us;
if (measured) c->feedback_received = 1;
int usable = measured && net->window.valid;
const char *reason = "hold";
if (!usable) { c->high = c->low = 0; reason = "unavailable"; }
else if (c->delay_us > 100000) {
c->low = 0;
if (!c->high) { c->high = 1; c->high_since_us = now; }
if (c->target < CALL_AUDIO_BATCH_MAX && now >= c->next_change_us && now - c->high_since_us >= 200000) {
c->target++; c->next_change_us = now + interval; reason = "enqueue_ack_delay";
}
} else if (c->delay_us < 40000 && !net->queued_packets) {
c->high = 0;
if (!c->low) { c->low = 1; c->low_since_us = now; }
if (c->target > 1 && now >= c->next_change_us && now - c->low_since_us >= 2000000) {
c->target--; c->next_change_us = now + interval; c->low_since_us = now; reason = "recovered";
}
} else c->high = c->low = 0;
if (c->target != previous) DEBUG_INFO(DEBUG_CATEGORY_CALL,
"audio batch target=%u->%u reason=%s delay_us=%llu router_rtt_us=%llu queued_us=%llu flight_us=%llu",
previous, c->target, reason, (unsigned long long)c->delay_us, (unsigned long long)(net ? net->delivery_rtt_us : 0),
(unsigned long long)(net ? net->queued_age_us : 0), (unsigned long long)(net ? net->inflight_age_us : 0));
return 0;
}
+14 -2
View File
@@ -1,5 +1,5 @@
/* Упаковка Opus звонка: 1..10 кадров, не более 1024 байт. Wire views заимствуют входной буфер.
* call.c собирает доступные кадры по готовности Router, без отдельного ожидания наполнения. */
/* Упаковка Opus звонка и выбор размера пачки по Router enqueue->ACK. Views заимствуют входной буфер.
* Владелец снимает статистику каждые 50мс. Отправка ждёт target кадров И готовности Router; deadline нет. */
#ifndef CALL_AUDIO_BATCH_H
#define CALL_AUDIO_BATCH_H
#include "call_proto.h"
@@ -15,10 +15,22 @@ struct call_audio_batch_view {
struct call_pkt_media header; /* Общие поля; count определяет число frames. */
struct call_audio_frame_view frames[CALL_AUDIO_BATCH_MAX];
};
struct ETCP_ROUTER_TX_STATS;
struct call_audio_batch_control {
uint64_t local_epoch, peer_epoch, last_us, high_since_us, low_since_us, next_change_us;
uint64_t delay_us; /* max полного RTT и возраста Router-очередей; локальное накопление не входит. */
unsigned target; /* Необходимое число кадров, 1..10; старт с 4. */
int feedback_received; /* Первый пригодный ACK в текущей Router epoch. */
int high, low; /* Гистерезис роста/снижения target. */
};
/* Encode/parse: 0/-1. Проверяют весь пакет до изменения out/length/view; ошибки логируются.
* Encode не допускает перекрытие output с Opus; capacity >= вычисленного размера, максимум 1024. */
int call_audio_batch_encode(const struct call_audio_batch_view *batch, uint8_t *out, size_t capacity, size_t *length);
int call_audio_batch_parse(const uint8_t *wire, size_t length, struct call_audio_batch_view *out);
/* Начальное target=4; новый звонок или смена Router epoch сбрасывает историю. */
void call_audio_batch_init(struct call_audio_batch_control *control, uint64_t now_us);
/* NULL — канал отсутствует; без свежих ACK/при блокировке target не растёт. 0/-1 с логом. */
int call_audio_batch_step(struct call_audio_batch_control *control, uint64_t now_us, const struct ETCP_ROUTER_TX_STATS *net);
#ifdef __cplusplus
}
#endif