summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2018-01-08 22:22:59 +0530
committerJonas Smedegaard <dr@jones.dk>2018-01-08 22:22:59 +0530
commit766bb4acdda738e450630c92d9c37e6cb42d9423 (patch)
tree8277c899ed928c69dfd1251bf889af6c78400f84 /src
Import baresip_0.5.7.orig.tar.gz
[dgit import orig baresip_0.5.7.orig.tar.gz]
Diffstat (limited to 'src')
-rw-r--r--src/account.c703
-rw-r--r--src/aucodec.c66
-rw-r--r--src/audio.c2081
-rw-r--r--src/aufilt.c28
-rw-r--r--src/aulevel.c85
-rw-r--r--src/auplay.c109
-rw-r--r--src/ausrc.c108
-rw-r--r--src/baresip.c248
-rw-r--r--src/bfcp.c199
-rw-r--r--src/call.c1877
-rw-r--r--src/cmd.c768
-rw-r--r--src/conf.c386
-rw-r--r--src/config.c925
-rw-r--r--src/contact.c338
-rw-r--r--src/core.h541
-rw-r--r--src/event.c171
-rw-r--r--src/h264.c182
-rw-r--r--src/log.c153
-rw-r--r--src/magic.h38
-rw-r--r--src/main.c265
-rw-r--r--src/mctrl.c44
-rw-r--r--src/menc.c65
-rw-r--r--src/message.c192
-rw-r--r--src/metric.c90
-rw-r--r--src/mnat.c90
-rw-r--r--src/module.c258
-rw-r--r--src/mos.c63
-rw-r--r--src/net.c561
-rw-r--r--src/play.c352
-rw-r--r--src/realtime.c100
-rw-r--r--src/reg.c265
-rw-r--r--src/rtpext.c112
-rw-r--r--src/rtpkeep.c165
-rw-r--r--src/sdp.c191
-rw-r--r--src/sipreq.c150
-rw-r--r--src/srcs.mk55
-rw-r--r--src/stream.c733
-rw-r--r--src/ua.c1906
-rw-r--r--src/ui.c195
-rw-r--r--src/vidcodec.c126
-rw-r--r--src/video.c1421
-rw-r--r--src/vidfilt.c103
-rw-r--r--src/vidisp.c132
-rw-r--r--src/vidsrc.c125
44 files changed, 16765 insertions, 0 deletions
diff --git a/src/account.c b/src/account.c
new file mode 100644
index 0000000..2a99e58
--- /dev/null
+++ b/src/account.c
@@ -0,0 +1,703 @@
+/**
+ * @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->outboundv); i++)
+ mem_deref(acc->outboundv[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 (msg_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 (msg_param_decode(params, name, &pl))
+ return 0;
+
+ *v = pl_u32(&pl);
+
+ return 0;
+}
+
+
+/*
+ * Decode STUN parameters, inspired by RFC 7064
+ *
+ * See RFC 3986:
+ *
+ * Use of the format "user:password" in the userinfo field is
+ * deprecated.
+ *
+ */
+static int stunsrv_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl srv, tmp;
+ struct uri uri;
+ int err;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ memset(&uri, 0, sizeof(uri));
+
+ if (0 == msg_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 (0 == msg_param_exists(&aor->params, "stunuser", &tmp))
+ err |= param_dstr(&acc->stun_user, &aor->params, "stunuser");
+ else if (pl_isset(&uri.user))
+ err |= pl_strdup(&acc->stun_user, &uri.user);
+ else
+ err |= pl_strdup(&acc->stun_user, &aor->uri.user);
+
+ if (0 == msg_param_exists(&aor->params, "stunpass", &tmp))
+ err |= param_dstr(&acc->stun_pass, &aor->params, "stunpass");
+ else if (pl_isset(&uri.password))
+ err |= pl_strdup(&acc->stun_pass, &uri.password);
+ else if (acc->auth_pass)
+ err |= str_dup(&acc->stun_pass, acc->auth_pass);
+
+ 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 == msg_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 list *aucodecl = baresip_aucodecl();
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->aucodecl);
+
+ if (0 == msg_param_exists(prm, "audio_codecs", &tmp)) {
+ struct pl acs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (msg_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(aucodecl,
+ 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 list *vidcodecl = baresip_vidcodecl();
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->vidcodecl);
+
+ if (0 == msg_param_exists(prm, "video_codecs", &tmp)) {
+ struct pl vcs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (msg_param_decode(prm, "video_codecs", &vcs))
+ return 0;
+
+ while (0 == csl_parse(&vcs, cname, sizeof(cname))) {
+ struct vidcodec *vc;
+
+ vc = (struct vidcodec *)vidcodec_find(vidcodecl,
+ 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");
+
+ acc->pubint = 0;
+ err |= param_u32(&acc->pubint, &aor->params, "pubint");
+
+ err |= param_dstr(&acc->regq, &aor->params, "regq");
+
+ for (i=0; i<ARRAY_SIZE(acc->outboundv); i++) {
+
+ char expr[16] = "outbound";
+
+ expr[8] = i + 1 + 0x30;
+ expr[9] = '\0';
+
+ err |= param_dstr(&acc->outboundv[i], &aor->params, expr);
+ }
+
+ /* backwards compat */
+ if (!acc->outboundv[0]) {
+ err |= param_dstr(&acc->outboundv[0], &aor->params,
+ "outbound");
+ }
+
+ err |= param_dstr(&acc->sipnat, &aor->params, "sipnat");
+
+ if (0 == msg_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);
+}
+
+
+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: error parsing 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 = re_sdprintf(&acc->auth_pass, "%H",
+ uri_password_unescape,
+ &acc->laddr.uri.password);
+ if (err)
+ goto out;
+ }
+ else if (0 == msg_param_decode(&acc->laddr.params, "auth_pass", &pl)) {
+ err = pl_strdup(&acc->auth_pass, &pl);
+ if (err)
+ goto out;
+ }
+
+ err = stunsrv_decode(acc, &acc->laddr);
+ if (err)
+ goto out;
+
+ if (acc->mnatid) {
+ acc->mnat = mnat_find(baresip_mnatl(), acc->mnatid);
+ if (!acc->mnat) {
+ warning("account: medianat not found: `%s'\n",
+ acc->mnatid);
+ }
+ }
+
+ if (acc->mencid) {
+ acc->menc = menc_find(baresip_mencl(), 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;
+}
+
+
+int account_set_auth_pass(struct account *acc, const char *pass)
+{
+ if (!acc)
+ return EINVAL;
+
+ acc->auth_pass = mem_deref(acc->auth_pass);
+
+ if (pass)
+ return str_dup(&acc->auth_pass, pass);
+
+ return 0;
+}
+
+
+/**
+ * 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 : baresip_aucodecl();
+}
+
+
+#ifdef USE_VIDEO
+struct list *account_vidcodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->vidcodecl))
+ ? (struct list *)&acc->vidcodecl : baresip_vidcodecl();
+}
+#endif
+
+
+struct sip_addr *account_laddr(const struct account *acc)
+{
+ return acc ? (struct sip_addr *)&acc->laddr : NULL;
+}
+
+
+uint32_t account_regint(const struct account *acc)
+{
+ return acc ? acc->regint : 0;
+}
+
+
+uint32_t account_pubint(const struct account *acc)
+{
+ return acc ? acc->pubint : 0;
+}
+
+
+enum answermode account_answermode(const struct account *acc)
+{
+ return acc ? acc->answermode : ANSWERMODE_MANUAL;
+}
+
+
+const char *account_aor(const struct account *acc)
+{
+ return acc ? acc->aor : NULL;
+}
+
+
+/**
+ * Get the authentication username of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Authentication username
+ */
+const char *account_auth_user(const struct account *acc)
+{
+ return acc ? acc->auth_user : NULL;
+}
+
+
+/**
+ * Get the SIP authentication password of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Authentication password
+ */
+const char *account_auth_pass(const struct account *acc)
+{
+ return acc ? acc->auth_pass : NULL;
+}
+
+
+/**
+ * Get the outbound SIP server of an account
+ *
+ * @param acc User-Agent account
+ * @param ix Index starting at zero
+ *
+ * @return Outbound SIP proxy, NULL if not configured
+ */
+const char *account_outbound(const struct account *acc, unsigned ix)
+{
+ if (!acc || ix >= ARRAY_SIZE(acc->outboundv))
+ return NULL;
+
+ return acc->outboundv[ix];
+}
+
+
+/**
+ * Get the audio packet-time (ptime) of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Packet-time (ptime)
+ */
+uint32_t account_ptime(const struct account *acc)
+{
+ return acc ? acc->ptime : 0;
+}
+
+
+/**
+ * Get the STUN username of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN username
+ */
+const char *account_stun_user(const struct account *acc)
+{
+ return acc ? acc->stun_user : NULL;
+}
+
+
+/**
+ * Get the STUN password of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN password
+ */
+const char *account_stun_pass(const struct account *acc)
+{
+ return acc ? acc->stun_pass : NULL;
+}
+
+
+/**
+ * Get the STUN hostname of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN hostname
+ */
+const char *account_stun_host(const struct account *acc)
+{
+ return acc ? acc->stun_host : 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->outboundv); i++) {
+ if (acc->outboundv[i]) {
+ err |= re_hprintf(pf, " outbound%d: %s\n",
+ i+1, acc->outboundv[i]);
+ }
+ }
+ err |= re_hprintf(pf, " ptime: %u\n", acc->ptime);
+ err |= re_hprintf(pf, " regint: %u\n", acc->regint);
+ err |= re_hprintf(pf, " pubint: %u\n", acc->pubint);
+ 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..35076ed
--- /dev/null
+++ b/src/aucodec.c
@@ -0,0 +1,66 @@
+/**
+ * @file aucodec.c Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register an Audio Codec
+ *
+ * @param aucodecl List of audio-codecs
+ * @param ac Audio Codec object
+ */
+void aucodec_register(struct list *aucodecl, struct aucodec *ac)
+{
+ if (!aucodecl || !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 struct list *aucodecl,
+ const char *name, uint32_t srate,
+ uint8_t ch)
+{
+ struct le *le;
+
+ for (le=list_head(aucodecl); 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;
+}
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..17eb5f6
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,2081 @@
+/**
+ * @file src/audio.c Audio stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * \ref GenericAudioStream
+ */
+#define _DEFAULT_SOURCE 1
+#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 = 3*1920 /* Max samples, 48000Hz 2ch at 60ms */
+};
+
+
+/**
+ * Audio transmit/encoder
+ *
+ *
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .-------. .-------. .--------. .--------. .--------.
+ | | | | | | | | | | |
+ |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP
+ | | | | | | | | | | |
+ ' '-------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ *
+ */
+struct autx {
+ struct ausrc_st *ausrc; /**< Audio Source */
+ struct ausrc_prm ausrc_prm; /**< Audio Source parameters */
+ const struct aucodec *ac; /**< Current audio encoder */
+ struct auenc_state *enc; /**< Audio encoder state (optional) */
+ struct aubuf *aubuf; /**< Packetize outgoing stream */
+ size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */
+ volatile bool aubuf_started;
+ 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]; /**< Audio source device name */
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for sending */
+ uint64_t ts_ext; /**< Ext. Timestamp for outgoing RTP */
+ uint32_t ts_base;
+ uint32_t ts_tel; /**< Timestamp for Telephony Events */
+ size_t psize; /**< Packet size for sending */
+ bool marker; /**< Marker bit for outgoing RTP */
+ bool muted; /**< Audio source is muted */
+ int cur_key; /**< Currently transmitted event */
+ enum aufmt src_fmt;
+ bool need_conv;
+
+ struct {
+ uint64_t aubuf_overrun;
+ uint64_t aubuf_underrun;
+ } stats;
+
+#ifdef HAVE_PTHREAD
+ union {
+ struct {
+ pthread_t tid;/**< Audio transmit thread */
+ bool run; /**< Audio transmit thread running */
+ } thr;
+ } u;
+#endif
+};
+
+
+/**
+ * Audio receive/decoder
+ *
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .--------. .-------. .--------. .--------. .--------.
+ |\ | | | | | | | | | |
+ | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP
+ |/ | | | | | | | | | |
+ '--------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ */
+struct aurx {
+ struct auplay_st *auplay; /**< Audio Player */
+ struct auplay_prm auplay_prm; /**< Audio Player parameters */
+ const struct aucodec *ac; /**< Current audio decoder */
+ struct audec_state *dec; /**< Audio decoder state (optional) */
+ struct aubuf *aubuf; /**< Incoming audio buffer */
+ size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */
+ volatile bool aubuf_started;
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in decoding order */
+ char device[64]; /**< Audio player device name */
+ 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 */
+ double level_last;
+ bool level_set;
+ enum aufmt play_fmt;
+ bool need_conv;
+ struct timestamp_recv ts_recv;
+ uint64_t n_discard;
+
+ struct {
+ uint64_t aubuf_overrun;
+ uint64_t aubuf_underrun;
+ } stats;
+};
+
+
+/** 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 */
+ bool level_enabled; /**< Audio level RTP ext. enabled */
+ unsigned extmap_aulevel; /**< ID Range 1-14 inclusive */
+ audio_event_h *eventh; /**< Event handler */
+ audio_err_h *errh; /**< Audio error handler */
+ void *arg; /**< Handler argument */
+};
+
+
+/* RFC 6464 */
+static const char *uri_aulevel = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+
+
+static double audio_calc_seconds(uint64_t rtp_ts, uint32_t clock_rate)
+{
+ double timestamp;
+
+ /* convert from RTP clockrate to seconds */
+ timestamp = (double)rtp_ts / (double)clock_rate;
+
+ return timestamp;
+}
+
+
+static double autx_calc_seconds(const struct autx *autx)
+{
+ uint64_t dur;
+
+ if (!autx->ac)
+ return .0;
+
+ dur = autx->ts_ext - autx->ts_base;
+
+ return audio_calc_seconds(dur, autx->ac->crate);
+}
+
+
+static double aurx_calc_seconds(const struct aurx *aurx)
+{
+ uint64_t dur;
+
+ if (!aurx->ac)
+ return .0;
+
+ dur = timestamp_duration(&aurx->ts_recv);
+
+ return audio_calc_seconds(dur, aurx->ac->crate);
+}
+
+
+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:
+ if (tx->u.thr.run) {
+ tx->u.thr.run = false;
+ pthread_join(tx->u.thr.tid, NULL);
+ }
+ break;
+#endif
+ 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;
+}
+
+
+static inline double calc_ptime(size_t nsamp, uint32_t srate, uint8_t channels)
+{
+ double ptime;
+
+ ptime = 1000.0 * (double)nsamp / (double)(srate * channels);
+
+ return ptime;
+}
+
+
+/**
+ * Get the DSP samplerate for an audio-codec (exception for G.722 and MPA)
+ */
+static inline uint32_t get_srate(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return ac->srate;
+}
+
+
+/**
+ * Get the DSP channels for an audio-codec (exception for MPA)
+ */
+static inline uint32_t get_ch(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return !str_casecmp(ac->name, "MPA") ? 2 : ac->ch;
+}
+
+
+static inline uint32_t get_framesize(const struct aucodec *ac,
+ uint32_t ptime)
+{
+ if (!ac)
+ return 0;
+
+ return calc_nsamp(get_srate(ac), get_ch(ac), 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) && get_ch(a) == get_ch(b);
+}
+
+
+static int add_audio_codec(struct audio *a, struct sdp_media *m,
+ struct aucodec *ac)
+{
+ if (!in_range(&a->cfg.srate, get_srate(ac))) {
+ debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n",
+ get_srate(ac), a->cfg.srate.min, a->cfg.srate.max);
+ return 0;
+ }
+
+ if (!in_range(&a->cfg.channels, get_ch(ac))) {
+ debug("audio: skip codec with %uch (audio range %uch-%uch)\n",
+ get_ch(ac), a->cfg.channels.min, a->cfg.channels.max);
+ return 0;
+ }
+
+ if (ac->crate < 8000) {
+ warning("audio: illegal clock rate %u\n", ac->crate);
+ return EINVAL;
+ }
+
+ return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->crate,
+ ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false,
+ "%s", ac->fmtp);
+}
+
+
+static int append_rtpext(struct audio *au, struct mbuf *mb,
+ int16_t *sampv, size_t sampc)
+{
+ uint8_t data[1];
+ double level;
+ int err;
+
+ /* audio level must be calculated from the audio samples that
+ * are actually sent on the network. */
+ level = aulevel_calc_dbov(sampv, sampc);
+
+ data[0] = (int)-level & 0x7f;
+
+ err = rtpext_encode(mb, au->extmap_aulevel, 1, data);
+ if (err) {
+ warning("audio: rtpext_encode failed (%m)\n", err);
+ return err;
+ }
+
+ return err;
+}
+
+
+/**
+ * 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 frame_size; /* number of samples per channel */
+ size_t sampc_rtp;
+ size_t len;
+ size_t ext_len = 0;
+ int err;
+
+ if (!tx->ac || !tx->ac->ench)
+ return;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (a->level_enabled) {
+
+ /* skip the extension header */
+ tx->mb->pos += RTPEXT_HDR_SIZE;
+
+ err = append_rtpext(a, tx->mb, sampv, sampc);
+ if (err)
+ return;
+
+ ext_len = tx->mb->pos - STREAM_PRESZ;
+
+ /* write the Extension header at the beginning */
+ tx->mb->pos = STREAM_PRESZ;
+
+ err = rtpext_hdr_encode(tx->mb, ext_len - RTPEXT_HDR_SIZE);
+ if (err)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ + ext_len;
+ tx->mb->end = STREAM_PRESZ + ext_len;
+ }
+
+ len = mbuf_get_space(tx->mb);
+
+ err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc);
+ if ((err & 0xffff0000) == 0x00010000) {
+ /* MPA needs some special treatment here */
+ tx->ts_ext = err & 0xffff;
+ err = 0;
+ }
+ else 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 + ext_len + len;
+
+ if (mbuf_get_left(tx->mb)) {
+
+ uint32_t rtp_ts = tx->ts_ext & 0xffffffff;
+
+ if (len) {
+ err = stream_send(a->strm, ext_len!=0, tx->marker, -1,
+ rtp_ts, tx->mb);
+ if (err)
+ goto out;
+ }
+ }
+
+ /* Convert from audio samplerate to RTP clockrate */
+ sampc_rtp = sampc * tx->ac->crate / tx->ac->srate;
+
+ /* The RTP clock rate used for generating the RTP timestamp is
+ * independent of the number of channels and the encoding
+ * However, MPA support variable packet durations. Thus, MPA
+ * should update the ts according to its current internal state.
+ */
+ frame_size = sampc_rtp / get_ch(tx->ac);
+
+ tx->ts_ext += (uint32_t)frame_size;
+
+ 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;
+ size_t sz;
+ size_t num_bytes;
+ struct le *le;
+ int err = 0;
+
+ sz = aufmt_sample_size(tx->src_fmt);
+ if (!sz)
+ return;
+
+ num_bytes = tx->psize;
+ sampc = tx->psize / sz;
+
+ /* timed read from audio-buffer */
+
+ if (tx->src_fmt == AUFMT_S16LE) {
+
+ aubuf_read(tx->aubuf, (uint8_t *)tx->sampv, num_bytes);
+ }
+ else {
+ /* Convert from ausrc format to 16-bit format */
+
+ void *tmp_sampv;
+
+ if (!tx->need_conv) {
+ info("audio: NOTE: source sample conversion"
+ " needed: %s --> %s\n",
+ aufmt_name(tx->src_fmt), aufmt_name(AUFMT_S16LE));
+ tx->need_conv = true;
+ }
+
+ tmp_sampv = mem_zalloc(num_bytes, NULL);
+ if (!tmp_sampv)
+ return;
+
+ aubuf_read(tx->aubuf, tmp_sampv, num_bytes);
+
+ auconv_to_s16(sampv, tx->src_fmt, tmp_sampv, sampc);
+
+ mem_deref(tmp_sampv);
+ }
+
+ /* 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 = (uint32_t)tx->ts_ext;
+
+ fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt);
+ if (!fmt)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ;
+ err = stream_send(a->strm, false, 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
+ *
+ * @note The sample format is set in rx->play_fmt
+ *
+ * @param buf Buffer to fill with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ */
+static void auplay_write_handler(void *sampv, size_t sampc, void *arg)
+{
+ struct aurx *rx = arg;
+ size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt);
+
+ if (rx->aubuf_started && aubuf_cur_size(rx->aubuf) < num_bytes) {
+
+ ++rx->stats.aubuf_underrun;
+
+ debug("audio: rx aubuf underrun (total %llu)\n",
+ rx->stats.aubuf_underrun);
+ }
+
+ aubuf_read(rx->aubuf, sampv, num_bytes);
+}
+
+
+/**
+ * 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 void *sampv, size_t sampc, void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+ size_t num_bytes = sampc * aufmt_sample_size(tx->src_fmt);
+
+ if (tx->muted)
+ memset((void *)sampv, 0, num_bytes);
+
+ if (aubuf_cur_size(tx->aubuf) >= tx->aubuf_maxsz) {
+
+ ++tx->stats.aubuf_overrun;
+
+ debug("audio: tx aubuf overrun (total %llu)\n",
+ tx->stats.aubuf_overrun);
+ }
+
+ (void)aubuf_write(tx->aubuf, sampv, num_bytes);
+
+ tx->aubuf_started = true;
+
+ if (a->cfg.txmode == AUDIO_MODE_POLL) {
+ unsigned i;
+
+ for (i=0; i<16; i++) {
+
+ if (aubuf_cur_size(tx->aubuf) < tx->psize)
+ break;
+
+ 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) {
+ sampc = rx->ac->srate * rx->ac->ch * rx->ptime / 1000;
+
+ 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;
+ }
+
+ if (aubuf_cur_size(rx->aubuf) >= rx->aubuf_maxsz) {
+
+ ++rx->stats.aubuf_overrun;
+
+ debug("audio: rx aubuf overrun (total %llu)\n",
+ rx->stats.aubuf_overrun);
+ }
+
+ if (rx->play_fmt == AUFMT_S16LE) {
+ err = aubuf_write_samp(rx->aubuf, sampv, sampc);
+ if (err)
+ goto out;
+ }
+ else {
+
+ /* Convert from 16-bit to auplay format */
+
+ void *tmp_sampv;
+ size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt);
+
+ if (!rx->need_conv) {
+ info("audio: NOTE: playback sample conversion"
+ " needed: %s --> %s\n",
+ aufmt_name(AUFMT_S16LE),
+ aufmt_name(rx->play_fmt));
+ rx->need_conv = true;
+ }
+
+ tmp_sampv = mem_zalloc(num_bytes, NULL);
+ if (!tmp_sampv)
+ return ENOMEM;
+
+ auconv_from_s16(rx->play_fmt, tmp_sampv, sampv, sampc);
+
+ err = aubuf_write(rx->aubuf, tmp_sampv, num_bytes);
+
+ mem_deref(tmp_sampv);
+
+ if (err)
+ goto out;
+ }
+
+ rx->aubuf_started = true;
+
+ out:
+ return err;
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg)
+{
+ struct audio *a = arg;
+ struct aurx *rx = &a->rx;
+ bool discard = false;
+ size_t i;
+ int wrap;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Telephone event? */
+ if (hdr->pt != rx->pt) {
+ const struct sdp_format *fmt;
+
+ fmt = sdp_media_lformat(stream_sdpmedia(a->strm), hdr->pt);
+
+ if (fmt && !str_casecmp(fmt->name, "telephone-event")) {
+ 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;
+ }
+
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ for (i=0; i<extc; i++) {
+
+ if (extv[i].id == a->extmap_aulevel) {
+
+ a->rx.level_last = -(double)(extv[i].data[0] & 0x7f);
+ a->rx.level_set = true;
+ }
+ else {
+ info("audio: rtp header ext ignored (id=%u)\n",
+ extv[i].id);
+ }
+ }
+
+ /* Save timestamp for incoming RTP packets */
+
+ if (rx->ts_recv.is_set) {
+
+ uint64_t ext_last, ext_now;
+
+ ext_last = calc_extended_timestamp(rx->ts_recv.num_wraps,
+ rx->ts_recv.last);
+
+ ext_now = calc_extended_timestamp(rx->ts_recv.num_wraps,
+ hdr->ts);
+
+ if (ext_now <= ext_last) {
+ uint64_t delta;
+
+ delta = ext_last - ext_now;
+
+ warning("audio: [time=%.3f]"
+ " discard old frame (%.3f seconds old)\n",
+ aurx_calc_seconds(rx),
+ audio_calc_seconds(delta, rx->ac->crate));
+
+ discard = true;
+ }
+ }
+ else {
+ rx->ts_recv.first = hdr->ts;
+ rx->ts_recv.last = hdr->ts;
+ rx->ts_recv.is_set = true;
+ }
+
+ wrap = timestamp_wrap(hdr->ts, rx->ts_recv.last);
+
+ switch (wrap) {
+
+ case -1:
+ warning("audio: rtp timestamp wraps backwards"
+ " (delta = %d) -- discard\n",
+ (int32_t)(rx->ts_recv.last - hdr->ts));
+ discard = true;
+ break;
+
+ case 0:
+ break;
+
+ case 1:
+ ++rx->ts_recv.num_wraps;
+ break;
+
+ default:
+ break;
+ }
+
+ rx->ts_recv.last = hdr->ts;
+
+#if 0
+ re_printf("[time=%.3f] wrap=%d discard=%d\n",
+ aurx_calc_seconds(rx), wrap, discard);
+#endif
+
+ if (discard) {
+ ++a->rx.n_discard;
+ 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;
+
+ return err;
+}
+
+
+/*
+ * EBU ACIP (Audio Contribution over IP) Profile
+ *
+ * Ref: https://tech.ebu.ch/docs/tech/tech3368.pdf
+ */
+static int set_ebuacip_params(struct audio *au, uint32_t ptime)
+{
+ struct sdp_media *sdp = stream_sdpmedia(au->strm);
+ const struct config_avt *avt = &au->strm->cfg;
+ char str[64];
+ int jbvalue = 0;
+ int jb_id = 0;
+ int err = 0;
+
+ /* set ebuacip version fixed value 0 for now. */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "version %i", 0);
+
+ /* set jb option, only one in our case */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "jb %i", jb_id);
+
+ /* define jb value in option */
+ if (0 == conf_get_str(conf_cur(), "ebuacip_jb_type",str,sizeof(str))) {
+
+ if (0 == str_cmp(str, "auto")) {
+
+ err |= sdp_media_set_lattr(sdp, false,
+ "ebuacip",
+ "jbdef %i auto %d-%d",
+ jb_id,
+ avt->jbuf_del.min * ptime,
+ avt->jbuf_del.max * ptime);
+ }
+ else if (0 == str_cmp(str, "fixed")) {
+
+ /* define jb value in option */
+ jbvalue = avt->jbuf_del.max * ptime;
+
+ err |= sdp_media_set_lattr(sdp, false,
+ "ebuacip",
+ "jbdef %i fixed %d",
+ jb_id, jbvalue);
+ }
+ }
+
+ /* set QOS recomendation use tos / 4 to set DSCP value */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "qosrec %u",
+ avt->rtp_tos / 4);
+
+ /* EBU ACIP FEC:: NOT SET IN BARESIP */
+
+ return err;
+}
+
+
+int audio_alloc(struct audio **ap, const struct stream_param *stream_prm,
+ 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, bool offerer,
+ audio_event_h *eventh, audio_err_h *errh, void *arg)
+{
+ struct audio *a;
+ struct autx *tx;
+ struct aurx *rx;
+ struct le *le;
+ int err;
+ (void)offerer;
+
+ 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;
+
+ tx->src_fmt = cfg->audio.src_fmt;
+ rx->play_fmt = cfg->audio.play_fmt;
+
+ err = stream_alloc(&a->strm, stream_prm, &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;
+
+ if (cfg->audio.level && offerer) {
+
+ a->extmap_aulevel = 1;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "extmap",
+ "%u %s",
+ a->extmap_aulevel, uri_aulevel);
+ if (err)
+ goto out;
+ }
+
+ if (cfg->sdp.ebuacip) {
+
+ err = set_ebuacip_params(a, 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 * sizeof(int16_t), NULL);
+ rx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL);
+ if (!tx->mb || !tx->sampv || !rx->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = telev_alloc(&a->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_ext = tx->ts_base = rand_u16();
+ 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;
+
+ out:
+ if (err)
+ mem_deref(a);
+ else
+ *ap = a;
+
+ return err;
+}
+
+
+#ifdef HAVE_PTHREAD
+static void *tx_thread(void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+ uint64_t ts = 0;
+
+ while (a->tx.u.thr.run) {
+
+ uint64_t now;
+
+ sys_msleep(4);
+
+ if (!tx->aubuf_started)
+ continue;
+
+ if (!a->tx.u.thr.run)
+ break;
+
+ now = tmr_jiffies();
+ if (!ts)
+ ts = now;
+
+ if (ts > now)
+ continue;
+
+ /* Now is the time to send */
+
+ if (aubuf_cur_size(tx->aubuf) >= tx->psize) {
+
+ poll_aubuf_tx(a);
+ }
+ else {
+ ++tx->stats.aubuf_underrun;
+
+ debug("audio: thread: tx aubuf underrun"
+ " (total %llu)\n", tx->stats.aubuf_underrun);
+ }
+
+ ts += tx->ptime;
+ }
+
+ return NULL;
+}
+#endif
+
+
+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 = get_ch(ac);
+ 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(baresip_aufiltl()); 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 = get_ch(ac);
+
+ 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), get_ch(ac), 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), get_ch(ac),
+ 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(baresip_auplayl(), NULL)) {
+
+ struct auplay_prm prm;
+
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = rx->ptime;
+ prm.fmt = rx->play_fmt;
+
+ if (!rx->aubuf) {
+ size_t psize;
+ size_t sz = aufmt_sample_size(rx->play_fmt);
+
+ psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ rx->aubuf_maxsz = psize * 8;
+
+ err = aubuf_alloc(&rx->aubuf, psize * 1,
+ rx->aubuf_maxsz);
+ if (err)
+ return err;
+ }
+
+ err = auplay_alloc(&rx->auplay, baresip_auplayl(),
+ 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;
+ }
+
+ rx->auplay_prm = prm;
+
+ info("audio: player started with sample format %s\n",
+ aufmt_name(rx->play_fmt));
+ }
+
+ 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 = get_ch(ac);
+
+ 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), get_ch(ac), 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), get_ch(ac));
+ if (err) {
+ warning("audio: could not setup ausrc resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Source */
+ if (!tx->ausrc && ausrc_find(baresip_ausrcl(), NULL)) {
+
+ struct ausrc_prm prm;
+ size_t sz;
+
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = tx->ptime;
+ prm.fmt = tx->src_fmt;
+
+ sz = aufmt_sample_size(tx->src_fmt);
+
+ tx->psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ tx->aubuf_maxsz = tx->psize * 30;
+
+ if (!tx->aubuf) {
+ err = aubuf_alloc(&tx->aubuf, tx->psize,
+ tx->aubuf_maxsz);
+ if (err)
+ return err;
+ }
+
+ err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(),
+ NULL, a->cfg.src_mod,
+ &prm, tx->device,
+ ausrc_read_handler, ausrc_error_handler, a);
+ if (err) {
+ warning("audio: start_source failed (%s.%s): %m\n",
+ a->cfg.src_mod, tx->device, err);
+ return err;
+ }
+
+ switch (a->cfg.txmode) {
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ 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
+
+ default:
+ break;
+ }
+
+ tx->ausrc_prm = prm;
+
+ info("audio: source started with sample format %s\n",
+ aufmt_name(tx->src_fmt));
+ }
+
+ 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(baresip_aufiltl())) {
+ 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), get_ch(ac));
+
+ /* Audio source must be stopped first */
+ if (reset) {
+ tx->ausrc = mem_deref(tx->ausrc);
+ }
+
+ 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, ac->crate, ac->crate);
+ stream_update_encoder(a->strm, pt_tx);
+
+ telev_set_srate(a->telev, ac->crate);
+
+ 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), get_ch(ac));
+
+ 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, ac->crate, ac->crate);
+
+ 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 != KEYCODE_REL) {
+ int event = telev_digit2code(key);
+ info("audio: send DTMF digit: '%c'\n", key);
+
+ if (event == -1) {
+ warning("audio: invalid DTMF digit (0x%02x)\n", key);
+ return EINVAL;
+ }
+
+ err = telev_send(a->telev, event, false);
+ }
+ else if (a->tx.cur_key && a->tx.cur_key != KEYCODE_REL) {
+ /* 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 source (i.e. Microphone)
+ *
+ * @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;
+}
+
+
+/**
+ * Get the mute state of an audio source
+ *
+ * @param a Audio stream
+ *
+ * @return True if muted, otherwise false
+ */
+bool audio_ismuted(const struct audio *a)
+{
+ if (!a)
+ return false;
+
+ return a->tx.muted;
+}
+
+
+static bool extmap_handler(const char *name, const char *value, void *arg)
+{
+ struct audio *au = arg;
+ struct sdp_extmap extmap;
+ int err;
+ (void)name;
+
+ err = sdp_extmap_decode(&extmap, value);
+ if (err) {
+ warning("audio: sdp_extmap_decode error (%m)\n", err);
+ return false;
+ }
+
+ if (0 == pl_strcasecmp(&extmap.name, uri_aulevel)) {
+
+ if (extmap.id < RTPEXT_ID_MIN || extmap.id > RTPEXT_ID_MAX) {
+ warning("audio: extmap id out of range (%u)\n",
+ extmap.id);
+ return false;
+ }
+
+ au->extmap_aulevel = extmap.id;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(au->strm), true,
+ "extmap",
+ "%u %s",
+ au->extmap_aulevel,
+ uri_aulevel);
+ if (err)
+ return false;
+
+ au->level_enabled = true;
+ info("audio: client-to-mixer audio levels enabled\n");
+ }
+
+ return false;
+}
+
+
+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 %ums -> %ums\n",
+ a->tx.ptime, ptime_tx);
+
+ tx->ptime = ptime_tx;
+
+ if (tx->ac) {
+ tx->psize = 2 * get_framesize(tx->ac,
+ ptime_tx);
+ }
+ }
+ }
+
+ /* Client-to-Mixer Audio Level Indication */
+ if (a->cfg.level) {
+ sdp_media_rattr_apply(stream_sdpmedia(a->strm),
+ "extmap",
+ extmap_handler, a);
+ }
+}
+
+
+/**
+ * Get the last value of the audio level from incoming RTP packets
+ *
+ * @param au Audio object
+ * @param levelp Pointer to where to write audio level value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_level_get(const struct audio *au, double *levelp)
+{
+ if (!au)
+ return EINVAL;
+
+ if (!au->level_enabled)
+ return ENOTSUP;
+
+ if (!au->rx.level_set)
+ return ENOENT;
+
+ if (levelp)
+ *levelp = au->rx.level_last;
+
+ return 0;
+}
+
+
+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), get_ch(ac));
+}
+
+
+int audio_debug(struct re_printf *pf, const struct audio *a)
+{
+ const struct autx *tx;
+ const struct aurx *rx;
+ size_t sztx, szrx;
+ int err;
+
+ if (!a)
+ return 0;
+
+ tx = &a->tx;
+ rx = &a->rx;
+
+ sztx = aufmt_sample_size(tx->src_fmt);
+ szrx = aufmt_sample_size(rx->play_fmt);
+
+ err = re_hprintf(pf, "\n--- Audio stream ---\n");
+
+ err |= re_hprintf(pf, " tx: %H ptime=%ums\n",
+ aucodec_print, tx->ac,
+ tx->ptime);
+ err |= re_hprintf(pf, " aubuf: %H"
+ " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n",
+ aubuf_debug, tx->aubuf,
+ calc_ptime(aubuf_cur_size(tx->aubuf)/sztx,
+ tx->ausrc_prm.srate,
+ tx->ausrc_prm.ch),
+ calc_ptime(tx->aubuf_maxsz/sztx,
+ tx->ausrc_prm.srate,
+ tx->ausrc_prm.ch),
+ tx->stats.aubuf_overrun,
+ tx->stats.aubuf_underrun);
+
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ autx_calc_seconds(tx));
+
+ err |= re_hprintf(pf,
+ " rx: %H\n"
+ " ptime=%ums pt=%d\n",
+ aucodec_print, rx->ac,
+ rx->ptime, rx->pt);
+ err |= re_hprintf(pf, " aubuf: %H"
+ " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n",
+ aubuf_debug, rx->aubuf,
+ calc_ptime(aubuf_cur_size(rx->aubuf)/szrx,
+ rx->auplay_prm.srate,
+ rx->auplay_prm.ch),
+ calc_ptime(rx->aubuf_maxsz/szrx,
+ rx->auplay_prm.srate,
+ rx->auplay_prm.ch),
+ rx->stats.aubuf_overrun,
+ rx->stats.aubuf_underrun
+ );
+
+ err |= re_hprintf(pf, " n_discard:%llu\n",
+ rx->n_discard);
+ if (rx->level_set) {
+ err |= re_hprintf(pf, " level %.3f dBov\n",
+ rx->level_last);
+ }
+ if (rx->ts_recv.is_set) {
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ aurx_calc_seconds(rx));
+ }
+ else {
+ err |= re_hprintf(pf, " time = (not started)\n");
+ }
+
+ 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));
+}
+
+
+int audio_set_source(struct audio *au, const char *mod, const char *device)
+{
+ struct autx *tx;
+ int err;
+
+ if (!au)
+ return EINVAL;
+
+ tx = &au->tx;
+
+ /* stop the audio device first */
+ tx->ausrc = mem_deref(tx->ausrc);
+
+ err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(),
+ NULL, mod, &tx->ausrc_prm, device,
+ ausrc_read_handler, ausrc_error_handler, au);
+ if (err) {
+ warning("audio: set_source failed (%s.%s): %m\n",
+ mod, device, err);
+ return err;
+ }
+
+ return 0;
+}
+
+
+int audio_set_player(struct audio *au, const char *mod, const char *device)
+{
+ struct aurx *rx;
+ int err;
+
+ if (!au)
+ return EINVAL;
+
+ rx = &au->rx;
+
+ /* stop the audio device first */
+ rx->auplay = mem_deref(rx->auplay);
+
+ err = auplay_alloc(&rx->auplay, baresip_auplayl(),
+ mod, &rx->auplay_prm, device,
+ auplay_write_handler, rx);
+ if (err) {
+ warning("audio: set_player failed (%s.%s): %m\n",
+ mod, device, err);
+ return err;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Reference:
+ *
+ * https://www.avm.de/de/Extern/files/x-rtp/xrtpv32.pdf
+ */
+int audio_print_rtpstat(struct re_printf *pf, const struct audio *a)
+{
+ const struct stream *s;
+ const struct rtcp_stats *rtcp;
+ int srate_tx = 8000;
+ int srate_rx = 8000;
+ int err;
+
+ if (!a)
+ return 1;
+
+ s = a->strm;
+ rtcp = &s->rtcp_stats;
+
+ if (!rtcp->tx.sent)
+ return 1;
+
+ if (a->tx.ac)
+ srate_tx = get_srate(a->tx.ac);
+ if (a->rx.ac)
+ srate_rx = get_srate(a->rx.ac);
+
+ err = re_hprintf(pf,
+ "EX=BareSip;" /* Reporter Identifier */
+ "CS=%d;" /* Call Setup in milliseconds */
+ "CD=%d;" /* Call Duration in seconds */
+ "PR=%u;PS=%u;" /* Packets RX, TX */
+ "PL=%d,%d;" /* Packets Lost RX, TX */
+ "PD=%d,%d;" /* Packets Discarded, RX, TX */
+ "JI=%.1f,%.1f;" /* Jitter RX, TX in timestamp units */
+ "IP=%J,%J" /* Local, Remote IPs */
+ ,
+ call_setup_duration(s->call) * 1000,
+ call_duration(s->call),
+
+ s->metric_rx.n_packets,
+ s->metric_tx.n_packets,
+
+ rtcp->rx.lost, rtcp->tx.lost,
+
+ s->metric_rx.n_err, s->metric_tx.n_err,
+
+ /* timestamp units (ie: 8 ts units = 1 ms @ 8KHZ) */
+ 1.0 * rtcp->rx.jit/1000 * (srate_rx/1000),
+ 1.0 * rtcp->tx.jit/1000 * (srate_tx/1000),
+
+ sdp_media_laddr(s->sdp),
+ sdp_media_raddr(s->sdp)
+ );
+
+ if (a->tx.ac) {
+ err |= re_hprintf(pf, ";EN=%s/%d", a->tx.ac->name, srate_tx );
+ }
+ if (a->rx.ac) {
+ err |= re_hprintf(pf, ";DE=%s/%d", a->rx.ac->name, srate_rx );
+ }
+
+ return err;
+}
diff --git a/src/aufilt.c b/src/aufilt.c
new file mode 100644
index 0000000..240edee
--- /dev/null
+++ b/src/aufilt.c
@@ -0,0 +1,28 @@
+/**
+ * @file aufilt.c Audio Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+void aufilt_register(struct list *aufiltl, struct aufilt *af)
+{
+ if (!aufiltl || !af)
+ return;
+
+ list_append(aufiltl, &af->le, af);
+
+ info("aufilt: %s\n", af->name);
+}
+
+
+void aufilt_unregister(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_unlink(&af->le);
+}
diff --git a/src/aulevel.c b/src/aulevel.c
new file mode 100644
index 0000000..bc9a27a
--- /dev/null
+++ b/src/aulevel.c
@@ -0,0 +1,85 @@
+/**
+ * @file src/aulevel.c Audio level
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <math.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Generic routine to calculate RMS (Root-Mean-Square) from
+ * a set of signed 16-bit values
+ *
+ * \verbatim
+
+ .---------------
+ | N-1
+ | ----.
+ | \
+ | \ 2
+ | | s[n]
+ | /
+ | /
+ _ | ----'
+ \ | n=0
+ \ | ------------
+ \| N
+
+ \endverbatim
+ *
+ * @param data Array of signed 16-bit values
+ * @param len Number of values
+ *
+ * @return RMS value from 0 to 32768
+ */
+static double calc_rms(const int16_t *data, size_t len)
+{
+ double sum = 0;
+ size_t i;
+
+ if (!data || !len)
+ return .0;
+
+ for (i = 0; i < len; i++) {
+ const double sample = data[i];
+
+ sum += sample * sample;
+ }
+
+ return sqrt(sum / (double)len);
+}
+
+
+/**
+ * Calculate the audio level in dBov from a set of audio samples.
+ * dBov is the level, in decibels, relative to the overload point
+ * of the system
+ *
+ * @param sampv Audio samples
+ * @param sampc Number of audio samples
+ *
+ * @return Audio level expressed in dBov
+ */
+double aulevel_calc_dbov(const int16_t *sampv, size_t sampc)
+{
+ static const double peak = 32767.0;
+ double rms, dbov;
+
+ if (!sampv || !sampc)
+ return AULEVEL_MIN;
+
+ rms = calc_rms(sampv, sampc) / peak;
+
+ dbov = 20 * log10(rms);
+
+ if (dbov < AULEVEL_MIN)
+ dbov = AULEVEL_MIN;
+ else if (dbov > AULEVEL_MAX)
+ dbov = AULEVEL_MAX;
+
+ return dbov;
+}
diff --git a/src/auplay.c b/src/auplay.c
new file mode 100644
index 0000000..38a7010
--- /dev/null
+++ b/src/auplay.c
@@ -0,0 +1,109 @@
+/**
+ * @file auplay.c Audio Player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+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 auplayl List of Audio Players
+ * @param name Audio Player name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_register(struct auplay **app, struct list *auplayl,
+ 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 auplayl List of Audio Players
+ * @param name Name of the Audio Player to find
+ *
+ * @return Matching Audio Player if found, otherwise NULL
+ */
+const struct auplay *auplay_find(const struct list *auplayl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(auplayl); 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 auplayl List of Audio Players
+ * @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, struct list *auplayl,
+ const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay *ap;
+
+ ap = (struct auplay *)auplay_find(auplayl, 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..c1ca416
--- /dev/null
+++ b/src/ausrc.c
@@ -0,0 +1,108 @@
+/**
+ * @file ausrc.c Audio Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+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 ausrcl List of Audio Sources
+ * @param name Audio Source name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_register(struct ausrc **asp, struct list *ausrcl,
+ 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 ausrcl List of Audio Sources
+ * @param name Name of the Audio Source to find
+ *
+ * @return Matching Audio Source if found, otherwise NULL
+ */
+const struct ausrc *ausrc_find(const struct list *ausrcl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(ausrcl); 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 ausrcl List of Audio Sources
+ * @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 list *ausrcl,
+ 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(ausrcl, name);
+ if (!as)
+ return ENOENT;
+
+ return as->alloch(stp, as, ctx, prm, device, rh, errh, arg);
+}
diff --git a/src/baresip.c b/src/baresip.c
new file mode 100644
index 0000000..54a8c2c
--- /dev/null
+++ b/src/baresip.c
@@ -0,0 +1,248 @@
+/**
+ * @file baresip.c Top-level baresip struct
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * Top-level struct that holds all other subsystems
+ * (move this instance to main.c later)
+ */
+static struct baresip {
+ struct network *net;
+ struct contacts contacts;
+ struct commands *commands;
+ struct player *player;
+ struct message *message;
+ struct list mnatl;
+ struct list mencl;
+ struct list aucodecl;
+ struct list ausrcl;
+ struct list auplayl;
+ struct list aufiltl;
+ struct list vidcodecl;
+ struct list vidsrcl;
+ struct list vidispl;
+ struct list vidfiltl;
+ struct ui_sub uis;
+} baresip;
+
+
+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 int insmod_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ int err;
+
+ err = module_load(carg->prm);
+ if (err) {
+ return re_hprintf(pf, "insmod: ERROR: could not load module"
+ " '%s': %m\n", carg->prm, err);
+ }
+
+ return re_hprintf(pf, "loaded module %s\n", carg->prm);
+}
+
+
+static int rmmod_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ (void)pf;
+
+ module_unload(carg->prm);
+
+ return 0;
+}
+
+
+static const struct cmd corecmdv[] = {
+ {"quit", 'q', 0, "Quit", cmd_quit },
+ {"insmod", 0, CMD_PRM, "Load module", insmod_handler },
+ {"rmmod", 0, CMD_PRM, "Unload module", rmmod_handler },
+};
+
+
+int baresip_init(struct config *cfg, bool prefer_ipv6)
+{
+ int err;
+
+ if (!cfg)
+ return EINVAL;
+
+ baresip.net = mem_deref(baresip.net);
+
+ list_init(&baresip.mnatl);
+ list_init(&baresip.mencl);
+ list_init(&baresip.aucodecl);
+ list_init(&baresip.ausrcl);
+ list_init(&baresip.auplayl);
+ list_init(&baresip.vidcodecl);
+ list_init(&baresip.vidsrcl);
+ list_init(&baresip.vidispl);
+ list_init(&baresip.vidfiltl);
+
+ /* Initialise Network */
+ err = net_alloc(&baresip.net, &cfg->net,
+ prefer_ipv6 ? AF_INET6 : AF_INET);
+ if (err) {
+ warning("ua: network init failed: %m\n", err);
+ return err;
+ }
+
+ err = contact_init(&baresip.contacts);
+ if (err)
+ return err;
+
+ err = cmd_init(&baresip.commands);
+ if (err)
+ return err;
+
+ err = play_init(&baresip.player);
+ if (err)
+ return err;
+
+ err = message_init(&baresip.message);
+ if (err) {
+ warning("baresip: message init failed: %m\n", err);
+ return err;
+ }
+
+ err = cmd_register(baresip.commands, corecmdv, ARRAY_SIZE(corecmdv));
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void baresip_close(void)
+{
+ cmd_unregister(baresip.commands, corecmdv);
+
+ baresip.message = mem_deref(baresip.message);
+ baresip.player = mem_deref(baresip.player);
+ baresip.commands = mem_deref(baresip.commands);
+ contact_close(&baresip.contacts);
+
+ baresip.net = mem_deref(baresip.net);
+
+ ui_reset(&baresip.uis);
+}
+
+
+struct network *baresip_network(void)
+{
+ return baresip.net;
+}
+
+
+struct contacts *baresip_contacts(void)
+{
+ return &baresip.contacts;
+}
+
+
+struct commands *baresip_commands(void)
+{
+ return baresip.commands;
+}
+
+
+struct player *baresip_player(void)
+{
+ return baresip.player;
+}
+
+
+struct list *baresip_mnatl(void)
+{
+ return &baresip.mnatl;
+}
+
+
+struct list *baresip_mencl(void)
+{
+ return &baresip.mencl;
+}
+
+
+struct message *baresip_message(void)
+{
+ return baresip.message;
+}
+
+
+/**
+ * Get the list of Audio Codecs
+ *
+ * @return List of audio-codecs
+ */
+struct list *baresip_aucodecl(void)
+{
+ return &baresip.aucodecl;
+}
+
+
+struct list *baresip_ausrcl(void)
+{
+ return &baresip.ausrcl;
+}
+
+
+struct list *baresip_auplayl(void)
+{
+ return &baresip.auplayl;
+}
+
+
+struct list *baresip_aufiltl(void)
+{
+ return &baresip.aufiltl;
+}
+
+
+struct list *baresip_vidcodecl(void)
+{
+ return &baresip.vidcodecl;
+}
+
+
+struct list *baresip_vidsrcl(void)
+{
+ return &baresip.vidsrcl;
+}
+
+
+struct list *baresip_vidispl(void)
+{
+ return &baresip.vidispl;
+}
+
+
+struct list *baresip_vidfiltl(void)
+{
+ return &baresip.vidfiltl;
+}
+
+
+struct ui_sub *baresip_uis(void)
+{
+ return &baresip.uis;
+}
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..99bec4b
--- /dev/null
+++ b/src/call.c
@@ -0,0 +1,1877 @@
+/**
+ * @file src/call.c Call Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0xca11ca11
+#include "magic.h"
+
+
+#define FOREACH_STREAM \
+ for (le = call->streaml.head; le; le = le->next)
+
+/** Call constants */
+enum {
+ PTIME = 20, /**< Packet time for audio */
+};
+
+
+/** 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_conn; /**< Time when call initiated */
+ time_t time_stop; /**< Time when call stopped */
+ bool outgoing; /**< True if outgoing, false if incoming */
+ bool got_offer; /**< Got SDP Offer from Peer */
+ bool on_hold; /**< True if call is on hold */
+ 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 */
+
+ struct config_avt config_avt; /**< AVT config */
+ struct config_call config_call; /**< Call config */
+
+ uint32_t rtp_timeout_ms; /**< RTP Timeout in [ms] */
+ uint32_t linenum; /**< Line number from 1 to N */
+};
+
+
+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);
+ if (err) {
+ warning("call: start:"
+ " audio_encoder_set error: %m\n", err);
+ }
+ err |= audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ warning("call: start:"
+ " audio_decoder_set error: %m\n", err);
+ }
+
+ if (!err) {
+ err = audio_start(call->audio);
+ if (err) {
+ warning("call: start:"
+ " audio_start error: %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 && !video_is_started(call->video)) {
+ err = video_start(call->video, call->peer_uri);
+ }
+ if (err) {
+ warning("call: video stream error: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("call: video stream is disabled..\n");
+ }
+
+ if (call->bfcp) {
+ err = bfcp_start(call->bfcp);
+ if (err) {
+ warning("call: could not start BFCP: %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, call->config_call.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) {
+ warning("call: medianat '%s' failed: %m\n",
+ call->acc->mnatid, err);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%m", err);
+ return;
+ }
+ else if (scode) {
+ warning("call: medianat failed: %u %s\n", scode, reason);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%u %s",
+ scode, reason);
+ return;
+ }
+
+ info("call: media-nat `%s' established\n", call->acc->mnatid);
+
+ /* 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;
+
+ debug("call: update media\n");
+
+ /* 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) {
+ warning("call: video stream error: %m\n", err);
+ return err;
+ }
+
+ if (!video_is_started(call->video)) {
+ err = video_start(call->video, call->peer_uri);
+ if (err) {
+ warning("call: update: failed to"
+ " start video (%m)\n", err);
+ }
+ }
+ }
+ else if (call->video) {
+ info("video stream is disabled..\n");
+ video_stop(call->video);
+ }
+#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 ? KEYCODE_REL : key, call->arg);
+}
+
+
+static void audio_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ warning("call: audio device error: %m (%s)\n", err, str);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+
+
+#ifdef USE_VIDEO
+static void video_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ warning("call: video device error: %m (%s)\n", err, str);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+#endif
+
+
+static void menc_error_handler(int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ warning("call: mediaenc '%s' error: %m\n", call->acc->mencid, err);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed");
+}
+
+
+static void stream_error_handler(struct stream *strm, int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ info("call: error in \"%s\" rtp stream (%m)\n",
+ sdp_media_name(stream_sdpmedia(strm)), err);
+
+ call->scode = 701;
+ set_state(call, STATE_TERMINATED);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "rtp stream error");
+}
+
+
+static int assign_linenum(uint32_t *linenum, const struct list *lst)
+{
+ uint32_t num;
+
+ for (num=CALL_LINENUM_MIN; num<CALL_LINENUM_MAX; num++) {
+
+ if (!call_find_linenum(lst, num)) {
+ *linenum = num;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+
+/**
+ * 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 dnsc DNS Client
+ * @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,
+ struct dnsc *dnsc,
+ call_event_h *eh, void *arg)
+{
+ struct call *call;
+ struct le *le;
+ struct stream_param stream_prm;
+ 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 || !prm)
+ return EINVAL;
+
+ debug("call: alloc with params laddr=%j, af=%s, use_rtp=%d\n",
+ &prm->laddr, net_af2name(prm->af), prm->use_rtp);
+
+ memset(&stream_prm, 0, sizeof(stream_prm));
+ stream_prm.use_rtp = prm->use_rtp;
+
+ call = mem_zalloc(sizeof(*call), call_destructor);
+ if (!call)
+ return ENOMEM;
+
+ MAGIC_INIT(call);
+
+ call->config_avt = cfg->avt;
+ call->config_call = cfg->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, &prm->laddr);
+ 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,
+ dnsc, call->af,
+ acc->stun_host, acc->stun_port,
+ acc->stun_user, acc->stun_pass,
+ call->sdp, !got_offer,
+ mnat_handler, call);
+ if (err) {
+ warning("call: medianat 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) {
+ warning("call: mediaenc session: %m\n", err);
+ goto out;
+ }
+ }
+ }
+
+ /* Audio stream */
+ err = audio_alloc(&call->audio, &stream_prm, cfg, call,
+ call->sdp, ++label,
+ acc->mnat, call->mnats, acc->menc, call->mencs,
+ acc->ptime, account_aucodecl(call->acc), !got_offer,
+ 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(baresip_vidsrcl(), NULL)
+ || NULL != vidisp_find(baresip_vidispl(), NULL));
+
+ debug("call: use_video=%d\n", use_video);
+
+ /* Video stream */
+ if (use_video) {
+ err = video_alloc(&call->video, &stream_prm, cfg,
+ call, call->sdp, ++label,
+ acc->mnat, call->mnats,
+ acc->menc, call->mencs,
+ "main",
+ account_vidcodecl(call->acc),
+ video_error_handler, call);
+ 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);
+ }
+
+ FOREACH_STREAM {
+ struct stream *strm = le->data;
+ stream_set_error_handler(strm, stream_error_handler, call);
+ }
+
+ if (cfg->avt.rtp_timeout) {
+ call_enable_rtp_timeout(call, cfg->avt.rtp_timeout*1000);
+ }
+
+ err = assign_linenum(&call->linenum, lst);
+ if (err) {
+ warning("call: could not assign linenumber\n");
+ goto out;
+ }
+
+ /* NOTE: The new call must always be added to the tail of list,
+ * which indicates the current call.
+ */
+ 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);
+
+ call->outgoing = true;
+
+ /* 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;
+
+ if (call->config_avt.rtp_stats)
+ call_set_xrtpstat(call);
+
+ 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 '%s' with %s\n",
+ sip_dialog_callid(sipsess_dialog(call->sess)),
+ 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;
+
+ tmr_cancel(&call->tmr_inv);
+
+ 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) {
+ info("call: answer: call is not in incoming state (%s)\n",
+ state_name(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;
+
+ if (hold == call->on_hold)
+ return 0;
+
+ info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri);
+
+ call->on_hold = hold;
+
+ 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));
+ err |= re_hprintf(pf, " direction: %s\n",
+ call->outgoing ? "Outgoing" : "Incoming");
+
+ /* 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, "[line %u] %H %9s %s %s", call->linenum,
+ print_duration, call,
+ state_name(call->state),
+ call->on_hold ? "(on hold)" : " ",
+ call->peer_uri);
+}
+
+
+/**
+ * Send a DTMF digit to the peer
+ *
+ * @param call Call object
+ * @param key DTMF digit to send (KEYCODE_REL 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;
+}
+
+
+struct account *call_account(const struct call *call)
+{
+ return call ? call->acc : 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) {
+ warning("call: reinvite: could not decode SDP offer:"
+ " %m\n", 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);
+
+ if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed"))
+ (void)sdp_decode_multipart(&msg->ctyp.params, msg->mb);
+
+ err = sdp_decode(call->sdp, msg->mb, false);
+ if (err) {
+ warning("call: could not decode SDP answer: %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_stream_start(call, true);
+
+ if (call->rtp_timeout_ms) {
+
+ struct le *le;
+
+ FOREACH_STREAM {
+ struct stream *strm = le->data;
+ stream_enable_rtp_timeout(strm, call->rtp_timeout_ms);
+ }
+ }
+
+ /* the transferor will hangup this call */
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 200, "OK");
+ }
+
+ /* must be done last, the handler might deref this call */
+ call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri);
+}
+
+
+#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, KEYCODE_REL, call->arg);
+}
+
+
+static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (msg_ctype_cmp(&msg->ctyp, "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*#a-d]+", &sig);
+ err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur);
+
+ if (err || !pl_isset(&sig) || sig.l == 0) {
+ (void)sip_reply(sip, msg, 400, "Bad Request");
+ }
+ else {
+ char s = toupper(sig.p[0]);
+ uint32_t duration = pl_u32(&dur);
+
+ 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 (msg_ctype_cmp(&msg->ctyp,
+ "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) {
+ warning("call: 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) {
+ warning("call: 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);
+}
+
+
+static bool have_common_audio_codecs(const struct call *call)
+{
+ const struct sdp_format *sc;
+ struct aucodec *ac;
+
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (!sc)
+ return false;
+
+ ac = sc->data; /* note: this will exclude telephone-event */
+
+ return ac != NULL;
+}
+
+
+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;
+
+ call->outgoing = false;
+
+ 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) {
+ struct sdp_media *m;
+ const struct sa *raddr;
+
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ call->got_offer = true;
+
+ /*
+ * Each media description in the SDP answer MUST
+ * use the same network type as the corresponding
+ * media description in the offer.
+ *
+ * See RFC 6157
+ */
+ m = stream_sdpmedia(audio_strm(call->audio));
+ raddr = sdp_media_raddr(m);
+
+ if (sa_af(raddr) != call->af) {
+ info("call: incompatible address-family"
+ " (local=%s, remote=%s)\n",
+ net_af2name(call->af),
+ net_af2name(sa_af(raddr)));
+
+ sip_treply(NULL, uag_sip(), msg,
+ 488, "Not Acceptable Here");
+
+ call_event_handler(call, CALL_EVENT_CLOSED,
+ "Wrong address family");
+ return 0;
+ }
+
+ /* Check if we have any common audio codecs, after
+ * the SDP offer has been parsed
+ */
+ if (!have_common_audio_codecs(call)) {
+ info("call: no common audio codecs - rejected\n");
+
+ sip_treply(NULL, uag_sip(), msg,
+ 488, "Not Acceptable Here");
+
+ call_event_handler(call, CALL_EVENT_CLOSED,
+ "No audio codecs");
+
+ return 0;
+ }
+ }
+
+ 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) {
+ warning("call: sipsess_accept: %m\n", err);
+ return err;
+ }
+
+ set_state(call, STATE_INCOMING);
+
+ /* New call */
+ if (call->config_call.local_timeout) {
+ tmr_start(&call->tmr_inv, call->config_call.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/%r)\n",
+ msg->scode, &msg->reason, &msg->ctyp.type, &msg->ctyp.subtype);
+
+ 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 (msg_ctype_cmp(&msg->ctyp, "application", "sdp")
+ && mbuf_get_left(msg->mb)
+ && !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed") &&
+ !sdp_decode_multipart(&msg->ctyp.params, 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;
+ }
+
+ call_stream_stop(call);
+
+ if (media)
+ call_stream_start(call, false);
+
+ if (media)
+ call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri);
+ else
+ call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri);
+
+ if (media)
+ update_media(call);
+}
+
+
+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) {
+ warning("call: sipsess_connect: %m\n", err);
+ }
+
+ /* save call setup timer */
+ call->time_conn = time(NULL);
+
+ 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 current call setup time in seconds
+ *
+ * @param call Call object
+ *
+ * @return Call setup in seconds
+ */
+uint32_t call_setup_duration(const struct call *call)
+{
+ if (!call || !call->time_conn || call->time_conn <= 0 )
+ return 0;
+
+ return (uint32_t)(call->time_start - call->time_conn);
+}
+
+
+/**
+ * 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, const struct sa *laddr)
+{
+ if (!call)
+ return EINVAL;
+
+ sdp_session_set_laddr(call->sdp, laddr);
+
+ 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) {
+ warning("call: transfer failed: %u %r\n", sc, &reason);
+ call_event_handler(call, CALL_EVENT_TRANSFER_FAILED,
+ "%u %r", 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);
+ call_event_handler(call, CALL_EVENT_TRANSFER_FAILED,
+ "%u %r", 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) {
+ warning("call: 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;
+
+ if (eh)
+ call->eh = eh;
+
+ if (dtmfh)
+ call->dtmfh = dtmfh;
+
+ if (arg)
+ call->arg = arg;
+}
+
+
+void call_set_xrtpstat(struct call *call)
+{
+ if (!call)
+ return;
+
+ sipsess_set_close_headers(call->sess,
+ "X-RTP-Stat: %H\r\n",
+ audio_print_rtpstat, call->audio);
+}
+
+
+bool call_is_onhold(const struct call *call)
+{
+ return call ? call->on_hold : false;
+}
+
+
+bool call_is_outgoing(const struct call *call)
+{
+ return call ? call->outgoing : false;
+}
+
+
+void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms)
+{
+ if (!call)
+ return;
+
+ call->rtp_timeout_ms = timeout_ms;
+}
+
+
+/**
+ * Get the line number for this call
+ *
+ * @param call Call object
+ *
+ * @return Line number from 1 to N
+ */
+uint32_t call_linenum(const struct call *call)
+{
+ return call ? call->linenum : 0;
+}
+
+
+struct call *call_find_linenum(const struct list *calls, uint32_t linenum)
+{
+ struct le *le;
+
+ for (le = list_head(calls); le; le = le->next) {
+ struct call *call = le->data;
+
+ if (linenum == call->linenum)
+ return call;
+ }
+
+ return NULL;
+}
+
+
+void call_set_current(struct list *calls, struct call *call)
+{
+ if (!calls || !call)
+ return;
+
+ list_unlink(&call->le);
+ list_append(calls, &call->le, call);
+}
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..c9d1745
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,768 @@
+/**
+ * @file src/cmd.c Command Interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <ctype.h>
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ KEYCODE_DEL = 0x7f,
+ LONG_PREFIX = '/'
+};
+
+
+struct cmds {
+ struct le le;
+ const struct cmd *cmdv;
+ size_t cmdc;
+};
+
+struct cmd_ctx {
+ struct mbuf *mb;
+ const struct cmd *cmd;
+ bool is_long;
+};
+
+struct commands {
+ struct list cmdl; /**< List of command blocks (struct cmds) */
+};
+
+
+static int cmd_print_all(struct re_printf *pf,
+ const struct commands *commands,
+ bool print_long, bool print_short,
+ const char *match, size_t match_len);
+
+
+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 void commands_destructor(void *data)
+{
+ struct commands *commands = data;
+
+ list_flush(&commands->cmdl);
+}
+
+
+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;
+}
+
+
+/**
+ * Find a command block
+ *
+ * @param commands Commands container
+ * @param cmdv Command vector
+ *
+ * @return Command block if found, otherwise NULL
+ */
+struct cmds *cmds_find(const struct commands *commands,
+ const struct cmd *cmdv)
+{
+ struct le *le;
+
+ if (!commands || !cmdv)
+ return NULL;
+
+ for (le = commands->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(const struct commands *commands,
+ char key)
+{
+ struct le *le;
+
+ if (!commands)
+ return NULL;
+
+ for (le = commands->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 KEYCODE_ESC: return "ESC";
+ }
+
+ buf[0] = cmd->key;
+ buf[1] = '\0';
+
+ if (cmd->flags & CMD_PRM)
+ strncat(buf, " ..", sz-1);
+
+ return buf;
+}
+
+
+static size_t get_match_long(const struct commands *commands,
+ const struct cmd **cmdp,
+ const char *str, size_t len)
+{
+ struct le *le;
+ size_t nmatch = 0;
+
+ if (!commands)
+ return 0;
+
+ for (le = commands->cmdl.head; le; le = le->next) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (!str_isset(cmd->name))
+ continue;
+
+ if (str_len(cmd->name) >= len &&
+ 0 == memcmp(cmd->name, str, len)) {
+
+ ++nmatch;
+ *cmdp = cmd;
+ }
+ }
+ }
+
+ return nmatch;
+}
+
+
+static int editor_input(struct commands *commands, struct mbuf *mb, char key,
+ struct re_printf *pf, bool *del, bool is_long)
+{
+ int err = 0;
+
+ switch (key) {
+
+ case KEYCODE_ESC:
+ *del = true;
+ return re_hprintf(pf, "\nCancel\n");
+
+ case KEYCODE_NONE:
+ case KEYCODE_REL:
+ break;
+
+ case '\n':
+ *del = true;
+ return re_hprintf(pf, "\n");
+
+ case '\b':
+ case KEYCODE_DEL:
+ if (mb->pos > 0) {
+ err |= re_hprintf(pf, "\b ");
+ mb->pos = mb->end = (mb->pos - 1);
+ }
+ break;
+
+ case '\t':
+ if (is_long) {
+ const struct cmd *cmd = NULL;
+ size_t n;
+
+ err = re_hprintf(pf,
+ "TAB completion for \"%b\":\n",
+ mb->buf, mb->end);
+ if (err)
+ return err;
+
+ /* Find all long commands that matches the N
+ * first characters of the input string.
+ *
+ * If the number of matches is exactly one,
+ * we can regard it as TAB completion.
+ */
+
+ err = cmd_print_all(pf, commands, true, false,
+ (char *)mb->buf, mb->end);
+ if (err)
+ return err;
+
+ n = get_match_long(commands, &cmd,
+ (char *)mb->buf, mb->end);
+ if (n == 1 && cmd) {
+
+ mb->pos = 0;
+ mbuf_write_str(mb, cmd->name);
+ }
+ else if (n == 0) {
+ err = re_hprintf(pf, "(none)\n");
+ }
+ }
+ else {
+ err = mbuf_write_u8(mb, key);
+ }
+ break;
+
+ default:
+ err = mbuf_write_u8(mb, key);
+ break;
+ }
+
+ if (is_long) {
+ err |= re_hprintf(pf, "\r/%b",
+ mb->buf, mb->end);
+ }
+ else
+ 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, void *data)
+{
+ struct cmd_arg arg;
+ int err;
+
+ memset(&arg, 0, sizeof(arg));
+
+ mb->pos = 0;
+ err = mbuf_strdup(mb, &arg.prm, mb->end);
+ if (err)
+ return err;
+
+ arg.key = cmd->key;
+ arg.complete = compl;
+ arg.data = data;
+
+ err = cmd->h(pf, &arg);
+
+ mem_deref(arg.prm);
+
+ return err;
+}
+
+
+/**
+ * Process long commands
+ *
+ * @param commands Commands container
+ * @param str Input string
+ * @param len Length of input string
+ * @param pf_resp Print function for response
+ * @param data Application data
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process_long(struct commands *commands, const char *str, size_t len,
+ struct re_printf *pf_resp, void *data)
+{
+ struct cmd_arg arg;
+ const struct cmd *cmd_long;
+ char *name = NULL, *prm = NULL;
+ struct pl pl_name, pl_prm;
+ int err;
+
+ if (!str || !len)
+ return EINVAL;
+
+ memset(&arg, 0, sizeof(arg));
+
+ err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm);
+ if (err) {
+ return err;
+ }
+
+ err = pl_strdup(&name, &pl_name);
+ if (pl_isset(&pl_prm))
+ err |= pl_strdup(&prm, &pl_prm);
+ if (err)
+ goto out;
+
+ cmd_long = cmd_find_long(commands, name);
+ if (cmd_long) {
+
+ arg.key = LONG_PREFIX;
+ arg.prm = prm;
+ arg.complete = true;
+ arg.data = data;
+
+ if (cmd_long->h)
+ err = cmd_long->h(pf_resp, &arg);
+ }
+ else {
+ err = re_hprintf(pf_resp, "command not found (%s)\n", name);
+ }
+
+ out:
+ mem_deref(name);
+ mem_deref(prm);
+
+ return err;
+}
+
+
+static int cmd_process_edit(struct commands *commands,
+ struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf, void *data)
+{
+ struct cmd_ctx *ctx;
+ bool compl = (key == '\n'), del = false;
+ int err;
+
+ if (!ctxp)
+ return EINVAL;
+
+ ctx = *ctxp;
+
+ err = editor_input(commands, ctx->mb, key, pf, &del, ctx->is_long);
+ if (err)
+ return err;
+
+ if (ctx->is_long) {
+
+ if (compl) {
+
+ err = cmd_process_long(commands,
+ (char *)ctx->mb->buf,
+ ctx->mb->end,
+ pf, data);
+ }
+ }
+ else {
+ if (compl ||
+ (ctx->cmd && ctx->cmd->flags & CMD_PROG))
+ err = cmd_report(ctx->cmd, pf, ctx->mb, compl, data);
+ }
+
+ if (del)
+ *ctxp = mem_deref(*ctxp);
+
+ return err;
+}
+
+
+/**
+ * Register commands
+ *
+ * @param commands Commands container
+ * @param cmdv Array of commands
+ * @param cmdc Number of commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_register(struct commands *commands,
+ const struct cmd *cmdv, size_t cmdc)
+{
+ struct cmds *cmds;
+ size_t i;
+
+ if (!commands || !cmdv || !cmdc)
+ return EINVAL;
+
+ cmds = cmds_find(commands, cmdv);
+ if (cmds)
+ return EALREADY;
+
+ /* verify that command is not registered */
+ for (i=0; i<cmdc; i++) {
+ const struct cmd *cmd = &cmdv[i];
+
+ if (cmd->key) {
+ const struct cmd *x = cmd_find_by_key(commands,
+ cmd->key);
+ if (x) {
+ warning("short command '%c' already"
+ " registered as \"%s\"\n",
+ x->key, x->desc);
+ return EALREADY;
+ }
+ }
+
+ if (cmd->key == LONG_PREFIX) {
+ warning("cmd: cannot register command with"
+ " short key '%c'\n", cmd->key);
+ return EINVAL;
+ }
+
+ if (str_isset(cmd->name) &&
+ cmd_find_long(commands, cmd->name)) {
+ warning("cmd: long command '%s' already registered\n",
+ cmd->name);
+ return EINVAL;
+ }
+ }
+
+ cmds = mem_zalloc(sizeof(*cmds), destructor);
+ if (!cmds)
+ return ENOMEM;
+
+ cmds->cmdv = cmdv;
+ cmds->cmdc = cmdc;
+
+ list_append(&commands->cmdl, &cmds->le, cmds);
+
+ return 0;
+}
+
+
+/**
+ * Unregister commands
+ *
+ * @param commands Commands container
+ * @param cmdv Array of commands
+ */
+void cmd_unregister(struct commands *commands, const struct cmd *cmdv)
+{
+ mem_deref(cmds_find(commands, cmdv));
+}
+
+
+/**
+ * Find a long command
+ *
+ * @param commands Commands container
+ * @param name Name of command, excluding prefix
+ *
+ * @return Command if found, NULL if not found
+ */
+const struct cmd *cmd_find_long(const struct commands *commands,
+ const char *name)
+{
+ struct le *le;
+
+ if (!commands || !name)
+ return NULL;
+
+ for (le = commands->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 (0 == str_casecmp(name, cmd->name) && cmd->h)
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Process input characters to the command system
+ *
+ * @param commands Commands container
+ * @param ctxp Pointer to context for editor (optional)
+ * @param key Input character
+ * @param pf Print function
+ * @param data Application data
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf, void *data)
+{
+ const struct cmd *cmd;
+
+ if (!commands)
+ return EINVAL;
+
+ if (key == KEYCODE_NONE) {
+ warning("cmd: process: illegal keycode NONE\n");
+ return EINVAL;
+ }
+
+ /* are we in edit-mode? */
+ if (ctxp && *ctxp) {
+
+ if (key == KEYCODE_REL)
+ return 0;
+
+ return cmd_process_edit(commands, ctxp, key, pf, data);
+ }
+
+ cmd = cmd_find_by_key(commands, key);
+ if (cmd) {
+ struct cmd_arg arg;
+
+ /* check for parameters */
+ if (cmd->flags & CMD_PRM) {
+
+ int err = 0;
+
+ if (ctxp) {
+ err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+ }
+
+ key = isdigit(key) ? key : KEYCODE_REL;
+
+ return cmd_process_edit(commands, ctxp, key, pf, data);
+ }
+
+ arg.key = key;
+ arg.prm = NULL;
+ arg.complete = true;
+ arg.data = data;
+
+ return cmd->h(pf, &arg);
+ }
+ else if (key == LONG_PREFIX) {
+
+ int err;
+
+ err = re_hprintf(pf, "%c", LONG_PREFIX);
+ if (err)
+ return err;
+
+ if (!ctxp) {
+ warning("cmd: ctxp is required\n");
+ return EINVAL;
+ }
+
+ err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+
+ (*ctxp)->is_long = true;
+
+ return 0;
+ }
+ else if (key == '\t') {
+ return cmd_print_all(pf, commands, false, true, NULL, 0);
+ }
+
+ if (key == KEYCODE_REL)
+ return 0;
+
+ return cmd_print(pf, commands);
+}
+
+
+struct cmd_sort {
+ struct le le;
+ const struct cmd *cmd;
+};
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct cmd_sort *cs1 = le1->data;
+ struct cmd_sort *cs2 = le2->data;
+ const struct cmd *cmd1 = cs1->cmd;
+ const struct cmd *cmd2 = cs2->cmd;
+ bool print_long = *(bool *)arg;
+
+ if (print_long) {
+ return str_casecmp(cs2->cmd->name ? cs2->cmd->name : "",
+ cs1->cmd->name ? cs1->cmd->name : "") >= 0;
+ }
+ else {
+ return tolower(cmd2->key) >= tolower(cmd1->key);
+ }
+}
+
+
+static int cmd_print_all(struct re_printf *pf,
+ const struct commands *commands,
+ bool print_long, bool print_short,
+ const char *match, size_t match_len)
+{
+ struct list sortedl = LIST_INIT;
+ struct le *le;
+ size_t width_long = 1;
+ size_t width_short = 5;
+ char fmt[64];
+ char buf[16];
+ int err = 0;
+
+ if (!commands)
+ return EINVAL;
+
+ for (le = commands->cmdl.head; le; le = le->next) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+ struct cmd_sort *cs;
+
+ if (match && match_len) {
+
+ if (str_len(cmd->name) >= match_len &&
+ 0 == memcmp(cmd->name, match, match_len)) {
+ /* Match */
+ }
+ else {
+ continue;
+ }
+ }
+
+ if (!str_isset(cmd->desc))
+ continue;
+
+ if (print_short && !print_long) {
+
+ if (cmd->key == KEYCODE_NONE)
+ continue;
+ }
+
+ cs = mem_zalloc(sizeof(*cs), NULL);
+ if (!cs) {
+ err = ENOMEM;
+ goto out;
+ }
+ cs->cmd = cmd;
+
+ list_append(&sortedl, &cs->le, cs);
+
+ width_long = max(width_long, 1+str_len(cmd->name)+3);
+ }
+ }
+
+ list_sort(&sortedl, sort_handler, &print_long);
+
+ if (re_snprintf(fmt, sizeof(fmt),
+ " %%-%zus %%-%zus %%s\n",
+ width_long, width_short) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (le = sortedl.head; le; le = le->next) {
+ struct cmd_sort *cs = le->data;
+ const struct cmd *cmd = cs->cmd;
+ char namep[64] = "";
+
+ if (print_long && str_isset(cmd->name)) {
+ re_snprintf(namep, sizeof(namep), "%c%s%s",
+ LONG_PREFIX, cmd->name,
+ (cmd->flags & CMD_PRM) ? " .." : "");
+ }
+
+ err |= re_hprintf(pf, fmt,
+ namep,
+ (print_short && cmd->key)
+ ? cmd_name(buf, sizeof(buf), cmd)
+ : "",
+ cmd->desc);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ out:
+ list_flush(&sortedl);
+ return err;
+}
+
+
+/**
+ * Print a list of available commands
+ *
+ * @param pf Print function
+ * @param commands Commands container
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_print(struct re_printf *pf, const struct commands *commands)
+{
+ int err = 0;
+
+ if (!pf)
+ return EINVAL;
+
+ err |= re_hprintf(pf, "--- Help ---\n");
+ err |= cmd_print_all(pf, commands, true, true, NULL, 0);
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Initialize the commands subsystem.
+ *
+ * @param commandsp Pointer to allocated commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_init(struct commands **commandsp)
+{
+ struct commands *commands;
+
+ if (!commandsp)
+ return EINVAL;
+
+ commands = mem_zalloc(sizeof(*commands), commands_destructor);
+ if (!commands)
+ return ENOMEM;
+
+ list_init(&commands->cmdl);
+
+ *commandsp = commands;
+
+ return 0;
+}
diff --git a/src/conf.c b/src/conf.c
new file mode 100644
index 0000000..2046216
--- /dev/null
+++ b/src/conf.c
@@ -0,0 +1,386 @@
+/**
+ * @file conf.c Configuration utils
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#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)
+#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
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_parse(const char *filename, confline_h *ch, void *arg)
+{
+ 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, arg);
+ }
+
+ 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[FS_PATH_MAX];
+ int err;
+
+ /* Use explicit conf path */
+ if (conf_path) {
+ if (re_snprintf(path, sz, "%s", conf_path) < 0)
+ return ENOMEM;
+ return 0;
+ }
+
+#ifdef CONFIG_PATH
+ str_ncpy(buf, CONFIG_PATH, sizeof(buf));
+ (void)err;
+#else
+ err = fs_gethome(buf, sizeof(buf));
+ if (err)
+ return err;
+#endif
+
+ 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;
+
+ /* note: second value may be quoted */
+ 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[FS_PATH_MAX], file[FS_PATH_MAX];
+ int err;
+
+#if defined (WIN32)
+ 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;
+ }
+
+ conf_obj = mem_deref(conf_obj);
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = config_parse_conf(conf_config(), conf_obj);
+ if (err)
+ goto out;
+
+ out:
+ return err;
+}
+
+
+/**
+ * Load all modules from config file
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @note conf_configure must be called first
+ */
+int conf_modules(void)
+{
+ int err;
+
+ err = module_init(conf_obj);
+ if (err) {
+ warning("conf: configure module parse error (%m)\n", err);
+ goto out;
+ }
+
+ print_populated("audio codec", list_count(baresip_aucodecl()));
+ print_populated("audio filter", list_count(baresip_aufiltl()));
+#ifdef USE_VIDEO
+ print_populated("video codec", list_count(baresip_vidcodecl()));
+ print_populated("video filter", list_count(baresip_vidfiltl()));
+#endif
+
+ out:
+ return err;
+}
+
+
+/**
+ * Get the current configuration object
+ *
+ * @return Config object
+ *
+ * @note It is only available after init and before conf_close()
+ */
+struct conf *conf_cur(void)
+{
+ if (!conf_obj) {
+ warning("conf: no config object\n");
+ }
+ return conf_obj;
+}
+
+
+void conf_close(void)
+{
+ conf_obj = mem_deref(conf_obj);
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..ce748d3
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,925 @@
+/**
+ * @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 */
+
+
+#undef SA_INIT
+#define SA_INIT { { {0} }, 0}
+
+
+#ifndef PREFIX
+#define PREFIX "/usr"
+#endif
+
+
+/** Core Run-time Configuration - populated from config file */
+static struct config core_config = {
+
+ /** SIP User-Agent */
+ {
+ 16,
+ "",
+ "",
+ ""
+ },
+
+ /** Call config */
+ {
+ 120,
+ 4
+ },
+
+ /** Audio */
+ {
+ PREFIX "/share/baresip",
+ "","",
+ "","",
+ "","",
+ {8000, 48000},
+ {1, 2},
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ AUDIO_MODE_POLL,
+ false,
+ AUFMT_S16LE,
+ AUFMT_S16LE,
+ },
+
+#ifdef USE_VIDEO
+ /** Video */
+ {
+ "", "",
+ "", "",
+ 352, 288,
+ 500000,
+ 25,
+ true,
+ },
+#endif
+
+ /** Audio/Video Transport */
+ {
+ 0xb8,
+ {1024, 49152},
+ {0, 0},
+ true,
+ false,
+ {5, 10},
+ false,
+ 0
+ },
+
+ /* Network */
+ {
+ "",
+ { {""} },
+ 0
+ },
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ {
+ ""
+ },
+#endif
+
+ /* SDP */
+ {
+ false
+ },
+};
+
+
+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 config_net *cfg = arg;
+ const size_t max_count = ARRAY_SIZE(cfg->nsv);
+ int err;
+
+ if (cfg->nsc >= max_count) {
+ warning("config: too many DNS nameservers (max %zu)\n",
+ max_count);
+ return EOVERFLOW;
+ }
+
+ /* Append dns_server to the network config */
+ err = pl_strcpy(pl, cfg->nsv[cfg->nsc].addr,
+ sizeof(cfg->nsv[0].addr));
+ if (err) {
+ warning("config: dns_server: could not copy string (%r)\n",
+ pl);
+ return err;
+ }
+
+ ++cfg->nsc;
+
+ return 0;
+}
+
+
+static enum aufmt resolve_aufmt(const struct pl *fmt)
+{
+ if (0 == pl_strcasecmp(fmt, "s16")) return AUFMT_S16LE;
+ if (0 == pl_strcasecmp(fmt, "float")) return AUFMT_FLOAT;
+ if (0 == pl_strcasecmp(fmt, "s24_3le")) return AUFMT_S24_3LE;
+
+ /* XXX remove this after librem is fixed */
+ if (0 == pl_strcasecmp(fmt, "s16le")) return AUFMT_S16LE;
+
+ return (enum aufmt)-1;
+}
+
+
+static int conf_get_aufmt(const struct conf *conf, const char *name,
+ int *fmtp)
+{
+ struct pl pl;
+ int fmt;
+ int err;
+
+ err = conf_get(conf, name, &pl);
+ if (err)
+ return err;
+
+ fmt = resolve_aufmt(&pl);
+ if (fmt == -1) {
+ warning("config: %s: sample format not supported"
+ " (%r)\n", name, &pl);
+ return EINVAL;
+ }
+
+ *fmtp = fmt;
+
+ return 0;
+}
+
+
+/**
+ * Parse the core configuration file and update baresip core config
+ *
+ * @param cfg Baresip core config to update
+ * @param conf Configuration file to parse
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+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};
+ struct pl txmode;
+ 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);
+ }
+ }
+
+ /* 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));
+
+ /* Call */
+ (void)conf_get_u32(conf, "call_local_timeout",
+ &cfg->call.local_timeout);
+ (void)conf_get_u32(conf, "call_max_calls",
+ &cfg->call.max_calls);
+
+ /* Audio */
+ (void)conf_get_str(conf, "audio_path", cfg->audio.audio_path,
+ sizeof(cfg->audio.audio_path));
+ (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;
+
+ if (0 == conf_get(conf, "audio_txmode", &txmode)) {
+
+ if (0 == pl_strcasecmp(&txmode, "poll"))
+ cfg->audio.txmode = AUDIO_MODE_POLL;
+ else if (0 == pl_strcasecmp(&txmode, "thread"))
+ cfg->audio.txmode = AUDIO_MODE_THREAD;
+ else {
+ warning("unsupported audio txmode (%r)\n", &txmode);
+ }
+ }
+
+ (void)conf_get_bool(conf, "audio_level", &cfg->audio.level);
+
+ conf_get_aufmt(conf, "ausrc_format", &cfg->audio.src_fmt);
+ conf_get_aufmt(conf, "auplay_format", &cfg->audio.play_fmt);
+
+#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);
+ (void)conf_get_bool(conf, "video_fullscreen", &cfg->video.fullscreen);
+#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 *= 1000;
+ cfg->avt.rtp_bw.max *= 1000;
+ }
+ (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);
+ (void)conf_get_u32(conf, "rtp_timeout", &cfg->avt.rtp_timeout);
+
+ if (err) {
+ warning("config: configure parse error (%m)\n", err);
+ }
+
+ /* Network */
+ (void)conf_apply(conf, "dns_server", dns_server_handler, &cfg->net);
+ (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
+
+ /* SDP */
+ (void)conf_get_bool(conf, "sdp_ebuacip", &cfg->sdp.ebuacip);
+
+ return err;
+}
+
+
+/**
+ * Print the baresip core config
+ *
+ * @param pf Print function
+ * @param cfg Baresip core config
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int config_print(struct re_printf *pf, const struct config *cfg)
+{
+ int err;
+
+ if (!cfg)
+ return 0;
+
+ err = re_hprintf(pf,
+ "\n"
+ "# SIP\n"
+ "sip_trans_bsize\t\t%u\n"
+ "sip_listen\t\t%s\n"
+ "sip_certificate\t%s\n"
+ "\n"
+ "# Call\n"
+ "call_local_timeout\t%u\n"
+ "call_max_calls\t%u\n"
+ "\n"
+ "# Audio\n"
+ "audio_path\t\t%s\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"
+ "audio_level\t\t%s\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"
+ "rtp_timeout\t\t%u # in seconds\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->sip.trans_bsize, cfg->sip.local, cfg->sip.cert,
+
+ cfg->call.local_timeout,
+ cfg->call.max_calls,
+
+ cfg->audio.audio_path,
+ 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,
+ cfg->audio.level ? "yes" : "no",
+
+#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->avt.rtp_timeout,
+
+ cfg->net.ifname
+
+#ifdef USE_VIDEO
+ ,cfg->bfcp.proto
+#endif
+ );
+
+ return err;
+}
+
+
+static const char *default_audio_device(void)
+{
+#if defined (ANDROID)
+ return "opensles,nil";
+#elif defined (DARWIN)
+ return "coreaudio,nil";
+#elif defined (FREEBSD)
+ return "oss,/dev/dsp";
+#elif defined (OPENBSD)
+ return "sndio,default";
+#elif defined (WIN32)
+ return "winwave,nil";
+#else
+ return "alsa,default";
+#endif
+}
+
+
+#ifdef USE_VIDEO
+static const char *default_video_device(void)
+{
+#ifdef DARWIN
+
+#ifdef QTCAPTURE_RUNLOOP
+ return "qtcapture,nil";
+#else
+ return "avcapture,nil";
+#endif
+
+#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"
+#ifdef HAVE_EPOLL
+ ", epoll .."
+#endif
+#ifdef HAVE_KQUEUE
+ ", kqueue .."
+#endif
+ "\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"
+ "# Call\n"
+ "call_local_timeout\t%u\n"
+ "call_max_calls\t%u\n"
+ "\n"
+ "# Audio\n"
+#if defined (PREFIX)
+ "#audio_path\t\t" PREFIX "/share/baresip\n"
+#else
+ "#audio_path\t\t/usr/share/baresip\n"
+#endif
+ "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"
+ "#audio_txmode\t\tpoll\t\t# poll, thread\n"
+ "audio_level\t\tno\n"
+ "ausrc_format\t\ts16\t\t# s16, float, ..\n"
+ "auplay_format\t\ts16\t\t# s16, float, ..\n"
+ ,
+ poll_method_name(poll_method_best()),
+ cfg->call.local_timeout,
+ cfg->call.max_calls,
+ 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"
+ "video_fullscreen\tyes\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"
+ "#rtp_timeout\t\t60\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;
+ size_t i;
+
+ for (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;
+}
+
+
+/**
+ * Write the baresip core config template to a file
+ *
+ * @param file Filename of output file
+ * @param cfg Baresip core config
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+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, "#module\t\t\t" MOD_PRE "mpa" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "codec2" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "ilbc" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "isac" 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 (ANDROID)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opensles" MOD_EXT "\n");
+#elif defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "audiounit" MOD_EXT "\n");
+#elif defined (FREEBSD)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n");
+#elif defined (OPENBSD)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "sndio" MOD_EXT "\n");
+#elif defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "pulse" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "jack" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aubridge" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aufile" MOD_EXT "\n");
+
+#ifdef USE_VIDEO
+
+ (void)re_fprintf(f, "\n# Video codec Modules (in order)\n");
+#ifdef USE_AVCODEC
+ (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 "vp8" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vp9" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "h265" 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, "#module\t\t\t" MOD_PRE "snapshot" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "swscale" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vidinfo" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video source modules\n");
+#if defined (DARWIN)
+
+#ifdef QTCAPTURE_RUNLOOP
+ (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 "avcapture" MOD_EXT "\n");
+#endif
+
+#elif defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "dshow" 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");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2_codec" MOD_EXT "\n");
+#endif
+#ifdef USE_AVFORMAT
+ (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, "#module\t\t\t" MOD_PRE "vidbridge" 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
+#ifdef LINUX
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "directfb" 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");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "fakevideo" MOD_EXT "\n");
+
+#endif /* USE_VIDEO */
+
+ (void)re_fprintf(f, "\n# Audio/Video source modules\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst1" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst_video1" 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, "#module\t\t\t" MOD_PRE "zrtp" 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 "debug_cmd"MOD_EXT"\n");
+#ifdef LINUX
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "dtmfio"MOD_EXT"\n");
+#endif
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "echo"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t\t" MOD_PRE "gtk" 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");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mqtt" 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");
+ (void)re_fprintf(f, "cons_listen\t\t0.0.0.0:5555\n");
+
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "http_listen\t\t0.0.0.0:8000\n");
+
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "evdev_device\t\t/dev/input/event0\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_mode_nb\t\t3 # 1-6\n");
+ (void)re_fprintf(f, "speex_mode_wb\t\t6 # 1-6\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# Opus codec parameters\n");
+ (void)re_fprintf(f, "opus_bitrate\t\t28000 # 6000-510000\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");
+
+ (void)re_fprintf(f,
+ "\n# ZRTP\n"
+ "#zrtp_hash\t\tno # Disable SDP zrtp-hash "
+ "(not recommended)\n");
+
+ (void)re_fprintf(f,
+ "\n# Menu\n"
+ "#redial_attempts\t\t3 # Num or <inf>\n"
+ "#redial_delay\t\t5 # Delay in seconds\n");
+
+ if (f)
+ (void)fclose(f);
+
+ return err;
+}
+
+
+/**
+ * Get the baresip core config
+ *
+ * @return Core config
+ */
+struct config *conf_config(void)
+{
+ return &core_config;
+}
diff --git a/src/contact.c b/src/contact.c
new file mode 100644
index 0000000..a84890c
--- /dev/null
+++ b/src/contact.c
@@ -0,0 +1,338 @@
+/**
+ * @file src/contact.c Contacts handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+
+
+enum access {
+ ACCESS_UNKNOWN = 0,
+ ACCESS_BLOCK,
+ ACCESS_ALLOW
+};
+
+struct contact {
+ struct le le;
+ struct le he; /* hash-element with key 'auri' */
+ struct sip_addr addr;
+ char *buf;
+ enum presence_status status;
+ enum access access;
+};
+
+
+static void destructor(void *arg)
+{
+ struct contact *c = arg;
+
+ hash_unlink(&c->he);
+ list_unlink(&c->le);
+ mem_deref(c->buf);
+}
+
+
+/**
+ * Add a contact
+ *
+ * @param contacts Contacts container
+ * @param contactp Pointer to allocated contact (optional)
+ * @param addr Contact in SIP address format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_add(struct contacts *contacts,
+ struct contact **contactp, const struct pl *addr)
+{
+ struct contact *c;
+ struct pl pl;
+ int err;
+
+ if (!contacts)
+ return EINVAL;
+
+ 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;
+ }
+
+ if (0 == msg_param_decode(&c->addr.params, "access", &pl)) {
+
+ if (0 == pl_strcasecmp(&pl, "block")) {
+ c->access = ACCESS_BLOCK;
+ }
+ else if (0 == pl_strcasecmp(&pl, "allow")) {
+ c->access = ACCESS_ALLOW;
+ }
+ else {
+ warning("contact: unknown 'access=%r' for '%r'\n",
+ &pl, addr);
+ err = EINVAL;
+ goto out;
+ }
+ }
+ else
+ c->access = ACCESS_UNKNOWN;
+
+ c->status = PRESENCE_UNKNOWN;
+
+ list_append(&contacts->cl, &c->le, c);
+ hash_append(contacts->cht, hash_joaat_pl(&c->addr.auri), &c->he, c);
+
+ if (contacts->handler)
+ contacts->handler(c, false, contacts->handler_arg);
+
+ out:
+ if (err)
+ mem_deref(c);
+ else if (contactp)
+ *contactp = c;
+
+ return err;
+}
+
+
+/**
+ * Remove a contact
+ *
+ * @param contacts Contacts container
+ * @param contact Contact to be removed
+ */
+void contact_remove(struct contacts *contacts, struct contact *contact)
+{
+ if (!contacts || !contact)
+ return;
+
+ if (contacts->handler)
+ contacts->handler(contact, true, contacts->handler_arg);
+
+ hash_unlink(&contact->he);
+ list_unlink(&contact->le);
+
+ mem_deref(contact);
+}
+
+
+void contact_set_update_handler(struct contacts *contacts,
+ contact_update_h *updateh, void *arg)
+{
+ if (!contacts) {
+ return;
+ }
+
+ contacts->handler = updateh;
+ contacts->handler_arg = arg;
+}
+
+
+/**
+ * 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
+ *
+ * @param contacts Contacts container
+ *
+ * @return List of contacts
+ */
+struct list *contact_list(const struct contacts *contacts)
+{
+ if (!contacts)
+ return NULL;
+
+ return (struct list *)&contacts->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;
+}
+
+enum presence_status contact_presence(const struct contact *c)
+{
+ if (!c)
+ return PRESENCE_UNKNOWN;
+
+ return c->status;
+}
+
+const char *contact_presence_str(enum presence_status status)
+{
+ switch (status) {
+
+ default:
+ case PRESENCE_UNKNOWN: return "\x1b[32mUnknown\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, const struct contacts *contacts)
+{
+ const struct list *lst;
+ struct le *le;
+ int err;
+
+ if (!contacts)
+ return 0;
+
+ lst = contact_list(contacts);
+
+ err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n",
+ list_count(lst));
+
+ for (le = list_head(lst); 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;
+}
+
+
+/**
+ * Initialise the contacts sub-system
+ *
+ * @param contacts Contacts container
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_init(struct contacts *contacts)
+{
+ int err = 0;
+
+ if (!contacts)
+ return EINVAL;
+
+ memset(contacts, 0, sizeof(*contacts));
+
+ list_init(&contacts->cl);
+
+ err = hash_alloc(&contacts->cht, 32);
+
+ return err;
+}
+
+
+/**
+ * @param contacts Contacts container
+ *
+ * Close the contacts sub-system
+ */
+void contact_close(struct contacts *contacts)
+{
+ if (!contacts)
+ return;
+
+ hash_clear(contacts->cht);
+ contacts->cht = mem_deref(contacts->cht);
+ list_flush(&contacts->cl);
+}
+
+
+static bool find_handler(struct le *le, void *arg)
+{
+ struct contact *c = le->data;
+
+ return 0 == pl_strcmp(&c->addr.auri, arg);
+}
+
+
+/**
+ * Lookup a SIP uri in all registered contacts
+ *
+ * @param contacts Contacts container
+ * @param uri SIP uri to lookup
+ *
+ * @return Matching contact if found, otherwise NULL
+ */
+struct contact *contact_find(const struct contacts *contacts, const char *uri)
+{
+ if (!contacts)
+ return NULL;
+
+ return list_ledata(hash_lookup(contacts->cht, hash_joaat_str(uri),
+ find_handler, (void *)uri));
+}
+
+
+/**
+ * Check the access parameter of a SIP uri
+ *
+ * - Matching uri has first presedence
+ * - Global <sip:*@*> uri has second presedence
+ *
+ * @param contacts Contacts container
+ * @param uri SIP uri to check for access
+ *
+ * @return True if blocked, false if allowed
+ */
+bool contact_block_access(const struct contacts *contacts, const char *uri)
+{
+ struct contact *c;
+
+ c = contact_find(contacts, uri);
+ if (c && c->access != ACCESS_UNKNOWN)
+ return c->access == ACCESS_BLOCK;
+
+ c = contact_find(contacts, "sip:*@*");
+ if (c && c->access != ACCESS_UNKNOWN)
+ return c->access == ACCESS_BLOCK;
+
+ return false;
+}
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..a390e0c
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,541 @@
+/**
+ * @file core.h Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#include <limits.h>
+
+
+/* max bytes in pathname */
+#if defined (PATH_MAX)
+#define FS_PATH_MAX PATH_MAX
+#elif defined (_POSIX_PATH_MAX)
+#define FS_PATH_MAX _POSIX_PATH_MAX
+#else
+#define FS_PATH_MAX 512
+#endif
+
+
+/**
+ * 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 */
+};
+
+
+/* forward declarations */
+struct stream_param;
+
+
+/*
+ * Account
+ */
+
+
+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 *outboundv[2]; /**< Optional SIP outbound proxies */
+ uint32_t ptime; /**< Configured packet time in [ms] */
+ uint32_t regint; /**< Registration interval in [seconds] */
+ uint32_t pubint; /**< Publication 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 {
+ const 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 stream_param *stream_prm,
+ 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, bool offerer,
+ 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);
+int audio_send_digit(struct audio *a, char key);
+void audio_sdp_attr_decode(struct audio *a);
+int audio_print_rtpstat(struct re_printf *pf, const struct audio *au);
+
+
+/*
+ * 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
+ */
+
+enum {
+ CALL_LINENUM_MIN = 1,
+ CALL_LINENUM_MAX = 256
+};
+
+struct call;
+
+/** Call parameters */
+struct call_prm {
+ struct sa laddr;
+ enum vidmode vidmode;
+ int af;
+ bool use_rtp;
+};
+
+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,
+ struct dnsc *dnsc,
+ 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, const struct sa *laddr);
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...);
+int call_af(const struct call *call);
+void call_set_xrtpstat(struct call *call);
+struct account *call_account(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 struct list *mnatl, 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_init(struct metric *metric);
+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);
+
+
+/*
+ * 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_debug(struct re_printf *pf, const struct reg *reg);
+int reg_status(struct re_printf *pf, const struct reg *reg);
+
+
+/*
+ * RTP Header Extensions
+ */
+
+#define RTPEXT_HDR_SIZE 4
+#define RTPEXT_TYPE_MAGIC 0xbede
+
+enum {
+ RTPEXT_ID_MIN = 1,
+ RTPEXT_ID_MAX = 14,
+};
+
+enum {
+ RTPEXT_LEN_MIN = 1,
+ RTPEXT_LEN_MAX = 16,
+};
+
+struct rtpext {
+ unsigned id:4;
+ unsigned len:4;
+ uint8_t data[RTPEXT_LEN_MAX];
+};
+
+
+int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes);
+int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len,
+ const uint8_t *data);
+int rtpext_decode(struct rtpext *ext, struct mbuf *mb);
+
+
+/*
+ * 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);
+
+
+/*
+ * SDP
+ */
+
+int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb);
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m);
+
+
+/*
+ * 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 rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg);
+typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg);
+
+typedef void (stream_error_h)(struct stream *strm, int err, void *arg);
+
+/** Common parameters for media stream */
+struct stream_param {
+ bool use_rtp;
+};
+
+/** Defines a generic media stream */
+struct stream {
+ 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; /**< RTCP Canonical end-point identifier */
+ 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 */
+ stream_error_h *errorh; /**< Stream error handler */
+ void *errorh_arg; /**< Error handler argument */
+ struct tmr tmr_rtp; /**< Timer for detecting RTP timeout */
+ uint64_t ts_last; /**< Timestamp of last received RTP pkt */
+ bool terminated; /**< Stream is terminated flag */
+ uint32_t rtp_timeout_ms; /**< RTP Timeout value in [ms] */
+};
+
+int stream_alloc(struct stream **sp, const struct stream_param *prm,
+ 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 ext, 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);
+void stream_set_error_handler(struct stream *strm,
+ stream_error_h *errorh, void *arg);
+int stream_debug(struct re_printf *pf, const struct stream *s);
+int stream_print(struct re_printf *pf, const struct stream *s);
+void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms);
+
+
+/*
+ * 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;
+
+typedef void (video_err_h)(int err, const char *str, void *arg);
+
+int video_alloc(struct video **vp, const struct stream_param *stream_prm,
+ 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,
+ video_err_h *errh, void *arg);
+int video_start(struct video *v, const char *peer);
+void video_stop(struct video *v);
+bool video_is_started(const 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);
+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);
+
+
+/*
+ * Timestamp helpers
+ */
+
+
+/**
+ * This struct is used to keep track of timestamps for
+ * incoming RTP packets.
+ */
+struct timestamp_recv {
+ uint32_t first;
+ uint32_t last;
+ bool is_set;
+ unsigned num_wraps;
+};
+
+
+static inline uint64_t calc_extended_timestamp(uint32_t num_wraps, uint32_t ts)
+{
+ uint64_t ext_ts;
+
+ ext_ts = (uint64_t)num_wraps * 0x100000000ULL;
+ ext_ts += (uint64_t)ts;
+
+ return ext_ts;
+}
+
+
+static inline uint64_t timestamp_duration(const struct timestamp_recv *ts)
+{
+ uint64_t last_ext;
+
+ if (!ts || !ts->is_set)
+ return 0;
+
+ last_ext = calc_extended_timestamp(ts->num_wraps, ts->last);
+
+ return last_ext - ts->first;
+}
+
+
+/*
+ * -1 backwards wrap-around
+ * 0 no wrap-around
+ * 1 forward wrap-around
+ */
+static inline int timestamp_wrap(uint32_t ts_new, uint32_t ts_old)
+{
+ int32_t delta;
+
+ if (ts_new < ts_old) {
+
+ delta = (int32_t)ts_new - (int32_t)ts_old;
+
+ if (delta > 0)
+ return 1;
+ }
+ else if ((int32_t)(ts_old - ts_new) > 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..b29ab8b
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,171 @@
+/**
+ * @file src/event.c Baresip event handling
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static const char *event_class_name(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING:
+ case UA_EVENT_REGISTER_OK:
+ case UA_EVENT_REGISTER_FAIL:
+ case UA_EVENT_UNREGISTERING:
+ return "register";
+
+ case UA_EVENT_SHUTDOWN:
+ case UA_EVENT_EXIT:
+ return "application";
+
+ case UA_EVENT_CALL_INCOMING:
+ case UA_EVENT_CALL_RINGING:
+ case UA_EVENT_CALL_PROGRESS:
+ case UA_EVENT_CALL_ESTABLISHED:
+ case UA_EVENT_CALL_CLOSED:
+ case UA_EVENT_CALL_TRANSFER_FAILED:
+ case UA_EVENT_CALL_DTMF_START:
+ case UA_EVENT_CALL_DTMF_END:
+ case UA_EVENT_CALL_RTCP:
+ return "call";
+
+ default:
+ return "other";
+ }
+}
+
+
+static int add_rtcp_stats(struct odict *od_parent, const struct rtcp_stats *rs)
+{
+ struct odict *od = NULL, *tx = NULL, *rx = NULL;
+ int err = 0;
+
+ if (!od_parent || !rs)
+ return EINVAL;
+
+ err = odict_alloc(&od, 8);
+ err |= odict_alloc(&tx, 8);
+ err |= odict_alloc(&rx, 8);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(tx, "sent", ODICT_INT, (int64_t)rs->tx.sent);
+ err |= odict_entry_add(tx, "lost", ODICT_INT, (int64_t)rs->tx.lost);
+ err |= odict_entry_add(tx, "jit", ODICT_INT, (int64_t)rs->tx.jit);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(rx, "sent", ODICT_INT, (int64_t)rs->rx.sent);
+ err |= odict_entry_add(rx, "lost", ODICT_INT, (int64_t)rs->rx.lost);
+ err |= odict_entry_add(rx, "jit", ODICT_INT, (int64_t)rs->rx.jit);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(od, "tx", ODICT_OBJECT, tx);
+ err |= odict_entry_add(od, "rx", ODICT_OBJECT, rx);
+ err |= odict_entry_add(od, "rtt", ODICT_INT, (int64_t)rs->rtt);
+ if (err)
+ goto out;
+
+ /* add object to the parent */
+ err = odict_entry_add(od_parent, "rtcp_stats", ODICT_OBJECT, od);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(od);
+
+ return err;
+}
+
+
+int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm)
+{
+ const char *event_str = uag_event_str(ev);
+ int err = 0;
+
+ if (!od)
+ return EINVAL;
+
+ err |= odict_entry_add(od, "type", ODICT_STRING, event_str);
+ err |= odict_entry_add(od, "class",
+ ODICT_STRING, event_class_name(ev));
+ err |= odict_entry_add(od, "accountaor", ODICT_STRING, ua_aor(ua));
+ if (err)
+ goto out;
+
+ if (call) {
+
+ const char *dir;
+
+ dir = call_is_outgoing(call) ? "outgoing" : "incoming";
+
+ err |= odict_entry_add(od, "direction", ODICT_STRING, dir);
+ err |= odict_entry_add(od, "peeruri",
+ ODICT_STRING, call_peeruri(call));
+ if (err)
+ goto out;
+ }
+
+ if (str_isset(prm)) {
+ err = odict_entry_add(od, "param", ODICT_STRING, prm);
+ if (err)
+ goto out;
+ }
+
+ if (ev == UA_EVENT_CALL_RTCP) {
+ struct stream *strm = NULL;
+
+ if (0 == str_casecmp(prm, "audio"))
+ strm = audio_strm(call_audio(call));
+#ifdef USE_VIDEO
+ else if (0 == str_casecmp(prm, "video"))
+ strm = video_strm(call_video(call));
+#endif
+
+ err = add_rtcp_stats(od, stream_rtcp_stats(strm));
+ if (err)
+ goto out;
+ }
+
+ out:
+
+ return err;
+}
+
+
+/**
+ * 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_SHUTDOWN: return "SHUTDOWN";
+ case UA_EVENT_EXIT: return "EXIT";
+ 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";
+ case UA_EVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED";
+ case UA_EVENT_CALL_DTMF_START: return "CALL_DTMF_START";
+ case UA_EVENT_CALL_DTMF_END: return "CALL_DTMF_END";
+ case UA_EVENT_CALL_RTCP: return "CALL_RTCP";
+ default: return "?";
+ }
+}
diff --git a/src/h264.c b/src/h264.c
new file mode 100644
index 0000000..2bb4a26
--- /dev/null
+++ b/src/h264.c
@@ -0,0 +1,182 @@
+/**
+ * @file src/h264.c H.264 video codec packetization (RFC 3984)
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+int h264_hdr_encode(const struct h264_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ v = hdr->f<<7 | hdr->nri<<5 | hdr->type<<0;
+
+ return mbuf_write_u8(mb, v);
+}
+
+
+int h264_hdr_decode(struct h264_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENOENT;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->f = v>>7 & 0x1;
+ hdr->nri = v>>5 & 0x3;
+ hdr->type = v>>0 & 0x1f;
+
+ return 0;
+}
+
+
+int h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb)
+{
+ uint8_t v = fu->s<<7 | fu->s<<6 | fu->r<<5 | fu->type;
+ return mbuf_write_u8(mb, v);
+}
+
+
+int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb)
+{
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENOENT;
+
+ v = mbuf_read_u8(mb);
+
+ fu->s = v>>7 & 0x1;
+ fu->e = v>>6 & 0x1;
+ fu->r = v>>5 & 0x1;
+ fu->type = v>>0 & 0x1f;
+
+ return 0;
+}
+
+
+/*
+ * Find the NAL start sequence in a H.264 byte stream
+ *
+ * @note: copied from ffmpeg source
+ */
+const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end)
+{
+ const uint8_t *a = p + 4 - ((long)p & 3);
+
+ for (end -= 3; p < a && p < end; p++ ) {
+ if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+ return p;
+ }
+
+ for (end -= 3; p < end; p += 4) {
+ uint32_t x = *(const uint32_t*)(void *)p;
+ if ( (x - 0x01010101) & (~x) & 0x80808080 ) {
+ if (p[1] == 0 ) {
+ if ( p[0] == 0 && p[2] == 1 )
+ return p;
+ if ( p[2] == 0 && p[3] == 1 )
+ return p+1;
+ }
+ if ( p[3] == 0 ) {
+ if ( p[2] == 0 && p[4] == 1 )
+ return p+2;
+ if ( p[4] == 0 && p[5] == 1 )
+ return p+3;
+ }
+ }
+ }
+
+ for (end += 3; p < end; p++) {
+ if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+ return p;
+ }
+
+ return end + 3;
+}
+
+
+static int rtp_send_data(const uint8_t *hdr, size_t hdr_sz,
+ const uint8_t *buf, size_t sz,
+ bool eof, uint32_t rtp_ts,
+ videnc_packet_h *pkth, void *arg)
+{
+ return pkth(eof, rtp_ts, hdr, hdr_sz, buf, sz, arg);
+}
+
+
+int h264_nal_send(bool first, bool last,
+ bool marker, uint32_t ihdr, uint32_t rtp_ts,
+ const uint8_t *buf, size_t size, size_t maxsz,
+ videnc_packet_h *pkth, void *arg)
+{
+ uint8_t hdr = (uint8_t)ihdr;
+ int err = 0;
+
+ if (first && last && size <= maxsz) {
+ err = rtp_send_data(&hdr, 1, buf, size, marker, rtp_ts,
+ pkth, arg);
+ }
+ else {
+ uint8_t fu_hdr[2];
+ const uint8_t type = hdr & 0x1f;
+ const uint8_t nri = hdr & 0x60;
+ const size_t sz = maxsz - 2;
+
+ fu_hdr[0] = nri | H264_NAL_FU_A;
+ fu_hdr[1] = first ? (1<<7 | type) : type;
+
+ while (size > sz) {
+ err |= rtp_send_data(fu_hdr, 2, buf, sz, false,
+ rtp_ts,
+ pkth, arg);
+ buf += sz;
+ size -= sz;
+ fu_hdr[1] &= ~(1 << 7);
+ }
+
+ if (last)
+ fu_hdr[1] |= 1<<6; /* end bit */
+
+ err |= rtp_send_data(fu_hdr, 2, buf, size, marker && last,
+ rtp_ts,
+ pkth, arg);
+ }
+
+ return err;
+}
+
+
+int h264_packetize(uint32_t rtp_ts, const uint8_t *buf, size_t len,
+ size_t pktsize, videnc_packet_h *pkth, void *arg)
+{
+ const uint8_t *start = buf;
+ const uint8_t *end = buf + len;
+ const uint8_t *r;
+ int err = 0;
+
+ r = h264_find_startcode(start, end);
+
+ while (r < end) {
+ const uint8_t *r1;
+
+ /* skip zeros */
+ while (!*(r++))
+ ;
+
+ r1 = h264_find_startcode(r, end);
+
+ err |= h264_nal_send(true, true, (r1 >= end), r[0],
+ rtp_ts, r+1, r1-r-1, pktsize,
+ pkth, arg);
+ r = r1;
+ }
+
+ return err;
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..650689d
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,153 @@
+/**
+ * @file log.c Logging
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct {
+ struct list logl;
+ bool debug;
+ bool info;
+ bool stder;
+} lg = {
+ LIST_INIT,
+ false,
+ true,
+ 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_info(bool enable)
+{
+ lg.info = 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 == LEVEL_WARN || level == LEVEL_ERROR;
+
+ if (color)
+ (void)re_fprintf(stdout, "\x1b[31m"); /* Red */
+
+ (void)re_fprintf(stdout, "%s", buf);
+
+ if (color)
+ (void)re_fprintf(stdout, "\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 ((LEVEL_DEBUG == level) && !lg.debug)
+ return;
+
+ if ((LEVEL_INFO == level) && !lg.info)
+ 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(LEVEL_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+
+void info(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!lg.info)
+ return;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_INFO, fmt, ap);
+ va_end(ap);
+}
+
+
+void warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_WARN, fmt, ap);
+ va_end(ap);
+}
+
+
+void error_msg(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_ERROR, fmt, ap);
+ va_end(ap);
+}
diff --git a/src/magic.h b/src/magic.h
new file mode 100644
index 0000000..523d38a
--- /dev/null
+++ b/src/magic.h
@@ -0,0 +1,38 @@
+/**
+ * @file magic.h Interface to magic macros
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef RELEASE
+
+#ifndef MAGIC
+#error "macro MAGIC must be defined"
+#endif
+
+
+/*
+ * Any C compiler conforming to C99 or later MUST support __func__
+ */
+#if __STDC_VERSION__ >= 199901L
+#define __MAGIC_FUNC__ (const char *)__func__
+#else
+#define __MAGIC_FUNC__ __FUNCTION__
+#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", \
+ __MAGIC_FUNC__, 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..b789648
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,265 @@
+/**
+ * @file src/main.c Main application code
+ *
+ * Copyright (C) 2010 - 2015 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) {
+ mod_close();
+ exit(0);
+ }
+
+ term = true;
+
+ info("terminated by signal %d\n", sig);
+
+ ua_stop_all(false);
+}
+
+
+static void ua_exit_handler(void *arg)
+{
+ (void)arg;
+ debug("ua exited -- stopping main runloop\n");
+
+ /* The main run-loop can be stopped now */
+ re_cancel();
+}
+
+
+static void usage(void)
+{
+ (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> Execute commands (repeat)\n"
+ "\t-f <path> Config path\n"
+ "\t-m <module> Pre-load modules (repeat)\n"
+ "\t-p <path> Audio files\n"
+ "\t-h -? Help\n"
+ "\t-t Test and exit\n"
+ "\t-u <parameters> Extra UA parameters\n"
+ "\t-v Verbose debug\n"
+ );
+}
+
+
+int main(int argc, char *argv[])
+{
+ bool prefer_ipv6 = false, run_daemon = false, test = false;
+ const char *ua_eprm = NULL;
+ const char *execmdv[16];
+ const char *audio_path = NULL;
+ const char *modv[16];
+ size_t execmdc = 0;
+ size_t modc = 0;
+ size_t i;
+ int err;
+
+ (void)re_fprintf(stdout, "baresip v%s"
+ " Copyright (C) 2010 - 2017"
+ " Alfred E. Heggestad et al.\n",
+ BARESIP_VERSION);
+
+ (void)sys_coredump_set(true);
+
+ err = libre_init();
+ if (err)
+ goto out;
+
+#ifdef HAVE_GETOPT
+ for (;;) {
+ const int c = getopt(argc, argv, "6de:f:p:hu:vtm:");
+ if (0 > c)
+ break;
+
+ switch (c) {
+
+ case '?':
+ case 'h':
+ usage();
+ return -2;
+
+#if HAVE_INET6
+ case '6':
+ prefer_ipv6 = true;
+ break;
+#endif
+
+ case 'd':
+ run_daemon = true;
+ break;
+
+ case 'e':
+ if (execmdc >= ARRAY_SIZE(execmdv)) {
+ warning("max %zu commands\n",
+ ARRAY_SIZE(execmdv));
+ err = EINVAL;
+ goto out;
+ }
+ execmdv[execmdc++] = optarg;
+ break;
+
+ case 'f':
+ conf_path_set(optarg);
+ break;
+
+ case 'm':
+ if (modc >= ARRAY_SIZE(modv)) {
+ warning("max %zu modules\n",
+ ARRAY_SIZE(modv));
+ err = EINVAL;
+ goto out;
+ }
+ modv[modc++] = optarg;
+ break;
+
+ case 'p':
+ audio_path = optarg;
+ break;
+
+ case 't':
+ test = true;
+ break;
+
+ case 'u':
+ ua_eprm = optarg;
+ break;
+
+ case 'v':
+ log_enable_debug(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ err = conf_configure();
+ if (err) {
+ warning("main: configure failed: %m\n", err);
+ goto out;
+ }
+
+ /*
+ * Initialise the top-level baresip struct, must be
+ * done AFTER configuration is complete.
+ */
+ err = baresip_init(conf_config(), prefer_ipv6);
+ if (err) {
+ warning("main: baresip init failed (%m)\n", err);
+ goto out;
+ }
+
+ /* Set audio path preferring the one given in -p argument (if any) */
+ if (audio_path)
+ play_set_path(baresip_player(), audio_path);
+ else if (str_isset(conf_config()->audio.audio_path)) {
+ play_set_path(baresip_player(),
+ conf_config()->audio.audio_path);
+ }
+
+ /* NOTE: must be done after all arguments are processed */
+ if (modc) {
+
+ info("pre-loading modules: %zu\n", modc);
+
+ for (i=0; i<modc; i++) {
+
+ err = module_preload(modv[i]);
+ if (err) {
+ re_fprintf(stderr,
+ "could not pre-load module"
+ " '%s' (%m)\n", modv[i], err);
+ }
+ }
+ }
+
+ /* Initialise User Agents */
+ err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")",
+ true, true, true, prefer_ipv6);
+ if (err)
+ goto out;
+
+ uag_set_exit_handler(ua_exit_handler, NULL);
+
+ if (ua_eprm) {
+ err = uag_set_extra_params(ua_eprm);
+ if (err)
+ goto out;
+ }
+
+ if (test)
+ 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");
+
+ /* Execute any commands from input arguments */
+ for (i=0; i<execmdc; i++) {
+ ui_input_str(execmdv[i]);
+ }
+
+ /* Main loop */
+ err = re_main(signal_handler);
+
+ out:
+ if (err)
+ ua_stop_all(true);
+
+ ua_close();
+ conf_close();
+
+ baresip_close();
+
+ /* NOTE: modules must be unloaded after all application
+ * activity has stopped.
+ */
+ debug("main: unloading modules..\n");
+ 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..1e1c78f
--- /dev/null
+++ b/src/menc.c
@@ -0,0 +1,65 @@
+/**
+ * @file menc.c Media encryption
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register a new Media encryption module
+ *
+ * @param mencl List of Media-encryption modules
+ * @param menc Media encryption module
+ */
+void menc_register(struct list *mencl, struct menc *menc)
+{
+ if (!mencl || !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 mencl List of Media-encryption modules
+ * @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 struct list *mencl, const char *id)
+{
+ struct le *le;
+
+ if (!mencl)
+ return NULL;
+
+ 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..e47f182
--- /dev/null
+++ b/src/message.c
@@ -0,0 +1,192 @@
+/**
+ * @file src/message.c SIP MESSAGE -- RFC 3428
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct message {
+ struct list lsnrl;
+ struct sip_lsnr *sip_lsnr;
+};
+
+struct message_lsnr {
+ struct le le;
+ message_recv_h *recvh;
+ void *arg;
+};
+
+
+static void destructor(void *data)
+{
+ struct message *message = data;
+
+ list_flush(&message->lsnrl);
+ mem_deref(message->sip_lsnr);
+}
+
+
+static void listener_destructor(void *data)
+{
+ struct message_lsnr *lsnr = data;
+
+ list_unlink(&lsnr->le);
+}
+
+
+static void handle_message(struct message_lsnr *lsnr, struct ua *ua,
+ const struct sip_msg *msg)
+{
+ static const char ctype_text[] = "text/plain";
+ struct pl ctype_pl = {ctype_text, sizeof(ctype_text)-1};
+ (void)ua;
+
+ if (msg_ctype_cmp(&msg->ctyp, "text", "plain") && lsnr->recvh) {
+
+ lsnr->recvh(&msg->from.auri, &ctype_pl,
+ msg->mb, lsnr->arg);
+
+ (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 message *message = arg;
+ struct ua *ua;
+ struct le *le = message->lsnrl.head;
+ bool hdld = false;
+
+ 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;
+ }
+
+ while (le) {
+ struct message_lsnr *lsnr = le->data;
+
+ le = le->next;
+
+ handle_message(lsnr, ua, msg);
+
+ hdld = true;
+ }
+
+ return hdld;
+}
+
+
+int message_init(struct message **messagep)
+{
+ struct message *message;
+ int err = 0;
+
+ if (!messagep)
+ return EINVAL;
+
+ message = mem_zalloc(sizeof(*message), destructor);
+ if (!message)
+ return ENOMEM;
+
+ /* note: cannot create sip listener here, there is not UAs yet */
+
+ if (err)
+ mem_deref(message);
+ else
+ *messagep = message;
+
+ return err;
+}
+
+
+int message_listen(struct message_lsnr **lsnrp, struct message *message,
+ message_recv_h *recvh, void *arg)
+{
+ struct message_lsnr *lsnr;
+ int err = 0;
+
+ if (!message || !recvh)
+ return EINVAL;
+
+ /* create the SIP listener if it does not exist */
+ if (!message->sip_lsnr) {
+
+ err = sip_listen(&message->sip_lsnr, uag_sip(), true,
+ request_handler, message);
+ if (err)
+ goto out;
+ }
+
+ lsnr = mem_zalloc(sizeof(*lsnr), listener_destructor);
+
+ lsnr->recvh = recvh;
+ lsnr->arg = arg;
+
+ list_append(&message->lsnrl, &lsnr->le, lsnr);
+
+ if (lsnrp)
+ *lsnrp = lsnr;
+
+ out:
+ return err;
+}
+
+
+/**
+ * Send SIP instant MESSAGE to a peer
+ *
+ * @param ua User-Agent object
+ * @param peer Peer SIP Address
+ * @param msg Message to send
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int message_send(struct ua *ua, const char *peer, const char *msg,
+ sip_resp_h *resph, void *arg)
+{
+ 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, resph, arg,
+ "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..f3e8d06
--- /dev/null
+++ b/src/metric.c
@@ -0,0 +1,90 @@
+/**
+ * @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 (!metric->started)
+ return;
+
+ 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();
+
+ metric->started = true;
+}
+
+
+void metric_init(struct metric *metric)
+{
+ if (!metric)
+ return;
+
+ tmr_start(&metric->tmr, 100, tmr_handler, metric);
+}
+
+
+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..4349589
--- /dev/null
+++ b/src/mnat.c
@@ -0,0 +1,90 @@
+/**
+ * @file mnat.c Media NAT
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+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 mnatl List of Media-NAT modules
+ * @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, struct list *mnatl,
+ 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 mnatl List of Media-NAT modules
+ * @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 struct list *mnatl, const char *id)
+{
+ struct mnat *mnat;
+ struct le *le;
+
+ if (!mnatl)
+ return NULL;
+
+ 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..68d469e
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,258 @@
+/**
+ * @file src/module.c Module loading
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * Append module extension, if not exist
+ *
+ * input: foobar
+ * output: foobar.so
+ *
+ */
+static void append_extension(char *buf, size_t sz, const char *name)
+{
+ if (0 == re_regex(name, str_len(name), "[^.]+"MOD_EXT, NULL)) {
+
+ str_ncpy(buf, name, sz);
+ }
+ else {
+ re_snprintf(buf, sz, "%s"MOD_EXT, name);
+ }
+}
+
+
+#ifdef STATIC
+
+/* Declared in static.c */
+extern const struct mod_export *mod_table[];
+
+static const struct mod_export *lookup_static_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[FS_PATH_MAX];
+ char namestr[256];
+ struct mod *m = NULL;
+ int err = 0;
+
+ if (!name)
+ return EINVAL;
+
+#ifdef STATIC
+ /* Try static first */
+ pl_strcpy(name, namestr, sizeof(namestr));
+
+ if (mod_find(namestr)) {
+ info("static module already loaded: %r\n", name);
+ return EALREADY;
+ }
+
+ err = mod_add(&m, lookup_static_module(name));
+ if (!err)
+ goto out;
+#else
+ (void)namestr;
+#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 mod *mod = NULL;
+ const struct mod_export *me;
+
+ debug("module: loading app %r\n", val);
+
+ if (load_module(&mod, arg, val)) {
+ return 0;
+ }
+
+ me = mod_export(mod);
+ if (0 != str_casecmp(me->type, "application")) {
+ warning("module_app %r should be type application (%s)\n",
+ val, me->type);
+ }
+
+ 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)
+{
+ struct le *le = list_tail(mod_list());
+
+ /* unload in reverse order */
+ while (le) {
+ struct mod *mod = le->data;
+ const struct mod_export *me = mod_export(mod);
+
+ le = le->prev;
+
+ if (me && 0 == str_casecmp(me->type, "application")) {
+ debug("module: unloading app %s\n", me->name);
+ mem_deref(mod);
+ }
+ }
+}
+
+
+int module_preload(const char *module)
+{
+ struct pl path, name;
+
+ if (!module)
+ return EINVAL;
+
+ pl_set_str(&path, ".");
+ pl_set_str(&name, module);
+
+ return load_module(NULL, &path, &name);
+}
+
+
+/**
+ * Load a module by name or by filename
+ *
+ * @param name Module name incl/excl extension, excluding module path
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * example: "foo"
+ * example: "foo.so"
+ */
+int module_load(const char *name)
+{
+ char filename[256];
+ struct pl path, pl_name;
+ int err;
+
+ if (!str_isset(name))
+ return EINVAL;
+
+ append_extension(filename, sizeof(filename), name);
+
+ pl_set_str(&pl_name, filename);
+
+ if (conf_get(conf_cur(), "module_path", &path))
+ pl_set_str(&path, ".");
+
+ err = load_module(NULL, &path, &pl_name);
+
+ return err;
+}
+
+
+/**
+ * Unload a module by name or by filename
+ *
+ * @param name module name incl/excl extension, excluding module path
+ *
+ * example: "foo"
+ * example: "foo.so"
+ */
+void module_unload(const char *name)
+{
+ char filename[256];
+ struct mod *mod;
+
+ if (!str_isset(name))
+ return;
+
+ append_extension(filename, sizeof(filename), name);
+
+ mod = mod_find(filename);
+ if (mod) {
+ info("unloading module: %s\n", filename);
+ mem_deref(mod);
+ return;
+ }
+
+ info("ERROR: Module %s is not currently loaded\n", name);
+}
diff --git a/src/mos.c b/src/mos.c
new file mode 100644
index 0000000..d86bbc1
--- /dev/null
+++ b/src/mos.c
@@ -0,0 +1,63 @@
+/**
+ * @file src/mos.c MOS (Mean Opinion Score)
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static double rfactor_to_mos(double r)
+{
+ double mos;
+
+ mos = 1 + (0.035) * (r) + (0.000007) * (r) * ((r) - 60) * (100 - (r));
+
+ if (mos > 5)
+ mos = 5;
+
+ return mos;
+}
+
+
+/**
+ * Calculate Pseudo-MOS (Mean Opinion Score)
+ *
+ * @param r_factor Pointer to where R-factor is written (optional)
+ * @param rtt Average roundtrip time
+ * @param jitter Jitter
+ * @param num_packets_lost Number of packets lost
+ *
+ * @return The calculated MOS value from 1 to 5
+ *
+ * Reference: https://metacpan.org/pod/Algorithm::MOS
+ */
+double mos_calculate(double *r_factor, double rtt,
+ double jitter, uint32_t num_packets_lost)
+{
+ double effective_latency = rtt + (jitter * 2) + 10;
+ double mos_val;
+ double r;
+
+ if (effective_latency < 160) {
+ r = 93.2 - (effective_latency / 40);
+ }
+ else {
+ r = 93.2 - (effective_latency - 120) / 10;
+ }
+
+ r = r - (num_packets_lost * 2.5);
+
+ if (r > 100)
+ r = 100;
+ else if (r < 0)
+ r = 0;
+
+ mos_val = rfactor_to_mos(r);
+
+ if (r_factor)
+ *r_factor = r;
+
+ return mos_val;
+}
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..dff8444
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,561 @@
+/**
+ * @file src/net.c Networking code
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct network {
+ 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[NET_MAX_NS];/**< 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;
+};
+
+
+static int net_dnssrv_add(struct network *net, const struct sa *sa)
+{
+ if (!net)
+ return EINVAL;
+
+ if (net->nsn >= ARRAY_SIZE(net->nsv))
+ return E2BIG;
+
+ sa_cpy(&net->nsv[net->nsn++], sa);
+
+ return 0;
+}
+
+
+static int net_dns_srv_get(const struct network *net,
+ struct sa *srvv, uint32_t *n, bool *from_sys)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t i, nsn = ARRAY_SIZE(nsv);
+ int err;
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err) {
+ nsn = 0;
+ }
+
+ if (net->nsn) {
+
+ if (net->nsn > *n)
+ return E2BIG;
+
+ /* Use any configured nameservers */
+ for (i=0; i<net->nsn; i++) {
+ srvv[i] = net->nsv[i];
+ }
+
+ *n = net->nsn;
+
+ if (from_sys)
+ *from_sys = false;
+ }
+ else {
+ if (nsn > *n)
+ return E2BIG;
+
+ for (i=0; i<nsn; i++)
+ srvv[i] = nsv[i];
+
+ *n = nsn;
+
+ if (from_sys)
+ *from_sys = true;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Check for DNS Server updates
+ */
+static void dns_refresh(struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = net_dns_srv_get(net, nsv, &nsn, NULL);
+ if (err)
+ return;
+
+ (void)dnsc_srv_set(net->dnsc, nsv, nsn);
+}
+
+
+/**
+ * Detect changes in IP address(es)
+ */
+static void ipchange_handler(void *arg)
+{
+ struct network *net = arg;
+ bool change;
+
+ tmr_start(&net->tmr, net->interval * 1000, ipchange_handler, net);
+
+ dns_refresh(net);
+
+ change = net_check(net);
+ if (change && net->ch) {
+ net->ch(net->arg);
+ }
+}
+
+
+/**
+ * Check if local IP address(es) changed
+ *
+ * @param net Network instance
+ *
+ * @return True if changed, otherwise false
+ */
+bool net_check(struct network *net)
+{
+ struct sa laddr = net->laddr;
+#ifdef HAVE_INET6
+ struct sa laddr6 = net->laddr6;
+#endif
+ bool change = false;
+
+ if (!net)
+ return 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(struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn = ARRAY_SIZE(nsv);
+ int err;
+
+ err = net_dns_srv_get(net, nsv, &nsn, NULL);
+ if (err)
+ return err;
+
+ 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);
+}
+
+
+static void net_destructor(void *data)
+{
+ struct network *net = data;
+
+ tmr_cancel(&net->tmr);
+ mem_deref(net->dnsc);
+}
+
+
+/**
+ * Initialise networking
+ *
+ * @param netp Pointer to allocated network instance
+ * @param cfg Network configuration
+ * @param af Preferred address family
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_alloc(struct network **netp, const struct config_net *cfg, int af)
+{
+ struct network *net;
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn = ARRAY_SIZE(nsv);
+ char buf4[128] = "", buf6[128] = "";
+ int err;
+
+ if (!netp || !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_msg("libre was compiled without IPv6-support"
+ ", but baresip was compiled with\n");
+ return EAFNOSUPPORT;
+ }
+#else
+ if (check_ipv6()) {
+ error_msg("libre was compiled with IPv6-support"
+ ", but baresip was compiled without\n");
+ return EAFNOSUPPORT;
+ }
+#endif
+
+ net = mem_zalloc(sizeof(*net), net_destructor);
+ if (!net)
+ return ENOMEM;
+
+ net->cfg = *cfg;
+ net->af = af;
+
+ tmr_init(&net->tmr);
+
+ if (cfg->nsc) {
+ size_t i;
+
+ for (i=0; i<cfg->nsc; i++) {
+
+ const char *ns = cfg->nsv[i].addr;
+ struct sa sa;
+
+ err = sa_decode(&sa, ns, str_len(ns));
+ if (err) {
+ warning("net: dns_server:"
+ " could not decode `%s' (%m)\n",
+ ns, err);
+ goto out;
+ }
+
+ err = net_dnssrv_add(net, &sa);
+ if (err) {
+ warning("net: failed to add nameserver: %m\n",
+ err);
+ goto out;
+ }
+ }
+ }
+
+ /* Initialise DNS resolver */
+ err = dns_init(net);
+ if (err) {
+ warning("net: dns_init: %m\n", err);
+ goto out;
+ }
+
+ 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);
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+ }
+ 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
+ }
+
+ if (sa_isset(&net->laddr, SA_ADDR)) {
+ re_snprintf(buf4, sizeof(buf4), " IPv4=%s:%j",
+ net->ifname, &net->laddr);
+ }
+#ifdef HAVE_INET6
+ if (sa_isset(&net->laddr6, SA_ADDR)) {
+ re_snprintf(buf6, sizeof(buf6), " IPv6=%s:%j",
+ net->ifname6, &net->laddr6);
+ }
+#endif
+
+ (void)dns_srv_get(net->domain, sizeof(net->domain), nsv, &nsn);
+
+ info("Local network address: %s %s\n",
+ buf4, buf6);
+
+ out:
+ if (err)
+ mem_deref(net);
+ else
+ *netp = net;
+
+ return err;
+}
+
+
+/**
+ * Use a specific DNS server
+ *
+ * @param net Network instance
+ * @param ns DNS Server IP address and port
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_use_nameserver(struct network *net, const struct sa *ns)
+{
+ struct dnsc *dnsc;
+ int err;
+
+ if (!net || !ns)
+ return EINVAL;
+
+ err = dnsc_alloc(&dnsc, NULL, ns, 1);
+ if (err)
+ return err;
+
+ mem_deref(net->dnsc);
+ net->dnsc = dnsc;
+
+ return 0;
+}
+
+
+/**
+ * Check for networking changes with a regular interval
+ *
+ * @param net Network instance
+ * @param interval Interval in seconds
+ * @param ch Handler called when a change was detected
+ * @param arg Handler argument
+ */
+void net_change(struct network *net, uint32_t interval,
+ net_change_h *ch, void *arg)
+{
+ if (!net)
+ return;
+
+ net->interval = interval;
+ net->ch = ch;
+ net->arg = arg;
+
+ if (interval)
+ tmr_start(&net->tmr, interval * 1000, ipchange_handler, net);
+ else
+ tmr_cancel(&net->tmr);
+}
+
+
+void net_force_change(struct network *net)
+{
+ if (net && net->ch) {
+ net->ch(net->arg);
+ }
+}
+
+
+static int dns_debug(struct re_printf *pf, const struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t i, nsn = ARRAY_SIZE(nsv);
+ bool from_sys = false;
+ int err;
+
+ if (!net)
+ return 0;
+
+ err = net_dns_srv_get(net, nsv, &nsn, &from_sys);
+ if (err)
+ nsn = 0;
+
+ err = re_hprintf(pf, " DNS Servers from %s: (%u)\n",
+ from_sys ? "System" : "Config", nsn);
+ for (i=0; i<nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]);
+
+ return err;
+}
+
+
+int net_af(const struct network *net)
+{
+ if (!net)
+ return AF_UNSPEC;
+
+ return net->af;
+}
+
+
+/**
+ * Print networking debug information
+ *
+ * @param pf Print handler for debug output
+ * @param net Network instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_debug(struct re_printf *pf, const struct network *net)
+{
+ int err;
+
+ if (!net)
+ return 0;
+
+ 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 |= re_hprintf(pf, " Domain: %s\n", net->domain);
+
+ err |= net_if_debug(pf, NULL);
+
+ err |= net_rt_debug(pf, NULL);
+
+ err |= dns_debug(pf, net);
+
+ return err;
+}
+
+
+/**
+ * Get the local IP Address for a specific Address Family (AF)
+ *
+ * @param net Network instance
+ * @param af Address Family
+ *
+ * @return Local IP Address
+ */
+const struct sa *net_laddr_af(const struct network *net, int af)
+{
+ if (!net)
+ return NULL;
+
+ 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
+ *
+ * @param net Network instance
+ *
+ * @return DNS Client
+ */
+struct dnsc *net_dnsc(const struct network *net)
+{
+ if (!net)
+ return NULL;
+
+ return net->dnsc;
+}
+
+
+/**
+ * Get the network domain name
+ *
+ * @param net Network instance
+ *
+ * @return Network domain
+ */
+const char *net_domain(const struct network *net)
+{
+ if (!net)
+ return NULL;
+
+ return net->domain[0] ? net->domain : NULL;
+}
diff --git a/src/play.c b/src/play.c
new file mode 100644
index 0000000..aa0b59f
--- /dev/null
+++ b/src/play.c
@@ -0,0 +1,352 @@
+/**
+ * @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 {PTIME = 40};
+
+/** 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 const char default_play_path[FS_PATH_MAX] = PREFIX "/share/baresip";
+
+
+struct player {
+ struct list playl;
+ char play_path[FS_PATH_MAX];
+};
+
+
+static void tmr_polling(void *arg);
+
+
+static void tmr_stop(void *arg)
+{
+ struct play *play = arg;
+ debug("play: player complete.\n");
+ mem_deref(play);
+}
+
+
+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)
+ tmr_start(&play->tmr, 1, tmr_stop, arg);
+ }
+
+ lock_rel(play->lock);
+}
+
+
+/**
+ * NOTE: DSP cannot be destroyed inside handler
+ */
+static void write_handler(void *sampv, size_t sampc, void *arg)
+{
+ struct play *play = arg;
+ size_t sz = sampc * 2;
+ size_t pos = 0;
+ size_t left;
+ size_t count;
+
+ lock_write_get(play->lock);
+
+ if (play->eof)
+ goto silence;
+
+ while (pos < sz) {
+ left = mbuf_get_left(play->mb);
+ count = (left > sz - pos) ? sz - pos : left;
+
+ (void)mbuf_read_mem(play->mb, (uint8_t *)sampv + pos, count);
+
+ pos += count;
+
+ if (pos < sz) {
+ if (play->repeat > 0)
+ play->repeat--;
+
+ if (play->repeat == 0) {
+ play->eof = true;
+ goto silence;
+ }
+
+ play->mb->pos = 0;
+ }
+ }
+
+ silence:
+ if (play->eof)
+ memset((uint8_t *)sampv + pos, 0, sz - pos);
+
+ lock_rel(play->lock);
+}
+
+
+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;
+ int16_t *p = (void *)buf;
+
+ n = sizeof(buf);
+
+ err = aufile_read(af, buf, &n);
+ if (err || !n)
+ break;
+
+ switch (prm.fmt) {
+
+ case AUFMT_S16LE:
+ /* convert from Little-Endian to Native-Endian */
+ for (i=0; i<n/2; i++) {
+ int16_t s = sys_ltohs(*p++);
+ err |= mbuf_write_u16(mb, s);
+ }
+
+ 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 player Audio-file player
+ * @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 player *player,
+ struct mbuf *tone, uint32_t srate,
+ uint8_t ch, int repeat)
+{
+ struct auplay_prm wprm;
+ struct play *play;
+ struct config *cfg;
+ int err;
+
+ if (!player)
+ return EINVAL;
+ 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.ch = ch;
+ wprm.srate = srate;
+ wprm.ptime = PTIME;
+ wprm.fmt = AUFMT_S16LE;
+
+ err = auplay_alloc(&play->auplay, baresip_auplayl(),
+ cfg->audio.alert_mod, &wprm,
+ cfg->audio.alert_dev, write_handler, play);
+ if (err)
+ goto out;
+
+ list_append(&player->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 player Audio-file player
+ * @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, struct player *player,
+ const char *filename, int repeat)
+{
+ struct mbuf *mb;
+ char path[FS_PATH_MAX];
+ uint32_t srate = 0;
+ uint8_t ch = 0;
+ int err;
+
+ if (!player)
+ return EINVAL;
+ if (playp && *playp)
+ return EALREADY;
+
+ if (re_snprintf(path, sizeof(path), "%s/%s",
+ player->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, player, mb, srate, ch, repeat);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static void player_destructor(void *data)
+{
+ struct player *player = data;
+
+ list_flush(&player->playl);
+}
+
+
+int play_init(struct player **playerp)
+{
+ struct player *player;
+
+ if (!playerp)
+ return EINVAL;
+
+ player = mem_zalloc(sizeof(*player), player_destructor);
+ if (!player)
+ return ENOMEM;
+
+ list_init(&player->playl);
+
+ str_ncpy(player->play_path, default_play_path,
+ sizeof(player->play_path));
+
+ *playerp = player;
+
+ return 0;
+}
+
+
+void play_set_path(struct player *player, const char *path)
+{
+ if (!player)
+ return;
+
+ str_ncpy(player->play_path, path, sizeof(player->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..23bb337
--- /dev/null
+++ b/src/reg.c
@@ -0,0 +1,265 @@
+/**
+ * @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 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_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 bool contact_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct reg *reg = arg;
+ struct sip_addr addr;
+ (void)msg;
+
+ if (sip_addr_decode(&addr, &hdr->val))
+ return false;
+
+ /* match our contact */
+ return 0 == pl_strcasecmp(&addr.uri.user, ua_local_cuser(reg->ua));
+}
+
+
+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->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;
+
+ hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT,
+ contact_handler, reg);
+ if (hdr) {
+ struct sip_addr addr;
+ struct pl pval;
+
+ if (0 == sip_addr_decode(&addr, &hdr->val) &&
+ 0 == msg_param_decode(&addr.params, "pub-gruu",
+ &pval)) {
+ ua_pub_gruu_set(reg->ua, &pval);
+ }
+ }
+
+ 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;
+
+ 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;
+
+ 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_local_cuser(reg->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ reg->id,
+ sip_auth_handler, ua_account(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->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;
+}
+
+
+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);
+
+ 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/rtpext.c b/src/rtpext.c
new file mode 100644
index 0000000..82a6f6e
--- /dev/null
+++ b/src/rtpext.c
@@ -0,0 +1,112 @@
+/**
+ * @file rtpext.c RTP Header Extensions
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * RFC 5285 A General Mechanism for RTP Header Extensions
+ *
+ * - One-Byte Header: Supported
+ * - Two-Byte Header: Not supported
+ */
+
+
+int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes)
+{
+ int err = 0;
+
+ if (!mb || !num_bytes)
+ return EINVAL;
+
+ if (num_bytes & 0x3) {
+ warning("rtpext: hdr_encode: num_bytes (%zu) must be multiple"
+ " of 4\n", num_bytes);
+ return EINVAL;
+ }
+
+ err |= mbuf_write_u16(mb, htons(RTPEXT_TYPE_MAGIC));
+ err |= mbuf_write_u16(mb, htons(num_bytes / 4));
+
+ return err;
+}
+
+
+int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len,
+ const uint8_t *data)
+{
+ size_t start;
+ int err;
+
+ if (!mb || !data)
+ return EINVAL;
+
+ if (id < RTPEXT_ID_MIN || id > RTPEXT_ID_MAX)
+ return EINVAL;
+ if (len < RTPEXT_LEN_MIN || len > RTPEXT_LEN_MAX)
+ return EINVAL;
+
+ start = mb->pos;
+
+ err = mbuf_write_u8(mb, id << 4 | (len-1));
+ err |= mbuf_write_mem(mb, data, len);
+ if (err)
+ return err;
+
+ /* padding */
+ while ((mb->pos - start) & 0x03)
+ err |= mbuf_write_u8(mb, 0x00);
+
+ return err;
+}
+
+
+int rtpext_decode(struct rtpext *ext, struct mbuf *mb)
+{
+ uint8_t v;
+ int err;
+
+ if (!ext || !mb)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ memset(ext, 0, sizeof(*ext));
+
+ v = mbuf_read_u8(mb);
+
+ ext->id = v >> 4;
+ ext->len = (v & 0x0f) + 1;
+
+ if (ext->id < RTPEXT_ID_MIN || ext->id > RTPEXT_ID_MAX) {
+ warning("rtpext: invalid ID %u\n", ext->id);
+ return EBADMSG;
+ }
+ if (ext->len > mbuf_get_left(mb)) {
+ warning("rtpext: short read\n");
+ return ENODATA;
+ }
+
+ err = mbuf_read_mem(mb, ext->data, ext->len);
+ if (err)
+ return err;
+
+ /* skip padding */
+ while (mbuf_get_left(mb)) {
+ uint8_t pad = mbuf_buf(mb)[0];
+
+ if (pad != 0x00)
+ break;
+
+ mbuf_advance(mb, 1);
+ }
+
+ return 0;
+}
diff --git a/src/rtpkeep.c b/src/rtpkeep.c
new file mode 100644
index 0000000..6b6cf81
--- /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,
+ 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..da4889c
--- /dev/null
+++ b/src/sdp.c
@@ -0,0 +1,191 @@
+/**
+ * @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
+ *
+ * @param ctype_prm Content type parameter
+ * @param mb Mbuffer containing the SDP
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb)
+{
+ struct pl bnd, s, e, p;
+ char expr[64];
+ int err;
+
+ if (!ctype_prm || !mb)
+ return EINVAL;
+
+ /* fetch the boundary tag, excluding quotes */
+ err = re_regex(ctype_prm->p, ctype_prm->l,
+ "boundary=[~]+", &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..314f45c
--- /dev/null
+++ b/src/sipreq.c
@@ -0,0 +1,150 @@
+/**
+ * @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:
+ if (sr->resph)
+ 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 || !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_account(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..a35e0d8
--- /dev/null
+++ b/src/srcs.mk
@@ -0,0 +1,55 @@
+#
+# srcs.mk All application source files.
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += account.c
+SRCS += aucodec.c
+SRCS += audio.c
+SRCS += aufilt.c
+SRCS += aulevel.c
+SRCS += auplay.c
+SRCS += ausrc.c
+SRCS += baresip.c
+SRCS += call.c
+SRCS += cmd.c
+SRCS += conf.c
+SRCS += config.c
+SRCS += contact.c
+SRCS += event.c
+SRCS += log.c
+SRCS += menc.c
+SRCS += message.c
+SRCS += metric.c
+SRCS += mnat.c
+SRCS += module.c
+SRCS += mos.c
+SRCS += net.c
+SRCS += play.c
+SRCS += realtime.c
+SRCS += reg.c
+SRCS += rtpext.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 += h264.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..38db2d6
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,733 @@
+/**
+ * @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"
+
+
+enum {
+ RTP_RECV_SIZE = 8192,
+ RTP_CHECK_INTERVAL = 1000 /* how often to check for RTP [ms] */
+};
+
+
+static void stream_close(struct stream *strm, int err)
+{
+ stream_error_h *errorh = strm->errorh;
+
+ strm->terminated = true;
+ strm->errorh = NULL;
+
+ if (errorh) {
+ errorh(strm, err, strm->errorh_arg);
+ }
+}
+
+
+static void check_rtp_handler(void *arg)
+{
+ struct stream *strm = arg;
+ const uint64_t now = tmr_jiffies();
+ int diff_ms;
+
+ tmr_start(&strm->tmr_rtp, RTP_CHECK_INTERVAL,
+ check_rtp_handler, strm);
+
+ /* If no RTP was received at all, check later */
+ if (!strm->ts_last)
+ return;
+
+ /* We are in sendrecv mode, check when the last RTP packet
+ * was received.
+ */
+ if (sdp_media_dir(strm->sdp) == SDP_SENDRECV) {
+
+ diff_ms = (int)(now - strm->ts_last);
+
+ debug("stream: last \"%s\" RTP packet: %d milliseconds\n",
+ sdp_media_name(strm->sdp), diff_ms);
+
+ /* check for large jumps in time */
+ if (diff_ms > (3600 * 1000)) {
+ strm->ts_last = 0;
+ return;
+ }
+
+ if (diff_ms > (int)strm->rtp_timeout_ms) {
+
+ info("stream: no %s RTP packets received for"
+ " %d milliseconds\n",
+ sdp_media_name(strm->sdp), diff_ms);
+
+ stream_close(strm, ETIMEDOUT);
+ }
+ }
+ else {
+ re_printf("check_rtp: not checking (dir=%s)\n",
+ sdp_dir_name(sdp_media_dir(strm->sdp)));
+ }
+}
+
+
+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(const struct stream *s)
+{
+ bool started = s->metric_tx.n_packets>0 || s->metric_rx.n_packets>0;
+
+ if (!started)
+ return;
+
+ info("\n%-9s Transmit: Receive:\n"
+ "packets: %7u %7u\n"
+ "avg. bitrate: %7.1f %7.1f (kbit/s)\n"
+ "errors: %7d %7d\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,
+ s->metric_tx.n_err, s->metric_rx.n_err
+ );
+
+ 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);
+
+ tmr_cancel(&s->tmr_rtp);
+ 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 handle_rtp(struct stream *s, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct rtpext extv[8];
+ size_t extc = 0;
+
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ if (hdr->ext && hdr->x.len && mb) {
+
+ const size_t pos = mb->pos;
+ const size_t end = mb->end;
+ const size_t ext_stop = mb->pos;
+ size_t ext_len;
+ size_t i;
+ int err;
+
+ if (hdr->x.type != RTPEXT_TYPE_MAGIC) {
+ info("stream: unknown ext type ignored (0x%04x)\n",
+ hdr->x.type);
+ goto handler;
+ }
+
+ ext_len = hdr->x.len*sizeof(uint32_t);
+ if (mb->pos < ext_len) {
+ warning("stream: corrupt rtp packet,"
+ " not enough space for rtpext of %zu bytes\n",
+ ext_len);
+ return;
+ }
+
+ mb->pos = mb->pos - ext_len;
+ mb->end = ext_stop;
+
+ for (i=0; i<ARRAY_SIZE(extv) && mbuf_get_left(mb); i++) {
+
+ err = rtpext_decode(&extv[i], mb);
+ if (err) {
+ warning("stream: rtpext_decode failed (%m)\n",
+ err);
+ return;
+ }
+ }
+
+ extc = i;
+
+ mb->pos = pos;
+ mb->end = end;
+ }
+
+ handler:
+ s->rtph(hdr, extv, extc, mb, s->arg);
+
+}
+
+
+static void rtp_handler(const struct sa *src, const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct stream *s = arg;
+ bool flush = false;
+ int err;
+
+ s->ts_last = tmr_jiffies();
+
+ 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)
+ handle_rtp(s, hdr, NULL);
+
+ handle_rtp(s, &hdr2, mb2);
+
+ mem_deref(mb2);
+ }
+ else {
+ if (lostcalc(s, hdr->seq) > 0)
+ handle_rtp(s, hdr, NULL);
+
+ handle_rtp(s, hdr, mb);
+ }
+}
+
+
+static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg)
+{
+ struct stream *s = arg;
+ (void)src;
+
+ s->ts_last = tmr_jiffies();
+
+ 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);
+
+ if (s->cfg.rtp_stats)
+ call_set_xrtpstat(s->call);
+
+ ua_event(call_get_ua(s->call), UA_EVENT_CALL_RTCP, s->call,
+ "%s", sdp_media_name(stream_sdpmedia(s)));
+ 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_handler, rtcp_handler, s);
+ if (err) {
+ warning("stream: rtp_listen failed: af=%s ports=%u-%u"
+ " (%m)\n", net_af2name(af),
+ s->cfg.rtp_ports.min, s->cfg.rtp_ports.max, 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 stream_param *prm,
+ 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 || !prm || !cfg || !call || !rtph)
+ return EINVAL;
+
+ s = mem_zalloc(sizeof(*s), stream_destructor);
+ if (!s)
+ return ENOMEM;
+
+ s->cfg = *cfg;
+ s->call = call;
+ s->rtph = rtph;
+ s->rtcph = rtcph;
+ s->arg = arg;
+ s->pseq = -1;
+ s->rtcp = s->cfg.rtcp_enable;
+
+ if (prm->use_rtp) {
+ err = stream_sock_alloc(s, call_af(call));
+ if (err) {
+ warning("stream: failed to create socket"
+ " for media '%s' (%m)\n", name, 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,
+ s->rtp ? sa_port(rtp_local(s->rtp)) : 9,
+ (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 && s->rtp) {
+ 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->rtp) {
+ 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;
+
+ metric_init(&s->metric_tx);
+ metric_init(&s->metric_rx);
+
+ 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 = call_account(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 ext, 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), ext,
+ 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 (!s)
+ return;
+
+ 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 / 1000);
+}
+
+
+void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms)
+{
+ if (!strm)
+ return;
+
+ strm->rtp_timeout_ms = timeout_ms;
+
+ tmr_cancel(&strm->tmr_rtp);
+
+ if (timeout_ms) {
+
+ info("stream: Enable RTP timeout (%u milliseconds)\n",
+ timeout_ms);
+
+ strm->ts_last = tmr_jiffies();
+ tmr_start(&strm->tmr_rtp, 10, check_rtp_handler, strm);
+ }
+}
+
+
+void stream_set_error_handler(struct stream *strm,
+ stream_error_h *errorh, void *arg)
+{
+ if (!strm)
+ return;
+
+ strm->errorh = errorh;
+ strm->errorh_arg = arg;
+}
+
+
+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, " local: %J, remote: %J/%J\n",
+ sdp_media_laddr(s->sdp),
+ 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);
+}
+
+
+const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm)
+{
+ return strm ? &strm->rtcp_stats : NULL;
+}
diff --git a/src/ua.c b/src/ua.c
new file mode 100644
index 0000000..0467e35
--- /dev/null
+++ b/src/ua.c
@@ -0,0 +1,1906 @@
+/**
+ * @file src/ua.c User-Agent
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+#include <ctype.h>
+
+
+/** Magic number */
+#define MAGIC 0x0a0a0a0a
+#include "magic.h"
+
+
+/** 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 pl extensionv[8]; /**< Vector of SIP extensions */
+ size_t extensionc; /**< Number of SIP extensions */
+ char *cuser; /**< SIP Contact username */
+ char *pub_gruu; /**< SIP Public GRUU */
+ int af; /**< Preferred Address Family */
+ int af_media; /**< Preferred Address Family for media */
+ enum presence_status my_status; /**< Presence Status */
+};
+
+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 */
+ sip_msg_h *subh; /**< Subscribe handler */
+ ua_exit_h *exith; /**< UA Exit handler */
+ void *arg; /**< UA Exit handler argument */
+ char *eprm; /**< Extra UA parameters */
+#ifdef USE_TLS
+ struct tls *tls; /**< TLS Context */
+#endif
+} uag = {
+ NULL,
+ LIST_INIT,
+ LIST_INIT,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ true,
+ false,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+#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,
+ bool use_rtp);
+
+
+/* This function is called when all SIP transactions are done */
+static void exit_handler(void *arg)
+{
+ (void)arg;
+
+ ua_event(NULL, UA_EVENT_EXIT, NULL, NULL);
+
+ debug("ua: sip-stack exit\n");
+
+ if (uag.exith)
+ uag.exith(uag.arg);
+}
+
+
+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;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* send event to all clients */
+ le = uag.ehl.head;
+ while (le) {
+ struct ua_eh *eh = le->data;
+ le = le->next;
+
+ 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 = NULL;
+ char params[256] = "";
+ unsigned i;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ acc = ua->acc;
+ uri = ua->acc->luri;
+ uri.user = uri.password = pl_null;
+
+ err = re_sdprintf(&reg_uri, "%H", uri_encode, &uri);
+ if (err)
+ goto out;
+
+ if (uag.cfg && str_isset(uag.cfg->uuid)) {
+ if (re_snprintf(params, sizeof(params),
+ ";+sip.instance=\"<urn:uuid:%s>\"",
+ uag.cfg->uuid) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ if (acc->regq) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";q=%s", acc->regq) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ if (acc->mnat && acc->mnat->ftag) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";%s", acc->mnat->ftag) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ 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->outboundv[i]);
+ if (err) {
+ warning("ua: SIP register failed: %m\n", err);
+ goto out;
+ }
+ }
+
+ out:
+ mem_deref(reg_uri);
+
+ return err;
+}
+
+
+/**
+ * Unregister all Register clients of a User-Agent
+ *
+ * @param ua User-Agent
+ */
+void ua_unregister(struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return;
+
+ if (!list_isempty(&ua->regl))
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ for (le = ua->regl.head; le; le = le->next) {
+ struct reg *reg = le->data;
+
+ reg_unregister(reg);
+ }
+}
+
+
+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 struct call *ua_find_call_onhold(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;
+
+ if (call_is_onhold(call))
+ return call;
+ }
+
+ return NULL;
+}
+
+
+static void resume_call(struct ua *ua)
+{
+ struct call *call;
+
+ call = ua_find_call_onhold(ua);
+ if (call) {
+ ua_printf(ua, "resuming previous call with '%s'\n",
+ call_peeruri(call));
+ call_hold(call, false);
+ }
+}
+
+
+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);
+
+ switch (ev) {
+
+ case CALL_EVENT_INCOMING:
+
+ if (contact_block_access(baresip_contacts(),
+ peeruri)) {
+
+ info("ua: blocked access: \"%s\"\n", peeruri);
+
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+ break;
+ }
+
+ 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:
+ ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri);
+ break;
+ }
+ break;
+
+ case CALL_EVENT_RINGING:
+ 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:
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+
+ resume_call(ua);
+ 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), true);
+ if (!err) {
+ struct pl pl;
+
+ pl_set_str(&pl, str);
+
+ err = call_connect(call2, &pl);
+ if (err) {
+ warning("ua: transfer: connect error: %m\n",
+ err);
+ }
+ }
+
+ if (err) {
+ (void)call_notify_sipfrag(call, 500, "Call Error");
+ mem_deref(call2);
+ }
+ break;
+
+ case CALL_EVENT_TRANSFER_FAILED:
+ ua_event(ua, UA_EVENT_CALL_TRANSFER_FAILED, call, str);
+ break;
+ }
+}
+
+
+static void call_dtmf_handler(struct call *call, char key, void *arg)
+{
+ struct ua *ua = arg;
+ char key_str[2];
+
+ MAGIC_CHECK(ua);
+
+ if (key != '\0') {
+
+ key_str[0] = key;
+ key_str[1] = '\0';
+
+ ua_event(ua, UA_EVENT_CALL_DTMF_START, call, key_str);
+ }
+ else {
+ ua_event(ua, UA_EVENT_CALL_DTMF_END, call, NULL);
+ }
+}
+
+
+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,
+ bool use_rtp)
+{
+ const struct network *net = baresip_network();
+ struct call_prm cprm;
+ int af = AF_UNSPEC;
+ int err;
+
+ if (*callp) {
+ warning("ua: call_alloc: call is already allocated\n");
+ return EALREADY;
+ }
+
+ /* 1. if AF_MEDIA is set, we prefer it
+ * 2. otherwise fall back to SIP AF
+ */
+ if (ua->af_media) {
+ af = ua->af_media;
+ }
+ else if (ua->af) {
+ af = ua->af;
+ }
+
+ memset(&cprm, 0, sizeof(cprm));
+
+ sa_cpy(&cprm.laddr, net_laddr_af(net, af));
+ cprm.vidmode = vidmode;
+ cprm.af = af;
+ cprm.use_rtp = use_rtp;
+
+ err = call_alloc(callp, conf_config(), &ua->calls,
+ ua->acc->dispname,
+ local_uri ? local_uri : ua->acc->aor,
+ ua->acc, ua, &cprm,
+ msg, xcall,
+ net_dnsc(net),
+ call_event_handler, ua);
+ if (err)
+ return err;
+
+ call_set_handlers(*callp, NULL, call_dtmf_handler, ua);
+
+ return 0;
+}
+
+
+static void handle_options(struct ua *ua, const struct sip_msg *msg)
+{
+ struct sip_contact contact;
+ struct call *call = NULL;
+ struct mbuf *desc = NULL;
+ const struct sip_hdr *hdr;
+ bool accept_sdp = true;
+ int err;
+
+ debug("ua: incoming OPTIONS message from %r (%J)\n",
+ &msg->from.auri, &msg->src);
+
+ /* application/sdp is the default if the
+ Accept header field is not present */
+ hdr = sip_msg_hdr(msg, SIP_HDR_ACCEPT);
+ if (hdr) {
+ accept_sdp = 0==pl_strcasecmp(&hdr->val, "application/sdp");
+ }
+
+ if (accept_sdp) {
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL,
+ false);
+ if (err) {
+ (void)sip_treply(NULL, uag.sip, msg,
+ 500, "Call Error");
+ return;
+ }
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ goto out;
+ }
+
+ sip_contact_set(&contact, ua_cuser(ua), &msg->dst, msg->tp);
+
+ err = sip_treplyf(NULL, NULL, uag.sip,
+ msg, true, 200, "OK",
+ "Allow: %s\r\n"
+ "%H"
+ "%H"
+ "%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ uag_allowed_methods(),
+ ua_print_supported, ua,
+ sip_contact_print, &contact,
+ desc ? "Content-Type: application/sdp\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+ if (err) {
+ warning("ua: 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);
+
+ if (!list_isempty(&ua->regl))
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ list_flush(&ua->calls);
+ list_flush(&ua->regl);
+ mem_deref(ua->cuser);
+ mem_deref(ua->pub_gruu);
+ mem_deref(ua->acc);
+
+ if (list_isempty(&uag.ual)) {
+ sip_close(uag.sip, false);
+ }
+}
+
+
+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)) {
+ warning("ua: 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;
+ char *buf = NULL;
+ 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
+
+ /* Decode SIP address */
+ if (uag.eprm) {
+ err = re_sdprintf(&buf, "%s;%s", aor, uag.eprm);
+ if (err)
+ goto out;
+ aor = buf;
+ }
+
+ err = account_alloc(&ua->acc, aor);
+ if (err)
+ goto out;
+
+ /* generate a unique contact-user, this is needed to route
+ incoming requests when using multiple useragents */
+ err = re_sdprintf(&ua->cuser, "%r-%p", &ua->acc->luri.user, ua);
+ 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 (uag.cfg && str_isset(uag.cfg->uuid))
+ add_extension(ua, "gruu");
+
+ 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)) {
+
+ warning("ua: outbound requires valid UUID!\n");
+ err = ENOSYS;
+ goto out;
+ }
+
+ for (i=0; i<ARRAY_SIZE(ua->acc->outboundv); i++) {
+
+ if (ua->acc->outboundv[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:
+ mem_deref(buf);
+ if (err)
+ mem_deref(ua);
+ else if (uap) {
+ *uap = ua;
+
+ ua->uap = uap;
+ }
+
+ return err;
+}
+
+
+static int uri_complete(struct ua *ua, struct mbuf *buf, const char *uri)
+{
+ size_t len;
+ int err = 0;
+
+ /* Skip initial whitespace */
+ while (isspace(*uri))
+ ++uri;
+
+ len = str_len(uri);
+
+ /* Append sip: scheme if missing */
+ if (0 != re_regex(uri, len, "sip:"))
+ err |= mbuf_printf(buf, "sip:");
+
+ err |= mbuf_write_str(buf, 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(buf, "@[%r]",
+ &ua->acc->luri.host);
+ else
+#endif
+ err |= mbuf_printf(buf, "@%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(buf, ":%u", ua->acc->luri.port);
+ break;
+ }
+ }
+
+ 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;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ if (params)
+ err |= mbuf_printf(dialbuf, "<");
+
+ err |= uri_complete(ua, dialbuf, uri);
+
+ 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, true);
+ 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;
+ }
+
+ (void)call_hangup(call, scode, reason);
+
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, reason);
+
+ mem_deref(call);
+
+ resume_call(ua);
+}
+
+
+/**
+ * Answer an incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to answer, 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;
+ }
+
+ return call_answer(call, 200);
+}
+
+
+int ua_progress(struct ua *ua, struct call *call)
+{
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ return call_progress(call);
+}
+
+
+/**
+ * Put the current call on hold and answer the incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to answer, or NULL for current call
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_hold_answer(struct ua *ua, struct call *call)
+{
+ struct call *pcall;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ /* put previous call on-hold */
+ pcall = ua_prev_call(ua);
+ if (pcall) {
+ ua_printf(ua, "putting call with '%s' on hold\n",
+ call_peeruri(pcall));
+
+ err = call_hold(pcall, true);
+ if (err)
+ return err;
+ }
+
+ return ua_answer(ua, call);
+}
+
+
+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)
+{
+ struct mbuf *dialbuf;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ err = uri_complete(ua, dialbuf, uri);
+ if (err)
+ goto out;
+
+ dialbuf->buf[dialbuf->end] = '\0';
+
+ err = sip_req_send(ua, "OPTIONS", (char *)dialbuf->buf, resph, arg,
+ "Accept: application/sdp\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ if (err) {
+ warning("ua: send options: (%m)\n", err);
+ }
+
+ out:
+ mem_deref(dialbuf);
+
+ 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 ? account_aor(ua->acc) : NULL;
+}
+
+
+/**
+ * Get presence status of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return presence status
+ */
+enum presence_status ua_presence_status(const struct ua *ua)
+{
+ return ua ? ua->my_status : PRESENCE_UNKNOWN;
+}
+
+
+/**
+ * Set presence status of a User-Agent
+ *
+ * @param ua User-Agent object
+ * @param status Presence status
+ */
+void ua_presence_status_set(struct ua *ua, const enum presence_status status)
+{
+ if (!ua)
+ return;
+
+ ua->my_status = status;
+}
+
+
+/**
+ * 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->outboundv[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).
+ */
+struct call *ua_call(const struct ua *ua)
+{
+ if (!ua)
+ return NULL;
+
+ return list_ledata(list_tail(&ua->calls));
+}
+
+
+struct call *ua_prev_call(const struct ua *ua)
+{
+ struct le *le;
+ int prev = 0;
+
+ if (!ua)
+ return NULL;
+
+ for (le = ua->calls.tail; le; le = le->prev) {
+ if ( prev == 1) {
+ struct call *call = le->data;
+ return call;
+ }
+ if ( prev == 0)
+ prev = 1;
+ }
+
+ 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, " nrefs: %u\n", mem_nrefs(ua));
+ err |= re_hprintf(pf, " cuser: %s\n", ua->cuser);
+ err |= re_hprintf(pf, " pub-gruu: %s\n", ua->pub_gruu);
+ 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) {
+ warning("ua: 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) {
+ warning("ua: 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) {
+ warning("ua: 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) {
+ warning("ua: SIP/TLS transport failed: %m\n", err);
+ return err;
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+static int ua_add_transp(struct network *net)
+{
+ int err = 0;
+
+ if (!uag.prefer_ipv6) {
+ if (sa_isset(net_laddr_af(net, AF_INET), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(net, AF_INET));
+ }
+
+#if HAVE_INET6
+ if (sa_isset(net_laddr_af(net, AF_INET6), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(net, 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)
+{
+ struct config *config = conf_config();
+ const struct sip_hdr *hdr;
+ struct ua *ua;
+ struct call *call = NULL;
+ char to_uri[256];
+ int err;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ warning("ua: %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 (config->call.max_calls &&
+ list_count(&ua->calls) + 1 > config->call.max_calls) {
+
+ info("ua: rejected call from %r (maximum %d calls)\n",
+ &msg->from.auri, config->call.max_calls);
+ (void)sip_treply(NULL, uag.sip, msg, 486, "Max Calls");
+ 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, true);
+ if (err) {
+ warning("ua: call_alloc: %m\n", err);
+ goto error;
+ }
+
+ err = call_accept(call, uag.sock, msg);
+ if (err)
+ goto error;
+
+ return;
+
+ error:
+ mem_deref(call);
+ (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error");
+}
+
+
+static void net_change_handler(void *arg)
+{
+ (void)arg;
+
+ info("IP-address changed: %j\n",
+ net_laddr_af(baresip_network(), AF_INET));
+
+ (void)uag_reset_transp(true, true);
+}
+
+
+static bool sub_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ warning("subscribe: no UA found for %r\n", &msg->uri.user);
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ if (uag.subh)
+ uag.subh(msg, ua);
+
+ return true;
+}
+
+
+/**
+ * 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();
+ struct network *net = baresip_network();
+ uint32_t bsize;
+ int err;
+
+ if (!net) {
+ warning("ua: no network\n");
+ return EINVAL;
+ }
+
+ uag.cfg = &cfg->sip;
+ bsize = cfg->sip.trans_bsize;
+
+ 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(net), bsize, bsize, bsize,
+ software, exit_handler, NULL);
+ if (err) {
+ warning("ua: sip stack failed: %m\n", err);
+ goto out;
+ }
+
+ err = ua_add_transp(net);
+ 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,
+ sub_handler, NULL);
+ if (err)
+ goto out;
+
+ net_change(net, 60, net_change_handler, NULL);
+
+ out:
+ if (err) {
+ warning("ua: init failed (%m)\n", err);
+ ua_close();
+ }
+ return err;
+}
+
+
+/**
+ * Close all active User-Agents
+ */
+void ua_close(void)
+{
+ uag.evsock = mem_deref(uag.evsock);
+ uag.sock = mem_deref(uag.sock);
+ uag.lsnr = mem_deref(uag.lsnr);
+ uag.sip = mem_deref(uag.sip);
+ uag.eprm = mem_deref(uag.eprm);
+
+#ifdef USE_TLS
+ uag.tls = mem_deref(uag.tls);
+#endif
+
+ list_flush(&uag.ual);
+ list_flush(&uag.ehl);
+
+ /* note: must be done before mod_close() */
+ module_app_unload();
+}
+
+
+/**
+ * Stop all User-Agents
+ *
+ * @param forced True to force, otherwise false
+ */
+void ua_stop_all(bool forced)
+{
+ struct le *le;
+ bool ext_ref = false;
+
+ info("ua: stop all (forced=%d)\n", forced);
+
+ /* check if someone else has grabbed a ref to ua */
+ le = uag.ual.head;
+ while (le) {
+
+ struct ua *ua = le->data;
+ le = le->next;
+
+ if (mem_nrefs(ua) > 1) {
+
+ list_unlink(&ua->le);
+ list_flush(&ua->calls);
+ mem_deref(ua);
+
+ ext_ref = true;
+ }
+
+ ua_event(ua, UA_EVENT_SHUTDOWN, NULL, NULL);
+ }
+
+ if (ext_ref) {
+ info("ua: ext_ref -> cannot unload mods\n");
+ return;
+ }
+ else {
+ 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);
+}
+
+
+/**
+ * Set the global UA exit handler. The exit handler will be called
+ * asyncronously when the SIP stack has exited.
+ *
+ * @param exith Exit handler
+ * @param arg Handler argument
+ */
+void uag_set_exit_handler(ua_exit_h *exith, void *arg)
+{
+ uag.exith = exith;
+ uag.arg = arg;
+}
+
+
+/**
+ * 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 network *net = baresip_network();
+ struct le *le;
+ int err;
+
+ /* Update SIP transports */
+ sip_transp_flush(uag.sip);
+
+ (void)net_check(net);
+ err = ua_add_transp(net);
+ 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;
+ const struct sa *laddr;
+
+ laddr = net_laddr_af(net, call_af(call));
+
+ err |= call_reset_transp(call, laddr);
+ }
+ }
+ }
+
+ 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
+ *
+ * @param pf Print handler for debug output
+ * @param ua User-Agent
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_print_calls(struct re_printf *pf, const struct ua *ua)
+{
+ uint32_t n, count=0;
+ uint32_t linenum;
+ int err = 0;
+
+ if (!ua) {
+ err |= re_hprintf(pf, "\n--- No active calls ---\n");
+ return err;
+ }
+
+ n = list_count(&ua->calls);
+
+ err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n",
+ n);
+
+ for (linenum=CALL_LINENUM_MIN; linenum<CALL_LINENUM_MAX; linenum++) {
+
+ const struct call *call;
+
+ call = call_find_linenum(&ua->calls, linenum);
+ if (call) {
+ ++count;
+
+ err |= re_hprintf(pf, " %c %H\n",
+ call == ua_call(ua) ? '>' : ' ',
+ call_info, call);
+ }
+
+ if (count >= n)
+ break;
+ }
+
+ 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
+}
+
+
+/**
+ * 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 == msg_param_decode(&laddr->params, name, &val)
+ &&
+ 0 == pl_strcasecmp(&val, value)) {
+ return ua;
+ }
+ }
+ else {
+ if (0 == msg_param_exists(&laddr->params, name, &val))
+ return ua;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the contact user/uri of a User-Agent (UA)
+ *
+ * If the Public GRUU is set, it will be returned.
+ * Otherwise the local contact-user (cuser) will be returned.
+ *
+ * @param ua User-Agent
+ *
+ * @return Contact user
+ */
+const char *ua_cuser(const struct ua *ua)
+{
+ if (!ua)
+ return NULL;
+
+ if (str_isset(ua->pub_gruu))
+ return ua->pub_gruu;
+
+ return ua->cuser;
+}
+
+
+const char *ua_local_cuser(const struct ua *ua)
+{
+ return ua ? ua->cuser : NULL;
+}
+
+
+/**
+ * Get Account of a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return Pointer to UA's account
+ */
+struct account *ua_account(const struct ua *ua)
+{
+ return ua ? ua->acc : NULL;
+}
+
+
+/**
+ * Set Public GRUU of a User-Agent (UA)
+ *
+ * @param ua User-Agent
+ * @param pval Public GRUU
+ */
+void ua_pub_gruu_set(struct ua *ua, const struct pl *pval)
+{
+ if (!ua)
+ return;
+
+ ua->pub_gruu = mem_deref(ua->pub_gruu);
+ (void)pl_strdup(&ua->pub_gruu, pval);
+}
+
+
+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,MESSAGE";
+}
+
+
+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 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;
+
+ uag_event_unregister(h);
+
+ 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_set_sub_handler(sip_msg_h *subh)
+{
+ uag.subh = subh;
+}
+
+
+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;
+}
+
+
+void ua_set_media_af(struct ua *ua, int af_media)
+{
+ if (!ua)
+ return;
+
+ ua->af_media = af_media;
+}
+
+
+int uag_set_extra_params(const char *eprm)
+{
+ uag.eprm = mem_deref(uag.eprm);
+
+ if (eprm)
+ return str_dup(&uag.eprm, eprm);
+
+ return 0;
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..40f476b
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,195 @@
+/**
+ * @file ui.c User Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+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 uis UI Subsystem
+ * @param ui The User-Interface (UI) module to register
+ */
+void ui_register(struct ui_sub *uis, struct ui *ui)
+{
+ if (!uis || !ui)
+ return;
+
+ list_append(&uis->uil, &ui->le, ui);
+
+ debug("ui: %s\n", ui->name);
+}
+
+
+/**
+ * Un-register a User-Interface (UI) module
+ *
+ * @param ui The User-Interface (UI) module to un-register
+ */
+void ui_unregister(struct ui *ui)
+{
+ if (!ui)
+ return;
+
+ list_unlink(&ui->le);
+}
+
+
+/**
+ * Send an input key to the UI subsystem, with a print function for response
+ *
+ * @param uis UI Subsystem
+ * @param key Input character
+ * @param pf Print function for the response
+ */
+void ui_input_key(struct ui_sub *uis, char key, struct re_printf *pf)
+{
+ if (!uis)
+ return;
+
+ (void)cmd_process(baresip_commands(), &uis->uictx, key, pf, NULL);
+}
+
+
+/**
+ * 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;
+ struct commands *commands = baresip_commands();
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl)
+ return EINVAL;
+
+ for (i=0; i<pl->l; i++) {
+ err |= cmd_process(commands, &ctx, pl->p[i], pf, NULL);
+ }
+
+ if (pl->l > 1 && ctx)
+ err |= cmd_process(commands, &ctx, '\n', pf, NULL);
+
+ return err;
+}
+
+
+/**
+ * Send output to all modules registered in the UI subsystem
+ *
+ * @param uis UI Subsystem
+ * @param fmt Formatted output string
+ */
+void ui_output(struct ui_sub *uis, const char *fmt, ...)
+{
+ char buf[512];
+ struct le *le;
+ va_list ap;
+ int n;
+
+ if (!uis)
+ return;
+
+ va_start(ap, fmt);
+ n = re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (n < 0)
+ return;
+
+ for (le = uis->uil.head; le; le = le->next) {
+ const struct ui *ui = le->data;
+
+ if (ui->outputh)
+ ui->outputh(buf);
+ }
+}
+
+
+/**
+ * Reset the state of the UI subsystem, free resources
+ *
+ * @param uis UI Subsystem
+ */
+void ui_reset(struct ui_sub *uis)
+{
+ if (!uis)
+ return;
+
+ uis->uictx = mem_deref(uis->uictx);
+}
+
+
+bool ui_isediting(const struct ui_sub *uis)
+{
+ if (!uis)
+ return false;
+
+ return uis->uictx != NULL;
+}
+
+
+int ui_password_prompt(char **passwordp)
+{
+ char pwd[64];
+ char *nl;
+ int err;
+
+ if (!passwordp)
+ return EINVAL;
+
+ /* 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(passwordp, pwd);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/src/vidcodec.c b/src/vidcodec.c
new file mode 100644
index 0000000..cd335ad
--- /dev/null
+++ b/src/vidcodec.c
@@ -0,0 +1,126 @@
+/**
+ * @file vidcodec.c Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * Register a Video Codec
+ *
+ * @param vidcodecl List of video-codecs
+ * @param vc Video Codec
+ */
+void vidcodec_register(struct list *vidcodecl, struct vidcodec *vc)
+{
+ if (!vidcodecl || !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 vidcodecl List of video-codecs
+ * @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 struct list *vidcodecl,
+ const char *name, const char *variant)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); 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;
+}
+
+
+/**
+ * Find a Video Encoder by name
+ *
+ * @param vidcodecl List of video-codecs
+ * @param name Name of the Video Encoder to find
+ *
+ * @return Matching Video Encoder if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find_encoder(const struct list *vidcodecl,
+ const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (vc->ench)
+ return vc;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a Video Decoder by name
+ *
+ * @param vidcodecl List of video-codecs
+ * @param name Name of the Video Decoder to find
+ *
+ * @return Matching Video Decoder if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find_decoder(const struct list *vidcodecl,
+ const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (vc->dech)
+ return vc;
+ }
+
+ return NULL;
+}
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..f191215
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,1421 @@
+/**
+ * @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"
+
+
+/** Magic number */
+#define MAGIC 0x00070d10
+#include "magic.h"
+
+
+/** Internal video-encoder format */
+#ifndef VIDENC_INTERNAL_FMT
+#define VIDENC_INTERNAL_FMT (VID_FMT_YUV420P)
+#endif
+
+
+enum {
+ SRATE = 90000,
+ MAX_MUTED_FRAMES = 3,
+};
+
+/** Video transmit parameters */
+enum {
+ MEDIA_POLL_RATE = 250, /**< in [Hz] */
+ BURST_MAX = 8192, /**< in bytes */
+ RTP_PRESZ = 4 + RTP_HEADER_SIZE, /**< TURN and RTP header */
+ RTP_TRAILSZ = 12 + 4, /**< SRTP/SRTCP trailer */
+ PICUP_INTERVAL = 500,
+};
+
+
+/**
+ * \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 lock *lock_tx; /**< Protect the sendq */
+ struct list sendq; /**< Tx-Queue (struct vidqent) */
+ struct tmr tmr_rtp; /**< Timer for sending RTP */
+ unsigned skipc; /**< Number of frames skipped */
+ struct list filtl; /**< Filters in encoding order */
+ char device[128]; /**< Source device name */
+ int muted_frames; /**< # of muted frames sent */
+ uint32_t ts_offset; /**< Random timestamp offset */
+ bool picup; /**< Send picture update */
+ bool muted; /**< Muted flag */
+ int frames; /**< Number of frames sent */
+ int efps; /**< Estimated frame-rate */
+ uint32_t ts_min;
+ uint32_t ts_max;
+};
+
+
+/**
+ * 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 */
+ struct tmr tmr_picup; /**< Picture update timer */
+ struct vidsz size; /**< Incoming video resolution */
+ enum vidorient orient; /**< Display orientation */
+ char device[128]; /**< Display device name */
+ int pt_rx; /**< Incoming RTP payload type */
+ int frames; /**< Number of frames received */
+ int efps; /**< Estimated frame-rate */
+ unsigned n_intra; /**< Intra-frames decoded */
+ unsigned n_picup; /**< Picture updates sent */
+ uint32_t ts_min;
+ uint32_t ts_max;
+};
+
+
+/** 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 */
+ bool started; /**< True if video is started */
+ char *peer; /**< Peer URI */
+ bool nack_pli; /**< Send NACK/PLI to peer */
+ video_err_h *errh; /**< Error handler */
+ void *arg; /**< Error handler argument */
+};
+
+
+struct vidqent {
+ struct le le;
+ struct sa dst;
+ bool marker;
+ uint8_t pt;
+ uint32_t ts;
+ struct mbuf *mb;
+};
+
+
+static void request_picture_update(struct vrx *vrx);
+
+
+static void vidqent_destructor(void *arg)
+{
+ struct vidqent *qent = arg;
+
+ list_unlink(&qent->le);
+ mem_deref(qent->mb);
+}
+
+
+static int vidqent_alloc(struct vidqent **qentp,
+ bool marker, uint8_t pt, uint32_t ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len)
+{
+ struct vidqent *qent;
+ int err = 0;
+
+ if (!qentp || !pld)
+ return EINVAL;
+
+ qent = mem_zalloc(sizeof(*qent), vidqent_destructor);
+ if (!qent)
+ return ENOMEM;
+
+ qent->marker = marker;
+ qent->pt = pt;
+ qent->ts = ts;
+
+ qent->mb = mbuf_alloc(RTP_PRESZ + hdr_len + pld_len + RTP_TRAILSZ);
+ if (!qent->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ qent->mb->pos = qent->mb->end = RTP_PRESZ;
+
+ if (hdr)
+ (void)mbuf_write_mem(qent->mb, hdr, hdr_len);
+
+ (void)mbuf_write_mem(qent->mb, pld, pld_len);
+
+ qent->mb->pos = RTP_PRESZ;
+
+ out:
+ if (err)
+ mem_deref(qent);
+ else
+ *qentp = qent;
+
+ return err;
+}
+
+
+static void vidqueue_poll(struct vtx *vtx, uint64_t jfs, uint64_t prev_jfs)
+{
+ size_t burst, sent;
+ uint64_t bandwidth_kbps;
+ struct le *le;
+
+ if (!vtx)
+ return;
+
+ lock_write_get(vtx->lock_tx);
+
+ le = vtx->sendq.head;
+ if (!le)
+ goto out;
+
+ /*
+ * time [ms] * bitrate [kbps] / 8 = bytes
+ */
+ bandwidth_kbps = vtx->video->cfg.bitrate / 1000;
+ burst = (1 + jfs - prev_jfs) * bandwidth_kbps / 4;
+
+ burst = min(burst, BURST_MAX);
+ sent = 0;
+
+ while (le) {
+
+ struct vidqent *qent = le->data;
+
+ sent += mbuf_get_left(qent->mb);
+
+ stream_send(vtx->video->strm, false, qent->marker, qent->pt,
+ qent->ts, qent->mb);
+
+ le = le->next;
+ mem_deref(qent);
+
+ if (sent > burst) {
+ break;
+ }
+ }
+
+ out:
+ lock_rel(vtx->lock_tx);
+}
+
+
+static void rtp_tmr_handler(void *arg)
+{
+ struct vtx *vtx = arg;
+ uint64_t pjfs;
+
+ pjfs = vtx->tmr_rtp.jfs;
+
+ tmr_start(&vtx->tmr_rtp, 1000/MEDIA_POLL_RATE, rtp_tmr_handler, vtx);
+
+ vidqueue_poll(vtx, vtx->tmr_rtp.jfs, pjfs);
+}
+
+
+static void video_destructor(void *arg)
+{
+ struct video *v = arg;
+ struct vtx *vtx = &v->vtx;
+ struct vrx *vrx = &v->vrx;
+
+ /* transmit */
+ lock_write_get(vtx->lock_tx);
+ list_flush(&vtx->sendq);
+ lock_rel(vtx->lock_tx);
+ mem_deref(vtx->lock_tx);
+
+ tmr_cancel(&vtx->tmr_rtp);
+ mem_deref(vtx->vsrc);
+ lock_write_get(vtx->lock);
+ mem_deref(vtx->frame);
+ mem_deref(vtx->mute_frame);
+ mem_deref(vtx->enc);
+ list_flush(&vtx->filtl);
+ lock_rel(vtx->lock);
+ mem_deref(vtx->lock);
+
+ /* receive */
+ tmr_cancel(&vrx->tmr_picup);
+ 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, uint32_t ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len,
+ void *arg)
+{
+ struct vtx *vtx = arg;
+ struct stream *strm = vtx->video->strm;
+ struct vidqent *qent;
+ uint32_t rtp_ts;
+ int err;
+
+ /* NOTE: does not handle timestamp wrap around */
+ if (ts < vtx->ts_min)
+ vtx->ts_min = ts;
+ if (ts > vtx->ts_max)
+ vtx->ts_max = ts;
+
+ /* add random timestamp offset */
+ rtp_ts = vtx->ts_offset + ts;
+
+ err = vidqent_alloc(&qent, marker, strm->pt_enc, rtp_ts,
+ hdr, hdr_len, pld, pld_len);
+ if (err)
+ return err;
+
+ lock_write_get(vtx->lock_tx);
+ qent->dst = *sdp_media_raddr(strm->sdp);
+ list_append(&vtx->sendq, &qent->le, qent);
+ lock_rel(vtx->lock_tx);
+
+ 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;
+ bool sendq_empty;
+
+ if (!vtx->enc)
+ return;
+
+ lock_write_get(vtx->lock_tx);
+ sendq_empty = (vtx->sendq.head == NULL);
+ lock_rel(vtx->lock_tx);
+
+ if (!sendq_empty) {
+ ++vtx->skipc;
+ return;
+ }
+
+ lock_write_get(vtx->lock);
+
+ /* Convert image */
+ if (frame->fmt != VIDENC_INTERNAL_FMT) {
+
+ vtx->vsrc_size = frame->size;
+
+ if (!vtx->frame) {
+
+ err = vidframe_alloc(&vtx->frame, VIDENC_INTERNAL_FMT,
+ &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);
+ if (err)
+ return;
+
+ 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;
+
+ warning("video: 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);
+ err |= lock_alloc(&vtx->lock_tx);
+ if (err)
+ return err;
+
+ tmr_init(&vtx->tmr_rtp);
+
+ vtx->video = video;
+
+ /* The initial value of the timestamp SHOULD be random */
+ vtx->ts_offset = rand_u16();
+
+ str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device));
+
+ tmr_start(&vtx->tmr_rtp, 1, rtp_tmr_handler, vtx);
+
+ vtx->ts_min = ~0;
+
+ 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));
+
+ vrx->ts_min = ~0;
+
+ return err;
+}
+
+
+static void picup_tmr_handler(void *arg)
+{
+ struct vrx *vrx = arg;
+
+ request_picture_update(vrx);
+}
+
+
+static void request_picture_update(struct vrx *vrx)
+{
+ struct video *v = vrx->video;
+
+ if (tmr_isrunning(&vrx->tmr_picup))
+ return;
+
+ tmr_start(&vrx->tmr_picup, PICUP_INTERVAL, picup_tmr_handler, vrx);
+
+ /* send RTCP FIR to peer */
+ stream_send_fir(v->strm, v->nack_pli);
+
+ /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */
+
+ ++vrx->n_picup;
+}
+
+
+/**
+ * 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_filt = NULL;
+ struct vidframe frame_store, *frame = &frame_store;
+ struct le *le;
+ bool intra;
+ int err = 0;
+
+ if (!hdr || !mbuf_get_left(mb))
+ return 0;
+
+ lock_write_get(vrx->lock);
+
+ /* No decoder set */
+ if (!vrx->dec) {
+ warning("video: No video decoder!\n");
+ goto out;
+ }
+
+ /* todo: check if RTP timestamp wraps */
+
+ if (hdr->ts < vrx->ts_min)
+ vrx->ts_min = hdr->ts;
+ if (hdr->ts > vrx->ts_max)
+ vrx->ts_max = hdr->ts;
+
+ frame->data[0] = NULL;
+ err = vrx->vc->dech(vrx->dec, frame, &intra, hdr->m, hdr->seq, mb);
+ if (err) {
+
+ if (err != EPROTO) {
+ warning("video: %s decode error"
+ " (seq=%u, %u bytes): %m\n",
+ vrx->vc->name, hdr->seq,
+ mbuf_get_left(mb), err);
+ }
+
+ request_picture_update(vrx);
+
+ goto out;
+ }
+
+ if (intra) {
+ tmr_cancel(&vrx->tmr_picup);
+ ++vrx->n_intra;
+ }
+
+ /* Got a full picture-frame? */
+ if (!vidframe_isvalid(frame))
+ goto out;
+
+ vrx->size = frame->size;
+
+ if (!list_isempty(&vrx->filtl)) {
+
+ err = vidframe_alloc(&frame_filt, frame->fmt, &frame->size);
+ if (err)
+ goto out;
+
+ vidframe_copy(frame_filt, frame);
+
+ frame = frame_filt;
+ }
+
+ /* 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);
+ frame_filt = mem_deref(frame_filt);
+ if (err == ENODEV) {
+ warning("video: video-display was closed\n");
+ vrx->vidisp = mem_deref(vrx->vidisp);
+
+ lock_rel(vrx->lock);
+
+ if (v->errh) {
+ v->errh(err, "display closed", v->arg);
+ }
+
+ return err;
+ }
+
+ ++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 rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg)
+{
+ struct video *v = arg;
+ int err;
+ (void)extv;
+ (void)extc;
+
+ 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 stream_param *stream_prm,
+ 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,
+ video_err_h *errh, void *arg)
+{
+ 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, stream_prm,
+ &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;
+
+ v->errh = errh;
+ v->arg = arg;
+
+ 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(baresip_vidfiltl()); 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) {
+ warning("video: 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;
+ vrx->vidisp_prm.fullscreen = vrx->video->cfg.fullscreen;
+
+ vd = (struct vidisp *)vidisp_find(baresip_vidispl(),
+ 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(baresip_vidsrcl(),
+ 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, VIDENC_INTERNAL_FMT, 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);
+
+ if (vidisp_find(baresip_vidispl(), NULL)) {
+ err = set_vidisp(&v->vrx);
+ if (err) {
+ warning("video: could not set vidisp '%s': %m\n",
+ v->vrx.device, err);
+ }
+ }
+ else {
+ info("video: no video display\n");
+ }
+
+ if (vidsrc_find(baresip_vidsrcl(), NULL)) {
+ 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) {
+ warning("video: could not set encoder format to"
+ " [%u x %u] %m\n",
+ size.w, size.h, err);
+ }
+ }
+ else {
+ info("video: no video source\n");
+ }
+
+ 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);
+ }
+
+ v->started = true;
+
+ return 0;
+}
+
+
+void video_stop(struct video *v)
+{
+ if (!v)
+ return;
+
+ debug("video: stopping video source ..\n");
+
+ v->started = false;
+ v->vtx.vsrc = mem_deref(v->vtx.vsrc);
+}
+
+
+bool video_is_started(const struct video *v)
+{
+ return v ? v->started : false;
+}
+
+
+/**
+ * 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->vidisp_prm.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.vidisp_prm.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->encupdh) {
+ info("video: vidcodec '%s' has no encoder\n", vc->name);
+ return ENOENT;
+ }
+
+ 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,
+ packet_handler, vtx);
+ if (err) {
+ warning("video: 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;
+
+ /* handle vidcodecs without a decoder */
+ if (!vc->decupdh) {
+ struct list *vidcodecl = baresip_vidcodecl();
+ struct vidcodec *vcd;
+
+ info("video: vidcodec '%s' has no decoder\n", vc->name);
+
+ vcd = (struct vidcodec *)vidcodec_find_decoder(vidcodecl,
+ vc->name);
+ if (!vcd) {
+ warning("video: could not find decoder (%s)\n",
+ vc->name);
+ return ENOENT;
+ }
+
+ vc = vcd;
+ }
+
+ 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) {
+ warning("video: 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, " started: %s\n", v->started ? "yes" : "no");
+
+ 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, " skipc=%u\n", vtx->skipc);
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ video_calc_seconds(vtx->ts_max - vtx->ts_min));
+
+ err |= re_hprintf(pf, " rx: %u x %u\n", vrx->size.w, vrx->size.h);
+ err |= re_hprintf(pf, " pt=%d\n", vrx->pt_rx);
+
+ err |= re_hprintf(pf, " n_intra=%u, n_picup=%u\n",
+ vrx->n_intra, vrx->n_picup);
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ video_calc_seconds(vrx->ts_max - vrx->ts_min));
+
+ if (!list_isempty(baresip_vidfiltl())) {
+ 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(baresip_vidsrcl(),
+ 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));
+}
+
+
+/**
+ * Calculate the RTP timestamp from Presentation Time Stamp (PTS)
+ * or Decoding Time Stamp (DTS) and framerate.
+ *
+ * @note The calculated RTP Timestamp may wrap around.
+ *
+ * @param pts Presentation Time Stamp (PTS)
+ * @param fps Framerate in [frames per second]
+ *
+ * @return RTP Timestamp
+ */
+uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps)
+{
+ uint64_t rtp_ts;
+
+ if (!fps)
+ return 0;
+
+ rtp_ts = ((uint64_t)SRATE * pts) / fps;
+
+ return (uint32_t)rtp_ts;
+}
+
+
+double video_calc_seconds(uint32_t rtp_ts)
+{
+ double timestamp;
+
+ /* convert from RTP clockrate to seconds */
+ timestamp = (double)rtp_ts / (double)SRATE;
+
+ return timestamp;
+}
diff --git a/src/vidfilt.c b/src/vidfilt.c
new file mode 100644
index 0000000..c55a1e5
--- /dev/null
+++ b/src/vidfilt.c
@@ -0,0 +1,103 @@
+/**
+ * @file vidfilt.c Video Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register a new Video Filter
+ *
+ * @param vidfiltl List of Video-Filters
+ * @param vf Video Filter to register
+ */
+void vidfilt_register(struct list *vidfiltl, struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_append(vidfiltl, &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);
+}
+
+
+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..d267f58
--- /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 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 vidispl List of Video-displays
+ * @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, struct list *vidispl, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh)
+{
+ struct vidisp *vd;
+
+ if (!vp || !vidispl)
+ 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 struct list *vidispl, const char *name)
+{
+ struct le *le;
+
+ for (le = list_head(vidispl); 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 vidispl List of Video-displays
+ * @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, struct list *vidispl,
+ const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp *vd = (struct vidisp *)vidisp_find(vidispl, 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..2c8f9ff
--- /dev/null
+++ b/src/vidsrc.c
@@ -0,0 +1,125 @@
+/**
+ * @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 void destructor(void *arg)
+{
+ struct vidsrc *vs = arg;
+
+ list_unlink(&vs->le);
+}
+
+
+/**
+ * Register a Video Source
+ *
+ * @param vsp Pointer to allocated Video Source
+ * @param vidsrcl List of Video Sources
+ * @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, struct list *vidsrcl,
+ const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh)
+{
+ struct vidsrc *vs;
+
+ if (!vsp || !vidsrcl)
+ 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 vidsrcl List of Video Sources
+ * @param name Name of the Video Source to find
+ *
+ * @return Matching Video Source if found, otherwise NULL
+ */
+const struct vidsrc *vidsrc_find(const struct list *vidsrcl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidsrcl); 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 vidsrcl List of Video Sources
+ * @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, struct list *vidsrcl,
+ 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(vidsrcl, name);
+ if (!vs)
+ return ENOENT;
+
+ return vs->alloch(stp, vs, ctx, prm, size, fmt, dev,
+ frameh, errorh, arg);
+}
+
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st)
+{
+ return st ? st->vs : NULL;
+}