diff options
author | Alfred E. Heggestad <aeh@db.org> | 2014-02-09 11:50:07 +0100 |
---|---|---|
committer | Alfred E. Heggestad <aeh@db.org> | 2014-02-09 11:50:07 +0100 |
commit | 98bf08bdcf2edd9d397f32650a8bfe62186fbecf (patch) | |
tree | ebc6ec71f44bff8c42e4eefced61948623df02fc /modules | |
parent | e6ad5cf4401b860ba402d4b7b3c7c254bc87a019 (diff) |
baresip 0.4.10
Diffstat (limited to 'modules')
213 files changed, 28589 insertions, 0 deletions
diff --git a/modules/account/account.c b/modules/account/account.c new file mode 100644 index 0000000..03b6c12 --- /dev/null +++ b/modules/account/account.c @@ -0,0 +1,155 @@ +/** + * @file account/account.c Load SIP accounts from file + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +static int account_write_template(const char *file) +{ + FILE *f = NULL; + const char *login, *pass, *domain; + + info("account: creating accounts template %s\n", file); + + f = fopen(file, "w"); + if (!f) + return errno; + + login = pass = sys_username(); + if (!login) { + login = "user"; + pass = "pass"; + } + + domain = net_domain(); + if (!domain) + domain = "domain"; + + (void)re_fprintf(f, + "#\n" + "# SIP accounts - one account per line\n" + "#\n" + "# Displayname <sip:user:password@domain" + ";uri-params>;addr-params\n" + "#\n" + "# uri-params:\n" + "# ;transport={udp,tcp,tls}\n" + "#\n" + "# addr-params:\n" + "# ;answermode={manual,early,auto}\n" + "# ;audio_codecs=speex/16000,pcma,...\n" + "# ;auth_user=username\n" + "# ;mediaenc={srtp,srtp-mand,srtp-mandf" + ",dtls_srtp,zrtp}\n" + "# ;medianat={stun,turn,ice}\n" + "# ;outbound=sip:primary.example.com\n" + "# ;outbound2=sip:secondary.example.com\n" + "# ;ptime={10,20,30,40,...}\n" + "# ;regint=3600\n" + "# ;regq=0.5\n" + "# ;rtpkeep={zero,stun,dyna,rtcp}\n" + "# ;sipnat={outbound}\n" + "# ;stunserver=stun:[user:pass]@host[:port]\n" + "# ;video_codecs=h264,h263,...\n" + "#\n" + "# Examples:\n" + "#\n" + "# <sip:user:secret@domain.com;transport=tcp>\n" + "# <sip:user:secret@1.2.3.4;transport=tcp>\n" + "# <sip:user:secret@" + "[2001:df8:0:16:216:6fff:fe91:614c]:5070" + ";transport=tcp>\n" + "#\n" + "<sip:%s:%s@%s>\n", login, pass, domain); + + if (f) + (void)fclose(f); + + return 0; +} + + +/** + * Add a User-Agent (UA) + * + * @param addr SIP Address string + * + * @return 0 if success, otherwise errorcode + */ +static int line_handler(const struct pl *addr) +{ + char buf[512]; + + (void)pl_strcpy(addr, buf, sizeof(buf)); + + return ua_alloc(NULL, buf); +} + + +/** + * Read the SIP accounts from the ~/.baresip/accounts file + * + * @return 0 if success, otherwise errorcode + */ +static int account_read_file(void) +{ + char path[256] = "", file[256] = ""; + uint32_t n; + int err; + + err = conf_path_get(path, sizeof(path)); + if (err) { + warning("account: conf_path_get (%m)\n", err); + return err; + } + + if (re_snprintf(file, sizeof(file), "%s/accounts", path) < 0) + return ENOMEM; + + if (!conf_fileexist(file)) { + + (void)fs_mkdir(path, 0700); + + err = account_write_template(file); + if (err) + return err; + } + + err = conf_parse(file, line_handler); + if (err) + return err; + + n = list_count(uag_list()); + info("Populated %u account%s\n", n, 1==n ? "" : "s"); + + if (list_isempty(uag_list())) { + warning("account: No SIP accounts found" + " -- check your config\n"); + return ENOENT; + } + + return 0; +} + + +static int module_init(void) +{ + return account_read_file(); +} + + +static int module_close(void) +{ + return 0; +} + + +const struct mod_export DECL_EXPORTS(account) = { + "account", + "application", + module_init, + module_close +}; diff --git a/modules/account/module.mk b/modules/account/module.mk new file mode 100644 index 0000000..37e3ba0 --- /dev/null +++ b/modules/account/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := account +$(MOD)_SRCS += account.c + +include mk/mod.mk diff --git a/modules/alsa/alsa.c b/modules/alsa/alsa.c new file mode 100644 index 0000000..6ffa5a8 --- /dev/null +++ b/modules/alsa/alsa.c @@ -0,0 +1,164 @@ +/** + * @file alsa.c ALSA sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#define _POSIX_SOURCE 1 +#include <sys/types.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <alsa/asoundlib.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "alsa.h" + + +/** + * @defgroup alsa alsa + * + * Advanced Linux Sound Architecture (ALSA) audio driver module + * + * + * References: + * + * http://www.alsa-project.org/main/index.php/Main_Page + */ + + +char alsa_dev[64] = "default"; + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +static inline snd_pcm_format_t audio_fmt(enum aufmt fmt) +{ + switch (fmt) { + + default: + case AUFMT_S16LE: return SND_PCM_FORMAT_S16_LE; + case AUFMT_PCMU: return SND_PCM_FORMAT_MU_LAW; + case AUFMT_PCMA: return SND_PCM_FORMAT_A_LAW; + } +} + + +int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch, enum aufmt fmt, + uint32_t num_frames) +{ + snd_pcm_hw_params_t *hw_params = NULL; + const snd_pcm_format_t pcmfmt = audio_fmt(fmt); + snd_pcm_uframes_t period = num_frames, bufsize = num_frames * 10; + int err; + + err = snd_pcm_hw_params_malloc(&hw_params); + if (err < 0) { + warning("alsa: cannot allocate hw params (%s)\n", + snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_any(pcm, hw_params); + if (err < 0) { + warning("alsa: cannot initialize hw params (%s)\n", + snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_set_access(pcm, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + warning("alsa: cannot set access type (%s)\n", + snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_set_format(pcm, hw_params, pcmfmt); + if (err < 0) { + warning("alsa: cannot set sample format %d (%s)\n", + pcmfmt, snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_set_rate(pcm, hw_params, srate, 0); + if (err < 0) { + warning("alsa: cannot set sample rate to %u Hz (%s)\n", + srate, snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_set_channels(pcm, hw_params, ch); + if (err < 0) { + warning("alsa: cannot set channel count to %d (%s)\n", + ch, snd_strerror(err)); + goto out; + } + + err = snd_pcm_hw_params_set_period_size_near(pcm, hw_params, + &period, 0); + if (err < 0) { + warning("alsa: cannot set period size to %d (%s)\n", + period, snd_strerror(err)); + } + + err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &bufsize); + if (err < 0) { + warning("alsa: cannot set buffer size to %d (%s)\n", + bufsize, snd_strerror(err)); + } + + err = snd_pcm_hw_params(pcm, hw_params); + if (err < 0) { + warning("alsa: cannot set parameters (%s)\n", + snd_strerror(err)); + goto out; + } + + err = snd_pcm_prepare(pcm); + if (err < 0) { + warning("alsa: cannot prepare audio interface for use (%s)\n", + snd_strerror(err)); + goto out; + } + + err = 0; + + out: + snd_pcm_hw_params_free(hw_params); + + if (err) { + warning("alsa: init failed: err=%d\n", err); + } + + return err; +} + + +static int alsa_init(void) +{ + int err; + + err = ausrc_register(&ausrc, "alsa", alsa_src_alloc); + err |= auplay_register(&auplay, "alsa", alsa_play_alloc); + + return err; +} + + +static int alsa_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(alsa) = { + "alsa", + "sound", + alsa_init, + alsa_close +}; diff --git a/modules/alsa/alsa.h b/modules/alsa/alsa.h new file mode 100644 index 0000000..f779fcc --- /dev/null +++ b/modules/alsa/alsa.h @@ -0,0 +1,18 @@ +/** + * @file alsa.h ALSA sound driver -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +extern char alsa_dev[64]; + +int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch, enum aufmt fmt, + uint32_t num_frames); +int alsa_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); +int alsa_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); diff --git a/modules/alsa/alsa_play.c b/modules/alsa/alsa_play.c new file mode 100644 index 0000000..856ed96 --- /dev/null +++ b/modules/alsa/alsa_play.c @@ -0,0 +1,155 @@ +/** + * @file alsa_play.c ALSA sound driver - player + * + * Copyright (C) 2010 Creytiv.com + */ +#define _POSIX_SOURCE 1 +#include <sys/types.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <alsa/asoundlib.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "alsa.h" + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + pthread_t thread; + bool run; + snd_pcm_t *write; + struct mbuf *mbw; + auplay_write_h *wh; + void *arg; + struct auplay_prm prm; + char *device; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + /* Wait for termination of other thread */ + if (st->run) { + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->write) + snd_pcm_close(st->write); + + mem_deref(st->mbw); + mem_deref(st->ap); + mem_deref(st->device); +} + + +static void *write_thread(void *arg) +{ + struct auplay_st *st = arg; + int n; + int num_frames; + + num_frames = st->prm.srate * st->prm.ptime / 1000; + + while (st->run) { + const int samples = num_frames; + + st->wh(st->mbw->buf, st->mbw->size, st->arg); + + n = snd_pcm_writei(st->write, st->mbw->buf, samples); + if (-EPIPE == n) { + snd_pcm_prepare(st->write); + + n = snd_pcm_writei(st->write, st->mbw->buf, samples); + if (n != samples) { + warning("alsa: write error: %s\n", + snd_strerror(n)); + } + } + else if (n < 0) { + warning("alsa: write error: %s\n", snd_strerror(n)); + } + else if (n != samples) { + warning("alsa: write: wrote %d of %d bytes\n", + n, samples); + } + } + + return NULL; +} + + +int alsa_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + uint32_t sampc; + int num_frames; + int err; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + if (prm->fmt != AUFMT_S16LE) + return EINVAL; + + if (!str_isset(device)) + device = alsa_dev; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + err = str_dup(&st->device, device); + if (err) + goto out; + + st->prm = *prm; + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + num_frames = st->prm.srate * st->prm.ptime / 1000; + + st->mbw = mbuf_alloc(2 * sampc); + if (!st->mbw) { + err = ENOMEM; + goto out; + } + + err = snd_pcm_open(&st->write, st->device, SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) { + warning("alsa: could not open auplay device '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + err = alsa_reset(st->write, st->prm.srate, st->prm.ch, st->prm.fmt, + num_frames); + if (err) { + warning("alsa: could not reset player '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, write_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/alsa/alsa_src.c b/modules/alsa/alsa_src.c new file mode 100644 index 0000000..441c501 --- /dev/null +++ b/modules/alsa/alsa_src.c @@ -0,0 +1,156 @@ +/** + * @file alsa_src.c ALSA sound driver - recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#define _POSIX_SOURCE 1 +#include <sys/types.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <alsa/asoundlib.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "alsa.h" + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + pthread_t thread; + bool run; + snd_pcm_t *read; + struct mbuf *mbr; + ausrc_read_h *rh; + void *arg; + struct ausrc_prm prm; + char *device; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + /* Wait for termination of other thread */ + if (st->run) { + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->read) + snd_pcm_close(st->read); + + mem_deref(st->mbr); + mem_deref(st->as); + mem_deref(st->device); +} + + +static void *read_thread(void *arg) +{ + struct ausrc_st *st = arg; + int num_frames; + int err; + + num_frames = st->prm.srate * st->prm.ptime / 1000; + + /* Start */ + err = snd_pcm_start(st->read); + if (err) { + warning("alsa: could not start ausrc device '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + while (st->run) { + err = snd_pcm_readi(st->read, st->mbr->buf, num_frames); + if (err == -EPIPE) { + snd_pcm_prepare(st->read); + continue; + } + else if (err <= 0) { + continue; + } + + st->rh(st->mbr->buf, err * 2 * st->prm.ch, st->arg); + } + + out: + return NULL; +} + + +int alsa_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + uint32_t sampc; + int num_frames; + int err; + (void)ctx; + (void)errh; + + if (!stp || !as || !prm || !rh) + return EINVAL; + if (prm->fmt != AUFMT_S16LE) + return EINVAL; + + if (!str_isset(device)) + device = alsa_dev; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + err = str_dup(&st->device, device); + if (err) + goto out; + + st->prm = *prm; + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + num_frames = st->prm.srate * st->prm.ptime / 1000; + + st->mbr = mbuf_alloc(2 * sampc); + if (!st->mbr) { + err = ENOMEM; + goto out; + } + + err = snd_pcm_open(&st->read, st->device, SND_PCM_STREAM_CAPTURE, 0); + if (err < 0) { + warning("alsa: could not open ausrc device '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + err = alsa_reset(st->read, st->prm.srate, st->prm.ch, st->prm.fmt, + num_frames); + if (err) { + warning("alsa: could not reset source '%s' (%s)\n", + st->device, snd_strerror(err)); + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/alsa/module.mk b/modules/alsa/module.mk new file mode 100644 index 0000000..c93f9b5 --- /dev/null +++ b/modules/alsa/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := alsa +$(MOD)_SRCS += alsa.c alsa_src.c alsa_play.c +$(MOD)_LFLAGS += -lasound + +include mk/mod.mk diff --git a/modules/amr/amr.c b/modules/amr/amr.c new file mode 100644 index 0000000..3b29788 --- /dev/null +++ b/modules/amr/amr.c @@ -0,0 +1,340 @@ +/** + * @file amr.c Adaptive Multi-Rate (AMR) audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#ifdef AMR_NB +#include <interf_enc.h> +#include <interf_dec.h> +#endif +#ifdef AMR_WB +#ifdef _TYPEDEF_H +#define typedef_h +#endif +#include <enc_if.h> +#include <dec_if.h> +#endif +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "amr" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#ifdef VO_AMRWBENC_ENC_IF_H +#define IF2E_IF_encode E_IF_encode +#define IF2D_IF_decode D_IF_decode +#endif + + +/* + * This module supports both AMR Narrowband (8000 Hz) and + * AMR Wideband (16000 Hz) audio codecs. + * + * Reference: + * + * http://tools.ietf.org/html/rfc4867 + * + * http://www.penguin.cz/~utx/amr + */ + + +#ifndef L_FRAME16k +#define L_FRAME16k 320 +#endif + +#ifndef NB_SERIAL_MAX +#define NB_SERIAL_MAX 61 +#endif + +enum { + FRAMESIZE_NB = 160 +}; + + +struct auenc_state { + const struct aucodec *ac; + void *enc; /**< Encoder state */ +}; + +struct audec_state { + const struct aucodec *ac; + void *dec; /**< Decoder state */ +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + switch (st->ac->srate) { + +#ifdef AMR_NB + case 8000: + Encoder_Interface_exit(st->enc); + break; +#endif + +#ifdef AMR_WB + case 16000: + E_IF_exit(st->enc); + break; +#endif + } +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + switch (st->ac->srate) { + +#ifdef AMR_NB + case 8000: + Decoder_Interface_exit(st->dec); + break; +#endif + +#ifdef AMR_WB + case 16000: + D_IF_exit(st->dec); + break; +#endif + } +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->ac = ac; + + switch (ac->srate) { + +#ifdef AMR_NB + case 8000: + st->enc = Encoder_Interface_init(0); + break; +#endif + +#ifdef AMR_WB + case 16000: + st->enc = E_IF_init(); + break; +#endif + } + + if (!st->enc) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->ac = ac; + + switch (ac->srate) { + +#ifdef AMR_NB + case 8000: + st->dec = Decoder_Interface_init(); + break; +#endif + +#ifdef AMR_WB + case 16000: + st->dec = D_IF_init(); + break; +#endif + } + + if (!st->dec) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +#ifdef AMR_WB +static int encode_wb(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int n; + + if (sampc != L_FRAME16k) + return EINVAL; + + if (*len < NB_SERIAL_MAX) + return ENOMEM; + + n = IF2E_IF_encode(st->enc, 8, sampv, buf, 0); + if (n <= 0) { + DEBUG_WARNING("encode error: %d\n", n); + return EPROTO; + } + + *len = n; + + return 0; +} + + +static int decode_wb(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + if (*sampc < L_FRAME16k) + return ENOMEM; + if (len > NB_SERIAL_MAX) + return EINVAL; + + IF2D_IF_decode(st->dec, buf, sampv, 0); + + *sampc = L_FRAME16k; + + return 0; +} +#endif + + +#ifdef AMR_NB +static int encode_nb(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + int r; + + if (!st || !buf || !len || !sampv || sampc != FRAMESIZE_NB) + return EINVAL; + if (*len < NB_SERIAL_MAX) + return ENOMEM; + + r = Encoder_Interface_Encode(st->enc, MR475, sampv, buf, 0); + if (r <= 0) + return EPROTO; + + *len = r; + + return 0; +} + + +static int decode_nb(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + if (!st || !sampv || !sampc || !buf) + return EINVAL; + + if (len > NB_SERIAL_MAX) + return EPROTO; + + if (*sampc < L_FRAME16k) + return ENOMEM; + + Decoder_Interface_Decode(st->dec, buf, sampv, 0); + + *sampc = FRAMESIZE_NB; + + return 0; +} +#endif + + +#ifdef AMR_WB +static struct aucodec amr_wb = { + LE_INIT, NULL, "AMR-WB", 16000, 1, NULL, + encode_update, encode_wb, + decode_update, decode_wb, + NULL, NULL, NULL +}; +#endif +#ifdef AMR_NB +static struct aucodec amr_nb = { + LE_INIT, NULL, "AMR", 8000, 1, NULL, + encode_update, encode_nb, + decode_update, decode_nb, + NULL, NULL, NULL +}; +#endif + + +static int module_init(void) +{ + int err = 0; + +#ifdef AMR_WB + aucodec_register(&amr_wb); +#endif +#ifdef AMR_NB + aucodec_register(&amr_nb); +#endif + + return err; +} + + +static int module_close(void) +{ +#ifdef AMR_WB + aucodec_unregister(&amr_wb); +#endif +#ifdef AMR_NB + aucodec_unregister(&amr_nb); +#endif + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(amr) = { + "amr", + "codec", + module_init, + module_close +}; diff --git a/modules/amr/module.mk b/modules/amr/module.mk new file mode 100644 index 0000000..cbe1013 --- /dev/null +++ b/modules/amr/module.mk @@ -0,0 +1,64 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := amr +$(MOD)_SRCS += amr.c + + +ifneq ($(shell [ -d $(SYSROOT)/include/opencore-amrnb ] && echo 1 ),) +CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/include/opencore-amrnb +$(MOD)_LFLAGS += -lopencore-amrnb +else +ifneq ($(shell [ -d $(SYSROOT_ALT)/include/opencore-amrnb ] && echo 1 ),) +CFLAGS += -DAMR_NB=1 -I$(SYSROOT_ALT)/include/opencore-amrnb +$(MOD)_LFLAGS += -lopencore-amrnb +else +ifneq ($(shell [ -d $(SYSROOT)/local/include/amrnb ] && echo 1),) +CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/local/include/amrnb +$(MOD)_LFLAGS += -lamrnb +else +ifneq ($(shell [ -d $(SYSROOT)/include/amrnb ] && echo 1),) +CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/include/amrnb +$(MOD)_LFLAGS += -lamrnb +endif +endif +endif +endif + + +ifneq ($(shell [ -f $(SYSROOT_ALT)/include/opencore-amrwb/enc_if.h ] && \ + echo 1 ),) +CFLAGS += -DAMR_WB=1 -I$(SYSROOT_ALT)/include/opencore-amrwb +$(MOD)_LFLAGS += -lopencore-amrwb +else +ifneq ($(shell [ -f $(SYSROOT)/local/include/amrwb/enc_if.h ] && echo 1),) +CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/local/include/amrwb +$(MOD)_LFLAGS += -lamrwb +else +ifneq ($(shell [ -f $(SYSROOT)/include/amrwb/enc_if.h ] && echo 1),) +CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/include/amrwb +$(MOD)_LFLAGS += -lamrwb +else +ifneq ($(shell [ -f $(SYSROOT)/include/vo-amrwbenc/enc_if.h ] && echo 1),) +CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/include/vo-amrwbenc +$(MOD)_LFLAGS += -lvo-amrwbenc +endif +endif +endif +endif + + +# extra for decoder +ifneq ($(shell [ -f $(SYSROOT)/include/opencore-amrwb/dec_if.h ] && echo 1 ),) +CFLAGS += -I$(SYSROOT)/include/opencore-amrwb +$(MOD)_LFLAGS += -lopencore-amrwb +endif + + +$(MOD)_LFLAGS += -lm + + +include mk/mod.mk diff --git a/modules/aubridge/aubridge.c b/modules/aubridge/aubridge.c new file mode 100644 index 0000000..421d903 --- /dev/null +++ b/modules/aubridge/aubridge.c @@ -0,0 +1,48 @@ +/** + * @file aubridge.c Audio bridge + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "aubridge.h" + + +static struct ausrc *ausrc; +static struct auplay *auplay; + +struct hash *ht_device; + + +static int module_init(void) +{ + int err; + + err = hash_alloc(&ht_device, 32); + if (err) + return err; + + err = ausrc_register(&ausrc, "aubridge", src_alloc); + err |= auplay_register(&auplay, "aubridge", play_alloc); + + return err; +} + + +static int module_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + ht_device = mem_deref(ht_device); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(aubridge) = { + "aubridge", + "audio", + module_init, + module_close, +}; diff --git a/modules/aubridge/aubridge.h b/modules/aubridge/aubridge.h new file mode 100644 index 0000000..76ec53f --- /dev/null +++ b/modules/aubridge/aubridge.h @@ -0,0 +1,41 @@ +/** + * @file aubridge.h Audio bridge -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct device; + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + struct device *dev; + struct ausrc_prm prm; + ausrc_read_h *rh; + void *arg; +}; + +struct auplay_st { + struct auplay *ap; /* inheritance */ + struct device *dev; + struct auplay_prm prm; + auplay_write_h *wh; + void *arg; +}; + + +extern struct hash *ht_device; + + +int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); + + +int device_connect(struct device **devp, const char *device, + struct auplay_st *auplay, struct ausrc_st *ausrc); +void device_stop(struct device *dev); diff --git a/modules/aubridge/device.c b/modules/aubridge/device.c new file mode 100644 index 0000000..b6b7e09 --- /dev/null +++ b/modules/aubridge/device.c @@ -0,0 +1,178 @@ +/** + * @file device.c Audio bridge -- virtual device table + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <pthread.h> +#include "aubridge.h" + + +/* The packet-time is fixed to 20 milliseconds */ +enum {PTIME = 20}; + + +struct device { + struct le le; + const struct ausrc_st *ausrc; + const struct auplay_st *auplay; + char name[64]; + pthread_t thread; + volatile bool run; +}; + + +static void destructor(void *arg) +{ + struct device *dev = arg; + + device_stop(dev); + + list_unlink(&dev->le); +} + + +static bool list_apply_handler(struct le *le, void *arg) +{ + struct device *st = le->data; + + return 0 == str_cmp(st->name, arg); +} + + +static struct device *find_device(const char *device) +{ + return list_ledata(hash_lookup(ht_device, hash_joaat_str(device), + list_apply_handler, (void *)device)); +} + + +static void *device_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct device *dev = arg; + struct auresamp rs; + int16_t *sampv_in, *sampv_out; + size_t sampc_in; + size_t sampc_out; + int err; + + sampc_in = dev->auplay->prm.srate * dev->auplay->prm.ch * PTIME/1000; + sampc_out = dev->ausrc->prm.srate * dev->ausrc->prm.ch * PTIME/1000; + + auresamp_init(&rs); + + sampv_in = mem_alloc(2 * sampc_in, NULL); + sampv_out = mem_alloc(2 * sampc_out, NULL); + if (!sampv_in || !sampv_out) + goto out; + + err = auresamp_setup(&rs, + dev->auplay->prm.srate, dev->auplay->prm.ch, + dev->ausrc->prm.srate, dev->ausrc->prm.ch); + if (err) + goto out; + + while (dev->run) { + + (void)sys_msleep(4); + + if (!dev->run) + break; + + now = tmr_jiffies(); + + if (ts > now) + continue; + + if (dev->auplay && dev->auplay->wh) { + dev->auplay->wh((void *)sampv_in, 2 * sampc_in, + dev->auplay->arg); + } + + err = auresamp(&rs, + sampv_out, &sampc_out, + sampv_in, sampc_in); + if (err) { + warning("aubridge: auresamp error: %m\n", err); + } + + if (dev->ausrc && dev->ausrc->rh) { + dev->ausrc->rh((void *)sampv_out, 2 * sampc_out, + dev->ausrc->arg); + } + + ts += PTIME; + } + + out: + mem_deref(sampv_in); + mem_deref(sampv_out); + + return NULL; +} + + +int device_connect(struct device **devp, const char *device, + struct auplay_st *auplay, struct ausrc_st *ausrc) +{ + struct device *dev; + int err = 0; + + if (!devp) + return EINVAL; + if (!str_isset(device)) + return ENODEV; + + dev = find_device(device); + if (dev) { + *devp = mem_ref(dev); + } + else { + dev = mem_zalloc(sizeof(*dev), destructor); + if (!dev) + return ENOMEM; + + str_ncpy(dev->name, device, sizeof(dev->name)); + + hash_append(ht_device, hash_joaat_str(device), &dev->le, dev); + + *devp = dev; + + debug("aubridge: created device '%s'\n", device); + } + + if (auplay) + dev->auplay = auplay; + if (ausrc) + dev->ausrc = ausrc; + + /* wait until we have both SRC+PLAY */ + if (dev->ausrc && dev->auplay && !dev->run) { + + dev->run = true; + err = pthread_create(&dev->thread, NULL, device_thread, dev); + if (err) { + dev->run = false; + } + } + + return err; +} + + +void device_stop(struct device *dev) +{ + if (!dev) + return; + + dev->auplay = NULL; + dev->ausrc = NULL; + + if (dev->run) { + dev->run = false; + pthread_join(dev->thread, NULL); + } +} diff --git a/modules/aubridge/module.mk b/modules/aubridge/module.mk new file mode 100644 index 0000000..b8e0105 --- /dev/null +++ b/modules/aubridge/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := aubridge +$(MOD)_SRCS += aubridge.c device.c src.c play.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/aubridge/play.c b/modules/aubridge/play.c new file mode 100644 index 0000000..c31792c --- /dev/null +++ b/modules/aubridge/play.c @@ -0,0 +1,52 @@ +/** + * @file aubridge/play.c Audio bridge -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "aubridge.h" + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + device_stop(st->dev); + + mem_deref(st->dev); + mem_deref(st->ap); +} + + +int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int err; + + if (!stp || !ap || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->prm = *prm; + st->wh = wh; + st->arg = arg; + + err = device_connect(&st->dev, device, st, NULL); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/aubridge/src.c b/modules/aubridge/src.c new file mode 100644 index 0000000..6439cdd --- /dev/null +++ b/modules/aubridge/src.c @@ -0,0 +1,55 @@ +/** + * @file aubridge/src.c Audio bridge -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "aubridge.h" + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + device_stop(st->dev); + + mem_deref(st->dev); + mem_deref(st->as); +} + + +int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err = 0; + (void)ctx; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->prm = *prm; + st->rh = rh; + st->arg = arg; + + err = device_connect(&st->dev, device, NULL, st); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/audiounit/audiounit.c b/modules/audiounit/audiounit.c new file mode 100644 index 0000000..d5bbcc7 --- /dev/null +++ b/modules/audiounit/audiounit.c @@ -0,0 +1,88 @@ +/** + * @file audiounit.c AudioUnit sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <re.h> +#include <baresip.h> +#include "audiounit.h" + + +AudioComponent output_comp = NULL; + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +#if TARGET_OS_IPHONE +static void interruptionListener(void *data, UInt32 inInterruptionState) +{ + (void)data; + + if (inInterruptionState == kAudioSessionBeginInterruption) { + info("audiounit: interrupt Begin\n"); + audiosess_interrupt(true); + } + else if (inInterruptionState == kAudioSessionEndInterruption) { + info("audiounit: interrupt End\n"); + audiosess_interrupt(false); + } +} +#endif + + +static int module_init(void) +{ + AudioComponentDescription desc; + int err; + +#if TARGET_OS_IPHONE + OSStatus ret; + + ret = AudioSessionInitialize(NULL, NULL, interruptionListener, 0); + if (ret && ret != kAudioSessionAlreadyInitialized) { + warning("audiounit: AudioSessionInitialize: %d\n", ret); + return ENODEV; + } +#endif + + desc.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; +#else + desc.componentSubType = kAudioUnitSubType_HALOutput; +#endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + output_comp = AudioComponentFindNext(NULL, &desc); + if (!output_comp) { + warning("audiounit: Voice Processing I/O not found\n"); + return ENOENT; + } + + err = auplay_register(&auplay, "audiounit", audiounit_player_alloc); + err |= ausrc_register(&ausrc, "audiounit", audiounit_recorder_alloc); + + return 0; +} + + +static int module_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(audiounit) = { + "audiounit", + "audio", + module_init, + module_close, +}; diff --git a/modules/audiounit/audiounit.h b/modules/audiounit/audiounit.h new file mode 100644 index 0000000..dd85131 --- /dev/null +++ b/modules/audiounit/audiounit.h @@ -0,0 +1,27 @@ +/** + * @file audiounit.h AudioUnit sound driver -- Internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +AudioComponent output_comp; + + +struct audiosess; +struct audiosess_st; + +typedef void (audiosess_int_h)(bool start, void *arg); + +int audiosess_alloc(struct audiosess_st **stp, + audiosess_int_h *inth, void *arg); +void audiosess_interrupt(bool interrupted); + + +int audiounit_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int audiounit_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); diff --git a/modules/audiounit/module.mk b/modules/audiounit/module.mk new file mode 100644 index 0000000..1dd1a30 --- /dev/null +++ b/modules/audiounit/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := audiounit +$(MOD)_SRCS += audiounit.c +$(MOD)_SRCS += sess.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += -framework CoreAudio -framework AudioToolbox + +include mk/mod.mk diff --git a/modules/audiounit/player.c b/modules/audiounit/player.c new file mode 100644 index 0000000..0c3a2d1 --- /dev/null +++ b/modules/audiounit/player.c @@ -0,0 +1,189 @@ +/** + * @file audiounit/player.c AudioUnit output player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <pthread.h> +#include <re.h> +#include <baresip.h> +#include "audiounit.h" + + +static uint8_t silbuf[4096]; /* silence */ + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + struct audiosess_st *sess; + AudioUnit au; + pthread_mutex_t mutex; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + pthread_mutex_lock(&st->mutex); + st->wh = NULL; + pthread_mutex_unlock(&st->mutex); + + AudioOutputUnitStop(st->au); + AudioUnitUninitialize(st->au); + AudioComponentInstanceDispose(st->au); + + mem_deref(st->sess); + mem_deref(st->ap); + + pthread_mutex_destroy(&st->mutex); +} + + +static OSStatus output_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + struct auplay_st *st = inRefCon; + auplay_write_h *wh; + void *arg; + uint32_t i; + + (void)ioActionFlags; + (void)inTimeStamp; + (void)inBusNumber; + (void)inNumberFrames; + + pthread_mutex_lock(&st->mutex); + wh = st->wh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!wh) + return 0; + + for (i = 0; i < ioData->mNumberBuffers; ++i) { + + AudioBuffer *ab = &ioData->mBuffers[i]; + + if (!wh(ab->mData, ab->mDataByteSize, arg)) { + + if (ab->mDataByteSize < sizeof(silbuf)) + ab->mData = silbuf; + else + memset(ab->mData, 0, ab->mDataByteSize); + } + } + + return 0; +} + + +static void interrupt_handler(bool interrupted, void *arg) +{ + struct auplay_st *st = arg; + + if (interrupted) + AudioOutputUnitStop(st->au); + else + AudioOutputUnitStart(st->au); +} + + +int audiounit_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + AudioStreamBasicDescription fmt; + AudioUnitElement outputBus = 0; + AURenderCallbackStruct cb; + struct auplay_st *st; + UInt32 enable = 1; + OSStatus ret = 0; + int err; + + (void)device; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audiosess_alloc(&st->sess, interrupt_handler, st); + if (err) + goto out; + + ret = AudioComponentInstanceNew(output_comp, &st->au); + if (ret) + goto out; + + ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, outputBus, + &enable, sizeof(enable)); + if (ret) + goto out; + + fmt.mSampleRate = prm->srate; + fmt.mFormatID = kAudioFormatLinearPCM; +#if TARGET_OS_IPHONE + fmt.mFormatFlags = kAudioFormatFlagsCanonical; +#else + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | kLinearPCMFormatFlagIsPacked; +#endif + fmt.mBitsPerChannel = 16; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBytesPerFrame = 2 * prm->ch; + fmt.mFramesPerPacket = 1; + fmt.mBytesPerPacket = 2 * prm->ch; + + ret = AudioUnitInitialize(st->au); + if (ret) + goto out; + + ret = AudioUnitSetProperty(st->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, outputBus, + &fmt, sizeof(fmt)); + if (ret) + goto out; + + cb.inputProc = output_callback; + cb.inputProcRefCon = st; + ret = AudioUnitSetProperty(st->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, outputBus, + &cb, sizeof(cb)); + if (ret) + goto out; + + ret = AudioOutputUnitStart(st->au); + if (ret) + goto out; + + out: + if (ret) { + warning("audiounit: player failed: %d (%c%c%c%c)\n", ret, + ret>>24, ret>>16, ret>>8, ret); + err = ENODEV; + } + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/audiounit/recorder.c b/modules/audiounit/recorder.c new file mode 100644 index 0000000..4b460d6 --- /dev/null +++ b/modules/audiounit/recorder.c @@ -0,0 +1,194 @@ +/** + * @file audiounit/recorder.c AudioUnit input recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <pthread.h> +#include <re.h> +#include <baresip.h> +#include "audiounit.h" + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + struct audiosess_st *sess; + AudioUnit au; + pthread_mutex_t mutex; + int ch; + ausrc_read_h *rh; + void *arg; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + pthread_mutex_lock(&st->mutex); + st->rh = NULL; + pthread_mutex_unlock(&st->mutex); + + AudioOutputUnitStop(st->au); + AudioUnitUninitialize(st->au); + AudioComponentInstanceDispose(st->au); + + mem_deref(st->sess); + mem_deref(st->as); + + pthread_mutex_destroy(&st->mutex); +} + + +static OSStatus input_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + struct ausrc_st *st = inRefCon; + AudioBufferList abl; + OSStatus ret; + ausrc_read_h *rh; + void *arg; + + (void)ioData; + + pthread_mutex_lock(&st->mutex); + rh = st->rh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!rh) + return 0; + + abl.mNumberBuffers = 1; + abl.mBuffers[0].mNumberChannels = st->ch; + abl.mBuffers[0].mData = NULL; + abl.mBuffers[0].mDataByteSize = inNumberFrames * 2; + + ret = AudioUnitRender(st->au, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + &abl); + if (ret) + return ret; + + rh(abl.mBuffers[0].mData, abl.mBuffers[0].mDataByteSize, arg); + + return 0; +} + + +static void interrupt_handler(bool interrupted, void *arg) +{ + struct ausrc_st *st = arg; + + if (interrupted) + AudioOutputUnitStop(st->au); + else + AudioOutputUnitStart(st->au); +} + + +int audiounit_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + AudioStreamBasicDescription fmt; + AudioUnitElement inputBus = 1; + AURenderCallbackStruct cb; + struct ausrc_st *st; + UInt32 enable = 1; + OSStatus ret = 0; + int err; + + (void)ctx; + (void)device; + (void)errh; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + st->ch = prm->ch; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audiosess_alloc(&st->sess, interrupt_handler, st); + if (err) + goto out; + + ret = AudioComponentInstanceNew(output_comp, &st->au); + if (ret) + goto out; + + ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, inputBus, + &enable, sizeof(enable)); + if (ret) + goto out; + + fmt.mSampleRate = prm->srate; + fmt.mFormatID = kAudioFormatLinearPCM; +#if TARGET_OS_IPHONE + fmt.mFormatFlags = kAudioFormatFlagsCanonical; +#else + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | kLinearPCMFormatFlagIsPacked; +#endif + fmt.mBitsPerChannel = 16; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBytesPerFrame = 2 * prm->ch; + fmt.mFramesPerPacket = 1; + fmt.mBytesPerPacket = 2 * prm->ch; + fmt.mReserved = 0; + + ret = AudioUnitSetProperty(st->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, inputBus, + &fmt, sizeof(fmt)); + if (ret) + goto out; + + /* NOTE: done after desc */ + ret = AudioUnitInitialize(st->au); + if (ret) + goto out; + + cb.inputProc = input_callback; + cb.inputProcRefCon = st; + ret = AudioUnitSetProperty(st->au, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, inputBus, + &cb, sizeof(cb)); + if (ret) + goto out; + + ret = AudioOutputUnitStart(st->au); + if (ret) + goto out; + + out: + if (ret) { + warning("audiounit: record failed: %d (%c%c%c%c)\n", ret, + ret>>24, ret>>16, ret>>8, ret); + err = ENODEV; + } + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/audiounit/sess.c b/modules/audiounit/sess.c new file mode 100644 index 0000000..abc966e --- /dev/null +++ b/modules/audiounit/sess.c @@ -0,0 +1,174 @@ +/** + * @file sess.c AudioUnit sound driver - session + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <re.h> +#include <baresip.h> +#include "audiounit.h" + + +struct audiosess { + struct list sessl; +}; + + +struct audiosess_st { + struct audiosess *as; + struct le le; + audiosess_int_h *inth; + void *arg; +}; + + +static struct audiosess *gas; + + +#if TARGET_OS_IPHONE +static void propListener(void *inClientData, AudioSessionPropertyID inID, + UInt32 inDataSize, const void *inData) +{ + struct audiosess *sess = inClientData; + CFDictionaryRef dref = inData; + CFNumberRef nref; + SInt32 reason = 0; + + (void)inDataSize; + (void)sess; + + if (kAudioSessionProperty_AudioRouteChange != inID) + return; + + nref = CFDictionaryGetValue( + dref, + CFSTR(kAudioSession_AudioRouteChangeKey_Reason) + ); + + CFNumberGetValue(nref, kCFNumberSInt32Type, &reason); + + info("audiounit: AudioRouteChange - reason %d\n", reason); +} +#endif + + +static void sess_destructor(void *arg) +{ + struct audiosess_st *st = arg; + + list_unlink(&st->le); + mem_deref(st->as); +} + + +static void destructor(void *arg) +{ + struct audiosess *as = arg; +#if TARGET_OS_IPHONE + AudioSessionPropertyID id = kAudioSessionProperty_AudioRouteChange; + + AudioSessionRemovePropertyListenerWithUserData(id, propListener, as); + AudioSessionSetActive(false); +#endif + + list_flush(&as->sessl); + + gas = NULL; +} + + +int audiosess_alloc(struct audiosess_st **stp, + audiosess_int_h *inth, void *arg) +{ + struct audiosess_st *st = NULL; + struct audiosess *as = NULL; + int err = 0; + bool created = false; +#if TARGET_OS_IPHONE + AudioSessionPropertyID id = kAudioSessionProperty_AudioRouteChange; + UInt32 category; + OSStatus ret; +#endif + + if (!stp) + return EINVAL; + +#if TARGET_OS_IPHONE + /* Must be done for all modules */ + category = kAudioSessionCategory_PlayAndRecord; + ret = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(category), &category); + if (ret) { + warning("audiounit: Audio Category: %d\n", ret); + return EINVAL; + } +#endif + + if (gas) + goto makesess; + + as = mem_zalloc(sizeof(*as), destructor); + if (!as) + return ENOMEM; + +#if TARGET_OS_IPHONE + ret = AudioSessionSetActive(true); + if (ret) { + warning("audiounit: AudioSessionSetActive: %d\n", ret); + err = ENOSYS; + goto out; + } + + ret = AudioSessionAddPropertyListener(id, propListener, as); + if (ret) { + warning("audiounit: AudioSessionAddPropertyListener: %d\n", + ret); + err = EINVAL; + goto out; + } +#endif + + gas = as; + created = true; + + makesess: + st = mem_zalloc(sizeof(*st), sess_destructor); + if (!st) { + err = ENOMEM; + goto out; + } + st->inth = inth; + st->arg = arg; + st->as = created ? gas : mem_ref(gas); + + list_append(&gas->sessl, &st->le, st); + + out: + if (err) { + mem_deref(as); + mem_deref(st); + } + else { + *stp = st; + } + + return err; +} + + +void audiosess_interrupt(bool start) +{ + struct le *le; + + if (!gas) + return; + + for (le = gas->sessl.head; le; le = le->next) { + + struct audiosess_st *st = le->data; + + if (st->inth) + st->inth(start, st->arg); + } +} diff --git a/modules/auloop/auloop.c b/modules/auloop/auloop.c new file mode 100644 index 0000000..821c5e6 --- /dev/null +++ b/modules/auloop/auloop.c @@ -0,0 +1,370 @@ +/** + * @file auloop.c Audio loop + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* Configurable items */ +#define PTIME 20 + + +/** Audio Loop */ +struct audio_loop { + uint32_t index; + struct aubuf *ab; + struct ausrc_st *ausrc; + struct auplay_st *auplay; + const struct aucodec *ac; + struct auenc_state *enc; + struct audec_state *dec; + int16_t *sampv; + size_t sampc; + struct tmr tmr; + uint32_t srate; + uint32_t ch; + + uint32_t n_read; + uint32_t n_write; +}; + +static const struct { + uint32_t srate; + uint32_t ch; +} configv[] = { + { 8000, 1}, + {16000, 1}, + {32000, 1}, + {48000, 1}, + { 8000, 2}, + {16000, 2}, + {32000, 2}, + {48000, 2}, +}; + +static struct audio_loop *gal = NULL; +static char aucodec[64]; + + +static void auloop_destructor(void *arg) +{ + struct audio_loop *al = arg; + + tmr_cancel(&al->tmr); + mem_deref(al->ausrc); + mem_deref(al->auplay); + mem_deref(al->sampv); + mem_deref(al->ab); + mem_deref(al->enc); + mem_deref(al->dec); +} + + +static void print_stats(struct audio_loop *al) +{ + double rw_ratio = 0.0; + + if (al->n_write) + rw_ratio = 1.0 * al->n_read / al->n_write; + + (void)re_fprintf(stderr, "\r%uHz %dch " + " n_read=%u n_write=%u rw_ratio=%.2f", + al->srate, al->ch, + al->n_read, al->n_write, rw_ratio); + + if (str_isset(aucodec)) + (void)re_fprintf(stderr, " codec='%s'", aucodec); +} + + +static void tmr_handler(void *arg) +{ + struct audio_loop *al = arg; + + tmr_start(&al->tmr, 100, tmr_handler, al); + print_stats(al); +} + + +static int codec_read(struct audio_loop *al, int16_t *sampv, size_t sampc) +{ + uint8_t x[2560]; + size_t xlen = sizeof(x); + int err; + + aubuf_read_samp(al->ab, al->sampv, al->sampc); + + err = al->ac->ench(al->enc, x, &xlen, al->sampv, al->sampc); + if (err) + goto out; + + err = al->ac->dech(al->dec, sampv, &sampc, x, xlen); + if (err) + goto out; + + out: + + return err; +} + + +static void read_handler(const uint8_t *buf, size_t sz, void *arg) +{ + struct audio_loop *al = arg; + int err; + + ++al->n_read; + + err = aubuf_write(al->ab, buf, sz); + if (err) { + warning("auloop: aubuf_write: %m\n", err); + } +} + + +static bool write_handler(uint8_t *buf, size_t sz, void *arg) +{ + struct audio_loop *al = arg; + int err; + + ++al->n_write; + + /* read from beginning */ + if (al->ac) { + err = codec_read(al, (void *)buf, sz/2); + if (err) { + warning("auloop: codec_read error " + "on %u bytes (%m)\n", sz, err); + } + } + else { + aubuf_read(al->ab, buf, sz); + } + + return true; +} + + +static void error_handler(int err, const char *str, void *arg) +{ + (void)arg; + warning("auloop: ausrc error: %m (%s)\n", err, str); + gal = mem_deref(gal); +} + + +static void start_codec(struct audio_loop *al, const char *name) +{ + struct auenc_param prm = {PTIME}; + int err; + + al->ac = aucodec_find(name, + configv[al->index].srate, + configv[al->index].ch); + if (!al->ac) { + warning("auloop: could not find codec: %s\n", name); + return; + } + + if (al->ac->encupdh) { + err = al->ac->encupdh(&al->enc, al->ac, &prm, NULL); + if (err) { + warning("auloop: encoder update failed: %m\n", err); + } + } + + if (al->ac->decupdh) { + err = al->ac->decupdh(&al->dec, al->ac, NULL); + if (err) { + warning("auloop: decoder update failed: %m\n", err); + } + } +} + + +static int auloop_reset(struct audio_loop *al) +{ + struct auplay_prm auplay_prm; + struct ausrc_prm ausrc_prm; + const struct config *cfg = conf_config(); + int err; + + if (!cfg) + return ENOENT; + + /* Optional audio codec */ + if (str_isset(aucodec)) + start_codec(al, aucodec); + + /* audio player/source must be stopped first */ + al->auplay = mem_deref(al->auplay); + al->ausrc = mem_deref(al->ausrc); + + al->sampv = mem_deref(al->sampv); + al->ab = mem_deref(al->ab); + + al->srate = configv[al->index].srate; + al->ch = configv[al->index].ch; + + if (str_isset(aucodec)) { + al->sampc = al->srate * al->ch * PTIME / 1000; + al->sampv = mem_alloc(al->sampc * 2, NULL); + if (!al->sampv) + return ENOMEM; + } + + info("Audio-loop: %uHz, %dch\n", al->srate, al->ch); + + err = aubuf_alloc(&al->ab, 320, 0); + if (err) + return err; + + auplay_prm.fmt = AUFMT_S16LE; + auplay_prm.srate = al->srate; + auplay_prm.ch = al->ch; + auplay_prm.ptime = PTIME; + err = auplay_alloc(&al->auplay, cfg->audio.play_mod, &auplay_prm, + cfg->audio.play_dev, write_handler, al); + if (err) { + warning("auloop: auplay %s,%s failed: %m\n", + cfg->audio.play_mod, cfg->audio.play_dev, + err); + return err; + } + + ausrc_prm.fmt = AUFMT_S16LE; + ausrc_prm.srate = al->srate; + ausrc_prm.ch = al->ch; + ausrc_prm.ptime = PTIME; + err = ausrc_alloc(&al->ausrc, NULL, cfg->audio.src_mod, + &ausrc_prm, cfg->audio.src_dev, + read_handler, error_handler, al); + if (err) { + warning("auloop: ausrc %s,%s failed: %m\n", cfg->audio.src_mod, + cfg->audio.src_dev, err); + return err; + } + + return err; +} + + +static int audio_loop_alloc(struct audio_loop **alp) +{ + struct audio_loop *al; + int err; + + al = mem_zalloc(sizeof(*al), auloop_destructor); + if (!al) + return ENOMEM; + + tmr_start(&al->tmr, 100, tmr_handler, al); + + err = auloop_reset(al); + if (err) + goto out; + + out: + if (err) + mem_deref(al); + else + *alp = al; + + return err; +} + + +static int audio_loop_cycle(struct audio_loop *al) +{ + int err; + + ++al->index; + + if (al->index >= ARRAY_SIZE(configv)) { + gal = mem_deref(gal); + info("\nAudio-loop stopped\n"); + return 0; + } + + err = auloop_reset(al); + if (err) + return err; + + info("\nAudio-loop started: %uHz, %dch\n", al->srate, al->ch); + + return 0; +} + + +/** + * Start the audio loop (for testing) + */ +static int auloop_start(struct re_printf *pf, void *arg) +{ + int err; + + (void)pf; + (void)arg; + + if (gal) { + err = audio_loop_cycle(gal); + if (err) { + warning("auloop: loop cycle: %m\n", err); + } + } + else { + err = audio_loop_alloc(&gal); + if (err) { + warning("auloop: alloc failed %m\n", err); + } + } + + return err; +} + + +static int auloop_stop(struct re_printf *pf, void *arg) +{ + (void)arg; + + if (gal) { + (void)re_hprintf(pf, "audio-loop stopped\n"); + gal = mem_deref(gal); + } + + return 0; +} + + +static const struct cmd cmdv[] = { + {'a', 0, "Start audio-loop", auloop_start }, + {'A', 0, "Stop audio-loop", auloop_stop }, +}; + + +static int module_init(void) +{ + conf_get_str(conf_cur(), "auloop_codec", aucodec, sizeof(aucodec)); + + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + auloop_stop(NULL, NULL); + cmd_unregister(cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(auloop) = { + "auloop", + "application", + module_init, + module_close, +}; diff --git a/modules/auloop/module.mk b/modules/auloop/module.mk new file mode 100644 index 0000000..9da52d5 --- /dev/null +++ b/modules/auloop/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := auloop +$(MOD)_SRCS += auloop.c + +include mk/mod.mk diff --git a/modules/avcapture/avcapture.m b/modules/avcapture/avcapture.m new file mode 100644 index 0000000..564a525 --- /dev/null +++ b/modules/avcapture/avcapture.m @@ -0,0 +1,399 @@ +/** + * @file avcapture.m AVFoundation video capture + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <AVFoundation/AVFoundation.h> + + +static struct vidsrc *vidsrcv[4]; + + +@interface avcap : NSObject < AVCaptureVideoDataOutputSampleBufferDelegate > +{ + AVCaptureSession *sess; + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; + struct vidsrc_st *vsrc; +} +- (void)setCamera:(const char *)name; +@end + + +struct vidsrc_st { + struct vidsrc *vs; + avcap *cap; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static void vidframe_set_pixbuf(struct vidframe *f, const CVImageBufferRef b) +{ + OSType type; + int i; + + if (!f || !b) + return; + + type = CVPixelBufferGetPixelFormatType(b); + + switch (type) { + + case kCVPixelFormatType_32BGRA: + f->fmt = VID_FMT_ARGB; + break; + + case kCVPixelFormatType_422YpCbCr8: + f->fmt = VID_FMT_UYVY422; + break; + + case kCVPixelFormatType_420YpCbCr8Planar: + f->fmt = VID_FMT_YUV420P; + break; + + case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: + f->fmt = VID_FMT_NV12; + break; + + default: + re_printf("avcapture: pixfmt %c%c%c%c\n", + type>>24, type>>16, type>>8, type>>0); + f->fmt = -1; + f->data[0] = NULL; + return; + } + + f->size.w = (int)CVPixelBufferGetWidth(b); + f->size.h = (int)CVPixelBufferGetHeight(b); + + if (!CVPixelBufferIsPlanar(b)) { + + f->data[0] = CVPixelBufferGetBaseAddress(b); + f->linesize[0] = (int)CVPixelBufferGetBytesPerRow(b); + f->data[1] = f->data[2] = f->data[3] = NULL; + f->linesize[1] = f->linesize[2] = f->linesize[3] = 0; + + return; + } + + for (i=0; i<4; i++) { + f->data[i] = CVPixelBufferGetBaseAddressOfPlane(b, i); + f->linesize[i] = CVPixelBufferGetBytesPerRowOfPlane(b, i); + } +} + + +@implementation avcap + + +- (NSString *)map_preset:(AVCaptureDevice *)dev sz:(const struct vidsz *)sz +{ + static const struct { + struct vidsz sz; + NSString * const * preset; + } mapv[] = { + {{ 192, 144}, &AVCaptureSessionPresetLow }, + {{ 480, 360}, &AVCaptureSessionPresetMedium }, + {{ 640, 480}, &AVCaptureSessionPresetHigh }, + {{1280, 720}, &AVCaptureSessionPreset1280x720} + }; + int i, best = -1; + + for (i=ARRAY_SIZE(mapv)-1; i>=0; i--) { + + NSString *preset = *mapv[i].preset; + + if (![sess canSetSessionPreset:preset] || + ![dev supportsAVCaptureSessionPreset:preset]) + continue; + + if (mapv[i].sz.w >= sz->w && mapv[i].sz.h >= sz->h) + best = i; + else + break; + } + + if (best >= 0) + return *mapv[best].preset; + else { + NSLog(@"no suitable preset found for %d x %d", sz->w, sz->h); + return AVCaptureSessionPresetHigh; + } +} + + ++ (AVCaptureDevicePosition)get_position:(const char *)name +{ + if (0 == str_casecmp(name, "back")) + return AVCaptureDevicePositionBack; + else if (0 == str_casecmp(name, "front")) + return AVCaptureDevicePositionFront; + else + return -1; +} + + ++ (AVCaptureDevice *)get_device:(AVCaptureDevicePosition)pos +{ + AVCaptureDevice *dev; + + for (dev in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + if (dev.position == pos) + return dev; + } + + return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; +} + + +- (void)start:(id)unused +{ + (void)unused; + + [sess startRunning]; +} + + +- (id)init:(struct vidsrc_st *)st + dev:(const char *)name + size:(const struct vidsz *)sz +{ + dispatch_queue_t queue; + AVCaptureDevice *dev; + + self = [super init]; + if (!self) + return nil; + + vsrc = st; + + dev = [avcap get_device:[avcap get_position:name]]; + if (!dev) + return nil; + + input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:nil]; + output = [[AVCaptureVideoDataOutput alloc] init]; + sess = [[AVCaptureSession alloc] init]; + if (!input || !output || !sess) + return nil; + + output.alwaysDiscardsLateVideoFrames = YES; + + queue = dispatch_queue_create("avcapture", NULL); + [output setSampleBufferDelegate:self queue:queue]; + dispatch_release(queue); + + sess.sessionPreset = [self map_preset:dev sz:sz]; + + [sess addInput:input]; + [sess addOutput:output]; + + [self start:nil]; + + return self; +} + + +- (void)stop:(id)unused +{ + (void)unused; + + [sess stopRunning]; + + if (output) { + AVCaptureConnection *conn; + + for (conn in output.connections) + conn.enabled = NO; + } + + [sess beginConfiguration]; + if (input) + [sess removeInput:input]; + if (output) + [sess removeOutput:output]; + [sess commitConfiguration]; + + [sess release]; +} + + +- (void)captureOutput:(AVCaptureOutput *)captureOutput +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)conn +{ + const CVImageBufferRef b = CMSampleBufferGetImageBuffer(sampleBuffer); + struct vidframe vf; + + (void)captureOutput; + (void)conn; + + if (!vsrc->frameh) + return; + + CVPixelBufferLockBaseAddress(b, 0); + + vidframe_set_pixbuf(&vf, b); + + if (vidframe_isvalid(&vf)) + vsrc->frameh(&vf, vsrc->arg); + + CVPixelBufferUnlockBaseAddress(b, 0); +} + + +- (void)setCamera:(const char *)name +{ + AVCaptureDevicePosition pos; + AVCaptureDevice *dev; + + pos = [avcap get_position:name]; + + if (pos == input.device.position) + return; + + dev = [avcap get_device:pos]; + if (!dev) + return; + + [sess beginConfiguration]; + [sess removeInput:input]; + input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:nil]; + [sess addInput:input]; + [sess commitConfiguration]; +} + + +@end + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + st->frameh = NULL; + + [st->cap performSelectorOnMainThread:@selector(stop:) + withObject:nil + waitUntilDone:YES]; + + [st->cap release]; + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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) +{ + NSAutoreleasePool *pool; + struct vidsrc_st *st; + int err = 0; + + (void)ctx; + (void)prm; + (void)fmt; + (void)dev; + (void)errorh; + + if (!stp || !size) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + pool = [NSAutoreleasePool new]; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + st->cap = [[avcap alloc] init:st + dev:dev ? dev : "front" + size:size]; + if (!st->cap) { + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + [pool release]; + + return err; +} + + +static void update(struct vidsrc_st *st, struct vidsrc_prm *prm, + const char *dev) +{ + (void)prm; + + if (!st) + return; + + if (dev) + [st->cap setCamera:dev]; +} + + +static int module_init(void) +{ + AVCaptureDevice *dev = nil; + NSAutoreleasePool *pool; + Class cls = NSClassFromString(@"AVCaptureDevice"); + size_t i = 0; + int err = 0; + if (!cls) + return ENOSYS; + + pool = [NSAutoreleasePool new]; + + /* populate devices */ + for (dev in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + + const char *name = [[dev localizedName] UTF8String]; + + if (i >= ARRAY_SIZE(vidsrcv)) + break; + + err = vidsrc_register(&vidsrcv[i++], name, alloc, update); + if (err) + break; + } + + [pool drain]; + + return err; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(vidsrcv); i++) + vidsrcv[i] = mem_deref(vidsrcv[i]); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(avcapture) = { + "avcapture", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/avcapture/module.mk b/modules/avcapture/module.mk new file mode 100644 index 0000000..d5d688e --- /dev/null +++ b/modules/avcapture/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := avcapture +$(MOD)_SRCS += avcapture.m +$(MOD)_LFLAGS += -framework AVFoundation + +include mk/mod.mk diff --git a/modules/avcodec/avcodec.c b/modules/avcodec/avcodec.c new file mode 100644 index 0000000..d6ce3de --- /dev/null +++ b/modules/avcodec/avcodec.c @@ -0,0 +1,176 @@ +/** + * @file avcodec.c Video codecs using FFmpeg libavcodec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#ifdef USE_X264 +#include <x264.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +int avcodec_resolve_codecid(const char *s) +{ + if (0 == str_casecmp(s, "H263")) + return CODEC_ID_H263; + else if (0 == str_casecmp(s, "H264")) + return CODEC_ID_H264; + else if (0 == str_casecmp(s, "MP4V-ES")) + return CODEC_ID_MPEG4; + else + return CODEC_ID_NONE; +} + + +static uint32_t packetization_mode(const char *fmtp) +{ + struct pl pl, mode; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "packetization-mode", &mode)) + return pl_u32(&mode); + + return 0; +} + + +static int h264_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + struct vidcodec *vc = arg; + const uint8_t profile_idc = 0x42; /* baseline profile */ + const uint8_t profile_iop = 0x80; + (void)offer; + + if (!mb || !fmt || !vc) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s" + " packetization-mode=0" + ";profile-level-id=%02x%02x%02x" + "\r\n", + fmt->id, profile_idc, profile_iop, h264_level_idc); +} + + +static bool h264_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data) +{ + (void)data; + + return packetization_mode(fmtp1) == packetization_mode(fmtp2); +} + + +static int h263_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + (void)offer; + (void)arg; + + if (!mb || !fmt) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s CIF=1;CIF4=1\r\n", fmt->id); +} + + +static int mpg4_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + (void)offer; + (void)arg; + + if (!mb || !fmt) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s profile-level-id=3\r\n", fmt->id); +} + + +static struct vidcodec h264 = { + .name = "H264", + .variant = "packetization-mode=0", + .encupdh = encode_update, +#ifdef USE_X264 + .ench = encode_x264, +#else + .ench = encode, +#endif + .decupdh = decode_update, + .dech = decode_h264, + .fmtp_ench = h264_fmtp_enc, + .fmtp_cmph = h264_fmtp_cmp, +}; + +static struct vidcodec h263 = { + .pt = "34", + .name = "H263", + .encupdh = encode_update, + .ench = encode, + .decupdh = decode_update, + .dech = decode_h263, + .fmtp_ench = h263_fmtp_enc, +}; + +static struct vidcodec mpg4 = { + .name = "MP4V-ES", + .encupdh = encode_update, + .ench = encode, + .decupdh = decode_update, + .dech = decode_mpeg4, + .fmtp_ench = mpg4_fmtp_enc, +}; + + +static int module_init(void) +{ +#ifdef USE_X264 + debug("avcodec: x264 build %d\n", X264_BUILD); +#else + debug("avcodec: using FFmpeg H.264 encoder\n"); +#endif + +#if LIBAVCODEC_VERSION_INT < ((53<<16)+(10<<8)+0) + avcodec_init(); +#endif + + avcodec_register_all(); + + if (avcodec_find_decoder(CODEC_ID_H264)) + vidcodec_register(&h264); + + if (avcodec_find_decoder(CODEC_ID_H263)) + vidcodec_register(&h263); + + if (avcodec_find_decoder(CODEC_ID_MPEG4)) + vidcodec_register(&mpg4); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&mpg4); + vidcodec_unregister(&h263); + vidcodec_unregister(&h264); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(avcodec) = { + "avcodec", + "codec", + module_init, + module_close +}; diff --git a/modules/avcodec/avcodec.h b/modules/avcodec/avcodec.h new file mode 100644 index 0000000..bbd022a --- /dev/null +++ b/modules/avcodec/avcodec.h @@ -0,0 +1,62 @@ +/** + * @file avcodec.h Video codecs using FFmpeg libavcodec -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +#if LIBAVCODEC_VERSION_INT >= ((54<<16)+(25<<8)+0) +#define CodecID AVCodecID +#endif + + +extern const uint8_t h264_level_idc; + + +/* + * Encode + */ + +struct videnc_state; + +int encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp); +int encode(struct videnc_state *st, bool update, const struct vidframe *frame, + videnc_packet_h *pkth, void *arg); +#ifdef USE_X264 +int encode_x264(struct videnc_state *st, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg); +#endif + + +/* + * Decode + */ + +struct viddec_state; + +int decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int decode_h263(struct viddec_state *st, struct vidframe *frame, + bool eof, uint16_t seq, struct mbuf *src); +int decode_h264(struct viddec_state *st, struct vidframe *frame, + bool eof, uint16_t seq, struct mbuf *src); +int decode_mpeg4(struct viddec_state *st, struct vidframe *frame, + bool eof, uint16_t seq, struct mbuf *src); +int decode_h263_test(struct viddec_state *st, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *src); + + +int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name, + const struct pl *val); +int h264_packetize(struct mbuf *mb, size_t pktsize, + videnc_packet_h *pkth, void *arg); +int h264_decode(struct viddec_state *st, struct mbuf *src); +int h264_nal_send(bool first, bool last, + bool marker, uint32_t ihdr, const uint8_t *buf, + size_t size, size_t maxsz, + videnc_packet_h *pkth, void *arg); + + +int avcodec_resolve_codecid(const char *s); diff --git a/modules/avcodec/decode.c b/modules/avcodec/decode.c new file mode 100644 index 0000000..36550a7 --- /dev/null +++ b/modules/avcodec/decode.c @@ -0,0 +1,346 @@ +/** + * @file avcodec/decode.c Video codecs using FFmpeg libavcodec -- decoder + * + * Copyright (C) 2010 - 2013 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#include <libavutil/mem.h> +#include "h26x.h" +#include "avcodec.h" + + +struct viddec_state { + AVCodec *codec; + AVCodecContext *ctx; + AVFrame *pict; + struct mbuf *mb; + bool got_keyframe; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *st = arg; + + mem_deref(st->mb); + + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); + av_free(st->ctx); + } + + if (st->pict) + av_free(st->pict); +} + + +static int init_decoder(struct viddec_state *st, const char *name) +{ + enum CodecID codec_id; + + codec_id = avcodec_resolve_codecid(name); + if (codec_id == CODEC_ID_NONE) + return EINVAL; + + st->codec = avcodec_find_decoder(codec_id); + if (!st->codec) + return ENOENT; + +#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(92<<8)+0) + st->ctx = avcodec_alloc_context3(st->codec); +#else + st->ctx = avcodec_alloc_context(); +#endif + +#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100) + st->pict = av_frame_alloc(); +#else + st->pict = avcodec_alloc_frame(); +#endif + + if (!st->ctx || !st->pict) + return ENOMEM; + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0) + if (avcodec_open2(st->ctx, st->codec, NULL) < 0) + return ENOENT; +#else + if (avcodec_open(st->ctx, st->codec) < 0) + return ENOENT; +#endif + + return 0; +} + + +int decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *st; + int err = 0; + + if (!vdsp || !vc) + return EINVAL; + + if (*vdsp) + return 0; + + (void)fmtp; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->mb = mbuf_alloc(1024); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + err = init_decoder(st, vc->name); + if (err) { + warning("avcodec: %s: could not init decoder\n", vc->name); + goto out; + } + + debug("avcodec: video decoder %s (%s)\n", vc->name, fmtp); + + out: + if (err) + mem_deref(st); + else + *vdsp = st; + + return err; +} + + +/* + * TODO: check input/output size + */ +static int ffdecode(struct viddec_state *st, struct vidframe *frame, + bool eof, struct mbuf *src) +{ + int i, got_picture, ret, err; + + /* assemble packets in "mbuf" */ + err = mbuf_write_mem(st->mb, mbuf_buf(src), mbuf_get_left(src)); + if (err) + return err; + + if (!eof) + return 0; + + st->mb->pos = 0; + + if (!st->got_keyframe) { + err = EPROTO; + goto out; + } + +#if LIBAVCODEC_VERSION_INT <= ((52<<16)+(23<<8)+0) + ret = avcodec_decode_video(st->ctx, st->pict, &got_picture, + st->mb->buf, + (int)mbuf_get_left(st->mb)); +#else + do { + AVPacket avpkt; + + av_init_packet(&avpkt); + avpkt.data = st->mb->buf; + avpkt.size = (int)mbuf_get_left(st->mb); + + ret = avcodec_decode_video2(st->ctx, st->pict, + &got_picture, &avpkt); + } while (0); +#endif + + if (ret < 0) { + err = EBADMSG; + goto out; + } + + mbuf_skip_to_end(src); + + if (got_picture) { + for (i=0; i<4; i++) { + frame->data[i] = st->pict->data[i]; + frame->linesize[i] = st->pict->linesize[i]; + } + frame->size.w = st->ctx->width; + frame->size.h = st->ctx->height; + frame->fmt = VID_FMT_YUV420P; + } + + out: + if (eof) + mbuf_rewind(st->mb); + + return err; +} + + +int h264_decode(struct viddec_state *st, struct mbuf *src) +{ + struct h264_hdr h264_hdr; + const uint8_t nal_seq[3] = {0, 0, 1}; + int err; + + err = h264_hdr_decode(&h264_hdr, src); + if (err) + return err; + + if (h264_hdr.f) { + info("avcodec: H264 forbidden bit set!\n"); + return EBADMSG; + } + + /* handle NAL types */ + if (1 <= h264_hdr.type && h264_hdr.type <= 23) { + + if (!st->got_keyframe) { + switch (h264_hdr.type) { + + case H264_NAL_PPS: + case H264_NAL_SPS: + st->got_keyframe = true; + break; + } + } + + /* prepend H.264 NAL start sequence */ + mbuf_write_mem(st->mb, nal_seq, 3); + + /* encode NAL header back to buffer */ + err = h264_hdr_encode(&h264_hdr, st->mb); + } + else if (H264_NAL_FU_A == h264_hdr.type) { + struct fu fu; + + err = fu_hdr_decode(&fu, src); + if (err) + return err; + h264_hdr.type = fu.type; + + if (fu.s) { + /* prepend H.264 NAL start sequence */ + mbuf_write_mem(st->mb, nal_seq, 3); + + /* encode NAL header back to buffer */ + err = h264_hdr_encode(&h264_hdr, st->mb); + } + } + else { + warning("avcodec: unknown NAL type %u\n", h264_hdr.type); + return EBADMSG; + } + + return err; +} + + +int decode_h264(struct viddec_state *st, struct vidframe *frame, + bool eof, uint16_t seq, struct mbuf *src) +{ + int err; + + (void)seq; + + if (!src) + return 0; + + err = h264_decode(st, src); + if (err) + return err; + + return ffdecode(st, frame, eof, src); +} + + +int decode_mpeg4(struct viddec_state *st, struct vidframe *frame, + bool eof, uint16_t seq, struct mbuf *src) +{ + if (!src) + return 0; + + (void)seq; + + /* let the decoder handle this */ + st->got_keyframe = true; + + return ffdecode(st, frame, eof, src); +} + + +int decode_h263(struct viddec_state *st, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *src) +{ + struct h263_hdr hdr; + int err; + + if (!st || !frame) + return EINVAL; + + if (!src) + return 0; + + (void)seq; + + err = h263_hdr_decode(&hdr, src); + if (err) + return err; + +#if 0 + debug(".....[%s seq=%5u ] MODE %s -" + " SBIT=%u EBIT=%u I=%s" + " (%5u/%5u bytes)\n", + marker ? "M" : " ", seq, + h263_hdr_mode(&hdr) == H263_MODE_A ? "A" : "B", + hdr.sbit, hdr.ebit, hdr.i ? "Inter" : "Intra", + mbuf_get_left(src), st->mb->end); +#endif + + if (!hdr.i) + st->got_keyframe = true; + +#if 0 + if (st->mb->pos == 0) { + uint8_t *p = mbuf_buf(src); + + if (p[0] != 0x00 || p[1] != 0x00) { + warning("invalid PSC detected (%02x %02x)\n", + p[0], p[1]); + return EPROTO; + } + } +#endif + + /* + * The H.263 Bit-stream can be fragmented on bit-level, + * indicated by SBIT and EBIT. Example: + * + * 8 bit 2 bit + * .--------.--. + * Packet 1 | | | + * SBIT=0 '--------'--' + * EBIT=6 + * .------.--------.--------. + * Packet 2 | | | | + * SBIT=2 '------'--------'--------' + * EBIT=0 6bit 8bit 8bit + * + */ + + if (hdr.sbit > 0) { + const uint8_t mask = (1 << (8 - hdr.sbit)) - 1; + const uint8_t sbyte = mbuf_read_u8(src) & mask; + + st->mb->buf[st->mb->end - 1] |= sbyte; + } + + return ffdecode(st, frame, marker, src); +} diff --git a/modules/avcodec/encode.c b/modules/avcodec/encode.c new file mode 100644 index 0000000..559c53e --- /dev/null +++ b/modules/avcodec/encode.c @@ -0,0 +1,646 @@ +/** + * @file avcodec/encode.c Video codecs using FFmpeg libavcodec -- encoder + * + * Copyright (C) 2010 - 2013 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#include <libavutil/mem.h> +#ifdef USE_X264 +#include <x264.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +enum { + DEFAULT_GOP_SIZE = 10, +}; + + +struct picsz { + enum h263_fmt fmt; /**< Picture size */ + uint8_t mpi; /**< Minimum Picture Interval (1-32) */ +}; + + +struct videnc_state { + AVCodec *codec; + AVCodecContext *ctx; + AVFrame *pict; + struct mbuf *mb; + size_t sz_max; /* todo: figure out proper buffer size */ + int64_t pts; + struct mbuf *mb_frag; + struct videnc_param encprm; + struct vidsz encsize; + enum CodecID codec_id; + + union { + struct { + struct picsz picszv[8]; + uint32_t picszn; + } h263; + + struct { + uint32_t packetization_mode; + uint32_t profile_idc; + uint32_t profile_iop; + uint32_t level_idc; + uint32_t max_fs; + uint32_t max_smbps; + } h264; + } u; + +#ifdef USE_X264 + x264_t *x264; +#endif +}; + + +static void destructor(void *arg) +{ + struct videnc_state *st = arg; + + mem_deref(st->mb); + mem_deref(st->mb_frag); + +#ifdef USE_X264 + if (st->x264) + x264_encoder_close(st->x264); +#endif + + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); + av_free(st->ctx); + } + + if (st->pict) + av_free(st->pict); +} + + +static enum h263_fmt h263_fmt(const struct pl *name) +{ + if (0 == pl_strcasecmp(name, "sqcif")) return H263_FMT_SQCIF; + if (0 == pl_strcasecmp(name, "qcif")) return H263_FMT_QCIF; + if (0 == pl_strcasecmp(name, "cif")) return H263_FMT_CIF; + if (0 == pl_strcasecmp(name, "cif4")) return H263_FMT_4CIF; + if (0 == pl_strcasecmp(name, "cif16")) return H263_FMT_16CIF; + return H263_FMT_OTHER; +} + + +static int decode_sdpparam_h263(struct videnc_state *st, const struct pl *name, + const struct pl *val) +{ + enum h263_fmt fmt = h263_fmt(name); + const int mpi = pl_u32(val); + + if (fmt == H263_FMT_OTHER) { + info("h263: unknown param '%r'\n", name); + return 0; + } + if (mpi < 1 || mpi > 32) { + info("h263: %r: MPI out of range %d\n", name, mpi); + return 0; + } + + if (st->u.h263.picszn >= ARRAY_SIZE(st->u.h263.picszv)) { + info("h263: picszv overflow: %r\n", name); + return 0; + } + + st->u.h263.picszv[st->u.h263.picszn].fmt = fmt; + st->u.h263.picszv[st->u.h263.picszn].mpi = mpi; + + ++st->u.h263.picszn; + + return 0; +} + + +static int init_encoder(struct videnc_state *st) +{ + st->codec = avcodec_find_encoder(st->codec_id); + if (!st->codec) + return ENOENT; + + return 0; +} + + +static int open_encoder(struct videnc_state *st, + const struct videnc_param *prm, + const struct vidsz *size) +{ + int err = 0; + + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); + av_free(st->ctx); + } + + if (st->pict) + av_free(st->pict); + +#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(92<<8)+0) + st->ctx = avcodec_alloc_context3(st->codec); +#else + st->ctx = avcodec_alloc_context(); +#endif + +#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100) + st->pict = av_frame_alloc(); +#else + st->pict = avcodec_alloc_frame(); +#endif + + if (!st->ctx || !st->pict) { + err = ENOMEM; + goto out; + } + + st->ctx->bit_rate = prm->bitrate; + st->ctx->width = size->w; + st->ctx->height = size->h; + st->ctx->gop_size = DEFAULT_GOP_SIZE; + st->ctx->pix_fmt = PIX_FMT_YUV420P; + st->ctx->time_base.num = 1; + st->ctx->time_base.den = prm->fps; + + /* params to avoid ffmpeg/x264 default preset error */ + if (st->codec_id == CODEC_ID_H264) { + st->ctx->me_method = ME_UMH; + st->ctx->me_range = 16; + st->ctx->qmin = 10; + st->ctx->qmax = 51; + st->ctx->max_qdiff = 4; + } + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0) + if (avcodec_open2(st->ctx, st->codec, NULL) < 0) { + err = ENOENT; + goto out; + } +#else + if (avcodec_open(st->ctx, st->codec) < 0) { + err = ENOENT; + goto out; + } +#endif + + out: + if (err) { + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); + av_free(st->ctx); + st->ctx = NULL; + } + + if (st->pict) { + av_free(st->pict); + st->pict = NULL; + } + } + else + st->encsize = *size; + + return err; +} + + +int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name, + const struct pl *val) +{ + if (0 == pl_strcasecmp(name, "packetization-mode")) { + st->u.h264.packetization_mode = pl_u32(val); + + if (st->u.h264.packetization_mode != 0) { + warning("avcodec: illegal packetization-mode %u\n", + st->u.h264.packetization_mode); + return EPROTO; + } + } + else if (0 == pl_strcasecmp(name, "profile-level-id")) { + struct pl prof = *val; + if (prof.l != 6) { + warning("avcodec: invalid profile-level-id (%r)\n", + val); + return EPROTO; + } + + prof.l = 2; + st->u.h264.profile_idc = pl_x32(&prof); prof.p += 2; + st->u.h264.profile_iop = pl_x32(&prof); prof.p += 2; + st->u.h264.level_idc = pl_x32(&prof); + } + else if (0 == pl_strcasecmp(name, "max-fs")) { + st->u.h264.max_fs = pl_u32(val); + } + else if (0 == pl_strcasecmp(name, "max-smbps")) { + st->u.h264.max_smbps = pl_u32(val); + } + + return 0; +} + + +static void param_handler(const struct pl *name, const struct pl *val, + void *arg) +{ + struct videnc_state *st = arg; + + if (st->codec_id == CODEC_ID_H263) + (void)decode_sdpparam_h263(st, name, val); + else if (st->codec_id == CODEC_ID_H264) + (void)decode_sdpparam_h264(st, name, val); +} + + +static int general_packetize(struct mbuf *mb, size_t pktsize, + videnc_packet_h *pkth, void *arg) +{ + int err = 0; + + /* Assemble frame into smaller packets */ + while (!err) { + size_t sz, left = mbuf_get_left(mb); + bool last = (left < pktsize); + if (!left) + break; + + sz = last ? left : pktsize; + + err = pkth(last, NULL, 0, mbuf_buf(mb), sz, arg); + + mbuf_advance(mb, sz); + } + + return err; +} + + +static int h263_packetize(struct videnc_state *st, struct mbuf *mb, + videnc_packet_h *pkth, void *arg) +{ + struct h263_strm h263_strm; + struct h263_hdr h263_hdr; + size_t pos; + int err; + + /* Decode bit-stream header, used by packetizer */ + err = h263_strm_decode(&h263_strm, mb); + if (err) + return err; + + h263_hdr_copy_strm(&h263_hdr, &h263_strm); + + st->mb_frag->pos = st->mb_frag->end = 0; + err = h263_hdr_encode(&h263_hdr, st->mb_frag); + pos = st->mb_frag->pos; + + /* Assemble frame into smaller packets */ + while (!err) { + size_t sz, left = mbuf_get_left(mb); + bool last = (left < st->encprm.pktsize); + if (!left) + break; + + sz = last ? left : st->encprm.pktsize; + + st->mb_frag->pos = st->mb_frag->end = pos; + err = mbuf_write_mem(st->mb_frag, mbuf_buf(mb), sz); + if (err) + break; + + st->mb_frag->pos = 0; + + err = pkth(last, NULL, 0, mbuf_buf(st->mb_frag), + mbuf_get_left(st->mb_frag), arg); + + mbuf_advance(mb, sz); + } + + return err; +} + + +#ifdef USE_X264 +static int open_encoder_x264(struct videnc_state *st, struct videnc_param *prm, + const struct vidsz *size) +{ + x264_param_t xprm; + + x264_param_default(&xprm); + +#if X264_BUILD >= 87 + x264_param_apply_profile(&xprm, "baseline"); +#endif + + xprm.i_level_idc = h264_level_idc; + xprm.i_width = size->w; + xprm.i_height = size->h; + xprm.i_csp = X264_CSP_I420; + xprm.i_fps_num = prm->fps; + xprm.i_fps_den = 1; + xprm.rc.i_bitrate = prm->bitrate / 1024; /* kbit/s */ + xprm.rc.i_rc_method = X264_RC_CQP; + xprm.i_log_level = X264_LOG_WARNING; + + /* ultrafast preset */ + xprm.i_frame_reference = 1; + xprm.i_scenecut_threshold = 0; + xprm.b_deblocking_filter = 0; + xprm.b_cabac = 0; + xprm.i_bframe = 0; + xprm.analyse.intra = 0; + xprm.analyse.inter = 0; + xprm.analyse.b_transform_8x8 = 0; + xprm.analyse.i_me_method = X264_ME_DIA; + xprm.analyse.i_subpel_refine = 0; +#if X264_BUILD >= 59 + xprm.rc.i_aq_mode = 0; +#endif + xprm.analyse.b_mixed_references = 0; + xprm.analyse.i_trellis = 0; +#if X264_BUILD >= 63 + xprm.i_bframe_adaptive = X264_B_ADAPT_NONE; +#endif +#if X264_BUILD >= 70 + xprm.rc.b_mb_tree = 0; +#endif + + /* slice-based threading (--tune=zerolatency) */ +#if X264_BUILD >= 80 + xprm.rc.i_lookahead = 0; + xprm.i_sync_lookahead = 0; + xprm.i_bframe = 0; +#endif + + /* put SPS/PPS before each keyframe */ + xprm.b_repeat_headers = 1; + +#if X264_BUILD >= 82 + /* needed for x264_encoder_intra_refresh() */ + xprm.b_intra_refresh = 1; +#endif + + if (st->x264) + x264_encoder_close(st->x264); + + st->x264 = x264_encoder_open(&xprm); + if (!st->x264) { + warning("avcodec: x264_encoder_open() failed\n"); + return ENOENT; + } + + st->encsize = *size; + + return 0; +} +#endif + + +int encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp) +{ + struct videnc_state *st; + int err = 0; + + if (!vesp || !vc || !prm) + return EINVAL; + + if (*vesp) + return 0; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->encprm = *prm; + + st->codec_id = avcodec_resolve_codecid(vc->name); + if (st->codec_id == CODEC_ID_NONE) { + err = EINVAL; + goto out; + } + + st->mb = mbuf_alloc(FF_MIN_BUFFER_SIZE * 20); + st->mb_frag = mbuf_alloc(1024); + if (!st->mb || !st->mb_frag) { + err = ENOMEM; + goto out; + } + + st->sz_max = st->mb->size; + + if (st->codec_id == CODEC_ID_H264) { +#ifndef USE_X264 + err = init_encoder(st); +#endif + } + else + err = init_encoder(st); + if (err) { + warning("avcodec: %s: could not init encoder\n", vc->name); + goto out; + } + + if (str_isset(fmtp)) { + struct pl sdp_fmtp; + + pl_set_str(&sdp_fmtp, fmtp); + + fmt_param_apply(&sdp_fmtp, param_handler, st); + } + + debug("avcodec: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, prm->fps, prm->bitrate, prm->pktsize); + + out: + if (err) + mem_deref(st); + else + *vesp = st; + + return err; +} + + +#ifdef USE_X264 +int encode_x264(struct videnc_state *st, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg) +{ + x264_picture_t pic_in, pic_out; + x264_nal_t *nal; + int i_nal; + int i, err, ret; + + if (!st->x264 || !vidsz_cmp(&st->encsize, &frame->size)) { + + err = open_encoder_x264(st, &st->encprm, &frame->size); + if (err) + return err; + } + + if (update) { +#if X264_BUILD >= 95 + x264_encoder_intra_refresh(st->x264); +#endif + debug("avcodec: x264 picture update\n"); + } + + x264_picture_init(&pic_in); + + pic_in.i_type = update ? X264_TYPE_IDR : X264_TYPE_AUTO; + pic_in.i_qpplus1 = 0; + pic_in.i_pts = ++st->pts; + + pic_in.img.i_csp = X264_CSP_I420; + pic_in.img.i_plane = 3; + for (i=0; i<3; i++) { + pic_in.img.i_stride[i] = frame->linesize[i]; + pic_in.img.plane[i] = frame->data[i]; + } + + ret = x264_encoder_encode(st->x264, &nal, &i_nal, &pic_in, &pic_out); + if (ret < 0) { + fprintf(stderr, "x264 [error]: x264_encoder_encode failed\n"); + } + if (i_nal == 0) + return 0; + + err = 0; + for (i=0; i<i_nal && !err; i++) { + const uint8_t hdr = nal[i].i_ref_idc<<5 | nal[i].i_type<<0; + int offset = 0; + +#if X264_BUILD >= 76 + const uint8_t *p = nal[i].p_payload; + + /* Find the NAL Escape code [00 00 01] */ + if (nal[i].i_payload > 4 && p[0] == 0x00 && p[1] == 0x00) { + if (p[2] == 0x00 && p[3] == 0x01) + offset = 4 + 1; + else if (p[2] == 0x01) + offset = 3 + 1; + } +#endif + + /* skip Supplemental Enhancement Information (SEI) */ + if (nal[i].i_type == H264_NAL_SEI) + continue; + + err = h264_nal_send(true, true, (i+1)==i_nal, hdr, + nal[i].p_payload + offset, + nal[i].i_payload - offset, + st->encprm.pktsize, pkth, arg); + } + + return err; +} +#endif + + +int encode(struct videnc_state *st, bool update, const struct vidframe *frame, + videnc_packet_h *pkth, void *arg) +{ + int i, err, ret; + + if (!st || !frame || !pkth) + return EINVAL; + + if (!st->ctx || !vidsz_cmp(&st->encsize, &frame->size)) { + + err = open_encoder(st, &st->encprm, &frame->size); + if (err) { + warning("avcodec: open_encoder: %m\n", err); + return err; + } + } + + for (i=0; i<4; i++) { + st->pict->data[i] = frame->data[i]; + st->pict->linesize[i] = frame->linesize[i]; + } + st->pict->pts = st->pts++; + if (update) { + debug("avcodec: encoder picture update\n"); + st->pict->key_frame = 1; +#ifdef FF_I_TYPE + st->pict->pict_type = FF_I_TYPE; /* Infra Frame */ +#else + st->pict->pict_type = AV_PICTURE_TYPE_I; +#endif + } + else { + st->pict->key_frame = 0; + st->pict->pict_type = 0; + } + + mbuf_rewind(st->mb); + +#if LIBAVCODEC_VERSION_INT >= ((54<<16)+(1<<8)+0) + do { + AVPacket avpkt; + int got_packet; + + av_init_packet(&avpkt); + + avpkt.data = st->mb->buf; + avpkt.size = (int)st->mb->size; + + ret = avcodec_encode_video2(st->ctx, &avpkt, + st->pict, &got_packet); + if (ret < 0) + return EBADMSG; + if (!got_packet) + return 0; + + mbuf_set_end(st->mb, avpkt.size); + + } while (0); +#else + ret = avcodec_encode_video(st->ctx, st->mb->buf, + (int)st->mb->size, st->pict); + if (ret < 0 ) + return EBADMSG; + + /* todo: figure out proper buffer size */ + if (ret > (int)st->sz_max) { + debug("avcodec: grow encode buffer %u --> %d\n", + st->sz_max, ret); + st->sz_max = ret; + } + + mbuf_set_end(st->mb, ret); +#endif + + switch (st->codec_id) { + + case CODEC_ID_H263: + err = h263_packetize(st, st->mb, pkth, arg); + break; + + case CODEC_ID_H264: + err = h264_packetize(st->mb, st->encprm.pktsize, pkth, arg); + break; + + case CODEC_ID_MPEG4: + err = general_packetize(st->mb, st->encprm.pktsize, pkth, arg); + break; + + default: + err = EPROTO; + break; + } + + return err; +} diff --git a/modules/avcodec/h263.c b/modules/avcodec/h263.c new file mode 100644 index 0000000..7e29ecd --- /dev/null +++ b/modules/avcodec/h263.c @@ -0,0 +1,176 @@ +/** + * @file h263.c H.263 video codec (RFC 4629) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#ifdef USE_X264 +#include <x264.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +int h263_hdr_encode(const struct h263_hdr *hdr, struct mbuf *mb) +{ + uint32_t v; /* host byte order */ + + v = hdr->f<<31 | hdr->p<<30 | hdr->sbit<<27 | hdr->ebit<<24; + v |= hdr->src<<21 | hdr->i<<20 | hdr->u<<19 | hdr->s<<18 | hdr->a<<17; + v |= hdr->r<<13 | hdr->dbq<<11 | hdr->trb<<8 | hdr->tr<<0; + + return mbuf_write_u32(mb, htonl(v)); +} + + +enum h263_mode h263_hdr_mode(const struct h263_hdr *hdr) +{ + if (!hdr->f) { + return H263_MODE_A; + } + else { + if (!hdr->p) + return H263_MODE_B; + else + return H263_MODE_C; + } +} + + +int h263_hdr_decode(struct h263_hdr *hdr, struct mbuf *mb) +{ + uint32_t v; + + if (!hdr) + return EINVAL; + if (mbuf_get_left(mb) < H263_HDR_SIZE_MODEA) + return EBADMSG; + + v = ntohl(mbuf_read_u32(mb)); + + /* Common */ + hdr->f = v>>31 & 0x1; + hdr->p = v>>30 & 0x1; + hdr->sbit = v>>27 & 0x7; + hdr->ebit = v>>24 & 0x7; + hdr->src = v>>21 & 0x7; + + switch (h263_hdr_mode(hdr)) { + + case H263_MODE_A: + hdr->i = v>>20 & 0x1; + hdr->u = v>>19 & 0x1; + hdr->s = v>>18 & 0x1; + hdr->a = v>>17 & 0x1; + hdr->r = v>>13 & 0xf; + hdr->dbq = v>>11 & 0x3; + hdr->trb = v>>8 & 0x7; + hdr->tr = v>>0 & 0xff; + break; + + case H263_MODE_B: + hdr->quant = v>>16 & 0x1f; + hdr->gobn = v>>11 & 0x1f; + hdr->mba = v>>2 & 0x1ff; + + if (mbuf_get_left(mb) < 4) + return EBADMSG; + + v = ntohl(mbuf_read_u32(mb)); + + hdr->i = v>>31 & 0x1; + hdr->u = v>>30 & 0x1; + hdr->s = v>>29 & 0x1; + hdr->a = v>>28 & 0x1; + hdr->hmv1 = v>>21 & 0x7f; + hdr->vmv1 = v>>14 & 0x7f; + hdr->hmv2 = v>>7 & 0x7f; + hdr->vmv2 = v>>0 & 0x7f; + break; + + case H263_MODE_C: + /* NOTE: Mode C is optional, only parts decoded */ + if (mbuf_get_left(mb) < 8) + return EBADMSG; + + v = ntohl(mbuf_read_u32(mb)); + hdr->i = v>>31 & 0x1; + hdr->u = v>>30 & 0x1; + hdr->s = v>>29 & 0x1; + hdr->a = v>>28 & 0x1; + + (void)mbuf_read_u32(mb); /* ignore */ + break; + } + + return 0; +} + + +/** Find PSC (Picture Start Code) in bit-stream */ +const uint8_t *h263_strm_find_psc(const uint8_t *p, uint32_t size) +{ + const uint8_t *end = p + size - 1; + + for (; p < end; p++) { + if (p[0] == 0x00 && p[1] == 0x00) + return p; + } + + return NULL; +} + + +int h263_strm_decode(struct h263_strm *s, struct mbuf *mb) +{ + const uint8_t *p; + + if (mbuf_get_left(mb) < 6) + return EINVAL; + + p = mbuf_buf(mb); + + s->psc[0] = p[0]; + s->psc[1] = p[1]; + + s->temp_ref = (p[2]<<6 & 0xc0) | (p[3]>>2 & 0x3f); + + s->split_scr = p[4]>>7 & 0x1; + s->doc_camera = p[4]>>6 & 0x1; + s->pic_frz_rel = p[4]>>5 & 0x1; + s->src_fmt = p[4]>>2 & 0x7; + s->pic_type = p[4]>>1 & 0x1; + s->umv = p[4]>>0 & 0x1; + + s->sac = p[5]>>7 & 0x1; + s->apm = p[5]>>6 & 0x1; + s->pb = p[5]>>5 & 0x1; + s->pquant = p[5]>>0 & 0x1f; + + s->cpm = p[6]>>7 & 0x1; + s->pei = p[6]>>6 & 0x1; + + return 0; +} + + +/** Copy H.263 bit-stream to H.263 RTP payload header */ +void h263_hdr_copy_strm(struct h263_hdr *hdr, const struct h263_strm *s) +{ + hdr->f = 0; /* Mode A */ + hdr->p = 0; + hdr->sbit = 0; + hdr->ebit = 0; + hdr->src = s->src_fmt; + hdr->i = s->pic_type; + hdr->u = s->umv; + hdr->s = s->sac; + hdr->a = s->apm; + hdr->r = 0; + hdr->dbq = 0; /* No PB-frames */ + hdr->trb = 0; /* No PB-frames */ + hdr->tr = s->temp_ref; +} diff --git a/modules/avcodec/h264.c b/modules/avcodec/h264.c new file mode 100644 index 0000000..4c2aa59 --- /dev/null +++ b/modules/avcodec/h264.c @@ -0,0 +1,188 @@ +/** + * @file avcodec/h264.c H.264 video codec (RFC 3984) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#ifdef USE_X264 +#include <x264.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +const uint8_t h264_level_idc = 0x0c; + + +int h264_hdr_encode(const struct h264_hdr *hdr, struct mbuf *mb) +{ + uint8_t v; + + v = hdr->f<<7 | hdr->nri<<5 | hdr->type<<0; + + return mbuf_write_u8(mb, v); +} + + +int h264_hdr_decode(struct h264_hdr *hdr, struct mbuf *mb) +{ + uint8_t v; + + if (mbuf_get_left(mb) < 1) + return ENOENT; + + v = mbuf_read_u8(mb); + + hdr->f = v>>7 & 0x1; + hdr->nri = v>>5 & 0x3; + hdr->type = v>>0 & 0x1f; + + return 0; +} + + +int fu_hdr_encode(const struct fu *fu, struct mbuf *mb) +{ + uint8_t v = fu->s<<7 | fu->s<<6 | fu->r<<5 | fu->type; + return mbuf_write_u8(mb, v); +} + + +int fu_hdr_decode(struct fu *fu, struct mbuf *mb) +{ + uint8_t v; + + if (mbuf_get_left(mb) < 1) + return ENOENT; + + v = mbuf_read_u8(mb); + + fu->s = v>>7 & 0x1; + fu->e = v>>6 & 0x1; + fu->r = v>>5 & 0x1; + fu->type = v>>0 & 0x1f; + + return 0; +} + + +/* + * Find the NAL start sequence in a H.264 byte stream + * + * @note: copied from ffmpeg source + */ +const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *a = p + 4 - ((long)p & 3); + + for (end -= 3; p < a && p < end; p++ ) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + for (end -= 3; p < end; p += 4) { + uint32_t x = *(const uint32_t*)(void *)p; + if ( (x - 0x01010101) & (~x) & 0x80808080 ) { + if (p[1] == 0 ) { + if ( p[0] == 0 && p[2] == 1 ) + return p; + if ( p[2] == 0 && p[3] == 1 ) + return p+1; + } + if ( p[3] == 0 ) { + if ( p[2] == 0 && p[4] == 1 ) + return p+2; + if ( p[4] == 0 && p[5] == 1 ) + return p+3; + } + } + } + + for (end += 3; p < end; p++) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + return end + 3; +} + + +static int rtp_send_data(const uint8_t *hdr, size_t hdr_sz, + const uint8_t *buf, size_t sz, bool eof, + videnc_packet_h *pkth, void *arg) +{ + return pkth(eof, hdr, hdr_sz, buf, sz, arg); +} + + +int h264_nal_send(bool first, bool last, + bool marker, uint32_t ihdr, const uint8_t *buf, + size_t size, size_t maxsz, + videnc_packet_h *pkth, void *arg) +{ + uint8_t hdr = (uint8_t)ihdr; + int err = 0; + + if (first && last && size <= maxsz) { + err = rtp_send_data(&hdr, 1, buf, size, marker, + pkth, arg); + } + else { + uint8_t fu_hdr[2]; + const uint8_t type = hdr & 0x1f; + const uint8_t nri = hdr & 0x60; + const size_t sz = maxsz - 2; + + fu_hdr[0] = nri | H264_NAL_FU_A; + fu_hdr[1] = first ? (1<<7 | type) : type; + + while (size > sz) { + err |= rtp_send_data(fu_hdr, 2, buf, sz, false, + pkth, arg); + buf += sz; + size -= sz; + fu_hdr[1] &= ~(1 << 7); + } + + if (last) + fu_hdr[1] |= 1<<6; /* end bit */ + + err |= rtp_send_data(fu_hdr, 2, buf, size, marker && last, + pkth, arg); + } + + return err; +} + + +int h264_packetize(struct mbuf *mb, size_t pktsize, + videnc_packet_h *pkth, void *arg) +{ + const uint8_t *start = mb->buf; + const uint8_t *end = start + mb->end; + const uint8_t *r; + int err = 0; + + r = h264_find_startcode(mb->buf, end); + + while (r < end) { + const uint8_t *r1; + + /* skip zeros */ + while (!*(r++)) + ; + + r1 = h264_find_startcode(r, end); + + err |= h264_nal_send(true, true, (r1 >= end), r[0], + r+1, r1-r-1, pktsize, + pkth, arg); + r = r1; + } + + return err; +} diff --git a/modules/avcodec/h26x.h b/modules/avcodec/h26x.h new file mode 100644 index 0000000..7a21696 --- /dev/null +++ b/modules/avcodec/h26x.h @@ -0,0 +1,165 @@ +/** + * @file h26x.h Interface to H.26x video codecs + * + * Copyright (C) 2010 Creytiv.com + */ + + +/* + * H.263 + */ + + +enum h263_mode { + H263_MODE_A, + H263_MODE_B, + H263_MODE_C +}; + +enum { + H263_HDR_SIZE_MODEA = 4, + H263_HDR_SIZE_MODEB = 8, + H263_HDR_SIZE_MODEC = 12 +}; + +/** H.263 picture size format */ +enum h263_fmt { + H263_FMT_SQCIF = 1, /**< 128 x 96 */ + H263_FMT_QCIF = 2, /**< 176 x 144 */ + H263_FMT_CIF = 3, /**< 352 x 288 */ + H263_FMT_4CIF = 4, /**< 704 x 576 */ + H263_FMT_16CIF = 5, /**< 1408 x 1152 */ + H263_FMT_OTHER = 7, +}; + +/** + * H.263 Header defined in RFC 2190 + */ +struct h263_hdr { + + /* common */ + unsigned f:1; /**< 1 bit - Flag; 0=mode A, 1=mode B/C */ + unsigned p:1; /**< 1 bit - PB-frames, 0=mode B, 1=mode C */ + unsigned sbit:3; /**< 3 bits - Start Bit Position (SBIT) */ + unsigned ebit:3; /**< 3 bits - End Bit Position (EBIT) */ + unsigned src:3; /**< 3 bits - Source format */ + + /* mode A */ + unsigned i:1; /**< 1 bit - 0=intra-coded, 1=inter-coded */ + unsigned u:1; /**< 1 bit - Unrestricted Motion Vector */ + unsigned s:1; /**< 1 bit - Syntax-based Arithmetic Coding */ + unsigned a:1; /**< 1 bit - Advanced Prediction option */ + unsigned r:4; /**< 4 bits - Reserved (zero) */ + unsigned dbq:2; /**< 2 bits - DBQUANT */ + unsigned trb:3; /**< 3 bits - Temporal Reference for B-frame */ + unsigned tr:8; /**< 8 bits - Temporal Reference for P-frame */ + + /* mode B */ + unsigned quant:5; //=0 for GOB header + unsigned gobn:5; // gob number + unsigned mba:9; // address + unsigned hmv1:7; // horizontal motion vector + unsigned vmv1:7; // vertical motion vector + unsigned hmv2:7; + unsigned vmv2:7; + + +}; + +enum {I_FRAME=0, P_FRAME=1}; + +/** H.263 bit-stream header */ +struct h263_strm { + uint8_t psc[2]; /**< Picture Start Code (PSC) */ + + uint8_t temp_ref; /**< Temporal Reference */ + unsigned split_scr:1; /**< Split Screen Indicator */ + unsigned doc_camera:1; /**< Document Camera Indicator */ + unsigned pic_frz_rel:1; /**< Full Picture Freeze Release */ + unsigned src_fmt:3; /**< Source Format. 3=CIF */ + unsigned pic_type:1; /**< Picture Coding Type. 0=I, 1=P */ + unsigned umv:1; /**< Unrestricted Motion Vector mode */ + unsigned sac:1; /**< Syntax-based Arithmetic Coding */ + unsigned apm:1; /**< Advanced Prediction mode */ + unsigned pb:1; /**< PB-frames mode */ + unsigned pquant:5; /**< Quantizer Information */ + unsigned cpm:1; /**< Continuous Presence Multipoint */ + unsigned pei:1; /**< Extra Insertion Information */ + /* H.263 bit-stream ... */ +}; + +int h263_hdr_encode(const struct h263_hdr *hdr, struct mbuf *mb); +int h263_hdr_decode(struct h263_hdr *hdr, struct mbuf *mb); +enum h263_mode h263_hdr_mode(const struct h263_hdr *hdr); + +const uint8_t *h263_strm_find_psc(const uint8_t *p, uint32_t size); +int h263_strm_decode(struct h263_strm *s, struct mbuf *mb); +void h263_hdr_copy_strm(struct h263_hdr *hdr, const struct h263_strm *s); + + +/* + * H.264 + */ + + +/** NAL unit types (RFC 3984, Table 1) */ +enum { + H264_NAL_UNKNOWN = 0, + /* 1-23 NAL unit Single NAL unit packet per H.264 */ + H264_NAL_SLICE = 1, + H264_NAL_DPA = 2, + H264_NAL_DPB = 3, + H264_NAL_DPC = 4, + H264_NAL_IDR_SLICE = 5, + H264_NAL_SEI = 6, + H264_NAL_SPS = 7, + H264_NAL_PPS = 8, + H264_NAL_AUD = 9, + H264_NAL_END_SEQUENCE = 10, + H264_NAL_END_STREAM = 11, + H264_NAL_FILLER_DATA = 12, + H264_NAL_SPS_EXT = 13, + H264_NAL_AUX_SLICE = 19, + + H264_NAL_STAP_A = 24, /**< Single-time aggregation packet */ + H264_NAL_STAP_B = 25, /**< Single-time aggregation packet */ + H264_NAL_MTAP16 = 26, /**< Multi-time aggregation packet */ + H264_NAL_MTAP24 = 27, /**< Multi-time aggregation packet */ + H264_NAL_FU_A = 28, /**< Fragmentation unit */ + H264_NAL_FU_B = 29, /**< Fragmentation unit */ +}; + +/** + * H.264 Header defined in RFC 3984 + * + * <pre> + +---------------+ + |0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+ + |F|NRI| Type | + +---------------+ + * </pre> + */ +struct h264_hdr { + unsigned f:1; /**< 1 bit - Forbidden zero bit (must be 0) */ + unsigned nri:2; /**< 2 bits - nal_ref_idc */ + unsigned type:5; /**< 5 bits - nal_unit_type */ +}; + +int h264_hdr_encode(const struct h264_hdr *hdr, struct mbuf *mb); +int h264_hdr_decode(struct h264_hdr *hdr, struct mbuf *mb); + +/** Fragmentation Unit header */ +struct fu { + unsigned s:1; /**< Start bit */ + unsigned e:1; /**< End bit */ + unsigned r:1; /**< The Reserved bit MUST be equal to 0 */ + unsigned type:5; /**< The NAL unit payload type */ +}; + +int fu_hdr_encode(const struct fu *fu, struct mbuf *mb); +int fu_hdr_decode(struct fu *fu, struct mbuf *mb); + +const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end); + +int h264_decode_sprop_params(AVCodecContext *codec, struct pl *pl); diff --git a/modules/avcodec/module.mk b/modules/avcodec/module.mk new file mode 100644 index 0000000..b209a57 --- /dev/null +++ b/modules/avcodec/module.mk @@ -0,0 +1,20 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +USE_X264 := $(shell [ -f $(SYSROOT)/include/x264.h ] || \ + [ -f $(SYSROOT)/local/include/x264.h ] || \ + [ -f $(SYSROOT_ALT)/include/x264.h ] && echo "yes") + +MOD := avcodec +$(MOD)_SRCS += avcodec.c h263.c h264.c encode.c decode.c +$(MOD)_LFLAGS += -lavcodec -lavutil +CFLAGS += -I/usr/include/ffmpeg +ifneq ($(USE_X264),) +CFLAGS += -DUSE_X264 +$(MOD)_LFLAGS += -lx264 +endif + +include mk/mod.mk diff --git a/modules/avformat/avf.c b/modules/avformat/avf.c new file mode 100644 index 0000000..d1e1765 --- /dev/null +++ b/modules/avformat/avf.c @@ -0,0 +1,365 @@ +/** + * @file avf.c FFmpeg avformat video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <string.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#define FF_API_OLD_METADATA 0 +#include <libavformat/avformat.h> +#include <libavdevice/avdevice.h> +#include <libavcodec/avcodec.h> +#include <libswscale/swscale.h> + + +/* extra const-correctness added in 0.9.0 */ +/* note: macports has LIBSWSCALE_VERSION_MAJOR == 1 */ +/* #if LIBSWSCALE_VERSION_INT >= ((0<<16) + (9<<8) + (0)) */ +#if LIBSWSCALE_VERSION_MAJOR >= 2 || LIBSWSCALE_VERSION_MINOR >= 9 +#define SRCSLICE_CAST (const uint8_t **) +#else +#define SRCSLICE_CAST (uint8_t **) +#endif + + +/* backward compat */ +#if LIBAVCODEC_VERSION_MAJOR>52 || LIBAVCODEC_VERSION_INT>=((52<<16)+(64<<8)) +#define FFMPEG_HAVE_AVMEDIA_TYPES 1 +#endif +#ifndef FFMPEG_HAVE_AVMEDIA_TYPES +#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO +#endif + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + pthread_t thread; + bool run; + AVFormatContext *ic; + AVCodec *codec; + AVCodecContext *ctx; + struct SwsContext *sws; + struct vidsz app_sz; + struct vidsz sz; + vidsrc_frame_h *frameh; + void *arg; + int sindex; + int fps; +}; + + +static struct vidsrc *mod_avf; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->sws) + sws_freeContext(st->sws); + + if (st->ctx && st->ctx->codec) + avcodec_close(st->ctx); + + if (st->ic) { +#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (21<<8) + 0) + avformat_close_input(&st->ic); +#else + av_close_input_file(st->ic); +#endif + } + + mem_deref(st->vs); +} + + +static void handle_packet(struct vidsrc_st *st, AVPacket *pkt) +{ + AVPicture pict; + struct vidframe vf; + struct vidsz sz; + unsigned i; + + if (st->codec) { + AVFrame frame; + int got_pict, ret; + +#if LIBAVCODEC_VERSION_INT <= ((52<<16)+(23<<8)+0) + ret = avcodec_decode_video(st->ctx, &frame, &got_pict, + pkt->data, pkt->size); +#else + ret = avcodec_decode_video2(st->ctx, &frame, + &got_pict, pkt); +#endif + if (ret < 0 || !got_pict) + return; + + sz.w = st->ctx->width; + sz.h = st->ctx->height; + + /* check if size changed */ + if (!vidsz_cmp(&sz, &st->sz)) { + info("size changed: %d x %d ---> %d x %d\n", + st->sz.w, st->sz.h, sz.w, sz.h); + st->sz = sz; + + if (st->sws) { + sws_freeContext(st->sws); + st->sws = NULL; + } + } + + if (!st->sws) { + info("scaling: %d x %d ---> %d x %d\n", + st->sz.w, st->sz.h, + st->app_sz.w, st->app_sz.h); + + st->sws = sws_getContext(st->sz.w, st->sz.h, + st->ctx->pix_fmt, + st->app_sz.w, st->app_sz.h, + PIX_FMT_YUV420P, + SWS_BICUBIC, + NULL, NULL, NULL); + if (!st->sws) + return; + } + + ret = avpicture_alloc(&pict, PIX_FMT_YUV420P, + st->app_sz.w, st->app_sz.h); + if (ret < 0) + return; + + ret = sws_scale(st->sws, + SRCSLICE_CAST frame.data, frame.linesize, + 0, st->sz.h, pict.data, pict.linesize); + if (ret <= 0) + goto end; + } + else { + avpicture_fill(&pict, pkt->data, PIX_FMT_YUV420P, + st->sz.w, st->sz.h); + } + + vf.size = st->app_sz; + vf.fmt = VID_FMT_YUV420P; + for (i=0; i<4; i++) { + vf.data[i] = pict.data[i]; + vf.linesize[i] = pict.linesize[i]; + } + + st->frameh(&vf, st->arg); + + end: + if (st->codec) + avpicture_free(&pict); +} + + +static void *read_thread(void *data) +{ + struct vidsrc_st *st = data; + + while (st->run) { + AVPacket pkt; + + av_init_packet(&pkt); + + if (av_read_frame(st->ic, &pkt) < 0) { + sys_msleep(1000); + av_seek_frame(st->ic, -1, 0, 0); + continue; + } + + if (pkt.stream_index != st->sindex) + goto out; + + handle_packet(st, &pkt); + + /* simulate framerate */ + sys_msleep(1000/st->fps); + + out: + av_free_packet(&pkt); + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **mctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ +#if LIBAVFORMAT_VERSION_INT < ((52<<16) + (110<<8) + 0) + AVFormatParameters prms; +#endif + struct vidsrc_st *st; + bool found_stream = false; + uint32_t i; + int ret, err = 0; + + (void)mctx; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->app_sz = *size; + st->frameh = frameh; + st->arg = arg; + + if (prm) { + st->fps = prm->fps; + } + else { + st->fps = 25; + } + + /* + * avformat_open_input() was added in lavf 53.2.0 according to + * ffmpeg/doc/APIchanges + */ + +#if LIBAVFORMAT_VERSION_INT >= ((52<<16) + (110<<8) + 0) + (void)fmt; + ret = avformat_open_input(&st->ic, dev, NULL, NULL); +#else + + /* Params */ + memset(&prms, 0, sizeof(prms)); + + prms.time_base = (AVRational){1, st->fps}; + prms.channels = 1; + prms.width = size->w; + prms.height = size->h; + prms.pix_fmt = PIX_FMT_YUV420P; + prms.channel = 0; + + ret = av_open_input_file(&st->ic, dev, av_find_input_format(fmt), + 0, &prms); +#endif + + if (ret < 0) { + err = ENOENT; + goto out; + } + +#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (4<<8) + 0) + ret = avformat_find_stream_info(st->ic, NULL); +#else + ret = av_find_stream_info(st->ic); +#endif + + if (ret < 0) { + warning("avformat: %s: no stream info\n", dev); + err = ENOENT; + goto out; + } + +#if 0 + dump_format(st->ic, 0, dev, 0); +#endif + + for (i=0; i<st->ic->nb_streams; i++) { + const struct AVStream *strm = st->ic->streams[i]; + AVCodecContext *ctx = strm->codec; + + if (ctx->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + debug("avformat: stream %u: %u x %u codec=%s" + " time_base=%d/%d\n", + i, ctx->width, ctx->height, ctx->codec_name, + ctx->time_base.num, ctx->time_base.den); + + st->sz.w = ctx->width; + st->sz.h = ctx->height; + st->ctx = ctx; + st->sindex = strm->index; + + if (ctx->codec_id != CODEC_ID_NONE) { + + st->codec = avcodec_find_decoder(ctx->codec_id); + if (!st->codec) { + err = ENOENT; + goto out; + } + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0) + ret = avcodec_open2(ctx, st->codec, NULL); +#else + ret = avcodec_open(ctx, st->codec); +#endif + if (ret < 0) { + err = ENOENT; + goto out; + } + } + + found_stream = true; + break; + } + + if (!found_stream) { + err = ENOENT; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + /* register all codecs, demux and protocols */ + avcodec_register_all(); + avdevice_register_all(); + av_register_all(); + + return vidsrc_register(&mod_avf, "avformat", alloc, NULL); +} + + +static int module_close(void) +{ + mod_avf = mem_deref(mod_avf); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(avformat) = { + "avformat", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/avformat/module.mk b/modules/avformat/module.mk new file mode 100644 index 0000000..de37229 --- /dev/null +++ b/modules/avformat/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := avformat +$(MOD)_SRCS += avf.c +$(MOD)_LFLAGS += -lavdevice -lavformat -lavcodec -lavutil -lswscale + +include mk/mod.mk diff --git a/modules/bv32/bv32.c b/modules/bv32/bv32.c new file mode 100644 index 0000000..c19a8bc --- /dev/null +++ b/modules/bv32/bv32.c @@ -0,0 +1,180 @@ +/** + * @file bv32.c BroadVoice32 audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <bv32/bv32.h> +#include <bv32/bitpack.h> + + +/* + * BroadVoice32 Wideband Audio codec (RFC 4298) + * + * http://www.broadcom.com/support/broadvoice/downloads.php + * http://files.freeswitch.org/downloads/libs/libbv32-0.1.tar.gz + */ + + +enum { + NSAMP = 80, + CODED_OCTETS = 20 +}; + + +struct auenc_state { + struct BV32_Encoder_State cs; + struct BV32_Bit_Stream bsc; +}; + +struct audec_state { + struct BV32_Decoder_State ds; + struct BV32_Bit_Stream bsd; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + Reset_BV32_Coder(&st->cs); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + Reset_BV32_Decoder(&st->ds); +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + Reset_BV32_Coder(&st->cs); + + *aesp = st; + + return 0; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + Reset_BV32_Decoder(&st->ds); + + *adsp = st; + + return 0; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + size_t i, nframe; + + nframe = sampc / NSAMP; + + if (*len < nframe * CODED_OCTETS) + return ENOMEM; + + for (i=0; i<nframe; i++) { + BV32_Encode(&st->bsc, &st->cs, (short *)&sampv[i*NSAMP]); + BV32_BitPack((void *)&buf[i*CODED_OCTETS], &st->bsc); + } + + *len = CODED_OCTETS * nframe; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + size_t i, nframe; + + nframe = len / CODED_OCTETS; + + if (*sampc < NSAMP*nframe) + return ENOMEM; + + for (i=0; i<nframe; i++) { + BV32_BitUnPack((void *)&buf[i*CODED_OCTETS], &st->bsd); + BV32_Decode(&st->bsd, &st->ds, (short *)&sampv[i*NSAMP]); + } + + *sampc = NSAMP * nframe; + + return 0; +} + + +static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + BV32_PLC(&st->ds, sampv); + *sampc = NSAMP; + + return 0; +} + + +static struct aucodec bv32 = { + LE_INIT, 0, "BV32", 16000, 1, NULL, + encode_update, encode, + decode_update, decode, plc, + NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(&bv32); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&bv32); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(bv32) = { + "bv32", + "codec", + module_init, + module_close +}; diff --git a/modules/bv32/module.mk b/modules/bv32/module.mk new file mode 100644 index 0000000..2e842ff --- /dev/null +++ b/modules/bv32/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := bv32 +$(MOD)_SRCS += bv32.c +$(MOD)_LFLAGS += -lbv32 -lm + +include mk/mod.mk diff --git a/modules/cairo/cairo.c b/modules/cairo/cairo.c new file mode 100644 index 0000000..a4956ad --- /dev/null +++ b/modules/cairo/cairo.c @@ -0,0 +1,231 @@ +/** + * @file cairo.c Cairo module + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <pthread.h> +#include <math.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <cairo/cairo.h> + + +#if !defined (M_PI) +#define M_PI 3.14159265358979323846264338327 +#endif + + +/* + * Note: This module is very experimental! + * + * Use Cairo library to draw graphics into a frame buffer + */ + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + struct vidsrc_prm prm; + struct vidsz size; + cairo_surface_t *surface; + cairo_t *cr; + double step; + bool run; + pthread_t thread; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->cr) + cairo_destroy(st->cr); + if (st->surface) + cairo_surface_destroy(st->surface); + + mem_deref(st->vs); +} + + +static void draw_gradient(cairo_t *cr, double step, int width, int height) +{ + cairo_pattern_t *pat; + double r, g, b; + double x, y, tx, ty; + char buf[128]; + double fontsize = 20.0; + + r = 0.1 + fabs(sin(5 * step)); + g = 0.0; + b = 0.1 + fabs(sin(3 * step)); + + x = width * (sin(10 * step) + 1)/2; + y = height * (1 - fabs(sin(30 * step))); + + tx = width/2 * (sin(5 * step) + 1)/2; + ty = fontsize + (height - fontsize) * (1 - fabs(sin(20 * step))); + + + pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height); + cairo_pattern_add_color_stop_rgba (pat, 1, r, g, b, 1); + cairo_pattern_add_color_stop_rgba (pat, 0, 0, 0, 0, 1); + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source (cr, pat); + cairo_fill (cr); + cairo_pattern_destroy (pat); + + pat = cairo_pattern_create_radial (x-128, y-128, 25.6, + x+128, y+128, 128.0); + cairo_pattern_add_color_stop_rgba (pat, 0, 0, 1, 0, 1); + cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 0, 1); + cairo_set_source (cr, pat); + cairo_arc (cr, x, y, 76.8, 0, 2 * M_PI); + cairo_fill (cr); + cairo_pattern_destroy (pat); + + /* Draw text */ + cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size (cr, fontsize); + + re_snprintf(buf, sizeof(buf), "%H", fmt_gmtime, NULL); + + cairo_move_to (cr, tx, ty); + cairo_text_path (cr, buf); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill_preserve (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_line_width (cr, 0.1); + cairo_stroke (cr); +} + + +static void process(struct vidsrc_st *st) +{ + struct vidframe f; + + draw_gradient(st->cr, st->step, st->size.w, st->size.h); + st->step += 0.02 / st->prm.fps; + + vidframe_init_buf(&f, VID_FMT_RGB32, &st->size, + cairo_image_surface_get_data(st->surface)); + + st->frameh(&f, st->arg); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + uint64_t ts = 0; + + while (st->run) { + + uint64_t now; + + sys_msleep(2); + + now = tmr_jiffies(); + if (!ts) + ts = now; + + if (ts > now) + continue; + + process(st); + + ts += 1000/st->prm.fps; + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err = 0; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + if (!stp || !prm || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + st->prm = *prm; + st->size = *size; + + st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + size->w, size->h); + st->cr = cairo_create(st->surface); + + info("cairo: surface with format %d (%d x %d) stride=%d\n", + cairo_image_surface_get_format(st->surface), + cairo_image_surface_get_width(st->surface), + cairo_image_surface_get_height(st->surface), + cairo_image_surface_get_stride(st->surface)); + + st->step = rand_u16() / 1000.0; + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + return vidsrc_register(&vidsrc, "cairo", alloc, NULL); +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(cairo) = { + "cairo", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/cairo/module.mk b/modules/cairo/module.mk new file mode 100644 index 0000000..636d1a9 --- /dev/null +++ b/modules/cairo/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := cairo +$(MOD)_SRCS += cairo.c +$(MOD)_LFLAGS += -lcairo +CFLAGS += -I$(SYSROOT)/include/cairo + +include mk/mod.mk diff --git a/modules/celt/celt.c b/modules/celt/celt.c new file mode 100644 index 0000000..8c89678 --- /dev/null +++ b/modules/celt/celt.c @@ -0,0 +1,417 @@ +/** + * @file celt.c CELT (Code-Excited Lapped Transform) audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <celt/celt.h> +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "celt" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/** + * @defgroup celt celt + * + * CELT audio codec + * + * @deprecated Replaced by the @ref opus module + * + * NOTE: + * + * The CELT codec has been merged into the IETF Opus codec and is now obsolete + */ + + +#ifdef CELT_GET_FRAME_SIZE +#define CELT_OLD_API 1 +#endif + + +/** Celt constants */ +enum { + DEFAULT_FRAME_SIZE = 640, /**< Framesize in [samples] */ + DEFAULT_BITRATE = 64000, /**< 32-128 kbps */ + DEFAULT_PTIME = 20, /**< Packet time in [ms] */ + MAX_FRAMES = 16 /**< Maximum frames per packet */ +}; + + +struct aucodec_st { + struct aucodec *ac; /**< Inheritance - base class */ + CELTMode *mode; /**< Shared CELT mode */ + CELTEncoder *enc; /**< CELT Encoder state */ + CELTDecoder *dec; /**< CELT Decoder state */ + int32_t frame_size; /**< Frame size in [samples] */ + uint32_t bitrate; /**< Bit-rate in [bit/s] */ + uint32_t fsize; /**< PCM Frame size in bytes */ + uint32_t bytes_per_packet; /**< Encoded packet size in bytes */ + bool low_overhead; /**< Low-Overhead Mode */ + uint16_t bpfv[MAX_FRAMES]; /**< Bytes per Frame vector */ + uint16_t bpfn; /**< Number of 'Bytes per Frame' */ +}; + + +/* Configurable items: */ +static uint32_t celt_low_overhead = 0; /* can be 0 or 1 */ +static struct aucodec *celtv[2]; + + +static void celt_destructor(void *arg) +{ + struct aucodec_st *st = arg; + + if (st->enc) + celt_encoder_destroy(st->enc); + if (st->dec) + celt_decoder_destroy(st->dec); + + if (st->mode) + celt_mode_destroy(st->mode); + + mem_deref(st->ac); +} + + +static void decode_param(const struct pl *name, const struct pl *val, + void *arg) +{ + struct aucodec_st *st = arg; + int err; + + if (0 == pl_strcasecmp(name, "bitrate")) { + st->bitrate = pl_u32(val) * 1000; + } + else if (0 == pl_strcasecmp(name, "frame-size")) { + st->frame_size = pl_u32(val); + + if (st->frame_size & 0x1) { + DEBUG_WARNING("frame-size is NOT even: %u\n", + st->frame_size); + } + } + else if (0 == pl_strcasecmp(name, "low-overhead")) { + struct pl fs, bpfv; + uint32_t i; + + st->low_overhead = true; + + err = re_regex(val->p, val->l, "[0-9]+/[0-9,]+", &fs, &bpfv); + if (err) + return; + + st->frame_size = pl_u32(&fs); + + for (i=0; i<ARRAY_SIZE(st->bpfv) && bpfv.l > 0; i++) { + struct pl bpf, co; + + co.l = 0; + if (re_regex(bpfv.p, bpfv.l, "[0-9]+[,]*", &bpf, &co)) + break; + + pl_advance(&bpfv, bpf.l + co.l); + + st->bpfv[i] = pl_u32(&bpf); + } + st->bpfn = i; + } + else { + DEBUG_NOTICE("unknown param: %r = %r\n", name, val); + } +} + + +static int decode_params(struct aucodec_st *st, const char *fmtp) +{ + struct pl params; + + pl_set_str(¶ms, fmtp); + + fmt_param_apply(¶ms, decode_param, st); + + return 0; +} + + +static int alloc(struct aucodec_st **stp, struct aucodec *ac, + struct aucodec_prm *encp, struct aucodec_prm *decp, + const char *fmtp) +{ + struct aucodec_st *st; + const uint32_t srate = aucodec_srate(ac); + const uint8_t ch = aucodec_ch(ac); + int err = 0; + + (void)decp; + + st = mem_zalloc(sizeof(*st), celt_destructor); + if (!st) + return ENOMEM; + + st->ac = mem_ref(ac); + + st->bitrate = DEFAULT_BITRATE; + st->low_overhead = celt_low_overhead; + + if (encp && encp->ptime) { + st->frame_size = srate * ch * encp->ptime / 1000; + DEBUG_NOTICE("calc ptime=%u ---> frame_size=%u\n", + encp->ptime, st->frame_size); + } + else { + st->frame_size = DEFAULT_FRAME_SIZE; + } + + if (str_isset(fmtp)) + decode_params(st, fmtp); + + /* Common mode */ + st->mode = celt_mode_create(srate, st->frame_size, NULL); + if (!st->mode) { + DEBUG_WARNING("alloc: could not create CELT mode\n"); + err = EPROTO; + goto out; + } + +#ifdef CELT_GET_FRAME_SIZE + celt_mode_info(st->mode, CELT_GET_FRAME_SIZE, &st->frame_size); +#endif + + st->fsize = 2 * st->frame_size * ch; + st->bytes_per_packet = (st->bitrate * st->frame_size / srate + 4)/8; + + DEBUG_NOTICE("alloc: frame_size=%u bitrate=%ubit/s fsize=%u" + " bytes_per_packet=%u\n", + st->frame_size, st->bitrate, st->fsize, + st->bytes_per_packet); + + /* Encoder */ +#ifdef CELT_OLD_API + st->enc = celt_encoder_create(st->mode, ch, NULL); +#else + st->enc = celt_encoder_create(srate, ch, NULL); +#endif + if (!st->enc) { + DEBUG_WARNING("alloc: could not create CELT encoder\n"); + err = EPROTO; + goto out; + } + + /* Decoder */ +#ifdef CELT_OLD_API + st->dec = celt_decoder_create(st->mode, ch, NULL); +#else + st->dec = celt_decoder_create(srate, ch, NULL); +#endif + if (!st->dec) { + DEBUG_WARNING("alloc: could not create CELT decoder\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int encode_frame(struct aucodec_st *st, uint16_t *size, uint8_t *buf, + struct mbuf *src) +{ + int len; + + /* NOTE: PCM audio in signed 16-bit format (native endian) */ + len = celt_encode(st->enc, (short *)mbuf_buf(src), st->frame_size, + buf, st->bytes_per_packet); + if (len < 0) { + DEBUG_WARNING("celt_encode: returned %d\n", len); + return EINVAL; + } + + DEBUG_INFO("encode: %u -> %d\n", mbuf_get_left(src), len); + + *size = len; + + mbuf_advance(src, st->fsize); + + return 0; +} + + +static int encode(struct aucodec_st *st, struct mbuf *dst, struct mbuf *src) +{ + struct { + uint8_t buf[1024]; + uint16_t len; + } framev[MAX_FRAMES]; + uint32_t i; + size_t n; + int err = 0; + + n = src->end / st->fsize; + if (n > MAX_FRAMES) { + n = MAX_FRAMES; + DEBUG_WARNING("number of frames truncated to %u\n", n); + } + + DEBUG_INFO("enc: %u bytes into %u frames\n", src->end, n); + + if (n ==0) { + DEBUG_WARNING("enc: short frame (%u < %u)\n", + src->end, st->fsize); + return EINVAL; + } + + /* Encode all frames into temp buffer */ + for (i=0; i<n && !err; i++) { + framev[i].len = sizeof(framev[i].buf); + err = encode_frame(st, &framev[i].len, framev[i].buf, src); + } + + if (!st->low_overhead) { + /* Encode all length headers */ + for (i=0; i<n && !err; i++) { + uint16_t len = framev[i].len; + + while (len >= 0xff) { + err = mbuf_write_u8(dst, 0xff); + len -= 0xff; + } + err = mbuf_write_u8(dst, len); + } + } + + /* Encode all frame buffers */ + for (i=0; i<n && !err; i++) { + err = mbuf_write_mem(dst, framev[i].buf, framev[i].len); + } + + return err; +} + + +static int decode_frame(struct aucodec_st *st, struct mbuf *dst, + struct mbuf *src, uint16_t src_len) +{ + int ret, err; + + if (mbuf_get_left(src) < src_len) { + DEBUG_WARNING("dec: corrupt frame %u < %u\n", + mbuf_get_left(src), src_len); + return EPROTO; + } + + /* Make sure there is enough space in the buffer */ + if (mbuf_get_space(dst) < st->fsize) { + err = mbuf_resize(dst, dst->size + st->fsize); + if (err) + return err; + } + + ret = celt_decode(st->dec, mbuf_buf(src), src_len, + (short *)mbuf_buf(dst), st->frame_size); + if (CELT_OK != ret) { + DEBUG_WARNING("celt_decode: ret=%d\n", ret); + } + + DEBUG_INFO("decode: %u -> %u\n", src_len, st->fsize); + + if (src) + mbuf_advance(src, src_len); + + dst->end += st->fsize; + + return 0; +} + + +/* src=NULL means lost packet */ +static int decode(struct aucodec_st *st, struct mbuf *dst, struct mbuf *src) +{ + uint16_t lengthv[MAX_FRAMES]; + uint16_t total_length = 0; + uint32_t i, n; + int err = 0; + + DEBUG_INFO("decode %u bytes\n", mbuf_get_left(src)); + + if (st->low_overhead) { + /* No length bytes */ + for (i=0; i<st->bpfn && !err; i++) { + err = decode_frame(st, dst, src, st->bpfv[i]); + } + } + else { + bool done = false; + + /* Read the length bytes */ + for (i=0; i<ARRAY_SIZE(lengthv) && !done; i++) { + uint8_t byte; + + if (mbuf_get_left(src) < 1) + return EPROTO; + + /* Decode length */ + lengthv[i] = 0; + do { + byte = mbuf_read_u8(src); + lengthv[i] += byte; + } + while (byte == 0xff); + + total_length += lengthv[i]; + + if (total_length >= mbuf_get_left(src)) + done = true; + } + n = i; + DEBUG_INFO("decoded %d frames\n", n); + + for (i=0; i<n && !err; i++) { + err = decode_frame(st, dst, src, lengthv[i]); + } + } + + return err; +} + + +static int module_init(void) +{ + int err; + + err = aucodec_register(&celtv[0], 0, "CELT", 48000, 1, NULL, + alloc, encode, decode, NULL); + err |= aucodec_register(&celtv[1], 0, "CELT", 32000, 1, NULL, + alloc, encode, decode, NULL); + + return err; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(celtv); i++) + celtv[i] = mem_deref(celtv[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(celt) = { + "celt", + "codec", + module_init, + module_close +}; diff --git a/modules/celt/module.mk b/modules/celt/module.mk new file mode 100644 index 0000000..47e6ea0 --- /dev/null +++ b/modules/celt/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := celt +$(MOD)_SRCS += celt.c +$(MOD)_LFLAGS += `pkg-config --libs celt` + +include mk/mod.mk diff --git a/modules/cons/cons.c b/modules/cons/cons.c new file mode 100644 index 0000000..8cf5b10 --- /dev/null +++ b/modules/cons/cons.c @@ -0,0 +1,185 @@ +/** + * @file cons.c Socket-based command-line console + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +enum {CONS_PORT = 5555}; + +struct ui_st { + struct ui *ui; /* base class */ + struct udp_sock *us; + struct tcp_sock *ts; + struct tcp_conn *tc; + ui_input_h *h; + void *arg; +}; + + +static struct ui *cons; +static struct ui_st *cons_cur = NULL; /* allow only one instance */ + + +static int print_handler(const char *p, size_t size, void *arg) +{ + return mbuf_write_mem(arg, (uint8_t *)p, size); +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct ui_st *st = arg; + struct mbuf *mbr = mbuf_alloc(64); + struct re_printf pf; + + pf.vph = print_handler; + pf.arg = mbr; + + while (mbuf_get_left(mb)) + st->h(mbuf_read_u8(mb), &pf, st->arg); + + mbr->pos = 0; + (void)udp_send(st->us, src, mbr); + + mem_deref(mbr); +} + + +static void cons_destructor(void *arg) +{ + struct ui_st *st = arg; + + mem_deref(st->us); + mem_deref(st->tc); + mem_deref(st->ts); + + mem_deref(st->ui); + + cons_cur = NULL; +} + + +static int tcp_write_handler(const char *p, size_t size, void *arg) +{ + struct mbuf mb; + + mb.buf = (uint8_t *)p; + mb.pos = 0; + mb.end = mb.size = size; + + return tcp_send(arg, &mb); +} + + +static void tcp_recv_handler(struct mbuf *mb, void *arg) +{ + struct ui_st *st = arg; + struct re_printf pf; + + pf.vph = tcp_write_handler; + pf.arg = st->tc; + + while (mbuf_get_left(mb) > 0) { + + const char key = mbuf_read_u8(mb); + + st->h(key, &pf, st->arg); + } +} + + +static void tcp_close_handler(int err, void *arg) +{ + struct ui_st *st = arg; + + (void)err; + + st->tc = mem_deref(st->tc); +} + + +static void tcp_conn_handler(const struct sa *peer, void *arg) +{ + struct ui_st *st = arg; + + (void)peer; + + /* only one connection allowed */ + st->tc = mem_deref(st->tc); + (void)tcp_accept(&st->tc, st->ts, NULL, tcp_recv_handler, + tcp_close_handler, st); +} + + +static int cons_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *h, void *arg) +{ + struct sa local; + struct ui_st *st; + int err; + + if (!stp) + return EINVAL; + + if (cons_cur) { + *stp = mem_ref(cons_cur); + return 0; + } + + st = mem_zalloc(sizeof(*st), cons_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(cons); + st->h = h; + st->arg = arg; + + err = sa_set_str(&local, "0.0.0.0", prm->port ? prm->port : CONS_PORT); + if (err) + goto out; + err = udp_listen(&st->us, &local, udp_recv, st); + if (err) { + warning("cons: failed to listen on UDP port %d (%m)\n", + sa_port(&local), err); + goto out; + } + + err = tcp_listen(&st->ts, &local, tcp_conn_handler, st); + if (err) { + warning("cons: failed to listen on TCP port %d (%m)\n", + sa_port(&local), err); + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = cons_cur = st; + + return err; +} + + +static int cons_init(void) +{ + return ui_register(&cons, "cons", cons_alloc, NULL); +} + + +static int cons_close(void) +{ + cons = mem_deref(cons); + return 0; +} + + +const struct mod_export DECL_EXPORTS(cons) = { + "cons", + "ui", + cons_init, + cons_close +}; diff --git a/modules/cons/module.mk b/modules/cons/module.mk new file mode 100644 index 0000000..1857dee --- /dev/null +++ b/modules/cons/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := cons +$(MOD)_SRCS += cons.c + +include mk/mod.mk diff --git a/modules/contact/contact.c b/modules/contact/contact.c new file mode 100644 index 0000000..b896e24 --- /dev/null +++ b/modules/contact/contact.c @@ -0,0 +1,214 @@ +/** + * @file modules/contact/contact.c Contacts module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +/* + * Contact module + * + * - read contact entries from ~/.baresip/contacts + * - populate local database of contacts + */ + + +static const char *chat_peer; /**< Selected chat peer */ +static char cmd_desc[128] = "Send MESSAGE to peer"; + + +static int confline_handler(const struct pl *addr) +{ + return contact_add(NULL, addr); +} + + +static int cmd_contact(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct contact *cnt = NULL; + struct pl dname, user, pl; + struct le *le; + int err = 0; + + pl_set_str(&pl, carg->prm); + + dname.l = user.l = pl.l; + + err |= re_hprintf(pf, "\n"); + + for (le = list_head(contact_list()); le; le = le->next) { + + struct contact *c = le->data; + + dname.p = contact_addr(c)->dname.p; + user.p = contact_addr(c)->uri.user.p; + + /* if displayname is set, try to match the displayname + * otherwise we try to match the username only + */ + if (dname.p) { + + if (0 == pl_casecmp(&dname, &pl)) { + err |= re_hprintf(pf, "%s\n", contact_str(c)); + cnt = c; + } + } + else if (user.p) { + + if (0 == pl_casecmp(&user, &pl)) { + err |= re_hprintf(pf, "%s\n", contact_str(c)); + cnt = c; + } + } + } + + if (!cnt) + err |= re_hprintf(pf, "(no matches)\n"); + + if (carg->complete && cnt) { + + switch (carg->key) { + + case '/': + err = ua_connect(uag_current(), NULL, NULL, + contact_str(cnt), NULL, VIDMODE_ON); + if (err) { + warning("contact: ua_connect failed: %m\n", + err); + } + break; + + case '=': + chat_peer = contact_str(cnt); + (void)re_hprintf(pf, "Selected chat peer: %s\n", + chat_peer); + re_snprintf(cmd_desc, sizeof(cmd_desc), + "Send MESSAGE to %s", chat_peer); + break; + + default: + break; + } + } + + return err; +} + + +static int cmd_message(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err; + + (void)pf; + + err = message_send(uag_current(), chat_peer, carg->prm); + if (err) { + (void)re_hprintf(pf, "chat: ua_im_send() failed (%m)\n", err); + } + + return err; +} + + +static const struct cmd cmdv[] = { + {'/', CMD_IPRM, "Dial from contacts", cmd_contact }, + {'=', CMD_IPRM, "Select chat peer", cmd_contact }, + {'C', 0, "List contacts", contacts_print }, + {'-', CMD_PRM, cmd_desc, cmd_message }, +}; + + +static int write_template(const char *file) +{ + const char *user, *domain; + FILE *f = NULL; + + info("contact: creating contacts template %s\n", file); + + f = fopen(file, "w"); + if (!f) + return errno; + + user = sys_username(); + if (!user) + user = "user"; + domain = net_domain(); + if (!domain) + domain = "domain"; + + (void)re_fprintf(f, + "#\n" + "# SIP contacts\n" + "#\n" + "# Displayname <sip:user@domain>;addr-params\n" + "#\n" + "# addr-params:\n" + "# ;presence={none,p2p}\n" + "#\n" + "\n" + "\"Echo Server\" <sip:echo@creytiv.com>\n" + "\"%s\" <sip:%s@%s>;presence=p2p\n", + user, user, domain); + + if (f) + (void)fclose(f); + + return 0; +} + + +static int module_init(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/contacts", path) < 0) + return ENOMEM; + + if (!conf_fileexist(file)) { + + (void)fs_mkdir(path, 0700); + + err = write_template(file); + if (err) + return err; + } + + err = conf_parse(file, confline_handler); + if (err) + return err; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + info("Populated %u contacts\n", list_count(contact_list())); + + return err; +} + + +static int module_close(void) +{ + cmd_unregister(cmdv); + list_flush(contact_list()); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(contact) = { + "contact", + "application", + module_init, + module_close +}; diff --git a/modules/contact/module.mk b/modules/contact/module.mk new file mode 100644 index 0000000..e9361c8 --- /dev/null +++ b/modules/contact/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := contact +$(MOD)_SRCS += contact.c + +include mk/mod.mk diff --git a/modules/coreaudio/coreaudio.c b/modules/coreaudio/coreaudio.c new file mode 100644 index 0000000..bb6ea64 --- /dev/null +++ b/modules/coreaudio/coreaudio.c @@ -0,0 +1,128 @@ +/** + * @file coreaudio.c Apple Coreaudio sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioToolbox.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +int audio_fmt(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kAudioFormatLinearPCM; + case AUFMT_PCMA: return kAudioFormatALaw; + case AUFMT_PCMU: return kAudioFormatULaw; + default: + warning("coreaudio: unknown format %d\n", fmt); + return -1; + } +} + + +int bytesps(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return 2; + case AUFMT_PCMA: return 1; + case AUFMT_PCMU: return 1; + default: return 0; + } +} + + +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0 +static void interruptionListener(void *data, UInt32 inInterruptionState) +{ + (void)data; + + /* TODO: implement this properly */ + + if (inInterruptionState == kAudioSessionBeginInterruption) { + debug("coreaudio: player interrupt: Begin\n"); + } + else if (inInterruptionState == kAudioSessionEndInterruption) { + debug("coreaudio: player interrupt: End\n"); + } +} + + +int audio_session_enable(void) +{ + OSStatus res; + UInt32 category; + + res = AudioSessionInitialize(NULL, NULL, interruptionListener, 0); + if (res && res != 1768843636) + return ENODEV; + + category = kAudioSessionCategory_PlayAndRecord; + res = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(category), &category); + if (res) { + warning("coreaudio: Audio Category: %d\n", res); + return ENODEV; + } + + res = AudioSessionSetActive(true); + if (res) { + warning("coreaudio: AudioSessionSetActive: %d\n", res); + return ENODEV; + } + + return 0; +} + + +void audio_session_disable(void) +{ + AudioSessionSetActive(false); +} +#else +int audio_session_enable(void) +{ + return 0; +} + + +void audio_session_disable(void) +{ +} +#endif + + +static int module_init(void) +{ + int err; + + err = auplay_register(&auplay, "coreaudio", coreaudio_player_alloc); + err |= ausrc_register(&ausrc, "coreaudio", coreaudio_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(coreaudio) = { + "coreaudio", + "audio", + module_init, + module_close, +}; diff --git a/modules/coreaudio/coreaudio.h b/modules/coreaudio/coreaudio.h new file mode 100644 index 0000000..530e45e --- /dev/null +++ b/modules/coreaudio/coreaudio.h @@ -0,0 +1,20 @@ +/** + * @file coreaudio.h Apple Coreaudio sound driver -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +int audio_session_enable(void); +void audio_session_disable(void); + +int audio_fmt(enum aufmt fmt); +int bytesps(enum aufmt fmt); + +int coreaudio_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int coreaudio_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); diff --git a/modules/coreaudio/module.mk b/modules/coreaudio/module.mk new file mode 100644 index 0000000..35d51cf --- /dev/null +++ b/modules/coreaudio/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := coreaudio +$(MOD)_SRCS += coreaudio.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += -framework CoreAudio -framework AudioToolbox + +include mk/mod.mk diff --git a/modules/coreaudio/player.c b/modules/coreaudio/player.c new file mode 100644 index 0000000..68aca4e --- /dev/null +++ b/modules/coreaudio/player.c @@ -0,0 +1,167 @@ +/** + * @file coreaudio/player.c Apple Coreaudio sound driver - player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioQueue.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +/* This value can be tuned */ +#if TARGET_OS_IPHONE +#define BUFC 20 +#else +#define BUFC 6 +#endif + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + AudioQueueRef queue; + AudioQueueBufferRef buf[BUFC]; + pthread_mutex_t mutex; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + uint32_t i; + + pthread_mutex_lock(&st->mutex); + st->wh = NULL; + pthread_mutex_unlock(&st->mutex); + + audio_session_disable(); + + if (st->queue) { + AudioQueuePause(st->queue); + AudioQueueStop(st->queue, true); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) + if (st->buf[i]) + AudioQueueFreeBuffer(st->queue, st->buf[i]); + + AudioQueueDispose(st->queue, true); + } + + mem_deref(st->ap); + + pthread_mutex_destroy(&st->mutex); +} + + +static void play_handler(void *userData, AudioQueueRef outQ, + AudioQueueBufferRef outQB) +{ + struct auplay_st *st = userData; + auplay_write_h *wh; + void *arg; + + pthread_mutex_lock(&st->mutex); + wh = st->wh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!wh) + return; + + if (!wh(outQB->mAudioData, outQB->mAudioDataByteSize, arg)) { + /* Set the buffer to silence */ + memset(outQB->mAudioData, 0, outQB->mAudioDataByteSize); + } + + AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL); +} + + +int coreaudio_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + AudioStreamBasicDescription fmt; + struct auplay_st *st; + uint32_t sampc, bytc, i; + OSStatus status; + int err; + + (void)device; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audio_session_enable(); + if (err) + goto out; + + fmt.mSampleRate = (Float64)prm->srate; + fmt.mFormatID = audio_fmt(prm->fmt); + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * bytesps(prm->fmt); + fmt.mBytesPerPacket = prm->ch * bytesps(prm->fmt); + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 8*bytesps(prm->fmt); + + status = AudioQueueNewOutput(&fmt, play_handler, st, NULL, + kCFRunLoopCommonModes, 0, &st->queue); + if (status) { + warning("coreaudio: AudioQueueNewOutput error: %i\n", status); + err = ENODEV; + goto out; + } + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + bytc = sampc * bytesps(prm->fmt); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) { + + status = AudioQueueAllocateBuffer(st->queue, bytc, + &st->buf[i]); + if (status) { + err = ENOMEM; + goto out; + } + + st->buf[i]->mAudioDataByteSize = bytc; + + memset(st->buf[i]->mAudioData, 0, + st->buf[i]->mAudioDataByteSize); + + (void)AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL); + } + + status = AudioQueueStart(st->queue, NULL); + if (status) { + warning("coreaudio: AudioQueueStart error %i\n", status); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/coreaudio/recorder.c b/modules/coreaudio/recorder.c new file mode 100644 index 0000000..b1f91fc --- /dev/null +++ b/modules/coreaudio/recorder.c @@ -0,0 +1,199 @@ +/** + * @file coreaudio/recorder.c Apple Coreaudio sound driver - recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioQueue.h> +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +#define BUFC 3 + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + AudioQueueRef queue; + AudioQueueBufferRef buf[BUFC]; + pthread_mutex_t mutex; + struct mbuf *mb; + ausrc_read_h *rh; + void *arg; + unsigned int ptime; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + uint32_t i; + + pthread_mutex_lock(&st->mutex); + st->rh = NULL; + pthread_mutex_unlock(&st->mutex); + + audio_session_disable(); + + if (st->queue) { + AudioQueuePause(st->queue); + AudioQueueStop(st->queue, true); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) + if (st->buf[i]) + AudioQueueFreeBuffer(st->queue, st->buf[i]); + + AudioQueueDispose(st->queue, true); + } + + mem_deref(st->mb); + mem_deref(st->as); + + pthread_mutex_destroy(&st->mutex); +} + + +static void record_handler(void *userData, AudioQueueRef inQ, + AudioQueueBufferRef inQB, + const AudioTimeStamp *inStartTime, + UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) +{ + struct ausrc_st *st = userData; + struct mbuf *mb = st->mb; + unsigned int ptime; + ausrc_read_h *rh; + size_t sz, sp; + void *arg; + (void)inStartTime; + (void)inNumPackets; + (void)inPacketDesc; + + pthread_mutex_lock(&st->mutex); + ptime = st->ptime; + rh = st->rh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!rh) + return; + + sz = inQB->mAudioDataByteSize; + sp = mbuf_get_space(mb); + + if (sz >= sp) { + mbuf_write_mem(mb, inQB->mAudioData, sp); + rh(mb->buf, (uint32_t)mb->size, arg); + mb->pos = 0; + mbuf_write_mem(mb, (uint8_t *)inQB->mAudioData + sp, sz - sp); + } + else { + mbuf_write_mem(mb, inQB->mAudioData, sz); + } + + AudioQueueEnqueueBuffer(inQ, inQB, 0, NULL); + + /* Force a sleep here, coreaudio's timing is too fast */ +#if !TARGET_OS_IPHONE +#define ENCODE_TIME 1000 + usleep((ptime * 1000) - ENCODE_TIME); +#endif +} + + +int coreaudio_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + AudioStreamBasicDescription fmt; + struct ausrc_st *st; + uint32_t sampc, bytc, i; + OSStatus status; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->ptime = prm->ptime; + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + bytc = sampc * bytesps(prm->fmt); + + st->mb = mbuf_alloc(bytc); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audio_session_enable(); + if (err) + goto out; + + fmt.mSampleRate = (Float64)prm->srate; + fmt.mFormatID = audio_fmt(prm->fmt); + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * bytesps(prm->fmt); + fmt.mBytesPerPacket = prm->ch * bytesps(prm->fmt); + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 8*bytesps(prm->fmt); + + status = AudioQueueNewInput(&fmt, record_handler, st, NULL, + kCFRunLoopCommonModes, 0, &st->queue); + if (status) { + warning("coreaudio: AudioQueueNewInput error: %i\n", status); + err = ENODEV; + goto out; + } + + for (i=0; i<ARRAY_SIZE(st->buf); i++) { + + status = AudioQueueAllocateBuffer(st->queue, bytc, + &st->buf[i]); + if (status) { + err = ENOMEM; + goto out; + } + + AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL); + } + + status = AudioQueueStart(st->queue, NULL); + if (status) { + warning("coreaudio: AudioQueueStart error %i\n", status); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/directfb/directfb.c b/modules/directfb/directfb.c new file mode 100644 index 0000000..ad98e2b --- /dev/null +++ b/modules/directfb/directfb.c @@ -0,0 +1,191 @@ +/** + * @file directfb.c DirectFB video display module + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de> + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <directfb.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + IDirectFBWindow *window; /**< DirectFB Window */ + IDirectFBSurface *surface; /**< Surface for pixels */ + IDirectFBDisplayLayer *layer; /**< Display layer */ +}; + + +static IDirectFB *dfb; +static struct vidisp *vid; + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->surface) + st->surface->Release(st->surface); + if (st->window) + st->window->Release(st->window); + if (st->layer) + st->layer->Release(st->layer); + + mem_deref(st->vd); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + + /* Not used by DirectFB */ + (void) prm; + (void) dev; + (void) resizeh; + (void) arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &st->layer); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + void *pixels; + int pitch, i; + unsigned h; + uint8_t *p; + (void) title; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("directfb: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + if (st->surface) { + st->surface->Release(st->surface); + st->surface = NULL; + } + if (st->window) { + st->window->Release(st->window); + st->window = NULL; + } + } + + if (!st->window) { + DFBWindowDescription desc; + + desc.flags = DWDESC_WIDTH|DWDESC_HEIGHT|DWDESC_PIXELFORMAT; + desc.width = frame->size.w; + desc.height = frame->size.h; + desc.pixelformat = DSPF_I420; + + st->layer->CreateWindow(st->layer, &desc, &st->window); + + st->size = frame->size; + st->window->SetOpacity(st->window, 0xff); + st->window->GetSurface(st->window, &st->surface); + } + + st->surface->Lock(st->surface, DSLF_WRITE, &pixels, &pitch); + + p = pixels; + for (i=0; i<3; i++) { + + const uint8_t *s = frame->data[i]; + const unsigned stp = frame->linesize[0] / frame->linesize[i]; + const unsigned sz = frame->size.w / stp; + + for (h = 0; h < frame->size.h; h += stp) { + + memcpy(p, s, sz); + + s += frame->linesize[i]; + p += (pitch / stp); + } + } + + st->surface->Unlock(st->surface); + + /* Update the screen! */ + st->surface->Flip(st->surface, 0, 0); + + return 0; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st || !st->window) + return; + + st->window->SetOpacity(st->window, 0x00); +} + + +static int module_init(void) +{ + int err = 0; + DFBResult ret; + + ret = DirectFBInit(NULL, NULL); + if (ret) { + DirectFBError("DirectFBInit() failed", ret); + return (int) ret; + } + + ret = DirectFBCreate(&dfb); + if (ret) { + DirectFBError("DirectFBCreate() failed", ret); + return (int) ret; + } + + err = vidisp_register(&vid, "directfb", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + if (dfb) { + dfb->Release(dfb); + dfb = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(directfb) = { + "directfb", + "vidisp", + module_init, + module_close +}; diff --git a/modules/directfb/module.mk b/modules/directfb/module.mk new file mode 100644 index 0000000..bde6556 --- /dev/null +++ b/modules/directfb/module.mk @@ -0,0 +1,13 @@ +# +# module.mk - DirectFB video display module +# +# Copyright (C) 2010 Creytiv.com +# Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de>. +# + +MOD := directfb +$(MOD)_SRCS += directfb.c +$(MOD)_LFLAGS += `pkg-config --libs directfb ` +CFLAGS += `pkg-config --cflags directfb ` + +include mk/mod.mk diff --git a/modules/dshow/dshow.cpp b/modules/dshow/dshow.cpp new file mode 100644 index 0000000..e9e433f --- /dev/null +++ b/modules/dshow/dshow.cpp @@ -0,0 +1,511 @@ +/** + * @file dshow.cpp Windows DirectShow video-source + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2010 Dusan Stevanovic + */ + +#include <stdio.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <comutil.h> +#include <commctrl.h> +#include <dshow.h> +#include <qedit.h> + + +#define DEBUG_MODULE "dshow" +#define DEBUG_LEVEL 6 +#include <re_dbg.h> + + +const CLSID CLSID_SampleGrabber = { 0xc1f400a0, 0x3f08, 0x11d3, + { 0x9f, 0x0b, 0x00, 0x60, 0x08, 0x03, 0x9e, 0x37 } +}; + + +class Grabber; + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + ICaptureGraphBuilder2 *capture; + IBaseFilter *grabber_filter; + IBaseFilter *dev_filter; + ISampleGrabber *grabber; + IMoniker *dev_moniker; + IGraphBuilder *graph; + IMediaControl *mc; + + Grabber *grab; + + struct vidsz size; + vidsrc_frame_h *frameh; + void *arg; +}; + + +class Grabber : public ISampleGrabberCB { +public: + Grabber(struct vidsrc_st *st) : src(st) { } + + STDMETHOD(QueryInterface)(REFIID InterfaceIdentifier, + VOID** ppvObject) throw() + { + if (InterfaceIdentifier == __uuidof(ISampleGrabberCB)) { + *ppvObject = (ISampleGrabberCB**) this; + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHOD_(ULONG, AddRef)() throw() + { + return 2; + } + + STDMETHOD_(ULONG, Release)() throw() + { + return 1; + } + + STDMETHOD(BufferCB) (double sample_time, BYTE *buf, long buf_len) + { + struct vidframe vidframe; + + /* XXX: should be VID_FMT_BGR24 */ + vidframe_init_buf(&vidframe, VID_FMT_RGB32, &src->size, buf); + + if (src->frameh) + src->frameh(&vidframe, src->arg); + + return S_OK; + } + + STDMETHOD(SampleCB) (double sample_time, IMediaSample *samp) + { + return S_OK; + } + +private: + struct vidsrc_st *src; +}; + + +static struct vidsrc *vsrc; + + +static int get_device(struct vidsrc_st *st, const char *name) +{ + ICreateDevEnum *dev_enum; + IEnumMoniker *enum_mon; + IMoniker *mon; + ULONG fetched; + HRESULT res; + int id = 0; + bool found = false; + + if (!st) + return EINVAL; + + res = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, (void**)&dev_enum); + if (res != NOERROR) + return ENOENT; + + res = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + &enum_mon, 0); + if (res != NOERROR) + return ENOENT; + + enum_mon->Reset(); + while (enum_mon->Next(1, &mon, &fetched) == S_OK && !found) { + + IPropertyBag *bag; + VARIANT var; + char dev_name[256]; + int len = 0; + + res = mon->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&bag); + if (!SUCCEEDED(res)) + continue; + + var.vt = VT_BSTR; + res = bag->Read(L"FriendlyName", &var, NULL); + if (NOERROR != res) + continue; + + len = WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, + dev_name, sizeof(dev_name), + NULL, NULL); + + if (len > 0) { + found = !str_isset(name) || + !str_casecmp(dev_name, name); + + if (found) { + re_printf("dshow: got device '%s' id=%d\n", + name, id); + st->dev_moniker = mon; + } + } + + SysFreeString(var.bstrVal); + bag->Release(); + if (!found) { + mon->Release(); + ++id; + } + } + + return found ? 0 : ENOENT; +} + + +static int add_sample_grabber(struct vidsrc_st *st) +{ + AM_MEDIA_TYPE mt; + HRESULT hr; + + hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, (void**) &st->grabber_filter); + if (FAILED(hr)) + return ENOMEM; + + hr = st->graph->AddFilter(st->grabber_filter, L"Sample Grabber"); + if (FAILED(hr)) + return ENOMEM; + + hr = st->grabber_filter->QueryInterface(IID_ISampleGrabber, + (void**)&st->grabber); + if (FAILED(hr)) + return ENODEV; + + hr = st->grabber->SetCallback(st->grab, 1); + if (FAILED(hr)) + return ENOSYS; + + memset(&mt, 0, sizeof(mt)); + mt.majortype = MEDIATYPE_Video; + mt.subtype = MEDIASUBTYPE_RGB24; /* XXX: try YUV420P */ + hr = st->grabber->SetMediaType(&mt); + if (FAILED(hr)) + return ENODEV; + + st->grabber->SetOneShot(FALSE); + st->grabber->SetBufferSamples(FALSE); + + return 0; +} + + +static AM_MEDIA_TYPE *free_mt(AM_MEDIA_TYPE *mt) +{ + if (!mt) + return NULL; + + if (mt->cbFormat) { + CoTaskMemFree((PVOID)mt->pbFormat); + } + if (mt->pUnk != NULL) { + mt->pUnk->Release(); + mt->pUnk = NULL; + } + + CoTaskMemFree((PVOID)mt); + + return NULL; +} + + +static int config_pin(struct vidsrc_st *st, IPin *pin) +{ + AM_MEDIA_TYPE *mt; + AM_MEDIA_TYPE *best_mt = NULL; + IEnumMediaTypes *media_enum = NULL; + IAMStreamConfig *stream_conf = NULL; + VIDEOINFOHEADER *vih; + HRESULT hr; + int h = st->size.h; + int w = st->size.w; + int rh, rw; + int wh, rwrh; + int best_match = 0; + int err = 0; + + if (!pin || !st) + return EINVAL; + + hr = pin->EnumMediaTypes(&media_enum); + if (FAILED(hr)) + return ENODATA; + + while ((hr = media_enum->Next(1, &mt, NULL)) == S_OK) { + if (mt->formattype != FORMAT_VideoInfo) + continue; + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + wh = w * h; + rwrh = rw * rh; + if (wh == rwrh) { + best_mt = free_mt(best_mt); + break; + } + else { + int diff = abs(rwrh - wh); + + if (best_match != 0 && diff >= best_match) + mt = free_mt(mt); + else { + best_match = diff; + free_mt(best_mt); + best_mt = mt; + } + } + } + if (hr != S_OK) + mt = free_mt(mt); + + if (mt == NULL && best_mt == NULL) { + err = ENODATA; + goto out; + } + if (mt == NULL) + mt = best_mt; + + hr = pin->QueryInterface(IID_IAMStreamConfig, + (void **) &stream_conf); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + hr = stream_conf->SetFormat(mt); + mt = free_mt(mt); + if (FAILED(hr)) { + err = ERANGE; + goto out; + } + + hr = stream_conf->GetFormat(&mt); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + if (mt->formattype != FORMAT_VideoInfo) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *)mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + if (w != rw || h != rh) { + DEBUG_WARNING("config_pin: picture size missmatch: " + "wanted %d x %d, got %d x %d\n", + w, h, rw, rh); + } + st->size.w = rw; + st->size.h = rh; + + out: + if (media_enum) + media_enum->Release(); + if (stream_conf) + stream_conf->Release(); + free_mt(mt); + + return err; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = (struct vidsrc_st *)arg; + + if (st->mc) { + st->mc->Stop(); + st->mc->Release(); + } + + if (st->grabber) { + st->grabber->SetCallback(NULL, 1); + st->grabber->Release(); + } + if (st->grabber_filter) + st->grabber_filter->Release(); + if (st->dev_moniker) + st->dev_moniker->Release(); + if (st->dev_filter) + st->dev_filter->Release(); + if (st->capture) { + st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + NULL, NULL, NULL); + st->capture->Release(); + } + if (st->graph) + st->graph->Release(); + + delete st->grab; + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + IEnumPins *pin_enum = NULL; + IPin *pin = NULL; + HRESULT hr; + int err; + (void)ctx; + (void)errorh; + + if (!stp || !vs || !prm || !size) + return EINVAL; + + st = (struct vidsrc_st *) mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + err = get_device(st, dev); + if (err) + goto out; + + st->vs = (struct vidsrc *)mem_ref(vs); + + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + st->grab = new Grabber(st); + if (!st->grab) { + err = ENOMEM; + goto out; + } + + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, + IID_IGraphBuilder, (void **) &st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_IGraphBuilder failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL, + CLSCTX_INPROC, IID_ICaptureGraphBuilder2, + (void **) &st->capture); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_ICaptureGraphBuilder2: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->capture->SetFiltergraph(st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: SetFiltergraph failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_moniker->BindToObject(NULL, NULL, IID_IBaseFilter, + (void **) &st->dev_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: bind to base filter failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->graph->AddFilter(st->dev_filter, L"Video Capture"); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: VideoCapture failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_filter->EnumPins(&pin_enum); + if (pin_enum) { + pin_enum->Reset(); + hr = pin_enum->Next(1, &pin, NULL); + } + + add_sample_grabber(st); + err = config_pin(st, pin); + pin->Release(); + if (err) + goto out; + + hr = st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + st->dev_filter, + NULL, st->grabber_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: RenderStream failed\n"); + err = ENODEV; + goto out; + } + + hr = st->graph->QueryInterface(IID_IMediaControl, + (void **) &st->mc); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IMediaControl failed\n"); + err = ENODEV; + goto out; + } + + hr = st->mc->Run(); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: Run failed\n"); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + if (CoInitialize(NULL) != S_OK) + return ENODATA; + + return vidsrc_register(&vsrc, "dshow", alloc, NULL); +} + + +static int module_close(void) +{ + vsrc = (struct vidsrc *) mem_deref(vsrc); + CoUninitialize(); + + return 0; +} + + +extern "C" const struct mod_export DECL_EXPORTS(dshow) = { + "dshow", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/dshow/module.mk b/modules/dshow/module.mk new file mode 100644 index 0000000..f70623d --- /dev/null +++ b/modules/dshow/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := dshow +$(MOD)_SRCS += dshow.cpp +$(MOD)_LFLAGS += -lstrmiids -lole32 -loleaut32 -lstdc++ + +include mk/mod.mk diff --git a/modules/dtls_srtp/dtls.c b/modules/dtls_srtp/dtls.c new file mode 100644 index 0000000..f787cd2 --- /dev/null +++ b/modules/dtls_srtp/dtls.c @@ -0,0 +1,234 @@ +/** + * @file dtls.c DTLS functions + * + * Copyright (C) 2010 Creytiv.com + */ +#define OPENSSL_NO_KRB5 1 +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "dtls_srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* note: shadow struct in libre's tls module */ +struct tls { + SSL_CTX *ctx; + char *pass; /* password for private key */ + /* ... */ + EVP_PKEY *key; + X509 *x; +}; + + +static void destructor(void *data) +{ + struct tls *tls = data; + + if (tls->ctx) + SSL_CTX_free(tls->ctx); + + if (tls->x) + X509_free(tls->x); + if (tls->key) + EVP_PKEY_free(tls->key); + + mem_deref(tls->pass); +} + + +static int cert_generate(X509 *x, EVP_PKEY *privkey, const char *aor, + int expire_days) +{ + X509_EXTENSION *ext; + X509_NAME *subj; + int ret; + int err = ENOMEM; + + subj = X509_NAME_new(); + if (!subj) + goto out; + + X509_set_version(x, 2); + + ASN1_INTEGER_set(X509_get_serialNumber(x), rand_u32()); + + ret = X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, + (unsigned char *)aor, + (int)strlen(aor), -1, 0); + if (!ret) + goto out; + + if (!X509_set_issuer_name(x, subj) || + !X509_set_subject_name(x, subj)) + goto out; + + X509_gmtime_adj(X509_get_notBefore(x), 0); + X509_gmtime_adj(X509_get_notAfter(x), 60*60*24*expire_days); + + if (!X509_set_pubkey(x, privkey)) + goto out; + + ext = X509V3_EXT_conf_nid(NULL, NULL, + NID_basic_constraints, "CA:FALSE"); + if (1 != X509_add_ext(x, ext, -1)) + goto out; + X509_EXTENSION_free(ext); + + err = 0; + + out: + if (subj) + X509_NAME_free(subj); + + return err; +} + + +static int tls_gen_selfsigned_cert(struct tls *tls, const char *aor) +{ + RSA *rsa; + int err = ENOMEM; + + rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL); + if (!rsa) + goto out; + + tls->key = EVP_PKEY_new(); + if (!tls->key) + goto out; + if (!EVP_PKEY_set1_RSA(tls->key, rsa)) + goto out; + + tls->x = X509_new(); + if (!tls->x) + goto out; + + if (cert_generate(tls->x, tls->key, aor, 365)) + goto out; + + /* Sign the certificate */ + if (!X509_sign(tls->x, tls->key, EVP_sha1())) + goto out; + + err = 0; + + out: + if (rsa) + RSA_free(rsa); + + return err; +} + + +int dtls_alloc_selfsigned(struct tls **tlsp, const char *aor, + const char *srtp_profiles) +{ + struct tls *tls; + int r, err; + + if (!tlsp || !aor) + return EINVAL; + + tls = mem_zalloc(sizeof(*tls), destructor); + if (!tls) + return ENOMEM; + + SSL_library_init(); + + tls->ctx = SSL_CTX_new(DTLSv1_method()); + if (!tls->ctx) { + err = ENOMEM; + goto out; + } + +#if (OPENSSL_VERSION_NUMBER < 0x00905100L) + SSL_CTX_set_verify_depth(tls->ctx, 1); +#endif + + SSL_CTX_set_read_ahead(tls->ctx, 1); + + /* Generate self-signed certificate */ + err = tls_gen_selfsigned_cert(tls, aor); + if (err) { + DEBUG_WARNING("failed to generate certificate (%s): %m\n", + aor, err); + goto out; + } + + r = SSL_CTX_use_certificate(tls->ctx, tls->x); + if (r != 1) { + err = EINVAL; + goto out; + } + + r = SSL_CTX_use_PrivateKey(tls->ctx, tls->key); + if (r != 1) { + err = EINVAL; + goto out; + } + + if (0 != SSL_CTX_set_tlsext_use_srtp(tls->ctx, srtp_profiles)) { + DEBUG_WARNING("could not enable SRTP for profiles '%s'\n", + srtp_profiles); + err = ENOSYS; + goto out; + } + + err = 0; + out: + if (err) + mem_deref(tls); + else + *tlsp = tls; + + return err; +} + + +int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls) +{ + uint8_t md[64]; + unsigned int i, len; + int err = 0; + + if (!pf || !tls) + return EINVAL; + + len = sizeof(md); + if (1 != X509_digest(tls->x, EVP_sha1(), md, &len)) + return ENOENT; + + for (i=0; i<len; i++) { + err |= re_hprintf(pf, "%s%02x", i==0?"":":", md[i]); + } + + return err; +} + + +int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls) +{ + uint8_t md[64]; + unsigned int i, len; + int err = 0; + + if (!pf || !tls) + return EINVAL; + + len = sizeof(md); + if (1 != X509_digest(tls->x, EVP_sha256(), md, &len)) + return ENOENT; + + for (i=0; i<len; i++) { + err |= re_hprintf(pf, "%s%02x", i==0?"":":", md[i]); + } + + return err; +} diff --git a/modules/dtls_srtp/dtls_srtp.c b/modules/dtls_srtp/dtls_srtp.c new file mode 100644 index 0000000..b25c143 --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.c @@ -0,0 +1,403 @@ +/** + * @file dtls_srtp.c DTLS-SRTP media encryption + * + * Copyright (C) 2010 Creytiv.com + */ + +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include <string.h> +#include "dtls_srtp.h" + + +/* + * STACK Diagram: + * + * application + * | + * | + * [DTLS] [SRTP] + * \ / + * \ / + * \ / + * \/ + * ( TURN/ICE ) + * | + * | + * socket + * + */ + +struct menc_sess { + struct sdp_session *sdp; + bool offerer; + menc_error_h *errorh; + void *arg; +}; + +/* media */ +struct dtls_srtp { + struct sock sockv[2]; + const struct menc_sess *sess; + struct sdp_media *sdpm; + struct tmr tmr; + bool started; + bool active; + bool mux; +}; + +static struct tls *tls; +static const char* srtp_profiles = + "SRTP_AES128_CM_SHA1_80:" + "SRTP_AES128_CM_SHA1_32"; + + +static void sess_destructor(void *arg) +{ + struct menc_sess *sess = arg; + + mem_deref(sess->sdp); +} + + +static void destructor(void *arg) +{ + struct dtls_srtp *st = arg; + size_t i; + + tmr_cancel(&st->tmr); + + for (i=0; i<2; i++) { + struct sock *s = &st->sockv[i]; + + mem_deref(s->uh_srtp); + mem_deref(s->dtls); + mem_deref(s->app_sock); /* must be freed last */ + mem_deref(s->tx); + mem_deref(s->rx); + } + + mem_deref(st->sdpm); +} + + +static bool verify_fingerprint(const struct sdp_session *sess, + const struct sdp_media *media, + struct dtls_flow *tc) +{ + struct pl hash; + char hashstr[32]; + uint8_t md_sdp[64]; + size_t sz_sdp = sizeof(md_sdp); + struct tls_fingerprint tls_fp; + + if (sdp_fingerprint_decode(sdp_rattr(sess, media, "fingerprint"), + &hash, md_sdp, &sz_sdp)) + return false; + + pl_strcpy(&hash, hashstr, sizeof(hashstr)); + + if (dtls_get_remote_fingerprint(tc, hashstr, &tls_fp)) { + warning("dtls_srtp: could not get DTLS fingerprint\n"); + return false; + } + + if (sz_sdp != tls_fp.len || 0 != memcmp(md_sdp, tls_fp.md, sz_sdp)) { + warning("dtls_srtp: %s fingerprint mismatch\n", hashstr); + info("DTLS: %w\n", tls_fp.md, (size_t)tls_fp.len); + info("SDP: %w\n", md_sdp, sz_sdp); + return false; + } + + info("dtls_srtp: verified %s fingerprint OK\n", hashstr); + + return true; +} + + +static void dtls_established_handler(int err, struct dtls_flow *flow, + const char *profile, + const struct key *client_key, + const struct key *server_key, + void *arg) +{ + struct sock *sock = arg; + const struct dtls_srtp *ds = sock->ds; + + if (!verify_fingerprint(ds->sess->sdp, ds->sdpm, flow)) { + warning("dtls_srtp: could not verify remote fingerprint\n"); + if (ds->sess->errorh) + ds->sess->errorh(EPIPE, ds->sess->arg); + return; + } + + sock->negotiated = true; + + info("dtls_srtp: ---> DTLS-SRTP complete (%s/%s) Profile=%s\n", + sdp_media_name(ds->sdpm), + sock->is_rtp ? "RTP" : "RTCP", profile); + + err |= srtp_stream_add(&sock->tx, profile, + ds->active ? client_key : server_key, + true); + + err |= srtp_stream_add(&sock->rx, profile, + ds->active ? server_key : client_key, + false); + + err |= srtp_install(sock); + if (err) { + warning("dtls_srtp: srtp_install: %m\n", err); + } +} + + +static int session_alloc(struct menc_sess **sessp, + struct sdp_session *sdp, bool offerer, + menc_error_h *errorh, void *arg) +{ + struct menc_sess *sess; + int err; + + if (!sessp || !sdp) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), sess_destructor); + if (!sess) + return ENOMEM; + + sess->sdp = mem_ref(sdp); + sess->offerer = offerer; + sess->errorh = errorh; + sess->arg = arg; + + /* RFC 4145 */ + err = sdp_session_set_lattr(sdp, true, "setup", + offerer ? "actpass" : "active"); + if (err) + goto out; + + /* RFC 4572 */ + err = sdp_session_set_lattr(sdp, true, "fingerprint", "SHA-1 %H", + dtls_print_sha1_fingerprint, tls); + if (err) + goto out; + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_start_sock(struct sock *sock, struct sdp_media *sdpm) +{ + struct sa raddr; + int err = 0; + + if (!sock->app_sock || sock->negotiated || sock->dtls) + return 0; + + if (sock->is_rtp) + raddr = *sdp_media_raddr(sdpm); + else + sdp_media_raddr_rtcp(sdpm, &raddr); + + if (sa_isset(&raddr, SA_ALL)) { + + err = dtls_flow_alloc(&sock->dtls, tls, sock->app_sock, + dtls_established_handler, sock); + if (err) + return err; + + err = dtls_flow_start(sock->dtls, &raddr, sock->ds->active); + } + + return err; +} + + +static int media_start(struct dtls_srtp *st, struct sdp_media *sdpm) +{ + int err = 0; + + if (st->started) + return 0; + + debug("dtls_srtp: media_start: '%s' mux=%d, active=%d\n", + sdp_media_name(sdpm), st->mux, st->active); + + if (!sdp_media_has_media(sdpm)) + return 0; + + err = media_start_sock(&st->sockv[0], sdpm); + + if (!st->mux) + err |= media_start_sock(&st->sockv[1], sdpm); + + if (err) + return err; + + st->started = true; + + return 0; +} + + +static void timeout(void *arg) +{ + struct dtls_srtp *st = arg; + + media_start(st, st->sdpm); +} + + +static int media_alloc(struct menc_media **mp, struct menc_sess *sess, + struct rtp_sock *rtp, int proto, + void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct dtls_srtp *st; + const char *setup, *fingerprint; + int err = 0; + unsigned i; + (void)rtp; + + if (!mp || !sess || proto != IPPROTO_UDP) + return EINVAL; + + st = (struct dtls_srtp *)*mp; + if (st) + goto setup; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->sess = sess; + st->sdpm = mem_ref(sdpm); + st->sockv[0].app_sock = mem_ref(rtpsock); + st->sockv[1].app_sock = mem_ref(rtcpsock); + + for (i=0; i<2; i++) + st->sockv[i].ds = st; + + st->sockv[0].is_rtp = true; + st->sockv[1].is_rtp = false; + + if (err) { + mem_deref(st); + return err; + } + else + *mp = (struct menc_media *)st; + + setup: + st->mux = (rtpsock == rtcpsock); + + setup = sdp_rattr(st->sess->sdp, st->sdpm, "setup"); + if (setup) { + st->active = !(0 == str_casecmp(setup, "active")); + + /* note: we need to wait for ICE to settle ... */ + tmr_start(&st->tmr, 100, timeout, st); + } + + /* SDP offer/answer on fingerprint attribute */ + fingerprint = sdp_rattr(st->sess->sdp, st->sdpm, "fingerprint"); + if (fingerprint) { + + struct pl hash; + + err = sdp_fingerprint_decode(fingerprint, &hash, NULL, NULL); + if (err) + return err; + + if (0 == pl_strcasecmp(&hash, "SHA-1")) { + err = sdp_media_set_lattr(st->sdpm, true, + "fingerprint", "SHA-1 %H", + dtls_print_sha1_fingerprint, + tls); + } + else if (0 == pl_strcasecmp(&hash, "SHA-256")) { + err = sdp_media_set_lattr(st->sdpm, true, + "fingerprint", "SHA-256 %H", + dtls_print_sha256_fingerprint, + tls); + } + else { + info("dtls_srtp: unsupported fingerprint hash `%r'\n", + &hash); + return EPROTO; + } + } + + return err; +} + + +static struct menc dtls_srtp = { + LE_INIT, "dtls_srtp", "UDP/TLS/RTP/SAVP", session_alloc, media_alloc +}; + +static struct menc dtls_srtpf = { + LE_INIT, "dtls_srtpf", "UDP/TLS/RTP/SAVPF", session_alloc, media_alloc +}; + +static struct menc dtls_srtp2 = { + /* note: temp for Webrtc interop */ + LE_INIT, "srtp-mandf", "RTP/SAVPF", session_alloc, media_alloc +}; + + +static int module_init(void) +{ + err_status_t ret; + int err; + + crypto_kernel_shutdown(); + ret = srtp_init(); + if (err_status_ok != ret) { + warning("dtls_srtp: srtp_init() failed: ret=%d\n", ret); + return ENOSYS; + } + + err = dtls_alloc_selfsigned(&tls, "dtls@baresip", srtp_profiles); + if (err) + return err; + + menc_register(&dtls_srtpf); + menc_register(&dtls_srtp); + menc_register(&dtls_srtp2); + + debug("DTLS-SRTP ready with profiles %s\n", srtp_profiles); + + return 0; +} + + +static int module_close(void) +{ + menc_unregister(&dtls_srtp); + menc_unregister(&dtls_srtpf); + menc_unregister(&dtls_srtp2); + tls = mem_deref(tls); + crypto_kernel_shutdown(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(dtls_srtp) = { + "dtls_srtp", + "menc", + module_init, + module_close +}; diff --git a/modules/dtls_srtp/dtls_srtp.h b/modules/dtls_srtp/dtls_srtp.h new file mode 100644 index 0000000..6f85bf3 --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.h @@ -0,0 +1,59 @@ +/** + * @file dtls_srtp.h DTLS-SRTP Internal api + * + * Copyright (C) 2010 Creytiv.com + */ + + +enum { + LAYER_SRTP = 20, + LAYER_DTLS = 20, /* must be above zero */ +}; + +struct sock { + const struct dtls_srtp *ds; + struct dtls_flow *dtls; + struct srtp_stream *tx; + struct srtp_stream *rx; + struct udp_helper *uh_srtp; + void *app_sock; + bool negotiated; + bool is_rtp; +}; + +struct key { + uint8_t key[256]; + size_t key_len; + uint8_t salt[256]; + size_t salt_len; +}; + + +/* dtls.c */ +int dtls_alloc_selfsigned(struct tls **tlsp, const char *aor, + const char *srtp_profile); +int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls); +int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls); + + +/* srtp.c */ +int srtp_stream_add(struct srtp_stream **sp, const char *profile, + const struct key *key, bool tx); +int srtp_install(struct sock *sock); + + +/* tls_udp.c */ +struct dtls_flow; + +typedef void (dtls_estab_h)(int err, struct dtls_flow *tc, + const char *profile, + const struct key *client_key, + const struct key *server_key, + void *arg); + +int dtls_flow_alloc(struct dtls_flow **flowp, struct tls *tls, + struct udp_sock *us, dtls_estab_h *estabh, void *arg); +int dtls_flow_start(struct dtls_flow *flow, const struct sa *peer, + bool active); +int dtls_get_remote_fingerprint(const struct dtls_flow *flow, const char *type, + struct tls_fingerprint *fp); diff --git a/modules/dtls_srtp/module.mk b/modules/dtls_srtp/module.mk new file mode 100644 index 0000000..87dc952 --- /dev/null +++ b/modules/dtls_srtp/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := dtls_srtp +$(MOD)_SRCS += dtls_srtp.c dtls.c srtp.c tls_udp.c +$(MOD)_LFLAGS += -lsrtp + +include mk/mod.mk diff --git a/modules/dtls_srtp/srtp.c b/modules/dtls_srtp/srtp.c new file mode 100644 index 0000000..7f33ccc --- /dev/null +++ b/modules/dtls_srtp/srtp.c @@ -0,0 +1,232 @@ +/** + * @file dtls_srtp/srtp.c Secure RTP + * + * Copyright (C) 2010 Creytiv.com + */ + +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "dtls_srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct srtp_stream { + srtp_policy_t policy; + srtp_t srtp; + uint8_t key[SRTP_MAX_KEY_LEN]; +}; + + +/* + * See RFC 5764 figure 3: + * + * +----------------+ + * | 127 < B < 192 -+--> forward to RTP + * | | + * packet --> | 19 < B < 64 -+--> forward to DTLS + * | | + * | B < 2 -+--> forward to STUN + * +----------------+ + * + */ +static inline bool is_rtp_or_rtcp(const struct mbuf *mb) +{ + uint8_t b; + + if (mbuf_get_left(mb) < 1) + return false; + + b = mbuf_buf(mb)[0]; + + return 127 < b && b < 192; +} + + +static inline bool is_rtcp_packet(const struct mbuf *mb) +{ + uint8_t pt; + + if (mbuf_get_left(mb) < 2) + return false; + + pt = mbuf_buf(mb)[1] & 0x7f; + + return 64 <= pt && pt <= 95; +} + + +static int errstatus_print(struct re_printf *pf, err_status_t e) +{ + const char *s; + + switch (e) { + + case err_status_ok: s = "ok"; break; + case err_status_fail: s = "fail"; break; + case err_status_auth_fail: s = "auth_fail"; break; + case err_status_cipher_fail: s = "cipher_fail"; break; + case err_status_replay_fail: s = "replay_fail"; break; + + default: + return re_hprintf(pf, "err=%d", e); + } + + return re_hprintf(pf, "%s", s); +} + + +static void destructor(void *arg) +{ + struct srtp_stream *s = arg; + + if (s->srtp) + srtp_dealloc(s->srtp); +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct sock *sock = arg; + err_status_t e; + int len; + (void)dst; + + if (!is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) { + *err = mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN); + if (*err) + return true; + } + + if (is_rtcp_packet(mb)) { + e = srtp_protect_rtcp(sock->tx->srtp, mbuf_buf(mb), &len); + } + else { + e = srtp_protect(sock->tx->srtp, mbuf_buf(mb), &len); + } + + if (err_status_ok != e) { + DEBUG_WARNING("send: failed to protect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + *err = EPROTO; + return false; + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct sock *sock = arg; + err_status_t e; + int len; + (void)src; + + if (!is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (is_rtcp_packet(mb)) { + e = srtp_unprotect_rtcp(sock->rx->srtp, mbuf_buf(mb), &len); + } + else { + e = srtp_unprotect(sock->rx->srtp, mbuf_buf(mb), &len); + } + + if (e != err_status_ok) { + DEBUG_WARNING("recv: failed to unprotect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + return true; /* error - drop packet */ + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +int srtp_stream_add(struct srtp_stream **sp, const char *profile, + const struct key *key, bool tx) +{ + struct srtp_stream *s; + err_status_t e; + int err = 0; + + if (!sp || !key || key->key_len > SRTP_MAX_KEY_LEN) + return EINVAL; + + s = mem_zalloc(sizeof(*s), destructor); + if (!s) + return ENOMEM; + + memcpy(s->key, key->key, key->key_len); + append_salt_to_key(s->key, (unsigned int)key->key_len, + (unsigned char *)key->salt, + (unsigned int)key->salt_len); + + /* note: policy and key must be on the heap */ + + if (0 == str_casecmp(profile, "SRTP_AES128_CM_SHA1_80")) { + crypto_policy_set_aes_cm_128_hmac_sha1_80(&s->policy.rtp); + crypto_policy_set_aes_cm_128_hmac_sha1_80(&s->policy.rtcp); + } + else if (0 == str_casecmp(profile, "SRTP_AES128_CM_SHA1_32")) { + crypto_policy_set_aes_cm_128_hmac_sha1_32(&s->policy.rtp); + crypto_policy_set_aes_cm_128_hmac_sha1_32(&s->policy.rtcp); + } + else { + DEBUG_WARNING("unsupported profile: %s\n", profile); + err = ENOSYS; + goto out; + } + + s->policy.ssrc.type = tx ? ssrc_any_outbound : ssrc_any_inbound; + s->policy.key = s->key; + s->policy.next = NULL; + + e = srtp_create(&s->srtp, &s->policy); + if (err_status_ok != e) { + s->srtp = NULL; + DEBUG_WARNING("srtp_create() failed. e=%d\n", e); + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(s); + else + *sp = s; + + return err; +} + + +int srtp_install(struct sock *sock) +{ + return udp_register_helper(&sock->uh_srtp, sock->app_sock, + LAYER_SRTP, + send_handler, + recv_handler, + sock); +} diff --git a/modules/dtls_srtp/tls_udp.c b/modules/dtls_srtp/tls_udp.c new file mode 100644 index 0000000..ada5310 --- /dev/null +++ b/modules/dtls_srtp/tls_udp.c @@ -0,0 +1,391 @@ +/** + * @file dtls_srtp/tls_udp.c DTLS socket for DTLS-SRTP + * + * Copyright (C) 2010 Creytiv.com + */ + +#define OPENSSL_NO_KRB5 1 +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <re.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "tls_udp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* note: shadow struct in dtls.c */ +struct tls { + SSL_CTX *ctx; +}; + +struct dtls_flow { + struct udp_helper *uh; + struct udp_sock *us; + struct tls *tls; + struct tmr tmr; + struct sa peer; + SSL *ssl; + BIO *sbio_out; + BIO *sbio_in; + bool up; + dtls_estab_h *estabh; + void *arg; +}; + + +static void check_timer(struct dtls_flow *flow); + + +static int bio_create(BIO *b) +{ + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + + return 1; +} + + +static int bio_destroy(BIO *b) +{ + if (!b) + return 0; + + b->ptr = NULL; + b->init = 0; + b->flags = 0; + + return 1; +} + + +static int bio_write(BIO *b, const char *buf, int len) +{ + struct dtls_flow *tc = b->ptr; + struct mbuf *mb; + enum {SPACE = 4}; /* sizeof TURN channel header */ + int err; + + mb = mbuf_alloc(SPACE + len); + if (!mb) + return -1; + + (void)mbuf_fill(mb, 0x00, SPACE); + (void)mbuf_write_mem(mb, (void *)buf, len); + + mb->pos = SPACE; + + err = udp_send_helper(tc->us, &tc->peer, mb, tc->uh); + if (err) { + DEBUG_WARNING("udp_send_helper: %m\n", err); + } + + mem_deref(mb); + + return err ? -1 : len; +} + + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + (void)b; + (void)num; + (void)ptr; + + if (cmd == BIO_CTRL_FLUSH) { + /* The OpenSSL library needs this */ + return 1; + } + + return 0; +} + + +static struct bio_method_st bio_udp_send = { + BIO_TYPE_SOURCE_SINK, + "udp_send", + bio_write, + 0, + 0, + 0, + bio_ctrl, + bio_create, + bio_destroy, + 0 +}; + + +static int verify_callback(int ok, X509_STORE_CTX *ctx) +{ + (void)ok; + (void)ctx; + return 1; /* We trust the certificate from peer */ +} + + +#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined(DTLS_CTRL_GET_TIMEOUT) +static void timeout(void *arg) +{ + struct dtls_flow *tc = arg; + + DTLSv1_handle_timeout(tc->ssl); + + check_timer(tc); +} +#endif + + +static void check_timer(struct dtls_flow *tc) +{ +#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined (DTLS_CTRL_GET_TIMEOUT) + struct timeval tv = {0, 0}; + long x; + + x = DTLSv1_get_timeout(tc->ssl, &tv); + + if (x) { + uint64_t delay = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + tmr_start(&tc->tmr, delay, timeout, tc); + } + +#else + (void)tc; +#endif +} + + +static int get_srtp_key_info(const struct dtls_flow *tc, char *name, size_t sz, + struct key *client_key, struct key *server_key) +{ + SRTP_PROTECTION_PROFILE *sel; + const char *keymatexportlabel = "EXTRACTOR-dtls_srtp"; + uint8_t exportedkeymat[1024], *p; + int keymatexportlen; + size_t kl = 128, sl = 112; + + sel = SSL_get_selected_srtp_profile(tc->ssl); + if (!sel) + return ENOENT; + + str_ncpy(name, sel->name, sz); + + kl /= 8; + sl /= 8; + + keymatexportlen = (int)(kl + sl)*2; + if (keymatexportlen != 60) { + DEBUG_WARNING("expected 60 bits, but keying material is %d\n", + keymatexportlen); + return EINVAL; + } + + if (!SSL_export_keying_material(tc->ssl, exportedkeymat, + keymatexportlen, + keymatexportlabel, + strlen(keymatexportlabel), + NULL, 0, 0)) { + return ENOENT; + } + + p = exportedkeymat; + + memcpy(client_key->key, p, kl); p += kl; + memcpy(server_key->key, p, kl); p += kl; + memcpy(client_key->salt, p, sl); p += sl; + memcpy(server_key->salt, p, sl); p += sl; + + client_key->key_len = server_key->key_len = kl; + client_key->salt_len = server_key->salt_len = sl; + + return 0; +} + + +static void destructor(void *arg) +{ + struct dtls_flow *flow = arg; + + if (flow->ssl) { + (void)SSL_shutdown(flow->ssl); + SSL_free(flow->ssl); + } + + mem_deref(flow->uh); + mem_deref(flow->us); + + tmr_cancel(&flow->tmr); +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct dtls_flow *flow = arg; + uint8_t b; + int r; + + if (mbuf_get_left(mb) < 1) + return false; + + /* ignore non-DTLS packets */ + b = mb->buf[mb->pos]; + if (b < 20 || b > 63) + return false; + + if (!sa_cmp(src, &flow->peer, SA_ALL)) + return false; + + /* feed SSL data to the BIO */ + r = BIO_write(flow->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb)); + if (r <= 0) + return true; + + SSL_read(flow->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb)); + + if (!flow->up && SSL_state(flow->ssl) == SSL_ST_OK) { + + struct key client_key, server_key; + char profile[256]; + int err; + + flow->up = true; + + err = get_srtp_key_info(flow, profile, sizeof(profile), + &client_key, &server_key); + if (err) { + DEBUG_WARNING("SRTP key info: %m\n", err); + return true; + } + + flow->estabh(0, flow, profile, + &client_key, &server_key, flow->arg); + } + + return true; +} + + +int dtls_flow_alloc(struct dtls_flow **flowp, struct tls *tls, + struct udp_sock *us, dtls_estab_h *estabh, void *arg) +{ + struct dtls_flow *flow; + int err = ENOMEM; + + if (!flowp || !tls || !us || !estabh) + return EINVAL; + + flow = mem_zalloc(sizeof(*flow), destructor); + if (!flow) + return ENOMEM; + + flow->tls = tls; + flow->us = mem_ref(us); + flow->estabh = estabh; + flow->arg = arg; + + err = udp_register_helper(&flow->uh, us, LAYER_DTLS, NULL, + recv_handler, flow); + if (err) + goto out; + + flow->ssl = SSL_new(tls->ctx); + if (!flow->ssl) + goto out; + + flow->sbio_in = BIO_new(BIO_s_mem()); + if (!flow->sbio_in) + goto out; + + flow->sbio_out = BIO_new(&bio_udp_send); + if (!flow->sbio_out) { + BIO_free(flow->sbio_in); + goto out; + } + flow->sbio_out->ptr = flow; + + SSL_set_bio(flow->ssl, flow->sbio_in, flow->sbio_out); + + tmr_init(&flow->tmr); + + err = 0; + + out: + if (err) + mem_deref(flow); + else + *flowp = flow; + + return err; +} + + +int dtls_flow_start(struct dtls_flow *flow, const struct sa *peer, bool active) +{ + int r, err = 0; + + if (!flow || !peer) + return EINVAL; + + flow->peer = *peer; + + if (active) { + r = SSL_connect(flow->ssl); + if (r < 0) { + int ssl_err = SSL_get_error(flow->ssl, r); + + ERR_clear_error(); + + if (ssl_err != SSL_ERROR_WANT_READ) { + DEBUG_WARNING("SSL_connect() failed" + " (err=%d)\n", ssl_err); + } + } + + check_timer(flow); + } + else { + SSL_set_accept_state(flow->ssl); + + SSL_set_verify_depth(flow->ssl, 0); + SSL_set_verify(flow->ssl, + SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, + verify_callback); + } + + return err; +} + + +static const EVP_MD *type2evp(const char *type) +{ + if (0 == str_casecmp(type, "SHA-1")) + return EVP_sha1(); + else if (0 == str_casecmp(type, "SHA-256")) + return EVP_sha256(); + else + return NULL; +} + + +int dtls_get_remote_fingerprint(const struct dtls_flow *flow, const char *type, + struct tls_fingerprint *fp) +{ + X509 *x; + + if (!flow || !fp) + return EINVAL; + + x = SSL_get_peer_certificate(flow->ssl); + if (!x) + return EPROTO; + + fp->len = sizeof(fp->md); + if (1 != X509_digest(x, type2evp(type), fp->md, &fp->len)) + return ENOENT; + + return 0; +} diff --git a/modules/evdev/evdev.c b/modules/evdev/evdev.c new file mode 100644 index 0000000..e54ba6b --- /dev/null +++ b/modules/evdev/evdev.c @@ -0,0 +1,348 @@ +/** + * @file evdev.c Input event device UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/input.h> +#include <re.h> +#include <baresip.h> +#include "print.h" + + +#define DEBUG_MODULE "evdev" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* Note: + * + * KEY_NUMERIC_xyz added in linux kernel 2.6.28 + */ + + +struct ui_st { + struct ui *ui; /* base class */ + int fd; + ui_input_h *h; + void *arg; +}; + + +static struct ui *evdev; +static char evdev_device[64] = "/dev/event0"; + + +static void evdev_close(struct ui_st *st) +{ + if (st->fd < 0) + return; + + fd_close(st->fd); + (void)close(st->fd); + st->fd = -1; +} + + +static void evdev_destructor(void *arg) +{ + struct ui_st *st = arg; + + evdev_close(st); + mem_deref(st->ui); +} + + +static int code2ascii(uint16_t modifier, uint16_t code) +{ + switch (code) { + + case KEY_0: return '0'; + case KEY_1: return '1'; + case KEY_2: return '2'; + case KEY_3: return KEY_LEFTSHIFT==modifier ? '#' : '3'; + case KEY_4: return '4'; + case KEY_5: return '5'; + case KEY_6: return '6'; + case KEY_7: return '7'; + case KEY_8: return '8'; + case KEY_9: return '9'; + case KEY_BACKSPACE: return '\b'; + case KEY_ENTER: return '\n'; + case KEY_ESC: return 0x1b; + case KEY_KPASTERISK: return '*'; +#ifdef KEY_NUMERIC_0 + case KEY_NUMERIC_0: return '0'; +#endif +#ifdef KEY_NUMERIC_1 + case KEY_NUMERIC_1: return '1'; +#endif +#ifdef KEY_NUMERIC_2 + case KEY_NUMERIC_2: return '2'; +#endif +#ifdef KEY_NUMERIC_3 + case KEY_NUMERIC_3: return '3'; +#endif +#ifdef KEY_NUMERIC_4 + case KEY_NUMERIC_4: return '4'; +#endif +#ifdef KEY_NUMERIC_5 + case KEY_NUMERIC_5: return '5'; +#endif +#ifdef KEY_NUMERIC_6 + case KEY_NUMERIC_6: return '6'; +#endif +#ifdef KEY_NUMERIC_7 + case KEY_NUMERIC_7: return '7'; +#endif +#ifdef KEY_NUMERIC_8 + case KEY_NUMERIC_8: return '8'; +#endif +#ifdef KEY_NUMERIC_9 + case KEY_NUMERIC_9: return '9'; +#endif +#ifdef KEY_NUMERIC_STAR + case KEY_NUMERIC_STAR: return '*'; +#endif +#ifdef KEY_NUMERIC_POUND + case KEY_NUMERIC_POUND: return '#'; +#endif +#ifdef KEY_KP0 + case KEY_KP0: return '0'; +#endif +#ifdef KEY_KP1 + case KEY_KP1: return '1'; +#endif +#ifdef KEY_KP2 + case KEY_KP2: return '2'; +#endif +#ifdef KEY_KP3 + case KEY_KP3: return '3'; +#endif +#ifdef KEY_KP4 + case KEY_KP4: return '4'; +#endif +#ifdef KEY_KP5 + case KEY_KP5: return '5'; +#endif +#ifdef KEY_KP6 + case KEY_KP6: return '6'; +#endif +#ifdef KEY_KP7 + case KEY_KP7: return '7'; +#endif +#ifdef KEY_KP8 + case KEY_KP8: return '8'; +#endif +#ifdef KEY_KP9 + case KEY_KP9: return '9'; +#endif +#ifdef KEY_KPDOT + case KEY_KPDOT: return 0x1b; +#endif +#ifdef KEY_KPENTER + case KEY_KPENTER: return '\n'; +#endif + default: return -1; + } +} + + +static int stderr_handler(const char *p, size_t sz, void *arg) +{ + (void)arg; + + if (write(STDERR_FILENO, p, sz) < 0) + return errno; + + return 0; +} + + +static void reportkey(struct ui_st *st, int ascii) +{ + struct re_printf pf; + + pf.vph = stderr_handler; + + if (!st->h) + return; + + st->h(ascii, &pf, st->arg); +} + + +static void evdev_fd_handler(int flags, void *arg) +{ + struct ui_st *st = arg; + struct input_event evv[64]; /* the events (up to 64 at once) */ + uint16_t modifier = 0; + size_t n; + int i; + + /* This might happen if you unplug a USB device */ + if (flags & FD_EXCEPT) { + DEBUG_WARNING("fd handler: FD_EXCEPT - device unplugged?\n"); + evdev_close(st); + return; + } + + if (FD_READ != flags) { + DEBUG_WARNING("fd_handler: unexpected flags 0x%02x\n", flags); + return; + } + + n = read(st->fd, evv, sizeof(evv)); + + if (n < (int) sizeof(struct input_event)) { + DEBUG_WARNING("event: short read (%m)\n", errno); + return; + } + + for (i = 0; i < (int) (n / sizeof(struct input_event)); i++) { + const struct input_event *ev = &evv[i]; + + DEBUG_INFO("Event: type %u, code %u, value %d\n", + ev->type, ev->code, ev->value); + + if (EV_KEY != ev->type) + continue; + + if (KEY_LEFTSHIFT == ev->code) { + modifier = KEY_LEFTSHIFT; + continue; + } + + if (1 == ev->value) { + const int ascii = code2ascii(modifier, ev->code); + if (-1 == ascii) { + DEBUG_WARNING("unhandled key code %u\n", + ev->code); + } + else + reportkey(st, ascii); + modifier = 0; + } + else if (0 == ev->value) { + reportkey(st, 0x00); + } + } +} + + +static int evdev_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *uih, void *arg) +{ + const char *dev = str_isset(prm->device) ? prm->device : evdev_device; + struct ui_st *st; + int err = 0; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), evdev_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(evdev); + st->fd = open(dev, O_RDWR); + if (st->fd < 0) { + err = errno; + goto out; + } + +#if 0 + /* grab the event device to prevent it from propagating + its events to the regular keyboard driver */ + if (-1 == ioctl(st->fd, EVIOCGRAB, (void *)1)) { + DEBUG_WARNING("ioctl EVIOCGRAB on %s (%m)\n", dev, errno); + } +#endif + + print_name(st->fd); + print_events(st->fd); + print_keys(st->fd); + print_leds(st->fd); + + err = fd_listen(st->fd, FD_READ, evdev_fd_handler, st); + if (err) + goto out; + + st->h = uih; + st->arg = arg; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int buzz(const struct ui_st *st, int value) +{ + struct input_event ev; + ssize_t n; + + ev.type = EV_SND; + ev.code = SND_BELL; + ev.value = value; + + n = write(st->fd, &ev, sizeof(ev)); + if (n < 0) { + DEBUG_WARNING("output: write fd=%d (%m)\n", st->fd, errno); + } + + return errno; +} + + +static int evdev_output(struct ui_st *st, const char *str) +{ + int err = 0; + + if (!str) + return EINVAL; + + while (*str) { + switch (*str++) { + + case '\a': + err |= buzz(st, 1); + break; + + default: + err |= buzz(st, 0); + break; + } + } + + return err; +} + + +static int module_init(void) +{ + return ui_register(&evdev, "evdev", evdev_alloc, evdev_output); +} + + +static int module_close(void) +{ + evdev = mem_deref(evdev); + return 0; +} + + +const struct mod_export DECL_EXPORTS(evdev) = { + "evdev", + "ui", + module_init, + module_close +}; diff --git a/modules/evdev/module.mk b/modules/evdev/module.mk new file mode 100644 index 0000000..5d9ede2 --- /dev/null +++ b/modules/evdev/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := evdev +$(MOD)_SRCS += evdev.c +$(MOD)_SRCS += print.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/evdev/print.c b/modules/evdev/print.c new file mode 100644 index 0000000..3e2b762 --- /dev/null +++ b/modules/evdev/print.c @@ -0,0 +1,518 @@ +/** + * @file print.c Input event device info + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/input.h> +#include <re_types.h> +#include <re_fmt.h> +#include "print.h" + + +#define DEBUG_MODULE "evdev" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + + +/** + * Print the name information + * + * @param fd Device file descriptor + */ +void print_name(int fd) +{ + char name[256]= "Unknown"; + + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { + perror("evdev ioctl"); + } + + DEBUG_NOTICE("evdev device name: %s\n", name); +} + + +/** + * Print supported events + * + * @param fd Device file descriptor + */ +void print_events(int fd) +{ + uint8_t evtype_bitmask[EV_MAX/8 + 1]; + int i; + + memset(evtype_bitmask, 0, sizeof(evtype_bitmask)); + if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) { + DEBUG_WARNING("evdev ioctl EVIOCGBIT (%m)\n", errno); + return; + } + + printf("Supported event types:\n"); + + for (i = 0; i < EV_MAX; i++) { + if (!test_bit(i, evtype_bitmask)) + continue; + + printf(" Event type 0x%02x ", i); + + switch (i) { + + case EV_KEY : + printf(" (Keys or Buttons)\n"); + break; + case EV_REL : + printf(" (Relative Axes)\n"); + break; + case EV_ABS : + printf(" (Absolute Axes)\n"); + break; + case EV_MSC : + printf(" (Something miscellaneous)\n"); + break; + case EV_LED : + printf(" (LEDs)\n"); + break; + case EV_SND : + printf(" (Sounds)\n"); + break; + case EV_REP : + printf(" (Repeat)\n"); + break; + case EV_FF : + printf(" (Force Feedback)\n"); + break; + default: + printf(" (Unknown event type: 0x%04x)\n", i); + break; + } + } +} + + +/** + * Print supported keys + * + * @param fd Device file descriptor + */ +void print_keys(int fd) +{ + uint8_t key_bitmask[KEY_MAX/8 + 1]; + int i; + + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), + key_bitmask) < 0) { + perror("evdev ioctl"); + } + + printf("Supported Keys:\n"); + + for (i = 0; i < KEY_MAX; i++) { + if (!test_bit(i, key_bitmask)) + continue; + + printf(" Key 0x%02x ", i); + + switch (i) { + + case KEY_RESERVED : printf(" (Reserved)\n"); break; + case KEY_ESC : printf(" (Escape)\n"); break; + case KEY_1 : printf(" (1)\n"); break; + case KEY_2 : printf(" (2)\n"); break; + case KEY_3 : printf(" (3)\n"); break; + case KEY_4 : printf(" (4)\n"); break; + case KEY_5 : printf(" (5)\n"); break; + case KEY_6 : printf(" (6)\n"); break; + case KEY_7 : printf(" (7)\n"); break; + case KEY_8 : printf(" (8)\n"); break; + case KEY_9 : printf(" ()\n"); break; + case KEY_0 : printf(" ()\n"); break; + case KEY_MINUS : printf(" (-)\n"); break; + case KEY_EQUAL : printf(" (=)\n"); break; + case KEY_BACKSPACE : printf(" (Backspace)\n"); break; + case KEY_TAB : printf(" (Tab)\n"); break; + case KEY_Q : printf(" (Q)\n"); break; + case KEY_W : printf(" (W)\n"); break; + case KEY_E : printf(" (E)\n"); break; + case KEY_R : printf(" (R)\n"); break; + case KEY_T : printf(" (T)\n"); break; + case KEY_Y : printf(" (Y)\n"); break; + case KEY_U : printf(" (U)\n"); break; + case KEY_I : printf(" (I)\n"); break; + case KEY_O : printf(" (O)\n"); break; + case KEY_P : printf(" (P)\n"); break; + case KEY_LEFTBRACE : printf(" ([)\n"); break; + case KEY_RIGHTBRACE : printf(" (])\n"); break; + case KEY_ENTER : printf(" (Enter)\n"); break; + case KEY_LEFTCTRL : printf(" (LH Control)\n"); break; + case KEY_A : printf(" (A)\n"); break; + case KEY_S : printf(" (S)\n"); break; + case KEY_D : printf(" (D)\n"); break; + case KEY_F : printf(" (F)\n"); break; + case KEY_G : printf(" (G)\n"); break; + case KEY_H : printf(" (H)\n"); break; + case KEY_J : printf(" (J)\n"); break; + case KEY_K : printf(" (K)\n"); break; + case KEY_L : printf(" (L)\n"); break; + case KEY_SEMICOLON : printf(" (;)\n"); break; + case KEY_APOSTROPHE : printf(" (')\n"); break; + case KEY_GRAVE : printf(" (`)\n"); break; + case KEY_LEFTSHIFT : printf(" (LH Shift)\n"); break; + case KEY_BACKSLASH : printf(" (\\)\n"); break; + case KEY_Z : printf(" (Z)\n"); break; + case KEY_X : printf(" (X)\n"); break; + case KEY_C : printf(" (C)\n"); break; + case KEY_V : printf(" (V)\n"); break; + case KEY_B : printf(" (B)\n"); break; + case KEY_N : printf(" (N)\n"); break; + case KEY_M : printf(" (M)\n"); break; + case KEY_COMMA : printf(" (,)\n"); break; + case KEY_DOT : printf(" (.)\n"); break; + case KEY_SLASH : printf(" (/)\n"); break; + case KEY_RIGHTSHIFT : printf(" (RH Shift)\n"); break; + case KEY_KPASTERISK : printf(" (*)\n"); break; + case KEY_LEFTALT : printf(" (LH Alt)\n"); break; + case KEY_SPACE : printf(" (Space)\n"); break; + case KEY_CAPSLOCK : printf(" (CapsLock)\n"); break; + case KEY_F1 : printf(" (F1)\n"); break; + case KEY_F2 : printf(" (F2)\n"); break; + case KEY_F3 : printf(" (F3)\n"); break; + case KEY_F4 : printf(" (F4)\n"); break; + case KEY_F5 : printf(" (F5)\n"); break; + case KEY_F6 : printf(" (F6)\n"); break; + case KEY_F7 : printf(" (F7)\n"); break; + case KEY_F8 : printf(" (F8)\n"); break; + case KEY_F9 : printf(" (F9)\n"); break; + case KEY_F10 : printf(" (F10)\n"); break; + case KEY_NUMLOCK : printf(" (NumLock)\n"); break; + case KEY_SCROLLLOCK : printf(" (ScrollLock)\n"); break; + case KEY_KP7 : printf(" (KeyPad 7)\n"); break; + case KEY_KP8 : printf(" (KeyPad 8)\n"); break; + case KEY_KP9 : printf(" (Keypad 9)\n"); break; + case KEY_KPMINUS : printf(" (KeyPad Minus)\n"); break; + case KEY_KP4 : printf(" (KeyPad 4)\n"); break; + case KEY_KP5 : printf(" (KeyPad 5)\n"); break; + case KEY_KP6 : printf(" (KeyPad 6)\n"); break; + case KEY_KPPLUS : printf(" (KeyPad Plus)\n"); break; + case KEY_KP1 : printf(" (KeyPad 1)\n"); break; + case KEY_KP2 : printf(" (KeyPad 2)\n"); break; + case KEY_KP3 : printf(" (KeyPad 3)\n"); break; + case KEY_KPDOT : printf(" (KeyPad decimal point)\n"); break; +/* case KEY_103RD : printf(" (Huh?)\n"); break; */ + case KEY_F13 : printf(" (F13)\n"); break; + case KEY_102ND : printf(" (Beats me...)\n"); break; + case KEY_F11 : printf(" (F11)\n"); break; + case KEY_F12 : printf(" (F12)\n"); break; + case KEY_F14 : printf(" (F14)\n"); break; + case KEY_F15 : printf(" (F15)\n"); break; + case KEY_F16 : printf(" (F16)\n"); break; + case KEY_F17 : printf(" (F17)\n"); break; + case KEY_F18 : printf(" (F18)\n"); break; + case KEY_F19 : printf(" (F19)\n"); break; + case KEY_F20 : printf(" (F20)\n"); break; + case KEY_KPENTER : printf(" (Keypad Enter)\n"); break; + case KEY_RIGHTCTRL : printf(" (RH Control)\n"); break; + case KEY_KPSLASH : printf(" (KeyPad Forward Slash)\n"); break; + case KEY_SYSRQ : printf(" (System Request)\n"); break; + case KEY_RIGHTALT : printf(" (RH Alternate)\n"); break; + case KEY_LINEFEED : printf(" (Line Feed)\n"); break; + case KEY_HOME : printf(" (Home)\n"); break; + case KEY_UP : printf(" (Up)\n"); break; + case KEY_PAGEUP : printf(" (Page Up)\n"); break; + case KEY_LEFT : printf(" (Left)\n"); break; + case KEY_RIGHT : printf(" (Right)\n"); break; + case KEY_END : printf(" (End)\n"); break; + case KEY_DOWN : printf(" (Down)\n"); break; + case KEY_PAGEDOWN : printf(" (Page Down)\n"); break; + case KEY_INSERT : printf(" (Insert)\n"); break; + case KEY_DELETE : printf(" (Delete)\n"); break; + case KEY_MACRO : printf(" (Macro)\n"); break; + case KEY_MUTE : printf(" (Mute)\n"); break; + case KEY_VOLUMEDOWN : printf(" (Volume Down)\n"); break; + case KEY_VOLUMEUP : printf(" (Volume Up)\n"); break; + case KEY_POWER : printf(" (Power)\n"); break; + case KEY_KPEQUAL : printf(" (KeyPad Equal)\n"); break; + case KEY_KPPLUSMINUS : printf(" (KeyPad +/-)\n"); break; + case KEY_PAUSE : printf(" (Pause)\n"); break; + case KEY_F21 : printf(" (F21)\n"); break; + case KEY_F22 : printf(" (F22)\n"); break; + case KEY_F23 : printf(" (F23)\n"); break; + case KEY_F24 : printf(" (F24)\n"); break; + case KEY_KPCOMMA : printf(" (KeyPad comma)\n"); break; + case KEY_LEFTMETA : printf(" (LH Meta)\n"); break; + case KEY_RIGHTMETA : printf(" (RH Meta)\n"); break; + case KEY_COMPOSE : printf(" (Compose)\n"); break; + case KEY_STOP : printf(" (Stop)\n"); break; + case KEY_AGAIN : printf(" (Again)\n"); break; + case KEY_PROPS : printf(" (Properties)\n"); break; + case KEY_UNDO : printf(" (Undo)\n"); break; + case KEY_FRONT : printf(" (Front)\n"); break; + case KEY_COPY : printf(" (Copy)\n"); break; + case KEY_OPEN : printf(" (Open)\n"); break; + case KEY_PASTE : printf(" (Paste)\n"); break; + case KEY_FIND : printf(" (Find)\n"); break; + case KEY_CUT : printf(" (Cut)\n"); break; + case KEY_HELP : printf(" (Help)\n"); break; + case KEY_MENU : printf(" (Menu)\n"); break; + case KEY_CALC : printf(" (Calculator)\n"); break; + case KEY_SETUP : printf(" (Setup)\n"); break; + case KEY_SLEEP : printf(" (Sleep)\n"); break; + case KEY_WAKEUP : printf(" (Wakeup)\n"); break; + case KEY_FILE : printf(" (File)\n"); break; + case KEY_SENDFILE : printf(" (Send File)\n"); break; + case KEY_DELETEFILE : printf(" (Delete File)\n"); break; + case KEY_XFER : printf(" (Transfer)\n"); break; + case KEY_PROG1 : printf(" (Program 1)\n"); break; + case KEY_PROG2 : printf(" (Program 2)\n"); break; + case KEY_WWW : printf(" (Web Browser)\n"); break; + case KEY_MSDOS : printf(" (DOS mode)\n"); break; + case KEY_COFFEE : printf(" (Coffee)\n"); break; + case KEY_DIRECTION : printf(" (Direction)\n"); break; + case KEY_CYCLEWINDOWS : printf(" (Window cycle)\n"); break; + case KEY_MAIL : printf(" (Mail)\n"); break; + case KEY_BOOKMARKS : printf(" (Book Marks)\n"); break; + case KEY_COMPUTER : printf(" (Computer)\n"); break; + case KEY_BACK : printf(" (Back)\n"); break; + case KEY_FORWARD : printf(" (Forward)\n"); break; + case KEY_CLOSECD : printf(" (Close CD)\n"); break; + case KEY_EJECTCD : printf(" (Eject CD)\n"); break; + case KEY_EJECTCLOSECD : printf(" (Eject / Close CD)\n"); break; + case KEY_NEXTSONG : printf(" (Next Song)\n"); break; + case KEY_PLAYPAUSE : printf(" (Play and Pause)\n"); break; + case KEY_PREVIOUSSONG : printf(" (Previous Song)\n"); break; + case KEY_STOPCD : printf(" (Stop CD)\n"); break; + case KEY_RECORD : printf(" (Record)\n"); break; + case KEY_REWIND : printf(" (Rewind)\n"); break; + case KEY_PHONE : printf(" (Phone)\n"); break; + case KEY_ISO : printf(" (ISO)\n"); break; + case KEY_CONFIG : printf(" (Config)\n"); break; + case KEY_HOMEPAGE : printf(" (Home)\n"); break; + case KEY_REFRESH : printf(" (Refresh)\n"); break; + case KEY_EXIT : printf(" (Exit)\n"); break; + case KEY_MOVE : printf(" (Move)\n"); break; + case KEY_EDIT : printf(" (Edit)\n"); break; + case KEY_SCROLLUP : printf(" (Scroll Up)\n"); break; + case KEY_SCROLLDOWN : printf(" (Scroll Down)\n"); break; + case KEY_KPLEFTPAREN : printf(" (KeyPad LH paren)\n"); break; + case KEY_KPRIGHTPAREN : printf(" (KeyPad RH paren)\n"); break; +#if 0 + case KEY_INTL1 : printf(" (Intl 1)\n"); break; + case KEY_INTL2 : printf(" (Intl 2)\n"); break; + case KEY_INTL3 : printf(" (Intl 3)\n"); break; + case KEY_INTL4 : printf(" (Intl 4)\n"); break; + case KEY_INTL5 : printf(" (Intl 5)\n"); break; + case KEY_INTL6 : printf(" (Intl 6)\n"); break; + case KEY_INTL7 : printf(" (Intl 7)\n"); break; + case KEY_INTL8 : printf(" (Intl 8)\n"); break; + case KEY_INTL9 : printf(" (Intl 9)\n"); break; + case KEY_LANG1 : printf(" (Language 1)\n"); break; + case KEY_LANG2 : printf(" (Language 2)\n"); break; + case KEY_LANG3 : printf(" (Language 3)\n"); break; + case KEY_LANG4 : printf(" (Language 4)\n"); break; + case KEY_LANG5 : printf(" (Language 5)\n"); break; + case KEY_LANG6 : printf(" (Language 6)\n"); break; + case KEY_LANG7 : printf(" (Language 7)\n"); break; + case KEY_LANG8 : printf(" (Language 8)\n"); break; + case KEY_LANG9 : printf(" (Language 9)\n"); break; +#endif + case KEY_PLAYCD : printf(" (Play CD)\n"); break; + case KEY_PAUSECD : printf(" (Pause CD)\n"); break; + case KEY_PROG3 : printf(" (Program 3)\n"); break; + case KEY_PROG4 : printf(" (Program 4)\n"); break; + case KEY_SUSPEND : printf(" (Suspend)\n"); break; + case KEY_CLOSE : printf(" (Close)\n"); break; + case KEY_UNKNOWN : printf(" (Specifically unknown)\n"); break; +#ifdef KEY_BRIGHTNESSDOWN + case KEY_BRIGHTNESSDOWN: printf(" (Brightness Down)\n");break; +#endif +#ifdef KEY_BRIGHTNESSUP + case KEY_BRIGHTNESSUP : printf(" (Brightness Up)\n"); break; +#endif + case BTN_0 : printf(" (Button 0)\n"); break; + case BTN_1 : printf(" (Button 1)\n"); break; + case BTN_2 : printf(" (Button 2)\n"); break; + case BTN_3 : printf(" (Button 3)\n"); break; + case BTN_4 : printf(" (Button 4)\n"); break; + case BTN_5 : printf(" (Button 5)\n"); break; + case BTN_6 : printf(" (Button 6)\n"); break; + case BTN_7 : printf(" (Button 7)\n"); break; + case BTN_8 : printf(" (Button 8)\n"); break; + case BTN_9 : printf(" (Button 9)\n"); break; + case BTN_LEFT : printf(" (Left Button)\n"); break; + case BTN_RIGHT : printf(" (Right Button)\n"); break; + case BTN_MIDDLE : printf(" (Middle Button)\n"); break; + case BTN_SIDE : printf(" (Side Button)\n"); break; + case BTN_EXTRA : printf(" (Extra Button)\n"); break; + case BTN_FORWARD : printf(" (Forward Button)\n"); break; + case BTN_BACK : printf(" (Back Button)\n"); break; + case BTN_TRIGGER : printf(" (Trigger Button)\n"); break; + case BTN_THUMB : printf(" (Thumb Button)\n"); break; + case BTN_THUMB2 : printf(" (Second Thumb Button)\n"); break; + case BTN_TOP : printf(" (Top Button)\n"); break; + case BTN_TOP2 : printf(" (Second Top Button)\n"); break; + case BTN_PINKIE : printf(" (Pinkie Button)\n"); break; + case BTN_BASE : printf(" (Base Button)\n"); break; + case BTN_BASE2 : printf(" (Second Base Button)\n"); break; + case BTN_BASE3 : printf(" (Third Base Button)\n"); break; + case BTN_BASE4 : printf(" (Fourth Base Button)\n"); break; + case BTN_BASE5 : printf(" (Fifth Base Button)\n"); break; + case BTN_BASE6 : printf(" (Sixth Base Button)\n"); break; + case BTN_DEAD : printf(" (Dead Button)\n"); break; + case BTN_A : printf(" (Button A)\n"); break; + case BTN_B : printf(" (Button B)\n"); break; + case BTN_C : printf(" (Button C)\n"); break; + case BTN_X : printf(" (Button X)\n"); break; + case BTN_Y : printf(" (Button Y)\n"); break; + case BTN_Z : printf(" (Button Z)\n"); break; + case BTN_TL : printf(" (Thumb Left Button)\n"); break; + case BTN_TR : printf(" (Thumb Right Button )\n"); break; + case BTN_TL2 : printf(" (Second Thumb Left Button)\n"); break; + case BTN_TR2 : printf(" (Second Thumb Right Button )\n"); + break; + case BTN_SELECT : printf(" (Select Button)\n"); break; + case BTN_MODE : printf(" (Mode Button)\n"); break; + case BTN_THUMBL : printf(" (Another Left Thumb Button )\n"); + break; + case BTN_THUMBR : printf(" (Another Right Thumb Button )\n"); + break; + case BTN_TOOL_PEN : printf(" (Digitiser Pen Tool)\n"); break; + case BTN_TOOL_RUBBER : printf(" (Digitiser Rubber Tool)\n"); + break; + case BTN_TOOL_BRUSH : printf(" (Digitiser Brush Tool)\n"); + break; + case BTN_TOOL_PENCIL : printf(" (Digitiser Pencil Tool)\n"); + break; + case BTN_TOOL_AIRBRUSH:printf(" (Digitiser Airbrush Tool)\n"); + break; + case BTN_TOOL_FINGER : printf(" (Digitiser Finger Tool)\n"); + break; + case BTN_TOOL_MOUSE : printf(" (Digitiser Mouse Tool)\n"); + break; + case BTN_TOOL_LENS : printf(" (Digitiser Lens Tool)\n"); break; + case BTN_TOUCH : printf(" (Digitiser Touch Button )\n"); break; + case BTN_STYLUS : printf(" (Digitiser Stylus Button )\n"); + break; + case BTN_STYLUS2: printf(" (Second Digitiser Stylus Btn)\n"); + break; +#ifdef KEY_NUMERIC_0 + case KEY_NUMERIC_0: printf(" (Numeric 0)\n"); + break; +#endif +#ifdef KEY_NUMERIC_1 + case KEY_NUMERIC_1: printf(" (Numeric 1)\n"); + break; +#endif +#ifdef KEY_NUMERIC_2 + case KEY_NUMERIC_2: printf(" (Numeric 2)\n"); + break; +#endif +#ifdef KEY_NUMERIC_3 + case KEY_NUMERIC_3: printf(" (Numeric 3)\n"); + break; +#endif +#ifdef KEY_NUMERIC_4 + case KEY_NUMERIC_4: printf(" (Numeric 4)\n"); + break; +#endif +#ifdef KEY_NUMERIC_5 + case KEY_NUMERIC_5: printf(" (Numeric 5)\n"); + break; +#endif +#ifdef KEY_NUMERIC_6 + case KEY_NUMERIC_6: printf(" (Numeric 6)\n"); + break; +#endif +#ifdef KEY_NUMERIC_7 + case KEY_NUMERIC_7: printf(" (Numeric 7)\n"); + break; +#endif +#ifdef KEY_NUMERIC_8 + case KEY_NUMERIC_8: printf(" (Numeric 8)\n"); + break; +#endif +#ifdef KEY_NUMERIC_9 + case KEY_NUMERIC_9: printf(" (Numeric 9)\n"); + break; +#endif +#ifdef KEY_NUMERIC_STAR + case KEY_NUMERIC_STAR: printf(" (Numeric *)\n"); + break; +#endif +#ifdef KEY_NUMERIC_POUND + case KEY_NUMERIC_POUND: printf(" (Numeric #)\n"); + break; +#endif + default: + printf(" (Unknown key)\n"); + } + } +} + + +/** + * Print supported LEDs + * + * @param fd Device file descriptor + */ +void print_leds(int fd) +{ + uint8_t led_bitmask[LED_MAX/8 + 1]; + int i; + + memset(led_bitmask, 0, sizeof(led_bitmask)); + if (ioctl(fd, EVIOCGBIT(EV_LED, sizeof(led_bitmask)), + led_bitmask) < 0) { + perror("evdev ioctl"); + } + + printf("Supported LEDs:\n"); + + for (i = 0; i < LED_MAX; i++) { + if (!test_bit(i, led_bitmask)) + continue; + + printf(" LED type 0x%02x ", i); + + switch (i) { + + case LED_NUML : + printf(" (Num Lock)\n"); + break; + case LED_CAPSL : + printf(" (Caps Lock)\n"); + break; + case LED_SCROLLL : + printf(" (Scroll Lock)\n"); + break; + case LED_COMPOSE : + printf(" (Compose)\n"); + break; + case LED_KANA : + printf(" (Kana)\n"); + break; + case LED_SLEEP : + printf(" (Sleep)\n"); + break; + case LED_SUSPEND : + printf(" (Suspend)\n"); + break; + case LED_MUTE : + printf(" (Mute)\n"); + break; + case LED_MISC : + printf(" (Miscellaneous)\n"); + break; + default: + printf(" (Unknown LED type: 0x%04x)\n", i); + } + } +} diff --git a/modules/evdev/print.h b/modules/evdev/print.h new file mode 100644 index 0000000..49acfcf --- /dev/null +++ b/modules/evdev/print.h @@ -0,0 +1,11 @@ +/** + * @file print.h Interface to Input event device info + * + * Copyright (C) 2010 Creytiv.com + */ + + +void print_name(int fd); +void print_events(int fd); +void print_keys(int fd); +void print_leds(int fd); diff --git a/modules/g711/g711.c b/modules/g711/g711.c new file mode 100644 index 0000000..f1179d5 --- /dev/null +++ b/modules/g711/g711.c @@ -0,0 +1,126 @@ +/** + * @file g711.c G.711 Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +static int pcmu_encode(struct auenc_state *aes, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + (void)aes; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc) + return ENOMEM; + + *len = sampc; + + while (sampc--) + *buf++ = g711_pcm2ulaw(*sampv++); + + return 0; +} + + +static int pcmu_decode(struct audec_state *ads, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + (void)ads; + + if (!sampv || !sampc || !buf) + return EINVAL; + + if (*sampc < len) + return ENOMEM; + + *sampc = len; + + while (len--) + *sampv++ = g711_ulaw2pcm(*buf++); + + return 0; +} + + +static int pcma_encode(struct auenc_state *aes, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + (void)aes; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc) + return ENOMEM; + + *len = sampc; + + while (sampc--) + *buf++ = g711_pcm2alaw(*sampv++); + + return 0; +} + + +static int pcma_decode(struct audec_state *ads, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + (void)ads; + + if (!sampv || !sampc || !buf) + return EINVAL; + + if (*sampc < len) + return ENOMEM; + + *sampc = len; + + while (len--) + *sampv++ = g711_alaw2pcm(*buf++); + + return 0; +} + + +static struct aucodec pcmu = { + LE_INIT, "0", "PCMU", 8000, 1, NULL, + NULL, pcmu_encode, NULL, pcmu_decode, NULL, NULL, NULL +}; + +static struct aucodec pcma = { + LE_INIT, "8", "PCMA", 8000, 1, NULL, + NULL, pcma_encode, NULL, pcma_decode, NULL, NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(&pcmu); + aucodec_register(&pcma); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&pcma); + aucodec_unregister(&pcmu); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g711) = { + "g711", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g711/module.mk b/modules/g711/module.mk new file mode 100644 index 0000000..432269d --- /dev/null +++ b/modules/g711/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g711 +$(MOD)_SRCS += g711.c + +include mk/mod.mk diff --git a/modules/g722/g722.c b/modules/g722/g722.c new file mode 100644 index 0000000..5e07e3a --- /dev/null +++ b/modules/g722/g722.c @@ -0,0 +1,196 @@ +/** + * @file g722.c G.722 audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <re.h> +#include <baresip.h> +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include <spandsp.h> + + +#define DEBUG_MODULE "g722" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* + http://www.soft-switch.org/spandsp-modules.html + */ + +/* From RFC 3551: + + 4.5.2 G722 + + G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding + within 64 kbit/s". The G.722 encoder produces a stream of octets, + each of which SHALL be octet-aligned in an RTP packet. The first bit + transmitted in the G.722 octet, which is the most significant bit of + the higher sub-band sample, SHALL correspond to the most significant + bit of the octet in the RTP packet. + + Even though the actual sampling rate for G.722 audio is 16,000 Hz, + the RTP clock rate for the G722 payload format is 8,000 Hz because + that value was erroneously assigned in RFC 1890 and must remain + unchanged for backward compatibility. The octet rate or sample-pair + rate is 8,000 Hz. + */ + +enum { + G722_SAMPLE_RATE = 16000, + G722_BITRATE_48k = 48000, + G722_BITRATE_56k = 56000, + G722_BITRATE_64k = 64000 +}; + + +struct auenc_state { + g722_encode_state_t enc; +}; + +struct audec_state { + g722_decode_state_t dec; +}; + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_alloc(sizeof(*st), NULL); + if (!st) + return ENOMEM; + + if (!g722_encode_init(&st->enc, G722_BITRATE_64k, 0)) { + DEBUG_WARNING("g722_encode_init failed\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_alloc(sizeof(*st), NULL); + if (!st) + return ENOMEM; + + if (!g722_decode_init(&st->dec, G722_BITRATE_64k, 0)) { + DEBUG_WARNING("g722_decode_init failed\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int n; + + n = g722_encode(&st->enc, buf, sampv, (int)sampc); + if (n <= 0) { + DEBUG_WARNING("g722_encode: len=%d\n", n); + return EPROTO; + } + else if (n > (int)*len) { + DEBUG_WARNING("encode: wrote %d > %d buf\n", n, *len); + return EOVERFLOW; + } + + *len = n; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int n; + + if (!st || !sampv || !buf) + return EINVAL; + + n = g722_decode(&st->dec, sampv, buf, (int)len); + if (n < 0) { + DEBUG_WARNING("g722_decode: n=%d\n", n); + return EPROTO; + } + + *sampc = n; + + return 0; +} + + +static struct aucodec g722 = { + LE_INIT, "9", "G722", 8000, 1, NULL, + encode_update, encode, + decode_update, decode, NULL, + NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(&g722); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&g722); + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(g722) = { + "g722", + "codec", + module_init, + module_close +}; diff --git a/modules/g722/module.mk b/modules/g722/module.mk new file mode 100644 index 0000000..f56dd07 --- /dev/null +++ b/modules/g722/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g722 +$(MOD)_SRCS += g722.c +$(MOD)_LFLAGS += -lspandsp + +include mk/mod.mk diff --git a/modules/g7221/decode.c b/modules/g7221/decode.c new file mode 100644 index 0000000..6977a12 --- /dev/null +++ b/modules/g7221/decode.c @@ -0,0 +1,67 @@ +/** + * @file g7221/decode.c G.722.1 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <g722_1.h> +#include "g7221.h" + + +struct audec_state { + g722_1_decode_state_t dec; +}; + + +int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac; + struct audec_state *ads; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + ads = *adsp; + + if (ads) + return 0; + + ads = mem_alloc(sizeof(*ads), NULL); + if (!ads) + return ENOMEM; + + if (!g722_1_decode_init(&ads->dec, g7221->bitrate, ac->srate)) { + mem_deref(ads); + return EPROTO; + } + + *adsp = ads; + + return 0; +} + + +int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + size_t framec; + + if (!ads || !sampv || !sampc || !buf) + return EINVAL; + + framec = len / ads->dec.bytes_per_frame; + + if (len != ads->dec.bytes_per_frame * framec) + return EPROTO; + + if (*sampc < ads->dec.frame_size * framec) + return ENOMEM; + + *sampc = g722_1_decode(&ads->dec, sampv, buf, (int)len); + + return 0; +} diff --git a/modules/g7221/encode.c b/modules/g7221/encode.c new file mode 100644 index 0000000..8bb5b2b --- /dev/null +++ b/modules/g7221/encode.c @@ -0,0 +1,68 @@ +/** + * @file g7221/encode.c G.722.1 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <g722_1.h> +#include "g7221.h" + + +struct auenc_state { + g722_1_encode_state_t enc; +}; + + +int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac; + struct auenc_state *aes; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + aes = *aesp; + + if (aes) + return 0; + + aes = mem_alloc(sizeof(*aes), NULL); + if (!aes) + return ENOMEM; + + if (!g722_1_encode_init(&aes->enc, g7221->bitrate, ac->srate)) { + mem_deref(aes); + return EPROTO; + } + + *aesp = aes; + + return 0; +} + + +int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + size_t framec; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + framec = sampc / aes->enc.frame_size; + + if (sampc != aes->enc.frame_size * framec) + return EPROTO; + + if (*len < aes->enc.bytes_per_frame * framec) + return ENOMEM; + + *len = g722_1_encode(&aes->enc, buf, sampv, (int)sampc); + + return 0; +} diff --git a/modules/g7221/g7221.c b/modules/g7221/g7221.c new file mode 100644 index 0000000..a224f46 --- /dev/null +++ b/modules/g7221/g7221.c @@ -0,0 +1,49 @@ +/** + * @file g7221.c G.722.1 Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "g7221.h" + + +static struct g7221_aucodec g7221 = { + .ac = { + .name = "G7221", + .srate = 16000, + .ch = 1, + .encupdh = g7221_encode_update, + .ench = g7221_encode, + .decupdh = g7221_decode_update, + .dech = g7221_decode, + .fmtp_ench = g7221_fmtp_enc, + .fmtp_cmph = g7221_fmtp_cmp, + }, + .bitrate = 32000, +}; + + +static int module_init(void) +{ + aucodec_register((struct aucodec *)&g7221); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister((struct aucodec *)&g7221); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g7221) = { + "g7221", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g7221/g7221.h b/modules/g7221/g7221.h new file mode 100644 index 0000000..635fc01 --- /dev/null +++ b/modules/g7221/g7221.h @@ -0,0 +1,29 @@ +/** + * @file g7221.h Private G.722.1 Interface + * + * Copyright (C) 2010 Creytiv.com + */ + +struct g7221_aucodec { + struct aucodec ac; + uint32_t bitrate; +}; + +/* Encode */ +int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp); +int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc); + + +/* Decode */ +int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp); +int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len); + + +/* SDP */ +int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg); diff --git a/modules/g7221/module.mk b/modules/g7221/module.mk new file mode 100644 index 0000000..e0471a7 --- /dev/null +++ b/modules/g7221/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g7221 +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += g7221.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lg722_1 + +include mk/mod.mk diff --git a/modules/g7221/sdp.c b/modules/g7221/sdp.c new file mode 100644 index 0000000..b46351e --- /dev/null +++ b/modules/g7221/sdp.c @@ -0,0 +1,54 @@ +/** + * @file g7221/sdp.c H.264 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "g7221.h" + + +static uint32_t g7221_bitrate(const char *fmtp) +{ + struct pl pl, bitrate; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "bitrate", &bitrate)) + return pl_u32(&bitrate); + + return 0; +} + + +int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct g7221_aucodec *g7221 = arg; + (void)offer; + + if (!mb || !fmt || !g7221) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s bitrate=%u\r\n", + fmt->id, g7221->bitrate); +} + + +bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg) +{ + const struct g7221_aucodec *g7221 = arg; + (void)lfmtp; + + if (!g7221) + return false; + + if (g7221->bitrate != g7221_bitrate(rfmtp)) + return false; + + return true; +} diff --git a/modules/g726/g726.c b/modules/g726/g726.c new file mode 100644 index 0000000..a7ae8f8 --- /dev/null +++ b/modules/g726/g726.c @@ -0,0 +1,201 @@ +/** + * @file g726.c G.726 Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include <spandsp.h> + + +enum { MAX_PACKET = 100 }; + + +struct g726_aucodec { + struct aucodec ac; + int bitrate; +}; + +struct auenc_state { + g726_state_t st; +}; + +struct audec_state { + g726_state_t st; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + g726_release(&st->st); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + g726_release(&st->st); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct g726_aucodec *gac = (struct g726_aucodec *)ac; + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR, + G726_PACKING_LEFT)) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct g726_aucodec *gac = (struct g726_aucodec *)ac; + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR, + G726_PACKING_LEFT)) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < MAX_PACKET) + return ENOMEM; + + *len = g726_encode(&st->st, buf, sampv, (int)sampc); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + if (!sampv || !sampc || !buf) + return EINVAL; + + *sampc = g726_decode(&st->st, sampv, buf, (int)len); + + return 0; +} + + +static struct g726_aucodec g726[4] = { + { + { + LE_INIT, 0, "G726-40", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 40000 + }, + { + { + LE_INIT, 0, "G726-32", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 32000 + }, + { + { + LE_INIT, 0, "G726-24", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 24000 + }, + { + { + LE_INIT, 0, "G726-16", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 16000 + } +}; + + +static int module_init(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(g726); i++) + aucodec_register((struct aucodec *)&g726[i]); + + return 0; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(g726); i++) + aucodec_unregister((struct aucodec *)&g726[i]); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g726) = { + "g726", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g726/module.mk b/modules/g726/module.mk new file mode 100644 index 0000000..c828ea0 --- /dev/null +++ b/modules/g726/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g726 +$(MOD)_SRCS += g726.c +$(MOD)_LFLAGS += -lspandsp + +include mk/mod.mk diff --git a/modules/gsm/gsm.c b/modules/gsm/gsm.c new file mode 100644 index 0000000..798bae7 --- /dev/null +++ b/modules/gsm/gsm.c @@ -0,0 +1,171 @@ +/** + * @file gsm.c GSM Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <gsm.h> /* please report if you have problems finding this file */ +#include <re.h> +#include <baresip.h> + + +enum { + FRAME_SIZE = 160 +}; + + +struct auenc_state { + gsm enc; +}; + +struct audec_state { + gsm dec; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + gsm_destroy(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + gsm_destroy(st->dec); +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)ac; + (void)prm; + (void)fmtp; + + if (!aesp) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->enc = gsm_create(); + if (!st->enc) { + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)ac; + (void)fmtp; + + if (!adsp) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->dec = gsm_create(); + if (!st->dec) { + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + if (sampc != FRAME_SIZE) + return EPROTO; + if (*len < sizeof(gsm_frame)) + return ENOMEM; + + gsm_encode(st->enc, (gsm_signal *)sampv, buf); + + *len = sizeof(gsm_frame); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int ret; + + if (*sampc < FRAME_SIZE) + return ENOMEM; + if (len < sizeof(gsm_frame)) + return EBADMSG; + + ret = gsm_decode(st->dec, (gsm_byte *)buf, (gsm_signal *)sampv); + if (ret) + return EPROTO; + + *sampc = 160; + + return 0; +} + + +static struct aucodec ac_gsm = { + LE_INIT, "3", "GSM", 8000, 1, NULL, + encode_update, encode, decode_update, decode, NULL, NULL, NULL +}; + + +static int module_init(void) +{ + debug("gsm: GSM v%u.%u.%u\n", GSM_MAJOR, GSM_MINOR, GSM_PATCHLEVEL); + + aucodec_register(&ac_gsm); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&ac_gsm); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gsm) = { + "gsm", + "codec", + module_init, + module_close +}; diff --git a/modules/gsm/module.mk b/modules/gsm/module.mk new file mode 100644 index 0000000..48c8ff0 --- /dev/null +++ b/modules/gsm/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gsm +$(MOD)_SRCS += gsm.c +$(MOD)_LFLAGS += -L$(SYSROOT)/lib -lgsm +CFLAGS += -I$(SYSROOT)/include/gsm -I$(SYSROOT)/local/include + +include mk/mod.mk diff --git a/modules/gst/README b/modules/gst/README new file mode 100644 index 0000000..0076e9f --- /dev/null +++ b/modules/gst/README @@ -0,0 +1,34 @@ +Gstreamer notes +--------------- + + The module 'gst' is using the Gstreamer framework to play external + media and provide this as an internal audio source. + + +Debian NOTES + + The http handler 'neonhttpsrc' is by default not part of Debian Etch. + You must download the gst-plugins-bad package manually and build it. + + +Currently installed packages: + +$ dpkg --get-selections | grep gstrea +gstreamer0.10-alsa install +gstreamer0.10-doc install +gstreamer0.10-ffmpeg install +gstreamer0.10-plugins-bad install +gstreamer0.10-plugins-base install +gstreamer0.10-plugins-good install +gstreamer0.10-plugins-ugly install +gstreamer0.10-tools install +gstreamer0.10-x install +libgstreamer-plugins-base0.10-0 install +libgstreamer-plugins-base0.10-dev install +libgstreamer0.10-0 install +libgstreamer0.10-dev install + + +baresip configuration: + + module gst.so diff --git a/modules/gst/dump.c b/modules/gst/dump.c new file mode 100644 index 0000000..685d8f6 --- /dev/null +++ b/modules/gst/dump.c @@ -0,0 +1,65 @@ +/** + * @file dump.c Gstreamer playbin pipeline - dump utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst.h" + + +void gst_dump_props(GstElement *g) +{ + uint64_t u64; + gchar *strval; + double volume; + int n; + + debug("Gst properties:\n"); + + g_object_get(g, "delay", &u64, NULL); + debug(" delay: %lu ns\n", u64); + + g_object_get(g, "uri", &strval, NULL); + debug(" uri: %s\n", strval); + g_free(strval); + + g_object_get(g, "suburi", &strval, NULL); + debug(" suburi: %s\n", strval); + g_free(strval); + + g_object_get(g, "queue-size", &u64, NULL); + debug(" queue-size: %lu ns\n", u64); + + g_object_get(g, "queue-threshold", &u64, NULL); + debug(" queue-threshold: %lu ns\n", u64); + + g_object_get(g, "nstreams", &n, NULL); + debug(" nstreams: %d\n", n); + + g_object_get(g, "volume", &volume, NULL); + debug(" Volume: %f\n", volume); +} + + +void gst_dump_caps(const GstCaps *caps) +{ + GstStructure *s; + int rate, channels, width; + + if (!caps) + return; + + if (!gst_caps_get_size(caps)) + return; + + s = gst_caps_get_structure(caps, 0); + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + + info("gst: caps dump: %d Hz, %d channels, width=%d\n", + rate, channels, width); +} diff --git a/modules/gst/gst.c b/modules/gst/gst.c new file mode 100644 index 0000000..7af74f4 --- /dev/null +++ b/modules/gst/gst.c @@ -0,0 +1,449 @@ +/** + * @file gst.c Gstreamer playbin pipeline + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#define __USE_POSIX199309 +#include <time.h> +#include <pthread.h> +#include <gst/gst.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "gst.h" + + +#define DEBUG_MODULE "gst" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/** + * Defines the Gstreamer state + * + * <pre> + * ptime=variable ptime=20ms + * .-----------. N kHz .---------. N kHz + * | | 1-2 channels | | 1-2 channels + * | Gstreamer |--------------->|Packetize|-------------> [read handler] + * | | | | + * '-----------' '---------' + * + * </pre> + */ +struct ausrc_st { + struct ausrc *as; /**< Inheritance */ + pthread_t tid; /**< Thread ID */ + bool run; /**< Running flag */ + ausrc_read_h *rh; /**< Read handler */ + ausrc_error_h *errh; /**< Error handler */ + void *arg; /**< Handler argument */ + struct ausrc_prm prm; /**< Read parameters */ + struct aubuf *aubuf; /**< Packet buffer */ + uint32_t psize; /**< Packet size in bytes */ + + /* Gstreamer */ + char *uri; + GstElement *pipeline, *bin, *source, *capsfilt, *sink; + GMainLoop *loop; +}; + + +typedef struct _GstFakeSink GstFakeSink; +static char gst_uri[256] = "http://relay1.slayradio.org:8000/"; +static struct ausrc *ausrc; + + +static void *thread(void *arg) +{ + struct ausrc_st *st = arg; + + /* Now set to playing and iterate. */ + DEBUG_NOTICE("Setting pipeline to PLAYING\n"); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + + while (st->run) { + g_main_loop_run(st->loop); + } + + return NULL; +} + + +static gboolean bus_watch_handler(GstBus *bus, GstMessage *msg, gpointer data) +{ + struct ausrc_st *st = data; + GMainLoop *loop = st->loop; + GstTagList *tag_list; + gchar *title; + GError *err; + gchar *d; + + (void)bus; + + switch (GST_MESSAGE_TYPE(msg)) { + + case GST_MESSAGE_EOS: + DEBUG_NOTICE("End-of-stream\n"); + + /* XXX decrementing repeat count? */ + + /* Re-start stream */ + if (st->run) { + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + } + else { + g_main_loop_quit(loop); + } + break; + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &d); + + DEBUG_WARNING("Error: %d(%m) message=%s\n", err->code, + err->code, err->message); + DEBUG_WARNING("Debug: %s\n", d); + + g_free(d); + + /* Call error handler */ + if (st->errh) + st->errh(err->code, err->message, st->arg); + + g_error_free(err); + + st->run = false; + g_main_loop_quit(loop); + break; + + case GST_MESSAGE_TAG: + gst_message_parse_tag(msg, &tag_list); + + if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &title)) { + DEBUG_NOTICE("Title: %s\n", title); + g_free(title); + } + break; + + default: + break; + } + + return TRUE; +} + + +static void format_check(struct ausrc_st *st, GstStructure *s) +{ + int rate, channels, width; + gboolean sign; + + if (!st || !s) + return; + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + gst_structure_get_boolean(s, "signed", &sign); + + if ((int)st->prm.srate != rate) { + DEBUG_WARNING("expected %u Hz (got %u Hz)\n", st->prm.srate, + rate); + } + if (st->prm.ch != channels) { + DEBUG_WARNING("expected %d channels (got %d)\n", + st->prm.ch, channels); + } + if (16 != width) { + DEBUG_WARNING("expected 16-bit width (got %d)\n", width); + } + if (!sign) { + DEBUG_WARNING("expected signed 16-bit format\n"); + } +} + + +static void play_packet(struct ausrc_st *st) +{ + uint8_t buf[st->psize]; + + /* timed read from audio-buffer */ + if (aubuf_get(st->aubuf, st->prm.ptime, buf, sizeof(buf))) + return; + + /* call read handler */ + if (st->rh) + st->rh(buf, sizeof(buf), st->arg); +} + + +/* Expected format: 16-bit signed PCM */ +static void packet_handler(struct ausrc_st *st, GstBuffer *buffer) +{ + int err; + + if (!st->run) + return; + + /* NOTE: When streaming from files, the buffer will be filled up + * pretty quickly.. + */ + + err = aubuf_write(st->aubuf, GST_BUFFER_DATA(buffer), + GST_BUFFER_SIZE(buffer)); + if (err) { + DEBUG_WARNING("aubuf_write: %m\n", err); + } + + /* Empty buffer now */ + while (st->run) { + const struct timespec delay = {0, st->prm.ptime*1000000/2}; + + play_packet(st); + + if (aubuf_cur_size(st->aubuf) < st->psize) + break; + + (void)nanosleep(&delay, NULL); + } +} + + +static void handoff_handler(GstFakeSink *fakesink, GstBuffer *buffer, + GstPad *pad, gpointer user_data) +{ + struct ausrc_st *st = user_data; + + (void)fakesink; + (void)pad; + + format_check(st, gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0)); + + packet_handler(st, buffer); +} + + +static void set_caps(struct ausrc_st *st) +{ + GstCaps *caps; + + /* Set the capabilities we want */ + caps = gst_caps_new_simple("audio/x-raw-int", + "rate", G_TYPE_INT, st->prm.srate, + "channels", G_TYPE_INT, st->prm.ch, + "width", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN,true, + NULL); +#if 0 + gst_dump_caps(caps); +#endif + g_object_set(G_OBJECT(st->capsfilt), "caps", caps, NULL); +} + + +/** + * Set up the Gstreamer pipeline. The playbin element is used to decode + * all kinds of different formats. The capsfilter is used to deliver the + * audio in a fixed format (X Hz, 1-2 channels, 16 bit signed) + * + * The pipeline looks like this: + * + * <pre> + * .--------------. .------------------------------------------. + * | playbin | |mybin .------------. .------------. | + * |----. .----| |-----. | capsfilter | | fakesink | | + * |sink| |src |--->|ghost| |----. .---| |----. .---| | handoff + * |----' '----| |pad |-->|sink| |src|-->|sink| |src|--+--> handler + * | | |-----' '------------' '------------' | + * '--------------' '------------------------------------------' + * </pre> + * + * @param st Audio source state + * + * @return 0 if success, otherwise errorcode + */ +static int gst_setup(struct ausrc_st *st) +{ + GstBus *bus; + GstPad *pad; + + st->loop = g_main_loop_new(NULL, FALSE); + + st->pipeline = gst_pipeline_new("pipeline"); + if (!st->pipeline) { + DEBUG_WARNING("failed to create pipeline element\n"); + return ENOMEM; + } + + /********************* Player BIN **************************/ + + st->source = gst_element_factory_make("playbin", "source"); + if (!st->source) { + DEBUG_WARNING("failed to create playbin source element\n"); + return ENOMEM; + } + + /********************* My BIN **************************/ + + st->bin = gst_bin_new("mybin"); + + st->capsfilt = gst_element_factory_make("capsfilter", NULL); + if (!st->capsfilt) { + DEBUG_WARNING("failed to create capsfilter element\n"); + return ENOMEM; + } + + set_caps(st); + + st->sink = gst_element_factory_make("fakesink", "sink"); + if (!st->sink) { + DEBUG_WARNING("failed to create sink element\n"); + return ENOMEM; + } + + gst_bin_add_many(GST_BIN(st->bin), st->capsfilt, st->sink, NULL); + gst_element_link_many(st->capsfilt, st->sink, NULL); + + /* add ghostpad */ + pad = gst_element_get_pad(st->capsfilt, "sink"); + gst_element_add_pad(st->bin, gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + /* put all elements in a bin */ + gst_bin_add_many(GST_BIN(st->pipeline), st->source, NULL); + + /* Override audio-sink handoff handler */ + g_object_set(G_OBJECT(st->sink), "signal-handoffs", TRUE, NULL); + g_signal_connect(st->sink, "handoff", G_CALLBACK(handoff_handler), st); + g_object_set(G_OBJECT(st->source), "audio-sink", st->bin, NULL); + + /********************* Misc **************************/ + + /* Bus watch */ + bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline)); + gst_bus_add_watch(bus, bus_watch_handler, st); + gst_object_unref(bus); + + /* Set URI */ + g_object_set(G_OBJECT(st->source), "uri", st->uri, NULL); + + return 0; +} + + +static void gst_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + g_main_loop_quit(st->loop); + pthread_join(st->tid, NULL); + } + + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(st->pipeline)); + + mem_deref(st->uri); + mem_deref(st->aubuf); + + mem_deref(st->as); +} + + +static int gst_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + (void)ctx; + + if (!device) + device = gst_uri; + + if (!prm) + return EINVAL; + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), gst_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = str_dup(&st->uri, device); + if (err) + goto out; + + st->prm = *prm; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->psize = 2 * sampc; + + err = aubuf_alloc(&st->aubuf, st->psize, 0); + if (err) + goto out; + + err = gst_setup(st); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->tid, NULL, thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int mod_gst_init(void) +{ + gchar *s; + + gst_init(0, NULL); + + s = gst_version_string(); + + DEBUG_NOTICE("init: %s\n", s); + + g_free(s); + + return ausrc_register(&ausrc, "gst", gst_alloc); +} + + +static int mod_gst_close(void) +{ + gst_deinit(); + ausrc = mem_deref(ausrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst) = { + "gst", + "sound", + mod_gst_init, + mod_gst_close +}; diff --git a/modules/gst/gst.h b/modules/gst/gst.h new file mode 100644 index 0000000..9627188 --- /dev/null +++ b/modules/gst/gst.h @@ -0,0 +1,9 @@ +/** + * @file gst.h Gstreamer playbin pipeline -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +void gst_dump_props(GstElement *g); +void gst_dump_caps(const GstCaps *caps); diff --git a/modules/gst/module.mk b/modules/gst/module.mk new file mode 100644 index 0000000..9b95765 --- /dev/null +++ b/modules/gst/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst +$(MOD)_SRCS += gst.c dump.c +$(MOD)_LFLAGS += `pkg-config --libs gstreamer-0.10` +CFLAGS += `pkg-config --cflags gstreamer-0.10` + +include mk/mod.mk diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c new file mode 100644 index 0000000..12ef9dd --- /dev/null +++ b/modules/httpd/httpd.c @@ -0,0 +1,103 @@ +/** + * @file httpd.c Webserver UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +static struct http_sock *httpsock; + + +static int html_print_head(struct re_printf *pf, void *unused) +{ + (void)unused; + + return re_hprintf(pf, + "<html>\n" + "<head>\n" + "<title>Baresip v" BARESIP_VERSION "</title>\n" + "</head>\n"); +} + + +static int html_print_cmd(struct re_printf *pf, const struct http_msg *req) +{ + struct pl params; + + if (!pf || !req) + return EINVAL; + + if (pl_isset(&req->prm)) { + params.p = req->prm.p + 1; + params.l = req->prm.l - 1; + } + else { + params.p = "h"; + params.l = 1; + } + + return re_hprintf(pf, + "%H" + "<body>\n" + "<pre>\n" + "%H" + "</pre>\n" + "</body>\n" + "</html>\n", + html_print_head, NULL, + ui_input_pl, ¶ms); +} + + +static void http_req_handler(struct http_conn *conn, + const struct http_msg *msg, void *arg) +{ + (void)arg; + + if (0 == pl_strcasecmp(&msg->path, "/")) { + + http_creply(conn, 200, "OK", + "text/html;charset=UTF-8", + "%H", html_print_cmd, msg); + } + else { + http_ereply(conn, 404, "Not Found"); + } +} + + +static int module_init(void) +{ + struct sa laddr; + int err; + + if (conf_get_sa(conf_cur(), "http_listen", &laddr)) { + sa_set_str(&laddr, "0.0.0.0", 8000); + } + + err = http_listen(&httpsock, &laddr, http_req_handler, NULL); + if (err) + return err; + + info("httpd: listening on %J\n", &laddr); + + return 0; +} + + +static int module_close(void) +{ + httpsock = mem_deref(httpsock); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(httpd) = { + "httpd", + "application", + module_init, + module_close, +}; diff --git a/modules/httpd/module.mk b/modules/httpd/module.mk new file mode 100644 index 0000000..a29d2c5 --- /dev/null +++ b/modules/httpd/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := httpd +$(MOD)_SRCS += httpd.c + +include mk/mod.mk diff --git a/modules/ice/ice.c b/modules/ice/ice.c new file mode 100644 index 0000000..e8de4d7 --- /dev/null +++ b/modules/ice/ice.c @@ -0,0 +1,696 @@ +/** + * @file ice.c ICE Module + * + * Copyright (C) 2010 Creytiv.com + */ +#ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> +#include <SystemConfiguration/SCNetworkReachability.h> +#endif +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup ice ice + * + * Interactive Connectivity Establishment (ICE) for media NAT traversal + * + * This module enables ICE for NAT traversal. You can enable ICE + * in your accounts file with the parameter ;medianat=ice. The following + * options can be configured: + * + \verbatim + ice_turn {yes,no} # Enable TURN candidates + ice_debug {yes,no} # Enable ICE debugging/tracing + ice_nomination {regular,aggressive} # Regular or aggressive nomination + ice_mode {full,lite} # Full ICE-mode or ICE-lite + \endverbatim + */ + + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + struct sdp_session *sdp; + struct ice *ice; + char *user; + char *pass; + int mediac; + bool started; + bool send_reinvite; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + struct comp { + struct sa laddr; + void *sock; + } compv[2]; + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct icem *icem; + bool complete; +}; + + +static struct mnat *mnat; +static struct { + enum ice_mode mode; + enum ice_nomination nom; + bool turn; + bool debug; +} ice = { + ICE_MODE_FULL, + ICE_NOMINATION_REGULAR, + true, + false +}; + + +static void gather_handler(int err, uint16_t scode, const char *reason, + void *arg); + + +static bool is_cellular(const struct sa *laddr) +{ +#if TARGET_OS_IPHONE + SCNetworkReachabilityRef r; + SCNetworkReachabilityFlags flags = 0; + bool cell = false; + + r = SCNetworkReachabilityCreateWithAddressPair(NULL, + &laddr->u.sa, NULL); + if (!r) + return false; + + if (SCNetworkReachabilityGetFlags(r, &flags)) { + + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + cell = true; + } + + CFRelease(r); + + return cell; +#else + (void)laddr; + return false; +#endif +} + + +static void ice_printf(struct mnat_media *m, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + debug("%s: %v", m ? sdp_media_name(m->sdpm) : "ICE", fmt, &ap); + va_end(ap); +} + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); + mem_deref(sess->user); + mem_deref(sess->pass); + mem_deref(sess->ice); + mem_deref(sess->sdp); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + unsigned i; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->icem); + for (i=0; i<2; i++) + mem_deref(m->compv[i].sock); +} + + +static bool candidate_handler(struct le *le, void *arg) +{ + return 0 != sdp_media_set_lattr(arg, false, ice_attr_cand, "%H", + ice_cand_encode, le->data); +} + + +/** + * Update the local SDP attributes, this can be called multiple times + * when the state of the ICE machinery changes + */ +static int set_media_attributes(struct mnat_media *m) +{ + int err = 0; + + if (icem_mismatch(m->icem)) { + err = sdp_media_set_lattr(m->sdpm, true, + ice_attr_mismatch, NULL); + return err; + } + else { + sdp_media_del_lattr(m->sdpm, ice_attr_mismatch); + } + + /* Encode all my candidates */ + sdp_media_del_lattr(m->sdpm, ice_attr_cand); + if (list_apply(icem_lcandl(m->icem), true, candidate_handler, m->sdpm)) + return ENOMEM; + + if (ice_remotecands_avail(m->icem)) { + err |= sdp_media_set_lattr(m->sdpm, true, + ice_attr_remote_cand, "%H", + ice_remotecands_encode, m->icem); + } + + return err; +} + + +static bool if_handler(const char *ifname, const struct sa *sa, void *arg) +{ + struct mnat_media *m = arg; + uint16_t lprio; + unsigned i; + int err = 0; + + /* Skip loopback and link-local addresses */ + if (sa_is_loopback(sa) || sa_is_linklocal(sa)) + return false; + + lprio = is_cellular(sa) ? 0 : 10; + + ice_printf(m, "added interface: %s:%j (local prio %u)\n", + ifname, sa, lprio); + + for (i=0; i<2; i++) { + if (m->compv[i].sock) + err |= icem_cand_add(m->icem, i+1, lprio, ifname, sa); + } + + if (err) { + warning("ice: %s:%j: icem_cand_add: %m\n", ifname, sa, err); + } + + return false; +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + net_if_apply(if_handler, m); + + switch (ice.mode) { + + default: + case ICE_MODE_FULL: + if (ice.turn) { + err = icem_gather_relay(m->icem, &sess->srv, + sess->user, sess->pass); + } + else { + err = icem_gather_srflx(m->icem, &sess->srv); + } + break; + + case ICE_MODE_LITE: + gather_handler(0, 0, NULL, m); + break; + } + + return err; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + const char *usage; + int err; + + if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->sdp = mem_ref(ss); + sess->estabh = estabh; + sess->arg = arg; + + err = str_dup(&sess->user, user); + err |= str_dup(&sess->pass, pass); + if (err) + goto out; + + err = ice_alloc(&sess->ice, ice.mode, offerer); + if (err) + goto out; + + ice_conf(sess->ice)->nom = ice.nom; + ice_conf(sess->ice)->debug = ice.debug; + + if (ICE_MODE_LITE == ice.mode) { + err |= sdp_session_set_lattr(ss, true, + ice_attr_lite, NULL); + } + + err |= sdp_session_set_lattr(ss, true, + ice_attr_ufrag, ice_ufrag(sess->ice)); + err |= sdp_session_set_lattr(ss, true, + ice_attr_pwd, ice_pwd(sess->ice)); + if (err) + goto out; + + usage = ice.turn ? stun_usage_relay : stun_usage_binding; + + err = stun_server_discover(&sess->dnsq, dnsc, usage, stun_proto_udp, + af, srv, port, dns_handler, sess); + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static bool verify_peer_ice(struct mnat_sess *ms) +{ + struct le *le; + + for (le = ms->medial.head; le; le = le->next) { + struct mnat_media *m = le->data; + struct sa raddr[2]; + unsigned i; + + if (!sdp_media_has_media(m->sdpm)) { + info("ice: stream '%s' is disabled -- ignore\n", + sdp_media_name(m->sdpm)); + continue; + } + + raddr[0] = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr[1]); + + for (i=0; i<2; i++) { + if (m->compv[i].sock && + !icem_verify_support(m->icem, i+1, &raddr[i])) { + warning("ice: %s.%u: no remote candidates" + " found (address = %J)\n", + sdp_media_name(m->sdpm), + i+1, &raddr[i]); + return false; + } + } + } + + return true; +} + + +static bool refresh_comp_laddr(struct mnat_media *m, unsigned id, + struct comp *comp, const struct sa *laddr) +{ + bool changed = false; + + if (!m || !comp || !comp->sock || !laddr) + return false; + + if (!sa_cmp(&comp->laddr, laddr, SA_ALL)) { + changed = true; + + ice_printf(m, "comp%u setting local: %J\n", id, laddr); + } + + sa_cpy(&comp->laddr, laddr); + + if (id == 1) + sdp_media_set_laddr(m->sdpm, &comp->laddr); + else if (id == 2) + sdp_media_set_laddr_rtcp(m->sdpm, &comp->laddr); + + return changed; +} + + +/* + * Update SDP Media with local addresses + */ +static bool refresh_laddr(struct mnat_media *m, + const struct sa *laddr1, + const struct sa *laddr2) +{ + bool changed = false; + + changed |= refresh_comp_laddr(m, 1, &m->compv[0], laddr1); + changed |= refresh_comp_laddr(m, 2, &m->compv[1], laddr2); + + return changed; +} + + +static void gather_handler(int err, uint16_t scode, const char *reason, + void *arg) +{ + struct mnat_media *m = arg; + + if (err || scode) { + warning("ice: gather error: %m (%u %s)\n", + err, scode, reason); + } + else { + refresh_laddr(m, + icem_cand_default(m->icem, 1), + icem_cand_default(m->icem, 2)); + + info("ice: %s: Default local candidates: %J / %J\n", + sdp_media_name(m->sdpm), + &m->compv[0].laddr, &m->compv[1].laddr); + + (void)set_media_attributes(m); + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static void conncheck_handler(int err, bool update, void *arg) +{ + struct mnat_media *m = arg; + struct mnat_sess *sess = m->sess; + struct le *le; + + info("ice: %s: connectivity check is complete (update=%d)\n", + sdp_media_name(m->sdpm), update); + + ice_printf(m, "Dumping media state: %H\n", icem_debug, m->icem); + + if (err) { + warning("ice: connectivity check failed: %m\n", err); + } + else { + bool changed; + + m->complete = true; + + changed = refresh_laddr(m, + icem_selected_laddr(m->icem, 1), + icem_selected_laddr(m->icem, 2)); + if (changed) + sess->send_reinvite = true; + + (void)set_media_attributes(m); + + /* Check all conncheck flags */ + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *mx = le->data; + if (!mx->complete) + return; + } + } + + /* call estab-handler and send re-invite */ + if (sess->send_reinvite && update) { + + info("ice: %s: sending Re-INVITE with updated" + " default candidates\n", + sdp_media_name(m->sdpm)); + + sess->estabh(0, 0, NULL, sess->arg); + sess->send_reinvite = false; + } +} + + +static int ice_start(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + ice_printf(NULL, "ICE Start: %H", ice_debug, sess->ice); + + /* Update SDP media */ + if (sess->started) { + + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + icem_update(m->icem); + + refresh_laddr(m, + icem_selected_laddr(m->icem, 1), + icem_selected_laddr(m->icem, 2)); + + err |= set_media_attributes(m); + } + + return err; + } + + /* Clear all conncheck flags */ + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + if (sdp_media_has_media(m->sdpm)) { + m->complete = false; + + if (ice.mode == ICE_MODE_FULL) { + err = icem_conncheck_start(m->icem); + if (err) + return err; + } + } + else { + m->complete = true; + } + } + + sess->started = true; + + return 0; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + unsigned i; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->compv[0].sock = mem_ref(sock1); + m->compv[1].sock = mem_ref(sock2); + + err = icem_alloc(&m->icem, sess->ice, proto, 0, + gather_handler, conncheck_handler, m); + if (err) + goto out; + + icem_set_name(m->icem, sdp_media_name(sdpm)); + + for (i=0; i<2; i++) { + if (m->compv[i].sock) + err |= icem_comp_add(m->icem, i+1, m->compv[i].sock); + } + + if (sa_isset(&sess->srv, SA_ALL)) + err |= media_start(sess, m); + + out: + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static bool sdp_attr_handler(const char *name, const char *value, void *arg) +{ + struct mnat_sess *sess = arg; + return 0 != ice_sdp_decode(sess->ice, name, value); +} + + +static bool media_attr_handler(const char *name, const char *value, void *arg) +{ + struct mnat_media *m = arg; + return 0 != icem_sdp_decode(m->icem, name, value); +} + + +static int enable_turn_channels(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + for (le = sess->medial.head; le; le = le->next) { + + struct mnat_media *m = le->data; + struct sa raddr[2]; + unsigned i; + + err |= set_media_attributes(m); + + raddr[0] = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr[1]); + + for (i=0; i<2; i++) { + if (m->compv[i].sock && sa_isset(&raddr[i], SA_ALL)) + err |= icem_add_chan(m->icem, i+1, &raddr[i]); + } + } + + return err; +} + + +/** This can be called several times */ +static int update(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + /* SDP session */ + (void)sdp_session_rattr_apply(sess->sdp, NULL, sdp_attr_handler, sess); + + /* SDP medialines */ + for (le = sess->medial.head; le; le = le->next) { + struct mnat_media *m = le->data; + + sdp_media_rattr_apply(m->sdpm, NULL, media_attr_handler, m); + } + + /* 5.1. Verifying ICE Support */ + if (verify_peer_ice(sess)) { + err = ice_start(sess); + } + else if (ice.turn) { + info("ice: ICE not supported by peer, fallback to TURN\n"); + err = enable_turn_channels(sess); + } + else { + info("ice: ICE not supported by peer\n"); + + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + err |= set_media_attributes(m); + } + } + + return err; +} + + +static int module_init(void) +{ +#ifdef MODULE_CONF + struct pl pl; + + conf_get_bool(conf_cur(), "ice_turn", &ice.turn); + conf_get_bool(conf_cur(), "ice_debug", &ice.debug); + + if (!conf_get(conf_cur(), "ice_nomination", &pl)) { + if (0 == pl_strcasecmp(&pl, "regular")) + ice.nom = ICE_NOMINATION_REGULAR; + else if (0 == pl_strcasecmp(&pl, "aggressive")) + ice.nom = ICE_NOMINATION_AGGRESSIVE; + else { + warning("ice: unknown nomination: %r\n", &pl); + } + } + if (!conf_get(conf_cur(), "ice_mode", &pl)) { + if (!pl_strcasecmp(&pl, "full")) + ice.mode = ICE_MODE_FULL; + else if (!pl_strcasecmp(&pl, "lite")) + ice.mode = ICE_MODE_LITE; + else { + warning("ice: unknown mode: %r\n", &pl); + } + } +#endif + + return mnat_register(&mnat, "ice", "+sip.ice", + session_alloc, media_alloc, update); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(ice) = { + "ice", + "mnat", + module_init, + module_close, +}; diff --git a/modules/ice/module.mk b/modules/ice/module.mk new file mode 100644 index 0000000..9a8254f --- /dev/null +++ b/modules/ice/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := ice +$(MOD)_SRCS += ice.c + +include mk/mod.mk diff --git a/modules/ilbc/ilbc.c b/modules/ilbc/ilbc.c new file mode 100644 index 0000000..549be4d --- /dev/null +++ b/modules/ilbc/ilbc.c @@ -0,0 +1,354 @@ +/** + * @file ilbc.c Internet Low Bit Rate Codec (iLBC) audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <iLBC_define.h> +#include <iLBC_decode.h> +#include <iLBC_encode.h> + + +/* + * This module implements the iLBC audio codec as defined in: + * + * RFC 3951 Internet Low Bit Rate Codec (iLBC) + * RFC 3952 RTP Payload Format for iLBC Speech + * + * The iLBC source code is not included here, but can be downloaded from + * http://ilbcfreeware.org/ + * + * You can also use the source distributed by the Freeswitch project, + * see www.freeswitch.org, and then freeswitch/libs/codec/ilbc. + * Or you can look in the asterisk source code ... + * + * mode=20 15.20 kbit/s 160samp 38bytes + * mode=30 13.33 kbit/s 240samp 50bytes + */ + +enum { + DEFAULT_MODE = 20, /* 20ms or 30ms */ + USE_ENHANCER = 1 +}; + +struct auenc_state { + iLBC_Enc_Inst_t enc; + int mode; + uint32_t enc_bytes; +}; + +struct audec_state { + iLBC_Dec_Inst_t dec; + int mode; + uint32_t nsamp; + size_t dec_bytes; +}; + + +static char ilbc_fmtp[32]; + + +static void set_encoder_mode(struct auenc_state *st, int mode) +{ + if (st->mode == mode) + return; + + info("ilbc: set iLBC encoder mode %dms\n", mode); + + st->mode = mode; + + switch (mode) { + + case 20: + st->enc_bytes = NO_OF_BYTES_20MS; + break; + + case 30: + st->enc_bytes = NO_OF_BYTES_30MS; + break; + + default: + warning("ilbc: unknown encoder mode %d\n", mode); + return; + } + + st->enc_bytes = initEncode(&st->enc, mode); +} + + +static void set_decoder_mode(struct audec_state *st, int mode) +{ + if (st->mode == mode) + return; + + info("ilbc: set iLBC decoder mode %dms\n", mode); + + st->mode = mode; + + switch (mode) { + + case 20: + st->nsamp = BLOCKL_20MS; + break; + + case 30: + st->nsamp = BLOCKL_30MS; + break; + + default: + warning("ilbc: unknown decoder mode %d\n", mode); + return; + } + + st->nsamp = initDecode(&st->dec, mode, USE_ENHANCER); +} + + +static void encoder_fmtp_decode(struct auenc_state *st, const char *fmtp) +{ + struct pl mode; + + if (!fmtp) + return; + + if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode)) + return; + + set_encoder_mode(st, pl_u32(&mode)); +} + + +static void decoder_fmtp_decode(struct audec_state *st, const char *fmtp) +{ + struct pl mode; + + if (!fmtp) + return; + + if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode)) + return; + + set_decoder_mode(st, pl_u32(&mode)); +} + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + (void)st; +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + (void)st; +} + + +static int check_ptime(const struct auenc_param *prm) +{ + if (!prm) + return 0; + + switch (prm->ptime) { + + case 20: + case 30: + return 0; + + default: + warning("ilbc: invalid ptime %u ms\n", prm->ptime); + return EINVAL; + } +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + + if (!aesp || !ac || !prm) + return EINVAL; + if (check_ptime(prm)) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + set_encoder_mode(st, DEFAULT_MODE); + + if (str_isset(fmtp)) + encoder_fmtp_decode(st, fmtp); + + /* update parameters after SDP was decoded */ + if (prm) { + prm->ptime = st->mode; + } + + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + set_decoder_mode(st, DEFAULT_MODE); + + if (str_isset(fmtp)) + decoder_fmtp_decode(st, fmtp); + + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + float float_buf[sampc]; + uint32_t i; + + /* Make sure there is enough space */ + if (*len < st->enc_bytes) { + warning("ilbc: encode: buffer is too small (%u bytes)\n", + *len); + return ENOMEM; + } + + /* Convert from 16-bit samples to float */ + for (i=0; i<sampc; i++) { + const int16_t v = sampv[i]; + float_buf[i] = (float)v; + } + + iLBC_encode(buf, /* (o) encoded data bits iLBC */ + float_buf, /* (o) speech vector to encode */ + &st->enc); /* (i/o) the general encoder state */ + + *len = st->enc_bytes; + + return 0; +} + + +static int do_dec(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + float float_buf[st->nsamp]; + const int mode = len ? 1 : 0; + uint32_t i; + + /* Make sure there is enough space in the buffer */ + if (*sampc < st->nsamp) + return ENOMEM; + + iLBC_decode(float_buf, /* (o) decoded signal block */ + (uint8_t *)buf, /* (i) encoded signal bits */ + &st->dec, /* (i/o) the decoder state structure */ + mode); /* (i) 0: bad packet, PLC, 1: normal */ + + /* Convert from float to 16-bit samples */ + for (i=0; i<st->nsamp; i++) { + sampv[i] = (int16_t)float_buf[i]; + } + + *sampc = st->nsamp; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + /* Try to detect mode */ + if (st->dec_bytes != len) { + + st->dec_bytes = len; + + switch (st->dec_bytes) { + + case NO_OF_BYTES_20MS: + set_decoder_mode(st, 20); + break; + + case NO_OF_BYTES_30MS: + set_decoder_mode(st, 30); + break; + + default: + warning("ilbc: decode: expect %u, got %u\n", + st->dec_bytes, len); + return EINVAL; + } + } + + return do_dec(st, sampv, sampc, buf, len); +} + + +static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + return do_dec(st, sampv, sampc, NULL, 0); +} + + +static struct aucodec ilbc = { + LE_INIT, 0, "iLBC", 8000, 1, ilbc_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0 +}; + + +static int module_init(void) +{ + (void)re_snprintf(ilbc_fmtp, sizeof(ilbc_fmtp), + "mode=%d", DEFAULT_MODE); + + aucodec_register(&ilbc); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&ilbc); + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(ilbc) = { + "ilbc", + "codec", + module_init, + module_close +}; diff --git a/modules/ilbc/module.mk b/modules/ilbc/module.mk new file mode 100644 index 0000000..f549a67 --- /dev/null +++ b/modules/ilbc/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := ilbc +$(MOD)_SRCS += ilbc.c +$(MOD)_LFLAGS += -lilbc -lm + +include mk/mod.mk diff --git a/modules/isac/isac.c b/modules/isac/isac.c new file mode 100644 index 0000000..b1aa96e --- /dev/null +++ b/modules/isac/isac.c @@ -0,0 +1,223 @@ +/** + * @file isac.c iSAC audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "isac.h" + + +/* + * draft-ietf-avt-rtp-isac-04 + */ + + +struct auenc_state { + ISACStruct *inst; +}; + +struct audec_state { + ISACStruct *inst; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + if (st->inst) + WebRtcIsac_Free(st->inst); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + if (st->inst) + WebRtcIsac_Free(st->inst); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_alloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + if (WebRtcIsac_Create(&st->inst) < 0) { + err = ENOMEM; + goto out; + } + + WebRtcIsac_EncoderInit(st->inst, 0); + + if (ac->srate == 32000) + WebRtcIsac_SetEncSampRate(st->inst, kIsacSuperWideband); + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_alloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + if (WebRtcIsac_Create(&st->inst) < 0) { + err = ENOMEM; + goto out; + } + + WebRtcIsac_DecoderInit(st->inst); + + if (ac->srate == 32000) + WebRtcIsac_SetDecSampRate(st->inst, kIsacSuperWideband); + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + WebRtc_Word16 len1, len2; + size_t l; + + if (!st || !buf || !len || !sampv || !sampc) + return EINVAL; + + /* 10 ms audio blocks */ + len1 = WebRtcIsac_Encode(st->inst, sampv, (void *)buf); + len2 = WebRtcIsac_Encode(st->inst, &sampv[sampc/2], (void *)buf); + + l = len1 ? len1 : len2; + + if (l > *len) + return ENOMEM; + + *len = l; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + WebRtc_Word16 speechType; + int n; + + if (!st || !sampv || !sampc || !buf || !len) + return EINVAL; + + n = WebRtcIsac_Decode(st->inst, (void *)buf, len, + (void *)sampv, &speechType); + if (n < 0) + return EPROTO; + + if ((size_t)n > *sampc) + return ENOMEM; + + *sampc = n; + + return 0; +} + + +static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + int n; + + if (!st || !sampv || !sampc) + return EINVAL; + + n = WebRtcIsac_DecodePlc(st->inst, (void *)sampv, 1); + if (n < 0) + return EPROTO; + + *sampc = n; + + return 0; +} + + +static struct aucodec isacv[] = { + { + LE_INIT, 0, "isac", 32000, 1, NULL, + encode_update, encode, decode_update, decode, plc, NULL, NULL + }, + { + LE_INIT, 0, "isac", 16000, 1, NULL, + encode_update, encode, decode_update, decode, plc, NULL, NULL + } +}; + + +static int module_init(void) +{ + unsigned i; + + for (i=0; i<ARRAY_SIZE(isacv); i++) + aucodec_register(&isacv[i]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(isacv); + + while (i--) + aucodec_unregister(&isacv[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(isac) = { + "isac", + "codec", + module_init, + module_close +}; diff --git a/modules/isac/module.mk b/modules/isac/module.mk new file mode 100644 index 0000000..64cafde --- /dev/null +++ b/modules/isac/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := isac +$(MOD)_SRCS += isac.c +$(MOD)_LFLAGS += -lisac + +include mk/mod.mk diff --git a/modules/l16/l16.c b/modules/l16/l16.c new file mode 100644 index 0000000..c506f4b --- /dev/null +++ b/modules/l16/l16.c @@ -0,0 +1,96 @@ +/** + * @file l16.c 16-bit linear codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +enum {NR_CODECS = 8}; + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int16_t *p = (void *)buf; + (void)st; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc*2) + return ENOMEM; + + *len = sampc*2; + + while (sampc--) + *p++ = htons(*sampv++); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int16_t *p = (void *)buf; + (void)st; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*sampc < len/2) + return ENOMEM; + + *sampc = len/2; + + len /= 2; + while (len--) + *sampv++ = ntohs(*p++); + + return 0; +} + + +/* See RFC 3551 */ +static struct aucodec l16v[NR_CODECS] = { + {LE_INIT, "10", "L16", 44100, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 32000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 16000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 8000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, "11", "L16", 44100, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 32000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 16000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 8000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +}; + + +static int module_init(void) +{ + size_t i; + + for (i=0; i<NR_CODECS; i++) + aucodec_register(&l16v[i]); + + return 0; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<NR_CODECS; i++) + aucodec_unregister(&l16v[i]); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(l16) = { + "l16", + "codec", + module_init, + module_close +}; diff --git a/modules/l16/module.mk b/modules/l16/module.mk new file mode 100644 index 0000000..b870d18 --- /dev/null +++ b/modules/l16/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := l16 +$(MOD)_SRCS += l16.c + +include mk/mod.mk diff --git a/modules/mda/mda.c b/modules/mda/mda.c new file mode 100644 index 0000000..208facc --- /dev/null +++ b/modules/mda/mda.c @@ -0,0 +1,40 @@ +/** + * @file mda.c Symbian MDA audio driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "mda.h" + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + int err; + + err = auplay_register(&auplay, "mda", mda_player_alloc); + err |= ausrc_register(&ausrc, "mda", mda_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(mda) = { + "mda", + "audio", + module_init, + module_close, +}; diff --git a/modules/mda/mda.h b/modules/mda/mda.h new file mode 100644 index 0000000..eee41d9 --- /dev/null +++ b/modules/mda/mda.h @@ -0,0 +1,17 @@ +/** + * @file mda.h Symbian MDA audio driver -- Internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +int mda_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int mda_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); + +int convert_srate(uint32_t srate); +int convert_channels(uint8_t ch); diff --git a/modules/mda/player.cpp b/modules/mda/player.cpp new file mode 100644 index 0000000..4cfa18c --- /dev/null +++ b/modules/mda/player.cpp @@ -0,0 +1,176 @@ +/** + * @file player.cpp Symbian MDA audio driver -- player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mdaaudiooutputstream.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" + +#define DEBUG_MODULE "player" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> +} + + +enum {VOLUME = 100}; + +class mda_player; +struct auplay_st { + struct auplay *ap; /* inheritance */ + mda_player *mda; + auplay_write_h *wh; + void *arg; +}; + + +class mda_player : public MMdaAudioOutputStreamCallback, public CBase +{ +public: + mda_player(struct auplay_st *st, struct auplay_prm *prm); + ~mda_player(); + void play(); + + /* from MMdaAudioOutputStreamCallback */ + virtual void MaoscOpenComplete(TInt aError); + virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaoscPlayComplete(TInt aError); + +private: + CMdaAudioOutputStream *iOutput; + TMdaAudioDataSettings iSettings; + TBool iIsReady; + TBuf8<320> iBuf; + struct auplay_st *state; +}; + + +mda_player::mda_player(struct auplay_st *st, struct auplay_prm *prm) + :iIsReady(EFalse) +{ + state = st; + + iBuf.FillZ(320); + + iSettings.iSampleRate = convert_srate(prm->srate); + iSettings.iChannels = convert_channels(prm->ch); + iSettings.iVolume = VOLUME; + + iOutput = CMdaAudioOutputStream::NewL(*this); + iOutput->Open(&iSettings); +} + + +mda_player::~mda_player() +{ + if (iOutput) { + iOutput->Stop(); + delete iOutput; + } +} + + +void mda_player::play() +{ + /* call write handler here */ + state->wh((uint8_t *)&iBuf[0], iBuf.Length(), state->arg); + + TRAPD(ret, iOutput->WriteL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("WriteL left with %d\n", ret); + } +} + + +void mda_player::MaoscOpenComplete(TInt aError) +{ + if (KErrNone != aError) { + iIsReady = EFalse; + DEBUG_WARNING("mda player error: %d\n", aError); + return; + } + + iOutput->SetAudioPropertiesL(iSettings.iSampleRate, + iSettings.iChannels); + iOutput->SetPriority(EMdaPriorityNormal, + EMdaPriorityPreferenceTime); + + iIsReady = ETrue; + + play(); +} + + +/* + * Note: In reality, this function is called approx. 1 millisecond after the + * last block was played, hence we have to generate buffer N+1 while buffer N + * is playing. + */ +void mda_player::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer) +{ + (void)aBuffer; + + if (KErrNone != aError && KErrCancel != aError) { + DEBUG_WARNING("MaoscBufferCopied [aError=%d]\n", aError); + } + if (aError == KErrAbort) { + DEBUG_NOTICE("player aborted\n"); + return; + } + + play(); +} + + +void mda_player::MaoscPlayComplete(TInt aError) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaoscPlayComplete [aError=%d]\n", aError); + } +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = (struct auplay_st *)arg; + + delete st->mda; + + mem_deref(st->ap); +} + + +int mda_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int err = 0; + + (void)device; + + st = (struct auplay_st *)mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = (struct auplay *)mem_ref(ap); + st->wh = wh; + st->arg = arg; + + st->mda = new mda_player(st, prm); + if (!st->mda) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/mda/recorder.cpp b/modules/mda/recorder.cpp new file mode 100644 index 0000000..6c358dc --- /dev/null +++ b/modules/mda/recorder.cpp @@ -0,0 +1,170 @@ +/** + * @file recorder.cpp Symbian MDA audio driver -- recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mdaaudioinputstream.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" + +#define DEBUG_MODULE "recorder" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> +} + + +enum {VOLUME = 100}; + +class mda_recorder; +struct ausrc_st { + struct ausrc *as; /* inheritance */ + mda_recorder *mda; + ausrc_read_h *rh; + void *arg; +}; + + +class mda_recorder : public MMdaAudioInputStreamCallback, public CBase +{ +public: + mda_recorder(struct ausrc_st *st, struct ausrc_prm *prm); + ~mda_recorder(); + + /* from MMdaAudioInputStreamCallback */ + virtual void MaiscOpenComplete(TInt aError); + virtual void MaiscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaiscRecordComplete(TInt aError); + +private: + CMdaAudioInputStream *iInput; + TMdaAudioDataSettings iSettings; + TBool iIsReady; + TBuf8<320> iBuf; + struct ausrc_st *state; +}; + + +mda_recorder::mda_recorder(struct ausrc_st *st, struct ausrc_prm *prm) + :iIsReady(EFalse) +{ + state = st; + + iInput = CMdaAudioInputStream::NewL(*this); + + iSettings.iSampleRate = convert_srate(prm->srate); + iSettings.iChannels = convert_channels(prm->ch); + iSettings.iVolume = VOLUME; + + iInput->Open(&iSettings); +} + + +mda_recorder::~mda_recorder() +{ + if (iInput) { + iInput->Stop(); + delete iInput; + } +} + + +void mda_recorder::MaiscOpenComplete(TInt aError) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaiscOpenComplete %d\n", aError); + return; + } + + iInput->SetGain(iInput->MaxGain()); + iInput->SetAudioPropertiesL(iSettings.iSampleRate, + iSettings.iChannels); + iInput->SetPriority(EMdaPriorityNormal, + EMdaPriorityPreferenceTime); + + TRAPD(ret, iInput->ReadL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("ReadL left with %d\n", ret); + } +} + + +void mda_recorder::MaiscBufferCopied(TInt aError, const TDesC8& aBuffer) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaiscBufferCopied: error=%d %d bytes\n", + aError, aBuffer.Length()); + return; + } + + state->rh(aBuffer.Ptr(), aBuffer.Length(), state->arg); + + iBuf.Zero(); + TRAPD(ret, iInput->ReadL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("ReadL left with %d\n", ret); + } +} + + +void mda_recorder::MaiscRecordComplete(TInt aError) +{ + DEBUG_NOTICE("MaiscRecordComplete: error=%d\n", aError); + +#if 0 + if (KErrOverflow == aError) { + + /* re-open input stream */ + iInput->Stop(); + iInput->Open(&iSettings); + } +#endif +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = (struct ausrc_st *)arg; + + delete st->mda; + + mem_deref(st->as); +} + + +int mda_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err = 0; + + (void)ctx; + (void)device; + (void)errh; + + st = (struct ausrc_st *)mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = (struct ausrc *)mem_ref(as); + st->rh = rh; + st->arg = arg; + + st->mda = new mda_recorder(st, prm); + if (!st->mda) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/mda/util.cpp b/modules/mda/util.cpp new file mode 100644 index 0000000..b879c7f --- /dev/null +++ b/modules/mda/util.cpp @@ -0,0 +1,39 @@ +/** + * @file util.cpp Symbian MDA audio driver -- utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" +} + + +int convert_srate(uint32_t srate) +{ + switch (srate) { + + case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz; + case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz; + case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz; + case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz; + case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz; + default: return -1; + } +} + + +int convert_channels(uint8_t ch) +{ + switch (ch) { + + case 1: return TMdaAudioDataSettings::EChannelsMono; + case 2: return TMdaAudioDataSettings::EChannelsStereo; + default: return -1; + } +} diff --git a/modules/menu/menu.c b/modules/menu/menu.c new file mode 100644 index 0000000..583fb69 --- /dev/null +++ b/modules/menu/menu.c @@ -0,0 +1,618 @@ +/** + * @file menu.c Interactive menu + * + * Copyright (C) 2010 Creytiv.com + */ +#include <time.h> +#include <re.h> +#include <baresip.h> + + +/** Defines the status modes */ +enum statmode { + STATMODE_CALL = 0, + STATMODE_OFF, +}; + + +static uint64_t start_ticks; /**< Ticks when app started */ +static time_t start_time; /**< Start time of application */ +static struct tmr tmr_alert; /**< Incoming call alert timer */ +static struct tmr tmr_stat; /**< Call status timer */ +static enum statmode statmode; /**< Status mode */ +static struct mbuf *dialbuf; /**< Buffer for dialled number */ +static struct le *le_cur; /**< Current User-Agent (struct ua) */ + + +static void menu_set_incall(bool incall); +static void update_callstatus(void); + + +static void check_registrations(void) +{ + static bool ual_ready = false; + struct le *le; + uint32_t n; + + if (ual_ready) + return; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + + if (!ua_isregistered(ua)) + return; + } + + n = list_count(uag_list()); + + /* We are ready */ + (void)re_printf("\x1b[32mAll %u useragent%s registered successfully!" + " (%u ms)\x1b[;m\n", + n, n==1 ? "" : "s", + (uint32_t)(tmr_jiffies() - start_ticks)); + + ual_ready = true; +} + + +/** + * Return the current User-Agent in focus + * + * @return Current User-Agent + */ +static struct ua *uag_cur(void) +{ + if (list_isempty(uag_list())) + return NULL; + + if (!le_cur) + le_cur = list_head(uag_list()); + + return list_ledata(le_cur); +} + + +/* Return TRUE if there are any active calls for any UAs */ +static bool have_active_calls(void) +{ + struct le *le; + + for (le = list_head(uag_list()); le; le = le->next) { + + struct ua *ua = le->data; + + if (ua_call(ua)) + return true; + } + + return false; +} + + +static int print_system_info(struct re_printf *pf, void *arg) +{ + uint32_t uptime; + int err = 0; + + (void)arg; + + uptime = (uint32_t)((long long)(tmr_jiffies() - start_ticks)/1000); + + err |= re_hprintf(pf, "\n--- System info: ---\n"); + + err |= re_hprintf(pf, " Machine: %s/%s\n", sys_arch_get(), + sys_os_get()); + err |= re_hprintf(pf, " Version: %s (libre v%s)\n", + BARESIP_VERSION, sys_libre_version_get()); + err |= re_hprintf(pf, " Build: %H\n", sys_build_get, NULL); + err |= re_hprintf(pf, " Kernel: %H\n", sys_kernel_get, NULL); + err |= re_hprintf(pf, " Uptime: %H\n", fmt_human_time, &uptime); + err |= re_hprintf(pf, " Started: %s", ctime(&start_time)); + +#ifdef __VERSION__ + err |= re_hprintf(pf, " Compiler: %s\n", __VERSION__); +#endif + + return err; +} + + +/** + * Print the SIP Registration for all User-Agents + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +static int ua_print_reg_status(struct re_printf *pf, void *unused) +{ + struct le *le; + int err; + + (void)unused; + + err = re_hprintf(pf, "\n--- Useragents: %u ---\n", + list_count(uag_list())); + + for (le = list_head(uag_list()); le && !err; le = le->next) { + const struct ua *ua = le->data; + + err = re_hprintf(pf, "%s ", ua == uag_cur() ? ">" : " "); + err |= ua_print_status(pf, ua); + } + + err |= re_hprintf(pf, "\n"); + + return err; +} + + +/** + * Print the current SIP Call status for the current User-Agent + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +static int ua_print_call_status(struct re_printf *pf, void *unused) +{ + struct call *call; + int err; + + (void)unused; + + call = ua_call(uag_cur()); + if (call) { + err = re_hprintf(pf, "\n%H\n", call_debug, call); + } + else { + err = re_hprintf(pf, "\n(no active calls)\n"); + } + + return err; +} + + +static int dial_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err = 0; + + (void)pf; + + if (str_isset(carg->prm)) { + + mbuf_rewind(dialbuf); + (void)mbuf_write_str(dialbuf, carg->prm); + + err = ua_connect(uag_cur(), NULL, NULL, + carg->prm, NULL, VIDMODE_ON); + } + else if (dialbuf->end > 0) { + + char *uri; + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return err; + + err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON); + + mem_deref(uri); + } + + if (err) { + warning("menu: ua_connect failed: %m\n", err); + } + + return err; +} + + +static int cmd_answer(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + ua_answer(uag_cur(), NULL); + + return 0; +} + + +static int cmd_hangup(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + ua_hangup(uag_cur(), NULL, 0, NULL); + + /* note: must be called after ua_hangup() */ + menu_set_incall(have_active_calls()); + + return 0; +} + + +static int cmd_ua_next(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + if (!le_cur) + le_cur = list_head(uag_list()); + + le_cur = le_cur->next ? le_cur->next : list_head(uag_list()); + + (void)re_fprintf(stderr, "ua: %s\n", ua_aor(list_ledata(le_cur))); + + uag_current_set(list_ledata(le_cur)); + + update_callstatus(); + + return 0; +} + + +static int cmd_ua_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_debug(pf, uag_cur()); +} + + +static int cmd_print_calls(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_print_calls(pf, uag_cur()); +} + + +static int cmd_config_print(struct re_printf *pf, void *unused) +{ + (void)unused; + return config_print(pf, conf_config()); +} + + +static const struct cmd cmdv[] = { + {'M', 0, "Main loop debug", re_debug }, + {'\n', 0, "Accept incoming call", cmd_answer }, + {'b', 0, "Hangup call", cmd_hangup }, + {'c', 0, "Call status", ua_print_call_status }, + {'d', CMD_PRM, "Dial", dial_handler }, + {'h', 0, "Help menu", cmd_print }, + {'i', 0, "SIP debug", ua_print_sip_status }, + {'l', 0, "List active calls", cmd_print_calls }, + {'m', 0, "Module debug", mod_debug }, + {'n', 0, "Network debug", net_debug }, + {'r', 0, "Registration info", ua_print_reg_status }, + {'s', 0, "System info", print_system_info }, + {'t', 0, "Timer debug", tmr_status }, + {'u', 0, "UA debug", cmd_ua_debug }, + {'y', 0, "Memory status", mem_status }, + {0x1b, 0, "Hangup call", cmd_hangup }, + {' ', 0, "Toggle UAs", cmd_ua_next }, + {'g', 0, "Print configuration", cmd_config_print }, + + {'#', CMD_PRM, NULL, dial_handler }, + {'*', CMD_PRM, NULL, dial_handler }, + {'0', CMD_PRM, NULL, dial_handler }, + {'1', CMD_PRM, NULL, dial_handler }, + {'2', CMD_PRM, NULL, dial_handler }, + {'3', CMD_PRM, NULL, dial_handler }, + {'4', CMD_PRM, NULL, dial_handler }, + {'5', CMD_PRM, NULL, dial_handler }, + {'6', CMD_PRM, NULL, dial_handler }, + {'7', CMD_PRM, NULL, dial_handler }, + {'8', CMD_PRM, NULL, dial_handler }, + {'9', CMD_PRM, NULL, dial_handler }, +}; + + +static int call_audio_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return audio_debug(pf, call_audio(ua_call(uag_cur()))); +} + + +static int call_audioenc_cycle(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + audio_encoder_cycle(call_audio(ua_call(uag_cur()))); + return 0; +} + + +static int call_reinvite(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + return call_modify(ua_call(uag_cur())); +} + + +static int call_mute(struct re_printf *pf, void *unused) +{ + static bool muted = false; + (void)unused; + + muted = !muted; + (void)re_hprintf(pf, "\ncall %smuted\n", muted ? "" : "un-"); + audio_mute(call_audio(ua_call(uag_cur())), muted); + + return 0; +} + + +static int call_xfer(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + static bool xfer_inprogress; + + if (!xfer_inprogress && !carg->complete) { + statmode = STATMODE_OFF; + re_hprintf(pf, "\rPlease enter transfer target SIP uri:\n"); + } + + xfer_inprogress = true; + + if (carg->complete) { + statmode = STATMODE_CALL; + xfer_inprogress = false; + return call_transfer(ua_call(uag_cur()), carg->prm); + } + + return 0; +} + + +static int call_holdresume(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + (void)pf; + + return call_hold(ua_call(uag_cur()), 'x' == carg->key); +} + + +#ifdef USE_VIDEO +static int call_videoenc_cycle(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + video_encoder_cycle(call_video(ua_call(uag_cur()))); + return 0; +} + + +static int call_video_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return video_debug(pf, call_video(ua_call(uag_cur()))); +} +#endif + + +static int digit_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct call *call; + int err = 0; + + (void)pf; + + call = ua_call(uag_cur()); + if (call) + err = call_send_digit(call, carg->key); + + return err; +} + + +static int toggle_statmode(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + if (statmode == STATMODE_OFF) + statmode = STATMODE_CALL; + else + statmode = STATMODE_OFF; + + return 0; +} + + +static const struct cmd callcmdv[] = { + {'I', 0, "Send re-INVITE", call_reinvite }, + {'X', 0, "Call resume", call_holdresume }, + {'a', 0, "Audio stream", call_audio_debug }, + {'e', 0, "Cycle audio encoder", call_audioenc_cycle }, + {'m', 0, "Call mute/un-mute", call_mute }, + {'r', CMD_IPRM,"Transfer call", call_xfer }, + {'x', 0, "Call hold", call_holdresume }, + +#ifdef USE_VIDEO + {'E', 0, "Cycle video encoder", call_videoenc_cycle }, + {'v', 0, "Video stream", call_video_debug }, +#endif + + {'#', 0, NULL, digit_handler }, + {'*', 0, NULL, digit_handler }, + {'0', 0, NULL, digit_handler }, + {'1', 0, NULL, digit_handler }, + {'2', 0, NULL, digit_handler }, + {'3', 0, NULL, digit_handler }, + {'4', 0, NULL, digit_handler }, + {'5', 0, NULL, digit_handler }, + {'6', 0, NULL, digit_handler }, + {'7', 0, NULL, digit_handler }, + {'8', 0, NULL, digit_handler }, + {'9', 0, NULL, digit_handler }, + {0x00, 0, NULL, digit_handler }, + + {'S', 0, "Statusmode toggle", toggle_statmode }, +}; + + +static void menu_set_incall(bool incall) +{ + /* Dynamic menus */ + if (incall) { + (void)cmd_register(callcmdv, ARRAY_SIZE(callcmdv)); + } + else { + cmd_unregister(callcmdv); + } +} + + +static void tmrstat_handler(void *arg) +{ + struct call *call; + (void)arg; + + /* the UI will only show the current active call */ + call = ua_call(uag_cur()); + if (!call) + return; + + tmr_start(&tmr_stat, 100, tmrstat_handler, 0); + + if (STATMODE_OFF != statmode) { + (void)re_fprintf(stderr, "%H\r", call_status, call); + } +} + + +static void update_callstatus(void) +{ + /* if there are any active calls, enable the call status view */ + if (have_active_calls()) + tmr_start(&tmr_stat, 100, tmrstat_handler, 0); + else + tmr_cancel(&tmr_stat); +} + + +static void alert_start(void *arg) +{ + (void)arg; + + ui_output("\033[10;1000]\033[11;1000]\a"); + + tmr_start(&tmr_alert, 1000, alert_start, NULL); +} + + +static void alert_stop(void) +{ + if (tmr_isrunning(&tmr_alert)) + ui_output("\r"); + + tmr_cancel(&tmr_alert); +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + (void)call; + (void)prm; + (void)arg; + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + info("%s: Incoming call from: %s %s -" + " (press ENTER to accept)\n", + ua_aor(ua), call_peername(call), call_peeruri(call)); + alert_start(0); + break; + + case UA_EVENT_CALL_ESTABLISHED: + case UA_EVENT_CALL_CLOSED: + alert_stop(); + break; + + case UA_EVENT_REGISTER_OK: + check_registrations(); + break; + + case UA_EVENT_UNREGISTERING: + return; + + default: + break; + } + + menu_set_incall(have_active_calls()); + update_callstatus(); +} + + +static void message_handler(const struct pl *peer, const struct pl *ctype, + struct mbuf *body, void *arg) +{ + (void)ctype; + (void)arg; + + (void)re_fprintf(stderr, "\r%r: \"%b\"\n", peer, + mbuf_buf(body), mbuf_get_left(body)); + + (void)play_file(NULL, "message.wav", 0); +} + + +static int module_init(void) +{ + int err; + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + start_ticks = tmr_jiffies(); + (void)time(&start_time); + tmr_init(&tmr_alert); + statmode = STATMODE_CALL; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + err |= uag_event_register(ua_event_handler, NULL); + + err |= message_init(message_handler, NULL); + + return err; +} + + +static int module_close(void) +{ + message_close(); + uag_event_unregister(ua_event_handler); + cmd_unregister(cmdv); + + menu_set_incall(false); + tmr_cancel(&tmr_alert); + tmr_cancel(&tmr_stat); + dialbuf = mem_deref(dialbuf); + + le_cur = NULL; + + return 0; +} + + +const struct mod_export DECL_EXPORTS(menu) = { + "menu", + "application", + module_init, + module_close +}; diff --git a/modules/menu/module.mk b/modules/menu/module.mk new file mode 100644 index 0000000..d727f4b --- /dev/null +++ b/modules/menu/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := menu +$(MOD)_SRCS += menu.c + +include mk/mod.mk diff --git a/modules/mwi/module.mk b/modules/mwi/module.mk new file mode 100644 index 0000000..f6d1bde --- /dev/null +++ b/modules/mwi/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := mwi +$(MOD)_SRCS += mwi.c + +include mk/mod.mk diff --git a/modules/mwi/mwi.c b/modules/mwi/mwi.c new file mode 100644 index 0000000..21aca08 --- /dev/null +++ b/modules/mwi/mwi.c @@ -0,0 +1,141 @@ +/** + * @file mwi.c Message Waiting Indication (RFC 3842) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +struct mwi { + struct le le; + struct sipsub *sub; + struct ua *ua; +}; + +static struct tmr tmr; +static struct list mwil; + + +static void destructor(void *arg) +{ + struct mwi *mwi = arg; + + list_unlink(&mwi->le); + mem_deref(mwi->sub); +} + + +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 void notify_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct mwi *mwi = arg; + + if (mbuf_get_left(msg->mb)) { + re_printf("----- MWI for %s -----\n", ua_aor(mwi->ua)); + re_printf("%b\n", mbuf_buf(msg->mb), mbuf_get_left(msg->mb)); + } + + (void)sip_treply(NULL, sip, msg, 200, "OK"); +} + + +static void close_handler(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, + void *arg) +{ + struct mwi *mwi = arg; + (void)substate; + + info("mwi: subscription for %s closed: %s (%u %r)\n", + ua_aor(mwi->ua), + err ? strerror(err) : "", + err ? 0 : msg->scode, + err ? 0 : &msg->reason); + + mem_deref(mwi); +} + + +static int mwi_subscribe(struct ua *ua) +{ + const char *routev[1]; + struct mwi *mwi; + int err; + + mwi = mem_zalloc(sizeof(*mwi), destructor); + if (!mwi) + return ENOMEM; + + list_append(&mwil, &mwi->le, mwi); + mwi->ua = ua; + + routev[0] = ua_outbound(ua); + + info("mwi: subscribing to messages for %s\n", ua_aor(ua)); + + err = sipevent_subscribe(&mwi->sub, uag_sipevent_sock(), ua_aor(ua), + NULL, ua_aor(ua), "message-summary", NULL, + 600, ua_cuser(ua), + routev, routev[0] ? 1 : 0, + auth_handler, ua_prm(ua), true, NULL, + notify_handler, close_handler, mwi, + "Accept:" + " application/simple-message-summary\r\n"); + if (err) { + warning("mwi: subscribe ERROR: %m\n", err); + } + + if (err) + mem_deref(mwi); + + return err; +} + + +static void tmr_handler(void *arg) +{ + struct le *le; + + (void)arg; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + mwi_subscribe(ua); + } +} + + +static int module_init(void) +{ + list_init(&mwil); + tmr_start(&tmr, 10, tmr_handler, 0); + + return 0; +} + + +static int module_close(void) +{ + tmr_cancel(&tmr); + list_flush(&mwil); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(mwi) = { + "mwi", + "application", + module_init, + module_close, +}; diff --git a/modules/natbd/module.mk b/modules/natbd/module.mk new file mode 100644 index 0000000..d6da3f9 --- /dev/null +++ b/modules/natbd/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := natbd +$(MOD)_SRCS += natbd.c + +include mk/mod.mk diff --git a/modules/natbd/natbd.c b/modules/natbd/natbd.c new file mode 100644 index 0000000..6711961 --- /dev/null +++ b/modules/natbd/natbd.c @@ -0,0 +1,508 @@ +/** + * @file natbd.c NAT Behavior Discovery Module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "natbd" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* + * NAT Behavior Discovery Using STUN (RFC 5780) + * + * This module is only for diagnostics purposes and does not affect + * the main SIP client. It uses the NATBD api in libre to detect the + * NAT Behaviour, by sending STUN packets to a STUN server. Both + * protocols UDP and TCP are supported. + */ + + +struct natbd { + struct nat_hairpinning *nh; + struct nat_filtering *nf; + struct nat_lifetime *nl; + struct nat_mapping *nm; + struct nat_genalg *ga; + struct stun_dns *dns; + struct sa stun_srv; + struct tmr tmr; + char host[256]; + uint16_t port; + uint32_t interval; + bool terminated; + int proto; + int res_hp; + enum nat_type res_nm; + enum nat_type res_nf; + struct nat_lifetime_interval res_nl; + uint32_t n_nl; + int status_ga; +}; + +static struct natbd *natbdv[2]; + + +static const char *hairpinning_str(int res_hp) +{ + switch (res_hp) { + + case -1: return "Unknown"; + case 0: return "Not Supported"; + default: return "Supported"; + } +} + + +static const char *genalg_str(int status) +{ + switch (status) { + + case -1: return "Not Detected"; + case 0: return "Unknown"; + case 1: return "Detected"; + default: return "???"; + } +} + + +static int natbd_status(struct re_printf *pf, void *arg) +{ + const struct natbd *natbd = arg; + int err; + + if (!pf || !natbd) + return 0; + + err = re_hprintf(pf, "NAT Binding Discovery (using %s:%J)\n", + net_proto2name(natbd->proto), + &natbd->stun_srv); + err |= re_hprintf(pf, " Hairpinning: %s\n", + hairpinning_str(natbd->res_hp)); + err |= re_hprintf(pf, " Mapping: %s\n", + nat_type_str(natbd->res_nm)); + if (natbd->proto == IPPROTO_UDP) { + err |= re_hprintf(pf, " Filtering: %s\n", + nat_type_str(natbd->res_nf)); + err |= re_hprintf(pf, " Lifetime: min=%u cur=%u max=%u" + " (%u probes)\n", natbd->res_nl.min, + natbd->res_nl.cur, natbd->res_nl.max, + natbd->n_nl); + } + err |= re_hprintf(pf, " Generic ALG: %s\n", + genalg_str(natbd->status_ga)); + + return err; +} + + +static void nat_hairpinning_handler(int err, bool supported, void *arg) +{ + struct natbd *natbd = arg; + const int res_hp = (0 == err) ? supported : -1; + + if (natbd->terminated) + return; + + if (res_hp != natbd->res_hp) { + info("NAT Hairpinning %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + hairpinning_str(natbd->res_hp), + hairpinning_str(res_hp)); + } + + natbd->res_hp = res_hp; + + natbd->nh = mem_deref(natbd->nh); +} + + +static void nat_mapping_handler(int err, enum nat_type type, void *arg) +{ + struct natbd *natbd = arg; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("NAT mapping failed (%m)\n", err); + goto out; + } + + if (type != natbd->res_nm) { + info("NAT Mapping %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + nat_type_str(natbd->res_nm), + nat_type_str(type)); + } + + natbd->res_nm = type; + + out: + natbd->nm = mem_deref(natbd->nm); +} + + +static void nat_filtering_handler(int err, enum nat_type type, void *arg) +{ + struct natbd *natbd = arg; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("NAT filtering failed (%m)\n", err); + goto out; + } + + if (type != natbd->res_nf) { + info("NAT Filtering %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + nat_type_str(natbd->res_nf), + nat_type_str(type)); + } + + natbd->res_nf = type; + + out: + natbd->nf = mem_deref(natbd->nf); +} + + +static void nat_lifetime_handler(int err, + const struct nat_lifetime_interval *interval, + void *arg) +{ + struct natbd *natbd = arg; + + ++natbd->n_nl; + + if (err) { + DEBUG_WARNING("nat_lifetime_handler: (%m)\n", err); + return; + } + + natbd->res_nl = *interval; + + info("NAT Binding lifetime for %s: min=%u cur=%u max=%u\n", + net_proto2name(natbd->proto), + interval->min, interval->cur, interval->max); +} + + +static void nat_genalg_handler(int err, uint16_t scode, const char *reason, + int status, const struct sa *map, + void *arg) +{ + struct natbd *natbd = arg; + + (void)map; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("Generic ALG detection failed: %m\n", err); + goto out; + } + else if (scode) { + DEBUG_WARNING("Generic ALG detection failed: %u %s\n", + scode, reason); + goto out; + } + + if (status != natbd->status_ga) { + info("Generic ALG for %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + genalg_str(natbd->status_ga), + genalg_str(status)); + } + + natbd->status_ga = status; + + out: + natbd->ga = mem_deref(natbd->ga); +} + + +static void destructor(void *arg) +{ + struct natbd *natbd = arg; + + natbd->terminated = true; + + tmr_cancel(&natbd->tmr); + mem_deref(natbd->dns); + mem_deref(natbd->nh); + mem_deref(natbd->nm); + mem_deref(natbd->nf); + mem_deref(natbd->nl); + mem_deref(natbd->ga); +} + + +static int natbd_start(struct natbd *natbd) +{ + int err = 0; + + if (!natbd->nh) { + err |= nat_hairpinning_alloc(&natbd->nh, &natbd->stun_srv, + natbd->proto, NULL, + nat_hairpinning_handler, natbd); + err |= nat_hairpinning_start(natbd->nh); + if (err) { + DEBUG_WARNING("nat_hairpinning_start() failed (%m)\n", + err); + } + } + + if (!natbd->nm) { + err |= nat_mapping_alloc(&natbd->nm, net_laddr_af(net_af()), + &natbd->stun_srv, natbd->proto, NULL, + nat_mapping_handler, natbd); + err |= nat_mapping_start(natbd->nm); + if (err) { + DEBUG_WARNING("nat_mapping_start() failed (%m)\n", + err); + } + } + + if (natbd->proto == IPPROTO_UDP) { + + if (!natbd->nf) { + err |= nat_filtering_alloc(&natbd->nf, + &natbd->stun_srv, NULL, + nat_filtering_handler, + natbd); + err |= nat_filtering_start(natbd->nf); + if (err) { + DEBUG_WARNING("nat_filtering_start() (%m)\n", + err); + } + } + } + + if (!natbd->ga) { + err |= nat_genalg_alloc(&natbd->ga, &natbd->stun_srv, + natbd->proto, NULL, + nat_genalg_handler, natbd); + + if (err) { + DEBUG_WARNING("natbd_init: %m\n", err); + } + err |= nat_genalg_start(natbd->ga); + if (err) { + DEBUG_WARNING("nat_genalg_start() failed (%m)\n", + err); + } + } + + return err; +} + + +static void timeout(void *arg) +{ + struct natbd *natbd = arg; + + info("%H\n", natbd_status, natbd); + + natbd_start(natbd); + + tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd); +} + + +static void dns_handler(int err, const struct sa *addr, void *arg) +{ + struct natbd *natbd = arg; + + if (err) { + DEBUG_WARNING("failed to resolve '%s' (%m)\n", + natbd->host, err); + goto out; + } + + info("natbd: resolved STUN-server for %s -- %J\n", + net_proto2name(natbd->proto), addr); + + sa_cpy(&natbd->stun_srv, addr); + + natbd_start(natbd); + + /* Lifetime discovery is a special test */ + if (natbd->proto == IPPROTO_UDP) { + + err = nat_lifetime_alloc(&natbd->nl, &natbd->stun_srv, 3, + NULL, nat_lifetime_handler, natbd); + err |= nat_lifetime_start(natbd->nl); + if (err) { + DEBUG_WARNING("nat_lifetime_start() failed (%m)\n", + err); + } + } + + tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd); + + out: + natbd->dns = mem_deref(natbd->dns); +} + + +static void timeout_init(void *arg) +{ + struct natbd *natbd = arg; + const char *proto_str; + int err = 0; + + if (sa_isset(&natbd->stun_srv, SA_ALL)) { + dns_handler(0, &natbd->stun_srv, natbd); + return; + } + + if (natbd->proto == IPPROTO_UDP) + proto_str = stun_proto_udp; + else if (natbd->proto == IPPROTO_TCP) + proto_str = stun_proto_tcp; + else { + err = EPROTONOSUPPORT; + goto out; + } + + err = stun_server_discover(&natbd->dns, net_dnsc(), + stun_usage_binding, + proto_str, net_af(), + natbd->host, natbd->port, + dns_handler, natbd); + if (err) + goto out; + + out: + if (err) { + DEBUG_WARNING("timeout_init: %m\n", err); + } +} + + +static int natbd_alloc(struct natbd **natbdp, uint32_t interval, + int proto, const char *server) +{ + struct pl host, port; + struct natbd *natbd; + int err = 0; + + if (!natbdp || !interval || !proto || !server) + return EINVAL; + + natbd = mem_zalloc(sizeof(*natbd), destructor); + if (!natbd) + return ENOMEM; + + natbd->interval = interval; + natbd->proto = proto; + natbd->res_hp = -1; + + if (0 == sa_decode(&natbd->stun_srv, server, str_len(server))) { + ; + } + else if (0 == re_regex(server, str_len(server), "[^:]+[:]*[^]*", + &host, NULL, &port)) { + + pl_strcpy(&host, natbd->host, sizeof(natbd->host)); + natbd->port = pl_u32(&port); + } + else { + DEBUG_WARNING("failed to decode natbd_server (%s)\n", + server); + err = EINVAL; + goto out; + } + + tmr_start(&natbd->tmr, 1, timeout_init, natbd); + + out: + if (err) + mem_deref(natbd); + else + *natbdp = natbd; + + return err; +} + + +static int status(struct re_printf *pf, void *unused) +{ + size_t i; + int err = 0; + + (void)unused; + + for (i=0; i<ARRAY_SIZE(natbdv); i++) { + + if (natbdv[i]) + err |= natbd_status(pf, natbdv[i]); + } + + return err; +} + + +static const struct cmd cmdv[] = { + {'z', 0, "NAT status", status} +}; + + +static int module_init(void) +{ + char server[256] = ""; + uint32_t interval = 3600; + int err; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + (void)conf_get_u32(conf_cur(), "natbd_interval", &interval); + (void)conf_get_str(conf_cur(), "natbd_server", server, sizeof(server)); + + if (!server[0]) { + DEBUG_WARNING("missing config 'natbd_server'\n"); + return EINVAL; + } + + info("natbd: Enable NAT Behavior Discovery using STUN server %s\n", + server); + + err |= natbd_alloc(&natbdv[0], interval, IPPROTO_UDP, server); + err |= natbd_alloc(&natbdv[1], interval, IPPROTO_TCP, server); + if (err) { + DEBUG_WARNING("failed to allocate natbd state: %m\n", err); + } + + return err; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(natbdv); i++) + natbdv[i] = mem_deref(natbdv[i]); + + cmd_unregister(cmdv); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(natbd) = { + "natbd", + "natbd", + module_init, + module_close, +}; diff --git a/modules/natpmp/libnatpmp.c b/modules/natpmp/libnatpmp.c new file mode 100644 index 0000000..7969007 --- /dev/null +++ b/modules/natpmp/libnatpmp.c @@ -0,0 +1,235 @@ +/** + * @file libnatpmp.c NAT-PMP Client library + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "libnatpmp.h" + + +enum { + NATPMP_DELAY = 250, + NATPMP_MAXTX = 9, +}; + +struct natpmp_req { + struct natpmp_req **npp; + struct udp_sock *us; + struct tmr tmr; + struct mbuf *mb; + struct sa srv; + unsigned n; + natpmp_resp_h *resph; + void *arg; +}; + + +static void completed(struct natpmp_req *np, int err, + const struct natpmp_resp *resp) +{ + natpmp_resp_h *resph = np->resph; + void *arg = np->arg; + + tmr_cancel(&np->tmr); + + if (np->npp) { + *np->npp = NULL; + np->npp = NULL; + } + + np->resph = NULL; + + /* must be destroyed before calling handler */ + mem_deref(np); + + if (resph) + resph(err, resp, arg); +} + + +static void destructor(void *arg) +{ + struct natpmp_req *np = arg; + + tmr_cancel(&np->tmr); + mem_deref(np->us); + mem_deref(np->mb); +} + + +static void timeout(void *arg) +{ + struct natpmp_req *np = arg; + int err; + + if (np->n > NATPMP_MAXTX) { + completed(np, ETIMEDOUT, NULL); + return; + } + + tmr_start(&np->tmr, NATPMP_DELAY<<np->n, timeout, arg); + +#if 1 + debug("natpmp: {n=%u} tx %u bytes\n", np->n, np->mb->end); +#endif + + np->n++; + + np->mb->pos = 0; + err = udp_send(np->us, &np->srv, np->mb); + if (err) { + completed(np, err, NULL); + } +} + + +static int resp_decode(struct natpmp_resp *resp, struct mbuf *mb) +{ + resp->vers = mbuf_read_u8(mb); + resp->op = mbuf_read_u8(mb); + resp->result = ntohs(mbuf_read_u16(mb)); + resp->epoch = ntohl(mbuf_read_u32(mb)); + + if (!(resp->op & 0x80)) + return EPROTO; + resp->op &= ~0x80; + + switch (resp->op) { + + case NATPMP_OP_EXTERNAL: + resp->u.ext_addr = ntohl(mbuf_read_u32(mb)); + break; + + case NATPMP_OP_MAPPING_UDP: + case NATPMP_OP_MAPPING_TCP: + resp->u.map.int_port = ntohs(mbuf_read_u16(mb)); + resp->u.map.ext_port = ntohs(mbuf_read_u16(mb)); + resp->u.map.lifetime = ntohl(mbuf_read_u32(mb)); + break; + + default: + warning("natmap: unknown opcode %d\n", resp->op); + return EBADMSG; + } + + return 0; +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct natpmp_req *np = arg; + struct natpmp_resp resp; + + if (!sa_cmp(src, &np->srv, SA_ALL)) + return; + + if (resp_decode(&resp, mb)) + return; + + completed(np, 0, &resp); +} + + +static int natpmp_init(struct natpmp_req *np, const struct sa *srv, + uint8_t opcode, natpmp_resp_h *resph, void *arg) +{ + int err; + + if (!np || !srv) + return EINVAL; + + /* a new UDP socket for each NAT-PMP request */ + err = udp_listen(&np->us, NULL, udp_recv, np); + if (err) + return err; + + np->srv = *srv; + np->resph = resph; + np->arg = arg; + + udp_connect(np->us, srv); + + np->mb = mbuf_alloc(512); + if (!np->mb) + return ENOMEM; + + err |= mbuf_write_u8(np->mb, NATPMP_VERSION); + err |= mbuf_write_u8(np->mb, opcode); + + return err; +} + + +int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv, + natpmp_resp_h *resph, void *arg) +{ + struct natpmp_req *np; + int err; + + np = mem_zalloc(sizeof(*np), destructor); + if (!np) + return ENOMEM; + + err = natpmp_init(np, srv, NATPMP_OP_EXTERNAL, resph, arg); + if (err) + goto out; + + timeout(np); + + out: + if (err) + mem_deref(np); + else if (npp) { + np->npp = npp; + *npp = np; + } + else { + /* Destroy the transaction now */ + mem_deref(np); + } + + return err; +} + + +int natpmp_mapping_request(struct natpmp_req **npp, const struct sa *srv, + uint16_t int_port, uint16_t ext_port, + uint32_t lifetime, natpmp_resp_h *resph, void *arg) +{ + struct natpmp_req *np; + int err; + + np = mem_zalloc(sizeof(*np), destructor); + if (!np) + return ENOMEM; + + err = natpmp_init(np, srv, NATPMP_OP_MAPPING_UDP, resph, arg); + if (err) + goto out; + + err |= mbuf_write_u16(np->mb, 0x0000); + err |= mbuf_write_u16(np->mb, htons(int_port)); + err |= mbuf_write_u16(np->mb, htons(ext_port)); + err |= mbuf_write_u32(np->mb, htonl(lifetime)); + if (err) + goto out; + + timeout(np); + + out: + if (err) + mem_deref(np); + else if (npp) { + np->npp = npp; + *npp = np; + } + else { + /* Destroy the transaction now */ + mem_deref(np); + } + + return err; +} diff --git a/modules/natpmp/libnatpmp.h b/modules/natpmp/libnatpmp.h new file mode 100644 index 0000000..d1cc33c --- /dev/null +++ b/modules/natpmp/libnatpmp.h @@ -0,0 +1,53 @@ +/** + * @file libnatpmp.h Interface to NAT-PMP Client library + * + * Copyright (C) 2010 Creytiv.com + */ + + +enum { + NATPMP_VERSION = 0, + NATPMP_PORT = 5351, +}; + +enum natpmp_op { + NATPMP_OP_EXTERNAL = 0, + NATPMP_OP_MAPPING_UDP = 1, + NATPMP_OP_MAPPING_TCP = 2, +}; + +enum natpmp_result { + NATPMP_SUCCESS = 0, + NATPMP_UNSUP_VERSION = 1, + NATPMP_REFUSED = 2, + NATPMP_NETWORK_FAILURE = 3, + NATPMP_OUT_OF_RESOURCES = 4, + NATPMP_UNSUP_OPCODE = 5 +}; + +struct natpmp_resp { + uint8_t vers; + uint8_t op; + uint16_t result; + uint32_t epoch; + + union { + uint32_t ext_addr; + struct { + uint16_t int_port; + uint16_t ext_port; + uint32_t lifetime; + } map; + } u; +}; + +struct natpmp_req; + +typedef void (natpmp_resp_h)(int err, const struct natpmp_resp *resp, + void *arg); + +int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv, + natpmp_resp_h *h, void *arg); +int natpmp_mapping_request(struct natpmp_req **natpmpp, const struct sa *srv, + uint16_t int_port, uint16_t ext_port, + uint32_t lifetime, natpmp_resp_h *resph, void *arg); diff --git a/modules/natpmp/module.mk b/modules/natpmp/module.mk new file mode 100644 index 0000000..3087c77 --- /dev/null +++ b/modules/natpmp/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := natpmp +$(MOD)_SRCS += natpmp.c libnatpmp.c + +include mk/mod.mk diff --git a/modules/natpmp/natpmp.c b/modules/natpmp/natpmp.c new file mode 100644 index 0000000..04b45f0 --- /dev/null +++ b/modules/natpmp/natpmp.c @@ -0,0 +1,313 @@ +/** + * @file natpmp.c NAT-PMP Module for Media NAT-traversal + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "libnatpmp.h" + + +/** + * @defgroup natpmp natpmp + * + * NAT Port Mapping Protocol (NAT-PMP) + * + * https://tools.ietf.org/html/rfc6886 + */ + +enum { + LIFETIME = 300 /* seconds */ +}; + +struct mnat_sess { + struct list medial; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct natpmp_req *natpmp; + struct tmr tmr; + uint16_t int_port; + uint32_t lifetime; + bool granted; +}; + + +static struct mnat *mnat; +static struct sa natpmp_srv, natpmp_extaddr; +static struct natpmp_req *natpmp_ext; + + +static void natpmp_resp_handler(int err, const struct natpmp_resp *resp, + void *arg); + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + /* Destroy the mapping */ + if (m->granted) { + (void)natpmp_mapping_request(NULL, &natpmp_srv, + m->int_port, 0, 0, NULL, NULL); + } + + list_unlink(&m->le); + tmr_cancel(&m->tmr); + mem_deref(m->sdpm); + mem_deref(m->natpmp); +} + + +static void is_complete(struct mnat_sess *sess) +{ + struct le *le; + + for (le = sess->medial.head; le; le = le->next) { + + struct mnat_media *m = le->data; + + if (!m->granted) + return; + } + + if (sess->estabh) { + sess->estabh(0, 0, "done", sess->arg); + + sess->estabh = NULL; + } +} + + +static void refresh_timeout(void *arg) +{ + struct mnat_media *m = arg; + + m->natpmp = mem_deref(m->natpmp); + (void)natpmp_mapping_request(&m->natpmp, &natpmp_srv, + m->int_port, 0, m->lifetime, + natpmp_resp_handler, m); +} + + +static void natpmp_resp_handler(int err, const struct natpmp_resp *resp, + void *arg) +{ + struct mnat_media *m = arg; + struct sa map_addr; + + if (err) { + warning("natpmp: response error: %m\n", err); + return; + } + + if (resp->op != NATPMP_OP_MAPPING_UDP) + return; + if (resp->result != NATPMP_SUCCESS) { + warning("natpmp: request failed with result code: %d\n", + resp->result); + return; + } + + if (resp->u.map.int_port != m->int_port) { + info("natpmp: ignoring response for internal_port=%u\n", + resp->u.map.int_port); + return; + } + + info("natpmp: mapping granted:" + " internal_port=%u, external_port=%u, lifetime=%u\n", + resp->u.map.int_port, resp->u.map.ext_port, + resp->u.map.lifetime); + + map_addr = natpmp_extaddr; + sa_set_port(&map_addr, resp->u.map.ext_port); + m->lifetime = resp->u.map.lifetime; + + /* Update SDP media with external IP-address mapping */ + sdp_media_set_laddr(m->sdpm, &map_addr); + + m->granted = true; + + tmr_start(&m->tmr, m->lifetime * 1000 * 3/4, refresh_timeout, m); + + is_complete(m->sess); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err = 0; + (void)af; + (void)port; + (void)user; + (void)pass; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->estabh = estabh; + sess->arg = arg; + + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + struct sa laddr; + int err = 0; + (void)sock2; + + if (!mp || !sess || !sdpm || proto != IPPROTO_UDP) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sess = sess; + m->sdpm = mem_ref(sdpm); + m->lifetime = LIFETIME; + + err = udp_local_get(sock1, &laddr); + if (err) + goto out; + + m->int_port = sa_port(&laddr); + + info("natpmp: local UDP port is %u\n", m->int_port); + + err = natpmp_mapping_request(&m->natpmp, &natpmp_srv, + m->int_port, 0, m->lifetime, + natpmp_resp_handler, m); + if (err) + goto out; + + out: + if (err) + mem_deref(m); + else { + *mp = m; + } + + return err; +} + + +static void extaddr_handler(int err, const struct natpmp_resp *resp, void *arg) +{ + (void)arg; + + if (err) { + warning("natpmp: external address ERROR: %m\n", err); + return; + } + + if (resp->result != NATPMP_SUCCESS) { + warning("natpmp: external address failed" + " with result code: %d\n", resp->result); + return; + } + + if (resp->op != NATPMP_OP_EXTERNAL) + return; + + sa_set_in(&natpmp_extaddr, resp->u.ext_addr, 0); + + info("natpmp: discovered External address: %j\n", &natpmp_extaddr); +} + + +static bool net_rt_handler(const char *ifname, const struct sa *dst, + int dstlen, const struct sa *gw, void *arg) +{ + (void)dstlen; + (void)arg; + + if (sa_af(dst) != AF_INET) + return false; + + if (sa_in(dst) == 0) { + natpmp_srv = *gw; + sa_set_port(&natpmp_srv, NATPMP_PORT); + info("natpmp: found default gateway %j on interface '%s'\n", + gw, ifname); + return true; + } + + return false; +} + + +static int module_init(void) +{ + int err; + + sa_init(&natpmp_srv, AF_INET); + sa_set_port(&natpmp_srv, NATPMP_PORT); + + net_rt_list(net_rt_handler, NULL); + + conf_get_sa(conf_cur(), "natpmp_server", &natpmp_srv); + + info("natpmp: using NAT-PMP server at %J\n", &natpmp_srv); + + err = natpmp_external_request(&natpmp_ext, &natpmp_srv, + extaddr_handler, NULL); + if (err) + return err; + + return mnat_register(&mnat, "natpmp", NULL, + session_alloc, media_alloc, NULL); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + natpmp_ext = mem_deref(natpmp_ext); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(natpmp) = { + "natpmp", + "mnat", + module_init, + module_close, +}; diff --git a/modules/opengl/module.mk b/modules/opengl/module.mk new file mode 100644 index 0000000..4b348ed --- /dev/null +++ b/modules/opengl/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opengl +$(MOD)_SRCS += opengl.m +$(MOD)_LFLAGS += -framework OpenGL -framework Cocoa -lobjc + +include mk/mod.mk diff --git a/modules/opengl/opengl.m b/modules/opengl/opengl.m new file mode 100644 index 0000000..d3ca972 --- /dev/null +++ b/modules/opengl/opengl.m @@ -0,0 +1,522 @@ +/** + * @file opengl.m Video driver for OpenGL on MacOSX + * + * Copyright (C) 2010 Creytiv.com + */ +#include <Cocoa/Cocoa.h> +#include <OpenGL/gl.h> +#include <OpenGL/glext.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + NSOpenGLContext *ctx; + NSWindow *win; + GLhandleARB PHandle; + char *prog; +}; + + +static struct vidisp *vid; /**< OPENGL Video-display */ + + +static const char *FProgram= + "uniform sampler2DRect Ytex;\n" + "uniform sampler2DRect Utex,Vtex;\n" + "void main(void) {\n" + " float nx,ny,r,g,b,y,u,v;\n" + " vec4 txl,ux,vx;" + " nx=gl_TexCoord[0].x;\n" + " ny=%d.0-gl_TexCoord[0].y;\n" + " y=texture2DRect(Ytex,vec2(nx,ny)).r;\n" + " u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n" + " v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n" + + " y=1.1643*(y-0.0625);\n" + " u=u-0.5;\n" + " v=v-0.5;\n" + + " r=y+1.5958*v;\n" + " g=y-0.39173*u-0.81290*v;\n" + " b=y+2.017*u;\n" + + " gl_FragColor=vec4(r,g,b,1.0);\n" + "}\n"; + + +static void destructor(void *arg) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + struct vidisp_st *st = arg; + + if (st->ctx) { + [st->ctx clearDrawable]; + [st->ctx release]; + } + + [st->win release]; + + if (st->PHandle) { + glUseProgramObjectARB(0); + glDeleteObjectARB(st->PHandle); + } + + mem_deref(st->prog); + + [pool release]; + + mem_deref(st->vd); +} + + +static int create_window(struct vidisp_st *st) +{ + NSRect rect = NSMakeRect(0, 0, 100, 100); + NSUInteger style; + + if (st->win) + return 0; + + style = NSTitledWindowMask | + NSClosableWindowMask | + NSMiniaturizableWindowMask; + + st->win = [[NSWindow alloc] initWithContentRect:rect + styleMask:style + backing:NSBackingStoreBuffered + defer:FALSE]; + if (!st->win) { + warning("opengl: could not create NSWindow\n"); + return ENOMEM; + } + + [st->win setLevel:NSFloatingWindowLevel]; + [st->win useOptimizedDrawing:YES]; + + return 0; +} + + +static void opengl_reset(struct vidisp_st *st, const struct vidsz *sz) +{ + if (st->PHandle) { + glUseProgramObjectARB(0); + glDeleteObjectARB(st->PHandle); + st->PHandle = 0; + st->prog = mem_deref(st->prog); + } + + st->size = *sz; +} + + +static int setup_shader(struct vidisp_st *st, int width, int height) +{ + GLhandleARB FSHandle, PHandle; + const char *progv[1]; + char buf[1024]; + int err, i; + + if (st->PHandle) + return 0; + + err = re_sdprintf(&st->prog, FProgram, height); + if (err) + return err; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, 0, height, -1, 1); + glViewport(0, 0, width, height); + glClearColor(0, 0, 0, 0); + glColor3f(1.0f, 0.84f, 0.0f); + glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + + /* Set up program objects. */ + PHandle = glCreateProgramObjectARB(); + FSHandle = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + + /* Compile the shader. */ + progv[0] = st->prog; + glShaderSourceARB(FSHandle, 1, progv, NULL); + glCompileShaderARB(FSHandle); + + /* Print the compilation log. */ + glGetObjectParameterivARB(FSHandle, GL_OBJECT_COMPILE_STATUS_ARB, &i); + if (i != 1) { + warning("opengl: shader compile failed\n"); + return ENOSYS; + } + + glGetInfoLogARB(FSHandle, sizeof(buf), NULL, buf); + + /* Create a complete program object. */ + glAttachObjectARB(PHandle, FSHandle); + glLinkProgramARB(PHandle); + + /* And print the link log. */ + glGetInfoLogARB(PHandle, sizeof(buf), NULL, buf); + + /* Finally, use the program. */ + glUseProgramObjectARB(PHandle); + + st->PHandle = PHandle; + + return 0; +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + NSOpenGLPixelFormatAttribute attr[] = { + NSOpenGLPFAColorSize, 32, + NSOpenGLPFADepthSize, 16, + NSOpenGLPFADoubleBuffer, + 0 + }; + NSOpenGLPixelFormat *fmt; + NSAutoreleasePool *pool; + struct vidisp_st *st; + GLint vsync = 1; + int err = 0; + + (void)dev; + (void)resizeh; + (void)arg; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return ENOMEM; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; + if (!fmt) { + err = ENOMEM; + warning("opengl: Failed creating OpenGL format\n"); + goto out; + } + + st->ctx = [[NSOpenGLContext alloc] initWithFormat:fmt + shareContext:nil]; + + [fmt release]; + + if (!st->ctx) { + err = ENOMEM; + warning("opengl: Failed creating OpenGL context\n"); + goto out; + } + + /* Use provided view, or create our own */ + if (prm && prm->view) { + [st->ctx setView:prm->view]; + } + else { + err = create_window(st); + if (err) + goto out; + + if (prm) + prm->view = [st->win contentView]; + } + + /* Enable vertical sync */ + [st->ctx setValues:&vsync forParameter:NSOpenGLCPSwapInterval]; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + [pool release]; + + return err; +} + + +static inline void draw_yuv(GLhandleARB PHandle, int height, + const uint8_t *Ytex, int linesizeY, + const uint8_t *Utex, int linesizeU, + const uint8_t *Vtex, int linesizeV) +{ + int i; + + /* This might not be required, but should not hurt. */ + glEnable(GL_TEXTURE_2D); + + /* Select texture unit 1 as the active unit and bind the U texture. */ + glActiveTexture(GL_TEXTURE1); + i = glGetUniformLocationARB(PHandle, "Utex"); + glUniform1iARB(i,1); /* Bind Utex to texture unit 1 */ + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,1); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE, + linesizeU, height/2, 0, + GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex); + + /* Select texture unit 2 as the active unit and bind the V texture. */ + glActiveTexture(GL_TEXTURE2); + i = glGetUniformLocationARB(PHandle, "Vtex"); + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,2); + glUniform1iARB(i,2); /* Bind Vtext to texture unit 2 */ + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE, + linesizeV, height/2, 0, + GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex); + + /* Select texture unit 0 as the active unit and bind the Y texture. */ + glActiveTexture(GL_TEXTURE0); + i = glGetUniformLocationARB(PHandle,"Ytex"); + glUniform1iARB(i,0); /* Bind Ytex to texture unit 0 */ + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,3); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_LUMINANCE, + linesizeY, height, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, Ytex); +} + + +static inline void draw_blit(int width, int height) +{ + glClear(GL_COLOR_BUFFER_BIT); + + /* Draw image */ + + glBegin(GL_QUADS); + { + glTexCoord2i(0, 0); + glVertex2i(0, 0); + glTexCoord2i(width, 0); + glVertex2i(width, 0); + glTexCoord2i(width, height); + glVertex2i(width, height); + glTexCoord2i(0, height); + glVertex2i(0, height); + } + glEnd(); +} + + +static inline void draw_rgb(const uint8_t *pic, int w, int h) +{ + glEnable(GL_TEXTURE_RECTANGLE_EXT); + glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1); + + glTextureRangeAPPLE(GL_TEXTURE_RECTANGLE_EXT, w * h * 2, pic); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_STORAGE_HINT_APPLE, + GL_STORAGE_SHARED_APPLE); + glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, w, h, 0, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pic); + + /* draw */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_TEXTURE_2D); + + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho( (GLfloat)0, (GLfloat)w, (GLfloat)0, (GLfloat)h, -1.0, 1.0); + + glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glBegin(GL_QUADS); + { + glTexCoord2f(0.0f, 0.0f); + glVertex2f(0.0f, h); + glTexCoord2f(0.0f, h); + glVertex2f(0.0f, 0.0f); + glTexCoord2f(w, h); + glVertex2f(w, 0.0f); + glTexCoord2f(w, 0.0f); + glVertex2f(w, h); + } + glEnd(); +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + NSAutoreleasePool *pool; + bool upd = false; + int err = 0; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return ENOMEM; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("opengl: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + opengl_reset(st, &frame->size); + + upd = true; + } + + if (upd && st->win) { + + const NSSize size = {frame->size.w, frame->size.h}; + char capt[256]; + + [st->win setContentSize:size]; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + [st->win setTitle:[NSString stringWithUTF8String:capt]]; + + [st->win makeKeyAndOrderFront:nil]; + [st->win display]; + [st->win center]; + + [st->ctx clearDrawable]; + [st->ctx setView:[st->win contentView]]; + } + + [st->ctx makeCurrentContext]; + + if (frame->fmt == VID_FMT_YUV420P) { + + if (!st->PHandle) { + + debug("opengl: using Vertex shader with YUV420P\n"); + + err = setup_shader(st, frame->size.w, frame->size.h); + if (err) + goto out; + } + + draw_yuv(st->PHandle, frame->size.h, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); + draw_blit(frame->size.w, frame->size.h); + } + else if (frame->fmt == VID_FMT_RGB32) { + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glViewport(0, 0, frame->size.w, frame->size.h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + draw_rgb(frame->data[0], frame->size.w, frame->size.h); + } + else { + warning("opengl: unknown pixel format %s\n", + vidfmt_name(frame->fmt)); + err = EINVAL; + } + + [st->ctx flushBuffer]; + + out: + [pool release]; + + return err; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st) + return; + + [st->win orderOut:nil]; +} + + +static int module_init(void) +{ + NSApplication *app; + int err; + + app = [NSApplication sharedApplication]; + if (!app) + return ENOSYS; + + err = vidisp_register(&vid, "opengl", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opengl) = { + "opengl", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/opengles/context.m b/modules/opengles/context.m new file mode 100644 index 0000000..354c74f --- /dev/null +++ b/modules/opengles/context.m @@ -0,0 +1,115 @@ +/** + * @file context.m OpenGLES Context for iOS + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <UIKit/UIKit.h> +#include <QuartzCore/CAEAGLLayer.h> +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#include "opengles.h" + + +@interface GlView : UIView +{ + struct vidisp_st *st; +} +@end + + +static EAGLContext *ctx = NULL; + + +@implementation GlView + + ++ (Class)layerClass +{ + return [CAEAGLLayer class]; +} + + +- (id)initWithFrame:(CGRect)frame vidisp:(struct vidisp_st *)vst +{ + self = [super initWithFrame:frame]; + if (!self) + return nil; + + self.layer.opaque = YES; + + st = vst; + + return self; +} + + +- (void) render_sel:(id)unused +{ + (void)unused; + + if (!ctx) { + UIWindow* window = [UIApplication sharedApplication].keyWindow; + + ctx = [EAGLContext alloc]; + [ctx initWithAPI:kEAGLRenderingAPIOpenGLES1]; + + [EAGLContext setCurrentContext:ctx]; + + [window addSubview:self]; + [window bringSubviewToFront:self]; + + [window makeKeyAndVisible]; + } + + if (!st->framebuffer) { + + opengles_addbuffers(st); + + [ctx renderbufferStorage:GL_RENDERBUFFER_OES + fromDrawable:(CAEAGLLayer*)self.layer]; + } + + opengles_render(st); + + [ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; +} + + +- (void) dealloc +{ + [ctx release]; + + [super dealloc]; +} + + +@end + + +void context_destroy(struct vidisp_st *st) +{ + [(UIView *)st->view release]; +} + + +int context_init(struct vidisp_st *st) +{ + UIWindow* window = [UIApplication sharedApplication].keyWindow; + + st->view = [[GlView new] initWithFrame:window.bounds vidisp:st]; + + return 0; +} + + +void context_render(struct vidisp_st *st) +{ + UIView *view = st->view; + + [view performSelectorOnMainThread:@selector(render_sel:) + withObject:nil + waitUntilDone:YES]; +} diff --git a/modules/opengles/module.mk b/modules/opengles/module.mk new file mode 100644 index 0000000..a9c5300 --- /dev/null +++ b/modules/opengles/module.mk @@ -0,0 +1,16 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opengles +$(MOD)_SRCS += opengles.c + +ifeq ($(OS),darwin) +$(MOD)_SRCS += context.m + +$(MOD)_LFLAGS += -lobjc -framework CoreGraphics -framework CoreFoundation +endif + +include mk/mod.mk diff --git a/modules/opengles/opengles.c b/modules/opengles/opengles.c new file mode 100644 index 0000000..fa87012 --- /dev/null +++ b/modules/opengles/opengles.c @@ -0,0 +1,295 @@ +/** + * @file opengles.c Video driver for OpenGLES + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#include "opengles.h" + + +#define DEBUG_MODULE "opengles" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +static struct vidisp *vid; + + +static int texture_init(struct vidisp_st *st) +{ + glGenTextures(1, &st->texture_id); + if (!st->texture_id) + return ENOMEM; + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + st->vf->size.w, st->vf->size.h, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + return 0; +} + + +static void texture_render(struct vidisp_st *st) +{ + static const GLfloat coords[4 * 2] = { + 0.0, 1.0, + 1.0, 1.0, + 0.0, 0.0, + 1.0, 0.0 + }; + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + st->vf->size.w, st->vf->size.h, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]); + + /* Setup the vertices */ + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, st->vertices); + + /* Setup the texture coordinates */ + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, coords); + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + + glEnable(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDisable(GL_TEXTURE_2D); +} + + +static void setup_layout(struct vidisp_st *st, const struct vidsz *screensz, + struct vidrect *ortho, struct vidrect *vp) +{ + int x, y, w, h, i = 0; + + w = st->vf->size.w; + h = st->vf->size.h; + + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = w; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = h; + st->vertices[i++] = 0; + st->vertices[i++] = w; + st->vertices[i++] = h; + st->vertices[i++] = 0; + + x = (screensz->w - w) / 2; + y = (screensz->h - h) / 2; + + if (x < 0) { + vp->x = 0; + ortho->x = -x; + } + else { + vp->x = x; + ortho->x = 0; + } + + if (y < 0) { + vp->y = 0; + ortho->y = -y; + } + else { + vp->y = y; + ortho->y = 0; + } + + vp->w = screensz->w - 2 * vp->x; + vp->h = screensz->h - 2 * vp->y; + + ortho->w = w - ortho->x; + ortho->h = h - ortho->y; +} + + +void opengles_addbuffers(struct vidisp_st *st) +{ + glGenFramebuffersOES(1, &st->framebuffer); + glGenRenderbuffersOES(1, &st->renderbuffer); + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer); +} + + +void opengles_render(struct vidisp_st *st) +{ + if (!st->texture_id) { + + struct vidrect ortho, vp; + struct vidsz bufsz; + int err = 0; + + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, + GL_RENDERBUFFER_WIDTH_OES, + (GLint *)&bufsz.w); + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, + GL_RENDERBUFFER_HEIGHT_OES, + (GLint *)&bufsz.h); + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, + GL_COLOR_ATTACHMENT0_OES, + GL_RENDERBUFFER_OES, + st->renderbuffer); + + err = texture_init(st); + if (err) + return; + + glBindRenderbufferOES(GL_FRAMEBUFFER_OES, st->renderbuffer); + + setup_layout(st, &bufsz, &ortho, &vp); + + + /* Set up Viewports etc. */ + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + + glViewport(vp.x, vp.y, vp.w, vp.h); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrthof(ortho.x, ortho.w, ortho.y, ortho.h, 0.0f, 1.0f); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glDisable(GL_DEPTH_TEST); + glDisableClientState(GL_COLOR_ARRAY); + } + + texture_render(st); + + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glBindTexture(GL_TEXTURE_2D, 0); + glEnable(GL_DEPTH_TEST); + + glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer); +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + glDeleteTextures(1, &st->texture_id); + glDeleteFramebuffersOES(1, &st->framebuffer); + glDeleteRenderbuffersOES(1, &st->renderbuffer); + + context_destroy(st); + + mem_deref(st->vf); + mem_deref(st->vd); +} + + +static int opengles_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, + void *arg) +{ + struct vidisp_st *st; + int err = 0; + + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + err = context_init(st); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int opengles_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + int err; + + (void)title; + + if (!st->vf) { + if (frame->size.w & 3) { + DEBUG_WARNING("width must be multiple of 4\n"); + return EINVAL; + } + + err = vidframe_alloc(&st->vf, VID_FMT_RGB565, &frame->size); + if (err) + return err; + } + + vidconv(st->vf, frame, NULL); + + context_render(st); + + return 0; +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "opengles", opengles_alloc, NULL, + opengles_display, NULL); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opengles) = { + "opengles", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/opengles/opengles.h b/modules/opengles/opengles.h new file mode 100644 index 0000000..9eac3fb --- /dev/null +++ b/modules/opengles/opengles.h @@ -0,0 +1,28 @@ +/** + * @file opengles.h Internal API to OpenGLES module + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct vidisp_st { + struct vidisp *vd; + struct vidframe *vf; + + /* GLES: */ + GLuint framebuffer; + GLuint renderbuffer; + GLuint texture_id; + GLfloat vertices[4 * 3]; + + void *view; +}; + + +void opengles_addbuffers(struct vidisp_st *st); +void opengles_render(struct vidisp_st *st); + + +int context_init(struct vidisp_st *st); +void context_destroy(struct vidisp_st *st); +void context_render(struct vidisp_st *st); diff --git a/modules/opensles/module.mk b/modules/opensles/module.mk new file mode 100644 index 0000000..30ecb4c --- /dev/null +++ b/modules/opensles/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opensles +$(MOD)_SRCS += opensles.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += -lOpenSLES + +include mk/mod.mk diff --git a/modules/opensles/opensles.c b/modules/opensles/opensles.c new file mode 100644 index 0000000..9da39f0 --- /dev/null +++ b/modules/opensles/opensles.c @@ -0,0 +1,66 @@ +/** + * @file opensles.c OpenSLES audio driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +SLObjectItf engineObject = NULL; +SLEngineItf engineEngine; + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + SLresult r; + int err; + + r = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, + &engineEngine); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + err = auplay_register(&auplay, "opensles", opensles_player_alloc); + err |= ausrc_register(&ausrc, "opensles", opensles_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opensles) = { + "opensles", + "audio", + module_init, + module_close, +}; diff --git a/modules/opensles/opensles.h b/modules/opensles/opensles.h new file mode 100644 index 0000000..2970413 --- /dev/null +++ b/modules/opensles/opensles.h @@ -0,0 +1,18 @@ +/** + * @file opensles.h OpenSLES audio driver -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +extern SLObjectItf engineObject; +extern SLEngineItf engineEngine; + + +int opensles_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int opensles_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); diff --git a/modules/opensles/player.c b/modules/opensles/player.c new file mode 100644 index 0000000..a317e5d --- /dev/null +++ b/modules/opensles/player.c @@ -0,0 +1,172 @@ +/** + * @file opensles/player.c OpenSLES audio driver -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define DEBUG_MODULE "opensles/player" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + int16_t buf[160 * 2]; + auplay_write_h *wh; + void *arg; + + SLObjectItf outputMixObject; + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + SLAndroidSimpleBufferQueueItf BufferQueue; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + if (st->bqPlayerObject != NULL) + (*st->bqPlayerObject)->Destroy(st->bqPlayerObject); + + if (st->outputMixObject != NULL) + (*st->outputMixObject)->Destroy(st->outputMixObject); + + mem_deref(st->ap); +} + + +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct auplay_st *st = context; + + st->wh((void *)st->buf, sizeof(st->buf), st->arg); + + (*st->BufferQueue)->Enqueue(bq, st->buf, sizeof(st->buf)); +} + + +static int createOutput(struct auplay_st *st) +{ + const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; + const SLboolean req[1] = {SL_BOOLEAN_FALSE}; + SLresult r; + + r = (*engineEngine)->CreateOutputMix(engineEngine, + &st->outputMixObject, 1, ids, req); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->outputMixObject)->Realize(st->outputMixObject, + SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +static int createPlayer(struct auplay_st *st, struct auplay_prm *prm) +{ + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 + }; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN}; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + SLDataLocator_OutputMix loc_outmix = { + SL_DATALOCATOR_OUTPUTMIX, st->outputMixObject + }; + SLDataSink audioSnk = {&loc_outmix, NULL}; + const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + SLresult r; + + r = (*engineEngine)->CreateAudioPlayer(engineEngine, + &st->bqPlayerObject, + &audioSrc, &audioSnk, + ARRAY_SIZE(ids), ids, req); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("CreateAudioPlayer error: r = %d\n", r); + return ENODEV; + } + + r = (*st->bqPlayerObject)->Realize(st->bqPlayerObject, + SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject, + SL_IID_PLAY, + &st->bqPlayerPlay); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject, + SL_IID_BUFFERQUEUE, + &st->BufferQueue); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->BufferQueue)->RegisterCallback(st->BufferQueue, + bqPlayerCallback, st); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerPlay)->SetPlayState(st->bqPlayerPlay, + SL_PLAYSTATE_PLAYING); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +int opensles_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int err; + (void)device; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + err = createOutput(st); + if (err) + goto out; + + err = createPlayer(st, prm); + if (err) + goto out; + + /* kick-start the buffer callback */ + bqPlayerCallback(st->BufferQueue, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/opensles/recorder.c b/modules/opensles/recorder.c new file mode 100644 index 0000000..6301451 --- /dev/null +++ b/modules/opensles/recorder.c @@ -0,0 +1,224 @@ +/** + * @file opensles/recorder.c OpenSLES audio driver -- recording + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <pthread.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define DEBUG_MODULE "opensles/recorder" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + int16_t buf[160]; + pthread_t thread; + bool run; + ausrc_read_h *rh; + void *arg; + + SLObjectItf recObject; + SLRecordItf recRecord; + SLAndroidSimpleBufferQueueItf recBufferQueue; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->recObject != NULL) + (*st->recObject)->Destroy(st->recObject); + + mem_deref(st->as); +} + + +static void *record_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + SLresult r; + + while (st->run) { + + (void)sys_usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; +#if 1 + if (now > ts + 100) { + debug("opensles: cpu lagging behind (%u ms)\n", + now - ts); + } +#endif + + r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->buf, sizeof(st->buf)); + if (r != SL_RESULT_SUCCESS) { + DEBUG_WARNING("Enqueue: r = %d\n", r); + } + + ts += 20; + } + + return NULL; +} + + +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct ausrc_st *st = context; + (void)bq; + + st->rh((void *)st->buf, sizeof(st->buf), st->arg); +} + + +static int createAudioRecorder(struct ausrc_st *st, struct ausrc_prm *prm) +{ + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 + }; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN}; + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + SLresult r; + + r = (*engineEngine)->CreateAudioRecorder(engineEngine, + &st->recObject, + &audioSrc, + &audioSnk, 1, id, req); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("CreateAudioRecorder failed: r = %d\n", r); + return ENODEV; + } + + r = (*st->recObject)->Realize(st->recObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("recorder: Realize r = %d\n", r); + return ENODEV; + } + + r = (*st->recObject)->GetInterface(st->recObject, SL_IID_RECORD, + &st->recRecord); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->recObject)->GetInterface(st->recObject, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &st->recBufferQueue); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->recBufferQueue)->RegisterCallback(st->recBufferQueue, + bqRecorderCallback, + st); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +static int startRecording(struct ausrc_st *st) +{ + SLresult r; + + (*st->recRecord)->SetRecordState(st->recRecord, + SL_RECORDSTATE_STOPPED); + (*st->recBufferQueue)->Clear(st->recBufferQueue); + +#if 0 + r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->buf, sizeof(st->buf)); + if (SL_RESULT_SUCCESS != r) + return ENODEV; +#endif + + r = (*st->recRecord)->SetRecordState(st->recRecord, + SL_RECORDSTATE_RECORDING); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +int opensles_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err; + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + err = createAudioRecorder(st, prm); + if (err) { + DEBUG_WARNING("failed to create recorder\n"); + goto out; + } + + err = startRecording(st); + if (err) { + DEBUG_WARNING("failed to start recorder\n"); + goto out; + } + + st->run = true; + + err = pthread_create(&st->thread, NULL, record_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/opus/decode.c b/modules/opus/decode.c new file mode 100644 index 0000000..f2d67b1 --- /dev/null +++ b/modules/opus/decode.c @@ -0,0 +1,101 @@ +/** + * @file opus/decode.c Opus Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +struct audec_state { + OpusDecoder *dec; + unsigned ch; +}; + + +static void destructor(void *arg) +{ + struct audec_state *ads = arg; + + if (ads->dec) + opus_decoder_destroy(ads->dec); +} + + +int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + struct audec_state *ads; + int opuserr, err = 0; + (void)fmtp; + + if (!adsp || !ac || !ac->ch) + return EINVAL; + + ads = *adsp; + + if (ads) + return 0; + + ads = mem_zalloc(sizeof(*ads), destructor); + if (!ads) + return ENOMEM; + + ads->ch = ac->ch; + + ads->dec = opus_decoder_create(ac->srate, ac->ch, &opuserr); + if (!ads->dec) { + warning("opus: decoder create: %s\n", opus_strerror(opuserr)); + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(ads); + else + *adsp = ads; + + return err; +} + + +int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int n; + + if (!ads || !sampv || !sampc || !buf) + return EINVAL; + + n = opus_decode(ads->dec, buf, (opus_int32)len, + sampv, (int)(*sampc/ads->ch), 0); + if (n < 0) { + warning("opus: decode error: %s\n", opus_strerror(n)); + return EPROTO; + } + + *sampc = n * ads->ch; + + return 0; +} + + +int opus_decode_pkloss(struct audec_state *ads, int16_t *sampv, size_t *sampc) +{ + int n; + + if (!ads || !sampv || !sampc) + return EINVAL; + + n = opus_decode(ads->dec, NULL, 0, sampv, (int)(*sampc/ads->ch), 0); + if (n < 0) + return EPROTO; + + *sampc = n * ads->ch; + + return 0; +} diff --git a/modules/opus/encode.c b/modules/opus/encode.c new file mode 100644 index 0000000..3272490 --- /dev/null +++ b/modules/opus/encode.c @@ -0,0 +1,170 @@ +/** + * @file opus/encode.c Opus Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +struct auenc_state { + OpusEncoder *enc; + unsigned ch; +}; + + +static void destructor(void *arg) +{ + struct auenc_state *aes = arg; + + if (aes->enc) + opus_encoder_destroy(aes->enc); +} + + +static opus_int32 srate2bw(opus_int32 srate) +{ + if (srate >= 48000) + return OPUS_BANDWIDTH_FULLBAND; + else if (srate >= 24000) + return OPUS_BANDWIDTH_SUPERWIDEBAND; + else if (srate >= 16000) + return OPUS_BANDWIDTH_WIDEBAND; + else if (srate >= 12000) + return OPUS_BANDWIDTH_MEDIUMBAND; + else + return OPUS_BANDWIDTH_NARROWBAND; +} + + +#if 0 +static const char *bwname(opus_int32 bw) +{ + switch (bw) { + case OPUS_BANDWIDTH_FULLBAND: return "full"; + case OPUS_BANDWIDTH_SUPERWIDEBAND: return "superwide"; + case OPUS_BANDWIDTH_WIDEBAND: return "wide"; + case OPUS_BANDWIDTH_MEDIUMBAND: return "medium"; + case OPUS_BANDWIDTH_NARROWBAND: return "narrow"; + default: return "???"; + } +} + + +static const char *chname(opus_int32 ch) +{ + switch (ch) { + case OPUS_AUTO: return "auto"; + case 1: return "mono"; + case 2: return "stereo"; + default: return "???"; + } +} +#endif + + +int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *param, const char *fmtp) +{ + struct auenc_state *aes; + struct opus_param prm; + opus_int32 fch, vbr; + (void)param; + + if (!aesp || !ac || !ac->ch) + return EINVAL; + + aes = *aesp; + + if (!aes) { + const opus_int32 complex = 10; + int opuserr; + + aes = mem_zalloc(sizeof(*aes), destructor); + if (!aes) + return ENOMEM; + + aes->ch = ac->ch; + + aes->enc = opus_encoder_create(ac->srate, ac->ch, + /* this has big impact on cpu */ + OPUS_APPLICATION_AUDIO, + &opuserr); + if (!aes->enc) { + warning("opus: encoder create: %s\n", + opus_strerror(opuserr)); + mem_deref(aes); + return ENOMEM; + } + + (void)opus_encoder_ctl(aes->enc, OPUS_SET_COMPLEXITY(complex)); + + *aesp = aes; + } + + prm.srate = 48000; + prm.bitrate = OPUS_AUTO; + prm.stereo = 1; + prm.cbr = 0; + prm.inband_fec = 0; + prm.dtx = 0; + + opus_decode_fmtp(&prm, fmtp); + + fch = prm.stereo ? OPUS_AUTO : 1; + vbr = prm.cbr ? 0 : 1; + + (void)opus_encoder_ctl(aes->enc, + OPUS_SET_MAX_BANDWIDTH(srate2bw(prm.srate))); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_BITRATE(prm.bitrate)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_FORCE_CHANNELS(fch)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_VBR(vbr)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_INBAND_FEC(prm.inband_fec)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_DTX(prm.dtx)); + + +#if 0 + { + opus_int32 bw, complex; + + (void)opus_encoder_ctl(aes->enc, OPUS_GET_MAX_BANDWIDTH(&bw)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_BITRATE(&prm.bitrate)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_FORCE_CHANNELS(&fch)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_VBR(&vbr)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_INBAND_FEC(&prm.inband_fec)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_DTX(&prm.dtx)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_COMPLEXITY(&complex)); + + debug("opus: encode bw=%s bitrate=%i fch=%s " + "vbr=%i fec=%i dtx=%i complex=%i\n", + bwname(bw), prm.bitrate, chname(fch), + vbr, prm.inband_fec, prm.dtx, complex); + } +#endif + + return 0; +} + + +int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + opus_int32 n; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + n = opus_encode(aes->enc, sampv, (int)(sampc/aes->ch), + buf, (opus_int32)(*len)); + if (n < 0) { + warning("opus: encode error: %s\n", opus_strerror((int)n)); + return EPROTO; + } + + *len = n; + + return 0; +} diff --git a/modules/opus/module.mk b/modules/opus/module.mk new file mode 100644 index 0000000..ded0f13 --- /dev/null +++ b/modules/opus/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opus +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += opus.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lopus -lm + +include mk/mod.mk diff --git a/modules/opus/opus.c b/modules/opus/opus.c new file mode 100644 index 0000000..28b24b9 --- /dev/null +++ b/modules/opus/opus.c @@ -0,0 +1,63 @@ +/** + * @file opus.c Opus Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +/** + * @defgroup opus opus + * + * The OPUS audio codec + * + * Latest supported version: libopus 1.0.0 + * + * References: + * + * draft-ietf-codec-opus-10 + * draft-spittka-payload-rtp-opus-00 + * + * http://opus-codec.org/downloads/ + */ + + +static struct aucodec opus = { + .name = "opus", + .srate = 48000, + .ch = 2, + .fmtp = "stereo=1;sprop-stereo=1", + .encupdh = opus_encode_update, + .ench = opus_encode_frm, + .decupdh = opus_decode_update, + .dech = opus_decode_frm, + .plch = opus_decode_pkloss, +}; + + +static int module_init(void) +{ + aucodec_register(&opus); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&opus); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opus) = { + "opus", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/opus/opus.h b/modules/opus/opus.h new file mode 100644 index 0000000..2bed161 --- /dev/null +++ b/modules/opus/opus.h @@ -0,0 +1,34 @@ +/** + * @file opus.h Private Opus Interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct opus_param { + opus_int32 srate; + opus_int32 bitrate; + opus_int32 stereo; + opus_int32 cbr; + opus_int32 inband_fec; + opus_int32 dtx; +}; + + +/* Encode */ +int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp); +int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc); + + +/* Decode */ +int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp); +int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len); +int opus_decode_pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc); + + +/* SDP */ +void opus_decode_fmtp(struct opus_param *prm, const char *fmtp); diff --git a/modules/opus/sdp.c b/modules/opus/sdp.c new file mode 100644 index 0000000..024c8a6 --- /dev/null +++ b/modules/opus/sdp.c @@ -0,0 +1,51 @@ +/** + * @file opus/sdp.c Opus SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +static void assign_if(opus_int32 *v, const struct pl *pl, + uint32_t min, uint32_t max) +{ + const uint32_t val = pl_u32(pl); + + if (val < min || val > max) + return; + + *v = val; +} + + +void opus_decode_fmtp(struct opus_param *prm, const char *fmtp) +{ + struct pl pl, val; + + if (!prm || !fmtp) + return; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "maxplaybackrate", &val)) + assign_if(&prm->srate, &val, 8000, 48000); + + if (fmt_param_get(&pl, "maxaveragebitrate", &val)) + assign_if(&prm->bitrate, &val, 6000, 510000); + + if (fmt_param_get(&pl, "stereo", &val)) + assign_if(&prm->stereo, &val, 0, 1); + + if (fmt_param_get(&pl, "cbr", &val)) + assign_if(&prm->cbr, &val, 0, 1); + + if (fmt_param_get(&pl, "useinbandfec", &val)) + assign_if(&prm->inband_fec, &val, 0, 1); + + if (fmt_param_get(&pl, "usedtx", &val)) + assign_if(&prm->dtx, &val, 0, 1); +} diff --git a/modules/oss/module.mk b/modules/oss/module.mk new file mode 100644 index 0000000..dd4b09a --- /dev/null +++ b/modules/oss/module.mk @@ -0,0 +1,18 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := oss +$(MOD)_SRCS += oss.c +$(MOD)_LFLAGS += + +ifeq ($(OS), openbsd) +$(MOD)_LFLAGS += -lossaudio +endif +ifeq ($(OS), netbsd) +$(MOD)_LFLAGS += -lossaudio +endif + +include mk/mod.mk diff --git a/modules/oss/oss.c b/modules/oss/oss.c new file mode 100644 index 0000000..3acd9df --- /dev/null +++ b/modules/oss/oss.c @@ -0,0 +1,353 @@ +/** + * @file oss.c Open Sound System (OSS) driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#if defined(NETBSD) || defined(OPENBSD) +#include <soundcard.h> +#elif defined (LINUX) +#include <linux/soundcard.h> +#else +#include <sys/soundcard.h> +#endif + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + int fd; + struct mbuf *mb; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; +}; + +struct auplay_st { + struct auplay *ap; /* inheritance */ + pthread_t thread; + bool run; + int fd; + uint8_t *buf; + uint32_t sz; + auplay_write_h *wh; + void *arg; +}; + + +static struct ausrc *ausrc; +static struct auplay *auplay; +static char oss_dev[64] = "/dev/dsp"; + + +/* + * Automatically calculate the fragment size depending on sampling rate + * and number of channels. More entries can be added to the table below. + * + * NOTE. Powermac 8200 and linux 2.4.18 gives: + * SNDCTL_DSP_SETFRAGMENT: Invalid argument + */ +static int set_fragment(int fd, uint32_t sampc) +{ + static const struct { + uint16_t max; + uint16_t size; + } fragv[] = { + {10, 7}, /* 10 x 2^7 = 1280 = 4 x 320 */ + {15, 7}, /* 15 x 2^7 = 1920 = 6 x 320 */ + {20, 7}, /* 20 x 2^7 = 2560 = 8 x 320 */ + {25, 7}, /* 25 x 2^7 = 3200 = 10 x 320 */ + {15, 8}, /* 15 x 2^8 = 3840 = 12 x 320 */ + {20, 8}, /* 20 x 2^8 = 5120 = 16 x 320 */ + {25, 8} /* 25 x 2^8 = 6400 = 20 x 320 */ + }; + size_t i; + const uint32_t buf_size = 2 * sampc; + + for (i=0; i<ARRAY_SIZE(fragv); i++) { + const uint16_t frag_max = fragv[i].max; + const uint16_t frag_size = fragv[i].size; + const uint32_t fragment_size = frag_max * (1<<frag_size); + + if (0 == (fragment_size%buf_size)) { + int fragment = (frag_max<<16) | frag_size; + + if (0 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT, + &fragment)) { + return 0; + } + } + } + + return ENODEV; +} + + +static int oss_reset(int fd, uint32_t srate, uint8_t ch, int sampc, + int nonblock) +{ + int format = AFMT_S16_LE; + int speed = srate; + int channels = ch; + int blocksize = 0; + int err; + + err = set_fragment(fd, sampc); + if (err) + return err; + + if (0 != ioctl(fd, FIONBIO, &nonblock)) + return errno; + if (0 != ioctl(fd, SNDCTL_DSP_SETFMT, &format)) + return errno; + if (0 != ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)) + return errno; + if (2 == channels) { + int stereo = 1; + if (0 != ioctl(fd, SNDCTL_DSP_STEREO, &stereo)) + return errno; + } + if (0 != ioctl(fd, SNDCTL_DSP_SPEED, &speed)) + return errno; + + (void)ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize); + + info("oss: init: %u bit %d Hz %d ch, blocksize=%d\n", + format, speed, channels, blocksize); + + return 0; +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (-1 != st->fd) { + fd_close(st->fd); + (void)close(st->fd); + } + + mem_deref(st->buf); + mem_deref(st->ap); +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (-1 != st->fd) { + fd_close(st->fd); + (void)close(st->fd); + } + + mem_deref(st->mb); + mem_deref(st->as); +} + + +static void read_handler(int flags, void *arg) +{ + struct ausrc_st *st = arg; + struct mbuf *mb = st->mb; + int n; + (void)flags; + + n = read(st->fd, mbuf_buf(mb), mbuf_get_space(mb)); + if (n <= 0) + return; + + mb->pos += n; + + if (mb->pos < mb->size) + return; + + st->rh(mb->buf, mb->size, st->arg); + + mb->pos = 0; +} + + +static void *play_thread(void *arg) +{ + struct auplay_st *st = arg; + int n; + + while (st->run) { + + st->wh(st->buf, st->sz, st->arg); + + n = write(st->fd, st->buf, st->sz); + if (n < 0) { + warning("oss: write: %m\n", errno); + break; + } + } + + return NULL; +} + + +static int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + (void)ctx; + (void)errh; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->fd = -1; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + if (!device) + device = oss_dev; + + prm->fmt = AUFMT_S16LE; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->mb = mbuf_alloc(2 * sampc); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + st->fd = open(device, O_RDONLY); + if (st->fd < 0) { + err = errno; + goto out; + } + + err = fd_listen(st->fd, FD_READ, read_handler, st); + if (err) + goto out; + + err = oss_reset(st->fd, prm->srate, prm->ch, sampc, 1); + if (err) + goto out; + + st->as = mem_ref(as); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + unsigned sampc; + int err; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->fd = -1; + st->wh = wh; + st->arg = arg; + + if (!device) + device = oss_dev; + + prm->fmt = AUFMT_S16LE; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sz = 2 * sampc; + st->buf = mem_alloc(st->sz, NULL); + if (!st->buf) { + err = ENOMEM; + goto out; + } + + st->fd = open(device, O_WRONLY); + if (st->fd < 0) { + err = errno; + goto out; + } + + err = oss_reset(st->fd, prm->srate, prm->ch, sampc, 0); + if (err) + goto out; + + st->ap = mem_ref(ap); + + st->run = true; + err = pthread_create(&st->thread, NULL, play_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + int err; + + err = ausrc_register(&ausrc, "oss", src_alloc); + err |= auplay_register(&auplay, "oss", play_alloc); + + return err; +} + + +static int module_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(oss) = { + "oss", + "audio", + module_init, + module_close, +}; diff --git a/modules/plc/module.mk b/modules/plc/module.mk new file mode 100644 index 0000000..bc8ae4f --- /dev/null +++ b/modules/plc/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := plc +$(MOD)_SRCS += plc.c +$(MOD)_LFLAGS += "-lspandsp" + +include mk/mod.mk diff --git a/modules/plc/plc.c b/modules/plc/plc.c new file mode 100644 index 0000000..5409258 --- /dev/null +++ b/modules/plc/plc.c @@ -0,0 +1,109 @@ +/** + * @file plc.c PLC -- Packet Loss Concealment + * + * Copyright (C) 2010 Creytiv.com + */ +#include <spandsp.h> +#include <re.h> +#include <baresip.h> + + +struct plc_st { + struct aufilt_dec_st af; /* base class */ + plc_state_t plc; + size_t sampc; +}; + + +static void destructor(void *arg) +{ + struct plc_st *st = arg; + + list_unlink(&st->af.le); +} + + +static int update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct plc_st *st; + int err = 0; + (void)ctx; + (void)af; + + if (!stp || !prm) + return EINVAL; + + if (*stp) + return 0; + + /* XXX: add support for stereo PLC */ + if (prm->ch != 1) { + warning("plc: only mono supported (ch=%u)\n", prm->ch); + return ENOSYS; + } + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + if (!plc_init(&st->plc)) { + err = ENOMEM; + goto out; + } + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + out: + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +/* + * PLC is only valid for Decoding (RX) + * + * NOTE: sampc == 0 , means Packet loss + */ +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct plc_st *plc = (struct plc_st *)st; + + if (*sampc) + plc_rx(&plc->plc, sampv, (int)*sampc); + else + *sampc = plc_fillin(&plc->plc, sampv, (int)plc->sampc); + + return 0; +} + + +static struct aufilt plc = { + LE_INIT, "plc", NULL, NULL, update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&plc); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&plc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(plc) = { + "plc", + "filter", + module_init, + module_close +}; diff --git a/modules/portaudio/module.mk b/modules/portaudio/module.mk new file mode 100644 index 0000000..81e5b80 --- /dev/null +++ b/modules/portaudio/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := portaudio +$(MOD)_SRCS += portaudio.c +$(MOD)_LFLAGS += -lportaudio + +include mk/mod.mk diff --git a/modules/portaudio/portaudio.c b/modules/portaudio/portaudio.c new file mode 100644 index 0000000..36ca3f1 --- /dev/null +++ b/modules/portaudio/portaudio.c @@ -0,0 +1,331 @@ +/** + * @file portaudio.c Portaudio sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <portaudio.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* + * portaudio v19 is required + */ + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + PaStream *stream_rd; + ausrc_read_h *rh; + void *arg; + volatile bool ready; + unsigned ch; +}; + +struct auplay_st { + struct auplay *ap; /* inheritance */ + PaStream *stream_wr; + auplay_write_h *wh; + void *arg; + volatile bool ready; + unsigned ch; +}; + + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +/* + * This routine will be called by the PortAudio engine when audio is needed. + * It may called at interrupt level on some machines so don't do anything + * that could mess up the system like calling malloc() or free(). + */ +static int read_callback(const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData) +{ + struct ausrc_st *st = userData; + unsigned sampc; + + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->rh(inputBuffer, 2*sampc, st->arg); + + return paContinue; +} + + +static int write_callback(const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData) +{ + struct auplay_st *st = userData; + unsigned sampc; + + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->wh(outputBuffer, 2*sampc, st->arg); + + return paContinue; +} + + +static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, + uint32_t dev) +{ + PaStreamParameters prm_in; + PaError err; + unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; + + memset(&prm_in, 0, sizeof(prm_in)); + prm_in.device = dev; + prm_in.channelCount = prm->ch; + prm_in.sampleFormat = paInt16; + prm_in.suggestedLatency = 0.100; + + st->stream_rd = NULL; + err = Pa_OpenStream(&st->stream_rd, &prm_in, NULL, prm->srate, + frames_per_buffer, paNoFlag, read_callback, st); + if (paNoError != err) { + warning("portaudio: read: Pa_OpenStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + err = Pa_StartStream(st->stream_rd); + if (paNoError != err) { + warning("portaudio: read: Pa_StartStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + return 0; +} + + +static int write_stream_open(struct auplay_st *st, + const struct auplay_prm *prm, uint32_t dev) +{ + PaStreamParameters prm_out; + PaError err; + unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; + + memset(&prm_out, 0, sizeof(prm_out)); + prm_out.device = dev; + prm_out.channelCount = prm->ch; + prm_out.sampleFormat = paInt16; + prm_out.suggestedLatency = 0.100; + + st->stream_wr = NULL; + err = Pa_OpenStream(&st->stream_wr, NULL, &prm_out, prm->srate, + frames_per_buffer, paNoFlag, write_callback, st); + if (paNoError != err) { + warning("portaudio: write: Pa_OpenStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + err = Pa_StartStream(st->stream_wr); + if (paNoError != err) { + warning("portaudio: write: Pa_StartStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + return 0; +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + st->ready = false; + + if (st->stream_rd) { + Pa_AbortStream(st->stream_rd); + Pa_CloseStream(st->stream_rd); + } + + mem_deref(st->as); +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + st->ready = false; + + if (st->stream_wr) { + Pa_AbortStream(st->stream_wr); + Pa_CloseStream(st->stream_wr); + } + + mem_deref(st->ap); +} + + +static int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + PaDeviceIndex dev_index; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + if (str_isset(device)) + dev_index = atoi(device); + else + dev_index = Pa_GetDefaultInputDevice(); + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + st->ch = prm->ch; + + st->ready = true; + + err = read_stream_open(st, prm, dev_index); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + PaDeviceIndex dev_index; + int err; + + (void)device; + + if (!stp || !ap || !prm) + return EINVAL; + + if (str_isset(device)) + dev_index = atoi(device); + else + dev_index = Pa_GetDefaultOutputDevice(); + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + st->ch = prm->ch; + + st->ready = true; + + err = write_stream_open(st, prm, dev_index); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int pa_init(void) +{ + PaError paerr; + int i, n, err = 0; + + paerr = Pa_Initialize(); + if (paNoError != paerr) { + warning("portaudio: init: %s\n", Pa_GetErrorText(paerr)); + return ENODEV; + } + + n = Pa_GetDeviceCount(); + + info("portaudio: device count is %d\n", n); + + for (i=0; i<n; i++) { + const PaDeviceInfo *devinfo; + + devinfo = Pa_GetDeviceInfo(i); + + debug("portaudio: device %d: %s\n", i, devinfo->name); + (void)devinfo; + } + + if (paNoDevice != Pa_GetDefaultInputDevice()) + err |= ausrc_register(&ausrc, "portaudio", src_alloc); + + if (paNoDevice != Pa_GetDefaultOutputDevice()) + err |= auplay_register(&auplay, "portaudio", play_alloc); + + return err; +} + + +static int pa_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + Pa_Terminate(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(portaudio) = { + "portaudio", + "sound", + pa_init, + pa_close +}; diff --git a/modules/presence/module.mk b/modules/presence/module.mk new file mode 100644 index 0000000..6ec5d17 --- /dev/null +++ b/modules/presence/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := presence +$(MOD)_SRCS += presence.c subscriber.c notifier.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/presence/notifier.c b/modules/presence/notifier.c new file mode 100644 index 0000000..ced7b75 --- /dev/null +++ b/modules/presence/notifier.c @@ -0,0 +1,270 @@ +/** + * @file notifier.c Presence notifier + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +/* + * Notifier - other people are subscribing to the status of our AOR. + * we must maintain a list of active notifications. we receive a SUBSCRIBE + * message from peer, and send NOTIFY to all peers when the Status changes + */ + + +struct notifier { + struct le le; + struct sipevent_sock *sock; + struct sipnot *not; + struct ua *ua; +}; + +static enum presence_status my_status = PRESENCE_OPEN; +static struct list notifierl; +static struct sipevent_sock *evsock; + + +static const char *presence_status_str(enum presence_status st) +{ + switch (st) { + + case PRESENCE_OPEN: return "open"; + case PRESENCE_CLOSED: return "closed"; + default: return "?"; + } +} + + +static int notify(struct notifier *not, enum presence_status status) +{ + const char *aor = ua_aor(not->ua); + struct mbuf *mb; + int err; + + mb = mbuf_alloc(1024); + if (!mb) + return ENOMEM; + + err = mbuf_printf(mb, + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n" + "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\r\n" + " xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\"\r\n" + " xmlns:rpid=\"urn:ietf:params:xml:ns:pidf:rpid\"\r\n" + " entity=\"%s\">\r\n" + " <dm:person id=\"p4159\"><rpid:activities/></dm:person>\r\n" + " <tuple id=\"t4109\">\r\n" + " <status>\r\n" + " <basic>%s</basic>\r\n" + " </status>\r\n" + " <contact>%s</contact>\r\n" + " </tuple>\r\n" + "</presence>\r\n" + ,aor, presence_status_str(status), aor); + if (err) + goto out; + + mb->pos = 0; + + err = sipevent_notify(not->not, mb, SIPEVENT_ACTIVE, 0, 0); + if (err) { + warning("presence: notify to %s failed (%m)\n", aor, err); + } + + out: + mem_deref(mb); + return err; +} + + +static void sipnot_close_handler(int err, const struct sip_msg *msg, + void *arg) +{ + struct notifier *not = arg; + + if (err) { + info("presence: notifier closed (%m)\n", err); + } + else if (msg) { + info("presence: notifier closed (%u %r)\n", + msg->scode, &msg->reason); + } + + not = mem_deref(not); +} + + +static void destructor(void *arg) +{ + struct notifier *not = arg; + + list_unlink(¬->le); + mem_deref(not->not); + mem_deref(not->sock); + mem_deref(not->ua); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + return account_auth(arg, username, password, realm); +} + + +static int notifier_alloc(struct notifier **notp, struct sipevent_sock *sock, + const struct sip_msg *msg, + const struct sipevent_event *se, struct ua *ua) +{ + struct notifier *not; + int err; + + if (!sock || !msg || !se) + return EINVAL; + + not = mem_zalloc(sizeof(*not), destructor); + if (!not) + return ENOMEM; + + not->sock = mem_ref(sock); + not->ua = mem_ref(ua); + + err = sipevent_accept(¬->not, sock, msg, NULL, se, 200, "OK", + 600, 600, 600, + ua_cuser(not->ua), "application/pidf+xml", + auth_handler, ua_prm(not->ua), true, + sipnot_close_handler, not, NULL); + if (err) { + warning("presence: sipevent_accept failed: %m\n", err); + goto out; + } + + list_append(¬ifierl, ¬->le, not); + + out: + if (err) + mem_deref(not); + else if (notp) + *notp = not; + + return err; +} + + +static int notifier_add(struct sipevent_sock *sock, const struct sip_msg *msg, + struct ua *ua) +{ + const struct sip_hdr *hdr; + struct sipevent_event se; + struct notifier *not; + int err; + + hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); + if (!hdr) + return EPROTO; + + err = sipevent_event_decode(&se, &hdr->val); + if (err) + return err; + + if (pl_strcasecmp(&se.event, "presence")) { + info("presence: unexpected event '%r'\n", &se.event); + return EPROTO; + } + + err = notifier_alloc(¬, sock, msg, &se, ua); + if (err) + return err; + + (void)notify(not, my_status); + + return 0; +} + + +static void notifier_update_status(enum presence_status status) +{ + struct le *le; + + if (status == my_status) + return; + + info("presence: update my status from '%s' to '%s'\n", + contact_presence_str(my_status), + contact_presence_str(status)); + + my_status = status; + + for (le = notifierl.head; le; le = le->next) { + + struct notifier *not = le->data; + + (void)notify(not, status); + } +} + + +static int cmd_online(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + notifier_update_status(PRESENCE_OPEN); + return 0; +} + + +static int cmd_offline(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + notifier_update_status(PRESENCE_CLOSED); + return 0; +} + + +static const struct cmd cmdv[] = { + {'[', 0, "Set presence online", cmd_online }, + {']', 0, "Set presence offline", cmd_offline }, +}; + + +static bool sub_handler(const struct sip_msg *msg, void *arg) +{ + struct ua *ua; + + (void)arg; + + ua = uag_find(&msg->uri.user); + if (!ua) { + warning("presence: no UA found for %r\n", &msg->uri.user); + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return true; + } + + if (notifier_add(evsock, msg, ua)) + (void)sip_treply(NULL, uag_sip(), msg, 400, "Bad Presence"); + + return true; +} + + +int notifier_init(void) +{ + int err; + + err = sipevent_listen(&evsock, uag_sip(), 32, 32, sub_handler, NULL); + if (err) + return err; + + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +void notifier_close(void) +{ + cmd_unregister(cmdv); + list_flush(¬ifierl); + evsock = mem_deref(evsock); +} diff --git a/modules/presence/presence.c b/modules/presence/presence.c new file mode 100644 index 0000000..7ed4908 --- /dev/null +++ b/modules/presence/presence.c @@ -0,0 +1,41 @@ +/** + * @file presence.c Presence module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +static int module_init(void) +{ + int err; + + err = subscriber_init(); + if (err) + return err; + + err = notifier_init(); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + notifier_close(); + subscriber_close(); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(presence) = { + "presence", + "application", + module_init, + module_close +}; diff --git a/modules/presence/presence.h b/modules/presence/presence.h new file mode 100644 index 0000000..a1c5e31 --- /dev/null +++ b/modules/presence/presence.h @@ -0,0 +1,13 @@ +/** + * @file presence.h Presence module interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +int subscriber_init(void); +void subscriber_close(void); + + +int notifier_init(void); +void notifier_close(void); diff --git a/modules/presence/subscriber.c b/modules/presence/subscriber.c new file mode 100644 index 0000000..22361d5 --- /dev/null +++ b/modules/presence/subscriber.c @@ -0,0 +1,273 @@ +/** + * @file subscriber.c Presence subscriber + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +/* + * Subscriber - we subscribe to the status information of N resources. + * + * For each entry in the address book marked with ;presence=p2p, + * we send a SUBSCRIBE to that person, and expect to receive + * a NOTIFY when her status changes. + */ + + +struct presence { + struct le le; + struct sipsub *sub; + struct tmr tmr; + enum presence_status status; + unsigned failc; + struct contact *contact; +}; + +static struct list presencel; + + +static void tmr_handler(void *arg); + + +static uint32_t wait_term(const struct sipevent_substate *substate) +{ + uint32_t wait; + + switch (substate->reason) { + + case SIPEVENT_DEACTIVATED: + case SIPEVENT_TIMEOUT: + wait = 5; + break; + + case SIPEVENT_REJECTED: + case SIPEVENT_NORESOURCE: + wait = 3600; + break; + + case SIPEVENT_PROBATION: + case SIPEVENT_GIVEUP: + default: + wait = 300; + if (pl_isset(&substate->retry_after)) + wait = max(wait, pl_u32(&substate->retry_after)); + break; + } + + return wait; +} + + +static uint32_t wait_fail(unsigned failc) +{ + switch (failc) { + + case 1: return 30; + case 2: return 300; + case 3: return 3600; + default: return 86400; + } +} + + +static void notify_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + enum presence_status status = PRESENCE_CLOSED; + struct presence *pres = arg; + const struct sip_hdr *hdr; + struct pl pl; + + pres->failc = 0; + + hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_TYPE); + if (!hdr || 0 != pl_strcasecmp(&hdr->val, "application/pidf+xml")) { + + if (hdr) + warning("presence: unsupported content-type: '%r'\n", + &hdr->val); + + sip_treplyf(NULL, NULL, sip, msg, false, + 415, "Unsupported Media Type", + "Accept: application/pidf+xml\r\n" + "Content-Length: 0\r\n" + "\r\n"); + return; + } + + if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), + "<status>[^<]*<basic>[^<]*</basic>[^<]*</status>", + NULL, &pl, NULL)) { + + if (!pl_strcasecmp(&pl, "open")) + status = PRESENCE_OPEN; + } + + if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), + "<rpid:away/>")) { + + status = PRESENCE_CLOSED; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:busy/>")) { + + status = PRESENCE_BUSY; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:on-the-phone/>")) { + + status = PRESENCE_BUSY; + } + + (void)sip_treply(NULL, sip, msg, 200, "OK"); + + contact_set_presence(pres->contact, status); +} + + +static void close_handler(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, void *arg) +{ + struct presence *pres = arg; + uint32_t wait; + + pres->sub = mem_deref(pres->sub); + + info("presence: subscriber closed <%r>: ", + &contact_addr(pres->contact)->auri); + + if (substate) { + info("%s", sipevent_reason_name(substate->reason)); + wait = wait_term(substate); + } + else if (msg) { + info("%u %r", msg->scode, &msg->reason); + wait = wait_fail(++pres->failc); + } + else { + info("%m", err); + wait = wait_fail(++pres->failc); + } + + info("; will retry in %u secs (failc=%u)\n", wait, pres->failc); + + tmr_start(&pres->tmr, wait * 1000, tmr_handler, pres); + + contact_set_presence(pres->contact, PRESENCE_UNKNOWN); +} + + +static void destructor(void *arg) +{ + struct presence *pres = arg; + + list_unlink(&pres->le); + tmr_cancel(&pres->tmr); + mem_deref(pres->contact); + mem_deref(pres->sub); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + return account_auth(arg, username, password, realm); +} + + +static int subscribe(struct presence *pres) +{ + const char *routev[1]; + struct ua *ua; + char uri[256]; + int err; + + /* We use the first UA */ + ua = uag_find_aor(NULL); + if (!ua) { + warning("presence: no UA found\n"); + return ENOENT; + } + + pl_strcpy(&contact_addr(pres->contact)->auri, uri, sizeof(uri)); + + routev[0] = ua_outbound(ua); + + err = sipevent_subscribe(&pres->sub, uag_sipevent_sock(), uri, NULL, + ua_aor(ua), "presence", NULL, 600, + ua_cuser(ua), routev, routev[0] ? 1 : 0, + auth_handler, ua_prm(ua), true, NULL, + notify_handler, close_handler, pres, + "%H", ua_print_supported, ua); + if (err) { + warning("presence: sipevent_subscribe failed: %m\n", err); + } + + return err; +} + + +static void tmr_handler(void *arg) +{ + struct presence *pres = arg; + + if (subscribe(pres)) { + tmr_start(&pres->tmr, wait_fail(++pres->failc) * 1000, + tmr_handler, pres); + } +} + + +static int presence_alloc(struct contact *contact) +{ + struct presence *pres; + + pres = mem_zalloc(sizeof(*pres), destructor); + if (!pres) + return ENOMEM; + + pres->status = PRESENCE_UNKNOWN; + pres->contact = mem_ref(contact); + + tmr_init(&pres->tmr); + tmr_start(&pres->tmr, 1000, tmr_handler, pres); + + list_append(&presencel, &pres->le, pres); + + return 0; +} + + +int subscriber_init(void) +{ + struct le *le; + int err = 0; + + for (le = list_head(contact_list()); le; le = le->next) { + + struct contact *c = le->data; + struct sip_addr *addr = contact_addr(c); + struct pl val; + + if (0 == sip_param_decode(&addr->params, "presence", &val) && + 0 == pl_strcasecmp(&val, "p2p")) { + + err |= presence_alloc(le->data); + } + } + + info("Subscribing to %u contacts\n", list_count(&presencel)); + + return err; +} + + +void subscriber_close(void) +{ + list_flush(&presencel); +} diff --git a/modules/qtcapture/module.mk b/modules/qtcapture/module.mk new file mode 100644 index 0000000..3c73139 --- /dev/null +++ b/modules/qtcapture/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := qtcapture +$(MOD)_SRCS += qtcapture.m +$(MOD)_LFLAGS += -framework Cocoa -framework QTKit -framework CoreVideo + +include mk/mod.mk diff --git a/modules/qtcapture/qtcapture.m b/modules/qtcapture/qtcapture.m new file mode 100644 index 0000000..b5fce0e --- /dev/null +++ b/modules/qtcapture/qtcapture.m @@ -0,0 +1,403 @@ +/** + * @file qtcapture.m Video source using QTKit QTCapture + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <QTKit/QTKit.h> + + +static void frame_handler(struct vidsrc_st *st, + const CVImageBufferRef videoFrame); +static struct vidsrc *vidsrc; + + +@interface qtcap : NSObject +{ + QTCaptureSession *sess; + QTCaptureDeviceInput *input; + QTCaptureDecompressedVideoOutput *output; + struct vidsrc_st *vsrc; +} +@end + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + qtcap *cap; + struct lock *lock; + struct vidsz app_sz; + struct vidsz sz; + struct mbuf *buf; + vidsrc_frame_h *frameh; + void *arg; + bool started; +#ifdef QTCAPTURE_RUNLOOP + struct tmr tmr; +#endif +}; + + +@implementation qtcap + + +- (id)init:(struct vidsrc_st *)st + dev:(const char *)name +{ + NSAutoreleasePool *pool; + QTCaptureDevice *dev; + BOOL success = NO; + NSError *err; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return nil; + + self = [super init]; + if (!self) + goto out; + + vsrc = st; + sess = [[QTCaptureSession alloc] init]; + if (!sess) + goto out; + + if (str_isset(name)) { + NSString *s = [NSString stringWithUTF8String:name]; + dev = [QTCaptureDevice deviceWithUniqueID:s]; + info("qtcapture: using device: %s\n", name); + } + else { + dev = [QTCaptureDevice + defaultInputDeviceWithMediaType:QTMediaTypeVideo]; + } + + success = [dev open:&err]; + if (!success) + goto out; + + input = [[QTCaptureDeviceInput alloc] initWithDevice:dev]; + success = [sess addInput:input error:&err]; + if (!success) + goto out; + + output = [[QTCaptureDecompressedVideoOutput alloc] init]; + [output setDelegate:self]; + [output setPixelBufferAttributes: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:st->app_sz.h], kCVPixelBufferHeightKey, + [NSNumber numberWithInt:st->app_sz.w], kCVPixelBufferWidthKey, +#if 0 + /* This does not work reliably */ + [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8Planar], + (id)kCVPixelBufferPixelFormatTypeKey, +#endif + nil]]; + + success = [sess addOutput:output error:&err]; + if (!success) + goto out; + + /* Start */ + [sess startRunning]; + + out: + if (!success && self) { + [self dealloc]; + self = nil; + } + + [pool release]; + + return self; +} + + +- (void)stop:(id)unused +{ + (void)unused; + + [sess stopRunning]; + + if ([[input device] isOpen]) { + [[input device] close]; + [sess removeInput:input]; + [input release]; + } + + if (output) { + [output setDelegate:nil]; + [sess removeOutput:output]; + [output release]; + } +} + + +- (void)dealloc +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [self performSelectorOnMainThread:@selector(stop:) + withObject:nil + waitUntilDone:YES]; + + [sess release]; + + [super dealloc]; + + [pool release]; +} + + +- (void)captureOutput:(QTCaptureOutput *)captureOutput + didOutputVideoFrame:(CVImageBufferRef)videoFrame + withSampleBuffer:(QTSampleBuffer *)sampleBuffer + fromConnection:(QTCaptureConnection *)connection +{ + (void)captureOutput; + (void)sampleBuffer; + (void)connection; + +#if 0 + printf("got frame: %zu x %zu - fmt=0x%08x\n", + CVPixelBufferGetWidth(videoFrame), + CVPixelBufferGetHeight(videoFrame), + CVPixelBufferGetPixelFormatType(videoFrame)); +#endif + + frame_handler(vsrc, videoFrame); +} + + +@end + + +static enum vidfmt get_pixfmt(OSType type) +{ + switch (type) { + + case kCVPixelFormatType_420YpCbCr8Planar: return VID_FMT_YUV420P; + case kCVPixelFormatType_422YpCbCr8: return VID_FMT_UYVY422; + case 0x79757673: /* yuvs */ return VID_FMT_YUYV422; + case kCVPixelFormatType_32ARGB: return VID_FMT_ARGB; + default: return -1; + } +} + + +static inline void avpict_init_planar(struct vidframe *p, + const CVImageBufferRef f) +{ + int i; + + if (!p) + return; + + for (i=0; i<3; i++) { + p->data[i] = CVPixelBufferGetBaseAddressOfPlane(f, i); + p->linesize[i] = (int)CVPixelBufferGetBytesPerRowOfPlane(f, i); + } + + p->data[3] = NULL; + p->linesize[3] = 0; +} + + +static inline void avpict_init_chunky(struct vidframe *p, + const CVImageBufferRef f) +{ + p->data[0] = CVPixelBufferGetBaseAddress(f); + p->linesize[0] = (int)CVPixelBufferGetBytesPerRow(f); + + p->data[1] = p->data[2] = p->data[3] = NULL; + p->linesize[1] = p->linesize[2] = p->linesize[3] = 0; +} + + +static void frame_handler(struct vidsrc_st *st, + const CVImageBufferRef videoFrame) +{ + struct vidframe src; + vidsrc_frame_h *frameh; + void *arg; + enum vidfmt vidfmt; + + lock_write_get(st->lock); + frameh = st->frameh; + arg = st->arg; + lock_rel(st->lock); + + if (!frameh) + return; + + vidfmt = get_pixfmt(CVPixelBufferGetPixelFormatType(videoFrame)); + if (vidfmt == (enum vidfmt)-1) { + warning("qtcapture: unknown pixel format: 0x%08x\n", + CVPixelBufferGetPixelFormatType(videoFrame)); + return; + } + + st->started = true; + + st->sz.w = (int)CVPixelBufferGetWidth(videoFrame); + st->sz.h = (int)CVPixelBufferGetHeight(videoFrame); + + CVPixelBufferLockBaseAddress(videoFrame, 0); + + if (CVPixelBufferIsPlanar(videoFrame)) + avpict_init_planar(&src, videoFrame); + else + avpict_init_chunky(&src, videoFrame); + + src.fmt = vidfmt; + src.size = st->sz; + + CVPixelBufferUnlockBaseAddress(videoFrame, 0); + + frameh(&src, arg); +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + +#ifdef QTCAPTURE_RUNLOOP + tmr_cancel(&st->tmr); +#endif + + lock_write_get(st->lock); + st->frameh = NULL; + lock_rel(st->lock); + + [st->cap dealloc]; + + mem_deref(st->buf); + mem_deref(st->lock); + + mem_deref(st->vs); +} + + +#ifdef QTCAPTURE_RUNLOOP +static void tmr_handler(void *arg) +{ + struct vidsrc_st *st = arg; + + /* Check if frame_handler was called */ + if (st->started) + return; + + tmr_start(&st->tmr, 100, tmr_handler, st); + + /* Simulate the Run-Loop */ + (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); +} +#endif + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + if (size) + st->app_sz = *size; + + err = lock_alloc(&st->lock); + if (err) + goto out; + + st->cap = [[qtcap alloc] init:st dev:dev]; + if (!st->cap) { + err = ENODEV; + goto out; + } + +#ifdef QTCAPTURE_RUNLOOP + tmr_start(&st->tmr, 10, tmr_handler, st); +#endif + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static void device_info(void) +{ + NSAutoreleasePool *pool; + NSArray *devs; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return; + + devs = [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]; + + if (devs && [devs count] > 1) { + QTCaptureDevice *d; + + debug("qtcapture: devices:\n"); + + for (d in devs) { + NSString *name = [d localizedDisplayName]; + + debug(" %s: %s\n", + [[d uniqueID] UTF8String], + [name UTF8String]); + } + } + + [pool release]; +} + + +static int module_init(void) +{ + device_info(); + return vidsrc_register(&vidsrc, "qtcapture", alloc, NULL); +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(qtcapture) = { + "qtcapture", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/quicktime/module.mk b/modules/quicktime/module.mk new file mode 100644 index 0000000..af67862 --- /dev/null +++ b/modules/quicktime/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := quicktime +$(MOD)_SRCS += quicktime.c +$(MOD)_LFLAGS += -framework QuickTime -lswscale + +include mk/mod.mk diff --git a/modules/quicktime/quicktime.c b/modules/quicktime/quicktime.c new file mode 100644 index 0000000..ad746a0 --- /dev/null +++ b/modules/quicktime/quicktime.c @@ -0,0 +1,328 @@ +/** + * @file quicktime.c Quicktime video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <pthread.h> +#include <re.h> +#include <QuickTime/QuickTimeComponents.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> +#include <baresip.h> + + +#define DEBUG_MODULE "quicktime" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + pthread_t thread; + pthread_mutex_t mutex; + struct vidsz sz; + SeqGrabComponent seq_grab; + SGDataUPP upp; + SGChannel ch; + struct mbuf *buf; + struct SwsContext *sws; + vidsrc_frame_h *frameh; + void *arg; + bool run; +}; + + +static struct vidsrc *vidsrc; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->seq_grab) { + + pthread_mutex_lock(&st->mutex); + SGStop(st->seq_grab); + pthread_mutex_unlock(&st->mutex); + + if (st->run) { + pthread_join(st->thread, NULL); + } + + if (st->upp) { + DisposeSGDataUPP(st->upp); + } + + if (st->ch) { + SGDisposeChannel(st->seq_grab, st->ch); + } + + CloseComponent(st->seq_grab); + } + + if (st->sws) + sws_freeContext(st->sws); + + mem_deref(st->buf); + mem_deref(st->vs); +} + + +static OSErr frame_handler(SGChannel c, Ptr p, long len, long *offset, + long chRefCon, TimeValue timeval, short writeType, + long refCon) +{ + struct vidsrc_st *st = (struct vidsrc_st *)refCon; + ImageDescriptionHandle imageDesc; + AVPicture pict_src, pict_dst; + struct vidframe vidframe; + ComponentResult result; + int i, ret; + int new_len; + (void)c; + (void)p; + (void)len; + (void)offset; + (void)chRefCon; + (void)timeval; + (void)writeType; + (void)refCon; + + if (!st->buf) { + + imageDesc = (ImageDescriptionHandle)NewHandle(0); + if (!imageDesc) + return noErr; + + result = SGGetChannelSampleDescription(c,(Handle)imageDesc); + if (result != noErr) { + re_printf("GetChanSampDesc: %d\n", result); + DisposeHandle((Handle)imageDesc); + return noErr; + } + + st->sz.w = (*imageDesc)->width; + st->sz.h = (*imageDesc)->height; + + /* buffer size after scaling */ + new_len = avpicture_get_size(PIX_FMT_YUV420P, + st->sz.w, st->sz.h); + +#if 1 + re_fprintf(stderr, "got frame len=%u (%ux%u) [%s] depth=%u\n", + len, st->sz.w, st->sz.h, + (*imageDesc)->name, (*imageDesc)->depth); +#endif + + DisposeHandle((Handle)imageDesc); + + st->buf = mbuf_alloc(new_len); + if (!st->buf) + return noErr; + } + + if (!st->sws) { + st->sws = sws_getContext(st->sz.w, st->sz.h, PIX_FMT_YUYV422, + st->sz.w, st->sz.h, PIX_FMT_YUV420P, + SWS_BICUBIC, NULL, NULL, NULL); + if (!st->sws) + return noErr; + } + + avpicture_fill(&pict_src, (uint8_t *)p, PIX_FMT_YUYV422, + st->sz.w, st->sz.h); + + avpicture_fill(&pict_dst, st->buf->buf, PIX_FMT_YUV420P, + st->sz.w, st->sz.h); + + ret = sws_scale(st->sws, + pict_src.data, pict_src.linesize, 0, st->sz.h, + pict_dst.data, pict_dst.linesize); + + if (ret <= 0) { + re_fprintf(stderr, "scale: sws_scale: returned %d\n", ret); + return noErr; + } + + for (i=0; i<4; i++) { + vidframe.data[i] = pict_dst.data[i]; + vidframe.linesize[i] = pict_dst.linesize[i]; + } + + vidframe.size = st->sz; + vidframe.valid = true; + + st->frameh(&vidframe, st->arg); + + return noErr; +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + ComponentResult result; + + for (;;) { + pthread_mutex_lock(&st->mutex); + result = SGIdle(st->seq_grab); + pthread_mutex_unlock(&st->mutex); + + if (result != noErr) + break; + + usleep(10000); + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, + struct vidsrc_prm *prm, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + ComponentResult result; + Rect rect; + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + re_printf("quicktime alloc: %u x %u fps=%d\n", + prm->size.w, prm->size.h, prm->fps); + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) { + re_fprintf(stderr, "mutex error: %s\n", strerror(err)); + goto out; + } + + st->seq_grab = OpenDefaultComponent(SeqGrabComponentType, 0); + if (!st->seq_grab) { + re_fprintf(stderr, "Unable to open component\n"); + err = ENODEV; + goto out; + } + + result = SGInitialize(st->seq_grab); + if (result != noErr) { + re_fprintf(stderr, "Unable to initialize sequence grabber\n"); + err = ENODEV; + goto out; + } + + result = SGSetGWorld(st->seq_grab, NULL, NULL); + if (result != noErr) { + re_fprintf(stderr, "Unable to set gworld\n"); + err = ENODEV; + goto out; + } + + result = SGSetDataRef(st->seq_grab, 0, 0, seqGrabDontMakeMovie); + if (result != noErr) { + re_fprintf(stderr, "Unable to set data ref\n"); + err = ENODEV; + goto out; + } + + result = SGNewChannel(st->seq_grab, VideoMediaType, &st->ch); + if (result != noErr) { + re_fprintf(stderr, "Unable to allocate channel (result=%d)\n", + result); + err = ENOMEM; + goto out; + } + + /* XXX: check flags */ + result = SGSetChannelUsage(st->ch, + seqGrabRecord | + seqGrabLowLatencyCapture); + if (result != noErr) { + re_fprintf(stderr, "Unable to set channel usage\n"); + err = ENODEV; + goto out; + } + + rect.top = 0; + rect.left = 0; + rect.bottom = prm->size.h; + rect.right = prm->size.w; + + result = SGSetChannelBounds(st->ch, &rect); + if (result != noErr) { + re_fprintf(stderr, "Unable to set channel bounds\n"); + err = ENODEV; + goto out; + } + + st->upp = NewSGDataUPP(frame_handler); + if (!st->upp) { + re_fprintf(stderr, "Unable to allocate data upp\n"); + err = ENOMEM; + goto out; + } + + result = SGSetDataProc(st->seq_grab, st->upp, (long)st); + if (result != noErr) { + re_fprintf(stderr, "Unable to sg callback\n"); + err = ENODEV; + goto out; + } + + result = SGStartRecord(st->seq_grab); + if (result != noErr) { + re_fprintf(stderr, "Unable to start record: %d\n", result); + err = ENODEV; + goto out; + } + + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + re_fprintf(stderr, "thread error: %s\n", strerror(err)); + goto out; + } + + st->run = true; + + out: + if (err) + mem_deref(st); + else + *stp = st; + return err; +} + + +static int qt_init(void) +{ + return vidsrc_register(&vidsrc, "quicktime", alloc, NULL); +} + + +static int qt_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(quicktime) = { + "quicktime", + "videosrc", + qt_init, + qt_close +}; diff --git a/modules/rst/audio.c b/modules/rst/audio.c new file mode 100644 index 0000000..554ec21 --- /dev/null +++ b/modules/rst/audio.c @@ -0,0 +1,263 @@ +/** + * @file rst/audio.c MP3/ICY HTTP Audio Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#define _BSD_SOURCE 1 +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <mpg123.h> +#include "rst.h" + + +struct ausrc_st { + struct ausrc *as; + pthread_t thread; + struct rst *rst; + mpg123_handle *mp3; + struct aubuf *aubuf; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; + bool run; + uint32_t psize; + uint32_t ptime; +}; + + +static struct ausrc *ausrc; + + +static void destructor(void *arg) +{ + struct ausrc_st *st = arg; + + rst_set_audio(st->rst, NULL); + mem_deref(st->rst); + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->mp3) { + mpg123_close(st->mp3); + mpg123_delete(st->mp3); + } + + mem_deref(st->aubuf); + mem_deref(st->as); +} + + +static void *play_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + uint8_t *buf; + + buf = mem_alloc(st->psize, NULL); + if (!buf) + return NULL; + + while (st->run) { + + (void)usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; +#if 1 + if (now > ts + 100) { + re_printf("rst: cpu lagging behind (%u ms)\n", + now - ts); + } +#endif + + aubuf_read(st->aubuf, buf, st->psize); + + st->rh(buf, st->psize, st->arg); + + ts += st->ptime; + } + + mem_deref(buf); + + return NULL; +} + + +static inline int decode(struct ausrc_st *st) +{ + int err, ch, encoding; + struct mbuf *mb; + long srate; + + mb = mbuf_alloc(4096); + if (!mb) + return ENOMEM; + + err = mpg123_read(st->mp3, mb->buf, mb->size, &mb->end); + + switch (err) { + + case MPG123_NEW_FORMAT: + mpg123_getformat(st->mp3, &srate, &ch, &encoding); + re_printf("rst: new format: %i hz, %i ch, encoding 0x%04x\n", + srate, ch, encoding); + /*@fallthrough@*/ + + case MPG123_OK: + case MPG123_NEED_MORE: + if (mb->end == 0) + break; + aubuf_append(st->aubuf, mb); + break; + + default: + re_printf("rst: mpg123_read error: %s\n", + mpg123_plain_strerror(err)); + break; + } + + mem_deref(mb); + + return err; +} + + +void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz) +{ + int err; + + if (!st) + return; + + err = mpg123_feed(st->mp3, buf, sz); + if (err) + return; + + while (MPG123_OK == decode(st)) + ; +} + + +static int alloc_handler(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *dev, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->errh = errh; + st->arg = arg; + + st->mp3 = mpg123_new(NULL, &err); + if (!st->mp3) { + err = ENODEV; + goto out; + } + + err = mpg123_open_feed(st->mp3); + if (err != MPG123_OK) { + re_printf("rst: mpg123_open_feed: %s\n", + mpg123_strerror(st->mp3)); + err = ENODEV; + goto out; + } + + /* Set wanted output format */ + mpg123_format_none(st->mp3); + mpg123_format(st->mp3, prm->srate, prm->ch, MPG123_ENC_SIGNED_16); + mpg123_volume(st->mp3, 0.3); + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->ptime = prm->ptime; + st->psize = sampc * 2; + + prm->fmt = AUFMT_S16LE; + + re_printf("rst: audio ptime=%u psize=%u aubuf=[%u:%u]\n", + st->ptime, st->psize, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + + /* 1 - 20 seconds of audio */ + err = aubuf_alloc(&st->aubuf, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + if (err) + goto out; + + if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) { + st->rst = mem_ref(*ctx); + } + else { + err = rst_alloc(&st->rst, dev); + if (err) + goto out; + + if (ctx) + *ctx = (struct media_ctx *)st->rst; + } + + rst_set_audio(st->rst, st); + + st->run = true; + + err = pthread_create(&st->thread, NULL, play_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int rst_audio_init(void) +{ + int err; + + err = mpg123_init(); + if (err != MPG123_OK) { + re_printf("rst: mpg123_init: %s\n", + mpg123_plain_strerror(err)); + return ENODEV; + } + + return ausrc_register(&ausrc, "rst", alloc_handler); +} + + +void rst_audio_close(void) +{ + ausrc = mem_deref(ausrc); + + mpg123_exit(); +} diff --git a/modules/rst/module.mk b/modules/rst/module.mk new file mode 100644 index 0000000..6026c22 --- /dev/null +++ b/modules/rst/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2011 Creytiv.com +# + +MOD := rst +$(MOD)_SRCS += audio.c +$(MOD)_SRCS += rst.c +$(MOD)_SRCS += video.c +$(MOD)_LFLAGS += `pkg-config --libs cairo libmpg123` +CFLAGS += `pkg-config --cflags cairo libmpg123` + +include mk/mod.mk diff --git a/modules/rst/rst.c b/modules/rst/rst.c new file mode 100644 index 0000000..21b5bfe --- /dev/null +++ b/modules/rst/rst.c @@ -0,0 +1,408 @@ +/** + * @file rst.c MP3/ICY HTTP AV Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "rst.h" + + +enum { + RETRY_WAIT = 10000, +}; + + +struct rst { + const char *id; + struct ausrc_st *ausrc_st; + struct vidsrc_st *vidsrc_st; + struct tmr tmr; + struct dns_query *dnsq; + struct tcp_conn *tc; + struct mbuf *mb; + char *host; + char *path; + char *name; + char *meta; + bool head_recv; + size_t metaint; + size_t metasz; + size_t bytec; + uint16_t port; +}; + + +static int rst_connect(struct rst *rst); + + +static void destructor(void *arg) +{ + struct rst *rst = arg; + + tmr_cancel(&rst->tmr); + mem_deref(rst->dnsq); + mem_deref(rst->tc); + mem_deref(rst->mb); + mem_deref(rst->host); + mem_deref(rst->path); + mem_deref(rst->name); + mem_deref(rst->meta); +} + + +static void reconnect(void *arg) +{ + struct rst *rst = arg; + int err; + + rst->mb = mem_deref(rst->mb); + rst->name = mem_deref(rst->name); + rst->meta = mem_deref(rst->meta); + + rst->head_recv = false; + rst->metaint = 0; + rst->metasz = 0; + rst->bytec = 0; + + err = rst_connect(rst); + if (err) + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); +} + + +static void recv_handler(struct mbuf *mb, void *arg) +{ + struct rst *rst = arg; + size_t n; + + if (!rst->head_recv) { + + struct pl hdr, name, metaint, eoh; + + if (rst->mb) { + size_t pos; + int err; + + pos = rst->mb->pos; + + rst->mb->pos = rst->mb->end; + + err = mbuf_write_mem(rst->mb, mbuf_buf(mb), + mbuf_get_left(mb)); + if (err) { + re_printf("rst: buffer write error: %m\n", + err); + rst->tc = mem_deref(rst->tc); + tmr_start(&rst->tmr, RETRY_WAIT, + reconnect, rst); + return; + } + + rst->mb->pos = pos; + } + else { + rst->mb = mem_ref(mb); + } + + if (re_regex((const char *)mbuf_buf(rst->mb), + mbuf_get_left(rst->mb), + "[^\r\n]1\r\n\r\n", &eoh)) + return; + + rst->head_recv = true; + + hdr.p = (const char *)mbuf_buf(rst->mb); + hdr.l = eoh.p + 5 - hdr.p; + + if (!re_regex(hdr.p, hdr.l, "icy-name:[ \t]*[^\r\n]+\r\n", + NULL, &name)) + (void)pl_strdup(&rst->name, &name); + + if (!re_regex(hdr.p, hdr.l, "icy-metaint:[ \t]*[0-9]+\r\n", + NULL, &metaint)) + rst->metaint = pl_u32(&metaint); + + if (rst->metaint == 0) { + re_printf("rst: icy meta interval not available\n"); + rst->tc = mem_deref(rst->tc); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } + + rst_video_update(rst->vidsrc_st, rst->name, NULL); + + rst->mb->pos += hdr.l; + + re_printf("rst: name='%s' metaint=%zu\n", + rst->name, rst->metaint); + + if (rst->mb->pos >= rst->mb->end) + return; + + mb = rst->mb; + } + + while (mb->pos < mb->end) { + + if (rst->metasz > 0) { + + n = min(mbuf_get_left(mb), rst->metasz - rst->bytec); + + if (rst->meta) + mbuf_read_mem(mb, + (uint8_t *)&rst->meta[rst->bytec], + n); + else + mb->pos += n; + + rst->bytec += n; +#if 0 + re_printf("rst: metadata %zu bytes\n", n); +#endif + if (rst->bytec >= rst->metasz) { +#if 0 + re_printf("rst: metadata: [%s]\n", rst->meta); +#endif + rst->metasz = 0; + rst->bytec = 0; + + rst_video_update(rst->vidsrc_st, rst->name, + rst->meta); + } + } + else if (rst->bytec < rst->metaint) { + + n = min(mbuf_get_left(mb), rst->metaint - rst->bytec); + + rst_audio_feed(rst->ausrc_st, mbuf_buf(mb), n); + + rst->bytec += n; + mb->pos += n; +#if 0 + re_printf("rst: mp3data %zu bytes\n", n); +#endif + } + else { + rst->metasz = mbuf_read_u8(mb) * 16; + rst->bytec = 0; + + rst->meta = mem_deref(rst->meta); + rst->meta = mem_zalloc(rst->metasz + 1, NULL); +#if 0 + re_printf("rst: metalength %zu bytes\n", rst->metasz); +#endif + } + } +} + + +static void estab_handler(void *arg) +{ + struct rst *rst = arg; + struct mbuf *mb; + int err; + + re_printf("rst: connection established\n"); + + mb = mbuf_alloc(512); + if (!mb) { + err = ENOMEM; + goto out; + } + + err = mbuf_printf(mb, + "GET %s HTTP/1.0\r\n" + "Icy-MetaData: 1\r\n" + "\r\n", + rst->path); + if (err) + goto out; + + mb->pos = 0; + + err = tcp_send(rst->tc, mb); + if (err) + goto out; + + out: + if (err) { + re_printf("rst: error sending HTTP request: %m\n", err); + } + + mem_deref(mb); +} + + +static void close_handler(int err, void *arg) +{ + struct rst *rst = arg; + + re_printf("rst: tcp closed: %i\n", err); + + rst->tc = mem_deref(rst->tc); + + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); +} + + +static void dns_handler(int err, const struct dnshdr *hdr, struct list *ansl, + struct list *authl, struct list *addl, void *arg) +{ + struct rst *rst = arg; + struct dnsrr *rr; + struct sa srv; + + (void)err; + (void)hdr; + (void)authl; + (void)addl; + + rr = dns_rrlist_find(ansl, rst->host, DNS_TYPE_A, DNS_CLASS_IN, true); + if (!rr) { + re_printf("rst: unable to resolve: %s\n", rst->host); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } + + sa_set_in(&srv, rr->rdata.a.addr, rst->port); + + err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler, + close_handler, rst); + if (err) { + re_printf("rst: tcp connect error: %m\n", err); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } +} + + +static int rst_connect(struct rst *rst) +{ + struct sa srv; + int err; + + if (!sa_set_str(&srv, rst->host, rst->port)) { + + err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler, + close_handler, rst); + if (err) { + re_printf("rst: tcp connect error: %m\n", err); + } + } + else { + err = dnsc_query(&rst->dnsq, net_dnsc(), rst->host, DNS_TYPE_A, + DNS_CLASS_IN, true, dns_handler, rst); + if (err) { + re_printf("rst: dns query error: %m\n", err); + } + } + + return err; +} + + +int rst_alloc(struct rst **rstp, const char *dev) +{ + struct pl host, port, path; + struct rst *rst; + int err; + + if (!rstp || !dev) + return EINVAL; + + if (re_regex(dev, strlen(dev), "http://[^:/]+[:]*[0-9]*[^]+", + &host, NULL, &port, &path)) { + re_printf("rst: bad http url: %s\n", dev); + return EBADMSG; + } + + rst = mem_zalloc(sizeof(*rst), destructor); + if (!rst) + return ENOMEM; + + rst->id = "rst"; + + err = pl_strdup(&rst->host, &host); + if (err) + goto out; + + err = pl_strdup(&rst->path, &path); + if (err) + goto out; + + rst->port = pl_u32(&port); + rst->port = rst->port ? rst->port : 80; + + err = rst_connect(rst); + if (err) + goto out; + + out: + if (err) + mem_deref(rst); + else + *rstp = rst; + + return err; +} + + +void rst_set_audio(struct rst *rst, struct ausrc_st *st) +{ + if (!rst) + return; + + rst->ausrc_st = st; +} + + +void rst_set_video(struct rst *rst, struct vidsrc_st *st) +{ + if (!rst) + return; + + rst->vidsrc_st = st; +} + + +static int module_init(void) +{ + int err; + + err = rst_audio_init(); + if (err) + goto out; + + err = rst_video_init(); + if (err) + goto out; + + out: + if (err) { + rst_audio_close(); + rst_video_close(); + } + + return err; +} + + +static int module_close(void) +{ + rst_audio_close(); + rst_video_close(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(rst) = { + "rst", + "avsrc", + module_init, + module_close +}; diff --git a/modules/rst/rst.h b/modules/rst/rst.h new file mode 100644 index 0000000..950a7de --- /dev/null +++ b/modules/rst/rst.h @@ -0,0 +1,26 @@ +/** + * @file rst.h MP3/ICY HTTP AV Source + * + * Copyright (C) 2011 Creytiv.com + */ + + +/* Shared AV state */ +struct rst; + +int rst_alloc(struct rst **rstp, const char *dev); +void rst_set_audio(struct rst *rst, struct ausrc_st *st); +void rst_set_video(struct rst *rst, struct vidsrc_st *st); + + +/* Audio */ +void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz); +int rst_audio_init(void); +void rst_audio_close(void); + + +/* Video */ +void rst_video_update(struct vidsrc_st *st, const char *name, + const char *meta); +int rst_video_init(void); +void rst_video_close(void); diff --git a/modules/rst/video.c b/modules/rst/video.c new file mode 100644 index 0000000..db6bfc9 --- /dev/null +++ b/modules/rst/video.c @@ -0,0 +1,280 @@ +/** + * @file rst/video.c MP3/ICY HTTP Video Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#define _BSD_SOURCE 1 +#include <pthread.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <cairo/cairo.h> +#include "rst.h" + + +struct vidsrc_st { + struct vidsrc *vs; + pthread_mutex_t mutex; + pthread_t thread; + struct vidsrc_prm prm; + struct vidsz size; + struct rst *rst; + cairo_surface_t *surface; + cairo_t *cairo; + struct vidframe *frame; + vidsrc_frame_h *frameh; + void *arg; + bool run; +}; + + +static struct vidsrc *vidsrc; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + rst_set_video(st->rst, NULL); + mem_deref(st->rst); + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->cairo) + cairo_destroy(st->cairo); + + if (st->surface) + cairo_surface_destroy(st->surface); + + mem_deref(st->frame); + mem_deref(st->vs); +} + + +static void *video_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct vidsrc_st *st = arg; + + while (st->run) { + + (void)usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; + + pthread_mutex_lock(&st->mutex); + st->frameh(st->frame, st->arg); + pthread_mutex_unlock(&st->mutex); + + ts += 1000/st->prm.fps; + } + + return NULL; +} + + +static void background(cairo_t *cr, unsigned width, unsigned height) +{ + cairo_pattern_t *pat; + double r, g, b; + + pat = cairo_pattern_create_linear(0.0, 0.0, 0.0, height); + if (!pat) + return; + + r = 0.0; + g = 0.0; + b = 0.8; + + cairo_pattern_add_color_stop_rgba(pat, 1, r, g, b, 1); + cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0.2, 1); + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_source(cr, pat); + cairo_fill(cr); + + cairo_pattern_destroy(pat); +} + + +static void icy_printf(cairo_t *cr, int x, int y, double size, + const char *fmt, ...) +{ + char buf[4096] = ""; + va_list ap; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* Draw text */ + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, size); + cairo_move_to(cr, x, y); + cairo_text_path(cr, buf); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); +} + + +static size_t linelen(const struct pl *pl) +{ + size_t len = 72, i; + + if (pl->l <= len) + return pl->l; + + for (i=len; i>1; i--) { + + if (pl->p[i-1] == ' ') { + len = i; + break; + } + } + + return len; +} + + +void rst_video_update(struct vidsrc_st *st, const char *name, const char *meta) +{ + struct vidframe frame; + + if (!st) + return; + + background(st->cairo, st->size.w, st->size.h); + + icy_printf(st->cairo, 50, 100, 40.0, "%s", name); + + if (meta) { + + struct pl title; + + if (!re_regex(meta, strlen(meta), + "StreamTitle='[ \t]*[^;]+;", NULL, &title)) { + + unsigned i; + + title.l--; + + for (i=0; title.l; i++) { + + const size_t len = linelen(&title); + + icy_printf(st->cairo, 50, 150 + 25*i, 18.0, + "%b", title.p, len); + + title.p += len; + title.l -= len; + } + } + } + + vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size, + cairo_image_surface_get_data(st->surface)); + + pthread_mutex_lock(&st->mutex); + vidconv(st->frame, &frame, NULL); + pthread_mutex_unlock(&st->mutex); +} + + +static int alloc_handler(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + + (void)fmt; + (void)errorh; + + if (!stp || !vs || !prm || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + st->vs = mem_ref(vs); + st->prm = *prm; + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + size->w, size->h); + if (!st->surface) { + err = ENOMEM; + goto out; + } + + st->cairo = cairo_create(st->surface); + if (!st->cairo) { + err = ENOMEM; + goto out; + } + + err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size); + if (err) + goto out; + + vidframe_fill(st->frame, 0, 0, 0); + + if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) { + st->rst = mem_ref(*ctx); + } + else { + err = rst_alloc(&st->rst, dev); + if (err) + goto out; + + if (ctx) + *ctx = (struct media_ctx *)st->rst; + } + + rst_set_video(st->rst, st); + + st->run = true; + + err = pthread_create(&st->thread, NULL, video_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int rst_video_init(void) +{ + return vidsrc_register(&vidsrc, "rst", alloc_handler, NULL); +} + + +void rst_video_close(void) +{ + vidsrc = mem_deref(vidsrc); +} diff --git a/modules/sdl/module.mk b/modules/sdl/module.mk new file mode 100644 index 0000000..bf100e5 --- /dev/null +++ b/modules/sdl/module.mk @@ -0,0 +1,19 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sdl +$(MOD)_SRCS += sdl.c +$(MOD)_SRCS += util.c + +CFLAGS += -DUSE_SDL +$(MOD)_LFLAGS += -lSDL +ifeq ($(OS),darwin) +# note: APP_LFLAGS is needed, as main.o links to -lSDLmain +APP_LFLAGS += -lSDL -lSDLmain -lobjc \ + -framework CoreFoundation -framework Foundation -framework Cocoa +endif + +include mk/mod.mk diff --git a/modules/sdl/sdl.c b/modules/sdl/sdl.c new file mode 100644 index 0000000..eb902b3 --- /dev/null +++ b/modules/sdl/sdl.c @@ -0,0 +1,319 @@ +/** + * @file sdl/sdl.c SDL - Simple DirectMedia Layer v1.2 + * + * Copyright (C) 2010 Creytiv.com + */ +#include <SDL/SDL.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "sdl.h" + + +/** Local constants */ +enum { + KEY_RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct vidisp_st { + struct vidisp *vd; /* inheritance */ +}; + +/** Global SDL data */ +static struct { + struct tmr tmr; + SDL_Surface *screen; /**< SDL Surface */ + SDL_Overlay *bmp; /**< SDL YUV Overlay */ + struct vidsz size; /**< Current size */ + vidisp_resize_h *resizeh; /**< Screen resize handler */ + void *arg; /**< Handler argument */ + bool fullscreen; + bool open; +} sdl; + + +static struct vidisp *vid; + + +static void event_handler(void *arg); + + +static void sdl_reset(void) +{ + if (sdl.bmp) { + SDL_FreeYUVOverlay(sdl.bmp); + sdl.bmp = NULL; + } + + if (sdl.screen) { + SDL_FreeSurface(sdl.screen); + sdl.screen = NULL; + } +} + + +static void handle_resize(int w, int h) +{ + struct vidsz size; + + size.w = w; + size.h = h; + + /* notify app */ + if (sdl.resizeh) + sdl.resizeh(&size, sdl.arg); +} + + +static void timeout(void *arg) +{ + (void)arg; + + tmr_start(&sdl.tmr, 1, event_handler, NULL); + + /* Emulate key-release */ + ui_input(0x00); +} + + +static void event_handler(void *arg) +{ + SDL_Event event; + char ch; + + (void)arg; + + tmr_start(&sdl.tmr, 100, event_handler, NULL); + + while (SDL_PollEvent(&event)) { + + switch (event.type) { + + case SDL_KEYDOWN: + + switch (event.key.keysym.sym) { + + case SDLK_ESCAPE: + if (!sdl.fullscreen) + break; + + sdl.fullscreen = false; + sdl_reset(); + break; + + case SDLK_f: + if (sdl.fullscreen) + break; + + sdl.fullscreen = true; + sdl_reset(); + break; + + default: + ch = event.key.keysym.unicode & 0x7f; + + /* Relay key-press to UI subsystem */ + if (isprint(ch)) { + tmr_start(&sdl.tmr, KEY_RELEASE_VAL, + timeout, NULL); + ui_input(ch); + } + break; + } + + break; + + case SDL_VIDEORESIZE: + handle_resize(event.resize.w, event.resize.h); + break; + + case SDL_QUIT: + ui_input('q'); + break; + + default: + break; + } + } +} + + +static int sdl_open(void) +{ + if (sdl.open) + return 0; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + warning("sdl: unable to init SDL: %s\n", SDL_GetError()); + return ENODEV; + } + + SDL_EnableUNICODE(1); + + tmr_start(&sdl.tmr, 100, event_handler, NULL); + sdl.open = true; + + return 0; +} + + +static void sdl_close(void) +{ + tmr_cancel(&sdl.tmr); + sdl_reset(); + + if (sdl.open) { + SDL_Quit(); + sdl.open = false; + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + mem_deref(st->vd); + sdl_close(); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err; + + /* Not used by SDL */ + (void)prm; + (void)dev; + + if (sdl.open) + return EBUSY; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + sdl.resizeh = resizeh; + sdl.arg = arg; + + err = sdl_open(); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +/** + * Display a video frame + * + * @param st Video display state + * @param title Window title + * @param frame Video frame + * + * @return 0 if success, otherwise errorcode + * + * @note: On Darwin, this must be called from the main thread + */ +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + SDL_Rect rect; + + if (!st || !sdl.open) + return EINVAL; + + if (!vidsz_cmp(&sdl.size, &frame->size)) { + if (sdl.size.w && sdl.size.h) { + info("sdl: reset size %u x %u ---> %u x %u\n", + sdl.size.w, sdl.size.h, + frame->size.w, frame->size.h); + } + sdl_reset(); + } + + if (!sdl.screen) { + int flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; + char capt[256]; + + if (sdl.fullscreen) + flags |= SDL_FULLSCREEN; + else if (sdl.resizeh) + flags |= SDL_RESIZABLE; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + SDL_WM_SetCaption(capt, capt); + + sdl.screen = SDL_SetVideoMode(frame->size.w, frame->size.h, + 0, flags); + if (!sdl.screen) { + warning("sdl: unable to get video screen: %s\n", + SDL_GetError()); + return ENODEV; + } + + sdl.size = frame->size; + } + + if (!sdl.bmp) { + sdl.bmp = SDL_CreateYUVOverlay(frame->size.w, frame->size.h, + SDL_YV12_OVERLAY, sdl.screen); + if (!sdl.bmp) { + warning("sdl: unable to create overlay: %s\n", + SDL_GetError()); + return ENODEV; + } + } + + SDL_LockYUVOverlay(sdl.bmp); + picture_copy(sdl.bmp->pixels, sdl.bmp->pitches, frame); + SDL_UnlockYUVOverlay(sdl.bmp); + + rect.x = 0; + rect.y = 0; + rect.w = sdl.size.w; + rect.h = sdl.size.h; + + SDL_DisplayYUVOverlay(sdl.bmp, &rect); + + return 0; +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "sdl", alloc, NULL, display, NULL); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl) = { + "sdl", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/sdl/sdl.h b/modules/sdl/sdl.h new file mode 100644 index 0000000..992f70d --- /dev/null +++ b/modules/sdl/sdl.h @@ -0,0 +1,9 @@ +/** + * @file sdl.h Simple DirectMedia Layer module -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +void picture_copy(uint8_t *data[4], uint16_t linesize[4], + const struct vidframe *frame); diff --git a/modules/sdl/util.c b/modules/sdl/util.c new file mode 100644 index 0000000..59a1cdd --- /dev/null +++ b/modules/sdl/util.c @@ -0,0 +1,54 @@ +/** + * @file util.c Simple DirectMedia Layer module -- utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "sdl.h" + + +static void img_copy_plane(uint8_t *dst, int dst_wrap, + const uint8_t *src, int src_wrap, + int width, int height) +{ + if (!dst || !src) + return; + + for (;height > 0; height--) { + memcpy(dst, src, width); + dst += dst_wrap; + src += src_wrap; + } +} + + +static int get_plane_bytewidth(int width, int plane) +{ + if (plane == 1 || plane == 2) + width = -((-width) >> 1); + + return (width * 8 + 7) >> 3; +} + + +void picture_copy(uint8_t *data[4], uint16_t linesize[4], + const struct vidframe *frame) +{ + const int map[3] = {0, 2, 1}; + int i; + + for (i=0; i<3; i++) { + int h; + int bwidth = get_plane_bytewidth(frame->size.w, i); + h = frame->size.h; + if (i == 1 || i == 2) { + h = -((-frame->size.h) >> 1); + } + img_copy_plane(data[map[i]], linesize[map[i]], + frame->data[i], frame->linesize[i], + bwidth, h); + } +} diff --git a/modules/sdl2/module.mk b/modules/sdl2/module.mk new file mode 100644 index 0000000..958f7ea --- /dev/null +++ b/modules/sdl2/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sdl2 +$(MOD)_SRCS += sdl.c +$(MOD)_LFLAGS += -lSDL2 + +include mk/mod.mk diff --git a/modules/sdl2/sdl.c b/modules/sdl2/sdl.c new file mode 100644 index 0000000..e688689 --- /dev/null +++ b/modules/sdl2/sdl.c @@ -0,0 +1,238 @@ +/** + * @file sdl2/sdl.c Simple DirectMedia Layer module for SDL v2.0 + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <SDL2/SDL.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + SDL_Window *window; /**< SDL Window */ + SDL_Renderer *renderer; /**< SDL Renderer */ + SDL_Texture *texture; /**< Texture for pixels */ + struct vidsz size; /**< Current size */ + bool fullscreen; /**< Fullscreen flag */ +}; + + +static struct vidisp *vid; + + +static void sdl_reset(struct vidisp_st *st) +{ + if (st->texture) { + /*SDL_DestroyTexture(st->texture);*/ + st->texture = NULL; + } + + if (st->renderer) { + /*SDL_DestroyRenderer(st->renderer);*/ + st->renderer = NULL; + } + + if (st->window) { + SDL_DestroyWindow(st->window); + st->window = NULL; + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + sdl_reset(st); + + mem_deref(st->vd); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + + /* Not used by SDL */ + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + void *pixels; + uint8_t *p; + int pitch, ret; + unsigned i, h; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("sdl: reset size: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + sdl_reset(st); + } + + if (!st->window) { + Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS; + char capt[256]; + + if (st->fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + st->window = SDL_CreateWindow(capt, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + frame->size.w, frame->size.h, + flags); + if (!st->window) { + warning("sdl: unable to create sdl window: %s\n", + SDL_GetError()); + return ENODEV; + } + + st->size = frame->size; + + SDL_RaiseWindow(st->window); + SDL_SetWindowBordered(st->window, true); + SDL_ShowWindow(st->window); + } + + if (!st->renderer) { + + Uint32 flags = 0; + + flags |= SDL_RENDERER_ACCELERATED; + flags |= SDL_RENDERER_PRESENTVSYNC; + + st->renderer = SDL_CreateRenderer(st->window, -1, flags); + if (!st->renderer) { + warning("sdl: unable to create renderer: %s\n", + SDL_GetError()); + return ENOMEM; + } + } + + if (!st->texture) { + + st->texture = SDL_CreateTexture(st->renderer, + SDL_PIXELFORMAT_IYUV, + SDL_TEXTUREACCESS_STREAMING, + frame->size.w, frame->size.h); + if (!st->texture) { + warning("sdl: unable to create texture: %s\n", + SDL_GetError()); + return ENODEV; + } + } + + ret = SDL_LockTexture(st->texture, NULL, &pixels, &pitch); + if (ret != 0) { + warning("sdl: unable to lock texture (ret=%d)\n", ret); + return ENODEV; + } + + p = pixels; + for (i=0; i<3; i++) { + + const uint8_t *s = frame->data[i]; + const unsigned stp = frame->linesize[0] / frame->linesize[i]; + const unsigned sz = frame->size.w / stp; + + for (h = 0; h < frame->size.h; h += stp) { + + memcpy(p, s, sz); + + s += frame->linesize[i]; + p += (pitch / stp); + } + } + + SDL_UnlockTexture(st->texture); + + /* Blit the sprite onto the screen */ + SDL_RenderCopy(st->renderer, st->texture, NULL, NULL); + + /* Update the screen! */ + SDL_RenderPresent(st->renderer); + + return 0; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st || !st->window) + return; + + SDL_HideWindow(st->window); +} + + +static int module_init(void) +{ + int err; + + if (SDL_VideoInit(NULL) < 0) { + warning("sdl2: unable to init Video: %s\n", + SDL_GetError()); + return ENODEV; + } + + err = vidisp_register(&vid, "sdl2", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + SDL_VideoQuit(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl2) = { + "sdl2", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/selfview/module.mk b/modules/selfview/module.mk new file mode 100644 index 0000000..2719433 --- /dev/null +++ b/modules/selfview/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := selfview +$(MOD)_SRCS += selfview.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/selfview/selfview.c b/modules/selfview/selfview.c new file mode 100644 index 0000000..cf5cdc3 --- /dev/null +++ b/modules/selfview/selfview.c @@ -0,0 +1,267 @@ +/** + * @file selfview.c Selfview Video-Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* shared state */ +struct selfview { + struct lock *lock; /**< Protect frame */ + struct vidframe *frame; /**< Copy of encoded frame */ +}; + +struct selfview_enc { + struct vidfilt_enc_st vf; /**< Inheritance */ + struct selfview *selfview; /**< Ref. to shared state */ + struct vidisp_st *disp; /**< Selfview display */ +}; + +struct selfview_dec { + struct vidfilt_dec_st vf; /**< Inheritance */ + struct selfview *selfview; /**< Ref. to shared state */ +}; + + +static struct vidsz selfview_size = {0, 0}; + + +static void destructor(void *arg) +{ + struct selfview *st = arg; + + lock_write_get(st->lock); + mem_deref(st->frame); + lock_rel(st->lock); + mem_deref(st->lock); +} + + +static void encode_destructor(void *arg) +{ + struct selfview_enc *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->selfview); + mem_deref(st->disp); +} + + +static void decode_destructor(void *arg) +{ + struct selfview_dec *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->selfview); +} + + +static int selfview_alloc(struct selfview **selfviewp, void **ctx) +{ + struct selfview *selfview; + int err; + + if (!selfviewp || !ctx) + return EINVAL; + + if (*ctx) { + *selfviewp = mem_ref(*ctx); + } + else { + selfview = mem_zalloc(sizeof(*selfview), destructor); + if (!selfview) + return ENOMEM; + + err = lock_alloc(&selfview->lock); + if (err) + return err; + + *ctx = selfview; + *selfviewp = selfview; + } + + return 0; +} + + +static int encode_update(struct vidfilt_enc_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct selfview_enc *st; + int err; + + if (!stp || !ctx || !vf) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + err = selfview_alloc(&st->selfview, ctx); + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct vidfilt_dec_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct selfview_dec *st; + int err; + + if (!stp || !ctx || !vf) + return EINVAL; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + err = selfview_alloc(&st->selfview, ctx); + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_dec_st *)st; + + return err; +} + + +static int encode_win(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + struct selfview_enc *enc = (struct selfview_enc *)st; + int err; + + if (!frame) + return 0; + + if (!enc->disp) { + + err = vidisp_alloc(&enc->disp, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + return vidisp_display(enc->disp, "Selfview", frame); +} + + +static int encode_pip(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + struct selfview_enc *enc = (struct selfview_enc *)st; + struct selfview *selfview = enc->selfview; + int err = 0; + + if (!frame) + return 0; + + lock_write_get(selfview->lock); + if (!selfview->frame) { + struct vidsz sz; + + /* Use size if configured, or else 20% of main window */ + if (selfview_size.w && selfview_size.h) { + sz = selfview_size; + } + else { + sz.w = frame->size.w / 5; + sz.h = frame->size.h / 5; + } + + err = vidframe_alloc(&selfview->frame, VID_FMT_YUV420P, &sz); + } + if (!err) + vidconv(selfview->frame, frame, NULL); + lock_rel(selfview->lock); + + return err; +} + + +static int decode_pip(struct vidfilt_dec_st *st, struct vidframe *frame) +{ + struct selfview_dec *dec = (struct selfview_dec *)st; + struct selfview *sv = dec->selfview; + + if (!frame) + return 0; + + lock_read_get(sv->lock); + if (sv->frame) { + struct vidrect rect; + + rect.w = min(sv->frame->size.w, frame->size.w/2); + rect.h = min(sv->frame->size.h, frame->size.h/2); + if (rect.w <= (frame->size.w - 10)) + rect.x = frame->size.w - rect.w - 10; + else + rect.x = frame->size.w/2; + if (rect.h <= (frame->size.h - 10)) + rect.y = frame->size.h - rect.h - 10; + else + rect.y = frame->size.h/2; + + vidconv(frame, sv->frame, &rect); + + vidframe_draw_rect(frame, rect.x, rect.y, rect.w, rect.h, + 127, 127, 127); + } + lock_rel(sv->lock); + + return 0; +} + + +static struct vidfilt selfview_win = { + LE_INIT, "selfview_window", encode_update, encode_win, NULL, NULL +}; +static struct vidfilt selfview_pip = { + LE_INIT, "selfview_pip", + encode_update, encode_pip, decode_update, decode_pip +}; + + +static int module_init(void) +{ + struct pl pl; + + if (conf_get(conf_cur(), "video_selfview", &pl)) + return 0; + + if (0 == pl_strcasecmp(&pl, "window")) + vidfilt_register(&selfview_win); + else if (0 == pl_strcasecmp(&pl, "pip")) + vidfilt_register(&selfview_pip); + + (void)conf_get_vidsz(conf_cur(), "selfview_size", &selfview_size); + + return 0; +} + + +static int module_close(void) +{ + vidfilt_unregister(&selfview_win); + vidfilt_unregister(&selfview_pip); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(selfview) = { + "selfview", + "vidfilt", + module_init, + module_close +}; diff --git a/modules/silk/module.mk b/modules/silk/module.mk new file mode 100644 index 0000000..ac8ebb1 --- /dev/null +++ b/modules/silk/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := silk +$(MOD)_SRCS += silk.c +$(MOD)_LFLAGS += -lSKP_SILK_SDK + +include mk/mod.mk diff --git a/modules/silk/silk.c b/modules/silk/silk.c new file mode 100644 index 0000000..95f708c --- /dev/null +++ b/modules/silk/silk.c @@ -0,0 +1,259 @@ +/** + * @file silk.c Skype SILK audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <silk/SKP_Silk_SDK_API.h> + + +/* + * References: https://developer.skype.com/silk + */ + + +enum { + MAX_BYTES_PER_FRAME = 250, + MAX_FRAME_SIZE = 2*480, +}; + + +struct auenc_state { + void *enc; + SKP_SILK_SDK_EncControlStruct encControl; +}; + +struct audec_state { + void *dec; + SKP_SILK_SDK_DecControlStruct decControl; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + mem_deref(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + mem_deref(st->dec); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int ret, err = 0; + int32_t enc_size; + (void)fmtp; + + if (!aesp || !ac || !prm) + return EINVAL; + if (*aesp) + return 0; + + ret = SKP_Silk_SDK_Get_Encoder_Size(&enc_size); + if (ret || enc_size <= 0) + return EINVAL; + + st = mem_alloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->enc = mem_alloc(enc_size, NULL); + if (!st->enc) { + err = ENOMEM; + goto out; + } + + ret = SKP_Silk_SDK_InitEncoder(st->enc, &st->encControl); + if (ret) { + err = EPROTO; + goto out; + } + + st->encControl.API_sampleRate = ac->srate; + st->encControl.maxInternalSampleRate = ac->srate; + st->encControl.packetSize = prm->ptime * ac->srate / 1000; + st->encControl.bitRate = 64000; + st->encControl.complexity = 2; + st->encControl.useInBandFEC = 0; + st->encControl.useDTX = 0; + + info("silk: encoder: %dHz, psize=%d, bitrate=%d, complex=%d," + " fec=%d, dtx=%d\n", + st->encControl.API_sampleRate, + st->encControl.packetSize, + st->encControl.bitRate, + st->encControl.complexity, + st->encControl.useInBandFEC, + st->encControl.useDTX); + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int ret, err = 0; + int32_t dec_size; + (void)fmtp; + + if (*adsp) + return 0; + + ret = SKP_Silk_SDK_Get_Decoder_Size(&dec_size); + if (ret || dec_size <= 0) + return EINVAL; + + st = mem_alloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->dec = mem_alloc(dec_size, NULL); + if (!st->dec) { + err = ENOMEM; + goto out; + } + + ret = SKP_Silk_SDK_InitDecoder(st->dec); + if (ret) { + err = EPROTO; + goto out; + } + + st->decControl.API_sampleRate = ac->srate; + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int ret; + int16_t nBytesOut; + + if (*len < MAX_BYTES_PER_FRAME) + return ENOMEM; + + nBytesOut = *len; + ret = SKP_Silk_SDK_Encode(st->enc, + &st->encControl, + sampv, + (int)sampc, + buf, + &nBytesOut); + if (ret) { + warning("silk: SKP_Silk_SDK_Encode: ret=%d\n", ret); + } + + *len = nBytesOut; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + int16_t nsamp = *sampc; + int ret; + + ret = SKP_Silk_SDK_Decode(st->dec, + &st->decControl, + 0, + buf, + (int)len, + sampv, + &nsamp); + if (ret) { + warning("silk: SKP_Silk_SDK_Decode: ret=%d\n", ret); + } + + *sampc = nsamp; + + return 0; +} + + +static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + int16_t nsamp = *sampc; + int ret; + + ret = SKP_Silk_SDK_Decode(st->dec, + &st->decControl, + 1, + NULL, + 0, + sampv, + &nsamp); + if (ret) + return EPROTO; + + *sampc = nsamp; + + return 0; +} + + +static struct aucodec silk[] = { + { + LE_INIT, 0, "SILK", 24000, 1, NULL, + encode_update, encode, decode_update, decode, plc, 0, 0 + }, + +}; + + +static int module_init(void) +{ + debug("silk: SILK %s\n", SKP_Silk_SDK_get_version()); + + aucodec_register(&silk[0]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(silk); + + while (i--) + aucodec_unregister(&silk[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(silk) = { + "silk", + "codec", + module_init, + module_close +}; diff --git a/modules/snapshot/module.mk b/modules/snapshot/module.mk new file mode 100644 index 0000000..1be53bd --- /dev/null +++ b/modules/snapshot/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := snapshot +$(MOD)_SRCS += snapshot.c png_vf.c +$(MOD)_LFLAGS += -lpng + +include mk/mod.mk diff --git a/modules/snapshot/png_vf.c b/modules/snapshot/png_vf.c new file mode 100644 index 0000000..d7f1f8a --- /dev/null +++ b/modules/snapshot/png_vf.c @@ -0,0 +1,188 @@ +/** + * @file png_vf.c Write vidframe to a PNG-file + * + * Author: Doug Blewett + * Review: Alfred E. Heggestad + */ +#define _BSD_SOURCE 1 +#include <string.h> +#include <time.h> +#include <png.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "png_vf.h" + + +static char *png_filename(const struct tm *tmx, const char *name, + char *buf, unsigned int length); +static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers, + int png_height); + + +int png_save_vidframe(const struct vidframe *vf, const char *path) +{ + png_byte **png_row_pointers = NULL; + png_byte *row; + const png_byte *p; + png_byte red, green, blue; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + FILE *fp = NULL; + size_t x, y; + unsigned int width = vf->size.w & ~1; + unsigned int height = vf->size.h & ~1; + unsigned int bytes_per_pixel = 3; /* RGB format */ + time_t tnow; + struct tm *tmx; + char filename_buf[64]; + struct vidframe *f2 = NULL; + int err = 0; + + tnow = time(NULL); + tmx = localtime(&tnow); + + if (vf->fmt != VID_FMT_RGB32) { + + err = vidframe_alloc(&f2, VID_FMT_RGB32, &vf->size); + if (err) + goto out; + + vidconv(f2, vf, NULL); + vf = f2; + } + + /* Initialize the write struct. */ + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) { + err = ENOMEM; + goto out; + } + + /* Initialize the info struct. */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + err = ENOMEM; + goto out; + } + + /* Set up error handling. */ + if (setjmp(png_jmpbuf(png_ptr))) { + err = ENOMEM; + goto out; + } + + /* Set image attributes. */ + png_set_IHDR(png_ptr, + info_ptr, + width, + height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Initialize rows of PNG + * bytes_per_row = width * bytes_per_pixel; + */ + png_row_pointers = png_malloc(png_ptr, + height * sizeof(png_byte *)); + + for (y = 0; y < height; ++y) { + png_row_pointers[y] = + (png_byte *) png_malloc(png_ptr, + width * sizeof(uint8_t) * + bytes_per_pixel); + } + + p = vf->data[0]; + for (y = 0; y < height; ++y) { + + row = png_row_pointers[y]; + + for (x = 0; x < width; ++x) { + + red = *p++; + green = *p++; + blue = *p++; + + *row++ = blue; + *row++ = green; + *row++ = red; + + ++p; /* skip alpha */ + } + } + + /* Write the image data. */ + fp = fopen(png_filename(tmx, path, + filename_buf, sizeof(filename_buf)), "wb"); + if (fp == NULL) { + err = errno; + goto out; + } + + png_init_io(png_ptr, fp); + png_set_rows(png_ptr, info_ptr, png_row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + info("png: wrote %s\n", filename_buf); + + out: + /* Finish writing. */ + mem_deref(f2); + png_save_free(png_ptr, png_row_pointers, height); + png_destroy_write_struct(&png_ptr, &info_ptr); + if (fp) + fclose(fp); + + return 0; +} + + +static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers, + int png_height) +{ + int y; + + /* Cleanup. */ + if (png_height == 0 || png_row_pointers == NULL) + return; + + for (y = 0; y < png_height; y++) { + png_free(png_ptr, png_row_pointers[y]); + } + png_free(png_ptr, png_row_pointers); +} + + +static char *png_filename(const struct tm *tmx, const char *name, + char *buf, unsigned int length) +{ + /* + * -2013-03-03-15-22-56.png - 24 chars + */ + if (strlen(name) + 24 >= length) { + buf[0] = '\0'; + return buf; + } + + sprintf(buf, (tmx->tm_mon < 9 ? "%s-%d-0%d" : "%s-%d-%d"), name, + 1900 + tmx->tm_year, tmx->tm_mon + 1); + + sprintf(buf + strlen(buf), (tmx->tm_mday < 10 ? "-0%d" : "-%d"), + tmx->tm_mday); + + sprintf(buf + strlen(buf), (tmx->tm_hour < 10 ? "-0%d" : "-%d"), + tmx->tm_hour); + + sprintf(buf + strlen(buf), (tmx->tm_min < 10 ? "-0%d" : "-%d"), + tmx->tm_min); + + sprintf(buf + strlen(buf), (tmx->tm_sec < 10 ? "-0%d.png" : "-%d.png"), + tmx->tm_sec); + + return buf; +} diff --git a/modules/snapshot/png_vf.h b/modules/snapshot/png_vf.h new file mode 100644 index 0000000..17660cf --- /dev/null +++ b/modules/snapshot/png_vf.h @@ -0,0 +1,6 @@ +/** + * @file png_vf.h + */ + + +int png_save_vidframe(const struct vidframe *vf, const char *path); diff --git a/modules/snapshot/snapshot.c b/modules/snapshot/snapshot.c new file mode 100644 index 0000000..b8a01de --- /dev/null +++ b/modules/snapshot/snapshot.c @@ -0,0 +1,90 @@ +/** + * @file snapshot.c Snapshot Video-Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "png_vf.h" + + +static bool flag_enc, flag_dec; + + +static int encode(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + (void)st; + + if (!frame) + return 0; + + if (flag_enc) { + flag_enc = false; + png_save_vidframe(frame, "snapshot-send"); + } + + return 0; +} + + +static int decode(struct vidfilt_dec_st *st, struct vidframe *frame) +{ + (void)st; + + if (!frame) + return 0; + + if (flag_dec) { + flag_dec = false; + png_save_vidframe(frame, "snapshot-recv"); + } + + return 0; +} + + +static int do_snapshot(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + /* NOTE: not re-entrant */ + flag_enc = flag_dec = true; + + return 0; +} + + +static struct vidfilt snapshot = { + LE_INIT, "snapshot", NULL, encode, NULL, decode, +}; + + +static const struct cmd cmdv[] = { + {'o', 0, "Take video snapshot", do_snapshot }, +}; + + +static int module_init(void) +{ + vidfilt_register(&snapshot); + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + vidfilt_unregister(&snapshot); + cmd_unregister(cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(snapshot) = { + "snapshot", + "vidfilt", + module_init, + module_close +}; diff --git a/modules/sndfile/module.mk b/modules/sndfile/module.mk new file mode 100644 index 0000000..7fed4de --- /dev/null +++ b/modules/sndfile/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sndfile +$(MOD)_SRCS += sndfile.c +$(MOD)_LFLAGS += -lsndfile + +include mk/mod.mk diff --git a/modules/sndfile/sndfile.c b/modules/sndfile/sndfile.c new file mode 100644 index 0000000..e8fe9c9 --- /dev/null +++ b/modules/sndfile/sndfile.c @@ -0,0 +1,180 @@ +/** + * @file sndfile.c Audio dumper using libsndfile + * + * Copyright (C) 2010 Creytiv.com + */ +#include <sndfile.h> +#include <time.h> +#include <re.h> +#include <baresip.h> + + +struct sndfile_enc { + struct aufilt_enc_st af; /* base class */ + SNDFILE *enc; +}; + +struct sndfile_dec { + struct aufilt_dec_st af; /* base class */ + SNDFILE *dec; +}; + + +static int timestamp_print(struct re_printf *pf, const struct tm *tm) +{ + if (!tm) + return 0; + + return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +static void enc_destructor(void *arg) +{ + struct sndfile_enc *st = arg; + + if (st->enc) + sf_close(st->enc); + + list_unlink(&st->af.le); +} + + +static void dec_destructor(void *arg) +{ + struct sndfile_dec *st = arg; + + if (st->dec) + sf_close(st->dec); + + list_unlink(&st->af.le); +} + + +static SNDFILE *openfile(const struct aufilt_prm *prm, bool enc) +{ + char filename[128]; + SF_INFO sfinfo; + time_t tnow = time(0); + struct tm *tm = localtime(&tnow); + SNDFILE *sf; + + (void)re_snprintf(filename, sizeof(filename), + "dump-%H-%s.wav", + timestamp_print, tm, enc ? "enc" : "dec"); + + sfinfo.samplerate = prm->srate; + sfinfo.channels = prm->ch; + sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + + sf = sf_open(filename, SFM_WRITE, &sfinfo); + if (!sf) { + warning("sndfile: could not open: %s\n", filename); + puts(sf_strerror(NULL)); + return NULL; + } + + info("sndfile: dumping %s audio to %s\n", + enc ? "encode" : "decode", filename); + + return sf; +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct sndfile_enc *st; + int err = 0; + (void)ctx; + (void)af; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return EINVAL; + + st->enc = openfile(prm, true); + if (!st->enc) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct sndfile_dec *st; + int err = 0; + (void)ctx; + (void)af; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return EINVAL; + + st->dec = openfile(prm, false); + if (!st->dec) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct sndfile_enc *sf = (struct sndfile_enc *)st; + + sf_write_short(sf->enc, sampv, *sampc); + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct sndfile_dec *sf = (struct sndfile_dec *)st; + + sf_write_short(sf->dec, sampv, *sampc); + + return 0; +} + + +static struct aufilt sndfile = { + LE_INIT, "sndfile", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&sndfile); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&sndfile); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sndfile) = { + "sndfile", + "filter", + module_init, + module_close +}; diff --git a/modules/speex/module.mk b/modules/speex/module.mk new file mode 100644 index 0000000..c8d0015 --- /dev/null +++ b/modules/speex/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex +$(MOD)_SRCS += speex.c +$(MOD)_LFLAGS += -lspeex +CFLAGS += -Wno-strict-prototypes + +include mk/mod.mk diff --git a/modules/speex/speex.c b/modules/speex/speex.c new file mode 100644 index 0000000..32db0c9 --- /dev/null +++ b/modules/speex/speex.c @@ -0,0 +1,498 @@ +/** + * @file speex.c Speex audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <speex/speex.h> +#include <speex/speex_stereo.h> +#include <speex/speex_callbacks.h> +#include <re.h> +#include <baresip.h> + + +enum { + MIN_FRAME_SIZE = 43, + SPEEX_PTIME = 20, +}; + + +struct auenc_state { + void *enc; + SpeexBits bits; + + uint32_t frame_size; /* Number of sample-frames */ + uint8_t channels; +}; + + +struct audec_state { + void *dec; + SpeexBits bits; + SpeexStereoState stereo; + SpeexCallback callback; + + uint32_t frame_size; /* Number of sample-frames */ + uint8_t channels; +}; + + +static char speex_fmtp[128]; + + +/** Speex configuration */ +static struct { + int quality; + int complexity; + int enhancement; + int vbr; + int vad; +} sconf = { + 3, /* 0-10 */ + 2, /* 0-10 */ + 0, /* 0 or 1 */ + 0, /* 0 or 1 */ + 0 /* 0 or 1 */ +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + speex_bits_destroy(&st->bits); + speex_encoder_destroy(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + speex_bits_destroy(&st->bits); + speex_decoder_destroy(st->dec); +} + + +static void encoder_config(void *st) +{ + int ret; + + ret = speex_encoder_ctl(st, SPEEX_SET_QUALITY, &sconf.quality); + if (ret) { + warning("speex: SPEEX_SET_QUALITY: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &sconf.complexity); + if (ret) { + warning("speex: SPEEX_SET_COMPLEXITY: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_VBR, &sconf.vbr); + if (ret) { + warning("speex: SPEEX_SET_VBR: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_VAD, &sconf.vad); + if (ret) { + warning("speex: SPEEX_SET_VAD: %d\n", ret); + } +} + + +static void decoder_config(void *st) +{ + int ret; + + ret = speex_decoder_ctl(st, SPEEX_SET_ENH, &sconf.enhancement); + if (ret) { + warning("speex: SPEEX_SET_ENH: %d\n", ret); + } +} + + +static int decode_param(struct auenc_state *st, const struct pl *name, + const struct pl *val) +{ + int ret; + + /* mode: List supported Speex decoding modes. The valid modes are + different for narrowband and wideband, and are defined as follows: + + {1,2,3,4,5,6,7,8,any} for narrowband + {0,1,2,3,4,5,6,7,8,9,10,any} for wideband and ultra-wideband + */ + if (0 == pl_strcasecmp(name, "mode")) { + struct pl v; + int mode; + + /* parameter is quoted */ + if (re_regex(val->p, val->l, "\"[^\"]+\"", &v)) + v = *val; + + if (0 == pl_strcasecmp(&v, "any")) + return 0; + + mode = pl_u32(&v); + + ret = speex_encoder_ctl(st->enc, SPEEX_SET_MODE, &mode); + if (ret) { + warning("speex: SPEEX_SET_MODE: ret=%d\n", ret); + } + } + /* vbr: variable bit rate - either 'on' 'off' or 'vad' */ + else if (0 == pl_strcasecmp(name, "vbr")) { + int vbr = 0, vad = 0; + + if (0 == pl_strcasecmp(val, "on")) + vbr = 1; + else if (0 == pl_strcasecmp(val, "off")) + vbr = 0; + else if (0 == pl_strcasecmp(val, "vad")) + vad = 1; + else { + warning("speex: invalid vbr value %r\n", val); + } + + debug("speex: setting VBR=%d VAD=%d\n", vbr, vad); + ret = speex_encoder_ctl(st->enc, SPEEX_SET_VBR, &vbr); + if (ret) { + warning("speex: SPEEX_SET_VBR: ret=%d\n", ret); + } + ret = speex_encoder_ctl(st->enc, SPEEX_SET_VAD, &vad); + if (ret) { + warning("speex: SPEEX_SET_VAD: ret=%d\n", ret); + } + } + else if (0 == pl_strcasecmp(name, "cng")) { + int dtx = 0; + + if (0 == pl_strcasecmp(val, "on")) + dtx = 0; + else if (0 == pl_strcasecmp(val, "off")) + dtx = 1; + + ret = speex_encoder_ctl(st->enc, SPEEX_SET_DTX, &dtx); + if (ret) { + warning("speex: SPEEX_SET_DTX: ret=%d\n", ret); + } + } + else { + debug("speex: unknown Speex param: %r=%r\n", name, val); + } + + return 0; +} + + +static void param_handler(const struct pl *name, const struct pl *val, + void *arg) +{ + struct auenc_state *st = arg; + + decode_param(st, name, val); +} + + +static const SpeexMode *resolve_mode(uint32_t srate) +{ + switch (srate) { + + default: + case 8000: return &speex_nb_mode; + case 16000: return &speex_wb_mode; + case 32000: return &speex_uwb_mode; + } +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int ret, err = 0; + + if (!aesp || !ac || !prm) + return EINVAL; + if (prm->ptime != SPEEX_PTIME) + return EPROTO; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->frame_size = ac->srate * SPEEX_PTIME / 1000; + st->channels = ac->ch; + + /* Encoder */ + st->enc = speex_encoder_init(resolve_mode(ac->srate)); + if (!st->enc) { + err = ENOMEM; + goto out; + } + + speex_bits_init(&st->bits); + + encoder_config(st->enc); + + ret = speex_encoder_ctl(st->enc, SPEEX_GET_FRAME_SIZE, + &st->frame_size); + if (ret) { + warning("speex: SPEEX_GET_FRAME_SIZE: %d\n", ret); + } + + if (str_isset(fmtp)) { + struct pl params; + + pl_set_str(¶ms, fmtp); + + fmt_param_apply(¶ms, param_handler, st); + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->frame_size = ac->srate * SPEEX_PTIME / 1000; + st->channels = ac->ch; + + /* Decoder */ + st->dec = speex_decoder_init(resolve_mode(ac->srate)); + if (!st->dec) { + err = ENOMEM; + goto out; + } + + speex_bits_init(&st->bits); + + if (2 == st->channels) { + + /* Stereo. */ + st->stereo.balance = 1; + st->stereo.e_ratio = .5f; + st->stereo.smooth_left = 1; + st->stereo.smooth_right = 1; + + st->callback.callback_id = SPEEX_INBAND_STEREO; + st->callback.func = speex_std_stereo_request_handler; + st->callback.data = &st->stereo; + speex_decoder_ctl(st->dec, SPEEX_SET_HANDLER, + &st->callback); + } + + decoder_config(st->dec); + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + const size_t n = st->channels * st->frame_size; + int ret, r; + + if (*len < 128) + return ENOMEM; + + /* VAD */ + if (!sampv || !sampc) { + /* 5 zeros interpreted by Speex as silence (submode 0) */ + speex_bits_pack(&st->bits, 0, 5); + goto out; + } + + /* Handle multiple Speex frames in one RTP packet */ + while (sampc > 0) { + + /* Assume stereo */ + if (2 == st->channels) { + speex_encode_stereo_int((int16_t *)sampv, + st->frame_size, &st->bits); + } + + ret = speex_encode_int(st->enc, (int16_t *)sampv, &st->bits); + if (1 != ret) { + warning("speex: speex_encode_int: ret=%d\n", ret); + } + + sampc -= n; + sampv += n; + } + + out: + /* Terminate bit stream */ + speex_bits_pack(&st->bits, 15, 5); + + r = speex_bits_write(&st->bits, (char *)buf, (int)*len); + *len = r; + + speex_bits_reset(&st->bits); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + const size_t n = st->channels * st->frame_size; + size_t i = 0; + + /* Read into bit-stream */ + speex_bits_read_from(&st->bits, (char *)buf, (int)len); + + /* Handle multiple Speex frames in one RTP packet */ + while (speex_bits_remaining(&st->bits) >= MIN_FRAME_SIZE) { + int ret; + + if (*sampc < n) + return ENOMEM; + + ret = speex_decode_int(st->dec, &st->bits, + (int16_t *)&sampv[i]); + if (ret < 0) { + if (-1 == ret) { + } + else if (-2 == ret) { + warning("speex: decode: corrupt stream\n"); + } + else { + warning("speex: decode: speex_decode_int:" + " ret=%d\n", ret); + } + break; + } + + /* Transforms a mono frame into a stereo frame + using intensity stereo info */ + if (2 == st->channels) { + speex_decode_stereo_int((int16_t *)&sampv[i], + st->frame_size, + &st->stereo); + } + + i += n; + *sampc -= n; + } + + *sampc = i; + + return 0; +} + + +static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + const size_t n = st->channels * st->frame_size; + + if (*sampc < n) + return ENOMEM; + + /* Silence */ + speex_decode_int(st->dec, NULL, sampv); + *sampc = n; + + return 0; +} + + +static void config_parse(struct conf *conf) +{ + uint32_t v; + + if (0 == conf_get_u32(conf, "speex_quality", &v)) + sconf.quality = v; + if (0 == conf_get_u32(conf, "speex_complexity", &v)) + sconf.complexity = v; + if (0 == conf_get_u32(conf, "speex_enhancement", &v)) + sconf.enhancement = v; + if (0 == conf_get_u32(conf, "speex_vbr", &v)) + sconf.vbr = v; + if (0 == conf_get_u32(conf, "speex_vad", &v)) + sconf.vad = v; +} + + +static struct aucodec speexv[] = { + + /* Stereo Speex */ + {LE_INIT, 0, "speex", 32000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + + /* Standard Speex */ + {LE_INIT, 0, "speex", 32000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, +}; + + +static int speex_init(void) +{ + size_t i; + + config_parse(conf_cur()); + + (void)re_snprintf(speex_fmtp, sizeof(speex_fmtp), + "mode=\"7\";vbr=%s;cng=on", + sconf.vad ? "vad" : (sconf.vbr ? "on" : "off")); + + for (i=0; i<ARRAY_SIZE(speexv); i++) + aucodec_register(&speexv[i]); + + return 0; +} + + +static int speex_close(void) +{ + size_t i; + for (i=0; i<ARRAY_SIZE(speexv); i++) + aucodec_unregister(&speexv[i]); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex) = { + "speex", + "codec", + speex_init, + speex_close +}; diff --git a/modules/speex_aec/module.mk b/modules/speex_aec/module.mk new file mode 100644 index 0000000..9e29696 --- /dev/null +++ b/modules/speex_aec/module.mk @@ -0,0 +1,15 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex_aec +$(MOD)_SRCS += speex_aec.c +ifneq ($(HAVE_SPEEXDSP),) +$(MOD)_LFLAGS += "-lspeexdsp" +else +$(MOD)_LFLAGS += "-lspeex" +endif + +include mk/mod.mk diff --git a/modules/speex_aec/speex_aec.c b/modules/speex_aec/speex_aec.c new file mode 100644 index 0000000..15ea552 --- /dev/null +++ b/modules/speex_aec/speex_aec.c @@ -0,0 +1,222 @@ +/** + * @file speex_aec.c Speex Acoustic Echo Cancellation + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <speex/speex.h> +#include <speex/speex_echo.h> +#include <re.h> +#include <baresip.h> + + +struct speex_st { + int16_t *out; + SpeexEchoState *state; +}; + +struct enc_st { + struct aufilt_enc_st af; /* base class */ + struct speex_st *st; +}; + +struct dec_st { + struct aufilt_dec_st af; /* base class */ + struct speex_st *st; +}; + + +static void enc_destructor(void *arg) +{ + struct enc_st *st = arg; + + list_unlink(&st->af.le); + mem_deref(st->st); +} + + +static void dec_destructor(void *arg) +{ + struct dec_st *st = arg; + + list_unlink(&st->af.le); + mem_deref(st->st); +} + + +#ifdef SPEEX_SET_VBR_MAX_BITRATE +static void speex_aec_destructor(void *arg) +{ + struct speex_st *st = arg; + + if (st->state) + speex_echo_state_destroy(st->state); + + mem_deref(st->out); +} + + +static int aec_alloc(struct speex_st **stp, void **ctx, struct aufilt_prm *prm) +{ + struct speex_st *st; + uint32_t sampc; + int err, tmp, fl; + + if (!stp || !ctx || !prm) + return EINVAL; + + if (*ctx) { + *stp = mem_ref(*ctx); + return 0; + } + + st = mem_zalloc(sizeof(*st), speex_aec_destructor); + if (!st) + return ENOMEM; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->out = mem_alloc(2 * sampc, NULL); + if (!st->out) { + err = ENOMEM; + goto out; + } + + /* Echo canceller with 200 ms tail length */ + fl = 10 * sampc; + st->state = speex_echo_state_init(sampc, fl); + if (!st->state) { + err = ENOMEM; + goto out; + } + + tmp = prm->srate; + err = speex_echo_ctl(st->state, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp); + if (err < 0) { + warning("speex_aec: speex_echo_ctl: err=%d\n", err); + } + + info("speex_aec: Speex AEC loaded: srate = %uHz\n", prm->srate); + + out: + if (err) + mem_deref(st); + else + *ctx = *stp = st; + + return err; +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct enc_st *st; + int err; + + if (!stp || !ctx || !af || !prm) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + err = aec_alloc(&st->st, ctx, prm); + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct dec_st *st; + int err; + + if (!stp || !ctx || !af || !prm) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return ENOMEM; + + err = aec_alloc(&st->st, ctx, prm); + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct enc_st *est = (struct enc_st *)st; + struct speex_st *sp = est->st; + + if (*sampc) { + speex_echo_capture(sp->state, sampv, sp->out); + memcpy(sampv, sp->out, *sampc * 2); + } + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct dec_st *dst = (struct dec_st *)st; + struct speex_st *sp = dst->st; + + if (*sampc) + speex_echo_playback(sp->state, sampv); + + return 0; +} +#endif + + +static struct aufilt speex_aec = { + LE_INIT, "speex_aec", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + /* Note: Hack to check libspeex version */ +#ifdef SPEEX_SET_VBR_MAX_BITRATE + aufilt_register(&speex_aec); + return 0; +#else + return ENOSYS; +#endif +} + + +static int module_close(void) +{ + aufilt_unregister(&speex_aec); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_aec) = { + "speex_aec", + "filter", + module_init, + module_close +}; diff --git a/modules/speex_pp/module.mk b/modules/speex_pp/module.mk new file mode 100644 index 0000000..fad5f88 --- /dev/null +++ b/modules/speex_pp/module.mk @@ -0,0 +1,15 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex_pp +$(MOD)_SRCS += speex_pp.c +ifneq ($(HAVE_SPEEXDSP),) +$(MOD)_LFLAGS += "-lspeexdsp" +else +$(MOD)_LFLAGS += "-lspeex" +endif + +include mk/mod.mk diff --git a/modules/speex_pp/speex_pp.c b/modules/speex_pp/speex_pp.c new file mode 100644 index 0000000..4c14f65 --- /dev/null +++ b/modules/speex_pp/speex_pp.c @@ -0,0 +1,154 @@ +/** + * @file speex_pp.c Speex Pre-processor + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <stdlib.h> +#include <speex/speex.h> +#include <speex/speex_preprocess.h> +#include <re.h> +#include <baresip.h> + + +struct preproc { + struct aufilt_enc_st af; /* base class */ + SpeexPreprocessState *state; +}; + + +/** Speex configuration */ +static struct { + int denoise_enabled; + int agc_enabled; + int vad_enabled; + int dereverb_enabled; + spx_int32_t agc_level; +} pp_conf = { + 1, + 1, + 1, + 1, + 8000 +}; + + +static void speexpp_destructor(void *arg) +{ + struct preproc *st = arg; + + if (st->state) + speex_preprocess_state_destroy(st->state); + + list_unlink(&st->af.le); +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct preproc *st; + unsigned sampc; + (void)ctx; + + if (!stp || !af || !prm || prm->ch != 1) + return EINVAL; + + st = mem_zalloc(sizeof(*st), speexpp_destructor); + if (!st) + return ENOMEM; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->state = speex_preprocess_state_init(sampc, prm->srate); + if (!st->state) + goto error; + + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DENOISE, + &pp_conf.denoise_enabled); + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_AGC, + &pp_conf.agc_enabled); + +#ifdef SPEEX_PREPROCESS_SET_AGC_TARGET + if (pp_conf.agc_enabled) { + speex_preprocess_ctl(st->state, + SPEEX_PREPROCESS_SET_AGC_TARGET, + &pp_conf.agc_level); + } +#endif + + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_VAD, + &pp_conf.vad_enabled); + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DEREVERB, + &pp_conf.dereverb_enabled); + + info("speex_pp: Speex preprocessor loaded: srate = %uHz\n", + prm->srate); + + *stp = (struct aufilt_enc_st *)st; + return 0; + + error: + mem_deref(st); + return ENOMEM; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct preproc *pp = (struct preproc *)st; + int is_speech = 1; + + if (!*sampc) + return 0; + + /* NOTE: Using this macro to check libspeex version */ +#ifdef SPEEX_PREPROCESS_SET_NOISE_SUPPRESS + /* New API */ + is_speech = speex_preprocess_run(pp->state, sampv); +#else + /* Old API - not tested! */ + is_speech = speex_preprocess(pp->state, sampv, NULL); +#endif + + /* XXX: Handle is_speech and VAD */ + (void)is_speech; + + return 0; +} + + +static void config_parse(struct conf *conf) +{ + uint32_t v; + + if (0 == conf_get_u32(conf, "speex_agc_level", &v)) + pp_conf.agc_level = v; +} + + +static struct aufilt preproc = { + LE_INIT, "speex_pp", encode_update, encode, NULL, NULL +}; + +static int module_init(void) +{ + config_parse(conf_cur()); + aufilt_register(&preproc); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&preproc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_pp) = { + "speex_pp", + "filter", + module_init, + module_close +}; diff --git a/modules/srtp/module.mk b/modules/srtp/module.mk new file mode 100644 index 0000000..ac4a1c7 --- /dev/null +++ b/modules/srtp/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := srtp +$(MOD)_SRCS += srtp.c sdes.c +$(MOD)_LFLAGS += -lsrtp + +include mk/mod.mk diff --git a/modules/srtp/sdes.c b/modules/srtp/sdes.c new file mode 100644 index 0000000..a750432 --- /dev/null +++ b/modules/srtp/sdes.c @@ -0,0 +1,45 @@ +/** + * @file sdes.c SDP Security Descriptions for Media Streams (RFC 4568) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +const char sdp_attr_crypto[] = "crypto"; + + +int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite, + const char *key, size_t key_len) +{ + return sdp_media_set_lattr(m, true, sdp_attr_crypto, "%u %s inline:%b", + tag, suite, key, key_len); +} + + +/* http://tools.ietf.org/html/rfc4568 + * a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] + */ +int sdes_decode_crypto(struct crypto *c, const char *val) +{ + struct pl tag, key_prms; + int err; + + err = re_regex(val, str_len(val), "[0-9]+ [^ ]+ [^ ]+[]*[^]*", + &tag, &c->suite, &key_prms, NULL, &c->sess_prms); + if (err) + return err; + + c->tag = pl_u32(&tag); + + c->lifetime = c->mki = pl_null; + err = re_regex(key_prms.p, key_prms.l, "[^:]+:[^|]+[|]*[^|]*[|]*[^|]*", + &c->key_method, &c->key_info, + NULL, &c->lifetime, NULL, &c->mki); + if (err) + return err; + + return 0; +} diff --git a/modules/srtp/sdes.h b/modules/srtp/sdes.h new file mode 100644 index 0000000..66cc2f1 --- /dev/null +++ b/modules/srtp/sdes.h @@ -0,0 +1,22 @@ +/** + * @file sdes.h SDP Security Descriptions for Media Streams (RFC 4568) API + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct crypto { + uint32_t tag; + struct pl suite; + struct pl key_method; + struct pl key_info; + struct pl lifetime; /* optional */ + struct pl mki; /* optional */ + struct pl sess_prms; /* optional */ +}; + +extern const char sdp_attr_crypto[]; + +int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite, + const char *key, size_t key_len); +int sdes_decode_crypto(struct crypto *c, const char *val); diff --git a/modules/srtp/srtp.c b/modules/srtp/srtp.c new file mode 100644 index 0000000..bb86e96 --- /dev/null +++ b/modules/srtp/srtp.c @@ -0,0 +1,472 @@ +/** + * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711) + * + * Copyright (C) 2010 Creytiv.com + */ +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +#define DEBUG_MODULE "srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct menc_st { + /* one SRTP session per media line */ + uint8_t key_tx[32]; /* 32 for alignment, only 30 used */ + uint8_t key_rx[32]; + srtp_t srtp_tx, srtp_rx; + srtp_policy_t policy_tx, policy_rx; + bool use_srtp; + char *crypto_suite; + + void *rtpsock; + void *rtcpsock; + struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */ + struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */ + struct sdp_media *sdpm; +}; + + +static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32"; +static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80"; + + +static void destructor(void *arg) +{ + struct menc_st *st = arg; + + mem_deref(st->sdpm); + mem_deref(st->crypto_suite); + + /* note: must be done before freeing socket */ + mem_deref(st->uh_rtp); + mem_deref(st->uh_rtcp); + mem_deref(st->rtpsock); + mem_deref(st->rtcpsock); + + if (st->srtp_tx) + srtp_dealloc(st->srtp_tx); + if (st->srtp_rx) + srtp_dealloc(st->srtp_rx); +} + + +static bool cryptosuite_issupported(const struct pl *suite) +{ + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true; + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true; + + return false; +} + + +static int errstatus_print(struct re_printf *pf, err_status_t e) +{ + const char *s; + + switch (e) { + + case err_status_ok: s = "ok"; break; + case err_status_fail: s = "fail"; break; + case err_status_auth_fail: s = "auth_fail"; break; + case err_status_cipher_fail: s = "cipher_fail"; break; + case err_status_replay_fail: s = "replay_fail"; break; + + default: + return re_hprintf(pf, "err=%d", e); + } + + return re_hprintf(pf, "%s", s); +} + + +/* + * See RFC 5764 figure 3: + * + * +----------------+ + * | 127 < B < 192 -+--> forward to RTP + * | | + * packet --> | 19 < B < 64 -+--> forward to DTLS + * | | + * | B < 2 -+--> forward to STUN + * +----------------+ + * + */ +static bool is_rtp_or_rtcp(const struct mbuf *mb) +{ + uint8_t b; + + if (mbuf_get_left(mb) < 1) + return false; + + b = mbuf_buf(mb)[0]; + + return 127 < b && b < 192; +} + + +static bool is_rtcp_packet(const struct mbuf *mb) +{ + uint8_t pt; + + if (mbuf_get_left(mb) < 2) + return false; + + pt = mbuf_buf(mb)[1] & 0x7f; + + return 64 <= pt && pt <= 95; +} + + +static int start_srtp(struct menc_st *st, const char *suite) +{ + crypto_policy_t policy; + err_status_t e; + + if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) { + crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy); + } + else if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) { + crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy); + } + else { + DEBUG_WARNING("unknown SRTP crypto suite (%s)\n", suite); + return ENOENT; + } + + /* transmit policy */ + st->policy_tx.rtp = policy; + st->policy_tx.rtcp = policy; + st->policy_tx.ssrc.type = ssrc_any_outbound; + st->policy_tx.key = st->key_tx; + st->policy_tx.next = NULL; + + /* receive policy */ + st->policy_rx.rtp = policy; + st->policy_rx.rtcp = policy; + st->policy_rx.ssrc.type = ssrc_any_inbound; + st->policy_rx.key = st->key_rx; + st->policy_rx.next = NULL; + + /* allocate and initialize the SRTP session */ + e = srtp_create(&st->srtp_tx, &st->policy_tx); + if (e != err_status_ok) { + DEBUG_WARNING("srtp_create TX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + e = srtp_create(&st->srtp_rx, &st->policy_rx); + if (err_status_ok != e) { + DEBUG_WARNING("srtp_create RX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + /* use SRTP for this stream/session */ + st->use_srtp = true; + + return 0; +} + + +static int setup_srtp(struct menc_st *st) +{ + err_status_t e; + + /* init SRTP */ + e = crypto_get_random(st->key_tx, SRTP_MASTER_KEY_LEN); + if (err_status_ok != e) { + DEBUG_WARNING("crypto_get_random() failed (%H)\n", + errstatus_print, e); + return ENOSYS; + } + + return 0; +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)dst; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) { + mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN); + } + + if (is_rtcp_packet(mb)) { + e = srtp_protect_rtcp(st->srtp_tx, mbuf_buf(mb), &len); + } + else { + e = srtp_protect(st->srtp_tx, mbuf_buf(mb), &len); + } + + if (err_status_ok != e) { + DEBUG_WARNING("send: failed to protect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + *err = EPROTO; + return false; + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)src; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (is_rtcp_packet(mb)) { + e = srtp_unprotect_rtcp(st->srtp_rx, mbuf_buf(mb), &len); + } + else { + e = srtp_unprotect(st->srtp_rx, mbuf_buf(mb), &len); + } + + if (e != err_status_ok) { + DEBUG_WARNING("recv: failed to unprotect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + return true; /* error - drop packet */ + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */ +static int sdp_enc(struct menc_st *st, struct sdp_media *m, + uint32_t tag, const char *suite) +{ + char key[128] = ""; + size_t olen; + int err; + + olen = sizeof(key); + err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen); + if (err) + return err; + + return sdes_encode_crypto(m, tag, suite, key, olen); +} + + +static int start_crypto(struct menc_st *st, const struct pl *key_info) +{ + size_t olen; + int err; + + /* key-info is BASE64 encoded */ + + olen = sizeof(st->key_rx); + err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen); + if (err) + return err; + + if (SRTP_MASTER_KEY_LEN != olen) { + DEBUG_WARNING("srtp keylen is %u (should be 30)\n", olen); + } + + err = start_srtp(st, st->crypto_suite); + if (err) + return err; + + info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n", + sdp_media_name(st->sdpm), st->crypto_suite); + + return 0; +} + + +static bool sdp_attr_handler(const char *name, const char *value, void *arg) +{ + struct menc_st *st = arg; + struct crypto c; + (void)name; + + if (sdes_decode_crypto(&c, value)) + return false; + + if (0 != pl_strcmp(&c.key_method, "inline")) + return false; + + if (!cryptosuite_issupported(&c.suite)) + return false; + + st->crypto_suite = mem_deref(st->crypto_suite); + pl_strdup(&st->crypto_suite, &c.suite); + + if (start_crypto(st, &c.key_info)) + return false; + + sdp_enc(st, st->sdpm, c.tag, st->crypto_suite); + + return true; +} + + +static int alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_st *st; + const char *rattr = NULL; + int layer = 10; /* above zero */ + int err = 0; + bool mux = (rtpsock == rtcpsock); + (void)sess; + (void)rtp; + + if (!stp || !sdpm) + return EINVAL; + if (proto != IPPROTO_UDP) + return EPROTONOSUPPORT; + + st = (struct menc_st *)*stp; + if (!st) { + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->sdpm = mem_ref(sdpm); + + err = sdp_media_set_alt_protos(st->sdpm, 4, + "RTP/AVP", + "RTP/AVPF", + "RTP/SAVP", + "RTP/SAVPF"); + if (err) + goto out; + + if (rtpsock) { + st->rtpsock = mem_ref(rtpsock); + err |= udp_register_helper(&st->uh_rtp, rtpsock, + layer, send_handler, + recv_handler, st); + } + if (rtcpsock && !mux) { + st->rtcpsock = mem_ref(rtcpsock); + err |= udp_register_helper(&st->uh_rtcp, rtcpsock, + layer, send_handler, + recv_handler, st); + } + if (err) + goto out; + + /* set our preferred crypto-suite */ + err |= str_dup(&st->crypto_suite, aes_cm_128_hmac_sha1_80); + if (err) + goto out; + + err = setup_srtp(st); + if (err) + goto out; + } + + /* SDP handling */ + + if (sdp_media_rattr(st->sdpm, "crypto")) { + + rattr = sdp_media_rattr_apply(st->sdpm, "crypto", + sdp_attr_handler, st); + if (!rattr) { + DEBUG_WARNING("no valid a=crypto attribute from" + " remote peer\n"); + } + } + + if (!rattr) + err = sdp_enc(st, sdpm, 0, st->crypto_suite); + + out: + if (err) + mem_deref(st); + else + *stp = (struct menc_media *)st; + + return err; +} + + +static struct menc menc_srtp_opt = { + LE_INIT, "srtp", "RTP/AVP", NULL, alloc +}; + +static struct menc menc_srtp_mand = { + LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc +}; + +static struct menc menc_srtp_mandf = { + LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc +}; + + +static int mod_srtp_init(void) +{ + err_status_t err; + + err = srtp_init(); + if (err_status_ok != err) { + DEBUG_WARNING("srtp_init() failed (%H)\n", + errstatus_print, err); + return ENOSYS; + } + + menc_register(&menc_srtp_opt); + menc_register(&menc_srtp_mand); + menc_register(&menc_srtp_mandf); + + return 0; +} + + +static int mod_srtp_close(void) +{ + menc_unregister(&menc_srtp_mandf); + menc_unregister(&menc_srtp_mand); + menc_unregister(&menc_srtp_opt); + + crypto_kernel_shutdown(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(srtp) = { + "srtp", + "menc", + mod_srtp_init, + mod_srtp_close +}; diff --git a/modules/stdio/module.mk b/modules/stdio/module.mk new file mode 100644 index 0000000..4c52b28 --- /dev/null +++ b/modules/stdio/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := stdio +$(MOD)_SRCS += stdio.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/stdio/stdio.c b/modules/stdio/stdio.c new file mode 100644 index 0000000..bfdc9e7 --- /dev/null +++ b/modules/stdio/stdio.c @@ -0,0 +1,182 @@ +/** + * @file stdio.c Standard Input/Output UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <termios.h> +#include <re.h> +#include <baresip.h> + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct ui *ui; /* base class */ + struct tmr tmr; + struct termios term; + bool term_set; + ui_input_h *h; + void *arg; +}; + + +/* We only allow one instance */ +static struct ui_st *_ui; +static struct ui *stdio; + + +static void ui_destructor(void *arg) +{ + struct ui_st *st = arg; + + fd_close(STDIN_FILENO); + + if (st->term_set) + tcsetattr(STDIN_FILENO, TCSANOW, &st->term); + + tmr_cancel(&st->tmr); + mem_deref(st->ui); + + _ui = NULL; +} + + +static int print_handler(const char *p, size_t size, void *arg) +{ + (void)arg; + + return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM; +} + + +static void report_key(struct ui_st *ui, char key) +{ + struct re_printf pf; + + pf.vph = print_handler; + + if (ui->h) + ui->h(key, &pf, ui->arg); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, 0x00); +} + + +static void ui_fd_handler(int flags, void *arg) +{ + struct ui_st *st = arg; + char key; + (void)flags; + + if (1 != read(STDIN_FILENO, &key, 1)) { + return; + } + + tmr_start(&st->tmr, RELEASE_VAL, timeout, st); + report_key(st, key); +} + + +static int term_setup(struct ui_st *st) +{ + struct termios now; + + if (tcgetattr(STDIN_FILENO, &st->term) < 0) + return errno; + + now = st->term; + + now.c_lflag |= ISIG; + now.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + + /* required on Solaris */ + now.c_cc[VMIN] = 1; + now.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &now) < 0) + return errno; + + st->term_set = true; + + return 0; +} + + +static int ui_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *ih, void *arg) +{ + struct ui_st *st; + int err; + + (void)prm; + + if (!stp) + return EINVAL; + + if (_ui) { + *stp = mem_ref(_ui); + return 0; + } + + st = mem_zalloc(sizeof(*st), ui_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(stdio); + tmr_init(&st->tmr); + + err = fd_listen(STDIN_FILENO, FD_READ, ui_fd_handler, st); + if (err) + goto out; + + err = term_setup(st); + if (err) { + info("stdio: could not setup terminal: %m\n", err); + err = 0; + } + + st->h = ih; + st->arg = arg; + + out: + if (err) + mem_deref(st); + else + *stp = _ui = st; + + return err; +} + + +static int module_init(void) +{ + return ui_register(&stdio, "stdio", ui_alloc, NULL); +} + + +static int module_close(void) +{ + stdio = mem_deref(stdio); + return 0; +} + + +const struct mod_export DECL_EXPORTS(stdio) = { + "stdio", + "ui", + module_init, + module_close +}; diff --git a/modules/stun/module.mk b/modules/stun/module.mk new file mode 100644 index 0000000..6ae88b5 --- /dev/null +++ b/modules/stun/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := stun +$(MOD)_SRCS += stun.c + +include mk/mod.mk diff --git a/modules/stun/stun.c b/modules/stun/stun.c new file mode 100644 index 0000000..d7edf94 --- /dev/null +++ b/modules/stun/stun.c @@ -0,0 +1,251 @@ +/** + * @file stun.c STUN Module for Media NAT-traversal + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup stun stun + * + * Session Traversal Utilities for NAT (STUN) for media NAT traversal + */ + + +enum {LAYER = 0, INTERVAL = 30}; + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + mnat_estab_h *estabh; + void *arg; + int mediac; +}; + + +struct mnat_media { + struct le le; + struct sa addr1; + struct sa addr2; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct stun_keepalive *ska1; + struct stun_keepalive *ska2; + void *sock1; + void *sock2; + int proto; +}; + + +static struct mnat *mnat; + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->ska1); + mem_deref(m->ska2); + mem_deref(m->sock1); + mem_deref(m->sock2); +} + + +static void mapped_handler1(int err, const struct sa *map_addr, void *arg) +{ + struct mnat_media *m = arg; + + if (!err) { + + sdp_media_set_laddr(m->sdpm, map_addr); + + m->addr1 = *map_addr; + + if (m->ska2 && !sa_isset(&m->addr2, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, 0, NULL, m->sess->arg); +} + + +static void mapped_handler2(int err, const struct sa *map_addr, void *arg) +{ + struct mnat_media *m = arg; + + if (!err) { + + sdp_media_set_laddr_rtcp(m->sdpm, map_addr); + + m->addr2 = *map_addr; + + if (m->ska1 && !sa_isset(&m->addr1, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, 0, NULL, m->sess->arg); +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + if (m->sock1) { + err |= stun_keepalive_alloc(&m->ska1, m->proto, + m->sock1, LAYER, &sess->srv, NULL, + mapped_handler1, m); + } + if (m->sock2) { + err |= stun_keepalive_alloc(&m->ska2, m->proto, + m->sock2, LAYER, &sess->srv, NULL, + mapped_handler2, m); + } + if (err) + return err; + + stun_keepalive_enable(m->ska1, INTERVAL); + stun_keepalive_enable(m->ska2, INTERVAL); + + return 0; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err; + (void)user; + (void)pass; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->estabh = estabh; + sess->arg = arg; + + err = stun_server_discover(&sess->dnsq, dnsc, + stun_usage_binding, stun_proto_udp, + af, srv, port, dns_handler, sess); + + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->sock1 = mem_ref(sock1); + m->sock2 = mem_ref(sock2); + m->proto = proto; + + if (sa_isset(&sess->srv, SA_ALL)) + err = media_start(sess, m); + + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static int module_init(void) +{ + return mnat_register(&mnat, "stun", NULL, session_alloc, media_alloc, + NULL); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(stun) = { + "stun", + "mnat", + module_init, + module_close, +}; diff --git a/modules/syslog/module.mk b/modules/syslog/module.mk new file mode 100644 index 0000000..94fd185 --- /dev/null +++ b/modules/syslog/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := syslog +$(MOD)_SRCS += syslog.c + +include mk/mod.mk diff --git a/modules/syslog/syslog.c b/modules/syslog/syslog.c new file mode 100644 index 0000000..40e5690 --- /dev/null +++ b/modules/syslog/syslog.c @@ -0,0 +1,112 @@ +/** + * @file syslog.c Syslog module + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#define _GNU_SOURCE 1 +#include <stdio.h> +#include <syslog.h> +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "syslog" +#define DEBUG_LEVEL 6 +#include <re_dbg.h> + + +#if defined (DARWIN) || defined (__GLIBC__) + +static FILE *fv[2]; + + +static int writer(void *cookie, const char *p, int len) +{ + (void)cookie; + + syslog(LOG_NOTICE, "%.*s", (int)len, p); + + return len; +} + + +static void tolog(int ix, FILE **pfp) +{ +#if defined (__GLIBC__) + static cookie_io_functions_t memfile_func = { + .write = (cookie_write_function_t *)writer + }; +#endif + FILE *f; + +#if defined (__GLIBC__) + f = fopencookie(NULL, "w+", memfile_func); +#else + f = fwopen(NULL, writer); +#endif + + if (!f) + return; + + setvbuf(f, NULL, _IOLBF, 0); + fv[ix] = *pfp = f; +} + + +static void restore(int ix, FILE **fp) +{ + if (fv[ix]) { + *fp = fv[ix]; + fv[ix] = NULL; + } +} +#endif + + +static void syslog_handler(int level, const char *p, size_t len, void *arg) +{ + (void)arg; + + syslog(level, "%.*s", (int)len, p); +} + + +static int module_init(void) +{ + openlog("baresip", LOG_NDELAY | LOG_PID, LOG_LOCAL0); + +#if defined (DARWIN) || defined (__GLIBC__) + /* Redirect stdout/stderr to syslog */ + tolog(0, &stdout); + tolog(1, &stderr); +#endif + + dbg_init(DBG_INFO, DBG_NONE); + dbg_handler_set(syslog_handler, NULL); + + return 0; +} + + +static int module_close(void) +{ + dbg_handler_set(NULL, NULL); + +#if defined (DARWIN) || defined (__GLIBC__) + restore(0, &stdout); + restore(1, &stderr); +#endif + + closelog(); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(syslog) = { + "syslog", + "syslog", + module_init, + module_close +}; diff --git a/modules/turn/module.mk b/modules/turn/module.mk new file mode 100644 index 0000000..876308d --- /dev/null +++ b/modules/turn/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := turn +$(MOD)_SRCS += turn.c + +include mk/mod.mk diff --git a/modules/turn/turn.c b/modules/turn/turn.c new file mode 100644 index 0000000..41588e6 --- /dev/null +++ b/modules/turn/turn.c @@ -0,0 +1,300 @@ +/** + * @file turn.c TURN Module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup turn turn + * + * Traversal Using Relays around NAT (TURN) for media NAT traversal + * + * XXX: use turn RSV_TOKEN for RTP/RTCP even/odd pair ? + */ + + +enum {LAYER = 0}; + + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + char *user; + char *pass; + mnat_estab_h *estabh; + void *arg; + int mediac; +}; + + +struct mnat_media { + struct le le; + struct sa addr1; + struct sa addr2; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct turnc *turnc1; + struct turnc *turnc2; + void *sock1; + void *sock2; + int proto; +}; + + +static struct mnat *mnat; + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); + mem_deref(sess->user); + mem_deref(sess->pass); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->turnc1); + mem_deref(m->turnc2); + mem_deref(m->sock1); + mem_deref(m->sock2); +} + + +static void turn_handler1(int err, uint16_t scode, const char *reason, + const struct sa *relay_addr, + const struct sa *mapped_addr, + const struct stun_msg *msg, + void *arg) +{ + struct mnat_media *m = arg; + (void)mapped_addr; + (void)msg; + + if (!err && !scode) { + + sdp_media_set_laddr(m->sdpm, relay_addr); + + m->addr1 = *relay_addr; + + if (m->turnc2 && !sa_isset(&m->addr2, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static void turn_handler2(int err, uint16_t scode, const char *reason, + const struct sa *relay_addr, + const struct sa *mapped_addr, + const struct stun_msg *msg, + void *arg) +{ + struct mnat_media *m = arg; + (void)mapped_addr; + (void)msg; + + if (!err && !scode) { + + sdp_media_set_laddr_rtcp(m->sdpm, relay_addr); + + m->addr2 = *relay_addr; + + if (m->turnc1 && !sa_isset(&m->addr1, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + if (m->sock1) { + err |= turnc_alloc(&m->turnc1, NULL, + m->proto, m->sock1, LAYER, + &sess->srv, sess->user, sess->pass, + TURN_DEFAULT_LIFETIME, + turn_handler1, m); + } + if (m->sock2) { + err |= turnc_alloc(&m->turnc2, NULL, + m->proto, m->sock2, LAYER, + &sess->srv, sess->user, sess->pass, + TURN_DEFAULT_LIFETIME, + turn_handler2, m); + } + + return err; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + err = str_dup(&sess->user, user); + err |= str_dup(&sess->pass, pass); + if (err) + goto out; + + sess->estabh = estabh; + sess->arg = arg; + + err = stun_server_discover(&sess->dnsq, dnsc, + stun_usage_relay, stun_proto_udp, + af, srv, port, dns_handler, sess); + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->sock1 = mem_ref(sock1); + m->sock2 = mem_ref(sock2); + m->proto = proto; + + if (sa_isset(&sess->srv, SA_ALL)) + err = media_start(sess, m); + + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static int update(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + if (!sess) + return EINVAL; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + struct sa raddr1, raddr2; + + raddr1 = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr2); + + if (m->turnc1 && sa_isset(&raddr1, SA_ALL)) + err |= turnc_add_chan(m->turnc1, &raddr1, NULL, NULL); + + if (m->turnc2 && sa_isset(&raddr2, SA_ALL)) + err |= turnc_add_chan(m->turnc2, &raddr2, NULL, NULL); + } + + return err; +} + + +static int module_init(void) +{ + return mnat_register(&mnat, "turn", NULL, session_alloc, media_alloc, + update); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(turn) = { + "turn", + "mnat", + module_init, + module_close, +}; diff --git a/modules/uuid/module.mk b/modules/uuid/module.mk new file mode 100644 index 0000000..64a3909 --- /dev/null +++ b/modules/uuid/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := uuid +$(MOD)_SRCS += uuid.c +ifneq ($(OS),darwin) +$(MOD)_LFLAGS += -luuid +endif + +include mk/mod.mk diff --git a/modules/uuid/uuid.c b/modules/uuid/uuid.c new file mode 100644 index 0000000..6426a6e --- /dev/null +++ b/modules/uuid/uuid.c @@ -0,0 +1,96 @@ +/** + * @file modules/uuid/uuid.c Generate and load UUID + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <uuid/uuid.h> +#include <re.h> +#include <baresip.h> + + +static int uuid_init(const char *file) +{ + char uuid[37]; + uuid_t uu; + FILE *f = NULL; + int err = 0; + + f = fopen(file, "r"); + if (f) { + err = 0; + goto out; + } + + f = fopen(file, "w"); + if (!f) { + err = errno; + warning("uuid: fopen() %s (%m)\n", file, err); + goto out; + } + + uuid_generate(uu); + + uuid_unparse(uu, uuid); + + re_fprintf(f, "%s", uuid); + + info("uuid: generated new UUID (%s)\n", uuid); + + out: + if (f) + fclose(f); + + return err; +} + + +static int uuid_load(const char *file, char *uuid, size_t sz) +{ + FILE *f = NULL; + int err = 0; + + f = fopen(file, "r"); + if (!f) + return errno; + + if (!fgets(uuid, (int)sz, f)) + err = errno; + + (void)fclose(f); + + return err; +} + + +static int module_init(void) +{ + struct config *cfg = conf_config(); + char path[256]; + int err = 0; + + err = conf_path_get(path, sizeof(path)); + if (err) + return err; + + strncat(path, "/uuid", sizeof(path) - strlen(path) - 1); + + err = uuid_init(path); + if (err) + return err; + + err = uuid_load(path, cfg->sip.uuid, sizeof(cfg->sip.uuid)); + if (err) + return err; + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(uuid) = { + "uuid", + NULL, + module_init, + NULL +}; diff --git a/modules/v4l/module.mk b/modules/v4l/module.mk new file mode 100644 index 0000000..05ff278 --- /dev/null +++ b/modules/v4l/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := v4l +$(MOD)_SRCS += v4l.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/v4l/v4l.c b/modules/v4l/v4l.c new file mode 100644 index 0000000..1f3e760 --- /dev/null +++ b/modules/v4l/v4l.c @@ -0,0 +1,257 @@ +/** + * @file v4l.c Video4Linux video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include <libv4l1-videodev.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz size; + struct mbuf *mb; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static void v4l_get_caps(struct vidsrc_st *st) +{ + struct video_capability caps; + + if (-1 == ioctl(st->fd, VIDIOCGCAP, &caps)) { + warning("v4l: VIDIOCGCAP: %m\n", errno); + return; + } + + info("v4l: video: \"%s\" (%ux%u) - (%ux%u)\n", caps.name, + caps.minwidth, caps.minheight, + caps.maxwidth, caps.maxheight); + + if (VID_TYPE_CAPTURE != caps.type) { + warning("v4l: not a capture device (%d)\n", caps.type); + } +} + + +static int v4l_check_palette(struct vidsrc_st *st) +{ + struct video_picture pic; + + if (-1 == ioctl(st->fd, VIDIOCGPICT, &pic)) { + warning("v4l: VIDIOCGPICT: %m\n", errno); + return errno; + } + + if (VIDEO_PALETTE_RGB24 != pic.palette) { + warning("v4l: unsupported palette %d (only RGB24 supp.)\n", + pic.palette); + return ENODEV; + } + + return 0; +} + + +static int v4l_get_win(int fd, int width, int height) +{ + struct video_window win; + + if (-1 == ioctl(fd, VIDIOCGWIN, &win)) { + warning("v4l: VIDIOCGWIN: %m\n", errno); + return errno; + } + + info("v4l: video window: x,y=%u,%u (%u x %u)\n", + win.x, win.y, win.width, win.height); + + win.width = width; + win.height = height; + + if (-1 == ioctl(fd, VIDIOCSWIN, &win)) { + warning("v4l: VIDIOCSWIN: %m\n", errno); + return errno; + } + + return 0; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size, buf); + + st->frameh(&frame, st->arg); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + + while (st->run) { + ssize_t n; + + n = read(st->fd, st->mb->buf, st->mb->size); + if ((ssize_t)st->mb->size != n) { + warning("v4l: video read: %d -> %d bytes\n", + st->mb->size, n); + continue; + } + + call_frame_handler(st, st->mb->buf); + } + + return NULL; +} + + +static int vd_open(struct vidsrc_st *v4l, const char *device) +{ + /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open + * the video device. + */ + v4l->fd = open(device, O_RDWR); + if (v4l->fd < 0) { + warning("v4l: open %s: %m\n", device, errno); + return errno; + } + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->fd >= 0) + close(st->fd); + + mem_deref(st->mb); + mem_deref(st->vs); +} + + +static uint32_t rgb24_size(const struct vidsz *sz) +{ + return sz ? (sz->w * sz->h * 24/8) : 0; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + if (!str_isset(dev)) + dev = "/dev/video0"; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->fd = -1; + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + info("v4l: open: %s (%u x %u)\n", dev, size->w, size->h); + + err = vd_open(st, dev); + if (err) + goto out; + + v4l_get_caps(st); + + err = v4l_check_palette(st); + if (err) + goto out; + + err = v4l_get_win(st->fd, st->size.w, st->size.h); + if (err) + goto out; + + /* note: assumes RGB24 */ + st->mb = mbuf_alloc(rgb24_size(&st->size)); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int v4l_init(void) +{ + return vidsrc_register(&vidsrc, "v4l", alloc, NULL); +} + + +static int v4l_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l) = { + "v4l", + "vidsrc", + v4l_init, + v4l_close +}; diff --git a/modules/v4l2/module.mk b/modules/v4l2/module.mk new file mode 100644 index 0000000..61360cb --- /dev/null +++ b/modules/v4l2/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := v4l2 +$(MOD)_SRCS += v4l2.c +$(MOD)_LFLAGS += -lv4l2 + +include mk/mod.mk diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c new file mode 100644 index 0000000..2b60908 --- /dev/null +++ b/modules/v4l2/v4l2.c @@ -0,0 +1,575 @@ +/** + * @file v4l2.c Video4Linux2 video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include <linux/videodev2.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libv4l2.h> + + +enum io_method { + IO_METHOD_READ = 0, + IO_METHOD_MMAP +}; + +struct buffer { + void *start; + size_t length; +}; + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz sz, app_sz; + struct mbuf *mb; + vidsrc_frame_h *frameh; + void *arg; + enum io_method io; + struct buffer *buffers; + unsigned int n_buffers; +}; + + +static struct vidsrc *vidsrc; + + +static void get_video_input(struct vidsrc_st *st) +{ + struct v4l2_input input; + + memset(&input, 0, sizeof(input)); + + if (-1 == v4l2_ioctl(st->fd, VIDIOC_G_INPUT, &input.index)) { + warning("v4l2: VIDIOC_G_INPUT: %m\n", errno); + return; + } + + if (-1 == v4l2_ioctl(st->fd, VIDIOC_ENUMINPUT, &input)) { + warning("v4l2: VIDIOC_ENUMINPUT: %m\n", errno); + return; + } + + info("v4l2: Current input: %s\n", input.name); +} + + +static int xioctl(int fd, unsigned long int request, void *arg) +{ + int r; + + do { + r = v4l2_ioctl(fd, request, arg); + } + while (-1 == r && EINTR == errno); + + return r; +} + + +static int init_read(struct vidsrc_st *st, unsigned int buffer_size) +{ + st->buffers = calloc(1, sizeof (*st->buffers)); + if (!st->buffers) + return ENOMEM; + + st->buffers[0].length = buffer_size; + st->buffers[0].start = malloc(buffer_size); + if (!st->buffers[0].start) + return ENOMEM; + + return 0; +} + + +static int init_mmap(struct vidsrc_st *st, const char *dev_name) +{ + struct v4l2_requestbuffers req; + + memset(&req, 0, sizeof(req)); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(st->fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + warning("v4l2: %s does not support " + "memory mapping\n", dev_name); + return errno; + } + else { + return errno; + } + } + + if (req.count < 2) { + warning("v4l2: Insufficient buffer memory on %s\n", dev_name); + return ENOMEM; + } + + st->buffers = calloc(req.count, sizeof(*st->buffers)); + if (!st->buffers) + return ENOMEM; + + for (st->n_buffers = 0; st->n_buffers<req.count; ++st->n_buffers) { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = st->n_buffers; + + if (-1 == xioctl(st->fd, VIDIOC_QUERYBUF, &buf)) { + warning("v4l2: VIDIOC_QUERYBUF\n"); + return errno; + } + + st->buffers[st->n_buffers].length = buf.length; + st->buffers[st->n_buffers].start = + v4l2_mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + st->fd, buf.m.offset); + + if (MAP_FAILED == st->buffers[st->n_buffers].start) { + warning("v4l2: mmap failed\n"); + return ENODEV; + } + } + + return 0; +} + + +static int v4l2_init_device(struct vidsrc_st *st, const char *dev_name) +{ + struct v4l2_capability cap; + struct v4l2_format fmt; + unsigned int min; + const char *pix; + int err; + + if (-1 == xioctl(st->fd, VIDIOC_QUERYCAP, &cap)) { + if (EINVAL == errno) { + warning("v4l2: %s is no V4L2 device\n", dev_name); + return ENODEV; + } + else { + warning("v4l2: VIDIOC_QUERYCAP: %m\n", errno); + return errno; + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + warning("v4l2: %s is no video capture device\n", dev_name); + return ENODEV; + } + + switch (st->io) { + + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + warning("%s does not support read i/o\n", dev_name); + return ENOSYS; + } + break; + + case IO_METHOD_MMAP: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + warning("v4l2: %s does not support streaming i/o\n", + dev_name); + return ENOSYS; + } + break; + } + + /* Select video input, video standard and tune here. */ + + memset(&fmt, 0, sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = st->app_sz.w; + fmt.fmt.pix.height = st->app_sz.h; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (-1 == xioctl(st->fd, VIDIOC_S_FMT, &fmt)) { + warning("v4l2: VIDIOC_S_FMT: %m\n", errno); + return errno; + } + + /* Note VIDIOC_S_FMT may change width and height. */ + + /* Buggy driver paranoia. */ + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + + st->sz.w = fmt.fmt.pix.width; + st->sz.h = fmt.fmt.pix.height; + + if (!vidsz_cmp(&st->sz, &st->app_sz)) { + info("v4l2: scaling %u x %u ---> %u x %u\n", + st->sz.w, st->sz.h, st->app_sz.w, st->app_sz.h); + } + + switch (st->io) { + + case IO_METHOD_READ: + err = init_read(st, fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + err = init_mmap(st, dev_name); + break; + + default: + warning("v4l2: unknown io: %d\n", st->io); + err = EINVAL; + break; + } + + if (err) + return err; + + pix = (char *)&fmt.fmt.pix.pixelformat; + + if (V4L2_PIX_FMT_YUV420 != fmt.fmt.pix.pixelformat) { + warning("v4l2: %s: expected YUV420 got %c%c%c%c\n", dev_name, + pix[0], pix[1], pix[2], pix[3]); + return ENODEV; + } + + info("v4l2: %s: found valid V4L2 device (%u x %u) pixfmt=%c%c%c%c\n", + dev_name, fmt.fmt.pix.width, fmt.fmt.pix.height, + pix[0], pix[1], pix[2], pix[3]); + + return 0; +} + + +static void stop_capturing(struct vidsrc_st *st) +{ + enum v4l2_buf_type type; + + if (st->fd < 0) + return; + + switch (st->io) { + + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (-1 == xioctl(st->fd, VIDIOC_STREAMOFF, &type)) + warning("v4l2: VIDIOC_STREAMOFF\n"); + break; + } +} + + +static void uninit_device(struct vidsrc_st *st) +{ + unsigned int i; + + switch (st->io) { + + case IO_METHOD_READ: + if (st->buffers) + free(st->buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i=0; i<st->n_buffers; ++i) + v4l2_munmap(st->buffers[i].start, + st->buffers[i].length); + break; + } + + free(st->buffers); +} + + +static int start_capturing(struct vidsrc_st *st, int fd) +{ + unsigned int i; + enum v4l2_buf_type type; + + switch (st->io) { + + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < st->n_buffers; ++i) { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl (fd, VIDIOC_QBUF, &buf)) + return errno; + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (-1 == xioctl (fd, VIDIOC_STREAMON, &type)) + return errno; + + break; + } + + return 0; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, VID_FMT_YUV420P, &st->sz, buf); + + st->frameh(&frame, st->arg); +} + + +static int read_frame(struct vidsrc_st *st) +{ + struct v4l2_buffer buf; + ssize_t n; + + switch (st->io) { + + case IO_METHOD_READ: + n = v4l2_read(st->fd, st->mb->buf, st->mb->size); + if (-1 == n) { + switch (errno) { + + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + warning("v4l2: read error: %m\n", errno); + BREAKPOINT; + return errno; + } + } + + call_frame_handler(st, st->mb->buf); + break; + + case IO_METHOD_MMAP: + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl (st->fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + warning("v4l2: VIDIOC_DQBUF: %m\n", errno); + return errno; + } + } + + if (buf.index >= st->n_buffers) { + warning("v4l2: index >= n_buffers\n"); + } + + call_frame_handler(st, st->buffers[buf.index].start); + + if (-1 == xioctl (st->fd, VIDIOC_QBUF, &buf)) { + warning("v4l2: VIDIOC_QBUF\n"); + return errno; + } + break; + } + + return 0; +} + + +static int vd_open(struct vidsrc_st *st, const char *device) +{ + /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open + * the video device. + */ + st->fd = v4l2_open(device, O_RDWR); + if (st->fd < 0) { + warning("v4l2: open %s: %m\n", device, errno); + return errno; + } + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + stop_capturing(st); + uninit_device(st); + + if (st->fd >= 0) + v4l2_close(st->fd); + + mem_deref(st->mb); + mem_deref(st->vs); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + int err; + + while (st->run) { + err = read_frame(st); + if (err) { + warning("v4l2: read_frame: %m\n", err); + } + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + if (!str_isset(dev)) + dev = "/dev/video0"; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->fd = -1; + st->io = IO_METHOD_MMAP; + + st->app_sz = *size; + st->frameh = frameh; + st->arg = arg; + + err = vd_open(st, dev); + if (err) + goto out; + + /* Try Video4Linux 2 first .. */ + err = v4l2_init_device(st, dev); + if (err) + goto out; + + get_video_input(st); + + st->mb = mbuf_alloc(st->app_sz.w * st->app_sz.h * 3 / 2); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + err = start_capturing(st, st->fd); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int v4l_init(void) +{ + return vidsrc_register(&vidsrc, "v4l2", alloc, NULL); +} + + +static int v4l_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2) = { + "v4l2", + "vidsrc", + v4l_init, + v4l_close +}; diff --git a/modules/vidbridge/disp.c b/modules/vidbridge/disp.c new file mode 100644 index 0000000..bce9cff --- /dev/null +++ b/modules/vidbridge/disp.c @@ -0,0 +1,95 @@ +/** + * @file vidbridge/disp.c Video bridge -- display + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->vidsrc) + st->vidsrc->vidisp = NULL; + + list_unlink(&st->le); + mem_deref(st->device); + mem_deref(st->vd); +} + + +int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + (void)prm; + (void)resizeh; + (void)arg; + + if (!stp || !vd || !dev) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + err = str_dup(&st->device, dev); + if (err) + goto out; + + /* find the vidsrc with the same device-name */ + st->vidsrc = vidbridge_src_find(dev); + if (st->vidsrc) { + st->vidsrc->vidisp = st; + } + + hash_append(ht_disp, hash_joaat_str(dev), &st->le, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static bool list_apply_handler(struct le *le, void *arg) +{ + struct vidisp_st *st = le->data; + + return 0 == str_cmp(st->device, arg); +} + + +int vidbridge_disp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + int err = 0; + (void)title; + + if (st->vidsrc) + vidbridge_src_input(st->vidsrc, frame); + else { + debug("vidbridge: display: dropping frame (%u x %u)\n", + frame->size.w, frame->size.h); + } + + return err; +} + + +struct vidisp_st *vidbridge_disp_find(const char *device) +{ + return list_ledata(hash_lookup(ht_disp, hash_joaat_str(device), + list_apply_handler, (void *)device)); +} diff --git a/modules/vidbridge/module.mk b/modules/vidbridge/module.mk new file mode 100644 index 0000000..13000b6 --- /dev/null +++ b/modules/vidbridge/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vidbridge +$(MOD)_SRCS += vidbridge.c src.c disp.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/vidbridge/src.c b/modules/vidbridge/src.c new file mode 100644 index 0000000..ff9bf70 --- /dev/null +++ b/modules/vidbridge/src.c @@ -0,0 +1,94 @@ +/** + * @file vidbridge/src.c Video bridge -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->vidisp) + st->vidisp->vidsrc = NULL; + + list_unlink(&st->le); + mem_deref(st->device); + mem_deref(st->vs); +} + + +int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + err = str_dup(&st->device, dev); + if (err) + goto out; + + /* find a vidisp device with same name */ + st->vidisp = vidbridge_disp_find(dev); + if (st->vidisp) { + st->vidisp->vidsrc = st; + } + + hash_append(ht_src, hash_joaat_str(dev), &st->le, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static bool list_apply_handler(struct le *le, void *arg) +{ + struct vidsrc_st *st = le->data; + + return 0 == str_cmp(st->device, arg); +} + + +struct vidsrc_st *vidbridge_src_find(const char *device) +{ + return list_ledata(hash_lookup(ht_src, hash_joaat_str(device), + list_apply_handler, (void *)device)); +} + + +void vidbridge_src_input(const struct vidsrc_st *st, + const struct vidframe *frame) +{ + if (!st || !frame) + return; + + if (st->frameh) + st->frameh((struct vidframe *)frame, st->arg); +} diff --git a/modules/vidbridge/vidbridge.c b/modules/vidbridge/vidbridge.c new file mode 100644 index 0000000..69aa41f --- /dev/null +++ b/modules/vidbridge/vidbridge.c @@ -0,0 +1,58 @@ +/** + * @file vidbridge.c Video bridge + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static struct vidisp *vidisp; +static struct vidsrc *vidsrc; + +struct hash *ht_src; +struct hash *ht_disp; + + +static int module_init(void) +{ + int err; + + err = hash_alloc(&ht_src, 32); + err |= hash_alloc(&ht_disp, 32); + if (err) + return err; + + err = vidisp_register(&vidisp, "vidbridge", vidbridge_disp_alloc, + NULL, vidbridge_disp_display, 0); + if (err) + return err; + + err = vidsrc_register(&vidsrc, "vidbridge", vidbridge_src_alloc, NULL); + if (err) + return err; + + return err; +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + vidisp = mem_deref(vidisp); + + ht_src = mem_deref(ht_src); + ht_disp = mem_deref(ht_disp); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidbridge) = { + "vidbridge", + "video", + module_init, + module_close, +}; diff --git a/modules/vidbridge/vidbridge.h b/modules/vidbridge/vidbridge.h new file mode 100644 index 0000000..15fcdfc --- /dev/null +++ b/modules/vidbridge/vidbridge.h @@ -0,0 +1,47 @@ +/** + * @file vidbridge.h Video bridge -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance (1st) */ + + struct le le; + struct vidisp_st *vidisp; + char *device; + vidsrc_frame_h *frameh; + void *arg; +}; + + +struct vidisp_st { + struct vidisp *vd; /* inheritance (1st) */ + + struct le le; + struct vidsrc_st *vidsrc; + char *device; +}; + + +extern struct hash *ht_src; +extern struct hash *ht_disp; + + +int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg); +int vidbridge_disp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame); +struct vidisp_st *vidbridge_disp_find(const char *device); + + +int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *vidbridge_src_find(const char *device); +void vidbridge_src_input(const struct vidsrc_st *st, + const struct vidframe *frame); diff --git a/modules/vidloop/module.mk b/modules/vidloop/module.mk new file mode 100644 index 0000000..4775ef1 --- /dev/null +++ b/modules/vidloop/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vidloop +$(MOD)_SRCS += vidloop.c + +include mk/mod.mk diff --git a/modules/vidloop/vidloop.c b/modules/vidloop/vidloop.c new file mode 100644 index 0000000..305a876 --- /dev/null +++ b/modules/vidloop/vidloop.c @@ -0,0 +1,392 @@ +/** + * @file vidloop.c Video loop + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <string.h> +#include <time.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** Video Statistics */ +struct vstat { + uint64_t tsamp; + uint32_t frames; + size_t bytes; + uint32_t bitrate; + double efps; +}; + + +/** Video loop */ +struct video_loop { + const struct vidcodec *vc; + struct config_video cfg; + struct videnc_state *enc; + struct viddec_state *dec; + struct vidisp_st *vidisp; + struct vidsrc_st *vsrc; + struct list filtencl; + struct list filtdecl; + struct vstat stat; + struct tmr tmr_bw; + uint16_t seq; +}; + + +static struct video_loop *gvl; + + +static int display(struct video_loop *vl, struct vidframe *frame) +{ + struct le *le; + int err = 0; + + if (!vidframe_isvalid(frame)) + return 0; + + /* Process video frame through all Video Filters */ + for (le = vl->filtdecl.head; le; le = le->next) { + + struct vidfilt_dec_st *st = le->data; + + if (st->vf->dech) + err |= st->vf->dech(st, frame); + } + + /* display frame */ + (void)vidisp_display(vl->vidisp, "Video Loop", frame); + + return err; +} + + +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 video_loop *vl = arg; + struct vidframe frame; + struct mbuf *mb; + int err = 0; + + mb = mbuf_alloc(hdr_len + pld_len); + if (!mb) + return ENOMEM; + + if (hdr_len) + mbuf_write_mem(mb, hdr, hdr_len); + mbuf_write_mem(mb, pld, pld_len); + + mb->pos = 0; + + vl->stat.bytes += mbuf_get_left(mb); + + /* decode */ + frame.data[0] = NULL; + if (vl->dec) { + err = vl->vc->dech(vl->dec, &frame, marker, vl->seq++, mb); + if (err) { + warning("vidloop: codec decode: %m\n", err); + goto out; + } + } + + display(vl, &frame); + + out: + mem_deref(mb); + + return 0; +} + + +static void vidsrc_frame_handler(struct vidframe *frame, void *arg) +{ + struct video_loop *vl = arg; + struct vidframe *f2 = NULL; + struct le *le; + int err = 0; + + ++vl->stat.frames; + + if (frame->fmt != VID_FMT_YUV420P) { + + if (vidframe_alloc(&f2, VID_FMT_YUV420P, &frame->size)) + return; + + vidconv(f2, frame, 0); + + frame = f2; + } + + /* Process video frame through all Video Filters */ + for (le = vl->filtencl.head; le; le = le->next) { + + struct vidfilt_enc_st *st = le->data; + + if (st->vf->ench) + err |= st->vf->ench(st, frame); + } + + if (vl->enc) { + (void)vl->vc->ench(vl->enc, false, frame, + packet_handler, vl); + } + else { + vl->stat.bytes += vidframe_size(frame->fmt, &frame->size); + (void)display(vl, frame); + } + + mem_deref(f2); +} + + +static void vidloop_destructor(void *arg) +{ + struct video_loop *vl = arg; + + tmr_cancel(&vl->tmr_bw); + mem_deref(vl->vsrc); + mem_deref(vl->vidisp); + mem_deref(vl->enc); + mem_deref(vl->dec); + list_flush(&vl->filtencl); + list_flush(&vl->filtdecl); +} + + +static int enable_codec(struct video_loop *vl) +{ + struct videnc_param prm; + int err; + + prm.fps = vl->cfg.fps; + prm.pktsize = 1024; + prm.bitrate = vl->cfg.bitrate; + prm.max_fs = -1; + + /* Use the first video codec */ + vl->vc = vidcodec_find(NULL, NULL); + if (!vl->vc) + return ENOENT; + + err = vl->vc->encupdh(&vl->enc, vl->vc, &prm, NULL); + if (err) { + warning("vidloop: update encoder failed: %m\n", err); + return err; + } + + if (vl->vc->decupdh) { + err = vl->vc->decupdh(&vl->dec, vl->vc, NULL); + if (err) { + warning("vidloop: update decoder failed: %m\n", err); + return err; + } + } + + return 0; +} + + +static void disable_codec(struct video_loop *vl) +{ + vl->enc = mem_deref(vl->enc); + vl->dec = mem_deref(vl->dec); + vl->vc = NULL; +} + + +static void print_status(struct video_loop *vl) +{ + (void)re_fprintf(stderr, "\rstatus: EFPS=%.1f %u kbit/s \r", + vl->stat.efps, vl->stat.bitrate); +} + + +static void calc_bitrate(struct video_loop *vl) +{ + const uint64_t now = tmr_jiffies(); + + if (now > vl->stat.tsamp) { + + const uint32_t dur = (uint32_t)(now - vl->stat.tsamp); + + vl->stat.efps = 1000.0f * vl->stat.frames / dur; + + vl->stat.bitrate = (uint32_t) (8 * vl->stat.bytes / dur); + } + + vl->stat.frames = 0; + vl->stat.bytes = 0; + vl->stat.tsamp = now; +} + + +static void timeout_bw(void *arg) +{ + struct video_loop *vl = arg; + + tmr_start(&vl->tmr_bw, 5000, timeout_bw, vl); + + calc_bitrate(vl); + print_status(vl); +} + + +static int vsrc_reopen(struct video_loop *vl, const struct vidsz *sz) +{ + struct vidsrc_prm prm; + int err; + + info("vidloop: %s,%s: open video source: %u x %u\n", + vl->cfg.src_mod, vl->cfg.src_dev, + sz->w, sz->h); + + prm.orient = VIDORIENT_PORTRAIT; + prm.fps = vl->cfg.fps; + + vl->vsrc = mem_deref(vl->vsrc); + err = vidsrc_alloc(&vl->vsrc, vl->cfg.src_mod, NULL, &prm, sz, + NULL, vl->cfg.src_dev, vidsrc_frame_handler, + NULL, vl); + if (err) { + warning("x11: vidsrc %s failed: %m\n", vl->cfg.src_dev, err); + } + + return err; +} + + +static int video_loop_alloc(struct video_loop **vlp, const struct vidsz *size) +{ + struct video_loop *vl; + struct config *cfg; + struct le *le; + int err = 0; + + cfg = conf_config(); + if (!cfg) + return EINVAL; + + vl = mem_zalloc(sizeof(*vl), vidloop_destructor); + if (!vl) + return ENOMEM; + + vl->cfg = cfg->video; + tmr_init(&vl->tmr_bw); + + /* Video filters */ + for (le = list_head(vidfilt_list()); le; le = le->next) { + struct vidfilt *vf = le->data; + void *ctx = NULL; + + info("vidloop: added video-filter `%s'\n", vf->name); + + err |= vidfilt_enc_append(&vl->filtencl, &ctx, vf); + err |= vidfilt_dec_append(&vl->filtdecl, &ctx, vf); + if (err) { + warning("vidloop: vidfilt error: %m\n", err); + } + } + + err = vsrc_reopen(vl, size); + if (err) + goto out; + + err = vidisp_alloc(&vl->vidisp, NULL, NULL, NULL, NULL, vl); + if (err) { + warning("vidloop: video display failed: %m\n", err); + goto out; + } + + tmr_start(&vl->tmr_bw, 1000, timeout_bw, vl); + + out: + if (err) + mem_deref(vl); + else + *vlp = vl; + + return err; +} + + +/** + * Start the video loop (for testing) + */ +static int vidloop_start(struct re_printf *pf, void *arg) +{ + struct vidsz size; + struct config *cfg = conf_config(); + int err = 0; + + (void)arg; + + size.w = cfg->video.width; + size.h = cfg->video.height; + + if (gvl) { + if (gvl->vc) + disable_codec(gvl); + else + (void)enable_codec(gvl); + + (void)re_hprintf(pf, "%sabled codec: %s\n", + gvl->vc ? "En" : "Dis", + gvl->vc ? gvl->vc->name : ""); + } + else { + (void)re_hprintf(pf, "Enable video-loop on %s,%s: %u x %u\n", + cfg->video.src_mod, cfg->video.src_dev, + size.w, size.h); + + err = video_loop_alloc(&gvl, &size); + if (err) { + warning("vidloop: alloc: %m\n", err); + } + } + + return err; +} + + +static int vidloop_stop(struct re_printf *pf, void *arg) +{ + (void)arg; + + if (gvl) + (void)re_hprintf(pf, "Disable video-loop\n"); + gvl = mem_deref(gvl); + return 0; +} + + +static const struct cmd cmdv[] = { + {'v', 0, "Start video-loop", vidloop_start }, + {'V', 0, "Stop video-loop", vidloop_stop }, +}; + + +static int module_init(void) +{ + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + vidloop_stop(NULL, NULL); + cmd_unregister(cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = { + "vidloop", + "application", + module_init, + module_close, +}; diff --git a/modules/vpx/decode.c b/modules/vpx/decode.c new file mode 100644 index 0000000..f441619 --- /dev/null +++ b/modules/vpx/decode.c @@ -0,0 +1,273 @@ +/** + * @file vpx/decode.c VP8 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8dx.h> +#include "vp8.h" + + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct hdr { + unsigned x:1; + unsigned noref:1; + unsigned start:1; + unsigned partid:4; + /* extension fields */ + unsigned i:1; + unsigned l:1; + unsigned t:1; + unsigned k:1; + uint16_t picid; + uint8_t tl0picidx; + unsigned tid:2; + unsigned y:1; + unsigned keyidx:5; +}; + +struct viddec_state { + vpx_codec_ctx_t ctx; + struct mbuf *mb; + bool ctxup; + bool started; + uint16_t seq; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + if (vds->ctxup) + vpx_codec_destroy(&vds->ctx); + + mem_deref(vds->mb); +} + + +int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + vpx_codec_err_t res; + int err = 0; + (void)vc; + (void)fmtp; + + if (!vdsp) + return EINVAL; + + vds = *vdsp; + + if (vds) + return 0; + + vds = mem_zalloc(sizeof(*vds), destructor); + if (!vds) + return ENOMEM; + + vds->mb = mbuf_alloc(1024); + if (!vds->mb) { + err = ENOMEM; + goto out; + } + + res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp8_dx_algo, NULL, 0); + if (res) { + err = ENOMEM; + goto out; + } + + vds->ctxup = true; + + out: + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb) +{ + uint8_t v; + + memset(hdr, 0, sizeof(*hdr)); + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->x = v>>7 & 0x1; + hdr->noref = v>>5 & 0x1; + hdr->start = v>>4 & 0x1; + hdr->partid = v & 0x07; + + if (hdr->x) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->i = v>>7 & 0x1; + hdr->l = v>>6 & 0x1; + hdr->t = v>>5 & 0x1; + hdr->k = v>>4 & 0x1; + } + + if (hdr->i) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + if (v>>7 & 0x1) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + hdr->picid = (v & 0x7f)<<8; + hdr->picid += mbuf_read_u8(mb); + } + else { + hdr->picid = v & 0x7f; + } + } + + if (hdr->l) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + hdr->tl0picidx = mbuf_read_u8(mb); + } + + if (hdr->t || hdr->k) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->tid = v>>6 & 0x3; + hdr->y = v>>5 & 0x1; + hdr->keyidx = v & 0x1f; + } + + return 0; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +int vp8_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb) +{ + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t *img; + struct hdr hdr; + int err, i; + + if (!vds || !frame || !mb) + return EINVAL; + + err = hdr_decode(&hdr, mb); + if (err) + return err; + +#if 0 + debug("vp8: header: x=%u noref=%u start=%u partid=%u " + "i=%u l=%u t=%u k=%u " + "picid=%u tl0picidx=%u tid=%u y=%u keyidx=%u\n", + hdr.x, hdr.noref, hdr.start, hdr.partid, + hdr.i, hdr.l, hdr.t, hdr.k, + hdr.picid, hdr.tl0picidx, hdr.tid, hdr.y, hdr.keyidx); +#endif + + if (hdr.start && hdr.partid == 0) { + + mbuf_rewind(vds->mb); + vds->started = true; + } + else { + if (!vds->started) + return 0; + + if (seq_diff(vds->seq, seq) != 1) { + mbuf_rewind(vds->mb); + vds->started = false; + return 0; + } + } + + vds->seq = seq; + + err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb)); + if (err) + goto out; + + if (!marker) { + + if (vds->mb->end > DECODE_MAXSZ) { + warning("vp8: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + res = vpx_codec_decode(&vds->ctx, vds->mb->buf, + (unsigned int)vds->mb->end, NULL, 1); + if (res) { + debug("vp8: decode error: %s\n", vpx_codec_err_to_string(res)); + err = EPROTO; + goto out; + } + + img = vpx_codec_get_frame(&vds->ctx, &iter); + if (!img) { + debug("vp8: no picture\n"); + goto out; + } + + if (img->fmt != VPX_IMG_FMT_I420) { + warning("vp8: bad pixel format (%i)\n", img->fmt); + goto out; + } + + for (i=0; i<4; i++) { + frame->data[i] = img->planes[i]; + frame->linesize[i] = img->stride[i]; + } + + frame->size.w = img->d_w; + frame->size.h = img->d_h; + frame->fmt = VID_FMT_YUV420P; + + out: + mbuf_rewind(vds->mb); + vds->started = false; + + return err; +} diff --git a/modules/vpx/encode.c b/modules/vpx/encode.c new file mode 100644 index 0000000..82f6656 --- /dev/null +++ b/modules/vpx/encode.c @@ -0,0 +1,253 @@ +/** + * @file vpx/encode.c VP8 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_encoder.h> +#include <vpx/vp8cx.h> +#include "vp8.h" + + +enum { + HDR_SIZE = 4, +}; + + +struct videnc_state { + vpx_codec_ctx_t ctx; + struct vidsz size; + vpx_codec_pts_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + bool ctxup; + uint16_t picid; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + + if (ves->ctxup) + vpx_codec_destroy(&ves->ctx); +} + + +int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp) +{ + const struct vp8_vidcodec *vp8 = (struct vp8_vidcodec *)vc; + struct videnc_state *ves; + uint32_t max_fs; + (void)vp8; + + if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1)) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), destructor); + if (!ves) + return ENOMEM; + + ves->picid = rand_u16(); + + *vesp = ves; + } + else { + if (ves->ctxup && (ves->bitrate != prm->bitrate || + ves->fps != prm->fps)) { + + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + + max_fs = vp8_max_fs(fmtp); + if (max_fs > 0) + prm->max_fs = max_fs * 256; + + return 0; +} + + +static int open_encoder(struct videnc_state *ves, const struct vidsz *size) +{ + vpx_codec_enc_cfg_t cfg; + vpx_codec_err_t res; + + res = vpx_codec_enc_config_default(&vpx_codec_vp8_cx_algo, &cfg, 0); + if (res) + return EPROTO; + + cfg.g_profile = 2; + cfg.g_w = size->w; + cfg.g_h = size->h; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = ves->fps; + cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; + cfg.g_pass = VPX_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.rc_end_usage = VPX_VBR; + cfg.rc_target_bitrate = ves->bitrate; + cfg.kf_mode = VPX_KF_AUTO; + + if (ves->ctxup) { + debug("vp8: re-opening encoder\n"); + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + + res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp8_cx_algo, &cfg, + VPX_CODEC_USE_OUTPUT_PARTITION); + if (res) { + warning("vp8: enc init: %s\n", vpx_codec_err_to_string(res)); + return EPROTO; + } + + ves->ctxup = true; + + res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 16); + if (res) { + warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + + res = vpx_codec_control(&ves->ctx, VP8E_SET_NOISE_SENSITIVITY, 0); + if (res) { + warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + + return 0; +} + + +static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool noref, bool start, + uint8_t partid, uint16_t picid) +{ + hdr[0] = 1<<7 | noref<<5 | start<<4 | (partid & 0x7); + hdr[1] = 1<<7; + hdr[2] = 1<<7 | (picid>>8 & 0x7f); + hdr[3] = picid & 0xff; +} + + +static inline int packetize(bool marker, const uint8_t *buf, size_t len, + size_t maxlen, bool noref, uint8_t partid, + uint16_t picid, videnc_packet_h *pkth, void *arg) +{ + uint8_t hdr[HDR_SIZE]; + bool start = true; + int err = 0; + + maxlen -= sizeof(hdr); + + while (len > maxlen) { + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(false, hdr, sizeof(hdr), buf, maxlen, arg); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(marker, hdr, sizeof(hdr), buf, len, arg); + + return err; +} + + +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg) +{ + vpx_enc_frame_flags_t flags = 0; + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t img; + int err, i; + + if (!ves || !frame || !pkth || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) { + + err = open_encoder(ves, &frame->size); + if (err) + return err; + + ves->size = frame->size; + } + + if (update) { + /* debug("vp8: picture update\n"); */ + flags |= VPX_EFLAG_FORCE_KF; + } + + memset(&img, 0, sizeof(img)); + + img.fmt = VPX_IMG_FMT_I420; + img.w = img.d_w = frame->size.w; + img.h = img.d_h = frame->size.h; + + for (i=0; i<4; i++) { + img.stride[i] = frame->linesize[i]; + img.planes[i] = frame->data[i]; + } + + res = vpx_codec_encode(&ves->ctx, &img, ves->pts++, 1, + flags, VPX_DL_REALTIME); + if (res) { + warning("vp8: enc error: %s\n", vpx_codec_err_to_string(res)); + return ENOMEM; + } + + ++ves->picid; + + for (;;) { + bool keyframe = false, marker = true; + const vpx_codec_cx_pkt_t *pkt; + uint8_t partid = 0; + + pkt = vpx_codec_get_cx_data(&ves->ctx, &iter); + if (!pkt) + break; + + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) + continue; + + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) + keyframe = true; + + if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) + marker = false; + + if (pkt->data.frame.partition_id >= 0) + partid = pkt->data.frame.partition_id; + + err = packetize(marker, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, !keyframe, partid, ves->picid, + pkth, arg); + if (err) + return err; + } + + return 0; +} diff --git a/modules/vpx/module.mk b/modules/vpx/module.mk new file mode 100644 index 0000000..bcd8c9d --- /dev/null +++ b/modules/vpx/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vpx +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += vpx.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lvpx + +include mk/mod.mk diff --git a/modules/vpx/sdp.c b/modules/vpx/sdp.c new file mode 100644 index 0000000..21ed9d6 --- /dev/null +++ b/modules/vpx/sdp.c @@ -0,0 +1,39 @@ +/** + * @file vpx/sdp.c VP8 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "vp8.h" + + +uint32_t vp8_max_fs(const char *fmtp) +{ + struct pl pl, max_fs; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "max-fs", &max_fs)) + return pl_u32(&max_fs); + + return 0; +} + + +int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct vp8_vidcodec *vp8 = arg; + (void)offer; + + if (!mb || !fmt || !vp8 || !vp8->max_fs) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n", + fmt->id, vp8->max_fs); +} diff --git a/modules/vpx/vp8.h b/modules/vpx/vp8.h new file mode 100644 index 0000000..fb704c5 --- /dev/null +++ b/modules/vpx/vp8.h @@ -0,0 +1,30 @@ +/** + * @file vp8.h Private VP8 Interface + * + * Copyright (C) 2010 Creytiv.com + */ + +struct vp8_vidcodec { + struct vidcodec vc; + uint32_t max_fs; +}; + +/* Encode */ +int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp); +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg); + + +/* Decode */ +int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int vp8_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb); + + +/* SDP */ +uint32_t vp8_max_fs(const char *fmtp); +int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); diff --git a/modules/vpx/vpx.c b/modules/vpx/vpx.c new file mode 100644 index 0000000..4b492cf --- /dev/null +++ b/modules/vpx/vpx.c @@ -0,0 +1,60 @@ +/** + * @file vpx.c VP8 video codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#define VPX_CODEC_DISABLE_COMPAT 1 +#define VPX_DISABLE_CTRL_TYPECHECKS 1 +#include <vpx/vpx_encoder.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8cx.h> +#include <vpx/vp8dx.h> +#include "vp8.h" + + +/* + * Experimental support for WebM VP8 video codec: + * + * http://www.webmproject.org/ + * + * http://tools.ietf.org/html/draft-ietf-payload-vp8-08 + */ + + +static struct vp8_vidcodec vpx = { + .vc = { + .name = "VP8", + .encupdh = vp8_encode_update, + .ench = vp8_encode, + .decupdh = vp8_decode_update, + .dech = vp8_decode, + .fmtp_ench = vp8_fmtp_enc, + }, + .max_fs = 3600 +}; + + +static int module_init(void) +{ + vidcodec_register((struct vidcodec *)&vpx); + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister((struct vidcodec *)&vpx); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vpx) = { + "vpx", + "codec", + module_init, + module_close +}; diff --git a/modules/vumeter/module.mk b/modules/vumeter/module.mk new file mode 100644 index 0000000..caa8605 --- /dev/null +++ b/modules/vumeter/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vumeter +$(MOD)_SRCS += vumeter.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/vumeter/vumeter.c b/modules/vumeter/vumeter.c new file mode 100644 index 0000000..1a17fa8 --- /dev/null +++ b/modules/vumeter/vumeter.c @@ -0,0 +1,198 @@ +/** + * @file vumeter.c VU-meter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <stdlib.h> +#include <re.h> +#include <baresip.h> + + +struct vumeter_enc { + struct aufilt_enc_st af; /* inheritance */ + struct tmr tmr; + int16_t avg_rec; +}; + +struct vumeter_dec { + struct aufilt_dec_st af; /* inheritance */ + struct tmr tmr; + int16_t avg_play; +}; + + +static void enc_destructor(void *arg) +{ + struct vumeter_enc *st = arg; + + list_unlink(&st->af.le); + tmr_cancel(&st->tmr); +} + + +static void dec_destructor(void *arg) +{ + struct vumeter_dec *st = arg; + + list_unlink(&st->af.le); + tmr_cancel(&st->tmr); +} + + +static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc) +{ + int32_t v = 0; + size_t i; + + if (!sampv || !sampc) + return 0; + + for (i=0; i<sampc; i++) + v += abs(sampv[i]); + + return v/sampc; +} + + +static int audio_print_vu(struct re_printf *pf, int16_t *avg) +{ + char buf[16]; + size_t res; + + res = min(2 * sizeof(buf) * (*avg)/0x8000, + sizeof(buf)-1); + + memset(buf, '=', res); + buf[res] = '\0'; + + return re_hprintf(pf, "[%-16s]", buf); +} + + +static void print_vumeter(int pos, int color, int value) +{ + /* move cursor to a fixed position */ + re_fprintf(stderr, "\x1b[%dG", pos); + + /* print VU-meter in Nice colors */ + re_fprintf(stderr, " \x1b[%dm%H\x1b[;m\r", + color, audio_print_vu, &value); +} + + +static void enc_tmr_handler(void *arg) +{ + struct vumeter_enc *st = arg; + + tmr_start(&st->tmr, 100, enc_tmr_handler, st); + print_vumeter(60, 31, st->avg_rec); +} + + +static void dec_tmr_handler(void *arg) +{ + struct vumeter_dec *st = arg; + + tmr_start(&st->tmr, 100, dec_tmr_handler, st); + print_vumeter(80, 32, st->avg_play); +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_enc *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + *stp = (struct aufilt_enc_st *)st; + + return 0; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_dec *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return ENOMEM; + + *stp = (struct aufilt_dec_st *)st; + + return 0; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_enc *vu = (struct vumeter_enc *)st; + + vu->avg_rec = calc_avg_s16(sampv, *sampc); + + if (!tmr_isrunning(&vu->tmr)) + tmr_start(&vu->tmr, 1, enc_tmr_handler, vu); + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_dec *vu = (struct vumeter_dec *)st; + + vu->avg_play = calc_avg_s16(sampv, *sampc); + + if (!tmr_isrunning(&vu->tmr)) + tmr_start(&vu->tmr, 1, dec_tmr_handler, vu); + + return 0; +} + + +static struct aufilt vumeter = { + LE_INIT, "vumeter", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&vumeter); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&vumeter); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vumeter) = { + "vumeter", + "filter", + module_init, + module_close +}; diff --git a/modules/wincons/module.mk b/modules/wincons/module.mk new file mode 100644 index 0000000..aa1746e --- /dev/null +++ b/modules/wincons/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := wincons +$(MOD)_SRCS += wincons.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/wincons/wincons.c b/modules/wincons/wincons.c new file mode 100644 index 0000000..6724fc7 --- /dev/null +++ b/modules/wincons/wincons.c @@ -0,0 +1,183 @@ +/** + * @file wincons.c Windows console input + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <winsock2.h> +#include <re.h> +#include <baresip.h> + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct ui *ui; /* base class */ + struct tmr tmr; + struct mqueue *mq; + HANDLE hThread; + bool run; + ui_input_h *h; + void *arg; +}; + + +static struct ui *wincons; + + +static void destructor(void *arg) +{ + struct ui_st *st = arg; + + st->run = false; + CloseHandle(st->hThread); + + tmr_cancel(&st->tmr); + mem_deref(st->mq); + mem_deref(st->ui); +} + + +static int print_handler(const char *p, size_t size, void *arg) +{ + (void)arg; + + return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM; +} + + +static void report_key(struct ui_st *ui, char key) +{ + struct re_printf pf; + + pf.vph = print_handler; + + if (ui->h) + ui->h(key, &pf, ui->arg); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, 0x00); +} + + +static DWORD WINAPI input_thread(LPVOID arg) +{ + struct ui_st *st = arg; + + HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE ); + DWORD mode; + + /* Switch to raw mode */ + GetConsoleMode(hstdin, &mode); + SetConsoleMode(hstdin, 0); + + while (st->run) { + + char buf[4]; + DWORD i, count = 0; + + ReadConsole(hstdin, buf, sizeof(buf), &count, NULL); + + for (i=0; i<count; i++) { + int ch = buf[i]; + + if (ch == '\r') + ch = '\n'; + + /* + * The keys are read from a thread so we have + * to send them to the RE main event loop via + * a message queue + */ + mqueue_push(st->mq, ch, 0); + } + } + + /* Restore the console to its previous state */ + SetConsoleMode(hstdin, mode); + + return 0; +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct ui_st *st = arg; + (void)data; + + tmr_start(&st->tmr, RELEASE_VAL, timeout, st); + report_key(st, id); +} + + +static int ui_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *ih, void *arg) +{ + struct ui_st *st; + DWORD threadID; + int err = 0; + (void)prm; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(wincons); + st->h = ih; + st->arg = arg; + + tmr_init(&st->tmr); + + err = mqueue_alloc(&st->mq, mqueue_handler, st); + if (err) + goto out; + + st->run = true; + st->hThread = CreateThread(NULL, 0, input_thread, st, 0, &threadID); + if (!st->hThread) { + st->run = false; + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + return ui_register(&wincons, "wincons", ui_alloc, NULL); +} + + +static int module_close(void) +{ + wincons = mem_deref(wincons); + return 0; +} + + +const struct mod_export DECL_EXPORTS(wincons) = { + "wincons", + "ui", + module_init, + module_close +}; diff --git a/modules/winwave/module.mk b/modules/winwave/module.mk new file mode 100644 index 0000000..ba47da0 --- /dev/null +++ b/modules/winwave/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := winwave +$(MOD)_SRCS += winwave.c src.c play.c +$(MOD)_LFLAGS += -lwinmm + +include mk/mod.mk diff --git a/modules/winwave/play.c b/modules/winwave/play.c new file mode 100644 index 0000000..1de2e1f --- /dev/null +++ b/modules/winwave/play.c @@ -0,0 +1,223 @@ +/** + * @file winwave/play.c Windows sound driver -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define WRITE_BUFFERS 4 +#define INC_WPOS(a) ((a) = (((a) + 1) % WRITE_BUFFERS)) + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + struct dspbuf bufs[WRITE_BUFFERS]; + int pos; + HWAVEOUT waveout; + bool rdy; + size_t inuse; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + int i; + + st->wh = NULL; + + /* Mark the device for closing, and wait for all the + * buffers to be returned by the driver + */ + st->rdy = false; + while (st->inuse > 0) + Sleep(50); + + waveOutClose(st->waveout); + + for (i = 0; i < WRITE_BUFFERS; i++) { + waveOutUnprepareHeader(st->waveout, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + mem_deref(st->ap); +} + + +static int dsp_write(struct auplay_st *st) +{ + MMRESULT res; + WAVEHDR *wh; + struct mbuf *mb; + + if (!st->rdy) + return EINVAL; + + wh = &st->bufs[st->pos].wh; + if (wh->dwFlags & WHDR_PREPARED) { + return EINVAL; + } + mb = st->bufs[st->pos].mb; + wh->lpData = (LPSTR)mb->buf; + + if (st->wh) { + st->wh(mb->buf, mb->size, st->arg); + } + + wh->dwBufferLength = mb->size; + wh->dwFlags = 0; + wh->dwUser = (DWORD_PTR) mb; + + waveOutPrepareHeader(st->waveout, wh, sizeof(*wh)); + + INC_WPOS(st->pos); + + res = waveOutWrite(st->waveout, wh, sizeof(*wh)); + if (res != MMSYSERR_NOERROR) + DEBUG_WARNING("dsp_write: waveOutWrite: failed: %08x\n", res); + else + st->inuse++; + + return 0; +} + + +static void CALLBACK waveOutCallback(HWAVEOUT hwo, + UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2) +{ + struct auplay_st *st = (struct auplay_st *)dwInstance; + WAVEHDR *wh = (WAVEHDR *)dwParam1; + + (void)hwo; + (void)dwParam2; + + switch (uMsg) { + + case WOM_OPEN: + st->rdy = true; + break; + + case WOM_DONE: + /*LOCK();*/ + waveOutUnprepareHeader(st->waveout, wh, sizeof(*wh)); + /*UNLOCK();*/ + st->inuse--; + dsp_write(st); + break; + + case WOM_CLOSE: + st->rdy = false; + break; + + default: + break; + } +} + + +static int write_stream_open(struct auplay_st *st, + const struct auplay_prm *prm) +{ + WAVEFORMATEX wfmt; + MMRESULT res; + uint32_t sampc; + int i; + + /* Open an audio I/O stream. */ + st->waveout = NULL; + st->pos = 0; + st->rdy = false; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + for (i = 0; i < WRITE_BUFFERS; i++) { + memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR)); + st->bufs[i].mb = mbuf_alloc(2 * sampc); + } + + wfmt.wFormatTag = WAVE_FORMAT_PCM; + wfmt.nChannels = prm->ch; + wfmt.nSamplesPerSec = prm->srate; + wfmt.wBitsPerSample = 16; + wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8; + wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; + wfmt.cbSize = 0; + + res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveOutCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveOutOpen: failed %d\n", res); + return EINVAL; + } + waveOutClose(st->waveout); + res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveOutCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveOutOpen: failed %d\n", res); + return EINVAL; + } + + return 0; +} + + +int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int i, err; + (void)device; + + if (!stp || !ap || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + prm->fmt = AUFMT_S16LE; + + err = write_stream_open(st, prm); + if (err) + goto out; + + /* The write runs at 100ms intervals + * prepare enough buffers to suite its needs + */ + for (i = 0; i < 5; i++) + dsp_write(st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/winwave/src.c b/modules/winwave/src.c new file mode 100644 index 0000000..1793bf3 --- /dev/null +++ b/modules/winwave/src.c @@ -0,0 +1,206 @@ +/** + * @file winwave/src.c Windows sound driver -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define READ_BUFFERS 4 +#define INC_RPOS(a) ((a) = (((a) + 1) % READ_BUFFERS)) + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + struct dspbuf bufs[READ_BUFFERS]; + int pos; + HWAVEIN wavein; + bool rdy; + size_t inuse; + ausrc_read_h *rh; + void *arg; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + int i; + + st->rh = NULL; + + waveInStop(st->wavein); + waveInReset(st->wavein); + waveInClose(st->wavein); + + for (i = 0; i < READ_BUFFERS; i++) { + waveInUnprepareHeader(st->wavein, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + mem_deref(st->as); +} + + +static int add_wave_in(struct ausrc_st *st) +{ + struct dspbuf *db = &st->bufs[st->pos]; + WAVEHDR *wh = &db->wh; + MMRESULT res; + + wh->lpData = (LPSTR)db->mb->buf; + wh->dwBufferLength = db->mb->size; + wh->dwBytesRecorded = 0; + wh->dwFlags = 0; + wh->dwUser = (DWORD_PTR)db->mb; + + waveInPrepareHeader(st->wavein, wh, sizeof(*wh)); + res = waveInAddBuffer(st->wavein, wh, sizeof(*wh)); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("add_wave_in: waveInAddBuffer fail: %08x\n", + res); + return ENOMEM; + } + + INC_RPOS(st->pos); + + st->inuse++; + + return 0; +} + + +static void CALLBACK waveInCallback(HWAVEOUT hwo, + UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2) +{ + struct ausrc_st *st = (struct ausrc_st *)dwInstance; + WAVEHDR *wh = (WAVEHDR *)dwParam1; + + (void)hwo; + (void)dwParam2; + + if (!st->rh) + return; + + switch (uMsg) { + + case WIM_CLOSE: + st->rdy = false; + break; + + case WIM_OPEN: + st->rdy = true; + break; + + case WIM_DATA: + if (st->inuse < 3) + add_wave_in(st); + + st->rh((uint8_t *)wh->lpData, wh->dwBytesRecorded, st->arg); + + waveInUnprepareHeader(st->wavein, wh, sizeof(*wh)); + st->inuse--; + break; + + default: + break; + } +} + + +static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm) +{ + WAVEFORMATEX wfmt; + MMRESULT res; + uint32_t sampc; + int i, err = 0; + + /* Open an audio INPUT stream. */ + st->wavein = NULL; + st->pos = 0; + st->rdy = false; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + for (i = 0; i < READ_BUFFERS; i++) { + memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR)); + st->bufs[i].mb = mbuf_alloc(2 * sampc); + if (!st->bufs[i].mb) + return ENOMEM; + } + + wfmt.wFormatTag = WAVE_FORMAT_PCM; + wfmt.nChannels = prm->ch; + wfmt.nSamplesPerSec = prm->srate; + wfmt.wBitsPerSample = 16; + wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8; + wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; + wfmt.cbSize = 0; + + res = waveInOpen(&st->wavein, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveInCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveInOpen: failed %d\n", err); + return EINVAL; + } + + /* Prepare enough IN buffers to suite at least 50ms of data */ + for (i = 0; i < READ_BUFFERS; i++) + err |= add_wave_in(st); + + waveInStart(st->wavein); + + return err; +} + + +int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + prm->fmt = AUFMT_S16LE; + + err = read_stream_open(st, prm); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/winwave/winwave.c b/modules/winwave/winwave.c new file mode 100644 index 0000000..141fbfc --- /dev/null +++ b/modules/winwave/winwave.c @@ -0,0 +1,55 @@ +/** + * @file winwave.c Windows sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +static int ww_init(void) +{ + int play_dev_count, src_dev_count; + int err; + + play_dev_count = waveOutGetNumDevs(); + src_dev_count = waveInGetNumDevs(); + + info("winwave: output devices: %d, input devices: %d\n", + play_dev_count, src_dev_count); + + err = ausrc_register(&ausrc, "winwave", winwave_src_alloc); + err |= auplay_register(&auplay, "winwave", winwave_play_alloc); + + return err; +} + + +static int ww_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(winwave) = { + "winwave", + "sound", + ww_init, + ww_close +}; diff --git a/modules/winwave/winwave.h b/modules/winwave/winwave.h new file mode 100644 index 0000000..ccccddf --- /dev/null +++ b/modules/winwave/winwave.h @@ -0,0 +1,20 @@ +/** + * @file winwave.h Windows sound driver -- internal api + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct dspbuf { + WAVEHDR wh; + struct mbuf *mb; +}; + + +int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); +int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); diff --git a/modules/x11/module.mk b/modules/x11/module.mk new file mode 100644 index 0000000..ada2daa --- /dev/null +++ b/modules/x11/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11 +$(MOD)_SRCS += x11.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext + +include mk/mod.mk diff --git a/modules/x11/x11.c b/modules/x11/x11.c new file mode 100644 index 0000000..c75b6bd --- /dev/null +++ b/modules/x11/x11.c @@ -0,0 +1,342 @@ +/** + * @file x11.c Video driver for X11 + * + * Copyright (C) 2010 Creytiv.com + */ + +#ifndef SOLARIS +#define _XOPEN_SOURCE 1 +#endif +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + + Display *disp; + Window win; + GC gc; + XImage *image; + XShmSegmentInfo shm; + bool xshmat; + bool internal; + enum vidfmt pixfmt; +}; + + +static struct vidisp *vid; /**< X11 Video-display */ + +static struct { + int shm_error; + int (*errorh) (Display *, XErrorEvent *); +} x11; + + +/* NOTE: Global handler */ +static int error_handler(Display *d, XErrorEvent *e) +{ + if (e->error_code == BadAccess) + x11.shm_error = 1; + else if (x11.errorh) + return x11.errorh(d, e); + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->image) { + st->image->data = NULL; + XDestroyImage(st->image); + } + + if (st->gc) + XFreeGC(st->disp, st->gc); + + if (st->xshmat) + XShmDetach(st->disp, &st->shm); + + if (st->shm.shmaddr != (char *)-1) + shmdt(st->shm.shmaddr); + + if (st->shm.shmid >= 0) + shmctl(st->shm.shmid, IPC_RMID, NULL); + + if (st->disp) { + if (st->internal && st->win) + XDestroyWindow(st->disp, st->win); + + XCloseDisplay(st->disp); + } + + mem_deref(st->vd); +} + + +static int create_window(struct vidisp_st *st, const struct vidsz *sz) +{ + st->win = XCreateSimpleWindow(st->disp, DefaultRootWindow(st->disp), + 0, 0, sz->w, sz->h, 1, 0, 0); + if (!st->win) { + warning("x11: failed to create X window\n"); + return ENOMEM; + } + + XClearWindow(st->disp, st->win); + XMapRaised(st->disp, st->win); + + return 0; +} + + +static int x11_reset(struct vidisp_st *st, const struct vidsz *sz) +{ + XWindowAttributes attrs; + XGCValues gcv; + size_t bufsz, pixsz; + int err = 0; + + if (!XGetWindowAttributes(st->disp, st->win, &attrs)) { + warning("x11: cant't get window attributes\n"); + return EINVAL; + } + + switch (attrs.depth) { + + case 24: + st->pixfmt = VID_FMT_RGB32; + pixsz = 4; + break; + + case 16: + st->pixfmt = VID_FMT_RGB565; + pixsz = 2; + break; + + case 15: + st->pixfmt = VID_FMT_RGB555; + pixsz = 2; + break; + + default: + warning("x11: colordepth not supported: %d\n", attrs.depth); + return ENOSYS; + } + + bufsz = sz->w * sz->h * pixsz; + + if (st->image) { + XDestroyImage(st->image); + st->image = NULL; + } + + if (st->xshmat) + XShmDetach(st->disp, &st->shm); + + if (st->shm.shmaddr != (char *)-1) + shmdt(st->shm.shmaddr); + + if (st->shm.shmid >= 0) + shmctl(st->shm.shmid, IPC_RMID, NULL); + + st->shm.shmid = shmget(IPC_PRIVATE, bufsz, IPC_CREAT | 0777); + if (st->shm.shmid < 0) { + warning("x11: failed to allocate shared memory\n"); + return ENOMEM; + } + + st->shm.shmaddr = shmat(st->shm.shmid, NULL, 0); + if (st->shm.shmaddr == (char *)-1) { + warning("x11: failed to attach to shared memory\n"); + return ENOMEM; + } + + st->shm.readOnly = true; + + x11.shm_error = 0; + x11.errorh = XSetErrorHandler(error_handler); + + if (!XShmAttach(st->disp, &st->shm)) { + warning("x11: failed to attach X to shared memory\n"); + return ENOMEM; + } + + XSync(st->disp, False); + XSetErrorHandler(x11.errorh); + + if (x11.shm_error) + info("x11: shared memory disabled\n"); + else + st->xshmat = true; + + gcv.graphics_exposures = false; + + st->gc = XCreateGC(st->disp, st->win, GCGraphicsExposures, &gcv); + if (!st->gc) { + warning("x11: failed to create graphics context\n"); + return ENOMEM; + } + + if (st->xshmat) { + st->image = XShmCreateImage(st->disp, attrs.visual, + attrs.depth, ZPixmap, + st->shm.shmaddr, &st->shm, + sz->w, sz->h); + } + else { + st->image = XCreateImage(st->disp, attrs.visual, + attrs.depth, ZPixmap, 0, + st->shm.shmaddr, + sz->w, sz->h, 32, 0); + + } + if (!st->image) { + warning("x11: Failed to create X image\n"); + return ENOMEM; + } + + XResizeWindow(st->disp, st->win, sz->w, sz->h); + + st->size = *sz; + + return err; +} + + +/* prm->view points to the XWINDOW ID */ +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + st->shm.shmaddr = (char *)-1; + + st->disp = XOpenDisplay(NULL); + if (!st->disp) { + warning("x11: could not open X display\n"); + err = ENODEV; + goto out; + } + + /* Use provided view, or create our own */ + if (prm && prm->view) + st->win = (Window)prm->view; + else + st->internal = true; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + struct vidframe frame_rgb; + int err = 0; + + if (!vidsz_cmp(&st->size, &frame->size)) { + char capt[256]; + + if (st->size.w && st->size.h) { + info("x11: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + if (st->internal && !st->win) + err = create_window(st, &frame->size); + + err |= x11_reset(st, &frame->size); + if (err) + return err; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + XStoreName(st->disp, st->win, capt); + } + + /* Convert from YUV420P to RGB */ + + vidframe_init_buf(&frame_rgb, st->pixfmt, &frame->size, + (uint8_t *)st->shm.shmaddr); + + vidconv(&frame_rgb, frame, 0); + + /* draw */ + if (st->xshmat) + XShmPutImage(st->disp, st->win, st->gc, st->image, + 0, 0, 0, 0, st->size.w, st->size.h, false); + else + XPutImage(st->disp, st->win, st->gc, st->image, + 0, 0, 0, 0, st->size.w, st->size.h); + + XSync(st->disp, false); + + return err; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st) + return; + + if (st->win) + XLowerWindow(st->disp, st->win); +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "x11", alloc, NULL, display, hide); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(x11) = { + "x11", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/x11grab/module.mk b/modules/x11grab/module.mk new file mode 100644 index 0000000..5e9b2af --- /dev/null +++ b/modules/x11grab/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11grab +$(MOD)_SRCS += x11grab.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext + +include mk/mod.mk diff --git a/modules/x11grab/x11grab.c b/modules/x11grab/x11grab.c new file mode 100644 index 0000000..93477a5 --- /dev/null +++ b/modules/x11grab/x11grab.c @@ -0,0 +1,218 @@ +/** + * @file x11grab.c X11 grabbing video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#ifndef SOLARIS +#define _XOPEN_SOURCE 1 +#endif +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* + * XXX: add option to select a specific X window and x,y offset + */ + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + Display *disp; + XImage *image; + pthread_t thread; + bool run; + int fps; + struct vidsz size; + enum vidfmt pixfmt; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static int x11grab_open(struct vidsrc_st *st, const struct vidsz *sz) +{ + int x = 0, y = 0; + + st->disp = XOpenDisplay(NULL); + if (!st->disp) { + warning("x11grab: error opening display\n"); + return ENODEV; + } + + st->image = XGetImage(st->disp, + RootWindow(st->disp, DefaultScreen(st->disp)), + x, y, sz->w, sz->h, AllPlanes, ZPixmap); + if (!st->image) { + warning("x11grab: error creating Ximage\n"); + return ENODEV; + } + + switch (st->image->bits_per_pixel) { + + case 32: + st->pixfmt = VID_FMT_RGB32; + break; + + case 16: + st->pixfmt = (st->image->green_mask == 0x7e0) + ? VID_FMT_RGB565 + : VID_FMT_RGB555; + break; + + default: + warning("x11grab: not supported: bpp=%d\n", + st->image->bits_per_pixel); + return ENOSYS; + } + + return 0; +} + + +static inline uint8_t *x11grab_read(struct vidsrc_st *st) +{ + const int x = 0, y = 0; + XImage *im; + + im = XGetSubImage(st->disp, + RootWindow(st->disp, DefaultScreen(st->disp)), + x, y, st->size.w, st->size.h, AllPlanes, ZPixmap, + st->image, 0, 0); + if (!im) + return NULL; + + return (uint8_t *)st->image->data; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, st->pixfmt, &st->size, buf); + + st->frameh(&frame, st->arg); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + uint64_t ts = tmr_jiffies(); + uint8_t *buf; + + while (st->run) { + + if (tmr_jiffies() < ts) { + sys_msleep(4); + continue; + } + + buf = x11grab_read(st); + if (!buf) + continue; + + ts += (1000/st->fps); + + call_frame_handler(st, buf); + } + + return NULL; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->image) + XDestroyImage(st->image); + + if (st->disp) + XCloseDisplay(st->disp); + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + 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_st *st; + int err; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + if (!stp || !prm || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->size = *size; + st->fps = prm->fps; + st->frameh = frameh; + st->arg = arg; + + err = x11grab_open(st, size); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int x11grab_init(void) +{ + return vidsrc_register(&vidsrc, "x11grab", alloc, NULL); +} + + +static int x11grab_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(x11grab) = { + "x11grab", + "vidsrc", + x11grab_init, + x11grab_close +}; diff --git a/modules/zrtp/module.mk b/modules/zrtp/module.mk new file mode 100644 index 0000000..7bcc0ac --- /dev/null +++ b/modules/zrtp/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := zrtp +$(MOD)_SRCS += zrtp.c +$(MOD)_LFLAGS += -lzrtp -lbn +CFLAGS += -I/usr/local/include/libzrtp + +include mk/mod.mk diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c new file mode 100644 index 0000000..1055483 --- /dev/null +++ b/modules/zrtp/zrtp.c @@ -0,0 +1,300 @@ +/** + * @file zrtp.c ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <zrtp.h> + + +/** + * @defgroup zrtp zrtp + * + * ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Experimental support for ZRTP + * + * See http://tools.ietf.org/html/rfc6189 + * + * Briefly tested with Twinkle 1.4.2 and Jitsi 2.2.4603.9615 + * + * This module is using ZRTP implementation in Freeswitch + * https://github.com/traviscross/libzrtp + */ + + +struct menc_sess { + zrtp_session_t *zrtp_session; +}; + +struct menc_media { + const struct menc_sess *sess; + struct udp_helper *uh; + struct sa raddr; + void *rtpsock; + zrtp_stream_t *zrtp_stream; +}; + + +static zrtp_global_t *zrtp_global; +static zrtp_config_t zrtp_config; + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = arg; + + if (st->zrtp_session) + zrtp_session_down(st->zrtp_session); +} + + +static void media_destructor(void *arg) +{ + struct menc_media *st = arg; + + mem_deref(st->uh); + mem_deref(st->rtpsock); + + if (st->zrtp_stream) + zrtp_stream_stop(st->zrtp_stream); +} + + +static bool udp_helper_send(int *err, struct sa *dst, + struct mbuf *mb, void *arg) +{ + struct menc_media *st = arg; + unsigned int length; + zrtp_status_t s; + (void)dst; + + length = (unsigned int)mbuf_get_left(mb); + + s = zrtp_process_rtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_process_rtp failed (status = %d)\n", s); + return false; + } + + /* make sure target buffer is large enough */ + if (length > mbuf_get_space(mb)) { + warning("zrtp: zrtp_process_rtp: length > space (%u > %u)\n", + length, mbuf_get_space(mb)); + *err = ENOMEM; + } + + mb->end = mb->pos + length; + + return false; +} + + +static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_media *st = arg; + unsigned int length; + zrtp_status_t s; + (void)src; + + length = (unsigned int)mbuf_get_left(mb); + + s = zrtp_process_srtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length); + if (s != zrtp_status_ok) { + + if (s == zrtp_status_drop) + return true; + + warning("zrtp: zrtp_process_srtp: %d\n", s); + return false; + } + + mb->end = mb->pos + length; + + return false; +} + + +static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, + bool offerer, menc_error_h *errorh, void *arg) +{ + struct menc_sess *st; + zrtp_status_t s; + int err = 0; + (void)offerer; + (void)errorh; + (void)arg; + + if (!sessp || !sdp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + s = zrtp_session_init(zrtp_global, NULL, + ZRTP_SIGNALING_ROLE_UNKNOWN, &st->zrtp_session); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_session_init failed (status = %d)\n", s); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *sessp = st; + + return err; +} + + +static int media_alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_media *st; + zrtp_status_t s; + int err = 0; + + if (!stp || !sess || proto != IPPROTO_UDP) + return EINVAL; + + st = *stp; + if (st) + goto start; + + st = mem_zalloc(sizeof(*st), media_destructor); + if (!st) + return ENOMEM; + + st->sess = sess; + st->rtpsock = mem_ref(rtpsock); + + err = udp_register_helper(&st->uh, rtpsock, 0, + udp_helper_send, udp_helper_recv, st); + if (err) + goto out; + + s = zrtp_stream_attach(sess->zrtp_session, &st->zrtp_stream); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_stream_attach failed (status=%d)\n", s); + err = EPROTO; + goto out; + } + + zrtp_stream_set_userdata(st->zrtp_stream, st); + + out: + if (err) { + mem_deref(st); + return err; + } + else + *stp = st; + + start: + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + st->raddr = *sdp_media_raddr(sdpm); + + s = zrtp_stream_start(st->zrtp_stream, rtp_sess_ssrc(rtp)); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_stream_start: status = %d\n", s); + } + } + + return err; +} + + +static int zrtp_send_rtp_callback(const zrtp_stream_t *stream, + char *rtp_packet, + unsigned int rtp_packet_length) +{ + struct menc_media *st = zrtp_stream_get_userdata(stream); + struct mbuf *mb; + int err; + + if (!sa_isset(&st->raddr, SA_ALL)) + return zrtp_status_ok; + + mb = mbuf_alloc(rtp_packet_length); + if (!mb) + return zrtp_status_alloc_fail; + + (void)mbuf_write_mem(mb, (void *)rtp_packet, rtp_packet_length); + mb->pos = 0; + + err = udp_send(st->rtpsock, &st->raddr, mb); + if (err) { + warning("zrtp: udp_send %u bytes (%m)\n", + rtp_packet_length, err); + } + + mem_deref(mb); + + return zrtp_status_ok; +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static int module_init(void) +{ + zrtp_status_t s; + char config_path[256] = ""; + int err; + + zrtp_config_defaults(&zrtp_config); + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + if (re_snprintf(zrtp_config.cache_file_cfg.cache_path, + sizeof(zrtp_config.cache_file_cfg.cache_path), + "%s/zrtp_cache.dat", config_path) < 0) + return ENOMEM; + + rand_bytes(zrtp_config.zid, sizeof(zrtp_config.zid)); + + zrtp_config.cb.misc_cb.on_send_packet = zrtp_send_rtp_callback; + + s = zrtp_init(&zrtp_config, &zrtp_global); + if (zrtp_status_ok != s) { + warning("zrtp: zrtp_init() failed (status = %d)\n", s); + return ENOSYS; + } + + menc_register(&menc_zrtp); + + return 0; +} + + +static int module_close(void) +{ + menc_unregister(&menc_zrtp); + + if (zrtp_global) { + zrtp_down(zrtp_global); + zrtp_global = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(zrtp) = { + "zrtp", + "menc", + module_init, + module_close +}; |