add support for DMR reverse-channel data in the EMB field;

pull/121/merge
Bryan Biedenkapp 3 weeks ago
parent 07b0ebaf85
commit 12d8d5f313

@ -130,6 +130,15 @@ namespace dmr
const uint32_t RAW_AMBE_LENGTH_BYTES = 9U; const uint32_t RAW_AMBE_LENGTH_BYTES = 9U;
/** @} */ /** @} */
/** @name Reverse Channel Commands */
const uint8_t RC_CEASE_TRANSMIT[5U] = { 0x07U, 0x0DU, 0x04U, 0xF1U, 0xF0U };
const uint8_t RC_REQUEST_CEASE_TRANSMIT[5U] = { 0x02U, 0x7BU, 0x4EU, 0x48U, 0x70U };
const uint8_t RC_MAX_POWER[5U] = { 0x01U, 0xC3U, 0xDDU, 0x3CU, 0x10U };
const uint8_t RC_MIN_POWER[5U] = { 0x04U, 0xB5U, 0x97U, 0x85U, 0x90U };
const uint8_t RC_POWER_INCREASE[5U] = { 0x04U, 0x5BU, 0xA7U, 0x58U, 0xA0U };
const uint8_t RC_POWER_DECREASE[5U] = { 0x01U, 0x2DU, 0xEDU, 0xE1U, 0x20U };
/** @} */
/** @name Thresholds */ /** @name Thresholds */
/** @brief TSCC maximum CSC count */ /** @brief TSCC maximum CSC count */
const uint16_t TSCC_MAX_CSC_CNT = 511U; const uint16_t TSCC_MAX_CSC_CNT = 511U;

@ -130,6 +130,14 @@ namespace network
BUSY_DENY = 0x00U, //!< Busy Deny BUSY_DENY = 0x00U, //!< Busy Deny
REJECT_TRAFFIC = 0x01U, //!< Reject Active Traffic REJECT_TRAFFIC = 0x01U, //!< Reject Active Traffic
// DMR Reverse Channel Commands
DMR_RC_CEASE_TRANSMIT = 0xD2U, //!< DMR Reverse Channel: Cease Transmission
DMR_RC_REQUEST_CEASE_TRANSMIT = 0xD3U, //!< DMR Reverse Channel: Request Cease Transmission
DMR_RC_MAXIMUM_POWER = 0xD4U, //!< DMR Reverse Channel: Maximum Power
DMR_RC_MINIMUM_POWER = 0xD5U, //!< DMR Reverse Channel: Minimum Power
DMR_RC_POWER_INCREASE_ONE_STEP = 0xD6U, //!< DMR Reverse Channel: Increase Power One Step
DMR_RC_POWER_DECREASE_ONE_STEP = 0xD7U, //!< DMR Reverse Channel: Decrease Power One Step
}; };
}; };

@ -1058,6 +1058,12 @@ void HostFNE::processPeerDMRInCallCtrl(network::NET_ICC::ENUM command, uint32_t
{ {
switch (command) { switch (command) {
case network::NET_ICC::REJECT_TRAFFIC: case network::NET_ICC::REJECT_TRAFFIC:
case network::NET_ICC::DMR_RC_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_MAXIMUM_POWER:
case network::NET_ICC::DMR_RC_MINIMUM_POWER:
case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP:
case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP:
m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, dstId, slot, peerId, ssrc, streamId); m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, dstId, slot, peerId, ssrc, streamId);
break; break;

@ -2508,7 +2508,15 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network::
switch (command) { switch (command) {
case network::NET_ICC::REJECT_TRAFFIC: case network::NET_ICC::REJECT_TRAFFIC:
case network::NET_ICC::DMR_RC_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_MAXIMUM_POWER:
case network::NET_ICC::DMR_RC_MINIMUM_POWER:
case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP:
case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP:
{ {
const bool callTakeover = (command == network::NET_ICC::REJECT_TRAFFIC);
// is this a local peer? // is this a local peer?
if (ssrc > 0 && (m_peers.find(ssrc) != m_peers.end())) { if (ssrc > 0 && (m_peers.find(ssrc) != m_peers.end())) {
FNEPeerConnection* connection = m_peers[ssrc]; FNEPeerConnection* connection = m_peers[ssrc];
@ -2520,26 +2528,28 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network::
// send ICC request to local peer // send ICC request to local peer
writePeerICC(ssrc, streamId, subFunc, command, dstId, slotNo, true); writePeerICC(ssrc, streamId, subFunc, command, dstId, slotNo, true);
// flag the protocol call handler to allow call takeover on the next audio frame if (callTakeover) {
switch (subFunc) { // flag the protocol call handler to allow call takeover on the next audio frame
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame switch (subFunc) {
m_tagDMR->triggerCallTakeover(dstId); case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame
break; m_tagDMR->triggerCallTakeover(dstId);
break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame
m_tagP25->triggerCallTakeover(dstId); m_tagP25->triggerCallTakeover(dstId);
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame
m_tagNXDN->triggerCallTakeover(dstId); m_tagNXDN->triggerCallTakeover(dstId);
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame
m_tagAnalog->triggerCallTakeover(dstId); m_tagAnalog->triggerCallTakeover(dstId);
break; break;
default: default:
break; break;
}
} }
} }
} }
@ -2566,26 +2576,28 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network::
} }
m_peers.shared_unlock(); m_peers.shared_unlock();
// flag the protocol call handler to allow call takeover on the next audio frame if (callTakeover) {
switch (subFunc) { // flag the protocol call handler to allow call takeover on the next audio frame
case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame switch (subFunc) {
m_tagDMR->triggerCallTakeover(dstId); case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame
break; m_tagDMR->triggerCallTakeover(dstId);
break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame
m_tagP25->triggerCallTakeover(dstId); m_tagP25->triggerCallTakeover(dstId);
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame
m_tagNXDN->triggerCallTakeover(dstId); m_tagNXDN->triggerCallTakeover(dstId);
break; break;
case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame
m_tagAnalog->triggerCallTakeover(dstId); m_tagAnalog->triggerCallTakeover(dstId);
break; break;
default: default:
break; break;
}
} }
// send further up the network tree (only if ICC request came from a local peer) // send further up the network tree (only if ICC request came from a local peer)

@ -168,7 +168,8 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz
m_notifyCC(true), m_notifyCC(true),
m_ccDebug(debug), m_ccDebug(debug),
m_verbose(verbose), m_verbose(verbose),
m_debug(debug) m_debug(debug),
m_reverseChannelCommand(network::NET_ICC::NOP)
{ {
m_interval.start(); m_interval.start();
@ -550,6 +551,27 @@ void Slot::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId)
m_rfLastSrcId = 0U; m_rfLastSrcId = 0U;
m_rfTGHang.stop(); m_rfTGHang.stop();
m_rfState = RS_RF_REJECTED; m_rfState = RS_RF_REJECTED;
m_reverseChannelCommand = network::NET_ICC::NOP;
}
}
break;
case network::NET_ICC::DMR_RC_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT:
case network::NET_ICC::DMR_RC_MAXIMUM_POWER:
case network::NET_ICC::DMR_RC_MINIMUM_POWER:
case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP:
case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP:
{
if (m_rfState == RS_RF_AUDIO && m_rfLC != nullptr && (dstId == 0U || m_rfLC->getDstId() == dstId)) {
m_reverseChannelCommand = command;
if (m_verbose) {
LogInfoEx(LOG_DMR, "Slot %u, set DMR reverse channel command = $%02X, dstId = %u", m_slotNo, command, dstId);
}
}
else if (m_verbose) {
LogInfoEx(LOG_DMR, "Slot %u, ignored DMR reverse channel command = $%02X, no active matching RF audio call, dstId = %u", m_slotNo, command, dstId);
} }
} }
break; break;

@ -445,6 +445,8 @@ namespace dmr
bool m_verbose; bool m_verbose;
bool m_debug; bool m_debug;
network::NET_ICC::ENUM m_reverseChannelCommand;
static Control* s_dmr; static Control* s_dmr;
static bool s_authoritative; static bool s_authoritative;

@ -471,6 +471,12 @@ bool Voice::process(uint8_t* data, uint32_t len)
// Regenerate the EMB // Regenerate the EMB
emb.setColorCode(m_slot->s_colorCode); emb.setColorCode(m_slot->s_colorCode);
emb.setLCSS(lcss); emb.setLCSS(lcss);
// Emit reverse-channel data in the final voice burst of a superframe
if (m_rfN == 5U) {
applyReverseChannelCommand(m_slot->m_reverseChannelCommand, data, emb);
}
emb.encode(data + 2U); emb.encode(data + 2U);
if (!m_slot->m_rfTimeout) { if (!m_slot->m_rfTimeout) {
@ -1315,6 +1321,45 @@ void Voice::logGPSPosition(const uint32_t srcId, const uint8_t* data)
LogInfoEx(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); LogInfoEx(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error);
} }
/* Helper to apply a DMR reverse channel command received via in-call control. */
bool Voice::applyReverseChannelCommand(network::NET_ICC::ENUM command, uint8_t* data, dmr::data::EMB& emb)
{
const uint8_t* payload = nullptr;
switch (command) {
case network::NET_ICC::DMR_RC_CEASE_TRANSMIT:
payload = RC_CEASE_TRANSMIT;
break;
case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT:
payload = RC_REQUEST_CEASE_TRANSMIT;
break;
case network::NET_ICC::DMR_RC_MAXIMUM_POWER:
payload = RC_MAX_POWER;
break;
case network::NET_ICC::DMR_RC_MINIMUM_POWER:
payload = RC_MIN_POWER;
break;
case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP:
payload = RC_POWER_INCREASE;
break;
case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP:
payload = RC_POWER_DECREASE;
break;
default:
return false;
}
data[16U] = (data[16U] & 0xF0U) | (payload[0U] & 0x0FU);
data[17U] = payload[1U];
data[18U] = payload[2U];
data[19U] = payload[3U];
data[20U] = (data[20U] & 0x0FU) | (payload[4U] & 0xF0U);
// Reverse-channel command data is indicated via PI in EMB.
emb.setPI(true);
return true;
}
/* Helper to insert AMBE null frames for missing audio. */ /* Helper to insert AMBE null frames for missing audio. */
void Voice::insertNullAudio(uint8_t* data) void Voice::insertNullAudio(uint8_t* data)

@ -19,6 +19,7 @@
#include "Defines.h" #include "Defines.h"
#include "common/dmr/data/NetData.h" #include "common/dmr/data/NetData.h"
#include "common/dmr/data/EMB.h"
#include "common/dmr/data/EmbeddedData.h" #include "common/dmr/data/EmbeddedData.h"
#include "common/dmr/lc/LC.h" #include "common/dmr/lc/LC.h"
#include "common/dmr/lc/PrivacyLC.h" #include "common/dmr/lc/PrivacyLC.h"
@ -132,6 +133,15 @@ namespace dmr
*/ */
void logGPSPosition(const uint32_t srcId, const uint8_t* data); void logGPSPosition(const uint32_t srcId, const uint8_t* data);
/**
* @brief Helper to apply a DMR reverse channel command received via in-call control.
* @param command The reverse channel command.
* @param data Buffer containing command data.
* @param emb Embedded data associated with the command.
* @return bool True if the command was successfully applied, otherwise false.
*/
bool applyReverseChannelCommand(network::NET_ICC::ENUM command, uint8_t* data, dmr::data::EMB& emb);
/** /**
* @brief Helper to insert AMBE null frames for missing audio. * @brief Helper to insert AMBE null frames for missing audio.
* @param data Buffer containing frame data. * @param data Buffer containing frame data.

Loading…
Cancel
Save

Powered by TurnKey Linux.