From cfbdb19d57a87c39a1f9f2613f7c9f572502cd5f Mon Sep 17 00:00:00 2001 From: Christian Hoene Date: Fri, 22 Apr 2016 16:08:07 +0200 Subject: First version with MPA coding --- modules/mpa/decode.c | 136 +++++++++++++++++++++++++++++++++++ modules/mpa/encode.c | 141 ++++++++++++++++++++++++++++++++++++ modules/mpa/module.mk | 14 ++++ modules/mpa/module.mk~ | 14 ++++ modules/mpa/mpa.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ modules/mpa/mpa.h | 32 +++++++++ modules/mpa/sdp.c | 54 ++++++++++++++ 7 files changed, 580 insertions(+) create mode 100644 modules/mpa/decode.c create mode 100644 modules/mpa/encode.c create mode 100644 modules/mpa/module.mk create mode 100644 modules/mpa/module.mk~ create mode 100644 modules/mpa/mpa.c create mode 100644 modules/mpa/mpa.h create mode 100644 modules/mpa/sdp.c (limited to 'modules/mpa') diff --git a/modules/mpa/decode.c b/modules/mpa/decode.c new file mode 100644 index 0000000..11ab8b4 --- /dev/null +++ b/modules/mpa/decode.c @@ -0,0 +1,136 @@ +/** + * @file mpa/decode.c mpa Decode + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include +#include +#include +#include "mpa.h" + + +struct audec_state { + mpg123_handle *dec; +}; + + +static void destructor(void *arg) +{ + struct audec_state *ads = arg; + + mpg123_close(ads->dec); + mpg123_delete(ads->dec); +} + + +int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + struct audec_state *ads; + int mpaerr, err=0; + + if (!adsp || !ac || !ac->ch) + return EINVAL; + + ads = *adsp; + + if (ads) + return 0; + + ads = mem_zalloc(sizeof(*ads), destructor); + if (!ads) + return ENOMEM; + + mpg123_delete(ads->dec); + ads->dec = mpg123_new(NULL,&mpaerr); + if (!ads->dec) { + warning("mpa: decoder create: %s\n", mpg123_plain_strerror(mpaerr)); + err = ENOMEM; + goto out; + } + + mpaerr = mpg123_param(ads->dec, MPG123_VERBOSE, 4, 4.); + if(mpaerr != MPG123_OK) { + error("MPA libmpg123 param error %s", mpg123_plain_strerror(mpaerr)); + return EINVAL; + } + + + mpaerr = mpg123_format(ads->dec, 48000 /*ac->srate*/, 2 /*ac->ch*/, MPG123_ENC_SIGNED_16); + if(mpaerr != MPG123_OK) { + error("MPA libmpg123 format error %s", mpg123_plain_strerror(mpaerr)); + return EINVAL; + } + + mpaerr = mpg123_open_feed(ads->dec); + if(mpaerr != MPG123_OK) { + error("MPA libmpg123 open feed error %s", mpg123_plain_strerror(mpaerr)); + return EINVAL; + } + + + 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 mpaerr, channels, encoding; + long samplerate; + size_t n; + uint32_t header; + + if (!ads || !sampv || !sampc || !buf) + return EINVAL; + + if(len<=4) + return EINVAL; + header = *(uint32_t*)buf; + if(header != 0) { + error("MPA header is not zero %08X\n", header); + return EPROTO; + } + + + mpaerr = mpg123_decode(ads->dec, buf+4, len-4, (unsigned char*)sampv, *sampc*2, &n); + if(mpaerr == MPG123_NEW_FORMAT) { + mpg123_getformat(ads->dec, &samplerate, &channels, &encoding); + info("MPA libmpg123 format change %d %d %04X\n",samplerate,channels,encoding); + } + else if(mpaerr == MPG123_NEED_MORE) + return 0; + else if(mpaerr != MPG123_OK) { + error("MPA libmpg123 feed error %d %s", mpaerr, mpg123_plain_strerror(mpaerr)); + return EPROTO; + } + +// warning("mpa decode %d %d %d\n",*sampc,len,n); + *sampc = n / 2; + + return 0; +} + +int mpa_decode_pkloss(struct audec_state *ads, int16_t *sampv, size_t *sampc) +{ + if (!ads || !sampv || !sampc) + return EINVAL; + + warning("mpa packet loss %d\n",*sampc); +// n = opus_decode(ads->dec, NULL, 0, sampv, (int)(*sampc/ads->ch), 0); +// if (n < 0) +// return EPROTO; + +// *sampc = n * ads->ch; + + return 0; +} + + diff --git a/modules/mpa/encode.c b/modules/mpa/encode.c new file mode 100644 index 0000000..1195844 --- /dev/null +++ b/modules/mpa/encode.c @@ -0,0 +1,141 @@ +/** + * @file mpa/encode.c mpa Encode + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include +#include +#include +#include +#include +#include "mpa.h" + + +struct auenc_state { + twolame_options *enc; + int channels; + SpeexResamplerState *resampler; +}; + + +static void destructor(void *arg) +{ + struct auenc_state *aes = arg; + + if (aes->enc) + twolame_close(&aes->enc); +} + +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; + const struct aucodec *auc = aucodec_find("MPA", 90000, 1); + int mpares; + + (void)param; + + if (!aesp || !ac || !ac->ch) + return EINVAL; + + aes = *aesp; + + if (!aes) { + aes = mem_zalloc(sizeof(*aes), destructor); + aes->enc = twolame_init(); + if (!aes->enc) { + warning("mpa: encoder create failed"); + mem_deref(aes); + return ENOMEM; + } + aes->channels = auc->ch; + *aesp = aes; + } + + prm.samplerate = 32000; + prm.bitrate = 128000; + prm.layer = 2; + prm.mode = STEREO; + mpa_decode_fmtp(&prm, fmtp); + + mpares = 0; + mpares |= twolame_set_verbosity(aes->enc, 5); + mpares |= 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); + mpares |= twolame_set_version(aes->enc, prm.samplerate < 32000 ? TWOLAME_MPEG2 : TWOLAME_MPEG1); + mpares |= twolame_set_bitrate(aes->enc, prm.bitrate/1000); + mpares |= twolame_set_in_samplerate(aes->enc, prm.samplerate); + mpares |= twolame_set_out_samplerate(aes->enc, prm.samplerate); + mpares |= twolame_set_num_channels(aes->enc, 2); + if(mpares!=0) { + warning("mpa: encoder set failed\n"); + return EINVAL; + } + + twolame_print_config(aes->enc); + + mpares = twolame_init_params(aes->enc); + if(mpares!=0) { + warning("mpa: encoder init params failed\n"); + return EINVAL; + } + + if(prm.samplerate != 48000) { + aes->resampler = speex_resampler_init(2, 48000, prm.samplerate, 3, &mpares); + if(mpares!=RESAMPLER_ERR_SUCCESS) { + warning("mpa: resampler init failed %d\n",mpares); + return EINVAL; + } + + } + else + aes->resampler = NULL; + return 0; +} + + +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 ds_len,in_len; + static int16_t ds[1920]; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + if(aes->resampler) { + in_len = sampc/2; + n=speex_resampler_process_interleaved_int(aes->resampler, sampv, &in_len, ds, &ds_len); + if (n!=RESAMPLER_ERR_SUCCESS || in_len != sampc/2) { + warning("mpa: downsample error: %s\n", strerror(n)); + return EPROTO; + } + n = twolame_encode_buffer_interleaved(aes->enc, ds, ds_len/2, + buf+4, (*len)-4); + warning("mpa encode %d %d %d %d %d\n",ds_len,sampc,aes->channels,*len,n); + } + else + n = twolame_encode_buffer_interleaved(aes->enc, sampv, (int)(sampc/2), + buf+4, (*len)-4); + + if (n < 0) { + warning("mpa: encode error: %s\n", strerror((int)n)); + return EPROTO; + } + + if(n > 0) { + *(uint32_t*)buf = 0; + *len = n+4; + } + else + *len = 0; + +// warning("mpa encode %d %d %d %d\n",sampc,aes->channels,*len,n); + 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/module.mk~ b/modules/mpa/module.mk~ new file mode 100644 index 0000000..9b4b98c --- /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 -lm + +include mk/mod.mk diff --git a/modules/mpa/mpa.c b/modules/mpa/mpa.c new file mode 100644 index 0000000..cf0a273 --- /dev/null +++ b/modules/mpa/mpa.c @@ -0,0 +1,189 @@ +/** + * @file mpa.c mpa Audio Codec + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include +#include +#include +#include +#include "mpa.h" +#include + +/** + * @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, + .name = "MPA", + .srate = 90000, + .ch = 1, + .fmtp = "", + .encupdh = mpa_encode_update, + .ench = mpa_encode_frm, + .decupdh = mpa_decode_update, + .dech = mpa_decode_frm, + .plch = mpa_decode_pkloss, +}; + + +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)) { + (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]==';') + mpa.fmtp = fmtp+1; + 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..f9c2934 --- /dev/null +++ b/modules/mpa/mpa.h @@ -0,0 +1,32 @@ +/** + * @file mpa.h Private mpa Interface + * + * Copyright (C) 2016 Symonics GmbH + */ + + +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); +int mpa_decode_pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc); + + +/* 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..0e4d811 --- /dev/null +++ b/modules/mpa/sdp.c @@ -0,0 +1,54 @@ +/** + * @file mpa/sdp.c mpa SDP Functions + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include +#include +#include +#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, 32000, 512000); + + 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; + } +} -- cgit v1.2.3