rtp_stream: allow multiple codecs / use codec filter from Assignment

Allow configuring MGW conns with multiple codecs. The new codecs filter
can have multiple results, and MGCP can configure multiple codecs. Get
rid of this bottleneck, that so far limits to a single codec to MGW.

On Assignment Complete, set codec_filter.assignment to the assigned
codec, and use that to set the resulting codec (possibly multiple codecs
in the future) to create the CN side MGW endpoint.

Related: SYS#5066
Change-Id: If9c67b298b30f893ec661f84c9fc622ad01b5ee5
diff --git a/src/libmsc/call_leg.c b/src/libmsc/call_leg.c
index 03c9882..a8c5c41 100644
--- a/src/libmsc/call_leg.c
+++ b/src/libmsc/call_leg.c
@@ -324,14 +324,15 @@
 /* Make sure an MGW endpoint CI is set up for an RTP connection.
  * This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one.
  * If not yet present, allocate the rtp_stream for the given direction.
- * Then, call rtp_stream_set_codec() if codec_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
+ * Then, call rtp_stream_set_codecs() if codecs_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
  * remote_addr_if_known is non-NULL.
  * Finally make sure that a CRCX is sent out for this direction, if this has not already happened.
  * If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an
  * MDCX.
  */
 int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans,
-		       const enum mgcp_codecs *codec_if_known, const struct osmo_sockaddr_str *remote_addr_if_known)
+		       const struct sdp_audio_codecs *codecs_if_known,
+		       const struct osmo_sockaddr_str *remote_addr_if_known)
 {
 	if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans))
 		return -EIO;
@@ -340,8 +341,8 @@
 		cl->rtp[dir]->use_osmux = true;
 		cl->rtp[dir]->remote_osmux_cid = -1; /* wildcard */
 	}
-	if (codec_if_known)
-		rtp_stream_set_codec(cl->rtp[dir], *codec_if_known);
+	if (codecs_if_known)
+		rtp_stream_set_codecs(cl->rtp[dir], codecs_if_known);
 	if (remote_addr_if_known && osmo_sockaddr_str_is_nonzero(remote_addr_if_known))
 		rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known);
 	return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint);
@@ -350,25 +351,25 @@
 int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1,
 			  struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2)
 {
-	enum mgcp_codecs codec;
+	struct sdp_audio_codecs *codecs;
 
 	cl1->local_bridge = cl2;
 	cl2->local_bridge = cl1;
 
 	/* We may just copy the codec info we have for the RAN side of the first leg to the CN side of both legs. This
 	 * also means that if both legs use different codecs the MGW must perform transcoding on the second leg. */
-	if (!cl1->rtp[RTP_TO_RAN] || !cl1->rtp[RTP_TO_RAN]->codec_known) {
+	if (!cl1->rtp[RTP_TO_RAN] || !cl1->rtp[RTP_TO_RAN]->codecs_known) {
 		LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side RTP stream codec is not known, not ready for bridging\n");
 		return -EINVAL;
 	}
-	codec = cl1->rtp[RTP_TO_RAN]->codec;
+	codecs = &cl1->rtp[RTP_TO_RAN]->codecs;
 
 	if (!cl1->rtp[RTP_TO_CN] || !cl2->rtp[RTP_TO_CN])
 		return -ENOTCONN;
 
 	call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1,
-			   &codec, &cl2->rtp[RTP_TO_CN]->local);
+			   codecs, &cl2->rtp[RTP_TO_CN]->local);
 	call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2,
-			   &codec, &cl1->rtp[RTP_TO_CN]->local);
+			   codecs, &cl1->rtp[RTP_TO_CN]->local);
 	return 0;
 }
diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c
index 4a484bb..269fd57 100644
--- a/src/libmsc/gsm_04_08_cc.c
+++ b/src/libmsc/gsm_04_08_cc.c
@@ -1822,18 +1822,18 @@
 		return -EINVAL;
 	}
 
-	if (!rtp_cn->codec_known) {
+	if (!rtp_cn->codecs_known) {
 		LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR,
 			      "Cannot RTP CREATE to MNCC, no codec set up for the RTP CN side\n");
 		return -EINVAL;
 	}
 
 	/* Codec */
-	m = codec_mapping_by_mgcp_codec(rtp_cn->codec);
+	m = codec_mapping_by_subtype_name(rtp_cn->codecs.codec[0].subtype_name);
 	if (!m) {
 		LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR,
 			      "Cannot RTP CREATE to MNCC, cannot resolve codec '%s'\n",
-			      osmo_mgcpc_codec_name(rtp_cn->codec));
+			      sdp_audio_codec_to_str(&rtp_cn->codecs.codec[0]));
 		return -EINVAL;
 	}
 	payload_msg_type = m->mncc_payload_msg_type;
@@ -1841,9 +1841,9 @@
 	/* Payload Type number */
 	mgcp_info = osmo_mgcpc_ep_ci_get_rtp_info(rtp_cn->ci);
 	if (mgcp_info && mgcp_info->ptmap_len)
-		payload_type = map_codec_to_pt(mgcp_info->ptmap, mgcp_info->ptmap_len, rtp_cn->codec);
+		payload_type = map_codec_to_pt(mgcp_info->ptmap, mgcp_info->ptmap_len, m->mgcp);
 	else
-		payload_type = rtp_cn->codec;
+		payload_type = m->mgcp;
 
 	rtp_cn_local = call_leg_local_ip(cl, RTP_TO_CN);
 	if (!rtp_cn_local) {
diff --git a/src/libmsc/mncc_call.c b/src/libmsc/mncc_call.c
index fbf96f3..1cf0c3d 100644
--- a/src/libmsc/mncc_call.c
+++ b/src/libmsc/mncc_call.c
@@ -263,14 +263,14 @@
 		return true;
 	}
 
-	if (!mncc_call->rtps->codec_known) {
+	if (!mncc_call->rtps->codecs_known) {
 		LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no codec set\n");
 		return true;
 	}
 
 	LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, responding with " OSMO_SOCKADDR_STR_FMT " %s\n",
 		      OSMO_SOCKADDR_STR_FMT_ARGS(&mncc_call->rtps->local),
-		      osmo_mgcpc_codec_name(mncc_call->rtps->codec));
+		      sdp_audio_codecs_to_str(&mncc_call->rtps->codecs));
 	/* Already know what RTP IP:port to tell the MNCC. Send it. */
 	return mncc_call_tx_rtp_create(mncc_call);
 }
@@ -295,15 +295,16 @@
 		return false;
 	}
 
-	if (mncc_call->rtps->codec_known) {
-		const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mncc_call->rtps->codec);
+	if (mncc_call->rtps->codecs_known) {
+		struct sdp_audio_codec *codec = &mncc_call->rtps->codecs.codec[0];
+		const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name);
 
 		if (!m) {
 			mncc_call_error(mncc_call, "Failed to resolve audio codec '%s'\n",
-					osmo_mgcpc_codec_name(mncc_call->rtps->codec));
+					sdp_audio_codec_to_str(codec));
 			return false;
 		}
-		mncc_msg.rtp.payload_type = m->sdp.payload_type;
+		mncc_msg.rtp.payload_type = codec->payload_type;
 		mncc_msg.rtp.payload_msg_type = m->mncc_payload_msg_type;
 	}
 
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index 7db1d0a..e9f1840 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -1327,6 +1327,7 @@
 	struct rtp_stream *rtps_to_ran = msc_a->cc.call_leg ? msc_a->cc.call_leg->rtp[RTP_TO_RAN] : NULL;
 	const enum mgcp_codecs *codec_if_known = ac->assignment_complete.codec_present ?
 							&ac->assignment_complete.codec : NULL;
+	const struct codec_mapping *codec_cn = NULL;
 
 	if (!rtps_to_ran) {
 		LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but no RTP stream is set up\n");
@@ -1344,9 +1345,26 @@
 		return;
 	}
 
-	/* Update RAN-side endpoint CI: */
-	if (codec_if_known)
-		rtp_stream_set_codec(rtps_to_ran, *codec_if_known);
+	if (codec_if_known) {
+		codec_cn = codec_mapping_by_mgcp_codec(*codec_if_known);
+		if (!codec_cn) {
+			LOG_TRANS(cc_trans, LOGL_ERROR, "Unknown codec in Assignment Complete: %s\n",
+				  osmo_mgcpc_codec_name(*codec_if_known));
+			call_leg_release(msc_a->cc.call_leg);
+			return;
+		}
+
+		/* Update RAN-side endpoint CI from Assignment result */
+		rtp_stream_set_one_codec(rtps_to_ran, &codec_cn->sdp);
+
+		/* Update codec filter with Assignment result, for the CN side */
+		cc_trans->cc.codecs.assignment = codec_cn->sdp;
+	} else {
+		/* No codec passed in Assignment Complete, set 'codecs.assignment' to none. */
+		cc_trans->cc.codecs.assignment = (struct sdp_audio_codec){};
+		LOG_TRANS(cc_trans, LOGL_INFO, "Assignment Complete without voice codec\n");
+	}
+
 	rtp_stream_set_remote_addr(rtps_to_ran, &ac->assignment_complete.remote_rtp);
 	if (rtps_to_ran->use_osmux)
 		rtp_stream_set_remote_osmux_cid(rtps_to_ran,
@@ -1364,8 +1382,9 @@
 	 *   endpoint,
 	 * - the Assignment has chosen a speech codec
 	 * go on to create the CN side RTP stream's CI. */
+	codec_filter_run(&cc_trans->cc.codecs);
 	if (call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_CN, cc_trans->callref, cc_trans,
-			       codec_if_known, NULL)) {
+			       &cc_trans->cc.codecs.result.audio_codecs, NULL)) {
 		LOG_MSC_A_CAT(msc_a, DCC, LOGL_ERROR, "Error creating MGW CI towards CN\n");
 		call_leg_release(msc_a->cc.call_leg);
 		return;
@@ -1760,11 +1779,13 @@
 	struct call_leg *cl = msc_a->cc.call_leg;
 	struct msc_i *msc_i = msc_a_msc_i(msc_a);
 	struct gsm_network *net = msc_a_net(msc_a);
-	enum mgcp_codecs codec, *codec_ptr;
+	struct sdp_audio_codecs *codecs;
 
 	OSMO_ASSERT(!msc_a->cc.active_trans);
 	msc_a->cc.active_trans = cc_trans;
 
+	cc_trans->cc.codecs.assignment = (struct sdp_audio_codec){};
+
 	OSMO_ASSERT(cc_trans && cc_trans->type == TRANS_CC);
 
 	if (!cl) {
@@ -1785,20 +1806,17 @@
 		}
 	}
 
-	/* This will lead to either MSC_EV_CALL_LEG_LOCAL_ADDR_AVAILABLE or MSC_EV_CALL_LEG_TERM.
-	 * If the local address is already known, then immediately trigger. */
+	/* Make sure an MGW endpoint towards RAN is present. If it is already set up, "skip" to
+	 * MSC_EV_CALL_LEG_LOCAL_ADDR_AVAILABLE immediately. If not, set it up. */
 	if (call_leg_local_ip(cl, RTP_TO_RAN))
 		return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_RAN]);
 
-	if (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU) {
-		/* FUTURE: ran_infra->force_mgw_codecs_to_ran is intended to be used here instead of the special
-		 * condition on OSMO_RAT_UTRAN_IU and the mgcp_codecs value CODEC_IUFP */
-		codec = CODEC_IUFP;
-		codec_ptr = &codec;
-	} else {
-		codec_ptr = NULL;
-	}
-	return call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_RAN, cc_trans->callref, cc_trans, codec_ptr, NULL);
+	codec_filter_run(&cc_trans->cc.codecs);
+	if (msc_a->c.ran->force_mgw_codecs_to_ran.count)
+		codecs = &msc_a->c.ran->force_mgw_codecs_to_ran;
+	else
+		codecs = &cc_trans->cc.codecs.result.audio_codecs;
+	return call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_RAN, cc_trans->callref, cc_trans, codecs, NULL);
 }
 
 int msc_a_try_call_assignment(struct gsm_trans *cc_trans)
diff --git a/src/libmsc/msc_ho.c b/src/libmsc/msc_ho.c
index 7e7905b..d53bb9e 100644
--- a/src/libmsc/msc_ho.c
+++ b/src/libmsc/msc_ho.c
@@ -39,6 +39,7 @@
 #include <osmocom/msc/call_leg.h>
 #include <osmocom/msc/rtp_stream.h>
 #include <osmocom/msc/mncc_call.h>
+#include <osmocom/msc/codec_mapping.h>
 
 struct osmo_fsm msc_ho_fsm;
 
@@ -570,7 +571,7 @@
 
 	/* Backup old cell's RTP IP:port and codec data */
 	msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
-	msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+	msc_a->ho.old_cell.codecs = rtp_to_ran->codecs;
 
 	/* Blindly taken over from an MNCC trace of existing code: send an all-zero CCCAP: */
 	outgoing_call_req.fields |= MNCC_F_CCCAP;
@@ -707,7 +708,7 @@
 
 	/* Backup old cell's RTP IP:port and codec data */
 	msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
-	msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+	msc_a->ho.old_cell.codecs = rtp_to_ran->codecs;
 
 	LOG_HO(msc_a, LOGL_DEBUG, "Switching RTP stream to new cell: from " OSMO_SOCKADDR_STR_FMT " to " OSMO_SOCKADDR_STR_FMT "\n",
 	       OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.old_cell.ran_remote_rtp),
@@ -726,10 +727,17 @@
 
 	/* Switch over to the new peer */
 	rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.new_cell.ran_remote_rtp);
-	if (msc_a->ho.new_cell.codec_present)
-		rtp_stream_set_codec(rtp_to_ran, msc_a->ho.new_cell.codec);
-	else
+	if (msc_a->ho.new_cell.codec_present) {
+		struct sdp_audio_codecs codecs = {};
+		if (!sdp_audio_codecs_add_mgcp_codec(&codecs, msc_a->ho.new_cell.codec)) {
+			LOG_HO(msc_a, LOGL_ERROR,
+			       "Cannot resolve codec: %s\n", osmo_mgcpc_codec_name(msc_a->ho.new_cell.codec));
+		} else {
+			rtp_stream_set_codecs(rtp_to_ran, &codecs);
+		}
+	} else {
 		LOG_HO(msc_a, LOGL_ERROR, "No codec is set\n");
+	}
 	rtp_stream_commit(rtp_to_ran);
 }
 
@@ -768,7 +776,7 @@
 
 	/* Switch back to the old cell */
 	rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.old_cell.ran_remote_rtp);
-	rtp_stream_set_codec(rtp_to_ran, msc_a->ho.old_cell.codec);
+	rtp_stream_set_codecs(rtp_to_ran, &msc_a->ho.old_cell.codecs);
 	rtp_stream_commit(rtp_to_ran);
 }
 
diff --git a/src/libmsc/msc_t.c b/src/libmsc/msc_t.c
index 43bc74e..787d736 100644
--- a/src/libmsc/msc_t.c
+++ b/src/libmsc/msc_t.c
@@ -450,9 +450,9 @@
 		if (r->codec_present) {
 			LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got %s\n",
 				  osmo_mgcpc_codec_name(r->codec));
-			rtp_stream_set_codec(rtp_ran, r->codec);
+			rtp_stream_set_codecs_from_mgcp_codec(rtp_ran, r->codec);
 			if (rtp_cn)
-				rtp_stream_set_codec(rtp_cn, r->codec);
+				rtp_stream_set_codecs_from_mgcp_codec(rtp_cn, r->codec);
 		} else {
 			LOG_MSC_T(msc_t, LOGL_DEBUG, "No codec in Handover Request Ack\n");
 		}
diff --git a/src/libmsc/rtp_stream.c b/src/libmsc/rtp_stream.c
index b7e7381..ac44414 100644
--- a/src/libmsc/rtp_stream.c
+++ b/src/libmsc/rtp_stream.c
@@ -28,6 +28,7 @@
 #include <osmocom/msc/transaction.h>
 #include <osmocom/msc/call_leg.h>
 #include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/codec_mapping.h>
 
 #define LOG_RTPS(rtps, level, fmt, args...) \
 	LOGPFSML(rtps->fi, level, fmt, ##args)
@@ -78,10 +79,10 @@
 			OSMO_STRBUF_PRINTF(sb, ":no-remote-port");
 		else if (!rtps->remote_sent_to_mgw)
 			OSMO_STRBUF_PRINTF(sb, ":remote-port-not-sent");
-		if (!rtps->codec_known)
-			OSMO_STRBUF_PRINTF(sb, ":no-codec");
-		else if (!rtps->codec_sent_to_mgw)
-			OSMO_STRBUF_PRINTF(sb, ":codec-not-sent");
+		if (!rtps->codecs_known)
+			OSMO_STRBUF_PRINTF(sb, ":no-codecs");
+		else if (!rtps->codecs_sent_to_mgw)
+			OSMO_STRBUF_PRINTF(sb, ":codecs-not-sent");
 		if (rtps->use_osmux) {
 			if (rtps->remote_osmux_cid < 0)
 				OSMO_STRBUF_PRINTF(sb, ":no-remote-osmux-cid");
@@ -141,7 +142,7 @@
 	    && osmo_sockaddr_str_is_nonzero(&rtps->remote)
 	    && rtps->remote_sent_to_mgw
 	    && (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw)
-	    && rtps->codec_known)
+	    && rtps->codecs_known)
 		rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHED);
 }
 
@@ -171,14 +172,14 @@
 		osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE, rtps);
 		check_established(rtps);
 
-		if ((!rtps->remote_sent_to_mgw || !rtps->codec_sent_to_mgw)
+		if ((!rtps->remote_sent_to_mgw || !rtps->codecs_sent_to_mgw)
 		    && osmo_sockaddr_str_is_nonzero(&rtps->remote)
 		    && (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw)
-		    && rtps->codec_known) {
+		    && rtps->codecs_known) {
 			LOG_RTPS(rtps, LOGL_DEBUG,
 				 "local ip:port set;%s%s%s triggering MDCX to send the new settings\n",
-				 (!rtps->remote_sent_to_mgw)? " remote ip:port not yet sent," : "",
-				 (!rtps->codec_sent_to_mgw)? " codec not yet sent," : "",
+				 (!rtps->remote_sent_to_mgw) ? " remote ip:port not yet sent," : "",
+				 (!rtps->codecs_sent_to_mgw) ? " codecs not yet sent," : "",
 				 (rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw) ? "Osmux CID not yet sent,": "");
 			rtp_stream_do_mdcx(rtps);
 		}
@@ -192,7 +193,7 @@
 	case RTP_STREAM_EV_CRCX_FAIL:
 	case RTP_STREAM_EV_MDCX_FAIL:
 		rtps->remote_sent_to_mgw = false;
-		rtps->codec_sent_to_mgw = false;
+		rtps->codecs_sent_to_mgw = false;
 		rtps->remote_osmux_cid_sent_to_mgw = false;
 		rtp_stream_update_id(rtps);
 		rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING);
@@ -310,10 +311,28 @@
 	if (verb == MGCP_VERB_CRCX)
 		verb_info.conn_mode = rtps->crcx_conn_mode;
 
-	if (rtps->codec_known) {
-		verb_info.codecs[0] = rtps->codec;
-		verb_info.codecs_len = 1;
-		rtps->codec_sent_to_mgw = true;
+	if (rtps->codecs_known) {
+		/* Send the list of codecs to the MGW. Ideally we would just feed the SDP directly, but for legacy
+		 * reasons we still need to translate to a struct mgcp_conn_peer representation to send it. */
+		struct sdp_audio_codec *codec;
+		int i = 0;
+		foreach_sdp_audio_codec(codec, &rtps->codecs) {
+			const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name);
+			if (!m) {
+				LOG_RTPS(rtps, LOGL_ERROR, "Cannot map codec '%s' to MGCP: codec is unknown\n",
+					 codec->subtype_name);
+				continue;
+			}
+			verb_info.codecs[i] = m->mgcp;
+			verb_info.ptmap[i] = (struct ptmap){
+				.codec = m->mgcp,
+				.pt = codec->payload_type,
+			};
+			i++;
+			verb_info.codecs_len = i;
+			verb_info.ptmap_len = i;
+		}
+		rtps->codecs_sent_to_mgw = true;
 	}
 	if (osmo_sockaddr_str_is_nonzero(&rtps->remote)) {
 		int rc = osmo_strlcpy(verb_info.addr, rtps->remote.ip, sizeof(verb_info.addr));
@@ -368,12 +387,12 @@
 		LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no remote RTP address known\n");
 		return -1;
 	}
-	if (!rtps->codec_known) {
-		LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codec known\n");
+	if (!rtps->codecs_known) {
+		LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codecs known\n");
 		return -1;
 	}
-	if (rtps->remote_sent_to_mgw && rtps->codec_sent_to_mgw) {
-		LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: both remote RTP address and codec already set up at MGW\n");
+	if (rtps->remote_sent_to_mgw && rtps->codecs_sent_to_mgw) {
+		LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: both remote RTP address and codecs already set up at MGW\n");
 		return 0;
 	}
 	if (!rtps->ci) {
@@ -383,22 +402,47 @@
 
 	LOG_RTPS(rtps, LOGL_DEBUG, "Committing: Tx MDCX to update the MGW: updating%s%s%s\n",
 		 rtps->remote_sent_to_mgw ? "" : " remote-RTP-IP-port",
-		 rtps->codec_sent_to_mgw ? "" : " codec",
+		 rtps->codecs_sent_to_mgw ? "" : " codecs",
 		 (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw) ? "" : " remote-Osmux-CID");
 	return rtp_stream_do_mdcx(rtps);
 }
 
-void rtp_stream_set_codec(struct rtp_stream *rtps, enum mgcp_codecs codec)
+void rtp_stream_set_codecs(struct rtp_stream *rtps, const struct sdp_audio_codecs *codecs)
 {
+	if (!codecs || !codecs->count)
+		return;
+	if (sdp_audio_codecs_cmp(&rtps->codecs, codecs, false, true) == 0) {
+		LOG_RTPS(rtps, LOGL_DEBUG, "no change: codecs already set to %s\n",
+			 sdp_audio_codecs_to_str(&rtps->codecs));
+		return;
+	}
 	if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED)
 		rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING);
-	LOG_RTPS(rtps, LOGL_DEBUG, "setting codec to %s\n", osmo_mgcpc_codec_name(codec));
-	rtps->codec = codec;
-	rtps->codec_known = true;
-	rtps->codec_sent_to_mgw = false;
+	LOG_RTPS(rtps, LOGL_DEBUG, "setting codecs to %s\n", sdp_audio_codecs_to_str(codecs));
+	rtps->codecs = *codecs;
+	rtps->codecs_known = true;
+	rtps->codecs_sent_to_mgw = false;
 	rtp_stream_update_id(rtps);
 }
 
+/* Convenience shortcut to call rtp_stream_set_codecs() with a list of only one sdp_audio_codec record. */
+void rtp_stream_set_one_codec(struct rtp_stream *rtps, const struct sdp_audio_codec *codec)
+{
+	struct sdp_audio_codecs codecs = {};
+	sdp_audio_codecs_add_copy(&codecs, codec);
+	rtp_stream_set_codecs(rtps, &codecs);
+}
+
+/* For legacy, rather use rtp_stream_set_codecs() with a full codecs list. */
+bool rtp_stream_set_codecs_from_mgcp_codec(struct rtp_stream *rtps, enum mgcp_codecs codec)
+{
+	struct sdp_audio_codecs codecs = {};
+	if (!sdp_audio_codecs_add_mgcp_codec(&codecs, codec))
+		return false;
+	rtp_stream_set_codecs(rtps, &codecs);
+	return true;
+}
+
 void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_sockaddr_str *r)
 {
 	if (osmo_sockaddr_str_cmp(&rtps->remote, r) == 0) {
@@ -433,7 +477,7 @@
 	if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED)
 		return false;
 	if (!rtps->remote_sent_to_mgw
-	    || !rtps->codec_sent_to_mgw
+	    || !rtps->codecs_sent_to_mgw
 	    || (rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw))
 		return false;
 	return true;