aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVadim Yanitskiy <vyanitskiy@sysmocom.de>2023-07-19 19:13:58 +0700
committerVadim Yanitskiy <vyanitskiy@sysmocom.de>2023-07-30 16:08:01 +0700
commitd1f8f3429cc77361ade0176f801acd979d3c5ca1 (patch)
tree683ff8a9fa45b439ad40358d5216912d99022dbf
parent97d3bd3e6209b8b272f79d76dcbc23db66998c8f (diff)
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). Note that this patch comments out TCH/F14.4 in bts_supports_cm_data(), so that all channel allocations for this mode would be NACKed. The reason for this is that the rate adaptation functions for TCH/F14.4 are different than the RA1'/RA1 and the RA1/RA2. For more information, see: * 3GPP TS 44.021, section 8 (functions RA1'/RA1) * ITU-T I.460, section 1.1 "Rate adaption of 8, 16 and 32 kbit/s streams" Change-Id: I5e3701ad52d5d428fd02caff037881045f2d0a02 Related: OS#1572
-rw-r--r--.gitignore1
-rw-r--r--configure.ac1
-rw-r--r--include/osmo-bts/Makefile.am1
-rw-r--r--include/osmo-bts/csd_v110.h23
-rw-r--r--src/common/Makefile.am1
-rw-r--r--src/common/bts.c2
-rw-r--r--src/common/csd_v110.c157
-rw-r--r--src/common/l1sap.c66
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/csd/Makefile.am25
-rw-r--r--tests/csd/csd_test.c138
-rw-r--r--tests/csd/csd_test.err66
-rw-r--r--tests/testsuite.at6
13 files changed, 475 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index bf0f844c..34d55b8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,7 @@ src/osmo-bts-oc2g/misc/.dirstamp
tests/atconfig
tests/package.m4
tests/amr/amr_test
+tests/csd/csd_test
tests/agch/agch_test
tests/paging/paging_test
tests/cipher/cipher_test
diff --git a/configure.ac b/configure.ac
index 82074d46..b2b651ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -445,6 +445,7 @@ AC_OUTPUT(
tests/power/Makefile
tests/meas/Makefile
tests/amr/Makefile
+ tests/csd/Makefile
doc/Makefile
doc/examples/Makefile
doc/manuals/Makefile
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
index 0b4058d2..cbd0fc37 100644
--- a/include/osmo-bts/Makefile.am
+++ b/include/osmo-bts/Makefile.am
@@ -24,6 +24,7 @@ noinst_HEADERS = \
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 00000000..f6be0ae2
--- /dev/null
+++ b/include/osmo-bts/csd_v110.h
@@ -0,0 +1,23 @@
+#pragma once
+
+/* RFC4040 "clearmode" RTP payload length */
+#define RFC4040_RTP_PLEN 160
+
+struct gsm_lchan;
+
+struct csd_v110_frame_desc {
+ uint16_t num_blocks;
+ uint16_t num_bits;
+};
+
+struct csd_v110_lchan_desc {
+ struct csd_v110_frame_desc fr;
+ struct csd_v110_frame_desc hr;
+};
+
+extern const struct csd_v110_lchan_desc csd_v110_lchan_desc[256];
+
+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 5cd7909e..d13415d1 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -51,6 +51,7 @@ libbts_a_SOURCES = \
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/bts.c b/src/common/bts.c
index 7257f37b..1f0040c4 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -875,7 +875,7 @@ static bool bts_supports_cm_data(const struct gsm_bts *bts,
switch (bts->variant) {
case BTS_OSMO_TRX:
switch (cm->chan_rate) {
- case RSL_CMOD_CSD_T_14k4:
+ /* TODO: RSL_CMOD_CSD_T_14k4 */
case RSL_CMOD_CSD_T_9k6:
if (cm->chan_rt != RSL_CMOD_CRT_TCH_Bm)
return false; /* invalid */
diff --git a/src/common/csd_v110.c b/src/common/csd_v110.c
new file mode 100644
index 00000000..6800761e
--- /dev/null
+++ b/src/common/csd_v110.c
@@ -0,0 +1,157 @@
+/*
+ * (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>
+
+/* key is enum gsm48_chan_mode, so assuming a value in range 0..255 */
+const struct csd_v110_lchan_desc csd_v110_lchan_desc[256] = {
+#if 0
+ [GSM48_CMODE_DATA_14k5] = {
+ /* TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */
+ .fr = { .num_blocks = 1, .num_bits = 290 },
+ },
+#endif
+ [GSM48_CMODE_DATA_12k0] = {
+ /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */
+ .fr = { .num_blocks = 4, .num_bits = 60 },
+ },
+ [GSM48_CMODE_DATA_6k0] = {
+ /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */
+ .fr = { .num_blocks = 2, .num_bits = 60 },
+ /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */
+ .hr = { .num_blocks = 4, .num_bits = 60 },
+ },
+ [GSM48_CMODE_DATA_3k6] = {
+ /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */
+ .fr = { .num_blocks = 2, .num_bits = 36 },
+ /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */
+ .hr = { .num_blocks = 4, .num_bits = 36 },
+ },
+};
+
+int csd_v110_rtp_encode(const struct gsm_lchan *lchan, uint8_t *rtp,
+ const uint8_t *data, size_t data_len)
+{
+ const struct csd_v110_frame_desc *desc;
+ ubit_t ra_bits[80 * 4];
+
+ OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc));
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].fr;
+ else
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].hr;
+ if (OSMO_UNLIKELY(desc->num_blocks == 0))
+ return -ENOTSUP;
+
+ /* handle empty/incomplete frames gracefully */
+ if (OSMO_UNLIKELY(data_len < (desc->num_blocks * desc->num_bits)))
+ return -ENODATA;
+
+ /* RA1'/RA1: convert to an intermediate data rate */
+ for (unsigned int i = 0; i < desc->num_blocks; i++) {
+ struct osmo_v110_decoded_frame df;
+
+ /* convert modified V.110 frames to normal V.110 frames */
+ if (desc->num_bits == 60)
+ osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60);
+ else /* desc->num_bits == 36 */
+ osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36);
+
+ /* FIXME: E1 .. E3 must be set by out-of-band knowledge! */
+ memset(&df.e_bits[0], 0, 3);
+
+ osmo_v110_encode_frame(&ra_bits[i * 80], 80, &df);
+ }
+
+ /* RA1/RA2: convert from an intermediate rate to 64 kbit/s */
+ if (desc->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)
+{
+ const struct csd_v110_frame_desc *desc;
+ ubit_t ra_bits[80 * 4];
+
+ OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc));
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].fr;
+ else
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].hr;
+ if (OSMO_UNLIKELY(desc->num_blocks == 0))
+ return -ENOTSUP;
+
+ if (OSMO_UNLIKELY(rtp_len != RFC4040_RTP_PLEN))
+ return -EINVAL;
+
+ /* RA1/RA2: convert from 64 kbit/s to an intermediate rate */
+ if (desc->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) & 0x01;
+ }
+ } 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 < desc->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 (desc->num_bits == 60)
+ osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df);
+ else /* desc->num_bits == 36 */
+ osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df);
+ }
+
+ return desc->num_blocks * desc->num_bits;
+}
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 61fb3ee7..13d271ea 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -58,6 +58,7 @@
#include <osmo-bts/pcuif_proto.h>
#include <osmo-bts/cbch.h>
#include <osmo-bts/asci.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)
@@ -1829,9 +1830,27 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
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,
- const uint8_t *rtp_pl, uint16_t rtp_pl_len)
+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) {
lchan_osmux_send_frame(lchan, rtp_pl, rtp_pl_len,
@@ -1859,7 +1878,7 @@ static void send_rtp_rfc5993(struct gsm_lchan *lchan, uint32_t fn,
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
@@ -1884,7 +1903,7 @@ static void tch_ul_bfi_handler(struct gsm_lchan *lchan,
/* 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;
}
}
@@ -1892,7 +1911,7 @@ static void tch_ul_bfi_handler(struct gsm_lchan *lchan,
/* 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;
}
@@ -1948,11 +1967,21 @@ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
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 */
@@ -2247,10 +2276,23 @@ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
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 */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 100c5d48..8d175be8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = paging cipher agch misc handover tx_power power meas ta_control amr
+SUBDIRS = paging cipher agch misc handover tx_power power meas ta_control amr csd
if ENABLE_SYSMOBTS
SUBDIRS += sysmobts
diff --git a/tests/csd/Makefile.am b/tests/csd/Makefile.am
new file mode 100644
index 00000000..2041dd7d
--- /dev/null
+++ b/tests/csd/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(NULL)
+AM_LDFLAGS = -no-install
+LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(NULL)
+
+check_PROGRAMS = csd_test
+EXTRA_DIST = csd_test.err
+
+csd_test_SOURCES = csd_test.c
+csd_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/csd/csd_test.c b/tests/csd/csd_test.c
new file mode 100644
index 00000000..16995b1b
--- /dev/null
+++ b/tests/csd/csd_test.c
@@ -0,0 +1,138 @@
+/*
+ * (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/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 BBUF_MAX 290
+
+struct test_case {
+ const char *name;
+ enum gsm_chan_t lchan_type;
+ enum gsm48_chan_mode tch_mode;
+};
+
+static const struct test_case tests[] = {
+ {
+ .name = "TCH/F14.4",
+ .lchan_type = GSM_LCHAN_TCH_F,
+ .tch_mode = GSM48_CMODE_DATA_14k5,
+ },
+ {
+ .name = "TCH/F9.6",
+ .lchan_type = GSM_LCHAN_TCH_F,
+ .tch_mode = GSM48_CMODE_DATA_12k0,
+ },
+ {
+ .name = "TCH/F4.8",
+ .lchan_type = GSM_LCHAN_TCH_F,
+ .tch_mode = GSM48_CMODE_DATA_6k0,
+ },
+ {
+ .name = "TCH/H4.8",
+ .lchan_type = GSM_LCHAN_TCH_H,
+ .tch_mode = GSM48_CMODE_DATA_6k0,
+ },
+ {
+ .name = "TCH/F2.4",
+ .lchan_type = GSM_LCHAN_TCH_F,
+ .tch_mode = GSM48_CMODE_DATA_3k6,
+ },
+ {
+ .name = "TCH/H2.4",
+ .lchan_type = GSM_LCHAN_TCH_H,
+ .tch_mode = GSM48_CMODE_DATA_3k6,
+ },
+};
+
+static void exec_test_case(const struct test_case *tc)
+{
+ const struct csd_v110_frame_desc *desc;
+ uint8_t rtp[RFC4040_RTP_PLEN] = { 0 };
+ ubit_t data_enc[BBUF_MAX];
+ ubit_t data_dec[BBUF_MAX];
+ int rc;
+
+ /* obtain a V.110 frame description for the given channel type/rate */
+ OSMO_ASSERT(tc->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc));
+ if (tc->lchan_type == GSM_LCHAN_TCH_F)
+ desc = &csd_v110_lchan_desc[tc->tch_mode].fr;
+ else
+ desc = &csd_v110_lchan_desc[tc->tch_mode].hr;
+
+ /* total number of bits carried by a radio interface block */
+ const unsigned int bit_num = desc->num_bits * desc->num_blocks;
+ if (bit_num == 0) {
+ fprintf(stderr, "[i] Skipping '%s' (not implemented)\n", tc->name);
+ return;
+ }
+
+ fprintf(stderr, "[i] Testing '%s' (bitnum=%u)\n", tc->name, bit_num);
+
+ struct gsm_lchan lchan = {
+ .type = tc->lchan_type,
+ .tch_mode = tc->tch_mode,
+ };
+
+ /* populate the data_enc[] buffer with some bits */
+ OSMO_ASSERT(bit_num <= BBUF_MAX);
+ for (unsigned int i = 0; i < bit_num; i++)
+ data_enc[i] = i & 0x01;
+
+ /* encode an RTP frame and print it */
+ rc = csd_v110_rtp_encode(&lchan, &rtp[0], &data_enc[0], bit_num);
+ fprintf(stderr, "[i] csd_v110_rtp_encode() returns %d\n", rc);
+ if (rc != RFC4040_RTP_PLEN)
+ return;
+ /* print the encoded RTP frame (16 bytes per row) */
+ for (unsigned int i = 0; i < sizeof(rtp) / 16; i++)
+ fprintf(stderr, " %s\n", osmo_hexdump(&rtp[i * 16], 16));
+
+ /* decode the encoded RTP frame */
+ rc = csd_v110_rtp_decode(&lchan, &data_dec[0], &rtp[0], sizeof(rtp));
+ fprintf(stderr, "[i] csd_v110_rtp_decode() returns %d\n", rc);
+ if (rc != bit_num)
+ return;
+ /* compare data_dec[] vs data_enc[] */
+ for (unsigned int i = 0; i < bit_num; i++) {
+ if (data_dec[i] == data_enc[i])
+ continue;
+ fprintf(stderr, "[!] Data mismatch @ %03u: D%u vs E%u\n",
+ i, data_dec[i], data_enc[i]);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(tests); i++)
+ exec_test_case(&tests[i]);
+
+ return 0;
+}
diff --git a/tests/csd/csd_test.err b/tests/csd/csd_test.err
new file mode 100644
index 00000000..bc9747cc
--- /dev/null
+++ b/tests/csd/csd_test.err
@@ -0,0 +1,66 @@
+[i] Skipping 'TCH/F14.4' (not implemented)
+[i] Testing 'TCH/F9.6' (bitnum=240)
+[i] csd_v110_rtp_encode() returns 160
+ 3f 3f 3f 3f bf bf bf bf ff 7f 7f 7f bf bf bf bf
+ ff 7f 7f 7f bf 3f 7f 7f bf bf bf bf ff 7f 7f 7f
+ bf bf bf bf ff 7f 7f 7f 3f 3f 3f 3f bf bf bf bf
+ ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f bf 3f 7f 7f
+ bf bf bf bf ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f
+ 3f 3f 3f 3f bf bf bf bf ff 7f 7f 7f bf bf bf bf
+ ff 7f 7f 7f bf 3f 7f 7f bf bf bf bf ff 7f 7f 7f
+ bf bf bf bf ff 7f 7f 7f 3f 3f 3f 3f bf bf bf bf
+ ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f bf 3f 7f 7f
+ bf bf bf bf ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f
+[i] csd_v110_rtp_decode() returns 240
+[i] Testing 'TCH/F4.8' (bitnum=120)
+[i] csd_v110_rtp_encode() returns 160
+ 7f 7f 7f 7f 7f 7f 7f 7f ff 7f ff 7f ff 7f ff 7f
+ ff ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff 7f
+ ff ff 7f ff 7f ff 7f ff ff 7f 7f 7f 7f ff 7f ff
+ ff 7f ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff
+ ff 7f ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff
+ 7f 7f 7f 7f 7f 7f 7f 7f ff 7f ff 7f ff 7f ff 7f
+ ff ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff 7f
+ ff ff 7f ff 7f ff 7f ff ff 7f 7f 7f 7f ff 7f ff
+ ff 7f ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff
+ ff 7f ff 7f ff 7f ff 7f ff ff 7f ff 7f ff 7f ff
+[i] csd_v110_rtp_decode() returns 120
+[i] Testing 'TCH/H4.8' (bitnum=240)
+[i] csd_v110_rtp_encode() returns 160
+ 3f 3f 3f 3f bf bf bf bf ff 7f 7f 7f bf bf bf bf
+ ff 7f 7f 7f bf 3f 7f 7f bf bf bf bf ff 7f 7f 7f
+ bf bf bf bf ff 7f 7f 7f 3f 3f 3f 3f bf bf bf bf
+ ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f bf 3f 7f 7f
+ bf bf bf bf ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f
+ 3f 3f 3f 3f bf bf bf bf ff 7f 7f 7f bf bf bf bf
+ ff 7f 7f 7f bf 3f 7f 7f bf bf bf bf ff 7f 7f 7f
+ bf bf bf bf ff 7f 7f 7f 3f 3f 3f 3f bf bf bf bf
+ ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f bf 3f 7f 7f
+ bf bf bf bf ff 7f 7f 7f bf bf bf bf ff 7f 7f 7f
+[i] csd_v110_rtp_decode() returns 240
+[i] Testing 'TCH/F2.4' (bitnum=72)
+[i] csd_v110_rtp_encode() returns 160
+ 7f 7f 7f 7f 7f 7f 7f 7f ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f 7f 7f ff 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+ 7f 7f 7f 7f 7f 7f 7f 7f ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f 7f 7f ff 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+ ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff ff 7f 7f ff
+[i] csd_v110_rtp_decode() returns 72
+[i] Testing 'TCH/H2.4' (bitnum=144)
+[i] csd_v110_rtp_encode() returns 160
+ 3f 3f 3f 3f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f
+ bf 7f bf 7f bf 3f 7f 7f bf 7f bf 7f bf 7f bf 7f
+ bf 7f bf 7f bf 7f bf 7f 3f 3f 3f 3f bf 7f bf 7f
+ bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 3f 7f 7f
+ bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f
+ 3f 3f 3f 3f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f
+ bf 7f bf 7f bf 3f 7f 7f bf 7f bf 7f bf 7f bf 7f
+ bf 7f bf 7f bf 7f bf 7f 3f 3f 3f 3f bf 7f bf 7f
+ bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 3f 7f 7f
+ bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f bf 7f
+[i] csd_v110_rtp_decode() returns 144
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 7e6ec4b8..a7012b18 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -70,3 +70,9 @@ cat $abs_srcdir/amr/amr_test.ok > expout
cat $abs_srcdir/amr/amr_test.err > experr
AT_CHECK([$abs_top_builddir/tests/amr/amr_test], [], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([csd])
+AT_KEYWORDS([csd])
+cat $abs_srcdir/csd/csd_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/csd/csd_test], [], [ignore], [experr])
+AT_CLEANUP