You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

372 lines
16 KiB

// call_headless.c — headless-аудио-сокет для P2P-звонков (PCM, без тактирования)
//
// TCP-сокет с бинарным фреймингом [type:1][call_id:8][len:2][data:len].
// Клиент привязывается к звонку (HELLO) и обменивается ГОТОВЫМ PCM (int16 mono 48k):
// клиент → ядро: PCM-кадр (960 сэмплов) — кодируется в Opus и уходит пиру;
// ядро → клиент: Opus пира декодируется в PCM и отдаётся клиенту.
// Тактирование/джиттер — на стороне клиента (ядро лишь кодирует/декодирует).
// Сигналинг — на control-сокете (chat_headless_control), медиа — здесь.
#include "call_headless.h"
#include "call.h"
#include "../utun_instance.h"
#include "../../lib/opus_codec.h"
#include "../../lib/u_async.h"
#include "../../lib/mem.h"
#include "../../lib/socket_compat.h"
#include "../../lib/debug_config.h"
#include "../../lib/platform_compat.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define CH_ID "call_audio"
#define AUDIO_RECV_BUF_SIZE 65536
#define AUDIO_SEND_BUF_SIZE 65536
#define AUDIO_MAX_CLIENTS 16
#define AUDIO_MAX_FRAME 2048
#define CA_SAMPLE_RATE 48000
#define CA_FRAME_SAMPLES 960 /* 20мс @48k */
#define CA_PCM_FRAME_BYTES (CA_FRAME_SAMPLES * (int)sizeof(int16_t)) /* 1920 */
#define CA_BITRATE 32000
struct call_audio_client {
socket_t fd;
void* socket_id;
uint64_t call_id; /* 0 = не привязан */
uint8_t recv_buf[AUDIO_RECV_BUF_SIZE];
int recv_len;
uint8_t send_buf[AUDIO_SEND_BUF_SIZE];
int send_len;
int send_offset;
int closing;
struct call_audio* ca;
struct call_audio_client* next;
};
struct call_audio {
struct UASYNC* ua;
struct UTUN_INSTANCE* inst;
struct call_audio_client* clients;
int client_count;
socket_t listen_fd;
void* listen_sock_id;
int running;
void* cleanup_timer;
/* Opus-кодеки (одна активная звонка за раз — busy в call.c) */
uint64_t enc_call_id;
opus_codec_encoder_t* enc;
uint64_t dec_call_id;
opus_codec_decoder_t* dec;
/* диагностика */
uint32_t c_enc_frames;
uint32_t c_dec_frames;
uint32_t c_enc_dropped;
uint32_t c_dec_dropped;
};
static struct call_audio* ca_of(struct UTUN_INSTANCE* inst) {
return inst ? (struct call_audio*)inst->call_audio : NULL;
}
static void ca_close_client(struct call_audio_client* cli);
/* ── отправка фрейма клиенту (с буферизацией как в control-сокете) ── */
static void ca_send_raw(struct call_audio_client* cli, const uint8_t* data, size_t len) {
struct call_audio* ca = cli->ca;
if (!cli || cli->closing || cli->fd == SOCKET_INVALID || !data || len == 0) return;
if (cli->send_len > 0) {
size_t space = sizeof(cli->send_buf) - (size_t)cli->send_len;
if (len > space) return;
memcpy(cli->send_buf + cli->send_len, data, len);
cli->send_len += (int)len;
return;
}
ssize_t r = send(cli->fd, data, (int)len, 0);
if (r < 0 && (socket_get_error() == ERR_AGAIN || socket_get_error() == ERR_WOULDBLOCK)) {
if (len <= sizeof(cli->send_buf)) { memcpy(cli->send_buf, data, len); cli->send_len = (int)len; cli->send_offset = 0; }
if (ca && ca->ua) uasync_set_socket_write(ca->ua, cli->socket_id, 1);
return;
}
if (r >= 0 && (size_t)r < len) {
size_t remain = len - (size_t)r;
if (remain <= sizeof(cli->send_buf)) { memcpy(cli->send_buf, data + r, remain); cli->send_len = (int)remain; cli->send_offset = 0; }
if (ca && ca->ua) uasync_set_socket_write(ca->ua, cli->socket_id, 1);
}
if (r < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: send failed err=%d, closing client", CH_ID, socket_get_error());
ca_close_client(cli);
}
}
static void ca_write_frame(struct call_audio_client* cli, uint8_t type, uint64_t call_id,
const uint8_t* data, uint16_t len) {
uint8_t hdr[CALL_AUDIO_HDR_SIZE];
hdr[0] = type;
memcpy(hdr + 1, &call_id, 8);
hdr[9] = (uint8_t)(len >> 8);
hdr[10] = (uint8_t)(len & 0xFF);
ca_send_raw(cli, hdr, sizeof(hdr));
if (len) ca_send_raw(cli, data, len);
}
/* ── Opus-кодеки (одна активная звонка за раз) ── */
static opus_codec_encoder_t* ca_encoder(struct call_audio* ca, uint64_t call_id) {
if (ca->enc && ca->enc_call_id == call_id) return ca->enc;
if (ca->enc) { opus_codec_encoder_destroy(ca->enc); ca->enc = NULL; }
ca->enc = opus_codec_encoder_create(CA_SAMPLE_RATE, 1);
if (ca->enc) {
opus_codec_encoder_bitrate_set(ca->enc, CA_BITRATE);
ca->enc_call_id = call_id;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: encoder created for call_id=%016llx", CH_ID, (unsigned long long)call_id);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: encoder create failed", CH_ID);
}
return ca->enc;
}
static opus_codec_decoder_t* ca_decoder(struct call_audio* ca, uint64_t call_id) {
if (ca->dec && ca->dec_call_id == call_id) return ca->dec;
if (ca->dec) { opus_codec_decoder_destroy(ca->dec); ca->dec = NULL; }
ca->dec = opus_codec_decoder_create(CA_SAMPLE_RATE, 1);
if (ca->dec) {
ca->dec_call_id = call_id;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: decoder created for call_id=%016llx", CH_ID, (unsigned long long)call_id);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: decoder create failed", CH_ID);
}
return ca->dec;
}
/* ── приём медиа от пира → PCM клиенту ── */
static void ca_media_recv(struct UTUN_INSTANCE* inst, uint64_t call_id,
const uint8_t* opus, int len, void* arg) {
struct call_audio* ca = (struct call_audio*)arg;
(void)inst;
if (!ca || !opus || len <= 0) return;
opus_codec_decoder_t* dec = ca_decoder(ca, call_id);
if (!dec) return;
int16_t pcm[CA_FRAME_SAMPLES];
int n = opus_codec_decode(dec, opus, len, pcm, CA_FRAME_SAMPLES);
if (n <= 0) {
ca->c_dec_dropped++;
return;
}
ca->c_dec_frames++;
for (struct call_audio_client* c = ca->clients; c; c = c->next) {
if (c->call_id == call_id && !c->closing) {
ca_write_frame(c, CALL_AUDIO_FRAME, call_id, (const uint8_t*)pcm, (uint16_t)(n * (int)sizeof(int16_t)));
break;
}
}
}
/* ── разбор входящих фреймов ── */
static void ca_parse_frames(struct call_audio_client* cli) {
struct call_audio* ca = cli->ca;
uint8_t* p = cli->recv_buf;
int avail = cli->recv_len;
int consumed = 0;
while (avail - consumed >= CALL_AUDIO_HDR_SIZE) {
uint8_t type = p[consumed];
uint64_t call_id; memcpy(&call_id, p + consumed + 1, 8);
uint16_t flen = (uint16_t)((p[consumed + 9] << 8) | p[consumed + 10]);
if (flen > AUDIO_MAX_FRAME) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: frame too big %u — closing", CH_ID, flen);
ca_close_client(cli);
return;
}
if (avail - consumed < (int)(CALL_AUDIO_HDR_SIZE + flen)) break; /* нужен ещё кусок */
const uint8_t* data = p + consumed + CALL_AUDIO_HDR_SIZE;
if (type == CALL_AUDIO_HELLO) {
cli->call_id = call_id;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio client bound to call_id=%016llx", CH_ID, (unsigned long long)call_id);
} else if (type == CALL_AUDIO_FRAME) {
if (flen != CA_PCM_FRAME_BYTES) {
ca->c_enc_dropped++;
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: bad PCM frame size=%u (expect %d) — dropped",
CH_ID, flen, CA_PCM_FRAME_BYTES);
} else {
opus_codec_encoder_t* enc = ca_encoder(ca, call_id);
uint8_t opus[256];
int olen = enc ? opus_codec_encode(enc, (const int16_t*)data, CA_FRAME_SAMPLES, opus, (int)sizeof(opus)) : -1;
if (olen <= 0) {
ca->c_enc_dropped++;
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: encode failed (olen=%d) call_id=%016llx",
CH_ID, olen, (unsigned long long)call_id);
} else {
ca->c_enc_frames++;
if (call_send_media(ca->inst, call_id, opus, olen) != 0)
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: send_media failed call_id=%016llx len=%d",
CH_ID, (unsigned long long)call_id, olen);
}
}
} else {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: unknown frame type=0x%02x — closing", CH_ID, type);
ca_close_client(cli);
return;
}
consumed += CALL_AUDIO_HDR_SIZE + flen;
}
if (consumed > 0) {
memmove(cli->recv_buf, cli->recv_buf + consumed, (size_t)(cli->recv_len - consumed));
cli->recv_len -= consumed;
}
}
/* ── socket callbacks ── */
static void ca_write_callback(socket_t fd, void* arg) {
struct call_audio_client* cli = (struct call_audio_client*)arg;
struct call_audio* ca = cli ? cli->ca : NULL;
if (!cli || cli->closing || fd != cli->fd) return;
if (cli->send_len <= 0) { if (ca && ca->ua) uasync_set_socket_write(ca->ua, cli->socket_id, 0); return; }
ssize_t r = send(cli->fd, cli->send_buf + cli->send_offset, cli->send_len - cli->send_offset, 0);
if (r < 0 && (socket_get_error() == ERR_AGAIN || socket_get_error() == ERR_WOULDBLOCK)) return;
if (r <= 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: send failed (r=%zd err=%d) — closing client", CH_ID, r, socket_get_error());
ca_close_client(cli);
return;
}
cli->send_offset += (int)r;
if (cli->send_offset >= cli->send_len) { cli->send_len = 0; cli->send_offset = 0; if (ca && ca->ua) uasync_set_socket_write(ca->ua, cli->socket_id, 0); }
}
static void ca_close_client(struct call_audio_client* cli) {
struct call_audio* ca = cli ? cli->ca : NULL;
if (!cli || cli->closing) return;
cli->closing = 1;
if (ca && ca->ua && cli->socket_id) uasync_remove_socket_t(ca->ua, cli->fd);
if (cli->fd != SOCKET_INVALID) { socket_close_wrapper(cli->fd); cli->fd = SOCKET_INVALID; }
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio client closed call_id=%016llx", CH_ID, (unsigned long long)cli->call_id);
}
static void ca_read_callback(socket_t fd, void* arg) {
struct call_audio_client* cli = (struct call_audio_client*)arg;
if (!cli || cli->closing) return;
uint8_t tmp[4096];
ssize_t r = recv(fd, tmp, (int)sizeof(tmp), 0);
if (r <= 0) { ca_close_client(cli); return; }
if (cli->recv_len + (int)r > AUDIO_RECV_BUF_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: recv buffer overflow — closing", CH_ID);
ca_close_client(cli);
return;
}
memcpy(cli->recv_buf + cli->recv_len, tmp, (size_t)r);
cli->recv_len += (int)r;
ca_parse_frames(cli);
}
static void ca_accept_callback(socket_t fd, void* arg) {
struct call_audio* ca = (struct call_audio*)arg;
struct sockaddr_storage addr; socklen_t alen = sizeof(addr);
socket_t cfd = accept(fd, (struct sockaddr*)&addr, &alen);
if (cfd == SOCKET_INVALID) {
int e = socket_get_error();
if (e != ERR_AGAIN && e != ERR_WOULDBLOCK) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: accept failed: %s", CH_ID, socket_strerror(e));
return;
}
socket_set_nonblocking(cfd);
if (ca->client_count >= AUDIO_MAX_CLIENTS) { socket_close_wrapper(cfd); DEBUG_WARN(DEBUG_CATEGORY_CALL, "%s: max clients reached", CH_ID); return; }
struct call_audio_client* cli = u_calloc(1, sizeof(*cli));
if (!cli) { socket_close_wrapper(cfd); return; }
cli->fd = cfd;
cli->ca = ca;
cli->socket_id = uasync_add_socket_t(ca->ua, cfd, ca_read_callback, ca_write_callback, NULL, "call_client", cli);
if (!cli->socket_id) { u_free(cli); socket_close_wrapper(cfd); DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: failed to register client fd", CH_ID); return; }
cli->next = ca->clients; ca->clients = cli; ca->client_count++;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio client connected fd=%d total=%d", CH_ID, (int)cfd, ca->client_count);
}
/* ── периодическая очистка закрытых клиентов ── */
static void ca_cleanup(void* arg) {
struct call_audio* ca = (struct call_audio*)arg;
ca->cleanup_timer = NULL;
if (!ca->running) return;
struct call_audio_client** prev = &ca->clients;
while (*prev) {
if ((*prev)->closing) {
struct call_audio_client* dead = *prev; *prev = dead->next;
if (dead->fd != SOCKET_INVALID) socket_close_wrapper(dead->fd);
u_free(dead); ca->client_count--;
} else prev = &(*prev)->next;
}
ca->cleanup_timer = uasync_set_timeout(ca->ua, 5000, ca, ca_cleanup, "ca_cleanup");
if (!ca->cleanup_timer) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: cleanup timer scheduling failed", CH_ID);
}
/* ── init / destroy ── */
int call_headless_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
const char* bind_ip, int port) {
if (!ua || !inst || !bind_ip || port <= 0 || port > 65535) return -1;
struct call_audio* ca = ca_of(inst);
if (ca && ca->running) return 0;
if (!ca) {
ca = u_calloc(1, sizeof(*ca));
if (!ca) return -1;
inst->call_audio = ca;
}
ca->ua = ua; ca->inst = inst;
ca->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (ca->listen_fd == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: socket() failed: %s", CH_ID, socket_strerror(socket_get_error())); return -1; }
socket_set_reuseaddr(ca->listen_fd, 1);
socket_set_nonblocking(ca->listen_fd);
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET; sin.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, bind_ip, &sin.sin_addr) != 1) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
if (bind(ca->listen_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: bind(%s:%d) failed: %s", CH_ID, bind_ip, port, socket_strerror(socket_get_error()));
socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1;
}
if (listen(ca->listen_fd, 5) < 0) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
ca->listen_sock_id = uasync_add_socket_t(ua, ca->listen_fd, ca_accept_callback, NULL, NULL, "call_listen", ca);
if (!ca->listen_sock_id) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; return -1; }
call_set_media_recv_cb(inst, ca_media_recv, ca);
ca->cleanup_timer = uasync_set_timeout(ua, 5000, ca, ca_cleanup, "ca_cleanup");
if (!ca->cleanup_timer) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: cleanup timer scheduling failed", CH_ID);
ca->running = 1;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio socket listening on %s:%d", CH_ID, bind_ip, port);
return 0;
}
void call_headless_destroy(struct UTUN_INSTANCE* inst) {
struct call_audio* ca = ca_of(inst);
if (!ca || !ca->running) return;
ca->running = 0;
if (ca->cleanup_timer) { uasync_cancel_timeout(ca->ua, ca->cleanup_timer); ca->cleanup_timer = NULL; }
call_set_media_recv_cb(inst, NULL, NULL);
if (ca->listen_sock_id && ca->ua) uasync_remove_socket_t(ca->ua, ca->listen_fd);
if (ca->listen_fd != SOCKET_INVALID) { socket_close_wrapper(ca->listen_fd); ca->listen_fd = SOCKET_INVALID; }
if (ca->enc) { opus_codec_encoder_destroy(ca->enc); ca->enc = NULL; }
if (ca->dec) { opus_codec_decoder_destroy(ca->dec); ca->dec = NULL; }
struct call_audio_client* c = ca->clients;
while (c) {
struct call_audio_client* n = c->next;
ca_close_client(c);
u_free(c);
c = n;
}
ca->clients = NULL; ca->client_count = 0;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio socket destroyed (enc=%u dec=%u enc_drop=%u dec_drop=%u)",
CH_ID, ca->c_enc_frames, ca->c_dec_frames, ca->c_enc_dropped, ca->c_dec_dropped);
}