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.
105 lines
3.4 KiB
TypeScript
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}`;
|
|
}
|
|
}
|