From 3ccf1704c16ef32ae8d2469f2da960021310dfd5 Mon Sep 17 00:00:00 2001
From: Dragos Oancea <droancea@yahoo.com>
Date: Wed, 7 Dec 2016 06:07:23 -0500
Subject: [PATCH] FS-9825: Added AMR-WB transcoding capabilities (in both
 Bandwidth Efficient and Octet Aligned modes)

---
 configure.ac                         |   5 +-
 src/mod/codecs/mod_amrwb/LEGAL       |  14 ++
 src/mod/codecs/mod_amrwb/Makefile.am |  17 +-
 src/mod/codecs/mod_amrwb/README      |  29 +++
 src/mod/codecs/mod_amrwb/amrwb_be.c  | 132 +++++++++++
 src/mod/codecs/mod_amrwb/amrwb_be.h  |  55 +++++
 src/mod/codecs/mod_amrwb/bitshift.c  |  76 ++++++
 src/mod/codecs/mod_amrwb/bitshift.h  |  57 +++++
 src/mod/codecs/mod_amrwb/mod_amrwb.c | 341 +++++++++++++++++++++++++--
 9 files changed, 699 insertions(+), 27 deletions(-)
 create mode 100644 src/mod/codecs/mod_amrwb/LEGAL
 create mode 100644 src/mod/codecs/mod_amrwb/README
 create mode 100644 src/mod/codecs/mod_amrwb/amrwb_be.c
 create mode 100644 src/mod/codecs/mod_amrwb/amrwb_be.h
 create mode 100644 src/mod/codecs/mod_amrwb/bitshift.c
 create mode 100644 src/mod/codecs/mod_amrwb/bitshift.h

diff --git a/configure.ac b/configure.ac
index 705043ef5f..47afda4666 100644
--- a/configure.ac
+++ b/configure.ac
@@ -831,6 +831,10 @@ PKG_CHECK_MODULES([AMR], [opencore-amrnb >= 0.1.0],[
 		AM_CONDITIONAL([HAVE_AMR],[true])],[
 		AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_AMR],[false])])
 
+PKG_CHECK_MODULES([AMRWB], [opencore-amrwb >= 0.1.0 vo-amrwbenc >= 0.1.0],[
+		AM_CONDITIONAL([HAVE_AMRWB],[true])],[
+		AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_AMRWB],[false])])
+
 AC_CHECK_LIB(apr-1, apr_pool_mutex_set, use_system_apr=yes, use_system_apr=no)
 AM_CONDITIONAL([SYSTEM_APR],[test "${use_system_apr}" = "yes"])
 AC_CHECK_LIB(aprutil-1, apr_queue_pop_timeout, use_system_aprutil=yes, use_system_aprutil=no)
@@ -1934,7 +1938,6 @@ AM_CONDITIONAL(ISMAC, [test `uname -s` = Darwin])
 AM_CONDITIONAL(ISFREEBSD, [test `uname -s` = FreeBSD])
 AM_CONDITIONAL(IS64BITLINUX, [test `uname -m` = x86_64])
 
-AM_CONDITIONAL(HAVE_AMRWB, [ test -d ${switch_srcdir}/libs/amrwb ])
 AM_CONDITIONAL(HAVE_G723_1, [ test -d ${switch_srcdir}/libs/libg723_1 ])
 AM_CONDITIONAL(HAVE_G729, [ test -d ${switch_srcdir}/libs/libg729 ])
 
diff --git a/src/mod/codecs/mod_amrwb/LEGAL b/src/mod/codecs/mod_amrwb/LEGAL
new file mode 100644
index 0000000000..f05c6c45aa
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/LEGAL
@@ -0,0 +1,14 @@
+LEGAL.
+
+Please bear in mind that the software, as modified by Athonet, implements 
+functions for the AMR standard, which are covered by third parties' essential patents.
+To the best of Athonet's knowledge, such essential patent rights holders are: 
+Nokia, Orange, Acacia (now VoiceAge) and Ericsson.
+
+ATHONET HEREBY DISCLAIMS ANY AND ALL LIABILITY IN CASE YOUR USE, 
+MODIFICATION, COPY OR  FURTHER DISTRIBUTION OF THE SOFTWARE, AS 
+MODIFIED BY ATHONET, INFRINGES IN ANY WAY THIRD PARTY'S INTELLECTUAL PROPERTY RIGHTS.
+
+THIS DISCLAIMER SHALL NOT PREJUDICE ANY OTHER LIMITATION OF LIABILITY 
+INCLUDED IN THE APPLICABLE LICENSE.
+
diff --git a/src/mod/codecs/mod_amrwb/Makefile.am b/src/mod/codecs/mod_amrwb/Makefile.am
index ddb1a56999..d3a7d67761 100644
--- a/src/mod/codecs/mod_amrwb/Makefile.am
+++ b/src/mod/codecs/mod_amrwb/Makefile.am
@@ -1,20 +1,21 @@
 include $(top_srcdir)/build/modmake.rulesam
 MODNAME=mod_amrwb
 
-AMRWB_DIR=$(switch_srcdir)/libs/amrwb
-AMRWB_BUILDDIR=$(switch_builddir)/libs/amrwb
-AMRWB_A=$(AMRWB_BUILDDIR)/libamrwb.a
-
 mod_LTLIBRARIES = mod_amrwb.la
-mod_amrwb_la_SOURCES  = mod_amrwb.c
+if HAVE_AMRWB
+mod_amrwb_la_SOURCES  = mod_amrwb.c bitshift.c amrwb_be.c
+else
+mod_amrwb_la_SOURCES  = mod_amrwb.c 
+endif
+
 mod_amrwb_la_CFLAGS   = $(AM_CFLAGS)
 mod_amrwb_la_LIBADD   = $(switch_builddir)/libfreeswitch.la
 mod_amrwb_la_LDFLAGS  = -avoid-version -module -no-undefined -shared
 
 if HAVE_AMRWB
-BUILT_SOURCES= $(AMRWB_A)
-mod_amrwb_la_CFLAGS += -I$(AMRWB_DIR)
-mod_amrwb_la_LIBADD += $(AMRWB_A)
+# install AMRWB libraries:  opencore-amrwb (decoder) and vo-amrwbenc (encoder)  - check README
+mod_amrwb_la_CFLAGS += $(AMRWB_CFLAGS) 
+mod_amrwb_la_LIBADD += $(AMRWB_LIBS) 
 else
 mod_amrwb_la_CFLAGS += -DAMRWB_PASSTHROUGH
 endif
diff --git a/src/mod/codecs/mod_amrwb/README b/src/mod/codecs/mod_amrwb/README
new file mode 100644
index 0000000000..d7f0cf385d
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/README
@@ -0,0 +1,29 @@
+Tested with mobile devices (VOLTE) : 
+Samsung S6 Edge, iPhone, Samsung Note4, Samsung S6
+
+Tested with SIP clients : 
+Linphone, pjsip
+
+INSTALL 
+
+1. install AMRWB libraries
+
+libopencore-amrwb-dev - Adaptive Multi-Rate - Wideband speech codec - development files
+libopencore-amrwb0 - Adaptive Multi-Rate - Wideband speech codec - shared library
+libopencore-amrwb0-dbg - Adaptive Multi-Rate - Wideband speech codec - debugging symbols
+libvo-amrwbenc-dev - VisualOn AMR-WB encoder library (development files)
+libvo-amrwbenc0 - VisualOn AMR-WB encoder library
+vo-amrwbenc-dbg - VisualOn AMR-WB encoder library (debugging symbols)
+
+apt-get install libopencore-amrwb-dev libopencore-amrwb0 libopencore-amrwb0-dbg libvo-amrwbenc-dev libvo-amrwbenc0 vo-amrwbenc-dbg
+
+This was tested on Debian 8.
+
+2. copy files dec_if.h and enc_if.h in the directory with the sourcecode  (mod_amrwb). 
+
+cp /usr/include/opencore-amrwb/dec_if.h  .
+cp /usr/include/vo-amrwbenc/enc_if.h .
+
+3.  make, make install 
+
+
diff --git a/src/mod/codecs/mod_amrwb/amrwb_be.c b/src/mod/codecs/mod_amrwb/amrwb_be.c
new file mode 100644
index 0000000000..12489d8256
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/amrwb_be.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2016, Athonet (www.athonet.com)
+ * Dragos Oancea  <dragos.oancea@athonet.com>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __AMRWB_BE_H__
+#include "bitshift.h"
+#include "amrwb_be.h"
+
+extern const int switch_amrwb_frame_sizes[];
+
+/* Bandwidth Efficient AMR-WB */
+/* https://tools.ietf.org/html/rfc4867#page-17 */
+
+/* this works the same as in AMR NB*/
+extern switch_bool_t switch_amrwb_pack_be(unsigned char *shift_buf, int n) 
+{
+	uint8_t save_toc, ft;
+
+	save_toc = shift_buf[1];
+
+	/* we must convert OA TOC -> BE TOC */ 
+	/* OA TOC
+	0 1 2 3 4 5 6 7
+	+-+-+-+-+-+-+-+-+
+	|F|  FT   |Q|P1|P2|
+	+-+-+-+-+-+-+-+-+
+	F (1 bit): see definition in Section 4.3.2.
+
+	FT (4 bits, unsigned integer): see definition in Section 4.3.2.
+
+	Q (1 bit): see definition in Section 4.3.2.
+
+	P bits: padding bits, MUST be set to zero, and MUST be ignored on reception.
+	*/
+
+	/* BE TOC:
+	 0 1 2 3 4 5
+	 +-+-+-+-+-+-+
+	|F|  FT   |Q|
+	+-+-+-+-+-+-+
+	F = 0 , FT = XXXX , Q = 1 
+	eg: Frame Types (FT): ftp://www.3gpp.org/tsg_sa/TSG_SA/TSGS_04/Docs/PDF/SP-99253.pdf - table 1a 
+	*/
+
+	ft = save_toc >> 3 ; /* drop Q, P1, P2  */
+	ft &= ~(1 << 5); /* clear -  will mark just 1 frame - bit F */
+
+	/* we only encode one frame, so bit 0 of TOC will be 0 */
+	shift_buf[0] |= (ft >> 1); /* first 3 bits of FT */
+
+	switch_amr_array_lshift(6, shift_buf+1, n);
+	/*make sure we clear the bit - it will be used as padding of the trailing byte */
+	shift_buf[1] |= 1 << 6; /* set bit Q instead of P1 */
+	if (( ft >> 0 ) & 1) {
+		/* set last bit of TOC instead of P2 */
+		shift_buf[1] |= 1 << 7;
+	} else {
+		/* reset last bit of TOC instead of P2 */
+		shift_buf[1] &= ~(1 << 7);  
+	}
+
+	return SWITCH_TRUE;
+}
+
+extern switch_bool_t switch_amrwb_unpack_be(unsigned char *encoded_buf, uint8_t *tmp, int encoded_len) 
+{
+	int framesz, index, ft;
+	uint8_t shift_tocs[2] = {0x00, 0x00};
+	uint8_t *shift_buf;
+
+	memcpy(shift_tocs, encoded_buf, 2); 
+	/* shift for BE */
+	switch_amr_array_lshift(4, shift_tocs, 2);
+	ft = shift_tocs[0] >> 3;
+	ft &= ~(1 << 5); /* Frame Type*/
+	shift_buf = encoded_buf + 1; /* skip CMR */
+	/* shift for BE */
+	switch_amr_array_lshift(2, shift_buf, encoded_len - 1);
+	/* get frame size */
+	index = ((shift_tocs[0] >> 3) & 0x0f);
+	if (index > 10) {
+		return SWITCH_FALSE;
+	}
+	framesz = switch_amrwb_frame_sizes[index];
+	tmp[0] = shift_tocs[0]; /* save TOC */
+	memcpy(&tmp[1], shift_buf, framesz);
+
+	return SWITCH_TRUE;
+}
+#endif 
+
+/* For Emacs:
+ * Local Variables:
+ * mode:c
+ * indent-tabs-mode:t
+ * tab-width:4
+ * c-basic-offset:4
+ * End:
+ * For VIM:
+ * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
+ */
diff --git a/src/mod/codecs/mod_amrwb/amrwb_be.h b/src/mod/codecs/mod_amrwb/amrwb_be.h
new file mode 100644
index 0000000000..6a871b4f0c
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/amrwb_be.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016, Athonet (www.athonet.com)
+ * Dragos Oancea  <dragos.oancea@athonet.com>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __AMRWB_BE_H__
+#define __AMRWB_BE_H__
+
+/* Bandwidth Efficient AMR-WB */
+extern switch_bool_t switch_amrwb_pack_be(unsigned char *shift_buf, int n); 
+extern switch_bool_t switch_amrwb_unpack_be(unsigned char *encoded_buf, uint8_t *tmp, int encoded_len);
+
+#endif 
+
+/* For Emacs:
+ * Local Variables:
+ * mode:c
+ * indent-tabs-mode:t
+ * tab-width:4
+ * c-basic-offset:4
+ * End:
+ * For VIM:
+ * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
+ */
+
diff --git a/src/mod/codecs/mod_amrwb/bitshift.c b/src/mod/codecs/mod_amrwb/bitshift.c
new file mode 100644
index 0000000000..825433cd9f
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/bitshift.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016, Athonet (www.athonet.com)
+ * Paolo Missiaggia  <paolo.missiaggia@athonet.com>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BITSHIFT_H__
+#include "bitshift.h"
+
+/*
+ * LEFT shift of an entire array of N bits, with N included between 0 and 8
+ */
+extern int switch_amr_array_lshift(uint8_t lshift, uint8_t *buf, int a_len)
+{
+	int i = 0;
+	uint8_t first_byte;
+	uint8_t second_byte;
+
+	if (!buf || !a_len)
+		return (-1);
+
+	if ((lshift < 0) || (lshift > 8))
+		return (-1);
+
+	first_byte = 0xFF >> lshift;
+	second_byte = ~first_byte;
+
+	for (i = 1; i < a_len; i++) {
+		buf[i - 1] = ((buf[i - 1] & first_byte) << lshift) |
+				((buf[i] & second_byte) >> (8 - lshift));
+	}
+	i--;
+	buf[i] = ((buf[i] & first_byte) << lshift);
+	return (0);
+}
+#endif 
+
+/* For Emacs:
+ * Local Variables:
+ * mode:c
+ * indent-tabs-mode:t
+ * tab-width:4
+ * c-basic-offset:4
+ * End:
+ * For VIM:
+ * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
+ */
+
diff --git a/src/mod/codecs/mod_amrwb/bitshift.h b/src/mod/codecs/mod_amrwb/bitshift.h
new file mode 100644
index 0000000000..f448ecf652
--- /dev/null
+++ b/src/mod/codecs/mod_amrwb/bitshift.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2016, Athonet (www.athonet.com)
+ * Paolo Missiaggia  <paolo.missiaggia@athonet.com>
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __BITSHIFT_H__
+#define __BITSHIFT_H__
+
+#include "switch.h"
+/*
+ * LEFT shift of an entire array of N bits, with N included between 0 and 8
+ */
+extern int switch_amr_array_lshift(uint8_t lshift, uint8_t *buf, int a_len);
+
+#endif 
+
+/* For Emacs:
+ * Local Variables:
+ * mode:c
+ * indent-tabs-mode:t
+ * tab-width:4
+ * c-basic-offset:4
+ * End:
+ * For VIM:
+ * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
+ */
+
diff --git a/src/mod/codecs/mod_amrwb/mod_amrwb.c b/src/mod/codecs/mod_amrwb/mod_amrwb.c
index 681e3d9536..60cf954428 100644
--- a/src/mod/codecs/mod_amrwb/mod_amrwb.c
+++ b/src/mod/codecs/mod_amrwb/mod_amrwb.c
@@ -25,6 +25,9 @@
  * 
  * Anthony Minessale II <anthm@freeswitch.org>
  * Brian K. West <brian@freeswitch.org>
+ * Dragos Oancea <dragos.oancea@athonet.com>
+ * Federico Favaro <federico.favaro@athonet.com>
+ * Marco Sinibaldi <marco.sinibaldi@athonet.com>
  *
  * The amrwb codec itself is not distributed with this module.
  *
@@ -38,8 +41,11 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_amrwb_load);
 SWITCH_MODULE_DEFINITION(mod_amrwb, mod_amrwb_load, NULL, NULL);
 
 #ifndef AMRWB_PASSTHROUGH
-#include "dec_if.h"
-#include "enc_if.h"
+#include "opencore-amrwb/dec_if.h" /*AMR-WB decoder API*/
+#include "vo-amrwbenc/enc_if.h" /*AMR-WB encoder API*/
+
+#include "bitshift.h"
+#include "amrwb_be.h"
 
 typedef enum {
 	AMRWB_OPT_OCTET_ALIGN = (1 << 0),
@@ -71,14 +77,105 @@ struct amrwb_context {
 	switch_byte_t ptime;
 	switch_byte_t channels;
 	switch_byte_t flags;
+	int max_red;
+	int debug;
 };
 
-#define AMRWB_DEFAULT_BITRATE AMRWB_BITRATE_24K
+#define SWITCH_AMRWB_DEFAULT_BITRATE AMRWB_BITRATE_24K
 
 static struct {
 	switch_byte_t default_bitrate;
+	switch_byte_t volte;
+	switch_byte_t adjust_bitrate;
+	int debug;
 } globals;
 
+const int switch_amrwb_frame_sizes[] = {17, 23, 32, 36, 40, 46, 50, 58, 60, 5};
+
+#define SWITCH_AMRWB_OUT_MAX_SIZE 61
+#define SWITCH_AMRWB_MODES 10 /* Silence Indicator (SID) included */
+
+static switch_bool_t switch_amrwb_unpack_oa(unsigned char *buf, uint8_t *tmp, int encoded_data_len)
+{
+	uint8_t *tocs;
+	int index;
+	int framesz;
+
+	buf++;/* CMR skip */
+	tocs = buf;
+	index = ((tocs[0]>>3) & 0xf);
+	buf++; /* point to voice payload */
+
+	if (index > 10) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMRWB decoder (OA): Invalid TOC: 0x%x", index);
+		return SWITCH_FALSE;
+	}
+	framesz = switch_amrwb_frame_sizes[index];
+	if (framesz > encoded_data_len - 1) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMRWB decoder (OA): Invalid frame size: %d\n", framesz);
+		return SWITCH_FALSE;
+	}
+	tmp[0] = tocs[0];
+	memcpy(&tmp[1], buf, framesz);
+	
+	return SWITCH_TRUE;
+}
+
+static switch_bool_t switch_amrwb_pack_oa(unsigned char *shift_buf, int n)
+{
+/* Interleaving code here */
+	return SWITCH_TRUE;
+}
+
+static switch_bool_t switch_amrwb_info(unsigned char *encoded_buf, int encoded_data_len, int payload_format, char *print_text) 
+{
+	uint8_t *tocs;
+	int framesz, index, not_last_frame, q, ft;
+	uint8_t shift_tocs[2] = {0x00, 0x00};
+
+	if (!encoded_buf) {
+		return SWITCH_FALSE;
+	}
+	
+	/* payload format can be OA (octed-aligned) or BE (bandwidth efficient)*/
+	if (payload_format) {
+		/* OA */
+		encoded_buf++; /* CMR skip */
+		tocs = encoded_buf; 
+		index = (tocs[0] >> 3) & 0x0f;
+		if (index > SWITCH_AMRWB_MODES) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMRWB decoder (OA): Invalid TOC 0x%x\n", index);
+			return SWITCH_FALSE;
+		}
+		framesz = switch_amrwb_frame_sizes[index];
+		not_last_frame = (tocs[0] >> 7) & 1; 
+		q = (tocs[0] >> 2) & 1; 
+		ft = tocs[0] >> 3;
+		ft &= ~(1 << 5); /* Frame Type */
+	} else {
+		/* BE */
+		memcpy(shift_tocs, encoded_buf, 2); 
+		/* shift for BE */
+		switch_amr_array_lshift(4, shift_tocs, 2);
+		not_last_frame = (shift_tocs[0] >> 7) & 1; 
+		q = (shift_tocs[0] >> 2) & 1; 
+		ft = shift_tocs[0] >> 3;
+		ft &= ~(1 << 5); /* Frame Type */
+		index = (shift_tocs[0] >> 3) & 0x0f;
+		if (index > SWITCH_AMRWB_MODES) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMRWB decoder (BE): Invalid TOC 0x%x\n", index);
+			return SWITCH_FALSE;
+		}
+		framesz = switch_amrwb_frame_sizes[index];
+	}
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s): FT: [0x%x] Q: [0x%x] Frame flag: [%d]\n", 
+													print_text, payload_format ? "OA":"BE", ft, q, not_last_frame);
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s): AMRWB encoded voice payload sz: [%d] : | encoded_data_len: [%d]\n", 
+													print_text, payload_format ? "OA":"BE", framesz, encoded_data_len);
+
+	return SWITCH_TRUE;
+} 
 #endif
 
 static switch_status_t switch_amrwb_init(switch_codec_t *codec, switch_codec_flag_t flags, const switch_codec_settings_t *codec_settings)
@@ -103,6 +200,17 @@ static switch_status_t switch_amrwb_init(switch_codec_t *codec, switch_codec_fla
 		return SWITCH_STATUS_FALSE;
 	} else {
 
+		/* "mode" may mean two different things: 
+		 * "Octed Aligned" or "Bandwidth Efficient" encoding mode , 
+		 * or the actual bitrate  which is set with FMTP param "mode-set". */
+		/* https://tools.ietf.org/html/rfc4867 */
+
+		/* set the default mode just in case there's no "mode-set" FMTP param */
+		context->enc_mode = globals.default_bitrate;
+
+		/* octet-align = 0  - per RFC - if there's no `octet-align` FMTP value then BE is employed */
+		switch_clear_flag(context, AMRWB_OPT_OCTET_ALIGN);
+
 		if (codec->fmtp_in) {
 			argc = switch_separate_string(codec->fmtp_in, ';', argv, (sizeof(argv) / sizeof(argv[0])));
 			for (x = 0; x < argc; x++) {
@@ -129,7 +237,7 @@ static switch_status_t switch_amrwb_init(switch_codec_t *codec, switch_codec_fla
 						if (atoi(arg)) {
 							switch_set_flag(context, AMRWB_OPT_ROBUST_SORTING);
 						}
-					} else if (!strcasecmp(data, "interveaving")) {
+					} else if (!strcasecmp(data, "interleaving")) {
 						if (atoi(arg)) {
 							switch_set_flag(context, AMRWB_OPT_INTERLEAVING);
 						}
@@ -141,23 +249,23 @@ static switch_status_t switch_amrwb_init(switch_codec_t *codec, switch_codec_fla
 						context->channels = (switch_byte_t) atoi(arg);
 					} else if (!strcasecmp(data, "maxptime")) {
 						context->max_ptime = (switch_byte_t) atoi(arg);
+					} else if (!strcasecmp(data, "max-red")) {
+						context->max_red = atoi(arg);
 					} else if (!strcasecmp(data, "mode-set")) {
 						int y, m_argc;
-						char *m_argv[9]; /* AMR-WB has 9 modes, AMR has 8 */
+						char *m_argv[SWITCH_AMRWB_MODES-1]; /* AMRWB has 9 modes */
 						m_argc = switch_separate_string(arg, ',', m_argv, (sizeof(m_argv) / sizeof(m_argv[0])));
 						for (y = 0; y < m_argc; y++) {
 							context->enc_modes |= (1 << atoi(m_argv[y]));
+							context->enc_mode = atoi(m_argv[y]);
 						}
 					}
 				}
 			}
 		}
 
-		/* init to default if there's no "mode-set" param */
-		context->enc_mode = globals.default_bitrate;
-		/* choose the highest mode (bitrate) for high audio quality from fmtp "mode-set" param */
-		/* Note: mode-set = 0 is a valid mode */
 		if (context->enc_modes) {
+			/* choose the highest mode (bitrate) for high audio quality. */
 			for (i = 8; i > -1; i--) {
 				if (context->enc_modes & (1 << i)) {
 					context->enc_mode = (switch_byte_t) i;
@@ -166,9 +274,17 @@ static switch_status_t switch_amrwb_init(switch_codec_t *codec, switch_codec_fla
 			}
 		}
 
+		if (globals.adjust_bitrate) {
+			switch_set_flag(codec, SWITCH_CODEC_FLAG_HAS_ADJ_BITRATE);
+		}
 
-		switch_snprintf(fmtptmp, sizeof(fmtptmp), "octet-align=%d; mode-set=%d", switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN) ? 1 : 0,
-						context->enc_mode);
+		if (!globals.volte) {
+			switch_snprintf(fmtptmp, sizeof(fmtptmp), "octet-align=%d; mode-set=%d", 
+					switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN) ? 1 : 0, context->enc_mode);
+		} else {
+			switch_snprintf(fmtptmp, sizeof(fmtptmp), "octet-align=%d; mode-set=%d; max-red=0; mode-change-capability=2", 
+					switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN) ? 1 : 0, context->enc_mode);
+		}
 		codec->fmtp_out = switch_core_strdup(codec->memory_pool, fmtptmp);
 
 		context->encoder_state = NULL;
@@ -196,7 +312,7 @@ static switch_status_t switch_amrwb_destroy(switch_codec_t *codec)
 
 	if (context->encoder_state) {
 		E_IF_exit(context->encoder_state);
-	}
+}
 	if (context->decoder_state) {
 		D_IF_exit(context->decoder_state);
 	}
@@ -217,14 +333,37 @@ static switch_status_t switch_amrwb_encode(switch_codec_t *codec,
 	return SWITCH_STATUS_FALSE;
 #else
 	struct amrwb_context *context = codec->private_info;
+	int n;
+	unsigned char *shift_buf = encoded_data;
 
 	if (!context) {
 		return SWITCH_STATUS_FALSE;
 	}
 
-	*encoded_data_len = E_IF_encode(context->encoder_state, context->enc_mode, (int16_t *) decoded_data, (switch_byte_t *) encoded_data, 0);
+	n = E_IF_encode(context->encoder_state, context->enc_mode, (int16_t *) decoded_data, (switch_byte_t *) encoded_data + 1, 0);
+	if (n < 0) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "AMRWB encoder: E_IF_encode() ERROR!\n");
+		return SWITCH_STATUS_FALSE;
+	}
+
+	/* set CMR + TOC (F + 3 bits of FT), 1111 = CMR: No mode request */ 
+	*(switch_byte_t *) encoded_data = 0xf0;
+	*encoded_data_len  = n;
+
+	if (switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN)) {
+		switch_amrwb_pack_oa(shift_buf, n);  /* the payload is OA as it 
+												comes out of the encoding function */
+		*encoded_data_len = n + 1;
+	} else {
+		switch_amrwb_pack_be(shift_buf, n);
+	}
+
+	if (globals.debug) {
+		switch_amrwb_info(shift_buf, *encoded_data_len, switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN) ? 1 : 0, "AMRWB encoder");
+	}
 
 	return SWITCH_STATUS_SUCCESS;
+
 #endif
 }
 
@@ -240,28 +379,159 @@ static switch_status_t switch_amrwb_decode(switch_codec_t *codec,
 	return SWITCH_STATUS_FALSE;
 #else
 	struct amrwb_context *context = codec->private_info;
+	unsigned char *buf = encoded_data;
+	uint8_t tmp[SWITCH_AMRWB_OUT_MAX_SIZE]; 
 
 	if (!context) {
 		return SWITCH_STATUS_FALSE;
 	}
 
-	D_IF_decode(context->decoder_state, (unsigned char *) encoded_data, (int16_t *) decoded_data, 0);
+	if (globals.debug) {
+		switch_amrwb_info(buf, encoded_data_len, switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN) ? 1 : 0, "AMRWB decoder");
+	}
+
+	if (switch_test_flag(context, AMRWB_OPT_OCTET_ALIGN)) { 
+		/* Octed Aligned */
+		if (!switch_amrwb_unpack_oa(buf, tmp, encoded_data_len)) {
+			return SWITCH_STATUS_FALSE;
+		}
+	} else { 
+		/* Bandwidth Efficient */
+		if (!switch_amrwb_unpack_be(buf, tmp, encoded_data_len)) {
+			return SWITCH_STATUS_FALSE;
+		}
+	}
+
+	D_IF_decode(context->decoder_state, tmp, (int16_t *) decoded_data, 0);
+
 	*decoded_data_len = codec->implementation->decoded_bytes_per_packet;
 
 	return SWITCH_STATUS_SUCCESS;
 #endif
 }
 
+#ifndef AMRWB_PASSTHROUGH
+static switch_status_t switch_amrwb_control(switch_codec_t *codec,
+										   switch_codec_control_command_t cmd,
+										   switch_codec_control_type_t ctype,
+										   void *cmd_data,
+										   switch_codec_control_type_t atype,
+										   void *cmd_arg,
+										   switch_codec_control_type_t *rtype,
+										   void **ret_data)
+{
+	struct amrwb_context *context = codec->private_info;
+
+	switch(cmd) {
+	case SCC_DEBUG:
+		{
+			int32_t level = *((uint32_t *) cmd_data);
+			context->debug = level;
+		}
+		break;
+	case SCC_AUDIO_ADJUST_BITRATE:
+		{
+			const char *cmd = (const char *)cmd_data;
+
+			if (!strcasecmp(cmd, "increase")) {
+				if (context->enc_mode < SWITCH_AMRWB_MODES - 1) {
+					int mode_step = 2; /*this is the mode, not the actual bitrate*/
+					context->enc_mode = context->enc_mode + mode_step; 
+					if (globals.debug || context->debug) {
+						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+								"AMRWB encoder: Adjusting mode to %d (increase)\n", context->enc_mode);
+					}
+				} 
+			} else if (!strcasecmp(cmd, "decrease")) {
+				if (context->enc_mode > 0) {
+					int mode_step = 2; /*this is the mode, not the actual bitrate*/
+					context->enc_mode = context->enc_mode - mode_step; 
+					if (globals.debug || context->debug) {
+						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+								"AMRWB encoder: Adjusting mode to %d (decrease)\n", context->enc_mode);
+					}
+				}
+			} else if (!strcasecmp(cmd, "default")) {
+					context->enc_mode = globals.default_bitrate;
+					if (globals.debug || context->debug) {
+						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+								"AMRWB encoder: Adjusting mode to %d (default)\n", context->enc_mode);
+					}
+			} else {
+				/*minimum bitrate (AMRWB mode)*/
+				context->enc_mode = 0;
+				if (globals.debug || context->debug) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, 
+							"AMRWB encoder: Adjusting mode to %d (minimum)\n", context->enc_mode);
+				}
+			}
+		}
+		break;
+	default:
+		break;
+	} 
+	
+	return SWITCH_STATUS_SUCCESS;
+}
+#endif 
+
+static char *generate_fmtp(switch_memory_pool_t *pool , int octet_align)
+{ 
+	char buf[256] = { 0 };
+
+	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "octet-align=%d; ", octet_align);
+
+#ifndef AMRWB_PASSTHROUGH
+	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "mode-set=%d; ", globals.default_bitrate);
+
+	if (globals.volte) {
+		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "max-red=0; mode-change-capability=2; ");
+	}
+#endif
+
+	if (end_of(buf) == ' ') {
+		*(end_of_p(buf) - 1) = '\0';
+	}
+
+	return switch_core_strdup(pool, buf);
+}
+
+#ifndef AMRWB_PASSTHROUGH
+
+#define AMRWB_DEBUG_SYNTAX "<on|off>"
+SWITCH_STANDARD_API(mod_amrwb_debug)
+{
+	if (zstr(cmd)) {
+		stream->write_function(stream, "-USAGE: %s\n", AMRWB_DEBUG_SYNTAX);
+	} else {
+		if (!strcasecmp(cmd, "on")) {
+			globals.debug = 1;
+			stream->write_function(stream, "AMRWB Debug: on\n");
+		} else if (!strcasecmp(cmd, "off")) {
+				globals.debug = 0;
+				stream->write_function(stream, "AMRWB Debug: off\n");
+		} else {
+				stream->write_function(stream, "-USAGE: %s\n", AMRWB_DEBUG_SYNTAX);
+		}
+	}
+	return SWITCH_STATUS_SUCCESS;
+}
+#endif
+
 /* Registration */
 SWITCH_MODULE_LOAD_FUNCTION(mod_amrwb_load)
 {
 	switch_codec_interface_t *codec_interface;
+	char *default_fmtp_oa = NULL;
+	char *default_fmtp_be = NULL;
+
 #ifndef AMRWB_PASSTHROUGH
+	switch_api_interface_t *commands_api_interface;
 	char *cf = "amrwb.conf";
 	switch_xml_t cfg, xml, settings, param;
 
 	memset(&globals, 0, sizeof(globals));
-	globals.default_bitrate = AMRWB_DEFAULT_BITRATE;
+	globals.default_bitrate = SWITCH_AMRWB_DEFAULT_BITRATE;
 
 	if ((xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
 		if ((settings = switch_xml_child(cfg, "settings"))) {
@@ -271,6 +541,13 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_amrwb_load)
 				if (!strcasecmp(var, "default-bitrate")) {
 					globals.default_bitrate = (switch_byte_t) atoi(val);
 				}
+				if (!strcasecmp(var, "volte")) {
+					/* ETSI TS 126 236 compatibility:  http://www.etsi.org/deliver/etsi_ts/126200_126299/126236/10.00.00_60/ts_126236v100000p.pdf */
+					globals.volte = (switch_byte_t) atoi(val);
+				}
+				if (!strcasecmp(var, "adjust-bitrate")) {
+					globals.adjust_bitrate = (switch_byte_t) atoi(val);
+				}
 			}
 		}
 	}
@@ -279,10 +556,38 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_amrwb_load)
 	/* connect my internal structure to the blank pointer passed to me */
 	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
 
-	SWITCH_ADD_CODEC(codec_interface, "AMR-WB");
+#ifndef AMRWB_PASSTHROUGH
+	SWITCH_ADD_API(commands_api_interface, "amrwb_debug", "Set AMR-WB Debug", mod_amrwb_debug, AMRWB_DEBUG_SYNTAX);
+
+	switch_console_set_complete("add amrwb_debug on");
+	switch_console_set_complete("add amrwb_debug off");
+#else
+#define SWITCH_AMRWB_OUT_MAX_SIZE 0
+#endif 
+
+	SWITCH_ADD_CODEC(codec_interface, "AMR-WB / Octet Aligned");
+
+	default_fmtp_oa = generate_fmtp(pool, 1);
+
 	switch_core_codec_add_implementation(pool, codec_interface,
-										 SWITCH_CODEC_TYPE_AUDIO, 100, "AMR-WB", "octet-align=0", 16000, 16000, 23850,
-										 20000, 320, 640, 0, 1, 1, switch_amrwb_init, switch_amrwb_encode, switch_amrwb_decode, switch_amrwb_destroy);
+										 SWITCH_CODEC_TYPE_AUDIO, 100, "AMR-WB", default_fmtp_oa, 
+										 16000, 16000, 23850, 20000, 320, 640, SWITCH_AMRWB_OUT_MAX_SIZE, 1, 1,
+										 switch_amrwb_init, switch_amrwb_encode, switch_amrwb_decode, switch_amrwb_destroy);
+#ifndef AMRWB_PASSTHROUGH
+	codec_interface->implementations->codec_control = switch_amrwb_control;
+#endif 
+
+	SWITCH_ADD_CODEC(codec_interface, "AMR-WB / Bandwidth Efficient");
+
+	default_fmtp_be = generate_fmtp(pool, 0);
+
+	switch_core_codec_add_implementation(pool, codec_interface,
+										 SWITCH_CODEC_TYPE_AUDIO, 110, "AMR-WB", default_fmtp_be,  
+										 16000, 16000, 23850, 20000, 320, 640, SWITCH_AMRWB_OUT_MAX_SIZE, 1, 1, 
+										 switch_amrwb_init, switch_amrwb_encode, switch_amrwb_decode, switch_amrwb_destroy);
+#ifndef AMRWB_PASSTHROUGH
+	codec_interface->implementations->codec_control = switch_amrwb_control;
+#endif 
 	/* indicate that the module should continue to be loaded */
 	return SWITCH_STATUS_SUCCESS;
 }