From 609221e3847b763d1bdc361b29f41afa80a2bc56 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 20:48:27 +0200 Subject: [PATCH] Separate call audio and video from signaling queues --- src/call/call.c | 38 +++++++++++++++++++++++++++------- src/transport_layer/etcp_api.h | 6 +++++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/call/call.c b/src/call/call.c index 734bdf80..26c688d3 100644 --- a/src/call/call.c +++ b/src/call/call.c @@ -13,6 +13,7 @@ #include "call.h" #include "call_priv.h" +#include "call_video_transport.h" #include "call_proto.h" #include "../dm/dm_core.h" #include "../utun_instance.h" @@ -147,7 +148,8 @@ static void call_session_free_cb(void* arg) { /* Освободить медиа-объекты сессии: отменить waiter на send_q роутера и * дренировать tx_q (без отправки). Безопасно вызывать из uasync-потока. */ static void call_session_cleanup(struct call_ctx* ctx, struct call_session* s) { - etcp_router_cancel_send_ready(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL, &s->send_waiter); + call_video_transport_stop(s); + etcp_router_cancel_send_ready(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_AUDIO, &s->send_waiter); if (s->tx_q) { struct ll_entry* e; while ((e = queue_data_get(s->tx_q)) != NULL) { @@ -282,6 +284,7 @@ static void call_end(struct call_ctx* ctx, struct call_session* s, uint8_t reaso if (!ctx || !s || s->state == CALL_ST_ENDED) return; s->state = CALL_ST_ENDED; + call_video_transport_stop(s); s->reason = reason; ctx->c_calls_ended++; @@ -679,6 +682,19 @@ static void call_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { const uint8_t* body = d + ROUTER_SVC_PAYLOAD_OFF; size_t blen = entry->len - ROUTER_SVC_PAYLOAD_OFF; + if ((subcmd == CALL_SUBCMD_MEDIA) != (d[0] == ETCP_RT_ID_CALL_AUDIO)) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "call packet in wrong channel svc=%u subcmd=%u", d[0], subcmd); + queue_dgram_free(entry); queue_entry_free(entry); return; + } + if (subcmd != CALL_SUBCMD_INVITE && blen >= 9) { + uint64_t id; memcpy(&id, body + 1, 8); + struct call_session *s = call_session_find(ctx, id); + if (s && (s->peer_node_id != from || s->group_id != group)) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "call packet peer/group mismatch id=%llu", (unsigned long long)id); + queue_dgram_free(entry); queue_entry_free(entry); return; + } + } + switch (subcmd) { case CALL_SUBCMD_INVITE: call_handle_invite(ctx, from, group, body, blen); break; case CALL_SUBCMD_RINGING: call_handle_ringing(ctx, from, body, blen); break; @@ -722,6 +738,10 @@ int call_init(struct UTUN_INSTANCE* inst) { } ctx->initialized = 1; + if (etcp_router_bind(inst, ETCP_RT_ID_CALL_AUDIO, call_etcp_recv) || call_video_transport_bind(inst)) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call media channel bind failed"); + call_destroy(inst); return -1; + } DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: initialized (svc=0x%02x)", CALL_ID, ETCP_RT_ID_CALL); return 0; } @@ -746,6 +766,8 @@ void call_destroy(struct UTUN_INSTANCE* inst) { queue_entry_free(e); } queue_free(ctx->sessions); + call_video_transport_unbind(inst); + etcp_router_unbind(inst, ETCP_RT_ID_CALL_AUDIO); etcp_router_unbind(inst, ETCP_RT_ID_CALL); inst->call = NULL; uint32_t started = ctx->c_calls_started, ended = ctx->c_calls_ended; @@ -792,17 +814,19 @@ static void call_tx_flush(struct call_session* s) { struct call_ctx* ctx = s->ctx; if (s->state == CALL_ST_ENDED) return; /* гарантируем router conn — иначе has_room/on_send_ready не видят его */ - etcp_router_conn_get(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL); + etcp_router_conn_get(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_AUDIO); while (s->tx_q && s->tx_q->head) { - if (!etcp_router_send_q_has_room(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL)) { - etcp_router_on_send_ready(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL, + if (!etcp_router_send_q_has_room(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_AUDIO)) { + etcp_router_on_send_ready(ctx->inst, s->group_id, s->peer_node_id, ETCP_RT_ID_CALL_AUDIO, &s->send_waiter, call_tx_drain_cb, s); return; } struct ll_entry* e = queue_data_get(s->tx_q); - if (etcp_route_send(ctx->inst, s->group_id, s->peer_node_id, e, 1, 0) == 0) + struct call_pkt_media packet; memcpy(&packet, e->dgram + 1, sizeof(packet)); + if (etcp_route_send(ctx->inst, s->group_id, s->peer_node_id, e, 1, 0) == 0) { s->c_tx_frames++; - else DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: media enqueue failed id=%016llx", CALL_ID, (unsigned long long)s->call_id); + if (packet.capture_pts_us != INT64_MIN) call_video_audio_sent(s, packet.capture_pts_us + 20000); + } else DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: media enqueue failed id=%016llx", CALL_ID, (unsigned long long)s->call_id); } } @@ -838,7 +862,7 @@ int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t* if (!e) return -1; e->dgram = u_malloc(1 + mlen); if (!e->dgram) { queue_entry_free(e); return -1; } - e->dgram[0] = ETCP_RT_ID_CALL; + e->dgram[0] = ETCP_RT_ID_CALL_AUDIO; memcpy(e->dgram + 1, buf, mlen); e->len = (uint16_t)(1 + mlen); diff --git a/src/transport_layer/etcp_api.h b/src/transport_layer/etcp_api.h index 4d3a0d0e..2e995691 100644 --- a/src/transport_layer/etcp_api.h +++ b/src/transport_layer/etcp_api.h @@ -44,7 +44,8 @@ extern "C" { // 0x06 ICMP_PROXY, 0x07 MEDIA_DELIVERY, 0x08 TCP_PROXY_CLIENT, // 0x11 CONN_MGR, 0x32 ADMIN_KEY, 0x33 JOIN, 0x34 DM, // 0x35 DM_MAILBOX, 0x36 CALL, 0x37 FILE_TRANSFER, 0x38 DM_MEDIA, 0x39 MEMBER_FILES, -// 0x3A GROUP_AVATAR, 0x3B NAT_DETECTION, 0x3C STREAM +// 0x3A GROUP_AVATAR, 0x3B NAT_DETECTION, 0x3C STREAM, +// 0x3D CALL_VIDEO_CONTROL, 0x3E CALL_VIDEO, 0x3F CALL_AUDIO // // !!! Новый ID: впиши его в нужный реестр выше и убедись, что значение // не занято в том же пространстве. @@ -81,6 +82,9 @@ extern "C" { #define ETCP_RT_ID_DM 0x34 // dm_core — прямой p2p чат #define ETCP_RT_ID_DM_MAILBOX 0x35 // dm_mailbox — offline-хранение #define ETCP_RT_ID_CALL 0x36 // call — P2P аудио-звонок +#define ETCP_RT_ID_CALL_VIDEO_CONTROL 0x3D // возможности и восстановление видеотрека +#define ETCP_RT_ID_CALL_VIDEO 0x3E // видео, отдельный ACK/debt/backpressure +#define ETCP_RT_ID_CALL_AUDIO 0x3F // аудио, независимо от сигналинга/видео #define ETCP_RT_ID_FILE_TRANSFER 0x37 // file_transfer — передача неизменяемых файлов #define ETCP_RT_ID_DM_MEDIA 0x38 // dm_media — custody, location и подтверждения медиа #define ETCP_RT_ID_MEMBER_FILES 0x39 // member_files — каталоги и файлы участников группы