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
This commit is contained in:
Vadim Yanitskiy 2018-08-28 03:47:05 +07:00 committed by Vadim Yanitskiy
parent b1cfa18d77
commit df7fa3e296
18 changed files with 761 additions and 8 deletions

View File

@ -17,14 +17,15 @@ osmo-clean-workspace.sh
mkdir "$deps" || true mkdir "$deps" || true
osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false
# TODO: ask whether fail is expected, because osmocom-bb build succeeds? # TODO: ask whether fail is expected, because osmocom-bb build succeeds?
#"$deps"/libosmocore/contrib/verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]") #"$deps"/libosmocore/contrib/verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="$inst/lib" export LD_LIBRARY_PATH="$inst/lib"
osmo-build-dep.sh libosmocore "" ac_cv_path_DOXYGEN=false
osmo-build-dep.sh gapk
set +x set +x
echo echo
echo echo

View File

@ -63,4 +63,6 @@ ms 1
rplmn 001 01 rplmn 001 01
audio audio
io-handler none io-handler none
alsa-output-dev default
alsa-input-dev default
no shutdown no shutdown

View File

@ -63,6 +63,8 @@ ms one
rplmn 001 01 rplmn 001 01
audio audio
io-handler none io-handler none
alsa-output-dev default
alsa-input-dev default
no shutdown no shutdown
! !
ms two ms two
@ -117,4 +119,6 @@ ms two
rplmn 001 01 rplmn 001 01
audio audio
io-handler none io-handler none
alsa-output-dev default
alsa-input-dev default
no shutdown no shutdown

View File

@ -55,6 +55,11 @@ AC_ARG_WITH([lua53], [
[Enable LUA scripting support @<:@default=check@:>@]) [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 found_lua53=no
AS_IF([test "x$with_lua53" != "xno"], [ AS_IF([test "x$with_lua53" != "xno"], [
PKG_CHECK_MODULES(LIBLUA, lua53, [found_lua53=yes], [found_lua53=no]) PKG_CHECK_MODULES(LIBLUA, lua53, [found_lua53=yes], [found_lua53=no])
@ -64,6 +69,15 @@ AS_IF([test "x$with_lua53" != "xno"], [
]) ])
AM_CONDITIONAL([BUILD_LUA], test "x$found_lua53" = "xyes") 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 dnl checks for header files
AC_HEADER_STDC AC_HEADER_STDC

View File

@ -25,6 +25,7 @@ enum {
DMOB, DMOB,
DPRIM, DPRIM,
DLUA, DLUA,
DGAPK,
}; };
extern const struct log_info log_info; extern const struct log_info log_info;

View File

@ -6,6 +6,7 @@
#include <osmocom/core/write_queue.h> #include <osmocom/core/write_queue.h>
struct osmocom_ms; struct osmocom_ms;
struct gapk_io_state;
/* FIXME no 'mobile' specific stuff should be here */ /* FIXME no 'mobile' specific stuff should be here */
#include <osmocom/bb/mobile/support.h> #include <osmocom/bb/mobile/support.h>
@ -94,6 +95,9 @@ struct osmocom_ms {
struct osmomncc_entity mncc_entity; struct osmomncc_entity mncc_entity;
struct llist_head trans_list; struct llist_head trans_list;
/* Audio I/O */
struct gapk_io_state *gapk_io;
void *lua_state; void *lua_state;
int lua_cb_ref; int lua_cb_ref;
char *lua_script; char *lua_script;

View File

@ -1,4 +1,4 @@
noinst_HEADERS = gsm322.h gsm480_ss.h gsm411_sms.h gsm48_cc.h gsm48_mm.h \ 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 \ gsm48_rr.h mncc.h settings.h subscriber.h support.h \
transaction.h vty.h mncc_sock.h mncc_ms.h primitives.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

View File

@ -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 */

View File

@ -17,6 +17,8 @@ enum mncc_handler_t {
enum audio_io_handler { enum audio_io_handler {
/* No handler, drop frames */ /* No handler, drop frames */
AUDIO_IOH_NONE = 0, AUDIO_IOH_NONE = 0,
/* libosmo-gapk based handler */
AUDIO_IOH_GAPK,
/* L1 PHY (e.g. Calypso DSP) */ /* L1 PHY (e.g. Calypso DSP) */
AUDIO_IOH_L1PHY, AUDIO_IOH_L1PHY,
/* External MNCC app (via MNCC socket) */ /* External MNCC app (via MNCC socket) */
@ -31,6 +33,8 @@ static inline const char *audio_io_handler_name(enum audio_io_handler val)
struct audio_settings { struct audio_settings {
enum audio_io_handler io_handler; enum audio_io_handler io_handler;
char alsa_output_dev[128];
char alsa_input_dev[128];
}; };
struct gsm_settings { struct gsm_settings {

View File

@ -141,6 +141,12 @@ static const struct log_info_cat default_categories[] = {
.color = "\033[1;32m", .color = "\033[1;32m",
.enabled = 1, .loglevel = LOGL_DEBUG, .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 = { const struct log_info log_info = {

View File

@ -9,6 +9,7 @@ AM_CFLAGS = \
$(LIBOSMOVTY_CFLAGS) \ $(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \ $(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOCODEC_CFLAGS) \ $(LIBOSMOCODEC_CFLAGS) \
$(LIBOSMOGAPK_CFLAGS) \
$(LIBGPS_CFLAGS) \ $(LIBGPS_CFLAGS) \
$(LIBLUA_CFLAGS) \ $(LIBLUA_CFLAGS) \
$(NULL) $(NULL)
@ -43,6 +44,7 @@ mobile_LDADD = \
$(LIBOSMOVTY_LIBS) \ $(LIBOSMOVTY_LIBS) \
$(LIBOSMOGSM_LIBS) \ $(LIBOSMOGSM_LIBS) \
$(LIBOSMOCODEC_LIBS) \ $(LIBOSMOCODEC_LIBS) \
$(LIBOSMOGAPK_LIBS) \
$(LIBGPS_LIBS) \ $(LIBGPS_LIBS) \
$(LIBLUA_LIBS) \ $(LIBLUA_LIBS) \
$(NULL) $(NULL)
@ -54,3 +56,9 @@ libmobile_a_SOURCES += script_lua.c
else else
libmobile_a_SOURCES += script_nolua.c libmobile_a_SOURCES += script_nolua.c
endif endif
# GAPK I/O support
if BUILD_GAPK
AM_CPPFLAGS += -DWITH_GAPK_IO=1
libmobile_a_SOURCES += gapk_io.c
endif

View File

@ -33,6 +33,7 @@
#include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/mobile/app_mobile.h>
#include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/mncc.h>
#include <osmocom/bb/mobile/voice.h> #include <osmocom/bb/mobile/voice.h>
#include <osmocom/bb/mobile/gapk_io.h>
#include <osmocom/bb/mobile/primitives.h> #include <osmocom/bb/mobile/primitives.h>
#include <osmocom/bb/common/sap_interface.h> #include <osmocom/bb/common/sap_interface.h>
@ -72,6 +73,10 @@ int mobile_work(struct osmocom_ms *ms)
w |= gsm322_cs_dequeue(ms); w |= gsm322_cs_dequeue(ms);
w |= gsm_sim_job_dequeue(ms); w |= gsm_sim_job_dequeue(ms);
w |= mncc_dequeue(ms); w |= mncc_dequeue(ms);
#ifdef WITH_GAPK_IO
if (ms->gapk_io != NULL)
w |= gapk_io_serve_ms(ms);
#endif
if (w) if (w)
work = 1; work = 1;
} while (w); } while (w);
@ -156,6 +161,12 @@ int mobile_exit(struct osmocom_ms *ms, int force)
return -EBUSY; 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); gsm322_exit(ms);
gsm48_mm_exit(ms); gsm48_mm_exit(ms);
gsm48_rr_exit(ms); gsm48_rr_exit(ms);
@ -448,6 +459,11 @@ int l23_app_init(const char *config_file)
osmo_gps_init(); osmo_gps_init();
#ifdef WITH_GAPK_IO
/* Init GAPK audio I/O */
gapk_io_init();
#endif
vty_info.tall_ctx = l23_ctx; vty_info.tall_ctx = l23_ctx;
vty_init(&vty_info); vty_init(&vty_info);
logging_vty_add_cmds(); logging_vty_add_cmds();

View File

@ -0,0 +1,571 @@
/*
* 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.
*
*/
#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;
}

View File

@ -75,6 +75,8 @@
#include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/logging.h>
#include <osmocom/bb/common/networks.h> #include <osmocom/bb/common/networks.h>
#include <osmocom/bb/common/l1ctl.h> #include <osmocom/bb/common/l1ctl.h>
#include <osmocom/bb/mobile/gapk_io.h>
#include <osmocom/bb/mobile/vty.h> #include <osmocom/bb/mobile/vty.h>
#include <osmocom/bb/common/utils.h> #include <osmocom/bb/common/utils.h>
@ -3473,6 +3475,15 @@ static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr,
&& ch_type != RSL_CHAN_Lm_ACCHs) && ch_type != RSL_CHAN_Lm_ACCHs)
return -ENOTSUP; 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 */ /* Apply indicated channel mode */
LOGP(DRR, LOGL_INFO, "setting TCH mode to %s, audio mode to %d\n", 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); get_value_string(gsm48_chan_mode_names, mode), rr->audio_mode);
@ -4019,6 +4030,12 @@ static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg)
if (cause) if (cause)
return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ); 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 #ifdef TEST_FREQUENCY_MOD
LOGP(DRR, LOGL_INFO, " TESTING: frequency modify ASS.CMD\n"); LOGP(DRR, LOGL_INFO, " TESTING: frequency modify ASS.CMD\n");
before_time = 1; before_time = 1;
@ -5622,6 +5639,7 @@ int gsm48_rr_init(struct osmocom_ms *ms)
break; break;
case AUDIO_IOH_MNCC_SOCK: case AUDIO_IOH_MNCC_SOCK:
case AUDIO_IOH_LOOPBACK: case AUDIO_IOH_LOOPBACK:
case AUDIO_IOH_GAPK:
rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ; rr->audio_mode = AUDIO_RX_TRAFFIC_IND | AUDIO_TX_TRAFFIC_REQ;
break; break;
case AUDIO_IOH_NONE: case AUDIO_IOH_NONE:

View File

@ -58,7 +58,7 @@ int mobile_exit(struct osmocom_ms *ms, int force);
const char *debug_default = 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 = const char *openbsc_copyright =
"Copyright (C) 2010-2015 Andreas Eversberg, Sylvain Munaut, Holger Freyther, Harald Welte\n" "Copyright (C) 2010-2015 Andreas Eversberg, Sylvain Munaut, Holger Freyther, Harald Welte\n"

View File

@ -29,6 +29,7 @@
static char *layer2_socket_path = "/tmp/osmocom_l2"; static char *layer2_socket_path = "/tmp/osmocom_l2";
static char *sap_socket_path = "/tmp/osmocom_sap"; static char *sap_socket_path = "/tmp/osmocom_sap";
static char *mncc_socket_path = "/tmp/ms_mncc"; static char *mncc_socket_path = "/tmp/ms_mncc";
static char *alsa_dev_default = "default";
int gsm_settings_init(struct osmocom_ms *ms) int gsm_settings_init(struct osmocom_ms *ms)
{ {
@ -44,6 +45,8 @@ int gsm_settings_init(struct osmocom_ms *ms)
/* Audio settings: drop TCH frames by default */ /* Audio settings: drop TCH frames by default */
set->audio.io_handler = AUDIO_IOH_NONE; 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 */ /* Built-in MNCC handler */
set->mncc_handler = MNCC_HANDLER_INTERNAL; set->mncc_handler = MNCC_HANDLER_INTERNAL;
@ -203,6 +206,7 @@ int gsm_random_imei(struct gsm_settings *set)
const struct value_string audio_io_handler_names[] = { const struct value_string audio_io_handler_names[] = {
{ AUDIO_IOH_NONE, "none" }, { AUDIO_IOH_NONE, "none" },
{ AUDIO_IOH_GAPK, "gapk" },
{ AUDIO_IOH_L1PHY, "l1phy" }, { AUDIO_IOH_L1PHY, "l1phy" },
{ AUDIO_IOH_MNCC_SOCK, "mncc-sock" }, { AUDIO_IOH_MNCC_SOCK, "mncc-sock" },
{ AUDIO_IOH_LOOPBACK, "loopback" }, { AUDIO_IOH_LOOPBACK, "loopback" },

View File

@ -1,5 +1,7 @@
/* /*
* (C) 2010 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* *
* All Rights Reserved * All Rights Reserved
* *
@ -15,7 +17,8 @@
* *
*/ */
#include <stdlib.h> #include <string.h>
#include <errno.h>
#include <osmocom/core/msgb.h> #include <osmocom/core/msgb.h>
#include <osmocom/codec/codec.h> #include <osmocom/codec/codec.h>
@ -24,6 +27,7 @@
#include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/logging.h>
#include <osmocom/bb/common/osmocom_data.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/mncc.h>
#include <osmocom/bb/mobile/voice.h> #include <osmocom/bb/mobile/voice.h>
@ -76,6 +80,15 @@ static int gsm_recv_voice(struct osmocom_ms *ms, struct msgb *msg)
return gsm_send_voice_msg(ms, msg); return gsm_send_voice_msg(ms, msg);
case AUDIO_IOH_MNCC_SOCK: case AUDIO_IOH_MNCC_SOCK:
return gsm_forward_mncc(ms, msg); 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_L1PHY:
case AUDIO_IOH_NONE: case AUDIO_IOH_NONE:
/* Drop voice frame */ /* Drop voice frame */

View File

@ -1543,8 +1543,14 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms)
set->any_timeout, VTY_NEWLINE); set->any_timeout, VTY_NEWLINE);
vty_out(vty, " audio%s", 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",
vty_out(vty, " io-handler %s%s", audio_io_handler_name(set->audio.io_handler), VTY_NEWLINE); 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 */ /* no shutdown must be written to config, because shutdown is default */
vty_out(vty, " %sshutdown%s", (ms->shutdown != MS_SHUTDOWN_NONE) ? "" : "no ", vty_out(vty, " %sshutdown%s", (ms->shutdown != MS_SHUTDOWN_NONE) ? "" : "no ",
@ -2831,9 +2837,10 @@ static int set_audio_io_handler(struct vty *vty, enum audio_io_handler val)
} }
DEFUN(cfg_ms_audio_io_handler, cfg_ms_audio_io_handler_cmd, 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" "Set TCH frame I/O handler\n"
"No handler, drop TCH frames (default)\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" "L1 PHY (e.g. Calypso DSP in Motorola C1xx phones)\n"
"External MNCC application (e.g. LCR) via MNCC socket\n" "External MNCC application (e.g. LCR) via MNCC socket\n"
"Return TCH frame payload back to sender\n") "Return TCH frame payload back to sender\n")
@ -2849,6 +2856,13 @@ DEFUN(cfg_ms_audio_io_handler, cfg_ms_audio_io_handler_cmd,
} }
} }
#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); return set_audio_io_handler(vty, val);
} }
@ -2858,6 +2872,34 @@ DEFUN(cfg_ms_audio_no_io_handler, cfg_ms_audio_no_io_handler_cmd,
return set_audio_io_handler(vty, AUDIO_IOH_NONE); 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", DEFUN(cfg_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown",
NO_STR "Activate and run MS") NO_STR "Activate and run MS")
{ {
@ -3134,6 +3176,8 @@ int ms_vty_init(void)
install_node(&audio_node, config_write_dummy); install_node(&audio_node, config_write_dummy);
install_element(AUDIO_NODE, &cfg_ms_audio_io_handler_cmd); 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_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 */ /* Register the talloc context introspection command */
osmo_talloc_vty_add_cmds(); osmo_talloc_vty_add_cmds();