diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 4b3540b9..19ea98aa 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -73,6 +73,9 @@ network: # Flag indicating UDP audio should be transmitted without the length leader. # NOTE: This flag is only applicable when encoding G.711 uLaw. udpNoIncludeLength: false + # Flag indicating UDP audio should be RTP framed. + # NOTE: This flag is only applicable when encoding G.711 uLaw. + udpRTPFrames: false # Source "Radio ID" for transmitted audio frames. sourceId: 1234567 diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index bd625428..43f2be4e 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -19,6 +19,7 @@ #include "common/p25/dfsi/LC.h" #include "common/p25/lc/LC.h" #include "common/p25/P25Utils.h" +#include "common/network/RTPHeader.h" #include "common/network/udp/Socket.h" #include "common/Log.h" #include "common/StopWatch.h" @@ -30,6 +31,7 @@ #include "SampleTimeConversion.h" using namespace network; +using namespace network::frame; using namespace network::udp; #include @@ -67,6 +69,8 @@ static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FF #define BIAS (0x84) // bias for linear code #define CLIP 8159 +const uint8_t RTP_G711_PAYLOAD_TYPE = 0x00U; + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -288,6 +292,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpReceiveAddress("127.0.0.1"), m_udpNoIncludeLength(false), m_udpUseULaw(false), + m_udpRTPFrames(false), m_srcId(p25::defines::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), @@ -345,7 +350,9 @@ HostBridge::HostBridge(const std::string& confFile) : m_detectedSampleCnt(0U), m_dumpSampleLevel(false), m_running(false), - m_debug(false) + m_debug(false), + m_rtpSeqNo(0U), + m_rtpTimestamp(INVALID_TS) #if defined(_WIN32) , m_encoderState(nullptr), @@ -1005,8 +1012,12 @@ bool HostBridge::createNetwork() m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); m_udpUseULaw = networkConf["udpUseULaw"].as(false); - if (m_udpUseULaw) + if (m_udpUseULaw) { m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as(false); + m_udpRTPFrames = networkConf["udpRTPFrames"].as(false); + if (m_udpRTPFrames) + m_udpNoIncludeLength = true; // RTP disables the length being included + } if (m_udpUseULaw && m_udpMetadata) m_udpMetadata = false; // metadata isn't supported when encoding uLaw @@ -1072,9 +1083,10 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio Send Port: %u", m_udpSendPort); LogInfo(" UDP Audio Receive Address: %s", m_udpReceiveAddress.c_str()); LogInfo(" UDP Audio Receive Port: %u", m_udpReceivePort); - LogInfo(" UDP Audio Use uLaw Encoding: %u", m_udpUseULaw ? "yes" : "no"); + LogInfo(" UDP Audio Use uLaw Encoding: %s", m_udpUseULaw ? "yes" : "no"); if (m_udpUseULaw) { - LogInfo(" UDP Audio No Length Header: %u", m_udpNoIncludeLength ? "yes" : "no"); + LogInfo(" UDP Audio No Length Header: %s", m_udpNoIncludeLength ? "yes" : "no"); + LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "yes" : "no"); } } @@ -1161,11 +1173,24 @@ void HostBridge::processUDPAudio() UInt8Array __pcm = std::make_unique(pcmLength); uint8_t* pcm = __pcm.get(); - if (m_udpNoIncludeLength) { - ::memcpy(pcm, buffer, pcmLength); + if (m_udpRTPFrames) { + RTPHeader rtpHeader = RTPHeader(); + rtpHeader.decode(buffer); + + if (rtpHeader.getPayloadType() != RTP_G711_PAYLOAD_TYPE) { + LogError(LOG_HOST, "Invalid RTP payload type %u", rtpHeader.getPayloadType()); + return; + } + + ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, MBE_SAMPLES_LENGTH); } else { - ::memcpy(pcm, buffer + 4U, pcmLength); + if (m_udpNoIncludeLength) { + ::memcpy(pcm, buffer, pcmLength); + } + else { + ::memcpy(pcm, buffer + 4U, pcmLength); + } } // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); @@ -1379,6 +1404,9 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) m_rxDMRPILC = lc::PrivacyLC(); m_rxStartTime = 0U; m_rxStreamId = 0U; + + m_rtpSeqNo = 0U; + m_rtpTimestamp = INVALID_TS; return; } @@ -1498,6 +1526,22 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); } + + // are we sending RTP audio frames? + if (m_udpRTPFrames) { + uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + if (rtpFrame != nullptr) { + length += RTP_HEADER_LENGTH_BYTES; + uint8_t* newAudioData = new uint8_t[length]; + ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + delete[] audioData; + + audioData = newAudioData; + } + + m_rtpSeqNo++; + } } else { __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); @@ -1798,6 +1842,9 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) m_rxP25LC = lc::LC(); m_rxStartTime = 0U; m_rxStreamId = 0U; + + m_rtpSeqNo = 0U; + m_rtpTimestamp = INVALID_TS; return; } @@ -2064,6 +2111,22 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); } + + // are we sending RTP audio frames? + if (m_udpRTPFrames) { + uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); + if (rtpFrame != nullptr) { + length += RTP_HEADER_LENGTH_BYTES; + uint8_t* newAudioData = new uint8_t[length]; + ::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); + ::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); + delete[] audioData; + + audioData = newAudioData; + } + + m_rtpSeqNo++; + } } else { __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); @@ -2278,6 +2341,40 @@ void HostBridge::generatePreambleTone() m_outputAudio.addData(sineSamples, frameCount); } +/* Helper to generate outgoing RTP headers. */ + +uint8_t* HostBridge::generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq) +{ + uint32_t timestamp = m_rtpTimestamp; + if (timestamp != INVALID_TS) { + timestamp += (RTP_GENERIC_CLOCK_RATE / 50); + if (m_debug) + LogDebug(LOG_NET, "HostBridge::generateRTPHeaders() RTP, previous TS = %u, TS = %u, rtpSeq = %u", m_rtpTimestamp, timestamp, rtpSeq); + m_rtpTimestamp = timestamp; + } + + // generate RTP header + RTPHeader header = RTPHeader(); + + header.setPayloadType(RTP_G711_PAYLOAD_TYPE); + header.setTimestamp(timestamp); + header.setSequence(rtpSeq); + header.setSSRC(m_network->getPeerId()); + + uint8_t* buffer = new uint8_t[RTP_HEADER_LENGTH_BYTES + msgLen]; + ::memset(buffer, 0x00U, RTP_HEADER_LENGTH_BYTES + msgLen); + + header.encode(buffer); + + if (timestamp == INVALID_TS) { + if (m_debug) + LogDebug(LOG_NET, "HostBridge::generateRTPHeaders() RTP, initial TS = %u, rtpSeq = %u", header.getTimestamp(), rtpSeq); + m_rtpTimestamp = header.getTimestamp(); + } + + return buffer; +} + /* Helper to end a local or UDP call. */ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) @@ -2334,6 +2431,9 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) m_dmrN = 0U; m_p25SeqNo = 0U; m_p25N = 0U; + + m_rtpSeqNo = 0U; + m_rtpTimestamp = INVALID_TS; } /* Entry point to audio processing thread. */ diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 675f8cfa..b80c6fda 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -161,6 +161,7 @@ private: std::string m_udpReceiveAddress; bool m_udpNoIncludeLength; bool m_udpUseULaw; + bool m_udpRTPFrames; uint32_t m_srcId; uint32_t m_srcIdOverride; @@ -240,6 +241,9 @@ private: bool m_running; bool m_debug; + uint16_t m_rtpSeqNo; + uint32_t m_rtpTimestamp; + static std::mutex m_audioMutex; static std::mutex m_networkMutex; @@ -433,6 +437,14 @@ private: */ void encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId = 0U, uint32_t forcedDstId = 0U); + /** + * @brief Helper to generate outgoing RTP headers. + * @param msgLen Message Length. + * @param rtpSeq RTP Sequence. + * @returns uint8_t* Buffer containing the encoded RTP headers. + */ + uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + /** * @brief Helper to generate the preamble tone. */