/* 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 --codec [--pt ] [--rate ] [--channels ] #include "helper/HL_Rtp.h" #include "media/MT_CodecList.h" #include "media/MT_Codec.h" #include "audio/Audio_WavFile.h" #include #include #include #include #include #include // --------------------------------------------------------------------------- // CLI helpers // --------------------------------------------------------------------------- static void usage(const char* progname) { fprintf(stderr, "Usage: %s --codec [--pt ] [--rate ] [--channels ]\n" "\n" "Codecs: pcmu pcma g722 g729 opus gsm gsmhr gsmefr amrnb amrwb evs ilbc20 ilbc30 isac16 isac32\n" "\n" "Options:\n" " --codec Codec name (required)\n" " --pt Override RTP payload type\n" " --rate Sample rate hint for Opus (default 48000)\n" " --channels 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 }, { "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 == "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 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 input(payloadData, payloadLen); std::span 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(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; }