diff --git a/config.yml b/config.yml index 76e8e123..aade37a1 100644 --- a/config.yml +++ b/config.yml @@ -41,6 +41,7 @@ protocols: dumpCsbkData: false callHang: 5 txHang: 8 + silenceThreshold: 21 queueSize: 5000 verbose: true debug: false @@ -92,7 +93,7 @@ system: dmrNetId: 1 voiceChNo: - 1 - colorCode: 5 + colorCode: 1 nac: 293 pSuperGroup: FFFF netId: BB800 diff --git a/dmr/Control.cpp b/dmr/Control.cpp index 710b0b89..51523148 100644 --- a/dmr/Control.cpp +++ b/dmr/Control.cpp @@ -136,12 +136,22 @@ void Control::setOptions(yaml::Node& conf, uint32_t netId, uint8_t siteId, uint8 m_slot2->setTSCC(enableTSCC, dedicatedTSCC); break; default: - LogError(LOG_NET, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); + LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); break; } + uint32_t silenceThreshold = dmrProtocol["silenceThreshold"].as(dmr::DEFAULT_SILENCE_THRESHOLD); + if (silenceThreshold > MAX_DMR_VOICE_ERRORS) { + LogWarning(LOG_DMR, "Silence threshold > %u, defaulting to %u", dmr::MAX_DMR_VOICE_ERRORS, dmr::DEFAULT_SILENCE_THRESHOLD); + silenceThreshold = dmr::DEFAULT_SILENCE_THRESHOLD; + } + + m_slot1->setSilenceThreshold(silenceThreshold); + m_slot2->setSilenceThreshold(silenceThreshold); + if (printOptions) { LogInfo(" TSCC Slot: %u", m_tsccSlotNo); + LogInfo(" Silence Threshold: %u (%.1f%%)", silenceThreshold, float(silenceThreshold) / 1.41F); } } diff --git a/dmr/DMRDefines.h b/dmr/DMRDefines.h index 22733e5d..537cff1b 100644 --- a/dmr/DMRDefines.h +++ b/dmr/DMRDefines.h @@ -99,6 +99,8 @@ namespace dmr 0x81U, 0x52U, 0x60U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU }; + const uint8_t DMR_NULL_AMBE[] = { 0xB1U, 0xA8U, 0x22U, 0x25U, 0x6BU, 0xD1U, 0x6CU, 0xCFU, 0x67U }; + const uint8_t PAYLOAD_LEFT_MASK[] = { 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U }; const uint8_t PAYLOAD_RIGHT_MASK[] = { 0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU }; @@ -130,6 +132,9 @@ namespace dmr const uint16_t DMR_LOGICAL_CH_ABSOLUTE = 0xFFFU; + const uint32_t DEFAULT_SILENCE_THRESHOLD = 21U; + const uint32_t MAX_DMR_VOICE_ERRORS = 141U; + // PDU Data Formats const uint8_t DPF_UDT = 0x00U; const uint8_t DPF_RESPONSE = 0x01U; diff --git a/dmr/Slot.cpp b/dmr/Slot.cpp index 5d0da21a..7634e1ad 100644 --- a/dmr/Slot.cpp +++ b/dmr/Slot.cpp @@ -142,6 +142,7 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_minRSSI(0U), m_aveRSSI(0U), m_rssiCount(0U), + m_silenceThreshold(DEFAULT_SILENCE_THRESHOLD), m_ccSeq(0U), m_ccRunning(false), m_enableTSCC(false), @@ -525,6 +526,15 @@ void Slot::setTSCC(bool enable, bool dedicated) m_dedicatedTSCC = dedicated; } +/// +/// Helper to set the voice error silence threshold. +/// +/// +void Slot::setSilenceThreshold(uint32_t threshold) +{ + m_silenceThreshold = threshold; +} + /// /// Helper to initialize the DMR slot processor. /// diff --git a/dmr/Slot.h b/dmr/Slot.h index 24740677..eb15385d 100644 --- a/dmr/Slot.h +++ b/dmr/Slot.h @@ -93,6 +93,8 @@ namespace dmr /// Helper to enable and configure TSCC support for this slot. void setTSCC(bool enable, bool dedicated); + /// Helper to set the voice error silence threshold. + void setSilenceThreshold(uint32_t threshold); /// Helper to initialize the slot processor. static void init(uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, @@ -156,6 +158,8 @@ namespace dmr uint32_t m_aveRSSI; uint32_t m_rssiCount; + uint32_t m_silenceThreshold; + uint8_t m_ccSeq; bool m_ccRunning; diff --git a/dmr/VoicePacket.cpp b/dmr/VoicePacket.cpp index 8962d05b..8d15e28f 100644 --- a/dmr/VoicePacket.cpp +++ b/dmr/VoicePacket.cpp @@ -259,6 +259,13 @@ bool VoicePacket::process(uint8_t* data, uint32_t len) errors, float(errors) / 1.41F); } + if (errors > m_slot->m_silenceThreshold) { + insertNullAudio(data + 2U); + m_fec.regenerateDMR(data + 2U); + + LogWarning(LOG_RF, DMR_DT_VOICE_SYNC ", exceeded lost audio threshold, filling in"); + } + m_slot->m_rfErrs += errors; } @@ -312,6 +319,20 @@ bool VoicePacket::process(uint8_t* data, uint32_t len) m_rfN, errors, float(errors) / 1.41F); } + if (errors > m_slot->m_silenceThreshold) { + // Get the LCSS from the EMB + data::EMB emb; + emb.decode(data + 2U); + + insertNullAudio(data + 2U); + m_fec.regenerateDMR(data + 2U); + + // Regenerate EMB + emb.encode(data + 2U); + + LogWarning(LOG_RF, DMR_DT_VOICE ", exceeded lost audio threshold, filling in"); + } + m_slot->m_rfErrs += errors; } @@ -540,10 +561,24 @@ bool VoicePacket::process(uint8_t* data, uint32_t len) if (fid == FID_ETSI || fid == FID_DMRA) { errors = m_fec.regenerateDMR(data + 2U); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, DT_VOICE audio, sequence no = %u, errs = %u/141 (%.1f%%)", + LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_rfN, errors, float(errors) / 1.41F); } + if (errors > m_slot->m_silenceThreshold) { + // Get the LCSS from the EMB + data::EMB emb; + emb.decode(data + 2U); + + insertNullAudio(data + 2U); + m_fec.regenerateDMR(data + 2U); + + // Regenerate EMB + emb.encode(data + 2U); + + LogWarning(LOG_RF, DMR_DT_VOICE ", exceeded lost audio threshold, filling in"); + } + m_slot->m_rfErrs += errors; } @@ -1121,6 +1156,27 @@ void VoicePacket::logGPSPosition(const uint32_t srcId, const uint8_t* data) LogMessage(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); } +/// +/// Helper to insert AMBE null frames for missing audio. +/// +/// +void VoicePacket::insertNullAudio(uint8_t* data) +{ + uint8_t* ambeBuffer = new uint8_t[dmr::DMR_AMBE_LENGTH_BYTES]; + ::memset(ambeBuffer, 0x00U, dmr::DMR_AMBE_LENGTH_BYTES); + + for (uint32_t i = 0; i < 3U; i++) { + ::memcpy(ambeBuffer + (i * 9U), DMR_NULL_AMBE, 9U); + } + + ::memcpy(data, ambeBuffer, 13U); + data[13U] = ambeBuffer[13U] & 0xF0U; + data[19U] = ambeBuffer[13U] & 0x0FU; + ::memcpy(data + 20U, ambeBuffer + 14U, 13U); + + delete[] ambeBuffer; +} + /// /// Helper to insert DMR AMBE silence frames. /// diff --git a/dmr/VoicePacket.h b/dmr/VoicePacket.h index e8f7a77f..b5d52c1e 100644 --- a/dmr/VoicePacket.h +++ b/dmr/VoicePacket.h @@ -105,6 +105,8 @@ namespace dmr /// void logGPSPosition(const uint32_t srcId, const uint8_t* data); + /// Helper to insert AMBE null frames for missing audio. + void insertNullAudio(uint8_t* data); /// Helper to insert DMR AMBE silence frames. bool insertSilence(const uint8_t* data, uint8_t seqNo); /// Helper to insert DMR AMBE silence frames. diff --git a/p25/Control.cpp b/p25/Control.cpp index 38134bce..40c5126d 100644 --- a/p25/Control.cpp +++ b/p25/Control.cpp @@ -214,6 +214,10 @@ void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const s m_ackTSBKRequests = control["ackRequests"].as(true); m_voice->m_silenceThreshold = p25Protocol["silenceThreshold"].as(p25::DEFAULT_SILENCE_THRESHOLD); + if (m_voice->m_silenceThreshold > MAX_P25_VOICE_ERRORS) { + LogWarning(LOG_P25, "Silence threshold > %u, defaulting to %u", p25::MAX_P25_VOICE_ERRORS, p25::DEFAULT_SILENCE_THRESHOLD); + m_voice->m_silenceThreshold = p25::DEFAULT_SILENCE_THRESHOLD; + } m_disableNetworkHDU = p25Protocol["disableNetworkHDU"].as(false); diff --git a/p25/P25Defines.h b/p25/P25Defines.h index 2fd017ed..0a0bb982 100644 --- a/p25/P25Defines.h +++ b/p25/P25Defines.h @@ -202,6 +202,7 @@ namespace p25 const uint32_t P25_TGID_ALL = 0xFFFFU; const uint32_t DEFAULT_SILENCE_THRESHOLD = 124U; + const uint32_t MAX_P25_VOICE_ERRORS = 1233U; // PDU Format Type(s) const uint8_t PDU_FMT_RSP = 0x03U; diff --git a/p25/VoicePacket.cpp b/p25/VoicePacket.cpp index 6ccf6449..bdc323fe 100644 --- a/p25/VoicePacket.cpp +++ b/p25/VoicePacket.cpp @@ -889,7 +889,7 @@ VoicePacket::VoicePacket(Control* p25, network::BaseNetwork* network, bool debug m_hadVoice(false), m_lastRejectId(0U), m_lastPatchGroup(0U), - m_silenceThreshold(124U), + m_silenceThreshold(DEFAULT_SILENCE_THRESHOLD), m_vocLDU1Count(0U), m_verbose(verbose), m_debug(debug)