mgcp/rtp: Fix timestamp offset when patching RTP packets

The current implementation increments the seqno but does not increment
the RTP timestamp, leading to two identical timestamps following one
after the other.

This patch fixes this by adding the computed tsdelta when the offset
is calulated. In the unlikely case, that a tsdelta hasn't been
computed yet when the SSRC changes, a tsdelta is computed based on
the RTP rate and a RTP packet duration of 20ms (one speech frame per
channel and packet). If the RTP rate is not known, a rate of 8000 is
assumed.

Note that this approach presumes, that the per RTP packet duration
(in samples) is the same for the last two packets of the stream being
replaced (the first one).

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
index 8b6a56b..0b52c1c 100644
--- a/openbsc/include/openbsc/mgcp_internal.h
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -77,6 +77,10 @@
 
 	/* per endpoint data */
 	int payload_type;
+	uint32_t rate;
+	uint32_t frame_duration_num;
+	uint32_t frame_duration_den;
+	int  frames_per_packet;
 	char *fmtp_extra;
 
 	/*
diff --git a/openbsc/src/libmgcp/mgcp_network.c b/openbsc/src/libmgcp/mgcp_network.c
index 72d0a5c..75d39c1 100644
--- a/openbsc/src/libmgcp/mgcp_network.c
+++ b/openbsc/src/libmgcp/mgcp_network.c
@@ -238,15 +238,30 @@
 		state->transit = arrival_time - timestamp;
 		state->out_stream = state->in_stream;
 	} else if (state->in_stream.ssrc != rtp_hdr->ssrc) {
+		int32_t tsdelta = state->out_stream.last_tsdelta;
+		if (tsdelta == 0) {
+			tsdelta = rtp_end->rate * rtp_end->frames_per_packet *
+				rtp_end->frame_duration_num /
+				rtp_end->frame_duration_den;
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "Computed timestamp delta %d based on "
+			     "rate %d, num frames %d, frame duration %d/%d\n",
+			     tsdelta, rtp_end->rate, rtp_end->frames_per_packet,
+			     rtp_end->frame_duration_num,
+			     rtp_end->frame_duration_den);
+		}
 		state->in_stream.ssrc = rtp_hdr->ssrc;
 		state->seq_offset = (state->out_stream.last_seq + 1) - seq;
-		state->timestamp_offset = state->out_stream.last_timestamp - timestamp;
+		state->timestamp_offset =
+			(state->out_stream.last_timestamp + tsdelta) - timestamp;
 		state->patch = endp->allow_patch;
 		LOGP(DMGCP, LOGL_NOTICE,
-			"The SSRC changed on 0x%x SSRC: %u offset: %d from %s:%d in %d\n",
+			"The SSRC changed on 0x%x SSRC: %u offset: %d tsdelta: %d "
+			"from %s:%d in %d\n",
 			ENDPOINT_NUMBER(endp), state->in_stream.ssrc,
-			state->seq_offset, inet_ntoa(addr->sin_addr),
-			ntohs(addr->sin_port), endp->conn_mode);
+			state->seq_offset, tsdelta,
+			inet_ntoa(addr->sin_addr), ntohs(addr->sin_port),
+			endp->conn_mode);
 
 		state->in_stream.last_tsdelta = 0;
 	} else {
diff --git a/openbsc/src/libmgcp/mgcp_protocol.c b/openbsc/src/libmgcp/mgcp_protocol.c
index aec2cb0..7096495 100644
--- a/openbsc/src/libmgcp/mgcp_protocol.c
+++ b/openbsc/src/libmgcp/mgcp_protocol.c
@@ -40,6 +40,12 @@
 	for (line = strtok_r(NULL, "\r\n", &save); line;\
 	     line = strtok_r(NULL, "\r\n", &save))
 
+/* Assume audio frame length of 20ms */
+#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20
+#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000
+#define DEFAULT_RTP_AUDIO_FRAMES_PER_PACKET 1
+#define DEFAULT_RTP_AUDIO_DEFAULT_RATE  8000
+
 static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
 
 struct mgcp_parse_data {
@@ -965,6 +971,12 @@
 	end->local_alloc = -1;
 	talloc_free(end->fmtp_extra);
 	end->fmtp_extra = NULL;
+
+	/* Set default values */
+	end->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+	end->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+	end->frames_per_packet  = DEFAULT_RTP_AUDIO_FRAMES_PER_PACKET;
+	end->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
 }
 
 static void mgcp_rtp_end_init(struct mgcp_rtp_end *end)
diff --git a/openbsc/tests/mgcp/mgcp_test.c b/openbsc/tests/mgcp/mgcp_test.c
index b2d20b4..d36aaa8 100644
--- a/openbsc/tests/mgcp/mgcp_test.c
+++ b/openbsc/tests/mgcp/mgcp_test.c
@@ -417,7 +417,7 @@
 	struct mgcp_trunk_config trunk;
 	struct mgcp_endpoint endp;
 	struct mgcp_rtp_state state;
-	struct mgcp_rtp_end rtp;
+	struct mgcp_rtp_end *rtp = &endp.net_end;
 	struct sockaddr_in addr = {0};
 	char buffer[4096];
 
@@ -426,15 +426,21 @@
 	memset(&trunk, 0, sizeof(trunk));
 	memset(&endp, 0, sizeof(endp));
 	memset(&state, 0, sizeof(state));
-	memset(&rtp, 0, sizeof(rtp));
 
 	trunk.number_endpoints = 1;
 	trunk.endpoints = &endp;
-
-	rtp.payload_type = 98;
-	endp.allow_patch = 1;
 	endp.tcfg = &trunk;
 
+	/* This doesn't free endp but resets/frees all fields of the structure
+	 * and invokes mgcp_rtp_end_reset() for each mgcp_rtp_end. OTOH, it
+	 * expects valid pointer fields (either NULL or talloc'ed), so the
+	 * memset is still needed. It also requires that endp.tcfg and
+	 * trunk.endpoints are set up properly. */
+	mgcp_free_endp(&endp);
+
+	rtp->payload_type = 98;
+	endp.allow_patch = 1;
+
 	for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) {
 		struct rtp_packet_info *info = test_rtp_packets1 + i;
 
@@ -442,7 +448,7 @@
 		OSMO_ASSERT(info->len >= 0);
 		memmove(buffer, info->data, info->len);
 
-		mgcp_patch_and_count(&endp, &state, &rtp, &addr,
+		mgcp_patch_and_count(&endp, &state, rtp, &addr,
 				     buffer, info->len);
 
 		printf("TS: %d, dTS: %d, TS Errs: in %d, out %d\n",
diff --git a/openbsc/tests/mgcp/mgcp_test.ok b/openbsc/tests/mgcp/mgcp_test.ok
index 8c3fa26..5666424 100644
--- a/openbsc/tests/mgcp/mgcp_test.ok
+++ b/openbsc/tests/mgcp/mgcp_test.ok
@@ -41,7 +41,7 @@
 TS: 1400, dTS: 120, TS Errs: in 4, out 4
 TS: 1560, dTS: 160, TS Errs: in 5, out 5
 TS: 1720, dTS: 160, TS Errs: in 5, out 5
-TS: 1720, dTS: 160, TS Errs: in 5, out 6
-TS: 1880, dTS: 160, TS Errs: in 5, out 6
-TS: 2040, dTS: 160, TS Errs: in 5, out 6
+TS: 1880, dTS: 160, TS Errs: in 5, out 5
+TS: 2040, dTS: 160, TS Errs: in 5, out 5
+TS: 2200, dTS: 160, TS Errs: in 5, out 5
 Done