pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/29264 )
Change subject: trxcon: Initial support for forwarding AMR ......................................................................
trxcon: Initial support for forwarding AMR
This allows TTCN3 L1CTL module (used in BTS_Tests) to transmit and receive AMR payloads towards osmo-bts-trx.
Related: SYS#5987 Change-Id: Ia20bc96e39726a919a556c83c8be48cb31af7331 --- M include/l1ctl_proto.h M src/host/layer23/src/common/l1ctl.c M src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h M src/host/trxcon/include/osmocom/bb/l1sched/logging.h M src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h M src/host/trxcon/src/l1ctl.c M src/host/trxcon/src/sched_lchan_tchf.c M src/host/trxcon/src/trxcon_fsm.c M src/host/virt_phy/src/l1ctl_sap.c M src/target/firmware/layer1/l23_api.c 10 files changed, 170 insertions(+), 55 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/64/29264/1
diff --git a/include/l1ctl_proto.h b/include/l1ctl_proto.h index 3720f82..a15f905 100644 --- a/include/l1ctl_proto.h +++ b/include/l1ctl_proto.h @@ -162,7 +162,10 @@ uint8_t tch_mode; /* enum tch_mode */ uint8_t audio_mode; uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */ - uint8_t padding[1]; + struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */ + uint8_t start_codec; + uint8_t codecs_bitmask; + } amr; } __attribute__((packed));
/* data on the CCCH was found. This is following the header */ @@ -241,7 +244,10 @@ #define AUDIO_RX_TRAFFIC_IND (1<<3) uint8_t audio_mode; uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */ - uint8_t padding[1]; + struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */ + uint8_t start_codec; + uint8_t codecs_bitmask; + } amr; } __attribute__((packed));
/* the l1_info_ul header is in front */ diff --git a/src/host/layer23/src/common/l1ctl.c b/src/host/layer23/src/common/l1ctl.c index e33074a..d520403 100644 --- a/src/host/layer23/src/common/l1ctl.c +++ b/src/host/layer23/src/common/l1ctl.c @@ -472,6 +472,7 @@ req->tch_mode = tch_mode; req->audio_mode = audio_mode; req->tch_loop_mode = tch_loop_mode; + /* TODO: Set AMR codec in req if req->tch_mode==GSM48_CMODE_SPEECH_AMR */
return osmo_send_l1(ms, msg); } diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h index 04303f0..9686562 100644 --- a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h +++ b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h @@ -297,6 +297,8 @@ uint8_t ber_num; /*! Sum of bit error rates */ float ber_sum; + /* last received dtx frame type */ + uint8_t last_dtx; } amr;
/*! A5/X encryption state */ diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h index fb1c018..d2b80e3 100644 --- a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h +++ b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h @@ -17,12 +17,13 @@ LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
+#define LOGP_LCHAN_NAME_FMT "TS%u-%s" #define LOGP_LCHAN_NAME_ARGS(lchan) \ (lchan)->ts->index, l1sched_lchan_desc[(lchan)->type].name
/* Messages using l1sched_lchan_state as the context */ #define LOGP_LCHAN_CAT(lchan, cat, level, fmt, args...) \ - LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, "TS%u-%s " fmt, \ + LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, LOGP_LCHAN_NAME_FMT " " fmt, \ LOGP_LCHAN_NAME_ARGS(lchan), ## args)
/* Common messages using l1sched_lchan_state as the context */ diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h index 2a6f7d5..6344733 100644 --- a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h @@ -58,6 +58,10 @@ /* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */ struct trxcon_param_set_ccch_tch_mode_req { uint8_t mode; + struct { + uint8_t start_codec; + uint8_t codecs_bitmask; + } amr; bool applied; };
diff --git a/src/host/trxcon/src/l1ctl.c b/src/host/trxcon/src/l1ctl.c index 4d6bed2..033b93d 100644 --- a/src/host/trxcon/src/l1ctl.c +++ b/src/host/trxcon/src/l1ctl.c @@ -702,6 +702,10 @@ struct trxcon_param_set_ccch_tch_mode_req req = { .mode = mode_req->tch_mode, }; + if (mode_req->tch_mode == GSM48_CMODE_SPEECH_AMR) { + req.amr.start_codec = mode_req->amr.start_codec; + req.amr.codecs_bitmask = mode_req->amr.codecs_bitmask; + }
rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_TCH_MODE_REQ, &req); if (rc != 0 || !req.applied) { diff --git a/src/host/trxcon/src/sched_lchan_tchf.c b/src/host/trxcon/src/sched_lchan_tchf.c index 96acf0e..0474df7 100644 --- a/src/host/trxcon/src/sched_lchan_tchf.c +++ b/src/host/trxcon/src/sched_lchan_tchf.c @@ -30,11 +30,63 @@ #include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_amr_dtx.h> #include <osmocom/codec/codec.h>
#include <osmocom/bb/l1sched/l1sched.h> #include <osmocom/bb/l1sched/logging.h>
+/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Uplink TCH/F. + * + * +---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | Burst 'a' received first + * +---+---+---+---+---+---+---+---+ + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h') + * + * TDMA frame number of burst 'h' is always used as the table index. */ +static const uint8_t sched_tchf_ul_amr_cmi_map[26] = { + [7] = 1, /* TCH/F: a=0 / h=7 */ + [16] = 1, /* TCH/F: a=8 / h=16 */ + [24] = 1, /* TCH/F: a=17 / h=24 */ +}; + +/*! determine whether an uplink AMR block is CMI according to 3GPP TS 45.009. + * \param[in] fn_begin frame number of the beginning of the block. + * \returns true in case of CMI; false otherwise. */ +static inline bool ul_amr_fn_is_cmi(uint32_t fn_begin) +{ + switch (fn_begin % 26) { + /*! See also: 3GPP TS 45.009, section 3.2.1.3 Transmitter/Receiver Synchronisation */ + /* valid for AHS subslot 0 and AFS: */ + case 0: + case 8: + case 17: + /* valid for AHS subslot 1: */ + case 1: + case 9: + case 18: + return true; + break; + /* Complementary values for sanity check */ + /* valid for AHS subslot 0 and AFS: */ + case 4: + case 13: + case 21: + /* valid for AHS subslot 1: */ + case 5: + case 14: + case 22: + return false; + break; + default: + LOGP(l1sched_log_cat_data, LOGL_DEBUG, + "uplink frame number fn_begin=%u does not mark the beginning of a voice block!\n", fn_begin); + OSMO_ASSERT(false); + return false; + break; + } +} + int rx_tchf_fn(struct l1sched_lchan_state *lchan, uint32_t fn, uint8_t bid, const sbit_t *bits, const struct l1sched_meas_set *meas) @@ -43,6 +95,9 @@ sbit_t *buffer, *offset; uint8_t l2[128], *mask; size_t l2_len; + int amr = 0; + uint8_t ft; + bool amr_is_cmr;
/* Set up pointers */ mask = &lchan->rx_burst_mask; @@ -97,12 +152,36 @@ 1, 1, &n_errors, &n_bits_total); break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... + /* the first FN 0,8,17 defines that CMI is included in frame, + * the first FN 4,13,21 defines that CMR is included in frame. + * NOTE: A frame ends 7 FN after start. */ - LOGP_LCHAND(lchan, LOGL_ERROR, "AMR isn't supported yet\n"); - return -ENOTSUP; + amr_is_cmr = !sched_tchf_ul_amr_cmi_map[fn % 26]; + + /* we store tch_data + 2 header bytes, the amr variable set to + * 2 will allow us to skip the first 2 bytes in case we did + * receive an FACCH frame instead of a voice frame (we do not + * know this before we actually decode the frame) */ + amr = 2; + rc = gsm0503_tch_afs_decode_dtx(l2 + amr, buffer, + amr_is_cmr, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.ul_ft, + &lchan->amr.ul_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx); + + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + if (lchan->amr.last_dtx == AMR_OTHER) { + ft = lchan->amr.codec[lchan->amr.ul_ft]; + } else { + /* SID frames will always get Frame Type Index 8 (AMR_SID) */ + ft = AMR_SID; + } + rc = osmo_amr_rtp_enc(l2, + lchan->amr.codec[lchan->amr.ul_cmr], + ft, AMR_GOOD); + LOGP_LCHAND(lchan, LOGL_ERROR, "osmo_amr_rtp_enc() returned rc=%d\n", rc); + } + + break; default: LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); return -EINVAL; @@ -186,55 +265,51 @@ if (br->bid > 0) return 0;
- /* Check the current TCH mode */ - switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: - case GSM48_CMODE_SPEECH_V1: /* FR */ - l2_len = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - l2_len = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /** - * TODO: AMR requires a dedicated loop, - * which will be implemented later... - */ - LOGP_LCHAND(lchan, LOGL_ERROR, - "AMR isn't supported yet, dropping frame...\n"); - - /* Forget this primitive */ - l1sched_prim_drop(lchan); - - return -ENOTSUP; - default: - LOGP_LCHAND(lchan, LOGL_ERROR, - "Invalid TCH mode: %u, dropping frame...\n", - lchan->tch_mode); - - /* Forget this primitive */ - l1sched_prim_drop(lchan); - - return -EINVAL; - } - - /* Determine and check the payload length */ - if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) { - l2_len = GSM_MACBLOCK_LEN; /* FACCH */ - } else if (lchan->prim->payload_len != l2_len) { - LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu " - "(expected %zu for TCH or %u for FACCH), so dropping...\n", - lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN); - - l1sched_prim_drop(lchan); - return -EINVAL; - } - /* Shift buffer by 4 bursts back for interleaving */ memcpy(buffer, buffer + 464, 464);
- /* Encode payload */ - rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1); + /* populate the buffer with bursts */ + if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) { /* FACCH */ + /* Encode payload */ + rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, GSM_MACBLOCK_LEN, 1); + } else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + /* the first FN 0,8,17 defines that CMI is included in frame, + * the first FN 4,13,21 defines that CMR is included in frame. + */ + rc = gsm0503_tch_afs_encode(buffer, lchan->prim->payload + 2, + lchan->prim->payload_len - 2, !ul_amr_fn_is_cmi(br->fn), + lchan->amr.codec, lchan->amr.codecs, + lchan->amr.ul_ft, + lchan->amr.ul_cmr); + } else { + /* Determine and check the payload length */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* FR */ + l2_len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + l2_len = GSM_EFR_BYTES; + break; + default: + LOGP_LCHAND(lchan, LOGL_ERROR, + "Invalid TCH mode: %u, dropping frame...\n", + lchan->tch_mode); + /* Forget this primitive */ + l1sched_prim_drop(lchan); + return -EINVAL; + } + if (lchan->prim->payload_len != l2_len) { + LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu " + "(expected %zu for TCH or %u for FACCH), so dropping...\n", + lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN); + + l1sched_prim_drop(lchan); + return -EINVAL; + } + rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1); + } + if (rc) { LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n", lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, diff --git a/src/host/trxcon/src/trxcon_fsm.c b/src/host/trxcon/src/trxcon_fsm.c index aa7193a..1713539 100644 --- a/src/host/trxcon/src/trxcon_fsm.c +++ b/src/host/trxcon/src/trxcon_fsm.c @@ -36,6 +36,7 @@ #include <osmocom/bb/trxcon/l1ctl_server.h> #include <osmocom/bb/trxcon/l1ctl_proto.h> #include <osmocom/bb/l1sched/l1sched.h> +#include <osmocom/bb/l1sched/logging.h>
#define S(x) (1 << (x))
@@ -340,6 +341,26 @@ if (!lchan->active) continue; lchan->tch_mode = req->mode; + if (req->mode == GSM48_CMODE_SPEECH_AMR) { + int pos, n, acum; + uint8_t bmask = req->amr.codecs_bitmask; + n = 0, acum = 0; + while ((pos = ffs(bmask)) != 0) { + acum += pos; + LOGPFSML(fi, LOGL_DEBUG, + LOGP_LCHAN_NAME_FMT " AMR codec[%u] = %u\n", + LOGP_LCHAN_NAME_ARGS(lchan), n, acum - 1); + lchan->amr.codec[n++] = acum - 1; + bmask >>= pos; + } + if (n == 0) { + LOGPFSML(fi, LOGL_ERROR, + LOGP_LCHAN_NAME_FMT " Empty AMR codec mode bitmask!\n", + LOGP_LCHAN_NAME_ARGS(lchan)); + continue; + } + lchan->amr.codecs = n; + } req->applied = true; } } diff --git a/src/host/virt_phy/src/l1ctl_sap.c b/src/host/virt_phy/src/l1ctl_sap.c index bab6259..a0e8c0c 100644 --- a/src/host/virt_phy/src/l1ctl_sap.c +++ b/src/host/virt_phy/src/l1ctl_sap.c @@ -486,6 +486,7 @@
l1_model_tch_mode_set(ms, tch_mode_req->tch_mode); ms->state.audio_mode = tch_mode_req->audio_mode; + /* TODO: Handle AMR codecs from tch_mode_req if tch_mode_req->tch_mode==GSM48_CMODE_SPEECH_AMR */
LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n", tch_mode_req->tch_mode, tch_mode_req->audio_mode); diff --git a/src/target/firmware/layer1/l23_api.c b/src/target/firmware/layer1/l23_api.c index a133c62..599272f 100644 --- a/src/target/firmware/layer1/l23_api.c +++ b/src/target/firmware/layer1/l23_api.c @@ -553,6 +553,7 @@
l1s.tch_sync = 1; /* Needed for audio to work */ l1s.tch_loop_mode = tch_mode_req->tch_loop_mode; + /* TODO: Handle AMR codecs from tch_mode_req if tch_mode_req->tch_mode==GSM48_CMODE_SPEECH_AMR */
l1ctl_tx_tch_mode_conf(tch_mode, audio_mode); } @@ -726,4 +727,3 @@ { sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx); } -