diff options
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;"..\..\..\re\include"..\..\..\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(¶ms, fmtp); + + fmt_param_apply(¶ms, decode_param, st); + + return 0; +} + + +static int alloc(struct aucodec_st **stp, struct aucodec *ac, + struct aucodec_prm *encp, struct aucodec_prm *decp, + const char *fmtp) +{ + struct aucodec_st *st; + const uint32_t srate = aucodec_srate(ac); + const uint8_t ch = aucodec_ch(ac); + int err = 0; + + (void)decp; + + st = mem_zalloc(sizeof(*st), celt_destructor); + if (!st) + return ENOMEM; + + st->ac = mem_ref(ac); + + st->bitrate = DEFAULT_BITRATE; + st->low_overhead = celt_low_overhead; + + if (encp && encp->ptime) { + st->frame_size = srate * ch * encp->ptime / 1000; + DEBUG_NOTICE("calc ptime=%u ---> frame_size=%u\n", + encp->ptime, st->frame_size); + } + else { + st->frame_size = DEFAULT_FRAME_SIZE; + } + + if (str_isset(fmtp)) + decode_params(st, fmtp); + + /* Common mode */ + st->mode = celt_mode_create(srate, st->frame_size, NULL); + if (!st->mode) { + DEBUG_WARNING("alloc: could not create CELT mode\n"); + err = EPROTO; + goto out; + } + +#ifdef CELT_GET_FRAME_SIZE + celt_mode_info(st->mode, CELT_GET_FRAME_SIZE, &st->frame_size); +#endif + + st->fsize = 2 * st->frame_size * ch; + st->bytes_per_packet = (st->bitrate * st->frame_size / srate + 4)/8; + + DEBUG_NOTICE("alloc: frame_size=%u bitrate=%ubit/s fsize=%u" + " bytes_per_packet=%u\n", + st->frame_size, st->bitrate, st->fsize, + st->bytes_per_packet); + + /* Encoder */ +#ifdef CELT_OLD_API + st->enc = celt_encoder_create(st->mode, ch, NULL); +#else + st->enc = celt_encoder_create(srate, ch, NULL); +#endif + if (!st->enc) { + DEBUG_WARNING("alloc: could not create CELT encoder\n"); + err = EPROTO; + goto out; + } + + /* Decoder */ +#ifdef CELT_OLD_API + st->dec = celt_decoder_create(st->mode, ch, NULL); +#else + st->dec = celt_decoder_create(srate, ch, NULL); +#endif + if (!st->dec) { + DEBUG_WARNING("alloc: could not create CELT decoder\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int encode_frame(struct aucodec_st *st, uint16_t *size, uint8_t *buf, + struct mbuf *src) +{ + int len; + + /* NOTE: PCM audio in signed 16-bit format (native endian) */ + len = celt_encode(st->enc, (short *)mbuf_buf(src), st->frame_size, + buf, st->bytes_per_packet); + if (len < 0) { + DEBUG_WARNING("celt_encode: returned %d\n", len); + return EINVAL; + } + + DEBUG_INFO("encode: %u -> %d\n", mbuf_get_left(src), len); + + *size = len; + + mbuf_advance(src, st->fsize); + + return 0; +} + + +static int encode(struct aucodec_st *st, struct mbuf *dst, struct mbuf *src) +{ + struct { + uint8_t buf[1024]; + uint16_t len; + } framev[MAX_FRAMES]; + uint32_t i; + size_t n; + int err = 0; + + n = src->end / st->fsize; + if (n > MAX_FRAMES) { + n = MAX_FRAMES; + DEBUG_WARNING("number of frames truncated to %u\n", n); + } + + DEBUG_INFO("enc: %u bytes into %u frames\n", src->end, n); + + if (n ==0) { + DEBUG_WARNING("enc: short frame (%u < %u)\n", + src->end, st->fsize); + return EINVAL; + } + + /* Encode all frames into temp buffer */ + for (i=0; i<n && !err; i++) { + framev[i].len = sizeof(framev[i].buf); + err = encode_frame(st, &framev[i].len, framev[i].buf, src); + } + + if (!st->low_overhead) { + /* Encode all length headers */ + for (i=0; i<n && !err; i++) { + uint16_t len = framev[i].len; + + while (len >= 0xff) { + err = mbuf_write_u8(dst, 0xff); + len -= 0xff; + } + err = mbuf_write_u8(dst, len); + } + } + + /* Encode all frame buffers */ + for (i=0; i<n && !err; i++) { + err = mbuf_write_mem(dst, framev[i].buf, framev[i].len); + } + + return err; +} + + +static int decode_frame(struct aucodec_st *st, struct mbuf *dst, + struct mbuf *src, uint16_t src_len) +{ + int ret, err; + + if (mbuf_get_left(src) < src_len) { + DEBUG_WARNING("dec: corrupt frame %u < %u\n", + mbuf_get_left(src), src_len); + return EPROTO; + } + + /* Make sure there is enough space in the buffer */ + if (mbuf_get_space(dst) < st->fsize) { + err = mbuf_resize(dst, dst->size + st->fsize); + if (err) + return err; + } + + ret = celt_decode(st->dec, mbuf_buf(src), src_len, + (short *)mbuf_buf(dst), st->frame_size); + if (CELT_OK != ret) { + DEBUG_WARNING("celt_decode: ret=%d\n", ret); + } + + DEBUG_INFO("decode: %u -> %u\n", src_len, st->fsize); + + if (src) + mbuf_advance(src, src_len); + + dst->end += st->fsize; + + return 0; +} + + +/* src=NULL means lost packet */ +static int decode(struct aucodec_st *st, struct mbuf *dst, struct mbuf *src) +{ + uint16_t lengthv[MAX_FRAMES]; + uint16_t total_length = 0; + uint32_t i, n; + int err = 0; + + DEBUG_INFO("decode %u bytes\n", mbuf_get_left(src)); + + if (st->low_overhead) { + /* No length bytes */ + for (i=0; i<st->bpfn && !err; i++) { + err = decode_frame(st, dst, src, st->bpfv[i]); + } + } + else { + bool done = false; + + /* Read the length bytes */ + for (i=0; i<ARRAY_SIZE(lengthv) && !done; i++) { + uint8_t byte; + + if (mbuf_get_left(src) < 1) + return EPROTO; + + /* Decode length */ + lengthv[i] = 0; + do { + byte = mbuf_read_u8(src); + lengthv[i] += byte; + } + while (byte == 0xff); + + total_length += lengthv[i]; + + if (total_length >= mbuf_get_left(src)) + done = true; + } + n = i; + DEBUG_INFO("decoded %d frames\n", n); + + for (i=0; i<n && !err; i++) { + err = decode_frame(st, dst, src, lengthv[i]); + } + } + + return err; +} + + +static int module_init(void) +{ + int err; + + err = aucodec_register(&celtv[0], 0, "CELT", 48000, 1, NULL, + alloc, encode, decode, NULL); + err |= aucodec_register(&celtv[1], 0, "CELT", 32000, 1, NULL, + alloc, encode, decode, NULL); + + return err; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(celtv); i++) + celtv[i] = mem_deref(celtv[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(celt) = { + "celt", + "codec", + module_init, + module_close +}; diff --git a/modules/celt/module.mk b/modules/celt/module.mk new file mode 100644 index 0000000..47e6ea0 --- /dev/null +++ b/modules/celt/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := celt +$(MOD)_SRCS += celt.c +$(MOD)_LFLAGS += `pkg-config --libs celt` + +include mk/mod.mk diff --git a/modules/cons/cons.c b/modules/cons/cons.c new file mode 100644 index 0000000..8cf5b10 --- /dev/null +++ b/modules/cons/cons.c @@ -0,0 +1,185 @@ +/** + * @file cons.c Socket-based command-line console + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +enum {CONS_PORT = 5555}; + +struct ui_st { + struct ui *ui; /* base class */ + struct udp_sock *us; + struct tcp_sock *ts; + struct tcp_conn *tc; + ui_input_h *h; + void *arg; +}; + + +static struct ui *cons; +static struct ui_st *cons_cur = NULL; /* allow only one instance */ + + +static int print_handler(const char *p, size_t size, void *arg) +{ + return mbuf_write_mem(arg, (uint8_t *)p, size); +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct ui_st *st = arg; + struct mbuf *mbr = mbuf_alloc(64); + struct re_printf pf; + + pf.vph = print_handler; + pf.arg = mbr; + + while (mbuf_get_left(mb)) + st->h(mbuf_read_u8(mb), &pf, st->arg); + + mbr->pos = 0; + (void)udp_send(st->us, src, mbr); + + mem_deref(mbr); +} + + +static void cons_destructor(void *arg) +{ + struct ui_st *st = arg; + + mem_deref(st->us); + mem_deref(st->tc); + mem_deref(st->ts); + + mem_deref(st->ui); + + cons_cur = NULL; +} + + +static int tcp_write_handler(const char *p, size_t size, void *arg) +{ + struct mbuf mb; + + mb.buf = (uint8_t *)p; + mb.pos = 0; + mb.end = mb.size = size; + + return tcp_send(arg, &mb); +} + + +static void tcp_recv_handler(struct mbuf *mb, void *arg) +{ + struct ui_st *st = arg; + struct re_printf pf; + + pf.vph = tcp_write_handler; + pf.arg = st->tc; + + while (mbuf_get_left(mb) > 0) { + + const char key = mbuf_read_u8(mb); + + st->h(key, &pf, st->arg); + } +} + + +static void tcp_close_handler(int err, void *arg) +{ + struct ui_st *st = arg; + + (void)err; + + st->tc = mem_deref(st->tc); +} + + +static void tcp_conn_handler(const struct sa *peer, void *arg) +{ + struct ui_st *st = arg; + + (void)peer; + + /* only one connection allowed */ + st->tc = mem_deref(st->tc); + (void)tcp_accept(&st->tc, st->ts, NULL, tcp_recv_handler, + tcp_close_handler, st); +} + + +static int cons_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *h, void *arg) +{ + struct sa local; + struct ui_st *st; + int err; + + if (!stp) + return EINVAL; + + if (cons_cur) { + *stp = mem_ref(cons_cur); + return 0; + } + + st = mem_zalloc(sizeof(*st), cons_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(cons); + st->h = h; + st->arg = arg; + + err = sa_set_str(&local, "0.0.0.0", prm->port ? prm->port : CONS_PORT); + if (err) + goto out; + err = udp_listen(&st->us, &local, udp_recv, st); + if (err) { + warning("cons: failed to listen on UDP port %d (%m)\n", + sa_port(&local), err); + goto out; + } + + err = tcp_listen(&st->ts, &local, tcp_conn_handler, st); + if (err) { + warning("cons: failed to listen on TCP port %d (%m)\n", + sa_port(&local), err); + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = cons_cur = st; + + return err; +} + + +static int cons_init(void) +{ + return ui_register(&cons, "cons", cons_alloc, NULL); +} + + +static int cons_close(void) +{ + cons = mem_deref(cons); + return 0; +} + + +const struct mod_export DECL_EXPORTS(cons) = { + "cons", + "ui", + cons_init, + cons_close +}; diff --git a/modules/cons/module.mk b/modules/cons/module.mk new file mode 100644 index 0000000..1857dee --- /dev/null +++ b/modules/cons/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := cons +$(MOD)_SRCS += cons.c + +include mk/mod.mk diff --git a/modules/contact/contact.c b/modules/contact/contact.c new file mode 100644 index 0000000..b896e24 --- /dev/null +++ b/modules/contact/contact.c @@ -0,0 +1,214 @@ +/** + * @file modules/contact/contact.c Contacts module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +/* + * Contact module + * + * - read contact entries from ~/.baresip/contacts + * - populate local database of contacts + */ + + +static const char *chat_peer; /**< Selected chat peer */ +static char cmd_desc[128] = "Send MESSAGE to peer"; + + +static int confline_handler(const struct pl *addr) +{ + return contact_add(NULL, addr); +} + + +static int cmd_contact(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct contact *cnt = NULL; + struct pl dname, user, pl; + struct le *le; + int err = 0; + + pl_set_str(&pl, carg->prm); + + dname.l = user.l = pl.l; + + err |= re_hprintf(pf, "\n"); + + for (le = list_head(contact_list()); le; le = le->next) { + + struct contact *c = le->data; + + dname.p = contact_addr(c)->dname.p; + user.p = contact_addr(c)->uri.user.p; + + /* if displayname is set, try to match the displayname + * otherwise we try to match the username only + */ + if (dname.p) { + + if (0 == pl_casecmp(&dname, &pl)) { + err |= re_hprintf(pf, "%s\n", contact_str(c)); + cnt = c; + } + } + else if (user.p) { + + if (0 == pl_casecmp(&user, &pl)) { + err |= re_hprintf(pf, "%s\n", contact_str(c)); + cnt = c; + } + } + } + + if (!cnt) + err |= re_hprintf(pf, "(no matches)\n"); + + if (carg->complete && cnt) { + + switch (carg->key) { + + case '/': + err = ua_connect(uag_current(), NULL, NULL, + contact_str(cnt), NULL, VIDMODE_ON); + if (err) { + warning("contact: ua_connect failed: %m\n", + err); + } + break; + + case '=': + chat_peer = contact_str(cnt); + (void)re_hprintf(pf, "Selected chat peer: %s\n", + chat_peer); + re_snprintf(cmd_desc, sizeof(cmd_desc), + "Send MESSAGE to %s", chat_peer); + break; + + default: + break; + } + } + + return err; +} + + +static int cmd_message(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err; + + (void)pf; + + err = message_send(uag_current(), chat_peer, carg->prm); + if (err) { + (void)re_hprintf(pf, "chat: ua_im_send() failed (%m)\n", err); + } + + return err; +} + + +static const struct cmd cmdv[] = { + {'/', CMD_IPRM, "Dial from contacts", cmd_contact }, + {'=', CMD_IPRM, "Select chat peer", cmd_contact }, + {'C', 0, "List contacts", contacts_print }, + {'-', CMD_PRM, cmd_desc, cmd_message }, +}; + + +static int write_template(const char *file) +{ + const char *user, *domain; + FILE *f = NULL; + + info("contact: creating contacts template %s\n", file); + + f = fopen(file, "w"); + if (!f) + return errno; + + user = sys_username(); + if (!user) + user = "user"; + domain = net_domain(); + if (!domain) + domain = "domain"; + + (void)re_fprintf(f, + "#\n" + "# SIP contacts\n" + "#\n" + "# Displayname <sip:user@domain>;addr-params\n" + "#\n" + "# addr-params:\n" + "# ;presence={none,p2p}\n" + "#\n" + "\n" + "\"Echo Server\" <sip:echo@creytiv.com>\n" + "\"%s\" <sip:%s@%s>;presence=p2p\n", + user, user, domain); + + if (f) + (void)fclose(f); + + return 0; +} + + +static int module_init(void) +{ + char path[256] = "", file[256] = ""; + int err; + + err = conf_path_get(path, sizeof(path)); + if (err) + return err; + + if (re_snprintf(file, sizeof(file), "%s/contacts", path) < 0) + return ENOMEM; + + if (!conf_fileexist(file)) { + + (void)fs_mkdir(path, 0700); + + err = write_template(file); + if (err) + return err; + } + + err = conf_parse(file, confline_handler); + if (err) + return err; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + info("Populated %u contacts\n", list_count(contact_list())); + + return err; +} + + +static int module_close(void) +{ + cmd_unregister(cmdv); + list_flush(contact_list()); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(contact) = { + "contact", + "application", + module_init, + module_close +}; diff --git a/modules/contact/module.mk b/modules/contact/module.mk new file mode 100644 index 0000000..e9361c8 --- /dev/null +++ b/modules/contact/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := contact +$(MOD)_SRCS += contact.c + +include mk/mod.mk diff --git a/modules/coreaudio/coreaudio.c b/modules/coreaudio/coreaudio.c new file mode 100644 index 0000000..bb6ea64 --- /dev/null +++ b/modules/coreaudio/coreaudio.c @@ -0,0 +1,128 @@ +/** + * @file coreaudio.c Apple Coreaudio sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioToolbox.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +int audio_fmt(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kAudioFormatLinearPCM; + case AUFMT_PCMA: return kAudioFormatALaw; + case AUFMT_PCMU: return kAudioFormatULaw; + default: + warning("coreaudio: unknown format %d\n", fmt); + return -1; + } +} + + +int bytesps(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return 2; + case AUFMT_PCMA: return 1; + case AUFMT_PCMU: return 1; + default: return 0; + } +} + + +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0 +static void interruptionListener(void *data, UInt32 inInterruptionState) +{ + (void)data; + + /* TODO: implement this properly */ + + if (inInterruptionState == kAudioSessionBeginInterruption) { + debug("coreaudio: player interrupt: Begin\n"); + } + else if (inInterruptionState == kAudioSessionEndInterruption) { + debug("coreaudio: player interrupt: End\n"); + } +} + + +int audio_session_enable(void) +{ + OSStatus res; + UInt32 category; + + res = AudioSessionInitialize(NULL, NULL, interruptionListener, 0); + if (res && res != 1768843636) + return ENODEV; + + category = kAudioSessionCategory_PlayAndRecord; + res = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(category), &category); + if (res) { + warning("coreaudio: Audio Category: %d\n", res); + return ENODEV; + } + + res = AudioSessionSetActive(true); + if (res) { + warning("coreaudio: AudioSessionSetActive: %d\n", res); + return ENODEV; + } + + return 0; +} + + +void audio_session_disable(void) +{ + AudioSessionSetActive(false); +} +#else +int audio_session_enable(void) +{ + return 0; +} + + +void audio_session_disable(void) +{ +} +#endif + + +static int module_init(void) +{ + int err; + + err = auplay_register(&auplay, "coreaudio", coreaudio_player_alloc); + err |= ausrc_register(&ausrc, "coreaudio", coreaudio_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(coreaudio) = { + "coreaudio", + "audio", + module_init, + module_close, +}; diff --git a/modules/coreaudio/coreaudio.h b/modules/coreaudio/coreaudio.h new file mode 100644 index 0000000..530e45e --- /dev/null +++ b/modules/coreaudio/coreaudio.h @@ -0,0 +1,20 @@ +/** + * @file coreaudio.h Apple Coreaudio sound driver -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +int audio_session_enable(void); +void audio_session_disable(void); + +int audio_fmt(enum aufmt fmt); +int bytesps(enum aufmt fmt); + +int coreaudio_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int coreaudio_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); diff --git a/modules/coreaudio/module.mk b/modules/coreaudio/module.mk new file mode 100644 index 0000000..35d51cf --- /dev/null +++ b/modules/coreaudio/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := coreaudio +$(MOD)_SRCS += coreaudio.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += -framework CoreAudio -framework AudioToolbox + +include mk/mod.mk diff --git a/modules/coreaudio/player.c b/modules/coreaudio/player.c new file mode 100644 index 0000000..68aca4e --- /dev/null +++ b/modules/coreaudio/player.c @@ -0,0 +1,167 @@ +/** + * @file coreaudio/player.c Apple Coreaudio sound driver - player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioQueue.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +/* This value can be tuned */ +#if TARGET_OS_IPHONE +#define BUFC 20 +#else +#define BUFC 6 +#endif + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + AudioQueueRef queue; + AudioQueueBufferRef buf[BUFC]; + pthread_mutex_t mutex; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + uint32_t i; + + pthread_mutex_lock(&st->mutex); + st->wh = NULL; + pthread_mutex_unlock(&st->mutex); + + audio_session_disable(); + + if (st->queue) { + AudioQueuePause(st->queue); + AudioQueueStop(st->queue, true); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) + if (st->buf[i]) + AudioQueueFreeBuffer(st->queue, st->buf[i]); + + AudioQueueDispose(st->queue, true); + } + + mem_deref(st->ap); + + pthread_mutex_destroy(&st->mutex); +} + + +static void play_handler(void *userData, AudioQueueRef outQ, + AudioQueueBufferRef outQB) +{ + struct auplay_st *st = userData; + auplay_write_h *wh; + void *arg; + + pthread_mutex_lock(&st->mutex); + wh = st->wh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!wh) + return; + + if (!wh(outQB->mAudioData, outQB->mAudioDataByteSize, arg)) { + /* Set the buffer to silence */ + memset(outQB->mAudioData, 0, outQB->mAudioDataByteSize); + } + + AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL); +} + + +int coreaudio_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + AudioStreamBasicDescription fmt; + struct auplay_st *st; + uint32_t sampc, bytc, i; + OSStatus status; + int err; + + (void)device; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audio_session_enable(); + if (err) + goto out; + + fmt.mSampleRate = (Float64)prm->srate; + fmt.mFormatID = audio_fmt(prm->fmt); + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * bytesps(prm->fmt); + fmt.mBytesPerPacket = prm->ch * bytesps(prm->fmt); + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 8*bytesps(prm->fmt); + + status = AudioQueueNewOutput(&fmt, play_handler, st, NULL, + kCFRunLoopCommonModes, 0, &st->queue); + if (status) { + warning("coreaudio: AudioQueueNewOutput error: %i\n", status); + err = ENODEV; + goto out; + } + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + bytc = sampc * bytesps(prm->fmt); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) { + + status = AudioQueueAllocateBuffer(st->queue, bytc, + &st->buf[i]); + if (status) { + err = ENOMEM; + goto out; + } + + st->buf[i]->mAudioDataByteSize = bytc; + + memset(st->buf[i]->mAudioData, 0, + st->buf[i]->mAudioDataByteSize); + + (void)AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL); + } + + status = AudioQueueStart(st->queue, NULL); + if (status) { + warning("coreaudio: AudioQueueStart error %i\n", status); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/coreaudio/recorder.c b/modules/coreaudio/recorder.c new file mode 100644 index 0000000..b1f91fc --- /dev/null +++ b/modules/coreaudio/recorder.c @@ -0,0 +1,199 @@ +/** + * @file coreaudio/recorder.c Apple Coreaudio sound driver - recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioToolbox/AudioQueue.h> +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "coreaudio.h" + + +#define BUFC 3 + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + AudioQueueRef queue; + AudioQueueBufferRef buf[BUFC]; + pthread_mutex_t mutex; + struct mbuf *mb; + ausrc_read_h *rh; + void *arg; + unsigned int ptime; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + uint32_t i; + + pthread_mutex_lock(&st->mutex); + st->rh = NULL; + pthread_mutex_unlock(&st->mutex); + + audio_session_disable(); + + if (st->queue) { + AudioQueuePause(st->queue); + AudioQueueStop(st->queue, true); + + for (i=0; i<ARRAY_SIZE(st->buf); i++) + if (st->buf[i]) + AudioQueueFreeBuffer(st->queue, st->buf[i]); + + AudioQueueDispose(st->queue, true); + } + + mem_deref(st->mb); + mem_deref(st->as); + + pthread_mutex_destroy(&st->mutex); +} + + +static void record_handler(void *userData, AudioQueueRef inQ, + AudioQueueBufferRef inQB, + const AudioTimeStamp *inStartTime, + UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) +{ + struct ausrc_st *st = userData; + struct mbuf *mb = st->mb; + unsigned int ptime; + ausrc_read_h *rh; + size_t sz, sp; + void *arg; + (void)inStartTime; + (void)inNumPackets; + (void)inPacketDesc; + + pthread_mutex_lock(&st->mutex); + ptime = st->ptime; + rh = st->rh; + arg = st->arg; + pthread_mutex_unlock(&st->mutex); + + if (!rh) + return; + + sz = inQB->mAudioDataByteSize; + sp = mbuf_get_space(mb); + + if (sz >= sp) { + mbuf_write_mem(mb, inQB->mAudioData, sp); + rh(mb->buf, (uint32_t)mb->size, arg); + mb->pos = 0; + mbuf_write_mem(mb, (uint8_t *)inQB->mAudioData + sp, sz - sp); + } + else { + mbuf_write_mem(mb, inQB->mAudioData, sz); + } + + AudioQueueEnqueueBuffer(inQ, inQB, 0, NULL); + + /* Force a sleep here, coreaudio's timing is too fast */ +#if !TARGET_OS_IPHONE +#define ENCODE_TIME 1000 + usleep((ptime * 1000) - ENCODE_TIME); +#endif +} + + +int coreaudio_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + AudioStreamBasicDescription fmt; + struct ausrc_st *st; + uint32_t sampc, bytc, i; + OSStatus status; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->ptime = prm->ptime; + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + bytc = sampc * bytesps(prm->fmt); + + st->mb = mbuf_alloc(bytc); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + err = audio_session_enable(); + if (err) + goto out; + + fmt.mSampleRate = (Float64)prm->srate; + fmt.mFormatID = audio_fmt(prm->fmt); + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * bytesps(prm->fmt); + fmt.mBytesPerPacket = prm->ch * bytesps(prm->fmt); + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 8*bytesps(prm->fmt); + + status = AudioQueueNewInput(&fmt, record_handler, st, NULL, + kCFRunLoopCommonModes, 0, &st->queue); + if (status) { + warning("coreaudio: AudioQueueNewInput error: %i\n", status); + err = ENODEV; + goto out; + } + + for (i=0; i<ARRAY_SIZE(st->buf); i++) { + + status = AudioQueueAllocateBuffer(st->queue, bytc, + &st->buf[i]); + if (status) { + err = ENOMEM; + goto out; + } + + AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL); + } + + status = AudioQueueStart(st->queue, NULL); + if (status) { + warning("coreaudio: AudioQueueStart error %i\n", status); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/directfb/directfb.c b/modules/directfb/directfb.c new file mode 100644 index 0000000..ad98e2b --- /dev/null +++ b/modules/directfb/directfb.c @@ -0,0 +1,191 @@ +/** + * @file directfb.c DirectFB video display module + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de> + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <directfb.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + IDirectFBWindow *window; /**< DirectFB Window */ + IDirectFBSurface *surface; /**< Surface for pixels */ + IDirectFBDisplayLayer *layer; /**< Display layer */ +}; + + +static IDirectFB *dfb; +static struct vidisp *vid; + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->surface) + st->surface->Release(st->surface); + if (st->window) + st->window->Release(st->window); + if (st->layer) + st->layer->Release(st->layer); + + mem_deref(st->vd); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + + /* Not used by DirectFB */ + (void) prm; + (void) dev; + (void) resizeh; + (void) arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &st->layer); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + void *pixels; + int pitch, i; + unsigned h; + uint8_t *p; + (void) title; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("directfb: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + if (st->surface) { + st->surface->Release(st->surface); + st->surface = NULL; + } + if (st->window) { + st->window->Release(st->window); + st->window = NULL; + } + } + + if (!st->window) { + DFBWindowDescription desc; + + desc.flags = DWDESC_WIDTH|DWDESC_HEIGHT|DWDESC_PIXELFORMAT; + desc.width = frame->size.w; + desc.height = frame->size.h; + desc.pixelformat = DSPF_I420; + + st->layer->CreateWindow(st->layer, &desc, &st->window); + + st->size = frame->size; + st->window->SetOpacity(st->window, 0xff); + st->window->GetSurface(st->window, &st->surface); + } + + st->surface->Lock(st->surface, DSLF_WRITE, &pixels, &pitch); + + p = pixels; + for (i=0; i<3; i++) { + + const uint8_t *s = frame->data[i]; + const unsigned stp = frame->linesize[0] / frame->linesize[i]; + const unsigned sz = frame->size.w / stp; + + for (h = 0; h < frame->size.h; h += stp) { + + memcpy(p, s, sz); + + s += frame->linesize[i]; + p += (pitch / stp); + } + } + + st->surface->Unlock(st->surface); + + /* Update the screen! */ + st->surface->Flip(st->surface, 0, 0); + + return 0; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st || !st->window) + return; + + st->window->SetOpacity(st->window, 0x00); +} + + +static int module_init(void) +{ + int err = 0; + DFBResult ret; + + ret = DirectFBInit(NULL, NULL); + if (ret) { + DirectFBError("DirectFBInit() failed", ret); + return (int) ret; + } + + ret = DirectFBCreate(&dfb); + if (ret) { + DirectFBError("DirectFBCreate() failed", ret); + return (int) ret; + } + + err = vidisp_register(&vid, "directfb", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + if (dfb) { + dfb->Release(dfb); + dfb = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(directfb) = { + "directfb", + "vidisp", + module_init, + module_close +}; diff --git a/modules/directfb/module.mk b/modules/directfb/module.mk new file mode 100644 index 0000000..bde6556 --- /dev/null +++ b/modules/directfb/module.mk @@ -0,0 +1,13 @@ +# +# module.mk - DirectFB video display module +# +# Copyright (C) 2010 Creytiv.com +# Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de>. +# + +MOD := directfb +$(MOD)_SRCS += directfb.c +$(MOD)_LFLAGS += `pkg-config --libs directfb ` +CFLAGS += `pkg-config --cflags directfb ` + +include mk/mod.mk diff --git a/modules/dshow/dshow.cpp b/modules/dshow/dshow.cpp new file mode 100644 index 0000000..e9e433f --- /dev/null +++ b/modules/dshow/dshow.cpp @@ -0,0 +1,511 @@ +/** + * @file dshow.cpp Windows DirectShow video-source + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2010 Dusan Stevanovic + */ + +#include <stdio.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <comutil.h> +#include <commctrl.h> +#include <dshow.h> +#include <qedit.h> + + +#define DEBUG_MODULE "dshow" +#define DEBUG_LEVEL 6 +#include <re_dbg.h> + + +const CLSID CLSID_SampleGrabber = { 0xc1f400a0, 0x3f08, 0x11d3, + { 0x9f, 0x0b, 0x00, 0x60, 0x08, 0x03, 0x9e, 0x37 } +}; + + +class Grabber; + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + ICaptureGraphBuilder2 *capture; + IBaseFilter *grabber_filter; + IBaseFilter *dev_filter; + ISampleGrabber *grabber; + IMoniker *dev_moniker; + IGraphBuilder *graph; + IMediaControl *mc; + + Grabber *grab; + + struct vidsz size; + vidsrc_frame_h *frameh; + void *arg; +}; + + +class Grabber : public ISampleGrabberCB { +public: + Grabber(struct vidsrc_st *st) : src(st) { } + + STDMETHOD(QueryInterface)(REFIID InterfaceIdentifier, + VOID** ppvObject) throw() + { + if (InterfaceIdentifier == __uuidof(ISampleGrabberCB)) { + *ppvObject = (ISampleGrabberCB**) this; + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHOD_(ULONG, AddRef)() throw() + { + return 2; + } + + STDMETHOD_(ULONG, Release)() throw() + { + return 1; + } + + STDMETHOD(BufferCB) (double sample_time, BYTE *buf, long buf_len) + { + struct vidframe vidframe; + + /* XXX: should be VID_FMT_BGR24 */ + vidframe_init_buf(&vidframe, VID_FMT_RGB32, &src->size, buf); + + if (src->frameh) + src->frameh(&vidframe, src->arg); + + return S_OK; + } + + STDMETHOD(SampleCB) (double sample_time, IMediaSample *samp) + { + return S_OK; + } + +private: + struct vidsrc_st *src; +}; + + +static struct vidsrc *vsrc; + + +static int get_device(struct vidsrc_st *st, const char *name) +{ + ICreateDevEnum *dev_enum; + IEnumMoniker *enum_mon; + IMoniker *mon; + ULONG fetched; + HRESULT res; + int id = 0; + bool found = false; + + if (!st) + return EINVAL; + + res = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, (void**)&dev_enum); + if (res != NOERROR) + return ENOENT; + + res = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + &enum_mon, 0); + if (res != NOERROR) + return ENOENT; + + enum_mon->Reset(); + while (enum_mon->Next(1, &mon, &fetched) == S_OK && !found) { + + IPropertyBag *bag; + VARIANT var; + char dev_name[256]; + int len = 0; + + res = mon->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&bag); + if (!SUCCEEDED(res)) + continue; + + var.vt = VT_BSTR; + res = bag->Read(L"FriendlyName", &var, NULL); + if (NOERROR != res) + continue; + + len = WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, + dev_name, sizeof(dev_name), + NULL, NULL); + + if (len > 0) { + found = !str_isset(name) || + !str_casecmp(dev_name, name); + + if (found) { + re_printf("dshow: got device '%s' id=%d\n", + name, id); + st->dev_moniker = mon; + } + } + + SysFreeString(var.bstrVal); + bag->Release(); + if (!found) { + mon->Release(); + ++id; + } + } + + return found ? 0 : ENOENT; +} + + +static int add_sample_grabber(struct vidsrc_st *st) +{ + AM_MEDIA_TYPE mt; + HRESULT hr; + + hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, (void**) &st->grabber_filter); + if (FAILED(hr)) + return ENOMEM; + + hr = st->graph->AddFilter(st->grabber_filter, L"Sample Grabber"); + if (FAILED(hr)) + return ENOMEM; + + hr = st->grabber_filter->QueryInterface(IID_ISampleGrabber, + (void**)&st->grabber); + if (FAILED(hr)) + return ENODEV; + + hr = st->grabber->SetCallback(st->grab, 1); + if (FAILED(hr)) + return ENOSYS; + + memset(&mt, 0, sizeof(mt)); + mt.majortype = MEDIATYPE_Video; + mt.subtype = MEDIASUBTYPE_RGB24; /* XXX: try YUV420P */ + hr = st->grabber->SetMediaType(&mt); + if (FAILED(hr)) + return ENODEV; + + st->grabber->SetOneShot(FALSE); + st->grabber->SetBufferSamples(FALSE); + + return 0; +} + + +static AM_MEDIA_TYPE *free_mt(AM_MEDIA_TYPE *mt) +{ + if (!mt) + return NULL; + + if (mt->cbFormat) { + CoTaskMemFree((PVOID)mt->pbFormat); + } + if (mt->pUnk != NULL) { + mt->pUnk->Release(); + mt->pUnk = NULL; + } + + CoTaskMemFree((PVOID)mt); + + return NULL; +} + + +static int config_pin(struct vidsrc_st *st, IPin *pin) +{ + AM_MEDIA_TYPE *mt; + AM_MEDIA_TYPE *best_mt = NULL; + IEnumMediaTypes *media_enum = NULL; + IAMStreamConfig *stream_conf = NULL; + VIDEOINFOHEADER *vih; + HRESULT hr; + int h = st->size.h; + int w = st->size.w; + int rh, rw; + int wh, rwrh; + int best_match = 0; + int err = 0; + + if (!pin || !st) + return EINVAL; + + hr = pin->EnumMediaTypes(&media_enum); + if (FAILED(hr)) + return ENODATA; + + while ((hr = media_enum->Next(1, &mt, NULL)) == S_OK) { + if (mt->formattype != FORMAT_VideoInfo) + continue; + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + wh = w * h; + rwrh = rw * rh; + if (wh == rwrh) { + best_mt = free_mt(best_mt); + break; + } + else { + int diff = abs(rwrh - wh); + + if (best_match != 0 && diff >= best_match) + mt = free_mt(mt); + else { + best_match = diff; + free_mt(best_mt); + best_mt = mt; + } + } + } + if (hr != S_OK) + mt = free_mt(mt); + + if (mt == NULL && best_mt == NULL) { + err = ENODATA; + goto out; + } + if (mt == NULL) + mt = best_mt; + + hr = pin->QueryInterface(IID_IAMStreamConfig, + (void **) &stream_conf); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *) mt->pbFormat; + hr = stream_conf->SetFormat(mt); + mt = free_mt(mt); + if (FAILED(hr)) { + err = ERANGE; + goto out; + } + + hr = stream_conf->GetFormat(&mt); + if (FAILED(hr)) { + err = EINVAL; + goto out; + } + if (mt->formattype != FORMAT_VideoInfo) { + err = EINVAL; + goto out; + } + + vih = (VIDEOINFOHEADER *)mt->pbFormat; + rw = vih->bmiHeader.biWidth; + rh = vih->bmiHeader.biHeight; + + if (w != rw || h != rh) { + DEBUG_WARNING("config_pin: picture size missmatch: " + "wanted %d x %d, got %d x %d\n", + w, h, rw, rh); + } + st->size.w = rw; + st->size.h = rh; + + out: + if (media_enum) + media_enum->Release(); + if (stream_conf) + stream_conf->Release(); + free_mt(mt); + + return err; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = (struct vidsrc_st *)arg; + + if (st->mc) { + st->mc->Stop(); + st->mc->Release(); + } + + if (st->grabber) { + st->grabber->SetCallback(NULL, 1); + st->grabber->Release(); + } + if (st->grabber_filter) + st->grabber_filter->Release(); + if (st->dev_moniker) + st->dev_moniker->Release(); + if (st->dev_filter) + st->dev_filter->Release(); + if (st->capture) { + st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + NULL, NULL, NULL); + st->capture->Release(); + } + if (st->graph) + st->graph->Release(); + + delete st->grab; + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, + const char *fmt, const char *dev, + vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + IEnumPins *pin_enum = NULL; + IPin *pin = NULL; + HRESULT hr; + int err; + (void)ctx; + (void)errorh; + + if (!stp || !vs || !prm || !size) + return EINVAL; + + st = (struct vidsrc_st *) mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + err = get_device(st, dev); + if (err) + goto out; + + st->vs = (struct vidsrc *)mem_ref(vs); + + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + st->grab = new Grabber(st); + if (!st->grab) { + err = ENOMEM; + goto out; + } + + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, + IID_IGraphBuilder, (void **) &st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_IGraphBuilder failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL, + CLSCTX_INPROC, IID_ICaptureGraphBuilder2, + (void **) &st->capture); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IID_ICaptureGraphBuilder2: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->capture->SetFiltergraph(st->graph); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: SetFiltergraph failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_moniker->BindToObject(NULL, NULL, IID_IBaseFilter, + (void **) &st->dev_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: bind to base filter failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->graph->AddFilter(st->dev_filter, L"Video Capture"); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: VideoCapture failed: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->dev_filter->EnumPins(&pin_enum); + if (pin_enum) { + pin_enum->Reset(); + hr = pin_enum->Next(1, &pin, NULL); + } + + add_sample_grabber(st); + err = config_pin(st, pin); + pin->Release(); + if (err) + goto out; + + hr = st->capture->RenderStream(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, + st->dev_filter, + NULL, st->grabber_filter); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: RenderStream failed\n"); + err = ENODEV; + goto out; + } + + hr = st->graph->QueryInterface(IID_IMediaControl, + (void **) &st->mc); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: IMediaControl failed\n"); + err = ENODEV; + goto out; + } + + hr = st->mc->Run(); + if (FAILED(hr)) { + DEBUG_WARNING("alloc: Run failed\n"); + err = ENODEV; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + if (CoInitialize(NULL) != S_OK) + return ENODATA; + + return vidsrc_register(&vsrc, "dshow", alloc, NULL); +} + + +static int module_close(void) +{ + vsrc = (struct vidsrc *) mem_deref(vsrc); + CoUninitialize(); + + return 0; +} + + +extern "C" const struct mod_export DECL_EXPORTS(dshow) = { + "dshow", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/dshow/module.mk b/modules/dshow/module.mk new file mode 100644 index 0000000..f70623d --- /dev/null +++ b/modules/dshow/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := dshow +$(MOD)_SRCS += dshow.cpp +$(MOD)_LFLAGS += -lstrmiids -lole32 -loleaut32 -lstdc++ + +include mk/mod.mk diff --git a/modules/dtls_srtp/dtls.c b/modules/dtls_srtp/dtls.c new file mode 100644 index 0000000..f787cd2 --- /dev/null +++ b/modules/dtls_srtp/dtls.c @@ -0,0 +1,234 @@ +/** + * @file dtls.c DTLS functions + * + * Copyright (C) 2010 Creytiv.com + */ +#define OPENSSL_NO_KRB5 1 +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "dtls_srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* note: shadow struct in libre's tls module */ +struct tls { + SSL_CTX *ctx; + char *pass; /* password for private key */ + /* ... */ + EVP_PKEY *key; + X509 *x; +}; + + +static void destructor(void *data) +{ + struct tls *tls = data; + + if (tls->ctx) + SSL_CTX_free(tls->ctx); + + if (tls->x) + X509_free(tls->x); + if (tls->key) + EVP_PKEY_free(tls->key); + + mem_deref(tls->pass); +} + + +static int cert_generate(X509 *x, EVP_PKEY *privkey, const char *aor, + int expire_days) +{ + X509_EXTENSION *ext; + X509_NAME *subj; + int ret; + int err = ENOMEM; + + subj = X509_NAME_new(); + if (!subj) + goto out; + + X509_set_version(x, 2); + + ASN1_INTEGER_set(X509_get_serialNumber(x), rand_u32()); + + ret = X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, + (unsigned char *)aor, + (int)strlen(aor), -1, 0); + if (!ret) + goto out; + + if (!X509_set_issuer_name(x, subj) || + !X509_set_subject_name(x, subj)) + goto out; + + X509_gmtime_adj(X509_get_notBefore(x), 0); + X509_gmtime_adj(X509_get_notAfter(x), 60*60*24*expire_days); + + if (!X509_set_pubkey(x, privkey)) + goto out; + + ext = X509V3_EXT_conf_nid(NULL, NULL, + NID_basic_constraints, "CA:FALSE"); + if (1 != X509_add_ext(x, ext, -1)) + goto out; + X509_EXTENSION_free(ext); + + err = 0; + + out: + if (subj) + X509_NAME_free(subj); + + return err; +} + + +static int tls_gen_selfsigned_cert(struct tls *tls, const char *aor) +{ + RSA *rsa; + int err = ENOMEM; + + rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL); + if (!rsa) + goto out; + + tls->key = EVP_PKEY_new(); + if (!tls->key) + goto out; + if (!EVP_PKEY_set1_RSA(tls->key, rsa)) + goto out; + + tls->x = X509_new(); + if (!tls->x) + goto out; + + if (cert_generate(tls->x, tls->key, aor, 365)) + goto out; + + /* Sign the certificate */ + if (!X509_sign(tls->x, tls->key, EVP_sha1())) + goto out; + + err = 0; + + out: + if (rsa) + RSA_free(rsa); + + return err; +} + + +int dtls_alloc_selfsigned(struct tls **tlsp, const char *aor, + const char *srtp_profiles) +{ + struct tls *tls; + int r, err; + + if (!tlsp || !aor) + return EINVAL; + + tls = mem_zalloc(sizeof(*tls), destructor); + if (!tls) + return ENOMEM; + + SSL_library_init(); + + tls->ctx = SSL_CTX_new(DTLSv1_method()); + if (!tls->ctx) { + err = ENOMEM; + goto out; + } + +#if (OPENSSL_VERSION_NUMBER < 0x00905100L) + SSL_CTX_set_verify_depth(tls->ctx, 1); +#endif + + SSL_CTX_set_read_ahead(tls->ctx, 1); + + /* Generate self-signed certificate */ + err = tls_gen_selfsigned_cert(tls, aor); + if (err) { + DEBUG_WARNING("failed to generate certificate (%s): %m\n", + aor, err); + goto out; + } + + r = SSL_CTX_use_certificate(tls->ctx, tls->x); + if (r != 1) { + err = EINVAL; + goto out; + } + + r = SSL_CTX_use_PrivateKey(tls->ctx, tls->key); + if (r != 1) { + err = EINVAL; + goto out; + } + + if (0 != SSL_CTX_set_tlsext_use_srtp(tls->ctx, srtp_profiles)) { + DEBUG_WARNING("could not enable SRTP for profiles '%s'\n", + srtp_profiles); + err = ENOSYS; + goto out; + } + + err = 0; + out: + if (err) + mem_deref(tls); + else + *tlsp = tls; + + return err; +} + + +int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls) +{ + uint8_t md[64]; + unsigned int i, len; + int err = 0; + + if (!pf || !tls) + return EINVAL; + + len = sizeof(md); + if (1 != X509_digest(tls->x, EVP_sha1(), md, &len)) + return ENOENT; + + for (i=0; i<len; i++) { + err |= re_hprintf(pf, "%s%02x", i==0?"":":", md[i]); + } + + return err; +} + + +int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls) +{ + uint8_t md[64]; + unsigned int i, len; + int err = 0; + + if (!pf || !tls) + return EINVAL; + + len = sizeof(md); + if (1 != X509_digest(tls->x, EVP_sha256(), md, &len)) + return ENOENT; + + for (i=0; i<len; i++) { + err |= re_hprintf(pf, "%s%02x", i==0?"":":", md[i]); + } + + return err; +} diff --git a/modules/dtls_srtp/dtls_srtp.c b/modules/dtls_srtp/dtls_srtp.c new file mode 100644 index 0000000..b25c143 --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.c @@ -0,0 +1,403 @@ +/** + * @file dtls_srtp.c DTLS-SRTP media encryption + * + * Copyright (C) 2010 Creytiv.com + */ + +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include <string.h> +#include "dtls_srtp.h" + + +/* + * STACK Diagram: + * + * application + * | + * | + * [DTLS] [SRTP] + * \ / + * \ / + * \ / + * \/ + * ( TURN/ICE ) + * | + * | + * socket + * + */ + +struct menc_sess { + struct sdp_session *sdp; + bool offerer; + menc_error_h *errorh; + void *arg; +}; + +/* media */ +struct dtls_srtp { + struct sock sockv[2]; + const struct menc_sess *sess; + struct sdp_media *sdpm; + struct tmr tmr; + bool started; + bool active; + bool mux; +}; + +static struct tls *tls; +static const char* srtp_profiles = + "SRTP_AES128_CM_SHA1_80:" + "SRTP_AES128_CM_SHA1_32"; + + +static void sess_destructor(void *arg) +{ + struct menc_sess *sess = arg; + + mem_deref(sess->sdp); +} + + +static void destructor(void *arg) +{ + struct dtls_srtp *st = arg; + size_t i; + + tmr_cancel(&st->tmr); + + for (i=0; i<2; i++) { + struct sock *s = &st->sockv[i]; + + mem_deref(s->uh_srtp); + mem_deref(s->dtls); + mem_deref(s->app_sock); /* must be freed last */ + mem_deref(s->tx); + mem_deref(s->rx); + } + + mem_deref(st->sdpm); +} + + +static bool verify_fingerprint(const struct sdp_session *sess, + const struct sdp_media *media, + struct dtls_flow *tc) +{ + struct pl hash; + char hashstr[32]; + uint8_t md_sdp[64]; + size_t sz_sdp = sizeof(md_sdp); + struct tls_fingerprint tls_fp; + + if (sdp_fingerprint_decode(sdp_rattr(sess, media, "fingerprint"), + &hash, md_sdp, &sz_sdp)) + return false; + + pl_strcpy(&hash, hashstr, sizeof(hashstr)); + + if (dtls_get_remote_fingerprint(tc, hashstr, &tls_fp)) { + warning("dtls_srtp: could not get DTLS fingerprint\n"); + return false; + } + + if (sz_sdp != tls_fp.len || 0 != memcmp(md_sdp, tls_fp.md, sz_sdp)) { + warning("dtls_srtp: %s fingerprint mismatch\n", hashstr); + info("DTLS: %w\n", tls_fp.md, (size_t)tls_fp.len); + info("SDP: %w\n", md_sdp, sz_sdp); + return false; + } + + info("dtls_srtp: verified %s fingerprint OK\n", hashstr); + + return true; +} + + +static void dtls_established_handler(int err, struct dtls_flow *flow, + const char *profile, + const struct key *client_key, + const struct key *server_key, + void *arg) +{ + struct sock *sock = arg; + const struct dtls_srtp *ds = sock->ds; + + if (!verify_fingerprint(ds->sess->sdp, ds->sdpm, flow)) { + warning("dtls_srtp: could not verify remote fingerprint\n"); + if (ds->sess->errorh) + ds->sess->errorh(EPIPE, ds->sess->arg); + return; + } + + sock->negotiated = true; + + info("dtls_srtp: ---> DTLS-SRTP complete (%s/%s) Profile=%s\n", + sdp_media_name(ds->sdpm), + sock->is_rtp ? "RTP" : "RTCP", profile); + + err |= srtp_stream_add(&sock->tx, profile, + ds->active ? client_key : server_key, + true); + + err |= srtp_stream_add(&sock->rx, profile, + ds->active ? server_key : client_key, + false); + + err |= srtp_install(sock); + if (err) { + warning("dtls_srtp: srtp_install: %m\n", err); + } +} + + +static int session_alloc(struct menc_sess **sessp, + struct sdp_session *sdp, bool offerer, + menc_error_h *errorh, void *arg) +{ + struct menc_sess *sess; + int err; + + if (!sessp || !sdp) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), sess_destructor); + if (!sess) + return ENOMEM; + + sess->sdp = mem_ref(sdp); + sess->offerer = offerer; + sess->errorh = errorh; + sess->arg = arg; + + /* RFC 4145 */ + err = sdp_session_set_lattr(sdp, true, "setup", + offerer ? "actpass" : "active"); + if (err) + goto out; + + /* RFC 4572 */ + err = sdp_session_set_lattr(sdp, true, "fingerprint", "SHA-1 %H", + dtls_print_sha1_fingerprint, tls); + if (err) + goto out; + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_start_sock(struct sock *sock, struct sdp_media *sdpm) +{ + struct sa raddr; + int err = 0; + + if (!sock->app_sock || sock->negotiated || sock->dtls) + return 0; + + if (sock->is_rtp) + raddr = *sdp_media_raddr(sdpm); + else + sdp_media_raddr_rtcp(sdpm, &raddr); + + if (sa_isset(&raddr, SA_ALL)) { + + err = dtls_flow_alloc(&sock->dtls, tls, sock->app_sock, + dtls_established_handler, sock); + if (err) + return err; + + err = dtls_flow_start(sock->dtls, &raddr, sock->ds->active); + } + + return err; +} + + +static int media_start(struct dtls_srtp *st, struct sdp_media *sdpm) +{ + int err = 0; + + if (st->started) + return 0; + + debug("dtls_srtp: media_start: '%s' mux=%d, active=%d\n", + sdp_media_name(sdpm), st->mux, st->active); + + if (!sdp_media_has_media(sdpm)) + return 0; + + err = media_start_sock(&st->sockv[0], sdpm); + + if (!st->mux) + err |= media_start_sock(&st->sockv[1], sdpm); + + if (err) + return err; + + st->started = true; + + return 0; +} + + +static void timeout(void *arg) +{ + struct dtls_srtp *st = arg; + + media_start(st, st->sdpm); +} + + +static int media_alloc(struct menc_media **mp, struct menc_sess *sess, + struct rtp_sock *rtp, int proto, + void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct dtls_srtp *st; + const char *setup, *fingerprint; + int err = 0; + unsigned i; + (void)rtp; + + if (!mp || !sess || proto != IPPROTO_UDP) + return EINVAL; + + st = (struct dtls_srtp *)*mp; + if (st) + goto setup; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->sess = sess; + st->sdpm = mem_ref(sdpm); + st->sockv[0].app_sock = mem_ref(rtpsock); + st->sockv[1].app_sock = mem_ref(rtcpsock); + + for (i=0; i<2; i++) + st->sockv[i].ds = st; + + st->sockv[0].is_rtp = true; + st->sockv[1].is_rtp = false; + + if (err) { + mem_deref(st); + return err; + } + else + *mp = (struct menc_media *)st; + + setup: + st->mux = (rtpsock == rtcpsock); + + setup = sdp_rattr(st->sess->sdp, st->sdpm, "setup"); + if (setup) { + st->active = !(0 == str_casecmp(setup, "active")); + + /* note: we need to wait for ICE to settle ... */ + tmr_start(&st->tmr, 100, timeout, st); + } + + /* SDP offer/answer on fingerprint attribute */ + fingerprint = sdp_rattr(st->sess->sdp, st->sdpm, "fingerprint"); + if (fingerprint) { + + struct pl hash; + + err = sdp_fingerprint_decode(fingerprint, &hash, NULL, NULL); + if (err) + return err; + + if (0 == pl_strcasecmp(&hash, "SHA-1")) { + err = sdp_media_set_lattr(st->sdpm, true, + "fingerprint", "SHA-1 %H", + dtls_print_sha1_fingerprint, + tls); + } + else if (0 == pl_strcasecmp(&hash, "SHA-256")) { + err = sdp_media_set_lattr(st->sdpm, true, + "fingerprint", "SHA-256 %H", + dtls_print_sha256_fingerprint, + tls); + } + else { + info("dtls_srtp: unsupported fingerprint hash `%r'\n", + &hash); + return EPROTO; + } + } + + return err; +} + + +static struct menc dtls_srtp = { + LE_INIT, "dtls_srtp", "UDP/TLS/RTP/SAVP", session_alloc, media_alloc +}; + +static struct menc dtls_srtpf = { + LE_INIT, "dtls_srtpf", "UDP/TLS/RTP/SAVPF", session_alloc, media_alloc +}; + +static struct menc dtls_srtp2 = { + /* note: temp for Webrtc interop */ + LE_INIT, "srtp-mandf", "RTP/SAVPF", session_alloc, media_alloc +}; + + +static int module_init(void) +{ + err_status_t ret; + int err; + + crypto_kernel_shutdown(); + ret = srtp_init(); + if (err_status_ok != ret) { + warning("dtls_srtp: srtp_init() failed: ret=%d\n", ret); + return ENOSYS; + } + + err = dtls_alloc_selfsigned(&tls, "dtls@baresip", srtp_profiles); + if (err) + return err; + + menc_register(&dtls_srtpf); + menc_register(&dtls_srtp); + menc_register(&dtls_srtp2); + + debug("DTLS-SRTP ready with profiles %s\n", srtp_profiles); + + return 0; +} + + +static int module_close(void) +{ + menc_unregister(&dtls_srtp); + menc_unregister(&dtls_srtpf); + menc_unregister(&dtls_srtp2); + tls = mem_deref(tls); + crypto_kernel_shutdown(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(dtls_srtp) = { + "dtls_srtp", + "menc", + module_init, + module_close +}; diff --git a/modules/dtls_srtp/dtls_srtp.h b/modules/dtls_srtp/dtls_srtp.h new file mode 100644 index 0000000..6f85bf3 --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.h @@ -0,0 +1,59 @@ +/** + * @file dtls_srtp.h DTLS-SRTP Internal api + * + * Copyright (C) 2010 Creytiv.com + */ + + +enum { + LAYER_SRTP = 20, + LAYER_DTLS = 20, /* must be above zero */ +}; + +struct sock { + const struct dtls_srtp *ds; + struct dtls_flow *dtls; + struct srtp_stream *tx; + struct srtp_stream *rx; + struct udp_helper *uh_srtp; + void *app_sock; + bool negotiated; + bool is_rtp; +}; + +struct key { + uint8_t key[256]; + size_t key_len; + uint8_t salt[256]; + size_t salt_len; +}; + + +/* dtls.c */ +int dtls_alloc_selfsigned(struct tls **tlsp, const char *aor, + const char *srtp_profile); +int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls); +int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls); + + +/* srtp.c */ +int srtp_stream_add(struct srtp_stream **sp, const char *profile, + const struct key *key, bool tx); +int srtp_install(struct sock *sock); + + +/* tls_udp.c */ +struct dtls_flow; + +typedef void (dtls_estab_h)(int err, struct dtls_flow *tc, + const char *profile, + const struct key *client_key, + const struct key *server_key, + void *arg); + +int dtls_flow_alloc(struct dtls_flow **flowp, struct tls *tls, + struct udp_sock *us, dtls_estab_h *estabh, void *arg); +int dtls_flow_start(struct dtls_flow *flow, const struct sa *peer, + bool active); +int dtls_get_remote_fingerprint(const struct dtls_flow *flow, const char *type, + struct tls_fingerprint *fp); diff --git a/modules/dtls_srtp/module.mk b/modules/dtls_srtp/module.mk new file mode 100644 index 0000000..87dc952 --- /dev/null +++ b/modules/dtls_srtp/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := dtls_srtp +$(MOD)_SRCS += dtls_srtp.c dtls.c srtp.c tls_udp.c +$(MOD)_LFLAGS += -lsrtp + +include mk/mod.mk diff --git a/modules/dtls_srtp/srtp.c b/modules/dtls_srtp/srtp.c new file mode 100644 index 0000000..7f33ccc --- /dev/null +++ b/modules/dtls_srtp/srtp.c @@ -0,0 +1,232 @@ +/** + * @file dtls_srtp/srtp.c Secure RTP + * + * Copyright (C) 2010 Creytiv.com + */ + +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "dtls_srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct srtp_stream { + srtp_policy_t policy; + srtp_t srtp; + uint8_t key[SRTP_MAX_KEY_LEN]; +}; + + +/* + * See RFC 5764 figure 3: + * + * +----------------+ + * | 127 < B < 192 -+--> forward to RTP + * | | + * packet --> | 19 < B < 64 -+--> forward to DTLS + * | | + * | B < 2 -+--> forward to STUN + * +----------------+ + * + */ +static inline bool is_rtp_or_rtcp(const struct mbuf *mb) +{ + uint8_t b; + + if (mbuf_get_left(mb) < 1) + return false; + + b = mbuf_buf(mb)[0]; + + return 127 < b && b < 192; +} + + +static inline bool is_rtcp_packet(const struct mbuf *mb) +{ + uint8_t pt; + + if (mbuf_get_left(mb) < 2) + return false; + + pt = mbuf_buf(mb)[1] & 0x7f; + + return 64 <= pt && pt <= 95; +} + + +static int errstatus_print(struct re_printf *pf, err_status_t e) +{ + const char *s; + + switch (e) { + + case err_status_ok: s = "ok"; break; + case err_status_fail: s = "fail"; break; + case err_status_auth_fail: s = "auth_fail"; break; + case err_status_cipher_fail: s = "cipher_fail"; break; + case err_status_replay_fail: s = "replay_fail"; break; + + default: + return re_hprintf(pf, "err=%d", e); + } + + return re_hprintf(pf, "%s", s); +} + + +static void destructor(void *arg) +{ + struct srtp_stream *s = arg; + + if (s->srtp) + srtp_dealloc(s->srtp); +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct sock *sock = arg; + err_status_t e; + int len; + (void)dst; + + if (!is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) { + *err = mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN); + if (*err) + return true; + } + + if (is_rtcp_packet(mb)) { + e = srtp_protect_rtcp(sock->tx->srtp, mbuf_buf(mb), &len); + } + else { + e = srtp_protect(sock->tx->srtp, mbuf_buf(mb), &len); + } + + if (err_status_ok != e) { + DEBUG_WARNING("send: failed to protect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + *err = EPROTO; + return false; + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct sock *sock = arg; + err_status_t e; + int len; + (void)src; + + if (!is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (is_rtcp_packet(mb)) { + e = srtp_unprotect_rtcp(sock->rx->srtp, mbuf_buf(mb), &len); + } + else { + e = srtp_unprotect(sock->rx->srtp, mbuf_buf(mb), &len); + } + + if (e != err_status_ok) { + DEBUG_WARNING("recv: failed to unprotect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + return true; /* error - drop packet */ + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +int srtp_stream_add(struct srtp_stream **sp, const char *profile, + const struct key *key, bool tx) +{ + struct srtp_stream *s; + err_status_t e; + int err = 0; + + if (!sp || !key || key->key_len > SRTP_MAX_KEY_LEN) + return EINVAL; + + s = mem_zalloc(sizeof(*s), destructor); + if (!s) + return ENOMEM; + + memcpy(s->key, key->key, key->key_len); + append_salt_to_key(s->key, (unsigned int)key->key_len, + (unsigned char *)key->salt, + (unsigned int)key->salt_len); + + /* note: policy and key must be on the heap */ + + if (0 == str_casecmp(profile, "SRTP_AES128_CM_SHA1_80")) { + crypto_policy_set_aes_cm_128_hmac_sha1_80(&s->policy.rtp); + crypto_policy_set_aes_cm_128_hmac_sha1_80(&s->policy.rtcp); + } + else if (0 == str_casecmp(profile, "SRTP_AES128_CM_SHA1_32")) { + crypto_policy_set_aes_cm_128_hmac_sha1_32(&s->policy.rtp); + crypto_policy_set_aes_cm_128_hmac_sha1_32(&s->policy.rtcp); + } + else { + DEBUG_WARNING("unsupported profile: %s\n", profile); + err = ENOSYS; + goto out; + } + + s->policy.ssrc.type = tx ? ssrc_any_outbound : ssrc_any_inbound; + s->policy.key = s->key; + s->policy.next = NULL; + + e = srtp_create(&s->srtp, &s->policy); + if (err_status_ok != e) { + s->srtp = NULL; + DEBUG_WARNING("srtp_create() failed. e=%d\n", e); + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(s); + else + *sp = s; + + return err; +} + + +int srtp_install(struct sock *sock) +{ + return udp_register_helper(&sock->uh_srtp, sock->app_sock, + LAYER_SRTP, + send_handler, + recv_handler, + sock); +} diff --git a/modules/dtls_srtp/tls_udp.c b/modules/dtls_srtp/tls_udp.c new file mode 100644 index 0000000..ada5310 --- /dev/null +++ b/modules/dtls_srtp/tls_udp.c @@ -0,0 +1,391 @@ +/** + * @file dtls_srtp/tls_udp.c DTLS socket for DTLS-SRTP + * + * Copyright (C) 2010 Creytiv.com + */ + +#define OPENSSL_NO_KRB5 1 +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <re.h> +#include "dtls_srtp.h" + + +#define DEBUG_MODULE "tls_udp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* note: shadow struct in dtls.c */ +struct tls { + SSL_CTX *ctx; +}; + +struct dtls_flow { + struct udp_helper *uh; + struct udp_sock *us; + struct tls *tls; + struct tmr tmr; + struct sa peer; + SSL *ssl; + BIO *sbio_out; + BIO *sbio_in; + bool up; + dtls_estab_h *estabh; + void *arg; +}; + + +static void check_timer(struct dtls_flow *flow); + + +static int bio_create(BIO *b) +{ + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + + return 1; +} + + +static int bio_destroy(BIO *b) +{ + if (!b) + return 0; + + b->ptr = NULL; + b->init = 0; + b->flags = 0; + + return 1; +} + + +static int bio_write(BIO *b, const char *buf, int len) +{ + struct dtls_flow *tc = b->ptr; + struct mbuf *mb; + enum {SPACE = 4}; /* sizeof TURN channel header */ + int err; + + mb = mbuf_alloc(SPACE + len); + if (!mb) + return -1; + + (void)mbuf_fill(mb, 0x00, SPACE); + (void)mbuf_write_mem(mb, (void *)buf, len); + + mb->pos = SPACE; + + err = udp_send_helper(tc->us, &tc->peer, mb, tc->uh); + if (err) { + DEBUG_WARNING("udp_send_helper: %m\n", err); + } + + mem_deref(mb); + + return err ? -1 : len; +} + + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + (void)b; + (void)num; + (void)ptr; + + if (cmd == BIO_CTRL_FLUSH) { + /* The OpenSSL library needs this */ + return 1; + } + + return 0; +} + + +static struct bio_method_st bio_udp_send = { + BIO_TYPE_SOURCE_SINK, + "udp_send", + bio_write, + 0, + 0, + 0, + bio_ctrl, + bio_create, + bio_destroy, + 0 +}; + + +static int verify_callback(int ok, X509_STORE_CTX *ctx) +{ + (void)ok; + (void)ctx; + return 1; /* We trust the certificate from peer */ +} + + +#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined(DTLS_CTRL_GET_TIMEOUT) +static void timeout(void *arg) +{ + struct dtls_flow *tc = arg; + + DTLSv1_handle_timeout(tc->ssl); + + check_timer(tc); +} +#endif + + +static void check_timer(struct dtls_flow *tc) +{ +#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined (DTLS_CTRL_GET_TIMEOUT) + struct timeval tv = {0, 0}; + long x; + + x = DTLSv1_get_timeout(tc->ssl, &tv); + + if (x) { + uint64_t delay = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + tmr_start(&tc->tmr, delay, timeout, tc); + } + +#else + (void)tc; +#endif +} + + +static int get_srtp_key_info(const struct dtls_flow *tc, char *name, size_t sz, + struct key *client_key, struct key *server_key) +{ + SRTP_PROTECTION_PROFILE *sel; + const char *keymatexportlabel = "EXTRACTOR-dtls_srtp"; + uint8_t exportedkeymat[1024], *p; + int keymatexportlen; + size_t kl = 128, sl = 112; + + sel = SSL_get_selected_srtp_profile(tc->ssl); + if (!sel) + return ENOENT; + + str_ncpy(name, sel->name, sz); + + kl /= 8; + sl /= 8; + + keymatexportlen = (int)(kl + sl)*2; + if (keymatexportlen != 60) { + DEBUG_WARNING("expected 60 bits, but keying material is %d\n", + keymatexportlen); + return EINVAL; + } + + if (!SSL_export_keying_material(tc->ssl, exportedkeymat, + keymatexportlen, + keymatexportlabel, + strlen(keymatexportlabel), + NULL, 0, 0)) { + return ENOENT; + } + + p = exportedkeymat; + + memcpy(client_key->key, p, kl); p += kl; + memcpy(server_key->key, p, kl); p += kl; + memcpy(client_key->salt, p, sl); p += sl; + memcpy(server_key->salt, p, sl); p += sl; + + client_key->key_len = server_key->key_len = kl; + client_key->salt_len = server_key->salt_len = sl; + + return 0; +} + + +static void destructor(void *arg) +{ + struct dtls_flow *flow = arg; + + if (flow->ssl) { + (void)SSL_shutdown(flow->ssl); + SSL_free(flow->ssl); + } + + mem_deref(flow->uh); + mem_deref(flow->us); + + tmr_cancel(&flow->tmr); +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct dtls_flow *flow = arg; + uint8_t b; + int r; + + if (mbuf_get_left(mb) < 1) + return false; + + /* ignore non-DTLS packets */ + b = mb->buf[mb->pos]; + if (b < 20 || b > 63) + return false; + + if (!sa_cmp(src, &flow->peer, SA_ALL)) + return false; + + /* feed SSL data to the BIO */ + r = BIO_write(flow->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb)); + if (r <= 0) + return true; + + SSL_read(flow->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb)); + + if (!flow->up && SSL_state(flow->ssl) == SSL_ST_OK) { + + struct key client_key, server_key; + char profile[256]; + int err; + + flow->up = true; + + err = get_srtp_key_info(flow, profile, sizeof(profile), + &client_key, &server_key); + if (err) { + DEBUG_WARNING("SRTP key info: %m\n", err); + return true; + } + + flow->estabh(0, flow, profile, + &client_key, &server_key, flow->arg); + } + + return true; +} + + +int dtls_flow_alloc(struct dtls_flow **flowp, struct tls *tls, + struct udp_sock *us, dtls_estab_h *estabh, void *arg) +{ + struct dtls_flow *flow; + int err = ENOMEM; + + if (!flowp || !tls || !us || !estabh) + return EINVAL; + + flow = mem_zalloc(sizeof(*flow), destructor); + if (!flow) + return ENOMEM; + + flow->tls = tls; + flow->us = mem_ref(us); + flow->estabh = estabh; + flow->arg = arg; + + err = udp_register_helper(&flow->uh, us, LAYER_DTLS, NULL, + recv_handler, flow); + if (err) + goto out; + + flow->ssl = SSL_new(tls->ctx); + if (!flow->ssl) + goto out; + + flow->sbio_in = BIO_new(BIO_s_mem()); + if (!flow->sbio_in) + goto out; + + flow->sbio_out = BIO_new(&bio_udp_send); + if (!flow->sbio_out) { + BIO_free(flow->sbio_in); + goto out; + } + flow->sbio_out->ptr = flow; + + SSL_set_bio(flow->ssl, flow->sbio_in, flow->sbio_out); + + tmr_init(&flow->tmr); + + err = 0; + + out: + if (err) + mem_deref(flow); + else + *flowp = flow; + + return err; +} + + +int dtls_flow_start(struct dtls_flow *flow, const struct sa *peer, bool active) +{ + int r, err = 0; + + if (!flow || !peer) + return EINVAL; + + flow->peer = *peer; + + if (active) { + r = SSL_connect(flow->ssl); + if (r < 0) { + int ssl_err = SSL_get_error(flow->ssl, r); + + ERR_clear_error(); + + if (ssl_err != SSL_ERROR_WANT_READ) { + DEBUG_WARNING("SSL_connect() failed" + " (err=%d)\n", ssl_err); + } + } + + check_timer(flow); + } + else { + SSL_set_accept_state(flow->ssl); + + SSL_set_verify_depth(flow->ssl, 0); + SSL_set_verify(flow->ssl, + SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, + verify_callback); + } + + return err; +} + + +static const EVP_MD *type2evp(const char *type) +{ + if (0 == str_casecmp(type, "SHA-1")) + return EVP_sha1(); + else if (0 == str_casecmp(type, "SHA-256")) + return EVP_sha256(); + else + return NULL; +} + + +int dtls_get_remote_fingerprint(const struct dtls_flow *flow, const char *type, + struct tls_fingerprint *fp) +{ + X509 *x; + + if (!flow || !fp) + return EINVAL; + + x = SSL_get_peer_certificate(flow->ssl); + if (!x) + return EPROTO; + + fp->len = sizeof(fp->md); + if (1 != X509_digest(x, type2evp(type), fp->md, &fp->len)) + return ENOENT; + + return 0; +} diff --git a/modules/evdev/evdev.c b/modules/evdev/evdev.c new file mode 100644 index 0000000..e54ba6b --- /dev/null +++ b/modules/evdev/evdev.c @@ -0,0 +1,348 @@ +/** + * @file evdev.c Input event device UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/input.h> +#include <re.h> +#include <baresip.h> +#include "print.h" + + +#define DEBUG_MODULE "evdev" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* Note: + * + * KEY_NUMERIC_xyz added in linux kernel 2.6.28 + */ + + +struct ui_st { + struct ui *ui; /* base class */ + int fd; + ui_input_h *h; + void *arg; +}; + + +static struct ui *evdev; +static char evdev_device[64] = "/dev/event0"; + + +static void evdev_close(struct ui_st *st) +{ + if (st->fd < 0) + return; + + fd_close(st->fd); + (void)close(st->fd); + st->fd = -1; +} + + +static void evdev_destructor(void *arg) +{ + struct ui_st *st = arg; + + evdev_close(st); + mem_deref(st->ui); +} + + +static int code2ascii(uint16_t modifier, uint16_t code) +{ + switch (code) { + + case KEY_0: return '0'; + case KEY_1: return '1'; + case KEY_2: return '2'; + case KEY_3: return KEY_LEFTSHIFT==modifier ? '#' : '3'; + case KEY_4: return '4'; + case KEY_5: return '5'; + case KEY_6: return '6'; + case KEY_7: return '7'; + case KEY_8: return '8'; + case KEY_9: return '9'; + case KEY_BACKSPACE: return '\b'; + case KEY_ENTER: return '\n'; + case KEY_ESC: return 0x1b; + case KEY_KPASTERISK: return '*'; +#ifdef KEY_NUMERIC_0 + case KEY_NUMERIC_0: return '0'; +#endif +#ifdef KEY_NUMERIC_1 + case KEY_NUMERIC_1: return '1'; +#endif +#ifdef KEY_NUMERIC_2 + case KEY_NUMERIC_2: return '2'; +#endif +#ifdef KEY_NUMERIC_3 + case KEY_NUMERIC_3: return '3'; +#endif +#ifdef KEY_NUMERIC_4 + case KEY_NUMERIC_4: return '4'; +#endif +#ifdef KEY_NUMERIC_5 + case KEY_NUMERIC_5: return '5'; +#endif +#ifdef KEY_NUMERIC_6 + case KEY_NUMERIC_6: return '6'; +#endif +#ifdef KEY_NUMERIC_7 + case KEY_NUMERIC_7: return '7'; +#endif +#ifdef KEY_NUMERIC_8 + case KEY_NUMERIC_8: return '8'; +#endif +#ifdef KEY_NUMERIC_9 + case KEY_NUMERIC_9: return '9'; +#endif +#ifdef KEY_NUMERIC_STAR + case KEY_NUMERIC_STAR: return '*'; +#endif +#ifdef KEY_NUMERIC_POUND + case KEY_NUMERIC_POUND: return '#'; +#endif +#ifdef KEY_KP0 + case KEY_KP0: return '0'; +#endif +#ifdef KEY_KP1 + case KEY_KP1: return '1'; +#endif +#ifdef KEY_KP2 + case KEY_KP2: return '2'; +#endif +#ifdef KEY_KP3 + case KEY_KP3: return '3'; +#endif +#ifdef KEY_KP4 + case KEY_KP4: return '4'; +#endif +#ifdef KEY_KP5 + case KEY_KP5: return '5'; +#endif +#ifdef KEY_KP6 + case KEY_KP6: return '6'; +#endif +#ifdef KEY_KP7 + case KEY_KP7: return '7'; +#endif +#ifdef KEY_KP8 + case KEY_KP8: return '8'; +#endif +#ifdef KEY_KP9 + case KEY_KP9: return '9'; +#endif +#ifdef KEY_KPDOT + case KEY_KPDOT: return 0x1b; +#endif +#ifdef KEY_KPENTER + case KEY_KPENTER: return '\n'; +#endif + default: return -1; + } +} + + +static int stderr_handler(const char *p, size_t sz, void *arg) +{ + (void)arg; + + if (write(STDERR_FILENO, p, sz) < 0) + return errno; + + return 0; +} + + +static void reportkey(struct ui_st *st, int ascii) +{ + struct re_printf pf; + + pf.vph = stderr_handler; + + if (!st->h) + return; + + st->h(ascii, &pf, st->arg); +} + + +static void evdev_fd_handler(int flags, void *arg) +{ + struct ui_st *st = arg; + struct input_event evv[64]; /* the events (up to 64 at once) */ + uint16_t modifier = 0; + size_t n; + int i; + + /* This might happen if you unplug a USB device */ + if (flags & FD_EXCEPT) { + DEBUG_WARNING("fd handler: FD_EXCEPT - device unplugged?\n"); + evdev_close(st); + return; + } + + if (FD_READ != flags) { + DEBUG_WARNING("fd_handler: unexpected flags 0x%02x\n", flags); + return; + } + + n = read(st->fd, evv, sizeof(evv)); + + if (n < (int) sizeof(struct input_event)) { + DEBUG_WARNING("event: short read (%m)\n", errno); + return; + } + + for (i = 0; i < (int) (n / sizeof(struct input_event)); i++) { + const struct input_event *ev = &evv[i]; + + DEBUG_INFO("Event: type %u, code %u, value %d\n", + ev->type, ev->code, ev->value); + + if (EV_KEY != ev->type) + continue; + + if (KEY_LEFTSHIFT == ev->code) { + modifier = KEY_LEFTSHIFT; + continue; + } + + if (1 == ev->value) { + const int ascii = code2ascii(modifier, ev->code); + if (-1 == ascii) { + DEBUG_WARNING("unhandled key code %u\n", + ev->code); + } + else + reportkey(st, ascii); + modifier = 0; + } + else if (0 == ev->value) { + reportkey(st, 0x00); + } + } +} + + +static int evdev_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *uih, void *arg) +{ + const char *dev = str_isset(prm->device) ? prm->device : evdev_device; + struct ui_st *st; + int err = 0; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), evdev_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(evdev); + st->fd = open(dev, O_RDWR); + if (st->fd < 0) { + err = errno; + goto out; + } + +#if 0 + /* grab the event device to prevent it from propagating + its events to the regular keyboard driver */ + if (-1 == ioctl(st->fd, EVIOCGRAB, (void *)1)) { + DEBUG_WARNING("ioctl EVIOCGRAB on %s (%m)\n", dev, errno); + } +#endif + + print_name(st->fd); + print_events(st->fd); + print_keys(st->fd); + print_leds(st->fd); + + err = fd_listen(st->fd, FD_READ, evdev_fd_handler, st); + if (err) + goto out; + + st->h = uih; + st->arg = arg; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int buzz(const struct ui_st *st, int value) +{ + struct input_event ev; + ssize_t n; + + ev.type = EV_SND; + ev.code = SND_BELL; + ev.value = value; + + n = write(st->fd, &ev, sizeof(ev)); + if (n < 0) { + DEBUG_WARNING("output: write fd=%d (%m)\n", st->fd, errno); + } + + return errno; +} + + +static int evdev_output(struct ui_st *st, const char *str) +{ + int err = 0; + + if (!str) + return EINVAL; + + while (*str) { + switch (*str++) { + + case '\a': + err |= buzz(st, 1); + break; + + default: + err |= buzz(st, 0); + break; + } + } + + return err; +} + + +static int module_init(void) +{ + return ui_register(&evdev, "evdev", evdev_alloc, evdev_output); +} + + +static int module_close(void) +{ + evdev = mem_deref(evdev); + return 0; +} + + +const struct mod_export DECL_EXPORTS(evdev) = { + "evdev", + "ui", + module_init, + module_close +}; diff --git a/modules/evdev/module.mk b/modules/evdev/module.mk new file mode 100644 index 0000000..5d9ede2 --- /dev/null +++ b/modules/evdev/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := evdev +$(MOD)_SRCS += evdev.c +$(MOD)_SRCS += print.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/evdev/print.c b/modules/evdev/print.c new file mode 100644 index 0000000..3e2b762 --- /dev/null +++ b/modules/evdev/print.c @@ -0,0 +1,518 @@ +/** + * @file print.c Input event device info + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/input.h> +#include <re_types.h> +#include <re_fmt.h> +#include "print.h" + + +#define DEBUG_MODULE "evdev" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + + +/** + * Print the name information + * + * @param fd Device file descriptor + */ +void print_name(int fd) +{ + char name[256]= "Unknown"; + + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { + perror("evdev ioctl"); + } + + DEBUG_NOTICE("evdev device name: %s\n", name); +} + + +/** + * Print supported events + * + * @param fd Device file descriptor + */ +void print_events(int fd) +{ + uint8_t evtype_bitmask[EV_MAX/8 + 1]; + int i; + + memset(evtype_bitmask, 0, sizeof(evtype_bitmask)); + if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) { + DEBUG_WARNING("evdev ioctl EVIOCGBIT (%m)\n", errno); + return; + } + + printf("Supported event types:\n"); + + for (i = 0; i < EV_MAX; i++) { + if (!test_bit(i, evtype_bitmask)) + continue; + + printf(" Event type 0x%02x ", i); + + switch (i) { + + case EV_KEY : + printf(" (Keys or Buttons)\n"); + break; + case EV_REL : + printf(" (Relative Axes)\n"); + break; + case EV_ABS : + printf(" (Absolute Axes)\n"); + break; + case EV_MSC : + printf(" (Something miscellaneous)\n"); + break; + case EV_LED : + printf(" (LEDs)\n"); + break; + case EV_SND : + printf(" (Sounds)\n"); + break; + case EV_REP : + printf(" (Repeat)\n"); + break; + case EV_FF : + printf(" (Force Feedback)\n"); + break; + default: + printf(" (Unknown event type: 0x%04x)\n", i); + break; + } + } +} + + +/** + * Print supported keys + * + * @param fd Device file descriptor + */ +void print_keys(int fd) +{ + uint8_t key_bitmask[KEY_MAX/8 + 1]; + int i; + + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), + key_bitmask) < 0) { + perror("evdev ioctl"); + } + + printf("Supported Keys:\n"); + + for (i = 0; i < KEY_MAX; i++) { + if (!test_bit(i, key_bitmask)) + continue; + + printf(" Key 0x%02x ", i); + + switch (i) { + + case KEY_RESERVED : printf(" (Reserved)\n"); break; + case KEY_ESC : printf(" (Escape)\n"); break; + case KEY_1 : printf(" (1)\n"); break; + case KEY_2 : printf(" (2)\n"); break; + case KEY_3 : printf(" (3)\n"); break; + case KEY_4 : printf(" (4)\n"); break; + case KEY_5 : printf(" (5)\n"); break; + case KEY_6 : printf(" (6)\n"); break; + case KEY_7 : printf(" (7)\n"); break; + case KEY_8 : printf(" (8)\n"); break; + case KEY_9 : printf(" ()\n"); break; + case KEY_0 : printf(" ()\n"); break; + case KEY_MINUS : printf(" (-)\n"); break; + case KEY_EQUAL : printf(" (=)\n"); break; + case KEY_BACKSPACE : printf(" (Backspace)\n"); break; + case KEY_TAB : printf(" (Tab)\n"); break; + case KEY_Q : printf(" (Q)\n"); break; + case KEY_W : printf(" (W)\n"); break; + case KEY_E : printf(" (E)\n"); break; + case KEY_R : printf(" (R)\n"); break; + case KEY_T : printf(" (T)\n"); break; + case KEY_Y : printf(" (Y)\n"); break; + case KEY_U : printf(" (U)\n"); break; + case KEY_I : printf(" (I)\n"); break; + case KEY_O : printf(" (O)\n"); break; + case KEY_P : printf(" (P)\n"); break; + case KEY_LEFTBRACE : printf(" ([)\n"); break; + case KEY_RIGHTBRACE : printf(" (])\n"); break; + case KEY_ENTER : printf(" (Enter)\n"); break; + case KEY_LEFTCTRL : printf(" (LH Control)\n"); break; + case KEY_A : printf(" (A)\n"); break; + case KEY_S : printf(" (S)\n"); break; + case KEY_D : printf(" (D)\n"); break; + case KEY_F : printf(" (F)\n"); break; + case KEY_G : printf(" (G)\n"); break; + case KEY_H : printf(" (H)\n"); break; + case KEY_J : printf(" (J)\n"); break; + case KEY_K : printf(" (K)\n"); break; + case KEY_L : printf(" (L)\n"); break; + case KEY_SEMICOLON : printf(" (;)\n"); break; + case KEY_APOSTROPHE : printf(" (')\n"); break; + case KEY_GRAVE : printf(" (`)\n"); break; + case KEY_LEFTSHIFT : printf(" (LH Shift)\n"); break; + case KEY_BACKSLASH : printf(" (\\)\n"); break; + case KEY_Z : printf(" (Z)\n"); break; + case KEY_X : printf(" (X)\n"); break; + case KEY_C : printf(" (C)\n"); break; + case KEY_V : printf(" (V)\n"); break; + case KEY_B : printf(" (B)\n"); break; + case KEY_N : printf(" (N)\n"); break; + case KEY_M : printf(" (M)\n"); break; + case KEY_COMMA : printf(" (,)\n"); break; + case KEY_DOT : printf(" (.)\n"); break; + case KEY_SLASH : printf(" (/)\n"); break; + case KEY_RIGHTSHIFT : printf(" (RH Shift)\n"); break; + case KEY_KPASTERISK : printf(" (*)\n"); break; + case KEY_LEFTALT : printf(" (LH Alt)\n"); break; + case KEY_SPACE : printf(" (Space)\n"); break; + case KEY_CAPSLOCK : printf(" (CapsLock)\n"); break; + case KEY_F1 : printf(" (F1)\n"); break; + case KEY_F2 : printf(" (F2)\n"); break; + case KEY_F3 : printf(" (F3)\n"); break; + case KEY_F4 : printf(" (F4)\n"); break; + case KEY_F5 : printf(" (F5)\n"); break; + case KEY_F6 : printf(" (F6)\n"); break; + case KEY_F7 : printf(" (F7)\n"); break; + case KEY_F8 : printf(" (F8)\n"); break; + case KEY_F9 : printf(" (F9)\n"); break; + case KEY_F10 : printf(" (F10)\n"); break; + case KEY_NUMLOCK : printf(" (NumLock)\n"); break; + case KEY_SCROLLLOCK : printf(" (ScrollLock)\n"); break; + case KEY_KP7 : printf(" (KeyPad 7)\n"); break; + case KEY_KP8 : printf(" (KeyPad 8)\n"); break; + case KEY_KP9 : printf(" (Keypad 9)\n"); break; + case KEY_KPMINUS : printf(" (KeyPad Minus)\n"); break; + case KEY_KP4 : printf(" (KeyPad 4)\n"); break; + case KEY_KP5 : printf(" (KeyPad 5)\n"); break; + case KEY_KP6 : printf(" (KeyPad 6)\n"); break; + case KEY_KPPLUS : printf(" (KeyPad Plus)\n"); break; + case KEY_KP1 : printf(" (KeyPad 1)\n"); break; + case KEY_KP2 : printf(" (KeyPad 2)\n"); break; + case KEY_KP3 : printf(" (KeyPad 3)\n"); break; + case KEY_KPDOT : printf(" (KeyPad decimal point)\n"); break; +/* case KEY_103RD : printf(" (Huh?)\n"); break; */ + case KEY_F13 : printf(" (F13)\n"); break; + case KEY_102ND : printf(" (Beats me...)\n"); break; + case KEY_F11 : printf(" (F11)\n"); break; + case KEY_F12 : printf(" (F12)\n"); break; + case KEY_F14 : printf(" (F14)\n"); break; + case KEY_F15 : printf(" (F15)\n"); break; + case KEY_F16 : printf(" (F16)\n"); break; + case KEY_F17 : printf(" (F17)\n"); break; + case KEY_F18 : printf(" (F18)\n"); break; + case KEY_F19 : printf(" (F19)\n"); break; + case KEY_F20 : printf(" (F20)\n"); break; + case KEY_KPENTER : printf(" (Keypad Enter)\n"); break; + case KEY_RIGHTCTRL : printf(" (RH Control)\n"); break; + case KEY_KPSLASH : printf(" (KeyPad Forward Slash)\n"); break; + case KEY_SYSRQ : printf(" (System Request)\n"); break; + case KEY_RIGHTALT : printf(" (RH Alternate)\n"); break; + case KEY_LINEFEED : printf(" (Line Feed)\n"); break; + case KEY_HOME : printf(" (Home)\n"); break; + case KEY_UP : printf(" (Up)\n"); break; + case KEY_PAGEUP : printf(" (Page Up)\n"); break; + case KEY_LEFT : printf(" (Left)\n"); break; + case KEY_RIGHT : printf(" (Right)\n"); break; + case KEY_END : printf(" (End)\n"); break; + case KEY_DOWN : printf(" (Down)\n"); break; + case KEY_PAGEDOWN : printf(" (Page Down)\n"); break; + case KEY_INSERT : printf(" (Insert)\n"); break; + case KEY_DELETE : printf(" (Delete)\n"); break; + case KEY_MACRO : printf(" (Macro)\n"); break; + case KEY_MUTE : printf(" (Mute)\n"); break; + case KEY_VOLUMEDOWN : printf(" (Volume Down)\n"); break; + case KEY_VOLUMEUP : printf(" (Volume Up)\n"); break; + case KEY_POWER : printf(" (Power)\n"); break; + case KEY_KPEQUAL : printf(" (KeyPad Equal)\n"); break; + case KEY_KPPLUSMINUS : printf(" (KeyPad +/-)\n"); break; + case KEY_PAUSE : printf(" (Pause)\n"); break; + case KEY_F21 : printf(" (F21)\n"); break; + case KEY_F22 : printf(" (F22)\n"); break; + case KEY_F23 : printf(" (F23)\n"); break; + case KEY_F24 : printf(" (F24)\n"); break; + case KEY_KPCOMMA : printf(" (KeyPad comma)\n"); break; + case KEY_LEFTMETA : printf(" (LH Meta)\n"); break; + case KEY_RIGHTMETA : printf(" (RH Meta)\n"); break; + case KEY_COMPOSE : printf(" (Compose)\n"); break; + case KEY_STOP : printf(" (Stop)\n"); break; + case KEY_AGAIN : printf(" (Again)\n"); break; + case KEY_PROPS : printf(" (Properties)\n"); break; + case KEY_UNDO : printf(" (Undo)\n"); break; + case KEY_FRONT : printf(" (Front)\n"); break; + case KEY_COPY : printf(" (Copy)\n"); break; + case KEY_OPEN : printf(" (Open)\n"); break; + case KEY_PASTE : printf(" (Paste)\n"); break; + case KEY_FIND : printf(" (Find)\n"); break; + case KEY_CUT : printf(" (Cut)\n"); break; + case KEY_HELP : printf(" (Help)\n"); break; + case KEY_MENU : printf(" (Menu)\n"); break; + case KEY_CALC : printf(" (Calculator)\n"); break; + case KEY_SETUP : printf(" (Setup)\n"); break; + case KEY_SLEEP : printf(" (Sleep)\n"); break; + case KEY_WAKEUP : printf(" (Wakeup)\n"); break; + case KEY_FILE : printf(" (File)\n"); break; + case KEY_SENDFILE : printf(" (Send File)\n"); break; + case KEY_DELETEFILE : printf(" (Delete File)\n"); break; + case KEY_XFER : printf(" (Transfer)\n"); break; + case KEY_PROG1 : printf(" (Program 1)\n"); break; + case KEY_PROG2 : printf(" (Program 2)\n"); break; + case KEY_WWW : printf(" (Web Browser)\n"); break; + case KEY_MSDOS : printf(" (DOS mode)\n"); break; + case KEY_COFFEE : printf(" (Coffee)\n"); break; + case KEY_DIRECTION : printf(" (Direction)\n"); break; + case KEY_CYCLEWINDOWS : printf(" (Window cycle)\n"); break; + case KEY_MAIL : printf(" (Mail)\n"); break; + case KEY_BOOKMARKS : printf(" (Book Marks)\n"); break; + case KEY_COMPUTER : printf(" (Computer)\n"); break; + case KEY_BACK : printf(" (Back)\n"); break; + case KEY_FORWARD : printf(" (Forward)\n"); break; + case KEY_CLOSECD : printf(" (Close CD)\n"); break; + case KEY_EJECTCD : printf(" (Eject CD)\n"); break; + case KEY_EJECTCLOSECD : printf(" (Eject / Close CD)\n"); break; + case KEY_NEXTSONG : printf(" (Next Song)\n"); break; + case KEY_PLAYPAUSE : printf(" (Play and Pause)\n"); break; + case KEY_PREVIOUSSONG : printf(" (Previous Song)\n"); break; + case KEY_STOPCD : printf(" (Stop CD)\n"); break; + case KEY_RECORD : printf(" (Record)\n"); break; + case KEY_REWIND : printf(" (Rewind)\n"); break; + case KEY_PHONE : printf(" (Phone)\n"); break; + case KEY_ISO : printf(" (ISO)\n"); break; + case KEY_CONFIG : printf(" (Config)\n"); break; + case KEY_HOMEPAGE : printf(" (Home)\n"); break; + case KEY_REFRESH : printf(" (Refresh)\n"); break; + case KEY_EXIT : printf(" (Exit)\n"); break; + case KEY_MOVE : printf(" (Move)\n"); break; + case KEY_EDIT : printf(" (Edit)\n"); break; + case KEY_SCROLLUP : printf(" (Scroll Up)\n"); break; + case KEY_SCROLLDOWN : printf(" (Scroll Down)\n"); break; + case KEY_KPLEFTPAREN : printf(" (KeyPad LH paren)\n"); break; + case KEY_KPRIGHTPAREN : printf(" (KeyPad RH paren)\n"); break; +#if 0 + case KEY_INTL1 : printf(" (Intl 1)\n"); break; + case KEY_INTL2 : printf(" (Intl 2)\n"); break; + case KEY_INTL3 : printf(" (Intl 3)\n"); break; + case KEY_INTL4 : printf(" (Intl 4)\n"); break; + case KEY_INTL5 : printf(" (Intl 5)\n"); break; + case KEY_INTL6 : printf(" (Intl 6)\n"); break; + case KEY_INTL7 : printf(" (Intl 7)\n"); break; + case KEY_INTL8 : printf(" (Intl 8)\n"); break; + case KEY_INTL9 : printf(" (Intl 9)\n"); break; + case KEY_LANG1 : printf(" (Language 1)\n"); break; + case KEY_LANG2 : printf(" (Language 2)\n"); break; + case KEY_LANG3 : printf(" (Language 3)\n"); break; + case KEY_LANG4 : printf(" (Language 4)\n"); break; + case KEY_LANG5 : printf(" (Language 5)\n"); break; + case KEY_LANG6 : printf(" (Language 6)\n"); break; + case KEY_LANG7 : printf(" (Language 7)\n"); break; + case KEY_LANG8 : printf(" (Language 8)\n"); break; + case KEY_LANG9 : printf(" (Language 9)\n"); break; +#endif + case KEY_PLAYCD : printf(" (Play CD)\n"); break; + case KEY_PAUSECD : printf(" (Pause CD)\n"); break; + case KEY_PROG3 : printf(" (Program 3)\n"); break; + case KEY_PROG4 : printf(" (Program 4)\n"); break; + case KEY_SUSPEND : printf(" (Suspend)\n"); break; + case KEY_CLOSE : printf(" (Close)\n"); break; + case KEY_UNKNOWN : printf(" (Specifically unknown)\n"); break; +#ifdef KEY_BRIGHTNESSDOWN + case KEY_BRIGHTNESSDOWN: printf(" (Brightness Down)\n");break; +#endif +#ifdef KEY_BRIGHTNESSUP + case KEY_BRIGHTNESSUP : printf(" (Brightness Up)\n"); break; +#endif + case BTN_0 : printf(" (Button 0)\n"); break; + case BTN_1 : printf(" (Button 1)\n"); break; + case BTN_2 : printf(" (Button 2)\n"); break; + case BTN_3 : printf(" (Button 3)\n"); break; + case BTN_4 : printf(" (Button 4)\n"); break; + case BTN_5 : printf(" (Button 5)\n"); break; + case BTN_6 : printf(" (Button 6)\n"); break; + case BTN_7 : printf(" (Button 7)\n"); break; + case BTN_8 : printf(" (Button 8)\n"); break; + case BTN_9 : printf(" (Button 9)\n"); break; + case BTN_LEFT : printf(" (Left Button)\n"); break; + case BTN_RIGHT : printf(" (Right Button)\n"); break; + case BTN_MIDDLE : printf(" (Middle Button)\n"); break; + case BTN_SIDE : printf(" (Side Button)\n"); break; + case BTN_EXTRA : printf(" (Extra Button)\n"); break; + case BTN_FORWARD : printf(" (Forward Button)\n"); break; + case BTN_BACK : printf(" (Back Button)\n"); break; + case BTN_TRIGGER : printf(" (Trigger Button)\n"); break; + case BTN_THUMB : printf(" (Thumb Button)\n"); break; + case BTN_THUMB2 : printf(" (Second Thumb Button)\n"); break; + case BTN_TOP : printf(" (Top Button)\n"); break; + case BTN_TOP2 : printf(" (Second Top Button)\n"); break; + case BTN_PINKIE : printf(" (Pinkie Button)\n"); break; + case BTN_BASE : printf(" (Base Button)\n"); break; + case BTN_BASE2 : printf(" (Second Base Button)\n"); break; + case BTN_BASE3 : printf(" (Third Base Button)\n"); break; + case BTN_BASE4 : printf(" (Fourth Base Button)\n"); break; + case BTN_BASE5 : printf(" (Fifth Base Button)\n"); break; + case BTN_BASE6 : printf(" (Sixth Base Button)\n"); break; + case BTN_DEAD : printf(" (Dead Button)\n"); break; + case BTN_A : printf(" (Button A)\n"); break; + case BTN_B : printf(" (Button B)\n"); break; + case BTN_C : printf(" (Button C)\n"); break; + case BTN_X : printf(" (Button X)\n"); break; + case BTN_Y : printf(" (Button Y)\n"); break; + case BTN_Z : printf(" (Button Z)\n"); break; + case BTN_TL : printf(" (Thumb Left Button)\n"); break; + case BTN_TR : printf(" (Thumb Right Button )\n"); break; + case BTN_TL2 : printf(" (Second Thumb Left Button)\n"); break; + case BTN_TR2 : printf(" (Second Thumb Right Button )\n"); + break; + case BTN_SELECT : printf(" (Select Button)\n"); break; + case BTN_MODE : printf(" (Mode Button)\n"); break; + case BTN_THUMBL : printf(" (Another Left Thumb Button )\n"); + break; + case BTN_THUMBR : printf(" (Another Right Thumb Button )\n"); + break; + case BTN_TOOL_PEN : printf(" (Digitiser Pen Tool)\n"); break; + case BTN_TOOL_RUBBER : printf(" (Digitiser Rubber Tool)\n"); + break; + case BTN_TOOL_BRUSH : printf(" (Digitiser Brush Tool)\n"); + break; + case BTN_TOOL_PENCIL : printf(" (Digitiser Pencil Tool)\n"); + break; + case BTN_TOOL_AIRBRUSH:printf(" (Digitiser Airbrush Tool)\n"); + break; + case BTN_TOOL_FINGER : printf(" (Digitiser Finger Tool)\n"); + break; + case BTN_TOOL_MOUSE : printf(" (Digitiser Mouse Tool)\n"); + break; + case BTN_TOOL_LENS : printf(" (Digitiser Lens Tool)\n"); break; + case BTN_TOUCH : printf(" (Digitiser Touch Button )\n"); break; + case BTN_STYLUS : printf(" (Digitiser Stylus Button )\n"); + break; + case BTN_STYLUS2: printf(" (Second Digitiser Stylus Btn)\n"); + break; +#ifdef KEY_NUMERIC_0 + case KEY_NUMERIC_0: printf(" (Numeric 0)\n"); + break; +#endif +#ifdef KEY_NUMERIC_1 + case KEY_NUMERIC_1: printf(" (Numeric 1)\n"); + break; +#endif +#ifdef KEY_NUMERIC_2 + case KEY_NUMERIC_2: printf(" (Numeric 2)\n"); + break; +#endif +#ifdef KEY_NUMERIC_3 + case KEY_NUMERIC_3: printf(" (Numeric 3)\n"); + break; +#endif +#ifdef KEY_NUMERIC_4 + case KEY_NUMERIC_4: printf(" (Numeric 4)\n"); + break; +#endif +#ifdef KEY_NUMERIC_5 + case KEY_NUMERIC_5: printf(" (Numeric 5)\n"); + break; +#endif +#ifdef KEY_NUMERIC_6 + case KEY_NUMERIC_6: printf(" (Numeric 6)\n"); + break; +#endif +#ifdef KEY_NUMERIC_7 + case KEY_NUMERIC_7: printf(" (Numeric 7)\n"); + break; +#endif +#ifdef KEY_NUMERIC_8 + case KEY_NUMERIC_8: printf(" (Numeric 8)\n"); + break; +#endif +#ifdef KEY_NUMERIC_9 + case KEY_NUMERIC_9: printf(" (Numeric 9)\n"); + break; +#endif +#ifdef KEY_NUMERIC_STAR + case KEY_NUMERIC_STAR: printf(" (Numeric *)\n"); + break; +#endif +#ifdef KEY_NUMERIC_POUND + case KEY_NUMERIC_POUND: printf(" (Numeric #)\n"); + break; +#endif + default: + printf(" (Unknown key)\n"); + } + } +} + + +/** + * Print supported LEDs + * + * @param fd Device file descriptor + */ +void print_leds(int fd) +{ + uint8_t led_bitmask[LED_MAX/8 + 1]; + int i; + + memset(led_bitmask, 0, sizeof(led_bitmask)); + if (ioctl(fd, EVIOCGBIT(EV_LED, sizeof(led_bitmask)), + led_bitmask) < 0) { + perror("evdev ioctl"); + } + + printf("Supported LEDs:\n"); + + for (i = 0; i < LED_MAX; i++) { + if (!test_bit(i, led_bitmask)) + continue; + + printf(" LED type 0x%02x ", i); + + switch (i) { + + case LED_NUML : + printf(" (Num Lock)\n"); + break; + case LED_CAPSL : + printf(" (Caps Lock)\n"); + break; + case LED_SCROLLL : + printf(" (Scroll Lock)\n"); + break; + case LED_COMPOSE : + printf(" (Compose)\n"); + break; + case LED_KANA : + printf(" (Kana)\n"); + break; + case LED_SLEEP : + printf(" (Sleep)\n"); + break; + case LED_SUSPEND : + printf(" (Suspend)\n"); + break; + case LED_MUTE : + printf(" (Mute)\n"); + break; + case LED_MISC : + printf(" (Miscellaneous)\n"); + break; + default: + printf(" (Unknown LED type: 0x%04x)\n", i); + } + } +} diff --git a/modules/evdev/print.h b/modules/evdev/print.h new file mode 100644 index 0000000..49acfcf --- /dev/null +++ b/modules/evdev/print.h @@ -0,0 +1,11 @@ +/** + * @file print.h Interface to Input event device info + * + * Copyright (C) 2010 Creytiv.com + */ + + +void print_name(int fd); +void print_events(int fd); +void print_keys(int fd); +void print_leds(int fd); diff --git a/modules/g711/g711.c b/modules/g711/g711.c new file mode 100644 index 0000000..f1179d5 --- /dev/null +++ b/modules/g711/g711.c @@ -0,0 +1,126 @@ +/** + * @file g711.c G.711 Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +static int pcmu_encode(struct auenc_state *aes, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + (void)aes; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc) + return ENOMEM; + + *len = sampc; + + while (sampc--) + *buf++ = g711_pcm2ulaw(*sampv++); + + return 0; +} + + +static int pcmu_decode(struct audec_state *ads, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + (void)ads; + + if (!sampv || !sampc || !buf) + return EINVAL; + + if (*sampc < len) + return ENOMEM; + + *sampc = len; + + while (len--) + *sampv++ = g711_ulaw2pcm(*buf++); + + return 0; +} + + +static int pcma_encode(struct auenc_state *aes, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + (void)aes; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc) + return ENOMEM; + + *len = sampc; + + while (sampc--) + *buf++ = g711_pcm2alaw(*sampv++); + + return 0; +} + + +static int pcma_decode(struct audec_state *ads, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + (void)ads; + + if (!sampv || !sampc || !buf) + return EINVAL; + + if (*sampc < len) + return ENOMEM; + + *sampc = len; + + while (len--) + *sampv++ = g711_alaw2pcm(*buf++); + + return 0; +} + + +static struct aucodec pcmu = { + LE_INIT, "0", "PCMU", 8000, 1, NULL, + NULL, pcmu_encode, NULL, pcmu_decode, NULL, NULL, NULL +}; + +static struct aucodec pcma = { + LE_INIT, "8", "PCMA", 8000, 1, NULL, + NULL, pcma_encode, NULL, pcma_decode, NULL, NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(&pcmu); + aucodec_register(&pcma); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&pcma); + aucodec_unregister(&pcmu); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g711) = { + "g711", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g711/module.mk b/modules/g711/module.mk new file mode 100644 index 0000000..432269d --- /dev/null +++ b/modules/g711/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g711 +$(MOD)_SRCS += g711.c + +include mk/mod.mk diff --git a/modules/g722/g722.c b/modules/g722/g722.c new file mode 100644 index 0000000..5e07e3a --- /dev/null +++ b/modules/g722/g722.c @@ -0,0 +1,196 @@ +/** + * @file g722.c G.722 audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <re.h> +#include <baresip.h> +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include <spandsp.h> + + +#define DEBUG_MODULE "g722" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* + http://www.soft-switch.org/spandsp-modules.html + */ + +/* From RFC 3551: + + 4.5.2 G722 + + G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding + within 64 kbit/s". The G.722 encoder produces a stream of octets, + each of which SHALL be octet-aligned in an RTP packet. The first bit + transmitted in the G.722 octet, which is the most significant bit of + the higher sub-band sample, SHALL correspond to the most significant + bit of the octet in the RTP packet. + + Even though the actual sampling rate for G.722 audio is 16,000 Hz, + the RTP clock rate for the G722 payload format is 8,000 Hz because + that value was erroneously assigned in RFC 1890 and must remain + unchanged for backward compatibility. The octet rate or sample-pair + rate is 8,000 Hz. + */ + +enum { + G722_SAMPLE_RATE = 16000, + G722_BITRATE_48k = 48000, + G722_BITRATE_56k = 56000, + G722_BITRATE_64k = 64000 +}; + + +struct auenc_state { + g722_encode_state_t enc; +}; + +struct audec_state { + g722_decode_state_t dec; +}; + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_alloc(sizeof(*st), NULL); + if (!st) + return ENOMEM; + + if (!g722_encode_init(&st->enc, G722_BITRATE_64k, 0)) { + DEBUG_WARNING("g722_encode_init failed\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_alloc(sizeof(*st), NULL); + if (!st) + return ENOMEM; + + if (!g722_decode_init(&st->dec, G722_BITRATE_64k, 0)) { + DEBUG_WARNING("g722_decode_init failed\n"); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int n; + + n = g722_encode(&st->enc, buf, sampv, (int)sampc); + if (n <= 0) { + DEBUG_WARNING("g722_encode: len=%d\n", n); + return EPROTO; + } + else if (n > (int)*len) { + DEBUG_WARNING("encode: wrote %d > %d buf\n", n, *len); + return EOVERFLOW; + } + + *len = n; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int n; + + if (!st || !sampv || !buf) + return EINVAL; + + n = g722_decode(&st->dec, sampv, buf, (int)len); + if (n < 0) { + DEBUG_WARNING("g722_decode: n=%d\n", n); + return EPROTO; + } + + *sampc = n; + + return 0; +} + + +static struct aucodec g722 = { + LE_INIT, "9", "G722", 8000, 1, NULL, + encode_update, encode, + decode_update, decode, NULL, + NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(&g722); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&g722); + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(g722) = { + "g722", + "codec", + module_init, + module_close +}; diff --git a/modules/g722/module.mk b/modules/g722/module.mk new file mode 100644 index 0000000..f56dd07 --- /dev/null +++ b/modules/g722/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g722 +$(MOD)_SRCS += g722.c +$(MOD)_LFLAGS += -lspandsp + +include mk/mod.mk diff --git a/modules/g7221/decode.c b/modules/g7221/decode.c new file mode 100644 index 0000000..6977a12 --- /dev/null +++ b/modules/g7221/decode.c @@ -0,0 +1,67 @@ +/** + * @file g7221/decode.c G.722.1 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <g722_1.h> +#include "g7221.h" + + +struct audec_state { + g722_1_decode_state_t dec; +}; + + +int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac; + struct audec_state *ads; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + ads = *adsp; + + if (ads) + return 0; + + ads = mem_alloc(sizeof(*ads), NULL); + if (!ads) + return ENOMEM; + + if (!g722_1_decode_init(&ads->dec, g7221->bitrate, ac->srate)) { + mem_deref(ads); + return EPROTO; + } + + *adsp = ads; + + return 0; +} + + +int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + size_t framec; + + if (!ads || !sampv || !sampc || !buf) + return EINVAL; + + framec = len / ads->dec.bytes_per_frame; + + if (len != ads->dec.bytes_per_frame * framec) + return EPROTO; + + if (*sampc < ads->dec.frame_size * framec) + return ENOMEM; + + *sampc = g722_1_decode(&ads->dec, sampv, buf, (int)len); + + return 0; +} diff --git a/modules/g7221/encode.c b/modules/g7221/encode.c new file mode 100644 index 0000000..8bb5b2b --- /dev/null +++ b/modules/g7221/encode.c @@ -0,0 +1,68 @@ +/** + * @file g7221/encode.c G.722.1 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <g722_1.h> +#include "g7221.h" + + +struct auenc_state { + g722_1_encode_state_t enc; +}; + + +int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac; + struct auenc_state *aes; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + aes = *aesp; + + if (aes) + return 0; + + aes = mem_alloc(sizeof(*aes), NULL); + if (!aes) + return ENOMEM; + + if (!g722_1_encode_init(&aes->enc, g7221->bitrate, ac->srate)) { + mem_deref(aes); + return EPROTO; + } + + *aesp = aes; + + return 0; +} + + +int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + size_t framec; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + framec = sampc / aes->enc.frame_size; + + if (sampc != aes->enc.frame_size * framec) + return EPROTO; + + if (*len < aes->enc.bytes_per_frame * framec) + return ENOMEM; + + *len = g722_1_encode(&aes->enc, buf, sampv, (int)sampc); + + return 0; +} diff --git a/modules/g7221/g7221.c b/modules/g7221/g7221.c new file mode 100644 index 0000000..a224f46 --- /dev/null +++ b/modules/g7221/g7221.c @@ -0,0 +1,49 @@ +/** + * @file g7221.c G.722.1 Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "g7221.h" + + +static struct g7221_aucodec g7221 = { + .ac = { + .name = "G7221", + .srate = 16000, + .ch = 1, + .encupdh = g7221_encode_update, + .ench = g7221_encode, + .decupdh = g7221_decode_update, + .dech = g7221_decode, + .fmtp_ench = g7221_fmtp_enc, + .fmtp_cmph = g7221_fmtp_cmp, + }, + .bitrate = 32000, +}; + + +static int module_init(void) +{ + aucodec_register((struct aucodec *)&g7221); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister((struct aucodec *)&g7221); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g7221) = { + "g7221", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g7221/g7221.h b/modules/g7221/g7221.h new file mode 100644 index 0000000..635fc01 --- /dev/null +++ b/modules/g7221/g7221.h @@ -0,0 +1,29 @@ +/** + * @file g7221.h Private G.722.1 Interface + * + * Copyright (C) 2010 Creytiv.com + */ + +struct g7221_aucodec { + struct aucodec ac; + uint32_t bitrate; +}; + +/* Encode */ +int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp); +int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc); + + +/* Decode */ +int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp); +int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len); + + +/* SDP */ +int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg); diff --git a/modules/g7221/module.mk b/modules/g7221/module.mk new file mode 100644 index 0000000..e0471a7 --- /dev/null +++ b/modules/g7221/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g7221 +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += g7221.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lg722_1 + +include mk/mod.mk diff --git a/modules/g7221/sdp.c b/modules/g7221/sdp.c new file mode 100644 index 0000000..b46351e --- /dev/null +++ b/modules/g7221/sdp.c @@ -0,0 +1,54 @@ +/** + * @file g7221/sdp.c H.264 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "g7221.h" + + +static uint32_t g7221_bitrate(const char *fmtp) +{ + struct pl pl, bitrate; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "bitrate", &bitrate)) + return pl_u32(&bitrate); + + return 0; +} + + +int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct g7221_aucodec *g7221 = arg; + (void)offer; + + if (!mb || !fmt || !g7221) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s bitrate=%u\r\n", + fmt->id, g7221->bitrate); +} + + +bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg) +{ + const struct g7221_aucodec *g7221 = arg; + (void)lfmtp; + + if (!g7221) + return false; + + if (g7221->bitrate != g7221_bitrate(rfmtp)) + return false; + + return true; +} diff --git a/modules/g726/g726.c b/modules/g726/g726.c new file mode 100644 index 0000000..a7ae8f8 --- /dev/null +++ b/modules/g726/g726.c @@ -0,0 +1,201 @@ +/** + * @file g726.c G.726 Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include <spandsp.h> + + +enum { MAX_PACKET = 100 }; + + +struct g726_aucodec { + struct aucodec ac; + int bitrate; +}; + +struct auenc_state { + g726_state_t st; +}; + +struct audec_state { + g726_state_t st; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + g726_release(&st->st); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + g726_release(&st->st); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct g726_aucodec *gac = (struct g726_aucodec *)ac; + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR, + G726_PACKING_LEFT)) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct g726_aucodec *gac = (struct g726_aucodec *)ac; + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR, + G726_PACKING_LEFT)) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < MAX_PACKET) + return ENOMEM; + + *len = g726_encode(&st->st, buf, sampv, (int)sampc); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + if (!sampv || !sampc || !buf) + return EINVAL; + + *sampc = g726_decode(&st->st, sampv, buf, (int)len); + + return 0; +} + + +static struct g726_aucodec g726[4] = { + { + { + LE_INIT, 0, "G726-40", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 40000 + }, + { + { + LE_INIT, 0, "G726-32", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 32000 + }, + { + { + LE_INIT, 0, "G726-24", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 24000 + }, + { + { + LE_INIT, 0, "G726-16", 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 16000 + } +}; + + +static int module_init(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(g726); i++) + aucodec_register((struct aucodec *)&g726[i]); + + return 0; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(g726); i++) + aucodec_unregister((struct aucodec *)&g726[i]); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(g726) = { + "g726", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/g726/module.mk b/modules/g726/module.mk new file mode 100644 index 0000000..c828ea0 --- /dev/null +++ b/modules/g726/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := g726 +$(MOD)_SRCS += g726.c +$(MOD)_LFLAGS += -lspandsp + +include mk/mod.mk diff --git a/modules/gsm/gsm.c b/modules/gsm/gsm.c new file mode 100644 index 0000000..798bae7 --- /dev/null +++ b/modules/gsm/gsm.c @@ -0,0 +1,171 @@ +/** + * @file gsm.c GSM Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <gsm.h> /* please report if you have problems finding this file */ +#include <re.h> +#include <baresip.h> + + +enum { + FRAME_SIZE = 160 +}; + + +struct auenc_state { + gsm enc; +}; + +struct audec_state { + gsm dec; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + gsm_destroy(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + gsm_destroy(st->dec); +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)ac; + (void)prm; + (void)fmtp; + + if (!aesp) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->enc = gsm_create(); + if (!st->enc) { + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)ac; + (void)fmtp; + + if (!adsp) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->dec = gsm_create(); + if (!st->dec) { + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + if (sampc != FRAME_SIZE) + return EPROTO; + if (*len < sizeof(gsm_frame)) + return ENOMEM; + + gsm_encode(st->enc, (gsm_signal *)sampv, buf); + + *len = sizeof(gsm_frame); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int ret; + + if (*sampc < FRAME_SIZE) + return ENOMEM; + if (len < sizeof(gsm_frame)) + return EBADMSG; + + ret = gsm_decode(st->dec, (gsm_byte *)buf, (gsm_signal *)sampv); + if (ret) + return EPROTO; + + *sampc = 160; + + return 0; +} + + +static struct aucodec ac_gsm = { + LE_INIT, "3", "GSM", 8000, 1, NULL, + encode_update, encode, decode_update, decode, NULL, NULL, NULL +}; + + +static int module_init(void) +{ + debug("gsm: GSM v%u.%u.%u\n", GSM_MAJOR, GSM_MINOR, GSM_PATCHLEVEL); + + aucodec_register(&ac_gsm); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&ac_gsm); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gsm) = { + "gsm", + "codec", + module_init, + module_close +}; diff --git a/modules/gsm/module.mk b/modules/gsm/module.mk new file mode 100644 index 0000000..48c8ff0 --- /dev/null +++ b/modules/gsm/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gsm +$(MOD)_SRCS += gsm.c +$(MOD)_LFLAGS += -L$(SYSROOT)/lib -lgsm +CFLAGS += -I$(SYSROOT)/include/gsm -I$(SYSROOT)/local/include + +include mk/mod.mk diff --git a/modules/gst/README b/modules/gst/README new file mode 100644 index 0000000..0076e9f --- /dev/null +++ b/modules/gst/README @@ -0,0 +1,34 @@ +Gstreamer notes +--------------- + + The module 'gst' is using the Gstreamer framework to play external + media and provide this as an internal audio source. + + +Debian NOTES + + The http handler 'neonhttpsrc' is by default not part of Debian Etch. + You must download the gst-plugins-bad package manually and build it. + + +Currently installed packages: + +$ dpkg --get-selections | grep gstrea +gstreamer0.10-alsa install +gstreamer0.10-doc install +gstreamer0.10-ffmpeg install +gstreamer0.10-plugins-bad install +gstreamer0.10-plugins-base install +gstreamer0.10-plugins-good install +gstreamer0.10-plugins-ugly install +gstreamer0.10-tools install +gstreamer0.10-x install +libgstreamer-plugins-base0.10-0 install +libgstreamer-plugins-base0.10-dev install +libgstreamer0.10-0 install +libgstreamer0.10-dev install + + +baresip configuration: + + module gst.so diff --git a/modules/gst/dump.c b/modules/gst/dump.c new file mode 100644 index 0000000..685d8f6 --- /dev/null +++ b/modules/gst/dump.c @@ -0,0 +1,65 @@ +/** + * @file dump.c Gstreamer playbin pipeline - dump utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst.h" + + +void gst_dump_props(GstElement *g) +{ + uint64_t u64; + gchar *strval; + double volume; + int n; + + debug("Gst properties:\n"); + + g_object_get(g, "delay", &u64, NULL); + debug(" delay: %lu ns\n", u64); + + g_object_get(g, "uri", &strval, NULL); + debug(" uri: %s\n", strval); + g_free(strval); + + g_object_get(g, "suburi", &strval, NULL); + debug(" suburi: %s\n", strval); + g_free(strval); + + g_object_get(g, "queue-size", &u64, NULL); + debug(" queue-size: %lu ns\n", u64); + + g_object_get(g, "queue-threshold", &u64, NULL); + debug(" queue-threshold: %lu ns\n", u64); + + g_object_get(g, "nstreams", &n, NULL); + debug(" nstreams: %d\n", n); + + g_object_get(g, "volume", &volume, NULL); + debug(" Volume: %f\n", volume); +} + + +void gst_dump_caps(const GstCaps *caps) +{ + GstStructure *s; + int rate, channels, width; + + if (!caps) + return; + + if (!gst_caps_get_size(caps)) + return; + + s = gst_caps_get_structure(caps, 0); + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + + info("gst: caps dump: %d Hz, %d channels, width=%d\n", + rate, channels, width); +} diff --git a/modules/gst/gst.c b/modules/gst/gst.c new file mode 100644 index 0000000..7af74f4 --- /dev/null +++ b/modules/gst/gst.c @@ -0,0 +1,449 @@ +/** + * @file gst.c Gstreamer playbin pipeline + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#define __USE_POSIX199309 +#include <time.h> +#include <pthread.h> +#include <gst/gst.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "gst.h" + + +#define DEBUG_MODULE "gst" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/** + * Defines the Gstreamer state + * + * <pre> + * ptime=variable ptime=20ms + * .-----------. N kHz .---------. N kHz + * | | 1-2 channels | | 1-2 channels + * | Gstreamer |--------------->|Packetize|-------------> [read handler] + * | | | | + * '-----------' '---------' + * + * </pre> + */ +struct ausrc_st { + struct ausrc *as; /**< Inheritance */ + pthread_t tid; /**< Thread ID */ + bool run; /**< Running flag */ + ausrc_read_h *rh; /**< Read handler */ + ausrc_error_h *errh; /**< Error handler */ + void *arg; /**< Handler argument */ + struct ausrc_prm prm; /**< Read parameters */ + struct aubuf *aubuf; /**< Packet buffer */ + uint32_t psize; /**< Packet size in bytes */ + + /* Gstreamer */ + char *uri; + GstElement *pipeline, *bin, *source, *capsfilt, *sink; + GMainLoop *loop; +}; + + +typedef struct _GstFakeSink GstFakeSink; +static char gst_uri[256] = "http://relay1.slayradio.org:8000/"; +static struct ausrc *ausrc; + + +static void *thread(void *arg) +{ + struct ausrc_st *st = arg; + + /* Now set to playing and iterate. */ + DEBUG_NOTICE("Setting pipeline to PLAYING\n"); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + + while (st->run) { + g_main_loop_run(st->loop); + } + + return NULL; +} + + +static gboolean bus_watch_handler(GstBus *bus, GstMessage *msg, gpointer data) +{ + struct ausrc_st *st = data; + GMainLoop *loop = st->loop; + GstTagList *tag_list; + gchar *title; + GError *err; + gchar *d; + + (void)bus; + + switch (GST_MESSAGE_TYPE(msg)) { + + case GST_MESSAGE_EOS: + DEBUG_NOTICE("End-of-stream\n"); + + /* XXX decrementing repeat count? */ + + /* Re-start stream */ + if (st->run) { + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + } + else { + g_main_loop_quit(loop); + } + break; + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &d); + + DEBUG_WARNING("Error: %d(%m) message=%s\n", err->code, + err->code, err->message); + DEBUG_WARNING("Debug: %s\n", d); + + g_free(d); + + /* Call error handler */ + if (st->errh) + st->errh(err->code, err->message, st->arg); + + g_error_free(err); + + st->run = false; + g_main_loop_quit(loop); + break; + + case GST_MESSAGE_TAG: + gst_message_parse_tag(msg, &tag_list); + + if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &title)) { + DEBUG_NOTICE("Title: %s\n", title); + g_free(title); + } + break; + + default: + break; + } + + return TRUE; +} + + +static void format_check(struct ausrc_st *st, GstStructure *s) +{ + int rate, channels, width; + gboolean sign; + + if (!st || !s) + return; + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + gst_structure_get_boolean(s, "signed", &sign); + + if ((int)st->prm.srate != rate) { + DEBUG_WARNING("expected %u Hz (got %u Hz)\n", st->prm.srate, + rate); + } + if (st->prm.ch != channels) { + DEBUG_WARNING("expected %d channels (got %d)\n", + st->prm.ch, channels); + } + if (16 != width) { + DEBUG_WARNING("expected 16-bit width (got %d)\n", width); + } + if (!sign) { + DEBUG_WARNING("expected signed 16-bit format\n"); + } +} + + +static void play_packet(struct ausrc_st *st) +{ + uint8_t buf[st->psize]; + + /* timed read from audio-buffer */ + if (aubuf_get(st->aubuf, st->prm.ptime, buf, sizeof(buf))) + return; + + /* call read handler */ + if (st->rh) + st->rh(buf, sizeof(buf), st->arg); +} + + +/* Expected format: 16-bit signed PCM */ +static void packet_handler(struct ausrc_st *st, GstBuffer *buffer) +{ + int err; + + if (!st->run) + return; + + /* NOTE: When streaming from files, the buffer will be filled up + * pretty quickly.. + */ + + err = aubuf_write(st->aubuf, GST_BUFFER_DATA(buffer), + GST_BUFFER_SIZE(buffer)); + if (err) { + DEBUG_WARNING("aubuf_write: %m\n", err); + } + + /* Empty buffer now */ + while (st->run) { + const struct timespec delay = {0, st->prm.ptime*1000000/2}; + + play_packet(st); + + if (aubuf_cur_size(st->aubuf) < st->psize) + break; + + (void)nanosleep(&delay, NULL); + } +} + + +static void handoff_handler(GstFakeSink *fakesink, GstBuffer *buffer, + GstPad *pad, gpointer user_data) +{ + struct ausrc_st *st = user_data; + + (void)fakesink; + (void)pad; + + format_check(st, gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0)); + + packet_handler(st, buffer); +} + + +static void set_caps(struct ausrc_st *st) +{ + GstCaps *caps; + + /* Set the capabilities we want */ + caps = gst_caps_new_simple("audio/x-raw-int", + "rate", G_TYPE_INT, st->prm.srate, + "channels", G_TYPE_INT, st->prm.ch, + "width", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN,true, + NULL); +#if 0 + gst_dump_caps(caps); +#endif + g_object_set(G_OBJECT(st->capsfilt), "caps", caps, NULL); +} + + +/** + * Set up the Gstreamer pipeline. The playbin element is used to decode + * all kinds of different formats. The capsfilter is used to deliver the + * audio in a fixed format (X Hz, 1-2 channels, 16 bit signed) + * + * The pipeline looks like this: + * + * <pre> + * .--------------. .------------------------------------------. + * | playbin | |mybin .------------. .------------. | + * |----. .----| |-----. | capsfilter | | fakesink | | + * |sink| |src |--->|ghost| |----. .---| |----. .---| | handoff + * |----' '----| |pad |-->|sink| |src|-->|sink| |src|--+--> handler + * | | |-----' '------------' '------------' | + * '--------------' '------------------------------------------' + * </pre> + * + * @param st Audio source state + * + * @return 0 if success, otherwise errorcode + */ +static int gst_setup(struct ausrc_st *st) +{ + GstBus *bus; + GstPad *pad; + + st->loop = g_main_loop_new(NULL, FALSE); + + st->pipeline = gst_pipeline_new("pipeline"); + if (!st->pipeline) { + DEBUG_WARNING("failed to create pipeline element\n"); + return ENOMEM; + } + + /********************* Player BIN **************************/ + + st->source = gst_element_factory_make("playbin", "source"); + if (!st->source) { + DEBUG_WARNING("failed to create playbin source element\n"); + return ENOMEM; + } + + /********************* My BIN **************************/ + + st->bin = gst_bin_new("mybin"); + + st->capsfilt = gst_element_factory_make("capsfilter", NULL); + if (!st->capsfilt) { + DEBUG_WARNING("failed to create capsfilter element\n"); + return ENOMEM; + } + + set_caps(st); + + st->sink = gst_element_factory_make("fakesink", "sink"); + if (!st->sink) { + DEBUG_WARNING("failed to create sink element\n"); + return ENOMEM; + } + + gst_bin_add_many(GST_BIN(st->bin), st->capsfilt, st->sink, NULL); + gst_element_link_many(st->capsfilt, st->sink, NULL); + + /* add ghostpad */ + pad = gst_element_get_pad(st->capsfilt, "sink"); + gst_element_add_pad(st->bin, gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + /* put all elements in a bin */ + gst_bin_add_many(GST_BIN(st->pipeline), st->source, NULL); + + /* Override audio-sink handoff handler */ + g_object_set(G_OBJECT(st->sink), "signal-handoffs", TRUE, NULL); + g_signal_connect(st->sink, "handoff", G_CALLBACK(handoff_handler), st); + g_object_set(G_OBJECT(st->source), "audio-sink", st->bin, NULL); + + /********************* Misc **************************/ + + /* Bus watch */ + bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline)); + gst_bus_add_watch(bus, bus_watch_handler, st); + gst_object_unref(bus); + + /* Set URI */ + g_object_set(G_OBJECT(st->source), "uri", st->uri, NULL); + + return 0; +} + + +static void gst_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + g_main_loop_quit(st->loop); + pthread_join(st->tid, NULL); + } + + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(st->pipeline)); + + mem_deref(st->uri); + mem_deref(st->aubuf); + + mem_deref(st->as); +} + + +static int gst_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + (void)ctx; + + if (!device) + device = gst_uri; + + if (!prm) + return EINVAL; + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), gst_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = str_dup(&st->uri, device); + if (err) + goto out; + + st->prm = *prm; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->psize = 2 * sampc; + + err = aubuf_alloc(&st->aubuf, st->psize, 0); + if (err) + goto out; + + err = gst_setup(st); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->tid, NULL, thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int mod_gst_init(void) +{ + gchar *s; + + gst_init(0, NULL); + + s = gst_version_string(); + + DEBUG_NOTICE("init: %s\n", s); + + g_free(s); + + return ausrc_register(&ausrc, "gst", gst_alloc); +} + + +static int mod_gst_close(void) +{ + gst_deinit(); + ausrc = mem_deref(ausrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst) = { + "gst", + "sound", + mod_gst_init, + mod_gst_close +}; diff --git a/modules/gst/gst.h b/modules/gst/gst.h new file mode 100644 index 0000000..9627188 --- /dev/null +++ b/modules/gst/gst.h @@ -0,0 +1,9 @@ +/** + * @file gst.h Gstreamer playbin pipeline -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +void gst_dump_props(GstElement *g); +void gst_dump_caps(const GstCaps *caps); diff --git a/modules/gst/module.mk b/modules/gst/module.mk new file mode 100644 index 0000000..9b95765 --- /dev/null +++ b/modules/gst/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst +$(MOD)_SRCS += gst.c dump.c +$(MOD)_LFLAGS += `pkg-config --libs gstreamer-0.10` +CFLAGS += `pkg-config --cflags gstreamer-0.10` + +include mk/mod.mk diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c new file mode 100644 index 0000000..12ef9dd --- /dev/null +++ b/modules/httpd/httpd.c @@ -0,0 +1,103 @@ +/** + * @file httpd.c Webserver UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +static struct http_sock *httpsock; + + +static int html_print_head(struct re_printf *pf, void *unused) +{ + (void)unused; + + return re_hprintf(pf, + "<html>\n" + "<head>\n" + "<title>Baresip v" BARESIP_VERSION "</title>\n" + "</head>\n"); +} + + +static int html_print_cmd(struct re_printf *pf, const struct http_msg *req) +{ + struct pl params; + + if (!pf || !req) + return EINVAL; + + if (pl_isset(&req->prm)) { + params.p = req->prm.p + 1; + params.l = req->prm.l - 1; + } + else { + params.p = "h"; + params.l = 1; + } + + return re_hprintf(pf, + "%H" + "<body>\n" + "<pre>\n" + "%H" + "</pre>\n" + "</body>\n" + "</html>\n", + html_print_head, NULL, + ui_input_pl, ¶ms); +} + + +static void http_req_handler(struct http_conn *conn, + const struct http_msg *msg, void *arg) +{ + (void)arg; + + if (0 == pl_strcasecmp(&msg->path, "/")) { + + http_creply(conn, 200, "OK", + "text/html;charset=UTF-8", + "%H", html_print_cmd, msg); + } + else { + http_ereply(conn, 404, "Not Found"); + } +} + + +static int module_init(void) +{ + struct sa laddr; + int err; + + if (conf_get_sa(conf_cur(), "http_listen", &laddr)) { + sa_set_str(&laddr, "0.0.0.0", 8000); + } + + err = http_listen(&httpsock, &laddr, http_req_handler, NULL); + if (err) + return err; + + info("httpd: listening on %J\n", &laddr); + + return 0; +} + + +static int module_close(void) +{ + httpsock = mem_deref(httpsock); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(httpd) = { + "httpd", + "application", + module_init, + module_close, +}; diff --git a/modules/httpd/module.mk b/modules/httpd/module.mk new file mode 100644 index 0000000..a29d2c5 --- /dev/null +++ b/modules/httpd/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := httpd +$(MOD)_SRCS += httpd.c + +include mk/mod.mk diff --git a/modules/ice/ice.c b/modules/ice/ice.c new file mode 100644 index 0000000..e8de4d7 --- /dev/null +++ b/modules/ice/ice.c @@ -0,0 +1,696 @@ +/** + * @file ice.c ICE Module + * + * Copyright (C) 2010 Creytiv.com + */ +#ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> +#include <SystemConfiguration/SCNetworkReachability.h> +#endif +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup ice ice + * + * Interactive Connectivity Establishment (ICE) for media NAT traversal + * + * This module enables ICE for NAT traversal. You can enable ICE + * in your accounts file with the parameter ;medianat=ice. The following + * options can be configured: + * + \verbatim + ice_turn {yes,no} # Enable TURN candidates + ice_debug {yes,no} # Enable ICE debugging/tracing + ice_nomination {regular,aggressive} # Regular or aggressive nomination + ice_mode {full,lite} # Full ICE-mode or ICE-lite + \endverbatim + */ + + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + struct sdp_session *sdp; + struct ice *ice; + char *user; + char *pass; + int mediac; + bool started; + bool send_reinvite; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + struct comp { + struct sa laddr; + void *sock; + } compv[2]; + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct icem *icem; + bool complete; +}; + + +static struct mnat *mnat; +static struct { + enum ice_mode mode; + enum ice_nomination nom; + bool turn; + bool debug; +} ice = { + ICE_MODE_FULL, + ICE_NOMINATION_REGULAR, + true, + false +}; + + +static void gather_handler(int err, uint16_t scode, const char *reason, + void *arg); + + +static bool is_cellular(const struct sa *laddr) +{ +#if TARGET_OS_IPHONE + SCNetworkReachabilityRef r; + SCNetworkReachabilityFlags flags = 0; + bool cell = false; + + r = SCNetworkReachabilityCreateWithAddressPair(NULL, + &laddr->u.sa, NULL); + if (!r) + return false; + + if (SCNetworkReachabilityGetFlags(r, &flags)) { + + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + cell = true; + } + + CFRelease(r); + + return cell; +#else + (void)laddr; + return false; +#endif +} + + +static void ice_printf(struct mnat_media *m, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + debug("%s: %v", m ? sdp_media_name(m->sdpm) : "ICE", fmt, &ap); + va_end(ap); +} + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); + mem_deref(sess->user); + mem_deref(sess->pass); + mem_deref(sess->ice); + mem_deref(sess->sdp); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + unsigned i; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->icem); + for (i=0; i<2; i++) + mem_deref(m->compv[i].sock); +} + + +static bool candidate_handler(struct le *le, void *arg) +{ + return 0 != sdp_media_set_lattr(arg, false, ice_attr_cand, "%H", + ice_cand_encode, le->data); +} + + +/** + * Update the local SDP attributes, this can be called multiple times + * when the state of the ICE machinery changes + */ +static int set_media_attributes(struct mnat_media *m) +{ + int err = 0; + + if (icem_mismatch(m->icem)) { + err = sdp_media_set_lattr(m->sdpm, true, + ice_attr_mismatch, NULL); + return err; + } + else { + sdp_media_del_lattr(m->sdpm, ice_attr_mismatch); + } + + /* Encode all my candidates */ + sdp_media_del_lattr(m->sdpm, ice_attr_cand); + if (list_apply(icem_lcandl(m->icem), true, candidate_handler, m->sdpm)) + return ENOMEM; + + if (ice_remotecands_avail(m->icem)) { + err |= sdp_media_set_lattr(m->sdpm, true, + ice_attr_remote_cand, "%H", + ice_remotecands_encode, m->icem); + } + + return err; +} + + +static bool if_handler(const char *ifname, const struct sa *sa, void *arg) +{ + struct mnat_media *m = arg; + uint16_t lprio; + unsigned i; + int err = 0; + + /* Skip loopback and link-local addresses */ + if (sa_is_loopback(sa) || sa_is_linklocal(sa)) + return false; + + lprio = is_cellular(sa) ? 0 : 10; + + ice_printf(m, "added interface: %s:%j (local prio %u)\n", + ifname, sa, lprio); + + for (i=0; i<2; i++) { + if (m->compv[i].sock) + err |= icem_cand_add(m->icem, i+1, lprio, ifname, sa); + } + + if (err) { + warning("ice: %s:%j: icem_cand_add: %m\n", ifname, sa, err); + } + + return false; +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + net_if_apply(if_handler, m); + + switch (ice.mode) { + + default: + case ICE_MODE_FULL: + if (ice.turn) { + err = icem_gather_relay(m->icem, &sess->srv, + sess->user, sess->pass); + } + else { + err = icem_gather_srflx(m->icem, &sess->srv); + } + break; + + case ICE_MODE_LITE: + gather_handler(0, 0, NULL, m); + break; + } + + return err; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + const char *usage; + int err; + + if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->sdp = mem_ref(ss); + sess->estabh = estabh; + sess->arg = arg; + + err = str_dup(&sess->user, user); + err |= str_dup(&sess->pass, pass); + if (err) + goto out; + + err = ice_alloc(&sess->ice, ice.mode, offerer); + if (err) + goto out; + + ice_conf(sess->ice)->nom = ice.nom; + ice_conf(sess->ice)->debug = ice.debug; + + if (ICE_MODE_LITE == ice.mode) { + err |= sdp_session_set_lattr(ss, true, + ice_attr_lite, NULL); + } + + err |= sdp_session_set_lattr(ss, true, + ice_attr_ufrag, ice_ufrag(sess->ice)); + err |= sdp_session_set_lattr(ss, true, + ice_attr_pwd, ice_pwd(sess->ice)); + if (err) + goto out; + + usage = ice.turn ? stun_usage_relay : stun_usage_binding; + + err = stun_server_discover(&sess->dnsq, dnsc, usage, stun_proto_udp, + af, srv, port, dns_handler, sess); + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static bool verify_peer_ice(struct mnat_sess *ms) +{ + struct le *le; + + for (le = ms->medial.head; le; le = le->next) { + struct mnat_media *m = le->data; + struct sa raddr[2]; + unsigned i; + + if (!sdp_media_has_media(m->sdpm)) { + info("ice: stream '%s' is disabled -- ignore\n", + sdp_media_name(m->sdpm)); + continue; + } + + raddr[0] = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr[1]); + + for (i=0; i<2; i++) { + if (m->compv[i].sock && + !icem_verify_support(m->icem, i+1, &raddr[i])) { + warning("ice: %s.%u: no remote candidates" + " found (address = %J)\n", + sdp_media_name(m->sdpm), + i+1, &raddr[i]); + return false; + } + } + } + + return true; +} + + +static bool refresh_comp_laddr(struct mnat_media *m, unsigned id, + struct comp *comp, const struct sa *laddr) +{ + bool changed = false; + + if (!m || !comp || !comp->sock || !laddr) + return false; + + if (!sa_cmp(&comp->laddr, laddr, SA_ALL)) { + changed = true; + + ice_printf(m, "comp%u setting local: %J\n", id, laddr); + } + + sa_cpy(&comp->laddr, laddr); + + if (id == 1) + sdp_media_set_laddr(m->sdpm, &comp->laddr); + else if (id == 2) + sdp_media_set_laddr_rtcp(m->sdpm, &comp->laddr); + + return changed; +} + + +/* + * Update SDP Media with local addresses + */ +static bool refresh_laddr(struct mnat_media *m, + const struct sa *laddr1, + const struct sa *laddr2) +{ + bool changed = false; + + changed |= refresh_comp_laddr(m, 1, &m->compv[0], laddr1); + changed |= refresh_comp_laddr(m, 2, &m->compv[1], laddr2); + + return changed; +} + + +static void gather_handler(int err, uint16_t scode, const char *reason, + void *arg) +{ + struct mnat_media *m = arg; + + if (err || scode) { + warning("ice: gather error: %m (%u %s)\n", + err, scode, reason); + } + else { + refresh_laddr(m, + icem_cand_default(m->icem, 1), + icem_cand_default(m->icem, 2)); + + info("ice: %s: Default local candidates: %J / %J\n", + sdp_media_name(m->sdpm), + &m->compv[0].laddr, &m->compv[1].laddr); + + (void)set_media_attributes(m); + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static void conncheck_handler(int err, bool update, void *arg) +{ + struct mnat_media *m = arg; + struct mnat_sess *sess = m->sess; + struct le *le; + + info("ice: %s: connectivity check is complete (update=%d)\n", + sdp_media_name(m->sdpm), update); + + ice_printf(m, "Dumping media state: %H\n", icem_debug, m->icem); + + if (err) { + warning("ice: connectivity check failed: %m\n", err); + } + else { + bool changed; + + m->complete = true; + + changed = refresh_laddr(m, + icem_selected_laddr(m->icem, 1), + icem_selected_laddr(m->icem, 2)); + if (changed) + sess->send_reinvite = true; + + (void)set_media_attributes(m); + + /* Check all conncheck flags */ + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *mx = le->data; + if (!mx->complete) + return; + } + } + + /* call estab-handler and send re-invite */ + if (sess->send_reinvite && update) { + + info("ice: %s: sending Re-INVITE with updated" + " default candidates\n", + sdp_media_name(m->sdpm)); + + sess->estabh(0, 0, NULL, sess->arg); + sess->send_reinvite = false; + } +} + + +static int ice_start(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + ice_printf(NULL, "ICE Start: %H", ice_debug, sess->ice); + + /* Update SDP media */ + if (sess->started) { + + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + icem_update(m->icem); + + refresh_laddr(m, + icem_selected_laddr(m->icem, 1), + icem_selected_laddr(m->icem, 2)); + + err |= set_media_attributes(m); + } + + return err; + } + + /* Clear all conncheck flags */ + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + if (sdp_media_has_media(m->sdpm)) { + m->complete = false; + + if (ice.mode == ICE_MODE_FULL) { + err = icem_conncheck_start(m->icem); + if (err) + return err; + } + } + else { + m->complete = true; + } + } + + sess->started = true; + + return 0; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + unsigned i; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->compv[0].sock = mem_ref(sock1); + m->compv[1].sock = mem_ref(sock2); + + err = icem_alloc(&m->icem, sess->ice, proto, 0, + gather_handler, conncheck_handler, m); + if (err) + goto out; + + icem_set_name(m->icem, sdp_media_name(sdpm)); + + for (i=0; i<2; i++) { + if (m->compv[i].sock) + err |= icem_comp_add(m->icem, i+1, m->compv[i].sock); + } + + if (sa_isset(&sess->srv, SA_ALL)) + err |= media_start(sess, m); + + out: + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static bool sdp_attr_handler(const char *name, const char *value, void *arg) +{ + struct mnat_sess *sess = arg; + return 0 != ice_sdp_decode(sess->ice, name, value); +} + + +static bool media_attr_handler(const char *name, const char *value, void *arg) +{ + struct mnat_media *m = arg; + return 0 != icem_sdp_decode(m->icem, name, value); +} + + +static int enable_turn_channels(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + for (le = sess->medial.head; le; le = le->next) { + + struct mnat_media *m = le->data; + struct sa raddr[2]; + unsigned i; + + err |= set_media_attributes(m); + + raddr[0] = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr[1]); + + for (i=0; i<2; i++) { + if (m->compv[i].sock && sa_isset(&raddr[i], SA_ALL)) + err |= icem_add_chan(m->icem, i+1, &raddr[i]); + } + } + + return err; +} + + +/** This can be called several times */ +static int update(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + /* SDP session */ + (void)sdp_session_rattr_apply(sess->sdp, NULL, sdp_attr_handler, sess); + + /* SDP medialines */ + for (le = sess->medial.head; le; le = le->next) { + struct mnat_media *m = le->data; + + sdp_media_rattr_apply(m->sdpm, NULL, media_attr_handler, m); + } + + /* 5.1. Verifying ICE Support */ + if (verify_peer_ice(sess)) { + err = ice_start(sess); + } + else if (ice.turn) { + info("ice: ICE not supported by peer, fallback to TURN\n"); + err = enable_turn_channels(sess); + } + else { + info("ice: ICE not supported by peer\n"); + + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + err |= set_media_attributes(m); + } + } + + return err; +} + + +static int module_init(void) +{ +#ifdef MODULE_CONF + struct pl pl; + + conf_get_bool(conf_cur(), "ice_turn", &ice.turn); + conf_get_bool(conf_cur(), "ice_debug", &ice.debug); + + if (!conf_get(conf_cur(), "ice_nomination", &pl)) { + if (0 == pl_strcasecmp(&pl, "regular")) + ice.nom = ICE_NOMINATION_REGULAR; + else if (0 == pl_strcasecmp(&pl, "aggressive")) + ice.nom = ICE_NOMINATION_AGGRESSIVE; + else { + warning("ice: unknown nomination: %r\n", &pl); + } + } + if (!conf_get(conf_cur(), "ice_mode", &pl)) { + if (!pl_strcasecmp(&pl, "full")) + ice.mode = ICE_MODE_FULL; + else if (!pl_strcasecmp(&pl, "lite")) + ice.mode = ICE_MODE_LITE; + else { + warning("ice: unknown mode: %r\n", &pl); + } + } +#endif + + return mnat_register(&mnat, "ice", "+sip.ice", + session_alloc, media_alloc, update); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(ice) = { + "ice", + "mnat", + module_init, + module_close, +}; diff --git a/modules/ice/module.mk b/modules/ice/module.mk new file mode 100644 index 0000000..9a8254f --- /dev/null +++ b/modules/ice/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := ice +$(MOD)_SRCS += ice.c + +include mk/mod.mk diff --git a/modules/ilbc/ilbc.c b/modules/ilbc/ilbc.c new file mode 100644 index 0000000..549be4d --- /dev/null +++ b/modules/ilbc/ilbc.c @@ -0,0 +1,354 @@ +/** + * @file ilbc.c Internet Low Bit Rate Codec (iLBC) audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <iLBC_define.h> +#include <iLBC_decode.h> +#include <iLBC_encode.h> + + +/* + * This module implements the iLBC audio codec as defined in: + * + * RFC 3951 Internet Low Bit Rate Codec (iLBC) + * RFC 3952 RTP Payload Format for iLBC Speech + * + * The iLBC source code is not included here, but can be downloaded from + * http://ilbcfreeware.org/ + * + * You can also use the source distributed by the Freeswitch project, + * see www.freeswitch.org, and then freeswitch/libs/codec/ilbc. + * Or you can look in the asterisk source code ... + * + * mode=20 15.20 kbit/s 160samp 38bytes + * mode=30 13.33 kbit/s 240samp 50bytes + */ + +enum { + DEFAULT_MODE = 20, /* 20ms or 30ms */ + USE_ENHANCER = 1 +}; + +struct auenc_state { + iLBC_Enc_Inst_t enc; + int mode; + uint32_t enc_bytes; +}; + +struct audec_state { + iLBC_Dec_Inst_t dec; + int mode; + uint32_t nsamp; + size_t dec_bytes; +}; + + +static char ilbc_fmtp[32]; + + +static void set_encoder_mode(struct auenc_state *st, int mode) +{ + if (st->mode == mode) + return; + + info("ilbc: set iLBC encoder mode %dms\n", mode); + + st->mode = mode; + + switch (mode) { + + case 20: + st->enc_bytes = NO_OF_BYTES_20MS; + break; + + case 30: + st->enc_bytes = NO_OF_BYTES_30MS; + break; + + default: + warning("ilbc: unknown encoder mode %d\n", mode); + return; + } + + st->enc_bytes = initEncode(&st->enc, mode); +} + + +static void set_decoder_mode(struct audec_state *st, int mode) +{ + if (st->mode == mode) + return; + + info("ilbc: set iLBC decoder mode %dms\n", mode); + + st->mode = mode; + + switch (mode) { + + case 20: + st->nsamp = BLOCKL_20MS; + break; + + case 30: + st->nsamp = BLOCKL_30MS; + break; + + default: + warning("ilbc: unknown decoder mode %d\n", mode); + return; + } + + st->nsamp = initDecode(&st->dec, mode, USE_ENHANCER); +} + + +static void encoder_fmtp_decode(struct auenc_state *st, const char *fmtp) +{ + struct pl mode; + + if (!fmtp) + return; + + if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode)) + return; + + set_encoder_mode(st, pl_u32(&mode)); +} + + +static void decoder_fmtp_decode(struct audec_state *st, const char *fmtp) +{ + struct pl mode; + + if (!fmtp) + return; + + if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode)) + return; + + set_decoder_mode(st, pl_u32(&mode)); +} + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + (void)st; +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + (void)st; +} + + +static int check_ptime(const struct auenc_param *prm) +{ + if (!prm) + return 0; + + switch (prm->ptime) { + + case 20: + case 30: + return 0; + + default: + warning("ilbc: invalid ptime %u ms\n", prm->ptime); + return EINVAL; + } +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + + if (!aesp || !ac || !prm) + return EINVAL; + if (check_ptime(prm)) + return EINVAL; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + set_encoder_mode(st, DEFAULT_MODE); + + if (str_isset(fmtp)) + encoder_fmtp_decode(st, fmtp); + + /* update parameters after SDP was decoded */ + if (prm) { + prm->ptime = st->mode; + } + + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + set_decoder_mode(st, DEFAULT_MODE); + + if (str_isset(fmtp)) + decoder_fmtp_decode(st, fmtp); + + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + float float_buf[sampc]; + uint32_t i; + + /* Make sure there is enough space */ + if (*len < st->enc_bytes) { + warning("ilbc: encode: buffer is too small (%u bytes)\n", + *len); + return ENOMEM; + } + + /* Convert from 16-bit samples to float */ + for (i=0; i<sampc; i++) { + const int16_t v = sampv[i]; + float_buf[i] = (float)v; + } + + iLBC_encode(buf, /* (o) encoded data bits iLBC */ + float_buf, /* (o) speech vector to encode */ + &st->enc); /* (i/o) the general encoder state */ + + *len = st->enc_bytes; + + return 0; +} + + +static int do_dec(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + float float_buf[st->nsamp]; + const int mode = len ? 1 : 0; + uint32_t i; + + /* Make sure there is enough space in the buffer */ + if (*sampc < st->nsamp) + return ENOMEM; + + iLBC_decode(float_buf, /* (o) decoded signal block */ + (uint8_t *)buf, /* (i) encoded signal bits */ + &st->dec, /* (i/o) the decoder state structure */ + mode); /* (i) 0: bad packet, PLC, 1: normal */ + + /* Convert from float to 16-bit samples */ + for (i=0; i<st->nsamp; i++) { + sampv[i] = (int16_t)float_buf[i]; + } + + *sampc = st->nsamp; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + /* Try to detect mode */ + if (st->dec_bytes != len) { + + st->dec_bytes = len; + + switch (st->dec_bytes) { + + case NO_OF_BYTES_20MS: + set_decoder_mode(st, 20); + break; + + case NO_OF_BYTES_30MS: + set_decoder_mode(st, 30); + break; + + default: + warning("ilbc: decode: expect %u, got %u\n", + st->dec_bytes, len); + return EINVAL; + } + } + + return do_dec(st, sampv, sampc, buf, len); +} + + +static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + return do_dec(st, sampv, sampc, NULL, 0); +} + + +static struct aucodec ilbc = { + LE_INIT, 0, "iLBC", 8000, 1, ilbc_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0 +}; + + +static int module_init(void) +{ + (void)re_snprintf(ilbc_fmtp, sizeof(ilbc_fmtp), + "mode=%d", DEFAULT_MODE); + + aucodec_register(&ilbc); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&ilbc); + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(ilbc) = { + "ilbc", + "codec", + module_init, + module_close +}; diff --git a/modules/ilbc/module.mk b/modules/ilbc/module.mk new file mode 100644 index 0000000..f549a67 --- /dev/null +++ b/modules/ilbc/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := ilbc +$(MOD)_SRCS += ilbc.c +$(MOD)_LFLAGS += -lilbc -lm + +include mk/mod.mk diff --git a/modules/isac/isac.c b/modules/isac/isac.c new file mode 100644 index 0000000..b1aa96e --- /dev/null +++ b/modules/isac/isac.c @@ -0,0 +1,223 @@ +/** + * @file isac.c iSAC audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "isac.h" + + +/* + * draft-ietf-avt-rtp-isac-04 + */ + + +struct auenc_state { + ISACStruct *inst; +}; + +struct audec_state { + ISACStruct *inst; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + if (st->inst) + WebRtcIsac_Free(st->inst); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + if (st->inst) + WebRtcIsac_Free(st->inst); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int err = 0; + (void)prm; + (void)fmtp; + + if (!aesp || !ac) + return EINVAL; + + if (*aesp) + return 0; + + st = mem_alloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + if (WebRtcIsac_Create(&st->inst) < 0) { + err = ENOMEM; + goto out; + } + + WebRtcIsac_EncoderInit(st->inst, 0); + + if (ac->srate == 32000) + WebRtcIsac_SetEncSampRate(st->inst, kIsacSuperWideband); + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + + if (*adsp) + return 0; + + st = mem_alloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + if (WebRtcIsac_Create(&st->inst) < 0) { + err = ENOMEM; + goto out; + } + + WebRtcIsac_DecoderInit(st->inst); + + if (ac->srate == 32000) + WebRtcIsac_SetDecSampRate(st->inst, kIsacSuperWideband); + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + WebRtc_Word16 len1, len2; + size_t l; + + if (!st || !buf || !len || !sampv || !sampc) + return EINVAL; + + /* 10 ms audio blocks */ + len1 = WebRtcIsac_Encode(st->inst, sampv, (void *)buf); + len2 = WebRtcIsac_Encode(st->inst, &sampv[sampc/2], (void *)buf); + + l = len1 ? len1 : len2; + + if (l > *len) + return ENOMEM; + + *len = l; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + WebRtc_Word16 speechType; + int n; + + if (!st || !sampv || !sampc || !buf || !len) + return EINVAL; + + n = WebRtcIsac_Decode(st->inst, (void *)buf, len, + (void *)sampv, &speechType); + if (n < 0) + return EPROTO; + + if ((size_t)n > *sampc) + return ENOMEM; + + *sampc = n; + + return 0; +} + + +static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + int n; + + if (!st || !sampv || !sampc) + return EINVAL; + + n = WebRtcIsac_DecodePlc(st->inst, (void *)sampv, 1); + if (n < 0) + return EPROTO; + + *sampc = n; + + return 0; +} + + +static struct aucodec isacv[] = { + { + LE_INIT, 0, "isac", 32000, 1, NULL, + encode_update, encode, decode_update, decode, plc, NULL, NULL + }, + { + LE_INIT, 0, "isac", 16000, 1, NULL, + encode_update, encode, decode_update, decode, plc, NULL, NULL + } +}; + + +static int module_init(void) +{ + unsigned i; + + for (i=0; i<ARRAY_SIZE(isacv); i++) + aucodec_register(&isacv[i]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(isacv); + + while (i--) + aucodec_unregister(&isacv[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(isac) = { + "isac", + "codec", + module_init, + module_close +}; diff --git a/modules/isac/module.mk b/modules/isac/module.mk new file mode 100644 index 0000000..64cafde --- /dev/null +++ b/modules/isac/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := isac +$(MOD)_SRCS += isac.c +$(MOD)_LFLAGS += -lisac + +include mk/mod.mk diff --git a/modules/l16/l16.c b/modules/l16/l16.c new file mode 100644 index 0000000..c506f4b --- /dev/null +++ b/modules/l16/l16.c @@ -0,0 +1,96 @@ +/** + * @file l16.c 16-bit linear codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +enum {NR_CODECS = 8}; + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int16_t *p = (void *)buf; + (void)st; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < sampc*2) + return ENOMEM; + + *len = sampc*2; + + while (sampc--) + *p++ = htons(*sampv++); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int16_t *p = (void *)buf; + (void)st; + + if (!buf || !len || !sampv) + return EINVAL; + + if (*sampc < len/2) + return ENOMEM; + + *sampc = len/2; + + len /= 2; + while (len--) + *sampv++ = ntohs(*p++); + + return 0; +} + + +/* See RFC 3551 */ +static struct aucodec l16v[NR_CODECS] = { + {LE_INIT, "10", "L16", 44100, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 32000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 16000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 8000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, "11", "L16", 44100, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 32000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 16000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, + {LE_INIT, 0, "L16", 8000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +}; + + +static int module_init(void) +{ + size_t i; + + for (i=0; i<NR_CODECS; i++) + aucodec_register(&l16v[i]); + + return 0; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<NR_CODECS; i++) + aucodec_unregister(&l16v[i]); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(l16) = { + "l16", + "codec", + module_init, + module_close +}; diff --git a/modules/l16/module.mk b/modules/l16/module.mk new file mode 100644 index 0000000..b870d18 --- /dev/null +++ b/modules/l16/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := l16 +$(MOD)_SRCS += l16.c + +include mk/mod.mk diff --git a/modules/mda/mda.c b/modules/mda/mda.c new file mode 100644 index 0000000..208facc --- /dev/null +++ b/modules/mda/mda.c @@ -0,0 +1,40 @@ +/** + * @file mda.c Symbian MDA audio driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "mda.h" + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + int err; + + err = auplay_register(&auplay, "mda", mda_player_alloc); + err |= ausrc_register(&ausrc, "mda", mda_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(mda) = { + "mda", + "audio", + module_init, + module_close, +}; diff --git a/modules/mda/mda.h b/modules/mda/mda.h new file mode 100644 index 0000000..eee41d9 --- /dev/null +++ b/modules/mda/mda.h @@ -0,0 +1,17 @@ +/** + * @file mda.h Symbian MDA audio driver -- Internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +int mda_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int mda_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); + +int convert_srate(uint32_t srate); +int convert_channels(uint8_t ch); diff --git a/modules/mda/player.cpp b/modules/mda/player.cpp new file mode 100644 index 0000000..4cfa18c --- /dev/null +++ b/modules/mda/player.cpp @@ -0,0 +1,176 @@ +/** + * @file player.cpp Symbian MDA audio driver -- player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mdaaudiooutputstream.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" + +#define DEBUG_MODULE "player" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> +} + + +enum {VOLUME = 100}; + +class mda_player; +struct auplay_st { + struct auplay *ap; /* inheritance */ + mda_player *mda; + auplay_write_h *wh; + void *arg; +}; + + +class mda_player : public MMdaAudioOutputStreamCallback, public CBase +{ +public: + mda_player(struct auplay_st *st, struct auplay_prm *prm); + ~mda_player(); + void play(); + + /* from MMdaAudioOutputStreamCallback */ + virtual void MaoscOpenComplete(TInt aError); + virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaoscPlayComplete(TInt aError); + +private: + CMdaAudioOutputStream *iOutput; + TMdaAudioDataSettings iSettings; + TBool iIsReady; + TBuf8<320> iBuf; + struct auplay_st *state; +}; + + +mda_player::mda_player(struct auplay_st *st, struct auplay_prm *prm) + :iIsReady(EFalse) +{ + state = st; + + iBuf.FillZ(320); + + iSettings.iSampleRate = convert_srate(prm->srate); + iSettings.iChannels = convert_channels(prm->ch); + iSettings.iVolume = VOLUME; + + iOutput = CMdaAudioOutputStream::NewL(*this); + iOutput->Open(&iSettings); +} + + +mda_player::~mda_player() +{ + if (iOutput) { + iOutput->Stop(); + delete iOutput; + } +} + + +void mda_player::play() +{ + /* call write handler here */ + state->wh((uint8_t *)&iBuf[0], iBuf.Length(), state->arg); + + TRAPD(ret, iOutput->WriteL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("WriteL left with %d\n", ret); + } +} + + +void mda_player::MaoscOpenComplete(TInt aError) +{ + if (KErrNone != aError) { + iIsReady = EFalse; + DEBUG_WARNING("mda player error: %d\n", aError); + return; + } + + iOutput->SetAudioPropertiesL(iSettings.iSampleRate, + iSettings.iChannels); + iOutput->SetPriority(EMdaPriorityNormal, + EMdaPriorityPreferenceTime); + + iIsReady = ETrue; + + play(); +} + + +/* + * Note: In reality, this function is called approx. 1 millisecond after the + * last block was played, hence we have to generate buffer N+1 while buffer N + * is playing. + */ +void mda_player::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer) +{ + (void)aBuffer; + + if (KErrNone != aError && KErrCancel != aError) { + DEBUG_WARNING("MaoscBufferCopied [aError=%d]\n", aError); + } + if (aError == KErrAbort) { + DEBUG_NOTICE("player aborted\n"); + return; + } + + play(); +} + + +void mda_player::MaoscPlayComplete(TInt aError) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaoscPlayComplete [aError=%d]\n", aError); + } +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = (struct auplay_st *)arg; + + delete st->mda; + + mem_deref(st->ap); +} + + +int mda_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int err = 0; + + (void)device; + + st = (struct auplay_st *)mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = (struct auplay *)mem_ref(ap); + st->wh = wh; + st->arg = arg; + + st->mda = new mda_player(st, prm); + if (!st->mda) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/mda/recorder.cpp b/modules/mda/recorder.cpp new file mode 100644 index 0000000..6c358dc --- /dev/null +++ b/modules/mda/recorder.cpp @@ -0,0 +1,170 @@ +/** + * @file recorder.cpp Symbian MDA audio driver -- recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mdaaudioinputstream.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" + +#define DEBUG_MODULE "recorder" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> +} + + +enum {VOLUME = 100}; + +class mda_recorder; +struct ausrc_st { + struct ausrc *as; /* inheritance */ + mda_recorder *mda; + ausrc_read_h *rh; + void *arg; +}; + + +class mda_recorder : public MMdaAudioInputStreamCallback, public CBase +{ +public: + mda_recorder(struct ausrc_st *st, struct ausrc_prm *prm); + ~mda_recorder(); + + /* from MMdaAudioInputStreamCallback */ + virtual void MaiscOpenComplete(TInt aError); + virtual void MaiscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaiscRecordComplete(TInt aError); + +private: + CMdaAudioInputStream *iInput; + TMdaAudioDataSettings iSettings; + TBool iIsReady; + TBuf8<320> iBuf; + struct ausrc_st *state; +}; + + +mda_recorder::mda_recorder(struct ausrc_st *st, struct ausrc_prm *prm) + :iIsReady(EFalse) +{ + state = st; + + iInput = CMdaAudioInputStream::NewL(*this); + + iSettings.iSampleRate = convert_srate(prm->srate); + iSettings.iChannels = convert_channels(prm->ch); + iSettings.iVolume = VOLUME; + + iInput->Open(&iSettings); +} + + +mda_recorder::~mda_recorder() +{ + if (iInput) { + iInput->Stop(); + delete iInput; + } +} + + +void mda_recorder::MaiscOpenComplete(TInt aError) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaiscOpenComplete %d\n", aError); + return; + } + + iInput->SetGain(iInput->MaxGain()); + iInput->SetAudioPropertiesL(iSettings.iSampleRate, + iSettings.iChannels); + iInput->SetPriority(EMdaPriorityNormal, + EMdaPriorityPreferenceTime); + + TRAPD(ret, iInput->ReadL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("ReadL left with %d\n", ret); + } +} + + +void mda_recorder::MaiscBufferCopied(TInt aError, const TDesC8& aBuffer) +{ + if (KErrNone != aError) { + DEBUG_WARNING("MaiscBufferCopied: error=%d %d bytes\n", + aError, aBuffer.Length()); + return; + } + + state->rh(aBuffer.Ptr(), aBuffer.Length(), state->arg); + + iBuf.Zero(); + TRAPD(ret, iInput->ReadL(iBuf)); + if (KErrNone != ret) { + DEBUG_WARNING("ReadL left with %d\n", ret); + } +} + + +void mda_recorder::MaiscRecordComplete(TInt aError) +{ + DEBUG_NOTICE("MaiscRecordComplete: error=%d\n", aError); + +#if 0 + if (KErrOverflow == aError) { + + /* re-open input stream */ + iInput->Stop(); + iInput->Open(&iSettings); + } +#endif +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = (struct ausrc_st *)arg; + + delete st->mda; + + mem_deref(st->as); +} + + +int mda_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err = 0; + + (void)ctx; + (void)device; + (void)errh; + + st = (struct ausrc_st *)mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = (struct ausrc *)mem_ref(as); + st->rh = rh; + st->arg = arg; + + st->mda = new mda_recorder(st, prm); + if (!st->mda) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/mda/util.cpp b/modules/mda/util.cpp new file mode 100644 index 0000000..b879c7f --- /dev/null +++ b/modules/mda/util.cpp @@ -0,0 +1,39 @@ +/** + * @file util.cpp Symbian MDA audio driver -- utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <e32def.h> +#include <e32std.h> +#include <mda/common/audio.h> + +extern "C" { +#include <re.h> +#include <baresip.h> +#include "mda.h" +} + + +int convert_srate(uint32_t srate) +{ + switch (srate) { + + case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz; + case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz; + case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz; + case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz; + case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz; + default: return -1; + } +} + + +int convert_channels(uint8_t ch) +{ + switch (ch) { + + case 1: return TMdaAudioDataSettings::EChannelsMono; + case 2: return TMdaAudioDataSettings::EChannelsStereo; + default: return -1; + } +} diff --git a/modules/menu/menu.c b/modules/menu/menu.c new file mode 100644 index 0000000..583fb69 --- /dev/null +++ b/modules/menu/menu.c @@ -0,0 +1,618 @@ +/** + * @file menu.c Interactive menu + * + * Copyright (C) 2010 Creytiv.com + */ +#include <time.h> +#include <re.h> +#include <baresip.h> + + +/** Defines the status modes */ +enum statmode { + STATMODE_CALL = 0, + STATMODE_OFF, +}; + + +static uint64_t start_ticks; /**< Ticks when app started */ +static time_t start_time; /**< Start time of application */ +static struct tmr tmr_alert; /**< Incoming call alert timer */ +static struct tmr tmr_stat; /**< Call status timer */ +static enum statmode statmode; /**< Status mode */ +static struct mbuf *dialbuf; /**< Buffer for dialled number */ +static struct le *le_cur; /**< Current User-Agent (struct ua) */ + + +static void menu_set_incall(bool incall); +static void update_callstatus(void); + + +static void check_registrations(void) +{ + static bool ual_ready = false; + struct le *le; + uint32_t n; + + if (ual_ready) + return; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + + if (!ua_isregistered(ua)) + return; + } + + n = list_count(uag_list()); + + /* We are ready */ + (void)re_printf("\x1b[32mAll %u useragent%s registered successfully!" + " (%u ms)\x1b[;m\n", + n, n==1 ? "" : "s", + (uint32_t)(tmr_jiffies() - start_ticks)); + + ual_ready = true; +} + + +/** + * Return the current User-Agent in focus + * + * @return Current User-Agent + */ +static struct ua *uag_cur(void) +{ + if (list_isempty(uag_list())) + return NULL; + + if (!le_cur) + le_cur = list_head(uag_list()); + + return list_ledata(le_cur); +} + + +/* Return TRUE if there are any active calls for any UAs */ +static bool have_active_calls(void) +{ + struct le *le; + + for (le = list_head(uag_list()); le; le = le->next) { + + struct ua *ua = le->data; + + if (ua_call(ua)) + return true; + } + + return false; +} + + +static int print_system_info(struct re_printf *pf, void *arg) +{ + uint32_t uptime; + int err = 0; + + (void)arg; + + uptime = (uint32_t)((long long)(tmr_jiffies() - start_ticks)/1000); + + err |= re_hprintf(pf, "\n--- System info: ---\n"); + + err |= re_hprintf(pf, " Machine: %s/%s\n", sys_arch_get(), + sys_os_get()); + err |= re_hprintf(pf, " Version: %s (libre v%s)\n", + BARESIP_VERSION, sys_libre_version_get()); + err |= re_hprintf(pf, " Build: %H\n", sys_build_get, NULL); + err |= re_hprintf(pf, " Kernel: %H\n", sys_kernel_get, NULL); + err |= re_hprintf(pf, " Uptime: %H\n", fmt_human_time, &uptime); + err |= re_hprintf(pf, " Started: %s", ctime(&start_time)); + +#ifdef __VERSION__ + err |= re_hprintf(pf, " Compiler: %s\n", __VERSION__); +#endif + + return err; +} + + +/** + * Print the SIP Registration for all User-Agents + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +static int ua_print_reg_status(struct re_printf *pf, void *unused) +{ + struct le *le; + int err; + + (void)unused; + + err = re_hprintf(pf, "\n--- Useragents: %u ---\n", + list_count(uag_list())); + + for (le = list_head(uag_list()); le && !err; le = le->next) { + const struct ua *ua = le->data; + + err = re_hprintf(pf, "%s ", ua == uag_cur() ? ">" : " "); + err |= ua_print_status(pf, ua); + } + + err |= re_hprintf(pf, "\n"); + + return err; +} + + +/** + * Print the current SIP Call status for the current User-Agent + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +static int ua_print_call_status(struct re_printf *pf, void *unused) +{ + struct call *call; + int err; + + (void)unused; + + call = ua_call(uag_cur()); + if (call) { + err = re_hprintf(pf, "\n%H\n", call_debug, call); + } + else { + err = re_hprintf(pf, "\n(no active calls)\n"); + } + + return err; +} + + +static int dial_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err = 0; + + (void)pf; + + if (str_isset(carg->prm)) { + + mbuf_rewind(dialbuf); + (void)mbuf_write_str(dialbuf, carg->prm); + + err = ua_connect(uag_cur(), NULL, NULL, + carg->prm, NULL, VIDMODE_ON); + } + else if (dialbuf->end > 0) { + + char *uri; + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return err; + + err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON); + + mem_deref(uri); + } + + if (err) { + warning("menu: ua_connect failed: %m\n", err); + } + + return err; +} + + +static int cmd_answer(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + ua_answer(uag_cur(), NULL); + + return 0; +} + + +static int cmd_hangup(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + ua_hangup(uag_cur(), NULL, 0, NULL); + + /* note: must be called after ua_hangup() */ + menu_set_incall(have_active_calls()); + + return 0; +} + + +static int cmd_ua_next(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + if (!le_cur) + le_cur = list_head(uag_list()); + + le_cur = le_cur->next ? le_cur->next : list_head(uag_list()); + + (void)re_fprintf(stderr, "ua: %s\n", ua_aor(list_ledata(le_cur))); + + uag_current_set(list_ledata(le_cur)); + + update_callstatus(); + + return 0; +} + + +static int cmd_ua_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_debug(pf, uag_cur()); +} + + +static int cmd_print_calls(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_print_calls(pf, uag_cur()); +} + + +static int cmd_config_print(struct re_printf *pf, void *unused) +{ + (void)unused; + return config_print(pf, conf_config()); +} + + +static const struct cmd cmdv[] = { + {'M', 0, "Main loop debug", re_debug }, + {'\n', 0, "Accept incoming call", cmd_answer }, + {'b', 0, "Hangup call", cmd_hangup }, + {'c', 0, "Call status", ua_print_call_status }, + {'d', CMD_PRM, "Dial", dial_handler }, + {'h', 0, "Help menu", cmd_print }, + {'i', 0, "SIP debug", ua_print_sip_status }, + {'l', 0, "List active calls", cmd_print_calls }, + {'m', 0, "Module debug", mod_debug }, + {'n', 0, "Network debug", net_debug }, + {'r', 0, "Registration info", ua_print_reg_status }, + {'s', 0, "System info", print_system_info }, + {'t', 0, "Timer debug", tmr_status }, + {'u', 0, "UA debug", cmd_ua_debug }, + {'y', 0, "Memory status", mem_status }, + {0x1b, 0, "Hangup call", cmd_hangup }, + {' ', 0, "Toggle UAs", cmd_ua_next }, + {'g', 0, "Print configuration", cmd_config_print }, + + {'#', CMD_PRM, NULL, dial_handler }, + {'*', CMD_PRM, NULL, dial_handler }, + {'0', CMD_PRM, NULL, dial_handler }, + {'1', CMD_PRM, NULL, dial_handler }, + {'2', CMD_PRM, NULL, dial_handler }, + {'3', CMD_PRM, NULL, dial_handler }, + {'4', CMD_PRM, NULL, dial_handler }, + {'5', CMD_PRM, NULL, dial_handler }, + {'6', CMD_PRM, NULL, dial_handler }, + {'7', CMD_PRM, NULL, dial_handler }, + {'8', CMD_PRM, NULL, dial_handler }, + {'9', CMD_PRM, NULL, dial_handler }, +}; + + +static int call_audio_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return audio_debug(pf, call_audio(ua_call(uag_cur()))); +} + + +static int call_audioenc_cycle(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + audio_encoder_cycle(call_audio(ua_call(uag_cur()))); + return 0; +} + + +static int call_reinvite(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + return call_modify(ua_call(uag_cur())); +} + + +static int call_mute(struct re_printf *pf, void *unused) +{ + static bool muted = false; + (void)unused; + + muted = !muted; + (void)re_hprintf(pf, "\ncall %smuted\n", muted ? "" : "un-"); + audio_mute(call_audio(ua_call(uag_cur())), muted); + + return 0; +} + + +static int call_xfer(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + static bool xfer_inprogress; + + if (!xfer_inprogress && !carg->complete) { + statmode = STATMODE_OFF; + re_hprintf(pf, "\rPlease enter transfer target SIP uri:\n"); + } + + xfer_inprogress = true; + + if (carg->complete) { + statmode = STATMODE_CALL; + xfer_inprogress = false; + return call_transfer(ua_call(uag_cur()), carg->prm); + } + + return 0; +} + + +static int call_holdresume(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + (void)pf; + + return call_hold(ua_call(uag_cur()), 'x' == carg->key); +} + + +#ifdef USE_VIDEO +static int call_videoenc_cycle(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + video_encoder_cycle(call_video(ua_call(uag_cur()))); + return 0; +} + + +static int call_video_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return video_debug(pf, call_video(ua_call(uag_cur()))); +} +#endif + + +static int digit_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct call *call; + int err = 0; + + (void)pf; + + call = ua_call(uag_cur()); + if (call) + err = call_send_digit(call, carg->key); + + return err; +} + + +static int toggle_statmode(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + if (statmode == STATMODE_OFF) + statmode = STATMODE_CALL; + else + statmode = STATMODE_OFF; + + return 0; +} + + +static const struct cmd callcmdv[] = { + {'I', 0, "Send re-INVITE", call_reinvite }, + {'X', 0, "Call resume", call_holdresume }, + {'a', 0, "Audio stream", call_audio_debug }, + {'e', 0, "Cycle audio encoder", call_audioenc_cycle }, + {'m', 0, "Call mute/un-mute", call_mute }, + {'r', CMD_IPRM,"Transfer call", call_xfer }, + {'x', 0, "Call hold", call_holdresume }, + +#ifdef USE_VIDEO + {'E', 0, "Cycle video encoder", call_videoenc_cycle }, + {'v', 0, "Video stream", call_video_debug }, +#endif + + {'#', 0, NULL, digit_handler }, + {'*', 0, NULL, digit_handler }, + {'0', 0, NULL, digit_handler }, + {'1', 0, NULL, digit_handler }, + {'2', 0, NULL, digit_handler }, + {'3', 0, NULL, digit_handler }, + {'4', 0, NULL, digit_handler }, + {'5', 0, NULL, digit_handler }, + {'6', 0, NULL, digit_handler }, + {'7', 0, NULL, digit_handler }, + {'8', 0, NULL, digit_handler }, + {'9', 0, NULL, digit_handler }, + {0x00, 0, NULL, digit_handler }, + + {'S', 0, "Statusmode toggle", toggle_statmode }, +}; + + +static void menu_set_incall(bool incall) +{ + /* Dynamic menus */ + if (incall) { + (void)cmd_register(callcmdv, ARRAY_SIZE(callcmdv)); + } + else { + cmd_unregister(callcmdv); + } +} + + +static void tmrstat_handler(void *arg) +{ + struct call *call; + (void)arg; + + /* the UI will only show the current active call */ + call = ua_call(uag_cur()); + if (!call) + return; + + tmr_start(&tmr_stat, 100, tmrstat_handler, 0); + + if (STATMODE_OFF != statmode) { + (void)re_fprintf(stderr, "%H\r", call_status, call); + } +} + + +static void update_callstatus(void) +{ + /* if there are any active calls, enable the call status view */ + if (have_active_calls()) + tmr_start(&tmr_stat, 100, tmrstat_handler, 0); + else + tmr_cancel(&tmr_stat); +} + + +static void alert_start(void *arg) +{ + (void)arg; + + ui_output("\033[10;1000]\033[11;1000]\a"); + + tmr_start(&tmr_alert, 1000, alert_start, NULL); +} + + +static void alert_stop(void) +{ + if (tmr_isrunning(&tmr_alert)) + ui_output("\r"); + + tmr_cancel(&tmr_alert); +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + (void)call; + (void)prm; + (void)arg; + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + info("%s: Incoming call from: %s %s -" + " (press ENTER to accept)\n", + ua_aor(ua), call_peername(call), call_peeruri(call)); + alert_start(0); + break; + + case UA_EVENT_CALL_ESTABLISHED: + case UA_EVENT_CALL_CLOSED: + alert_stop(); + break; + + case UA_EVENT_REGISTER_OK: + check_registrations(); + break; + + case UA_EVENT_UNREGISTERING: + return; + + default: + break; + } + + menu_set_incall(have_active_calls()); + update_callstatus(); +} + + +static void message_handler(const struct pl *peer, const struct pl *ctype, + struct mbuf *body, void *arg) +{ + (void)ctype; + (void)arg; + + (void)re_fprintf(stderr, "\r%r: \"%b\"\n", peer, + mbuf_buf(body), mbuf_get_left(body)); + + (void)play_file(NULL, "message.wav", 0); +} + + +static int module_init(void) +{ + int err; + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + start_ticks = tmr_jiffies(); + (void)time(&start_time); + tmr_init(&tmr_alert); + statmode = STATMODE_CALL; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + err |= uag_event_register(ua_event_handler, NULL); + + err |= message_init(message_handler, NULL); + + return err; +} + + +static int module_close(void) +{ + message_close(); + uag_event_unregister(ua_event_handler); + cmd_unregister(cmdv); + + menu_set_incall(false); + tmr_cancel(&tmr_alert); + tmr_cancel(&tmr_stat); + dialbuf = mem_deref(dialbuf); + + le_cur = NULL; + + return 0; +} + + +const struct mod_export DECL_EXPORTS(menu) = { + "menu", + "application", + module_init, + module_close +}; diff --git a/modules/menu/module.mk b/modules/menu/module.mk new file mode 100644 index 0000000..d727f4b --- /dev/null +++ b/modules/menu/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := menu +$(MOD)_SRCS += menu.c + +include mk/mod.mk diff --git a/modules/mwi/module.mk b/modules/mwi/module.mk new file mode 100644 index 0000000..f6d1bde --- /dev/null +++ b/modules/mwi/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := mwi +$(MOD)_SRCS += mwi.c + +include mk/mod.mk diff --git a/modules/mwi/mwi.c b/modules/mwi/mwi.c new file mode 100644 index 0000000..21aca08 --- /dev/null +++ b/modules/mwi/mwi.c @@ -0,0 +1,141 @@ +/** + * @file mwi.c Message Waiting Indication (RFC 3842) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +struct mwi { + struct le le; + struct sipsub *sub; + struct ua *ua; +}; + +static struct tmr tmr; +static struct list mwil; + + +static void destructor(void *arg) +{ + struct mwi *mwi = arg; + + list_unlink(&mwi->le); + mem_deref(mwi->sub); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + struct account *acc = arg; + return account_auth(acc, username, password, realm); +} + + +static void notify_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct mwi *mwi = arg; + + if (mbuf_get_left(msg->mb)) { + re_printf("----- MWI for %s -----\n", ua_aor(mwi->ua)); + re_printf("%b\n", mbuf_buf(msg->mb), mbuf_get_left(msg->mb)); + } + + (void)sip_treply(NULL, sip, msg, 200, "OK"); +} + + +static void close_handler(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, + void *arg) +{ + struct mwi *mwi = arg; + (void)substate; + + info("mwi: subscription for %s closed: %s (%u %r)\n", + ua_aor(mwi->ua), + err ? strerror(err) : "", + err ? 0 : msg->scode, + err ? 0 : &msg->reason); + + mem_deref(mwi); +} + + +static int mwi_subscribe(struct ua *ua) +{ + const char *routev[1]; + struct mwi *mwi; + int err; + + mwi = mem_zalloc(sizeof(*mwi), destructor); + if (!mwi) + return ENOMEM; + + list_append(&mwil, &mwi->le, mwi); + mwi->ua = ua; + + routev[0] = ua_outbound(ua); + + info("mwi: subscribing to messages for %s\n", ua_aor(ua)); + + err = sipevent_subscribe(&mwi->sub, uag_sipevent_sock(), ua_aor(ua), + NULL, ua_aor(ua), "message-summary", NULL, + 600, ua_cuser(ua), + routev, routev[0] ? 1 : 0, + auth_handler, ua_prm(ua), true, NULL, + notify_handler, close_handler, mwi, + "Accept:" + " application/simple-message-summary\r\n"); + if (err) { + warning("mwi: subscribe ERROR: %m\n", err); + } + + if (err) + mem_deref(mwi); + + return err; +} + + +static void tmr_handler(void *arg) +{ + struct le *le; + + (void)arg; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + mwi_subscribe(ua); + } +} + + +static int module_init(void) +{ + list_init(&mwil); + tmr_start(&tmr, 10, tmr_handler, 0); + + return 0; +} + + +static int module_close(void) +{ + tmr_cancel(&tmr); + list_flush(&mwil); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(mwi) = { + "mwi", + "application", + module_init, + module_close, +}; diff --git a/modules/natbd/module.mk b/modules/natbd/module.mk new file mode 100644 index 0000000..d6da3f9 --- /dev/null +++ b/modules/natbd/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := natbd +$(MOD)_SRCS += natbd.c + +include mk/mod.mk diff --git a/modules/natbd/natbd.c b/modules/natbd/natbd.c new file mode 100644 index 0000000..6711961 --- /dev/null +++ b/modules/natbd/natbd.c @@ -0,0 +1,508 @@ +/** + * @file natbd.c NAT Behavior Discovery Module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "natbd" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/* + * NAT Behavior Discovery Using STUN (RFC 5780) + * + * This module is only for diagnostics purposes and does not affect + * the main SIP client. It uses the NATBD api in libre to detect the + * NAT Behaviour, by sending STUN packets to a STUN server. Both + * protocols UDP and TCP are supported. + */ + + +struct natbd { + struct nat_hairpinning *nh; + struct nat_filtering *nf; + struct nat_lifetime *nl; + struct nat_mapping *nm; + struct nat_genalg *ga; + struct stun_dns *dns; + struct sa stun_srv; + struct tmr tmr; + char host[256]; + uint16_t port; + uint32_t interval; + bool terminated; + int proto; + int res_hp; + enum nat_type res_nm; + enum nat_type res_nf; + struct nat_lifetime_interval res_nl; + uint32_t n_nl; + int status_ga; +}; + +static struct natbd *natbdv[2]; + + +static const char *hairpinning_str(int res_hp) +{ + switch (res_hp) { + + case -1: return "Unknown"; + case 0: return "Not Supported"; + default: return "Supported"; + } +} + + +static const char *genalg_str(int status) +{ + switch (status) { + + case -1: return "Not Detected"; + case 0: return "Unknown"; + case 1: return "Detected"; + default: return "???"; + } +} + + +static int natbd_status(struct re_printf *pf, void *arg) +{ + const struct natbd *natbd = arg; + int err; + + if (!pf || !natbd) + return 0; + + err = re_hprintf(pf, "NAT Binding Discovery (using %s:%J)\n", + net_proto2name(natbd->proto), + &natbd->stun_srv); + err |= re_hprintf(pf, " Hairpinning: %s\n", + hairpinning_str(natbd->res_hp)); + err |= re_hprintf(pf, " Mapping: %s\n", + nat_type_str(natbd->res_nm)); + if (natbd->proto == IPPROTO_UDP) { + err |= re_hprintf(pf, " Filtering: %s\n", + nat_type_str(natbd->res_nf)); + err |= re_hprintf(pf, " Lifetime: min=%u cur=%u max=%u" + " (%u probes)\n", natbd->res_nl.min, + natbd->res_nl.cur, natbd->res_nl.max, + natbd->n_nl); + } + err |= re_hprintf(pf, " Generic ALG: %s\n", + genalg_str(natbd->status_ga)); + + return err; +} + + +static void nat_hairpinning_handler(int err, bool supported, void *arg) +{ + struct natbd *natbd = arg; + const int res_hp = (0 == err) ? supported : -1; + + if (natbd->terminated) + return; + + if (res_hp != natbd->res_hp) { + info("NAT Hairpinning %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + hairpinning_str(natbd->res_hp), + hairpinning_str(res_hp)); + } + + natbd->res_hp = res_hp; + + natbd->nh = mem_deref(natbd->nh); +} + + +static void nat_mapping_handler(int err, enum nat_type type, void *arg) +{ + struct natbd *natbd = arg; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("NAT mapping failed (%m)\n", err); + goto out; + } + + if (type != natbd->res_nm) { + info("NAT Mapping %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + nat_type_str(natbd->res_nm), + nat_type_str(type)); + } + + natbd->res_nm = type; + + out: + natbd->nm = mem_deref(natbd->nm); +} + + +static void nat_filtering_handler(int err, enum nat_type type, void *arg) +{ + struct natbd *natbd = arg; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("NAT filtering failed (%m)\n", err); + goto out; + } + + if (type != natbd->res_nf) { + info("NAT Filtering %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + nat_type_str(natbd->res_nf), + nat_type_str(type)); + } + + natbd->res_nf = type; + + out: + natbd->nf = mem_deref(natbd->nf); +} + + +static void nat_lifetime_handler(int err, + const struct nat_lifetime_interval *interval, + void *arg) +{ + struct natbd *natbd = arg; + + ++natbd->n_nl; + + if (err) { + DEBUG_WARNING("nat_lifetime_handler: (%m)\n", err); + return; + } + + natbd->res_nl = *interval; + + info("NAT Binding lifetime for %s: min=%u cur=%u max=%u\n", + net_proto2name(natbd->proto), + interval->min, interval->cur, interval->max); +} + + +static void nat_genalg_handler(int err, uint16_t scode, const char *reason, + int status, const struct sa *map, + void *arg) +{ + struct natbd *natbd = arg; + + (void)map; + + if (natbd->terminated) + return; + + if (err) { + DEBUG_WARNING("Generic ALG detection failed: %m\n", err); + goto out; + } + else if (scode) { + DEBUG_WARNING("Generic ALG detection failed: %u %s\n", + scode, reason); + goto out; + } + + if (status != natbd->status_ga) { + info("Generic ALG for %s changed from (%s) to (%s)\n", + net_proto2name(natbd->proto), + genalg_str(natbd->status_ga), + genalg_str(status)); + } + + natbd->status_ga = status; + + out: + natbd->ga = mem_deref(natbd->ga); +} + + +static void destructor(void *arg) +{ + struct natbd *natbd = arg; + + natbd->terminated = true; + + tmr_cancel(&natbd->tmr); + mem_deref(natbd->dns); + mem_deref(natbd->nh); + mem_deref(natbd->nm); + mem_deref(natbd->nf); + mem_deref(natbd->nl); + mem_deref(natbd->ga); +} + + +static int natbd_start(struct natbd *natbd) +{ + int err = 0; + + if (!natbd->nh) { + err |= nat_hairpinning_alloc(&natbd->nh, &natbd->stun_srv, + natbd->proto, NULL, + nat_hairpinning_handler, natbd); + err |= nat_hairpinning_start(natbd->nh); + if (err) { + DEBUG_WARNING("nat_hairpinning_start() failed (%m)\n", + err); + } + } + + if (!natbd->nm) { + err |= nat_mapping_alloc(&natbd->nm, net_laddr_af(net_af()), + &natbd->stun_srv, natbd->proto, NULL, + nat_mapping_handler, natbd); + err |= nat_mapping_start(natbd->nm); + if (err) { + DEBUG_WARNING("nat_mapping_start() failed (%m)\n", + err); + } + } + + if (natbd->proto == IPPROTO_UDP) { + + if (!natbd->nf) { + err |= nat_filtering_alloc(&natbd->nf, + &natbd->stun_srv, NULL, + nat_filtering_handler, + natbd); + err |= nat_filtering_start(natbd->nf); + if (err) { + DEBUG_WARNING("nat_filtering_start() (%m)\n", + err); + } + } + } + + if (!natbd->ga) { + err |= nat_genalg_alloc(&natbd->ga, &natbd->stun_srv, + natbd->proto, NULL, + nat_genalg_handler, natbd); + + if (err) { + DEBUG_WARNING("natbd_init: %m\n", err); + } + err |= nat_genalg_start(natbd->ga); + if (err) { + DEBUG_WARNING("nat_genalg_start() failed (%m)\n", + err); + } + } + + return err; +} + + +static void timeout(void *arg) +{ + struct natbd *natbd = arg; + + info("%H\n", natbd_status, natbd); + + natbd_start(natbd); + + tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd); +} + + +static void dns_handler(int err, const struct sa *addr, void *arg) +{ + struct natbd *natbd = arg; + + if (err) { + DEBUG_WARNING("failed to resolve '%s' (%m)\n", + natbd->host, err); + goto out; + } + + info("natbd: resolved STUN-server for %s -- %J\n", + net_proto2name(natbd->proto), addr); + + sa_cpy(&natbd->stun_srv, addr); + + natbd_start(natbd); + + /* Lifetime discovery is a special test */ + if (natbd->proto == IPPROTO_UDP) { + + err = nat_lifetime_alloc(&natbd->nl, &natbd->stun_srv, 3, + NULL, nat_lifetime_handler, natbd); + err |= nat_lifetime_start(natbd->nl); + if (err) { + DEBUG_WARNING("nat_lifetime_start() failed (%m)\n", + err); + } + } + + tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd); + + out: + natbd->dns = mem_deref(natbd->dns); +} + + +static void timeout_init(void *arg) +{ + struct natbd *natbd = arg; + const char *proto_str; + int err = 0; + + if (sa_isset(&natbd->stun_srv, SA_ALL)) { + dns_handler(0, &natbd->stun_srv, natbd); + return; + } + + if (natbd->proto == IPPROTO_UDP) + proto_str = stun_proto_udp; + else if (natbd->proto == IPPROTO_TCP) + proto_str = stun_proto_tcp; + else { + err = EPROTONOSUPPORT; + goto out; + } + + err = stun_server_discover(&natbd->dns, net_dnsc(), + stun_usage_binding, + proto_str, net_af(), + natbd->host, natbd->port, + dns_handler, natbd); + if (err) + goto out; + + out: + if (err) { + DEBUG_WARNING("timeout_init: %m\n", err); + } +} + + +static int natbd_alloc(struct natbd **natbdp, uint32_t interval, + int proto, const char *server) +{ + struct pl host, port; + struct natbd *natbd; + int err = 0; + + if (!natbdp || !interval || !proto || !server) + return EINVAL; + + natbd = mem_zalloc(sizeof(*natbd), destructor); + if (!natbd) + return ENOMEM; + + natbd->interval = interval; + natbd->proto = proto; + natbd->res_hp = -1; + + if (0 == sa_decode(&natbd->stun_srv, server, str_len(server))) { + ; + } + else if (0 == re_regex(server, str_len(server), "[^:]+[:]*[^]*", + &host, NULL, &port)) { + + pl_strcpy(&host, natbd->host, sizeof(natbd->host)); + natbd->port = pl_u32(&port); + } + else { + DEBUG_WARNING("failed to decode natbd_server (%s)\n", + server); + err = EINVAL; + goto out; + } + + tmr_start(&natbd->tmr, 1, timeout_init, natbd); + + out: + if (err) + mem_deref(natbd); + else + *natbdp = natbd; + + return err; +} + + +static int status(struct re_printf *pf, void *unused) +{ + size_t i; + int err = 0; + + (void)unused; + + for (i=0; i<ARRAY_SIZE(natbdv); i++) { + + if (natbdv[i]) + err |= natbd_status(pf, natbdv[i]); + } + + return err; +} + + +static const struct cmd cmdv[] = { + {'z', 0, "NAT status", status} +}; + + +static int module_init(void) +{ + char server[256] = ""; + uint32_t interval = 3600; + int err; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + (void)conf_get_u32(conf_cur(), "natbd_interval", &interval); + (void)conf_get_str(conf_cur(), "natbd_server", server, sizeof(server)); + + if (!server[0]) { + DEBUG_WARNING("missing config 'natbd_server'\n"); + return EINVAL; + } + + info("natbd: Enable NAT Behavior Discovery using STUN server %s\n", + server); + + err |= natbd_alloc(&natbdv[0], interval, IPPROTO_UDP, server); + err |= natbd_alloc(&natbdv[1], interval, IPPROTO_TCP, server); + if (err) { + DEBUG_WARNING("failed to allocate natbd state: %m\n", err); + } + + return err; +} + + +static int module_close(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(natbdv); i++) + natbdv[i] = mem_deref(natbdv[i]); + + cmd_unregister(cmdv); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(natbd) = { + "natbd", + "natbd", + module_init, + module_close, +}; diff --git a/modules/natpmp/libnatpmp.c b/modules/natpmp/libnatpmp.c new file mode 100644 index 0000000..7969007 --- /dev/null +++ b/modules/natpmp/libnatpmp.c @@ -0,0 +1,235 @@ +/** + * @file libnatpmp.c NAT-PMP Client library + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "libnatpmp.h" + + +enum { + NATPMP_DELAY = 250, + NATPMP_MAXTX = 9, +}; + +struct natpmp_req { + struct natpmp_req **npp; + struct udp_sock *us; + struct tmr tmr; + struct mbuf *mb; + struct sa srv; + unsigned n; + natpmp_resp_h *resph; + void *arg; +}; + + +static void completed(struct natpmp_req *np, int err, + const struct natpmp_resp *resp) +{ + natpmp_resp_h *resph = np->resph; + void *arg = np->arg; + + tmr_cancel(&np->tmr); + + if (np->npp) { + *np->npp = NULL; + np->npp = NULL; + } + + np->resph = NULL; + + /* must be destroyed before calling handler */ + mem_deref(np); + + if (resph) + resph(err, resp, arg); +} + + +static void destructor(void *arg) +{ + struct natpmp_req *np = arg; + + tmr_cancel(&np->tmr); + mem_deref(np->us); + mem_deref(np->mb); +} + + +static void timeout(void *arg) +{ + struct natpmp_req *np = arg; + int err; + + if (np->n > NATPMP_MAXTX) { + completed(np, ETIMEDOUT, NULL); + return; + } + + tmr_start(&np->tmr, NATPMP_DELAY<<np->n, timeout, arg); + +#if 1 + debug("natpmp: {n=%u} tx %u bytes\n", np->n, np->mb->end); +#endif + + np->n++; + + np->mb->pos = 0; + err = udp_send(np->us, &np->srv, np->mb); + if (err) { + completed(np, err, NULL); + } +} + + +static int resp_decode(struct natpmp_resp *resp, struct mbuf *mb) +{ + resp->vers = mbuf_read_u8(mb); + resp->op = mbuf_read_u8(mb); + resp->result = ntohs(mbuf_read_u16(mb)); + resp->epoch = ntohl(mbuf_read_u32(mb)); + + if (!(resp->op & 0x80)) + return EPROTO; + resp->op &= ~0x80; + + switch (resp->op) { + + case NATPMP_OP_EXTERNAL: + resp->u.ext_addr = ntohl(mbuf_read_u32(mb)); + break; + + case NATPMP_OP_MAPPING_UDP: + case NATPMP_OP_MAPPING_TCP: + resp->u.map.int_port = ntohs(mbuf_read_u16(mb)); + resp->u.map.ext_port = ntohs(mbuf_read_u16(mb)); + resp->u.map.lifetime = ntohl(mbuf_read_u32(mb)); + break; + + default: + warning("natmap: unknown opcode %d\n", resp->op); + return EBADMSG; + } + + return 0; +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct natpmp_req *np = arg; + struct natpmp_resp resp; + + if (!sa_cmp(src, &np->srv, SA_ALL)) + return; + + if (resp_decode(&resp, mb)) + return; + + completed(np, 0, &resp); +} + + +static int natpmp_init(struct natpmp_req *np, const struct sa *srv, + uint8_t opcode, natpmp_resp_h *resph, void *arg) +{ + int err; + + if (!np || !srv) + return EINVAL; + + /* a new UDP socket for each NAT-PMP request */ + err = udp_listen(&np->us, NULL, udp_recv, np); + if (err) + return err; + + np->srv = *srv; + np->resph = resph; + np->arg = arg; + + udp_connect(np->us, srv); + + np->mb = mbuf_alloc(512); + if (!np->mb) + return ENOMEM; + + err |= mbuf_write_u8(np->mb, NATPMP_VERSION); + err |= mbuf_write_u8(np->mb, opcode); + + return err; +} + + +int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv, + natpmp_resp_h *resph, void *arg) +{ + struct natpmp_req *np; + int err; + + np = mem_zalloc(sizeof(*np), destructor); + if (!np) + return ENOMEM; + + err = natpmp_init(np, srv, NATPMP_OP_EXTERNAL, resph, arg); + if (err) + goto out; + + timeout(np); + + out: + if (err) + mem_deref(np); + else if (npp) { + np->npp = npp; + *npp = np; + } + else { + /* Destroy the transaction now */ + mem_deref(np); + } + + return err; +} + + +int natpmp_mapping_request(struct natpmp_req **npp, const struct sa *srv, + uint16_t int_port, uint16_t ext_port, + uint32_t lifetime, natpmp_resp_h *resph, void *arg) +{ + struct natpmp_req *np; + int err; + + np = mem_zalloc(sizeof(*np), destructor); + if (!np) + return ENOMEM; + + err = natpmp_init(np, srv, NATPMP_OP_MAPPING_UDP, resph, arg); + if (err) + goto out; + + err |= mbuf_write_u16(np->mb, 0x0000); + err |= mbuf_write_u16(np->mb, htons(int_port)); + err |= mbuf_write_u16(np->mb, htons(ext_port)); + err |= mbuf_write_u32(np->mb, htonl(lifetime)); + if (err) + goto out; + + timeout(np); + + out: + if (err) + mem_deref(np); + else if (npp) { + np->npp = npp; + *npp = np; + } + else { + /* Destroy the transaction now */ + mem_deref(np); + } + + return err; +} diff --git a/modules/natpmp/libnatpmp.h b/modules/natpmp/libnatpmp.h new file mode 100644 index 0000000..d1cc33c --- /dev/null +++ b/modules/natpmp/libnatpmp.h @@ -0,0 +1,53 @@ +/** + * @file libnatpmp.h Interface to NAT-PMP Client library + * + * Copyright (C) 2010 Creytiv.com + */ + + +enum { + NATPMP_VERSION = 0, + NATPMP_PORT = 5351, +}; + +enum natpmp_op { + NATPMP_OP_EXTERNAL = 0, + NATPMP_OP_MAPPING_UDP = 1, + NATPMP_OP_MAPPING_TCP = 2, +}; + +enum natpmp_result { + NATPMP_SUCCESS = 0, + NATPMP_UNSUP_VERSION = 1, + NATPMP_REFUSED = 2, + NATPMP_NETWORK_FAILURE = 3, + NATPMP_OUT_OF_RESOURCES = 4, + NATPMP_UNSUP_OPCODE = 5 +}; + +struct natpmp_resp { + uint8_t vers; + uint8_t op; + uint16_t result; + uint32_t epoch; + + union { + uint32_t ext_addr; + struct { + uint16_t int_port; + uint16_t ext_port; + uint32_t lifetime; + } map; + } u; +}; + +struct natpmp_req; + +typedef void (natpmp_resp_h)(int err, const struct natpmp_resp *resp, + void *arg); + +int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv, + natpmp_resp_h *h, void *arg); +int natpmp_mapping_request(struct natpmp_req **natpmpp, const struct sa *srv, + uint16_t int_port, uint16_t ext_port, + uint32_t lifetime, natpmp_resp_h *resph, void *arg); diff --git a/modules/natpmp/module.mk b/modules/natpmp/module.mk new file mode 100644 index 0000000..3087c77 --- /dev/null +++ b/modules/natpmp/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := natpmp +$(MOD)_SRCS += natpmp.c libnatpmp.c + +include mk/mod.mk diff --git a/modules/natpmp/natpmp.c b/modules/natpmp/natpmp.c new file mode 100644 index 0000000..04b45f0 --- /dev/null +++ b/modules/natpmp/natpmp.c @@ -0,0 +1,313 @@ +/** + * @file natpmp.c NAT-PMP Module for Media NAT-traversal + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "libnatpmp.h" + + +/** + * @defgroup natpmp natpmp + * + * NAT Port Mapping Protocol (NAT-PMP) + * + * https://tools.ietf.org/html/rfc6886 + */ + +enum { + LIFETIME = 300 /* seconds */ +}; + +struct mnat_sess { + struct list medial; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct natpmp_req *natpmp; + struct tmr tmr; + uint16_t int_port; + uint32_t lifetime; + bool granted; +}; + + +static struct mnat *mnat; +static struct sa natpmp_srv, natpmp_extaddr; +static struct natpmp_req *natpmp_ext; + + +static void natpmp_resp_handler(int err, const struct natpmp_resp *resp, + void *arg); + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + /* Destroy the mapping */ + if (m->granted) { + (void)natpmp_mapping_request(NULL, &natpmp_srv, + m->int_port, 0, 0, NULL, NULL); + } + + list_unlink(&m->le); + tmr_cancel(&m->tmr); + mem_deref(m->sdpm); + mem_deref(m->natpmp); +} + + +static void is_complete(struct mnat_sess *sess) +{ + struct le *le; + + for (le = sess->medial.head; le; le = le->next) { + + struct mnat_media *m = le->data; + + if (!m->granted) + return; + } + + if (sess->estabh) { + sess->estabh(0, 0, "done", sess->arg); + + sess->estabh = NULL; + } +} + + +static void refresh_timeout(void *arg) +{ + struct mnat_media *m = arg; + + m->natpmp = mem_deref(m->natpmp); + (void)natpmp_mapping_request(&m->natpmp, &natpmp_srv, + m->int_port, 0, m->lifetime, + natpmp_resp_handler, m); +} + + +static void natpmp_resp_handler(int err, const struct natpmp_resp *resp, + void *arg) +{ + struct mnat_media *m = arg; + struct sa map_addr; + + if (err) { + warning("natpmp: response error: %m\n", err); + return; + } + + if (resp->op != NATPMP_OP_MAPPING_UDP) + return; + if (resp->result != NATPMP_SUCCESS) { + warning("natpmp: request failed with result code: %d\n", + resp->result); + return; + } + + if (resp->u.map.int_port != m->int_port) { + info("natpmp: ignoring response for internal_port=%u\n", + resp->u.map.int_port); + return; + } + + info("natpmp: mapping granted:" + " internal_port=%u, external_port=%u, lifetime=%u\n", + resp->u.map.int_port, resp->u.map.ext_port, + resp->u.map.lifetime); + + map_addr = natpmp_extaddr; + sa_set_port(&map_addr, resp->u.map.ext_port); + m->lifetime = resp->u.map.lifetime; + + /* Update SDP media with external IP-address mapping */ + sdp_media_set_laddr(m->sdpm, &map_addr); + + m->granted = true; + + tmr_start(&m->tmr, m->lifetime * 1000 * 3/4, refresh_timeout, m); + + is_complete(m->sess); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err = 0; + (void)af; + (void)port; + (void)user; + (void)pass; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->estabh = estabh; + sess->arg = arg; + + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + struct sa laddr; + int err = 0; + (void)sock2; + + if (!mp || !sess || !sdpm || proto != IPPROTO_UDP) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sess = sess; + m->sdpm = mem_ref(sdpm); + m->lifetime = LIFETIME; + + err = udp_local_get(sock1, &laddr); + if (err) + goto out; + + m->int_port = sa_port(&laddr); + + info("natpmp: local UDP port is %u\n", m->int_port); + + err = natpmp_mapping_request(&m->natpmp, &natpmp_srv, + m->int_port, 0, m->lifetime, + natpmp_resp_handler, m); + if (err) + goto out; + + out: + if (err) + mem_deref(m); + else { + *mp = m; + } + + return err; +} + + +static void extaddr_handler(int err, const struct natpmp_resp *resp, void *arg) +{ + (void)arg; + + if (err) { + warning("natpmp: external address ERROR: %m\n", err); + return; + } + + if (resp->result != NATPMP_SUCCESS) { + warning("natpmp: external address failed" + " with result code: %d\n", resp->result); + return; + } + + if (resp->op != NATPMP_OP_EXTERNAL) + return; + + sa_set_in(&natpmp_extaddr, resp->u.ext_addr, 0); + + info("natpmp: discovered External address: %j\n", &natpmp_extaddr); +} + + +static bool net_rt_handler(const char *ifname, const struct sa *dst, + int dstlen, const struct sa *gw, void *arg) +{ + (void)dstlen; + (void)arg; + + if (sa_af(dst) != AF_INET) + return false; + + if (sa_in(dst) == 0) { + natpmp_srv = *gw; + sa_set_port(&natpmp_srv, NATPMP_PORT); + info("natpmp: found default gateway %j on interface '%s'\n", + gw, ifname); + return true; + } + + return false; +} + + +static int module_init(void) +{ + int err; + + sa_init(&natpmp_srv, AF_INET); + sa_set_port(&natpmp_srv, NATPMP_PORT); + + net_rt_list(net_rt_handler, NULL); + + conf_get_sa(conf_cur(), "natpmp_server", &natpmp_srv); + + info("natpmp: using NAT-PMP server at %J\n", &natpmp_srv); + + err = natpmp_external_request(&natpmp_ext, &natpmp_srv, + extaddr_handler, NULL); + if (err) + return err; + + return mnat_register(&mnat, "natpmp", NULL, + session_alloc, media_alloc, NULL); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + natpmp_ext = mem_deref(natpmp_ext); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(natpmp) = { + "natpmp", + "mnat", + module_init, + module_close, +}; diff --git a/modules/opengl/module.mk b/modules/opengl/module.mk new file mode 100644 index 0000000..4b348ed --- /dev/null +++ b/modules/opengl/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opengl +$(MOD)_SRCS += opengl.m +$(MOD)_LFLAGS += -framework OpenGL -framework Cocoa -lobjc + +include mk/mod.mk diff --git a/modules/opengl/opengl.m b/modules/opengl/opengl.m new file mode 100644 index 0000000..d3ca972 --- /dev/null +++ b/modules/opengl/opengl.m @@ -0,0 +1,522 @@ +/** + * @file opengl.m Video driver for OpenGL on MacOSX + * + * Copyright (C) 2010 Creytiv.com + */ +#include <Cocoa/Cocoa.h> +#include <OpenGL/gl.h> +#include <OpenGL/glext.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + NSOpenGLContext *ctx; + NSWindow *win; + GLhandleARB PHandle; + char *prog; +}; + + +static struct vidisp *vid; /**< OPENGL Video-display */ + + +static const char *FProgram= + "uniform sampler2DRect Ytex;\n" + "uniform sampler2DRect Utex,Vtex;\n" + "void main(void) {\n" + " float nx,ny,r,g,b,y,u,v;\n" + " vec4 txl,ux,vx;" + " nx=gl_TexCoord[0].x;\n" + " ny=%d.0-gl_TexCoord[0].y;\n" + " y=texture2DRect(Ytex,vec2(nx,ny)).r;\n" + " u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n" + " v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n" + + " y=1.1643*(y-0.0625);\n" + " u=u-0.5;\n" + " v=v-0.5;\n" + + " r=y+1.5958*v;\n" + " g=y-0.39173*u-0.81290*v;\n" + " b=y+2.017*u;\n" + + " gl_FragColor=vec4(r,g,b,1.0);\n" + "}\n"; + + +static void destructor(void *arg) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + struct vidisp_st *st = arg; + + if (st->ctx) { + [st->ctx clearDrawable]; + [st->ctx release]; + } + + [st->win release]; + + if (st->PHandle) { + glUseProgramObjectARB(0); + glDeleteObjectARB(st->PHandle); + } + + mem_deref(st->prog); + + [pool release]; + + mem_deref(st->vd); +} + + +static int create_window(struct vidisp_st *st) +{ + NSRect rect = NSMakeRect(0, 0, 100, 100); + NSUInteger style; + + if (st->win) + return 0; + + style = NSTitledWindowMask | + NSClosableWindowMask | + NSMiniaturizableWindowMask; + + st->win = [[NSWindow alloc] initWithContentRect:rect + styleMask:style + backing:NSBackingStoreBuffered + defer:FALSE]; + if (!st->win) { + warning("opengl: could not create NSWindow\n"); + return ENOMEM; + } + + [st->win setLevel:NSFloatingWindowLevel]; + [st->win useOptimizedDrawing:YES]; + + return 0; +} + + +static void opengl_reset(struct vidisp_st *st, const struct vidsz *sz) +{ + if (st->PHandle) { + glUseProgramObjectARB(0); + glDeleteObjectARB(st->PHandle); + st->PHandle = 0; + st->prog = mem_deref(st->prog); + } + + st->size = *sz; +} + + +static int setup_shader(struct vidisp_st *st, int width, int height) +{ + GLhandleARB FSHandle, PHandle; + const char *progv[1]; + char buf[1024]; + int err, i; + + if (st->PHandle) + return 0; + + err = re_sdprintf(&st->prog, FProgram, height); + if (err) + return err; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, 0, height, -1, 1); + glViewport(0, 0, width, height); + glClearColor(0, 0, 0, 0); + glColor3f(1.0f, 0.84f, 0.0f); + glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + + /* Set up program objects. */ + PHandle = glCreateProgramObjectARB(); + FSHandle = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + + /* Compile the shader. */ + progv[0] = st->prog; + glShaderSourceARB(FSHandle, 1, progv, NULL); + glCompileShaderARB(FSHandle); + + /* Print the compilation log. */ + glGetObjectParameterivARB(FSHandle, GL_OBJECT_COMPILE_STATUS_ARB, &i); + if (i != 1) { + warning("opengl: shader compile failed\n"); + return ENOSYS; + } + + glGetInfoLogARB(FSHandle, sizeof(buf), NULL, buf); + + /* Create a complete program object. */ + glAttachObjectARB(PHandle, FSHandle); + glLinkProgramARB(PHandle); + + /* And print the link log. */ + glGetInfoLogARB(PHandle, sizeof(buf), NULL, buf); + + /* Finally, use the program. */ + glUseProgramObjectARB(PHandle); + + st->PHandle = PHandle; + + return 0; +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + NSOpenGLPixelFormatAttribute attr[] = { + NSOpenGLPFAColorSize, 32, + NSOpenGLPFADepthSize, 16, + NSOpenGLPFADoubleBuffer, + 0 + }; + NSOpenGLPixelFormat *fmt; + NSAutoreleasePool *pool; + struct vidisp_st *st; + GLint vsync = 1; + int err = 0; + + (void)dev; + (void)resizeh; + (void)arg; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return ENOMEM; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; + if (!fmt) { + err = ENOMEM; + warning("opengl: Failed creating OpenGL format\n"); + goto out; + } + + st->ctx = [[NSOpenGLContext alloc] initWithFormat:fmt + shareContext:nil]; + + [fmt release]; + + if (!st->ctx) { + err = ENOMEM; + warning("opengl: Failed creating OpenGL context\n"); + goto out; + } + + /* Use provided view, or create our own */ + if (prm && prm->view) { + [st->ctx setView:prm->view]; + } + else { + err = create_window(st); + if (err) + goto out; + + if (prm) + prm->view = [st->win contentView]; + } + + /* Enable vertical sync */ + [st->ctx setValues:&vsync forParameter:NSOpenGLCPSwapInterval]; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + [pool release]; + + return err; +} + + +static inline void draw_yuv(GLhandleARB PHandle, int height, + const uint8_t *Ytex, int linesizeY, + const uint8_t *Utex, int linesizeU, + const uint8_t *Vtex, int linesizeV) +{ + int i; + + /* This might not be required, but should not hurt. */ + glEnable(GL_TEXTURE_2D); + + /* Select texture unit 1 as the active unit and bind the U texture. */ + glActiveTexture(GL_TEXTURE1); + i = glGetUniformLocationARB(PHandle, "Utex"); + glUniform1iARB(i,1); /* Bind Utex to texture unit 1 */ + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,1); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE, + linesizeU, height/2, 0, + GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex); + + /* Select texture unit 2 as the active unit and bind the V texture. */ + glActiveTexture(GL_TEXTURE2); + i = glGetUniformLocationARB(PHandle, "Vtex"); + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,2); + glUniform1iARB(i,2); /* Bind Vtext to texture unit 2 */ + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE, + linesizeV, height/2, 0, + GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex); + + /* Select texture unit 0 as the active unit and bind the Y texture. */ + glActiveTexture(GL_TEXTURE0); + i = glGetUniformLocationARB(PHandle,"Ytex"); + glUniform1iARB(i,0); /* Bind Ytex to texture unit 0 */ + glBindTexture(GL_TEXTURE_RECTANGLE_EXT,3); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); + + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_LUMINANCE, + linesizeY, height, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, Ytex); +} + + +static inline void draw_blit(int width, int height) +{ + glClear(GL_COLOR_BUFFER_BIT); + + /* Draw image */ + + glBegin(GL_QUADS); + { + glTexCoord2i(0, 0); + glVertex2i(0, 0); + glTexCoord2i(width, 0); + glVertex2i(width, 0); + glTexCoord2i(width, height); + glVertex2i(width, height); + glTexCoord2i(0, height); + glVertex2i(0, height); + } + glEnd(); +} + + +static inline void draw_rgb(const uint8_t *pic, int w, int h) +{ + glEnable(GL_TEXTURE_RECTANGLE_EXT); + glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1); + + glTextureRangeAPPLE(GL_TEXTURE_RECTANGLE_EXT, w * h * 2, pic); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, + GL_TEXTURE_STORAGE_HINT_APPLE, + GL_STORAGE_SHARED_APPLE); + glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE); + + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, w, h, 0, + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pic); + + /* draw */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_TEXTURE_2D); + + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho( (GLfloat)0, (GLfloat)w, (GLfloat)0, (GLfloat)h, -1.0, 1.0); + + glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glBegin(GL_QUADS); + { + glTexCoord2f(0.0f, 0.0f); + glVertex2f(0.0f, h); + glTexCoord2f(0.0f, h); + glVertex2f(0.0f, 0.0f); + glTexCoord2f(w, h); + glVertex2f(w, 0.0f); + glTexCoord2f(w, 0.0f); + glVertex2f(w, h); + } + glEnd(); +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + NSAutoreleasePool *pool; + bool upd = false; + int err = 0; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return ENOMEM; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("opengl: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + opengl_reset(st, &frame->size); + + upd = true; + } + + if (upd && st->win) { + + const NSSize size = {frame->size.w, frame->size.h}; + char capt[256]; + + [st->win setContentSize:size]; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + [st->win setTitle:[NSString stringWithUTF8String:capt]]; + + [st->win makeKeyAndOrderFront:nil]; + [st->win display]; + [st->win center]; + + [st->ctx clearDrawable]; + [st->ctx setView:[st->win contentView]]; + } + + [st->ctx makeCurrentContext]; + + if (frame->fmt == VID_FMT_YUV420P) { + + if (!st->PHandle) { + + debug("opengl: using Vertex shader with YUV420P\n"); + + err = setup_shader(st, frame->size.w, frame->size.h); + if (err) + goto out; + } + + draw_yuv(st->PHandle, frame->size.h, + frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], + frame->data[2], frame->linesize[2]); + draw_blit(frame->size.w, frame->size.h); + } + else if (frame->fmt == VID_FMT_RGB32) { + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glViewport(0, 0, frame->size.w, frame->size.h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + draw_rgb(frame->data[0], frame->size.w, frame->size.h); + } + else { + warning("opengl: unknown pixel format %s\n", + vidfmt_name(frame->fmt)); + err = EINVAL; + } + + [st->ctx flushBuffer]; + + out: + [pool release]; + + return err; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st) + return; + + [st->win orderOut:nil]; +} + + +static int module_init(void) +{ + NSApplication *app; + int err; + + app = [NSApplication sharedApplication]; + if (!app) + return ENOSYS; + + err = vidisp_register(&vid, "opengl", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opengl) = { + "opengl", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/opengles/context.m b/modules/opengles/context.m new file mode 100644 index 0000000..354c74f --- /dev/null +++ b/modules/opengles/context.m @@ -0,0 +1,115 @@ +/** + * @file context.m OpenGLES Context for iOS + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <UIKit/UIKit.h> +#include <QuartzCore/CAEAGLLayer.h> +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#include "opengles.h" + + +@interface GlView : UIView +{ + struct vidisp_st *st; +} +@end + + +static EAGLContext *ctx = NULL; + + +@implementation GlView + + ++ (Class)layerClass +{ + return [CAEAGLLayer class]; +} + + +- (id)initWithFrame:(CGRect)frame vidisp:(struct vidisp_st *)vst +{ + self = [super initWithFrame:frame]; + if (!self) + return nil; + + self.layer.opaque = YES; + + st = vst; + + return self; +} + + +- (void) render_sel:(id)unused +{ + (void)unused; + + if (!ctx) { + UIWindow* window = [UIApplication sharedApplication].keyWindow; + + ctx = [EAGLContext alloc]; + [ctx initWithAPI:kEAGLRenderingAPIOpenGLES1]; + + [EAGLContext setCurrentContext:ctx]; + + [window addSubview:self]; + [window bringSubviewToFront:self]; + + [window makeKeyAndVisible]; + } + + if (!st->framebuffer) { + + opengles_addbuffers(st); + + [ctx renderbufferStorage:GL_RENDERBUFFER_OES + fromDrawable:(CAEAGLLayer*)self.layer]; + } + + opengles_render(st); + + [ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; +} + + +- (void) dealloc +{ + [ctx release]; + + [super dealloc]; +} + + +@end + + +void context_destroy(struct vidisp_st *st) +{ + [(UIView *)st->view release]; +} + + +int context_init(struct vidisp_st *st) +{ + UIWindow* window = [UIApplication sharedApplication].keyWindow; + + st->view = [[GlView new] initWithFrame:window.bounds vidisp:st]; + + return 0; +} + + +void context_render(struct vidisp_st *st) +{ + UIView *view = st->view; + + [view performSelectorOnMainThread:@selector(render_sel:) + withObject:nil + waitUntilDone:YES]; +} diff --git a/modules/opengles/module.mk b/modules/opengles/module.mk new file mode 100644 index 0000000..a9c5300 --- /dev/null +++ b/modules/opengles/module.mk @@ -0,0 +1,16 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opengles +$(MOD)_SRCS += opengles.c + +ifeq ($(OS),darwin) +$(MOD)_SRCS += context.m + +$(MOD)_LFLAGS += -lobjc -framework CoreGraphics -framework CoreFoundation +endif + +include mk/mod.mk diff --git a/modules/opengles/opengles.c b/modules/opengles/opengles.c new file mode 100644 index 0000000..fa87012 --- /dev/null +++ b/modules/opengles/opengles.c @@ -0,0 +1,295 @@ +/** + * @file opengles.c Video driver for OpenGLES + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#include "opengles.h" + + +#define DEBUG_MODULE "opengles" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +static struct vidisp *vid; + + +static int texture_init(struct vidisp_st *st) +{ + glGenTextures(1, &st->texture_id); + if (!st->texture_id) + return ENOMEM; + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + st->vf->size.w, st->vf->size.h, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + return 0; +} + + +static void texture_render(struct vidisp_st *st) +{ + static const GLfloat coords[4 * 2] = { + 0.0, 1.0, + 1.0, 1.0, + 0.0, 0.0, + 1.0, 0.0 + }; + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, + st->vf->size.w, st->vf->size.h, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]); + + /* Setup the vertices */ + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, st->vertices); + + /* Setup the texture coordinates */ + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, coords); + + glBindTexture(GL_TEXTURE_2D, st->texture_id); + + glEnable(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDisable(GL_TEXTURE_2D); +} + + +static void setup_layout(struct vidisp_st *st, const struct vidsz *screensz, + struct vidrect *ortho, struct vidrect *vp) +{ + int x, y, w, h, i = 0; + + w = st->vf->size.w; + h = st->vf->size.h; + + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = w; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = 0; + st->vertices[i++] = h; + st->vertices[i++] = 0; + st->vertices[i++] = w; + st->vertices[i++] = h; + st->vertices[i++] = 0; + + x = (screensz->w - w) / 2; + y = (screensz->h - h) / 2; + + if (x < 0) { + vp->x = 0; + ortho->x = -x; + } + else { + vp->x = x; + ortho->x = 0; + } + + if (y < 0) { + vp->y = 0; + ortho->y = -y; + } + else { + vp->y = y; + ortho->y = 0; + } + + vp->w = screensz->w - 2 * vp->x; + vp->h = screensz->h - 2 * vp->y; + + ortho->w = w - ortho->x; + ortho->h = h - ortho->y; +} + + +void opengles_addbuffers(struct vidisp_st *st) +{ + glGenFramebuffersOES(1, &st->framebuffer); + glGenRenderbuffersOES(1, &st->renderbuffer); + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer); +} + + +void opengles_render(struct vidisp_st *st) +{ + if (!st->texture_id) { + + struct vidrect ortho, vp; + struct vidsz bufsz; + int err = 0; + + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, + GL_RENDERBUFFER_WIDTH_OES, + (GLint *)&bufsz.w); + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, + GL_RENDERBUFFER_HEIGHT_OES, + (GLint *)&bufsz.h); + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, + GL_COLOR_ATTACHMENT0_OES, + GL_RENDERBUFFER_OES, + st->renderbuffer); + + err = texture_init(st); + if (err) + return; + + glBindRenderbufferOES(GL_FRAMEBUFFER_OES, st->renderbuffer); + + setup_layout(st, &bufsz, &ortho, &vp); + + + /* Set up Viewports etc. */ + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer); + + glViewport(vp.x, vp.y, vp.w, vp.h); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrthof(ortho.x, ortho.w, ortho.y, ortho.h, 0.0f, 1.0f); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glDisable(GL_DEPTH_TEST); + glDisableClientState(GL_COLOR_ARRAY); + } + + texture_render(st); + + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glBindTexture(GL_TEXTURE_2D, 0); + glEnable(GL_DEPTH_TEST); + + glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer); +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + glDeleteTextures(1, &st->texture_id); + glDeleteFramebuffersOES(1, &st->framebuffer); + glDeleteRenderbuffersOES(1, &st->renderbuffer); + + context_destroy(st); + + mem_deref(st->vf); + mem_deref(st->vd); +} + + +static int opengles_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, + void *arg) +{ + struct vidisp_st *st; + int err = 0; + + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + err = context_init(st); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int opengles_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + int err; + + (void)title; + + if (!st->vf) { + if (frame->size.w & 3) { + DEBUG_WARNING("width must be multiple of 4\n"); + return EINVAL; + } + + err = vidframe_alloc(&st->vf, VID_FMT_RGB565, &frame->size); + if (err) + return err; + } + + vidconv(st->vf, frame, NULL); + + context_render(st); + + return 0; +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "opengles", opengles_alloc, NULL, + opengles_display, NULL); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opengles) = { + "opengles", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/opengles/opengles.h b/modules/opengles/opengles.h new file mode 100644 index 0000000..9eac3fb --- /dev/null +++ b/modules/opengles/opengles.h @@ -0,0 +1,28 @@ +/** + * @file opengles.h Internal API to OpenGLES module + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct vidisp_st { + struct vidisp *vd; + struct vidframe *vf; + + /* GLES: */ + GLuint framebuffer; + GLuint renderbuffer; + GLuint texture_id; + GLfloat vertices[4 * 3]; + + void *view; +}; + + +void opengles_addbuffers(struct vidisp_st *st); +void opengles_render(struct vidisp_st *st); + + +int context_init(struct vidisp_st *st); +void context_destroy(struct vidisp_st *st); +void context_render(struct vidisp_st *st); diff --git a/modules/opensles/module.mk b/modules/opensles/module.mk new file mode 100644 index 0000000..30ecb4c --- /dev/null +++ b/modules/opensles/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opensles +$(MOD)_SRCS += opensles.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += -lOpenSLES + +include mk/mod.mk diff --git a/modules/opensles/opensles.c b/modules/opensles/opensles.c new file mode 100644 index 0000000..9da39f0 --- /dev/null +++ b/modules/opensles/opensles.c @@ -0,0 +1,66 @@ +/** + * @file opensles.c OpenSLES audio driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +SLObjectItf engineObject = NULL; +SLEngineItf engineEngine; + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + SLresult r; + int err; + + r = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, + &engineEngine); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + err = auplay_register(&auplay, "opensles", opensles_player_alloc); + err |= ausrc_register(&ausrc, "opensles", opensles_recorder_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opensles) = { + "opensles", + "audio", + module_init, + module_close, +}; diff --git a/modules/opensles/opensles.h b/modules/opensles/opensles.h new file mode 100644 index 0000000..2970413 --- /dev/null +++ b/modules/opensles/opensles.h @@ -0,0 +1,18 @@ +/** + * @file opensles.h OpenSLES audio driver -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +extern SLObjectItf engineObject; +extern SLEngineItf engineEngine; + + +int opensles_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int opensles_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); diff --git a/modules/opensles/player.c b/modules/opensles/player.c new file mode 100644 index 0000000..a317e5d --- /dev/null +++ b/modules/opensles/player.c @@ -0,0 +1,172 @@ +/** + * @file opensles/player.c OpenSLES audio driver -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define DEBUG_MODULE "opensles/player" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + int16_t buf[160 * 2]; + auplay_write_h *wh; + void *arg; + + SLObjectItf outputMixObject; + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + SLAndroidSimpleBufferQueueItf BufferQueue; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + if (st->bqPlayerObject != NULL) + (*st->bqPlayerObject)->Destroy(st->bqPlayerObject); + + if (st->outputMixObject != NULL) + (*st->outputMixObject)->Destroy(st->outputMixObject); + + mem_deref(st->ap); +} + + +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct auplay_st *st = context; + + st->wh((void *)st->buf, sizeof(st->buf), st->arg); + + (*st->BufferQueue)->Enqueue(bq, st->buf, sizeof(st->buf)); +} + + +static int createOutput(struct auplay_st *st) +{ + const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; + const SLboolean req[1] = {SL_BOOLEAN_FALSE}; + SLresult r; + + r = (*engineEngine)->CreateOutputMix(engineEngine, + &st->outputMixObject, 1, ids, req); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->outputMixObject)->Realize(st->outputMixObject, + SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +static int createPlayer(struct auplay_st *st, struct auplay_prm *prm) +{ + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 + }; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN}; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + SLDataLocator_OutputMix loc_outmix = { + SL_DATALOCATOR_OUTPUTMIX, st->outputMixObject + }; + SLDataSink audioSnk = {&loc_outmix, NULL}; + const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + SLresult r; + + r = (*engineEngine)->CreateAudioPlayer(engineEngine, + &st->bqPlayerObject, + &audioSrc, &audioSnk, + ARRAY_SIZE(ids), ids, req); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("CreateAudioPlayer error: r = %d\n", r); + return ENODEV; + } + + r = (*st->bqPlayerObject)->Realize(st->bqPlayerObject, + SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject, + SL_IID_PLAY, + &st->bqPlayerPlay); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject, + SL_IID_BUFFERQUEUE, + &st->BufferQueue); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->BufferQueue)->RegisterCallback(st->BufferQueue, + bqPlayerCallback, st); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->bqPlayerPlay)->SetPlayState(st->bqPlayerPlay, + SL_PLAYSTATE_PLAYING); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +int opensles_player_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int err; + (void)device; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + err = createOutput(st); + if (err) + goto out; + + err = createPlayer(st, prm); + if (err) + goto out; + + /* kick-start the buffer callback */ + bqPlayerCallback(st->BufferQueue, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/opensles/recorder.c b/modules/opensles/recorder.c new file mode 100644 index 0000000..6301451 --- /dev/null +++ b/modules/opensles/recorder.c @@ -0,0 +1,224 @@ +/** + * @file opensles/recorder.c OpenSLES audio driver -- recording + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <pthread.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define DEBUG_MODULE "opensles/recorder" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + int16_t buf[160]; + pthread_t thread; + bool run; + ausrc_read_h *rh; + void *arg; + + SLObjectItf recObject; + SLRecordItf recRecord; + SLAndroidSimpleBufferQueueItf recBufferQueue; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->recObject != NULL) + (*st->recObject)->Destroy(st->recObject); + + mem_deref(st->as); +} + + +static void *record_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + SLresult r; + + while (st->run) { + + (void)sys_usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; +#if 1 + if (now > ts + 100) { + debug("opensles: cpu lagging behind (%u ms)\n", + now - ts); + } +#endif + + r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->buf, sizeof(st->buf)); + if (r != SL_RESULT_SUCCESS) { + DEBUG_WARNING("Enqueue: r = %d\n", r); + } + + ts += 20; + } + + return NULL; +} + + +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct ausrc_st *st = context; + (void)bq; + + st->rh((void *)st->buf, sizeof(st->buf), st->arg); +} + + +static int createAudioRecorder(struct ausrc_st *st, struct ausrc_prm *prm) +{ + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 + }; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN}; + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + SLresult r; + + r = (*engineEngine)->CreateAudioRecorder(engineEngine, + &st->recObject, + &audioSrc, + &audioSnk, 1, id, req); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("CreateAudioRecorder failed: r = %d\n", r); + return ENODEV; + } + + r = (*st->recObject)->Realize(st->recObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != r) { + DEBUG_WARNING("recorder: Realize r = %d\n", r); + return ENODEV; + } + + r = (*st->recObject)->GetInterface(st->recObject, SL_IID_RECORD, + &st->recRecord); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->recObject)->GetInterface(st->recObject, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &st->recBufferQueue); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + r = (*st->recBufferQueue)->RegisterCallback(st->recBufferQueue, + bqRecorderCallback, + st); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +static int startRecording(struct ausrc_st *st) +{ + SLresult r; + + (*st->recRecord)->SetRecordState(st->recRecord, + SL_RECORDSTATE_STOPPED); + (*st->recBufferQueue)->Clear(st->recBufferQueue); + +#if 0 + r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->buf, sizeof(st->buf)); + if (SL_RESULT_SUCCESS != r) + return ENODEV; +#endif + + r = (*st->recRecord)->SetRecordState(st->recRecord, + SL_RECORDSTATE_RECORDING); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + return 0; +} + + +int opensles_recorder_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err; + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + err = createAudioRecorder(st, prm); + if (err) { + DEBUG_WARNING("failed to create recorder\n"); + goto out; + } + + err = startRecording(st); + if (err) { + DEBUG_WARNING("failed to start recorder\n"); + goto out; + } + + st->run = true; + + err = pthread_create(&st->thread, NULL, record_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/opus/decode.c b/modules/opus/decode.c new file mode 100644 index 0000000..f2d67b1 --- /dev/null +++ b/modules/opus/decode.c @@ -0,0 +1,101 @@ +/** + * @file opus/decode.c Opus Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +struct audec_state { + OpusDecoder *dec; + unsigned ch; +}; + + +static void destructor(void *arg) +{ + struct audec_state *ads = arg; + + if (ads->dec) + opus_decoder_destroy(ads->dec); +} + + +int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + struct audec_state *ads; + int opuserr, err = 0; + (void)fmtp; + + if (!adsp || !ac || !ac->ch) + return EINVAL; + + ads = *adsp; + + if (ads) + return 0; + + ads = mem_zalloc(sizeof(*ads), destructor); + if (!ads) + return ENOMEM; + + ads->ch = ac->ch; + + ads->dec = opus_decoder_create(ac->srate, ac->ch, &opuserr); + if (!ads->dec) { + warning("opus: decoder create: %s\n", opus_strerror(opuserr)); + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(ads); + else + *adsp = ads; + + return err; +} + + +int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int n; + + if (!ads || !sampv || !sampc || !buf) + return EINVAL; + + n = opus_decode(ads->dec, buf, (opus_int32)len, + sampv, (int)(*sampc/ads->ch), 0); + if (n < 0) { + warning("opus: decode error: %s\n", opus_strerror(n)); + return EPROTO; + } + + *sampc = n * ads->ch; + + return 0; +} + + +int opus_decode_pkloss(struct audec_state *ads, int16_t *sampv, size_t *sampc) +{ + int n; + + if (!ads || !sampv || !sampc) + return EINVAL; + + n = opus_decode(ads->dec, NULL, 0, sampv, (int)(*sampc/ads->ch), 0); + if (n < 0) + return EPROTO; + + *sampc = n * ads->ch; + + return 0; +} diff --git a/modules/opus/encode.c b/modules/opus/encode.c new file mode 100644 index 0000000..3272490 --- /dev/null +++ b/modules/opus/encode.c @@ -0,0 +1,170 @@ +/** + * @file opus/encode.c Opus Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +struct auenc_state { + OpusEncoder *enc; + unsigned ch; +}; + + +static void destructor(void *arg) +{ + struct auenc_state *aes = arg; + + if (aes->enc) + opus_encoder_destroy(aes->enc); +} + + +static opus_int32 srate2bw(opus_int32 srate) +{ + if (srate >= 48000) + return OPUS_BANDWIDTH_FULLBAND; + else if (srate >= 24000) + return OPUS_BANDWIDTH_SUPERWIDEBAND; + else if (srate >= 16000) + return OPUS_BANDWIDTH_WIDEBAND; + else if (srate >= 12000) + return OPUS_BANDWIDTH_MEDIUMBAND; + else + return OPUS_BANDWIDTH_NARROWBAND; +} + + +#if 0 +static const char *bwname(opus_int32 bw) +{ + switch (bw) { + case OPUS_BANDWIDTH_FULLBAND: return "full"; + case OPUS_BANDWIDTH_SUPERWIDEBAND: return "superwide"; + case OPUS_BANDWIDTH_WIDEBAND: return "wide"; + case OPUS_BANDWIDTH_MEDIUMBAND: return "medium"; + case OPUS_BANDWIDTH_NARROWBAND: return "narrow"; + default: return "???"; + } +} + + +static const char *chname(opus_int32 ch) +{ + switch (ch) { + case OPUS_AUTO: return "auto"; + case 1: return "mono"; + case 2: return "stereo"; + default: return "???"; + } +} +#endif + + +int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *param, const char *fmtp) +{ + struct auenc_state *aes; + struct opus_param prm; + opus_int32 fch, vbr; + (void)param; + + if (!aesp || !ac || !ac->ch) + return EINVAL; + + aes = *aesp; + + if (!aes) { + const opus_int32 complex = 10; + int opuserr; + + aes = mem_zalloc(sizeof(*aes), destructor); + if (!aes) + return ENOMEM; + + aes->ch = ac->ch; + + aes->enc = opus_encoder_create(ac->srate, ac->ch, + /* this has big impact on cpu */ + OPUS_APPLICATION_AUDIO, + &opuserr); + if (!aes->enc) { + warning("opus: encoder create: %s\n", + opus_strerror(opuserr)); + mem_deref(aes); + return ENOMEM; + } + + (void)opus_encoder_ctl(aes->enc, OPUS_SET_COMPLEXITY(complex)); + + *aesp = aes; + } + + prm.srate = 48000; + prm.bitrate = OPUS_AUTO; + prm.stereo = 1; + prm.cbr = 0; + prm.inband_fec = 0; + prm.dtx = 0; + + opus_decode_fmtp(&prm, fmtp); + + fch = prm.stereo ? OPUS_AUTO : 1; + vbr = prm.cbr ? 0 : 1; + + (void)opus_encoder_ctl(aes->enc, + OPUS_SET_MAX_BANDWIDTH(srate2bw(prm.srate))); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_BITRATE(prm.bitrate)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_FORCE_CHANNELS(fch)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_VBR(vbr)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_INBAND_FEC(prm.inband_fec)); + (void)opus_encoder_ctl(aes->enc, OPUS_SET_DTX(prm.dtx)); + + +#if 0 + { + opus_int32 bw, complex; + + (void)opus_encoder_ctl(aes->enc, OPUS_GET_MAX_BANDWIDTH(&bw)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_BITRATE(&prm.bitrate)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_FORCE_CHANNELS(&fch)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_VBR(&vbr)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_INBAND_FEC(&prm.inband_fec)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_DTX(&prm.dtx)); + (void)opus_encoder_ctl(aes->enc, OPUS_GET_COMPLEXITY(&complex)); + + debug("opus: encode bw=%s bitrate=%i fch=%s " + "vbr=%i fec=%i dtx=%i complex=%i\n", + bwname(bw), prm.bitrate, chname(fch), + vbr, prm.inband_fec, prm.dtx, complex); + } +#endif + + return 0; +} + + +int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + opus_int32 n; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + n = opus_encode(aes->enc, sampv, (int)(sampc/aes->ch), + buf, (opus_int32)(*len)); + if (n < 0) { + warning("opus: encode error: %s\n", opus_strerror((int)n)); + return EPROTO; + } + + *len = n; + + return 0; +} diff --git a/modules/opus/module.mk b/modules/opus/module.mk new file mode 100644 index 0000000..ded0f13 --- /dev/null +++ b/modules/opus/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := opus +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += opus.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lopus -lm + +include mk/mod.mk diff --git a/modules/opus/opus.c b/modules/opus/opus.c new file mode 100644 index 0000000..28b24b9 --- /dev/null +++ b/modules/opus/opus.c @@ -0,0 +1,63 @@ +/** + * @file opus.c Opus Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +/** + * @defgroup opus opus + * + * The OPUS audio codec + * + * Latest supported version: libopus 1.0.0 + * + * References: + * + * draft-ietf-codec-opus-10 + * draft-spittka-payload-rtp-opus-00 + * + * http://opus-codec.org/downloads/ + */ + + +static struct aucodec opus = { + .name = "opus", + .srate = 48000, + .ch = 2, + .fmtp = "stereo=1;sprop-stereo=1", + .encupdh = opus_encode_update, + .ench = opus_encode_frm, + .decupdh = opus_decode_update, + .dech = opus_decode_frm, + .plch = opus_decode_pkloss, +}; + + +static int module_init(void) +{ + aucodec_register(&opus); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&opus); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(opus) = { + "opus", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/opus/opus.h b/modules/opus/opus.h new file mode 100644 index 0000000..2bed161 --- /dev/null +++ b/modules/opus/opus.h @@ -0,0 +1,34 @@ +/** + * @file opus.h Private Opus Interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct opus_param { + opus_int32 srate; + opus_int32 bitrate; + opus_int32 stereo; + opus_int32 cbr; + opus_int32 inband_fec; + opus_int32 dtx; +}; + + +/* Encode */ +int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp); +int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc); + + +/* Decode */ +int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp); +int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len); +int opus_decode_pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc); + + +/* SDP */ +void opus_decode_fmtp(struct opus_param *prm, const char *fmtp); diff --git a/modules/opus/sdp.c b/modules/opus/sdp.c new file mode 100644 index 0000000..024c8a6 --- /dev/null +++ b/modules/opus/sdp.c @@ -0,0 +1,51 @@ +/** + * @file opus/sdp.c Opus SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <opus/opus.h> +#include "opus.h" + + +static void assign_if(opus_int32 *v, const struct pl *pl, + uint32_t min, uint32_t max) +{ + const uint32_t val = pl_u32(pl); + + if (val < min || val > max) + return; + + *v = val; +} + + +void opus_decode_fmtp(struct opus_param *prm, const char *fmtp) +{ + struct pl pl, val; + + if (!prm || !fmtp) + return; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "maxplaybackrate", &val)) + assign_if(&prm->srate, &val, 8000, 48000); + + if (fmt_param_get(&pl, "maxaveragebitrate", &val)) + assign_if(&prm->bitrate, &val, 6000, 510000); + + if (fmt_param_get(&pl, "stereo", &val)) + assign_if(&prm->stereo, &val, 0, 1); + + if (fmt_param_get(&pl, "cbr", &val)) + assign_if(&prm->cbr, &val, 0, 1); + + if (fmt_param_get(&pl, "useinbandfec", &val)) + assign_if(&prm->inband_fec, &val, 0, 1); + + if (fmt_param_get(&pl, "usedtx", &val)) + assign_if(&prm->dtx, &val, 0, 1); +} diff --git a/modules/oss/module.mk b/modules/oss/module.mk new file mode 100644 index 0000000..dd4b09a --- /dev/null +++ b/modules/oss/module.mk @@ -0,0 +1,18 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := oss +$(MOD)_SRCS += oss.c +$(MOD)_LFLAGS += + +ifeq ($(OS), openbsd) +$(MOD)_LFLAGS += -lossaudio +endif +ifeq ($(OS), netbsd) +$(MOD)_LFLAGS += -lossaudio +endif + +include mk/mod.mk diff --git a/modules/oss/oss.c b/modules/oss/oss.c new file mode 100644 index 0000000..3acd9df --- /dev/null +++ b/modules/oss/oss.c @@ -0,0 +1,353 @@ +/** + * @file oss.c Open Sound System (OSS) driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#if defined(NETBSD) || defined(OPENBSD) +#include <soundcard.h> +#elif defined (LINUX) +#include <linux/soundcard.h> +#else +#include <sys/soundcard.h> +#endif + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + int fd; + struct mbuf *mb; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; +}; + +struct auplay_st { + struct auplay *ap; /* inheritance */ + pthread_t thread; + bool run; + int fd; + uint8_t *buf; + uint32_t sz; + auplay_write_h *wh; + void *arg; +}; + + +static struct ausrc *ausrc; +static struct auplay *auplay; +static char oss_dev[64] = "/dev/dsp"; + + +/* + * Automatically calculate the fragment size depending on sampling rate + * and number of channels. More entries can be added to the table below. + * + * NOTE. Powermac 8200 and linux 2.4.18 gives: + * SNDCTL_DSP_SETFRAGMENT: Invalid argument + */ +static int set_fragment(int fd, uint32_t sampc) +{ + static const struct { + uint16_t max; + uint16_t size; + } fragv[] = { + {10, 7}, /* 10 x 2^7 = 1280 = 4 x 320 */ + {15, 7}, /* 15 x 2^7 = 1920 = 6 x 320 */ + {20, 7}, /* 20 x 2^7 = 2560 = 8 x 320 */ + {25, 7}, /* 25 x 2^7 = 3200 = 10 x 320 */ + {15, 8}, /* 15 x 2^8 = 3840 = 12 x 320 */ + {20, 8}, /* 20 x 2^8 = 5120 = 16 x 320 */ + {25, 8} /* 25 x 2^8 = 6400 = 20 x 320 */ + }; + size_t i; + const uint32_t buf_size = 2 * sampc; + + for (i=0; i<ARRAY_SIZE(fragv); i++) { + const uint16_t frag_max = fragv[i].max; + const uint16_t frag_size = fragv[i].size; + const uint32_t fragment_size = frag_max * (1<<frag_size); + + if (0 == (fragment_size%buf_size)) { + int fragment = (frag_max<<16) | frag_size; + + if (0 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT, + &fragment)) { + return 0; + } + } + } + + return ENODEV; +} + + +static int oss_reset(int fd, uint32_t srate, uint8_t ch, int sampc, + int nonblock) +{ + int format = AFMT_S16_LE; + int speed = srate; + int channels = ch; + int blocksize = 0; + int err; + + err = set_fragment(fd, sampc); + if (err) + return err; + + if (0 != ioctl(fd, FIONBIO, &nonblock)) + return errno; + if (0 != ioctl(fd, SNDCTL_DSP_SETFMT, &format)) + return errno; + if (0 != ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)) + return errno; + if (2 == channels) { + int stereo = 1; + if (0 != ioctl(fd, SNDCTL_DSP_STEREO, &stereo)) + return errno; + } + if (0 != ioctl(fd, SNDCTL_DSP_SPEED, &speed)) + return errno; + + (void)ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize); + + info("oss: init: %u bit %d Hz %d ch, blocksize=%d\n", + format, speed, channels, blocksize); + + return 0; +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (-1 != st->fd) { + fd_close(st->fd); + (void)close(st->fd); + } + + mem_deref(st->buf); + mem_deref(st->ap); +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (-1 != st->fd) { + fd_close(st->fd); + (void)close(st->fd); + } + + mem_deref(st->mb); + mem_deref(st->as); +} + + +static void read_handler(int flags, void *arg) +{ + struct ausrc_st *st = arg; + struct mbuf *mb = st->mb; + int n; + (void)flags; + + n = read(st->fd, mbuf_buf(mb), mbuf_get_space(mb)); + if (n <= 0) + return; + + mb->pos += n; + + if (mb->pos < mb->size) + return; + + st->rh(mb->buf, mb->size, st->arg); + + mb->pos = 0; +} + + +static void *play_thread(void *arg) +{ + struct auplay_st *st = arg; + int n; + + while (st->run) { + + st->wh(st->buf, st->sz, st->arg); + + n = write(st->fd, st->buf, st->sz); + if (n < 0) { + warning("oss: write: %m\n", errno); + break; + } + } + + return NULL; +} + + +static int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + (void)ctx; + (void)errh; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->fd = -1; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + if (!device) + device = oss_dev; + + prm->fmt = AUFMT_S16LE; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->mb = mbuf_alloc(2 * sampc); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + st->fd = open(device, O_RDONLY); + if (st->fd < 0) { + err = errno; + goto out; + } + + err = fd_listen(st->fd, FD_READ, read_handler, st); + if (err) + goto out; + + err = oss_reset(st->fd, prm->srate, prm->ch, sampc, 1); + if (err) + goto out; + + st->as = mem_ref(as); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + unsigned sampc; + int err; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->fd = -1; + st->wh = wh; + st->arg = arg; + + if (!device) + device = oss_dev; + + prm->fmt = AUFMT_S16LE; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sz = 2 * sampc; + st->buf = mem_alloc(st->sz, NULL); + if (!st->buf) { + err = ENOMEM; + goto out; + } + + st->fd = open(device, O_WRONLY); + if (st->fd < 0) { + err = errno; + goto out; + } + + err = oss_reset(st->fd, prm->srate, prm->ch, sampc, 0); + if (err) + goto out; + + st->ap = mem_ref(ap); + + st->run = true; + err = pthread_create(&st->thread, NULL, play_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + int err; + + err = ausrc_register(&ausrc, "oss", src_alloc); + err |= auplay_register(&auplay, "oss", play_alloc); + + return err; +} + + +static int module_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(oss) = { + "oss", + "audio", + module_init, + module_close, +}; diff --git a/modules/plc/module.mk b/modules/plc/module.mk new file mode 100644 index 0000000..bc8ae4f --- /dev/null +++ b/modules/plc/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := plc +$(MOD)_SRCS += plc.c +$(MOD)_LFLAGS += "-lspandsp" + +include mk/mod.mk diff --git a/modules/plc/plc.c b/modules/plc/plc.c new file mode 100644 index 0000000..5409258 --- /dev/null +++ b/modules/plc/plc.c @@ -0,0 +1,109 @@ +/** + * @file plc.c PLC -- Packet Loss Concealment + * + * Copyright (C) 2010 Creytiv.com + */ +#include <spandsp.h> +#include <re.h> +#include <baresip.h> + + +struct plc_st { + struct aufilt_dec_st af; /* base class */ + plc_state_t plc; + size_t sampc; +}; + + +static void destructor(void *arg) +{ + struct plc_st *st = arg; + + list_unlink(&st->af.le); +} + + +static int update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct plc_st *st; + int err = 0; + (void)ctx; + (void)af; + + if (!stp || !prm) + return EINVAL; + + if (*stp) + return 0; + + /* XXX: add support for stereo PLC */ + if (prm->ch != 1) { + warning("plc: only mono supported (ch=%u)\n", prm->ch); + return ENOSYS; + } + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + if (!plc_init(&st->plc)) { + err = ENOMEM; + goto out; + } + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + out: + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +/* + * PLC is only valid for Decoding (RX) + * + * NOTE: sampc == 0 , means Packet loss + */ +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct plc_st *plc = (struct plc_st *)st; + + if (*sampc) + plc_rx(&plc->plc, sampv, (int)*sampc); + else + *sampc = plc_fillin(&plc->plc, sampv, (int)plc->sampc); + + return 0; +} + + +static struct aufilt plc = { + LE_INIT, "plc", NULL, NULL, update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&plc); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&plc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(plc) = { + "plc", + "filter", + module_init, + module_close +}; diff --git a/modules/portaudio/module.mk b/modules/portaudio/module.mk new file mode 100644 index 0000000..81e5b80 --- /dev/null +++ b/modules/portaudio/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := portaudio +$(MOD)_SRCS += portaudio.c +$(MOD)_LFLAGS += -lportaudio + +include mk/mod.mk diff --git a/modules/portaudio/portaudio.c b/modules/portaudio/portaudio.c new file mode 100644 index 0000000..36ca3f1 --- /dev/null +++ b/modules/portaudio/portaudio.c @@ -0,0 +1,331 @@ +/** + * @file portaudio.c Portaudio sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <portaudio.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* + * portaudio v19 is required + */ + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + PaStream *stream_rd; + ausrc_read_h *rh; + void *arg; + volatile bool ready; + unsigned ch; +}; + +struct auplay_st { + struct auplay *ap; /* inheritance */ + PaStream *stream_wr; + auplay_write_h *wh; + void *arg; + volatile bool ready; + unsigned ch; +}; + + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +/* + * This routine will be called by the PortAudio engine when audio is needed. + * It may called at interrupt level on some machines so don't do anything + * that could mess up the system like calling malloc() or free(). + */ +static int read_callback(const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData) +{ + struct ausrc_st *st = userData; + unsigned sampc; + + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->rh(inputBuffer, 2*sampc, st->arg); + + return paContinue; +} + + +static int write_callback(const void *inputBuffer, void *outputBuffer, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, void *userData) +{ + struct auplay_st *st = userData; + unsigned sampc; + + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->wh(outputBuffer, 2*sampc, st->arg); + + return paContinue; +} + + +static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, + uint32_t dev) +{ + PaStreamParameters prm_in; + PaError err; + unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; + + memset(&prm_in, 0, sizeof(prm_in)); + prm_in.device = dev; + prm_in.channelCount = prm->ch; + prm_in.sampleFormat = paInt16; + prm_in.suggestedLatency = 0.100; + + st->stream_rd = NULL; + err = Pa_OpenStream(&st->stream_rd, &prm_in, NULL, prm->srate, + frames_per_buffer, paNoFlag, read_callback, st); + if (paNoError != err) { + warning("portaudio: read: Pa_OpenStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + err = Pa_StartStream(st->stream_rd); + if (paNoError != err) { + warning("portaudio: read: Pa_StartStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + return 0; +} + + +static int write_stream_open(struct auplay_st *st, + const struct auplay_prm *prm, uint32_t dev) +{ + PaStreamParameters prm_out; + PaError err; + unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000; + + memset(&prm_out, 0, sizeof(prm_out)); + prm_out.device = dev; + prm_out.channelCount = prm->ch; + prm_out.sampleFormat = paInt16; + prm_out.suggestedLatency = 0.100; + + st->stream_wr = NULL; + err = Pa_OpenStream(&st->stream_wr, NULL, &prm_out, prm->srate, + frames_per_buffer, paNoFlag, write_callback, st); + if (paNoError != err) { + warning("portaudio: write: Pa_OpenStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + err = Pa_StartStream(st->stream_wr); + if (paNoError != err) { + warning("portaudio: write: Pa_StartStream: %s\n", + Pa_GetErrorText(err)); + return EINVAL; + } + + return 0; +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + st->ready = false; + + if (st->stream_rd) { + Pa_AbortStream(st->stream_rd); + Pa_CloseStream(st->stream_rd); + } + + mem_deref(st->as); +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + st->ready = false; + + if (st->stream_wr) { + Pa_AbortStream(st->stream_wr); + Pa_CloseStream(st->stream_wr); + } + + mem_deref(st->ap); +} + + +static int src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + PaDeviceIndex dev_index; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + if (str_isset(device)) + dev_index = atoi(device); + else + dev_index = Pa_GetDefaultInputDevice(); + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + st->ch = prm->ch; + + st->ready = true; + + err = read_stream_open(st, prm, dev_index); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + PaDeviceIndex dev_index; + int err; + + (void)device; + + if (!stp || !ap || !prm) + return EINVAL; + + if (str_isset(device)) + dev_index = atoi(device); + else + dev_index = Pa_GetDefaultOutputDevice(); + + prm->fmt = AUFMT_S16LE; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + st->ch = prm->ch; + + st->ready = true; + + err = write_stream_open(st, prm, dev_index); + if (err) + goto out; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int pa_init(void) +{ + PaError paerr; + int i, n, err = 0; + + paerr = Pa_Initialize(); + if (paNoError != paerr) { + warning("portaudio: init: %s\n", Pa_GetErrorText(paerr)); + return ENODEV; + } + + n = Pa_GetDeviceCount(); + + info("portaudio: device count is %d\n", n); + + for (i=0; i<n; i++) { + const PaDeviceInfo *devinfo; + + devinfo = Pa_GetDeviceInfo(i); + + debug("portaudio: device %d: %s\n", i, devinfo->name); + (void)devinfo; + } + + if (paNoDevice != Pa_GetDefaultInputDevice()) + err |= ausrc_register(&ausrc, "portaudio", src_alloc); + + if (paNoDevice != Pa_GetDefaultOutputDevice()) + err |= auplay_register(&auplay, "portaudio", play_alloc); + + return err; +} + + +static int pa_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + Pa_Terminate(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(portaudio) = { + "portaudio", + "sound", + pa_init, + pa_close +}; diff --git a/modules/presence/module.mk b/modules/presence/module.mk new file mode 100644 index 0000000..6ec5d17 --- /dev/null +++ b/modules/presence/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := presence +$(MOD)_SRCS += presence.c subscriber.c notifier.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/presence/notifier.c b/modules/presence/notifier.c new file mode 100644 index 0000000..ced7b75 --- /dev/null +++ b/modules/presence/notifier.c @@ -0,0 +1,270 @@ +/** + * @file notifier.c Presence notifier + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +/* + * Notifier - other people are subscribing to the status of our AOR. + * we must maintain a list of active notifications. we receive a SUBSCRIBE + * message from peer, and send NOTIFY to all peers when the Status changes + */ + + +struct notifier { + struct le le; + struct sipevent_sock *sock; + struct sipnot *not; + struct ua *ua; +}; + +static enum presence_status my_status = PRESENCE_OPEN; +static struct list notifierl; +static struct sipevent_sock *evsock; + + +static const char *presence_status_str(enum presence_status st) +{ + switch (st) { + + case PRESENCE_OPEN: return "open"; + case PRESENCE_CLOSED: return "closed"; + default: return "?"; + } +} + + +static int notify(struct notifier *not, enum presence_status status) +{ + const char *aor = ua_aor(not->ua); + struct mbuf *mb; + int err; + + mb = mbuf_alloc(1024); + if (!mb) + return ENOMEM; + + err = mbuf_printf(mb, + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n" + "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\r\n" + " xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\"\r\n" + " xmlns:rpid=\"urn:ietf:params:xml:ns:pidf:rpid\"\r\n" + " entity=\"%s\">\r\n" + " <dm:person id=\"p4159\"><rpid:activities/></dm:person>\r\n" + " <tuple id=\"t4109\">\r\n" + " <status>\r\n" + " <basic>%s</basic>\r\n" + " </status>\r\n" + " <contact>%s</contact>\r\n" + " </tuple>\r\n" + "</presence>\r\n" + ,aor, presence_status_str(status), aor); + if (err) + goto out; + + mb->pos = 0; + + err = sipevent_notify(not->not, mb, SIPEVENT_ACTIVE, 0, 0); + if (err) { + warning("presence: notify to %s failed (%m)\n", aor, err); + } + + out: + mem_deref(mb); + return err; +} + + +static void sipnot_close_handler(int err, const struct sip_msg *msg, + void *arg) +{ + struct notifier *not = arg; + + if (err) { + info("presence: notifier closed (%m)\n", err); + } + else if (msg) { + info("presence: notifier closed (%u %r)\n", + msg->scode, &msg->reason); + } + + not = mem_deref(not); +} + + +static void destructor(void *arg) +{ + struct notifier *not = arg; + + list_unlink(¬->le); + mem_deref(not->not); + mem_deref(not->sock); + mem_deref(not->ua); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + return account_auth(arg, username, password, realm); +} + + +static int notifier_alloc(struct notifier **notp, struct sipevent_sock *sock, + const struct sip_msg *msg, + const struct sipevent_event *se, struct ua *ua) +{ + struct notifier *not; + int err; + + if (!sock || !msg || !se) + return EINVAL; + + not = mem_zalloc(sizeof(*not), destructor); + if (!not) + return ENOMEM; + + not->sock = mem_ref(sock); + not->ua = mem_ref(ua); + + err = sipevent_accept(¬->not, sock, msg, NULL, se, 200, "OK", + 600, 600, 600, + ua_cuser(not->ua), "application/pidf+xml", + auth_handler, ua_prm(not->ua), true, + sipnot_close_handler, not, NULL); + if (err) { + warning("presence: sipevent_accept failed: %m\n", err); + goto out; + } + + list_append(¬ifierl, ¬->le, not); + + out: + if (err) + mem_deref(not); + else if (notp) + *notp = not; + + return err; +} + + +static int notifier_add(struct sipevent_sock *sock, const struct sip_msg *msg, + struct ua *ua) +{ + const struct sip_hdr *hdr; + struct sipevent_event se; + struct notifier *not; + int err; + + hdr = sip_msg_hdr(msg, SIP_HDR_EVENT); + if (!hdr) + return EPROTO; + + err = sipevent_event_decode(&se, &hdr->val); + if (err) + return err; + + if (pl_strcasecmp(&se.event, "presence")) { + info("presence: unexpected event '%r'\n", &se.event); + return EPROTO; + } + + err = notifier_alloc(¬, sock, msg, &se, ua); + if (err) + return err; + + (void)notify(not, my_status); + + return 0; +} + + +static void notifier_update_status(enum presence_status status) +{ + struct le *le; + + if (status == my_status) + return; + + info("presence: update my status from '%s' to '%s'\n", + contact_presence_str(my_status), + contact_presence_str(status)); + + my_status = status; + + for (le = notifierl.head; le; le = le->next) { + + struct notifier *not = le->data; + + (void)notify(not, status); + } +} + + +static int cmd_online(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + notifier_update_status(PRESENCE_OPEN); + return 0; +} + + +static int cmd_offline(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + notifier_update_status(PRESENCE_CLOSED); + return 0; +} + + +static const struct cmd cmdv[] = { + {'[', 0, "Set presence online", cmd_online }, + {']', 0, "Set presence offline", cmd_offline }, +}; + + +static bool sub_handler(const struct sip_msg *msg, void *arg) +{ + struct ua *ua; + + (void)arg; + + ua = uag_find(&msg->uri.user); + if (!ua) { + warning("presence: no UA found for %r\n", &msg->uri.user); + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return true; + } + + if (notifier_add(evsock, msg, ua)) + (void)sip_treply(NULL, uag_sip(), msg, 400, "Bad Presence"); + + return true; +} + + +int notifier_init(void) +{ + int err; + + err = sipevent_listen(&evsock, uag_sip(), 32, 32, sub_handler, NULL); + if (err) + return err; + + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +void notifier_close(void) +{ + cmd_unregister(cmdv); + list_flush(¬ifierl); + evsock = mem_deref(evsock); +} diff --git a/modules/presence/presence.c b/modules/presence/presence.c new file mode 100644 index 0000000..7ed4908 --- /dev/null +++ b/modules/presence/presence.c @@ -0,0 +1,41 @@ +/** + * @file presence.c Presence module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +static int module_init(void) +{ + int err; + + err = subscriber_init(); + if (err) + return err; + + err = notifier_init(); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + notifier_close(); + subscriber_close(); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(presence) = { + "presence", + "application", + module_init, + module_close +}; diff --git a/modules/presence/presence.h b/modules/presence/presence.h new file mode 100644 index 0000000..a1c5e31 --- /dev/null +++ b/modules/presence/presence.h @@ -0,0 +1,13 @@ +/** + * @file presence.h Presence module interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +int subscriber_init(void); +void subscriber_close(void); + + +int notifier_init(void); +void notifier_close(void); diff --git a/modules/presence/subscriber.c b/modules/presence/subscriber.c new file mode 100644 index 0000000..22361d5 --- /dev/null +++ b/modules/presence/subscriber.c @@ -0,0 +1,273 @@ +/** + * @file subscriber.c Presence subscriber + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +/* + * Subscriber - we subscribe to the status information of N resources. + * + * For each entry in the address book marked with ;presence=p2p, + * we send a SUBSCRIBE to that person, and expect to receive + * a NOTIFY when her status changes. + */ + + +struct presence { + struct le le; + struct sipsub *sub; + struct tmr tmr; + enum presence_status status; + unsigned failc; + struct contact *contact; +}; + +static struct list presencel; + + +static void tmr_handler(void *arg); + + +static uint32_t wait_term(const struct sipevent_substate *substate) +{ + uint32_t wait; + + switch (substate->reason) { + + case SIPEVENT_DEACTIVATED: + case SIPEVENT_TIMEOUT: + wait = 5; + break; + + case SIPEVENT_REJECTED: + case SIPEVENT_NORESOURCE: + wait = 3600; + break; + + case SIPEVENT_PROBATION: + case SIPEVENT_GIVEUP: + default: + wait = 300; + if (pl_isset(&substate->retry_after)) + wait = max(wait, pl_u32(&substate->retry_after)); + break; + } + + return wait; +} + + +static uint32_t wait_fail(unsigned failc) +{ + switch (failc) { + + case 1: return 30; + case 2: return 300; + case 3: return 3600; + default: return 86400; + } +} + + +static void notify_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + enum presence_status status = PRESENCE_CLOSED; + struct presence *pres = arg; + const struct sip_hdr *hdr; + struct pl pl; + + pres->failc = 0; + + hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_TYPE); + if (!hdr || 0 != pl_strcasecmp(&hdr->val, "application/pidf+xml")) { + + if (hdr) + warning("presence: unsupported content-type: '%r'\n", + &hdr->val); + + sip_treplyf(NULL, NULL, sip, msg, false, + 415, "Unsupported Media Type", + "Accept: application/pidf+xml\r\n" + "Content-Length: 0\r\n" + "\r\n"); + return; + } + + if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), + "<status>[^<]*<basic>[^<]*</basic>[^<]*</status>", + NULL, &pl, NULL)) { + + if (!pl_strcasecmp(&pl, "open")) + status = PRESENCE_OPEN; + } + + if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), + "<rpid:away/>")) { + + status = PRESENCE_CLOSED; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:busy/>")) { + + status = PRESENCE_BUSY; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:on-the-phone/>")) { + + status = PRESENCE_BUSY; + } + + (void)sip_treply(NULL, sip, msg, 200, "OK"); + + contact_set_presence(pres->contact, status); +} + + +static void close_handler(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, void *arg) +{ + struct presence *pres = arg; + uint32_t wait; + + pres->sub = mem_deref(pres->sub); + + info("presence: subscriber closed <%r>: ", + &contact_addr(pres->contact)->auri); + + if (substate) { + info("%s", sipevent_reason_name(substate->reason)); + wait = wait_term(substate); + } + else if (msg) { + info("%u %r", msg->scode, &msg->reason); + wait = wait_fail(++pres->failc); + } + else { + info("%m", err); + wait = wait_fail(++pres->failc); + } + + info("; will retry in %u secs (failc=%u)\n", wait, pres->failc); + + tmr_start(&pres->tmr, wait * 1000, tmr_handler, pres); + + contact_set_presence(pres->contact, PRESENCE_UNKNOWN); +} + + +static void destructor(void *arg) +{ + struct presence *pres = arg; + + list_unlink(&pres->le); + tmr_cancel(&pres->tmr); + mem_deref(pres->contact); + mem_deref(pres->sub); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + return account_auth(arg, username, password, realm); +} + + +static int subscribe(struct presence *pres) +{ + const char *routev[1]; + struct ua *ua; + char uri[256]; + int err; + + /* We use the first UA */ + ua = uag_find_aor(NULL); + if (!ua) { + warning("presence: no UA found\n"); + return ENOENT; + } + + pl_strcpy(&contact_addr(pres->contact)->auri, uri, sizeof(uri)); + + routev[0] = ua_outbound(ua); + + err = sipevent_subscribe(&pres->sub, uag_sipevent_sock(), uri, NULL, + ua_aor(ua), "presence", NULL, 600, + ua_cuser(ua), routev, routev[0] ? 1 : 0, + auth_handler, ua_prm(ua), true, NULL, + notify_handler, close_handler, pres, + "%H", ua_print_supported, ua); + if (err) { + warning("presence: sipevent_subscribe failed: %m\n", err); + } + + return err; +} + + +static void tmr_handler(void *arg) +{ + struct presence *pres = arg; + + if (subscribe(pres)) { + tmr_start(&pres->tmr, wait_fail(++pres->failc) * 1000, + tmr_handler, pres); + } +} + + +static int presence_alloc(struct contact *contact) +{ + struct presence *pres; + + pres = mem_zalloc(sizeof(*pres), destructor); + if (!pres) + return ENOMEM; + + pres->status = PRESENCE_UNKNOWN; + pres->contact = mem_ref(contact); + + tmr_init(&pres->tmr); + tmr_start(&pres->tmr, 1000, tmr_handler, pres); + + list_append(&presencel, &pres->le, pres); + + return 0; +} + + +int subscriber_init(void) +{ + struct le *le; + int err = 0; + + for (le = list_head(contact_list()); le; le = le->next) { + + struct contact *c = le->data; + struct sip_addr *addr = contact_addr(c); + struct pl val; + + if (0 == sip_param_decode(&addr->params, "presence", &val) && + 0 == pl_strcasecmp(&val, "p2p")) { + + err |= presence_alloc(le->data); + } + } + + info("Subscribing to %u contacts\n", list_count(&presencel)); + + return err; +} + + +void subscriber_close(void) +{ + list_flush(&presencel); +} diff --git a/modules/qtcapture/module.mk b/modules/qtcapture/module.mk new file mode 100644 index 0000000..3c73139 --- /dev/null +++ b/modules/qtcapture/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := qtcapture +$(MOD)_SRCS += qtcapture.m +$(MOD)_LFLAGS += -framework Cocoa -framework QTKit -framework CoreVideo + +include mk/mod.mk diff --git a/modules/qtcapture/qtcapture.m b/modules/qtcapture/qtcapture.m new file mode 100644 index 0000000..b5fce0e --- /dev/null +++ b/modules/qtcapture/qtcapture.m @@ -0,0 +1,403 @@ +/** + * @file qtcapture.m Video source using QTKit QTCapture + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <QTKit/QTKit.h> + + +static void frame_handler(struct vidsrc_st *st, + const CVImageBufferRef videoFrame); +static struct vidsrc *vidsrc; + + +@interface qtcap : NSObject +{ + QTCaptureSession *sess; + QTCaptureDeviceInput *input; + QTCaptureDecompressedVideoOutput *output; + struct vidsrc_st *vsrc; +} +@end + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + qtcap *cap; + struct lock *lock; + struct vidsz app_sz; + struct vidsz sz; + struct mbuf *buf; + vidsrc_frame_h *frameh; + void *arg; + bool started; +#ifdef QTCAPTURE_RUNLOOP + struct tmr tmr; +#endif +}; + + +@implementation qtcap + + +- (id)init:(struct vidsrc_st *)st + dev:(const char *)name +{ + NSAutoreleasePool *pool; + QTCaptureDevice *dev; + BOOL success = NO; + NSError *err; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return nil; + + self = [super init]; + if (!self) + goto out; + + vsrc = st; + sess = [[QTCaptureSession alloc] init]; + if (!sess) + goto out; + + if (str_isset(name)) { + NSString *s = [NSString stringWithUTF8String:name]; + dev = [QTCaptureDevice deviceWithUniqueID:s]; + info("qtcapture: using device: %s\n", name); + } + else { + dev = [QTCaptureDevice + defaultInputDeviceWithMediaType:QTMediaTypeVideo]; + } + + success = [dev open:&err]; + if (!success) + goto out; + + input = [[QTCaptureDeviceInput alloc] initWithDevice:dev]; + success = [sess addInput:input error:&err]; + if (!success) + goto out; + + output = [[QTCaptureDecompressedVideoOutput alloc] init]; + [output setDelegate:self]; + [output setPixelBufferAttributes: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:st->app_sz.h], kCVPixelBufferHeightKey, + [NSNumber numberWithInt:st->app_sz.w], kCVPixelBufferWidthKey, +#if 0 + /* This does not work reliably */ + [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8Planar], + (id)kCVPixelBufferPixelFormatTypeKey, +#endif + nil]]; + + success = [sess addOutput:output error:&err]; + if (!success) + goto out; + + /* Start */ + [sess startRunning]; + + out: + if (!success && self) { + [self dealloc]; + self = nil; + } + + [pool release]; + + return self; +} + + +- (void)stop:(id)unused +{ + (void)unused; + + [sess stopRunning]; + + if ([[input device] isOpen]) { + [[input device] close]; + [sess removeInput:input]; + [input release]; + } + + if (output) { + [output setDelegate:nil]; + [sess removeOutput:output]; + [output release]; + } +} + + +- (void)dealloc +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [self performSelectorOnMainThread:@selector(stop:) + withObject:nil + waitUntilDone:YES]; + + [sess release]; + + [super dealloc]; + + [pool release]; +} + + +- (void)captureOutput:(QTCaptureOutput *)captureOutput + didOutputVideoFrame:(CVImageBufferRef)videoFrame + withSampleBuffer:(QTSampleBuffer *)sampleBuffer + fromConnection:(QTCaptureConnection *)connection +{ + (void)captureOutput; + (void)sampleBuffer; + (void)connection; + +#if 0 + printf("got frame: %zu x %zu - fmt=0x%08x\n", + CVPixelBufferGetWidth(videoFrame), + CVPixelBufferGetHeight(videoFrame), + CVPixelBufferGetPixelFormatType(videoFrame)); +#endif + + frame_handler(vsrc, videoFrame); +} + + +@end + + +static enum vidfmt get_pixfmt(OSType type) +{ + switch (type) { + + case kCVPixelFormatType_420YpCbCr8Planar: return VID_FMT_YUV420P; + case kCVPixelFormatType_422YpCbCr8: return VID_FMT_UYVY422; + case 0x79757673: /* yuvs */ return VID_FMT_YUYV422; + case kCVPixelFormatType_32ARGB: return VID_FMT_ARGB; + default: return -1; + } +} + + +static inline void avpict_init_planar(struct vidframe *p, + const CVImageBufferRef f) +{ + int i; + + if (!p) + return; + + for (i=0; i<3; i++) { + p->data[i] = CVPixelBufferGetBaseAddressOfPlane(f, i); + p->linesize[i] = (int)CVPixelBufferGetBytesPerRowOfPlane(f, i); + } + + p->data[3] = NULL; + p->linesize[3] = 0; +} + + +static inline void avpict_init_chunky(struct vidframe *p, + const CVImageBufferRef f) +{ + p->data[0] = CVPixelBufferGetBaseAddress(f); + p->linesize[0] = (int)CVPixelBufferGetBytesPerRow(f); + + p->data[1] = p->data[2] = p->data[3] = NULL; + p->linesize[1] = p->linesize[2] = p->linesize[3] = 0; +} + + +static void frame_handler(struct vidsrc_st *st, + const CVImageBufferRef videoFrame) +{ + struct vidframe src; + vidsrc_frame_h *frameh; + void *arg; + enum vidfmt vidfmt; + + lock_write_get(st->lock); + frameh = st->frameh; + arg = st->arg; + lock_rel(st->lock); + + if (!frameh) + return; + + vidfmt = get_pixfmt(CVPixelBufferGetPixelFormatType(videoFrame)); + if (vidfmt == (enum vidfmt)-1) { + warning("qtcapture: unknown pixel format: 0x%08x\n", + CVPixelBufferGetPixelFormatType(videoFrame)); + return; + } + + st->started = true; + + st->sz.w = (int)CVPixelBufferGetWidth(videoFrame); + st->sz.h = (int)CVPixelBufferGetHeight(videoFrame); + + CVPixelBufferLockBaseAddress(videoFrame, 0); + + if (CVPixelBufferIsPlanar(videoFrame)) + avpict_init_planar(&src, videoFrame); + else + avpict_init_chunky(&src, videoFrame); + + src.fmt = vidfmt; + src.size = st->sz; + + CVPixelBufferUnlockBaseAddress(videoFrame, 0); + + frameh(&src, arg); +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + +#ifdef QTCAPTURE_RUNLOOP + tmr_cancel(&st->tmr); +#endif + + lock_write_get(st->lock); + st->frameh = NULL; + lock_rel(st->lock); + + [st->cap dealloc]; + + mem_deref(st->buf); + mem_deref(st->lock); + + mem_deref(st->vs); +} + + +#ifdef QTCAPTURE_RUNLOOP +static void tmr_handler(void *arg) +{ + struct vidsrc_st *st = arg; + + /* Check if frame_handler was called */ + if (st->started) + return; + + tmr_start(&st->tmr, 100, tmr_handler, st); + + /* Simulate the Run-Loop */ + (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); +} +#endif + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + if (size) + st->app_sz = *size; + + err = lock_alloc(&st->lock); + if (err) + goto out; + + st->cap = [[qtcap alloc] init:st dev:dev]; + if (!st->cap) { + err = ENODEV; + goto out; + } + +#ifdef QTCAPTURE_RUNLOOP + tmr_start(&st->tmr, 10, tmr_handler, st); +#endif + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static void device_info(void) +{ + NSAutoreleasePool *pool; + NSArray *devs; + + pool = [[NSAutoreleasePool alloc] init]; + if (!pool) + return; + + devs = [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]; + + if (devs && [devs count] > 1) { + QTCaptureDevice *d; + + debug("qtcapture: devices:\n"); + + for (d in devs) { + NSString *name = [d localizedDisplayName]; + + debug(" %s: %s\n", + [[d uniqueID] UTF8String], + [name UTF8String]); + } + } + + [pool release]; +} + + +static int module_init(void) +{ + device_info(); + return vidsrc_register(&vidsrc, "qtcapture", alloc, NULL); +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(qtcapture) = { + "qtcapture", + "vidsrc", + module_init, + module_close +}; diff --git a/modules/quicktime/module.mk b/modules/quicktime/module.mk new file mode 100644 index 0000000..af67862 --- /dev/null +++ b/modules/quicktime/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := quicktime +$(MOD)_SRCS += quicktime.c +$(MOD)_LFLAGS += -framework QuickTime -lswscale + +include mk/mod.mk diff --git a/modules/quicktime/quicktime.c b/modules/quicktime/quicktime.c new file mode 100644 index 0000000..ad746a0 --- /dev/null +++ b/modules/quicktime/quicktime.c @@ -0,0 +1,328 @@ +/** + * @file quicktime.c Quicktime video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <pthread.h> +#include <re.h> +#include <QuickTime/QuickTimeComponents.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> +#include <baresip.h> + + +#define DEBUG_MODULE "quicktime" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + pthread_t thread; + pthread_mutex_t mutex; + struct vidsz sz; + SeqGrabComponent seq_grab; + SGDataUPP upp; + SGChannel ch; + struct mbuf *buf; + struct SwsContext *sws; + vidsrc_frame_h *frameh; + void *arg; + bool run; +}; + + +static struct vidsrc *vidsrc; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->seq_grab) { + + pthread_mutex_lock(&st->mutex); + SGStop(st->seq_grab); + pthread_mutex_unlock(&st->mutex); + + if (st->run) { + pthread_join(st->thread, NULL); + } + + if (st->upp) { + DisposeSGDataUPP(st->upp); + } + + if (st->ch) { + SGDisposeChannel(st->seq_grab, st->ch); + } + + CloseComponent(st->seq_grab); + } + + if (st->sws) + sws_freeContext(st->sws); + + mem_deref(st->buf); + mem_deref(st->vs); +} + + +static OSErr frame_handler(SGChannel c, Ptr p, long len, long *offset, + long chRefCon, TimeValue timeval, short writeType, + long refCon) +{ + struct vidsrc_st *st = (struct vidsrc_st *)refCon; + ImageDescriptionHandle imageDesc; + AVPicture pict_src, pict_dst; + struct vidframe vidframe; + ComponentResult result; + int i, ret; + int new_len; + (void)c; + (void)p; + (void)len; + (void)offset; + (void)chRefCon; + (void)timeval; + (void)writeType; + (void)refCon; + + if (!st->buf) { + + imageDesc = (ImageDescriptionHandle)NewHandle(0); + if (!imageDesc) + return noErr; + + result = SGGetChannelSampleDescription(c,(Handle)imageDesc); + if (result != noErr) { + re_printf("GetChanSampDesc: %d\n", result); + DisposeHandle((Handle)imageDesc); + return noErr; + } + + st->sz.w = (*imageDesc)->width; + st->sz.h = (*imageDesc)->height; + + /* buffer size after scaling */ + new_len = avpicture_get_size(PIX_FMT_YUV420P, + st->sz.w, st->sz.h); + +#if 1 + re_fprintf(stderr, "got frame len=%u (%ux%u) [%s] depth=%u\n", + len, st->sz.w, st->sz.h, + (*imageDesc)->name, (*imageDesc)->depth); +#endif + + DisposeHandle((Handle)imageDesc); + + st->buf = mbuf_alloc(new_len); + if (!st->buf) + return noErr; + } + + if (!st->sws) { + st->sws = sws_getContext(st->sz.w, st->sz.h, PIX_FMT_YUYV422, + st->sz.w, st->sz.h, PIX_FMT_YUV420P, + SWS_BICUBIC, NULL, NULL, NULL); + if (!st->sws) + return noErr; + } + + avpicture_fill(&pict_src, (uint8_t *)p, PIX_FMT_YUYV422, + st->sz.w, st->sz.h); + + avpicture_fill(&pict_dst, st->buf->buf, PIX_FMT_YUV420P, + st->sz.w, st->sz.h); + + ret = sws_scale(st->sws, + pict_src.data, pict_src.linesize, 0, st->sz.h, + pict_dst.data, pict_dst.linesize); + + if (ret <= 0) { + re_fprintf(stderr, "scale: sws_scale: returned %d\n", ret); + return noErr; + } + + for (i=0; i<4; i++) { + vidframe.data[i] = pict_dst.data[i]; + vidframe.linesize[i] = pict_dst.linesize[i]; + } + + vidframe.size = st->sz; + vidframe.valid = true; + + st->frameh(&vidframe, st->arg); + + return noErr; +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + ComponentResult result; + + for (;;) { + pthread_mutex_lock(&st->mutex); + result = SGIdle(st->seq_grab); + pthread_mutex_unlock(&st->mutex); + + if (result != noErr) + break; + + usleep(10000); + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, + struct vidsrc_prm *prm, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + ComponentResult result; + Rect rect; + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + re_printf("quicktime alloc: %u x %u fps=%d\n", + prm->size.w, prm->size.h, prm->fps); + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) { + re_fprintf(stderr, "mutex error: %s\n", strerror(err)); + goto out; + } + + st->seq_grab = OpenDefaultComponent(SeqGrabComponentType, 0); + if (!st->seq_grab) { + re_fprintf(stderr, "Unable to open component\n"); + err = ENODEV; + goto out; + } + + result = SGInitialize(st->seq_grab); + if (result != noErr) { + re_fprintf(stderr, "Unable to initialize sequence grabber\n"); + err = ENODEV; + goto out; + } + + result = SGSetGWorld(st->seq_grab, NULL, NULL); + if (result != noErr) { + re_fprintf(stderr, "Unable to set gworld\n"); + err = ENODEV; + goto out; + } + + result = SGSetDataRef(st->seq_grab, 0, 0, seqGrabDontMakeMovie); + if (result != noErr) { + re_fprintf(stderr, "Unable to set data ref\n"); + err = ENODEV; + goto out; + } + + result = SGNewChannel(st->seq_grab, VideoMediaType, &st->ch); + if (result != noErr) { + re_fprintf(stderr, "Unable to allocate channel (result=%d)\n", + result); + err = ENOMEM; + goto out; + } + + /* XXX: check flags */ + result = SGSetChannelUsage(st->ch, + seqGrabRecord | + seqGrabLowLatencyCapture); + if (result != noErr) { + re_fprintf(stderr, "Unable to set channel usage\n"); + err = ENODEV; + goto out; + } + + rect.top = 0; + rect.left = 0; + rect.bottom = prm->size.h; + rect.right = prm->size.w; + + result = SGSetChannelBounds(st->ch, &rect); + if (result != noErr) { + re_fprintf(stderr, "Unable to set channel bounds\n"); + err = ENODEV; + goto out; + } + + st->upp = NewSGDataUPP(frame_handler); + if (!st->upp) { + re_fprintf(stderr, "Unable to allocate data upp\n"); + err = ENOMEM; + goto out; + } + + result = SGSetDataProc(st->seq_grab, st->upp, (long)st); + if (result != noErr) { + re_fprintf(stderr, "Unable to sg callback\n"); + err = ENODEV; + goto out; + } + + result = SGStartRecord(st->seq_grab); + if (result != noErr) { + re_fprintf(stderr, "Unable to start record: %d\n", result); + err = ENODEV; + goto out; + } + + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + re_fprintf(stderr, "thread error: %s\n", strerror(err)); + goto out; + } + + st->run = true; + + out: + if (err) + mem_deref(st); + else + *stp = st; + return err; +} + + +static int qt_init(void) +{ + return vidsrc_register(&vidsrc, "quicktime", alloc, NULL); +} + + +static int qt_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(quicktime) = { + "quicktime", + "videosrc", + qt_init, + qt_close +}; diff --git a/modules/rst/audio.c b/modules/rst/audio.c new file mode 100644 index 0000000..554ec21 --- /dev/null +++ b/modules/rst/audio.c @@ -0,0 +1,263 @@ +/** + * @file rst/audio.c MP3/ICY HTTP Audio Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#define _BSD_SOURCE 1 +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <mpg123.h> +#include "rst.h" + + +struct ausrc_st { + struct ausrc *as; + pthread_t thread; + struct rst *rst; + mpg123_handle *mp3; + struct aubuf *aubuf; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; + bool run; + uint32_t psize; + uint32_t ptime; +}; + + +static struct ausrc *ausrc; + + +static void destructor(void *arg) +{ + struct ausrc_st *st = arg; + + rst_set_audio(st->rst, NULL); + mem_deref(st->rst); + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->mp3) { + mpg123_close(st->mp3); + mpg123_delete(st->mp3); + } + + mem_deref(st->aubuf); + mem_deref(st->as); +} + + +static void *play_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + uint8_t *buf; + + buf = mem_alloc(st->psize, NULL); + if (!buf) + return NULL; + + while (st->run) { + + (void)usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; +#if 1 + if (now > ts + 100) { + re_printf("rst: cpu lagging behind (%u ms)\n", + now - ts); + } +#endif + + aubuf_read(st->aubuf, buf, st->psize); + + st->rh(buf, st->psize, st->arg); + + ts += st->ptime; + } + + mem_deref(buf); + + return NULL; +} + + +static inline int decode(struct ausrc_st *st) +{ + int err, ch, encoding; + struct mbuf *mb; + long srate; + + mb = mbuf_alloc(4096); + if (!mb) + return ENOMEM; + + err = mpg123_read(st->mp3, mb->buf, mb->size, &mb->end); + + switch (err) { + + case MPG123_NEW_FORMAT: + mpg123_getformat(st->mp3, &srate, &ch, &encoding); + re_printf("rst: new format: %i hz, %i ch, encoding 0x%04x\n", + srate, ch, encoding); + /*@fallthrough@*/ + + case MPG123_OK: + case MPG123_NEED_MORE: + if (mb->end == 0) + break; + aubuf_append(st->aubuf, mb); + break; + + default: + re_printf("rst: mpg123_read error: %s\n", + mpg123_plain_strerror(err)); + break; + } + + mem_deref(mb); + + return err; +} + + +void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz) +{ + int err; + + if (!st) + return; + + err = mpg123_feed(st->mp3, buf, sz); + if (err) + return; + + while (MPG123_OK == decode(st)) + ; +} + + +static int alloc_handler(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *dev, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + unsigned sampc; + int err; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->errh = errh; + st->arg = arg; + + st->mp3 = mpg123_new(NULL, &err); + if (!st->mp3) { + err = ENODEV; + goto out; + } + + err = mpg123_open_feed(st->mp3); + if (err != MPG123_OK) { + re_printf("rst: mpg123_open_feed: %s\n", + mpg123_strerror(st->mp3)); + err = ENODEV; + goto out; + } + + /* Set wanted output format */ + mpg123_format_none(st->mp3); + mpg123_format(st->mp3, prm->srate, prm->ch, MPG123_ENC_SIGNED_16); + mpg123_volume(st->mp3, 0.3); + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->ptime = prm->ptime; + st->psize = sampc * 2; + + prm->fmt = AUFMT_S16LE; + + re_printf("rst: audio ptime=%u psize=%u aubuf=[%u:%u]\n", + st->ptime, st->psize, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + + /* 1 - 20 seconds of audio */ + err = aubuf_alloc(&st->aubuf, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + if (err) + goto out; + + if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) { + st->rst = mem_ref(*ctx); + } + else { + err = rst_alloc(&st->rst, dev); + if (err) + goto out; + + if (ctx) + *ctx = (struct media_ctx *)st->rst; + } + + rst_set_audio(st->rst, st); + + st->run = true; + + err = pthread_create(&st->thread, NULL, play_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int rst_audio_init(void) +{ + int err; + + err = mpg123_init(); + if (err != MPG123_OK) { + re_printf("rst: mpg123_init: %s\n", + mpg123_plain_strerror(err)); + return ENODEV; + } + + return ausrc_register(&ausrc, "rst", alloc_handler); +} + + +void rst_audio_close(void) +{ + ausrc = mem_deref(ausrc); + + mpg123_exit(); +} diff --git a/modules/rst/module.mk b/modules/rst/module.mk new file mode 100644 index 0000000..6026c22 --- /dev/null +++ b/modules/rst/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2011 Creytiv.com +# + +MOD := rst +$(MOD)_SRCS += audio.c +$(MOD)_SRCS += rst.c +$(MOD)_SRCS += video.c +$(MOD)_LFLAGS += `pkg-config --libs cairo libmpg123` +CFLAGS += `pkg-config --cflags cairo libmpg123` + +include mk/mod.mk diff --git a/modules/rst/rst.c b/modules/rst/rst.c new file mode 100644 index 0000000..21b5bfe --- /dev/null +++ b/modules/rst/rst.c @@ -0,0 +1,408 @@ +/** + * @file rst.c MP3/ICY HTTP AV Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "rst.h" + + +enum { + RETRY_WAIT = 10000, +}; + + +struct rst { + const char *id; + struct ausrc_st *ausrc_st; + struct vidsrc_st *vidsrc_st; + struct tmr tmr; + struct dns_query *dnsq; + struct tcp_conn *tc; + struct mbuf *mb; + char *host; + char *path; + char *name; + char *meta; + bool head_recv; + size_t metaint; + size_t metasz; + size_t bytec; + uint16_t port; +}; + + +static int rst_connect(struct rst *rst); + + +static void destructor(void *arg) +{ + struct rst *rst = arg; + + tmr_cancel(&rst->tmr); + mem_deref(rst->dnsq); + mem_deref(rst->tc); + mem_deref(rst->mb); + mem_deref(rst->host); + mem_deref(rst->path); + mem_deref(rst->name); + mem_deref(rst->meta); +} + + +static void reconnect(void *arg) +{ + struct rst *rst = arg; + int err; + + rst->mb = mem_deref(rst->mb); + rst->name = mem_deref(rst->name); + rst->meta = mem_deref(rst->meta); + + rst->head_recv = false; + rst->metaint = 0; + rst->metasz = 0; + rst->bytec = 0; + + err = rst_connect(rst); + if (err) + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); +} + + +static void recv_handler(struct mbuf *mb, void *arg) +{ + struct rst *rst = arg; + size_t n; + + if (!rst->head_recv) { + + struct pl hdr, name, metaint, eoh; + + if (rst->mb) { + size_t pos; + int err; + + pos = rst->mb->pos; + + rst->mb->pos = rst->mb->end; + + err = mbuf_write_mem(rst->mb, mbuf_buf(mb), + mbuf_get_left(mb)); + if (err) { + re_printf("rst: buffer write error: %m\n", + err); + rst->tc = mem_deref(rst->tc); + tmr_start(&rst->tmr, RETRY_WAIT, + reconnect, rst); + return; + } + + rst->mb->pos = pos; + } + else { + rst->mb = mem_ref(mb); + } + + if (re_regex((const char *)mbuf_buf(rst->mb), + mbuf_get_left(rst->mb), + "[^\r\n]1\r\n\r\n", &eoh)) + return; + + rst->head_recv = true; + + hdr.p = (const char *)mbuf_buf(rst->mb); + hdr.l = eoh.p + 5 - hdr.p; + + if (!re_regex(hdr.p, hdr.l, "icy-name:[ \t]*[^\r\n]+\r\n", + NULL, &name)) + (void)pl_strdup(&rst->name, &name); + + if (!re_regex(hdr.p, hdr.l, "icy-metaint:[ \t]*[0-9]+\r\n", + NULL, &metaint)) + rst->metaint = pl_u32(&metaint); + + if (rst->metaint == 0) { + re_printf("rst: icy meta interval not available\n"); + rst->tc = mem_deref(rst->tc); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } + + rst_video_update(rst->vidsrc_st, rst->name, NULL); + + rst->mb->pos += hdr.l; + + re_printf("rst: name='%s' metaint=%zu\n", + rst->name, rst->metaint); + + if (rst->mb->pos >= rst->mb->end) + return; + + mb = rst->mb; + } + + while (mb->pos < mb->end) { + + if (rst->metasz > 0) { + + n = min(mbuf_get_left(mb), rst->metasz - rst->bytec); + + if (rst->meta) + mbuf_read_mem(mb, + (uint8_t *)&rst->meta[rst->bytec], + n); + else + mb->pos += n; + + rst->bytec += n; +#if 0 + re_printf("rst: metadata %zu bytes\n", n); +#endif + if (rst->bytec >= rst->metasz) { +#if 0 + re_printf("rst: metadata: [%s]\n", rst->meta); +#endif + rst->metasz = 0; + rst->bytec = 0; + + rst_video_update(rst->vidsrc_st, rst->name, + rst->meta); + } + } + else if (rst->bytec < rst->metaint) { + + n = min(mbuf_get_left(mb), rst->metaint - rst->bytec); + + rst_audio_feed(rst->ausrc_st, mbuf_buf(mb), n); + + rst->bytec += n; + mb->pos += n; +#if 0 + re_printf("rst: mp3data %zu bytes\n", n); +#endif + } + else { + rst->metasz = mbuf_read_u8(mb) * 16; + rst->bytec = 0; + + rst->meta = mem_deref(rst->meta); + rst->meta = mem_zalloc(rst->metasz + 1, NULL); +#if 0 + re_printf("rst: metalength %zu bytes\n", rst->metasz); +#endif + } + } +} + + +static void estab_handler(void *arg) +{ + struct rst *rst = arg; + struct mbuf *mb; + int err; + + re_printf("rst: connection established\n"); + + mb = mbuf_alloc(512); + if (!mb) { + err = ENOMEM; + goto out; + } + + err = mbuf_printf(mb, + "GET %s HTTP/1.0\r\n" + "Icy-MetaData: 1\r\n" + "\r\n", + rst->path); + if (err) + goto out; + + mb->pos = 0; + + err = tcp_send(rst->tc, mb); + if (err) + goto out; + + out: + if (err) { + re_printf("rst: error sending HTTP request: %m\n", err); + } + + mem_deref(mb); +} + + +static void close_handler(int err, void *arg) +{ + struct rst *rst = arg; + + re_printf("rst: tcp closed: %i\n", err); + + rst->tc = mem_deref(rst->tc); + + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); +} + + +static void dns_handler(int err, const struct dnshdr *hdr, struct list *ansl, + struct list *authl, struct list *addl, void *arg) +{ + struct rst *rst = arg; + struct dnsrr *rr; + struct sa srv; + + (void)err; + (void)hdr; + (void)authl; + (void)addl; + + rr = dns_rrlist_find(ansl, rst->host, DNS_TYPE_A, DNS_CLASS_IN, true); + if (!rr) { + re_printf("rst: unable to resolve: %s\n", rst->host); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } + + sa_set_in(&srv, rr->rdata.a.addr, rst->port); + + err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler, + close_handler, rst); + if (err) { + re_printf("rst: tcp connect error: %m\n", err); + tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst); + return; + } +} + + +static int rst_connect(struct rst *rst) +{ + struct sa srv; + int err; + + if (!sa_set_str(&srv, rst->host, rst->port)) { + + err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler, + close_handler, rst); + if (err) { + re_printf("rst: tcp connect error: %m\n", err); + } + } + else { + err = dnsc_query(&rst->dnsq, net_dnsc(), rst->host, DNS_TYPE_A, + DNS_CLASS_IN, true, dns_handler, rst); + if (err) { + re_printf("rst: dns query error: %m\n", err); + } + } + + return err; +} + + +int rst_alloc(struct rst **rstp, const char *dev) +{ + struct pl host, port, path; + struct rst *rst; + int err; + + if (!rstp || !dev) + return EINVAL; + + if (re_regex(dev, strlen(dev), "http://[^:/]+[:]*[0-9]*[^]+", + &host, NULL, &port, &path)) { + re_printf("rst: bad http url: %s\n", dev); + return EBADMSG; + } + + rst = mem_zalloc(sizeof(*rst), destructor); + if (!rst) + return ENOMEM; + + rst->id = "rst"; + + err = pl_strdup(&rst->host, &host); + if (err) + goto out; + + err = pl_strdup(&rst->path, &path); + if (err) + goto out; + + rst->port = pl_u32(&port); + rst->port = rst->port ? rst->port : 80; + + err = rst_connect(rst); + if (err) + goto out; + + out: + if (err) + mem_deref(rst); + else + *rstp = rst; + + return err; +} + + +void rst_set_audio(struct rst *rst, struct ausrc_st *st) +{ + if (!rst) + return; + + rst->ausrc_st = st; +} + + +void rst_set_video(struct rst *rst, struct vidsrc_st *st) +{ + if (!rst) + return; + + rst->vidsrc_st = st; +} + + +static int module_init(void) +{ + int err; + + err = rst_audio_init(); + if (err) + goto out; + + err = rst_video_init(); + if (err) + goto out; + + out: + if (err) { + rst_audio_close(); + rst_video_close(); + } + + return err; +} + + +static int module_close(void) +{ + rst_audio_close(); + rst_video_close(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(rst) = { + "rst", + "avsrc", + module_init, + module_close +}; diff --git a/modules/rst/rst.h b/modules/rst/rst.h new file mode 100644 index 0000000..950a7de --- /dev/null +++ b/modules/rst/rst.h @@ -0,0 +1,26 @@ +/** + * @file rst.h MP3/ICY HTTP AV Source + * + * Copyright (C) 2011 Creytiv.com + */ + + +/* Shared AV state */ +struct rst; + +int rst_alloc(struct rst **rstp, const char *dev); +void rst_set_audio(struct rst *rst, struct ausrc_st *st); +void rst_set_video(struct rst *rst, struct vidsrc_st *st); + + +/* Audio */ +void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz); +int rst_audio_init(void); +void rst_audio_close(void); + + +/* Video */ +void rst_video_update(struct vidsrc_st *st, const char *name, + const char *meta); +int rst_video_init(void); +void rst_video_close(void); diff --git a/modules/rst/video.c b/modules/rst/video.c new file mode 100644 index 0000000..db6bfc9 --- /dev/null +++ b/modules/rst/video.c @@ -0,0 +1,280 @@ +/** + * @file rst/video.c MP3/ICY HTTP Video Source + * + * Copyright (C) 2011 Creytiv.com + */ + +#define _BSD_SOURCE 1 +#include <pthread.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <cairo/cairo.h> +#include "rst.h" + + +struct vidsrc_st { + struct vidsrc *vs; + pthread_mutex_t mutex; + pthread_t thread; + struct vidsrc_prm prm; + struct vidsz size; + struct rst *rst; + cairo_surface_t *surface; + cairo_t *cairo; + struct vidframe *frame; + vidsrc_frame_h *frameh; + void *arg; + bool run; +}; + + +static struct vidsrc *vidsrc; + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + rst_set_video(st->rst, NULL); + mem_deref(st->rst); + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->cairo) + cairo_destroy(st->cairo); + + if (st->surface) + cairo_surface_destroy(st->surface); + + mem_deref(st->frame); + mem_deref(st->vs); +} + + +static void *video_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct vidsrc_st *st = arg; + + while (st->run) { + + (void)usleep(4000); + + now = tmr_jiffies(); + + if (ts > now) + continue; + + pthread_mutex_lock(&st->mutex); + st->frameh(st->frame, st->arg); + pthread_mutex_unlock(&st->mutex); + + ts += 1000/st->prm.fps; + } + + return NULL; +} + + +static void background(cairo_t *cr, unsigned width, unsigned height) +{ + cairo_pattern_t *pat; + double r, g, b; + + pat = cairo_pattern_create_linear(0.0, 0.0, 0.0, height); + if (!pat) + return; + + r = 0.0; + g = 0.0; + b = 0.8; + + cairo_pattern_add_color_stop_rgba(pat, 1, r, g, b, 1); + cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0.2, 1); + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_source(cr, pat); + cairo_fill(cr); + + cairo_pattern_destroy(pat); +} + + +static void icy_printf(cairo_t *cr, int x, int y, double size, + const char *fmt, ...) +{ + char buf[4096] = ""; + va_list ap; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* Draw text */ + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, size); + cairo_move_to(cr, x, y); + cairo_text_path(cr, buf); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); +} + + +static size_t linelen(const struct pl *pl) +{ + size_t len = 72, i; + + if (pl->l <= len) + return pl->l; + + for (i=len; i>1; i--) { + + if (pl->p[i-1] == ' ') { + len = i; + break; + } + } + + return len; +} + + +void rst_video_update(struct vidsrc_st *st, const char *name, const char *meta) +{ + struct vidframe frame; + + if (!st) + return; + + background(st->cairo, st->size.w, st->size.h); + + icy_printf(st->cairo, 50, 100, 40.0, "%s", name); + + if (meta) { + + struct pl title; + + if (!re_regex(meta, strlen(meta), + "StreamTitle='[ \t]*[^;]+;", NULL, &title)) { + + unsigned i; + + title.l--; + + for (i=0; title.l; i++) { + + const size_t len = linelen(&title); + + icy_printf(st->cairo, 50, 150 + 25*i, 18.0, + "%b", title.p, len); + + title.p += len; + title.l -= len; + } + } + } + + vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size, + cairo_image_surface_get_data(st->surface)); + + pthread_mutex_lock(&st->mutex); + vidconv(st->frame, &frame, NULL); + pthread_mutex_unlock(&st->mutex); +} + + +static int alloc_handler(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + + (void)fmt; + (void)errorh; + + if (!stp || !vs || !prm || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + err = pthread_mutex_init(&st->mutex, NULL); + if (err) + goto out; + + st->vs = mem_ref(vs); + st->prm = *prm; + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + size->w, size->h); + if (!st->surface) { + err = ENOMEM; + goto out; + } + + st->cairo = cairo_create(st->surface); + if (!st->cairo) { + err = ENOMEM; + goto out; + } + + err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size); + if (err) + goto out; + + vidframe_fill(st->frame, 0, 0, 0); + + if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) { + st->rst = mem_ref(*ctx); + } + else { + err = rst_alloc(&st->rst, dev); + if (err) + goto out; + + if (ctx) + *ctx = (struct media_ctx *)st->rst; + } + + rst_set_video(st->rst, st); + + st->run = true; + + err = pthread_create(&st->thread, NULL, video_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int rst_video_init(void) +{ + return vidsrc_register(&vidsrc, "rst", alloc_handler, NULL); +} + + +void rst_video_close(void) +{ + vidsrc = mem_deref(vidsrc); +} diff --git a/modules/sdl/module.mk b/modules/sdl/module.mk new file mode 100644 index 0000000..bf100e5 --- /dev/null +++ b/modules/sdl/module.mk @@ -0,0 +1,19 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sdl +$(MOD)_SRCS += sdl.c +$(MOD)_SRCS += util.c + +CFLAGS += -DUSE_SDL +$(MOD)_LFLAGS += -lSDL +ifeq ($(OS),darwin) +# note: APP_LFLAGS is needed, as main.o links to -lSDLmain +APP_LFLAGS += -lSDL -lSDLmain -lobjc \ + -framework CoreFoundation -framework Foundation -framework Cocoa +endif + +include mk/mod.mk diff --git a/modules/sdl/sdl.c b/modules/sdl/sdl.c new file mode 100644 index 0000000..eb902b3 --- /dev/null +++ b/modules/sdl/sdl.c @@ -0,0 +1,319 @@ +/** + * @file sdl/sdl.c SDL - Simple DirectMedia Layer v1.2 + * + * Copyright (C) 2010 Creytiv.com + */ +#include <SDL/SDL.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "sdl.h" + + +/** Local constants */ +enum { + KEY_RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct vidisp_st { + struct vidisp *vd; /* inheritance */ +}; + +/** Global SDL data */ +static struct { + struct tmr tmr; + SDL_Surface *screen; /**< SDL Surface */ + SDL_Overlay *bmp; /**< SDL YUV Overlay */ + struct vidsz size; /**< Current size */ + vidisp_resize_h *resizeh; /**< Screen resize handler */ + void *arg; /**< Handler argument */ + bool fullscreen; + bool open; +} sdl; + + +static struct vidisp *vid; + + +static void event_handler(void *arg); + + +static void sdl_reset(void) +{ + if (sdl.bmp) { + SDL_FreeYUVOverlay(sdl.bmp); + sdl.bmp = NULL; + } + + if (sdl.screen) { + SDL_FreeSurface(sdl.screen); + sdl.screen = NULL; + } +} + + +static void handle_resize(int w, int h) +{ + struct vidsz size; + + size.w = w; + size.h = h; + + /* notify app */ + if (sdl.resizeh) + sdl.resizeh(&size, sdl.arg); +} + + +static void timeout(void *arg) +{ + (void)arg; + + tmr_start(&sdl.tmr, 1, event_handler, NULL); + + /* Emulate key-release */ + ui_input(0x00); +} + + +static void event_handler(void *arg) +{ + SDL_Event event; + char ch; + + (void)arg; + + tmr_start(&sdl.tmr, 100, event_handler, NULL); + + while (SDL_PollEvent(&event)) { + + switch (event.type) { + + case SDL_KEYDOWN: + + switch (event.key.keysym.sym) { + + case SDLK_ESCAPE: + if (!sdl.fullscreen) + break; + + sdl.fullscreen = false; + sdl_reset(); + break; + + case SDLK_f: + if (sdl.fullscreen) + break; + + sdl.fullscreen = true; + sdl_reset(); + break; + + default: + ch = event.key.keysym.unicode & 0x7f; + + /* Relay key-press to UI subsystem */ + if (isprint(ch)) { + tmr_start(&sdl.tmr, KEY_RELEASE_VAL, + timeout, NULL); + ui_input(ch); + } + break; + } + + break; + + case SDL_VIDEORESIZE: + handle_resize(event.resize.w, event.resize.h); + break; + + case SDL_QUIT: + ui_input('q'); + break; + + default: + break; + } + } +} + + +static int sdl_open(void) +{ + if (sdl.open) + return 0; + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + warning("sdl: unable to init SDL: %s\n", SDL_GetError()); + return ENODEV; + } + + SDL_EnableUNICODE(1); + + tmr_start(&sdl.tmr, 100, event_handler, NULL); + sdl.open = true; + + return 0; +} + + +static void sdl_close(void) +{ + tmr_cancel(&sdl.tmr); + sdl_reset(); + + if (sdl.open) { + SDL_Quit(); + sdl.open = false; + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + mem_deref(st->vd); + sdl_close(); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err; + + /* Not used by SDL */ + (void)prm; + (void)dev; + + if (sdl.open) + return EBUSY; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + sdl.resizeh = resizeh; + sdl.arg = arg; + + err = sdl_open(); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +/** + * Display a video frame + * + * @param st Video display state + * @param title Window title + * @param frame Video frame + * + * @return 0 if success, otherwise errorcode + * + * @note: On Darwin, this must be called from the main thread + */ +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + SDL_Rect rect; + + if (!st || !sdl.open) + return EINVAL; + + if (!vidsz_cmp(&sdl.size, &frame->size)) { + if (sdl.size.w && sdl.size.h) { + info("sdl: reset size %u x %u ---> %u x %u\n", + sdl.size.w, sdl.size.h, + frame->size.w, frame->size.h); + } + sdl_reset(); + } + + if (!sdl.screen) { + int flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; + char capt[256]; + + if (sdl.fullscreen) + flags |= SDL_FULLSCREEN; + else if (sdl.resizeh) + flags |= SDL_RESIZABLE; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + SDL_WM_SetCaption(capt, capt); + + sdl.screen = SDL_SetVideoMode(frame->size.w, frame->size.h, + 0, flags); + if (!sdl.screen) { + warning("sdl: unable to get video screen: %s\n", + SDL_GetError()); + return ENODEV; + } + + sdl.size = frame->size; + } + + if (!sdl.bmp) { + sdl.bmp = SDL_CreateYUVOverlay(frame->size.w, frame->size.h, + SDL_YV12_OVERLAY, sdl.screen); + if (!sdl.bmp) { + warning("sdl: unable to create overlay: %s\n", + SDL_GetError()); + return ENODEV; + } + } + + SDL_LockYUVOverlay(sdl.bmp); + picture_copy(sdl.bmp->pixels, sdl.bmp->pitches, frame); + SDL_UnlockYUVOverlay(sdl.bmp); + + rect.x = 0; + rect.y = 0; + rect.w = sdl.size.w; + rect.h = sdl.size.h; + + SDL_DisplayYUVOverlay(sdl.bmp, &rect); + + return 0; +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "sdl", alloc, NULL, display, NULL); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl) = { + "sdl", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/sdl/sdl.h b/modules/sdl/sdl.h new file mode 100644 index 0000000..992f70d --- /dev/null +++ b/modules/sdl/sdl.h @@ -0,0 +1,9 @@ +/** + * @file sdl.h Simple DirectMedia Layer module -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +void picture_copy(uint8_t *data[4], uint16_t linesize[4], + const struct vidframe *frame); diff --git a/modules/sdl/util.c b/modules/sdl/util.c new file mode 100644 index 0000000..59a1cdd --- /dev/null +++ b/modules/sdl/util.c @@ -0,0 +1,54 @@ +/** + * @file util.c Simple DirectMedia Layer module -- utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "sdl.h" + + +static void img_copy_plane(uint8_t *dst, int dst_wrap, + const uint8_t *src, int src_wrap, + int width, int height) +{ + if (!dst || !src) + return; + + for (;height > 0; height--) { + memcpy(dst, src, width); + dst += dst_wrap; + src += src_wrap; + } +} + + +static int get_plane_bytewidth(int width, int plane) +{ + if (plane == 1 || plane == 2) + width = -((-width) >> 1); + + return (width * 8 + 7) >> 3; +} + + +void picture_copy(uint8_t *data[4], uint16_t linesize[4], + const struct vidframe *frame) +{ + const int map[3] = {0, 2, 1}; + int i; + + for (i=0; i<3; i++) { + int h; + int bwidth = get_plane_bytewidth(frame->size.w, i); + h = frame->size.h; + if (i == 1 || i == 2) { + h = -((-frame->size.h) >> 1); + } + img_copy_plane(data[map[i]], linesize[map[i]], + frame->data[i], frame->linesize[i], + bwidth, h); + } +} diff --git a/modules/sdl2/module.mk b/modules/sdl2/module.mk new file mode 100644 index 0000000..958f7ea --- /dev/null +++ b/modules/sdl2/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sdl2 +$(MOD)_SRCS += sdl.c +$(MOD)_LFLAGS += -lSDL2 + +include mk/mod.mk diff --git a/modules/sdl2/sdl.c b/modules/sdl2/sdl.c new file mode 100644 index 0000000..e688689 --- /dev/null +++ b/modules/sdl2/sdl.c @@ -0,0 +1,238 @@ +/** + * @file sdl2/sdl.c Simple DirectMedia Layer module for SDL v2.0 + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <SDL2/SDL.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + SDL_Window *window; /**< SDL Window */ + SDL_Renderer *renderer; /**< SDL Renderer */ + SDL_Texture *texture; /**< Texture for pixels */ + struct vidsz size; /**< Current size */ + bool fullscreen; /**< Fullscreen flag */ +}; + + +static struct vidisp *vid; + + +static void sdl_reset(struct vidisp_st *st) +{ + if (st->texture) { + /*SDL_DestroyTexture(st->texture);*/ + st->texture = NULL; + } + + if (st->renderer) { + /*SDL_DestroyRenderer(st->renderer);*/ + st->renderer = NULL; + } + + if (st->window) { + SDL_DestroyWindow(st->window); + st->window = NULL; + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + sdl_reset(st); + + mem_deref(st->vd); +} + + +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + + /* Not used by SDL */ + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + void *pixels; + uint8_t *p; + int pitch, ret; + unsigned i, h; + + if (!vidsz_cmp(&st->size, &frame->size)) { + if (st->size.w && st->size.h) { + info("sdl: reset size: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + sdl_reset(st); + } + + if (!st->window) { + Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS; + char capt[256]; + + if (st->fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + st->window = SDL_CreateWindow(capt, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + frame->size.w, frame->size.h, + flags); + if (!st->window) { + warning("sdl: unable to create sdl window: %s\n", + SDL_GetError()); + return ENODEV; + } + + st->size = frame->size; + + SDL_RaiseWindow(st->window); + SDL_SetWindowBordered(st->window, true); + SDL_ShowWindow(st->window); + } + + if (!st->renderer) { + + Uint32 flags = 0; + + flags |= SDL_RENDERER_ACCELERATED; + flags |= SDL_RENDERER_PRESENTVSYNC; + + st->renderer = SDL_CreateRenderer(st->window, -1, flags); + if (!st->renderer) { + warning("sdl: unable to create renderer: %s\n", + SDL_GetError()); + return ENOMEM; + } + } + + if (!st->texture) { + + st->texture = SDL_CreateTexture(st->renderer, + SDL_PIXELFORMAT_IYUV, + SDL_TEXTUREACCESS_STREAMING, + frame->size.w, frame->size.h); + if (!st->texture) { + warning("sdl: unable to create texture: %s\n", + SDL_GetError()); + return ENODEV; + } + } + + ret = SDL_LockTexture(st->texture, NULL, &pixels, &pitch); + if (ret != 0) { + warning("sdl: unable to lock texture (ret=%d)\n", ret); + return ENODEV; + } + + p = pixels; + for (i=0; i<3; i++) { + + const uint8_t *s = frame->data[i]; + const unsigned stp = frame->linesize[0] / frame->linesize[i]; + const unsigned sz = frame->size.w / stp; + + for (h = 0; h < frame->size.h; h += stp) { + + memcpy(p, s, sz); + + s += frame->linesize[i]; + p += (pitch / stp); + } + } + + SDL_UnlockTexture(st->texture); + + /* Blit the sprite onto the screen */ + SDL_RenderCopy(st->renderer, st->texture, NULL, NULL); + + /* Update the screen! */ + SDL_RenderPresent(st->renderer); + + return 0; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st || !st->window) + return; + + SDL_HideWindow(st->window); +} + + +static int module_init(void) +{ + int err; + + if (SDL_VideoInit(NULL) < 0) { + warning("sdl2: unable to init Video: %s\n", + SDL_GetError()); + return ENODEV; + } + + err = vidisp_register(&vid, "sdl2", alloc, NULL, display, hide); + if (err) + return err; + + return 0; +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + SDL_VideoQuit(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl2) = { + "sdl2", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/selfview/module.mk b/modules/selfview/module.mk new file mode 100644 index 0000000..2719433 --- /dev/null +++ b/modules/selfview/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := selfview +$(MOD)_SRCS += selfview.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/selfview/selfview.c b/modules/selfview/selfview.c new file mode 100644 index 0000000..cf5cdc3 --- /dev/null +++ b/modules/selfview/selfview.c @@ -0,0 +1,267 @@ +/** + * @file selfview.c Selfview Video-Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* shared state */ +struct selfview { + struct lock *lock; /**< Protect frame */ + struct vidframe *frame; /**< Copy of encoded frame */ +}; + +struct selfview_enc { + struct vidfilt_enc_st vf; /**< Inheritance */ + struct selfview *selfview; /**< Ref. to shared state */ + struct vidisp_st *disp; /**< Selfview display */ +}; + +struct selfview_dec { + struct vidfilt_dec_st vf; /**< Inheritance */ + struct selfview *selfview; /**< Ref. to shared state */ +}; + + +static struct vidsz selfview_size = {0, 0}; + + +static void destructor(void *arg) +{ + struct selfview *st = arg; + + lock_write_get(st->lock); + mem_deref(st->frame); + lock_rel(st->lock); + mem_deref(st->lock); +} + + +static void encode_destructor(void *arg) +{ + struct selfview_enc *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->selfview); + mem_deref(st->disp); +} + + +static void decode_destructor(void *arg) +{ + struct selfview_dec *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->selfview); +} + + +static int selfview_alloc(struct selfview **selfviewp, void **ctx) +{ + struct selfview *selfview; + int err; + + if (!selfviewp || !ctx) + return EINVAL; + + if (*ctx) { + *selfviewp = mem_ref(*ctx); + } + else { + selfview = mem_zalloc(sizeof(*selfview), destructor); + if (!selfview) + return ENOMEM; + + err = lock_alloc(&selfview->lock); + if (err) + return err; + + *ctx = selfview; + *selfviewp = selfview; + } + + return 0; +} + + +static int encode_update(struct vidfilt_enc_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct selfview_enc *st; + int err; + + if (!stp || !ctx || !vf) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + err = selfview_alloc(&st->selfview, ctx); + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct vidfilt_dec_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct selfview_dec *st; + int err; + + if (!stp || !ctx || !vf) + return EINVAL; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + err = selfview_alloc(&st->selfview, ctx); + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_dec_st *)st; + + return err; +} + + +static int encode_win(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + struct selfview_enc *enc = (struct selfview_enc *)st; + int err; + + if (!frame) + return 0; + + if (!enc->disp) { + + err = vidisp_alloc(&enc->disp, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + return vidisp_display(enc->disp, "Selfview", frame); +} + + +static int encode_pip(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + struct selfview_enc *enc = (struct selfview_enc *)st; + struct selfview *selfview = enc->selfview; + int err = 0; + + if (!frame) + return 0; + + lock_write_get(selfview->lock); + if (!selfview->frame) { + struct vidsz sz; + + /* Use size if configured, or else 20% of main window */ + if (selfview_size.w && selfview_size.h) { + sz = selfview_size; + } + else { + sz.w = frame->size.w / 5; + sz.h = frame->size.h / 5; + } + + err = vidframe_alloc(&selfview->frame, VID_FMT_YUV420P, &sz); + } + if (!err) + vidconv(selfview->frame, frame, NULL); + lock_rel(selfview->lock); + + return err; +} + + +static int decode_pip(struct vidfilt_dec_st *st, struct vidframe *frame) +{ + struct selfview_dec *dec = (struct selfview_dec *)st; + struct selfview *sv = dec->selfview; + + if (!frame) + return 0; + + lock_read_get(sv->lock); + if (sv->frame) { + struct vidrect rect; + + rect.w = min(sv->frame->size.w, frame->size.w/2); + rect.h = min(sv->frame->size.h, frame->size.h/2); + if (rect.w <= (frame->size.w - 10)) + rect.x = frame->size.w - rect.w - 10; + else + rect.x = frame->size.w/2; + if (rect.h <= (frame->size.h - 10)) + rect.y = frame->size.h - rect.h - 10; + else + rect.y = frame->size.h/2; + + vidconv(frame, sv->frame, &rect); + + vidframe_draw_rect(frame, rect.x, rect.y, rect.w, rect.h, + 127, 127, 127); + } + lock_rel(sv->lock); + + return 0; +} + + +static struct vidfilt selfview_win = { + LE_INIT, "selfview_window", encode_update, encode_win, NULL, NULL +}; +static struct vidfilt selfview_pip = { + LE_INIT, "selfview_pip", + encode_update, encode_pip, decode_update, decode_pip +}; + + +static int module_init(void) +{ + struct pl pl; + + if (conf_get(conf_cur(), "video_selfview", &pl)) + return 0; + + if (0 == pl_strcasecmp(&pl, "window")) + vidfilt_register(&selfview_win); + else if (0 == pl_strcasecmp(&pl, "pip")) + vidfilt_register(&selfview_pip); + + (void)conf_get_vidsz(conf_cur(), "selfview_size", &selfview_size); + + return 0; +} + + +static int module_close(void) +{ + vidfilt_unregister(&selfview_win); + vidfilt_unregister(&selfview_pip); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(selfview) = { + "selfview", + "vidfilt", + module_init, + module_close +}; diff --git a/modules/silk/module.mk b/modules/silk/module.mk new file mode 100644 index 0000000..ac8ebb1 --- /dev/null +++ b/modules/silk/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := silk +$(MOD)_SRCS += silk.c +$(MOD)_LFLAGS += -lSKP_SILK_SDK + +include mk/mod.mk diff --git a/modules/silk/silk.c b/modules/silk/silk.c new file mode 100644 index 0000000..95f708c --- /dev/null +++ b/modules/silk/silk.c @@ -0,0 +1,259 @@ +/** + * @file silk.c Skype SILK audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <silk/SKP_Silk_SDK_API.h> + + +/* + * References: https://developer.skype.com/silk + */ + + +enum { + MAX_BYTES_PER_FRAME = 250, + MAX_FRAME_SIZE = 2*480, +}; + + +struct auenc_state { + void *enc; + SKP_SILK_SDK_EncControlStruct encControl; +}; + +struct audec_state { + void *dec; + SKP_SILK_SDK_DecControlStruct decControl; +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + mem_deref(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + mem_deref(st->dec); +} + + +static int encode_update(struct auenc_state **aesp, + const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int ret, err = 0; + int32_t enc_size; + (void)fmtp; + + if (!aesp || !ac || !prm) + return EINVAL; + if (*aesp) + return 0; + + ret = SKP_Silk_SDK_Get_Encoder_Size(&enc_size); + if (ret || enc_size <= 0) + return EINVAL; + + st = mem_alloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->enc = mem_alloc(enc_size, NULL); + if (!st->enc) { + err = ENOMEM; + goto out; + } + + ret = SKP_Silk_SDK_InitEncoder(st->enc, &st->encControl); + if (ret) { + err = EPROTO; + goto out; + } + + st->encControl.API_sampleRate = ac->srate; + st->encControl.maxInternalSampleRate = ac->srate; + st->encControl.packetSize = prm->ptime * ac->srate / 1000; + st->encControl.bitRate = 64000; + st->encControl.complexity = 2; + st->encControl.useInBandFEC = 0; + st->encControl.useDTX = 0; + + info("silk: encoder: %dHz, psize=%d, bitrate=%d, complex=%d," + " fec=%d, dtx=%d\n", + st->encControl.API_sampleRate, + st->encControl.packetSize, + st->encControl.bitRate, + st->encControl.complexity, + st->encControl.useInBandFEC, + st->encControl.useDTX); + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int ret, err = 0; + int32_t dec_size; + (void)fmtp; + + if (*adsp) + return 0; + + ret = SKP_Silk_SDK_Get_Decoder_Size(&dec_size); + if (ret || dec_size <= 0) + return EINVAL; + + st = mem_alloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->dec = mem_alloc(dec_size, NULL); + if (!st->dec) { + err = ENOMEM; + goto out; + } + + ret = SKP_Silk_SDK_InitDecoder(st->dec); + if (ret) { + err = EPROTO; + goto out; + } + + st->decControl.API_sampleRate = ac->srate; + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int ret; + int16_t nBytesOut; + + if (*len < MAX_BYTES_PER_FRAME) + return ENOMEM; + + nBytesOut = *len; + ret = SKP_Silk_SDK_Encode(st->enc, + &st->encControl, + sampv, + (int)sampc, + buf, + &nBytesOut); + if (ret) { + warning("silk: SKP_Silk_SDK_Encode: ret=%d\n", ret); + } + + *len = nBytesOut; + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + int16_t nsamp = *sampc; + int ret; + + ret = SKP_Silk_SDK_Decode(st->dec, + &st->decControl, + 0, + buf, + (int)len, + sampv, + &nsamp); + if (ret) { + warning("silk: SKP_Silk_SDK_Decode: ret=%d\n", ret); + } + + *sampc = nsamp; + + return 0; +} + + +static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + int16_t nsamp = *sampc; + int ret; + + ret = SKP_Silk_SDK_Decode(st->dec, + &st->decControl, + 1, + NULL, + 0, + sampv, + &nsamp); + if (ret) + return EPROTO; + + *sampc = nsamp; + + return 0; +} + + +static struct aucodec silk[] = { + { + LE_INIT, 0, "SILK", 24000, 1, NULL, + encode_update, encode, decode_update, decode, plc, 0, 0 + }, + +}; + + +static int module_init(void) +{ + debug("silk: SILK %s\n", SKP_Silk_SDK_get_version()); + + aucodec_register(&silk[0]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(silk); + + while (i--) + aucodec_unregister(&silk[i]); + + return 0; +} + + +/** Module exports */ +EXPORT_SYM const struct mod_export DECL_EXPORTS(silk) = { + "silk", + "codec", + module_init, + module_close +}; diff --git a/modules/snapshot/module.mk b/modules/snapshot/module.mk new file mode 100644 index 0000000..1be53bd --- /dev/null +++ b/modules/snapshot/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := snapshot +$(MOD)_SRCS += snapshot.c png_vf.c +$(MOD)_LFLAGS += -lpng + +include mk/mod.mk diff --git a/modules/snapshot/png_vf.c b/modules/snapshot/png_vf.c new file mode 100644 index 0000000..d7f1f8a --- /dev/null +++ b/modules/snapshot/png_vf.c @@ -0,0 +1,188 @@ +/** + * @file png_vf.c Write vidframe to a PNG-file + * + * Author: Doug Blewett + * Review: Alfred E. Heggestad + */ +#define _BSD_SOURCE 1 +#include <string.h> +#include <time.h> +#include <png.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "png_vf.h" + + +static char *png_filename(const struct tm *tmx, const char *name, + char *buf, unsigned int length); +static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers, + int png_height); + + +int png_save_vidframe(const struct vidframe *vf, const char *path) +{ + png_byte **png_row_pointers = NULL; + png_byte *row; + const png_byte *p; + png_byte red, green, blue; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + FILE *fp = NULL; + size_t x, y; + unsigned int width = vf->size.w & ~1; + unsigned int height = vf->size.h & ~1; + unsigned int bytes_per_pixel = 3; /* RGB format */ + time_t tnow; + struct tm *tmx; + char filename_buf[64]; + struct vidframe *f2 = NULL; + int err = 0; + + tnow = time(NULL); + tmx = localtime(&tnow); + + if (vf->fmt != VID_FMT_RGB32) { + + err = vidframe_alloc(&f2, VID_FMT_RGB32, &vf->size); + if (err) + goto out; + + vidconv(f2, vf, NULL); + vf = f2; + } + + /* Initialize the write struct. */ + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) { + err = ENOMEM; + goto out; + } + + /* Initialize the info struct. */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + err = ENOMEM; + goto out; + } + + /* Set up error handling. */ + if (setjmp(png_jmpbuf(png_ptr))) { + err = ENOMEM; + goto out; + } + + /* Set image attributes. */ + png_set_IHDR(png_ptr, + info_ptr, + width, + height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + /* Initialize rows of PNG + * bytes_per_row = width * bytes_per_pixel; + */ + png_row_pointers = png_malloc(png_ptr, + height * sizeof(png_byte *)); + + for (y = 0; y < height; ++y) { + png_row_pointers[y] = + (png_byte *) png_malloc(png_ptr, + width * sizeof(uint8_t) * + bytes_per_pixel); + } + + p = vf->data[0]; + for (y = 0; y < height; ++y) { + + row = png_row_pointers[y]; + + for (x = 0; x < width; ++x) { + + red = *p++; + green = *p++; + blue = *p++; + + *row++ = blue; + *row++ = green; + *row++ = red; + + ++p; /* skip alpha */ + } + } + + /* Write the image data. */ + fp = fopen(png_filename(tmx, path, + filename_buf, sizeof(filename_buf)), "wb"); + if (fp == NULL) { + err = errno; + goto out; + } + + png_init_io(png_ptr, fp); + png_set_rows(png_ptr, info_ptr, png_row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + info("png: wrote %s\n", filename_buf); + + out: + /* Finish writing. */ + mem_deref(f2); + png_save_free(png_ptr, png_row_pointers, height); + png_destroy_write_struct(&png_ptr, &info_ptr); + if (fp) + fclose(fp); + + return 0; +} + + +static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers, + int png_height) +{ + int y; + + /* Cleanup. */ + if (png_height == 0 || png_row_pointers == NULL) + return; + + for (y = 0; y < png_height; y++) { + png_free(png_ptr, png_row_pointers[y]); + } + png_free(png_ptr, png_row_pointers); +} + + +static char *png_filename(const struct tm *tmx, const char *name, + char *buf, unsigned int length) +{ + /* + * -2013-03-03-15-22-56.png - 24 chars + */ + if (strlen(name) + 24 >= length) { + buf[0] = '\0'; + return buf; + } + + sprintf(buf, (tmx->tm_mon < 9 ? "%s-%d-0%d" : "%s-%d-%d"), name, + 1900 + tmx->tm_year, tmx->tm_mon + 1); + + sprintf(buf + strlen(buf), (tmx->tm_mday < 10 ? "-0%d" : "-%d"), + tmx->tm_mday); + + sprintf(buf + strlen(buf), (tmx->tm_hour < 10 ? "-0%d" : "-%d"), + tmx->tm_hour); + + sprintf(buf + strlen(buf), (tmx->tm_min < 10 ? "-0%d" : "-%d"), + tmx->tm_min); + + sprintf(buf + strlen(buf), (tmx->tm_sec < 10 ? "-0%d.png" : "-%d.png"), + tmx->tm_sec); + + return buf; +} diff --git a/modules/snapshot/png_vf.h b/modules/snapshot/png_vf.h new file mode 100644 index 0000000..17660cf --- /dev/null +++ b/modules/snapshot/png_vf.h @@ -0,0 +1,6 @@ +/** + * @file png_vf.h + */ + + +int png_save_vidframe(const struct vidframe *vf, const char *path); diff --git a/modules/snapshot/snapshot.c b/modules/snapshot/snapshot.c new file mode 100644 index 0000000..b8a01de --- /dev/null +++ b/modules/snapshot/snapshot.c @@ -0,0 +1,90 @@ +/** + * @file snapshot.c Snapshot Video-Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "png_vf.h" + + +static bool flag_enc, flag_dec; + + +static int encode(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + (void)st; + + if (!frame) + return 0; + + if (flag_enc) { + flag_enc = false; + png_save_vidframe(frame, "snapshot-send"); + } + + return 0; +} + + +static int decode(struct vidfilt_dec_st *st, struct vidframe *frame) +{ + (void)st; + + if (!frame) + return 0; + + if (flag_dec) { + flag_dec = false; + png_save_vidframe(frame, "snapshot-recv"); + } + + return 0; +} + + +static int do_snapshot(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + /* NOTE: not re-entrant */ + flag_enc = flag_dec = true; + + return 0; +} + + +static struct vidfilt snapshot = { + LE_INIT, "snapshot", NULL, encode, NULL, decode, +}; + + +static const struct cmd cmdv[] = { + {'o', 0, "Take video snapshot", do_snapshot }, +}; + + +static int module_init(void) +{ + vidfilt_register(&snapshot); + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + vidfilt_unregister(&snapshot); + cmd_unregister(cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(snapshot) = { + "snapshot", + "vidfilt", + module_init, + module_close +}; diff --git a/modules/sndfile/module.mk b/modules/sndfile/module.mk new file mode 100644 index 0000000..7fed4de --- /dev/null +++ b/modules/sndfile/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sndfile +$(MOD)_SRCS += sndfile.c +$(MOD)_LFLAGS += -lsndfile + +include mk/mod.mk diff --git a/modules/sndfile/sndfile.c b/modules/sndfile/sndfile.c new file mode 100644 index 0000000..e8fe9c9 --- /dev/null +++ b/modules/sndfile/sndfile.c @@ -0,0 +1,180 @@ +/** + * @file sndfile.c Audio dumper using libsndfile + * + * Copyright (C) 2010 Creytiv.com + */ +#include <sndfile.h> +#include <time.h> +#include <re.h> +#include <baresip.h> + + +struct sndfile_enc { + struct aufilt_enc_st af; /* base class */ + SNDFILE *enc; +}; + +struct sndfile_dec { + struct aufilt_dec_st af; /* base class */ + SNDFILE *dec; +}; + + +static int timestamp_print(struct re_printf *pf, const struct tm *tm) +{ + if (!tm) + return 0; + + return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +static void enc_destructor(void *arg) +{ + struct sndfile_enc *st = arg; + + if (st->enc) + sf_close(st->enc); + + list_unlink(&st->af.le); +} + + +static void dec_destructor(void *arg) +{ + struct sndfile_dec *st = arg; + + if (st->dec) + sf_close(st->dec); + + list_unlink(&st->af.le); +} + + +static SNDFILE *openfile(const struct aufilt_prm *prm, bool enc) +{ + char filename[128]; + SF_INFO sfinfo; + time_t tnow = time(0); + struct tm *tm = localtime(&tnow); + SNDFILE *sf; + + (void)re_snprintf(filename, sizeof(filename), + "dump-%H-%s.wav", + timestamp_print, tm, enc ? "enc" : "dec"); + + sfinfo.samplerate = prm->srate; + sfinfo.channels = prm->ch; + sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + + sf = sf_open(filename, SFM_WRITE, &sfinfo); + if (!sf) { + warning("sndfile: could not open: %s\n", filename); + puts(sf_strerror(NULL)); + return NULL; + } + + info("sndfile: dumping %s audio to %s\n", + enc ? "encode" : "decode", filename); + + return sf; +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct sndfile_enc *st; + int err = 0; + (void)ctx; + (void)af; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return EINVAL; + + st->enc = openfile(prm, true); + if (!st->enc) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct sndfile_dec *st; + int err = 0; + (void)ctx; + (void)af; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return EINVAL; + + st->dec = openfile(prm, false); + if (!st->dec) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct sndfile_enc *sf = (struct sndfile_enc *)st; + + sf_write_short(sf->enc, sampv, *sampc); + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct sndfile_dec *sf = (struct sndfile_dec *)st; + + sf_write_short(sf->dec, sampv, *sampc); + + return 0; +} + + +static struct aufilt sndfile = { + LE_INIT, "sndfile", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&sndfile); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&sndfile); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sndfile) = { + "sndfile", + "filter", + module_init, + module_close +}; diff --git a/modules/speex/module.mk b/modules/speex/module.mk new file mode 100644 index 0000000..c8d0015 --- /dev/null +++ b/modules/speex/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex +$(MOD)_SRCS += speex.c +$(MOD)_LFLAGS += -lspeex +CFLAGS += -Wno-strict-prototypes + +include mk/mod.mk diff --git a/modules/speex/speex.c b/modules/speex/speex.c new file mode 100644 index 0000000..32db0c9 --- /dev/null +++ b/modules/speex/speex.c @@ -0,0 +1,498 @@ +/** + * @file speex.c Speex audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <speex/speex.h> +#include <speex/speex_stereo.h> +#include <speex/speex_callbacks.h> +#include <re.h> +#include <baresip.h> + + +enum { + MIN_FRAME_SIZE = 43, + SPEEX_PTIME = 20, +}; + + +struct auenc_state { + void *enc; + SpeexBits bits; + + uint32_t frame_size; /* Number of sample-frames */ + uint8_t channels; +}; + + +struct audec_state { + void *dec; + SpeexBits bits; + SpeexStereoState stereo; + SpeexCallback callback; + + uint32_t frame_size; /* Number of sample-frames */ + uint8_t channels; +}; + + +static char speex_fmtp[128]; + + +/** Speex configuration */ +static struct { + int quality; + int complexity; + int enhancement; + int vbr; + int vad; +} sconf = { + 3, /* 0-10 */ + 2, /* 0-10 */ + 0, /* 0 or 1 */ + 0, /* 0 or 1 */ + 0 /* 0 or 1 */ +}; + + +static void encode_destructor(void *arg) +{ + struct auenc_state *st = arg; + + speex_bits_destroy(&st->bits); + speex_encoder_destroy(st->enc); +} + + +static void decode_destructor(void *arg) +{ + struct audec_state *st = arg; + + speex_bits_destroy(&st->bits); + speex_decoder_destroy(st->dec); +} + + +static void encoder_config(void *st) +{ + int ret; + + ret = speex_encoder_ctl(st, SPEEX_SET_QUALITY, &sconf.quality); + if (ret) { + warning("speex: SPEEX_SET_QUALITY: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &sconf.complexity); + if (ret) { + warning("speex: SPEEX_SET_COMPLEXITY: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_VBR, &sconf.vbr); + if (ret) { + warning("speex: SPEEX_SET_VBR: %d\n", ret); + } + + ret = speex_encoder_ctl(st, SPEEX_SET_VAD, &sconf.vad); + if (ret) { + warning("speex: SPEEX_SET_VAD: %d\n", ret); + } +} + + +static void decoder_config(void *st) +{ + int ret; + + ret = speex_decoder_ctl(st, SPEEX_SET_ENH, &sconf.enhancement); + if (ret) { + warning("speex: SPEEX_SET_ENH: %d\n", ret); + } +} + + +static int decode_param(struct auenc_state *st, const struct pl *name, + const struct pl *val) +{ + int ret; + + /* mode: List supported Speex decoding modes. The valid modes are + different for narrowband and wideband, and are defined as follows: + + {1,2,3,4,5,6,7,8,any} for narrowband + {0,1,2,3,4,5,6,7,8,9,10,any} for wideband and ultra-wideband + */ + if (0 == pl_strcasecmp(name, "mode")) { + struct pl v; + int mode; + + /* parameter is quoted */ + if (re_regex(val->p, val->l, "\"[^\"]+\"", &v)) + v = *val; + + if (0 == pl_strcasecmp(&v, "any")) + return 0; + + mode = pl_u32(&v); + + ret = speex_encoder_ctl(st->enc, SPEEX_SET_MODE, &mode); + if (ret) { + warning("speex: SPEEX_SET_MODE: ret=%d\n", ret); + } + } + /* vbr: variable bit rate - either 'on' 'off' or 'vad' */ + else if (0 == pl_strcasecmp(name, "vbr")) { + int vbr = 0, vad = 0; + + if (0 == pl_strcasecmp(val, "on")) + vbr = 1; + else if (0 == pl_strcasecmp(val, "off")) + vbr = 0; + else if (0 == pl_strcasecmp(val, "vad")) + vad = 1; + else { + warning("speex: invalid vbr value %r\n", val); + } + + debug("speex: setting VBR=%d VAD=%d\n", vbr, vad); + ret = speex_encoder_ctl(st->enc, SPEEX_SET_VBR, &vbr); + if (ret) { + warning("speex: SPEEX_SET_VBR: ret=%d\n", ret); + } + ret = speex_encoder_ctl(st->enc, SPEEX_SET_VAD, &vad); + if (ret) { + warning("speex: SPEEX_SET_VAD: ret=%d\n", ret); + } + } + else if (0 == pl_strcasecmp(name, "cng")) { + int dtx = 0; + + if (0 == pl_strcasecmp(val, "on")) + dtx = 0; + else if (0 == pl_strcasecmp(val, "off")) + dtx = 1; + + ret = speex_encoder_ctl(st->enc, SPEEX_SET_DTX, &dtx); + if (ret) { + warning("speex: SPEEX_SET_DTX: ret=%d\n", ret); + } + } + else { + debug("speex: unknown Speex param: %r=%r\n", name, val); + } + + return 0; +} + + +static void param_handler(const struct pl *name, const struct pl *val, + void *arg) +{ + struct auenc_state *st = arg; + + decode_param(st, name, val); +} + + +static const SpeexMode *resolve_mode(uint32_t srate) +{ + switch (srate) { + + default: + case 8000: return &speex_nb_mode; + case 16000: return &speex_wb_mode; + case 32000: return &speex_uwb_mode; + } +} + + +static int encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp) +{ + struct auenc_state *st; + int ret, err = 0; + + if (!aesp || !ac || !prm) + return EINVAL; + if (prm->ptime != SPEEX_PTIME) + return EPROTO; + if (*aesp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->frame_size = ac->srate * SPEEX_PTIME / 1000; + st->channels = ac->ch; + + /* Encoder */ + st->enc = speex_encoder_init(resolve_mode(ac->srate)); + if (!st->enc) { + err = ENOMEM; + goto out; + } + + speex_bits_init(&st->bits); + + encoder_config(st->enc); + + ret = speex_encoder_ctl(st->enc, SPEEX_GET_FRAME_SIZE, + &st->frame_size); + if (ret) { + warning("speex: SPEEX_GET_FRAME_SIZE: %d\n", ret); + } + + if (str_isset(fmtp)) { + struct pl params; + + pl_set_str(¶ms, fmtp); + + fmt_param_apply(¶ms, param_handler, st); + } + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static int decode_update(struct audec_state **adsp, + const struct aucodec *ac, const char *fmtp) +{ + struct audec_state *st; + int err = 0; + (void)fmtp; + + if (!adsp || !ac) + return EINVAL; + if (*adsp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + st->frame_size = ac->srate * SPEEX_PTIME / 1000; + st->channels = ac->ch; + + /* Decoder */ + st->dec = speex_decoder_init(resolve_mode(ac->srate)); + if (!st->dec) { + err = ENOMEM; + goto out; + } + + speex_bits_init(&st->bits); + + if (2 == st->channels) { + + /* Stereo. */ + st->stereo.balance = 1; + st->stereo.e_ratio = .5f; + st->stereo.smooth_left = 1; + st->stereo.smooth_right = 1; + + st->callback.callback_id = SPEEX_INBAND_STEREO; + st->callback.func = speex_std_stereo_request_handler; + st->callback.data = &st->stereo; + speex_decoder_ctl(st->dec, SPEEX_SET_HANDLER, + &st->callback); + } + + decoder_config(st->dec); + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *st, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + const size_t n = st->channels * st->frame_size; + int ret, r; + + if (*len < 128) + return ENOMEM; + + /* VAD */ + if (!sampv || !sampc) { + /* 5 zeros interpreted by Speex as silence (submode 0) */ + speex_bits_pack(&st->bits, 0, 5); + goto out; + } + + /* Handle multiple Speex frames in one RTP packet */ + while (sampc > 0) { + + /* Assume stereo */ + if (2 == st->channels) { + speex_encode_stereo_int((int16_t *)sampv, + st->frame_size, &st->bits); + } + + ret = speex_encode_int(st->enc, (int16_t *)sampv, &st->bits); + if (1 != ret) { + warning("speex: speex_encode_int: ret=%d\n", ret); + } + + sampc -= n; + sampv += n; + } + + out: + /* Terminate bit stream */ + speex_bits_pack(&st->bits, 15, 5); + + r = speex_bits_write(&st->bits, (char *)buf, (int)*len); + *len = r; + + speex_bits_reset(&st->bits); + + return 0; +} + + +static int decode(struct audec_state *st, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + const size_t n = st->channels * st->frame_size; + size_t i = 0; + + /* Read into bit-stream */ + speex_bits_read_from(&st->bits, (char *)buf, (int)len); + + /* Handle multiple Speex frames in one RTP packet */ + while (speex_bits_remaining(&st->bits) >= MIN_FRAME_SIZE) { + int ret; + + if (*sampc < n) + return ENOMEM; + + ret = speex_decode_int(st->dec, &st->bits, + (int16_t *)&sampv[i]); + if (ret < 0) { + if (-1 == ret) { + } + else if (-2 == ret) { + warning("speex: decode: corrupt stream\n"); + } + else { + warning("speex: decode: speex_decode_int:" + " ret=%d\n", ret); + } + break; + } + + /* Transforms a mono frame into a stereo frame + using intensity stereo info */ + if (2 == st->channels) { + speex_decode_stereo_int((int16_t *)&sampv[i], + st->frame_size, + &st->stereo); + } + + i += n; + *sampc -= n; + } + + *sampc = i; + + return 0; +} + + +static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc) +{ + const size_t n = st->channels * st->frame_size; + + if (*sampc < n) + return ENOMEM; + + /* Silence */ + speex_decode_int(st->dec, NULL, sampv); + *sampc = n; + + return 0; +} + + +static void config_parse(struct conf *conf) +{ + uint32_t v; + + if (0 == conf_get_u32(conf, "speex_quality", &v)) + sconf.quality = v; + if (0 == conf_get_u32(conf, "speex_complexity", &v)) + sconf.complexity = v; + if (0 == conf_get_u32(conf, "speex_enhancement", &v)) + sconf.enhancement = v; + if (0 == conf_get_u32(conf, "speex_vbr", &v)) + sconf.vbr = v; + if (0 == conf_get_u32(conf, "speex_vad", &v)) + sconf.vad = v; +} + + +static struct aucodec speexv[] = { + + /* Stereo Speex */ + {LE_INIT, 0, "speex", 32000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 2, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + + /* Standard Speex */ + {LE_INIT, 0, "speex", 32000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 1, speex_fmtp, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, +}; + + +static int speex_init(void) +{ + size_t i; + + config_parse(conf_cur()); + + (void)re_snprintf(speex_fmtp, sizeof(speex_fmtp), + "mode=\"7\";vbr=%s;cng=on", + sconf.vad ? "vad" : (sconf.vbr ? "on" : "off")); + + for (i=0; i<ARRAY_SIZE(speexv); i++) + aucodec_register(&speexv[i]); + + return 0; +} + + +static int speex_close(void) +{ + size_t i; + for (i=0; i<ARRAY_SIZE(speexv); i++) + aucodec_unregister(&speexv[i]); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex) = { + "speex", + "codec", + speex_init, + speex_close +}; diff --git a/modules/speex_aec/module.mk b/modules/speex_aec/module.mk new file mode 100644 index 0000000..9e29696 --- /dev/null +++ b/modules/speex_aec/module.mk @@ -0,0 +1,15 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex_aec +$(MOD)_SRCS += speex_aec.c +ifneq ($(HAVE_SPEEXDSP),) +$(MOD)_LFLAGS += "-lspeexdsp" +else +$(MOD)_LFLAGS += "-lspeex" +endif + +include mk/mod.mk diff --git a/modules/speex_aec/speex_aec.c b/modules/speex_aec/speex_aec.c new file mode 100644 index 0000000..15ea552 --- /dev/null +++ b/modules/speex_aec/speex_aec.c @@ -0,0 +1,222 @@ +/** + * @file speex_aec.c Speex Acoustic Echo Cancellation + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <speex/speex.h> +#include <speex/speex_echo.h> +#include <re.h> +#include <baresip.h> + + +struct speex_st { + int16_t *out; + SpeexEchoState *state; +}; + +struct enc_st { + struct aufilt_enc_st af; /* base class */ + struct speex_st *st; +}; + +struct dec_st { + struct aufilt_dec_st af; /* base class */ + struct speex_st *st; +}; + + +static void enc_destructor(void *arg) +{ + struct enc_st *st = arg; + + list_unlink(&st->af.le); + mem_deref(st->st); +} + + +static void dec_destructor(void *arg) +{ + struct dec_st *st = arg; + + list_unlink(&st->af.le); + mem_deref(st->st); +} + + +#ifdef SPEEX_SET_VBR_MAX_BITRATE +static void speex_aec_destructor(void *arg) +{ + struct speex_st *st = arg; + + if (st->state) + speex_echo_state_destroy(st->state); + + mem_deref(st->out); +} + + +static int aec_alloc(struct speex_st **stp, void **ctx, struct aufilt_prm *prm) +{ + struct speex_st *st; + uint32_t sampc; + int err, tmp, fl; + + if (!stp || !ctx || !prm) + return EINVAL; + + if (*ctx) { + *stp = mem_ref(*ctx); + return 0; + } + + st = mem_zalloc(sizeof(*st), speex_aec_destructor); + if (!st) + return ENOMEM; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->out = mem_alloc(2 * sampc, NULL); + if (!st->out) { + err = ENOMEM; + goto out; + } + + /* Echo canceller with 200 ms tail length */ + fl = 10 * sampc; + st->state = speex_echo_state_init(sampc, fl); + if (!st->state) { + err = ENOMEM; + goto out; + } + + tmp = prm->srate; + err = speex_echo_ctl(st->state, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp); + if (err < 0) { + warning("speex_aec: speex_echo_ctl: err=%d\n", err); + } + + info("speex_aec: Speex AEC loaded: srate = %uHz\n", prm->srate); + + out: + if (err) + mem_deref(st); + else + *ctx = *stp = st; + + return err; +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct enc_st *st; + int err; + + if (!stp || !ctx || !af || !prm) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + err = aec_alloc(&st->st, ctx, prm); + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_enc_st *)st; + + return err; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct dec_st *st; + int err; + + if (!stp || !ctx || !af || !prm) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return ENOMEM; + + err = aec_alloc(&st->st, ctx, prm); + + if (err) + mem_deref(st); + else + *stp = (struct aufilt_dec_st *)st; + + return err; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct enc_st *est = (struct enc_st *)st; + struct speex_st *sp = est->st; + + if (*sampc) { + speex_echo_capture(sp->state, sampv, sp->out); + memcpy(sampv, sp->out, *sampc * 2); + } + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct dec_st *dst = (struct dec_st *)st; + struct speex_st *sp = dst->st; + + if (*sampc) + speex_echo_playback(sp->state, sampv); + + return 0; +} +#endif + + +static struct aufilt speex_aec = { + LE_INIT, "speex_aec", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + /* Note: Hack to check libspeex version */ +#ifdef SPEEX_SET_VBR_MAX_BITRATE + aufilt_register(&speex_aec); + return 0; +#else + return ENOSYS; +#endif +} + + +static int module_close(void) +{ + aufilt_unregister(&speex_aec); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_aec) = { + "speex_aec", + "filter", + module_init, + module_close +}; diff --git a/modules/speex_pp/module.mk b/modules/speex_pp/module.mk new file mode 100644 index 0000000..fad5f88 --- /dev/null +++ b/modules/speex_pp/module.mk @@ -0,0 +1,15 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := speex_pp +$(MOD)_SRCS += speex_pp.c +ifneq ($(HAVE_SPEEXDSP),) +$(MOD)_LFLAGS += "-lspeexdsp" +else +$(MOD)_LFLAGS += "-lspeex" +endif + +include mk/mod.mk diff --git a/modules/speex_pp/speex_pp.c b/modules/speex_pp/speex_pp.c new file mode 100644 index 0000000..4c14f65 --- /dev/null +++ b/modules/speex_pp/speex_pp.c @@ -0,0 +1,154 @@ +/** + * @file speex_pp.c Speex Pre-processor + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <stdlib.h> +#include <speex/speex.h> +#include <speex/speex_preprocess.h> +#include <re.h> +#include <baresip.h> + + +struct preproc { + struct aufilt_enc_st af; /* base class */ + SpeexPreprocessState *state; +}; + + +/** Speex configuration */ +static struct { + int denoise_enabled; + int agc_enabled; + int vad_enabled; + int dereverb_enabled; + spx_int32_t agc_level; +} pp_conf = { + 1, + 1, + 1, + 1, + 8000 +}; + + +static void speexpp_destructor(void *arg) +{ + struct preproc *st = arg; + + if (st->state) + speex_preprocess_state_destroy(st->state); + + list_unlink(&st->af.le); +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct preproc *st; + unsigned sampc; + (void)ctx; + + if (!stp || !af || !prm || prm->ch != 1) + return EINVAL; + + st = mem_zalloc(sizeof(*st), speexpp_destructor); + if (!st) + return ENOMEM; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->state = speex_preprocess_state_init(sampc, prm->srate); + if (!st->state) + goto error; + + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DENOISE, + &pp_conf.denoise_enabled); + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_AGC, + &pp_conf.agc_enabled); + +#ifdef SPEEX_PREPROCESS_SET_AGC_TARGET + if (pp_conf.agc_enabled) { + speex_preprocess_ctl(st->state, + SPEEX_PREPROCESS_SET_AGC_TARGET, + &pp_conf.agc_level); + } +#endif + + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_VAD, + &pp_conf.vad_enabled); + speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DEREVERB, + &pp_conf.dereverb_enabled); + + info("speex_pp: Speex preprocessor loaded: srate = %uHz\n", + prm->srate); + + *stp = (struct aufilt_enc_st *)st; + return 0; + + error: + mem_deref(st); + return ENOMEM; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct preproc *pp = (struct preproc *)st; + int is_speech = 1; + + if (!*sampc) + return 0; + + /* NOTE: Using this macro to check libspeex version */ +#ifdef SPEEX_PREPROCESS_SET_NOISE_SUPPRESS + /* New API */ + is_speech = speex_preprocess_run(pp->state, sampv); +#else + /* Old API - not tested! */ + is_speech = speex_preprocess(pp->state, sampv, NULL); +#endif + + /* XXX: Handle is_speech and VAD */ + (void)is_speech; + + return 0; +} + + +static void config_parse(struct conf *conf) +{ + uint32_t v; + + if (0 == conf_get_u32(conf, "speex_agc_level", &v)) + pp_conf.agc_level = v; +} + + +static struct aufilt preproc = { + LE_INIT, "speex_pp", encode_update, encode, NULL, NULL +}; + +static int module_init(void) +{ + config_parse(conf_cur()); + aufilt_register(&preproc); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&preproc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_pp) = { + "speex_pp", + "filter", + module_init, + module_close +}; diff --git a/modules/srtp/module.mk b/modules/srtp/module.mk new file mode 100644 index 0000000..ac4a1c7 --- /dev/null +++ b/modules/srtp/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := srtp +$(MOD)_SRCS += srtp.c sdes.c +$(MOD)_LFLAGS += -lsrtp + +include mk/mod.mk diff --git a/modules/srtp/sdes.c b/modules/srtp/sdes.c new file mode 100644 index 0000000..a750432 --- /dev/null +++ b/modules/srtp/sdes.c @@ -0,0 +1,45 @@ +/** + * @file sdes.c SDP Security Descriptions for Media Streams (RFC 4568) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +const char sdp_attr_crypto[] = "crypto"; + + +int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite, + const char *key, size_t key_len) +{ + return sdp_media_set_lattr(m, true, sdp_attr_crypto, "%u %s inline:%b", + tag, suite, key, key_len); +} + + +/* http://tools.ietf.org/html/rfc4568 + * a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] + */ +int sdes_decode_crypto(struct crypto *c, const char *val) +{ + struct pl tag, key_prms; + int err; + + err = re_regex(val, str_len(val), "[0-9]+ [^ ]+ [^ ]+[]*[^]*", + &tag, &c->suite, &key_prms, NULL, &c->sess_prms); + if (err) + return err; + + c->tag = pl_u32(&tag); + + c->lifetime = c->mki = pl_null; + err = re_regex(key_prms.p, key_prms.l, "[^:]+:[^|]+[|]*[^|]*[|]*[^|]*", + &c->key_method, &c->key_info, + NULL, &c->lifetime, NULL, &c->mki); + if (err) + return err; + + return 0; +} diff --git a/modules/srtp/sdes.h b/modules/srtp/sdes.h new file mode 100644 index 0000000..66cc2f1 --- /dev/null +++ b/modules/srtp/sdes.h @@ -0,0 +1,22 @@ +/** + * @file sdes.h SDP Security Descriptions for Media Streams (RFC 4568) API + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct crypto { + uint32_t tag; + struct pl suite; + struct pl key_method; + struct pl key_info; + struct pl lifetime; /* optional */ + struct pl mki; /* optional */ + struct pl sess_prms; /* optional */ +}; + +extern const char sdp_attr_crypto[]; + +int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite, + const char *key, size_t key_len); +int sdes_decode_crypto(struct crypto *c, const char *val); diff --git a/modules/srtp/srtp.c b/modules/srtp/srtp.c new file mode 100644 index 0000000..bb86e96 --- /dev/null +++ b/modules/srtp/srtp.c @@ -0,0 +1,472 @@ +/** + * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711) + * + * Copyright (C) 2010 Creytiv.com + */ +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +#define DEBUG_MODULE "srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct menc_st { + /* one SRTP session per media line */ + uint8_t key_tx[32]; /* 32 for alignment, only 30 used */ + uint8_t key_rx[32]; + srtp_t srtp_tx, srtp_rx; + srtp_policy_t policy_tx, policy_rx; + bool use_srtp; + char *crypto_suite; + + void *rtpsock; + void *rtcpsock; + struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */ + struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */ + struct sdp_media *sdpm; +}; + + +static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32"; +static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80"; + + +static void destructor(void *arg) +{ + struct menc_st *st = arg; + + mem_deref(st->sdpm); + mem_deref(st->crypto_suite); + + /* note: must be done before freeing socket */ + mem_deref(st->uh_rtp); + mem_deref(st->uh_rtcp); + mem_deref(st->rtpsock); + mem_deref(st->rtcpsock); + + if (st->srtp_tx) + srtp_dealloc(st->srtp_tx); + if (st->srtp_rx) + srtp_dealloc(st->srtp_rx); +} + + +static bool cryptosuite_issupported(const struct pl *suite) +{ + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true; + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true; + + return false; +} + + +static int errstatus_print(struct re_printf *pf, err_status_t e) +{ + const char *s; + + switch (e) { + + case err_status_ok: s = "ok"; break; + case err_status_fail: s = "fail"; break; + case err_status_auth_fail: s = "auth_fail"; break; + case err_status_cipher_fail: s = "cipher_fail"; break; + case err_status_replay_fail: s = "replay_fail"; break; + + default: + return re_hprintf(pf, "err=%d", e); + } + + return re_hprintf(pf, "%s", s); +} + + +/* + * See RFC 5764 figure 3: + * + * +----------------+ + * | 127 < B < 192 -+--> forward to RTP + * | | + * packet --> | 19 < B < 64 -+--> forward to DTLS + * | | + * | B < 2 -+--> forward to STUN + * +----------------+ + * + */ +static bool is_rtp_or_rtcp(const struct mbuf *mb) +{ + uint8_t b; + + if (mbuf_get_left(mb) < 1) + return false; + + b = mbuf_buf(mb)[0]; + + return 127 < b && b < 192; +} + + +static bool is_rtcp_packet(const struct mbuf *mb) +{ + uint8_t pt; + + if (mbuf_get_left(mb) < 2) + return false; + + pt = mbuf_buf(mb)[1] & 0x7f; + + return 64 <= pt && pt <= 95; +} + + +static int start_srtp(struct menc_st *st, const char *suite) +{ + crypto_policy_t policy; + err_status_t e; + + if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) { + crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy); + } + else if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) { + crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy); + } + else { + DEBUG_WARNING("unknown SRTP crypto suite (%s)\n", suite); + return ENOENT; + } + + /* transmit policy */ + st->policy_tx.rtp = policy; + st->policy_tx.rtcp = policy; + st->policy_tx.ssrc.type = ssrc_any_outbound; + st->policy_tx.key = st->key_tx; + st->policy_tx.next = NULL; + + /* receive policy */ + st->policy_rx.rtp = policy; + st->policy_rx.rtcp = policy; + st->policy_rx.ssrc.type = ssrc_any_inbound; + st->policy_rx.key = st->key_rx; + st->policy_rx.next = NULL; + + /* allocate and initialize the SRTP session */ + e = srtp_create(&st->srtp_tx, &st->policy_tx); + if (e != err_status_ok) { + DEBUG_WARNING("srtp_create TX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + e = srtp_create(&st->srtp_rx, &st->policy_rx); + if (err_status_ok != e) { + DEBUG_WARNING("srtp_create RX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + /* use SRTP for this stream/session */ + st->use_srtp = true; + + return 0; +} + + +static int setup_srtp(struct menc_st *st) +{ + err_status_t e; + + /* init SRTP */ + e = crypto_get_random(st->key_tx, SRTP_MASTER_KEY_LEN); + if (err_status_ok != e) { + DEBUG_WARNING("crypto_get_random() failed (%H)\n", + errstatus_print, e); + return ENOSYS; + } + + return 0; +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)dst; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) { + mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN); + } + + if (is_rtcp_packet(mb)) { + e = srtp_protect_rtcp(st->srtp_tx, mbuf_buf(mb), &len); + } + else { + e = srtp_protect(st->srtp_tx, mbuf_buf(mb), &len); + } + + if (err_status_ok != e) { + DEBUG_WARNING("send: failed to protect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + *err = EPROTO; + return false; + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)src; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (is_rtcp_packet(mb)) { + e = srtp_unprotect_rtcp(st->srtp_rx, mbuf_buf(mb), &len); + } + else { + e = srtp_unprotect(st->srtp_rx, mbuf_buf(mb), &len); + } + + if (e != err_status_ok) { + DEBUG_WARNING("recv: failed to unprotect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + return true; /* error - drop packet */ + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */ +static int sdp_enc(struct menc_st *st, struct sdp_media *m, + uint32_t tag, const char *suite) +{ + char key[128] = ""; + size_t olen; + int err; + + olen = sizeof(key); + err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen); + if (err) + return err; + + return sdes_encode_crypto(m, tag, suite, key, olen); +} + + +static int start_crypto(struct menc_st *st, const struct pl *key_info) +{ + size_t olen; + int err; + + /* key-info is BASE64 encoded */ + + olen = sizeof(st->key_rx); + err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen); + if (err) + return err; + + if (SRTP_MASTER_KEY_LEN != olen) { + DEBUG_WARNING("srtp keylen is %u (should be 30)\n", olen); + } + + err = start_srtp(st, st->crypto_suite); + if (err) + return err; + + info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n", + sdp_media_name(st->sdpm), st->crypto_suite); + + return 0; +} + + +static bool sdp_attr_handler(const char *name, const char *value, void *arg) +{ + struct menc_st *st = arg; + struct crypto c; + (void)name; + + if (sdes_decode_crypto(&c, value)) + return false; + + if (0 != pl_strcmp(&c.key_method, "inline")) + return false; + + if (!cryptosuite_issupported(&c.suite)) + return false; + + st->crypto_suite = mem_deref(st->crypto_suite); + pl_strdup(&st->crypto_suite, &c.suite); + + if (start_crypto(st, &c.key_info)) + return false; + + sdp_enc(st, st->sdpm, c.tag, st->crypto_suite); + + return true; +} + + +static int alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_st *st; + const char *rattr = NULL; + int layer = 10; /* above zero */ + int err = 0; + bool mux = (rtpsock == rtcpsock); + (void)sess; + (void)rtp; + + if (!stp || !sdpm) + return EINVAL; + if (proto != IPPROTO_UDP) + return EPROTONOSUPPORT; + + st = (struct menc_st *)*stp; + if (!st) { + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->sdpm = mem_ref(sdpm); + + err = sdp_media_set_alt_protos(st->sdpm, 4, + "RTP/AVP", + "RTP/AVPF", + "RTP/SAVP", + "RTP/SAVPF"); + if (err) + goto out; + + if (rtpsock) { + st->rtpsock = mem_ref(rtpsock); + err |= udp_register_helper(&st->uh_rtp, rtpsock, + layer, send_handler, + recv_handler, st); + } + if (rtcpsock && !mux) { + st->rtcpsock = mem_ref(rtcpsock); + err |= udp_register_helper(&st->uh_rtcp, rtcpsock, + layer, send_handler, + recv_handler, st); + } + if (err) + goto out; + + /* set our preferred crypto-suite */ + err |= str_dup(&st->crypto_suite, aes_cm_128_hmac_sha1_80); + if (err) + goto out; + + err = setup_srtp(st); + if (err) + goto out; + } + + /* SDP handling */ + + if (sdp_media_rattr(st->sdpm, "crypto")) { + + rattr = sdp_media_rattr_apply(st->sdpm, "crypto", + sdp_attr_handler, st); + if (!rattr) { + DEBUG_WARNING("no valid a=crypto attribute from" + " remote peer\n"); + } + } + + if (!rattr) + err = sdp_enc(st, sdpm, 0, st->crypto_suite); + + out: + if (err) + mem_deref(st); + else + *stp = (struct menc_media *)st; + + return err; +} + + +static struct menc menc_srtp_opt = { + LE_INIT, "srtp", "RTP/AVP", NULL, alloc +}; + +static struct menc menc_srtp_mand = { + LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc +}; + +static struct menc menc_srtp_mandf = { + LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc +}; + + +static int mod_srtp_init(void) +{ + err_status_t err; + + err = srtp_init(); + if (err_status_ok != err) { + DEBUG_WARNING("srtp_init() failed (%H)\n", + errstatus_print, err); + return ENOSYS; + } + + menc_register(&menc_srtp_opt); + menc_register(&menc_srtp_mand); + menc_register(&menc_srtp_mandf); + + return 0; +} + + +static int mod_srtp_close(void) +{ + menc_unregister(&menc_srtp_mandf); + menc_unregister(&menc_srtp_mand); + menc_unregister(&menc_srtp_opt); + + crypto_kernel_shutdown(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(srtp) = { + "srtp", + "menc", + mod_srtp_init, + mod_srtp_close +}; diff --git a/modules/stdio/module.mk b/modules/stdio/module.mk new file mode 100644 index 0000000..4c52b28 --- /dev/null +++ b/modules/stdio/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := stdio +$(MOD)_SRCS += stdio.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/stdio/stdio.c b/modules/stdio/stdio.c new file mode 100644 index 0000000..bfdc9e7 --- /dev/null +++ b/modules/stdio/stdio.c @@ -0,0 +1,182 @@ +/** + * @file stdio.c Standard Input/Output UI module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <termios.h> +#include <re.h> +#include <baresip.h> + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct ui *ui; /* base class */ + struct tmr tmr; + struct termios term; + bool term_set; + ui_input_h *h; + void *arg; +}; + + +/* We only allow one instance */ +static struct ui_st *_ui; +static struct ui *stdio; + + +static void ui_destructor(void *arg) +{ + struct ui_st *st = arg; + + fd_close(STDIN_FILENO); + + if (st->term_set) + tcsetattr(STDIN_FILENO, TCSANOW, &st->term); + + tmr_cancel(&st->tmr); + mem_deref(st->ui); + + _ui = NULL; +} + + +static int print_handler(const char *p, size_t size, void *arg) +{ + (void)arg; + + return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM; +} + + +static void report_key(struct ui_st *ui, char key) +{ + struct re_printf pf; + + pf.vph = print_handler; + + if (ui->h) + ui->h(key, &pf, ui->arg); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, 0x00); +} + + +static void ui_fd_handler(int flags, void *arg) +{ + struct ui_st *st = arg; + char key; + (void)flags; + + if (1 != read(STDIN_FILENO, &key, 1)) { + return; + } + + tmr_start(&st->tmr, RELEASE_VAL, timeout, st); + report_key(st, key); +} + + +static int term_setup(struct ui_st *st) +{ + struct termios now; + + if (tcgetattr(STDIN_FILENO, &st->term) < 0) + return errno; + + now = st->term; + + now.c_lflag |= ISIG; + now.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + + /* required on Solaris */ + now.c_cc[VMIN] = 1; + now.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &now) < 0) + return errno; + + st->term_set = true; + + return 0; +} + + +static int ui_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *ih, void *arg) +{ + struct ui_st *st; + int err; + + (void)prm; + + if (!stp) + return EINVAL; + + if (_ui) { + *stp = mem_ref(_ui); + return 0; + } + + st = mem_zalloc(sizeof(*st), ui_destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(stdio); + tmr_init(&st->tmr); + + err = fd_listen(STDIN_FILENO, FD_READ, ui_fd_handler, st); + if (err) + goto out; + + err = term_setup(st); + if (err) { + info("stdio: could not setup terminal: %m\n", err); + err = 0; + } + + st->h = ih; + st->arg = arg; + + out: + if (err) + mem_deref(st); + else + *stp = _ui = st; + + return err; +} + + +static int module_init(void) +{ + return ui_register(&stdio, "stdio", ui_alloc, NULL); +} + + +static int module_close(void) +{ + stdio = mem_deref(stdio); + return 0; +} + + +const struct mod_export DECL_EXPORTS(stdio) = { + "stdio", + "ui", + module_init, + module_close +}; diff --git a/modules/stun/module.mk b/modules/stun/module.mk new file mode 100644 index 0000000..6ae88b5 --- /dev/null +++ b/modules/stun/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := stun +$(MOD)_SRCS += stun.c + +include mk/mod.mk diff --git a/modules/stun/stun.c b/modules/stun/stun.c new file mode 100644 index 0000000..d7edf94 --- /dev/null +++ b/modules/stun/stun.c @@ -0,0 +1,251 @@ +/** + * @file stun.c STUN Module for Media NAT-traversal + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup stun stun + * + * Session Traversal Utilities for NAT (STUN) for media NAT traversal + */ + + +enum {LAYER = 0, INTERVAL = 30}; + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + mnat_estab_h *estabh; + void *arg; + int mediac; +}; + + +struct mnat_media { + struct le le; + struct sa addr1; + struct sa addr2; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct stun_keepalive *ska1; + struct stun_keepalive *ska2; + void *sock1; + void *sock2; + int proto; +}; + + +static struct mnat *mnat; + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->ska1); + mem_deref(m->ska2); + mem_deref(m->sock1); + mem_deref(m->sock2); +} + + +static void mapped_handler1(int err, const struct sa *map_addr, void *arg) +{ + struct mnat_media *m = arg; + + if (!err) { + + sdp_media_set_laddr(m->sdpm, map_addr); + + m->addr1 = *map_addr; + + if (m->ska2 && !sa_isset(&m->addr2, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, 0, NULL, m->sess->arg); +} + + +static void mapped_handler2(int err, const struct sa *map_addr, void *arg) +{ + struct mnat_media *m = arg; + + if (!err) { + + sdp_media_set_laddr_rtcp(m->sdpm, map_addr); + + m->addr2 = *map_addr; + + if (m->ska1 && !sa_isset(&m->addr1, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, 0, NULL, m->sess->arg); +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + if (m->sock1) { + err |= stun_keepalive_alloc(&m->ska1, m->proto, + m->sock1, LAYER, &sess->srv, NULL, + mapped_handler1, m); + } + if (m->sock2) { + err |= stun_keepalive_alloc(&m->ska2, m->proto, + m->sock2, LAYER, &sess->srv, NULL, + mapped_handler2, m); + } + if (err) + return err; + + stun_keepalive_enable(m->ska1, INTERVAL); + stun_keepalive_enable(m->ska2, INTERVAL); + + return 0; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err; + (void)user; + (void)pass; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + sess->estabh = estabh; + sess->arg = arg; + + err = stun_server_discover(&sess->dnsq, dnsc, + stun_usage_binding, stun_proto_udp, + af, srv, port, dns_handler, sess); + + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->sock1 = mem_ref(sock1); + m->sock2 = mem_ref(sock2); + m->proto = proto; + + if (sa_isset(&sess->srv, SA_ALL)) + err = media_start(sess, m); + + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static int module_init(void) +{ + return mnat_register(&mnat, "stun", NULL, session_alloc, media_alloc, + NULL); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(stun) = { + "stun", + "mnat", + module_init, + module_close, +}; diff --git a/modules/syslog/module.mk b/modules/syslog/module.mk new file mode 100644 index 0000000..94fd185 --- /dev/null +++ b/modules/syslog/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := syslog +$(MOD)_SRCS += syslog.c + +include mk/mod.mk diff --git a/modules/syslog/syslog.c b/modules/syslog/syslog.c new file mode 100644 index 0000000..40e5690 --- /dev/null +++ b/modules/syslog/syslog.c @@ -0,0 +1,112 @@ +/** + * @file syslog.c Syslog module + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#define _GNU_SOURCE 1 +#include <stdio.h> +#include <syslog.h> +#include <re.h> +#include <baresip.h> + + +#define DEBUG_MODULE "syslog" +#define DEBUG_LEVEL 6 +#include <re_dbg.h> + + +#if defined (DARWIN) || defined (__GLIBC__) + +static FILE *fv[2]; + + +static int writer(void *cookie, const char *p, int len) +{ + (void)cookie; + + syslog(LOG_NOTICE, "%.*s", (int)len, p); + + return len; +} + + +static void tolog(int ix, FILE **pfp) +{ +#if defined (__GLIBC__) + static cookie_io_functions_t memfile_func = { + .write = (cookie_write_function_t *)writer + }; +#endif + FILE *f; + +#if defined (__GLIBC__) + f = fopencookie(NULL, "w+", memfile_func); +#else + f = fwopen(NULL, writer); +#endif + + if (!f) + return; + + setvbuf(f, NULL, _IOLBF, 0); + fv[ix] = *pfp = f; +} + + +static void restore(int ix, FILE **fp) +{ + if (fv[ix]) { + *fp = fv[ix]; + fv[ix] = NULL; + } +} +#endif + + +static void syslog_handler(int level, const char *p, size_t len, void *arg) +{ + (void)arg; + + syslog(level, "%.*s", (int)len, p); +} + + +static int module_init(void) +{ + openlog("baresip", LOG_NDELAY | LOG_PID, LOG_LOCAL0); + +#if defined (DARWIN) || defined (__GLIBC__) + /* Redirect stdout/stderr to syslog */ + tolog(0, &stdout); + tolog(1, &stderr); +#endif + + dbg_init(DBG_INFO, DBG_NONE); + dbg_handler_set(syslog_handler, NULL); + + return 0; +} + + +static int module_close(void) +{ + dbg_handler_set(NULL, NULL); + +#if defined (DARWIN) || defined (__GLIBC__) + restore(0, &stdout); + restore(1, &stderr); +#endif + + closelog(); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(syslog) = { + "syslog", + "syslog", + module_init, + module_close +}; diff --git a/modules/turn/module.mk b/modules/turn/module.mk new file mode 100644 index 0000000..876308d --- /dev/null +++ b/modules/turn/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := turn +$(MOD)_SRCS += turn.c + +include mk/mod.mk diff --git a/modules/turn/turn.c b/modules/turn/turn.c new file mode 100644 index 0000000..41588e6 --- /dev/null +++ b/modules/turn/turn.c @@ -0,0 +1,300 @@ +/** + * @file turn.c TURN Module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup turn turn + * + * Traversal Using Relays around NAT (TURN) for media NAT traversal + * + * XXX: use turn RSV_TOKEN for RTP/RTCP even/odd pair ? + */ + + +enum {LAYER = 0}; + + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + char *user; + char *pass; + mnat_estab_h *estabh; + void *arg; + int mediac; +}; + + +struct mnat_media { + struct le le; + struct sa addr1; + struct sa addr2; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct turnc *turnc1; + struct turnc *turnc2; + void *sock1; + void *sock2; + int proto; +}; + + +static struct mnat *mnat; + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_flush(&sess->medial); + mem_deref(sess->dnsq); + mem_deref(sess->user); + mem_deref(sess->pass); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->turnc1); + mem_deref(m->turnc2); + mem_deref(m->sock1); + mem_deref(m->sock2); +} + + +static void turn_handler1(int err, uint16_t scode, const char *reason, + const struct sa *relay_addr, + const struct sa *mapped_addr, + const struct stun_msg *msg, + void *arg) +{ + struct mnat_media *m = arg; + (void)mapped_addr; + (void)msg; + + if (!err && !scode) { + + sdp_media_set_laddr(m->sdpm, relay_addr); + + m->addr1 = *relay_addr; + + if (m->turnc2 && !sa_isset(&m->addr2, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static void turn_handler2(int err, uint16_t scode, const char *reason, + const struct sa *relay_addr, + const struct sa *mapped_addr, + const struct stun_msg *msg, + void *arg) +{ + struct mnat_media *m = arg; + (void)mapped_addr; + (void)msg; + + if (!err && !scode) { + + sdp_media_set_laddr_rtcp(m->sdpm, relay_addr); + + m->addr2 = *relay_addr; + + if (m->turnc1 && !sa_isset(&m->addr1, SA_ALL)) + return; + + if (--m->sess->mediac) + return; + } + + m->sess->estabh(err, scode, reason, m->sess->arg); +} + + +static int media_start(struct mnat_sess *sess, struct mnat_media *m) +{ + int err = 0; + + if (m->sock1) { + err |= turnc_alloc(&m->turnc1, NULL, + m->proto, m->sock1, LAYER, + &sess->srv, sess->user, sess->pass, + TURN_DEFAULT_LIFETIME, + turn_handler1, m); + } + if (m->sock2) { + err |= turnc_alloc(&m->turnc2, NULL, + m->proto, m->sock2, LAYER, + &sess->srv, sess->user, sess->pass, + TURN_DEFAULT_LIFETIME, + turn_handler2, m); + } + + return err; +} + + +static void dns_handler(int err, const struct sa *srv, void *arg) +{ + struct mnat_sess *sess = arg; + struct le *le; + + if (err) + goto out; + + sess->srv = *srv; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + + err = media_start(sess, m); + if (err) + goto out; + } + + return; + + out: + sess->estabh(err, 0, NULL, sess->arg); +} + + +static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc, + int af, const char *srv, uint16_t port, + const char *user, const char *pass, + struct sdp_session *ss, bool offerer, + mnat_estab_h *estabh, void *arg) +{ + struct mnat_sess *sess; + int err; + (void)ss; + (void)offerer; + + if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh) + return EINVAL; + + sess = mem_zalloc(sizeof(*sess), session_destructor); + if (!sess) + return ENOMEM; + + err = str_dup(&sess->user, user); + err |= str_dup(&sess->pass, pass); + if (err) + goto out; + + sess->estabh = estabh; + sess->arg = arg; + + err = stun_server_discover(&sess->dnsq, dnsc, + stun_usage_relay, stun_proto_udp, + af, srv, port, dns_handler, sess); + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess, + int proto, void *sock1, void *sock2, + struct sdp_media *sdpm) +{ + struct mnat_media *m; + int err = 0; + + if (!mp || !sess || !sdpm) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + list_append(&sess->medial, &m->le, m); + m->sdpm = mem_ref(sdpm); + m->sess = sess; + m->sock1 = mem_ref(sock1); + m->sock2 = mem_ref(sock2); + m->proto = proto; + + if (sa_isset(&sess->srv, SA_ALL)) + err = media_start(sess, m); + + if (err) + mem_deref(m); + else { + *mp = m; + ++sess->mediac; + } + + return err; +} + + +static int update(struct mnat_sess *sess) +{ + struct le *le; + int err = 0; + + if (!sess) + return EINVAL; + + for (le=sess->medial.head; le; le=le->next) { + + struct mnat_media *m = le->data; + struct sa raddr1, raddr2; + + raddr1 = *sdp_media_raddr(m->sdpm); + sdp_media_raddr_rtcp(m->sdpm, &raddr2); + + if (m->turnc1 && sa_isset(&raddr1, SA_ALL)) + err |= turnc_add_chan(m->turnc1, &raddr1, NULL, NULL); + + if (m->turnc2 && sa_isset(&raddr2, SA_ALL)) + err |= turnc_add_chan(m->turnc2, &raddr2, NULL, NULL); + } + + return err; +} + + +static int module_init(void) +{ + return mnat_register(&mnat, "turn", NULL, session_alloc, media_alloc, + update); +} + + +static int module_close(void) +{ + mnat = mem_deref(mnat); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(turn) = { + "turn", + "mnat", + module_init, + module_close, +}; diff --git a/modules/uuid/module.mk b/modules/uuid/module.mk new file mode 100644 index 0000000..64a3909 --- /dev/null +++ b/modules/uuid/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := uuid +$(MOD)_SRCS += uuid.c +ifneq ($(OS),darwin) +$(MOD)_LFLAGS += -luuid +endif + +include mk/mod.mk diff --git a/modules/uuid/uuid.c b/modules/uuid/uuid.c new file mode 100644 index 0000000..6426a6e --- /dev/null +++ b/modules/uuid/uuid.c @@ -0,0 +1,96 @@ +/** + * @file modules/uuid/uuid.c Generate and load UUID + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <uuid/uuid.h> +#include <re.h> +#include <baresip.h> + + +static int uuid_init(const char *file) +{ + char uuid[37]; + uuid_t uu; + FILE *f = NULL; + int err = 0; + + f = fopen(file, "r"); + if (f) { + err = 0; + goto out; + } + + f = fopen(file, "w"); + if (!f) { + err = errno; + warning("uuid: fopen() %s (%m)\n", file, err); + goto out; + } + + uuid_generate(uu); + + uuid_unparse(uu, uuid); + + re_fprintf(f, "%s", uuid); + + info("uuid: generated new UUID (%s)\n", uuid); + + out: + if (f) + fclose(f); + + return err; +} + + +static int uuid_load(const char *file, char *uuid, size_t sz) +{ + FILE *f = NULL; + int err = 0; + + f = fopen(file, "r"); + if (!f) + return errno; + + if (!fgets(uuid, (int)sz, f)) + err = errno; + + (void)fclose(f); + + return err; +} + + +static int module_init(void) +{ + struct config *cfg = conf_config(); + char path[256]; + int err = 0; + + err = conf_path_get(path, sizeof(path)); + if (err) + return err; + + strncat(path, "/uuid", sizeof(path) - strlen(path) - 1); + + err = uuid_init(path); + if (err) + return err; + + err = uuid_load(path, cfg->sip.uuid, sizeof(cfg->sip.uuid)); + if (err) + return err; + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(uuid) = { + "uuid", + NULL, + module_init, + NULL +}; diff --git a/modules/v4l/module.mk b/modules/v4l/module.mk new file mode 100644 index 0000000..05ff278 --- /dev/null +++ b/modules/v4l/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := v4l +$(MOD)_SRCS += v4l.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/v4l/v4l.c b/modules/v4l/v4l.c new file mode 100644 index 0000000..1f3e760 --- /dev/null +++ b/modules/v4l/v4l.c @@ -0,0 +1,257 @@ +/** + * @file v4l.c Video4Linux video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include <libv4l1-videodev.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz size; + struct mbuf *mb; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static void v4l_get_caps(struct vidsrc_st *st) +{ + struct video_capability caps; + + if (-1 == ioctl(st->fd, VIDIOCGCAP, &caps)) { + warning("v4l: VIDIOCGCAP: %m\n", errno); + return; + } + + info("v4l: video: \"%s\" (%ux%u) - (%ux%u)\n", caps.name, + caps.minwidth, caps.minheight, + caps.maxwidth, caps.maxheight); + + if (VID_TYPE_CAPTURE != caps.type) { + warning("v4l: not a capture device (%d)\n", caps.type); + } +} + + +static int v4l_check_palette(struct vidsrc_st *st) +{ + struct video_picture pic; + + if (-1 == ioctl(st->fd, VIDIOCGPICT, &pic)) { + warning("v4l: VIDIOCGPICT: %m\n", errno); + return errno; + } + + if (VIDEO_PALETTE_RGB24 != pic.palette) { + warning("v4l: unsupported palette %d (only RGB24 supp.)\n", + pic.palette); + return ENODEV; + } + + return 0; +} + + +static int v4l_get_win(int fd, int width, int height) +{ + struct video_window win; + + if (-1 == ioctl(fd, VIDIOCGWIN, &win)) { + warning("v4l: VIDIOCGWIN: %m\n", errno); + return errno; + } + + info("v4l: video window: x,y=%u,%u (%u x %u)\n", + win.x, win.y, win.width, win.height); + + win.width = width; + win.height = height; + + if (-1 == ioctl(fd, VIDIOCSWIN, &win)) { + warning("v4l: VIDIOCSWIN: %m\n", errno); + return errno; + } + + return 0; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size, buf); + + st->frameh(&frame, st->arg); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + + while (st->run) { + ssize_t n; + + n = read(st->fd, st->mb->buf, st->mb->size); + if ((ssize_t)st->mb->size != n) { + warning("v4l: video read: %d -> %d bytes\n", + st->mb->size, n); + continue; + } + + call_frame_handler(st, st->mb->buf); + } + + return NULL; +} + + +static int vd_open(struct vidsrc_st *v4l, const char *device) +{ + /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open + * the video device. + */ + v4l->fd = open(device, O_RDWR); + if (v4l->fd < 0) { + warning("v4l: open %s: %m\n", device, errno); + return errno; + } + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->fd >= 0) + close(st->fd); + + mem_deref(st->mb); + mem_deref(st->vs); +} + + +static uint32_t rgb24_size(const struct vidsz *sz) +{ + return sz ? (sz->w * sz->h * 24/8) : 0; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + if (!str_isset(dev)) + dev = "/dev/video0"; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->fd = -1; + st->size = *size; + st->frameh = frameh; + st->arg = arg; + + info("v4l: open: %s (%u x %u)\n", dev, size->w, size->h); + + err = vd_open(st, dev); + if (err) + goto out; + + v4l_get_caps(st); + + err = v4l_check_palette(st); + if (err) + goto out; + + err = v4l_get_win(st->fd, st->size.w, st->size.h); + if (err) + goto out; + + /* note: assumes RGB24 */ + st->mb = mbuf_alloc(rgb24_size(&st->size)); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int v4l_init(void) +{ + return vidsrc_register(&vidsrc, "v4l", alloc, NULL); +} + + +static int v4l_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l) = { + "v4l", + "vidsrc", + v4l_init, + v4l_close +}; diff --git a/modules/v4l2/module.mk b/modules/v4l2/module.mk new file mode 100644 index 0000000..61360cb --- /dev/null +++ b/modules/v4l2/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := v4l2 +$(MOD)_SRCS += v4l2.c +$(MOD)_LFLAGS += -lv4l2 + +include mk/mod.mk diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c new file mode 100644 index 0000000..2b60908 --- /dev/null +++ b/modules/v4l2/v4l2.c @@ -0,0 +1,575 @@ +/** + * @file v4l2.c Video4Linux2 video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include <linux/videodev2.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libv4l2.h> + + +enum io_method { + IO_METHOD_READ = 0, + IO_METHOD_MMAP +}; + +struct buffer { + void *start; + size_t length; +}; + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz sz, app_sz; + struct mbuf *mb; + vidsrc_frame_h *frameh; + void *arg; + enum io_method io; + struct buffer *buffers; + unsigned int n_buffers; +}; + + +static struct vidsrc *vidsrc; + + +static void get_video_input(struct vidsrc_st *st) +{ + struct v4l2_input input; + + memset(&input, 0, sizeof(input)); + + if (-1 == v4l2_ioctl(st->fd, VIDIOC_G_INPUT, &input.index)) { + warning("v4l2: VIDIOC_G_INPUT: %m\n", errno); + return; + } + + if (-1 == v4l2_ioctl(st->fd, VIDIOC_ENUMINPUT, &input)) { + warning("v4l2: VIDIOC_ENUMINPUT: %m\n", errno); + return; + } + + info("v4l2: Current input: %s\n", input.name); +} + + +static int xioctl(int fd, unsigned long int request, void *arg) +{ + int r; + + do { + r = v4l2_ioctl(fd, request, arg); + } + while (-1 == r && EINTR == errno); + + return r; +} + + +static int init_read(struct vidsrc_st *st, unsigned int buffer_size) +{ + st->buffers = calloc(1, sizeof (*st->buffers)); + if (!st->buffers) + return ENOMEM; + + st->buffers[0].length = buffer_size; + st->buffers[0].start = malloc(buffer_size); + if (!st->buffers[0].start) + return ENOMEM; + + return 0; +} + + +static int init_mmap(struct vidsrc_st *st, const char *dev_name) +{ + struct v4l2_requestbuffers req; + + memset(&req, 0, sizeof(req)); + + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(st->fd, VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + warning("v4l2: %s does not support " + "memory mapping\n", dev_name); + return errno; + } + else { + return errno; + } + } + + if (req.count < 2) { + warning("v4l2: Insufficient buffer memory on %s\n", dev_name); + return ENOMEM; + } + + st->buffers = calloc(req.count, sizeof(*st->buffers)); + if (!st->buffers) + return ENOMEM; + + for (st->n_buffers = 0; st->n_buffers<req.count; ++st->n_buffers) { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = st->n_buffers; + + if (-1 == xioctl(st->fd, VIDIOC_QUERYBUF, &buf)) { + warning("v4l2: VIDIOC_QUERYBUF\n"); + return errno; + } + + st->buffers[st->n_buffers].length = buf.length; + st->buffers[st->n_buffers].start = + v4l2_mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + st->fd, buf.m.offset); + + if (MAP_FAILED == st->buffers[st->n_buffers].start) { + warning("v4l2: mmap failed\n"); + return ENODEV; + } + } + + return 0; +} + + +static int v4l2_init_device(struct vidsrc_st *st, const char *dev_name) +{ + struct v4l2_capability cap; + struct v4l2_format fmt; + unsigned int min; + const char *pix; + int err; + + if (-1 == xioctl(st->fd, VIDIOC_QUERYCAP, &cap)) { + if (EINVAL == errno) { + warning("v4l2: %s is no V4L2 device\n", dev_name); + return ENODEV; + } + else { + warning("v4l2: VIDIOC_QUERYCAP: %m\n", errno); + return errno; + } + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + warning("v4l2: %s is no video capture device\n", dev_name); + return ENODEV; + } + + switch (st->io) { + + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + warning("%s does not support read i/o\n", dev_name); + return ENOSYS; + } + break; + + case IO_METHOD_MMAP: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + warning("v4l2: %s does not support streaming i/o\n", + dev_name); + return ENOSYS; + } + break; + } + + /* Select video input, video standard and tune here. */ + + memset(&fmt, 0, sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = st->app_sz.w; + fmt.fmt.pix.height = st->app_sz.h; + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (-1 == xioctl(st->fd, VIDIOC_S_FMT, &fmt)) { + warning("v4l2: VIDIOC_S_FMT: %m\n", errno); + return errno; + } + + /* Note VIDIOC_S_FMT may change width and height. */ + + /* Buggy driver paranoia. */ + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + + st->sz.w = fmt.fmt.pix.width; + st->sz.h = fmt.fmt.pix.height; + + if (!vidsz_cmp(&st->sz, &st->app_sz)) { + info("v4l2: scaling %u x %u ---> %u x %u\n", + st->sz.w, st->sz.h, st->app_sz.w, st->app_sz.h); + } + + switch (st->io) { + + case IO_METHOD_READ: + err = init_read(st, fmt.fmt.pix.sizeimage); + break; + + case IO_METHOD_MMAP: + err = init_mmap(st, dev_name); + break; + + default: + warning("v4l2: unknown io: %d\n", st->io); + err = EINVAL; + break; + } + + if (err) + return err; + + pix = (char *)&fmt.fmt.pix.pixelformat; + + if (V4L2_PIX_FMT_YUV420 != fmt.fmt.pix.pixelformat) { + warning("v4l2: %s: expected YUV420 got %c%c%c%c\n", dev_name, + pix[0], pix[1], pix[2], pix[3]); + return ENODEV; + } + + info("v4l2: %s: found valid V4L2 device (%u x %u) pixfmt=%c%c%c%c\n", + dev_name, fmt.fmt.pix.width, fmt.fmt.pix.height, + pix[0], pix[1], pix[2], pix[3]); + + return 0; +} + + +static void stop_capturing(struct vidsrc_st *st) +{ + enum v4l2_buf_type type; + + if (st->fd < 0) + return; + + switch (st->io) { + + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (-1 == xioctl(st->fd, VIDIOC_STREAMOFF, &type)) + warning("v4l2: VIDIOC_STREAMOFF\n"); + break; + } +} + + +static void uninit_device(struct vidsrc_st *st) +{ + unsigned int i; + + switch (st->io) { + + case IO_METHOD_READ: + if (st->buffers) + free(st->buffers[0].start); + break; + + case IO_METHOD_MMAP: + for (i=0; i<st->n_buffers; ++i) + v4l2_munmap(st->buffers[i].start, + st->buffers[i].length); + break; + } + + free(st->buffers); +} + + +static int start_capturing(struct vidsrc_st *st, int fd) +{ + unsigned int i; + enum v4l2_buf_type type; + + switch (st->io) { + + case IO_METHOD_READ: + /* Nothing to do. */ + break; + + case IO_METHOD_MMAP: + for (i = 0; i < st->n_buffers; ++i) { + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + if (-1 == xioctl (fd, VIDIOC_QBUF, &buf)) + return errno; + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (-1 == xioctl (fd, VIDIOC_STREAMON, &type)) + return errno; + + break; + } + + return 0; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, VID_FMT_YUV420P, &st->sz, buf); + + st->frameh(&frame, st->arg); +} + + +static int read_frame(struct vidsrc_st *st) +{ + struct v4l2_buffer buf; + ssize_t n; + + switch (st->io) { + + case IO_METHOD_READ: + n = v4l2_read(st->fd, st->mb->buf, st->mb->size); + if (-1 == n) { + switch (errno) { + + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + warning("v4l2: read error: %m\n", errno); + BREAKPOINT; + return errno; + } + } + + call_frame_handler(st, st->mb->buf); + break; + + case IO_METHOD_MMAP: + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl (st->fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + + case EAGAIN: + return 0; + + case EIO: + /* Could ignore EIO, see spec. */ + + /* fall through */ + + default: + warning("v4l2: VIDIOC_DQBUF: %m\n", errno); + return errno; + } + } + + if (buf.index >= st->n_buffers) { + warning("v4l2: index >= n_buffers\n"); + } + + call_frame_handler(st, st->buffers[buf.index].start); + + if (-1 == xioctl (st->fd, VIDIOC_QBUF, &buf)) { + warning("v4l2: VIDIOC_QBUF\n"); + return errno; + } + break; + } + + return 0; +} + + +static int vd_open(struct vidsrc_st *st, const char *device) +{ + /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open + * the video device. + */ + st->fd = v4l2_open(device, O_RDWR); + if (st->fd < 0) { + warning("v4l2: open %s: %m\n", device, errno); + return errno; + } + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + stop_capturing(st); + uninit_device(st); + + if (st->fd >= 0) + v4l2_close(st->fd); + + mem_deref(st->mb); + mem_deref(st->vs); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + int err; + + while (st->run) { + err = read_frame(st); + if (err) { + warning("v4l2: read_frame: %m\n", err); + } + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + if (!str_isset(dev)) + dev = "/dev/video0"; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->fd = -1; + st->io = IO_METHOD_MMAP; + + st->app_sz = *size; + st->frameh = frameh; + st->arg = arg; + + err = vd_open(st, dev); + if (err) + goto out; + + /* Try Video4Linux 2 first .. */ + err = v4l2_init_device(st, dev); + if (err) + goto out; + + get_video_input(st); + + st->mb = mbuf_alloc(st->app_sz.w * st->app_sz.h * 3 / 2); + if (!st->mb) { + err = ENOMEM; + goto out; + } + + err = start_capturing(st, st->fd); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int v4l_init(void) +{ + return vidsrc_register(&vidsrc, "v4l2", alloc, NULL); +} + + +static int v4l_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2) = { + "v4l2", + "vidsrc", + v4l_init, + v4l_close +}; diff --git a/modules/vidbridge/disp.c b/modules/vidbridge/disp.c new file mode 100644 index 0000000..bce9cff --- /dev/null +++ b/modules/vidbridge/disp.c @@ -0,0 +1,95 @@ +/** + * @file vidbridge/disp.c Video bridge -- display + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->vidsrc) + st->vidsrc->vidisp = NULL; + + list_unlink(&st->le); + mem_deref(st->device); + mem_deref(st->vd); +} + + +int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + (void)prm; + (void)resizeh; + (void)arg; + + if (!stp || !vd || !dev) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + + err = str_dup(&st->device, dev); + if (err) + goto out; + + /* find the vidsrc with the same device-name */ + st->vidsrc = vidbridge_src_find(dev); + if (st->vidsrc) { + st->vidsrc->vidisp = st; + } + + hash_append(ht_disp, hash_joaat_str(dev), &st->le, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static bool list_apply_handler(struct le *le, void *arg) +{ + struct vidisp_st *st = le->data; + + return 0 == str_cmp(st->device, arg); +} + + +int vidbridge_disp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + int err = 0; + (void)title; + + if (st->vidsrc) + vidbridge_src_input(st->vidsrc, frame); + else { + debug("vidbridge: display: dropping frame (%u x %u)\n", + frame->size.w, frame->size.h); + } + + return err; +} + + +struct vidisp_st *vidbridge_disp_find(const char *device) +{ + return list_ledata(hash_lookup(ht_disp, hash_joaat_str(device), + list_apply_handler, (void *)device)); +} diff --git a/modules/vidbridge/module.mk b/modules/vidbridge/module.mk new file mode 100644 index 0000000..13000b6 --- /dev/null +++ b/modules/vidbridge/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vidbridge +$(MOD)_SRCS += vidbridge.c src.c disp.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/vidbridge/src.c b/modules/vidbridge/src.c new file mode 100644 index 0000000..ff9bf70 --- /dev/null +++ b/modules/vidbridge/src.c @@ -0,0 +1,94 @@ +/** + * @file vidbridge/src.c Video bridge -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->vidisp) + st->vidisp->vidsrc = NULL; + + list_unlink(&st->le); + mem_deref(st->device); + mem_deref(st->vs); +} + + +int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + (void)ctx; + (void)prm; + (void)fmt; + (void)errorh; + + if (!stp || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->frameh = frameh; + st->arg = arg; + + err = str_dup(&st->device, dev); + if (err) + goto out; + + /* find a vidisp device with same name */ + st->vidisp = vidbridge_disp_find(dev); + if (st->vidisp) { + st->vidisp->vidsrc = st; + } + + hash_append(ht_src, hash_joaat_str(dev), &st->le, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static bool list_apply_handler(struct le *le, void *arg) +{ + struct vidsrc_st *st = le->data; + + return 0 == str_cmp(st->device, arg); +} + + +struct vidsrc_st *vidbridge_src_find(const char *device) +{ + return list_ledata(hash_lookup(ht_src, hash_joaat_str(device), + list_apply_handler, (void *)device)); +} + + +void vidbridge_src_input(const struct vidsrc_st *st, + const struct vidframe *frame) +{ + if (!st || !frame) + return; + + if (st->frameh) + st->frameh((struct vidframe *)frame, st->arg); +} diff --git a/modules/vidbridge/vidbridge.c b/modules/vidbridge/vidbridge.c new file mode 100644 index 0000000..69aa41f --- /dev/null +++ b/modules/vidbridge/vidbridge.c @@ -0,0 +1,58 @@ +/** + * @file vidbridge.c Video bridge + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +static struct vidisp *vidisp; +static struct vidsrc *vidsrc; + +struct hash *ht_src; +struct hash *ht_disp; + + +static int module_init(void) +{ + int err; + + err = hash_alloc(&ht_src, 32); + err |= hash_alloc(&ht_disp, 32); + if (err) + return err; + + err = vidisp_register(&vidisp, "vidbridge", vidbridge_disp_alloc, + NULL, vidbridge_disp_display, 0); + if (err) + return err; + + err = vidsrc_register(&vidsrc, "vidbridge", vidbridge_src_alloc, NULL); + if (err) + return err; + + return err; +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + vidisp = mem_deref(vidisp); + + ht_src = mem_deref(ht_src); + ht_disp = mem_deref(ht_disp); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidbridge) = { + "vidbridge", + "video", + module_init, + module_close, +}; diff --git a/modules/vidbridge/vidbridge.h b/modules/vidbridge/vidbridge.h new file mode 100644 index 0000000..15fcdfc --- /dev/null +++ b/modules/vidbridge/vidbridge.h @@ -0,0 +1,47 @@ +/** + * @file vidbridge.h Video bridge -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance (1st) */ + + struct le le; + struct vidisp_st *vidisp; + char *device; + vidsrc_frame_h *frameh; + void *arg; +}; + + +struct vidisp_st { + struct vidisp *vd; /* inheritance (1st) */ + + struct le le; + struct vidsrc_st *vidsrc; + char *device; +}; + + +extern struct hash *ht_src; +extern struct hash *ht_disp; + + +int vidbridge_disp_alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg); +int vidbridge_disp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame); +struct vidisp_st *vidbridge_disp_find(const char *device); + + +int vidbridge_src_alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg); +struct vidsrc_st *vidbridge_src_find(const char *device); +void vidbridge_src_input(const struct vidsrc_st *st, + const struct vidframe *frame); diff --git a/modules/vidloop/module.mk b/modules/vidloop/module.mk new file mode 100644 index 0000000..4775ef1 --- /dev/null +++ b/modules/vidloop/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vidloop +$(MOD)_SRCS += vidloop.c + +include mk/mod.mk diff --git a/modules/vidloop/vidloop.c b/modules/vidloop/vidloop.c new file mode 100644 index 0000000..305a876 --- /dev/null +++ b/modules/vidloop/vidloop.c @@ -0,0 +1,392 @@ +/** + * @file vidloop.c Video loop + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <string.h> +#include <time.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** Video Statistics */ +struct vstat { + uint64_t tsamp; + uint32_t frames; + size_t bytes; + uint32_t bitrate; + double efps; +}; + + +/** Video loop */ +struct video_loop { + const struct vidcodec *vc; + struct config_video cfg; + struct videnc_state *enc; + struct viddec_state *dec; + struct vidisp_st *vidisp; + struct vidsrc_st *vsrc; + struct list filtencl; + struct list filtdecl; + struct vstat stat; + struct tmr tmr_bw; + uint16_t seq; +}; + + +static struct video_loop *gvl; + + +static int display(struct video_loop *vl, struct vidframe *frame) +{ + struct le *le; + int err = 0; + + if (!vidframe_isvalid(frame)) + return 0; + + /* Process video frame through all Video Filters */ + for (le = vl->filtdecl.head; le; le = le->next) { + + struct vidfilt_dec_st *st = le->data; + + if (st->vf->dech) + err |= st->vf->dech(st, frame); + } + + /* display frame */ + (void)vidisp_display(vl->vidisp, "Video Loop", frame); + + return err; +} + + +static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len, void *arg) +{ + struct video_loop *vl = arg; + struct vidframe frame; + struct mbuf *mb; + int err = 0; + + mb = mbuf_alloc(hdr_len + pld_len); + if (!mb) + return ENOMEM; + + if (hdr_len) + mbuf_write_mem(mb, hdr, hdr_len); + mbuf_write_mem(mb, pld, pld_len); + + mb->pos = 0; + + vl->stat.bytes += mbuf_get_left(mb); + + /* decode */ + frame.data[0] = NULL; + if (vl->dec) { + err = vl->vc->dech(vl->dec, &frame, marker, vl->seq++, mb); + if (err) { + warning("vidloop: codec decode: %m\n", err); + goto out; + } + } + + display(vl, &frame); + + out: + mem_deref(mb); + + return 0; +} + + +static void vidsrc_frame_handler(struct vidframe *frame, void *arg) +{ + struct video_loop *vl = arg; + struct vidframe *f2 = NULL; + struct le *le; + int err = 0; + + ++vl->stat.frames; + + if (frame->fmt != VID_FMT_YUV420P) { + + if (vidframe_alloc(&f2, VID_FMT_YUV420P, &frame->size)) + return; + + vidconv(f2, frame, 0); + + frame = f2; + } + + /* Process video frame through all Video Filters */ + for (le = vl->filtencl.head; le; le = le->next) { + + struct vidfilt_enc_st *st = le->data; + + if (st->vf->ench) + err |= st->vf->ench(st, frame); + } + + if (vl->enc) { + (void)vl->vc->ench(vl->enc, false, frame, + packet_handler, vl); + } + else { + vl->stat.bytes += vidframe_size(frame->fmt, &frame->size); + (void)display(vl, frame); + } + + mem_deref(f2); +} + + +static void vidloop_destructor(void *arg) +{ + struct video_loop *vl = arg; + + tmr_cancel(&vl->tmr_bw); + mem_deref(vl->vsrc); + mem_deref(vl->vidisp); + mem_deref(vl->enc); + mem_deref(vl->dec); + list_flush(&vl->filtencl); + list_flush(&vl->filtdecl); +} + + +static int enable_codec(struct video_loop *vl) +{ + struct videnc_param prm; + int err; + + prm.fps = vl->cfg.fps; + prm.pktsize = 1024; + prm.bitrate = vl->cfg.bitrate; + prm.max_fs = -1; + + /* Use the first video codec */ + vl->vc = vidcodec_find(NULL, NULL); + if (!vl->vc) + return ENOENT; + + err = vl->vc->encupdh(&vl->enc, vl->vc, &prm, NULL); + if (err) { + warning("vidloop: update encoder failed: %m\n", err); + return err; + } + + if (vl->vc->decupdh) { + err = vl->vc->decupdh(&vl->dec, vl->vc, NULL); + if (err) { + warning("vidloop: update decoder failed: %m\n", err); + return err; + } + } + + return 0; +} + + +static void disable_codec(struct video_loop *vl) +{ + vl->enc = mem_deref(vl->enc); + vl->dec = mem_deref(vl->dec); + vl->vc = NULL; +} + + +static void print_status(struct video_loop *vl) +{ + (void)re_fprintf(stderr, "\rstatus: EFPS=%.1f %u kbit/s \r", + vl->stat.efps, vl->stat.bitrate); +} + + +static void calc_bitrate(struct video_loop *vl) +{ + const uint64_t now = tmr_jiffies(); + + if (now > vl->stat.tsamp) { + + const uint32_t dur = (uint32_t)(now - vl->stat.tsamp); + + vl->stat.efps = 1000.0f * vl->stat.frames / dur; + + vl->stat.bitrate = (uint32_t) (8 * vl->stat.bytes / dur); + } + + vl->stat.frames = 0; + vl->stat.bytes = 0; + vl->stat.tsamp = now; +} + + +static void timeout_bw(void *arg) +{ + struct video_loop *vl = arg; + + tmr_start(&vl->tmr_bw, 5000, timeout_bw, vl); + + calc_bitrate(vl); + print_status(vl); +} + + +static int vsrc_reopen(struct video_loop *vl, const struct vidsz *sz) +{ + struct vidsrc_prm prm; + int err; + + info("vidloop: %s,%s: open video source: %u x %u\n", + vl->cfg.src_mod, vl->cfg.src_dev, + sz->w, sz->h); + + prm.orient = VIDORIENT_PORTRAIT; + prm.fps = vl->cfg.fps; + + vl->vsrc = mem_deref(vl->vsrc); + err = vidsrc_alloc(&vl->vsrc, vl->cfg.src_mod, NULL, &prm, sz, + NULL, vl->cfg.src_dev, vidsrc_frame_handler, + NULL, vl); + if (err) { + warning("x11: vidsrc %s failed: %m\n", vl->cfg.src_dev, err); + } + + return err; +} + + +static int video_loop_alloc(struct video_loop **vlp, const struct vidsz *size) +{ + struct video_loop *vl; + struct config *cfg; + struct le *le; + int err = 0; + + cfg = conf_config(); + if (!cfg) + return EINVAL; + + vl = mem_zalloc(sizeof(*vl), vidloop_destructor); + if (!vl) + return ENOMEM; + + vl->cfg = cfg->video; + tmr_init(&vl->tmr_bw); + + /* Video filters */ + for (le = list_head(vidfilt_list()); le; le = le->next) { + struct vidfilt *vf = le->data; + void *ctx = NULL; + + info("vidloop: added video-filter `%s'\n", vf->name); + + err |= vidfilt_enc_append(&vl->filtencl, &ctx, vf); + err |= vidfilt_dec_append(&vl->filtdecl, &ctx, vf); + if (err) { + warning("vidloop: vidfilt error: %m\n", err); + } + } + + err = vsrc_reopen(vl, size); + if (err) + goto out; + + err = vidisp_alloc(&vl->vidisp, NULL, NULL, NULL, NULL, vl); + if (err) { + warning("vidloop: video display failed: %m\n", err); + goto out; + } + + tmr_start(&vl->tmr_bw, 1000, timeout_bw, vl); + + out: + if (err) + mem_deref(vl); + else + *vlp = vl; + + return err; +} + + +/** + * Start the video loop (for testing) + */ +static int vidloop_start(struct re_printf *pf, void *arg) +{ + struct vidsz size; + struct config *cfg = conf_config(); + int err = 0; + + (void)arg; + + size.w = cfg->video.width; + size.h = cfg->video.height; + + if (gvl) { + if (gvl->vc) + disable_codec(gvl); + else + (void)enable_codec(gvl); + + (void)re_hprintf(pf, "%sabled codec: %s\n", + gvl->vc ? "En" : "Dis", + gvl->vc ? gvl->vc->name : ""); + } + else { + (void)re_hprintf(pf, "Enable video-loop on %s,%s: %u x %u\n", + cfg->video.src_mod, cfg->video.src_dev, + size.w, size.h); + + err = video_loop_alloc(&gvl, &size); + if (err) { + warning("vidloop: alloc: %m\n", err); + } + } + + return err; +} + + +static int vidloop_stop(struct re_printf *pf, void *arg) +{ + (void)arg; + + if (gvl) + (void)re_hprintf(pf, "Disable video-loop\n"); + gvl = mem_deref(gvl); + return 0; +} + + +static const struct cmd cmdv[] = { + {'v', 0, "Start video-loop", vidloop_start }, + {'V', 0, "Stop video-loop", vidloop_stop }, +}; + + +static int module_init(void) +{ + return cmd_register(cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + vidloop_stop(NULL, NULL); + cmd_unregister(cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = { + "vidloop", + "application", + module_init, + module_close, +}; diff --git a/modules/vpx/decode.c b/modules/vpx/decode.c new file mode 100644 index 0000000..f441619 --- /dev/null +++ b/modules/vpx/decode.c @@ -0,0 +1,273 @@ +/** + * @file vpx/decode.c VP8 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8dx.h> +#include "vp8.h" + + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct hdr { + unsigned x:1; + unsigned noref:1; + unsigned start:1; + unsigned partid:4; + /* extension fields */ + unsigned i:1; + unsigned l:1; + unsigned t:1; + unsigned k:1; + uint16_t picid; + uint8_t tl0picidx; + unsigned tid:2; + unsigned y:1; + unsigned keyidx:5; +}; + +struct viddec_state { + vpx_codec_ctx_t ctx; + struct mbuf *mb; + bool ctxup; + bool started; + uint16_t seq; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + if (vds->ctxup) + vpx_codec_destroy(&vds->ctx); + + mem_deref(vds->mb); +} + + +int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + vpx_codec_err_t res; + int err = 0; + (void)vc; + (void)fmtp; + + if (!vdsp) + return EINVAL; + + vds = *vdsp; + + if (vds) + return 0; + + vds = mem_zalloc(sizeof(*vds), destructor); + if (!vds) + return ENOMEM; + + vds->mb = mbuf_alloc(1024); + if (!vds->mb) { + err = ENOMEM; + goto out; + } + + res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp8_dx_algo, NULL, 0); + if (res) { + err = ENOMEM; + goto out; + } + + vds->ctxup = true; + + out: + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb) +{ + uint8_t v; + + memset(hdr, 0, sizeof(*hdr)); + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->x = v>>7 & 0x1; + hdr->noref = v>>5 & 0x1; + hdr->start = v>>4 & 0x1; + hdr->partid = v & 0x07; + + if (hdr->x) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->i = v>>7 & 0x1; + hdr->l = v>>6 & 0x1; + hdr->t = v>>5 & 0x1; + hdr->k = v>>4 & 0x1; + } + + if (hdr->i) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + if (v>>7 & 0x1) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + hdr->picid = (v & 0x7f)<<8; + hdr->picid += mbuf_read_u8(mb); + } + else { + hdr->picid = v & 0x7f; + } + } + + if (hdr->l) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + hdr->tl0picidx = mbuf_read_u8(mb); + } + + if (hdr->t || hdr->k) { + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + hdr->tid = v>>6 & 0x3; + hdr->y = v>>5 & 0x1; + hdr->keyidx = v & 0x1f; + } + + return 0; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +int vp8_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb) +{ + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t *img; + struct hdr hdr; + int err, i; + + if (!vds || !frame || !mb) + return EINVAL; + + err = hdr_decode(&hdr, mb); + if (err) + return err; + +#if 0 + debug("vp8: header: x=%u noref=%u start=%u partid=%u " + "i=%u l=%u t=%u k=%u " + "picid=%u tl0picidx=%u tid=%u y=%u keyidx=%u\n", + hdr.x, hdr.noref, hdr.start, hdr.partid, + hdr.i, hdr.l, hdr.t, hdr.k, + hdr.picid, hdr.tl0picidx, hdr.tid, hdr.y, hdr.keyidx); +#endif + + if (hdr.start && hdr.partid == 0) { + + mbuf_rewind(vds->mb); + vds->started = true; + } + else { + if (!vds->started) + return 0; + + if (seq_diff(vds->seq, seq) != 1) { + mbuf_rewind(vds->mb); + vds->started = false; + return 0; + } + } + + vds->seq = seq; + + err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb)); + if (err) + goto out; + + if (!marker) { + + if (vds->mb->end > DECODE_MAXSZ) { + warning("vp8: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + res = vpx_codec_decode(&vds->ctx, vds->mb->buf, + (unsigned int)vds->mb->end, NULL, 1); + if (res) { + debug("vp8: decode error: %s\n", vpx_codec_err_to_string(res)); + err = EPROTO; + goto out; + } + + img = vpx_codec_get_frame(&vds->ctx, &iter); + if (!img) { + debug("vp8: no picture\n"); + goto out; + } + + if (img->fmt != VPX_IMG_FMT_I420) { + warning("vp8: bad pixel format (%i)\n", img->fmt); + goto out; + } + + for (i=0; i<4; i++) { + frame->data[i] = img->planes[i]; + frame->linesize[i] = img->stride[i]; + } + + frame->size.w = img->d_w; + frame->size.h = img->d_h; + frame->fmt = VID_FMT_YUV420P; + + out: + mbuf_rewind(vds->mb); + vds->started = false; + + return err; +} diff --git a/modules/vpx/encode.c b/modules/vpx/encode.c new file mode 100644 index 0000000..82f6656 --- /dev/null +++ b/modules/vpx/encode.c @@ -0,0 +1,253 @@ +/** + * @file vpx/encode.c VP8 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_encoder.h> +#include <vpx/vp8cx.h> +#include "vp8.h" + + +enum { + HDR_SIZE = 4, +}; + + +struct videnc_state { + vpx_codec_ctx_t ctx; + struct vidsz size; + vpx_codec_pts_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + bool ctxup; + uint16_t picid; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + + if (ves->ctxup) + vpx_codec_destroy(&ves->ctx); +} + + +int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp) +{ + const struct vp8_vidcodec *vp8 = (struct vp8_vidcodec *)vc; + struct videnc_state *ves; + uint32_t max_fs; + (void)vp8; + + if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1)) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), destructor); + if (!ves) + return ENOMEM; + + ves->picid = rand_u16(); + + *vesp = ves; + } + else { + if (ves->ctxup && (ves->bitrate != prm->bitrate || + ves->fps != prm->fps)) { + + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + + max_fs = vp8_max_fs(fmtp); + if (max_fs > 0) + prm->max_fs = max_fs * 256; + + return 0; +} + + +static int open_encoder(struct videnc_state *ves, const struct vidsz *size) +{ + vpx_codec_enc_cfg_t cfg; + vpx_codec_err_t res; + + res = vpx_codec_enc_config_default(&vpx_codec_vp8_cx_algo, &cfg, 0); + if (res) + return EPROTO; + + cfg.g_profile = 2; + cfg.g_w = size->w; + cfg.g_h = size->h; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = ves->fps; + cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; + cfg.g_pass = VPX_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.rc_end_usage = VPX_VBR; + cfg.rc_target_bitrate = ves->bitrate; + cfg.kf_mode = VPX_KF_AUTO; + + if (ves->ctxup) { + debug("vp8: re-opening encoder\n"); + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + + res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp8_cx_algo, &cfg, + VPX_CODEC_USE_OUTPUT_PARTITION); + if (res) { + warning("vp8: enc init: %s\n", vpx_codec_err_to_string(res)); + return EPROTO; + } + + ves->ctxup = true; + + res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 16); + if (res) { + warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + + res = vpx_codec_control(&ves->ctx, VP8E_SET_NOISE_SENSITIVITY, 0); + if (res) { + warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } + + return 0; +} + + +static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool noref, bool start, + uint8_t partid, uint16_t picid) +{ + hdr[0] = 1<<7 | noref<<5 | start<<4 | (partid & 0x7); + hdr[1] = 1<<7; + hdr[2] = 1<<7 | (picid>>8 & 0x7f); + hdr[3] = picid & 0xff; +} + + +static inline int packetize(bool marker, const uint8_t *buf, size_t len, + size_t maxlen, bool noref, uint8_t partid, + uint16_t picid, videnc_packet_h *pkth, void *arg) +{ + uint8_t hdr[HDR_SIZE]; + bool start = true; + int err = 0; + + maxlen -= sizeof(hdr); + + while (len > maxlen) { + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(false, hdr, sizeof(hdr), buf, maxlen, arg); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(marker, hdr, sizeof(hdr), buf, len, arg); + + return err; +} + + +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg) +{ + vpx_enc_frame_flags_t flags = 0; + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t img; + int err, i; + + if (!ves || !frame || !pkth || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) { + + err = open_encoder(ves, &frame->size); + if (err) + return err; + + ves->size = frame->size; + } + + if (update) { + /* debug("vp8: picture update\n"); */ + flags |= VPX_EFLAG_FORCE_KF; + } + + memset(&img, 0, sizeof(img)); + + img.fmt = VPX_IMG_FMT_I420; + img.w = img.d_w = frame->size.w; + img.h = img.d_h = frame->size.h; + + for (i=0; i<4; i++) { + img.stride[i] = frame->linesize[i]; + img.planes[i] = frame->data[i]; + } + + res = vpx_codec_encode(&ves->ctx, &img, ves->pts++, 1, + flags, VPX_DL_REALTIME); + if (res) { + warning("vp8: enc error: %s\n", vpx_codec_err_to_string(res)); + return ENOMEM; + } + + ++ves->picid; + + for (;;) { + bool keyframe = false, marker = true; + const vpx_codec_cx_pkt_t *pkt; + uint8_t partid = 0; + + pkt = vpx_codec_get_cx_data(&ves->ctx, &iter); + if (!pkt) + break; + + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) + continue; + + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) + keyframe = true; + + if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) + marker = false; + + if (pkt->data.frame.partition_id >= 0) + partid = pkt->data.frame.partition_id; + + err = packetize(marker, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, !keyframe, partid, ves->picid, + pkth, arg); + if (err) + return err; + } + + return 0; +} diff --git a/modules/vpx/module.mk b/modules/vpx/module.mk new file mode 100644 index 0000000..bcd8c9d --- /dev/null +++ b/modules/vpx/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vpx +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += vpx.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lvpx + +include mk/mod.mk diff --git a/modules/vpx/sdp.c b/modules/vpx/sdp.c new file mode 100644 index 0000000..21ed9d6 --- /dev/null +++ b/modules/vpx/sdp.c @@ -0,0 +1,39 @@ +/** + * @file vpx/sdp.c VP8 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "vp8.h" + + +uint32_t vp8_max_fs(const char *fmtp) +{ + struct pl pl, max_fs; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "max-fs", &max_fs)) + return pl_u32(&max_fs); + + return 0; +} + + +int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct vp8_vidcodec *vp8 = arg; + (void)offer; + + if (!mb || !fmt || !vp8 || !vp8->max_fs) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n", + fmt->id, vp8->max_fs); +} diff --git a/modules/vpx/vp8.h b/modules/vpx/vp8.h new file mode 100644 index 0000000..fb704c5 --- /dev/null +++ b/modules/vpx/vp8.h @@ -0,0 +1,30 @@ +/** + * @file vp8.h Private VP8 Interface + * + * Copyright (C) 2010 Creytiv.com + */ + +struct vp8_vidcodec { + struct vidcodec vc; + uint32_t max_fs; +}; + +/* Encode */ +int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp); +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame, + videnc_packet_h *pkth, void *arg); + + +/* Decode */ +int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int vp8_decode(struct viddec_state *vds, struct vidframe *frame, + bool marker, uint16_t seq, struct mbuf *mb); + + +/* SDP */ +uint32_t vp8_max_fs(const char *fmtp); +int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); diff --git a/modules/vpx/vpx.c b/modules/vpx/vpx.c new file mode 100644 index 0000000..4b492cf --- /dev/null +++ b/modules/vpx/vpx.c @@ -0,0 +1,60 @@ +/** + * @file vpx.c VP8 video codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#define VPX_CODEC_DISABLE_COMPAT 1 +#define VPX_DISABLE_CTRL_TYPECHECKS 1 +#include <vpx/vpx_encoder.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8cx.h> +#include <vpx/vp8dx.h> +#include "vp8.h" + + +/* + * Experimental support for WebM VP8 video codec: + * + * http://www.webmproject.org/ + * + * http://tools.ietf.org/html/draft-ietf-payload-vp8-08 + */ + + +static struct vp8_vidcodec vpx = { + .vc = { + .name = "VP8", + .encupdh = vp8_encode_update, + .ench = vp8_encode, + .decupdh = vp8_decode_update, + .dech = vp8_decode, + .fmtp_ench = vp8_fmtp_enc, + }, + .max_fs = 3600 +}; + + +static int module_init(void) +{ + vidcodec_register((struct vidcodec *)&vpx); + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister((struct vidcodec *)&vpx); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vpx) = { + "vpx", + "codec", + module_init, + module_close +}; diff --git a/modules/vumeter/module.mk b/modules/vumeter/module.mk new file mode 100644 index 0000000..caa8605 --- /dev/null +++ b/modules/vumeter/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vumeter +$(MOD)_SRCS += vumeter.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/vumeter/vumeter.c b/modules/vumeter/vumeter.c new file mode 100644 index 0000000..1a17fa8 --- /dev/null +++ b/modules/vumeter/vumeter.c @@ -0,0 +1,198 @@ +/** + * @file vumeter.c VU-meter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <stdlib.h> +#include <re.h> +#include <baresip.h> + + +struct vumeter_enc { + struct aufilt_enc_st af; /* inheritance */ + struct tmr tmr; + int16_t avg_rec; +}; + +struct vumeter_dec { + struct aufilt_dec_st af; /* inheritance */ + struct tmr tmr; + int16_t avg_play; +}; + + +static void enc_destructor(void *arg) +{ + struct vumeter_enc *st = arg; + + list_unlink(&st->af.le); + tmr_cancel(&st->tmr); +} + + +static void dec_destructor(void *arg) +{ + struct vumeter_dec *st = arg; + + list_unlink(&st->af.le); + tmr_cancel(&st->tmr); +} + + +static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc) +{ + int32_t v = 0; + size_t i; + + if (!sampv || !sampc) + return 0; + + for (i=0; i<sampc; i++) + v += abs(sampv[i]); + + return v/sampc; +} + + +static int audio_print_vu(struct re_printf *pf, int16_t *avg) +{ + char buf[16]; + size_t res; + + res = min(2 * sizeof(buf) * (*avg)/0x8000, + sizeof(buf)-1); + + memset(buf, '=', res); + buf[res] = '\0'; + + return re_hprintf(pf, "[%-16s]", buf); +} + + +static void print_vumeter(int pos, int color, int value) +{ + /* move cursor to a fixed position */ + re_fprintf(stderr, "\x1b[%dG", pos); + + /* print VU-meter in Nice colors */ + re_fprintf(stderr, " \x1b[%dm%H\x1b[;m\r", + color, audio_print_vu, &value); +} + + +static void enc_tmr_handler(void *arg) +{ + struct vumeter_enc *st = arg; + + tmr_start(&st->tmr, 100, enc_tmr_handler, st); + print_vumeter(60, 31, st->avg_rec); +} + + +static void dec_tmr_handler(void *arg) +{ + struct vumeter_dec *st = arg; + + tmr_start(&st->tmr, 100, dec_tmr_handler, st); + print_vumeter(80, 32, st->avg_play); +} + + +static int encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_enc *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + *stp = (struct aufilt_enc_st *)st; + + return 0; +} + + +static int decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_dec *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), dec_destructor); + if (!st) + return ENOMEM; + + *stp = (struct aufilt_dec_st *)st; + + return 0; +} + + +static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_enc *vu = (struct vumeter_enc *)st; + + vu->avg_rec = calc_avg_s16(sampv, *sampc); + + if (!tmr_isrunning(&vu->tmr)) + tmr_start(&vu->tmr, 1, enc_tmr_handler, vu); + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_dec *vu = (struct vumeter_dec *)st; + + vu->avg_play = calc_avg_s16(sampv, *sampc); + + if (!tmr_isrunning(&vu->tmr)) + tmr_start(&vu->tmr, 1, dec_tmr_handler, vu); + + return 0; +} + + +static struct aufilt vumeter = { + LE_INIT, "vumeter", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + aufilt_register(&vumeter); + return 0; +} + + +static int module_close(void) +{ + aufilt_unregister(&vumeter); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vumeter) = { + "vumeter", + "filter", + module_init, + module_close +}; diff --git a/modules/wincons/module.mk b/modules/wincons/module.mk new file mode 100644 index 0000000..aa1746e --- /dev/null +++ b/modules/wincons/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := wincons +$(MOD)_SRCS += wincons.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/wincons/wincons.c b/modules/wincons/wincons.c new file mode 100644 index 0000000..6724fc7 --- /dev/null +++ b/modules/wincons/wincons.c @@ -0,0 +1,183 @@ +/** + * @file wincons.c Windows console input + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <winsock2.h> +#include <re.h> +#include <baresip.h> + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct ui *ui; /* base class */ + struct tmr tmr; + struct mqueue *mq; + HANDLE hThread; + bool run; + ui_input_h *h; + void *arg; +}; + + +static struct ui *wincons; + + +static void destructor(void *arg) +{ + struct ui_st *st = arg; + + st->run = false; + CloseHandle(st->hThread); + + tmr_cancel(&st->tmr); + mem_deref(st->mq); + mem_deref(st->ui); +} + + +static int print_handler(const char *p, size_t size, void *arg) +{ + (void)arg; + + return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM; +} + + +static void report_key(struct ui_st *ui, char key) +{ + struct re_printf pf; + + pf.vph = print_handler; + + if (ui->h) + ui->h(key, &pf, ui->arg); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, 0x00); +} + + +static DWORD WINAPI input_thread(LPVOID arg) +{ + struct ui_st *st = arg; + + HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE ); + DWORD mode; + + /* Switch to raw mode */ + GetConsoleMode(hstdin, &mode); + SetConsoleMode(hstdin, 0); + + while (st->run) { + + char buf[4]; + DWORD i, count = 0; + + ReadConsole(hstdin, buf, sizeof(buf), &count, NULL); + + for (i=0; i<count; i++) { + int ch = buf[i]; + + if (ch == '\r') + ch = '\n'; + + /* + * The keys are read from a thread so we have + * to send them to the RE main event loop via + * a message queue + */ + mqueue_push(st->mq, ch, 0); + } + } + + /* Restore the console to its previous state */ + SetConsoleMode(hstdin, mode); + + return 0; +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct ui_st *st = arg; + (void)data; + + tmr_start(&st->tmr, RELEASE_VAL, timeout, st); + report_key(st, id); +} + + +static int ui_alloc(struct ui_st **stp, struct ui_prm *prm, + ui_input_h *ih, void *arg) +{ + struct ui_st *st; + DWORD threadID; + int err = 0; + (void)prm; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->ui = mem_ref(wincons); + st->h = ih; + st->arg = arg; + + tmr_init(&st->tmr); + + err = mqueue_alloc(&st->mq, mqueue_handler, st); + if (err) + goto out; + + st->run = true; + st->hThread = CreateThread(NULL, 0, input_thread, st, 0, &threadID); + if (!st->hThread) { + st->run = false; + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int module_init(void) +{ + return ui_register(&wincons, "wincons", ui_alloc, NULL); +} + + +static int module_close(void) +{ + wincons = mem_deref(wincons); + return 0; +} + + +const struct mod_export DECL_EXPORTS(wincons) = { + "wincons", + "ui", + module_init, + module_close +}; diff --git a/modules/winwave/module.mk b/modules/winwave/module.mk new file mode 100644 index 0000000..ba47da0 --- /dev/null +++ b/modules/winwave/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := winwave +$(MOD)_SRCS += winwave.c src.c play.c +$(MOD)_LFLAGS += -lwinmm + +include mk/mod.mk diff --git a/modules/winwave/play.c b/modules/winwave/play.c new file mode 100644 index 0000000..1de2e1f --- /dev/null +++ b/modules/winwave/play.c @@ -0,0 +1,223 @@ +/** + * @file winwave/play.c Windows sound driver -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define WRITE_BUFFERS 4 +#define INC_WPOS(a) ((a) = (((a) + 1) % WRITE_BUFFERS)) + + +struct auplay_st { + struct auplay *ap; /* inheritance */ + struct dspbuf bufs[WRITE_BUFFERS]; + int pos; + HWAVEOUT waveout; + bool rdy; + size_t inuse; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + int i; + + st->wh = NULL; + + /* Mark the device for closing, and wait for all the + * buffers to be returned by the driver + */ + st->rdy = false; + while (st->inuse > 0) + Sleep(50); + + waveOutClose(st->waveout); + + for (i = 0; i < WRITE_BUFFERS; i++) { + waveOutUnprepareHeader(st->waveout, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + mem_deref(st->ap); +} + + +static int dsp_write(struct auplay_st *st) +{ + MMRESULT res; + WAVEHDR *wh; + struct mbuf *mb; + + if (!st->rdy) + return EINVAL; + + wh = &st->bufs[st->pos].wh; + if (wh->dwFlags & WHDR_PREPARED) { + return EINVAL; + } + mb = st->bufs[st->pos].mb; + wh->lpData = (LPSTR)mb->buf; + + if (st->wh) { + st->wh(mb->buf, mb->size, st->arg); + } + + wh->dwBufferLength = mb->size; + wh->dwFlags = 0; + wh->dwUser = (DWORD_PTR) mb; + + waveOutPrepareHeader(st->waveout, wh, sizeof(*wh)); + + INC_WPOS(st->pos); + + res = waveOutWrite(st->waveout, wh, sizeof(*wh)); + if (res != MMSYSERR_NOERROR) + DEBUG_WARNING("dsp_write: waveOutWrite: failed: %08x\n", res); + else + st->inuse++; + + return 0; +} + + +static void CALLBACK waveOutCallback(HWAVEOUT hwo, + UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2) +{ + struct auplay_st *st = (struct auplay_st *)dwInstance; + WAVEHDR *wh = (WAVEHDR *)dwParam1; + + (void)hwo; + (void)dwParam2; + + switch (uMsg) { + + case WOM_OPEN: + st->rdy = true; + break; + + case WOM_DONE: + /*LOCK();*/ + waveOutUnprepareHeader(st->waveout, wh, sizeof(*wh)); + /*UNLOCK();*/ + st->inuse--; + dsp_write(st); + break; + + case WOM_CLOSE: + st->rdy = false; + break; + + default: + break; + } +} + + +static int write_stream_open(struct auplay_st *st, + const struct auplay_prm *prm) +{ + WAVEFORMATEX wfmt; + MMRESULT res; + uint32_t sampc; + int i; + + /* Open an audio I/O stream. */ + st->waveout = NULL; + st->pos = 0; + st->rdy = false; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + for (i = 0; i < WRITE_BUFFERS; i++) { + memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR)); + st->bufs[i].mb = mbuf_alloc(2 * sampc); + } + + wfmt.wFormatTag = WAVE_FORMAT_PCM; + wfmt.nChannels = prm->ch; + wfmt.nSamplesPerSec = prm->srate; + wfmt.wBitsPerSample = 16; + wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8; + wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; + wfmt.cbSize = 0; + + res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveOutCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveOutOpen: failed %d\n", res); + return EINVAL; + } + waveOutClose(st->waveout); + res = waveOutOpen(&st->waveout, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveOutCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveOutOpen: failed %d\n", res); + return EINVAL; + } + + return 0; +} + + +int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int i, err; + (void)device; + + if (!stp || !ap || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = mem_ref(ap); + st->wh = wh; + st->arg = arg; + + prm->fmt = AUFMT_S16LE; + + err = write_stream_open(st, prm); + if (err) + goto out; + + /* The write runs at 100ms intervals + * prepare enough buffers to suite its needs + */ + for (i = 0; i < 5; i++) + dsp_write(st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/winwave/src.c b/modules/winwave/src.c new file mode 100644 index 0000000..1793bf3 --- /dev/null +++ b/modules/winwave/src.c @@ -0,0 +1,206 @@ +/** + * @file winwave/src.c Windows sound driver -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define READ_BUFFERS 4 +#define INC_RPOS(a) ((a) = (((a) + 1) % READ_BUFFERS)) + + +struct ausrc_st { + struct ausrc *as; /* inheritance */ + struct dspbuf bufs[READ_BUFFERS]; + int pos; + HWAVEIN wavein; + bool rdy; + size_t inuse; + ausrc_read_h *rh; + void *arg; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + int i; + + st->rh = NULL; + + waveInStop(st->wavein); + waveInReset(st->wavein); + waveInClose(st->wavein); + + for (i = 0; i < READ_BUFFERS; i++) { + waveInUnprepareHeader(st->wavein, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + mem_deref(st->as); +} + + +static int add_wave_in(struct ausrc_st *st) +{ + struct dspbuf *db = &st->bufs[st->pos]; + WAVEHDR *wh = &db->wh; + MMRESULT res; + + wh->lpData = (LPSTR)db->mb->buf; + wh->dwBufferLength = db->mb->size; + wh->dwBytesRecorded = 0; + wh->dwFlags = 0; + wh->dwUser = (DWORD_PTR)db->mb; + + waveInPrepareHeader(st->wavein, wh, sizeof(*wh)); + res = waveInAddBuffer(st->wavein, wh, sizeof(*wh)); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("add_wave_in: waveInAddBuffer fail: %08x\n", + res); + return ENOMEM; + } + + INC_RPOS(st->pos); + + st->inuse++; + + return 0; +} + + +static void CALLBACK waveInCallback(HWAVEOUT hwo, + UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2) +{ + struct ausrc_st *st = (struct ausrc_st *)dwInstance; + WAVEHDR *wh = (WAVEHDR *)dwParam1; + + (void)hwo; + (void)dwParam2; + + if (!st->rh) + return; + + switch (uMsg) { + + case WIM_CLOSE: + st->rdy = false; + break; + + case WIM_OPEN: + st->rdy = true; + break; + + case WIM_DATA: + if (st->inuse < 3) + add_wave_in(st); + + st->rh((uint8_t *)wh->lpData, wh->dwBytesRecorded, st->arg); + + waveInUnprepareHeader(st->wavein, wh, sizeof(*wh)); + st->inuse--; + break; + + default: + break; + } +} + + +static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm) +{ + WAVEFORMATEX wfmt; + MMRESULT res; + uint32_t sampc; + int i, err = 0; + + /* Open an audio INPUT stream. */ + st->wavein = NULL; + st->pos = 0; + st->rdy = false; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + + for (i = 0; i < READ_BUFFERS; i++) { + memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR)); + st->bufs[i].mb = mbuf_alloc(2 * sampc); + if (!st->bufs[i].mb) + return ENOMEM; + } + + wfmt.wFormatTag = WAVE_FORMAT_PCM; + wfmt.nChannels = prm->ch; + wfmt.nSamplesPerSec = prm->srate; + wfmt.wBitsPerSample = 16; + wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8; + wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; + wfmt.cbSize = 0; + + res = waveInOpen(&st->wavein, WAVE_MAPPER, &wfmt, + (DWORD_PTR) waveInCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + DEBUG_WARNING("waveInOpen: failed %d\n", err); + return EINVAL; + } + + /* Prepare enough IN buffers to suite at least 50ms of data */ + for (i = 0; i < READ_BUFFERS; i++) + err |= add_wave_in(st); + + waveInStart(st->wavein); + + return err; +} + + +int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = mem_ref(as); + st->rh = rh; + st->arg = arg; + + prm->fmt = AUFMT_S16LE; + + err = read_stream_open(st, prm); + + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/winwave/winwave.c b/modules/winwave/winwave.c new file mode 100644 index 0000000..141fbfc --- /dev/null +++ b/modules/winwave/winwave.c @@ -0,0 +1,55 @@ +/** + * @file winwave.c Windows sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <windows.h> +#include <mmsystem.h> +#include <baresip.h> +#include "winwave.h" + + +#define DEBUG_MODULE "winwave" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +static int ww_init(void) +{ + int play_dev_count, src_dev_count; + int err; + + play_dev_count = waveOutGetNumDevs(); + src_dev_count = waveInGetNumDevs(); + + info("winwave: output devices: %d, input devices: %d\n", + play_dev_count, src_dev_count); + + err = ausrc_register(&ausrc, "winwave", winwave_src_alloc); + err |= auplay_register(&auplay, "winwave", winwave_play_alloc); + + return err; +} + + +static int ww_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(winwave) = { + "winwave", + "sound", + ww_init, + ww_close +}; diff --git a/modules/winwave/winwave.h b/modules/winwave/winwave.h new file mode 100644 index 0000000..ccccddf --- /dev/null +++ b/modules/winwave/winwave.h @@ -0,0 +1,20 @@ +/** + * @file winwave.h Windows sound driver -- internal api + * + * Copyright (C) 2010 Creytiv.com + */ + + +struct dspbuf { + WAVEHDR wh; + struct mbuf *mb; +}; + + +int winwave_src_alloc(struct ausrc_st **stp, struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg); +int winwave_play_alloc(struct auplay_st **stp, struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); diff --git a/modules/x11/module.mk b/modules/x11/module.mk new file mode 100644 index 0000000..ada2daa --- /dev/null +++ b/modules/x11/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11 +$(MOD)_SRCS += x11.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext + +include mk/mod.mk diff --git a/modules/x11/x11.c b/modules/x11/x11.c new file mode 100644 index 0000000..c75b6bd --- /dev/null +++ b/modules/x11/x11.c @@ -0,0 +1,342 @@ +/** + * @file x11.c Video driver for X11 + * + * Copyright (C) 2010 Creytiv.com + */ + +#ifndef SOLARIS +#define _XOPEN_SOURCE 1 +#endif +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +struct vidisp_st { + struct vidisp *vd; /**< Inheritance (1st) */ + struct vidsz size; /**< Current size */ + + Display *disp; + Window win; + GC gc; + XImage *image; + XShmSegmentInfo shm; + bool xshmat; + bool internal; + enum vidfmt pixfmt; +}; + + +static struct vidisp *vid; /**< X11 Video-display */ + +static struct { + int shm_error; + int (*errorh) (Display *, XErrorEvent *); +} x11; + + +/* NOTE: Global handler */ +static int error_handler(Display *d, XErrorEvent *e) +{ + if (e->error_code == BadAccess) + x11.shm_error = 1; + else if (x11.errorh) + return x11.errorh(d, e); + + return 0; +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->image) { + st->image->data = NULL; + XDestroyImage(st->image); + } + + if (st->gc) + XFreeGC(st->disp, st->gc); + + if (st->xshmat) + XShmDetach(st->disp, &st->shm); + + if (st->shm.shmaddr != (char *)-1) + shmdt(st->shm.shmaddr); + + if (st->shm.shmid >= 0) + shmctl(st->shm.shmid, IPC_RMID, NULL); + + if (st->disp) { + if (st->internal && st->win) + XDestroyWindow(st->disp, st->win); + + XCloseDisplay(st->disp); + } + + mem_deref(st->vd); +} + + +static int create_window(struct vidisp_st *st, const struct vidsz *sz) +{ + st->win = XCreateSimpleWindow(st->disp, DefaultRootWindow(st->disp), + 0, 0, sz->w, sz->h, 1, 0, 0); + if (!st->win) { + warning("x11: failed to create X window\n"); + return ENOMEM; + } + + XClearWindow(st->disp, st->win); + XMapRaised(st->disp, st->win); + + return 0; +} + + +static int x11_reset(struct vidisp_st *st, const struct vidsz *sz) +{ + XWindowAttributes attrs; + XGCValues gcv; + size_t bufsz, pixsz; + int err = 0; + + if (!XGetWindowAttributes(st->disp, st->win, &attrs)) { + warning("x11: cant't get window attributes\n"); + return EINVAL; + } + + switch (attrs.depth) { + + case 24: + st->pixfmt = VID_FMT_RGB32; + pixsz = 4; + break; + + case 16: + st->pixfmt = VID_FMT_RGB565; + pixsz = 2; + break; + + case 15: + st->pixfmt = VID_FMT_RGB555; + pixsz = 2; + break; + + default: + warning("x11: colordepth not supported: %d\n", attrs.depth); + return ENOSYS; + } + + bufsz = sz->w * sz->h * pixsz; + + if (st->image) { + XDestroyImage(st->image); + st->image = NULL; + } + + if (st->xshmat) + XShmDetach(st->disp, &st->shm); + + if (st->shm.shmaddr != (char *)-1) + shmdt(st->shm.shmaddr); + + if (st->shm.shmid >= 0) + shmctl(st->shm.shmid, IPC_RMID, NULL); + + st->shm.shmid = shmget(IPC_PRIVATE, bufsz, IPC_CREAT | 0777); + if (st->shm.shmid < 0) { + warning("x11: failed to allocate shared memory\n"); + return ENOMEM; + } + + st->shm.shmaddr = shmat(st->shm.shmid, NULL, 0); + if (st->shm.shmaddr == (char *)-1) { + warning("x11: failed to attach to shared memory\n"); + return ENOMEM; + } + + st->shm.readOnly = true; + + x11.shm_error = 0; + x11.errorh = XSetErrorHandler(error_handler); + + if (!XShmAttach(st->disp, &st->shm)) { + warning("x11: failed to attach X to shared memory\n"); + return ENOMEM; + } + + XSync(st->disp, False); + XSetErrorHandler(x11.errorh); + + if (x11.shm_error) + info("x11: shared memory disabled\n"); + else + st->xshmat = true; + + gcv.graphics_exposures = false; + + st->gc = XCreateGC(st->disp, st->win, GCGraphicsExposures, &gcv); + if (!st->gc) { + warning("x11: failed to create graphics context\n"); + return ENOMEM; + } + + if (st->xshmat) { + st->image = XShmCreateImage(st->disp, attrs.visual, + attrs.depth, ZPixmap, + st->shm.shmaddr, &st->shm, + sz->w, sz->h); + } + else { + st->image = XCreateImage(st->disp, attrs.visual, + attrs.depth, ZPixmap, 0, + st->shm.shmaddr, + sz->w, sz->h, 32, 0); + + } + if (!st->image) { + warning("x11: Failed to create X image\n"); + return ENOMEM; + } + + XResizeWindow(st->disp, st->win, sz->w, sz->h); + + st->size = *sz; + + return err; +} + + +/* prm->view points to the XWINDOW ID */ +static int alloc(struct vidisp_st **stp, struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + int err = 0; + (void)dev; + (void)resizeh; + (void)arg; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = mem_ref(vd); + st->shm.shmaddr = (char *)-1; + + st->disp = XOpenDisplay(NULL); + if (!st->disp) { + warning("x11: could not open X display\n"); + err = ENODEV; + goto out; + } + + /* Use provided view, or create our own */ + if (prm && prm->view) + st->win = (Window)prm->view; + else + st->internal = true; + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + struct vidframe frame_rgb; + int err = 0; + + if (!vidsz_cmp(&st->size, &frame->size)) { + char capt[256]; + + if (st->size.w && st->size.h) { + info("x11: reset: %u x %u ---> %u x %u\n", + st->size.w, st->size.h, + frame->size.w, frame->size.h); + } + + if (st->internal && !st->win) + err = create_window(st, &frame->size); + + err |= x11_reset(st, &frame->size); + if (err) + return err; + + if (title) { + re_snprintf(capt, sizeof(capt), "%s - %u x %u", + title, frame->size.w, frame->size.h); + } + else { + re_snprintf(capt, sizeof(capt), "%u x %u", + frame->size.w, frame->size.h); + } + + XStoreName(st->disp, st->win, capt); + } + + /* Convert from YUV420P to RGB */ + + vidframe_init_buf(&frame_rgb, st->pixfmt, &frame->size, + (uint8_t *)st->shm.shmaddr); + + vidconv(&frame_rgb, frame, 0); + + /* draw */ + if (st->xshmat) + XShmPutImage(st->disp, st->win, st->gc, st->image, + 0, 0, 0, 0, st->size.w, st->size.h, false); + else + XPutImage(st->disp, st->win, st->gc, st->image, + 0, 0, 0, 0, st->size.w, st->size.h); + + XSync(st->disp, false); + + return err; +} + + +static void hide(struct vidisp_st *st) +{ + if (!st) + return; + + if (st->win) + XLowerWindow(st->disp, st->win); +} + + +static int module_init(void) +{ + return vidisp_register(&vid, "x11", alloc, NULL, display, hide); +} + + +static int module_close(void) +{ + vid = mem_deref(vid); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(x11) = { + "x11", + "vidisp", + module_init, + module_close, +}; diff --git a/modules/x11grab/module.mk b/modules/x11grab/module.mk new file mode 100644 index 0000000..5e9b2af --- /dev/null +++ b/modules/x11grab/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11grab +$(MOD)_SRCS += x11grab.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext + +include mk/mod.mk diff --git a/modules/x11grab/x11grab.c b/modules/x11grab/x11grab.c new file mode 100644 index 0000000..93477a5 --- /dev/null +++ b/modules/x11grab/x11grab.c @@ -0,0 +1,218 @@ +/** + * @file x11grab.c X11 grabbing video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <unistd.h> +#ifndef SOLARIS +#define _XOPEN_SOURCE 1 +#endif +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/* + * XXX: add option to select a specific X window and x,y offset + */ + + +struct vidsrc_st { + struct vidsrc *vs; /* inheritance */ + Display *disp; + XImage *image; + pthread_t thread; + bool run; + int fps; + struct vidsz size; + enum vidfmt pixfmt; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static int x11grab_open(struct vidsrc_st *st, const struct vidsz *sz) +{ + int x = 0, y = 0; + + st->disp = XOpenDisplay(NULL); + if (!st->disp) { + warning("x11grab: error opening display\n"); + return ENODEV; + } + + st->image = XGetImage(st->disp, + RootWindow(st->disp, DefaultScreen(st->disp)), + x, y, sz->w, sz->h, AllPlanes, ZPixmap); + if (!st->image) { + warning("x11grab: error creating Ximage\n"); + return ENODEV; + } + + switch (st->image->bits_per_pixel) { + + case 32: + st->pixfmt = VID_FMT_RGB32; + break; + + case 16: + st->pixfmt = (st->image->green_mask == 0x7e0) + ? VID_FMT_RGB565 + : VID_FMT_RGB555; + break; + + default: + warning("x11grab: not supported: bpp=%d\n", + st->image->bits_per_pixel); + return ENOSYS; + } + + return 0; +} + + +static inline uint8_t *x11grab_read(struct vidsrc_st *st) +{ + const int x = 0, y = 0; + XImage *im; + + im = XGetSubImage(st->disp, + RootWindow(st->disp, DefaultScreen(st->disp)), + x, y, st->size.w, st->size.h, AllPlanes, ZPixmap, + st->image, 0, 0); + if (!im) + return NULL; + + return (uint8_t *)st->image->data; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, st->pixfmt, &st->size, buf); + + st->frameh(&frame, st->arg); +} + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + uint64_t ts = tmr_jiffies(); + uint8_t *buf; + + while (st->run) { + + if (tmr_jiffies() < ts) { + sys_msleep(4); + continue; + } + + buf = x11grab_read(st); + if (!buf) + continue; + + ts += (1000/st->fps); + + call_frame_handler(st, buf); + } + + return NULL; +} + + +static void destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (st->image) + XDestroyImage(st->image); + + if (st->disp) + XCloseDisplay(st->disp); + + mem_deref(st->vs); +} + + +static int alloc(struct vidsrc_st **stp, struct vidsrc *vs, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, + const char *dev, vidsrc_frame_h *frameh, + vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc_st *st; + int err; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + if (!stp || !prm || !size || !frameh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = mem_ref(vs); + st->size = *size; + st->fps = prm->fps; + st->frameh = frameh; + st->arg = arg; + + err = x11grab_open(st, size); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int x11grab_init(void) +{ + return vidsrc_register(&vidsrc, "x11grab", alloc, NULL); +} + + +static int x11grab_close(void) +{ + vidsrc = mem_deref(vidsrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(x11grab) = { + "x11grab", + "vidsrc", + x11grab_init, + x11grab_close +}; diff --git a/modules/zrtp/module.mk b/modules/zrtp/module.mk new file mode 100644 index 0000000..7bcc0ac --- /dev/null +++ b/modules/zrtp/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := zrtp +$(MOD)_SRCS += zrtp.c +$(MOD)_LFLAGS += -lzrtp -lbn +CFLAGS += -I/usr/local/include/libzrtp + +include mk/mod.mk diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c new file mode 100644 index 0000000..1055483 --- /dev/null +++ b/modules/zrtp/zrtp.c @@ -0,0 +1,300 @@ +/** + * @file zrtp.c ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <zrtp.h> + + +/** + * @defgroup zrtp zrtp + * + * ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Experimental support for ZRTP + * + * See http://tools.ietf.org/html/rfc6189 + * + * Briefly tested with Twinkle 1.4.2 and Jitsi 2.2.4603.9615 + * + * This module is using ZRTP implementation in Freeswitch + * https://github.com/traviscross/libzrtp + */ + + +struct menc_sess { + zrtp_session_t *zrtp_session; +}; + +struct menc_media { + const struct menc_sess *sess; + struct udp_helper *uh; + struct sa raddr; + void *rtpsock; + zrtp_stream_t *zrtp_stream; +}; + + +static zrtp_global_t *zrtp_global; +static zrtp_config_t zrtp_config; + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = arg; + + if (st->zrtp_session) + zrtp_session_down(st->zrtp_session); +} + + +static void media_destructor(void *arg) +{ + struct menc_media *st = arg; + + mem_deref(st->uh); + mem_deref(st->rtpsock); + + if (st->zrtp_stream) + zrtp_stream_stop(st->zrtp_stream); +} + + +static bool udp_helper_send(int *err, struct sa *dst, + struct mbuf *mb, void *arg) +{ + struct menc_media *st = arg; + unsigned int length; + zrtp_status_t s; + (void)dst; + + length = (unsigned int)mbuf_get_left(mb); + + s = zrtp_process_rtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_process_rtp failed (status = %d)\n", s); + return false; + } + + /* make sure target buffer is large enough */ + if (length > mbuf_get_space(mb)) { + warning("zrtp: zrtp_process_rtp: length > space (%u > %u)\n", + length, mbuf_get_space(mb)); + *err = ENOMEM; + } + + mb->end = mb->pos + length; + + return false; +} + + +static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_media *st = arg; + unsigned int length; + zrtp_status_t s; + (void)src; + + length = (unsigned int)mbuf_get_left(mb); + + s = zrtp_process_srtp(st->zrtp_stream, (char *)mbuf_buf(mb), &length); + if (s != zrtp_status_ok) { + + if (s == zrtp_status_drop) + return true; + + warning("zrtp: zrtp_process_srtp: %d\n", s); + return false; + } + + mb->end = mb->pos + length; + + return false; +} + + +static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, + bool offerer, menc_error_h *errorh, void *arg) +{ + struct menc_sess *st; + zrtp_status_t s; + int err = 0; + (void)offerer; + (void)errorh; + (void)arg; + + if (!sessp || !sdp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + s = zrtp_session_init(zrtp_global, NULL, + ZRTP_SIGNALING_ROLE_UNKNOWN, &st->zrtp_session); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_session_init failed (status = %d)\n", s); + err = EPROTO; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *sessp = st; + + return err; +} + + +static int media_alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_media *st; + zrtp_status_t s; + int err = 0; + + if (!stp || !sess || proto != IPPROTO_UDP) + return EINVAL; + + st = *stp; + if (st) + goto start; + + st = mem_zalloc(sizeof(*st), media_destructor); + if (!st) + return ENOMEM; + + st->sess = sess; + st->rtpsock = mem_ref(rtpsock); + + err = udp_register_helper(&st->uh, rtpsock, 0, + udp_helper_send, udp_helper_recv, st); + if (err) + goto out; + + s = zrtp_stream_attach(sess->zrtp_session, &st->zrtp_stream); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_stream_attach failed (status=%d)\n", s); + err = EPROTO; + goto out; + } + + zrtp_stream_set_userdata(st->zrtp_stream, st); + + out: + if (err) { + mem_deref(st); + return err; + } + else + *stp = st; + + start: + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + st->raddr = *sdp_media_raddr(sdpm); + + s = zrtp_stream_start(st->zrtp_stream, rtp_sess_ssrc(rtp)); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_stream_start: status = %d\n", s); + } + } + + return err; +} + + +static int zrtp_send_rtp_callback(const zrtp_stream_t *stream, + char *rtp_packet, + unsigned int rtp_packet_length) +{ + struct menc_media *st = zrtp_stream_get_userdata(stream); + struct mbuf *mb; + int err; + + if (!sa_isset(&st->raddr, SA_ALL)) + return zrtp_status_ok; + + mb = mbuf_alloc(rtp_packet_length); + if (!mb) + return zrtp_status_alloc_fail; + + (void)mbuf_write_mem(mb, (void *)rtp_packet, rtp_packet_length); + mb->pos = 0; + + err = udp_send(st->rtpsock, &st->raddr, mb); + if (err) { + warning("zrtp: udp_send %u bytes (%m)\n", + rtp_packet_length, err); + } + + mem_deref(mb); + + return zrtp_status_ok; +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static int module_init(void) +{ + zrtp_status_t s; + char config_path[256] = ""; + int err; + + zrtp_config_defaults(&zrtp_config); + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + if (re_snprintf(zrtp_config.cache_file_cfg.cache_path, + sizeof(zrtp_config.cache_file_cfg.cache_path), + "%s/zrtp_cache.dat", config_path) < 0) + return ENOMEM; + + rand_bytes(zrtp_config.zid, sizeof(zrtp_config.zid)); + + zrtp_config.cb.misc_cb.on_send_packet = zrtp_send_rtp_callback; + + s = zrtp_init(&zrtp_config, &zrtp_global); + if (zrtp_status_ok != s) { + warning("zrtp: zrtp_init() failed (status = %d)\n", s); + return ENOSYS; + } + + menc_register(&menc_zrtp); + + return 0; +} + + +static int module_close(void) +{ + menc_unregister(&menc_zrtp); + + if (zrtp_global) { + zrtp_down(zrtp_global); + zrtp_global = NULL; + } + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(zrtp) = { + "zrtp", + "menc", + module_init, + module_close +}; 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 Binary files differnew file mode 100644 index 0000000..2e1b717 --- /dev/null +++ b/share/busy.wav diff --git a/share/callwaiting.wav b/share/callwaiting.wav Binary files differnew file mode 100644 index 0000000..c9bf9b2 --- /dev/null +++ b/share/callwaiting.wav diff --git a/share/error.wav b/share/error.wav Binary files differnew file mode 100644 index 0000000..2c48280 --- /dev/null +++ b/share/error.wav diff --git a/share/message.wav b/share/message.wav Binary files differnew file mode 100644 index 0000000..d36e466 --- /dev/null +++ b/share/message.wav diff --git a/share/notfound.wav b/share/notfound.wav Binary files differnew file mode 100644 index 0000000..9f1b055 --- /dev/null +++ b/share/notfound.wav diff --git a/share/ring.wav b/share/ring.wav Binary files differnew file mode 100644 index 0000000..1b0650f --- /dev/null +++ b/share/ring.wav diff --git a/share/ringback.wav b/share/ringback.wav Binary files differnew file mode 100644 index 0000000..d21e20e --- /dev/null +++ b/share/ringback.wav diff --git a/src/account.c b/src/account.c new file mode 100644 index 0000000..95ceeba --- /dev/null +++ b/src/account.c @@ -0,0 +1,574 @@ +/** + * @file src/account.c User-Agent account + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +enum { + REG_INTERVAL = 3600, +}; + + +static void destructor(void *arg) +{ + struct account *acc = arg; + size_t i; + + list_clear(&acc->aucodecl); + list_clear(&acc->vidcodecl); + mem_deref(acc->auth_user); + mem_deref(acc->auth_pass); + for (i=0; i<ARRAY_SIZE(acc->outbound); i++) + mem_deref(acc->outbound[i]); + mem_deref(acc->regq); + mem_deref(acc->rtpkeep); + mem_deref(acc->sipnat); + mem_deref(acc->stun_user); + mem_deref(acc->stun_pass); + mem_deref(acc->stun_host); + mem_deref(acc->mnatid); + mem_deref(acc->mencid); + mem_deref(acc->aor); + mem_deref(acc->dispname); + mem_deref(acc->buf); +} + + +static int param_dstr(char **dstr, const struct pl *params, const char *name) +{ + struct pl pl; + + if (sip_param_decode(params, name, &pl)) + return 0; + + return pl_strdup(dstr, &pl); +} + + +static int param_u32(uint32_t *v, const struct pl *params, const char *name) +{ + struct pl pl; + + if (sip_param_decode(params, name, &pl)) + return 0; + + *v = pl_u32(&pl); + + return 0; +} + + +static int stunsrv_decode(struct account *acc, const struct sip_addr *aor) +{ + struct pl srv; + struct uri uri; + int err; + + if (!acc || !aor) + return EINVAL; + + memset(&uri, 0, sizeof(uri)); + + if (0 == sip_param_decode(&aor->params, "stunserver", &srv)) { + + info("using stunserver: '%r'\n", &srv); + + err = uri_decode(&uri, &srv); + if (err) { + warning("account: %r: decode failed: %m\n", &srv, err); + memset(&uri, 0, sizeof(uri)); + } + + if (0 != pl_strcasecmp(&uri.scheme, "stun")) { + warning("account: unknown scheme: %r\n", &uri.scheme); + return EINVAL; + } + } + + err = 0; + if (pl_isset(&uri.user)) + err |= pl_strdup(&acc->stun_user, &uri.user); + else + err |= pl_strdup(&acc->stun_user, &aor->uri.user); + + if (pl_isset(&uri.password)) + err |= pl_strdup(&acc->stun_pass, &uri.password); + else + err |= pl_strdup(&acc->stun_pass, &aor->uri.password); + + if (pl_isset(&uri.host)) + err |= pl_strdup(&acc->stun_host, &uri.host); + else + err |= pl_strdup(&acc->stun_host, &aor->uri.host); + + acc->stun_port = uri.port; + + return err; +} + + +/** Decode media parameters */ +static int media_decode(struct account *acc, const struct pl *prm) +{ + int err = 0; + + if (!acc || !prm) + return EINVAL; + + err |= param_dstr(&acc->mencid, prm, "mediaenc"); + err |= param_dstr(&acc->mnatid, prm, "medianat"); + err |= param_dstr(&acc->rtpkeep, prm, "rtpkeep" ); + err |= param_u32(&acc->ptime, prm, "ptime" ); + + return err; +} + + +/* Decode answermode parameter */ +static void answermode_decode(struct account *prm, const struct pl *pl) +{ + struct pl amode; + + if (0 == sip_param_decode(pl, "answermode", &amode)) { + + if (0 == pl_strcasecmp(&amode, "manual")) { + prm->answermode = ANSWERMODE_MANUAL; + } + else if (0 == pl_strcasecmp(&amode, "early")) { + prm->answermode = ANSWERMODE_EARLY; + } + else if (0 == pl_strcasecmp(&amode, "auto")) { + prm->answermode = ANSWERMODE_AUTO; + } + else { + warning("account: answermode unknown (%r)\n", &amode); + prm->answermode = ANSWERMODE_MANUAL; + } + } +} + + +static int csl_parse(struct pl *pl, char *str, size_t sz) +{ + struct pl ws = PL_INIT, val, ws2 = PL_INIT, cma = PL_INIT; + int err; + + err = re_regex(pl->p, pl->l, "[ \t]*[^, \t]+[ \t]*[,]*", + &ws, &val, &ws2, &cma); + if (err) + return err; + + pl_advance(pl, ws.l + val.l + ws2.l + cma.l); + + (void)pl_strcpy(&val, str, sz); + + return 0; +} + + +static int audio_codecs_decode(struct account *acc, const struct pl *prm) +{ + struct pl tmp; + + if (!acc || !prm) + return EINVAL; + + list_init(&acc->aucodecl); + + if (0 == sip_param_exists(prm, "audio_codecs", &tmp)) { + struct pl acs; + char cname[64]; + unsigned i = 0; + + if (sip_param_decode(prm, "audio_codecs", &acs)) + return 0; + + while (0 == csl_parse(&acs, cname, sizeof(cname))) { + struct aucodec *ac; + struct pl pl_cname, pl_srate, pl_ch = PL_INIT; + uint32_t srate = 8000; + uint8_t ch = 1; + + /* Format: "codec/srate/ch" */ + if (0 == re_regex(cname, str_len(cname), + "[^/]+/[0-9]+[/]*[0-9]*", + &pl_cname, &pl_srate, + NULL, &pl_ch)) { + (void)pl_strcpy(&pl_cname, cname, + sizeof(cname)); + srate = pl_u32(&pl_srate); + if (pl_isset(&pl_ch)) + ch = pl_u32(&pl_ch); + } + + ac = (struct aucodec *)aucodec_find(cname, srate, ch); + if (!ac) { + warning("account: audio codec not found:" + " %s/%u/%d\n", + cname, srate, ch); + continue; + } + + /* NOTE: static list with references to aucodec */ + list_append(&acc->aucodecl, &acc->acv[i++], ac); + + if (i >= ARRAY_SIZE(acc->acv)) + break; + } + } + + return 0; +} + + +#ifdef USE_VIDEO +static int video_codecs_decode(struct account *acc, const struct pl *prm) +{ + struct pl tmp; + + if (!acc || !prm) + return EINVAL; + + list_init(&acc->vidcodecl); + + if (0 == sip_param_exists(prm, "video_codecs", &tmp)) { + struct pl vcs; + char cname[64]; + unsigned i = 0; + + if (sip_param_decode(prm, "video_codecs", &vcs)) + return 0; + + while (0 == csl_parse(&vcs, cname, sizeof(cname))) { + struct vidcodec *vc; + + vc = (struct vidcodec *)vidcodec_find(cname, NULL); + if (!vc) { + warning("account: video codec not found: %s\n", + cname); + continue; + } + + /* NOTE: static list with references to vidcodec */ + list_append(&acc->vidcodecl, &acc->vcv[i++], vc); + + if (i >= ARRAY_SIZE(acc->vcv)) + break; + } + } + + return 0; +} +#endif + + +static int sip_params_decode(struct account *acc, const struct sip_addr *aor) +{ + struct pl auth_user; + size_t i; + int err = 0; + + if (!acc || !aor) + return EINVAL; + + acc->regint = REG_INTERVAL + (rand_u32()&0xff); + err |= param_u32(&acc->regint, &aor->params, "regint"); + + err |= param_dstr(&acc->regq, &aor->params, "regq"); + + for (i=0; i<ARRAY_SIZE(acc->outbound); i++) { + + char expr[16] = "outbound"; + + expr[8] = i + 1 + 0x30; + expr[9] = '\0'; + + err |= param_dstr(&acc->outbound[i], &aor->params, expr); + } + + /* backwards compat */ + if (!acc->outbound[0]) { + err |= param_dstr(&acc->outbound[0], &aor->params, "outbound"); + } + + err |= param_dstr(&acc->sipnat, &aor->params, "sipnat"); + + if (0 == sip_param_decode(&aor->params, "auth_user", &auth_user)) + err |= pl_strdup(&acc->auth_user, &auth_user); + else + err |= pl_strdup(&acc->auth_user, &aor->uri.user); + + if (pl_isset(&aor->dname)) + err |= pl_strdup(&acc->dispname, &aor->dname); + + return err; +} + + +static int encode_uri_user(struct re_printf *pf, const struct uri *uri) +{ + struct uri uuri = *uri; + + uuri.password = uuri.params = uuri.headers = pl_null; + + return uri_encode(pf, &uuri); +} + + +static int password_prompt(struct account *acc) +{ + char pwd[64]; + char *nl; + int err; + + (void)re_printf("Please enter password for %r@%r: ", + &acc->luri.user, &acc->luri.host); + + /* note: blocking UI call */ + fgets(pwd, sizeof(pwd), stdin); + pwd[sizeof(pwd) - 1] = '\0'; + + nl = strchr(pwd, '\n'); + if (nl == NULL) { + (void)re_printf("Invalid password (0 - 63 characters" + " followed by newline)\n"); + return EINVAL; + } + + *nl = '\0'; + + err = str_dup(&acc->auth_pass, pwd); + if (err) + return err; + + return 0; +} + + +int account_alloc(struct account **accp, const char *sipaddr) +{ + struct account *acc; + struct pl pl; + int err = 0; + + if (!accp || !sipaddr) + return EINVAL; + + acc = mem_zalloc(sizeof(*acc), destructor); + if (!acc) + return ENOMEM; + + err = str_dup(&acc->buf, sipaddr); + if (err) + goto out; + + pl_set_str(&pl, acc->buf); + err = sip_addr_decode(&acc->laddr, &pl); + if (err) { + warning("account: invalid SIP address: `%r'\n", &pl); + goto out; + } + + acc->luri = acc->laddr.uri; + acc->luri.password = pl_null; + + err = re_sdprintf(&acc->aor, "%H", encode_uri_user, &acc->luri); + if (err) + goto out; + + /* Decode parameters */ + acc->ptime = 20; + err |= sip_params_decode(acc, &acc->laddr); + answermode_decode(acc, &acc->laddr.params); + err |= audio_codecs_decode(acc, &acc->laddr.params); +#ifdef USE_VIDEO + err |= video_codecs_decode(acc, &acc->laddr.params); +#endif + err |= media_decode(acc, &acc->laddr.params); + if (err) + goto out; + + /* optional password prompt */ + if (!pl_isset(&acc->laddr.uri.password)) { + err = password_prompt(acc); + if (err) + goto out; + } + else { + err = pl_strdup(&acc->auth_pass, &acc->laddr.uri.password); + if (err) + goto out; + } + + if (acc->mnatid) { + err = stunsrv_decode(acc, &acc->laddr); + if (err) + goto out; + + acc->mnat = mnat_find(acc->mnatid); + if (!acc->mnat) { + warning("account: medianat not found: %s\n", + acc->mnatid); + } + } + + if (acc->mencid) { + acc->menc = menc_find(acc->mencid); + if (!acc->menc) { + warning("account: mediaenc not found: %s\n", + acc->mencid); + } + } + + out: + if (err) + mem_deref(acc); + else + *accp = acc; + + return err; +} + + +/** + * Sets the displayed name. Pass null in dname to disable display name + * + * @param acc User-Agent account + * @param dname Display name (NULL to disable) + * + * @return 0 if success, otherwise errorcode + */ +int account_set_display_name(struct account *acc, const char *dname) +{ + if (!acc) + return EINVAL; + + acc->dispname = mem_deref(acc->dispname); + + if (dname) + return str_dup(&acc->dispname, dname); + + return 0; +} + + +/** + * Authenticate a User-Agent (UA) + * + * @param acc User-Agent account + * @param username Pointer to allocated username string + * @param password Pointer to allocated password string + * @param realm Realm string + * + * @return 0 if success, otherwise errorcode + */ +int account_auth(const struct account *acc, char **username, char **password, + const char *realm) +{ + if (!acc) + return EINVAL; + + (void)realm; + + *username = mem_ref(acc->auth_user); + *password = mem_ref(acc->auth_pass); + + return 0; +} + + +struct list *account_aucodecl(const struct account *acc) +{ + return (acc && !list_isempty(&acc->aucodecl)) + ? (struct list *)&acc->aucodecl : aucodec_list(); +} + + +#ifdef USE_VIDEO +struct list *account_vidcodecl(const struct account *acc) +{ + return (acc && !list_isempty(&acc->vidcodecl)) + ? (struct list *)&acc->vidcodecl : vidcodec_list(); +} +#endif + + +struct sip_addr *account_laddr(const struct account *acc) +{ + return acc ? (struct sip_addr *)&acc->laddr : NULL; +} + + +static const char *answermode_str(enum answermode mode) +{ + switch (mode) { + + case ANSWERMODE_MANUAL: return "manual"; + case ANSWERMODE_EARLY: return "early"; + case ANSWERMODE_AUTO: return "auto"; + default: return "???"; + } +} + + +int account_debug(struct re_printf *pf, const struct account *acc) +{ + struct le *le; + size_t i; + int err = 0; + + if (!acc) + return 0; + + err |= re_hprintf(pf, "\nAccount:\n"); + + err |= re_hprintf(pf, " address: %s\n", acc->buf); + err |= re_hprintf(pf, " luri: %H\n", + uri_encode, &acc->luri); + err |= re_hprintf(pf, " aor: %s\n", acc->aor); + err |= re_hprintf(pf, " dispname: %s\n", acc->dispname); + err |= re_hprintf(pf, " answermode: %s\n", + answermode_str(acc->answermode)); + if (!list_isempty(&acc->aucodecl)) { + err |= re_hprintf(pf, " audio_codecs:"); + for (le = list_head(&acc->aucodecl); le; le = le->next) { + const struct aucodec *ac = le->data; + err |= re_hprintf(pf, " %s/%u/%u", + ac->name, ac->srate, ac->ch); + } + err |= re_hprintf(pf, "\n"); + } + err |= re_hprintf(pf, " auth_user: %s\n", acc->auth_user); + err |= re_hprintf(pf, " mediaenc: %s\n", + acc->mencid ? acc->mencid : "none"); + err |= re_hprintf(pf, " medianat: %s\n", + acc->mnatid ? acc->mnatid : "none"); + for (i=0; i<ARRAY_SIZE(acc->outbound); i++) { + if (acc->outbound[i]) { + err |= re_hprintf(pf, " outbound%d: %s\n", + i+1, acc->outbound[i]); + } + } + err |= re_hprintf(pf, " ptime: %u\n", acc->ptime); + err |= re_hprintf(pf, " regint: %u\n", acc->regint); + err |= re_hprintf(pf, " regq: %s\n", acc->regq); + err |= re_hprintf(pf, " rtpkeep: %s\n", acc->rtpkeep); + err |= re_hprintf(pf, " sipnat: %s\n", acc->sipnat); + err |= re_hprintf(pf, " stunserver: stun:%s@%s:%u\n", + acc->stun_user, acc->stun_host, acc->stun_port); + if (!list_isempty(&acc->vidcodecl)) { + err |= re_hprintf(pf, " video_codecs:"); + for (le = list_head(&acc->vidcodecl); le; le = le->next) { + const struct vidcodec *vc = le->data; + err |= re_hprintf(pf, " %s", vc->name); + } + err |= re_hprintf(pf, "\n"); + } + + return err; +} diff --git a/src/aucodec.c b/src/aucodec.c new file mode 100644 index 0000000..67e3256 --- /dev/null +++ b/src/aucodec.c @@ -0,0 +1,76 @@ +/** + * @file aucodec.c Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list aucodecl; + + +/** + * Register an Audio Codec + * + * @param ac Audio Codec object + */ +void aucodec_register(struct aucodec *ac) +{ + if (!ac) + return; + + list_append(&aucodecl, &ac->le, ac); + + info("aucodec: %s/%u/%u\n", ac->name, ac->srate, ac->ch); +} + + +/** + * Unregister an Audio Codec + * + * @param ac Audio Codec object + */ +void aucodec_unregister(struct aucodec *ac) +{ + if (!ac) + return; + + list_unlink(&ac->le); +} + + +const struct aucodec *aucodec_find(const char *name, uint32_t srate, + uint8_t ch) +{ + struct le *le; + + for (le=aucodecl.head; le; le=le->next) { + + struct aucodec *ac = le->data; + + if (name && 0 != str_casecmp(name, ac->name)) + continue; + + if (srate && srate != ac->srate) + continue; + + if (ch && ch != ac->ch) + continue; + + return ac; + } + + return NULL; +} + + +/** + * Get the list of Audio Codecs + */ +struct list *aucodec_list(void) +{ + return &aucodecl; +} diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..548b5ac --- /dev/null +++ b/src/audio.c @@ -0,0 +1,1369 @@ +/** + * @file src/audio.c Audio stream + * + * Copyright (C) 2010 Creytiv.com + * \ref GenericAudioStream + */ +#define _BSD_SOURCE 1 +#include <string.h> +#include <stdlib.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "core.h" + + +/** Magic number */ +#define MAGIC 0x000a0d10 +#include "magic.h" + + +/** + * \page GenericAudioStream Generic Audio Stream + * + * Implements a generic audio stream. The application can allocate multiple + * instances of a audio stream, mapping it to a particular SDP media line. + * The audio object has a DSP sound card sink and source, and an audio encoder + * and decoder. A particular audio object is mapped to a generic media + * stream object. Each audio channel has an optional audio filtering chain. + * + *<pre> + * write read + * | /|\ + * \|/ | + * .------. .---------. .-------. + * |filter|<--| audio |--->|encoder| + * '------' | | |-------| + * | object |--->|decoder| + * '---------' '-------' + * | /|\ + * | | + * \|/ | + * .------. .-----. + * |auplay| |ausrc| + * '------' '-----' + *</pre> + */ + +enum { + AUDIO_SAMPSZ = 1920, +}; + + +/** + * Audio transmit/encoder + * + * + \verbatim + + Processing encoder pipeline: + + . .-------. .-------. .--------. .--------. .--------. + | | | | | | | | | | | + |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP + | | | | | | | | | | | + ' '-------' '-------' '--------' '--------' '--------' + + \endverbatim + * + */ +struct autx { + struct ausrc_st *ausrc; /**< Audio Source */ + const struct aucodec *ac; /**< Current audio encoder */ + struct auenc_state *enc; /**< Audio encoder state (optional) */ + struct aubuf *aubuf; /**< Packetize outgoing stream */ + struct auresamp resamp; /**< Optional resampler for DSP */ + struct list filtl; /**< Audio filters in encoding order */ + struct mbuf *mb; /**< Buffer for outgoing RTP packets */ + char device[64]; + int16_t *sampv; /**< Sample buffer */ + int16_t *sampv_rs; /**< Sample buffer for resampler */ + uint32_t ptime; /**< Packet time for sending */ + uint32_t ts; /**< Timestamp for outgoing RTP */ + uint32_t ts_tel; /**< Timestamp for Telephony Events */ + size_t psize; /**< Packet size for sending */ + bool marker; /**< Marker bit for outgoing RTP */ + bool is_g722; /**< Set if encoder is G.722 codec */ + bool muted; /**< Audio source is muted */ + int cur_key; /**< Currently transmitted event */ + + union { + struct tmr tmr; /**< Timer for sending RTP packets */ +#ifdef HAVE_PTHREAD + struct { + pthread_t tid;/**< Audio transmit thread */ + bool run; /**< Audio transmit thread running */ + } thr; +#endif + } u; +}; + + +/** + * Audio receive/decoder + * + \verbatim + + Processing decoder pipeline: + + .--------. .-------. .--------. .--------. .--------. + |\ | | | | | | | | | | + | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP + |/ | | | | | | | | | | + '--------' '-------' '--------' '--------' '--------' + + \endverbatim + */ +struct aurx { + struct auplay_st *auplay; /**< Audio Player */ + const struct aucodec *ac; /**< Current audio decoder */ + struct audec_state *dec; /**< Audio decoder state (optional) */ + struct aubuf *aubuf; /**< Incoming audio buffer */ + struct auresamp resamp; /**< Optional resampler for DSP */ + struct list filtl; /**< Audio filters in decoding order */ + char device[64]; + int16_t *sampv; /**< Sample buffer */ + int16_t *sampv_rs; /**< Sample buffer for resampler */ + uint32_t ptime; /**< Packet time for receiving */ + int pt; /**< Payload type for incoming RTP */ + int pt_tel; /**< Event payload type - receive */ +}; + + +/** Generic Audio stream */ +struct audio { + MAGIC_DECL /**< Magic number for debugging */ + struct autx tx; /**< Transmit */ + struct aurx rx; /**< Receive */ + struct stream *strm; /**< Generic media stream */ + struct telev *telev; /**< Telephony events */ + struct config_audio cfg; /**< Audio configuration */ + bool started; /**< Stream is started flag */ + audio_event_h *eventh; /**< Event handler */ + audio_err_h *errh; /**< Audio error handler */ + void *arg; /**< Handler argument */ +}; + + +static void stop_tx(struct autx *tx, struct audio *a) +{ + if (!tx || !a) + return; + + switch (a->cfg.txmode) { + +#ifdef HAVE_PTHREAD + case AUDIO_MODE_THREAD: + case AUDIO_MODE_THREAD_REALTIME: + if (tx->u.thr.run) { + tx->u.thr.run = false; + pthread_join(tx->u.thr.tid, NULL); + } + break; +#endif + case AUDIO_MODE_TMR: + tmr_cancel(&tx->u.tmr); + break; + + default: + break; + } + + /* audio source must be stopped first */ + tx->ausrc = mem_deref(tx->ausrc); + tx->aubuf = mem_deref(tx->aubuf); + + list_flush(&tx->filtl); +} + + +static void stop_rx(struct aurx *rx) +{ + if (!rx) + return; + + /* audio player must be stopped first */ + rx->auplay = mem_deref(rx->auplay); + rx->aubuf = mem_deref(rx->aubuf); + + list_flush(&rx->filtl); +} + + +static void audio_destructor(void *arg) +{ + struct audio *a = arg; + + stop_tx(&a->tx, a); + stop_rx(&a->rx); + + mem_deref(a->tx.enc); + mem_deref(a->rx.dec); + mem_deref(a->tx.aubuf); + mem_deref(a->tx.mb); + mem_deref(a->tx.sampv); + mem_deref(a->rx.sampv); + mem_deref(a->rx.aubuf); + mem_deref(a->tx.sampv_rs); + mem_deref(a->rx.sampv_rs); + + list_flush(&a->tx.filtl); + list_flush(&a->rx.filtl); + + mem_deref(a->strm); + mem_deref(a->telev); +} + + +/** + * Calculate number of samples from sample rate, channels and packet time + * + * @param srate Sample rate in [Hz] + * @param channels Number of channels + * @param ptime Packet time in [ms] + * + * @return Number of samples + */ +static inline uint32_t calc_nsamp(uint32_t srate, uint8_t channels, + uint16_t ptime) +{ + return srate * channels * ptime / 1000; +} + + +/** + * Get the DSP samplerate for an audio-codec (exception for G.722) + */ +static inline uint32_t get_srate(const struct aucodec *ac) +{ + if (!ac) + return 0; + + return !str_casecmp(ac->name, "G722") ? 16000 : ac->srate; +} + + +static inline uint32_t get_framesize(const struct aucodec *ac, + uint32_t ptime) +{ + if (!ac) + return 0; + + return calc_nsamp(get_srate(ac), ac->ch, ptime); +} + + +static bool aucodec_equal(const struct aucodec *a, const struct aucodec *b) +{ + if (!a || !b) + return false; + + return get_srate(a) == get_srate(b) && a->ch == b->ch; +} + + +static int add_audio_codec(struct audio *a, struct sdp_media *m, + struct aucodec *ac) +{ + if (!in_range(&a->cfg.srate, ac->srate)) { + debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n", + ac->srate, a->cfg.srate.min, a->cfg.srate.max); + return 0; + } + + if (!in_range(&a->cfg.channels, ac->ch)) { + debug("audio: skip codec with %uch (audio range %uch-%uch)\n", + ac->ch, a->cfg.channels.min, a->cfg.channels.max); + return 0; + } + + return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->srate, + ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false, + "%s", ac->fmtp); +} + + +/** + * Encoder audio and send via stream + * + * @note This function has REAL-TIME properties + * + * @param a Audio object + * @param tx Audio transmit object + * @param sampv Audio samples + * @param sampc Number of audio samples + */ +static void encode_rtp_send(struct audio *a, struct autx *tx, + int16_t *sampv, size_t sampc) +{ + size_t len; + int err; + + if (!tx->ac) + return; + + tx->mb->pos = tx->mb->end = STREAM_PRESZ; + len = mbuf_get_space(tx->mb); + + err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc); + if (err) { + warning("audio: %s encode error: %d samples (%m)\n", + tx->ac->name, sampc, err); + goto out; + } + + tx->mb->pos = STREAM_PRESZ; + tx->mb->end = STREAM_PRESZ + len; + + if (mbuf_get_left(tx->mb)) { + + err = stream_send(a->strm, tx->marker, -1, tx->ts, tx->mb); + if (err) + goto out; + } + + tx->ts += (uint32_t)(tx->is_g722 ? sampc/2 : sampc); + + out: + tx->marker = false; +} + + +/* + * @note This function has REAL-TIME properties + */ +static void poll_aubuf_tx(struct audio *a) +{ + struct autx *tx = &a->tx; + int16_t *sampv = tx->sampv; + size_t sampc; + struct le *le; + int err = 0; + + sampc = tx->psize / 2; + + /* timed read from audio-buffer */ + if (aubuf_get_samp(tx->aubuf, tx->ptime, tx->sampv, sampc)) + return; + + /* optional resampler */ + if (tx->resamp.resample) { + size_t sampc_rs = AUDIO_SAMPSZ; + + err = auresamp(&tx->resamp, + tx->sampv_rs, &sampc_rs, + tx->sampv, sampc); + if (err) + return; + + sampv = tx->sampv_rs; + sampc = sampc_rs; + } + + /* Process exactly one audio-frame in list order */ + for (le = tx->filtl.head; le; le = le->next) { + struct aufilt_enc_st *st = le->data; + + if (st->af && st->af->ench) + err |= st->af->ench(st, sampv, &sampc); + } + if (err) { + warning("audio: aufilter encode: %m\n", err); + } + + /* Encode and send */ + encode_rtp_send(a, tx, sampv, sampc); +} + + +static void check_telev(struct audio *a, struct autx *tx) +{ + const struct sdp_format *fmt; + bool marker = false; + int err; + + tx->mb->pos = tx->mb->end = STREAM_PRESZ; + + err = telev_poll(a->telev, &marker, tx->mb); + if (err) + return; + + if (marker) + tx->ts_tel = tx->ts; + + fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt); + if (!fmt) + return; + + tx->mb->pos = STREAM_PRESZ; + err = stream_send(a->strm, marker, fmt->pt, tx->ts_tel, tx->mb); + if (err) { + warning("audio: telev: stream_send %m\n", err); + } +} + + +/** + * Write samples to Audio Player. + * + * @note This function has REAL-TIME properties + * + * @note The application is responsible for filling in silence in + * the case of underrun + * + * @note This function may be called from any thread + * + * @param buf Buffer to fill with audio samples + * @param sz Number of bytes in buffer + * @param arg Handler argument + * + * @return true for valid audio samples, false for silence + */ +static bool auplay_write_handler(uint8_t *buf, size_t sz, void *arg) +{ + struct aurx *rx = arg; + + aubuf_read(rx->aubuf, buf, sz); + + return true; +} + + +/** + * Read samples from Audio Source + * + * @note This function has REAL-TIME properties + * + * @note This function may be called from any thread + * + * @param buf Buffer with audio samples + * @param sz Number of bytes in buffer + * @param arg Handler argument + */ +static void ausrc_read_handler(const uint8_t *buf, size_t sz, void *arg) +{ + struct audio *a = arg; + struct autx *tx = &a->tx; + + if (tx->muted) + memset((void *)buf, 0, sz); + + (void)aubuf_write(tx->aubuf, buf, sz); + + if (a->cfg.txmode == AUDIO_MODE_POLL) + poll_aubuf_tx(a); + + /* Exact timing: send Telephony-Events from here */ + check_telev(a, tx); +} + + +static void ausrc_error_handler(int err, const char *str, void *arg) +{ + struct audio *a = arg; + MAGIC_CHECK(a); + + if (a->errh) + a->errh(err, str, a->arg); +} + + +static int pt_handler(struct audio *a, uint8_t pt_old, uint8_t pt_new) +{ + const struct sdp_format *lc; + + lc = sdp_media_lformat(stream_sdpmedia(a->strm), pt_new); + if (!lc) + return ENOENT; + + if (pt_old != (uint8_t)-1) { + info("Audio decoder changed payload %u -> %u\n", + pt_old, pt_new); + } + + a->rx.pt = pt_new; + + return audio_decoder_set(a, lc->data, lc->pt, lc->params); +} + + +static void handle_telev(struct audio *a, struct mbuf *mb) +{ + int event, digit; + bool end; + + if (telev_recv(a->telev, mb, &event, &end)) + return; + + digit = telev_code2digit(event); + if (digit >= 0 && a->eventh) + a->eventh(digit, end, a->arg); +} + + +static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb) +{ + size_t sampc = AUDIO_SAMPSZ; + int16_t *sampv; + struct le *le; + int err = 0; + + /* No decoder set */ + if (!rx->ac) + return 0; + + if (mbuf_get_left(mb)) { + err = rx->ac->dech(rx->dec, rx->sampv, &sampc, + mbuf_buf(mb), mbuf_get_left(mb)); + } + else if (rx->ac->plch) { + err = rx->ac->plch(rx->dec, rx->sampv, &sampc); + } + else { + /* no PLC in the codec, might be done in filters below */ + sampc = 0; + } + + if (err) { + warning("audio: %s codec decode %u bytes: %m\n", + rx->ac->name, mbuf_get_left(mb), err); + goto out; + } + + /* Process exactly one audio-frame in reverse list order */ + for (le = rx->filtl.tail; le; le = le->prev) { + struct aufilt_dec_st *st = le->data; + + if (st->af && st->af->dech) + err |= st->af->dech(st, rx->sampv, &sampc); + } + + if (!rx->aubuf) + goto out; + + sampv = rx->sampv; + + /* optional resampler */ + if (rx->resamp.resample) { + size_t sampc_rs = AUDIO_SAMPSZ; + + err = auresamp(&rx->resamp, + rx->sampv_rs, &sampc_rs, + rx->sampv, sampc); + if (err) + return err; + + sampv = rx->sampv_rs; + sampc = sampc_rs; + } + + err = aubuf_write_samp(rx->aubuf, sampv, sampc); + if (err) + goto out; + + out: + return err; +} + + +/* Handle incoming stream data from the network */ +static void stream_recv_handler(const struct rtp_header *hdr, + struct mbuf *mb, void *arg) +{ + struct audio *a = arg; + struct aurx *rx = &a->rx; + int err; + + if (!mb) + goto out; + + /* Telephone event? */ + if (hdr->pt == rx->pt_tel) { + handle_telev(a, mb); + return; + } + + /* Comfort Noise (CN) as of RFC 3389 */ + if (PT_CN == hdr->pt) + return; + + /* Audio payload-type changed? */ + /* XXX: this logic should be moved to stream.c */ + if (hdr->pt != rx->pt) { + + err = pt_handler(a, rx->pt, hdr->pt); + if (err) + return; + } + + out: + (void)aurx_stream_decode(&a->rx, mb); +} + + +static int add_telev_codec(struct audio *a) +{ + struct sdp_media *m = stream_sdpmedia(audio_strm(a)); + struct sdp_format *sf; + int err; + + /* Use payload-type 101 if available, for CiscoGW interop */ + err = sdp_format_add(&sf, m, false, + (!sdp_media_lformat(m, 101)) ? "101" : NULL, + telev_rtpfmt, TELEV_SRATE, 1, NULL, + NULL, NULL, false, "0-15"); + if (err) + return err; + + a->rx.pt_tel = sf->pt; + + return err; +} + + +int audio_alloc(struct audio **ap, const struct config *cfg, + struct call *call, struct sdp_session *sdp_sess, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + uint32_t ptime, const struct list *aucodecl, + audio_event_h *eventh, audio_err_h *errh, void *arg) +{ + struct audio *a; + struct autx *tx; + struct aurx *rx; + struct le *le; + int err; + + if (!ap || !cfg) + return EINVAL; + + a = mem_zalloc(sizeof(*a), audio_destructor); + if (!a) + return ENOMEM; + + MAGIC_INIT(a); + + a->cfg = cfg->audio; + tx = &a->tx; + rx = &a->rx; + + err = stream_alloc(&a->strm, &cfg->avt, call, sdp_sess, + "audio", label, + mnat, mnat_sess, menc, menc_sess, + call_localuri(call), + stream_recv_handler, NULL, a); + if (err) + goto out; + + if (cfg->avt.rtp_bw.max) { + stream_set_bw(a->strm, AUDIO_BANDWIDTH); + } + + err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true, + "ptime", "%u", ptime); + if (err) + goto out; + + /* Audio codecs */ + for (le = list_head(aucodecl); le; le = le->next) { + err = add_audio_codec(a, stream_sdpmedia(a->strm), le->data); + if (err) + goto out; + } + + tx->mb = mbuf_alloc(STREAM_PRESZ + 4096); + tx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); + rx->sampv = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); + if (!tx->mb || !tx->sampv || !rx->sampv) { + err = ENOMEM; + goto out; + } + + err = telev_alloc(&a->telev, TELEV_PTIME); + if (err) + goto out; + + err = add_telev_codec(a); + if (err) + goto out; + + auresamp_init(&tx->resamp); + str_ncpy(tx->device, a->cfg.src_dev, sizeof(tx->device)); + tx->ptime = ptime; + tx->ts = 160; + tx->marker = true; + + auresamp_init(&rx->resamp); + str_ncpy(rx->device, a->cfg.play_dev, sizeof(rx->device)); + rx->pt = -1; + rx->ptime = ptime; + + a->eventh = eventh; + a->errh = errh; + a->arg = arg; + + if (a->cfg.txmode == AUDIO_MODE_TMR) + tmr_init(&tx->u.tmr); + + out: + if (err) + mem_deref(a); + else + *ap = a; + + return err; +} + + +#ifdef HAVE_PTHREAD +static void *tx_thread(void *arg) +{ + struct audio *a = arg; + + /* Enable Real-time mode for this thread, if available */ + if (a->cfg.txmode == AUDIO_MODE_THREAD_REALTIME) + (void)realtime_enable(true, 1); + + while (a->tx.u.thr.run) { + + poll_aubuf_tx(a); + + sys_msleep(5); + } + + return NULL; +} +#endif + + +static void timeout_tx(void *arg) +{ + struct audio *a = arg; + + tmr_start(&a->tx.u.tmr, 5, timeout_tx, a); + + poll_aubuf_tx(a); +} + + +static void aufilt_param_set(struct aufilt_prm *prm, + const struct aucodec *ac, uint32_t ptime) +{ + if (!ac) { + memset(prm, 0, sizeof(*prm)); + return; + } + + prm->srate = get_srate(ac); + prm->ch = ac->ch; + prm->ptime = ptime; +} + + +static int autx_print_pipeline(struct re_printf *pf, const struct autx *autx) +{ + struct le *le; + int err; + + if (!autx) + return 0; + + err = re_hprintf(pf, "audio tx pipeline: %10s", + autx->ausrc ? autx->ausrc->as->name : "src"); + + for (le = list_head(&autx->filtl); le; le = le->next) { + struct aufilt_enc_st *st = le->data; + + if (st->af->ench) + err |= re_hprintf(pf, " ---> %s", st->af->name); + } + + err |= re_hprintf(pf, " ---> %s\n", + autx->ac ? autx->ac->name : "encoder"); + + return err; +} + + +static int aurx_print_pipeline(struct re_printf *pf, const struct aurx *aurx) +{ + struct le *le; + int err; + + if (!aurx) + return 0; + + err = re_hprintf(pf, "audio rx pipeline: %10s", + aurx->auplay ? aurx->auplay->ap->name : "play"); + + for (le = list_head(&aurx->filtl); le; le = le->next) { + struct aufilt_dec_st *st = le->data; + + if (st->af->dech) + err |= re_hprintf(pf, " <--- %s", st->af->name); + } + + err |= re_hprintf(pf, " <--- %s\n", + aurx->ac ? aurx->ac->name : "decoder"); + + return err; +} + + +/** + * Setup the audio-filter chain + * + * must be called before auplay/ausrc-alloc + * + * @param a Audio object + * + * @return 0 if success, otherwise errorcode + */ +static int aufilt_setup(struct audio *a) +{ + struct aufilt_prm encprm, decprm; + struct autx *tx = &a->tx; + struct aurx *rx = &a->rx; + struct le *le; + int err = 0; + + /* wait until we have both Encoder and Decoder */ + if (!tx->ac || !rx->ac) + return 0; + + if (!list_isempty(&tx->filtl) || !list_isempty(&rx->filtl)) + return 0; + + aufilt_param_set(&encprm, tx->ac, tx->ptime); + aufilt_param_set(&decprm, rx->ac, rx->ptime); + + /* Audio filters */ + for (le = list_head(aufilt_list()); le; le = le->next) { + struct aufilt *af = le->data; + struct aufilt_enc_st *encst = NULL; + struct aufilt_dec_st *decst = NULL; + void *ctx = NULL; + + if (af->encupdh) { + err |= af->encupdh(&encst, &ctx, af, &encprm); + if (err) + break; + + encst->af = af; + list_append(&tx->filtl, &encst->le, encst); + } + + if (af->decupdh) { + err |= af->decupdh(&decst, &ctx, af, &decprm); + if (err) + break; + + decst->af = af; + list_append(&rx->filtl, &decst->le, decst); + } + + if (err) { + warning("audio: audio-filter '%s'" + " update failed (%m)\n", af->name, err); + break; + } + } + + return 0; +} + + +static int start_player(struct aurx *rx, struct audio *a) +{ + const struct aucodec *ac = rx->ac; + uint32_t srate_dsp = get_srate(ac); + uint32_t channels_dsp; + bool resamp = false; + int err; + + if (!ac) + return 0; + + channels_dsp = ac->ch; + + if (a->cfg.srate_play && a->cfg.srate_play != srate_dsp) { + resamp = true; + srate_dsp = a->cfg.srate_play; + } + if (a->cfg.channels_play && a->cfg.channels_play != channels_dsp) { + resamp = true; + channels_dsp = a->cfg.channels_play; + } + + /* Optional resampler, if configured */ + if (resamp && !rx->sampv_rs) { + + info("audio: enable auplay resampler:" + " %uHz/%uch --> %uHz/%uch\n", + get_srate(ac), ac->ch, srate_dsp, channels_dsp); + + rx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); + if (!rx->sampv_rs) + return ENOMEM; + + err = auresamp_setup(&rx->resamp, + get_srate(ac), ac->ch, + srate_dsp, channels_dsp); + if (err) { + warning("audio: could not setup auplay resampler" + " (%m)\n", err); + return err; + } + } + + /* Start Audio Player */ + if (!rx->auplay && auplay_find(NULL)) { + + struct auplay_prm prm; + + prm.fmt = AUFMT_S16LE; + prm.srate = srate_dsp; + prm.ch = channels_dsp; + prm.ptime = rx->ptime; + + if (!rx->aubuf) { + size_t psize; + + psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime); + + err = aubuf_alloc(&rx->aubuf, psize * 1, psize * 8); + if (err) + return err; + } + + err = auplay_alloc(&rx->auplay, a->cfg.play_mod, + &prm, rx->device, + auplay_write_handler, rx); + if (err) { + warning("audio: start_player failed (%s.%s): %m\n", + a->cfg.play_mod, rx->device, err); + return err; + } + } + + return 0; +} + + +static int start_source(struct autx *tx, struct audio *a) +{ + const struct aucodec *ac = tx->ac; + uint32_t srate_dsp = get_srate(ac); + uint32_t channels_dsp; + bool resamp = false; + int err; + + if (!ac) + return 0; + + channels_dsp = ac->ch; + + if (a->cfg.srate_src && a->cfg.srate_src != srate_dsp) { + resamp = true; + srate_dsp = a->cfg.srate_src; + } + if (a->cfg.channels_src && a->cfg.channels_src != channels_dsp) { + resamp = true; + channels_dsp = a->cfg.channels_src; + } + + /* Optional resampler, if configured */ + if (resamp && !tx->sampv_rs) { + + info("audio: enable ausrc resampler:" + " %uHz/%uch <-- %uHz/%uch\n", + get_srate(ac), ac->ch, srate_dsp, channels_dsp); + + tx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL); + if (!tx->sampv_rs) + return ENOMEM; + + err = auresamp_setup(&tx->resamp, + srate_dsp, channels_dsp, + get_srate(ac), ac->ch); + if (err) { + warning("audio: could not setup ausrc resampler" + " (%m)\n", err); + return err; + } + } + + /* Start Audio Source */ + if (!tx->ausrc && ausrc_find(NULL)) { + + struct ausrc_prm prm; + + prm.fmt = AUFMT_S16LE; + prm.srate = srate_dsp; + prm.ch = channels_dsp; + prm.ptime = tx->ptime; + + tx->psize = 2 * calc_nsamp(prm.srate, prm.ch, prm.ptime); + + if (!tx->aubuf) { + err = aubuf_alloc(&tx->aubuf, tx->psize * 2, + tx->psize * 30); + if (err) + return err; + } + + err = ausrc_alloc(&tx->ausrc, NULL, a->cfg.src_mod, + &prm, tx->device, + ausrc_read_handler, ausrc_error_handler, a); + if (err) { + warning("audio: start_source failed: %m\n", err); + return err; + } + + switch (a->cfg.txmode) { +#ifdef HAVE_PTHREAD + case AUDIO_MODE_THREAD: + case AUDIO_MODE_THREAD_REALTIME: + if (!tx->u.thr.run) { + tx->u.thr.run = true; + err = pthread_create(&tx->u.thr.tid, NULL, + tx_thread, a); + if (err) { + tx->u.thr.tid = false; + return err; + } + } + break; +#endif + + case AUDIO_MODE_TMR: + tmr_start(&tx->u.tmr, 1, timeout_tx, a); + break; + + default: + break; + } + } + + return 0; +} + + +/** + * Start the audio playback and recording + * + * @param a Audio object + * + * @return 0 if success, otherwise errorcode + */ +int audio_start(struct audio *a) +{ + int err; + + if (!a) + return EINVAL; + + /* Audio filter */ + if (!list_isempty(aufilt_list())) { + err = aufilt_setup(a); + if (err) + return err; + } + + /* configurable order of play/src start */ + if (a->cfg.src_first) { + err = start_source(&a->tx, a); + err |= start_player(&a->rx, a); + } + else { + err = start_player(&a->rx, a); + err |= start_source(&a->tx, a); + } + if (err) + return err; + + if (a->tx.ac && a->rx.ac) { + + if (!a->started) { + info("%H%H", + autx_print_pipeline, &a->tx, + aurx_print_pipeline, &a->rx); + } + + a->started = true; + } + + return err; +} + + +/** + * Stop the audio playback and recording + * + * @param a Audio object + */ +void audio_stop(struct audio *a) +{ + if (!a) + return; + + stop_tx(&a->tx, a); + stop_rx(&a->rx); +} + + +int audio_encoder_set(struct audio *a, const struct aucodec *ac, + int pt_tx, const char *params) +{ + struct autx *tx; + int err = 0; + bool reset; + + if (!a || !ac) + return EINVAL; + + tx = &a->tx; + + reset = !aucodec_equal(ac, tx->ac); + + if (ac != tx->ac) { + info("audio: Set audio encoder: %s %uHz %dch\n", + ac->name, get_srate(ac), ac->ch); + + /* Audio source must be stopped first */ + if (reset) { + tx->ausrc = mem_deref(tx->ausrc); + } + + tx->is_g722 = (0 == str_casecmp(ac->name, "G722")); + tx->enc = mem_deref(tx->enc); + tx->ac = ac; + } + + if (ac->encupdh) { + struct auenc_param prm; + + prm.ptime = tx->ptime; + + err = ac->encupdh(&tx->enc, ac, &prm, params); + if (err) { + warning("audio: alloc encoder: %m\n", err); + return err; + } + } + + stream_set_srate(a->strm, get_srate(ac), get_srate(ac)); + stream_update_encoder(a->strm, pt_tx); + + if (!tx->ausrc) { + err |= audio_start(a); + } + + return err; +} + + +int audio_decoder_set(struct audio *a, const struct aucodec *ac, + int pt_rx, const char *params) +{ + struct aurx *rx; + bool reset = false; + int err = 0; + + if (!a || !ac) + return EINVAL; + + rx = &a->rx; + + reset = !aucodec_equal(ac, rx->ac); + + if (ac != rx->ac) { + + info("audio: Set audio decoder: %s %uHz %dch\n", + ac->name, get_srate(ac), ac->ch); + + rx->pt = pt_rx; + rx->ac = ac; + rx->dec = mem_deref(rx->dec); + } + + if (ac->decupdh) { + err = ac->decupdh(&rx->dec, ac, params); + if (err) { + warning("audio: alloc decoder: %m\n", err); + return err; + } + } + + stream_set_srate(a->strm, get_srate(ac), get_srate(ac)); + + if (reset) { + + rx->auplay = mem_deref(rx->auplay); + + /* Reset audio filter chain */ + list_flush(&rx->filtl); + + err |= audio_start(a); + } + + return err; +} + + +/** + * Use the next audio encoder in the local list of negotiated codecs + * + * @param audio Audio object + */ +void audio_encoder_cycle(struct audio *audio) +{ + const struct sdp_format *rc = NULL; + + if (!audio) + return; + + rc = sdp_media_format_cycle(stream_sdpmedia(audio_strm(audio))); + if (!rc) { + info("audio: encoder cycle: no remote codec found\n"); + return; + } + + (void)audio_encoder_set(audio, rc->data, rc->pt, rc->params); +} + + +struct stream *audio_strm(const struct audio *a) +{ + return a ? a->strm : NULL; +} + + +int audio_send_digit(struct audio *a, char key) +{ + int err = 0; + + if (!a) + return EINVAL; + + if (key > 0) { + info("audio: send DTMF digit: '%c'\n", key); + err = telev_send(a->telev, telev_digit2code(key), false); + } + else if (a->tx.cur_key) { + /* Key release */ + info("audio: send DTMF digit end: '%c'\n", a->tx.cur_key); + err = telev_send(a->telev, + telev_digit2code(a->tx.cur_key), true); + } + + a->tx.cur_key = key; + + return err; +} + + +/** + * Mute the audio stream + * + * @param a Audio stream + * @param muted True to mute, false to un-mute + */ +void audio_mute(struct audio *a, bool muted) +{ + if (!a) + return; + + a->tx.muted = muted; +} + + +void audio_sdp_attr_decode(struct audio *a) +{ + const char *attr; + + if (!a) + return; + + /* This is probably only meaningful for audio data, but + may be used with other media types if it makes sense. */ + attr = sdp_media_rattr(stream_sdpmedia(a->strm), "ptime"); + if (attr) { + struct autx *tx = &a->tx; + uint32_t ptime_tx = atoi(attr); + + if (ptime_tx && ptime_tx != a->tx.ptime) { + + info("audio: peer changed ptime_tx %u -> %u\n", + a->tx.ptime, ptime_tx); + + tx->ptime = ptime_tx; + + if (tx->ac) { + tx->psize = 2 * get_framesize(tx->ac, + ptime_tx); + } + } + } +} + + +static int aucodec_print(struct re_printf *pf, const struct aucodec *ac) +{ + if (!ac) + return 0; + + return re_hprintf(pf, "%s %uHz/%dch", ac->name, get_srate(ac), ac->ch); +} + + +int audio_debug(struct re_printf *pf, const struct audio *a) +{ + const struct autx *tx; + const struct aurx *rx; + int err; + + if (!a) + return 0; + + tx = &a->tx; + rx = &a->rx; + + err = re_hprintf(pf, "\n--- Audio stream ---\n"); + + err |= re_hprintf(pf, " tx: %H %H ptime=%ums\n", + aucodec_print, tx->ac, + aubuf_debug, tx->aubuf, + tx->ptime); + + err |= re_hprintf(pf, " rx: %H %H ptime=%ums pt=%d pt_tel=%d\n", + aucodec_print, rx->ac, + aubuf_debug, rx->aubuf, + rx->ptime, rx->pt, rx->pt_tel); + + err |= re_hprintf(pf, + " %H" + " %H", + autx_print_pipeline, tx, + aurx_print_pipeline, rx); + + err |= stream_debug(pf, a->strm); + + return err; +} + + +void audio_set_devicename(struct audio *a, const char *src, const char *play) +{ + if (!a) + return; + + str_ncpy(a->tx.device, src, sizeof(a->tx.device)); + str_ncpy(a->rx.device, play, sizeof(a->rx.device)); +} diff --git a/src/aufilt.c b/src/aufilt.c new file mode 100644 index 0000000..c749aaf --- /dev/null +++ b/src/aufilt.c @@ -0,0 +1,37 @@ +/** + * @file aufilt.c Audio Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list afl; + + +void aufilt_register(struct aufilt *af) +{ + if (!af) + return; + + list_append(&afl, &af->le, af); + + info("aufilt: %s\n", af->name); +} + + +void aufilt_unregister(struct aufilt *af) +{ + if (!af) + return; + + list_unlink(&af->le); +} + + +struct list *aufilt_list(void) +{ + return &afl; +} diff --git a/src/auplay.c b/src/auplay.c new file mode 100644 index 0000000..a1247b6 --- /dev/null +++ b/src/auplay.c @@ -0,0 +1,108 @@ +/** + * @file auplay.c Audio Player + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list auplayl = LIST_INIT; + + +static void destructor(void *arg) +{ + struct auplay *ap = arg; + + list_unlink(&ap->le); +} + + +/** + * Register an Audio Player + * + * @param app Pointer to allocated Audio Player object + * @param name Audio Player name + * @param alloch Allocation handler + * + * @return 0 if success, otherwise errorcode + */ +int auplay_register(struct auplay **app, const char *name, + auplay_alloc_h *alloch) +{ + struct auplay *ap; + + if (!app) + return EINVAL; + + ap = mem_zalloc(sizeof(*ap), destructor); + if (!ap) + return ENOMEM; + + list_append(&auplayl, &ap->le, ap); + + ap->name = name; + ap->alloch = alloch; + + info("auplay: %s\n", name); + + *app = ap; + + return 0; +} + + +/** + * Find an Audio Player by name + * + * @param name Name of the Audio Player to find + * + * @return Matching Audio Player if found, otherwise NULL + */ +const struct auplay *auplay_find(const char *name) +{ + struct le *le; + + for (le=auplayl.head; le; le=le->next) { + + struct auplay *ap = le->data; + + if (str_isset(name) && 0 != str_casecmp(name, ap->name)) + continue; + + return ap; + } + + return NULL; +} + + +/** + * Allocate an Audio Player state + * + * @param stp Pointer to allocated Audio Player state + * @param name Name of Audio Player + * @param prm Audio Player parameters + * @param device Name of Audio Player device (driver specific) + * @param wh Write handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int auplay_alloc(struct auplay_st **stp, const char *name, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay *ap; + + ap = (struct auplay *)auplay_find(name); + if (!ap) + return ENOENT; + + if (!prm->srate || !prm->ch) + return EINVAL; + + return ap->alloch(stp, ap, prm, device, wh, arg); +} diff --git a/src/ausrc.c b/src/ausrc.c new file mode 100644 index 0000000..9782db3 --- /dev/null +++ b/src/ausrc.c @@ -0,0 +1,106 @@ +/** + * @file ausrc.c Audio Source + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list ausrcl = LIST_INIT; + + +static void destructor(void *arg) +{ + struct ausrc *as = arg; + + list_unlink(&as->le); +} + + +/** + * Register an Audio Source + * + * @param asp Pointer to allocated Audio Source object + * @param name Audio Source name + * @param alloch Allocation handler + * + * @return 0 if success, otherwise errorcode + */ +int ausrc_register(struct ausrc **asp, const char *name, ausrc_alloc_h *alloch) +{ + struct ausrc *as; + + if (!asp) + return EINVAL; + + as = mem_zalloc(sizeof(*as), destructor); + if (!as) + return ENOMEM; + + list_append(&ausrcl, &as->le, as); + + as->name = name; + as->alloch = alloch; + + info("ausrc: %s\n", name); + + *asp = as; + + return 0; +} + + +/** + * Find an Audio Source by name + * + * @param name Name of the Audio Source to find + * + * @return Matching Audio Source if found, otherwise NULL + */ +const struct ausrc *ausrc_find(const char *name) +{ + struct le *le; + + for (le=ausrcl.head; le; le=le->next) { + + struct ausrc *as = le->data; + + if (str_isset(name) && 0 != str_casecmp(name, as->name)) + continue; + + return as; + } + + return NULL; +} + + +/** + * Allocate an Audio Source state + * + * @param stp Pointer to allocated Audio Source state + * @param ctx Media context (optional) + * @param name Name of Audio Source + * @param prm Audio Source parameters + * @param device Name of Audio Source device (driver specific) + * @param rh Read handler + * @param errh Error handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int ausrc_alloc(struct ausrc_st **stp, struct media_ctx **ctx, + const char *name, struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc *as; + + as = (struct ausrc *)ausrc_find(name); + if (!as) + return ENOENT; + + return as->alloch(stp, as, ctx, prm, device, rh, errh, arg); +} diff --git a/src/bfcp.c b/src/bfcp.c new file mode 100644 index 0000000..5b69142 --- /dev/null +++ b/src/bfcp.c @@ -0,0 +1,199 @@ +/** + * @file bfcp.c BFCP client + * + * Copyright (C) 2011 Creytiv.com + */ +#include <stdlib.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +struct bfcp { + struct bfcp_conn *conn; + struct sdp_media *sdpm; + struct mnat_media *mnat_st; + bool active; + + /* server */ + uint32_t lconfid; + uint16_t luserid; +}; + + +static void destructor(void *arg) +{ + struct bfcp *bfcp = arg; + + mem_deref(bfcp->mnat_st); + mem_deref(bfcp->sdpm); + mem_deref(bfcp->conn); +} + + +static const char *bfcp_sdp_transp(enum bfcp_transp tp) +{ + switch (tp) { + + case BFCP_UDP: return "UDP/BFCP"; + case BFCP_DTLS: return "UDP/TLS/BFCP"; + default: return NULL; + } +} + + +static enum bfcp_transp str2tp(const char *proto) +{ + if (0 == str_casecmp(proto, "udp")) + return BFCP_UDP; + else if (0 == str_casecmp(proto, "dtls")) + return BFCP_DTLS; + else { + warning("unsupported BFCP protocol: %s\n", proto); + return -1; + } +} + + +static void bfcp_resp_handler(int err, const struct bfcp_msg *msg, void *arg) +{ + struct bfcp *bfcp = arg; + (void)bfcp; + + if (err) { + warning("bfcp: error response: %m\n", err); + return; + } + + info("bfcp: received BFCP response: '%s'\n", + bfcp_prim_name(msg->prim)); +} + + +static void bfcp_msg_handler(const struct bfcp_msg *msg, void *arg) +{ + struct bfcp *bfcp = arg; + + info("bfcp: received BFCP message '%s'\n", bfcp_prim_name(msg->prim)); + + switch (msg->prim) { + + case BFCP_HELLO: + (void)bfcp_reply(bfcp->conn, msg, BFCP_HELLO_ACK, 0); + break; + + default: + (void)bfcp_ereply(bfcp->conn, msg, BFCP_UNKNOWN_PRIM); + break; + } +} + + +int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess, + const char *proto, bool offerer, + const struct mnat *mnat, struct mnat_sess *mnat_sess) +{ + struct bfcp *bfcp; + struct sa laddr; + enum bfcp_transp transp; + int err; + + if (!bfcpp || !sdp_sess) + return EINVAL; + + transp = str2tp(proto); + + bfcp = mem_zalloc(sizeof(*bfcp), destructor); + if (!bfcp) + return ENOMEM; + + bfcp->active = offerer; + + sa_init(&laddr, AF_INET); + + err = bfcp_listen(&bfcp->conn, transp, &laddr, uag_tls(), + bfcp_msg_handler, bfcp); + if (err) + goto out; + + err = sdp_media_add(&bfcp->sdpm, sdp_sess, "application", + sa_port(&laddr), bfcp_sdp_transp(transp)); + if (err) + goto out; + + err = sdp_format_add(NULL, bfcp->sdpm, false, "*", NULL, + 0, 0, NULL, NULL, NULL, false, NULL); + if (err) + goto out; + + err |= sdp_media_set_lattr(bfcp->sdpm, true, "floorctrl", "c-s"); + err |= sdp_media_set_lattr(bfcp->sdpm, true, "setup", + bfcp->active ? "active" : "actpass"); + + if (bfcp->active) { + err |= sdp_media_set_lattr(bfcp->sdpm, true, + "connection", "new"); + } + else { + bfcp->lconfid = 1000 + (rand_u16() & 0xf); + bfcp->luserid = 1 + (rand_u16() & 0x7); + + err |= sdp_media_set_lattr(bfcp->sdpm, true, "confid", + "%u", bfcp->lconfid); + err |= sdp_media_set_lattr(bfcp->sdpm, true, "userid", + "%u", bfcp->luserid); + } + + if (err) + goto out; + + if (mnat) { + info("bfcp: enabled medianat '%s' on UDP socket\n", mnat->id); + + err = mnat->mediah(&bfcp->mnat_st, mnat_sess, IPPROTO_UDP, + bfcp_sock(bfcp->conn), NULL, bfcp->sdpm); + if (err) + goto out; + } + + info("bfcp: %s BFCP agent protocol '%s' on port %d\n", + bfcp->active ? "Active" : "Passive", + proto, sa_port(&laddr)); + + out: + if (err) + mem_deref(bfcp); + else + *bfcpp = bfcp; + + return err; +} + + +int bfcp_start(struct bfcp *bfcp) +{ + const struct sa *paddr; + uint32_t confid = 0; + uint16_t userid = 0; + int err = 0; + + if (!bfcp) + return EINVAL; + + if (!sdp_media_rport(bfcp->sdpm)) { + info("bfcp channel is disabled\n"); + return 0; + } + + if (bfcp->active) { + + paddr = sdp_media_raddr(bfcp->sdpm); + confid = sdp_media_rattr_u32(bfcp->sdpm, "confid"); + userid = sdp_media_rattr_u32(bfcp->sdpm, "userid"); + + err = bfcp_request(bfcp->conn, paddr, BFCP_VER2, BFCP_HELLO, + confid, userid, bfcp_resp_handler, bfcp, 0); + } + + return err; +} diff --git a/src/call.c b/src/call.c new file mode 100644 index 0000000..03f0577 --- /dev/null +++ b/src/call.c @@ -0,0 +1,1567 @@ +/** + * @file call.c Call Control + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +#define DEBUG_MODULE "call" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + +/** Magic number */ +#define MAGIC 0xca11ca11 +#include "magic.h" + + +#ifndef RELEASE +#define CALL_DEBUG 1 /**< Enable call debugging */ +#endif + +#define FOREACH_STREAM \ + for (le = call->streaml.head; le; le = le->next) + +/** Call constants */ +enum { + PTIME = 20, /**< Packet time for audio */ + LOCAL_TIMEOUT = 120, /**< Incoming call timeout in [seconds] */ +}; + + +/** Call States */ +enum state { + STATE_IDLE = 0, + STATE_INCOMING, + STATE_OUTGOING, + STATE_RINGING, + STATE_EARLY, + STATE_ESTABLISHED, + STATE_TERMINATED +}; + +/** SIP Call Control object */ +struct call { + MAGIC_DECL /**< Magic number for debugging */ + struct le le; /**< Linked list element */ + struct ua *ua; /**< SIP User-agent */ + struct account *acc; /**< Account (ref.) */ + struct sipsess *sess; /**< SIP Session */ + struct sdp_session *sdp; /**< SDP Session */ + struct sipsub *sub; /**< Call transfer REFER subscription */ + struct sipnot *not; /**< REFER/NOTIFY client */ + struct list streaml; /**< List of mediastreams (struct stream) */ + struct audio *audio; /**< Audio stream */ +#ifdef USE_VIDEO + struct video *video; /**< Video stream */ + struct bfcp *bfcp; /**< BFCP Client */ +#endif + enum state state; /**< Call state */ + char *local_uri; /**< Local SIP uri */ + char *local_name; /**< Local display name */ + char *peer_uri; /**< Peer SIP Address */ + char *peer_name; /**< Peer display name */ + struct tmr tmr_inv; /**< Timer for incoming calls */ + struct tmr tmr_dtmf; /**< Timer for incoming DTMF events */ + time_t time_start; /**< Time when call started */ + time_t time_stop; /**< Time when call stopped */ + bool got_offer; /**< Got SDP Offer from Peer */ + struct mnat_sess *mnats; /**< Media NAT session */ + bool mnat_wait; /**< Waiting for MNAT to establish */ + struct menc_sess *mencs; /**< Media encryption session state */ + int af; /**< Preferred Address Family */ + uint16_t scode; /**< Termination status code */ + call_event_h *eh; /**< Event handler */ + call_dtmf_h *dtmfh; /**< DTMF handler */ + void *arg; /**< Handler argument */ +}; + + +static int send_invite(struct call *call); + + +static const char *state_name(enum state st) +{ + switch (st) { + + case STATE_IDLE: return "IDLE"; + case STATE_INCOMING: return "INCOMING"; + case STATE_OUTGOING: return "OUTGOING"; + case STATE_RINGING: return "RINGING"; + case STATE_EARLY: return "EARLY"; + case STATE_ESTABLISHED: return "ESTABLISHED"; + case STATE_TERMINATED: return "TERMINATED"; + default: return "???"; + } +} + + +static void set_state(struct call *call, enum state st) +{ + call->state = st; +} + + +static void call_stream_start(struct call *call, bool active) +{ + const struct sdp_format *sc; + int err; + + /* Audio Stream */ + sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL); + if (sc) { + struct aucodec *ac = sc->data; + + if (ac) { + err = audio_encoder_set(call->audio, sc->data, + sc->pt, sc->params); + err |= audio_decoder_set(call->audio, sc->data, + sc->pt, sc->params); + if (!err) { + err = audio_start(call->audio); + } + if (err) { + DEBUG_WARNING("audio stream: %m\n", err); + } + } + else { + info("call: no common audio-codecs..\n"); + } + } + else { + info("call: audio stream is disabled..\n"); + } + +#ifdef USE_VIDEO + /* Video Stream */ + sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL); + if (sc) { + err = video_encoder_set(call->video, sc->data, sc->pt, + sc->params); + err |= video_decoder_set(call->video, sc->data, sc->pt, + sc->rparams); + if (!err) { + err = video_start(call->video, call->peer_uri); + } + if (err) { + DEBUG_WARNING("video stream: %m\n", err); + } + } + else if (call->video) { + info("call: video stream is disabled..\n"); + } + + if (call->bfcp) { + err = bfcp_start(call->bfcp); + if (err) { + DEBUG_WARNING("bfcp_start() error: %m\n", err); + } + } +#endif + + if (active) { + struct le *le; + + tmr_cancel(&call->tmr_inv); + call->time_start = time(NULL); + + FOREACH_STREAM { + stream_reset(le->data); + } + } +} + + +static void call_stream_stop(struct call *call) +{ + if (!call) + return; + + call->time_stop = time(NULL); + + /* Audio */ + audio_stop(call->audio); + + /* Video */ +#ifdef USE_VIDEO + video_stop(call->video); +#endif + + tmr_cancel(&call->tmr_inv); +} + + +static void call_event_handler(struct call *call, enum call_event ev, + const char *fmt, ...) +{ + call_event_h *eh = call->eh; + void *eh_arg = call->arg; + char buf[256]; + va_list ap; + + if (!eh) + return; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + eh(call, ev, buf, eh_arg); +} + + +static void invite_timeout(void *arg) +{ + struct call *call = arg; + + info("%s: Local timeout after %u seconds\n", + call->peer_uri, LOCAL_TIMEOUT); + + call_event_handler(call, CALL_EVENT_CLOSED, "Local timeout"); +} + + +/** Called when all media streams are established */ +static void mnat_handler(int err, uint16_t scode, const char *reason, + void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + if (err) { + DEBUG_WARNING("medianat '%s' failed: %m\n", + call->acc->mnatid, err); + call_event_handler(call, CALL_EVENT_CLOSED, "%m", err); + return; + } + else if (scode) { + DEBUG_WARNING("medianat failed: %u %s\n", scode, reason); + call_event_handler(call, CALL_EVENT_CLOSED, "%u %s", + scode, reason); + return; + } + + /* Re-INVITE */ + if (!call->mnat_wait) { + info("call: medianat established -- sending Re-INVITE\n"); + (void)call_modify(call); + return; + } + + call->mnat_wait = false; + + switch (call->state) { + + case STATE_OUTGOING: + (void)send_invite(call); + break; + + case STATE_INCOMING: + call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri); + break; + + default: + break; + } +} + + +static int update_media(struct call *call) +{ + const struct sdp_format *sc; + struct le *le; + int err = 0; + + /* media attributes */ + audio_sdp_attr_decode(call->audio); + +#ifdef USE_VIDEO + if (call->video) + video_sdp_attr_decode(call->video); +#endif + + /* Update each stream */ + FOREACH_STREAM { + stream_update(le->data); + } + + if (call->acc->mnat && call->acc->mnat->updateh && call->mnats) + err = call->acc->mnat->updateh(call->mnats); + + sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL); + if (sc) { + struct aucodec *ac = sc->data; + if (ac) { + err = audio_decoder_set(call->audio, sc->data, + sc->pt, sc->params); + err |= audio_encoder_set(call->audio, sc->data, + sc->pt, sc->params); + } + else { + info("no common audio-codecs..\n"); + } + } + else { + info("audio stream is disabled..\n"); + } + +#ifdef USE_VIDEO + sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL); + if (sc) { + err = video_encoder_set(call->video, sc->data, + sc->pt, sc->params); + if (err) { + DEBUG_WARNING("video stream: %m\n", err); + } + } + else if (call->video) { + info("video stream is disabled..\n"); + } +#endif + + return err; +} + + +static void print_summary(const struct call *call) +{ + uint32_t dur = call_duration(call); + if (!dur) + return; + + info("%s: Call with %s terminated (duration: %H)\n", + call->local_uri, call->peer_uri, fmt_human_time, &dur); +} + + +static void call_destructor(void *arg) +{ + struct call *call = arg; + + if (call->state != STATE_IDLE) + print_summary(call); + + call_stream_stop(call); + list_unlink(&call->le); + tmr_cancel(&call->tmr_dtmf); + + mem_deref(call->sess); + mem_deref(call->local_uri); + mem_deref(call->local_name); + mem_deref(call->peer_uri); + mem_deref(call->peer_name); + mem_deref(call->audio); +#ifdef USE_VIDEO + mem_deref(call->video); + mem_deref(call->bfcp); +#endif + mem_deref(call->sdp); + mem_deref(call->mnats); + mem_deref(call->mencs); + mem_deref(call->sub); + mem_deref(call->not); + mem_deref(call->acc); +} + + +static void audio_event_handler(int key, bool end, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + info("received event: '%c' (end=%d)\n", key, end); + + if (call->dtmfh) + call->dtmfh(call, end ? 0x00 : key, call->arg); +} + + +static void audio_error_handler(int err, const char *str, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + if (err) { + DEBUG_WARNING("Audio error: %m (%s)\n", err, str); + } + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, str); +} + + +static void menc_error_handler(int err, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + DEBUG_WARNING("mediaenc error: %m\n", err); + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed"); +} + + +/** + * Allocate a new Call state object + * + * @param callp Pointer to allocated Call state object + * @param cfg Global configuration + * @param lst List of call objects + * @param local_name Local display name (optional) + * @param local_uri Local SIP uri + * @param acc Account parameters + * @param ua User-Agent + * @param prm Call parameters + * @param msg SIP message for incoming calls + * @param xcall Optional call to inherit properties from + * @param eh Call event handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int call_alloc(struct call **callp, const struct config *cfg, struct list *lst, + const char *local_name, const char *local_uri, + struct account *acc, struct ua *ua, const struct call_prm *prm, + const struct sip_msg *msg, struct call *xcall, + call_event_h *eh, void *arg) +{ + struct call *call; + enum vidmode vidmode = prm ? prm->vidmode : VIDMODE_OFF; + bool use_video = true, got_offer = false; + int label = 0; + int err = 0; + + if (!cfg || !local_uri || !acc || !ua) + return EINVAL; + + call = mem_zalloc(sizeof(*call), call_destructor); + if (!call) + return ENOMEM; + + MAGIC_INIT(call); + + tmr_init(&call->tmr_inv); + + call->acc = mem_ref(acc); + call->ua = ua; + call->state = STATE_IDLE; + call->eh = eh; + call->arg = arg; + call->af = prm ? prm->af : AF_INET; + + err = str_dup(&call->local_uri, local_uri); + if (local_name) + err |= str_dup(&call->local_name, local_name); + if (err) + goto out; + + /* Init SDP info */ + err = sdp_session_alloc(&call->sdp, net_laddr_af(call->af)); + if (err) + goto out; + + err = sdp_session_set_lattr(call->sdp, true, + "tool", "baresip " BARESIP_VERSION); + if (err) + goto out; + + /* Check for incoming SDP Offer */ + if (msg && mbuf_get_left(msg->mb)) + got_offer = true; + + /* Initialise media NAT handling */ + if (acc->mnat) { + err = acc->mnat->sessh(&call->mnats, net_dnsc(), call->af, + acc->stun_host, acc->stun_port, + acc->stun_user, acc->stun_pass, + call->sdp, !got_offer, + mnat_handler, call); + if (err) { + DEBUG_WARNING("mnat session: %m\n", err); + goto out; + } + } + call->mnat_wait = true; + + /* Media encryption */ + if (acc->menc) { + if (acc->menc->sessh) { + err = acc->menc->sessh(&call->mencs, call->sdp, + !got_offer, + menc_error_handler, call); + if (err) { + DEBUG_WARNING("mediaenc session: %m\n", err); + goto out; + } + } + } + + /* Audio stream */ + err = audio_alloc(&call->audio, cfg, call, + call->sdp, ++label, + acc->mnat, call->mnats, acc->menc, call->mencs, + acc->ptime, account_aucodecl(call->acc), + audio_event_handler, audio_error_handler, call); + if (err) + goto out; + +#ifdef USE_VIDEO + /* We require at least one video codec, and at least one + video source or video display */ + use_video = (vidmode != VIDMODE_OFF) + && (list_head(account_vidcodecl(call->acc)) != NULL) + && (NULL != vidsrc_find(NULL) || NULL != vidisp_find(NULL)); + + /* Video stream */ + if (use_video) { + err = video_alloc(&call->video, cfg, + call, call->sdp, ++label, + acc->mnat, call->mnats, + acc->menc, call->mencs, + "main", + account_vidcodecl(call->acc)); + if (err) + goto out; + } + + if (str_isset(cfg->bfcp.proto)) { + + err = bfcp_alloc(&call->bfcp, call->sdp, + cfg->bfcp.proto, !got_offer, + acc->mnat, call->mnats); + if (err) + goto out; + } +#else + (void)use_video; + (void)vidmode; +#endif + + /* inherit certain properties from original call */ + if (xcall) { + call->not = mem_ref(xcall->not); + } + + list_append(lst, &call->le, call); + + out: + if (err) + mem_deref(call); + else if (callp) + *callp = call; + + return err; +} + + +int call_connect(struct call *call, const struct pl *paddr) +{ + struct sip_addr addr; + int err; + + if (!call || !paddr) + return EINVAL; + + info("call: connecting to '%r'..\n", paddr); + + /* if the peer-address is a full SIP address then we need + * to parse it and extract the SIP uri part. + */ + if (0 == sip_addr_decode(&addr, paddr) && addr.dname.p) { + err = pl_strdup(&call->peer_uri, &addr.auri); + } + else { + err = pl_strdup(&call->peer_uri, paddr); + } + if (err) + return err; + + set_state(call, STATE_OUTGOING); + + /* If we are using asyncronous medianat like STUN/TURN, then + * wait until completed before sending the INVITE */ + if (!call->acc->mnat) + err = send_invite(call); + + return err; +} + + +/** + * Update the current call by sending Re-INVITE or UPDATE + * + * @param call Call object + * + * @return 0 if success, otherwise errorcode + */ +int call_modify(struct call *call) +{ + struct mbuf *desc; + int err; + + if (!call) + return EINVAL; + + err = call_sdp_get(call, &desc, true); + if (!err) + err = sipsess_modify(call->sess, desc); + + mem_deref(desc); + + return err; +} + + +int call_hangup(struct call *call, uint16_t scode, const char *reason) +{ + int err = 0; + + if (!call) + return EINVAL; + + switch (call->state) { + + case STATE_INCOMING: + if (scode < 400) { + scode = 486; + reason = "Rejected"; + } + info("call: rejecting incoming call from %s (%u %s)\n", + call->peer_uri, scode, reason); + (void)sipsess_reject(call->sess, scode, reason, NULL); + break; + + default: + info("call: terminate call with %s\n", call->peer_uri); + call->sess = mem_deref(call->sess); + break; + } + + set_state(call, STATE_TERMINATED); + + call_stream_stop(call); + + return err; +} + + +int call_progress(struct call *call) +{ + struct mbuf *desc; + int err; + + if (!call) + return EINVAL; + + err = call_sdp_get(call, &desc, false); + if (err) + return err; + + err = sipsess_progress(call->sess, 183, "Session Progress", + desc, "Allow: %s\r\n", uag_allowed_methods()); + + if (!err) + call_stream_start(call, false); + + mem_deref(desc); + + return 0; +} + + +int call_answer(struct call *call, uint16_t scode) +{ + struct mbuf *desc; + int err; + + if (!call || !call->sess) + return EINVAL; + + if (STATE_INCOMING != call->state) { + return 0; + } + + info("answering call from %s with %u\n", call->peer_uri, scode); + + if (call->got_offer) { + + err = update_media(call); + if (err) + return err; + } + + err = sdp_encode(&desc, call->sdp, !call->got_offer); + if (err) + return err; + + err = sipsess_answer(call->sess, scode, "Answering", desc, + "Allow: %s\r\n", uag_allowed_methods()); + + mem_deref(desc); + + return err; +} + + +/** + * Check if the current call has an active audio stream + * + * @param call Call object + * + * @return True if active stream, otherwise false + */ +bool call_has_audio(const struct call *call) +{ + if (!call) + return false; + + return sdp_media_has_media(stream_sdpmedia(audio_strm(call->audio))); +} + + +/** + * Check if the current call has an active video stream + * + * @param call Call object + * + * @return True if active stream, otherwise false + */ +bool call_has_video(const struct call *call) +{ + if (!call) + return false; + +#ifdef USE_VIDEO + return sdp_media_has_media(stream_sdpmedia(video_strm(call->video))); +#else + return false; +#endif +} + + +/** + * Put the current call on hold/resume + * + * @param call Call object + * @param hold True to hold, false to resume + * + * @return 0 if success, otherwise errorcode + */ +int call_hold(struct call *call, bool hold) +{ + struct le *le; + + if (!call || !call->sess) + return EINVAL; + + info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri); + + FOREACH_STREAM + stream_hold(le->data, hold); + + return call_modify(call); +} + + +int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer) +{ + return sdp_encode(descp, call->sdp, offer); +} + + +const char *call_peeruri(const struct call *call) +{ + return call ? call->peer_uri : NULL; +} + + +const char *call_localuri(const struct call *call) +{ + return call ? call->local_uri : NULL; +} + + +/** + * Get the name of the peer + * + * @param call Call object + * + * @return Peer name + */ +const char *call_peername(const struct call *call) +{ + return call ? call->peer_name : NULL; +} + + +int call_debug(struct re_printf *pf, const struct call *call) +{ + int err; + + if (!call) + return 0; + + err = re_hprintf(pf, "===== Call debug (%s) =====\n", + state_name(call->state)); + + /* SIP Session debug */ + err |= re_hprintf(pf, + " local_uri: %s <%s>\n" + " peer_uri: %s <%s>\n" + " af=%s\n", + call->local_name, call->local_uri, + call->peer_name, call->peer_uri, + net_af2name(call->af)); + + /* SDP debug */ + err |= sdp_session_debug(pf, call->sdp); + + return err; +} + + +static int print_duration(struct re_printf *pf, const struct call *call) +{ + const uint32_t dur = call_duration(call); + const uint32_t sec = dur%60%60; + const uint32_t min = dur/60%60; + const uint32_t hrs = dur/60/60; + + return re_hprintf(pf, "%u:%02u:%02u", hrs, min, sec); +} + + +int call_status(struct re_printf *pf, const struct call *call) +{ + struct le *le; + int err; + + if (!call) + return EINVAL; + + switch (call->state) { + + case STATE_EARLY: + case STATE_ESTABLISHED: + break; + default: + return 0; + } + + err = re_hprintf(pf, "\r[%H]", print_duration, call); + + FOREACH_STREAM + err |= stream_print(pf, le->data); + + err |= re_hprintf(pf, " (bit/s)"); + +#ifdef USE_VIDEO + if (call->video) + err |= video_print(pf, call->video); +#endif + + return err; +} + + +int call_jbuf_stat(struct re_printf *pf, const struct call *call) +{ + struct le *le; + int err = 0; + + if (!call) + return EINVAL; + + FOREACH_STREAM + err |= stream_jbuf_stat(pf, le->data); + + return err; +} + + +int call_info(struct re_printf *pf, const struct call *call) +{ + if (!call) + return 0; + + return re_hprintf(pf, "%H %8s %s", print_duration, call, + state_name(call->state), call->peer_uri); +} + + +/** + * Send a DTMF digit to the peer + * + * @param call Call object + * @param key DTMF digit to send (0x00 for key release) + * + * @return 0 if success, otherwise errorcode + */ +int call_send_digit(struct call *call, char key) +{ + if (!call) + return EINVAL; + + return audio_send_digit(call->audio, key); +} + + +struct ua *call_get_ua(const struct call *call) +{ + return call ? call->ua : NULL; +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + struct account *acc = arg; + return account_auth(acc, username, password, realm); +} + + +static int sipsess_offer_handler(struct mbuf **descp, + const struct sip_msg *msg, void *arg) +{ + const bool got_offer = mbuf_get_left(msg->mb); + struct call *call = arg; + int err; + + MAGIC_CHECK(call); + + info("call: got re-INVITE%s\n", got_offer ? " (SDP Offer)" : ""); + + if (got_offer) { + + /* Decode SDP Offer */ + err = sdp_decode(call->sdp, msg->mb, true); + if (err) + return err; + + err = update_media(call); + if (err) + return err; + } + + /* Encode SDP Answer */ + return sdp_encode(descp, call->sdp, !got_offer); +} + + +static int sipsess_answer_handler(const struct sip_msg *msg, void *arg) +{ + struct call *call = arg; + int err; + + MAGIC_CHECK(call); + + (void)sdp_decode_multipart(&msg->ctype, msg->mb); + + err = sdp_decode(call->sdp, msg->mb, false); + if (err) { + DEBUG_WARNING("answer: sdp_decode: %m\n", err); + return err; + } + + err = update_media(call); + if (err) + return err; + + return 0; +} + + +static void sipsess_estab_handler(const struct sip_msg *msg, void *arg) +{ + struct call *call = arg; + + MAGIC_CHECK(call); + + (void)msg; + + if (call->state == STATE_ESTABLISHED) + return; + + set_state(call, STATE_ESTABLISHED); + + call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri); + + call_stream_start(call, true); + + /* the transferor will hangup this call */ + if (call->not) { + (void)call_notify_sipfrag(call, 200, "OK"); + } +} + + +#ifdef USE_VIDEO +static void call_handle_info_req(struct call *call, const struct sip_msg *req) +{ + struct pl body; + bool pfu; + int err; + + (void)call; + + pl_set_mbuf(&body, req->mb); + + err = mctrl_handle_media_control(&body, &pfu); + if (err) + return; + + if (pfu) { + video_update_picture(call->video); + } +} +#endif + + +static void dtmfend_handler(void *arg) +{ + struct call *call = arg; + + if (call->dtmfh) + call->dtmfh(call, 0x00, call->arg); +} + + +static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + + if (!pl_strcasecmp(&msg->ctype, "application/dtmf-relay")) { + + struct pl body, sig, dur; + int err; + + pl_set_mbuf(&body, msg->mb); + + err = re_regex(body.p, body.l, "Signal=[0-9]+", &sig); + err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur); + + if (err) { + (void)sip_reply(sip, msg, 400, "Bad Request"); + } + else { + char s = pl_u32(&sig); + uint32_t duration = pl_u32(&dur); + + if (s == 10) s = '*'; + else if (s == 11) s = '#'; + else s += '0'; + + info("received DTMF: '%c' (duration=%r)\n", s, &dur); + + (void)sip_reply(sip, msg, 200, "OK"); + + if (call->dtmfh) { + tmr_start(&call->tmr_dtmf, duration, + dtmfend_handler, call); + call->dtmfh(call, s, call->arg); + } + } + } +#ifdef USE_VIDEO + else if (!pl_strcasecmp(&msg->ctype, + "application/media_control+xml")) { + call_handle_info_req(call, msg); + (void)sip_reply(sip, msg, 200, "OK"); + } +#endif + else { + (void)sip_reply(sip, msg, 488, "Not Acceptable Here"); + } +} + + +static void sipnot_close_handler(int err, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + + if (err) + info("call: notification closed: %m\n", err); + else if (msg) + info("call: notification closed: %u %r\n", + msg->scode, &msg->reason); + + call->not = mem_deref(call->not); +} + + +static void sipsess_refer_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + const struct sip_hdr *hdr; + int err; + + /* get the transfer target */ + hdr = sip_msg_hdr(msg, SIP_HDR_REFER_TO); + if (!hdr) { + DEBUG_WARNING("bad REFER request from %r\n", &msg->from.auri); + (void)sip_reply(sip, msg, 400, "Missing Refer-To header"); + return; + } + + /* The REFER creates an implicit subscription. + * Reply 202 to the REFER request + */ + call->not = mem_deref(call->not); + err = sipevent_accept(&call->not, uag_sipevent_sock(), msg, + sipsess_dialog(call->sess), NULL, + 202, "Accepted", 60, 60, 60, + ua_cuser(call->ua), "message/sipfrag", + auth_handler, call->acc, true, + sipnot_close_handler, call, + "Allow: %s\r\n", uag_allowed_methods()); + if (err) { + DEBUG_WARNING("refer: sipevent_accept failed: %m\n", err); + return; + } + + (void)call_notify_sipfrag(call, 100, "Trying"); + + call_event_handler(call, CALL_EVENT_TRANSFER, "%r", &hdr->val); +} + + +static void sipsess_close_handler(int err, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + char reason[128] = ""; + + MAGIC_CHECK(call); + + if (err) { + info("%s: session closed: %m\n", call->peer_uri, err); + + if (call->not) { + (void)call_notify_sipfrag(call, 500, "%m", err); + } + } + else if (msg) { + + call->scode = msg->scode; + + (void)re_snprintf(reason, sizeof(reason), "%u %r", + msg->scode, &msg->reason); + + info("%s: session closed: %u %r\n", + call->peer_uri, msg->scode, &msg->reason); + + if (call->not) { + (void)call_notify_sipfrag(call, msg->scode, + "%r", &msg->reason); + } + } + else { + info("%s: session closed\n", call->peer_uri); + } + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, reason); +} + + +int call_accept(struct call *call, struct sipsess_sock *sess_sock, + const struct sip_msg *msg) +{ + bool got_offer; + int err; + + if (!call || !msg) + return EINVAL; + + got_offer = (mbuf_get_left(msg->mb) > 0); + + err = pl_strdup(&call->peer_uri, &msg->from.auri); + if (err) + return err; + + if (pl_isset(&msg->from.dname)) { + err = pl_strdup(&call->peer_name, &msg->from.dname); + if (err) + return err; + } + + if (got_offer) { + + err = sdp_decode(call->sdp, msg->mb, true); + if (err) + return err; + + call->got_offer = true; + } + + err = sipsess_accept(&call->sess, sess_sock, msg, 180, "Ringing", + ua_cuser(call->ua), "application/sdp", NULL, + auth_handler, call->acc, true, + sipsess_offer_handler, sipsess_answer_handler, + sipsess_estab_handler, sipsess_info_handler, + sipsess_refer_handler, sipsess_close_handler, + call, "Allow: %s\r\n", uag_allowed_methods()); + if (err) { + DEBUG_WARNING("sipsess_accept: %m\n", err); + return err; + } + + set_state(call, STATE_INCOMING); + + /* New call */ + tmr_start(&call->tmr_inv, LOCAL_TIMEOUT*1000, invite_timeout, call); + + if (!call->acc->mnat) + call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri); + + return err; +} + + +static void sipsess_progr_handler(const struct sip_msg *msg, void *arg) +{ + struct call *call = arg; + bool media; + + MAGIC_CHECK(call); + + info("call: SIP Progress: %u %r (%r)\n", + msg->scode, &msg->reason, &msg->ctype); + + if (msg->scode <= 100) + return; + + /* check for 18x and content-type + * + * 1. start media-stream if application/sdp + * 2. play local ringback tone if not + * + * we must also handle changes to/from 180 and 183, + * so we reset the media-stream/ringback each time. + */ + if (!pl_strcasecmp(&msg->ctype, "application/sdp") + && mbuf_get_left(msg->mb) + && !sdp_decode(call->sdp, msg->mb, false)) { + media = true; + } + else if (!sdp_decode_multipart(&msg->ctype, msg->mb) && + !sdp_decode(call->sdp, msg->mb, false)) { + media = true; + } + else + media = false; + + switch (msg->scode) { + + case 180: + set_state(call, STATE_RINGING); + break; + + case 183: + set_state(call, STATE_EARLY); + break; + } + + if (media) + call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri); + else + call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri); + + call_stream_stop(call); + + if (media) + call_stream_start(call, false); +} + + +static int send_invite(struct call *call) +{ + const char *routev[1]; + struct mbuf *desc; + int err; + + routev[0] = ua_outbound(call->ua); + + err = call_sdp_get(call, &desc, true); + if (err) + return err; + + err = sipsess_connect(&call->sess, uag_sipsess_sock(), + call->peer_uri, + call->local_name, + call->local_uri, + ua_cuser(call->ua), + routev[0] ? routev : NULL, + routev[0] ? 1 : 0, + "application/sdp", desc, + auth_handler, call->acc, true, + sipsess_offer_handler, sipsess_answer_handler, + sipsess_progr_handler, sipsess_estab_handler, + sipsess_info_handler, sipsess_refer_handler, + sipsess_close_handler, call, + "Allow: %s\r\n%H", uag_allowed_methods(), + ua_print_supported, call->ua); + if (err) { + DEBUG_WARNING("sipsess_connect: %m\n", err); + } + + mem_deref(desc); + + return err; +} + + +/** + * Get the current call duration in seconds + * + * @param call Call object + * + * @return Duration in seconds + */ +uint32_t call_duration(const struct call *call) +{ + if (!call || !call->time_start) + return 0; + + return (uint32_t)(time(NULL) - call->time_start); +} + + +/** + * Get the audio object for the current call + * + * @param call Call object + * + * @return Audio object + */ +struct audio *call_audio(const struct call *call) +{ + return call ? call->audio : NULL; +} + + +/** + * Get the video object for the current call + * + * @param call Call object + * + * @return Video object + */ +struct video *call_video(const struct call *call) +{ +#ifdef USE_VIDEO + return call ? call->video : NULL; +#else + (void)call; + return NULL; +#endif +} + + +/** + * Get the list of media streams for the current call + * + * @param call Call object + * + * @return List of media streams + */ +struct list *call_streaml(const struct call *call) +{ + return call ? (struct list *)&call->streaml : NULL; +} + + +int call_reset_transp(struct call *call) +{ + if (!call) + return EINVAL; + + sdp_session_set_laddr(call->sdp, net_laddr_af(call->af)); + + return call_modify(call); +} + + +int call_notify_sipfrag(struct call *call, uint16_t scode, + const char *reason, ...) +{ + struct mbuf *mb; + va_list ap; + int err; + + if (!call) + return EINVAL; + + mb = mbuf_alloc(512); + if (!mb) + return ENOMEM; + + va_start(ap, reason); + (void)mbuf_printf(mb, "SIP/2.0 %u %v\n", scode, reason, &ap); + va_end(ap); + + mb->pos = 0; + + if (scode >= 200) { + err = sipevent_notify(call->not, mb, SIPEVENT_TERMINATED, + SIPEVENT_NORESOURCE, 0); + + call->not = mem_deref(call->not); + } + else { + err = sipevent_notify(call->not, mb, SIPEVENT_ACTIVE, + SIPEVENT_NORESOURCE, 0); + } + + mem_deref(mb); + + return err; +} + + +static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + struct pl scode, reason; + uint32_t sc; + + if (re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb), + "SIP/2.0 [0-9]+ [^\r\n]+", &scode, &reason)) { + (void)sip_reply(sip, msg, 400, "Bad sipfrag"); + return; + } + + (void)sip_reply(sip, msg, 200, "OK"); + + sc = pl_u32(&scode); + + if (sc >= 300) { + DEBUG_WARNING("call transfer failed: %u %r\n", sc, &reason); + } + else if (sc >= 200) { + call_event_handler(call, CALL_EVENT_CLOSED, "Call transfered"); + } +} + + +static void sipsub_close_handler(int err, const struct sip_msg *msg, + const struct sipevent_substate *substate, + void *arg) +{ + struct call *call = arg; + + (void)substate; + + call->sub = mem_deref(call->sub); + + if (err) { + info("call: subscription closed: %m\n", err); + } + else if (msg && msg->scode >= 300) { + info("call: transfer failed: %u %r\n", + msg->scode, &msg->reason); + } +} + + +static int normalize_uri(char **out, const char *uri, const struct uri *luri) +{ + struct uri uri2; + struct pl pl; + int err; + + if (!out || !uri || !luri) + return EINVAL; + + pl_set_str(&pl, uri); + + if (0 == uri_decode(&uri2, &pl)) { + + err = str_dup(out, uri); + } + else { + uri2 = *luri; + + uri2.user = pl; + uri2.password = pl_null; + uri2.params = pl_null; + + err = re_sdprintf(out, "%H", uri_encode, &uri2); + } + + return err; +} + + +/** + * Transfer the call to a target SIP uri + * + * @param call Call object + * @param uri Target SIP uri + * + * @return 0 if success, otherwise errorcode + */ +int call_transfer(struct call *call, const char *uri) +{ + char *nuri; + int err; + + if (!call || !uri) + return EINVAL; + + err = normalize_uri(&nuri, uri, &call->acc->luri); + if (err) + return err; + + info("transferring call to %s\n", nuri); + + call->sub = mem_deref(call->sub); + err = sipevent_drefer(&call->sub, uag_sipevent_sock(), + sipsess_dialog(call->sess), ua_cuser(call->ua), + auth_handler, call->acc, true, + sipsub_notify_handler, sipsub_close_handler, + call, + "Refer-To: %s\r\n", nuri); + if (err) { + DEBUG_WARNING("sipevent_drefer: %m\n", err); + } + + mem_deref(nuri); + + return err; +} + + +int call_af(const struct call *call) +{ + return call ? call->af : AF_UNSPEC; +} + + +uint16_t call_scode(const struct call *call) +{ + return call ? call->scode : 0; +} + + +void call_set_handlers(struct call *call, call_event_h *eh, + call_dtmf_h *dtmfh, void *arg) +{ + if (!call) + return; + + call->eh = eh; + call->dtmfh = dtmfh; + call->arg = arg; +} diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..7562081 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,351 @@ +/** + * @file src/cmd.c Command Interface + * + * Copyright (C) 2010 Creytiv.com + */ +#include <ctype.h> +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +enum { + REL = 0x00, + ESC = 0x1b, + DEL = 0x7f, +}; + + +struct cmds { + struct le le; + const struct cmd *cmdv; + size_t cmdc; +}; + +struct cmd_ctx { + struct mbuf *mb; + const struct cmd *cmd; +}; + + +static struct list cmdl; /**< List of command blocks (struct cmds) */ + + +static void destructor(void *arg) +{ + struct cmds *cmds = arg; + + list_unlink(&cmds->le); +} + + +static void ctx_destructor(void *arg) +{ + struct cmd_ctx *ctx = arg; + + mem_deref(ctx->mb); +} + + +static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd) +{ + struct cmd_ctx *ctx; + + ctx = mem_zalloc(sizeof(*ctx), ctx_destructor); + if (!ctx) + return ENOMEM; + + ctx->mb = mbuf_alloc(32); + if (!ctx->mb) { + mem_deref(ctx); + return ENOMEM; + } + + ctx->cmd = cmd; + + *ctxp = ctx; + + return 0; +} + + +static struct cmds *cmds_find(const struct cmd *cmdv) +{ + struct le *le; + + if (!cmdv) + return NULL; + + for (le = cmdl.head; le; le = le->next) { + struct cmds *cmds = le->data; + + if (cmds->cmdv == cmdv) + return cmds; + } + + return NULL; +} + + +static const struct cmd *cmd_find_by_key(char key) +{ + struct le *le; + + for (le = cmdl.tail; le; le = le->prev) { + + struct cmds *cmds = le->data; + size_t i; + + for (i=0; i<cmds->cmdc; i++) { + + const struct cmd *cmd = &cmds->cmdv[i]; + + if (cmd->key == key && cmd->h) + return cmd; + } + } + + return NULL; +} + + +static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd) +{ + switch (cmd->key) { + + case ' ': return "SPACE"; + case '\n': return "ENTER"; + case ESC: return "ESC"; + } + + buf[0] = cmd->key; + buf[1] = '\0'; + + if (cmd->flags & CMD_PRM) + strncat(buf, " ..", sz-1); + + return buf; +} + + +static int editor_input(struct mbuf *mb, char key, + struct re_printf *pf, bool *del) +{ + int err = 0; + + switch (key) { + + case ESC: + *del = true; + return re_hprintf(pf, "\nCancel\n"); + + case REL: + break; + + case '\n': + *del = true; + return re_hprintf(pf, "\n"); + + case '\b': + case DEL: + if (mb->pos > 0) + mb->pos = mb->end = (mb->pos - 1); + break; + + default: + err = mbuf_write_u8(mb, key); + break; + } + + err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end); + + return err; +} + + +static int cmd_report(const struct cmd *cmd, struct re_printf *pf, + struct mbuf *mb, bool compl) +{ + struct cmd_arg arg; + int err; + + mb->pos = 0; + err = mbuf_strdup(mb, &arg.prm, mb->end); + if (err) + return err; + + arg.key = cmd->key; + arg.complete = compl; + + err = cmd->h(pf, &arg); + + mem_deref(arg.prm); + + return err; +} + + +static int cmd_process_edit(struct cmd_ctx **ctxp, char key, + struct re_printf *pf) +{ + struct cmd_ctx *ctx; + bool compl = (key == '\n'), del = false; + int err; + + if (!ctxp) + return EINVAL; + + ctx = *ctxp; + + err = editor_input(ctx->mb, key, pf, &del); + if (err) + return err; + + if (compl || ctx->cmd->flags & CMD_PROG) + err = cmd_report(ctx->cmd, pf, ctx->mb, compl); + + if (del) + *ctxp = mem_deref(*ctxp); + + return err; +} + + +/** + * Register commands + * + * @param cmdv Array of commands + * @param cmdc Number of commands + * + * @return 0 if success, otherwise errorcode + */ +int cmd_register(const struct cmd *cmdv, size_t cmdc) +{ + struct cmds *cmds; + + if (!cmdv || !cmdc) + return EINVAL; + + cmds = cmds_find(cmdv); + if (cmds) + return EALREADY; + + cmds = mem_zalloc(sizeof(*cmds), destructor); + if (!cmds) + return ENOMEM; + + cmds->cmdv = cmdv; + cmds->cmdc = cmdc; + + list_append(&cmdl, &cmds->le, cmds); + + return 0; +} + + +/** + * Unregister commands + * + * @param cmdv Array of commands + */ +void cmd_unregister(const struct cmd *cmdv) +{ + mem_deref(cmds_find(cmdv)); +} + + +/** + * Process input characters to the command system + * + * @param ctxp Pointer to context for editor (optional) + * @param key Input character + * @param pf Print function + * + * @return 0 if success, otherwise errorcode + */ +int cmd_process(struct cmd_ctx **ctxp, char key, struct re_printf *pf) +{ + const struct cmd *cmd; + + /* are we in edit-mode? */ + if (ctxp && *ctxp) { + + if (key == REL) + return 0; + + return cmd_process_edit(ctxp, key, pf); + } + + cmd = cmd_find_by_key(key); + if (cmd) { + struct cmd_arg arg; + + /* check for parameters */ + if (cmd->flags & CMD_PRM) { + + if (ctxp) { + int err = ctx_alloc(ctxp, cmd); + if (err) + return err; + } + + return cmd_process_edit(ctxp, + isdigit(key) ? key : 0, + pf); + } + + arg.key = key; + arg.prm = NULL; + arg.complete = true; + + return cmd->h(pf, &arg); + } + + if (key == REL) + return 0; + + return cmd_print(pf, NULL); +} + + +/** + * Print a list of available commands + * + * @param pf Print function + * @param unused Unused variable + * + * @return 0 if success, otherwise errorcode + */ +int cmd_print(struct re_printf *pf, void *unused) +{ + size_t width = 5; + char fmt[32], buf[8]; + int err = 0; + int key; + + (void)unused; + + if (!pf) + return EINVAL; + + (void)re_snprintf(fmt, sizeof(fmt), " %%-%zus %%s\n", width); + + err |= re_hprintf(pf, "--- Help ---\n"); + + /* print in alphabetical order */ + for (key = 1; key <= 0x80; key++) { + + const struct cmd *cmd = cmd_find_by_key(key); + if (!cmd || !str_isset(cmd->desc)) + continue; + + err |= re_hprintf(pf, fmt, cmd_name(buf, sizeof(buf), cmd), + cmd->desc); + + } + + err |= re_hprintf(pf, "\n"); + + return err; +} diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000..cf93f1b --- /dev/null +++ b/src/conf.c @@ -0,0 +1,380 @@ +/** + * @file conf.c Configuration utils + * + * Copyright (C) 2010 Creytiv.com + */ +#define _BSD_SOURCE 1 +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <stdio.h> +#include <sys/stat.h> +#ifdef HAVE_IO_H +#include <io.h> +#endif +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "core.h" + + +#define DEBUG_MODULE "" +#define DEBUG_LEVEL 0 +#include <re_dbg.h> + + +#ifdef WIN32 +#define open _open +#define read _read +#define close _close +#endif + + +#if defined (WIN32) || defined (__SYMBIAN32__) +#define DIR_SEP "\\" +#else +#define DIR_SEP "/" +#endif + + +static const char *conf_path = NULL; +static struct conf *conf_obj; + + +/** + * Check if a file exists + * + * @param path Filename + * + * @return True if exist, False if not + */ +bool conf_fileexist(const char *path) +{ + struct stat st; + + if (!path) + return false; + + if (stat(path, &st) < 0) + return false; + + if ((st.st_mode & S_IFMT) != S_IFREG) + return false; + + return st.st_size > 0; +} + + +static void print_populated(const char *what, uint32_t n) +{ + info("Populated %u %s%s\n", n, what, 1==n ? "" : "s"); +} + + +/** + * Parse a config file, calling handler for each line + * + * @param filename Config file + * @param ch Line handler + * + * @return 0 if success, otherwise errorcode + */ +int conf_parse(const char *filename, confline_h *ch) +{ + struct pl pl, val; + struct mbuf *mb; + int err = 0, fd = open(filename, O_RDONLY); + if (fd < 0) + return errno; + + mb = mbuf_alloc(1024); + if (!mb) { + err = ENOMEM; + goto out; + } + + for (;;) { + uint8_t buf[1024]; + + const ssize_t n = read(fd, (void *)buf, sizeof(buf)); + if (n < 0) { + err = errno; + break; + } + else if (n == 0) + break; + + err |= mbuf_write_mem(mb, buf, n); + } + + pl.p = (const char *)mb->buf; + pl.l = mb->end; + + while (pl.p < ((const char *)mb->buf + mb->end) && !err) { + const char *lb = pl_strchr(&pl, '\n'); + + val.p = pl.p; + val.l = lb ? (uint32_t)(lb - pl.p) : pl.l; + pl_advance(&pl, val.l + 1); + + if (!val.l || val.p[0] == '#') + continue; + + err = ch(&val); + } + + out: + mem_deref(mb); + (void)close(fd); + + return err; +} + + +/** + * Set the path to configuration files + * + * @param path Configuration path + */ +void conf_path_set(const char *path) +{ + conf_path = path; +} + + +/** + * Get the path to configuration files + * + * @param path Buffer to write path + * @param sz Size of path buffer + * + * @return 0 if success, otherwise errorcode + */ +int conf_path_get(char *path, size_t sz) +{ + char buf[256]; + int err; + + /* Use explicit conf path */ + if (conf_path) { + if (re_snprintf(path, sz, "%s", conf_path) < 0) + return ENOMEM; + return 0; + } + + err = fs_gethome(buf, sizeof(buf)); + if (err) + return err; + + if (re_snprintf(path, sz, "%s" DIR_SEP ".baresip", buf) < 0) + return ENOMEM; + + return 0; +} + + +int conf_get_range(const struct conf *conf, const char *name, + struct range *rng) +{ + struct pl r, min, max; + uint32_t v; + int err; + + err = conf_get(conf, name, &r); + if (err) + return err; + + err = re_regex(r.p, r.l, "[0-9]+-[0-9]+", &min, &max); + if (err) { + /* fallback to non-range numeric value */ + err = conf_get_u32(conf, name, &v); + if (err) { + warning("conf: %s: could not parse range: (%r)\n", + name, &r); + return err; + } + + rng->min = rng->max = v; + + return err; + } + + rng->min = pl_u32(&min); + rng->max = pl_u32(&max); + + if (rng->min > rng->max) { + warning("conf: %s: invalid range (%u - %u)\n", + name, rng->min, rng->max); + return EINVAL; + } + + return 0; +} + + +int conf_get_csv(const struct conf *conf, const char *name, + char *str1, size_t sz1, char *str2, size_t sz2) +{ + struct pl r, pl1, pl2 = pl_null; + int err; + + err = conf_get(conf, name, &r); + if (err) + return err; + + err = re_regex(r.p, r.l, "[^,]+,[^]*", &pl1, &pl2); + if (err) + return err; + + (void)pl_strcpy(&pl1, str1, sz1); + if (pl_isset(&pl2)) + (void)pl_strcpy(&pl2, str2, sz2); + + return 0; +} + + +int conf_get_vidsz(const struct conf *conf, const char *name, struct vidsz *sz) +{ + struct pl r, w, h; + int err; + + err = conf_get(conf, name, &r); + if (err) + return err; + + w.l = h.l = 0; + err = re_regex(r.p, r.l, "[0-9]+x[0-9]+", &w, &h); + if (err) + return err; + + if (pl_isset(&w) && pl_isset(&h)) { + sz->w = pl_u32(&w); + sz->h = pl_u32(&h); + } + + /* check resolution */ + if (sz->w & 0x1 || sz->h & 0x1) { + warning("conf: %s: should be multiple of 2 (%u x %u)\n", + name, sz->w, sz->h); + return EINVAL; + } + + return 0; +} + + +int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa) +{ + struct pl opt; + int err; + + if (!conf || !name || !sa) + return EINVAL; + + err = conf_get(conf, name, &opt); + if (err) + return err; + + return sa_decode(sa, opt.p, opt.l); +} + + +/** + * Configure the system with default settings + * + * @return 0 if success, otherwise errorcode + */ +int conf_configure(void) +{ + char path[256], file[256]; + int err; + +#if defined (WIN32) || defined (__SYMBIAN32__) + dbg_init(DBG_INFO, DBG_NONE); +#endif + + err = conf_path_get(path, sizeof(path)); + if (err) { + warning("conf: could not get config path: %m\n", err); + return err; + } + + if (re_snprintf(file, sizeof(file), "%s/config", path) < 0) + return ENOMEM; + + if (!conf_fileexist(file)) { + + (void)fs_mkdir(path, 0700); + + err = config_write_template(file, conf_config()); + if (err) + goto out; + } + + err = conf_alloc(&conf_obj, file); + if (err) + goto out; + + err = config_parse_conf(conf_config(), conf_obj); + if (err) + goto out; + + out: + conf_obj = mem_deref(conf_obj); + return err; +} + + +/** + * Load all modules from config file + * + * @return 0 if success, otherwise errorcode + */ +int conf_modules(void) +{ + char path[256], file[256]; + int err; + + err = conf_path_get(path, sizeof(path)); + if (err) + return err; + + if (re_snprintf(file, sizeof(file), "%s/config", path) < 0) + return ENOMEM; + + err = conf_alloc(&conf_obj, file); + if (err) + goto out; + + err = module_init(conf_obj); + if (err) { + warning("conf: configure module parse error (%m)\n", err); + goto out; + } + + print_populated("audio codec", list_count(aucodec_list())); + print_populated("audio filter", list_count(aufilt_list())); +#ifdef USE_VIDEO + print_populated("video codec", list_count(vidcodec_list())); + print_populated("video filter", list_count(vidfilt_list())); +#endif + + out: + conf_obj = mem_deref(conf_obj); + return err; +} + + +/** + * Get the current configuration object + * + * @return Config object + * + * @note It is only available during init + */ +struct conf *conf_cur(void) +{ + return conf_obj; +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..dd65c52 --- /dev/null +++ b/src/config.c @@ -0,0 +1,706 @@ +/** + * @file config.c Core Configuration + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <dirent.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "core.h" + + +#undef MOD_PRE +#define MOD_PRE "" /**< Module prefix */ + + +/** Core Run-time Configuration - populated from config file */ +/** @todo: move config parsing/decoding to a module */ +static struct config core_config = { + /* Input */ + { + "/dev/event0", + 5555 + }, + + /** SIP User-Agent */ + { + 16, + "", + "", + "" + }, + + /** Audio */ + { + "","", + "","", + "","", + {8000, 48000}, + {1, 2}, + 0, + 0, + 0, + 0, + false, + AUDIO_MODE_POLL, + }, + +#ifdef USE_VIDEO + /** Video */ + { + "", "", + "", "", + 352, 288, + 512000, + 25, + }, +#endif + + /** Audio/Video Transport */ + { + 0xb8, + {1024, 49152}, + {0, 0}, + true, + false, + {5, 10}, + false + }, + + /* Network */ + { + "" + }, + +#ifdef USE_VIDEO + /* BFCP */ + { + "" + }, +#endif +}; + + +static int range_print(struct re_printf *pf, const struct range *rng) +{ + if (!rng) + return 0; + + return re_hprintf(pf, "%u-%u", rng->min, rng->max); +} + + +static int dns_server_handler(const struct pl *pl, void *arg) +{ + struct sa sa; + int err; + + (void)arg; + + err = sa_decode(&sa, pl->p, pl->l); + if (err) { + warning("config: dns_server: could not decode `%r'\n", pl); + return err; + } + + err = net_dnssrv_add(&sa); + if (err) { + warning("config: failed to add nameserver %r: %m\n", pl, err); + } + + return err; +} + + +int config_parse_conf(struct config *cfg, const struct conf *conf) +{ + struct pl pollm, as, ap; + enum poll_method method; + struct vidsz size = {0, 0}; + uint32_t v; + int err = 0; + + if (!cfg || !conf) + return EINVAL; + + /* Core */ + if (0 == conf_get(conf, "poll_method", &pollm)) { + if (0 == poll_method_type(&method, &pollm)) { + err = poll_method_set(method); + if (err) { + warning("config: poll method (%r) set: %m\n", + &pollm, err); + } + } + else { + warning("config: unknown poll method (%r)\n", &pollm); + } + } + + /* Input */ + (void)conf_get_str(conf, "input_device", cfg->input.device, + sizeof(cfg->input.device)); + (void)conf_get_u32(conf, "input_port", &cfg->input.port); + + /* SIP */ + (void)conf_get_u32(conf, "sip_trans_bsize", &cfg->sip.trans_bsize); + (void)conf_get_str(conf, "sip_listen", cfg->sip.local, + sizeof(cfg->sip.local)); + (void)conf_get_str(conf, "sip_certificate", cfg->sip.cert, + sizeof(cfg->sip.cert)); + + /* Audio */ + (void)conf_get_csv(conf, "audio_player", + cfg->audio.play_mod, + sizeof(cfg->audio.play_mod), + cfg->audio.play_dev, + sizeof(cfg->audio.play_dev)); + + (void)conf_get_csv(conf, "audio_source", + cfg->audio.src_mod, sizeof(cfg->audio.src_mod), + cfg->audio.src_dev, sizeof(cfg->audio.src_dev)); + + (void)conf_get_csv(conf, "audio_alert", + cfg->audio.alert_mod, + sizeof(cfg->audio.alert_mod), + cfg->audio.alert_dev, + sizeof(cfg->audio.alert_dev)); + + (void)conf_get_range(conf, "audio_srate", &cfg->audio.srate); + (void)conf_get_range(conf, "audio_channels", &cfg->audio.channels); + (void)conf_get_u32(conf, "ausrc_srate", &cfg->audio.srate_src); + (void)conf_get_u32(conf, "auplay_srate", &cfg->audio.srate_play); + (void)conf_get_u32(conf, "ausrc_channels", &cfg->audio.channels_src); + (void)conf_get_u32(conf, "auplay_channels", &cfg->audio.channels_play); + + if (0 == conf_get(conf, "audio_source", &as) && + 0 == conf_get(conf, "audio_player", &ap)) + cfg->audio.src_first = as.p < ap.p; + +#ifdef USE_VIDEO + /* Video */ + (void)conf_get_csv(conf, "video_source", + cfg->video.src_mod, sizeof(cfg->video.src_mod), + cfg->video.src_dev, sizeof(cfg->video.src_dev)); + (void)conf_get_csv(conf, "video_display", + cfg->video.disp_mod, sizeof(cfg->video.disp_mod), + cfg->video.disp_dev, sizeof(cfg->video.disp_dev)); + if (0 == conf_get_vidsz(conf, "video_size", &size)) { + cfg->video.width = size.w; + cfg->video.height = size.h; + } + (void)conf_get_u32(conf, "video_bitrate", &cfg->video.bitrate); + (void)conf_get_u32(conf, "video_fps", &cfg->video.fps); +#else + (void)size; +#endif + + /* AVT - Audio/Video Transport */ + if (0 == conf_get_u32(conf, "rtp_tos", &v)) + cfg->avt.rtp_tos = v; + (void)conf_get_range(conf, "rtp_ports", &cfg->avt.rtp_ports); + if (0 == conf_get_range(conf, "rtp_bandwidth", + &cfg->avt.rtp_bw)) { + cfg->avt.rtp_bw.min *= 1024; + cfg->avt.rtp_bw.max *= 1024; + } + (void)conf_get_bool(conf, "rtcp_enable", &cfg->avt.rtcp_enable); + (void)conf_get_bool(conf, "rtcp_mux", &cfg->avt.rtcp_mux); + (void)conf_get_range(conf, "jitter_buffer_delay", + &cfg->avt.jbuf_del); + (void)conf_get_bool(conf, "rtp_stats", &cfg->avt.rtp_stats); + + if (err) { + warning("config: configure parse error (%m)\n", err); + } + + /* Network */ + (void)conf_apply(conf, "dns_server", dns_server_handler, NULL); + (void)conf_get_str(conf, "net_interface", + cfg->net.ifname, sizeof(cfg->net.ifname)); + +#ifdef USE_VIDEO + /* BFCP */ + (void)conf_get_str(conf, "bfcp_proto", cfg->bfcp.proto, + sizeof(cfg->bfcp.proto)); +#endif + + return err; +} + + +int config_print(struct re_printf *pf, const struct config *cfg) +{ + int err; + + if (!cfg) + return 0; + + err = re_hprintf(pf, + "\n" + "# Input\n" + "input_device\t\t%s\n" + "input_port\t\t%u\n" + "\n" + "# SIP\n" + "sip_trans_bsize\t\t%u\n" + "sip_listen\t\t%s\n" + "sip_certificate\t%s\n" + "\n" + "# Audio\n" + "audio_player\t\t%s,%s\n" + "audio_source\t\t%s,%s\n" + "audio_alert\t\t%s,%s\n" + "audio_srate\t\t%H\n" + "audio_channels\t\t%H\n" + "auplay_srate\t\t%u\n" + "ausrc_srate\t\t%u\n" + "auplay_channels\t\t%u\n" + "ausrc_channels\t\t%u\n" + "\n" +#ifdef USE_VIDEO + "# Video\n" + "video_source\t\t%s,%s\n" + "video_display\t\t%s,%s\n" + "video_size\t\t\"%ux%u\"\n" + "video_bitrate\t\t%u\n" + "video_fps\t\t%u\n" + "\n" +#endif + "# AVT\n" + "rtp_tos\t\t\t%u\n" + "rtp_ports\t\t%H\n" + "rtp_bandwidth\t\t%H\n" + "rtcp_enable\t\t%s\n" + "rtcp_mux\t\t%s\n" + "jitter_buffer_delay\t%H\n" + "rtp_stats\t\t%s\n" + "\n" + "# Network\n" + "net_interface\t\t%s\n" + "\n" +#ifdef USE_VIDEO + "# BFCP\n" + "bfcp_proto\t\t%s\n" + "\n" +#endif + , + + cfg->input.device, cfg->input.port, + + cfg->sip.trans_bsize, cfg->sip.local, cfg->sip.cert, + + cfg->audio.play_mod, cfg->audio.play_dev, + cfg->audio.src_mod, cfg->audio.src_dev, + cfg->audio.alert_mod, cfg->audio.alert_dev, + range_print, &cfg->audio.srate, + range_print, &cfg->audio.channels, + cfg->audio.srate_play, cfg->audio.srate_src, + cfg->audio.channels_play, cfg->audio.channels_src, + +#ifdef USE_VIDEO + cfg->video.src_mod, cfg->video.src_dev, + cfg->video.disp_mod, cfg->video.disp_dev, + cfg->video.width, cfg->video.height, + cfg->video.bitrate, cfg->video.fps, +#endif + + cfg->avt.rtp_tos, + range_print, &cfg->avt.rtp_ports, + range_print, &cfg->avt.rtp_bw, + cfg->avt.rtcp_enable ? "yes" : "no", + cfg->avt.rtcp_mux ? "yes" : "no", + range_print, &cfg->avt.jbuf_del, + cfg->avt.rtp_stats ? "yes" : "no", + + cfg->net.ifname + +#ifdef USE_VIDEO + ,cfg->bfcp.proto +#endif + ); + + return err; +} + + +static const char *default_audio_device(void) +{ +#ifdef DARWIN + return "coreaudio,nil"; +#elif defined (FREEBSD) + return "oss,/dev/dsp"; +#elif defined (WIN32) + return "winwave,nil"; +#else + return "alsa,default"; +#endif +} + + +#ifdef USE_VIDEO +static const char *default_video_device(void) +{ +#ifdef DARWIN + return "qtcapture,nil"; +#else + return "v4l2,/dev/video0"; +#endif +} + + +static const char *default_video_display(void) +{ +#ifdef DARWIN + return "opengl,nil"; +#else + return "x11,nil"; +#endif +} +#endif + + +static int default_interface_print(struct re_printf *pf, void *unused) +{ + char ifname[64]; + (void)unused; + + if (0 == net_rt_default_get(AF_INET, ifname, sizeof(ifname))) + return re_hprintf(pf, "%s", ifname); + else + return re_hprintf(pf, "eth0"); +} + + +static int core_config_template(struct re_printf *pf, const struct config *cfg) +{ + int err = 0; + + if (!cfg) + return 0; + + err |= re_hprintf(pf, + "\n# Core\n" + "poll_method\t\t%s\t\t# poll, select, epoll ..\n" + "\n# Input\n" + "input_device\t\t/dev/event0\n" + "input_port\t\t5555\n" + "\n# SIP\n" + "sip_trans_bsize\t\t128\n" + "#sip_listen\t\t0.0.0.0:5060\n" + "#sip_certificate\tcert.pem\n" + "\n# Audio\n" + "audio_player\t\t%s\n" + "audio_source\t\t%s\n" + "audio_alert\t\t%s\n" + "audio_srate\t\t%u-%u\n" + "audio_channels\t\t%u-%u\n" + "#ausrc_srate\t\t48000\n" + "#auplay_srate\t\t48000\n" + "#ausrc_channels\t\t0\n" + "#auplay_channels\t\t0\n" + , + poll_method_name(poll_method_best()), + default_audio_device(), + default_audio_device(), + default_audio_device(), + cfg->audio.srate.min, cfg->audio.srate.max, + cfg->audio.channels.min, cfg->audio.channels.max); + +#ifdef USE_VIDEO + err |= re_hprintf(pf, + "\n# Video\n" + "#video_source\t\t%s\n" + "#video_display\t\t%s\n" + "video_size\t\t%dx%d\n" + "video_bitrate\t\t%u\n" + "video_fps\t\t%u\n", + default_video_device(), + default_video_display(), + cfg->video.width, cfg->video.height, + cfg->video.bitrate, cfg->video.fps); +#endif + + err |= re_hprintf(pf, + "\n# AVT - Audio/Video Transport\n" + "rtp_tos\t\t\t184\n" + "#rtp_ports\t\t10000-20000\n" + "#rtp_bandwidth\t\t512-1024 # [kbit/s]\n" + "rtcp_enable\t\tyes\n" + "rtcp_mux\t\tno\n" + "jitter_buffer_delay\t%u-%u\t\t# frames\n" + "rtp_stats\t\tno\n" + "\n# Network\n" + "#dns_server\t\t10.0.0.1:53\n" + "#net_interface\t\t%H\n", + cfg->avt.jbuf_del.min, cfg->avt.jbuf_del.max, + default_interface_print, NULL); + +#ifdef USE_VIDEO + err |= re_hprintf(pf, + "\n# BFCP\n" + "#bfcp_proto\t\tudp\n"); +#endif + + return err; +} + + +static uint32_t count_modules(const char *path) +{ + DIR *dirp; + struct dirent *dp; + uint32_t n = 0; + + dirp = opendir(path); + if (!dirp) + return 0; + + while ((dp = readdir(dirp)) != NULL) { + + size_t len = strlen(dp->d_name); + const size_t x = sizeof(MOD_EXT)-1; + + if (len <= x) + continue; + + if (0==memcmp(&dp->d_name[len-x], MOD_EXT, x)) + ++n; + } + + (void)closedir(dirp); + + return n; +} + + +static const char *detect_module_path(bool *valid) +{ + static const char * const pathv[] = { +#if defined (PREFIX) + "" PREFIX "/lib/baresip/modules", +#else + "/usr/local/lib/baresip/modules", + "/usr/lib/baresip/modules", +#endif + }; + const char *current = pathv[0]; + uint32_t nmax = 0; + + for (size_t i=0; i<ARRAY_SIZE(pathv); i++) { + + uint32_t n = count_modules(pathv[i]); + + info("%s: detected %u modules\n", pathv[i], n); + + if (n > nmax) { + nmax = n; + current = pathv[i]; + } + } + + if (nmax > 0) + *valid = true; + + return current; +} + + +int config_write_template(const char *file, const struct config *cfg) +{ + FILE *f = NULL; + int err = 0; + const char *modpath; + bool modpath_valid = false; + + if (!file || !cfg) + return EINVAL; + + info("config: creating config template %s\n", file); + + f = fopen(file, "w"); + if (!f) { + warning("config: writing %s: %m\n", file, errno); + return errno; + } + + (void)re_fprintf(f, + "#\n" + "# baresip configuration\n" + "#\n" + "\n" + "#------------------------------------" + "------------------------------------------\n"); + + (void)re_fprintf(f, "%H", core_config_template, cfg); + + (void)re_fprintf(f, + "\n#------------------------------------" + "------------------------------------------\n" + "# Modules\n" + "\n"); + + modpath = detect_module_path(&modpath_valid); + (void)re_fprintf(f, "%smodule_path\t\t%s\n", + modpath_valid ? "" : "#", modpath); + + (void)re_fprintf(f, "\n# UI Modules\n"); +#if defined (WIN32) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "wincons" MOD_EXT "\n"); +#else + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stdio" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cons" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "evdev" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "httpd" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Audio codec Modules (in order)\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "opus" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "silk" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "amr" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g7221" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g722" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g726" MOD_EXT "\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "g711" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gsm" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "l16" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "bv32" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Audio filter Modules (in encoding order)\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "vumeter" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sndfile" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_aec" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_pp" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "plc" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Audio driver Modules\n"); +#if defined (WIN32) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n"); +#elif defined (__SYMBIAN32__) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "mda" MOD_EXT "\n"); +#elif defined (DARWIN) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n"); +#else + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n"); + +#ifdef USE_VIDEO + + (void)re_fprintf(f, "\n# Video codec Modules (in order)\n"); +#ifdef USE_FFMPEG + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n"); +#else + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vpx" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Video filter Modules (in encoding order)\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "selfview" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Video source modules\n"); +#if defined (DARWIN) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "qtcapture" MOD_EXT "\n"); +#else + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2" MOD_EXT "\n"); +#endif +#ifdef USE_FFMPEG + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avformat" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11grab" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cairo" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Video display modules\n"); +#ifdef DARWIN + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opengl" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sdl2" MOD_EXT "\n"); + +#endif /* USE_VIDEO */ + + (void)re_fprintf(f, + "\n# Audio/Video source modules\n" + "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n" + "#module\t\t\t" MOD_PRE "gst" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Media NAT modules\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stun" MOD_EXT "\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "turn" MOD_EXT "\n"); + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "ice" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "natpmp" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Media encryption modules\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "srtp" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "dtls_srtp" MOD_EXT "\n"); + (void)re_fprintf(f, "\n"); + + (void)re_fprintf(f, "\n#------------------------------------" + "------------------------------------------\n"); + (void)re_fprintf(f, "# Temporary Modules (loaded then unloaded)\n"); + (void)re_fprintf(f, "\n"); + (void)re_fprintf(f, "#module_tmp\t\t" MOD_PRE "uuid" MOD_EXT "\n"); + (void)re_fprintf(f, "module_tmp\t\t" MOD_PRE "account" MOD_EXT "\n"); + (void)re_fprintf(f, "\n"); + + (void)re_fprintf(f, "\n#------------------------------------" + "------------------------------------------\n"); + (void)re_fprintf(f, "# Application Modules\n"); + (void)re_fprintf(f, "\n"); + (void)re_fprintf(f, "module_app\t\t" MOD_PRE "auloop"MOD_EXT"\n"); + (void)re_fprintf(f, "module_app\t\t" MOD_PRE "contact"MOD_EXT"\n"); + (void)re_fprintf(f, "module_app\t\t" MOD_PRE "menu"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mwi"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "natbd"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "presence"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "syslog"MOD_EXT"\n"); +#ifdef USE_VIDEO + (void)re_fprintf(f, "module_app\t\t" MOD_PRE "vidloop"MOD_EXT"\n"); +#endif + (void)re_fprintf(f, "\n"); + + (void)re_fprintf(f, "\n#------------------------------------" + "------------------------------------------\n"); + (void)re_fprintf(f, "# Module parameters\n"); + (void)re_fprintf(f, "\n"); + + (void)re_fprintf(f, "\n# Speex codec parameters\n"); + (void)re_fprintf(f, "speex_quality\t\t7 # 0-10\n"); + (void)re_fprintf(f, "speex_complexity\t7 # 0-10\n"); + (void)re_fprintf(f, "speex_enhancement\t0 # 0-1\n"); + (void)re_fprintf(f, "speex_vbr\t\t0 # Variable Bit Rate 0-1\n"); + (void)re_fprintf(f, "speex_vad\t\t0 # Voice Activity Detection 0-1\n"); + (void)re_fprintf(f, "speex_agc_level\t\t8000\n"); + + (void)re_fprintf(f, "\n# NAT Behavior Discovery\n"); + (void)re_fprintf(f, "natbd_server\t\tcreytiv.com\n"); + (void)re_fprintf(f, "natbd_interval\t\t600\t\t# in seconds\n"); + + (void)re_fprintf(f, + "\n# Selfview\n" + "video_selfview\t\twindow # {window,pip}\n" + "#selfview_size\t\t64x64\n"); + + (void)re_fprintf(f, + "\n# ICE\n" + "ice_turn\t\tno\n" + "ice_debug\t\tno\n" + "ice_nomination\t\tregular\t# {regular,aggressive}\n" + "ice_mode\t\tfull\t# {full,lite}\n"); + + if (f) + (void)fclose(f); + + return err; +} + + +struct config *conf_config(void) +{ + return &core_config; +} diff --git a/src/contact.c b/src/contact.c new file mode 100644 index 0000000..5161a1c --- /dev/null +++ b/src/contact.c @@ -0,0 +1,161 @@ +/** + * @file src/contact.c Contacts handling + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +struct contact { + struct le le; + struct sip_addr addr; + char *buf; + enum presence_status status; +}; + +static struct list cl; + + +static void destructor(void *arg) +{ + struct contact *c = arg; + + list_unlink(&c->le); + mem_deref(c->buf); +} + + +/** + * Add a contact + * + * @param contactp Pointer to allocated contact (optional) + * @param addr Contact in SIP address format + * + * @return 0 if success, otherwise errorcode + */ +int contact_add(struct contact **contactp, const struct pl *addr) +{ + struct contact *c; + struct pl pl; + int err; + + c = mem_zalloc(sizeof(*c), destructor); + if (!c) + return ENOMEM; + + err = pl_strdup(&c->buf, addr); + if (err) + goto out; + + pl_set_str(&pl, c->buf); + + err = sip_addr_decode(&c->addr, &pl); + if (err) { + warning("contact: decode error '%r'\n", addr); + goto out; + } + + c->status = PRESENCE_UNKNOWN; + + list_append(&cl, &c->le, c); + + out: + if (err) + mem_deref(c); + else if (contactp) + *contactp = c; + + return err; +} + + +/** + * Get the SIP address of a contact + * + * @param c Contact + * + * @return SIP Address + */ +struct sip_addr *contact_addr(const struct contact *c) +{ + return c ? (struct sip_addr *)&c->addr : NULL; +} + + +/** + * Get the contact string + * + * @param c Contact + * + * @return Contact string + */ +const char *contact_str(const struct contact *c) +{ + return c ? c->buf : NULL; +} + + +/** + * Get the list of contacts + * + * @return List of contacts + */ +struct list *contact_list(void) +{ + return &cl; +} + + +void contact_set_presence(struct contact *c, enum presence_status status) +{ + if (!c) + return; + + if (c->status != PRESENCE_UNKNOWN && c->status != status) { + + info("<%r> changed status from %s to %s\n", &c->addr.auri, + contact_presence_str(c->status), + contact_presence_str(status)); + } + + c->status = status; +} + + +const char *contact_presence_str(enum presence_status status) +{ + switch (status) { + + default: + case PRESENCE_UNKNOWN: return "\x1b[32m\x1b[;m"; + case PRESENCE_OPEN: return "\x1b[32mOnline\x1b[;m"; + case PRESENCE_CLOSED: return "\x1b[31mOffline\x1b[;m"; + case PRESENCE_BUSY: return "\x1b[31mBusy\x1b[;m"; + } +} + + +int contacts_print(struct re_printf *pf, void *unused) +{ + struct le *le; + int err; + + (void)unused; + + err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n", + list_count(contact_list())); + + for (le = list_head(contact_list()); le && !err; le = le->next) { + const struct contact *c = le->data; + const struct sip_addr *addr = &c->addr; + + err = re_hprintf(pf, "%20s %r <%r>\n", + contact_presence_str(c->status), + &addr->dname, &addr->auri); + } + + err |= re_hprintf(pf, "\n"); + + return err; +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..9b92b4b --- /dev/null +++ b/src/core.h @@ -0,0 +1,389 @@ +/** + * @file core.h Internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +/** + * RFC 3551: + * + * 0 - 95 Static payload types + * 96 - 127 Dynamic payload types + */ +enum { + PT_CN = 13, + PT_STAT_MIN = 0, + PT_STAT_MAX = 95, + PT_DYN_MIN = 96, + PT_DYN_MAX = 127 +}; + + +/** Media constants */ +enum { + AUDIO_BANDWIDTH = 128000 /**< Bandwidth for audio in bits/s */ +}; + + +/* + * Account + */ + + +/** Defines the answermodes */ +enum answermode { + ANSWERMODE_MANUAL = 0, + ANSWERMODE_EARLY, + ANSWERMODE_AUTO +}; + +struct account { + char *buf; /**< Buffer for the SIP address */ + struct sip_addr laddr; /**< Decoded SIP address */ + struct uri luri; /**< Decoded AOR uri */ + char *dispname; /**< Display name */ + char *aor; /**< Local SIP uri */ + + /* parameters: */ + enum answermode answermode; /**< Answermode for incoming calls */ + struct le acv[8]; /**< List elements for aucodecl */ + struct list aucodecl; /**< List of preferred audio-codecs */ + char *auth_user; /**< Authentication username */ + char *auth_pass; /**< Authentication password */ + char *mnatid; /**< Media NAT handling */ + char *mencid; /**< Media encryption type */ + const struct mnat *mnat; /**< MNAT module */ + const struct menc *menc; /**< MENC module */ + char *outbound[2]; /**< Optional SIP outbound proxies */ + uint32_t ptime; /**< Configured packet time in [ms] */ + uint32_t regint; /**< Registration interval in [seconds] */ + char *regq; /**< Registration Q-value */ + char *rtpkeep; /**< RTP Keepalive mechanism */ + char *sipnat; /**< SIP Nat mechanism */ + char *stun_user; /**< STUN Username */ + char *stun_pass; /**< STUN Password */ + char *stun_host; /**< STUN Hostname */ + uint16_t stun_port; /**< STUN Port number */ + struct le vcv[4]; /**< List elements for vidcodecl */ + struct list vidcodecl; /**< List of preferred video-codecs */ +}; + + +/* + * Audio Player + */ + +struct auplay_st { + struct auplay *ap; +}; + +struct auplay { + struct le le; + const char *name; + auplay_alloc_h *alloch; +}; + + +/* + * Audio Source + */ + +struct ausrc_st { + struct ausrc *as; +}; + +struct ausrc { + struct le le; + const char *name; + ausrc_alloc_h *alloch; +}; + + +/* + * Audio Stream + */ + +struct audio; + +typedef void (audio_event_h)(int key, bool end, void *arg); +typedef void (audio_err_h)(int err, const char *str, void *arg); + +int audio_alloc(struct audio **ap, const struct config *cfg, + struct call *call, struct sdp_session *sdp_sess, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + uint32_t ptime, const struct list *aucodecl, + audio_event_h *eventh, audio_err_h *errh, void *arg); +int audio_start(struct audio *a); +void audio_stop(struct audio *a); +int audio_encoder_set(struct audio *a, const struct aucodec *ac, + int pt_tx, const char *params); +int audio_decoder_set(struct audio *a, const struct aucodec *ac, + int pt_rx, const char *params); +struct stream *audio_strm(const struct audio *a); +int audio_send_digit(struct audio *a, char key); +void audio_sdp_attr_decode(struct audio *a); + + +/* + * BFCP + */ + +struct bfcp; +int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess, + const char *proto, bool offerer, + const struct mnat *mnat, struct mnat_sess *mnat_sess); +int bfcp_start(struct bfcp *bfcp); + + +/* + * Call Control + */ + +struct call; + +/** Call parameters */ +struct call_prm { + enum vidmode vidmode; + int af; +}; + +int call_alloc(struct call **callp, const struct config *cfg, + struct list *lst, + const char *local_name, const char *local_uri, + struct account *acc, struct ua *ua, const struct call_prm *prm, + const struct sip_msg *msg, struct call *xcall, + call_event_h *eh, void *arg); +int call_connect(struct call *call, const struct pl *paddr); +int call_accept(struct call *call, struct sipsess_sock *sess_sock, + const struct sip_msg *msg); +int call_hangup(struct call *call, uint16_t scode, const char *reason); +int call_progress(struct call *call); +int call_answer(struct call *call, uint16_t scode); +int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer); +int call_jbuf_stat(struct re_printf *pf, const struct call *call); +int call_info(struct re_printf *pf, const struct call *call); +int call_reset_transp(struct call *call); +int call_notify_sipfrag(struct call *call, uint16_t scode, + const char *reason, ...); +int call_af(const struct call *call); + + +/* + * Conf + */ + +int conf_get_range(const struct conf *conf, const char *name, + struct range *rng); +int conf_get_csv(const struct conf *conf, const char *name, + char *str1, size_t sz1, char *str2, size_t sz2); + + +/* + * Media control + */ + +int mctrl_handle_media_control(struct pl *body, bool *pfu); + + +/* + * Media NAT traversal + */ + +struct mnat { + struct le le; + const char *id; + const char *ftag; + mnat_sess_h *sessh; + mnat_media_h *mediah; + mnat_update_h *updateh; +}; + +const struct mnat *mnat_find(const char *id); + + +/* + * Metric + */ + +struct metric { + /* internal stuff: */ + struct tmr tmr; + uint64_t ts_start; + bool started; + + /* counters: */ + uint32_t n_packets; + uint32_t n_bytes; + uint32_t n_err; + + /* bitrate calculation */ + uint32_t cur_bitrate; + uint64_t ts_last; + uint32_t n_bytes_last; +}; + +void metric_reset(struct metric *metric); +void metric_add_packet(struct metric *metric, size_t packetsize); +uint32_t metric_avg_bitrate(const struct metric *metric); + + +/* + * Module + */ + +int module_init(const struct conf *conf); +void module_app_unload(void); + + +/* + * Network + */ + +int net_reset(void); + + +/* + * Register client + */ + +struct reg; + +int reg_add(struct list *lst, struct ua *ua, int regid); +int reg_register(struct reg *reg, const char *reg_uri, + const char *params, uint32_t regint, const char *outbound); +void reg_unregister(struct reg *reg); +bool reg_isok(const struct reg *reg); +int reg_sipfd(const struct reg *reg); +int reg_debug(struct re_printf *pf, const struct reg *reg); +int reg_status(struct re_printf *pf, const struct reg *reg); + + +/* + * RTP keepalive + */ + +struct rtpkeep; + +int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto, + struct rtp_sock *rtp, struct sdp_media *sdp); +void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts); + + +/* + * SIP Request + */ + +int sip_req_send(struct ua *ua, const char *method, const char *uri, + sip_resp_h *resph, void *arg, const char *fmt, ...); + + +/* + * SDP + */ + +int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb); +const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m); + + +/* + * Stream + */ + +struct stream; +struct rtp_header; + +enum {STREAM_PRESZ = 4+12}; /* same as RTP_HEADER_SIZE */ + +typedef void (stream_rtp_h)(const struct rtp_header *hdr, struct mbuf *mb, + void *arg); +typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg); + +int stream_alloc(struct stream **sp, const struct config_avt *cfg, + struct call *call, struct sdp_session *sdp_sess, + const char *name, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + const char *cname, + stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg); +struct sdp_media *stream_sdpmedia(const struct stream *s); +int stream_send(struct stream *s, bool marker, int pt, uint32_t ts, + struct mbuf *mb); +void stream_update(struct stream *s); +void stream_update_encoder(struct stream *s, int pt_enc); +int stream_jbuf_stat(struct re_printf *pf, const struct stream *s); +void stream_hold(struct stream *s, bool hold); +void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx); +void stream_send_fir(struct stream *s, bool pli); +void stream_reset(struct stream *s); +void stream_set_bw(struct stream *s, uint32_t bps); +int stream_debug(struct re_printf *pf, const struct stream *s); +int stream_print(struct re_printf *pf, const struct stream *s); + + +/* + * User-Agent + */ + +struct ua; + +void ua_event(struct ua *ua, enum ua_event ev, struct call *call, + const char *fmt, ...); +void ua_printf(const struct ua *ua, const char *fmt, ...); + +struct tls *uag_tls(void); +const char *uag_allowed_methods(void); + + +/* + * Video Display + */ + +struct vidisp { + struct le le; + const char *name; + vidisp_alloc_h *alloch; + vidisp_update_h *updateh; + vidisp_disp_h *disph; + vidisp_hide_h *hideh; +}; + +struct vidisp *vidisp_get(struct vidisp_st *st); + + +/* + * Video Source + */ + +struct vidsrc { + struct le le; + const char *name; + vidsrc_alloc_h *alloch; + vidsrc_update_h *updateh; +}; + +struct vidsrc *vidsrc_get(struct vidsrc_st *st); + + +/* + * Video Stream + */ + +struct video; + +int video_alloc(struct video **vp, const struct config *cfg, + struct call *call, struct sdp_session *sdp_sess, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + const char *content, const struct list *vidcodecl); +int video_start(struct video *v, const char *peer); +void video_stop(struct video *v); +int video_encoder_set(struct video *v, struct vidcodec *vc, + int pt_tx, const char *params); +int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx, + const char *fmtp); +struct stream *video_strm(const struct video *v); +void video_update_picture(struct video *v); +void video_sdp_attr_decode(struct video *v); +int video_print(struct re_printf *pf, const struct video *v); diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..1ccdc32 --- /dev/null +++ b/src/log.c @@ -0,0 +1,139 @@ +/** + * @file log.c Logging + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + + +static struct { + struct list logl; + bool debug; + bool stder; +} lg = { + .logl = LIST_INIT, + .debug = false, + .stder = true +}; + + +void log_register_handler(struct log *log) +{ + if (!log) + return; + + list_append(&lg.logl, &log->le, log); +} + + +void log_unregister_handler(struct log *log) +{ + if (!log) + return; + + list_unlink(&log->le); +} + + +void log_enable_debug(bool enable) +{ + lg.debug = enable; +} + + +void log_enable_stderr(bool enable) +{ + lg.stder = enable; +} + + +void vlog(enum log_level level, const char *fmt, va_list ap) +{ + char buf[4096]; + struct le *le; + + if (re_vsnprintf(buf, sizeof(buf), fmt, ap) < 0) + return; + + if (lg.stder) { + + bool color = level == WARN || level == ERROR; + + if (color) + (void)re_fprintf(stderr, "\x1b[31m"); /* Red */ + + (void)re_fprintf(stderr, "%s", buf); + + if (color) + (void)re_fprintf(stderr, "\x1b[;m"); + } + + le = lg.logl.head; + + while (le) { + + struct log *log = le->data; + le = le->next; + + if (log->h) + log->h(level, buf); + } +} + + +void loglv(enum log_level level, const char *fmt, ...) +{ + va_list ap; + + if ((DEBUG == level) && !lg.debug) + return; + + va_start(ap, fmt); + vlog(level, fmt, ap); + va_end(ap); +} + + +void debug(const char *fmt, ...) +{ + va_list ap; + + if (!lg.debug) + return; + + va_start(ap, fmt); + vlog(DEBUG, fmt, ap); + va_end(ap); +} + + +void info(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(INFO, fmt, ap); + va_end(ap); +} + + +void warning(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(WARN, fmt, ap); + va_end(ap); +} + + +void error(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(ERROR, fmt, ap); + va_end(ap); +} diff --git a/src/magic.h b/src/magic.h new file mode 100644 index 0000000..15eacee --- /dev/null +++ b/src/magic.h @@ -0,0 +1,27 @@ +/** + * @file magic.h Interface to magic macros + * + * Copyright (C) 2010 Creytiv.com + */ + + +#ifndef RELEASE + +#ifndef MAGIC +#error "macro MAGIC must be defined" +#endif + +/** Check magic number */ +#define MAGIC_DECL uint32_t magic; +#define MAGIC_INIT(s) (s)->magic = MAGIC +#define MAGIC_CHECK(s) \ + if (MAGIC != s->magic) { \ + warning("%s: wrong magic struct=%p (magic=0x%08x)\n", \ + __FUNCTION__, s, s->magic); \ + BREAKPOINT; \ + } +#else +#define MAGIC_DECL +#define MAGIC_INIT(s) +#define MAGIC_CHECK(s) do {(void)(s);} while (0); +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d55b349 --- /dev/null +++ b/src/main.c @@ -0,0 +1,154 @@ +/** + * @file main.c Main application code + * + * Copyright (C) 2010 - 2011 Creytiv.com + */ +#ifdef SOLARIS +#define __EXTENSIONS__ 1 +#endif +#include <stdlib.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_GETOPT +#include <getopt.h> +#endif +#include <re.h> +#include <baresip.h> + + +static void signal_handler(int sig) +{ + static bool term = false; + + if (term) + exit(0); + + term = true; + + info("terminated by signal %d\n", sig); + + ua_stop_all(false); +} + + +int main(int argc, char *argv[]) +{ + bool prefer_ipv6 = false, run_daemon = false; + const char *exec = NULL; + int err; + + (void)re_fprintf(stderr, "baresip v%s" + " Copyright (C) 2010 - 2014" + " Alfred E. Heggestad et al.\n", + BARESIP_VERSION); + + (void)sys_coredump_set(true); + +#ifdef HAVE_GETOPT + for (;;) { + const int c = getopt(argc, argv, "6de:f:hv"); + if (0 > c) + break; + + switch (c) { + + case '?': + case 'h': + (void)re_fprintf(stderr, + "Usage: baresip [options]\n" + "options:\n" +#if HAVE_INET6 + "\t-6 Prefer IPv6\n" +#endif + "\t-d Daemon\n" + "\t-e <commands> Exec commands\n" + "\t-f <path> Config path\n" + "\t-h -? Help\n" + "\t-v Verbose debug\n" + ); + return -2; + +#if HAVE_INET6 + case '6': + prefer_ipv6 = true; + break; +#endif + + case 'd': + run_daemon = true; + break; + + case 'e': + exec = optarg; + break; + + case 'f': + conf_path_set(optarg); + break; + + case 'v': + log_enable_debug(true); + break; + + default: + break; + } + } +#else + (void)argc; + (void)argv; +#endif + + err = libre_init(); + if (err) + goto out; + + err = conf_configure(); + if (err) { + warning("main: configure failed: %m\n", err); + goto out; + } + + /* Initialise User Agents */ + err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")", + true, true, true, prefer_ipv6); + if (err) + goto out; + + /* Load modules */ + err = conf_modules(); + if (err) + goto out; + + if (run_daemon) { + err = sys_daemon(); + if (err) + goto out; + + log_enable_stderr(false); + } + + info("baresip is ready.\n"); + + if (exec) + ui_input_str(exec); + + /* Main loop */ + err = re_main(signal_handler); + + out: + if (err) + ua_stop_all(true); + + ua_close(); + mod_close(); + + libre_close(); + + /* Check for memory leaks */ + tmr_debug(); + mem_debug(); + + return err; +} diff --git a/src/mctrl.c b/src/mctrl.c new file mode 100644 index 0000000..d3abab8 --- /dev/null +++ b/src/mctrl.c @@ -0,0 +1,44 @@ +/** + * @file mctrl.c Media Control + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/* + * RFC 5168 XML Schema for Media Control + * note: deprecated, use RTCP FIR instead + * + * + * Example XML Document: + * + * <pre> + + <?xml version="1.0" encoding="utf-8"?> + <media_control> + <vc_primitive> + <to_encoder> + <picture_fast_update> + </picture_fast_update> + </to_encoder> + </vc_primitive> + </media_control> + + </pre> + */ +int mctrl_handle_media_control(struct pl *body, bool *pfu) +{ + if (!body) + return EINVAL; + + /* XXX: Poor-mans XML parsing (use xml-parser instead) */ + if (0 == re_regex(body->p, body->l, "picture_fast_update")) { + if (pfu) + *pfu = true; + } + + return 0; +} diff --git a/src/menc.c b/src/menc.c new file mode 100644 index 0000000..a99a94a --- /dev/null +++ b/src/menc.c @@ -0,0 +1,63 @@ +/** + * @file menc.c Media encryption + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list mencl = LIST_INIT; + + +/** + * Register a new Media encryption module + * + * @param menc Media encryption module + */ +void menc_register(struct menc *menc) +{ + if (!menc) + return; + + list_append(&mencl, &menc->le, menc); + + info("mediaenc: %s\n", menc->id); +} + + +/** + * Unregister a Media encryption module + * + * @param menc Media encryption module + */ +void menc_unregister(struct menc *menc) +{ + if (!menc) + return; + + list_unlink(&menc->le); +} + + +/** + * Find a Media Encryption module by name + * + * @param id Name of the Media Encryption module to find + * + * @return Matching Media Encryption module if found, otherwise NULL + */ +const struct menc *menc_find(const char *id) +{ + struct le *le; + + for (le = mencl.head; le; le = le->next) { + struct menc *me = le->data; + + if (0 == str_casecmp(id, me->id)) + return me; + } + + return NULL; +} diff --git a/src/message.c b/src/message.c new file mode 100644 index 0000000..b0e33e1 --- /dev/null +++ b/src/message.c @@ -0,0 +1,138 @@ +/** + * @file message.c SIP MESSAGE -- RFC 3428 + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct sip_lsnr *lsnr; +static message_recv_h *recvh; +static void *recvarg; + + +static void handle_message(struct ua *ua, const struct sip_msg *msg) +{ + static const char *ctype_text = "text/plain"; + struct pl mtype; + (void)ua; + + if (re_regex(msg->ctype.p, msg->ctype.l, "[^;]+", &mtype)) + mtype = msg->ctype; + + if (0==pl_strcasecmp(&mtype, ctype_text) && recvh) { + recvh(&msg->from.auri, &msg->ctype, msg->mb, recvarg); + (void)sip_reply(uag_sip(), msg, 200, "OK"); + } + else { + (void)sip_replyf(uag_sip(), msg, 415, "Unsupported Media Type", + "Accept: %s\r\n" + "Content-Length: 0\r\n" + "\r\n", + ctype_text); + } +} + + +static bool request_handler(const struct sip_msg *msg, void *arg) +{ + struct ua *ua; + + (void)arg; + + if (pl_strcmp(&msg->met, "MESSAGE")) + return false; + + ua = uag_find(&msg->uri.user); + if (!ua) { + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return true; + } + + handle_message(ua, msg); + + return true; +} + + +static void resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct ua *ua = arg; + + (void)ua; + + if (err) { + (void)re_fprintf(stderr, " \x1b[31m%m\x1b[;m\n", err); + return; + } + + if (msg->scode >= 300) { + (void)re_fprintf(stderr, " \x1b[31m%u %r\x1b[;m\n", + msg->scode, &msg->reason); + } +} + + +int message_init(message_recv_h *h, void *arg) +{ + int err; + + err = sip_listen(&lsnr, uag_sip(), true, request_handler, NULL); + if (err) + return err; + + recvh = h; + recvarg = arg; + + return 0; +} + + +void message_close(void) +{ + lsnr = mem_deref(lsnr); +} + + +/** + * Send SIP instant MESSAGE to a peer + * + * @param ua User-Agent object + * @param peer Peer SIP Address + * @param msg Message to send + * + * @return 0 if success, otherwise errorcode + */ +int message_send(struct ua *ua, const char *peer, const char *msg) +{ + struct sip_addr addr; + struct pl pl; + char *uri = NULL; + int err = 0; + + if (!ua || !peer || !msg) + return EINVAL; + + pl_set_str(&pl, peer); + + err = sip_addr_decode(&addr, &pl); + if (err) + return err; + + err = pl_strdup(&uri, &addr.auri); + if (err) + return err; + + err = sip_req_send(ua, "MESSAGE", uri, resp_handler, ua, + "Accept: text/plain\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %zu\r\n" + "\r\n%s", + str_len(msg), msg); + + mem_deref(uri); + + return err; +} diff --git a/src/metric.c b/src/metric.c new file mode 100644 index 0000000..8cc0b18 --- /dev/null +++ b/src/metric.c @@ -0,0 +1,79 @@ +/** + * @file metric.c Metrics for media transmit/receive + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +enum {TMR_INTERVAL = 3}; +static void tmr_handler(void *arg) +{ + struct metric *metric = arg; + const uint64_t now = tmr_jiffies(); + uint32_t diff; + + tmr_start(&metric->tmr, TMR_INTERVAL * 1000, tmr_handler, metric); + + if (now <= metric->ts_last) + return; + + if (metric->ts_last) { + uint32_t bytes = metric->n_bytes - metric->n_bytes_last; + diff = (uint32_t)(now - metric->ts_last); + metric->cur_bitrate = 1000 * 8 * bytes / diff; + } + + /* Update counters */ + metric->ts_last = now; + metric->n_bytes_last = metric->n_bytes; +} + + +static void metric_start(struct metric *metric) +{ + if (metric->started) + return; + + metric->ts_start = tmr_jiffies(); + tmr_start(&metric->tmr, 1, tmr_handler, metric); + + metric->started = true; +} + + +void metric_reset(struct metric *metric) +{ + if (!metric) + return; + + tmr_cancel(&metric->tmr); +} + + +void metric_add_packet(struct metric *metric, size_t packetsize) +{ + if (!metric) + return; + + if (!metric->started) + metric_start(metric); + + metric->n_bytes += (uint32_t)packetsize; + metric->n_packets++; +} + + +uint32_t metric_avg_bitrate(const struct metric *metric) +{ + int diff; + + if (!metric || !metric->ts_start) + return 0; + + diff = (int)(tmr_jiffies() - metric->ts_start); + + return 1000 * 8 * metric->n_bytes / diff; +} diff --git a/src/mnat.c b/src/mnat.c new file mode 100644 index 0000000..416e93b --- /dev/null +++ b/src/mnat.c @@ -0,0 +1,87 @@ +/** + * @file mnat.c Media NAT + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list mnatl = LIST_INIT; + + +static void destructor(void *arg) +{ + struct mnat *mnat = arg; + + list_unlink(&mnat->le); +} + + +/** + * Register a Media NAT traversal module + * + * @param mnatp Pointer to allocated Media NAT traversal module + * @param id Media NAT Identifier + * @param ftag SIP Feature tag (optional) + * @param sessh Session allocation handler + * @param mediah Media allocation handler + * @param updateh Update handler + * + * @return 0 if success, otherwise errorcode + */ +int mnat_register(struct mnat **mnatp, const char *id, const char *ftag, + mnat_sess_h *sessh, mnat_media_h *mediah, + mnat_update_h *updateh) +{ + struct mnat *mnat; + + if (!mnatp || !id || !sessh || !mediah) + return EINVAL; + + mnat = mem_zalloc(sizeof(*mnat), destructor); + if (!mnat) + return ENOMEM; + + list_append(&mnatl, &mnat->le, mnat); + + mnat->id = id; + mnat->ftag = ftag; + mnat->sessh = sessh; + mnat->mediah = mediah; + mnat->updateh = updateh; + + info("medianat: %s\n", id); + + *mnatp = mnat; + + return 0; +} + + +/** + * Find a Media NAT module by name + * + * @param id Name of the Media NAT module to find + * + * @return Matching Media NAT module if found, otherwise NULL + */ +const struct mnat *mnat_find(const char *id) +{ + struct mnat *mnat; + struct le *le; + + for (le=mnatl.head; le; le=le->next) { + + mnat = le->data; + + if (str_casecmp(mnat->id, id)) + continue; + + return mnat; + } + + return NULL; +} diff --git a/src/module.c b/src/module.c new file mode 100644 index 0000000..12b3d13 --- /dev/null +++ b/src/module.c @@ -0,0 +1,156 @@ +/** + * @file module.c Module loading + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +struct modapp { + struct mod *mod; + struct le le; +}; + + +static struct list modappl; + + +static void modapp_destructor(void *arg) +{ + struct modapp *modapp = arg; + list_unlink(&modapp->le); + mem_deref(modapp->mod); +} + + +#ifdef STATIC + +/* Declared in static.c */ +extern const struct mod_export *mod_table[]; + +static const struct mod_export *find_module(const struct pl *pl) +{ + struct pl name; + uint32_t i; + + if (re_regex(pl->p, pl->l, "[^.]+.[^]*", &name, NULL)) + name = *pl; + + for (i=0; ; i++) { + const struct mod_export *me = mod_table[i]; + if (!me) + return NULL; + if (0 == pl_strcasecmp(&name, me->name)) + return me; + } + + return NULL; +} +#endif + + +static int load_module(struct mod **modp, const struct pl *modpath, + const struct pl *name) +{ + char file[256]; + struct mod *m = NULL; + int err = 0; + + if (!name) + return EINVAL; + +#ifdef STATIC + /* Try static first */ + err = mod_add(&m, find_module(name)); + if (!err) + goto out; +#endif + + /* Then dynamic */ + if (re_snprintf(file, sizeof(file), "%r/%r", modpath, name) < 0) { + err = ENOMEM; + goto out; + } + err = mod_load(&m, file); + if (err) + goto out; + + out: + if (err) { + warning("module %r: %m\n", name, err); + } + else if (modp) + *modp = m; + + return err; +} + + +static int module_handler(const struct pl *val, void *arg) +{ + (void)load_module(NULL, arg, val); + return 0; +} + + +static int module_tmp_handler(const struct pl *val, void *arg) +{ + struct mod *mod = NULL; + (void)load_module(&mod, arg, val); + mem_deref(mod); + return 0; +} + + +static int module_app_handler(const struct pl *val, void *arg) +{ + struct modapp *modapp; + + modapp = mem_zalloc(sizeof(*modapp), modapp_destructor); + if (!modapp) + return ENOMEM; + + if (load_module(&modapp->mod, arg, val)) { + mem_deref(modapp); + return 0; + } + + list_prepend(&modappl, &modapp->le, modapp); + + return 0; +} + + +int module_init(const struct conf *conf) +{ + struct pl path; + int err; + + if (!conf) + return EINVAL; + + if (conf_get(conf, "module_path", &path)) + pl_set_str(&path, "."); + + err = conf_apply(conf, "module", module_handler, &path); + if (err) + return err; + + err = conf_apply(conf, "module_tmp", module_tmp_handler, &path); + if (err) + return err; + + err = conf_apply(conf, "module_app", module_app_handler, &path); + if (err) + return err; + + return 0; +} + + +void module_app_unload(void) +{ + list_flush(&modappl); +} diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..4829a5a --- /dev/null +++ b/src/net.c @@ -0,0 +1,439 @@ +/** + * @file net.c Networking code + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct { + struct config_net cfg; + struct sa laddr; + char ifname[16]; +#ifdef HAVE_INET6 + struct sa laddr6; + char ifname6[16]; +#endif + struct tmr tmr; + struct dnsc *dnsc; + struct sa nsv[4]; /**< Configured name servers */ + uint32_t nsn; /**< Number of configured name servers */ + uint32_t interval; + int af; /**< Preferred address family */ + char domain[64]; /**< DNS domain from network */ + net_change_h *ch; + void *arg; +} net; + + +/** + * Check for DNS Server updates + */ +static void dns_refresh(void) +{ + struct sa nsv[8]; + uint32_t i, nsn; + int err; + + nsn = ARRAY_SIZE(nsv); + + err = dns_srv_get(NULL, 0, nsv, &nsn); + if (err) + return; + + for (i=0; i<net.nsn; i++) + sa_cpy(&nsv[nsn++], &net.nsv[i]); + + (void)dnsc_srv_set(net.dnsc, nsv, nsn); +} + + +/** + * Detect changes in IP address(es) + */ +static void ipchange_handler(void *arg) +{ + bool change; + (void)arg; + + tmr_start(&net.tmr, net.interval * 1000, ipchange_handler, NULL); + + dns_refresh(); + + change = net_check(); + if (change && net.ch) { + net.ch(net.arg); + } +} + + +/** + * Check if local IP address(es) changed + * + * @return True if changed, otherwise false + */ +bool net_check(void) +{ + struct sa laddr = net.laddr; +#ifdef HAVE_INET6 + struct sa laddr6 = net.laddr6; +#endif + bool change = false; + + if (str_isset(net.cfg.ifname)) { + + (void)net_if_getaddr(net.cfg.ifname, AF_INET, &net.laddr); + +#ifdef HAVE_INET6 + (void)net_if_getaddr(net.cfg.ifname, AF_INET6, &net.laddr6); +#endif + } + else { + (void)net_default_source_addr_get(AF_INET, &net.laddr); + (void)net_rt_default_get(AF_INET, net.ifname, + sizeof(net.ifname)); + +#ifdef HAVE_INET6 + (void)net_default_source_addr_get(AF_INET6, &net.laddr6); + (void)net_rt_default_get(AF_INET6, net.ifname6, + sizeof(net.ifname6)); +#endif + } + + if (sa_isset(&net.laddr, SA_ADDR) && + !sa_cmp(&laddr, &net.laddr, SA_ADDR)) { + change = true; + info("net: local IPv4 address changed: %j -> %j\n", + &laddr, &net.laddr); + } + +#ifdef HAVE_INET6 + if (sa_isset(&net.laddr6, SA_ADDR) && + !sa_cmp(&laddr6, &net.laddr6, SA_ADDR)) { + change = true; + info("net: local IPv6 address changed: %j -> %j\n", + &laddr6, &net.laddr6); + } +#endif + + return change; +} + + +static int dns_init(void) +{ + struct sa nsv[8]; + uint32_t i, nsn; + int err; + + nsn = ARRAY_SIZE(nsv); + + err = dns_srv_get(net.domain, sizeof(net.domain), nsv, &nsn); + if (err) { + nsn = 0; + } + + /* Add any configured nameservers */ + for (i=0; i<net.nsn && nsn < ARRAY_SIZE(nsv); i++) + sa_cpy(&nsv[nsn++], &net.nsv[i]); + + return dnsc_alloc(&net.dnsc, NULL, nsv, nsn); +} + + +/** + * Return TRUE if libre supports IPv6 + */ +static bool check_ipv6(void) +{ + struct sa sa; + + return 0 == sa_set_str(&sa, "::1", 2000); +} + + +/** + * Initialise networking + * + * @param cfg Network configuration + * @param af Preferred address family + * + * @return 0 if success, otherwise errorcode + */ +int net_init(const struct config_net *cfg, int af) +{ + int err; + + if (!cfg) + return EINVAL; + + /* + * baresip/libre must be built with matching HAVE_INET6 value. + * if different the size of `struct sa' will not match and the + * application is very likely to crash. + */ +#ifdef HAVE_INET6 + if (!check_ipv6()) { + error("libre was compiled without IPv6-support" + ", but baresip was compiled with\n"); + return EAFNOSUPPORT; + } +#else + if (check_ipv6()) { + error("libre was compiled with IPv6-support" + ", but baresip was compiled without\n"); + return EAFNOSUPPORT; + } +#endif + + net.cfg = *cfg; + net.af = af; + + tmr_init(&net.tmr); + + /* Initialise DNS resolver */ + err = dns_init(); + if (err) { + warning("net: dns_init: %m\n", err); + return err; + } + + sa_init(&net.laddr, AF_INET); + (void)sa_set_str(&net.laddr, "127.0.0.1", 0); + + if (str_isset(cfg->ifname)) { + + bool got_it = false; + + info("Binding to interface '%s'\n", cfg->ifname); + + str_ncpy(net.ifname, cfg->ifname, sizeof(net.ifname)); + + err = net_if_getaddr(cfg->ifname, + AF_INET, &net.laddr); + if (err) { + info("net: %s: could not get IPv4 address (%m)\n", + cfg->ifname, err); + } + else + got_it = true; + +#ifdef HAVE_INET6 + str_ncpy(net.ifname6, cfg->ifname, + sizeof(net.ifname6)); + + err = net_if_getaddr(cfg->ifname, + AF_INET6, &net.laddr6); + if (err) { + info("net: %s: could not get IPv6 address (%m)\n", + cfg->ifname, err); + } + else + got_it = true; +#endif + if (got_it) + err = 0; + else { + warning("net: %s: could not get network address\n", + cfg->ifname); + return EADDRNOTAVAIL; + } + } + else { + (void)net_default_source_addr_get(AF_INET, &net.laddr); + (void)net_rt_default_get(AF_INET, net.ifname, + sizeof(net.ifname)); + +#ifdef HAVE_INET6 + sa_init(&net.laddr6, AF_INET6); + + (void)net_default_source_addr_get(AF_INET6, &net.laddr6); + (void)net_rt_default_get(AF_INET6, net.ifname6, + sizeof(net.ifname6)); +#endif + } + + (void)re_fprintf(stderr, "Local network address:"); + + if (sa_isset(&net.laddr, SA_ADDR)) { + (void)re_fprintf(stderr, " IPv4=%s:%j", + net.ifname, &net.laddr); + } +#ifdef HAVE_INET6 + if (sa_isset(&net.laddr6, SA_ADDR)) { + (void)re_fprintf(stderr, " IPv6=%s:%j", + net.ifname6, &net.laddr6); + } +#endif + (void)re_fprintf(stderr, "\n"); + + return err; +} + + +/** + * Reset the DNS resolver + * + * @return 0 if success, otherwise errorcode + */ +int net_reset(void) +{ + net.dnsc = mem_deref(net.dnsc); + + return dns_init(); +} + + +/** + * Close networking + */ +void net_close(void) +{ + net.dnsc = mem_deref(net.dnsc); + tmr_cancel(&net.tmr); +} + + +/** + * Add a DNS server + * + * @param sa DNS Server IP address and port + * + * @return 0 if success, otherwise errorcode + */ +int net_dnssrv_add(const struct sa *sa) +{ + if (net.nsn >= ARRAY_SIZE(net.nsv)) + return E2BIG; + + sa_cpy(&net.nsv[net.nsn++], sa); + + return 0; +} + + +/** + * Check for networking changes with a regular interval + * + * @param interval Interval in seconds + * @param ch Handler called when a change was detected + * @param arg Handler argument + */ +void net_change(uint32_t interval, net_change_h *ch, void *arg) +{ + net.interval = interval; + net.ch = ch; + net.arg = arg; + + if (interval) + tmr_start(&net.tmr, interval * 1000, ipchange_handler, NULL); + else + tmr_cancel(&net.tmr); +} + + +static int dns_debug(struct re_printf *pf, void *unused) +{ + struct sa nsv[4]; + uint32_t i, nsn; + int err; + + (void)unused; + + nsn = ARRAY_SIZE(nsv); + + err = dns_srv_get(NULL, 0, nsv, &nsn); + if (err) + nsn = 0; + + err = re_hprintf(pf, " DNS Servers: (%u)\n", nsn); + for (i=0; i<nsn; i++) + err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]); + for (i=0; i<net.nsn; i++) + err |= re_hprintf(pf, " %u: %J\n", nsn+i, &net.nsv[i]); + + return err; +} + + +int net_af(void) +{ + return net.af; +} + + +/** + * Print networking debug information + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +int net_debug(struct re_printf *pf, void *unused) +{ + int err; + + (void)unused; + + err = re_hprintf(pf, "--- Network debug ---\n"); + err |= re_hprintf(pf, " Preferred AF: %s\n", net_af2name(net.af)); + err |= re_hprintf(pf, " Local IPv4: %9s - %j\n", + net.ifname, &net.laddr); +#ifdef HAVE_INET6 + err |= re_hprintf(pf, " Local IPv6: %9s - %j\n", + net.ifname6, &net.laddr6); +#endif + + err |= net_if_debug(pf, NULL); + + err |= net_rt_debug(pf, NULL); + + err |= dns_debug(pf, NULL); + + return err; +} + + +/** + * Get the local IP Address for a specific Address Family (AF) + * + * @param af Address Family + * + * @return Local IP Address + */ +const struct sa *net_laddr_af(int af) +{ + switch (af) { + + case AF_INET: return &net.laddr; +#ifdef HAVE_INET6 + case AF_INET6: return &net.laddr6; +#endif + default: return NULL; + } +} + + +/** + * Get the DNS Client + * + * @return DNS Client + */ +struct dnsc *net_dnsc(void) +{ + return net.dnsc; +} + + +/** + * Get the network domain name + * + * @return Network domain + */ +const char *net_domain(void) +{ + return net.domain[0] ? net.domain : NULL; +} diff --git a/src/play.c b/src/play.c new file mode 100644 index 0000000..9ea8788 --- /dev/null +++ b/src/play.c @@ -0,0 +1,317 @@ +/** + * @file src/play.c Audio-file player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "core.h" + + +enum {SILENCE_DUR = 2000, PTIME = 100}; + +/** Audio file player */ +struct play { + struct le le; + struct play **playp; + struct lock *lock; + struct mbuf *mb; + struct auplay_st *auplay; + struct tmr tmr; + int repeat; + bool eof; +}; + + +#ifndef PREFIX +#define PREFIX "/usr" +#endif +static char play_path[256] = PREFIX "/share/baresip"; +static struct list playl; + + +static void tmr_polling(void *arg); + + +static void tmr_stop(void *arg) +{ + struct play *play = arg; + mem_deref(play); +} + + +static void tmr_repeat(void *arg) +{ + struct play *play = arg; + + lock_write_get(play->lock); + + play->mb->pos = 0; + play->eof = false; + + tmr_start(&play->tmr, 1000, tmr_polling, arg); + + lock_rel(play->lock); +} + + +static void tmr_polling(void *arg) +{ + struct play *play = arg; + + lock_write_get(play->lock); + + tmr_start(&play->tmr, 1000, tmr_polling, arg); + + if (play->eof) { + if (play->repeat > 0) + play->repeat--; + + if (play->repeat == 0) + tmr_start(&play->tmr, 1, tmr_stop, arg); + else + tmr_start(&play->tmr, SILENCE_DUR, tmr_repeat, arg); + } + + lock_rel(play->lock); +} + + +/** + * NOTE: DSP cannot be destroyed inside handler + */ +static bool write_handler(uint8_t *buf, size_t sz, void *arg) +{ + struct play *play = arg; + + lock_write_get(play->lock); + + if (play->eof) + goto silence; + + if (mbuf_get_left(play->mb) < sz) { + play->eof = true; + } + else { + (void)mbuf_read_mem(play->mb, buf, sz); + } + + silence: + if (play->eof) + memset(buf, 0, sz); + + lock_rel(play->lock); + + return true; +} + + +static void destructor(void *arg) +{ + struct play *play = arg; + + list_unlink(&play->le); + tmr_cancel(&play->tmr); + + lock_write_get(play->lock); + play->eof = true; + lock_rel(play->lock); + + mem_deref(play->auplay); + mem_deref(play->mb); + mem_deref(play->lock); + + if (play->playp) + *play->playp = NULL; +} + + +static int aufile_load(struct mbuf *mb, const char *filename, + uint32_t *srate, uint8_t *channels) +{ + struct aufile_prm prm; + struct aufile *af; + int err; + + err = aufile_open(&af, &prm, filename, AUFILE_READ); + if (err) + return err; + + while (!err) { + uint8_t buf[4096]; + size_t i, n; + + n = sizeof(buf); + + err = aufile_read(af, buf, &n); + if (err || !n) + break; + + switch (prm.fmt) { + + case AUFMT_S16LE: + err = mbuf_write_mem(mb, buf, n); + break; + + case AUFMT_PCMA: + for (i=0; i<n; i++) { + err |= mbuf_write_u16(mb, + g711_alaw2pcm(buf[i])); + } + break; + + case AUFMT_PCMU: + for (i=0; i<n; i++) { + err |= mbuf_write_u16(mb, + g711_ulaw2pcm(buf[i])); + } + break; + + default: + err = ENOSYS; + break; + } + } + + mem_deref(af); + + if (!err) { + mb->pos = 0; + + *srate = prm.srate; + *channels = prm.channels; + } + + return err; +} + + +/** + * Play a tone from a PCM buffer + * + * @param playp Pointer to allocated player object + * @param tone PCM buffer to play + * @param srate Sampling rate + * @param ch Number of channels + * @param repeat Number of times to repeat + * + * @return 0 if success, otherwise errorcode + */ +int play_tone(struct play **playp, struct mbuf *tone, uint32_t srate, + uint8_t ch, int repeat) +{ + struct auplay_prm wprm; + struct play *play; + struct config *cfg; + int err; + + if (playp && *playp) + return EALREADY; + + cfg = conf_config(); + if (!cfg) + return ENOENT; + + play = mem_zalloc(sizeof(*play), destructor); + if (!play) + return ENOMEM; + + tmr_init(&play->tmr); + play->repeat = repeat; + play->mb = mem_ref(tone); + + err = lock_alloc(&play->lock); + if (err) + goto out; + + wprm.fmt = AUFMT_S16LE; + wprm.ch = ch; + wprm.srate = srate; + wprm.ptime = PTIME; + + err = auplay_alloc(&play->auplay, cfg->audio.alert_mod, &wprm, + cfg->audio.alert_dev, write_handler, play); + if (err) + goto out; + + list_append(&playl, &play->le, play); + tmr_start(&play->tmr, 1000, tmr_polling, play); + + out: + if (err) { + mem_deref(play); + } + else if (playp) { + play->playp = playp; + *playp = play; + } + + return err; +} + + +/** + * Play an audio file in WAV format + * + * @param playp Pointer to allocated player object + * @param filename Name of WAV file to play + * @param repeat Number of times to repeat + * + * @return 0 if success, otherwise errorcode + */ +int play_file(struct play **playp, const char *filename, int repeat) +{ + struct mbuf *mb; + char path[512]; + uint32_t srate; + uint8_t ch; + int err; + + if (playp && *playp) + return EALREADY; + + if (re_snprintf(path, sizeof(path), "%s/%s", + play_path, filename) < 0) + return ENOMEM; + + mb = mbuf_alloc(1024); + if (!mb) + return ENOMEM; + + err = aufile_load(mb, path, &srate, &ch); + if (err) { + warning("play: %s: %m\n", path, err); + goto out; + } + + err = play_tone(playp, mb, srate, ch, repeat); + + out: + mem_deref(mb); + + return err; +} + + +void play_init(void) +{ + list_init(&playl); +} + + +/** + * Close all active audio players + */ +void play_close(void) +{ + list_flush(&playl); +} + + +void play_set_path(const char *path) +{ + str_ncpy(play_path, path, sizeof(play_path)); +} diff --git a/src/realtime.c b/src/realtime.c new file mode 100644 index 0000000..a144e22 --- /dev/null +++ b/src/realtime.c @@ -0,0 +1,100 @@ +/** + * @file realtime.c Real-Time scheduling + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#ifdef DARWIN +#include <sys/types.h> +#include <sys/sysctl.h> +#include <stdio.h> +#include <mach/mach.h> +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif +#endif + + +#ifdef DARWIN +static int set_realtime(int period, int computation, int constraint) +{ + struct thread_time_constraint_policy ttcpolicy; + int ret; + + ttcpolicy.period = period; /* HZ/160 */ + ttcpolicy.computation = computation; /* HZ/3300 */ + ttcpolicy.constraint = constraint; /* HZ/2200 */ + ttcpolicy.preemptible = 1; + + ret = thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret != KERN_SUCCESS) + return ENOSYS; + + return 0; +} +#endif + + +/** + * Enable real-time scheduling (for selected platforms) + * + * @param enable True to enable, false to disable + * @param fps Wanted video framerate + * + * @return 0 if success, otherwise errorcode + */ +int realtime_enable(bool enable, int fps) +{ +#ifdef DARWIN + if (enable) { +#if TARGET_OS_IPHONE + int bus_speed = 100000000; +#else + int ret, bus_speed; + int mib[2] = { CTL_HW, HW_BUS_FREQ }; + size_t len; + + len = sizeof(bus_speed); + ret = sysctl (mib, 2, &bus_speed, &len, NULL, 0); + if (ret < 0) { + return ENOSYS; + } + + info("realtime: fps=%d bus_speed=%d\n", fps, bus_speed); +#endif + + return set_realtime(bus_speed / fps, + bus_speed / 3300, bus_speed / 2200); + } + else { + kern_return_t ret; + thread_standard_policy_data_t pt; + mach_msg_type_number_t cnt = THREAD_STANDARD_POLICY_COUNT; + boolean_t get_default = TRUE; + + ret = thread_policy_get(mach_thread_self(), + THREAD_STANDARD_POLICY, + (thread_policy_t)&pt, + &cnt, &get_default); + if (KERN_SUCCESS != ret) + return ENOSYS; + + ret = thread_policy_set(mach_thread_self(), + THREAD_STANDARD_POLICY, + (thread_policy_t)&pt, + THREAD_STANDARD_POLICY_COUNT); + if (KERN_SUCCESS != ret) + return ENOSYS; + + return 0; + } +#else + (void)enable; + (void)fps; + return ENOSYS; +#endif +} diff --git a/src/reg.c b/src/reg.c new file mode 100644 index 0000000..2588873 --- /dev/null +++ b/src/reg.c @@ -0,0 +1,269 @@ +/** + * @file reg.c Register Client + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** Register client */ +struct reg { + struct le le; /**< Linked list element */ + struct ua *ua; /**< Pointer to parent UA object */ + struct sipreg *sipreg; /**< SIP Register client */ + int id; /**< Registration ID (for SIP outbound) */ + + /* status: */ + uint16_t scode; /**< Registration status code */ + char *srv; /**< SIP Server id */ + int sipfd; /**< Cached file-descr. for SIP conn */ + int af; /**< Cached address family for SIP conn */ +}; + + +static void destructor(void *arg) +{ + struct reg *reg = arg; + + list_unlink(®->le); + mem_deref(reg->sipreg); + mem_deref(reg->srv); +} + + +static int sipmsg_fd(const struct sip_msg *msg) +{ + if (!msg) + return -1; + + switch (msg->tp) { + + case SIP_TRANSP_UDP: + return udp_sock_fd(msg->sock, AF_UNSPEC); + + case SIP_TRANSP_TCP: + case SIP_TRANSP_TLS: + return tcp_conn_fd(sip_msg_tcpconn(msg)); + + default: + return -1; + } +} + + +static int sipmsg_af(const struct sip_msg *msg) +{ + struct sa laddr; + int err = 0; + + if (!msg) + return AF_UNSPEC; + + switch (msg->tp) { + + case SIP_TRANSP_UDP: + err = udp_local_get(msg->sock, &laddr); + break; + + case SIP_TRANSP_TCP: + case SIP_TRANSP_TLS: + err = tcp_conn_local_get(sip_msg_tcpconn(msg), &laddr); + break; + + default: + return AF_UNSPEC; + } + + return err ? AF_UNSPEC : sa_af(&laddr); +} + + +static const char *af_name(int af) +{ + switch (af) { + + case AF_INET: return "v4"; + case AF_INET6: return "v6"; + default: return "v?"; + } +} + + +static int sip_auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + struct account *acc = arg; + return account_auth(acc, username, password, realm); +} + + +static void register_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct reg *reg = arg; + const struct sip_hdr *hdr; + + if (err) { + warning("reg: %s: Register: %m\n", ua_aor(reg->ua), err); + + reg->scode = 999; + + ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%m", err); + return; + } + + hdr = sip_msg_hdr(msg, SIP_HDR_SERVER); + if (hdr) { + reg->srv = mem_deref(reg->srv); + (void)pl_strdup(®->srv, &hdr->val); + } + + if (200 <= msg->scode && msg->scode <= 299) { + + uint32_t n_bindings; + + n_bindings = sip_msg_hdr_count(msg, SIP_HDR_CONTACT); + reg->sipfd = sipmsg_fd(msg); + reg->af = sipmsg_af(msg); + + if (msg->scode != reg->scode) { + ua_printf(reg->ua, "{%d/%s/%s} %u %r (%s)" + " [%u binding%s]\n", + reg->id, sip_transp_name(msg->tp), + af_name(reg->af), msg->scode, &msg->reason, + reg->srv, n_bindings, + 1==n_bindings?"":"s"); + } + + reg->scode = msg->scode; + + ua_event(reg->ua, UA_EVENT_REGISTER_OK, NULL, "%u %r", + msg->scode, &msg->reason); + } + else if (msg->scode >= 300) { + + warning("reg: %s: %u %r (%s)\n", ua_aor(reg->ua), + msg->scode, &msg->reason, reg->srv); + + reg->scode = msg->scode; + reg->sipfd = -1; + + ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%u %r", + msg->scode, &msg->reason); + } +} + + +int reg_add(struct list *lst, struct ua *ua, int regid) +{ + struct reg *reg; + + if (!lst || !ua) + return EINVAL; + + reg = mem_zalloc(sizeof(*reg), destructor); + if (!reg) + return ENOMEM; + + reg->ua = ua; + reg->id = regid; + reg->sipfd = -1; + + list_append(lst, ®->le, reg); + + return 0; +} + + +int reg_register(struct reg *reg, const char *reg_uri, const char *params, + uint32_t regint, const char *outbound) +{ + const char *routev[1]; + int err; + + if (!reg || !reg_uri) + return EINVAL; + + reg->scode = 0; + routev[0] = outbound; + + reg->sipreg = mem_deref(reg->sipreg); + err = sipreg_register(®->sipreg, uag_sip(), reg_uri, + ua_aor(reg->ua), ua_aor(reg->ua), + regint, ua_cuser(reg->ua), + routev[0] ? routev : NULL, + routev[0] ? 1 : 0, + reg->id, + sip_auth_handler, ua_prm(reg->ua), true, + register_handler, reg, + params[0] ? ¶ms[1] : NULL, + "Allow: %s\r\n", uag_allowed_methods()); + if (err) + return err; + + return 0; +} + + +void reg_unregister(struct reg *reg) +{ + if (!reg) + return; + + reg->scode = 0; + reg->sipfd = -1; + reg->af = 0; + + reg->sipreg = mem_deref(reg->sipreg); +} + + +bool reg_isok(const struct reg *reg) +{ + if (!reg) + return false; + + return 200 <= reg->scode && reg->scode <= 299; +} + + +int reg_sipfd(const struct reg *reg) +{ + return reg ? reg->sipfd : -1; +} + + +static const char *print_scode(uint16_t scode) +{ + if (0 == scode) return "\x1b[33m" "zzz" "\x1b[;m"; + else if (200 == scode) return "\x1b[32m" "OK " "\x1b[;m"; + else return "\x1b[31m" "ERR" "\x1b[;m"; +} + + +int reg_debug(struct re_printf *pf, const struct reg *reg) +{ + int err = 0; + + if (!reg) + return 0; + + err |= re_hprintf(pf, "\nRegister client:\n"); + err |= re_hprintf(pf, " id: %d\n", reg->id); + err |= re_hprintf(pf, " scode: %u (%s)\n", + reg->scode, print_scode(reg->scode)); + err |= re_hprintf(pf, " srv: %s\n", reg->srv); + err |= re_hprintf(pf, " sipfd: %d\n", reg->sipfd); + + return err; +} + + +int reg_status(struct re_printf *pf, const struct reg *reg) +{ + if (!reg) + return 0; + + return re_hprintf(pf, " %s %s", print_scode(reg->scode), reg->srv); +} diff --git a/src/rtpkeep.c b/src/rtpkeep.c new file mode 100644 index 0000000..7f32c41 --- /dev/null +++ b/src/rtpkeep.c @@ -0,0 +1,165 @@ +/** + * @file rtpkeep.c RTP Keepalive + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/* + * See draft-ietf-avt-app-rtp-keepalive: + * + * "zero" 4.1. Transport Packet of 0-byte + * "rtcp" 4.3. RTCP Packets Multiplexed with RTP Packets + * "stun" 4.4. STUN Indication Packet + * "dyna" 4.6. RTP Packet with Unknown Payload Type + */ + + +enum { + Tr_UDP = 15, + Tr_TCP = 7200 +}; + +/** RTP Keepalive */ +struct rtpkeep { + struct rtp_sock *rtp; + struct sdp_media *sdp; + struct tmr tmr; + char *method; + uint32_t ts; + bool flag; +}; + + +static void destructor(void *arg) +{ + struct rtpkeep *rk = arg; + + tmr_cancel(&rk->tmr); + mem_deref(rk->method); +} + + +static int send_keepalive(struct rtpkeep *rk) +{ + int err = 0; + + if (!str_casecmp(rk->method, "zero")) { + struct mbuf *mb = mbuf_alloc(1); + if (!mb) + return ENOMEM; + err = udp_send(rtp_sock(rk->rtp), + sdp_media_raddr(rk->sdp), mb); + mem_deref(mb); + } + else if (!str_casecmp(rk->method, "stun")) { + err = stun_indication(IPPROTO_UDP, rtp_sock(rk->rtp), + sdp_media_raddr(rk->sdp), 0, + STUN_METHOD_BINDING, NULL, 0, false, 0); + } + else if (!str_casecmp(rk->method, "dyna")) { + struct mbuf *mb = mbuf_alloc(RTP_HEADER_SIZE); + int pt = sdp_media_find_unused_pt(rk->sdp); + if (!mb) + return ENOMEM; + if (pt == -1) + return ENOENT; + mb->pos = mb->end = RTP_HEADER_SIZE; + + err = rtp_send(rk->rtp, sdp_media_raddr(rk->sdp), false, + pt, rk->ts, mb); + + mem_deref(mb); + } + else if (!str_casecmp(rk->method, "rtcp")) { + + if (sdp_media_rattr(rk->sdp, "rtcp-mux")) { + /* do nothing */ + ; + } + else { + warning("rtpkeep: rtcp-mux is disabled\n"); + } + } + else { + warning("rtpkeep: unknown method: %s\n", rk->method); + return ENOSYS; + } + + return err; +} + + +/** + * Logic: + * + * We check for RTP activity every 15 seconds, and clear the flag. + * The flag is set for every transmitted RTP packet. If the flag + * is not set, it means that we have not sent any RTP packet in the + * last period of 0 - 15 seconds. Start transmitting RTP keepalives + * now and every 15 seconds after that. + * + * @param arg Handler argument + */ +static void timeout(void *arg) +{ + struct rtpkeep *rk = arg; + int err; + + tmr_start(&rk->tmr, Tr_UDP * 1000, timeout, rk); + + if (rk->flag) { + rk->flag = false; + return; + } + + err = send_keepalive(rk); + if (err) { + warning("rtpkeep: send keepalive failed: %m\n", err); + } +} + + +int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto, + struct rtp_sock *rtp, struct sdp_media *sdp) +{ + struct rtpkeep *rk; + int err; + + if (!rkp || !method || proto != IPPROTO_UDP || !rtp || !sdp) + return EINVAL; + + rk = mem_zalloc(sizeof(*rk), destructor); + if (!rk) + return ENOMEM; + + rk->rtp = rtp; + rk->sdp = sdp; + + err = str_dup(&rk->method, method); + if (err) + goto out; + + tmr_start(&rk->tmr, 20, timeout, rk); + + out: + if (err) + mem_deref(rk); + else + *rkp = rk; + + return err; +} + + +void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts) +{ + if (!rk) + return; + + rk->ts = ts; + rk->flag = true; +} diff --git a/src/sdp.c b/src/sdp.c new file mode 100644 index 0000000..7a8ed29 --- /dev/null +++ b/src/sdp.c @@ -0,0 +1,186 @@ +/** + * @file src/sdp.c SDP functions + * + * Copyright (C) 2011 Creytiv.com + */ +#include <stdlib.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +uint32_t sdp_media_rattr_u32(const struct sdp_media *m, const char *name) +{ + const char *attr = sdp_media_rattr(m, name); + return attr ? atoi(attr) : 0; +} + + +/* + * Get a remote attribute from the SDP. Try the media-level first, + * and if it does not exist then try session-level. + */ +const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m, + const char *name) +{ + const char *x; + + x = sdp_media_rattr(m, name); + if (x) + return x; + + x = sdp_session_rattr(s, name); + if (x) + return x; + + return NULL; +} + + +/* RFC 4572 */ +int sdp_fingerprint_decode(const char *attr, struct pl *hash, + uint8_t *md, size_t *sz) +{ + struct pl f; + const char *p; + int err; + + if (!attr || !hash) + return EINVAL; + + err = re_regex(attr, str_len(attr), "[^ ]+ [0-9A-F:]+", hash, &f); + if (err) + return err; + + if (md && sz) { + if (*sz < (f.l+1)/3) + return EOVERFLOW; + + for (p = f.p; p < (f.p+f.l); p += 3) { + *md++ = ch_hex(p[0]) << 4 | ch_hex(p[1]); + } + + *sz = (f.l+1)/3; + } + + return 0; +} + + +bool sdp_media_has_media(const struct sdp_media *m) +{ + bool has; + + has = sdp_media_rformat(m, NULL) != NULL; + if (has) + return sdp_media_rport(m) != 0; + + return false; +} + + +/** + * Find a dynamic payload type that is not used + * + * @param m SDP Media + * + * @return Unused payload type, -1 if no found + */ +int sdp_media_find_unused_pt(const struct sdp_media *m) +{ + int pt; + + for (pt = PT_DYN_MAX; pt>=PT_DYN_MIN; pt--) { + + if (!sdp_media_format(m, false, NULL, pt, NULL, -1, -1)) + return pt; + } + + return -1; +} + + +const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m) +{ + struct sdp_format *sf; + struct list *lst; + + again: + sf = (struct sdp_format *)sdp_media_rformat(m, NULL); + if (!sf) + return NULL; + + lst = sf->le.list; + + /* move top-most codec to end of list */ + list_unlink(&sf->le); + list_append(lst, &sf->le, sf); + + sf = (struct sdp_format *)sdp_media_rformat(m, NULL); + if (!str_casecmp(sf->name, telev_rtpfmt)) + goto again; + + return sf; +} + + +static void decode_part(const struct pl *part, struct mbuf *mb) +{ + struct pl hdrs, body; + + if (re_regex(part->p, part->l, "\r\n\r\n[^]+", &body)) + return; + + hdrs.p = part->p; + hdrs.l = body.p - part->p - 2; + + if (0 == re_regex(hdrs.p, hdrs.l, "application/sdp")) { + + mb->pos += (body.p - (char *)mbuf_buf(mb)); + mb->end = mb->pos + body.l; + } +} + + +/** + * Decode a multipart/mixed message and find the part with application/sdp + */ +int sdp_decode_multipart(const struct pl *ctype, struct mbuf *mb) +{ + struct pl bnd, s, e, p; + char expr[64]; + int err; + + if (!ctype || !mb) + return EINVAL; + + /* fetch the boundary tag, excluding quotes */ + err = re_regex(ctype->p, ctype->l, + "multipart/mixed;[ \t]*boundary=[~]+", NULL, &bnd); + if (err) + return err; + + if (re_snprintf(expr, sizeof(expr), "--%r[^]+", &bnd) < 0) + return ENOMEM; + + /* find 1st boundary */ + err = re_regex((char *)mbuf_buf(mb), mbuf_get_left(mb), expr, &s); + if (err) + return err; + + /* iterate over each part */ + while (s.l > 2) { + if (re_regex(s.p, s.l, expr, &e)) + return 0; + + p.p = s.p + 2; + p.l = e.p - p.p - bnd.l - 2; + + /* valid part in "p" */ + decode_part(&p, mb); + + s = e; + } + + return 0; +} diff --git a/src/sipreq.c b/src/sipreq.c new file mode 100644 index 0000000..1518645 --- /dev/null +++ b/src/sipreq.c @@ -0,0 +1,149 @@ +/** + * @file sipreq.c SIP Authenticated Request + * + * Copyright (C) 2011 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** SIP Authenticated Request */ +struct sip_req { + struct sip_loopstate ls; + struct sip_dialog *dlg; + struct sip_auth *auth; + struct sip_request *req; + char *method; + char *fmt; + sip_resp_h *resph; + void *arg; +}; + + +static int request(struct sip_req *sr); + + +static void destructor(void *arg) +{ + struct sip_req *sr = arg; + + mem_deref(sr->req); + mem_deref(sr->auth); + mem_deref(sr->dlg); + mem_deref(sr->method); + mem_deref(sr->fmt); +} + + +static void resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct sip_req *sr = arg; + + if (err || sip_request_loops(&sr->ls, msg->scode)) + goto out; + + if (msg->scode < 200) { + return; + } + else if (msg->scode < 300) { + ; + } + else { + switch (msg->scode) { + + case 401: + case 407: + err = sip_auth_authenticate(sr->auth, msg); + if (err) { + err = (err == EAUTH) ? 0 : err; + break; + } + + err = request(sr); + if (err) + break; + + return; + + case 403: + sip_auth_reset(sr->auth); + break; + } + } + + out: + sr->resph(err, msg, sr->arg); + + /* destroy now */ + mem_deref(sr); +} + + +static int auth_handler(char **username, char **password, + const char *realm, void *arg) +{ + struct account *acc = arg; + + return account_auth(acc, username, password, realm); +} + + +static int request(struct sip_req *sr) +{ + return sip_drequestf(&sr->req, uag_sip(), true, sr->method, sr->dlg, + 0, sr->auth, NULL, resp_handler, + sr, sr->fmt ? "%s" : NULL, sr->fmt); +} + + +int sip_req_send(struct ua *ua, const char *method, const char *uri, + sip_resp_h *resph, void *arg, const char *fmt, ...) +{ + const char *routev[1]; + struct sip_req *sr; + int err; + + if (!ua || !method || !uri || !resph || !fmt) + return EINVAL; + + routev[0] = ua_outbound(ua); + + sr = mem_zalloc(sizeof(*sr), destructor); + if (!sr) + return ENOMEM; + + sr->resph = resph; + sr->arg = arg; + + err = str_dup(&sr->method, method); + + if (fmt) { + va_list ap; + + va_start(ap, fmt); + err |= re_vsdprintf(&sr->fmt, fmt, ap); + va_end(ap); + } + + if (err) + goto out; + + err = sip_dialog_alloc(&sr->dlg, uri, uri, NULL, ua_aor(ua), + routev[0] ? routev : NULL, + routev[0] ? 1 : 0); + if (err) + goto out; + + err = sip_auth_alloc(&sr->auth, auth_handler, ua_prm(ua), true); + if (err) + goto out; + + err = request(sr); + + out: + if (err) + mem_deref(sr); + + return err; +} diff --git a/src/srcs.mk b/src/srcs.mk new file mode 100644 index 0000000..d3aa765 --- /dev/null +++ b/src/srcs.mk @@ -0,0 +1,49 @@ +# +# srcs.mk All application source files. +# +# Copyright (C) 2010 Creytiv.com +# + +SRCS += account.c +SRCS += aucodec.c +SRCS += audio.c +SRCS += aufilt.c +SRCS += auplay.c +SRCS += ausrc.c +SRCS += call.c +SRCS += cmd.c +SRCS += conf.c +SRCS += config.c +SRCS += contact.c +SRCS += log.c +SRCS += menc.c +SRCS += message.c +SRCS += metric.c +SRCS += mnat.c +SRCS += module.c +SRCS += net.c +SRCS += play.c +SRCS += realtime.c +SRCS += reg.c +SRCS += rtpkeep.c +SRCS += sdp.c +SRCS += sipreq.c +SRCS += stream.c +SRCS += ua.c +SRCS += ui.c + +ifneq ($(USE_VIDEO),) +SRCS += bfcp.c +SRCS += mctrl.c +SRCS += video.c +SRCS += vidcodec.c +SRCS += vidfilt.c +SRCS += vidisp.c +SRCS += vidsrc.c +endif + +ifneq ($(STATIC),) +SRCS += static.c +endif + +APP_SRCS += main.c diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..0e5b89c --- /dev/null +++ b/src/stream.c @@ -0,0 +1,582 @@ +/** + * @file stream.c Generic Media Stream + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <time.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +#define MAGIC 0x00814ea5 +#include "magic.h" + + +enum { + RTP_RECV_SIZE = 8192, +}; + + +/** Defines a generic media stream */ +struct stream { + MAGIC_DECL + + struct le le; /**< Linked list element */ + struct config_avt cfg; /**< Stream configuration */ + struct call *call; /**< Ref. to call object */ + struct sdp_media *sdp; /**< SDP Media line */ + struct rtp_sock *rtp; /**< RTP Socket */ + struct rtpkeep *rtpkeep; /**< RTP Keepalive */ + struct rtcp_stats rtcp_stats;/**< RTCP statistics */ + struct jbuf *jbuf; /**< Jitter Buffer for incoming RTP */ + struct mnat_media *mns; /**< Media NAT traversal state */ + const struct menc *menc; /**< Media encryption module */ + struct menc_sess *mencs; /**< Media encryption session state */ + struct menc_media *mes; /**< Media Encryption media state */ + struct metric metric_tx; /**< Metrics for transmit */ + struct metric metric_rx; /**< Metrics for receiving */ + char *cname; + uint32_t ssrc_rx; /**< Incoming syncronizing source */ + uint32_t pseq; /**< Sequence number for incoming RTP */ + int pt_enc; /**< Payload type for encoding */ + bool rtcp; /**< Enable RTCP */ + bool rtcp_mux; /**< RTP/RTCP multiplex supported by peer */ + bool jbuf_started; /**< True if jitter-buffer was started */ + stream_rtp_h *rtph; /**< Stream RTP handler */ + stream_rtcp_h *rtcph; /**< Stream RTCP handler */ + void *arg; /**< Handler argument */ +}; + + +static inline int lostcalc(struct stream *s, uint16_t seq) +{ + const uint16_t delta = seq - s->pseq; + int lostc; + + if (s->pseq == (uint32_t)-1) + lostc = 0; + else if (delta == 0) + return -1; + else if (delta < 3000) + lostc = delta - 1; + else if (delta < 0xff9c) + lostc = 0; + else + return -2; + + s->pseq = seq; + + return lostc; +} + + +static void print_rtp_stats(struct stream *s) +{ + info("\n%-9s Transmit: Receive:\n" + "packets: %7u %7u\n" + "avg. bitrate: %7.1f %7.1f (kbit/s)\n", + sdp_media_name(s->sdp), + s->metric_tx.n_packets, s->metric_rx.n_packets, + 1.0*metric_avg_bitrate(&s->metric_tx)/1000, + 1.0*metric_avg_bitrate(&s->metric_rx)/1000); + + if (s->rtcp_stats.tx.sent || s->rtcp_stats.rx.sent) { + + info("pkt.report: %7u %7u\n" + "lost: %7d %7d\n" + "jitter: %7.1f %7.1f (ms)\n", + s->rtcp_stats.tx.sent, s->rtcp_stats.rx.sent, + s->rtcp_stats.tx.lost, s->rtcp_stats.rx.lost, + 1.0*s->rtcp_stats.tx.jit/1000, + 1.0*s->rtcp_stats.rx.jit/1000); + } +} + + +static void stream_destructor(void *arg) +{ + struct stream *s = arg; + + if (s->cfg.rtp_stats) + print_rtp_stats(s); + + metric_reset(&s->metric_tx); + metric_reset(&s->metric_rx); + + list_unlink(&s->le); + mem_deref(s->rtpkeep); + mem_deref(s->sdp); + mem_deref(s->mes); + mem_deref(s->mencs); + mem_deref(s->mns); + mem_deref(s->jbuf); + mem_deref(s->rtp); + mem_deref(s->cname); +} + + +static void rtp_recv(const struct sa *src, const struct rtp_header *hdr, + struct mbuf *mb, void *arg) +{ + struct stream *s = arg; + bool flush = false; + int err; + + if (!mbuf_get_left(mb)) + return; + + if (!(sdp_media_ldir(s->sdp) & SDP_RECVONLY)) + return; + + metric_add_packet(&s->metric_rx, mbuf_get_left(mb)); + + if (hdr->ssrc != s->ssrc_rx) { + if (s->ssrc_rx) { + flush = true; + info("stream: %s: SSRC changed %x -> %x" + " (%u bytes from %J)\n", + sdp_media_name(s->sdp), s->ssrc_rx, hdr->ssrc, + mbuf_get_left(mb), src); + } + s->ssrc_rx = hdr->ssrc; + } + + if (s->jbuf) { + + struct rtp_header hdr2; + void *mb2 = NULL; + + /* Put frame in Jitter Buffer */ + if (flush) + jbuf_flush(s->jbuf); + + err = jbuf_put(s->jbuf, hdr, mb); + if (err) { + info("%s: dropping %u bytes from %J (%m)\n", + sdp_media_name(s->sdp), mb->end, + src, err); + s->metric_rx.n_err++; + } + + if (jbuf_get(s->jbuf, &hdr2, &mb2)) { + + if (!s->jbuf_started) + return; + + memset(&hdr2, 0, sizeof(hdr2)); + } + + s->jbuf_started = true; + + if (lostcalc(s, hdr2.seq) > 0) + s->rtph(hdr, NULL, s->arg); + + s->rtph(&hdr2, mb2, s->arg); + + mem_deref(mb2); + } + else { + if (lostcalc(s, hdr->seq) > 0) + s->rtph(hdr, NULL, s->arg); + + s->rtph(hdr, mb, s->arg); + } +} + + +static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg) +{ + struct stream *s = arg; + (void)src; + + if (s->rtcph) + s->rtcph(msg, s->arg); + + switch (msg->hdr.pt) { + + case RTCP_SR: + (void)rtcp_stats(s->rtp, msg->r.sr.ssrc, &s->rtcp_stats); + break; + } +} + + +static int stream_sock_alloc(struct stream *s, int af) +{ + struct sa laddr; + int tos, err; + + if (!s) + return EINVAL; + + /* we listen on all interfaces */ + sa_init(&laddr, af); + + err = rtp_listen(&s->rtp, IPPROTO_UDP, &laddr, + s->cfg.rtp_ports.min, s->cfg.rtp_ports.max, + s->rtcp, rtp_recv, rtcp_handler, s); + if (err) + return err; + + tos = s->cfg.rtp_tos; + (void)udp_setsockopt(rtp_sock(s->rtp), IPPROTO_IP, IP_TOS, + &tos, sizeof(tos)); + (void)udp_setsockopt(rtcp_sock(s->rtp), IPPROTO_IP, IP_TOS, + &tos, sizeof(tos)); + + udp_rxsz_set(rtp_sock(s->rtp), RTP_RECV_SIZE); + + return 0; +} + + +int stream_alloc(struct stream **sp, const struct config_avt *cfg, + struct call *call, struct sdp_session *sdp_sess, + const char *name, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + const char *cname, + stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg) +{ + struct stream *s; + int err; + + if (!sp || !cfg || !call || !rtph) + return EINVAL; + + s = mem_zalloc(sizeof(*s), stream_destructor); + if (!s) + return ENOMEM; + + MAGIC_INIT(s); + + s->cfg = *cfg; + s->call = call; + s->rtph = rtph; + s->rtcph = rtcph; + s->arg = arg; + s->pseq = -1; + s->rtcp = s->cfg.rtcp_enable; + + err = stream_sock_alloc(s, call_af(call)); + if (err) + goto out; + + err = str_dup(&s->cname, cname); + if (err) + goto out; + + /* Jitter buffer */ + if (cfg->jbuf_del.min && cfg->jbuf_del.max) { + + err = jbuf_alloc(&s->jbuf, cfg->jbuf_del.min, + cfg->jbuf_del.max); + if (err) + goto out; + } + + err = sdp_media_add(&s->sdp, sdp_sess, name, + sa_port(rtp_local(s->rtp)), + (menc && menc->sdp_proto) ? menc->sdp_proto : + sdp_proto_rtpavp); + if (err) + goto out; + + if (label) { + err |= sdp_media_set_lattr(s->sdp, true, + "label", "%d", label); + } + + /* RFC 5506 */ + if (s->rtcp) + err |= sdp_media_set_lattr(s->sdp, true, "rtcp-rsize", NULL); + + /* RFC 5576 */ + if (s->rtcp) { + err |= sdp_media_set_lattr(s->sdp, true, + "ssrc", "%u cname:%s", + rtp_sess_ssrc(s->rtp), cname); + } + + /* RFC 5761 */ + if (cfg->rtcp_mux) + err |= sdp_media_set_lattr(s->sdp, true, "rtcp-mux", NULL); + + if (err) + goto out; + + if (mnat) { + err = mnat->mediah(&s->mns, mnat_sess, IPPROTO_UDP, + rtp_sock(s->rtp), + s->rtcp ? rtcp_sock(s->rtp) : NULL, + s->sdp); + if (err) + goto out; + } + + if (menc) { + s->menc = menc; + s->mencs = mem_ref(menc_sess); + err = menc->mediah(&s->mes, menc_sess, + s->rtp, + IPPROTO_UDP, + rtp_sock(s->rtp), + s->rtcp ? rtcp_sock(s->rtp) : NULL, + s->sdp); + if (err) + goto out; + } + + if (err) + goto out; + + s->pt_enc = -1; + + list_append(call_streaml(call), &s->le, s); + + out: + if (err) + mem_deref(s); + else + *sp = s; + + return err; +} + + +struct sdp_media *stream_sdpmedia(const struct stream *s) +{ + return s ? s->sdp : NULL; +} + + +static void stream_start_keepalive(struct stream *s) +{ + const char *rtpkeep; + + if (!s) + return; + + rtpkeep = ua_prm(call_get_ua(s->call))->rtpkeep; + + s->rtpkeep = mem_deref(s->rtpkeep); + + if (rtpkeep && sdp_media_rformat(s->sdp, NULL)) { + int err; + err = rtpkeep_alloc(&s->rtpkeep, rtpkeep, + IPPROTO_UDP, s->rtp, s->sdp); + if (err) { + warning("stream: rtpkeep_alloc failed: %m\n", err); + } + } +} + + +int stream_send(struct stream *s, bool marker, int pt, uint32_t ts, + struct mbuf *mb) +{ + int err = 0; + + if (!s) + return EINVAL; + + if (!sa_isset(sdp_media_raddr(s->sdp), SA_ALL)) + return 0; + if (sdp_media_dir(s->sdp) != SDP_SENDRECV) + return 0; + + metric_add_packet(&s->metric_tx, mbuf_get_left(mb)); + + if (pt < 0) + pt = s->pt_enc; + + if (pt >= 0) { + err = rtp_send(s->rtp, sdp_media_raddr(s->sdp), + marker, pt, ts, mb); + if (err) + s->metric_tx.n_err++; + } + + rtpkeep_refresh(s->rtpkeep, ts); + + return err; +} + + +static void stream_remote_set(struct stream *s) +{ + struct sa rtcp; + + if (!s) + return; + + /* RFC 5761 */ + if (s->cfg.rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) { + + if (!s->rtcp_mux) + info("%s: RTP/RTCP multiplexing enabled\n", + sdp_media_name(s->sdp)); + s->rtcp_mux = true; + } + + rtcp_enable_mux(s->rtp, s->rtcp_mux); + + sdp_media_raddr_rtcp(s->sdp, &rtcp); + + rtcp_start(s->rtp, s->cname, + s->rtcp_mux ? sdp_media_raddr(s->sdp): &rtcp); +} + + +void stream_update(struct stream *s) +{ + const struct sdp_format *fmt; + int err = 0; + + if (!s) + return; + + fmt = sdp_media_rformat(s->sdp, NULL); + + s->pt_enc = fmt ? fmt->pt : -1; + + if (sdp_media_has_media(s->sdp)) + stream_remote_set(s); + + if (s->menc && s->menc->mediah) { + err = s->menc->mediah(&s->mes, s->mencs, s->rtp, + IPPROTO_UDP, + rtp_sock(s->rtp), + s->rtcp ? rtcp_sock(s->rtp) : NULL, + s->sdp); + if (err) { + warning("stream: mediaenc update: %m\n", err); + } + } +} + + +void stream_update_encoder(struct stream *s, int pt_enc) +{ + if (pt_enc >= 0) + s->pt_enc = pt_enc; +} + + +int stream_jbuf_stat(struct re_printf *pf, const struct stream *s) +{ + struct jbuf_stat stat; + int err; + + if (!s) + return EINVAL; + + err = re_hprintf(pf, " %s:", sdp_media_name(s->sdp)); + + err |= jbuf_stats(s->jbuf, &stat); + if (err) { + err = re_hprintf(pf, "Jbuf stat: (not available)"); + } + else { + err = re_hprintf(pf, "Jbuf stat: put=%u get=%u or=%u ur=%u", + stat.n_put, stat.n_get, + stat.n_overflow, stat.n_underflow); + } + + return err; +} + + +void stream_hold(struct stream *s, bool hold) +{ + if (!s) + return; + + sdp_media_set_ldir(s->sdp, hold ? SDP_SENDONLY : SDP_SENDRECV); +} + + +void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx) +{ + if (!s) + return; + + rtcp_set_srate(s->rtp, srate_tx, srate_rx); +} + + +void stream_send_fir(struct stream *s, bool pli) +{ + int err; + + if (!s) + return; + + if (pli) + err = rtcp_send_pli(s->rtp, s->ssrc_rx); + else + err = rtcp_send_fir(s->rtp, rtp_sess_ssrc(s->rtp)); + + if (err) { + s->metric_tx.n_err++; + + warning("stream: failed to send RTCP %s: %m\n", + pli ? "PLI" : "FIR", err); + } +} + + +void stream_reset(struct stream *s) +{ + if (!s) + return; + + jbuf_flush(s->jbuf); + + stream_start_keepalive(s); +} + + +void stream_set_bw(struct stream *s, uint32_t bps) +{ + if (!s) + return; + + sdp_media_set_lbandwidth(s->sdp, SDP_BANDWIDTH_AS, bps / 1024); +} + + +int stream_debug(struct re_printf *pf, const struct stream *s) +{ + struct sa rrtcp; + int err; + + if (!s) + return 0; + + err = re_hprintf(pf, " %s dir=%s pt_enc=%d\n", sdp_media_name(s->sdp), + sdp_dir_name(sdp_media_dir(s->sdp)), + s->pt_enc); + + sdp_media_raddr_rtcp(s->sdp, &rrtcp); + err |= re_hprintf(pf, " remote: %J/%J\n", + sdp_media_raddr(s->sdp), &rrtcp); + + err |= rtp_debug(pf, s->rtp); + err |= jbuf_debug(pf, s->jbuf); + + return err; +} + + +int stream_print(struct re_printf *pf, const struct stream *s) +{ + if (!s) + return 0; + + return re_hprintf(pf, " %s=%u/%u", sdp_media_name(s->sdp), + s->metric_tx.cur_bitrate, + s->metric_rx.cur_bitrate); +} diff --git a/src/ua.c b/src/ua.c new file mode 100644 index 0000000..713890c --- /dev/null +++ b/src/ua.c @@ -0,0 +1,1562 @@ +/** + * @file src/ua.c User-Agent + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +#define DEBUG_MODULE "ua" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + +/** Magic number */ +#define MAGIC 0x0a0a0a0a +#include "magic.h" + + +enum { + MAX_CALLS = 4 +}; + + +/** Defines a SIP User Agent object */ +struct ua { + MAGIC_DECL /**< Magic number for struct ua */ + struct ua **uap; /**< Pointer to application's ua */ + struct le le; /**< Linked list element */ + struct account *acc; /**< Account Parameters */ + struct list regl; /**< List of Register clients */ + struct list calls; /**< List of active calls (struct call) */ + struct play *play; /**< Playback of ringtones etc. */ + struct pl extensionv[8]; /**< Vector of SIP extensions */ + size_t extensionc; /**< Number of SIP extensions */ + char *cuser; /**< SIP Contact username */ + int af; /**< Preferred Address Family */ +}; + +struct ua_eh { + struct le le; + ua_event_h *h; + void *arg; +}; + +static struct { + struct config_sip *cfg; /**< SIP configuration */ + struct list ual; /**< List of User-Agents (struct ua) */ + struct list ehl; /**< Event handlers (struct ua_eh) */ + struct sip *sip; /**< SIP Stack */ + struct sip_lsnr *lsnr; /**< SIP Listener */ + struct sipsess_sock *sock; /**< SIP Session socket */ + struct sipevent_sock *evsock; /**< SIP Event socket */ + struct ua *ua_cur; /**< Current User-Agent */ + bool use_udp; /**< Use UDP transport */ + bool use_tcp; /**< Use TCP transport */ + bool use_tls; /**< Use TLS transport */ + bool prefer_ipv6; /**< Force IPv6 transport */ +#ifdef USE_TLS + struct tls *tls; /**< TLS Context */ +#endif +} uag = { + NULL, + LIST_INIT, + LIST_INIT, + NULL, + NULL, + NULL, + NULL, + NULL, + true, + true, + true, + false, +#ifdef USE_TLS + NULL, +#endif +}; + + +/* prototypes */ +static int ua_call_alloc(struct call **callp, struct ua *ua, + enum vidmode vidmode, const struct sip_msg *msg, + struct call *xcall, const char *local_uri); + + +/* This function is called when all SIP transactions are done */ +static void exit_handler(void *arg) +{ + (void)arg; + + re_cancel(); +} + + +void ua_printf(const struct ua *ua, const char *fmt, ...) +{ + va_list ap; + + if (!ua) + return; + + va_start(ap, fmt); + info("%r@%r: %v", &ua->acc->luri.user, &ua->acc->luri.host, fmt, &ap); + va_end(ap); +} + + +void ua_event(struct ua *ua, enum ua_event ev, struct call *call, + const char *fmt, ...) +{ + struct le *le; + char buf[256]; + va_list ap; + + if (!ua) + return; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* send event to all clients */ + for (le = uag.ehl.head; le; le = le->next) { + + struct ua_eh *eh = le->data; + + eh->h(ua, ev, call, buf, eh->arg); + } +} + + +/** + * Start registration of a User-Agent + * + * @param ua User-Agent + * + * @return 0 if success, otherwise errorcode + */ +int ua_register(struct ua *ua) +{ + struct account *acc; + struct le *le; + struct uri uri; + char reg_uri[64]; + char params[256] = ""; + unsigned i; + int err; + + if (!ua) + return EINVAL; + + acc = ua->acc; + uri = ua->acc->luri; + uri.user = uri.password = pl_null; + if (re_snprintf(reg_uri, sizeof(reg_uri), "%H", uri_encode, &uri) < 0) + return ENOMEM; + + if (str_isset(uag.cfg->uuid)) { + if (re_snprintf(params, sizeof(params), + ";+sip.instance=\"<urn:uuid:%s>\"", + uag.cfg->uuid) < 0) + return ENOMEM; + } + + if (acc->regq) { + if (re_snprintf(¶ms[strlen(params)], + sizeof(params) - strlen(params), + ";q=%s", acc->regq) < 0) + return ENOMEM; + } + + if (acc->mnat && acc->mnat->ftag) { + if (re_snprintf(¶ms[strlen(params)], + sizeof(params) - strlen(params), + ";%s", acc->mnat->ftag) < 0) + return ENOMEM; + } + + ua_event(ua, UA_EVENT_REGISTERING, NULL, NULL); + + for (le = ua->regl.head, i=0; le; le = le->next, i++) { + struct reg *reg = le->data; + + err = reg_register(reg, reg_uri, params, + acc->regint, acc->outbound[i]); + if (err) { + DEBUG_WARNING("SIP register failed: %m\n", err); + return err; + } + } + + return 0; +} + + +bool ua_isregistered(const struct ua *ua) +{ + struct le *le; + + if (!ua) + return false; + + for (le = ua->regl.head; le; le = le->next) { + + const struct reg *reg = le->data; + + /* it is enough if one of the registrations work */ + if (reg_isok(reg)) + return true; + } + + return false; +} + + +static const char *translate_errorcode(uint16_t scode) +{ + switch (scode) { + + case 404: return "notfound.wav"; + case 486: return "busy.wav"; + case 487: return NULL; /* ignore */ + default: return "error.wav"; + } +} + + +static void call_event_handler(struct call *call, enum call_event ev, + const char *str, void *arg) +{ + struct ua *ua = arg; + const char *peeruri; + struct call *call2 = NULL; + int err; + + MAGIC_CHECK(ua); + + peeruri = call_peeruri(call); + + /* stop any ringtones */ + ua->play = mem_deref(ua->play); + + switch (ev) { + + case CALL_EVENT_INCOMING: + switch (ua->acc->answermode) { + + case ANSWERMODE_EARLY: + (void)call_progress(call); + break; + + case ANSWERMODE_AUTO: + (void)call_answer(call, 200); + break; + + case ANSWERMODE_MANUAL: + default: + if (list_count(&ua->calls) > 1) { + (void)play_file(&ua->play, + "callwaiting.wav", 3); + } + else { + /* Alert user */ + (void)play_file(&ua->play, "ring.wav", -1); + } + + ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri); + break; + } + break; + + case CALL_EVENT_RINGING: + (void)play_file(&ua->play, "ringback.wav", -1); + + ua_event(ua, UA_EVENT_CALL_RINGING, call, peeruri); + break; + + case CALL_EVENT_PROGRESS: + ua_printf(ua, "Call in-progress: %s\n", peeruri); + ua_event(ua, UA_EVENT_CALL_PROGRESS, call, peeruri); + break; + + case CALL_EVENT_ESTABLISHED: + ua_printf(ua, "Call established: %s\n", peeruri); + ua_event(ua, UA_EVENT_CALL_ESTABLISHED, call, peeruri); + break; + + case CALL_EVENT_CLOSED: + if (call_scode(call)) { + const char *tone; + tone = translate_errorcode(call_scode(call)); + if (tone) + (void)play_file(&ua->play, tone, 1); + } + ua_event(ua, UA_EVENT_CALL_CLOSED, call, str); + mem_deref(call); + break; + + case CALL_EVENT_TRANSFER: + + /* + * Create a new call to transfer target. + * + * NOTE: we will automatically connect a new call to the + * transfer target + */ + + ua_printf(ua, "transferring call to %s\n", str); + + err = ua_call_alloc(&call2, ua, VIDMODE_ON, NULL, call, + call_localuri(call)); + if (!err) { + struct pl pl; + + pl_set_str(&pl, str); + + err = call_connect(call2, &pl); + if (err) { + DEBUG_WARNING("transfer: connect error: %m\n", + err); + } + } + + if (err) { + (void)call_notify_sipfrag(call, 500, "%m", err); + mem_deref(call2); + } + break; + } +} + + +static int ua_call_alloc(struct call **callp, struct ua *ua, + enum vidmode vidmode, const struct sip_msg *msg, + struct call *xcall, const char *local_uri) +{ + struct call_prm cprm; + + if (*callp) { + DEBUG_WARNING("call_alloc: call is already allocated\n"); + return EALREADY; + } + + cprm.vidmode = vidmode; + cprm.af = ua->af; + + return call_alloc(callp, conf_config(), &ua->calls, + ua->acc->dispname, + local_uri ? local_uri : ua->acc->aor, + ua->acc, ua, &cprm, + msg, xcall, call_event_handler, ua); +} + + +static void handle_options(struct ua *ua, const struct sip_msg *msg) +{ + struct call *call = NULL; + struct mbuf *desc = NULL; + int err; + + err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL); + if (err) { + (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error"); + return; + } + + err = call_sdp_get(call, &desc, true); + if (err) + goto out; + + err = sip_treplyf(NULL, NULL, uag.sip, + msg, true, 200, "OK", + "Contact: <sip:%s@%J%s>\r\n" + "Content-Type: application/sdp\r\n" + "Content-Length: %zu\r\n" + "\r\n" + "%b", + ua->cuser, &msg->dst, sip_transp_param(msg->tp), + mbuf_get_left(desc), + mbuf_buf(desc), + mbuf_get_left(desc)); + if (err) { + DEBUG_WARNING("options: sip_treplyf: %m\n", err); + } + + out: + mem_deref(desc); + mem_deref(call); +} + + +static void ua_destructor(void *arg) +{ + struct ua *ua = arg; + + if (ua->uap) { + *ua->uap = NULL; + ua->uap = NULL; + } + + list_unlink(&ua->le); + + ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL); + + list_flush(&ua->calls); + list_flush(&ua->regl); + mem_deref(ua->play); + mem_deref(ua->cuser); + mem_deref(ua->acc); +} + + +static bool request_handler(const struct sip_msg *msg, void *arg) +{ + struct ua *ua; + + (void)arg; + + if (pl_strcmp(&msg->met, "OPTIONS")) + return false; + + ua = uag_find(&msg->uri.user); + if (!ua) { + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return true; + } + + handle_options(ua, msg); + + return true; +} + + +static void add_extension(struct ua *ua, const char *extension) +{ + struct pl e; + + if (ua->extensionc >= ARRAY_SIZE(ua->extensionv)) { + DEBUG_WARNING("maximum %u number of SIP extensions\n"); + return; + } + + pl_set_str(&e, extension); + + ua->extensionv[ua->extensionc++] = e; +} + + +/** + * Allocate a SIP User-Agent + * + * @param uap Pointer to allocated User-Agent object + * @param aor SIP Address-of-Record (AOR) + * + * @return 0 if success, otherwise errorcode + */ +int ua_alloc(struct ua **uap, const char *aor) +{ + struct ua *ua; + int err; + + if (!aor) + return EINVAL; + + ua = mem_zalloc(sizeof(*ua), ua_destructor); + if (!ua) + return ENOMEM; + + MAGIC_INIT(ua); + + list_init(&ua->calls); + +#if HAVE_INET6 + ua->af = uag.prefer_ipv6 ? AF_INET6 : AF_INET; +#else + ua->af = AF_INET; +#endif + + /* generate a unique contact-user, this is needed to route + incoming requests when using multiple useragents */ + err = re_sdprintf(&ua->cuser, "%p", ua); + if (err) + goto out; + + /* Decode SIP address */ + + err = account_alloc(&ua->acc, aor); + if (err) + goto out; + + if (ua->acc->sipnat) { + ua_printf(ua, "Using sipnat: `%s'\n", ua->acc->sipnat); + } + + if (ua->acc->mnat) { + ua_printf(ua, "Using medianat `%s'\n", + ua->acc->mnat->id); + + if (0 == str_casecmp(ua->acc->mnat->id, "ice")) + add_extension(ua, "ice"); + } + + if (ua->acc->menc) { + ua_printf(ua, "Using media encryption `%s'\n", + ua->acc->menc->id); + } + + /* Register clients */ + if (0 == str_casecmp(ua->acc->sipnat, "outbound")) { + + size_t i; + + add_extension(ua, "path"); + add_extension(ua, "outbound"); + + if (!str_isset(uag.cfg->uuid)) { + + DEBUG_WARNING("outbound requires valid UUID!\n"); + err = ENOSYS; + goto out; + } + + for (i=0; i<ARRAY_SIZE(ua->acc->outbound); i++) { + + if (ua->acc->outbound[i] && ua->acc->regint) { + err = reg_add(&ua->regl, ua, (int)i+1); + if (err) + break; + } + } + } + else if (ua->acc->regint) { + err = reg_add(&ua->regl, ua, 0); + } + if (err) + goto out; + + list_append(&uag.ual, &ua->le, ua); + + if (ua->acc->regint) { + err = ua_register(ua); + } + + if (!uag_current()) + uag_current_set(ua); + + out: + if (err) + mem_deref(ua); + else if (uap) { + *uap = ua; + + ua->uap = uap; + } + + return err; +} + + +/** + * Connect an outgoing call to a given SIP uri + * + * @param ua User-Agent + * @param callp Optional pointer to allocated call object + * @param from_uri Optional From uri, or NULL for default AOR + * @param uri SIP uri to connect to + * @param params Optional URI parameters + * @param vmode Video mode + * + * @return 0 if success, otherwise errorcode + */ +int ua_connect(struct ua *ua, struct call **callp, + const char *from_uri, const char *uri, + const char *params, enum vidmode vmode) +{ + struct call *call = NULL; + struct mbuf *dialbuf; + struct pl pl; + size_t len; + int err = 0; + + if (!ua || !str_isset(uri)) + return EINVAL; + + len = str_len(uri); + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + if (params) + err |= mbuf_printf(dialbuf, "<"); + + /* Append sip: scheme if missing */ + if (0 != re_regex(uri, len, "sip:")) + err |= mbuf_printf(dialbuf, "sip:"); + + err |= mbuf_write_str(dialbuf, uri); + + /* Append domain if missing */ + if (0 != re_regex(uri, len, "[^@]+@[^]+", NULL, NULL)) { +#if HAVE_INET6 + if (AF_INET6 == ua->acc->luri.af) + err |= mbuf_printf(dialbuf, "@[%r]", + &ua->acc->luri.host); + else +#endif + err |= mbuf_printf(dialbuf, "@%r", + &ua->acc->luri.host); + + /* Also append port if specified and not 5060 */ + switch (ua->acc->luri.port) { + + case 0: + case SIP_PORT: + break; + + default: + err |= mbuf_printf(dialbuf, ":%u", ua->acc->luri.port); + break; + } + } + + if (params) { + err |= mbuf_printf(dialbuf, ";%s", params); + } + + /* Append any optional URI parameters */ + err |= mbuf_write_pl(dialbuf, &ua->acc->luri.params); + + if (params) + err |= mbuf_printf(dialbuf, ">"); + + if (err) + goto out; + + err = ua_call_alloc(&call, ua, vmode, NULL, NULL, from_uri); + if (err) + goto out; + + pl.p = (char *)dialbuf->buf; + pl.l = dialbuf->end; + + err = call_connect(call, &pl); + + if (err) + mem_deref(call); + else if (callp) + *callp = call; + + out: + mem_deref(dialbuf); + + return err; +} + + +/** + * Hangup the current call + * + * @param ua User-Agent + * @param call Call to hangup, or NULL for current call + * @param scode Optional status code + * @param reason Optional reason + */ +void ua_hangup(struct ua *ua, struct call *call, + uint16_t scode, const char *reason) +{ + if (!ua) + return; + + if (!call) { + call = ua_call(ua); + if (!call) + return; + } + + ua->play = mem_deref(ua->play); + + (void)call_hangup(call, scode, reason); + + mem_deref(call); +} + + +/** + * Answer an incoming call + * + * @param ua User-Agent + * @param call Call to hangup, or NULL for current call + * + * @return 0 if success, otherwise errorcode + */ +int ua_answer(struct ua *ua, struct call *call) +{ + if (!ua) + return EINVAL; + + if (!call) { + call = ua_call(ua); + if (!call) + return ENOENT; + } + + /* todo: put previous call on-hold (if configured) */ + + ua->play = mem_deref(ua->play); + + return call_answer(call, 200); +} + + +int ua_print_status(struct re_printf *pf, const struct ua *ua) +{ + struct le *le; + int err; + + if (!ua) + return 0; + + err = re_hprintf(pf, "%-42s", ua->acc->aor); + + for (le = ua->regl.head; le; le = le->next) + err |= reg_status(pf, le->data); + + err |= re_hprintf(pf, "\n"); + + return err; +} + + +/** + * Send SIP OPTIONS message to a peer + * + * @param ua User-Agent object + * @param uri Peer SIP Address + * @param resph Response handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int ua_options_send(struct ua *ua, const char *uri, + options_resp_h *resph, void *arg) +{ + int err; + + if (!ua) + return EINVAL; + + err = sip_req_send(ua, "OPTIONS", uri, resph, arg, + "Accept: application/sdp\r\n" + "Content-Length: 0\r\n" + "\r\n"); + if (err) { + DEBUG_WARNING("send options: (%m)\n", err); + } + + return err; +} + + +/** + * Get the AOR of a User-Agent + * + * @param ua User-Agent object + * + * @return AOR + */ +const char *ua_aor(const struct ua *ua) +{ + return ua ? ua->acc->aor : NULL; +} + + +/** + * Get the outbound SIP proxy of a User-Agent + * + * @param ua User-Agent object + * + * @return Outbound SIP proxy uri + */ +const char *ua_outbound(const struct ua *ua) +{ + /* NOTE: we pick the first outbound server, should be rotated? */ + return ua ? ua->acc->outbound[0] : NULL; +} + + +/** + * Get the current call object of a User-Agent + * + * @param ua User-Agent object + * + * @return Current call, NULL if no active calls + * + * + * Current call strategy: + * + * We can only have 1 current call. The current call is the one that was + * added last (end of the list), which is not on-hold + */ +struct call *ua_call(const struct ua *ua) +{ + struct le *le; + + if (!ua) + return NULL; + + for (le = ua->calls.tail; le; le = le->prev) { + + struct call *call = le->data; + + /* todo: check if call is on-hold */ + + return call; + } + + return NULL; +} + + +int ua_debug(struct re_printf *pf, const struct ua *ua) +{ + struct le *le; + int err; + + if (!ua) + return 0; + + err = re_hprintf(pf, "--- %s ---\n", ua->acc->aor); + err |= re_hprintf(pf, " cuser: %s\n", ua->cuser); + err |= re_hprintf(pf, " af: %s\n", net_af2name(ua->af)); + err |= re_hprintf(pf, " %H", ua_print_supported, ua); + + err |= account_debug(pf, ua->acc); + + for (le = ua->regl.head; le; le = le->next) + err |= reg_debug(pf, le->data); + + return err; +} + + +/* One instance */ + + +static int add_transp_af(const struct sa *laddr) +{ + struct sa local; + int err = 0; + + if (str_isset(uag.cfg->local)) { + err = sa_decode(&local, uag.cfg->local, + str_len(uag.cfg->local)); + if (err) { + err = sa_set_str(&local, uag.cfg->local, 0); + if (err) { + DEBUG_WARNING("decode failed: %s\n", + uag.cfg->local); + return err; + } + } + + if (!sa_isset(&local, SA_ADDR)) { + uint16_t port = sa_port(&local); + (void)sa_set_sa(&local, &laddr->u.sa); + sa_set_port(&local, port); + } + + if (sa_af(laddr) != sa_af(&local)) + return 0; + } + else { + sa_cpy(&local, laddr); + sa_set_port(&local, 0); + } + + if (uag.use_udp) + err |= sip_transp_add(uag.sip, SIP_TRANSP_UDP, &local); + if (uag.use_tcp) + err |= sip_transp_add(uag.sip, SIP_TRANSP_TCP, &local); + if (err) { + DEBUG_WARNING("SIP Transport failed: %m\n", err); + return err; + } + +#ifdef USE_TLS + if (uag.use_tls) { + /* Build our SSL context*/ + if (!uag.tls) { + const char *cert = NULL; + + if (str_isset(uag.cfg->cert)) { + cert = uag.cfg->cert; + info("SIP Certificate: %s\n", cert); + } + + err = tls_alloc(&uag.tls, TLS_METHOD_SSLV23, + cert, NULL); + if (err) { + DEBUG_WARNING("tls_alloc() failed: %m\n", err); + return err; + } + } + + if (sa_isset(&local, SA_PORT)) + sa_set_port(&local, sa_port(&local) + 1); + + err = sip_transp_add(uag.sip, SIP_TRANSP_TLS, &local, uag.tls); + if (err) { + DEBUG_WARNING("SIP/TLS transport failed: %m\n", err); + return err; + } + } +#endif + + return err; +} + + +static int ua_add_transp(void) +{ + int err = 0; + + if (!uag.prefer_ipv6) { + if (sa_isset(net_laddr_af(AF_INET), SA_ADDR)) + err |= add_transp_af(net_laddr_af(AF_INET)); + } + +#if HAVE_INET6 + if (sa_isset(net_laddr_af(AF_INET6), SA_ADDR)) + err |= add_transp_af(net_laddr_af(AF_INET6)); +#endif + + return err; +} + + +static bool require_handler(const struct sip_hdr *hdr, + const struct sip_msg *msg, void *arg) +{ + struct ua *ua = arg; + bool supported = false; + size_t i; + (void)msg; + + for (i=0; i<ua->extensionc; i++) { + + if (!pl_casecmp(&hdr->val, &ua->extensionv[i])) { + supported = true; + break; + } + } + + return !supported; +} + + +/* Handle incoming calls */ +static void sipsess_conn_handler(const struct sip_msg *msg, void *arg) +{ + const struct sip_hdr *hdr; + struct ua *ua; + struct call *call = NULL; + char str[256], to_uri[256]; + int err; + + (void)arg; + + ua = uag_find(&msg->uri.user); + if (!ua) { + DEBUG_WARNING("%r: UA not found: %r\n", + &msg->from.auri, &msg->uri.user); + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return; + } + + /* handle multiple calls */ + if (list_count(&ua->calls) + 1 > MAX_CALLS) { + info("ua: rejected call from %r (maximum %d calls)\n", + &msg->from.auri, MAX_CALLS); + (void)sip_treply(NULL, uag.sip, msg, 486, "Busy Here"); + return; + } + + /* Handle Require: header, check for any required extensions */ + hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_REQUIRE, + require_handler, ua); + if (hdr) { + info("ua: call from %r rejected with 420" + " -- option-tag '%r' not supported\n", + &msg->from.auri, &hdr->val); + + (void)sip_treplyf(NULL, NULL, uag.sip, msg, false, + 420, "Bad Extension", + "Unsupported: %r\r\n" + "Content-Length: 0\r\n\r\n", + &hdr->val); + return; + } + + (void)pl_strcpy(&msg->to.auri, to_uri, sizeof(to_uri)); + + err = ua_call_alloc(&call, ua, VIDMODE_ON, msg, NULL, to_uri); + if (err) { + DEBUG_WARNING("call_alloc: %m\n", err); + goto error; + } + + err = call_accept(call, uag.sock, msg); + if (err) + goto error; + + return; + + error: + mem_deref(call); + (void)re_snprintf(str, sizeof(str), "Error (%m)", err); + (void)sip_treply(NULL, uag.sip, msg, 500, str); +} + + +static void net_change_handler(void *arg) +{ + (void)arg; + + info("IP-address changed: %j\n", net_laddr_af(AF_INET)); + + (void)uag_reset_transp(true, true); +} + + +static int cmd_quit(struct re_printf *pf, void *unused) +{ + int err; + + (void)unused; + + err = re_hprintf(pf, "Quit\n"); + + ua_stop_all(false); + + return err; +} + + +static const struct cmd cmdv[] = { + {'q', 0, "Quit", cmd_quit }, +}; + + +/** + * Initialise the User-Agents + * + * @param software SIP User-Agent string + * @param udp Enable UDP transport + * @param tcp Enable TCP transport + * @param tls Enable TLS transport + * @param prefer_ipv6 Prefer IPv6 flag + * + * @return 0 if success, otherwise errorcode + */ +int ua_init(const char *software, bool udp, bool tcp, bool tls, + bool prefer_ipv6) +{ + struct config *cfg = conf_config(); + uint32_t bsize; + int err; + + uag.cfg = &cfg->sip; + bsize = cfg->sip.trans_bsize; + ui_init(&cfg->input); + + play_init(); + + /* Initialise Network */ + err = net_init(&cfg->net, prefer_ipv6 ? AF_INET6 : AF_INET); + if (err) { + DEBUG_WARNING("network init failed: %m\n", err); + return err; + } + + uag.use_udp = udp; + uag.use_tcp = tcp; + uag.use_tls = tls; + uag.prefer_ipv6 = prefer_ipv6; + + list_init(&uag.ual); + + err = sip_alloc(&uag.sip, net_dnsc(), bsize, bsize, bsize, + software, exit_handler, NULL); + if (err) { + DEBUG_WARNING("sip stack failed: %m\n", err); + goto out; + } + + err = ua_add_transp(); + if (err) + goto out; + + err = sip_listen(&uag.lsnr, uag.sip, true, request_handler, NULL); + if (err) + goto out; + + err = sipsess_listen(&uag.sock, uag.sip, bsize, + sipsess_conn_handler, NULL); + if (err) + goto out; + + err = sipevent_listen(&uag.evsock, uag.sip, bsize, bsize, NULL, NULL); + if (err) + goto out; + + err = cmd_register(cmdv, ARRAY_SIZE(cmdv)); + if (err) + goto out; + + net_change(60, net_change_handler, NULL); + + out: + if (err) { + DEBUG_WARNING("init failed (%m)\n", err); + ua_close(); + } + return err; +} + + +/** + * Close all active User-Agents + */ +void ua_close(void) +{ + cmd_unregister(cmdv); + net_close(); + play_close(); + + uag.evsock = mem_deref(uag.evsock); + uag.sock = mem_deref(uag.sock); + uag.lsnr = mem_deref(uag.lsnr); + uag.sip = mem_deref(uag.sip); + +#ifdef USE_TLS + uag.tls = mem_deref(uag.tls); +#endif + + list_flush(&uag.ual); + list_flush(&uag.ehl); +} + + +/** + * Stop all User-Agents + * + * @param forced True to force, otherwise false + */ +void ua_stop_all(bool forced) +{ + module_app_unload(); + + if (!list_isempty(&uag.ual)) { + const uint32_t n = list_count(&uag.ual); + info("Stopping %u useragent%s.. %s\n", + n, n==1 ? "" : "s", forced ? "(Forced)" : ""); + } + + if (forced) + sipsess_close_all(uag.sock); + else + list_flush(&uag.ual); + + sip_close(uag.sip, forced); +} + + +/** + * Reset the SIP transports for all User-Agents + * + * @param reg True to reset registration + * @param reinvite True to update active calls + * + * @return 0 if success, otherwise errorcode + */ +int uag_reset_transp(bool reg, bool reinvite) +{ + struct le *le; + int err; + + /* Update SIP transports */ + sip_transp_flush(uag.sip); + + (void)net_check(); + err = ua_add_transp(); + if (err) + return err; + + /* Re-REGISTER all User-Agents */ + for (le = uag.ual.head; le; le = le->next) { + struct ua *ua = le->data; + + if (reg && ua->acc->regint) { + err |= ua_register(ua); + } + + /* update all active calls */ + if (reinvite) { + struct le *lec; + + for (lec = ua->calls.head; lec; lec = lec->next) { + struct call *call = lec->data; + + err |= call_reset_transp(call); + } + } + } + + return err; +} + + +/** + * Print the SIP Status for all User-Agents + * + * @param pf Print handler for debug output + * @param unused Unused parameter + * + * @return 0 if success, otherwise errorcode + */ +int ua_print_sip_status(struct re_printf *pf, void *unused) +{ + (void)unused; + return sip_debug(pf, uag.sip); +} + + +/** + * Print all calls for a given User-Agent + */ +int ua_print_calls(struct re_printf *pf, const struct ua *ua) +{ + struct le *le; + int err = 0; + + err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n", + list_count(&ua->calls)); + + for (le = ua->calls.head; le; le = le->next) { + + const struct call *call = le->data; + + err |= re_hprintf(pf, " %H\n", call_info, call); + } + + err |= re_hprintf(pf, "\n"); + + return err; +} + + +/** + * Get the global SIP Stack + * + * @return SIP Stack + */ +struct sip *uag_sip(void) +{ + return uag.sip; +} + + +/** + * Get the global SIP Session socket + * + * @return SIP Session socket + */ +struct sipsess_sock *uag_sipsess_sock(void) +{ + return uag.sock; +} + + +/** + * Get the global SIP Event socket + * + * @return SIP Event socket + */ +struct sipevent_sock *uag_sipevent_sock(void) +{ + return uag.evsock; +} + + +struct tls *uag_tls(void) +{ +#ifdef USE_TLS + return uag.tls; +#else + return NULL; +#endif +} + + +/** + * Get the name of the User-Agent event + * + * @param ev User-Agent event + * + * @return Name of the event + */ +const char *uag_event_str(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: return "REGISTERING"; + case UA_EVENT_REGISTER_OK: return "REGISTER_OK"; + case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL"; + case UA_EVENT_UNREGISTERING: return "UNREGISTERING"; + case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING"; + case UA_EVENT_CALL_RINGING: return "CALL_RINGING"; + case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS"; + case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED"; + case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED"; + default: return "?"; + } +} + + +/** + * Get the current SIP socket file descriptor for a User-Agent + * + * @param ua User-Agent + * + * @return File descriptor, or -1 if not available + */ +int ua_sipfd(const struct ua *ua) +{ + struct le *le; + + if (!ua) + return -1; + + for (le = ua->regl.head; le; le = le->next) { + + struct reg *reg = le->data; + int fd; + + fd = reg_sipfd(reg); + if (fd != -1) + return fd; + } + + return -1; +} + + +/** + * Find the correct UA from the contact user + * + * @param cuser Contact username + * + * @return Matching UA if found, NULL if not found + */ +struct ua *uag_find(const struct pl *cuser) +{ + struct le *le; + + for (le = uag.ual.head; le; le = le->next) { + struct ua *ua = le->data; + + if (0 == pl_strcasecmp(cuser, ua->cuser)) + return ua; + } + + /* Try also matching by AOR, for better interop */ + for (le = uag.ual.head; le; le = le->next) { + struct ua *ua = le->data; + + if (0 == pl_casecmp(cuser, &ua->acc->luri.user)) + return ua; + } + + return NULL; +} + + +/** + * Find a User-Agent (UA) from an Address-of-Record (AOR) + * + * @param aor Address-of-Record string + * + * @return User-Agent (UA) if found, otherwise NULL + */ +struct ua *uag_find_aor(const char *aor) +{ + struct le *le; + + for (le = uag.ual.head; le; le = le->next) { + struct ua *ua = le->data; + + if (str_isset(aor) && str_cmp(ua->acc->aor, aor)) + continue; + + return ua; + } + + return NULL; +} + + +/** + * Find a User-Agent (UA) which has certain address parameter and/or value + * + * @param name SIP Address parameter name + * @param value SIP Address parameter value (optional) + * + * @return User-Agent (UA) if found, otherwise NULL + */ +struct ua *uag_find_param(const char *name, const char *value) +{ + struct le *le; + + for (le = uag.ual.head; le; le = le->next) { + struct ua *ua = le->data; + struct sip_addr *laddr = account_laddr(ua->acc); + struct pl val; + + if (value) { + + if (0 == sip_param_decode(&laddr->params, name, &val) + && + 0 == pl_strcasecmp(&val, value)) { + return ua; + } + } + else { + if (0 == sip_param_exists(&laddr->params, name, &val)) + return ua; + } + } + + return NULL; +} + + +/** + * Get the contact user of a User-Agent (UA) + * + * @param ua User-Agent + * + * @return Contact user + */ +const char *ua_cuser(const struct ua *ua) +{ + return ua ? ua->cuser : NULL; +} + + +struct list *uag_list(void) +{ + return &uag.ual; +} + + +/** + * Return list of methods supported by the UA + * + * @return String of supported methods + */ +const char *uag_allowed_methods(void) +{ + return "INVITE,ACK,BYE,CANCEL,OPTIONS,REFER,NOTIFY,SUBSCRIBE,INFO"; +} + + +int ua_print_supported(struct re_printf *pf, const struct ua *ua) +{ + size_t i; + int err; + + err = re_hprintf(pf, "Supported:"); + + for (i=0; i<ua->extensionc; i++) { + err |= re_hprintf(pf, "%s%r", + i==0 ? " " : ",", &ua->extensionv[i]); + } + + err |= re_hprintf(pf, "\r\n"); + + return err; +} + + +struct account *ua_prm(const struct ua *ua) +{ + return ua ? ua->acc : NULL; +} + + +struct list *ua_calls(const struct ua *ua) +{ + return ua ? (struct list *)&ua->calls : NULL; +} + + +static void eh_destructor(void *arg) +{ + struct ua_eh *eh = arg; + list_unlink(&eh->le); +} + + +int uag_event_register(ua_event_h *h, void *arg) +{ + struct ua_eh *eh; + + if (!h) + return EINVAL; + + eh = mem_zalloc(sizeof(*eh), eh_destructor); + if (!eh) + return ENOMEM; + + eh->h = h; + eh->arg = arg; + + list_append(&uag.ehl, &eh->le, eh); + + return 0; +} + + +void uag_event_unregister(ua_event_h *h) +{ + struct le *le; + + for (le = uag.ehl.head; le; le = le->next) { + + struct ua_eh *eh = le->data; + + if (eh->h == h) { + mem_deref(eh); + break; + } + } +} + + +void uag_current_set(struct ua *ua) +{ + uag.ua_cur = ua; +} + + +struct ua *uag_current(void) +{ + if (list_isempty(uag_list())) + return NULL; + + return uag.ua_cur; +} diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..2d69fd7 --- /dev/null +++ b/src/ui.c @@ -0,0 +1,185 @@ +/** + * @file ui.c User Interface + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** User Interface */ +struct ui { + struct le le; + const char *name; + struct ui_st *st; /* only one instance */ + ui_output_h *outputh; + struct cmd_ctx *ctx; +}; + +static struct list uil; /**< List of UIs (struct ui) */ +static struct config_input input_cfg; + + +static void ui_handler(char key, struct re_printf *pf, void *arg) +{ + struct ui *ui = arg; + + (void)cmd_process(ui ? &ui->ctx : NULL, key, pf); +} + + +static void destructor(void *arg) +{ + struct ui *ui = arg; + + list_unlink(&ui->le); + mem_deref(ui->st); + mem_deref(ui->ctx); +} + + +static int stdout_handler(const char *p, size_t size, void *arg) +{ + (void)arg; + + if (1 != fwrite(p, size, 1, stdout)) + return ENOMEM; + + return 0; +} + + +/** + * Register a new User-Interface (UI) module + * + * @param uip Pointer to allocated UI module + * @param name Name of the UI module + * @param alloch UI allocation handler + * @param outh UI output handler + * + * @return 0 if success, otherwise errorcode + */ +int ui_register(struct ui **uip, const char *name, + ui_alloc_h *alloch, ui_output_h *outh) +{ + struct ui *ui; + int err = 0; + + if (!uip) + return EINVAL; + + ui = mem_zalloc(sizeof(*ui), destructor); + if (!ui) + return ENOMEM; + + list_append(&uil, &ui->le, ui); + + ui->name = name; + ui->outputh = outh; + + if (alloch) { + struct ui_prm prm; + + prm.device = input_cfg.device; + prm.port = input_cfg.port; + + err = alloch(&ui->st, &prm, ui_handler, ui); + if (err) { + warning("ui: register: module '%s' failed (%m)\n", + ui->name, err); + } + } + + if (err) + mem_deref(ui); + else + *uip = ui; + + return err; +} + + +/** + * Send input to the UI subsystem + * + * @param key Input character + */ +void ui_input(char key) +{ + struct re_printf pf; + + pf.vph = stdout_handler; + pf.arg = NULL; + + ui_handler(key, &pf, list_ledata(uil.head)); +} + + +/** + * Send an input string to the UI subsystem + * + * @param str Input string + */ +void ui_input_str(const char *str) +{ + struct re_printf pf; + struct pl pl; + + if (!str) + return; + + pf.vph = stdout_handler; + pf.arg = NULL; + + pl_set_str(&pl, str); + + (void)ui_input_pl(&pf, &pl); +} + + +int ui_input_pl(struct re_printf *pf, const struct pl *pl) +{ + struct cmd_ctx *ctx = NULL; + size_t i; + int err = 0; + + if (!pf || !pl) + return EINVAL; + + for (i=0; i<pl->l; i++) { + err |= cmd_process(&ctx, pl->p[i], pf); + } + + if (pl->l > 1 && ctx) + err |= cmd_process(&ctx, '\n', pf); + + return err; +} + + +/** + * Send output to all modules registered in the UI subsystem + * + * @param str Output string + */ +void ui_output(const char *str) +{ + struct le *le; + + for (le = uil.head; le; le = le->next) { + const struct ui *ui = le->data; + + if (ui->outputh) + ui->outputh(ui->st, str); + } +} + + +void ui_init(const struct config_input *cfg) +{ + if (!cfg) + return; + + input_cfg = *cfg; +} diff --git a/src/vidcodec.c b/src/vidcodec.c new file mode 100644 index 0000000..7624ae7 --- /dev/null +++ b/src/vidcodec.c @@ -0,0 +1,81 @@ +/** + * @file vidcodec.c Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + + +static struct list vidcodecl; + + +/** + * Register a Video Codec + * + * @param vc Video Codec + */ +void vidcodec_register(struct vidcodec *vc) +{ + if (!vc) + return; + + list_append(&vidcodecl, &vc->le, vc); + + info("vidcodec: %s\n", vc->name); +} + + +/** + * Unregister a Video Codec + * + * @param vc Video Codec + */ +void vidcodec_unregister(struct vidcodec *vc) +{ + if (!vc) + return; + + list_unlink(&vc->le); +} + + +/** + * Find a Video Codec by name + * + * @param name Name of the Video Codec to find + * @param variant Codec Variant + * + * @return Matching Video Codec if found, otherwise NULL + */ +const struct vidcodec *vidcodec_find(const char *name, const char *variant) +{ + struct le *le; + + for (le=vidcodecl.head; le; le=le->next) { + + struct vidcodec *vc = le->data; + + if (name && 0 != str_casecmp(name, vc->name)) + continue; + + if (variant && 0 != str_casecmp(variant, vc->variant)) + continue; + + return vc; + } + + return NULL; +} + + +/** + * Get the list of Video Codecs + * + * @return List of Video Codecs + */ +struct list *vidcodec_list(void) +{ + return &vidcodecl; +} diff --git a/src/video.c b/src/video.c new file mode 100644 index 0000000..f7eb032 --- /dev/null +++ b/src/video.c @@ -0,0 +1,1073 @@ +/** + * @file src/video.c Video stream + * + * Copyright (C) 2010 Creytiv.com + * + * \ref GenericVideoStream + */ +#include <string.h> +#include <stdlib.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "core.h" + + +#define DEBUG_MODULE "video" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +/** Magic number */ +#define MAGIC 0x00070d10 +#include "magic.h" + + +enum { + SRATE = 90000, + MAX_MUTED_FRAMES = 3, +}; + + +/** + * \page GenericVideoStream Generic Video Stream + * + * Implements a generic video stream. The application can allocate multiple + * instances of a video stream, mapping it to a particular SDP media line. + * The video object has a Video Display and Source, and a video encoder + * and decoder. A particular video object is mapped to a generic media + * stream object. + * + *<pre> + * recv send + * | /|\ + * \|/ | + * .---------. .-------. + * | video |--->|encoder| + * | | |-------| + * | object |--->|decoder| + * '---------' '-------' + * | /|\ + * | | + * \|/ | + * .-------. .-------. + * |Video | |Video | + * |Display| |Source | + * '-------' '-------' + *</pre> + */ + +/** + * Video stream - transmitter/encoder direction + + \verbatim + + Processing encoder pipeline: + + . .--------. .- - - - -. .---------. .---------. + | ._O_. | | ! ! | | | | + | |___|-->| vidsrc |-->! vidconv !-->| vidfilt |-->| encoder |---> RTP + | | | ! ! | | | | + ' '--------' '- - - - -' '---------' '---------' + (optional) + \endverbatim + */ +struct vtx { + struct video *video; /**< Parent */ + const struct vidcodec *vc; /**< Current Video encoder */ + struct videnc_state *enc; /**< Video encoder state */ + struct vidsrc_prm vsrc_prm; /**< Video source parameters */ + struct vidsz vsrc_size; /**< Video source size */ + struct vidsrc_st *vsrc; /**< Video source */ + struct lock *lock; /**< Lock for encoder */ + struct vidframe *frame; /**< Source frame */ + struct vidframe *mute_frame; /**< Frame with muted video */ + struct mbuf *mb; /**< Packetization buffer */ + struct list filtl; /**< Filters in encoding order */ + char device[64]; + int muted_frames; /**< # of muted frames sent */ + uint32_t ts_tx; /**< Outgoing RTP timestamp */ + bool picup; /**< Send picture update */ + bool muted; /**< Muted flag */ + int frames; /**< Number of frames sent */ + int efps; /**< Estimated frame-rate */ +}; + + +/** + * Video stream - receiver/decoder direction + + \verbatim + + Processing decoder pipeline: + + .~~~~~~~~. .--------. .---------. .---------. + | _o_ | | | | | | | + | | |<--| vidisp |<--| vidfilt |<--| decoder |<--- RTP + | /'\ | | | | | | | + '~~~~~~~~' '--------' '---------' '---------' + + \endverbatim + + */ +struct vrx { + struct video *video; /**< Parent */ + const struct vidcodec *vc; /**< Current video decoder */ + struct viddec_state *dec; /**< Video decoder state */ + struct vidisp_prm vidisp_prm; /**< Video display parameters */ + struct vidisp_st *vidisp; /**< Video display */ + struct lock *lock; /**< Lock for decoder */ + struct list filtl; /**< Filters in decoding order */ + enum vidorient orient; /**< Display orientation */ + char device[64]; + bool fullscreen; /**< Fullscreen flag */ + int pt_rx; /**< Incoming RTP payload type */ + int frames; /**< Number of frames received */ + int efps; /**< Estimated frame-rate */ +}; + + +/** Generic Video stream */ +struct video { + MAGIC_DECL /**< Magic number for debugging */ + struct config_video cfg;/**< Video configuration */ + struct stream *strm; /**< Generic media stream */ + struct vtx vtx; /**< Transmit/encoder direction */ + struct vrx vrx; /**< Receive/decoder direction */ + struct tmr tmr; /**< Timer for frame-rate estimation */ + char *peer; /**< Peer URI */ + bool nack_pli; /**< Send NACK/PLI to peer */ +}; + + +static void video_destructor(void *arg) +{ + struct video *v = arg; + struct vtx *vtx = &v->vtx; + struct vrx *vrx = &v->vrx; + + /* transmit */ + mem_deref(vtx->vsrc); + lock_write_get(vtx->lock); + mem_deref(vtx->frame); + mem_deref(vtx->mute_frame); + mem_deref(vtx->enc); + mem_deref(vtx->mb); + list_flush(&vtx->filtl); + lock_rel(vtx->lock); + mem_deref(vtx->lock); + + /* receive */ + lock_write_get(vrx->lock); + mem_deref(vrx->dec); + mem_deref(vrx->vidisp); + list_flush(&vrx->filtl); + lock_rel(vrx->lock); + mem_deref(vrx->lock); + + tmr_cancel(&v->tmr); + mem_deref(v->strm); + mem_deref(v->peer); +} + + +static int get_fps(const struct video *v) +{ + const char *attr; + + /* RFC4566 */ + attr = sdp_media_rattr(stream_sdpmedia(v->strm), "framerate"); + if (attr) { + /* NOTE: fractional values are ignored */ + const double fps = atof(attr); + return (int)fps; + } + else + return v->cfg.fps; +} + + +static int packet_handler(bool marker, const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len, void *arg) +{ + struct vtx *tx = arg; + int err = 0; + + tx->mb->pos = tx->mb->end = STREAM_PRESZ; + + if (hdr_len) err |= mbuf_write_mem(tx->mb, hdr, hdr_len); + if (pld_len) err |= mbuf_write_mem(tx->mb, pld, pld_len); + + tx->mb->pos = STREAM_PRESZ; + + if (!err) { + err = stream_send(tx->video->strm, marker, -1, + tx->ts_tx, tx->mb); + } + + return err; +} + + +/** + * Encode video and send via RTP stream + * + * @note This function has REAL-TIME properties + * + * @param vtx Video transmit object + * @param frame Video frame to send + */ +static void encode_rtp_send(struct vtx *vtx, struct vidframe *frame) +{ + struct le *le; + int err = 0; + + if (!vtx->enc) + return; + + lock_write_get(vtx->lock); + + /* Convert image */ + if (frame->fmt != VID_FMT_YUV420P) { + + vtx->vsrc_size = frame->size; + + if (!vtx->frame) { + + err = vidframe_alloc(&vtx->frame, VID_FMT_YUV420P, + &vtx->vsrc_size); + if (err) + goto unlock; + } + + vidconv(vtx->frame, frame, 0); + frame = vtx->frame; + } + + /* Process video frame through all Video Filters */ + for (le = vtx->filtl.head; le; le = le->next) { + + struct vidfilt_enc_st *st = le->data; + + if (st->vf && st->vf->ench) + err |= st->vf->ench(st, frame); + } + + unlock: + lock_rel(vtx->lock); + + if (err) + return; + + /* Encode the whole picture frame */ + err = vtx->vc->ench(vtx->enc, vtx->picup, frame, packet_handler, vtx); + if (err) { + DEBUG_WARNING("encode: %m\n", err); + return; + } + + vtx->ts_tx += (SRATE/vtx->vsrc_prm.fps); + vtx->picup = false; +} + + +/** + * Read frames from video source + * + * @param frame Video frame + * @param arg Handler argument + * + * @note This function has REAL-TIME properties + */ +static void vidsrc_frame_handler(struct vidframe *frame, void *arg) +{ + struct vtx *vtx = arg; + + ++vtx->frames; + + /* Is the video muted? If so insert video mute image */ + if (vtx->muted) + frame = vtx->mute_frame; + + if (vtx->muted && vtx->muted_frames >= MAX_MUTED_FRAMES) + return; + + /* Encode and send */ + encode_rtp_send(vtx, frame); + vtx->muted_frames++; +} + + +static void vidsrc_error_handler(int err, void *arg) +{ + struct vtx *vtx = arg; + + DEBUG_WARNING("Video-source error: %m\n", err); + + vtx->vsrc = mem_deref(vtx->vsrc); +} + + +static int vtx_alloc(struct vtx *vtx, struct video *video) +{ + int err; + + err = lock_alloc(&vtx->lock); + if (err) + return err; + + vtx->mb = mbuf_alloc(STREAM_PRESZ + 512); + if (!vtx->mb) + return ENOMEM; + + vtx->video = video; + vtx->ts_tx = 160; + + str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device)); + + return err; +} + + +static int vrx_alloc(struct vrx *vrx, struct video *video) +{ + int err; + + err = lock_alloc(&vrx->lock); + if (err) + return err; + + vrx->video = video; + vrx->pt_rx = -1; + vrx->orient = VIDORIENT_PORTRAIT; + + str_ncpy(vrx->device, video->cfg.disp_dev, sizeof(vrx->device)); + + return err; +} + + +/** + * Decode incoming RTP packets using the Video decoder + * + * NOTE: mb=NULL if no packet received + * + * @param vrx Video receive object + * @param hdr RTP Header + * @param mb Buffer with RTP payload + * + * @return 0 if success, otherwise errorcode + */ +static int video_stream_decode(struct vrx *vrx, const struct rtp_header *hdr, + struct mbuf *mb) +{ + struct video *v = vrx->video; + struct vidframe frame; + struct le *le; + int err = 0; + + if (!hdr || !mbuf_get_left(mb)) + return 0; + + lock_write_get(vrx->lock); + + /* No decoder set */ + if (!vrx->dec) { + DEBUG_WARNING("No video decoder!\n"); + goto out; + } + + frame.data[0] = NULL; + err = vrx->vc->dech(vrx->dec, &frame, hdr->m, hdr->seq, mb); + if (err) { + + if (err != EPROTO) { + DEBUG_WARNING("%s decode error" + " (seq=%u, %u bytes): %m\n", + vrx->vc->name, hdr->seq, + mbuf_get_left(mb), err); + } + + /* send RTCP FIR to peer */ + stream_send_fir(v->strm, v->nack_pli); + + /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */ + + goto out; + } + + /* Got a full picture-frame? */ + if (!vidframe_isvalid(&frame)) + goto out; + + /* Process video frame through all Video Filters */ + for (le = vrx->filtl.head; le; le = le->next) { + + struct vidfilt_dec_st *st = le->data; + + if (st->vf && st->vf->dech) + err |= st->vf->dech(st, &frame); + } + + err = vidisp_display(vrx->vidisp, v->peer, &frame); + + ++vrx->frames; + +out: + lock_rel(vrx->lock); + + return err; +} + + +static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new) +{ + const struct sdp_format *lc; + + lc = sdp_media_lformat(stream_sdpmedia(v->strm), pt_new); + if (!lc) + return ENOENT; + + if (pt_old != (uint8_t)-1) { + info("Video decoder changed payload %u -> %u\n", + pt_old, pt_new); + } + + v->vrx.pt_rx = pt_new; + + return video_decoder_set(v, lc->data, lc->pt, lc->rparams); +} + + +/* Handle incoming stream data from the network */ +static void stream_recv_handler(const struct rtp_header *hdr, + struct mbuf *mb, void *arg) +{ + struct video *v = arg; + int err; + + if (!mb) + goto out; + + /* Video payload-type changed? */ + if (hdr->pt == v->vrx.pt_rx) + goto out; + + err = pt_handler(v, v->vrx.pt_rx, hdr->pt); + if (err) + return; + + out: + (void)video_stream_decode(&v->vrx, hdr, mb); +} + + +static void rtcp_handler(struct rtcp_msg *msg, void *arg) +{ + struct video *v = arg; + + switch (msg->hdr.pt) { + + case RTCP_FIR: + v->vtx.picup = true; + break; + + case RTCP_PSFB: + if (msg->hdr.count == RTCP_PSFB_PLI) + v->vtx.picup = true; + break; + + case RTCP_RTPFB: + if (msg->hdr.count == RTCP_RTPFB_GNACK) + v->vtx.picup = true; + break; + + default: + break; + } +} + + +static int vtx_print_pipeline(struct re_printf *pf, const struct vtx *vtx) +{ + struct le *le; + struct vidsrc *vs; + int err; + + if (!vtx) + return 0; + + vs = vidsrc_get(vtx->vsrc); + + err = re_hprintf(pf, "video tx pipeline: %10s", + vs ? vs->name : "src"); + + for (le = list_head(&vtx->filtl); le; le = le->next) { + struct vidfilt_enc_st *st = le->data; + + if (st->vf->ench) + err |= re_hprintf(pf, " ---> %s", st->vf->name); + } + + err |= re_hprintf(pf, " ---> %s\n", + vtx->vc ? vtx->vc->name : "encoder"); + + return err; +} + + +static int vrx_print_pipeline(struct re_printf *pf, const struct vrx *vrx) +{ + struct le *le; + struct vidisp *vd; + int err; + + if (!vrx) + return 0; + + vd = vidisp_get(vrx->vidisp); + + err = re_hprintf(pf, "video rx pipeline: %10s", + vd ? vd->name : "disp"); + + for (le = list_head(&vrx->filtl); le; le = le->next) { + struct vidfilt_dec_st *st = le->data; + + if (st->vf->dech) + err |= re_hprintf(pf, " <--- %s", st->vf->name); + } + + err |= re_hprintf(pf, " <--- %s\n", + vrx->vc ? vrx->vc->name : "decoder"); + + return err; +} + + +int video_alloc(struct video **vp, const struct config *cfg, + struct call *call, struct sdp_session *sdp_sess, int label, + const struct mnat *mnat, struct mnat_sess *mnat_sess, + const struct menc *menc, struct menc_sess *menc_sess, + const char *content, const struct list *vidcodecl) +{ + struct video *v; + struct le *le; + int err = 0; + + if (!vp || !cfg) + return EINVAL; + + v = mem_zalloc(sizeof(*v), video_destructor); + if (!v) + return ENOMEM; + + MAGIC_INIT(v); + + v->cfg = cfg->video; + tmr_init(&v->tmr); + + err = stream_alloc(&v->strm, &cfg->avt, call, sdp_sess, "video", label, + mnat, mnat_sess, menc, menc_sess, + call_localuri(call), + stream_recv_handler, rtcp_handler, v); + if (err) + goto out; + + if (cfg->avt.rtp_bw.max >= AUDIO_BANDWIDTH) { + stream_set_bw(v->strm, cfg->avt.rtp_bw.max - AUDIO_BANDWIDTH); + } + + err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true, + "framerate", "%d", v->cfg.fps); + + /* RFC 4585 */ + err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true, + "rtcp-fb", "* nack pli"); + + /* RFC 4796 */ + if (content) { + err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true, + "content", "%s", content); + } + + if (err) + goto out; + + err = vtx_alloc(&v->vtx, v); + err |= vrx_alloc(&v->vrx, v); + if (err) + goto out; + + /* Video codecs */ + for (le = list_head(vidcodecl); le; le = le->next) { + struct vidcodec *vc = le->data; + err |= sdp_format_add(NULL, stream_sdpmedia(v->strm), false, + vc->pt, vc->name, 90000, 1, + vc->fmtp_ench, vc->fmtp_cmph, vc, false, + "%s", vc->fmtp); + } + + /* Video filters */ + for (le = list_head(vidfilt_list()); le; le = le->next) { + struct vidfilt *vf = le->data; + void *ctx = NULL; + + err |= vidfilt_enc_append(&v->vtx.filtl, &ctx, vf); + err |= vidfilt_dec_append(&v->vrx.filtl, &ctx, vf); + if (err) { + DEBUG_WARNING("video-filter '%s' failed (%m)\n", + vf->name, err); + break; + } + } + + out: + if (err) + mem_deref(v); + else + *vp = v; + + return err; +} + + +static void vidisp_resize_handler(const struct vidsz *sz, void *arg) +{ + struct vrx *vrx = arg; + (void)vrx; + + info("video: display resized: %u x %u\n", sz->w, sz->h); + + /* XXX: update wanted picturesize and send re-invite to peer */ +} + + +/* Set the video display - can be called multiple times */ +static int set_vidisp(struct vrx *vrx) +{ + struct vidisp *vd; + + vrx->vidisp = mem_deref(vrx->vidisp); + vrx->vidisp_prm.view = NULL; + + vd = (struct vidisp *)vidisp_find(vrx->video->cfg.disp_mod); + if (!vd) + return ENOENT; + + return vd->alloch(&vrx->vidisp, vd, &vrx->vidisp_prm, vrx->device, + vidisp_resize_handler, vrx); +} + + +/* Set the encoder format - can be called multiple times */ +static int set_encoder_format(struct vtx *vtx, const char *src, + const char *dev, struct vidsz *size) +{ + struct vidsrc *vs = (struct vidsrc *)vidsrc_find(src); + int err; + + if (!vs) + return ENOENT; + + vtx->vsrc_size = *size; + vtx->vsrc_prm.fps = get_fps(vtx->video); + vtx->vsrc_prm.orient = VIDORIENT_PORTRAIT; + + vtx->vsrc = mem_deref(vtx->vsrc); + + err = vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm, + &vtx->vsrc_size, NULL, dev, vidsrc_frame_handler, + vidsrc_error_handler, vtx); + if (err) { + info("video: no video source '%s': %m\n", src, err); + return err; + } + + vtx->mute_frame = mem_deref(vtx->mute_frame); + err = vidframe_alloc(&vtx->mute_frame, VID_FMT_YUV420P, size); + if (err) + return err; + + vidframe_fill(vtx->mute_frame, 0xff, 0xff, 0xff); + + return err; +} + + +enum {TMR_INTERVAL = 5}; +static void tmr_handler(void *arg) +{ + struct video *v = arg; + + tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v); + + /* Estimate framerates */ + v->vtx.efps = v->vtx.frames / TMR_INTERVAL; + v->vrx.efps = v->vrx.frames / TMR_INTERVAL; + + v->vtx.frames = 0; + v->vrx.frames = 0; +} + + +int video_start(struct video *v, const char *peer) +{ + struct vidsz size; + int err; + + if (!v) + return EINVAL; + + if (peer) { + mem_deref(v->peer); + err = str_dup(&v->peer, peer); + if (err) + return err; + } + + stream_set_srate(v->strm, SRATE, SRATE); + + err = set_vidisp(&v->vrx); + if (err) { + DEBUG_WARNING("could not set vidisp '%s': %m\n", + v->vrx.device, err); + } + + size.w = v->cfg.width; + size.h = v->cfg.height; + err = set_encoder_format(&v->vtx, v->cfg.src_mod, + v->vtx.device, &size); + if (err) { + DEBUG_WARNING("could not set encoder format to" + " [%u x %u] %m\n", + size.w, size.h, err); + } + + tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v); + + if (v->vtx.vc && v->vrx.vc) { + info("%H%H", + vtx_print_pipeline, &v->vtx, + vrx_print_pipeline, &v->vrx); + } + + return 0; +} + + +void video_stop(struct video *v) +{ + if (!v) + return; + + v->vtx.vsrc = mem_deref(v->vtx.vsrc); +} + + +/** + * Mute the video stream + * + * @param v Video stream + * @param muted True to mute, false to un-mute + */ +void video_mute(struct video *v, bool muted) +{ + struct vtx *vtx; + + if (!v) + return; + + vtx = &v->vtx; + + vtx->muted = muted; + vtx->muted_frames = 0; + vtx->picup = true; + + video_update_picture(v); +} + + +static int vidisp_update(struct vrx *vrx) +{ + struct vidisp *vd = vidisp_get(vrx->vidisp); + int err = 0; + + if (vd->updateh) { + err = vd->updateh(vrx->vidisp, vrx->fullscreen, + vrx->orient, NULL); + } + + return err; +} + + +/** + * Enable video display fullscreen + * + * @param v Video stream + * @param fs True for fullscreen, otherwise false + * + * @return 0 if success, otherwise errorcode + */ +int video_set_fullscreen(struct video *v, bool fs) +{ + if (!v) + return EINVAL; + + v->vrx.fullscreen = fs; + + return vidisp_update(&v->vrx); +} + + +static void vidsrc_update(struct vtx *vtx, const char *dev) +{ + struct vidsrc *vs = vidsrc_get(vtx->vsrc); + + if (vs && vs->updateh) + vs->updateh(vtx->vsrc, &vtx->vsrc_prm, dev); +} + + +/** + * Set the orientation of the Video source and display + * + * @param v Video stream + * @param orient Video orientation (enum vidorient) + * + * @return 0 if success, otherwise errorcode + */ +int video_set_orient(struct video *v, int orient) +{ + if (!v) + return EINVAL; + + v->vtx.vsrc_prm.orient = v->vrx.orient = orient; + vidsrc_update(&v->vtx, NULL); + return vidisp_update(&v->vrx); +} + + +int video_encoder_set(struct video *v, struct vidcodec *vc, + int pt_tx, const char *params) +{ + struct vtx *vtx; + int err = 0; + + if (!v) + return EINVAL; + + vtx = &v->vtx; + + if (vc != vtx->vc) { + + struct videnc_param prm; + + prm.bitrate = v->cfg.bitrate; + prm.pktsize = 1024; + prm.fps = get_fps(v); + prm.max_fs = -1; + + info("Set video encoder: %s %s (%u bit/s, %u fps)\n", + vc->name, vc->variant, prm.bitrate, prm.fps); + + vtx->enc = mem_deref(vtx->enc); + err = vc->encupdh(&vtx->enc, vc, &prm, params); + if (err) { + DEBUG_WARNING("encoder alloc: %m\n", err); + return err; + } + + vtx->vc = vc; + } + + stream_update_encoder(v->strm, pt_tx); + + return err; +} + + +int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx, + const char *fmtp) +{ + struct vrx *vrx; + int err = 0; + + if (!v) + return EINVAL; + + vrx = &v->vrx; + + vrx->pt_rx = pt_rx; + + if (vc != vrx->vc) { + + info("Set video decoder: %s %s\n", vc->name, vc->variant); + + vrx->dec = mem_deref(vrx->dec); + + err = vc->decupdh(&vrx->dec, vc, fmtp); + if (err) { + DEBUG_WARNING("decoder alloc: %m\n", err); + return err; + } + + vrx->vc = vc; + } + + return err; +} + + +/** + * Use the next video encoder in the local list of negotiated codecs + * + * @param video Video object + */ +void video_encoder_cycle(struct video *video) +{ + const struct sdp_format *rc = NULL; + + if (!video) + return; + + rc = sdp_media_format_cycle(stream_sdpmedia(video_strm(video))); + if (!rc) { + info("cycle video: no remote codec found\n"); + return; + } + + (void)video_encoder_set(video, rc->data, rc->pt, rc->params); +} + + +struct stream *video_strm(const struct video *v) +{ + return v ? v->strm : NULL; +} + + +void video_update_picture(struct video *v) +{ + if (!v) + return; + v->vtx.picup = true; +} + + +/** + * Get the driver-specific view of the video stream + * + * @param v Video stream + * + * @return Opaque view + */ +void *video_view(const struct video *v) +{ + if (!v) + return NULL; + + return v->vrx.vidisp_prm.view; +} + + +/** + * Set the current Video Source device name + * + * @param v Video stream + * @param dev Device name + */ +void video_vidsrc_set_device(struct video *v, const char *dev) +{ + if (!v) + return; + + vidsrc_update(&v->vtx, dev); +} + + +static bool sdprattr_contains(struct stream *s, const char *name, + const char *str) +{ + const char *attr = sdp_media_rattr(stream_sdpmedia(s), name); + return attr ? (NULL != strstr(attr, str)) : false; +} + + +void video_sdp_attr_decode(struct video *v) +{ + if (!v) + return; + + /* RFC 4585 */ + v->nack_pli = sdprattr_contains(v->strm, "rtcp-fb", "nack"); +} + + +int video_debug(struct re_printf *pf, const struct video *v) +{ + const struct vtx *vtx; + const struct vrx *vrx; + int err; + + if (!v) + return 0; + + vtx = &v->vtx; + vrx = &v->vrx; + + err = re_hprintf(pf, "\n--- Video stream ---\n"); + err |= re_hprintf(pf, " tx: %u x %u, fps=%d\n", + vtx->vsrc_size.w, + vtx->vsrc_size.h, vtx->vsrc_prm.fps); + err |= re_hprintf(pf, " rx: pt=%d\n", vrx->pt_rx); + + if (!list_isempty(vidfilt_list())) { + err |= vtx_print_pipeline(pf, vtx); + err |= vrx_print_pipeline(pf, vrx); + } + + err |= stream_debug(pf, v->strm); + + return err; +} + + +int video_print(struct re_printf *pf, const struct video *v) +{ + if (!v) + return 0; + + return re_hprintf(pf, " efps=%d/%d", v->vtx.efps, v->vrx.efps); +} + + +int video_set_source(struct video *v, const char *name, const char *dev) +{ + struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name); + struct vtx *vtx; + + if (!v) + return EINVAL; + + if (!vs) + return ENOENT; + + vtx = &v->vtx; + + vtx->vsrc = mem_deref(vtx->vsrc); + + return vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm, + &vtx->vsrc_size, NULL, dev, + vidsrc_frame_handler, vidsrc_error_handler, vtx); +} + + +void video_set_devicename(struct video *v, const char *src, const char *disp) +{ + if (!v) + return; + + str_ncpy(v->vtx.device, src, sizeof(v->vtx.device)); + str_ncpy(v->vrx.device, disp, sizeof(v->vrx.device)); +} diff --git a/src/vidfilt.c b/src/vidfilt.c new file mode 100644 index 0000000..a8d8426 --- /dev/null +++ b/src/vidfilt.c @@ -0,0 +1,116 @@ +/** + * @file vidfilt.c Video Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static struct list vfl; + + +/** + * Register a new Video Filter + * + * @param vf Video Filter to register + */ +void vidfilt_register(struct vidfilt *vf) +{ + if (!vf) + return; + + list_append(&vfl, &vf->le, vf); + + info("vidfilt: %s\n", vf->name); +} + + +/** + * Unregister a Video Filter + * + * @param vf Video Filter to unregister + */ +void vidfilt_unregister(struct vidfilt *vf) +{ + if (!vf) + return; + + list_unlink(&vf->le); +} + + +/** + * Get the list of registered Video Filters + * + * @return List of Video Filters + */ +struct list *vidfilt_list(void) +{ + return &vfl; +} + + +static void vidfilt_enc_destructor(void *arg) +{ + struct vidfilt_enc_st *st = arg; + + list_unlink(&st->le); +} + + +int vidfilt_enc_append(struct list *filtl, void **ctx, + const struct vidfilt *vf) +{ + struct vidfilt_enc_st *st = NULL; + int err; + + if (vf->encupdh) { + err = vf->encupdh(&st, ctx, vf); + if (err) + return err; + } + else { + st = mem_zalloc(sizeof(*st), vidfilt_enc_destructor); + if (!st) + return ENOMEM; + } + + st->vf = vf; + list_append(filtl, &st->le, st); + + return 0; +} + + +static void vidfilt_dec_destructor(void *arg) +{ + struct vidfilt_dec_st *st = arg; + + list_unlink(&st->le); +} + + +int vidfilt_dec_append(struct list *filtl, void **ctx, + const struct vidfilt *vf) +{ + struct vidfilt_dec_st *st = NULL; + int err; + + if (vf->decupdh) { + err = vf->decupdh(&st, ctx, vf); + if (err) + return err; + } + else { + st = mem_zalloc(sizeof(*st), vidfilt_dec_destructor); + if (!st) + return ENOMEM; + } + + st->vf = vf; + list_append(filtl, &st->le, st); + + return 0; +} diff --git a/src/vidisp.c b/src/vidisp.c new file mode 100644 index 0000000..94ceee5 --- /dev/null +++ b/src/vidisp.c @@ -0,0 +1,132 @@ +/** + * @file vidisp.c Video Display + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** Video Display state */ +struct vidisp_st { + struct vidisp *vd; /**< Video Display */ +}; + + +static struct list vidispl = LIST_INIT; + + +static void destructor(void *arg) +{ + struct vidisp *vd = arg; + + list_unlink(&vd->le); +} + + +/** + * Register a Video output display + * + * @param vp Pointer to allocated Video Display + * @param name Name of Video Display + * @param alloch Allocation handler + * @param updateh Update handler + * @param disph Display handler + * @param hideh Hide-window handler + * + * @return 0 if success, otherwise errorcode + */ +int vidisp_register(struct vidisp **vp, const char *name, + vidisp_alloc_h *alloch, vidisp_update_h *updateh, + vidisp_disp_h *disph, vidisp_hide_h *hideh) +{ + struct vidisp *vd; + + if (!vp) + return EINVAL; + + vd = mem_zalloc(sizeof(*vd), destructor); + if (!vd) + return ENOMEM; + + list_append(&vidispl, &vd->le, vd); + + vd->name = name; + vd->alloch = alloch; + vd->updateh = updateh; + vd->disph = disph; + vd->hideh = hideh; + + info("vidisp: %s\n", name); + + *vp = vd; + return 0; +} + + +const struct vidisp *vidisp_find(const char *name) +{ + struct le *le; + + for (le = vidispl.head; le; le = le->next) { + struct vidisp *vd = le->data; + + if (str_isset(name) && 0 != str_casecmp(name, vd->name)) + continue; + + /* Found */ + return vd; + } + + return NULL; +} + + +/** + * Allocate a video display state + * + * @param stp Pointer to allocated display state + * @param name Name of video display + * @param prm Video display parameters (optional) + * @param dev Display device + * @param resizeh Window resize handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int vidisp_alloc(struct vidisp_st **stp, const char *name, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp *vd = (struct vidisp *)vidisp_find(name); + if (!vd) + return ENOENT; + + return vd->alloch(stp, vd, prm, dev, resizeh, arg); +} + + +/** + * Display a video frame + * + * @param st Video display state + * @param title Display title + * @param frame Video frame + * + * @return 0 if success, otherwise errorcode + */ +int vidisp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + if (!st || !frame) + return EINVAL; + + return st->vd->disph(st, title, frame); +} + + +struct vidisp *vidisp_get(struct vidisp_st *st) +{ + return st ? st->vd : NULL; +} diff --git a/src/vidsrc.c b/src/vidsrc.c new file mode 100644 index 0000000..e0bb297 --- /dev/null +++ b/src/vidsrc.c @@ -0,0 +1,134 @@ +/** + * @file vidsrc.c Video Source + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** Video Source state */ +struct vidsrc_st { + struct vidsrc *vs; /**< Video Source */ +}; + + +static struct list vidsrcl = LIST_INIT; + + +static void destructor(void *arg) +{ + struct vidsrc *vs = arg; + + list_unlink(&vs->le); +} + + +/** + * Register a Video Source + * + * @param vsp Pointer to allocated Video Source + * @param name Name of Video Source + * @param alloch Allocation handler + * @param updateh Update handler + * + * @return 0 if success, otherwise errorcode + */ +int vidsrc_register(struct vidsrc **vsp, const char *name, + vidsrc_alloc_h *alloch, vidsrc_update_h *updateh) +{ + struct vidsrc *vs; + + if (!vsp) + return EINVAL; + + vs = mem_zalloc(sizeof(*vs), destructor); + if (!vs) + return ENOMEM; + + list_append(&vidsrcl, &vs->le, vs); + + vs->name = name; + vs->alloch = alloch; + vs->updateh = updateh; + + info("vidsrc: %s\n", name); + + *vsp = vs; + + return 0; +} + + +/** + * Find a Video Source by name + * + * @param name Name of the Video Source to find + * + * @return Matching Video Source if found, otherwise NULL + */ +const struct vidsrc *vidsrc_find(const char *name) +{ + struct le *le; + + for (le=vidsrcl.head; le; le=le->next) { + + struct vidsrc *vs = le->data; + + if (str_isset(name) && 0 != str_casecmp(name, vs->name)) + continue; + + return vs; + } + + return NULL; +} + + +/** + * Allocate a new video source state + * + * @param stp Pointer to allocated state + * @param name Name of the video source + * @param ctx Optional media context + * @param prm Video source parameters + * @param size Wanted video size of the source + * @param fmt Format parameter + * @param dev Video device + * @param frameh Video frame handler + * @param errorh Error handler (optional) + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int vidsrc_alloc(struct vidsrc_st **stp, const char *name, + struct media_ctx **ctx, struct vidsrc_prm *prm, + const struct vidsz *size, const char *fmt, const char *dev, + vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg) +{ + struct vidsrc *vs = (struct vidsrc *)vidsrc_find(name); + if (!vs) + return ENOENT; + + return vs->alloch(stp, vs, ctx, prm, size, fmt, dev, + frameh, errorh, arg); +} + + +/** + * Get the list of Video Sources + * + * @return List of Video Sources + */ +struct list *vidsrc_list(void) +{ + return &vidsrcl; +} + + +struct vidsrc *vidsrc_get(struct vidsrc_st *st) +{ + return st ? st->vs : NULL; +} |