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
This commit is contained in:
Pau Espin 2022-09-02 17:02:17 +02:00 committed by Vadim Yanitskiy
parent a8ace99d72
commit a53e93fe9c
14 changed files with 406 additions and 110 deletions

View File

@ -162,7 +162,10 @@ struct l1ctl_tch_mode_conf {
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 @@ struct l1ctl_tch_mode_req {
#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 */

View File

@ -472,6 +472,7 @@ int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode,
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);
}

View File

@ -297,6 +297,8 @@ struct l1sched_lchan_state {
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 */

View File

@ -17,12 +17,13 @@ extern int l1sched_log_cat_data;
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 */

View File

@ -2,6 +2,7 @@ noinst_HEADERS = \
l1ctl_proto.h \
l1ctl_server.h \
l1ctl.h \
sched_utils.h \
trx_if.h \
logging.h \
trxcon.h \

View File

@ -0,0 +1,62 @@
/* Auxiliary scheduler utilities.
*
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@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/>.
*
*/
#pragma once
#include <stdint.h>
#include <errno.h>
#include <stdbool.h>
/*! 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:
OSMO_ASSERT(false);
return false;
break;
}
}

View File

@ -58,6 +58,10 @@ struct trxcon_param_fbsb_search_req {
/* 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;
};

View File

@ -705,6 +705,10 @@ static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
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) {

View File

@ -103,6 +103,8 @@ const char *l1sched_burst_mask2str(const uint8_t *mask, int bits)
*/
size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
{
int rc;
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == L1SCHED_TCHF) { /* Full Rate */
@ -119,8 +121,18 @@ size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
l2[0] = 0xc0;
return GSM_EFR_BYTES;
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
/* FIXME: AMR is not implemented yet */
return 0;
rc = osmo_amr_rtp_enc(l2,
lchan->amr.codec[lchan->amr.dl_cmr],
lchan->amr.codec[lchan->amr.dl_ft],
AMR_BAD);
if (rc < 2) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Failed to encode AMR_BAD frame (rc=%d), "
"not sending BFI\n", rc);
return 0;
}
memset(l2 + 2, 0, rc - 2);
return rc;
case GSM48_CMODE_SIGN:
LOGP_LCHAND(lchan, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
return 0;

View File

@ -30,10 +30,26 @@
#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>
#include <osmocom/bb/trxcon/sched_utils.h>
/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Downlink 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_dl_amr_cmi_map[26] = {
[11] = 1, /* TCH/F: a=4 / h=11 */
[20] = 1, /* TCH/F: a=13 / h=20 */
[3] = 1, /* TCH/F: a=21 / h=3 (21+7=28, 25 is idle -> 29. 29%26=3) */
};
int rx_tchf_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
@ -43,6 +59,9 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
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 +116,37 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
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 4,13,21 defines that CMI is included in frame,
* the first FN 0,8,17 defines that CMR/CMC 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_dl_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.dl_ft,
&lchan->amr.dl_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.dl_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.dl_cmr],
ft, AMR_GOOD);
if (rc < 0)
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;
@ -121,7 +165,7 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* FACCH received, forward it to the higher layers */
l1sched_handle_data_ind(lchan, l2, GSM_MACBLOCK_LEN,
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
@ -186,63 +230,101 @@ int tx_tchf_fn(struct l1sched_lchan_state *lchan,
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 (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
/* Encode payload */
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, GSM_MACBLOCK_LEN, 1);
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
int len;
uint8_t cmr_codec;
int ft, cmr, i;
enum osmo_amr_type ft_codec;
enum osmo_amr_quality bfi;
int8_t sti, cmi;
bool amr_fn_is_cmr;
/* 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.
*/
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
&cmr_codec, &cmi, &ft_codec,
&bfi, &sti);
if (len < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
goto free_bad_msg;
}
ft = -1;
cmr = -1;
for (i = 0; i < lchan->amr.codecs; i++) {
if (lchan->amr.codec[i] == ft_codec)
ft = i;
if (lchan->amr.codec[i] == cmr_codec)
cmr = i;
}
if (ft < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
goto free_bad_msg;
}
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
ft_codec);
goto free_bad_msg;
}
lchan->amr.ul_ft = ft;
if (cmr < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
} else {
lchan->amr.ul_cmr = cmr;
}
rc = gsm0503_tch_afs_encode(buffer, lchan->prim->payload + 2,
lchan->prim->payload_len - 2, amr_fn_is_cmr,
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,
lchan->prim->payload_len));
free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}

View File

@ -33,10 +33,31 @@
#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>
#include <osmocom/bb/trxcon/sched_utils.h>
/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Downlink TCH/H.
*
* +---+---+---+---+---+---+
* | a | b | c | d | e | f | Burst 'a' received first
* +---+---+---+---+---+---+
* ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f')
* ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd')
*
* TDMA frame number of burst 'f' is always used as the table index. */
static const uint8_t sched_tchh_dl_amr_cmi_map[26] = {
[15] = 1, /* TCH/H(0): a=4 / d=10 / f=15 */
[23] = 1, /* TCH/H(0): a=13 / d=19 / f=23 */
[6] = 1, /* TCH/H(0): a=21 / d=2 / f=6 */
[16] = 1, /* TCH/H(1): a=5 / d=11 / f=16 */
[24] = 1, /* TCH/H(1): a=14 / d=20 / f=24 */
[7] = 1, /* TCH/H(1): a=22 / d=3 / f=7 */
};
static const uint8_t tch_h0_traffic_block_map[3][4] = {
/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
@ -80,6 +101,18 @@ const uint8_t tch_h1_ul_facch_block_map[3][6] = {
{ 18, 20, 22, 24, 1, 3 },
};
/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1).
* This mapping is valid for both FACCH/H(0) and FACCH/H(1).
* TDMA frame number of burst 'f' is used as the table index. */
static const uint8_t sched_tchh_dl_facch_map[26] = {
[15] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */
[16] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */
[23] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */
[24] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */
[6] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */
[7] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */
};
/**
* Can a TCH/H block transmission be initiated / finished
* on a given frame number and a given channel type?
@ -199,6 +232,9 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
int amr = 0;
uint8_t ft;
bool fn_is_cmi;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
@ -261,12 +297,34 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
&n_errors, &n_bits_total);
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\n");
return -ENOTSUP;
/* the first FN FN 4,13,21 or 5,14,22 defines that CMI is
* included in frame, the first FN FN 0,8,17 or 1,9,18 defines
* that CMR/CMC is included in frame. */
fn_is_cmi = sched_tchh_dl_amr_cmi_map[fn % 26];
/* See comment in function rx_tchf_fn() */
amr = 2;
rc = gsm0503_tch_ahs_decode_dtx(l2 + amr, buffer,
!sched_tchh_dl_facch_map[fn % 26],
!fn_is_cmi, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
&lchan->amr.dl_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.dl_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.dl_cmr],
ft, AMR_GOOD);
if (rc < 0)
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;
@ -298,7 +356,7 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
l1sched_lchan_meas_avg(lchan, 6);
/* FACCH/H received, forward to the higher layers */
l1sched_handle_data_ind(lchan, l2, GSM_MACBLOCK_LEN,
l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
@ -394,62 +452,98 @@ int tx_tchh_fn(struct l1sched_lchan_state *lchan,
goto send_burst;
}
/* Check the current TCH mode */
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* HR */
l2_len = GSM_HR_BYTES + 1;
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 payload length */
/* populate the buffer with bursts */
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
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);
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, lchan->prim->payload_len);
lchan->ul_facch_blocks = 6;
} else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
int len;
uint8_t cmr_codec;
int ft, cmr, i;
enum osmo_amr_type ft_codec;
enum osmo_amr_quality bfi;
int8_t sti, cmi;
bool amr_fn_is_cmr;
/* 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.
*/
amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
&cmr_codec, &cmi, &ft_codec,
&bfi, &sti);
if (len < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
goto free_bad_msg;
}
ft = -1;
cmr = -1;
for (i = 0; i < lchan->amr.codecs; i++) {
if (lchan->amr.codec[i] == ft_codec)
ft = i;
if (lchan->amr.codec[i] == cmr_codec)
cmr = i;
}
if (ft < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
goto free_bad_msg;
}
if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
ft_codec);
goto free_bad_msg;
}
lchan->amr.ul_ft = ft;
if (cmr < 0) {
LOGP_LCHAND(lchan, LOGL_ERROR,
"Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
} else {
lchan->amr.ul_cmr = cmr;
}
rc = gsm0503_tch_ahs_encode(buffer, lchan->prim->payload + 2,
lchan->prim->payload_len - 2, amr_fn_is_cmr,
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: /* HR */
l2_len = GSM_HR_BYTES + 1;
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);
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
}
/* Encode the payload */
rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
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,
lchan->prim->payload_len));
free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
/* A FACCH/H frame occupies 6 bursts */
if (L1SCHED_PRIM_IS_FACCH(lchan->prim))
lchan->ul_facch_blocks = 6;
send_burst:
/* Determine which burst should be sent */
offset = buffer + br->bid * 116;

View File

@ -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))
@ -351,6 +352,31 @@ static void trxcon_st_dedicated_action(struct osmo_fsm_inst *fi,
if (!lchan->active)
continue;
lchan->tch_mode = req->mode;
if (req->mode == GSM48_CMODE_SPEECH_AMR) {
uint8_t bmask = req->amr.codecs_bitmask;
int n = 0;
int acum = 0;
int pos;
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;
lchan->amr.dl_ft = req->amr.start_codec;
lchan->amr.dl_cmr = req->amr.start_codec;
lchan->amr.ul_ft = req->amr.start_codec;
lchan->amr.ul_cmr = req->amr.start_codec;
}
req->applied = true;
}
}

View File

@ -486,6 +486,7 @@ void l1ctl_rx_tch_mode_req(struct l1_model_ms *ms, struct msgb *msg)
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);

View File

@ -553,6 +553,7 @@ static void l1ctl_rx_tch_mode_req(struct msgb *msg)
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 @@ void l1a_l23api_init(void)
{
sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx);
}