Files
rtphone/test/rtp_decode/main.cpp

292 lines
9.9 KiB
C++

/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// rtp_decode — read an rtpdump file, decode RTP with a given codec, write WAV.
//
// Usage:
// rtp_decode <input.rtp> <output.wav> --codec <name> [--pt <N>] [--rate <N>] [--channels <N>]
#include "helper/HL_Rtp.h"
#include "media/MT_CodecList.h"
#include "media/MT_Codec.h"
#include "audio/Audio_WavFile.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <stdexcept>
// ---------------------------------------------------------------------------
// CLI helpers
// ---------------------------------------------------------------------------
static void usage(const char* progname)
{
fprintf(stderr,
"Usage: %s <input.rtp> <output.wav> --codec <name> [--pt <N>] [--rate <N>] [--channels <N>]\n"
"\n"
"Codecs: pcmu pcma g722 g729 opus gsm gsmhr gsmefr\n"
" amrnb amrwb amrnb-bwe amrwb-bwe evs ilbc20 ilbc30 isac16 isac32\n"
"\n"
"Options:\n"
" --codec <name> Codec name (required)\n"
" --pt <N> Override RTP payload type\n"
" --rate <N> Sample rate hint for Opus (default 48000)\n"
" --channels <N> Channel count hint for Opus (default 2)\n",
progname);
}
static const char* getOption(int argc, char* argv[], const char* name)
{
for (int i = 1; i < argc - 1; ++i) {
if (strcmp(argv[i], name) == 0)
return argv[i + 1];
}
return nullptr;
}
// ---------------------------------------------------------------------------
// Default payload types for codecs without a fixed standard PT
// ---------------------------------------------------------------------------
struct CodecDefaults
{
const char* name;
int defaultPt; // -1 = must be specified via --pt
bool needsPt; // true if --pt is required when no default exists
};
static const CodecDefaults kCodecTable[] = {
{ "pcmu", 0, false },
{ "pcma", 8, false },
{ "g722", 9, false },
{ "g729", 18, false },
{ "gsm", 3, false },
{ "opus", 106, false },
{ "amrnb", -1, true },
{ "amrwb", -1, true },
{ "amrnb-bwe", -1, true },
{ "amrwb-bwe", -1, true },
{ "gsmhr", -1, true },
{ "gsmefr", 126, false },
{ "evs", 127, false },
{ "ilbc20", -1, true },
{ "ilbc30", -1, true },
{ "isac16", -1, true },
{ "isac32", -1, true },
};
static const CodecDefaults* findCodecDefaults(const std::string& name)
{
for (auto& c : kCodecTable)
if (name == c.name)
return &c;
return nullptr;
}
// ---------------------------------------------------------------------------
// Build CodecList::Settings for the requested codec
// ---------------------------------------------------------------------------
static MT::CodecList::Settings buildSettings(const std::string& codecName, int pt,
int opusRate, int opusChannels)
{
MT::CodecList::Settings s;
if (codecName == "opus") {
s.mOpusSpec.push_back(MT::CodecList::Settings::OpusSpec(pt, opusRate, opusChannels));
} else if (codecName == "gsm") {
s.mGsmFrPayloadType = pt;
} else if (codecName == "gsmhr") {
s.mGsmHrPayloadType = pt;
} else if (codecName == "gsmefr") {
s.mGsmEfrPayloadType = pt;
} else if (codecName == "amrnb") {
s.mAmrNbOctetPayloadType.insert(pt);
} else if (codecName == "amrwb") {
s.mAmrWbOctetPayloadType.insert(pt);
} else if (codecName == "amrnb-bwe") {
s.mAmrNbPayloadType.insert(pt);
} else if (codecName == "amrwb-bwe") {
s.mAmrWbPayloadType.insert(pt);
} else if (codecName == "evs") {
MT::CodecList::Settings::EvsSpec ev;
ev.mPayloadType = pt;
s.mEvsSpec.push_back(ev);
} else if (codecName == "ilbc20") {
s.mIlbc20PayloadType = pt;
} else if (codecName == "ilbc30") {
s.mIlbc30PayloadType = pt;
} else if (codecName == "isac16") {
s.mIsac16KPayloadType = pt;
} else if (codecName == "isac32") {
s.mIsac32KPayloadType = pt;
}
// pcmu, pcma, g722, g729 — fixed PT, auto-registered by CodecList::init()
return s;
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
if (argc < 4) {
usage(argv[0]);
return 1;
}
const char* inputPath = argv[1];
const char* outputPath = argv[2];
const char* codecArg = getOption(argc, argv, "--codec");
if (!codecArg) {
fprintf(stderr, "Error: --codec is required\n\n");
usage(argv[0]);
return 1;
}
std::string codecName = codecArg;
const auto* defaults = findCodecDefaults(codecName);
if (!defaults) {
fprintf(stderr, "Error: unknown codec '%s'\n\n", codecArg);
usage(argv[0]);
return 1;
}
// Resolve payload type
int pt = defaults->defaultPt;
const char* ptArg = getOption(argc, argv, "--pt");
if (ptArg) {
pt = atoi(ptArg);
} else if (defaults->needsPt) {
fprintf(stderr, "Error: --pt is required for codec '%s'\n\n", codecArg);
usage(argv[0]);
return 1;
}
int opusRate = 48000;
int opusChannels = 2;
const char* rateArg = getOption(argc, argv, "--rate");
if (rateArg)
opusRate = atoi(rateArg);
const char* chArg = getOption(argc, argv, "--channels");
if (chArg)
opusChannels = atoi(chArg);
// -----------------------------------------------------------------------
// 1. Load rtpdump
// -----------------------------------------------------------------------
RtpDump dump(inputPath);
try {
dump.load();
} catch (const std::exception& e) {
fprintf(stderr, "Error loading rtpdump '%s': %s\n", inputPath, e.what());
return 1;
}
if (dump.count() == 0) {
fprintf(stderr, "No packets in '%s'\n", inputPath);
return 1;
}
fprintf(stderr, "Loaded %zu packets from '%s'\n", dump.count(), inputPath);
// -----------------------------------------------------------------------
// 2. Create codec
// -----------------------------------------------------------------------
auto settings = buildSettings(codecName, pt, opusRate, opusChannels);
MT::CodecList codecList(settings);
MT::PCodec codec = codecList.createCodecByPayloadType(pt);
if (!codec) {
fprintf(stderr, "Error: could not create codec for payload type %d\n", pt);
return 1;
}
auto codecInfo = codec->info();
fprintf(stderr, "Codec: %s samplerate=%d channels=%d pcmLength=%d frameTime=%dms\n",
codecInfo.mName.c_str(), codecInfo.mSamplerate, codecInfo.mChannels,
codecInfo.mPcmLength, codecInfo.mFrameTime);
// -----------------------------------------------------------------------
// 3. Open WAV writer
// -----------------------------------------------------------------------
Audio::WavFileWriter writer;
if (!writer.open(outputPath, codecInfo.mSamplerate, codecInfo.mChannels)) {
fprintf(stderr, "Error: could not open WAV file '%s' for writing\n", outputPath);
return 1;
}
// -----------------------------------------------------------------------
// 4. Decode loop
// -----------------------------------------------------------------------
std::vector<uint8_t> pcmBuffer(65536);
size_t totalDecodedBytes = 0;
size_t packetsDecoded = 0;
size_t packetsSkipped = 0;
for (size_t i = 0; i < dump.count(); ++i) {
const auto& rawData = dump.rawDataAt(i);
// Verify it's actually RTP
if (!RtpHelper::isRtp(rawData.data(), rawData.size())) {
++packetsSkipped;
continue;
}
// Parse RTP to get payload
jrtplib::RTPPacket& rtpPacket = dump.packetAt(i);
// Check payload type matches what we expect
int pktPt = rtpPacket.GetPayloadType();
if (pktPt != pt) {
++packetsSkipped;
continue;
}
uint8_t* payloadData = rtpPacket.GetPayloadData();
size_t payloadLen = rtpPacket.GetPayloadLength();
if (!payloadData || payloadLen == 0) {
++packetsSkipped;
continue;
}
std::span<const uint8_t> input(payloadData, payloadLen);
std::span<uint8_t> output(pcmBuffer.data(), pcmBuffer.size());
try {
auto result = codec->decode(input, output);
if (result.mDecoded > 0) {
writer.write(pcmBuffer.data(), result.mDecoded);
totalDecodedBytes += result.mDecoded;
++packetsDecoded;
}
} catch (const std::exception& e) {
fprintf(stderr, "Warning: decode error at packet %zu: %s\n", i, e.what());
++packetsSkipped;
}
}
// -----------------------------------------------------------------------
// 5. Close WAV and print summary
// -----------------------------------------------------------------------
writer.close();
size_t totalSamples = totalDecodedBytes / (sizeof(int16_t) * codecInfo.mChannels);
double durationSec = (codecInfo.mSamplerate > 0)
? static_cast<double>(totalSamples) / codecInfo.mSamplerate
: 0.0;
fprintf(stderr, "\nDone.\n");
fprintf(stderr, " Packets decoded: %zu\n", packetsDecoded);
fprintf(stderr, " Packets skipped: %zu\n", packetsSkipped);
fprintf(stderr, " Decoded PCM: %zu bytes\n", totalDecodedBytes);
fprintf(stderr, " Duration: %.3f seconds\n", durationSec);
fprintf(stderr, " Output: %s\n", outputPath);
return 0;
}