npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@kmholding/rtp-packetizer

v2.0.2

Published

High-performance RTP packetizer with true SRC resampling, 8↔48kHz, 5 interpolation methods & real-time diagnostic

Downloads

4,917

Readme

@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

npm install @kmholding/rtp-packetizer

yarn

yarn add @kmholding/rtp-packetizer

bun

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)

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

Core Classes

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

Helper Utilities

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

Constants

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

Type Definitions

// 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

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

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

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

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:

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:

// 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:

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:

// 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:

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:

// 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:

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:

// 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:

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:

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:

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

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

Basic Usage:

// 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

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

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

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

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?

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

High Jitter?

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

Memory Leak?

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

License

MIT © K&M Holdings