add support to inject silence frames during a hang time when using UDP audio, where UDP audio has ended but the drop timer hasn't timed out;

pull/85/head
Bryan Biedenkapp 10 months ago
parent fb0e51f36a
commit 1c51ff59bd

@ -68,6 +68,7 @@ network:
udpReceivePort: 32001
# PCM over UDP receive address.
udpReceiveAddress: "127.0.0.1"
# Flag indicating UDP audio should be encoded using G.711 uLaw.
udpUseULaw: false
# Flag indicating UDP audio should be transmitted without the length leader.
@ -79,6 +80,9 @@ network:
# Flag indicating UDP audio should follow the USRP format.
udpUsrp: false
# Flag indicating the UDP audio will be padded with silence during hang time before end of call.
udpHangSilence: true
# Traffic Encryption
tek:
# Flag indicating whether or not traffic encryption is enabled.
@ -135,7 +139,7 @@ system:
# Relative sample level for VOX to activate.
voxSampleLevel: 80.0
# Amount of time (ms) from loss of active VOX level to drop audio.
# Amount of time (ms) from loss of active call (loss of local VOX or end of UDP audio stream) before ending call.
dropTimeMs: 180
# Enables detection of MDC1200 packets on the PCM side of the bridge.

@ -298,6 +298,7 @@ HostBridge::HostBridge(const std::string& confFile) :
m_udpUseULaw(false),
m_udpRTPFrames(false),
m_udpUsrp(false),
m_udpSilenceDuringHang(true),
m_tekAlgoId(p25::defines::ALGO_UNENCRYPT),
m_tekKeyId(0U),
m_requestedTek(false),
@ -318,7 +319,10 @@ HostBridge::HostBridge(const std::string& confFile) :
m_txMode(1U),
m_voxSampleLevel(30.0f),
m_dropTimeMS(180U),
m_dropTime(1000U, 0U, 180U),
m_localDropTime(1000U, 0U, 180U),
m_udpCallClock(1000U, 0U, 80U),
m_udpHangTime(1000U, 0U, 180U),
m_udpDropTime(1000U, 0U, 180U),
m_detectAnalogMDC1200(false),
m_preambleLeaderTone(false),
m_preambleTone(2175),
@ -976,8 +980,31 @@ bool HostBridge::readParams()
m_txMode = TX_MODE_P25;
m_voxSampleLevel = systemConf["voxSampleLevel"].as<float>(30.0f);
m_dropTimeMS = (uint16_t)systemConf["dropTimeMs"].as<uint32_t>(180);
m_dropTime = Timer(1000U, 0U, m_dropTimeMS);
m_dropTimeMS = (uint16_t)systemConf["dropTimeMs"].as<uint32_t>(180U);
yaml::Node networkConf = m_conf["network"];
m_udpAudio = networkConf["udpAudio"].as<bool>(false);
bool udpSilenceDuringHang = networkConf["udpHangSilence"].as<bool>(true);
switch (m_txMode) {
case TX_MODE_DMR:
break;
case TX_MODE_P25:
{
if (m_udpAudio && udpSilenceDuringHang && m_dropTimeMS < 360U) {
::LogWarning(LOG_HOST, "When using UDP silence during hang time, the minimum allowable drop time is 360ms.");
m_dropTimeMS = 360U; // drop time for UDP is minimum 360ms when using silence during hang time
}
}
break;
}
m_localDropTime = Timer(1000U, 0U, m_dropTimeMS);
m_udpDropTime = Timer(1000U, 0U, m_dropTimeMS);
// bryanb: UDP drop timer cannot be less then 180ms
if (m_dropTimeMS > 180U)
m_udpDropTime = Timer(1000U, 0U, m_dropTimeMS);
m_detectAnalogMDC1200 = systemConf["detectAnalogMDC1200"].as<bool>(false);
@ -991,9 +1018,6 @@ bool HostBridge::readParams()
m_localAudio = systemConf["localAudio"].as<bool>(true);
yaml::Node networkConf = m_conf["network"];
m_udpAudio = networkConf["udpAudio"].as<bool>(false);
m_trace = systemConf["trace"].as<bool>(false);
m_debug = systemConf["debug"].as<bool>(false);
@ -1044,6 +1068,7 @@ bool HostBridge::createNetwork()
m_udpReceiveAddress = networkConf["udpReceiveAddress"].as<std::string>();
m_udpUseULaw = networkConf["udpUseULaw"].as<bool>(false);
m_udpUsrp = networkConf["udpUsrp"].as<bool>(false);
m_udpSilenceDuringHang = networkConf["udpHangSilence"].as<bool>(true);
if (m_udpUsrp) {
m_udpMetadata = false; // USRP disables metadata due to USRP always having metadata
@ -1063,6 +1088,9 @@ bool HostBridge::createNetwork()
if (m_udpUseULaw && m_udpMetadata)
m_udpMetadata = false; // metadata isn't supported when encoding uLaw
if (m_udpSilenceDuringHang && m_udpRTPFrames)
m_udpCallClock = Timer(1000U, 0U, 160U); // packets every 160ms
yaml::Node tekConf = networkConf["tek"];
bool tekEnable = tekConf["enable"].as<bool>(false);
std::string tekAlgo = tekConf["tekAlgo"].as<std::string>();
@ -1160,6 +1188,7 @@ bool HostBridge::createNetwork()
LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "yes" : "no");
}
LogInfo(" UDP Audio USRP: %s", m_udpUsrp ? "yes" : "no");
LogInfo(" UDP Silence During Hangtime: %s", m_udpSilenceDuringHang ? "yes" : "no");
}
LogInfo(" Traffic Encrypted: %s", tekEnable ? "yes" : "no");
@ -1375,15 +1404,18 @@ void HostBridge::processUDPAudio()
}
}
m_dropTime.stop();
m_udpCallClock.stop();
m_udpDropTime.stop();
if (!m_dropTime.isRunning())
m_dropTime.start();
if (!m_udpDropTime.isRunning())
m_udpDropTime.start();
m_udpCallClock.start();
}
// If audio detection is active and no call is in progress, encode and transmit the audio
if (m_audioDetect && !m_callInProgress) {
m_dropTime.start();
m_udpDropTime.start();
m_udpCallClock.start();
switch (m_txMode) {
case TX_MODE_DMR:
@ -2708,7 +2740,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId)
trafficType = UDP_CALL;
}
if (srcId == 0U && !m_audioDetect && !m_dropTime.isRunning()) {
if (srcId == 0U && !m_audioDetect && (!m_localDropTime.isRunning() || !m_udpDropTime.isRunning())) {
LogError(LOG_HOST, "%s, call end, ignoring invalid call end, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId);
return;
}
@ -2716,7 +2748,10 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId)
LogMessage(LOG_HOST, "%s, call end, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId);
m_audioDetect = false;
m_dropTime.stop();
m_localDropTime.stop();
m_udpDropTime.stop();
m_udpHangTime.stop();
m_udpCallClock.stop();
if (!m_callInProgress) {
switch (m_txMode) {
@ -2911,17 +2946,17 @@ void* HostBridge::threadAudioProcess(void* arg)
}
}
bridge->m_dropTime.stop();
bridge->m_localDropTime.stop();
} else {
// if we've exceeded the audio drop timeout, then really drop the audio
if (bridge->m_dropTime.isRunning() && bridge->m_dropTime.hasExpired()) {
if (bridge->m_localDropTime.isRunning() && bridge->m_localDropTime.hasExpired()) {
if (bridge->m_audioDetect) {
bridge->callEnd(srcId, dstId);
}
}
if (!bridge->m_dropTime.isRunning())
bridge->m_dropTime.start();
if (!bridge->m_localDropTime.isRunning())
bridge->m_localDropTime.start();
}
if (bridge->m_audioDetect && !bridge->m_callInProgress) {
@ -3032,6 +3067,161 @@ void* HostBridge::threadNetworkProcess(void* arg)
return nullptr;
}
/* Helper to reset IMBE buffer with null frames. */
void HostBridge::resetWithNullAudio(uint8_t* data, bool encrypted)
{
if (data == nullptr)
return;
// clear buffer for next sequence
::memset(data, 0x00U, 9U * 25U);
// fill with null
if (!encrypted) {
::memcpy(data + 10U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 26U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 55U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 80U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 105U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 130U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 155U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 180U, P25DEF::NULL_IMBE, 11U);
::memcpy(data + 204U, P25DEF::NULL_IMBE, 11U);
}
else {
::memcpy(data + 10U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 26U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 55U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 80U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 105U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 130U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 155U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 180U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
::memcpy(data + 204U, P25DEF::ENCRYPTED_NULL_IMBE, 11U);
}
}
/* */
void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId)
{
switch (m_txMode) {
case TX_MODE_DMR:
{
using namespace dmr;
using namespace dmr::defines;
m_dmrN = (uint8_t)(m_dmrSeqNo % 6);
// send DMR voice
uint8_t data[DMR_FRAME_LENGTH_BYTES];
m_ambeCount = 0U;
::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES);
m_ambeCount++;
::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES);
m_ambeCount++;
::memcpy(m_ambeBuffer + (m_ambeCount * 9U), NULL_AMBE, RAW_AMBE_LENGTH_BYTES);
m_ambeCount++;
::memcpy(data, m_ambeBuffer, 13U);
data[13U] = (uint8_t)(m_ambeBuffer[13U] & 0xF0);
data[19U] = (uint8_t)(m_ambeBuffer[13U] & 0x0F);
::memcpy(data + 20U, m_ambeBuffer + 14U, 13U);
DataType::E dataType = DataType::VOICE_SYNC;
if (m_dmrN == 0)
dataType = DataType::VOICE_SYNC;
else {
dataType = DataType::VOICE;
uint8_t lcss = m_dmrEmbeddedData.getData(data, m_dmrN);
// generated embedded signalling
data::EMB emb = data::EMB();
emb.setColorCode(0U);
emb.setLCSS(lcss);
emb.encode(data);
}
LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN);
// generate DMR network frame
data::NetData dmrData;
dmrData.setSlotNo(m_slot);
dmrData.setDataType(dataType);
dmrData.setSrcId(srcId);
dmrData.setDstId(dstId);
dmrData.setFLCO(FLCO::GROUP);
dmrData.setN(m_dmrN);
dmrData.setSeqNo(m_dmrSeqNo);
dmrData.setBER(0U);
dmrData.setRSSI(0U);
dmrData.setData(data);
m_network->writeDMR(dmrData, false);
m_txStreamId = m_network->getDMRStreamId(m_slot);
m_dmrSeqNo++;
}
break;
case TX_MODE_P25:
{
using namespace p25;
using namespace p25::defines;
// fill the LDU buffers appropriately
switch (m_p25N) {
// LDU1
case 0:
resetWithNullAudio(m_netLDU1, false);
break;
// LDU2
case 1:
resetWithNullAudio(m_netLDU2, false);
break;
}
lc::LC lc = lc::LC();
lc.setLCO(LCO::GROUP);
lc.setGroup(true);
lc.setPriority(4U);
lc.setDstId(dstId);
lc.setSrcId(srcId);
lc.setAlgId(ALGO_UNENCRYPT);
lc.setKId(0);
data::LowSpeedData lsd = data::LowSpeedData();
// send P25 LDU1
if (m_p25N == 0U) {
LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId);
m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT);
m_p25N++;
break;
}
// send P25 LDU2
if (m_p25N == 1U) {
LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U);
m_network->writeP25LDU2(lc, lsd, m_netLDU2);
m_p25N = 0U;
break;
}
}
break;
}
}
/* Entry point to call watchdog handler thread. */
void* HostBridge::threadCallWatchdog(void* arg)
@ -3073,8 +3263,14 @@ void* HostBridge::threadCallWatchdog(void* arg)
uint32_t ms = stopWatch.elapsed();
stopWatch.start();
if (bridge->m_dropTime.isRunning())
bridge->m_dropTime.clock(ms);
if (!bridge->m_trafficFromUDP) {
if (bridge->m_localDropTime.isRunning())
bridge->m_localDropTime.clock(ms);
}
else {
if (bridge->m_udpDropTime.isRunning())
bridge->m_udpDropTime.clock(ms);
}
std::string trafficType = LOCAL_CALL;
if (bridge->m_trafficFromUDP)
@ -3093,15 +3289,47 @@ void* HostBridge::threadCallWatchdog(void* arg)
srcId = bridge->m_udpSrcId;
dstId = bridge->m_udpDstId;
if (bridge->m_dropTime.isRunning() && bridge->m_dropTime.hasExpired()) {
if (bridge->m_trafficFromUDP) {
bridge->callEnd(srcId, dstId);
if (bridge->m_udpSilenceDuringHang) {
if (bridge->m_udpCallClock.isRunning())
bridge->m_udpCallClock.clock(ms);
if (bridge->m_udpCallClock.isRunning() && bridge->m_udpCallClock.hasExpired()) {
bridge->m_udpCallClock.stop();
bridge->m_udpHangTime.start();
bridge->m_dmrN = 0U;
bridge->m_p25N = 0U;
}
if (bridge->m_udpHangTime.isRunning())
bridge->m_udpHangTime.clock(ms);
if (bridge->m_udpHangTime.isRunning() && bridge->m_udpHangTime.hasExpired()) {
bridge->callEndSilence(srcId, dstId);
bridge->m_udpHangTime.start();
}
}
if (bridge->m_udpDropTime.isRunning() && bridge->m_udpDropTime.hasExpired()) {
if (bridge->m_udpSilenceDuringHang) {
bridge->m_udpHangTime.stop();
switch (bridge->m_txMode) {
case TX_MODE_DMR:
// TODO: send DMR silence
break;
case TX_MODE_P25:
if (bridge->m_p25N > 0U)
bridge->callEndSilence(srcId, dstId);
break;
}
}
bridge->callEnd(srcId, dstId);
}
}
else {
// if we've exceeded the drop timeout, then really drop the audio
if (bridge->m_dropTime.isRunning() && (bridge->m_dropTime.getTimer() >= dropTimeout)) {
if (bridge->m_localDropTime.isRunning() && (bridge->m_localDropTime.getTimer() >= dropTimeout)) {
LogMessage(LOG_HOST, "%s, terminating stuck call", trafficType.c_str());
bridge->callEnd(srcId, dstId);
}

@ -166,6 +166,7 @@ private:
bool m_udpUseULaw;
bool m_udpRTPFrames;
bool m_udpUsrp;
bool m_udpSilenceDuringHang;
uint8_t m_tekAlgoId;
uint16_t m_tekKeyId;
@ -192,7 +193,10 @@ private:
float m_voxSampleLevel;
uint16_t m_dropTimeMS;
Timer m_dropTime;
Timer m_localDropTime;
Timer m_udpCallClock;
Timer m_udpHangTime;
Timer m_udpDropTime;
bool m_detectAnalogMDC1200;
@ -506,6 +510,20 @@ private:
*/
static void* threadNetworkProcess(void* arg);
/**
* @brief Helper to reset IMBE buffer with null frames.
* @param data Buffer containing frame data.
* @param encrypted Flag indicating whether or not the data is encrypted.
*/
void resetWithNullAudio(uint8_t* data, bool encrypted);
/**
* @brief Helper to send silence audio frames.
* @param srcId
* @param dstId
*/
void callEndSilence(uint32_t srcId, uint32_t dstId);
/**
* @brief Entry point to call watchdog handler thread.
* @param arg Instance of the thread_t structure.

Loading…
Cancel
Save

Powered by TurnKey Linux.