diff options
author | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 22:22:59 +0530 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 22:22:59 +0530 |
commit | 766bb4acdda738e450630c92d9c37e6cb42d9423 (patch) | |
tree | 8277c899ed928c69dfd1251bf889af6c78400f84 /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.c | 703 | ||||
-rw-r--r-- | src/aucodec.c | 66 | ||||
-rw-r--r-- | src/audio.c | 2081 | ||||
-rw-r--r-- | src/aufilt.c | 28 | ||||
-rw-r--r-- | src/aulevel.c | 85 | ||||
-rw-r--r-- | src/auplay.c | 109 | ||||
-rw-r--r-- | src/ausrc.c | 108 | ||||
-rw-r--r-- | src/baresip.c | 248 | ||||
-rw-r--r-- | src/bfcp.c | 199 | ||||
-rw-r--r-- | src/call.c | 1877 | ||||
-rw-r--r-- | src/cmd.c | 768 | ||||
-rw-r--r-- | src/conf.c | 386 | ||||
-rw-r--r-- | src/config.c | 925 | ||||
-rw-r--r-- | src/contact.c | 338 | ||||
-rw-r--r-- | src/core.h | 541 | ||||
-rw-r--r-- | src/event.c | 171 | ||||
-rw-r--r-- | src/h264.c | 182 | ||||
-rw-r--r-- | src/log.c | 153 | ||||
-rw-r--r-- | src/magic.h | 38 | ||||
-rw-r--r-- | src/main.c | 265 | ||||
-rw-r--r-- | src/mctrl.c | 44 | ||||
-rw-r--r-- | src/menc.c | 65 | ||||
-rw-r--r-- | src/message.c | 192 | ||||
-rw-r--r-- | src/metric.c | 90 | ||||
-rw-r--r-- | src/mnat.c | 90 | ||||
-rw-r--r-- | src/module.c | 258 | ||||
-rw-r--r-- | src/mos.c | 63 | ||||
-rw-r--r-- | src/net.c | 561 | ||||
-rw-r--r-- | src/play.c | 352 | ||||
-rw-r--r-- | src/realtime.c | 100 | ||||
-rw-r--r-- | src/reg.c | 265 | ||||
-rw-r--r-- | src/rtpext.c | 112 | ||||
-rw-r--r-- | src/rtpkeep.c | 165 | ||||
-rw-r--r-- | src/sdp.c | 191 | ||||
-rw-r--r-- | src/sipreq.c | 150 | ||||
-rw-r--r-- | src/srcs.mk | 55 | ||||
-rw-r--r-- | src/stream.c | 733 | ||||
-rw-r--r-- | src/ua.c | 1906 | ||||
-rw-r--r-- | src/ui.c | 195 | ||||
-rw-r--r-- | src/vidcodec.c | 126 | ||||
-rw-r--r-- | src/video.c | 1421 | ||||
-rw-r--r-- | src/vidfilt.c | 103 | ||||
-rw-r--r-- | src/vidisp.c | 132 | ||||
-rw-r--r-- | src/vidsrc.c | 125 |
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(®->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(®->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, ®->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(®->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] ? ¶ms[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(®_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(¶ms[strlen(params)], + sizeof(params) - strlen(params), + ";q=%s", acc->regq) < 0) { + err = ENOMEM; + goto out; + } + } + + if (acc->mnat && acc->mnat->ftag) { + if (re_snprintf(¶ms[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; +} |