laforge has submitted this change. ( 
https://gerrit.osmocom.org/c/osmocom-bb/+/30337 )

Change subject: mobile: integrate GAPK based audio (voice) I/O support
......................................................................

mobile: integrate GAPK based audio (voice) I/O support

This change introduces a new feature to the mobile application -
audio I/O support, which allows the user to speak right from the
host side running mobile through its ordinary mic and speakers.

The audio I/O is based on libosmogapk [1][2], which in its turn
uses the ALSA sound system for the playback and capture.  This
is a new optional dependency of mobile, which is automatically
picked up if available during the build configuration.  Whether
to depend on it or not can be controlled using '--with-gapk-io'.

The API offered by libosmogapk implies to use the processing chains,
which generally consist of a source block, several processing blocks,
and a sink block.  The mobile app implements the following chains:

  - 'pq_audio_source' (voice capture -> frame encoding),
  - 'pq_audio_sink' (frame decoding -> voice playback).

both taking/storing TCH frames from/to the following two buffers:

  - 'tch_fb_ul' - a buffer for to be played DL TCH frames,
  - 'tch_fb_dl' - a buffer for encoded UL TCH frames.

The buffers are served by a new function gapk_io_dequeue().

[1] https://gitea.osmocom.org/osmocom/gapk/
[1] https://osmocom.org/projects/gapk

Change-Id: Ib86b0746606c191573cc773f01172afbb52f33a9
Related: OS#5599
---
M contrib/jenkins.sh
M doc/examples/mobile/default.cfg
M doc/examples/mobile/multi_ms.cfg
M src/host/layer23/configure.ac
M src/host/layer23/include/osmocom/bb/common/logging.h
M src/host/layer23/include/osmocom/bb/common/osmocom_data.h
M src/host/layer23/include/osmocom/bb/mobile/Makefile.am
A src/host/layer23/include/osmocom/bb/mobile/gapk_io.h
M src/host/layer23/include/osmocom/bb/mobile/settings.h
M src/host/layer23/src/common/logging.c
M src/host/layer23/src/mobile/Makefile.am
M src/host/layer23/src/mobile/app_mobile.c
A src/host/layer23/src/mobile/gapk_io.c
M src/host/layer23/src/mobile/gsm48_rr.c
M src/host/layer23/src/mobile/main.c
M src/host/layer23/src/mobile/settings.c
M src/host/layer23/src/mobile/voice.c
M src/host/layer23/src/mobile/vty_interface.c
18 files changed, 761 insertions(+), 8 deletions(-)

Approvals:
  Jenkins Builder: Verified
  pespin: Looks good to me, but someone else must approve
  Hoernchen: Looks good to me, but someone else must approve
  laforge: Looks good to me, approved



diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
index fafbdfb..2758f4e 100755
--- a/contrib/jenkins.sh
+++ b/contrib/jenkins.sh
@@ -17,14 +17,15 @@

 mkdir "$deps" || true

-osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false
-
 # TODO: ask whether fail is expected, because osmocom-bb build succeeds?
 #"$deps"/libosmocore/contrib/verify_value_string_arrays_are_terminated.py 
$(find . -name "*.[hc]")

 export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
 export LD_LIBRARY_PATH="$inst/lib"

+osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false
+osmo-build-dep.sh gapk
+
 set +x
 echo
 echo
diff --git a/doc/examples/mobile/default.cfg b/doc/examples/mobile/default.cfg
index e24e07f..841a356 100644
--- a/doc/examples/mobile/default.cfg
+++ b/doc/examples/mobile/default.cfg
@@ -63,4 +63,6 @@
   rplmn 001 01
  audio
   io-handler none
+  alsa-output-dev default
+  alsa-input-dev default
  no shutdown
diff --git a/doc/examples/mobile/multi_ms.cfg b/doc/examples/mobile/multi_ms.cfg
index 86a9840..c7d4097 100644
--- a/doc/examples/mobile/multi_ms.cfg
+++ b/doc/examples/mobile/multi_ms.cfg
@@ -63,6 +63,8 @@
   rplmn 001 01
  audio
   io-handler none
+  alsa-output-dev default
+  alsa-input-dev default
  no shutdown
 !
 ms two
@@ -117,4 +119,6 @@
   rplmn 001 01
  audio
   io-handler none
+  alsa-output-dev default
+  alsa-input-dev default
  no shutdown
diff --git a/src/host/layer23/configure.ac b/src/host/layer23/configure.ac
index 50cb8ca..9c11dbd 100644
--- a/src/host/layer23/configure.ac
+++ b/src/host/layer23/configure.ac
@@ -55,6 +55,11 @@
                       [Enable LUA scripting support @<:@default=check@:>@])
 ])

+AC_ARG_WITH([gapk_io], [
+       AS_HELP_STRING([--with-gapk-io],
+                      [Enable GAPK I/O support @<:@default=check@:>@])
+])
+
 found_lua53=no
 AS_IF([test "x$with_lua53" != "xno"], [
        PKG_CHECK_MODULES(LIBLUA, lua53, [found_lua53=yes], [found_lua53=no])
@@ -64,6 +69,15 @@
 ])
 AM_CONDITIONAL([BUILD_LUA], test "x$found_lua53" = "xyes")

+found_gapk=no
+AS_IF([test "x$with_gapk_io" != "xno"], [
+       PKG_CHECK_MODULES(LIBOSMOGAPK, libosmogapk, [found_gapk=yes], 
[found_gapk=no])
+       AS_IF([test "x$with_gapk_io" = "xyes" -a "x$found_gapk" = "xno"], [
+               AC_MSG_ERROR([GAPK I/O support requested but pkg-config is 
unable to find it])
+       ])
+])
+AM_CONDITIONAL([BUILD_GAPK], test "x$found_gapk" = "xyes")
+
 dnl checks for header files
 AC_HEADER_STDC

diff --git a/src/host/layer23/include/osmocom/bb/common/logging.h 
b/src/host/layer23/include/osmocom/bb/common/logging.h
index bf6e6aa..e968528 100644
--- a/src/host/layer23/include/osmocom/bb/common/logging.h
+++ b/src/host/layer23/include/osmocom/bb/common/logging.h
@@ -25,6 +25,7 @@
        DMOB,
        DPRIM,
        DLUA,
+       DGAPK,
 };

 extern const struct log_info log_info;
diff --git a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h 
b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
index 14e594c..a8af1e8 100644
--- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
+++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
@@ -6,6 +6,7 @@
 #include <osmocom/core/write_queue.h>
 
 struct osmocom_ms;
+struct gapk_io_state;

        /* FIXME no 'mobile' specific stuff should be here */
 #include <osmocom/bb/mobile/support.h>
@@ -94,6 +95,9 @@
        struct osmomncc_entity mncc_entity;
        struct llist_head trans_list;

+       /* Audio I/O */
+       struct gapk_io_state *gapk_io;
+
        void *lua_state;
        int lua_cb_ref;
        char *lua_script;
diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am 
b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am
index 623964f..978a701 100644
--- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am
+++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am
@@ -1,4 +1,4 @@
 noinst_HEADERS = gsm322.h gsm480_ss.h gsm411_sms.h gsm48_cc.h gsm48_mm.h \
                 gsm48_rr.h mncc.h settings.h subscriber.h support.h \
                 transaction.h vty.h mncc_sock.h mncc_ms.h primitives.h \
-                app_mobile.h voice.h
+                app_mobile.h voice.h gapk_io.h
diff --git a/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h 
b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h
new file mode 100644
index 0000000..3a02e64
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#ifdef WITH_GAPK_IO
+
+#include <stdint.h>
+
+#include <osmocom/gapk/procqueue.h>
+#include <osmocom/gapk/codecs.h>
+
+#define GAPK_ULDL_QUEUE_LIMIT  8
+
+/* Forward declarations */
+struct osmocom_ms;
+struct msgb;
+
+struct gapk_io_state {
+       /* src/alsa -> proc/codec -> sink/tch_fb */
+       struct osmo_gapk_pq *pq_source;
+       /* src/tch_fb -> proc/codec -> sink/alsa */
+       struct osmo_gapk_pq *pq_sink;
+
+       /* Description of currently used codec / format */
+       const struct osmo_gapk_format_desc *phy_fmt_desc;
+       const struct osmo_gapk_codec_desc *codec_desc;
+
+       /* DL TCH frame buffer (received, to be played) */
+       struct llist_head tch_dl_fb;
+       unsigned int tch_dl_fb_len;
+       /* UL TCH frame buffer (captured, to be sent) */
+       struct llist_head tch_ul_fb;
+       unsigned int tch_ul_fb_len;
+};
+
+void gapk_io_init(void);
+
+int gapk_io_init_ms(struct osmocom_ms *ms, enum osmo_gapk_codec_type codec);
+int gapk_io_init_ms_chan(struct osmocom_ms *ms, uint8_t ch_type, uint8_t 
ch_mode);
+int gapk_io_clean_up_ms(struct osmocom_ms *ms);
+
+void gapk_io_enqueue_dl(struct gapk_io_state *state, struct msgb *msg);
+int gapk_io_serve_ms(struct osmocom_ms *ms);
+
+#endif /* WITH_GAPK_IO */
diff --git a/src/host/layer23/include/osmocom/bb/mobile/settings.h 
b/src/host/layer23/include/osmocom/bb/mobile/settings.h
index 27b9f5a..2800fea 100644
--- a/src/host/layer23/include/osmocom/bb/mobile/settings.h
+++ b/src/host/layer23/include/osmocom/bb/mobile/settings.h
@@ -17,6 +17,8 @@
 enum audio_io_handler {
        /* No handler, drop frames */
        AUDIO_IOH_NONE = 0,
+       /* libosmo-gapk based handler */
+       AUDIO_IOH_GAPK,
        /* L1 PHY (e.g. Calypso DSP) */
        AUDIO_IOH_L1PHY,
        /* External MNCC app (via MNCC socket) */
@@ -31,6 +33,8 @@

 struct audio_settings {
        enum audio_io_handler   io_handler;
+       char alsa_output_dev[128];
+       char alsa_input_dev[128];
 };

 struct gsm_settings {
diff --git a/src/host/layer23/src/common/logging.c 
b/src/host/layer23/src/common/logging.c
index 96decf9..56f0322 100644
--- a/src/host/layer23/src/common/logging.c
+++ b/src/host/layer23/src/common/logging.c
@@ -141,6 +141,12 @@
                .color = "\033[1;32m",
                .enabled = 1, .loglevel = LOGL_DEBUG,
        },
+       [DGAPK] = {
+               .name = "DGAPK",
+               .description = "GAPK audio",
+               .color = "\033[0;36m",
+               .enabled = 1, .loglevel = LOGL_DEBUG,
+       },
 };

 const struct log_info log_info = {
diff --git a/src/host/layer23/src/mobile/Makefile.am 
b/src/host/layer23/src/mobile/Makefile.am
index 3db27b7..b7b25e3 100644
--- a/src/host/layer23/src/mobile/Makefile.am
+++ b/src/host/layer23/src/mobile/Makefile.am
@@ -9,6 +9,7 @@
        $(LIBOSMOVTY_CFLAGS) \
        $(LIBOSMOGSM_CFLAGS) \
        $(LIBOSMOCODEC_CFLAGS) \
+       $(LIBOSMOGAPK_CFLAGS) \
        $(LIBGPS_CFLAGS) \
        $(LIBLUA_CFLAGS) \
        $(NULL)
@@ -43,6 +44,7 @@
        $(LIBOSMOVTY_LIBS) \
        $(LIBOSMOGSM_LIBS) \
        $(LIBOSMOCODEC_LIBS) \
+       $(LIBOSMOGAPK_LIBS) \
        $(LIBGPS_LIBS) \
        $(LIBLUA_LIBS) \
        $(NULL)
@@ -54,3 +56,9 @@
 else
 libmobile_a_SOURCES += script_nolua.c
 endif
+
+# GAPK I/O support
+if BUILD_GAPK
+AM_CPPFLAGS += -DWITH_GAPK_IO=1
+libmobile_a_SOURCES += gapk_io.c
+endif
diff --git a/src/host/layer23/src/mobile/app_mobile.c 
b/src/host/layer23/src/mobile/app_mobile.c
index dd67e70..e59ecb9 100644
--- a/src/host/layer23/src/mobile/app_mobile.c
+++ b/src/host/layer23/src/mobile/app_mobile.c
@@ -33,6 +33,7 @@
 #include <osmocom/bb/mobile/app_mobile.h>
 #include <osmocom/bb/mobile/mncc.h>
 #include <osmocom/bb/mobile/voice.h>
+#include <osmocom/bb/mobile/gapk_io.h>
 #include <osmocom/bb/mobile/primitives.h>
 #include <osmocom/bb/common/sap_interface.h>

@@ -72,6 +73,10 @@
                w |= gsm322_cs_dequeue(ms);
                w |= gsm_sim_job_dequeue(ms);
                w |= mncc_dequeue(ms);
+#ifdef WITH_GAPK_IO
+               if (ms->gapk_io != NULL)
+                       w |= gapk_io_serve_ms(ms);
+#endif
                if (w)
                        work = 1;
        } while (w);
@@ -156,6 +161,12 @@
                return -EBUSY;
        }

+#ifdef WITH_GAPK_IO
+       /* Clean up GAPK state, if preset */
+       if (ms->gapk_io != NULL)
+               gapk_io_clean_up_ms(ms);
+#endif
+
        gsm322_exit(ms);
        gsm48_mm_exit(ms);
        gsm48_rr_exit(ms);
@@ -448,6 +459,11 @@

        osmo_gps_init();

+#ifdef WITH_GAPK_IO
+       /* Init GAPK audio I/O */
+       gapk_io_init();
+#endif
+
        vty_info.tall_ctx = l23_ctx;
        vty_init(&vty_info);
        logging_vty_add_cmds();
diff --git a/src/host/layer23/src/mobile/gapk_io.c 
b/src/host/layer23/src/mobile/gapk_io.c
new file mode 100644
index 0000000..b2b55d9
--- /dev/null
+++ b/src/host/layer23/src/mobile/gapk_io.c
@@ -0,0 +1,571 @@
+/*
+ * GAPK (GSM Audio Pocket Knife) based audio I/O
+ *
+ * (C) 2017-2022 by Vadim Yanitskiy <[email protected]>
+ * Contributions by sysmocom - s.f.m.c. GmbH <[email protected]>
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include <osmocom/gapk/procqueue.h>
+#include <osmocom/gapk/formats.h>
+#include <osmocom/gapk/codecs.h>
+#include <osmocom/gapk/common.h>
+
+#include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/common/logging.h>
+
+#include <osmocom/bb/mobile/voice.h>
+#include <osmocom/bb/mobile/gapk_io.h>
+
+/* The RAW PCM format is common for both audio source and sink */
+static const struct osmo_gapk_format_desc *rawpcm_fmt;
+
+static int pq_queue_tch_fb_recv(void *_state, uint8_t *out,
+                               const uint8_t *in, unsigned int in_len)
+{
+       struct gapk_io_state *state = (struct gapk_io_state *)_state;
+       struct msgb *tch_msg;
+       size_t frame_len;
+
+       /* Obtain one TCH frame from the DL buffer */
+       tch_msg = msgb_dequeue_count(&state->tch_dl_fb,
+                                    &state->tch_dl_fb_len);
+       if (tch_msg == NULL)
+               return -EIO;
+
+       /* Calculate received frame length */
+       frame_len = msgb_l3len(tch_msg);
+
+       /* Copy the frame bytes from message */
+       memcpy(out, tch_msg->l3h, frame_len);
+
+       /* Release memory */
+       msgb_free(tch_msg);
+
+       return frame_len;
+}
+
+static int pq_queue_tch_fb_send(void *_state, uint8_t *out,
+                               const uint8_t *in, unsigned int in_len)
+{
+       struct gapk_io_state *state = (struct gapk_io_state *)_state;
+       struct msgb *tch_msg;
+
+       if (state->tch_ul_fb_len >= GAPK_ULDL_QUEUE_LIMIT) {
+               LOGP(DGAPK, LOGL_ERROR, "UL TCH frame buffer overflow, dropping 
msg\n");
+               return -EOVERFLOW;
+       }
+
+       /* Allocate a new message for the lower layers */
+       tch_msg = msgb_alloc_headroom(in_len + 64, 64, "TCH frame");
+       if (tch_msg == NULL)
+               return -ENOMEM;
+
+       /* Copy the frame bytes to a new message */
+       tch_msg->l2h = msgb_put(tch_msg, in_len);
+       memcpy(tch_msg->l2h, in, in_len);
+
+       /* Put encoded TCH frame to the UL buffer */
+       msgb_enqueue_count(&state->tch_ul_fb, tch_msg,
+                          &state->tch_ul_fb_len);
+
+       return 0;
+}
+
+/**
+ * A custom TCH frame buffer block, which actually
+ * handles incoming frames from DL buffer and puts
+ * outgoing frames to UL buffer...
+ */
+static int pq_queue_tch_fb(struct osmo_gapk_pq *pq,
+                          struct gapk_io_state *io_state,
+                          bool is_src)
+{
+       struct osmo_gapk_pq_item *item;
+       unsigned int frame_len;
+
+       LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': Adding TCH frame buffer %s\n",
+            pq->name, is_src ? "input" : "output");
+
+       /* Allocate and add a new queue item */
+       item = osmo_gapk_pq_add_item(pq);
+       if (item == NULL)
+               return -ENOMEM;
+
+       /* General item type and description */
+       item->type = is_src ? OSMO_GAPK_ITEM_TYPE_SOURCE : 
OSMO_GAPK_ITEM_TYPE_SINK;
+       item->cat_name = is_src ? "source" : "sink";
+       item->sub_name = "tch_fb";
+
+       /* I/O length */
+       frame_len = io_state->phy_fmt_desc->frame_len;
+       item->len_in  = is_src ? 0 : frame_len;
+       item->len_out = is_src ? frame_len : 0;
+
+       /* Handler and it's state */
+       item->proc = is_src ? &pq_queue_tch_fb_recv : &pq_queue_tch_fb_send;
+       item->state = io_state;
+
+       return 0;
+}
+
+/**
+ * Auxiliary wrapper around format conversion block.
+ * Is used to perform either a conversion from the format,
+ * produced by encoder, to canonical, or a conversion
+ * from canonical format to the format expected by decoder.
+ */
+static int pq_queue_codec_fmt_conv(struct osmo_gapk_pq *pq,
+                                  const struct osmo_gapk_codec_desc *codec,
+                                  bool is_src)
+{
+       const struct osmo_gapk_format_desc *codec_fmt_desc;
+
+       /* Get format description */
+       codec_fmt_desc = osmo_gapk_fmt_get_from_type(is_src ?
+               codec->codec_enc_format_type : codec->codec_dec_format_type);
+       if (codec_fmt_desc == NULL)
+               return -ENOTSUP;
+
+       /* Put format conversion block */
+       return osmo_gapk_pq_queue_fmt_convert(pq, codec_fmt_desc, !is_src);
+}
+
+/**
+ * Prepares the following queue (source is mic):
+ *
+ * source/alsa -> proc/codec -> proc/format ->
+ *   -> proc/format -> sink/tch_fb
+ *
+ * The two format conversion blocks are aimed to
+ * convert an encoder specific format
+ * to a PHY specific format.
+ */
+static int prepare_audio_source(struct gapk_io_state *gapk_io,
+                               const char *alsa_input_dev)
+{
+       struct osmo_gapk_pq *pq;
+       char *pq_desc;
+       int rc;
+
+       LOGP(DGAPK, LOGL_DEBUG, "Prepare audio input (capture) chain\n");
+
+       /* Allocate a processing queue */
+       pq = osmo_gapk_pq_create("pq_audio_source");
+       if (pq == NULL)
+               return -ENOMEM;
+
+       /* ALSA audio source */
+       rc = osmo_gapk_pq_queue_alsa_input(pq, alsa_input_dev, 
rawpcm_fmt->frame_len);
+       if (rc)
+               goto error;
+
+       /* Frame encoder */
+       rc = osmo_gapk_pq_queue_codec(pq, gapk_io->codec_desc, 1);
+       if (rc)
+               goto error;
+
+       /* Encoder specific format -> canonical */
+       rc = pq_queue_codec_fmt_conv(pq, gapk_io->codec_desc, true);
+       if (rc)
+               goto error;
+
+       /* Canonical -> PHY specific format */
+       rc = osmo_gapk_pq_queue_fmt_convert(pq, gapk_io->phy_fmt_desc, 1);
+       if (rc)
+               goto error;
+
+       /* TCH frame buffer sink */
+       rc = pq_queue_tch_fb(pq, gapk_io, false);
+       if (rc)
+               goto error;
+
+       /* Check composed queue in strict mode */
+       rc = osmo_gapk_pq_check(pq, 1);
+       if (rc)
+               goto error;
+
+       /* Prepare queue (allocate buffers, etc.) */
+       rc = osmo_gapk_pq_prepare(pq);
+       if (rc)
+               goto error;
+
+       /* Save pointer within MS GAPK state */
+       gapk_io->pq_source = pq;
+
+       /* Describe prepared chain */
+       pq_desc = osmo_gapk_pq_describe(pq);
+       LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': chain '%s' prepared\n", pq->name, 
pq_desc);
+       talloc_free(pq_desc);
+
+       return 0;
+
+error:
+       talloc_free(pq);
+       return rc;
+}
+
+/**
+ * Prepares the following queue (sink is speaker):
+ *
+ * src/tch_fb -> proc/format -> [proc/ecu] ->
+ *   proc/format -> proc/codec -> sink/alsa
+ *
+ * The two format conversion blocks (proc/format)
+ * are aimed to convert a PHY specific format
+ * to an encoder specific format.
+ *
+ * A ECU (Error Concealment Unit) block is optionally
+ * added if implemented for a given codec.
+ */
+static int prepare_audio_sink(struct gapk_io_state *gapk_io,
+                             const char *alsa_output_dev)
+{
+       struct osmo_gapk_pq *pq;
+       char *pq_desc;
+       int rc;
+
+       LOGP(DGAPK, LOGL_DEBUG, "Prepare audio output (playback) chain\n");
+
+       /* Allocate a processing queue */
+       pq = osmo_gapk_pq_create("pq_audio_sink");
+       if (pq == NULL)
+               return -ENOMEM;
+
+       /* TCH frame buffer source */
+       rc = pq_queue_tch_fb(pq, gapk_io, true);
+       if (rc)
+               goto error;
+
+#if 0
+       /* TODO: PHY specific format -> canonical */
+       rc = osmo_gapk_pq_queue_fmt_convert(pq, gapk_io->phy_fmt_desc, 0);
+       if (rc)
+               goto error;
+#endif
+
+       /* Optional ECU (Error Concealment Unit) */
+       osmo_gapk_pq_queue_ecu(pq, gapk_io->codec_desc);
+
+#if 0
+       /* TODO: canonical -> decoder specific format */
+       rc = pq_queue_codec_fmt_conv(pq, gapk_io->codec_desc, false);
+       if (rc)
+               goto error;
+#endif
+
+       /* Frame decoder */
+       rc = osmo_gapk_pq_queue_codec(pq, gapk_io->codec_desc, 0);
+       if (rc)
+               goto error;
+
+       /* ALSA audio sink */
+       rc = osmo_gapk_pq_queue_alsa_output(pq, alsa_output_dev, 
rawpcm_fmt->frame_len);
+       if (rc)
+               goto error;
+
+       /* Check composed queue in strict mode */
+       rc = osmo_gapk_pq_check(pq, 1);
+       if (rc)
+               goto error;
+
+       /* Prepare queue (allocate buffers, etc.) */
+       rc = osmo_gapk_pq_prepare(pq);
+       if (rc)
+               goto error;
+
+       /* Save pointer within MS GAPK state */
+       gapk_io->pq_sink = pq;
+
+       /* Describe prepared chain */
+       pq_desc = osmo_gapk_pq_describe(pq);
+       LOGP(DGAPK, LOGL_DEBUG, "PQ '%s': chain '%s' prepared\n", pq->name, 
pq_desc);
+       talloc_free(pq_desc);
+
+       return 0;
+
+error:
+       talloc_free(pq);
+       return rc;
+}
+
+/**
+ * Cleans up both TCH frame I/O buffers, destroys both
+ * processing queues (chains), and deallocates the memory.
+ * Should be called when a voice call is finished...
+ */
+int gapk_io_clean_up_ms(struct osmocom_ms *ms)
+{
+       struct msgb *msg;
+
+       if (ms->gapk_io == NULL)
+               return 0;
+
+       /* Flush TCH frame I/O buffers */
+       while ((msg = msgb_dequeue(&ms->gapk_io->tch_dl_fb)))
+               msgb_free(msg);
+       while ((msg = msgb_dequeue(&ms->gapk_io->tch_ul_fb)))
+               msgb_free(msg);
+
+       /* Destroy both audio I/O chains */
+       if (ms->gapk_io->pq_source)
+               osmo_gapk_pq_destroy(ms->gapk_io->pq_source);
+       if (ms->gapk_io->pq_sink)
+               osmo_gapk_pq_destroy(ms->gapk_io->pq_sink);
+
+       talloc_free(ms->gapk_io);
+       ms->gapk_io = NULL;
+
+       return 0;
+}
+
+/**
+ * Picks the corresponding PHY's frame format for a given codec.
+ * To be used with PHYs that produce audio frames in RTP format,
+ * such as trxcon (GSM 05.03 libosmocoding API).
+ */
+static enum osmo_gapk_format_type phy_fmt_pick_rtp(enum osmo_gapk_codec_type 
codec)
+{
+       switch (codec) {
+       case CODEC_HR:
+               return FMT_RTP_HR_IETF;
+       case CODEC_FR:
+               return FMT_GSM;
+       case CODEC_EFR:
+               return FMT_RTP_EFR;
+       case CODEC_AMR:
+               return FMT_RTP_AMR;
+       default:
+               return FMT_INVALID;
+       }
+}
+
+/**
+ * Allocates both TCH frame I/O buffers
+ * and prepares both processing queues (chains).
+ * Should be called when a voice call is initiated...
+ */
+int gapk_io_init_ms(struct osmocom_ms *ms, enum osmo_gapk_codec_type codec)
+{
+       const struct osmo_gapk_format_desc *phy_fmt_desc;
+       const struct osmo_gapk_codec_desc *codec_desc;
+       struct gsm_settings *set = &ms->settings;
+       enum osmo_gapk_format_type phy_fmt;
+       struct gapk_io_state *gapk_io;
+       int rc = 0;
+
+       LOGP(DGAPK, LOGL_NOTICE, "Initialize GAPK I/O\n");
+
+       OSMO_ASSERT(ms->gapk_io == NULL);
+
+       /* Make sure that the chosen codec has description */
+       codec_desc = osmo_gapk_codec_get_from_type(codec);
+       if (codec_desc == NULL) {
+               LOGP(DGAPK, LOGL_ERROR, "Invalid codec type 0x%02x\n", codec);
+               return -EINVAL;
+       }
+
+       /* Make sure that the chosen codec is supported */
+       if (codec_desc->codec_encode == NULL || codec_desc->codec_decode == 
NULL) {
+               LOGP(DGAPK, LOGL_ERROR,
+                    "Codec '%s' is not supported by GAPK\n", codec_desc->name);
+               return -ENOTSUP;
+       }
+
+       /**
+        * Pick the corresponding PHY's frame format
+        * TODO: ask PHY, which format is supported?
+        * FIXME: RTP (valid for trxcon) is used for now
+        */
+       phy_fmt = phy_fmt_pick_rtp(codec);
+       phy_fmt_desc = osmo_gapk_fmt_get_from_type(phy_fmt);
+       if (phy_fmt_desc == NULL) {
+               LOGP(DGAPK, LOGL_ERROR, "Failed to pick the PHY specific "
+                    "frame format for codec '%s'\n", codec_desc->name);
+               return -EINVAL;
+       }
+
+       gapk_io = talloc_zero(ms, struct gapk_io_state);
+       if (gapk_io == NULL) {
+               LOGP(DGAPK, LOGL_ERROR, "Failed to allocate memory\n");
+               return -ENOMEM;
+       }
+
+       /* Init TCH frame I/O buffers */
+       INIT_LLIST_HEAD(&gapk_io->tch_dl_fb);
+       INIT_LLIST_HEAD(&gapk_io->tch_ul_fb);
+
+       /* Store the codec / format description */
+       gapk_io->codec_desc = codec_desc;
+       gapk_io->phy_fmt_desc = phy_fmt_desc;
+
+       /* Use gapk_io_state as talloc context for both chains */
+       osmo_gapk_set_talloc_ctx(gapk_io);
+
+       /* Prepare both source and sink chains */
+       rc |= prepare_audio_source(gapk_io, set->audio.alsa_input_dev);
+       rc |= prepare_audio_sink(gapk_io, set->audio.alsa_output_dev);
+
+       /* Fall back to ms instance */
+       osmo_gapk_set_talloc_ctx(ms);
+
+       /* If at lease one chain constructor failed */
+       if (rc) {
+               /* Destroy both audio I/O chains */
+               if (gapk_io->pq_source)
+                       osmo_gapk_pq_destroy(gapk_io->pq_source);
+               if (gapk_io->pq_sink)
+                       osmo_gapk_pq_destroy(gapk_io->pq_sink);
+
+               /* Release the memory and return */
+               talloc_free(gapk_io);
+
+               LOGP(DGAPK, LOGL_ERROR, "Failed to initialize GAPK I/O\n");
+               return rc;
+       }
+
+       /* Init pointers */
+       ms->gapk_io = gapk_io;
+
+       LOGP(DGAPK, LOGL_NOTICE,
+            "GAPK I/O initialized for MS '%s', codec '%s'\n",
+            ms->name, codec_desc->name);
+
+       return 0;
+}
+
+/**
+ * Wrapper around gapk_io_init_ms(), that maps both
+ * given GSM 04.08 channel type (HR/FR) and channel
+ * mode to a codec from 'osmo_gapk_codec_type' enum,
+ * checks if a mapped codec is supported by GAPK,
+ * and finally calls the wrapped function.
+ */
+int gapk_io_init_ms_chan(struct osmocom_ms *ms,
+                        uint8_t ch_type, uint8_t ch_mode)
+{
+       enum osmo_gapk_codec_type codec;
+
+       /* Map GSM 04.08 channel mode to GAPK codec type */
+       switch (ch_mode) {
+       case GSM48_CMODE_SPEECH_V1: /* FR or HR */
+               if (ch_type == RSL_CHAN_Bm_ACCHs)
+                       codec = CODEC_FR;
+               else
+                       codec = CODEC_HR;
+               break;
+
+       case GSM48_CMODE_SPEECH_EFR:
+               codec = CODEC_EFR;
+               break;
+
+       case GSM48_CMODE_SPEECH_AMR:
+               codec = CODEC_AMR;
+               break;
+
+       /* Signalling or CSD, do nothing */
+       case GSM48_CMODE_DATA_14k5:
+       case GSM48_CMODE_DATA_12k0:
+       case GSM48_CMODE_DATA_6k0:
+       case GSM48_CMODE_DATA_3k6:
+       case GSM48_CMODE_SIGN:
+               return 0;
+       default:
+               LOGP(DGAPK, LOGL_ERROR, "Unhandled channel mode 0x%02x (%s)\n",
+                    ch_mode, get_value_string(gsm48_chan_mode_names, ch_mode));
+               return -EINVAL;
+       }
+
+       return gapk_io_init_ms(ms, codec);
+}
+
+/**
+ * Performs basic initialization of GAPK library,
+ * setting the talloc root context and a logging category.
+ * Should be called during the application initialization...
+ */
+void gapk_io_init(void)
+{
+       /* Init logging subsystem */
+       osmo_gapk_log_init(DGAPK);
+
+       /* Make RAWPCM format info easy to access */
+       rawpcm_fmt = osmo_gapk_fmt_get_from_type(FMT_RAWPCM_S16LE);
+}
+
+void gapk_io_enqueue_dl(struct gapk_io_state *state, struct msgb *msg)
+{
+       if (state->tch_dl_fb_len >= GAPK_ULDL_QUEUE_LIMIT) {
+               LOGP(DGAPK, LOGL_ERROR, "DL TCH frame buffer overflow, dropping 
msg\n");
+               msgb_free(msg);
+               return;
+       }
+
+       msgb_enqueue_count(&state->tch_dl_fb, msg,
+                          &state->tch_dl_fb_len);
+}
+
+/* Serves both UL/DL TCH frame I/O buffers */
+int gapk_io_serve_ms(struct osmocom_ms *ms)
+{
+       struct gapk_io_state *gapk_io = ms->gapk_io;
+       int work = 0;
+
+       /**
+        * Make sure we have at least two DL frames
+        * to prevent discontinuous playback.
+        */
+       if (gapk_io->tch_dl_fb_len < 2)
+               return 0;
+
+       /**
+        * TODO: if there is an active call, but no TCH frames
+        * in DL buffer, put silence frames using the upcoming
+        * ECU (Error Concealment Unit) of libosmocodec.
+        */
+       while (!llist_empty(&gapk_io->tch_dl_fb)) {
+               /* Decode and play a received DL TCH frame */
+               osmo_gapk_pq_execute(gapk_io->pq_sink);
+
+               /* Record and encode an UL TCH frame back */
+               osmo_gapk_pq_execute(gapk_io->pq_source);
+
+               work |= 1;
+       }
+
+       while (!llist_empty(&gapk_io->tch_ul_fb)) {
+               struct msgb *tch_msg;
+
+               /* Obtain one TCH frame from the UL buffer */
+               tch_msg = msgb_dequeue_count(&gapk_io->tch_ul_fb,
+                                            &gapk_io->tch_ul_fb_len);
+
+               /* Push a voice frame to the lower layers */
+               gsm_send_voice_msg(ms, tch_msg);
+
+               work |= 1;
+       }
+
+       return work;
+}
diff --git a/src/host/layer23/src/mobile/gsm48_rr.c 
b/src/host/layer23/src/mobile/gsm48_rr.c
index 65b021a..610242c 100644
--- a/src/host/layer23/src/mobile/gsm48_rr.c
+++ b/src/host/layer23/src/mobile/gsm48_rr.c
@@ -75,6 +75,8 @@
 #include <osmocom/bb/common/logging.h>
 #include <osmocom/bb/common/networks.h>
 #include <osmocom/bb/common/l1ctl.h>
+
+#include <osmocom/bb/mobile/gapk_io.h>
 #include <osmocom/bb/mobile/vty.h>
 #include <osmocom/bb/common/utils.h>

@@ -3473,6 +3475,15 @@
         && ch_type != RSL_CHAN_Lm_ACCHs)
                return -ENOTSUP;
 
+#ifdef WITH_GAPK_IO
+       /* Poke GAPK audio back-end, if it is chosen */
+       if (ms->settings.audio.io_handler == AUDIO_IOH_GAPK) {
+               int rc = gapk_io_init_ms_chan(ms, ch_type, mode);
+               if (rc)
+                       return rc;
+       }
+#endif
+
        /* Apply indicated channel mode */
        LOGP(DRR, LOGL_INFO, "setting TCH mode to %s, audio mode to %d\n",
             get_value_string(gsm48_chan_mode_names, mode), rr->audio_mode);
@@ -4019,6 +4030,12 @@
        if (cause)
                return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ);

+#ifdef WITH_GAPK_IO
+       /* Poke GAPK audio back-end, if it is chosen */
+       if (ms->settings.audio.io_handler == AUDIO_IOH_GAPK)
+               gapk_io_init_ms_chan(ms, ch_type, cda->mode);
+#endif
+
 #ifdef TEST_FREQUENCY_MOD
        LOGP(DRR, LOGL_INFO, " TESTING: frequency modify ASS.CMD\n");
        before_time = 1;
@@ -5622,6 +5639,7 @@
                break;
        case AUDIO_IOH_MNCC_SOCK:
        case AUDIO_IOH_LOOPBACK:
+       case AUDIO_IOH_GAPK:
                rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ;
                break;
        case AUDIO_IOH_NONE:
diff --git a/src/host/layer23/src/mobile/main.c 
b/src/host/layer23/src/mobile/main.c
index 926358e..088e6c9 100644
--- a/src/host/layer23/src/mobile/main.c
+++ b/src/host/layer23/src/mobile/main.c
@@ -58,7 +58,7 @@


 const char *debug_default =
-       
"DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM:DSAP:DGPS:DMOB:DPRIM:DLUA";
+       
"DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM:DSAP:DGPS:DMOB:DPRIM:DLUA:DGAPK";

 const char *openbsc_copyright =
        "Copyright (C) 2010-2015 Andreas Eversberg, Sylvain Munaut, Holger 
Freyther, Harald Welte\n"
diff --git a/src/host/layer23/src/mobile/settings.c 
b/src/host/layer23/src/mobile/settings.c
index 7627c11..02a5ff7 100644
--- a/src/host/layer23/src/mobile/settings.c
+++ b/src/host/layer23/src/mobile/settings.c
@@ -29,6 +29,7 @@
 static char *layer2_socket_path = "/tmp/osmocom_l2";
 static char *sap_socket_path = "/tmp/osmocom_sap";
 static char *mncc_socket_path = "/tmp/ms_mncc";
+static char *alsa_dev_default = "default";

 int gsm_settings_init(struct osmocom_ms *ms)
 {
@@ -44,6 +45,8 @@

        /* Audio settings: drop TCH frames by default */
        set->audio.io_handler = AUDIO_IOH_NONE;
+       OSMO_STRLCPY_ARRAY(set->audio.alsa_output_dev, alsa_dev_default);
+       OSMO_STRLCPY_ARRAY(set->audio.alsa_input_dev, alsa_dev_default);

        /* Built-in MNCC handler */
        set->mncc_handler = MNCC_HANDLER_INTERNAL;
@@ -203,6 +206,7 @@

 const struct value_string audio_io_handler_names[] = {
        { AUDIO_IOH_NONE,       "none" },
+       { AUDIO_IOH_GAPK,       "gapk" },
        { AUDIO_IOH_L1PHY,      "l1phy" },
        { AUDIO_IOH_MNCC_SOCK,  "mncc-sock" },
        { AUDIO_IOH_LOOPBACK,   "loopback" },
diff --git a/src/host/layer23/src/mobile/voice.c 
b/src/host/layer23/src/mobile/voice.c
index cb1718f..c3c6a6a 100644
--- a/src/host/layer23/src/mobile/voice.c
+++ b/src/host/layer23/src/mobile/voice.c
@@ -1,5 +1,7 @@
 /*
  * (C) 2010 by Andreas Eversberg <[email protected]>
+ * (C) 2017-2018 by Vadim Yanitskiy <[email protected]>
+ * (C) 2022 by sysmocom - s.f.m.c. GmbH <[email protected]>
  *
  * All Rights Reserved
  *
@@ -15,7 +17,8 @@
  *
  */

-#include <stdlib.h>
+#include <string.h>
+#include <errno.h>

 #include <osmocom/core/msgb.h>
 #include <osmocom/codec/codec.h>
@@ -24,6 +27,7 @@

 #include <osmocom/bb/common/logging.h>
 #include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/mobile/gapk_io.h>
 #include <osmocom/bb/mobile/mncc.h>
 #include <osmocom/bb/mobile/voice.h>

@@ -76,6 +80,15 @@
                return gsm_send_voice_msg(ms, msg);
        case AUDIO_IOH_MNCC_SOCK:
                return gsm_forward_mncc(ms, msg);
+       case AUDIO_IOH_GAPK:
+#ifdef WITH_GAPK_IO
+               /* Prevent null pointer dereference */
+               OSMO_ASSERT(ms->gapk_io != NULL);
+
+               /* Enqueue a frame to the DL TCH buffer */
+               gapk_io_enqueue_dl(ms->gapk_io, msg);
+               break;
+#endif
        case AUDIO_IOH_L1PHY:
        case AUDIO_IOH_NONE:
                /* Drop voice frame */
diff --git a/src/host/layer23/src/mobile/vty_interface.c 
b/src/host/layer23/src/mobile/vty_interface.c
index a94f96b..3bab608 100644
--- a/src/host/layer23/src/mobile/vty_interface.c
+++ b/src/host/layer23/src/mobile/vty_interface.c
@@ -1543,8 +1543,14 @@
                        set->any_timeout, VTY_NEWLINE);

        vty_out(vty, " audio%s", VTY_NEWLINE);
-       if (!hide_default || set->audio.io_handler != AUDIO_IOH_NONE)
-               vty_out(vty, "  io-handler %s%s", 
audio_io_handler_name(set->audio.io_handler), VTY_NEWLINE);
+       vty_out(vty, "  io-handler %s%s",
+               audio_io_handler_name(set->audio.io_handler), VTY_NEWLINE);
+       if (set->audio.io_handler == AUDIO_IOH_GAPK) {
+               vty_out(vty, "  alsa-output-dev %s%s",
+                       set->audio.alsa_output_dev, VTY_NEWLINE);
+               vty_out(vty, "  alsa-input-dev %s%s",
+                       set->audio.alsa_input_dev, VTY_NEWLINE);
+       }

        /* no shutdown must be written to config, because shutdown is default */
        vty_out(vty, " %sshutdown%s", (ms->shutdown != MS_SHUTDOWN_NONE) ? "" : 
"no ",
@@ -2831,9 +2837,10 @@
 }

 DEFUN(cfg_ms_audio_io_handler, cfg_ms_audio_io_handler_cmd,
-       "io-handler (none|l1phy|mncc-sock|loopback)",
+       "io-handler (none|gapk|l1phy|mncc-sock|loopback)",
        "Set TCH frame I/O handler\n"
        "No handler, drop TCH frames (default)\n"
+       "libosmo-gapk based I/O handler (requires ALSA)\n"
        "L1 PHY (e.g. Calypso DSP in Motorola C1xx phones)\n"
        "External MNCC application (e.g. LCR) via MNCC socket\n"
        "Return TCH frame payload back to sender\n")
@@ -2849,6 +2856,13 @@
                }
        }

+#ifndef WITH_GAPK_IO
+       if (val == AUDIO_IOH_GAPK) {
+               vty_out(vty, "GAPK I/O is not compiled in (--with-gapk-io)%s", 
VTY_NEWLINE);
+               return CMD_WARNING;
+       }
+#endif
+
        return set_audio_io_handler(vty, val);
 }

@@ -2858,6 +2872,34 @@
        return set_audio_io_handler(vty, AUDIO_IOH_NONE);
 }

+DEFUN(cfg_ms_audio_alsa_out_dev, cfg_ms_audio_alsa_out_dev_cmd,
+       "alsa-output-dev (default|NAME)",
+       "Set ALSA output (playback) device name (for GAPK only)\n"
+       "Default system playback device (default)\n"
+       "Name of a custom playback device")
+{
+       struct osmocom_ms *ms = vty->index;
+       struct gsm_settings *set = &ms->settings;
+
+       OSMO_STRLCPY_ARRAY(set->audio.alsa_output_dev, argv[0]);
+
+       return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ms_audio_alsa_in_dev, cfg_ms_audio_alsa_in_dev_cmd,
+       "alsa-input-dev (default|NAME)",
+       "Set ALSA input (capture) device name (for GAPK only)\n"
+       "Default system recording device (default)\n"
+       "Name of a custom recording device")
+{
+       struct osmocom_ms *ms = vty->index;
+       struct gsm_settings *set = &ms->settings;
+
+       OSMO_STRLCPY_ARRAY(set->audio.alsa_input_dev, argv[0]);
+
+       return CMD_SUCCESS;
+}
+
 DEFUN(cfg_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown",
        NO_STR "Activate and run MS")
 {
@@ -3134,6 +3176,8 @@
        install_node(&audio_node, config_write_dummy);
        install_element(AUDIO_NODE, &cfg_ms_audio_io_handler_cmd);
        install_element(AUDIO_NODE, &cfg_ms_audio_no_io_handler_cmd);
+       install_element(AUDIO_NODE, &cfg_ms_audio_alsa_out_dev_cmd);
+       install_element(AUDIO_NODE, &cfg_ms_audio_alsa_in_dev_cmd);

        /* Register the talloc context introspection command */
        osmo_talloc_vty_add_cmds();

--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/30337
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: Ib86b0746606c191573cc773f01172afbb52f33a9
Gerrit-Change-Number: 30337
Gerrit-PatchSet: 8
Gerrit-Owner: fixeria <[email protected]>
Gerrit-Reviewer: Hoernchen <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>
Gerrit-CC: fixeria <[email protected]>
Gerrit-MessageType: merged

Reply via email to