diff mbox

[05/11] mgcp: Add RTP audio transcoding

Message ID 1399891147-31419-5-git-send-email-jerlbeck@sysmocom.de
State Superseded
Headers show

Commit Message

Jacob Erlbeck May 12, 2014, 10:39 a.m. UTC
This patch implements audio transcoding between the formats GSM,
PCMA, L16, and optionally G.729.

The feature needs to be enabled by using the autoconf option
'--enable-mgcp-transcoding'. In this case mgcp_transcode.c will
be compiled and linked to osmo-bsc_mgcp, and the transcoding
functions provided will be registered as processing callbacks.

If G.729 support is required, libcg729 needs to be installed and
'--with-g729' must be passed to ./configure.

Ticket: OW#1111
Sponsored-by: On-Waves ehf
---
 openbsc/configure.ac                       |   15 +
 openbsc/src/osmo-bsc_mgcp/Makefile.am      |   10 +-
 openbsc/src/osmo-bsc_mgcp/g711common.h     |  187 ++++++++++++
 openbsc/src/osmo-bsc_mgcp/mgcp_main.c      |   15 +
 openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c |  459 ++++++++++++++++++++++++++++
 openbsc/src/osmo-bsc_mgcp/mgcp_transcode.h |   34 +++
 6 files changed, 718 insertions(+), 2 deletions(-)
 create mode 100644 openbsc/src/osmo-bsc_mgcp/g711common.h
 create mode 100644 openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c
 create mode 100644 openbsc/src/osmo-bsc_mgcp/mgcp_transcode.h

Comments

Holger Freyther May 14, 2014, 5:46 a.m. UTC | #1
On Mon, May 12, 2014 at 12:39:01PM +0200, Jacob Erlbeck wrote:

> +#include "g711common.h"
> +#include <gsm.h>
> +#include <bcg729/decoder.h>
> +#include <bcg729/encoder.h>

these must be guarded with the approriate defines. Specially the g729
ones. I think you are lucky as /usr/include/bcg729/encoder.h just
exists. :)

> +++ b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c
> @@ -0,0 +1,459 @@
> +/*
> + * (C) 2014 by On-Waves

shared copyright here. :)

> +#include "../../bscconfig.h"

Does this work with make distcheck? srcdir != builddir?

> +	/* cleanup first */
> +	if (state) {
> +		talloc_free(state);
> +		dst_end->rtp_process_data = NULL;

		state = NULL;
> +	}


Or just avoid assigning state that early?

> +		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;

Will the CRCX/MDCX fail in this case? I am a bit too lazy to check this
right now.

> +	/* TODO: remove me
> +	fprintf(stderr, "sample_cnt = %d, sample_idx = %d, plen = %d -> %d, "
> +		"hdr_size = %d, len = %d, pt = %d\n",
> +	       sample_cnt, sample_idx, payload_len, nbytes, rtp_hdr_size, *len,
> +	       data[1]);
> +	       */

You want to keep this for now?

> +#ifndef OPENBSC_MGCP_TRANSCODE_H
> +#define OPENBSC_MGCP_TRANSCODE_H

I started to use "#pragma once". It is supported by GCC for a long
time and even the Microsoft Compiler handles it correctly.
Jacob Erlbeck May 14, 2014, 12:44 p.m. UTC | #2
Hello Holger,

I'm already modifying the patches and planning to send them tomorrow.

On 14.05.2014 07:46, Holger Hans Peter Freyther wrote:
> On Mon, May 12, 2014 at 12:39:01PM +0200, Jacob Erlbeck wrote:
> 
>> +#include "g711common.h"
>> +#include <gsm.h>
>> +#include <bcg729/decoder.h>
>> +#include <bcg729/encoder.h>
> 
> these must be guarded with the approriate defines. Specially the g729
> ones. I think you are lucky as /usr/include/bcg729/encoder.h just
> exists. :)

Thanks for spotting those, they're just left overs and can be removed
completely (the real ones are in mpgc_transcode.c are guarded).

> 
>> +++ b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c
>> @@ -0,0 +1,459 @@
>> +/*
>> + * (C) 2014 by On-Waves
> 
> shared copyright here. :)

By whom?

> 
>> +#include "../../bscconfig.h"
> 
> Does this work with make distcheck? srcdir != builddir?

Apparently yes. This line dates back to 2010
(5a29c7fa895a14112a1ac65e541f4b87ae9dba04) and can be found like that at
several other places too. Removing the '../../' works too, but I don't
want to change that within this commit.

> 
>> +	/* cleanup first */
>> +	if (state) {
>> +		talloc_free(state);
>> +		dst_end->rtp_process_data = NULL;
> 
> 		state = NULL;
>> +	}
> 
> 
> Or just avoid assigning state that early?

This might called multiply during a single call and this way does a full
transcoding reset every time including cleaning up and disabling it when
called with src_end == NULL.

> 
>> +		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;
> 
> Will the CRCX/MDCX fail in this case? I am a bit too lazy to check this
> right now.

No. It just falls back to passing all packets unmodified. I'd rather
like to address this when real negiation is added, but OTOH it wouldn't
be much effort to just terminate the connection in this case.

> 
>> +	/* TODO: remove me
>> +	fprintf(stderr, "sample_cnt = %d, sample_idx = %d, plen = %d -> %d, "
>> +		"hdr_size = %d, len = %d, pt = %d\n",
>> +	       sample_cnt, sample_idx, payload_len, nbytes, rtp_hdr_size, *len,
>> +	       data[1]);
>> +	       */
> 
> You want to keep this for now?

No.

> 
>> +#ifndef OPENBSC_MGCP_TRANSCODE_H
>> +#define OPENBSC_MGCP_TRANSCODE_H
> 
> I started to use "#pragma once". It is supported by GCC for a long
> time and even the Microsoft Compiler handles it correctly.
> 

Hmm, I just found that in only single file yet. I'm just traditionally
reluctant with #pragma'a in general, but if we want to change the style
I can change this accordingly.
Holger Freyther May 15, 2014, 8:59 a.m. UTC | #3
On Wed, May 14, 2014 at 02:44:26PM +0200, Jacob Erlbeck wrote:

Hi!

> > I started to use "#pragma once". It is supported by GCC for a long
> > time and even the Microsoft Compiler handles it correctly.
> > 
> 
> Hmm, I just found that in only single file yet. I'm just traditionally
> reluctant with #pragma'a in general, but if we want to change the style
> I can change this accordingly.

I started to use "#pragma once" in the osmo-pcu. I highly encourage
to use it for new files.
Holger Freyther May 15, 2014, 7:31 p.m. UTC | #4
On Wed, May 14, 2014 at 02:44:26PM +0200, Jacob Erlbeck wrote:

> >> +	/* cleanup first */
> >> +	if (state) {
> >> +		talloc_free(state);
> >> +		dst_end->rtp_process_data = NULL;
> > 
> > 		state = NULL;
> >> +	}
> > 
> > 
> > Or just avoid assigning state that early?
> 
> This might called multiply during a single call and this way does a full
> transcoding reset every time including cleaning up and disabling it when
> called with src_end == NULL.

Yes, my question was maybe use if (dst_end->rtp_process_data) and assign
state only after it. What we should avoid is having state be a dangling
pointer.
diff mbox

Patch

diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index 978f526..ead05af 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -56,6 +56,21 @@  fi
 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/src/osmo-bsc_mgcp/Makefile.am b/openbsc/src/osmo-bsc_mgcp/Makefile.am
index 0456cf1..a620e7a 100644
--- a/openbsc/src/osmo-bsc_mgcp/Makefile.am
+++ b/openbsc/src/osmo-bsc_mgcp/Makefile.am
@@ -1,10 +1,16 @@ 
 AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
 AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) \
-	$(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS)
+	$(LIBOSMOVTY_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS) \
+	$(LIBBCG729_CFLAGS)
 
 bin_PROGRAMS = osmo-bsc_mgcp
 
 osmo_bsc_mgcp_SOURCES = mgcp_main.c
+if BUILD_MGCP_TRANSCODING
+    osmo_bsc_mgcp_SOURCES += mgcp_transcode.c
+endif
 osmo_bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
 		 $(top_builddir)/src/libmgcp/libmgcp.a -lrt \
-		 $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS)
+		 $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) $(LIBBCG729_LIBS)
+
+noinst_HEADERS = g711common.h mgcp_transcode.h
diff --git a/openbsc/src/osmo-bsc_mgcp/g711common.h b/openbsc/src/osmo-bsc_mgcp/g711common.h
new file mode 100644
index 0000000..cb35fc6
--- /dev/null
+++ b/openbsc/src/osmo-bsc_mgcp/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/osmo-bsc_mgcp/mgcp_main.c b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
index 14ec221..5ac4c26 100644
--- a/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
+++ b/openbsc/src/osmo-bsc_mgcp/mgcp_main.c
@@ -31,6 +31,11 @@ 
 
 #include <sys/socket.h>
 
+#include "g711common.h"
+#include <gsm.h>
+#include <bcg729/decoder.h>
+#include <bcg729/encoder.h>
+
 #include <openbsc/debug.h>
 #include <openbsc/gsm_data.h>
 #include <openbsc/mgcp.h>
@@ -49,6 +54,10 @@ 
 
 #include "../../bscconfig.h"
 
+#ifdef BUILD_MGCP_TRANSCODING
+#include "mgcp_transcode.h"
+#endif
+
 /* this is here for the vty... it will never be called */
 void subscr_put() { abort(); }
 
@@ -207,6 +216,12 @@  int main(int argc, char **argv)
 	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/src/osmo-bsc_mgcp/mgcp_transcode.c b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c
new file mode 100644
index 0000000..c6b2508
--- /dev/null
+++ b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.c
@@ -0,0 +1,459 @@ 
+/*
+ * (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;
+};
+
+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 = dst_end->rtp_process_data;
+	enum audio_format src_fmt, dst_fmt;
+
+	/* cleanup first */
+	if (state) {
+		talloc_free(state);
+		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;
+	}
+
+	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;
+}
+
+
+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;
+	size_t sample_cnt = 0;
+	size_t sample_idx;
+	int16_t samples[10*160];
+	uint8_t *src = (uint8_t *)payload_data;
+	uint8_t *dst = (uint8_t *)payload_data;
+	size_t nbytes = payload_len;
+	size_t frame_remainder;
+
+	if (!state)
+		return 0;
+
+	if (state->src_fmt == state->dst_fmt)
+		return 0;
+
+	/* TODO: check payload type (-> G.711 comfort noise) */
+
+	/* Decode src into samples */
+	while (nbytes >= state->src_frame_size) {
+		if (sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(samples)) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Sample buffer too small: %d > %d.\n",
+			     sample_cnt + state->src_samples_per_frame,
+			     ARRAY_SIZE(samples));
+			return -ENOSPC;
+		}
+		switch (state->src_fmt) {
+		case AF_GSM:
+			if (gsm_decode(state->src.gsm_handle,
+				       (gsm_byte *)src, samples + 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, samples + sample_cnt);
+			break;
+#endif
+		case AF_PCMA:
+			alaw_decode(src, samples + sample_cnt,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(samples + sample_cnt, src,
+				state->src_frame_size);
+			break;
+		case AF_L16:
+			l16_decode(src, samples + sample_cnt,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		src        += state->src_frame_size;
+		nbytes     -= state->src_frame_size;
+		sample_cnt += state->src_samples_per_frame;
+	}
+
+	/* Add silence if necessary */
+	frame_remainder = sample_cnt % state->dst_samples_per_frame;
+	if (frame_remainder) {
+		size_t silence = state->dst_samples_per_frame - frame_remainder;
+		if (sample_cnt + silence > ARRAY_SIZE(samples)) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Sample buffer too small for silence: %d > %d.\n",
+			     sample_cnt + silence,
+			     ARRAY_SIZE(samples));
+			return -ENOSPC;
+		}
+
+		while (silence > 0) {
+			samples[sample_cnt] = 0;
+			sample_cnt += 1;
+			silence -= 1;
+		}
+	}
+
+	/* Encode samples into dst */
+	sample_idx = 0;
+	nbytes = 0;
+	while (sample_idx + state->dst_samples_per_frame <= sample_cnt) {
+		if (nbytes + state->dst_frame_size > buf_size) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "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,
+				   samples + sample_idx, dst);
+			break;
+#ifdef HAVE_BCG729
+		case AF_G729:
+			bcg729Encoder(state->dst.g729_enc,
+				      samples + sample_idx, dst);
+			break;
+#endif
+		case AF_PCMA:
+			alaw_encode(samples + sample_idx, dst,
+				    state->src_samples_per_frame);
+			break;
+		case AF_S16:
+			memmove(dst, samples + sample_idx, state->dst_frame_size);
+			break;
+		case AF_L16:
+			l16_encode(samples + sample_idx, dst,
+				   state->src_samples_per_frame);
+			break;
+		default:
+			break;
+		}
+		dst        += state->dst_frame_size;
+		nbytes     += state->dst_frame_size;
+		sample_idx += state->dst_samples_per_frame;
+	}
+
+	*len = rtp_hdr_size + nbytes;
+	/* Patch payload type */
+	data[1] = (data[1] & 0x80) | (dst_end->payload_type & 0x7f);
+
+	/* TODO: remove me
+	fprintf(stderr, "sample_cnt = %d, sample_idx = %d, plen = %d -> %d, "
+		"hdr_size = %d, len = %d, pt = %d\n",
+	       sample_cnt, sample_idx, payload_len, nbytes, rtp_hdr_size, *len,
+	       data[1]);
+	       */
+
+	return 0;
+}
diff --git a/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.h b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.h
new file mode 100644
index 0000000..2dfb06a
--- /dev/null
+++ b/openbsc/src/osmo-bsc_mgcp/mgcp_transcode.h
@@ -0,0 +1,34 @@ 
+/*
+ * (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);
+#endif /* OPENBSC_MGCP_TRANSCODE_H */