Merge remote-tracking branch 'origin/jerlbeck/features/mgcp-transcoding'
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index 26a7b62..72f6e6a 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -57,6 +57,21 @@
 AM_CONDITIONAL(BUILD_SMPP, test "x$osmo_ac_build_smpp" = "xyes")
 AC_SUBST(osmo_ac_build_smpp)
 
+# Enable/disable transcoding within osmo-bsc_mgcp?
+AC_ARG_ENABLE([mgcp-transcoding], [AS_HELP_STRING([--enable-mgcp-transcoding], [Build the MGCP gateway with internal transcoding enabled.])],
+    [osmo_ac_mgcp_transcoding="$enableval"],[osmo_ac_mgcp_transcoding="no"])
+AC_ARG_WITH([g729], [AS_HELP_STRING([--with-g729], [Enable G.729 encoding/decoding.])], [osmo_ac_with_g729="$withval"],[osmo_ac_with_g729="no"])
+
+if test "$osmo_ac_mgcp_transcoding" = "yes" ; then
+    AC_SEARCH_LIBS(gsm_create, gsm)
+    if test "$osmo_ac_with_g729" = "yes" ; then
+	PKG_CHECK_MODULES(LIBBCG729, libbcg729 >= 0.1, [AC_DEFINE([HAVE_BCG729], [1], [Use bgc729 decoder/encoder])])
+    fi
+    AC_DEFINE(BUILD_MGCP_TRANSCODING, 1, [Define if we want to build the MGCP gateway with transcoding support])
+fi
+AM_CONDITIONAL(BUILD_MGCP_TRANSCODING, test "x$osmo_ac_mgcp_transcoding" = "xyes")
+AC_SUBST(osmo_ac_mgcp_transcoding)
+
 
 found_libgtp=yes
 PKG_CHECK_MODULES(LIBGTP, libgtp, , found_libgtp=no)
diff --git a/openbsc/contrib/testconv/Makefile b/openbsc/contrib/testconv/Makefile
new file mode 100644
index 0000000..bb856f7
--- /dev/null
+++ b/openbsc/contrib/testconv/Makefile
@@ -0,0 +1,16 @@
+
+OBJS = testconv_main.o
+
+CC = gcc
+CFLAGS = -O0 -ggdb -Wall
+LDFLAGS =
+CPPFLAGS = -I../.. -I../../include $(shell pkg-config --cflags libosmocore) $(shell pkg-config --cflags libbcg729)
+LIBS =  ../../src/libmgcp/libmgcp.a ../../src/libcommon/libcommon.a $(shell pkg-config --libs libosmocore) $(shell pkg-config --libs libbcg729) -lgsm -lrt
+
+testconv: $(OBJS)
+	$(CC)  -o $@ $^ $(LDFLAGS) $(LIBS)
+
+testconv_main.o: testconv_main.c
+
+$(OBJS):
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
diff --git a/openbsc/contrib/testconv/testconv_main.c b/openbsc/contrib/testconv/testconv_main.c
new file mode 100644
index 0000000..89dce1a
--- /dev/null
+++ b/openbsc/contrib/testconv/testconv_main.c
@@ -0,0 +1,133 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include "bscconfig.h"
+#ifndef BUILD_MGCP_TRANSCODING
+#error "Requires MGCP transcoding enabled (see --enable-mgcp-transcoding)"
+#endif
+
+#include "openbsc/mgcp_transcode.h"
+
+static int audio_name_to_type(const char *name)
+{
+	if (!strcasecmp(name, "gsm"))
+		return 3;
+#ifdef HAVE_BCG729
+	else if (!strcasecmp(name, "g729"))
+		return 18;
+#endif
+	else if (!strcasecmp(name, "pcma"))
+		return 8;
+	else if (!strcasecmp(name, "l16"))
+		return 11;
+	return -1;
+}
+
+int mgcp_get_trans_frame_size(void *state_, int nsamples, int dst);
+
+int main(int argc, char **argv)
+{
+	char buf[4096] = {0x80, 0};
+	int cc, rc;
+	struct mgcp_rtp_end *dst_end;
+	struct mgcp_rtp_end *src_end;
+	struct mgcp_trunk_config tcfg = {{0}};
+	struct mgcp_endpoint endp = {0};
+	struct mgcp_process_rtp_state *state;
+	int in_size;
+	int in_samples = 160;
+	int out_samples = 0;
+	uint32_t ts = 0;
+	uint16_t seq = 0;
+
+	osmo_init_logging(&log_info);
+
+	tcfg.endpoints = &endp;
+	tcfg.number_endpoints = 1;
+	endp.tcfg = &tcfg;
+	mgcp_free_endp(&endp);
+
+	dst_end = &endp.bts_end;
+	src_end = &endp.net_end;
+
+	if (argc <= 2)
+		errx(1, "Usage: {gsm|g729|pcma|l16} {gsm|g729|pcma|l16} [SPP]");
+
+	if ((src_end->payload_type = audio_name_to_type(argv[1])) == -1)
+		errx(1, "invalid input format '%s'", argv[1]);
+	if ((dst_end->payload_type = audio_name_to_type(argv[2])) == -1)
+		errx(1, "invalid output format '%s'", argv[2]);
+	if (argc > 3)
+		out_samples = atoi(argv[3]);
+
+	if (out_samples) {
+		dst_end->frame_duration_den = dst_end->rate;
+		dst_end->frame_duration_num = out_samples;
+		dst_end->frames_per_packet = 1;
+	}
+
+	rc = mgcp_transcoding_setup(&endp, dst_end, src_end);
+	if (rc < 0)
+		errx(1, "setup failed: %s", strerror(-rc));
+
+	state = dst_end->rtp_process_data;
+	OSMO_ASSERT(state != NULL);
+
+	in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0);
+	OSMO_ASSERT(sizeof(buf) >= in_size + 12);
+
+	buf[1] = src_end->payload_type;
+	*(uint16_t*)(buf+2) = htons(1);
+	*(uint32_t*)(buf+4) = htonl(0);
+	*(uint32_t*)(buf+8) = htonl(0xaabbccdd);
+
+	while ((cc = read(0, buf + 12, in_size))) {
+		int cont;
+		int len;
+
+		if (cc != in_size)
+			err(1, "read");
+
+		*(uint16_t*)(buf+2) = htonl(seq);
+		*(uint32_t*)(buf+4) = htonl(ts);
+
+		seq += 1;
+		ts += in_samples;
+
+		cc += 12; /* include RTP header */
+
+		len = cc;
+
+		do {
+			cont = mgcp_transcoding_process_rtp(&endp, dst_end,
+							    buf, &len, sizeof(buf));
+			if (cont == -EAGAIN) {
+				fprintf(stderr, "Got EAGAIN\n");
+				break;
+			}
+
+			if (cont < 0)
+				errx(1, "processing failed: %s", strerror(-cont));
+
+			len -= 12; /* ignore RTP header */
+
+			if (write(1, buf + 12, len) != len)
+				err(1, "write");
+
+			len = cont;
+		} while (len > 0);
+	}
+	return 0;
+}
+
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
index d902315..b739d0f 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -14,7 +14,7 @@
 		osmo_msc_data.h osmo_bsc_grace.h sms_queue.h abis_om2000.h \
 		bss.h gsm_data_shared.h control_cmd.h ipaccess.h mncc_int.h \
 		arfcn_range_encode.h nat_rewrite_trie.h bsc_nat_callstats.h \
-		osmux.h
+		osmux.h mgcp_transcode.h
 
 openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
 openbscdir = $(includedir)/openbsc
diff --git a/openbsc/include/openbsc/mgcp.h b/openbsc/include/openbsc/mgcp.h
index 939e7ee..d4d6140 100644
--- a/openbsc/include/openbsc/mgcp.h
+++ b/openbsc/include/openbsc/mgcp.h
@@ -65,6 +65,7 @@
 struct mgcp_endpoint;
 struct mgcp_config;
 struct mgcp_trunk_config;
+struct mgcp_rtp_end;
 
 #define MGCP_ENDP_CRCX 1
 #define MGCP_ENDP_DLCX 2
@@ -86,6 +87,18 @@
 typedef int (*mgcp_reset)(struct mgcp_trunk_config *cfg);
 typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone);
 
+typedef int (*mgcp_processing)(struct mgcp_endpoint *endp,
+			       struct mgcp_rtp_end *dst_end,
+			       char *data, int *len, int buf_size);
+typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp,
+				     struct mgcp_rtp_end *dst_end,
+				     struct mgcp_rtp_end *src_end);
+
+typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp,
+				int *payload_type,
+				const char**subtype_name,
+				const char**fmtp_extra);
+
 #define PORT_ALLOC_STATIC	0
 #define PORT_ALLOC_DYNAMIC	1
 
@@ -156,6 +169,12 @@
 	struct in_addr transcoder_in;
 	int transcoder_remote_base;
 
+	/* RTP processing */
+	mgcp_processing rtp_processing_cb;
+	mgcp_processing_setup setup_rtp_processing_cb;
+
+	mgcp_get_format get_net_downlink_format_cb;
+
 	struct osmo_wqueue gw_fd;
 
 	struct mgcp_port_range bts_ports;
@@ -163,6 +182,8 @@
 	struct mgcp_port_range transcoder_ports;
 	int endp_dscp;
 
+	int bts_force_ptime;
+
 	mgcp_change change_cb;
 	mgcp_policy policy_cb;
 	mgcp_reset reset_cb;
diff --git a/openbsc/include/openbsc/mgcp_internal.h b/openbsc/include/openbsc/mgcp_internal.h
index 52bf558..2b44e40 100644
--- a/openbsc/include/openbsc/mgcp_internal.h
+++ b/openbsc/include/openbsc/mgcp_internal.h
@@ -80,16 +80,21 @@
 	/* per endpoint data */
 	int payload_type;
 	uint32_t rate;
+	int channels;
 	uint32_t frame_duration_num;
 	uint32_t frame_duration_den;
 	int  frames_per_packet;
 	uint32_t packet_duration_ms;
 	char *fmtp_extra;
+	char *audio_name;
+	char *subtype_name;
 	int output_enabled;
+	int force_output_ptime;
 
 	/* RTP patching */
 	int force_constant_ssrc; /* -1: always, 0: don't, 1: once */
 	int force_aligned_timing;
+	void *rtp_process_data;
 
 	/*
 	 * Each end has a socket...
@@ -118,6 +123,7 @@
 
 struct mgcp_lco {
 	char *string;
+	char *codec;
 	int pkt_period_min; /* time in ms */
 	int pkt_period_max; /* time in ms */
 };
@@ -205,6 +211,19 @@
 			uint32_t *expected, int *loss);
 uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *);
 
+/* payload processing default functions */
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end,
+				char *data, int *len, int buf_size);
+
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+				      struct mgcp_rtp_end *dst_end,
+				      struct mgcp_rtp_end *src_end);
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**subtype_name,
+					  const char**fmtp_extra);
+
 enum {
 	MGCP_DEST_NET = 0,
 	MGCP_DEST_BTS,
diff --git a/openbsc/include/openbsc/mgcp_transcode.h b/openbsc/include/openbsc/mgcp_transcode.h
new file mode 100644
index 0000000..0961634
--- /dev/null
+++ b/openbsc/include/openbsc/mgcp_transcode.h
@@ -0,0 +1,36 @@
+/*
+ * (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * 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/>.
+ *
+ */
+#ifndef OPENBSC_MGCP_TRANSCODE_H
+#define OPENBSC_MGCP_TRANSCODE_H
+
+int mgcp_transcoding_setup(struct mgcp_endpoint *endp,
+			   struct mgcp_rtp_end *dst_end,
+			   struct mgcp_rtp_end *src_end);
+
+void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra);
+
+int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp,
+				 struct mgcp_rtp_end *dst_end,
+				 char *data, int *len, int buf_size);
+
+int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst);
+#endif /* OPENBSC_MGCP_TRANSCODE_H */
diff --git a/openbsc/src/libmgcp/Makefile.am b/openbsc/src/libmgcp/Makefile.am
index 262ad34..e5dab1a 100644
--- a/openbsc/src/libmgcp/Makefile.am
+++ b/openbsc/src/libmgcp/Makefile.am
@@ -1,9 +1,15 @@
 AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
-AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
-	   $(LIBOSMONETIF_CFLAGS) $(COVERAGE_CFLAGS)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
+	    $(LIBOSMONETIF_CFLAGS) $(COVERAGE_CFLAGS) $(LIBBCG729_CFLAGS)
 AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \
-	     $(LIBOSMONETIF_LIBS) $(COVERAGE_LDFLAGS)
+	     $(LIBOSMONETIF_LIBS) $(COVERAGE_LDFLAGS) $(LIBBCG729_LIBS)
 
 noinst_LIBRARIES = libmgcp.a
 
+noinst_HEADERS = g711common.h
+
 libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c osmux.c
+
+if BUILD_MGCP_TRANSCODING
+    libmgcp_a_SOURCES += mgcp_transcode.c
+endif
diff --git a/openbsc/src/libmgcp/g711common.h b/openbsc/src/libmgcp/g711common.h
new file mode 100644
index 0000000..cb35fc6
--- /dev/null
+++ b/openbsc/src/libmgcp/g711common.h
@@ -0,0 +1,187 @@
+/*
+ *  PCM - A-Law conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *  Wrapper for linphone Codec class by Simon Morlat <simon.morlat@linphone.org>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+static inline int val_seg(int val)
+{
+	int r = 0;
+	val >>= 7; /*7 = 4 + 3*/
+	if (val & 0xf0) {
+		val >>= 4;
+		r += 4;
+	}
+	if (val & 0x0c) {
+		val >>= 2;
+		r += 2;
+	}
+	if (val & 0x02)
+		r += 1;
+	return r;
+}
+
+/*
+ * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data.
+ *
+ *		Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	0000000wxyza			000wxyz
+ *	0000001wxyza			001wxyz
+ *	000001wxyzab			010wxyz
+ *	00001wxyzabc			011wxyz
+ *	0001wxyzabcd			100wxyz
+ *	001wxyzabcde			101wxyz
+ *	01wxyzabcdef			110wxyz
+ *	1wxyzabcdefg			111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account.
+ */
+
+static inline unsigned char s16_to_alaw(int pcm_val)
+{
+	int		mask;
+	int		seg;
+	unsigned char	aval;
+
+	if (pcm_val >= 0) {
+		mask = 0xD5;
+	} else {
+		mask = 0x55;
+		pcm_val = -pcm_val;
+		if (pcm_val > 0x7fff)
+			pcm_val = 0x7fff;
+	}
+
+	if (pcm_val < 256) /*256 = 32 << 3*/
+		aval = pcm_val >> 4; /*4 = 1 + 3*/
+	else {
+		/* Convert the scaled magnitude to segment number. */
+		seg = val_seg(pcm_val);
+		aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	}
+	return aval ^ mask;
+}
+
+/*
+ * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+static inline int alaw_to_s16(unsigned char a_val)
+{
+	int		t;
+	int		seg;
+
+	a_val ^= 0x55;
+	t = a_val & 0x7f;
+	if (t < 16)
+		t = (t << 4) + 8;
+	else {
+		seg = (t >> 4) & 0x07;
+		t = ((t & 0x0f) << 4) + 0x108;
+		t <<= seg -1;
+	}
+	return ((a_val & 0x80) ? t : -t);
+}
+/*
+ * s16_to_ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ *	Biased Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	00000001wxyza			000wxyz
+ *	0000001wxyzab			001wxyz
+ *	000001wxyzabc			010wxyz
+ *	00001wxyzabcd			011wxyz
+ *	0001wxyzabcde			100wxyz
+ *	001wxyzabcdef			101wxyz
+ *	01wxyzabcdefg			110wxyz
+ *	1wxyzabcdefgh			111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz.  * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+
+static inline unsigned char s16_to_ulaw(int pcm_val)	/* 2's complement (16-bit range) */
+{
+	int mask;
+	int seg;
+	unsigned char uval;
+
+	if (pcm_val < 0) {
+		pcm_val = 0x84 - pcm_val;
+		mask = 0x7f;
+	} else {
+		pcm_val += 0x84;
+		mask = 0xff;
+	}
+	if (pcm_val > 0x7fff)
+		pcm_val = 0x7fff;
+
+	/* Convert the scaled magnitude to segment number. */
+	seg = val_seg(pcm_val);
+
+	/*
+	 * Combine the sign, segment, quantization bits;
+	 * and complement the code word.
+	 */
+	uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	return uval ^ mask;
+}
+
+/*
+ * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static inline int ulaw_to_s16(unsigned char u_val)
+{
+	int t;
+
+	/* Complement to obtain normal u-law value. */
+	u_val = ~u_val;
+
+	/*
+	 * Extract and bias the quantization bits. Then
+	 * shift up by the segment number and subtract out the bias.
+	 */
+	t = ((u_val & 0x0f) << 3) + 0x84;
+	t <<= (u_val & 0x70) >> 4;
+
+	return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
+}
diff --git a/openbsc/src/libmgcp/mgcp_network.c b/openbsc/src/libmgcp/mgcp_network.c
index 5e8284d..219d3f9 100644
--- a/openbsc/src/libmgcp/mgcp_network.c
+++ b/openbsc/src/libmgcp/mgcp_network.c
@@ -79,6 +79,7 @@
 #define RTP_SEQ_MOD		(1 << 16)
 #define RTP_MAX_DROPOUT		3000
 #define RTP_MAX_MISORDER	100
+#define RTP_BUF_SIZE		4096
 
 enum {
 	MGCP_PROTO_RTP,
@@ -339,6 +340,31 @@
 	return timestamp_error;
 }
 
+int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end,
+				char *data, int *len, int buf_size)
+{
+	return 0;
+}
+
+int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp,
+				      struct mgcp_rtp_end *dst_end,
+				      struct mgcp_rtp_end *src_end)
+{
+	return 0;
+}
+
+void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra)
+{
+	/* Use the BTS side parameters when passing the SDP data (for
+	 * downlink) to the net peer.
+	 */
+	*payload_type = endp->bts_end.payload_type;
+	*audio_name = endp->bts_end.audio_name;
+	*fmtp_extra = endp->bts_end.fmtp_extra;
+}
 
 /**
  * The RFC 3550 Appendix A assumes there are multiple sources but
@@ -588,11 +614,28 @@
 	if (!rtp_end->output_enabled)
 		rtp_end->dropped_packets += 1;
 	else if (is_rtp) {
-		mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, rc);
-		forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx], buf, rc);
-		return mgcp_udp_send(rtp_end->rtp.fd,
-				     &rtp_end->addr,
-				     rtp_end->rtp_port, buf, rc);
+		int cont;
+		int nbytes = 0;
+		int len = rc;
+		mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len);
+		do {
+			cont = endp->cfg->rtp_processing_cb(endp, rtp_end,
+							buf, &len, RTP_BUF_SIZE);
+			if (cont < 0)
+				break;
+
+			forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx],
+				     buf, len);
+			rc = mgcp_udp_send(rtp_end->rtp.fd,
+					   &rtp_end->addr,
+					   rtp_end->rtp_port, buf, len);
+
+			if (rc <= 0)
+				return rc;
+			nbytes += rc;
+			len = cont;
+		} while (len > 0);
+		return nbytes;
 	} else if (!tcfg->omit_rtcp) {
 		return mgcp_udp_send(rtp_end->rtcp.fd,
 				     &rtp_end->addr,
@@ -627,7 +670,7 @@
 
 static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
 {
-	char buf[4096];
+	char buf[RTP_BUF_SIZE];
 	struct sockaddr_in addr;
 	struct mgcp_endpoint *endp;
 	int rc, proto;
@@ -723,7 +766,7 @@
 
 static int rtp_data_bts(struct osmo_fd *fd, unsigned int what)
 {
-	char buf[4096];
+	char buf[RTP_BUF_SIZE];
 	struct sockaddr_in addr;
 	struct mgcp_endpoint *endp;
 	int rc, proto;
@@ -790,7 +833,7 @@
 static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp,
 			      int dest, struct osmo_fd *fd)
 {
-	char buf[4096];
+	char buf[RTP_BUF_SIZE];
 	struct sockaddr_in addr;
 	struct mgcp_config *cfg;
 	int rc, proto;
diff --git a/openbsc/src/libmgcp/mgcp_protocol.c b/openbsc/src/libmgcp/mgcp_protocol.c
index 1a88a84..f0457d1 100644
--- a/openbsc/src/libmgcp/mgcp_protocol.c
+++ b/openbsc/src/libmgcp/mgcp_protocol.c
@@ -28,6 +28,7 @@
 #include <time.h>
 #include <limits.h>
 #include <unistd.h>
+#include <errno.h>
 
 #include <osmocom/core/msgb.h>
 #include <osmocom/core/talloc.h>
@@ -77,6 +78,9 @@
 #define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000
 #define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20
 #define DEFAULT_RTP_AUDIO_DEFAULT_RATE  8000
+#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1
+
+#define PTYPE_UNDEFINED (-1)
 
 static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
 
@@ -107,6 +111,8 @@
 static void create_transcoder(struct mgcp_endpoint *endp);
 static void delete_transcoder(struct mgcp_endpoint *endp);
 
+static void setup_rtp_processing(struct mgcp_endpoint *endp);
+
 static int mgcp_analyze_header(struct mgcp_parse_data *parse, char *data);
 
 static int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line)
@@ -237,50 +243,106 @@
 	return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL);
 }
 
-static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
-					     const char *msg, const char *trans_id)
+static int write_response_sdp(struct mgcp_endpoint *endp,
+			      char *sdp_record, size_t size, const char *addr)
 {
-	const char *addr = endp->cfg->local_ip;
-	const char *fmtp_extra = endp->bts_end.fmtp_extra;
-	char sdp_record[4096];
+	const char *fmtp_extra;
+	const char *audio_name;
+	int payload_type;
 	int len;
+	int nchars;
 
-	if (!addr)
-		addr = endp->cfg->source_addr;
+	endp->cfg->get_net_downlink_format_cb(endp, &payload_type,
+					      &audio_name, &fmtp_extra);
 
-	len = snprintf(sdp_record, sizeof(sdp_record) - 1,
-			"I: %u%s\n\n"
+	len = snprintf(sdp_record, size,
 			"v=0\r\n"
 			"o=- %u 23 IN IP4 %s\r\n"
 			"c=IN IP4 %s\r\n"
-			"t=0 0\r\n"
-			"m=audio %d RTP/AVP %d\r\n"
-			"a=rtpmap:%d %s\r\n"
-			"%s%s",
-			endp->ci, endp->cfg->osmux && endp->osmux ? "\nX-Osmux: On" : "",
-			endp->ci, addr, addr,
-			endp->net_end.local_port, endp->bts_end.payload_type,
-			endp->bts_end.payload_type, endp->tcfg->audio_name,
-			fmtp_extra ? fmtp_extra : "", fmtp_extra ? "\r\n" : "");
+			"t=0 0\r\n",
+			endp->ci, addr, addr);
 
-	if (len < 0 || len >= sizeof(sdp_record))
+	if (len < 0 || len >= size)
 		goto buffer_too_small;
 
+	if (payload_type >= 0) {
+		nchars = snprintf(sdp_record + len, size - len,
+				  "m=audio %d RTP/AVP %d\r\n",
+				  endp->net_end.local_port, payload_type);
+		if (nchars < 0 || nchars >= size - len)
+			goto buffer_too_small;
+
+		len += nchars;
+
+		if (audio_name) {
+			nchars = snprintf(sdp_record + len, size - len,
+					  "a=rtpmap:%d %s\r\n",
+					  payload_type, audio_name);
+
+			if (nchars < 0 || nchars >= size - len)
+				goto buffer_too_small;
+
+			len += nchars;
+		}
+
+		if (fmtp_extra) {
+			nchars = snprintf(sdp_record + len, size - len,
+					  "a=rtpmap:%d %s\r\n",
+					  payload_type, audio_name);
+
+			if (nchars < 0 || nchars >= size - len)
+				goto buffer_too_small;
+
+			len += nchars;
+		}
+	}
 	if (endp->bts_end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) {
-		int nchars = snprintf(sdp_record + len, sizeof(sdp_record) - len,
-				      "a=ptime:%d\r\n",
-				      endp->bts_end.packet_duration_ms);
-		if (nchars < 0 || nchars >= sizeof(sdp_record) - len)
+		nchars = snprintf(sdp_record + len, size - len,
+				  "a=ptime:%d\r\n",
+				  endp->bts_end.packet_duration_ms);
+		if (nchars < 0 || nchars >= size - len)
 			goto buffer_too_small;
 
 		len += nchars;
 	}
-	return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record);
+
+	return len;
 
 buffer_too_small:
 	LOGP(DMGCP, LOGL_ERROR, "SDP buffer too small: %d (needed %d)\n",
-	     sizeof(sdp_record), len);
-	return NULL;
+	     size, len);
+	return -1;
+}
+
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+					     const char *msg, const char *trans_id)
+{
+	const char *addr = endp->cfg->local_ip;
+	char sdp_record[4096];
+	int len;
+	int nchars;
+
+	if (!addr)
+		addr = endp->cfg->source_addr;
+
+	len = snprintf(sdp_record, sizeof(sdp_record),
+		       "I: %u%s\n\n",
+			endp->ci,
+			endp->cfg->osmux && endp->osmux ? "\nX-Osmux: On" : "");
+
+	if (len < 0)
+		return NULL;
+
+	nchars = write_response_sdp(endp, sdp_record + len,
+				    sizeof(sdp_record) - len - 1, addr);
+	if (nchars < 0)
+		return NULL;
+
+	len += nchars;
+
+	sdp_record[sizeof(sdp_record) - 1] = '\0';
+
+	return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record);
 }
 
 /*
@@ -526,6 +588,72 @@
 	return ret;
 }
 
+static int set_audio_info(void *ctx, struct mgcp_rtp_end *rtp,
+			  int payload_type, const char *audio_name)
+{
+	int rate = rtp->rate;
+	int channels = rtp->channels;
+	char audio_codec[64];
+
+	talloc_free(rtp->subtype_name);
+	rtp->subtype_name = NULL;
+	talloc_free(rtp->audio_name);
+	rtp->audio_name = NULL;
+
+	if (payload_type != PTYPE_UNDEFINED)
+		rtp->payload_type = payload_type;
+
+	if (!audio_name) {
+		switch (payload_type) {
+		case 3: audio_name = "GSM/8000/1"; break;
+		case 8: audio_name = "PCMA/8000/1"; break;
+		case 18: audio_name = "G729/8000/1"; break;
+		default:
+			 /* Payload type is unknown, don't change rate and
+			  * channels. */
+			 /* TODO: return value? */
+			 return 0;
+		}
+	}
+
+	if (sscanf(audio_name, "%63[^/]/%d/%d",
+		   audio_codec, &rate, &channels) < 1)
+		return -EINVAL;
+
+	rtp->rate = rate;
+	rtp->channels = channels;
+	rtp->subtype_name = talloc_strdup(ctx, audio_codec);
+	rtp->audio_name = talloc_strdup(ctx, audio_name);
+
+	if (!strcmp(audio_codec, "G729")) {
+		rtp->frame_duration_num = 10;
+		rtp->frame_duration_den = 1000;
+	} else {
+		rtp->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
+		rtp->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN;
+	}
+
+	if (payload_type < 0) {
+		payload_type = 96;
+		if (rate == 8000 && channels == 1) {
+			if (!strcmp(audio_codec, "GSM"))
+				payload_type = 3;
+			else if (!strcmp(audio_codec, "PCMA"))
+				payload_type = 8;
+			else if (!strcmp(audio_codec, "G729"))
+				payload_type = 18;
+		}
+
+		rtp->payload_type = payload_type;
+	}
+
+	if (channels != 1)
+		LOGP(DMGCP, LOGL_NOTICE,
+		     "Channels != 1 in SDP: '%s'\n", audio_name);
+
+	return 0;
+}
+
 static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end,
 			 struct mgcp_port_range *range,
 			 int (*alloc)(struct mgcp_endpoint *endp, int port))
@@ -612,11 +740,8 @@
 			break;
 		case 'a': {
 			int payload;
-			int rate;
-			int channels = 1;
 			int ptime, ptime2 = 0;
 			char audio_name[64];
-			char audio_codec[64];
 
 			if (audio_payload == -1)
 				break;
@@ -626,15 +751,7 @@
 				if (payload != audio_payload)
 					break;
 
-				if (sscanf(audio_name, "%[^/]/%d/%d",
-					  audio_codec, &rate, &channels) < 2)
-					break;
-
-				rtp->rate = rate;
-				if (channels != 1)
-					LOGP(DMGCP, LOGL_NOTICE,
-					     "Channels != 1 in SDP: '%s' on 0x%x\n",
-					     line, ENDPOINT_NUMBER(p->endp));
+				set_audio_info(p->cfg, rtp, payload, audio_name);
 			} else if (sscanf(line, "a=ptime:%d-%d",
 					  &ptime, &ptime2) >= 1) {
 				if (ptime2 > 0 && ptime2 != ptime)
@@ -657,8 +774,8 @@
 				   &port, &audio_payload) == 2) {
 				rtp->rtp_port = htons(port);
 				rtp->rtcp_port = htons(port + 1);
-				rtp->payload_type = audio_payload;
 				found_media = 1;
+				set_audio_info(p->cfg, rtp, audio_payload, NULL);
 			}
 			break;
 		}
@@ -685,9 +802,10 @@
 
 	if (found_media)
 		LOGP(DMGCP, LOGL_NOTICE,
-		     "Got media info via SDP: port %d, payload %d, "
+		     "Got media info via SDP: port %d, payload %d (%s), "
 		     "duration %d, addr %s\n",
 		     ntohs(rtp->rtp_port), rtp->payload_type,
+		     rtp->subtype_name ? rtp->subtype_name : "unknown",
 		     rtp->packet_duration_ms, inet_ntoa(rtp->addr));
 
 	return found_media;
@@ -700,9 +818,12 @@
 static void set_local_cx_options(void *ctx, struct mgcp_lco *lco,
 				 const char *options)
 {
-	char *p_opt;
+	char *p_opt, *a_opt;
+	char codec[9];
 
 	talloc_free(lco->string);
+	talloc_free(lco->codec);
+	lco->codec = NULL;
 	lco->pkt_period_min = lco->pkt_period_max = 0;
 	lco->string = talloc_strdup(ctx, options ? options : "");
 
@@ -710,6 +831,10 @@
 	if (p_opt && sscanf(p_opt, "p:%d-%d",
 			    &lco->pkt_period_min, &lco->pkt_period_max) == 1)
 		lco->pkt_period_max = lco->pkt_period_min;
+
+	a_opt = strstr(lco->string, "a:");
+	if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1)
+		lco->codec = talloc_strdup(ctx, codec);
 }
 
 void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change,
@@ -849,11 +974,21 @@
 	endp->allocated = 1;
 
 	/* set up RTP media parameters */
-	endp->bts_end.payload_type = tcfg->audio_payload;
+	set_audio_info(p->cfg, &endp->bts_end, tcfg->audio_payload, tcfg->audio_name);
 	endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints,
 						tcfg->audio_fmtp_extra);
 	if (have_sdp)
 		parse_sdp_data(&endp->net_end, p);
+	else if (endp->local_options.codec)
+		set_audio_info(p->cfg, &endp->net_end,
+			       PTYPE_UNDEFINED, endp->local_options.codec);
+
+	if (p->cfg->bts_force_ptime) {
+		endp->bts_end.packet_duration_ms = p->cfg->bts_force_ptime;
+		endp->bts_end.force_output_ptime = 1;
+	}
+
+	setup_rtp_processing(endp);
 
 	/* policy CB */
 	if (p->cfg->policy_cb) {
@@ -904,6 +1039,7 @@
 	struct mgcp_endpoint *endp = p->endp;
 	int error_code = 500;
 	int silent = 0, osmux = 0;
+	int have_sdp = 0;
 	char *line;
 	const char *local_options = NULL;
 
@@ -950,6 +1086,7 @@
 			break;
 		case '\0':
 			/* SDP file begins */
+			have_sdp = 1;
 			parse_sdp_data(&endp->net_end, p);
 			/* This will exhaust p->save, so the loop will
 			 * terminate next time.
@@ -980,6 +1117,12 @@
 	set_local_cx_options(endp->tcfg->endpoints, &endp->local_options,
 			     local_options);
 
+	if (!have_sdp && endp->local_options.codec)
+		set_audio_info(p->cfg, &endp->net_end,
+			       PTYPE_UNDEFINED, endp->local_options.codec);
+
+	setup_rtp_processing(endp);
+
 	/* policy CB */
 	if (p->cfg->policy_cb) {
 		int rc;
@@ -1223,6 +1366,11 @@
 	cfg->bts_ports.base_port = RTP_PORT_DEFAULT;
 	cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT;
 
+	cfg->rtp_processing_cb = &mgcp_rtp_processing_default;
+	cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default;
+
+	cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default;
+
 	/* default trunk handling */
 	cfg->trunk.cfg = cfg;
 	cfg->trunk.trunk_nr = 0;
@@ -1288,6 +1436,12 @@
 	end->local_alloc = -1;
 	talloc_free(end->fmtp_extra);
 	end->fmtp_extra = NULL;
+	talloc_free(end->subtype_name);
+	end->subtype_name = NULL;
+	talloc_free(end->audio_name);
+	end->audio_name = NULL;
+	talloc_free(end->rtp_process_data);
+	end->rtp_process_data = NULL;
 
 	/* Set default values */
 	end->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM;
@@ -1295,6 +1449,7 @@
 	end->frames_per_packet  = 0; /* unknown */
 	end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS;
 	end->rate               = DEFAULT_RTP_AUDIO_DEFAULT_RATE;
+	end->channels           = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS;
 	end->output_enabled	= 0;
 }
 
@@ -1340,6 +1495,8 @@
 
 	talloc_free(endp->local_options.string);
 	endp->local_options.string = NULL;
+	talloc_free(endp->local_options.codec);
+	endp->local_options.codec = NULL;
 
 	mgcp_rtp_end_reset(&endp->bts_end);
 	mgcp_rtp_end_reset(&endp->net_end);
@@ -1372,23 +1529,26 @@
 {
 	char buf[2096];
 	int len;
+	int nchars;
 
 	/* hardcoded to AMR right now, we do not know the real type at this point */
 	len = snprintf(buf, sizeof(buf),
 			"%s 42 %x@mgw MGCP 1.0\r\n"
 			"C: 4256\r\n"
 			"M: %s\r\n"
-			"\r\n"
-			"c=IN IP4 %s\r\n"
-			"m=audio %d RTP/AVP %d\r\n"
-			"a=rtpmap:%d %s\r\n",
-			msg, endpoint, mode, endp->cfg->source_addr,
-			port, endp->tcfg->audio_payload,
-			endp->tcfg->audio_payload, endp->tcfg->audio_name);
+			"\r\n",
+			msg, endpoint, mode);
 
 	if (len < 0)
 		return;
 
+	nchars = write_response_sdp(endp, buf + len, sizeof(buf) + len - 1,
+				    endp->cfg->source_addr);
+	if (nchars < 0)
+		return;
+
+	len += nchars;
+
 	buf[sizeof(buf) - 1] = '\0';
 
 	send_trans(endp->cfg, buf, len);
@@ -1442,6 +1602,27 @@
 	return send_agent(endp->cfg, buf, len);
 }
 
+static void setup_rtp_processing(struct mgcp_endpoint *endp)
+{
+	struct mgcp_config *cfg = endp->cfg;
+
+	if (endp->type != MGCP_RTP_DEFAULT)
+		return;
+
+	if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+		return;
+
+	if (endp->conn_mode & MGCP_CONN_SEND_ONLY)
+		cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end);
+	else
+		cfg->setup_rtp_processing_cb(endp, &endp->net_end, NULL);
+
+	if (endp->conn_mode & MGCP_CONN_RECV_ONLY)
+		cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end);
+	else
+		cfg->setup_rtp_processing_cb(endp, &endp->bts_end, NULL);
+}
+
 static void create_transcoder(struct mgcp_endpoint *endp)
 {
 	int port;
diff --git a/openbsc/src/libmgcp/mgcp_transcode.c b/openbsc/src/libmgcp/mgcp_transcode.c
new file mode 100644
index 0000000..581cd32
--- /dev/null
+++ b/openbsc/src/libmgcp/mgcp_transcode.c
@@ -0,0 +1,550 @@
+/*
+ * (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include "../../bscconfig.h"
+
+#include "g711common.h"
+#include <gsm.h>
+#ifdef HAVE_BCG729
+#include <bcg729/decoder.h>
+#include <bcg729/encoder.h>
+#endif
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <osmocom/core/talloc.h>
+
+enum audio_format {
+	AF_INVALID,
+	AF_S16,
+	AF_L16,
+	AF_GSM,
+	AF_G729,
+	AF_PCMA
+};
+
+struct mgcp_process_rtp_state {
+	/* decoding */
+	enum audio_format src_fmt;
+	union {
+		gsm gsm_handle;
+#ifdef HAVE_BCG729
+		bcg729DecoderChannelContextStruct *g729_dec;
+#endif
+	} src;
+	size_t src_frame_size;
+	size_t src_samples_per_frame;
+
+	/* processing */
+
+	/* encoding */
+	enum audio_format dst_fmt;
+	union {
+		gsm gsm_handle;
+#ifdef HAVE_BCG729
+		bcg729EncoderChannelContextStruct *g729_enc;
+#endif
+	} dst;
+	size_t dst_frame_size;
+	size_t dst_samples_per_frame;
+	int dst_packet_duration;
+
+	int is_running;
+	uint16_t next_seq;
+	uint32_t next_time;
+	int16_t samples[10*160];
+	size_t sample_cnt;
+	size_t sample_offs;
+};
+
+int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst)
+{
+	struct mgcp_process_rtp_state *state = state_;
+	if (dst)
+		return (nsamples >= 0 ?
+			nsamples / state->dst_samples_per_frame :
+			1) * state->dst_frame_size;
+	else
+		return (nsamples >= 0 ?
+			nsamples / state->src_samples_per_frame :
+			1) * state->src_frame_size;
+}
+
+static enum audio_format get_audio_format(const struct mgcp_rtp_end *rtp_end)
+{
+	if (rtp_end->subtype_name) {
+		if (!strcmp("GSM", rtp_end->subtype_name))
+			return AF_GSM;
+		if (!strcmp("PCMA", rtp_end->subtype_name))
+			return AF_PCMA;
+#ifdef HAVE_BCG729
+		if (!strcmp("G729", rtp_end->subtype_name))
+			return AF_G729;
+#endif
+		if (!strcmp("L16", rtp_end->subtype_name))
+			return AF_L16;
+	}
+
+	switch (rtp_end->payload_type) {
+	case 3 /* GSM */:
+		return AF_GSM;
+	case 8 /* PCMA */:
+		return AF_PCMA;
+#ifdef HAVE_BCG729
+	case 18 /* G.729 */:
+		return AF_G729;
+#endif
+	case 11 /* L16 */:
+		return AF_L16;
+	default:
+		return AF_INVALID;
+	}
+}
+
+static void l16_encode(short *sample, unsigned char *buf, size_t n)
+{
+	for (; n > 0; --n, ++sample, buf += 2) {
+		buf[0] = sample[0] >> 8;
+		buf[1] = sample[0] & 0xff;
+	}
+}
+
+static void l16_decode(unsigned char *buf, short *sample, size_t n)
+{
+	for (; n > 0; --n, ++sample, buf += 2)
+		sample[0] = ((short)buf[0] << 8) | buf[1];
+}
+
+static void alaw_encode(short *sample, unsigned char *buf, size_t n)
+{
+	for (; n > 0; --n)
+		*(buf++) = s16_to_alaw(*(sample++));
+}
+
+static void alaw_decode(unsigned char *buf, short *sample, size_t n)
+{
+	for (; n > 0; --n)
+		*(sample++) = alaw_to_s16(*(buf++));
+}
+
+static int processing_state_destructor(struct mgcp_process_rtp_state *state)
+{
+	switch (state->src_fmt) {
+	case AF_GSM:
+		if (state->dst.gsm_handle)
+			gsm_destroy(state->src.gsm_handle);
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		if (state->src.g729_dec)
+			closeBcg729DecoderChannel(state->src.g729_dec);
+		break;
+#endif
+	default:
+		break;
+	}
+	switch (state->dst_fmt) {
+	case AF_GSM:
+		if (state->dst.gsm_handle)
+			gsm_destroy(state->dst.gsm_handle);
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		if (state->dst.g729_enc)
+			closeBcg729EncoderChannel(state->dst.g729_enc);
+		break;
+#endif
+	default:
+		break;
+	}
+	return 0;
+}
+
+int mgcp_transcoding_setup(struct mgcp_endpoint *endp,
+			   struct mgcp_rtp_end *dst_end,
+			   struct mgcp_rtp_end *src_end)
+{
+	struct mgcp_process_rtp_state *state;
+	enum audio_format src_fmt, dst_fmt;
+
+	/* cleanup first */
+	if (dst_end->rtp_process_data) {
+		talloc_free(dst_end->rtp_process_data);
+		dst_end->rtp_process_data = NULL;
+	}
+
+	if (!src_end)
+		return 0;
+
+	src_fmt = get_audio_format(src_end);
+	dst_fmt = get_audio_format(dst_end);
+
+	LOGP(DMGCP, LOGL_ERROR,
+	     "Checking transcoding: %s (%d) -> %s (%d)\n",
+	     src_end->subtype_name, src_end->payload_type,
+	     dst_end->subtype_name, dst_end->payload_type);
+
+	if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) {
+		if (!src_end->subtype_name || !dst_end->subtype_name)
+			/* Not enough info, do nothing */
+			return 0;
+
+		if (strcmp(src_end->subtype_name, dst_end->subtype_name) == 0)
+			/* Nothing to do */
+			return 0;
+
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot transcode: %s codec not supported (%s -> %s).\n",
+		     src_fmt != AF_INVALID ? "destination" : "source",
+		     src_end->audio_name, dst_end->audio_name);
+		return -EINVAL;
+	}
+
+	if (src_end->rate && dst_end->rate && src_end->rate != dst_end->rate) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot transcode: rate conversion (%d -> %d) not supported.\n",
+		     src_end->rate, dst_end->rate);
+		return -EINVAL;
+	}
+
+	state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state);
+	talloc_set_destructor(state, processing_state_destructor);
+	dst_end->rtp_process_data = state;
+
+	state->src_fmt = src_fmt;
+
+	switch (state->src_fmt) {
+	case AF_L16:
+	case AF_S16:
+		state->src_frame_size = 80 * sizeof(short);
+		state->src_samples_per_frame = 80;
+		break;
+	case AF_GSM:
+		state->src_frame_size = sizeof(gsm_frame);
+		state->src_samples_per_frame = 160;
+		state->src.gsm_handle = gsm_create();
+		if (!state->src.gsm_handle) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize GSM decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		state->src_frame_size = 10;
+		state->src_samples_per_frame = 80;
+		state->src.g729_dec = initBcg729DecoderChannel();
+		if (!state->src.g729_dec) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize G.729 decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#endif
+	case AF_PCMA:
+		state->src_frame_size = 80;
+		state->src_samples_per_frame = 80;
+		break;
+	default:
+		break;
+	}
+
+	state->dst_fmt = dst_fmt;
+
+	switch (state->dst_fmt) {
+	case AF_L16:
+	case AF_S16:
+		state->dst_frame_size = 80*sizeof(short);
+		state->dst_samples_per_frame = 80;
+		break;
+	case AF_GSM:
+		state->dst_frame_size = sizeof(gsm_frame);
+		state->dst_samples_per_frame = 160;
+		state->dst.gsm_handle = gsm_create();
+		if (!state->dst.gsm_handle) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize GSM encoder.\n");
+			return -EINVAL;
+		}
+		break;
+#ifdef HAVE_BCG729
+	case AF_G729:
+		state->dst_frame_size = 10;
+		state->dst_samples_per_frame = 80;
+		state->dst.g729_enc = initBcg729EncoderChannel();
+		if (!state->dst.g729_enc) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize G.729 decoder.\n");
+			return -EINVAL;
+		}
+		break;
+#endif
+	case AF_PCMA:
+		state->dst_frame_size = 80;
+		state->dst_samples_per_frame = 80;
+		break;
+	default:
+		break;
+	}
+
+	if (dst_end->force_output_ptime)
+		state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end);
+
+	LOGP(DMGCP, LOGL_INFO,
+	     "Initialized RTP processing on: 0x%x "
+	     "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n",
+	     ENDPOINT_NUMBER(endp),
+	     src_fmt, src_end->payload_type, src_end->rate, src_end->fmtp_extra,
+	     dst_fmt, dst_end->payload_type, dst_end->rate, dst_end->fmtp_extra);
+
+	return 0;
+}
+
+void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp,
+					  int *payload_type,
+					  const char**audio_name,
+					  const char**fmtp_extra)
+{
+	struct mgcp_process_rtp_state *state = endp->net_end.rtp_process_data;
+	if (!state || endp->net_end.payload_type < 0) {
+		*payload_type = endp->bts_end.payload_type;
+		*audio_name = endp->bts_end.audio_name;
+		*fmtp_extra = endp->bts_end.fmtp_extra;
+		return;
+	}
+
+	*payload_type = endp->net_end.payload_type;
+	*fmtp_extra = endp->net_end.fmtp_extra;
+	*audio_name = endp->net_end.audio_name;
+}
+
+static int decode_audio(struct mgcp_process_rtp_state *state,
+			uint8_t **src, size_t *nbytes)
+{
+	while (*nbytes >= state->src_frame_size) {
+		if (state->sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(state->samples)) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Sample buffer too small: %d > %d.\n",
+			     state->sample_cnt + state->src_samples_per_frame,
+			     ARRAY_SIZE(state->samples));
+			return -ENOSPC;
+		}
+		switch (state->src_fmt) {
+		case AF_GSM:
+			if (gsm_decode(state->src.gsm_handle,
+				       (gsm_byte *)*src, state->samples + state->sample_cnt) < 0) {
+				LOGP(DMGCP, LOGL_ERROR,
+				     "Failed to decode GSM.\n");
+				return -EINVAL;
+			}
+			break;
+#ifdef HAVE_BCG729
+		case AF_G729:
+			bcg729Decoder(state->src.g729_dec, *src, 0, state->samples + state->sample_cnt);
+			break;
+#endif
+		case AF_PCMA:
+			alaw_decode(*src, state->samples + state->sample_cnt,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(state->samples + state->sample_cnt, *src,
+				state->src_frame_size);
+			break;
+		case AF_L16:
+			l16_decode(*src, state->samples + state->sample_cnt,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		*src        += state->src_frame_size;
+		*nbytes     -= state->src_frame_size;
+		state->sample_cnt += state->src_samples_per_frame;
+	}
+	return 0;
+}
+
+static int encode_audio(struct mgcp_process_rtp_state *state,
+			uint8_t *dst, size_t buf_size, size_t max_samples)
+{
+	int nbytes = 0;
+	size_t nsamples = 0;
+	/* Encode samples into dst */
+	while (nsamples + state->dst_samples_per_frame <= max_samples) {
+		if (nbytes + state->dst_frame_size > buf_size) {
+			if (nbytes > 0)
+				break;
+
+			/* Not even one frame fits into the buffer */
+			LOGP(DMGCP, LOGL_INFO,
+			     "Encoding (RTP) buffer too small: %d > %d.\n",
+			     nbytes + state->dst_frame_size, buf_size);
+			return -ENOSPC;
+		}
+		switch (state->dst_fmt) {
+		case AF_GSM:
+			gsm_encode(state->dst.gsm_handle,
+				   state->samples + state->sample_offs, dst);
+			break;
+#ifdef HAVE_BCG729
+		case AF_G729:
+			bcg729Encoder(state->dst.g729_enc,
+				      state->samples + state->sample_offs, dst);
+			break;
+#endif
+		case AF_PCMA:
+			alaw_encode(state->samples + state->sample_offs, dst,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(dst, state->samples + state->sample_offs,
+				state->dst_frame_size);
+			break;
+		case AF_L16:
+			l16_encode(state->samples + state->sample_offs, dst,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		dst        += state->dst_frame_size;
+		nbytes     += state->dst_frame_size;
+		state->sample_offs += state->dst_samples_per_frame;
+		nsamples   += state->dst_samples_per_frame;
+	}
+	state->sample_cnt -= nsamples;
+	return nbytes;
+}
+
+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;
+	size_t rtp_hdr_size = 12;
+	char *payload_data = data + rtp_hdr_size;
+	int payload_len = *len - rtp_hdr_size;
+	uint8_t *src = (uint8_t *)payload_data;
+	uint8_t *dst = (uint8_t *)payload_data;
+	size_t nbytes = payload_len;
+	size_t nsamples;
+	size_t max_samples;
+	uint32_t ts_no;
+	int rc;
+
+	if (!state)
+		return 0;
+
+	if (state->src_fmt == state->dst_fmt) {
+		if (!state->dst_packet_duration)
+			return 0;
+
+		/* TODO: repackage without transcoding */
+	}
+
+	/* If the remaining samples do not fit into a fixed ptime,
+	 * a) discard them, if the next packet is much later
+	 * b) add silence and * send it, if the current packet is not
+	 *    yet too late
+	 * c) append the sample data, if the timestamp matches exactly
+	 */
+
+	/* TODO: check payload type (-> G.711 comfort noise) */
+
+	if (payload_len > 0) {
+		ts_no = ntohl(*(uint32_t*)(data+4));
+		if (!state->is_running)
+			state->next_seq = ntohs(*(uint32_t*)(data+4));
+
+		state->is_running = 1;
+
+		if (state->sample_cnt > 0) {
+			int32_t delta = ts_no - state->next_time;
+			/* TODO: check sequence? reordering? packet loss? */
+
+			if (delta > state->sample_cnt)
+				/* There is a time gap between the last packet
+				 * and the current one. Just discard the
+				 * partial data that is left in the buffer.
+				 * TODO: This can be improved by adding silence
+				 * instead if the delta is small enough.
+				 */
+				state->sample_cnt = 0;
+			else if (delta < 0) {
+				LOGP(DMGCP, LOGL_NOTICE,
+				     "RTP time jumps backwards, delta = %d, "
+				     "discarding buffered samples\n",
+				     delta);
+				state->sample_cnt = 0;
+				state->sample_offs = 0;
+				return -EAGAIN;
+			}
+
+			/* Make sure the samples start without offset */
+			if (state->sample_offs && state->sample_cnt)
+				memmove(&state->samples[0],
+					&state->samples[state->sample_offs],
+					state->sample_cnt *
+					sizeof(state->samples[0]));
+		}
+
+		state->sample_offs = 0;
+
+		/* Append decoded audio to samples */
+		decode_audio(state, &src, &nbytes);
+
+		if (nbytes > 0)
+			LOGP(DMGCP, LOGL_NOTICE,
+			     "Skipped audio frame in RTP packet: %d octets\n",
+			     nbytes);
+	} else
+		ts_no = state->next_time;
+
+	if (state->sample_cnt < state->dst_packet_duration)
+		return -EAGAIN;
+
+	max_samples =
+		state->dst_packet_duration ?
+		state->dst_packet_duration : state->sample_cnt;
+
+	nsamples = state->sample_cnt;
+
+	rc = encode_audio(state, dst, buf_size, max_samples);
+	if (rc <= 0)
+		return rc;
+
+	nsamples -= state->sample_cnt;
+
+	*len = rtp_hdr_size + rc;
+	*(uint16_t*)(data+2) = htonl(state->next_seq);
+	*(uint32_t*)(data+4) = htonl(ts_no);
+
+	state->next_seq += 1;
+	state->next_time = ts_no + nsamples;
+
+	return nsamples ? rtp_hdr_size : 0;
+}
diff --git a/openbsc/src/libmgcp/mgcp_vty.c b/openbsc/src/libmgcp/mgcp_vty.c
index 1f8a63a..26b5706 100644
--- a/openbsc/src/libmgcp/mgcp_vty.c
+++ b/openbsc/src/libmgcp/mgcp_vty.c
@@ -366,6 +366,26 @@
       RTP_STR
       "Apply IP_TOS to the audio stream\n" "The DSCP value\n")
 
+#define FORCE_PTIME_STR "Force a fixed ptime for packets sent to the BTS"
+DEFUN(cfg_mgcp_rtp_force_ptime,
+      cfg_mgcp_rtp_force_ptime_cmd,
+      "rtp force-ptime (10|20|40)",
+      RTP_STR FORCE_PTIME_STR
+      "The required ptime (packet duration) in ms\n")
+{
+	g_cfg->bts_force_ptime = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_rtp_force_ptime,
+      cfg_mgcp_no_rtp_force_ptime_cmd,
+      "no rtp force-ptime",
+      NO_STR RTP_STR FORCE_PTIME_STR)
+{
+	g_cfg->bts_force_ptime = 0;
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_mgcp_sdp_fmtp_extra,
       cfg_mgcp_sdp_fmtp_extra_cmd,
       "sdp audio fmtp-extra .NAME",
@@ -1123,6 +1143,8 @@
 	install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd);
diff --git a/openbsc/src/osmo-bsc_mgcp/Makefile.am b/openbsc/src/osmo-bsc_mgcp/Makefile.am
index 7b62621..fba76b4 100644
--- a/openbsc/src/osmo-bsc_mgcp/Makefile.am
+++ b/openbsc/src/osmo-bsc_mgcp/Makefile.am
@@ -5,7 +5,8 @@
 bin_PROGRAMS = osmo-bsc_mgcp
 
 osmo_bsc_mgcp_SOURCES = mgcp_main.c
+
 osmo_bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
 		 $(top_builddir)/src/libmgcp/libmgcp.a -lrt \
 		 $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \
-		 $(LIBOSMONETIF_LIBS)
+		 $(LIBOSMONETIF_LIBS) $(LIBBCG729_LIBS)
diff --git a/openbsc/src/osmo-bsc_mgcp/mgcp_main.c b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
index 14ec221..8c3808a 100644
--- a/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
+++ b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
@@ -49,6 +49,10 @@
 
 #include "../../bscconfig.h"
 
+#ifdef BUILD_MGCP_TRANSCODING
+#include "openbsc/mgcp_transcode.h"
+#endif
+
 /* this is here for the vty... it will never be called */
 void subscr_put() { abort(); }
 
@@ -207,6 +211,12 @@
 	if (!cfg)
 		return -1;
 
+#ifdef BUILD_MGCP_TRANSCODING
+	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;
+#endif
+
 	vty_info.copyright = openbsc_copyright;
 	vty_init(&vty_info);
 	logging_vty_add_cmds(&log_info);
diff --git a/openbsc/tests/atlocal.in b/openbsc/tests/atlocal.in
index 4635113..542a78e 100644
--- a/openbsc/tests/atlocal.in
+++ b/openbsc/tests/atlocal.in
@@ -1,3 +1,4 @@
 enable_nat_test='@osmo_ac_build_nat@'
 enable_smpp_test='@osmo_ac_build_smpp@'
 enable_bsc_test='@osmo_ac_build_bsc@'
+enable_mgcp_transcoding_test='@osmo_ac_mgcp_transcoding@'
diff --git a/openbsc/tests/mgcp/Makefile.am b/openbsc/tests/mgcp/Makefile.am
index 79e0bf4..ce9e596 100644
--- a/openbsc/tests/mgcp/Makefile.am
+++ b/openbsc/tests/mgcp/Makefile.am
@@ -1,11 +1,15 @@
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
-AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) $(LIBBCG729_CFLAGS)
 AM_LDFLAGS = $(COVERAGE_LDFLAGS)
 
-EXTRA_DIST = mgcp_test.ok
+EXTRA_DIST = mgcp_test.ok mgcp_transcoding_test.ok
 
 noinst_PROGRAMS = mgcp_test
 
+if BUILD_MGCP_TRANSCODING
+noinst_PROGRAMS += mgcp_transcoding_test
+endif
+
 mgcp_test_SOURCES = mgcp_test.c
 
 mgcp_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
@@ -13,3 +17,12 @@
 		$(top_builddir)/src/libcommon/libcommon.a \
 		$(LIBOSMOCORE_LIBS) -lrt -lm $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) \
 		$(LIBRARY_DL) $(LIBOSMONETIF_LIBS)
+
+mgcp_transcoding_test_SOURCES = mgcp_transcoding_test.c
+
+mgcp_transcoding_test_LDADD = \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmgcp/libmgcp.a \
+		$(top_builddir)/src/libcommon/libcommon.a \
+		$(LIBOSMOCORE_LIBS) $(LIBBCG729_LIBS) -lrt -lm $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS) \
+		$(LIBRARY_DL) $(LIBOSMONETIF_LIBS)
diff --git a/openbsc/tests/mgcp/mgcp_test.c b/openbsc/tests/mgcp/mgcp_test.c
index 5388483..19615d9 100644
--- a/openbsc/tests/mgcp/mgcp_test.c
+++ b/openbsc/tests/mgcp/mgcp_test.c
@@ -168,6 +168,12 @@
 		 "a=rtpmap:99 AMR/8000\r\n"	\
 		 "a=ptime:40\r\n"
 
+#define MDCX4_RO "MDCX 18983221 1@mgw MGCP 1.0\r\n" \
+		 "M: recvonly\r"		\
+		 "C: 2\r\n"          \
+		 "I: 1\r\n"                    \
+		 "L: p:20, a:AMR, nt:IN\r\n"
+
 #define SHORT2	"CRCX 1"
 #define SHORT2_RET "510 000000 FAIL\r\n"
 #define SHORT3	"CRCX 1 1@mgw"
@@ -258,6 +264,7 @@
 	{ "MDCX4_PT2", MDCX4_PT2, MDCX4_RET("18983218"), 99, 126 },
 	{ "MDCX4_PT3", MDCX4_PT3, MDCX4_RET("18983219"), 99, 126 },
 	{ "MDCX4_SO", MDCX4_SO, MDCX4_RET("18983220"), 99, 126 },
+	{ "MDCX4_RO", MDCX4_RO, MDCX4_RET("18983221"), PTYPE_IGNORE, 126 },
 	{ "DLCX", DLCX, DLCX_RET, -1, -1 },
 	{ "CRCX_ZYN", CRCX_ZYN, CRCX_ZYN_RET, 97, 126 },
 	{ "EMPTY", EMPTY, EMPTY_RET },
diff --git a/openbsc/tests/mgcp/mgcp_test.ok b/openbsc/tests/mgcp/mgcp_test.ok
index 3901cfb..7301a81 100644
--- a/openbsc/tests/mgcp/mgcp_test.ok
+++ b/openbsc/tests/mgcp/mgcp_test.ok
@@ -49,6 +49,11 @@
 Detected packet duration: 40
 Requested packetetization period: 20-20
 Connection mode: 2: SEND
+Testing MDCX4_RO
+Dummy packets: 1
+Packet duration not set
+Requested packetetization period: 20-20
+Connection mode: 1: RECV
 Testing DLCX
 Detected packet duration: 20
 Requested packetization period not set
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.c b/openbsc/tests/mgcp/mgcp_transcoding_test.c
new file mode 100644
index 0000000..9ba2c4b
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.c
@@ -0,0 +1,377 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include "bscconfig.h"
+#ifndef BUILD_MGCP_TRANSCODING
+#error "Requires MGCP transcoding enabled (see --enable-mgcp-transcoding)"
+#endif
+
+#include "openbsc/mgcp_transcode.h"
+
+uint8_t *audio_frame_l16[] = {
+};
+
+struct rtp_packets {
+	float t;
+	int len;
+	char *data;
+};
+
+struct rtp_packets audio_packets_l16[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 332,
+		"\x80\x0B\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+		"\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED"
+	},
+};
+
+struct rtp_packets audio_packets_gsm[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_size[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 41,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_data[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE"
+		"\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE"
+		"\xEE"
+	},
+};
+
+struct rtp_packets audio_packets_gsm_invalid_ptype[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 45,
+		"\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B"
+		"\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A"
+		"\xDE"
+	},
+};
+
+struct rtp_packets audio_packets_g729[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 32,
+		"\x80\x12\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xAF\xC2\x81\x40\x00\xFA\xCE\xA4\x21\x7C\xC5\xC3\x4F\xA5\x98\xF5"
+		"\xB2\x95\xC4\xAD"
+	},
+};
+
+struct rtp_packets audio_packets_pcma[] = {
+	/* RTP: SeqNo=1, TS=160 */
+	{0.020000, 172,
+		"\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+		"\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25"
+	},
+};
+
+
+
+static int audio_name_to_type(const char *name)
+{
+	if (!strcasecmp(name, "gsm"))
+		return 3;
+#ifdef HAVE_BCG729
+	else if (!strcasecmp(name, "g729"))
+		return 18;
+#endif
+	else if (!strcasecmp(name, "pcma"))
+		return 8;
+	else if (!strcasecmp(name, "l16"))
+		return 11;
+	return -1;
+}
+
+int mgcp_get_trans_frame_size(void *state_, int nsamples, int dst);
+
+static int transcode_test(const char *srcfmt, const char *dstfmt,
+			  uint8_t *src_pkts, size_t src_pkt_size)
+{
+	char buf[4096] = {0x80, 0};
+	int rc;
+	struct mgcp_rtp_end *dst_end;
+	struct mgcp_rtp_end *src_end;
+	struct mgcp_trunk_config tcfg = {{0}};
+	struct mgcp_endpoint endp = {0};
+	struct mgcp_process_rtp_state *state;
+	int in_size;
+	int in_samples = 160;
+	int len, cont;
+
+	printf("== Transcoding test ==\n");
+	printf("converting %s -> %s\n", srcfmt, dstfmt);
+
+	tcfg.endpoints = &endp;
+	tcfg.number_endpoints = 1;
+	endp.tcfg = &tcfg;
+	mgcp_free_endp(&endp);
+
+	dst_end = &endp.bts_end;
+	src_end = &endp.net_end;
+
+	src_end->payload_type = audio_name_to_type(srcfmt);
+	dst_end->payload_type = audio_name_to_type(dstfmt);
+
+	rc = mgcp_transcoding_setup(&endp, dst_end, src_end);
+	if (rc < 0)
+		errx(1, "setup failed: %s", strerror(-rc));
+
+	state = dst_end->rtp_process_data;
+	OSMO_ASSERT(state != NULL);
+
+	in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0);
+	OSMO_ASSERT(sizeof(buf) >= in_size + 12);
+
+	memcpy(buf, src_pkts, src_pkt_size);
+
+	len = src_pkt_size;
+
+	cont = mgcp_transcoding_process_rtp(&endp, dst_end,
+					    buf, &len, sizeof(buf));
+	if (cont < 0)
+		errx(1, "processing failed: %s", strerror(-cont));
+
+	if (len < 24) {
+		printf("encoded: %s\n", osmo_hexdump((unsigned char *)buf, len));
+	} else {
+		const char *str = osmo_hexdump((unsigned char *)buf, len);
+		int i = 0;
+		const int prefix = 4;
+		const int cutlen = 48;
+		int nchars = 0;
+
+		printf("encoded:\n");
+		do {
+			nchars = printf("%*s%-.*s", prefix, "", cutlen, str + i);
+			i += nchars - prefix;
+			printf("\n");
+		} while (nchars - prefix >= cutlen);
+	}
+	return 0;
+}
+
+static int test_repacking(int in_samples, int out_samples, int no_transcode)
+{
+	char buf[4096] = {0x80, 0};
+	int cc, rc;
+	struct mgcp_rtp_end *dst_end;
+	struct mgcp_rtp_end *src_end;
+	struct mgcp_config *cfg;
+	struct mgcp_trunk_config tcfg = {{0}};
+	struct mgcp_endpoint endp = {0};
+	struct mgcp_process_rtp_state *state;
+	int in_cnt;
+	int out_size;
+	int in_size;
+	uint32_t ts = 0;
+	uint16_t seq = 0;
+	const char *srcfmt = "pcma";
+	const char *dstfmt = no_transcode ? "pcma" : "l16";
+
+	cfg = mgcp_config_alloc();
+
+	tcfg.endpoints = &endp;
+	tcfg.number_endpoints = 1;
+	tcfg.cfg = cfg;
+	endp.tcfg = &tcfg;
+	endp.cfg = cfg;
+	mgcp_free_endp(&endp);
+
+	dst_end = &endp.bts_end;
+	src_end = &endp.net_end;
+
+	printf("== Transcoding test ==\n");
+	printf("converting %s -> %s\n", srcfmt, dstfmt);
+
+	src_end->payload_type = audio_name_to_type(srcfmt);
+	dst_end->payload_type = audio_name_to_type(dstfmt);
+
+	if (out_samples) {
+		dst_end->frame_duration_den = dst_end->rate;
+		dst_end->frame_duration_num = out_samples;
+		dst_end->frames_per_packet = 1;
+		dst_end->force_output_ptime = 1;
+	}
+
+	rc = mgcp_transcoding_setup(&endp, dst_end, src_end);
+	if (rc < 0)
+		errx(1, "setup failed: %s", strerror(-rc));
+
+	state = dst_end->rtp_process_data;
+	OSMO_ASSERT(state != NULL);
+
+	in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0);
+	OSMO_ASSERT(sizeof(buf) >= in_size + 12);
+
+	out_size = mgcp_transcoding_get_frame_size(state, -1, 1);
+	OSMO_ASSERT(sizeof(buf) >= out_size + 12);
+
+	buf[1] = src_end->payload_type;
+	*(uint16_t*)(buf+2) = htons(1);
+	*(uint32_t*)(buf+4) = htonl(0);
+	*(uint32_t*)(buf+8) = htonl(0xaabbccdd);
+
+	for (in_cnt = 0; in_cnt < 16; in_cnt++) {
+		int cont;
+		int len;
+
+		/* fake PCMA data */
+		printf("generating %d %s input samples\n", in_samples, srcfmt);
+		for (cc = 0; cc < in_samples; cc++)
+			buf[12+cc] = cc;
+
+		*(uint16_t*)(buf+2) = htonl(seq);
+		*(uint32_t*)(buf+4) = htonl(ts);
+
+		seq += 1;
+		ts += in_samples;
+
+		cc += 12; /* include RTP header */
+
+		len = cc;
+
+		do {
+			cont = mgcp_transcoding_process_rtp(&endp, dst_end,
+							    buf, &len, sizeof(buf));
+			if (cont == -EAGAIN) {
+				fprintf(stderr, "Got EAGAIN\n");
+				break;
+			}
+
+			if (cont < 0)
+				errx(1, "processing failed: %s", strerror(-cont));
+
+			len -= 12; /* ignore RTP header */
+
+			printf("got %d %s output frames (%d octets)\n",
+			       len / out_size, dstfmt, len);
+
+			len = cont;
+		} while (len > 0);
+	}
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	osmo_init_logging(&log_info);
+
+	printf("=== Transcoding Good Cases ===\n");
+
+	transcode_test("l16", "l16",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("l16", "gsm",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("l16", "pcma",
+		       (uint8_t *)audio_packets_l16[0].data,
+		       audio_packets_l16[0].len);
+	transcode_test("gsm", "l16",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("gsm", "gsm",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm[0].data,
+		       audio_packets_gsm[0].len);
+	transcode_test("pcma", "l16",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+	transcode_test("pcma", "gsm",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+	transcode_test("pcma", "pcma",
+		       (uint8_t *)audio_packets_pcma[0].data,
+		       audio_packets_pcma[0].len);
+
+	printf("=== Transcoding Bad Cases ===\n");
+
+	printf("Invalid size:\n");
+	transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_size[0].data,
+		       audio_packets_gsm_invalid_size[0].len);
+
+	printf("Invalid data:\n");
+	transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_data[0].data,
+		       audio_packets_gsm_invalid_data[0].len);
+
+	printf("Invalid payload type:\n");
+	transcode_test("gsm", "pcma",
+		       (uint8_t *)audio_packets_gsm_invalid_ptype[0].data,
+		       audio_packets_gsm_invalid_ptype[0].len);
+
+	printf("=== Repacking ===\n");
+
+	test_repacking(160, 160, 0);
+	test_repacking(160, 160, 1);
+	test_repacking(160, 80, 0);
+	test_repacking(160, 80, 1);
+	test_repacking(160, 320, 0);
+	test_repacking(160, 320, 1);
+	test_repacking(160, 240, 0);
+	test_repacking(160, 240, 1);
+	test_repacking(160, 100, 0);
+	test_repacking(160, 100, 1);
+
+	return 0;
+}
+
diff --git a/openbsc/tests/mgcp/mgcp_transcoding_test.ok b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
new file mode 100644
index 0000000..189d079
--- /dev/null
+++ b/openbsc/tests/mgcp/mgcp_transcoding_test.ok
@@ -0,0 +1,534 @@
+=== Transcoding Good Cases ===
+== Transcoding test ==
+converting l16 -> l16
+encoded:
+    80 0b 00 01 00 00 00 a0 11 22 33 44 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 
+    5a 9e 40 13 00 00 bf ed a5 62 bf ed 
+== Transcoding test ==
+converting l16 -> gsm
+encoded:
+    80 0b 00 00 00 00 00 a0 11 22 33 44 d4 7c e3 e9 
+    62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc 
+    69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de 
+== Transcoding test ==
+converting l16 -> pcma
+encoded:
+    80 0b 00 00 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 
+== Transcoding test ==
+converting gsm -> l16
+encoded:
+    80 03 00 00 00 00 00 a0 11 22 33 44 00 00 54 00 
+    59 f0 34 20 c4 c8 b9 f8 e2 18 f1 e8 f2 28 f0 e0 
+    46 08 4f 80 2c a0 a9 c8 80 00 c0 58 3f 80 63 c0 
+    24 b8 fa b8 f6 88 0b a0 c8 70 a8 b0 c8 c0 3b a8 
+    66 a0 2e 38 d1 f8 98 98 aa 18 e8 30 26 a0 37 40 
+    37 e8 17 00 ee 50 b7 80 b1 88 de 28 18 40 45 b0 
+    4f 48 21 d8 df 78 ae 68 ab 98 d6 b8 18 18 48 90 
+    4e 70 27 40 e8 10 b5 b0 ac 80 d4 60 14 50 48 48 
+    50 10 2a 00 ec 08 ba 00 af 58 d1 c0 10 60 45 c8 
+    54 10 30 78 f1 a8 bb 18 ad 48 ce 30 0a e8 3f 30 
+    4f 10 32 18 f6 18 bf 20 ac 30 cd 80 0b d0 43 d8 
+    55 e0 34 a0 f5 78 bc 98 ad 98 cd c8 0a 80 40 58 
+    51 c0 35 40 f9 60 c1 68 ac c8 cb 38 08 00 40 98 
+    51 e0 34 d8 fa 28 c2 f0 ae 40 c7 70 02 d0 3c a8 
+    54 78 38 a0 fc 68 c2 08 ad 50 c7 78 01 60 39 c0 
+    51 38 3a e8 00 e8 c6 38 ab d8 c4 00 fe 08 39 18 
+    50 30 39 50 01 d8 ca 70 b1 80 c4 c8 fc 58 36 40 
+    51 d8 3b 08 02 80 c8 58 b0 60 c5 a8 fb d0 33 e8 
+    4e 80 3c e0 06 10 cb 90 ae 48 c2 60 f9 58 34 08 
+    4d a0 3a a8 06 48 cf 80 b4 60 c3 e8 f7 90 30 18 
+    4d a0 3b 98 07 90 cf 18 b4 68 c4 88 
+== Transcoding test ==
+converting gsm -> gsm
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 
+    62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc 
+    69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de 
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 03 00 00 00 00 00 a0 11 22 33 44 d5 a0 a3 bf 
+    38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 
+    17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 
+    04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 
+    02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd 
+    19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf 
+    10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf 
+    62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 
+    db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 
+    f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 
+    ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 
+== Transcoding test ==
+converting pcma -> l16
+encoded:
+    80 08 00 00 00 00 00 a0 11 22 33 44 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 
+    5a 00 42 00 00 08 be 00 a6 00 be 00 
+== Transcoding test ==
+converting pcma -> gsm
+encoded:
+    80 08 00 00 00 00 00 a0 11 22 33 44 d4 b9 f4 5d 
+    d9 50 5a e1 a0 cd 76 ea 52 0e 87 53 ad d4 ea a2 
+    0a 63 ca e9 60 79 e2 2a 25 d2 c0 f3 39 
+== Transcoding test ==
+converting pcma -> pcma
+encoded:
+    80 08 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 
+    d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 
+=== Transcoding Bad Cases ===
+Invalid size:
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 
+    62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc 
+    69 9c d1 f0 66 7a ec 49 7a 
+Invalid data:
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 03 00 01 00 00 00 a0 11 22 33 44 ee ee ee ee 
+    ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee ee 
+    ee ee ee ee ee ee ee ee ee ee ee ee ee 
+Invalid payload type:
+== Transcoding test ==
+converting gsm -> pcma
+encoded:
+    80 08 00 00 00 00 00 a0 11 22 33 44 d5 a0 a3 bf 
+    38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 
+    17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 
+    04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 
+    02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd 
+    19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf 
+    10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf 
+    62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 
+    db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 
+    f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 
+    ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 
+=== Repacking ===
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+generating 160 pcma input samples
+got 2 l16 output frames (320 octets)
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+generating 160 pcma input samples
+got 2 pcma output frames (160 octets)
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 l16 output frames (640 octets)
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 4 pcma output frames (320 octets)
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 l16 output frames (480 octets)
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+generating 160 pcma input samples
+generating 160 pcma input samples
+got 3 pcma output frames (240 octets)
+== Transcoding test ==
+converting pcma -> l16
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+generating 160 pcma input samples
+got 1 l16 output frames (160 octets)
+got 1 l16 output frames (160 octets)
+== Transcoding test ==
+converting pcma -> pcma
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
+generating 160 pcma input samples
+got 1 pcma output frames (80 octets)
+got 1 pcma output frames (80 octets)
diff --git a/openbsc/tests/testsuite.at b/openbsc/tests/testsuite.at
index 4465b25..57310d6 100644
--- a/openbsc/tests/testsuite.at
+++ b/openbsc/tests/testsuite.at
@@ -27,6 +27,13 @@
 AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_test], [], [expout], [ignore])
 AT_CLEANUP
 
+AT_SETUP([mgcp-trans])
+AT_KEYWORDS([mgcp-trans])
+AT_CHECK([test "$enable_mgcp_transcoding_test" == yes || exit 77])
+cat $abs_srcdir/mgcp/mgcp_transcoding_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_transcoding_test], [], [expout], [ignore])
+AT_CLEANUP
+
 AT_SETUP([gprs])
 AT_KEYWORDS([gprs])
 cat $abs_srcdir/gprs/gprs_test.ok > expout