aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/codec_filter.c
blob: 99ae7b26a2b6777e8f0f0e61492bb7bdbd9b7e87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* Filter/overlay codec selections for a voice call, across MS, RAN and CN limitations */
/*
 * (C) 2019-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * Author: Neels Hofmeyr
 *
 * SPDX-License-Identifier: AGPL-3.0+
 *
 * 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 Affero 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/>.
 */

#include <osmocom/gsm/protocol/gsm_08_08.h>

#include <osmocom/msc/codec_filter.h>
#include <osmocom/msc/codec_mapping.h>
#include <osmocom/msc/debug.h>

/* Add all known payload types encountered in GSM networks */
static void sdp_add_all_geran_codecs(struct sdp_audio_codecs *ac)
{
	/* In order of preference. TODO: make configurable */
	static const enum gsm48_bcap_speech_ver mobile_codecs[] = {
		GSM48_BCAP_SV_AMR_F	/*!< 4   GSM FR V3 (FR AMR) */,
		GSM48_BCAP_SV_AMR_H	/*!< 5   GSM HR V3 (HR_AMR) */,
		GSM48_BCAP_SV_EFR	/*!< 2   GSM FR V2 (GSM EFR) */,
		GSM48_BCAP_SV_FR	/*!< 0   GSM FR V1 (GSM FR) */,
		GSM48_BCAP_SV_HR	/*!< 1   GSM HR V1 (GSM HR) */,
	};
	int i;
	for (i = 0; i < ARRAY_SIZE(mobile_codecs); i++)
		sdp_audio_codecs_add_speech_ver(ac, mobile_codecs[i]);
}

/* Add all known AMR payload types encountered in UTRAN networks */
static void sdp_add_all_utran_codecs(struct sdp_audio_codecs *ac)
{
	/* In order of preference. TODO: make configurable */
	static const enum gsm48_bcap_speech_ver utran_codecs[] = {
		GSM48_BCAP_SV_AMR_F	/*!< 4   GSM FR V3 (FR AMR) */,
		GSM48_BCAP_SV_AMR_H	/*!< 5   GSM HR V3 (HR_AMR) */,
		GSM48_BCAP_SV_AMR_OH	/*!< 11  GSM HR V6 (OHR AMR) */,
		GSM48_BCAP_SV_AMR_FW	/*!< 8   GSM FR V5 (FR AMR-WB) */,
		GSM48_BCAP_SV_AMR_OFW	/*!< 6   GSM FR V4 (OFR AMR-WB) */,
		GSM48_BCAP_SV_AMR_OHW	/*!< 7   GSM HR V4 (OHR AMR-WB) */,
	};
	int i;
	for (i = 0; i < ARRAY_SIZE(utran_codecs); i++)
		sdp_audio_codecs_add_speech_ver(ac, utran_codecs[i]);
}

void codec_filter_set_ran(struct codec_filter *codec_filter, enum osmo_rat_type ran_type)
{
	codec_filter->ran = (struct sdp_audio_codecs){};

	switch (ran_type) {
	default:
	case OSMO_RAT_GERAN_A:
		sdp_add_all_geran_codecs(&codec_filter->ran);
		break;

	case OSMO_RAT_UTRAN_IU:
		sdp_add_all_utran_codecs(&codec_filter->ran);
		break;
	}
}

void codec_filter_set_bss(struct codec_filter *codec_filter,
			  const struct gsm0808_speech_codec_list *codec_list_bss_supported)
{
	codec_filter->bss = (struct sdp_audio_codecs){};
	if (codec_list_bss_supported)
		sdp_audio_codecs_from_speech_codec_list(&codec_filter->bss, codec_list_bss_supported);
}

int codec_filter_set_remote(struct codec_filter *codec_filter, const char *remote_sdp)
{
	return sdp_msg_from_sdp_str(&codec_filter->remote, remote_sdp);
}

void codec_filter_set_local_rtp(struct codec_filter *codec_filter, const struct osmo_sockaddr_str *rtp)
{
	if (!rtp)
		codec_filter->result.rtp = (struct osmo_sockaddr_str){0};
	else
		codec_filter->result.rtp = *rtp;
}

/* Render intersections of all known audio codec constraints to reach a resulting choice of favorite audio codec, plus
 * possible set of alternative audio codecs, in codec_filter->result. (The result.rtp address remains unchanged.) */
int codec_filter_run(struct codec_filter *codec_filter)
{
	struct sdp_audio_codecs *r = &codec_filter->result.audio_codecs;
	struct sdp_audio_codec *a = &codec_filter->assignment;
	*r = codec_filter->ran;
	if (codec_filter->ms.count)
		sdp_audio_codecs_intersection(r, &codec_filter->ms, false);
	if (codec_filter->bss.count)
		sdp_audio_codecs_intersection(r, &codec_filter->bss, false);
	if (codec_filter->remote.audio_codecs.count)
		sdp_audio_codecs_intersection(r, &codec_filter->remote.audio_codecs, true);

#if 0
	/* Future: If osmo-msc were able to trigger a re-assignment after the remote side has picked a codec mismatching
	 * the initial Assignment, then this code here would make sense: keep the other codecs as available to choose
	 * from, but put the currently assigned codec in the first position. So far we only offer the single assigned
	 * codec, because we have no way to deal with the remote side picking a different codec.
	 * Another approach would be to postpone assignment until we know the codecs from the remote side. */
	if (sdp_audio_codec_is_set(a)) {
		/* Assignment has completed, the chosen codec should be the first of the resulting SDP.
		 * Make sure this is actually listed in the result SDP and move to first place. */
		struct sdp_audio_codec *select = sdp_audio_codecs_by_descr(r, a);

		if (!select) {
			/* Not present. Add. */
			if (sdp_audio_codec_by_payload_type(r, a->payload_type, false)) {
				/* Oh crunch, that payload type number is already in use.
				 * Find an unused one. */
				for (a->payload_type = 96; a->payload_type <= 127; a->payload_type++) {
					if (!sdp_audio_codec_by_payload_type(r, a->payload_type, false))
						break;
				}

				if (a->payload_type > 127)
					return -ENOSPC;
			}
			select = sdp_audio_codecs_add_copy(r, a);
		}

		sdp_audio_codecs_select(r, select);
	}
#else
	/* Currently, osmo-msc does not trigger re-assignment if the remote side has picked a codec that is different
	 * from the already assigned codec.
	 * So, if locally, Assignment has already chosen a codec, this is the single definitive result to be used
	 * towards the CN. */
	if (sdp_audio_codec_is_set(a)) {
		/* Assignment has completed, the chosen codec should be the the only possible one. */
		*r = (struct sdp_audio_codecs){};
		sdp_audio_codecs_add_copy(r, a);
	}
#endif
	return 0;
}

int codec_filter_to_str_buf(char *buf, size_t buflen, const struct codec_filter *codec_filter)
{
	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
	OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, &codec_filter->result);
	OSMO_STRBUF_PRINTF(sb, " (from:");

	if (sdp_audio_codec_is_set(&codec_filter->assignment)) {
		OSMO_STRBUF_PRINTF(sb, " assigned=");
		OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, &codec_filter->assignment);
	}

	if (codec_filter->remote.audio_codecs.count
	    || osmo_sockaddr_str_is_nonzero(&codec_filter->remote.rtp)) {
		OSMO_STRBUF_PRINTF(sb, " remote=");
		OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, &codec_filter->remote);
	}

	if (codec_filter->ms.count) {
		OSMO_STRBUF_PRINTF(sb, " MS={");
		OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->ms);
		OSMO_STRBUF_PRINTF(sb, "}");
	}

	if (codec_filter->bss.count) {
		OSMO_STRBUF_PRINTF(sb, " bss={");
		OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->bss);
		OSMO_STRBUF_PRINTF(sb, "}");
	}

	OSMO_STRBUF_PRINTF(sb, " RAN={");
	OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->ran);
	OSMO_STRBUF_PRINTF(sb, "}");

	OSMO_STRBUF_PRINTF(sb, ")");

	return sb.chars_needed;
}

char *codec_filter_to_str_c(void *ctx, const struct codec_filter *codec_filter)
{
	OSMO_NAME_C_IMPL(ctx, 128, "codec_filter_to_str_c-ERROR", codec_filter_to_str_buf, codec_filter)
}

const char *codec_filter_to_str(const struct codec_filter *codec_filter)
{
	return codec_filter_to_str_c(OTC_SELECT, codec_filter);
}