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