Queue complete headless PCM frames and account for overflow drops
This commit is contained in:
1 parent
ae9d3f0103
commit
913ae531ec
2 files changed
+35
-14
No files matched your search
+33
-12
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user