npm package: @kmholding/rtp-packetizer
Description: High-performance RTP packetizer with true SRC resampling, 8↔48kHz, 5 interpolation methods & real-time diagnostic
Author: K&M Holdings
Latest version: 2.0.2

# @kmholding/rtp-packetizer

A comprehensive TypeScript library for real-time audio processing, RTP (Real-Time Protocol) packet handling, and audio resampling. Perfect for VoIP applications, real-time audio streaming, and audio diagnostics.

---

## Features

- **RTP Packetization**: Fully-featured RTP packet creation, parsing, and management
- **Audio Processing**:
  - Volume gain control with clipping detection
  - Audio normalization and peak analysis
  - Fade in/fade out effects
  - Volume statistics (Peak, RMS, dBFS)
- **Codec Support**:
  - G.711 μ-law (PCMU)
  - G.711 A-law (PCMA)
  - 16-bit Linear PCM (L16)
- **Resampling**:
  - Manual interpolation methods (Linear, Cubic, Lagrange)
  - libsamplerate integration for high-quality resampling
  - Unified interface for easy switching
- **Network Diagnostics**:
  - Jitter and delay analysis
  - Packet loss detection
  - Quality rating and warnings
  - Timing analysis reports
- **Zero Dependencies**: Core functionality works without external native modules

---

## Installation

### npm

```bash
npm install @kmholding/rtp-packetizer
```

### yarn

```bash
yarn add @kmholding/rtp-packetizer
```

### bun

```bash
bun add @kmholding/rtp-packetizer
```

---

## Import Styles

The library supports multiple ways to import, allowing you to only import what you need:

### Main Entry Point (All exports)

```typescript
import { AudioStream, AudioGain, RTPHelper, CodecHelper } from '@kmholding/rtp-packetizer'
```

### Core Classes

```typescript
// Import audio processing classes
import {
	AudioStream,
	AudioGain,
	AudioResampling,
	AudioLibSampleRate,
	AudioUnifiedResampler,
	AudioDiagnostic,
} from '@kmholding/rtp-packetizer/core'
```

### Helper Utilities

```typescript
// Import low-level helper utilities
import { RTPHelper, CodecHelper, BufferHelper } from '@kmholding/rtp-packetizer/helpers'
```

### Constants

```typescript
// Import all constants and enums
import {
	RESAMPLING_METHODS,
	RESAMPLING_QUALITIES,
	RESAMPLING_PROVIDES,
	RTP_PAYLOAD_TYPES,
} from '@kmholding/rtp-packetizer/constants'
```

### Type Definitions

```typescript
// Import TypeScript interfaces and types
import type {
	AudioStream,
	RTPHeader,
	StreamingChannelConfig,
	StreamingStats,
	TimingAnalysisReport,
	GainResult,
	VolumeStats,
	QualityAnalysis,
} from '@kmholding/rtp-packetizer/types'
```

---

## Quick Start

### Basic RTP Streaming

```typescript
import { AudioStream } from '@kmholding/rtp-packetizer/core'
import { RTP_PAYLOAD_TYPES } from '@kmholding/rtp-packetizer/constants'

// Create streaming instance
const libStreamCore = new AudioStream({
	payloadType: RTP_PAYLOAD_TYPES.L16, // 16-bit PCM
	prebufferFrames: 6,
})

// Initialize channel
const channelID = libStreamCore.initialize({
	channelID: 'call-001',
	externalHost: '192.168.1.100',
	externalPort: 5060,
	sampleRate: 8000,
	frameSize: 160, // 20ms at 8kHz
	enableLogging: true,
})

// Prepare audio (16-bit PCM)
const audioBuffer = Buffer.from([
	/* 16-bit PCM data */
])

// Queue frames with optional RTP params
const framesQueued = libStreamCore.queueAudioFrames(channelID, audioBuffer, {
	startSeqNum: 1000,
	startTimestamp: 0,
	ssrc: Math.floor(Math.random() * 0xffffffff),
})

console.log(`Queued ${framesQueued} frames`)

// Get and send packets
let packet

while ((packet = libStreamCore.getNextPacket(channelID))) {
	console.log(`Seq: ${packet.seqNum}, Timestamp: ${packet.timestamp}`)
}

// Check stats
const stats = libStreamCore.getChannelStats(channelID)

console.log(`Bytes sent: ${stats?.totalBytesSent}`)
```

### Audio Gain Control

```typescript
import { AudioGain } from '@kmholding/rtp-packetizer/core'
import { BufferHelper } from '@kmholding/rtp-packetizer/helpers'

const libBufferHelper = new BufferHelper()
const libGainCore = new AudioGain({
	gain: 1.2, // 20% volume increase
	peak: 0.9, // Target peak level
})

// Get audio as buffer
const audioBuffer = Buffer.from([
	/* 16-bit PCM */
])

// Apply amplification
const result = libGainCore.amplify(audioBuffer, 1.2)

console.log(`Clipping: ${result.clippingRate.toFixed(2)}% (${result.clippingCount} samples)`)

// Analyze volume
const samples = libBufferHelper.bufferToInt16(result.buffer)
const stats = libGainCore.getVolumeStats(samples)

console.log(`Peak: ${stats.peak}, RMS: ${stats.rms}, dBFS: ${stats.dbfs.toFixed(2)}`)

// Normalize to 95% peak
const normalized = libGainCore.normalize(samples, 0.95)

// Apply fade effects
const fadedIn = libGainCore.fadeIn(normalized, 4000) // Fade in over 4000 samples
const fadedOut = libGainCore.fadeOut(fadedIn, 4000) // Fade out
```

### Resampling

```typescript
import { AudioUnifiedResampler } from '@kmholding/rtp-packetizer/core'
import { RESAMPLING_PROVIDES } from '@kmholding/rtp-packetizer/constants'

// Fast manual resampling
const libUnifiedManualCore = new AudioUnifiedResampler({
	provide: RESAMPLING_PROVIDES.MANUAL,
	method: 'LAGRANGE', // High quality interpolation
})

const audioData = new Int16Array([
	/* 16000 Hz audio */
])
const resampled = await libUnifiedManualCore.resample(audioData, 16000, 8000)

console.log(`Resampled: ${audioData.length} → ${resampled.length} samples`)

// High-quality libsamplerate
const libUnifiedHQCore = new AudioUnifiedResampler({
	provide: RESAMPLING_PROVIDES.LIBSAMPLERATE,
	quality: 'SRC_SINC_BEST_QUALITY',
	channels: 1,
})

const hqResult = await libUnifiedHQCore.resample(audioData, 16000, 8000)

console.log(`Best quality resampling done`)

libUnifiedHQCore.destroy() // Important: cleanup resources
```

### Network Diagnostics

```typescript
import { AudioDiagnostic } from '@kmholding/rtp-packetizer/core'

const libDiagnosticCore = new AudioDiagnostic({ frameIntervalMs: 20 })

// Collect packet timing logs
const timingLogs = [
	{ seq: 1, ts: 1000, sentAt: Date.now() },
	{ seq: 2, ts: 1160, sentAt: Date.now() + 20 },
	{ seq: 3, ts: 1320, sentAt: Date.now() + 25 }, // 5ms jitter
	{ seq: 4, ts: 1480, sentAt: Date.now() + 45 }, // 25ms jitter
	// ... more packets collected during streaming
]

// Analyze quality
const report = libDiagnosticCore.analyze(timingLogs, 20)

if (report) {
	console.log(`Quality: ${report.quality.rating}`)
	console.log(`Jitter: max=${report.jitters.max.toFixed(2)}ms, avg=${report.jitters.avg.toFixed(2)}ms`)
	console.log(`Delay: min=${report.delays.min.toFixed(2)}ms, avg=${report.delays.avg.toFixed(2)}ms`)

	if (report.errors.length > 0) {
		report.errors.forEach((e) => console.error(`ERROR: ${e}`))
	}
	if (report.warnings.length > 0) {
		report.warnings.forEach((w) => console.warn(`WARN: ${w}`))
	}

	// Print formatted report
	console.log('\n' + libDiagnosticCore.formatReport(report))
}
```

---

## Core Classes

### AudioStream

High-level RTP packet management for audio streaming.

**Constructor:**

```typescript
import { AudioStream } from '@kmholding/rtp-packetizer/core'
import { RTP_PAYLOAD_TYPES } from '@kmholding/rtp-packetizer/constants'

const libStreamCore = new AudioStream({
	prebufferFrames: 6, // Buffer 6 frames before sending
	payloadType: RTP_PAYLOAD_TYPES.L16, // 16-bit PCM
})
```

**Basic Usage:**

```typescript
// 1. Initialize channel
const channelID = libStreamCore.initialize({
	channelID: 'session-1',
	externalHost: '10.0.0.1',
	externalPort: 5060,
	sampleRate: 8000, // Telephony standard
	frameSize: 160, // 20ms frame
	prebufferFrames: 6, // Pre-buffer 120ms
	enableLogging: true,
})

// 2. Queue audio frames
const audioBuffer = Buffer.alloc(320) // 40ms of audio
const framesQueued = libStreamCore.queueAudioFrames(channelID, audioBuffer, {
	startSeqNum: Math.floor(Math.random() * 65536),
	startTimestamp: Math.floor(Math.random() * 0xffffffff),
	ssrc: Math.floor(Math.random() * 0xffffffff),
})

// 3. Retrieve packets
let packet = libStreamCore.getNextPacket(channelID)

while (packet) {
	console.log(`Sending packet: seq=${packet.seqNum}, ts=${packet.timestamp}`)

	packet = libStreamCore.getNextPacket(channelID)
}

// 4. Monitor stats
const stats = libStreamCore.getChannelStats(channelID)

console.log(`Queued: ${stats?.totalPacketsQueued}, Sent: ${stats?.totalFramesSent}`)
```

**Methods:**

- `initialize(config)` - Initialize streaming channel
  - Returns: Channel ID string
- `queueAudioFrames(channelID, audioBuffer, options?)` - Queue audio for transmission
  - Returns: Number of frames queued
- `getNextPacket(channelID)` - Retrieve next queued packet
  - Returns: `QueuedRTPPacket | null`
- `getChannelStats(channelID)` - Get channel statistics
  - Returns: `StreamingStats`

---

### AudioGain

Volume control and audio normalization engine.

**Constructor:**

```typescript
import { AudioGain } from '@kmholding/rtp-packetizer/core'

const libGainCore = new AudioGain({
	gain: 1.2, // Volume multiplier
	peak: 0.9, // Target peak level
	threshold: 0.6, // Compression threshold
	ratio: 4, // Compression ratio
})
```

**Basic Usage:**

```typescript
// 1. Analyze audio levels
const audioBuffer = Buffer.alloc(8000)
const stats = libGainCore.getVolumeStats(audioBuffer)

console.log(`Peak: ${stats.peak}, RMS: ${stats.rms}dB, dBFS: ${stats.dbfs}`)

// 2. Apply volume gain
const amplified = libGainCore.applyVolumeGain(audioBuffer, 1.5)

// 3. Normalize to target peak
const normalized = libGainCore.normalize(audioBuffer, 0.9)

// 4. Fade effects
const fadeInBuffer = libGainCore.fadeIn(audioBuffer, 4000) // 500ms fade at 8kHz
const fadeOutBuffer = libGainCore.fadeOut(audioBuffer, 4000)

// 5. Full amplification with stats
const { buffer: processed, stats: ampStats } = libGainCore.amplify(audioBuffer, 2.0)

console.log(`Processed: peak=${ampStats.peak}, rms=${ampStats.rms}`)
```

**Methods:**

- `applyVolumeGain(samples, gain?)` - Apply gain with clipping detection
  - Returns: Processed Buffer
- `amplify(buffer, gain?)` - Amplify with statistics
  - Returns: `{ buffer: Buffer, stats: VolumeStats }`
- `normalize(samples, targetPeak?)` - Normalize to peak level
  - Returns: Normalized Buffer
- `getVolumeStats(samples)` - Get peak, RMS, dBFS values
  - Returns: `VolumeStats`
- `fadeIn(samples, fadeLengthSamples)` - Fade in from silence
  - Returns: Faded Buffer
- `fadeOut(samples, fadeLengthSamples)` - Fade out to silence
  - Returns: Faded Buffer

---

### AudioResampling

CPU-efficient resampling using interpolation methods.

**Constructor:**

```typescript
import { AudioResampling } from '@kmholding/rtp-packetizer/core'
import { RESAMPLING_METHODS } from '@kmholding/rtp-packetizer/constants'

const libResamplerCore = new AudioResampling({
	method: RESAMPLING_METHODS.LAGRANGE, // Highest quality
})
```

**Basic Usage:**

```typescript
// 1. Generic resample
const audioBuffer = Buffer.alloc(16000) // 2 seconds at 8kHz
const resampled = libResamplerCore.resample(audioBuffer, 8000, 16000) // 8kHz → 16kHz

console.log(`Original: ${audioBuffer.length} bytes, Resampled: ${resampled.length} bytes`)

// 2. Use convenience methods
const to8kHz = libResamplerCore.resampleTo8kHz(audioBuffer)
const to16kHz = libResamplerCore.resampleTo16kHz(audioBuffer)

// 3. Different interpolation methods
const libLinearCore = new AudioResampling({ method: 'LINEAR' })
const libCubicCore = new AudioResampling({ method: 'CUBIC' })
const libLagrangeCore = new AudioResampling({ method: 'LAGRANGE' })

const fastResult = libLinearCore.resample(audioBuffer, 8000, 16000) // Fastest
const mediumResult = libCubicCore.resample(audioBuffer, 8000, 16000) // Balanced
const qualityResult = libLagrangeCore.resample(audioBuffer, 8000, 16000) // Best quality
```

**Methods:**

- `resample(samples, sourceSampleRate, targetSampleRate)` - Generic resample
  - Returns: Resampled Buffer
- `resampleLinear()` - Linear interpolation (fast, lower quality)
  - Returns: Resampled Buffer
- `resampleCubic()` - Cubic interpolation (medium)
  - Returns: Resampled Buffer
- `resampleLagrange()` - Lagrange interpolation (high quality)
  - Returns: Resampled Buffer
- `resampleTo8kHz()` - Convenience: resample to 8kHz
  - Returns: Resampled Buffer
- `resampleTo16kHz()` - Convenience: resample to 16kHz
  - Returns: Resampled Buffer

---

### AudioLibSampleRate

High-quality resampling using libsamplerate (Secret Rabbit Code).

**Constructor:**

```typescript
import { AudioLibSampleRate } from '@kmholding/rtp-packetizer/core'
import { RESAMPLING_QUALITIES } from '@kmholding/rtp-packetizer/constants'

const libResamplerCore = new AudioLibSampleRate({
	quality: RESAMPLING_QUALITIES.SRC_SINC_BEST_QUALITY, // Highest quality
	channels: 1, // Mono
})
```

**Basic Usage:**

```typescript
// 1. Initialize for resampling
const audioBuffer = Buffer.alloc(16000)
const initResult = await libResamplerCore.initialize(8000, 16000, {
	channels: 1,
	inputLength: audioBuffer.length,
})

console.log(`Initialized: ${initResult.success}`)

// 2. Resample audio
const resampled = libResamplerCore.resample(audioBuffer, 8000, 16000, 1)

console.log(`Input: ${audioBuffer.length}, Output: ${resampled.length}`)

// 3. Convenience methods
const to8kHz = await libResamplerCore.resampleTo8kHz(audioBuffer, 16000)
const to16kHz = await libResamplerCore.resampleTo16kHz(audioBuffer, 8000)

// 4. Clean up when done (IMPORTANT!)
libResamplerCore.destroyAll() // Free libsamplerate resources
```

**Methods:**

- `async initialize(sourceSampleRate, targetSampleRate, config?)` - Initialize resampler
  - Returns: `{ success: boolean }`
- `resample(input, sourceSampleRate, targetSampleRate, channels?)` - Resample buffer
  - Returns: Resampled Buffer
- `async resampleTo8kHz(input, sourceSampleRate, config?)` - Resample to 8kHz
  - Returns: Resampled Buffer
- `async resampleTo16kHz(input, sourceSampleRate, config?)` - Resample to 16kHz
  - Returns: Resampled Buffer
- `destroyAll()` - **IMPORTANT**: Free libsamplerate resources
  - Returns: void

---

### AudioUnifiedResampler

Unified interface for resampling (manual or libsamplerate).

**Constructor:**

```typescript
import { AudioUnifiedResampler } from '@kmholding/rtp-packetizer/core'
import { RESAMPLING_PROVIDES } from '@kmholding/rtp-packetizer/constants'

// Option 1: CPU-efficient manual interpolation
const libUnifiedManualCore = new AudioUnifiedResampler({
	provide: RESAMPLING_PROVIDES.MANUAL,
	method: 'LAGRANGE', // Interpolation method
	channels: 1,
})

// Option 2: High-quality libsamplerate
const libUnifiedHQCore = new AudioUnifiedResampler({
	provide: RESAMPLING_PROVIDES.LIBSAMPLERATE,
	quality: 'SRC_SINC_BEST_QUALITY',
	channels: 1,
})
```

**Basic Usage:**

```typescript
const audioBuffer = Buffer.alloc(16000)

// 1. Manual resample (synchronous, lightweight)
const manualResult = await libUnifiedManualCore.resample(audioBuffer, 8000, 16000)

console.log(`Manual: ${audioBuffer.length} → ${manualResult.length} bytes`)

// 2. Libsamplerate resample (asynchronous, high quality)
const libResult = await libUnifiedHQCore.resample(audioBuffer, 8000, 16000)

console.log(`Libsamplerate: ${audioBuffer.length} → ${libResult.length} bytes`)

// 3. Choose provider based on quality requirements
if (requiresHighQuality) {
	const resampled = await libUnifiedHQCore.resample(audio, 8000, 16000)
} else {
	const resampled = await libUnifiedManualCore.resample(audio, 8000, 16000)
}

// 4. Clean up
libUnifiedHQCore.destroy() // For libsamplerate provider
```

**Methods:**

- `async resample(samples, sourceSampleRate, targetSampleRate)` - Resample buffer
  - Returns: Resampled Buffer
- `destroy()` - Cleanup resources (important for libsamplerate)
  - Returns: void

---

### AudioDiagnostic

Analyze RTP streaming quality, timing, and jitter metrics.

**Constructor:**

```typescript
import { AudioDiagnostic } from '@kmholding/rtp-packetizer/core'

const libDiagnosticCore = new AudioDiagnostic({
	frameIntervalMs: 20, // Expected frame duration
})
```

**Basic Usage:**

```typescript
// 1. Create timing log during streaming
const sendLog = [
	{ seq: 1, ts: 1000, sentAt: 0 },
	{ seq: 2, ts: 1160, sentAt: 20 },
	{ seq: 3, ts: 1320, sentAt: 40 },
	{ seq: 4, ts: 1480, sentAt: 60 },
]

// 2. Analyze stream quality
const report = libDiagnosticCore.analyze(sendLog, 20)

if (report) {
	console.log(`Jitter: ${report.jitterMs}ms`)
	console.log(`Quality: ${report.quality}`) // EXCELLENT, GOOD, FAIR, POOR
	console.log(`Skipped Packets: ${report.skippedPackets}`)
	console.log(`Timestamp Drift: ${report.timestampDriftMs}ms`)
}

// 3. Format readable report
const formattedReport = libDiagnosticCore.formatReport(report)

console.log(formattedReport)

// 4. Quality thresholds
// EXCELLENT: jitter ≤ 5ms
// GOOD: jitter ≤ 15ms
// FAIR: jitter ≤ 30ms
// POOR: jitter > 30ms
```

**Methods:**

- `analyze(sendLog, frameIntervalMs?)` - Analyze packet timing
  - Input: `SendTimingLog[]` with { seq, ts, sentAt }
  - Returns: `TimingAnalysisReport | null`
- `formatReport(report)` - Format report as readable text
  - Returns: String with formatted analysis

**Quality Ratings:**

- **Excellent** (jitter ≤ 5ms) - Optimal streaming
- **Good** (jitter ≤ 15ms) - Acceptable for VoIP
- **Fair** (jitter ≤ 30ms) - Noticeable but manageable
- **Poor** (jitter > 30ms) - Quality degradation

---

## Helper Classes

### RTPHelper (Static)

Low-level RTP packet operations.

**Methods:**

- `parseRTPHeader(buffer)` - Extract header from bytes
- `parseRTPPacket(packet)` - Parse complete packet
- `createRTPHeader(header)` - Create header bytes
- `createRTPPacket(header, payload)` - Create full packet
- `isValidRTPPacket(msg)` - Validate packet structure
- `isPacketLoss(lastSeq, currentSeq)` - Detect loss
- `calculateSequenceGap(lastSeq, currentSeq)` - Count missing packets
- `calculateJitter(timestamps)` - Calculate jitter stats
- `rtpTimestampToMs(timestamp, sampleRate?)` - Convert timestamp
- `msToRtpTimestamp(ms, sampleRate?)` - Convert to timestamp
- `getPayloadTypeName(payloadType)` - Get codec name

---

### CodecHelper (Static)

Audio codec encoding/decoding.

**Methods:**

- `decodeULaw(buffer)` - Decode μ-law to PCM
- `linearToULaw(sample)` - Encode single sample
- `linearToULawBuffer(pcmBuffer)` - Encode buffer
- `decodeALaw(buffer)` - Decode A-law to PCM
- `linearToALaw(sample)` - Encode to A-law
- `linearToALawBuffer(pcmBuffer)` - Encode buffer
- `decodePcmu(byte)` - Alias for decodeULaw
- `linearToPcmu(sample)` - Alias for linearToULaw

---

### BufferHelper (Static)

Buffer and array utilities.

**Methods:**

- `bufferToInt16(buffer)` - Buffer → Int16Array
- `int16ToBuffer(int16Array)` - Int16Array → Buffer
- `concatenateInt16Arrays(...arrays)` - Merge arrays
- `sliceInt16Array(array, start, end?)` - Slice array
- `cloneInt16Array(array)` - Copy array
- `littleToBigEndian16(buffer)` - Endian conversion
- `bigToLittleEndian16(buffer)` - Endian conversion
- `getBufferDurationMs(buffer, sampleRate)` - Calculate duration
- `allocateSilence(sampleCount)` - Create silence
- `trimSilence(samples, threshold?)` - Remove silence edges

---

## Constants

```typescript
RESAMPLING_METHODS = {
	LINEAR: 'LINEAR',
	LAGRANGE: 'LAGRANGE',
	CUBIC: 'CUBIC',
}

RESAMPLING_QUALITIES = {
	BEST_QUALITY: 'SRC_SINC_BEST_QUALITY',
	MEDIUM_QUALITY: 'SRC_SINC_MEDIUM_QUALITY',
	LINEAR: 'SRC_LINEAR',
}

RTP_PAYLOAD_TYPES = {
	PCMU: 0, // G.711 μ-law
	PCMA: 8, // G.711 A-law
	GSM: 3, // GSM
	L16: 11, // 16-bit PCM
	OPUS: 111, // Opus
	// ... and more
}
```

---

## Complete Usage Examples

### Audio Processing Pipeline

```typescript
import { AudioGain, AudioResampling } from '@kmholding/rtp-packetizer/core'
import { BufferHelper, CodecHelper } from '@kmholding/rtp-packetizer/helpers'

async function processAudio(inputFile: string): Promise<Buffer> {
	const libBufferHelper = new BufferHelper()
	const libCodecHelper = new CodecHelper()

	// 1. Load audio (16kHz, 16-bit PCM)
	const rawAudio = loadAudioFile(inputFile)

	// 2. Normalize volume to 95% peak
	const libGainCore = new AudioGain({ peak: 0.95 })
	const samples = libBufferHelper.bufferToInt16(rawAudio)
	const normalized = libGainCore.normalize(samples)

	// 3. Resample to 8kHz for VoIP
	const libResamplerCore = new AudioResampling({ method: 'LAGRANGE' })
	const resampled = libResamplerCore.resample(normalized, 16000, 8000)

	// 4. Apply fade in (500ms @ 8kHz = 4000 samples)
	const fadedIn = libGainCore.fadeIn(resampled, 4000)

	// 5. Encode to U-Law (PCMU)
	const pcmBuffer = libBufferHelper.int16ToBuffer(fadedIn)
	const ulawBuffer = libCodecHelper.linearToULawBuffer(pcmBuffer)

	return ulawBuffer
}
```

### RTP Streaming with Diagnostics

```typescript
import { AudioStream, AudioDiagnostic } from '@kmholding/rtp-packetizer/core'
import * as dgram from 'dgram'

class RTPSession {
	private readonly libStreamCore = new AudioStream()
	private readonly libDiagnosticCore = new AudioDiagnostic({ frameIntervalMs: 20 })

	private timingLogs: any[] = []
	private socket = dgram.createSocket('udp4')

	initialize(remoteHost: string, remotePort: number) {
		this.libStreamCore.initialize({
			channelID: 'session-1',
			externalHost: remoteHost,
			externalPort: remotePort,
			sampleRate: 8000,
		})
	}

	sendAudio(audioBuffer: Buffer) {
		const channelID = 'session-1'

		this.libStreamCore.queueAudioFrames(channelID, audioBuffer)

		let packet

		while ((packet = this.libStreamCore.getNextPacket(channelID))) {
			// Record for diagnostics
			this.timingLogs.push({
				seq: packet.seqNum,
				ts: packet.timestamp,
				sentAt: Date.now(),
			})

			// Send to remote
			this.socket.send(packet.buffer, remotePort, remoteHost)
		}
	}

	analyzeQuality() {
		if (this.timingLogs.length < 10) {
			return null
		}

		return this.libDiagnosticCore.analyze(this.timingLogs)
	}

	printDiagnostics() {
		const report = this.analyzeQuality()

		if (!report) {
			return
		}

		console.log(this.libDiagnosticCore.formatReport(report))

		if (report.errors.length > 0) {
			report.errors.forEach((e) => console.error(`  - ${e}`))
		}

		if (report.warnings.length > 0) {
			report.warnings.forEach((w) => console.warn(`  - ${w}`))
		}
	}
}
```

### Codec Conversion

```typescript
import { CodecHelper, BufferHelper } from '@kmholding/rtp-packetizer/helpers'

const libBufferHelper = new BufferHelper()
const libCodecHelper = new CodecHelper()

// Convert PCM to PCMU
function wavToPcmu(wavFile: Buffer): Buffer {
	return libCodecHelper.linearToULawBuffer(wavFile)
}

// Receive PCMU packets and reconstruct PCM
function pcmuToPcm(packets: Buffer[]): Buffer {
	const pcmSamples = packets.map((p) => CodecHelper.decodePcmuBuffer(p))
	const combined = libBufferHelper.concatenateInt16Arrays(...pcmSamples)

	return libBufferHelper.int16ToBuffer(combined)
}

// Usage
const pcm = loadWavFile('input.wav')
const pcmu = wavToPcmu(pcm)
// ... transmit pcmu packets ...
const receivedPackets = [
	/* PCMU data */
]

const reconstructed = pcmuToPcm(receivedPackets)

saveWavFile('output.wav', reconstructed)
```

---

## Performance

### Resampling Speed (20ms frame @ 8kHz)

- LINEAR: 0.1ms
- CUBIC: 0.3ms
- LAGRANGE: 0.5ms
- LIBSAMPLERATE: 0.2-0.8ms

### Memory per Channel

- ~10KB base
- 160 bytes per frame in queue
- Total: base + (frameSize × prebufferFrames × 2)

### Tips

1. Reuse class instances
2. Use LIBSAMPLERATE for high quality, MANUAL for speed
3. Call `destroy()` on libsamplerate instances
4. Process in batches when possible

---

## Troubleshooting

**Audio Clipping?**

```typescript
if (result.clippingRate > 0) {
	const normalized = gain.normalize(samples, 0.8)
}
```

**High Jitter?**

```typescript
libStreamCore.initialize({
	frameSize: 80, // Smaller frames
	prebufferFrames: 10, // More buffering
})
```

**Memory Leak?**

```typescript
try {
	// Use resampler
} finally {
	libResamplerCore.destroy() // Always cleanup
}
```

---

## License

MIT © K&M Holdings