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:
parent
b1cfa18d77
commit
df7fa3e296
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 */
|
|
@ -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 {
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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" },
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue