fix(readme): improve architecture and call flow documentation with Mermaid diagrams

This commit is contained in:
2026-04-11 19:20:14 +00:00
parent 37ba7501fa
commit c40c726dc3
4 changed files with 70 additions and 38 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 2026-04-11 - 1.20.5 - fix(readme)
improve architecture and call flow documentation with Mermaid diagrams
- Replace ASCII architecture and audio pipeline diagrams with Mermaid diagrams for better readability
- Document the WebRTC browser call setup sequence, including offer handling and session-to-call linking
## 2026-04-11 - 1.20.4 - fix(deps) ## 2026-04-11 - 1.20.4 - fix(deps)
bump @design.estate/dees-catalog to ^3.71.1 bump @design.estate/dees-catalog to ^3.71.1

View File

@@ -28,39 +28,26 @@ siprouter sits between your SIP trunk providers and your endpoints — hardware
## 🏗️ Architecture ## 🏗️ Architecture
``` ```mermaid
┌─────────────────────────────────────┐ flowchart TB
Browser Softphone Browser["🌐 Browser Softphone<br/>(WebRTC via WebSocket signaling)"]
(WebRTC via WebSocket signaling) │ Devices["📞 SIP Devices<br/>(HT801, desk phones, ATAs)"]
└──────────────┬──────────────────────┘ Trunks["☎️ SIP Trunk Providers<br/>(sipgate, easybell, …)"]
│ Opus/WebRTC
subgraph Router["siprouter"]
┌──────────────────────────────────────┐ direction TB
siprouter │ subgraph TS["TypeScript Control Plane"]
TSBits["Config · WebRTC Signaling<br/>REST API · Web Dashboard<br/>Voicebox Manager · TTS Cache"]
TypeScript Control Plane │ end
│ ┌────────────────────────────────┐ │ subgraph Rust["Rust proxy-engine (data plane)"]
│ │ Config · WebRTC Signaling │ │ RustBits["SIP Stack · Dialog SM · Auth<br/>Call Manager · N-Leg Mixer<br/>48kHz f32 Bus · Jitter Buffer<br/>Codec Engine · RTP Port Pool<br/>WebRTC Engine · Kokoro TTS<br/>Voicemail · IVR · Recording"]
│ REST API · Web Dashboard │ │ end
│ Voicebox Manager · TTS Cache │ │ TS <-->|"JSON-over-stdio IPC"| Rust
└────────────┬───────────────────┘ │ end
│ JSON-over-stdio IPC │
┌────────────┴───────────────────┐ │ Browser <-->|"Opus / WebRTC"| TS
Rust proxy-engine (data plane) │ │ Rust <-->|"SIP / RTP"| Devices
│ │ Rust <-->|"SIP / RTP"| Trunks
│ │ SIP Stack · Dialog SM · Auth │ │
│ │ Call Manager · N-Leg Mixer │ │
│ │ 48kHz f32 Bus · Jitter Buffer │ │
│ │ Codec Engine · RTP Port Pool │ │
│ │ WebRTC Engine · Kokoro TTS │ │
│ │ Voicemail · IVR · Recording │ │
│ └────┬──────────────────┬────────┘ │
└───────┤──────────────────┤───────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ SIP Devices │ │ SIP Trunk │
│ (HT801 etc) │ │ Providers │
└─────────────┘ └─────────────┘
``` ```
### 🧠 Key Design Decisions ### 🧠 Key Design Decisions
@@ -71,6 +58,37 @@ siprouter sits between your SIP trunk providers and your endpoints — hardware
- **Per-Session Codec Isolation** — Each call leg gets its own encoder/decoder/resampler/denoiser state — no cross-call corruption. - **Per-Session Codec Isolation** — Each call leg gets its own encoder/decoder/resampler/denoiser state — no cross-call corruption.
- **SDP Codec Negotiation** — Outbound encoding uses the codec actually negotiated in SDP answers, not just the first offered codec. - **SDP Codec Negotiation** — Outbound encoding uses the codec actually negotiated in SDP answers, not just the first offered codec.
### 📲 WebRTC Browser Call Flow
Browser calls are set up in a strict three-step dance — the WebRTC leg cannot be attached at call-creation time because the browser's session ID is only known once the SDP offer arrives:
```mermaid
sequenceDiagram
participant B as Browser
participant TS as TypeScript (sipproxy.ts)
participant R as Rust proxy-engine
participant P as SIP Provider
B->>TS: POST /api/call
TS->>R: make_call (pending call, no WebRTC leg yet)
R-->>TS: call_created
TS-->>B: webrtc-incoming (callId)
B->>TS: webrtc-offer (sessionId, SDP)
TS->>R: handle_webrtc_offer
R-->>TS: webrtc-answer (SDP)
TS-->>B: webrtc-answer
Note over R: Standalone WebRTC session<br/>(not yet attached to call)
B->>TS: webrtc_link (callId + sessionId)
TS->>R: link session → call
R->>R: wire WebRTC leg through mixer
R->>P: SIP INVITE
P-->>R: 200 OK + SDP
R-->>TS: call_answered
Note over B,P: Bidirectional Opus ↔ codec-transcoded<br/>audio flows through the mixer
```
--- ---
## 🚀 Getting Started ## 🚀 Getting Started
@@ -246,9 +264,17 @@ The `proxy-engine` binary handles all real-time audio processing with a **48kHz
### Audio Pipeline ### Audio Pipeline
``` ```mermaid
Inbound: Wire RTP → Jitter Buffer → Decode → Resample to 48kHz → Denoise (RNNoise) → Mix Bus flowchart LR
Outbound: Mix Bus → Mix-Minus → Resample to codec rate → Encode → Wire RTP subgraph Inbound["Inbound path (per leg)"]
direction LR
IN_RTP["Wire RTP"] --> IN_JB["Jitter Buffer"] --> IN_DEC["Decode"] --> IN_RS["Resample → 48 kHz"] --> IN_DN["Denoise (RNNoise)"] --> IN_BUS["Mix Bus"]
end
subgraph Outbound["Outbound path (per leg)"]
direction LR
OUT_BUS["Mix Bus"] --> OUT_MM["Mix-Minus"] --> OUT_RS["Resample → codec rate"] --> OUT_ENC["Encode"] --> OUT_RTP["Wire RTP"]
end
``` ```
- **Adaptive jitter buffer** — per-leg `BTreeMap`-based buffer keyed by RTP sequence number. Delivers exactly one frame per 20ms mixer tick in sequence order. Adaptive target depth starts at 3 frames (60ms) and adjusts between 26 frames based on observed network jitter. Handles hold/resume by detecting large forward sequence jumps and resetting cleanly. - **Adaptive jitter buffer** — per-leg `BTreeMap`-based buffer keyed by RTP sequence number. Delivers exactly one frame per 20ms mixer tick in sequence order. Adaptive target depth starts at 3 frames (60ms) and adjusts between 26 frames based on observed network jitter. Handles hold/resume by detecting large forward sequence jumps and resetting cleanly.

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: 'siprouter', name: 'siprouter',
version: '1.20.4', version: '1.20.5',
description: 'undefined' description: 'undefined'
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: 'siprouter', name: 'siprouter',
version: '1.20.4', version: '1.20.5',
description: 'undefined' description: 'undefined'
} }