From 6b9c5cb3cdba25d96b0a2dfc06c1b725c3716688 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Thu, 13 Jan 2022 18:17:56 +0100 Subject: add codec_sdp_cc_t9n.h,c Related: SYS#5066 Change-Id: Iaa307be6a8487aa8d4ba7cd59d5c5ef04818a744 --- include/osmocom/msc/Makefile.am | 1 + include/osmocom/msc/codec_sdp_cc_t9n.h | 68 +++++ src/libmsc/Makefile.am | 1 + src/libmsc/codec_sdp_cc_t9n.c | 446 +++++++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 include/osmocom/msc/codec_sdp_cc_t9n.h create mode 100644 src/libmsc/codec_sdp_cc_t9n.c diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am index adda44c76..e1cf2cd68 100644 --- a/include/osmocom/msc/Makefile.am +++ b/include/osmocom/msc/Makefile.am @@ -1,6 +1,7 @@ noinst_HEADERS = \ call_leg.h \ cell_id_list.h \ + codec_sdp_cc_t9n.h \ db.h \ debug.h \ e_link.h \ diff --git a/include/osmocom/msc/codec_sdp_cc_t9n.h b/include/osmocom/msc/codec_sdp_cc_t9n.h new file mode 100644 index 000000000..093456133 --- /dev/null +++ b/include/osmocom/msc/codec_sdp_cc_t9n.h @@ -0,0 +1,68 @@ +/* Routines for translation ("t9n") between SDP codec names and CC/BSSMAP codec constants */ +#pragma once + +#include +#include +#include +#include +#include + +#define NO_MGCP_CODEC 0xffffffff + +extern const struct gsm_mncc_bearer_cap bearer_cap_empty; + +enum codec_frhr { + CODEC_FRHR_NONE = 0, + CODEC_FRHR_FR, + CODEC_FRHR_HR, +}; + +struct codec_mapping { + /* The sdp.payload_type number in a mapping is not necessarily imperative, but may just reflect the usual + * payload type number for a given codec. */ + struct sdp_audio_codec sdp; + /* The id that mgcp_client.h uses for this codec. Must be set in each mapping, because 0 means PCMU. */ + enum mgcp_codecs mgcp; + /* Nr of used entries in speech_ver[] below. */ + unsigned int speech_ver_count; + /* Entries to add to Speech Version lists when this codec is present, if any. */ + enum gsm48_bcap_speech_ver speech_ver[8]; + /* If applicable, one of GSM_TCHF_FRAME, GSM_TCHF_FRAME_EFR, GSM_TCHH_FRAME, GSM_TCH_FRAME_AMR; or zero. */ + uint32_t mncc_payload_msg_type; + /* Set to true if gsm0808_speech_codec_type below reflects a meaningful value. */ + bool has_gsm0808_speech_codec_type; + /* gsm0808_speech_codec_type corresponds to gsm0808_speech_codec[_list]->type */ + enum gsm0808_speech_codec_type gsm0808_speech_codec_type; + /* If applicable, entries to add to Permitted Speech lists when this codec is present; or zero. */ + enum gsm0808_permitted_speech perm_speech; + /* If applicable, indicator whether this codec can work on a GERAN half-rate lchan, or whether full-rate is + * required. Leave zero when this codec does not apply to GERAN. */ + enum codec_frhr frhr; +}; + +extern const struct codec_mapping codec_map[]; +#define foreach_codec_mapping(CODEC_MAPPING) \ + for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++) + +const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver); +const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct, + uint16_t cfg); +const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech); +const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name); +const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp); + +int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver); +int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec); +int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac); +int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap); + +struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac, + enum gsm48_bcap_speech_ver speech_ver); +struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec); +void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc); + +void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl); + +int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac); + +enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec); diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am index de02a1779..b843a65d8 100644 --- a/src/libmsc/Makefile.am +++ b/src/libmsc/Makefile.am @@ -27,6 +27,7 @@ noinst_LIBRARIES = \ libmsc_a_SOURCES = \ call_leg.c \ cell_id_list.c \ + codec_sdp_cc_t9n.c \ sccp_ran.c \ msc_vty.c \ db.c \ diff --git a/src/libmsc/codec_sdp_cc_t9n.c b/src/libmsc/codec_sdp_cc_t9n.c new file mode 100644 index 000000000..07089f985 --- /dev/null +++ b/src/libmsc/codec_sdp_cc_t9n.c @@ -0,0 +1,446 @@ +/* Translate codec discriminators between SDP and various GSM representations */ +/* + * (C) 2019-2022 by sysmocom - s.m.f.c. GmbH + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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. + */ +#include + +#include + +#include +#include +#include + +const struct codec_mapping codec_map[] = { + /* FIXME: I'm not sure about OFR, OHR -- O means octet-aligned?? */ + /* FIXME: sdp.fmtp handling is not done properly. I am not sure whether we should completely remove fmtp from + * this mapping, or more intelligently map actual fmtp contents (instead of strcmp). I hope that we will reach + * some clarity on this subject in the future. */ + { + .sdp = { + .payload_type = 0, + .subtype_name = "PCMU", + .rate = 8000, + }, + .mgcp = CODEC_PCMU_8000_1, + }, + { + .sdp = { + .payload_type = 3, + .subtype_name = "GSM", + .rate = 8000, + }, + .mgcp = CODEC_GSM_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_FR }, + .mncc_payload_msg_type = GSM_TCHF_FRAME, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_FR1, + .perm_speech = GSM0808_PERM_FR1, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 8, + .subtype_name = "PCMA", + .rate = 8000, + }, + .mgcp = CODEC_PCMA_8000_1, + }, + { + .sdp = { + .payload_type = 18, + .subtype_name = "G729", + .rate = 8000, + }, + .mgcp = CODEC_G729_8000_1, + }, + { + .sdp = { + .payload_type = 110, + .subtype_name = "GSM-EFR", + .rate = 8000, + }, + .mgcp = CODEC_GSMEFR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_EFR }, + .mncc_payload_msg_type = GSM_TCHF_FRAME_EFR, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_FR2, + .perm_speech = GSM0808_PERM_FR2, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 111, + .subtype_name = "GSM-HR-08", + .rate = 8000, + }, + .mgcp = CODEC_GSMHR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_HR }, + .mncc_payload_msg_type = GSM_TCHH_FRAME, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_HR1, + .perm_speech = GSM0808_PERM_HR1, + .frhr = CODEC_FRHR_HR, + }, + { + .sdp = { + .payload_type = 112, + .subtype_name = "AMR", + .rate = 8000, + /* It is empirically shown to be important to send this fmtp parameter to a SIP peer in SDP, + * otherwise the voice audio is broken noise. + * However, a SIP peer may offer AMR without this parameter set in its SDP, so fmtp must be + * ignored during codec matching: otherwise an incoming AMR codec without this parameter fails + * to match this entry, and it ends in an aborted call due to no codec match. + * If the peer offers plain "AMR/8000" and we reply with "AMR/8000 fmtp:octet-align=1", + * then everything works out happily, */ + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_AMR_F }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_FR3, + .perm_speech = GSM0808_PERM_FR3, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 112, + .subtype_name = "AMR", + .rate = 8000, + .fmtp = "octet-align=1;mode-set=0,1,2,3", + }, + .mgcp = CODEC_AMR_8000_1, + .speech_ver_count = 2, + .speech_ver = { GSM48_BCAP_SV_AMR_H, GSM48_BCAP_SV_AMR_OH }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_HR3, + .perm_speech = GSM0808_PERM_HR3, + .frhr = CODEC_FRHR_HR, + }, + { + .sdp = { + .payload_type = 113, + .subtype_name = "AMR-WB", + .rate = 16000, + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMRWB_16000_1, + .speech_ver_count = 2, + .speech_ver = { GSM48_BCAP_SV_AMR_OFW, GSM48_BCAP_SV_AMR_FW }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_FR5, + .perm_speech = GSM0808_PERM_FR5, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 113, + .subtype_name = "AMR-WB", + .rate = 16000, + .fmtp = "octet-align=1;mode-set=0,1,2,3", /* TODO: does this make sense?? */ + }, + .mgcp = CODEC_AMRWB_16000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_AMR_OHW }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec_type = true, + .gsm0808_speech_codec_type = GSM0808_SCT_HR4, + .perm_speech = GSM0808_PERM_HR4, + .frhr = CODEC_FRHR_HR, + }, +}; + +const struct gsm_mncc_bearer_cap bearer_cap_empty = { + .speech_ver = { -1 }, + }; + +const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + int i; + for (i = 0; i < m->speech_ver_count; i++) + if (m->speech_ver[i] == speech_ver) + return m; + } + return NULL; +} + + +const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct, uint16_t cfg) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!m->has_gsm0808_speech_codec_type) + continue; + if (m->gsm0808_speech_codec_type == sct) + return m; + /* TODO: evaluate cfg bits? */ + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (m->perm_speech == perm_speech) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!strcmp(m->sdp.subtype_name, subtype_name)) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (m->mgcp == mgcp) + return m; + } + return NULL; +} + +/* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero + * otherwise (as in, return the number of items added). */ +int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver) +{ + int i; + for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { + if (bearer_cap->speech_ver[i] == speech_ver) + return 0; + if (bearer_cap->speech_ver[i] == -1) { + bearer_cap->speech_ver[i] = speech_ver; + bearer_cap->speech_ver[i+1] = -1; + return 1; + } + } + return 0; +} + +/* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio. + * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */ +int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap) +{ + bool hr_present; + int i; + for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { + const struct codec_mapping *m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]); + + if (!m) + continue; + + if (m->frhr == CODEC_FRHR_HR) + hr_present = true; + } + + if (hr_present) + bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR; + else + bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY; + + return 0; +} + +/* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities. + * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and + * return 0). */ +int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec) +{ + const struct codec_mapping *m; + int added = 0; + foreach_codec_mapping(m) { + int i; + if (strcmp(m->sdp.subtype_name, codec->subtype_name)) + continue; + /* TODO also match rate and fmtp? */ + for (i = 0; i < m->speech_ver_count; i++) { + added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]); + } + } + return added; +} + +/* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with + * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added. + * Note that Speech Version entries are only appended, no previous entries are removed. + * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio + * must also be set (before or after this function); see also bearer_cap_set_radio(). */ +int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac) +{ + const struct sdp_audio_codec *codec; + int added = 0; + + foreach_sdp_audio_codec(codec, ac) { + added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec); + } + + return added; +} + +/* Convert Speech Version to SDP audio codec and append to SDP message struct. */ +struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac, + enum gsm48_bcap_speech_ver speech_ver) +{ + const struct codec_mapping *m; + struct sdp_audio_codec *ret = NULL; + foreach_codec_mapping(m) { + int i; + for (i = 0; i < m->speech_ver_count; i++) { + if (m->speech_ver[i] == speech_ver) { + ret = sdp_audio_codecs_add_copy(ac, &m->sdp); + break; + } + } + } + return ret; +} + +struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec) +{ + const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec); + if (!m) + return NULL; + return sdp_audio_codecs_add_copy(ac, &m->sdp); +} + +void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) { + if (bc->speech_ver[i] == -1) + break; + sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]); + } +} + +void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl) +{ + int i; + for (i = 0; i < cl->len; i++) { + const struct gsm0808_speech_codec *sc = &cl->codec[i]; + const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec_type(sc->type, sc->cfg); + if (!m) + continue; + sdp_audio_codecs_add_copy(ac, &m->sdp); + } +} + +int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac) +{ + const struct sdp_audio_codec *codec; + bool fr_present = false; + int first_fr_idx = -1; + bool hr_present = false; + int first_hr_idx = -1; + int idx = -1; + + *ct = (struct gsm0808_channel_type){ + .ch_indctr = GSM0808_CHAN_SPEECH, + }; + + foreach_sdp_audio_codec(codec, ac) { + const struct codec_mapping *m; + int i; + bool dup; + idx++; + foreach_codec_mapping(m) { + if (strcmp(m->sdp.subtype_name, codec->subtype_name)) + continue; + + switch (m->perm_speech) { + default: + continue; + + case GSM0808_PERM_FR1: + case GSM0808_PERM_FR2: + case GSM0808_PERM_FR3: + case GSM0808_PERM_FR4: + case GSM0808_PERM_FR5: + fr_present = true; + if (first_fr_idx < 0) + first_fr_idx = idx; + break; + + case GSM0808_PERM_HR1: + case GSM0808_PERM_HR2: + case GSM0808_PERM_HR3: + case GSM0808_PERM_HR4: + case GSM0808_PERM_HR6: + hr_present = true; + if (first_hr_idx < 0) + first_hr_idx = idx; + break; + } + + /* Avoid duplicates */ + dup = false; + for (i = 0; i < ct->perm_spch_len; i++) { + if (ct->perm_spch[i] == m->perm_speech) { + dup = true; + break; + } + } + if (dup) + continue; + + ct->perm_spch[ct->perm_spch_len] = m->perm_speech; + ct->perm_spch_len++; + } + } + + if (fr_present && hr_present) { + if (first_fr_idx <= first_hr_idx) + ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF; + else + ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF; + } else if (fr_present && !hr_present) + ct->ch_rate_type = GSM0808_SPEECH_FULL_BM; + else if (!fr_present && hr_present) + ct->ch_rate_type = GSM0808_SPEECH_HALF_LM; + else + return -EINVAL; + return 0; +} + +enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false)) + return m->mgcp; + } + return NO_MGCP_CODEC; +} -- cgit v1.2.3