Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 1 | /* Routines for translation between codec representations: SDP, CC/BSSMAP variants, MGCP, MNCC */ |
| 2 | /* |
Vadim Yanitskiy | 999a593 | 2023-05-18 17:22:26 +0700 | [diff] [blame] | 3 | * (C) 2019-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 4 | * 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 | |
| 31 | const 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 Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 108 | }, |
| 109 | .perm_speech = GSM0808_PERM_HR1, |
| 110 | .frhr = CODEC_FRHR_HR, |
| 111 | }, |
| 112 | { |
| 113 | .sdp = { |
Neels Hofmeyr | e38f1eb | 2023-11-28 05:28:21 +0100 | [diff] [blame] | 114 | /* 112 is just what we use by default. The other call leg may impose a different number. */ |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 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 Smith | b4b9ec8 | 2023-05-25 10:47:47 +0200 | [diff] [blame] | 206 | { |
| 207 | .sdp = { |
| 208 | .payload_type = 120, |
| 209 | .subtype_name = "CLEARMODE", |
| 210 | .rate = 8000, |
| 211 | }, |
Vadim Yanitskiy | 9145531 | 2023-07-25 18:44:46 +0700 | [diff] [blame] | 212 | .has_gsm0808_speech_codec = true, |
| 213 | .gsm0808_speech_codec = { |
| 214 | .pi = true, /* PI indicates CSDoIP is supported */ |
| 215 | .pt = false, /* PT indicates CSDoTDM is not supported */ |
| 216 | .type = GSM0808_SCT_CSD, |
| 217 | .cfg = 0, /* R2/R3 not set (redundancy not supported) */ |
| 218 | }, |
Oliver Smith | b4b9ec8 | 2023-05-25 10:47:47 +0200 | [diff] [blame] | 219 | .mgcp = CODEC_CLEARMODE, |
| 220 | }, |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 221 | }; |
| 222 | |
| 223 | #define foreach_codec_mapping(CODEC_MAPPING) \ |
| 224 | for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++) |
| 225 | |
| 226 | const struct gsm_mncc_bearer_cap bearer_cap_empty = { |
| 227 | .speech_ver = { -1 }, |
| 228 | }; |
| 229 | |
| 230 | const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver) |
| 231 | { |
| 232 | const struct codec_mapping *m; |
| 233 | foreach_codec_mapping(m) { |
| 234 | int i; |
| 235 | for (i = 0; i < m->speech_ver_count; i++) |
| 236 | if (m->speech_ver[i] == speech_ver) |
| 237 | return m; |
| 238 | } |
| 239 | return NULL; |
| 240 | } |
| 241 | |
| 242 | const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct) |
| 243 | { |
| 244 | const struct codec_mapping *m; |
| 245 | foreach_codec_mapping(m) { |
| 246 | if (!m->has_gsm0808_speech_codec) |
| 247 | continue; |
| 248 | if (m->gsm0808_speech_codec.type == sct) |
| 249 | return m; |
| 250 | } |
| 251 | return NULL; |
| 252 | } |
| 253 | |
| 254 | const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec(const struct gsm0808_speech_codec *sc) |
| 255 | { |
| 256 | const struct codec_mapping *m; |
| 257 | foreach_codec_mapping(m) { |
| 258 | if (!m->has_gsm0808_speech_codec) |
| 259 | continue; |
| 260 | if (m->gsm0808_speech_codec.type != sc->type) |
| 261 | continue; |
| 262 | /* Return only those where sc->cfg is a subset of m->gsm0808_speech_codec.cfg. */ |
| 263 | if ((m->gsm0808_speech_codec.cfg & sc->cfg) != sc->cfg) |
| 264 | continue; |
| 265 | return m; |
| 266 | } |
| 267 | return NULL; |
| 268 | } |
| 269 | |
| 270 | const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech) |
| 271 | { |
| 272 | const struct codec_mapping *m; |
| 273 | foreach_codec_mapping(m) { |
| 274 | if (m->perm_speech == perm_speech) |
| 275 | return m; |
| 276 | } |
| 277 | return NULL; |
| 278 | } |
| 279 | |
| 280 | const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name) |
| 281 | { |
| 282 | const struct codec_mapping *m; |
| 283 | foreach_codec_mapping(m) { |
| 284 | if (!strcmp(m->sdp.subtype_name, subtype_name)) |
| 285 | return m; |
| 286 | } |
| 287 | return NULL; |
| 288 | } |
| 289 | |
| 290 | const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp) |
| 291 | { |
| 292 | const struct codec_mapping *m; |
| 293 | foreach_codec_mapping(m) { |
| 294 | if (m->mgcp == mgcp) |
| 295 | return m; |
| 296 | } |
| 297 | return NULL; |
| 298 | } |
| 299 | |
| 300 | /* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero |
| 301 | * otherwise (as in, return the number of items added). */ |
| 302 | int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver) |
| 303 | { |
| 304 | int i; |
| 305 | for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { |
| 306 | if (bearer_cap->speech_ver[i] == speech_ver) |
| 307 | return 0; |
| 308 | if (bearer_cap->speech_ver[i] == -1) { |
| 309 | bearer_cap->speech_ver[i] = speech_ver; |
| 310 | bearer_cap->speech_ver[i+1] = -1; |
| 311 | return 1; |
| 312 | } |
| 313 | } |
| 314 | return 0; |
| 315 | } |
| 316 | |
| 317 | /* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio. |
| 318 | * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */ |
| 319 | int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap) |
| 320 | { |
| 321 | bool hr_present = false; |
| 322 | int i; |
| 323 | for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { |
| 324 | const struct codec_mapping *m; |
| 325 | |
| 326 | if (bearer_cap->speech_ver[i] == -1) |
| 327 | break; |
| 328 | |
| 329 | m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]); |
| 330 | |
| 331 | if (!m) |
| 332 | continue; |
| 333 | |
| 334 | if (m->frhr == CODEC_FRHR_HR) |
| 335 | hr_present = true; |
| 336 | } |
| 337 | |
| 338 | if (hr_present) |
| 339 | bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR; |
| 340 | else |
| 341 | bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY; |
| 342 | |
| 343 | return 0; |
| 344 | } |
| 345 | |
| 346 | /* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities. |
| 347 | * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and |
| 348 | * return 0). */ |
| 349 | int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec) |
| 350 | { |
| 351 | const struct codec_mapping *m; |
| 352 | int added = 0; |
| 353 | foreach_codec_mapping(m) { |
| 354 | int i; |
| 355 | if (strcmp(m->sdp.subtype_name, codec->subtype_name)) |
| 356 | continue; |
| 357 | /* TODO also match rate and fmtp? */ |
| 358 | for (i = 0; i < m->speech_ver_count; i++) |
| 359 | added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]); |
| 360 | } |
| 361 | return added; |
| 362 | } |
| 363 | |
| 364 | /* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with |
| 365 | * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added. |
| 366 | * Note that Speech Version entries are only appended, no previous entries are removed. |
| 367 | * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio |
| 368 | * must also be set (before or after this function); see also bearer_cap_set_radio(). */ |
| 369 | int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac) |
| 370 | { |
| 371 | const struct sdp_audio_codec *codec; |
| 372 | int added = 0; |
| 373 | |
| 374 | foreach_sdp_audio_codec(codec, ac) { |
| 375 | added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec); |
| 376 | } |
| 377 | |
| 378 | return added; |
| 379 | } |
| 380 | |
| 381 | /* Convert Speech Version to SDP audio codec and append to SDP message struct. */ |
| 382 | struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac, |
| 383 | enum gsm48_bcap_speech_ver speech_ver) |
| 384 | { |
| 385 | const struct codec_mapping *m; |
| 386 | struct sdp_audio_codec *ret = NULL; |
| 387 | foreach_codec_mapping(m) { |
| 388 | int i; |
| 389 | for (i = 0; i < m->speech_ver_count; i++) { |
| 390 | if (m->speech_ver[i] == speech_ver) { |
| 391 | ret = sdp_audio_codecs_add_copy(ac, &m->sdp); |
| 392 | break; |
| 393 | } |
| 394 | } |
| 395 | } |
| 396 | return ret; |
| 397 | } |
| 398 | |
| 399 | struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec) |
| 400 | { |
| 401 | const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec); |
| 402 | if (!m) |
| 403 | return NULL; |
| 404 | return sdp_audio_codecs_add_copy(ac, &m->sdp); |
| 405 | } |
| 406 | |
| 407 | void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc) |
| 408 | { |
| 409 | unsigned int i; |
| 410 | |
| 411 | for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) { |
| 412 | if (bc->speech_ver[i] == -1) |
| 413 | break; |
| 414 | sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]); |
| 415 | } |
| 416 | } |
| 417 | |
Neels Hofmeyr | cefe594 | 2023-11-17 04:11:19 +0100 | [diff] [blame] | 418 | /* Append an entry for the given sdp_audio_codec to the gsm0808_speech_codec_list. |
| 419 | * Return 0 if an entry was added, -ENOENT when there is no mapping to gsm0808_speech_codec for the given |
| 420 | * sdp_audio_codec, and -ENOSPC when scl is full and nothing could be added. */ |
| 421 | int sdp_audio_codec_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codec *codec) |
| 422 | { |
| 423 | const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name); |
| 424 | if (!m) |
| 425 | return -ENOENT; |
| 426 | if (!m->has_gsm0808_speech_codec) |
| 427 | return -ENOENT; |
| 428 | if (scl->len >= ARRAY_SIZE(scl->codec)) |
| 429 | return -ENOSPC; |
| 430 | scl->codec[scl->len] = m->gsm0808_speech_codec; |
| 431 | /* FIXME: apply AMR configuration according to codec->fmtp */ |
| 432 | scl->len++; |
| 433 | return 0; |
| 434 | } |
| 435 | |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 436 | void sdp_audio_codecs_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codecs *ac) |
| 437 | { |
| 438 | const struct sdp_audio_codec *codec; |
| 439 | |
| 440 | *scl = (struct gsm0808_speech_codec_list){}; |
| 441 | |
| 442 | foreach_sdp_audio_codec(codec, ac) { |
Neels Hofmeyr | cefe594 | 2023-11-17 04:11:19 +0100 | [diff] [blame] | 443 | int rc = sdp_audio_codec_to_speech_codec_list(scl, codec); |
| 444 | if (rc == -ENOSPC) |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 445 | break; |
Neels Hofmeyr | b2ce748 | 2022-01-13 18:17:56 +0100 | [diff] [blame] | 446 | } |
| 447 | } |
| 448 | |
| 449 | void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl) |
| 450 | { |
| 451 | int i; |
| 452 | for (i = 0; i < cl->len; i++) { |
| 453 | const struct gsm0808_speech_codec *sc = &cl->codec[i]; |
| 454 | const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec(sc); |
| 455 | if (!m) |
| 456 | continue; |
| 457 | sdp_audio_codecs_add_copy(ac, &m->sdp); |
| 458 | /* FIXME: for AMR, apply sc->cfg to the added codec's fmtp */ |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac) |
| 463 | { |
| 464 | const struct sdp_audio_codec *codec; |
| 465 | bool fr_present = false; |
| 466 | int first_fr_idx = -1; |
| 467 | bool hr_present = false; |
| 468 | int first_hr_idx = -1; |
| 469 | int idx = -1; |
| 470 | |
| 471 | *ct = (struct gsm0808_channel_type){ |
| 472 | .ch_indctr = GSM0808_CHAN_SPEECH, |
| 473 | }; |
| 474 | |
| 475 | foreach_sdp_audio_codec(codec, ac) { |
| 476 | const struct codec_mapping *m; |
| 477 | int i; |
| 478 | bool dup; |
| 479 | idx++; |
| 480 | foreach_codec_mapping(m) { |
| 481 | if (strcmp(m->sdp.subtype_name, codec->subtype_name)) |
| 482 | continue; |
| 483 | |
| 484 | switch (m->perm_speech) { |
| 485 | default: |
| 486 | continue; |
| 487 | |
| 488 | case GSM0808_PERM_FR1: |
| 489 | case GSM0808_PERM_FR2: |
| 490 | case GSM0808_PERM_FR3: |
| 491 | case GSM0808_PERM_FR4: |
| 492 | case GSM0808_PERM_FR5: |
| 493 | fr_present = true; |
| 494 | if (first_fr_idx < 0) |
| 495 | first_fr_idx = idx; |
| 496 | break; |
| 497 | |
| 498 | case GSM0808_PERM_HR1: |
| 499 | case GSM0808_PERM_HR2: |
| 500 | case GSM0808_PERM_HR3: |
| 501 | case GSM0808_PERM_HR4: |
| 502 | case GSM0808_PERM_HR6: |
| 503 | hr_present = true; |
| 504 | if (first_hr_idx < 0) |
| 505 | first_hr_idx = idx; |
| 506 | break; |
| 507 | } |
| 508 | |
| 509 | /* Avoid duplicates */ |
| 510 | dup = false; |
| 511 | for (i = 0; i < ct->perm_spch_len; i++) { |
| 512 | if (ct->perm_spch[i] == m->perm_speech) { |
| 513 | dup = true; |
| 514 | break; |
| 515 | } |
| 516 | } |
| 517 | if (dup) |
| 518 | continue; |
| 519 | |
| 520 | ct->perm_spch[ct->perm_spch_len] = m->perm_speech; |
| 521 | ct->perm_spch_len++; |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | if (fr_present && hr_present) { |
| 526 | if (first_fr_idx <= first_hr_idx) |
| 527 | ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF; |
| 528 | else |
| 529 | ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF; |
| 530 | } else if (fr_present && !hr_present) |
| 531 | ct->ch_rate_type = GSM0808_SPEECH_FULL_BM; |
| 532 | else if (!fr_present && hr_present) |
| 533 | ct->ch_rate_type = GSM0808_SPEECH_HALF_LM; |
| 534 | else |
| 535 | return -EINVAL; |
| 536 | return 0; |
| 537 | } |
| 538 | |
| 539 | enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec) |
| 540 | { |
| 541 | const struct codec_mapping *m; |
| 542 | foreach_codec_mapping(m) { |
| 543 | if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false)) |
| 544 | return m->mgcp; |
| 545 | } |
| 546 | return NO_MGCP_CODEC; |
| 547 | } |