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 5004to confirm RTP traffic is arriving
MKV plays with only one track
- Ensure both
callerandcalleehostnames and ports are distinct - Confirm both RTP streams are active before
StartRecording()is called
Codec mismatch / garbled audio
- Verify
AudioStreamInformation::codecmatches the codec negotiated in SIP SDP - For G.711,
kPcmuis µ-law (North America / Japan) andkPcmais a-law (Europe / international)