call: continue bounded video flush without waiting for media ticks

This commit is contained in:
evgeny committed 2026-10-15 16:38:49 +02:00
1 parent ff51e30a55
commit 9886bc2fde
3 files changed
+167 -9

No files matched your search

+44 -9
View File
@@ -16,12 +16,13 @@ struct video_tx_frame {
struct call_video_frame frame;
uint8_t *data;
uint64_t started_tb;
unsigned budget_yields;
};
struct call_video_transport {
struct call_session *session;
struct call_video_caps local, peer;
struct call_video_ops ops;
void *arg, *timer;
void *arg, *timer, *flush_pending;
struct call_video_receiver *receiver;
struct video_tx_frame tx[VIDEO_TX_FRAMES];
unsigned tx_count, tx_bytes;
@@ -41,6 +42,8 @@ struct call_video_transport {
uint64_t report_us;
unsigned offered, accepted, held_busy, transport_busy, key_wait;
int64_t max_audio_ahead;
uint64_t flush_scheduled_us, flush_wait_max_us;
unsigned flush_yields, flush_runs, flush_net_wait, flush_audio_wait;
};
static struct call_session *active(struct UTUN_INSTANCE *inst, uint64_t id) {
@@ -86,9 +89,16 @@ static void pop_tx(struct call_video_transport *v) {
v->tx_bytes -= v->tx[0].frame.size; u_free(v->tx[0].data); --v->tx_count;
memmove(v->tx, v->tx + 1, v->tx_count * sizeof(v->tx[0]));
}
/* call_soon остаётся в очереди с NULL callback до прохода uasync; v можно освободить сразу. */
static void cancel_flush(struct call_video_transport *v) {
if (v->flush_pending && uasync_call_soon_cancel(v->session->ctx->inst->ua, v->flush_pending) != ERR_OK)
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video flush cancel failed call=%llu", (unsigned long long)v->session->call_id);
v->flush_pending = NULL; v->flush_scheduled_us = 0;
}
static void discard_tx(struct call_video_transport *v, const char *reason) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX discard call=%llu frames=%u bytes=%u reason=%s",
(unsigned long long)v->session->call_id, v->tx_count, v->tx_bytes, reason);
cancel_flush(v);
while (v->tx_count) pop_tx(v);
v->need_key = 1;
if (v->ops.need_key) v->ops.need_key(v->arg);
@@ -149,13 +159,16 @@ static void update_rate(struct call_video_transport *v) {
if (now - v->report_us >= 1000000) {
DEBUG_DEBUG(DEBUG_CATEGORY_CALL,
"video TX flow call=%llu offered=%u accepted=%u audio_busy=%u transport_busy=%u key_wait=%u "
"audio_ahead_us=%lld held=%d queue=%u bytes=%u audio_pts=%lld queue_age_us=%llu tail_ahead_us=%lld",
"audio_ahead_us=%lld held=%d queue=%u bytes=%u audio_pts=%lld queue_age_us=%llu tail_ahead_us=%lld "
"flush_yields=%u flush_runs=%u flush_wait_max_us=%llu flush_net_wait=%u flush_audio_wait=%u",
(unsigned long long)s->call_id, v->offered, v->accepted, v->held_busy, v->transport_busy, v->key_wait,
(long long)v->max_audio_ahead, held, v->tx_count, v->tx_bytes,
(long long)v->audio_end_pts,
(unsigned long long)(v->tx_count ? (get_time_tb() - v->tx[0].started_tb) * 100 : 0),
(long long)(v->tx_count && v->audio_end_pts >= 0 ? v->tx[v->tx_count - 1].frame.pts_us - v->audio_end_pts : 0));
(long long)(v->tx_count && v->audio_end_pts >= 0 ? v->tx[v->tx_count - 1].frame.pts_us - v->audio_end_pts : 0),
v->flush_yields, v->flush_runs, (unsigned long long)v->flush_wait_max_us, v->flush_net_wait, v->flush_audio_wait);
v->report_us = now; v->offered = v->accepted = v->held_busy = v->transport_busy = v->key_wait = 0; v->max_audio_ahead = 0;
v->flush_yields = v->flush_runs = v->flush_net_wait = v->flush_audio_wait = 0; v->flush_wait_max_us = 0;
}
struct call_video_rate_sample sample = {0};
sample.now_us = now; sample.window_end_us = now / ROUTE_TX_SAMPLE_US * ROUTE_TX_SAMPLE_US;
@@ -182,12 +195,25 @@ static void update_rate(struct call_video_transport *v) {
v->ops.bitrate(v->arg, v->rate_format.generation, v->rate_request, result.target_bps);
}
}
static void flush(struct call_video_transport *v);
/* Снимаем handle до callback: повторный бюджет будет поставлен в следующий batch uasync. */
static void continue_flush(void *arg) {
struct call_video_transport *v = arg;
v->flush_pending = NULL; v->flush_runs++;
flush(v);
}
static void flush(struct call_video_transport *v) {
if (v->flush_scheduled_us) {
uint64_t wait = get_time_us() - v->flush_scheduled_us;
if (wait > v->flush_wait_max_us) v->flush_wait_max_us = wait;
}
// Другой источник flush мог успеть раньше отложенного вызова: оставляем только одно продолжение.
cancel_flush(v);
if (!v->tx_count) return;
struct call_session *s = v->session;
struct ETCP_ROUTER_CONN *conn = NULL;
uint8_t wire[CALL_VIDEO_HEADER_BYTES + CALL_VIDEO_FRAGMENT_BYTES];
// Ограниченный burst одного вызова; повторяется по audio send и таймеру 50мс.
// Ограниченный burst одного вызова. Только budget ставит немедленное продолжение; backpressure — нет.
for (unsigned budget = 0; v->tx_count && budget < 16; budget++) {
struct video_tx_frame *head = &v->tx[0];
struct call_video_frame *f = &head->frame;
@@ -195,15 +221,15 @@ static void flush(struct call_video_transport *v) {
if (!v->have_peer || !call_video_format_supported(&v->local, &v->peer, &f->format)) {
discard_tx(v, "capabilities_changed"); return;
}
if (v->acknowledged_revision != v->local_revision || f->pts_us > v->audio_end_pts) return;
if (v->acknowledged_revision != v->local_revision || f->pts_us > v->audio_end_pts) { v->flush_audio_wait++; return; }
if (!conn) conn = etcp_router_conn_get(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO);
if (!conn) { discard_tx(v, "router_allocation"); return; }
struct ETCP_ROUTER_TX_STATS net;
if (!etcp_router_tx_stats(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO, get_time_us(), &net)
&& media_bytes(net.queued_bytes + net.inflight_bytes, (uint64_t)net.queued_packets + net.inflight_packets)
>= debt_limit(v, net.baseline_rtt_us)) return;
>= debt_limit(v, net.baseline_rtt_us)) { v->flush_net_wait++; return; }
if (!etcp_router_send_q_has_room(s->ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_VIDEO)
|| queue_entry_count(conn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) return;
|| queue_entry_count(conn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) { v->flush_net_wait++; return; }
uint32_t remaining = f->size - f->offset;
f->length = remaining < CALL_VIDEO_FRAGMENT_BYTES ? remaining : CALL_VIDEO_FRAGMENT_BYTES;
f->data = head->data + f->offset;
@@ -214,12 +240,19 @@ static void flush(struct call_video_transport *v) {
if (f->offset == f->size) {
if (f->pts_us % 1000000 < 34000)
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"video timing stage=send call=%llu frame=%llu pts=%lld boot_us=%llu bytes=%u queue_age_us=%llu",
"video timing stage=send call=%llu frame=%llu pts=%lld boot_us=%llu bytes=%u queue_age_us=%llu budget_yields=%u",
(unsigned long long)f->call_id, (unsigned long long)f->frame_id, (long long)f->pts_us,
(unsigned long long)get_time_us(), f->size, (unsigned long long)((get_time_tb() - head->started_tb) * 100));
(unsigned long long)get_time_us(), f->size, (unsigned long long)((get_time_tb() - head->started_tb) * 100),
head->budget_yields);
pop_tx(v);
}
}
if (v->tx_count && v->acknowledged_revision == v->local_revision && v->tx[0].frame.pts_us <= v->audio_end_pts) {
v->flush_pending = uasync_call_soon(s->ctx->inst->ua, v, continue_flush);
if (v->flush_pending) {
v->flush_scheduled_us = get_time_us(); v->flush_yields++; v->tx[0].budget_yields++;
} else DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video flush continuation allocation failed call=%llu", (unsigned long long)s->call_id);
}
}
static void tick(void *arg) {
struct call_video_transport *v = arg;
@@ -242,6 +275,7 @@ void call_video_transport_stop(struct call_session *s) {
struct call_video_transport *v = s->video;
if (!v) return;
s->video = NULL;
cancel_flush(v);
if (v->timer) uasync_cancel_timeout(s->ctx->inst->ua, v->timer);
call_video_receiver_destroy(v->receiver);
while (v->tx_count) pop_tx(v);
@@ -326,6 +360,7 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr
if (!data) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video TX allocation failed bytes=%u", f->size); v->need_key = 1; return -1; }
memcpy(data, f->data, f->size);
struct video_tx_frame *tail = &v->tx[v->tx_count++];
tail->budget_yields = 0;
tail->frame = *f; tail->frame.data = tail->data = data; tail->started_tb = get_time_tb(); v->tx_bytes += f->size;
v->tx_last_id = f->frame_id; v->tx_last_pts = f->pts_us; v->tx_last_format = f->format; v->need_key = 0;
v->accepted++; flush(v); return 1;