diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 55217ff7..4b3540b9 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -58,7 +58,7 @@ network: # Enable PCM audio over UDP. udpAudio: false - # Enable meta data such as dstId and srcId in the UDP data + # Enable meta data such as dstId and srcId in the UDP data. udpMetadata: false # PCM over UDP send port. udpSendPort: 34001 @@ -68,6 +68,11 @@ 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. + # NOTE: This flag is only applicable when encoding G.711 uLaw. + udpNoIncludeLength: false # Source "Radio ID" for transmitted audio frames. sourceId: 1234567 diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 6fe4e54c..bd625428 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -55,6 +55,18 @@ const int NUMBER_OF_BUFFERS = 32; #define LOCAL_CALL "Local Traffic" #define UDP_CALL "UDP Traffic" +#define SIGN_BIT (0x80) // sign bit for a A-law byte +#define QUANT_MASK (0xf) // quantization field mask +#define NSEGS (8) // number of A-law segments +#define SEG_SHIFT (4) // left shift for segment number +#define SEG_MASK (0x70) // segment field mask + +static short seg_aend[8] = { 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF }; +static short seg_uend[8] = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF }; + +#define BIAS (0x84) // bias for linear code +#define CLIP 8159 + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -135,6 +147,128 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit } } +/* */ + +static short search(short val, short* table, short size) +{ + for (short i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + + return (size); +} + +/* Helper to convert PCM into G.711 aLaw. */ + +uint8_t pcmToaLaw(short pcm) +{ + short mask; + unsigned char aval; + + pcm = pcm >> 3; + + if (pcm >= 0) { + mask = 0xD5; // sign (7th) bit = 1 + } else { + mask = 0x55; // sign bit = 0 + pcm = -pcm - 1; + } + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_aend, 8); + + /* + ** combine the sign, segment, quantization bits + */ + if (seg >= 8) // out of range, return maximum value + return (uint8_t)(0x7F ^ mask); + else { + aval = (uint8_t) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm >> 1) & QUANT_MASK; + else + aval |= (pcm >> seg) & QUANT_MASK; + + return (aval ^ mask); + } +} + +/* Helper to convert G.711 aLaw into PCM. */ + +short aLawToPCM(uint8_t alaw) +{ + alaw ^= 0x55; + + short t = (alaw & QUANT_MASK) << 4; + short seg = ((unsigned)alaw & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + + return ((alaw & SIGN_BIT) ? t : -t); +} + +/* Helper to convert PCM into G.711 uLaw. */ + +uint8_t pcmTouLaw(short pcm) +{ + short mask; + + // get the sign and the magnitude of the value + pcm = pcm >> 2; + if (pcm < 0) { + pcm = -pcm; + mask = 0x7FU; + } else { + mask = 0xFFU; + } + + // clip the magnitude + if (pcm > CLIP) + pcm = CLIP; + pcm += (BIAS >> 2); + + // convert the scaled magnitude to segment number + short seg = search(pcm, seg_uend, 8); + + /* + ** combine the sign, segment, quantization bits; + ** and complement the code word + */ + if (seg >= 8) // out of range, return maximum value. + return (uint8_t)(0x7F ^ mask); + else { + uint8_t ulaw = (uint8_t)(seg << 4) | ((pcm >> (seg + 1)) & 0xF); + return (ulaw ^ mask); + } +} + +/* Helper to convert G.711 uLaw into PCM. */ + +short uLawToPCM(uint8_t ulaw) +{ + // complement to obtain normal u-law value + ulaw = ~ulaw; + + /* + ** extract and bias the quantization bits; then + ** shift up by the segment number and subtract out the bias + */ + short t = ((ulaw & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)ulaw & SEG_MASK) >> SEG_SHIFT; + + return ((ulaw & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -152,6 +286,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpSendAddress("127.0.0.1"), m_udpReceivePort(32001), m_udpReceiveAddress("127.0.0.1"), + m_udpNoIncludeLength(false), + m_udpUseULaw(false), m_srcId(p25::defines::WUID_FNE), m_srcIdOverride(0U), m_overrideSrcIdFromMDC(false), @@ -867,6 +1003,13 @@ bool HostBridge::createNetwork() m_udpSendAddress = networkConf["udpSendAddress"].as(); m_udpReceivePort = (uint16_t)networkConf["udpReceivePort"].as(34001); m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); + m_udpUseULaw = networkConf["udpUseULaw"].as(false); + + if (m_udpUseULaw) + m_udpNoIncludeLength = networkConf["udpNoIncludeLength"].as(false); + + if (m_udpUseULaw && m_udpMetadata) + m_udpMetadata = false; // metadata isn't supported when encoding uLaw m_srcId = (uint32_t)networkConf["sourceId"].as(p25::defines::WUID_FNE); m_overrideSrcIdFromMDC = networkConf["overrideSourceIdFromMDC"].as(false); @@ -929,6 +1072,10 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio Send Port: %u", m_udpSendPort); LogInfo(" UDP Audio Receive Address: %s", m_udpReceiveAddress.c_str()); LogInfo(" UDP Audio Receive Port: %u", m_udpReceivePort); + LogInfo(" UDP Audio Use uLaw Encoding: %u", m_udpUseULaw ? "yes" : "no"); + if (m_udpUseULaw) { + LogInfo(" UDP Audio No Length Header: %u", m_udpNoIncludeLength ? "yes" : "no"); + } } LogInfo(" Source ID: %u", m_srcId); @@ -1004,12 +1151,22 @@ void HostBridge::processUDPAudio() if (m_debug) Utils::dump(1U, "UDP Audio Network Packet", buffer, length); - uint32_t pcmLength = __GET_UINT32(buffer, 0U); + uint32_t pcmLength = 0; + if (m_udpNoIncludeLength) { + pcmLength = length; + } else { + pcmLength = __GET_UINT32(buffer, 0U); + } UInt8Array __pcm = std::make_unique(pcmLength); uint8_t* pcm = __pcm.get(); - ::memcpy(pcm, buffer + 4U, pcmLength); + if (m_udpNoIncludeLength) { + ::memcpy(pcm, buffer, pcmLength); + } + else { + ::memcpy(pcm, buffer + 4U, pcmLength); + } // Utils::dump(1U, "PCM RECV BYTE BUFFER", pcm, pcmLength); @@ -1025,9 +1182,17 @@ void HostBridge::processUDPAudio() int smpIdx = 0; short samples[MBE_SAMPLES_LENGTH]; - for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) { - samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); - smpIdx++; + if (m_udpUseULaw) { + for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx++) { + samples[smpIdx] = uLawToPCM(pcm[pcmIdx]); + smpIdx++; + } + } + else { + for (uint32_t pcmIdx = 0; pcmIdx < pcmLength; pcmIdx += 2) { + samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]); + smpIdx++; + } } m_inputAudio.addData(samples, MBE_SAMPLES_LENGTH); @@ -1307,18 +1472,37 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst if (m_udpAudio) { int pcmIdx = 0; uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = pcmTouLaw(samples[smpIdx]); + } + } + else { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } } 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) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + 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); + } + } + else { + __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + } } else { length = (MBE_SAMPLES_LENGTH * 2U) + 12U; @@ -1854,18 +2038,37 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI if (m_udpAudio) { int pcmIdx = 0; uint8_t pcm[MBE_SAMPLES_LENGTH * 2U]; - for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; + if (m_udpUseULaw) { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[smpIdx] = pcmTouLaw(samples[smpIdx]); + } + } + else { + for (uint32_t smpIdx = 0; smpIdx < MBE_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } } 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) - __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); - ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + 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); + } + } + else { + __SET_UINT32((MBE_SAMPLES_LENGTH * 2U), audioData, 0U); + ::memcpy(audioData + 4U, pcm, MBE_SAMPLES_LENGTH * 2U); + } } else { length = (MBE_SAMPLES_LENGTH * 2U) + 12U; diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index 2533fcfe..675f8cfa 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -91,6 +91,31 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unitID, mdc_u8_t extra0, mdc_u8_t extra1, mdc_u8_t extra2, mdc_u8_t extra3, void* context); +/** + * @brief Helper to convert PCM into G.711 aLaw. + * @param pcm PCM value. + * @return uint8_t aLaw value. + */ +uint8_t pcmToaLaw(short pcm); +/** + * @brief Helper to convert G.711 aLaw into PCM. + * @param alaw aLaw value. + * @return short PCM value. + */ +short aLawToPCM(uint8_t alaw); +/** + * @brief Helper to convert PCM into G.711 uLaw. + * @param pcm PCM value. + * @return uint8_t uLaw value. + */ +uint8_t pcmTouLaw(short pcm); +/** + * @brief Helper to convert G.711 uLaw into PCM. + * @param ulaw uLaw value. + * @return short PCM value. + */ +short uLawToPCM(uint8_t ulaw); + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -134,6 +159,8 @@ private: std::string m_udpSendAddress; uint16_t m_udpReceivePort; std::string m_udpReceiveAddress; + bool m_udpNoIncludeLength; + bool m_udpUseULaw; uint32_t m_srcId; uint32_t m_srcIdOverride;