From d32c8ed6e3f5ea3d21a849dd5f44af60ab4c42ed Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 23:16:20 +0200 Subject: [PATCH] Report decoder acceptance separately from keyframe recovery --- src/call/call_video_transport.c | 35 ++++++++++++++++++++++++++++----- src/call/call_video_transport.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/call/call_video_transport.c b/src/call/call_video_transport.c index a67ceacb..236a8c0d 100644 --- a/src/call/call_video_transport.c +++ b/src/call/call_video_transport.c @@ -10,7 +10,7 @@ #include "../../lib/debug_config.h" #include -enum { VIDEO_CAPS = 1, VIDEO_KEY_REQUEST = 2, VIDEO_CAPS_ACK = 3 }; +enum { VIDEO_CAPS = 1, VIDEO_KEY_REQUEST = 2, VIDEO_CAPS_ACK = 3, VIDEO_DECODE_OK = 4, VIDEO_DECODE_REJECT = 5 }; struct call_video_transport { struct call_session *session; struct call_video_caps local, peer; @@ -29,6 +29,8 @@ struct call_video_transport { uint64_t rate_request, produced_id, route_local_epoch, route_peer_epoch, rate_requested_us; uint32_t applied_bps, maximum_bps, range_minimum_bps, range_maximum_bps; int rate_pending, admit; + uint64_t received_generation, decode_generation; + int decode_result, decode_pending, tx_rejected; }; static struct call_session *active(struct UTUN_INSTANCE *inst, uint64_t id) { @@ -55,6 +57,7 @@ static int control_send(struct call_video_transport *v, uint8_t type) { struct call_session *s = v->session; uint8_t data[17 + CALL_VIDEO_CAPS_BYTES]; data[0] = type; memcpy(data + 1, &s->call_id, 8); uint64_t revision = type == VIDEO_CAPS ? v->local_revision : type == VIDEO_CAPS_ACK ? v->peer_revision : 0; + if (type == VIDEO_DECODE_OK || type == VIDEO_DECODE_REJECT) revision = v->decode_generation; memcpy(data + 9, &revision, 8); size_t length = 17; if (type == VIDEO_CAPS) { @@ -82,7 +85,7 @@ static void update_rate(struct call_video_transport *v) { if (!v->rate_format.generation || !v->applied_bps) return; struct call_session *s = v->session; uint64_t now = get_time_us(); - if (v->rate_pending && now - v->rate_requested_us > 2000000) { + if (v->rate_pending && now - v->rate_requested_us > 12000000) { v->rate_pending = 0; v->applied_bps = 0; v->admit = 0; DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video encoder command timeout call=%llu generation=%llu request=%llu", (unsigned long long)s->call_id, (unsigned long long)v->rate_format.generation, (unsigned long long)v->rate_request); @@ -173,7 +176,8 @@ static void tick(void *arg) { // Отдельный control может обогнать ACCEPT: повторяем до подтверждения приложения. if (v->local_revision && v->acknowledged_revision != v->local_revision && get_time_tb() - v->caps_sent_tb >= 5000) control_send(v, VIDEO_CAPS); - update_rate(v); + if (v->decode_pending && !control_send(v, v->decode_result ? VIDEO_DECODE_OK : VIDEO_DECODE_REJECT)) v->decode_pending = 0; + if (!v->tx_rejected) update_rate(v); flush(v); v->timer = uasync_set_timeout(v->session->ctx->inst->ua, 500, v, tick, "call_video"); if (!v->timer) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video timer rearm failed call=%llu", (unsigned long long)v->session->call_id); @@ -233,6 +237,7 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr f ? f->size : 0, f ? f->offset : 0, f ? f->length : 0, f && call_video_format_valid(&f->format)); return -1; } if (!v->have_peer || v->acknowledged_revision != v->local_revision) { v->need_key = 1; return 0; } + if (v->tx_rejected) return 0; if (!call_video_format_supported(&v->local, &v->peer, &f->format)) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX unsupported format codec=%u level=%u bitrate_cap=%u profile=%u", f->format.codec, f->format.level, f->format.maximum_bps, f->format.profile); return -1; @@ -284,7 +289,7 @@ int call_video_configure_rate(struct UTUN_INSTANCE *inst, uint64_t id, const str (unsigned long long)id, range_minimum, maximum); return -1; } if (v->rate_format.generation != generation) { - v->rate_format = *format; v->rate_pending = 0; v->admit = 1; + v->rate_format = *format; v->rate_pending = 0; v->admit = 1; v->tx_rejected = 0; call_video_rate_init(&v->rate, get_time_us()); route_tx_meter_reset(&v->production, get_time_us()); } v->applied_bps = applied; v->maximum_bps = maximum; @@ -323,6 +328,19 @@ void call_video_request_key(struct UTUN_INSTANCE *inst, uint64_t id) { if (v->key_request_tb && now - v->key_request_tb < 5000) return; if (!control_send(v, VIDEO_KEY_REQUEST)) v->key_request_tb = now; } +int call_video_decode_result(struct UTUN_INSTANCE *inst, uint64_t id, uint64_t generation, int accepted) { + struct call_session *s = active(inst, id); + struct call_video_transport *v = s ? s->video : NULL; + if (!v || !generation || generation != v->received_generation || (accepted != 0 && accepted != 1)) { + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video stale decoder result call=%llu generation=%llu", + (unsigned long long)id, (unsigned long long)generation); return -1; + } + v->decode_generation = generation; v->decode_result = accepted; v->decode_pending = 1; + if (!control_send(v, accepted ? VIDEO_DECODE_OK : VIDEO_DECODE_REJECT)) v->decode_pending = 0; + DEBUG_INFO(DEBUG_CATEGORY_CALL, "video decoder result call=%llu generation=%llu accepted=%d pending=%d", + (unsigned long long)id, (unsigned long long)generation, accepted, v->decode_pending); + return 0; +} static int ready(struct UTUN_INSTANCE *inst, uint64_t group, uint64_t peer, const uint8_t *data, size_t length) { struct call_video_frame f; if (call_video_fragment_parse(data, length, &f)) return 1; @@ -382,12 +400,19 @@ static void receive(struct ETCP_CONN *conn, struct ll_entry *entry) { } else if (data[0] == VIDEO_KEY_REQUEST && length == 17 && !revision) { v->need_key = 1; if (v->ops.need_key) v->ops.need_key(v->arg); + } else if ((data[0] == VIDEO_DECODE_OK || data[0] == VIDEO_DECODE_REJECT) && length == 17 && revision) { + if (revision != v->rate_format.generation) goto done; + int accepted = data[0] == VIDEO_DECODE_OK; + DEBUG_INFO(DEBUG_CATEGORY_CALL, "video remote decoder result call=%llu generation=%llu accepted=%d", + (unsigned long long)id, (unsigned long long)revision, accepted); + if (!accepted) { v->tx_rejected = 1; v->admit = 0; if (v->data) discard_tx(v, "decoder_rejected"); } + if (v->ops.decoded) v->ops.decoded(v->arg, revision, accepted); } else goto invalid; } else if (v->ops.frame) { if (!v->have_peer || !call_video_format_supported(&v->peer, &v->local, &f.format)) goto invalid; struct call_video_frame complete; int result = call_video_receiver_push(v->receiver, &f, get_time_us(), &complete); - if (result == 1) v->ops.frame(v->arg, &complete); + if (result == 1) { v->received_generation = complete.format.generation; v->ops.frame(v->arg, &complete); } else if (result < 0 || (!(f.flags & CALL_VIDEO_KEY) && call_video_receiver_needs_key(v->receiver))) call_video_request_key(conn->instance, id); } diff --git a/src/call/call_video_transport.h b/src/call/call_video_transport.h index 9b129c9a..d6b014a7 100644 --- a/src/call/call_video_transport.h +++ b/src/call/call_video_transport.h @@ -16,6 +16,7 @@ struct call_video_ops { void (*frame)(void *arg, const struct call_video_frame *frame); void (*need_key)(void *arg); void (*bitrate)(void *arg, uint64_t generation, uint64_t request, uint32_t bps); + void (*decoded)(void *arg, uint64_t generation, int accepted); }; /* Настройка активного звонка, включая receive-only. NULL ops снимает платформенные callbacks. * capabilities отражают только реально проверенные профили. 0 — успешно, -1 — ошибка. */ @@ -28,6 +29,8 @@ int call_video_send_frame(struct UTUN_INSTANCE *inst, const struct call_video_fr void call_video_resume_receive(struct UTUN_INSTANCE *inst, uint64_t call_id); /* Decoder потерял references. Запрос идемпотентный, отправляется не чаще раза в 500мс. */ void call_video_request_key(struct UTUN_INSTANCE *inst, uint64_t call_id); +/* Результат запуска decoder конкретного поколения, отдельно от потери references. */ +int call_video_decode_result(struct UTUN_INSTANCE *inst, uint64_t call_id, uint64_t generation, int accepted); /* После открытия encoder; format — согласованный и фактически проверенный режим. * maximum_bps — дополнительный предел регулятора, не выше format.maximum_bps; * applied_bps может быть выше maximum_bps (нужно снижение), но не выше envelope.