summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2016-04-24 16:06:20 +0200
committerAlfred E. Heggestad <aeh@db.org>2016-04-24 16:06:20 +0200
commit936a29e4eb8deac34fd902cd84653f5411f6f768 (patch)
tree248fb218393c4c42f0cfe266b922efe3767f5af5 /modules
parent488fa550651037a7e7f7ff21a4a11369ab3afec2 (diff)
parent1081461b418ef49943be1adae8752ad41052ef87 (diff)
Merge pull request #125 from choene/master
Added support for MPA coding
Diffstat (limited to 'modules')
-rw-r--r--modules/mpa/decode.c199
-rw-r--r--modules/mpa/encode.c175
-rw-r--r--modules/mpa/module.mk14
-rw-r--r--modules/mpa/mpa.c201
-rw-r--r--modules/mpa/mpa.h35
-rw-r--r--modules/mpa/sdp.c55
6 files changed, 679 insertions, 0 deletions
diff --git a/modules/mpa/decode.c b/modules/mpa/decode.c
new file mode 100644
index 0000000..d62bd60
--- /dev/null
+++ b/modules/mpa/decode.c
@@ -0,0 +1,199 @@
+/**
+ * @file mpa/decode.c mpa Decode
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <mpg123.h>
+#include <speex/speex_resampler.h>
+#include <string.h>
+#include "mpa.h"
+
+struct audec_state {
+ mpg123_handle *dec;
+ SpeexResamplerState *resampler;
+ int channels;
+ int16_t intermediate_buffer[MPA_FRAMESIZE*2];
+ int start;
+};
+
+
+static void destructor(void *arg)
+{
+ struct audec_state *ads = arg;
+
+ mpg123_close(ads->dec);
+ mpg123_delete(ads->dec);
+#ifdef DEBUG
+ debug("mpa: decoder destroyed\n");
+#endif
+}
+
+
+int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp)
+{
+ struct audec_state *ads;
+ int result, err=0;
+
+ if (!adsp || !ac || !ac->ch)
+ return EINVAL;
+
+ ads = *adsp;
+
+#ifdef DEBUG
+ debug("mpa: decoder created %s\n",fmtp);
+#endif
+
+ if (ads)
+ mem_deref(ads);
+
+ ads = mem_zalloc(sizeof(*ads), destructor);
+ if (!ads)
+ return ENOMEM;
+ ads->channels = 0;
+ ads->resampler = NULL;
+ ads->start = 0;
+
+ ads->dec = mpg123_new(NULL,&result);
+ if (!ads->dec) {
+ error("mpa: decoder create: %s\n",
+ mpg123_plain_strerror(result));
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef DEBUG
+ result = mpg123_param(ads->dec, MPG123_VERBOSE, 4, 4.);
+#else
+ result = mpg123_param(ads->dec, MPG123_VERBOSE, 0, 0.);
+#endif
+ if (result != MPG123_OK) {
+ error("MPA libmpg123 param error %s",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+
+ result = mpg123_format_all(ads->dec);
+ if (result != MPG123_OK) {
+ error("MPA libmpg123 format error %s",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+ result = mpg123_open_feed(ads->dec);
+ if (result != MPG123_OK) {
+ error("MPA libmpg123 open feed error %s",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+
+ out:
+ if (err)
+ mem_deref(ads);
+ else
+ *adsp = ads;
+
+ return err;
+}
+
+
+int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int result, channels, encoding, i;
+ long samplerate;
+ size_t n;
+ spx_uint32_t intermediate_len;
+ spx_uint32_t out_len;
+
+ if (!ads || !sampv || !sampc || !buf || len<=4)
+ return EINVAL;
+
+ if(*(uint32_t*)buf != 0) {
+ error("MPA header is not zero %08X, not supported yet\n",
+ *(uint32_t*)buf);
+ return EPROTO;
+ }
+
+ if (ads->resampler) {
+ result = mpg123_decode(ads->dec, buf+4, len-4,
+ (unsigned char*)ads->intermediate_buffer,
+ sizeof(ads->intermediate_buffer), &n);
+ /* n counts bytes */
+ intermediate_len = n / 2 / ads->channels;
+ /* intermediate_len counts samples per channel */
+ out_len = *sampc;
+ result=speex_resampler_process_interleaved_int(
+ ads->resampler, ads->intermediate_buffer,
+ &intermediate_len, sampv, &out_len);
+ if (result!=RESAMPLER_ERR_SUCCESS) {
+ error("mpa: upsample error: %s %d %d\n",
+ strerror(result), out_len, *sampc/2);
+ return EPROTO;
+ }
+#ifdef DEBUG
+ info("mpa decode %d %d %d %d\n",intermediate_len,*sampc,
+ out_len,n);
+#endif
+ *sampc = out_len * ads->channels;
+ }
+ else {
+ result = mpg123_decode(ads->dec, buf+4, len-4,
+ (unsigned char*)sampv, *sampc*2, &n);
+#ifdef DEBUG
+ info("mpa decode %d %d\n",*sampc,n);
+#endif
+ *sampc = n / 2;
+ }
+
+ if (ads->start<100) { /* mpg123 needs some to sync */
+ ads->start++;
+ *sampc=0;
+ }
+ if (ads->channels==1) {
+ for (i=*sampc-1;i>=0;i--)
+ sampv[i+i+1]=sampv[i+i]=sampv[i];
+ *sampc *= 2;
+ }
+
+ if (result == MPG123_NEW_FORMAT) {
+ mpg123_getformat(ads->dec, &samplerate, &channels, &encoding);
+ info("MPA libmpg123 format change %d %d %04X\n",samplerate
+ ,channels,encoding);
+
+ ads->channels = channels;
+ ads->start = 0;
+ if (samplerate != 48000) {
+ ads->resampler = speex_resampler_init(channels,
+ samplerate, 48000, 3, &result);
+ if (result!=RESAMPLER_ERR_SUCCESS
+ || ads->resampler==NULL) {
+ error("mpa: upsampler failed %d\n",result);
+ return EINVAL;
+ }
+ }
+ else
+ ads->resampler = NULL;
+ }
+ else if (result == MPG123_NEED_MORE)
+ return 0;
+ else if (result != MPG123_OK) {
+ error("MPA libmpg123 feed error %d %s", result,
+ mpg123_plain_strerror(result));
+ return EPROTO;
+ }
+
+#ifdef DEBUG
+ debug("mpa decode %d %d %d\n",*sampc,len,n);
+#endif
+ return 0;
+}
+
diff --git a/modules/mpa/encode.c b/modules/mpa/encode.c
new file mode 100644
index 0000000..6ed6493
--- /dev/null
+++ b/modules/mpa/encode.c
@@ -0,0 +1,175 @@
+/**
+ * @file mpa/encode.c mpa Encode
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <twolame.h>
+#include <string.h>
+#include <speex/speex_resampler.h>
+#include "mpa.h"
+
+struct auenc_state {
+ twolame_options *enc;
+ int channels;
+ SpeexResamplerState *resampler;
+ int16_t intermediate_buffer[BARESIP_FRAMESIZE];
+};
+
+
+static void destructor(void *arg)
+{
+ struct auenc_state *aes = arg;
+
+ if (aes->enc)
+ twolame_close(&aes->enc);
+#ifdef DEBUG
+ debug("mpa: encoder destroyed\n");
+#endif
+}
+
+int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *param, const char *fmtp)
+{
+ struct auenc_state *aes;
+ struct mpa_param prm;
+ int result,err=0;
+
+ (void)param;
+
+ if (!aesp || !ac || !ac->ch)
+ return EINVAL;
+
+ aes = *aesp;
+ if (aes) {
+ info("ever?");
+ mem_deref(aes);
+ }
+
+ aes = mem_zalloc(sizeof(*aes), destructor);
+ aes->enc = twolame_init();
+ if (!aes->enc) {
+ error("mpa: encoder create failed");
+ mem_deref(aes);
+ return ENOMEM;
+ }
+ aes->channels = ac->ch;
+#ifdef DEBUG
+ debug("mpa: encoder created %s\n",fmtp);
+#endif
+
+ prm.samplerate = 32000;
+ prm.bitrate = 128000;
+ prm.layer = 2;
+ prm.mode = SINGLE_CHANNEL;
+ mpa_decode_fmtp(&prm, fmtp);
+
+ result = 0;
+#ifdef DEBUG
+ result |= twolame_set_verbosity(aes->enc, 5);
+#else
+ result |= twolame_set_verbosity(aes->enc, 0);
+#endif
+
+ result |= twolame_set_mode(aes->enc,
+ prm.mode == SINGLE_CHANNEL ? TWOLAME_MONO :
+ prm.mode == DUAL_CHANNEL ? TWOLAME_DUAL_CHANNEL :
+ prm.mode == JOINT_STEREO ? TWOLAME_JOINT_STEREO :
+ prm.mode == STEREO ? TWOLAME_STEREO : TWOLAME_AUTO_MODE);
+ result |= twolame_set_version(aes->enc,
+ prm.samplerate < 32000 ? TWOLAME_MPEG2 : TWOLAME_MPEG1);
+ result |= twolame_set_bitrate(aes->enc, prm.bitrate/1000);
+ result |= twolame_set_in_samplerate(aes->enc, prm.samplerate);
+ result |= twolame_set_out_samplerate(aes->enc, prm.samplerate);
+ result |= twolame_set_num_channels(aes->enc, 2);
+ if (result!=0) {
+ error("mpa: encoder set failed\n");
+ err=EINVAL;
+ goto out;
+ }
+
+ result = twolame_init_params(aes->enc);
+ if (result!=0) {
+ error("mpa: encoder init params failed\n");
+ err=EINVAL;
+ goto out;
+ }
+
+ twolame_print_config(aes->enc);
+
+ if (prm.samplerate != 48000) {
+ aes->resampler = speex_resampler_init(2, 48000,
+ prm.samplerate, 3, &result);
+ if (result!=RESAMPLER_ERR_SUCCESS) {
+ error("mpa: resampler init failed %d\n",result);
+ err=EINVAL;
+ goto out;
+ }
+
+ }
+ else
+ aes->resampler = NULL;
+
+out:
+ if (err)
+ mem_deref(aes);
+ else
+ *aesp = aes;
+
+ return err;
+}
+
+
+int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int n;
+ spx_uint32_t intermediate_len,in_len;
+
+ if (!aes || !buf || !len || !sampv)
+ return EINVAL;
+
+ if (aes->resampler) {
+ in_len = sampc/2;
+ intermediate_len = sizeof(aes->intermediate_buffer)
+ / sizeof(aes->intermediate_buffer[0]);
+ n=speex_resampler_process_interleaved_int(aes->resampler,
+ sampv, &in_len, aes->intermediate_buffer,
+ &intermediate_len);
+ if (n!=RESAMPLER_ERR_SUCCESS || in_len != sampc/2) {
+ warning("mpa: downsample error: %s %d %d\n",
+ strerror(n), in_len, sampc/2);
+ return EPROTO;
+ }
+ n = twolame_encode_buffer_interleaved(aes->enc,
+ aes->intermediate_buffer, intermediate_len,
+ buf+4, (*len)-4);
+#ifdef DEBUG
+ debug("mpa encode %d %d %d %d %d\n",intermediate_len,sampc,
+ aes->channels,*len,n);
+#endif
+ }
+ else
+ n = twolame_encode_buffer_interleaved(aes->enc, sampv,
+ (int)(sampc/2), buf+4, (*len)-4);
+
+ if (n < 0) {
+ error("mpa: encode error: %s\n", strerror((int)n));
+ return EPROTO;
+ }
+
+ if (n > 0) {
+ *(uint32_t*)buf = 0;
+ *len = n+4;
+ }
+ else
+ *len = 0;
+
+#ifdef DEBUG
+ debug("mpa encode %d %d %d %d\n",sampc,aes->channels,*len,n);
+#endif
+ return 0;
+}
+
diff --git a/modules/mpa/module.mk b/modules/mpa/module.mk
new file mode 100644
index 0000000..b060f12
--- /dev/null
+++ b/modules/mpa/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2016 Symonics GmbH
+#
+
+MOD := mpa
+$(MOD)_SRCS += mpa.c
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_SRCS += encode.c
+$(MOD)_LFLAGS += -ltwolame -lmpg123 -lspeexdsp -lm
+
+include mk/mod.mk
diff --git a/modules/mpa/mpa.c b/modules/mpa/mpa.c
new file mode 100644
index 0000000..88ebfbe
--- /dev/null
+++ b/modules/mpa/mpa.c
@@ -0,0 +1,201 @@
+/**
+ * @file mpa.c mpa Audio Codec
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <ctype.h>
+#include <string.h>
+#include "mpa.h"
+#include <mpg123.h>
+
+/**
+ * @defgroup mpa mpa
+ *
+ * The mpa audio codec
+ *
+ * Supported version:
+ * libmpg123 1.16.0 or later
+ * libtwolame 0.3.13 or later
+ *
+ * References:
+ *
+ * RFC 2250 RTP Payload Format for the mpa Speech and Audio Codec
+ *
+ */
+
+/*
+4.1.17. Registration of MIME media type audio/MPA
+
+ MIME media type name: audio
+
+ MIME subtype name: MPA (MPEG audio)
+
+ Required parameters: None
+
+ Optional parameters:
+ layer: which layer of MPEG audio encoding; permissible values
+ are 1, 2, 3.
+
+ samplerate: the rate at which audio is sampled. MPEG-1 audio
+ supports sampling rates of 32, 44.1, and 48 kHz; MPEG-2
+ supports sampling rates of 16, 22.05 and 24 kHz. This parameter
+ is separate from the RTP timestamp clock rate which is always
+ 90000 Hz for MPA.
+
+ mode: permissible values are "stereo", "joint_stereo",
+ "single_channel", "dual_channel". The "channels" parameter
+ does not apply to MPA. It is undefined to put a number of
+ channels in the SDP rtpmap attribute for MPA.
+
+ bitrate: the data rate for the audio bit stream.
+
+ ptime: RECOMMENDED duration of each packet in milliseconds.
+
+ maxptime: maximum duration of each packet in milliseconds.
+
+ Parameters which are omitted are left to the encoder to choose
+ based on the session bandwidth, configuration information, or
+ other constraints. The selected layer as well as the sampling
+ rate and mode are indicated in the payload so receivers can
+ process the data without these parameters being specified
+ externally.
+
+ Encoding considerations:
+ This type is only defined for transfer via RTP [RFC 3550].
+
+ Security considerations: See Section 5 of RFC 3555
+
+ Interoperability considerations: none
+
+ Published specification: RFC 3551
+
+ Applications which use this media type:
+ Audio and video streaming and conferencing tools.
+
+*/
+
+
+static struct aucodec mpa = {
+ .pt = NULL, /* for the time being, to cope
+with AVT AC1 interop problems, we do not use "14" here */
+ .name = "MPA",
+ .srate = 90000,
+ .ch = 1,
+ .fmtp = "",
+ .encupdh = mpa_encode_update,
+ .ench = mpa_encode_frm,
+ .decupdh = mpa_decode_update,
+ .dech = mpa_decode_frm,
+};
+
+
+static int module_init(void)
+{
+ struct conf *conf = conf_cur();
+ uint32_t value;
+ static char fmtp[256];
+ static char mode[30];
+ int res;
+
+ /** generate fmtp string based on config file */
+
+ strcpy(mode,mpa.fmtp);
+
+ if (0 == conf_get_u32(conf, "mpa_bitrate", &value)) {
+ if (value<8000 || value>384000) {
+ error("MPA bitrate between 8000 and "
+ "384000 are allowed.");
+ return -1;
+ }
+
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; bitrate=%d", value);
+ }
+ if (0 == conf_get_u32(conf, "mpa_layer", &value)) {
+ if (value<1 || value>4) {
+ error("MPA layer 1, 2 or 3 are allowed.");
+ return -1;
+ }
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; layer=%d", value);
+ }
+ if (0 == conf_get_u32(conf, "mpa_samplerate", &value)) {
+ switch (value) {
+ case 32000:
+ case 44100:
+ case 48000:
+ case 16000:
+ case 22050:
+ case 24000:
+ break;
+ default:
+ error("MPA samplerates of 16, 22.05, 24, 32, "
+ "44.1, and 48 kHz are allowed.");
+ return -1;
+ }
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; samplerate=%d", value);
+ }
+ if (0 == conf_get_str(conf, "mpa_mode", mode, sizeof(mode))) {
+ char *p = mode;
+ while (*p) {
+ *p = tolower(*p);
+ ++p;
+ }
+
+ if (strcmp(mode,"stereo")
+ && strcmp(mode,"joint_stereo")
+ && strcmp(mode,"single_channel")
+ && strcmp(mode,"dual_channel")) {
+ error("MPA mode: Permissible values are stereo, "
+ "joint_stereo, single_channel, dual_channel");
+ return -1;
+ }
+
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; mode=%s", mode);
+ }
+
+ if (fmtp[0]==';' && fmtp[1]==' ')
+ mpa.fmtp = fmtp+2;
+ else
+ mpa.fmtp = fmtp;
+
+ /* init decoder library */
+ res = mpg123_init();
+ if (res != MPG123_OK) {
+ error("MPA libmpg123 init error %s",
+ mpg123_plain_strerror(res));
+ return -1;
+ }
+
+ aucodec_register(&mpa);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&mpa);
+
+ mpg123_exit();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(mpa) = {
+ "MPA",
+ "audio codec",
+ module_init,
+ module_close,
+};
+
diff --git a/modules/mpa/mpa.h b/modules/mpa/mpa.h
new file mode 100644
index 0000000..35055b6
--- /dev/null
+++ b/modules/mpa/mpa.h
@@ -0,0 +1,35 @@
+/**
+ * @file mpa.h Private mpa Interface
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#define MPA_FRAMESIZE 1152
+#define BARESIP_FRAMESIZE (48000/50*2)
+
+#undef DEBUG
+
+struct mpa_param {
+ unsigned samplerate;
+ unsigned bitrate;
+ unsigned layer;
+ enum { AUTO=0, STEREO, JOINT_STEREO, SINGLE_CHANNEL, DUAL_CHANNEL }
+ mode;
+};
+
+
+/* Encode */
+int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc);
+
+
+/* Decode */
+int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp);
+int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len);
+
+/* SDP */
+void mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp);
diff --git a/modules/mpa/sdp.c b/modules/mpa/sdp.c
new file mode 100644
index 0000000..363b72c
--- /dev/null
+++ b/modules/mpa/sdp.c
@@ -0,0 +1,55 @@
+/**
+ * @file mpa/sdp.c mpa SDP Functions
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <string.h>
+#include "mpa.h"
+
+
+static void assign_if (uint32_t *v, const struct pl *pl,
+ uint32_t min, uint32_t max)
+{
+ const uint32_t val = pl_u32(pl);
+
+ if (val < min || val > max)
+ return;
+
+ *v = val;
+}
+
+
+void mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp)
+{
+ struct pl pl, val;
+
+ if (!prm || !fmtp)
+ return;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "bitrate", &val))
+ assign_if (&prm->bitrate, &val, 8000, 384000);
+
+ if (fmt_param_get(&pl, "samplerate", &val))
+ assign_if (&prm->samplerate, &val, 16000, 48000);
+
+ if (fmt_param_get(&pl, "layer", &val))
+ assign_if (&prm->layer, &val, 1, 3);
+
+ if (fmt_param_get(&pl, "mode", &val)) {
+
+ if (!strncmp("stereo",pl.p,pl.l))
+ prm->mode = STEREO;
+ else if (!strncmp("joint_stereo",pl.p,pl.l))
+ prm->mode = JOINT_STEREO;
+ else if (!strncmp("single_channel",pl.p,pl.l))
+ prm->mode = SINGLE_CHANNEL;
+ else if (!strncmp("dual_channel",pl.p,pl.l))
+ prm->mode = DUAL_CHANNEL;
+ }
+}
+