fixeria has uploaded this change for review.
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 */
To view, visit change 33832. To unsubscribe, or for help writing mail filters, visit settings.