SIP Call Recording

MediaX provides a dedicated SipRecorder class that records both legs of a SIP/RTP audio call into a single Matroska file — each leg as a separate, labelled audio track.

Supported Audio Codecs

AudioCodecType Codec RTP Payload Type MKV Codec ID
kPcmu G.711 µ-law 0 (PCMU) A_MS/ACM (WAVE_FORMAT_MULAW)
kPcma G.711 a-law 8 (PCMA) A_MS/ACM (WAVE_FORMAT_ALAW)
kOpus Opus dynamic A_OPUS
kG729 G.729 18 A_MS/ACM (WAVE_FORMAT_G729)
kL16 PCM 16-bit big-endian 11 A_PCM/INT/BIG
kMelpe MELPe (STANAG 4591) dynamic A_MELPE (custom)

SipRecorder Quick Start

#include "sip/sip_recorder.h"
#include "rtp/rtp_types.h"

mediax::sip::SipCallConfig config;
config.filename = "call_recording.mkv";

// Caller leg (MKV track 1)
config.caller.session_name = "Caller";
config.caller.hostname     = "239.0.0.1";   // unicast or multicast
config.caller.port         = 5004;
config.caller.sample_rate  = 8000;
config.caller.channels     = 1;
config.caller.codec        = mediax::rtp::AudioCodecType::kPcmu;

// Callee leg (MKV track 2)
config.callee.session_name = "Callee";
config.callee.hostname     = "239.0.0.2";
config.callee.port         = 5006;
config.callee.sample_rate  = 8000;
config.callee.channels     = 1;
config.callee.codec        = mediax::rtp::AudioCodecType::kPcma;

mediax::sip::SipRecorder recorder;
recorder.SetConfig(config);

if (!recorder.Open()) {
  std::cerr << "Failed to open SIP recorder\n";
  return 1;
}

recorder.StartRecording();   // starts two background threads

// ... wait for call to end ...
std::this_thread::sleep_for(std::chrono::seconds(30));

recorder.StopRecording();    // joins threads and flushes clusters
recorder.Close();            // finalises the MKV file

MELPe (STANAG 4591)

MELPe is a NATO narrowband voice codec defined in STANAG 4591. Three bitrates are supported:

config.caller.codec         = mediax::rtp::AudioCodecType::kMelpe;
config.caller.melpe_bitrate = 2400;  // 600, 1200, or 2400 bps

The bitrate is stored in a 1-byte codec-private field inside the MKV track header.

Opus Example

config.caller.codec       = mediax::rtp::AudioCodecType::kOpus;
config.caller.sample_rate = 48000;
config.caller.channels    = 1;

Opus frames are stored with a standard 19-byte OpusHead codec-private blob so the MKV file is compatible with ffplay, VLC, and other standard tools.

Using MkvRecorder Directly (Advanced)

For fine-grained control you can drive MkvRecorder and RtpAudioDepayloader independently:

#include "rtp/mkv_recorder.h"
#include "rtp/rtp_audio_depayloader.h"

// 1. Configure recorder
mediax::rtp::MkvRecorder mkv;
mediax::rtp::RecordingConfig mkv_cfg;
mkv_cfg.filename = "audio_only.mkv";
mkv_cfg.width    = 0;  // audio-only mode
mkv_cfg.height   = 0;
mkv.SetConfig(mkv_cfg);

// 2. Add audio tracks before Open()
mediax::rtp::AudioTrackConfig track1;
track1.track_number = 1;
track1.codec        = mediax::rtp::AudioCodecType::kPcmu;
track1.sample_rate  = 8000;
track1.channels     = 1;
track1.track_name   = "Caller";
mkv.AddAudioTrack(track1);

mediax::rtp::AudioTrackConfig track2;
track2.track_number = 2;
track2.codec        = mediax::rtp::AudioCodecType::kPcma;
track2.sample_rate  = 8000;
track2.channels     = 1;
track2.track_name   = "Callee";
mkv.AddAudioTrack(track2);

mkv.Open();

// 3. Receive and write audio frames
// G.711 20 ms frame at 8 kHz = 160 bytes
uint8_t pcmu_frame[160] = { /* codec payload */ };
mkv.WriteAudioFrame(1, pcmu_frame, sizeof(pcmu_frame), timestamp_ms);
mkv.WriteAudioFrame(2, pcma_frame, sizeof(pcma_frame), timestamp_ms);

mkv.Close();

Playing the Recording

# Play both audio tracks (select with 'a' in ffplay)
ffplay call_recording.mkv

# Extract caller track
ffmpeg -i call_recording.mkv -map 0:a:0 caller.wav

# Extract callee track
ffmpeg -i call_recording.mkv -map 0:a:1 callee.wav

# Mix both tracks to a single stereo file
ffmpeg -i call_recording.mkv \
  -filter_complex "[0:a:0][0:a:1]amerge=inputs=2[aout]" \
  -map "[aout]" -ac 2 mixed_call.wav

SipRecorder API Summary

Method Description
SetConfig(SipCallConfig) Configure the recording before Open()
Open() Open MKV file and RTP sockets
StartRecording() Start background receive threads
StopRecording() Stop threads and flush
Close() Finalise MKV and release resources
IsOpen() Check if recorder is open
IsRecording() Check if threads are running
GetFrameCount() Total frames written (both tracks)
GetBytesWritten() Total bytes written to MKV

AudioTrackConfig Fields

Field Type Description
codec AudioCodecType Audio codec
sample_rate uint32_t Sample rate in Hz (e.g. 8000, 48000)
channels uint16_t Number of channels (1=mono, 2=stereo)
track_number uint32_t MKV track number (must be unique per file)
track_name std::string Human-readable track label
melpe_bitrate uint32_t MELPe bitrate in bps (600, 1200, 2400)

Troubleshooting

No audio packets received

  • Verify the SIP SDP negotiated the same port and codec as AudioStreamInformation
  • Check firewall rules allow inbound UDP on the configured ports
  • Use tcpdump -i any udp port 5004 to confirm RTP traffic is arriving

MKV plays with only one track

  • Ensure both caller and callee hostnames and ports are distinct
  • Confirm both RTP streams are active before StartRecording() is called

Codec mismatch / garbled audio

  • Verify AudioStreamInformation::codec matches the codec negotiated in SIP SDP
  • For G.711, kPcmu is µ-law (North America / Japan) and kPcma is a-law (Europe / international)