From 005d891fb4d2811d1516a6f7cbd81cce266ea642 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 23:18:40 +0200 Subject: [PATCH] Bound video send debt and test decoder acceptance and rejection --- src/call/call_video_transport.c | 14 +++++++++++++- tests/test_call.c | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/call/call_video_transport.c b/src/call/call_video_transport.c index 236a8c0d..ad98cc81 100644 --- a/src/call/call_video_transport.c +++ b/src/call/call_video_transport.c @@ -81,6 +81,14 @@ static uint64_t media_bytes(uint64_t bytes, uint64_t packets) { uint64_t headers = packets * CALL_VIDEO_HEADER_BYTES; return bytes >= headers ? bytes - headers : 0; } +/* Ограничиваем накопление полусекундой целевого потока плюс базовый RTT. + * 64 KiB оставляем для всплеска IDR; абсолютный предел — защита памяти. */ +static uint64_t debt_limit(const struct call_video_transport *v, uint32_t rtt_us) { + uint64_t rtt = rtt_us < 2000000 ? rtt_us : 2000000; + uint64_t limit = (uint64_t)(v->applied_bps ? v->applied_bps : 1000000) * (rtt + 500000) / 8000000; + if (limit < 65536) limit = 65536; + return limit < CALL_VIDEO_MAX_FRAME ? limit : CALL_VIDEO_MAX_FRAME; +} static void update_rate(struct call_video_transport *v) { if (!v->rate_format.generation || !v->applied_bps) return; struct call_session *s = v->session; @@ -128,7 +136,7 @@ static void update_rate(struct call_video_transport *v) { sample.applied_bps = v->applied_bps; sample.minimum_bps = minimum; sample.maximum_bps = maximum; sample.baseline_rtt_us = net.baseline_rtt_us; sample.rtt_us = net.rtt_us; sample.frame_interval_us = v->tx.format.fps ? 1000000 / v->tx.format.fps : 33333; - sample.debt_limit_bytes = CALL_VIDEO_MAX_FRAME; + sample.debt_limit_bytes = debt_limit(v, net.baseline_rtt_us); sample.valid = found && net.window.valid && produced.valid && net.window.end_us == produced.end_us; // Первый KEY должен создать логический канал; до этого нет транспортных измерений. sample.ready = found ? net.ready : 1; @@ -158,6 +166,10 @@ static void flush(struct call_video_transport *v) { uint8_t wire[CALL_VIDEO_HEADER_BYTES + CALL_VIDEO_FRAGMENT_BYTES]; // Ограниченный burst одного вызова; повторяется по audio send и таймеру 50мс. for (unsigned budget = 0; v->data && budget < 16; budget++) { + 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; 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; uint32_t remaining = v->tx.size - v->tx.offset; diff --git a/tests/test_call.c b/tests/test_call.c index d5c8cc28..397c031b 100644 --- a/tests/test_call.c +++ b/tests/test_call.c @@ -65,6 +65,7 @@ enum call_phase { P_WAIT_MEDIA, /* B: media_recv_cb получил кадр */ P_VIDEO_CONFIG, P_VIDEO_SEND, P_VIDEO_HOLD, P_VIDEO_BUSY, P_VIDEO_RESUME, P_VIDEO_RANGE_WAIT, P_VIDEO_SWITCH, P_VIDEO_SWITCH_WAIT, P_VIDEO_SHRINK_WAIT, P_VIDEO_PORT_OPEN, P_VIDEO_PORT_RECEIVE, P_VIDEO_PORT_SEND, P_VIDEO_PORT_ACK, + P_VIDEO_DECODE_OK, P_VIDEO_DECODE_REJECT, P_HANGUP, /* A: call_hangup */ P_WAIT_ENDED1, /* обе ENDED */ P_CALL2_START, /* A: call_start (2-й звонок) */ @@ -650,10 +651,25 @@ static void call_tick(void* arg) { case P_VIDEO_PORT_ACK: { if (!g_port_frame) break; if (g_port_frame != 1) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video port TX payload mismatch"); t->result = 2; break; } + if (call_video_decode_result(A, t->call_id, 1, 1) + || call_video_decode_result(A, t->call_id, 2, 0) != -1) { t->result = 2; break; } + t->phase = P_VIDEO_DECODE_OK; break; + } + case P_VIDEO_DECODE_OK: { + struct call_video_port_status status; + if (call_video_port_status(t->call_id, &status) || status.decoded_generation != 1) break; + if (!status.decoded_accepted || call_video_decode_result(A, t->call_id, 1, 0)) { t->result = 2; break; } + t->phase_ticks = 0; t->phase = P_VIDEO_DECODE_REJECT; break; + } + case P_VIDEO_DECODE_REJECT: { + struct call_video_port_status status; + if (call_video_port_status(t->call_id, &status) || status.decoded_generation != 1 || status.decoded_accepted) break; + if (++t->phase_ticks < 10) break; // Регулятор не должен отменять запрет decoder на следующем tick. + struct call_video_frame f = video_test_frame(t->call_id, 1); f.frame_id = 2; f.pts_us += 33333; + if (call_video_send_frame(B, &f) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video ignored decoder rejection"); t->result = 2; break; } call_video_port_request_key(t->call_id); call_video_port_close(t->call_id); call_video_port_detach(B); // Отменяет уже поставленный post до destroy ядра. - struct call_video_port_status status; if (call_video_port_status(t->call_id, &status) != -1) { t->result = 2; break; } t->phase = P_HANGUP; break; }