fixeria has uploaded this change for review.
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 libosmo-gapk, which offers ALSA sound
sysyem support. The libosmo-gapk's API implies to use the
processing chains and queue, which basically consist of a source
block, several processing blocks, and a sink block. So, there
are two voice processing chains:
- 'pq_audio_source' (voice capture -> frame encoding),
- 'pq_audio_sink' (frame decoding -> voice playback).
Both of them exchange frames from two dedicated buffers:
- 'tch_fb_ul' - a buffer for to be played DL TCH frames,
- 'tch_fb_dl' - a buffer for encoded UL TCH frames.
In its turn, both buffers are served by a new gapk_io_dequeue()
function, which is being called within the mobile_work() loop.
Change-Id: Ib86b0746606c191573cc773f01172afbb52f33a9
Related: OS#5599
---
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
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
16 files changed, 715 insertions(+), 40 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/37/30337/1
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 f74efa2..b75fefe 100644
--- a/src/host/layer23/configure.ac
+++ b/src/host/layer23/configure.ac
@@ -44,6 +44,7 @@
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.10.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm)
PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec)
+PKG_CHECK_MODULES(LIBOSMOGAPK, libosmogapk)
AC_CHECK_LIB(gps, gps_waiting, LIBGPS_CFLAGS=" -D_HAVE_GPSD" LIBGPS_LIBS=" -lgps ",,)
AC_SUBST([LIBGPS_CFLAGS])
AC_SUBST([LIBGPS_LIBS])
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..1b26533 100644
--- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
+++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h
@@ -8,6 +8,7 @@
struct osmocom_ms;
/* FIXME no 'mobile' specific stuff should be here */
+#include <osmocom/bb/mobile/gapk_io.h>
#include <osmocom/bb/mobile/support.h>
#include <osmocom/bb/mobile/settings.h>
#include <osmocom/bb/mobile/subscriber.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/gapk_io.h b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h
new file mode 100644
index 0000000..1b3ffa7
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <osmocom/gapk/procqueue.h>
+#include <osmocom/gapk/codecs.h>
+
+/* Forward declarations */
+struct osmocom_ms;
+
+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;
+
+ /* Buffer for to be played TCH frames (from DL) */
+ struct llist_head tch_fb_dl;
+ /* Buffer for encoded TCH frames (for UL) */
+ struct llist_head tch_fb_ul;
+};
+
+void gapk_io_init(void);
+int gapk_io_dequeue(struct osmocom_ms *ms);
+
+int gapk_io_init_ms_chan(struct osmocom_ms *ms,
+ uint8_t ch_type, uint8_t ch_mode);
+int gapk_io_init_ms(struct osmocom_ms *ms,
+ enum osmo_gapk_codec_type codec);
+int gapk_io_clean_up_ms(struct osmocom_ms *ms);
diff --git a/src/host/layer23/include/osmocom/bb/mobile/settings.h b/src/host/layer23/include/osmocom/bb/mobile/settings.h
index 8edde53..e2e30d8 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,
/* Return to sender */
@@ -29,6 +31,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..95039ad 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)
@@ -30,6 +31,7 @@
support.c \
transaction.c \
vty_interface.c \
+ gapk_io.c \
voice.c \
$(NULL)
@@ -43,6 +45,7 @@
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOGAPK_LIBS) \
$(LIBGPS_LIBS) \
$(LIBLUA_LIBS) \
$(NULL)
diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c
index dd67e70..af3fa61 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,7 @@
w |= gsm322_cs_dequeue(ms);
w |= gsm_sim_job_dequeue(ms);
w |= mncc_dequeue(ms);
+ w |= gapk_io_dequeue(ms);
if (w)
work = 1;
} while (w);
@@ -156,6 +158,10 @@
return -EBUSY;
}
+ /* Clean up GAPK state, if preset */
+ if (ms->gapk_io != NULL)
+ gapk_io_clean_up_ms(ms);
+
gsm322_exit(ms);
gsm48_mm_exit(ms);
gsm48_rr_exit(ms);
@@ -448,6 +454,9 @@
osmo_gps_init();
+ /* Init GAPK audio I/O */
+ gapk_io_init();
+
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..c92b799
--- /dev/null
+++ b/src/host/layer23/src/mobile/gapk_io.c
@@ -0,0 +1,565 @@
+/*
+ * GAPK (GSM Audio Pocket Knife) based audio I/O
+ *
+ * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
+ * Contributions by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#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>
+
+/* 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(&state->tch_fb_dl);
+
+ /* Make sure we've got a frame */
+ 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;
+
+ /* 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(&state->tch_fb_ul, tch_msg);
+
+ 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_fb_dl)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&ms->gapk_io->tch_fb_ul)))
+ 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_fb_dl);
+ INIT_LLIST_HEAD(&gapk_io->tch_fb_ul);
+
+ /* 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);
+}
+
+/* Serves both TCH frame I/O buffers */
+int gapk_io_dequeue(struct osmocom_ms *ms)
+{
+ struct gapk_io_state *gapk_io = ms->gapk_io;
+ struct llist_head *entry;
+ size_t frame_count = 0;
+ int work = 0;
+
+ /* There is no active call, nothing to do */
+ if (gapk_io == NULL)
+ return 0;
+
+ /**
+ * Make sure we have at least two frames
+ * to prevent discontinuous playback.
+ */
+ llist_for_each(entry, &gapk_io->tch_fb_dl)
+ if (++frame_count > 2)
+ break;
+ if (frame_count < 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_fb_dl)) {
+ /* Decode and play 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_fb_ul)) {
+ struct msgb *tch_msg;
+
+ /* Obtain one TCH frame from the UL buffer */
+ tch_msg = msgb_dequeue(&gapk_io->tch_fb_ul);
+
+ /* 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 8eaf0f6..01fac62 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>
@@ -3443,6 +3445,13 @@
return -ENOTSUP;
}
+ /* Poke GAPK audio back-end, if it is chosen */
+ if (ms->settings.audio.io_handler == AUDIO_IOH_GAPK) {
+ rc = gapk_io_init_ms_chan(ms, ch_type, mode);
+ if (rc)
+ return rc;
+ }
+
/* 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);
@@ -3969,6 +3978,10 @@
if (cause)
return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ);
+ /* 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);
+
#ifdef TEST_FREQUENCY_MOD
LOGP(DRR, LOGL_INFO, " TESTING: frequency modify ASS.CMD\n");
before_time = 1;
@@ -5544,6 +5557,7 @@
case AUDIO_IOH_L1PHY:
rr->audio_mode = AUDIO_RX_SPEAKER | AUDIO_TX_MICROPHONE;
break;
+ case AUDIO_IOH_GAPK:
case AUDIO_IOH_LOOPBACK:
rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ;
break;
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 10e9984..c471ba6 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_LOOPBACK, "loopback" },
{ 0x00, NULL}
diff --git a/src/host/layer23/src/mobile/voice.c b/src/host/layer23/src/mobile/voice.c
index 5f569c7..0cd8a96 100644
--- a/src/host/layer23/src/mobile/voice.c
+++ b/src/host/layer23/src/mobile/voice.c
@@ -1,5 +1,6 @@
/*
* (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
@@ -15,61 +16,47 @@
*
*/
-#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/codec/codec.h>
#include <osmocom/bb/common/logging.h>
#include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/mobile/settings.h>
+#include <osmocom/bb/mobile/gapk_io.h>
#include <osmocom/bb/mobile/mncc.h>
#include <osmocom/bb/mobile/voice.h>
-
/*
- * receive voice
+ * TCH frame (voice) router
*/
-
static int gsm_recv_voice(struct osmocom_ms *ms, struct msgb *msg)
{
- struct gsm_data_frame *mncc;
-
- /* Drop the l1ctl_info_dl header */
- msgb_pull_to_l2(msg);
- /* push mncc header in front of data */
- mncc = (struct gsm_data_frame *)
- msgb_push(msg, sizeof(struct gsm_data_frame));
- mncc->callref = ms->mncc_entity.ref;
-
- /* FIXME: FR, EFR only! */
- switch (ms->rrlayer.cd_now.mode) {
- case GSM48_CMODE_SPEECH_V1:
- mncc->msg_type = GSM_TCHF_FRAME;
+ switch (ms->settings.audio.io_handler) {
+ case AUDIO_IOH_LOOPBACK:
+ /* Send voice frame back, if appropriate */
+ gsm_send_voice_msg(ms, msg);
break;
- case GSM48_CMODE_SPEECH_EFR:
- mncc->msg_type = GSM_TCHF_FRAME_EFR;
+ case AUDIO_IOH_GAPK:
+ /* Prevent null pointer dereference */
+ OSMO_ASSERT(ms->gapk_io != NULL);
+
+ /* Push a frame to the DL frame buffer */
+ msgb_enqueue(&ms->gapk_io->tch_fb_dl, msg);
break;
+ case AUDIO_IOH_L1PHY:
+ case AUDIO_IOH_NONE:
default:
- /* TODO: print error message here */
- goto exit_free;
+ msgb_free(msg);
}
- /* send voice frame back, if appropriate */
- if (ms->settings.audio.io_handler == AUDIO_IOH_LOOPBACK)
- gsm_send_voice_frame(ms, mncc);
-
- /* distribute and then free */
- if (ms->mncc_entity.mncc_recv && ms->mncc_entity.ref) {
- ms->mncc_entity.mncc_recv(ms, mncc->msg_type, mncc);
- }
-
-exit_free:
- msgb_free(msg);
return 0;
}
/*
- * send voice
+ * Send voice to the lower layers
*/
int gsm_send_voice_msg(struct osmocom_ms *ms, struct msgb *msg)
{
@@ -107,12 +94,10 @@
}
/*
- * init
+ * Init TCH frame (voice) router
*/
-
int gsm_voice_init(struct osmocom_ms *ms)
{
ms->l1_entity.l1_traffic_ind = gsm_recv_voice;
-
return 0;
}
diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c
index 0abd79c..3bfe532 100644
--- a/src/host/layer23/src/mobile/vty_interface.c
+++ b/src/host/layer23/src/mobile/vty_interface.c
@@ -1543,8 +1543,12 @@
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);
+ 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 +2835,10 @@
}
DEFUN(cfg_ms_audio_io_handler, cfg_ms_audio_io_handler_cmd,
- "io-handler (none|l1phy|loopback)",
+ "io-handler (none|gapk|l1phy|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 Mot C1xx phones)\n"
"Return TCH frame payload back to sender\n")
{
@@ -2847,6 +2852,40 @@
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 playback (i.e. speakers) device name\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;
+ const char *dev_name = argv[0];
+
+ /* Just copy device name */
+ strncpy(set->audio.alsa_output_dev, dev_name,
+ sizeof(set->audio.alsa_output_dev) - 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ms_audio_alsa_in_dev, cfg_ms_audio_alsa_in_dev_cmd,
+ "alsa-input-dev (default|NAME)",
+ "Set ALSA recording (i.e. mic) device name\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;
+ const char *dev_name = argv[0];
+
+ /* Just copy device name */
+ strncpy(set->audio.alsa_input_dev, dev_name,
+ sizeof(set->audio.alsa_input_dev) - 1);
+
+ return CMD_SUCCESS;
+}
+
DEFUN(cfg_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown",
NO_STR "Activate and run MS")
{
@@ -3123,6 +3162,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 change 30337. To unsubscribe, or for help writing mail filters, visit settings.