fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-bts/+/33832 )
Change subject: l1sap: proper rate adaptation for CSD (RFC4040 'clearmode') ......................................................................
l1sap: proper rate adaptation for CSD (RFC4040 'clearmode')
Since 95407f3f osmo-bts-trx supports scheduling all CSD specific channel rates, however the rate adaptation was missing.
On the radio interface we deal with CSD-modified V.110 frames, which need to be converted to normal 80-bit V.110 frames (RA1'/RA1), which in turn need to be batched and sent in RFC4040 "clearmode" 160 octet RTP payloads (RA1/RA2 as per I.460).
Change-Id: I5e3701ad52d5d428fd02caff037881045f2d0a02 Related: OS#1572 --- M include/osmo-bts/Makefile.am A include/osmo-bts/csd_v110.h M src/common/Makefile.am A src/common/csd_v110.c M src/common/l1sap.c 5 files changed, 250 insertions(+), 11 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-bts refs/changes/32/33832/1
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am index 1a07287..3e54035 100644 --- a/include/osmo-bts/Makefile.am +++ b/include/osmo-bts/Makefile.am @@ -23,6 +23,7 @@ tx_power.h \ control_if.h \ cbch.h \ + csd_v110.h \ l1sap.h \ lchan.h \ power_control.h \ diff --git a/include/osmo-bts/csd_v110.h b/include/osmo-bts/csd_v110.h new file mode 100644 index 0000000..b80f95d --- /dev/null +++ b/include/osmo-bts/csd_v110.h @@ -0,0 +1,11 @@ +#pragma once + +/* RFC4040 "clearmode" RTP payload length */ +#define RFC4040_RTP_PLEN 160 + +struct gsm_lchan; + +int csd_v110_rtp_encode(const struct gsm_lchan *lchan, uint8_t *rtp, + const uint8_t *data, size_t data_len); +int csd_v110_rtp_decode(const struct gsm_lchan *lchan, uint8_t *data, + const uint8_t *rtp, size_t rtp_len); diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 270139c..beae875 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -50,6 +50,7 @@ bts_ctrl_commands.c \ bts_ctrl_lookup.c \ bts_shutdown_fsm.c \ + csd_v110.c \ l1sap.c \ cbch.c \ power_control.c \ diff --git a/src/common/csd_v110.c b/src/common/csd_v110.c new file mode 100644 index 0000000..f800f4c --- /dev/null +++ b/src/common/csd_v110.c @@ -0,0 +1,166 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * Author: Vadim Yanitskiy vyanitskiy@sysmocom.de + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm44021.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/isdn/v110.h> + +#include <osmo-bts/csd_v110.h> +#include <osmo-bts/lchan.h> + +#define DATA_RATE(type, mode) ((type << 8) | mode) + +static int lchan_data_rate(const struct gsm_lchan *lchan, + size_t *num_blocks, + size_t *num_bits) +{ + switch (DATA_RATE(lchan->type, lchan->tch_mode)) { + /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_F, GSM48_CMODE_DATA_12k0): + *num_blocks = 4; + *num_bits = 60; + break; + /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_F, GSM48_CMODE_DATA_6k0): + *num_blocks = 2; + *num_bits = 60; + break; + /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_H, GSM48_CMODE_DATA_6k0): + *num_blocks = 4; + *num_bits = 60; + break; + /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_F, GSM48_CMODE_DATA_3k6): + *num_blocks = 2; + *num_bits = 36; + break; + /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_H, GSM48_CMODE_DATA_3k6): + *num_blocks = 4; + *num_bits = 36; + break; + /* TODO: TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */ + case DATA_RATE(GSM_LCHAN_TCH_F, GSM48_CMODE_DATA_14k5): + default: + return -ENOTSUP; + } + + return 0; +} + +int csd_v110_rtp_encode(const struct gsm_lchan *lchan, uint8_t *rtp, + const uint8_t *data, size_t data_len) +{ + size_t num_blocks, num_bits; + ubit_t ra_bits[80 * 4]; + int rc; + + rc = lchan_data_rate(lchan, &num_blocks, &num_bits); + if (rc) + return rc; + + /* handle empty/incomplete frames gracefully */ + if (OSMO_UNLIKELY(data_len < (num_blocks * num_bits))) { + /* reuse ra_bits[]: fake a frame with all bits set to zero */ + memset(&ra_bits[0], 0x00, sizeof(ra_bits)); + data_len = num_blocks * num_bits; + data = &ra_bits[0]; + } + + /* RA1'/RA1: convert to an intermediate data rate */ + for (unsigned int i = 0; i < num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + /* convert modified V.110 frames to normal V.110 frames */ + if (num_bits == 60) + osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60); + else /* num_bits == 36 */ + osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36); + osmo_v110_encode_frame(&ra_bits[i * 80], 80, &df); + } + + /* RA1/RA2: convert from an intermediate rate to 64 kbit/s */ + if (num_blocks == 4) { + /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */ + for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) { + rtp[i] = (0xff >> 2); + rtp[i] |= ra_bits[j++] << 7; + rtp[i] |= ra_bits[j++] << 6; + } + } else { + /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */ + for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++) { + rtp[i] = (0xff >> 1); + rtp[i] |= ra_bits[i] << 7; + } + } + + return RFC4040_RTP_PLEN; +} + +int csd_v110_rtp_decode(const struct gsm_lchan *lchan, uint8_t *data, + const uint8_t *rtp, size_t rtp_len) +{ + size_t num_blocks, num_bits; + ubit_t ra_bits[80 * 4]; + int rc; + + rc = lchan_data_rate(lchan, &num_blocks, &num_bits); + if (rc) + return rc; + + if (OSMO_UNLIKELY(rtp_len != RFC4040_RTP_PLEN)) + return -EINVAL; + + /* RA1/RA2: convert from 64 kbit/s to an intermediate rate */ + if (num_blocks == 4) { + /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */ + for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) { + ra_bits[j++] = rtp[i] >> 7; + ra_bits[j++] = rtp[i] >> 6; + } + } else { + /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */ + for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++) + ra_bits[i] = rtp[i] >> 7; + } + + /* RA1'/RA1: convert to an intermediate data rate */ + for (unsigned int i = 0; i < num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + /* convert modified V.110 frames to normal V.110 frames */ + osmo_v110_decode_frame(&df, &ra_bits[i * 80], 80); + if (num_bits == 60) + osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df); + else /* num_bits == 36 */ + osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df); + } + + return num_blocks * num_bits; +} diff --git a/src/common/l1sap.c b/src/common/l1sap.c index 7f61f2c..14074f0 100644 --- a/src/common/l1sap.c +++ b/src/common/l1sap.c @@ -57,6 +57,7 @@ #include <osmo-bts/rtp_input_preen.h> #include <osmo-bts/pcuif_proto.h> #include <osmo-bts/cbch.h> +#include <osmo-bts/csd_v110.h>
/* determine the CCCH block number based on the frame number */ unsigned int l1sap_fn2ccch_block(uint32_t fn) @@ -1780,8 +1781,26 @@ return 1; }
+static void send_ul_rtp_packet_data(struct gsm_lchan *lchan, uint32_t fn, + const uint8_t *data, uint16_t data_len) +{ + uint8_t rtp_pl[RFC4040_RTP_PLEN]; + int rc; + + rc = csd_v110_rtp_encode(lchan, &rtp_pl[0], data, data_len); + if (rc < 0) + return; + + osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, + &rtp_pl[0], sizeof(rtp_pl), + fn_ms_adj(fn, lchan), + lchan->rtp_tx_marker); + /* Only clear the marker bit once we have sent a RTP packet with it */ + lchan->rtp_tx_marker = false; +} + /* a helper function for the logic in l1sap_tch_ind() */ -static void send_ul_rtp_packet(struct gsm_lchan *lchan, uint32_t fn, +static void send_ul_rtp_packet_speech(struct gsm_lchan *lchan, uint32_t fn, const uint8_t *rtp_pl, uint16_t rtp_pl_len) { if (lchan->abis_ip.osmux.use) { @@ -1810,7 +1829,7 @@ else toc = 0x00; msgb_push_u8(msg, toc); - send_ul_rtp_packet(lchan, fn, msg->data, msg->len); + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); }
/* A helper function for l1sap_tch_ind(): handling BFI @@ -1835,7 +1854,7 @@ /* did it actually give us some output? */ if (rc > 0) { /* yes, send it out in RTP */ - send_ul_rtp_packet(lchan, fn, ecu_out, rc); + send_ul_rtp_packet_speech(lchan, fn, ecu_out, rc); return; } } @@ -1843,7 +1862,7 @@ /* Are we in rtp continuous-streaming special mode? If so, send out * a BFI packet as zero-length RTP payload. */ if (lchan->ts->trx->bts->rtp_nogaps_mode) { - send_ul_rtp_packet(lchan, fn, NULL, 0); + send_ul_rtp_packet_speech(lchan, fn, NULL, 0); return; }
@@ -1900,11 +1919,21 @@ if (lchan->ecu_state) osmo_ecu_frame_in(lchan->ecu_state, false, msg->data, msg->len); /* hand msg to RTP code for transmission */ - if (bts->emit_hr_rfc5993 && lchan->type == GSM_LCHAN_TCH_H && - lchan->tch_mode == GSM48_CMODE_SPEECH_V1) - send_rtp_rfc5993(lchan, fn, msg); - else - send_ul_rtp_packet(lchan, fn, msg->data, msg->len); + switch (lchan->rsl_cmode) { + case RSL_CMOD_SPD_SPEECH: + if (bts->emit_hr_rfc5993 && lchan->type == GSM_LCHAN_TCH_H && + lchan->tch_mode == GSM48_CMODE_SPEECH_V1) + send_rtp_rfc5993(lchan, fn, msg); + else + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); + break; + case RSL_CMOD_SPD_DATA: + send_ul_rtp_packet_data(lchan, fn, msg->data, msg->len); + break; + case RSL_CMOD_SPD_SIGN: + default: /* shall not happen */ + OSMO_ASSERT(0); + } /* if loopback is enabled, also queue received RTP data */ if (lchan->loopback) { /* add new frame to queue, make sure the queue doesn't get too long */ @@ -2191,10 +2220,23 @@ OSMO_ASSERT(0); }
- msg = l1sap_msgb_alloc(rtp_pl_len); + msg = l1sap_msgb_alloc(512); if (!msg) return; - memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); + + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) { + int rc = csd_v110_rtp_decode(lchan, msg->tail, + rtp_pl, rtp_pl_len); + if (rc > 0) { + msgb_put(msg, rc); + } else { + msgb_free(msg); + return; + } + } else { + memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); + } + msgb_pull(msg, sizeof(struct osmo_phsap_prim));
/* Store RTP header Marker bit in control buffer */