summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2014-02-09 11:50:07 +0100
committerAlfred E. Heggestad <aeh@db.org>2014-02-09 11:50:07 +0100
commit98bf08bdcf2edd9d397f32650a8bfe62186fbecf (patch)
treeebc6ec71f44bff8c42e4eefced61948623df02fc /modules
parente6ad5cf4401b860ba402d4b7b3c7c254bc87a019 (diff)
baresip 0.4.10
Diffstat (limited to 'modules')
-rw-r--r--modules/account/account.c155
-rw-r--r--modules/account/module.mk10
-rw-r--r--modules/alsa/alsa.c164
-rw-r--r--modules/alsa/alsa.h18
-rw-r--r--modules/alsa/alsa_play.c155
-rw-r--r--modules/alsa/alsa_src.c156
-rw-r--r--modules/alsa/module.mk11
-rw-r--r--modules/amr/amr.c340
-rw-r--r--modules/amr/module.mk64
-rw-r--r--modules/aubridge/aubridge.c48
-rw-r--r--modules/aubridge/aubridge.h41
-rw-r--r--modules/aubridge/device.c178
-rw-r--r--modules/aubridge/module.mk11
-rw-r--r--modules/aubridge/play.c52
-rw-r--r--modules/aubridge/src.c55
-rw-r--r--modules/audiounit/audiounit.c88
-rw-r--r--modules/audiounit/audiounit.h27
-rw-r--r--modules/audiounit/module.mk14
-rw-r--r--modules/audiounit/player.c189
-rw-r--r--modules/audiounit/recorder.c194
-rw-r--r--modules/audiounit/sess.c174
-rw-r--r--modules/auloop/auloop.c370
-rw-r--r--modules/auloop/module.mk10
-rw-r--r--modules/avcapture/avcapture.m399
-rw-r--r--modules/avcapture/module.mk11
-rw-r--r--modules/avcodec/avcodec.c176
-rw-r--r--modules/avcodec/avcodec.h62
-rw-r--r--modules/avcodec/decode.c346
-rw-r--r--modules/avcodec/encode.c646
-rw-r--r--modules/avcodec/h263.c176
-rw-r--r--modules/avcodec/h264.c188
-rw-r--r--modules/avcodec/h26x.h165
-rw-r--r--modules/avcodec/module.mk20
-rw-r--r--modules/avformat/avf.c365
-rw-r--r--modules/avformat/module.mk11
-rw-r--r--modules/bv32/bv32.c180
-rw-r--r--modules/bv32/module.mk11
-rw-r--r--modules/cairo/cairo.c231
-rw-r--r--modules/cairo/module.mk12
-rw-r--r--modules/celt/celt.c417
-rw-r--r--modules/celt/module.mk11
-rw-r--r--modules/cons/cons.c185
-rw-r--r--modules/cons/module.mk10
-rw-r--r--modules/contact/contact.c214
-rw-r--r--modules/contact/module.mk10
-rw-r--r--modules/coreaudio/coreaudio.c128
-rw-r--r--modules/coreaudio/coreaudio.h20
-rw-r--r--modules/coreaudio/module.mk13
-rw-r--r--modules/coreaudio/player.c167
-rw-r--r--modules/coreaudio/recorder.c199
-rw-r--r--modules/directfb/directfb.c191
-rw-r--r--modules/directfb/module.mk13
-rw-r--r--modules/dshow/dshow.cpp511
-rw-r--r--modules/dshow/module.mk11
-rw-r--r--modules/dtls_srtp/dtls.c234
-rw-r--r--modules/dtls_srtp/dtls_srtp.c403
-rw-r--r--modules/dtls_srtp/dtls_srtp.h59
-rw-r--r--modules/dtls_srtp/module.mk11
-rw-r--r--modules/dtls_srtp/srtp.c232
-rw-r--r--modules/dtls_srtp/tls_udp.c391
-rw-r--r--modules/evdev/evdev.c348
-rw-r--r--modules/evdev/module.mk12
-rw-r--r--modules/evdev/print.c518
-rw-r--r--modules/evdev/print.h11
-rw-r--r--modules/g711/g711.c126
-rw-r--r--modules/g711/module.mk10
-rw-r--r--modules/g722/g722.c196
-rw-r--r--modules/g722/module.mk11
-rw-r--r--modules/g7221/decode.c67
-rw-r--r--modules/g7221/encode.c68
-rw-r--r--modules/g7221/g7221.c49
-rw-r--r--modules/g7221/g7221.h29
-rw-r--r--modules/g7221/module.mk14
-rw-r--r--modules/g7221/sdp.c54
-rw-r--r--modules/g726/g726.c201
-rw-r--r--modules/g726/module.mk11
-rw-r--r--modules/gsm/gsm.c171
-rw-r--r--modules/gsm/module.mk12
-rw-r--r--modules/gst/README34
-rw-r--r--modules/gst/dump.c65
-rw-r--r--modules/gst/gst.c449
-rw-r--r--modules/gst/gst.h9
-rw-r--r--modules/gst/module.mk12
-rw-r--r--modules/httpd/httpd.c103
-rw-r--r--modules/httpd/module.mk10
-rw-r--r--modules/ice/ice.c696
-rw-r--r--modules/ice/module.mk10
-rw-r--r--modules/ilbc/ilbc.c354
-rw-r--r--modules/ilbc/module.mk11
-rw-r--r--modules/isac/isac.c223
-rw-r--r--modules/isac/module.mk11
-rw-r--r--modules/l16/l16.c96
-rw-r--r--modules/l16/module.mk10
-rw-r--r--modules/mda/mda.c40
-rw-r--r--modules/mda/mda.h17
-rw-r--r--modules/mda/player.cpp176
-rw-r--r--modules/mda/recorder.cpp170
-rw-r--r--modules/mda/util.cpp39
-rw-r--r--modules/menu/menu.c618
-rw-r--r--modules/menu/module.mk10
-rw-r--r--modules/mwi/module.mk10
-rw-r--r--modules/mwi/mwi.c141
-rw-r--r--modules/natbd/module.mk10
-rw-r--r--modules/natbd/natbd.c508
-rw-r--r--modules/natpmp/libnatpmp.c235
-rw-r--r--modules/natpmp/libnatpmp.h53
-rw-r--r--modules/natpmp/module.mk10
-rw-r--r--modules/natpmp/natpmp.c313
-rw-r--r--modules/opengl/module.mk11
-rw-r--r--modules/opengl/opengl.m522
-rw-r--r--modules/opengles/context.m115
-rw-r--r--modules/opengles/module.mk16
-rw-r--r--modules/opengles/opengles.c295
-rw-r--r--modules/opengles/opengles.h28
-rw-r--r--modules/opensles/module.mk13
-rw-r--r--modules/opensles/opensles.c66
-rw-r--r--modules/opensles/opensles.h18
-rw-r--r--modules/opensles/player.c172
-rw-r--r--modules/opensles/recorder.c224
-rw-r--r--modules/opus/decode.c101
-rw-r--r--modules/opus/encode.c170
-rw-r--r--modules/opus/module.mk14
-rw-r--r--modules/opus/opus.c63
-rw-r--r--modules/opus/opus.h34
-rw-r--r--modules/opus/sdp.c51
-rw-r--r--modules/oss/module.mk18
-rw-r--r--modules/oss/oss.c353
-rw-r--r--modules/plc/module.mk11
-rw-r--r--modules/plc/plc.c109
-rw-r--r--modules/portaudio/module.mk11
-rw-r--r--modules/portaudio/portaudio.c331
-rw-r--r--modules/presence/module.mk11
-rw-r--r--modules/presence/notifier.c270
-rw-r--r--modules/presence/presence.c41
-rw-r--r--modules/presence/presence.h13
-rw-r--r--modules/presence/subscriber.c273
-rw-r--r--modules/qtcapture/module.mk11
-rw-r--r--modules/qtcapture/qtcapture.m403
-rw-r--r--modules/quicktime/module.mk11
-rw-r--r--modules/quicktime/quicktime.c328
-rw-r--r--modules/rst/audio.c263
-rw-r--r--modules/rst/module.mk14
-rw-r--r--modules/rst/rst.c408
-rw-r--r--modules/rst/rst.h26
-rw-r--r--modules/rst/video.c280
-rw-r--r--modules/sdl/module.mk19
-rw-r--r--modules/sdl/sdl.c319
-rw-r--r--modules/sdl/sdl.h9
-rw-r--r--modules/sdl/util.c54
-rw-r--r--modules/sdl2/module.mk11
-rw-r--r--modules/sdl2/sdl.c238
-rw-r--r--modules/selfview/module.mk11
-rw-r--r--modules/selfview/selfview.c267
-rw-r--r--modules/silk/module.mk11
-rw-r--r--modules/silk/silk.c259
-rw-r--r--modules/snapshot/module.mk11
-rw-r--r--modules/snapshot/png_vf.c188
-rw-r--r--modules/snapshot/png_vf.h6
-rw-r--r--modules/snapshot/snapshot.c90
-rw-r--r--modules/sndfile/module.mk11
-rw-r--r--modules/sndfile/sndfile.c180
-rw-r--r--modules/speex/module.mk12
-rw-r--r--modules/speex/speex.c498
-rw-r--r--modules/speex_aec/module.mk15
-rw-r--r--modules/speex_aec/speex_aec.c222
-rw-r--r--modules/speex_pp/module.mk15
-rw-r--r--modules/speex_pp/speex_pp.c154
-rw-r--r--modules/srtp/module.mk11
-rw-r--r--modules/srtp/sdes.c45
-rw-r--r--modules/srtp/sdes.h22
-rw-r--r--modules/srtp/srtp.c472
-rw-r--r--modules/stdio/module.mk11
-rw-r--r--modules/stdio/stdio.c182
-rw-r--r--modules/stun/module.mk10
-rw-r--r--modules/stun/stun.c251
-rw-r--r--modules/syslog/module.mk10
-rw-r--r--modules/syslog/syslog.c112
-rw-r--r--modules/turn/module.mk10
-rw-r--r--modules/turn/turn.c300
-rw-r--r--modules/uuid/module.mk13
-rw-r--r--modules/uuid/uuid.c96
-rw-r--r--modules/v4l/module.mk11
-rw-r--r--modules/v4l/v4l.c257
-rw-r--r--modules/v4l2/module.mk11
-rw-r--r--modules/v4l2/v4l2.c575
-rw-r--r--modules/vidbridge/disp.c95
-rw-r--r--modules/vidbridge/module.mk11
-rw-r--r--modules/vidbridge/src.c94
-rw-r--r--modules/vidbridge/vidbridge.c58
-rw-r--r--modules/vidbridge/vidbridge.h47
-rw-r--r--modules/vidloop/module.mk10
-rw-r--r--modules/vidloop/vidloop.c392
-rw-r--r--modules/vpx/decode.c273
-rw-r--r--modules/vpx/encode.c253
-rw-r--r--modules/vpx/module.mk14
-rw-r--r--modules/vpx/sdp.c39
-rw-r--r--modules/vpx/vp8.h30
-rw-r--r--modules/vpx/vpx.c60
-rw-r--r--modules/vumeter/module.mk11
-rw-r--r--modules/vumeter/vumeter.c198
-rw-r--r--modules/wincons/module.mk11
-rw-r--r--modules/wincons/wincons.c183
-rw-r--r--modules/winwave/module.mk11
-rw-r--r--modules/winwave/play.c223
-rw-r--r--modules/winwave/src.c206
-rw-r--r--modules/winwave/winwave.c55
-rw-r--r--modules/winwave/winwave.h20
-rw-r--r--modules/x11/module.mk11
-rw-r--r--modules/x11/x11.c342
-rw-r--r--modules/x11grab/module.mk11
-rw-r--r--modules/x11grab/x11grab.c218
-rw-r--r--modules/zrtp/module.mk12
-rw-r--r--modules/zrtp/zrtp.c300
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(&params, fmtp);
+
+ fmt_param_apply(&params, 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, &params);
+}
+
+
+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(&not->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->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(&notifierl, &not->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(&not, 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(&notifierl);
+ 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);