mgcp: Deal with receiving another payload type

In case we get offered G729 and G711 we might have selected
G729 as the audio codec. The first packet we receive might be
G711 though. In that case we will need to change. But only if
we have a matching alternate codec payload_type. E.g. in the
case of comfort noise we will receive the PT=11 and we don't
want to change.
diff --git a/openbsc/src/libmgcp/mgcp_transcode.c b/openbsc/src/libmgcp/mgcp_transcode.c
index e961ba6..38daeb8 100644
--- a/openbsc/src/libmgcp/mgcp_transcode.c
+++ b/openbsc/src/libmgcp/mgcp_transcode.c
@@ -397,11 +397,62 @@
 	return nbytes;
 }
 
+static struct mgcp_rtp_end *source_for_dest(struct mgcp_endpoint *endp,
+					struct mgcp_rtp_end *dst_end)
+{
+	if (&endp->bts_end == dst_end)
+		return &endp->net_end;
+	else if (&endp->net_end == dst_end)
+		return &endp->bts_end;
+	OSMO_ASSERT(0);
+}
+
+/*
+ * With some modems we get offered multiple codecs
+ * and we have selected one of them. It might not
+ * be the right one and we need to detect this with
+ * the first audio packets. One difficulty is that
+ * we patch the rtp payload type in place, so we
+ * need to discuss this.
+ */
+struct mgcp_process_rtp_state *check_transcode_state(
+				struct mgcp_endpoint *endp,
+				struct mgcp_rtp_end *dst_end,
+				struct rtp_hdr *rtp_hdr)
+{
+	struct mgcp_rtp_end *src_end;
+
+	/* Only deal with messages from net to bts */
+	if (&endp->bts_end != dst_end)
+		goto done;
+
+	src_end = source_for_dest(endp, dst_end);
+
+	/* Already patched */
+	if (rtp_hdr->payload_type == dst_end->codec.payload_type)
+		goto done;
+	/* The payload we expect */
+	if (rtp_hdr->payload_type == src_end->codec.payload_type)
+		goto done;
+	/* The matching alternate payload type? Then switch */
+	if (rtp_hdr->payload_type == src_end->alt_codec.payload_type) {
+		struct mgcp_config *cfg = endp->cfg;
+		struct mgcp_rtp_codec tmp_codec = src_end->alt_codec;
+		src_end->alt_codec = src_end->codec;
+		src_end->codec = tmp_codec;
+		cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+		cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+	}
+
+done:
+	return dst_end->rtp_process_data;
+}
+
 int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp,
 				struct mgcp_rtp_end *dst_end,
 			     char *data, int *len, int buf_size)
 {
-	struct mgcp_process_rtp_state *state = dst_end->rtp_process_data;
+	struct mgcp_process_rtp_state *state;
 	const size_t rtp_hdr_size = sizeof(struct rtp_hdr);
 	struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data;
 	char *payload_data = (char *) &rtp_hdr->data[0];
@@ -414,6 +465,7 @@
 	uint32_t ts_no;
 	int rc;
 
+	state = check_transcode_state(endp, dst_end, rtp_hdr);
 	if (!state)
 		return 0;
 
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.c b/openbsc/tests/mgcp/mgcp_transcoding_test.c
index 079f62e..cf679b3 100644
--- a/openbsc/tests/mgcp/mgcp_transcoding_test.c
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.c
@@ -174,6 +174,9 @@
 	tcfg = talloc_zero(cfg, struct mgcp_trunk_config);
 	endp = talloc_zero(tcfg, struct mgcp_endpoint);
 
+	cfg->setup_rtp_processing_cb = mgcp_transcoding_setup;
+	cfg->rtp_processing_cb = mgcp_transcoding_process_rtp;
+	cfg->get_net_downlink_format_cb = mgcp_transcoding_net_downlink_format;
 
 	tcfg->endpoints = endp;
 	tcfg->number_endpoints = 1;
@@ -433,6 +436,63 @@
 	}
 }
 
+static void test_transcode_change(void)
+{
+	char buf[4096] = {0x80, 0};
+	void *ctx;
+
+	struct mgcp_endpoint *endp;
+	struct mgcp_process_rtp_state *state;
+	struct rtp_hdr *hdr;
+
+	int len, res;
+
+	{
+		/* from GSM to PCMA and same ptime */
+		printf("Testing Initial G729->GSM, PCMA->GSM\n");
+		given_configured_endpoint(160, 0, "g729", "gsm", &ctx, &endp);
+		endp->net_end.alt_codec = endp->net_end.codec;
+		endp->net_end.alt_codec.payload_type = audio_name_to_type("pcma");
+		state = endp->bts_end.rtp_process_data;
+
+		/* initial transcoding work */
+		OSMO_ASSERT(state->src_fmt == AF_G729);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 8);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 18);
+
+		/* result */
+		len = audio_packets_pcma[0].len;
+		memcpy(buf, audio_packets_pcma[0].data, len);
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == sizeof(struct rtp_hdr));
+		OSMO_ASSERT(state->sample_cnt == 0);
+		OSMO_ASSERT(state->src_fmt == AF_PCMA);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 18);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 8);
+
+		len = res;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(res == -ENOMSG);
+
+
+		/* now check that comfort noise doesn't change anything */
+		len = audio_packets_pcma[1].len;
+		memcpy(buf, audio_packets_pcma[1].data, len);
+		hdr = (struct rtp_hdr *) buf;
+		hdr->payload_type = 11;
+		res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf));
+		OSMO_ASSERT(state->sample_cnt == 80);
+		OSMO_ASSERT(state->src_fmt == AF_PCMA);
+		OSMO_ASSERT(state->dst_fmt == AF_GSM);
+		OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 18);
+		OSMO_ASSERT(endp->net_end.codec.payload_type == 8);
+
+		talloc_free(ctx);
+	}
+}
+
 static int test_repacking(int in_samples, int out_samples, int no_transcode)
 {
 	char buf[4096] = {0x80, 0};
@@ -582,6 +642,7 @@
 	test_repacking(160, 100, 1);
 	test_rtp_seq_state();
 	test_transcode_result();
+	test_transcode_change();
 
 	return 0;
 }
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.ok b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
index 7c1c8ce..5cfc289 100644
--- a/openbsc/tests/mgcp/mgcp_transcoding_test.ok
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
@@ -536,3 +536,4 @@
 generating 160 pcma input samples
 got 1 pcma output frames (80 octets) count=12
 got 1 pcma output frames (80 octets) count=12
+Testing Initial G729->GSM, PCMA->GSM