changeset 256:a33edf624061

libgsmfr2: start with API definition and port of libgsmfrp code
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 12 Apr 2024 20:49:53 +0000
parents 07f936338de1
children 77b7b7b7b384
files libgsmfr2/Makefile libgsmfr2/comfort_noise.c libgsmfr2/pp_bad.c libgsmfr2/pp_good.c libgsmfr2/pp_internal.h libgsmfr2/pp_state.c libgsmfr2/prng.c libgsmfr2/sidclass.c libgsmfr2/silence_frame.c libgsmfr2/tw_gsmfr.h libgsmfr2/xmaxc_mean.c
diffstat 11 files changed, 700 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/Makefile	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,25 @@
+CC=	gcc
+CFLAGS=	-O2
+OBJS=	comfort_noise.o pp_bad.o pp_good.o pp_state.o prng.o sidclass.o \
+	silence_frame.o xmaxc_mean.o
+HDRS=	pp_internal.h tw_gsmfr.h
+LIB=	libgsmfr2.a
+
+INSTALL_PREFIX=	/usr/local
+
+all:	${LIB}
+
+${OBJS}:	${HDRS}
+
+${LIB}:	${OBJS}
+	ar rcu $@ ${OBJS}
+	ranlib $@
+
+install:
+	mkdir -p ${INSTALL_PREFIX}/include
+	install -c -m 444 tw_gsmfr.h ${INSTALL_PREFIX}/include
+	mkdir -p ${INSTALL_PREFIX}/lib
+	install -c -m 444 ${LIB} ${INSTALL_PREFIX}/lib
+
+clean:
+	rm -f *.[oa] errs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/comfort_noise.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,111 @@
+/*
+ * In this module we implement comfort noise generation per GSM 06.12
+ * or 3GPP TS 46.012.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+static const uint8_t fold_table_8to6[24] = {
+	1, 2, 3, 4, 5, 6, 1, 2,
+	1, 2, 3, 4, 5, 6, 3, 4,
+	1, 2, 3, 4, 5, 6, 5, 6,
+};
+
+static const uint8_t bc[4] = {0, 0, 0, 0};
+static const uint8_t Nc[4] = {40, 120, 40, 120};
+
+static uint8_t random_1to6(struct gsmfr_preproc_state *st)
+{
+	uint8_t range8, range6;
+
+	range8 = gsmfr_preproc_prng(st, 3);
+	range6 = fold_table_8to6[(st->cn_random_6fold << 3) | range8];
+	st->cn_random_6fold++;
+	if (st->cn_random_6fold >= 3)
+		st->cn_random_6fold = 0;
+	return range6;
+}
+
+void gsmfr_preproc_gen_cn(struct gsmfr_preproc_state *st, uint8_t *frame)
+{
+	unsigned sub, pulse;
+	uint8_t Mc, xmc[13];
+	uint8_t *c;
+
+	/* global bytes (magic and LARc) are fixed */
+	memcpy(frame, st->sid_prefix, 5);
+	c = frame + 5;
+	/* now do the 4 subframes, mostly PRNG output */
+	for (sub = 0; sub < 4; sub++) {
+		Mc = gsmfr_preproc_prng(st, 2);
+		for (pulse = 0; pulse < 13; pulse++)
+			xmc[pulse] = random_1to6(st);
+		/* packing code from libgsm */
+		*c++ =   ((Nc[sub] & 0x7F) << 1)
+		       | ((bc[sub] >> 1) & 0x1);
+		*c++ =   ((bc[sub] & 0x1) << 7)
+		       | ((Mc & 0x3) << 5)
+		       | ((st->sid_xmaxc >> 1) & 0x1F);
+		*c++ =   ((st->sid_xmaxc & 0x1) << 7)
+		       | ((xmc[0] & 0x7) << 4)
+		       | ((xmc[1] & 0x7) << 1)
+		       | ((xmc[2] >> 2) & 0x1);
+		*c++ =   ((xmc[2] & 0x3) << 6)
+		       | ((xmc[3] & 0x7) << 3)
+		       | (xmc[4] & 0x7);
+		*c++ =   ((xmc[5] & 0x7) << 5)
+		       | ((xmc[6] & 0x7) << 2)
+		       | ((xmc[7] >> 1) & 0x3);
+		*c++ =   ((xmc[7] & 0x1) << 7)
+		       | ((xmc[8] & 0x7) << 4)
+		       | ((xmc[9] & 0x7) << 1)
+		       | ((xmc[10] >> 2) & 0x1);
+		*c++ =   ((xmc[10] & 0x3) << 6)
+		       | ((xmc[11] & 0x7) << 3)
+		       | (xmc[12] & 0x7);
+	}
+}
+
+void gsmfr_preproc_sid2cn(struct gsmfr_preproc_state *st, uint8_t *frame)
+{
+	unsigned sub, pulse;
+	uint8_t Mc, xmc[13];
+	uint8_t *c;
+
+	/* save LARc and Xmaxc from the last subframe for subsequent CN gen */
+	memcpy(st->sid_prefix, frame, 5);
+	st->sid_xmaxc = ((frame[27] & 0x1F) << 1) | (frame[28] >> 7);
+	/* ... and turn *this* frame into very first CN output */
+	c = frame + 5;
+	for (sub = 0; sub < 4; sub++) {
+		Mc = gsmfr_preproc_prng(st, 2);
+		for (pulse = 0; pulse < 13; pulse++)
+			xmc[pulse] = random_1to6(st);
+		/* keep each of Xmaxc and replace the rest with CN */
+		*c++ =   ((Nc[sub] & 0x7F) << 1)
+		       | ((bc[sub] >> 1) & 0x1);
+		*c &= 0x1F;
+		*c++ |=  ((bc[sub] & 0x1) << 7)
+		       | ((Mc & 0x3) << 5);
+		*c &= 0x80;
+		*c++ |=  ((xmc[0] & 0x7) << 4)
+		       | ((xmc[1] & 0x7) << 1)
+		       | ((xmc[2] >> 2) & 0x1);
+		*c++ =   ((xmc[2] & 0x3) << 6)
+		       | ((xmc[3] & 0x7) << 3)
+		       | (xmc[4] & 0x7);
+		*c++ =   ((xmc[5] & 0x7) << 5)
+		       | ((xmc[6] & 0x7) << 2)
+		       | ((xmc[7] >> 1) & 0x3);
+		*c++ =   ((xmc[7] & 0x1) << 7)
+		       | ((xmc[8] & 0x7) << 4)
+		       | ((xmc[9] & 0x7) << 1)
+		       | ((xmc[10] >> 2) & 0x1);
+		*c++ =   ((xmc[10] & 0x3) << 6)
+		       | ((xmc[11] & 0x7) << 3)
+		       | (xmc[12] & 0x7);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/pp_bad.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,137 @@
+/*
+ * In this module we implement our handling of BFI frame gaps
+ * and invalid SID frames.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+static int reduce_xmaxc(uint8_t *frame)
+{
+	int mute_flag = 1;
+	unsigned sub, xmaxc;
+
+	for (sub = 0; sub < 4; sub++) {
+		xmaxc = ((frame[sub*7+6] & 0x1F) << 1) | (frame[sub*7+7] >> 7);
+		if (xmaxc > 4) {
+			xmaxc -= 4;
+			mute_flag = 0;
+		} else
+			xmaxc = 0;
+		frame[sub*7+6] &= 0xE0;
+		frame[sub*7+6] |= xmaxc >> 1;
+		frame[sub*7+7] &= 0x7F;
+		frame[sub*7+7] |= (xmaxc & 1) << 7;
+	}
+	return mute_flag;
+}
+
+static void random_grid_pos(struct gsmfr_preproc_state *st, uint8_t *frame)
+{
+	unsigned sub, Mc;
+
+	for (sub = 0; sub < 4; sub++) {
+		Mc = gsmfr_preproc_prng(st, 2);
+		frame[sub*7+6] &= 0x9F;
+		frame[sub*7+6] |= Mc << 5;
+	}
+}
+
+static int reduce_xmaxc_sid(struct gsmfr_preproc_state *st)
+{
+	if (st->sid_xmaxc > 4) {
+		st->sid_xmaxc -= 4;
+		return 0;
+	} else {
+		st->sid_xmaxc = 0;
+		return 1;
+	}
+}
+
+void gsmfr_preproc_bfi(struct gsmfr_preproc_state *st, int taf, uint8_t *frame)
+{
+	int mute;
+
+	switch (st->rx_state) {
+	case NO_DATA:
+		memcpy(frame, &gsmfr_preproc_silence_frame,
+			GSMFR_RTP_FRAME_LEN);
+		return;
+	case SPEECH:
+		memcpy(frame, &st->speech_frame, GSMFR_RTP_FRAME_LEN);
+		st->rx_state = SPEECH_MUTING;
+		return;
+	case SPEECH_MUTING:
+		mute = reduce_xmaxc(st->speech_frame);
+		memcpy(frame, &st->speech_frame, GSMFR_RTP_FRAME_LEN);
+		random_grid_pos(st, frame);
+		if (mute)
+			st->rx_state = NO_DATA;
+		return;
+	case COMFORT_NOISE:
+		if (taf)
+			st->rx_state = LOST_SID;
+		gsmfr_preproc_gen_cn(st, frame);
+		return;
+	case LOST_SID:
+		if (taf) {
+			st->rx_state = CN_MUTING;
+			reduce_xmaxc_sid(st);
+		}
+		gsmfr_preproc_gen_cn(st, frame);
+		return;
+	case CN_MUTING:
+		if (reduce_xmaxc_sid(st)) {
+			st->rx_state = NO_DATA;
+			memcpy(frame, &gsmfr_preproc_silence_frame,
+				GSMFR_RTP_FRAME_LEN);
+		} else
+			gsmfr_preproc_gen_cn(st, frame);
+		return;
+	}
+}
+
+void gsmfr_preproc_invalid_sid(struct gsmfr_preproc_state *st, uint8_t *frame)
+{
+	int mute;
+
+	switch (st->rx_state) {
+	case NO_DATA:
+		memcpy(frame, &gsmfr_preproc_silence_frame,
+			GSMFR_RTP_FRAME_LEN);
+		return;
+	case SPEECH:
+		/*
+		 * Make CN out of the last good speech frame, following the
+		 * "NOTE" at the end of section 6.1.2 in TS 46.031.
+		 */
+		st->rx_state = COMFORT_NOISE;
+		memcpy(st->sid_prefix, &st->speech_frame, 5);
+		st->sid_xmaxc = gsmfr_preproc_xmaxc_mean(st->speech_frame);
+		gsmfr_preproc_gen_cn(st, frame);
+		return;
+	case SPEECH_MUTING:
+		/* ignore invalid SID in this state and act as if we got BFI */
+		mute = reduce_xmaxc(st->speech_frame);
+		memcpy(frame, &st->speech_frame, GSMFR_RTP_FRAME_LEN);
+		random_grid_pos(st, frame);
+		if (mute)
+			st->rx_state = NO_DATA;
+		return;
+	case COMFORT_NOISE:
+	case LOST_SID:
+		st->rx_state = COMFORT_NOISE;
+		gsmfr_preproc_gen_cn(st, frame);
+		return;
+	case CN_MUTING:
+		if (reduce_xmaxc_sid(st)) {
+			st->rx_state = NO_DATA;
+			memcpy(frame, &gsmfr_preproc_silence_frame,
+				GSMFR_RTP_FRAME_LEN);
+		} else
+			gsmfr_preproc_gen_cn(st, frame);
+		return;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/pp_good.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,32 @@
+/*
+ * In this module we implement preprocessing of received good frames.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+void gsmfr_preproc_good_frame(struct gsmfr_preproc_state *st, uint8_t *frame)
+{
+	int sid;
+
+	/* always set correct magic */
+	frame[0] = 0xD0 | frame[0] & 0x0F;
+	/* now classify by SID */
+	sid = gsmfr_preproc_sid_classify(frame);
+	switch (sid) {
+	case 0:		/* good speech frame */
+		st->rx_state = SPEECH;
+		memcpy(&st->speech_frame, frame, GSMFR_RTP_FRAME_LEN);
+		return;
+	case 1:		/* invalid SID frame */
+		/* state-dependent handling, similar to BFI */
+		gsmfr_preproc_invalid_sid(st, frame);
+		return;
+	case 2:		/* valid SID frame */
+		st->rx_state = COMFORT_NOISE;
+		gsmfr_preproc_sid2cn(st, frame);
+		return;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/pp_internal.h	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,36 @@
+/*
+ * This header file is internal to libgsmfr2;
+ * here we define our state structure for the Rx DTX preprocessor component.
+ */
+
+enum rx_dtx_st {
+	NO_DATA = 0,
+	SPEECH,
+	SPEECH_MUTING,
+	COMFORT_NOISE,
+	LOST_SID,
+	CN_MUTING,
+};
+
+struct gsmfr_preproc_state {
+	enum rx_dtx_st	rx_state;
+	uint8_t		speech_frame[GSMFR_RTP_FRAME_LEN];
+	uint8_t		sid_prefix[5];
+	uint8_t		sid_xmaxc;
+	uint32_t	cn_random_lfsr;
+	unsigned	cn_random_6fold;
+};
+
+/* we use the same LFSR PRNG for CN as ETSI EFR implementation */
+#define PN_INITIAL_SEED 0x70816958L   /* Pseudo noise generator seed value  */
+
+/* internal functions */
+extern void gsmfr_preproc_gen_cn(struct gsmfr_preproc_state *state,
+				 uint8_t *frame);
+extern void gsmfr_preproc_sid2cn(struct gsmfr_preproc_state *state,
+				 uint8_t *frame);
+extern void gsmfr_preproc_invalid_sid(struct gsmfr_preproc_state *state,
+				      uint8_t *frame);
+extern uint16_t gsmfr_preproc_prng(struct gsmfr_preproc_state *state,
+				   uint16_t no_bits);
+extern uint8_t gsmfr_preproc_xmaxc_mean(const uint8_t *frame);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/pp_state.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,26 @@
+/*
+ * In this module we implement allocation and initialization
+ * of state structures for our GSM FR preprocessor.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+struct gsmfr_preproc_state *gsmfr_preproc_create(void)
+{
+	struct gsmfr_preproc_state *st;
+
+	st = malloc(sizeof(struct gsmfr_preproc_state));
+	if (st)
+		gsmfr_preproc_reset(st);
+	return st;
+}
+
+void gsmfr_preproc_reset(struct gsmfr_preproc_state *st)
+{
+	memset(st, 0, sizeof(struct gsmfr_preproc_state));
+	st->cn_random_lfsr = PN_INITIAL_SEED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/prng.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,53 @@
+/*
+ * We use a pseudorandom sequence generator function in two places:
+ * when generating comfort noise (GSM 06.12 section 6.1), and when
+ * randomizing grid position parameters as part of speech muting
+ * during error handling (GSM 06.11 section 6).
+ *
+ * Our PRNG is a copy of the pseudonoise() function from ETSI EFR code,
+ * where it is used for similar purposes.
+ */
+
+#include <stdint.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+uint16_t gsmfr_preproc_prng(struct gsmfr_preproc_state *st, uint16_t no_bits)
+{
+    uint16_t noise_bits, Sn, i;
+
+    noise_bits = 0;
+    for (i = 0; i < no_bits; i++)
+    {
+        /* State n == 31 */
+        if ((st->cn_random_lfsr & 0x00000001L) != 0)
+        {
+            Sn = 1;
+        }
+        else
+        {
+            Sn = 0;
+        }
+
+        /* State n == 3 */
+        if ((st->cn_random_lfsr & 0x10000000L) != 0)
+        {
+            Sn = Sn ^ 1;
+        }
+        else
+        {
+            Sn = Sn ^ 0;
+        }
+
+        noise_bits = noise_bits << 1;
+        noise_bits = noise_bits | st->cn_random_lfsr & 1;
+
+        st->cn_random_lfsr >>= 1;
+        if (Sn & 1)
+        {
+            st->cn_random_lfsr |= 0x40000000L;
+        }
+    }
+
+    return noise_bits;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/sidclass.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,51 @@
+/*
+ * gsmfr_preproc_sid_classify() utility function classifies
+ * a GSM 06.10 frame in RTP encoding according to the rules
+ * of GSM 06.31 (or 3GPP TS 46.031) section 6.1.1, judging it
+ * as SID=0, SID=1 or SID=2.
+ */
+
+#include <stdint.h>
+#include "tw_gsmfr.h"
+
+static const unsigned short sid_field_bits[95] = {
+	57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73,
+	75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91,
+	93, 94, 113, 114, 116, 117, 119, 120, 122, 123,
+	125, 126, 128, 129, 131, 132, 134, 135, 137,
+	138, 140, 141, 143, 144, 146, 147, 149, 150,
+	169, 170, 172, 173, 175, 176, 178, 179, 181,
+	182, 184, 185, 187, 188, 190, 191, 193, 194,
+	196, 197, 199, 200, 202, 203, 205, 206, 225,
+	226, 228, 229, 231, 232, 234, 235, 237, 240,
+	243, 246, 249, 252, 255, 258, 261
+};
+
+static inline int get_bit(const uint8_t *frame, unsigned bitnum)
+{
+	unsigned bytenum = bitnum >> 3;
+	unsigned bit_in_byte = 7 - (bitnum & 7);
+	unsigned bitmask = 1 << bit_in_byte;
+
+	if (frame[bytenum] & bitmask)
+		return 1;
+	else
+		return 0;
+}
+
+int gsmfr_preproc_sid_classify(const uint8_t *frame)
+{
+	unsigned idx, n;
+
+	n = 0;
+	for (idx = 0; idx < 95; idx++) {
+		if (get_bit(frame, sid_field_bits[idx]))
+			n++;
+		if (n >= 16)
+			return 0;
+	}
+	if (n < 2)
+		return 2;
+	else
+		return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/silence_frame.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,16 @@
+/*
+ * Table 1 in section 6 of 3GPP TS 46.011 specifies a silence frame
+ * in the form of GSM 06.10 parameters; here we implement this exact
+ * silence frame in libgsm encoding, which is also RTP encoding.
+ */
+
+#include <stdint.h>
+#include "tw_gsmfr.h"
+
+const uint8_t gsmfr_preproc_silence_frame[GSMFR_RTP_FRAME_LEN] = {
+	0xDA, 0xA7, 0xAA, 0xA5, 0x1A,
+	0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+	0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+	0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+	0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/tw_gsmfr.h	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,113 @@
+/*
+ * This header file is the external public interface to libgsmfr2:
+ * Themyscira Wireless implementation of GSM FRv1 speech codec
+ * that includes not only the core transcoding functions of GSM 06.10,
+ * but also Rx DTX functions (substitution and muting per GSM 06.11,
+ * comfort noise per GSM 06.12, overall Rx DTX control per GSM 06.31)
+ * in the decoder direction and the optional homing frame mechanism.
+ *
+ * This header file should be installed in some system include directory
+ * such that it can be included by C sources as <tw_gsmfr.h>.
+ */
+
+#ifndef	__THEMWI_GSMFR_H
+#define	__THEMWI_GSMFR_H
+
+#include <stdint.h>
+
+#define	GSMFR_RTP_FRAME_LEN	33
+#define	GSMFR_NUM_PARAMS	76
+
+/*
+ * GSM 06.10 encoder & decoder portion of the library
+ *
+ * This part is peculiar in that the same state structure is used for
+ * both the encoder and the decoder - however, each given instance
+ * of this state structure must be used for only one of the two.
+ */
+
+struct gsmfr_0610_state;	/* opaque to external users */
+
+struct gsmfr_0610_state *gsmfr_0610_create(void);
+/* use standard free() call to free it afterward */
+
+/* reset state to initial */
+void gsmfr_0610_reset(struct gsmfr_0610_state *state);
+
+/* interface structure for passing frames of codec parameters */
+
+struct gsmfr_param_frame {
+	int16_t	LARc[8];
+	int16_t	Nc[4];
+	int16_t	bc[4];
+	int16_t	Mc[4];
+	int16_t	xmaxc[4];
+	int16_t	xMc[4][13];
+};
+
+/* encoder public functions */
+
+void gsmfr_0610_encode_params(struct gsmfr_0610_state *st, const int16_t *pcm,
+			      struct gsmfr_param_frame *param);
+void gsmfr_0610_encode_frame(struct gsmfr_0610_state *st, const int16_t *pcm,
+			     uint8_t *frame);
+void gsmfr_0610_encoder_homing(struct gsmfr_0610_state *st, const int16_t *pcm);
+
+/* decoder public functions */
+
+void gsmfr_0610_decode_params(struct gsmfr_0610_state *st,
+			      const struct gsmfr_param_frame *param,
+			      int16_t *pcm);
+void gsmfr_0610_decode_frame(struct gsmfr_0610_state *st, const uint8_t *frame,
+			     int16_t *pcm);
+
+/* conversion between RTP packed format and struct gsmfr_param_frame */
+
+void gsmfr_pack_frame(const struct gsmfr_param_frame *param, uint8_t *frame);
+void gsmfr_unpack_frame(const uint8_t *frame, struct gsmfr_param_frame *param);
+
+/* Rx DTX handler preprocessor portion of the library */
+
+struct gsmfr_preproc_state;	/* opaque to external users */
+
+struct gsmfr_preproc_state *gsmfr_preproc_create(void);
+/* use standard free() call to free it afterward */
+
+/* reset state to initial */
+void gsmfr_preproc_reset(struct gsmfr_preproc_state *state);
+
+/* main processing functions */
+void gsmfr_preproc_good_frame(struct gsmfr_preproc_state *state,
+				uint8_t *frame);
+void gsmfr_preproc_bfi(struct gsmfr_preproc_state *state, int taf,
+			uint8_t *frame_out);
+
+/* utility function */
+int gsmfr_preproc_sid_classify(const uint8_t *frame);
+
+/* utility datum */
+extern const uint8_t gsmfr_preproc_silence_frame[GSMFR_RTP_FRAME_LEN];
+
+/*
+ * Full GSM-FR decoder: Rx DTX handler followed by 06.10 decoder,
+ * plus the decoder homing function.
+ */
+
+struct gsmfr_fulldec_state;	/* opaque to external users */
+
+struct gsmfr_fulldec_state *gsmfr_fulldec_create(void);
+/* use standard free() call to free it afterward */
+
+/* reset state to initial */
+void gsmfr_fulldec_reset(struct gsmfr_fulldec_state *state);
+
+/* main processing functions */
+void gsmfr_fulldec_good_frame(struct gsmfr_fulldec_state *state,
+				const uint8_t *frame, int16_t *pcm);
+void gsmfr_fulldec_bfi(struct gsmfr_fulldec_state *state, int taf,
+			int16_t *pcm);
+
+/* utility datum */
+extern const uint8_t gsmfr_decoder_homing_frame[GSMFR_RTP_FRAME_LEN];
+
+#endif	/* include guard */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgsmfr2/xmaxc_mean.c	Fri Apr 12 20:49:53 2024 +0000
@@ -0,0 +1,100 @@
+/*
+ * The function implemented in this module computes a mean Xmaxc
+ * for comfort noise generation from the 4 subframe Xmaxc parameters
+ * extracted from an input frame.
+ */
+
+#include <stdint.h>
+#include "tw_gsmfr.h"
+#include "pp_internal.h"
+
+static const uint16_t dequant_table[64] = {
+	  0,   1,   2,   3,   4,   5,   6,   7,
+	  8,   9,  10,  11,  12,  13,  14,  15,
+	 16,  18,  20,  22,  24,  26,  28,  30,
+	 32,  36,  40,  44,  48,  52,  56,  60,
+	 64,  72,  80,  88,  96, 104, 112, 120,
+	128, 144, 160, 176, 192, 208, 224, 240,
+	256, 288, 320, 352, 384, 416, 448, 480,
+	512, 576, 640, 704, 768, 832, 896, 960,
+};
+
+static const uint8_t requant_table[1024] = {
+	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+	16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,
+	24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+	28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+	32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33,
+	34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 35,
+	36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37,
+	38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39,
+	40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+	41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+	42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+	43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
+	44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+	45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+	46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
+	47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+	48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+	48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+	49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+	49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+	50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+	51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+	51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+	52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
+	52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
+	53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+	53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+	54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+	54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+	55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+	55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+	56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+	56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+	56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+	56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+	57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+	57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+	57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+	57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+	58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+	58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+	58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+	58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+	59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+	59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+	59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+	59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+	60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+	60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+	60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+	60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+	61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+	61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+	61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+	61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+	62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+	62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+	62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+	62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+	63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+	63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+	63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+	63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+};
+
+uint8_t gsmfr_preproc_xmaxc_mean(const uint8_t *frame)
+{
+	unsigned sub, xmaxc, sum;
+
+	sum = 0;
+	for (sub = 0; sub < 4; sub++) {
+		xmaxc = ((frame[sub*7+6] & 0x1F) << 1) | (frame[sub*7+7] >> 7);
+		sum += dequant_table[xmaxc];
+	}
+	xmaxc = requant_table[sum >> 2];
+	return xmaxc;
+}