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