mgcp: Add RTP audio transcoding

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
This commit is contained in:
Jacob Erlbeck 2014-03-13 14:25:51 +01:00
parent 997e1e8e9d
commit 239a853f40
6 changed files with 706 additions and 2 deletions

View File

@ -57,6 +57,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)

View File

@ -1,11 +1,17 @@
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) \
$(LIBOSMONETIF_LIBS)
$(LIBOSMONETIF_LIBS) $(LIBBCG729_LIBS)
noinst_HEADERS = g711common.h mgcp_transcode.h

View File

@ -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));
}

View File

@ -49,6 +49,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 +211,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);

View File

@ -0,0 +1,452 @@
/*
* (C) 2014 by Sysmocom s.f.m.c. GmbH
* (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;
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;
}
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);
return 0;
}

View File

@ -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 */