Add support for USRP (#83)

* Add support for USRP UDP transport

* Maintain USRP sequence

* Oops not sure how I did that

* Update DMR to be like P25

* Check for invalid UDP configuration

---------

Co-authored-by: firealarmss <caleb.k4php@gmail.com>
4.11f_maint
firealarmss 11 months ago committed by GitHub
parent db1d000b21
commit aedabceac7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -76,6 +76,8 @@ network:
# Flag indicating UDP audio should be RTP framed. # Flag indicating UDP audio should be RTP framed.
# NOTE: This flag is only applicable when encoding G.711 uLaw. # NOTE: This flag is only applicable when encoding G.711 uLaw.
udpRTPFrames: false udpRTPFrames: false
# Flag indicating UDP audio should follow the USRP format.
udpUsrp: false
# Source "Radio ID" for transmitted audio frames. # Source "Radio ID" for transmitted audio frames.
sourceId: 1234567 sourceId: 1234567

@ -5,6 +5,7 @@
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Caleb, K4PHP
* *
*/ */
#include "Defines.h" #include "Defines.h"
@ -293,6 +294,7 @@ HostBridge::HostBridge(const std::string& confFile) :
m_udpNoIncludeLength(false), m_udpNoIncludeLength(false),
m_udpUseULaw(false), m_udpUseULaw(false),
m_udpRTPFrames(false), m_udpRTPFrames(false),
m_udpUsrp(false),
m_srcId(p25::defines::WUID_FNE), m_srcId(p25::defines::WUID_FNE),
m_srcIdOverride(0U), m_srcIdOverride(0U),
m_overrideSrcIdFromMDC(false), m_overrideSrcIdFromMDC(false),
@ -353,7 +355,8 @@ HostBridge::HostBridge(const std::string& confFile) :
m_trace(false), m_trace(false),
m_debug(false), m_debug(false),
m_rtpSeqNo(0U), m_rtpSeqNo(0U),
m_rtpTimestamp(INVALID_TS) m_rtpTimestamp(INVALID_TS),
m_usrpSeqNo(0U)
#if defined(_WIN32) #if defined(_WIN32)
, ,
m_encoderState(nullptr), m_encoderState(nullptr),
@ -1019,10 +1022,19 @@ bool HostBridge::createNetwork()
m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as<uint32_t>(34001); m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as<uint32_t>(34001);
m_udpReceiveAddress = networkConf["udpReceiveAddress"].as<std::string>(); m_udpReceiveAddress = networkConf["udpReceiveAddress"].as<std::string>();
m_udpUseULaw = networkConf["udpUseULaw"].as<bool>(false); m_udpUseULaw = networkConf["udpUseULaw"].as<bool>(false);
m_udpUsrp = networkConf["udpUsrp"].as<bool>(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) { if (m_udpUseULaw) {
m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as<bool>(false); m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as<bool>(false);
m_udpRTPFrames = networkConf["udpRTPFrames"].as<bool>(false); m_udpRTPFrames = networkConf["udpRTPFrames"].as<bool>(false);
m_udpUsrp = false; // ULaw disables USRP
if (m_udpRTPFrames) if (m_udpRTPFrames)
m_udpNoIncludeLength = true; // RTP disables the length being included 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 No Length Header: %s", m_udpNoIncludeLength ? "yes" : "no");
LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "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); LogInfo(" Source ID: %u", m_srcId);
@ -1178,31 +1191,40 @@ void HostBridge::processUDPAudio()
pcmLength = __GET_UINT32(buffer, 0U); pcmLength = __GET_UINT32(buffer, 0U);
} }
if (m_udpRTPFrames) if (m_udpRTPFrames || m_udpUsrp)
pcmLength = MBE_SAMPLES_LENGTH * 2U; pcmLength = MBE_SAMPLES_LENGTH * 2U;
UInt8Array __pcm = std::make_unique<uint8_t[]>(pcmLength); UInt8Array __pcm = std::make_unique<uint8_t[]>(pcmLength);
uint8_t* pcm = __pcm.get(); 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) { ::memcpy(pcm, buffer + RTP_HEADER_LENGTH_BYTES, MBE_SAMPLES_LENGTH * 2U);
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);
} }
else { 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); // 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::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t diff = now - m_rxStartTime; 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); 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; uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U;
uint8_t* audioData = nullptr; uint8_t* audioData = nullptr;
if (!m_udpMetadata) { if (!m_udpUsrp) {
audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length) if (!m_udpMetadata) {
if (m_udpUseULaw) { audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length)
length = (MBE_SAMPLES_LENGTH) + 4U; if (m_udpUseULaw) {
if (m_udpNoIncludeLength) { length = (MBE_SAMPLES_LENGTH)+4U;
length = MBE_SAMPLES_LENGTH; if (m_udpNoIncludeLength) {
::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH); length = MBE_SAMPLES_LENGTH;
} else { ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH);
__SET_UINT32(MBE_SAMPLES_LENGTH, audioData, 0U); }
::memcpy(audioData + 4U, 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;
} }
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 { 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); __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); ::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 { else {
length = (MBE_SAMPLES_LENGTH * 2U) + 12U; uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH];
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); length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH;
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header)
// embed destination and source IDs m_usrpSeqNo++;
__SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); usrpHeader[15U] = 1; // set PTT state to true
__SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); __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; 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::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t diff = now - m_rxStartTime; 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); 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; uint32_t length = (MBE_SAMPLES_LENGTH * 2U) + 4U;
uint8_t* audioData = nullptr; 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_udpUsrp) {
if (m_udpRTPFrames) { if (!m_udpMetadata) {
uint8_t* rtpFrame = generateRTPHeaders(MBE_SAMPLES_LENGTH, m_rtpSeqNo); audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + 4U]; // PCM + 4 bytes (PCM length)
if (rtpFrame != nullptr) { if (m_udpUseULaw) {
length += RTP_HEADER_LENGTH_BYTES; length = (MBE_SAMPLES_LENGTH)+4U;
uint8_t* newAudioData = new uint8_t[length]; if (m_udpNoIncludeLength) {
::memcpy(newAudioData, rtpFrame, RTP_HEADER_LENGTH_BYTES); length = MBE_SAMPLES_LENGTH;
::memcpy(newAudioData + RTP_HEADER_LENGTH_BYTES, audioData, MBE_SAMPLES_LENGTH); ::memcpy(audioData, pcm, MBE_SAMPLES_LENGTH);
delete[] audioData; }
else {
audioData = newAudioData; __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 { 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); __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U);
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); ::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 { else {
length = (MBE_SAMPLES_LENGTH * 2U) + 12U; uint8_t* usrpHeader = new uint8_t[USRP_HEADER_LENGTH];
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); length = (MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH;
::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); audioData = new uint8_t[(MBE_SAMPLES_LENGTH * 2U) + USRP_HEADER_LENGTH]; // PCM + 32 bytes (USRP Header)
// embed destination and source IDs m_usrpSeqNo++;
__SET_UINT32(dstId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 4U)); usrpHeader[15U] = 1; // set PTT state to true
__SET_UINT32(srcId, audioData, ((MBE_SAMPLES_LENGTH * 2U) + 8U)); __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; sockaddr_storage addr;
@ -2341,6 +2407,22 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_
m_p25N++; 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. */ /* Helper to generate the single-tone preamble tone. */
void HostBridge::generatePreambleTone() void HostBridge::generatePreambleTone()

@ -56,6 +56,8 @@
#define DECSTATE_SIZE 2048 #define DECSTATE_SIZE 2048
#define ENCSTATE_SIZE 6144 #define ENCSTATE_SIZE 6144
#define USRP_HEADER_LENGTH 32
const uint8_t FULL_RATE_MODE = 0x00U; const uint8_t FULL_RATE_MODE = 0x00U;
const uint8_t HALF_RATE_MODE = 0x01U; const uint8_t HALF_RATE_MODE = 0x01U;
@ -162,6 +164,7 @@ private:
bool m_udpNoIncludeLength; bool m_udpNoIncludeLength;
bool m_udpUseULaw; bool m_udpUseULaw;
bool m_udpRTPFrames; bool m_udpRTPFrames;
bool m_udpUsrp;
uint32_t m_srcId; uint32_t m_srcId;
uint32_t m_srcIdOverride; uint32_t m_srcIdOverride;
@ -245,6 +248,8 @@ private:
uint16_t m_rtpSeqNo; uint16_t m_rtpSeqNo;
uint32_t m_rtpTimestamp; uint32_t m_rtpTimestamp;
uint32_t m_usrpSeqNo;
static std::mutex m_audioMutex; static std::mutex m_audioMutex;
static std::mutex m_networkMutex; static std::mutex m_networkMutex;
@ -446,6 +451,11 @@ private:
*/ */
uint8_t* generateRTPHeaders(uint8_t msgLen, uint16_t& rtpSeq); 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. * @brief Helper to generate the single-tone preamble tone.
*/ */

Loading…
Cancel
Save

Powered by TurnKey Linux.