diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 633543ee..75741117 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -76,6 +76,8 @@ network: # Flag indicating UDP audio should be RTP framed. # NOTE: This flag is only applicable when encoding G.711 uLaw. udpRTPFrames: false + # Flag indicating UDP audio should follow the USRP format. + udpUsrp: false # Source "Radio ID" for transmitted audio frames. sourceId: 1234567 diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 17b541ef..ef52fb79 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -5,6 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2025 Caleb, K4PHP * */ #include "Defines.h" @@ -293,6 +294,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpNoIncludeLength(false), m_udpUseULaw(false), m_udpRTPFrames(false), + m_udpUsrp(false), m_srcId(p25::defines::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), @@ -353,7 +355,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_trace(false), m_debug(false), m_rtpSeqNo(0U), - m_rtpTimestamp(INVALID_TS) + m_rtpTimestamp(INVALID_TS), + m_usrpSeqNo(0U) #if defined(_WIN32) , m_encoderState(nullptr), @@ -1019,10 +1022,19 @@ bool HostBridge::createNetwork() m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as(34001); m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); m_udpUseULaw = networkConf["udpUseULaw"].as(false); + m_udpUsrp = networkConf["udpUsrp"].as(false); + if (m_udpUsrp) { + m_udpMetadata = false; // USRP disables metadata due to USRP always having metadata + m_udpRTPFrames = false; // USRP disables RTP + m_udpNoIncludeLength = true; // USRP disables length + m_udpUseULaw = false; // USRP disables ULaw + } + if (m_udpUseULaw) { m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as(false); m_udpRTPFrames = networkConf["udpRTPFrames"].as(false); + m_udpUsrp = false; // ULaw disables USRP if (m_udpRTPFrames) m_udpNoIncludeLength = true; // RTP disables the length being included } @@ -1096,6 +1108,7 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio No Length Header: %s", m_udpNoIncludeLength ? "yes" : "no"); LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "yes" : "no"); } + LogInfo(" UDP Audio USRP: %s", m_udpUsrp ? "yes" : "no"); } LogInfo(" Source ID: %u", m_srcId); @@ -1178,31 +1191,40 @@ void HostBridge::processUDPAudio() pcmLength = __GET_UINT32(buffer, 0U); } - if (m_udpRTPFrames) + if (m_udpRTPFrames || m_udpUsrp) pcmLength = MBE_SAMPLES_LENGTH * 2U; UInt8Array __pcm = std::make_unique(pcmLength); uint8_t* pcm = __pcm.get(); + + if (!m_udpUsrp) { + 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; + } - 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 * 2U); - } - else { - if (m_udpNoIncludeLength) { - ::memcpy(pcm, buffer, pcmLength); + ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, MBE_SAMPLES_LENGTH * 2U); } else { - ::memcpy(pcm, buffer + 4U, pcmLength); + if (m_udpNoIncludeLength) { + ::memcpy(pcm, buffer, pcmLength); + } + else { + ::memcpy(pcm, buffer + 4U, pcmLength); + } } } + else { + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + ::memcpy(usrpHeader, buffer, USRP_HEADER_LENGTH); + + if (usrpHeader[15U] == 1U && length > USRP_HEADER_LENGTH) // PTT state true and ensure we did not just receive a USRP header + ::memcpy(pcm, buffer + USRP_HEADER_LENGTH, pcmLength); + } // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); @@ -1444,6 +1466,10 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; + // send USRP end of transmission + if (m_udpUsrp) + sendUsrpEot(); + LogMessage(LOG_HOST, "P25, call end (T), srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } @@ -1539,48 +1565,65 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; - if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) - if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH) + 4U; - if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); - } else { - __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; + if (!m_udpUsrp) { + if (!m_udpMetadata) { + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + if (m_udpUseULaw) { + length = (MBE_SAMPLES_LENGTH)+4U; + if (m_udpNoIncludeLength) { + length = MBE_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + } + else { + __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); } - m_rtpSeqNo++; + // 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); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); } } else { + length = (MBE_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + + // embed destination and source IDs + __SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); + __SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); - - // embed destination and source IDs - __SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - __SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + + length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + + m_usrpSeqNo++; + usrpHeader[15U] = 1; // set PTT state to true + __SET_UINT32(m_usrpSeqNo, usrpHeader, 4U); + + ::memcpy(usrpHeader, "USRP", 4); + ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -1863,6 +1906,11 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; + // send USRP end of transmission + if (m_udpUsrp) { + sendUsrpEot(); + } + LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } @@ -2130,48 +2178,66 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U; uint8_t* audioData = nullptr; - if (!m_udpMetadata) { - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) - if (m_udpUseULaw) { - length = (MBE_SAMPLES_LENGTH) + 4U; - if (m_udpNoIncludeLength) { - length = MBE_SAMPLES_LENGTH; - ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); - } else { - __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; + if (!m_udpUsrp) { + if (!m_udpMetadata) { + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) + if (m_udpUseULaw) { + length = (MBE_SAMPLES_LENGTH)+4U; + if (m_udpNoIncludeLength) { + length = MBE_SAMPLES_LENGTH; + ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); + } + else { + __SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH); } - m_rtpSeqNo++; + // 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); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); } } else { + length = (MBE_SAMPLES_LENGTH * 2U) + 12U; + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + + // embed destination and source IDs + __SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); + __SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); } } else { - length = (MBE_SAMPLES_LENGTH * 2U) + 12U; - audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 12U]; // PCM + (4 bytes (PCM length) + 4 bytes (srcId) + 4 bytes (dstId)) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); - - // embed destination and source IDs - __SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); - __SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + + length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH; + audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header) + + m_usrpSeqNo++; + usrpHeader[15U] = 1; // set PTT state to true + __SET_UINT32(m_usrpSeqNo, usrpHeader, 4U); + + ::memcpy(usrpHeader, "USRP", 4); + ::memcpy(audioData, usrpHeader, USRP_HEADER_LENGTH); // copy USRP header into the UDP payload + ::memcpy(audioData + USRP_HEADER_LENGTH, pcm, MBE_SAMPLES_LENGTH * 2U); } sockaddr_storage addr; @@ -2341,6 +2407,22 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ m_p25N++; } +/* Helper to send USRP end of transmission */ + +void HostBridge::sendUsrpEot() { + sockaddr_storage addr; + uint32_t addrLen; + + uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH]; + + m_usrpSeqNo = 0U; + ::memcpy(usrpHeader, "USRP", 4); + + if (udp::Socket::lookup(m_udpSendAddress, m_udpSendPort, addr, addrLen) == 0) { + m_udpAudioSocket->write(usrpHeader, USRP_HEADER_LENGTH, addr, addrLen); + } +} + /* Helper to generate the single-tone preamble tone. */ void HostBridge::generatePreambleTone() diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 2bbf9da9..4fb06ce6 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -56,6 +56,8 @@ #define DECSTATE_SIZE 2048 #define ENCSTATE_SIZE 6144 +#define USRP_HEADER_LENGTH 32 + const uint8_t FULL_RATE_MODE = 0x00U; const uint8_t HALF_RATE_MODE = 0x01U; @@ -162,6 +164,7 @@ private: bool m_udpNoIncludeLength; bool m_udpUseULaw; bool m_udpRTPFrames; + bool m_udpUsrp; uint32_t m_srcId; uint32_t m_srcIdOverride; @@ -245,6 +248,8 @@ private: uint16_t m_rtpSeqNo; uint32_t m_rtpTimestamp; + uint32_t m_usrpSeqNo; + static std::mutex m_audioMutex; static std::mutex m_networkMutex; @@ -446,6 +451,11 @@ private: */ uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); + /** + * @brief Helper to generate USRP end of transmission + */ + void sendUsrpEot(); + /** * @brief Helper to generate the single-tone preamble tone. */