falconia has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmocore/+/37558?usp=email )
Change subject: codec: add osmo_hr_sid_classify() ...................................................................... codec: add osmo_hr_sid_classify() In order to support DTX, each frame coming out of the channel decoder for TCH/FS, TCH/HS and TCH/EFS needs to be classified as valid SID, invalid SID or non-SID speech as specified in GSM 06.31, 06.41 and 06.81, respectively. However, the case of TCH/HS (GSM 06.41) is more difficult than FR & EFR: ETSI provided an example implementation instead of a stipulation, and because they failed to document the BCI error flag they relied upon, ETSI's reference implementation of HR SID classification defied understanding for a long time. But now this mystery has been cracked: https://osmocom.org/projects/retro-gsm/wiki/HRv1_error_flags Add a function to libosmocodec that implements the same logic as ETSI's swSidDetection(), including support for BCI flag. This function is intended to be used in OsmoBTS (it can post-process the output of sysmoBTS PHY) when TW-TS-002 output is requested, and it can also be used in implementations of SDR-based GSM MS when feeding TCH/HS Rx to a proper speech decoder and Rx DTX handler for HRv1 codec. Related: OS#6036 Change-Id: I5f4eb65379646125b966cf182775b6e9348900bd --- M include/osmocom/codec/codec.h M src/codec/Makefile.am A src/codec/hr_sid_class.c 3 files changed, 211 insertions(+), 0 deletions(-) git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/58/37558/1 diff --git a/include/osmocom/codec/codec.h b/include/osmocom/codec/codec.h index c5981f8..7a23e7f 100644 --- a/include/osmocom/codec/codec.h +++ b/include/osmocom/codec/codec.h @@ -100,6 +100,9 @@ enum osmo_gsm631_sid_class osmo_fr_sid_classify(const uint8_t *rtp_payload); enum osmo_gsm631_sid_class osmo_efr_sid_classify(const uint8_t *rtp_payload); +enum osmo_gsm631_sid_class osmo_hr_sid_classify(const uint8_t *rtp_payload, + bool bci_flag, + bool *bfi_from_bci); /*! Check if given FR codec frame is any kind of SID, valid or invalid * \param[in] rtp_payload Buffer with RTP payload diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am index bb01b9d..e9d58c1 100644 --- a/src/codec/Makefile.am +++ b/src/codec/Makefile.am @@ -18,6 +18,7 @@ gsm620.c \ gsm660.c \ gsm690.c \ + hr_sid_class.c \ ecu.c \ ecu_fr.c \ ecu_fr_old.c \ diff --git a/src/codec/hr_sid_class.c b/src/codec/hr_sid_class.c new file mode 100644 index 0000000..3454062 --- /dev/null +++ b/src/codec/hr_sid_class.c @@ -0,0 +1,178 @@ +/* + * This module implements osmo_hr_sid_classify() function - an independent + * reimplementation of the logic that was recommended (but not stipulated + * as normative) by ETSI for classifying received TCH/HS frames as + * valid SID, invalid SID or non-SID speech. + * + * Author: Mychaela N. Falconia <fal...@freecalypso.org>, 2024 - however, + * Mother Mychaela's contributions are NOT subject to copyright. + * No rights reserved, all rights relinquished. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <osmocom/codec/codec.h> + +/* + * This helper function takes a byte, counts how many bits are set to 1, + * and returns that number. + */ +static unsigned ones_in_byte(uint8_t byte) +{ + unsigned count = 0; + + for (; byte; byte >>= 1) { + if (byte & 1) + count++; + } + return count; +} + +/* + * This helper function takes two byte arrays of equal length (data and mask), + * applies the mask to the data, then counts how many bits are set to 1 + * under the mask, and returns that number. + */ +static unsigned count_ones_under_mask(const uint8_t *data, const uint8_t *mask, + unsigned nbytes) +{ + unsigned n, accum; + uint8_t and; + + accum = 0; + for (n = 0; n < nbytes; n++) { + and = *data++ & *mask++; + accum += ones_in_byte(and); + } + return accum; +} + +/* + * When a GSM-HR SID frame has been decoded correctly in voiced mode, + * the 79 bits of the SID field will be the last bits in the frame. + * In the packed format of TS 101 318, the bits of interest will be + * in the last 10 bytes. The following array is the mask to be applied. + */ +static const uint8_t sid_field_last10_mask[10] = + {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +/* + * When a GSM-HR SID frame has been incorrectly decoded in unvoiced mode + * (both mode bits got flipped to 0 by channel errors), the 79 bits + * of the SID field will be badly misordered all over the frame. + * However, they can still be counted for the purpose of SID detection. + * The following array is the mask to be applied to the whole frame + * (14 bytes) to locate the misordered SID field. + */ +static const uint8_t sid_field_misordered[14] = + {0x08, 0xEF, 0x1F, 0x3F, 0xF3, 0xFC, 0xA4, + 0xFF, 0xFA, 0x3F, 0xFF, 0x47, 0xFF, 0xEC}; + +/* + * In the channel coding scheme on TCH/HS, the HR codec frame of 112 bits + * is divided into 95 class 1 bits and 17 class 2 bits. In the packed + * format of TS 101 318, all 17 class 2 bits will always be in the last + * 4 bytes; however, the specific bits will be different depending on + * whether the frame was decoded in voiced or unvoiced mode. + * The following two arrays are masks to be applied to the last 4 bytes. + */ +static const uint8_t class2_mask_voiced[4] = {0x7F, 0x80, 0x3F, 0xE0}; +static const uint8_t class2_mask_unvoiced[4] = {0x07, 0x07, 0xFF, 0xE0}; + +/* + * osmo_hr_sid_classify() - this function is an independent reimplementation + * of the logic that was recommended (but not stipulated as normative) by ETSI + * for classifying received TCH/HS frames as valid SID, invalid SID or non-SID + * speech. ETSI's original version is swSidDetection() function in reid.c + * in GSM 06.06 source; the present version implements exactly the same + * logic (same inputs will produce same output), but differs in the following + * ways: + * + * - The frame of channel-decoded 112 payload bits was passed in the form + * of an array of 18 codec parameters in ETSI's version; the present version + * uses the packed format of TS 101 318 instead. + * + * - The C code implementation was written anew by Mother Mychaela; no code + * in this file has been copied directly from GSM 06.06 code drop. + * + * This function is meant to be used only in the same network element + * that performs GSM 05.03 channel decoding (OsmoBTS, new implementations + * of GSM MS), _*NOT*_ in programs or network elements that receive + * HRv1 codec frames from other elements via RTP or Abis-E1 etc! + * + * The BCI logic recommended by ETSI and implemented in practice by at least + * one vendor whose implementation has been reverse-engineered (TI Calypso) + * is included in this function. To understand this logic, please refer + * to this wiki description: + * + * https://osmocom.org/projects/retro-gsm/wiki/HRv1_error_flags + */ +enum osmo_gsm631_sid_class osmo_hr_sid_classify(const uint8_t *rtp_payload, + bool bci_flag, + bool *bfi_from_bci) +{ + uint8_t mode_bits = rtp_payload[4] & 0x30; + unsigned sid_field_ones, class1_ones, class2_ones; + unsigned sid_field_zeros, class1_zeros; + unsigned invalid_sid_threshold; + enum osmo_gsm631_sid_class sidc; + + if (mode_bits != 0) { /* decoded as voiced */ + sid_field_ones = count_ones_under_mask(rtp_payload + 4, + sid_field_last10_mask, 10); + class2_ones = count_ones_under_mask(rtp_payload + 10, + class2_mask_voiced, 4); + } else { /* decoded as unvoiced */ + sid_field_ones = count_ones_under_mask(rtp_payload, + sid_field_misordered, 14); + class2_ones = count_ones_under_mask(rtp_payload + 10, + class2_mask_unvoiced, 4); + } + class1_ones = sid_field_ones - class2_ones; + sid_field_zeros = 79 - sid_field_ones; + class1_zeros = 62 - class1_ones; + + /* frame classification logic recommended by ETSI */ + if (bci_flag) + invalid_sid_threshold = 16; + else + invalid_sid_threshold = 11; + + if (class1_zeros < 3) + sidc = OSMO_GSM631_SID_CLASS_VALID; + else if (sid_field_zeros < invalid_sid_threshold) + sidc = OSMO_GSM631_SID_CLASS_INVALID; + else + sidc = OSMO_GSM631_SID_CLASS_SPEECH; + + /* If the mode bits got corrupted and the frame was channel-decoded + * as unvoiced, it cannot be taken as valid SID because the bits + * that hold CN parameters have been misordered. Therefore, + * we have to turn it into invalid SID classification. + */ + if (mode_bits == 0 && sidc == OSMO_GSM631_SID_CLASS_VALID) + sidc = OSMO_GSM631_SID_CLASS_INVALID; + + /* ETSI's peculiar logic that "upgrades" BCI error flag to BFI + * (from lowest to highest error severity) when the decoded bit + * pattern matches a set criterion. We leave it up to applications + * whether they choose to apply this logic or not. If this logic + * is not wanted, pass NULL pointer as the last argument. + */ + if (bci_flag && bfi_from_bci && + sid_field_zeros >= 16 && sid_field_zeros <= 25) + *bfi_from_bci = true; + + return sidc; +} -- To view, visit https://gerrit.osmocom.org/c/libosmocore/+/37558?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: libosmocore Gerrit-Branch: master Gerrit-Change-Id: I5f4eb65379646125b966cf182775b6e9348900bd Gerrit-Change-Number: 37558 Gerrit-PatchSet: 1 Gerrit-Owner: falconia <fal...@freecalypso.org> Gerrit-MessageType: newchange