blob: b58efd96a6cc1399ff24f815ebca7dfba67d7ff9 [file] [log] [blame]
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +01001/* Routines for translation between codec representations: SDP, CC/BSSMAP variants, MGCP, MNCC */
2/*
Vadim Yanitskiy999a5932023-05-18 17:22:26 +07003 * (C) 2019-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +01004 * All Rights Reserved
5 *
6 * Author: Neels Hofmeyr
7 *
8 * SPDX-License-Identifier: AGPL-3.0+
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23#include <string.h>
24
25#include <osmocom/gsm/mncc.h>
26
27#include <osmocom/msc/sdp_msg.h>
28#include <osmocom/msc/codec_mapping.h>
29#include <osmocom/msc/mncc.h>
30
31const struct codec_mapping codec_map[] = {
32 /* FIXME: sdp.fmtp handling is not done properly yet, proper mode-set and octet-align handling will follow in
33 * separate patches. */
34 {
35 .sdp = {
36 .payload_type = 0,
37 .subtype_name = "PCMU",
38 .rate = 8000,
39 },
40 .mgcp = CODEC_PCMU_8000_1,
41 },
42 {
43 .sdp = {
44 .payload_type = 3,
45 .subtype_name = "GSM",
46 .rate = 8000,
47 },
48 .mgcp = CODEC_GSM_8000_1,
49 .speech_ver_count = 1,
50 .speech_ver = { GSM48_BCAP_SV_FR },
51 .mncc_payload_msg_type = GSM_TCHF_FRAME,
52 .has_gsm0808_speech_codec = true,
53 .gsm0808_speech_codec = {
54 .fi = true,
55 .type = GSM0808_SCT_FR1,
56 },
57 .perm_speech = GSM0808_PERM_FR1,
58 .frhr = CODEC_FRHR_FR,
59 },
60 {
61 .sdp = {
62 .payload_type = 8,
63 .subtype_name = "PCMA",
64 .rate = 8000,
65 },
66 .mgcp = CODEC_PCMA_8000_1,
67 },
68 {
69 .sdp = {
70 .payload_type = 18,
71 .subtype_name = "G729",
72 .rate = 8000,
73 },
74 .mgcp = CODEC_G729_8000_1,
75 },
76 {
77 .sdp = {
78 .payload_type = 110,
79 .subtype_name = "GSM-EFR",
80 .rate = 8000,
81 },
82 .mgcp = CODEC_GSMEFR_8000_1,
83 .speech_ver_count = 1,
84 .speech_ver = { GSM48_BCAP_SV_EFR },
85 .mncc_payload_msg_type = GSM_TCHF_FRAME_EFR,
86 .has_gsm0808_speech_codec = true,
87 .gsm0808_speech_codec = {
88 .fi = true,
89 .type = GSM0808_SCT_FR2,
90 },
91 .perm_speech = GSM0808_PERM_FR2,
92 .frhr = CODEC_FRHR_FR,
93 },
94 {
95 .sdp = {
96 .payload_type = 111,
97 .subtype_name = "GSM-HR-08",
98 .rate = 8000,
99 },
100 .mgcp = CODEC_GSMHR_8000_1,
101 .speech_ver_count = 1,
102 .speech_ver = { GSM48_BCAP_SV_HR },
103 .mncc_payload_msg_type = GSM_TCHH_FRAME,
104 .has_gsm0808_speech_codec = true,
105 .gsm0808_speech_codec = {
106 .fi = true,
107 .type = GSM0808_SCT_HR1,
108 .cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR,
109 },
110 .perm_speech = GSM0808_PERM_HR1,
111 .frhr = CODEC_FRHR_HR,
112 },
113 {
114 .sdp = {
115 .payload_type = 112,
116 .subtype_name = "AMR",
117 .rate = 8000,
118 /* AMR is always octet-aligned in 2G and 3G RAN, so this fmtp is signalled to remote call legs.
119 * So far, fmtp is ignored in incoming SIP SDP, so an incoming SDP without 'octet-align=1' will
120 * match with this entry; we will still reply with 'octet-align=1', which often works out. */
121 .fmtp = "octet-align=1",
122 },
123 .mgcp = CODEC_AMR_8000_1,
124 .speech_ver_count = 1,
125 .speech_ver = { GSM48_BCAP_SV_AMR_F },
126 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
127 .has_gsm0808_speech_codec = true,
128 .gsm0808_speech_codec = {
129 .fi = true,
130 .type = GSM0808_SCT_FR3,
131 .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR,
132 },
133 .perm_speech = GSM0808_PERM_FR3,
134 .frhr = CODEC_FRHR_FR,
135 },
136 {
137 /* Another entry like the above, to map HR3 to AMR, too. */
138 .sdp = {
139 .payload_type = 112,
140 .subtype_name = "AMR",
141 .rate = 8000,
142 .fmtp = "octet-align=1",
143 },
144 .mgcp = CODEC_AMR_8000_1,
145 .speech_ver_count = 2,
146 .speech_ver = { GSM48_BCAP_SV_AMR_H, GSM48_BCAP_SV_AMR_OH },
147 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
148 .has_gsm0808_speech_codec = true,
149 .gsm0808_speech_codec = {
150 .fi = true,
151 .type = GSM0808_SCT_HR3,
152 .cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR,
153 },
154 .perm_speech = GSM0808_PERM_HR3,
155 .frhr = CODEC_FRHR_HR,
156 },
157 {
158 .sdp = {
159 .payload_type = 113,
160 .subtype_name = "AMR-WB",
161 .rate = 16000,
162 .fmtp = "octet-align=1",
163 },
164 .mgcp = CODEC_AMRWB_16000_1,
165 .speech_ver_count = 2,
166 .speech_ver = { GSM48_BCAP_SV_AMR_OFW, GSM48_BCAP_SV_AMR_FW },
167 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
168 .has_gsm0808_speech_codec = true,
169 .gsm0808_speech_codec = {
170 .fi = true,
171 .type = GSM0808_SCT_FR5,
172 .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR_WB,
173 },
174 .perm_speech = GSM0808_PERM_FR5,
175 .frhr = CODEC_FRHR_FR,
176 },
177 {
178 /* Another entry like the above, to map HR4 to AMR-WB, too. */
179 .sdp = {
180 .payload_type = 113,
181 .subtype_name = "AMR-WB",
182 .rate = 16000,
183 .fmtp = "octet-align=1",
184 },
185 .mgcp = CODEC_AMRWB_16000_1,
186 .speech_ver_count = 1,
187 .speech_ver = { GSM48_BCAP_SV_AMR_OHW },
188 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
189 .has_gsm0808_speech_codec = true,
190 .gsm0808_speech_codec = {
191 .fi = true,
192 .type = GSM0808_SCT_HR4,
193 .cfg = GSM0808_SC_CFG_DEFAULT_OHR_AMR_WB,
194 },
195 .perm_speech = GSM0808_PERM_HR4,
196 .frhr = CODEC_FRHR_HR,
197 },
198 {
199 .sdp = {
200 .payload_type = 96,
201 .subtype_name = "VND.3GPP.IUFP",
202 .rate = 16000,
203 },
204 .mgcp = CODEC_IUFP,
205 },
Oliver Smithb4b9ec82023-05-25 10:47:47 +0200206 {
207 .sdp = {
208 .payload_type = 120,
209 .subtype_name = "CLEARMODE",
210 .rate = 8000,
211 },
212 .mgcp = CODEC_CLEARMODE,
213 },
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100214};
215
216#define foreach_codec_mapping(CODEC_MAPPING) \
217 for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++)
218
219const struct gsm_mncc_bearer_cap bearer_cap_empty = {
220 .speech_ver = { -1 },
221 };
222
223const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver)
224{
225 const struct codec_mapping *m;
226 foreach_codec_mapping(m) {
227 int i;
228 for (i = 0; i < m->speech_ver_count; i++)
229 if (m->speech_ver[i] == speech_ver)
230 return m;
231 }
232 return NULL;
233}
234
235const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct)
236{
237 const struct codec_mapping *m;
238 foreach_codec_mapping(m) {
239 if (!m->has_gsm0808_speech_codec)
240 continue;
241 if (m->gsm0808_speech_codec.type == sct)
242 return m;
243 }
244 return NULL;
245}
246
247const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec(const struct gsm0808_speech_codec *sc)
248{
249 const struct codec_mapping *m;
250 foreach_codec_mapping(m) {
251 if (!m->has_gsm0808_speech_codec)
252 continue;
253 if (m->gsm0808_speech_codec.type != sc->type)
254 continue;
255 /* Return only those where sc->cfg is a subset of m->gsm0808_speech_codec.cfg. */
256 if ((m->gsm0808_speech_codec.cfg & sc->cfg) != sc->cfg)
257 continue;
258 return m;
259 }
260 return NULL;
261}
262
263const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech)
264{
265 const struct codec_mapping *m;
266 foreach_codec_mapping(m) {
267 if (m->perm_speech == perm_speech)
268 return m;
269 }
270 return NULL;
271}
272
273const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name)
274{
275 const struct codec_mapping *m;
276 foreach_codec_mapping(m) {
277 if (!strcmp(m->sdp.subtype_name, subtype_name))
278 return m;
279 }
280 return NULL;
281}
282
283const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp)
284{
285 const struct codec_mapping *m;
286 foreach_codec_mapping(m) {
287 if (m->mgcp == mgcp)
288 return m;
289 }
290 return NULL;
291}
292
293/* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero
294 * otherwise (as in, return the number of items added). */
295int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver)
296{
297 int i;
298 for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
299 if (bearer_cap->speech_ver[i] == speech_ver)
300 return 0;
301 if (bearer_cap->speech_ver[i] == -1) {
302 bearer_cap->speech_ver[i] = speech_ver;
303 bearer_cap->speech_ver[i+1] = -1;
304 return 1;
305 }
306 }
307 return 0;
308}
309
310/* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio.
311 * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */
312int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap)
313{
314 bool hr_present = false;
315 int i;
316 for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
317 const struct codec_mapping *m;
318
319 if (bearer_cap->speech_ver[i] == -1)
320 break;
321
322 m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]);
323
324 if (!m)
325 continue;
326
327 if (m->frhr == CODEC_FRHR_HR)
328 hr_present = true;
329 }
330
331 if (hr_present)
332 bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR;
333 else
334 bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY;
335
336 return 0;
337}
338
339/* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities.
340 * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and
341 * return 0). */
342int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec)
343{
344 const struct codec_mapping *m;
345 int added = 0;
346 foreach_codec_mapping(m) {
347 int i;
348 if (strcmp(m->sdp.subtype_name, codec->subtype_name))
349 continue;
350 /* TODO also match rate and fmtp? */
351 for (i = 0; i < m->speech_ver_count; i++)
352 added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]);
353 }
354 return added;
355}
356
357/* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with
358 * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added.
359 * Note that Speech Version entries are only appended, no previous entries are removed.
360 * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio
361 * must also be set (before or after this function); see also bearer_cap_set_radio(). */
362int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac)
363{
364 const struct sdp_audio_codec *codec;
365 int added = 0;
366
367 foreach_sdp_audio_codec(codec, ac) {
368 added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec);
369 }
370
371 return added;
372}
373
374/* Convert Speech Version to SDP audio codec and append to SDP message struct. */
375struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac,
376 enum gsm48_bcap_speech_ver speech_ver)
377{
378 const struct codec_mapping *m;
379 struct sdp_audio_codec *ret = NULL;
380 foreach_codec_mapping(m) {
381 int i;
382 for (i = 0; i < m->speech_ver_count; i++) {
383 if (m->speech_ver[i] == speech_ver) {
384 ret = sdp_audio_codecs_add_copy(ac, &m->sdp);
385 break;
386 }
387 }
388 }
389 return ret;
390}
391
392struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec)
393{
394 const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec);
395 if (!m)
396 return NULL;
397 return sdp_audio_codecs_add_copy(ac, &m->sdp);
398}
399
400void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc)
401{
402 unsigned int i;
403
404 for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) {
405 if (bc->speech_ver[i] == -1)
406 break;
407 sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]);
408 }
409}
410
411void sdp_audio_codecs_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codecs *ac)
412{
413 const struct sdp_audio_codec *codec;
414
415 *scl = (struct gsm0808_speech_codec_list){};
416
417 foreach_sdp_audio_codec(codec, ac) {
418 const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name);
419 if (!m)
420 continue;
421 if (!m->has_gsm0808_speech_codec)
422 continue;
423 if (scl->len >= ARRAY_SIZE(scl->codec))
424 break;
425 scl->codec[scl->len] = m->gsm0808_speech_codec;
426 /* FIXME: apply AMR configuration according to codec->fmtp */
427 scl->len++;
428 }
429}
430
431void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl)
432{
433 int i;
434 for (i = 0; i < cl->len; i++) {
435 const struct gsm0808_speech_codec *sc = &cl->codec[i];
436 const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec(sc);
437 if (!m)
438 continue;
439 sdp_audio_codecs_add_copy(ac, &m->sdp);
440 /* FIXME: for AMR, apply sc->cfg to the added codec's fmtp */
441 }
442}
443
444int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac)
445{
446 const struct sdp_audio_codec *codec;
447 bool fr_present = false;
448 int first_fr_idx = -1;
449 bool hr_present = false;
450 int first_hr_idx = -1;
451 int idx = -1;
452
453 *ct = (struct gsm0808_channel_type){
454 .ch_indctr = GSM0808_CHAN_SPEECH,
455 };
456
457 foreach_sdp_audio_codec(codec, ac) {
458 const struct codec_mapping *m;
459 int i;
460 bool dup;
461 idx++;
462 foreach_codec_mapping(m) {
463 if (strcmp(m->sdp.subtype_name, codec->subtype_name))
464 continue;
465
466 switch (m->perm_speech) {
467 default:
468 continue;
469
470 case GSM0808_PERM_FR1:
471 case GSM0808_PERM_FR2:
472 case GSM0808_PERM_FR3:
473 case GSM0808_PERM_FR4:
474 case GSM0808_PERM_FR5:
475 fr_present = true;
476 if (first_fr_idx < 0)
477 first_fr_idx = idx;
478 break;
479
480 case GSM0808_PERM_HR1:
481 case GSM0808_PERM_HR2:
482 case GSM0808_PERM_HR3:
483 case GSM0808_PERM_HR4:
484 case GSM0808_PERM_HR6:
485 hr_present = true;
486 if (first_hr_idx < 0)
487 first_hr_idx = idx;
488 break;
489 }
490
491 /* Avoid duplicates */
492 dup = false;
493 for (i = 0; i < ct->perm_spch_len; i++) {
494 if (ct->perm_spch[i] == m->perm_speech) {
495 dup = true;
496 break;
497 }
498 }
499 if (dup)
500 continue;
501
502 ct->perm_spch[ct->perm_spch_len] = m->perm_speech;
503 ct->perm_spch_len++;
504 }
505 }
506
507 if (fr_present && hr_present) {
508 if (first_fr_idx <= first_hr_idx)
509 ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF;
510 else
511 ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF;
512 } else if (fr_present && !hr_present)
513 ct->ch_rate_type = GSM0808_SPEECH_FULL_BM;
514 else if (!fr_present && hr_present)
515 ct->ch_rate_type = GSM0808_SPEECH_HALF_LM;
516 else
517 return -EINVAL;
518 return 0;
519}
520
521enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec)
522{
523 const struct codec_mapping *m;
524 foreach_codec_mapping(m) {
525 if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false))
526 return m->mgcp;
527 }
528 return NO_MGCP_CODEC;
529}