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
