summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.c574
-rw-r--r--src/aucodec.c76
-rw-r--r--src/audio.c1369
-rw-r--r--src/aufilt.c37
-rw-r--r--src/auplay.c108
-rw-r--r--src/ausrc.c106
-rw-r--r--src/bfcp.c199
-rw-r--r--src/call.c1567
-rw-r--r--src/cmd.c351
-rw-r--r--src/conf.c380
-rw-r--r--src/config.c706
-rw-r--r--src/contact.c161
-rw-r--r--src/core.h389
-rw-r--r--src/log.c139
-rw-r--r--src/magic.h27
-rw-r--r--src/main.c154
-rw-r--r--src/mctrl.c44
-rw-r--r--src/menc.c63
-rw-r--r--src/message.c138
-rw-r--r--src/metric.c79
-rw-r--r--src/mnat.c87
-rw-r--r--src/module.c156
-rw-r--r--src/net.c439
-rw-r--r--src/play.c317
-rw-r--r--src/realtime.c100
-rw-r--r--src/reg.c269
-rw-r--r--src/rtpkeep.c165
-rw-r--r--src/sdp.c186
-rw-r--r--src/sipreq.c149
-rw-r--r--src/srcs.mk49
-rw-r--r--src/stream.c582
-rw-r--r--src/ua.c1562
-rw-r--r--src/ui.c185
-rw-r--r--src/vidcodec.c81
-rw-r--r--src/video.c1073
-rw-r--r--src/vidfilt.c116
-rw-r--r--src/vidisp.c132
-rw-r--r--src/vidsrc.c134
38 files changed, 12449 insertions, 0 deletions
diff --git a/src/account.c b/src/account.c
new file mode 100644
index 0000000..95ceeba
--- /dev/null
+++ b/src/account.c
@@ -0,0 +1,574 @@
+/**
+ * @file src/account.c User-Agent account
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ REG_INTERVAL = 3600,
+};
+
+
+static void destructor(void *arg)
+{
+ struct account *acc = arg;
+ size_t i;
+
+ list_clear(&acc->aucodecl);
+ list_clear(&acc->vidcodecl);
+ mem_deref(acc->auth_user);
+ mem_deref(acc->auth_pass);
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++)
+ mem_deref(acc->outbound[i]);
+ mem_deref(acc->regq);
+ mem_deref(acc->rtpkeep);
+ mem_deref(acc->sipnat);
+ mem_deref(acc->stun_user);
+ mem_deref(acc->stun_pass);
+ mem_deref(acc->stun_host);
+ mem_deref(acc->mnatid);
+ mem_deref(acc->mencid);
+ mem_deref(acc->aor);
+ mem_deref(acc->dispname);
+ mem_deref(acc->buf);
+}
+
+
+static int param_dstr(char **dstr, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (sip_param_decode(params, name, &pl))
+ return 0;
+
+ return pl_strdup(dstr, &pl);
+}
+
+
+static int param_u32(uint32_t *v, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (sip_param_decode(params, name, &pl))
+ return 0;
+
+ *v = pl_u32(&pl);
+
+ return 0;
+}
+
+
+static int stunsrv_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl srv;
+ struct uri uri;
+ int err;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ memset(&uri, 0, sizeof(uri));
+
+ if (0 == sip_param_decode(&aor->params, "stunserver", &srv)) {
+
+ info("using stunserver: '%r'\n", &srv);
+
+ err = uri_decode(&uri, &srv);
+ if (err) {
+ warning("account: %r: decode failed: %m\n", &srv, err);
+ memset(&uri, 0, sizeof(uri));
+ }
+
+ if (0 != pl_strcasecmp(&uri.scheme, "stun")) {
+ warning("account: unknown scheme: %r\n", &uri.scheme);
+ return EINVAL;
+ }
+ }
+
+ err = 0;
+ if (pl_isset(&uri.user))
+ err |= pl_strdup(&acc->stun_user, &uri.user);
+ else
+ err |= pl_strdup(&acc->stun_user, &aor->uri.user);
+
+ if (pl_isset(&uri.password))
+ err |= pl_strdup(&acc->stun_pass, &uri.password);
+ else
+ err |= pl_strdup(&acc->stun_pass, &aor->uri.password);
+
+ if (pl_isset(&uri.host))
+ err |= pl_strdup(&acc->stun_host, &uri.host);
+ else
+ err |= pl_strdup(&acc->stun_host, &aor->uri.host);
+
+ acc->stun_port = uri.port;
+
+ return err;
+}
+
+
+/** Decode media parameters */
+static int media_decode(struct account *acc, const struct pl *prm)
+{
+ int err = 0;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ err |= param_dstr(&acc->mencid, prm, "mediaenc");
+ err |= param_dstr(&acc->mnatid, prm, "medianat");
+ err |= param_dstr(&acc->rtpkeep, prm, "rtpkeep" );
+ err |= param_u32(&acc->ptime, prm, "ptime" );
+
+ return err;
+}
+
+
+/* Decode answermode parameter */
+static void answermode_decode(struct account *prm, const struct pl *pl)
+{
+ struct pl amode;
+
+ if (0 == sip_param_decode(pl, "answermode", &amode)) {
+
+ if (0 == pl_strcasecmp(&amode, "manual")) {
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ else if (0 == pl_strcasecmp(&amode, "early")) {
+ prm->answermode = ANSWERMODE_EARLY;
+ }
+ else if (0 == pl_strcasecmp(&amode, "auto")) {
+ prm->answermode = ANSWERMODE_AUTO;
+ }
+ else {
+ warning("account: answermode unknown (%r)\n", &amode);
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ }
+}
+
+
+static int csl_parse(struct pl *pl, char *str, size_t sz)
+{
+ struct pl ws = PL_INIT, val, ws2 = PL_INIT, cma = PL_INIT;
+ int err;
+
+ err = re_regex(pl->p, pl->l, "[ \t]*[^, \t]+[ \t]*[,]*",
+ &ws, &val, &ws2, &cma);
+ if (err)
+ return err;
+
+ pl_advance(pl, ws.l + val.l + ws2.l + cma.l);
+
+ (void)pl_strcpy(&val, str, sz);
+
+ return 0;
+}
+
+
+static int audio_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->aucodecl);
+
+ if (0 == sip_param_exists(prm, "audio_codecs", &tmp)) {
+ struct pl acs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (sip_param_decode(prm, "audio_codecs", &acs))
+ return 0;
+
+ while (0 == csl_parse(&acs, cname, sizeof(cname))) {
+ struct aucodec *ac;
+ struct pl pl_cname, pl_srate, pl_ch = PL_INIT;
+ uint32_t srate = 8000;
+ uint8_t ch = 1;
+
+ /* Format: "codec/srate/ch" */
+ if (0 == re_regex(cname, str_len(cname),
+ "[^/]+/[0-9]+[/]*[0-9]*",
+ &pl_cname, &pl_srate,
+ NULL, &pl_ch)) {
+ (void)pl_strcpy(&pl_cname, cname,
+ sizeof(cname));
+ srate = pl_u32(&pl_srate);
+ if (pl_isset(&pl_ch))
+ ch = pl_u32(&pl_ch);
+ }
+
+ ac = (struct aucodec *)aucodec_find(cname, srate, ch);
+ if (!ac) {
+ warning("account: audio codec not found:"
+ " %s/%u/%d\n",
+ cname, srate, ch);
+ continue;
+ }
+
+ /* NOTE: static list with references to aucodec */
+ list_append(&acc->aucodecl, &acc->acv[i++], ac);
+
+ if (i >= ARRAY_SIZE(acc->acv))
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef USE_VIDEO
+static int video_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->vidcodecl);
+
+ if (0 == sip_param_exists(prm, "video_codecs", &tmp)) {
+ struct pl vcs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (sip_param_decode(prm, "video_codecs", &vcs))
+ return 0;
+
+ while (0 == csl_parse(&vcs, cname, sizeof(cname))) {
+ struct vidcodec *vc;
+
+ vc = (struct vidcodec *)vidcodec_find(cname, NULL);
+ if (!vc) {
+ warning("account: video codec not found: %s\n",
+ cname);
+ continue;
+ }
+
+ /* NOTE: static list with references to vidcodec */
+ list_append(&acc->vidcodecl, &acc->vcv[i++], vc);
+
+ if (i >= ARRAY_SIZE(acc->vcv))
+ break;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+static int sip_params_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl auth_user;
+ size_t i;
+ int err = 0;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ acc->regint = REG_INTERVAL + (rand_u32()&0xff);
+ err |= param_u32(&acc->regint, &aor->params, "regint");
+
+ err |= param_dstr(&acc->regq, &aor->params, "regq");
+
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++) {
+
+ char expr[16] = "outbound";
+
+ expr[8] = i + 1 + 0x30;
+ expr[9] = '\0';
+
+ err |= param_dstr(&acc->outbound[i], &aor->params, expr);
+ }
+
+ /* backwards compat */
+ if (!acc->outbound[0]) {
+ err |= param_dstr(&acc->outbound[0], &aor->params, "outbound");
+ }
+
+ err |= param_dstr(&acc->sipnat, &aor->params, "sipnat");
+
+ if (0 == sip_param_decode(&aor->params, "auth_user", &auth_user))
+ err |= pl_strdup(&acc->auth_user, &auth_user);
+ else
+ err |= pl_strdup(&acc->auth_user, &aor->uri.user);
+
+ if (pl_isset(&aor->dname))
+ err |= pl_strdup(&acc->dispname, &aor->dname);
+
+ return err;
+}
+
+
+static int encode_uri_user(struct re_printf *pf, const struct uri *uri)
+{
+ struct uri uuri = *uri;
+
+ uuri.password = uuri.params = uuri.headers = pl_null;
+
+ return uri_encode(pf, &uuri);
+}
+
+
+static int password_prompt(struct account *acc)
+{
+ char pwd[64];
+ char *nl;
+ int err;
+
+ (void)re_printf("Please enter password for %r@%r: ",
+ &acc->luri.user, &acc->luri.host);
+
+ /* note: blocking UI call */
+ fgets(pwd, sizeof(pwd), stdin);
+ pwd[sizeof(pwd) - 1] = '\0';
+
+ nl = strchr(pwd, '\n');
+ if (nl == NULL) {
+ (void)re_printf("Invalid password (0 - 63 characters"
+ " followed by newline)\n");
+ return EINVAL;
+ }
+
+ *nl = '\0';
+
+ err = str_dup(&acc->auth_pass, pwd);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+int account_alloc(struct account **accp, const char *sipaddr)
+{
+ struct account *acc;
+ struct pl pl;
+ int err = 0;
+
+ if (!accp || !sipaddr)
+ return EINVAL;
+
+ acc = mem_zalloc(sizeof(*acc), destructor);
+ if (!acc)
+ return ENOMEM;
+
+ err = str_dup(&acc->buf, sipaddr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, acc->buf);
+ err = sip_addr_decode(&acc->laddr, &pl);
+ if (err) {
+ warning("account: invalid SIP address: `%r'\n", &pl);
+ goto out;
+ }
+
+ acc->luri = acc->laddr.uri;
+ acc->luri.password = pl_null;
+
+ err = re_sdprintf(&acc->aor, "%H", encode_uri_user, &acc->luri);
+ if (err)
+ goto out;
+
+ /* Decode parameters */
+ acc->ptime = 20;
+ err |= sip_params_decode(acc, &acc->laddr);
+ answermode_decode(acc, &acc->laddr.params);
+ err |= audio_codecs_decode(acc, &acc->laddr.params);
+#ifdef USE_VIDEO
+ err |= video_codecs_decode(acc, &acc->laddr.params);
+#endif
+ err |= media_decode(acc, &acc->laddr.params);
+ if (err)
+ goto out;
+
+ /* optional password prompt */
+ if (!pl_isset(&acc->laddr.uri.password)) {
+ err = password_prompt(acc);
+ if (err)
+ goto out;
+ }
+ else {
+ err = pl_strdup(&acc->auth_pass, &acc->laddr.uri.password);
+ if (err)
+ goto out;
+ }
+
+ if (acc->mnatid) {
+ err = stunsrv_decode(acc, &acc->laddr);
+ if (err)
+ goto out;
+
+ acc->mnat = mnat_find(acc->mnatid);
+ if (!acc->mnat) {
+ warning("account: medianat not found: %s\n",
+ acc->mnatid);
+ }
+ }
+
+ if (acc->mencid) {
+ acc->menc = menc_find(acc->mencid);
+ if (!acc->menc) {
+ warning("account: mediaenc not found: %s\n",
+ acc->mencid);
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(acc);
+ else
+ *accp = acc;
+
+ return err;
+}
+
+
+/**
+ * Sets the displayed name. Pass null in dname to disable display name
+ *
+ * @param acc User-Agent account
+ * @param dname Display name (NULL to disable)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_set_display_name(struct account *acc, const char *dname)
+{
+ if (!acc)
+ return EINVAL;
+
+ acc->dispname = mem_deref(acc->dispname);
+
+ if (dname)
+ return str_dup(&acc->dispname, dname);
+
+ return 0;
+}
+
+
+/**
+ * Authenticate a User-Agent (UA)
+ *
+ * @param acc User-Agent account
+ * @param username Pointer to allocated username string
+ * @param password Pointer to allocated password string
+ * @param realm Realm string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_auth(const struct account *acc, char **username, char **password,
+ const char *realm)
+{
+ if (!acc)
+ return EINVAL;
+
+ (void)realm;
+
+ *username = mem_ref(acc->auth_user);
+ *password = mem_ref(acc->auth_pass);
+
+ return 0;
+}
+
+
+struct list *account_aucodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->aucodecl))
+ ? (struct list *)&acc->aucodecl : aucodec_list();
+}
+
+
+#ifdef USE_VIDEO
+struct list *account_vidcodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->vidcodecl))
+ ? (struct list *)&acc->vidcodecl : vidcodec_list();
+}
+#endif
+
+
+struct sip_addr *account_laddr(const struct account *acc)
+{
+ return acc ? (struct sip_addr *)&acc->laddr : NULL;
+}
+
+
+static const char *answermode_str(enum answermode mode)
+{
+ switch (mode) {
+
+ case ANSWERMODE_MANUAL: return "manual";
+ case ANSWERMODE_EARLY: return "early";
+ case ANSWERMODE_AUTO: return "auto";
+ default: return "???";
+ }
+}
+
+
+int account_debug(struct re_printf *pf, const struct account *acc)
+{
+ struct le *le;
+ size_t i;
+ int err = 0;
+
+ if (!acc)
+ return 0;
+
+ err |= re_hprintf(pf, "\nAccount:\n");
+
+ err |= re_hprintf(pf, " address: %s\n", acc->buf);
+ err |= re_hprintf(pf, " luri: %H\n",
+ uri_encode, &acc->luri);
+ err |= re_hprintf(pf, " aor: %s\n", acc->aor);
+ err |= re_hprintf(pf, " dispname: %s\n", acc->dispname);
+ err |= re_hprintf(pf, " answermode: %s\n",
+ answermode_str(acc->answermode));
+ if (!list_isempty(&acc->aucodecl)) {
+ err |= re_hprintf(pf, " audio_codecs:");
+ for (le = list_head(&acc->aucodecl); le; le = le->next) {
+ const struct aucodec *ac = le->data;
+ err |= re_hprintf(pf, " %s/%u/%u",
+ ac->name, ac->srate, ac->ch);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+ err |= re_hprintf(pf, " auth_user: %s\n", acc->auth_user);
+ err |= re_hprintf(pf, " mediaenc: %s\n",
+ acc->mencid ? acc->mencid : "none");
+ err |= re_hprintf(pf, " medianat: %s\n",
+ acc->mnatid ? acc->mnatid : "none");
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++) {
+ if (acc->outbound[i]) {
+ err |= re_hprintf(pf, " outbound%d: %s\n",
+ i+1, acc->outbound[i]);
+ }
+ }
+ err |= re_hprintf(pf, " ptime: %u\n", acc->ptime);
+ err |= re_hprintf(pf, " regint: %u\n", acc->regint);
+ err |= re_hprintf(pf, " regq: %s\n", acc->regq);
+ err |= re_hprintf(pf, " rtpkeep: %s\n", acc->rtpkeep);
+ err |= re_hprintf(pf, " sipnat: %s\n", acc->sipnat);
+ err |= re_hprintf(pf, " stunserver: stun:%s@%s:%u\n",
+ acc->stun_user, acc->stun_host, acc->stun_port);
+ if (!list_isempty(&acc->vidcodecl)) {
+ err |= re_hprintf(pf, " video_codecs:");
+ for (le = list_head(&acc->vidcodecl); le; le = le->next) {
+ const struct vidcodec *vc = le->data;
+ err |= re_hprintf(pf, " %s", vc->name);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+
+ return err;
+}
diff --git a/src/aucodec.c b/src/aucodec.c
new file mode 100644
index 0000000..67e3256
--- /dev/null
+++ b/src/aucodec.c
@@ -0,0 +1,76 @@
+/**
+ * @file aucodec.c Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list aucodecl;
+
+
+/**
+ * Register an Audio Codec
+ *
+ * @param ac Audio Codec object
+ */
+void aucodec_register(struct aucodec *ac)
+{
+ if (!ac)
+ return;
+
+ list_append(&aucodecl, &ac->le, ac);
+
+ info("aucodec: %s/%u/%u\n", ac->name, ac->srate, ac->ch);
+}
+
+
+/**
+ * Unregister an Audio Codec
+ *
+ * @param ac Audio Codec object
+ */
+void aucodec_unregister(struct aucodec *ac)
+{
+ if (!ac)
+ return;
+
+ list_unlink(&ac->le);
+}
+
+
+const struct aucodec *aucodec_find(const char *name, uint32_t srate,
+ uint8_t ch)
+{
+ struct le *le;
+
+ for (le=aucodecl.head; le; le=le->next) {
+
+ struct aucodec *ac = le->data;
+
+ if (name && 0 != str_casecmp(name, ac->name))
+ continue;
+
+ if (srate && srate != ac->srate)
+ continue;
+
+ if (ch && ch != ac->ch)
+ continue;
+
+ return ac;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the list of Audio Codecs
+ */
+struct list *aucodec_list(void)
+{
+ return &aucodecl;
+}
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));
+}
diff --git a/src/aufilt.c b/src/aufilt.c
new file mode 100644
index 0000000..c749aaf
--- /dev/null
+++ b/src/aufilt.c
@@ -0,0 +1,37 @@
+/**
+ * @file aufilt.c Audio Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list afl;
+
+
+void aufilt_register(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_append(&afl, &af->le, af);
+
+ info("aufilt: %s\n", af->name);
+}
+
+
+void aufilt_unregister(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_unlink(&af->le);
+}
+
+
+struct list *aufilt_list(void)
+{
+ return &afl;
+}
diff --git a/src/auplay.c b/src/auplay.c
new file mode 100644
index 0000000..a1247b6
--- /dev/null
+++ b/src/auplay.c
@@ -0,0 +1,108 @@
+/**
+ * @file auplay.c Audio Player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list auplayl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct auplay *ap = arg;
+
+ list_unlink(&ap->le);
+}
+
+
+/**
+ * Register an Audio Player
+ *
+ * @param app Pointer to allocated Audio Player object
+ * @param name Audio Player name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_register(struct auplay **app, const char *name,
+ auplay_alloc_h *alloch)
+{
+ struct auplay *ap;
+
+ if (!app)
+ return EINVAL;
+
+ ap = mem_zalloc(sizeof(*ap), destructor);
+ if (!ap)
+ return ENOMEM;
+
+ list_append(&auplayl, &ap->le, ap);
+
+ ap->name = name;
+ ap->alloch = alloch;
+
+ info("auplay: %s\n", name);
+
+ *app = ap;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Player by name
+ *
+ * @param name Name of the Audio Player to find
+ *
+ * @return Matching Audio Player if found, otherwise NULL
+ */
+const struct auplay *auplay_find(const char *name)
+{
+ struct le *le;
+
+ for (le=auplayl.head; le; le=le->next) {
+
+ struct auplay *ap = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, ap->name))
+ continue;
+
+ return ap;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Player state
+ *
+ * @param stp Pointer to allocated Audio Player state
+ * @param name Name of Audio Player
+ * @param prm Audio Player parameters
+ * @param device Name of Audio Player device (driver specific)
+ * @param wh Write handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_alloc(struct auplay_st **stp, const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay *ap;
+
+ ap = (struct auplay *)auplay_find(name);
+ if (!ap)
+ return ENOENT;
+
+ if (!prm->srate || !prm->ch)
+ return EINVAL;
+
+ return ap->alloch(stp, ap, prm, device, wh, arg);
+}
diff --git a/src/ausrc.c b/src/ausrc.c
new file mode 100644
index 0000000..9782db3
--- /dev/null
+++ b/src/ausrc.c
@@ -0,0 +1,106 @@
+/**
+ * @file ausrc.c Audio Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list ausrcl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct ausrc *as = arg;
+
+ list_unlink(&as->le);
+}
+
+
+/**
+ * Register an Audio Source
+ *
+ * @param asp Pointer to allocated Audio Source object
+ * @param name Audio Source name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_register(struct ausrc **asp, const char *name, ausrc_alloc_h *alloch)
+{
+ struct ausrc *as;
+
+ if (!asp)
+ return EINVAL;
+
+ as = mem_zalloc(sizeof(*as), destructor);
+ if (!as)
+ return ENOMEM;
+
+ list_append(&ausrcl, &as->le, as);
+
+ as->name = name;
+ as->alloch = alloch;
+
+ info("ausrc: %s\n", name);
+
+ *asp = as;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Source by name
+ *
+ * @param name Name of the Audio Source to find
+ *
+ * @return Matching Audio Source if found, otherwise NULL
+ */
+const struct ausrc *ausrc_find(const char *name)
+{
+ struct le *le;
+
+ for (le=ausrcl.head; le; le=le->next) {
+
+ struct ausrc *as = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, as->name))
+ continue;
+
+ return as;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Source state
+ *
+ * @param stp Pointer to allocated Audio Source state
+ * @param ctx Media context (optional)
+ * @param name Name of Audio Source
+ * @param prm Audio Source parameters
+ * @param device Name of Audio Source device (driver specific)
+ * @param rh Read handler
+ * @param errh Error handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_alloc(struct ausrc_st **stp, struct media_ctx **ctx,
+ const char *name, struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc *as;
+
+ as = (struct ausrc *)ausrc_find(name);
+ if (!as)
+ return ENOENT;
+
+ return as->alloch(stp, as, ctx, prm, device, rh, errh, arg);
+}
diff --git a/src/bfcp.c b/src/bfcp.c
new file mode 100644
index 0000000..5b69142
--- /dev/null
+++ b/src/bfcp.c
@@ -0,0 +1,199 @@
+/**
+ * @file bfcp.c BFCP client
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct bfcp {
+ struct bfcp_conn *conn;
+ struct sdp_media *sdpm;
+ struct mnat_media *mnat_st;
+ bool active;
+
+ /* server */
+ uint32_t lconfid;
+ uint16_t luserid;
+};
+
+
+static void destructor(void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ mem_deref(bfcp->mnat_st);
+ mem_deref(bfcp->sdpm);
+ mem_deref(bfcp->conn);
+}
+
+
+static const char *bfcp_sdp_transp(enum bfcp_transp tp)
+{
+ switch (tp) {
+
+ case BFCP_UDP: return "UDP/BFCP";
+ case BFCP_DTLS: return "UDP/TLS/BFCP";
+ default: return NULL;
+ }
+}
+
+
+static enum bfcp_transp str2tp(const char *proto)
+{
+ if (0 == str_casecmp(proto, "udp"))
+ return BFCP_UDP;
+ else if (0 == str_casecmp(proto, "dtls"))
+ return BFCP_DTLS;
+ else {
+ warning("unsupported BFCP protocol: %s\n", proto);
+ return -1;
+ }
+}
+
+
+static void bfcp_resp_handler(int err, const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+ (void)bfcp;
+
+ if (err) {
+ warning("bfcp: error response: %m\n", err);
+ return;
+ }
+
+ info("bfcp: received BFCP response: '%s'\n",
+ bfcp_prim_name(msg->prim));
+}
+
+
+static void bfcp_msg_handler(const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ info("bfcp: received BFCP message '%s'\n", bfcp_prim_name(msg->prim));
+
+ switch (msg->prim) {
+
+ case BFCP_HELLO:
+ (void)bfcp_reply(bfcp->conn, msg, BFCP_HELLO_ACK, 0);
+ break;
+
+ default:
+ (void)bfcp_ereply(bfcp->conn, msg, BFCP_UNKNOWN_PRIM);
+ break;
+ }
+}
+
+
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess)
+{
+ struct bfcp *bfcp;
+ struct sa laddr;
+ enum bfcp_transp transp;
+ int err;
+
+ if (!bfcpp || !sdp_sess)
+ return EINVAL;
+
+ transp = str2tp(proto);
+
+ bfcp = mem_zalloc(sizeof(*bfcp), destructor);
+ if (!bfcp)
+ return ENOMEM;
+
+ bfcp->active = offerer;
+
+ sa_init(&laddr, AF_INET);
+
+ err = bfcp_listen(&bfcp->conn, transp, &laddr, uag_tls(),
+ bfcp_msg_handler, bfcp);
+ if (err)
+ goto out;
+
+ err = sdp_media_add(&bfcp->sdpm, sdp_sess, "application",
+ sa_port(&laddr), bfcp_sdp_transp(transp));
+ if (err)
+ goto out;
+
+ err = sdp_format_add(NULL, bfcp->sdpm, false, "*", NULL,
+ 0, 0, NULL, NULL, NULL, false, NULL);
+ if (err)
+ goto out;
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "floorctrl", "c-s");
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "setup",
+ bfcp->active ? "active" : "actpass");
+
+ if (bfcp->active) {
+ err |= sdp_media_set_lattr(bfcp->sdpm, true,
+ "connection", "new");
+ }
+ else {
+ bfcp->lconfid = 1000 + (rand_u16() & 0xf);
+ bfcp->luserid = 1 + (rand_u16() & 0x7);
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "confid",
+ "%u", bfcp->lconfid);
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "userid",
+ "%u", bfcp->luserid);
+ }
+
+ if (err)
+ goto out;
+
+ if (mnat) {
+ info("bfcp: enabled medianat '%s' on UDP socket\n", mnat->id);
+
+ err = mnat->mediah(&bfcp->mnat_st, mnat_sess, IPPROTO_UDP,
+ bfcp_sock(bfcp->conn), NULL, bfcp->sdpm);
+ if (err)
+ goto out;
+ }
+
+ info("bfcp: %s BFCP agent protocol '%s' on port %d\n",
+ bfcp->active ? "Active" : "Passive",
+ proto, sa_port(&laddr));
+
+ out:
+ if (err)
+ mem_deref(bfcp);
+ else
+ *bfcpp = bfcp;
+
+ return err;
+}
+
+
+int bfcp_start(struct bfcp *bfcp)
+{
+ const struct sa *paddr;
+ uint32_t confid = 0;
+ uint16_t userid = 0;
+ int err = 0;
+
+ if (!bfcp)
+ return EINVAL;
+
+ if (!sdp_media_rport(bfcp->sdpm)) {
+ info("bfcp channel is disabled\n");
+ return 0;
+ }
+
+ if (bfcp->active) {
+
+ paddr = sdp_media_raddr(bfcp->sdpm);
+ confid = sdp_media_rattr_u32(bfcp->sdpm, "confid");
+ userid = sdp_media_rattr_u32(bfcp->sdpm, "userid");
+
+ err = bfcp_request(bfcp->conn, paddr, BFCP_VER2, BFCP_HELLO,
+ confid, userid, bfcp_resp_handler, bfcp, 0);
+ }
+
+ return err;
+}
diff --git a/src/call.c b/src/call.c
new file mode 100644
index 0000000..03f0577
--- /dev/null
+++ b/src/call.c
@@ -0,0 +1,1567 @@
+/**
+ * @file call.c Call Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "call"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+/** Magic number */
+#define MAGIC 0xca11ca11
+#include "magic.h"
+
+
+#ifndef RELEASE
+#define CALL_DEBUG 1 /**< Enable call debugging */
+#endif
+
+#define FOREACH_STREAM \
+ for (le = call->streaml.head; le; le = le->next)
+
+/** Call constants */
+enum {
+ PTIME = 20, /**< Packet time for audio */
+ LOCAL_TIMEOUT = 120, /**< Incoming call timeout in [seconds] */
+};
+
+
+/** Call States */
+enum state {
+ STATE_IDLE = 0,
+ STATE_INCOMING,
+ STATE_OUTGOING,
+ STATE_RINGING,
+ STATE_EARLY,
+ STATE_ESTABLISHED,
+ STATE_TERMINATED
+};
+
+/** SIP Call Control object */
+struct call {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< SIP User-agent */
+ struct account *acc; /**< Account (ref.) */
+ struct sipsess *sess; /**< SIP Session */
+ struct sdp_session *sdp; /**< SDP Session */
+ struct sipsub *sub; /**< Call transfer REFER subscription */
+ struct sipnot *not; /**< REFER/NOTIFY client */
+ struct list streaml; /**< List of mediastreams (struct stream) */
+ struct audio *audio; /**< Audio stream */
+#ifdef USE_VIDEO
+ struct video *video; /**< Video stream */
+ struct bfcp *bfcp; /**< BFCP Client */
+#endif
+ enum state state; /**< Call state */
+ char *local_uri; /**< Local SIP uri */
+ char *local_name; /**< Local display name */
+ char *peer_uri; /**< Peer SIP Address */
+ char *peer_name; /**< Peer display name */
+ struct tmr tmr_inv; /**< Timer for incoming calls */
+ struct tmr tmr_dtmf; /**< Timer for incoming DTMF events */
+ time_t time_start; /**< Time when call started */
+ time_t time_stop; /**< Time when call stopped */
+ bool got_offer; /**< Got SDP Offer from Peer */
+ struct mnat_sess *mnats; /**< Media NAT session */
+ bool mnat_wait; /**< Waiting for MNAT to establish */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ int af; /**< Preferred Address Family */
+ uint16_t scode; /**< Termination status code */
+ call_event_h *eh; /**< Event handler */
+ call_dtmf_h *dtmfh; /**< DTMF handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static int send_invite(struct call *call);
+
+
+static const char *state_name(enum state st)
+{
+ switch (st) {
+
+ case STATE_IDLE: return "IDLE";
+ case STATE_INCOMING: return "INCOMING";
+ case STATE_OUTGOING: return "OUTGOING";
+ case STATE_RINGING: return "RINGING";
+ case STATE_EARLY: return "EARLY";
+ case STATE_ESTABLISHED: return "ESTABLISHED";
+ case STATE_TERMINATED: return "TERMINATED";
+ default: return "???";
+ }
+}
+
+
+static void set_state(struct call *call, enum state st)
+{
+ call->state = st;
+}
+
+
+static void call_stream_start(struct call *call, bool active)
+{
+ const struct sdp_format *sc;
+ int err;
+
+ /* Audio Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+
+ if (ac) {
+ err = audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ err |= audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ if (!err) {
+ err = audio_start(call->audio);
+ }
+ if (err) {
+ DEBUG_WARNING("audio stream: %m\n", err);
+ }
+ }
+ else {
+ info("call: no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("call: audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ /* Video Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data, sc->pt,
+ sc->params);
+ err |= video_decoder_set(call->video, sc->data, sc->pt,
+ sc->rparams);
+ if (!err) {
+ err = video_start(call->video, call->peer_uri);
+ }
+ if (err) {
+ DEBUG_WARNING("video stream: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("call: video stream is disabled..\n");
+ }
+
+ if (call->bfcp) {
+ err = bfcp_start(call->bfcp);
+ if (err) {
+ DEBUG_WARNING("bfcp_start() error: %m\n", err);
+ }
+ }
+#endif
+
+ if (active) {
+ struct le *le;
+
+ tmr_cancel(&call->tmr_inv);
+ call->time_start = time(NULL);
+
+ FOREACH_STREAM {
+ stream_reset(le->data);
+ }
+ }
+}
+
+
+static void call_stream_stop(struct call *call)
+{
+ if (!call)
+ return;
+
+ call->time_stop = time(NULL);
+
+ /* Audio */
+ audio_stop(call->audio);
+
+ /* Video */
+#ifdef USE_VIDEO
+ video_stop(call->video);
+#endif
+
+ tmr_cancel(&call->tmr_inv);
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *fmt, ...)
+{
+ call_event_h *eh = call->eh;
+ void *eh_arg = call->arg;
+ char buf[256];
+ va_list ap;
+
+ if (!eh)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ eh(call, ev, buf, eh_arg);
+}
+
+
+static void invite_timeout(void *arg)
+{
+ struct call *call = arg;
+
+ info("%s: Local timeout after %u seconds\n",
+ call->peer_uri, LOCAL_TIMEOUT);
+
+ call_event_handler(call, CALL_EVENT_CLOSED, "Local timeout");
+}
+
+
+/** Called when all media streams are established */
+static void mnat_handler(int err, uint16_t scode, const char *reason,
+ void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ DEBUG_WARNING("medianat '%s' failed: %m\n",
+ call->acc->mnatid, err);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%m", err);
+ return;
+ }
+ else if (scode) {
+ DEBUG_WARNING("medianat failed: %u %s\n", scode, reason);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%u %s",
+ scode, reason);
+ return;
+ }
+
+ /* Re-INVITE */
+ if (!call->mnat_wait) {
+ info("call: medianat established -- sending Re-INVITE\n");
+ (void)call_modify(call);
+ return;
+ }
+
+ call->mnat_wait = false;
+
+ switch (call->state) {
+
+ case STATE_OUTGOING:
+ (void)send_invite(call);
+ break;
+
+ case STATE_INCOMING:
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int update_media(struct call *call)
+{
+ const struct sdp_format *sc;
+ struct le *le;
+ int err = 0;
+
+ /* media attributes */
+ audio_sdp_attr_decode(call->audio);
+
+#ifdef USE_VIDEO
+ if (call->video)
+ video_sdp_attr_decode(call->video);
+#endif
+
+ /* Update each stream */
+ FOREACH_STREAM {
+ stream_update(le->data);
+ }
+
+ if (call->acc->mnat && call->acc->mnat->updateh && call->mnats)
+ err = call->acc->mnat->updateh(call->mnats);
+
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+ if (ac) {
+ err = audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ err |= audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ }
+ else {
+ info("no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ DEBUG_WARNING("video stream: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("video stream is disabled..\n");
+ }
+#endif
+
+ return err;
+}
+
+
+static void print_summary(const struct call *call)
+{
+ uint32_t dur = call_duration(call);
+ if (!dur)
+ return;
+
+ info("%s: Call with %s terminated (duration: %H)\n",
+ call->local_uri, call->peer_uri, fmt_human_time, &dur);
+}
+
+
+static void call_destructor(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->state != STATE_IDLE)
+ print_summary(call);
+
+ call_stream_stop(call);
+ list_unlink(&call->le);
+ tmr_cancel(&call->tmr_dtmf);
+
+ mem_deref(call->sess);
+ mem_deref(call->local_uri);
+ mem_deref(call->local_name);
+ mem_deref(call->peer_uri);
+ mem_deref(call->peer_name);
+ mem_deref(call->audio);
+#ifdef USE_VIDEO
+ mem_deref(call->video);
+ mem_deref(call->bfcp);
+#endif
+ mem_deref(call->sdp);
+ mem_deref(call->mnats);
+ mem_deref(call->mencs);
+ mem_deref(call->sub);
+ mem_deref(call->not);
+ mem_deref(call->acc);
+}
+
+
+static void audio_event_handler(int key, bool end, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ info("received event: '%c' (end=%d)\n", key, end);
+
+ if (call->dtmfh)
+ call->dtmfh(call, end ? 0x00 : key, call->arg);
+}
+
+
+static void audio_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ DEBUG_WARNING("Audio error: %m (%s)\n", err, str);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+
+
+static void menc_error_handler(int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ DEBUG_WARNING("mediaenc error: %m\n", err);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed");
+}
+
+
+/**
+ * Allocate a new Call state object
+ *
+ * @param callp Pointer to allocated Call state object
+ * @param cfg Global configuration
+ * @param lst List of call objects
+ * @param local_name Local display name (optional)
+ * @param local_uri Local SIP uri
+ * @param acc Account parameters
+ * @param ua User-Agent
+ * @param prm Call parameters
+ * @param msg SIP message for incoming calls
+ * @param xcall Optional call to inherit properties from
+ * @param eh Call event handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_alloc(struct call **callp, const struct config *cfg, struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ call_event_h *eh, void *arg)
+{
+ struct call *call;
+ enum vidmode vidmode = prm ? prm->vidmode : VIDMODE_OFF;
+ bool use_video = true, got_offer = false;
+ int label = 0;
+ int err = 0;
+
+ if (!cfg || !local_uri || !acc || !ua)
+ return EINVAL;
+
+ call = mem_zalloc(sizeof(*call), call_destructor);
+ if (!call)
+ return ENOMEM;
+
+ MAGIC_INIT(call);
+
+ tmr_init(&call->tmr_inv);
+
+ call->acc = mem_ref(acc);
+ call->ua = ua;
+ call->state = STATE_IDLE;
+ call->eh = eh;
+ call->arg = arg;
+ call->af = prm ? prm->af : AF_INET;
+
+ err = str_dup(&call->local_uri, local_uri);
+ if (local_name)
+ err |= str_dup(&call->local_name, local_name);
+ if (err)
+ goto out;
+
+ /* Init SDP info */
+ err = sdp_session_alloc(&call->sdp, net_laddr_af(call->af));
+ if (err)
+ goto out;
+
+ err = sdp_session_set_lattr(call->sdp, true,
+ "tool", "baresip " BARESIP_VERSION);
+ if (err)
+ goto out;
+
+ /* Check for incoming SDP Offer */
+ if (msg && mbuf_get_left(msg->mb))
+ got_offer = true;
+
+ /* Initialise media NAT handling */
+ if (acc->mnat) {
+ err = acc->mnat->sessh(&call->mnats, net_dnsc(), call->af,
+ acc->stun_host, acc->stun_port,
+ acc->stun_user, acc->stun_pass,
+ call->sdp, !got_offer,
+ mnat_handler, call);
+ if (err) {
+ DEBUG_WARNING("mnat session: %m\n", err);
+ goto out;
+ }
+ }
+ call->mnat_wait = true;
+
+ /* Media encryption */
+ if (acc->menc) {
+ if (acc->menc->sessh) {
+ err = acc->menc->sessh(&call->mencs, call->sdp,
+ !got_offer,
+ menc_error_handler, call);
+ if (err) {
+ DEBUG_WARNING("mediaenc session: %m\n", err);
+ goto out;
+ }
+ }
+ }
+
+ /* Audio stream */
+ err = audio_alloc(&call->audio, cfg, call,
+ call->sdp, ++label,
+ acc->mnat, call->mnats, acc->menc, call->mencs,
+ acc->ptime, account_aucodecl(call->acc),
+ audio_event_handler, audio_error_handler, call);
+ if (err)
+ goto out;
+
+#ifdef USE_VIDEO
+ /* We require at least one video codec, and at least one
+ video source or video display */
+ use_video = (vidmode != VIDMODE_OFF)
+ && (list_head(account_vidcodecl(call->acc)) != NULL)
+ && (NULL != vidsrc_find(NULL) || NULL != vidisp_find(NULL));
+
+ /* Video stream */
+ if (use_video) {
+ err = video_alloc(&call->video, cfg,
+ call, call->sdp, ++label,
+ acc->mnat, call->mnats,
+ acc->menc, call->mencs,
+ "main",
+ account_vidcodecl(call->acc));
+ if (err)
+ goto out;
+ }
+
+ if (str_isset(cfg->bfcp.proto)) {
+
+ err = bfcp_alloc(&call->bfcp, call->sdp,
+ cfg->bfcp.proto, !got_offer,
+ acc->mnat, call->mnats);
+ if (err)
+ goto out;
+ }
+#else
+ (void)use_video;
+ (void)vidmode;
+#endif
+
+ /* inherit certain properties from original call */
+ if (xcall) {
+ call->not = mem_ref(xcall->not);
+ }
+
+ list_append(lst, &call->le, call);
+
+ out:
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ return err;
+}
+
+
+int call_connect(struct call *call, const struct pl *paddr)
+{
+ struct sip_addr addr;
+ int err;
+
+ if (!call || !paddr)
+ return EINVAL;
+
+ info("call: connecting to '%r'..\n", paddr);
+
+ /* if the peer-address is a full SIP address then we need
+ * to parse it and extract the SIP uri part.
+ */
+ if (0 == sip_addr_decode(&addr, paddr) && addr.dname.p) {
+ err = pl_strdup(&call->peer_uri, &addr.auri);
+ }
+ else {
+ err = pl_strdup(&call->peer_uri, paddr);
+ }
+ if (err)
+ return err;
+
+ set_state(call, STATE_OUTGOING);
+
+ /* If we are using asyncronous medianat like STUN/TURN, then
+ * wait until completed before sending the INVITE */
+ if (!call->acc->mnat)
+ err = send_invite(call);
+
+ return err;
+}
+
+
+/**
+ * Update the current call by sending Re-INVITE or UPDATE
+ *
+ * @param call Call object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_modify(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ err = call_sdp_get(call, &desc, true);
+ if (!err)
+ err = sipsess_modify(call->sess, desc);
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+int call_hangup(struct call *call, uint16_t scode, const char *reason)
+{
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ switch (call->state) {
+
+ case STATE_INCOMING:
+ if (scode < 400) {
+ scode = 486;
+ reason = "Rejected";
+ }
+ info("call: rejecting incoming call from %s (%u %s)\n",
+ call->peer_uri, scode, reason);
+ (void)sipsess_reject(call->sess, scode, reason, NULL);
+ break;
+
+ default:
+ info("call: terminate call with %s\n", call->peer_uri);
+ call->sess = mem_deref(call->sess);
+ break;
+ }
+
+ set_state(call, STATE_TERMINATED);
+
+ call_stream_stop(call);
+
+ return err;
+}
+
+
+int call_progress(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ err = call_sdp_get(call, &desc, false);
+ if (err)
+ return err;
+
+ err = sipsess_progress(call->sess, 183, "Session Progress",
+ desc, "Allow: %s\r\n", uag_allowed_methods());
+
+ if (!err)
+ call_stream_start(call, false);
+
+ mem_deref(desc);
+
+ return 0;
+}
+
+
+int call_answer(struct call *call, uint16_t scode)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ if (STATE_INCOMING != call->state) {
+ return 0;
+ }
+
+ info("answering call from %s with %u\n", call->peer_uri, scode);
+
+ if (call->got_offer) {
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ err = sdp_encode(&desc, call->sdp, !call->got_offer);
+ if (err)
+ return err;
+
+ err = sipsess_answer(call->sess, scode, "Answering", desc,
+ "Allow: %s\r\n", uag_allowed_methods());
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Check if the current call has an active audio stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_audio(const struct call *call)
+{
+ if (!call)
+ return false;
+
+ return sdp_media_has_media(stream_sdpmedia(audio_strm(call->audio)));
+}
+
+
+/**
+ * Check if the current call has an active video stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_video(const struct call *call)
+{
+ if (!call)
+ return false;
+
+#ifdef USE_VIDEO
+ return sdp_media_has_media(stream_sdpmedia(video_strm(call->video)));
+#else
+ return false;
+#endif
+}
+
+
+/**
+ * Put the current call on hold/resume
+ *
+ * @param call Call object
+ * @param hold True to hold, false to resume
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_hold(struct call *call, bool hold)
+{
+ struct le *le;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri);
+
+ FOREACH_STREAM
+ stream_hold(le->data, hold);
+
+ return call_modify(call);
+}
+
+
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer)
+{
+ return sdp_encode(descp, call->sdp, offer);
+}
+
+
+const char *call_peeruri(const struct call *call)
+{
+ return call ? call->peer_uri : NULL;
+}
+
+
+const char *call_localuri(const struct call *call)
+{
+ return call ? call->local_uri : NULL;
+}
+
+
+/**
+ * Get the name of the peer
+ *
+ * @param call Call object
+ *
+ * @return Peer name
+ */
+const char *call_peername(const struct call *call)
+{
+ return call ? call->peer_name : NULL;
+}
+
+
+int call_debug(struct re_printf *pf, const struct call *call)
+{
+ int err;
+
+ if (!call)
+ return 0;
+
+ err = re_hprintf(pf, "===== Call debug (%s) =====\n",
+ state_name(call->state));
+
+ /* SIP Session debug */
+ err |= re_hprintf(pf,
+ " local_uri: %s <%s>\n"
+ " peer_uri: %s <%s>\n"
+ " af=%s\n",
+ call->local_name, call->local_uri,
+ call->peer_name, call->peer_uri,
+ net_af2name(call->af));
+
+ /* SDP debug */
+ err |= sdp_session_debug(pf, call->sdp);
+
+ return err;
+}
+
+
+static int print_duration(struct re_printf *pf, const struct call *call)
+{
+ const uint32_t dur = call_duration(call);
+ const uint32_t sec = dur%60%60;
+ const uint32_t min = dur/60%60;
+ const uint32_t hrs = dur/60/60;
+
+ return re_hprintf(pf, "%u:%02u:%02u", hrs, min, sec);
+}
+
+
+int call_status(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ switch (call->state) {
+
+ case STATE_EARLY:
+ case STATE_ESTABLISHED:
+ break;
+ default:
+ return 0;
+ }
+
+ err = re_hprintf(pf, "\r[%H]", print_duration, call);
+
+ FOREACH_STREAM
+ err |= stream_print(pf, le->data);
+
+ err |= re_hprintf(pf, " (bit/s)");
+
+#ifdef USE_VIDEO
+ if (call->video)
+ err |= video_print(pf, call->video);
+#endif
+
+ return err;
+}
+
+
+int call_jbuf_stat(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ FOREACH_STREAM
+ err |= stream_jbuf_stat(pf, le->data);
+
+ return err;
+}
+
+
+int call_info(struct re_printf *pf, const struct call *call)
+{
+ if (!call)
+ return 0;
+
+ return re_hprintf(pf, "%H %8s %s", print_duration, call,
+ state_name(call->state), call->peer_uri);
+}
+
+
+/**
+ * Send a DTMF digit to the peer
+ *
+ * @param call Call object
+ * @param key DTMF digit to send (0x00 for key release)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_send_digit(struct call *call, char key)
+{
+ if (!call)
+ return EINVAL;
+
+ return audio_send_digit(call->audio, key);
+}
+
+
+struct ua *call_get_ua(const struct call *call)
+{
+ return call ? call->ua : NULL;
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int sipsess_offer_handler(struct mbuf **descp,
+ const struct sip_msg *msg, void *arg)
+{
+ const bool got_offer = mbuf_get_left(msg->mb);
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ info("call: got re-INVITE%s\n", got_offer ? " (SDP Offer)" : "");
+
+ if (got_offer) {
+
+ /* Decode SDP Offer */
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ /* Encode SDP Answer */
+ return sdp_encode(descp, call->sdp, !got_offer);
+}
+
+
+static int sipsess_answer_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ (void)sdp_decode_multipart(&msg->ctype, msg->mb);
+
+ err = sdp_decode(call->sdp, msg->mb, false);
+ if (err) {
+ DEBUG_WARNING("answer: sdp_decode: %m\n", err);
+ return err;
+ }
+
+ err = update_media(call);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static void sipsess_estab_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+
+ MAGIC_CHECK(call);
+
+ (void)msg;
+
+ if (call->state == STATE_ESTABLISHED)
+ return;
+
+ set_state(call, STATE_ESTABLISHED);
+
+ call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri);
+
+ call_stream_start(call, true);
+
+ /* the transferor will hangup this call */
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 200, "OK");
+ }
+}
+
+
+#ifdef USE_VIDEO
+static void call_handle_info_req(struct call *call, const struct sip_msg *req)
+{
+ struct pl body;
+ bool pfu;
+ int err;
+
+ (void)call;
+
+ pl_set_mbuf(&body, req->mb);
+
+ err = mctrl_handle_media_control(&body, &pfu);
+ if (err)
+ return;
+
+ if (pfu) {
+ video_update_picture(call->video);
+ }
+}
+#endif
+
+
+static void dtmfend_handler(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->dtmfh)
+ call->dtmfh(call, 0x00, call->arg);
+}
+
+
+static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (!pl_strcasecmp(&msg->ctype, "application/dtmf-relay")) {
+
+ struct pl body, sig, dur;
+ int err;
+
+ pl_set_mbuf(&body, msg->mb);
+
+ err = re_regex(body.p, body.l, "Signal=[0-9]+", &sig);
+ err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur);
+
+ if (err) {
+ (void)sip_reply(sip, msg, 400, "Bad Request");
+ }
+ else {
+ char s = pl_u32(&sig);
+ uint32_t duration = pl_u32(&dur);
+
+ if (s == 10) s = '*';
+ else if (s == 11) s = '#';
+ else s += '0';
+
+ info("received DTMF: '%c' (duration=%r)\n", s, &dur);
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ if (call->dtmfh) {
+ tmr_start(&call->tmr_dtmf, duration,
+ dtmfend_handler, call);
+ call->dtmfh(call, s, call->arg);
+ }
+ }
+ }
+#ifdef USE_VIDEO
+ else if (!pl_strcasecmp(&msg->ctype,
+ "application/media_control+xml")) {
+ call_handle_info_req(call, msg);
+ (void)sip_reply(sip, msg, 200, "OK");
+ }
+#endif
+ else {
+ (void)sip_reply(sip, msg, 488, "Not Acceptable Here");
+ }
+}
+
+
+static void sipnot_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (err)
+ info("call: notification closed: %m\n", err);
+ else if (msg)
+ info("call: notification closed: %u %r\n",
+ msg->scode, &msg->reason);
+
+ call->not = mem_deref(call->not);
+}
+
+
+static void sipsess_refer_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ const struct sip_hdr *hdr;
+ int err;
+
+ /* get the transfer target */
+ hdr = sip_msg_hdr(msg, SIP_HDR_REFER_TO);
+ if (!hdr) {
+ DEBUG_WARNING("bad REFER request from %r\n", &msg->from.auri);
+ (void)sip_reply(sip, msg, 400, "Missing Refer-To header");
+ return;
+ }
+
+ /* The REFER creates an implicit subscription.
+ * Reply 202 to the REFER request
+ */
+ call->not = mem_deref(call->not);
+ err = sipevent_accept(&call->not, uag_sipevent_sock(), msg,
+ sipsess_dialog(call->sess), NULL,
+ 202, "Accepted", 60, 60, 60,
+ ua_cuser(call->ua), "message/sipfrag",
+ auth_handler, call->acc, true,
+ sipnot_close_handler, call,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ DEBUG_WARNING("refer: sipevent_accept failed: %m\n", err);
+ return;
+ }
+
+ (void)call_notify_sipfrag(call, 100, "Trying");
+
+ call_event_handler(call, CALL_EVENT_TRANSFER, "%r", &hdr->val);
+}
+
+
+static void sipsess_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ char reason[128] = "";
+
+ MAGIC_CHECK(call);
+
+ if (err) {
+ info("%s: session closed: %m\n", call->peer_uri, err);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 500, "%m", err);
+ }
+ }
+ else if (msg) {
+
+ call->scode = msg->scode;
+
+ (void)re_snprintf(reason, sizeof(reason), "%u %r",
+ msg->scode, &msg->reason);
+
+ info("%s: session closed: %u %r\n",
+ call->peer_uri, msg->scode, &msg->reason);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, msg->scode,
+ "%r", &msg->reason);
+ }
+ }
+ else {
+ info("%s: session closed\n", call->peer_uri);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, reason);
+}
+
+
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg)
+{
+ bool got_offer;
+ int err;
+
+ if (!call || !msg)
+ return EINVAL;
+
+ got_offer = (mbuf_get_left(msg->mb) > 0);
+
+ err = pl_strdup(&call->peer_uri, &msg->from.auri);
+ if (err)
+ return err;
+
+ if (pl_isset(&msg->from.dname)) {
+ err = pl_strdup(&call->peer_name, &msg->from.dname);
+ if (err)
+ return err;
+ }
+
+ if (got_offer) {
+
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ call->got_offer = true;
+ }
+
+ err = sipsess_accept(&call->sess, sess_sock, msg, 180, "Ringing",
+ ua_cuser(call->ua), "application/sdp", NULL,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_estab_handler, sipsess_info_handler,
+ sipsess_refer_handler, sipsess_close_handler,
+ call, "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ DEBUG_WARNING("sipsess_accept: %m\n", err);
+ return err;
+ }
+
+ set_state(call, STATE_INCOMING);
+
+ /* New call */
+ tmr_start(&call->tmr_inv, LOCAL_TIMEOUT*1000, invite_timeout, call);
+
+ if (!call->acc->mnat)
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+
+ return err;
+}
+
+
+static void sipsess_progr_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ bool media;
+
+ MAGIC_CHECK(call);
+
+ info("call: SIP Progress: %u %r (%r)\n",
+ msg->scode, &msg->reason, &msg->ctype);
+
+ if (msg->scode <= 100)
+ return;
+
+ /* check for 18x and content-type
+ *
+ * 1. start media-stream if application/sdp
+ * 2. play local ringback tone if not
+ *
+ * we must also handle changes to/from 180 and 183,
+ * so we reset the media-stream/ringback each time.
+ */
+ if (!pl_strcasecmp(&msg->ctype, "application/sdp")
+ && mbuf_get_left(msg->mb)
+ && !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else if (!sdp_decode_multipart(&msg->ctype, msg->mb) &&
+ !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else
+ media = false;
+
+ switch (msg->scode) {
+
+ case 180:
+ set_state(call, STATE_RINGING);
+ break;
+
+ case 183:
+ set_state(call, STATE_EARLY);
+ break;
+ }
+
+ if (media)
+ call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri);
+ else
+ call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri);
+
+ call_stream_stop(call);
+
+ if (media)
+ call_stream_start(call, false);
+}
+
+
+static int send_invite(struct call *call)
+{
+ const char *routev[1];
+ struct mbuf *desc;
+ int err;
+
+ routev[0] = ua_outbound(call->ua);
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ return err;
+
+ err = sipsess_connect(&call->sess, uag_sipsess_sock(),
+ call->peer_uri,
+ call->local_name,
+ call->local_uri,
+ ua_cuser(call->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ "application/sdp", desc,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_progr_handler, sipsess_estab_handler,
+ sipsess_info_handler, sipsess_refer_handler,
+ sipsess_close_handler, call,
+ "Allow: %s\r\n%H", uag_allowed_methods(),
+ ua_print_supported, call->ua);
+ if (err) {
+ DEBUG_WARNING("sipsess_connect: %m\n", err);
+ }
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Get the current call duration in seconds
+ *
+ * @param call Call object
+ *
+ * @return Duration in seconds
+ */
+uint32_t call_duration(const struct call *call)
+{
+ if (!call || !call->time_start)
+ return 0;
+
+ return (uint32_t)(time(NULL) - call->time_start);
+}
+
+
+/**
+ * Get the audio object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Audio object
+ */
+struct audio *call_audio(const struct call *call)
+{
+ return call ? call->audio : NULL;
+}
+
+
+/**
+ * Get the video object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Video object
+ */
+struct video *call_video(const struct call *call)
+{
+#ifdef USE_VIDEO
+ return call ? call->video : NULL;
+#else
+ (void)call;
+ return NULL;
+#endif
+}
+
+
+/**
+ * Get the list of media streams for the current call
+ *
+ * @param call Call object
+ *
+ * @return List of media streams
+ */
+struct list *call_streaml(const struct call *call)
+{
+ return call ? (struct list *)&call->streaml : NULL;
+}
+
+
+int call_reset_transp(struct call *call)
+{
+ if (!call)
+ return EINVAL;
+
+ sdp_session_set_laddr(call->sdp, net_laddr_af(call->af));
+
+ return call_modify(call);
+}
+
+
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, reason);
+ (void)mbuf_printf(mb, "SIP/2.0 %u %v\n", scode, reason, &ap);
+ va_end(ap);
+
+ mb->pos = 0;
+
+ if (scode >= 200) {
+ err = sipevent_notify(call->not, mb, SIPEVENT_TERMINATED,
+ SIPEVENT_NORESOURCE, 0);
+
+ call->not = mem_deref(call->not);
+ }
+ else {
+ err = sipevent_notify(call->not, mb, SIPEVENT_ACTIVE,
+ SIPEVENT_NORESOURCE, 0);
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ struct pl scode, reason;
+ uint32_t sc;
+
+ if (re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb),
+ "SIP/2.0 [0-9]+ [^\r\n]+", &scode, &reason)) {
+ (void)sip_reply(sip, msg, 400, "Bad sipfrag");
+ return;
+ }
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ sc = pl_u32(&scode);
+
+ if (sc >= 300) {
+ DEBUG_WARNING("call transfer failed: %u %r\n", sc, &reason);
+ }
+ else if (sc >= 200) {
+ call_event_handler(call, CALL_EVENT_CLOSED, "Call transfered");
+ }
+}
+
+
+static void sipsub_close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg)
+{
+ struct call *call = arg;
+
+ (void)substate;
+
+ call->sub = mem_deref(call->sub);
+
+ if (err) {
+ info("call: subscription closed: %m\n", err);
+ }
+ else if (msg && msg->scode >= 300) {
+ info("call: transfer failed: %u %r\n",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+static int normalize_uri(char **out, const char *uri, const struct uri *luri)
+{
+ struct uri uri2;
+ struct pl pl;
+ int err;
+
+ if (!out || !uri || !luri)
+ return EINVAL;
+
+ pl_set_str(&pl, uri);
+
+ if (0 == uri_decode(&uri2, &pl)) {
+
+ err = str_dup(out, uri);
+ }
+ else {
+ uri2 = *luri;
+
+ uri2.user = pl;
+ uri2.password = pl_null;
+ uri2.params = pl_null;
+
+ err = re_sdprintf(out, "%H", uri_encode, &uri2);
+ }
+
+ return err;
+}
+
+
+/**
+ * Transfer the call to a target SIP uri
+ *
+ * @param call Call object
+ * @param uri Target SIP uri
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_transfer(struct call *call, const char *uri)
+{
+ char *nuri;
+ int err;
+
+ if (!call || !uri)
+ return EINVAL;
+
+ err = normalize_uri(&nuri, uri, &call->acc->luri);
+ if (err)
+ return err;
+
+ info("transferring call to %s\n", nuri);
+
+ call->sub = mem_deref(call->sub);
+ err = sipevent_drefer(&call->sub, uag_sipevent_sock(),
+ sipsess_dialog(call->sess), ua_cuser(call->ua),
+ auth_handler, call->acc, true,
+ sipsub_notify_handler, sipsub_close_handler,
+ call,
+ "Refer-To: %s\r\n", nuri);
+ if (err) {
+ DEBUG_WARNING("sipevent_drefer: %m\n", err);
+ }
+
+ mem_deref(nuri);
+
+ return err;
+}
+
+
+int call_af(const struct call *call)
+{
+ return call ? call->af : AF_UNSPEC;
+}
+
+
+uint16_t call_scode(const struct call *call)
+{
+ return call ? call->scode : 0;
+}
+
+
+void call_set_handlers(struct call *call, call_event_h *eh,
+ call_dtmf_h *dtmfh, void *arg)
+{
+ if (!call)
+ return;
+
+ call->eh = eh;
+ call->dtmfh = dtmfh;
+ call->arg = arg;
+}
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..7562081
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,351 @@
+/**
+ * @file src/cmd.c Command Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ REL = 0x00,
+ ESC = 0x1b,
+ DEL = 0x7f,
+};
+
+
+struct cmds {
+ struct le le;
+ const struct cmd *cmdv;
+ size_t cmdc;
+};
+
+struct cmd_ctx {
+ struct mbuf *mb;
+ const struct cmd *cmd;
+};
+
+
+static struct list cmdl; /**< List of command blocks (struct cmds) */
+
+
+static void destructor(void *arg)
+{
+ struct cmds *cmds = arg;
+
+ list_unlink(&cmds->le);
+}
+
+
+static void ctx_destructor(void *arg)
+{
+ struct cmd_ctx *ctx = arg;
+
+ mem_deref(ctx->mb);
+}
+
+
+static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd)
+{
+ struct cmd_ctx *ctx;
+
+ ctx = mem_zalloc(sizeof(*ctx), ctx_destructor);
+ if (!ctx)
+ return ENOMEM;
+
+ ctx->mb = mbuf_alloc(32);
+ if (!ctx->mb) {
+ mem_deref(ctx);
+ return ENOMEM;
+ }
+
+ ctx->cmd = cmd;
+
+ *ctxp = ctx;
+
+ return 0;
+}
+
+
+static struct cmds *cmds_find(const struct cmd *cmdv)
+{
+ struct le *le;
+
+ if (!cmdv)
+ return NULL;
+
+ for (le = cmdl.head; le; le = le->next) {
+ struct cmds *cmds = le->data;
+
+ if (cmds->cmdv == cmdv)
+ return cmds;
+ }
+
+ return NULL;
+}
+
+
+static const struct cmd *cmd_find_by_key(char key)
+{
+ struct le *le;
+
+ for (le = cmdl.tail; le; le = le->prev) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (cmd->key == key && cmd->h)
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+
+static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd)
+{
+ switch (cmd->key) {
+
+ case ' ': return "SPACE";
+ case '\n': return "ENTER";
+ case ESC: return "ESC";
+ }
+
+ buf[0] = cmd->key;
+ buf[1] = '\0';
+
+ if (cmd->flags & CMD_PRM)
+ strncat(buf, " ..", sz-1);
+
+ return buf;
+}
+
+
+static int editor_input(struct mbuf *mb, char key,
+ struct re_printf *pf, bool *del)
+{
+ int err = 0;
+
+ switch (key) {
+
+ case ESC:
+ *del = true;
+ return re_hprintf(pf, "\nCancel\n");
+
+ case REL:
+ break;
+
+ case '\n':
+ *del = true;
+ return re_hprintf(pf, "\n");
+
+ case '\b':
+ case DEL:
+ if (mb->pos > 0)
+ mb->pos = mb->end = (mb->pos - 1);
+ break;
+
+ default:
+ err = mbuf_write_u8(mb, key);
+ break;
+ }
+
+ err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end);
+
+ return err;
+}
+
+
+static int cmd_report(const struct cmd *cmd, struct re_printf *pf,
+ struct mbuf *mb, bool compl)
+{
+ struct cmd_arg arg;
+ int err;
+
+ mb->pos = 0;
+ err = mbuf_strdup(mb, &arg.prm, mb->end);
+ if (err)
+ return err;
+
+ arg.key = cmd->key;
+ arg.complete = compl;
+
+ err = cmd->h(pf, &arg);
+
+ mem_deref(arg.prm);
+
+ return err;
+}
+
+
+static int cmd_process_edit(struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf)
+{
+ struct cmd_ctx *ctx;
+ bool compl = (key == '\n'), del = false;
+ int err;
+
+ if (!ctxp)
+ return EINVAL;
+
+ ctx = *ctxp;
+
+ err = editor_input(ctx->mb, key, pf, &del);
+ if (err)
+ return err;
+
+ if (compl || ctx->cmd->flags & CMD_PROG)
+ err = cmd_report(ctx->cmd, pf, ctx->mb, compl);
+
+ if (del)
+ *ctxp = mem_deref(*ctxp);
+
+ return err;
+}
+
+
+/**
+ * Register commands
+ *
+ * @param cmdv Array of commands
+ * @param cmdc Number of commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_register(const struct cmd *cmdv, size_t cmdc)
+{
+ struct cmds *cmds;
+
+ if (!cmdv || !cmdc)
+ return EINVAL;
+
+ cmds = cmds_find(cmdv);
+ if (cmds)
+ return EALREADY;
+
+ cmds = mem_zalloc(sizeof(*cmds), destructor);
+ if (!cmds)
+ return ENOMEM;
+
+ cmds->cmdv = cmdv;
+ cmds->cmdc = cmdc;
+
+ list_append(&cmdl, &cmds->le, cmds);
+
+ return 0;
+}
+
+
+/**
+ * Unregister commands
+ *
+ * @param cmdv Array of commands
+ */
+void cmd_unregister(const struct cmd *cmdv)
+{
+ mem_deref(cmds_find(cmdv));
+}
+
+
+/**
+ * Process input characters to the command system
+ *
+ * @param ctxp Pointer to context for editor (optional)
+ * @param key Input character
+ * @param pf Print function
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process(struct cmd_ctx **ctxp, char key, struct re_printf *pf)
+{
+ const struct cmd *cmd;
+
+ /* are we in edit-mode? */
+ if (ctxp && *ctxp) {
+
+ if (key == REL)
+ return 0;
+
+ return cmd_process_edit(ctxp, key, pf);
+ }
+
+ cmd = cmd_find_by_key(key);
+ if (cmd) {
+ struct cmd_arg arg;
+
+ /* check for parameters */
+ if (cmd->flags & CMD_PRM) {
+
+ if (ctxp) {
+ int err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+ }
+
+ return cmd_process_edit(ctxp,
+ isdigit(key) ? key : 0,
+ pf);
+ }
+
+ arg.key = key;
+ arg.prm = NULL;
+ arg.complete = true;
+
+ return cmd->h(pf, &arg);
+ }
+
+ if (key == REL)
+ return 0;
+
+ return cmd_print(pf, NULL);
+}
+
+
+/**
+ * Print a list of available commands
+ *
+ * @param pf Print function
+ * @param unused Unused variable
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_print(struct re_printf *pf, void *unused)
+{
+ size_t width = 5;
+ char fmt[32], buf[8];
+ int err = 0;
+ int key;
+
+ (void)unused;
+
+ if (!pf)
+ return EINVAL;
+
+ (void)re_snprintf(fmt, sizeof(fmt), " %%-%zus %%s\n", width);
+
+ err |= re_hprintf(pf, "--- Help ---\n");
+
+ /* print in alphabetical order */
+ for (key = 1; key <= 0x80; key++) {
+
+ const struct cmd *cmd = cmd_find_by_key(key);
+ if (!cmd || !str_isset(cmd->desc))
+ continue;
+
+ err |= re_hprintf(pf, fmt, cmd_name(buf, sizeof(buf), cmd),
+ cmd->desc);
+
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/conf.c b/src/conf.c
new file mode 100644
index 0000000..cf93f1b
--- /dev/null
+++ b/src/conf.c
@@ -0,0 +1,380 @@
+/**
+ * @file conf.c Configuration utils
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <sys/stat.h>
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE ""
+#define DEBUG_LEVEL 0
+#include <re_dbg.h>
+
+
+#ifdef WIN32
+#define open _open
+#define read _read
+#define close _close
+#endif
+
+
+#if defined (WIN32) || defined (__SYMBIAN32__)
+#define DIR_SEP "\\"
+#else
+#define DIR_SEP "/"
+#endif
+
+
+static const char *conf_path = NULL;
+static struct conf *conf_obj;
+
+
+/**
+ * Check if a file exists
+ *
+ * @param path Filename
+ *
+ * @return True if exist, False if not
+ */
+bool conf_fileexist(const char *path)
+{
+ struct stat st;
+
+ if (!path)
+ return false;
+
+ if (stat(path, &st) < 0)
+ return false;
+
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+ return false;
+
+ return st.st_size > 0;
+}
+
+
+static void print_populated(const char *what, uint32_t n)
+{
+ info("Populated %u %s%s\n", n, what, 1==n ? "" : "s");
+}
+
+
+/**
+ * Parse a config file, calling handler for each line
+ *
+ * @param filename Config file
+ * @param ch Line handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_parse(const char *filename, confline_h *ch)
+{
+ struct pl pl, val;
+ struct mbuf *mb;
+ int err = 0, fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return errno;
+
+ mb = mbuf_alloc(1024);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (;;) {
+ uint8_t buf[1024];
+
+ const ssize_t n = read(fd, (void *)buf, sizeof(buf));
+ if (n < 0) {
+ err = errno;
+ break;
+ }
+ else if (n == 0)
+ break;
+
+ err |= mbuf_write_mem(mb, buf, n);
+ }
+
+ pl.p = (const char *)mb->buf;
+ pl.l = mb->end;
+
+ while (pl.p < ((const char *)mb->buf + mb->end) && !err) {
+ const char *lb = pl_strchr(&pl, '\n');
+
+ val.p = pl.p;
+ val.l = lb ? (uint32_t)(lb - pl.p) : pl.l;
+ pl_advance(&pl, val.l + 1);
+
+ if (!val.l || val.p[0] == '#')
+ continue;
+
+ err = ch(&val);
+ }
+
+ out:
+ mem_deref(mb);
+ (void)close(fd);
+
+ return err;
+}
+
+
+/**
+ * Set the path to configuration files
+ *
+ * @param path Configuration path
+ */
+void conf_path_set(const char *path)
+{
+ conf_path = path;
+}
+
+
+/**
+ * Get the path to configuration files
+ *
+ * @param path Buffer to write path
+ * @param sz Size of path buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_path_get(char *path, size_t sz)
+{
+ char buf[256];
+ int err;
+
+ /* Use explicit conf path */
+ if (conf_path) {
+ if (re_snprintf(path, sz, "%s", conf_path) < 0)
+ return ENOMEM;
+ return 0;
+ }
+
+ err = fs_gethome(buf, sizeof(buf));
+ if (err)
+ return err;
+
+ if (re_snprintf(path, sz, "%s" DIR_SEP ".baresip", buf) < 0)
+ return ENOMEM;
+
+ return 0;
+}
+
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng)
+{
+ struct pl r, min, max;
+ uint32_t v;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ err = re_regex(r.p, r.l, "[0-9]+-[0-9]+", &min, &max);
+ if (err) {
+ /* fallback to non-range numeric value */
+ err = conf_get_u32(conf, name, &v);
+ if (err) {
+ warning("conf: %s: could not parse range: (%r)\n",
+ name, &r);
+ return err;
+ }
+
+ rng->min = rng->max = v;
+
+ return err;
+ }
+
+ rng->min = pl_u32(&min);
+ rng->max = pl_u32(&max);
+
+ if (rng->min > rng->max) {
+ warning("conf: %s: invalid range (%u - %u)\n",
+ name, rng->min, rng->max);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2)
+{
+ struct pl r, pl1, pl2 = pl_null;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ err = re_regex(r.p, r.l, "[^,]+,[^]*", &pl1, &pl2);
+ if (err)
+ return err;
+
+ (void)pl_strcpy(&pl1, str1, sz1);
+ if (pl_isset(&pl2))
+ (void)pl_strcpy(&pl2, str2, sz2);
+
+ return 0;
+}
+
+
+int conf_get_vidsz(const struct conf *conf, const char *name, struct vidsz *sz)
+{
+ struct pl r, w, h;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ w.l = h.l = 0;
+ err = re_regex(r.p, r.l, "[0-9]+x[0-9]+", &w, &h);
+ if (err)
+ return err;
+
+ if (pl_isset(&w) && pl_isset(&h)) {
+ sz->w = pl_u32(&w);
+ sz->h = pl_u32(&h);
+ }
+
+ /* check resolution */
+ if (sz->w & 0x1 || sz->h & 0x1) {
+ warning("conf: %s: should be multiple of 2 (%u x %u)\n",
+ name, sz->w, sz->h);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa)
+{
+ struct pl opt;
+ int err;
+
+ if (!conf || !name || !sa)
+ return EINVAL;
+
+ err = conf_get(conf, name, &opt);
+ if (err)
+ return err;
+
+ return sa_decode(sa, opt.p, opt.l);
+}
+
+
+/**
+ * Configure the system with default settings
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_configure(void)
+{
+ char path[256], file[256];
+ int err;
+
+#if defined (WIN32) || defined (__SYMBIAN32__)
+ dbg_init(DBG_INFO, DBG_NONE);
+#endif
+
+ err = conf_path_get(path, sizeof(path));
+ if (err) {
+ warning("conf: could not get config path: %m\n", err);
+ return err;
+ }
+
+ if (re_snprintf(file, sizeof(file), "%s/config", path) < 0)
+ return ENOMEM;
+
+ if (!conf_fileexist(file)) {
+
+ (void)fs_mkdir(path, 0700);
+
+ err = config_write_template(file, conf_config());
+ if (err)
+ goto out;
+ }
+
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = config_parse_conf(conf_config(), conf_obj);
+ if (err)
+ goto out;
+
+ out:
+ conf_obj = mem_deref(conf_obj);
+ return err;
+}
+
+
+/**
+ * Load all modules from config file
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_modules(void)
+{
+ char path[256], file[256];
+ int err;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err)
+ return err;
+
+ if (re_snprintf(file, sizeof(file), "%s/config", path) < 0)
+ return ENOMEM;
+
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = module_init(conf_obj);
+ if (err) {
+ warning("conf: configure module parse error (%m)\n", err);
+ goto out;
+ }
+
+ print_populated("audio codec", list_count(aucodec_list()));
+ print_populated("audio filter", list_count(aufilt_list()));
+#ifdef USE_VIDEO
+ print_populated("video codec", list_count(vidcodec_list()));
+ print_populated("video filter", list_count(vidfilt_list()));
+#endif
+
+ out:
+ conf_obj = mem_deref(conf_obj);
+ return err;
+}
+
+
+/**
+ * Get the current configuration object
+ *
+ * @return Config object
+ *
+ * @note It is only available during init
+ */
+struct conf *conf_cur(void)
+{
+ return conf_obj;
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..dd65c52
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,706 @@
+/**
+ * @file config.c Core Configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <dirent.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#undef MOD_PRE
+#define MOD_PRE "" /**< Module prefix */
+
+
+/** Core Run-time Configuration - populated from config file */
+/** @todo: move config parsing/decoding to a module */
+static struct config core_config = {
+ /* Input */
+ {
+ "/dev/event0",
+ 5555
+ },
+
+ /** SIP User-Agent */
+ {
+ 16,
+ "",
+ "",
+ ""
+ },
+
+ /** Audio */
+ {
+ "","",
+ "","",
+ "","",
+ {8000, 48000},
+ {1, 2},
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ AUDIO_MODE_POLL,
+ },
+
+#ifdef USE_VIDEO
+ /** Video */
+ {
+ "", "",
+ "", "",
+ 352, 288,
+ 512000,
+ 25,
+ },
+#endif
+
+ /** Audio/Video Transport */
+ {
+ 0xb8,
+ {1024, 49152},
+ {0, 0},
+ true,
+ false,
+ {5, 10},
+ false
+ },
+
+ /* Network */
+ {
+ ""
+ },
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ {
+ ""
+ },
+#endif
+};
+
+
+static int range_print(struct re_printf *pf, const struct range *rng)
+{
+ if (!rng)
+ return 0;
+
+ return re_hprintf(pf, "%u-%u", rng->min, rng->max);
+}
+
+
+static int dns_server_handler(const struct pl *pl, void *arg)
+{
+ struct sa sa;
+ int err;
+
+ (void)arg;
+
+ err = sa_decode(&sa, pl->p, pl->l);
+ if (err) {
+ warning("config: dns_server: could not decode `%r'\n", pl);
+ return err;
+ }
+
+ err = net_dnssrv_add(&sa);
+ if (err) {
+ warning("config: failed to add nameserver %r: %m\n", pl, err);
+ }
+
+ return err;
+}
+
+
+int config_parse_conf(struct config *cfg, const struct conf *conf)
+{
+ struct pl pollm, as, ap;
+ enum poll_method method;
+ struct vidsz size = {0, 0};
+ uint32_t v;
+ int err = 0;
+
+ if (!cfg || !conf)
+ return EINVAL;
+
+ /* Core */
+ if (0 == conf_get(conf, "poll_method", &pollm)) {
+ if (0 == poll_method_type(&method, &pollm)) {
+ err = poll_method_set(method);
+ if (err) {
+ warning("config: poll method (%r) set: %m\n",
+ &pollm, err);
+ }
+ }
+ else {
+ warning("config: unknown poll method (%r)\n", &pollm);
+ }
+ }
+
+ /* Input */
+ (void)conf_get_str(conf, "input_device", cfg->input.device,
+ sizeof(cfg->input.device));
+ (void)conf_get_u32(conf, "input_port", &cfg->input.port);
+
+ /* SIP */
+ (void)conf_get_u32(conf, "sip_trans_bsize", &cfg->sip.trans_bsize);
+ (void)conf_get_str(conf, "sip_listen", cfg->sip.local,
+ sizeof(cfg->sip.local));
+ (void)conf_get_str(conf, "sip_certificate", cfg->sip.cert,
+ sizeof(cfg->sip.cert));
+
+ /* Audio */
+ (void)conf_get_csv(conf, "audio_player",
+ cfg->audio.play_mod,
+ sizeof(cfg->audio.play_mod),
+ cfg->audio.play_dev,
+ sizeof(cfg->audio.play_dev));
+
+ (void)conf_get_csv(conf, "audio_source",
+ cfg->audio.src_mod, sizeof(cfg->audio.src_mod),
+ cfg->audio.src_dev, sizeof(cfg->audio.src_dev));
+
+ (void)conf_get_csv(conf, "audio_alert",
+ cfg->audio.alert_mod,
+ sizeof(cfg->audio.alert_mod),
+ cfg->audio.alert_dev,
+ sizeof(cfg->audio.alert_dev));
+
+ (void)conf_get_range(conf, "audio_srate", &cfg->audio.srate);
+ (void)conf_get_range(conf, "audio_channels", &cfg->audio.channels);
+ (void)conf_get_u32(conf, "ausrc_srate", &cfg->audio.srate_src);
+ (void)conf_get_u32(conf, "auplay_srate", &cfg->audio.srate_play);
+ (void)conf_get_u32(conf, "ausrc_channels", &cfg->audio.channels_src);
+ (void)conf_get_u32(conf, "auplay_channels", &cfg->audio.channels_play);
+
+ if (0 == conf_get(conf, "audio_source", &as) &&
+ 0 == conf_get(conf, "audio_player", &ap))
+ cfg->audio.src_first = as.p < ap.p;
+
+#ifdef USE_VIDEO
+ /* Video */
+ (void)conf_get_csv(conf, "video_source",
+ cfg->video.src_mod, sizeof(cfg->video.src_mod),
+ cfg->video.src_dev, sizeof(cfg->video.src_dev));
+ (void)conf_get_csv(conf, "video_display",
+ cfg->video.disp_mod, sizeof(cfg->video.disp_mod),
+ cfg->video.disp_dev, sizeof(cfg->video.disp_dev));
+ if (0 == conf_get_vidsz(conf, "video_size", &size)) {
+ cfg->video.width = size.w;
+ cfg->video.height = size.h;
+ }
+ (void)conf_get_u32(conf, "video_bitrate", &cfg->video.bitrate);
+ (void)conf_get_u32(conf, "video_fps", &cfg->video.fps);
+#else
+ (void)size;
+#endif
+
+ /* AVT - Audio/Video Transport */
+ if (0 == conf_get_u32(conf, "rtp_tos", &v))
+ cfg->avt.rtp_tos = v;
+ (void)conf_get_range(conf, "rtp_ports", &cfg->avt.rtp_ports);
+ if (0 == conf_get_range(conf, "rtp_bandwidth",
+ &cfg->avt.rtp_bw)) {
+ cfg->avt.rtp_bw.min *= 1024;
+ cfg->avt.rtp_bw.max *= 1024;
+ }
+ (void)conf_get_bool(conf, "rtcp_enable", &cfg->avt.rtcp_enable);
+ (void)conf_get_bool(conf, "rtcp_mux", &cfg->avt.rtcp_mux);
+ (void)conf_get_range(conf, "jitter_buffer_delay",
+ &cfg->avt.jbuf_del);
+ (void)conf_get_bool(conf, "rtp_stats", &cfg->avt.rtp_stats);
+
+ if (err) {
+ warning("config: configure parse error (%m)\n", err);
+ }
+
+ /* Network */
+ (void)conf_apply(conf, "dns_server", dns_server_handler, NULL);
+ (void)conf_get_str(conf, "net_interface",
+ cfg->net.ifname, sizeof(cfg->net.ifname));
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ (void)conf_get_str(conf, "bfcp_proto", cfg->bfcp.proto,
+ sizeof(cfg->bfcp.proto));
+#endif
+
+ return err;
+}
+
+
+int config_print(struct re_printf *pf, const struct config *cfg)
+{
+ int err;
+
+ if (!cfg)
+ return 0;
+
+ err = re_hprintf(pf,
+ "\n"
+ "# Input\n"
+ "input_device\t\t%s\n"
+ "input_port\t\t%u\n"
+ "\n"
+ "# SIP\n"
+ "sip_trans_bsize\t\t%u\n"
+ "sip_listen\t\t%s\n"
+ "sip_certificate\t%s\n"
+ "\n"
+ "# Audio\n"
+ "audio_player\t\t%s,%s\n"
+ "audio_source\t\t%s,%s\n"
+ "audio_alert\t\t%s,%s\n"
+ "audio_srate\t\t%H\n"
+ "audio_channels\t\t%H\n"
+ "auplay_srate\t\t%u\n"
+ "ausrc_srate\t\t%u\n"
+ "auplay_channels\t\t%u\n"
+ "ausrc_channels\t\t%u\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# Video\n"
+ "video_source\t\t%s,%s\n"
+ "video_display\t\t%s,%s\n"
+ "video_size\t\t\"%ux%u\"\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n"
+ "\n"
+#endif
+ "# AVT\n"
+ "rtp_tos\t\t\t%u\n"
+ "rtp_ports\t\t%H\n"
+ "rtp_bandwidth\t\t%H\n"
+ "rtcp_enable\t\t%s\n"
+ "rtcp_mux\t\t%s\n"
+ "jitter_buffer_delay\t%H\n"
+ "rtp_stats\t\t%s\n"
+ "\n"
+ "# Network\n"
+ "net_interface\t\t%s\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# BFCP\n"
+ "bfcp_proto\t\t%s\n"
+ "\n"
+#endif
+ ,
+
+ cfg->input.device, cfg->input.port,
+
+ cfg->sip.trans_bsize, cfg->sip.local, cfg->sip.cert,
+
+ cfg->audio.play_mod, cfg->audio.play_dev,
+ cfg->audio.src_mod, cfg->audio.src_dev,
+ cfg->audio.alert_mod, cfg->audio.alert_dev,
+ range_print, &cfg->audio.srate,
+ range_print, &cfg->audio.channels,
+ cfg->audio.srate_play, cfg->audio.srate_src,
+ cfg->audio.channels_play, cfg->audio.channels_src,
+
+#ifdef USE_VIDEO
+ cfg->video.src_mod, cfg->video.src_dev,
+ cfg->video.disp_mod, cfg->video.disp_dev,
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps,
+#endif
+
+ cfg->avt.rtp_tos,
+ range_print, &cfg->avt.rtp_ports,
+ range_print, &cfg->avt.rtp_bw,
+ cfg->avt.rtcp_enable ? "yes" : "no",
+ cfg->avt.rtcp_mux ? "yes" : "no",
+ range_print, &cfg->avt.jbuf_del,
+ cfg->avt.rtp_stats ? "yes" : "no",
+
+ cfg->net.ifname
+
+#ifdef USE_VIDEO
+ ,cfg->bfcp.proto
+#endif
+ );
+
+ return err;
+}
+
+
+static const char *default_audio_device(void)
+{
+#ifdef DARWIN
+ return "coreaudio,nil";
+#elif defined (FREEBSD)
+ return "oss,/dev/dsp";
+#elif defined (WIN32)
+ return "winwave,nil";
+#else
+ return "alsa,default";
+#endif
+}
+
+
+#ifdef USE_VIDEO
+static const char *default_video_device(void)
+{
+#ifdef DARWIN
+ return "qtcapture,nil";
+#else
+ return "v4l2,/dev/video0";
+#endif
+}
+
+
+static const char *default_video_display(void)
+{
+#ifdef DARWIN
+ return "opengl,nil";
+#else
+ return "x11,nil";
+#endif
+}
+#endif
+
+
+static int default_interface_print(struct re_printf *pf, void *unused)
+{
+ char ifname[64];
+ (void)unused;
+
+ if (0 == net_rt_default_get(AF_INET, ifname, sizeof(ifname)))
+ return re_hprintf(pf, "%s", ifname);
+ else
+ return re_hprintf(pf, "eth0");
+}
+
+
+static int core_config_template(struct re_printf *pf, const struct config *cfg)
+{
+ int err = 0;
+
+ if (!cfg)
+ return 0;
+
+ err |= re_hprintf(pf,
+ "\n# Core\n"
+ "poll_method\t\t%s\t\t# poll, select, epoll ..\n"
+ "\n# Input\n"
+ "input_device\t\t/dev/event0\n"
+ "input_port\t\t5555\n"
+ "\n# SIP\n"
+ "sip_trans_bsize\t\t128\n"
+ "#sip_listen\t\t0.0.0.0:5060\n"
+ "#sip_certificate\tcert.pem\n"
+ "\n# Audio\n"
+ "audio_player\t\t%s\n"
+ "audio_source\t\t%s\n"
+ "audio_alert\t\t%s\n"
+ "audio_srate\t\t%u-%u\n"
+ "audio_channels\t\t%u-%u\n"
+ "#ausrc_srate\t\t48000\n"
+ "#auplay_srate\t\t48000\n"
+ "#ausrc_channels\t\t0\n"
+ "#auplay_channels\t\t0\n"
+ ,
+ poll_method_name(poll_method_best()),
+ default_audio_device(),
+ default_audio_device(),
+ default_audio_device(),
+ cfg->audio.srate.min, cfg->audio.srate.max,
+ cfg->audio.channels.min, cfg->audio.channels.max);
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# Video\n"
+ "#video_source\t\t%s\n"
+ "#video_display\t\t%s\n"
+ "video_size\t\t%dx%d\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n",
+ default_video_device(),
+ default_video_display(),
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps);
+#endif
+
+ err |= re_hprintf(pf,
+ "\n# AVT - Audio/Video Transport\n"
+ "rtp_tos\t\t\t184\n"
+ "#rtp_ports\t\t10000-20000\n"
+ "#rtp_bandwidth\t\t512-1024 # [kbit/s]\n"
+ "rtcp_enable\t\tyes\n"
+ "rtcp_mux\t\tno\n"
+ "jitter_buffer_delay\t%u-%u\t\t# frames\n"
+ "rtp_stats\t\tno\n"
+ "\n# Network\n"
+ "#dns_server\t\t10.0.0.1:53\n"
+ "#net_interface\t\t%H\n",
+ cfg->avt.jbuf_del.min, cfg->avt.jbuf_del.max,
+ default_interface_print, NULL);
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# BFCP\n"
+ "#bfcp_proto\t\tudp\n");
+#endif
+
+ return err;
+}
+
+
+static uint32_t count_modules(const char *path)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ uint32_t n = 0;
+
+ dirp = opendir(path);
+ if (!dirp)
+ return 0;
+
+ while ((dp = readdir(dirp)) != NULL) {
+
+ size_t len = strlen(dp->d_name);
+ const size_t x = sizeof(MOD_EXT)-1;
+
+ if (len <= x)
+ continue;
+
+ if (0==memcmp(&dp->d_name[len-x], MOD_EXT, x))
+ ++n;
+ }
+
+ (void)closedir(dirp);
+
+ return n;
+}
+
+
+static const char *detect_module_path(bool *valid)
+{
+ static const char * const pathv[] = {
+#if defined (PREFIX)
+ "" PREFIX "/lib/baresip/modules",
+#else
+ "/usr/local/lib/baresip/modules",
+ "/usr/lib/baresip/modules",
+#endif
+ };
+ const char *current = pathv[0];
+ uint32_t nmax = 0;
+
+ for (size_t i=0; i<ARRAY_SIZE(pathv); i++) {
+
+ uint32_t n = count_modules(pathv[i]);
+
+ info("%s: detected %u modules\n", pathv[i], n);
+
+ if (n > nmax) {
+ nmax = n;
+ current = pathv[i];
+ }
+ }
+
+ if (nmax > 0)
+ *valid = true;
+
+ return current;
+}
+
+
+int config_write_template(const char *file, const struct config *cfg)
+{
+ FILE *f = NULL;
+ int err = 0;
+ const char *modpath;
+ bool modpath_valid = false;
+
+ if (!file || !cfg)
+ return EINVAL;
+
+ info("config: creating config template %s\n", file);
+
+ f = fopen(file, "w");
+ if (!f) {
+ warning("config: writing %s: %m\n", file, errno);
+ return errno;
+ }
+
+ (void)re_fprintf(f,
+ "#\n"
+ "# baresip configuration\n"
+ "#\n"
+ "\n"
+ "#------------------------------------"
+ "------------------------------------------\n");
+
+ (void)re_fprintf(f, "%H", core_config_template, cfg);
+
+ (void)re_fprintf(f,
+ "\n#------------------------------------"
+ "------------------------------------------\n"
+ "# Modules\n"
+ "\n");
+
+ modpath = detect_module_path(&modpath_valid);
+ (void)re_fprintf(f, "%smodule_path\t\t%s\n",
+ modpath_valid ? "" : "#", modpath);
+
+ (void)re_fprintf(f, "\n# UI Modules\n");
+#if defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "wincons" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stdio" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cons" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "evdev" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "httpd" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio codec Modules (in order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "opus" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "silk" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "amr" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g7221" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g722" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g726" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "g711" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gsm" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "l16" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "bv32" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "vumeter" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sndfile" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_aec" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_pp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "plc" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio driver Modules\n");
+#if defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n");
+#elif defined (__SYMBIAN32__)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "mda" MOD_EXT "\n");
+#elif defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n");
+
+#ifdef USE_VIDEO
+
+ (void)re_fprintf(f, "\n# Video codec Modules (in order)\n");
+#ifdef USE_FFMPEG
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vpx" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "selfview" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video source modules\n");
+#if defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "qtcapture" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2" MOD_EXT "\n");
+#endif
+#ifdef USE_FFMPEG
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avformat" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11grab" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cairo" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video display modules\n");
+#ifdef DARWIN
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opengl" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sdl2" MOD_EXT "\n");
+
+#endif /* USE_VIDEO */
+
+ (void)re_fprintf(f,
+ "\n# Audio/Video source modules\n"
+ "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n"
+ "#module\t\t\t" MOD_PRE "gst" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media NAT modules\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stun" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "turn" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "ice" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "natpmp" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media encryption modules\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "dtls_srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Temporary Modules (loaded then unloaded)\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "#module_tmp\t\t" MOD_PRE "uuid" MOD_EXT "\n");
+ (void)re_fprintf(f, "module_tmp\t\t" MOD_PRE "account" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Application Modules\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "auloop"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "contact"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "menu"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mwi"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "natbd"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "presence"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "syslog"MOD_EXT"\n");
+#ifdef USE_VIDEO
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "vidloop"MOD_EXT"\n");
+#endif
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Module parameters\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n# Speex codec parameters\n");
+ (void)re_fprintf(f, "speex_quality\t\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_complexity\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_enhancement\t0 # 0-1\n");
+ (void)re_fprintf(f, "speex_vbr\t\t0 # Variable Bit Rate 0-1\n");
+ (void)re_fprintf(f, "speex_vad\t\t0 # Voice Activity Detection 0-1\n");
+ (void)re_fprintf(f, "speex_agc_level\t\t8000\n");
+
+ (void)re_fprintf(f, "\n# NAT Behavior Discovery\n");
+ (void)re_fprintf(f, "natbd_server\t\tcreytiv.com\n");
+ (void)re_fprintf(f, "natbd_interval\t\t600\t\t# in seconds\n");
+
+ (void)re_fprintf(f,
+ "\n# Selfview\n"
+ "video_selfview\t\twindow # {window,pip}\n"
+ "#selfview_size\t\t64x64\n");
+
+ (void)re_fprintf(f,
+ "\n# ICE\n"
+ "ice_turn\t\tno\n"
+ "ice_debug\t\tno\n"
+ "ice_nomination\t\tregular\t# {regular,aggressive}\n"
+ "ice_mode\t\tfull\t# {full,lite}\n");
+
+ if (f)
+ (void)fclose(f);
+
+ return err;
+}
+
+
+struct config *conf_config(void)
+{
+ return &core_config;
+}
diff --git a/src/contact.c b/src/contact.c
new file mode 100644
index 0000000..5161a1c
--- /dev/null
+++ b/src/contact.c
@@ -0,0 +1,161 @@
+/**
+ * @file src/contact.c Contacts handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+struct contact {
+ struct le le;
+ struct sip_addr addr;
+ char *buf;
+ enum presence_status status;
+};
+
+static struct list cl;
+
+
+static void destructor(void *arg)
+{
+ struct contact *c = arg;
+
+ list_unlink(&c->le);
+ mem_deref(c->buf);
+}
+
+
+/**
+ * Add a contact
+ *
+ * @param contactp Pointer to allocated contact (optional)
+ * @param addr Contact in SIP address format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_add(struct contact **contactp, const struct pl *addr)
+{
+ struct contact *c;
+ struct pl pl;
+ int err;
+
+ c = mem_zalloc(sizeof(*c), destructor);
+ if (!c)
+ return ENOMEM;
+
+ err = pl_strdup(&c->buf, addr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, c->buf);
+
+ err = sip_addr_decode(&c->addr, &pl);
+ if (err) {
+ warning("contact: decode error '%r'\n", addr);
+ goto out;
+ }
+
+ c->status = PRESENCE_UNKNOWN;
+
+ list_append(&cl, &c->le, c);
+
+ out:
+ if (err)
+ mem_deref(c);
+ else if (contactp)
+ *contactp = c;
+
+ return err;
+}
+
+
+/**
+ * Get the SIP address of a contact
+ *
+ * @param c Contact
+ *
+ * @return SIP Address
+ */
+struct sip_addr *contact_addr(const struct contact *c)
+{
+ return c ? (struct sip_addr *)&c->addr : NULL;
+}
+
+
+/**
+ * Get the contact string
+ *
+ * @param c Contact
+ *
+ * @return Contact string
+ */
+const char *contact_str(const struct contact *c)
+{
+ return c ? c->buf : NULL;
+}
+
+
+/**
+ * Get the list of contacts
+ *
+ * @return List of contacts
+ */
+struct list *contact_list(void)
+{
+ return &cl;
+}
+
+
+void contact_set_presence(struct contact *c, enum presence_status status)
+{
+ if (!c)
+ return;
+
+ if (c->status != PRESENCE_UNKNOWN && c->status != status) {
+
+ info("<%r> changed status from %s to %s\n", &c->addr.auri,
+ contact_presence_str(c->status),
+ contact_presence_str(status));
+ }
+
+ c->status = status;
+}
+
+
+const char *contact_presence_str(enum presence_status status)
+{
+ switch (status) {
+
+ default:
+ case PRESENCE_UNKNOWN: return "\x1b[32m\x1b[;m";
+ case PRESENCE_OPEN: return "\x1b[32mOnline\x1b[;m";
+ case PRESENCE_CLOSED: return "\x1b[31mOffline\x1b[;m";
+ case PRESENCE_BUSY: return "\x1b[31mBusy\x1b[;m";
+ }
+}
+
+
+int contacts_print(struct re_printf *pf, void *unused)
+{
+ struct le *le;
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n",
+ list_count(contact_list()));
+
+ for (le = list_head(contact_list()); le && !err; le = le->next) {
+ const struct contact *c = le->data;
+ const struct sip_addr *addr = &c->addr;
+
+ err = re_hprintf(pf, "%20s %r <%r>\n",
+ contact_presence_str(c->status),
+ &addr->dname, &addr->auri);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..9b92b4b
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,389 @@
+/**
+ * @file core.h Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/**
+ * RFC 3551:
+ *
+ * 0 - 95 Static payload types
+ * 96 - 127 Dynamic payload types
+ */
+enum {
+ PT_CN = 13,
+ PT_STAT_MIN = 0,
+ PT_STAT_MAX = 95,
+ PT_DYN_MIN = 96,
+ PT_DYN_MAX = 127
+};
+
+
+/** Media constants */
+enum {
+ AUDIO_BANDWIDTH = 128000 /**< Bandwidth for audio in bits/s */
+};
+
+
+/*
+ * Account
+ */
+
+
+/** Defines the answermodes */
+enum answermode {
+ ANSWERMODE_MANUAL = 0,
+ ANSWERMODE_EARLY,
+ ANSWERMODE_AUTO
+};
+
+struct account {
+ char *buf; /**< Buffer for the SIP address */
+ struct sip_addr laddr; /**< Decoded SIP address */
+ struct uri luri; /**< Decoded AOR uri */
+ char *dispname; /**< Display name */
+ char *aor; /**< Local SIP uri */
+
+ /* parameters: */
+ enum answermode answermode; /**< Answermode for incoming calls */
+ struct le acv[8]; /**< List elements for aucodecl */
+ struct list aucodecl; /**< List of preferred audio-codecs */
+ char *auth_user; /**< Authentication username */
+ char *auth_pass; /**< Authentication password */
+ char *mnatid; /**< Media NAT handling */
+ char *mencid; /**< Media encryption type */
+ const struct mnat *mnat; /**< MNAT module */
+ const struct menc *menc; /**< MENC module */
+ char *outbound[2]; /**< Optional SIP outbound proxies */
+ uint32_t ptime; /**< Configured packet time in [ms] */
+ uint32_t regint; /**< Registration interval in [seconds] */
+ char *regq; /**< Registration Q-value */
+ char *rtpkeep; /**< RTP Keepalive mechanism */
+ char *sipnat; /**< SIP Nat mechanism */
+ char *stun_user; /**< STUN Username */
+ char *stun_pass; /**< STUN Password */
+ char *stun_host; /**< STUN Hostname */
+ uint16_t stun_port; /**< STUN Port number */
+ struct le vcv[4]; /**< List elements for vidcodecl */
+ struct list vidcodecl; /**< List of preferred video-codecs */
+};
+
+
+/*
+ * Audio Player
+ */
+
+struct auplay_st {
+ struct auplay *ap;
+};
+
+struct auplay {
+ struct le le;
+ const char *name;
+ auplay_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Source
+ */
+
+struct ausrc_st {
+ struct ausrc *as;
+};
+
+struct ausrc {
+ struct le le;
+ const char *name;
+ ausrc_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Stream
+ */
+
+struct audio;
+
+typedef void (audio_event_h)(int key, bool end, void *arg);
+typedef void (audio_err_h)(int err, const char *str, void *arg);
+
+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);
+int audio_start(struct audio *a);
+void audio_stop(struct audio *a);
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params);
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params);
+struct stream *audio_strm(const struct audio *a);
+int audio_send_digit(struct audio *a, char key);
+void audio_sdp_attr_decode(struct audio *a);
+
+
+/*
+ * BFCP
+ */
+
+struct bfcp;
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess);
+int bfcp_start(struct bfcp *bfcp);
+
+
+/*
+ * Call Control
+ */
+
+struct call;
+
+/** Call parameters */
+struct call_prm {
+ enum vidmode vidmode;
+ int af;
+};
+
+int call_alloc(struct call **callp, const struct config *cfg,
+ struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ call_event_h *eh, void *arg);
+int call_connect(struct call *call, const struct pl *paddr);
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg);
+int call_hangup(struct call *call, uint16_t scode, const char *reason);
+int call_progress(struct call *call);
+int call_answer(struct call *call, uint16_t scode);
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer);
+int call_jbuf_stat(struct re_printf *pf, const struct call *call);
+int call_info(struct re_printf *pf, const struct call *call);
+int call_reset_transp(struct call *call);
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...);
+int call_af(const struct call *call);
+
+
+/*
+ * Conf
+ */
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng);
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2);
+
+
+/*
+ * Media control
+ */
+
+int mctrl_handle_media_control(struct pl *body, bool *pfu);
+
+
+/*
+ * Media NAT traversal
+ */
+
+struct mnat {
+ struct le le;
+ const char *id;
+ const char *ftag;
+ mnat_sess_h *sessh;
+ mnat_media_h *mediah;
+ mnat_update_h *updateh;
+};
+
+const struct mnat *mnat_find(const char *id);
+
+
+/*
+ * Metric
+ */
+
+struct metric {
+ /* internal stuff: */
+ struct tmr tmr;
+ uint64_t ts_start;
+ bool started;
+
+ /* counters: */
+ uint32_t n_packets;
+ uint32_t n_bytes;
+ uint32_t n_err;
+
+ /* bitrate calculation */
+ uint32_t cur_bitrate;
+ uint64_t ts_last;
+ uint32_t n_bytes_last;
+};
+
+void metric_reset(struct metric *metric);
+void metric_add_packet(struct metric *metric, size_t packetsize);
+uint32_t metric_avg_bitrate(const struct metric *metric);
+
+
+/*
+ * Module
+ */
+
+int module_init(const struct conf *conf);
+void module_app_unload(void);
+
+
+/*
+ * Network
+ */
+
+int net_reset(void);
+
+
+/*
+ * Register client
+ */
+
+struct reg;
+
+int reg_add(struct list *lst, struct ua *ua, int regid);
+int reg_register(struct reg *reg, const char *reg_uri,
+ const char *params, uint32_t regint, const char *outbound);
+void reg_unregister(struct reg *reg);
+bool reg_isok(const struct reg *reg);
+int reg_sipfd(const struct reg *reg);
+int reg_debug(struct re_printf *pf, const struct reg *reg);
+int reg_status(struct re_printf *pf, const struct reg *reg);
+
+
+/*
+ * RTP keepalive
+ */
+
+struct rtpkeep;
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp);
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts);
+
+
+/*
+ * SIP Request
+ */
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...);
+
+
+/*
+ * SDP
+ */
+
+int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb);
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m);
+
+
+/*
+ * Stream
+ */
+
+struct stream;
+struct rtp_header;
+
+enum {STREAM_PRESZ = 4+12}; /* same as RTP_HEADER_SIZE */
+
+typedef void (stream_rtp_h)(const struct rtp_header *hdr, struct mbuf *mb,
+ void *arg);
+typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg);
+
+int stream_alloc(struct stream **sp, const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg);
+struct sdp_media *stream_sdpmedia(const struct stream *s);
+int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb);
+void stream_update(struct stream *s);
+void stream_update_encoder(struct stream *s, int pt_enc);
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s);
+void stream_hold(struct stream *s, bool hold);
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx);
+void stream_send_fir(struct stream *s, bool pli);
+void stream_reset(struct stream *s);
+void stream_set_bw(struct stream *s, uint32_t bps);
+int stream_debug(struct re_printf *pf, const struct stream *s);
+int stream_print(struct re_printf *pf, const struct stream *s);
+
+
+/*
+ * User-Agent
+ */
+
+struct ua;
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...);
+void ua_printf(const struct ua *ua, const char *fmt, ...);
+
+struct tls *uag_tls(void);
+const char *uag_allowed_methods(void);
+
+
+/*
+ * Video Display
+ */
+
+struct vidisp {
+ struct le le;
+ const char *name;
+ vidisp_alloc_h *alloch;
+ vidisp_update_h *updateh;
+ vidisp_disp_h *disph;
+ vidisp_hide_h *hideh;
+};
+
+struct vidisp *vidisp_get(struct vidisp_st *st);
+
+
+/*
+ * Video Source
+ */
+
+struct vidsrc {
+ struct le le;
+ const char *name;
+ vidsrc_alloc_h *alloch;
+ vidsrc_update_h *updateh;
+};
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st);
+
+
+/*
+ * Video Stream
+ */
+
+struct video;
+
+int video_alloc(struct video **vp, 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,
+ const char *content, const struct list *vidcodecl);
+int video_start(struct video *v, const char *peer);
+void video_stop(struct video *v);
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params);
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp);
+struct stream *video_strm(const struct video *v);
+void video_update_picture(struct video *v);
+void video_sdp_attr_decode(struct video *v);
+int video_print(struct re_printf *pf, const struct video *v);
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..1ccdc32
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,139 @@
+/**
+ * @file log.c Logging
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct {
+ struct list logl;
+ bool debug;
+ bool stder;
+} lg = {
+ .logl = LIST_INIT,
+ .debug = false,
+ .stder = true
+};
+
+
+void log_register_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_append(&lg.logl, &log->le, log);
+}
+
+
+void log_unregister_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_unlink(&log->le);
+}
+
+
+void log_enable_debug(bool enable)
+{
+ lg.debug = enable;
+}
+
+
+void log_enable_stderr(bool enable)
+{
+ lg.stder = enable;
+}
+
+
+void vlog(enum log_level level, const char *fmt, va_list ap)
+{
+ char buf[4096];
+ struct le *le;
+
+ if (re_vsnprintf(buf, sizeof(buf), fmt, ap) < 0)
+ return;
+
+ if (lg.stder) {
+
+ bool color = level == WARN || level == ERROR;
+
+ if (color)
+ (void)re_fprintf(stderr, "\x1b[31m"); /* Red */
+
+ (void)re_fprintf(stderr, "%s", buf);
+
+ if (color)
+ (void)re_fprintf(stderr, "\x1b[;m");
+ }
+
+ le = lg.logl.head;
+
+ while (le) {
+
+ struct log *log = le->data;
+ le = le->next;
+
+ if (log->h)
+ log->h(level, buf);
+ }
+}
+
+
+void loglv(enum log_level level, const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((DEBUG == level) && !lg.debug)
+ return;
+
+ va_start(ap, fmt);
+ vlog(level, fmt, ap);
+ va_end(ap);
+}
+
+
+void debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!lg.debug)
+ return;
+
+ va_start(ap, fmt);
+ vlog(DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+
+void info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(INFO, fmt, ap);
+ va_end(ap);
+}
+
+
+void warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(WARN, fmt, ap);
+ va_end(ap);
+}
+
+
+void error(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(ERROR, fmt, ap);
+ va_end(ap);
+}
diff --git a/src/magic.h b/src/magic.h
new file mode 100644
index 0000000..15eacee
--- /dev/null
+++ b/src/magic.h
@@ -0,0 +1,27 @@
+/**
+ * @file magic.h Interface to magic macros
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef RELEASE
+
+#ifndef MAGIC
+#error "macro MAGIC must be defined"
+#endif
+
+/** Check magic number */
+#define MAGIC_DECL uint32_t magic;
+#define MAGIC_INIT(s) (s)->magic = MAGIC
+#define MAGIC_CHECK(s) \
+ if (MAGIC != s->magic) { \
+ warning("%s: wrong magic struct=%p (magic=0x%08x)\n", \
+ __FUNCTION__, s, s->magic); \
+ BREAKPOINT; \
+ }
+#else
+#define MAGIC_DECL
+#define MAGIC_INIT(s)
+#define MAGIC_CHECK(s) do {(void)(s);} while (0);
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..d55b349
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,154 @@
+/**
+ * @file main.c Main application code
+ *
+ * Copyright (C) 2010 - 2011 Creytiv.com
+ */
+#ifdef SOLARIS
+#define __EXTENSIONS__ 1
+#endif
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_GETOPT
+#include <getopt.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+
+
+static void signal_handler(int sig)
+{
+ static bool term = false;
+
+ if (term)
+ exit(0);
+
+ term = true;
+
+ info("terminated by signal %d\n", sig);
+
+ ua_stop_all(false);
+}
+
+
+int main(int argc, char *argv[])
+{
+ bool prefer_ipv6 = false, run_daemon = false;
+ const char *exec = NULL;
+ int err;
+
+ (void)re_fprintf(stderr, "baresip v%s"
+ " Copyright (C) 2010 - 2014"
+ " Alfred E. Heggestad et al.\n",
+ BARESIP_VERSION);
+
+ (void)sys_coredump_set(true);
+
+#ifdef HAVE_GETOPT
+ for (;;) {
+ const int c = getopt(argc, argv, "6de:f:hv");
+ if (0 > c)
+ break;
+
+ switch (c) {
+
+ case '?':
+ case 'h':
+ (void)re_fprintf(stderr,
+ "Usage: baresip [options]\n"
+ "options:\n"
+#if HAVE_INET6
+ "\t-6 Prefer IPv6\n"
+#endif
+ "\t-d Daemon\n"
+ "\t-e <commands> Exec commands\n"
+ "\t-f <path> Config path\n"
+ "\t-h -? Help\n"
+ "\t-v Verbose debug\n"
+ );
+ return -2;
+
+#if HAVE_INET6
+ case '6':
+ prefer_ipv6 = true;
+ break;
+#endif
+
+ case 'd':
+ run_daemon = true;
+ break;
+
+ case 'e':
+ exec = optarg;
+ break;
+
+ case 'f':
+ conf_path_set(optarg);
+ break;
+
+ case 'v':
+ log_enable_debug(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ err = libre_init();
+ if (err)
+ goto out;
+
+ err = conf_configure();
+ if (err) {
+ warning("main: configure failed: %m\n", err);
+ goto out;
+ }
+
+ /* Initialise User Agents */
+ err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")",
+ true, true, true, prefer_ipv6);
+ if (err)
+ goto out;
+
+ /* Load modules */
+ err = conf_modules();
+ if (err)
+ goto out;
+
+ if (run_daemon) {
+ err = sys_daemon();
+ if (err)
+ goto out;
+
+ log_enable_stderr(false);
+ }
+
+ info("baresip is ready.\n");
+
+ if (exec)
+ ui_input_str(exec);
+
+ /* Main loop */
+ err = re_main(signal_handler);
+
+ out:
+ if (err)
+ ua_stop_all(true);
+
+ ua_close();
+ mod_close();
+
+ libre_close();
+
+ /* Check for memory leaks */
+ tmr_debug();
+ mem_debug();
+
+ return err;
+}
diff --git a/src/mctrl.c b/src/mctrl.c
new file mode 100644
index 0000000..d3abab8
--- /dev/null
+++ b/src/mctrl.c
@@ -0,0 +1,44 @@
+/**
+ * @file mctrl.c Media Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * RFC 5168 XML Schema for Media Control
+ * note: deprecated, use RTCP FIR instead
+ *
+ *
+ * Example XML Document:
+ *
+ * <pre>
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <media_control>
+ <vc_primitive>
+ <to_encoder>
+ <picture_fast_update>
+ </picture_fast_update>
+ </to_encoder>
+ </vc_primitive>
+ </media_control>
+
+ </pre>
+ */
+int mctrl_handle_media_control(struct pl *body, bool *pfu)
+{
+ if (!body)
+ return EINVAL;
+
+ /* XXX: Poor-mans XML parsing (use xml-parser instead) */
+ if (0 == re_regex(body->p, body->l, "picture_fast_update")) {
+ if (pfu)
+ *pfu = true;
+ }
+
+ return 0;
+}
diff --git a/src/menc.c b/src/menc.c
new file mode 100644
index 0000000..a99a94a
--- /dev/null
+++ b/src/menc.c
@@ -0,0 +1,63 @@
+/**
+ * @file menc.c Media encryption
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list mencl = LIST_INIT;
+
+
+/**
+ * Register a new Media encryption module
+ *
+ * @param menc Media encryption module
+ */
+void menc_register(struct menc *menc)
+{
+ if (!menc)
+ return;
+
+ list_append(&mencl, &menc->le, menc);
+
+ info("mediaenc: %s\n", menc->id);
+}
+
+
+/**
+ * Unregister a Media encryption module
+ *
+ * @param menc Media encryption module
+ */
+void menc_unregister(struct menc *menc)
+{
+ if (!menc)
+ return;
+
+ list_unlink(&menc->le);
+}
+
+
+/**
+ * Find a Media Encryption module by name
+ *
+ * @param id Name of the Media Encryption module to find
+ *
+ * @return Matching Media Encryption module if found, otherwise NULL
+ */
+const struct menc *menc_find(const char *id)
+{
+ struct le *le;
+
+ for (le = mencl.head; le; le = le->next) {
+ struct menc *me = le->data;
+
+ if (0 == str_casecmp(id, me->id))
+ return me;
+ }
+
+ return NULL;
+}
diff --git a/src/message.c b/src/message.c
new file mode 100644
index 0000000..b0e33e1
--- /dev/null
+++ b/src/message.c
@@ -0,0 +1,138 @@
+/**
+ * @file message.c SIP MESSAGE -- RFC 3428
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct sip_lsnr *lsnr;
+static message_recv_h *recvh;
+static void *recvarg;
+
+
+static void handle_message(struct ua *ua, const struct sip_msg *msg)
+{
+ static const char *ctype_text = "text/plain";
+ struct pl mtype;
+ (void)ua;
+
+ if (re_regex(msg->ctype.p, msg->ctype.l, "[^;]+", &mtype))
+ mtype = msg->ctype;
+
+ if (0==pl_strcasecmp(&mtype, ctype_text) && recvh) {
+ recvh(&msg->from.auri, &msg->ctype, msg->mb, recvarg);
+ (void)sip_reply(uag_sip(), msg, 200, "OK");
+ }
+ else {
+ (void)sip_replyf(uag_sip(), msg, 415, "Unsupported Media Type",
+ "Accept: %s\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ ctype_text);
+ }
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ if (pl_strcmp(&msg->met, "MESSAGE"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ handle_message(ua, msg);
+
+ return true;
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+
+ (void)ua;
+
+ if (err) {
+ (void)re_fprintf(stderr, " \x1b[31m%m\x1b[;m\n", err);
+ return;
+ }
+
+ if (msg->scode >= 300) {
+ (void)re_fprintf(stderr, " \x1b[31m%u %r\x1b[;m\n",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+int message_init(message_recv_h *h, void *arg)
+{
+ int err;
+
+ err = sip_listen(&lsnr, uag_sip(), true, request_handler, NULL);
+ if (err)
+ return err;
+
+ recvh = h;
+ recvarg = arg;
+
+ return 0;
+}
+
+
+void message_close(void)
+{
+ lsnr = mem_deref(lsnr);
+}
+
+
+/**
+ * Send SIP instant MESSAGE to a peer
+ *
+ * @param ua User-Agent object
+ * @param peer Peer SIP Address
+ * @param msg Message to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int message_send(struct ua *ua, const char *peer, const char *msg)
+{
+ struct sip_addr addr;
+ struct pl pl;
+ char *uri = NULL;
+ int err = 0;
+
+ if (!ua || !peer || !msg)
+ return EINVAL;
+
+ pl_set_str(&pl, peer);
+
+ err = sip_addr_decode(&addr, &pl);
+ if (err)
+ return err;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ return err;
+
+ err = sip_req_send(ua, "MESSAGE", uri, resp_handler, ua,
+ "Accept: text/plain\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n%s",
+ str_len(msg), msg);
+
+ mem_deref(uri);
+
+ return err;
+}
diff --git a/src/metric.c b/src/metric.c
new file mode 100644
index 0000000..8cc0b18
--- /dev/null
+++ b/src/metric.c
@@ -0,0 +1,79 @@
+/**
+ * @file metric.c Metrics for media transmit/receive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {TMR_INTERVAL = 3};
+static void tmr_handler(void *arg)
+{
+ struct metric *metric = arg;
+ const uint64_t now = tmr_jiffies();
+ uint32_t diff;
+
+ tmr_start(&metric->tmr, TMR_INTERVAL * 1000, tmr_handler, metric);
+
+ if (now <= metric->ts_last)
+ return;
+
+ if (metric->ts_last) {
+ uint32_t bytes = metric->n_bytes - metric->n_bytes_last;
+ diff = (uint32_t)(now - metric->ts_last);
+ metric->cur_bitrate = 1000 * 8 * bytes / diff;
+ }
+
+ /* Update counters */
+ metric->ts_last = now;
+ metric->n_bytes_last = metric->n_bytes;
+}
+
+
+static void metric_start(struct metric *metric)
+{
+ if (metric->started)
+ return;
+
+ metric->ts_start = tmr_jiffies();
+ tmr_start(&metric->tmr, 1, tmr_handler, metric);
+
+ metric->started = true;
+}
+
+
+void metric_reset(struct metric *metric)
+{
+ if (!metric)
+ return;
+
+ tmr_cancel(&metric->tmr);
+}
+
+
+void metric_add_packet(struct metric *metric, size_t packetsize)
+{
+ if (!metric)
+ return;
+
+ if (!metric->started)
+ metric_start(metric);
+
+ metric->n_bytes += (uint32_t)packetsize;
+ metric->n_packets++;
+}
+
+
+uint32_t metric_avg_bitrate(const struct metric *metric)
+{
+ int diff;
+
+ if (!metric || !metric->ts_start)
+ return 0;
+
+ diff = (int)(tmr_jiffies() - metric->ts_start);
+
+ return 1000 * 8 * metric->n_bytes / diff;
+}
diff --git a/src/mnat.c b/src/mnat.c
new file mode 100644
index 0000000..416e93b
--- /dev/null
+++ b/src/mnat.c
@@ -0,0 +1,87 @@
+/**
+ * @file mnat.c Media NAT
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list mnatl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct mnat *mnat = arg;
+
+ list_unlink(&mnat->le);
+}
+
+
+/**
+ * Register a Media NAT traversal module
+ *
+ * @param mnatp Pointer to allocated Media NAT traversal module
+ * @param id Media NAT Identifier
+ * @param ftag SIP Feature tag (optional)
+ * @param sessh Session allocation handler
+ * @param mediah Media allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mnat_register(struct mnat **mnatp, const char *id, const char *ftag,
+ mnat_sess_h *sessh, mnat_media_h *mediah,
+ mnat_update_h *updateh)
+{
+ struct mnat *mnat;
+
+ if (!mnatp || !id || !sessh || !mediah)
+ return EINVAL;
+
+ mnat = mem_zalloc(sizeof(*mnat), destructor);
+ if (!mnat)
+ return ENOMEM;
+
+ list_append(&mnatl, &mnat->le, mnat);
+
+ mnat->id = id;
+ mnat->ftag = ftag;
+ mnat->sessh = sessh;
+ mnat->mediah = mediah;
+ mnat->updateh = updateh;
+
+ info("medianat: %s\n", id);
+
+ *mnatp = mnat;
+
+ return 0;
+}
+
+
+/**
+ * Find a Media NAT module by name
+ *
+ * @param id Name of the Media NAT module to find
+ *
+ * @return Matching Media NAT module if found, otherwise NULL
+ */
+const struct mnat *mnat_find(const char *id)
+{
+ struct mnat *mnat;
+ struct le *le;
+
+ for (le=mnatl.head; le; le=le->next) {
+
+ mnat = le->data;
+
+ if (str_casecmp(mnat->id, id))
+ continue;
+
+ return mnat;
+ }
+
+ return NULL;
+}
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 0000000..12b3d13
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,156 @@
+/**
+ * @file module.c Module loading
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct modapp {
+ struct mod *mod;
+ struct le le;
+};
+
+
+static struct list modappl;
+
+
+static void modapp_destructor(void *arg)
+{
+ struct modapp *modapp = arg;
+ list_unlink(&modapp->le);
+ mem_deref(modapp->mod);
+}
+
+
+#ifdef STATIC
+
+/* Declared in static.c */
+extern const struct mod_export *mod_table[];
+
+static const struct mod_export *find_module(const struct pl *pl)
+{
+ struct pl name;
+ uint32_t i;
+
+ if (re_regex(pl->p, pl->l, "[^.]+.[^]*", &name, NULL))
+ name = *pl;
+
+ for (i=0; ; i++) {
+ const struct mod_export *me = mod_table[i];
+ if (!me)
+ return NULL;
+ if (0 == pl_strcasecmp(&name, me->name))
+ return me;
+ }
+
+ return NULL;
+}
+#endif
+
+
+static int load_module(struct mod **modp, const struct pl *modpath,
+ const struct pl *name)
+{
+ char file[256];
+ struct mod *m = NULL;
+ int err = 0;
+
+ if (!name)
+ return EINVAL;
+
+#ifdef STATIC
+ /* Try static first */
+ err = mod_add(&m, find_module(name));
+ if (!err)
+ goto out;
+#endif
+
+ /* Then dynamic */
+ if (re_snprintf(file, sizeof(file), "%r/%r", modpath, name) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ err = mod_load(&m, file);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ warning("module %r: %m\n", name, err);
+ }
+ else if (modp)
+ *modp = m;
+
+ return err;
+}
+
+
+static int module_handler(const struct pl *val, void *arg)
+{
+ (void)load_module(NULL, arg, val);
+ return 0;
+}
+
+
+static int module_tmp_handler(const struct pl *val, void *arg)
+{
+ struct mod *mod = NULL;
+ (void)load_module(&mod, arg, val);
+ mem_deref(mod);
+ return 0;
+}
+
+
+static int module_app_handler(const struct pl *val, void *arg)
+{
+ struct modapp *modapp;
+
+ modapp = mem_zalloc(sizeof(*modapp), modapp_destructor);
+ if (!modapp)
+ return ENOMEM;
+
+ if (load_module(&modapp->mod, arg, val)) {
+ mem_deref(modapp);
+ return 0;
+ }
+
+ list_prepend(&modappl, &modapp->le, modapp);
+
+ return 0;
+}
+
+
+int module_init(const struct conf *conf)
+{
+ struct pl path;
+ int err;
+
+ if (!conf)
+ return EINVAL;
+
+ if (conf_get(conf, "module_path", &path))
+ pl_set_str(&path, ".");
+
+ err = conf_apply(conf, "module", module_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_tmp", module_tmp_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_app", module_app_handler, &path);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void module_app_unload(void)
+{
+ list_flush(&modappl);
+}
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..4829a5a
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,439 @@
+/**
+ * @file net.c Networking code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct {
+ struct config_net cfg;
+ struct sa laddr;
+ char ifname[16];
+#ifdef HAVE_INET6
+ struct sa laddr6;
+ char ifname6[16];
+#endif
+ struct tmr tmr;
+ struct dnsc *dnsc;
+ struct sa nsv[4]; /**< Configured name servers */
+ uint32_t nsn; /**< Number of configured name servers */
+ uint32_t interval;
+ int af; /**< Preferred address family */
+ char domain[64]; /**< DNS domain from network */
+ net_change_h *ch;
+ void *arg;
+} net;
+
+
+/**
+ * Check for DNS Server updates
+ */
+static void dns_refresh(void)
+{
+ struct sa nsv[8];
+ uint32_t i, nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err)
+ return;
+
+ for (i=0; i<net.nsn; i++)
+ sa_cpy(&nsv[nsn++], &net.nsv[i]);
+
+ (void)dnsc_srv_set(net.dnsc, nsv, nsn);
+}
+
+
+/**
+ * Detect changes in IP address(es)
+ */
+static void ipchange_handler(void *arg)
+{
+ bool change;
+ (void)arg;
+
+ tmr_start(&net.tmr, net.interval * 1000, ipchange_handler, NULL);
+
+ dns_refresh();
+
+ change = net_check();
+ if (change && net.ch) {
+ net.ch(net.arg);
+ }
+}
+
+
+/**
+ * Check if local IP address(es) changed
+ *
+ * @return True if changed, otherwise false
+ */
+bool net_check(void)
+{
+ struct sa laddr = net.laddr;
+#ifdef HAVE_INET6
+ struct sa laddr6 = net.laddr6;
+#endif
+ bool change = false;
+
+ if (str_isset(net.cfg.ifname)) {
+
+ (void)net_if_getaddr(net.cfg.ifname, AF_INET, &net.laddr);
+
+#ifdef HAVE_INET6
+ (void)net_if_getaddr(net.cfg.ifname, AF_INET6, &net.laddr6);
+#endif
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net.laddr);
+ (void)net_rt_default_get(AF_INET, net.ifname,
+ sizeof(net.ifname));
+
+#ifdef HAVE_INET6
+ (void)net_default_source_addr_get(AF_INET6, &net.laddr6);
+ (void)net_rt_default_get(AF_INET6, net.ifname6,
+ sizeof(net.ifname6));
+#endif
+ }
+
+ if (sa_isset(&net.laddr, SA_ADDR) &&
+ !sa_cmp(&laddr, &net.laddr, SA_ADDR)) {
+ change = true;
+ info("net: local IPv4 address changed: %j -> %j\n",
+ &laddr, &net.laddr);
+ }
+
+#ifdef HAVE_INET6
+ if (sa_isset(&net.laddr6, SA_ADDR) &&
+ !sa_cmp(&laddr6, &net.laddr6, SA_ADDR)) {
+ change = true;
+ info("net: local IPv6 address changed: %j -> %j\n",
+ &laddr6, &net.laddr6);
+ }
+#endif
+
+ return change;
+}
+
+
+static int dns_init(void)
+{
+ struct sa nsv[8];
+ uint32_t i, nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(net.domain, sizeof(net.domain), nsv, &nsn);
+ if (err) {
+ nsn = 0;
+ }
+
+ /* Add any configured nameservers */
+ for (i=0; i<net.nsn && nsn < ARRAY_SIZE(nsv); i++)
+ sa_cpy(&nsv[nsn++], &net.nsv[i]);
+
+ return dnsc_alloc(&net.dnsc, NULL, nsv, nsn);
+}
+
+
+/**
+ * Return TRUE if libre supports IPv6
+ */
+static bool check_ipv6(void)
+{
+ struct sa sa;
+
+ return 0 == sa_set_str(&sa, "::1", 2000);
+}
+
+
+/**
+ * Initialise networking
+ *
+ * @param cfg Network configuration
+ * @param af Preferred address family
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_init(const struct config_net *cfg, int af)
+{
+ int err;
+
+ if (!cfg)
+ return EINVAL;
+
+ /*
+ * baresip/libre must be built with matching HAVE_INET6 value.
+ * if different the size of `struct sa' will not match and the
+ * application is very likely to crash.
+ */
+#ifdef HAVE_INET6
+ if (!check_ipv6()) {
+ error("libre was compiled without IPv6-support"
+ ", but baresip was compiled with\n");
+ return EAFNOSUPPORT;
+ }
+#else
+ if (check_ipv6()) {
+ error("libre was compiled with IPv6-support"
+ ", but baresip was compiled without\n");
+ return EAFNOSUPPORT;
+ }
+#endif
+
+ net.cfg = *cfg;
+ net.af = af;
+
+ tmr_init(&net.tmr);
+
+ /* Initialise DNS resolver */
+ err = dns_init();
+ if (err) {
+ warning("net: dns_init: %m\n", err);
+ return err;
+ }
+
+ sa_init(&net.laddr, AF_INET);
+ (void)sa_set_str(&net.laddr, "127.0.0.1", 0);
+
+ if (str_isset(cfg->ifname)) {
+
+ bool got_it = false;
+
+ info("Binding to interface '%s'\n", cfg->ifname);
+
+ str_ncpy(net.ifname, cfg->ifname, sizeof(net.ifname));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET, &net.laddr);
+ if (err) {
+ info("net: %s: could not get IPv4 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+
+#ifdef HAVE_INET6
+ str_ncpy(net.ifname6, cfg->ifname,
+ sizeof(net.ifname6));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET6, &net.laddr6);
+ if (err) {
+ info("net: %s: could not get IPv6 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+#endif
+ if (got_it)
+ err = 0;
+ else {
+ warning("net: %s: could not get network address\n",
+ cfg->ifname);
+ return EADDRNOTAVAIL;
+ }
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net.laddr);
+ (void)net_rt_default_get(AF_INET, net.ifname,
+ sizeof(net.ifname));
+
+#ifdef HAVE_INET6
+ sa_init(&net.laddr6, AF_INET6);
+
+ (void)net_default_source_addr_get(AF_INET6, &net.laddr6);
+ (void)net_rt_default_get(AF_INET6, net.ifname6,
+ sizeof(net.ifname6));
+#endif
+ }
+
+ (void)re_fprintf(stderr, "Local network address:");
+
+ if (sa_isset(&net.laddr, SA_ADDR)) {
+ (void)re_fprintf(stderr, " IPv4=%s:%j",
+ net.ifname, &net.laddr);
+ }
+#ifdef HAVE_INET6
+ if (sa_isset(&net.laddr6, SA_ADDR)) {
+ (void)re_fprintf(stderr, " IPv6=%s:%j",
+ net.ifname6, &net.laddr6);
+ }
+#endif
+ (void)re_fprintf(stderr, "\n");
+
+ return err;
+}
+
+
+/**
+ * Reset the DNS resolver
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_reset(void)
+{
+ net.dnsc = mem_deref(net.dnsc);
+
+ return dns_init();
+}
+
+
+/**
+ * Close networking
+ */
+void net_close(void)
+{
+ net.dnsc = mem_deref(net.dnsc);
+ tmr_cancel(&net.tmr);
+}
+
+
+/**
+ * Add a DNS server
+ *
+ * @param sa DNS Server IP address and port
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_dnssrv_add(const struct sa *sa)
+{
+ if (net.nsn >= ARRAY_SIZE(net.nsv))
+ return E2BIG;
+
+ sa_cpy(&net.nsv[net.nsn++], sa);
+
+ return 0;
+}
+
+
+/**
+ * Check for networking changes with a regular interval
+ *
+ * @param interval Interval in seconds
+ * @param ch Handler called when a change was detected
+ * @param arg Handler argument
+ */
+void net_change(uint32_t interval, net_change_h *ch, void *arg)
+{
+ net.interval = interval;
+ net.ch = ch;
+ net.arg = arg;
+
+ if (interval)
+ tmr_start(&net.tmr, interval * 1000, ipchange_handler, NULL);
+ else
+ tmr_cancel(&net.tmr);
+}
+
+
+static int dns_debug(struct re_printf *pf, void *unused)
+{
+ struct sa nsv[4];
+ uint32_t i, nsn;
+ int err;
+
+ (void)unused;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err)
+ nsn = 0;
+
+ err = re_hprintf(pf, " DNS Servers: (%u)\n", nsn);
+ for (i=0; i<nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]);
+ for (i=0; i<net.nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", nsn+i, &net.nsv[i]);
+
+ return err;
+}
+
+
+int net_af(void)
+{
+ return net.af;
+}
+
+
+/**
+ * Print networking debug information
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_debug(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "--- Network debug ---\n");
+ err |= re_hprintf(pf, " Preferred AF: %s\n", net_af2name(net.af));
+ err |= re_hprintf(pf, " Local IPv4: %9s - %j\n",
+ net.ifname, &net.laddr);
+#ifdef HAVE_INET6
+ err |= re_hprintf(pf, " Local IPv6: %9s - %j\n",
+ net.ifname6, &net.laddr6);
+#endif
+
+ err |= net_if_debug(pf, NULL);
+
+ err |= net_rt_debug(pf, NULL);
+
+ err |= dns_debug(pf, NULL);
+
+ return err;
+}
+
+
+/**
+ * Get the local IP Address for a specific Address Family (AF)
+ *
+ * @param af Address Family
+ *
+ * @return Local IP Address
+ */
+const struct sa *net_laddr_af(int af)
+{
+ switch (af) {
+
+ case AF_INET: return &net.laddr;
+#ifdef HAVE_INET6
+ case AF_INET6: return &net.laddr6;
+#endif
+ default: return NULL;
+ }
+}
+
+
+/**
+ * Get the DNS Client
+ *
+ * @return DNS Client
+ */
+struct dnsc *net_dnsc(void)
+{
+ return net.dnsc;
+}
+
+
+/**
+ * Get the network domain name
+ *
+ * @return Network domain
+ */
+const char *net_domain(void)
+{
+ return net.domain[0] ? net.domain : NULL;
+}
diff --git a/src/play.c b/src/play.c
new file mode 100644
index 0000000..9ea8788
--- /dev/null
+++ b/src/play.c
@@ -0,0 +1,317 @@
+/**
+ * @file src/play.c Audio-file player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {SILENCE_DUR = 2000, PTIME = 100};
+
+/** Audio file player */
+struct play {
+ struct le le;
+ struct play **playp;
+ struct lock *lock;
+ struct mbuf *mb;
+ struct auplay_st *auplay;
+ struct tmr tmr;
+ int repeat;
+ bool eof;
+};
+
+
+#ifndef PREFIX
+#define PREFIX "/usr"
+#endif
+static char play_path[256] = PREFIX "/share/baresip";
+static struct list playl;
+
+
+static void tmr_polling(void *arg);
+
+
+static void tmr_stop(void *arg)
+{
+ struct play *play = arg;
+ mem_deref(play);
+}
+
+
+static void tmr_repeat(void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ play->mb->pos = 0;
+ play->eof = false;
+
+ tmr_start(&play->tmr, 1000, tmr_polling, arg);
+
+ lock_rel(play->lock);
+}
+
+
+static void tmr_polling(void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ tmr_start(&play->tmr, 1000, tmr_polling, arg);
+
+ if (play->eof) {
+ if (play->repeat > 0)
+ play->repeat--;
+
+ if (play->repeat == 0)
+ tmr_start(&play->tmr, 1, tmr_stop, arg);
+ else
+ tmr_start(&play->tmr, SILENCE_DUR, tmr_repeat, arg);
+ }
+
+ lock_rel(play->lock);
+}
+
+
+/**
+ * NOTE: DSP cannot be destroyed inside handler
+ */
+static bool write_handler(uint8_t *buf, size_t sz, void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ if (play->eof)
+ goto silence;
+
+ if (mbuf_get_left(play->mb) < sz) {
+ play->eof = true;
+ }
+ else {
+ (void)mbuf_read_mem(play->mb, buf, sz);
+ }
+
+ silence:
+ if (play->eof)
+ memset(buf, 0, sz);
+
+ lock_rel(play->lock);
+
+ return true;
+}
+
+
+static void destructor(void *arg)
+{
+ struct play *play = arg;
+
+ list_unlink(&play->le);
+ tmr_cancel(&play->tmr);
+
+ lock_write_get(play->lock);
+ play->eof = true;
+ lock_rel(play->lock);
+
+ mem_deref(play->auplay);
+ mem_deref(play->mb);
+ mem_deref(play->lock);
+
+ if (play->playp)
+ *play->playp = NULL;
+}
+
+
+static int aufile_load(struct mbuf *mb, const char *filename,
+ uint32_t *srate, uint8_t *channels)
+{
+ struct aufile_prm prm;
+ struct aufile *af;
+ int err;
+
+ err = aufile_open(&af, &prm, filename, AUFILE_READ);
+ if (err)
+ return err;
+
+ while (!err) {
+ uint8_t buf[4096];
+ size_t i, n;
+
+ n = sizeof(buf);
+
+ err = aufile_read(af, buf, &n);
+ if (err || !n)
+ break;
+
+ switch (prm.fmt) {
+
+ case AUFMT_S16LE:
+ err = mbuf_write_mem(mb, buf, n);
+ break;
+
+ case AUFMT_PCMA:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_alaw2pcm(buf[i]));
+ }
+ break;
+
+ case AUFMT_PCMU:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_ulaw2pcm(buf[i]));
+ }
+ break;
+
+ default:
+ err = ENOSYS;
+ break;
+ }
+ }
+
+ mem_deref(af);
+
+ if (!err) {
+ mb->pos = 0;
+
+ *srate = prm.srate;
+ *channels = prm.channels;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play a tone from a PCM buffer
+ *
+ * @param playp Pointer to allocated player object
+ * @param tone PCM buffer to play
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_tone(struct play **playp, struct mbuf *tone, uint32_t srate,
+ uint8_t ch, int repeat)
+{
+ struct auplay_prm wprm;
+ struct play *play;
+ struct config *cfg;
+ int err;
+
+ if (playp && *playp)
+ return EALREADY;
+
+ cfg = conf_config();
+ if (!cfg)
+ return ENOENT;
+
+ play = mem_zalloc(sizeof(*play), destructor);
+ if (!play)
+ return ENOMEM;
+
+ tmr_init(&play->tmr);
+ play->repeat = repeat;
+ play->mb = mem_ref(tone);
+
+ err = lock_alloc(&play->lock);
+ if (err)
+ goto out;
+
+ wprm.fmt = AUFMT_S16LE;
+ wprm.ch = ch;
+ wprm.srate = srate;
+ wprm.ptime = PTIME;
+
+ err = auplay_alloc(&play->auplay, cfg->audio.alert_mod, &wprm,
+ cfg->audio.alert_dev, write_handler, play);
+ if (err)
+ goto out;
+
+ list_append(&playl, &play->le, play);
+ tmr_start(&play->tmr, 1000, tmr_polling, play);
+
+ out:
+ if (err) {
+ mem_deref(play);
+ }
+ else if (playp) {
+ play->playp = playp;
+ *playp = play;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play an audio file in WAV format
+ *
+ * @param playp Pointer to allocated player object
+ * @param filename Name of WAV file to play
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_file(struct play **playp, const char *filename, int repeat)
+{
+ struct mbuf *mb;
+ char path[512];
+ uint32_t srate;
+ uint8_t ch;
+ int err;
+
+ if (playp && *playp)
+ return EALREADY;
+
+ if (re_snprintf(path, sizeof(path), "%s/%s",
+ play_path, filename) < 0)
+ return ENOMEM;
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ err = aufile_load(mb, path, &srate, &ch);
+ if (err) {
+ warning("play: %s: %m\n", path, err);
+ goto out;
+ }
+
+ err = play_tone(playp, mb, srate, ch, repeat);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+void play_init(void)
+{
+ list_init(&playl);
+}
+
+
+/**
+ * Close all active audio players
+ */
+void play_close(void)
+{
+ list_flush(&playl);
+}
+
+
+void play_set_path(const char *path)
+{
+ str_ncpy(play_path, path, sizeof(play_path));
+}
diff --git a/src/realtime.c b/src/realtime.c
new file mode 100644
index 0000000..a144e22
--- /dev/null
+++ b/src/realtime.c
@@ -0,0 +1,100 @@
+/**
+ * @file realtime.c Real-Time scheduling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#ifdef DARWIN
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <stdio.h>
+#include <mach/mach.h>
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#endif
+
+
+#ifdef DARWIN
+static int set_realtime(int period, int computation, int constraint)
+{
+ struct thread_time_constraint_policy ttcpolicy;
+ int ret;
+
+ ttcpolicy.period = period; /* HZ/160 */
+ ttcpolicy.computation = computation; /* HZ/3300 */
+ ttcpolicy.constraint = constraint; /* HZ/2200 */
+ ttcpolicy.preemptible = 1;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_TIME_CONSTRAINT_POLICY,
+ (thread_policy_t)&ttcpolicy,
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+ if (ret != KERN_SUCCESS)
+ return ENOSYS;
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Enable real-time scheduling (for selected platforms)
+ *
+ * @param enable True to enable, false to disable
+ * @param fps Wanted video framerate
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int realtime_enable(bool enable, int fps)
+{
+#ifdef DARWIN
+ if (enable) {
+#if TARGET_OS_IPHONE
+ int bus_speed = 100000000;
+#else
+ int ret, bus_speed;
+ int mib[2] = { CTL_HW, HW_BUS_FREQ };
+ size_t len;
+
+ len = sizeof(bus_speed);
+ ret = sysctl (mib, 2, &bus_speed, &len, NULL, 0);
+ if (ret < 0) {
+ return ENOSYS;
+ }
+
+ info("realtime: fps=%d bus_speed=%d\n", fps, bus_speed);
+#endif
+
+ return set_realtime(bus_speed / fps,
+ bus_speed / 3300, bus_speed / 2200);
+ }
+ else {
+ kern_return_t ret;
+ thread_standard_policy_data_t pt;
+ mach_msg_type_number_t cnt = THREAD_STANDARD_POLICY_COUNT;
+ boolean_t get_default = TRUE;
+
+ ret = thread_policy_get(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ &cnt, &get_default);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ THREAD_STANDARD_POLICY_COUNT);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ return 0;
+ }
+#else
+ (void)enable;
+ (void)fps;
+ return ENOSYS;
+#endif
+}
diff --git a/src/reg.c b/src/reg.c
new file mode 100644
index 0000000..2588873
--- /dev/null
+++ b/src/reg.c
@@ -0,0 +1,269 @@
+/**
+ * @file reg.c Register Client
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Register client */
+struct reg {
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< Pointer to parent UA object */
+ struct sipreg *sipreg; /**< SIP Register client */
+ int id; /**< Registration ID (for SIP outbound) */
+
+ /* status: */
+ uint16_t scode; /**< Registration status code */
+ char *srv; /**< SIP Server id */
+ int sipfd; /**< Cached file-descr. for SIP conn */
+ int af; /**< Cached address family for SIP conn */
+};
+
+
+static void destructor(void *arg)
+{
+ struct reg *reg = arg;
+
+ list_unlink(&reg->le);
+ mem_deref(reg->sipreg);
+ mem_deref(reg->srv);
+}
+
+
+static int sipmsg_fd(const struct sip_msg *msg)
+{
+ if (!msg)
+ return -1;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ return udp_sock_fd(msg->sock, AF_UNSPEC);
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ return tcp_conn_fd(sip_msg_tcpconn(msg));
+
+ default:
+ return -1;
+ }
+}
+
+
+static int sipmsg_af(const struct sip_msg *msg)
+{
+ struct sa laddr;
+ int err = 0;
+
+ if (!msg)
+ return AF_UNSPEC;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ err = udp_local_get(msg->sock, &laddr);
+ break;
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ err = tcp_conn_local_get(sip_msg_tcpconn(msg), &laddr);
+ break;
+
+ default:
+ return AF_UNSPEC;
+ }
+
+ return err ? AF_UNSPEC : sa_af(&laddr);
+}
+
+
+static const char *af_name(int af)
+{
+ switch (af) {
+
+ case AF_INET: return "v4";
+ case AF_INET6: return "v6";
+ default: return "v?";
+ }
+}
+
+
+static int sip_auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static void register_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct reg *reg = arg;
+ const struct sip_hdr *hdr;
+
+ if (err) {
+ warning("reg: %s: Register: %m\n", ua_aor(reg->ua), err);
+
+ reg->scode = 999;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%m", err);
+ return;
+ }
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_SERVER);
+ if (hdr) {
+ reg->srv = mem_deref(reg->srv);
+ (void)pl_strdup(&reg->srv, &hdr->val);
+ }
+
+ if (200 <= msg->scode && msg->scode <= 299) {
+
+ uint32_t n_bindings;
+
+ n_bindings = sip_msg_hdr_count(msg, SIP_HDR_CONTACT);
+ reg->sipfd = sipmsg_fd(msg);
+ reg->af = sipmsg_af(msg);
+
+ if (msg->scode != reg->scode) {
+ ua_printf(reg->ua, "{%d/%s/%s} %u %r (%s)"
+ " [%u binding%s]\n",
+ reg->id, sip_transp_name(msg->tp),
+ af_name(reg->af), msg->scode, &msg->reason,
+ reg->srv, n_bindings,
+ 1==n_bindings?"":"s");
+ }
+
+ reg->scode = msg->scode;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_OK, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+ else if (msg->scode >= 300) {
+
+ warning("reg: %s: %u %r (%s)\n", ua_aor(reg->ua),
+ msg->scode, &msg->reason, reg->srv);
+
+ reg->scode = msg->scode;
+ reg->sipfd = -1;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+int reg_add(struct list *lst, struct ua *ua, int regid)
+{
+ struct reg *reg;
+
+ if (!lst || !ua)
+ return EINVAL;
+
+ reg = mem_zalloc(sizeof(*reg), destructor);
+ if (!reg)
+ return ENOMEM;
+
+ reg->ua = ua;
+ reg->id = regid;
+ reg->sipfd = -1;
+
+ list_append(lst, &reg->le, reg);
+
+ return 0;
+}
+
+
+int reg_register(struct reg *reg, const char *reg_uri, const char *params,
+ uint32_t regint, const char *outbound)
+{
+ const char *routev[1];
+ int err;
+
+ if (!reg || !reg_uri)
+ return EINVAL;
+
+ reg->scode = 0;
+ routev[0] = outbound;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+ err = sipreg_register(&reg->sipreg, uag_sip(), reg_uri,
+ ua_aor(reg->ua), ua_aor(reg->ua),
+ regint, ua_cuser(reg->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ reg->id,
+ sip_auth_handler, ua_prm(reg->ua), true,
+ register_handler, reg,
+ params[0] ? &params[1] : NULL,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void reg_unregister(struct reg *reg)
+{
+ if (!reg)
+ return;
+
+ reg->scode = 0;
+ reg->sipfd = -1;
+ reg->af = 0;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+}
+
+
+bool reg_isok(const struct reg *reg)
+{
+ if (!reg)
+ return false;
+
+ return 200 <= reg->scode && reg->scode <= 299;
+}
+
+
+int reg_sipfd(const struct reg *reg)
+{
+ return reg ? reg->sipfd : -1;
+}
+
+
+static const char *print_scode(uint16_t scode)
+{
+ if (0 == scode) return "\x1b[33m" "zzz" "\x1b[;m";
+ else if (200 == scode) return "\x1b[32m" "OK " "\x1b[;m";
+ else return "\x1b[31m" "ERR" "\x1b[;m";
+}
+
+
+int reg_debug(struct re_printf *pf, const struct reg *reg)
+{
+ int err = 0;
+
+ if (!reg)
+ return 0;
+
+ err |= re_hprintf(pf, "\nRegister client:\n");
+ err |= re_hprintf(pf, " id: %d\n", reg->id);
+ err |= re_hprintf(pf, " scode: %u (%s)\n",
+ reg->scode, print_scode(reg->scode));
+ err |= re_hprintf(pf, " srv: %s\n", reg->srv);
+ err |= re_hprintf(pf, " sipfd: %d\n", reg->sipfd);
+
+ return err;
+}
+
+
+int reg_status(struct re_printf *pf, const struct reg *reg)
+{
+ if (!reg)
+ return 0;
+
+ return re_hprintf(pf, " %s %s", print_scode(reg->scode), reg->srv);
+}
diff --git a/src/rtpkeep.c b/src/rtpkeep.c
new file mode 100644
index 0000000..7f32c41
--- /dev/null
+++ b/src/rtpkeep.c
@@ -0,0 +1,165 @@
+/**
+ * @file rtpkeep.c RTP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * See draft-ietf-avt-app-rtp-keepalive:
+ *
+ * "zero" 4.1. Transport Packet of 0-byte
+ * "rtcp" 4.3. RTCP Packets Multiplexed with RTP Packets
+ * "stun" 4.4. STUN Indication Packet
+ * "dyna" 4.6. RTP Packet with Unknown Payload Type
+ */
+
+
+enum {
+ Tr_UDP = 15,
+ Tr_TCP = 7200
+};
+
+/** RTP Keepalive */
+struct rtpkeep {
+ struct rtp_sock *rtp;
+ struct sdp_media *sdp;
+ struct tmr tmr;
+ char *method;
+ uint32_t ts;
+ bool flag;
+};
+
+
+static void destructor(void *arg)
+{
+ struct rtpkeep *rk = arg;
+
+ tmr_cancel(&rk->tmr);
+ mem_deref(rk->method);
+}
+
+
+static int send_keepalive(struct rtpkeep *rk)
+{
+ int err = 0;
+
+ if (!str_casecmp(rk->method, "zero")) {
+ struct mbuf *mb = mbuf_alloc(1);
+ if (!mb)
+ return ENOMEM;
+ err = udp_send(rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), mb);
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "stun")) {
+ err = stun_indication(IPPROTO_UDP, rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), 0,
+ STUN_METHOD_BINDING, NULL, 0, false, 0);
+ }
+ else if (!str_casecmp(rk->method, "dyna")) {
+ struct mbuf *mb = mbuf_alloc(RTP_HEADER_SIZE);
+ int pt = sdp_media_find_unused_pt(rk->sdp);
+ if (!mb)
+ return ENOMEM;
+ if (pt == -1)
+ return ENOENT;
+ mb->pos = mb->end = RTP_HEADER_SIZE;
+
+ err = rtp_send(rk->rtp, sdp_media_raddr(rk->sdp), false,
+ pt, rk->ts, mb);
+
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "rtcp")) {
+
+ if (sdp_media_rattr(rk->sdp, "rtcp-mux")) {
+ /* do nothing */
+ ;
+ }
+ else {
+ warning("rtpkeep: rtcp-mux is disabled\n");
+ }
+ }
+ else {
+ warning("rtpkeep: unknown method: %s\n", rk->method);
+ return ENOSYS;
+ }
+
+ return err;
+}
+
+
+/**
+ * Logic:
+ *
+ * We check for RTP activity every 15 seconds, and clear the flag.
+ * The flag is set for every transmitted RTP packet. If the flag
+ * is not set, it means that we have not sent any RTP packet in the
+ * last period of 0 - 15 seconds. Start transmitting RTP keepalives
+ * now and every 15 seconds after that.
+ *
+ * @param arg Handler argument
+ */
+static void timeout(void *arg)
+{
+ struct rtpkeep *rk = arg;
+ int err;
+
+ tmr_start(&rk->tmr, Tr_UDP * 1000, timeout, rk);
+
+ if (rk->flag) {
+ rk->flag = false;
+ return;
+ }
+
+ err = send_keepalive(rk);
+ if (err) {
+ warning("rtpkeep: send keepalive failed: %m\n", err);
+ }
+}
+
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp)
+{
+ struct rtpkeep *rk;
+ int err;
+
+ if (!rkp || !method || proto != IPPROTO_UDP || !rtp || !sdp)
+ return EINVAL;
+
+ rk = mem_zalloc(sizeof(*rk), destructor);
+ if (!rk)
+ return ENOMEM;
+
+ rk->rtp = rtp;
+ rk->sdp = sdp;
+
+ err = str_dup(&rk->method, method);
+ if (err)
+ goto out;
+
+ tmr_start(&rk->tmr, 20, timeout, rk);
+
+ out:
+ if (err)
+ mem_deref(rk);
+ else
+ *rkp = rk;
+
+ return err;
+}
+
+
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts)
+{
+ if (!rk)
+ return;
+
+ rk->ts = ts;
+ rk->flag = true;
+}
diff --git a/src/sdp.c b/src/sdp.c
new file mode 100644
index 0000000..7a8ed29
--- /dev/null
+++ b/src/sdp.c
@@ -0,0 +1,186 @@
+/**
+ * @file src/sdp.c SDP functions
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+uint32_t sdp_media_rattr_u32(const struct sdp_media *m, const char *name)
+{
+ const char *attr = sdp_media_rattr(m, name);
+ return attr ? atoi(attr) : 0;
+}
+
+
+/*
+ * Get a remote attribute from the SDP. Try the media-level first,
+ * and if it does not exist then try session-level.
+ */
+const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m,
+ const char *name)
+{
+ const char *x;
+
+ x = sdp_media_rattr(m, name);
+ if (x)
+ return x;
+
+ x = sdp_session_rattr(s, name);
+ if (x)
+ return x;
+
+ return NULL;
+}
+
+
+/* RFC 4572 */
+int sdp_fingerprint_decode(const char *attr, struct pl *hash,
+ uint8_t *md, size_t *sz)
+{
+ struct pl f;
+ const char *p;
+ int err;
+
+ if (!attr || !hash)
+ return EINVAL;
+
+ err = re_regex(attr, str_len(attr), "[^ ]+ [0-9A-F:]+", hash, &f);
+ if (err)
+ return err;
+
+ if (md && sz) {
+ if (*sz < (f.l+1)/3)
+ return EOVERFLOW;
+
+ for (p = f.p; p < (f.p+f.l); p += 3) {
+ *md++ = ch_hex(p[0]) << 4 | ch_hex(p[1]);
+ }
+
+ *sz = (f.l+1)/3;
+ }
+
+ return 0;
+}
+
+
+bool sdp_media_has_media(const struct sdp_media *m)
+{
+ bool has;
+
+ has = sdp_media_rformat(m, NULL) != NULL;
+ if (has)
+ return sdp_media_rport(m) != 0;
+
+ return false;
+}
+
+
+/**
+ * Find a dynamic payload type that is not used
+ *
+ * @param m SDP Media
+ *
+ * @return Unused payload type, -1 if no found
+ */
+int sdp_media_find_unused_pt(const struct sdp_media *m)
+{
+ int pt;
+
+ for (pt = PT_DYN_MAX; pt>=PT_DYN_MIN; pt--) {
+
+ if (!sdp_media_format(m, false, NULL, pt, NULL, -1, -1))
+ return pt;
+ }
+
+ return -1;
+}
+
+
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m)
+{
+ struct sdp_format *sf;
+ struct list *lst;
+
+ again:
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!sf)
+ return NULL;
+
+ lst = sf->le.list;
+
+ /* move top-most codec to end of list */
+ list_unlink(&sf->le);
+ list_append(lst, &sf->le, sf);
+
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!str_casecmp(sf->name, telev_rtpfmt))
+ goto again;
+
+ return sf;
+}
+
+
+static void decode_part(const struct pl *part, struct mbuf *mb)
+{
+ struct pl hdrs, body;
+
+ if (re_regex(part->p, part->l, "\r\n\r\n[^]+", &body))
+ return;
+
+ hdrs.p = part->p;
+ hdrs.l = body.p - part->p - 2;
+
+ if (0 == re_regex(hdrs.p, hdrs.l, "application/sdp")) {
+
+ mb->pos += (body.p - (char *)mbuf_buf(mb));
+ mb->end = mb->pos + body.l;
+ }
+}
+
+
+/**
+ * Decode a multipart/mixed message and find the part with application/sdp
+ */
+int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb)
+{
+ struct pl bnd, s, e, p;
+ char expr[64];
+ int err;
+
+ if (!ctype || !mb)
+ return EINVAL;
+
+ /* fetch the boundary tag, excluding quotes */
+ err = re_regex(ctype->p, ctype->l,
+ "multipart/mixed;[ \t]*boundary=[~]+", NULL, &bnd);
+ if (err)
+ return err;
+
+ if (re_snprintf(expr, sizeof(expr), "--%r[^]+", &bnd) < 0)
+ return ENOMEM;
+
+ /* find 1st boundary */
+ err = re_regex((char *)mbuf_buf(mb), mbuf_get_left(mb), expr, &s);
+ if (err)
+ return err;
+
+ /* iterate over each part */
+ while (s.l > 2) {
+ if (re_regex(s.p, s.l, expr, &e))
+ return 0;
+
+ p.p = s.p + 2;
+ p.l = e.p - p.p - bnd.l - 2;
+
+ /* valid part in "p" */
+ decode_part(&p, mb);
+
+ s = e;
+ }
+
+ return 0;
+}
diff --git a/src/sipreq.c b/src/sipreq.c
new file mode 100644
index 0000000..1518645
--- /dev/null
+++ b/src/sipreq.c
@@ -0,0 +1,149 @@
+/**
+ * @file sipreq.c SIP Authenticated Request
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** SIP Authenticated Request */
+struct sip_req {
+ struct sip_loopstate ls;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct sip_request *req;
+ char *method;
+ char *fmt;
+ sip_resp_h *resph;
+ void *arg;
+};
+
+
+static int request(struct sip_req *sr);
+
+
+static void destructor(void *arg)
+{
+ struct sip_req *sr = arg;
+
+ mem_deref(sr->req);
+ mem_deref(sr->auth);
+ mem_deref(sr->dlg);
+ mem_deref(sr->method);
+ mem_deref(sr->fmt);
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sip_req *sr = arg;
+
+ if (err || sip_request_loops(&sr->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ ;
+ }
+ else {
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sr->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = request(sr);
+ if (err)
+ break;
+
+ return;
+
+ case 403:
+ sip_auth_reset(sr->auth);
+ break;
+ }
+ }
+
+ out:
+ sr->resph(err, msg, sr->arg);
+
+ /* destroy now */
+ mem_deref(sr);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int request(struct sip_req *sr)
+{
+ return sip_drequestf(&sr->req, uag_sip(), true, sr->method, sr->dlg,
+ 0, sr->auth, NULL, resp_handler,
+ sr, sr->fmt ? "%s" : NULL, sr->fmt);
+}
+
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...)
+{
+ const char *routev[1];
+ struct sip_req *sr;
+ int err;
+
+ if (!ua || !method || !uri || !resph || !fmt)
+ return EINVAL;
+
+ routev[0] = ua_outbound(ua);
+
+ sr = mem_zalloc(sizeof(*sr), destructor);
+ if (!sr)
+ return ENOMEM;
+
+ sr->resph = resph;
+ sr->arg = arg;
+
+ err = str_dup(&sr->method, method);
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ err |= re_vsdprintf(&sr->fmt, fmt, ap);
+ va_end(ap);
+ }
+
+ if (err)
+ goto out;
+
+ err = sip_dialog_alloc(&sr->dlg, uri, uri, NULL, ua_aor(ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0);
+ if (err)
+ goto out;
+
+ err = sip_auth_alloc(&sr->auth, auth_handler, ua_prm(ua), true);
+ if (err)
+ goto out;
+
+ err = request(sr);
+
+ out:
+ if (err)
+ mem_deref(sr);
+
+ return err;
+}
diff --git a/src/srcs.mk b/src/srcs.mk
new file mode 100644
index 0000000..d3aa765
--- /dev/null
+++ b/src/srcs.mk
@@ -0,0 +1,49 @@
+#
+# srcs.mk All application source files.
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += account.c
+SRCS += aucodec.c
+SRCS += audio.c
+SRCS += aufilt.c
+SRCS += auplay.c
+SRCS += ausrc.c
+SRCS += call.c
+SRCS += cmd.c
+SRCS += conf.c
+SRCS += config.c
+SRCS += contact.c
+SRCS += log.c
+SRCS += menc.c
+SRCS += message.c
+SRCS += metric.c
+SRCS += mnat.c
+SRCS += module.c
+SRCS += net.c
+SRCS += play.c
+SRCS += realtime.c
+SRCS += reg.c
+SRCS += rtpkeep.c
+SRCS += sdp.c
+SRCS += sipreq.c
+SRCS += stream.c
+SRCS += ua.c
+SRCS += ui.c
+
+ifneq ($(USE_VIDEO),)
+SRCS += bfcp.c
+SRCS += mctrl.c
+SRCS += video.c
+SRCS += vidcodec.c
+SRCS += vidfilt.c
+SRCS += vidisp.c
+SRCS += vidsrc.c
+endif
+
+ifneq ($(STATIC),)
+SRCS += static.c
+endif
+
+APP_SRCS += main.c
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 0000000..0e5b89c
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,582 @@
+/**
+ * @file stream.c Generic Media Stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define MAGIC 0x00814ea5
+#include "magic.h"
+
+
+enum {
+ RTP_RECV_SIZE = 8192,
+};
+
+
+/** Defines a generic media stream */
+struct stream {
+ MAGIC_DECL
+
+ struct le le; /**< Linked list element */
+ struct config_avt cfg; /**< Stream configuration */
+ struct call *call; /**< Ref. to call object */
+ struct sdp_media *sdp; /**< SDP Media line */
+ struct rtp_sock *rtp; /**< RTP Socket */
+ struct rtpkeep *rtpkeep; /**< RTP Keepalive */
+ struct rtcp_stats rtcp_stats;/**< RTCP statistics */
+ struct jbuf *jbuf; /**< Jitter Buffer for incoming RTP */
+ struct mnat_media *mns; /**< Media NAT traversal state */
+ const struct menc *menc; /**< Media encryption module */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ struct menc_media *mes; /**< Media Encryption media state */
+ struct metric metric_tx; /**< Metrics for transmit */
+ struct metric metric_rx; /**< Metrics for receiving */
+ char *cname;
+ uint32_t ssrc_rx; /**< Incoming syncronizing source */
+ uint32_t pseq; /**< Sequence number for incoming RTP */
+ int pt_enc; /**< Payload type for encoding */
+ bool rtcp; /**< Enable RTCP */
+ bool rtcp_mux; /**< RTP/RTCP multiplex supported by peer */
+ bool jbuf_started; /**< True if jitter-buffer was started */
+ stream_rtp_h *rtph; /**< Stream RTP handler */
+ stream_rtcp_h *rtcph; /**< Stream RTCP handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static inline int lostcalc(struct stream *s, uint16_t seq)
+{
+ const uint16_t delta = seq - s->pseq;
+ int lostc;
+
+ if (s->pseq == (uint32_t)-1)
+ lostc = 0;
+ else if (delta == 0)
+ return -1;
+ else if (delta < 3000)
+ lostc = delta - 1;
+ else if (delta < 0xff9c)
+ lostc = 0;
+ else
+ return -2;
+
+ s->pseq = seq;
+
+ return lostc;
+}
+
+
+static void print_rtp_stats(struct stream *s)
+{
+ info("\n%-9s Transmit: Receive:\n"
+ "packets: %7u %7u\n"
+ "avg. bitrate: %7.1f %7.1f (kbit/s)\n",
+ sdp_media_name(s->sdp),
+ s->metric_tx.n_packets, s->metric_rx.n_packets,
+ 1.0*metric_avg_bitrate(&s->metric_tx)/1000,
+ 1.0*metric_avg_bitrate(&s->metric_rx)/1000);
+
+ if (s->rtcp_stats.tx.sent || s->rtcp_stats.rx.sent) {
+
+ info("pkt.report: %7u %7u\n"
+ "lost: %7d %7d\n"
+ "jitter: %7.1f %7.1f (ms)\n",
+ s->rtcp_stats.tx.sent, s->rtcp_stats.rx.sent,
+ s->rtcp_stats.tx.lost, s->rtcp_stats.rx.lost,
+ 1.0*s->rtcp_stats.tx.jit/1000,
+ 1.0*s->rtcp_stats.rx.jit/1000);
+ }
+}
+
+
+static void stream_destructor(void *arg)
+{
+ struct stream *s = arg;
+
+ if (s->cfg.rtp_stats)
+ print_rtp_stats(s);
+
+ metric_reset(&s->metric_tx);
+ metric_reset(&s->metric_rx);
+
+ list_unlink(&s->le);
+ mem_deref(s->rtpkeep);
+ mem_deref(s->sdp);
+ mem_deref(s->mes);
+ mem_deref(s->mencs);
+ mem_deref(s->mns);
+ mem_deref(s->jbuf);
+ mem_deref(s->rtp);
+ mem_deref(s->cname);
+}
+
+
+static void rtp_recv(const struct sa *src, const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct stream *s = arg;
+ bool flush = false;
+ int err;
+
+ if (!mbuf_get_left(mb))
+ return;
+
+ if (!(sdp_media_ldir(s->sdp) & SDP_RECVONLY))
+ return;
+
+ metric_add_packet(&s->metric_rx, mbuf_get_left(mb));
+
+ if (hdr->ssrc != s->ssrc_rx) {
+ if (s->ssrc_rx) {
+ flush = true;
+ info("stream: %s: SSRC changed %x -> %x"
+ " (%u bytes from %J)\n",
+ sdp_media_name(s->sdp), s->ssrc_rx, hdr->ssrc,
+ mbuf_get_left(mb), src);
+ }
+ s->ssrc_rx = hdr->ssrc;
+ }
+
+ if (s->jbuf) {
+
+ struct rtp_header hdr2;
+ void *mb2 = NULL;
+
+ /* Put frame in Jitter Buffer */
+ if (flush)
+ jbuf_flush(s->jbuf);
+
+ err = jbuf_put(s->jbuf, hdr, mb);
+ if (err) {
+ info("%s: dropping %u bytes from %J (%m)\n",
+ sdp_media_name(s->sdp), mb->end,
+ src, err);
+ s->metric_rx.n_err++;
+ }
+
+ if (jbuf_get(s->jbuf, &hdr2, &mb2)) {
+
+ if (!s->jbuf_started)
+ return;
+
+ memset(&hdr2, 0, sizeof(hdr2));
+ }
+
+ s->jbuf_started = true;
+
+ if (lostcalc(s, hdr2.seq) > 0)
+ s->rtph(hdr, NULL, s->arg);
+
+ s->rtph(&hdr2, mb2, s->arg);
+
+ mem_deref(mb2);
+ }
+ else {
+ if (lostcalc(s, hdr->seq) > 0)
+ s->rtph(hdr, NULL, s->arg);
+
+ s->rtph(hdr, mb, s->arg);
+ }
+}
+
+
+static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg)
+{
+ struct stream *s = arg;
+ (void)src;
+
+ if (s->rtcph)
+ s->rtcph(msg, s->arg);
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ (void)rtcp_stats(s->rtp, msg->r.sr.ssrc, &s->rtcp_stats);
+ break;
+ }
+}
+
+
+static int stream_sock_alloc(struct stream *s, int af)
+{
+ struct sa laddr;
+ int tos, err;
+
+ if (!s)
+ return EINVAL;
+
+ /* we listen on all interfaces */
+ sa_init(&laddr, af);
+
+ err = rtp_listen(&s->rtp, IPPROTO_UDP, &laddr,
+ s->cfg.rtp_ports.min, s->cfg.rtp_ports.max,
+ s->rtcp, rtp_recv, rtcp_handler, s);
+ if (err)
+ return err;
+
+ tos = s->cfg.rtp_tos;
+ (void)udp_setsockopt(rtp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+ (void)udp_setsockopt(rtcp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+
+ udp_rxsz_set(rtp_sock(s->rtp), RTP_RECV_SIZE);
+
+ return 0;
+}
+
+
+int stream_alloc(struct stream **sp, const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg)
+{
+ struct stream *s;
+ int err;
+
+ if (!sp || !cfg || !call || !rtph)
+ return EINVAL;
+
+ s = mem_zalloc(sizeof(*s), stream_destructor);
+ if (!s)
+ return ENOMEM;
+
+ MAGIC_INIT(s);
+
+ s->cfg = *cfg;
+ s->call = call;
+ s->rtph = rtph;
+ s->rtcph = rtcph;
+ s->arg = arg;
+ s->pseq = -1;
+ s->rtcp = s->cfg.rtcp_enable;
+
+ err = stream_sock_alloc(s, call_af(call));
+ if (err)
+ goto out;
+
+ err = str_dup(&s->cname, cname);
+ if (err)
+ goto out;
+
+ /* Jitter buffer */
+ if (cfg->jbuf_del.min && cfg->jbuf_del.max) {
+
+ err = jbuf_alloc(&s->jbuf, cfg->jbuf_del.min,
+ cfg->jbuf_del.max);
+ if (err)
+ goto out;
+ }
+
+ err = sdp_media_add(&s->sdp, sdp_sess, name,
+ sa_port(rtp_local(s->rtp)),
+ (menc && menc->sdp_proto) ? menc->sdp_proto :
+ sdp_proto_rtpavp);
+ if (err)
+ goto out;
+
+ if (label) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "label", "%d", label);
+ }
+
+ /* RFC 5506 */
+ if (s->rtcp)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-rsize", NULL);
+
+ /* RFC 5576 */
+ if (s->rtcp) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "ssrc", "%u cname:%s",
+ rtp_sess_ssrc(s->rtp), cname);
+ }
+
+ /* RFC 5761 */
+ if (cfg->rtcp_mux)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-mux", NULL);
+
+ if (err)
+ goto out;
+
+ if (mnat) {
+ err = mnat->mediah(&s->mns, mnat_sess, IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (menc) {
+ s->menc = menc;
+ s->mencs = mem_ref(menc_sess);
+ err = menc->mediah(&s->mes, menc_sess,
+ s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (err)
+ goto out;
+
+ s->pt_enc = -1;
+
+ list_append(call_streaml(call), &s->le, s);
+
+ out:
+ if (err)
+ mem_deref(s);
+ else
+ *sp = s;
+
+ return err;
+}
+
+
+struct sdp_media *stream_sdpmedia(const struct stream *s)
+{
+ return s ? s->sdp : NULL;
+}
+
+
+static void stream_start_keepalive(struct stream *s)
+{
+ const char *rtpkeep;
+
+ if (!s)
+ return;
+
+ rtpkeep = ua_prm(call_get_ua(s->call))->rtpkeep;
+
+ s->rtpkeep = mem_deref(s->rtpkeep);
+
+ if (rtpkeep && sdp_media_rformat(s->sdp, NULL)) {
+ int err;
+ err = rtpkeep_alloc(&s->rtpkeep, rtpkeep,
+ IPPROTO_UDP, s->rtp, s->sdp);
+ if (err) {
+ warning("stream: rtpkeep_alloc failed: %m\n", err);
+ }
+ }
+}
+
+
+int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb)
+{
+ int err = 0;
+
+ if (!s)
+ return EINVAL;
+
+ if (!sa_isset(sdp_media_raddr(s->sdp), SA_ALL))
+ return 0;
+ if (sdp_media_dir(s->sdp) != SDP_SENDRECV)
+ return 0;
+
+ metric_add_packet(&s->metric_tx, mbuf_get_left(mb));
+
+ if (pt < 0)
+ pt = s->pt_enc;
+
+ if (pt >= 0) {
+ err = rtp_send(s->rtp, sdp_media_raddr(s->sdp),
+ marker, pt, ts, mb);
+ if (err)
+ s->metric_tx.n_err++;
+ }
+
+ rtpkeep_refresh(s->rtpkeep, ts);
+
+ return err;
+}
+
+
+static void stream_remote_set(struct stream *s)
+{
+ struct sa rtcp;
+
+ if (!s)
+ return;
+
+ /* RFC 5761 */
+ if (s->cfg.rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) {
+
+ if (!s->rtcp_mux)
+ info("%s: RTP/RTCP multiplexing enabled\n",
+ sdp_media_name(s->sdp));
+ s->rtcp_mux = true;
+ }
+
+ rtcp_enable_mux(s->rtp, s->rtcp_mux);
+
+ sdp_media_raddr_rtcp(s->sdp, &rtcp);
+
+ rtcp_start(s->rtp, s->cname,
+ s->rtcp_mux ? sdp_media_raddr(s->sdp): &rtcp);
+}
+
+
+void stream_update(struct stream *s)
+{
+ const struct sdp_format *fmt;
+ int err = 0;
+
+ if (!s)
+ return;
+
+ fmt = sdp_media_rformat(s->sdp, NULL);
+
+ s->pt_enc = fmt ? fmt->pt : -1;
+
+ if (sdp_media_has_media(s->sdp))
+ stream_remote_set(s);
+
+ if (s->menc && s->menc->mediah) {
+ err = s->menc->mediah(&s->mes, s->mencs, s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err) {
+ warning("stream: mediaenc update: %m\n", err);
+ }
+ }
+}
+
+
+void stream_update_encoder(struct stream *s, int pt_enc)
+{
+ if (pt_enc >= 0)
+ s->pt_enc = pt_enc;
+}
+
+
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s)
+{
+ struct jbuf_stat stat;
+ int err;
+
+ if (!s)
+ return EINVAL;
+
+ err = re_hprintf(pf, " %s:", sdp_media_name(s->sdp));
+
+ err |= jbuf_stats(s->jbuf, &stat);
+ if (err) {
+ err = re_hprintf(pf, "Jbuf stat: (not available)");
+ }
+ else {
+ err = re_hprintf(pf, "Jbuf stat: put=%u get=%u or=%u ur=%u",
+ stat.n_put, stat.n_get,
+ stat.n_overflow, stat.n_underflow);
+ }
+
+ return err;
+}
+
+
+void stream_hold(struct stream *s, bool hold)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_ldir(s->sdp, hold ? SDP_SENDONLY : SDP_SENDRECV);
+}
+
+
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx)
+{
+ if (!s)
+ return;
+
+ rtcp_set_srate(s->rtp, srate_tx, srate_rx);
+}
+
+
+void stream_send_fir(struct stream *s, bool pli)
+{
+ int err;
+
+ if (!s)
+ return;
+
+ if (pli)
+ err = rtcp_send_pli(s->rtp, s->ssrc_rx);
+ else
+ err = rtcp_send_fir(s->rtp, rtp_sess_ssrc(s->rtp));
+
+ if (err) {
+ s->metric_tx.n_err++;
+
+ warning("stream: failed to send RTCP %s: %m\n",
+ pli ? "PLI" : "FIR", err);
+ }
+}
+
+
+void stream_reset(struct stream *s)
+{
+ if (!s)
+ return;
+
+ jbuf_flush(s->jbuf);
+
+ stream_start_keepalive(s);
+}
+
+
+void stream_set_bw(struct stream *s, uint32_t bps)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_lbandwidth(s->sdp, SDP_BANDWIDTH_AS, bps / 1024);
+}
+
+
+int stream_debug(struct re_printf *pf, const struct stream *s)
+{
+ struct sa rrtcp;
+ int err;
+
+ if (!s)
+ return 0;
+
+ err = re_hprintf(pf, " %s dir=%s pt_enc=%d\n", sdp_media_name(s->sdp),
+ sdp_dir_name(sdp_media_dir(s->sdp)),
+ s->pt_enc);
+
+ sdp_media_raddr_rtcp(s->sdp, &rrtcp);
+ err |= re_hprintf(pf, " remote: %J/%J\n",
+ sdp_media_raddr(s->sdp), &rrtcp);
+
+ err |= rtp_debug(pf, s->rtp);
+ err |= jbuf_debug(pf, s->jbuf);
+
+ return err;
+}
+
+
+int stream_print(struct re_printf *pf, const struct stream *s)
+{
+ if (!s)
+ return 0;
+
+ return re_hprintf(pf, " %s=%u/%u", sdp_media_name(s->sdp),
+ s->metric_tx.cur_bitrate,
+ s->metric_rx.cur_bitrate);
+}
diff --git a/src/ua.c b/src/ua.c
new file mode 100644
index 0000000..713890c
--- /dev/null
+++ b/src/ua.c
@@ -0,0 +1,1562 @@
+/**
+ * @file src/ua.c User-Agent
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "ua"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+/** Magic number */
+#define MAGIC 0x0a0a0a0a
+#include "magic.h"
+
+
+enum {
+ MAX_CALLS = 4
+};
+
+
+/** Defines a SIP User Agent object */
+struct ua {
+ MAGIC_DECL /**< Magic number for struct ua */
+ struct ua **uap; /**< Pointer to application's ua */
+ struct le le; /**< Linked list element */
+ struct account *acc; /**< Account Parameters */
+ struct list regl; /**< List of Register clients */
+ struct list calls; /**< List of active calls (struct call) */
+ struct play *play; /**< Playback of ringtones etc. */
+ struct pl extensionv[8]; /**< Vector of SIP extensions */
+ size_t extensionc; /**< Number of SIP extensions */
+ char *cuser; /**< SIP Contact username */
+ int af; /**< Preferred Address Family */
+};
+
+struct ua_eh {
+ struct le le;
+ ua_event_h *h;
+ void *arg;
+};
+
+static struct {
+ struct config_sip *cfg; /**< SIP configuration */
+ struct list ual; /**< List of User-Agents (struct ua) */
+ struct list ehl; /**< Event handlers (struct ua_eh) */
+ struct sip *sip; /**< SIP Stack */
+ struct sip_lsnr *lsnr; /**< SIP Listener */
+ struct sipsess_sock *sock; /**< SIP Session socket */
+ struct sipevent_sock *evsock; /**< SIP Event socket */
+ struct ua *ua_cur; /**< Current User-Agent */
+ bool use_udp; /**< Use UDP transport */
+ bool use_tcp; /**< Use TCP transport */
+ bool use_tls; /**< Use TLS transport */
+ bool prefer_ipv6; /**< Force IPv6 transport */
+#ifdef USE_TLS
+ struct tls *tls; /**< TLS Context */
+#endif
+} uag = {
+ NULL,
+ LIST_INIT,
+ LIST_INIT,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ true,
+ false,
+#ifdef USE_TLS
+ NULL,
+#endif
+};
+
+
+/* prototypes */
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri);
+
+
+/* This function is called when all SIP transactions are done */
+static void exit_handler(void *arg)
+{
+ (void)arg;
+
+ re_cancel();
+}
+
+
+void ua_printf(const struct ua *ua, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!ua)
+ return;
+
+ va_start(ap, fmt);
+ info("%r@%r: %v", &ua->acc->luri.user, &ua->acc->luri.host, fmt, &ap);
+ va_end(ap);
+}
+
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...)
+{
+ struct le *le;
+ char buf[256];
+ va_list ap;
+
+ if (!ua)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* send event to all clients */
+ for (le = uag.ehl.head; le; le = le->next) {
+
+ struct ua_eh *eh = le->data;
+
+ eh->h(ua, ev, call, buf, eh->arg);
+ }
+}
+
+
+/**
+ * Start registration of a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_register(struct ua *ua)
+{
+ struct account *acc;
+ struct le *le;
+ struct uri uri;
+ char reg_uri[64];
+ char params[256] = "";
+ unsigned i;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ acc = ua->acc;
+ uri = ua->acc->luri;
+ uri.user = uri.password = pl_null;
+ if (re_snprintf(reg_uri, sizeof(reg_uri), "%H", uri_encode, &uri) < 0)
+ return ENOMEM;
+
+ if (str_isset(uag.cfg->uuid)) {
+ if (re_snprintf(params, sizeof(params),
+ ";+sip.instance=\"<urn:uuid:%s>\"",
+ uag.cfg->uuid) < 0)
+ return ENOMEM;
+ }
+
+ if (acc->regq) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";q=%s", acc->regq) < 0)
+ return ENOMEM;
+ }
+
+ if (acc->mnat && acc->mnat->ftag) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";%s", acc->mnat->ftag) < 0)
+ return ENOMEM;
+ }
+
+ ua_event(ua, UA_EVENT_REGISTERING, NULL, NULL);
+
+ for (le = ua->regl.head, i=0; le; le = le->next, i++) {
+ struct reg *reg = le->data;
+
+ err = reg_register(reg, reg_uri, params,
+ acc->regint, acc->outbound[i]);
+ if (err) {
+ DEBUG_WARNING("SIP register failed: %m\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+bool ua_isregistered(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return false;
+
+ for (le = ua->regl.head; le; le = le->next) {
+
+ const struct reg *reg = le->data;
+
+ /* it is enough if one of the registrations work */
+ if (reg_isok(reg))
+ return true;
+ }
+
+ return false;
+}
+
+
+static const char *translate_errorcode(uint16_t scode)
+{
+ switch (scode) {
+
+ case 404: return "notfound.wav";
+ case 486: return "busy.wav";
+ case 487: return NULL; /* ignore */
+ default: return "error.wav";
+ }
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *str, void *arg)
+{
+ struct ua *ua = arg;
+ const char *peeruri;
+ struct call *call2 = NULL;
+ int err;
+
+ MAGIC_CHECK(ua);
+
+ peeruri = call_peeruri(call);
+
+ /* stop any ringtones */
+ ua->play = mem_deref(ua->play);
+
+ switch (ev) {
+
+ case CALL_EVENT_INCOMING:
+ switch (ua->acc->answermode) {
+
+ case ANSWERMODE_EARLY:
+ (void)call_progress(call);
+ break;
+
+ case ANSWERMODE_AUTO:
+ (void)call_answer(call, 200);
+ break;
+
+ case ANSWERMODE_MANUAL:
+ default:
+ if (list_count(&ua->calls) > 1) {
+ (void)play_file(&ua->play,
+ "callwaiting.wav", 3);
+ }
+ else {
+ /* Alert user */
+ (void)play_file(&ua->play, "ring.wav", -1);
+ }
+
+ ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri);
+ break;
+ }
+ break;
+
+ case CALL_EVENT_RINGING:
+ (void)play_file(&ua->play, "ringback.wav", -1);
+
+ ua_event(ua, UA_EVENT_CALL_RINGING, call, peeruri);
+ break;
+
+ case CALL_EVENT_PROGRESS:
+ ua_printf(ua, "Call in-progress: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_PROGRESS, call, peeruri);
+ break;
+
+ case CALL_EVENT_ESTABLISHED:
+ ua_printf(ua, "Call established: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_ESTABLISHED, call, peeruri);
+ break;
+
+ case CALL_EVENT_CLOSED:
+ if (call_scode(call)) {
+ const char *tone;
+ tone = translate_errorcode(call_scode(call));
+ if (tone)
+ (void)play_file(&ua->play, tone, 1);
+ }
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+ break;
+
+ case CALL_EVENT_TRANSFER:
+
+ /*
+ * Create a new call to transfer target.
+ *
+ * NOTE: we will automatically connect a new call to the
+ * transfer target
+ */
+
+ ua_printf(ua, "transferring call to %s\n", str);
+
+ err = ua_call_alloc(&call2, ua, VIDMODE_ON, NULL, call,
+ call_localuri(call));
+ if (!err) {
+ struct pl pl;
+
+ pl_set_str(&pl, str);
+
+ err = call_connect(call2, &pl);
+ if (err) {
+ DEBUG_WARNING("transfer: connect error: %m\n",
+ err);
+ }
+ }
+
+ if (err) {
+ (void)call_notify_sipfrag(call, 500, "%m", err);
+ mem_deref(call2);
+ }
+ break;
+ }
+}
+
+
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri)
+{
+ struct call_prm cprm;
+
+ if (*callp) {
+ DEBUG_WARNING("call_alloc: call is already allocated\n");
+ return EALREADY;
+ }
+
+ cprm.vidmode = vidmode;
+ cprm.af = ua->af;
+
+ return call_alloc(callp, conf_config(), &ua->calls,
+ ua->acc->dispname,
+ local_uri ? local_uri : ua->acc->aor,
+ ua->acc, ua, &cprm,
+ msg, xcall, call_event_handler, ua);
+}
+
+
+static void handle_options(struct ua *ua, const struct sip_msg *msg)
+{
+ struct call *call = NULL;
+ struct mbuf *desc = NULL;
+ int err;
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL);
+ if (err) {
+ (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error");
+ return;
+ }
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ goto out;
+
+ err = sip_treplyf(NULL, NULL, uag.sip,
+ msg, true, 200, "OK",
+ "Contact: <sip:%s@%J%s>\r\n"
+ "Content-Type: application/sdp\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ ua->cuser, &msg->dst, sip_transp_param(msg->tp),
+ mbuf_get_left(desc),
+ mbuf_buf(desc),
+ mbuf_get_left(desc));
+ if (err) {
+ DEBUG_WARNING("options: sip_treplyf: %m\n", err);
+ }
+
+ out:
+ mem_deref(desc);
+ mem_deref(call);
+}
+
+
+static void ua_destructor(void *arg)
+{
+ struct ua *ua = arg;
+
+ if (ua->uap) {
+ *ua->uap = NULL;
+ ua->uap = NULL;
+ }
+
+ list_unlink(&ua->le);
+
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ list_flush(&ua->calls);
+ list_flush(&ua->regl);
+ mem_deref(ua->play);
+ mem_deref(ua->cuser);
+ mem_deref(ua->acc);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ if (pl_strcmp(&msg->met, "OPTIONS"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ handle_options(ua, msg);
+
+ return true;
+}
+
+
+static void add_extension(struct ua *ua, const char *extension)
+{
+ struct pl e;
+
+ if (ua->extensionc >= ARRAY_SIZE(ua->extensionv)) {
+ DEBUG_WARNING("maximum %u number of SIP extensions\n");
+ return;
+ }
+
+ pl_set_str(&e, extension);
+
+ ua->extensionv[ua->extensionc++] = e;
+}
+
+
+/**
+ * Allocate a SIP User-Agent
+ *
+ * @param uap Pointer to allocated User-Agent object
+ * @param aor SIP Address-of-Record (AOR)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_alloc(struct ua **uap, const char *aor)
+{
+ struct ua *ua;
+ int err;
+
+ if (!aor)
+ return EINVAL;
+
+ ua = mem_zalloc(sizeof(*ua), ua_destructor);
+ if (!ua)
+ return ENOMEM;
+
+ MAGIC_INIT(ua);
+
+ list_init(&ua->calls);
+
+#if HAVE_INET6
+ ua->af = uag.prefer_ipv6 ? AF_INET6 : AF_INET;
+#else
+ ua->af = AF_INET;
+#endif
+
+ /* generate a unique contact-user, this is needed to route
+ incoming requests when using multiple useragents */
+ err = re_sdprintf(&ua->cuser, "%p", ua);
+ if (err)
+ goto out;
+
+ /* Decode SIP address */
+
+ err = account_alloc(&ua->acc, aor);
+ if (err)
+ goto out;
+
+ if (ua->acc->sipnat) {
+ ua_printf(ua, "Using sipnat: `%s'\n", ua->acc->sipnat);
+ }
+
+ if (ua->acc->mnat) {
+ ua_printf(ua, "Using medianat `%s'\n",
+ ua->acc->mnat->id);
+
+ if (0 == str_casecmp(ua->acc->mnat->id, "ice"))
+ add_extension(ua, "ice");
+ }
+
+ if (ua->acc->menc) {
+ ua_printf(ua, "Using media encryption `%s'\n",
+ ua->acc->menc->id);
+ }
+
+ /* Register clients */
+ if (0 == str_casecmp(ua->acc->sipnat, "outbound")) {
+
+ size_t i;
+
+ add_extension(ua, "path");
+ add_extension(ua, "outbound");
+
+ if (!str_isset(uag.cfg->uuid)) {
+
+ DEBUG_WARNING("outbound requires valid UUID!\n");
+ err = ENOSYS;
+ goto out;
+ }
+
+ for (i=0; i<ARRAY_SIZE(ua->acc->outbound); i++) {
+
+ if (ua->acc->outbound[i] && ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, (int)i+1);
+ if (err)
+ break;
+ }
+ }
+ }
+ else if (ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, 0);
+ }
+ if (err)
+ goto out;
+
+ list_append(&uag.ual, &ua->le, ua);
+
+ if (ua->acc->regint) {
+ err = ua_register(ua);
+ }
+
+ if (!uag_current())
+ uag_current_set(ua);
+
+ out:
+ if (err)
+ mem_deref(ua);
+ else if (uap) {
+ *uap = ua;
+
+ ua->uap = uap;
+ }
+
+ return err;
+}
+
+
+/**
+ * Connect an outgoing call to a given SIP uri
+ *
+ * @param ua User-Agent
+ * @param callp Optional pointer to allocated call object
+ * @param from_uri Optional From uri, or NULL for default AOR
+ * @param uri SIP uri to connect to
+ * @param params Optional URI parameters
+ * @param vmode Video mode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_connect(struct ua *ua, struct call **callp,
+ const char *from_uri, const char *uri,
+ const char *params, enum vidmode vmode)
+{
+ struct call *call = NULL;
+ struct mbuf *dialbuf;
+ struct pl pl;
+ size_t len;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ len = str_len(uri);
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ if (params)
+ err |= mbuf_printf(dialbuf, "<");
+
+ /* Append sip: scheme if missing */
+ if (0 != re_regex(uri, len, "sip:"))
+ err |= mbuf_printf(dialbuf, "sip:");
+
+ err |= mbuf_write_str(dialbuf, uri);
+
+ /* Append domain if missing */
+ if (0 != re_regex(uri, len, "[^@]+@[^]+", NULL, NULL)) {
+#if HAVE_INET6
+ if (AF_INET6 == ua->acc->luri.af)
+ err |= mbuf_printf(dialbuf, "@[%r]",
+ &ua->acc->luri.host);
+ else
+#endif
+ err |= mbuf_printf(dialbuf, "@%r",
+ &ua->acc->luri.host);
+
+ /* Also append port if specified and not 5060 */
+ switch (ua->acc->luri.port) {
+
+ case 0:
+ case SIP_PORT:
+ break;
+
+ default:
+ err |= mbuf_printf(dialbuf, ":%u", ua->acc->luri.port);
+ break;
+ }
+ }
+
+ if (params) {
+ err |= mbuf_printf(dialbuf, ";%s", params);
+ }
+
+ /* Append any optional URI parameters */
+ err |= mbuf_write_pl(dialbuf, &ua->acc->luri.params);
+
+ if (params)
+ err |= mbuf_printf(dialbuf, ">");
+
+ if (err)
+ goto out;
+
+ err = ua_call_alloc(&call, ua, vmode, NULL, NULL, from_uri);
+ if (err)
+ goto out;
+
+ pl.p = (char *)dialbuf->buf;
+ pl.l = dialbuf->end;
+
+ err = call_connect(call, &pl);
+
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ out:
+ mem_deref(dialbuf);
+
+ return err;
+}
+
+
+/**
+ * Hangup the current call
+ *
+ * @param ua User-Agent
+ * @param call Call to hangup, or NULL for current call
+ * @param scode Optional status code
+ * @param reason Optional reason
+ */
+void ua_hangup(struct ua *ua, struct call *call,
+ uint16_t scode, const char *reason)
+{
+ if (!ua)
+ return;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return;
+ }
+
+ ua->play = mem_deref(ua->play);
+
+ (void)call_hangup(call, scode, reason);
+
+ mem_deref(call);
+}
+
+
+/**
+ * Answer an incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to hangup, or NULL for current call
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_answer(struct ua *ua, struct call *call)
+{
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ /* todo: put previous call on-hold (if configured) */
+
+ ua->play = mem_deref(ua->play);
+
+ return call_answer(call, 200);
+}
+
+
+int ua_print_status(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "%-42s", ua->acc->aor);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_status(pf, le->data);
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Send SIP OPTIONS message to a peer
+ *
+ * @param ua User-Agent object
+ * @param uri Peer SIP Address
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_options_send(struct ua *ua, const char *uri,
+ options_resp_h *resph, void *arg)
+{
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ err = sip_req_send(ua, "OPTIONS", uri, resph, arg,
+ "Accept: application/sdp\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ if (err) {
+ DEBUG_WARNING("send options: (%m)\n", err);
+ }
+
+ return err;
+}
+
+
+/**
+ * Get the AOR of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return AOR
+ */
+const char *ua_aor(const struct ua *ua)
+{
+ return ua ? ua->acc->aor : NULL;
+}
+
+
+/**
+ * Get the outbound SIP proxy of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Outbound SIP proxy uri
+ */
+const char *ua_outbound(const struct ua *ua)
+{
+ /* NOTE: we pick the first outbound server, should be rotated? */
+ return ua ? ua->acc->outbound[0] : NULL;
+}
+
+
+/**
+ * Get the current call object of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Current call, NULL if no active calls
+ *
+ *
+ * Current call strategy:
+ *
+ * We can only have 1 current call. The current call is the one that was
+ * added last (end of the list), which is not on-hold
+ */
+struct call *ua_call(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return NULL;
+
+ for (le = ua->calls.tail; le; le = le->prev) {
+
+ struct call *call = le->data;
+
+ /* todo: check if call is on-hold */
+
+ return call;
+ }
+
+ return NULL;
+}
+
+
+int ua_debug(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "--- %s ---\n", ua->acc->aor);
+ err |= re_hprintf(pf, " cuser: %s\n", ua->cuser);
+ err |= re_hprintf(pf, " af: %s\n", net_af2name(ua->af));
+ err |= re_hprintf(pf, " %H", ua_print_supported, ua);
+
+ err |= account_debug(pf, ua->acc);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_debug(pf, le->data);
+
+ return err;
+}
+
+
+/* One instance */
+
+
+static int add_transp_af(const struct sa *laddr)
+{
+ struct sa local;
+ int err = 0;
+
+ if (str_isset(uag.cfg->local)) {
+ err = sa_decode(&local, uag.cfg->local,
+ str_len(uag.cfg->local));
+ if (err) {
+ err = sa_set_str(&local, uag.cfg->local, 0);
+ if (err) {
+ DEBUG_WARNING("decode failed: %s\n",
+ uag.cfg->local);
+ return err;
+ }
+ }
+
+ if (!sa_isset(&local, SA_ADDR)) {
+ uint16_t port = sa_port(&local);
+ (void)sa_set_sa(&local, &laddr->u.sa);
+ sa_set_port(&local, port);
+ }
+
+ if (sa_af(laddr) != sa_af(&local))
+ return 0;
+ }
+ else {
+ sa_cpy(&local, laddr);
+ sa_set_port(&local, 0);
+ }
+
+ if (uag.use_udp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_UDP, &local);
+ if (uag.use_tcp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_TCP, &local);
+ if (err) {
+ DEBUG_WARNING("SIP Transport failed: %m\n", err);
+ return err;
+ }
+
+#ifdef USE_TLS
+ if (uag.use_tls) {
+ /* Build our SSL context*/
+ if (!uag.tls) {
+ const char *cert = NULL;
+
+ if (str_isset(uag.cfg->cert)) {
+ cert = uag.cfg->cert;
+ info("SIP Certificate: %s\n", cert);
+ }
+
+ err = tls_alloc(&uag.tls, TLS_METHOD_SSLV23,
+ cert, NULL);
+ if (err) {
+ DEBUG_WARNING("tls_alloc() failed: %m\n", err);
+ return err;
+ }
+ }
+
+ if (sa_isset(&local, SA_PORT))
+ sa_set_port(&local, sa_port(&local) + 1);
+
+ err = sip_transp_add(uag.sip, SIP_TRANSP_TLS, &local, uag.tls);
+ if (err) {
+ DEBUG_WARNING("SIP/TLS transport failed: %m\n", err);
+ return err;
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+static int ua_add_transp(void)
+{
+ int err = 0;
+
+ if (!uag.prefer_ipv6) {
+ if (sa_isset(net_laddr_af(AF_INET), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(AF_INET));
+ }
+
+#if HAVE_INET6
+ if (sa_isset(net_laddr_af(AF_INET6), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(AF_INET6));
+#endif
+
+ return err;
+}
+
+
+static bool require_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+ bool supported = false;
+ size_t i;
+ (void)msg;
+
+ for (i=0; i<ua->extensionc; i++) {
+
+ if (!pl_casecmp(&hdr->val, &ua->extensionv[i])) {
+ supported = true;
+ break;
+ }
+ }
+
+ return !supported;
+}
+
+
+/* Handle incoming calls */
+static void sipsess_conn_handler(const struct sip_msg *msg, void *arg)
+{
+ const struct sip_hdr *hdr;
+ struct ua *ua;
+ struct call *call = NULL;
+ char str[256], to_uri[256];
+ int err;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ DEBUG_WARNING("%r: UA not found: %r\n",
+ &msg->from.auri, &msg->uri.user);
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return;
+ }
+
+ /* handle multiple calls */
+ if (list_count(&ua->calls) + 1 > MAX_CALLS) {
+ info("ua: rejected call from %r (maximum %d calls)\n",
+ &msg->from.auri, MAX_CALLS);
+ (void)sip_treply(NULL, uag.sip, msg, 486, "Busy Here");
+ return;
+ }
+
+ /* Handle Require: header, check for any required extensions */
+ hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_REQUIRE,
+ require_handler, ua);
+ if (hdr) {
+ info("ua: call from %r rejected with 420"
+ " -- option-tag '%r' not supported\n",
+ &msg->from.auri, &hdr->val);
+
+ (void)sip_treplyf(NULL, NULL, uag.sip, msg, false,
+ 420, "Bad Extension",
+ "Unsupported: %r\r\n"
+ "Content-Length: 0\r\n\r\n",
+ &hdr->val);
+ return;
+ }
+
+ (void)pl_strcpy(&msg->to.auri, to_uri, sizeof(to_uri));
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, msg, NULL, to_uri);
+ if (err) {
+ DEBUG_WARNING("call_alloc: %m\n", err);
+ goto error;
+ }
+
+ err = call_accept(call, uag.sock, msg);
+ if (err)
+ goto error;
+
+ return;
+
+ error:
+ mem_deref(call);
+ (void)re_snprintf(str, sizeof(str), "Error (%m)", err);
+ (void)sip_treply(NULL, uag.sip, msg, 500, str);
+}
+
+
+static void net_change_handler(void *arg)
+{
+ (void)arg;
+
+ info("IP-address changed: %j\n", net_laddr_af(AF_INET));
+
+ (void)uag_reset_transp(true, true);
+}
+
+
+static int cmd_quit(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "Quit\n");
+
+ ua_stop_all(false);
+
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+ {'q', 0, "Quit", cmd_quit },
+};
+
+
+/**
+ * Initialise the User-Agents
+ *
+ * @param software SIP User-Agent string
+ * @param udp Enable UDP transport
+ * @param tcp Enable TCP transport
+ * @param tls Enable TLS transport
+ * @param prefer_ipv6 Prefer IPv6 flag
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_init(const char *software, bool udp, bool tcp, bool tls,
+ bool prefer_ipv6)
+{
+ struct config *cfg = conf_config();
+ uint32_t bsize;
+ int err;
+
+ uag.cfg = &cfg->sip;
+ bsize = cfg->sip.trans_bsize;
+ ui_init(&cfg->input);
+
+ play_init();
+
+ /* Initialise Network */
+ err = net_init(&cfg->net, prefer_ipv6 ? AF_INET6 : AF_INET);
+ if (err) {
+ DEBUG_WARNING("network init failed: %m\n", err);
+ return err;
+ }
+
+ uag.use_udp = udp;
+ uag.use_tcp = tcp;
+ uag.use_tls = tls;
+ uag.prefer_ipv6 = prefer_ipv6;
+
+ list_init(&uag.ual);
+
+ err = sip_alloc(&uag.sip, net_dnsc(), bsize, bsize, bsize,
+ software, exit_handler, NULL);
+ if (err) {
+ DEBUG_WARNING("sip stack failed: %m\n", err);
+ goto out;
+ }
+
+ err = ua_add_transp();
+ if (err)
+ goto out;
+
+ err = sip_listen(&uag.lsnr, uag.sip, true, request_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipsess_listen(&uag.sock, uag.sip, bsize,
+ sipsess_conn_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipevent_listen(&uag.evsock, uag.sip, bsize, bsize, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = cmd_register(cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ goto out;
+
+ net_change(60, net_change_handler, NULL);
+
+ out:
+ if (err) {
+ DEBUG_WARNING("init failed (%m)\n", err);
+ ua_close();
+ }
+ return err;
+}
+
+
+/**
+ * Close all active User-Agents
+ */
+void ua_close(void)
+{
+ cmd_unregister(cmdv);
+ net_close();
+ play_close();
+
+ uag.evsock = mem_deref(uag.evsock);
+ uag.sock = mem_deref(uag.sock);
+ uag.lsnr = mem_deref(uag.lsnr);
+ uag.sip = mem_deref(uag.sip);
+
+#ifdef USE_TLS
+ uag.tls = mem_deref(uag.tls);
+#endif
+
+ list_flush(&uag.ual);
+ list_flush(&uag.ehl);
+}
+
+
+/**
+ * Stop all User-Agents
+ *
+ * @param forced True to force, otherwise false
+ */
+void ua_stop_all(bool forced)
+{
+ module_app_unload();
+
+ if (!list_isempty(&uag.ual)) {
+ const uint32_t n = list_count(&uag.ual);
+ info("Stopping %u useragent%s.. %s\n",
+ n, n==1 ? "" : "s", forced ? "(Forced)" : "");
+ }
+
+ if (forced)
+ sipsess_close_all(uag.sock);
+ else
+ list_flush(&uag.ual);
+
+ sip_close(uag.sip, forced);
+}
+
+
+/**
+ * Reset the SIP transports for all User-Agents
+ *
+ * @param reg True to reset registration
+ * @param reinvite True to update active calls
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uag_reset_transp(bool reg, bool reinvite)
+{
+ struct le *le;
+ int err;
+
+ /* Update SIP transports */
+ sip_transp_flush(uag.sip);
+
+ (void)net_check();
+ err = ua_add_transp();
+ if (err)
+ return err;
+
+ /* Re-REGISTER all User-Agents */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (reg && ua->acc->regint) {
+ err |= ua_register(ua);
+ }
+
+ /* update all active calls */
+ if (reinvite) {
+ struct le *lec;
+
+ for (lec = ua->calls.head; lec; lec = lec->next) {
+ struct call *call = lec->data;
+
+ err |= call_reset_transp(call);
+ }
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Print the SIP Status for all User-Agents
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_print_sip_status(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return sip_debug(pf, uag.sip);
+}
+
+
+/**
+ * Print all calls for a given User-Agent
+ */
+int ua_print_calls(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err = 0;
+
+ err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n",
+ list_count(&ua->calls));
+
+ for (le = ua->calls.head; le; le = le->next) {
+
+ const struct call *call = le->data;
+
+ err |= re_hprintf(pf, " %H\n", call_info, call);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Get the global SIP Stack
+ *
+ * @return SIP Stack
+ */
+struct sip *uag_sip(void)
+{
+ return uag.sip;
+}
+
+
+/**
+ * Get the global SIP Session socket
+ *
+ * @return SIP Session socket
+ */
+struct sipsess_sock *uag_sipsess_sock(void)
+{
+ return uag.sock;
+}
+
+
+/**
+ * Get the global SIP Event socket
+ *
+ * @return SIP Event socket
+ */
+struct sipevent_sock *uag_sipevent_sock(void)
+{
+ return uag.evsock;
+}
+
+
+struct tls *uag_tls(void)
+{
+#ifdef USE_TLS
+ return uag.tls;
+#else
+ return NULL;
+#endif
+}
+
+
+/**
+ * Get the name of the User-Agent event
+ *
+ * @param ev User-Agent event
+ *
+ * @return Name of the event
+ */
+const char *uag_event_str(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING: return "REGISTERING";
+ case UA_EVENT_REGISTER_OK: return "REGISTER_OK";
+ case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL";
+ case UA_EVENT_UNREGISTERING: return "UNREGISTERING";
+ case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING";
+ case UA_EVENT_CALL_RINGING: return "CALL_RINGING";
+ case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS";
+ case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED";
+ case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED";
+ default: return "?";
+ }
+}
+
+
+/**
+ * Get the current SIP socket file descriptor for a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return File descriptor, or -1 if not available
+ */
+int ua_sipfd(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return -1;
+
+ for (le = ua->regl.head; le; le = le->next) {
+
+ struct reg *reg = le->data;
+ int fd;
+
+ fd = reg_sipfd(reg);
+ if (fd != -1)
+ return fd;
+ }
+
+ return -1;
+}
+
+
+/**
+ * Find the correct UA from the contact user
+ *
+ * @param cuser Contact username
+ *
+ * @return Matching UA if found, NULL if not found
+ */
+struct ua *uag_find(const struct pl *cuser)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_strcasecmp(cuser, ua->cuser))
+ return ua;
+ }
+
+ /* Try also matching by AOR, for better interop */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_casecmp(cuser, &ua->acc->luri.user))
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) from an Address-of-Record (AOR)
+ *
+ * @param aor Address-of-Record string
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_aor(const char *aor)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (str_isset(aor) && str_cmp(ua->acc->aor, aor))
+ continue;
+
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) which has certain address parameter and/or value
+ *
+ * @param name SIP Address parameter name
+ * @param value SIP Address parameter value (optional)
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_param(const char *name, const char *value)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+ struct sip_addr *laddr = account_laddr(ua->acc);
+ struct pl val;
+
+ if (value) {
+
+ if (0 == sip_param_decode(&laddr->params, name, &val)
+ &&
+ 0 == pl_strcasecmp(&val, value)) {
+ return ua;
+ }
+ }
+ else {
+ if (0 == sip_param_exists(&laddr->params, name, &val))
+ return ua;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the contact user of a User-Agent (UA)
+ *
+ * @param ua User-Agent
+ *
+ * @return Contact user
+ */
+const char *ua_cuser(const struct ua *ua)
+{
+ return ua ? ua->cuser : NULL;
+}
+
+
+struct list *uag_list(void)
+{
+ return &uag.ual;
+}
+
+
+/**
+ * Return list of methods supported by the UA
+ *
+ * @return String of supported methods
+ */
+const char *uag_allowed_methods(void)
+{
+ return "INVITE,ACK,BYE,CANCEL,OPTIONS,REFER,NOTIFY,SUBSCRIBE,INFO";
+}
+
+
+int ua_print_supported(struct re_printf *pf, const struct ua *ua)
+{
+ size_t i;
+ int err;
+
+ err = re_hprintf(pf, "Supported:");
+
+ for (i=0; i<ua->extensionc; i++) {
+ err |= re_hprintf(pf, "%s%r",
+ i==0 ? " " : ",", &ua->extensionv[i]);
+ }
+
+ err |= re_hprintf(pf, "\r\n");
+
+ return err;
+}
+
+
+struct account *ua_prm(const struct ua *ua)
+{
+ return ua ? ua->acc : NULL;
+}
+
+
+struct list *ua_calls(const struct ua *ua)
+{
+ return ua ? (struct list *)&ua->calls : NULL;
+}
+
+
+static void eh_destructor(void *arg)
+{
+ struct ua_eh *eh = arg;
+ list_unlink(&eh->le);
+}
+
+
+int uag_event_register(ua_event_h *h, void *arg)
+{
+ struct ua_eh *eh;
+
+ if (!h)
+ return EINVAL;
+
+ eh = mem_zalloc(sizeof(*eh), eh_destructor);
+ if (!eh)
+ return ENOMEM;
+
+ eh->h = h;
+ eh->arg = arg;
+
+ list_append(&uag.ehl, &eh->le, eh);
+
+ return 0;
+}
+
+
+void uag_event_unregister(ua_event_h *h)
+{
+ struct le *le;
+
+ for (le = uag.ehl.head; le; le = le->next) {
+
+ struct ua_eh *eh = le->data;
+
+ if (eh->h == h) {
+ mem_deref(eh);
+ break;
+ }
+ }
+}
+
+
+void uag_current_set(struct ua *ua)
+{
+ uag.ua_cur = ua;
+}
+
+
+struct ua *uag_current(void)
+{
+ if (list_isempty(uag_list()))
+ return NULL;
+
+ return uag.ua_cur;
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..2d69fd7
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,185 @@
+/**
+ * @file ui.c User Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** User Interface */
+struct ui {
+ struct le le;
+ const char *name;
+ struct ui_st *st; /* only one instance */
+ ui_output_h *outputh;
+ struct cmd_ctx *ctx;
+};
+
+static struct list uil; /**< List of UIs (struct ui) */
+static struct config_input input_cfg;
+
+
+static void ui_handler(char key, struct re_printf *pf, void *arg)
+{
+ struct ui *ui = arg;
+
+ (void)cmd_process(ui ? &ui->ctx : NULL, key, pf);
+}
+
+
+static void destructor(void *arg)
+{
+ struct ui *ui = arg;
+
+ list_unlink(&ui->le);
+ mem_deref(ui->st);
+ mem_deref(ui->ctx);
+}
+
+
+static int stdout_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ if (1 != fwrite(p, size, 1, stdout))
+ return ENOMEM;
+
+ return 0;
+}
+
+
+/**
+ * Register a new User-Interface (UI) module
+ *
+ * @param uip Pointer to allocated UI module
+ * @param name Name of the UI module
+ * @param alloch UI allocation handler
+ * @param outh UI output handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ui_register(struct ui **uip, const char *name,
+ ui_alloc_h *alloch, ui_output_h *outh)
+{
+ struct ui *ui;
+ int err = 0;
+
+ if (!uip)
+ return EINVAL;
+
+ ui = mem_zalloc(sizeof(*ui), destructor);
+ if (!ui)
+ return ENOMEM;
+
+ list_append(&uil, &ui->le, ui);
+
+ ui->name = name;
+ ui->outputh = outh;
+
+ if (alloch) {
+ struct ui_prm prm;
+
+ prm.device = input_cfg.device;
+ prm.port = input_cfg.port;
+
+ err = alloch(&ui->st, &prm, ui_handler, ui);
+ if (err) {
+ warning("ui: register: module '%s' failed (%m)\n",
+ ui->name, err);
+ }
+ }
+
+ if (err)
+ mem_deref(ui);
+ else
+ *uip = ui;
+
+ return err;
+}
+
+
+/**
+ * Send input to the UI subsystem
+ *
+ * @param key Input character
+ */
+void ui_input(char key)
+{
+ struct re_printf pf;
+
+ pf.vph = stdout_handler;
+ pf.arg = NULL;
+
+ ui_handler(key, &pf, list_ledata(uil.head));
+}
+
+
+/**
+ * Send an input string to the UI subsystem
+ *
+ * @param str Input string
+ */
+void ui_input_str(const char *str)
+{
+ struct re_printf pf;
+ struct pl pl;
+
+ if (!str)
+ return;
+
+ pf.vph = stdout_handler;
+ pf.arg = NULL;
+
+ pl_set_str(&pl, str);
+
+ (void)ui_input_pl(&pf, &pl);
+}
+
+
+int ui_input_pl(struct re_printf *pf, const struct pl *pl)
+{
+ struct cmd_ctx *ctx = NULL;
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl)
+ return EINVAL;
+
+ for (i=0; i<pl->l; i++) {
+ err |= cmd_process(&ctx, pl->p[i], pf);
+ }
+
+ if (pl->l > 1 && ctx)
+ err |= cmd_process(&ctx, '\n', pf);
+
+ return err;
+}
+
+
+/**
+ * Send output to all modules registered in the UI subsystem
+ *
+ * @param str Output string
+ */
+void ui_output(const char *str)
+{
+ struct le *le;
+
+ for (le = uil.head; le; le = le->next) {
+ const struct ui *ui = le->data;
+
+ if (ui->outputh)
+ ui->outputh(ui->st, str);
+ }
+}
+
+
+void ui_init(const struct config_input *cfg)
+{
+ if (!cfg)
+ return;
+
+ input_cfg = *cfg;
+}
diff --git a/src/vidcodec.c b/src/vidcodec.c
new file mode 100644
index 0000000..7624ae7
--- /dev/null
+++ b/src/vidcodec.c
@@ -0,0 +1,81 @@
+/**
+ * @file vidcodec.c Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct list vidcodecl;
+
+
+/**
+ * Register a Video Codec
+ *
+ * @param vc Video Codec
+ */
+void vidcodec_register(struct vidcodec *vc)
+{
+ if (!vc)
+ return;
+
+ list_append(&vidcodecl, &vc->le, vc);
+
+ info("vidcodec: %s\n", vc->name);
+}
+
+
+/**
+ * Unregister a Video Codec
+ *
+ * @param vc Video Codec
+ */
+void vidcodec_unregister(struct vidcodec *vc)
+{
+ if (!vc)
+ return;
+
+ list_unlink(&vc->le);
+}
+
+
+/**
+ * Find a Video Codec by name
+ *
+ * @param name Name of the Video Codec to find
+ * @param variant Codec Variant
+ *
+ * @return Matching Video Codec if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find(const char *name, const char *variant)
+{
+ struct le *le;
+
+ for (le=vidcodecl.head; le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (variant && 0 != str_casecmp(variant, vc->variant))
+ continue;
+
+ return vc;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the list of Video Codecs
+ *
+ * @return List of Video Codecs
+ */
+struct list *vidcodec_list(void)
+{
+ return &vidcodecl;
+}
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..f7eb032
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,1073 @@
+/**
+ * @file src/video.c Video stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ *
+ * \ref GenericVideoStream
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "video"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Magic number */
+#define MAGIC 0x00070d10
+#include "magic.h"
+
+
+enum {
+ SRATE = 90000,
+ MAX_MUTED_FRAMES = 3,
+};
+
+
+/**
+ * \page GenericVideoStream Generic Video Stream
+ *
+ * Implements a generic video stream. The application can allocate multiple
+ * instances of a video stream, mapping it to a particular SDP media line.
+ * The video object has a Video Display and Source, and a video encoder
+ * and decoder. A particular video object is mapped to a generic media
+ * stream object.
+ *
+ *<pre>
+ * recv send
+ * | /|\
+ * \|/ |
+ * .---------. .-------.
+ * | video |--->|encoder|
+ * | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .-------. .-------.
+ * |Video | |Video |
+ * |Display| |Source |
+ * '-------' '-------'
+ *</pre>
+ */
+
+/**
+ * Video stream - transmitter/encoder direction
+
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .--------. .- - - - -. .---------. .---------.
+ | ._O_. | | ! ! | | | |
+ | |___|-->| vidsrc |-->! vidconv !-->| vidfilt |-->| encoder |---> RTP
+ | | | ! ! | | | |
+ ' '--------' '- - - - -' '---------' '---------'
+ (optional)
+ \endverbatim
+ */
+struct vtx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current Video encoder */
+ struct videnc_state *enc; /**< Video encoder state */
+ struct vidsrc_prm vsrc_prm; /**< Video source parameters */
+ struct vidsz vsrc_size; /**< Video source size */
+ struct vidsrc_st *vsrc; /**< Video source */
+ struct lock *lock; /**< Lock for encoder */
+ struct vidframe *frame; /**< Source frame */
+ struct vidframe *mute_frame; /**< Frame with muted video */
+ struct mbuf *mb; /**< Packetization buffer */
+ struct list filtl; /**< Filters in encoding order */
+ char device[64];
+ int muted_frames; /**< # of muted frames sent */
+ uint32_t ts_tx; /**< Outgoing RTP timestamp */
+ bool picup; /**< Send picture update */
+ bool muted; /**< Muted flag */
+ int frames; /**< Number of frames sent */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/**
+ * Video stream - receiver/decoder direction
+
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .~~~~~~~~. .--------. .---------. .---------.
+ | _o_ | | | | | | |
+ | | |<--| vidisp |<--| vidfilt |<--| decoder |<--- RTP
+ | /'\ | | | | | | |
+ '~~~~~~~~' '--------' '---------' '---------'
+
+ \endverbatim
+
+ */
+struct vrx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current video decoder */
+ struct viddec_state *dec; /**< Video decoder state */
+ struct vidisp_prm vidisp_prm; /**< Video display parameters */
+ struct vidisp_st *vidisp; /**< Video display */
+ struct lock *lock; /**< Lock for decoder */
+ struct list filtl; /**< Filters in decoding order */
+ enum vidorient orient; /**< Display orientation */
+ char device[64];
+ bool fullscreen; /**< Fullscreen flag */
+ int pt_rx; /**< Incoming RTP payload type */
+ int frames; /**< Number of frames received */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/** Generic Video stream */
+struct video {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct config_video cfg;/**< Video configuration */
+ struct stream *strm; /**< Generic media stream */
+ struct vtx vtx; /**< Transmit/encoder direction */
+ struct vrx vrx; /**< Receive/decoder direction */
+ struct tmr tmr; /**< Timer for frame-rate estimation */
+ char *peer; /**< Peer URI */
+ bool nack_pli; /**< Send NACK/PLI to peer */
+};
+
+
+static void video_destructor(void *arg)
+{
+ struct video *v = arg;
+ struct vtx *vtx = &v->vtx;
+ struct vrx *vrx = &v->vrx;
+
+ /* transmit */
+ mem_deref(vtx->vsrc);
+ lock_write_get(vtx->lock);
+ mem_deref(vtx->frame);
+ mem_deref(vtx->mute_frame);
+ mem_deref(vtx->enc);
+ mem_deref(vtx->mb);
+ list_flush(&vtx->filtl);
+ lock_rel(vtx->lock);
+ mem_deref(vtx->lock);
+
+ /* receive */
+ lock_write_get(vrx->lock);
+ mem_deref(vrx->dec);
+ mem_deref(vrx->vidisp);
+ list_flush(&vrx->filtl);
+ lock_rel(vrx->lock);
+ mem_deref(vrx->lock);
+
+ tmr_cancel(&v->tmr);
+ mem_deref(v->strm);
+ mem_deref(v->peer);
+}
+
+
+static int get_fps(const struct video *v)
+{
+ const char *attr;
+
+ /* RFC4566 */
+ attr = sdp_media_rattr(stream_sdpmedia(v->strm), "framerate");
+ if (attr) {
+ /* NOTE: fractional values are ignored */
+ const double fps = atof(attr);
+ return (int)fps;
+ }
+ else
+ return v->cfg.fps;
+}
+
+
+static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len, void *arg)
+{
+ struct vtx *tx = arg;
+ int err = 0;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (hdr_len) err |= mbuf_write_mem(tx->mb, hdr, hdr_len);
+ if (pld_len) err |= mbuf_write_mem(tx->mb, pld, pld_len);
+
+ tx->mb->pos = STREAM_PRESZ;
+
+ if (!err) {
+ err = stream_send(tx->video->strm, marker, -1,
+ tx->ts_tx, tx->mb);
+ }
+
+ return err;
+}
+
+
+/**
+ * Encode video and send via RTP stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param vtx Video transmit object
+ * @param frame Video frame to send
+ */
+static void encode_rtp_send(struct vtx *vtx, struct vidframe *frame)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!vtx->enc)
+ return;
+
+ lock_write_get(vtx->lock);
+
+ /* Convert image */
+ if (frame->fmt != VID_FMT_YUV420P) {
+
+ vtx->vsrc_size = frame->size;
+
+ if (!vtx->frame) {
+
+ err = vidframe_alloc(&vtx->frame, VID_FMT_YUV420P,
+ &vtx->vsrc_size);
+ if (err)
+ goto unlock;
+ }
+
+ vidconv(vtx->frame, frame, 0);
+ frame = vtx->frame;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vtx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf && st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ unlock:
+ lock_rel(vtx->lock);
+
+ if (err)
+ return;
+
+ /* Encode the whole picture frame */
+ err = vtx->vc->ench(vtx->enc, vtx->picup, frame, packet_handler, vtx);
+ if (err) {
+ DEBUG_WARNING("encode: %m\n", err);
+ return;
+ }
+
+ vtx->ts_tx += (SRATE/vtx->vsrc_prm.fps);
+ vtx->picup = false;
+}
+
+
+/**
+ * Read frames from video source
+ *
+ * @param frame Video frame
+ * @param arg Handler argument
+ *
+ * @note This function has REAL-TIME properties
+ */
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ ++vtx->frames;
+
+ /* Is the video muted? If so insert video mute image */
+ if (vtx->muted)
+ frame = vtx->mute_frame;
+
+ if (vtx->muted && vtx->muted_frames >= MAX_MUTED_FRAMES)
+ return;
+
+ /* Encode and send */
+ encode_rtp_send(vtx, frame);
+ vtx->muted_frames++;
+}
+
+
+static void vidsrc_error_handler(int err, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ DEBUG_WARNING("Video-source error: %m\n", err);
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+}
+
+
+static int vtx_alloc(struct vtx *vtx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vtx->lock);
+ if (err)
+ return err;
+
+ vtx->mb = mbuf_alloc(STREAM_PRESZ + 512);
+ if (!vtx->mb)
+ return ENOMEM;
+
+ vtx->video = video;
+ vtx->ts_tx = 160;
+
+ str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device));
+
+ return err;
+}
+
+
+static int vrx_alloc(struct vrx *vrx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vrx->lock);
+ if (err)
+ return err;
+
+ vrx->video = video;
+ vrx->pt_rx = -1;
+ vrx->orient = VIDORIENT_PORTRAIT;
+
+ str_ncpy(vrx->device, video->cfg.disp_dev, sizeof(vrx->device));
+
+ return err;
+}
+
+
+/**
+ * Decode incoming RTP packets using the Video decoder
+ *
+ * NOTE: mb=NULL if no packet received
+ *
+ * @param vrx Video receive object
+ * @param hdr RTP Header
+ * @param mb Buffer with RTP payload
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int video_stream_decode(struct vrx *vrx, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct video *v = vrx->video;
+ struct vidframe frame;
+ struct le *le;
+ int err = 0;
+
+ if (!hdr || !mbuf_get_left(mb))
+ return 0;
+
+ lock_write_get(vrx->lock);
+
+ /* No decoder set */
+ if (!vrx->dec) {
+ DEBUG_WARNING("No video decoder!\n");
+ goto out;
+ }
+
+ frame.data[0] = NULL;
+ err = vrx->vc->dech(vrx->dec, &frame, hdr->m, hdr->seq, mb);
+ if (err) {
+
+ if (err != EPROTO) {
+ DEBUG_WARNING("%s decode error"
+ " (seq=%u, %u bytes): %m\n",
+ vrx->vc->name, hdr->seq,
+ mbuf_get_left(mb), err);
+ }
+
+ /* send RTCP FIR to peer */
+ stream_send_fir(v->strm, v->nack_pli);
+
+ /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */
+
+ goto out;
+ }
+
+ /* Got a full picture-frame? */
+ if (!vidframe_isvalid(&frame))
+ goto out;
+
+ /* Process video frame through all Video Filters */
+ for (le = vrx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf && st->vf->dech)
+ err |= st->vf->dech(st, &frame);
+ }
+
+ err = vidisp_display(vrx->vidisp, v->peer, &frame);
+
+ ++vrx->frames;
+
+out:
+ lock_rel(vrx->lock);
+
+ return err;
+}
+
+
+static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(v->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Video decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ v->vrx.pt_rx = pt_new;
+
+ return video_decoder_set(v, lc->data, lc->pt, lc->rparams);
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct video *v = arg;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Video payload-type changed? */
+ if (hdr->pt == v->vrx.pt_rx)
+ goto out;
+
+ err = pt_handler(v, v->vrx.pt_rx, hdr->pt);
+ if (err)
+ return;
+
+ out:
+ (void)video_stream_decode(&v->vrx, hdr, mb);
+}
+
+
+static void rtcp_handler(struct rtcp_msg *msg, void *arg)
+{
+ struct video *v = arg;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_FIR:
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_PSFB:
+ if (msg->hdr.count == RTCP_PSFB_PLI)
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_RTPFB:
+ if (msg->hdr.count == RTCP_RTPFB_GNACK)
+ v->vtx.picup = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int vtx_print_pipeline(struct re_printf *pf, const struct vtx *vtx)
+{
+ struct le *le;
+ struct vidsrc *vs;
+ int err;
+
+ if (!vtx)
+ return 0;
+
+ vs = vidsrc_get(vtx->vsrc);
+
+ err = re_hprintf(pf, "video tx pipeline: %10s",
+ vs ? vs->name : "src");
+
+ for (le = list_head(&vtx->filtl); le; le = le->next) {
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= re_hprintf(pf, " ---> %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ vtx->vc ? vtx->vc->name : "encoder");
+
+ return err;
+}
+
+
+static int vrx_print_pipeline(struct re_printf *pf, const struct vrx *vrx)
+{
+ struct le *le;
+ struct vidisp *vd;
+ int err;
+
+ if (!vrx)
+ return 0;
+
+ vd = vidisp_get(vrx->vidisp);
+
+ err = re_hprintf(pf, "video rx pipeline: %10s",
+ vd ? vd->name : "disp");
+
+ for (le = list_head(&vrx->filtl); le; le = le->next) {
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf->dech)
+ err |= re_hprintf(pf, " <--- %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ vrx->vc ? vrx->vc->name : "decoder");
+
+ return err;
+}
+
+
+int video_alloc(struct video **vp, 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,
+ const char *content, const struct list *vidcodecl)
+{
+ struct video *v;
+ struct le *le;
+ int err = 0;
+
+ if (!vp || !cfg)
+ return EINVAL;
+
+ v = mem_zalloc(sizeof(*v), video_destructor);
+ if (!v)
+ return ENOMEM;
+
+ MAGIC_INIT(v);
+
+ v->cfg = cfg->video;
+ tmr_init(&v->tmr);
+
+ err = stream_alloc(&v->strm, &cfg->avt, call, sdp_sess, "video", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, rtcp_handler, v);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max >= AUDIO_BANDWIDTH) {
+ stream_set_bw(v->strm, cfg->avt.rtp_bw.max - AUDIO_BANDWIDTH);
+ }
+
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "framerate", "%d", v->cfg.fps);
+
+ /* RFC 4585 */
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "rtcp-fb", "* nack pli");
+
+ /* RFC 4796 */
+ if (content) {
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "content", "%s", content);
+ }
+
+ if (err)
+ goto out;
+
+ err = vtx_alloc(&v->vtx, v);
+ err |= vrx_alloc(&v->vrx, v);
+ if (err)
+ goto out;
+
+ /* Video codecs */
+ for (le = list_head(vidcodecl); le; le = le->next) {
+ struct vidcodec *vc = le->data;
+ err |= sdp_format_add(NULL, stream_sdpmedia(v->strm), false,
+ vc->pt, vc->name, 90000, 1,
+ vc->fmtp_ench, vc->fmtp_cmph, vc, false,
+ "%s", vc->fmtp);
+ }
+
+ /* Video filters */
+ for (le = list_head(vidfilt_list()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ err |= vidfilt_enc_append(&v->vtx.filtl, &ctx, vf);
+ err |= vidfilt_dec_append(&v->vrx.filtl, &ctx, vf);
+ if (err) {
+ DEBUG_WARNING("video-filter '%s' failed (%m)\n",
+ vf->name, err);
+ break;
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(v);
+ else
+ *vp = v;
+
+ return err;
+}
+
+
+static void vidisp_resize_handler(const struct vidsz *sz, void *arg)
+{
+ struct vrx *vrx = arg;
+ (void)vrx;
+
+ info("video: display resized: %u x %u\n", sz->w, sz->h);
+
+ /* XXX: update wanted picturesize and send re-invite to peer */
+}
+
+
+/* Set the video display - can be called multiple times */
+static int set_vidisp(struct vrx *vrx)
+{
+ struct vidisp *vd;
+
+ vrx->vidisp = mem_deref(vrx->vidisp);
+ vrx->vidisp_prm.view = NULL;
+
+ vd = (struct vidisp *)vidisp_find(vrx->video->cfg.disp_mod);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(&vrx->vidisp, vd, &vrx->vidisp_prm, vrx->device,
+ vidisp_resize_handler, vrx);
+}
+
+
+/* Set the encoder format - can be called multiple times */
+static int set_encoder_format(struct vtx *vtx, const char *src,
+ const char *dev, struct vidsz *size)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(src);
+ int err;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx->vsrc_size = *size;
+ vtx->vsrc_prm.fps = get_fps(vtx->video);
+ vtx->vsrc_prm.orient = VIDORIENT_PORTRAIT;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ err = vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev, vidsrc_frame_handler,
+ vidsrc_error_handler, vtx);
+ if (err) {
+ info("video: no video source '%s': %m\n", src, err);
+ return err;
+ }
+
+ vtx->mute_frame = mem_deref(vtx->mute_frame);
+ err = vidframe_alloc(&vtx->mute_frame, VID_FMT_YUV420P, size);
+ if (err)
+ return err;
+
+ vidframe_fill(vtx->mute_frame, 0xff, 0xff, 0xff);
+
+ return err;
+}
+
+
+enum {TMR_INTERVAL = 5};
+static void tmr_handler(void *arg)
+{
+ struct video *v = arg;
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ /* Estimate framerates */
+ v->vtx.efps = v->vtx.frames / TMR_INTERVAL;
+ v->vrx.efps = v->vrx.frames / TMR_INTERVAL;
+
+ v->vtx.frames = 0;
+ v->vrx.frames = 0;
+}
+
+
+int video_start(struct video *v, const char *peer)
+{
+ struct vidsz size;
+ int err;
+
+ if (!v)
+ return EINVAL;
+
+ if (peer) {
+ mem_deref(v->peer);
+ err = str_dup(&v->peer, peer);
+ if (err)
+ return err;
+ }
+
+ stream_set_srate(v->strm, SRATE, SRATE);
+
+ err = set_vidisp(&v->vrx);
+ if (err) {
+ DEBUG_WARNING("could not set vidisp '%s': %m\n",
+ v->vrx.device, err);
+ }
+
+ size.w = v->cfg.width;
+ size.h = v->cfg.height;
+ err = set_encoder_format(&v->vtx, v->cfg.src_mod,
+ v->vtx.device, &size);
+ if (err) {
+ DEBUG_WARNING("could not set encoder format to"
+ " [%u x %u] %m\n",
+ size.w, size.h, err);
+ }
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ if (v->vtx.vc && v->vrx.vc) {
+ info("%H%H",
+ vtx_print_pipeline, &v->vtx,
+ vrx_print_pipeline, &v->vrx);
+ }
+
+ return 0;
+}
+
+
+void video_stop(struct video *v)
+{
+ if (!v)
+ return;
+
+ v->vtx.vsrc = mem_deref(v->vtx.vsrc);
+}
+
+
+/**
+ * Mute the video stream
+ *
+ * @param v Video stream
+ * @param muted True to mute, false to un-mute
+ */
+void video_mute(struct video *v, bool muted)
+{
+ struct vtx *vtx;
+
+ if (!v)
+ return;
+
+ vtx = &v->vtx;
+
+ vtx->muted = muted;
+ vtx->muted_frames = 0;
+ vtx->picup = true;
+
+ video_update_picture(v);
+}
+
+
+static int vidisp_update(struct vrx *vrx)
+{
+ struct vidisp *vd = vidisp_get(vrx->vidisp);
+ int err = 0;
+
+ if (vd->updateh) {
+ err = vd->updateh(vrx->vidisp, vrx->fullscreen,
+ vrx->orient, NULL);
+ }
+
+ return err;
+}
+
+
+/**
+ * Enable video display fullscreen
+ *
+ * @param v Video stream
+ * @param fs True for fullscreen, otherwise false
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_fullscreen(struct video *v, bool fs)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vrx.fullscreen = fs;
+
+ return vidisp_update(&v->vrx);
+}
+
+
+static void vidsrc_update(struct vtx *vtx, const char *dev)
+{
+ struct vidsrc *vs = vidsrc_get(vtx->vsrc);
+
+ if (vs && vs->updateh)
+ vs->updateh(vtx->vsrc, &vtx->vsrc_prm, dev);
+}
+
+
+/**
+ * Set the orientation of the Video source and display
+ *
+ * @param v Video stream
+ * @param orient Video orientation (enum vidorient)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_orient(struct video *v, int orient)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vtx.vsrc_prm.orient = v->vrx.orient = orient;
+ vidsrc_update(&v->vtx, NULL);
+ return vidisp_update(&v->vrx);
+}
+
+
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params)
+{
+ struct vtx *vtx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vtx = &v->vtx;
+
+ if (vc != vtx->vc) {
+
+ struct videnc_param prm;
+
+ prm.bitrate = v->cfg.bitrate;
+ prm.pktsize = 1024;
+ prm.fps = get_fps(v);
+ prm.max_fs = -1;
+
+ info("Set video encoder: %s %s (%u bit/s, %u fps)\n",
+ vc->name, vc->variant, prm.bitrate, prm.fps);
+
+ vtx->enc = mem_deref(vtx->enc);
+ err = vc->encupdh(&vtx->enc, vc, &prm, params);
+ if (err) {
+ DEBUG_WARNING("encoder alloc: %m\n", err);
+ return err;
+ }
+
+ vtx->vc = vc;
+ }
+
+ stream_update_encoder(v->strm, pt_tx);
+
+ return err;
+}
+
+
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp)
+{
+ struct vrx *vrx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vrx = &v->vrx;
+
+ vrx->pt_rx = pt_rx;
+
+ if (vc != vrx->vc) {
+
+ info("Set video decoder: %s %s\n", vc->name, vc->variant);
+
+ vrx->dec = mem_deref(vrx->dec);
+
+ err = vc->decupdh(&vrx->dec, vc, fmtp);
+ if (err) {
+ DEBUG_WARNING("decoder alloc: %m\n", err);
+ return err;
+ }
+
+ vrx->vc = vc;
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next video encoder in the local list of negotiated codecs
+ *
+ * @param video Video object
+ */
+void video_encoder_cycle(struct video *video)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!video)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(video_strm(video)));
+ if (!rc) {
+ info("cycle video: no remote codec found\n");
+ return;
+ }
+
+ (void)video_encoder_set(video, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *video_strm(const struct video *v)
+{
+ return v ? v->strm : NULL;
+}
+
+
+void video_update_picture(struct video *v)
+{
+ if (!v)
+ return;
+ v->vtx.picup = true;
+}
+
+
+/**
+ * Get the driver-specific view of the video stream
+ *
+ * @param v Video stream
+ *
+ * @return Opaque view
+ */
+void *video_view(const struct video *v)
+{
+ if (!v)
+ return NULL;
+
+ return v->vrx.vidisp_prm.view;
+}
+
+
+/**
+ * Set the current Video Source device name
+ *
+ * @param v Video stream
+ * @param dev Device name
+ */
+void video_vidsrc_set_device(struct video *v, const char *dev)
+{
+ if (!v)
+ return;
+
+ vidsrc_update(&v->vtx, dev);
+}
+
+
+static bool sdprattr_contains(struct stream *s, const char *name,
+ const char *str)
+{
+ const char *attr = sdp_media_rattr(stream_sdpmedia(s), name);
+ return attr ? (NULL != strstr(attr, str)) : false;
+}
+
+
+void video_sdp_attr_decode(struct video *v)
+{
+ if (!v)
+ return;
+
+ /* RFC 4585 */
+ v->nack_pli = sdprattr_contains(v->strm, "rtcp-fb", "nack");
+}
+
+
+int video_debug(struct re_printf *pf, const struct video *v)
+{
+ const struct vtx *vtx;
+ const struct vrx *vrx;
+ int err;
+
+ if (!v)
+ return 0;
+
+ vtx = &v->vtx;
+ vrx = &v->vrx;
+
+ err = re_hprintf(pf, "\n--- Video stream ---\n");
+ err |= re_hprintf(pf, " tx: %u x %u, fps=%d\n",
+ vtx->vsrc_size.w,
+ vtx->vsrc_size.h, vtx->vsrc_prm.fps);
+ err |= re_hprintf(pf, " rx: pt=%d\n", vrx->pt_rx);
+
+ if (!list_isempty(vidfilt_list())) {
+ err |= vtx_print_pipeline(pf, vtx);
+ err |= vrx_print_pipeline(pf, vrx);
+ }
+
+ err |= stream_debug(pf, v->strm);
+
+ return err;
+}
+
+
+int video_print(struct re_printf *pf, const struct video *v)
+{
+ if (!v)
+ return 0;
+
+ return re_hprintf(pf, " efps=%d/%d", v->vtx.efps, v->vrx.efps);
+}
+
+
+int video_set_source(struct video *v, const char *name, const char *dev)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name);
+ struct vtx *vtx;
+
+ if (!v)
+ return EINVAL;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx = &v->vtx;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ return vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev,
+ vidsrc_frame_handler, vidsrc_error_handler, vtx);
+}
+
+
+void video_set_devicename(struct video *v, const char *src, const char *disp)
+{
+ if (!v)
+ return;
+
+ str_ncpy(v->vtx.device, src, sizeof(v->vtx.device));
+ str_ncpy(v->vrx.device, disp, sizeof(v->vrx.device));
+}
diff --git a/src/vidfilt.c b/src/vidfilt.c
new file mode 100644
index 0000000..a8d8426
--- /dev/null
+++ b/src/vidfilt.c
@@ -0,0 +1,116 @@
+/**
+ * @file vidfilt.c Video Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list vfl;
+
+
+/**
+ * Register a new Video Filter
+ *
+ * @param vf Video Filter to register
+ */
+void vidfilt_register(struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_append(&vfl, &vf->le, vf);
+
+ info("vidfilt: %s\n", vf->name);
+}
+
+
+/**
+ * Unregister a Video Filter
+ *
+ * @param vf Video Filter to unregister
+ */
+void vidfilt_unregister(struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_unlink(&vf->le);
+}
+
+
+/**
+ * Get the list of registered Video Filters
+ *
+ * @return List of Video Filters
+ */
+struct list *vidfilt_list(void)
+{
+ return &vfl;
+}
+
+
+static void vidfilt_enc_destructor(void *arg)
+{
+ struct vidfilt_enc_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_enc_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_enc_st *st = NULL;
+ int err;
+
+ if (vf->encupdh) {
+ err = vf->encupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_enc_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
+
+
+static void vidfilt_dec_destructor(void *arg)
+{
+ struct vidfilt_dec_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_dec_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_dec_st *st = NULL;
+ int err;
+
+ if (vf->decupdh) {
+ err = vf->decupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_dec_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
diff --git a/src/vidisp.c b/src/vidisp.c
new file mode 100644
index 0000000..94ceee5
--- /dev/null
+++ b/src/vidisp.c
@@ -0,0 +1,132 @@
+/**
+ * @file vidisp.c Video Display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Display state */
+struct vidisp_st {
+ struct vidisp *vd; /**< Video Display */
+};
+
+
+static struct list vidispl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct vidisp *vd = arg;
+
+ list_unlink(&vd->le);
+}
+
+
+/**
+ * Register a Video output display
+ *
+ * @param vp Pointer to allocated Video Display
+ * @param name Name of Video Display
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ * @param disph Display handler
+ * @param hideh Hide-window handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_register(struct vidisp **vp, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh)
+{
+ struct vidisp *vd;
+
+ if (!vp)
+ return EINVAL;
+
+ vd = mem_zalloc(sizeof(*vd), destructor);
+ if (!vd)
+ return ENOMEM;
+
+ list_append(&vidispl, &vd->le, vd);
+
+ vd->name = name;
+ vd->alloch = alloch;
+ vd->updateh = updateh;
+ vd->disph = disph;
+ vd->hideh = hideh;
+
+ info("vidisp: %s\n", name);
+
+ *vp = vd;
+ return 0;
+}
+
+
+const struct vidisp *vidisp_find(const char *name)
+{
+ struct le *le;
+
+ for (le = vidispl.head; le; le = le->next) {
+ struct vidisp *vd = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vd->name))
+ continue;
+
+ /* Found */
+ return vd;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a video display state
+ *
+ * @param stp Pointer to allocated display state
+ * @param name Name of video display
+ * @param prm Video display parameters (optional)
+ * @param dev Display device
+ * @param resizeh Window resize handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_alloc(struct vidisp_st **stp, const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp *vd = (struct vidisp *)vidisp_find(name);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(stp, vd, prm, dev, resizeh, arg);
+}
+
+
+/**
+ * Display a video frame
+ *
+ * @param st Video display state
+ * @param title Display title
+ * @param frame Video frame
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ if (!st || !frame)
+ return EINVAL;
+
+ return st->vd->disph(st, title, frame);
+}
+
+
+struct vidisp *vidisp_get(struct vidisp_st *st)
+{
+ return st ? st->vd : NULL;
+}
diff --git a/src/vidsrc.c b/src/vidsrc.c
new file mode 100644
index 0000000..e0bb297
--- /dev/null
+++ b/src/vidsrc.c
@@ -0,0 +1,134 @@
+/**
+ * @file vidsrc.c Video Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Source state */
+struct vidsrc_st {
+ struct vidsrc *vs; /**< Video Source */
+};
+
+
+static struct list vidsrcl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc *vs = arg;
+
+ list_unlink(&vs->le);
+}
+
+
+/**
+ * Register a Video Source
+ *
+ * @param vsp Pointer to allocated Video Source
+ * @param name Name of Video Source
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_register(struct vidsrc **vsp, const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh)
+{
+ struct vidsrc *vs;
+
+ if (!vsp)
+ return EINVAL;
+
+ vs = mem_zalloc(sizeof(*vs), destructor);
+ if (!vs)
+ return ENOMEM;
+
+ list_append(&vidsrcl, &vs->le, vs);
+
+ vs->name = name;
+ vs->alloch = alloch;
+ vs->updateh = updateh;
+
+ info("vidsrc: %s\n", name);
+
+ *vsp = vs;
+
+ return 0;
+}
+
+
+/**
+ * Find a Video Source by name
+ *
+ * @param name Name of the Video Source to find
+ *
+ * @return Matching Video Source if found, otherwise NULL
+ */
+const struct vidsrc *vidsrc_find(const char *name)
+{
+ struct le *le;
+
+ for (le=vidsrcl.head; le; le=le->next) {
+
+ struct vidsrc *vs = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vs->name))
+ continue;
+
+ return vs;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a new video source state
+ *
+ * @param stp Pointer to allocated state
+ * @param name Name of the video source
+ * @param ctx Optional media context
+ * @param prm Video source parameters
+ * @param size Wanted video size of the source
+ * @param fmt Format parameter
+ * @param dev Video device
+ * @param frameh Video frame handler
+ * @param errorh Error handler (optional)
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_alloc(struct vidsrc_st **stp, const char *name,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name);
+ if (!vs)
+ return ENOENT;
+
+ return vs->alloch(stp, vs, ctx, prm, size, fmt, dev,
+ frameh, errorh, arg);
+}
+
+
+/**
+ * Get the list of Video Sources
+ *
+ * @return List of Video Sources
+ */
+struct list *vidsrc_list(void)
+{
+ return &vidsrcl;
+}
+
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st)
+{
+ return st ? st->vs : NULL;
+}