Files
utun2/src/call/call.h
T

174 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// call — управление P2P аудио/видеозвонком одного узла.
// Начать вызов через start, ответить через accept/decline, завершить через hangup.
// Состояния и результаты приходят событиями CHAT_EVT_CALL_* из chat_event.h.
// Аудио подключается через call_audio.h, видеотрек — через call_video_transport.h или call_video_port.h.
//
// Модуль оперирует (group_id, peer_node_id): звонок можно инициировать из
// CHAT-группы или из DM (group_id — группа маршрутизации, как в dm_core).
// Сигналинг, аудио и видео идут через etcp_router с обязательными ROUTE_CRYPTO_SIGN|ENCRYPT; соединение
// оптимизируется через параллельные пробы conn_mgr (DIRECT/REVERSE/INDIRECT).
// Оба узла должны оставаться допущенными участниками группы доставки. При отзыве доступа
// или остановке группы звонок завершается, очереди медиа очищаются. Группа активного звонка
// не меняется; новый звонок из DM заново выбирает доступную общую группу через dm_call_route.
//
// Все функции, кроме явно отмеченных, — в uasync-потоке. GUI/headless вызывают через trampoline
// (gui_bridge_post_uasync_fn / uasync_post).
#ifndef CALL_H
#define CALL_H
#include <stdint.h>
#include "call_jitter.h"
#include "call_clock.h"
struct UTUN_INSTANCE;
#ifdef __cplusplus
extern "C" {
#endif
// ── жизненный цикл ──
/* Зарегистрировать сигналинг/медиа в роутере и создать реестр сессий после dm_core_init.
* inst/ua/роутер должны существовать. 0 = успех (повтор идемпотентен), -1 = ошибка с откатом. */
int call_init(struct UTUN_INSTANCE* inst);
/* Остановить сессии, отменить таймеры/CM/waiters и снять сервисы роутера перед dm_core_destroy.
* Сначала закрыть call_headless. Без отправки завершающих команд и событий ENDED; NULL/повтор — no-op.
* Регистрация call_audio_ops у ядра сохраняется, DSP и устройства frontend здесь не освобождаются. */
void call_destroy(struct UTUN_INSTANCE* inst);
/* uasync: после изменения участников/остановки CHAT-группы завершить затронутые звонки
* с CONNECT_FAIL, отменить их медиа/CM/повторы отозванным пирам. Остальные группы не затрагивает. */
void call_revalidate_members(struct UTUN_INSTANCE* inst, uint64_t group);
// ── сигналинг (uasync-поток) ──
/* Caller: ch_id — группа либо "dm:<conv_id>" (BGP-группа выбирается при запуске).
* out_call_id — ненулевой уникальный uint64, генерит вызывающий.
* При любом незавершённом обычном звонке/приглашении рации новый ID получает ENDED/BUSY без создания сессии.
* Уже зарегистрированный ID, включая ENDED до очистки, отклоняется без событий и изменения текущего звонка.
* После ENDED новый уникальный ID разрешён; результат запуска приходит через CHAT_EVT_CALL_*. */
void call_start (struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id, uint64_t out_call_id);
/* Вызвать все доступные устройства той же проверенной devgroup, что peer, в CHAT-канале.
* Список фиксируется ядром; DM здесь не допускается. Остальные условия как у call_start. */
void call_start_devices(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer, uint64_t call_id);
/* Снимок адресатов: devices=0 — один peer; иначе его devgroup. Только проверенные участники
* CHAT с живым маршрутом, без self. Возврат count>=0 либо -1, capacity не обрезает список молча. */
int call_resolve_targets(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t peer, int devices,
uint64_t* out, unsigned capacity);
// Callee: ответить только RINGING и ждать SELECT инициатора; медиа разрешает последующее CALL_ACCEPTED.
void call_accept (struct UTUN_INSTANCE* inst, uint64_t call_id);
// Callee: отклонить существующую незавершённую сессию, события DECLINED и ENDED с причиной DECLINE.
void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id);
// Любая сторона: завершить существующую сессию; неизвестный ID/ENDED — no-op.
void call_hangup (struct UTUN_INSTANCE* inst, uint64_t call_id);
// 1 = есть обычная сессия не в ENDED, включая WAIT_DECISION; 0 = свободен/модуль не запущен. Только uasync.
int call_busy(struct UTUN_INSTANCE* inst);
// Caller в WAIT_DECISION: подтвердить force и повторить INVITE с прежним ID/deadline. Только uasync.
void call_continue(struct UTUN_INSTANCE* inst, uint64_t call_id);
// Независимые настройки входящих вызовов; запрет выбранного типа приоритетнее проверки занятости.
enum call_policy {
CALL_POLICY_PUBLISH_PHONE = 1, CALL_POLICY_WARN_PHONE = 2, CALL_POLICY_REJECT_PHONE = 4,
CALL_POLICY_BLOCK_CALLS = 8, CALL_POLICY_BLOCK_RADIO = 16
};
/* Живое состояние ОС: 0=неизвестно, 1=свободен, 2=занят; policy — маска call_policy.
* Только uasync. Изменение статуса публикуется в BGP, если включён PUBLISH_PHONE. */
void call_set_phone_state(struct UTUN_INSTANCE* inst, int state, unsigned policy);
/* Флаги BGP PHONE_KNOWN/BUSY с учётом звонка приложения и разрешения публикации.
* Desktop без звонка свободен; у mobile состояние ОС может быть неизвестно. Только uasync. */
uint8_t call_phone_status(struct UTUN_INSTANCE* inst);
// ── медиа (релей Opus-кадров, uasync-поток) ──
/* Отправить закодированный Opus-кадр пиру (оборачивает в CALL_MEDIA).
* Только ACTIVE: len=1..985, opus/timing копируются до возврата; timing=NULL — без capture PTS.
* seq/ts выставляются автоматически. 0 = принят в локальную FIFO, -1 = неверные данные/нет ACTIVE/ошибка памяти.
* Результат не подтверждает доставку: FIFO750 удаляет старейший кадр при заполнении, роутер может отказать при drain. */
int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t* opus, int len,
const struct call_capture_time* timing);
/* Коллбэк приёма медиа: вызывается в uasync-потоке на каждый Opus-кадр пира.
* Используется headless-аудиосокетом; opus заимствован до возврата callback. */
typedef void (*call_media_recv_fn)(struct UTUN_INSTANCE* inst, uint64_t call_id,
const uint8_t* opus, int len, void* arg);
/* Один callback в текущем call_ctx, fn=NULL снимает; arg заимствован до замены/destroy.
* Требует call_init; stop/start чата сбрасывает этот callback. Независим от сохраняемого у ядра audio_ops. */
void call_set_media_recv_cb(struct UTUN_INSTANCE* inst, call_media_recv_fn fn, void* arg);
/* ── аудио-движок (опционально, GUI-сборки) ──
* Сервисный аудио-контур (codec + джиттер-буфер + тоны) регистрируется через
* call_set_audio_ops. call.c зовёт on_media на каждый принятый медиа-кадр (с seq и временем подачи пачки в Router)
* и get_stats для события CALL_STATS. Демон без аудио (headless) — ops не ставит. */
struct call_history_tx;
struct call_audio_ops {
// uasync: Opus/PTS заимствованы; ts_ms — время пачки, packet_start=1 только у её первого кадра.
void (*on_media)(struct UTUN_INSTANCE* inst, uint64_t call_id,
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len, const struct call_capture_time* timing, int packet_start);
/* uasync-поток: метрики (buffer_ms, tempo_x100=100 для 1x, накопительные dropped/underruns, range).
* Выходы необязательны. 0 = снимок получен, -1 = нет активного DSP; реализация может потреблять события диагностики. */
int (*get_stats)(uint64_t call_id, int* buffer_ms, int* tempo_x100,
uint32_t* dropped, uint32_t* underruns, struct call_jitter_range* range);
/* uasync: применить opus_codec_preset (0/1/2 = 16/32/64 кбит/с) к текущему и следующим звонкам.
* Необязательный callback; 0 = применено, -1 = ошибка, прежний битрейт сохранён. */
int (*set_codec_preset)(struct UTUN_INSTANCE* inst, int preset);
/* uasync: диагностическая копия сумм очередей, callback не меняет сетевое управление. */
void (*record_tx)(uint64_t call_id, const struct call_history_tx* snapshot);
};
/* Только uasync: сохранить заимствованный ops у ядра (NULL снимает регистрацию), допустимо до запуска чата.
* Объект живёт до снятия/уничтожения ядра. Stop/start чата сохраняет регистрацию, состояние звонка — нет. */
void call_set_audio_ops(struct UTUN_INSTANCE* inst, const struct call_audio_ops* ops);
/* Аргумент call_send_media_trampoline: heap-объект из u_malloc/u_calloc, callback освобождает его через u_free.
* Постить в inst->ua; inst должен жить до выполнения. Не заменяет эпохи/лимит TX в call_audio.h.
* len=1..256; timing передаётся в call_send_media как есть (без PTS задать timing.pts_us=INT64_MIN). */
struct call_media_arg {
struct UTUN_INSTANCE* inst; // Заимствованное ядро, живёт до callback.
uint64_t call_id; // ID ACTIVE-сессии на момент выполнения callback.
uint16_t len; // Число заполненных байтов opus, 1..256.
struct call_capture_time timing; // Capture PTS/epoch/codec delay, копия метаданных кадра.
uint8_t opus[256]; // Встроенная копия закодированного кадра.
};
void call_send_media_trampoline(void* arg);
/* Trampoline выполняются в uasync, вызывают одноимённый API и освобождают arg через u_free.
* arg — соответствующий ненулевой heap-объект, inst живёт до callback. Постановка не удерживает ядро.
* call_start_arg.ch_id должен содержать завершающий NUL; остальные действия используют call_id_arg. */
struct call_start_arg {
struct UTUN_INSTANCE* inst; // Заимствованное ядро, живёт до callback.
char ch_id[64]; // Десятичный ID группы либо dm:<conv_id>, с завершающим NUL.
uint64_t peer_node_id; // Ненулевой ID собеседника, не собственного узла.
uint8_t devices; // 1 — вызвать devgroup указанного устройства, 0 — только устройство.
uint64_t call_id; // Новый уникальный ненулевой ID, генерирует вызывающий.
};
struct call_id_arg {
struct UTUN_INSTANCE* inst; // Заимствованное ядро, живёт до callback.
uint64_t call_id; // ID сессии для accept/decline/hangup/continue.
};
void call_start_trampoline (void* arg);
void call_accept_trampoline (void* arg);
void call_decline_trampoline(void* arg);
void call_hangup_trampoline (void* arg);
void call_continue_trampoline(void* arg);
#ifdef __cplusplus
}
#endif
#endif /* CALL_H */