summaryrefslogtreecommitdiff
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
parente6ad5cf4401b860ba402d4b7b3c7c254bc87a019 (diff)
baresip 0.4.10
-rw-r--r--Makefile241
-rw-r--r--debian/changelog83
-rw-r--r--debian/compat1
-rw-r--r--debian/control21
-rw-r--r--debian/copyright9
-rw-r--r--debian/dirs3
-rw-r--r--debian/docs2
-rwxr-xr-xdebian/rules79
-rw-r--r--docs/COPYING31
-rw-r--r--docs/ChangeLog544
-rw-r--r--docs/README199
-rw-r--r--docs/TODO28
-rw-r--r--include/baresip.h902
-rw-r--r--mk/Doxyfile240
-rw-r--r--mk/mod.mk93
-rw-r--r--mk/modules.mk346
-rw-r--r--mk/symbian/baresip.mmp87
-rw-r--r--mk/symbian/baresip_gcce.pkg24
-rw-r--r--mk/symbian/baresip_reg.rss16
-rw-r--r--mk/symbian/baresip_thumb.pkg14
-rw-r--r--mk/symbian/bld.inf12
-rw-r--r--mk/symbian/ecrt.cpp31
-rw-r--r--mk/symbian/mod.cpp19
-rw-r--r--mk/symbian/static.c14
-rw-r--r--mk/win32/baresip.vcproj259
-rw-r--r--mk/win32/static.c14
-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
-rw-r--r--rpm/baresip.spec51
-rw-r--r--share/busy.wavbin0 -> 18459 bytes
-rw-r--r--share/callwaiting.wavbin0 -> 80418 bytes
-rw-r--r--share/error.wavbin0 -> 21463 bytes
-rw-r--r--share/message.wavbin0 -> 16684 bytes
-rw-r--r--share/notfound.wavbin0 -> 15760 bytes
-rw-r--r--share/ring.wavbin0 -> 35184 bytes
-rw-r--r--share/ringback.wavbin0 -> 68084 bytes
-rw-r--r--src/account.c574
-rw-r--r--src/aucodec.c76
-rw-r--r--src/audio.c1369
-rw-r--r--src/aufilt.c37
-rw-r--r--src/auplay.c108
-rw-r--r--src/ausrc.c106
-rw-r--r--src/bfcp.c199
-rw-r--r--src/call.c1567
-rw-r--r--src/cmd.c351
-rw-r--r--src/conf.c380
-rw-r--r--src/config.c706
-rw-r--r--src/contact.c161
-rw-r--r--src/core.h389
-rw-r--r--src/log.c139
-rw-r--r--src/magic.h27
-rw-r--r--src/main.c154
-rw-r--r--src/mctrl.c44
-rw-r--r--src/menc.c63
-rw-r--r--src/message.c138
-rw-r--r--src/metric.c79
-rw-r--r--src/mnat.c87
-rw-r--r--src/module.c156
-rw-r--r--src/net.c439
-rw-r--r--src/play.c317
-rw-r--r--src/realtime.c100
-rw-r--r--src/reg.c269
-rw-r--r--src/rtpkeep.c165
-rw-r--r--src/sdp.c186
-rw-r--r--src/sipreq.c149
-rw-r--r--src/srcs.mk49
-rw-r--r--src/stream.c582
-rw-r--r--src/ua.c1562
-rw-r--r--src/ui.c185
-rw-r--r--src/vidcodec.c81
-rw-r--r--src/video.c1073
-rw-r--r--src/vidfilt.c116
-rw-r--r--src/vidisp.c132
-rw-r--r--src/vidsrc.c134
285 files changed, 44401 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..db5dc0c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,241 @@
+#
+# Makefile
+#
+# Copyright (C) 2010 Creytiv.com
+#
+#
+# Internal features:
+#
+# USE_TLS Enable SIP over TLS transport
+# USE_VIDEO Enable Video-support
+#
+
+USE_VIDEO := 1
+
+PROJECT := baresip
+VERSION := 0.4.10
+
+ifndef LIBRE_MK
+LIBRE_MK := $(shell [ -f ../re/mk/re.mk ] && \
+ echo "../re/mk/re.mk")
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f ../re-$(VERSION)/mk/re.mk ] && \
+ echo "../re-$(VERSION)/mk/re.mk")
+endif
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f /usr/share/re/re.mk ] && \
+ echo "/usr/share/re/re.mk")
+endif
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f /usr/local/share/re/re.mk ] && \
+ echo "/usr/local/share/re/re.mk")
+endif
+endif
+
+include $(LIBRE_MK)
+include mk/modules.mk
+
+ifndef LIBREM_PATH
+LIBREM_PATH := $(shell [ -d ../rem ] && echo "../rem")
+endif
+
+
+CFLAGS += -I. -Iinclude -I$(LIBRE_INC) -I$(SYSROOT)/include
+CFLAGS += -I$(LIBREM_PATH)/include
+CFLAGS += -I$(SYSROOT)/local/include/rem -I$(SYSROOT)/include/rem
+
+CXXFLAGS += -I. -Iinclude -I$(LIBRE_INC)
+CXXFLAGS += -I$(LIBREM_PATH)/include
+CXXFLAGS += -I$(SYSROOT)/local/include/rem -I$(SYSROOT)/include/rem
+CXXFLAGS += $(EXTRA_CXXFLAGS)
+
+ifneq ($(LIBREM_PATH),)
+SPLINT_OPTIONS += -I$(LIBREM_PATH)/include
+CLANG_OPTIONS += -I$(LIBREM_PATH)/include
+endif
+
+ifeq ($(OS),win32)
+STATIC := yes
+endif
+
+
+# Optional dependencies
+ifneq ($(USE_VIDEO),)
+CFLAGS += -DUSE_VIDEO=1
+endif
+ifneq ($(STATIC),)
+CFLAGS += -DSTATIC=1
+CXXFLAGS += -DSTATIC=1
+endif
+CFLAGS += -DMODULE_CONF
+
+INSTALL := install
+ifeq ($(DESTDIR),)
+PREFIX := /usr/local
+else
+PREFIX := /usr
+endif
+BINDIR := $(PREFIX)/bin
+INCDIR := $(PREFIX)/include
+BIN := $(PROJECT)$(BIN_SUFFIX)
+SHARED := lib$(PROJECT)$(LIB_SUFFIX)
+STATICLIB := libbaresip.a
+ifeq ($(STATIC),)
+MOD_BINS:= $(patsubst %,%$(MOD_SUFFIX),$(MODULES))
+endif
+APP_MK := src/srcs.mk
+MOD_MK := $(patsubst %,modules/%/module.mk,$(MODULES))
+MOD_BLD := $(patsubst %,$(BUILD)/modules/%,$(MODULES))
+LIBDIR := $(PREFIX)/lib
+MOD_PATH := $(LIBDIR)/$(PROJECT)/modules
+SHARE_PATH := $(PREFIX)/share/$(PROJECT)
+CFLAGS += -DPREFIX=\"$(PREFIX)\"
+
+
+all: sanity $(MOD_BINS) $(BIN)
+
+.PHONY: modules
+modules: $(MOD_BINS)
+
+include $(APP_MK)
+include $(MOD_MK)
+
+OBJS := $(patsubst %.c,$(BUILD)/src/%.o,$(filter %.c,$(SRCS)))
+OBJS += $(patsubst %.m,$(BUILD)/src/%.o,$(filter %.m,$(SRCS)))
+OBJS += $(patsubst %.S,$(BUILD)/src/%.o,$(filter %.S,$(SRCS)))
+
+APP_OBJS := $(OBJS) $(patsubst %.c,$(BUILD)/src/%.o,$(APP_SRCS)) $(MOD_OBJS)
+
+ifneq ($(LIBREM_PATH),)
+LIBS += -L$(LIBREM_PATH)
+endif
+
+# Static build: include module linker-flags in binary
+ifneq ($(STATIC),)
+LIBS += $(MOD_LFLAGS)
+else
+LIBS += -L$(SYSROOT)/local/lib
+MOD_LFLAGS += -L$(SYSROOT)/local/lib
+endif
+
+LIBS += -lrem -lm
+
+
+-include $(APP_OBJS:.o=.d)
+
+sanity:
+ifeq ($(LIBRE_MK),)
+ @echo "ERROR: Missing common makefile for libre. Check LIBRE_MK"
+ @exit 2
+endif
+ifeq ($(LIBRE_INC),)
+ @echo "ERROR: Missing header files for libre. Check LIBRE_INC"
+ @exit 2
+endif
+ifeq ($(LIBRE_SO),)
+ @echo "ERROR: Missing library files for libre. Check LIBRE_SO"
+ @exit 2
+endif
+
+Makefile: mk/*.mk $(MOD_MK) $(LIBRE_MK)
+
+
+$(SHARED): $(APP_OBJS)
+ @echo " LD $@"
+ @$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -L$(LIBRE_SO) -lre $(LIBS) -o $@
+
+$(STATICLIB): $(APP_OBJS)
+ @echo " AR $@"
+ @rm -f $@; $(AR) $(AFLAGS) $@ $^
+ifneq ($(RANLIB),)
+ @echo " RANLIB $@"
+ @$(RANLIB) $@
+endif
+
+# GPROF requires static linking
+$(BIN): $(APP_OBJS)
+ @echo " LD $@"
+ifneq ($(GPROF),)
+ @$(LD) $(LFLAGS) $(APP_LFLAGS) $^ ../re/libre.a $(LIBS) -o $@
+else
+ @$(LD) $(LFLAGS) $(APP_LFLAGS) $^ -L$(LIBRE_SO) -lre $(LIBS) -o $@
+endif
+
+$(BUILD)/%.o: %.c $(BUILD) Makefile $(APP_MK)
+ @echo " CC $@"
+ @$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/%.o: %.m $(BUILD) Makefile $(APP_MK)
+ @echo " OC $@"
+ @$(CC) $(CFLAGS) $(OBJCFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/%.o: %.S $(BUILD) Makefile $(APP_MK)
+ @echo " AS $@"
+ @$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD): Makefile
+ @mkdir -p $(BUILD)/src $(MOD_BLD)
+ @touch $@
+
+install: $(BIN) $(MOD_BINS)
+ @mkdir -p $(DESTDIR)$(BINDIR)
+ $(INSTALL) -m 0755 $(BIN) $(DESTDIR)$(BINDIR)
+ @mkdir -p $(DESTDIR)$(MOD_PATH)
+ $(INSTALL) -m 0644 $(MOD_BINS) $(DESTDIR)$(MOD_PATH)
+ @mkdir -p $(DESTDIR)$(SHARE_PATH)
+ $(INSTALL) -m 0644 share/* $(DESTDIR)$(SHARE_PATH)
+
+install-dev: install-shared install-static
+
+install-shared: $(SHARED)
+ @mkdir -p $(DESTDIR)$(INCDIR)
+ $(INSTALL) -Cm 0644 include/baresip.h $(DESTDIR)$(INCDIR)
+ @mkdir -p $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0644 $(SHARED) $(DESTDIR)$(LIBDIR)
+
+install-static: $(STATICLIB)
+ @mkdir -p $(DESTDIR)$(INCDIR)
+ $(INSTALL) -Cm 0644 include/baresip.h $(DESTDIR)$(INCDIR)
+ @mkdir -p $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0644 $(STATICLIB) $(DESTDIR)$(LIBDIR)
+
+uninstall:
+ @rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
+ @rm -rf $(DESTDIR)$(MOD_PATH)
+
+.PHONY: clean
+clean:
+ @rm -rf $(BIN) $(MOD_BINS) $(SHARED) $(BUILD)
+ @rm -f *stamp \
+ `find . -name "*.[od]"` \
+ `find . -name "*~"` \
+ `find . -name "\.\#*"`
+
+.PHONY: ccheck
+ccheck:
+ @ccheck.pl > /dev/null
+
+version:
+ @perl -pi -e 's/BARESIP_VERSION.*/BARESIP_VERSION \"$(VERSION)"/' \
+ include/baresip.h
+ @perl -pi -e "s/PROJECT_NUMBER = .*/\
+PROJECT_NUMBER = $(VERSION)/" \
+ mk/Doxyfile
+ @echo "updating version number to $(VERSION)"
+
+src/static.c: $(BUILD) Makefile $(APP_MK) $(MOD_MK)
+ @echo " SH $@"
+ @echo "/* static.c - autogenerated by makefile */" > $@
+ @echo "#include <re_types.h>" >> $@
+ @echo "#include <re_mod.h>" >> $@
+ @echo "" >> $@
+ @for n in $(MODULES); do \
+ echo "extern const struct mod_export exports_$${n};" >> $@ ; \
+ done
+ @echo "" >> $@
+ @echo "const struct mod_export *mod_table[] = {" >> $@
+ @for n in $(MODULES); do \
+ echo " &exports_$${n}," >> $@ ; \
+ done
+ @echo " NULL" >> $@
+ @echo "};" >> $@
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..17bd759
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,83 @@
+baresip (0.4.10) unstable; urgency=low
+
+ * version 0.4.10
+
+ -- Alfred E. Heggestad <aeh@db.org> Thu, 23 Jan 2014 16:00:00 +0100
+
+baresip (0.4.9) unstable; urgency=low
+
+ * version 0.4.9
+
+ -- Alfred E. Heggestad <aeh@db.org> Mon, 6 Jan 2014 16:00:00 +0100
+
+baresip (0.4.8) unstable; urgency=low
+
+ * version 0.4.8
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 6 Dec 2013 23:00:00 +0100
+
+baresip (0.4.7) unstable; urgency=low
+
+ * version 0.4.7
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 12 Nov 2013 22:00:00 +0100
+
+baresip (0.4.6) unstable; urgency=low
+
+ * version 0.4.6
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 11 Oct 2013 20:00:00 +0100
+
+baresip (0.4.5) unstable; urgency=low
+
+ * version 0.4.5
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 31 Aug 2013 18:00:00 +0100
+
+baresip (0.4.4) unstable; urgency=low
+
+ * version 0.4.4
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 18 May 2013 10:00:00 +0100
+
+baresip (0.4.3) unstable; urgency=low
+
+ * version 0.4.3
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 1 Jan 2013 01:01:00 +0100
+
+baresip (0.4.2) unstable; urgency=low
+
+ * version 0.4.2
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 9 Sept 2012 09:09:00 +0100
+
+baresip (0.4.1) unstable; urgency=low
+
+ * version 0.4.1
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 21 Apr 2012 21:04:00 +0100
+
+baresip (0.4.0) unstable; urgency=low
+
+ * version 0.4.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 25 Dec 2011 12:25:00 +0100
+
+baresip (0.3.0) unstable; urgency=low
+
+ * version 0.3.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 7 Sept 2011 07:11:00 +0100
+
+baresip (0.2.0) unstable; urgency=low
+
+ * version 0.2.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 20 May 2011 20:05:00 +0100
+
+baresip (0.1.0) unstable; urgency=low
+
+ * version 0.1.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 5 Nov 2010 05:11:10 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..fde2419
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,21 @@
+Source: baresip
+Section: comm
+Priority: optional
+Maintainer: Alfred E. Heggestad <aeh@db.org>
+Build-Depends: debhelper (>= 4.0.0), librem-dev (>= 0.4.5), libre-dev (>= 0.4.5), libasound2-dev, libavformat-dev, libavdevice-dev
+Standards-Version: 3.6.2
+Homepage: http://www.creytiv.com/baresip.html
+
+Package: baresip
+Architecture: any
+Depends: ${shlibs:Depends}, librem (>= 0.4.5), libre (>= 0.4.5)
+Description: Modular SIP User-Agent with audio and video support
+ .
+ Design goals:
+ .
+ - Minimalistic and modular VoIP client
+ - SIP, SDP, RTP/RTCP, STUN/TURN/ICE
+ - IPv4 and IPv6 support
+ - RFC compliant
+ - Robust, fast, low footprint
+ - Portable C89 and C99 source code
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..bbb27a9
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,9 @@
+This package was debianized by Alfred E. Heggestad <aeh@db.org>
+
+It was downloaded from www.creytiv.com
+
+Copyright Holder: Creytiv.com
+
+License:
+
+Distributed under BSD license
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..217c62a
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,3 @@
+usr/bin
+usr/lib/baresip/modules
+usr/share/baresip
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..cafe689
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+docs/README
+docs/TODO
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..b4edc3b
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,79 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+BARESIP_FLAGS := MOD_AUTODETECT=1
+
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+ $(MAKE) RELEASE=1 $(BARESIP_FLAGS) DESTDIR=$(CURDIR)/debian/baresip
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ -$(MAKE) clean
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
+ $(MAKE) RELEASE=1 $(BARESIP_FLAGS) install DESTDIR=$(CURDIR)/debian/baresip
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+# dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_installinit
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_python
+# dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/docs/COPYING b/docs/COPYING
new file mode 100644
index 0000000..01e779d
--- /dev/null
+++ b/docs/COPYING
@@ -0,0 +1,31 @@
+Copyright (c) 2010 - 2014, Alfred E. Heggestad
+Copyright (c) 2010 - 2014, Richard Aas
+Copyright (c) 2010 - 2014, Creytiv.com
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/docs/ChangeLog b/docs/ChangeLog
new file mode 100644
index 0000000..3133f08
--- /dev/null
+++ b/docs/ChangeLog
@@ -0,0 +1,544 @@
+2014-01-23 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.10
+
+ * baresip:
+ - account: add account_set_display_name() -- thanks Dimitris
+ - audio: use both srate/channels to check if resampler is needed
+ - aufilt: change from frame_size to ptime
+ - auplay: change from frame_size to ptime
+ - ausrc: change from frame_size to ptime
+ - config: add optional ausrc_channels and auplay_channels
+ - config: create config dir with mode 0700 (suggested by Jann Horn)
+ - play: update auplay usage with ptime
+
+ * alsa: update to new ausrc/auplay API with ptime
+ fix bug when snd_pcm_readi() returns -EPIPE (thanks Remik)
+ open device from main thread instead of alsa-thread (thanks EL)
+ (caused problems with Sennheiser Century SC 660 + USB adapter)
+
+ * auloop: minor cleanups and improvements
+
+ * coreaudio: update to new ausrc/auplay API with ptime
+
+ * gst: update to new ausrc/auplay API with ptime
+
+ * l16: fix a bug with sample count
+
+ * opus: fix a memory corruption error in opus_decode_pkloss()
+
+ * oss: update to new ausrc/auplay API with ptime
+
+ * plc: update to new aufilt API with ptime
+
+ * portaudio: update to new ausrc/auplay API with ptime
+ fix bugs when using channels=2 (stereo)
+ configure device index using "device" parameter
+
+ * rst: update to new ausrc/auplay API with ptime
+
+ * speex_aec: update to new aufilt API with ptime
+
+ * speex_pp: update to new aufilt API with ptime
+
+ * winwave: update to new ausrc/auplay API with ptime
+
+ * zrtp: update to use libzrtp from Travis Cross' github
+ use config dir to store ZRTP cache-file (thanks Juha Heinanen)
+
+
+2014-01-06 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.9
+
+ * new modules:
+ - zrtp Media Path Key Agreement for Unicast Secure RTP
+
+ * build:
+ - added support for LLVM clang compiler
+
+ * baresip:
+ - account: add account_laddr()
+ - audio: upgrade to new librem auresamp API
+ - config: use oss,/dev/dsp as default device for FreeBSD
+ - log: added new logging framework
+ - main: added new verbose debug argument (-v)
+ - net: added sanity check for HAVE_INET6 build flag
+ - play: added play_set_path() -- thanks to Dimitris P.
+ - ua: added uag_find_param()
+ - ua: fix param-bug in ua_connect() -- thanks to Juha Heinanen
+
+ * aubridge: upgrade to new librem auresamp API
+
+ * avcodec: use new av_frame_alloc() api
+
+ * celt: deprecate CELT-module, use OPUS instead
+
+ * opengles: fix warnings (thanks to Dimitris P.)
+
+ * opensles: fix bugs in player and recorder
+
+ * opus: encode/decode sdp parameters as of I-D
+
+ * speex_resamp: module removed, replaced by librem's resampler
+
+ * zrtp: new module for ZRTP media encryption (use ;mediaenc=zrtp)
+
+
+2013-12-06 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.8
+
+ * new modules:
+ - dtls_srtp DTLS-SRTP media encryption module (RFC 5763,5764)
+ - aubridge Audio Bridge to connect auplay->ausrc
+ - vidbridge Video Bridge module to connect vidisp->vidsrc
+
+ * baresip:
+ - added RFC 5576 Source-Specific Media Attributes in SDP
+ - audio: set SDP bandwidth only if "rtp_bandwidth" config set
+ - play: do not store a copy of global config
+ - stream: save RTCP statistics from Sender-reports
+ - stream: add SDP ssrc attribute
+ - stream: added metrics for packets/bytes transmit/receive
+ - ua: added uag_current()/_set() to get/set current User-Agent
+ - video: set maximum RTP packet-size to 1024 bytes
+
+ * config:
+ - added "video_display module,device" for Video Display
+ - added "rtp_stats {off,on}" for RTP Statistics after Call
+ - default RTP bandwidth is now 0-0
+
+ * contact: dynamic command description for "Message" handling
+ dial from current UA (thanks to Simon Liebold)
+
+ * isac: upgrade to draft-ietf-avt-rtp-isac-04
+
+ * srtp: added auto-negotiation of RTP-profile for incoming calls
+ (RTP/AVP, RTP/AVPF, RTP/SAVP, RTP/SAVPF)
+
+ * vidloop: fix memory leak
+
+
+2013-11-12 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.7
+
+ * new modules:
+ - httpd HTTP webserver UI module
+
+ * baresip:
+ - added RFC 5506 Support for Reduced-Size RTCP
+ - audio: minor cleanups
+ - cmd: ignore RELEASE key in editor mode
+ - conf: add conf_get_sa()
+ - mnat: add address family (af) to session handler
+ - realtime: fixes for iOS (thanks Dimitris)
+ - ua: make ua_register() public
+ - ua: add ua_calls() to get list of calls
+ - ua: only create register client if regint > 0
+
+ * debian: update dependencies (thanks Juha Heinanen)
+
+ * rpm: added RPM package spec file
+
+ * alsa: open device from thread to avoid blocking re-main loop
+
+ * avcodec: build fixes for Debian Testing
+
+ * avformat: use sys_msleep()
+
+ * contact: improve matching logic (thanks EJC Lindner)
+
+ * dshow: initialize variables (found with cppcheck)
+
+ * evdev: fix formatted printing (found with cppcheck)
+
+ * ice: use address family (AF) from call
+
+ * ilbc: update to separate encoder/decoder states (thanks Dimitris)
+
+ * snapshot: initialize variables (found with cppcheck)
+
+ * stun: use address family (AF) from call
+
+ * turn: use address family (AF) from call
+
+ * uuid: fix usage of strncat()
+
+
+2013-10-11 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.6
+
+ * new modules:
+ - directfb DirectFB video display module (thanks Andreas Shimokawa)
+ - dshow Windows DirectShow vidsrc (thanks Dusan Stevanovic)
+ - wincons Console input driver for Windows
+
+ * baresip:
+ - audio: print audio-pipelines in console/debug
+ - aufilt: split into separate encoder+decoder states
+ - call: add local uri/name, dtmf-handler
+ - call: fix decoding of DTMF/SIP-INFO for '*' and '#'
+ - export CALL_EVENT_* in public API
+ - fix various clang warnings
+ - sipreq: use outbound proxy if specified (thanks EJC Lindner)
+ - ua: add possibility to specify 'struct call' for hangup/answer
+ - ua: move SIP extensions into a dynamic vector container
+ - ua: move playing of tones from call.c to ua.c
+ - vidfilt: split into separate encoder+decoder states
+ - vidisp: remove input handler
+
+ * menu: improve call-transfer handling
+
+ * plc: update to separate encoder/decoder states
+
+ * selfview: update to separate encoder/decoder states
+
+ * snapshot: remove state which was not needed
+
+ * sndfile: update to separate encoder/decoder states
+ print unique timestamp to saved files
+
+ * speex_aec: update to separate encoder/decoder states
+
+ * speex_pp: update to separate encoder/decoder states
+
+ * vidloop: update to separate encoder/decoder vidfilt states
+
+ * vumeter: update to separate encoder/decoder states
+
+ * wincons: new module for Console input on Win32
+
+
+2013-08-31 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.5
+
+ * new modules:
+ - account Account loader module
+ - natpmp NAT-PMP client (RFC 6886)
+ - sdl2 Video display using libSDL2
+
+ * baresip:
+ - account: added SIP account parser and container
+ - config: split conf.c into conf.c and config.c
+ - config: move enum audio_mode to struct config
+ - config: move uuid to struct config
+ - more usage of the #ifdef USE_VIDEO macro
+ - message: add handling of SIP MESSAGE send/recv
+ - mediaenc: added rtp_sock parameter to media-handler
+ - ua: cleanup public struct ua API
+ - vidisp api: remove unused 'parent' parameter
+ - call: handle incoming DTMF in SIP INFO (application/dtmf-relay)
+ - sdp: added sdp_decode_multipart()
+ - net: fix bug on IP-refresh when 'net_interface' is used
+ - video: minor cleanups
+ handle incoming RTCP_RTPFB_GNACK
+
+ * isac: fix encode_update() signature
+
+ * menu: move dialbuffer here from ua.c
+ added command 'g' to print current config
+
+ * mwi: multiple MWIs for multiple UAs
+
+ * presence: include supported methods in SIP messages
+
+ * srtp: improved interop and debugging
+ handle incoming RTP/RTCP-demultiplexing
+
+ * uuid: write loaded UUID directly to struct config
+
+ * vidloop: added video-filters
+
+
+2013-05-18 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.4
+
+ * new modules:
+ - g726 G.726 audio codec
+ - mwi Message Waiting Indication
+ - snapshot Save video-stream as PNG images
+
+ * config:
+ - added 'sip_certificate' to use a Certificate for SIP/TLS
+ - added 'ausrc_srate' and 'auplay_srate' to force DSP samplerate
+
+ * baresip:
+ - added a simple BFCP client
+ - aufilt: improved API
+ - mediaenc: improved API with session state
+ - ua: added event handler framework
+ - aucodec: improved API with separate encode/decode state
+ - vidcodec: improved API with separate encode/decode state
+ - sdp.c: added SDP helper functions
+ - ua: move registration client to reg.c
+ - audio: added internal resampler
+
+ * auloop: added config option 'auloop_codec' for setting codec
+
+ * ice: remove old 'ice_interface' config option
+
+ * menu: move handling of status-mode here
+
+ * selfview: added config option 'selfview_size'
+
+ * vp8: upgrade to draft-ietf-payload-vp8-08
+
+ * winwave: cleanup and minor fixes
+
+
+2013-01-01 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.3
+
+ * new modules:
+ - selfview Video selfview as video-filter module
+ - vumeter Audio-filter module to display recording/playback level
+
+ * config:
+ - added 'net_interface" to bind to a specific network interface
+ - added accounts 'regq' parameter for SIP Register client
+
+ * baresip:
+ - added video-filter plugin API (vidfilt)
+ - audio.c: cleanups, split into transmit/receive part
+ - ua: added SIP Allow-header (thanks Juha Heinanen)
+ - ua: added Register q-value (thanks Juha Heinanen)
+ - ua: fix DTMF end event bug
+
+ * avcodec: fix x264 fps bug (thanks Trevor Jim)
+
+ * ice: only include ufrag/pwd in session SDP (thanks Juha Heinanen)
+
+
+2012-09-09 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.2
+
+ * new modules:
+ - auloop Audio-loop test module
+ - contact Contacts module
+ - isac iSAC audio codec
+ - menu Interactive menu
+ - opengles OpenGLES video output
+ - presence Presence module
+ - syslog Syslog module
+ - vidloop Video-loop test module
+
+ * baresip:
+ - added support for call transfer
+ - added support for call waiting
+ - added multiple calls per user-agent
+ - added multiple registrations per user-agent
+ - cmd: added new command interface
+ - ua: handle SIP Require header for incoming calls
+ - ui: cleanup, use dynamic interactive menu
+
+ * config:
+ - added 'audio_alert' for ringtones etc.
+ - added 'outboundX=proxy' for multiple outbound proxies
+ - added 'module_tmp' for temporary module loading
+ - added 'module_app' for application modules
+
+ * avcodec: upgrade to latest FFmpeg and fix pts bug
+
+ * natbd: register command 'z' for status
+
+ * srtp: fix memleak on close
+
+ * uuid: added UUID loader
+
+
+2012-04-21 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.1
+
+ * baresip: do not include rem.h from baresip.h
+ rename struct conf to struct config
+ vidsrc API: move size to alloc handler
+ aucodec API: change fmtp type to 'const char *'
+ add SDP fmtp compare handler
+ vidcodec API: added enqueue and packetizer handlers
+ remove size from vidcodec_prm
+ remove decoder parameters from alloc
+ change fmtp type to 'const char *'
+ add SDP fmtp compare handler
+ remove aufile.c, use librem instead
+ audio: fix Telev timestamp (thanks Paulo Vicentini)
+ configurable order of playback/source start
+ ua_find: match AOR for interop (thanks Tomasz Ostrowski)
+ ua: more robust parsing for incoming MESSAGE
+ ua: password prompt (thanks to Juha Heinanen)
+
+ * build: detect amr, cairo, rst, silk modules
+
+ * config: split 'audio_dev' parameter into 'audio_player/audio_source'
+ order of audio_player/audio_source decide opening order
+ rename 'video_dev' parameter to 'video_source'
+ added optional 'auth_user=NAME' account parameter
+ (idea was suggested by Juha Heinanen)
+
+ * alsa: play: no need to call snd_pcm_start(), explictly started when
+ writing data to the device. (thanks to Christof Meerwald)
+
+ * amr: more portable AMR codec
+
+ * avcodec: automatic size from encoded frames
+ detect packetization-mode from SDP format
+ use enqueue handler
+
+ * avformat: update to latest versions of ffmpeg
+
+ * cairo: new experimental video source module
+
+ * cons: added support for TCP
+
+ * evdev: added KEY_KPx (thanks to ccwufu on OpenWRT forum)
+
+ * g7221: use bitrate from decoded SDP format
+ added optional G722_PCM_SHIFT for 14-bit compat
+
+ * rst: thread-based video source
+
+ * silk: fix crash, init encoder, bitrate=64000 and complexity=2
+ (reported by Juha Heinanen)
+
+ * srtp: decode SDES lifetime and MKI
+
+ * v4l, v4l2: better module detection for FreeBSD 9
+ do not include malloc.h
+ (thanks to Matthias Apitz)
+
+ * vpx: auto init of encoder
+
+ * winwave: fix memory leak (thanks to Tomasz Ostrowski)
+
+ * x11: add support for 16-bit graphics
+
+
+2011-12-25 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.0
+
+ * updated doxygen comments (thanks to Olle E. Johansson)
+
+ * docs: added modules description
+
+ * baresip: add ua_set_aumode(), configurable audio-tx mode
+ vidsrc API: added media_ctx shared with ausrc
+ ausrc API: add media_ctx shared with vidsrc
+ audio_encoder_set() - stop audio source first
+ audio_decoder_set() - include SDP format parameters
+ aufile: add PREFIX to share path (thanks to Juha Heinanen)
+ natbd.c: move code to a new module 'natbd'
+ get_login_name: check both LOGNAME and USER
+ ua.c: unique contact-user with address of struct ua
+ ua.c: find correct UA for incoming SIP Requests
+ ua_connect: param is optional (thanks to Juha Heinanen)
+ video: add video_set_source()
+
+ * amr: minor improvements
+
+ * audiounit: new module for MacOSX/iOS audio driver
+
+ * avcapture: new module for iOS video source
+
+ * avcodec: fixes for newer versions of libavcodec
+
+ * gsm: handle packet-loss
+
+ * natbd: move to separate module from core
+
+ * opengl: fix building on MacOSX 10.7
+ (thanks to David Jedda and Atle Samuelsen)
+
+ * opus: upgrade to opus v0.9.8
+
+ * rst: use media_ctx for shared audio/video stream
+
+ * sndfile: fix stereo mode
+
+
+2011-09-07 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.3.0
+
+ * baresip: use librem for media processing
+ added support for video selfview
+ aubuf, autone, vutil: moved to librem
+ ua: improved API
+ conf: use internal parser instead of fscanf()
+ vidloop: cleanup, use librem for processing
+
+ * config: add video_selfview={pip,window} parameter
+
+ * amr: new module for AMR and AMR-WB audio codecs (RFC 4867)
+
+ * avcodec, avformat: update to latest version of FFmpeg
+
+ * coreaudio: fix building on MacOSX 10.5 (thanks David Jedda)
+
+ * ice: fix building on MacOSX 10.5 (thanks David Jedda)
+
+ * opengl: remove deps to libswscale
+
+ * opensles: new module OpenSLES audio driver
+
+ * opus: new module for OPUS audio codec
+
+ * qtcapture: remove deps to libswscale
+
+ * rst: new module for mp3 audio streaming
+
+ * silk: new module for SILK audio codec
+
+ * v4l, v4l2: remove deps to libswscale
+
+ * x11: remove deps to libswscale, use librem vidconv instead
+
+ * x11grab: remove deps to libswscale
+
+
+2011-05-20 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.2.0
+
+ * baresip: Added support for SIP Outbound (RFC 5626)
+ The SDP Content Attribute (RFC 4796)
+ RTP/RTCP Multiplexing (RFC 5761)
+ RTP Keepalive (draft-ietf-avt-app-rtp-keepalive-09)
+
+ * config: add 'outbound' to sipnat parameter (remove stun, turn)
+ add rtpkeep={zero,stun,dyna,rtcp} parameter
+ audio_codecs parameter can now specify samplerate
+ add rtcp_mux for RTP/RTCP multiplexing on/off
+
+ * alsa: set buffersize and fix samplesize (thanks to Luigi Rizzo)
+
+ * avcodec: added support for MPEG4 video codec (RFC 3016)
+ wait for keyframe before decoding
+
+ * celt: upgrade libcelt version and cleanups
+
+ * coreaudio: fix buffering in recorder
+
+ * ice: several improvements and fixes
+ added new config options
+
+ * ilbc: handle asymmetric modes
+
+ * opengl: enable vertical sync
+
+ * sdl: upgrade to latest version of libSDL from mercurial
+
+ * vpx: added support for draft-westin-payload-vp8-02
+
+ * x11: handle remote display with optional shared memory
+
+ * x11grab: new video-source module (thanks to Luigi Rizzo)
+
+ * docs: updated doxygen comments
diff --git a/docs/README b/docs/README
new file mode 100644
index 0000000..7fe24bd
--- /dev/null
+++ b/docs/README
@@ -0,0 +1,199 @@
+README
+------
+
+Baresip is a portable and modular SIP User-Agent with audio and video support
+Copyright (c) 2010 - 2014 Creytiv.com
+
+Distributed under BSD license
+
+
+Design goals:
+
+* Minimalistic and modular VoIP client
+* SIP, SDP, RTP/RTCP, STUN/TURN/ICE
+* IPv4 and IPv6 support
+* RFC-compliancy
+* Robust, fast, low footprint
+* Portable C89 and C99 source code
+
+
+Modular Plugin Architecture:
+
+account Account loader
+alsa ALSA audio driver
+amr Adaptive Multi-Rate (AMR) audio codec
+aubridge Audio bridge module
+audiounit AudioUnit audio driver for MacOSX/iOS
+auloop Audio-loop test module
+avcapture Video source using iOS AVFoundation video capture
+avcodec Video codec using FFmpeg
+avformat Video source using FFmpeg libavformat
+bv32 BroadVoice32 audio codec
+cairo Cairo video source
+celt CELT audio codec (obsolete, use opus instead)
+cons UDP/TCP console UI driver
+contact Contacts module
+coreaudio Apple Coreaudio driver
+directfb DirectFB video display module
+dshow Windows DirectShow video source
+dtls_srtp DTLS-SRTP end-to-end encryption
+evdev Linux input driver
+g711 G.711 audio codec
+g722 G.722 audio codec
+g7221 G.722.1 audio codec
+g726 G.726 audio codec
+gsm GSM audio codec
+gst Gstreamer audio source
+httpd HTTP webserver UI-module
+ice ICE protocol for NAT Traversal
+ilbc iLBC audio codec
+isac iSAC audio codec
+l16 L16 audio codec
+mda Symbian Mediaserver audio driver (now deprecated)
+menu Interactive menu
+mwi Message Waiting Indication
+natbd NAT Behavior Discovery Module
+natpmp NAT Port Mapping Protocol (NAT-PMP) module
+opengl OpenGL video output
+opengles OpenGLES video output
+opensles OpenSLES audio driver
+opus OPUS Interactive audio codec
+oss Open Sound System (OSS) audio driver
+plc Packet Loss Concealment (PLC) using spandsp
+portaudio Portaudio driver
+presence Presence module
+qtcapture Apple QTCapture video source driver
+quicktime Apple Quicktime video source driver
+rst Radio streamer using mpg123
+sdl Simple DirectMedia Layer (SDL) video output driver
+sdl2 Simple DirectMedia Layer v2 (SDL2) video output driver
+selfview Video selfview module
+silk SILK audio codec
+snapshot Save video-stream as PNG images
+sndfile Audio dumper using libsndfile
+speex Speex audio codec
+speex_aec Acoustic Echo Cancellation (AEC) using libspeexdsp
+speex_pp Audio pre-processor using libspeexdsp
+srtp Secure RTP encryption
+stdio Standard input/output UI driver
+stun Session Traversal Utilities for NAT (STUN) module
+syslog Syslog module
+turn Obtaining Relay Addresses from STUN (TURN) module
+uuid UUID generator and loader
+v4l Video4Linux video source
+v4l2 Video4Linux2 video source
+vidbridge Video bridge module
+vidloop Video-loop test module
+vpx VP8/VPX video codec
+vumeter Display audio levels in console
+wincons Console input driver for Windows
+winwave Audio driver for Windows
+x11 X11 video output driver
+x11grab X11 grabber video source
+zrtp ZRTP media encryption module
+
+
+IETF RFC/I-Ds:
+
+* RFC 2190 RTP Payload Format for H.263 Video Streams (Historic)
+* RFC 2429 RTP Payload Format for 1998 ver of ITU-T Rec. H.263 Video (H.263+)
+* RFC 3016 RTP Payload Format for MPEG-4 Audio/Visual Streams
+* RFC 3428 SIP Extension for Instant Messaging
+* RFC 3711 The Secure Real-time Transport Protocol (SRTP)
+* RFC 3856 A Presence Event Package for SIP
+* RFC 3863 Presence Information Data Format (PIDF)
+* RFC 3951 Internet Low Bit Rate Codec (iLBC)
+* RFC 3952 RTP Payload Format for iLBC Speech
+* RFC 3984 RTP Payload Format for H.264 Video
+* RFC 4145 TCP-Based Media Transport in SDP
+* RFC 4240 Basic Network Media Services with SIP (partly)
+* RFC 4298 Broadvoice Speech Codecs
+* RFC 4347 Datagram Transport Layer Security
+* RFC 4568 SDP Security Descriptions for Media Streams
+* RFC 4572 Connection-Oriented Media Transport over TLS Protocol in SDP
+* RFC 4574 The SDP Label Attribute
+* RFC 4585 Extended RTP Profile for RTCP-Based Feedback (RTP/AVPF)
+* RFC 4587 RTP Payload Format for H.261 Video Streams
+* RFC 4629 RTP Payload Format for ITU-T Rec. H.263 Video
+* RFC 4796 The SDP Content Attribute
+* RFC 4867 RTP Payload Format for the AMR and AMR-WB Audio Codecs
+* RFC 4961 Symmetric RTP / RTP Control Protocol (RTCP)
+* RFC 5168 XML Schema for Media Control
+* RFC 5506 Support for Reduced-Size RTCP
+* RFC 5574 RTP Payload Format for the Speex Codec
+* RFC 5576 Source-Specific Media Attributes in SDP
+* RFC 5577 RTP Payload Format for ITU-T Recommendation G.722.1
+* RFC 5626 Managing Client-Initiated Connections in SIP
+* RFC 5761 Multiplexing RTP Data and Control Packets on a Single Port
+* RFC 5763 Framework for Establishing a SRTP Security Context Using DTLS
+* RFC 5764 DTLS Extension to Establish Keys for SRTP
+* RFC 5780 NAT Behaviour Discovery Using STUN
+* RFC 6263 App. Mechanism for Keeping Alive NAT Associated with RTP / RTCP
+* RFC 6716 Definition of the Opus Audio Codec
+* RFC 6886 NAT Port Mapping Protocol (NAT-PMP)
+
+* draft-ietf-avt-rtp-isac-04
+* draft-ietf-payload-vp8-08
+* draft-spittka-payload-rtp-opus-00
+
+
+Architecture:
+
+
+ .------.
+ |Video |
+ _ |Stream|\
+ /|'------' \ 1
+ / \
+ / _\|
+ .--. N .----. M .------. 1 .-------. 1 .-----.
+ |UA|--->|Call|--->|Audio |--->|Generic|--->|Media|
+ '--' '----' |Stream| |Stream | | NAT |
+ |1 '------' '-------' '-----'
+ | C| 1| |
+ \|/ .-----. .----. |
+ .-------. |Codec| |Jbuf| |1
+ | SIP | '-----' '----' |
+ |Session| 1| /|\ |
+ '-------' .---. | \|/
+ |DSP| .--------.
+ '---' |RTP/RTCP|
+ '--------'
+ | SRTP |
+ '--------'
+
+ A User-Agent (UA) has 0-N SIP Calls
+ A SIP Call has 0-M Media Streams
+
+
+Supported platforms:
+
+* Linux
+* FreeBSD
+* OpenBSD
+* NetBSD
+* Symbian OS
+* Solaris
+* Windows
+* Apple Mac OS X and iOS
+* Android
+
+
+Supported compilers:
+
+* gcc (v2.9x to v4.x)
+* gcce
+* llvm clang
+* ms vc2003 compiler
+* codewarrior
+
+
+External dependencies:
+
+libre
+librem
+
+
+Feedback:
+
+- Please send feedback to <libre@creytiv.com>
diff --git a/docs/TODO b/docs/TODO
new file mode 100644
index 0000000..7bef935
--- /dev/null
+++ b/docs/TODO
@@ -0,0 +1,28 @@
+TODO:
+
+-------------------------------------------------------------------------------
+Version v0.x.y:
+
+ ua: add support for SIP GRUU
+
+ conf: move generation of config template to a module ('config.so')
+
+ improve first-time user experience, add a new module that will
+ prompt the user for a SIP uri and (optionally) a password.
+
+ video rate-control, the outgoing video-stream bandwidth should be
+ configurable and the encoder should limit the rate to the configured
+ range. possibly also add a FPS throttler for fast vidsrc modules
+
+ improve gui and multi-UA and multi-call interaction
+
+ avcodec-audio.so -- create a new audio-codec module that will use
+ audio codecs from FFmpeg libavcodec, as a supplement to existing codecs.
+
+-------------------------------------------------------------------------------
+BUGS:
+
+ S605th: no DNS-server IP
+ sdl: crashes in virtualbox/linux
+
+-------------------------------------------------------------------------------
diff --git a/include/baresip.h b/include/baresip.h
new file mode 100644
index 0000000..03ea090
--- /dev/null
+++ b/include/baresip.h
@@ -0,0 +1,902 @@
+/**
+ * @file baresip.h Public Interface to Baresip
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifndef BARESIP_H__
+#define BARESIP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** Defines the Baresip version string */
+#define BARESIP_VERSION "0.4.10"
+
+
+/* forward declarations */
+struct sa;
+struct sdp_media;
+struct sdp_session;
+struct sip_msg;
+struct ua;
+struct vidframe;
+struct vidrect;
+struct vidsz;
+
+
+/*
+ * Account
+ */
+
+struct account;
+
+int account_alloc(struct account **accp, const char *sipaddr);
+int account_debug(struct re_printf *pf, const struct account *acc);
+int account_set_display_name(struct account *acc, const char *dname);
+int account_auth(const struct account *acc, char **username, char **password,
+ const char *realm);
+struct list *account_aucodecl(const struct account *acc);
+struct list *account_vidcodecl(const struct account *acc);
+struct sip_addr *account_laddr(const struct account *acc);
+
+
+/*
+ * Call
+ */
+
+enum call_event {
+ CALL_EVENT_INCOMING,
+ CALL_EVENT_RINGING,
+ CALL_EVENT_PROGRESS,
+ CALL_EVENT_ESTABLISHED,
+ CALL_EVENT_CLOSED,
+ CALL_EVENT_TRANSFER,
+};
+
+struct call;
+
+typedef void (call_event_h)(struct call *call, enum call_event ev,
+ const char *str, void *arg);
+typedef void (call_dtmf_h)(struct call *call, char key, void *arg);
+
+int call_modify(struct call *call);
+int call_hold(struct call *call, bool hold);
+int call_send_digit(struct call *call, char key);
+bool call_has_audio(const struct call *call);
+bool call_has_video(const struct call *call);
+int call_transfer(struct call *call, const char *uri);
+int call_status(struct re_printf *pf, const struct call *call);
+int call_debug(struct re_printf *pf, const struct call *call);
+void call_set_handlers(struct call *call, call_event_h *eh,
+ call_dtmf_h *dtmfh, void *arg);
+uint16_t call_scode(const struct call *call);
+uint32_t call_duration(const struct call *call);
+const char *call_peeruri(const struct call *call);
+const char *call_peername(const struct call *call);
+const char *call_localuri(const struct call *call);
+struct audio *call_audio(const struct call *call);
+struct video *call_video(const struct call *call);
+struct list *call_streaml(const struct call *call);
+struct ua *call_get_ua(const struct call *call);
+
+
+/*
+ * Conf (utils)
+ */
+
+
+/** Defines the configuration line handler */
+typedef int (confline_h)(const struct pl *addr);
+
+int conf_configure(void);
+int conf_modules(void);
+void conf_path_set(const char *path);
+int conf_path_get(char *path, size_t sz);
+int conf_parse(const char *filename, confline_h *ch);
+int conf_get_vidsz(const struct conf *conf, const char *name,
+ struct vidsz *sz);
+int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa);
+bool conf_fileexist(const char *path);
+struct conf *conf_cur(void);
+
+
+/*
+ * Config (core configuration)
+ */
+
+/** A range of numbers */
+struct range {
+ uint32_t min; /**< Minimum number */
+ uint32_t max; /**< Maximum number */
+};
+
+static inline bool in_range(const struct range *rng, uint32_t val)
+{
+ return rng ? (val >= rng->min && val <= rng->max) : false;
+}
+
+/** Audio transmit mode */
+enum audio_mode {
+ AUDIO_MODE_POLL = 0, /**< Polling mode */
+ AUDIO_MODE_THREAD, /**< Use dedicated thread */
+ AUDIO_MODE_THREAD_REALTIME, /**< Use dedicated realtime-thread */
+ AUDIO_MODE_TMR /**< Use timer */
+};
+
+/** Core configuration */
+struct config {
+ /** Input */
+ struct config_input {
+ char device[64]; /**< Input device name */
+ uint32_t port; /**< Input port number */
+ } input;
+
+ /** SIP User-Agent */
+ struct config_sip {
+ uint32_t trans_bsize; /**< SIP Transaction bucket size */
+ char uuid[64]; /**< Universally Unique Identifier */
+ char local[64]; /**< Local SIP Address */
+ char cert[256]; /**< SIP Certificate */
+ } sip;
+
+ /** Audio */
+ struct config_audio {
+ char src_mod[16]; /**< Audio source module */
+ char src_dev[128]; /**< Audio source device */
+ char play_mod[16]; /**< Audio playback module */
+ char play_dev[128]; /**< Audio playback device */
+ char alert_mod[16]; /**< Audio alert module */
+ char alert_dev[128]; /**< Audio alert device */
+ struct range srate; /**< Audio sampling rate in [Hz] */
+ struct range channels; /**< Nr. of audio channels (1=mono) */
+ uint32_t srate_play; /**< Opt. sampling rate for player */
+ uint32_t srate_src; /**< Opt. sampling rate for source */
+ uint32_t channels_play; /**< Opt. channels for player */
+ uint32_t channels_src; /**< Opt. channels for source */
+ bool src_first; /**< Audio source opened first */
+ enum audio_mode txmode; /**< Audio transmit mode */
+ } audio;
+
+#ifdef USE_VIDEO
+ /** Video */
+ struct config_video {
+ char src_mod[16]; /**< Video source module */
+ char src_dev[128]; /**< Video source device */
+ char disp_mod[16]; /**< Video display module */
+ char disp_dev[128]; /**< Video display device */
+ unsigned width, height; /**< Video resolution */
+ uint32_t bitrate; /**< Encoder bitrate in [bit/s] */
+ uint32_t fps; /**< Video framerate */
+ } video;
+#endif
+
+ /** Audio/Video Transport */
+ struct config_avt {
+ uint8_t rtp_tos; /**< Type-of-Service for outg. RTP */
+ struct range rtp_ports; /**< RTP port range */
+ struct range rtp_bw; /**< RTP Bandwidth range [bit/s] */
+ bool rtcp_enable; /**< RTCP is enabled */
+ bool rtcp_mux; /**< RTP/RTCP multiplexing */
+ struct range jbuf_del; /**< Delay, number of frames */
+ bool rtp_stats; /**< Enable RTP statistics */
+ } avt;
+
+ /* Network */
+ struct config_net {
+ char ifname[16]; /**< Bind to interface (optional) */
+ } net;
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ struct config_bfcp {
+ char proto[16]; /**< BFCP Transport (optional) */
+ } bfcp;
+#endif
+};
+
+int config_parse_conf(struct config *cfg, const struct conf *conf);
+int config_print(struct re_printf *pf, const struct config *cfg);
+int config_write_template(const char *file, const struct config *cfg);
+struct config *conf_config(void);
+
+
+/*
+ * Contact
+ */
+
+enum presence_status {
+ PRESENCE_UNKNOWN,
+ PRESENCE_OPEN,
+ PRESENCE_CLOSED,
+ PRESENCE_BUSY
+};
+
+struct contact;
+
+int contact_add(struct contact **contactp, const struct pl *addr);
+int contacts_print(struct re_printf *pf, void *unused);
+void contact_set_presence(struct contact *c, enum presence_status status);
+struct sip_addr *contact_addr(const struct contact *c);
+struct list *contact_list(void);
+const char *contact_str(const struct contact *c);
+const char *contact_presence_str(enum presence_status status);
+
+
+/*
+ * Media Context
+ */
+
+/** Media Context */
+struct media_ctx {
+ const char *id; /**< Media Context identifier */
+};
+
+
+/*
+ * Message
+ */
+
+typedef void (message_recv_h)(const struct pl *peer, const struct pl *ctype,
+ struct mbuf *body, void *arg);
+
+int message_init(message_recv_h *recvh, void *arg);
+void message_close(void);
+int message_send(struct ua *ua, const char *peer, const char *msg);
+
+
+/*
+ * Audio Source
+ */
+
+struct ausrc;
+struct ausrc_st;
+
+/** Audio Source parameters */
+struct ausrc_prm {
+ int fmt; /**< Audio format (enum aufmt) */
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+};
+
+typedef void (ausrc_read_h)(const uint8_t *buf, size_t sz, void *arg);
+typedef void (ausrc_error_h)(int err, const char *str, void *arg);
+
+typedef int (ausrc_alloc_h)(struct ausrc_st **stp, struct ausrc *ausrc,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+
+int ausrc_register(struct ausrc **asp, const char *name,
+ ausrc_alloc_h *alloch);
+const struct ausrc *ausrc_find(const char *name);
+int ausrc_alloc(struct ausrc_st **stp, struct media_ctx **ctx,
+ const char *name,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+
+
+/*
+ * Audio Player
+ */
+
+struct auplay;
+struct auplay_st;
+
+/** Audio Player parameters */
+struct auplay_prm {
+ int fmt; /**< Audio format (enum aufmt) */
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+};
+
+typedef bool (auplay_write_h)(uint8_t *buf, size_t sz, void *arg);
+
+typedef int (auplay_alloc_h)(struct auplay_st **stp, struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+
+int auplay_register(struct auplay **pp, const char *name,
+ auplay_alloc_h *alloch);
+const struct auplay *auplay_find(const char *name);
+int auplay_alloc(struct auplay_st **stp, const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+
+
+/*
+ * Audio Filter
+ */
+
+struct aufilt;
+
+/* Base class */
+struct aufilt_enc_st {
+ const struct aufilt *af;
+ struct le le;
+};
+
+struct aufilt_dec_st {
+ const struct aufilt *af;
+ struct le le;
+};
+
+/** Audio Filter Parameters */
+struct aufilt_prm {
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+};
+
+typedef int (aufilt_encupd_h)(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm);
+typedef int (aufilt_encode_h)(struct aufilt_enc_st *st,
+ int16_t *sampv, size_t *sampc);
+
+typedef int (aufilt_decupd_h)(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm);
+typedef int (aufilt_decode_h)(struct aufilt_dec_st *st,
+ int16_t *sampv, size_t *sampc);
+
+struct aufilt {
+ struct le le;
+ const char *name;
+ aufilt_encupd_h *encupdh;
+ aufilt_encode_h *ench;
+ aufilt_decupd_h *decupdh;
+ aufilt_decode_h *dech;
+};
+
+void aufilt_register(struct aufilt *af);
+void aufilt_unregister(struct aufilt *af);
+struct list *aufilt_list(void);
+
+
+/*
+ * Log
+ */
+
+enum log_level {
+ DEBUG = 0,
+ INFO,
+ WARN,
+#undef ERROR
+ ERROR,
+};
+
+typedef void (log_h)(uint32_t level, const char *msg);
+
+struct log {
+ struct le le;
+ log_h *h;
+};
+
+void log_register_handler(struct log *log);
+void log_unregister_handler(struct log *log);
+void log_enable_debug(bool enable);
+void log_enable_stderr(bool enable);
+void vlog(enum log_level level, const char *fmt, va_list ap);
+void loglv(enum log_level level, const char *fmt, ...);
+void debug(const char *fmt, ...);
+void info(const char *fmt, ...);
+void warning(const char *fmt, ...);
+void error(const char *fmt, ...);
+
+
+/*
+ * Menc - Media encryption (for RTP)
+ */
+
+struct menc;
+struct menc_sess;
+struct menc_media;
+
+
+typedef void (menc_error_h)(int err, void *arg);
+
+typedef int (menc_sess_h)(struct menc_sess **sessp, struct sdp_session *sdp,
+ bool offerer, menc_error_h *errorh, void *arg);
+
+typedef int (menc_media_h)(struct menc_media **mp, struct menc_sess *sess,
+ struct rtp_sock *rtp, int proto,
+ void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm);
+
+struct menc {
+ struct le le;
+ const char *id;
+ const char *sdp_proto;
+ menc_sess_h *sessh;
+ menc_media_h *mediah;
+};
+
+void menc_register(struct menc *menc);
+void menc_unregister(struct menc *menc);
+const struct menc *menc_find(const char *id);
+
+
+/*
+ * Net - Networking
+ */
+
+typedef void (net_change_h)(void *arg);
+
+int net_init(const struct config_net *cfg, int af);
+void net_close(void);
+int net_dnssrv_add(const struct sa *sa);
+void net_change(uint32_t interval, net_change_h *ch, void *arg);
+bool net_check(void);
+int net_af(void);
+int net_debug(struct re_printf *pf, void *unused);
+const struct sa *net_laddr_af(int af);
+const char *net_domain(void);
+struct dnsc *net_dnsc(void);
+
+
+/*
+ * Play - audio file player
+ */
+
+struct play;
+
+int play_file(struct play **playp, const char *filename, int repeat);
+int play_tone(struct play **playp, struct mbuf *tone,
+ uint32_t srate, uint8_t ch, int repeat);
+void play_init(void);
+void play_close(void);
+void play_set_path(const char *path);
+
+
+/*
+ * User Agent
+ */
+
+struct ua;
+
+/** Events from User-Agent */
+enum ua_event {
+ UA_EVENT_REGISTERING = 0,
+ UA_EVENT_REGISTER_OK,
+ UA_EVENT_REGISTER_FAIL,
+ UA_EVENT_UNREGISTERING,
+ UA_EVENT_CALL_INCOMING,
+ UA_EVENT_CALL_RINGING,
+ UA_EVENT_CALL_PROGRESS,
+ UA_EVENT_CALL_ESTABLISHED,
+ UA_EVENT_CALL_CLOSED,
+
+ UA_EVENT_MAX,
+};
+
+/** Video mode */
+enum vidmode {
+ VIDMODE_OFF = 0, /**< Video disabled */
+ VIDMODE_ON, /**< Video enabled */
+};
+
+/** Defines the User-Agent event handler */
+typedef void (ua_event_h)(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg);
+typedef void (options_resp_h)(int err, const struct sip_msg *msg, void *arg);
+
+/* Multiple instances */
+int ua_alloc(struct ua **uap, const char *aor);
+int ua_connect(struct ua *ua, struct call **callp,
+ const char *from_uri, const char *uri,
+ const char *params, enum vidmode vmode);
+void ua_hangup(struct ua *ua, struct call *call,
+ uint16_t scode, const char *reason);
+int ua_answer(struct ua *ua, struct call *call);
+int ua_options_send(struct ua *ua, const char *uri,
+ options_resp_h *resph, void *arg);
+int ua_sipfd(const struct ua *ua);
+int ua_debug(struct re_printf *pf, const struct ua *ua);
+int ua_print_calls(struct re_printf *pf, const struct ua *ua);
+int ua_print_status(struct re_printf *pf, const struct ua *ua);
+int ua_print_supported(struct re_printf *pf, const struct ua *ua);
+int ua_register(struct ua *ua);
+bool ua_isregistered(const struct ua *ua);
+const char *ua_aor(const struct ua *ua);
+const char *ua_cuser(const struct ua *ua);
+const char *ua_outbound(const struct ua *ua);
+struct call *ua_call(const struct ua *ua);
+struct account *ua_prm(const struct ua *ua);
+struct list *ua_calls(const struct ua *ua);
+
+
+/* One instance */
+int ua_init(const char *software, bool udp, bool tcp, bool tls,
+ bool prefer_ipv6);
+void ua_close(void);
+void ua_stop_all(bool forced);
+int uag_reset_transp(bool reg, bool reinvite);
+int uag_event_register(ua_event_h *eh, void *arg);
+void uag_event_unregister(ua_event_h *eh);
+int ua_print_sip_status(struct re_printf *pf, void *unused);
+struct ua *uag_find(const struct pl *cuser);
+struct ua *uag_find_aor(const char *aor);
+struct ua *uag_find_param(const char *name, const char *val);
+struct sip *uag_sip(void);
+const char *uag_event_str(enum ua_event ev);
+struct list *uag_list(void);
+void uag_current_set(struct ua *ua);
+struct ua *uag_current(void);
+struct sipsess_sock *uag_sipsess_sock(void);
+struct sipevent_sock *uag_sipevent_sock(void);
+
+
+/*
+ * User Interface
+ */
+
+struct ui;
+struct ui_st;
+
+/** User Interface parameters */
+struct ui_prm {
+ char *device; /**< Device name */
+ uint16_t port; /**< Port number */
+};
+typedef void (ui_input_h)(char key, struct re_printf *pf, void *arg);
+
+typedef int (ui_alloc_h)(struct ui_st **stp, struct ui_prm *prm,
+ ui_input_h *ih, void *arg);
+typedef int (ui_output_h)(struct ui_st *st, const char *str);
+
+void ui_init(const struct config_input *cfg);
+void ui_input(char key);
+void ui_input_str(const char *str);
+int ui_input_pl(struct re_printf *pf, const struct pl *pl);
+void ui_output(const char *str);
+int ui_register(struct ui **uip, const char *name,
+ ui_alloc_h *alloch, ui_output_h *outh);
+
+
+/*
+ * Command interface
+ */
+
+/** Command flags */
+enum {
+ CMD_PRM = (1<<0), /**< Command with parameter */
+ CMD_PROG = (1<<1), /**< Show progress */
+
+ CMD_IPRM = CMD_PRM | CMD_PROG, /**< Interactive parameter */
+};
+
+/** Command arguments */
+struct cmd_arg {
+ char key; /**< Which key was pressed */
+ char *prm; /**< Optional parameter */
+ bool complete; /**< True if complete */
+};
+
+/** Defines a command */
+struct cmd {
+ char key; /**< Input character */
+ int flags; /**< Optional command flags */
+ const char *desc; /**< Description string */
+ re_printf_h *h; /**< Command handler */
+};
+
+struct cmd_ctx;
+
+int cmd_register(const struct cmd *cmdv, size_t cmdc);
+void cmd_unregister(const struct cmd *cmdv);
+int cmd_process(struct cmd_ctx **ctxp, char key, struct re_printf *pf);
+int cmd_print(struct re_printf *pf, void *unused);
+
+
+/*
+ * Video Source
+ */
+
+struct vidsrc;
+struct vidsrc_st;
+
+/** Video Source parameters */
+struct vidsrc_prm {
+ int orient; /**< Wanted picture orientation (enum vidorient) */
+ int fps; /**< Wanted framerate */
+};
+
+typedef void (vidsrc_frame_h)(struct vidframe *frame, void *arg);
+typedef void (vidsrc_error_h)(int err, void *arg);
+
+typedef int (vidsrc_alloc_h)(struct vidsrc_st **vsp, 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);
+
+typedef void (vidsrc_update_h)(struct vidsrc_st *st, struct vidsrc_prm *prm,
+ const char *dev);
+
+int vidsrc_register(struct vidsrc **vp, const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh);
+const struct vidsrc *vidsrc_find(const char *name);
+struct list *vidsrc_list(void);
+int vidsrc_alloc(struct vidsrc_st **stp, const char *name,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg);
+
+
+/*
+ * Video Display
+ */
+
+struct vidisp;
+struct vidisp_st;
+
+/** Video Display parameters */
+struct vidisp_prm {
+ void *view; /**< Optional view (set by application or module) */
+};
+
+typedef void (vidisp_resize_h)(const struct vidsz *size, void *arg);
+
+typedef int (vidisp_alloc_h)(struct vidisp_st **vp,
+ struct vidisp *vd, struct vidisp_prm *prm,
+ const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+typedef int (vidisp_update_h)(struct vidisp_st *st, bool fullscreen,
+ int orient, const struct vidrect *window);
+typedef int (vidisp_disp_h)(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+typedef void (vidisp_hide_h)(struct vidisp_st *st);
+
+int vidisp_register(struct vidisp **vp, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh);
+int vidisp_alloc(struct vidisp_st **stp, const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+int vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+const struct vidisp *vidisp_find(const char *name);
+
+
+/*
+ * Audio Codec
+ */
+
+/** Audio Codec parameters */
+struct auenc_param {
+ uint32_t ptime; /**< Packet time in [ms] */
+};
+
+struct auenc_state;
+struct audec_state;
+struct aucodec;
+
+typedef int (auenc_update_h)(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+typedef int (auenc_encode_h)(struct auenc_state *aes, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc);
+
+typedef int (audec_update_h)(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp);
+typedef int (audec_decode_h)(struct audec_state *ads, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len);
+typedef int (audec_plc_h)(struct audec_state *ads,
+ int16_t *sampv, size_t *sampc);
+
+struct aucodec {
+ struct le le;
+ const char *pt;
+ const char *name;
+ uint32_t srate;
+ uint8_t ch;
+ const char *fmtp;
+ auenc_update_h *encupdh;
+ auenc_encode_h *ench;
+ audec_update_h *decupdh;
+ audec_decode_h *dech;
+ audec_plc_h *plch;
+ sdp_fmtp_enc_h *fmtp_ench;
+ sdp_fmtp_cmp_h *fmtp_cmph;
+};
+
+void aucodec_register(struct aucodec *ac);
+void aucodec_unregister(struct aucodec *ac);
+const struct aucodec *aucodec_find(const char *name, uint32_t srate,
+ uint8_t ch);
+struct list *aucodec_list(void);
+
+
+/*
+ * Video Codec
+ */
+
+/** Video Codec parameters */
+struct videnc_param {
+ unsigned bitrate; /**< Encoder bitrate in [bit/s] */
+ unsigned pktsize; /**< RTP packetsize in [bytes] */
+ unsigned fps; /**< Video framerate */
+ uint32_t max_fs;
+};
+
+struct videnc_state;
+struct viddec_state;
+struct vidcodec;
+
+typedef int (videnc_packet_h)(bool marker, const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len, void *arg);
+
+typedef int (videnc_update_h)(struct videnc_state **vesp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp);
+typedef int (videnc_encode_h)(struct videnc_state *ves, bool update,
+ const struct vidframe *frame,
+ videnc_packet_h *pkth, void *arg);
+
+typedef int (viddec_update_h)(struct viddec_state **vdsp,
+ const struct vidcodec *vc, const char *fmtp);
+typedef int (viddec_decode_h)(struct viddec_state *vds, struct vidframe *frame,
+ bool marker, uint16_t seq, struct mbuf *mb);
+
+struct vidcodec {
+ struct le le;
+ const char *pt;
+ const char *name;
+ const char *variant;
+ const char *fmtp;
+ videnc_update_h *encupdh;
+ videnc_encode_h *ench;
+ viddec_update_h *decupdh;
+ viddec_decode_h *dech;
+ sdp_fmtp_enc_h *fmtp_ench;
+ sdp_fmtp_cmp_h *fmtp_cmph;
+};
+
+void vidcodec_register(struct vidcodec *vc);
+void vidcodec_unregister(struct vidcodec *vc);
+const struct vidcodec *vidcodec_find(const char *name, const char *variant);
+struct list *vidcodec_list(void);
+
+
+/*
+ * Video Filter
+ */
+
+struct vidfilt;
+
+/* Base class */
+struct vidfilt_enc_st {
+ const struct vidfilt *vf;
+ struct le le;
+};
+
+struct vidfilt_dec_st {
+ const struct vidfilt *vf;
+ struct le le;
+};
+
+typedef int (vidfilt_encupd_h)(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf);
+typedef int (vidfilt_encode_h)(struct vidfilt_enc_st *st,
+ struct vidframe *frame);
+
+typedef int (vidfilt_decupd_h)(struct vidfilt_dec_st **stp, void **ctx,
+ const struct vidfilt *vf);
+typedef int (vidfilt_decode_h)(struct vidfilt_dec_st *st,
+ struct vidframe *frame);
+
+struct vidfilt {
+ struct le le;
+ const char *name;
+ vidfilt_encupd_h *encupdh;
+ vidfilt_encode_h *ench;
+ vidfilt_decupd_h *decupdh;
+ vidfilt_decode_h *dech;
+};
+
+void vidfilt_register(struct vidfilt *vf);
+void vidfilt_unregister(struct vidfilt *vf);
+struct list *vidfilt_list(void);
+int vidfilt_enc_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf);
+int vidfilt_dec_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf);
+
+
+/*
+ * Audio stream
+ */
+
+struct audio;
+
+void audio_mute(struct audio *a, bool muted);
+void audio_set_devicename(struct audio *a, const char *src, const char *play);
+void audio_encoder_cycle(struct audio *audio);
+int audio_debug(struct re_printf *pf, const struct audio *a);
+
+
+/*
+ * Video stream
+ */
+
+struct video;
+
+void video_mute(struct video *v, bool muted);
+void *video_view(const struct video *v);
+int video_set_fullscreen(struct video *v, bool fs);
+int video_set_orient(struct video *v, int orient);
+void video_vidsrc_set_device(struct video *v, const char *dev);
+int video_set_source(struct video *v, const char *name, const char *dev);
+void video_set_devicename(struct video *v, const char *src, const char *disp);
+void video_encoder_cycle(struct video *video);
+int video_debug(struct re_printf *pf, const struct video *v);
+
+
+/*
+ * Media NAT
+ */
+
+struct mnat;
+struct mnat_sess;
+struct mnat_media;
+
+typedef void (mnat_estab_h)(int err, uint16_t scode, const char *reason,
+ void *arg);
+
+typedef int (mnat_sess_h)(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *sdp, bool offerer,
+ mnat_estab_h *estabh, void *arg);
+
+typedef int (mnat_media_h)(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm);
+
+typedef int (mnat_update_h)(struct mnat_sess *sess);
+
+int mnat_register(struct mnat **mnatp, const char *id, const char *ftag,
+ mnat_sess_h *sessh, mnat_media_h *mediah,
+ mnat_update_h *updateh);
+
+
+/*
+ * Real-time
+ */
+int realtime_enable(bool enable, int fps);
+
+
+/*
+ * SDP
+ */
+
+bool sdp_media_has_media(const struct sdp_media *m);
+int sdp_media_find_unused_pt(const struct sdp_media *m);
+int sdp_fingerprint_decode(const char *attr, struct pl *hash,
+ uint8_t *md, size_t *sz);
+uint32_t sdp_media_rattr_u32(const struct sdp_media *sdpm, const char *name);
+const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m,
+ const char *name);
+
+
+/*
+ * Modules
+ */
+
+#ifdef STATIC
+#define DECL_EXPORTS(name) exports_ ##name
+#else
+#define DECL_EXPORTS(name) exports
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* BARESIP_H__ */
diff --git a/mk/Doxyfile b/mk/Doxyfile
new file mode 100644
index 0000000..2552315
--- /dev/null
+++ b/mk/Doxyfile
@@ -0,0 +1,240 @@
+# Doxyfile 1.4.7
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME = baresip
+PROJECT_NUMBER = 0.4.10
+OUTPUT_DIRECTORY = ../baresip-dox
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+#USE_WINDOWS_ENCODING = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH =
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS = YES
+SEPARATE_MEMBER_PAGES = NO
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = YES
+OPTIMIZE_OUTPUT_JAVA = NO
+#BUILTIN_STL_SUPPORT = NO
+DISTRIBUTE_GROUP_DOC = NO
+SUBGROUPING = YES
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+HIDE_UNDOC_MEMBERS = YES
+HIDE_UNDOC_CLASSES = YES
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = NO
+SHOW_INCLUDE_FILES = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_BY_SCOPE_NAME = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+#SHOW_DIRECTORIES = NO
+FILE_VERSION_FILTER =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = YES
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = YES
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = .
+FILE_PATTERNS = *.cpp *.c \
+ *.h \
+ *.m \
+ *.dox
+RECURSIVE = YES
+EXCLUDE =
+
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = */.svn/*
+
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS = *
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+#REFERENCES_LINK_SOURCE = YES
+#USE_HTAGS = NO
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+#HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = NO
+USE_PDFLATEX = NO
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_SCHEMA =
+XML_DTD =
+XML_PROGRAMLISTING = YES
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+SEARCH_INCLUDES = YES
+INCLUDE_PATH = include
+INCLUDE_FILE_PATTERNS =
+PREDEFINED = "DEBUG_MODULE=foo /**< hei */"
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+PERL_PATH = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = YES
+HAVE_DOT = YES
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+UML_LOOK = NO
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = YES
+#CALLER_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+DOT_GRAPH_MAX_NODES = 250
+#MAX_DOT_GRAPH_WIDTH = 1024
+#MAX_DOT_GRAPH_HEIGHT = 1024
+#MAX_DOT_GRAPH_DEPTH = 1000
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE = NO
diff --git a/mk/mod.mk b/mk/mod.mk
new file mode 100644
index 0000000..64ec23c
--- /dev/null
+++ b/mk/mod.mk
@@ -0,0 +1,93 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+$(MOD)_OBJS := $(patsubst %.c,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.c,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.cpp,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.cpp,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.m,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.m,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.S,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.S,$($(MOD)_SRCS)))
+
+-include $($(MOD)_OBJS:.o=.d)
+
+
+$(MOD)_NAME := $(MOD)
+
+
+ifeq ($(STATIC),)
+
+#
+# Dynamically loaded modules
+#
+
+$(MOD)$(MOD_SUFFIX): $($(MOD)_OBJS)
+ @echo " LD [M] $@"
+ @$(LD) $(LFLAGS) $(SH_LFLAGS) $(MOD_LFLAGS) $($(basename $@)_OBJS) \
+ $($(basename $@)_LFLAGS) -L$(LIBRE_SO) -lre -o $@
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.c $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CC [M] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.m $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " OC [M] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) $(OBJCFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.cpp $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CXX [M] $@"
+ @mkdir -p $(dir $@)
+ @$(CXX) $(CXXFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.S $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " AS [M] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+else
+
+#
+# Static linking of modules
+#
+
+# needed to deref variable now, append to list
+MOD_OBJS := $(MOD_OBJS) $($(MOD)_OBJS)
+MOD_LFLAGS := $(MOD_LFLAGS) $($(MOD)_LFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.c $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CC [m] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.m $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " OC [m] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) $(OBJCFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ \
+ $(DFLAGS)
+
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.cpp $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CXX [m] $@"
+ @mkdir -p $(dir $@)
+ @$(CXX) $(CXXFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.S $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " AS [m] $@"
+ @mkdir -p $(dir $@)
+ @$(CC) $(CFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+endif
diff --git a/mk/modules.mk b/mk/modules.mk
new file mode 100644
index 0000000..64428a4
--- /dev/null
+++ b/mk/modules.mk
@@ -0,0 +1,346 @@
+#
+# modules.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+# External libraries:
+#
+# USE_ALSA ALSA audio driver
+# USE_AMR Adaptive Multi-Rate (AMR) audio codec
+# USE_BV32 BroadVoice32 Wideband Audio codec
+# USE_CAIRO Cairo module
+# USE_CONS Console input driver
+# USE_COREAUDIO MacOSX Coreaudio audio driver
+# USE_EVDEV Event Device module
+# USE_FFMPEG FFmpeg video codec libraries
+# USE_G711 G.711 audio codec
+# USE_G722 G.722 audio codec
+# USE_G722_1 G.722.1 audio codec
+# USE_G726 G.726 audio codec
+# USE_GSM GSM audio codec
+# USE_GST Gstreamer audio module
+# USE_ILBC iLBC audio codec
+# USE_ISAC iSAC audio codec
+# USE_L16 L16 audio codec
+# USE_MPG123 Use mpg123
+# USE_OPUS Opus audio codec
+# USE_OSS OSS audio driver
+# USE_PLC Packet Loss Concealment
+# USE_PORTAUDIO Portaudio audio driver
+# USE_SDL libSDL video output
+# USE_SILK SILK (Skype) audio codec
+# USE_SNDFILE sndfile wav dumper
+# USE_SPEEX Speex audio codec
+# USE_SPEEX_AEC Speex Acoustic Echo Canceller
+# USE_SPEEX_PP Speex preprocessor
+# USE_SRTP Secure RTP module
+# USE_STDIO stdio input driver
+# USE_SYSLOG Syslog module
+# USE_UUID UUID module
+# USE_V4L Video4Linux module
+# USE_V4L2 Video4Linux2 module
+# USE_WINWAVE Windows audio driver
+# USE_X11 X11 video output
+#
+
+
+# Default is enabled
+MOD_AUTODETECT := 1
+
+ifneq ($(MOD_AUTODETECT),)
+
+USE_CONS := 1
+USE_G711 := 1
+USE_L16 := 1
+
+ifneq ($(OS),win32)
+
+USE_ALSA := $(shell [ -f $(SYSROOT)/include/alsa/asoundlib.h ] || \
+ [ -f $(SYSROOT_ALT)/include/alsa/asoundlib.h ] && echo "yes")
+USE_AMR := $(shell [ -d $(SYSROOT)/include/opencore-amrnb ] || \
+ [ -d $(SYSROOT_ALT)/include/opencore-amrnb ] || \
+ [ -d $(SYSROOT)/local/include/amrnb ] || \
+ [ -d $(SYSROOT)/include/amrnb ] && echo "yes")
+USE_BV32 := $(shell [ -f $(SYSROOT)/include/bv32/bv32.h ] || \
+ [ -f $(SYSROOT)/local/include/bv32/bv32.h ] && echo "yes")
+USE_CAIRO := $(shell [ -f $(SYSROOT)/include/cairo/cairo.h ] || \
+ [ -f $(SYSROOT_ALT)/include/cairo/cairo.h ] && echo "yes")
+USE_DTLS := $(shell [ -f $(SYSROOT)/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/dtls1.h ] && echo "yes")
+USE_DTLS_SRTP := $(shell [ -f $(SYSROOT)/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/srtp.h ] && echo "yes")
+USE_FFMPEG := $(shell [ -f $(SYSROOT)/include/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT)/local/include/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT)/include/ffmpeg/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT)/include/ffmpeg/avcodec.h ] || \
+ [ -f $(SYSROOT)/local/ffmpeg/avcodec.h ] || \
+ [ -f $(SYSROOT_ALT)/include/libavcodec/avcodec.h ] && echo "yes")
+USE_G722 := $(shell [ -f $(SYSROOT)/include/spandsp/g722.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/g722.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/g722.h ] && echo "yes")
+USE_G722_1 := $(shell [ -f $(SYSROOT)/include/g722_1.h ] || \
+ [ -f $(SYSROOT_ALT)/include/g722_1.h ] || \
+ [ -f $(SYSROOT)/local/include/g722_1.h ] && echo "yes")
+USE_G726 := $(shell [ -f $(SYSROOT)/include/spandsp/g726.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/g726.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/g726.h ] && echo "yes")
+USE_GSM := $(shell [ -f $(SYSROOT)/include/gsm.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gsm.h ] || \
+ [ -f $(SYSROOT)/include/gsm/gsm.h ] || \
+ [ -f $(SYSROOT)/local/include/gsm.h ] || \
+ [ -f $(SYSROOT)/local/include/gsm/gsm.h ] && echo "yes")
+USE_GST := $(shell [ -f $(SYSROOT)/include/gstreamer-0.10/gst/gst.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gstreamer-0.10/gst/gst.h ] && echo "yes")
+USE_ILBC := $(shell [ -f $(SYSROOT)/include/iLBC_define.h ] || \
+ [ -f $(SYSROOT)/local/include/iLBC_define.h ] && echo "yes")
+USE_ISAC := $(shell [ -f $(SYSROOT)/include/isac.h ] || \
+ [ -f $(SYSROOT)/local/include/isac.h ] && echo "yes")
+USE_MPG123 := $(shell [ -f $(SYSROOT)/include/mpg123.h ] || \
+ [ -f $(SYSROOT_ALT)/include/mpg123.h ] && echo "yes")
+USE_OPUS := $(shell [ -f $(SYSROOT)/include/opus/opus.h ] || \
+ [ -f $(SYSROOT_ALT)/include/opus/opus.h ] || \
+ [ -f $(SYSROOT)/local/include/opus/opus.h ] && echo "yes")
+USE_OSS := $(shell [ -f $(SYSROOT)/include/soundcard.h ] || \
+ [ -f $(SYSROOT)/include/linux/soundcard.h ] || \
+ [ -f $(SYSROOT)/include/sys/soundcard.h ] && echo "yes")
+USE_PLC := $(shell [ -f $(SYSROOT)/include/spandsp/plc.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/plc.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/plc.h ] && echo "yes")
+USE_PORTAUDIO := $(shell [ -f $(SYSROOT)/local/include/portaudio.h ] || \
+ [ -f $(SYSROOT)/include/portaudio.h ] || \
+ [ -f $(SYSROOT_ALT)/include/portaudio.h ] && echo "yes")
+USE_SDL := $(shell [ -f $(SYSROOT)/include/SDL/SDL.h ] || \
+ [ -f $(SYSROOT)/local/include/SDL/SDL.h ] || \
+ [ -f $(SYSROOT_ALT)/include/SDL/SDl.h ] && echo "yes")
+USE_SDL2 := $(shell [ -f $(SYSROOT)/include/SDL2/SDL.h ] || \
+ [ -f $(SYSROOT)/local/include/SDL2/SDL.h ] || \
+ [ -f $(SYSROOT_ALT)/include/SDL2/SDl.h ] && echo "yes")
+USE_SILK := $(shell [ -f $(SYSROOT)/include/silk/SKP_Silk_SDK_API.h ] || \
+ [ -f $(SYSROOT_ALT)/include/silk/SKP_Silk_SDK_API.h ] || \
+ [ -f $(SYSROOT)/local/include/silk/SKP_Silk_SDK_API.h ] && echo "yes")
+USE_SNDFILE := $(shell [ -f $(SYSROOT)/include/sndfile.h ] || \
+ [ -f $(SYSROOT_ALT)/include/sndfile.h ] && echo "yes")
+USE_STDIO := $(shell [ -f $(SYSROOT)/include/termios.h ] && echo "yes")
+HAVE_SPEEXDSP := $(shell \
+ [ -f $(SYSROOT)/local/lib/libspeexdsp$(LIB_SUFFIX) ] || \
+ [ -f $(SYSROOT)/lib/libspeexdsp$(LIB_SUFFIX) ] || \
+ [ -f $(SYSROOT_ALT)/lib/libspeexdsp$(LIB_SUFFIX) ] && echo "yes")
+ifeq ($(HAVE_SPEEXDSP),)
+HAVE_SPEEXDSP := \
+ $(shell find $(SYSROOT)/lib -name libspeexdsp$(LIB_SUFFIX) 2>/dev/null)
+endif
+USE_SPEEX := $(shell [ -f $(SYSROOT)/include/speex.h ] || \
+ [ -f $(SYSROOT)/include/speex/speex.h ] || \
+ [ -f $(SYSROOT)/local/include/speex.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex.h ] && echo "yes")
+USE_SPEEX_AEC := $(shell [ -f $(SYSROOT)/include/speex/speex_echo.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex_echo.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex_echo.h ] && echo "yes")
+USE_SPEEX_PP := $(shell [ -f $(SYSROOT)/include/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/local/include/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex_preprocess.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/include/speex/speex_preprocess.h ] && echo "yes")
+USE_SRTP := $(shell [ -f $(SYSROOT)/include/srtp/srtp.h ] || \
+ [ -f $(SYSROOT_ALT)/include/srtp/srtp.h ] || \
+ [ -f $(SYSROOT)/local/include/srtp/srtp.h ] && echo "yes")
+USE_SYSLOG := $(shell [ -f $(SYSROOT)/include/syslog.h ] || \
+ [ -f $(SYSROOT_ALT)/include/syslog.h ] || \
+ [ -f $(SYSROOT)/local/include/syslog.h ] && echo "yes")
+USE_UUID := $(shell [ -f $(SYSROOT)/include/uuid/uuid.h ] && echo "yes")
+USE_V4L := $(shell [ -f $(SYSROOT)/include/libv4l1.h ] || \
+ [ -f $(SYSROOT)/local/include/libv4l1.h ] \
+ && echo "yes")
+USE_V4L2 := $(shell [ -f $(SYSROOT)/include/libv4l2.h ] || \
+ [ -f $(SYSROOT)/local/include/libv4l2.h ] \
+ && echo "yes")
+USE_X11 := $(shell [ -f $(SYSROOT)/include/X11/Xlib.h ] || \
+ [ -f $(SYSROOT)/local/include/X11/Xlib.h ] || \
+ [ -f $(SYSROOT_ALT)/include/X11/Xlib.h ] && echo "yes")
+USE_VPX := $(shell [ -f $(SYSROOT)/include/vpx/vp8.h ] \
+ || [ -f $(SYSROOT)/local/include/vpx/vp8.h ] \
+ || [ -f $(SYSROOT_ALT)/include/vpx/vp8.h ] \
+ && echo "yes")
+endif
+
+# Platform specific modules
+ifeq ($(OS),darwin)
+USE_COREAUDIO := yes
+USE_OPENGL := yes
+
+ifneq ($(USE_FFMPEG),)
+ifneq ($(shell echo | $(CC) -E -dM - | grep '__LP64__'), )
+LP64 := 1
+endif
+
+ifndef LP64
+USE_QUICKTIME := yes
+endif
+
+endif
+
+USE_QTCAPTURE := yes
+
+endif
+ifeq ($(OS),linux)
+USE_EVDEV := $(shell [ -f $(SYSROOT)/include/linux/input.h ] && echo "yes")
+endif
+ifeq ($(OS),win32)
+USE_WINWAVE := yes
+MODULES += wincons
+endif
+
+endif
+
+# ------------------------------------------------------------------------- #
+
+MODULES += $(EXTRA_MODULES)
+MODULES += stun turn ice natbd auloop presence
+MODULES += menu contact vumeter mwi account natpmp httpd
+ifneq ($(HAVE_PTHREAD),)
+MODULES += aubridge
+endif
+ifneq ($(USE_VIDEO),)
+MODULES += vidloop selfview vidbridge
+endif
+
+
+ifneq ($(USE_ALSA),)
+MODULES += alsa
+endif
+ifneq ($(USE_AMR),)
+MODULES += amr
+endif
+ifneq ($(USE_BV32),)
+MODULES += bv32
+endif
+ifneq ($(USE_CAIRO),)
+MODULES += cairo
+ifneq ($(USE_MPG123),)
+MODULES += rst
+endif
+endif
+ifneq ($(USE_CONS),)
+MODULES += cons
+endif
+ifneq ($(USE_COREAUDIO),)
+MODULES += coreaudio
+endif
+ifneq ($(USE_QUICKTIME),)
+MODULES += quicktime
+endif
+ifneq ($(USE_QTCAPTURE),)
+MODULES += qtcapture
+CFLAGS += -DQTCAPTURE_RUNLOOP
+endif
+ifneq ($(USE_EVDEV),)
+MODULES += evdev
+endif
+ifneq ($(USE_FFMPEG),)
+USE_FFMPEG_AVFORMAT := 1
+CFLAGS += -I/usr/include/ffmpeg
+CFLAGS += -Wno-shadow -DUSE_FFMPEG
+MODULES += avcodec
+ifneq ($(USE_FFMPEG_AVFORMAT),)
+MODULES += avformat
+endif
+endif
+ifneq ($(USE_G711),)
+MODULES += g711
+endif
+ifneq ($(USE_G722),)
+MODULES += g722
+endif
+ifneq ($(USE_G722_1),)
+MODULES += g7221
+endif
+ifneq ($(USE_G726),)
+MODULES += g726
+endif
+ifneq ($(USE_GSM),)
+MODULES += gsm
+endif
+ifneq ($(USE_GST),)
+MODULES += gst
+endif
+ifneq ($(USE_ILBC),)
+MODULES += ilbc
+endif
+ifneq ($(USE_ISAC),)
+MODULES += isac
+endif
+ifneq ($(USE_L16),)
+MODULES += l16
+endif
+ifneq ($(USE_OPENGL),)
+MODULES += opengl
+endif
+ifneq ($(USE_OPENGLES),)
+MODULES += opengles
+endif
+ifneq ($(USE_OPUS),)
+MODULES += opus
+endif
+ifneq ($(USE_OSS),)
+MODULES += oss
+endif
+ifneq ($(USE_PLC),)
+MODULES += plc
+endif
+ifneq ($(USE_PORTAUDIO),)
+MODULES += portaudio
+endif
+ifneq ($(USE_SDL),)
+MODULES += sdl
+endif
+ifneq ($(USE_SDL2),)
+MODULES += sdl2
+endif
+ifneq ($(USE_SILK),)
+MODULES += silk
+endif
+ifneq ($(USE_SNDFILE),)
+MODULES += sndfile
+endif
+ifneq ($(USE_SPEEX),)
+MODULES += speex
+endif
+ifneq ($(USE_SPEEX_AEC),)
+MODULES += speex_aec
+endif
+ifneq ($(USE_SPEEX_PP),)
+MODULES += speex_pp
+endif
+ifneq ($(USE_SRTP),)
+MODULES += srtp
+ifneq ($(USE_DTLS_SRTP),)
+MODULES += dtls_srtp
+endif
+endif
+ifneq ($(USE_STDIO),)
+MODULES += stdio
+endif
+ifneq ($(USE_SYSLOG),)
+MODULES += syslog
+endif
+ifneq ($(USE_UUID),)
+MODULES += uuid
+endif
+ifneq ($(USE_V4L),)
+MODULES += v4l
+endif
+ifneq ($(USE_V4L2),)
+MODULES += v4l2
+endif
+ifneq ($(USE_VPX),)
+MODULES += vpx
+endif
+ifneq ($(USE_WINWAVE),)
+MODULES += winwave
+endif
+ifneq ($(USE_X11),)
+MODULES += x11 x11grab
+endif
diff --git a/mk/symbian/baresip.mmp b/mk/symbian/baresip.mmp
new file mode 100644
index 0000000..888ae0c
--- /dev/null
+++ b/mk/symbian/baresip.mmp
@@ -0,0 +1,87 @@
+/**
+ * @file baresip.mmp Symbian makefile for baresip application
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+TARGET baresip.exe
+TARGETTYPE exe
+UID 0x100039CE 0x20011300
+
+#ifdef EKA2
+VENDORID 0
+CAPABILITY NetworkServices
+
+SOURCEPATH .
+START RESOURCE baresip_reg.rss
+#ifdef WINSCW
+TARGETPATH \private\10003a3f\apps
+#else
+TARGETPATH \private\10003a3f\import\apps
+#endif
+END
+#endif
+
+MACRO HAVE_PWD_H
+MACRO HAVE_UNISTD_H
+//MACRO USE_VIDEO
+
+SOURCEPATH ..\..\src
+SOURCE aucodec.c
+SOURCE audio.c
+SOURCE aufilt.c
+SOURCE auplay.c
+SOURCE ausrc.c
+SOURCE bfcp.c
+SOURCE call.c
+SOURCE cmd.c
+SOURCE conf.c
+SOURCE contact.c
+SOURCE main.c
+SOURCE mctrl.c
+SOURCE menc.c
+SOURCE mnat.c
+SOURCE module.c
+SOURCE net.c
+SOURCE play.c
+SOURCE reg.c
+SOURCE rtpkeep.c
+SOURCE sdp.c
+SOURCE sipreq.c
+SOURCE stream.c
+SOURCE ua.c
+SOURCE ui.c
+SOURCE vidcodec.c
+SOURCE vidfilt.c
+
+/* Static modules */
+MACRO STATIC
+
+SOURCEPATH .
+SOURCE static.c
+
+SOURCEPATH ..\..\modules\cons
+SOURCE cons.c
+
+SOURCEPATH ..\..\modules\g711
+SOURCE g711.c
+
+SOURCEPATH ..\..\modules\mda
+SOURCE mda.c player.cpp recorder.cpp util.cpp
+LIBRARY mediaclientaudiostream.lib mediaclientaudioinputstream.lib
+
+#ifdef EKA2
+SOURCEPATH .
+SOURCE ecrt.cpp
+#endif
+
+SYSTEMINCLUDE . ..\..\include
+SYSTEMINCLUDE \epoc32\include \epoc32\include\libc \epoc32\include\re
+SYSTEMINCLUDE \epoc32\include\rem
+LIBRARY estlib.lib euser.lib
+LIBRARY esock.lib insock.lib
+STATICLIBRARY resdp.lib resipsess.lib resip.lib restun.lib redns.lib re.lib
+STATICLIBRARY resipevent.lib rebfcp.lib rem.lib
+#ifndef EKA2
+STATICLIBRARY ecrt0.lib
+STATICLIBRARY libgcc.a
+#endif
diff --git a/mk/symbian/baresip_gcce.pkg b/mk/symbian/baresip_gcce.pkg
new file mode 100644
index 0000000..f4e47e6
--- /dev/null
+++ b/mk/symbian/baresip_gcce.pkg
@@ -0,0 +1,24 @@
+; baresip_gcce.pkg
+;
+;Language - standard language definitions
+&EN
+
+; standard SIS file header
+#{"baresip"},(0x20011300),1,0,0
+
+;Localised Vendor name
+%{"Creytiv.com"}
+
+;Unique Vendor name
+:"Creytiv.com"
+
+;Supports Series 60 v 3.0
+[0x101F7961], 0, 0, 0, {"S60ProductID"}
+
+;Files to install
+"\epoc32\release\gcce\urel\baresip.exe" -"!:\sys\bin\baresip.exe"
+
+"\epoc32\data\z\private\10003a3f\import\apps\baresip_reg.rsc" -"!:\private\10003a3f\import\apps\baresip_reg.rsc"
+
+;required for application to be covered by backup/restore facility
+;"backup_registration.xml" -"c:\private\A00001F4\backup_registration.xml"
diff --git a/mk/symbian/baresip_reg.rss b/mk/symbian/baresip_reg.rss
new file mode 100644
index 0000000..58f91a3
--- /dev/null
+++ b/mk/symbian/baresip_reg.rss
@@ -0,0 +1,16 @@
+/*
+ * baresip_reg.rss
+ */
+
+#include <appinfo.rh>
+
+UID2 KUidAppRegistrationResourceFile
+UID3 0x20011300
+
+RESOURCE APP_REGISTRATION_INFO
+ {
+ app_file="baresip";
+
+ embeddability=KAppNotEmbeddable;
+ newfile=KAppDoesNotSupportNewFile;
+ }
diff --git a/mk/symbian/baresip_thumb.pkg b/mk/symbian/baresip_thumb.pkg
new file mode 100644
index 0000000..a789a49
--- /dev/null
+++ b/mk/symbian/baresip_thumb.pkg
@@ -0,0 +1,14 @@
+; baresip_thumb.pkg
+;
+;Language - standard language definitions
+&EN
+
+; standard SIS file header
+#{"baresip"},(0x20011300),1,0,0
+
+;Supports Series 80 v 2.0
+(0x101F8ED2), 0, 0, 0, {"Series80ProductID"}
+
+;Files to install
+"\epoc32\release\thumb\urel\baresip.exe"
+-"!:\system\apps\baresip\baresip.exe"
diff --git a/mk/symbian/bld.inf b/mk/symbian/bld.inf
new file mode 100644
index 0000000..e429de1
--- /dev/null
+++ b/mk/symbian/bld.inf
@@ -0,0 +1,12 @@
+/**
+ * @file bld.inf Symbian build information
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+PRJ_MMPFILES
+
+/* Application */
+baresip.mmp
+
+/* Modules */
diff --git a/mk/symbian/ecrt.cpp b/mk/symbian/ecrt.cpp
new file mode 100644
index 0000000..7d5e5d6
--- /dev/null
+++ b/mk/symbian/ecrt.cpp
@@ -0,0 +1,31 @@
+/**
+ * @file ecrt.cpp ECRT wrapper for Symbian OS
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <e32base.h>
+#include <e32cons.h>
+#include <sys/reent.h>
+
+_LIT(KAppName, "baresip");
+extern "C" int main(int argc, char** argv);
+
+
+TInt E32Main()
+{
+ __UHEAP_MARK;
+ CTrapCleanup* cleanup = CTrapCleanup::New();
+ int ret = 0;
+ TRAPD(err, ret = main(0, NULL));
+ if (err)
+ printf("main left with error %d\n", err);
+ if (ret)
+ printf("main returned %d\n", ret);
+ __ASSERT_ALWAYS(!err, User::Panic(KAppName, err));
+ CloseSTDLIB();
+ delete cleanup;
+ __UHEAP_MARKEND;
+ return err;
+}
diff --git a/mk/symbian/mod.cpp b/mk/symbian/mod.cpp
new file mode 100644
index 0000000..0ccbe2d
--- /dev/null
+++ b/mk/symbian/mod.cpp
@@ -0,0 +1,19 @@
+/**
+ * @file mod.cpp Module wrapper for Symbian OS
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <e32def.h>
+#include <e32std.h>
+#include <re_types.h>
+#include <re_mod.h>
+
+extern "C" {
+ extern const struct mod_export exports;
+}
+
+
+EXPORT_C void *NewModule()
+{
+ return (void *)&exports;
+}
diff --git a/mk/symbian/static.c b/mk/symbian/static.c
new file mode 100644
index 0000000..8ada5a1
--- /dev/null
+++ b/mk/symbian/static.c
@@ -0,0 +1,14 @@
+/* static.c - manually updated */
+#include <re_types.h>
+#include <re_mod.h>
+
+extern const struct mod_export exports_cons;
+extern const struct mod_export exports_g711;
+extern const struct mod_export exports_mda;
+
+const struct mod_export *mod_table[] = {
+ &exports_cons,
+ &exports_g711,
+ &exports_mda,
+ NULL
+};
diff --git a/mk/win32/baresip.vcproj b/mk/win32/baresip.vcproj
new file mode 100644
index 0000000..70095bc
--- /dev/null
+++ b/mk/win32/baresip.vcproj
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="7.10"
+ Name="baresip-win32"
+ ProjectGUID="{4B89C2D8-FB32-4D7C-9019-752A5664781C}"
+ Keyword="Win32Proj">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="..\..\Win32\Debug"
+ IntermediateDirectory="c:\tmp\baresip-win32\Debug"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\ffmpeg-win32-dev\include;..\..\..\"
+ PreprocessorDefinitions="WIN32,STATIC,HAVE_IO_H,HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE"
+ MinimalRebuild="TRUE"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="FALSE"
+ DebugInformationFormat="4"
+ CompileAs="1"
+ DisableSpecificWarnings="4142"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="winmm.lib wsock32.lib ..\..\..\re\Win32\Debug\re.lib ..\..\..\rem\Win32\Debug\rem.lib"
+ OutputFile="$(OutDir)/baresip-win32.exe"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile="$(OutDir)/baresip-win32.pdb"
+ SubSystem="1"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="..\..\Win32\Release"
+ IntermediateDirectory="c:\tmp\baresip-win32\release"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories="..\..\include;&quot;..\..\..\re\include&quot;..\..\..\rem\include"
+ PreprocessorDefinitions="WIN32,STATIC,HAVE_IO_H,HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="2"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="FALSE"
+ DebugInformationFormat="3"
+ DisableSpecificWarnings="4142"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="winmm.lib wsock32.lib ..\..\..\re\Win32\Release\re.lib ..\..\..\rem\Win32\Release\rem.lib"
+ OutputFile="$(OutDir)/baresip-win32.exe"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories=""
+ GenerateDebugInformation="TRUE"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
+ <File
+ RelativePath="..\..\src\aucodec.c">
+ </File>
+ <File
+ RelativePath="..\..\src\audio.c">
+ </File>
+ <File
+ RelativePath="..\..\src\aufilt.c">
+ </File>
+ <File
+ RelativePath="..\..\src\auplay.c">
+ </File>
+ <File
+ RelativePath="..\..\src\ausrc.c">
+ </File>
+ <File
+ RelativePath="..\..\src\bfcp.c">
+ </File>
+ <File
+ RelativePath="..\..\src\call.c">
+ </File>
+ <File
+ RelativePath="..\..\src\cmd.c">
+ </File>
+ <File
+ RelativePath="..\..\src\conf.c">
+ </File>
+ <File
+ RelativePath="..\..\src\contact.c">
+ </File>
+ <File
+ RelativePath="..\..\src\main.c">
+ </File>
+ <File
+ RelativePath="..\..\src\mctrl.c">
+ </File>
+ <File
+ RelativePath="..\..\src\menc.c">
+ </File>
+ <File
+ RelativePath="..\..\src\message.c">
+ </File>
+ <File
+ RelativePath="..\..\src\mnat.c">
+ </File>
+ <File
+ RelativePath="..\..\src\module.c">
+ </File>
+ <File
+ RelativePath="..\..\src\net.c">
+ </File>
+ <File
+ RelativePath="..\..\src\play.c">
+ </File>
+ <File
+ RelativePath="..\..\src\reg.c">
+ </File>
+ <File
+ RelativePath="..\..\src\rtpkeep.c">
+ </File>
+ <File
+ RelativePath=".\static.c">
+ </File>
+ <File
+ RelativePath="..\..\src\sdp.c">
+ </File>
+ <File
+ RelativePath="..\..\src\stream.c">
+ </File>
+ <File
+ RelativePath="..\..\src\sipreq.c">
+ </File>
+ <File
+ RelativePath="..\..\src\ua.c">
+ </File>
+ <File
+ RelativePath="..\..\src\ui.c">
+ </File>
+ <File
+ RelativePath="..\..\src\vidcodec.c">
+ </File>
+ <File
+ RelativePath="..\..\src\vidfilt.c">
+ </File>
+ <File
+ RelativePath="..\..\src\video.c">
+ </File>
+ <File
+ RelativePath="..\..\src\vidisp.c">
+ </File>
+ <File
+ RelativePath="..\..\src\vidsrc.c">
+ </File>
+ <Filter
+ Name="modules"
+ Filter="">
+ <Filter
+ Name="g711"
+ Filter="">
+ <File
+ RelativePath="..\..\modules\g711\g711.c">
+ </File>
+ </Filter>
+ <Filter
+ Name="cons"
+ Filter="">
+ <File
+ RelativePath="..\..\modules\cons\cons.c">
+ </File>
+ </Filter>
+ <Filter
+ Name="winwave"
+ Filter="">
+ <File
+ RelativePath="..\..\modules\winwave\winwave.c">
+ </File>
+ </Filter>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}">
+ <File
+ RelativePath=".\stdafx.h">
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/mk/win32/static.c b/mk/win32/static.c
new file mode 100644
index 0000000..9fd143e
--- /dev/null
+++ b/mk/win32/static.c
@@ -0,0 +1,14 @@
+/* static.c - manually updated */
+#include <re_types.h>
+#include <re_mod.h>
+
+extern const struct mod_export exports_cons;
+extern const struct mod_export exports_g711;
+extern const struct mod_export exports_winwave;
+
+const struct mod_export *mod_table[] = {
+ &exports_cons,
+ &exports_g711,
+ &exports_winwave,
+ NULL
+};
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);
+
+ /* Update the screen! */
+ SDL_RenderPresent(st->renderer);
+
+ return 0;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st || !st->window)
+ return;
+
+ SDL_HideWindow(st->window);
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ if (SDL_VideoInit(NULL) < 0) {
+ warning("sdl2: unable to init Video: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+
+ err = vidisp_register(&vid, "sdl2", alloc, NULL, display, hide);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ SDL_VideoQuit();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl2) = {
+ "sdl2",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/selfview/module.mk b/modules/selfview/module.mk
new file mode 100644
index 0000000..2719433
--- /dev/null
+++ b/modules/selfview/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := selfview
+$(MOD)_SRCS += selfview.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/selfview/selfview.c b/modules/selfview/selfview.c
new file mode 100644
index 0000000..cf5cdc3
--- /dev/null
+++ b/modules/selfview/selfview.c
@@ -0,0 +1,267 @@
+/**
+ * @file selfview.c Selfview Video-Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/* shared state */
+struct selfview {
+ struct lock *lock; /**< Protect frame */
+ struct vidframe *frame; /**< Copy of encoded frame */
+};
+
+struct selfview_enc {
+ struct vidfilt_enc_st vf; /**< Inheritance */
+ struct selfview *selfview; /**< Ref. to shared state */
+ struct vidisp_st *disp; /**< Selfview display */
+};
+
+struct selfview_dec {
+ struct vidfilt_dec_st vf; /**< Inheritance */
+ struct selfview *selfview; /**< Ref. to shared state */
+};
+
+
+static struct vidsz selfview_size = {0, 0};
+
+
+static void destructor(void *arg)
+{
+ struct selfview *st = arg;
+
+ lock_write_get(st->lock);
+ mem_deref(st->frame);
+ lock_rel(st->lock);
+ mem_deref(st->lock);
+}
+
+
+static void encode_destructor(void *arg)
+{
+ struct selfview_enc *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->selfview);
+ mem_deref(st->disp);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct selfview_dec *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->selfview);
+}
+
+
+static int selfview_alloc(struct selfview **selfviewp, void **ctx)
+{
+ struct selfview *selfview;
+ int err;
+
+ if (!selfviewp || !ctx)
+ return EINVAL;
+
+ if (*ctx) {
+ *selfviewp = mem_ref(*ctx);
+ }
+ else {
+ selfview = mem_zalloc(sizeof(*selfview), destructor);
+ if (!selfview)
+ return ENOMEM;
+
+ err = lock_alloc(&selfview->lock);
+ if (err)
+ return err;
+
+ *ctx = selfview;
+ *selfviewp = selfview;
+ }
+
+ return 0;
+}
+
+
+static int encode_update(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct selfview_enc *st;
+ int err;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = selfview_alloc(&st->selfview, ctx);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct vidfilt_dec_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct selfview_dec *st;
+ int err;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = selfview_alloc(&st->selfview, ctx);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode_win(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ struct selfview_enc *enc = (struct selfview_enc *)st;
+ int err;
+
+ if (!frame)
+ return 0;
+
+ if (!enc->disp) {
+
+ err = vidisp_alloc(&enc->disp, NULL, NULL, NULL, NULL, NULL);
+ if (err)
+ return err;
+ }
+
+ return vidisp_display(enc->disp, "Selfview", frame);
+}
+
+
+static int encode_pip(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ struct selfview_enc *enc = (struct selfview_enc *)st;
+ struct selfview *selfview = enc->selfview;
+ int err = 0;
+
+ if (!frame)
+ return 0;
+
+ lock_write_get(selfview->lock);
+ if (!selfview->frame) {
+ struct vidsz sz;
+
+ /* Use size if configured, or else 20% of main window */
+ if (selfview_size.w && selfview_size.h) {
+ sz = selfview_size;
+ }
+ else {
+ sz.w = frame->size.w / 5;
+ sz.h = frame->size.h / 5;
+ }
+
+ err = vidframe_alloc(&selfview->frame, VID_FMT_YUV420P, &sz);
+ }
+ if (!err)
+ vidconv(selfview->frame, frame, NULL);
+ lock_rel(selfview->lock);
+
+ return err;
+}
+
+
+static int decode_pip(struct vidfilt_dec_st *st, struct vidframe *frame)
+{
+ struct selfview_dec *dec = (struct selfview_dec *)st;
+ struct selfview *sv = dec->selfview;
+
+ if (!frame)
+ return 0;
+
+ lock_read_get(sv->lock);
+ if (sv->frame) {
+ struct vidrect rect;
+
+ rect.w = min(sv->frame->size.w, frame->size.w/2);
+ rect.h = min(sv->frame->size.h, frame->size.h/2);
+ if (rect.w <= (frame->size.w - 10))
+ rect.x = frame->size.w - rect.w - 10;
+ else
+ rect.x = frame->size.w/2;
+ if (rect.h <= (frame->size.h - 10))
+ rect.y = frame->size.h - rect.h - 10;
+ else
+ rect.y = frame->size.h/2;
+
+ vidconv(frame, sv->frame, &rect);
+
+ vidframe_draw_rect(frame, rect.x, rect.y, rect.w, rect.h,
+ 127, 127, 127);
+ }
+ lock_rel(sv->lock);
+
+ return 0;
+}
+
+
+static struct vidfilt selfview_win = {
+ LE_INIT, "selfview_window", encode_update, encode_win, NULL, NULL
+};
+static struct vidfilt selfview_pip = {
+ LE_INIT, "selfview_pip",
+ encode_update, encode_pip, decode_update, decode_pip
+};
+
+
+static int module_init(void)
+{
+ struct pl pl;
+
+ if (conf_get(conf_cur(), "video_selfview", &pl))
+ return 0;
+
+ if (0 == pl_strcasecmp(&pl, "window"))
+ vidfilt_register(&selfview_win);
+ else if (0 == pl_strcasecmp(&pl, "pip"))
+ vidfilt_register(&selfview_pip);
+
+ (void)conf_get_vidsz(conf_cur(), "selfview_size", &selfview_size);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&selfview_win);
+ vidfilt_unregister(&selfview_pip);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(selfview) = {
+ "selfview",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/silk/module.mk b/modules/silk/module.mk
new file mode 100644
index 0000000..ac8ebb1
--- /dev/null
+++ b/modules/silk/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := silk
+$(MOD)_SRCS += silk.c
+$(MOD)_LFLAGS += -lSKP_SILK_SDK
+
+include mk/mod.mk
diff --git a/modules/silk/silk.c b/modules/silk/silk.c
new file mode 100644
index 0000000..95f708c
--- /dev/null
+++ b/modules/silk/silk.c
@@ -0,0 +1,259 @@
+/**
+ * @file silk.c Skype SILK audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <silk/SKP_Silk_SDK_API.h>
+
+
+/*
+ * References: https://developer.skype.com/silk
+ */
+
+
+enum {
+ MAX_BYTES_PER_FRAME = 250,
+ MAX_FRAME_SIZE = 2*480,
+};
+
+
+struct auenc_state {
+ void *enc;
+ SKP_SILK_SDK_EncControlStruct encControl;
+};
+
+struct audec_state {
+ void *dec;
+ SKP_SILK_SDK_DecControlStruct decControl;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ mem_deref(st->enc);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ mem_deref(st->dec);
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int ret, err = 0;
+ int32_t enc_size;
+ (void)fmtp;
+
+ if (!aesp || !ac || !prm)
+ return EINVAL;
+ if (*aesp)
+ return 0;
+
+ ret = SKP_Silk_SDK_Get_Encoder_Size(&enc_size);
+ if (ret || enc_size <= 0)
+ return EINVAL;
+
+ st = mem_alloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->enc = mem_alloc(enc_size, NULL);
+ if (!st->enc) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ret = SKP_Silk_SDK_InitEncoder(st->enc, &st->encControl);
+ if (ret) {
+ err = EPROTO;
+ goto out;
+ }
+
+ st->encControl.API_sampleRate = ac->srate;
+ st->encControl.maxInternalSampleRate = ac->srate;
+ st->encControl.packetSize = prm->ptime * ac->srate / 1000;
+ st->encControl.bitRate = 64000;
+ st->encControl.complexity = 2;
+ st->encControl.useInBandFEC = 0;
+ st->encControl.useDTX = 0;
+
+ info("silk: encoder: %dHz, psize=%d, bitrate=%d, complex=%d,"
+ " fec=%d, dtx=%d\n",
+ st->encControl.API_sampleRate,
+ st->encControl.packetSize,
+ st->encControl.bitRate,
+ st->encControl.complexity,
+ st->encControl.useInBandFEC,
+ st->encControl.useDTX);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int ret, err = 0;
+ int32_t dec_size;
+ (void)fmtp;
+
+ if (*adsp)
+ return 0;
+
+ ret = SKP_Silk_SDK_Get_Decoder_Size(&dec_size);
+ if (ret || dec_size <= 0)
+ return EINVAL;
+
+ st = mem_alloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->dec = mem_alloc(dec_size, NULL);
+ if (!st->dec) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ret = SKP_Silk_SDK_InitDecoder(st->dec);
+ if (ret) {
+ err = EPROTO;
+ goto out;
+ }
+
+ st->decControl.API_sampleRate = ac->srate;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int ret;
+ int16_t nBytesOut;
+
+ if (*len < MAX_BYTES_PER_FRAME)
+ return ENOMEM;
+
+ nBytesOut = *len;
+ ret = SKP_Silk_SDK_Encode(st->enc,
+ &st->encControl,
+ sampv,
+ (int)sampc,
+ buf,
+ &nBytesOut);
+ if (ret) {
+ warning("silk: SKP_Silk_SDK_Encode: ret=%d\n", ret);
+ }
+
+ *len = nBytesOut;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ int16_t nsamp = *sampc;
+ int ret;
+
+ ret = SKP_Silk_SDK_Decode(st->dec,
+ &st->decControl,
+ 0,
+ buf,
+ (int)len,
+ sampv,
+ &nsamp);
+ if (ret) {
+ warning("silk: SKP_Silk_SDK_Decode: ret=%d\n", ret);
+ }
+
+ *sampc = nsamp;
+
+ return 0;
+}
+
+
+static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ int16_t nsamp = *sampc;
+ int ret;
+
+ ret = SKP_Silk_SDK_Decode(st->dec,
+ &st->decControl,
+ 1,
+ NULL,
+ 0,
+ sampv,
+ &nsamp);
+ if (ret)
+ return EPROTO;
+
+ *sampc = nsamp;
+
+ return 0;
+}
+
+
+static struct aucodec silk[] = {
+ {
+ LE_INIT, 0, "SILK", 24000, 1, NULL,
+ encode_update, encode, decode_update, decode, plc, 0, 0
+ },
+
+};
+
+
+static int module_init(void)
+{
+ debug("silk: SILK %s\n", SKP_Silk_SDK_get_version());
+
+ aucodec_register(&silk[0]);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ int i = ARRAY_SIZE(silk);
+
+ while (i--)
+ aucodec_unregister(&silk[i]);
+
+ return 0;
+}
+
+
+/** Module exports */
+EXPORT_SYM const struct mod_export DECL_EXPORTS(silk) = {
+ "silk",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/snapshot/module.mk b/modules/snapshot/module.mk
new file mode 100644
index 0000000..1be53bd
--- /dev/null
+++ b/modules/snapshot/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := snapshot
+$(MOD)_SRCS += snapshot.c png_vf.c
+$(MOD)_LFLAGS += -lpng
+
+include mk/mod.mk
diff --git a/modules/snapshot/png_vf.c b/modules/snapshot/png_vf.c
new file mode 100644
index 0000000..d7f1f8a
--- /dev/null
+++ b/modules/snapshot/png_vf.c
@@ -0,0 +1,188 @@
+/**
+ * @file png_vf.c Write vidframe to a PNG-file
+ *
+ * Author: Doug Blewett
+ * Review: Alfred E. Heggestad
+ */
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <time.h>
+#include <png.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "png_vf.h"
+
+
+static char *png_filename(const struct tm *tmx, const char *name,
+ char *buf, unsigned int length);
+static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers,
+ int png_height);
+
+
+int png_save_vidframe(const struct vidframe *vf, const char *path)
+{
+ png_byte **png_row_pointers = NULL;
+ png_byte *row;
+ const png_byte *p;
+ png_byte red, green, blue;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ FILE *fp = NULL;
+ size_t x, y;
+ unsigned int width = vf->size.w & ~1;
+ unsigned int height = vf->size.h & ~1;
+ unsigned int bytes_per_pixel = 3; /* RGB format */
+ time_t tnow;
+ struct tm *tmx;
+ char filename_buf[64];
+ struct vidframe *f2 = NULL;
+ int err = 0;
+
+ tnow = time(NULL);
+ tmx = localtime(&tnow);
+
+ if (vf->fmt != VID_FMT_RGB32) {
+
+ err = vidframe_alloc(&f2, VID_FMT_RGB32, &vf->size);
+ if (err)
+ goto out;
+
+ vidconv(f2, vf, NULL);
+ vf = f2;
+ }
+
+ /* Initialize the write struct. */
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL);
+ if (png_ptr == NULL) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Initialize the info struct. */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Set up error handling. */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Set image attributes. */
+ png_set_IHDR(png_ptr,
+ info_ptr,
+ width,
+ height,
+ 8,
+ PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ /* Initialize rows of PNG
+ * bytes_per_row = width * bytes_per_pixel;
+ */
+ png_row_pointers = png_malloc(png_ptr,
+ height * sizeof(png_byte *));
+
+ for (y = 0; y < height; ++y) {
+ png_row_pointers[y] =
+ (png_byte *) png_malloc(png_ptr,
+ width * sizeof(uint8_t) *
+ bytes_per_pixel);
+ }
+
+ p = vf->data[0];
+ for (y = 0; y < height; ++y) {
+
+ row = png_row_pointers[y];
+
+ for (x = 0; x < width; ++x) {
+
+ red = *p++;
+ green = *p++;
+ blue = *p++;
+
+ *row++ = blue;
+ *row++ = green;
+ *row++ = red;
+
+ ++p; /* skip alpha */
+ }
+ }
+
+ /* Write the image data. */
+ fp = fopen(png_filename(tmx, path,
+ filename_buf, sizeof(filename_buf)), "wb");
+ if (fp == NULL) {
+ err = errno;
+ goto out;
+ }
+
+ png_init_io(png_ptr, fp);
+ png_set_rows(png_ptr, info_ptr, png_row_pointers);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+
+ info("png: wrote %s\n", filename_buf);
+
+ out:
+ /* Finish writing. */
+ mem_deref(f2);
+ png_save_free(png_ptr, png_row_pointers, height);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (fp)
+ fclose(fp);
+
+ return 0;
+}
+
+
+static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers,
+ int png_height)
+{
+ int y;
+
+ /* Cleanup. */
+ if (png_height == 0 || png_row_pointers == NULL)
+ return;
+
+ for (y = 0; y < png_height; y++) {
+ png_free(png_ptr, png_row_pointers[y]);
+ }
+ png_free(png_ptr, png_row_pointers);
+}
+
+
+static char *png_filename(const struct tm *tmx, const char *name,
+ char *buf, unsigned int length)
+{
+ /*
+ * -2013-03-03-15-22-56.png - 24 chars
+ */
+ if (strlen(name) + 24 >= length) {
+ buf[0] = '\0';
+ return buf;
+ }
+
+ sprintf(buf, (tmx->tm_mon < 9 ? "%s-%d-0%d" : "%s-%d-%d"), name,
+ 1900 + tmx->tm_year, tmx->tm_mon + 1);
+
+ sprintf(buf + strlen(buf), (tmx->tm_mday < 10 ? "-0%d" : "-%d"),
+ tmx->tm_mday);
+
+ sprintf(buf + strlen(buf), (tmx->tm_hour < 10 ? "-0%d" : "-%d"),
+ tmx->tm_hour);
+
+ sprintf(buf + strlen(buf), (tmx->tm_min < 10 ? "-0%d" : "-%d"),
+ tmx->tm_min);
+
+ sprintf(buf + strlen(buf), (tmx->tm_sec < 10 ? "-0%d.png" : "-%d.png"),
+ tmx->tm_sec);
+
+ return buf;
+}
diff --git a/modules/snapshot/png_vf.h b/modules/snapshot/png_vf.h
new file mode 100644
index 0000000..17660cf
--- /dev/null
+++ b/modules/snapshot/png_vf.h
@@ -0,0 +1,6 @@
+/**
+ * @file png_vf.h
+ */
+
+
+int png_save_vidframe(const struct vidframe *vf, const char *path);
diff --git a/modules/snapshot/snapshot.c b/modules/snapshot/snapshot.c
new file mode 100644
index 0000000..b8a01de
--- /dev/null
+++ b/modules/snapshot/snapshot.c
@@ -0,0 +1,90 @@
+/**
+ * @file snapshot.c Snapshot Video-Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "png_vf.h"
+
+
+static bool flag_enc, flag_dec;
+
+
+static int encode(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ (void)st;
+
+ if (!frame)
+ return 0;
+
+ if (flag_enc) {
+ flag_enc = false;
+ png_save_vidframe(frame, "snapshot-send");
+ }
+
+ return 0;
+}
+
+
+static int decode(struct vidfilt_dec_st *st, struct vidframe *frame)
+{
+ (void)st;
+
+ if (!frame)
+ return 0;
+
+ if (flag_dec) {
+ flag_dec = false;
+ png_save_vidframe(frame, "snapshot-recv");
+ }
+
+ return 0;
+}
+
+
+static int do_snapshot(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ /* NOTE: not re-entrant */
+ flag_enc = flag_dec = true;
+
+ return 0;
+}
+
+
+static struct vidfilt snapshot = {
+ LE_INIT, "snapshot", NULL, encode, NULL, decode,
+};
+
+
+static const struct cmd cmdv[] = {
+ {'o', 0, "Take video snapshot", do_snapshot },
+};
+
+
+static int module_init(void)
+{
+ vidfilt_register(&snapshot);
+ return cmd_register(cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&snapshot);
+ cmd_unregister(cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(snapshot) = {
+ "snapshot",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/sndfile/module.mk b/modules/sndfile/module.mk
new file mode 100644
index 0000000..7fed4de
--- /dev/null
+++ b/modules/sndfile/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := sndfile
+$(MOD)_SRCS += sndfile.c
+$(MOD)_LFLAGS += -lsndfile
+
+include mk/mod.mk
diff --git a/modules/sndfile/sndfile.c b/modules/sndfile/sndfile.c
new file mode 100644
index 0000000..e8fe9c9
--- /dev/null
+++ b/modules/sndfile/sndfile.c
@@ -0,0 +1,180 @@
+/**
+ * @file sndfile.c Audio dumper using libsndfile
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <sndfile.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+
+
+struct sndfile_enc {
+ struct aufilt_enc_st af; /* base class */
+ SNDFILE *enc;
+};
+
+struct sndfile_dec {
+ struct aufilt_dec_st af; /* base class */
+ SNDFILE *dec;
+};
+
+
+static int timestamp_print(struct re_printf *pf, const struct tm *tm)
+{
+ if (!tm)
+ return 0;
+
+ return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d",
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+
+static void enc_destructor(void *arg)
+{
+ struct sndfile_enc *st = arg;
+
+ if (st->enc)
+ sf_close(st->enc);
+
+ list_unlink(&st->af.le);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct sndfile_dec *st = arg;
+
+ if (st->dec)
+ sf_close(st->dec);
+
+ list_unlink(&st->af.le);
+}
+
+
+static SNDFILE *openfile(const struct aufilt_prm *prm, bool enc)
+{
+ char filename[128];
+ SF_INFO sfinfo;
+ time_t tnow = time(0);
+ struct tm *tm = localtime(&tnow);
+ SNDFILE *sf;
+
+ (void)re_snprintf(filename, sizeof(filename),
+ "dump-%H-%s.wav",
+ timestamp_print, tm, enc ? "enc" : "dec");
+
+ sfinfo.samplerate = prm->srate;
+ sfinfo.channels = prm->ch;
+ sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+
+ sf = sf_open(filename, SFM_WRITE, &sfinfo);
+ if (!sf) {
+ warning("sndfile: could not open: %s\n", filename);
+ puts(sf_strerror(NULL));
+ return NULL;
+ }
+
+ info("sndfile: dumping %s audio to %s\n",
+ enc ? "encode" : "decode", filename);
+
+ return sf;
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct sndfile_enc *st;
+ int err = 0;
+ (void)ctx;
+ (void)af;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return EINVAL;
+
+ st->enc = openfile(prm, true);
+ if (!st->enc)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct sndfile_dec *st;
+ int err = 0;
+ (void)ctx;
+ (void)af;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return EINVAL;
+
+ st->dec = openfile(prm, false);
+ if (!st->dec)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct sndfile_enc *sf = (struct sndfile_enc *)st;
+
+ sf_write_short(sf->enc, sampv, *sampc);
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct sndfile_dec *sf = (struct sndfile_dec *)st;
+
+ sf_write_short(sf->dec, sampv, *sampc);
+
+ return 0;
+}
+
+
+static struct aufilt sndfile = {
+ LE_INIT, "sndfile", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ aufilt_register(&sndfile);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&sndfile);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sndfile) = {
+ "sndfile",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/speex/module.mk b/modules/speex/module.mk
new file mode 100644
index 0000000..c8d0015
--- /dev/null
+++ b/modules/speex/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex
+$(MOD)_SRCS += speex.c
+$(MOD)_LFLAGS += -lspeex
+CFLAGS += -Wno-strict-prototypes
+
+include mk/mod.mk
diff --git a/modules/speex/speex.c b/modules/speex/speex.c
new file mode 100644
index 0000000..32db0c9
--- /dev/null
+++ b/modules/speex/speex.c
@@ -0,0 +1,498 @@
+/**
+ * @file speex.c Speex audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <speex/speex.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.h>
+#include <re.h>
+#include <baresip.h>
+
+
+enum {
+ MIN_FRAME_SIZE = 43,
+ SPEEX_PTIME = 20,
+};
+
+
+struct auenc_state {
+ void *enc;
+ SpeexBits bits;
+
+ uint32_t frame_size; /* Number of sample-frames */
+ uint8_t channels;
+};
+
+
+struct audec_state {
+ void *dec;
+ SpeexBits bits;
+ SpeexStereoState stereo;
+ SpeexCallback callback;
+
+ uint32_t frame_size; /* Number of sample-frames */
+ uint8_t channels;
+};
+
+
+static char speex_fmtp[128];
+
+
+/** Speex configuration */
+static struct {
+ int quality;
+ int complexity;
+ int enhancement;
+ int vbr;
+ int vad;
+} sconf = {
+ 3, /* 0-10 */
+ 2, /* 0-10 */
+ 0, /* 0 or 1 */
+ 0, /* 0 or 1 */
+ 0 /* 0 or 1 */
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ speex_bits_destroy(&st->bits);
+ speex_encoder_destroy(st->enc);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ speex_bits_destroy(&st->bits);
+ speex_decoder_destroy(st->dec);
+}
+
+
+static void encoder_config(void *st)
+{
+ int ret;
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_QUALITY, &sconf.quality);
+ if (ret) {
+ warning("speex: SPEEX_SET_QUALITY: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &sconf.complexity);
+ if (ret) {
+ warning("speex: SPEEX_SET_COMPLEXITY: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_VBR, &sconf.vbr);
+ if (ret) {
+ warning("speex: SPEEX_SET_VBR: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_VAD, &sconf.vad);
+ if (ret) {
+ warning("speex: SPEEX_SET_VAD: %d\n", ret);
+ }
+}
+
+
+static void decoder_config(void *st)
+{
+ int ret;
+
+ ret = speex_decoder_ctl(st, SPEEX_SET_ENH, &sconf.enhancement);
+ if (ret) {
+ warning("speex: SPEEX_SET_ENH: %d\n", ret);
+ }
+}
+
+
+static int decode_param(struct auenc_state *st, const struct pl *name,
+ const struct pl *val)
+{
+ int ret;
+
+ /* mode: List supported Speex decoding modes. The valid modes are
+ different for narrowband and wideband, and are defined as follows:
+
+ {1,2,3,4,5,6,7,8,any} for narrowband
+ {0,1,2,3,4,5,6,7,8,9,10,any} for wideband and ultra-wideband
+ */
+ if (0 == pl_strcasecmp(name, "mode")) {
+ struct pl v;
+ int mode;
+
+ /* parameter is quoted */
+ if (re_regex(val->p, val->l, "\"[^\"]+\"", &v))
+ v = *val;
+
+ if (0 == pl_strcasecmp(&v, "any"))
+ return 0;
+
+ mode = pl_u32(&v);
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_MODE, &mode);
+ if (ret) {
+ warning("speex: SPEEX_SET_MODE: ret=%d\n", ret);
+ }
+ }
+ /* vbr: variable bit rate - either 'on' 'off' or 'vad' */
+ else if (0 == pl_strcasecmp(name, "vbr")) {
+ int vbr = 0, vad = 0;
+
+ if (0 == pl_strcasecmp(val, "on"))
+ vbr = 1;
+ else if (0 == pl_strcasecmp(val, "off"))
+ vbr = 0;
+ else if (0 == pl_strcasecmp(val, "vad"))
+ vad = 1;
+ else {
+ warning("speex: invalid vbr value %r\n", val);
+ }
+
+ debug("speex: setting VBR=%d VAD=%d\n", vbr, vad);
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_VBR, &vbr);
+ if (ret) {
+ warning("speex: SPEEX_SET_VBR: ret=%d\n", ret);
+ }
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_VAD, &vad);
+ if (ret) {
+ warning("speex: SPEEX_SET_VAD: ret=%d\n", ret);
+ }
+ }
+ else if (0 == pl_strcasecmp(name, "cng")) {
+ int dtx = 0;
+
+ if (0 == pl_strcasecmp(val, "on"))
+ dtx = 0;
+ else if (0 == pl_strcasecmp(val, "off"))
+ dtx = 1;
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_DTX, &dtx);
+ if (ret) {
+ warning("speex: SPEEX_SET_DTX: ret=%d\n", ret);
+ }
+ }
+ else {
+ debug("speex: unknown Speex param: %r=%r\n", name, val);
+ }
+
+ return 0;
+}
+
+
+static void param_handler(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct auenc_state *st = arg;
+
+ decode_param(st, name, val);
+}
+
+
+static const SpeexMode *resolve_mode(uint32_t srate)
+{
+ switch (srate) {
+
+ default:
+ case 8000: return &speex_nb_mode;
+ case 16000: return &speex_wb_mode;
+ case 32000: return &speex_uwb_mode;
+ }
+}
+
+
+static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int ret, err = 0;
+
+ if (!aesp || !ac || !prm)
+ return EINVAL;
+ if (prm->ptime != SPEEX_PTIME)
+ return EPROTO;
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->frame_size = ac->srate * SPEEX_PTIME / 1000;
+ st->channels = ac->ch;
+
+ /* Encoder */
+ st->enc = speex_encoder_init(resolve_mode(ac->srate));
+ if (!st->enc) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ speex_bits_init(&st->bits);
+
+ encoder_config(st->enc);
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_GET_FRAME_SIZE,
+ &st->frame_size);
+ if (ret) {
+ warning("speex: SPEEX_GET_FRAME_SIZE: %d\n", ret);
+ }
+
+ if (str_isset(fmtp)) {
+ struct pl params;
+
+ pl_set_str(&params, fmtp);
+
+ fmt_param_apply(&params, param_handler, st);
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->frame_size = ac->srate * SPEEX_PTIME / 1000;
+ st->channels = ac->ch;
+
+ /* Decoder */
+ st->dec = speex_decoder_init(resolve_mode(ac->srate));
+ if (!st->dec) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ speex_bits_init(&st->bits);
+
+ if (2 == st->channels) {
+
+ /* Stereo. */
+ st->stereo.balance = 1;
+ st->stereo.e_ratio = .5f;
+ st->stereo.smooth_left = 1;
+ st->stereo.smooth_right = 1;
+
+ st->callback.callback_id = SPEEX_INBAND_STEREO;
+ st->callback.func = speex_std_stereo_request_handler;
+ st->callback.data = &st->stereo;
+ speex_decoder_ctl(st->dec, SPEEX_SET_HANDLER,
+ &st->callback);
+ }
+
+ decoder_config(st->dec);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ const size_t n = st->channels * st->frame_size;
+ int ret, r;
+
+ if (*len < 128)
+ return ENOMEM;
+
+ /* VAD */
+ if (!sampv || !sampc) {
+ /* 5 zeros interpreted by Speex as silence (submode 0) */
+ speex_bits_pack(&st->bits, 0, 5);
+ goto out;
+ }
+
+ /* Handle multiple Speex frames in one RTP packet */
+ while (sampc > 0) {
+
+ /* Assume stereo */
+ if (2 == st->channels) {
+ speex_encode_stereo_int((int16_t *)sampv,
+ st->frame_size, &st->bits);
+ }
+
+ ret = speex_encode_int(st->enc, (int16_t *)sampv, &st->bits);
+ if (1 != ret) {
+ warning("speex: speex_encode_int: ret=%d\n", ret);
+ }
+
+ sampc -= n;
+ sampv += n;
+ }
+
+ out:
+ /* Terminate bit stream */
+ speex_bits_pack(&st->bits, 15, 5);
+
+ r = speex_bits_write(&st->bits, (char *)buf, (int)*len);
+ *len = r;
+
+ speex_bits_reset(&st->bits);
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ const size_t n = st->channels * st->frame_size;
+ size_t i = 0;
+
+ /* Read into bit-stream */
+ speex_bits_read_from(&st->bits, (char *)buf, (int)len);
+
+ /* Handle multiple Speex frames in one RTP packet */
+ while (speex_bits_remaining(&st->bits) >= MIN_FRAME_SIZE) {
+ int ret;
+
+ if (*sampc < n)
+ return ENOMEM;
+
+ ret = speex_decode_int(st->dec, &st->bits,
+ (int16_t *)&sampv[i]);
+ if (ret < 0) {
+ if (-1 == ret) {
+ }
+ else if (-2 == ret) {
+ warning("speex: decode: corrupt stream\n");
+ }
+ else {
+ warning("speex: decode: speex_decode_int:"
+ " ret=%d\n", ret);
+ }
+ break;
+ }
+
+ /* Transforms a mono frame into a stereo frame
+ using intensity stereo info */
+ if (2 == st->channels) {
+ speex_decode_stereo_int((int16_t *)&sampv[i],
+ st->frame_size,
+ &st->stereo);
+ }
+
+ i += n;
+ *sampc -= n;
+ }
+
+ *sampc = i;
+
+ return 0;
+}
+
+
+static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ const size_t n = st->channels * st->frame_size;
+
+ if (*sampc < n)
+ return ENOMEM;
+
+ /* Silence */
+ speex_decode_int(st->dec, NULL, sampv);
+ *sampc = n;
+
+ return 0;
+}
+
+
+static void config_parse(struct conf *conf)
+{
+ uint32_t v;
+
+ if (0 == conf_get_u32(conf, "speex_quality", &v))
+ sconf.quality = v;
+ if (0 == conf_get_u32(conf, "speex_complexity", &v))
+ sconf.complexity = v;
+ if (0 == conf_get_u32(conf, "speex_enhancement", &v))
+ sconf.enhancement = v;
+ if (0 == conf_get_u32(conf, "speex_vbr", &v))
+ sconf.vbr = v;
+ if (0 == conf_get_u32(conf, "speex_vad", &v))
+ sconf.vad = v;
+}
+
+
+static struct aucodec speexv[] = {
+
+ /* Stereo Speex */
+ {LE_INIT, 0, "speex", 32000, 2, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 16000, 2, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 8000, 2, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+
+ /* Standard Speex */
+ {LE_INIT, 0, "speex", 32000, 1, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 16000, 1, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 8000, 1, speex_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+};
+
+
+static int speex_init(void)
+{
+ size_t i;
+
+ config_parse(conf_cur());
+
+ (void)re_snprintf(speex_fmtp, sizeof(speex_fmtp),
+ "mode=\"7\";vbr=%s;cng=on",
+ sconf.vad ? "vad" : (sconf.vbr ? "on" : "off"));
+
+ for (i=0; i<ARRAY_SIZE(speexv); i++)
+ aucodec_register(&speexv[i]);
+
+ return 0;
+}
+
+
+static int speex_close(void)
+{
+ size_t i;
+ for (i=0; i<ARRAY_SIZE(speexv); i++)
+ aucodec_unregister(&speexv[i]);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex) = {
+ "speex",
+ "codec",
+ speex_init,
+ speex_close
+};
diff --git a/modules/speex_aec/module.mk b/modules/speex_aec/module.mk
new file mode 100644
index 0000000..9e29696
--- /dev/null
+++ b/modules/speex_aec/module.mk
@@ -0,0 +1,15 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex_aec
+$(MOD)_SRCS += speex_aec.c
+ifneq ($(HAVE_SPEEXDSP),)
+$(MOD)_LFLAGS += "-lspeexdsp"
+else
+$(MOD)_LFLAGS += "-lspeex"
+endif
+
+include mk/mod.mk
diff --git a/modules/speex_aec/speex_aec.c b/modules/speex_aec/speex_aec.c
new file mode 100644
index 0000000..15ea552
--- /dev/null
+++ b/modules/speex_aec/speex_aec.c
@@ -0,0 +1,222 @@
+/**
+ * @file speex_aec.c Speex Acoustic Echo Cancellation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <speex/speex.h>
+#include <speex/speex_echo.h>
+#include <re.h>
+#include <baresip.h>
+
+
+struct speex_st {
+ int16_t *out;
+ SpeexEchoState *state;
+};
+
+struct enc_st {
+ struct aufilt_enc_st af; /* base class */
+ struct speex_st *st;
+};
+
+struct dec_st {
+ struct aufilt_dec_st af; /* base class */
+ struct speex_st *st;
+};
+
+
+static void enc_destructor(void *arg)
+{
+ struct enc_st *st = arg;
+
+ list_unlink(&st->af.le);
+ mem_deref(st->st);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct dec_st *st = arg;
+
+ list_unlink(&st->af.le);
+ mem_deref(st->st);
+}
+
+
+#ifdef SPEEX_SET_VBR_MAX_BITRATE
+static void speex_aec_destructor(void *arg)
+{
+ struct speex_st *st = arg;
+
+ if (st->state)
+ speex_echo_state_destroy(st->state);
+
+ mem_deref(st->out);
+}
+
+
+static int aec_alloc(struct speex_st **stp, void **ctx, struct aufilt_prm *prm)
+{
+ struct speex_st *st;
+ uint32_t sampc;
+ int err, tmp, fl;
+
+ if (!stp || !ctx || !prm)
+ return EINVAL;
+
+ if (*ctx) {
+ *stp = mem_ref(*ctx);
+ return 0;
+ }
+
+ st = mem_zalloc(sizeof(*st), speex_aec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->out = mem_alloc(2 * sampc, NULL);
+ if (!st->out) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Echo canceller with 200 ms tail length */
+ fl = 10 * sampc;
+ st->state = speex_echo_state_init(sampc, fl);
+ if (!st->state) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ tmp = prm->srate;
+ err = speex_echo_ctl(st->state, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp);
+ if (err < 0) {
+ warning("speex_aec: speex_echo_ctl: err=%d\n", err);
+ }
+
+ info("speex_aec: Speex AEC loaded: srate = %uHz\n", prm->srate);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *ctx = *stp = st;
+
+ return err;
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct enc_st *st;
+ int err;
+
+ if (!stp || !ctx || !af || !prm)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = aec_alloc(&st->st, ctx, prm);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct dec_st *st;
+ int err;
+
+ if (!stp || !ctx || !af || !prm)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = aec_alloc(&st->st, ctx, prm);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct enc_st *est = (struct enc_st *)st;
+ struct speex_st *sp = est->st;
+
+ if (*sampc) {
+ speex_echo_capture(sp->state, sampv, sp->out);
+ memcpy(sampv, sp->out, *sampc * 2);
+ }
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct dec_st *dst = (struct dec_st *)st;
+ struct speex_st *sp = dst->st;
+
+ if (*sampc)
+ speex_echo_playback(sp->state, sampv);
+
+ return 0;
+}
+#endif
+
+
+static struct aufilt speex_aec = {
+ LE_INIT, "speex_aec", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ /* Note: Hack to check libspeex version */
+#ifdef SPEEX_SET_VBR_MAX_BITRATE
+ aufilt_register(&speex_aec);
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&speex_aec);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_aec) = {
+ "speex_aec",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/speex_pp/module.mk b/modules/speex_pp/module.mk
new file mode 100644
index 0000000..fad5f88
--- /dev/null
+++ b/modules/speex_pp/module.mk
@@ -0,0 +1,15 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex_pp
+$(MOD)_SRCS += speex_pp.c
+ifneq ($(HAVE_SPEEXDSP),)
+$(MOD)_LFLAGS += "-lspeexdsp"
+else
+$(MOD)_LFLAGS += "-lspeex"
+endif
+
+include mk/mod.mk
diff --git a/modules/speex_pp/speex_pp.c b/modules/speex_pp/speex_pp.c
new file mode 100644
index 0000000..4c14f65
--- /dev/null
+++ b/modules/speex_pp/speex_pp.c
@@ -0,0 +1,154 @@
+/**
+ * @file speex_pp.c Speex Pre-processor
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <speex/speex.h>
+#include <speex/speex_preprocess.h>
+#include <re.h>
+#include <baresip.h>
+
+
+struct preproc {
+ struct aufilt_enc_st af; /* base class */
+ SpeexPreprocessState *state;
+};
+
+
+/** Speex configuration */
+static struct {
+ int denoise_enabled;
+ int agc_enabled;
+ int vad_enabled;
+ int dereverb_enabled;
+ spx_int32_t agc_level;
+} pp_conf = {
+ 1,
+ 1,
+ 1,
+ 1,
+ 8000
+};
+
+
+static void speexpp_destructor(void *arg)
+{
+ struct preproc *st = arg;
+
+ if (st->state)
+ speex_preprocess_state_destroy(st->state);
+
+ list_unlink(&st->af.le);
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct preproc *st;
+ unsigned sampc;
+ (void)ctx;
+
+ if (!stp || !af || !prm || prm->ch != 1)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), speexpp_destructor);
+ if (!st)
+ return ENOMEM;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->state = speex_preprocess_state_init(sampc, prm->srate);
+ if (!st->state)
+ goto error;
+
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DENOISE,
+ &pp_conf.denoise_enabled);
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_AGC,
+ &pp_conf.agc_enabled);
+
+#ifdef SPEEX_PREPROCESS_SET_AGC_TARGET
+ if (pp_conf.agc_enabled) {
+ speex_preprocess_ctl(st->state,
+ SPEEX_PREPROCESS_SET_AGC_TARGET,
+ &pp_conf.agc_level);
+ }
+#endif
+
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_VAD,
+ &pp_conf.vad_enabled);
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DEREVERB,
+ &pp_conf.dereverb_enabled);
+
+ info("speex_pp: Speex preprocessor loaded: srate = %uHz\n",
+ prm->srate);
+
+ *stp = (struct aufilt_enc_st *)st;
+ return 0;
+
+ error:
+ mem_deref(st);
+ return ENOMEM;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct preproc *pp = (struct preproc *)st;
+ int is_speech = 1;
+
+ if (!*sampc)
+ return 0;
+
+ /* NOTE: Using this macro to check libspeex version */
+#ifdef SPEEX_PREPROCESS_SET_NOISE_SUPPRESS
+ /* New API */
+ is_speech = speex_preprocess_run(pp->state, sampv);
+#else
+ /* Old API - not tested! */
+ is_speech = speex_preprocess(pp->state, sampv, NULL);
+#endif
+
+ /* XXX: Handle is_speech and VAD */
+ (void)is_speech;
+
+ return 0;
+}
+
+
+static void config_parse(struct conf *conf)
+{
+ uint32_t v;
+
+ if (0 == conf_get_u32(conf, "speex_agc_level", &v))
+ pp_conf.agc_level = v;
+}
+
+
+static struct aufilt preproc = {
+ LE_INIT, "speex_pp", encode_update, encode, NULL, NULL
+};
+
+static int module_init(void)
+{
+ config_parse(conf_cur());
+ aufilt_register(&preproc);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&preproc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_pp) = {
+ "speex_pp",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/srtp/module.mk b/modules/srtp/module.mk
new file mode 100644
index 0000000..ac4a1c7
--- /dev/null
+++ b/modules/srtp/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := srtp
+$(MOD)_SRCS += srtp.c sdes.c
+$(MOD)_LFLAGS += -lsrtp
+
+include mk/mod.mk
diff --git a/modules/srtp/sdes.c b/modules/srtp/sdes.c
new file mode 100644
index 0000000..a750432
--- /dev/null
+++ b/modules/srtp/sdes.c
@@ -0,0 +1,45 @@
+/**
+ * @file sdes.c SDP Security Descriptions for Media Streams (RFC 4568)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+const char sdp_attr_crypto[] = "crypto";
+
+
+int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite,
+ const char *key, size_t key_len)
+{
+ return sdp_media_set_lattr(m, true, sdp_attr_crypto, "%u %s inline:%b",
+ tag, suite, key, key_len);
+}
+
+
+/* http://tools.ietf.org/html/rfc4568
+ * a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+ */
+int sdes_decode_crypto(struct crypto *c, const char *val)
+{
+ struct pl tag, key_prms;
+ int err;
+
+ err = re_regex(val, str_len(val), "[0-9]+ [^ ]+ [^ ]+[]*[^]*",
+ &tag, &c->suite, &key_prms, NULL, &c->sess_prms);
+ if (err)
+ return err;
+
+ c->tag = pl_u32(&tag);
+
+ c->lifetime = c->mki = pl_null;
+ err = re_regex(key_prms.p, key_prms.l, "[^:]+:[^|]+[|]*[^|]*[|]*[^|]*",
+ &c->key_method, &c->key_info,
+ NULL, &c->lifetime, NULL, &c->mki);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/modules/srtp/sdes.h b/modules/srtp/sdes.h
new file mode 100644
index 0000000..66cc2f1
--- /dev/null
+++ b/modules/srtp/sdes.h
@@ -0,0 +1,22 @@
+/**
+ * @file sdes.h SDP Security Descriptions for Media Streams (RFC 4568) API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct crypto {
+ uint32_t tag;
+ struct pl suite;
+ struct pl key_method;
+ struct pl key_info;
+ struct pl lifetime; /* optional */
+ struct pl mki; /* optional */
+ struct pl sess_prms; /* optional */
+};
+
+extern const char sdp_attr_crypto[];
+
+int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite,
+ const char *key, size_t key_len);
+int sdes_decode_crypto(struct crypto *c, const char *val);
diff --git a/modules/srtp/srtp.c b/modules/srtp/srtp.c
new file mode 100644
index 0000000..bb86e96
--- /dev/null
+++ b/modules/srtp/srtp.c
@@ -0,0 +1,472 @@
+/**
+ * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#if defined (__GNUC__) && !defined (asm)
+#define asm __asm__ /* workaround */
+#endif
+#include <srtp/srtp.h>
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+#define DEBUG_MODULE "srtp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+struct menc_st {
+ /* one SRTP session per media line */
+ uint8_t key_tx[32]; /* 32 for alignment, only 30 used */
+ uint8_t key_rx[32];
+ srtp_t srtp_tx, srtp_rx;
+ srtp_policy_t policy_tx, policy_rx;
+ bool use_srtp;
+ char *crypto_suite;
+
+ void *rtpsock;
+ void *rtcpsock;
+ struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */
+ struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */
+ struct sdp_media *sdpm;
+};
+
+
+static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80";
+
+
+static void destructor(void *arg)
+{
+ struct menc_st *st = arg;
+
+ mem_deref(st->sdpm);
+ mem_deref(st->crypto_suite);
+
+ /* note: must be done before freeing socket */
+ mem_deref(st->uh_rtp);
+ mem_deref(st->uh_rtcp);
+ mem_deref(st->rtpsock);
+ mem_deref(st->rtcpsock);
+
+ if (st->srtp_tx)
+ srtp_dealloc(st->srtp_tx);
+ if (st->srtp_rx)
+ srtp_dealloc(st->srtp_rx);
+}
+
+
+static bool cryptosuite_issupported(const struct pl *suite)
+{
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true;
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true;
+
+ return false;
+}
+
+
+static int errstatus_print(struct re_printf *pf, err_status_t e)
+{
+ const char *s;
+
+ switch (e) {
+
+ case err_status_ok: s = "ok"; break;
+ case err_status_fail: s = "fail"; break;
+ case err_status_auth_fail: s = "auth_fail"; break;
+ case err_status_cipher_fail: s = "cipher_fail"; break;
+ case err_status_replay_fail: s = "replay_fail"; break;
+
+ default:
+ return re_hprintf(pf, "err=%d", e);
+ }
+
+ return re_hprintf(pf, "%s", s);
+}
+
+
+/*
+ * See RFC 5764 figure 3:
+ *
+ * +----------------+
+ * | 127 < B < 192 -+--> forward to RTP
+ * | |
+ * packet --> | 19 < B < 64 -+--> forward to DTLS
+ * | |
+ * | B < 2 -+--> forward to STUN
+ * +----------------+
+ *
+ */
+static bool is_rtp_or_rtcp(const struct mbuf *mb)
+{
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mbuf_buf(mb)[0];
+
+ return 127 < b && b < 192;
+}
+
+
+static bool is_rtcp_packet(const struct mbuf *mb)
+{
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return false;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ return 64 <= pt && pt <= 95;
+}
+
+
+static int start_srtp(struct menc_st *st, const char *suite)
+{
+ crypto_policy_t policy;
+ err_status_t e;
+
+ if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy);
+ }
+ else if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy);
+ }
+ else {
+ DEBUG_WARNING("unknown SRTP crypto suite (%s)\n", suite);
+ return ENOENT;
+ }
+
+ /* transmit policy */
+ st->policy_tx.rtp = policy;
+ st->policy_tx.rtcp = policy;
+ st->policy_tx.ssrc.type = ssrc_any_outbound;
+ st->policy_tx.key = st->key_tx;
+ st->policy_tx.next = NULL;
+
+ /* receive policy */
+ st->policy_rx.rtp = policy;
+ st->policy_rx.rtcp = policy;
+ st->policy_rx.ssrc.type = ssrc_any_inbound;
+ st->policy_rx.key = st->key_rx;
+ st->policy_rx.next = NULL;
+
+ /* allocate and initialize the SRTP session */
+ e = srtp_create(&st->srtp_tx, &st->policy_tx);
+ if (e != err_status_ok) {
+ DEBUG_WARNING("srtp_create TX failed (%H)\n",
+ errstatus_print, e);
+ return EPROTO;
+ }
+
+ e = srtp_create(&st->srtp_rx, &st->policy_rx);
+ if (err_status_ok != e) {
+ DEBUG_WARNING("srtp_create RX failed (%H)\n",
+ errstatus_print, e);
+ return EPROTO;
+ }
+
+ /* use SRTP for this stream/session */
+ st->use_srtp = true;
+
+ return 0;
+}
+
+
+static int setup_srtp(struct menc_st *st)
+{
+ err_status_t e;
+
+ /* init SRTP */
+ e = crypto_get_random(st->key_tx, SRTP_MASTER_KEY_LEN);
+ if (err_status_ok != e) {
+ DEBUG_WARNING("crypto_get_random() failed (%H)\n",
+ errstatus_print, e);
+ return ENOSYS;
+ }
+
+ return 0;
+}
+
+
+static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ err_status_t e;
+ int len;
+ (void)dst;
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ len = (int)mbuf_get_left(mb);
+
+ if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) {
+ mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN);
+ }
+
+ if (is_rtcp_packet(mb)) {
+ e = srtp_protect_rtcp(st->srtp_tx, mbuf_buf(mb), &len);
+ }
+ else {
+ e = srtp_protect(st->srtp_tx, mbuf_buf(mb), &len);
+ }
+
+ if (err_status_ok != e) {
+ DEBUG_WARNING("send: failed to protect %s-packet"
+ " with %d bytes (%H)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP",
+ len, errstatus_print, e);
+ *err = EPROTO;
+ return false;
+ }
+
+ mbuf_set_end(mb, mb->pos + len);
+
+ return false; /* continue processing */
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ err_status_t e;
+ int len;
+ (void)src;
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ len = (int)mbuf_get_left(mb);
+
+ if (is_rtcp_packet(mb)) {
+ e = srtp_unprotect_rtcp(st->srtp_rx, mbuf_buf(mb), &len);
+ }
+ else {
+ e = srtp_unprotect(st->srtp_rx, mbuf_buf(mb), &len);
+ }
+
+ if (e != err_status_ok) {
+ DEBUG_WARNING("recv: failed to unprotect %s-packet"
+ " with %d bytes (%H)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP",
+ len, errstatus_print, e);
+ return true; /* error - drop packet */
+ }
+
+ mbuf_set_end(mb, mb->pos + len);
+
+ return false; /* continue processing */
+}
+
+
+/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */
+static int sdp_enc(struct menc_st *st, struct sdp_media *m,
+ uint32_t tag, const char *suite)
+{
+ char key[128] = "";
+ size_t olen;
+ int err;
+
+ olen = sizeof(key);
+ err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen);
+ if (err)
+ return err;
+
+ return sdes_encode_crypto(m, tag, suite, key, olen);
+}
+
+
+static int start_crypto(struct menc_st *st, const struct pl *key_info)
+{
+ size_t olen;
+ int err;
+
+ /* key-info is BASE64 encoded */
+
+ olen = sizeof(st->key_rx);
+ err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen);
+ if (err)
+ return err;
+
+ if (SRTP_MASTER_KEY_LEN != olen) {
+ DEBUG_WARNING("srtp keylen is %u (should be 30)\n", olen);
+ }
+
+ err = start_srtp(st, st->crypto_suite);
+ if (err)
+ return err;
+
+ info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n",
+ sdp_media_name(st->sdpm), st->crypto_suite);
+
+ return 0;
+}
+
+
+static bool sdp_attr_handler(const char *name, const char *value, void *arg)
+{
+ struct menc_st *st = arg;
+ struct crypto c;
+ (void)name;
+
+ if (sdes_decode_crypto(&c, value))
+ return false;
+
+ if (0 != pl_strcmp(&c.key_method, "inline"))
+ return false;
+
+ if (!cryptosuite_issupported(&c.suite))
+ return false;
+
+ st->crypto_suite = mem_deref(st->crypto_suite);
+ pl_strdup(&st->crypto_suite, &c.suite);
+
+ if (start_crypto(st, &c.key_info))
+ return false;
+
+ sdp_enc(st, st->sdpm, c.tag, st->crypto_suite);
+
+ return true;
+}
+
+
+static int alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_st *st;
+ const char *rattr = NULL;
+ int layer = 10; /* above zero */
+ int err = 0;
+ bool mux = (rtpsock == rtcpsock);
+ (void)sess;
+ (void)rtp;
+
+ if (!stp || !sdpm)
+ return EINVAL;
+ if (proto != IPPROTO_UDP)
+ return EPROTONOSUPPORT;
+
+ st = (struct menc_st *)*stp;
+ if (!st) {
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sdpm = mem_ref(sdpm);
+
+ err = sdp_media_set_alt_protos(st->sdpm, 4,
+ "RTP/AVP",
+ "RTP/AVPF",
+ "RTP/SAVP",
+ "RTP/SAVPF");
+ if (err)
+ goto out;
+
+ if (rtpsock) {
+ st->rtpsock = mem_ref(rtpsock);
+ err |= udp_register_helper(&st->uh_rtp, rtpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (rtcpsock && !mux) {
+ st->rtcpsock = mem_ref(rtcpsock);
+ err |= udp_register_helper(&st->uh_rtcp, rtcpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (err)
+ goto out;
+
+ /* set our preferred crypto-suite */
+ err |= str_dup(&st->crypto_suite, aes_cm_128_hmac_sha1_80);
+ if (err)
+ goto out;
+
+ err = setup_srtp(st);
+ if (err)
+ goto out;
+ }
+
+ /* SDP handling */
+
+ if (sdp_media_rattr(st->sdpm, "crypto")) {
+
+ rattr = sdp_media_rattr_apply(st->sdpm, "crypto",
+ sdp_attr_handler, st);
+ if (!rattr) {
+ DEBUG_WARNING("no valid a=crypto attribute from"
+ " remote peer\n");
+ }
+ }
+
+ if (!rattr)
+ err = sdp_enc(st, sdpm, 0, st->crypto_suite);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct menc_media *)st;
+
+ return err;
+}
+
+
+static struct menc menc_srtp_opt = {
+ LE_INIT, "srtp", "RTP/AVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mand = {
+ LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mandf = {
+ LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc
+};
+
+
+static int mod_srtp_init(void)
+{
+ err_status_t err;
+
+ err = srtp_init();
+ if (err_status_ok != err) {
+ DEBUG_WARNING("srtp_init() failed (%H)\n",
+ errstatus_print, err);
+ return ENOSYS;
+ }
+
+ menc_register(&menc_srtp_opt);
+ menc_register(&menc_srtp_mand);
+ menc_register(&menc_srtp_mandf);
+
+ return 0;
+}
+
+
+static int mod_srtp_close(void)
+{
+ menc_unregister(&menc_srtp_mandf);
+ menc_unregister(&menc_srtp_mand);
+ menc_unregister(&menc_srtp_opt);
+
+ crypto_kernel_shutdown();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(srtp) = {
+ "srtp",
+ "menc",
+ mod_srtp_init,
+ mod_srtp_close
+};
diff --git a/modules/stdio/module.mk b/modules/stdio/module.mk
new file mode 100644
index 0000000..4c52b28
--- /dev/null
+++ b/modules/stdio/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := stdio
+$(MOD)_SRCS += stdio.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/stdio/stdio.c b/modules/stdio/stdio.c
new file mode 100644
index 0000000..bfdc9e7
--- /dev/null
+++ b/modules/stdio/stdio.c
@@ -0,0 +1,182 @@
+/**
+ * @file stdio.c Standard Input/Output UI module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/** Local constants */
+enum {
+ RELEASE_VAL = 250 /**< Key release value in [ms] */
+};
+
+struct ui_st {
+ struct ui *ui; /* base class */
+ struct tmr tmr;
+ struct termios term;
+ bool term_set;
+ ui_input_h *h;
+ void *arg;
+};
+
+
+/* We only allow one instance */
+static struct ui_st *_ui;
+static struct ui *stdio;
+
+
+static void ui_destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ fd_close(STDIN_FILENO);
+
+ if (st->term_set)
+ tcsetattr(STDIN_FILENO, TCSANOW, &st->term);
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->ui);
+
+ _ui = NULL;
+}
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM;
+}
+
+
+static void report_key(struct ui_st *ui, char key)
+{
+ struct re_printf pf;
+
+ pf.vph = print_handler;
+
+ if (ui->h)
+ ui->h(key, &pf, ui->arg);
+}
+
+
+static void timeout(void *arg)
+{
+ struct ui_st *st = arg;
+
+ /* Emulate key-release */
+ report_key(st, 0x00);
+}
+
+
+static void ui_fd_handler(int flags, void *arg)
+{
+ struct ui_st *st = arg;
+ char key;
+ (void)flags;
+
+ if (1 != read(STDIN_FILENO, &key, 1)) {
+ return;
+ }
+
+ tmr_start(&st->tmr, RELEASE_VAL, timeout, st);
+ report_key(st, key);
+}
+
+
+static int term_setup(struct ui_st *st)
+{
+ struct termios now;
+
+ if (tcgetattr(STDIN_FILENO, &st->term) < 0)
+ return errno;
+
+ now = st->term;
+
+ now.c_lflag |= ISIG;
+ now.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
+
+ /* required on Solaris */
+ now.c_cc[VMIN] = 1;
+ now.c_cc[VTIME] = 0;
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &now) < 0)
+ return errno;
+
+ st->term_set = true;
+
+ return 0;
+}
+
+
+static int ui_alloc(struct ui_st **stp, struct ui_prm *prm,
+ ui_input_h *ih, void *arg)
+{
+ struct ui_st *st;
+ int err;
+
+ (void)prm;
+
+ if (!stp)
+ return EINVAL;
+
+ if (_ui) {
+ *stp = mem_ref(_ui);
+ return 0;
+ }
+
+ st = mem_zalloc(sizeof(*st), ui_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ui = mem_ref(stdio);
+ tmr_init(&st->tmr);
+
+ err = fd_listen(STDIN_FILENO, FD_READ, ui_fd_handler, st);
+ if (err)
+ goto out;
+
+ err = term_setup(st);
+ if (err) {
+ info("stdio: could not setup terminal: %m\n", err);
+ err = 0;
+ }
+
+ st->h = ih;
+ st->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = _ui = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return ui_register(&stdio, "stdio", ui_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ stdio = mem_deref(stdio);
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(stdio) = {
+ "stdio",
+ "ui",
+ module_init,
+ module_close
+};
diff --git a/modules/stun/module.mk b/modules/stun/module.mk
new file mode 100644
index 0000000..6ae88b5
--- /dev/null
+++ b/modules/stun/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := stun
+$(MOD)_SRCS += stun.c
+
+include mk/mod.mk
diff --git a/modules/stun/stun.c b/modules/stun/stun.c
new file mode 100644
index 0000000..d7edf94
--- /dev/null
+++ b/modules/stun/stun.c
@@ -0,0 +1,251 @@
+/**
+ * @file stun.c STUN Module for Media NAT-traversal
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup stun stun
+ *
+ * Session Traversal Utilities for NAT (STUN) for media NAT traversal
+ */
+
+
+enum {LAYER = 0, INTERVAL = 30};
+
+struct mnat_sess {
+ struct list medial;
+ struct sa srv;
+ struct stun_dns *dnsq;
+ mnat_estab_h *estabh;
+ void *arg;
+ int mediac;
+};
+
+
+struct mnat_media {
+ struct le le;
+ struct sa addr1;
+ struct sa addr2;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct stun_keepalive *ska1;
+ struct stun_keepalive *ska2;
+ void *sock1;
+ void *sock2;
+ int proto;
+};
+
+
+static struct mnat *mnat;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+ mem_deref(sess->dnsq);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ list_unlink(&m->le);
+ mem_deref(m->sdpm);
+ mem_deref(m->ska1);
+ mem_deref(m->ska2);
+ mem_deref(m->sock1);
+ mem_deref(m->sock2);
+}
+
+
+static void mapped_handler1(int err, const struct sa *map_addr, void *arg)
+{
+ struct mnat_media *m = arg;
+
+ if (!err) {
+
+ sdp_media_set_laddr(m->sdpm, map_addr);
+
+ m->addr1 = *map_addr;
+
+ if (m->ska2 && !sa_isset(&m->addr2, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, 0, NULL, m->sess->arg);
+}
+
+
+static void mapped_handler2(int err, const struct sa *map_addr, void *arg)
+{
+ struct mnat_media *m = arg;
+
+ if (!err) {
+
+ sdp_media_set_laddr_rtcp(m->sdpm, map_addr);
+
+ m->addr2 = *map_addr;
+
+ if (m->ska1 && !sa_isset(&m->addr1, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, 0, NULL, m->sess->arg);
+}
+
+
+static int media_start(struct mnat_sess *sess, struct mnat_media *m)
+{
+ int err = 0;
+
+ if (m->sock1) {
+ err |= stun_keepalive_alloc(&m->ska1, m->proto,
+ m->sock1, LAYER, &sess->srv, NULL,
+ mapped_handler1, m);
+ }
+ if (m->sock2) {
+ err |= stun_keepalive_alloc(&m->ska2, m->proto,
+ m->sock2, LAYER, &sess->srv, NULL,
+ mapped_handler2, m);
+ }
+ if (err)
+ return err;
+
+ stun_keepalive_enable(m->ska1, INTERVAL);
+ stun_keepalive_enable(m->ska2, INTERVAL);
+
+ return 0;
+}
+
+
+static void dns_handler(int err, const struct sa *srv, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ if (err)
+ goto out;
+
+ sess->srv = *srv;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+
+ err = media_start(sess, m);
+ if (err)
+ goto out;
+ }
+
+ return;
+
+ out:
+ sess->estabh(err, 0, NULL, sess->arg);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err;
+ (void)user;
+ (void)pass;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ err = stun_server_discover(&sess->dnsq, dnsc,
+ stun_usage_binding, stun_proto_udp,
+ af, srv, port, dns_handler, sess);
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sdpm = mem_ref(sdpm);
+ m->sess = sess;
+ m->sock1 = mem_ref(sock1);
+ m->sock2 = mem_ref(sock2);
+ m->proto = proto;
+
+ if (sa_isset(&sess->srv, SA_ALL))
+ err = media_start(sess, m);
+
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ ++sess->mediac;
+ }
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return mnat_register(&mnat, "stun", NULL, session_alloc, media_alloc,
+ NULL);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(stun) = {
+ "stun",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/syslog/module.mk b/modules/syslog/module.mk
new file mode 100644
index 0000000..94fd185
--- /dev/null
+++ b/modules/syslog/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := syslog
+$(MOD)_SRCS += syslog.c
+
+include mk/mod.mk
diff --git a/modules/syslog/syslog.c b/modules/syslog/syslog.c
new file mode 100644
index 0000000..40e5690
--- /dev/null
+++ b/modules/syslog/syslog.c
@@ -0,0 +1,112 @@
+/**
+ * @file syslog.c Syslog module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <syslog.h>
+#include <re.h>
+#include <baresip.h>
+
+
+#define DEBUG_MODULE "syslog"
+#define DEBUG_LEVEL 6
+#include <re_dbg.h>
+
+
+#if defined (DARWIN) || defined (__GLIBC__)
+
+static FILE *fv[2];
+
+
+static int writer(void *cookie, const char *p, int len)
+{
+ (void)cookie;
+
+ syslog(LOG_NOTICE, "%.*s", (int)len, p);
+
+ return len;
+}
+
+
+static void tolog(int ix, FILE **pfp)
+{
+#if defined (__GLIBC__)
+ static cookie_io_functions_t memfile_func = {
+ .write = (cookie_write_function_t *)writer
+ };
+#endif
+ FILE *f;
+
+#if defined (__GLIBC__)
+ f = fopencookie(NULL, "w+", memfile_func);
+#else
+ f = fwopen(NULL, writer);
+#endif
+
+ if (!f)
+ return;
+
+ setvbuf(f, NULL, _IOLBF, 0);
+ fv[ix] = *pfp = f;
+}
+
+
+static void restore(int ix, FILE **fp)
+{
+ if (fv[ix]) {
+ *fp = fv[ix];
+ fv[ix] = NULL;
+ }
+}
+#endif
+
+
+static void syslog_handler(int level, const char *p, size_t len, void *arg)
+{
+ (void)arg;
+
+ syslog(level, "%.*s", (int)len, p);
+}
+
+
+static int module_init(void)
+{
+ openlog("baresip", LOG_NDELAY | LOG_PID, LOG_LOCAL0);
+
+#if defined (DARWIN) || defined (__GLIBC__)
+ /* Redirect stdout/stderr to syslog */
+ tolog(0, &stdout);
+ tolog(1, &stderr);
+#endif
+
+ dbg_init(DBG_INFO, DBG_NONE);
+ dbg_handler_set(syslog_handler, NULL);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ dbg_handler_set(NULL, NULL);
+
+#if defined (DARWIN) || defined (__GLIBC__)
+ restore(0, &stdout);
+ restore(1, &stderr);
+#endif
+
+ closelog();
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(syslog) = {
+ "syslog",
+ "syslog",
+ module_init,
+ module_close
+};
diff --git a/modules/turn/module.mk b/modules/turn/module.mk
new file mode 100644
index 0000000..876308d
--- /dev/null
+++ b/modules/turn/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := turn
+$(MOD)_SRCS += turn.c
+
+include mk/mod.mk
diff --git a/modules/turn/turn.c b/modules/turn/turn.c
new file mode 100644
index 0000000..41588e6
--- /dev/null
+++ b/modules/turn/turn.c
@@ -0,0 +1,300 @@
+/**
+ * @file turn.c TURN Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup turn turn
+ *
+ * Traversal Using Relays around NAT (TURN) for media NAT traversal
+ *
+ * XXX: use turn RSV_TOKEN for RTP/RTCP even/odd pair ?
+ */
+
+
+enum {LAYER = 0};
+
+
+struct mnat_sess {
+ struct list medial;
+ struct sa srv;
+ struct stun_dns *dnsq;
+ char *user;
+ char *pass;
+ mnat_estab_h *estabh;
+ void *arg;
+ int mediac;
+};
+
+
+struct mnat_media {
+ struct le le;
+ struct sa addr1;
+ struct sa addr2;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct turnc *turnc1;
+ struct turnc *turnc2;
+ void *sock1;
+ void *sock2;
+ int proto;
+};
+
+
+static struct mnat *mnat;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+ mem_deref(sess->dnsq);
+ mem_deref(sess->user);
+ mem_deref(sess->pass);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ list_unlink(&m->le);
+ mem_deref(m->sdpm);
+ mem_deref(m->turnc1);
+ mem_deref(m->turnc2);
+ mem_deref(m->sock1);
+ mem_deref(m->sock2);
+}
+
+
+static void turn_handler1(int err, uint16_t scode, const char *reason,
+ const struct sa *relay_addr,
+ const struct sa *mapped_addr,
+ const struct stun_msg *msg,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ (void)mapped_addr;
+ (void)msg;
+
+ if (!err && !scode) {
+
+ sdp_media_set_laddr(m->sdpm, relay_addr);
+
+ m->addr1 = *relay_addr;
+
+ if (m->turnc2 && !sa_isset(&m->addr2, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, scode, reason, m->sess->arg);
+}
+
+
+static void turn_handler2(int err, uint16_t scode, const char *reason,
+ const struct sa *relay_addr,
+ const struct sa *mapped_addr,
+ const struct stun_msg *msg,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ (void)mapped_addr;
+ (void)msg;
+
+ if (!err && !scode) {
+
+ sdp_media_set_laddr_rtcp(m->sdpm, relay_addr);
+
+ m->addr2 = *relay_addr;
+
+ if (m->turnc1 && !sa_isset(&m->addr1, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, scode, reason, m->sess->arg);
+}
+
+
+static int media_start(struct mnat_sess *sess, struct mnat_media *m)
+{
+ int err = 0;
+
+ if (m->sock1) {
+ err |= turnc_alloc(&m->turnc1, NULL,
+ m->proto, m->sock1, LAYER,
+ &sess->srv, sess->user, sess->pass,
+ TURN_DEFAULT_LIFETIME,
+ turn_handler1, m);
+ }
+ if (m->sock2) {
+ err |= turnc_alloc(&m->turnc2, NULL,
+ m->proto, m->sock2, LAYER,
+ &sess->srv, sess->user, sess->pass,
+ TURN_DEFAULT_LIFETIME,
+ turn_handler2, m);
+ }
+
+ return err;
+}
+
+
+static void dns_handler(int err, const struct sa *srv, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ if (err)
+ goto out;
+
+ sess->srv = *srv;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+
+ err = media_start(sess, m);
+ if (err)
+ goto out;
+ }
+
+ return;
+
+ out:
+ sess->estabh(err, 0, NULL, sess->arg);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ err = str_dup(&sess->user, user);
+ err |= str_dup(&sess->pass, pass);
+ if (err)
+ goto out;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ err = stun_server_discover(&sess->dnsq, dnsc,
+ stun_usage_relay, stun_proto_udp,
+ af, srv, port, dns_handler, sess);
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sdpm = mem_ref(sdpm);
+ m->sess = sess;
+ m->sock1 = mem_ref(sock1);
+ m->sock2 = mem_ref(sock2);
+ m->proto = proto;
+
+ if (sa_isset(&sess->srv, SA_ALL))
+ err = media_start(sess, m);
+
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ ++sess->mediac;
+ }
+
+ return err;
+}
+
+
+static int update(struct mnat_sess *sess)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!sess)
+ return EINVAL;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+ struct sa raddr1, raddr2;
+
+ raddr1 = *sdp_media_raddr(m->sdpm);
+ sdp_media_raddr_rtcp(m->sdpm, &raddr2);
+
+ if (m->turnc1 && sa_isset(&raddr1, SA_ALL))
+ err |= turnc_add_chan(m->turnc1, &raddr1, NULL, NULL);
+
+ if (m->turnc2 && sa_isset(&raddr2, SA_ALL))
+ err |= turnc_add_chan(m->turnc2, &raddr2, NULL, NULL);
+ }
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return mnat_register(&mnat, "turn", NULL, session_alloc, media_alloc,
+ update);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(turn) = {
+ "turn",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/uuid/module.mk b/modules/uuid/module.mk
new file mode 100644
index 0000000..64a3909
--- /dev/null
+++ b/modules/uuid/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := uuid
+$(MOD)_SRCS += uuid.c
+ifneq ($(OS),darwin)
+$(MOD)_LFLAGS += -luuid
+endif
+
+include mk/mod.mk
diff --git a/modules/uuid/uuid.c b/modules/uuid/uuid.c
new file mode 100644
index 0000000..6426a6e
--- /dev/null
+++ b/modules/uuid/uuid.c
@@ -0,0 +1,96 @@
+/**
+ * @file modules/uuid/uuid.c Generate and load UUID
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <uuid/uuid.h>
+#include <re.h>
+#include <baresip.h>
+
+
+static int uuid_init(const char *file)
+{
+ char uuid[37];
+ uuid_t uu;
+ FILE *f = NULL;
+ int err = 0;
+
+ f = fopen(file, "r");
+ if (f) {
+ err = 0;
+ goto out;
+ }
+
+ f = fopen(file, "w");
+ if (!f) {
+ err = errno;
+ warning("uuid: fopen() %s (%m)\n", file, err);
+ goto out;
+ }
+
+ uuid_generate(uu);
+
+ uuid_unparse(uu, uuid);
+
+ re_fprintf(f, "%s", uuid);
+
+ info("uuid: generated new UUID (%s)\n", uuid);
+
+ out:
+ if (f)
+ fclose(f);
+
+ return err;
+}
+
+
+static int uuid_load(const char *file, char *uuid, size_t sz)
+{
+ FILE *f = NULL;
+ int err = 0;
+
+ f = fopen(file, "r");
+ if (!f)
+ return errno;
+
+ if (!fgets(uuid, (int)sz, f))
+ err = errno;
+
+ (void)fclose(f);
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ struct config *cfg = conf_config();
+ char path[256];
+ int err = 0;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err)
+ return err;
+
+ strncat(path, "/uuid", sizeof(path) - strlen(path) - 1);
+
+ err = uuid_init(path);
+ if (err)
+ return err;
+
+ err = uuid_load(path, cfg->sip.uuid, sizeof(cfg->sip.uuid));
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(uuid) = {
+ "uuid",
+ NULL,
+ module_init,
+ NULL
+};
diff --git a/modules/v4l/module.mk b/modules/v4l/module.mk
new file mode 100644
index 0000000..05ff278
--- /dev/null
+++ b/modules/v4l/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := v4l
+$(MOD)_SRCS += v4l.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/v4l/v4l.c b/modules/v4l/v4l.c
new file mode 100644
index 0000000..1f3e760
--- /dev/null
+++ b/modules/v4l/v4l.c
@@ -0,0 +1,257 @@
+/**
+ * @file v4l.c Video4Linux video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */
+#include <libv4l1-videodev.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+struct vidsrc_st {
+ struct vidsrc *vs; /* inheritance */
+
+ int fd;
+ pthread_t thread;
+ bool run;
+ struct vidsz size;
+ struct mbuf *mb;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void v4l_get_caps(struct vidsrc_st *st)
+{
+ struct video_capability caps;
+
+ if (-1 == ioctl(st->fd, VIDIOCGCAP, &caps)) {
+ warning("v4l: VIDIOCGCAP: %m\n", errno);
+ return;
+ }
+
+ info("v4l: video: \"%s\" (%ux%u) - (%ux%u)\n", caps.name,
+ caps.minwidth, caps.minheight,
+ caps.maxwidth, caps.maxheight);
+
+ if (VID_TYPE_CAPTURE != caps.type) {
+ warning("v4l: not a capture device (%d)\n", caps.type);
+ }
+}
+
+
+static int v4l_check_palette(struct vidsrc_st *st)
+{
+ struct video_picture pic;
+
+ if (-1 == ioctl(st->fd, VIDIOCGPICT, &pic)) {
+ warning("v4l: VIDIOCGPICT: %m\n", errno);
+ return errno;
+ }
+
+ if (VIDEO_PALETTE_RGB24 != pic.palette) {
+ warning("v4l: unsupported palette %d (only RGB24 supp.)\n",
+ pic.palette);
+ return ENODEV;
+ }
+
+ return 0;
+}
+
+
+static int v4l_get_win(int fd, int width, int height)
+{
+ struct video_window win;
+
+ if (-1 == ioctl(fd, VIDIOCGWIN, &win)) {
+ warning("v4l: VIDIOCGWIN: %m\n", errno);
+ return errno;
+ }
+
+ info("v4l: video window: x,y=%u,%u (%u x %u)\n",
+ win.x, win.y, win.width, win.height);
+
+ win.width = width;
+ win.height = height;
+
+ if (-1 == ioctl(fd, VIDIOCSWIN, &win)) {
+ warning("v4l: VIDIOCSWIN: %m\n", errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ while (st->run) {
+ ssize_t n;
+
+ n = read(st->fd, st->mb->buf, st->mb->size);
+ if ((ssize_t)st->mb->size != n) {
+ warning("v4l: video read: %d -> %d bytes\n",
+ st->mb->size, n);
+ continue;
+ }
+
+ call_frame_handler(st, st->mb->buf);
+ }
+
+ return NULL;
+}
+
+
+static int vd_open(struct vidsrc_st *v4l, const char *device)
+{
+ /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open
+ * the video device.
+ */
+ v4l->fd = open(device, O_RDWR);
+ if (v4l->fd < 0) {
+ warning("v4l: open %s: %m\n", device, errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->fd >= 0)
+ close(st->fd);
+
+ mem_deref(st->mb);
+ mem_deref(st->vs);
+}
+
+
+static uint32_t rgb24_size(const struct vidsz *sz)
+{
+ return sz ? (sz->w * sz->h * 24/8) : 0;
+}
+
+
+static int alloc(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ if (!str_isset(dev))
+ dev = "/dev/video0";
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = mem_ref(vs);
+ st->fd = -1;
+ st->size = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ info("v4l: open: %s (%u x %u)\n", dev, size->w, size->h);
+
+ err = vd_open(st, dev);
+ if (err)
+ goto out;
+
+ v4l_get_caps(st);
+
+ err = v4l_check_palette(st);
+ if (err)
+ goto out;
+
+ err = v4l_get_win(st->fd, st->size.w, st->size.h);
+ if (err)
+ goto out;
+
+ /* note: assumes RGB24 */
+ st->mb = mbuf_alloc(rgb24_size(&st->size));
+ if (!st->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int v4l_init(void)
+{
+ return vidsrc_register(&vidsrc, "v4l", alloc, NULL);
+}
+
+
+static int v4l_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l) = {
+ "v4l",
+ "vidsrc",
+ v4l_init,
+ v4l_close
+};
diff --git a/modules/v4l2/module.mk b/modules/v4l2/module.mk
new file mode 100644
index 0000000..61360cb
--- /dev/null
+++ b/modules/v4l2/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := v4l2
+$(MOD)_SRCS += v4l2.c
+$(MOD)_LFLAGS += -lv4l2
+
+include mk/mod.mk
diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c
new file mode 100644
index 0000000..2b60908
--- /dev/null
+++ b/modules/v4l2/v4l2.c
@@ -0,0 +1,575 @@
+/**
+ * @file v4l2.c Video4Linux2 video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */
+#include <linux/videodev2.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libv4l2.h>
+
+
+enum io_method {
+ IO_METHOD_READ = 0,
+ IO_METHOD_MMAP
+};
+
+struct buffer {
+ void *start;
+ size_t length;
+};
+
+struct vidsrc_st {
+ struct vidsrc *vs; /* inheritance */
+
+ int fd;
+ pthread_t thread;
+ bool run;
+ struct vidsz sz, app_sz;
+ struct mbuf *mb;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ enum io_method io;
+ struct buffer *buffers;
+ unsigned int n_buffers;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void get_video_input(struct vidsrc_st *st)
+{
+ struct v4l2_input input;
+
+ memset(&input, 0, sizeof(input));
+
+ if (-1 == v4l2_ioctl(st->fd, VIDIOC_G_INPUT, &input.index)) {
+ warning("v4l2: VIDIOC_G_INPUT: %m\n", errno);
+ return;
+ }
+
+ if (-1 == v4l2_ioctl(st->fd, VIDIOC_ENUMINPUT, &input)) {
+ warning("v4l2: VIDIOC_ENUMINPUT: %m\n", errno);
+ return;
+ }
+
+ info("v4l2: Current input: %s\n", input.name);
+}
+
+
+static int xioctl(int fd, unsigned long int request, void *arg)
+{
+ int r;
+
+ do {
+ r = v4l2_ioctl(fd, request, arg);
+ }
+ while (-1 == r && EINTR == errno);
+
+ return r;
+}
+
+
+static int init_read(struct vidsrc_st *st, unsigned int buffer_size)
+{
+ st->buffers = calloc(1, sizeof (*st->buffers));
+ if (!st->buffers)
+ return ENOMEM;
+
+ st->buffers[0].length = buffer_size;
+ st->buffers[0].start = malloc(buffer_size);
+ if (!st->buffers[0].start)
+ return ENOMEM;
+
+ return 0;
+}
+
+
+static int init_mmap(struct vidsrc_st *st, const char *dev_name)
+{
+ struct v4l2_requestbuffers req;
+
+ memset(&req, 0, sizeof(req));
+
+ req.count = 4;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+
+ if (-1 == xioctl(st->fd, VIDIOC_REQBUFS, &req)) {
+ if (EINVAL == errno) {
+ warning("v4l2: %s does not support "
+ "memory mapping\n", dev_name);
+ return errno;
+ }
+ else {
+ return errno;
+ }
+ }
+
+ if (req.count < 2) {
+ warning("v4l2: Insufficient buffer memory on %s\n", dev_name);
+ return ENOMEM;
+ }
+
+ st->buffers = calloc(req.count, sizeof(*st->buffers));
+ if (!st->buffers)
+ return ENOMEM;
+
+ for (st->n_buffers = 0; st->n_buffers<req.count; ++st->n_buffers) {
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = st->n_buffers;
+
+ if (-1 == xioctl(st->fd, VIDIOC_QUERYBUF, &buf)) {
+ warning("v4l2: VIDIOC_QUERYBUF\n");
+ return errno;
+ }
+
+ st->buffers[st->n_buffers].length = buf.length;
+ st->buffers[st->n_buffers].start =
+ v4l2_mmap(NULL /* start anywhere */,
+ buf.length,
+ PROT_READ | PROT_WRITE /* required */,
+ MAP_SHARED /* recommended */,
+ st->fd, buf.m.offset);
+
+ if (MAP_FAILED == st->buffers[st->n_buffers].start) {
+ warning("v4l2: mmap failed\n");
+ return ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+
+static int v4l2_init_device(struct vidsrc_st *st, const char *dev_name)
+{
+ struct v4l2_capability cap;
+ struct v4l2_format fmt;
+ unsigned int min;
+ const char *pix;
+ int err;
+
+ if (-1 == xioctl(st->fd, VIDIOC_QUERYCAP, &cap)) {
+ if (EINVAL == errno) {
+ warning("v4l2: %s is no V4L2 device\n", dev_name);
+ return ENODEV;
+ }
+ else {
+ warning("v4l2: VIDIOC_QUERYCAP: %m\n", errno);
+ return errno;
+ }
+ }
+
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
+ warning("v4l2: %s is no video capture device\n", dev_name);
+ return ENODEV;
+ }
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
+ warning("%s does not support read i/o\n", dev_name);
+ return ENOSYS;
+ }
+ break;
+
+ case IO_METHOD_MMAP:
+ if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
+ warning("v4l2: %s does not support streaming i/o\n",
+ dev_name);
+ return ENOSYS;
+ }
+ break;
+ }
+
+ /* Select video input, video standard and tune here. */
+
+ memset(&fmt, 0, sizeof(fmt));
+
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt.fmt.pix.width = st->app_sz.w;
+ fmt.fmt.pix.height = st->app_sz.h;
+ fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
+ fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+ if (-1 == xioctl(st->fd, VIDIOC_S_FMT, &fmt)) {
+ warning("v4l2: VIDIOC_S_FMT: %m\n", errno);
+ return errno;
+ }
+
+ /* Note VIDIOC_S_FMT may change width and height. */
+
+ /* Buggy driver paranoia. */
+ min = fmt.fmt.pix.width * 2;
+ if (fmt.fmt.pix.bytesperline < min)
+ fmt.fmt.pix.bytesperline = min;
+ min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
+ if (fmt.fmt.pix.sizeimage < min)
+ fmt.fmt.pix.sizeimage = min;
+
+ st->sz.w = fmt.fmt.pix.width;
+ st->sz.h = fmt.fmt.pix.height;
+
+ if (!vidsz_cmp(&st->sz, &st->app_sz)) {
+ info("v4l2: scaling %u x %u ---> %u x %u\n",
+ st->sz.w, st->sz.h, st->app_sz.w, st->app_sz.h);
+ }
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ err = init_read(st, fmt.fmt.pix.sizeimage);
+ break;
+
+ case IO_METHOD_MMAP:
+ err = init_mmap(st, dev_name);
+ break;
+
+ default:
+ warning("v4l2: unknown io: %d\n", st->io);
+ err = EINVAL;
+ break;
+ }
+
+ if (err)
+ return err;
+
+ pix = (char *)&fmt.fmt.pix.pixelformat;
+
+ if (V4L2_PIX_FMT_YUV420 != fmt.fmt.pix.pixelformat) {
+ warning("v4l2: %s: expected YUV420 got %c%c%c%c\n", dev_name,
+ pix[0], pix[1], pix[2], pix[3]);
+ return ENODEV;
+ }
+
+ info("v4l2: %s: found valid V4L2 device (%u x %u) pixfmt=%c%c%c%c\n",
+ dev_name, fmt.fmt.pix.width, fmt.fmt.pix.height,
+ pix[0], pix[1], pix[2], pix[3]);
+
+ return 0;
+}
+
+
+static void stop_capturing(struct vidsrc_st *st)
+{
+ enum v4l2_buf_type type;
+
+ if (st->fd < 0)
+ return;
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ /* Nothing to do. */
+ break;
+
+ case IO_METHOD_MMAP:
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ if (-1 == xioctl(st->fd, VIDIOC_STREAMOFF, &type))
+ warning("v4l2: VIDIOC_STREAMOFF\n");
+ break;
+ }
+}
+
+
+static void uninit_device(struct vidsrc_st *st)
+{
+ unsigned int i;
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ if (st->buffers)
+ free(st->buffers[0].start);
+ break;
+
+ case IO_METHOD_MMAP:
+ for (i=0; i<st->n_buffers; ++i)
+ v4l2_munmap(st->buffers[i].start,
+ st->buffers[i].length);
+ break;
+ }
+
+ free(st->buffers);
+}
+
+
+static int start_capturing(struct vidsrc_st *st, int fd)
+{
+ unsigned int i;
+ enum v4l2_buf_type type;
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ /* Nothing to do. */
+ break;
+
+ case IO_METHOD_MMAP:
+ for (i = 0; i < st->n_buffers; ++i) {
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
+ return errno;
+ }
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
+ return errno;
+
+ break;
+ }
+
+ return 0;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, VID_FMT_YUV420P, &st->sz, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static int read_frame(struct vidsrc_st *st)
+{
+ struct v4l2_buffer buf;
+ ssize_t n;
+
+ switch (st->io) {
+
+ case IO_METHOD_READ:
+ n = v4l2_read(st->fd, st->mb->buf, st->mb->size);
+ if (-1 == n) {
+ switch (errno) {
+
+ case EAGAIN:
+ return 0;
+
+ case EIO:
+ /* Could ignore EIO, see spec. */
+
+ /* fall through */
+
+ default:
+ warning("v4l2: read error: %m\n", errno);
+ BREAKPOINT;
+ return errno;
+ }
+ }
+
+ call_frame_handler(st, st->mb->buf);
+ break;
+
+ case IO_METHOD_MMAP:
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+
+ if (-1 == xioctl (st->fd, VIDIOC_DQBUF, &buf)) {
+ switch (errno) {
+
+ case EAGAIN:
+ return 0;
+
+ case EIO:
+ /* Could ignore EIO, see spec. */
+
+ /* fall through */
+
+ default:
+ warning("v4l2: VIDIOC_DQBUF: %m\n", errno);
+ return errno;
+ }
+ }
+
+ if (buf.index >= st->n_buffers) {
+ warning("v4l2: index >= n_buffers\n");
+ }
+
+ call_frame_handler(st, st->buffers[buf.index].start);
+
+ if (-1 == xioctl (st->fd, VIDIOC_QBUF, &buf)) {
+ warning("v4l2: VIDIOC_QBUF\n");
+ return errno;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+static int vd_open(struct vidsrc_st *st, const char *device)
+{
+ /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open
+ * the video device.
+ */
+ st->fd = v4l2_open(device, O_RDWR);
+ if (st->fd < 0) {
+ warning("v4l2: open %s: %m\n", device, errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ stop_capturing(st);
+ uninit_device(st);
+
+ if (st->fd >= 0)
+ v4l2_close(st->fd);
+
+ mem_deref(st->mb);
+ mem_deref(st->vs);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ int err;
+
+ while (st->run) {
+ err = read_frame(st);
+ if (err) {
+ warning("v4l2: read_frame: %m\n", err);
+ }
+ }
+
+ return NULL;
+}
+
+
+static int alloc(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ if (!str_isset(dev))
+ dev = "/dev/video0";
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = mem_ref(vs);
+ st->fd = -1;
+ st->io = IO_METHOD_MMAP;
+
+ st->app_sz = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = vd_open(st, dev);
+ if (err)
+ goto out;
+
+ /* Try Video4Linux 2 first .. */
+ err = v4l2_init_device(st, dev);
+ if (err)
+ goto out;
+
+ get_video_input(st);
+
+ st->mb = mbuf_alloc(st->app_sz.w * st->app_sz.h * 3 / 2);
+ if (!st->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = start_capturing(st, st->fd);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int v4l_init(void)
+{
+ return vidsrc_register(&vidsrc, "v4l2", alloc, NULL);
+}
+
+
+static int v4l_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2) = {
+ "v4l2",
+ "vidsrc",
+ v4l_init,
+ v4l_close
+};
diff --git a/modules/vidbridge/disp.c b/modules/vidbridge/disp.c
new file mode 100644
index 0000000..bce9cff
--- /dev/null
+++ b/modules/vidbridge/disp.c
@@ -0,0 +1,95 @@
+/**
+ * @file vidbridge/disp.c Video bridge -- display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ if (st->vidsrc)
+ st->vidsrc->vidisp = NULL;
+
+ list_unlink(&st->le);
+ mem_deref(st->device);
+ mem_deref(st->vd);
+}
+
+
+int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+ (void)prm;
+ (void)resizeh;
+ (void)arg;
+
+ if (!stp || !vd || !dev)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = mem_ref(vd);
+
+ err = str_dup(&st->device, dev);
+ if (err)
+ goto out;
+
+ /* find the vidsrc with the same device-name */
+ st->vidsrc = vidbridge_src_find(dev);
+ if (st->vidsrc) {
+ st->vidsrc->vidisp = st;
+ }
+
+ hash_append(ht_disp, hash_joaat_str(dev), &st->le, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static bool list_apply_handler(struct le *le, void *arg)
+{
+ struct vidisp_st *st = le->data;
+
+ return 0 == str_cmp(st->device, arg);
+}
+
+
+int vidbridge_disp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ int err = 0;
+ (void)title;
+
+ if (st->vidsrc)
+ vidbridge_src_input(st->vidsrc, frame);
+ else {
+ debug("vidbridge: display: dropping frame (%u x %u)\n",
+ frame->size.w, frame->size.h);
+ }
+
+ return err;
+}
+
+
+struct vidisp_st *vidbridge_disp_find(const char *device)
+{
+ return list_ledata(hash_lookup(ht_disp, hash_joaat_str(device),
+ list_apply_handler, (void *)device));
+}
diff --git a/modules/vidbridge/module.mk b/modules/vidbridge/module.mk
new file mode 100644
index 0000000..13000b6
--- /dev/null
+++ b/modules/vidbridge/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vidbridge
+$(MOD)_SRCS += vidbridge.c src.c disp.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/vidbridge/src.c b/modules/vidbridge/src.c
new file mode 100644
index 0000000..ff9bf70
--- /dev/null
+++ b/modules/vidbridge/src.c
@@ -0,0 +1,94 @@
+/**
+ * @file vidbridge/src.c Video bridge -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->vidisp)
+ st->vidisp->vidsrc = NULL;
+
+ list_unlink(&st->le);
+ mem_deref(st->device);
+ mem_deref(st->vs);
+}
+
+
+int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = mem_ref(vs);
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = str_dup(&st->device, dev);
+ if (err)
+ goto out;
+
+ /* find a vidisp device with same name */
+ st->vidisp = vidbridge_disp_find(dev);
+ if (st->vidisp) {
+ st->vidisp->vidsrc = st;
+ }
+
+ hash_append(ht_src, hash_joaat_str(dev), &st->le, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static bool list_apply_handler(struct le *le, void *arg)
+{
+ struct vidsrc_st *st = le->data;
+
+ return 0 == str_cmp(st->device, arg);
+}
+
+
+struct vidsrc_st *vidbridge_src_find(const char *device)
+{
+ return list_ledata(hash_lookup(ht_src, hash_joaat_str(device),
+ list_apply_handler, (void *)device));
+}
+
+
+void vidbridge_src_input(const struct vidsrc_st *st,
+ const struct vidframe *frame)
+{
+ if (!st || !frame)
+ return;
+
+ if (st->frameh)
+ st->frameh((struct vidframe *)frame, st->arg);
+}
diff --git a/modules/vidbridge/vidbridge.c b/modules/vidbridge/vidbridge.c
new file mode 100644
index 0000000..69aa41f
--- /dev/null
+++ b/modules/vidbridge/vidbridge.c
@@ -0,0 +1,58 @@
+/**
+ * @file vidbridge.c Video bridge
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+static struct vidisp *vidisp;
+static struct vidsrc *vidsrc;
+
+struct hash *ht_src;
+struct hash *ht_disp;
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = hash_alloc(&ht_src, 32);
+ err |= hash_alloc(&ht_disp, 32);
+ if (err)
+ return err;
+
+ err = vidisp_register(&vidisp, "vidbridge", vidbridge_disp_alloc,
+ NULL, vidbridge_disp_display, 0);
+ if (err)
+ return err;
+
+ err = vidsrc_register(&vidsrc, "vidbridge", vidbridge_src_alloc, NULL);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ vidisp = mem_deref(vidisp);
+
+ ht_src = mem_deref(ht_src);
+ ht_disp = mem_deref(ht_disp);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vidbridge) = {
+ "vidbridge",
+ "video",
+ module_init,
+ module_close,
+};
diff --git a/modules/vidbridge/vidbridge.h b/modules/vidbridge/vidbridge.h
new file mode 100644
index 0000000..15fcdfc
--- /dev/null
+++ b/modules/vidbridge/vidbridge.h
@@ -0,0 +1,47 @@
+/**
+ * @file vidbridge.h Video bridge -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct vidsrc_st {
+ struct vidsrc *vs; /* inheritance (1st) */
+
+ struct le le;
+ struct vidisp_st *vidisp;
+ char *device;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+struct vidisp_st {
+ struct vidisp *vd; /* inheritance (1st) */
+
+ struct le le;
+ struct vidsrc_st *vidsrc;
+ char *device;
+};
+
+
+extern struct hash *ht_src;
+extern struct hash *ht_disp;
+
+
+int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+int vidbridge_disp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+struct vidisp_st *vidbridge_disp_find(const char *device);
+
+
+int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg);
+struct vidsrc_st *vidbridge_src_find(const char *device);
+void vidbridge_src_input(const struct vidsrc_st *st,
+ const struct vidframe *frame);
diff --git a/modules/vidloop/module.mk b/modules/vidloop/module.mk
new file mode 100644
index 0000000..4775ef1
--- /dev/null
+++ b/modules/vidloop/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vidloop
+$(MOD)_SRCS += vidloop.c
+
+include mk/mod.mk
diff --git a/modules/vidloop/vidloop.c b/modules/vidloop/vidloop.c
new file mode 100644
index 0000000..305a876
--- /dev/null
+++ b/modules/vidloop/vidloop.c
@@ -0,0 +1,392 @@
+/**
+ * @file vidloop.c Video loop
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/** Video Statistics */
+struct vstat {
+ uint64_t tsamp;
+ uint32_t frames;
+ size_t bytes;
+ uint32_t bitrate;
+ double efps;
+};
+
+
+/** Video loop */
+struct video_loop {
+ const struct vidcodec *vc;
+ struct config_video cfg;
+ struct videnc_state *enc;
+ struct viddec_state *dec;
+ struct vidisp_st *vidisp;
+ struct vidsrc_st *vsrc;
+ struct list filtencl;
+ struct list filtdecl;
+ struct vstat stat;
+ struct tmr tmr_bw;
+ uint16_t seq;
+};
+
+
+static struct video_loop *gvl;
+
+
+static int display(struct video_loop *vl, struct vidframe *frame)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!vidframe_isvalid(frame))
+ return 0;
+
+ /* Process video frame through all Video Filters */
+ for (le = vl->filtdecl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf->dech)
+ err |= st->vf->dech(st, frame);
+ }
+
+ /* display frame */
+ (void)vidisp_display(vl->vidisp, "Video Loop", frame);
+
+ return err;
+}
+
+
+static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len, void *arg)
+{
+ struct video_loop *vl = arg;
+ struct vidframe frame;
+ struct mbuf *mb;
+ int err = 0;
+
+ mb = mbuf_alloc(hdr_len + pld_len);
+ if (!mb)
+ return ENOMEM;
+
+ if (hdr_len)
+ mbuf_write_mem(mb, hdr, hdr_len);
+ mbuf_write_mem(mb, pld, pld_len);
+
+ mb->pos = 0;
+
+ vl->stat.bytes += mbuf_get_left(mb);
+
+ /* decode */
+ frame.data[0] = NULL;
+ if (vl->dec) {
+ err = vl->vc->dech(vl->dec, &frame, marker, vl->seq++, mb);
+ if (err) {
+ warning("vidloop: codec decode: %m\n", err);
+ goto out;
+ }
+ }
+
+ display(vl, &frame);
+
+ out:
+ mem_deref(mb);
+
+ return 0;
+}
+
+
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct video_loop *vl = arg;
+ struct vidframe *f2 = NULL;
+ struct le *le;
+ int err = 0;
+
+ ++vl->stat.frames;
+
+ if (frame->fmt != VID_FMT_YUV420P) {
+
+ if (vidframe_alloc(&f2, VID_FMT_YUV420P, &frame->size))
+ return;
+
+ vidconv(f2, frame, 0);
+
+ frame = f2;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vl->filtencl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ if (vl->enc) {
+ (void)vl->vc->ench(vl->enc, false, frame,
+ packet_handler, vl);
+ }
+ else {
+ vl->stat.bytes += vidframe_size(frame->fmt, &frame->size);
+ (void)display(vl, frame);
+ }
+
+ mem_deref(f2);
+}
+
+
+static void vidloop_destructor(void *arg)
+{
+ struct video_loop *vl = arg;
+
+ tmr_cancel(&vl->tmr_bw);
+ mem_deref(vl->vsrc);
+ mem_deref(vl->vidisp);
+ mem_deref(vl->enc);
+ mem_deref(vl->dec);
+ list_flush(&vl->filtencl);
+ list_flush(&vl->filtdecl);
+}
+
+
+static int enable_codec(struct video_loop *vl)
+{
+ struct videnc_param prm;
+ int err;
+
+ prm.fps = vl->cfg.fps;
+ prm.pktsize = 1024;
+ prm.bitrate = vl->cfg.bitrate;
+ prm.max_fs = -1;
+
+ /* Use the first video codec */
+ vl->vc = vidcodec_find(NULL, NULL);
+ if (!vl->vc)
+ return ENOENT;
+
+ err = vl->vc->encupdh(&vl->enc, vl->vc, &prm, NULL);
+ if (err) {
+ warning("vidloop: update encoder failed: %m\n", err);
+ return err;
+ }
+
+ if (vl->vc->decupdh) {
+ err = vl->vc->decupdh(&vl->dec, vl->vc, NULL);
+ if (err) {
+ warning("vidloop: update decoder failed: %m\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+static void disable_codec(struct video_loop *vl)
+{
+ vl->enc = mem_deref(vl->enc);
+ vl->dec = mem_deref(vl->dec);
+ vl->vc = NULL;
+}
+
+
+static void print_status(struct video_loop *vl)
+{
+ (void)re_fprintf(stderr, "\rstatus: EFPS=%.1f %u kbit/s \r",
+ vl->stat.efps, vl->stat.bitrate);
+}
+
+
+static void calc_bitrate(struct video_loop *vl)
+{
+ const uint64_t now = tmr_jiffies();
+
+ if (now > vl->stat.tsamp) {
+
+ const uint32_t dur = (uint32_t)(now - vl->stat.tsamp);
+
+ vl->stat.efps = 1000.0f * vl->stat.frames / dur;
+
+ vl->stat.bitrate = (uint32_t) (8 * vl->stat.bytes / dur);
+ }
+
+ vl->stat.frames = 0;
+ vl->stat.bytes = 0;
+ vl->stat.tsamp = now;
+}
+
+
+static void timeout_bw(void *arg)
+{
+ struct video_loop *vl = arg;
+
+ tmr_start(&vl->tmr_bw, 5000, timeout_bw, vl);
+
+ calc_bitrate(vl);
+ print_status(vl);
+}
+
+
+static int vsrc_reopen(struct video_loop *vl, const struct vidsz *sz)
+{
+ struct vidsrc_prm prm;
+ int err;
+
+ info("vidloop: %s,%s: open video source: %u x %u\n",
+ vl->cfg.src_mod, vl->cfg.src_dev,
+ sz->w, sz->h);
+
+ prm.orient = VIDORIENT_PORTRAIT;
+ prm.fps = vl->cfg.fps;
+
+ vl->vsrc = mem_deref(vl->vsrc);
+ err = vidsrc_alloc(&vl->vsrc, vl->cfg.src_mod, NULL, &prm, sz,
+ NULL, vl->cfg.src_dev, vidsrc_frame_handler,
+ NULL, vl);
+ if (err) {
+ warning("x11: vidsrc %s failed: %m\n", vl->cfg.src_dev, err);
+ }
+
+ return err;
+}
+
+
+static int video_loop_alloc(struct video_loop **vlp, const struct vidsz *size)
+{
+ struct video_loop *vl;
+ struct config *cfg;
+ struct le *le;
+ int err = 0;
+
+ cfg = conf_config();
+ if (!cfg)
+ return EINVAL;
+
+ vl = mem_zalloc(sizeof(*vl), vidloop_destructor);
+ if (!vl)
+ return ENOMEM;
+
+ vl->cfg = cfg->video;
+ tmr_init(&vl->tmr_bw);
+
+ /* Video filters */
+ for (le = list_head(vidfilt_list()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ info("vidloop: added video-filter `%s'\n", vf->name);
+
+ err |= vidfilt_enc_append(&vl->filtencl, &ctx, vf);
+ err |= vidfilt_dec_append(&vl->filtdecl, &ctx, vf);
+ if (err) {
+ warning("vidloop: vidfilt error: %m\n", err);
+ }
+ }
+
+ err = vsrc_reopen(vl, size);
+ if (err)
+ goto out;
+
+ err = vidisp_alloc(&vl->vidisp, NULL, NULL, NULL, NULL, vl);
+ if (err) {
+ warning("vidloop: video display failed: %m\n", err);
+ goto out;
+ }
+
+ tmr_start(&vl->tmr_bw, 1000, timeout_bw, vl);
+
+ out:
+ if (err)
+ mem_deref(vl);
+ else
+ *vlp = vl;
+
+ return err;
+}
+
+
+/**
+ * Start the video loop (for testing)
+ */
+static int vidloop_start(struct re_printf *pf, void *arg)
+{
+ struct vidsz size;
+ struct config *cfg = conf_config();
+ int err = 0;
+
+ (void)arg;
+
+ size.w = cfg->video.width;
+ size.h = cfg->video.height;
+
+ if (gvl) {
+ if (gvl->vc)
+ disable_codec(gvl);
+ else
+ (void)enable_codec(gvl);
+
+ (void)re_hprintf(pf, "%sabled codec: %s\n",
+ gvl->vc ? "En" : "Dis",
+ gvl->vc ? gvl->vc->name : "");
+ }
+ else {
+ (void)re_hprintf(pf, "Enable video-loop on %s,%s: %u x %u\n",
+ cfg->video.src_mod, cfg->video.src_dev,
+ size.w, size.h);
+
+ err = video_loop_alloc(&gvl, &size);
+ if (err) {
+ warning("vidloop: alloc: %m\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+static int vidloop_stop(struct re_printf *pf, void *arg)
+{
+ (void)arg;
+
+ if (gvl)
+ (void)re_hprintf(pf, "Disable video-loop\n");
+ gvl = mem_deref(gvl);
+ return 0;
+}
+
+
+static const struct cmd cmdv[] = {
+ {'v', 0, "Start video-loop", vidloop_start },
+ {'V', 0, "Stop video-loop", vidloop_stop },
+};
+
+
+static int module_init(void)
+{
+ return cmd_register(cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ vidloop_stop(NULL, NULL);
+ cmd_unregister(cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = {
+ "vidloop",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/vpx/decode.c b/modules/vpx/decode.c
new file mode 100644
index 0000000..f441619
--- /dev/null
+++ b/modules/vpx/decode.c
@@ -0,0 +1,273 @@
+/**
+ * @file vpx/decode.c VP8 Decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_decoder.h>
+#include <vpx/vp8dx.h>
+#include "vp8.h"
+
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+struct hdr {
+ unsigned x:1;
+ unsigned noref:1;
+ unsigned start:1;
+ unsigned partid:4;
+ /* extension fields */
+ unsigned i:1;
+ unsigned l:1;
+ unsigned t:1;
+ unsigned k:1;
+ uint16_t picid;
+ uint8_t tl0picidx;
+ unsigned tid:2;
+ unsigned y:1;
+ unsigned keyidx:5;
+};
+
+struct viddec_state {
+ vpx_codec_ctx_t ctx;
+ struct mbuf *mb;
+ bool ctxup;
+ bool started;
+ uint16_t seq;
+};
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->ctxup)
+ vpx_codec_destroy(&vds->ctx);
+
+ mem_deref(vds->mb);
+}
+
+
+int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ vpx_codec_err_t res;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ vds->mb = mbuf_alloc(1024);
+ if (!vds->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp8_dx_algo, NULL, 0);
+ if (res) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->ctxup = true;
+
+ out:
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->x = v>>7 & 0x1;
+ hdr->noref = v>>5 & 0x1;
+ hdr->start = v>>4 & 0x1;
+ hdr->partid = v & 0x07;
+
+ if (hdr->x) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->i = v>>7 & 0x1;
+ hdr->l = v>>6 & 0x1;
+ hdr->t = v>>5 & 0x1;
+ hdr->k = v>>4 & 0x1;
+ }
+
+ if (hdr->i) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ if (v>>7 & 0x1) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->picid = (v & 0x7f)<<8;
+ hdr->picid += mbuf_read_u8(mb);
+ }
+ else {
+ hdr->picid = v & 0x7f;
+ }
+ }
+
+ if (hdr->l) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->tl0picidx = mbuf_read_u8(mb);
+ }
+
+ if (hdr->t || hdr->k) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->tid = v>>6 & 0x3;
+ hdr->y = v>>5 & 0x1;
+ hdr->keyidx = v & 0x1f;
+ }
+
+ return 0;
+}
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+int vp8_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool marker, uint16_t seq, struct mbuf *mb)
+{
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t *img;
+ struct hdr hdr;
+ int err, i;
+
+ if (!vds || !frame || !mb)
+ return EINVAL;
+
+ err = hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+#if 0
+ debug("vp8: header: x=%u noref=%u start=%u partid=%u "
+ "i=%u l=%u t=%u k=%u "
+ "picid=%u tl0picidx=%u tid=%u y=%u keyidx=%u\n",
+ hdr.x, hdr.noref, hdr.start, hdr.partid,
+ hdr.i, hdr.l, hdr.t, hdr.k,
+ hdr.picid, hdr.tl0picidx, hdr.tid, hdr.y, hdr.keyidx);
+#endif
+
+ if (hdr.start && hdr.partid == 0) {
+
+ mbuf_rewind(vds->mb);
+ vds->started = true;
+ }
+ else {
+ if (!vds->started)
+ return 0;
+
+ if (seq_diff(vds->seq, seq) != 1) {
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+ return 0;
+ }
+ }
+
+ vds->seq = seq;
+
+ err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (vds->mb->end > DECODE_MAXSZ) {
+ warning("vp8: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ res = vpx_codec_decode(&vds->ctx, vds->mb->buf,
+ (unsigned int)vds->mb->end, NULL, 1);
+ if (res) {
+ debug("vp8: decode error: %s\n", vpx_codec_err_to_string(res));
+ err = EPROTO;
+ goto out;
+ }
+
+ img = vpx_codec_get_frame(&vds->ctx, &iter);
+ if (!img) {
+ debug("vp8: no picture\n");
+ goto out;
+ }
+
+ if (img->fmt != VPX_IMG_FMT_I420) {
+ warning("vp8: bad pixel format (%i)\n", img->fmt);
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = img->planes[i];
+ frame->linesize[i] = img->stride[i];
+ }
+
+ frame->size.w = img->d_w;
+ frame->size.h = img->d_h;
+ frame->fmt = VID_FMT_YUV420P;
+
+ out:
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+
+ return err;
+}
diff --git a/modules/vpx/encode.c b/modules/vpx/encode.c
new file mode 100644
index 0000000..82f6656
--- /dev/null
+++ b/modules/vpx/encode.c
@@ -0,0 +1,253 @@
+/**
+ * @file vpx/encode.c VP8 Encode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_encoder.h>
+#include <vpx/vp8cx.h>
+#include "vp8.h"
+
+
+enum {
+ HDR_SIZE = 4,
+};
+
+
+struct videnc_state {
+ vpx_codec_ctx_t ctx;
+ struct vidsz size;
+ vpx_codec_pts_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ bool ctxup;
+ uint16_t picid;
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *ves = arg;
+
+ if (ves->ctxup)
+ vpx_codec_destroy(&ves->ctx);
+}
+
+
+int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp)
+{
+ const struct vp8_vidcodec *vp8 = (struct vp8_vidcodec *)vc;
+ struct videnc_state *ves;
+ uint32_t max_fs;
+ (void)vp8;
+
+ if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1))
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ ves->picid = rand_u16();
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->ctxup && (ves->bitrate != prm->bitrate ||
+ ves->fps != prm->fps)) {
+
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+
+ max_fs = vp8_max_fs(fmtp);
+ if (max_fs > 0)
+ prm->max_fs = max_fs * 256;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *ves, const struct vidsz *size)
+{
+ vpx_codec_enc_cfg_t cfg;
+ vpx_codec_err_t res;
+
+ res = vpx_codec_enc_config_default(&vpx_codec_vp8_cx_algo, &cfg, 0);
+ if (res)
+ return EPROTO;
+
+ cfg.g_profile = 2;
+ cfg.g_w = size->w;
+ cfg.g_h = size->h;
+ cfg.g_timebase.num = 1;
+ cfg.g_timebase.den = ves->fps;
+ cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
+ cfg.g_pass = VPX_RC_ONE_PASS;
+ cfg.g_lag_in_frames = 0;
+ cfg.rc_end_usage = VPX_VBR;
+ cfg.rc_target_bitrate = ves->bitrate;
+ cfg.kf_mode = VPX_KF_AUTO;
+
+ if (ves->ctxup) {
+ debug("vp8: re-opening encoder\n");
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+
+ res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp8_cx_algo, &cfg,
+ VPX_CODEC_USE_OUTPUT_PARTITION);
+ if (res) {
+ warning("vp8: enc init: %s\n", vpx_codec_err_to_string(res));
+ return EPROTO;
+ }
+
+ ves->ctxup = true;
+
+ res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 16);
+ if (res) {
+ warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+
+ res = vpx_codec_control(&ves->ctx, VP8E_SET_NOISE_SENSITIVITY, 0);
+ if (res) {
+ warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+
+ return 0;
+}
+
+
+static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool noref, bool start,
+ uint8_t partid, uint16_t picid)
+{
+ hdr[0] = 1<<7 | noref<<5 | start<<4 | (partid & 0x7);
+ hdr[1] = 1<<7;
+ hdr[2] = 1<<7 | (picid>>8 & 0x7f);
+ hdr[3] = picid & 0xff;
+}
+
+
+static inline int packetize(bool marker, const uint8_t *buf, size_t len,
+ size_t maxlen, bool noref, uint8_t partid,
+ uint16_t picid, videnc_packet_h *pkth, void *arg)
+{
+ uint8_t hdr[HDR_SIZE];
+ bool start = true;
+ int err = 0;
+
+ maxlen -= sizeof(hdr);
+
+ while (len > maxlen) {
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(false, hdr, sizeof(hdr), buf, maxlen, arg);
+
+ buf += maxlen;
+ len -= maxlen;
+ start = false;
+ }
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(marker, hdr, sizeof(hdr), buf, len, arg);
+
+ return err;
+}
+
+
+int vp8_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame,
+ videnc_packet_h *pkth, void *arg)
+{
+ vpx_enc_frame_flags_t flags = 0;
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t img;
+ int err, i;
+
+ if (!ves || !frame || !pkth || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) {
+
+ err = open_encoder(ves, &frame->size);
+ if (err)
+ return err;
+
+ ves->size = frame->size;
+ }
+
+ if (update) {
+ /* debug("vp8: picture update\n"); */
+ flags |= VPX_EFLAG_FORCE_KF;
+ }
+
+ memset(&img, 0, sizeof(img));
+
+ img.fmt = VPX_IMG_FMT_I420;
+ img.w = img.d_w = frame->size.w;
+ img.h = img.d_h = frame->size.h;
+
+ for (i=0; i<4; i++) {
+ img.stride[i] = frame->linesize[i];
+ img.planes[i] = frame->data[i];
+ }
+
+ res = vpx_codec_encode(&ves->ctx, &img, ves->pts++, 1,
+ flags, VPX_DL_REALTIME);
+ if (res) {
+ warning("vp8: enc error: %s\n", vpx_codec_err_to_string(res));
+ return ENOMEM;
+ }
+
+ ++ves->picid;
+
+ for (;;) {
+ bool keyframe = false, marker = true;
+ const vpx_codec_cx_pkt_t *pkt;
+ uint8_t partid = 0;
+
+ pkt = vpx_codec_get_cx_data(&ves->ctx, &iter);
+ if (!pkt)
+ break;
+
+ if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
+ continue;
+
+ if (pkt->data.frame.flags & VPX_FRAME_IS_KEY)
+ keyframe = true;
+
+ if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT)
+ marker = false;
+
+ if (pkt->data.frame.partition_id >= 0)
+ partid = pkt->data.frame.partition_id;
+
+ err = packetize(marker,
+ pkt->data.frame.buf,
+ pkt->data.frame.sz,
+ ves->pktsize, !keyframe, partid, ves->picid,
+ pkth, arg);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
diff --git a/modules/vpx/module.mk b/modules/vpx/module.mk
new file mode 100644
index 0000000..bcd8c9d
--- /dev/null
+++ b/modules/vpx/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vpx
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += vpx.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_LFLAGS += -lvpx
+
+include mk/mod.mk
diff --git a/modules/vpx/sdp.c b/modules/vpx/sdp.c
new file mode 100644
index 0000000..21ed9d6
--- /dev/null
+++ b/modules/vpx/sdp.c
@@ -0,0 +1,39 @@
+/**
+ * @file vpx/sdp.c VP8 SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "vp8.h"
+
+
+uint32_t vp8_max_fs(const char *fmtp)
+{
+ struct pl pl, max_fs;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "max-fs", &max_fs))
+ return pl_u32(&max_fs);
+
+ return 0;
+}
+
+
+int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ const struct vp8_vidcodec *vp8 = arg;
+ (void)offer;
+
+ if (!mb || !fmt || !vp8 || !vp8->max_fs)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n",
+ fmt->id, vp8->max_fs);
+}
diff --git a/modules/vpx/vp8.h b/modules/vpx/vp8.h
new file mode 100644
index 0000000..fb704c5
--- /dev/null
+++ b/modules/vpx/vp8.h
@@ -0,0 +1,30 @@
+/**
+ * @file vp8.h Private VP8 Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct vp8_vidcodec {
+ struct vidcodec vc;
+ uint32_t max_fs;
+};
+
+/* Encode */
+int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp);
+int vp8_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame,
+ videnc_packet_h *pkth, void *arg);
+
+
+/* Decode */
+int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int vp8_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool marker, uint16_t seq, struct mbuf *mb);
+
+
+/* SDP */
+uint32_t vp8_max_fs(const char *fmtp);
+int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
diff --git a/modules/vpx/vpx.c b/modules/vpx/vpx.c
new file mode 100644
index 0000000..4b492cf
--- /dev/null
+++ b/modules/vpx/vpx.c
@@ -0,0 +1,60 @@
+/**
+ * @file vpx.c VP8 video codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#define VPX_CODEC_DISABLE_COMPAT 1
+#define VPX_DISABLE_CTRL_TYPECHECKS 1
+#include <vpx/vpx_encoder.h>
+#include <vpx/vpx_decoder.h>
+#include <vpx/vp8cx.h>
+#include <vpx/vp8dx.h>
+#include "vp8.h"
+
+
+/*
+ * Experimental support for WebM VP8 video codec:
+ *
+ * http://www.webmproject.org/
+ *
+ * http://tools.ietf.org/html/draft-ietf-payload-vp8-08
+ */
+
+
+static struct vp8_vidcodec vpx = {
+ .vc = {
+ .name = "VP8",
+ .encupdh = vp8_encode_update,
+ .ench = vp8_encode,
+ .decupdh = vp8_decode_update,
+ .dech = vp8_decode,
+ .fmtp_ench = vp8_fmtp_enc,
+ },
+ .max_fs = 3600
+};
+
+
+static int module_init(void)
+{
+ vidcodec_register((struct vidcodec *)&vpx);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister((struct vidcodec *)&vpx);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vpx) = {
+ "vpx",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/vumeter/module.mk b/modules/vumeter/module.mk
new file mode 100644
index 0000000..caa8605
--- /dev/null
+++ b/modules/vumeter/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vumeter
+$(MOD)_SRCS += vumeter.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/vumeter/vumeter.c b/modules/vumeter/vumeter.c
new file mode 100644
index 0000000..1a17fa8
--- /dev/null
+++ b/modules/vumeter/vumeter.c
@@ -0,0 +1,198 @@
+/**
+ * @file vumeter.c VU-meter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+
+
+struct vumeter_enc {
+ struct aufilt_enc_st af; /* inheritance */
+ struct tmr tmr;
+ int16_t avg_rec;
+};
+
+struct vumeter_dec {
+ struct aufilt_dec_st af; /* inheritance */
+ struct tmr tmr;
+ int16_t avg_play;
+};
+
+
+static void enc_destructor(void *arg)
+{
+ struct vumeter_enc *st = arg;
+
+ list_unlink(&st->af.le);
+ tmr_cancel(&st->tmr);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct vumeter_dec *st = arg;
+
+ list_unlink(&st->af.le);
+ tmr_cancel(&st->tmr);
+}
+
+
+static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc)
+{
+ int32_t v = 0;
+ size_t i;
+
+ if (!sampv || !sampc)
+ return 0;
+
+ for (i=0; i<sampc; i++)
+ v += abs(sampv[i]);
+
+ return v/sampc;
+}
+
+
+static int audio_print_vu(struct re_printf *pf, int16_t *avg)
+{
+ char buf[16];
+ size_t res;
+
+ res = min(2 * sizeof(buf) * (*avg)/0x8000,
+ sizeof(buf)-1);
+
+ memset(buf, '=', res);
+ buf[res] = '\0';
+
+ return re_hprintf(pf, "[%-16s]", buf);
+}
+
+
+static void print_vumeter(int pos, int color, int value)
+{
+ /* move cursor to a fixed position */
+ re_fprintf(stderr, "\x1b[%dG", pos);
+
+ /* print VU-meter in Nice colors */
+ re_fprintf(stderr, " \x1b[%dm%H\x1b[;m\r",
+ color, audio_print_vu, &value);
+}
+
+
+static void enc_tmr_handler(void *arg)
+{
+ struct vumeter_enc *st = arg;
+
+ tmr_start(&st->tmr, 100, enc_tmr_handler, st);
+ print_vumeter(60, 31, st->avg_rec);
+}
+
+
+static void dec_tmr_handler(void *arg)
+{
+ struct vumeter_dec *st = arg;
+
+ tmr_start(&st->tmr, 100, dec_tmr_handler, st);
+ print_vumeter(80, 32, st->avg_play);
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_enc *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ *stp = (struct aufilt_enc_st *)st;
+
+ return 0;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_dec *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ *stp = (struct aufilt_dec_st *)st;
+
+ return 0;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_enc *vu = (struct vumeter_enc *)st;
+
+ vu->avg_rec = calc_avg_s16(sampv, *sampc);
+
+ if (!tmr_isrunning(&vu->tmr))
+ tmr_start(&vu->tmr, 1, enc_tmr_handler, vu);
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_dec *vu = (struct vumeter_dec *)st;
+
+ vu->avg_play = calc_avg_s16(sampv, *sampc);
+
+ if (!tmr_isrunning(&vu->tmr))
+ tmr_start(&vu->tmr, 1, dec_tmr_handler, vu);
+
+ return 0;
+}
+
+
+static struct aufilt vumeter = {
+ LE_INIT, "vumeter", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ aufilt_register(&vumeter);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&vumeter);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vumeter) = {
+ "vumeter",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/wincons/module.mk b/modules/wincons/module.mk
new file mode 100644
index 0000000..aa1746e
--- /dev/null
+++ b/modules/wincons/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := wincons
+$(MOD)_SRCS += wincons.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/wincons/wincons.c b/modules/wincons/wincons.c
new file mode 100644
index 0000000..6724fc7
--- /dev/null
+++ b/modules/wincons/wincons.c
@@ -0,0 +1,183 @@
+/**
+ * @file wincons.c Windows console input
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <winsock2.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/** Local constants */
+enum {
+ RELEASE_VAL = 250 /**< Key release value in [ms] */
+};
+
+struct ui_st {
+ struct ui *ui; /* base class */
+ struct tmr tmr;
+ struct mqueue *mq;
+ HANDLE hThread;
+ bool run;
+ ui_input_h *h;
+ void *arg;
+};
+
+
+static struct ui *wincons;
+
+
+static void destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ st->run = false;
+ CloseHandle(st->hThread);
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->mq);
+ mem_deref(st->ui);
+}
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM;
+}
+
+
+static void report_key(struct ui_st *ui, char key)
+{
+ struct re_printf pf;
+
+ pf.vph = print_handler;
+
+ if (ui->h)
+ ui->h(key, &pf, ui->arg);
+}
+
+
+static void timeout(void *arg)
+{
+ struct ui_st *st = arg;
+
+ /* Emulate key-release */
+ report_key(st, 0x00);
+}
+
+
+static DWORD WINAPI input_thread(LPVOID arg)
+{
+ struct ui_st *st = arg;
+
+ HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE );
+ DWORD mode;
+
+ /* Switch to raw mode */
+ GetConsoleMode(hstdin, &mode);
+ SetConsoleMode(hstdin, 0);
+
+ while (st->run) {
+
+ char buf[4];
+ DWORD i, count = 0;
+
+ ReadConsole(hstdin, buf, sizeof(buf), &count, NULL);
+
+ for (i=0; i<count; i++) {
+ int ch = buf[i];
+
+ if (ch == '\r')
+ ch = '\n';
+
+ /*
+ * The keys are read from a thread so we have
+ * to send them to the RE main event loop via
+ * a message queue
+ */
+ mqueue_push(st->mq, ch, 0);
+ }
+ }
+
+ /* Restore the console to its previous state */
+ SetConsoleMode(hstdin, mode);
+
+ return 0;
+}
+
+
+static void mqueue_handler(int id, void *data, void *arg)
+{
+ struct ui_st *st = arg;
+ (void)data;
+
+ tmr_start(&st->tmr, RELEASE_VAL, timeout, st);
+ report_key(st, id);
+}
+
+
+static int ui_alloc(struct ui_st **stp, struct ui_prm *prm,
+ ui_input_h *ih, void *arg)
+{
+ struct ui_st *st;
+ DWORD threadID;
+ int err = 0;
+ (void)prm;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ui = mem_ref(wincons);
+ st->h = ih;
+ st->arg = arg;
+
+ tmr_init(&st->tmr);
+
+ err = mqueue_alloc(&st->mq, mqueue_handler, st);
+ if (err)
+ goto out;
+
+ st->run = true;
+ st->hThread = CreateThread(NULL, 0, input_thread, st, 0, &threadID);
+ if (!st->hThread) {
+ st->run = false;
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return ui_register(&wincons, "wincons", ui_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ wincons = mem_deref(wincons);
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(wincons) = {
+ "wincons",
+ "ui",
+ module_init,
+ module_close
+};
diff --git a/modules/winwave/module.mk b/modules/winwave/module.mk
new file mode 100644
index 0000000..ba47da0
--- /dev/null
+++ b/modules/winwave/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := winwave
+$(MOD)_SRCS += winwave.c src.c play.c
+$(MOD)_LFLAGS += -lwinmm
+
+include mk/mod.mk
diff --git a/modules/winwave/play.c b/modules/winwave/play.c
new file mode 100644
index 0000000..1de2e1f
--- /dev/null
+++ b/modules/winwave/play.c
@@ -0,0 +1,223 @@
+/**
+ * @file winwave/play.c Windows sound driver -- playback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+#define DEBUG_MODULE "winwave"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#define WRITE_BUFFERS 4
+#define INC_WPOS(a) ((a) = (((a) + 1) % WRITE_BUFFERS))
+
+
+struct auplay_st {
+ struct auplay *ap; /* inheritance */
+ struct dspbuf bufs[WRITE_BUFFERS];
+ int pos;
+ HWAVEOUT waveout;
+ bool rdy;
+ size_t inuse;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+ int i;
+
+ st->wh = NULL;
+
+ /* Mark the device for closing, and wait for all the
+ * buffers to be returned by the driver
+ */
+ st->rdy = false;
+ while (st->inuse > 0)
+ Sleep(50);
+
+ waveOutClose(st->waveout);
+
+ for (i = 0; i < WRITE_BUFFERS; i++) {
+ waveOutUnprepareHeader(st->waveout, &st->bufs[i].wh,
+ sizeof(WAVEHDR));
+ mem_deref(st->bufs[i].mb);
+ }
+
+ mem_deref(st->ap);
+}
+
+
+static int dsp_write(struct auplay_st *st)
+{
+ MMRESULT res;
+ WAVEHDR *wh;
+ struct mbuf *mb;
+
+ if (!st->rdy)
+ return EINVAL;
+
+ wh = &st->bufs[st->pos].wh;
+ if (wh->dwFlags & WHDR_PREPARED) {
+ return EINVAL;
+ }
+ mb = st->bufs[st->pos].mb;
+ wh->lpData = (LPSTR)mb->buf;
+
+ if (st->wh) {
+ st->wh(mb->buf, mb->size, st->arg);
+ }
+
+ wh->dwBufferLength = mb->size;
+ wh->dwFlags = 0;
+ wh->dwUser = (DWORD_PTR) mb;
+
+ waveOutPrepareHeader(st->waveout, wh, sizeof(*wh));
+
+ INC_WPOS(st->pos);
+
+ res = waveOutWrite(st->waveout, wh, sizeof(*wh));
+ if (res != MMSYSERR_NOERROR)
+ DEBUG_WARNING("dsp_write: waveOutWrite: failed: %08x\n", res);
+ else
+ st->inuse++;
+
+ return 0;
+}
+
+
+static void CALLBACK waveOutCallback(HWAVEOUT hwo,
+ UINT uMsg,
+ DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2)
+{
+ struct auplay_st *st = (struct auplay_st *)dwInstance;
+ WAVEHDR *wh = (WAVEHDR *)dwParam1;
+
+ (void)hwo;
+ (void)dwParam2;
+
+ switch (uMsg) {
+
+ case WOM_OPEN:
+ st->rdy = true;
+ break;
+
+ case WOM_DONE:
+ /*LOCK();*/
+ waveOutUnprepareHeader(st->waveout, wh, sizeof(*wh));
+ /*UNLOCK();*/
+ st->inuse--;
+ dsp_write(st);
+ break;
+
+ case WOM_CLOSE:
+ st->rdy = false;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int write_stream_open(struct auplay_st *st,
+ const struct auplay_prm *prm)
+{
+ WAVEFORMATEX wfmt;
+ MMRESULT res;
+ uint32_t sampc;
+ int i;
+
+ /* Open an audio I/O stream. */
+ st->waveout = NULL;
+ st->pos = 0;
+ st->rdy = false;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ for (i = 0; i < WRITE_BUFFERS; i++) {
+ memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR));
+ st->bufs[i].mb = mbuf_alloc(2 * sampc);
+ }
+
+ wfmt.wFormatTag = WAVE_FORMAT_PCM;
+ wfmt.nChannels = prm->ch;
+ wfmt.nSamplesPerSec = prm->srate;
+ wfmt.wBitsPerSample = 16;
+ wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8;
+ wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
+ wfmt.cbSize = 0;
+
+ res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt,
+ (DWORD_PTR) waveOutCallback,
+ (DWORD_PTR) st,
+ CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT);
+ if (res != MMSYSERR_NOERROR) {
+ DEBUG_WARNING("waveOutOpen: failed %d\n", res);
+ return EINVAL;
+ }
+ waveOutClose(st->waveout);
+ res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt,
+ (DWORD_PTR) waveOutCallback,
+ (DWORD_PTR) st,
+ CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT);
+ if (res != MMSYSERR_NOERROR) {
+ DEBUG_WARNING("waveOutOpen: failed %d\n", res);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int i, err;
+ (void)device;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = mem_ref(ap);
+ st->wh = wh;
+ st->arg = arg;
+
+ prm->fmt = AUFMT_S16LE;
+
+ err = write_stream_open(st, prm);
+ if (err)
+ goto out;
+
+ /* The write runs at 100ms intervals
+ * prepare enough buffers to suite its needs
+ */
+ for (i = 0; i < 5; i++)
+ dsp_write(st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/winwave/src.c b/modules/winwave/src.c
new file mode 100644
index 0000000..1793bf3
--- /dev/null
+++ b/modules/winwave/src.c
@@ -0,0 +1,206 @@
+/**
+ * @file winwave/src.c Windows sound driver -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+#define DEBUG_MODULE "winwave"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#define READ_BUFFERS 4
+#define INC_RPOS(a) ((a) = (((a) + 1) % READ_BUFFERS))
+
+
+struct ausrc_st {
+ struct ausrc *as; /* inheritance */
+ struct dspbuf bufs[READ_BUFFERS];
+ int pos;
+ HWAVEIN wavein;
+ bool rdy;
+ size_t inuse;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+ int i;
+
+ st->rh = NULL;
+
+ waveInStop(st->wavein);
+ waveInReset(st->wavein);
+ waveInClose(st->wavein);
+
+ for (i = 0; i < READ_BUFFERS; i++) {
+ waveInUnprepareHeader(st->wavein, &st->bufs[i].wh,
+ sizeof(WAVEHDR));
+ mem_deref(st->bufs[i].mb);
+ }
+
+ mem_deref(st->as);
+}
+
+
+static int add_wave_in(struct ausrc_st *st)
+{
+ struct dspbuf *db = &st->bufs[st->pos];
+ WAVEHDR *wh = &db->wh;
+ MMRESULT res;
+
+ wh->lpData = (LPSTR)db->mb->buf;
+ wh->dwBufferLength = db->mb->size;
+ wh->dwBytesRecorded = 0;
+ wh->dwFlags = 0;
+ wh->dwUser = (DWORD_PTR)db->mb;
+
+ waveInPrepareHeader(st->wavein, wh, sizeof(*wh));
+ res = waveInAddBuffer(st->wavein, wh, sizeof(*wh));
+ if (res != MMSYSERR_NOERROR) {
+ DEBUG_WARNING("add_wave_in: waveInAddBuffer fail: %08x\n",
+ res);
+ return ENOMEM;
+ }
+
+ INC_RPOS(st->pos);
+
+ st->inuse++;
+
+ return 0;
+}
+
+
+static void CALLBACK waveInCallback(HWAVEOUT hwo,
+ UINT uMsg,
+ DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2)
+{
+ struct ausrc_st *st = (struct ausrc_st *)dwInstance;
+ WAVEHDR *wh = (WAVEHDR *)dwParam1;
+
+ (void)hwo;
+ (void)dwParam2;
+
+ if (!st->rh)
+ return;
+
+ switch (uMsg) {
+
+ case WIM_CLOSE:
+ st->rdy = false;
+ break;
+
+ case WIM_OPEN:
+ st->rdy = true;
+ break;
+
+ case WIM_DATA:
+ if (st->inuse < 3)
+ add_wave_in(st);
+
+ st->rh((uint8_t *)wh->lpData, wh->dwBytesRecorded, st->arg);
+
+ waveInUnprepareHeader(st->wavein, wh, sizeof(*wh));
+ st->inuse--;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm)
+{
+ WAVEFORMATEX wfmt;
+ MMRESULT res;
+ uint32_t sampc;
+ int i, err = 0;
+
+ /* Open an audio INPUT stream. */
+ st->wavein = NULL;
+ st->pos = 0;
+ st->rdy = false;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ for (i = 0; i < READ_BUFFERS; i++) {
+ memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR));
+ st->bufs[i].mb = mbuf_alloc(2 * sampc);
+ if (!st->bufs[i].mb)
+ return ENOMEM;
+ }
+
+ wfmt.wFormatTag = WAVE_FORMAT_PCM;
+ wfmt.nChannels = prm->ch;
+ wfmt.nSamplesPerSec = prm->srate;
+ wfmt.wBitsPerSample = 16;
+ wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8;
+ wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
+ wfmt.cbSize = 0;
+
+ res = waveInOpen(&st->wavein, WAVE_MAPPER, &wfmt,
+ (DWORD_PTR) waveInCallback,
+ (DWORD_PTR) st,
+ CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT);
+ if (res != MMSYSERR_NOERROR) {
+ DEBUG_WARNING("waveInOpen: failed %d\n", err);
+ return EINVAL;
+ }
+
+ /* Prepare enough IN buffers to suite at least 50ms of data */
+ for (i = 0; i < READ_BUFFERS; i++)
+ err |= add_wave_in(st);
+
+ waveInStart(st->wavein);
+
+ return err;
+}
+
+
+int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = mem_ref(as);
+ st->rh = rh;
+ st->arg = arg;
+
+ prm->fmt = AUFMT_S16LE;
+
+ err = read_stream_open(st, prm);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/winwave/winwave.c b/modules/winwave/winwave.c
new file mode 100644
index 0000000..141fbfc
--- /dev/null
+++ b/modules/winwave/winwave.c
@@ -0,0 +1,55 @@
+/**
+ * @file winwave.c Windows sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+#define DEBUG_MODULE "winwave"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+
+static int ww_init(void)
+{
+ int play_dev_count, src_dev_count;
+ int err;
+
+ play_dev_count = waveOutGetNumDevs();
+ src_dev_count = waveInGetNumDevs();
+
+ info("winwave: output devices: %d, input devices: %d\n",
+ play_dev_count, src_dev_count);
+
+ err = ausrc_register(&ausrc, "winwave", winwave_src_alloc);
+ err |= auplay_register(&auplay, "winwave", winwave_play_alloc);
+
+ return err;
+}
+
+
+static int ww_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(winwave) = {
+ "winwave",
+ "sound",
+ ww_init,
+ ww_close
+};
diff --git a/modules/winwave/winwave.h b/modules/winwave/winwave.h
new file mode 100644
index 0000000..ccccddf
--- /dev/null
+++ b/modules/winwave/winwave.h
@@ -0,0 +1,20 @@
+/**
+ * @file winwave.h Windows sound driver -- internal api
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct dspbuf {
+ WAVEHDR wh;
+ struct mbuf *mb;
+};
+
+
+int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
diff --git a/modules/x11/module.mk b/modules/x11/module.mk
new file mode 100644
index 0000000..ada2daa
--- /dev/null
+++ b/modules/x11/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := x11
+$(MOD)_SRCS += x11.c
+$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext
+
+include mk/mod.mk
diff --git a/modules/x11/x11.c b/modules/x11/x11.c
new file mode 100644
index 0000000..c75b6bd
--- /dev/null
+++ b/modules/x11/x11.c
@@ -0,0 +1,342 @@
+/**
+ * @file x11.c Video driver for X11
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifndef SOLARIS
+#define _XOPEN_SOURCE 1
+#endif
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+struct vidisp_st {
+ struct vidisp *vd; /**< Inheritance (1st) */
+ struct vidsz size; /**< Current size */
+
+ Display *disp;
+ Window win;
+ GC gc;
+ XImage *image;
+ XShmSegmentInfo shm;
+ bool xshmat;
+ bool internal;
+ enum vidfmt pixfmt;
+};
+
+
+static struct vidisp *vid; /**< X11 Video-display */
+
+static struct {
+ int shm_error;
+ int (*errorh) (Display *, XErrorEvent *);
+} x11;
+
+
+/* NOTE: Global handler */
+static int error_handler(Display *d, XErrorEvent *e)
+{
+ if (e->error_code == BadAccess)
+ x11.shm_error = 1;
+ else if (x11.errorh)
+ return x11.errorh(d, e);
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ if (st->image) {
+ st->image->data = NULL;
+ XDestroyImage(st->image);
+ }
+
+ if (st->gc)
+ XFreeGC(st->disp, st->gc);
+
+ if (st->xshmat)
+ XShmDetach(st->disp, &st->shm);
+
+ if (st->shm.shmaddr != (char *)-1)
+ shmdt(st->shm.shmaddr);
+
+ if (st->shm.shmid >= 0)
+ shmctl(st->shm.shmid, IPC_RMID, NULL);
+
+ if (st->disp) {
+ if (st->internal && st->win)
+ XDestroyWindow(st->disp, st->win);
+
+ XCloseDisplay(st->disp);
+ }
+
+ mem_deref(st->vd);
+}
+
+
+static int create_window(struct vidisp_st *st, const struct vidsz *sz)
+{
+ st->win = XCreateSimpleWindow(st->disp, DefaultRootWindow(st->disp),
+ 0, 0, sz->w, sz->h, 1, 0, 0);
+ if (!st->win) {
+ warning("x11: failed to create X window\n");
+ return ENOMEM;
+ }
+
+ XClearWindow(st->disp, st->win);
+ XMapRaised(st->disp, st->win);
+
+ return 0;
+}
+
+
+static int x11_reset(struct vidisp_st *st, const struct vidsz *sz)
+{
+ XWindowAttributes attrs;
+ XGCValues gcv;
+ size_t bufsz, pixsz;
+ int err = 0;
+
+ if (!XGetWindowAttributes(st->disp, st->win, &attrs)) {
+ warning("x11: cant't get window attributes\n");
+ return EINVAL;
+ }
+
+ switch (attrs.depth) {
+
+ case 24:
+ st->pixfmt = VID_FMT_RGB32;
+ pixsz = 4;
+ break;
+
+ case 16:
+ st->pixfmt = VID_FMT_RGB565;
+ pixsz = 2;
+ break;
+
+ case 15:
+ st->pixfmt = VID_FMT_RGB555;
+ pixsz = 2;
+ break;
+
+ default:
+ warning("x11: colordepth not supported: %d\n", attrs.depth);
+ return ENOSYS;
+ }
+
+ bufsz = sz->w * sz->h * pixsz;
+
+ if (st->image) {
+ XDestroyImage(st->image);
+ st->image = NULL;
+ }
+
+ if (st->xshmat)
+ XShmDetach(st->disp, &st->shm);
+
+ if (st->shm.shmaddr != (char *)-1)
+ shmdt(st->shm.shmaddr);
+
+ if (st->shm.shmid >= 0)
+ shmctl(st->shm.shmid, IPC_RMID, NULL);
+
+ st->shm.shmid = shmget(IPC_PRIVATE, bufsz, IPC_CREAT | 0777);
+ if (st->shm.shmid < 0) {
+ warning("x11: failed to allocate shared memory\n");
+ return ENOMEM;
+ }
+
+ st->shm.shmaddr = shmat(st->shm.shmid, NULL, 0);
+ if (st->shm.shmaddr == (char *)-1) {
+ warning("x11: failed to attach to shared memory\n");
+ return ENOMEM;
+ }
+
+ st->shm.readOnly = true;
+
+ x11.shm_error = 0;
+ x11.errorh = XSetErrorHandler(error_handler);
+
+ if (!XShmAttach(st->disp, &st->shm)) {
+ warning("x11: failed to attach X to shared memory\n");
+ return ENOMEM;
+ }
+
+ XSync(st->disp, False);
+ XSetErrorHandler(x11.errorh);
+
+ if (x11.shm_error)
+ info("x11: shared memory disabled\n");
+ else
+ st->xshmat = true;
+
+ gcv.graphics_exposures = false;
+
+ st->gc = XCreateGC(st->disp, st->win, GCGraphicsExposures, &gcv);
+ if (!st->gc) {
+ warning("x11: failed to create graphics context\n");
+ return ENOMEM;
+ }
+
+ if (st->xshmat) {
+ st->image = XShmCreateImage(st->disp, attrs.visual,
+ attrs.depth, ZPixmap,
+ st->shm.shmaddr, &st->shm,
+ sz->w, sz->h);
+ }
+ else {
+ st->image = XCreateImage(st->disp, attrs.visual,
+ attrs.depth, ZPixmap, 0,
+ st->shm.shmaddr,
+ sz->w, sz->h, 32, 0);
+
+ }
+ if (!st->image) {
+ warning("x11: Failed to create X image\n");
+ return ENOMEM;
+ }
+
+ XResizeWindow(st->disp, st->win, sz->w, sz->h);
+
+ st->size = *sz;
+
+ return err;
+}
+
+
+/* prm->view points to the XWINDOW ID */
+static int alloc(struct vidisp_st **stp, struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = mem_ref(vd);
+ st->shm.shmaddr = (char *)-1;
+
+ st->disp = XOpenDisplay(NULL);
+ if (!st->disp) {
+ warning("x11: could not open X display\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ /* Use provided view, or create our own */
+ if (prm && prm->view)
+ st->win = (Window)prm->view;
+ else
+ st->internal = true;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ struct vidframe frame_rgb;
+ int err = 0;
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ char capt[256];
+
+ if (st->size.w && st->size.h) {
+ info("x11: reset: %u x %u ---> %u x %u\n",
+ st->size.w, st->size.h,
+ frame->size.w, frame->size.h);
+ }
+
+ if (st->internal && !st->win)
+ err = create_window(st, &frame->size);
+
+ err |= x11_reset(st, &frame->size);
+ if (err)
+ return err;
+
+ if (title) {
+ re_snprintf(capt, sizeof(capt), "%s - %u x %u",
+ title, frame->size.w, frame->size.h);
+ }
+ else {
+ re_snprintf(capt, sizeof(capt), "%u x %u",
+ frame->size.w, frame->size.h);
+ }
+
+ XStoreName(st->disp, st->win, capt);
+ }
+
+ /* Convert from YUV420P to RGB */
+
+ vidframe_init_buf(&frame_rgb, st->pixfmt, &frame->size,
+ (uint8_t *)st->shm.shmaddr);
+
+ vidconv(&frame_rgb, frame, 0);
+
+ /* draw */
+ if (st->xshmat)
+ XShmPutImage(st->disp, st->win, st->gc, st->image,
+ 0, 0, 0, 0, st->size.w, st->size.h, false);
+ else
+ XPutImage(st->disp, st->win, st->gc, st->image,
+ 0, 0, 0, 0, st->size.w, st->size.h);
+
+ XSync(st->disp, false);
+
+ return err;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st)
+ return;
+
+ if (st->win)
+ XLowerWindow(st->disp, st->win);
+}
+
+
+static int module_init(void)
+{
+ return vidisp_register(&vid, "x11", alloc, NULL, display, hide);
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(x11) = {
+ "x11",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/x11grab/module.mk b/modules/x11grab/module.mk
new file mode 100644
index 0000000..5e9b2af
--- /dev/null
+++ b/modules/x11grab/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := x11grab
+$(MOD)_SRCS += x11grab.c
+$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext
+
+include mk/mod.mk
diff --git a/modules/x11grab/x11grab.c b/modules/x11grab/x11grab.c
new file mode 100644
index 0000000..93477a5
--- /dev/null
+++ b/modules/x11grab/x11grab.c
@@ -0,0 +1,218 @@
+/**
+ * @file x11grab.c X11 grabbing video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#ifndef SOLARIS
+#define _XOPEN_SOURCE 1
+#endif
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/*
+ * XXX: add option to select a specific X window and x,y offset
+ */
+
+
+struct vidsrc_st {
+ struct vidsrc *vs; /* inheritance */
+ Display *disp;
+ XImage *image;
+ pthread_t thread;
+ bool run;
+ int fps;
+ struct vidsz size;
+ enum vidfmt pixfmt;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static int x11grab_open(struct vidsrc_st *st, const struct vidsz *sz)
+{
+ int x = 0, y = 0;
+
+ st->disp = XOpenDisplay(NULL);
+ if (!st->disp) {
+ warning("x11grab: error opening display\n");
+ return ENODEV;
+ }
+
+ st->image = XGetImage(st->disp,
+ RootWindow(st->disp, DefaultScreen(st->disp)),
+ x, y, sz->w, sz->h, AllPlanes, ZPixmap);
+ if (!st->image) {
+ warning("x11grab: error creating Ximage\n");
+ return ENODEV;
+ }
+
+ switch (st->image->bits_per_pixel) {
+
+ case 32:
+ st->pixfmt = VID_FMT_RGB32;
+ break;
+
+ case 16:
+ st->pixfmt = (st->image->green_mask == 0x7e0)
+ ? VID_FMT_RGB565
+ : VID_FMT_RGB555;
+ break;
+
+ default:
+ warning("x11grab: not supported: bpp=%d\n",
+ st->image->bits_per_pixel);
+ return ENOSYS;
+ }
+
+ return 0;
+}
+
+
+static inline uint8_t *x11grab_read(struct vidsrc_st *st)
+{
+ const int x = 0, y = 0;
+ XImage *im;
+
+ im = XGetSubImage(st->disp,
+ RootWindow(st->disp, DefaultScreen(st->disp)),
+ x, y, st->size.w, st->size.h, AllPlanes, ZPixmap,
+ st->image, 0, 0);
+ if (!im)
+ return NULL;
+
+ return (uint8_t *)st->image->data;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, st->pixfmt, &st->size, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ uint64_t ts = tmr_jiffies();
+ uint8_t *buf;
+
+ while (st->run) {
+
+ if (tmr_jiffies() < ts) {
+ sys_msleep(4);
+ continue;
+ }
+
+ buf = x11grab_read(st);
+ if (!buf)
+ continue;
+
+ ts += (1000/st->fps);
+
+ call_frame_handler(st, buf);
+ }
+
+ return NULL;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->image)
+ XDestroyImage(st->image);
+
+ if (st->disp)
+ XCloseDisplay(st->disp);
+
+ mem_deref(st->vs);
+}
+
+
+static int alloc(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = mem_ref(vs);
+ st->size = *size;
+ st->fps = prm->fps;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = x11grab_open(st, size);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int x11grab_init(void)
+{
+ return vidsrc_register(&vidsrc, "x11grab", alloc, NULL);
+}
+
+
+static int x11grab_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(x11grab) = {
+ "x11grab",
+ "vidsrc",
+ x11grab_init,
+ x11grab_close
+};
diff --git a/modules/zrtp/module.mk b/modules/zrtp/module.mk
new file mode 100644
index 0000000..7bcc0ac
--- /dev/null
+++ b/modules/zrtp/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := zrtp
+$(MOD)_SRCS += zrtp.c
+$(MOD)_LFLAGS += -lzrtp -lbn
+CFLAGS += -I/usr/local/include/libzrtp
+
+include mk/mod.mk
diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c
new file mode 100644
index 0000000..1055483
--- /dev/null
+++ b/modules/zrtp/zrtp.c
@@ -0,0 +1,300 @@
+/**
+ * @file zrtp.c ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <zrtp.h>
+
+
+/**
+ * @defgroup zrtp zrtp
+ *
+ * ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Experimental support for ZRTP
+ *
+ * See http://tools.ietf.org/html/rfc6189
+ *
+ * Briefly tested with Twinkle 1.4.2 and Jitsi 2.2.4603.9615
+ *
+ * This module is using ZRTP implementation in Freeswitch
+ * https://github.com/traviscross/libzrtp
+ */
+
+
+struct menc_sess {
+ zrtp_session_t *zrtp_session;
+};
+
+struct menc_media {
+ const struct menc_sess *sess;
+ struct udp_helper *uh;
+ struct sa raddr;
+ void *rtpsock;
+ zrtp_stream_t *zrtp_stream;
+};
+
+
+static zrtp_global_t *zrtp_global;
+static zrtp_config_t zrtp_config;
+
+
+static void session_destructor(void *arg)
+{
+ struct menc_sess *st = arg;
+
+ if (st->zrtp_session)
+ zrtp_session_down(st->zrtp_session);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct menc_media *st = arg;
+
+ mem_deref(st->uh);
+ mem_deref(st->rtpsock);
+
+ if (st->zrtp_stream)
+ zrtp_stream_stop(st->zrtp_stream);
+}
+
+
+static bool udp_helper_send(int *err, struct sa *dst,
+ struct mbuf *mb, void *arg)
+{
+ struct menc_media *st = arg;
+ unsigned int length;
+ zrtp_status_t s;
+ (void)dst;
+
+ length = (unsigned int)mbuf_get_left(mb);
+
+ s = zrtp_process_rtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length);
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_process_rtp failed (status = %d)\n", s);
+ return false;
+ }
+
+ /* make sure target buffer is large enough */
+ if (length > mbuf_get_space(mb)) {
+ warning("zrtp: zrtp_process_rtp: length > space (%u > %u)\n",
+ length, mbuf_get_space(mb));
+ *err = ENOMEM;
+ }
+
+ mb->end = mb->pos + length;
+
+ return false;
+}
+
+
+static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct menc_media *st = arg;
+ unsigned int length;
+ zrtp_status_t s;
+ (void)src;
+
+ length = (unsigned int)mbuf_get_left(mb);
+
+ s = zrtp_process_srtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length);
+ if (s != zrtp_status_ok) {
+
+ if (s == zrtp_status_drop)
+ return true;
+
+ warning("zrtp: zrtp_process_srtp: %d\n", s);
+ return false;
+ }
+
+ mb->end = mb->pos + length;
+
+ return false;
+}
+
+
+static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp,
+ bool offerer, menc_error_h *errorh, void *arg)
+{
+ struct menc_sess *st;
+ zrtp_status_t s;
+ int err = 0;
+ (void)offerer;
+ (void)errorh;
+ (void)arg;
+
+ if (!sessp || !sdp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), session_destructor);
+ if (!st)
+ return ENOMEM;
+
+ s = zrtp_session_init(zrtp_global, NULL,
+ ZRTP_SIGNALING_ROLE_UNKNOWN, &st->zrtp_session);
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_session_init failed (status = %d)\n", s);
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *sessp = st;
+
+ return err;
+}
+
+
+static int media_alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_media *st;
+ zrtp_status_t s;
+ int err = 0;
+
+ if (!stp || !sess || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ st = *stp;
+ if (st)
+ goto start;
+
+ st = mem_zalloc(sizeof(*st), media_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sess = sess;
+ st->rtpsock = mem_ref(rtpsock);
+
+ err = udp_register_helper(&st->uh, rtpsock, 0,
+ udp_helper_send, udp_helper_recv, st);
+ if (err)
+ goto out;
+
+ s = zrtp_stream_attach(sess->zrtp_session, &st->zrtp_stream);
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_stream_attach failed (status=%d)\n", s);
+ err = EPROTO;
+ goto out;
+ }
+
+ zrtp_stream_set_userdata(st->zrtp_stream, st);
+
+ out:
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+ else
+ *stp = st;
+
+ start:
+ if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) {
+ st->raddr = *sdp_media_raddr(sdpm);
+
+ s = zrtp_stream_start(st->zrtp_stream, rtp_sess_ssrc(rtp));
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_stream_start: status = %d\n", s);
+ }
+ }
+
+ return err;
+}
+
+
+static int zrtp_send_rtp_callback(const zrtp_stream_t *stream,
+ char *rtp_packet,
+ unsigned int rtp_packet_length)
+{
+ struct menc_media *st = zrtp_stream_get_userdata(stream);
+ struct mbuf *mb;
+ int err;
+
+ if (!sa_isset(&st->raddr, SA_ALL))
+ return zrtp_status_ok;
+
+ mb = mbuf_alloc(rtp_packet_length);
+ if (!mb)
+ return zrtp_status_alloc_fail;
+
+ (void)mbuf_write_mem(mb, (void *)rtp_packet, rtp_packet_length);
+ mb->pos = 0;
+
+ err = udp_send(st->rtpsock, &st->raddr, mb);
+ if (err) {
+ warning("zrtp: udp_send %u bytes (%m)\n",
+ rtp_packet_length, err);
+ }
+
+ mem_deref(mb);
+
+ return zrtp_status_ok;
+}
+
+
+static struct menc menc_zrtp = {
+ LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc
+};
+
+
+static int module_init(void)
+{
+ zrtp_status_t s;
+ char config_path[256] = "";
+ int err;
+
+ zrtp_config_defaults(&zrtp_config);
+
+ err = conf_path_get(config_path, sizeof(config_path));
+ if (err) {
+ warning("zrtp: could not get config path: %m\n", err);
+ return err;
+ }
+ if (re_snprintf(zrtp_config.cache_file_cfg.cache_path,
+ sizeof(zrtp_config.cache_file_cfg.cache_path),
+ "%s/zrtp_cache.dat", config_path) < 0)
+ return ENOMEM;
+
+ rand_bytes(zrtp_config.zid, sizeof(zrtp_config.zid));
+
+ zrtp_config.cb.misc_cb.on_send_packet = zrtp_send_rtp_callback;
+
+ s = zrtp_init(&zrtp_config, &zrtp_global);
+ if (zrtp_status_ok != s) {
+ warning("zrtp: zrtp_init() failed (status = %d)\n", s);
+ return ENOSYS;
+ }
+
+ menc_register(&menc_zrtp);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ menc_unregister(&menc_zrtp);
+
+ if (zrtp_global) {
+ zrtp_down(zrtp_global);
+ zrtp_global = NULL;
+ }
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(zrtp) = {
+ "zrtp",
+ "menc",
+ module_init,
+ module_close
+};
diff --git a/rpm/baresip.spec b/rpm/baresip.spec
new file mode 100644
index 0000000..cdafcf5
--- /dev/null
+++ b/rpm/baresip.spec
@@ -0,0 +1,51 @@
+%define name baresip
+%define ver 0.4.10
+%define rel 1
+
+Summary: Modular SIP useragent
+Name: %name
+Version: %ver
+Release: %rel
+License: BSD
+Group: Applications/Internet
+Source0: file://%{name}-%{version}.tar.gz
+URL: http://www.creytiv.com/
+Vendor: Creytiv
+Packager: Alfred E. Heggestad <aeh@db.org>
+BuildRoot: /var/tmp/%{name}-build-root
+
+%description
+Baresip is a portable and modular SIP User-Agent with audio and video support
+
+%prep
+%setup
+
+%build
+make RELEASE=1
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT install \
+%ifarch x86_64
+ LIBDIR=/usr/lib64
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/sbin/chkconfig --add baresip
+/sbin/ldconfig
+
+%files
+%defattr(-,root,root,-)
+%{_bindir}/*
+%{_libdir}/%name/modules/*.so
+/usr/share/%name/*
+%doc
+
+
+%changelog
+* Fri Nov 5 2010 Alfred E. Heggestad <aeh@db.org> -
+- Initial build.
+
diff --git a/share/busy.wav b/share/busy.wav
new file mode 100644
index 0000000..2e1b717
--- /dev/null
+++ b/share/busy.wav
Binary files differ
diff --git a/share/callwaiting.wav b/share/callwaiting.wav
new file mode 100644
index 0000000..c9bf9b2
--- /dev/null
+++ b/share/callwaiting.wav
Binary files differ
diff --git a/share/error.wav b/share/error.wav
new file mode 100644
index 0000000..2c48280
--- /dev/null
+++ b/share/error.wav
Binary files differ
diff --git a/share/message.wav b/share/message.wav
new file mode 100644
index 0000000..d36e466
--- /dev/null
+++ b/share/message.wav
Binary files differ
diff --git a/share/notfound.wav b/share/notfound.wav
new file mode 100644
index 0000000..9f1b055
--- /dev/null
+++ b/share/notfound.wav
Binary files differ
diff --git a/share/ring.wav b/share/ring.wav
new file mode 100644
index 0000000..1b0650f
--- /dev/null
+++ b/share/ring.wav
Binary files differ
diff --git a/share/ringback.wav b/share/ringback.wav
new file mode 100644
index 0000000..d21e20e
--- /dev/null
+++ b/share/ringback.wav
Binary files differ
diff --git a/src/account.c b/src/account.c
new file mode 100644
index 0000000..95ceeba
--- /dev/null
+++ b/src/account.c
@@ -0,0 +1,574 @@
+/**
+ * @file src/account.c User-Agent account
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ REG_INTERVAL = 3600,
+};
+
+
+static void destructor(void *arg)
+{
+ struct account *acc = arg;
+ size_t i;
+
+ list_clear(&acc->aucodecl);
+ list_clear(&acc->vidcodecl);
+ mem_deref(acc->auth_user);
+ mem_deref(acc->auth_pass);
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++)
+ mem_deref(acc->outbound[i]);
+ mem_deref(acc->regq);
+ mem_deref(acc->rtpkeep);
+ mem_deref(acc->sipnat);
+ mem_deref(acc->stun_user);
+ mem_deref(acc->stun_pass);
+ mem_deref(acc->stun_host);
+ mem_deref(acc->mnatid);
+ mem_deref(acc->mencid);
+ mem_deref(acc->aor);
+ mem_deref(acc->dispname);
+ mem_deref(acc->buf);
+}
+
+
+static int param_dstr(char **dstr, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (sip_param_decode(params, name, &pl))
+ return 0;
+
+ return pl_strdup(dstr, &pl);
+}
+
+
+static int param_u32(uint32_t *v, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (sip_param_decode(params, name, &pl))
+ return 0;
+
+ *v = pl_u32(&pl);
+
+ return 0;
+}
+
+
+static int stunsrv_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl srv;
+ struct uri uri;
+ int err;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ memset(&uri, 0, sizeof(uri));
+
+ if (0 == sip_param_decode(&aor->params, "stunserver", &srv)) {
+
+ info("using stunserver: '%r'\n", &srv);
+
+ err = uri_decode(&uri, &srv);
+ if (err) {
+ warning("account: %r: decode failed: %m\n", &srv, err);
+ memset(&uri, 0, sizeof(uri));
+ }
+
+ if (0 != pl_strcasecmp(&uri.scheme, "stun")) {
+ warning("account: unknown scheme: %r\n", &uri.scheme);
+ return EINVAL;
+ }
+ }
+
+ err = 0;
+ if (pl_isset(&uri.user))
+ err |= pl_strdup(&acc->stun_user, &uri.user);
+ else
+ err |= pl_strdup(&acc->stun_user, &aor->uri.user);
+
+ if (pl_isset(&uri.password))
+ err |= pl_strdup(&acc->stun_pass, &uri.password);
+ else
+ err |= pl_strdup(&acc->stun_pass, &aor->uri.password);
+
+ if (pl_isset(&uri.host))
+ err |= pl_strdup(&acc->stun_host, &uri.host);
+ else
+ err |= pl_strdup(&acc->stun_host, &aor->uri.host);
+
+ acc->stun_port = uri.port;
+
+ return err;
+}
+
+
+/** Decode media parameters */
+static int media_decode(struct account *acc, const struct pl *prm)
+{
+ int err = 0;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ err |= param_dstr(&acc->mencid, prm, "mediaenc");
+ err |= param_dstr(&acc->mnatid, prm, "medianat");
+ err |= param_dstr(&acc->rtpkeep, prm, "rtpkeep" );
+ err |= param_u32(&acc->ptime, prm, "ptime" );
+
+ return err;
+}
+
+
+/* Decode answermode parameter */
+static void answermode_decode(struct account *prm, const struct pl *pl)
+{
+ struct pl amode;
+
+ if (0 == sip_param_decode(pl, "answermode", &amode)) {
+
+ if (0 == pl_strcasecmp(&amode, "manual")) {
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ else if (0 == pl_strcasecmp(&amode, "early")) {
+ prm->answermode = ANSWERMODE_EARLY;
+ }
+ else if (0 == pl_strcasecmp(&amode, "auto")) {
+ prm->answermode = ANSWERMODE_AUTO;
+ }
+ else {
+ warning("account: answermode unknown (%r)\n", &amode);
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ }
+}
+
+
+static int csl_parse(struct pl *pl, char *str, size_t sz)
+{
+ struct pl ws = PL_INIT, val, ws2 = PL_INIT, cma = PL_INIT;
+ int err;
+
+ err = re_regex(pl->p, pl->l, "[ \t]*[^, \t]+[ \t]*[,]*",
+ &ws, &val, &ws2, &cma);
+ if (err)
+ return err;
+
+ pl_advance(pl, ws.l + val.l + ws2.l + cma.l);
+
+ (void)pl_strcpy(&val, str, sz);
+
+ return 0;
+}
+
+
+static int audio_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->aucodecl);
+
+ if (0 == sip_param_exists(prm, "audio_codecs", &tmp)) {
+ struct pl acs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (sip_param_decode(prm, "audio_codecs", &acs))
+ return 0;
+
+ while (0 == csl_parse(&acs, cname, sizeof(cname))) {
+ struct aucodec *ac;
+ struct pl pl_cname, pl_srate, pl_ch = PL_INIT;
+ uint32_t srate = 8000;
+ uint8_t ch = 1;
+
+ /* Format: "codec/srate/ch" */
+ if (0 == re_regex(cname, str_len(cname),
+ "[^/]+/[0-9]+[/]*[0-9]*",
+ &pl_cname, &pl_srate,
+ NULL, &pl_ch)) {
+ (void)pl_strcpy(&pl_cname, cname,
+ sizeof(cname));
+ srate = pl_u32(&pl_srate);
+ if (pl_isset(&pl_ch))
+ ch = pl_u32(&pl_ch);
+ }
+
+ ac = (struct aucodec *)aucodec_find(cname, srate, ch);
+ if (!ac) {
+ warning("account: audio codec not found:"
+ " %s/%u/%d\n",
+ cname, srate, ch);
+ continue;
+ }
+
+ /* NOTE: static list with references to aucodec */
+ list_append(&acc->aucodecl, &acc->acv[i++], ac);
+
+ if (i >= ARRAY_SIZE(acc->acv))
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef USE_VIDEO
+static int video_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->vidcodecl);
+
+ if (0 == sip_param_exists(prm, "video_codecs", &tmp)) {
+ struct pl vcs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (sip_param_decode(prm, "video_codecs", &vcs))
+ return 0;
+
+ while (0 == csl_parse(&vcs, cname, sizeof(cname))) {
+ struct vidcodec *vc;
+
+ vc = (struct vidcodec *)vidcodec_find(cname, NULL);
+ if (!vc) {
+ warning("account: video codec not found: %s\n",
+ cname);
+ continue;
+ }
+
+ /* NOTE: static list with references to vidcodec */
+ list_append(&acc->vidcodecl, &acc->vcv[i++], vc);
+
+ if (i >= ARRAY_SIZE(acc->vcv))
+ break;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+static int sip_params_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl auth_user;
+ size_t i;
+ int err = 0;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ acc->regint = REG_INTERVAL + (rand_u32()&0xff);
+ err |= param_u32(&acc->regint, &aor->params, "regint");
+
+ err |= param_dstr(&acc->regq, &aor->params, "regq");
+
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++) {
+
+ char expr[16] = "outbound";
+
+ expr[8] = i + 1 + 0x30;
+ expr[9] = '\0';
+
+ err |= param_dstr(&acc->outbound[i], &aor->params, expr);
+ }
+
+ /* backwards compat */
+ if (!acc->outbound[0]) {
+ err |= param_dstr(&acc->outbound[0], &aor->params, "outbound");
+ }
+
+ err |= param_dstr(&acc->sipnat, &aor->params, "sipnat");
+
+ if (0 == sip_param_decode(&aor->params, "auth_user", &auth_user))
+ err |= pl_strdup(&acc->auth_user, &auth_user);
+ else
+ err |= pl_strdup(&acc->auth_user, &aor->uri.user);
+
+ if (pl_isset(&aor->dname))
+ err |= pl_strdup(&acc->dispname, &aor->dname);
+
+ return err;
+}
+
+
+static int encode_uri_user(struct re_printf *pf, const struct uri *uri)
+{
+ struct uri uuri = *uri;
+
+ uuri.password = uuri.params = uuri.headers = pl_null;
+
+ return uri_encode(pf, &uuri);
+}
+
+
+static int password_prompt(struct account *acc)
+{
+ char pwd[64];
+ char *nl;
+ int err;
+
+ (void)re_printf("Please enter password for %r@%r: ",
+ &acc->luri.user, &acc->luri.host);
+
+ /* note: blocking UI call */
+ fgets(pwd, sizeof(pwd), stdin);
+ pwd[sizeof(pwd) - 1] = '\0';
+
+ nl = strchr(pwd, '\n');
+ if (nl == NULL) {
+ (void)re_printf("Invalid password (0 - 63 characters"
+ " followed by newline)\n");
+ return EINVAL;
+ }
+
+ *nl = '\0';
+
+ err = str_dup(&acc->auth_pass, pwd);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+int account_alloc(struct account **accp, const char *sipaddr)
+{
+ struct account *acc;
+ struct pl pl;
+ int err = 0;
+
+ if (!accp || !sipaddr)
+ return EINVAL;
+
+ acc = mem_zalloc(sizeof(*acc), destructor);
+ if (!acc)
+ return ENOMEM;
+
+ err = str_dup(&acc->buf, sipaddr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, acc->buf);
+ err = sip_addr_decode(&acc->laddr, &pl);
+ if (err) {
+ warning("account: invalid SIP address: `%r'\n", &pl);
+ goto out;
+ }
+
+ acc->luri = acc->laddr.uri;
+ acc->luri.password = pl_null;
+
+ err = re_sdprintf(&acc->aor, "%H", encode_uri_user, &acc->luri);
+ if (err)
+ goto out;
+
+ /* Decode parameters */
+ acc->ptime = 20;
+ err |= sip_params_decode(acc, &acc->laddr);
+ answermode_decode(acc, &acc->laddr.params);
+ err |= audio_codecs_decode(acc, &acc->laddr.params);
+#ifdef USE_VIDEO
+ err |= video_codecs_decode(acc, &acc->laddr.params);
+#endif
+ err |= media_decode(acc, &acc->laddr.params);
+ if (err)
+ goto out;
+
+ /* optional password prompt */
+ if (!pl_isset(&acc->laddr.uri.password)) {
+ err = password_prompt(acc);
+ if (err)
+ goto out;
+ }
+ else {
+ err = pl_strdup(&acc->auth_pass, &acc->laddr.uri.password);
+ if (err)
+ goto out;
+ }
+
+ if (acc->mnatid) {
+ err = stunsrv_decode(acc, &acc->laddr);
+ if (err)
+ goto out;
+
+ acc->mnat = mnat_find(acc->mnatid);
+ if (!acc->mnat) {
+ warning("account: medianat not found: %s\n",
+ acc->mnatid);
+ }
+ }
+
+ if (acc->mencid) {
+ acc->menc = menc_find(acc->mencid);
+ if (!acc->menc) {
+ warning("account: mediaenc not found: %s\n",
+ acc->mencid);
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(acc);
+ else
+ *accp = acc;
+
+ return err;
+}
+
+
+/**
+ * Sets the displayed name. Pass null in dname to disable display name
+ *
+ * @param acc User-Agent account
+ * @param dname Display name (NULL to disable)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_set_display_name(struct account *acc, const char *dname)
+{
+ if (!acc)
+ return EINVAL;
+
+ acc->dispname = mem_deref(acc->dispname);
+
+ if (dname)
+ return str_dup(&acc->dispname, dname);
+
+ return 0;
+}
+
+
+/**
+ * Authenticate a User-Agent (UA)
+ *
+ * @param acc User-Agent account
+ * @param username Pointer to allocated username string
+ * @param password Pointer to allocated password string
+ * @param realm Realm string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_auth(const struct account *acc, char **username, char **password,
+ const char *realm)
+{
+ if (!acc)
+ return EINVAL;
+
+ (void)realm;
+
+ *username = mem_ref(acc->auth_user);
+ *password = mem_ref(acc->auth_pass);
+
+ return 0;
+}
+
+
+struct list *account_aucodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->aucodecl))
+ ? (struct list *)&acc->aucodecl : aucodec_list();
+}
+
+
+#ifdef USE_VIDEO
+struct list *account_vidcodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->vidcodecl))
+ ? (struct list *)&acc->vidcodecl : vidcodec_list();
+}
+#endif
+
+
+struct sip_addr *account_laddr(const struct account *acc)
+{
+ return acc ? (struct sip_addr *)&acc->laddr : NULL;
+}
+
+
+static const char *answermode_str(enum answermode mode)
+{
+ switch (mode) {
+
+ case ANSWERMODE_MANUAL: return "manual";
+ case ANSWERMODE_EARLY: return "early";
+ case ANSWERMODE_AUTO: return "auto";
+ default: return "???";
+ }
+}
+
+
+int account_debug(struct re_printf *pf, const struct account *acc)
+{
+ struct le *le;
+ size_t i;
+ int err = 0;
+
+ if (!acc)
+ return 0;
+
+ err |= re_hprintf(pf, "\nAccount:\n");
+
+ err |= re_hprintf(pf, " address: %s\n", acc->buf);
+ err |= re_hprintf(pf, " luri: %H\n",
+ uri_encode, &acc->luri);
+ err |= re_hprintf(pf, " aor: %s\n", acc->aor);
+ err |= re_hprintf(pf, " dispname: %s\n", acc->dispname);
+ err |= re_hprintf(pf, " answermode: %s\n",
+ answermode_str(acc->answermode));
+ if (!list_isempty(&acc->aucodecl)) {
+ err |= re_hprintf(pf, " audio_codecs:");
+ for (le = list_head(&acc->aucodecl); le; le = le->next) {
+ const struct aucodec *ac = le->data;
+ err |= re_hprintf(pf, " %s/%u/%u",
+ ac->name, ac->srate, ac->ch);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+ err |= re_hprintf(pf, " auth_user: %s\n", acc->auth_user);
+ err |= re_hprintf(pf, " mediaenc: %s\n",
+ acc->mencid ? acc->mencid : "none");
+ err |= re_hprintf(pf, " medianat: %s\n",
+ acc->mnatid ? acc->mnatid : "none");
+ for (i=0; i<ARRAY_SIZE(acc->outbound); i++) {
+ if (acc->outbound[i]) {
+ err |= re_hprintf(pf, " outbound%d: %s\n",
+ i+1, acc->outbound[i]);
+ }
+ }
+ err |= re_hprintf(pf, " ptime: %u\n", acc->ptime);
+ err |= re_hprintf(pf, " regint: %u\n", acc->regint);
+ err |= re_hprintf(pf, " regq: %s\n", acc->regq);
+ err |= re_hprintf(pf, " rtpkeep: %s\n", acc->rtpkeep);
+ err |= re_hprintf(pf, " sipnat: %s\n", acc->sipnat);
+ err |= re_hprintf(pf, " stunserver: stun:%s@%s:%u\n",
+ acc->stun_user, acc->stun_host, acc->stun_port);
+ if (!list_isempty(&acc->vidcodecl)) {
+ err |= re_hprintf(pf, " video_codecs:");
+ for (le = list_head(&acc->vidcodecl); le; le = le->next) {
+ const struct vidcodec *vc = le->data;
+ err |= re_hprintf(pf, " %s", vc->name);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+
+ return err;
+}
diff --git a/src/aucodec.c b/src/aucodec.c
new file mode 100644
index 0000000..67e3256
--- /dev/null
+++ b/src/aucodec.c
@@ -0,0 +1,76 @@
+/**
+ * @file aucodec.c Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list aucodecl;
+
+
+/**
+ * Register an Audio Codec
+ *
+ * @param ac Audio Codec object
+ */
+void aucodec_register(struct aucodec *ac)
+{
+ if (!ac)
+ return;
+
+ list_append(&aucodecl, &ac->le, ac);
+
+ info("aucodec: %s/%u/%u\n", ac->name, ac->srate, ac->ch);
+}
+
+
+/**
+ * Unregister an Audio Codec
+ *
+ * @param ac Audio Codec object
+ */
+void aucodec_unregister(struct aucodec *ac)
+{
+ if (!ac)
+ return;
+
+ list_unlink(&ac->le);
+}
+
+
+const struct aucodec *aucodec_find(const char *name, uint32_t srate,
+ uint8_t ch)
+{
+ struct le *le;
+
+ for (le=aucodecl.head; le; le=le->next) {
+
+ struct aucodec *ac = le->data;
+
+ if (name && 0 != str_casecmp(name, ac->name))
+ continue;
+
+ if (srate && srate != ac->srate)
+ continue;
+
+ if (ch && ch != ac->ch)
+ continue;
+
+ return ac;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the list of Audio Codecs
+ */
+struct list *aucodec_list(void)
+{
+ return &aucodecl;
+}
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..548b5ac
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,1369 @@
+/**
+ * @file src/audio.c Audio stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * \ref GenericAudioStream
+ */
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0x000a0d10
+#include "magic.h"
+
+
+/**
+ * \page GenericAudioStream Generic Audio Stream
+ *
+ * Implements a generic audio stream. The application can allocate multiple
+ * instances of a audio stream, mapping it to a particular SDP media line.
+ * The audio object has a DSP sound card sink and source, and an audio encoder
+ * and decoder. A particular audio object is mapped to a generic media
+ * stream object. Each audio channel has an optional audio filtering chain.
+ *
+ *<pre>
+ * write read
+ * | /|\
+ * \|/ |
+ * .------. .---------. .-------.
+ * |filter|<--| audio |--->|encoder|
+ * '------' | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .------. .-----.
+ * |auplay| |ausrc|
+ * '------' '-----'
+ *</pre>
+ */
+
+enum {
+ AUDIO_SAMPSZ = 1920,
+};
+
+
+/**
+ * Audio transmit/encoder
+ *
+ *
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .-------. .-------. .--------. .--------. .--------.
+ | | | | | | | | | | |
+ |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP
+ | | | | | | | | | | |
+ ' '-------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ *
+ */
+struct autx {
+ struct ausrc_st *ausrc; /**< Audio Source */
+ const struct aucodec *ac; /**< Current audio encoder */
+ struct auenc_state *enc; /**< Audio encoder state (optional) */
+ struct aubuf *aubuf; /**< Packetize outgoing stream */
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in encoding order */
+ struct mbuf *mb; /**< Buffer for outgoing RTP packets */
+ char device[64];
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for sending */
+ uint32_t ts; /**< Timestamp for outgoing RTP */
+ uint32_t ts_tel; /**< Timestamp for Telephony Events */
+ size_t psize; /**< Packet size for sending */
+ bool marker; /**< Marker bit for outgoing RTP */
+ bool is_g722; /**< Set if encoder is G.722 codec */
+ bool muted; /**< Audio source is muted */
+ int cur_key; /**< Currently transmitted event */
+
+ union {
+ struct tmr tmr; /**< Timer for sending RTP packets */
+#ifdef HAVE_PTHREAD
+ struct {
+ pthread_t tid;/**< Audio transmit thread */
+ bool run; /**< Audio transmit thread running */
+ } thr;
+#endif
+ } u;
+};
+
+
+/**
+ * Audio receive/decoder
+ *
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .--------. .-------. .--------. .--------. .--------.
+ |\ | | | | | | | | | |
+ | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP
+ |/ | | | | | | | | | |
+ '--------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ */
+struct aurx {
+ struct auplay_st *auplay; /**< Audio Player */
+ const struct aucodec *ac; /**< Current audio decoder */
+ struct audec_state *dec; /**< Audio decoder state (optional) */
+ struct aubuf *aubuf; /**< Incoming audio buffer */
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in decoding order */
+ char device[64];
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for receiving */
+ int pt; /**< Payload type for incoming RTP */
+ int pt_tel; /**< Event payload type - receive */
+};
+
+
+/** Generic Audio stream */
+struct audio {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct autx tx; /**< Transmit */
+ struct aurx rx; /**< Receive */
+ struct stream *strm; /**< Generic media stream */
+ struct telev *telev; /**< Telephony events */
+ struct config_audio cfg; /**< Audio configuration */
+ bool started; /**< Stream is started flag */
+ audio_event_h *eventh; /**< Event handler */
+ audio_err_h *errh; /**< Audio error handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stop_tx(struct autx *tx, struct audio *a)
+{
+ if (!tx || !a)
+ return;
+
+ switch (a->cfg.txmode) {
+
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ case AUDIO_MODE_THREAD_REALTIME:
+ if (tx->u.thr.run) {
+ tx->u.thr.run = false;
+ pthread_join(tx->u.thr.tid, NULL);
+ }
+ break;
+#endif
+ case AUDIO_MODE_TMR:
+ tmr_cancel(&tx->u.tmr);
+ break;
+
+ default:
+ break;
+ }
+
+ /* audio source must be stopped first */
+ tx->ausrc = mem_deref(tx->ausrc);
+ tx->aubuf = mem_deref(tx->aubuf);
+
+ list_flush(&tx->filtl);
+}
+
+
+static void stop_rx(struct aurx *rx)
+{
+ if (!rx)
+ return;
+
+ /* audio player must be stopped first */
+ rx->auplay = mem_deref(rx->auplay);
+ rx->aubuf = mem_deref(rx->aubuf);
+
+ list_flush(&rx->filtl);
+}
+
+
+static void audio_destructor(void *arg)
+{
+ struct audio *a = arg;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+
+ mem_deref(a->tx.enc);
+ mem_deref(a->rx.dec);
+ mem_deref(a->tx.aubuf);
+ mem_deref(a->tx.mb);
+ mem_deref(a->tx.sampv);
+ mem_deref(a->rx.sampv);
+ mem_deref(a->rx.aubuf);
+ mem_deref(a->tx.sampv_rs);
+ mem_deref(a->rx.sampv_rs);
+
+ list_flush(&a->tx.filtl);
+ list_flush(&a->rx.filtl);
+
+ mem_deref(a->strm);
+ mem_deref(a->telev);
+}
+
+
+/**
+ * Calculate number of samples from sample rate, channels and packet time
+ *
+ * @param srate Sample rate in [Hz]
+ * @param channels Number of channels
+ * @param ptime Packet time in [ms]
+ *
+ * @return Number of samples
+ */
+static inline uint32_t calc_nsamp(uint32_t srate, uint8_t channels,
+ uint16_t ptime)
+{
+ return srate * channels * ptime / 1000;
+}
+
+
+/**
+ * Get the DSP samplerate for an audio-codec (exception for G.722)
+ */
+static inline uint32_t get_srate(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return !str_casecmp(ac->name, "G722") ? 16000 : ac->srate;
+}
+
+
+static inline uint32_t get_framesize(const struct aucodec *ac,
+ uint32_t ptime)
+{
+ if (!ac)
+ return 0;
+
+ return calc_nsamp(get_srate(ac), ac->ch, ptime);
+}
+
+
+static bool aucodec_equal(const struct aucodec *a, const struct aucodec *b)
+{
+ if (!a || !b)
+ return false;
+
+ return get_srate(a) == get_srate(b) && a->ch == b->ch;
+}
+
+
+static int add_audio_codec(struct audio *a, struct sdp_media *m,
+ struct aucodec *ac)
+{
+ if (!in_range(&a->cfg.srate, ac->srate)) {
+ debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n",
+ ac->srate, a->cfg.srate.min, a->cfg.srate.max);
+ return 0;
+ }
+
+ if (!in_range(&a->cfg.channels, ac->ch)) {
+ debug("audio: skip codec with %uch (audio range %uch-%uch)\n",
+ ac->ch, a->cfg.channels.min, a->cfg.channels.max);
+ return 0;
+ }
+
+ return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->srate,
+ ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false,
+ "%s", ac->fmtp);
+}
+
+
+/**
+ * Encoder audio and send via stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param a Audio object
+ * @param tx Audio transmit object
+ * @param sampv Audio samples
+ * @param sampc Number of audio samples
+ */
+static void encode_rtp_send(struct audio *a, struct autx *tx,
+ int16_t *sampv, size_t sampc)
+{
+ size_t len;
+ int err;
+
+ if (!tx->ac)
+ return;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+ len = mbuf_get_space(tx->mb);
+
+ err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc);
+ if (err) {
+ warning("audio: %s encode error: %d samples (%m)\n",
+ tx->ac->name, sampc, err);
+ goto out;
+ }
+
+ tx->mb->pos = STREAM_PRESZ;
+ tx->mb->end = STREAM_PRESZ + len;
+
+ if (mbuf_get_left(tx->mb)) {
+
+ err = stream_send(a->strm, tx->marker, -1, tx->ts, tx->mb);
+ if (err)
+ goto out;
+ }
+
+ tx->ts += (uint32_t)(tx->is_g722 ? sampc/2 : sampc);
+
+ out:
+ tx->marker = false;
+}
+
+
+/*
+ * @note This function has REAL-TIME properties
+ */
+static void poll_aubuf_tx(struct audio *a)
+{
+ struct autx *tx = &a->tx;
+ int16_t *sampv = tx->sampv;
+ size_t sampc;
+ struct le *le;
+ int err = 0;
+
+ sampc = tx->psize / 2;
+
+ /* timed read from audio-buffer */
+ if (aubuf_get_samp(tx->aubuf, tx->ptime, tx->sampv, sampc))
+ return;
+
+ /* optional resampler */
+ if (tx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&tx->resamp,
+ tx->sampv_rs, &sampc_rs,
+ tx->sampv, sampc);
+ if (err)
+ return;
+
+ sampv = tx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ /* Process exactly one audio-frame in list order */
+ for (le = tx->filtl.head; le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af && st->af->ench)
+ err |= st->af->ench(st, sampv, &sampc);
+ }
+ if (err) {
+ warning("audio: aufilter encode: %m\n", err);
+ }
+
+ /* Encode and send */
+ encode_rtp_send(a, tx, sampv, sampc);
+}
+
+
+static void check_telev(struct audio *a, struct autx *tx)
+{
+ const struct sdp_format *fmt;
+ bool marker = false;
+ int err;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ err = telev_poll(a->telev, &marker, tx->mb);
+ if (err)
+ return;
+
+ if (marker)
+ tx->ts_tel = tx->ts;
+
+ fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt);
+ if (!fmt)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ;
+ err = stream_send(a->strm, marker, fmt->pt, tx->ts_tel, tx->mb);
+ if (err) {
+ warning("audio: telev: stream_send %m\n", err);
+ }
+}
+
+
+/**
+ * Write samples to Audio Player.
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note The application is responsible for filling in silence in
+ * the case of underrun
+ *
+ * @note This function may be called from any thread
+ *
+ * @param buf Buffer to fill with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ *
+ * @return true for valid audio samples, false for silence
+ */
+static bool auplay_write_handler(uint8_t *buf, size_t sz, void *arg)
+{
+ struct aurx *rx = arg;
+
+ aubuf_read(rx->aubuf, buf, sz);
+
+ return true;
+}
+
+
+/**
+ * Read samples from Audio Source
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note This function may be called from any thread
+ *
+ * @param buf Buffer with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ */
+static void ausrc_read_handler(const uint8_t *buf, size_t sz, void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+
+ if (tx->muted)
+ memset((void *)buf, 0, sz);
+
+ (void)aubuf_write(tx->aubuf, buf, sz);
+
+ if (a->cfg.txmode == AUDIO_MODE_POLL)
+ poll_aubuf_tx(a);
+
+ /* Exact timing: send Telephony-Events from here */
+ check_telev(a, tx);
+}
+
+
+static void ausrc_error_handler(int err, const char *str, void *arg)
+{
+ struct audio *a = arg;
+ MAGIC_CHECK(a);
+
+ if (a->errh)
+ a->errh(err, str, a->arg);
+}
+
+
+static int pt_handler(struct audio *a, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(a->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Audio decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ a->rx.pt = pt_new;
+
+ return audio_decoder_set(a, lc->data, lc->pt, lc->params);
+}
+
+
+static void handle_telev(struct audio *a, struct mbuf *mb)
+{
+ int event, digit;
+ bool end;
+
+ if (telev_recv(a->telev, mb, &event, &end))
+ return;
+
+ digit = telev_code2digit(event);
+ if (digit >= 0 && a->eventh)
+ a->eventh(digit, end, a->arg);
+}
+
+
+static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb)
+{
+ size_t sampc = AUDIO_SAMPSZ;
+ int16_t *sampv;
+ struct le *le;
+ int err = 0;
+
+ /* No decoder set */
+ if (!rx->ac)
+ return 0;
+
+ if (mbuf_get_left(mb)) {
+ err = rx->ac->dech(rx->dec, rx->sampv, &sampc,
+ mbuf_buf(mb), mbuf_get_left(mb));
+ }
+ else if (rx->ac->plch) {
+ err = rx->ac->plch(rx->dec, rx->sampv, &sampc);
+ }
+ else {
+ /* no PLC in the codec, might be done in filters below */
+ sampc = 0;
+ }
+
+ if (err) {
+ warning("audio: %s codec decode %u bytes: %m\n",
+ rx->ac->name, mbuf_get_left(mb), err);
+ goto out;
+ }
+
+ /* Process exactly one audio-frame in reverse list order */
+ for (le = rx->filtl.tail; le; le = le->prev) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af && st->af->dech)
+ err |= st->af->dech(st, rx->sampv, &sampc);
+ }
+
+ if (!rx->aubuf)
+ goto out;
+
+ sampv = rx->sampv;
+
+ /* optional resampler */
+ if (rx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&rx->resamp,
+ rx->sampv_rs, &sampc_rs,
+ rx->sampv, sampc);
+ if (err)
+ return err;
+
+ sampv = rx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ err = aubuf_write_samp(rx->aubuf, sampv, sampc);
+ if (err)
+ goto out;
+
+ out:
+ return err;
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct audio *a = arg;
+ struct aurx *rx = &a->rx;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Telephone event? */
+ if (hdr->pt == rx->pt_tel) {
+ handle_telev(a, mb);
+ return;
+ }
+
+ /* Comfort Noise (CN) as of RFC 3389 */
+ if (PT_CN == hdr->pt)
+ return;
+
+ /* Audio payload-type changed? */
+ /* XXX: this logic should be moved to stream.c */
+ if (hdr->pt != rx->pt) {
+
+ err = pt_handler(a, rx->pt, hdr->pt);
+ if (err)
+ return;
+ }
+
+ out:
+ (void)aurx_stream_decode(&a->rx, mb);
+}
+
+
+static int add_telev_codec(struct audio *a)
+{
+ struct sdp_media *m = stream_sdpmedia(audio_strm(a));
+ struct sdp_format *sf;
+ int err;
+
+ /* Use payload-type 101 if available, for CiscoGW interop */
+ err = sdp_format_add(&sf, m, false,
+ (!sdp_media_lformat(m, 101)) ? "101" : NULL,
+ telev_rtpfmt, TELEV_SRATE, 1, NULL,
+ NULL, NULL, false, "0-15");
+ if (err)
+ return err;
+
+ a->rx.pt_tel = sf->pt;
+
+ return err;
+}
+
+
+int audio_alloc(struct audio **ap, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ uint32_t ptime, const struct list *aucodecl,
+ audio_event_h *eventh, audio_err_h *errh, void *arg)
+{
+ struct audio *a;
+ struct autx *tx;
+ struct aurx *rx;
+ struct le *le;
+ int err;
+
+ if (!ap || !cfg)
+ return EINVAL;
+
+ a = mem_zalloc(sizeof(*a), audio_destructor);
+ if (!a)
+ return ENOMEM;
+
+ MAGIC_INIT(a);
+
+ a->cfg = cfg->audio;
+ tx = &a->tx;
+ rx = &a->rx;
+
+ err = stream_alloc(&a->strm, &cfg->avt, call, sdp_sess,
+ "audio", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, NULL, a);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max) {
+ stream_set_bw(a->strm, AUDIO_BANDWIDTH);
+ }
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "ptime", "%u", ptime);
+ if (err)
+ goto out;
+
+ /* Audio codecs */
+ for (le = list_head(aucodecl); le; le = le->next) {
+ err = add_audio_codec(a, stream_sdpmedia(a->strm), le->data);
+ if (err)
+ goto out;
+ }
+
+ tx->mb = mbuf_alloc(STREAM_PRESZ + 4096);
+ tx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ rx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!tx->mb || !tx->sampv || !rx->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = telev_alloc(&a->telev, TELEV_PTIME);
+ if (err)
+ goto out;
+
+ err = add_telev_codec(a);
+ if (err)
+ goto out;
+
+ auresamp_init(&tx->resamp);
+ str_ncpy(tx->device, a->cfg.src_dev, sizeof(tx->device));
+ tx->ptime = ptime;
+ tx->ts = 160;
+ tx->marker = true;
+
+ auresamp_init(&rx->resamp);
+ str_ncpy(rx->device, a->cfg.play_dev, sizeof(rx->device));
+ rx->pt = -1;
+ rx->ptime = ptime;
+
+ a->eventh = eventh;
+ a->errh = errh;
+ a->arg = arg;
+
+ if (a->cfg.txmode == AUDIO_MODE_TMR)
+ tmr_init(&tx->u.tmr);
+
+ out:
+ if (err)
+ mem_deref(a);
+ else
+ *ap = a;
+
+ return err;
+}
+
+
+#ifdef HAVE_PTHREAD
+static void *tx_thread(void *arg)
+{
+ struct audio *a = arg;
+
+ /* Enable Real-time mode for this thread, if available */
+ if (a->cfg.txmode == AUDIO_MODE_THREAD_REALTIME)
+ (void)realtime_enable(true, 1);
+
+ while (a->tx.u.thr.run) {
+
+ poll_aubuf_tx(a);
+
+ sys_msleep(5);
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void timeout_tx(void *arg)
+{
+ struct audio *a = arg;
+
+ tmr_start(&a->tx.u.tmr, 5, timeout_tx, a);
+
+ poll_aubuf_tx(a);
+}
+
+
+static void aufilt_param_set(struct aufilt_prm *prm,
+ const struct aucodec *ac, uint32_t ptime)
+{
+ if (!ac) {
+ memset(prm, 0, sizeof(*prm));
+ return;
+ }
+
+ prm->srate = get_srate(ac);
+ prm->ch = ac->ch;
+ prm->ptime = ptime;
+}
+
+
+static int autx_print_pipeline(struct re_printf *pf, const struct autx *autx)
+{
+ struct le *le;
+ int err;
+
+ if (!autx)
+ return 0;
+
+ err = re_hprintf(pf, "audio tx pipeline: %10s",
+ autx->ausrc ? autx->ausrc->as->name : "src");
+
+ for (le = list_head(&autx->filtl); le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af->ench)
+ err |= re_hprintf(pf, " ---> %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ autx->ac ? autx->ac->name : "encoder");
+
+ return err;
+}
+
+
+static int aurx_print_pipeline(struct re_printf *pf, const struct aurx *aurx)
+{
+ struct le *le;
+ int err;
+
+ if (!aurx)
+ return 0;
+
+ err = re_hprintf(pf, "audio rx pipeline: %10s",
+ aurx->auplay ? aurx->auplay->ap->name : "play");
+
+ for (le = list_head(&aurx->filtl); le; le = le->next) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af->dech)
+ err |= re_hprintf(pf, " <--- %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ aurx->ac ? aurx->ac->name : "decoder");
+
+ return err;
+}
+
+
+/**
+ * Setup the audio-filter chain
+ *
+ * must be called before auplay/ausrc-alloc
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int aufilt_setup(struct audio *a)
+{
+ struct aufilt_prm encprm, decprm;
+ struct autx *tx = &a->tx;
+ struct aurx *rx = &a->rx;
+ struct le *le;
+ int err = 0;
+
+ /* wait until we have both Encoder and Decoder */
+ if (!tx->ac || !rx->ac)
+ return 0;
+
+ if (!list_isempty(&tx->filtl) || !list_isempty(&rx->filtl))
+ return 0;
+
+ aufilt_param_set(&encprm, tx->ac, tx->ptime);
+ aufilt_param_set(&decprm, rx->ac, rx->ptime);
+
+ /* Audio filters */
+ for (le = list_head(aufilt_list()); le; le = le->next) {
+ struct aufilt *af = le->data;
+ struct aufilt_enc_st *encst = NULL;
+ struct aufilt_dec_st *decst = NULL;
+ void *ctx = NULL;
+
+ if (af->encupdh) {
+ err |= af->encupdh(&encst, &ctx, af, &encprm);
+ if (err)
+ break;
+
+ encst->af = af;
+ list_append(&tx->filtl, &encst->le, encst);
+ }
+
+ if (af->decupdh) {
+ err |= af->decupdh(&decst, &ctx, af, &decprm);
+ if (err)
+ break;
+
+ decst->af = af;
+ list_append(&rx->filtl, &decst->le, decst);
+ }
+
+ if (err) {
+ warning("audio: audio-filter '%s'"
+ " update failed (%m)\n", af->name, err);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int start_player(struct aurx *rx, struct audio *a)
+{
+ const struct aucodec *ac = rx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = ac->ch;
+
+ if (a->cfg.srate_play && a->cfg.srate_play != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_play;
+ }
+ if (a->cfg.channels_play && a->cfg.channels_play != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_play;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !rx->sampv_rs) {
+
+ info("audio: enable auplay resampler:"
+ " %uHz/%uch --> %uHz/%uch\n",
+ get_srate(ac), ac->ch, srate_dsp, channels_dsp);
+
+ rx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!rx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&rx->resamp,
+ get_srate(ac), ac->ch,
+ srate_dsp, channels_dsp);
+ if (err) {
+ warning("audio: could not setup auplay resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Player */
+ if (!rx->auplay && auplay_find(NULL)) {
+
+ struct auplay_prm prm;
+
+ prm.fmt = AUFMT_S16LE;
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = rx->ptime;
+
+ if (!rx->aubuf) {
+ size_t psize;
+
+ psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ err = aubuf_alloc(&rx->aubuf, psize * 1, psize * 8);
+ if (err)
+ return err;
+ }
+
+ err = auplay_alloc(&rx->auplay, a->cfg.play_mod,
+ &prm, rx->device,
+ auplay_write_handler, rx);
+ if (err) {
+ warning("audio: start_player failed (%s.%s): %m\n",
+ a->cfg.play_mod, rx->device, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+static int start_source(struct autx *tx, struct audio *a)
+{
+ const struct aucodec *ac = tx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = ac->ch;
+
+ if (a->cfg.srate_src && a->cfg.srate_src != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_src;
+ }
+ if (a->cfg.channels_src && a->cfg.channels_src != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_src;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !tx->sampv_rs) {
+
+ info("audio: enable ausrc resampler:"
+ " %uHz/%uch <-- %uHz/%uch\n",
+ get_srate(ac), ac->ch, srate_dsp, channels_dsp);
+
+ tx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!tx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&tx->resamp,
+ srate_dsp, channels_dsp,
+ get_srate(ac), ac->ch);
+ if (err) {
+ warning("audio: could not setup ausrc resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Source */
+ if (!tx->ausrc && ausrc_find(NULL)) {
+
+ struct ausrc_prm prm;
+
+ prm.fmt = AUFMT_S16LE;
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = tx->ptime;
+
+ tx->psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ if (!tx->aubuf) {
+ err = aubuf_alloc(&tx->aubuf, tx->psize * 2,
+ tx->psize * 30);
+ if (err)
+ return err;
+ }
+
+ err = ausrc_alloc(&tx->ausrc, NULL, a->cfg.src_mod,
+ &prm, tx->device,
+ ausrc_read_handler, ausrc_error_handler, a);
+ if (err) {
+ warning("audio: start_source failed: %m\n", err);
+ return err;
+ }
+
+ switch (a->cfg.txmode) {
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ case AUDIO_MODE_THREAD_REALTIME:
+ if (!tx->u.thr.run) {
+ tx->u.thr.run = true;
+ err = pthread_create(&tx->u.thr.tid, NULL,
+ tx_thread, a);
+ if (err) {
+ tx->u.thr.tid = false;
+ return err;
+ }
+ }
+ break;
+#endif
+
+ case AUDIO_MODE_TMR:
+ tmr_start(&tx->u.tmr, 1, timeout_tx, a);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Start the audio playback and recording
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_start(struct audio *a)
+{
+ int err;
+
+ if (!a)
+ return EINVAL;
+
+ /* Audio filter */
+ if (!list_isempty(aufilt_list())) {
+ err = aufilt_setup(a);
+ if (err)
+ return err;
+ }
+
+ /* configurable order of play/src start */
+ if (a->cfg.src_first) {
+ err = start_source(&a->tx, a);
+ err |= start_player(&a->rx, a);
+ }
+ else {
+ err = start_player(&a->rx, a);
+ err |= start_source(&a->tx, a);
+ }
+ if (err)
+ return err;
+
+ if (a->tx.ac && a->rx.ac) {
+
+ if (!a->started) {
+ info("%H%H",
+ autx_print_pipeline, &a->tx,
+ aurx_print_pipeline, &a->rx);
+ }
+
+ a->started = true;
+ }
+
+ return err;
+}
+
+
+/**
+ * Stop the audio playback and recording
+ *
+ * @param a Audio object
+ */
+void audio_stop(struct audio *a)
+{
+ if (!a)
+ return;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+}
+
+
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params)
+{
+ struct autx *tx;
+ int err = 0;
+ bool reset;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ tx = &a->tx;
+
+ reset = !aucodec_equal(ac, tx->ac);
+
+ if (ac != tx->ac) {
+ info("audio: Set audio encoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), ac->ch);
+
+ /* Audio source must be stopped first */
+ if (reset) {
+ tx->ausrc = mem_deref(tx->ausrc);
+ }
+
+ tx->is_g722 = (0 == str_casecmp(ac->name, "G722"));
+ tx->enc = mem_deref(tx->enc);
+ tx->ac = ac;
+ }
+
+ if (ac->encupdh) {
+ struct auenc_param prm;
+
+ prm.ptime = tx->ptime;
+
+ err = ac->encupdh(&tx->enc, ac, &prm, params);
+ if (err) {
+ warning("audio: alloc encoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, get_srate(ac), get_srate(ac));
+ stream_update_encoder(a->strm, pt_tx);
+
+ if (!tx->ausrc) {
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params)
+{
+ struct aurx *rx;
+ bool reset = false;
+ int err = 0;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ rx = &a->rx;
+
+ reset = !aucodec_equal(ac, rx->ac);
+
+ if (ac != rx->ac) {
+
+ info("audio: Set audio decoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), ac->ch);
+
+ rx->pt = pt_rx;
+ rx->ac = ac;
+ rx->dec = mem_deref(rx->dec);
+ }
+
+ if (ac->decupdh) {
+ err = ac->decupdh(&rx->dec, ac, params);
+ if (err) {
+ warning("audio: alloc decoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, get_srate(ac), get_srate(ac));
+
+ if (reset) {
+
+ rx->auplay = mem_deref(rx->auplay);
+
+ /* Reset audio filter chain */
+ list_flush(&rx->filtl);
+
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next audio encoder in the local list of negotiated codecs
+ *
+ * @param audio Audio object
+ */
+void audio_encoder_cycle(struct audio *audio)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!audio)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(audio_strm(audio)));
+ if (!rc) {
+ info("audio: encoder cycle: no remote codec found\n");
+ return;
+ }
+
+ (void)audio_encoder_set(audio, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *audio_strm(const struct audio *a)
+{
+ return a ? a->strm : NULL;
+}
+
+
+int audio_send_digit(struct audio *a, char key)
+{
+ int err = 0;
+
+ if (!a)
+ return EINVAL;
+
+ if (key > 0) {
+ info("audio: send DTMF digit: '%c'\n", key);
+ err = telev_send(a->telev, telev_digit2code(key), false);
+ }
+ else if (a->tx.cur_key) {
+ /* Key release */
+ info("audio: send DTMF digit end: '%c'\n", a->tx.cur_key);
+ err = telev_send(a->telev,
+ telev_digit2code(a->tx.cur_key), true);
+ }
+
+ a->tx.cur_key = key;
+
+ return err;
+}
+
+
+/**
+ * Mute the audio stream
+ *
+ * @param a Audio stream
+ * @param muted True to mute, false to un-mute
+ */
+void audio_mute(struct audio *a, bool muted)
+{
+ if (!a)
+ return;
+
+ a->tx.muted = muted;
+}
+
+
+void audio_sdp_attr_decode(struct audio *a)
+{
+ const char *attr;
+
+ if (!a)
+ return;
+
+ /* This is probably only meaningful for audio data, but
+ may be used with other media types if it makes sense. */
+ attr = sdp_media_rattr(stream_sdpmedia(a->strm), "ptime");
+ if (attr) {
+ struct autx *tx = &a->tx;
+ uint32_t ptime_tx = atoi(attr);
+
+ if (ptime_tx && ptime_tx != a->tx.ptime) {
+
+ info("audio: peer changed ptime_tx %u -> %u\n",
+ a->tx.ptime, ptime_tx);
+
+ tx->ptime = ptime_tx;
+
+ if (tx->ac) {
+ tx->psize = 2 * get_framesize(tx->ac,
+ ptime_tx);
+ }
+ }
+ }
+}
+
+
+static int aucodec_print(struct re_printf *pf, const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return re_hprintf(pf, "%s %uHz/%dch", ac->name, get_srate(ac), ac->ch);
+}
+
+
+int audio_debug(struct re_printf *pf, const struct audio *a)
+{
+ const struct autx *tx;
+ const struct aurx *rx;
+ int err;
+
+ if (!a)
+ return 0;
+
+ tx = &a->tx;
+ rx = &a->rx;
+
+ err = re_hprintf(pf, "\n--- Audio stream ---\n");
+
+ err |= re_hprintf(pf, " tx: %H %H ptime=%ums\n",
+ aucodec_print, tx->ac,
+ aubuf_debug, tx->aubuf,
+ tx->ptime);
+
+ err |= re_hprintf(pf, " rx: %H %H ptime=%ums pt=%d pt_tel=%d\n",
+ aucodec_print, rx->ac,
+ aubuf_debug, rx->aubuf,
+ rx->ptime, rx->pt, rx->pt_tel);
+
+ err |= re_hprintf(pf,
+ " %H"
+ " %H",
+ autx_print_pipeline, tx,
+ aurx_print_pipeline, rx);
+
+ err |= stream_debug(pf, a->strm);
+
+ return err;
+}
+
+
+void audio_set_devicename(struct audio *a, const char *src, const char *play)
+{
+ if (!a)
+ return;
+
+ str_ncpy(a->tx.device, src, sizeof(a->tx.device));
+ str_ncpy(a->rx.device, play, sizeof(a->rx.device));
+}
diff --git a/src/aufilt.c b/src/aufilt.c
new file mode 100644
index 0000000..c749aaf
--- /dev/null
+++ b/src/aufilt.c
@@ -0,0 +1,37 @@
+/**
+ * @file aufilt.c Audio Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list afl;
+
+
+void aufilt_register(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_append(&afl, &af->le, af);
+
+ info("aufilt: %s\n", af->name);
+}
+
+
+void aufilt_unregister(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_unlink(&af->le);
+}
+
+
+struct list *aufilt_list(void)
+{
+ return &afl;
+}
diff --git a/src/auplay.c b/src/auplay.c
new file mode 100644
index 0000000..a1247b6
--- /dev/null
+++ b/src/auplay.c
@@ -0,0 +1,108 @@
+/**
+ * @file auplay.c Audio Player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list auplayl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct auplay *ap = arg;
+
+ list_unlink(&ap->le);
+}
+
+
+/**
+ * Register an Audio Player
+ *
+ * @param app Pointer to allocated Audio Player object
+ * @param name Audio Player name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_register(struct auplay **app, const char *name,
+ auplay_alloc_h *alloch)
+{
+ struct auplay *ap;
+
+ if (!app)
+ return EINVAL;
+
+ ap = mem_zalloc(sizeof(*ap), destructor);
+ if (!ap)
+ return ENOMEM;
+
+ list_append(&auplayl, &ap->le, ap);
+
+ ap->name = name;
+ ap->alloch = alloch;
+
+ info("auplay: %s\n", name);
+
+ *app = ap;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Player by name
+ *
+ * @param name Name of the Audio Player to find
+ *
+ * @return Matching Audio Player if found, otherwise NULL
+ */
+const struct auplay *auplay_find(const char *name)
+{
+ struct le *le;
+
+ for (le=auplayl.head; le; le=le->next) {
+
+ struct auplay *ap = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, ap->name))
+ continue;
+
+ return ap;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Player state
+ *
+ * @param stp Pointer to allocated Audio Player state
+ * @param name Name of Audio Player
+ * @param prm Audio Player parameters
+ * @param device Name of Audio Player device (driver specific)
+ * @param wh Write handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_alloc(struct auplay_st **stp, const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay *ap;
+
+ ap = (struct auplay *)auplay_find(name);
+ if (!ap)
+ return ENOENT;
+
+ if (!prm->srate || !prm->ch)
+ return EINVAL;
+
+ return ap->alloch(stp, ap, prm, device, wh, arg);
+}
diff --git a/src/ausrc.c b/src/ausrc.c
new file mode 100644
index 0000000..9782db3
--- /dev/null
+++ b/src/ausrc.c
@@ -0,0 +1,106 @@
+/**
+ * @file ausrc.c Audio Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list ausrcl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct ausrc *as = arg;
+
+ list_unlink(&as->le);
+}
+
+
+/**
+ * Register an Audio Source
+ *
+ * @param asp Pointer to allocated Audio Source object
+ * @param name Audio Source name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_register(struct ausrc **asp, const char *name, ausrc_alloc_h *alloch)
+{
+ struct ausrc *as;
+
+ if (!asp)
+ return EINVAL;
+
+ as = mem_zalloc(sizeof(*as), destructor);
+ if (!as)
+ return ENOMEM;
+
+ list_append(&ausrcl, &as->le, as);
+
+ as->name = name;
+ as->alloch = alloch;
+
+ info("ausrc: %s\n", name);
+
+ *asp = as;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Source by name
+ *
+ * @param name Name of the Audio Source to find
+ *
+ * @return Matching Audio Source if found, otherwise NULL
+ */
+const struct ausrc *ausrc_find(const char *name)
+{
+ struct le *le;
+
+ for (le=ausrcl.head; le; le=le->next) {
+
+ struct ausrc *as = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, as->name))
+ continue;
+
+ return as;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Source state
+ *
+ * @param stp Pointer to allocated Audio Source state
+ * @param ctx Media context (optional)
+ * @param name Name of Audio Source
+ * @param prm Audio Source parameters
+ * @param device Name of Audio Source device (driver specific)
+ * @param rh Read handler
+ * @param errh Error handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_alloc(struct ausrc_st **stp, struct media_ctx **ctx,
+ const char *name, struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc *as;
+
+ as = (struct ausrc *)ausrc_find(name);
+ if (!as)
+ return ENOENT;
+
+ return as->alloch(stp, as, ctx, prm, device, rh, errh, arg);
+}
diff --git a/src/bfcp.c b/src/bfcp.c
new file mode 100644
index 0000000..5b69142
--- /dev/null
+++ b/src/bfcp.c
@@ -0,0 +1,199 @@
+/**
+ * @file bfcp.c BFCP client
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct bfcp {
+ struct bfcp_conn *conn;
+ struct sdp_media *sdpm;
+ struct mnat_media *mnat_st;
+ bool active;
+
+ /* server */
+ uint32_t lconfid;
+ uint16_t luserid;
+};
+
+
+static void destructor(void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ mem_deref(bfcp->mnat_st);
+ mem_deref(bfcp->sdpm);
+ mem_deref(bfcp->conn);
+}
+
+
+static const char *bfcp_sdp_transp(enum bfcp_transp tp)
+{
+ switch (tp) {
+
+ case BFCP_UDP: return "UDP/BFCP";
+ case BFCP_DTLS: return "UDP/TLS/BFCP";
+ default: return NULL;
+ }
+}
+
+
+static enum bfcp_transp str2tp(const char *proto)
+{
+ if (0 == str_casecmp(proto, "udp"))
+ return BFCP_UDP;
+ else if (0 == str_casecmp(proto, "dtls"))
+ return BFCP_DTLS;
+ else {
+ warning("unsupported BFCP protocol: %s\n", proto);
+ return -1;
+ }
+}
+
+
+static void bfcp_resp_handler(int err, const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+ (void)bfcp;
+
+ if (err) {
+ warning("bfcp: error response: %m\n", err);
+ return;
+ }
+
+ info("bfcp: received BFCP response: '%s'\n",
+ bfcp_prim_name(msg->prim));
+}
+
+
+static void bfcp_msg_handler(const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ info("bfcp: received BFCP message '%s'\n", bfcp_prim_name(msg->prim));
+
+ switch (msg->prim) {
+
+ case BFCP_HELLO:
+ (void)bfcp_reply(bfcp->conn, msg, BFCP_HELLO_ACK, 0);
+ break;
+
+ default:
+ (void)bfcp_ereply(bfcp->conn, msg, BFCP_UNKNOWN_PRIM);
+ break;
+ }
+}
+
+
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess)
+{
+ struct bfcp *bfcp;
+ struct sa laddr;
+ enum bfcp_transp transp;
+ int err;
+
+ if (!bfcpp || !sdp_sess)
+ return EINVAL;
+
+ transp = str2tp(proto);
+
+ bfcp = mem_zalloc(sizeof(*bfcp), destructor);
+ if (!bfcp)
+ return ENOMEM;
+
+ bfcp->active = offerer;
+
+ sa_init(&laddr, AF_INET);
+
+ err = bfcp_listen(&bfcp->conn, transp, &laddr, uag_tls(),
+ bfcp_msg_handler, bfcp);
+ if (err)
+ goto out;
+
+ err = sdp_media_add(&bfcp->sdpm, sdp_sess, "application",
+ sa_port(&laddr), bfcp_sdp_transp(transp));
+ if (err)
+ goto out;
+
+ err = sdp_format_add(NULL, bfcp->sdpm, false, "*", NULL,
+ 0, 0, NULL, NULL, NULL, false, NULL);
+ if (err)
+ goto out;
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "floorctrl", "c-s");
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "setup",
+ bfcp->active ? "active" : "actpass");
+
+ if (bfcp->active) {
+ err |= sdp_media_set_lattr(bfcp->sdpm, true,
+ "connection", "new");
+ }
+ else {
+ bfcp->lconfid = 1000 + (rand_u16() & 0xf);
+ bfcp->luserid = 1 + (rand_u16() & 0x7);
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "confid",
+ "%u", bfcp->lconfid);
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "userid",
+ "%u", bfcp->luserid);
+ }
+
+ if (err)
+ goto out;
+
+ if (mnat) {
+ info("bfcp: enabled medianat '%s' on UDP socket\n", mnat->id);
+
+ err = mnat->mediah(&bfcp->mnat_st, mnat_sess, IPPROTO_UDP,
+ bfcp_sock(bfcp->conn), NULL, bfcp->sdpm);
+ if (err)
+ goto out;
+ }
+
+ info("bfcp: %s BFCP agent protocol '%s' on port %d\n",
+ bfcp->active ? "Active" : "Passive",
+ proto, sa_port(&laddr));
+
+ out:
+ if (err)
+ mem_deref(bfcp);
+ else
+ *bfcpp = bfcp;
+
+ return err;
+}
+
+
+int bfcp_start(struct bfcp *bfcp)
+{
+ const struct sa *paddr;
+ uint32_t confid = 0;
+ uint16_t userid = 0;
+ int err = 0;
+
+ if (!bfcp)
+ return EINVAL;
+
+ if (!sdp_media_rport(bfcp->sdpm)) {
+ info("bfcp channel is disabled\n");
+ return 0;
+ }
+
+ if (bfcp->active) {
+
+ paddr = sdp_media_raddr(bfcp->sdpm);
+ confid = sdp_media_rattr_u32(bfcp->sdpm, "confid");
+ userid = sdp_media_rattr_u32(bfcp->sdpm, "userid");
+
+ err = bfcp_request(bfcp->conn, paddr, BFCP_VER2, BFCP_HELLO,
+ confid, userid, bfcp_resp_handler, bfcp, 0);
+ }
+
+ return err;
+}
diff --git a/src/call.c b/src/call.c
new file mode 100644
index 0000000..03f0577
--- /dev/null
+++ b/src/call.c
@@ -0,0 +1,1567 @@
+/**
+ * @file call.c Call Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "call"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+/** Magic number */
+#define MAGIC 0xca11ca11
+#include "magic.h"
+
+
+#ifndef RELEASE
+#define CALL_DEBUG 1 /**< Enable call debugging */
+#endif
+
+#define FOREACH_STREAM \
+ for (le = call->streaml.head; le; le = le->next)
+
+/** Call constants */
+enum {
+ PTIME = 20, /**< Packet time for audio */
+ LOCAL_TIMEOUT = 120, /**< Incoming call timeout in [seconds] */
+};
+
+
+/** Call States */
+enum state {
+ STATE_IDLE = 0,
+ STATE_INCOMING,
+ STATE_OUTGOING,
+ STATE_RINGING,
+ STATE_EARLY,
+ STATE_ESTABLISHED,
+ STATE_TERMINATED
+};
+
+/** SIP Call Control object */
+struct call {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< SIP User-agent */
+ struct account *acc; /**< Account (ref.) */
+ struct sipsess *sess; /**< SIP Session */
+ struct sdp_session *sdp; /**< SDP Session */
+ struct sipsub *sub; /**< Call transfer REFER subscription */
+ struct sipnot *not; /**< REFER/NOTIFY client */
+ struct list streaml; /**< List of mediastreams (struct stream) */
+ struct audio *audio; /**< Audio stream */
+#ifdef USE_VIDEO
+ struct video *video; /**< Video stream */
+ struct bfcp *bfcp; /**< BFCP Client */
+#endif
+ enum state state; /**< Call state */
+ char *local_uri; /**< Local SIP uri */
+ char *local_name; /**< Local display name */
+ char *peer_uri; /**< Peer SIP Address */
+ char *peer_name; /**< Peer display name */
+ struct tmr tmr_inv; /**< Timer for incoming calls */
+ struct tmr tmr_dtmf; /**< Timer for incoming DTMF events */
+ time_t time_start; /**< Time when call started */
+ time_t time_stop; /**< Time when call stopped */
+ bool got_offer; /**< Got SDP Offer from Peer */
+ struct mnat_sess *mnats; /**< Media NAT session */
+ bool mnat_wait; /**< Waiting for MNAT to establish */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ int af; /**< Preferred Address Family */
+ uint16_t scode; /**< Termination status code */
+ call_event_h *eh; /**< Event handler */
+ call_dtmf_h *dtmfh; /**< DTMF handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static int send_invite(struct call *call);
+
+
+static const char *state_name(enum state st)
+{
+ switch (st) {
+
+ case STATE_IDLE: return "IDLE";
+ case STATE_INCOMING: return "INCOMING";
+ case STATE_OUTGOING: return "OUTGOING";
+ case STATE_RINGING: return "RINGING";
+ case STATE_EARLY: return "EARLY";
+ case STATE_ESTABLISHED: return "ESTABLISHED";
+ case STATE_TERMINATED: return "TERMINATED";
+ default: return "???";
+ }
+}
+
+
+static void set_state(struct call *call, enum state st)
+{
+ call->state = st;
+}
+
+
+static void call_stream_start(struct call *call, bool active)
+{
+ const struct sdp_format *sc;
+ int err;
+
+ /* Audio Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+
+ if (ac) {
+ err = audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ err |= audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ if (!err) {
+ err = audio_start(call->audio);
+ }
+ if (err) {
+ DEBUG_WARNING("audio stream: %m\n", err);
+ }
+ }
+ else {
+ info("call: no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("call: audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ /* Video Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data, sc->pt,
+ sc->params);
+ err |= video_decoder_set(call->video, sc->data, sc->pt,
+ sc->rparams);
+ if (!err) {
+ err = video_start(call->video, call->peer_uri);
+ }
+ if (err) {
+ DEBUG_WARNING("video stream: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("call: video stream is disabled..\n");
+ }
+
+ if (call->bfcp) {
+ err = bfcp_start(call->bfcp);
+ if (err) {
+ DEBUG_WARNING("bfcp_start() error: %m\n", err);
+ }
+ }
+#endif
+
+ if (active) {
+ struct le *le;
+
+ tmr_cancel(&call->tmr_inv);
+ call->time_start = time(NULL);
+
+ FOREACH_STREAM {
+ stream_reset(le->data);
+ }
+ }
+}
+
+
+static void call_stream_stop(struct call *call)
+{
+ if (!call)
+ return;
+
+ call->time_stop = time(NULL);
+
+ /* Audio */
+ audio_stop(call->audio);
+
+ /* Video */
+#ifdef USE_VIDEO
+ video_stop(call->video);
+#endif
+
+ tmr_cancel(&call->tmr_inv);
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *fmt, ...)
+{
+ call_event_h *eh = call->eh;
+ void *eh_arg = call->arg;
+ char buf[256];
+ va_list ap;
+
+ if (!eh)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ eh(call, ev, buf, eh_arg);
+}
+
+
+static void invite_timeout(void *arg)
+{
+ struct call *call = arg;
+
+ info("%s: Local timeout after %u seconds\n",
+ call->peer_uri, LOCAL_TIMEOUT);
+
+ call_event_handler(call, CALL_EVENT_CLOSED, "Local timeout");
+}
+
+
+/** Called when all media streams are established */
+static void mnat_handler(int err, uint16_t scode, const char *reason,
+ void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ DEBUG_WARNING("medianat '%s' failed: %m\n",
+ call->acc->mnatid, err);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%m", err);
+ return;
+ }
+ else if (scode) {
+ DEBUG_WARNING("medianat failed: %u %s\n", scode, reason);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%u %s",
+ scode, reason);
+ return;
+ }
+
+ /* Re-INVITE */
+ if (!call->mnat_wait) {
+ info("call: medianat established -- sending Re-INVITE\n");
+ (void)call_modify(call);
+ return;
+ }
+
+ call->mnat_wait = false;
+
+ switch (call->state) {
+
+ case STATE_OUTGOING:
+ (void)send_invite(call);
+ break;
+
+ case STATE_INCOMING:
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int update_media(struct call *call)
+{
+ const struct sdp_format *sc;
+ struct le *le;
+ int err = 0;
+
+ /* media attributes */
+ audio_sdp_attr_decode(call->audio);
+
+#ifdef USE_VIDEO
+ if (call->video)
+ video_sdp_attr_decode(call->video);
+#endif
+
+ /* Update each stream */
+ FOREACH_STREAM {
+ stream_update(le->data);
+ }
+
+ if (call->acc->mnat && call->acc->mnat->updateh && call->mnats)
+ err = call->acc->mnat->updateh(call->mnats);
+
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+ if (ac) {
+ err = audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ err |= audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ }
+ else {
+ info("no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ DEBUG_WARNING("video stream: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("video stream is disabled..\n");
+ }
+#endif
+
+ return err;
+}
+
+
+static void print_summary(const struct call *call)
+{
+ uint32_t dur = call_duration(call);
+ if (!dur)
+ return;
+
+ info("%s: Call with %s terminated (duration: %H)\n",
+ call->local_uri, call->peer_uri, fmt_human_time, &dur);
+}
+
+
+static void call_destructor(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->state != STATE_IDLE)
+ print_summary(call);
+
+ call_stream_stop(call);
+ list_unlink(&call->le);
+ tmr_cancel(&call->tmr_dtmf);
+
+ mem_deref(call->sess);
+ mem_deref(call->local_uri);
+ mem_deref(call->local_name);
+ mem_deref(call->peer_uri);
+ mem_deref(call->peer_name);
+ mem_deref(call->audio);
+#ifdef USE_VIDEO
+ mem_deref(call->video);
+ mem_deref(call->bfcp);
+#endif
+ mem_deref(call->sdp);
+ mem_deref(call->mnats);
+ mem_deref(call->mencs);
+ mem_deref(call->sub);
+ mem_deref(call->not);
+ mem_deref(call->acc);
+}
+
+
+static void audio_event_handler(int key, bool end, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ info("received event: '%c' (end=%d)\n", key, end);
+
+ if (call->dtmfh)
+ call->dtmfh(call, end ? 0x00 : key, call->arg);
+}
+
+
+static void audio_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ DEBUG_WARNING("Audio error: %m (%s)\n", err, str);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+
+
+static void menc_error_handler(int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ DEBUG_WARNING("mediaenc error: %m\n", err);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed");
+}
+
+
+/**
+ * Allocate a new Call state object
+ *
+ * @param callp Pointer to allocated Call state object
+ * @param cfg Global configuration
+ * @param lst List of call objects
+ * @param local_name Local display name (optional)
+ * @param local_uri Local SIP uri
+ * @param acc Account parameters
+ * @param ua User-Agent
+ * @param prm Call parameters
+ * @param msg SIP message for incoming calls
+ * @param xcall Optional call to inherit properties from
+ * @param eh Call event handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_alloc(struct call **callp, const struct config *cfg, struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ call_event_h *eh, void *arg)
+{
+ struct call *call;
+ enum vidmode vidmode = prm ? prm->vidmode : VIDMODE_OFF;
+ bool use_video = true, got_offer = false;
+ int label = 0;
+ int err = 0;
+
+ if (!cfg || !local_uri || !acc || !ua)
+ return EINVAL;
+
+ call = mem_zalloc(sizeof(*call), call_destructor);
+ if (!call)
+ return ENOMEM;
+
+ MAGIC_INIT(call);
+
+ tmr_init(&call->tmr_inv);
+
+ call->acc = mem_ref(acc);
+ call->ua = ua;
+ call->state = STATE_IDLE;
+ call->eh = eh;
+ call->arg = arg;
+ call->af = prm ? prm->af : AF_INET;
+
+ err = str_dup(&call->local_uri, local_uri);
+ if (local_name)
+ err |= str_dup(&call->local_name, local_name);
+ if (err)
+ goto out;
+
+ /* Init SDP info */
+ err = sdp_session_alloc(&call->sdp, net_laddr_af(call->af));
+ if (err)
+ goto out;
+
+ err = sdp_session_set_lattr(call->sdp, true,
+ "tool", "baresip " BARESIP_VERSION);
+ if (err)
+ goto out;
+
+ /* Check for incoming SDP Offer */
+ if (msg && mbuf_get_left(msg->mb))
+ got_offer = true;
+
+ /* Initialise media NAT handling */
+ if (acc->mnat) {
+ err = acc->mnat->sessh(&call->mnats, net_dnsc(), call->af,
+ acc->stun_host, acc->stun_port,
+ acc->stun_user, acc->stun_pass,
+ call->sdp, !got_offer,
+ mnat_handler, call);
+ if (err) {
+ DEBUG_WARNING("mnat session: %m\n", err);
+ goto out;
+ }
+ }
+ call->mnat_wait = true;
+
+ /* Media encryption */
+ if (acc->menc) {
+ if (acc->menc->sessh) {
+ err = acc->menc->sessh(&call->mencs, call->sdp,
+ !got_offer,
+ menc_error_handler, call);
+ if (err) {
+ DEBUG_WARNING("mediaenc session: %m\n", err);
+ goto out;
+ }
+ }
+ }
+
+ /* Audio stream */
+ err = audio_alloc(&call->audio, cfg, call,
+ call->sdp, ++label,
+ acc->mnat, call->mnats, acc->menc, call->mencs,
+ acc->ptime, account_aucodecl(call->acc),
+ audio_event_handler, audio_error_handler, call);
+ if (err)
+ goto out;
+
+#ifdef USE_VIDEO
+ /* We require at least one video codec, and at least one
+ video source or video display */
+ use_video = (vidmode != VIDMODE_OFF)
+ && (list_head(account_vidcodecl(call->acc)) != NULL)
+ && (NULL != vidsrc_find(NULL) || NULL != vidisp_find(NULL));
+
+ /* Video stream */
+ if (use_video) {
+ err = video_alloc(&call->video, cfg,
+ call, call->sdp, ++label,
+ acc->mnat, call->mnats,
+ acc->menc, call->mencs,
+ "main",
+ account_vidcodecl(call->acc));
+ if (err)
+ goto out;
+ }
+
+ if (str_isset(cfg->bfcp.proto)) {
+
+ err = bfcp_alloc(&call->bfcp, call->sdp,
+ cfg->bfcp.proto, !got_offer,
+ acc->mnat, call->mnats);
+ if (err)
+ goto out;
+ }
+#else
+ (void)use_video;
+ (void)vidmode;
+#endif
+
+ /* inherit certain properties from original call */
+ if (xcall) {
+ call->not = mem_ref(xcall->not);
+ }
+
+ list_append(lst, &call->le, call);
+
+ out:
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ return err;
+}
+
+
+int call_connect(struct call *call, const struct pl *paddr)
+{
+ struct sip_addr addr;
+ int err;
+
+ if (!call || !paddr)
+ return EINVAL;
+
+ info("call: connecting to '%r'..\n", paddr);
+
+ /* if the peer-address is a full SIP address then we need
+ * to parse it and extract the SIP uri part.
+ */
+ if (0 == sip_addr_decode(&addr, paddr) && addr.dname.p) {
+ err = pl_strdup(&call->peer_uri, &addr.auri);
+ }
+ else {
+ err = pl_strdup(&call->peer_uri, paddr);
+ }
+ if (err)
+ return err;
+
+ set_state(call, STATE_OUTGOING);
+
+ /* If we are using asyncronous medianat like STUN/TURN, then
+ * wait until completed before sending the INVITE */
+ if (!call->acc->mnat)
+ err = send_invite(call);
+
+ return err;
+}
+
+
+/**
+ * Update the current call by sending Re-INVITE or UPDATE
+ *
+ * @param call Call object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_modify(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ err = call_sdp_get(call, &desc, true);
+ if (!err)
+ err = sipsess_modify(call->sess, desc);
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+int call_hangup(struct call *call, uint16_t scode, const char *reason)
+{
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ switch (call->state) {
+
+ case STATE_INCOMING:
+ if (scode < 400) {
+ scode = 486;
+ reason = "Rejected";
+ }
+ info("call: rejecting incoming call from %s (%u %s)\n",
+ call->peer_uri, scode, reason);
+ (void)sipsess_reject(call->sess, scode, reason, NULL);
+ break;
+
+ default:
+ info("call: terminate call with %s\n", call->peer_uri);
+ call->sess = mem_deref(call->sess);
+ break;
+ }
+
+ set_state(call, STATE_TERMINATED);
+
+ call_stream_stop(call);
+
+ return err;
+}
+
+
+int call_progress(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ err = call_sdp_get(call, &desc, false);
+ if (err)
+ return err;
+
+ err = sipsess_progress(call->sess, 183, "Session Progress",
+ desc, "Allow: %s\r\n", uag_allowed_methods());
+
+ if (!err)
+ call_stream_start(call, false);
+
+ mem_deref(desc);
+
+ return 0;
+}
+
+
+int call_answer(struct call *call, uint16_t scode)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ if (STATE_INCOMING != call->state) {
+ return 0;
+ }
+
+ info("answering call from %s with %u\n", call->peer_uri, scode);
+
+ if (call->got_offer) {
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ err = sdp_encode(&desc, call->sdp, !call->got_offer);
+ if (err)
+ return err;
+
+ err = sipsess_answer(call->sess, scode, "Answering", desc,
+ "Allow: %s\r\n", uag_allowed_methods());
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Check if the current call has an active audio stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_audio(const struct call *call)
+{
+ if (!call)
+ return false;
+
+ return sdp_media_has_media(stream_sdpmedia(audio_strm(call->audio)));
+}
+
+
+/**
+ * Check if the current call has an active video stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_video(const struct call *call)
+{
+ if (!call)
+ return false;
+
+#ifdef USE_VIDEO
+ return sdp_media_has_media(stream_sdpmedia(video_strm(call->video)));
+#else
+ return false;
+#endif
+}
+
+
+/**
+ * Put the current call on hold/resume
+ *
+ * @param call Call object
+ * @param hold True to hold, false to resume
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_hold(struct call *call, bool hold)
+{
+ struct le *le;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri);
+
+ FOREACH_STREAM
+ stream_hold(le->data, hold);
+
+ return call_modify(call);
+}
+
+
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer)
+{
+ return sdp_encode(descp, call->sdp, offer);
+}
+
+
+const char *call_peeruri(const struct call *call)
+{
+ return call ? call->peer_uri : NULL;
+}
+
+
+const char *call_localuri(const struct call *call)
+{
+ return call ? call->local_uri : NULL;
+}
+
+
+/**
+ * Get the name of the peer
+ *
+ * @param call Call object
+ *
+ * @return Peer name
+ */
+const char *call_peername(const struct call *call)
+{
+ return call ? call->peer_name : NULL;
+}
+
+
+int call_debug(struct re_printf *pf, const struct call *call)
+{
+ int err;
+
+ if (!call)
+ return 0;
+
+ err = re_hprintf(pf, "===== Call debug (%s) =====\n",
+ state_name(call->state));
+
+ /* SIP Session debug */
+ err |= re_hprintf(pf,
+ " local_uri: %s <%s>\n"
+ " peer_uri: %s <%s>\n"
+ " af=%s\n",
+ call->local_name, call->local_uri,
+ call->peer_name, call->peer_uri,
+ net_af2name(call->af));
+
+ /* SDP debug */
+ err |= sdp_session_debug(pf, call->sdp);
+
+ return err;
+}
+
+
+static int print_duration(struct re_printf *pf, const struct call *call)
+{
+ const uint32_t dur = call_duration(call);
+ const uint32_t sec = dur%60%60;
+ const uint32_t min = dur/60%60;
+ const uint32_t hrs = dur/60/60;
+
+ return re_hprintf(pf, "%u:%02u:%02u", hrs, min, sec);
+}
+
+
+int call_status(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ switch (call->state) {
+
+ case STATE_EARLY:
+ case STATE_ESTABLISHED:
+ break;
+ default:
+ return 0;
+ }
+
+ err = re_hprintf(pf, "\r[%H]", print_duration, call);
+
+ FOREACH_STREAM
+ err |= stream_print(pf, le->data);
+
+ err |= re_hprintf(pf, " (bit/s)");
+
+#ifdef USE_VIDEO
+ if (call->video)
+ err |= video_print(pf, call->video);
+#endif
+
+ return err;
+}
+
+
+int call_jbuf_stat(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ FOREACH_STREAM
+ err |= stream_jbuf_stat(pf, le->data);
+
+ return err;
+}
+
+
+int call_info(struct re_printf *pf, const struct call *call)
+{
+ if (!call)
+ return 0;
+
+ return re_hprintf(pf, "%H %8s %s", print_duration, call,
+ state_name(call->state), call->peer_uri);
+}
+
+
+/**
+ * Send a DTMF digit to the peer
+ *
+ * @param call Call object
+ * @param key DTMF digit to send (0x00 for key release)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_send_digit(struct call *call, char key)
+{
+ if (!call)
+ return EINVAL;
+
+ return audio_send_digit(call->audio, key);
+}
+
+
+struct ua *call_get_ua(const struct call *call)
+{
+ return call ? call->ua : NULL;
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int sipsess_offer_handler(struct mbuf **descp,
+ const struct sip_msg *msg, void *arg)
+{
+ const bool got_offer = mbuf_get_left(msg->mb);
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ info("call: got re-INVITE%s\n", got_offer ? " (SDP Offer)" : "");
+
+ if (got_offer) {
+
+ /* Decode SDP Offer */
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ /* Encode SDP Answer */
+ return sdp_encode(descp, call->sdp, !got_offer);
+}
+
+
+static int sipsess_answer_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ (void)sdp_decode_multipart(&msg->ctype, msg->mb);
+
+ err = sdp_decode(call->sdp, msg->mb, false);
+ if (err) {
+ DEBUG_WARNING("answer: sdp_decode: %m\n", err);
+ return err;
+ }
+
+ err = update_media(call);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static void sipsess_estab_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+
+ MAGIC_CHECK(call);
+
+ (void)msg;
+
+ if (call->state == STATE_ESTABLISHED)
+ return;
+
+ set_state(call, STATE_ESTABLISHED);
+
+ call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri);
+
+ call_stream_start(call, true);
+
+ /* the transferor will hangup this call */
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 200, "OK");
+ }
+}
+
+
+#ifdef USE_VIDEO
+static void call_handle_info_req(struct call *call, const struct sip_msg *req)
+{
+ struct pl body;
+ bool pfu;
+ int err;
+
+ (void)call;
+
+ pl_set_mbuf(&body, req->mb);
+
+ err = mctrl_handle_media_control(&body, &pfu);
+ if (err)
+ return;
+
+ if (pfu) {
+ video_update_picture(call->video);
+ }
+}
+#endif
+
+
+static void dtmfend_handler(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->dtmfh)
+ call->dtmfh(call, 0x00, call->arg);
+}
+
+
+static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (!pl_strcasecmp(&msg->ctype, "application/dtmf-relay")) {
+
+ struct pl body, sig, dur;
+ int err;
+
+ pl_set_mbuf(&body, msg->mb);
+
+ err = re_regex(body.p, body.l, "Signal=[0-9]+", &sig);
+ err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur);
+
+ if (err) {
+ (void)sip_reply(sip, msg, 400, "Bad Request");
+ }
+ else {
+ char s = pl_u32(&sig);
+ uint32_t duration = pl_u32(&dur);
+
+ if (s == 10) s = '*';
+ else if (s == 11) s = '#';
+ else s += '0';
+
+ info("received DTMF: '%c' (duration=%r)\n", s, &dur);
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ if (call->dtmfh) {
+ tmr_start(&call->tmr_dtmf, duration,
+ dtmfend_handler, call);
+ call->dtmfh(call, s, call->arg);
+ }
+ }
+ }
+#ifdef USE_VIDEO
+ else if (!pl_strcasecmp(&msg->ctype,
+ "application/media_control+xml")) {
+ call_handle_info_req(call, msg);
+ (void)sip_reply(sip, msg, 200, "OK");
+ }
+#endif
+ else {
+ (void)sip_reply(sip, msg, 488, "Not Acceptable Here");
+ }
+}
+
+
+static void sipnot_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (err)
+ info("call: notification closed: %m\n", err);
+ else if (msg)
+ info("call: notification closed: %u %r\n",
+ msg->scode, &msg->reason);
+
+ call->not = mem_deref(call->not);
+}
+
+
+static void sipsess_refer_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ const struct sip_hdr *hdr;
+ int err;
+
+ /* get the transfer target */
+ hdr = sip_msg_hdr(msg, SIP_HDR_REFER_TO);
+ if (!hdr) {
+ DEBUG_WARNING("bad REFER request from %r\n", &msg->from.auri);
+ (void)sip_reply(sip, msg, 400, "Missing Refer-To header");
+ return;
+ }
+
+ /* The REFER creates an implicit subscription.
+ * Reply 202 to the REFER request
+ */
+ call->not = mem_deref(call->not);
+ err = sipevent_accept(&call->not, uag_sipevent_sock(), msg,
+ sipsess_dialog(call->sess), NULL,
+ 202, "Accepted", 60, 60, 60,
+ ua_cuser(call->ua), "message/sipfrag",
+ auth_handler, call->acc, true,
+ sipnot_close_handler, call,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ DEBUG_WARNING("refer: sipevent_accept failed: %m\n", err);
+ return;
+ }
+
+ (void)call_notify_sipfrag(call, 100, "Trying");
+
+ call_event_handler(call, CALL_EVENT_TRANSFER, "%r", &hdr->val);
+}
+
+
+static void sipsess_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ char reason[128] = "";
+
+ MAGIC_CHECK(call);
+
+ if (err) {
+ info("%s: session closed: %m\n", call->peer_uri, err);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 500, "%m", err);
+ }
+ }
+ else if (msg) {
+
+ call->scode = msg->scode;
+
+ (void)re_snprintf(reason, sizeof(reason), "%u %r",
+ msg->scode, &msg->reason);
+
+ info("%s: session closed: %u %r\n",
+ call->peer_uri, msg->scode, &msg->reason);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, msg->scode,
+ "%r", &msg->reason);
+ }
+ }
+ else {
+ info("%s: session closed\n", call->peer_uri);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, reason);
+}
+
+
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg)
+{
+ bool got_offer;
+ int err;
+
+ if (!call || !msg)
+ return EINVAL;
+
+ got_offer = (mbuf_get_left(msg->mb) > 0);
+
+ err = pl_strdup(&call->peer_uri, &msg->from.auri);
+ if (err)
+ return err;
+
+ if (pl_isset(&msg->from.dname)) {
+ err = pl_strdup(&call->peer_name, &msg->from.dname);
+ if (err)
+ return err;
+ }
+
+ if (got_offer) {
+
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ call->got_offer = true;
+ }
+
+ err = sipsess_accept(&call->sess, sess_sock, msg, 180, "Ringing",
+ ua_cuser(call->ua), "application/sdp", NULL,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_estab_handler, sipsess_info_handler,
+ sipsess_refer_handler, sipsess_close_handler,
+ call, "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ DEBUG_WARNING("sipsess_accept: %m\n", err);
+ return err;
+ }
+
+ set_state(call, STATE_INCOMING);
+
+ /* New call */
+ tmr_start(&call->tmr_inv, LOCAL_TIMEOUT*1000, invite_timeout, call);
+
+ if (!call->acc->mnat)
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+
+ return err;
+}
+
+
+static void sipsess_progr_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ bool media;
+
+ MAGIC_CHECK(call);
+
+ info("call: SIP Progress: %u %r (%r)\n",
+ msg->scode, &msg->reason, &msg->ctype);
+
+ if (msg->scode <= 100)
+ return;
+
+ /* check for 18x and content-type
+ *
+ * 1. start media-stream if application/sdp
+ * 2. play local ringback tone if not
+ *
+ * we must also handle changes to/from 180 and 183,
+ * so we reset the media-stream/ringback each time.
+ */
+ if (!pl_strcasecmp(&msg->ctype, "application/sdp")
+ && mbuf_get_left(msg->mb)
+ && !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else if (!sdp_decode_multipart(&msg->ctype, msg->mb) &&
+ !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else
+ media = false;
+
+ switch (msg->scode) {
+
+ case 180:
+ set_state(call, STATE_RINGING);
+ break;
+
+ case 183:
+ set_state(call, STATE_EARLY);
+ break;
+ }
+
+ if (media)
+ call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri);
+ else
+ call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri);
+
+ call_stream_stop(call);
+
+ if (media)
+ call_stream_start(call, false);
+}
+
+
+static int send_invite(struct call *call)
+{
+ const char *routev[1];
+ struct mbuf *desc;
+ int err;
+
+ routev[0] = ua_outbound(call->ua);
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ return err;
+
+ err = sipsess_connect(&call->sess, uag_sipsess_sock(),
+ call->peer_uri,
+ call->local_name,
+ call->local_uri,
+ ua_cuser(call->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ "application/sdp", desc,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_progr_handler, sipsess_estab_handler,
+ sipsess_info_handler, sipsess_refer_handler,
+ sipsess_close_handler, call,
+ "Allow: %s\r\n%H", uag_allowed_methods(),
+ ua_print_supported, call->ua);
+ if (err) {
+ DEBUG_WARNING("sipsess_connect: %m\n", err);
+ }
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Get the current call duration in seconds
+ *
+ * @param call Call object
+ *
+ * @return Duration in seconds
+ */
+uint32_t call_duration(const struct call *call)
+{
+ if (!call || !call->time_start)
+ return 0;
+
+ return (uint32_t)(time(NULL) - call->time_start);
+}
+
+
+/**
+ * Get the audio object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Audio object
+ */
+struct audio *call_audio(const struct call *call)
+{
+ return call ? call->audio : NULL;
+}
+
+
+/**
+ * Get the video object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Video object
+ */
+struct video *call_video(const struct call *call)
+{
+#ifdef USE_VIDEO
+ return call ? call->video : NULL;
+#else
+ (void)call;
+ return NULL;
+#endif
+}
+
+
+/**
+ * Get the list of media streams for the current call
+ *
+ * @param call Call object
+ *
+ * @return List of media streams
+ */
+struct list *call_streaml(const struct call *call)
+{
+ return call ? (struct list *)&call->streaml : NULL;
+}
+
+
+int call_reset_transp(struct call *call)
+{
+ if (!call)
+ return EINVAL;
+
+ sdp_session_set_laddr(call->sdp, net_laddr_af(call->af));
+
+ return call_modify(call);
+}
+
+
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, reason);
+ (void)mbuf_printf(mb, "SIP/2.0 %u %v\n", scode, reason, &ap);
+ va_end(ap);
+
+ mb->pos = 0;
+
+ if (scode >= 200) {
+ err = sipevent_notify(call->not, mb, SIPEVENT_TERMINATED,
+ SIPEVENT_NORESOURCE, 0);
+
+ call->not = mem_deref(call->not);
+ }
+ else {
+ err = sipevent_notify(call->not, mb, SIPEVENT_ACTIVE,
+ SIPEVENT_NORESOURCE, 0);
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ struct pl scode, reason;
+ uint32_t sc;
+
+ if (re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb),
+ "SIP/2.0 [0-9]+ [^\r\n]+", &scode, &reason)) {
+ (void)sip_reply(sip, msg, 400, "Bad sipfrag");
+ return;
+ }
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ sc = pl_u32(&scode);
+
+ if (sc >= 300) {
+ DEBUG_WARNING("call transfer failed: %u %r\n", sc, &reason);
+ }
+ else if (sc >= 200) {
+ call_event_handler(call, CALL_EVENT_CLOSED, "Call transfered");
+ }
+}
+
+
+static void sipsub_close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg)
+{
+ struct call *call = arg;
+
+ (void)substate;
+
+ call->sub = mem_deref(call->sub);
+
+ if (err) {
+ info("call: subscription closed: %m\n", err);
+ }
+ else if (msg && msg->scode >= 300) {
+ info("call: transfer failed: %u %r\n",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+static int normalize_uri(char **out, const char *uri, const struct uri *luri)
+{
+ struct uri uri2;
+ struct pl pl;
+ int err;
+
+ if (!out || !uri || !luri)
+ return EINVAL;
+
+ pl_set_str(&pl, uri);
+
+ if (0 == uri_decode(&uri2, &pl)) {
+
+ err = str_dup(out, uri);
+ }
+ else {
+ uri2 = *luri;
+
+ uri2.user = pl;
+ uri2.password = pl_null;
+ uri2.params = pl_null;
+
+ err = re_sdprintf(out, "%H", uri_encode, &uri2);
+ }
+
+ return err;
+}
+
+
+/**
+ * Transfer the call to a target SIP uri
+ *
+ * @param call Call object
+ * @param uri Target SIP uri
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_transfer(struct call *call, const char *uri)
+{
+ char *nuri;
+ int err;
+
+ if (!call || !uri)
+ return EINVAL;
+
+ err = normalize_uri(&nuri, uri, &call->acc->luri);
+ if (err)
+ return err;
+
+ info("transferring call to %s\n", nuri);
+
+ call->sub = mem_deref(call->sub);
+ err = sipevent_drefer(&call->sub, uag_sipevent_sock(),
+ sipsess_dialog(call->sess), ua_cuser(call->ua),
+ auth_handler, call->acc, true,
+ sipsub_notify_handler, sipsub_close_handler,
+ call,
+ "Refer-To: %s\r\n", nuri);
+ if (err) {
+ DEBUG_WARNING("sipevent_drefer: %m\n", err);
+ }
+
+ mem_deref(nuri);
+
+ return err;
+}
+
+
+int call_af(const struct call *call)
+{
+ return call ? call->af : AF_UNSPEC;
+}
+
+
+uint16_t call_scode(const struct call *call)
+{
+ return call ? call->scode : 0;
+}
+
+
+void call_set_handlers(struct call *call, call_event_h *eh,
+ call_dtmf_h *dtmfh, void *arg)
+{
+ if (!call)
+ return;
+
+ call->eh = eh;
+ call->dtmfh = dtmfh;
+ call->arg = arg;
+}
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..7562081
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,351 @@
+/**
+ * @file src/cmd.c Command Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ REL = 0x00,
+ ESC = 0x1b,
+ DEL = 0x7f,
+};
+
+
+struct cmds {
+ struct le le;
+ const struct cmd *cmdv;
+ size_t cmdc;
+};
+
+struct cmd_ctx {
+ struct mbuf *mb;
+ const struct cmd *cmd;
+};
+
+
+static struct list cmdl; /**< List of command blocks (struct cmds) */
+
+
+static void destructor(void *arg)
+{
+ struct cmds *cmds = arg;
+
+ list_unlink(&cmds->le);
+}
+
+
+static void ctx_destructor(void *arg)
+{
+ struct cmd_ctx *ctx = arg;
+
+ mem_deref(ctx->mb);
+}
+
+
+static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd)
+{
+ struct cmd_ctx *ctx;
+
+ ctx = mem_zalloc(sizeof(*ctx), ctx_destructor);
+ if (!ctx)
+ return ENOMEM;
+
+ ctx->mb = mbuf_alloc(32);
+ if (!ctx->mb) {
+ mem_deref(ctx);
+ return ENOMEM;
+ }
+
+ ctx->cmd = cmd;
+
+ *ctxp = ctx;
+
+ return 0;
+}
+
+
+static struct cmds *cmds_find(const struct cmd *cmdv)
+{
+ struct le *le;
+
+ if (!cmdv)
+ return NULL;
+
+ for (le = cmdl.head; le; le = le->next) {
+ struct cmds *cmds = le->data;
+
+ if (cmds->cmdv == cmdv)
+ return cmds;
+ }
+
+ return NULL;
+}
+
+
+static const struct cmd *cmd_find_by_key(char key)
+{
+ struct le *le;
+
+ for (le = cmdl.tail; le; le = le->prev) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (cmd->key == key && cmd->h)
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+
+static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd)
+{
+ switch (cmd->key) {
+
+ case ' ': return "SPACE";
+ case '\n': return "ENTER";
+ case ESC: return "ESC";
+ }
+
+ buf[0] = cmd->key;
+ buf[1] = '\0';
+
+ if (cmd->flags & CMD_PRM)
+ strncat(buf, " ..", sz-1);
+
+ return buf;
+}
+
+
+static int editor_input(struct mbuf *mb, char key,
+ struct re_printf *pf, bool *del)
+{
+ int err = 0;
+
+ switch (key) {
+
+ case ESC:
+ *del = true;
+ return re_hprintf(pf, "\nCancel\n");
+
+ case REL:
+ break;
+
+ case '\n':
+ *del = true;
+ return re_hprintf(pf, "\n");
+
+ case '\b':
+ case DEL:
+ if (mb->pos > 0)
+ mb->pos = mb->end = (mb->pos - 1);
+ break;
+
+ default:
+ err = mbuf_write_u8(mb, key);
+ break;
+ }
+
+ err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end);
+
+ return err;
+}
+
+
+static int cmd_report(const struct cmd *cmd, struct re_printf *pf,
+ struct mbuf *mb, bool compl)
+{
+ struct cmd_arg arg;
+ int err;
+
+ mb->pos = 0;
+ err = mbuf_strdup(mb, &arg.prm, mb->end);
+ if (err)
+ return err;
+
+ arg.key = cmd->key;
+ arg.complete = compl;
+
+ err = cmd->h(pf, &arg);
+
+ mem_deref(arg.prm);
+
+ return err;
+}
+
+
+static int cmd_process_edit(struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf)
+{
+ struct cmd_ctx *ctx;
+ bool compl = (key == '\n'), del = false;
+ int err;
+
+ if (!ctxp)
+ return EINVAL;
+
+ ctx = *ctxp;
+
+ err = editor_input(ctx->mb, key, pf, &del);
+ if (err)
+ return err;
+
+ if (compl || ctx->cmd->flags & CMD_PROG)
+ err = cmd_report(ctx->cmd, pf, ctx->mb, compl);
+
+ if (del)
+ *ctxp = mem_deref(*ctxp);
+
+ return err;
+}
+
+
+/**
+ * Register commands
+ *
+ * @param cmdv Array of commands
+ * @param cmdc Number of commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_register(const struct cmd *cmdv, size_t cmdc)
+{
+ struct cmds *cmds;
+
+ if (!cmdv || !cmdc)
+ return EINVAL;
+
+ cmds = cmds_find(cmdv);
+ if (cmds)
+ return EALREADY;
+
+ cmds = mem_zalloc(sizeof(*cmds), destructor);
+ if (!cmds)
+ return ENOMEM;
+
+ cmds->cmdv = cmdv;
+ cmds->cmdc = cmdc;
+
+ list_append(&cmdl, &cmds->le, cmds);
+
+ return 0;
+}
+
+
+/**
+ * Unregister commands
+ *
+ * @param cmdv Array of commands
+ */
+void cmd_unregister(const struct cmd *cmdv)
+{
+ mem_deref(cmds_find(cmdv));
+}
+
+
+/**
+ * Process input characters to the command system
+ *
+ * @param ctxp Pointer to context for editor (optional)
+ * @param key Input character
+ * @param pf Print function
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process(struct cmd_ctx **ctxp, char key, struct re_printf *pf)
+{
+ const struct cmd *cmd;
+
+ /* are we in edit-mode? */
+ if (ctxp && *ctxp) {
+
+ if (key == REL)
+ return 0;
+
+ return cmd_process_edit(ctxp, key, pf);
+ }
+
+ cmd = cmd_find_by_key(key);
+ if (cmd) {
+ struct cmd_arg arg;
+
+ /* check for parameters */
+ if (cmd->flags & CMD_PRM) {
+
+ if (ctxp) {
+ int err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+ }
+
+ return cmd_process_edit(ctxp,
+ isdigit(key) ? key : 0,
+ pf);
+ }
+
+ arg.key = key;
+ arg.prm = NULL;
+ arg.complete = true;
+
+ return cmd->h(pf, &arg);
+ }
+
+ if (key == REL)
+ return 0;
+
+ return cmd_print(pf, NULL);
+}
+
+
+/**
+ * Print a list of available commands
+ *
+ * @param pf Print function
+ * @param unused Unused variable
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_print(struct re_printf *pf, void *unused)
+{
+ size_t width = 5;
+ char fmt[32], buf[8];
+ int err = 0;
+ int key;
+
+ (void)unused;
+
+ if (!pf)
+ return EINVAL;
+
+ (void)re_snprintf(fmt, sizeof(fmt), " %%-%zus %%s\n", width);
+
+ err |= re_hprintf(pf, "--- Help ---\n");
+
+ /* print in alphabetical order */
+ for (key = 1; key <= 0x80; key++) {
+
+ const struct cmd *cmd = cmd_find_by_key(key);
+ if (!cmd || !str_isset(cmd->desc))
+ continue;
+
+ err |= re_hprintf(pf, fmt, cmd_name(buf, sizeof(buf), cmd),
+ cmd->desc);
+
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/conf.c b/src/conf.c
new file mode 100644
index 0000000..cf93f1b
--- /dev/null
+++ b/src/conf.c
@@ -0,0 +1,380 @@
+/**
+ * @file conf.c Configuration utils
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <sys/stat.h>
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE ""
+#define DEBUG_LEVEL 0
+#include <re_dbg.h>
+
+
+#ifdef WIN32
+#define open _open
+#define read _read
+#define close _close
+#endif
+
+
+#if defined (WIN32) || defined (__SYMBIAN32__)
+#define DIR_SEP "\\"
+#else
+#define DIR_SEP "/"
+#endif
+
+
+static const char *conf_path = NULL;
+static struct conf *conf_obj;
+
+
+/**
+ * Check if a file exists
+ *
+ * @param path Filename
+ *
+ * @return True if exist, False if not
+ */
+bool conf_fileexist(const char *path)
+{
+ struct stat st;
+
+ if (!path)
+ return false;
+
+ if (stat(path, &st) < 0)
+ return false;
+
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+ return false;
+
+ return st.st_size > 0;
+}
+
+
+static void print_populated(const char *what, uint32_t n)
+{
+ info("Populated %u %s%s\n", n, what, 1==n ? "" : "s");
+}
+
+
+/**
+ * Parse a config file, calling handler for each line
+ *
+ * @param filename Config file
+ * @param ch Line handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_parse(const char *filename, confline_h *ch)
+{
+ struct pl pl, val;
+ struct mbuf *mb;
+ int err = 0, fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return errno;
+
+ mb = mbuf_alloc(1024);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (;;) {
+ uint8_t buf[1024];
+
+ const ssize_t n = read(fd, (void *)buf, sizeof(buf));
+ if (n < 0) {
+ err = errno;
+ break;
+ }
+ else if (n == 0)
+ break;
+
+ err |= mbuf_write_mem(mb, buf, n);
+ }
+
+ pl.p = (const char *)mb->buf;
+ pl.l = mb->end;
+
+ while (pl.p < ((const char *)mb->buf + mb->end) && !err) {
+ const char *lb = pl_strchr(&pl, '\n');
+
+ val.p = pl.p;
+ val.l = lb ? (uint32_t)(lb - pl.p) : pl.l;
+ pl_advance(&pl, val.l + 1);
+
+ if (!val.l || val.p[0] == '#')
+ continue;
+
+ err = ch(&val);
+ }
+
+ out:
+ mem_deref(mb);
+ (void)close(fd);
+
+ return err;
+}
+
+
+/**
+ * Set the path to configuration files
+ *
+ * @param path Configuration path
+ */
+void conf_path_set(const char *path)
+{
+ conf_path = path;
+}
+
+
+/**
+ * Get the path to configuration files
+ *
+ * @param path Buffer to write path
+ * @param sz Size of path buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_path_get(char *path, size_t sz)
+{
+ char buf[256];
+ int err;
+
+ /* Use explicit conf path */
+ if (conf_path) {
+ if (re_snprintf(path, sz, "%s", conf_path) < 0)
+ return ENOMEM;
+ return 0;
+ }
+
+ err = fs_gethome(buf, sizeof(buf));
+ if (err)
+ return err;
+
+ if (re_snprintf(path, sz, "%s" DIR_SEP ".baresip", buf) < 0)
+ return ENOMEM;
+
+ return 0;
+}
+
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng)
+{
+ struct pl r, min, max;
+ uint32_t v;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ err = re_regex(r.p, r.l, "[0-9]+-[0-9]+", &min, &max);
+ if (err) {
+ /* fallback to non-range numeric value */
+ err = conf_get_u32(conf, name, &v);
+ if (err) {
+ warning("conf: %s: could not parse range: (%r)\n",
+ name, &r);
+ return err;
+ }
+
+ rng->min = rng->max = v;
+
+ return err;
+ }
+
+ rng->min = pl_u32(&min);
+ rng->max = pl_u32(&max);
+
+ if (rng->min > rng->max) {
+ warning("conf: %s: invalid range (%u - %u)\n",
+ name, rng->min, rng->max);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2)
+{
+ struct pl r, pl1, pl2 = pl_null;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ err = re_regex(r.p, r.l, "[^,]+,[^]*", &pl1, &pl2);
+ if (err)
+ return err;
+
+ (void)pl_strcpy(&pl1, str1, sz1);
+ if (pl_isset(&pl2))
+ (void)pl_strcpy(&pl2, str2, sz2);
+
+ return 0;
+}
+
+
+int conf_get_vidsz(const struct conf *conf, const char *name, struct vidsz *sz)
+{
+ struct pl r, w, h;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ w.l = h.l = 0;
+ err = re_regex(r.p, r.l, "[0-9]+x[0-9]+", &w, &h);
+ if (err)
+ return err;
+
+ if (pl_isset(&w) && pl_isset(&h)) {
+ sz->w = pl_u32(&w);
+ sz->h = pl_u32(&h);
+ }
+
+ /* check resolution */
+ if (sz->w & 0x1 || sz->h & 0x1) {
+ warning("conf: %s: should be multiple of 2 (%u x %u)\n",
+ name, sz->w, sz->h);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa)
+{
+ struct pl opt;
+ int err;
+
+ if (!conf || !name || !sa)
+ return EINVAL;
+
+ err = conf_get(conf, name, &opt);
+ if (err)
+ return err;
+
+ return sa_decode(sa, opt.p, opt.l);
+}
+
+
+/**
+ * Configure the system with default settings
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_configure(void)
+{
+ char path[256], file[256];
+ int err;
+
+#if defined (WIN32) || defined (__SYMBIAN32__)
+ dbg_init(DBG_INFO, DBG_NONE);
+#endif
+
+ err = conf_path_get(path, sizeof(path));
+ if (err) {
+ warning("conf: could not get config path: %m\n", err);
+ return err;
+ }
+
+ if (re_snprintf(file, sizeof(file), "%s/config", path) < 0)
+ return ENOMEM;
+
+ if (!conf_fileexist(file)) {
+
+ (void)fs_mkdir(path, 0700);
+
+ err = config_write_template(file, conf_config());
+ if (err)
+ goto out;
+ }
+
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = config_parse_conf(conf_config(), conf_obj);
+ if (err)
+ goto out;
+
+ out:
+ conf_obj = mem_deref(conf_obj);
+ return err;
+}
+
+
+/**
+ * Load all modules from config file
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_modules(void)
+{
+ char path[256], file[256];
+ int err;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err)
+ return err;
+
+ if (re_snprintf(file, sizeof(file), "%s/config", path) < 0)
+ return ENOMEM;
+
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = module_init(conf_obj);
+ if (err) {
+ warning("conf: configure module parse error (%m)\n", err);
+ goto out;
+ }
+
+ print_populated("audio codec", list_count(aucodec_list()));
+ print_populated("audio filter", list_count(aufilt_list()));
+#ifdef USE_VIDEO
+ print_populated("video codec", list_count(vidcodec_list()));
+ print_populated("video filter", list_count(vidfilt_list()));
+#endif
+
+ out:
+ conf_obj = mem_deref(conf_obj);
+ return err;
+}
+
+
+/**
+ * Get the current configuration object
+ *
+ * @return Config object
+ *
+ * @note It is only available during init
+ */
+struct conf *conf_cur(void)
+{
+ return conf_obj;
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..dd65c52
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,706 @@
+/**
+ * @file config.c Core Configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <dirent.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#undef MOD_PRE
+#define MOD_PRE "" /**< Module prefix */
+
+
+/** Core Run-time Configuration - populated from config file */
+/** @todo: move config parsing/decoding to a module */
+static struct config core_config = {
+ /* Input */
+ {
+ "/dev/event0",
+ 5555
+ },
+
+ /** SIP User-Agent */
+ {
+ 16,
+ "",
+ "",
+ ""
+ },
+
+ /** Audio */
+ {
+ "","",
+ "","",
+ "","",
+ {8000, 48000},
+ {1, 2},
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ AUDIO_MODE_POLL,
+ },
+
+#ifdef USE_VIDEO
+ /** Video */
+ {
+ "", "",
+ "", "",
+ 352, 288,
+ 512000,
+ 25,
+ },
+#endif
+
+ /** Audio/Video Transport */
+ {
+ 0xb8,
+ {1024, 49152},
+ {0, 0},
+ true,
+ false,
+ {5, 10},
+ false
+ },
+
+ /* Network */
+ {
+ ""
+ },
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ {
+ ""
+ },
+#endif
+};
+
+
+static int range_print(struct re_printf *pf, const struct range *rng)
+{
+ if (!rng)
+ return 0;
+
+ return re_hprintf(pf, "%u-%u", rng->min, rng->max);
+}
+
+
+static int dns_server_handler(const struct pl *pl, void *arg)
+{
+ struct sa sa;
+ int err;
+
+ (void)arg;
+
+ err = sa_decode(&sa, pl->p, pl->l);
+ if (err) {
+ warning("config: dns_server: could not decode `%r'\n", pl);
+ return err;
+ }
+
+ err = net_dnssrv_add(&sa);
+ if (err) {
+ warning("config: failed to add nameserver %r: %m\n", pl, err);
+ }
+
+ return err;
+}
+
+
+int config_parse_conf(struct config *cfg, const struct conf *conf)
+{
+ struct pl pollm, as, ap;
+ enum poll_method method;
+ struct vidsz size = {0, 0};
+ uint32_t v;
+ int err = 0;
+
+ if (!cfg || !conf)
+ return EINVAL;
+
+ /* Core */
+ if (0 == conf_get(conf, "poll_method", &pollm)) {
+ if (0 == poll_method_type(&method, &pollm)) {
+ err = poll_method_set(method);
+ if (err) {
+ warning("config: poll method (%r) set: %m\n",
+ &pollm, err);
+ }
+ }
+ else {
+ warning("config: unknown poll method (%r)\n", &pollm);
+ }
+ }
+
+ /* Input */
+ (void)conf_get_str(conf, "input_device", cfg->input.device,
+ sizeof(cfg->input.device));
+ (void)conf_get_u32(conf, "input_port", &cfg->input.port);
+
+ /* SIP */
+ (void)conf_get_u32(conf, "sip_trans_bsize", &cfg->sip.trans_bsize);
+ (void)conf_get_str(conf, "sip_listen", cfg->sip.local,
+ sizeof(cfg->sip.local));
+ (void)conf_get_str(conf, "sip_certificate", cfg->sip.cert,
+ sizeof(cfg->sip.cert));
+
+ /* Audio */
+ (void)conf_get_csv(conf, "audio_player",
+ cfg->audio.play_mod,
+ sizeof(cfg->audio.play_mod),
+ cfg->audio.play_dev,
+ sizeof(cfg->audio.play_dev));
+
+ (void)conf_get_csv(conf, "audio_source",
+ cfg->audio.src_mod, sizeof(cfg->audio.src_mod),
+ cfg->audio.src_dev, sizeof(cfg->audio.src_dev));
+
+ (void)conf_get_csv(conf, "audio_alert",
+ cfg->audio.alert_mod,
+ sizeof(cfg->audio.alert_mod),
+ cfg->audio.alert_dev,
+ sizeof(cfg->audio.alert_dev));
+
+ (void)conf_get_range(conf, "audio_srate", &cfg->audio.srate);
+ (void)conf_get_range(conf, "audio_channels", &cfg->audio.channels);
+ (void)conf_get_u32(conf, "ausrc_srate", &cfg->audio.srate_src);
+ (void)conf_get_u32(conf, "auplay_srate", &cfg->audio.srate_play);
+ (void)conf_get_u32(conf, "ausrc_channels", &cfg->audio.channels_src);
+ (void)conf_get_u32(conf, "auplay_channels", &cfg->audio.channels_play);
+
+ if (0 == conf_get(conf, "audio_source", &as) &&
+ 0 == conf_get(conf, "audio_player", &ap))
+ cfg->audio.src_first = as.p < ap.p;
+
+#ifdef USE_VIDEO
+ /* Video */
+ (void)conf_get_csv(conf, "video_source",
+ cfg->video.src_mod, sizeof(cfg->video.src_mod),
+ cfg->video.src_dev, sizeof(cfg->video.src_dev));
+ (void)conf_get_csv(conf, "video_display",
+ cfg->video.disp_mod, sizeof(cfg->video.disp_mod),
+ cfg->video.disp_dev, sizeof(cfg->video.disp_dev));
+ if (0 == conf_get_vidsz(conf, "video_size", &size)) {
+ cfg->video.width = size.w;
+ cfg->video.height = size.h;
+ }
+ (void)conf_get_u32(conf, "video_bitrate", &cfg->video.bitrate);
+ (void)conf_get_u32(conf, "video_fps", &cfg->video.fps);
+#else
+ (void)size;
+#endif
+
+ /* AVT - Audio/Video Transport */
+ if (0 == conf_get_u32(conf, "rtp_tos", &v))
+ cfg->avt.rtp_tos = v;
+ (void)conf_get_range(conf, "rtp_ports", &cfg->avt.rtp_ports);
+ if (0 == conf_get_range(conf, "rtp_bandwidth",
+ &cfg->avt.rtp_bw)) {
+ cfg->avt.rtp_bw.min *= 1024;
+ cfg->avt.rtp_bw.max *= 1024;
+ }
+ (void)conf_get_bool(conf, "rtcp_enable", &cfg->avt.rtcp_enable);
+ (void)conf_get_bool(conf, "rtcp_mux", &cfg->avt.rtcp_mux);
+ (void)conf_get_range(conf, "jitter_buffer_delay",
+ &cfg->avt.jbuf_del);
+ (void)conf_get_bool(conf, "rtp_stats", &cfg->avt.rtp_stats);
+
+ if (err) {
+ warning("config: configure parse error (%m)\n", err);
+ }
+
+ /* Network */
+ (void)conf_apply(conf, "dns_server", dns_server_handler, NULL);
+ (void)conf_get_str(conf, "net_interface",
+ cfg->net.ifname, sizeof(cfg->net.ifname));
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ (void)conf_get_str(conf, "bfcp_proto", cfg->bfcp.proto,
+ sizeof(cfg->bfcp.proto));
+#endif
+
+ return err;
+}
+
+
+int config_print(struct re_printf *pf, const struct config *cfg)
+{
+ int err;
+
+ if (!cfg)
+ return 0;
+
+ err = re_hprintf(pf,
+ "\n"
+ "# Input\n"
+ "input_device\t\t%s\n"
+ "input_port\t\t%u\n"
+ "\n"
+ "# SIP\n"
+ "sip_trans_bsize\t\t%u\n"
+ "sip_listen\t\t%s\n"
+ "sip_certificate\t%s\n"
+ "\n"
+ "# Audio\n"
+ "audio_player\t\t%s,%s\n"
+ "audio_source\t\t%s,%s\n"
+ "audio_alert\t\t%s,%s\n"
+ "audio_srate\t\t%H\n"
+ "audio_channels\t\t%H\n"
+ "auplay_srate\t\t%u\n"
+ "ausrc_srate\t\t%u\n"
+ "auplay_channels\t\t%u\n"
+ "ausrc_channels\t\t%u\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# Video\n"
+ "video_source\t\t%s,%s\n"
+ "video_display\t\t%s,%s\n"
+ "video_size\t\t\"%ux%u\"\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n"
+ "\n"
+#endif
+ "# AVT\n"
+ "rtp_tos\t\t\t%u\n"
+ "rtp_ports\t\t%H\n"
+ "rtp_bandwidth\t\t%H\n"
+ "rtcp_enable\t\t%s\n"
+ "rtcp_mux\t\t%s\n"
+ "jitter_buffer_delay\t%H\n"
+ "rtp_stats\t\t%s\n"
+ "\n"
+ "# Network\n"
+ "net_interface\t\t%s\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# BFCP\n"
+ "bfcp_proto\t\t%s\n"
+ "\n"
+#endif
+ ,
+
+ cfg->input.device, cfg->input.port,
+
+ cfg->sip.trans_bsize, cfg->sip.local, cfg->sip.cert,
+
+ cfg->audio.play_mod, cfg->audio.play_dev,
+ cfg->audio.src_mod, cfg->audio.src_dev,
+ cfg->audio.alert_mod, cfg->audio.alert_dev,
+ range_print, &cfg->audio.srate,
+ range_print, &cfg->audio.channels,
+ cfg->audio.srate_play, cfg->audio.srate_src,
+ cfg->audio.channels_play, cfg->audio.channels_src,
+
+#ifdef USE_VIDEO
+ cfg->video.src_mod, cfg->video.src_dev,
+ cfg->video.disp_mod, cfg->video.disp_dev,
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps,
+#endif
+
+ cfg->avt.rtp_tos,
+ range_print, &cfg->avt.rtp_ports,
+ range_print, &cfg->avt.rtp_bw,
+ cfg->avt.rtcp_enable ? "yes" : "no",
+ cfg->avt.rtcp_mux ? "yes" : "no",
+ range_print, &cfg->avt.jbuf_del,
+ cfg->avt.rtp_stats ? "yes" : "no",
+
+ cfg->net.ifname
+
+#ifdef USE_VIDEO
+ ,cfg->bfcp.proto
+#endif
+ );
+
+ return err;
+}
+
+
+static const char *default_audio_device(void)
+{
+#ifdef DARWIN
+ return "coreaudio,nil";
+#elif defined (FREEBSD)
+ return "oss,/dev/dsp";
+#elif defined (WIN32)
+ return "winwave,nil";
+#else
+ return "alsa,default";
+#endif
+}
+
+
+#ifdef USE_VIDEO
+static const char *default_video_device(void)
+{
+#ifdef DARWIN
+ return "qtcapture,nil";
+#else
+ return "v4l2,/dev/video0";
+#endif
+}
+
+
+static const char *default_video_display(void)
+{
+#ifdef DARWIN
+ return "opengl,nil";
+#else
+ return "x11,nil";
+#endif
+}
+#endif
+
+
+static int default_interface_print(struct re_printf *pf, void *unused)
+{
+ char ifname[64];
+ (void)unused;
+
+ if (0 == net_rt_default_get(AF_INET, ifname, sizeof(ifname)))
+ return re_hprintf(pf, "%s", ifname);
+ else
+ return re_hprintf(pf, "eth0");
+}
+
+
+static int core_config_template(struct re_printf *pf, const struct config *cfg)
+{
+ int err = 0;
+
+ if (!cfg)
+ return 0;
+
+ err |= re_hprintf(pf,
+ "\n# Core\n"
+ "poll_method\t\t%s\t\t# poll, select, epoll ..\n"
+ "\n# Input\n"
+ "input_device\t\t/dev/event0\n"
+ "input_port\t\t5555\n"
+ "\n# SIP\n"
+ "sip_trans_bsize\t\t128\n"
+ "#sip_listen\t\t0.0.0.0:5060\n"
+ "#sip_certificate\tcert.pem\n"
+ "\n# Audio\n"
+ "audio_player\t\t%s\n"
+ "audio_source\t\t%s\n"
+ "audio_alert\t\t%s\n"
+ "audio_srate\t\t%u-%u\n"
+ "audio_channels\t\t%u-%u\n"
+ "#ausrc_srate\t\t48000\n"
+ "#auplay_srate\t\t48000\n"
+ "#ausrc_channels\t\t0\n"
+ "#auplay_channels\t\t0\n"
+ ,
+ poll_method_name(poll_method_best()),
+ default_audio_device(),
+ default_audio_device(),
+ default_audio_device(),
+ cfg->audio.srate.min, cfg->audio.srate.max,
+ cfg->audio.channels.min, cfg->audio.channels.max);
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# Video\n"
+ "#video_source\t\t%s\n"
+ "#video_display\t\t%s\n"
+ "video_size\t\t%dx%d\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n",
+ default_video_device(),
+ default_video_display(),
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps);
+#endif
+
+ err |= re_hprintf(pf,
+ "\n# AVT - Audio/Video Transport\n"
+ "rtp_tos\t\t\t184\n"
+ "#rtp_ports\t\t10000-20000\n"
+ "#rtp_bandwidth\t\t512-1024 # [kbit/s]\n"
+ "rtcp_enable\t\tyes\n"
+ "rtcp_mux\t\tno\n"
+ "jitter_buffer_delay\t%u-%u\t\t# frames\n"
+ "rtp_stats\t\tno\n"
+ "\n# Network\n"
+ "#dns_server\t\t10.0.0.1:53\n"
+ "#net_interface\t\t%H\n",
+ cfg->avt.jbuf_del.min, cfg->avt.jbuf_del.max,
+ default_interface_print, NULL);
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# BFCP\n"
+ "#bfcp_proto\t\tudp\n");
+#endif
+
+ return err;
+}
+
+
+static uint32_t count_modules(const char *path)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ uint32_t n = 0;
+
+ dirp = opendir(path);
+ if (!dirp)
+ return 0;
+
+ while ((dp = readdir(dirp)) != NULL) {
+
+ size_t len = strlen(dp->d_name);
+ const size_t x = sizeof(MOD_EXT)-1;
+
+ if (len <= x)
+ continue;
+
+ if (0==memcmp(&dp->d_name[len-x], MOD_EXT, x))
+ ++n;
+ }
+
+ (void)closedir(dirp);
+
+ return n;
+}
+
+
+static const char *detect_module_path(bool *valid)
+{
+ static const char * const pathv[] = {
+#if defined (PREFIX)
+ "" PREFIX "/lib/baresip/modules",
+#else
+ "/usr/local/lib/baresip/modules",
+ "/usr/lib/baresip/modules",
+#endif
+ };
+ const char *current = pathv[0];
+ uint32_t nmax = 0;
+
+ for (size_t i=0; i<ARRAY_SIZE(pathv); i++) {
+
+ uint32_t n = count_modules(pathv[i]);
+
+ info("%s: detected %u modules\n", pathv[i], n);
+
+ if (n > nmax) {
+ nmax = n;
+ current = pathv[i];
+ }
+ }
+
+ if (nmax > 0)
+ *valid = true;
+
+ return current;
+}
+
+
+int config_write_template(const char *file, const struct config *cfg)
+{
+ FILE *f = NULL;
+ int err = 0;
+ const char *modpath;
+ bool modpath_valid = false;
+
+ if (!file || !cfg)
+ return EINVAL;
+
+ info("config: creating config template %s\n", file);
+
+ f = fopen(file, "w");
+ if (!f) {
+ warning("config: writing %s: %m\n", file, errno);
+ return errno;
+ }
+
+ (void)re_fprintf(f,
+ "#\n"
+ "# baresip configuration\n"
+ "#\n"
+ "\n"
+ "#------------------------------------"
+ "------------------------------------------\n");
+
+ (void)re_fprintf(f, "%H", core_config_template, cfg);
+
+ (void)re_fprintf(f,
+ "\n#------------------------------------"
+ "------------------------------------------\n"
+ "# Modules\n"
+ "\n");
+
+ modpath = detect_module_path(&modpath_valid);
+ (void)re_fprintf(f, "%smodule_path\t\t%s\n",
+ modpath_valid ? "" : "#", modpath);
+
+ (void)re_fprintf(f, "\n# UI Modules\n");
+#if defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "wincons" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stdio" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cons" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "evdev" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "httpd" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio codec Modules (in order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "opus" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "silk" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "amr" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g7221" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g722" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g726" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "g711" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gsm" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "l16" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "bv32" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "vumeter" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sndfile" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_aec" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_pp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "plc" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio driver Modules\n");
+#if defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n");
+#elif defined (__SYMBIAN32__)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "mda" MOD_EXT "\n");
+#elif defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n");
+
+#ifdef USE_VIDEO
+
+ (void)re_fprintf(f, "\n# Video codec Modules (in order)\n");
+#ifdef USE_FFMPEG
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vpx" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "selfview" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video source modules\n");
+#if defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "qtcapture" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2" MOD_EXT "\n");
+#endif
+#ifdef USE_FFMPEG
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avformat" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11grab" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cairo" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video display modules\n");
+#ifdef DARWIN
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opengl" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sdl2" MOD_EXT "\n");
+
+#endif /* USE_VIDEO */
+
+ (void)re_fprintf(f,
+ "\n# Audio/Video source modules\n"
+ "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n"
+ "#module\t\t\t" MOD_PRE "gst" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media NAT modules\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stun" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "turn" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "ice" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "natpmp" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media encryption modules\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "dtls_srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Temporary Modules (loaded then unloaded)\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "#module_tmp\t\t" MOD_PRE "uuid" MOD_EXT "\n");
+ (void)re_fprintf(f, "module_tmp\t\t" MOD_PRE "account" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Application Modules\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "auloop"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "contact"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "menu"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mwi"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "natbd"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "presence"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "syslog"MOD_EXT"\n");
+#ifdef USE_VIDEO
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "vidloop"MOD_EXT"\n");
+#endif
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Module parameters\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n# Speex codec parameters\n");
+ (void)re_fprintf(f, "speex_quality\t\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_complexity\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_enhancement\t0 # 0-1\n");
+ (void)re_fprintf(f, "speex_vbr\t\t0 # Variable Bit Rate 0-1\n");
+ (void)re_fprintf(f, "speex_vad\t\t0 # Voice Activity Detection 0-1\n");
+ (void)re_fprintf(f, "speex_agc_level\t\t8000\n");
+
+ (void)re_fprintf(f, "\n# NAT Behavior Discovery\n");
+ (void)re_fprintf(f, "natbd_server\t\tcreytiv.com\n");
+ (void)re_fprintf(f, "natbd_interval\t\t600\t\t# in seconds\n");
+
+ (void)re_fprintf(f,
+ "\n# Selfview\n"
+ "video_selfview\t\twindow # {window,pip}\n"
+ "#selfview_size\t\t64x64\n");
+
+ (void)re_fprintf(f,
+ "\n# ICE\n"
+ "ice_turn\t\tno\n"
+ "ice_debug\t\tno\n"
+ "ice_nomination\t\tregular\t# {regular,aggressive}\n"
+ "ice_mode\t\tfull\t# {full,lite}\n");
+
+ if (f)
+ (void)fclose(f);
+
+ return err;
+}
+
+
+struct config *conf_config(void)
+{
+ return &core_config;
+}
diff --git a/src/contact.c b/src/contact.c
new file mode 100644
index 0000000..5161a1c
--- /dev/null
+++ b/src/contact.c
@@ -0,0 +1,161 @@
+/**
+ * @file src/contact.c Contacts handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+struct contact {
+ struct le le;
+ struct sip_addr addr;
+ char *buf;
+ enum presence_status status;
+};
+
+static struct list cl;
+
+
+static void destructor(void *arg)
+{
+ struct contact *c = arg;
+
+ list_unlink(&c->le);
+ mem_deref(c->buf);
+}
+
+
+/**
+ * Add a contact
+ *
+ * @param contactp Pointer to allocated contact (optional)
+ * @param addr Contact in SIP address format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_add(struct contact **contactp, const struct pl *addr)
+{
+ struct contact *c;
+ struct pl pl;
+ int err;
+
+ c = mem_zalloc(sizeof(*c), destructor);
+ if (!c)
+ return ENOMEM;
+
+ err = pl_strdup(&c->buf, addr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, c->buf);
+
+ err = sip_addr_decode(&c->addr, &pl);
+ if (err) {
+ warning("contact: decode error '%r'\n", addr);
+ goto out;
+ }
+
+ c->status = PRESENCE_UNKNOWN;
+
+ list_append(&cl, &c->le, c);
+
+ out:
+ if (err)
+ mem_deref(c);
+ else if (contactp)
+ *contactp = c;
+
+ return err;
+}
+
+
+/**
+ * Get the SIP address of a contact
+ *
+ * @param c Contact
+ *
+ * @return SIP Address
+ */
+struct sip_addr *contact_addr(const struct contact *c)
+{
+ return c ? (struct sip_addr *)&c->addr : NULL;
+}
+
+
+/**
+ * Get the contact string
+ *
+ * @param c Contact
+ *
+ * @return Contact string
+ */
+const char *contact_str(const struct contact *c)
+{
+ return c ? c->buf : NULL;
+}
+
+
+/**
+ * Get the list of contacts
+ *
+ * @return List of contacts
+ */
+struct list *contact_list(void)
+{
+ return &cl;
+}
+
+
+void contact_set_presence(struct contact *c, enum presence_status status)
+{
+ if (!c)
+ return;
+
+ if (c->status != PRESENCE_UNKNOWN && c->status != status) {
+
+ info("<%r> changed status from %s to %s\n", &c->addr.auri,
+ contact_presence_str(c->status),
+ contact_presence_str(status));
+ }
+
+ c->status = status;
+}
+
+
+const char *contact_presence_str(enum presence_status status)
+{
+ switch (status) {
+
+ default:
+ case PRESENCE_UNKNOWN: return "\x1b[32m\x1b[;m";
+ case PRESENCE_OPEN: return "\x1b[32mOnline\x1b[;m";
+ case PRESENCE_CLOSED: return "\x1b[31mOffline\x1b[;m";
+ case PRESENCE_BUSY: return "\x1b[31mBusy\x1b[;m";
+ }
+}
+
+
+int contacts_print(struct re_printf *pf, void *unused)
+{
+ struct le *le;
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n",
+ list_count(contact_list()));
+
+ for (le = list_head(contact_list()); le && !err; le = le->next) {
+ const struct contact *c = le->data;
+ const struct sip_addr *addr = &c->addr;
+
+ err = re_hprintf(pf, "%20s %r <%r>\n",
+ contact_presence_str(c->status),
+ &addr->dname, &addr->auri);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..9b92b4b
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,389 @@
+/**
+ * @file core.h Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/**
+ * RFC 3551:
+ *
+ * 0 - 95 Static payload types
+ * 96 - 127 Dynamic payload types
+ */
+enum {
+ PT_CN = 13,
+ PT_STAT_MIN = 0,
+ PT_STAT_MAX = 95,
+ PT_DYN_MIN = 96,
+ PT_DYN_MAX = 127
+};
+
+
+/** Media constants */
+enum {
+ AUDIO_BANDWIDTH = 128000 /**< Bandwidth for audio in bits/s */
+};
+
+
+/*
+ * Account
+ */
+
+
+/** Defines the answermodes */
+enum answermode {
+ ANSWERMODE_MANUAL = 0,
+ ANSWERMODE_EARLY,
+ ANSWERMODE_AUTO
+};
+
+struct account {
+ char *buf; /**< Buffer for the SIP address */
+ struct sip_addr laddr; /**< Decoded SIP address */
+ struct uri luri; /**< Decoded AOR uri */
+ char *dispname; /**< Display name */
+ char *aor; /**< Local SIP uri */
+
+ /* parameters: */
+ enum answermode answermode; /**< Answermode for incoming calls */
+ struct le acv[8]; /**< List elements for aucodecl */
+ struct list aucodecl; /**< List of preferred audio-codecs */
+ char *auth_user; /**< Authentication username */
+ char *auth_pass; /**< Authentication password */
+ char *mnatid; /**< Media NAT handling */
+ char *mencid; /**< Media encryption type */
+ const struct mnat *mnat; /**< MNAT module */
+ const struct menc *menc; /**< MENC module */
+ char *outbound[2]; /**< Optional SIP outbound proxies */
+ uint32_t ptime; /**< Configured packet time in [ms] */
+ uint32_t regint; /**< Registration interval in [seconds] */
+ char *regq; /**< Registration Q-value */
+ char *rtpkeep; /**< RTP Keepalive mechanism */
+ char *sipnat; /**< SIP Nat mechanism */
+ char *stun_user; /**< STUN Username */
+ char *stun_pass; /**< STUN Password */
+ char *stun_host; /**< STUN Hostname */
+ uint16_t stun_port; /**< STUN Port number */
+ struct le vcv[4]; /**< List elements for vidcodecl */
+ struct list vidcodecl; /**< List of preferred video-codecs */
+};
+
+
+/*
+ * Audio Player
+ */
+
+struct auplay_st {
+ struct auplay *ap;
+};
+
+struct auplay {
+ struct le le;
+ const char *name;
+ auplay_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Source
+ */
+
+struct ausrc_st {
+ struct ausrc *as;
+};
+
+struct ausrc {
+ struct le le;
+ const char *name;
+ ausrc_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Stream
+ */
+
+struct audio;
+
+typedef void (audio_event_h)(int key, bool end, void *arg);
+typedef void (audio_err_h)(int err, const char *str, void *arg);
+
+int audio_alloc(struct audio **ap, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ uint32_t ptime, const struct list *aucodecl,
+ audio_event_h *eventh, audio_err_h *errh, void *arg);
+int audio_start(struct audio *a);
+void audio_stop(struct audio *a);
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params);
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params);
+struct stream *audio_strm(const struct audio *a);
+int audio_send_digit(struct audio *a, char key);
+void audio_sdp_attr_decode(struct audio *a);
+
+
+/*
+ * BFCP
+ */
+
+struct bfcp;
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess);
+int bfcp_start(struct bfcp *bfcp);
+
+
+/*
+ * Call Control
+ */
+
+struct call;
+
+/** Call parameters */
+struct call_prm {
+ enum vidmode vidmode;
+ int af;
+};
+
+int call_alloc(struct call **callp, const struct config *cfg,
+ struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ call_event_h *eh, void *arg);
+int call_connect(struct call *call, const struct pl *paddr);
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg);
+int call_hangup(struct call *call, uint16_t scode, const char *reason);
+int call_progress(struct call *call);
+int call_answer(struct call *call, uint16_t scode);
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer);
+int call_jbuf_stat(struct re_printf *pf, const struct call *call);
+int call_info(struct re_printf *pf, const struct call *call);
+int call_reset_transp(struct call *call);
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...);
+int call_af(const struct call *call);
+
+
+/*
+ * Conf
+ */
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng);
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2);
+
+
+/*
+ * Media control
+ */
+
+int mctrl_handle_media_control(struct pl *body, bool *pfu);
+
+
+/*
+ * Media NAT traversal
+ */
+
+struct mnat {
+ struct le le;
+ const char *id;
+ const char *ftag;
+ mnat_sess_h *sessh;
+ mnat_media_h *mediah;
+ mnat_update_h *updateh;
+};
+
+const struct mnat *mnat_find(const char *id);
+
+
+/*
+ * Metric
+ */
+
+struct metric {
+ /* internal stuff: */
+ struct tmr tmr;
+ uint64_t ts_start;
+ bool started;
+
+ /* counters: */
+ uint32_t n_packets;
+ uint32_t n_bytes;
+ uint32_t n_err;
+
+ /* bitrate calculation */
+ uint32_t cur_bitrate;
+ uint64_t ts_last;
+ uint32_t n_bytes_last;
+};
+
+void metric_reset(struct metric *metric);
+void metric_add_packet(struct metric *metric, size_t packetsize);
+uint32_t metric_avg_bitrate(const struct metric *metric);
+
+
+/*
+ * Module
+ */
+
+int module_init(const struct conf *conf);
+void module_app_unload(void);
+
+
+/*
+ * Network
+ */
+
+int net_reset(void);
+
+
+/*
+ * Register client
+ */
+
+struct reg;
+
+int reg_add(struct list *lst, struct ua *ua, int regid);
+int reg_register(struct reg *reg, const char *reg_uri,
+ const char *params, uint32_t regint, const char *outbound);
+void reg_unregister(struct reg *reg);
+bool reg_isok(const struct reg *reg);
+int reg_sipfd(const struct reg *reg);
+int reg_debug(struct re_printf *pf, const struct reg *reg);
+int reg_status(struct re_printf *pf, const struct reg *reg);
+
+
+/*
+ * RTP keepalive
+ */
+
+struct rtpkeep;
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp);
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts);
+
+
+/*
+ * SIP Request
+ */
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...);
+
+
+/*
+ * SDP
+ */
+
+int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb);
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m);
+
+
+/*
+ * Stream
+ */
+
+struct stream;
+struct rtp_header;
+
+enum {STREAM_PRESZ = 4+12}; /* same as RTP_HEADER_SIZE */
+
+typedef void (stream_rtp_h)(const struct rtp_header *hdr, struct mbuf *mb,
+ void *arg);
+typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg);
+
+int stream_alloc(struct stream **sp, const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg);
+struct sdp_media *stream_sdpmedia(const struct stream *s);
+int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb);
+void stream_update(struct stream *s);
+void stream_update_encoder(struct stream *s, int pt_enc);
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s);
+void stream_hold(struct stream *s, bool hold);
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx);
+void stream_send_fir(struct stream *s, bool pli);
+void stream_reset(struct stream *s);
+void stream_set_bw(struct stream *s, uint32_t bps);
+int stream_debug(struct re_printf *pf, const struct stream *s);
+int stream_print(struct re_printf *pf, const struct stream *s);
+
+
+/*
+ * User-Agent
+ */
+
+struct ua;
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...);
+void ua_printf(const struct ua *ua, const char *fmt, ...);
+
+struct tls *uag_tls(void);
+const char *uag_allowed_methods(void);
+
+
+/*
+ * Video Display
+ */
+
+struct vidisp {
+ struct le le;
+ const char *name;
+ vidisp_alloc_h *alloch;
+ vidisp_update_h *updateh;
+ vidisp_disp_h *disph;
+ vidisp_hide_h *hideh;
+};
+
+struct vidisp *vidisp_get(struct vidisp_st *st);
+
+
+/*
+ * Video Source
+ */
+
+struct vidsrc {
+ struct le le;
+ const char *name;
+ vidsrc_alloc_h *alloch;
+ vidsrc_update_h *updateh;
+};
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st);
+
+
+/*
+ * Video Stream
+ */
+
+struct video;
+
+int video_alloc(struct video **vp, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *content, const struct list *vidcodecl);
+int video_start(struct video *v, const char *peer);
+void video_stop(struct video *v);
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params);
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp);
+struct stream *video_strm(const struct video *v);
+void video_update_picture(struct video *v);
+void video_sdp_attr_decode(struct video *v);
+int video_print(struct re_printf *pf, const struct video *v);
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..1ccdc32
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,139 @@
+/**
+ * @file log.c Logging
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct {
+ struct list logl;
+ bool debug;
+ bool stder;
+} lg = {
+ .logl = LIST_INIT,
+ .debug = false,
+ .stder = true
+};
+
+
+void log_register_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_append(&lg.logl, &log->le, log);
+}
+
+
+void log_unregister_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_unlink(&log->le);
+}
+
+
+void log_enable_debug(bool enable)
+{
+ lg.debug = enable;
+}
+
+
+void log_enable_stderr(bool enable)
+{
+ lg.stder = enable;
+}
+
+
+void vlog(enum log_level level, const char *fmt, va_list ap)
+{
+ char buf[4096];
+ struct le *le;
+
+ if (re_vsnprintf(buf, sizeof(buf), fmt, ap) < 0)
+ return;
+
+ if (lg.stder) {
+
+ bool color = level == WARN || level == ERROR;
+
+ if (color)
+ (void)re_fprintf(stderr, "\x1b[31m"); /* Red */
+
+ (void)re_fprintf(stderr, "%s", buf);
+
+ if (color)
+ (void)re_fprintf(stderr, "\x1b[;m");
+ }
+
+ le = lg.logl.head;
+
+ while (le) {
+
+ struct log *log = le->data;
+ le = le->next;
+
+ if (log->h)
+ log->h(level, buf);
+ }
+}
+
+
+void loglv(enum log_level level, const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((DEBUG == level) && !lg.debug)
+ return;
+
+ va_start(ap, fmt);
+ vlog(level, fmt, ap);
+ va_end(ap);
+}
+
+
+void debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!lg.debug)
+ return;
+
+ va_start(ap, fmt);
+ vlog(DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+
+void info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(INFO, fmt, ap);
+ va_end(ap);
+}
+
+
+void warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(WARN, fmt, ap);
+ va_end(ap);
+}
+
+
+void error(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(ERROR, fmt, ap);
+ va_end(ap);
+}
diff --git a/src/magic.h b/src/magic.h
new file mode 100644
index 0000000..15eacee
--- /dev/null
+++ b/src/magic.h
@@ -0,0 +1,27 @@
+/**
+ * @file magic.h Interface to magic macros
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef RELEASE
+
+#ifndef MAGIC
+#error "macro MAGIC must be defined"
+#endif
+
+/** Check magic number */
+#define MAGIC_DECL uint32_t magic;
+#define MAGIC_INIT(s) (s)->magic = MAGIC
+#define MAGIC_CHECK(s) \
+ if (MAGIC != s->magic) { \
+ warning("%s: wrong magic struct=%p (magic=0x%08x)\n", \
+ __FUNCTION__, s, s->magic); \
+ BREAKPOINT; \
+ }
+#else
+#define MAGIC_DECL
+#define MAGIC_INIT(s)
+#define MAGIC_CHECK(s) do {(void)(s);} while (0);
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..d55b349
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,154 @@
+/**
+ * @file main.c Main application code
+ *
+ * Copyright (C) 2010 - 2011 Creytiv.com
+ */
+#ifdef SOLARIS
+#define __EXTENSIONS__ 1
+#endif
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_GETOPT
+#include <getopt.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+
+
+static void signal_handler(int sig)
+{
+ static bool term = false;
+
+ if (term)
+ exit(0);
+
+ term = true;
+
+ info("terminated by signal %d\n", sig);
+
+ ua_stop_all(false);
+}
+
+
+int main(int argc, char *argv[])
+{
+ bool prefer_ipv6 = false, run_daemon = false;
+ const char *exec = NULL;
+ int err;
+
+ (void)re_fprintf(stderr, "baresip v%s"
+ " Copyright (C) 2010 - 2014"
+ " Alfred E. Heggestad et al.\n",
+ BARESIP_VERSION);
+
+ (void)sys_coredump_set(true);
+
+#ifdef HAVE_GETOPT
+ for (;;) {
+ const int c = getopt(argc, argv, "6de:f:hv");
+ if (0 > c)
+ break;
+
+ switch (c) {
+
+ case '?':
+ case 'h':
+ (void)re_fprintf(stderr,
+ "Usage: baresip [options]\n"
+ "options:\n"
+#if HAVE_INET6
+ "\t-6 Prefer IPv6\n"
+#endif
+ "\t-d Daemon\n"
+ "\t-e <commands> Exec commands\n"
+ "\t-f <path> Config path\n"
+ "\t-h -? Help\n"
+ "\t-v Verbose debug\n"
+ );
+ return -2;
+
+#if HAVE_INET6
+ case '6':
+ prefer_ipv6 = true;
+ break;
+#endif
+
+ case 'd':
+ run_daemon = true;
+ break;
+
+ case 'e':
+ exec = optarg;
+ break;
+
+ case 'f':
+ conf_path_set(optarg);
+ break;
+
+ case 'v':
+ log_enable_debug(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ err = libre_init();
+ if (err)
+ goto out;
+
+ err = conf_configure();
+ if (err) {
+ warning("main: configure failed: %m\n", err);
+ goto out;
+ }
+
+ /* Initialise User Agents */
+ err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")",
+ true, true, true, prefer_ipv6);
+ if (err)
+ goto out;
+
+ /* Load modules */
+ err = conf_modules();
+ if (err)
+ goto out;
+
+ if (run_daemon) {
+ err = sys_daemon();
+ if (err)
+ goto out;
+
+ log_enable_stderr(false);
+ }
+
+ info("baresip is ready.\n");
+
+ if (exec)
+ ui_input_str(exec);
+
+ /* Main loop */
+ err = re_main(signal_handler);
+
+ out:
+ if (err)
+ ua_stop_all(true);
+
+ ua_close();
+ mod_close();
+
+ libre_close();
+
+ /* Check for memory leaks */
+ tmr_debug();
+ mem_debug();
+
+ return err;
+}
diff --git a/src/mctrl.c b/src/mctrl.c
new file mode 100644
index 0000000..d3abab8
--- /dev/null
+++ b/src/mctrl.c
@@ -0,0 +1,44 @@
+/**
+ * @file mctrl.c Media Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * RFC 5168 XML Schema for Media Control
+ * note: deprecated, use RTCP FIR instead
+ *
+ *
+ * Example XML Document:
+ *
+ * <pre>
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <media_control>
+ <vc_primitive>
+ <to_encoder>
+ <picture_fast_update>
+ </picture_fast_update>
+ </to_encoder>
+ </vc_primitive>
+ </media_control>
+
+ </pre>
+ */
+int mctrl_handle_media_control(struct pl *body, bool *pfu)
+{
+ if (!body)
+ return EINVAL;
+
+ /* XXX: Poor-mans XML parsing (use xml-parser instead) */
+ if (0 == re_regex(body->p, body->l, "picture_fast_update")) {
+ if (pfu)
+ *pfu = true;
+ }
+
+ return 0;
+}
diff --git a/src/menc.c b/src/menc.c
new file mode 100644
index 0000000..a99a94a
--- /dev/null
+++ b/src/menc.c
@@ -0,0 +1,63 @@
+/**
+ * @file menc.c Media encryption
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list mencl = LIST_INIT;
+
+
+/**
+ * Register a new Media encryption module
+ *
+ * @param menc Media encryption module
+ */
+void menc_register(struct menc *menc)
+{
+ if (!menc)
+ return;
+
+ list_append(&mencl, &menc->le, menc);
+
+ info("mediaenc: %s\n", menc->id);
+}
+
+
+/**
+ * Unregister a Media encryption module
+ *
+ * @param menc Media encryption module
+ */
+void menc_unregister(struct menc *menc)
+{
+ if (!menc)
+ return;
+
+ list_unlink(&menc->le);
+}
+
+
+/**
+ * Find a Media Encryption module by name
+ *
+ * @param id Name of the Media Encryption module to find
+ *
+ * @return Matching Media Encryption module if found, otherwise NULL
+ */
+const struct menc *menc_find(const char *id)
+{
+ struct le *le;
+
+ for (le = mencl.head; le; le = le->next) {
+ struct menc *me = le->data;
+
+ if (0 == str_casecmp(id, me->id))
+ return me;
+ }
+
+ return NULL;
+}
diff --git a/src/message.c b/src/message.c
new file mode 100644
index 0000000..b0e33e1
--- /dev/null
+++ b/src/message.c
@@ -0,0 +1,138 @@
+/**
+ * @file message.c SIP MESSAGE -- RFC 3428
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct sip_lsnr *lsnr;
+static message_recv_h *recvh;
+static void *recvarg;
+
+
+static void handle_message(struct ua *ua, const struct sip_msg *msg)
+{
+ static const char *ctype_text = "text/plain";
+ struct pl mtype;
+ (void)ua;
+
+ if (re_regex(msg->ctype.p, msg->ctype.l, "[^;]+", &mtype))
+ mtype = msg->ctype;
+
+ if (0==pl_strcasecmp(&mtype, ctype_text) && recvh) {
+ recvh(&msg->from.auri, &msg->ctype, msg->mb, recvarg);
+ (void)sip_reply(uag_sip(), msg, 200, "OK");
+ }
+ else {
+ (void)sip_replyf(uag_sip(), msg, 415, "Unsupported Media Type",
+ "Accept: %s\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ ctype_text);
+ }
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ if (pl_strcmp(&msg->met, "MESSAGE"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ handle_message(ua, msg);
+
+ return true;
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+
+ (void)ua;
+
+ if (err) {
+ (void)re_fprintf(stderr, " \x1b[31m%m\x1b[;m\n", err);
+ return;
+ }
+
+ if (msg->scode >= 300) {
+ (void)re_fprintf(stderr, " \x1b[31m%u %r\x1b[;m\n",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+int message_init(message_recv_h *h, void *arg)
+{
+ int err;
+
+ err = sip_listen(&lsnr, uag_sip(), true, request_handler, NULL);
+ if (err)
+ return err;
+
+ recvh = h;
+ recvarg = arg;
+
+ return 0;
+}
+
+
+void message_close(void)
+{
+ lsnr = mem_deref(lsnr);
+}
+
+
+/**
+ * Send SIP instant MESSAGE to a peer
+ *
+ * @param ua User-Agent object
+ * @param peer Peer SIP Address
+ * @param msg Message to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int message_send(struct ua *ua, const char *peer, const char *msg)
+{
+ struct sip_addr addr;
+ struct pl pl;
+ char *uri = NULL;
+ int err = 0;
+
+ if (!ua || !peer || !msg)
+ return EINVAL;
+
+ pl_set_str(&pl, peer);
+
+ err = sip_addr_decode(&addr, &pl);
+ if (err)
+ return err;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ return err;
+
+ err = sip_req_send(ua, "MESSAGE", uri, resp_handler, ua,
+ "Accept: text/plain\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n%s",
+ str_len(msg), msg);
+
+ mem_deref(uri);
+
+ return err;
+}
diff --git a/src/metric.c b/src/metric.c
new file mode 100644
index 0000000..8cc0b18
--- /dev/null
+++ b/src/metric.c
@@ -0,0 +1,79 @@
+/**
+ * @file metric.c Metrics for media transmit/receive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {TMR_INTERVAL = 3};
+static void tmr_handler(void *arg)
+{
+ struct metric *metric = arg;
+ const uint64_t now = tmr_jiffies();
+ uint32_t diff;
+
+ tmr_start(&metric->tmr, TMR_INTERVAL * 1000, tmr_handler, metric);
+
+ if (now <= metric->ts_last)
+ return;
+
+ if (metric->ts_last) {
+ uint32_t bytes = metric->n_bytes - metric->n_bytes_last;
+ diff = (uint32_t)(now - metric->ts_last);
+ metric->cur_bitrate = 1000 * 8 * bytes / diff;
+ }
+
+ /* Update counters */
+ metric->ts_last = now;
+ metric->n_bytes_last = metric->n_bytes;
+}
+
+
+static void metric_start(struct metric *metric)
+{
+ if (metric->started)
+ return;
+
+ metric->ts_start = tmr_jiffies();
+ tmr_start(&metric->tmr, 1, tmr_handler, metric);
+
+ metric->started = true;
+}
+
+
+void metric_reset(struct metric *metric)
+{
+ if (!metric)
+ return;
+
+ tmr_cancel(&metric->tmr);
+}
+
+
+void metric_add_packet(struct metric *metric, size_t packetsize)
+{
+ if (!metric)
+ return;
+
+ if (!metric->started)
+ metric_start(metric);
+
+ metric->n_bytes += (uint32_t)packetsize;
+ metric->n_packets++;
+}
+
+
+uint32_t metric_avg_bitrate(const struct metric *metric)
+{
+ int diff;
+
+ if (!metric || !metric->ts_start)
+ return 0;
+
+ diff = (int)(tmr_jiffies() - metric->ts_start);
+
+ return 1000 * 8 * metric->n_bytes / diff;
+}
diff --git a/src/mnat.c b/src/mnat.c
new file mode 100644
index 0000000..416e93b
--- /dev/null
+++ b/src/mnat.c
@@ -0,0 +1,87 @@
+/**
+ * @file mnat.c Media NAT
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list mnatl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct mnat *mnat = arg;
+
+ list_unlink(&mnat->le);
+}
+
+
+/**
+ * Register a Media NAT traversal module
+ *
+ * @param mnatp Pointer to allocated Media NAT traversal module
+ * @param id Media NAT Identifier
+ * @param ftag SIP Feature tag (optional)
+ * @param sessh Session allocation handler
+ * @param mediah Media allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mnat_register(struct mnat **mnatp, const char *id, const char *ftag,
+ mnat_sess_h *sessh, mnat_media_h *mediah,
+ mnat_update_h *updateh)
+{
+ struct mnat *mnat;
+
+ if (!mnatp || !id || !sessh || !mediah)
+ return EINVAL;
+
+ mnat = mem_zalloc(sizeof(*mnat), destructor);
+ if (!mnat)
+ return ENOMEM;
+
+ list_append(&mnatl, &mnat->le, mnat);
+
+ mnat->id = id;
+ mnat->ftag = ftag;
+ mnat->sessh = sessh;
+ mnat->mediah = mediah;
+ mnat->updateh = updateh;
+
+ info("medianat: %s\n", id);
+
+ *mnatp = mnat;
+
+ return 0;
+}
+
+
+/**
+ * Find a Media NAT module by name
+ *
+ * @param id Name of the Media NAT module to find
+ *
+ * @return Matching Media NAT module if found, otherwise NULL
+ */
+const struct mnat *mnat_find(const char *id)
+{
+ struct mnat *mnat;
+ struct le *le;
+
+ for (le=mnatl.head; le; le=le->next) {
+
+ mnat = le->data;
+
+ if (str_casecmp(mnat->id, id))
+ continue;
+
+ return mnat;
+ }
+
+ return NULL;
+}
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 0000000..12b3d13
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,156 @@
+/**
+ * @file module.c Module loading
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct modapp {
+ struct mod *mod;
+ struct le le;
+};
+
+
+static struct list modappl;
+
+
+static void modapp_destructor(void *arg)
+{
+ struct modapp *modapp = arg;
+ list_unlink(&modapp->le);
+ mem_deref(modapp->mod);
+}
+
+
+#ifdef STATIC
+
+/* Declared in static.c */
+extern const struct mod_export *mod_table[];
+
+static const struct mod_export *find_module(const struct pl *pl)
+{
+ struct pl name;
+ uint32_t i;
+
+ if (re_regex(pl->p, pl->l, "[^.]+.[^]*", &name, NULL))
+ name = *pl;
+
+ for (i=0; ; i++) {
+ const struct mod_export *me = mod_table[i];
+ if (!me)
+ return NULL;
+ if (0 == pl_strcasecmp(&name, me->name))
+ return me;
+ }
+
+ return NULL;
+}
+#endif
+
+
+static int load_module(struct mod **modp, const struct pl *modpath,
+ const struct pl *name)
+{
+ char file[256];
+ struct mod *m = NULL;
+ int err = 0;
+
+ if (!name)
+ return EINVAL;
+
+#ifdef STATIC
+ /* Try static first */
+ err = mod_add(&m, find_module(name));
+ if (!err)
+ goto out;
+#endif
+
+ /* Then dynamic */
+ if (re_snprintf(file, sizeof(file), "%r/%r", modpath, name) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ err = mod_load(&m, file);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ warning("module %r: %m\n", name, err);
+ }
+ else if (modp)
+ *modp = m;
+
+ return err;
+}
+
+
+static int module_handler(const struct pl *val, void *arg)
+{
+ (void)load_module(NULL, arg, val);
+ return 0;
+}
+
+
+static int module_tmp_handler(const struct pl *val, void *arg)
+{
+ struct mod *mod = NULL;
+ (void)load_module(&mod, arg, val);
+ mem_deref(mod);
+ return 0;
+}
+
+
+static int module_app_handler(const struct pl *val, void *arg)
+{
+ struct modapp *modapp;
+
+ modapp = mem_zalloc(sizeof(*modapp), modapp_destructor);
+ if (!modapp)
+ return ENOMEM;
+
+ if (load_module(&modapp->mod, arg, val)) {
+ mem_deref(modapp);
+ return 0;
+ }
+
+ list_prepend(&modappl, &modapp->le, modapp);
+
+ return 0;
+}
+
+
+int module_init(const struct conf *conf)
+{
+ struct pl path;
+ int err;
+
+ if (!conf)
+ return EINVAL;
+
+ if (conf_get(conf, "module_path", &path))
+ pl_set_str(&path, ".");
+
+ err = conf_apply(conf, "module", module_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_tmp", module_tmp_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_app", module_app_handler, &path);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void module_app_unload(void)
+{
+ list_flush(&modappl);
+}
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..4829a5a
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,439 @@
+/**
+ * @file net.c Networking code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct {
+ struct config_net cfg;
+ struct sa laddr;
+ char ifname[16];
+#ifdef HAVE_INET6
+ struct sa laddr6;
+ char ifname6[16];
+#endif
+ struct tmr tmr;
+ struct dnsc *dnsc;
+ struct sa nsv[4]; /**< Configured name servers */
+ uint32_t nsn; /**< Number of configured name servers */
+ uint32_t interval;
+ int af; /**< Preferred address family */
+ char domain[64]; /**< DNS domain from network */
+ net_change_h *ch;
+ void *arg;
+} net;
+
+
+/**
+ * Check for DNS Server updates
+ */
+static void dns_refresh(void)
+{
+ struct sa nsv[8];
+ uint32_t i, nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err)
+ return;
+
+ for (i=0; i<net.nsn; i++)
+ sa_cpy(&nsv[nsn++], &net.nsv[i]);
+
+ (void)dnsc_srv_set(net.dnsc, nsv, nsn);
+}
+
+
+/**
+ * Detect changes in IP address(es)
+ */
+static void ipchange_handler(void *arg)
+{
+ bool change;
+ (void)arg;
+
+ tmr_start(&net.tmr, net.interval * 1000, ipchange_handler, NULL);
+
+ dns_refresh();
+
+ change = net_check();
+ if (change && net.ch) {
+ net.ch(net.arg);
+ }
+}
+
+
+/**
+ * Check if local IP address(es) changed
+ *
+ * @return True if changed, otherwise false
+ */
+bool net_check(void)
+{
+ struct sa laddr = net.laddr;
+#ifdef HAVE_INET6
+ struct sa laddr6 = net.laddr6;
+#endif
+ bool change = false;
+
+ if (str_isset(net.cfg.ifname)) {
+
+ (void)net_if_getaddr(net.cfg.ifname, AF_INET, &net.laddr);
+
+#ifdef HAVE_INET6
+ (void)net_if_getaddr(net.cfg.ifname, AF_INET6, &net.laddr6);
+#endif
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net.laddr);
+ (void)net_rt_default_get(AF_INET, net.ifname,
+ sizeof(net.ifname));
+
+#ifdef HAVE_INET6
+ (void)net_default_source_addr_get(AF_INET6, &net.laddr6);
+ (void)net_rt_default_get(AF_INET6, net.ifname6,
+ sizeof(net.ifname6));
+#endif
+ }
+
+ if (sa_isset(&net.laddr, SA_ADDR) &&
+ !sa_cmp(&laddr, &net.laddr, SA_ADDR)) {
+ change = true;
+ info("net: local IPv4 address changed: %j -> %j\n",
+ &laddr, &net.laddr);
+ }
+
+#ifdef HAVE_INET6
+ if (sa_isset(&net.laddr6, SA_ADDR) &&
+ !sa_cmp(&laddr6, &net.laddr6, SA_ADDR)) {
+ change = true;
+ info("net: local IPv6 address changed: %j -> %j\n",
+ &laddr6, &net.laddr6);
+ }
+#endif
+
+ return change;
+}
+
+
+static int dns_init(void)
+{
+ struct sa nsv[8];
+ uint32_t i, nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(net.domain, sizeof(net.domain), nsv, &nsn);
+ if (err) {
+ nsn = 0;
+ }
+
+ /* Add any configured nameservers */
+ for (i=0; i<net.nsn && nsn < ARRAY_SIZE(nsv); i++)
+ sa_cpy(&nsv[nsn++], &net.nsv[i]);
+
+ return dnsc_alloc(&net.dnsc, NULL, nsv, nsn);
+}
+
+
+/**
+ * Return TRUE if libre supports IPv6
+ */
+static bool check_ipv6(void)
+{
+ struct sa sa;
+
+ return 0 == sa_set_str(&sa, "::1", 2000);
+}
+
+
+/**
+ * Initialise networking
+ *
+ * @param cfg Network configuration
+ * @param af Preferred address family
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_init(const struct config_net *cfg, int af)
+{
+ int err;
+
+ if (!cfg)
+ return EINVAL;
+
+ /*
+ * baresip/libre must be built with matching HAVE_INET6 value.
+ * if different the size of `struct sa' will not match and the
+ * application is very likely to crash.
+ */
+#ifdef HAVE_INET6
+ if (!check_ipv6()) {
+ error("libre was compiled without IPv6-support"
+ ", but baresip was compiled with\n");
+ return EAFNOSUPPORT;
+ }
+#else
+ if (check_ipv6()) {
+ error("libre was compiled with IPv6-support"
+ ", but baresip was compiled without\n");
+ return EAFNOSUPPORT;
+ }
+#endif
+
+ net.cfg = *cfg;
+ net.af = af;
+
+ tmr_init(&net.tmr);
+
+ /* Initialise DNS resolver */
+ err = dns_init();
+ if (err) {
+ warning("net: dns_init: %m\n", err);
+ return err;
+ }
+
+ sa_init(&net.laddr, AF_INET);
+ (void)sa_set_str(&net.laddr, "127.0.0.1", 0);
+
+ if (str_isset(cfg->ifname)) {
+
+ bool got_it = false;
+
+ info("Binding to interface '%s'\n", cfg->ifname);
+
+ str_ncpy(net.ifname, cfg->ifname, sizeof(net.ifname));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET, &net.laddr);
+ if (err) {
+ info("net: %s: could not get IPv4 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+
+#ifdef HAVE_INET6
+ str_ncpy(net.ifname6, cfg->ifname,
+ sizeof(net.ifname6));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET6, &net.laddr6);
+ if (err) {
+ info("net: %s: could not get IPv6 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+#endif
+ if (got_it)
+ err = 0;
+ else {
+ warning("net: %s: could not get network address\n",
+ cfg->ifname);
+ return EADDRNOTAVAIL;
+ }
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net.laddr);
+ (void)net_rt_default_get(AF_INET, net.ifname,
+ sizeof(net.ifname));
+
+#ifdef HAVE_INET6
+ sa_init(&net.laddr6, AF_INET6);
+
+ (void)net_default_source_addr_get(AF_INET6, &net.laddr6);
+ (void)net_rt_default_get(AF_INET6, net.ifname6,
+ sizeof(net.ifname6));
+#endif
+ }
+
+ (void)re_fprintf(stderr, "Local network address:");
+
+ if (sa_isset(&net.laddr, SA_ADDR)) {
+ (void)re_fprintf(stderr, " IPv4=%s:%j",
+ net.ifname, &net.laddr);
+ }
+#ifdef HAVE_INET6
+ if (sa_isset(&net.laddr6, SA_ADDR)) {
+ (void)re_fprintf(stderr, " IPv6=%s:%j",
+ net.ifname6, &net.laddr6);
+ }
+#endif
+ (void)re_fprintf(stderr, "\n");
+
+ return err;
+}
+
+
+/**
+ * Reset the DNS resolver
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_reset(void)
+{
+ net.dnsc = mem_deref(net.dnsc);
+
+ return dns_init();
+}
+
+
+/**
+ * Close networking
+ */
+void net_close(void)
+{
+ net.dnsc = mem_deref(net.dnsc);
+ tmr_cancel(&net.tmr);
+}
+
+
+/**
+ * Add a DNS server
+ *
+ * @param sa DNS Server IP address and port
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_dnssrv_add(const struct sa *sa)
+{
+ if (net.nsn >= ARRAY_SIZE(net.nsv))
+ return E2BIG;
+
+ sa_cpy(&net.nsv[net.nsn++], sa);
+
+ return 0;
+}
+
+
+/**
+ * Check for networking changes with a regular interval
+ *
+ * @param interval Interval in seconds
+ * @param ch Handler called when a change was detected
+ * @param arg Handler argument
+ */
+void net_change(uint32_t interval, net_change_h *ch, void *arg)
+{
+ net.interval = interval;
+ net.ch = ch;
+ net.arg = arg;
+
+ if (interval)
+ tmr_start(&net.tmr, interval * 1000, ipchange_handler, NULL);
+ else
+ tmr_cancel(&net.tmr);
+}
+
+
+static int dns_debug(struct re_printf *pf, void *unused)
+{
+ struct sa nsv[4];
+ uint32_t i, nsn;
+ int err;
+
+ (void)unused;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err)
+ nsn = 0;
+
+ err = re_hprintf(pf, " DNS Servers: (%u)\n", nsn);
+ for (i=0; i<nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]);
+ for (i=0; i<net.nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", nsn+i, &net.nsv[i]);
+
+ return err;
+}
+
+
+int net_af(void)
+{
+ return net.af;
+}
+
+
+/**
+ * Print networking debug information
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_debug(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "--- Network debug ---\n");
+ err |= re_hprintf(pf, " Preferred AF: %s\n", net_af2name(net.af));
+ err |= re_hprintf(pf, " Local IPv4: %9s - %j\n",
+ net.ifname, &net.laddr);
+#ifdef HAVE_INET6
+ err |= re_hprintf(pf, " Local IPv6: %9s - %j\n",
+ net.ifname6, &net.laddr6);
+#endif
+
+ err |= net_if_debug(pf, NULL);
+
+ err |= net_rt_debug(pf, NULL);
+
+ err |= dns_debug(pf, NULL);
+
+ return err;
+}
+
+
+/**
+ * Get the local IP Address for a specific Address Family (AF)
+ *
+ * @param af Address Family
+ *
+ * @return Local IP Address
+ */
+const struct sa *net_laddr_af(int af)
+{
+ switch (af) {
+
+ case AF_INET: return &net.laddr;
+#ifdef HAVE_INET6
+ case AF_INET6: return &net.laddr6;
+#endif
+ default: return NULL;
+ }
+}
+
+
+/**
+ * Get the DNS Client
+ *
+ * @return DNS Client
+ */
+struct dnsc *net_dnsc(void)
+{
+ return net.dnsc;
+}
+
+
+/**
+ * Get the network domain name
+ *
+ * @return Network domain
+ */
+const char *net_domain(void)
+{
+ return net.domain[0] ? net.domain : NULL;
+}
diff --git a/src/play.c b/src/play.c
new file mode 100644
index 0000000..9ea8788
--- /dev/null
+++ b/src/play.c
@@ -0,0 +1,317 @@
+/**
+ * @file src/play.c Audio-file player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {SILENCE_DUR = 2000, PTIME = 100};
+
+/** Audio file player */
+struct play {
+ struct le le;
+ struct play **playp;
+ struct lock *lock;
+ struct mbuf *mb;
+ struct auplay_st *auplay;
+ struct tmr tmr;
+ int repeat;
+ bool eof;
+};
+
+
+#ifndef PREFIX
+#define PREFIX "/usr"
+#endif
+static char play_path[256] = PREFIX "/share/baresip";
+static struct list playl;
+
+
+static void tmr_polling(void *arg);
+
+
+static void tmr_stop(void *arg)
+{
+ struct play *play = arg;
+ mem_deref(play);
+}
+
+
+static void tmr_repeat(void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ play->mb->pos = 0;
+ play->eof = false;
+
+ tmr_start(&play->tmr, 1000, tmr_polling, arg);
+
+ lock_rel(play->lock);
+}
+
+
+static void tmr_polling(void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ tmr_start(&play->tmr, 1000, tmr_polling, arg);
+
+ if (play->eof) {
+ if (play->repeat > 0)
+ play->repeat--;
+
+ if (play->repeat == 0)
+ tmr_start(&play->tmr, 1, tmr_stop, arg);
+ else
+ tmr_start(&play->tmr, SILENCE_DUR, tmr_repeat, arg);
+ }
+
+ lock_rel(play->lock);
+}
+
+
+/**
+ * NOTE: DSP cannot be destroyed inside handler
+ */
+static bool write_handler(uint8_t *buf, size_t sz, void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ if (play->eof)
+ goto silence;
+
+ if (mbuf_get_left(play->mb) < sz) {
+ play->eof = true;
+ }
+ else {
+ (void)mbuf_read_mem(play->mb, buf, sz);
+ }
+
+ silence:
+ if (play->eof)
+ memset(buf, 0, sz);
+
+ lock_rel(play->lock);
+
+ return true;
+}
+
+
+static void destructor(void *arg)
+{
+ struct play *play = arg;
+
+ list_unlink(&play->le);
+ tmr_cancel(&play->tmr);
+
+ lock_write_get(play->lock);
+ play->eof = true;
+ lock_rel(play->lock);
+
+ mem_deref(play->auplay);
+ mem_deref(play->mb);
+ mem_deref(play->lock);
+
+ if (play->playp)
+ *play->playp = NULL;
+}
+
+
+static int aufile_load(struct mbuf *mb, const char *filename,
+ uint32_t *srate, uint8_t *channels)
+{
+ struct aufile_prm prm;
+ struct aufile *af;
+ int err;
+
+ err = aufile_open(&af, &prm, filename, AUFILE_READ);
+ if (err)
+ return err;
+
+ while (!err) {
+ uint8_t buf[4096];
+ size_t i, n;
+
+ n = sizeof(buf);
+
+ err = aufile_read(af, buf, &n);
+ if (err || !n)
+ break;
+
+ switch (prm.fmt) {
+
+ case AUFMT_S16LE:
+ err = mbuf_write_mem(mb, buf, n);
+ break;
+
+ case AUFMT_PCMA:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_alaw2pcm(buf[i]));
+ }
+ break;
+
+ case AUFMT_PCMU:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_ulaw2pcm(buf[i]));
+ }
+ break;
+
+ default:
+ err = ENOSYS;
+ break;
+ }
+ }
+
+ mem_deref(af);
+
+ if (!err) {
+ mb->pos = 0;
+
+ *srate = prm.srate;
+ *channels = prm.channels;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play a tone from a PCM buffer
+ *
+ * @param playp Pointer to allocated player object
+ * @param tone PCM buffer to play
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_tone(struct play **playp, struct mbuf *tone, uint32_t srate,
+ uint8_t ch, int repeat)
+{
+ struct auplay_prm wprm;
+ struct play *play;
+ struct config *cfg;
+ int err;
+
+ if (playp && *playp)
+ return EALREADY;
+
+ cfg = conf_config();
+ if (!cfg)
+ return ENOENT;
+
+ play = mem_zalloc(sizeof(*play), destructor);
+ if (!play)
+ return ENOMEM;
+
+ tmr_init(&play->tmr);
+ play->repeat = repeat;
+ play->mb = mem_ref(tone);
+
+ err = lock_alloc(&play->lock);
+ if (err)
+ goto out;
+
+ wprm.fmt = AUFMT_S16LE;
+ wprm.ch = ch;
+ wprm.srate = srate;
+ wprm.ptime = PTIME;
+
+ err = auplay_alloc(&play->auplay, cfg->audio.alert_mod, &wprm,
+ cfg->audio.alert_dev, write_handler, play);
+ if (err)
+ goto out;
+
+ list_append(&playl, &play->le, play);
+ tmr_start(&play->tmr, 1000, tmr_polling, play);
+
+ out:
+ if (err) {
+ mem_deref(play);
+ }
+ else if (playp) {
+ play->playp = playp;
+ *playp = play;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play an audio file in WAV format
+ *
+ * @param playp Pointer to allocated player object
+ * @param filename Name of WAV file to play
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_file(struct play **playp, const char *filename, int repeat)
+{
+ struct mbuf *mb;
+ char path[512];
+ uint32_t srate;
+ uint8_t ch;
+ int err;
+
+ if (playp && *playp)
+ return EALREADY;
+
+ if (re_snprintf(path, sizeof(path), "%s/%s",
+ play_path, filename) < 0)
+ return ENOMEM;
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ err = aufile_load(mb, path, &srate, &ch);
+ if (err) {
+ warning("play: %s: %m\n", path, err);
+ goto out;
+ }
+
+ err = play_tone(playp, mb, srate, ch, repeat);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+void play_init(void)
+{
+ list_init(&playl);
+}
+
+
+/**
+ * Close all active audio players
+ */
+void play_close(void)
+{
+ list_flush(&playl);
+}
+
+
+void play_set_path(const char *path)
+{
+ str_ncpy(play_path, path, sizeof(play_path));
+}
diff --git a/src/realtime.c b/src/realtime.c
new file mode 100644
index 0000000..a144e22
--- /dev/null
+++ b/src/realtime.c
@@ -0,0 +1,100 @@
+/**
+ * @file realtime.c Real-Time scheduling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#ifdef DARWIN
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <stdio.h>
+#include <mach/mach.h>
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#endif
+
+
+#ifdef DARWIN
+static int set_realtime(int period, int computation, int constraint)
+{
+ struct thread_time_constraint_policy ttcpolicy;
+ int ret;
+
+ ttcpolicy.period = period; /* HZ/160 */
+ ttcpolicy.computation = computation; /* HZ/3300 */
+ ttcpolicy.constraint = constraint; /* HZ/2200 */
+ ttcpolicy.preemptible = 1;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_TIME_CONSTRAINT_POLICY,
+ (thread_policy_t)&ttcpolicy,
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+ if (ret != KERN_SUCCESS)
+ return ENOSYS;
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Enable real-time scheduling (for selected platforms)
+ *
+ * @param enable True to enable, false to disable
+ * @param fps Wanted video framerate
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int realtime_enable(bool enable, int fps)
+{
+#ifdef DARWIN
+ if (enable) {
+#if TARGET_OS_IPHONE
+ int bus_speed = 100000000;
+#else
+ int ret, bus_speed;
+ int mib[2] = { CTL_HW, HW_BUS_FREQ };
+ size_t len;
+
+ len = sizeof(bus_speed);
+ ret = sysctl (mib, 2, &bus_speed, &len, NULL, 0);
+ if (ret < 0) {
+ return ENOSYS;
+ }
+
+ info("realtime: fps=%d bus_speed=%d\n", fps, bus_speed);
+#endif
+
+ return set_realtime(bus_speed / fps,
+ bus_speed / 3300, bus_speed / 2200);
+ }
+ else {
+ kern_return_t ret;
+ thread_standard_policy_data_t pt;
+ mach_msg_type_number_t cnt = THREAD_STANDARD_POLICY_COUNT;
+ boolean_t get_default = TRUE;
+
+ ret = thread_policy_get(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ &cnt, &get_default);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ THREAD_STANDARD_POLICY_COUNT);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ return 0;
+ }
+#else
+ (void)enable;
+ (void)fps;
+ return ENOSYS;
+#endif
+}
diff --git a/src/reg.c b/src/reg.c
new file mode 100644
index 0000000..2588873
--- /dev/null
+++ b/src/reg.c
@@ -0,0 +1,269 @@
+/**
+ * @file reg.c Register Client
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Register client */
+struct reg {
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< Pointer to parent UA object */
+ struct sipreg *sipreg; /**< SIP Register client */
+ int id; /**< Registration ID (for SIP outbound) */
+
+ /* status: */
+ uint16_t scode; /**< Registration status code */
+ char *srv; /**< SIP Server id */
+ int sipfd; /**< Cached file-descr. for SIP conn */
+ int af; /**< Cached address family for SIP conn */
+};
+
+
+static void destructor(void *arg)
+{
+ struct reg *reg = arg;
+
+ list_unlink(&reg->le);
+ mem_deref(reg->sipreg);
+ mem_deref(reg->srv);
+}
+
+
+static int sipmsg_fd(const struct sip_msg *msg)
+{
+ if (!msg)
+ return -1;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ return udp_sock_fd(msg->sock, AF_UNSPEC);
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ return tcp_conn_fd(sip_msg_tcpconn(msg));
+
+ default:
+ return -1;
+ }
+}
+
+
+static int sipmsg_af(const struct sip_msg *msg)
+{
+ struct sa laddr;
+ int err = 0;
+
+ if (!msg)
+ return AF_UNSPEC;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ err = udp_local_get(msg->sock, &laddr);
+ break;
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ err = tcp_conn_local_get(sip_msg_tcpconn(msg), &laddr);
+ break;
+
+ default:
+ return AF_UNSPEC;
+ }
+
+ return err ? AF_UNSPEC : sa_af(&laddr);
+}
+
+
+static const char *af_name(int af)
+{
+ switch (af) {
+
+ case AF_INET: return "v4";
+ case AF_INET6: return "v6";
+ default: return "v?";
+ }
+}
+
+
+static int sip_auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static void register_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct reg *reg = arg;
+ const struct sip_hdr *hdr;
+
+ if (err) {
+ warning("reg: %s: Register: %m\n", ua_aor(reg->ua), err);
+
+ reg->scode = 999;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%m", err);
+ return;
+ }
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_SERVER);
+ if (hdr) {
+ reg->srv = mem_deref(reg->srv);
+ (void)pl_strdup(&reg->srv, &hdr->val);
+ }
+
+ if (200 <= msg->scode && msg->scode <= 299) {
+
+ uint32_t n_bindings;
+
+ n_bindings = sip_msg_hdr_count(msg, SIP_HDR_CONTACT);
+ reg->sipfd = sipmsg_fd(msg);
+ reg->af = sipmsg_af(msg);
+
+ if (msg->scode != reg->scode) {
+ ua_printf(reg->ua, "{%d/%s/%s} %u %r (%s)"
+ " [%u binding%s]\n",
+ reg->id, sip_transp_name(msg->tp),
+ af_name(reg->af), msg->scode, &msg->reason,
+ reg->srv, n_bindings,
+ 1==n_bindings?"":"s");
+ }
+
+ reg->scode = msg->scode;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_OK, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+ else if (msg->scode >= 300) {
+
+ warning("reg: %s: %u %r (%s)\n", ua_aor(reg->ua),
+ msg->scode, &msg->reason, reg->srv);
+
+ reg->scode = msg->scode;
+ reg->sipfd = -1;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+int reg_add(struct list *lst, struct ua *ua, int regid)
+{
+ struct reg *reg;
+
+ if (!lst || !ua)
+ return EINVAL;
+
+ reg = mem_zalloc(sizeof(*reg), destructor);
+ if (!reg)
+ return ENOMEM;
+
+ reg->ua = ua;
+ reg->id = regid;
+ reg->sipfd = -1;
+
+ list_append(lst, &reg->le, reg);
+
+ return 0;
+}
+
+
+int reg_register(struct reg *reg, const char *reg_uri, const char *params,
+ uint32_t regint, const char *outbound)
+{
+ const char *routev[1];
+ int err;
+
+ if (!reg || !reg_uri)
+ return EINVAL;
+
+ reg->scode = 0;
+ routev[0] = outbound;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+ err = sipreg_register(&reg->sipreg, uag_sip(), reg_uri,
+ ua_aor(reg->ua), ua_aor(reg->ua),
+ regint, ua_cuser(reg->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ reg->id,
+ sip_auth_handler, ua_prm(reg->ua), true,
+ register_handler, reg,
+ params[0] ? &params[1] : NULL,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void reg_unregister(struct reg *reg)
+{
+ if (!reg)
+ return;
+
+ reg->scode = 0;
+ reg->sipfd = -1;
+ reg->af = 0;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+}
+
+
+bool reg_isok(const struct reg *reg)
+{
+ if (!reg)
+ return false;
+
+ return 200 <= reg->scode && reg->scode <= 299;
+}
+
+
+int reg_sipfd(const struct reg *reg)
+{
+ return reg ? reg->sipfd : -1;
+}
+
+
+static const char *print_scode(uint16_t scode)
+{
+ if (0 == scode) return "\x1b[33m" "zzz" "\x1b[;m";
+ else if (200 == scode) return "\x1b[32m" "OK " "\x1b[;m";
+ else return "\x1b[31m" "ERR" "\x1b[;m";
+}
+
+
+int reg_debug(struct re_printf *pf, const struct reg *reg)
+{
+ int err = 0;
+
+ if (!reg)
+ return 0;
+
+ err |= re_hprintf(pf, "\nRegister client:\n");
+ err |= re_hprintf(pf, " id: %d\n", reg->id);
+ err |= re_hprintf(pf, " scode: %u (%s)\n",
+ reg->scode, print_scode(reg->scode));
+ err |= re_hprintf(pf, " srv: %s\n", reg->srv);
+ err |= re_hprintf(pf, " sipfd: %d\n", reg->sipfd);
+
+ return err;
+}
+
+
+int reg_status(struct re_printf *pf, const struct reg *reg)
+{
+ if (!reg)
+ return 0;
+
+ return re_hprintf(pf, " %s %s", print_scode(reg->scode), reg->srv);
+}
diff --git a/src/rtpkeep.c b/src/rtpkeep.c
new file mode 100644
index 0000000..7f32c41
--- /dev/null
+++ b/src/rtpkeep.c
@@ -0,0 +1,165 @@
+/**
+ * @file rtpkeep.c RTP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * See draft-ietf-avt-app-rtp-keepalive:
+ *
+ * "zero" 4.1. Transport Packet of 0-byte
+ * "rtcp" 4.3. RTCP Packets Multiplexed with RTP Packets
+ * "stun" 4.4. STUN Indication Packet
+ * "dyna" 4.6. RTP Packet with Unknown Payload Type
+ */
+
+
+enum {
+ Tr_UDP = 15,
+ Tr_TCP = 7200
+};
+
+/** RTP Keepalive */
+struct rtpkeep {
+ struct rtp_sock *rtp;
+ struct sdp_media *sdp;
+ struct tmr tmr;
+ char *method;
+ uint32_t ts;
+ bool flag;
+};
+
+
+static void destructor(void *arg)
+{
+ struct rtpkeep *rk = arg;
+
+ tmr_cancel(&rk->tmr);
+ mem_deref(rk->method);
+}
+
+
+static int send_keepalive(struct rtpkeep *rk)
+{
+ int err = 0;
+
+ if (!str_casecmp(rk->method, "zero")) {
+ struct mbuf *mb = mbuf_alloc(1);
+ if (!mb)
+ return ENOMEM;
+ err = udp_send(rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), mb);
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "stun")) {
+ err = stun_indication(IPPROTO_UDP, rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), 0,
+ STUN_METHOD_BINDING, NULL, 0, false, 0);
+ }
+ else if (!str_casecmp(rk->method, "dyna")) {
+ struct mbuf *mb = mbuf_alloc(RTP_HEADER_SIZE);
+ int pt = sdp_media_find_unused_pt(rk->sdp);
+ if (!mb)
+ return ENOMEM;
+ if (pt == -1)
+ return ENOENT;
+ mb->pos = mb->end = RTP_HEADER_SIZE;
+
+ err = rtp_send(rk->rtp, sdp_media_raddr(rk->sdp), false,
+ pt, rk->ts, mb);
+
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "rtcp")) {
+
+ if (sdp_media_rattr(rk->sdp, "rtcp-mux")) {
+ /* do nothing */
+ ;
+ }
+ else {
+ warning("rtpkeep: rtcp-mux is disabled\n");
+ }
+ }
+ else {
+ warning("rtpkeep: unknown method: %s\n", rk->method);
+ return ENOSYS;
+ }
+
+ return err;
+}
+
+
+/**
+ * Logic:
+ *
+ * We check for RTP activity every 15 seconds, and clear the flag.
+ * The flag is set for every transmitted RTP packet. If the flag
+ * is not set, it means that we have not sent any RTP packet in the
+ * last period of 0 - 15 seconds. Start transmitting RTP keepalives
+ * now and every 15 seconds after that.
+ *
+ * @param arg Handler argument
+ */
+static void timeout(void *arg)
+{
+ struct rtpkeep *rk = arg;
+ int err;
+
+ tmr_start(&rk->tmr, Tr_UDP * 1000, timeout, rk);
+
+ if (rk->flag) {
+ rk->flag = false;
+ return;
+ }
+
+ err = send_keepalive(rk);
+ if (err) {
+ warning("rtpkeep: send keepalive failed: %m\n", err);
+ }
+}
+
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp)
+{
+ struct rtpkeep *rk;
+ int err;
+
+ if (!rkp || !method || proto != IPPROTO_UDP || !rtp || !sdp)
+ return EINVAL;
+
+ rk = mem_zalloc(sizeof(*rk), destructor);
+ if (!rk)
+ return ENOMEM;
+
+ rk->rtp = rtp;
+ rk->sdp = sdp;
+
+ err = str_dup(&rk->method, method);
+ if (err)
+ goto out;
+
+ tmr_start(&rk->tmr, 20, timeout, rk);
+
+ out:
+ if (err)
+ mem_deref(rk);
+ else
+ *rkp = rk;
+
+ return err;
+}
+
+
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts)
+{
+ if (!rk)
+ return;
+
+ rk->ts = ts;
+ rk->flag = true;
+}
diff --git a/src/sdp.c b/src/sdp.c
new file mode 100644
index 0000000..7a8ed29
--- /dev/null
+++ b/src/sdp.c
@@ -0,0 +1,186 @@
+/**
+ * @file src/sdp.c SDP functions
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+uint32_t sdp_media_rattr_u32(const struct sdp_media *m, const char *name)
+{
+ const char *attr = sdp_media_rattr(m, name);
+ return attr ? atoi(attr) : 0;
+}
+
+
+/*
+ * Get a remote attribute from the SDP. Try the media-level first,
+ * and if it does not exist then try session-level.
+ */
+const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m,
+ const char *name)
+{
+ const char *x;
+
+ x = sdp_media_rattr(m, name);
+ if (x)
+ return x;
+
+ x = sdp_session_rattr(s, name);
+ if (x)
+ return x;
+
+ return NULL;
+}
+
+
+/* RFC 4572 */
+int sdp_fingerprint_decode(const char *attr, struct pl *hash,
+ uint8_t *md, size_t *sz)
+{
+ struct pl f;
+ const char *p;
+ int err;
+
+ if (!attr || !hash)
+ return EINVAL;
+
+ err = re_regex(attr, str_len(attr), "[^ ]+ [0-9A-F:]+", hash, &f);
+ if (err)
+ return err;
+
+ if (md && sz) {
+ if (*sz < (f.l+1)/3)
+ return EOVERFLOW;
+
+ for (p = f.p; p < (f.p+f.l); p += 3) {
+ *md++ = ch_hex(p[0]) << 4 | ch_hex(p[1]);
+ }
+
+ *sz = (f.l+1)/3;
+ }
+
+ return 0;
+}
+
+
+bool sdp_media_has_media(const struct sdp_media *m)
+{
+ bool has;
+
+ has = sdp_media_rformat(m, NULL) != NULL;
+ if (has)
+ return sdp_media_rport(m) != 0;
+
+ return false;
+}
+
+
+/**
+ * Find a dynamic payload type that is not used
+ *
+ * @param m SDP Media
+ *
+ * @return Unused payload type, -1 if no found
+ */
+int sdp_media_find_unused_pt(const struct sdp_media *m)
+{
+ int pt;
+
+ for (pt = PT_DYN_MAX; pt>=PT_DYN_MIN; pt--) {
+
+ if (!sdp_media_format(m, false, NULL, pt, NULL, -1, -1))
+ return pt;
+ }
+
+ return -1;
+}
+
+
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m)
+{
+ struct sdp_format *sf;
+ struct list *lst;
+
+ again:
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!sf)
+ return NULL;
+
+ lst = sf->le.list;
+
+ /* move top-most codec to end of list */
+ list_unlink(&sf->le);
+ list_append(lst, &sf->le, sf);
+
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!str_casecmp(sf->name, telev_rtpfmt))
+ goto again;
+
+ return sf;
+}
+
+
+static void decode_part(const struct pl *part, struct mbuf *mb)
+{
+ struct pl hdrs, body;
+
+ if (re_regex(part->p, part->l, "\r\n\r\n[^]+", &body))
+ return;
+
+ hdrs.p = part->p;
+ hdrs.l = body.p - part->p - 2;
+
+ if (0 == re_regex(hdrs.p, hdrs.l, "application/sdp")) {
+
+ mb->pos += (body.p - (char *)mbuf_buf(mb));
+ mb->end = mb->pos + body.l;
+ }
+}
+
+
+/**
+ * Decode a multipart/mixed message and find the part with application/sdp
+ */
+int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb)
+{
+ struct pl bnd, s, e, p;
+ char expr[64];
+ int err;
+
+ if (!ctype || !mb)
+ return EINVAL;
+
+ /* fetch the boundary tag, excluding quotes */
+ err = re_regex(ctype->p, ctype->l,
+ "multipart/mixed;[ \t]*boundary=[~]+", NULL, &bnd);
+ if (err)
+ return err;
+
+ if (re_snprintf(expr, sizeof(expr), "--%r[^]+", &bnd) < 0)
+ return ENOMEM;
+
+ /* find 1st boundary */
+ err = re_regex((char *)mbuf_buf(mb), mbuf_get_left(mb), expr, &s);
+ if (err)
+ return err;
+
+ /* iterate over each part */
+ while (s.l > 2) {
+ if (re_regex(s.p, s.l, expr, &e))
+ return 0;
+
+ p.p = s.p + 2;
+ p.l = e.p - p.p - bnd.l - 2;
+
+ /* valid part in "p" */
+ decode_part(&p, mb);
+
+ s = e;
+ }
+
+ return 0;
+}
diff --git a/src/sipreq.c b/src/sipreq.c
new file mode 100644
index 0000000..1518645
--- /dev/null
+++ b/src/sipreq.c
@@ -0,0 +1,149 @@
+/**
+ * @file sipreq.c SIP Authenticated Request
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** SIP Authenticated Request */
+struct sip_req {
+ struct sip_loopstate ls;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct sip_request *req;
+ char *method;
+ char *fmt;
+ sip_resp_h *resph;
+ void *arg;
+};
+
+
+static int request(struct sip_req *sr);
+
+
+static void destructor(void *arg)
+{
+ struct sip_req *sr = arg;
+
+ mem_deref(sr->req);
+ mem_deref(sr->auth);
+ mem_deref(sr->dlg);
+ mem_deref(sr->method);
+ mem_deref(sr->fmt);
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sip_req *sr = arg;
+
+ if (err || sip_request_loops(&sr->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ ;
+ }
+ else {
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sr->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = request(sr);
+ if (err)
+ break;
+
+ return;
+
+ case 403:
+ sip_auth_reset(sr->auth);
+ break;
+ }
+ }
+
+ out:
+ sr->resph(err, msg, sr->arg);
+
+ /* destroy now */
+ mem_deref(sr);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int request(struct sip_req *sr)
+{
+ return sip_drequestf(&sr->req, uag_sip(), true, sr->method, sr->dlg,
+ 0, sr->auth, NULL, resp_handler,
+ sr, sr->fmt ? "%s" : NULL, sr->fmt);
+}
+
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...)
+{
+ const char *routev[1];
+ struct sip_req *sr;
+ int err;
+
+ if (!ua || !method || !uri || !resph || !fmt)
+ return EINVAL;
+
+ routev[0] = ua_outbound(ua);
+
+ sr = mem_zalloc(sizeof(*sr), destructor);
+ if (!sr)
+ return ENOMEM;
+
+ sr->resph = resph;
+ sr->arg = arg;
+
+ err = str_dup(&sr->method, method);
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ err |= re_vsdprintf(&sr->fmt, fmt, ap);
+ va_end(ap);
+ }
+
+ if (err)
+ goto out;
+
+ err = sip_dialog_alloc(&sr->dlg, uri, uri, NULL, ua_aor(ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0);
+ if (err)
+ goto out;
+
+ err = sip_auth_alloc(&sr->auth, auth_handler, ua_prm(ua), true);
+ if (err)
+ goto out;
+
+ err = request(sr);
+
+ out:
+ if (err)
+ mem_deref(sr);
+
+ return err;
+}
diff --git a/src/srcs.mk b/src/srcs.mk
new file mode 100644
index 0000000..d3aa765
--- /dev/null
+++ b/src/srcs.mk
@@ -0,0 +1,49 @@
+#
+# srcs.mk All application source files.
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += account.c
+SRCS += aucodec.c
+SRCS += audio.c
+SRCS += aufilt.c
+SRCS += auplay.c
+SRCS += ausrc.c
+SRCS += call.c
+SRCS += cmd.c
+SRCS += conf.c
+SRCS += config.c
+SRCS += contact.c
+SRCS += log.c
+SRCS += menc.c
+SRCS += message.c
+SRCS += metric.c
+SRCS += mnat.c
+SRCS += module.c
+SRCS += net.c
+SRCS += play.c
+SRCS += realtime.c
+SRCS += reg.c
+SRCS += rtpkeep.c
+SRCS += sdp.c
+SRCS += sipreq.c
+SRCS += stream.c
+SRCS += ua.c
+SRCS += ui.c
+
+ifneq ($(USE_VIDEO),)
+SRCS += bfcp.c
+SRCS += mctrl.c
+SRCS += video.c
+SRCS += vidcodec.c
+SRCS += vidfilt.c
+SRCS += vidisp.c
+SRCS += vidsrc.c
+endif
+
+ifneq ($(STATIC),)
+SRCS += static.c
+endif
+
+APP_SRCS += main.c
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 0000000..0e5b89c
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,582 @@
+/**
+ * @file stream.c Generic Media Stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define MAGIC 0x00814ea5
+#include "magic.h"
+
+
+enum {
+ RTP_RECV_SIZE = 8192,
+};
+
+
+/** Defines a generic media stream */
+struct stream {
+ MAGIC_DECL
+
+ struct le le; /**< Linked list element */
+ struct config_avt cfg; /**< Stream configuration */
+ struct call *call; /**< Ref. to call object */
+ struct sdp_media *sdp; /**< SDP Media line */
+ struct rtp_sock *rtp; /**< RTP Socket */
+ struct rtpkeep *rtpkeep; /**< RTP Keepalive */
+ struct rtcp_stats rtcp_stats;/**< RTCP statistics */
+ struct jbuf *jbuf; /**< Jitter Buffer for incoming RTP */
+ struct mnat_media *mns; /**< Media NAT traversal state */
+ const struct menc *menc; /**< Media encryption module */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ struct menc_media *mes; /**< Media Encryption media state */
+ struct metric metric_tx; /**< Metrics for transmit */
+ struct metric metric_rx; /**< Metrics for receiving */
+ char *cname;
+ uint32_t ssrc_rx; /**< Incoming syncronizing source */
+ uint32_t pseq; /**< Sequence number for incoming RTP */
+ int pt_enc; /**< Payload type for encoding */
+ bool rtcp; /**< Enable RTCP */
+ bool rtcp_mux; /**< RTP/RTCP multiplex supported by peer */
+ bool jbuf_started; /**< True if jitter-buffer was started */
+ stream_rtp_h *rtph; /**< Stream RTP handler */
+ stream_rtcp_h *rtcph; /**< Stream RTCP handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static inline int lostcalc(struct stream *s, uint16_t seq)
+{
+ const uint16_t delta = seq - s->pseq;
+ int lostc;
+
+ if (s->pseq == (uint32_t)-1)
+ lostc = 0;
+ else if (delta == 0)
+ return -1;
+ else if (delta < 3000)
+ lostc = delta - 1;
+ else if (delta < 0xff9c)
+ lostc = 0;
+ else
+ return -2;
+
+ s->pseq = seq;
+
+ return lostc;
+}
+
+
+static void print_rtp_stats(struct stream *s)
+{
+ info("\n%-9s Transmit: Receive:\n"
+ "packets: %7u %7u\n"
+ "avg. bitrate: %7.1f %7.1f (kbit/s)\n",
+ sdp_media_name(s->sdp),
+ s->metric_tx.n_packets, s->metric_rx.n_packets,
+ 1.0*metric_avg_bitrate(&s->metric_tx)/1000,
+ 1.0*metric_avg_bitrate(&s->metric_rx)/1000);
+
+ if (s->rtcp_stats.tx.sent || s->rtcp_stats.rx.sent) {
+
+ info("pkt.report: %7u %7u\n"
+ "lost: %7d %7d\n"
+ "jitter: %7.1f %7.1f (ms)\n",
+ s->rtcp_stats.tx.sent, s->rtcp_stats.rx.sent,
+ s->rtcp_stats.tx.lost, s->rtcp_stats.rx.lost,
+ 1.0*s->rtcp_stats.tx.jit/1000,
+ 1.0*s->rtcp_stats.rx.jit/1000);
+ }
+}
+
+
+static void stream_destructor(void *arg)
+{
+ struct stream *s = arg;
+
+ if (s->cfg.rtp_stats)
+ print_rtp_stats(s);
+
+ metric_reset(&s->metric_tx);
+ metric_reset(&s->metric_rx);
+
+ list_unlink(&s->le);
+ mem_deref(s->rtpkeep);
+ mem_deref(s->sdp);
+ mem_deref(s->mes);
+ mem_deref(s->mencs);
+ mem_deref(s->mns);
+ mem_deref(s->jbuf);
+ mem_deref(s->rtp);
+ mem_deref(s->cname);
+}
+
+
+static void rtp_recv(const struct sa *src, const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct stream *s = arg;
+ bool flush = false;
+ int err;
+
+ if (!mbuf_get_left(mb))
+ return;
+
+ if (!(sdp_media_ldir(s->sdp) & SDP_RECVONLY))
+ return;
+
+ metric_add_packet(&s->metric_rx, mbuf_get_left(mb));
+
+ if (hdr->ssrc != s->ssrc_rx) {
+ if (s->ssrc_rx) {
+ flush = true;
+ info("stream: %s: SSRC changed %x -> %x"
+ " (%u bytes from %J)\n",
+ sdp_media_name(s->sdp), s->ssrc_rx, hdr->ssrc,
+ mbuf_get_left(mb), src);
+ }
+ s->ssrc_rx = hdr->ssrc;
+ }
+
+ if (s->jbuf) {
+
+ struct rtp_header hdr2;
+ void *mb2 = NULL;
+
+ /* Put frame in Jitter Buffer */
+ if (flush)
+ jbuf_flush(s->jbuf);
+
+ err = jbuf_put(s->jbuf, hdr, mb);
+ if (err) {
+ info("%s: dropping %u bytes from %J (%m)\n",
+ sdp_media_name(s->sdp), mb->end,
+ src, err);
+ s->metric_rx.n_err++;
+ }
+
+ if (jbuf_get(s->jbuf, &hdr2, &mb2)) {
+
+ if (!s->jbuf_started)
+ return;
+
+ memset(&hdr2, 0, sizeof(hdr2));
+ }
+
+ s->jbuf_started = true;
+
+ if (lostcalc(s, hdr2.seq) > 0)
+ s->rtph(hdr, NULL, s->arg);
+
+ s->rtph(&hdr2, mb2, s->arg);
+
+ mem_deref(mb2);
+ }
+ else {
+ if (lostcalc(s, hdr->seq) > 0)
+ s->rtph(hdr, NULL, s->arg);
+
+ s->rtph(hdr, mb, s->arg);
+ }
+}
+
+
+static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg)
+{
+ struct stream *s = arg;
+ (void)src;
+
+ if (s->rtcph)
+ s->rtcph(msg, s->arg);
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ (void)rtcp_stats(s->rtp, msg->r.sr.ssrc, &s->rtcp_stats);
+ break;
+ }
+}
+
+
+static int stream_sock_alloc(struct stream *s, int af)
+{
+ struct sa laddr;
+ int tos, err;
+
+ if (!s)
+ return EINVAL;
+
+ /* we listen on all interfaces */
+ sa_init(&laddr, af);
+
+ err = rtp_listen(&s->rtp, IPPROTO_UDP, &laddr,
+ s->cfg.rtp_ports.min, s->cfg.rtp_ports.max,
+ s->rtcp, rtp_recv, rtcp_handler, s);
+ if (err)
+ return err;
+
+ tos = s->cfg.rtp_tos;
+ (void)udp_setsockopt(rtp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+ (void)udp_setsockopt(rtcp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+
+ udp_rxsz_set(rtp_sock(s->rtp), RTP_RECV_SIZE);
+
+ return 0;
+}
+
+
+int stream_alloc(struct stream **sp, const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg)
+{
+ struct stream *s;
+ int err;
+
+ if (!sp || !cfg || !call || !rtph)
+ return EINVAL;
+
+ s = mem_zalloc(sizeof(*s), stream_destructor);
+ if (!s)
+ return ENOMEM;
+
+ MAGIC_INIT(s);
+
+ s->cfg = *cfg;
+ s->call = call;
+ s->rtph = rtph;
+ s->rtcph = rtcph;
+ s->arg = arg;
+ s->pseq = -1;
+ s->rtcp = s->cfg.rtcp_enable;
+
+ err = stream_sock_alloc(s, call_af(call));
+ if (err)
+ goto out;
+
+ err = str_dup(&s->cname, cname);
+ if (err)
+ goto out;
+
+ /* Jitter buffer */
+ if (cfg->jbuf_del.min && cfg->jbuf_del.max) {
+
+ err = jbuf_alloc(&s->jbuf, cfg->jbuf_del.min,
+ cfg->jbuf_del.max);
+ if (err)
+ goto out;
+ }
+
+ err = sdp_media_add(&s->sdp, sdp_sess, name,
+ sa_port(rtp_local(s->rtp)),
+ (menc && menc->sdp_proto) ? menc->sdp_proto :
+ sdp_proto_rtpavp);
+ if (err)
+ goto out;
+
+ if (label) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "label", "%d", label);
+ }
+
+ /* RFC 5506 */
+ if (s->rtcp)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-rsize", NULL);
+
+ /* RFC 5576 */
+ if (s->rtcp) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "ssrc", "%u cname:%s",
+ rtp_sess_ssrc(s->rtp), cname);
+ }
+
+ /* RFC 5761 */
+ if (cfg->rtcp_mux)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-mux", NULL);
+
+ if (err)
+ goto out;
+
+ if (mnat) {
+ err = mnat->mediah(&s->mns, mnat_sess, IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (menc) {
+ s->menc = menc;
+ s->mencs = mem_ref(menc_sess);
+ err = menc->mediah(&s->mes, menc_sess,
+ s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (err)
+ goto out;
+
+ s->pt_enc = -1;
+
+ list_append(call_streaml(call), &s->le, s);
+
+ out:
+ if (err)
+ mem_deref(s);
+ else
+ *sp = s;
+
+ return err;
+}
+
+
+struct sdp_media *stream_sdpmedia(const struct stream *s)
+{
+ return s ? s->sdp : NULL;
+}
+
+
+static void stream_start_keepalive(struct stream *s)
+{
+ const char *rtpkeep;
+
+ if (!s)
+ return;
+
+ rtpkeep = ua_prm(call_get_ua(s->call))->rtpkeep;
+
+ s->rtpkeep = mem_deref(s->rtpkeep);
+
+ if (rtpkeep && sdp_media_rformat(s->sdp, NULL)) {
+ int err;
+ err = rtpkeep_alloc(&s->rtpkeep, rtpkeep,
+ IPPROTO_UDP, s->rtp, s->sdp);
+ if (err) {
+ warning("stream: rtpkeep_alloc failed: %m\n", err);
+ }
+ }
+}
+
+
+int stream_send(struct stream *s, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb)
+{
+ int err = 0;
+
+ if (!s)
+ return EINVAL;
+
+ if (!sa_isset(sdp_media_raddr(s->sdp), SA_ALL))
+ return 0;
+ if (sdp_media_dir(s->sdp) != SDP_SENDRECV)
+ return 0;
+
+ metric_add_packet(&s->metric_tx, mbuf_get_left(mb));
+
+ if (pt < 0)
+ pt = s->pt_enc;
+
+ if (pt >= 0) {
+ err = rtp_send(s->rtp, sdp_media_raddr(s->sdp),
+ marker, pt, ts, mb);
+ if (err)
+ s->metric_tx.n_err++;
+ }
+
+ rtpkeep_refresh(s->rtpkeep, ts);
+
+ return err;
+}
+
+
+static void stream_remote_set(struct stream *s)
+{
+ struct sa rtcp;
+
+ if (!s)
+ return;
+
+ /* RFC 5761 */
+ if (s->cfg.rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) {
+
+ if (!s->rtcp_mux)
+ info("%s: RTP/RTCP multiplexing enabled\n",
+ sdp_media_name(s->sdp));
+ s->rtcp_mux = true;
+ }
+
+ rtcp_enable_mux(s->rtp, s->rtcp_mux);
+
+ sdp_media_raddr_rtcp(s->sdp, &rtcp);
+
+ rtcp_start(s->rtp, s->cname,
+ s->rtcp_mux ? sdp_media_raddr(s->sdp): &rtcp);
+}
+
+
+void stream_update(struct stream *s)
+{
+ const struct sdp_format *fmt;
+ int err = 0;
+
+ if (!s)
+ return;
+
+ fmt = sdp_media_rformat(s->sdp, NULL);
+
+ s->pt_enc = fmt ? fmt->pt : -1;
+
+ if (sdp_media_has_media(s->sdp))
+ stream_remote_set(s);
+
+ if (s->menc && s->menc->mediah) {
+ err = s->menc->mediah(&s->mes, s->mencs, s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err) {
+ warning("stream: mediaenc update: %m\n", err);
+ }
+ }
+}
+
+
+void stream_update_encoder(struct stream *s, int pt_enc)
+{
+ if (pt_enc >= 0)
+ s->pt_enc = pt_enc;
+}
+
+
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s)
+{
+ struct jbuf_stat stat;
+ int err;
+
+ if (!s)
+ return EINVAL;
+
+ err = re_hprintf(pf, " %s:", sdp_media_name(s->sdp));
+
+ err |= jbuf_stats(s->jbuf, &stat);
+ if (err) {
+ err = re_hprintf(pf, "Jbuf stat: (not available)");
+ }
+ else {
+ err = re_hprintf(pf, "Jbuf stat: put=%u get=%u or=%u ur=%u",
+ stat.n_put, stat.n_get,
+ stat.n_overflow, stat.n_underflow);
+ }
+
+ return err;
+}
+
+
+void stream_hold(struct stream *s, bool hold)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_ldir(s->sdp, hold ? SDP_SENDONLY : SDP_SENDRECV);
+}
+
+
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx)
+{
+ if (!s)
+ return;
+
+ rtcp_set_srate(s->rtp, srate_tx, srate_rx);
+}
+
+
+void stream_send_fir(struct stream *s, bool pli)
+{
+ int err;
+
+ if (!s)
+ return;
+
+ if (pli)
+ err = rtcp_send_pli(s->rtp, s->ssrc_rx);
+ else
+ err = rtcp_send_fir(s->rtp, rtp_sess_ssrc(s->rtp));
+
+ if (err) {
+ s->metric_tx.n_err++;
+
+ warning("stream: failed to send RTCP %s: %m\n",
+ pli ? "PLI" : "FIR", err);
+ }
+}
+
+
+void stream_reset(struct stream *s)
+{
+ if (!s)
+ return;
+
+ jbuf_flush(s->jbuf);
+
+ stream_start_keepalive(s);
+}
+
+
+void stream_set_bw(struct stream *s, uint32_t bps)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_lbandwidth(s->sdp, SDP_BANDWIDTH_AS, bps / 1024);
+}
+
+
+int stream_debug(struct re_printf *pf, const struct stream *s)
+{
+ struct sa rrtcp;
+ int err;
+
+ if (!s)
+ return 0;
+
+ err = re_hprintf(pf, " %s dir=%s pt_enc=%d\n", sdp_media_name(s->sdp),
+ sdp_dir_name(sdp_media_dir(s->sdp)),
+ s->pt_enc);
+
+ sdp_media_raddr_rtcp(s->sdp, &rrtcp);
+ err |= re_hprintf(pf, " remote: %J/%J\n",
+ sdp_media_raddr(s->sdp), &rrtcp);
+
+ err |= rtp_debug(pf, s->rtp);
+ err |= jbuf_debug(pf, s->jbuf);
+
+ return err;
+}
+
+
+int stream_print(struct re_printf *pf, const struct stream *s)
+{
+ if (!s)
+ return 0;
+
+ return re_hprintf(pf, " %s=%u/%u", sdp_media_name(s->sdp),
+ s->metric_tx.cur_bitrate,
+ s->metric_rx.cur_bitrate);
+}
diff --git a/src/ua.c b/src/ua.c
new file mode 100644
index 0000000..713890c
--- /dev/null
+++ b/src/ua.c
@@ -0,0 +1,1562 @@
+/**
+ * @file src/ua.c User-Agent
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "ua"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+/** Magic number */
+#define MAGIC 0x0a0a0a0a
+#include "magic.h"
+
+
+enum {
+ MAX_CALLS = 4
+};
+
+
+/** Defines a SIP User Agent object */
+struct ua {
+ MAGIC_DECL /**< Magic number for struct ua */
+ struct ua **uap; /**< Pointer to application's ua */
+ struct le le; /**< Linked list element */
+ struct account *acc; /**< Account Parameters */
+ struct list regl; /**< List of Register clients */
+ struct list calls; /**< List of active calls (struct call) */
+ struct play *play; /**< Playback of ringtones etc. */
+ struct pl extensionv[8]; /**< Vector of SIP extensions */
+ size_t extensionc; /**< Number of SIP extensions */
+ char *cuser; /**< SIP Contact username */
+ int af; /**< Preferred Address Family */
+};
+
+struct ua_eh {
+ struct le le;
+ ua_event_h *h;
+ void *arg;
+};
+
+static struct {
+ struct config_sip *cfg; /**< SIP configuration */
+ struct list ual; /**< List of User-Agents (struct ua) */
+ struct list ehl; /**< Event handlers (struct ua_eh) */
+ struct sip *sip; /**< SIP Stack */
+ struct sip_lsnr *lsnr; /**< SIP Listener */
+ struct sipsess_sock *sock; /**< SIP Session socket */
+ struct sipevent_sock *evsock; /**< SIP Event socket */
+ struct ua *ua_cur; /**< Current User-Agent */
+ bool use_udp; /**< Use UDP transport */
+ bool use_tcp; /**< Use TCP transport */
+ bool use_tls; /**< Use TLS transport */
+ bool prefer_ipv6; /**< Force IPv6 transport */
+#ifdef USE_TLS
+ struct tls *tls; /**< TLS Context */
+#endif
+} uag = {
+ NULL,
+ LIST_INIT,
+ LIST_INIT,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ true,
+ false,
+#ifdef USE_TLS
+ NULL,
+#endif
+};
+
+
+/* prototypes */
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri);
+
+
+/* This function is called when all SIP transactions are done */
+static void exit_handler(void *arg)
+{
+ (void)arg;
+
+ re_cancel();
+}
+
+
+void ua_printf(const struct ua *ua, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!ua)
+ return;
+
+ va_start(ap, fmt);
+ info("%r@%r: %v", &ua->acc->luri.user, &ua->acc->luri.host, fmt, &ap);
+ va_end(ap);
+}
+
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...)
+{
+ struct le *le;
+ char buf[256];
+ va_list ap;
+
+ if (!ua)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* send event to all clients */
+ for (le = uag.ehl.head; le; le = le->next) {
+
+ struct ua_eh *eh = le->data;
+
+ eh->h(ua, ev, call, buf, eh->arg);
+ }
+}
+
+
+/**
+ * Start registration of a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_register(struct ua *ua)
+{
+ struct account *acc;
+ struct le *le;
+ struct uri uri;
+ char reg_uri[64];
+ char params[256] = "";
+ unsigned i;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ acc = ua->acc;
+ uri = ua->acc->luri;
+ uri.user = uri.password = pl_null;
+ if (re_snprintf(reg_uri, sizeof(reg_uri), "%H", uri_encode, &uri) < 0)
+ return ENOMEM;
+
+ if (str_isset(uag.cfg->uuid)) {
+ if (re_snprintf(params, sizeof(params),
+ ";+sip.instance=\"<urn:uuid:%s>\"",
+ uag.cfg->uuid) < 0)
+ return ENOMEM;
+ }
+
+ if (acc->regq) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";q=%s", acc->regq) < 0)
+ return ENOMEM;
+ }
+
+ if (acc->mnat && acc->mnat->ftag) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";%s", acc->mnat->ftag) < 0)
+ return ENOMEM;
+ }
+
+ ua_event(ua, UA_EVENT_REGISTERING, NULL, NULL);
+
+ for (le = ua->regl.head, i=0; le; le = le->next, i++) {
+ struct reg *reg = le->data;
+
+ err = reg_register(reg, reg_uri, params,
+ acc->regint, acc->outbound[i]);
+ if (err) {
+ DEBUG_WARNING("SIP register failed: %m\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+bool ua_isregistered(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return false;
+
+ for (le = ua->regl.head; le; le = le->next) {
+
+ const struct reg *reg = le->data;
+
+ /* it is enough if one of the registrations work */
+ if (reg_isok(reg))
+ return true;
+ }
+
+ return false;
+}
+
+
+static const char *translate_errorcode(uint16_t scode)
+{
+ switch (scode) {
+
+ case 404: return "notfound.wav";
+ case 486: return "busy.wav";
+ case 487: return NULL; /* ignore */
+ default: return "error.wav";
+ }
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *str, void *arg)
+{
+ struct ua *ua = arg;
+ const char *peeruri;
+ struct call *call2 = NULL;
+ int err;
+
+ MAGIC_CHECK(ua);
+
+ peeruri = call_peeruri(call);
+
+ /* stop any ringtones */
+ ua->play = mem_deref(ua->play);
+
+ switch (ev) {
+
+ case CALL_EVENT_INCOMING:
+ switch (ua->acc->answermode) {
+
+ case ANSWERMODE_EARLY:
+ (void)call_progress(call);
+ break;
+
+ case ANSWERMODE_AUTO:
+ (void)call_answer(call, 200);
+ break;
+
+ case ANSWERMODE_MANUAL:
+ default:
+ if (list_count(&ua->calls) > 1) {
+ (void)play_file(&ua->play,
+ "callwaiting.wav", 3);
+ }
+ else {
+ /* Alert user */
+ (void)play_file(&ua->play, "ring.wav", -1);
+ }
+
+ ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri);
+ break;
+ }
+ break;
+
+ case CALL_EVENT_RINGING:
+ (void)play_file(&ua->play, "ringback.wav", -1);
+
+ ua_event(ua, UA_EVENT_CALL_RINGING, call, peeruri);
+ break;
+
+ case CALL_EVENT_PROGRESS:
+ ua_printf(ua, "Call in-progress: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_PROGRESS, call, peeruri);
+ break;
+
+ case CALL_EVENT_ESTABLISHED:
+ ua_printf(ua, "Call established: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_ESTABLISHED, call, peeruri);
+ break;
+
+ case CALL_EVENT_CLOSED:
+ if (call_scode(call)) {
+ const char *tone;
+ tone = translate_errorcode(call_scode(call));
+ if (tone)
+ (void)play_file(&ua->play, tone, 1);
+ }
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+ break;
+
+ case CALL_EVENT_TRANSFER:
+
+ /*
+ * Create a new call to transfer target.
+ *
+ * NOTE: we will automatically connect a new call to the
+ * transfer target
+ */
+
+ ua_printf(ua, "transferring call to %s\n", str);
+
+ err = ua_call_alloc(&call2, ua, VIDMODE_ON, NULL, call,
+ call_localuri(call));
+ if (!err) {
+ struct pl pl;
+
+ pl_set_str(&pl, str);
+
+ err = call_connect(call2, &pl);
+ if (err) {
+ DEBUG_WARNING("transfer: connect error: %m\n",
+ err);
+ }
+ }
+
+ if (err) {
+ (void)call_notify_sipfrag(call, 500, "%m", err);
+ mem_deref(call2);
+ }
+ break;
+ }
+}
+
+
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri)
+{
+ struct call_prm cprm;
+
+ if (*callp) {
+ DEBUG_WARNING("call_alloc: call is already allocated\n");
+ return EALREADY;
+ }
+
+ cprm.vidmode = vidmode;
+ cprm.af = ua->af;
+
+ return call_alloc(callp, conf_config(), &ua->calls,
+ ua->acc->dispname,
+ local_uri ? local_uri : ua->acc->aor,
+ ua->acc, ua, &cprm,
+ msg, xcall, call_event_handler, ua);
+}
+
+
+static void handle_options(struct ua *ua, const struct sip_msg *msg)
+{
+ struct call *call = NULL;
+ struct mbuf *desc = NULL;
+ int err;
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL);
+ if (err) {
+ (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error");
+ return;
+ }
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ goto out;
+
+ err = sip_treplyf(NULL, NULL, uag.sip,
+ msg, true, 200, "OK",
+ "Contact: <sip:%s@%J%s>\r\n"
+ "Content-Type: application/sdp\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ ua->cuser, &msg->dst, sip_transp_param(msg->tp),
+ mbuf_get_left(desc),
+ mbuf_buf(desc),
+ mbuf_get_left(desc));
+ if (err) {
+ DEBUG_WARNING("options: sip_treplyf: %m\n", err);
+ }
+
+ out:
+ mem_deref(desc);
+ mem_deref(call);
+}
+
+
+static void ua_destructor(void *arg)
+{
+ struct ua *ua = arg;
+
+ if (ua->uap) {
+ *ua->uap = NULL;
+ ua->uap = NULL;
+ }
+
+ list_unlink(&ua->le);
+
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ list_flush(&ua->calls);
+ list_flush(&ua->regl);
+ mem_deref(ua->play);
+ mem_deref(ua->cuser);
+ mem_deref(ua->acc);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ if (pl_strcmp(&msg->met, "OPTIONS"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ handle_options(ua, msg);
+
+ return true;
+}
+
+
+static void add_extension(struct ua *ua, const char *extension)
+{
+ struct pl e;
+
+ if (ua->extensionc >= ARRAY_SIZE(ua->extensionv)) {
+ DEBUG_WARNING("maximum %u number of SIP extensions\n");
+ return;
+ }
+
+ pl_set_str(&e, extension);
+
+ ua->extensionv[ua->extensionc++] = e;
+}
+
+
+/**
+ * Allocate a SIP User-Agent
+ *
+ * @param uap Pointer to allocated User-Agent object
+ * @param aor SIP Address-of-Record (AOR)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_alloc(struct ua **uap, const char *aor)
+{
+ struct ua *ua;
+ int err;
+
+ if (!aor)
+ return EINVAL;
+
+ ua = mem_zalloc(sizeof(*ua), ua_destructor);
+ if (!ua)
+ return ENOMEM;
+
+ MAGIC_INIT(ua);
+
+ list_init(&ua->calls);
+
+#if HAVE_INET6
+ ua->af = uag.prefer_ipv6 ? AF_INET6 : AF_INET;
+#else
+ ua->af = AF_INET;
+#endif
+
+ /* generate a unique contact-user, this is needed to route
+ incoming requests when using multiple useragents */
+ err = re_sdprintf(&ua->cuser, "%p", ua);
+ if (err)
+ goto out;
+
+ /* Decode SIP address */
+
+ err = account_alloc(&ua->acc, aor);
+ if (err)
+ goto out;
+
+ if (ua->acc->sipnat) {
+ ua_printf(ua, "Using sipnat: `%s'\n", ua->acc->sipnat);
+ }
+
+ if (ua->acc->mnat) {
+ ua_printf(ua, "Using medianat `%s'\n",
+ ua->acc->mnat->id);
+
+ if (0 == str_casecmp(ua->acc->mnat->id, "ice"))
+ add_extension(ua, "ice");
+ }
+
+ if (ua->acc->menc) {
+ ua_printf(ua, "Using media encryption `%s'\n",
+ ua->acc->menc->id);
+ }
+
+ /* Register clients */
+ if (0 == str_casecmp(ua->acc->sipnat, "outbound")) {
+
+ size_t i;
+
+ add_extension(ua, "path");
+ add_extension(ua, "outbound");
+
+ if (!str_isset(uag.cfg->uuid)) {
+
+ DEBUG_WARNING("outbound requires valid UUID!\n");
+ err = ENOSYS;
+ goto out;
+ }
+
+ for (i=0; i<ARRAY_SIZE(ua->acc->outbound); i++) {
+
+ if (ua->acc->outbound[i] && ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, (int)i+1);
+ if (err)
+ break;
+ }
+ }
+ }
+ else if (ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, 0);
+ }
+ if (err)
+ goto out;
+
+ list_append(&uag.ual, &ua->le, ua);
+
+ if (ua->acc->regint) {
+ err = ua_register(ua);
+ }
+
+ if (!uag_current())
+ uag_current_set(ua);
+
+ out:
+ if (err)
+ mem_deref(ua);
+ else if (uap) {
+ *uap = ua;
+
+ ua->uap = uap;
+ }
+
+ return err;
+}
+
+
+/**
+ * Connect an outgoing call to a given SIP uri
+ *
+ * @param ua User-Agent
+ * @param callp Optional pointer to allocated call object
+ * @param from_uri Optional From uri, or NULL for default AOR
+ * @param uri SIP uri to connect to
+ * @param params Optional URI parameters
+ * @param vmode Video mode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_connect(struct ua *ua, struct call **callp,
+ const char *from_uri, const char *uri,
+ const char *params, enum vidmode vmode)
+{
+ struct call *call = NULL;
+ struct mbuf *dialbuf;
+ struct pl pl;
+ size_t len;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ len = str_len(uri);
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ if (params)
+ err |= mbuf_printf(dialbuf, "<");
+
+ /* Append sip: scheme if missing */
+ if (0 != re_regex(uri, len, "sip:"))
+ err |= mbuf_printf(dialbuf, "sip:");
+
+ err |= mbuf_write_str(dialbuf, uri);
+
+ /* Append domain if missing */
+ if (0 != re_regex(uri, len, "[^@]+@[^]+", NULL, NULL)) {
+#if HAVE_INET6
+ if (AF_INET6 == ua->acc->luri.af)
+ err |= mbuf_printf(dialbuf, "@[%r]",
+ &ua->acc->luri.host);
+ else
+#endif
+ err |= mbuf_printf(dialbuf, "@%r",
+ &ua->acc->luri.host);
+
+ /* Also append port if specified and not 5060 */
+ switch (ua->acc->luri.port) {
+
+ case 0:
+ case SIP_PORT:
+ break;
+
+ default:
+ err |= mbuf_printf(dialbuf, ":%u", ua->acc->luri.port);
+ break;
+ }
+ }
+
+ if (params) {
+ err |= mbuf_printf(dialbuf, ";%s", params);
+ }
+
+ /* Append any optional URI parameters */
+ err |= mbuf_write_pl(dialbuf, &ua->acc->luri.params);
+
+ if (params)
+ err |= mbuf_printf(dialbuf, ">");
+
+ if (err)
+ goto out;
+
+ err = ua_call_alloc(&call, ua, vmode, NULL, NULL, from_uri);
+ if (err)
+ goto out;
+
+ pl.p = (char *)dialbuf->buf;
+ pl.l = dialbuf->end;
+
+ err = call_connect(call, &pl);
+
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ out:
+ mem_deref(dialbuf);
+
+ return err;
+}
+
+
+/**
+ * Hangup the current call
+ *
+ * @param ua User-Agent
+ * @param call Call to hangup, or NULL for current call
+ * @param scode Optional status code
+ * @param reason Optional reason
+ */
+void ua_hangup(struct ua *ua, struct call *call,
+ uint16_t scode, const char *reason)
+{
+ if (!ua)
+ return;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return;
+ }
+
+ ua->play = mem_deref(ua->play);
+
+ (void)call_hangup(call, scode, reason);
+
+ mem_deref(call);
+}
+
+
+/**
+ * Answer an incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to hangup, or NULL for current call
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_answer(struct ua *ua, struct call *call)
+{
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ /* todo: put previous call on-hold (if configured) */
+
+ ua->play = mem_deref(ua->play);
+
+ return call_answer(call, 200);
+}
+
+
+int ua_print_status(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "%-42s", ua->acc->aor);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_status(pf, le->data);
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Send SIP OPTIONS message to a peer
+ *
+ * @param ua User-Agent object
+ * @param uri Peer SIP Address
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_options_send(struct ua *ua, const char *uri,
+ options_resp_h *resph, void *arg)
+{
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ err = sip_req_send(ua, "OPTIONS", uri, resph, arg,
+ "Accept: application/sdp\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ if (err) {
+ DEBUG_WARNING("send options: (%m)\n", err);
+ }
+
+ return err;
+}
+
+
+/**
+ * Get the AOR of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return AOR
+ */
+const char *ua_aor(const struct ua *ua)
+{
+ return ua ? ua->acc->aor : NULL;
+}
+
+
+/**
+ * Get the outbound SIP proxy of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Outbound SIP proxy uri
+ */
+const char *ua_outbound(const struct ua *ua)
+{
+ /* NOTE: we pick the first outbound server, should be rotated? */
+ return ua ? ua->acc->outbound[0] : NULL;
+}
+
+
+/**
+ * Get the current call object of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Current call, NULL if no active calls
+ *
+ *
+ * Current call strategy:
+ *
+ * We can only have 1 current call. The current call is the one that was
+ * added last (end of the list), which is not on-hold
+ */
+struct call *ua_call(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return NULL;
+
+ for (le = ua->calls.tail; le; le = le->prev) {
+
+ struct call *call = le->data;
+
+ /* todo: check if call is on-hold */
+
+ return call;
+ }
+
+ return NULL;
+}
+
+
+int ua_debug(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "--- %s ---\n", ua->acc->aor);
+ err |= re_hprintf(pf, " cuser: %s\n", ua->cuser);
+ err |= re_hprintf(pf, " af: %s\n", net_af2name(ua->af));
+ err |= re_hprintf(pf, " %H", ua_print_supported, ua);
+
+ err |= account_debug(pf, ua->acc);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_debug(pf, le->data);
+
+ return err;
+}
+
+
+/* One instance */
+
+
+static int add_transp_af(const struct sa *laddr)
+{
+ struct sa local;
+ int err = 0;
+
+ if (str_isset(uag.cfg->local)) {
+ err = sa_decode(&local, uag.cfg->local,
+ str_len(uag.cfg->local));
+ if (err) {
+ err = sa_set_str(&local, uag.cfg->local, 0);
+ if (err) {
+ DEBUG_WARNING("decode failed: %s\n",
+ uag.cfg->local);
+ return err;
+ }
+ }
+
+ if (!sa_isset(&local, SA_ADDR)) {
+ uint16_t port = sa_port(&local);
+ (void)sa_set_sa(&local, &laddr->u.sa);
+ sa_set_port(&local, port);
+ }
+
+ if (sa_af(laddr) != sa_af(&local))
+ return 0;
+ }
+ else {
+ sa_cpy(&local, laddr);
+ sa_set_port(&local, 0);
+ }
+
+ if (uag.use_udp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_UDP, &local);
+ if (uag.use_tcp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_TCP, &local);
+ if (err) {
+ DEBUG_WARNING("SIP Transport failed: %m\n", err);
+ return err;
+ }
+
+#ifdef USE_TLS
+ if (uag.use_tls) {
+ /* Build our SSL context*/
+ if (!uag.tls) {
+ const char *cert = NULL;
+
+ if (str_isset(uag.cfg->cert)) {
+ cert = uag.cfg->cert;
+ info("SIP Certificate: %s\n", cert);
+ }
+
+ err = tls_alloc(&uag.tls, TLS_METHOD_SSLV23,
+ cert, NULL);
+ if (err) {
+ DEBUG_WARNING("tls_alloc() failed: %m\n", err);
+ return err;
+ }
+ }
+
+ if (sa_isset(&local, SA_PORT))
+ sa_set_port(&local, sa_port(&local) + 1);
+
+ err = sip_transp_add(uag.sip, SIP_TRANSP_TLS, &local, uag.tls);
+ if (err) {
+ DEBUG_WARNING("SIP/TLS transport failed: %m\n", err);
+ return err;
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+static int ua_add_transp(void)
+{
+ int err = 0;
+
+ if (!uag.prefer_ipv6) {
+ if (sa_isset(net_laddr_af(AF_INET), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(AF_INET));
+ }
+
+#if HAVE_INET6
+ if (sa_isset(net_laddr_af(AF_INET6), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(AF_INET6));
+#endif
+
+ return err;
+}
+
+
+static bool require_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+ bool supported = false;
+ size_t i;
+ (void)msg;
+
+ for (i=0; i<ua->extensionc; i++) {
+
+ if (!pl_casecmp(&hdr->val, &ua->extensionv[i])) {
+ supported = true;
+ break;
+ }
+ }
+
+ return !supported;
+}
+
+
+/* Handle incoming calls */
+static void sipsess_conn_handler(const struct sip_msg *msg, void *arg)
+{
+ const struct sip_hdr *hdr;
+ struct ua *ua;
+ struct call *call = NULL;
+ char str[256], to_uri[256];
+ int err;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ DEBUG_WARNING("%r: UA not found: %r\n",
+ &msg->from.auri, &msg->uri.user);
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return;
+ }
+
+ /* handle multiple calls */
+ if (list_count(&ua->calls) + 1 > MAX_CALLS) {
+ info("ua: rejected call from %r (maximum %d calls)\n",
+ &msg->from.auri, MAX_CALLS);
+ (void)sip_treply(NULL, uag.sip, msg, 486, "Busy Here");
+ return;
+ }
+
+ /* Handle Require: header, check for any required extensions */
+ hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_REQUIRE,
+ require_handler, ua);
+ if (hdr) {
+ info("ua: call from %r rejected with 420"
+ " -- option-tag '%r' not supported\n",
+ &msg->from.auri, &hdr->val);
+
+ (void)sip_treplyf(NULL, NULL, uag.sip, msg, false,
+ 420, "Bad Extension",
+ "Unsupported: %r\r\n"
+ "Content-Length: 0\r\n\r\n",
+ &hdr->val);
+ return;
+ }
+
+ (void)pl_strcpy(&msg->to.auri, to_uri, sizeof(to_uri));
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, msg, NULL, to_uri);
+ if (err) {
+ DEBUG_WARNING("call_alloc: %m\n", err);
+ goto error;
+ }
+
+ err = call_accept(call, uag.sock, msg);
+ if (err)
+ goto error;
+
+ return;
+
+ error:
+ mem_deref(call);
+ (void)re_snprintf(str, sizeof(str), "Error (%m)", err);
+ (void)sip_treply(NULL, uag.sip, msg, 500, str);
+}
+
+
+static void net_change_handler(void *arg)
+{
+ (void)arg;
+
+ info("IP-address changed: %j\n", net_laddr_af(AF_INET));
+
+ (void)uag_reset_transp(true, true);
+}
+
+
+static int cmd_quit(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "Quit\n");
+
+ ua_stop_all(false);
+
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+ {'q', 0, "Quit", cmd_quit },
+};
+
+
+/**
+ * Initialise the User-Agents
+ *
+ * @param software SIP User-Agent string
+ * @param udp Enable UDP transport
+ * @param tcp Enable TCP transport
+ * @param tls Enable TLS transport
+ * @param prefer_ipv6 Prefer IPv6 flag
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_init(const char *software, bool udp, bool tcp, bool tls,
+ bool prefer_ipv6)
+{
+ struct config *cfg = conf_config();
+ uint32_t bsize;
+ int err;
+
+ uag.cfg = &cfg->sip;
+ bsize = cfg->sip.trans_bsize;
+ ui_init(&cfg->input);
+
+ play_init();
+
+ /* Initialise Network */
+ err = net_init(&cfg->net, prefer_ipv6 ? AF_INET6 : AF_INET);
+ if (err) {
+ DEBUG_WARNING("network init failed: %m\n", err);
+ return err;
+ }
+
+ uag.use_udp = udp;
+ uag.use_tcp = tcp;
+ uag.use_tls = tls;
+ uag.prefer_ipv6 = prefer_ipv6;
+
+ list_init(&uag.ual);
+
+ err = sip_alloc(&uag.sip, net_dnsc(), bsize, bsize, bsize,
+ software, exit_handler, NULL);
+ if (err) {
+ DEBUG_WARNING("sip stack failed: %m\n", err);
+ goto out;
+ }
+
+ err = ua_add_transp();
+ if (err)
+ goto out;
+
+ err = sip_listen(&uag.lsnr, uag.sip, true, request_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipsess_listen(&uag.sock, uag.sip, bsize,
+ sipsess_conn_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipevent_listen(&uag.evsock, uag.sip, bsize, bsize, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = cmd_register(cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ goto out;
+
+ net_change(60, net_change_handler, NULL);
+
+ out:
+ if (err) {
+ DEBUG_WARNING("init failed (%m)\n", err);
+ ua_close();
+ }
+ return err;
+}
+
+
+/**
+ * Close all active User-Agents
+ */
+void ua_close(void)
+{
+ cmd_unregister(cmdv);
+ net_close();
+ play_close();
+
+ uag.evsock = mem_deref(uag.evsock);
+ uag.sock = mem_deref(uag.sock);
+ uag.lsnr = mem_deref(uag.lsnr);
+ uag.sip = mem_deref(uag.sip);
+
+#ifdef USE_TLS
+ uag.tls = mem_deref(uag.tls);
+#endif
+
+ list_flush(&uag.ual);
+ list_flush(&uag.ehl);
+}
+
+
+/**
+ * Stop all User-Agents
+ *
+ * @param forced True to force, otherwise false
+ */
+void ua_stop_all(bool forced)
+{
+ module_app_unload();
+
+ if (!list_isempty(&uag.ual)) {
+ const uint32_t n = list_count(&uag.ual);
+ info("Stopping %u useragent%s.. %s\n",
+ n, n==1 ? "" : "s", forced ? "(Forced)" : "");
+ }
+
+ if (forced)
+ sipsess_close_all(uag.sock);
+ else
+ list_flush(&uag.ual);
+
+ sip_close(uag.sip, forced);
+}
+
+
+/**
+ * Reset the SIP transports for all User-Agents
+ *
+ * @param reg True to reset registration
+ * @param reinvite True to update active calls
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uag_reset_transp(bool reg, bool reinvite)
+{
+ struct le *le;
+ int err;
+
+ /* Update SIP transports */
+ sip_transp_flush(uag.sip);
+
+ (void)net_check();
+ err = ua_add_transp();
+ if (err)
+ return err;
+
+ /* Re-REGISTER all User-Agents */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (reg && ua->acc->regint) {
+ err |= ua_register(ua);
+ }
+
+ /* update all active calls */
+ if (reinvite) {
+ struct le *lec;
+
+ for (lec = ua->calls.head; lec; lec = lec->next) {
+ struct call *call = lec->data;
+
+ err |= call_reset_transp(call);
+ }
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Print the SIP Status for all User-Agents
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_print_sip_status(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return sip_debug(pf, uag.sip);
+}
+
+
+/**
+ * Print all calls for a given User-Agent
+ */
+int ua_print_calls(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err = 0;
+
+ err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n",
+ list_count(&ua->calls));
+
+ for (le = ua->calls.head; le; le = le->next) {
+
+ const struct call *call = le->data;
+
+ err |= re_hprintf(pf, " %H\n", call_info, call);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Get the global SIP Stack
+ *
+ * @return SIP Stack
+ */
+struct sip *uag_sip(void)
+{
+ return uag.sip;
+}
+
+
+/**
+ * Get the global SIP Session socket
+ *
+ * @return SIP Session socket
+ */
+struct sipsess_sock *uag_sipsess_sock(void)
+{
+ return uag.sock;
+}
+
+
+/**
+ * Get the global SIP Event socket
+ *
+ * @return SIP Event socket
+ */
+struct sipevent_sock *uag_sipevent_sock(void)
+{
+ return uag.evsock;
+}
+
+
+struct tls *uag_tls(void)
+{
+#ifdef USE_TLS
+ return uag.tls;
+#else
+ return NULL;
+#endif
+}
+
+
+/**
+ * Get the name of the User-Agent event
+ *
+ * @param ev User-Agent event
+ *
+ * @return Name of the event
+ */
+const char *uag_event_str(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING: return "REGISTERING";
+ case UA_EVENT_REGISTER_OK: return "REGISTER_OK";
+ case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL";
+ case UA_EVENT_UNREGISTERING: return "UNREGISTERING";
+ case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING";
+ case UA_EVENT_CALL_RINGING: return "CALL_RINGING";
+ case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS";
+ case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED";
+ case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED";
+ default: return "?";
+ }
+}
+
+
+/**
+ * Get the current SIP socket file descriptor for a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return File descriptor, or -1 if not available
+ */
+int ua_sipfd(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return -1;
+
+ for (le = ua->regl.head; le; le = le->next) {
+
+ struct reg *reg = le->data;
+ int fd;
+
+ fd = reg_sipfd(reg);
+ if (fd != -1)
+ return fd;
+ }
+
+ return -1;
+}
+
+
+/**
+ * Find the correct UA from the contact user
+ *
+ * @param cuser Contact username
+ *
+ * @return Matching UA if found, NULL if not found
+ */
+struct ua *uag_find(const struct pl *cuser)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_strcasecmp(cuser, ua->cuser))
+ return ua;
+ }
+
+ /* Try also matching by AOR, for better interop */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_casecmp(cuser, &ua->acc->luri.user))
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) from an Address-of-Record (AOR)
+ *
+ * @param aor Address-of-Record string
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_aor(const char *aor)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (str_isset(aor) && str_cmp(ua->acc->aor, aor))
+ continue;
+
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) which has certain address parameter and/or value
+ *
+ * @param name SIP Address parameter name
+ * @param value SIP Address parameter value (optional)
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_param(const char *name, const char *value)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+ struct sip_addr *laddr = account_laddr(ua->acc);
+ struct pl val;
+
+ if (value) {
+
+ if (0 == sip_param_decode(&laddr->params, name, &val)
+ &&
+ 0 == pl_strcasecmp(&val, value)) {
+ return ua;
+ }
+ }
+ else {
+ if (0 == sip_param_exists(&laddr->params, name, &val))
+ return ua;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the contact user of a User-Agent (UA)
+ *
+ * @param ua User-Agent
+ *
+ * @return Contact user
+ */
+const char *ua_cuser(const struct ua *ua)
+{
+ return ua ? ua->cuser : NULL;
+}
+
+
+struct list *uag_list(void)
+{
+ return &uag.ual;
+}
+
+
+/**
+ * Return list of methods supported by the UA
+ *
+ * @return String of supported methods
+ */
+const char *uag_allowed_methods(void)
+{
+ return "INVITE,ACK,BYE,CANCEL,OPTIONS,REFER,NOTIFY,SUBSCRIBE,INFO";
+}
+
+
+int ua_print_supported(struct re_printf *pf, const struct ua *ua)
+{
+ size_t i;
+ int err;
+
+ err = re_hprintf(pf, "Supported:");
+
+ for (i=0; i<ua->extensionc; i++) {
+ err |= re_hprintf(pf, "%s%r",
+ i==0 ? " " : ",", &ua->extensionv[i]);
+ }
+
+ err |= re_hprintf(pf, "\r\n");
+
+ return err;
+}
+
+
+struct account *ua_prm(const struct ua *ua)
+{
+ return ua ? ua->acc : NULL;
+}
+
+
+struct list *ua_calls(const struct ua *ua)
+{
+ return ua ? (struct list *)&ua->calls : NULL;
+}
+
+
+static void eh_destructor(void *arg)
+{
+ struct ua_eh *eh = arg;
+ list_unlink(&eh->le);
+}
+
+
+int uag_event_register(ua_event_h *h, void *arg)
+{
+ struct ua_eh *eh;
+
+ if (!h)
+ return EINVAL;
+
+ eh = mem_zalloc(sizeof(*eh), eh_destructor);
+ if (!eh)
+ return ENOMEM;
+
+ eh->h = h;
+ eh->arg = arg;
+
+ list_append(&uag.ehl, &eh->le, eh);
+
+ return 0;
+}
+
+
+void uag_event_unregister(ua_event_h *h)
+{
+ struct le *le;
+
+ for (le = uag.ehl.head; le; le = le->next) {
+
+ struct ua_eh *eh = le->data;
+
+ if (eh->h == h) {
+ mem_deref(eh);
+ break;
+ }
+ }
+}
+
+
+void uag_current_set(struct ua *ua)
+{
+ uag.ua_cur = ua;
+}
+
+
+struct ua *uag_current(void)
+{
+ if (list_isempty(uag_list()))
+ return NULL;
+
+ return uag.ua_cur;
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..2d69fd7
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,185 @@
+/**
+ * @file ui.c User Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** User Interface */
+struct ui {
+ struct le le;
+ const char *name;
+ struct ui_st *st; /* only one instance */
+ ui_output_h *outputh;
+ struct cmd_ctx *ctx;
+};
+
+static struct list uil; /**< List of UIs (struct ui) */
+static struct config_input input_cfg;
+
+
+static void ui_handler(char key, struct re_printf *pf, void *arg)
+{
+ struct ui *ui = arg;
+
+ (void)cmd_process(ui ? &ui->ctx : NULL, key, pf);
+}
+
+
+static void destructor(void *arg)
+{
+ struct ui *ui = arg;
+
+ list_unlink(&ui->le);
+ mem_deref(ui->st);
+ mem_deref(ui->ctx);
+}
+
+
+static int stdout_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ if (1 != fwrite(p, size, 1, stdout))
+ return ENOMEM;
+
+ return 0;
+}
+
+
+/**
+ * Register a new User-Interface (UI) module
+ *
+ * @param uip Pointer to allocated UI module
+ * @param name Name of the UI module
+ * @param alloch UI allocation handler
+ * @param outh UI output handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ui_register(struct ui **uip, const char *name,
+ ui_alloc_h *alloch, ui_output_h *outh)
+{
+ struct ui *ui;
+ int err = 0;
+
+ if (!uip)
+ return EINVAL;
+
+ ui = mem_zalloc(sizeof(*ui), destructor);
+ if (!ui)
+ return ENOMEM;
+
+ list_append(&uil, &ui->le, ui);
+
+ ui->name = name;
+ ui->outputh = outh;
+
+ if (alloch) {
+ struct ui_prm prm;
+
+ prm.device = input_cfg.device;
+ prm.port = input_cfg.port;
+
+ err = alloch(&ui->st, &prm, ui_handler, ui);
+ if (err) {
+ warning("ui: register: module '%s' failed (%m)\n",
+ ui->name, err);
+ }
+ }
+
+ if (err)
+ mem_deref(ui);
+ else
+ *uip = ui;
+
+ return err;
+}
+
+
+/**
+ * Send input to the UI subsystem
+ *
+ * @param key Input character
+ */
+void ui_input(char key)
+{
+ struct re_printf pf;
+
+ pf.vph = stdout_handler;
+ pf.arg = NULL;
+
+ ui_handler(key, &pf, list_ledata(uil.head));
+}
+
+
+/**
+ * Send an input string to the UI subsystem
+ *
+ * @param str Input string
+ */
+void ui_input_str(const char *str)
+{
+ struct re_printf pf;
+ struct pl pl;
+
+ if (!str)
+ return;
+
+ pf.vph = stdout_handler;
+ pf.arg = NULL;
+
+ pl_set_str(&pl, str);
+
+ (void)ui_input_pl(&pf, &pl);
+}
+
+
+int ui_input_pl(struct re_printf *pf, const struct pl *pl)
+{
+ struct cmd_ctx *ctx = NULL;
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl)
+ return EINVAL;
+
+ for (i=0; i<pl->l; i++) {
+ err |= cmd_process(&ctx, pl->p[i], pf);
+ }
+
+ if (pl->l > 1 && ctx)
+ err |= cmd_process(&ctx, '\n', pf);
+
+ return err;
+}
+
+
+/**
+ * Send output to all modules registered in the UI subsystem
+ *
+ * @param str Output string
+ */
+void ui_output(const char *str)
+{
+ struct le *le;
+
+ for (le = uil.head; le; le = le->next) {
+ const struct ui *ui = le->data;
+
+ if (ui->outputh)
+ ui->outputh(ui->st, str);
+ }
+}
+
+
+void ui_init(const struct config_input *cfg)
+{
+ if (!cfg)
+ return;
+
+ input_cfg = *cfg;
+}
diff --git a/src/vidcodec.c b/src/vidcodec.c
new file mode 100644
index 0000000..7624ae7
--- /dev/null
+++ b/src/vidcodec.c
@@ -0,0 +1,81 @@
+/**
+ * @file vidcodec.c Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct list vidcodecl;
+
+
+/**
+ * Register a Video Codec
+ *
+ * @param vc Video Codec
+ */
+void vidcodec_register(struct vidcodec *vc)
+{
+ if (!vc)
+ return;
+
+ list_append(&vidcodecl, &vc->le, vc);
+
+ info("vidcodec: %s\n", vc->name);
+}
+
+
+/**
+ * Unregister a Video Codec
+ *
+ * @param vc Video Codec
+ */
+void vidcodec_unregister(struct vidcodec *vc)
+{
+ if (!vc)
+ return;
+
+ list_unlink(&vc->le);
+}
+
+
+/**
+ * Find a Video Codec by name
+ *
+ * @param name Name of the Video Codec to find
+ * @param variant Codec Variant
+ *
+ * @return Matching Video Codec if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find(const char *name, const char *variant)
+{
+ struct le *le;
+
+ for (le=vidcodecl.head; le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (variant && 0 != str_casecmp(variant, vc->variant))
+ continue;
+
+ return vc;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the list of Video Codecs
+ *
+ * @return List of Video Codecs
+ */
+struct list *vidcodec_list(void)
+{
+ return &vidcodecl;
+}
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..f7eb032
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,1073 @@
+/**
+ * @file src/video.c Video stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ *
+ * \ref GenericVideoStream
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE "video"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Magic number */
+#define MAGIC 0x00070d10
+#include "magic.h"
+
+
+enum {
+ SRATE = 90000,
+ MAX_MUTED_FRAMES = 3,
+};
+
+
+/**
+ * \page GenericVideoStream Generic Video Stream
+ *
+ * Implements a generic video stream. The application can allocate multiple
+ * instances of a video stream, mapping it to a particular SDP media line.
+ * The video object has a Video Display and Source, and a video encoder
+ * and decoder. A particular video object is mapped to a generic media
+ * stream object.
+ *
+ *<pre>
+ * recv send
+ * | /|\
+ * \|/ |
+ * .---------. .-------.
+ * | video |--->|encoder|
+ * | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .-------. .-------.
+ * |Video | |Video |
+ * |Display| |Source |
+ * '-------' '-------'
+ *</pre>
+ */
+
+/**
+ * Video stream - transmitter/encoder direction
+
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .--------. .- - - - -. .---------. .---------.
+ | ._O_. | | ! ! | | | |
+ | |___|-->| vidsrc |-->! vidconv !-->| vidfilt |-->| encoder |---> RTP
+ | | | ! ! | | | |
+ ' '--------' '- - - - -' '---------' '---------'
+ (optional)
+ \endverbatim
+ */
+struct vtx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current Video encoder */
+ struct videnc_state *enc; /**< Video encoder state */
+ struct vidsrc_prm vsrc_prm; /**< Video source parameters */
+ struct vidsz vsrc_size; /**< Video source size */
+ struct vidsrc_st *vsrc; /**< Video source */
+ struct lock *lock; /**< Lock for encoder */
+ struct vidframe *frame; /**< Source frame */
+ struct vidframe *mute_frame; /**< Frame with muted video */
+ struct mbuf *mb; /**< Packetization buffer */
+ struct list filtl; /**< Filters in encoding order */
+ char device[64];
+ int muted_frames; /**< # of muted frames sent */
+ uint32_t ts_tx; /**< Outgoing RTP timestamp */
+ bool picup; /**< Send picture update */
+ bool muted; /**< Muted flag */
+ int frames; /**< Number of frames sent */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/**
+ * Video stream - receiver/decoder direction
+
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .~~~~~~~~. .--------. .---------. .---------.
+ | _o_ | | | | | | |
+ | | |<--| vidisp |<--| vidfilt |<--| decoder |<--- RTP
+ | /'\ | | | | | | |
+ '~~~~~~~~' '--------' '---------' '---------'
+
+ \endverbatim
+
+ */
+struct vrx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current video decoder */
+ struct viddec_state *dec; /**< Video decoder state */
+ struct vidisp_prm vidisp_prm; /**< Video display parameters */
+ struct vidisp_st *vidisp; /**< Video display */
+ struct lock *lock; /**< Lock for decoder */
+ struct list filtl; /**< Filters in decoding order */
+ enum vidorient orient; /**< Display orientation */
+ char device[64];
+ bool fullscreen; /**< Fullscreen flag */
+ int pt_rx; /**< Incoming RTP payload type */
+ int frames; /**< Number of frames received */
+ int efps; /**< Estimated frame-rate */
+};
+
+
+/** Generic Video stream */
+struct video {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct config_video cfg;/**< Video configuration */
+ struct stream *strm; /**< Generic media stream */
+ struct vtx vtx; /**< Transmit/encoder direction */
+ struct vrx vrx; /**< Receive/decoder direction */
+ struct tmr tmr; /**< Timer for frame-rate estimation */
+ char *peer; /**< Peer URI */
+ bool nack_pli; /**< Send NACK/PLI to peer */
+};
+
+
+static void video_destructor(void *arg)
+{
+ struct video *v = arg;
+ struct vtx *vtx = &v->vtx;
+ struct vrx *vrx = &v->vrx;
+
+ /* transmit */
+ mem_deref(vtx->vsrc);
+ lock_write_get(vtx->lock);
+ mem_deref(vtx->frame);
+ mem_deref(vtx->mute_frame);
+ mem_deref(vtx->enc);
+ mem_deref(vtx->mb);
+ list_flush(&vtx->filtl);
+ lock_rel(vtx->lock);
+ mem_deref(vtx->lock);
+
+ /* receive */
+ lock_write_get(vrx->lock);
+ mem_deref(vrx->dec);
+ mem_deref(vrx->vidisp);
+ list_flush(&vrx->filtl);
+ lock_rel(vrx->lock);
+ mem_deref(vrx->lock);
+
+ tmr_cancel(&v->tmr);
+ mem_deref(v->strm);
+ mem_deref(v->peer);
+}
+
+
+static int get_fps(const struct video *v)
+{
+ const char *attr;
+
+ /* RFC4566 */
+ attr = sdp_media_rattr(stream_sdpmedia(v->strm), "framerate");
+ if (attr) {
+ /* NOTE: fractional values are ignored */
+ const double fps = atof(attr);
+ return (int)fps;
+ }
+ else
+ return v->cfg.fps;
+}
+
+
+static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len, void *arg)
+{
+ struct vtx *tx = arg;
+ int err = 0;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (hdr_len) err |= mbuf_write_mem(tx->mb, hdr, hdr_len);
+ if (pld_len) err |= mbuf_write_mem(tx->mb, pld, pld_len);
+
+ tx->mb->pos = STREAM_PRESZ;
+
+ if (!err) {
+ err = stream_send(tx->video->strm, marker, -1,
+ tx->ts_tx, tx->mb);
+ }
+
+ return err;
+}
+
+
+/**
+ * Encode video and send via RTP stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param vtx Video transmit object
+ * @param frame Video frame to send
+ */
+static void encode_rtp_send(struct vtx *vtx, struct vidframe *frame)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!vtx->enc)
+ return;
+
+ lock_write_get(vtx->lock);
+
+ /* Convert image */
+ if (frame->fmt != VID_FMT_YUV420P) {
+
+ vtx->vsrc_size = frame->size;
+
+ if (!vtx->frame) {
+
+ err = vidframe_alloc(&vtx->frame, VID_FMT_YUV420P,
+ &vtx->vsrc_size);
+ if (err)
+ goto unlock;
+ }
+
+ vidconv(vtx->frame, frame, 0);
+ frame = vtx->frame;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vtx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf && st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ unlock:
+ lock_rel(vtx->lock);
+
+ if (err)
+ return;
+
+ /* Encode the whole picture frame */
+ err = vtx->vc->ench(vtx->enc, vtx->picup, frame, packet_handler, vtx);
+ if (err) {
+ DEBUG_WARNING("encode: %m\n", err);
+ return;
+ }
+
+ vtx->ts_tx += (SRATE/vtx->vsrc_prm.fps);
+ vtx->picup = false;
+}
+
+
+/**
+ * Read frames from video source
+ *
+ * @param frame Video frame
+ * @param arg Handler argument
+ *
+ * @note This function has REAL-TIME properties
+ */
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ ++vtx->frames;
+
+ /* Is the video muted? If so insert video mute image */
+ if (vtx->muted)
+ frame = vtx->mute_frame;
+
+ if (vtx->muted && vtx->muted_frames >= MAX_MUTED_FRAMES)
+ return;
+
+ /* Encode and send */
+ encode_rtp_send(vtx, frame);
+ vtx->muted_frames++;
+}
+
+
+static void vidsrc_error_handler(int err, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ DEBUG_WARNING("Video-source error: %m\n", err);
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+}
+
+
+static int vtx_alloc(struct vtx *vtx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vtx->lock);
+ if (err)
+ return err;
+
+ vtx->mb = mbuf_alloc(STREAM_PRESZ + 512);
+ if (!vtx->mb)
+ return ENOMEM;
+
+ vtx->video = video;
+ vtx->ts_tx = 160;
+
+ str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device));
+
+ return err;
+}
+
+
+static int vrx_alloc(struct vrx *vrx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vrx->lock);
+ if (err)
+ return err;
+
+ vrx->video = video;
+ vrx->pt_rx = -1;
+ vrx->orient = VIDORIENT_PORTRAIT;
+
+ str_ncpy(vrx->device, video->cfg.disp_dev, sizeof(vrx->device));
+
+ return err;
+}
+
+
+/**
+ * Decode incoming RTP packets using the Video decoder
+ *
+ * NOTE: mb=NULL if no packet received
+ *
+ * @param vrx Video receive object
+ * @param hdr RTP Header
+ * @param mb Buffer with RTP payload
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int video_stream_decode(struct vrx *vrx, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct video *v = vrx->video;
+ struct vidframe frame;
+ struct le *le;
+ int err = 0;
+
+ if (!hdr || !mbuf_get_left(mb))
+ return 0;
+
+ lock_write_get(vrx->lock);
+
+ /* No decoder set */
+ if (!vrx->dec) {
+ DEBUG_WARNING("No video decoder!\n");
+ goto out;
+ }
+
+ frame.data[0] = NULL;
+ err = vrx->vc->dech(vrx->dec, &frame, hdr->m, hdr->seq, mb);
+ if (err) {
+
+ if (err != EPROTO) {
+ DEBUG_WARNING("%s decode error"
+ " (seq=%u, %u bytes): %m\n",
+ vrx->vc->name, hdr->seq,
+ mbuf_get_left(mb), err);
+ }
+
+ /* send RTCP FIR to peer */
+ stream_send_fir(v->strm, v->nack_pli);
+
+ /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */
+
+ goto out;
+ }
+
+ /* Got a full picture-frame? */
+ if (!vidframe_isvalid(&frame))
+ goto out;
+
+ /* Process video frame through all Video Filters */
+ for (le = vrx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf && st->vf->dech)
+ err |= st->vf->dech(st, &frame);
+ }
+
+ err = vidisp_display(vrx->vidisp, v->peer, &frame);
+
+ ++vrx->frames;
+
+out:
+ lock_rel(vrx->lock);
+
+ return err;
+}
+
+
+static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(v->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Video decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ v->vrx.pt_rx = pt_new;
+
+ return video_decoder_set(v, lc->data, lc->pt, lc->rparams);
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct video *v = arg;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Video payload-type changed? */
+ if (hdr->pt == v->vrx.pt_rx)
+ goto out;
+
+ err = pt_handler(v, v->vrx.pt_rx, hdr->pt);
+ if (err)
+ return;
+
+ out:
+ (void)video_stream_decode(&v->vrx, hdr, mb);
+}
+
+
+static void rtcp_handler(struct rtcp_msg *msg, void *arg)
+{
+ struct video *v = arg;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_FIR:
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_PSFB:
+ if (msg->hdr.count == RTCP_PSFB_PLI)
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_RTPFB:
+ if (msg->hdr.count == RTCP_RTPFB_GNACK)
+ v->vtx.picup = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int vtx_print_pipeline(struct re_printf *pf, const struct vtx *vtx)
+{
+ struct le *le;
+ struct vidsrc *vs;
+ int err;
+
+ if (!vtx)
+ return 0;
+
+ vs = vidsrc_get(vtx->vsrc);
+
+ err = re_hprintf(pf, "video tx pipeline: %10s",
+ vs ? vs->name : "src");
+
+ for (le = list_head(&vtx->filtl); le; le = le->next) {
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= re_hprintf(pf, " ---> %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ vtx->vc ? vtx->vc->name : "encoder");
+
+ return err;
+}
+
+
+static int vrx_print_pipeline(struct re_printf *pf, const struct vrx *vrx)
+{
+ struct le *le;
+ struct vidisp *vd;
+ int err;
+
+ if (!vrx)
+ return 0;
+
+ vd = vidisp_get(vrx->vidisp);
+
+ err = re_hprintf(pf, "video rx pipeline: %10s",
+ vd ? vd->name : "disp");
+
+ for (le = list_head(&vrx->filtl); le; le = le->next) {
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf->dech)
+ err |= re_hprintf(pf, " <--- %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ vrx->vc ? vrx->vc->name : "decoder");
+
+ return err;
+}
+
+
+int video_alloc(struct video **vp, const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *content, const struct list *vidcodecl)
+{
+ struct video *v;
+ struct le *le;
+ int err = 0;
+
+ if (!vp || !cfg)
+ return EINVAL;
+
+ v = mem_zalloc(sizeof(*v), video_destructor);
+ if (!v)
+ return ENOMEM;
+
+ MAGIC_INIT(v);
+
+ v->cfg = cfg->video;
+ tmr_init(&v->tmr);
+
+ err = stream_alloc(&v->strm, &cfg->avt, call, sdp_sess, "video", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, rtcp_handler, v);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max >= AUDIO_BANDWIDTH) {
+ stream_set_bw(v->strm, cfg->avt.rtp_bw.max - AUDIO_BANDWIDTH);
+ }
+
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "framerate", "%d", v->cfg.fps);
+
+ /* RFC 4585 */
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "rtcp-fb", "* nack pli");
+
+ /* RFC 4796 */
+ if (content) {
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "content", "%s", content);
+ }
+
+ if (err)
+ goto out;
+
+ err = vtx_alloc(&v->vtx, v);
+ err |= vrx_alloc(&v->vrx, v);
+ if (err)
+ goto out;
+
+ /* Video codecs */
+ for (le = list_head(vidcodecl); le; le = le->next) {
+ struct vidcodec *vc = le->data;
+ err |= sdp_format_add(NULL, stream_sdpmedia(v->strm), false,
+ vc->pt, vc->name, 90000, 1,
+ vc->fmtp_ench, vc->fmtp_cmph, vc, false,
+ "%s", vc->fmtp);
+ }
+
+ /* Video filters */
+ for (le = list_head(vidfilt_list()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ err |= vidfilt_enc_append(&v->vtx.filtl, &ctx, vf);
+ err |= vidfilt_dec_append(&v->vrx.filtl, &ctx, vf);
+ if (err) {
+ DEBUG_WARNING("video-filter '%s' failed (%m)\n",
+ vf->name, err);
+ break;
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(v);
+ else
+ *vp = v;
+
+ return err;
+}
+
+
+static void vidisp_resize_handler(const struct vidsz *sz, void *arg)
+{
+ struct vrx *vrx = arg;
+ (void)vrx;
+
+ info("video: display resized: %u x %u\n", sz->w, sz->h);
+
+ /* XXX: update wanted picturesize and send re-invite to peer */
+}
+
+
+/* Set the video display - can be called multiple times */
+static int set_vidisp(struct vrx *vrx)
+{
+ struct vidisp *vd;
+
+ vrx->vidisp = mem_deref(vrx->vidisp);
+ vrx->vidisp_prm.view = NULL;
+
+ vd = (struct vidisp *)vidisp_find(vrx->video->cfg.disp_mod);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(&vrx->vidisp, vd, &vrx->vidisp_prm, vrx->device,
+ vidisp_resize_handler, vrx);
+}
+
+
+/* Set the encoder format - can be called multiple times */
+static int set_encoder_format(struct vtx *vtx, const char *src,
+ const char *dev, struct vidsz *size)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(src);
+ int err;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx->vsrc_size = *size;
+ vtx->vsrc_prm.fps = get_fps(vtx->video);
+ vtx->vsrc_prm.orient = VIDORIENT_PORTRAIT;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ err = vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev, vidsrc_frame_handler,
+ vidsrc_error_handler, vtx);
+ if (err) {
+ info("video: no video source '%s': %m\n", src, err);
+ return err;
+ }
+
+ vtx->mute_frame = mem_deref(vtx->mute_frame);
+ err = vidframe_alloc(&vtx->mute_frame, VID_FMT_YUV420P, size);
+ if (err)
+ return err;
+
+ vidframe_fill(vtx->mute_frame, 0xff, 0xff, 0xff);
+
+ return err;
+}
+
+
+enum {TMR_INTERVAL = 5};
+static void tmr_handler(void *arg)
+{
+ struct video *v = arg;
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ /* Estimate framerates */
+ v->vtx.efps = v->vtx.frames / TMR_INTERVAL;
+ v->vrx.efps = v->vrx.frames / TMR_INTERVAL;
+
+ v->vtx.frames = 0;
+ v->vrx.frames = 0;
+}
+
+
+int video_start(struct video *v, const char *peer)
+{
+ struct vidsz size;
+ int err;
+
+ if (!v)
+ return EINVAL;
+
+ if (peer) {
+ mem_deref(v->peer);
+ err = str_dup(&v->peer, peer);
+ if (err)
+ return err;
+ }
+
+ stream_set_srate(v->strm, SRATE, SRATE);
+
+ err = set_vidisp(&v->vrx);
+ if (err) {
+ DEBUG_WARNING("could not set vidisp '%s': %m\n",
+ v->vrx.device, err);
+ }
+
+ size.w = v->cfg.width;
+ size.h = v->cfg.height;
+ err = set_encoder_format(&v->vtx, v->cfg.src_mod,
+ v->vtx.device, &size);
+ if (err) {
+ DEBUG_WARNING("could not set encoder format to"
+ " [%u x %u] %m\n",
+ size.w, size.h, err);
+ }
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ if (v->vtx.vc && v->vrx.vc) {
+ info("%H%H",
+ vtx_print_pipeline, &v->vtx,
+ vrx_print_pipeline, &v->vrx);
+ }
+
+ return 0;
+}
+
+
+void video_stop(struct video *v)
+{
+ if (!v)
+ return;
+
+ v->vtx.vsrc = mem_deref(v->vtx.vsrc);
+}
+
+
+/**
+ * Mute the video stream
+ *
+ * @param v Video stream
+ * @param muted True to mute, false to un-mute
+ */
+void video_mute(struct video *v, bool muted)
+{
+ struct vtx *vtx;
+
+ if (!v)
+ return;
+
+ vtx = &v->vtx;
+
+ vtx->muted = muted;
+ vtx->muted_frames = 0;
+ vtx->picup = true;
+
+ video_update_picture(v);
+}
+
+
+static int vidisp_update(struct vrx *vrx)
+{
+ struct vidisp *vd = vidisp_get(vrx->vidisp);
+ int err = 0;
+
+ if (vd->updateh) {
+ err = vd->updateh(vrx->vidisp, vrx->fullscreen,
+ vrx->orient, NULL);
+ }
+
+ return err;
+}
+
+
+/**
+ * Enable video display fullscreen
+ *
+ * @param v Video stream
+ * @param fs True for fullscreen, otherwise false
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_fullscreen(struct video *v, bool fs)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vrx.fullscreen = fs;
+
+ return vidisp_update(&v->vrx);
+}
+
+
+static void vidsrc_update(struct vtx *vtx, const char *dev)
+{
+ struct vidsrc *vs = vidsrc_get(vtx->vsrc);
+
+ if (vs && vs->updateh)
+ vs->updateh(vtx->vsrc, &vtx->vsrc_prm, dev);
+}
+
+
+/**
+ * Set the orientation of the Video source and display
+ *
+ * @param v Video stream
+ * @param orient Video orientation (enum vidorient)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_orient(struct video *v, int orient)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vtx.vsrc_prm.orient = v->vrx.orient = orient;
+ vidsrc_update(&v->vtx, NULL);
+ return vidisp_update(&v->vrx);
+}
+
+
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params)
+{
+ struct vtx *vtx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vtx = &v->vtx;
+
+ if (vc != vtx->vc) {
+
+ struct videnc_param prm;
+
+ prm.bitrate = v->cfg.bitrate;
+ prm.pktsize = 1024;
+ prm.fps = get_fps(v);
+ prm.max_fs = -1;
+
+ info("Set video encoder: %s %s (%u bit/s, %u fps)\n",
+ vc->name, vc->variant, prm.bitrate, prm.fps);
+
+ vtx->enc = mem_deref(vtx->enc);
+ err = vc->encupdh(&vtx->enc, vc, &prm, params);
+ if (err) {
+ DEBUG_WARNING("encoder alloc: %m\n", err);
+ return err;
+ }
+
+ vtx->vc = vc;
+ }
+
+ stream_update_encoder(v->strm, pt_tx);
+
+ return err;
+}
+
+
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp)
+{
+ struct vrx *vrx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vrx = &v->vrx;
+
+ vrx->pt_rx = pt_rx;
+
+ if (vc != vrx->vc) {
+
+ info("Set video decoder: %s %s\n", vc->name, vc->variant);
+
+ vrx->dec = mem_deref(vrx->dec);
+
+ err = vc->decupdh(&vrx->dec, vc, fmtp);
+ if (err) {
+ DEBUG_WARNING("decoder alloc: %m\n", err);
+ return err;
+ }
+
+ vrx->vc = vc;
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next video encoder in the local list of negotiated codecs
+ *
+ * @param video Video object
+ */
+void video_encoder_cycle(struct video *video)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!video)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(video_strm(video)));
+ if (!rc) {
+ info("cycle video: no remote codec found\n");
+ return;
+ }
+
+ (void)video_encoder_set(video, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *video_strm(const struct video *v)
+{
+ return v ? v->strm : NULL;
+}
+
+
+void video_update_picture(struct video *v)
+{
+ if (!v)
+ return;
+ v->vtx.picup = true;
+}
+
+
+/**
+ * Get the driver-specific view of the video stream
+ *
+ * @param v Video stream
+ *
+ * @return Opaque view
+ */
+void *video_view(const struct video *v)
+{
+ if (!v)
+ return NULL;
+
+ return v->vrx.vidisp_prm.view;
+}
+
+
+/**
+ * Set the current Video Source device name
+ *
+ * @param v Video stream
+ * @param dev Device name
+ */
+void video_vidsrc_set_device(struct video *v, const char *dev)
+{
+ if (!v)
+ return;
+
+ vidsrc_update(&v->vtx, dev);
+}
+
+
+static bool sdprattr_contains(struct stream *s, const char *name,
+ const char *str)
+{
+ const char *attr = sdp_media_rattr(stream_sdpmedia(s), name);
+ return attr ? (NULL != strstr(attr, str)) : false;
+}
+
+
+void video_sdp_attr_decode(struct video *v)
+{
+ if (!v)
+ return;
+
+ /* RFC 4585 */
+ v->nack_pli = sdprattr_contains(v->strm, "rtcp-fb", "nack");
+}
+
+
+int video_debug(struct re_printf *pf, const struct video *v)
+{
+ const struct vtx *vtx;
+ const struct vrx *vrx;
+ int err;
+
+ if (!v)
+ return 0;
+
+ vtx = &v->vtx;
+ vrx = &v->vrx;
+
+ err = re_hprintf(pf, "\n--- Video stream ---\n");
+ err |= re_hprintf(pf, " tx: %u x %u, fps=%d\n",
+ vtx->vsrc_size.w,
+ vtx->vsrc_size.h, vtx->vsrc_prm.fps);
+ err |= re_hprintf(pf, " rx: pt=%d\n", vrx->pt_rx);
+
+ if (!list_isempty(vidfilt_list())) {
+ err |= vtx_print_pipeline(pf, vtx);
+ err |= vrx_print_pipeline(pf, vrx);
+ }
+
+ err |= stream_debug(pf, v->strm);
+
+ return err;
+}
+
+
+int video_print(struct re_printf *pf, const struct video *v)
+{
+ if (!v)
+ return 0;
+
+ return re_hprintf(pf, " efps=%d/%d", v->vtx.efps, v->vrx.efps);
+}
+
+
+int video_set_source(struct video *v, const char *name, const char *dev)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name);
+ struct vtx *vtx;
+
+ if (!v)
+ return EINVAL;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx = &v->vtx;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ return vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev,
+ vidsrc_frame_handler, vidsrc_error_handler, vtx);
+}
+
+
+void video_set_devicename(struct video *v, const char *src, const char *disp)
+{
+ if (!v)
+ return;
+
+ str_ncpy(v->vtx.device, src, sizeof(v->vtx.device));
+ str_ncpy(v->vrx.device, disp, sizeof(v->vrx.device));
+}
diff --git a/src/vidfilt.c b/src/vidfilt.c
new file mode 100644
index 0000000..a8d8426
--- /dev/null
+++ b/src/vidfilt.c
@@ -0,0 +1,116 @@
+/**
+ * @file vidfilt.c Video Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static struct list vfl;
+
+
+/**
+ * Register a new Video Filter
+ *
+ * @param vf Video Filter to register
+ */
+void vidfilt_register(struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_append(&vfl, &vf->le, vf);
+
+ info("vidfilt: %s\n", vf->name);
+}
+
+
+/**
+ * Unregister a Video Filter
+ *
+ * @param vf Video Filter to unregister
+ */
+void vidfilt_unregister(struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_unlink(&vf->le);
+}
+
+
+/**
+ * Get the list of registered Video Filters
+ *
+ * @return List of Video Filters
+ */
+struct list *vidfilt_list(void)
+{
+ return &vfl;
+}
+
+
+static void vidfilt_enc_destructor(void *arg)
+{
+ struct vidfilt_enc_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_enc_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_enc_st *st = NULL;
+ int err;
+
+ if (vf->encupdh) {
+ err = vf->encupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_enc_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
+
+
+static void vidfilt_dec_destructor(void *arg)
+{
+ struct vidfilt_dec_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_dec_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_dec_st *st = NULL;
+ int err;
+
+ if (vf->decupdh) {
+ err = vf->decupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_dec_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
diff --git a/src/vidisp.c b/src/vidisp.c
new file mode 100644
index 0000000..94ceee5
--- /dev/null
+++ b/src/vidisp.c
@@ -0,0 +1,132 @@
+/**
+ * @file vidisp.c Video Display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Display state */
+struct vidisp_st {
+ struct vidisp *vd; /**< Video Display */
+};
+
+
+static struct list vidispl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct vidisp *vd = arg;
+
+ list_unlink(&vd->le);
+}
+
+
+/**
+ * Register a Video output display
+ *
+ * @param vp Pointer to allocated Video Display
+ * @param name Name of Video Display
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ * @param disph Display handler
+ * @param hideh Hide-window handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_register(struct vidisp **vp, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh)
+{
+ struct vidisp *vd;
+
+ if (!vp)
+ return EINVAL;
+
+ vd = mem_zalloc(sizeof(*vd), destructor);
+ if (!vd)
+ return ENOMEM;
+
+ list_append(&vidispl, &vd->le, vd);
+
+ vd->name = name;
+ vd->alloch = alloch;
+ vd->updateh = updateh;
+ vd->disph = disph;
+ vd->hideh = hideh;
+
+ info("vidisp: %s\n", name);
+
+ *vp = vd;
+ return 0;
+}
+
+
+const struct vidisp *vidisp_find(const char *name)
+{
+ struct le *le;
+
+ for (le = vidispl.head; le; le = le->next) {
+ struct vidisp *vd = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vd->name))
+ continue;
+
+ /* Found */
+ return vd;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a video display state
+ *
+ * @param stp Pointer to allocated display state
+ * @param name Name of video display
+ * @param prm Video display parameters (optional)
+ * @param dev Display device
+ * @param resizeh Window resize handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_alloc(struct vidisp_st **stp, const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp *vd = (struct vidisp *)vidisp_find(name);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(stp, vd, prm, dev, resizeh, arg);
+}
+
+
+/**
+ * Display a video frame
+ *
+ * @param st Video display state
+ * @param title Display title
+ * @param frame Video frame
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ if (!st || !frame)
+ return EINVAL;
+
+ return st->vd->disph(st, title, frame);
+}
+
+
+struct vidisp *vidisp_get(struct vidisp_st *st)
+{
+ return st ? st->vd : NULL;
+}
diff --git a/src/vidsrc.c b/src/vidsrc.c
new file mode 100644
index 0000000..e0bb297
--- /dev/null
+++ b/src/vidsrc.c
@@ -0,0 +1,134 @@
+/**
+ * @file vidsrc.c Video Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Source state */
+struct vidsrc_st {
+ struct vidsrc *vs; /**< Video Source */
+};
+
+
+static struct list vidsrcl = LIST_INIT;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc *vs = arg;
+
+ list_unlink(&vs->le);
+}
+
+
+/**
+ * Register a Video Source
+ *
+ * @param vsp Pointer to allocated Video Source
+ * @param name Name of Video Source
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_register(struct vidsrc **vsp, const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh)
+{
+ struct vidsrc *vs;
+
+ if (!vsp)
+ return EINVAL;
+
+ vs = mem_zalloc(sizeof(*vs), destructor);
+ if (!vs)
+ return ENOMEM;
+
+ list_append(&vidsrcl, &vs->le, vs);
+
+ vs->name = name;
+ vs->alloch = alloch;
+ vs->updateh = updateh;
+
+ info("vidsrc: %s\n", name);
+
+ *vsp = vs;
+
+ return 0;
+}
+
+
+/**
+ * Find a Video Source by name
+ *
+ * @param name Name of the Video Source to find
+ *
+ * @return Matching Video Source if found, otherwise NULL
+ */
+const struct vidsrc *vidsrc_find(const char *name)
+{
+ struct le *le;
+
+ for (le=vidsrcl.head; le; le=le->next) {
+
+ struct vidsrc *vs = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vs->name))
+ continue;
+
+ return vs;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a new video source state
+ *
+ * @param stp Pointer to allocated state
+ * @param name Name of the video source
+ * @param ctx Optional media context
+ * @param prm Video source parameters
+ * @param size Wanted video size of the source
+ * @param fmt Format parameter
+ * @param dev Video device
+ * @param frameh Video frame handler
+ * @param errorh Error handler (optional)
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_alloc(struct vidsrc_st **stp, const char *name,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name);
+ if (!vs)
+ return ENOENT;
+
+ return vs->alloch(stp, vs, ctx, prm, size, fmt, dev,
+ frameh, errorh, arg);
+}
+
+
+/**
+ * Get the list of Video Sources
+ *
+ * @return List of Video Sources
+ */
+struct list *vidsrc_list(void)
+{
+ return &vidsrcl;
+}
+
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st)
+{
+ return st ? st->vs : NULL;
+}