Files
siprouter/ts/call/leg.ts
Juergen Kunz f3e1c96872 initial commit — SIP B2BUA + WebRTC bridge with Rust codec engine
Full-featured SIP router with multi-provider trunking, browser softphone
via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML
noise suppression, Kokoro neural TTS announcements, and a Lit-based
web dashboard with live call monitoring and REST API.
2026-04-09 23:03:55 +00:00

105 lines
3.4 KiB
TypeScript

/**
* ILeg interface — abstract connection from a Call hub to an endpoint.
*
* Concrete implementations: SipLeg (SIP devices + providers) and WebRtcLeg (browsers).
* Shared RTP utilities (header building, clock rates) are also defined here.
*/
import { Buffer } from 'node:buffer';
import type dgram from 'node:dgram';
import type { IEndpoint } from '../sip/index.ts';
import type { TLegState, TLegType, ILegStatus } from './types.ts';
import type { IRtpTranscoder } from '../codec.ts';
import type { SipDialog } from '../sip/index.ts';
import type { SipMessage } from '../sip/index.ts';
// ---------------------------------------------------------------------------
// ILeg interface
// ---------------------------------------------------------------------------
export interface ILeg {
readonly id: string;
readonly type: TLegType;
state: TLegState;
/** The SIP Call-ID used by this leg (for CallManager routing). */
readonly sipCallId: string;
/** Where this leg sends/receives RTP. */
readonly rtpPort: number | null;
readonly rtpSock: dgram.Socket | null;
remoteMedia: IEndpoint | null;
/** Negotiated codec payload type (e.g. 9 = G.722, 111 = Opus). */
codec: number | null;
/** Transcoder for converting to this leg's codec (set by Call when codecs differ). */
transcoder: IRtpTranscoder | null;
/** Packet counters. */
pktSent: number;
pktReceived: number;
/** SIP dialog (SipLegs only, null for WebRtcLegs). */
readonly dialog: SipDialog | null;
/**
* Send an RTP packet toward this leg's remote endpoint.
* If a transcoder is set, the Call should transcode before calling this.
*/
sendRtp(data: Buffer): void;
/**
* Callback set by the owning Call — invoked when this leg receives an RTP packet.
* The Call uses this to forward to other legs.
*/
onRtpReceived: ((data: Buffer) => void) | null;
/**
* Handle an incoming SIP message routed to this leg (SipLegs only).
* Returns a SipMessage response if one needs to be sent, or null.
*/
handleSipMessage(msg: SipMessage, rinfo: IEndpoint): void;
/** Release all resources (sockets, peer connections, etc.). */
teardown(): void;
/** Status snapshot for the dashboard. */
getStatus(): ILegStatus;
}
// ---------------------------------------------------------------------------
// Shared RTP utilities
// ---------------------------------------------------------------------------
/** RTP clock increment per 20ms frame for each codec. */
export function rtpClockIncrement(pt: number): number {
if (pt === 111) return 960; // Opus: 48000 Hz x 0.02s
if (pt === 9) return 160; // G.722: 8000 Hz x 0.02s (SDP clock rate quirk)
return 160; // PCMU/PCMA: 8000 Hz x 0.02s
}
/** Build a fresh RTP header with correct PT, timestamp, seq, SSRC. */
export function buildRtpHeader(pt: number, seq: number, ts: number, ssrc: number, marker: boolean): Buffer {
const hdr = Buffer.alloc(12);
hdr[0] = 0x80; // V=2
hdr[1] = (marker ? 0x80 : 0) | (pt & 0x7f);
hdr.writeUInt16BE(seq & 0xffff, 2);
hdr.writeUInt32BE(ts >>> 0, 4);
hdr.writeUInt32BE(ssrc >>> 0, 8);
return hdr;
}
/** Codec name for status display. */
export function codecDisplayName(pt: number | null): string | null {
if (pt === null) return null;
switch (pt) {
case 0: return 'PCMU';
case 8: return 'PCMA';
case 9: return 'G.722';
case 111: return 'Opus';
case 101: return 'telephone-event';
default: return `PT${pt}`;
}
}