summaryrefslogtreecommitdiff
path: root/src/audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio.c')
-rw-r--r--src/audio.c1369
1 files changed, 1369 insertions, 0 deletions
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..548b5ac
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,1369 @@
+/**
+ * @file src/audio.c Audio stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * \ref GenericAudioStream
+ */
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0x000a0d10
+#include "magic.h"
+
+
+/**
+ * \page GenericAudioStream Generic Audio Stream
+ *
+ * Implements a generic audio stream. The application can allocate multiple
+ * instances of a audio stream, mapping it to a particular SDP media line.
+ * The audio object has a DSP sound card sink and source, and an audio encoder
+ * and decoder. A particular audio object is mapped to a generic media
+ * stream object. Each audio channel has an optional audio filtering chain.
+ *
+ *<pre>
+ * write read
+ * | /|\
+ * \|/ |
+ * .------. .---------. .-------.
+ * |filter|<--| audio |--->|encoder|
+ * '------' | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .------. .-----.
+ * |auplay| |ausrc|
+ * '------' '-----'
+ *</pre>
+ */
+
+enum {
+ AUDIO_SAMPSZ = 1920,
+};
+
+
+/**
+ * Audio transmit/encoder
+ *
+ *
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .-------. .-------. .--------. .--------. .--------.
+ | | | | | | | | | | |
+ |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP
+ | | | | | | | | | | |
+ ' '-------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ *
+ */
+struct autx {
+ struct ausrc_st *ausrc; /**< Audio Source */
+ const struct aucodec *ac; /**< Current audio encoder */
+ struct auenc_state *enc; /**< Audio encoder state (optional) */
+ struct aubuf *aubuf; /**< Packetize outgoing stream */
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in encoding order */
+ struct mbuf *mb; /**< Buffer for outgoing RTP packets */
+ char device[64];
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for sending */
+ uint32_t ts; /**< Timestamp for outgoing RTP */
+ uint32_t ts_tel; /**< Timestamp for Telephony Events */
+ size_t psize; /**< Packet size for sending */
+ bool marker; /**< Marker bit for outgoing RTP */
+ bool is_g722; /**< Set if encoder is G.722 codec */
+ bool muted; /**< Audio source is muted */
+ int cur_key; /**< Currently transmitted event */
+
+ union {
+ struct tmr tmr; /**< Timer for sending RTP packets */
+#ifdef HAVE_PTHREAD
+ struct {
+ pthread_t tid;/**< Audio transmit thread */
+ bool run; /**< Audio transmit thread running */
+ } thr;
+#endif
+ } u;
+};
+
+
+/**
+ * Audio receive/decoder
+ *
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .--------. .-------. .--------. .--------. .--------.
+ |\ | | | | | | | | | |
+ | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP
+ |/ | | | | | | | | | |
+ '--------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ */
+struct aurx {
+ struct auplay_st *auplay; /**< Audio Player */
+ const struct aucodec *ac; /**< Current audio decoder */
+ struct audec_state *dec; /**< Audio decoder state (optional) */
+ struct aubuf *aubuf; /**< Incoming audio buffer */
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in decoding order */
+ char device[64];
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for receiving */
+ int pt; /**< Payload type for incoming RTP */
+ int pt_tel; /**< Event payload type - receive */
+};
+
+
+/** Generic Audio stream */
+struct audio {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct autx tx; /**< Transmit */
+ struct aurx rx; /**< Receive */
+ struct stream *strm; /**< Generic media stream */
+ struct telev *telev; /**< Telephony events */
+ struct config_audio cfg; /**< Audio configuration */
+ bool started; /**< Stream is started flag */
+ audio_event_h *eventh; /**< Event handler */
+ audio_err_h *errh; /**< Audio error handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stop_tx(struct autx *tx, struct audio *a)
+{
+ if (!tx || !a)
+ return;
+
+ switch (a->cfg.txmode) {
+
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ case AUDIO_MODE_THREAD_REALTIME:
+ if (tx->u.thr.run) {
+ tx->u.thr.run = false;
+ pthread_join(tx->u.thr.tid, NULL);
+ }
+ break;
+#endif
+ case AUDIO_MODE_TMR:
+ tmr_cancel(&tx->u.tmr);
+ break;
+
+ default:
+ break;
+ }
+
+ /* audio source must be stopped first */
+ tx->ausrc = mem_deref(tx->ausrc);
+ tx->aubuf = mem_deref(tx->aubuf);
+
+ list_flush(&tx->filtl);
+}
+
+
+static void stop_rx(struct aurx *rx)
+{
+ if (!rx)
+ return;
+
+ /* audio player must be stopped first */
+ rx->auplay = mem_deref(rx->auplay);
+ rx->aubuf = mem_deref(rx->aubuf);
+
+ list_flush(&rx->filtl);
+}
+
+
+static void audio_destructor(void *arg)
+{
+ struct audio *a = arg;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+
+ mem_deref(a->tx.enc);
+ mem_deref(a->rx.dec);
+ mem_deref(a->tx.aubuf);
+ mem_deref(a->tx.mb);
+ mem_deref(a->tx.sampv);
+ mem_deref(a->rx.sampv);
+ mem_deref(a->rx.aubuf);
+ mem_deref(a->tx.sampv_rs);
+ mem_deref(a->rx.sampv_rs);
+
+ list_flush(&a->tx.filtl);
+ list_flush(&a->rx.filtl);
+
+ mem_deref(a->strm);
+ mem_deref(a->telev);
+}
+
+
+/**
+ * Calculate number of samples from sample rate, channels and packet time
+ *
+ * @param srate Sample rate in [Hz]
+ * @param channels Number of channels
+ * @param ptime Packet time in [ms]
+ *
+ * @return Number of samples
+ */
+static inline uint32_t calc_nsamp(uint32_t srate, uint8_t channels,
+ uint16_t ptime)
+{
+ return srate * channels * ptime / 1000;
+}
+
+
+/**
+ * Get the DSP samplerate for an audio-codec (exception for G.722)
+ */
+static inline uint32_t get_srate(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return !str_casecmp(ac->name, "G722") ? 16000 : ac->srate;
+}
+
+
+static inline uint32_t get_framesize(const struct aucodec *ac,
+ uint32_t ptime)
+{
+ if (!ac)
+ return 0;
+
+ return calc_nsamp(get_srate(ac), ac->ch, ptime);
+}
+
+
+static bool aucodec_equal(const struct aucodec *a, const struct aucodec *b)
+{
+ if (!a || !b)
+ return false;
+
+ return get_srate(a) == get_srate(b) && a->ch == b->ch;
+}
+
+
+static int add_audio_codec(struct audio *a, struct sdp_media *m,
+ struct aucodec *ac)
+{
+ if (!in_range(&a->cfg.srate, ac->srate)) {
+ debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n",
+ ac->srate, a->cfg.srate.min, a->cfg.srate.max);
+ return 0;
+ }
+
+ if (!in_range(&a->cfg.channels, ac->ch)) {
+ debug("audio: skip codec with %uch (audio range %uch-%uch)\n",
+ ac->ch, a->cfg.channels.min, a->cfg.channels.max);
+ return 0;
+ }
+
+ return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->srate,
+ ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false,
+ "%s", ac->fmtp);
+}
+
+
+/**
+ * Encoder audio and send via stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param a Audio object
+ * @param tx Audio transmit object
+ * @param sampv Audio samples
+ * @param sampc Number of audio samples
+ */
+static void encode_rtp_send(struct audio *a, struct autx *tx,
+ int16_t *sampv, size_t sampc)
+{
+ size_t len;
+ int err;
+
+ if (!tx->ac)
+ return;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+ len = mbuf_get_space(tx->mb);
+
+ err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc);
+ if (err) {
+ warning("audio: %s encode error: %d samples (%m)\n",
+ tx->ac->name, sampc, err);
+ goto out;
+ }
+
+ tx->mb->pos = STREAM_PRESZ;
+ tx->mb->end = STREAM_PRESZ + len;
+
+ if (mbuf_get_left(tx->mb)) {
+
+ err = stream_send(a->strm, tx->marker, -1, tx->ts, tx->mb);
+ if (err)
+ goto out;
+ }
+
+ tx->ts += (uint32_t)(tx->is_g722 ? sampc/2 : sampc);
+
+ out:
+ tx->marker = false;
+}
+
+
+/*
+ * @note This function has REAL-TIME properties
+ */
+static void poll_aubuf_tx(struct audio *a)
+{
+ struct autx *tx = &a->tx;
+ int16_t *sampv = tx->sampv;
+ size_t sampc;
+ struct le *le;
+ int err = 0;
+
+ sampc = tx->psize / 2;
+
+ /* timed read from audio-buffer */
+ if (aubuf_get_samp(tx->aubuf, tx->ptime, tx->sampv, sampc))
+ return;
+
+ /* optional resampler */
+ if (tx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&tx->resamp,
+ tx->sampv_rs, &sampc_rs,
+ tx->sampv, sampc);
+ if (err)
+ return;
+
+ sampv = tx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ /* Process exactly one audio-frame in list order */
+ for (le = tx->filtl.head; le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af && st->af->ench)
+ err |= st->af->ench(st, sampv, &sampc);
+ }
+ if (err) {
+ warning("audio: aufilter encode: %m\n", err);
+ }
+
+ /* Encode and send */
+ encode_rtp_send(a, tx, sampv, sampc);
+}
+
+
+static void check_telev(struct audio *a, struct autx *tx)
+{
+ const struct sdp_format *fmt;
+ bool marker = false;
+ int err;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ err = telev_poll(a->telev, &marker, tx->mb);
+ if (err)
+ return;
+
+ if (marker)
+ tx->ts_tel = tx->ts;
+
+ fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt);
+ if (!fmt)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ;
+ err = stream_send(a->strm, marker, fmt->pt, tx->ts_tel, tx->mb);
+ if (err) {
+ warning("audio: telev: stream_send %m\n", err);
+ }
+}
+
+
+/**
+ * Write samples to Audio Player.
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note The application is responsible for filling in silence in
+ * the case of underrun
+ *
+ * @note This function may be called from any thread
+ *
+ * @param buf Buffer to fill with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ *
+ * @return true for valid audio samples, false for silence
+ */
+static bool auplay_write_handler(uint8_t *buf, size_t sz, void *arg)
+{
+ struct aurx *rx = arg;
+
+ aubuf_read(rx->aubuf, buf, sz);
+
+ return true;
+}
+
+
+/**
+ * Read samples from Audio Source
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note This function may be called from any thread
+ *
+ * @param buf Buffer with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ */
+static void ausrc_read_handler(const uint8_t *buf, size_t sz, void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+
+ if (tx->muted)
+ memset((void *)buf, 0, sz);
+
+ (void)aubuf_write(tx->aubuf, buf, sz);
+
+ if (a->cfg.txmode == AUDIO_MODE_POLL)
+ poll_aubuf_tx(a);
+
+ /* Exact timing: send Telephony-Events from here */
+ check_telev(a, tx);
+}
+
+
+static void ausrc_error_handler(int err, const char *str, void *arg)
+{
+ struct audio *a = arg;
+ MAGIC_CHECK(a);
+
+ if (a->errh)
+ a->errh(err, str, a->arg);
+}
+
+
+static int pt_handler(struct audio *a, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(a->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Audio decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ a->rx.pt = pt_new;
+
+ return audio_decoder_set(a, lc->data, lc->pt, lc->params);
+}
+
+
+static void handle_telev(struct audio *a, struct mbuf *mb)
+{
+ int event, digit;
+ bool end;
+
+ if (telev_recv(a->telev, mb, &event, &end))
+ return;
+
+ digit = telev_code2digit(event);
+ if (digit >= 0 && a->eventh)
+ a->eventh(digit, end, a->arg);
+}
+
+
+static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb)
+{
+ size_t sampc = AUDIO_SAMPSZ;
+ int16_t *sampv;
+ struct le *le;
+ int err = 0;
+
+ /* No decoder set */
+ if (!rx->ac)
+ return 0;
+
+ if (mbuf_get_left(mb)) {
+ err = rx->ac->dech(rx->dec, rx->sampv, &sampc,
+ mbuf_buf(mb), mbuf_get_left(mb));
+ }
+ else if (rx->ac->plch) {
+ err = rx->ac->plch(rx->dec, rx->sampv, &sampc);
+ }
+ else {
+ /* no PLC in the codec, might be done in filters below */
+ sampc = 0;
+ }
+
+ if (err) {
+ warning("audio: %s codec decode %u bytes: %m\n",
+ rx->ac->name, mbuf_get_left(mb), err);
+ goto out;
+ }
+
+ /* Process exactly one audio-frame in reverse list order */
+ for (le = rx->filtl.tail; le; le = le->prev) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af && st->af->dech)
+ err |= st->af->dech(st, rx->sampv, &sampc);
+ }
+
+ if (!rx->aubuf)
+ goto out;
+
+ sampv = rx->sampv;
+
+ /* optional resampler */
+ if (rx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&rx->resamp,
+ rx->sampv_rs, &sampc_rs,
+ rx->sampv, sampc);
+ if (err)
+ return err;
+
+ sampv = rx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ err = aubuf_write_samp(rx->aubuf, sampv, sampc);
+ if (err)
+ goto out;
+
+ out:
+ return err;
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct audio *a = arg;
+ struct aurx *rx = &a->rx;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Telephone event? */
+ if (hdr->pt == rx->pt_tel) {
+ handle_telev(a, mb);
+ return;
+ }
+
+ /* Comfort Noise (CN) as of RFC 3389 */
+ if (PT_CN == hdr->pt)
+ return;
+
+ /* Audio payload-type changed? */
+ /* XXX: this logic should be moved to stream.c */
+ if (hdr->pt != rx->pt) {
+
+ err = pt_handler(a, rx->pt, hdr->pt);
+ if (err)
+ return;
+ }
+
+ out:
+ (void)aurx_stream_decode(&a->rx, mb);
+}
+
+
+static int add_telev_codec(struct audio *a)
+{
+ struct sdp_media *m = stream_sdpmedia(audio_strm(a));
+ struct sdp_format *sf;
+ int err;
+
+ /* Use payload-type 101 if available, for CiscoGW interop */
+ err = sdp_format_add(&sf, m, false,
+ (!sdp_media_lformat(m, 101)) ? "101" : NULL,
+ telev_rtpfmt, TELEV_SRATE, 1, NULL,
+ NULL, NULL, false, "0-15");
+ if (err)
+ return err;
+
+ a->rx.pt_tel = sf->pt;
+
+ return err;
+}
+
+
+int audio_alloc(struct audio **ap, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ uint32_t ptime, const struct list *aucodecl,
+ audio_event_h *eventh, audio_err_h *errh, void *arg)
+{
+ struct audio *a;
+ struct autx *tx;
+ struct aurx *rx;
+ struct le *le;
+ int err;
+
+ if (!ap || !cfg)
+ return EINVAL;
+
+ a = mem_zalloc(sizeof(*a), audio_destructor);
+ if (!a)
+ return ENOMEM;
+
+ MAGIC_INIT(a);
+
+ a->cfg = cfg->audio;
+ tx = &a->tx;
+ rx = &a->rx;
+
+ err = stream_alloc(&a->strm, &cfg->avt, call, sdp_sess,
+ "audio", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, NULL, a);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max) {
+ stream_set_bw(a->strm, AUDIO_BANDWIDTH);
+ }
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "ptime", "%u", ptime);
+ if (err)
+ goto out;
+
+ /* Audio codecs */
+ for (le = list_head(aucodecl); le; le = le->next) {
+ err = add_audio_codec(a, stream_sdpmedia(a->strm), le->data);
+ if (err)
+ goto out;
+ }
+
+ tx->mb = mbuf_alloc(STREAM_PRESZ + 4096);
+ tx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ rx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!tx->mb || !tx->sampv || !rx->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = telev_alloc(&a->telev, TELEV_PTIME);
+ if (err)
+ goto out;
+
+ err = add_telev_codec(a);
+ if (err)
+ goto out;
+
+ auresamp_init(&tx->resamp);
+ str_ncpy(tx->device, a->cfg.src_dev, sizeof(tx->device));
+ tx->ptime = ptime;
+ tx->ts = 160;
+ tx->marker = true;
+
+ auresamp_init(&rx->resamp);
+ str_ncpy(rx->device, a->cfg.play_dev, sizeof(rx->device));
+ rx->pt = -1;
+ rx->ptime = ptime;
+
+ a->eventh = eventh;
+ a->errh = errh;
+ a->arg = arg;
+
+ if (a->cfg.txmode == AUDIO_MODE_TMR)
+ tmr_init(&tx->u.tmr);
+
+ out:
+ if (err)
+ mem_deref(a);
+ else
+ *ap = a;
+
+ return err;
+}
+
+
+#ifdef HAVE_PTHREAD
+static void *tx_thread(void *arg)
+{
+ struct audio *a = arg;
+
+ /* Enable Real-time mode for this thread, if available */
+ if (a->cfg.txmode == AUDIO_MODE_THREAD_REALTIME)
+ (void)realtime_enable(true, 1);
+
+ while (a->tx.u.thr.run) {
+
+ poll_aubuf_tx(a);
+
+ sys_msleep(5);
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void timeout_tx(void *arg)
+{
+ struct audio *a = arg;
+
+ tmr_start(&a->tx.u.tmr, 5, timeout_tx, a);
+
+ poll_aubuf_tx(a);
+}
+
+
+static void aufilt_param_set(struct aufilt_prm *prm,
+ const struct aucodec *ac, uint32_t ptime)
+{
+ if (!ac) {
+ memset(prm, 0, sizeof(*prm));
+ return;
+ }
+
+ prm->srate = get_srate(ac);
+ prm->ch = ac->ch;
+ prm->ptime = ptime;
+}
+
+
+static int autx_print_pipeline(struct re_printf *pf, const struct autx *autx)
+{
+ struct le *le;
+ int err;
+
+ if (!autx)
+ return 0;
+
+ err = re_hprintf(pf, "audio tx pipeline: %10s",
+ autx->ausrc ? autx->ausrc->as->name : "src");
+
+ for (le = list_head(&autx->filtl); le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af->ench)
+ err |= re_hprintf(pf, " ---> %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ autx->ac ? autx->ac->name : "encoder");
+
+ return err;
+}
+
+
+static int aurx_print_pipeline(struct re_printf *pf, const struct aurx *aurx)
+{
+ struct le *le;
+ int err;
+
+ if (!aurx)
+ return 0;
+
+ err = re_hprintf(pf, "audio rx pipeline: %10s",
+ aurx->auplay ? aurx->auplay->ap->name : "play");
+
+ for (le = list_head(&aurx->filtl); le; le = le->next) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af->dech)
+ err |= re_hprintf(pf, " <--- %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ aurx->ac ? aurx->ac->name : "decoder");
+
+ return err;
+}
+
+
+/**
+ * Setup the audio-filter chain
+ *
+ * must be called before auplay/ausrc-alloc
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int aufilt_setup(struct audio *a)
+{
+ struct aufilt_prm encprm, decprm;
+ struct autx *tx = &a->tx;
+ struct aurx *rx = &a->rx;
+ struct le *le;
+ int err = 0;
+
+ /* wait until we have both Encoder and Decoder */
+ if (!tx->ac || !rx->ac)
+ return 0;
+
+ if (!list_isempty(&tx->filtl) || !list_isempty(&rx->filtl))
+ return 0;
+
+ aufilt_param_set(&encprm, tx->ac, tx->ptime);
+ aufilt_param_set(&decprm, rx->ac, rx->ptime);
+
+ /* Audio filters */
+ for (le = list_head(aufilt_list()); le; le = le->next) {
+ struct aufilt *af = le->data;
+ struct aufilt_enc_st *encst = NULL;
+ struct aufilt_dec_st *decst = NULL;
+ void *ctx = NULL;
+
+ if (af->encupdh) {
+ err |= af->encupdh(&encst, &ctx, af, &encprm);
+ if (err)
+ break;
+
+ encst->af = af;
+ list_append(&tx->filtl, &encst->le, encst);
+ }
+
+ if (af->decupdh) {
+ err |= af->decupdh(&decst, &ctx, af, &decprm);
+ if (err)
+ break;
+
+ decst->af = af;
+ list_append(&rx->filtl, &decst->le, decst);
+ }
+
+ if (err) {
+ warning("audio: audio-filter '%s'"
+ " update failed (%m)\n", af->name, err);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int start_player(struct aurx *rx, struct audio *a)
+{
+ const struct aucodec *ac = rx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = ac->ch;
+
+ if (a->cfg.srate_play && a->cfg.srate_play != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_play;
+ }
+ if (a->cfg.channels_play && a->cfg.channels_play != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_play;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !rx->sampv_rs) {
+
+ info("audio: enable auplay resampler:"
+ " %uHz/%uch --> %uHz/%uch\n",
+ get_srate(ac), ac->ch, srate_dsp, channels_dsp);
+
+ rx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!rx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&rx->resamp,
+ get_srate(ac), ac->ch,
+ srate_dsp, channels_dsp);
+ if (err) {
+ warning("audio: could not setup auplay resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Player */
+ if (!rx->auplay && auplay_find(NULL)) {
+
+ struct auplay_prm prm;
+
+ prm.fmt = AUFMT_S16LE;
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = rx->ptime;
+
+ if (!rx->aubuf) {
+ size_t psize;
+
+ psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ err = aubuf_alloc(&rx->aubuf, psize * 1, psize * 8);
+ if (err)
+ return err;
+ }
+
+ err = auplay_alloc(&rx->auplay, a->cfg.play_mod,
+ &prm, rx->device,
+ auplay_write_handler, rx);
+ if (err) {
+ warning("audio: start_player failed (%s.%s): %m\n",
+ a->cfg.play_mod, rx->device, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+static int start_source(struct autx *tx, struct audio *a)
+{
+ const struct aucodec *ac = tx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = ac->ch;
+
+ if (a->cfg.srate_src && a->cfg.srate_src != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_src;
+ }
+ if (a->cfg.channels_src && a->cfg.channels_src != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_src;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !tx->sampv_rs) {
+
+ info("audio: enable ausrc resampler:"
+ " %uHz/%uch <-- %uHz/%uch\n",
+ get_srate(ac), ac->ch, srate_dsp, channels_dsp);
+
+ tx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!tx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&tx->resamp,
+ srate_dsp, channels_dsp,
+ get_srate(ac), ac->ch);
+ if (err) {
+ warning("audio: could not setup ausrc resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Source */
+ if (!tx->ausrc && ausrc_find(NULL)) {
+
+ struct ausrc_prm prm;
+
+ prm.fmt = AUFMT_S16LE;
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = tx->ptime;
+
+ tx->psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ if (!tx->aubuf) {
+ err = aubuf_alloc(&tx->aubuf, tx->psize * 2,
+ tx->psize * 30);
+ if (err)
+ return err;
+ }
+
+ err = ausrc_alloc(&tx->ausrc, NULL, a->cfg.src_mod,
+ &prm, tx->device,
+ ausrc_read_handler, ausrc_error_handler, a);
+ if (err) {
+ warning("audio: start_source failed: %m\n", err);
+ return err;
+ }
+
+ switch (a->cfg.txmode) {
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ case AUDIO_MODE_THREAD_REALTIME:
+ if (!tx->u.thr.run) {
+ tx->u.thr.run = true;
+ err = pthread_create(&tx->u.thr.tid, NULL,
+ tx_thread, a);
+ if (err) {
+ tx->u.thr.tid = false;
+ return err;
+ }
+ }
+ break;
+#endif
+
+ case AUDIO_MODE_TMR:
+ tmr_start(&tx->u.tmr, 1, timeout_tx, a);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Start the audio playback and recording
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_start(struct audio *a)
+{
+ int err;
+
+ if (!a)
+ return EINVAL;
+
+ /* Audio filter */
+ if (!list_isempty(aufilt_list())) {
+ err = aufilt_setup(a);
+ if (err)
+ return err;
+ }
+
+ /* configurable order of play/src start */
+ if (a->cfg.src_first) {
+ err = start_source(&a->tx, a);
+ err |= start_player(&a->rx, a);
+ }
+ else {
+ err = start_player(&a->rx, a);
+ err |= start_source(&a->tx, a);
+ }
+ if (err)
+ return err;
+
+ if (a->tx.ac && a->rx.ac) {
+
+ if (!a->started) {
+ info("%H%H",
+ autx_print_pipeline, &a->tx,
+ aurx_print_pipeline, &a->rx);
+ }
+
+ a->started = true;
+ }
+
+ return err;
+}
+
+
+/**
+ * Stop the audio playback and recording
+ *
+ * @param a Audio object
+ */
+void audio_stop(struct audio *a)
+{
+ if (!a)
+ return;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+}
+
+
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params)
+{
+ struct autx *tx;
+ int err = 0;
+ bool reset;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ tx = &a->tx;
+
+ reset = !aucodec_equal(ac, tx->ac);
+
+ if (ac != tx->ac) {
+ info("audio: Set audio encoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), ac->ch);
+
+ /* Audio source must be stopped first */
+ if (reset) {
+ tx->ausrc = mem_deref(tx->ausrc);
+ }
+
+ tx->is_g722 = (0 == str_casecmp(ac->name, "G722"));
+ tx->enc = mem_deref(tx->enc);
+ tx->ac = ac;
+ }
+
+ if (ac->encupdh) {
+ struct auenc_param prm;
+
+ prm.ptime = tx->ptime;
+
+ err = ac->encupdh(&tx->enc, ac, &prm, params);
+ if (err) {
+ warning("audio: alloc encoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, get_srate(ac), get_srate(ac));
+ stream_update_encoder(a->strm, pt_tx);
+
+ if (!tx->ausrc) {
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params)
+{
+ struct aurx *rx;
+ bool reset = false;
+ int err = 0;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ rx = &a->rx;
+
+ reset = !aucodec_equal(ac, rx->ac);
+
+ if (ac != rx->ac) {
+
+ info("audio: Set audio decoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), ac->ch);
+
+ rx->pt = pt_rx;
+ rx->ac = ac;
+ rx->dec = mem_deref(rx->dec);
+ }
+
+ if (ac->decupdh) {
+ err = ac->decupdh(&rx->dec, ac, params);
+ if (err) {
+ warning("audio: alloc decoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, get_srate(ac), get_srate(ac));
+
+ if (reset) {
+
+ rx->auplay = mem_deref(rx->auplay);
+
+ /* Reset audio filter chain */
+ list_flush(&rx->filtl);
+
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next audio encoder in the local list of negotiated codecs
+ *
+ * @param audio Audio object
+ */
+void audio_encoder_cycle(struct audio *audio)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!audio)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(audio_strm(audio)));
+ if (!rc) {
+ info("audio: encoder cycle: no remote codec found\n");
+ return;
+ }
+
+ (void)audio_encoder_set(audio, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *audio_strm(const struct audio *a)
+{
+ return a ? a->strm : NULL;
+}
+
+
+int audio_send_digit(struct audio *a, char key)
+{
+ int err = 0;
+
+ if (!a)
+ return EINVAL;
+
+ if (key > 0) {
+ info("audio: send DTMF digit: '%c'\n", key);
+ err = telev_send(a->telev, telev_digit2code(key), false);
+ }
+ else if (a->tx.cur_key) {
+ /* Key release */
+ info("audio: send DTMF digit end: '%c'\n", a->tx.cur_key);
+ err = telev_send(a->telev,
+ telev_digit2code(a->tx.cur_key), true);
+ }
+
+ a->tx.cur_key = key;
+
+ return err;
+}
+
+
+/**
+ * Mute the audio stream
+ *
+ * @param a Audio stream
+ * @param muted True to mute, false to un-mute
+ */
+void audio_mute(struct audio *a, bool muted)
+{
+ if (!a)
+ return;
+
+ a->tx.muted = muted;
+}
+
+
+void audio_sdp_attr_decode(struct audio *a)
+{
+ const char *attr;
+
+ if (!a)
+ return;
+
+ /* This is probably only meaningful for audio data, but
+ may be used with other media types if it makes sense. */
+ attr = sdp_media_rattr(stream_sdpmedia(a->strm), "ptime");
+ if (attr) {
+ struct autx *tx = &a->tx;
+ uint32_t ptime_tx = atoi(attr);
+
+ if (ptime_tx && ptime_tx != a->tx.ptime) {
+
+ info("audio: peer changed ptime_tx %u -> %u\n",
+ a->tx.ptime, ptime_tx);
+
+ tx->ptime = ptime_tx;
+
+ if (tx->ac) {
+ tx->psize = 2 * get_framesize(tx->ac,
+ ptime_tx);
+ }
+ }
+ }
+}
+
+
+static int aucodec_print(struct re_printf *pf, const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return re_hprintf(pf, "%s %uHz/%dch", ac->name, get_srate(ac), ac->ch);
+}
+
+
+int audio_debug(struct re_printf *pf, const struct audio *a)
+{
+ const struct autx *tx;
+ const struct aurx *rx;
+ int err;
+
+ if (!a)
+ return 0;
+
+ tx = &a->tx;
+ rx = &a->rx;
+
+ err = re_hprintf(pf, "\n--- Audio stream ---\n");
+
+ err |= re_hprintf(pf, " tx: %H %H ptime=%ums\n",
+ aucodec_print, tx->ac,
+ aubuf_debug, tx->aubuf,
+ tx->ptime);
+
+ err |= re_hprintf(pf, " rx: %H %H ptime=%ums pt=%d pt_tel=%d\n",
+ aucodec_print, rx->ac,
+ aubuf_debug, rx->aubuf,
+ rx->ptime, rx->pt, rx->pt_tel);
+
+ err |= re_hprintf(pf,
+ " %H"
+ " %H",
+ autx_print_pipeline, tx,
+ aurx_print_pipeline, rx);
+
+ err |= stream_debug(pf, a->strm);
+
+ return err;
+}
+
+
+void audio_set_devicename(struct audio *a, const char *src, const char *play)
+{
+ if (!a)
+ return;
+
+ str_ncpy(a->tx.device, src, sizeof(a->tx.device));
+ str_ncpy(a->rx.device, play, sizeof(a->rx.device));
+}