blob: 4063514ecc945a3ffb127465c85836cb89672717 [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,
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100108 },
109 .perm_speech = GSM0808_PERM_HR1,
110 .frhr = CODEC_FRHR_HR,
111 },
112 {
113 .sdp = {
114 .payload_type = 112,
115 .subtype_name = "AMR",
116 .rate = 8000,
117 /* AMR is always octet-aligned in 2G and 3G RAN, so this fmtp is signalled to remote call legs.
118 * So far, fmtp is ignored in incoming SIP SDP, so an incoming SDP without 'octet-align=1' will
119 * match with this entry; we will still reply with 'octet-align=1', which often works out. */
120 .fmtp = "octet-align=1",
121 },
122 .mgcp = CODEC_AMR_8000_1,
123 .speech_ver_count = 1,
124 .speech_ver = { GSM48_BCAP_SV_AMR_F },
125 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
126 .has_gsm0808_speech_codec = true,
127 .gsm0808_speech_codec = {
128 .fi = true,
129 .type = GSM0808_SCT_FR3,
130 .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR,
131 },
132 .perm_speech = GSM0808_PERM_FR3,
133 .frhr = CODEC_FRHR_FR,
134 },
135 {
136 /* Another entry like the above, to map HR3 to AMR, too. */
137 .sdp = {
138 .payload_type = 112,
139 .subtype_name = "AMR",
140 .rate = 8000,
141 .fmtp = "octet-align=1",
142 },
143 .mgcp = CODEC_AMR_8000_1,
144 .speech_ver_count = 2,
145 .speech_ver = { GSM48_BCAP_SV_AMR_H, GSM48_BCAP_SV_AMR_OH },
146 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
147 .has_gsm0808_speech_codec = true,
148 .gsm0808_speech_codec = {
149 .fi = true,
150 .type = GSM0808_SCT_HR3,
151 .cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR,
152 },
153 .perm_speech = GSM0808_PERM_HR3,
154 .frhr = CODEC_FRHR_HR,
155 },
156 {
157 .sdp = {
158 .payload_type = 113,
159 .subtype_name = "AMR-WB",
160 .rate = 16000,
161 .fmtp = "octet-align=1",
162 },
163 .mgcp = CODEC_AMRWB_16000_1,
164 .speech_ver_count = 2,
165 .speech_ver = { GSM48_BCAP_SV_AMR_OFW, GSM48_BCAP_SV_AMR_FW },
166 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
167 .has_gsm0808_speech_codec = true,
168 .gsm0808_speech_codec = {
169 .fi = true,
170 .type = GSM0808_SCT_FR5,
171 .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR_WB,
172 },
173 .perm_speech = GSM0808_PERM_FR5,
174 .frhr = CODEC_FRHR_FR,
175 },
176 {
177 /* Another entry like the above, to map HR4 to AMR-WB, too. */
178 .sdp = {
179 .payload_type = 113,
180 .subtype_name = "AMR-WB",
181 .rate = 16000,
182 .fmtp = "octet-align=1",
183 },
184 .mgcp = CODEC_AMRWB_16000_1,
185 .speech_ver_count = 1,
186 .speech_ver = { GSM48_BCAP_SV_AMR_OHW },
187 .mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
188 .has_gsm0808_speech_codec = true,
189 .gsm0808_speech_codec = {
190 .fi = true,
191 .type = GSM0808_SCT_HR4,
192 .cfg = GSM0808_SC_CFG_DEFAULT_OHR_AMR_WB,
193 },
194 .perm_speech = GSM0808_PERM_HR4,
195 .frhr = CODEC_FRHR_HR,
196 },
197 {
198 .sdp = {
199 .payload_type = 96,
200 .subtype_name = "VND.3GPP.IUFP",
201 .rate = 16000,
202 },
203 .mgcp = CODEC_IUFP,
204 },
Oliver Smithb4b9ec82023-05-25 10:47:47 +0200205 {
206 .sdp = {
207 .payload_type = 120,
208 .subtype_name = "CLEARMODE",
209 .rate = 8000,
210 },
Vadim Yanitskiy91455312023-07-25 18:44:46 +0700211 .has_gsm0808_speech_codec = true,
212 .gsm0808_speech_codec = {
213 .pi = true, /* PI indicates CSDoIP is supported */
214 .pt = false, /* PT indicates CSDoTDM is not supported */
215 .type = GSM0808_SCT_CSD,
216 .cfg = 0, /* R2/R3 not set (redundancy not supported) */
217 },
Oliver Smithb4b9ec82023-05-25 10:47:47 +0200218 .mgcp = CODEC_CLEARMODE,
219 },
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100220};
221
222#define foreach_codec_mapping(CODEC_MAPPING) \
223 for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++)
224
225const struct gsm_mncc_bearer_cap bearer_cap_empty = {
226 .speech_ver = { -1 },
227 };
228
229const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver)
230{
231 const struct codec_mapping *m;
232 foreach_codec_mapping(m) {
233 int i;
234 for (i = 0; i < m->speech_ver_count; i++)
235 if (m->speech_ver[i] == speech_ver)
236 return m;
237 }
238 return NULL;
239}
240
241const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct)
242{
243 const struct codec_mapping *m;
244 foreach_codec_mapping(m) {
245 if (!m->has_gsm0808_speech_codec)
246 continue;
247 if (m->gsm0808_speech_codec.type == sct)
248 return m;
249 }
250 return NULL;
251}
252
253const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec(const struct gsm0808_speech_codec *sc)
254{
255 const struct codec_mapping *m;
256 foreach_codec_mapping(m) {
257 if (!m->has_gsm0808_speech_codec)
258 continue;
259 if (m->gsm0808_speech_codec.type != sc->type)
260 continue;
261 /* Return only those where sc->cfg is a subset of m->gsm0808_speech_codec.cfg. */
262 if ((m->gsm0808_speech_codec.cfg & sc->cfg) != sc->cfg)
263 continue;
264 return m;
265 }
266 return NULL;
267}
268
269const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech)
270{
271 const struct codec_mapping *m;
272 foreach_codec_mapping(m) {
273 if (m->perm_speech == perm_speech)
274 return m;
275 }
276 return NULL;
277}
278
279const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name)
280{
281 const struct codec_mapping *m;
282 foreach_codec_mapping(m) {
283 if (!strcmp(m->sdp.subtype_name, subtype_name))
284 return m;
285 }
286 return NULL;
287}
288
289const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp)
290{
291 const struct codec_mapping *m;
292 foreach_codec_mapping(m) {
293 if (m->mgcp == mgcp)
294 return m;
295 }
296 return NULL;
297}
298
299/* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero
300 * otherwise (as in, return the number of items added). */
301int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver)
302{
303 int i;
304 for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
305 if (bearer_cap->speech_ver[i] == speech_ver)
306 return 0;
307 if (bearer_cap->speech_ver[i] == -1) {
308 bearer_cap->speech_ver[i] = speech_ver;
309 bearer_cap->speech_ver[i+1] = -1;
310 return 1;
311 }
312 }
313 return 0;
314}
315
316/* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio.
317 * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */
318int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap)
319{
320 bool hr_present = false;
321 int i;
322 for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
323 const struct codec_mapping *m;
324
325 if (bearer_cap->speech_ver[i] == -1)
326 break;
327
328 m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]);
329
330 if (!m)
331 continue;
332
333 if (m->frhr == CODEC_FRHR_HR)
334 hr_present = true;
335 }
336
337 if (hr_present)
338 bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR;
339 else
340 bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY;
341
342 return 0;
343}
344
345/* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities.
346 * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and
347 * return 0). */
348int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec)
349{
350 const struct codec_mapping *m;
351 int added = 0;
352 foreach_codec_mapping(m) {
353 int i;
354 if (strcmp(m->sdp.subtype_name, codec->subtype_name))
355 continue;
356 /* TODO also match rate and fmtp? */
357 for (i = 0; i < m->speech_ver_count; i++)
358 added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]);
359 }
360 return added;
361}
362
363/* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with
364 * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added.
365 * Note that Speech Version entries are only appended, no previous entries are removed.
366 * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio
367 * must also be set (before or after this function); see also bearer_cap_set_radio(). */
368int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac)
369{
370 const struct sdp_audio_codec *codec;
371 int added = 0;
372
373 foreach_sdp_audio_codec(codec, ac) {
374 added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec);
375 }
376
377 return added;
378}
379
380/* Convert Speech Version to SDP audio codec and append to SDP message struct. */
381struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac,
382 enum gsm48_bcap_speech_ver speech_ver)
383{
384 const struct codec_mapping *m;
385 struct sdp_audio_codec *ret = NULL;
386 foreach_codec_mapping(m) {
387 int i;
388 for (i = 0; i < m->speech_ver_count; i++) {
389 if (m->speech_ver[i] == speech_ver) {
390 ret = sdp_audio_codecs_add_copy(ac, &m->sdp);
391 break;
392 }
393 }
394 }
395 return ret;
396}
397
398struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec)
399{
400 const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec);
401 if (!m)
402 return NULL;
403 return sdp_audio_codecs_add_copy(ac, &m->sdp);
404}
405
406void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc)
407{
408 unsigned int i;
409
410 for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) {
411 if (bc->speech_ver[i] == -1)
412 break;
413 sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]);
414 }
415}
416
Neels Hofmeyrcefe5942023-11-17 04:11:19 +0100417/* Append an entry for the given sdp_audio_codec to the gsm0808_speech_codec_list.
418 * Return 0 if an entry was added, -ENOENT when there is no mapping to gsm0808_speech_codec for the given
419 * sdp_audio_codec, and -ENOSPC when scl is full and nothing could be added. */
420int sdp_audio_codec_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codec *codec)
421{
422 const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name);
423 if (!m)
424 return -ENOENT;
425 if (!m->has_gsm0808_speech_codec)
426 return -ENOENT;
427 if (scl->len >= ARRAY_SIZE(scl->codec))
428 return -ENOSPC;
429 scl->codec[scl->len] = m->gsm0808_speech_codec;
430 /* FIXME: apply AMR configuration according to codec->fmtp */
431 scl->len++;
432 return 0;
433}
434
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100435void sdp_audio_codecs_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codecs *ac)
436{
437 const struct sdp_audio_codec *codec;
438
439 *scl = (struct gsm0808_speech_codec_list){};
440
441 foreach_sdp_audio_codec(codec, ac) {
Neels Hofmeyrcefe5942023-11-17 04:11:19 +0100442 int rc = sdp_audio_codec_to_speech_codec_list(scl, codec);
443 if (rc == -ENOSPC)
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100444 break;
Neels Hofmeyrb2ce7482022-01-13 18:17:56 +0100445 }
446}
447
448void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl)
449{
450 int i;
451 for (i = 0; i < cl->len; i++) {
452 const struct gsm0808_speech_codec *sc = &cl->codec[i];
453 const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec(sc);
454 if (!m)
455 continue;
456 sdp_audio_codecs_add_copy(ac, &m->sdp);
457 /* FIXME: for AMR, apply sc->cfg to the added codec's fmtp */
458 }
459}
460
461int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac)
462{
463 const struct sdp_audio_codec *codec;
464 bool fr_present = false;
465 int first_fr_idx = -1;
466 bool hr_present = false;
467 int first_hr_idx = -1;
468 int idx = -1;
469
470 *ct = (struct gsm0808_channel_type){
471 .ch_indctr = GSM0808_CHAN_SPEECH,
472 };
473
474 foreach_sdp_audio_codec(codec, ac) {
475 const struct codec_mapping *m;
476 int i;
477 bool dup;
478 idx++;
479 foreach_codec_mapping(m) {
480 if (strcmp(m->sdp.subtype_name, codec->subtype_name))
481 continue;
482
483 switch (m->perm_speech) {
484 default:
485 continue;
486
487 case GSM0808_PERM_FR1:
488 case GSM0808_PERM_FR2:
489 case GSM0808_PERM_FR3:
490 case GSM0808_PERM_FR4:
491 case GSM0808_PERM_FR5:
492 fr_present = true;
493 if (first_fr_idx < 0)
494 first_fr_idx = idx;
495 break;
496
497 case GSM0808_PERM_HR1:
498 case GSM0808_PERM_HR2:
499 case GSM0808_PERM_HR3:
500 case GSM0808_PERM_HR4:
501 case GSM0808_PERM_HR6:
502 hr_present = true;
503 if (first_hr_idx < 0)
504 first_hr_idx = idx;
505 break;
506 }
507
508 /* Avoid duplicates */
509 dup = false;
510 for (i = 0; i < ct->perm_spch_len; i++) {
511 if (ct->perm_spch[i] == m->perm_speech) {
512 dup = true;
513 break;
514 }
515 }
516 if (dup)
517 continue;
518
519 ct->perm_spch[ct->perm_spch_len] = m->perm_speech;
520 ct->perm_spch_len++;
521 }
522 }
523
524 if (fr_present && hr_present) {
525 if (first_fr_idx <= first_hr_idx)
526 ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF;
527 else
528 ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF;
529 } else if (fr_present && !hr_present)
530 ct->ch_rate_type = GSM0808_SPEECH_FULL_BM;
531 else if (!fr_present && hr_present)
532 ct->ch_rate_type = GSM0808_SPEECH_HALF_LM;
533 else
534 return -EINVAL;
535 return 0;
536}
537
538enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec)
539{
540 const struct codec_mapping *m;
541 foreach_codec_mapping(m) {
542 if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false))
543 return m->mgcp;
544 }
545 return NO_MGCP_CODEC;
546}