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 486c36d0e..2d70c9888 100644 --- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h +++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h @@ -8,6 +8,7 @@ struct osmocom_ms; /* FIXME no 'mobile' specific stuff should be here */ +#include #include #include #include @@ -86,6 +87,9 @@ struct osmocom_ms { struct osmomncc_entity mncc_entity; struct llist_head trans_list; + /* Audio I/O */ + struct gapk_io_state *gapk_io; + void *lua_state; int lua_cb_ref; char *lua_script; diff --git a/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h new file mode 100644 index 000000000..1b3ffa793 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gapk_io.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +/* Forward declarations */ +struct osmocom_ms; + +struct gapk_io_state { + /* src/alsa -> proc/codec -> sink/tch_fb */ + struct osmo_gapk_pq *pq_source; + /* src/tch_fb -> proc/codec -> sink/alsa */ + struct osmo_gapk_pq *pq_sink; + + /* Description of currently used codec / format */ + const struct osmo_gapk_format_desc *phy_fmt_desc; + const struct osmo_gapk_codec_desc *codec_desc; + + /* Buffer for to be played TCH frames (from DL) */ + struct llist_head tch_fb_dl; + /* Buffer for encoded TCH frames (for UL) */ + struct llist_head tch_fb_ul; +}; + +void gapk_io_init(void); +int gapk_io_dequeue(struct osmocom_ms *ms); + +int gapk_io_init_ms_chan(struct osmocom_ms *ms, + uint8_t ch_type, uint8_t ch_mode); +int gapk_io_init_ms(struct osmocom_ms *ms, + enum osmo_gapk_codec_type codec); +int gapk_io_clean_up_ms(struct osmocom_ms *ms); diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 1b912fcc8..8dfebaf4a 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -46,6 +46,7 @@ libmobile_a_SOURCES += \ # MNCC and voice implementation libmobile_a_SOURCES += \ mncc_sock.c \ + gapk_io.c \ mnccms.c \ voice.c \ $(NULL) diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index 1e0e01576..2c36beb37 100644 --- a/src/host/layer23/src/mobile/app_mobile.c +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ int mobile_work(struct osmocom_ms *ms) w |= gsm322_cs_dequeue(ms); w |= gsm_sim_job_dequeue(ms); w |= mncc_dequeue(ms); + w |= gapk_io_dequeue(ms); if (w) work = 1; } while (w); @@ -158,6 +160,10 @@ int mobile_exit(struct osmocom_ms *ms, int force) return -EBUSY; } + /* Clean up GAPK state, if preset */ + if (ms->gapk_io != NULL) + gapk_io_clean_up_ms(ms); + gsm322_exit(ms); gsm48_mm_exit(ms); gsm48_rr_exit(ms); @@ -457,6 +463,9 @@ int l23_app_init(const char *config_file, osmo_gps_init(); + /* Init GAPK audio I/O */ + gapk_io_init(); + vty_info.tall_ctx = l23_ctx; vty_init(&vty_info); logging_vty_add_cmds(NULL); 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 000000000..66f6b2a8f --- /dev/null +++ b/src/host/layer23/src/mobile/gapk_io.c @@ -0,0 +1,578 @@ +/* + * GAPK (GSM Audio Pocket Knife) based audio I/O + * + * (C) 2017-2018 by Vadim Yanitskiy + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +/* The RAW PCM format is common for both audio source and sink */ +static const struct osmo_gapk_format_desc *rawpcm_fmt; + +static int pq_queue_tch_fb_recv(void *_state, uint8_t *out, + const uint8_t *in, unsigned int in_len) +{ + struct gapk_io_state *state = (struct gapk_io_state *) _state; + struct msgb *tch_msg; + size_t frame_len; + + /* Obtain one TCH frame from the DL buffer */ + tch_msg = msgb_dequeue(&state->tch_fb_dl); + + /* Make sure we've got a frame */ + if (!tch_msg) + return -EIO; + + /* Calculate received frame length */ + frame_len = msgb_l3len(tch_msg); + + /* Copy the frame bytes from message */ + memcpy(out, tch_msg->l3h, frame_len); + + /* Release memory */ + msgb_free(tch_msg); + + return frame_len; +} + +static int pq_queue_tch_fb_send(void *_state, uint8_t *out, + const uint8_t *in, unsigned int in_len) +{ + struct gapk_io_state *state = (struct gapk_io_state *) _state; + struct msgb *tch_msg; + + /* Allocate a new message for the lower layers */ + tch_msg = msgb_alloc_headroom(in_len + 64, 64, "TCH frame"); + if (!tch_msg) + return -ENOMEM; + + /* Copy the frame bytes to a new message */ + tch_msg->l2h = msgb_put(tch_msg, in_len); + memcpy(tch_msg->l2h, in, in_len); + + /* Put encoded TCH frame to the UL buffer */ + msgb_enqueue(&state->tch_fb_ul, tch_msg); + + return 0; +} + +/** + * A custom TCH frame buffer block, which actually + * handles incoming frames from DL buffer and puts + * outgoing frames to UL buffer... + */ +static int pq_queue_tch_fb(struct osmo_gapk_pq *pq, + struct gapk_io_state *io_state, int 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) + 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, int 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) + 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) + 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, 1); + 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, 0); + 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) + return -ENOMEM; + + /* TCH frame buffer source */ + rc = pq_queue_tch_fb(pq, gapk_io, 1); + if (rc) + goto error; + + /* PHY specific format -> canonical */ + rc = osmo_gapk_pq_queue_fmt_convert(pq, + gapk_io->phy_fmt_desc, 0); + if (rc) + goto error; + + /* Optional ECU (Error Concealment Unit) */ + osmo_gapk_pq_queue_ecu(pq, gapk_io->codec_desc); + + /* Canonical -> decoder specific format */ + rc = pq_queue_codec_fmt_conv(pq, gapk_io->codec_desc, 0); + if (rc) + goto error; + + /* 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) + return 0; + + /* Flush TCH frame I/O buffers */ + while ((msg = msgb_dequeue(&ms->gapk_io->tch_fb_dl))) + msgb_free(msg); + while ((msg = msgb_dequeue(&ms->gapk_io->tch_fb_ul))) + msgb_free(msg); + + /* Destroy both audio I/O chains */ + if (ms->gapk_io->pq_source) + osmo_gapk_pq_destroy(ms->gapk_io->pq_source); + if (ms->gapk_io->pq_sink) + osmo_gapk_pq_destroy(ms->gapk_io->pq_sink); + + talloc_free(ms->gapk_io); + ms->gapk_io = NULL; + + return 0; +} + +/** + * Picks the corresponding PHY's frame format for a given codec. + * To be used with PHYs that produce audio frames in RTP format, + * such as trxcon (GSM 05.03 libosmocoding API). + */ +static enum osmo_gapk_format_type phy_fmt_pick_rtp( + enum osmo_gapk_codec_type codec) +{ + switch (codec) { + case CODEC_HR: + return FMT_RTP_HR_IETF; + case CODEC_FR: + return FMT_GSM; + case CODEC_EFR: + return FMT_RTP_EFR; + case CODEC_AMR: + return FMT_RTP_AMR; + default: + return FMT_INVALID; + } +} + +/** + * Allocates both TCH frame I/O buffers + * and prepares both processing queues (chains). + * Should be called when a voice call is initiated... + */ +int gapk_io_init_ms(struct osmocom_ms *ms, + enum osmo_gapk_codec_type codec) +{ + const struct osmo_gapk_format_desc *phy_fmt_desc; + const struct osmo_gapk_codec_desc *codec_desc; + struct gsm_settings *set = &ms->settings; + enum osmo_gapk_format_type phy_fmt; + struct gapk_io_state *gapk_io; + int rc = 0; + + LOGP(DGAPK, LOGL_NOTICE, "Initialize GAPK I/O\n"); + + /* Make sure that the chosen codec has description */ + codec_desc = osmo_gapk_codec_get_from_type(codec); + if (!codec_desc) { + 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 || !codec_desc->codec_decode) { + LOGP(DGAPK, LOGL_ERROR, "Codec '%s' is not (fully) " + "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) { + LOGP(DGAPK, LOGL_ERROR, "Failed to pick the corresponding " + "PHY's frame format for codec '%s'\n", codec_desc->name); + return -EINVAL; + } + + /* Attempt to allocate memory */ + gapk_io = talloc_zero(ms, struct gapk_io_state); + if (!gapk_io) { + LOGP(DGAPK, LOGL_ERROR, "Failed to allocate memory\n"); + return -ENOMEM; + } + + /* Init TCH frame I/O buffers */ + INIT_LLIST_HEAD(&gapk_io->tch_fb_dl); + INIT_LLIST_HEAD(&gapk_io->tch_fb_ul); + + /* Store the codec / format description */ + gapk_io->codec_desc = codec_desc; + gapk_io->phy_fmt_desc = phy_fmt_desc; + + /* Use gapk_io_state as talloc context for both chains */ + osmo_gapk_set_talloc_ctx(gapk_io); + + /* Prepare both source and sink chains */ + rc |= prepare_audio_source(gapk_io, set->audio.alsa_input_dev); + rc |= prepare_audio_sink(gapk_io, set->audio.alsa_output_dev); + + /* Fall back to ms instance */ + osmo_gapk_set_talloc_ctx(ms); + + /* If at lease one chain constructor failed */ + if (rc) { + /* Destroy both audio I/O chains */ + if (gapk_io->pq_source) + osmo_gapk_pq_destroy(gapk_io->pq_source); + if (gapk_io->pq_sink) + osmo_gapk_pq_destroy(gapk_io->pq_sink); + + /* Release the memory and return */ + talloc_free(gapk_io); + + LOGP(DGAPK, LOGL_ERROR, "Failed to initialize GAPK I/O\n"); + return rc; + } + + /* Init pointers */ + ms->gapk_io = gapk_io; + + LOGP(DGAPK, LOGL_NOTICE, "GAPK I/O initialized for MS " + "'%s', codec '%s'\n", ms->name, codec_desc->name); + + return 0; +} + +/** + * Wrapper around gapk_io_init_ms(), that maps both + * given GSM 04.08 channel type (HR/FR) and channel + * mode to a codec from 'osmo_gapk_codec_type' enum, + * checks if a mapped codec is supported by GAPK, + * and finally calls the wrapped function. + */ +int gapk_io_init_ms_chan(struct osmocom_ms *ms, + uint8_t ch_type, uint8_t ch_mode) +{ + enum osmo_gapk_codec_type codec; + + /* Map GSM 04.08 channel mode to GAPK codec type */ + switch (ch_mode) { + case GSM48_CMODE_SPEECH_V1: /* HR or FR */ + codec = ch_type == RSL_CHAN_Bm_ACCHs ? + CODEC_FR : 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, "Invalid channel mode 0x%02x (%s)\n", + ch_mode, get_value_string(gsm48_chan_mode_names, ch_mode)); + return -EINVAL; + } + + /** + * What if there is an active GAPK I/O state? + * FIXME: this shouldn't happen + */ + if (ms->gapk_io != NULL) { + LOGP(DGAPK, LOGL_ERROR, "FIXME: cleaning up existing GAPK state\n"); + gapk_io_clean_up_ms(ms); + } + + 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); + + LOGP(DGAPK, LOGL_NOTICE, "init GAPK audio I/O\n"); +} + +/* Serves both TCH frame I/O buffers */ +int gapk_io_dequeue(struct osmocom_ms *ms) +{ + struct gapk_io_state *gapk_io = ms->gapk_io; + struct llist_head *entry; + size_t frame_count = 0; + int work = 0; + + /* There is no active call, nothing to do */ + if (!gapk_io) + return 0; + + /** + * Make sure we have at least two frames + * to prevent discontinuous playback. + */ + llist_for_each(entry, &gapk_io->tch_fb_dl) + if (++frame_count > 2) + break; + if (frame_count < 2) + return 0; + + /** + * TODO: if there is an active call, but no TCH frames + * in DL buffer, put silence frames using the upcoming + * ECU (Error Concealment Unit) of libosmocodec. + */ + while (!llist_empty(&gapk_io->tch_fb_dl)) { + /* Decode and play received DL TCH frame */ + osmo_gapk_pq_execute(gapk_io->pq_sink); + + /* Record and encode an UL TCH frame back */ + osmo_gapk_pq_execute(gapk_io->pq_source); + + work |= 1; + } + + while (!llist_empty(&gapk_io->tch_fb_ul)) { + struct msgb *tch_msg; + + /* Obtain one TCH frame from the UL buffer */ + tch_msg = msgb_dequeue(&gapk_io->tch_fb_ul); + + /* Push a voice frame to the lower layers */ + gsm_send_voice(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 a1358c766..bed1e47dc 100644 --- a/src/host/layer23/src/mobile/gsm48_rr.c +++ b/src/host/layer23/src/mobile/gsm48_rr.c @@ -78,6 +78,8 @@ #include #include #include + +#include #include #include @@ -3439,6 +3441,13 @@ static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, return -ENOTSUP; } + /* Poke GAPK audio back-end, if it is chosen */ + if (ms->settings.audio.io_target == AUDIO_IO_GAPK) { + rc = gapk_io_init_ms_chan(ms, ch_type, mode); + if (rc) + return rc; + } + /* Apply indicated channel mode */ LOGP(DRR, LOGL_INFO, "setting TCH mode to %s, audio mode to %d\n", get_value_string(gsm48_chan_mode_names, mode), rr->audio_mode); diff --git a/src/host/layer23/src/mobile/voice.c b/src/host/layer23/src/mobile/voice.c index 76c116cd5..ddecb82ae 100644 --- a/src/host/layer23/src/mobile/voice.c +++ b/src/host/layer23/src/mobile/voice.c @@ -1,5 +1,6 @@ /* * (C) 2010 by Andreas Eversberg + * (C) 2017-2018 by Vadim Yanitskiy * * All Rights Reserved * @@ -19,39 +20,75 @@ * */ -#include +#include +#include #include #include +#include +#include #include #include - /* - * receive voice + * TCH frame (voice) router */ - static int gsm_recv_voice(struct osmocom_ms *ms, struct msgb *msg) { - struct gsm_data_frame *mncc; + struct gsm_data_frame *frame; - /* distribute and then free */ - if (ms->mncc_entity.mncc_recv && ms->mncc_entity.ref) { - /* push mncc header in front of data */ - mncc = (struct gsm_data_frame *) - msgb_push(msg, sizeof(struct gsm_data_frame)); - mncc->msg_type = GSM_TCHF_FRAME; - mncc->callref = ms->mncc_entity.ref; - ms->mncc_entity.mncc_recv(ms, mncc->msg_type, mncc); + /* Make sure that a MNCC handler is set */ + if (!ms->mncc_entity.mncc_recv) { + msgb_free(msg); + return -ENOTSUP; + } + + /* TODO: Make sure there is an active call */ + + /* Route a frame according to settings */ + switch (ms->settings.audio.io_target) { + /* External MNCC application (e.g. LCR) */ + case AUDIO_IO_SOCKET: + /* Push MNCC header in front of data */ + frame = (struct gsm_data_frame *) + msgb_push(msg, sizeof(struct gsm_data_frame)); + + /* FIXME: set proper msg_type */ + frame->msg_type = GSM_TCHF_FRAME; + frame->callref = ms->mncc_entity.ref; + + /* Forward to an MNCC-handler */ + ms->mncc_entity.mncc_recv(ms, frame->msg_type, frame); + + /* Release memory */ + msgb_free(msg); + break; + + /* Build-in GAPK-based audio back-end */ + case AUDIO_IO_GAPK: + /* Prevent null pointer dereference */ + if (!ms->gapk_io) { + msgb_free(msg); + break; + } + + /* Push a frame to the DL frame buffer */ + msgb_enqueue(&ms->gapk_io->tch_fb_dl, msg); + break; + + /* Drop frame and release memory */ + case AUDIO_IO_HARDWARE: + case AUDIO_IO_NONE: + default: + msgb_free(msg); } - msgb_free(msg); return 0; } /* - * send voice + * Send voice to the lower layers */ int gsm_send_voice(struct osmocom_ms *ms, struct msgb *msg) { @@ -78,12 +115,10 @@ int gsm_send_voice_mncc(struct osmocom_ms *ms, struct gsm_data_frame *frame) } /* - * init + * Init TCH frame (voice) router */ - int gsm_voice_init(struct osmocom_ms *ms) { ms->l1_entity.l1_traffic_ind = gsm_recv_voice; - return 0; }