2 * GAPK (GSM Audio Pocket Knife) based audio I/O
4 * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
5 * Contributions by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
24 #include <osmocom/core/msgb.h>
25 #include <osmocom/core/utils.h>
27 #include <osmocom/gsm/protocol/gsm_04_08.h>
28 #include <osmocom/gsm/protocol/gsm_08_58.h>
30 #include <osmocom/gapk/procqueue.h>
31 #include <osmocom/gapk/formats.h>
32 #include <osmocom/gapk/codecs.h>
33 #include <osmocom/gapk/common.h>
35 #include <osmocom/bb/common/osmocom_data.h>
36 #include <osmocom/bb/common/ms.h>
37 #include <osmocom/bb/common/logging.h>
39 #include <osmocom/bb/mobile/tch.h>
40 #include <osmocom/bb/mobile/gapk_io.h>
42 /* The RAW PCM format is common for both audio source and sink */
43 static const struct osmo_gapk_format_desc
*rawpcm_fmt
;
45 static int pq_queue_tch_fb_recv(void *_state
, uint8_t *out
,
46 const uint8_t *in
, unsigned int in_len
)
48 struct gapk_io_state
*state
= (struct gapk_io_state
*)_state
;
52 /* Obtain one TCH frame from the DL buffer */
53 tch_msg
= msgb_dequeue_count(&state
->tch_dl_fb
,
54 &state
->tch_dl_fb_len
);
58 /* Calculate received frame length */
59 frame_len
= msgb_l3len(tch_msg
);
65 /* Copy the frame bytes from message */
66 memcpy(out
, tch_msg
->l3h
, frame_len
);
74 static int pq_queue_tch_fb_send(void *_state
, uint8_t *out
,
75 const uint8_t *in
, unsigned int in_len
)
77 struct gapk_io_state
*state
= (struct gapk_io_state
*)_state
;
80 if (state
->tch_ul_fb_len
>= GAPK_ULDL_QUEUE_LIMIT
) {
81 LOGP(DGAPK
, LOGL_ERROR
, "UL TCH frame buffer overflow, dropping msg\n");
85 /* Allocate a new message for the lower layers */
86 tch_msg
= msgb_alloc_headroom(in_len
+ 64, 64, "TCH frame");
90 /* Copy the frame bytes to a new message */
91 tch_msg
->l2h
= msgb_put(tch_msg
, in_len
);
92 memcpy(tch_msg
->l2h
, in
, in_len
);
94 /* Put encoded TCH frame to the UL buffer */
95 msgb_enqueue_count(&state
->tch_ul_fb
, tch_msg
,
96 &state
->tch_ul_fb_len
);
102 * A custom TCH frame buffer block, which actually
103 * handles incoming frames from DL buffer and puts
104 * outgoing frames to UL buffer...
106 static int pq_queue_tch_fb(struct osmo_gapk_pq
*pq
,
107 struct gapk_io_state
*io_state
,
110 struct osmo_gapk_pq_item
*item
;
111 unsigned int frame_len
;
113 LOGP(DGAPK
, LOGL_DEBUG
, "PQ '%s': Adding TCH frame buffer %s\n",
114 pq
->name
, is_src
? "input" : "output");
116 /* Allocate and add a new queue item */
117 item
= osmo_gapk_pq_add_item(pq
);
121 /* General item type and description */
122 item
->type
= is_src
? OSMO_GAPK_ITEM_TYPE_SOURCE
: OSMO_GAPK_ITEM_TYPE_SINK
;
123 item
->cat_name
= is_src
? "source" : "sink";
124 item
->sub_name
= "tch_fb";
127 frame_len
= io_state
->phy_fmt_desc
->frame_len
;
128 item
->len_in
= is_src
? 0 : frame_len
;
129 item
->len_out
= is_src
? frame_len
: 0;
131 /* Handler and it's state */
132 item
->proc
= is_src
? &pq_queue_tch_fb_recv
: &pq_queue_tch_fb_send
;
133 item
->state
= io_state
;
139 * Auxiliary wrapper around format conversion block.
140 * Is used to perform either a conversion from the format,
141 * produced by encoder, to canonical, or a conversion
142 * from canonical format to the format expected by decoder.
144 static int pq_queue_codec_fmt_conv(struct osmo_gapk_pq
*pq
,
145 const struct osmo_gapk_codec_desc
*codec
,
148 const struct osmo_gapk_format_desc
*codec_fmt_desc
;
150 /* Get format description */
151 codec_fmt_desc
= osmo_gapk_fmt_get_from_type(is_src
?
152 codec
->codec_enc_format_type
: codec
->codec_dec_format_type
);
153 if (codec_fmt_desc
== NULL
)
156 /* Put format conversion block */
157 return osmo_gapk_pq_queue_fmt_convert(pq
, codec_fmt_desc
, !is_src
);
161 * Prepares the following queue (source is mic):
163 * source/alsa -> proc/codec -> proc/format ->
164 * -> proc/format -> sink/tch_fb
166 * The two format conversion blocks are aimed to
167 * convert an encoder specific format
168 * to a PHY specific format.
170 static int prepare_audio_source(struct gapk_io_state
*gapk_io
,
171 const char *alsa_input_dev
)
173 struct osmo_gapk_pq
*pq
;
177 LOGP(DGAPK
, LOGL_DEBUG
, "Prepare audio input (capture) chain\n");
179 /* Allocate a processing queue */
180 pq
= osmo_gapk_pq_create("pq_audio_source");
184 /* ALSA audio source */
185 rc
= osmo_gapk_pq_queue_alsa_input(pq
, alsa_input_dev
, rawpcm_fmt
->frame_len
);
190 rc
= osmo_gapk_pq_queue_codec(pq
, gapk_io
->codec_desc
, 1);
194 /* Encoder specific format -> canonical */
195 rc
= pq_queue_codec_fmt_conv(pq
, gapk_io
->codec_desc
, true);
199 /* Canonical -> PHY specific format */
200 rc
= osmo_gapk_pq_queue_fmt_convert(pq
, gapk_io
->phy_fmt_desc
, 1);
204 /* TCH frame buffer sink */
205 rc
= pq_queue_tch_fb(pq
, gapk_io
, false);
209 /* Check composed queue in strict mode */
210 rc
= osmo_gapk_pq_check(pq
, 1);
214 /* Prepare queue (allocate buffers, etc.) */
215 rc
= osmo_gapk_pq_prepare(pq
);
219 /* Save pointer within MS GAPK state */
220 gapk_io
->pq_source
= pq
;
222 /* Describe prepared chain */
223 pq_desc
= osmo_gapk_pq_describe(pq
);
224 LOGP(DGAPK
, LOGL_DEBUG
, "PQ '%s': chain '%s' prepared\n", pq
->name
, pq_desc
);
225 talloc_free(pq_desc
);
235 * Prepares the following queue (sink is speaker):
237 * src/tch_fb -> proc/format -> [proc/ecu] ->
238 * proc/format -> proc/codec -> sink/alsa
240 * The two format conversion blocks (proc/format)
241 * are aimed to convert a PHY specific format
242 * to an encoder specific format.
244 * A ECU (Error Concealment Unit) block is optionally
245 * added if implemented for a given codec.
247 static int prepare_audio_sink(struct gapk_io_state
*gapk_io
,
248 const char *alsa_output_dev
)
250 struct osmo_gapk_pq
*pq
;
254 LOGP(DGAPK
, LOGL_DEBUG
, "Prepare audio output (playback) chain\n");
256 /* Allocate a processing queue */
257 pq
= osmo_gapk_pq_create("pq_audio_sink");
261 /* TCH frame buffer source */
262 rc
= pq_queue_tch_fb(pq
, gapk_io
, true);
266 /* PHY specific format -> canonical */
267 rc
= osmo_gapk_pq_queue_fmt_convert(pq
, gapk_io
->phy_fmt_desc
, 0);
271 /* Optional ECU (Error Concealment Unit) */
272 osmo_gapk_pq_queue_ecu(pq
, gapk_io
->codec_desc
);
274 /* Canonical -> decoder specific format */
275 rc
= pq_queue_codec_fmt_conv(pq
, gapk_io
->codec_desc
, false);
280 rc
= osmo_gapk_pq_queue_codec(pq
, gapk_io
->codec_desc
, 0);
284 /* ALSA audio sink */
285 rc
= osmo_gapk_pq_queue_alsa_output(pq
, alsa_output_dev
, rawpcm_fmt
->frame_len
);
289 /* Check composed queue in strict mode */
290 rc
= osmo_gapk_pq_check(pq
, 1);
294 /* Prepare queue (allocate buffers, etc.) */
295 rc
= osmo_gapk_pq_prepare(pq
);
299 /* Save pointer within MS GAPK state */
300 gapk_io
->pq_sink
= pq
;
302 /* Describe prepared chain */
303 pq_desc
= osmo_gapk_pq_describe(pq
);
304 LOGP(DGAPK
, LOGL_DEBUG
, "PQ '%s': chain '%s' prepared\n", pq
->name
, pq_desc
);
305 talloc_free(pq_desc
);
315 * Cleans up both TCH frame I/O buffers, destroys both
316 * processing queues (chains), and deallocates the memory.
317 * Should be called when a voice call is finished...
319 int gapk_io_clean_up_ms(struct osmocom_ms
*ms
)
323 if (ms
->gapk_io
== NULL
)
326 /* Flush TCH frame I/O buffers */
327 while ((msg
= msgb_dequeue(&ms
->gapk_io
->tch_dl_fb
)))
329 while ((msg
= msgb_dequeue(&ms
->gapk_io
->tch_ul_fb
)))
332 /* Destroy both audio I/O chains */
333 if (ms
->gapk_io
->pq_source
)
334 osmo_gapk_pq_destroy(ms
->gapk_io
->pq_source
);
335 if (ms
->gapk_io
->pq_sink
)
336 osmo_gapk_pq_destroy(ms
->gapk_io
->pq_sink
);
338 talloc_free(ms
->gapk_io
);
345 * Picks the corresponding PHY's frame format for a given codec.
346 * To be used with PHYs that produce audio frames in RTP format,
347 * such as trxcon (GSM 05.03 libosmocoding API).
349 static enum osmo_gapk_format_type
phy_fmt_pick_rtp(enum osmo_gapk_codec_type codec
)
353 return FMT_RTP_HR_IETF
;
366 * Picks the corresponding PHY's frame format for a given codec.
367 * To be used with PHYs that produce audio in TI Calypso format.
369 static enum osmo_gapk_format_type
phy_fmt_pick_ti(enum osmo_gapk_codec_type codec
)
378 case CODEC_AMR
: /* not supported */
385 * Allocates both TCH frame I/O buffers
386 * and prepares both processing queues (chains).
387 * Should be called when a voice call is initiated...
389 int gapk_io_init_ms(struct osmocom_ms
*ms
, enum osmo_gapk_codec_type codec
)
391 const struct osmo_gapk_format_desc
*phy_fmt_desc
;
392 const struct osmo_gapk_codec_desc
*codec_desc
;
393 struct gsm_settings
*set
= &ms
->settings
;
394 enum osmo_gapk_format_type phy_fmt
;
395 struct gapk_io_state
*gapk_io
;
398 LOGP(DGAPK
, LOGL_NOTICE
, "Initialize GAPK I/O\n");
400 OSMO_ASSERT(ms
->gapk_io
== NULL
);
402 /* Make sure that the chosen codec has description */
403 codec_desc
= osmo_gapk_codec_get_from_type(codec
);
404 if (codec_desc
== NULL
) {
405 LOGP(DGAPK
, LOGL_ERROR
, "Invalid codec type 0x%02x\n", codec
);
409 /* Make sure that the chosen codec is supported */
410 if (codec_desc
->codec_encode
== NULL
|| codec_desc
->codec_decode
== NULL
) {
411 LOGP(DGAPK
, LOGL_ERROR
,
412 "Codec '%s' is not supported by GAPK\n", codec_desc
->name
);
416 switch (set
->tch_voice
.io_format
) {
417 case TCH_VOICE_IOF_RTP
:
418 phy_fmt
= phy_fmt_pick_rtp(codec
);
420 case TCH_VOICE_IOF_TI
:
421 phy_fmt
= phy_fmt_pick_ti(codec
);
424 LOGP(DGAPK
, LOGL_ERROR
, "Unhandled I/O format %s\n",
425 tch_voice_io_format_name(set
->tch_voice
.io_format
));
429 phy_fmt_desc
= osmo_gapk_fmt_get_from_type(phy_fmt
);
430 if (phy_fmt_desc
== NULL
) {
431 LOGP(DGAPK
, LOGL_ERROR
, "Failed to pick the PHY specific "
432 "frame format for codec '%s'\n", codec_desc
->name
);
436 gapk_io
= talloc_zero(ms
, struct gapk_io_state
);
437 if (gapk_io
== NULL
) {
438 LOGP(DGAPK
, LOGL_ERROR
, "Failed to allocate memory\n");
442 /* Init TCH frame I/O buffers */
443 INIT_LLIST_HEAD(&gapk_io
->tch_dl_fb
);
444 INIT_LLIST_HEAD(&gapk_io
->tch_ul_fb
);
446 /* Store the codec / format description */
447 gapk_io
->codec_desc
= codec_desc
;
448 gapk_io
->phy_fmt_desc
= phy_fmt_desc
;
450 /* Use gapk_io_state as talloc context for both chains */
451 osmo_gapk_set_talloc_ctx(gapk_io
);
453 /* Prepare both source and sink chains */
454 rc
|= prepare_audio_source(gapk_io
, set
->tch_voice
.alsa_input_dev
);
455 rc
|= prepare_audio_sink(gapk_io
, set
->tch_voice
.alsa_output_dev
);
457 /* Fall back to ms instance */
458 osmo_gapk_set_talloc_ctx(ms
);
460 /* If at lease one chain constructor failed */
462 /* Destroy both audio I/O chains */
463 if (gapk_io
->pq_source
)
464 osmo_gapk_pq_destroy(gapk_io
->pq_source
);
465 if (gapk_io
->pq_sink
)
466 osmo_gapk_pq_destroy(gapk_io
->pq_sink
);
468 /* Release the memory and return */
469 talloc_free(gapk_io
);
471 LOGP(DGAPK
, LOGL_ERROR
, "Failed to initialize GAPK I/O\n");
476 ms
->gapk_io
= gapk_io
;
478 LOGP(DGAPK
, LOGL_NOTICE
,
479 "GAPK I/O initialized for MS '%s', codec '%s'\n",
480 ms
->name
, codec_desc
->name
);
486 * Wrapper around gapk_io_init_ms(), that maps both
487 * given GSM 04.08 channel type (HR/FR) and channel
488 * mode to a codec from 'osmo_gapk_codec_type' enum,
489 * checks if a mapped codec is supported by GAPK,
490 * and finally calls the wrapped function.
492 int gapk_io_init_ms_chan(struct osmocom_ms
*ms
,
493 uint8_t ch_type
, uint8_t ch_mode
)
495 enum osmo_gapk_codec_type codec
;
497 /* Map GSM 04.08 channel mode to GAPK codec type */
499 case GSM48_CMODE_SPEECH_V1
: /* FR or HR */
500 if (ch_type
== RSL_CHAN_Bm_ACCHs
)
506 case GSM48_CMODE_SPEECH_EFR
:
510 case GSM48_CMODE_SPEECH_AMR
:
514 /* Signalling or CSD, do nothing */
515 case GSM48_CMODE_DATA_14k5
:
516 case GSM48_CMODE_DATA_12k0
:
517 case GSM48_CMODE_DATA_6k0
:
518 case GSM48_CMODE_DATA_3k6
:
519 case GSM48_CMODE_SIGN
:
522 LOGP(DGAPK
, LOGL_ERROR
, "Unhandled channel mode 0x%02x (%s)\n",
523 ch_mode
, get_value_string(gsm48_chan_mode_names
, ch_mode
));
527 return gapk_io_init_ms(ms
, codec
);
531 * Performs basic initialization of GAPK library,
532 * setting the talloc root context and a logging category.
533 * Should be called during the application initialization...
535 void gapk_io_init(void)
537 /* Init logging subsystem */
538 osmo_gapk_log_init(DGAPK
);
540 /* Make RAWPCM format info easy to access */
541 rawpcm_fmt
= osmo_gapk_fmt_get_from_type(FMT_RAWPCM_S16LE
);
544 void gapk_io_enqueue_dl(struct gapk_io_state
*state
, struct msgb
*msg
)
546 if (state
->tch_dl_fb_len
>= GAPK_ULDL_QUEUE_LIMIT
) {
547 LOGP(DGAPK
, LOGL_ERROR
, "DL TCH frame buffer overflow, dropping msg\n");
552 msgb_enqueue_count(&state
->tch_dl_fb
, msg
,
553 &state
->tch_dl_fb_len
);
556 /* Serves both UL/DL TCH frame I/O buffers */
557 int gapk_io_serve_ms(struct osmocom_ms
*ms
)
559 struct gapk_io_state
*gapk_io
= ms
->gapk_io
;
563 * Make sure we have at least two DL frames
564 * to prevent discontinuous playback.
566 if (gapk_io
->tch_dl_fb_len
< 2)
570 * TODO: if there is an active call, but no TCH frames
571 * in DL buffer, put silence frames using the upcoming
572 * ECU (Error Concealment Unit) of libosmocodec.
574 while (!llist_empty(&gapk_io
->tch_dl_fb
)) {
575 /* Decode and play a received DL TCH frame */
576 osmo_gapk_pq_execute(gapk_io
->pq_sink
);
578 /* Record and encode an UL TCH frame back */
579 osmo_gapk_pq_execute(gapk_io
->pq_source
);
584 while (!llist_empty(&gapk_io
->tch_ul_fb
)) {
585 struct msgb
*tch_msg
;
587 /* Obtain one TCH frame from the UL buffer */
588 tch_msg
= msgb_dequeue_count(&gapk_io
->tch_ul_fb
,
589 &gapk_io
->tch_ul_fb_len
);
591 /* Push a voice frame to the lower layers */
592 tch_send_voice_msg(ms
, tch_msg
);