Queue complete headless PCM frames and account for overflow drops

This commit is contained in:
evgeny committed 2026-10-26 15:51:35 +01:00
1 parent ae9d3f0103
commit 913ae531ec
2 files changed
+35 -14

No files matched your search

+33 -12
View File
@@ -42,6 +42,8 @@ struct call_audio_client {
uint8_t send_buf[AUDIO_SEND_BUF_SIZE];
int send_len;
int send_offset;
uint32_t pcm_dropped; /* Целые исходящие PCM-кадры, отброшенные при переполнении. */
uint64_t drop_log_tb; /* Ограничение частоты WARN: не чаще раза в секунду. */
int closing;
struct call_audio* ca;
struct call_audio_client* next;
@@ -66,6 +68,7 @@ struct call_audio {
uint32_t c_dec_frames;
uint32_t c_enc_dropped;
uint32_t c_dec_dropped;
uint32_t c_pcm_dropped;
};
static struct call_audio* ca_of(struct UTUN_INSTANCE* inst) {
@@ -76,12 +79,24 @@ static void ca_close_client(struct call_audio_client* cli);
/* ── отправка фрейма клиенту (с буферизацией как в control-сокете) ── */
/* Принимает целый ограниченный фрейм; short write сохраняет его остаток перед следующими кадрами. */
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;
struct call_audio* ca = cli->ca;
if (cli->send_len > 0) {
size_t space = sizeof(cli->send_buf) - (size_t)cli->send_len;
if (len > space) return;
if (len > space) {
++cli->pcm_dropped; ++ca->c_pcm_dropped;
uint64_t now = get_time_tb();
if (cli->pcm_dropped == 1 || now - cli->drop_log_tb >= 10000) {
cli->drop_log_tb = now;
DEBUG_WARN(DEBUG_CATEGORY_CALL,
"%s: PCM frame dropped call=%016llx fd=%d frame=%zu queued=%d offset=%d capacity=%zu dropped=%u total=%u",
CH_ID, (unsigned long long)cli->call_id, (int)cli->fd, len, cli->send_len, cli->send_offset,
sizeof(cli->send_buf), cli->pcm_dropped, ca->c_pcm_dropped);
}
return;
}
memcpy(cli->send_buf + cli->send_len, data, len);
cli->send_len += (int)len;
return;
@@ -105,13 +120,18 @@ static void ca_send_raw(struct call_audio_client* cli, const uint8_t* data, size
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);
if (len > CA_PCM_FRAME_BYTES || (len && !data)) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: invalid output frame call=%016llx type=%u len=%u data=%p",
CH_ID, (unsigned long long)call_id, type, len, (const void*)data);
return;
}
uint8_t frame[CALL_AUDIO_HDR_SIZE + CA_PCM_FRAME_BYTES];
frame[0] = type;
memcpy(frame + 1, &call_id, 8);
frame[9] = (uint8_t)(len >> 8);
frame[10] = (uint8_t)(len & 0xFF);
if (len) memcpy(frame + CALL_AUDIO_HDR_SIZE, data, len);
ca_send_raw(cli, frame, CALL_AUDIO_HDR_SIZE + len);
}
/* ── Opus-кодеки (одна активная звонка за раз) ── */
@@ -249,7 +269,8 @@ static void ca_close_client(struct call_audio_client* cli) {
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);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio client closed call_id=%016llx pcm_drop=%u",
CH_ID, (unsigned long long)cli->call_id, cli->pcm_dropped);
}
static void ca_read_callback(socket_t fd, void* arg) {
@@ -381,7 +402,7 @@ void call_headless_destroy(struct UTUN_INSTANCE* inst) {
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);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: audio socket destroyed (enc=%u dec=%u enc_drop=%u dec_drop=%u pcm_drop=%u)",
CH_ID, ca->c_enc_frames, ca->c_dec_frames, ca->c_enc_dropped, ca->c_dec_dropped, ca->c_pcm_dropped);
inst->call_audio = NULL; u_free(ca);
}
+2 -2
View File
@@ -14,8 +14,8 @@
// call_id хранится в порядке байтов хоста, len — старший байт первым; PCM — native int16_t.
// Это отдельный сервис с кодеками instance, без DSP/jitter движка call_audio.h.
// Все вызовы и callbacks выполняются в потоке ua; клиент обеспечивает темп и воспроизведение.
// Известный дефект 26.10.2026: переполнение исходящей очереди может оставить заголовок
// без PCM и нарушить фрейминг. Аудит/план исправления — doc/call_audit_findings.txt,раздел11.
// Исходящая очередь принимает header+PCM целиком; при переполнении отбрасывается целый кадр с WARN/счётчиком.
// Частичная отправка продолжается перед следующим кадром. Размер очереди ограничен, темп чтения задаёт клиент.
#ifndef CALL_HEADLESS_H
#define CALL_HEADLESS_H