Correct call routing, media channels and API documentation
This commit is contained in:
1 parent
88e930cc0a
commit
aca6364378
2 files changed
+67
-27
No files matched your search
+64
-26
@@ -1,17 +1,18 @@
|
||||
# P2P аудио-звонок пользователю — архитектура
|
||||
# P2P аудио- и видеозвонок пользователю — архитектура
|
||||
|
||||
Статус: реализовано. Звонок использует роутер, CM подготавливает оптимальный путь.
|
||||
|
||||
## 1. Область
|
||||
|
||||
Голос 1:1. Звонок можно инициировать **из CHAT-группы или из p2p-чата (DM)** — модуль
|
||||
не привязан к типу группы: он оперирует `group_id` + `peer_node_id` (для DM `group_id` —
|
||||
DM-группа, маршрутизация через `etcp_router` идентична). Протокол расширяем под видео
|
||||
(track-модель), реализуется только аудио. Работает desktop↔desktop и desktop↔android
|
||||
Аудио и видео 1:1. Звонок можно инициировать **из CHAT-группы или из p2p-чата (DM)**.
|
||||
Модуль оперирует `group_id` + `peer_node_id`. Для `dm:<conv_id>` функция `dm_call_route`
|
||||
выбирает общую CHAT-группу с живым BGP-маршрутом к собеседнику; отдельная DM-группа не создаётся.
|
||||
Аудио и видео используют отдельные каналы `etcp_router`; возможности видеокодеков
|
||||
согласуются в активном звонке (см. [call_video.txt](call_video.txt)). Работает desktop↔desktop и desktop↔android
|
||||
(обе стороны компилируют общий стек `etcp_router` / `topo_group` (BGP) / `conn_mgr`).
|
||||
|
||||
Полный цикл: проверка онлайна (BGP) → INVITE → звонок → ACCEPT/DECLINE →
|
||||
аудиопоток через роутер → HANGUP. CM инициируется вызывающим при старте, вызываемым при принятии вызова.
|
||||
аудиопоток и опциональное видео через роутер → HANGUP. CM инициируется вызывающим при старте, вызываемым при принятии вызова.
|
||||
|
||||
### Входящие вызовы и телефонная занятость
|
||||
|
||||
@@ -42,11 +43,12 @@ Android обычного звонка показывает своё уведом
|
||||
|
||||
| Файл | Язык | Сборки | Назначение |
|
||||
|---|---|---|---|
|
||||
| `src/call/call_proto.h` | C | везде | wire-формат, subcmd, svc_id `ETCP_RT_ID_CALL 0x36` |
|
||||
| `src/call/call_proto.h` | C | везде | wire-формат сигналинга (`0x36`) и аудио (`0x3F`), subcmd |
|
||||
| `src/call/call.h` / `call.c` | C | везде (autotools + GUI) | сигналинг, реестр сессий, таймеры, watchdog (20с по приёму), релей медиа с backpressure, счётчики |
|
||||
| `src/call/call_audio.h/c` | C | только GUI-сборки | Opus encode/decode, фрейминг, pull-обвязка с тонами; регистрация через `call_audio_ops` |
|
||||
| `src/call/call_tones.h/c` | C | только GUI-сборки | генератор тонов (glitch/ended), синус |
|
||||
| `src/call/call_jitter.h/cpp` | C++ | только GUI-сборки | SoundTouch джиттер-буфер, C API |
|
||||
| `src/call/call_video.h/c`, `call_video_transport.h/c` | C | везде | формат видеотрека, согласование возможностей и передача кадров отдельными каналами |
|
||||
|
||||
Autotools-сборка демона остаётся C-only (`.cpp` не компилируется); сигналинг `call.c`
|
||||
присутствует и в демоне, аудиопроцессинг — только там, где есть GUI.
|
||||
@@ -60,11 +62,22 @@ Desktop направляет сообщения FFmpeg и Qt в общий `debu
|
||||
Отсутствующая desktop-запись приложения создаётся в XDG data applications для регистрации в portal.
|
||||
|
||||
|
||||
## 3. Протокол (сигналинг svc_id 0x36, первый байт payload = subcmd)
|
||||
## 3. Протокол
|
||||
|
||||
Формат приёма: `entry->dgram[0]=svc_id`, служебный заголовок роутера до
|
||||
Каналы роутера независимы:
|
||||
|
||||
| svc_id | Константа | Назначение |
|
||||
|---|---|---|
|
||||
| `0x36` | `ETCP_RT_ID_CALL` | сигналинг звонка |
|
||||
| `0x3D` | `ETCP_RT_ID_CALL_VIDEO_CONTROL` | согласование и восстановление видеотрека |
|
||||
| `0x3E` | `ETCP_RT_ID_CALL_VIDEO` | видеокадры |
|
||||
| `0x3F` | `ETCP_RT_ID_CALL_AUDIO` | аудиокадры `CALL_MEDIA` |
|
||||
|
||||
Формат приёма сигналинга и аудио: `entry->dgram[0]=svc_id`, служебный заголовок роутера до
|
||||
`ROUTER_SVC_PAYLOAD_OFF=26`, затем subcmd и тело команды.
|
||||
|
||||
Сигналинг (`0x36`):
|
||||
|
||||
```
|
||||
CALL_INVITE 0x01 {call_id:8, group_id:8, num_tracks:1, force:1, expires_us:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]}
|
||||
CALL_RINGING 0x02 {call_id:8}
|
||||
@@ -75,11 +88,16 @@ CALL_CANCEL 0x06 {call_id:8}
|
||||
CALL_HANGUP 0x07 {call_id:8, reason:1}
|
||||
CALL_END_ACK 0x08 {call_id:8}
|
||||
CALL_PHONE_BUSY 0x09 {call_id:8}
|
||||
```
|
||||
|
||||
Аудио (`0x3F`):
|
||||
|
||||
```
|
||||
CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, capture_pts_us:8, capture_epoch:8, codec_delay_samples:4, payload}
|
||||
```
|
||||
|
||||
- `call_id` — случайный uint64, генерит вызывающий; идентифицирует сессию на обеих сторонах.
|
||||
- `group_id` — CHAT-группа или DM-группа (общая маршрутизация).
|
||||
- `group_id` — CHAT-группа; для DM-звонка выбирается общая группа участников с живым BGP-маршрутом.
|
||||
- Сигналинг: `etcp_route_send(..., ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT)`.
|
||||
- Сигналинг, аудио и видео: обязательные `ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT` между конечными узлами.
|
||||
- AES-128-CCM шифрует тело, Ed25519 подписывает заголовок роутера и шифртекст. Посредники не имеют ключа для расшифровки.
|
||||
@@ -89,7 +107,8 @@ CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, capture_pts_us:8, capt
|
||||
Пустые служебные пакеты роутера не являются медиаданными. Перехода на открытое медиа при ошибках нет.
|
||||
- Каждый пакет получает случайный nonce(13) от OpenSSL CSPRNG; ошибка RNG прекращает шифрование.
|
||||
Надбавка к сервисным данным: nonce(13) + CRC(4) + CCM tag(16) + Ed25519(64) = 97 байт.
|
||||
- `CALL_MEDIA` — общий контейнер; `track_id` оставляет место видео (`type=VIDEO`).
|
||||
- `CALL_MEDIA` переносит только аудио (`track_id=CALL_TRACK_AUDIO`); остальные значения приёмник отклоняет.
|
||||
Видео имеет отдельный формат и транспорт: `call_video.h`, `call_video_transport.h`, [call_video.txt](call_video.txt).
|
||||
- Аудио: Opus 48kHz mono, 20ms (960 сэмплов), ~24–32 kbps. ~50 кадров/с.
|
||||
- Заголовок медиа — 36 байт с subcmd. `ts_ms` — время отправки для оценки сетевого джиттера;
|
||||
`capture_pts_us` — время первого входного сэмпла в монотонной шкале отправителя, `INT64_MIN` при отсутствии метки.
|
||||
@@ -375,27 +394,45 @@ CHAT_EVT_CALL_ERROR 38 [call_id:8][err:1][text:var]
|
||||
CHAT_EVT_CALL_AUDIO_ROUTE 39 (Android: текущий маршрут/наличие гарнитуры)
|
||||
```
|
||||
|
||||
## 10. Публичный API (call.h)
|
||||
## 10. Основные функции публичного API
|
||||
|
||||
```
|
||||
int call_start(ch_id, peer_node_id, out_call_id); // caller (post в uasync)
|
||||
void call_accept(call_id); // callee
|
||||
void call_decline(call_id);
|
||||
void call_hangup(call_id);
|
||||
int call_is_online(ch_id, peer_node_id); // BGP-проверка для UI
|
||||
// аудио-поток (общий процессинг в C-ядре):
|
||||
int call_encode_pcm(call_id, pcm, n, opus_out, cap); // single-writer: audio thread
|
||||
int call_send_encoded(call_id, opus, len); // post → uasync → tx_q + flush
|
||||
int call_pull_pcm(call_id, pcm_out, max); // single-writer: audio thread (ajb+tones)
|
||||
void call_release(call_id); // post → uasync: освободить аудио-объекты
|
||||
`call.h`: сигналинг и отправка готового Opus выполняются в uasync-потоке.
|
||||
Из других потоков команды передаются через `uasync_post`/мост GUI и соответствующие `call_*_trampoline`.
|
||||
`ch_id` — идентификатор CHAT-группы либо `dm:<conv_id>`; `out_call_id` — значение, заранее сгенерированное вызывающим.
|
||||
|
||||
```c
|
||||
void call_start(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id, uint64_t out_call_id);
|
||||
void call_accept(struct UTUN_INSTANCE* inst, uint64_t call_id);
|
||||
void call_decline(struct UTUN_INSTANCE* inst, uint64_t call_id);
|
||||
void call_hangup(struct UTUN_INSTANCE* inst, uint64_t call_id);
|
||||
int call_is_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t peer_node_id);
|
||||
int call_send_media(struct UTUN_INSTANCE* inst, uint64_t call_id, const uint8_t* opus, int len,
|
||||
const struct call_capture_time* timing);
|
||||
```
|
||||
|
||||
`call_init / call_destroy` — вызываются из `chat_core_init / chat_core_destroy`.
|
||||
`call_audio.h`: движок сам кодирует PCM и ставит отправку Opus в uasync.
|
||||
Захват и воспроизведение используют по одному аудиопотоку на направление.
|
||||
|
||||
```c
|
||||
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t capture_pts_us);
|
||||
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us);
|
||||
int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples);
|
||||
void call_audio_release(uint64_t call_id);
|
||||
```
|
||||
|
||||
`feed_pcm` принимает ровно 960 отсчётов; `feed_prepared_pcm` — 960 отсчётов после платформенного AEC
|
||||
и маску mute. `call_audio_release` вызывается в GUI-потоке после остановки обоих направлений I/O.
|
||||
Создание движка — `call_audio_start`, для подготовленного PCM — `call_audio_start_prepared`.
|
||||
Видео настраивается через `call_video_configure`, кадры отправляются через `call_video_send_frame`
|
||||
в uasync-потоке; полный контракт — в `call_video_transport.h`.
|
||||
|
||||
`call_init / call_destroy` вызываются из `chat_service_start / chat_service_stop` в `utun_instance.c`:
|
||||
инициализация после `dm_core_init`, освобождение перед `dm_core_destroy`.
|
||||
|
||||
## 11. Платформенный I/O
|
||||
|
||||
- **Desktop:** `tools/chatgui/src/call_audio_engine.cpp` — miniaudio full-duplex
|
||||
(capture → `call_encode_pcm`, `call_pull_pcm` → playback) + окно звонка (UI)
|
||||
(capture через VoiceAudioIo → `call_audio_feed_prepared_pcm`, `call_audio_pull_pcm` → playback) + окно звонка (UI)
|
||||
+ события в `gui_bridge_impl.cpp`.
|
||||
- **Android:** JNI `nativeCall*` + Kotlin `AudioRecord`/`AudioTrack`.
|
||||
**Переключение источника:** `AudioManager` (API31+ `setCommunicationDevice` +
|
||||
@@ -412,7 +449,8 @@ void call_release(call_id); // post → uasync: о
|
||||
mmx_optimized, sse_optimized) как C++. `call.c` попадёт через GLOB `src/*.c`.
|
||||
- `tools/chatgui-android/libutun_lite/CMakeLists.txt` + `utun_sources.cmake`:
|
||||
`LANGUAGES C CXX`, те же файлы.
|
||||
- `src/transport_layer/etcp_api.h`: `#define ETCP_RT_ID_CALL 0x34`.
|
||||
- `src/transport_layer/etcp_api.h`: `ETCP_RT_ID_CALL=0x36`, `ETCP_RT_ID_CALL_VIDEO_CONTROL=0x3D`,
|
||||
`ETCP_RT_ID_CALL_VIDEO=0x3E`, `ETCP_RT_ID_CALL_AUDIO=0x3F`.
|
||||
- `lib/debug_config.h`: `DEBUG_CATEGORY_CALL 31`, `DEBUG_CATEGORY_COUNT` → 32.
|
||||
|
||||
## 13. Порядок задач
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// call_proto.h — wire-формат и константы P2P аудио-звонка (svc_id ETCP_RT_ID_CALL 0x36)
|
||||
// call_proto.h — wire-формат сигналинга (ETCP_RT_ID_CALL 0x36) и аудио (ETCP_RT_ID_CALL_AUDIO 0x3F).
|
||||
// CALL_MEDIA несёт только CALL_TRACK_AUDIO; видео использует call_video.h и отдельные каналы
|
||||
// ETCP_RT_ID_CALL_VIDEO_CONTROL 0x3D / ETCP_RT_ID_CALL_VIDEO 0x3E (call_video_transport.h).
|
||||
//
|
||||
// Приём/отправка через etcp_router. На проводе первый байт payload = subcmd.
|
||||
// Сигналинг и медиа требуют ROUTE_CRYPTO_SIGN|ROUTE_CRYPTO_ENCRYPT: шифрование тела
|
||||
|
||||
Reference in new issue
Block a user