mobile: fix segfault on empty TRAFFIC.ind
[osmocom-bb.git] / src / host / layer23 / src / mobile / gapk_io.c
blobd6b84a5edaf20006abb70e0a09da0965e2fb4a36
1 /*
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>
7 * All Rights Reserved
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.
21 #include <string.h>
22 #include <errno.h>
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;
49 struct msgb *tch_msg;
50 size_t frame_len;
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);
55 if (tch_msg == NULL)
56 return -EIO;
58 /* Calculate received frame length */
59 frame_len = msgb_l3len(tch_msg);
60 if (frame_len == 0) {
61 msgb_free(tch_msg);
62 return -EIO;
65 /* Copy the frame bytes from message */
66 memcpy(out, tch_msg->l3h, frame_len);
68 /* Release memory */
69 msgb_free(tch_msg);
71 return 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;
78 struct msgb *tch_msg;
80 if (state->tch_ul_fb_len >= GAPK_ULDL_QUEUE_LIMIT) {
81 LOGP(DGAPK, LOGL_ERROR, "UL TCH frame buffer overflow, dropping msg\n");
82 return -EOVERFLOW;
85 /* Allocate a new message for the lower layers */
86 tch_msg = msgb_alloc_headroom(in_len + 64, 64, "TCH frame");
87 if (tch_msg == NULL)
88 return -ENOMEM;
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);
98 return 0;
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,
108 bool is_src)
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);
118 if (item == NULL)
119 return -ENOMEM;
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";
126 /* I/O length */
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;
135 return 0;
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,
146 bool is_src)
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)
154 return -ENOTSUP;
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;
174 char *pq_desc;
175 int rc;
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");
181 if (pq == NULL)
182 return -ENOMEM;
184 /* ALSA audio source */
185 rc = osmo_gapk_pq_queue_alsa_input(pq, alsa_input_dev, rawpcm_fmt->frame_len);
186 if (rc)
187 goto error;
189 /* Frame encoder */
190 rc = osmo_gapk_pq_queue_codec(pq, gapk_io->codec_desc, 1);
191 if (rc)
192 goto error;
194 /* Encoder specific format -> canonical */
195 rc = pq_queue_codec_fmt_conv(pq, gapk_io->codec_desc, true);
196 if (rc)
197 goto error;
199 /* Canonical -> PHY specific format */
200 rc = osmo_gapk_pq_queue_fmt_convert(pq, gapk_io->phy_fmt_desc, 1);
201 if (rc)
202 goto error;
204 /* TCH frame buffer sink */
205 rc = pq_queue_tch_fb(pq, gapk_io, false);
206 if (rc)
207 goto error;
209 /* Check composed queue in strict mode */
210 rc = osmo_gapk_pq_check(pq, 1);
211 if (rc)
212 goto error;
214 /* Prepare queue (allocate buffers, etc.) */
215 rc = osmo_gapk_pq_prepare(pq);
216 if (rc)
217 goto error;
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);
227 return 0;
229 error:
230 talloc_free(pq);
231 return rc;
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;
251 char *pq_desc;
252 int rc;
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");
258 if (pq == NULL)
259 return -ENOMEM;
261 /* TCH frame buffer source */
262 rc = pq_queue_tch_fb(pq, gapk_io, true);
263 if (rc)
264 goto error;
266 /* PHY specific format -> canonical */
267 rc = osmo_gapk_pq_queue_fmt_convert(pq, gapk_io->phy_fmt_desc, 0);
268 if (rc)
269 goto error;
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);
276 if (rc)
277 goto error;
279 /* Frame decoder */
280 rc = osmo_gapk_pq_queue_codec(pq, gapk_io->codec_desc, 0);
281 if (rc)
282 goto error;
284 /* ALSA audio sink */
285 rc = osmo_gapk_pq_queue_alsa_output(pq, alsa_output_dev, rawpcm_fmt->frame_len);
286 if (rc)
287 goto error;
289 /* Check composed queue in strict mode */
290 rc = osmo_gapk_pq_check(pq, 1);
291 if (rc)
292 goto error;
294 /* Prepare queue (allocate buffers, etc.) */
295 rc = osmo_gapk_pq_prepare(pq);
296 if (rc)
297 goto error;
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);
307 return 0;
309 error:
310 talloc_free(pq);
311 return rc;
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)
321 struct msgb *msg;
323 if (ms->gapk_io == NULL)
324 return 0;
326 /* Flush TCH frame I/O buffers */
327 while ((msg = msgb_dequeue(&ms->gapk_io->tch_dl_fb)))
328 msgb_free(msg);
329 while ((msg = msgb_dequeue(&ms->gapk_io->tch_ul_fb)))
330 msgb_free(msg);
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);
339 ms->gapk_io = NULL;
341 return 0;
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)
351 switch (codec) {
352 case CODEC_HR:
353 return FMT_RTP_HR_IETF;
354 case CODEC_FR:
355 return FMT_GSM;
356 case CODEC_EFR:
357 return FMT_RTP_EFR;
358 case CODEC_AMR:
359 return FMT_RTP_AMR;
360 default:
361 return FMT_INVALID;
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)
371 switch (codec) {
372 case CODEC_HR:
373 return FMT_TI_HR;
374 case CODEC_FR:
375 return FMT_TI_FR;
376 case CODEC_EFR:
377 return FMT_TI_EFR;
378 case CODEC_AMR: /* not supported */
379 default:
380 return FMT_INVALID;
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;
396 int rc = 0;
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);
406 return -EINVAL;
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);
413 return -ENOTSUP;
416 switch (set->tch_voice.io_format) {
417 case TCH_VOICE_IOF_RTP:
418 phy_fmt = phy_fmt_pick_rtp(codec);
419 break;
420 case TCH_VOICE_IOF_TI:
421 phy_fmt = phy_fmt_pick_ti(codec);
422 break;
423 default:
424 LOGP(DGAPK, LOGL_ERROR, "Unhandled I/O format %s\n",
425 tch_voice_io_format_name(set->tch_voice.io_format));
426 return -ENOTSUP;
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);
433 return -EINVAL;
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");
439 return -ENOMEM;
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 */
461 if (rc) {
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");
472 return rc;
475 /* Init pointers */
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);
482 return 0;
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 */
498 switch (ch_mode) {
499 case GSM48_CMODE_SPEECH_V1: /* FR or HR */
500 if (ch_type == RSL_CHAN_Bm_ACCHs)
501 codec = CODEC_FR;
502 else
503 codec = CODEC_HR;
504 break;
506 case GSM48_CMODE_SPEECH_EFR:
507 codec = CODEC_EFR;
508 break;
510 case GSM48_CMODE_SPEECH_AMR:
511 codec = CODEC_AMR;
512 break;
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:
520 return 0;
521 default:
522 LOGP(DGAPK, LOGL_ERROR, "Unhandled channel mode 0x%02x (%s)\n",
523 ch_mode, get_value_string(gsm48_chan_mode_names, ch_mode));
524 return -EINVAL;
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");
548 msgb_free(msg);
549 return;
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;
560 int work = 0;
563 * Make sure we have at least two DL frames
564 * to prevent discontinuous playback.
566 if (gapk_io->tch_dl_fb_len < 2)
567 return 0;
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);
581 work |= 1;
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);
594 work |= 1;
597 return work;