diff options
author | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 22:22:59 +0530 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2018-01-08 22:22:59 +0530 |
commit | 766bb4acdda738e450630c92d9c37e6cb42d9423 (patch) | |
tree | 8277c899ed928c69dfd1251bf889af6c78400f84 |
Import baresip_0.5.7.orig.tar.gz
[dgit import orig baresip_0.5.7.orig.tar.gz]
434 files changed, 74411 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..392a2cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Object files +*.o +*.ko + +# Libraries +*.lib +*.a + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +baresip +selftest + +# Generated files +src/static.c +build* +*.pc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..093f30f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: c + +os: + - linux + - osx + +compiler: + - clang + - gcc + +env: + - LIBRE=re LIBREM=rem + +sudo: require + +addons: + apt: + packages: + libssl-dev + +install: + - git clone https://github.com/creytiv/re.git + - git clone https://github.com/creytiv/rem.git + - curl -OL 'https://github.com/alfredh/pytools/raw/master/ccheck.py' + - for p in ${LIBRE} ${LIBREM}; do cd $p && sudo PATH="$PATH" make install && cd - && sudo rm -Rf $p; done + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo ldconfig; fi + +script: + - make V=1 CCACHE= EXTRA_CFLAGS=-Werror info test modules + - python2 ccheck.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f5b4c25 --- /dev/null +++ b/Makefile @@ -0,0 +1,305 @@ +# +# 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.5.7 +DESCR := "Baresip is a modular SIP User-Agent with audio and video support" + +# Verbose and silent build modes +ifeq ($(V),) +HIDE=@ +endif + +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) + +# XXX: common for C/C++ +CPPFLAGS += -DHAVE_INTTYPES_H + +ifneq ($(LIBREM_PATH),) +CLANG_OPTIONS += -I$(LIBREM_PATH)/include +endif + +ifeq ($(OS),win32) +STATIC := yes +endif + +ifeq ($(OS),freebsd) +ifneq ($(SYSROOT),) +CFLAGS += -I$(SYSROOT)/local/include +endif +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) +TEST_BIN := selftest$(BIN_SUFFIX) +SHARED := lib$(PROJECT)$(LIB_SUFFIX) +STATICLIB := libbaresip.a +ifeq ($(STATIC),) +MOD_BINS:= $(patsubst %,%$(MOD_SUFFIX),$(MODULES)) +endif +APP_MK := src/srcs.mk +TEST_MK := test/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 $(TEST_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) + +LIB_OBJS := $(OBJS) $(MOD_OBJS) + +TEST_OBJS := $(patsubst %.c,$(BUILD)/test/%.o,$(filter %.c,$(TEST_SRCS))) +TEST_OBJS += $(patsubst %.cpp,$(BUILD)/test/%.o,$(filter %.cpp,$(TEST_SRCS))) + +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 +LIBS += -L$(SYSROOT)/lib + +ifeq ($(OS),win32) +TEST_LIBS += -static-libgcc +endif + + +-include $(APP_OBJS:.o=.d) + +-include $(TEST_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): $(LIB_OBJS) + @echo " LD $@" + $(HIDE)$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -L$(LIBRE_SO) -lre $(LIBS) -o $@ + +$(STATICLIB): $(LIB_OBJS) + @echo " AR $@" + @rm -f $@; $(AR) $(AFLAGS) $@ $^ +ifneq ($(RANLIB),) + @echo " RANLIB $@" + $(HIDE)$(RANLIB) $@ +endif + +libbaresip.pc: + @echo 'prefix='$(PREFIX) > libbaresip.pc + @echo 'exec_prefix=$${prefix}' >> libbaresip.pc + @echo 'libdir=$${prefix}/lib' >> libbaresip.pc + @echo 'includedir=$${prefix}/include' >> libbaresip.pc + @echo '' >> libbaresip.pc + @echo 'Name: libbaresip' >> libbaresip.pc + @echo 'Description: $(DESCR)' >> libbaresip.pc + @echo 'Version: '$(VERSION) >> libbaresip.pc + @echo 'URL: http://www.creytiv.com/baresip.html' >> libbaresip.pc + @echo 'Libs: -L$${libdir} -lbaresip' >> libbaresip.pc + @echo 'Cflags: -I$${includedir}' >> libbaresip.pc + +# GPROF requires static linking +$(BIN): $(APP_OBJS) + @echo " LD $@" +ifneq ($(GPROF),) + $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ ../re/libre.a $(LIBS) -o $@ +else + $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ \ + -L$(LIBRE_SO) -lre $(LIBS) -o $@ +endif + + +.PHONY: test +test: $(TEST_BIN) + ./$(TEST_BIN) + +$(TEST_BIN): $(STATICLIB) $(TEST_OBJS) + @echo " LD $@" + $(HIDE)$(CXX) $(LFLAGS) $(TEST_OBJS) \ + -L$(LIBRE_SO) -L. \ + -l$(PROJECT) -lre $(LIBS) $(TEST_LIBS) -o $@ + +$(BUILD)/%.o: %.c $(BUILD) Makefile $(APP_MK) + @echo " CC $@" + $(HIDE)$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS) + +$(BUILD)/%.o: %.cpp $(BUILD) Makefile $(APP_MK) + @echo " CXX $@" + $(HIDE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ $(DFLAGS) + +$(BUILD)/%.o: %.m $(BUILD) Makefile $(APP_MK) + @echo " OC $@" + $(HIDE)$(CC) $(CFLAGS) $(OBJCFLAGS) -c $< -o $@ $(DFLAGS) + +$(BUILD)/%.o: %.S $(BUILD) Makefile $(APP_MK) + @echo " AS $@" + $(HIDE)$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS) + +$(BUILD): Makefile + @mkdir -p $(BUILD)/src $(MOD_BLD) $(BUILD)/test/mock $(BUILD)/test/sip + @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) libbaresip.pc + @mkdir -p $(DESTDIR)$(INCDIR) + $(INSTALL) -Cm 0644 include/baresip.h $(DESTDIR)$(INCDIR) + @mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(LIBDIR)/pkgconfig + $(INSTALL) -m 0644 $(SHARED) $(DESTDIR)$(LIBDIR) + $(INSTALL) -m 0644 libbaresip.pc $(DESTDIR)$(LIBDIR)/pkgconfig + +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) + @rm -f $(DESTDIR)$(PREFIX)/lib/$(SHARED) + @rm -f $(DESTDIR)$(PREFIX)/lib/$(STATICLIB) + @rm -f $(DESTDIR)$(PREFIX)/lib/pkgconfig/libbaresip.pc + +.PHONY: clean +clean: + @rm -rf $(BIN) $(MOD_BINS) $(SHARED) $(BUILD) $(TEST_BIN) \ + $(STATICLIB) libbaresip.pc + @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/README.md b/README.md new file mode 100644 index 0000000..8edddee --- /dev/null +++ b/README.md @@ -0,0 +1,441 @@ +baresip README +============== + + +![Baresip Logo](https://raw.githubusercontent.com/alfredh/baresip/master/share/logo.png) + + +Baresip is a portable and modular SIP User-Agent with audio and video support. +Copyright (c) 2010 - 2017 Creytiv.com +Distributed under BSD license + + +[![Build Status](https://travis-ci.org/alfredh/baresip.svg?branch=master)](https://travis-ci.org/alfredh/baresip) + + +## Features: + +* Call features: + - Unlimited number of SIP accounts + - Unlimited number of calls + - Unattended call transfer + - Auto answer + - Call hold and resume + - Microphone mute + - Call waiting + - Call recording + - Peer to peer calls + - Video calls + - Instant Messaging + - Custom ring tones + - Repeat last call (redial) + - Message Waiting Indication (MWI) + - Address book with presence + +* Signaling: + - SIP protocol support + - SIP outbound protocol for NAT-traversal + - SIP Re-invite + - SIP Routes + - SIP early media support + - DNS NAPTR/SRV support + - Multiple accounts support + - DTMF support (RTP, SIP INFO) + +* Security: + - Signalling encryption (TLS) + - Audio and video encryption (Secure RTP) + - DTLS-SRTP key exchange protocol + - ZRTP key exchange protocol + - SDES key exchange protocol + +* Audio: + - Low latency audio pipeline + - High definition audio codecs + - Audio device configuration + - Audio filter plugins + - Internal audio resampler for fixed sampling rates + - Linear 16 bit wave format support for ringtones + - Packet loss concealment (PLC) + - Configurable ringtone playback device + - Automatic gain control (AGC) and Noise reducation + - Acoustic echo control (AEC) + +* Audio-codecs: + - AMR narrowband, AMR wideband + - BroadVoice32 BV32 + - Codec2 + - G.711 + - G.722 + - G.726 + - GSM + - iLBC + - iSAC + - L16 + - MPA + - Opus + - Silk + - Speex + +* Audio-drivers: + - Advanced Linux Sound Architecture (ALSA) audio-driver + - Android OpenSLES audio-driver + - Gstreamer playbin input audio-driver + - JACK Audio Connection Kit audio-driver + - MacOSX/iOS coreaudio/audiounit audio-driver + - Open Sound System (OSS) audio-driver + - Portaudio audio-driver + - Windows winwave audio-driver + +* Video: + - Support for H.265, H.264, H.263, VP8, VP9, MPEG-4 Video + - Configurable resolution/framerate/bitrate + - Configurable video input/output + - Support for asymmetric video + +* Video-codecs: + - H.265 + - H.264 + - H.263 + - VP8 + - VP9 + - MPEG-4 + +* Video-drivers: + - iOS avcapture video-source + - FFmpeg/libav libavformat/avdevice input + - Cairo video-source test module + - Direct Show video-source + - MacOSX QTcapture/AVCapture video-source + - RST media player + - Linux V4L/V4L2 video-source + - X11 grabber video-source + - DirectFB video-output + - OpenGL/OpenGLES video-output + - SDL/SDL2 video-output + - X11 video-output + +* NAT-traversal: + - STUN support + - TURN server support + - ICE and ICE-lite support + - NATPMP support + +* Networking: + - multihoming, IPv4/IPv6 + - automatic network roaming + +* Management: + - Embedded web-server with HTTP interface + - Command-line console over UDP/TCP + - Command line interface (CLI) + - Simple configuration files + + +## Building + +baresip is using GNU makefiles, and the following packages must be +installed before building: + +* [libre](https://github.com/creytiv/re) +* [librem](https://github.com/creytiv/rem) +* [openssl](https://www.openssl.org/) + + +### Build with debug enabled + +``` +$ make +$ sudo make install +``` + +### Build with release + +``` +$ make RELEASE=1 +$ sudo make RELEASE=1 install +``` + +### Build with clang compiler + +``` +$ make CC=clang +$ sudo make CC=clang install +``` + +Modules will be built if external dependencies are installed. +After building you can start baresip like this: + +``` +$ baresip +``` + +The config files in $HOME/.baresip are automatically generated +the first time you run baresip. + + +## Documentation + +The online documentation generated with doxygen is available in +the main [website](http://creytiv.com/doxygen/baresip-dox/html/) + + +### Examples + +Configuration examples are available from the +[examples](https://github.com/alfredh/baresip/tree/master/docs/examples) +directory. + + +## License + +The baresip project is using the BSD license. + + +## Contributing + +Patches can sent via Github +[Pull-Requests](https://github.com/creytiv/baresip/pulls) or to the RE devel +[mailing-list](http://lists.creytiv.com/mailman/listinfo/re-devel). + + +## 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 +aufile Audio module for using a WAV-file as audio input +auloop Audio-loop test module +avahi Avahi Zeroconf Module +avcapture Video source using iOS AVFoundation video capture +avcodec Video codec using FFmpeg/libav libavcodec +avformat Video source using FFmpeg/libav libavformat +b2bua Back-to-Back User-Agent (B2BUA) module +bv32 BroadVoice32 audio codec +cairo Cairo video source +codec2 Codec2 low bit rate speech codec +cons UDP/TCP console UI driver +contact Contacts module +coreaudio Apple Coreaudio driver +debug_cmd Debug commands +directfb DirectFB video display module +dshow Windows DirectShow video source +dtls_srtp DTLS-SRTP end-to-end encryption +echo Echo server module +evdev Linux input driver +fakevideo Fake video input/output 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 +gst1 Gstreamer 1.0 audio source +gst_video Gstreamer video codec +gst_video1 Gstreamer 1.0 video codec +gtk GTK+ 2.0 UI +gzrtp ZRTP module using GNU ZRTP C++ library +h265 H.265 video codec +httpd HTTP webserver UI-module +ice ICE protocol for NAT Traversal +ilbc iLBC audio codec +isac iSAC audio codec +jack JACK Audio Connection Kit audio-driver +l16 L16 audio codec +libsrtp Secure RTP encryption using libsrtp +menu Interactive menu +mpa MPA Speech and Audio Codec +mqtt MQTT (Message Queue Telemetry Transport) module +mwi Message Waiting Indication +natbd NAT Behavior Discovery Module +natpmp NAT Port Mapping Protocol (NAT-PMP) module +omx OpenMAX IL video display 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 +pcp Port Control Protocol (PCP) module +plc Packet Loss Concealment (PLC) using spandsp +portaudio Portaudio driver +pulse Pulseaudio driver +presence Presence module +qtcapture Apple QTCapture 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 +sndio Audio driver for OpenBSD +speex Speex audio codec +speex_aec Acoustic Echo Cancellation (AEC) using libspeexdsp +speex_pp Audio pre-processor using libspeexdsp +srtp Secure RTP encryption (SDES) using libre SRTP-stack +stdio Standard input/output UI driver +stun Session Traversal Utilities for NAT (STUN) module +swscale Video scaling using libswscale +syslog Syslog module +turn Obtaining Relay Addresses from STUN (TURN) module +uuid UUID generator and loader +v4l Video4Linux video source +v4l2 Video4Linux2 video source +v4l2_codec Video4Linux2 video codec module (H264 hardware encoding) +vidbridge Video bridge module +vidinfo Video info overlay module +vidloop Video-loop test module +vp8 VP8 video codec +vp9 VP9 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 2250 RTP Payload Format for the mpa Speech and Audio Codec +* 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 5285 A General Mechanism for RTP Header Extensions +* 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 5627 Obtaining and Using GRUUs 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 6464 A RTP Header Extension for Client-to-Mixer Audio Level Indication +* RFC 6716 Definition of the Opus Audio Codec +* RFC 6886 NAT Port Mapping Protocol (NAT-PMP) +* RFC 7587 RTP Payload Format for the Opus Speech and Audio Codec +* RFC 7741 RTP Payload Format for VP8 Video +* RFC 7798 RTP Payload Format for High Efficiency Video Coding (HEVC) + +* draft-ietf-avt-rtp-isac-04 + + +## Architecture: +(note: out of date, needs updating) + +``` + .------. + |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: + +* Android +* Apple Mac OS X and iOS +* FreeBSD +* Linux +* NetBSD +* OpenBSD +* Solaris +* Windows (mingw and VS2015) + + +### Supported versions of C Standard library + +* Android bionic +* BSD libc +* GNU C Library (glibc) +* Windows C Run-Time Libraries (CRT) +* uClibc + + +### Supported compilers: + +* gcc 3.x +* gcc 4.x +* gcc 5.x +* gcc 6.x +* ms vc2003 compiler +* clang + +### Supported versions of OpenSSL + +* OpenSSL version 1.0.1 +* OpenSSL version 1.0.2 +* OpenSSL version 1.1.0 +* LibreSSL version 2.x + + +## Related projects + +* [libre](https://github.com/creytiv/re) +* [librem](https://github.com/creytiv/rem) +* [retest](https://github.com/creytiv/retest) +* [restund](http://creytiv.com/restund.html) + + +## References + +* Project homepage: http://www.creytiv.com/baresip.html +* Github: https://github.com/alfredh/baresip +* Mailing-list: http://lists.creytiv.com/mailman/listinfo/re-devel diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..364d0ae --- /dev/null +++ b/debian/changelog @@ -0,0 +1,192 @@ +baresip (0.5.7) unstable; urgency=medium + + * version 0.5.7 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Mon, 25 Des 2017 10:00:00 +0100 + +baresip (0.5.6) unstable; urgency=medium + + * version 0.5.6 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 14 Oct 2017 10:00:00 +0200 + +baresip (0.5.5) unstable; urgency=medium + + * version 0.5.5 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Thu, 7 Sep 2017 16:00:00 +0200 + +baresip (0.5.4) unstable; urgency=medium + + * version 0.5.4 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 24 Jun 2017 12:00:00 +0200 + +baresip (0.5.3) unstable; urgency=medium + + * version 0.5.3 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sun, 14 May 2017 07:00:00 +0200 + +baresip (0.5.2) unstable; urgency=medium + + * version 0.5.2 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 7 Apr 2017 19:00:00 +0200 + +baresip (0.5.1) unstable; urgency=medium + + * version 0.5.1 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 4 Mar 2017 10:00:00 +0100 + +baresip (0.5.0) unstable; urgency=medium + + * version 0.5.0 + + -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 23 Dec 2016 18:00:00 +0100 + +baresip (0.4.20) unstable; urgency=medium + + * version 0.4.20 + + -- Alfred E. Heggestad <aeh@db.org> Fri, 22 July 2016 20:00:00 +0100 + +baresip (0.4.19) unstable; urgency=medium + + * version 0.4.19 + + -- Alfred E. Heggestad <aeh@db.org> Fri, 20 May 2016 20:00:00 +0100 + +baresip (0.4.18) unstable; urgency=medium + + * version 0.4.18 + + -- Alfred E. Heggestad <aeh@db.org> Sat, 12 Mar 2016 18:00:00 +0100 + +baresip (0.4.17) unstable; urgency=low + + * version 0.4.17 + + -- Alfred E. Heggestad <aeh@db.org> Sun, 17 Jan 2016 12:00:00 +0100 + +baresip (0.4.16) unstable; urgency=low + + * version 0.4.16 + + -- Alfred E. Heggestad <aeh@db.org> Tue, 1 Dec 2015 12:00:00 +0100 + +baresip (0.4.15) unstable; urgency=low + + * version 0.4.15 + + -- Alfred E. Heggestad <aeh@db.org> Sat, 26 Sep 2015 12:00:00 +0100 + +baresip (0.4.14) unstable; urgency=low + + * version 0.4.14 + + -- Alfred E. Heggestad <aeh@db.org> Sat, 8 Aug 2015 12:00:00 +0100 + + +baresip (0.4.13) unstable; urgency=low + + * version 0.4.13 + + -- Alfred E. Heggestad <aeh@db.org> Sat, 20 Jun 2015 20:00:00 +0100 + +baresip (0.4.12) unstable; urgency=low + + * version 0.4.12 + + -- Alfred E. Heggestad <aeh@db.org> Wed, 24 Dec 2014 14:00:00 +0100 + +baresip (0.4.11) unstable; urgency=low + + * version 0.4.11 + + -- Alfred E. Heggestad <aeh@db.org> Sat, 21 Jun 2014 14:00:00 +0100 + +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..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..fa34192 --- /dev/null +++ b/debian/control @@ -0,0 +1,34 @@ +Source: baresip +Section: comm +Priority: optional +Maintainer: Alfred E. Heggestad <aeh@db.org> +Standards-Version: 3.9.5 +Build-Depends: debhelper (>= 9.20120311), librem-dev (>= 0.5.0), libre-dev (>= 0.5.4), libasound2-dev, libavformat-dev, libavdevice-dev, libswscale-dev +Homepage: http://www.creytiv.com/ + +Package: baresip +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, librem (>= 0.5.0), libre (>= 0.5.4) +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 + +Package: libbaresip +Architecture: any +Section: libs +Depends: ${shlibs:Depends}, ${misc:Depends}, librem (>= 0.5.0), libre (>= 0.5.4) +Description: Baresip library + +Package: libbaresip-dev +Architecture: any +Section: libdevel +Depends: libbaresip (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} +Description: Baresip library development files + See https://github.com/alfredh/baresip/wiki/Using-baresip-as-a-library + for an example. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..f56a8be --- /dev/null +++ b/debian/copyright @@ -0,0 +1,37 @@ +This package was debianized by Alfred E. Heggestad <aeh@db.org> + +It was downloaded from www.creytiv.com + + +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 Creytiv.com 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/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..b468964 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +README.md +docs/TODO diff --git a/debian/libbaresip-dev.dirs b/debian/libbaresip-dev.dirs new file mode 100644 index 0000000..3199aad --- /dev/null +++ b/debian/libbaresip-dev.dirs @@ -0,0 +1,3 @@ +usr/include +usr/lib +usr/share diff --git a/debian/libbaresip-dev.files b/debian/libbaresip-dev.files new file mode 100644 index 0000000..c3124ae --- /dev/null +++ b/debian/libbaresip-dev.files @@ -0,0 +1,2 @@ +usr/include +usr/lib/libbaresip.a diff --git a/debian/libbaresip.dirs b/debian/libbaresip.dirs new file mode 100644 index 0000000..6845771 --- /dev/null +++ b/debian/libbaresip.dirs @@ -0,0 +1 @@ +usr/lib diff --git a/debian/libbaresip.files b/debian/libbaresip.files new file mode 100644 index 0000000..0e8a305 --- /dev/null +++ b/debian/libbaresip.files @@ -0,0 +1 @@ +/usr/lib/libbaresip.so diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..45a39d1 --- /dev/null +++ b/debian/rules @@ -0,0 +1,90 @@ +#!/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 + +EXTRA_CFLAGS:="$(shell dpkg-buildflags --get CFLAGS)" +EXTRA_LFLAGS:="$(shell dpkg-buildflags --get LDFLAGS)" + + +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 \ + EXTRA_CFLAGS=$(EXTRA_CFLAGS) \ + EXTRA_LFLAGS=$(EXTRA_LFLAGS) + 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 + mkdir $(CURDIR)/debian/tmp + $(MAKE) RELEASE=1 $(BARESIP_FLAGS) install DESTDIR=$(CURDIR)/debian/baresip + $(MAKE) RELEASE=1 $(BARESIP_FLAGS) install-dev DESTDIR=$(CURDIR)/debian/tmp + dh_movefiles + +# 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 + +build-arch: build + +build-indep: build + +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..938d6cc --- /dev/null +++ b/docs/COPYING @@ -0,0 +1,31 @@ +Copyright (c) 2010 - 2017, Alfred E. Heggestad +Copyright (c) 2010 - 2017, Richard Aas +Copyright (c) 2010 - 2017, 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..d79818e --- /dev/null +++ b/docs/ChangeLog @@ -0,0 +1,1611 @@ +2017-12-25 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.7 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.7 + * NOTE: Requires libre v0.5.5 or later + Requires librem v0.5.0 or later + + * Credits: Thanks to Swedish Radio who sponsored many new + features in this release. + + * new commands: + - 'conf_reload' -- Reload config file + + * new modules: + - gzrtp ZRTP module using GNU ZRTP C++ library + (thanks glenvt18) + + - mqtt MQTT (Message Queue Telemetry Transport) module + (sponsored by Swedish Radio) + + * config: + - audio_txmode poll|thread Set audio transmit mode + - auplay_format s16|float|s24_3le Set playback sample format + - ausrc_format s16|float|s24_3le Set source sample format + - sdp_ebuacip yes|no Enable EBU-ACIP parameters + - zrtp_hash yes|no Enable/disable ZRTP hash + + * baresip-core: + - audio: add sample format conversion + - audio: add sample format for source/playback + - audio: check timestamps on incoming RTP packets + - audio: pace outgoing packets in txmode=thread + - audio: remove txmode with realtime thread + - audio: remove txmode with timer + - audio: set EBUACIP parameters in SDP + - auplay: add sample format to auplay_prm + - auplay: change write handler to any sample format + - ausrc: add sample format to ausrc_prm + - ausrc: change read handler to any sample format + - event.c: new file for generic event handling + - event: add event_encode_dict to encode event to a dictionary + - event: added UA_EVENT_CALL_RTCP for received RTCP + - log: print to stdout (ref #320) + + * selftest: + - add test for different audio tx-modes + - add test for float audio sample format + + * Modules: + + * alsa: add support for multiple sample formats + + * audiounit: add support for FLOAT sample format + + * auloop: add support for multiple sample formats + + * avahi: Bugfix: Destroy resolver after callback (#318) + (thanks Jonathan Sieber) + + * avcodec: change x264 rate control mode to ABR (#334) + (thanks Jonathan Sieber) + + * debug_cmd: add command 'conf_reload' to reload config file + + * gzrtp: ZRTP module using GNU ZRTP C++ library + (thanks glenvt18) + + * menu: add config 'ringback_disabled' to disable playing + of ringback tone. + + * mqtt: MQTT (Message Queue Telemetry Transport) module + new module using libmosquitto as the backend. + + * opus: fix encoder bitrate, ref #305 + add opus_stereo config parameter (thanks Ola Palm) + add config param opus_sprop_stereo (thanks Ola Palm) + + * portaudio: add support for FLOAT sample format + + * pulse: add support for FLOAT sample format + remove garbage at the beginning of a recording (#323) + + * quicktime: module was removed + + * rst: add support for multiple sample formats + + * zrtp: add signaling hash support (#311) + + + + +2017-10-14 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.6 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.6 + * NOTE: Requires libre v0.5.5 or later + Requires librem v0.5.0 or later + + * New Baresip logo (thanks Ernst and community) + + * baresip-core: + - log: rename error to error_msg due to GNU extension clash + - ua: remove ua_sipfd() + + * Modules: + + * avahi: Avahi Zeroconf Module (thanks Jonathan Sieber) + + * avcodec: handle fragment packet loss + + * cairo: draw a dancing logo + + * ice: set ICE role correctly + set retransmit count (RC) to 4 + + * opensles: fix recorder speaker setup (thanks Juha Heinanen) + + * opus: fix encoder bitrate, ref #305 + + * zrtp: encrypt/decrypt RTCP packets (thanks @glenvt18) + + +2017-09-07 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.5 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.5 + * NOTE: Requires libre v0.5.5 or later + Requires librem v0.5.0 or later + + * new commands: + - insmod module.so -- Load a module + - rmmod module.so -- Unload a module + + * config: + - fullscreen yes|no Enable fullscreen display + + * baresip-core: + - account: optional param 'auth_pass' for password + add account_set_auth_pass() + add account_aor() + add account_auth_pass() + - contact: add update handler (thanks Jonathan Sieber) + - h264: add rtp_ts RTP Timestamp + - module: add module_load/unload + remove list of application modules + - stream: reset timer on incoming RTCP packets (fixes #271) + - ui: make the API re-entrant + - video: add RTP timestamp to videnc packet handler + add video_calc_rtp_timestamp() + add video_calc_seconds() + - video: use RTP timestamp from video encoder + + * selftest: + - add test for video timestamps + + * Modules: + + * account: move password prompt here + + * av1: use encoder PTS to calculate RTP timestamp + + * avcodec: use encoder PTS to calculate RTP timestamp + use level_idc=0x1f for x264 + + * cons: updated UI api + + * evdev: updated UI api + + * gst_video: use encoder PTS to calculate RTP timestamp + + * gst_video1: use encoder PTS to calculate RTP timestamp + + * h265: use encoder PTS to calculate RTP timestamp + fix FU decoder bug + + * httpd: updated UI api + + * ice: move gathering from lib to app + (requires libre v0.5.5 or later) + + * menu: updated UI api + + * mwi: updated UI api + + * presence: Handle contacts added at run-time + (thanks Jonathan Sieber) + + * sdl: updated UI api + + * sdl2: add support for fullscreen video + + * stdio: updated UI api + + * v4l: add support for more pixel-formats + + * v4l2_codec: use encoder PTS to calculate RTP timestamp + + * vp8: use encoder PTS to calculate RTP timestamp + + * vp9: use encoder PTS to calculate RTP timestamp + + * wincons: updated UI api + + +2017-06-24 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.4 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.4 + * NOTE: Requires libre v0.5.4 or later + Requires librem v0.5.0 or later + + * config: + - audio_level yes|no Enable audio level RTP extension + + * baresip-core: + - add support for Client-to-Mixer Audio Level Indication (RFC 6464) + - add support for RTP Header Extensions (RFC 5285) + - module: dont load same static module twice + - ua: add ua_progress() + - ua: check for Accept header in incoming OPTIONS request + - use a dummy RTP port for incoming OPTIONS (ref #265) + - vidcodec: make the API re-entrant + - vidfilt: make the API re-entrant + - vidisp: make the API re-entrant + - vidsrc: make the API re-entrant + + * selftest: + - add test for audio level indication in call + - add test for call progress + + * Modules: + + * (all video modules updated with API-changes) + + * zrtp: check for RTP packet in send handler (ref #262) + (thanks to MobiSciLab for reporting the bug) + + - registered zrtp_log function with zrtp engine + - improved info message on how to verify remote peer + - improved setting and printing of zrtp cache file + (thanks Juha Heinanen) + + +2017-05-14 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.3 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.3 + * NOTE: Requires libre v0.5.3 or later + Requires librem v0.5.0 or later + + * config: + - (no changes) + + * build: + - detect jack module (thanks Tony Langley) + - Updated MSVS projects to vs2015 (thanks Mikhail Barg) + + * baresip-core: + - aulevel: add aulevel_calc_dbov() + - audio: Set correct clock rate for telephone events + (thanks Jan Hoffmann) + - play: Add gapless repeat for tone playback (thanks Jan Hoffmann) + + * selftest: + - add tests for aulevel + - add tests for audio player + - add mock aucodec/auplay + + * Modules: + + * gst_video1: Tune x264enc for low latency (thanks Jonathan Sieber) + + * httpd: fix a crash + + * ice: update to latest libre ICE-api + + * omx: Fixed some problems on OMX/RaspberryPi (thanks Jonathan Sieber) + + * srtp: fix SRTP for early-media (thanks Jan Hoffmann) + + * vumeter: use aulevel_calc_dbov to calculate signal energy + + * zrtp: update to latest libzrtp from freeswitch (thanks Juha Heinanen) + + +2017-04-07 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.2 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.2 + * NOTE: Requires libre v0.5.0 or later + Requires librem v0.5.0 or later + + * new modules: + - omx OpenMAX IL video display module (thanks Jonathan Sieber) + + * config: + - (no changes) + + * baresip-core: + - aucodec: make the API re-entrant + - aufilt: make the API re-entrant + - auplay: make the API re-entrant + - ausrc: make the API re-entrant + - video: using a video-source is now optional + + * Modules: + + * avformat: add pixelformat AV_PIX_FMT_YUVJ420P (Thanks Gary Metalle) + + * cairo: print picture info, use grey background + + * dtmfio: check fd before calling fclose (thanks Richard Perez) + + * h265: enable YUV444P pixelformat + + * oss: fix build for Solaris 11 + + * speex: mark the module as deprecated, see speex.org + + +2017-03-04 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.1 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.1 + * NOTE: Requires libre v0.5.0 or later + Requires librem v0.5.0 or later + + * new modules: + + * config: + - stunuser STUN username for STUN/TURN/ICE + - stunpass STUN password for STUN/TURN/ICE + - snd_path Path to sndfile audio dump files + + * baresip-core: + - account: add more accessor functions + - account: add 'stunuser' and 'stunpass' + - commands: make the struct commands opaque + - message: make the API re-entrant, multiple listeners + - menc: make the API re-entrant + - mnat: make the API re-entrant + + * selftest: + - add tests for account + - add tests for message + + * Modules: + + * amr: use MOD-CFLAGS instead of global CFLAGS + + * avcodec: added optional config 'avcodec_h264dec' to specify hardware + accellerated FFmpeg decoder (thanks Harald Gutmann) + + * avformat: remove blocking sleep, use packet timestamp to + pace video stream (thanks Harald Gutmann) + + * debug_cmd: add OpenSSL version to systems info + + * gtk: fix build where USE_NOTIFICATIONS is not defined + get rid of system header warnings by using -isystem + + * httpd: add support for un-escaping of URL parameters + (thanks to elektm93) + + * menu: add new command 'ausrc' to switch audio source + add new command 'auplay' to switch audio player + + * sdl2: add more pixelformats (ref #202) + (thanks Harald Gutmann) + + * sndfile: add config to specify path for dump files (thanks Elektm93) + add test for sndfile on *BSD. (#194) (thanks jungle-boogie) + + * swscale: get dst-size from config (ref #203) + + * v4l2_codec: Video device selection bug (#218) + (thanks Richard Perez) + + +2016-12-23 Alfred E. Heggestad <alfred.heggestad@gmail.com> + + * Version 0.5.0 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.5.0 + * NOTE: Requires libre v0.5.0 or later + Requires librem v0.5.0 or later + + * new modules: + - av1 Experimental AV1 video codec + - debug_cmd Debug commands for advanced users + - pcp Port Control Protocol (PCP) for NAT traversal + - swscale Video scaling using FFmpeg's libswscale + + * config: + - call_max_calls Maximum number of calls per account + + * baresip-core: + - call: add multiple lines + - call: start video on reinvite (thanks Gary Metalle) + - cmd: add support for long commands + - cmd: make it re-entrant + - config: add some modules to template (thanks Dmitrij D. Czarkoff) + - contact: make it re-entrant + - play: make it re-entrant + - vidcodec: add a intraframe-flag to api + - video: resend FIR until Intra frame received + + * selftest: + - add test for DTMF in call + - add test for contacts + - add test for long commands + - add test for maximum calls + - add test for multiple calls + - add test for video call + - add audio-source mock + - add video-codec mock + - add video-display mock + - add video-source mock + + * Modules: + + * aufile: convert samples from little-endian to host-endian + + * auloop: use long commands /auloop and /auloop_stop + + * av1: new module for Experimental AV1 video codec + + * avcodec: add config option 'avcodec_h264enc' to set encoder name + (thanks to @hargut) + + * avformat: fix init and warnings (thanks Maciej Koman) + + * b2bua: use long command /b2bua + + * contact: use long commands + + * debug_cmd: new module for advanced debug commands + + * g7221: expose spandsp api (thanks to Steve Underwood) + + * gtk: use long command /gtk + + * h265: add 'profile-id=1' to SDP + + * menu: add long commands + add command 'line' or '@' to set current call + + * opengl: fix deprecated warnings on OSX 10.12 + + * opensles: add support for stereo + (thanks to Juha Heinanen and Vijay Pratap Singh) + + * opus: add support for SDP parameter mirroring + (thanks to Sveriges Radio) + + * pcp: new module for Port Control Protocol (PCP) NAT traversal + requires librew (https://github.com/alfredh/rew) + + * plc: expose spandsp api (thanks to Steve Underwood) + + * presence: add long commands /presence_{on,off}line + + * snapshot: use long commands (thanks Dmitrij D. Czarkoff) + + * sndio: use driver-suggested buffer size (thanks Dmitrij D. Czarkoff) + + * swscale: new module for video filter using libswscale + + * v4l2: pick up VID_FMT_NV12 and VID_FMT_NV21 formats as well (#176) + don't check for native/emulated format (#179) + (thanks Dmitrij D. Czarkoff) + + * vidloop: use long commands + + * vp8: add 'intra' parameter to decoder api + fix building with old versions of libvpx + + * wincons: graceful closing of thread (fixes #151) + (thanks to @GGGO) + + * zrtp: use long command + + +2016-07-22 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.20 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.4.20 + * NOTE: Requires libre v0.4.17 or later + Requires librem v0.4.7 or later + + * new modules: + - pulse Pulseaudio driver + - vp9 VP9 video codec + + * config: + - audio_path Path to audio files + - call_local_timeout Timeout for incoming calls + - redial_attempts Number of redial attempts + - redial_delay Redial delay in seconds + + * baresip-core: + - baresip: added a global baresip instance (WIP) + - call: add RTP timeout (thanks to Sveriges Radio) + - config: added call_local_timeout for incoming call timeout + - config: added compile-time configureable CONFIG_PATH + - config: added 'audio_path' config variable (thanks Juha Heinanen) + - net: made it re-entrant with struct network + - ua: added uag_set_exit_handler + - ua: fix bug with reg_uri limited to 64-chars + - video: vidfilters should not modify decoded image + + * selftest: + - add test for network + - add test for sending SIP OPTIONS + - add test for RTP timeout + + * Modules: + + * avcodec: fix usage of deprecated API + + * avformat: remove support for scaling + fix usage of deprecated API + + * cons: relay log-messages to active UDP/TCP connections + https://github.com/alfredh/baresip/issues/144 + + * h265: fix usage of deprecated API + + * menu: added support for re-dial on failure + (thanks to Sveriges Radio) + + * mpa: Bug with reinit of codec structs (thanks Christian Hoene) + + * natpmp: added support for RTCP + + * presence: use correct struct in deref handler + + * pulse: new module for Pulseaudio driver + (thanks to Matthias Apitz for testing) + + * vidloop: vidfilters should not modify decoded image + + * vp8: module renamed from vpx.so to vp8.so + + * vp9: new module implementing VP9 video codec + + * wincons: use ReadConsoleInput, thanks to GGGO (fixes #139) + https://github.com/alfredh/baresip/issues/139 + + +2016-05-20 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.19 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.4.19 + * NOTE: Requires libre v0.4.14 or later + Requires librem v0.4.7 or later + + * new modules: + - mpa MPA Speech and Audio Codec (thanks Christian Hoene) + + * baresip-core: + - audio: remove is_g722 exception + use aucodec's rtp clockrate for calculating RTP timestamp + plc: make sure sampc is exactly one ptime frame + - aucodec: split srate into DSP srate and RTP clockrate + (these are different for e.g. G.722 and MDA) + - mos: add mos_calculate() (thanks Lorenzo Mangani) + - net: use configured dns servers only, if specified + - ua: fix potential NULL-pointer crash for uag.cfg + + * selftest: + - add test for SIP registration with DNS + - add test for SIP registration with authentication + - add test for MOS calculations + - added a mock DNS Server + - added a mock SIP Server + + * Modules: + + * aucodec: add support for NV12 and YUVJ420P pixel formats + + * daala: update to libdaala version 0.0-1564-g79787c7 + + * gtk: fix autodetection of libgtk+ 2.0 (thanks Charles Lehner) + + * h265: remove call to x265_cleanup, caused crash on OpenBSD + + * mpa: new module that implements MPA Speech and Audio Codec + (this module was contributed by Christian Hoene) + + * opus: added new configuration parameters: + opus_cbr {yes,no} # Constant Bitrate (inverse of VBR) + opus_inbandfec {yes,no} # Enable inband FEC + opus_dtx {yes,no} # Enable DTX + + * presence: improved interoperability, allow white space before + xml element closing tags (thanks Juha Heinanen) + + * x11: added borderless window (thanks Doug Blewett) + + +2016-03-12 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.18 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.4.18 + * NOTE: Requires libre v0.4.14 or later + Requires librem v0.4.7 or later + + * baresip-core: + - call: fix SIP INFO with dtmf-relay (thanks Gary Metalle) + - ua: add event UA_EVENT_CALL_CLOSED for ua_hangup() + + * selftest: + - add tests for answer a call and hangup + + * Modules: + + * alsa: fix potential crash (thanks Gary Metalle) + + * audiounit: fix compilation for iOS (issue #91) + + * avcodec: fix compilation for FFmpeg 3.0 + + * avformat: fix compilation for FFmpeg 3.0 + + * gtk: always handle incoming calls (thanks Charles Lehner) + + * h265: fix compilation for FFmpeg 3.0 + + * menu: add config 'menu_bell off/on' to enable Bell alert + add command 'A' for switch audio device (thanks AlexMarlo) + + * v4l2_codec: add list of encoders (fixes #99) + + +2016-01-17 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.17 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT tag: v0.4.17 + * NOTE: Requires libre v0.4.14 or later + Requires librem v0.4.7 or later + + * new modules: + - echo Echo server module + - jack JACK Audio Connection Kit audio-driver + + * baresip-core: + - config: keep config object in memory + - ua: moved playing of ringtones out of core, to "menu" module + (let's keep the core nice and slim..) + - ui: added ui_password_prompt() + + * selftest: + - silence debug/info log by default, only print warnings + (use -v to see verbose logging) + + * Modules: + + * alsa: added config option to specify the sample format + "alsa_sample_format {s16,float,s24_3le}" + thanks to Ola Palm for valuable feedback + + * audiounit: fix recording on OSX (thanks Sebastian Reimers) + print hardware samplerate in debug mode + + * auloop: add support for 44100 Hz samplerate + + * daala: update to latest libdaala API (thanks Dmitrij D. Czarkoff) + + * echo: new module which implements a simple Echo-server, to + be used in combination with the aubridge.so module. + contributed by Sebastian Reimers + + * gtk: fixes to support C89 compiler (thanks Dmitrij D. Czarkoff) + + * jack: new module which implements audio-driver for JACK + + * menu: playing of ringtones moved here, from ua.c + + * sndio: fix crash when device open fails (thanks Dmitrij D. Czarkoff) + + +2015-12-01 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.16 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT commit bed2241da3261e472f09b21958f0cc1324a94f27 + * GIT tag: v0.4.16 + * NOTE: Requires libre v0.4.14 or later + + * new modules: + - v4l2_codec Video4Linux2 video codec (H264 hardware encoding) + - vidinfo Video info overlay module + + * baresip-core: + - audio: add audio_set_source() and audio_set_player() + - audio: flush tx-buffer for all modes (thanks Thibault Gueslin) + - call: add call_is_outgoing() + - call: check address-family of incoming SDP offer (thanks Olle) + - h264: move H.264 packetization code to core + - main: add -u option to append extra global UA parameters + - main: pre-load modules after all arguments are parsed + - ua: add events UA_EVENT_SHUTDOWN,UA_EXIT + - ua: add ua_hold_answer() + - ua: add ua_set_media_af() + - ua: delay mod-unloading if mods has a ref to struct ua + + * build: + - add verbose build with V=1 (thanks Dmitrij D. Czarkoff) + - add pkg-config file (thanks William King) + - add travis.yml file for Github build-system + + * Modules: + + * alsa: fix memory leaks + + * avcodec: move common H.264 packetization code to core + + * cairo: use pkg-config in makefile + + * daala: update to latest libdaala (thanks Dmitrij D. Czarkoff) + + * gst_video: use H.264 packetization API from core + + * gst_video1: use H.264 packetization API from core + + * gtk: fix segmentation fault on window close + + * mwi: add 500ms delay after closing subscription + + * oss: use pthread for ausrc instead of fd_listen (fixes FreeBSD) + + * presence: use sipevent_sock instance from UA core + add 500ms delay after closing subscription + + * v4l2_codec: new module + + * vidinfo: new module + + * zrtp: fix ZRTP over TURN by moving helper to layer 10 + fix ZID verification (thanks Ingo Feinerer) + + +2015-09-26 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.15 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT commit 86262a6fc17e19e2be82eb8a2a05ec0f884d3d38 + * GIT tag: v0.4.15 + * NOTE: Requires libre v0.4.13 or later + + * added selftest binary + + * baresip-core: + - audio: fix televent when pt != 101 (reported by AndyJRobinson) + - magic: use __func__ for C99 or later + - sip: make sip_req_send() public + - ua: add UA_EVENT_CALL_DTMF_START/END, thanks Gary Metalle + + * Modules: + + * alsa: added extra logging + + * gtk: add support for libnotify (thanks Charles Lehner) + + * video: fix potential null deref (thanks Tomasz Ostrowski) + + * zrtp: added 36-bytes preamble for TURN-header + + +2015-08-08 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.14 + + * GIT URL: https://github.com/alfredh/baresip.git + * GIT commit ebac23b0692de71ee4c3a436f0372013150c937f + * GIT tag: v0.4.14 + * NOTE: Requires libre v0.4.13 or later + + * new modules: + - gtk GTK+ 2.0 UI (thanks Charles E. Lehner) + - gst1 Gstreamer 1.0 audio module + - gst_video1 Gstreamer 1.0 video module (thanks Thomas Strobel) + - daala Experimental video-codec using Daala + + * baresip-core: + - baresip: added -m argument to pre-load modules + - config: add kqueue to sample config (thanks Dmitrij D. Czarkoff) + - log: make code C89 compliant (thanks Victor Sergienko) + - module: added module_preload() + - ua: add CALL_EVENT_TRANSFER_FAILED + - ua: skip initial white space from uri (thanks Juha Heinanen) + - ua: ua_prev_call() + - videnc: move videnc_packet_h to update-handler + + * build: + - added optional $(MOD)_CFLAGS for local module CFLAGS + - added project file for Visual C++ Express 2010 + - freebsd: add include path to $(SYSROOT)/local/include + (thanks Hellmuth Michaelis) + + * Modules: + + * avcodec: make code C89 compliant (thanks Victor Sergienko) + + * cons: make code C89 compliant (thanks Victor Sergienko) + + * daala: new module + + * dshow: updates for VC2010 (thanks Victor Sergienko) + + * gst1: new module + + * gst_video1: new module + + * gtk: new module + + * menu: fix crash when 0 UAs (thanks Hans Petter Selasky) + added command 'H' to hold previous call (thanks xanm) + + * wincons: make code C89 compliant (thanks ggcoding) + + +2015-06-20 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.13 + + * GIT commit 2e3e825ef5532dfde5a8b52de9ebaac51aa20a9c + * NOTE: Requires libre v0.4.12 or later + + * new modules: + - aufile Audio module for using a WAV-file as audio input + - b2bua Back-to-Back User-Agent (B2BUA) module + - codec2 CODEC2 audio codec + - gst_video Gstreamer video codec + - h265 H.265 (HEVC) video codec + + * baresip-core: + - contact: add support for access-control (thanks Doug Blewett) + - ausrc: change base-class to a const pointer + - auplay: change base-class to a const pointer + - vidsrc: change base-class to a const pointer + - vidisp: change base-class to a const pointer + - video: smooth sending of video packets + + + * Modules: + + * amr: added support for octet-align mode (thanks to Stefan Sayer) + + * aubridge: copy audio-samples if resampler not needed + + * aufile: new module for using a WAV-file as audio source + + * avcapture: only register 1 video source + + * avformat: fix segfault on recent versions of libav + + * b2bua: new experimental module + + * codec2: new module for CODEC2 audio codec + + * dtls_srtp: uppercase fingerprint, interop (thanks Juha Heinanen) + alternative SDP protocols for interop + + * dtmfio: unregister event handler on close (thanks Hellmuth Michaelis) + + * gst_video: new module using Gstreamer as a video codec + (Thanks to Victor Sergienko and Fadeev Alexander) + + * h265: new module for H.265 video codec + + * httpd: added raw mode (thanks Lorenzo Mangani) + + * menu: create user-agent with a command 'R' (thanks Lorenzo Mangani) + + * opus: add configuration of "opus_bitrate" + (thanks to Juha Heinanen) + + * speex: add configuration of "speex_mode_nb" and "speex_mode_wb" + (thanks to Dmitrij D. Czarkoff and Juha Heinanen) + + * vidloop: add VIDLOOP_INTERNAL_FMT and split encoder/decoder + + * x11: catch Window delete (thanks to Doug Blewett) + + * zrtp: initialize remote_zid (thanks to Ingo Feinerer) + + +2014-12-24 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.12 + + * GIT commit 67993e35d980375458348b264c4a35a944bb5180 + * NOTE: Requires libre v0.4.11 or later + + * baresip: + - account: add regint and pubint + - audio: fix checking of sample-rate range + - config: remove the "input" block + - config: added support for quoted device parameters + - config: fix conversion of bandwidth to kbit/s + - config: generate more relevant config for FreeBSD and OpenBSD + (thanks Dmitrij D. Czarkoff) + - reg: add support for extracting GRUU parameter + - main: add -p option to set path to audio files + - sipreq: make response-handler optional + - ua: add support for GRUU (RFC 5627) + (many thanks to Juha Heinanen for starting this work and + helping out with the testing) + - ua: moved presence-status to each struct ua instance + - ua: add presence status to each User-Agent instance + - ua: use public-GRUU if set, otherwise local cuser + - ui: make UI single instance + - video: add VIDENC_INTERNAL_FMT (suggested by Victor Sergienko) + + * docs: added sample configuration files + + * account: added pubint for Publishing Interval + + * avcodec: upgrade to recent ffmpeg/libav APIs + either FFmpeg or libav can be used + + * celt: deleted module (replaced by opus) + + * cons: update usage of struct ui, added output handler + added config: cons_listen 0.0.0.0:5555 + + * evdev: update usage of struct ui, added output handler + added config: evdev_device /dev/input/event0 + + * httpd: added ui output handler + + * menu: added command 'o' for sending OPTION request + (thanks to Juha Heinanen) + + added command 'D' for accepting incoming calls + + * mwi: subscribe to MWI after Registration succeeded + (thanks to Juha Heinanen) + + * opensles: add double-buffering and some tuning + (thanks to Francesco Bradascio) + + * opus: added config "opus_bitrate" (thanks to Sebastian Reimers) + + * presence: added support for PUBLISH (thanks to Juha Heinanen) + interop fixes and tuning + + * stdio: update usage of struct ui, added output handler + + * uuid: use internal version of generating UUID + + * v4l2: use memory mapped mode only + + * vumeter: dont call tmr_start from non-RE thread + + * wincons: update usage of struct ui, added output handler + + * winwave: fix bug when closing player device + (thanks to Tomasz Ostrowski) + add support for mapping device name to index + + * zrtp: add support for verify SAS (thanks to Ingo Feinerer) + + +2014-06-21 Alfred E. Heggestad <aeh@db.org> + + * Version 0.4.11 + + * GIT commit 7a465f2eb92f4e32740093e5ad4970d528908c51 + + * baresip: + - audio: added audio_ismuted() to get audio mute status + - audio: fix timestamp generation for stereo-streams + - audio: send outgoing audio-packets as soon as possible + - audio: upgrade to sample-based ausrc/auplay API + - auplay: change API to use samples instead of 8-bit buffer + - auplay: remove option to specify sample format (always S16LE) + - ausrc: change API to use samples instead of 8-bit buffer + - ausrc: remove option to specify sample format (always S16LE) + - call: added support for X-RTP-Stat header (thanks Lorenzo Mangani) + - call: check for common audio-codecs (thanks Juha Heinanen) + - logging: use info() instead of DEBUG_INFO(); + - logging: use warning() instead of DEBUG_WARNING() + - play: convert WAV-file from little-endian to native-endian + - removed support for Symbian OS + + * debian: upgrade debian files + + * avcapture: also build for MacOSX + + * alsa: fix sample-endianess with SND_PCM_FORMAT_S16 + upgrade to sample-based ausrc/auplay API + + * audiounit: upgrade to sample-based ausrc/auplay API + + * auloop: upgrade to sample-based ausrc/auplay API + + * coreaudio: upgrade to sample-based ausrc/auplay API + + * dtls_srtp: use DTLS code from libre (needs libre v0.4.9 or later) + use SRTP code from libre (needs libre v0.4.9 or later) + + * dtmfio: new module to send DTMF-events via FIFO file + (contributed by Aaron Herting) + + * fakevideo: new module for fake video input/output driver + + * gst: upgrade to sample-based ausrc/auplay API + + * ice: set default candidates for ICE-lite + + * libsrtp: module 'srtp.so' renamed to 'libsrtp.so' + + * mda: Symbian MDA audio driver was deleted + + * menu: fix issue with audio-mute on multiple calls + + * opensles: upgrade to sample-based ausrc/auplay API + + * oss: upgrade to sample-based ausrc/auplay API + + * portaudio: upgrade to sample-based ausrc/auplay API + + * rst: upgrade to sample-based ausrc/auplay API + + * selftest: new module for testing the baresip core api + + * sndio: new module for OpenBSD audio driver + (It was contributed by Dmitrij D. Czarkoff, thank you!) + + * srtp: module is now using SRTP-stack from libre (v0.4.9 or later) + + * syslog: use logging framework to get messages + + * v4l2: add format negotiation and OpenBSD support + (contributed by Dmitrij D. Czarkoff) + + * winwave: upgrade to sample-based ausrc/auplay API + + +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/THANKS b/docs/THANKS new file mode 100644 index 0000000..b7f0949 --- /dev/null +++ b/docs/THANKS @@ -0,0 +1,47 @@ +@GGGO +@elektm93 +@glenvt18 +@jungle-boogie +Aaron Herting +AlexMarlo +Andreas Shimokawa +Atle Samuelsen +Charles Lehner +Christian Hoene +David Jedda +Dimitris P. +Dmitrij D. Czarkoff +Doug Blewett +Dusan Stevanovic +EJC Lindner +Fadeev Alexander +Gary Metalle +Hans Petter Selasky +Harald Gutmann +Hellmuth Michaelis +Ingo Feinerer +Iwan BK +Jan Hoffmann +Jonathan Sieber +Juha Heinanen +Lorenzo Mangani +Luigi Rizzo +Maciej Koman +Matthias Apitz +Mikhail Barg +Ola Palm +Olle E. Johansson +Richard Perez +Sebastian Reimers +Stefan Sayer +Steve Underwood +Thibault Gueslin +Thomas Strobel +Tomasz Ostrowski +Tony Langley +Trevor Jim +Victor Sergienko +William King + + +Sveriges Radio diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 0000000..e47f214 --- /dev/null +++ b/docs/TODO @@ -0,0 +1,11 @@ +TODO: + +------------------------------------------------------------------------------- + +Please see: + + https://github.com/alfredh/baresip/issues + + https://github.com/alfredh/baresip/wiki/Roadmap + +------------------------------------------------------------------------------- diff --git a/docs/examples/accounts b/docs/examples/accounts new file mode 100644 index 0000000..2b4a39c --- /dev/null +++ b/docs/examples/accounts @@ -0,0 +1,58 @@ +# +# SIP accounts - one account per line -- sample configuration +# +# Displayname <sip:user:password@domain;uri-params>;addr-params +# +# uri-params: +# ;transport={udp,tcp,tls} +# +# addr-params: +# ;answermode={manual,early,auto} +# ;audio_codecs=speex/16000,pcma,... +# ;auth_user=username +# ;mediaenc={srtp,srtp-mand,srtp-mandf,dtls_srtp,zrtp} +# ;medianat={stun,turn,ice} +# ;outbound="sip:primary.example.com;transport=tcp" +# ;outbound2=sip:secondary.example.com +# ;ptime={10,20,30,40,...} +# ;regint=3600 +# ;pubint=0 (publishing off) +# ;regq=0.5 +# ;rtpkeep={zero,stun,dyna,rtcp} +# ;sipnat={outbound} +# ;stunserver=stun:[user:pass]@host[:port] +# ;video_codecs=h264,h263,... +# +# Examples: +# +# <sip:user:secret@domain.com;transport=tcp> +# <sip:user:secret@1.2.3.4;transport=tcp> +# <sip:user:secret@[2001:df8:0:16:216:6fff:fe91:614c]:5070;transport=tcp> +# + + +# +# A very basic example +# +<sip:user@iptel.org> + + +# +# Use SIP Outbound over TCP, with ICE for Media NAT Traversal, and DTLS-SRTP for encryption +# +<sip:user:pass@example.com>;sipnat=outbound;outbound="sip:example.com;transport=tcp";medianat=ice;mediaenc=dtls_srtp + + +# +# Use ICE for Media NAT Traversal, using a specific STUN-server +# +<sip:user:pass@example.com>;medianat=ice;stunserver="stun:username:password@stunserver.org" + + +# +# Force audio-codec 'opus' and video-codec 'vp8' +# +<sip:user:pass@example.com>;audio_codecs=opus/48000/2;video_codecs=vp8 + + +# ... more examples can be added here ... diff --git a/docs/examples/config b/docs/examples/config new file mode 100644 index 0000000..1eabd62 --- /dev/null +++ b/docs/examples/config @@ -0,0 +1,177 @@ +# +# baresip configuration -- example for linux +# + +#------------------------------------------------------------------------------ + +# Core +poll_method epoll # poll, select, epoll .. + +# SIP +sip_trans_bsize 128 +#sip_listen 0.0.0.0:5060 +#sip_certificate cert.pem + +# Audio +audio_player alsa,default +audio_source alsa,default +audio_alert alsa,default +audio_srate 8000-48000 +audio_channels 1-2 +#ausrc_srate 48000 +#auplay_srate 48000 +#ausrc_channels 0 +#auplay_channels 0 + +# Video +#video_source v4l2,/dev/video0 +#video_display x11,nil +video_size 352x288 +video_bitrate 512000 +video_fps 25 + +# AVT - Audio/Video Transport +rtp_tos 184 +#rtp_ports 10000-20000 +#rtp_bandwidth 512-1024 # [kbit/s] +rtcp_enable yes +rtcp_mux no +jitter_buffer_delay 5-10 # frames +rtp_stats no + +# Network +#dns_server 10.0.0.1:53 +#net_interface wlan1 + +# BFCP +#bfcp_proto udp + +#------------------------------------------------------------------------------ +# Modules + +#module_path /usr/local/lib/baresip/modules + +# UI Modules +module stdio.so +#module cons.so +#module evdev.so +#module httpd.so + +# Audio codec Modules (in order) +module opus.so +#module silk.so +#module amr.so +#module g7221.so +#module g722.so +#module g726.so +module g711.so +#module gsm.so +#module l16.so +#module speex.so +#module bv32.so + +# Audio filter Modules (in encoding order) +#module vumeter.so +#module sndfile.so +#module speex_aec.so +#module speex_pp.so +#module plc.so + +# Audio driver Modules +module alsa.so +#module portaudio.so + +# Video codec Modules (in order) +module avcodec.so +module vpx.so + +# Video filter Modules (in encoding order) +#module selfview.so + +# Video source modules +#module v4l.so +module v4l2.so +#module avformat.so +#module x11grab.so +#module cairo.so + +# Video display modules +module x11.so +#module sdl2.so + +# Audio/Video source modules +#module rst.so +#module gst.so + +# Media NAT modules +module stun.so +module turn.so +module ice.so +#module natpmp.so + +# Media encryption modules +#module srtp.so +module dtls_srtp.so + + +#------------------------------------------------------------------------------ +# Temporary Modules (loaded then unloaded) + +module_tmp uuid.so +module_tmp account.so + + +#------------------------------------------------------------------------------ +# Application Modules + +module_app auloop.so +module_app contact.so +module_app menu.so +#module_app mwi.so +#module_app natbd.so +#module_app presence.so +#module_app syslog.so +module_app vidloop.so +#module_app gtk.so + + +#------------------------------------------------------------------------------ +# Module parameters + + +cons_listen 0.0.0.0:5555 + +evdev_device /dev/input/event0 + +# Speex codec parameters +speex_quality 7 # 0-10 +speex_complexity 7 # 0-10 +speex_enhancement 0 # 0-1 +speex_mode_nb 3 # 1-6 +speex_mode_wb 6 # 1-6 +speex_vbr 0 # Variable Bit Rate 0-1 +speex_vad 0 # Voice Activity Detection 0-1 +speex_agc_level 8000 + +# Opus codec parameters +opus_bitrate 28000 # 6000-510000 + +# NAT Behavior Discovery +natbd_server creytiv.com +natbd_interval 600 # in seconds + +# Selfview +video_selfview window # {window,pip} +#selfview_size 64x64 + +# ICE +ice_turn no +ice_debug no +ice_nomination regular # {regular,aggressive} +ice_mode full # {full,lite} + +# ZRTP +#zrtp_hash no # Disable SDP zrtp-hash (not recommended) + +# sndfile # +snd_path /tmp/ diff --git a/docs/examples/contacts b/docs/examples/contacts new file mode 100644 index 0000000..b5131a6 --- /dev/null +++ b/docs/examples/contacts @@ -0,0 +1,11 @@ +# +# SIP contacts +# +# Displayname <sip:user@domain>;addr-params +# +# addr-params: +# ;presence={none,p2p} +# + +"Echo Server" <sip:echo@creytiv.com> +"alfredh" <sip:alfredh@home>;presence=p2p diff --git a/include/baresip.h b/include/baresip.h new file mode 100644 index 0000000..746a95e --- /dev/null +++ b/include/baresip.h @@ -0,0 +1,1211 @@ +/** + * @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.5.7" + + +#ifndef NET_MAX_NS +#define NET_MAX_NS (4) +#endif + + +/* forward declarations */ +struct sa; +struct sdp_media; +struct sdp_session; +struct sip_msg; +struct stream; +struct ua; +struct vidframe; +struct vidrect; +struct vidsz; + + +/* + * Account + */ + +/** Defines the answermodes */ +enum answermode { + ANSWERMODE_MANUAL = 0, + ANSWERMODE_EARLY, + ANSWERMODE_AUTO +}; + +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_auth_pass(struct account *acc, const char *pass); +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); +uint32_t account_regint(const struct account *acc); +uint32_t account_pubint(const struct account *acc); +uint32_t account_ptime(const struct account *acc); +enum answermode account_answermode(const struct account *acc); +const char *account_aor(const struct account *acc); +const char *account_auth_user(const struct account *acc); +const char *account_auth_pass(const struct account *acc); +const char *account_outbound(const struct account *acc, unsigned ix); +const char *account_stun_user(const struct account *acc); +const char *account_stun_pass(const struct account *acc); +const char *account_stun_host(const struct account *acc); + + +/* + * Audio-level + */ + + +#define AULEVEL_MIN (-96.0) +#define AULEVEL_MAX (0.0) + + +double aulevel_calc_dbov(const int16_t *sampv, size_t sampc); + + +/* + * Call + */ + +enum call_event { + CALL_EVENT_INCOMING, + CALL_EVENT_RINGING, + CALL_EVENT_PROGRESS, + CALL_EVENT_ESTABLISHED, + CALL_EVENT_CLOSED, + CALL_EVENT_TRANSFER, + CALL_EVENT_TRANSFER_FAILED, +}; + +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); +uint32_t call_setup_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); +bool call_is_onhold(const struct call *call); +bool call_is_outgoing(const struct call *call); +void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms); +uint32_t call_linenum(const struct call *call); +struct call *call_find_linenum(const struct list *calls, uint32_t linenum); +void call_set_current(struct list *calls, struct call *call); + + +/* + * Conf (utils) + */ + + +/** Defines the configuration line handler */ +typedef int (confline_h)(const struct pl *addr, void *arg); + +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, void *arg); +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); +void conf_close(void); +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 */ +}; + + +/** 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 */ +}; + +/** Call config */ +struct config_call { + uint32_t local_timeout; /**< Incoming call timeout [sec] 0=off */ + uint32_t max_calls; /**< Maximum number of calls, 0=unlimited */ +}; + +/** Audio */ +struct config_audio { + char audio_path[256]; /**< Audio file directory */ + 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 */ + bool level; /**< Enable audio level indication */ + int src_fmt; /**< Audio source sample format */ + int play_fmt; /**< Audio playback sample format */ +}; + +#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 */ + bool fullscreen; /**< Enable fullscreen display */ +}; +#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 */ + uint32_t rtp_timeout; /**< RTP Timeout in seconds (0=off) */ +}; + +/* Network */ +struct config_net { + char ifname[16]; /**< Bind to interface (optional) */ + struct { + char addr[64]; + } nsv[NET_MAX_NS]; /**< Configured DNS nameservers */ + size_t nsc; /**< Number of DNS nameservers */ +}; + +#ifdef USE_VIDEO +/* BFCP */ +struct config_bfcp { + char proto[16]; /**< BFCP Transport (optional) */ +}; +#endif + +/** SDP */ +struct config_sdp { + bool ebuacip; /**< Enable EBU-ACIP parameters */ +}; + + +/** Core configuration */ +struct config { + + struct config_sip sip; + + struct config_call call; + + struct config_audio audio; + +#ifdef USE_VIDEO + struct config_video video; +#endif + struct config_avt avt; + + struct config_net net; + +#ifdef USE_VIDEO + struct config_bfcp bfcp; +#endif + + struct config_sdp sdp; +}; + +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; +typedef void (contact_update_h)(struct contact *c, bool removed, void *arg); + +struct contacts { + struct list cl; + struct hash *cht; + + contact_update_h *handler; + void* handler_arg; +}; + + +int contact_init(struct contacts *contacts); +void contact_close(struct contacts *contacts); +int contact_add(struct contacts *contacts, + struct contact **contactp, const struct pl *addr); +void contact_remove(struct contacts *contacts, struct contact *c); +void contact_set_update_handler(struct contacts *contacs, + contact_update_h *updateh, void *arg); +int contacts_print(struct re_printf *pf, const struct contacts *contacts); +enum presence_status contact_presence(const struct contact *c); +void contact_set_presence(struct contact *c, enum presence_status status); +bool contact_block_access(const struct contacts *contacts, const char *uri); +struct contact *contact_find(const struct contacts *contacts, + const char *uri); +struct sip_addr *contact_addr(const struct contact *c); +struct list *contact_list(const struct contacts *contacts); +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); + +struct message; +struct message_lsnr; + +int message_init(struct message **messagep); +int message_listen(struct message_lsnr **lsnrp, struct message *message, + message_recv_h *h, void *arg); +int message_send(struct ua *ua, const char *peer, const char *msg, + sip_resp_h *resph, void *arg); + + +/* + * Audio Source + */ + +struct ausrc; +struct ausrc_st; + +/** Audio Source parameters */ +struct ausrc_prm { + uint32_t srate; /**< Sampling rate in [Hz] */ + uint8_t ch; /**< Number of channels */ + uint32_t ptime; /**< Wanted packet-time in [ms] */ + int fmt; /**< Sample format (enum aufmt) */ +}; + +typedef void (ausrc_read_h)(const void *sampv, size_t sampc, void *arg); +typedef void (ausrc_error_h)(int err, const char *str, void *arg); + +typedef int (ausrc_alloc_h)(struct ausrc_st **stp, const 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, struct list *ausrcl, const char *name, + ausrc_alloc_h *alloch); +const struct ausrc *ausrc_find(const struct list *ausrcl, const char *name); +int ausrc_alloc(struct ausrc_st **stp, struct list *ausrcl, + 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 { + uint32_t srate; /**< Sampling rate in [Hz] */ + uint8_t ch; /**< Number of channels */ + uint32_t ptime; /**< Wanted packet-time in [ms] */ + int fmt; /**< Sample format (enum aufmt) */ +}; + +typedef void (auplay_write_h)(void *sampv, size_t sampc, void *arg); + +typedef int (auplay_alloc_h)(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); + +int auplay_register(struct auplay **pp, struct list *auplayl, + const char *name, auplay_alloc_h *alloch); +const struct auplay *auplay_find(const struct list *auplayl, const char *name); +int auplay_alloc(struct auplay_st **stp, struct list *auplayl, + 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 list *aufiltl, struct aufilt *af); +void aufilt_unregister(struct aufilt *af); + + +/* + * Log + */ + +enum log_level { + LEVEL_DEBUG = 0, + LEVEL_INFO, + LEVEL_WARN, + LEVEL_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 *logh); +void log_unregister_handler(struct log *logh); +void log_enable_debug(bool enable); +void log_enable_info(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_msg(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 list *mencl, struct menc *menc); +void menc_unregister(struct menc *menc); +const struct menc *menc_find(const struct list *mencl, const char *id); + + +/* + * Net - Networking + */ + +struct network; + +typedef void (net_change_h)(void *arg); + +int net_alloc(struct network **netp, const struct config_net *cfg, int af); +int net_use_nameserver(struct network *net, const struct sa *ns); +void net_change(struct network *net, uint32_t interval, + net_change_h *ch, void *arg); +void net_force_change(struct network *net); +bool net_check(struct network *net); +int net_af(const struct network *net); +int net_debug(struct re_printf *pf, const struct network *net); +const struct sa *net_laddr_af(const struct network *net, int af); +const char *net_domain(const struct network *net); +struct dnsc *net_dnsc(const struct network *net); + + +/* + * Play - audio file player + */ + +struct play; +struct player; + +int play_file(struct play **playp, struct player *player, + const char *filename, int repeat); +int play_tone(struct play **playp, struct player *player, + struct mbuf *tone, + uint32_t srate, uint8_t ch, int repeat); +int play_init(struct player **playerp); +void play_set_path(struct player *player, 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_SHUTDOWN, + UA_EVENT_EXIT, + + UA_EVENT_CALL_INCOMING, + UA_EVENT_CALL_RINGING, + UA_EVENT_CALL_PROGRESS, + UA_EVENT_CALL_ESTABLISHED, + UA_EVENT_CALL_CLOSED, + UA_EVENT_CALL_TRANSFER_FAILED, + UA_EVENT_CALL_DTMF_START, + UA_EVENT_CALL_DTMF_END, + UA_EVENT_CALL_RTCP, + + 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); + +typedef void (ua_exit_h)(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_progress(struct ua *ua, struct call *call); +int ua_hold_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_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); +void ua_unregister(struct ua *ua); +bool ua_isregistered(const struct ua *ua); +void ua_pub_gruu_set(struct ua *ua, const struct pl *pval); +const char *ua_aor(const struct ua *ua); +const char *ua_cuser(const struct ua *ua); +const char *ua_local_cuser(const struct ua *ua); +struct account *ua_account(const struct ua *ua); +const char *ua_outbound(const struct ua *ua); +struct call *ua_call(const struct ua *ua); +struct call *ua_prev_call(const struct ua *ua); +struct list *ua_calls(const struct ua *ua); +enum presence_status ua_presence_status(const struct ua *ua); +void ua_presence_status_set(struct ua *ua, const enum presence_status status); +void ua_set_media_af(struct ua *ua, int af_media); + + +/* 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); +void uag_set_exit_handler(ua_exit_h *exith, void *arg); +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); +void uag_set_sub_handler(sip_msg_h *subh); +int ua_print_sip_status(struct re_printf *pf, void *unused); +int uag_set_extra_params(const char *eprm); +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_sub { + struct list uil; /**< List of UIs (struct ui) */ + struct cmd_ctx *uictx; +}; + +typedef int (ui_output_h)(const char *str); + +/** Defines a User-Interface module */ +struct ui { + struct le le; /**< Linked-list element */ + const char *name; /**< Name of the UI-module */ + ui_output_h *outputh; /**< Handler for output strings (optional) */ +}; + +void ui_register(struct ui_sub *uis, struct ui *ui); +void ui_unregister(struct ui *ui); + +void ui_reset(struct ui_sub *uis); +void ui_input_key(struct ui_sub *uis, char key, struct re_printf *pf); +void ui_input_str(const char *str); +int ui_input_pl(struct re_printf *pf, const struct pl *pl); +void ui_output(struct ui_sub *uis, const char *fmt, ...); +bool ui_isediting(const struct ui_sub *uis); +int ui_password_prompt(char **passwordp); + + +/* + * Command interface + */ + +/* special keys */ +#define KEYCODE_NONE (0x00) +#define KEYCODE_REL (0x04) /* Key was released */ +#define KEYCODE_ESC (0x1b) + + +/** 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 */ + void *data; /**< Application data */ +}; + +/** Defines a command */ +struct cmd { + const char *name; /**< Long command */ + char key; /**< Short command */ + int flags; /**< Optional command flags */ + const char *desc; /**< Description string */ + re_printf_h *h; /**< Command handler */ +}; + +struct cmd_ctx; +struct commands; + + +int cmd_init(struct commands **commandsp); +int cmd_register(struct commands *commands, + const struct cmd *cmdv, size_t cmdc); +void cmd_unregister(struct commands *commands, const struct cmd *cmdv); +int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key, + struct re_printf *pf, void *data); +int cmd_process_long(struct commands *commands, const char *str, size_t len, + struct re_printf *pf_resp, void *data); +int cmd_print(struct re_printf *pf, const struct commands *commands); +const struct cmd *cmd_find_long(const struct commands *commands, + const char *name); +struct cmds *cmds_find(const struct commands *commands, + const struct cmd *cmdv); + + +/* + * 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, const 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, struct list *vidsrcl, const char *name, + vidsrc_alloc_h *alloch, vidsrc_update_h *updateh); +const struct vidsrc *vidsrc_find(const struct list *vidsrcl, const char *name); +int vidsrc_alloc(struct vidsrc_st **stp, struct list *vidsrcl, + 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) */ + bool fullscreen; /**< Enable fullscreen display */ +}; + +typedef void (vidisp_resize_h)(const struct vidsz *size, void *arg); + +typedef int (vidisp_alloc_h)(struct vidisp_st **vp, + const 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, struct list *vidispl, 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, struct list *vidispl, + 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 struct list *vidispl, 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; /* Audio samplerate */ + uint32_t crate; /* RTP Clock rate */ + 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 list *aucodecl, struct aucodec *ac); +void aucodec_unregister(struct aucodec *ac); +const struct aucodec *aucodec_find(const struct list *aucodecl, + const char *name, uint32_t srate, + uint8_t ch); + + +/* + * 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, uint32_t rtp_ts, + 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, + videnc_packet_h *pkth, void *arg); +typedef int (videnc_encode_h)(struct videnc_state *ves, bool update, + const struct vidframe *frame); + +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 *intra, 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 list *vidcodecl, struct vidcodec *vc); +void vidcodec_unregister(struct vidcodec *vc); +const struct vidcodec *vidcodec_find(const struct list *vidcodecl, + const char *name, const char *variant); +const struct vidcodec *vidcodec_find_encoder(const struct list *vidcodecl, + const char *name); +const struct vidcodec *vidcodec_find_decoder(const struct list *vidcodecl, + const char *name); + + +/* + * 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 list *vidfiltl, struct vidfilt *vf); +void vidfilt_unregister(struct vidfilt *vf); +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); +bool audio_ismuted(const struct audio *a); +void audio_set_devicename(struct audio *a, const char *src, const char *play); +int audio_set_source(struct audio *au, const char *mod, const char *device); +int audio_set_player(struct audio *au, const char *mod, const char *device); +void audio_encoder_cycle(struct audio *audio); +int audio_level_get(const struct audio *au, double *level); +int audio_debug(struct re_printf *pf, const struct audio *a); +struct stream *audio_strm(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); +uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps); +double video_calc_seconds(uint32_t rtp_ts); +struct stream *video_strm(const struct video *v); + + +/* + * Generic stream + */ + +const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm); + + +/* + * 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, struct list *mnatl, + 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); + + +/* + * SIP Request + */ + +int sip_req_send(struct ua *ua, const char *method, const char *uri, + sip_resp_h *resph, void *arg, const char *fmt, ...); + + +/* + * 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 h264_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 h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb); +int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb); + +const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end); + +int h264_packetize(uint32_t rtp_ts, const uint8_t *buf, size_t len, + size_t pktsize, videnc_packet_h *pkth, void *arg); +int h264_nal_send(bool first, bool last, + bool marker, uint32_t ihdr, uint32_t rtp_ts, + const uint8_t *buf, size_t size, size_t maxsz, + videnc_packet_h *pkth, void *arg); +static inline bool h264_is_keyframe(int type) +{ + return type == H264_NAL_SPS; +} + + +/* + * Modules + */ + +#ifdef STATIC +#define DECL_EXPORTS(name) exports_ ##name +#else +#define DECL_EXPORTS(name) exports +#endif + + +int module_preload(const char *module); +int module_load(const char *name); +void module_unload(const char *name); + + +/* + * MOS (Mean Opinion Score) + */ + +double mos_calculate(double *r_factor, double rtt, + double jitter, uint32_t num_packets_lost); + + +/* + * Generic event + */ + +int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev, + struct call *call, const char *prm); + + +/* + * Baresip instance + */ + +int baresip_init(struct config *cfg, bool prefer_ipv6); +void baresip_close(void); +struct network *baresip_network(void); +struct contacts *baresip_contacts(void); +struct commands *baresip_commands(void); +struct player *baresip_player(void); +struct message *baresip_message(void); +struct list *baresip_mnatl(void); +struct list *baresip_mencl(void); +struct list *baresip_aucodecl(void); +struct list *baresip_ausrcl(void); +struct list *baresip_auplayl(void); +struct list *baresip_aufiltl(void); +struct list *baresip_vidcodecl(void); +struct list *baresip_vidsrcl(void); +struct list *baresip_vidispl(void); +struct list *baresip_vidfiltl(void); +struct ui_sub *baresip_uis(void); + + +#ifdef __cplusplus +} +#endif + + +#endif /* BARESIP_H__ */ diff --git a/mk/Doxyfile b/mk/Doxyfile new file mode 100644 index 0000000..3606286 --- /dev/null +++ b/mk/Doxyfile @@ -0,0 +1,238 @@ +# Doxyfile 1.4.7 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = baresip +PROJECT_NUMBER = 0.5.7 +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_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..cf9a16f --- /dev/null +++ b/mk/mod.mk @@ -0,0 +1,105 @@ +# +# 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) + + +# +# function to extract the name of the module from the file/dir path +# +modulename = $(lastword $(subst /, ,$(dir $1))) + + +ifeq ($(STATIC),) + +# +# Dynamically loaded modules +# + +$(MOD)$(MOD_SUFFIX): $($(MOD)_OBJS) + @echo " LD [M] $@" + $(HIDE)$(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 $@) + $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(CXX) $(CXXFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(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 $@) + $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(CXX) $(CXXFLAGS) $($(call modulename,$@)_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 $@) + $(HIDE)$(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..9211dfd --- /dev/null +++ b/mk/modules.mk @@ -0,0 +1,467 @@ +# +# modules.mk +# +# Copyright (C) 2010 - 2017 Creytiv.com +# +# External libraries: +# +# USE_ALSA ALSA audio driver +# USE_AMR Adaptive Multi-Rate (AMR) audio codec +# USE_AUDIOUNIT AudioUnit audio driver for OSX/iOS +# USE_AVCAPTURE AVFoundation video capture for OSX/iOS +# USE_AVCODEC avcodec video codec module +# USE_AVFORMAT avformat video source module +# 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_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 0.10 audio module +# USE_GST1 Gstreamer 1.0 audio module +# USE_GST_VIDEO Gstreamer 0.10 video module +# USE_GST_VIDEO1 Gstreamer 1.0 video module +# USE_GTK GTK+ user interface +# USE_H265 H.265 video codec +# USE_ILBC iLBC audio codec +# USE_ISAC iSAC audio codec +# USE_JACK JACK Audio Connection Kit audio driver +# USE_L16 L16 audio codec +# USE_MPA MPA audo codec +# USE_MPG123 Use mpg123 +# USE_OMX_RPI RaspberryPi VideoCore display driver +# USE_OMX_BELLAGIO libomxil-bellagio xvideosink driver +# USE_OPUS Opus audio codec +# USE_OSS OSS audio driver +# USE_PLC Packet Loss Concealment +# USE_PORTAUDIO Portaudio audio driver +# USE_PULSE Pulseaudio 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 using libre +# USE_STDIO stdio input driver +# USE_SYSLOG Syslog 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_AVCODEC := $(shell [ -f $(SYSROOT)/include/libavcodec/avcodec.h ] || \ + [ -f $(SYSROOT)/local/include/libavcodec/avcodec.h ] || \ + [ -f $(SYSROOT)/include/$(MACHINE)/libavcodec/avcodec.h ] || \ + [ -f $(SYSROOT_ALT)/include/libavcodec/avcodec.h ] && echo "yes") +USE_AVFORMAT := $(shell [ -f $(SYSROOT)/include/libavformat/avformat.h ] || \ + [ -f $(SYSROOT)/local/include/libavformat/avformat.h ] || \ + [ -f $(SYSROOT)/include/$(MACHINE)/libavformat/avformat.h ] || \ + [ -f $(SYSROOT_ALT)/include/libavformat/avformat.h ] && echo "yes") +USE_AVAHI := $(shell pkg-config --exists avahi-client && 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)/local/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_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 pkg-config --exists gstreamer-0.10 && echo "yes") +USE_GST1 := $(shell pkg-config --exists gstreamer-1.0 && echo "yes") +USE_GST_VIDEO := \ + $(shell pkg-config --exists gstreamer-0.10 gstreamer-app-0.10 \ + && echo "yes") +USE_GST_VIDEO1 := $(shell pkg-config --exists gstreamer-1.0 gstreamer-app-1.0 \ + && echo "yes") +USE_GTK := $(shell pkg-config 'gtk+-2.0 >= 2.22' && \ + pkg-config 'glib-2.0 >= 2.32' && echo "yes") +ifneq ($(USE_AVCODEC),) +USE_H265 := $(shell [ -f $(SYSROOT)/include/x265.h ] || \ + [ -f $(SYSROOT)/local/include/x265.h ] || \ + [ -f $(SYSROOT_ALT)/include/x265.h ] && echo "yes") +endif +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_JACK := $(shell [ -f $(SYSROOT)/include/jack/jack.h ] || \ + [ -f $(SYSROOT)/local/include/jack/jack.h ] && echo "yes") +USE_MPG123 := $(shell [ -f $(SYSROOT)/include/mpg123.h ] || \ + [ -f $(SYSROOT)/local/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_PULSE := $(shell pkg-config --exists libpulse && 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 ] || \ + [ -f $(SYSROOT_ALT)/usr/local/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 +ifneq ($(USE_MPG123),) +ifneq ($(HAVE_SPEEXDSP),) +USE_MPA := $(shell [ -f $(SYSROOT)/include/twolame.h ] || \ + [ -f $(SYSROOT)/local/include/twolame.h ] || \ + [ -f $(SYSROOT_ALT)/include/twolame.h ] && echo "yes") +endif +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_SYSLOG := $(shell [ -f $(SYSROOT)/include/syslog.h ] || \ + [ -f $(SYSROOT_ALT)/include/syslog.h ] || \ + [ -f $(SYSROOT)/local/include/syslog.h ] && echo "yes") +HAVE_LIBMQTT := $(shell [ -f $(SYSROOT)/include/mosquitto.h ] || \ + [ -f $(SYSROOT)/local/include/mosquitto.h ] \ + && echo "yes") +USE_V4L := $(shell [ -f $(SYSROOT)/include/libv4l1.h ] || \ + [ -f $(SYSROOT)/local/include/libv4l1.h ] \ + && echo "yes") +HAVE_LIBV4L2 := $(shell [ -f $(SYSROOT)/include/libv4l2.h ] || \ + [ -f $(SYSROOT)/local/include/libv4l2.h ] \ + && echo "yes") +USE_V4L2 := $(shell [ -f $(SYSROOT)/include/linux/videodev2.h ] || \ + [ -f $(SYSROOT)/local/include/linux/videodev2.h ] || \ + [ -f $(SYSROOT)/include/sys/videoio.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_ZRTP := $(shell [ -f $(SYSROOT)/include/libzrtp/zrtp.h ] || \ + [ -f $(SYSROOT)/local/include/libzrtp/zrtp.h ] || \ + [ -f $(SYSROOT_ALT)/include/libzrtp/zrtp.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") +USE_OMX_RPI := $(shell [ -f /opt/vc/include/bcm_host.h ] || \ + [ -f $(SYSROOT)/include/bcm_host.h ] \ + || [ -f $(SYSROOT_ALT)/include/bcm_host.h ] \ + && echo "yes") +USE_OMX_BELLAGIO := $(shell [ -f /usr/include/OMX_Core.h ] \ + || [ -f $(SYSROOT)/include/OMX_Core.h ] \ + || [ -f $(SYSROOT_ALT)/include/OMX_Core.h ] \ + && echo "yes") +else +# Windows. +# Accounts for mingw with Windows SDK (formerly known as Platform SDK) +# mounted at /winsdk +USE_DSHOW := $(shell [ -f /winsdk/Include/um/dshow.h ] && echo "yes") +endif + +# Platform specific modules +ifeq ($(OS),darwin) +USE_COREAUDIO := yes +USE_OPENGL := yes + +USE_AVFOUNDATION := \ + $(shell [ -d /System/Library/Frameworks/AVFoundation.framework ] \ + && echo "yes") + +USE_AUDIOUNIT := \ + $(shell [ -d /System/Library/Frameworks/AudioUnit.framework ] \ + && echo "yes") + +ifneq ($(USE_AVFOUNDATION),) +USE_AVCAPTURE := yes +else +USE_QTCAPTURE := yes +endif + + +endif +ifeq ($(OS),linux) +USE_EVDEV := $(shell [ -f $(SYSROOT)/include/linux/input.h ] && echo "yes") +MODULES += dtmfio +endif +ifeq ($(OS),win32) +USE_WINWAVE := yes +MODULES += wincons +endif +ifeq ($(OS),openbsd) +MODULES += sndio +endif +ifeq ($(OS),freebsd) +MODULES += dtmfio +endif + +ifneq ($(USE_GTK),) +USE_LIBNOTIFY := $(shell pkg-config 'libnotify glib-2.0 < 2.40' && echo "yes") +endif + +endif + +# ------------------------------------------------------------------------- # + +MODULES += $(EXTRA_MODULES) +MODULES += stun turn ice natbd auloop presence +MODULES += menu contact vumeter mwi account natpmp httpd +MODULES += srtp +MODULES += uuid +MODULES += debug_cmd + +ifneq ($(HAVE_LIBMQTT),) +MODULES += mqtt +endif + +ifneq ($(HAVE_PTHREAD),) +MODULES += aubridge aufile +endif +ifneq ($(USE_VIDEO),) +MODULES += vidloop selfview vidbridge +ifneq ($(HAVE_PTHREAD),) +MODULES += fakevideo +endif +endif + + +ifneq ($(USE_ALSA),) +MODULES += alsa +endif +ifneq ($(USE_AMR),) +MODULES += amr +endif +ifneq ($(USE_AUDIOUNIT),) +MODULES += audiounit +endif +ifneq ($(USE_AVCAPTURE),) +MODULES += avcapture +endif +ifneq ($(USE_AVCODEC),) +MODULES += avcodec +ifneq ($(USE_AVFORMAT),) +MODULES += avformat +endif +ifneq ($(USE_AVAHI),) +MODULES += avahi +endif +endif +ifneq ($(USE_BV32),) +MODULES += bv32 +endif +ifneq ($(USE_CAIRO),) +MODULES += cairo +MODULES += vidinfo +ifneq ($(USE_MPG123),) +MODULES += rst +endif +endif +ifneq ($(USE_CONS),) +MODULES += cons +endif +ifneq ($(USE_COREAUDIO),) +MODULES += coreaudio +endif +ifneq ($(USE_DTLS_SRTP),) +MODULES += dtls_srtp +endif +ifneq ($(USE_QTCAPTURE),) +MODULES += qtcapture +CFLAGS += -DQTCAPTURE_RUNLOOP +endif +ifneq ($(USE_EVDEV),) +MODULES += evdev +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_GST1),) +MODULES += gst1 +endif +ifneq ($(USE_GST_VIDEO),) +MODULES += gst_video +endif +ifneq ($(USE_GST_VIDEO1),) +MODULES += gst_video1 +endif +ifneq ($(USE_GTK),) +MODULES += gtk +endif +ifneq ($(USE_H265),) +MODULES += h265 +endif +ifneq ($(USE_ILBC),) +MODULES += ilbc +endif +ifneq ($(USE_ISAC),) +MODULES += isac +endif +ifneq ($(USE_JACK),) +MODULES += jack +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_MPA),) +MODULES += mpa +endif +ifneq ($(USE_OSS),) +MODULES += oss +endif +ifneq ($(USE_PLC),) +MODULES += plc +endif +ifneq ($(USE_PORTAUDIO),) +MODULES += portaudio +endif +ifneq ($(USE_PULSE),) +MODULES += pulse +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_STDIO),) +MODULES += stdio +endif +ifneq ($(USE_SYSLOG),) +MODULES += syslog +endif +ifneq ($(USE_V4L),) +MODULES += v4l +endif +ifneq ($(USE_V4L2),) +MODULES += v4l2 v4l2_codec +endif +ifneq ($(USE_OMX_RPI),) +MODULES += omx +endif +ifneq ($(USE_OMX_BELLAGIO),) +MODULES += omx +endif +ifneq ($(USE_VPX),) +MODULES += vp8 +MODULES += $(shell pkg-config 'vpx >= 1.3.0' && echo "vp9") +endif +ifneq ($(USE_WINWAVE),) +MODULES += winwave +endif +ifneq ($(USE_X11),) +MODULES += x11 x11grab +endif +ifneq ($(USE_ZRTP),) +MODULES += zrtp +endif +ifneq ($(USE_GZRTP),) +MODULES += gzrtp +endif +ifneq ($(USE_DSHOW),) +MODULES += dshow +endif diff --git a/mk/win32/baresip.sln b/mk/win32/baresip.sln new file mode 100644 index 0000000..7c791e8 --- /dev/null +++ b/mk/win32/baresip.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baresip-win32", "baresip.vcxproj", "{4B89C2D8-FB32-4D7C-9019-752A5664781C}" + ProjectSection(ProjectDependencies) = postProject + {3E767371-A72B-4F5C-A695-8F844B0889C5} = {3E767371-A72B-4F5C-A695-8F844B0889C5} + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} = {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "re-win32", "..\..\..\re\mk\win32\re.vcxproj", "{40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rem-win32", "..\..\..\rem\mk\win32\rem.vcxproj", "{3E767371-A72B-4F5C-A695-8F844B0889C5}" + ProjectSection(ProjectDependencies) = postProject + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} = {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Debug|Win32.ActiveCfg = Debug|Win32 + {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Debug|Win32.Build.0 = Debug|Win32 + {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Release|Win32.ActiveCfg = Release|Win32 + {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Release|Win32.Build.0 = Release|Win32 + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Debug|Win32.ActiveCfg = Debug|Win32 + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Debug|Win32.Build.0 = Debug|Win32 + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Release|Win32.ActiveCfg = Release|Win32 + {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Release|Win32.Build.0 = Release|Win32 + {3E767371-A72B-4F5C-A695-8F844B0889C5}.Debug|Win32.ActiveCfg = Debug|Win32 + {3E767371-A72B-4F5C-A695-8F844B0889C5}.Debug|Win32.Build.0 = Debug|Win32 + {3E767371-A72B-4F5C-A695-8F844B0889C5}.Release|Win32.ActiveCfg = Release|Win32 + {3E767371-A72B-4F5C-A695-8F844B0889C5}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/mk/win32/baresip.vcxproj b/mk/win32/baresip.vcxproj new file mode 100644 index 0000000..c321b5f --- /dev/null +++ b/mk/win32/baresip.vcxproj @@ -0,0 +1,204 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>baresip-win32</ProjectName>
+ <ProjectGuid>{4B89C2D8-FB32-4D7C-9019-752A5664781C}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <PlatformToolset>v140</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <PlatformToolset>v140</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <CompileAs>CompileAsC</CompileAs>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalOptions>winmm.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions)</AdditionalOptions>
+ <OutputFile>$(OutDir)baresip-win32.exe</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)baresip-win32.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalOptions>winmm.lib wsock32.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions)</AdditionalOptions>
+ <OutputFile>$(OutDir)baresip-win32.exe</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\rem\mk\win32\rem.vcxproj">
+ <Project>{3e767371-a72b-4f5c-a695-8f844b0889c5}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\re\mk\win32\re.vcxproj">
+ <Project>{40b28df6-4b4a-411a-9eb7-8d80c2a29b9d}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\baresip.h" />
+ <ClInclude Include="..\..\modules\amr\amr.h" />
+ <ClInclude Include="..\..\modules\natpmp\libnatpmp.h" />
+ <ClInclude Include="..\..\modules\presence\presence.h" />
+ <ClInclude Include="..\..\modules\srtp\sdes.h" />
+ <ClInclude Include="..\..\modules\vidbridge\vidbridge.h" />
+ <ClInclude Include="..\..\modules\winwave\winwave.h" />
+ <ClInclude Include="..\..\src\core.h" />
+ <ClInclude Include="..\..\src\magic.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\modules\account\account.c" />
+ <ClCompile Include="..\..\modules\amr\amr.c" />
+ <ClCompile Include="..\..\modules\amr\sdp.c" />
+ <ClCompile Include="..\..\modules\auloop\auloop.c" />
+ <ClCompile Include="..\..\modules\b2bua\b2bua.c" />
+ <ClCompile Include="..\..\modules\cons\cons.c" />
+ <ClCompile Include="..\..\modules\contact\contact.c" />
+ <ClCompile Include="..\..\modules\debug_cmd\debug_cmd.c" />
+ <ClCompile Include="..\..\modules\dshow\dshow.cpp">
+ <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsCpp</CompileAs>
+ <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">CompileAsCpp</CompileAs>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\echo\echo.c" />
+ <ClCompile Include="..\..\modules\g711\g711.c" />
+ <ClCompile Include="..\..\modules\httpd\httpd.c" />
+ <ClCompile Include="..\..\modules\ice\ice.c" />
+ <ClCompile Include="..\..\modules\l16\l16.c" />
+ <ClCompile Include="..\..\modules\menu\menu.c" />
+ <ClCompile Include="..\..\modules\mwi\mwi.c" />
+ <ClCompile Include="..\..\modules\natbd\natbd.c" />
+ <ClCompile Include="..\..\modules\natpmp\libnatpmp.c" />
+ <ClCompile Include="..\..\modules\natpmp\natpmp.c" />
+ <ClCompile Include="..\..\modules\presence\notifier.c" />
+ <ClCompile Include="..\..\modules\presence\presence.c" />
+ <ClCompile Include="..\..\modules\presence\publisher.c" />
+ <ClCompile Include="..\..\modules\presence\subscriber.c" />
+ <ClCompile Include="..\..\modules\selfview\selfview.c" />
+ <ClCompile Include="..\..\modules\srtp\sdes.c" />
+ <ClCompile Include="..\..\modules\srtp\srtp.c" />
+ <ClCompile Include="..\..\modules\stun\stun.c" />
+ <ClCompile Include="..\..\modules\turn\turn.c" />
+ <ClCompile Include="..\..\modules\uuid\uuid.c" />
+ <ClCompile Include="..\..\modules\vidbridge\disp.c" />
+ <ClCompile Include="..\..\modules\vidbridge\src.c" />
+ <ClCompile Include="..\..\modules\vidbridge\vidbridge.c" />
+ <ClCompile Include="..\..\modules\vidloop\vidloop.c" />
+ <ClCompile Include="..\..\modules\vumeter\vumeter.c" />
+ <ClCompile Include="..\..\modules\wincons\wincons.c" />
+ <ClCompile Include="..\..\modules\winwave\play.c" />
+ <ClCompile Include="..\..\modules\winwave\src.c" />
+ <ClCompile Include="..\..\modules\winwave\winwave.c" />
+ <ClCompile Include="..\..\src\account.c" />
+ <ClCompile Include="..\..\src\aucodec.c" />
+ <ClCompile Include="..\..\src\audio.c" />
+ <ClCompile Include="..\..\src\aufilt.c" />
+ <ClCompile Include="..\..\src\auplay.c" />
+ <ClCompile Include="..\..\src\ausrc.c" />
+ <ClCompile Include="..\..\src\baresip.c" />
+ <ClCompile Include="..\..\src\bfcp.c" />
+ <ClCompile Include="..\..\src\call.c" />
+ <ClCompile Include="..\..\src\cmd.c" />
+ <ClCompile Include="..\..\src\conf.c" />
+ <ClCompile Include="..\..\src\config.c" />
+ <ClCompile Include="..\..\src\contact.c" />
+ <ClCompile Include="..\..\src\h264.c" />
+ <ClCompile Include="..\..\src\log.c" />
+ <ClCompile Include="..\..\src\main.c" />
+ <ClCompile Include="..\..\src\mctrl.c" />
+ <ClCompile Include="..\..\src\menc.c" />
+ <ClCompile Include="..\..\src\message.c" />
+ <ClCompile Include="..\..\src\metric.c" />
+ <ClCompile Include="..\..\src\mnat.c" />
+ <ClCompile Include="..\..\src\module.c" />
+ <ClCompile Include="..\..\src\mos.c" />
+ <ClCompile Include="..\..\src\net.c" />
+ <ClCompile Include="..\..\src\play.c" />
+ <ClCompile Include="..\..\src\realtime.c" />
+ <ClCompile Include="..\..\src\reg.c" />
+ <ClCompile Include="..\..\src\rtpkeep.c" />
+ <ClCompile Include="..\..\src\sdp.c" />
+ <ClCompile Include="..\..\src\sipreq.c" />
+ <ClCompile Include="..\..\src\stream.c" />
+ <ClCompile Include="..\..\src\ua.c" />
+ <ClCompile Include="..\..\src\ui.c" />
+ <ClCompile Include="..\..\src\vidcodec.c" />
+ <ClCompile Include="..\..\src\video.c" />
+ <ClCompile Include="..\..\src\vidfilt.c" />
+ <ClCompile Include="..\..\src\vidisp.c" />
+ <ClCompile Include="..\..\src\vidsrc.c" />
+ <ClCompile Include="static.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/mk/win32/baresip.vcxproj.filters b/mk/win32/baresip.vcxproj.filters new file mode 100644 index 0000000..5098a2b --- /dev/null +++ b/mk/win32/baresip.vcxproj.filters @@ -0,0 +1,366 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="include">
+ <UniqueIdentifier>{2334866b-8357-4e18-8898-48db4dfe2a15}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src">
+ <UniqueIdentifier>{5c30c989-28e5-402f-aaa0-860eafa95cc5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\video">
+ <UniqueIdentifier>{f5f435ce-121f-4b09-b66f-89e75e8abb40}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\static">
+ <UniqueIdentifier>{7f749aed-9d81-46c6-bdde-690ddc51a0a4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules">
+ <UniqueIdentifier>{6c97676c-7c18-404f-85a1-2fded4f104e3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\account">
+ <UniqueIdentifier>{f528695e-cf16-4c35-89ed-adcc330b63bc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\amr">
+ <UniqueIdentifier>{b8e32268-7d2c-46ae-b6a1-d7b02bbd4026}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\auloop">
+ <UniqueIdentifier>{0e11ace7-27c2-4ad3-8b5e-b3bf772f0b4d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\b2bua">
+ <UniqueIdentifier>{f44d6650-15a0-4fb3-8fe3-d91f522be831}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\cons">
+ <UniqueIdentifier>{6606d763-a0be-4bcc-a689-a9e5e38df7f5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\contact">
+ <UniqueIdentifier>{785597c1-f018-4f86-ae98-4855e6130bf7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\debug_cmd">
+ <UniqueIdentifier>{b139606b-86de-4c90-af91-f44b04bd1330}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\dshow">
+ <UniqueIdentifier>{4c01b3b6-a748-4cf1-af45-a8d5b7735267}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\echo">
+ <UniqueIdentifier>{3c2cd0c3-9de0-42ca-8f3a-6d7d929ce501}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\g711">
+ <UniqueIdentifier>{e679754c-643a-4284-9ed6-bf6fdc73e152}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\httpd">
+ <UniqueIdentifier>{8ca10960-9be4-4d19-9e32-2ae5bc415c0a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\ice">
+ <UniqueIdentifier>{1def0f02-ca40-49ee-b5e1-95aac5d2dda3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\l16">
+ <UniqueIdentifier>{5446737f-2a0f-4b4f-a86e-55d9c20118e7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\menu">
+ <UniqueIdentifier>{ebbcb368-d1fa-4899-80cf-e43ff4fde9b2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\mwi">
+ <UniqueIdentifier>{a7b0f22a-b82a-4594-a367-d8b8a9882f0d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\natbd">
+ <UniqueIdentifier>{7ceb961c-1033-4259-8f3e-ffa2d28a0f12}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\natpmp">
+ <UniqueIdentifier>{c093ccc3-d662-46e0-83df-2eb34e7e7d21}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\presence">
+ <UniqueIdentifier>{c82f29fb-bb8d-45c7-ad53-d25e997c1453}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\selfview">
+ <UniqueIdentifier>{ed5cc330-666d-4f9a-81a2-db7873b7abaa}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\srtp">
+ <UniqueIdentifier>{31ac6861-a9e9-467a-82ae-53d1d929e696}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\stun">
+ <UniqueIdentifier>{6e5dac26-781e-4683-958f-2e18fdc0b42e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\turn">
+ <UniqueIdentifier>{5a3c72cb-a1a5-4c46-b0b5-761d3677e56a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\uuid">
+ <UniqueIdentifier>{a4d5f1d9-5629-479e-86d4-4e7b6d58a38a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vidbridge">
+ <UniqueIdentifier>{044bc40b-b2a8-4450-b76a-d959559df6b1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vidloop">
+ <UniqueIdentifier>{71dc9cb5-19b1-4f74-bc23-02ca6b5a10b3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vumeter">
+ <UniqueIdentifier>{bc7810ea-bc52-4bee-a15c-aed12cf2a777}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\wincons">
+ <UniqueIdentifier>{8e7e8b8a-dbe7-4b58-80f3-e632b40d0e4e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\winwave">
+ <UniqueIdentifier>{e3bd480b-289e-4754-b1a2-55ea85a7ef45}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\baresip.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\core.h">
+ <Filter>src</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\magic.h">
+ <Filter>src</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\amr\amr.h">
+ <Filter>modules\amr</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\natpmp\libnatpmp.h">
+ <Filter>modules\natpmp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\presence\presence.h">
+ <Filter>modules\presence</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\srtp\sdes.h">
+ <Filter>modules\srtp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\vidbridge\vidbridge.h">
+ <Filter>modules\vidbridge</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\winwave\winwave.h">
+ <Filter>modules\winwave</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\src\account.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\aucodec.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\audio.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\aufilt.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\auplay.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ausrc.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\baresip.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\call.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\cmd.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\conf.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\config.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\contact.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\log.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\main.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\menc.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\message.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\metric.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mnat.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\module.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mos.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\play.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\realtime.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\reg.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtpkeep.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipreq.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stream.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ua.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ui.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\h264.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mctrl.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidcodec.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\video.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidfilt.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidisp.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidsrc.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="static.c">
+ <Filter>src\static</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\account\account.c">
+ <Filter>modules\account</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\amr\amr.c">
+ <Filter>modules\amr</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\amr\sdp.c">
+ <Filter>modules\amr</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\auloop\auloop.c">
+ <Filter>modules\auloop</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\b2bua\b2bua.c">
+ <Filter>modules\b2bua</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\cons\cons.c">
+ <Filter>modules\cons</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\contact\contact.c">
+ <Filter>modules\contact</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\debug_cmd\debug_cmd.c">
+ <Filter>modules\debug_cmd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\dshow\dshow.cpp">
+ <Filter>modules\dshow</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\echo\echo.c">
+ <Filter>modules\echo</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\g711\g711.c">
+ <Filter>modules\g711</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\httpd\httpd.c">
+ <Filter>modules\httpd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\ice\ice.c">
+ <Filter>modules\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\l16\l16.c">
+ <Filter>modules\l16</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\menu\menu.c">
+ <Filter>modules\menu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\mwi\mwi.c">
+ <Filter>modules\mwi</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natbd\natbd.c">
+ <Filter>modules\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natpmp\libnatpmp.c">
+ <Filter>modules\natpmp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natpmp\natpmp.c">
+ <Filter>modules\natpmp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\notifier.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\presence.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\publisher.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\subscriber.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\selfview\selfview.c">
+ <Filter>modules\selfview</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\srtp\sdes.c">
+ <Filter>modules\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\srtp\srtp.c">
+ <Filter>modules\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\stun\stun.c">
+ <Filter>modules\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\turn\turn.c">
+ <Filter>modules\turn</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\uuid\uuid.c">
+ <Filter>modules\uuid</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\disp.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\src.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\vidbridge.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidloop\vidloop.c">
+ <Filter>modules\vidloop</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vumeter\vumeter.c">
+ <Filter>modules\vumeter</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\wincons\wincons.c">
+ <Filter>modules\wincons</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\play.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\src.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\winwave.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project>
\ No newline at end of file diff --git a/mk/win32/static.c b/mk/win32/static.c new file mode 100644 index 0000000..4d67d9e --- /dev/null +++ b/mk/win32/static.c @@ -0,0 +1,38 @@ +/* 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_wincons; +extern const struct mod_export exports_g711; +extern const struct mod_export exports_winwave; +extern const struct mod_export exports_dshow; +extern const struct mod_export exports_account; +extern const struct mod_export exports_contact; +extern const struct mod_export exports_menu; +extern const struct mod_export exports_auloop; +extern const struct mod_export exports_vidloop; +extern const struct mod_export exports_uuid; +extern const struct mod_export exports_stun; +extern const struct mod_export exports_turn; +extern const struct mod_export exports_ice; +extern const struct mod_export exports_vumeter; + + +const struct mod_export *mod_table[] = { + &exports_wincons, + &exports_g711, + &exports_winwave, + &exports_dshow, + &exports_account, + &exports_contact, + &exports_menu, + &exports_auloop, + &exports_vidloop, + &exports_uuid, + &exports_stun, + &exports_turn, + &exports_ice, + &exports_vumeter, + NULL +}; diff --git a/modules/account/account.c b/modules/account/account.c new file mode 100644 index 0000000..c972908 --- /dev/null +++ b/modules/account/account.c @@ -0,0 +1,213 @@ +/** + * @file account/account.c Load SIP accounts from file + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup account account + * + * Load SIP accounts from a file + * + * This module is loading SIP accounts from file ~/.baresip/accounts. + * If the file exist and is readable, all SIP accounts will be populated + * from this file. If the file does not exist, a template file will be + * created. + * + * Examples: + \verbatim + "User 1 with password prompt" <sip:user@domain.com> + "User 2 with stored password" <sip:user:pass@domain.com> + "User 2 with ICE" <sip:user@1.2.3.4;transport=tcp>;medianat=ice + "User 3 with IPv6" <sip:user@[2001:df8:0:16:216:6fff:fe91:614c]:5070> + \endverbatim + */ + + +static int account_write_template(const char *file) +{ + FILE *f = NULL; + const char *login, *pass, *domain; + int r, err = 0; + + 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(baresip_network()); + if (!domain) + domain = "domain"; + + r = 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" + ";transport=tcp\"\n" + "# ;outbound2=sip:secondary.example.com\n" + "# ;ptime={10,20,30,40,...}\n" + "# ;regint=3600\n" + "# ;pubint=0 (publishing off)\n" + "# ;regq=0.5\n" + "# ;rtpkeep={zero,stun,dyna,rtcp}\n" + "# ;sipnat={outbound}\n" + "# ;stunuser=STUN/TURN/ICE-username\n" + "# ;stunpass=STUN/TURN/ICE-password\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 (r < 0) + err = ENOMEM; + + if (f) + (void)fclose(f); + + return err; +} + + +/** + * Add a User-Agent (UA) + * + * @param addr SIP Address string + * + * @return 0 if success, otherwise errorcode + */ +static int line_handler(const struct pl *addr, void *arg) +{ + char buf[512]; + struct ua *ua; + struct account *acc; + int err; + (void)arg; + + (void)pl_strcpy(addr, buf, sizeof(buf)); + + err = ua_alloc(&ua, buf); + if (err) + return err; + + acc = ua_account(ua); + if (!acc) { + warning("account: no account for this ua\n"); + return ENOENT; + } + + /* optional password prompt */ + if (!str_isset(account_auth_pass(acc))) { + char *pass = NULL; + + (void)re_printf("Please enter password for %s: ", + account_aor(acc)); + + err = ui_password_prompt(&pass); + if (err) + goto out; + + err = account_set_auth_pass(acc, pass); + + mem_deref(pass); + } + + out: + return err; +} + + +/** + * 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, NULL); + if (err) + return err; + + n = list_count(uag_list()); + info("Populated %u account%s\n", n, 1==n ? "" : "s"); + + if (list_isempty(uag_list())) { + info("account: No SIP accounts found\n" + " -- check your config " + "or add an account using 'uanew' command\n"); + } + + 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..8f461d6 --- /dev/null +++ b/modules/account/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2015 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..78de296 --- /dev/null +++ b/modules/alsa/alsa.c @@ -0,0 +1,184 @@ +/** + * @file alsa.c ALSA sound driver + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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; + + +int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch, + uint32_t num_frames, + snd_pcm_format_t pcmfmt) +{ + snd_pcm_hw_params_t *hw_params = NULL; + snd_pcm_uframes_t period = num_frames, bufsize = num_frames * 4; + int err; + + debug("alsa: reset: srate=%u, ch=%u, num_frames=%u, pcmfmt=%s\n", + srate, ch, num_frames, snd_pcm_format_name(pcmfmt)); + + 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; +} + + +snd_pcm_format_t aufmt_to_alsaformat(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return SND_PCM_FORMAT_S16; + case AUFMT_FLOAT: return SND_PCM_FORMAT_FLOAT; + case AUFMT_S24_3LE: return SND_PCM_FORMAT_S24_3LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + + +static int alsa_init(void) +{ + struct pl val; + int err; + + /* XXX: remove check later */ + if (0 == conf_get(conf_cur(), "alsa_sample_format", &val)) { + + warning("alsa: alsa_sample_format is deprecated" + " -- use ausrc_format or auplay_format instead\n"); + + (void)val; + } + + err = ausrc_register(&ausrc, baresip_ausrcl(), + "alsa", alsa_src_alloc); + err |= auplay_register(&auplay, baresip_auplayl(), + "alsa", alsa_play_alloc); + + return err; +} + + +static int alsa_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + /* releases all resources of the global configuration tree, + and sets snd_config to NULL. */ + snd_config_update_free_global(); + + 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..fbd67f2 --- /dev/null +++ b/modules/alsa/alsa.h @@ -0,0 +1,20 @@ +/** + * @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, + uint32_t num_frames, snd_pcm_format_t pcmfmt); +snd_pcm_format_t aufmt_to_alsaformat(enum aufmt fmt); +int alsa_src_alloc(struct ausrc_st **stp, const 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, const 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..6547e21 --- /dev/null +++ b/modules/alsa/alsa_play.c @@ -0,0 +1,169 @@ +/** + * @file alsa_play.c ALSA sound driver - player + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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 { + const struct auplay *ap; /* pointer to base-class (inheritance) */ + pthread_t thread; + bool run; + snd_pcm_t *write; + void *sampv; + size_t sampc; + 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) { + debug("alsa: stopping playback thread (%s)\n", st->device); + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->write) + snd_pcm_close(st->write); + + mem_deref(st->sampv); + 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; + void *sampv; + + st->wh(st->sampv, st->sampc, st->arg); + + sampv = st->sampv; + + n = snd_pcm_writei(st->write, sampv, samples); + + if (-EPIPE == n) { + snd_pcm_prepare(st->write); + + n = snd_pcm_writei(st->write, sampv, 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 samples\n", + n, samples); + } + } + + return NULL; +} + + +int alsa_play_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + snd_pcm_format_t pcmfmt; + int num_frames; + int err; + + if (!stp || !ap || !prm || !wh) + 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 = ap; + st->wh = wh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + num_frames = st->prm.srate * st->prm.ptime / 1000; + + st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); + if (!st->sampv) { + 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; + } + + pcmfmt = aufmt_to_alsaformat(prm->fmt); + if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) { + warning("alsa: unknown sample format '%s'\n", + aufmt_name(prm->fmt)); + err = EINVAL; + goto out; + } + + err = alsa_reset(st->write, st->prm.srate, st->prm.ch, num_frames, + pcmfmt); + 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; + } + + debug("alsa: playback started (%s)\n", st->device); + + 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..a21f846 --- /dev/null +++ b/modules/alsa/alsa_src.c @@ -0,0 +1,174 @@ +/** + * @file alsa_src.c ALSA sound driver - recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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 { + const struct ausrc *as; /* pointer to base-class (inheritance) */ + pthread_t thread; + bool run; + snd_pcm_t *read; + void *sampv; + size_t sampc; + 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) { + debug("alsa: stopping recording thread (%s)\n", st->device); + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->read) + snd_pcm_close(st->read); + + mem_deref(st->sampv); + 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) { + size_t sampc; + void *sampv; + + sampv = st->sampv; + + err = snd_pcm_readi(st->read, sampv, num_frames); + if (err == -EPIPE) { + snd_pcm_prepare(st->read); + continue; + } + else if (err <= 0) { + continue; + } + + sampc = err * st->prm.ch; + + st->rh(st->sampv, sampc, st->arg); + } + + out: + return NULL; +} + + +int alsa_src_alloc(struct ausrc_st **stp, const 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; + snd_pcm_format_t pcmfmt; + int num_frames; + int err; + (void)ctx; + (void)errh; + + if (!stp || !as || !prm || !rh) + 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 = as; + st->rh = rh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + num_frames = st->prm.srate * st->prm.ptime / 1000; + + st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); + if (!st->sampv) { + 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; + } + + pcmfmt = aufmt_to_alsaformat(prm->fmt); + if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) { + warning("alsa: unknown sample format '%s'\n", + aufmt_name(prm->fmt)); + err = EINVAL; + goto out; + } + + err = alsa_reset(st->read, st->prm.srate, st->prm.ch, num_frames, + pcmfmt); + 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; + } + + debug("alsa: recording started (%s) format=%s\n", + st->device, aufmt_name(prm->fmt)); + + 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..885a318 --- /dev/null +++ b/modules/amr/amr.c @@ -0,0 +1,344 @@ +/** + * @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> +#include "amr.h" + + +#ifdef VO_AMRWBENC_ENC_IF_H +#define IF2E_IF_encode E_IF_encode +#define IF2D_IF_decode D_IF_decode +#endif + + +/** + * @defgroup amr amr + * + * This module supports both AMR Narrowband (8000 Hz) and + * AMR Wideband (16000 Hz) audio codecs. + * + * NOTE: only octet-align mode is supported. + * + * + * 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; + + /* CMR value 15 indicates that no mode request is present */ + buf[0] = 15 << 4; + + n = IF2E_IF_encode(st->enc, 8, sampv, &buf[1], 0); + if (n <= 0) + return EPROTO; + + *len = (1 + 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[1], 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; + + /* CMR value 15 indicates that no mode request is present */ + buf[0] = 15 << 4; + + r = Encoder_Interface_Encode(st->enc, MR122, sampv, &buf[1], 0); + if (r <= 0) + return EPROTO; + + *len = (1 + 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[1], sampv, 0); + + *sampc = FRAMESIZE_NB; + + return 0; +} +#endif + + +#ifdef AMR_WB +static struct aucodec amr_wb = { + LE_INIT, NULL, "AMR-WB", 16000, 16000, 1, NULL, + encode_update, encode_wb, + decode_update, decode_wb, + NULL, amr_fmtp_enc, amr_fmtp_cmp +}; +#endif +#ifdef AMR_NB +static struct aucodec amr_nb = { + LE_INIT, NULL, "AMR", 8000, 8000, 1, NULL, + encode_update, encode_nb, + decode_update, decode_nb, + NULL, amr_fmtp_enc, amr_fmtp_cmp +}; +#endif + + +static int module_init(void) +{ + int err = 0; + +#ifdef AMR_WB + aucodec_register(baresip_aucodecl(), &amr_wb); +#endif +#ifdef AMR_NB + aucodec_register(baresip_aucodecl(), &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; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(amr) = { + "amr", + "codec", + module_init, + module_close +}; diff --git a/modules/amr/amr.h b/modules/amr/amr.h new file mode 100644 index 0000000..6746a83 --- /dev/null +++ b/modules/amr/amr.h @@ -0,0 +1,10 @@ +/** + * @file amr/amr.h AMR module -- internal interface + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + + +int amr_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool amr_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg); diff --git a/modules/amr/module.mk b/modules/amr/module.mk new file mode 100644 index 0000000..328f048 --- /dev/null +++ b/modules/amr/module.mk @@ -0,0 +1,64 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := amr +$(MOD)_SRCS += amr.c sdp.c + + +ifneq ($(shell [ -d $(SYSROOT)/include/opencore-amrnb ] && echo 1 ),) +$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/include/opencore-amrnb +$(MOD)_LFLAGS += -lopencore-amrnb +else +ifneq ($(shell [ -d $(SYSROOT_ALT)/include/opencore-amrnb ] && echo 1 ),) +$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT_ALT)/include/opencore-amrnb +$(MOD)_LFLAGS += -lopencore-amrnb +else +ifneq ($(shell [ -d $(SYSROOT)/local/include/amrnb ] && echo 1),) +$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/local/include/amrnb +$(MOD)_LFLAGS += -lamrnb +else +ifneq ($(shell [ -d $(SYSROOT)/include/amrnb ] && echo 1),) +$(MOD)_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 ),) +$(MOD)_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),) +$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/local/include/amrwb +$(MOD)_LFLAGS += -lamrwb +else +ifneq ($(shell [ -f $(SYSROOT)/include/amrwb/enc_if.h ] && echo 1),) +$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/include/amrwb +$(MOD)_LFLAGS += -lamrwb +else +ifneq ($(shell [ -f $(SYSROOT)/include/vo-amrwbenc/enc_if.h ] && echo 1),) +$(MOD)_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 ),) +$(MOD)_CFLAGS += -I$(SYSROOT)/include/opencore-amrwb +$(MOD)_LFLAGS += -lopencore-amrwb +endif + + +$(MOD)_LFLAGS += -lm + + +include mk/mod.mk diff --git a/modules/amr/sdp.c b/modules/amr/sdp.c new file mode 100644 index 0000000..e4b0cdf --- /dev/null +++ b/modules/amr/sdp.c @@ -0,0 +1,56 @@ +/** + * @file amr/sdp.c AMR SDP Functions + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "amr.h" + + +static bool amr_octet_align(const char *fmtp) +{ + struct pl pl, oa; + + if (!fmtp) + return false; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "octet-align", &oa)) + return 0 == pl_strcmp(&oa, "1"); + + return false; +} + + +int amr_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct aucodec *ac = arg; + (void)offer; + + if (!mb || !fmt || !ac) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s octet-align=1\r\n", + fmt->id); +} + + +bool amr_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg) +{ + const struct aucodec *ac = arg; + (void)lfmtp; + + if (!ac) + return false; + + if (!amr_octet_align(rfmtp)) { + info("amr: octet-align mode is required\n"); + return false; + } + + return true; +} diff --git a/modules/aubridge/aubridge.c b/modules/aubridge/aubridge.c new file mode 100644 index 0000000..13e0527 --- /dev/null +++ b/modules/aubridge/aubridge.c @@ -0,0 +1,67 @@ +/** + * @file aubridge.c Audio bridge + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "aubridge.h" + + +/** + * @defgroup aubridge aubridge + * + * Audio bridge module + * + * This module can be used to connect two audio devices together, + * so that all output to AUPLAY device is bridged as the input to + * a AUSRC device. + * + * Sample config: + * + \verbatim + audio_player aubridge,pseudo0 + audio_source aubridge,pseudo0 + \endverbatim + */ + + +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, baresip_ausrcl(), "aubridge", src_alloc); + err |= auplay_register(&auplay, baresip_auplayl(), + "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..7e24b6c --- /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 { + const struct ausrc *as; /* inheritance */ + struct device *dev; + struct ausrc_prm prm; + ausrc_read_h *rh; + void *arg; +}; + +struct auplay_st { + const 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, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int src_alloc(struct ausrc_st **stp, const 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..392d8e0 --- /dev/null +++ b/modules/aubridge/device.c @@ -0,0 +1,187 @@ +/** + * @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(sampv_in, sampc_in, dev->auplay->arg); + } + + if (rs.resample) { + err = auresamp(&rs, + sampv_out, &sampc_out, + sampv_in, sampc_in); + if (err) { + warning("aubridge: auresamp error" + " sampc_out=%zu, sampc_in=%zu (%m)\n", + sampc_out, sampc_in, err); + } + + if (dev->ausrc && dev->ausrc->rh) { + dev->ausrc->rh(sampv_out, sampc_out, + dev->ausrc->arg); + } + } + else { + if (dev->ausrc && dev->ausrc->rh) { + dev->ausrc->rh(sampv_in, sampc_in, + 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; + + info("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..ff0b98f --- /dev/null +++ b/modules/aubridge/play.c @@ -0,0 +1,58 @@ +/** + * @file aubridge/play.c Audio bridge -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.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); +} + + +int play_alloc(struct auplay_st **stp, const 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; + + if (prm->fmt != AUFMT_S16LE) { + warning("aubridge: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = 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..87fd64a --- /dev/null +++ b/modules/aubridge/src.c @@ -0,0 +1,61 @@ +/** + * @file aubridge/src.c Audio bridge -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.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); +} + + +int src_alloc(struct ausrc_st **stp, const 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; + + if (prm->fmt != AUFMT_S16LE) { + warning("aubridge: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = 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..b03bcec --- /dev/null +++ b/modules/audiounit/audiounit.c @@ -0,0 +1,97 @@ +/** + * @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" + + +/** + * @defgroup audiounit audiounit + * + * Audio driver module for OSX/iOS AudioUnit + */ + + +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, baresip_auplayl(), + "audiounit", audiounit_player_alloc); + err |= ausrc_register(&ausrc, baresip_ausrcl(), + "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..28e4a3a --- /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, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int audiounit_recorder_alloc(struct ausrc_st **stp, const 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..230b1ad --- /dev/null +++ b/modules/audiounit/player.c @@ -0,0 +1,212 @@ +/** + * @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 <rem.h> +#include <baresip.h> +#include "audiounit.h" + + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + struct audiosess_st *sess; + AudioUnit au; + pthread_mutex_t mutex; + auplay_write_h *wh; + void *arg; + uint32_t sampsz; +}; + + +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); + + 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]; + + wh(ab->mData, ab->mDataByteSize/st->sampsz, arg); + } + + return 0; +} + + +static void interrupt_handler(bool interrupted, void *arg) +{ + struct auplay_st *st = arg; + + if (interrupted) + AudioOutputUnitStop(st->au); + else + AudioOutputUnitStart(st->au); +} + + +static uint32_t aufmt_to_formatflags(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger; + case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat; + default: return 0; + } +} + + +int audiounit_player_alloc(struct auplay_st **stp, const 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; + Float64 hw_srate = 0.0; + UInt32 hw_size = sizeof(hw_srate); + int err; + + (void)device; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = 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) { + warning("audiounit: EnableIO failed (%d)\n", ret); + goto out; + } + + st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt); + + fmt.mSampleRate = prm->srate; + fmt.mFormatID = kAudioFormatLinearPCM; +#if TARGET_OS_IPHONE + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) + | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; +#else + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) + | kAudioFormatFlagIsPacked; +#endif + fmt.mBitsPerChannel = 8 * st->sampsz; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBytesPerFrame = st->sampsz * prm->ch; + fmt.mFramesPerPacket = 1; + fmt.mBytesPerPacket = st->sampsz * 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; + + ret = AudioUnitGetProperty(st->au, + kAudioUnitProperty_SampleRate, + kAudioUnitScope_Output, + 0, + &hw_srate, + &hw_size); + if (ret) + goto out; + + debug("audiounit: player hardware sample rate is now at %f Hz\n", + hw_srate); + + 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..b66ba07 --- /dev/null +++ b/modules/audiounit/recorder.c @@ -0,0 +1,263 @@ +/** + * @file audiounit/recorder.c AudioUnit input recorder + * + * Copyright (C) 2010 Creytiv.com + */ +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#include <TargetConditionals.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "audiounit.h" + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + struct audiosess_st *sess; + AudioUnit au; + pthread_mutex_t mutex; + int ch; + ausrc_read_h *rh; + void *arg; + uint32_t sampsz; +}; + + +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); + + 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 * st->sampsz; + + ret = AudioUnitRender(st->au, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + &abl); + if (ret) { + debug("audiounit: record: AudioUnitRender error (%d)\n", ret); + return ret; + } + + rh(abl.mBuffers[0].mData, + abl.mBuffers[0].mDataByteSize/st->sampsz, 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); +} + + +static uint32_t aufmt_to_formatflags(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger; + case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat; + default: return 0; + } +} + + +int audiounit_recorder_alloc(struct ausrc_st **stp, const 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; +#if ! TARGET_OS_IPHONE + UInt32 ausize = sizeof(AudioDeviceID); + AudioDeviceID inputDevice; + AudioObjectPropertyAddress auAddress = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; +#endif + Float64 hw_srate = 0.0; + UInt32 hw_size = sizeof(hw_srate); + OSStatus ret = 0; + int err; + + (void)ctx; + (void)device; + (void)errh; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = 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; + +#if ! TARGET_OS_IPHONE + enable = 0; + ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, 0, + &enable, sizeof(enable)); + if (ret) + goto out; + + ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &auAddress, + 0, + NULL, + &ausize, + &inputDevice); + if (ret) + goto out; + + ret = AudioUnitSetProperty(st->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &inputDevice, + sizeof(inputDevice)); + if (ret) + goto out; +#endif + + st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt); + + fmt.mSampleRate = prm->srate; + fmt.mFormatID = kAudioFormatLinearPCM; +#if TARGET_OS_IPHONE + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) + | kAudioFormatFlagsNativeEndian + | kAudioFormatFlagIsPacked; +#else + fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt) + | kLinearPCMFormatFlagIsPacked; +#endif + fmt.mBitsPerChannel = 8 * st->sampsz; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBytesPerFrame = st->sampsz * prm->ch; + fmt.mFramesPerPacket = 1; + fmt.mBytesPerPacket = st->sampsz * 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; + + ret = AudioUnitGetProperty(st->au, + kAudioUnitProperty_SampleRate, + kAudioUnitScope_Input, + 0, + &hw_srate, + &hw_size); + if (ret) + goto out; + + debug("audiounit: record hardware sample rate is now at %f Hz\n", + hw_srate); + + 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/aufile/aufile.c b/modules/aufile/aufile.c new file mode 100644 index 0000000..307aee1 --- /dev/null +++ b/modules/aufile/aufile.c @@ -0,0 +1,265 @@ +/** + * @file aufile.c WAV Audio Source + * + * Copyright (C) 2015 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#define _BSD_SOURCE 1 +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup aufile aufile + * + * Audio module for using a WAV-file as audio input + */ + + +struct ausrc_st { + const struct ausrc *as; /* base class */ + struct tmr tmr; + struct aufile *aufile; + struct aubuf *aubuf; + uint32_t ptime; + size_t sampc; + bool run; + pthread_t thread; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; +}; + + +static struct ausrc *ausrc; + + +static void destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + tmr_cancel(&st->tmr); + + mem_deref(st->aufile); + mem_deref(st->aubuf); +} + + +static void *play_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + int16_t *sampv; + + sampv = mem_alloc(st->sampc * 2, NULL); + if (!sampv) + return NULL; + + while (st->run) { + + sys_msleep(4); + + now = tmr_jiffies(); + + if (ts > now) + continue; + + aubuf_read_samp(st->aubuf, sampv, st->sampc); + + st->rh(sampv, st->sampc, st->arg); + + ts += st->ptime; + } + + mem_deref(sampv); + + info("aufile: player thread exited\n"); + + return NULL; +} + + +static void timeout(void *arg) +{ + struct ausrc_st *st = arg; + + tmr_start(&st->tmr, 1000, timeout, st); + + /* check if audio buffer is empty */ + if (aubuf_cur_size(st->aubuf) < (2 * st->sampc)) { + + info("aufile: end of file\n"); + + /* error handler must be called from re_main thread */ + if (st->errh) + st->errh(0, "end of file", st->arg); + } +} + + +static int read_file(struct ausrc_st *st) +{ + struct mbuf *mb; + int err; + + for (;;) { + uint16_t *sampv; + size_t i; + + mb = mbuf_alloc(4096); + if (!mb) + return ENOMEM; + + mb->end = mb->size; + + err = aufile_read(st->aufile, mb->buf, &mb->end); + if (err) + break; + + if (mb->end == 0) { + info("aufile: end of file\n"); + break; + } + + /* convert from Little-Endian to Native-Endian */ + sampv = (void *)mb->buf; + for (i=0; i<mb->end/2; i++) { + sampv[i] = sys_ltohs(sampv[i]); + } + + aubuf_append(st->aubuf, mb); + + mb = mem_deref(mb); + } + + info("aufile: loaded %zu bytes\n", aubuf_cur_size(st->aubuf)); + + mem_deref(mb); + return err; +} + + +static int alloc_handler(struct ausrc_st **stp, const 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; + struct aufile_prm fprm; + int err; + (void)ctx; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("aufile: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + info("aufile: loading input file '%s'\n", dev); + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = aufile_open(&st->aufile, &fprm, dev, AUFILE_READ); + if (err) { + warning("aufile: failed to open file '%s' (%m)\n", dev, err); + goto out; + } + + info("aufile: %s: %u Hz, %d channels\n", + dev, fprm.srate, fprm.channels); + + if (fprm.srate != prm->srate) { + warning("aufile: input file (%s) must have sample-rate" + " %u Hz\n", dev, prm->srate); + err = ENODEV; + goto out; + } + if (fprm.channels != prm->ch) { + warning("aufile: input file (%s) must have channels = %d\n", + dev, prm->ch); + err = ENODEV; + goto out; + } + if (fprm.fmt != AUFMT_S16LE) { + warning("aufile: input file must have format S16LE\n"); + err = ENODEV; + goto out; + } + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->ptime = prm->ptime; + + info("aufile: audio ptime=%u sampc=%zu aubuf=[%u:%u]\n", + st->ptime, st->sampc, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + + /* 1 - inf seconds of audio */ + err = aubuf_alloc(&st->aubuf, + prm->srate * prm->ch * 2, + 0); + if (err) + goto out; + + err = read_file(st); + if (err) + goto out; + + tmr_start(&st->tmr, 1000, timeout, 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; +} + + +static int module_init(void) +{ + return ausrc_register(&ausrc, baresip_ausrcl(), + "aufile", alloc_handler); +} + + +static int module_close(void) +{ + ausrc = mem_deref(ausrc); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(aufile) = { + "aufile", + "ausrc", + module_init, + module_close +}; diff --git a/modules/aufile/module.mk b/modules/aufile/module.mk new file mode 100644 index 0000000..49ebdc3 --- /dev/null +++ b/modules/aufile/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := aufile +$(MOD)_SRCS += aufile.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/auloop/auloop.c b/modules/auloop/auloop.c new file mode 100644 index 0000000..8324a88 --- /dev/null +++ b/modules/auloop/auloop.c @@ -0,0 +1,413 @@ +/** + * @file auloop.c Audio loop + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup auloop auloop + * + * Application module for testing audio drivers + * + * The audio loop will connect the AUSRC device to the AUPLAY device + * so that a local loopback audio can be heard. Different audio parameters + * can be tested, such as sampling rate and number of channels. + * + * The following commands are available: + \verbatim + /auloop Start audio-loop + /auloop_stop Stop audio-loop + \endverbatim + */ + + +/* 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; + enum aufmt fmt; + + uint32_t n_read; + uint32_t n_write; +}; + +static const struct { + uint32_t srate; + uint32_t ch; +} configv[] = { + { 8000, 1}, + {16000, 1}, + {32000, 1}, + {44100, 1}, + {48000, 1}, + { 8000, 2}, + {16000, 2}, + {32000, 2}, + {44100, 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(stdout, "\r%uHz %dch %s " + " n_read=%u n_write=%u rw_ratio=%.2f", + al->srate, al->ch, aufmt_name(al->fmt), + al->n_read, al->n_write, rw_ratio); + + if (str_isset(aucodec)) + (void)re_fprintf(stdout, " codec='%s'", aucodec); + + fflush(stdout); +} + + +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; + + if (al->ac->dech) { + err = al->ac->dech(al->dec, sampv, &sampc, x, xlen); + if (err) + goto out; + } + else { + info("auloop: no decode handler\n"); + } + + out: + + return err; +} + + +static void read_handler(const void *sampv, size_t sampc, void *arg) +{ + struct audio_loop *al = arg; + size_t num_bytes = sampc * aufmt_sample_size(al->fmt); + int err; + + ++al->n_read; + + err = aubuf_write(al->ab, sampv, num_bytes); + if (err) { + warning("auloop: aubuf_write: %m\n", err); + } +} + + +static void write_handler(void *sampv, size_t sampc, void *arg) +{ + struct audio_loop *al = arg; + size_t num_bytes = sampc * aufmt_sample_size(al->fmt); + int err; + + ++al->n_write; + + /* read from beginning */ + if (al->ac) { + err = codec_read(al, sampv, sampc); + if (err) { + warning("auloop: codec_read error " + "on %zu samples (%m)\n", sampc, err); + } + } + else { + aubuf_read(al->ab, sampv, num_bytes); + } +} + + +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(baresip_aucodecl(), 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; + + if (cfg->audio.src_fmt != cfg->audio.play_fmt) { + warning("auloop: ausrc_format and auplay_format" + " must be the same\n"); + return EINVAL; + } + + al->fmt = cfg->audio.src_fmt; + + /* Optional audio codec */ + if (str_isset(aucodec)) { + if (cfg->audio.src_fmt != AUFMT_S16LE) { + warning("auloop: only s16 supported with codec\n"); + return EINVAL; + } + + 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.srate = al->srate; + auplay_prm.ch = al->ch; + auplay_prm.ptime = PTIME; + auplay_prm.fmt = al->fmt; + err = auplay_alloc(&al->auplay, baresip_auplayl(), + 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.srate = al->srate; + ausrc_prm.ch = al->ch; + ausrc_prm.ptime = PTIME; + ausrc_prm.fmt = al->fmt; + err = ausrc_alloc(&al->ausrc, baresip_ausrcl(), + 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[] = { + {"auloop", 0, 0, "Start audio-loop", auloop_start }, + {"auloop_stop", 0, 0, "Stop audio-loop", auloop_stop }, +}; + + +static int module_init(void) +{ + conf_get_str(conf_cur(), "auloop_codec", aucodec, sizeof(aucodec)); + + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + auloop_stop(NULL, NULL); + cmd_unregister(baresip_commands(), 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/av1/av1.c b/modules/av1/av1.c new file mode 100644 index 0000000..e0590e4 --- /dev/null +++ b/modules/av1/av1.c @@ -0,0 +1,51 @@ +/** + * @file av1.c AV1 Video Codec + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "av1.h" + + +/** + * @defgroup av1 av1 + * + * The AV1 video codec (Experimental) + * + * Reference: http://aomedia.org/ + */ + + +static struct vidcodec av1 = { + .name = "AV1", + .encupdh = av1_encode_update, + .ench = av1_encode, + .decupdh = av1_decode_update, + .dech = av1_decode, +}; + + +static int module_init(void) +{ + vidcodec_register(baresip_vidcodecl(), &av1); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&av1); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(av1) = { + "av1", + "codec", + module_init, + module_close +}; diff --git a/modules/av1/av1.h b/modules/av1/av1.h new file mode 100644 index 0000000..7d5a32d --- /dev/null +++ b/modules/av1/av1.h @@ -0,0 +1,20 @@ +/** + * @file av1.h Private AV1 Interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + + +/* Encode */ +int av1_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int av1_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + + +/* Decode */ +int av1_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int av1_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb); diff --git a/modules/av1/decode.c b/modules/av1/decode.c new file mode 100644 index 0000000..21ccc75 --- /dev/null +++ b/modules/av1/decode.c @@ -0,0 +1,293 @@ +/** + * @file av1/decode.c AV1 Decode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <aom/aom.h> +#include <aom/aom_decoder.h> +#include <aom/aomdx.h> +#include "av1.h" + + +enum { + DECODE_MAXSZ = 524288, +}; + + +/* XXX: re-using VP9 header format for now.. */ +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 { + aom_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) + aom_codec_destroy(&vds->ctx); + + mem_deref(vds->mb); +} + + +int av1_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + aom_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 = aom_codec_dec_init(&vds->ctx, &aom_codec_av1_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; +} + + +/* XXX: check keyframe flag */ +static inline bool is_keyframe(struct mbuf *mb) +{ + if (mbuf_get_left(mb) < 1) + return false; + + if (mb->buf[mb->pos] & 0x01) + return false; + + return true; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +int av1_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb) +{ + aom_codec_iter_t iter = NULL; + aom_codec_err_t res; + aom_image_t *img; + struct hdr hdr; + int err, i; + + if (!vds || !frame || !intra || !mb) + return EINVAL; + + *intra = false; + + err = hdr_decode(&hdr, mb); + if (err) + return err; + +#if 1 + debug("av1: 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) { + + if (is_keyframe(mb)) + *intra = true; + + 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("av1: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + res = aom_codec_decode(&vds->ctx, vds->mb->buf, + (unsigned int)vds->mb->end, NULL, 1); + if (res) { + debug("av1: decode error: %s\n", aom_codec_err_to_string(res)); + err = EPROTO; + goto out; + } + + img = aom_codec_get_frame(&vds->ctx, &iter); + if (!img) { + debug("av1: no picture\n"); + goto out; + } + + if (img->fmt != AOM_IMG_FMT_I420) { + warning("av1: 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/av1/encode.c b/modules/av1/encode.c new file mode 100644 index 0000000..9e24bcc --- /dev/null +++ b/modules/av1/encode.c @@ -0,0 +1,259 @@ +/** + * @file av1/encode.c AV1 Encode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <aom/aom.h> +#include <aom/aom_encoder.h> +#include <aom/aomcx.h> +#include "av1.h" + + +enum { + HDR_SIZE = 4, +}; + + +struct videnc_state { + aom_codec_ctx_t ctx; + struct vidsz size; + aom_codec_pts_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + bool ctxup; + uint16_t picid; + videnc_packet_h *pkth; + void *arg; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + + if (ves->ctxup) + aom_codec_destroy(&ves->ctx); +} + + +int av1_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *ves; + + 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)) { + + aom_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + ves->pkth = pkth; + ves->arg = arg; + + return 0; +} + + +static int open_encoder(struct videnc_state *ves, const struct vidsz *size) +{ + aom_codec_enc_cfg_t cfg; + aom_codec_err_t res; + + res = aom_codec_enc_config_default(&aom_codec_av1_cx_algo, &cfg, 0); + if (res) + return EPROTO; + + 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 = AOM_ERROR_RESILIENT_DEFAULT; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.rc_end_usage = AOM_VBR; + cfg.rc_target_bitrate = ves->bitrate; + cfg.kf_mode = AOM_KF_AUTO; + + if (ves->ctxup) { + debug("av1: re-opening encoder\n"); + aom_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + + res = aom_codec_enc_init(&ves->ctx, &aom_codec_av1_cx_algo, &cfg, + 0); + if (res) { + warning("av1: enc init: %s\n", aom_codec_err_to_string(res)); + return EPROTO; + } + + ves->ctxup = true; + + res = aom_codec_control(&ves->ctx, AOME_SET_CPUUSED, 8); + if (res) { + warning("av1: codec ctrl C: %s\n", + aom_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, uint32_t rtp_ts, + 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, rtp_ts, hdr, sizeof(hdr), buf, maxlen, arg); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(marker, rtp_ts, hdr, sizeof(hdr), buf, len, arg); + + return err; +} + + +int av1_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + aom_enc_frame_flags_t flags = 0; + aom_codec_iter_t iter = NULL; + aom_codec_err_t res; + aom_image_t *img; + aom_img_fmt_t img_fmt; + int err = 0, i; + + if (!ves || !frame || 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("av1: picture update\n"); */ + flags |= AOM_EFLAG_FORCE_KF; + } + + img_fmt = AOM_IMG_FMT_I420; + + img = aom_img_wrap(NULL, img_fmt, frame->size.w, frame->size.h, + 16, NULL); + if (!img) { + warning("vp9: encoder: could not allocate image\n"); + err = ENOMEM; + goto out; + } + + for (i=0; i<4; i++) { + img->stride[i] = frame->linesize[i]; + img->planes[i] = frame->data[i]; + } + + res = aom_codec_encode(&ves->ctx, img, ves->pts++, 1, + flags, AOM_DL_REALTIME); + if (res) { + warning("av1: enc error: %s\n", aom_codec_err_to_string(res)); + return ENOMEM; + } + + ++ves->picid; + + for (;;) { + bool keyframe = false, marker = true; + const aom_codec_cx_pkt_t *pkt; + uint8_t partid = 0; + uint32_t ts; + + pkt = aom_codec_get_cx_data(&ves->ctx, &iter); + if (!pkt) + break; + + if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) + continue; + + if (pkt->data.frame.flags & AOM_FRAME_IS_KEY) + keyframe = true; + + if (pkt->data.frame.flags & AOM_FRAME_IS_FRAGMENT) + marker = false; + + if (pkt->data.frame.partition_id >= 0) + partid = pkt->data.frame.partition_id; + + ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps); + + err = packetize(marker, ts, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, !keyframe, partid, ves->picid, + ves->pkth, ves->arg); + if (err) + return err; + } + + out: + if (img) + aom_img_free(img); + + return err; +} diff --git a/modules/av1/module.mk b/modules/av1/module.mk new file mode 100644 index 0000000..146583f --- /dev/null +++ b/modules/av1/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2016 Creytiv.com +# + +MOD := av1 +$(MOD)_SRCS += av1.c +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_LFLAGS += -laom + +include mk/mod.mk diff --git a/modules/avahi/avahi.c b/modules/avahi/avahi.c new file mode 100644 index 0000000..035d51a --- /dev/null +++ b/modules/avahi/avahi.c @@ -0,0 +1,389 @@ +/** + * @file avahi.c Avahi Zeroconf Module + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2017 Jonathan Sieber + */ + +/** + * @defgroup avahi avahi + * + * This module implements DNS Service Discovery via Avahi Client API + * It does 2 things: + * 1) Announce _sipuri._udp resource for the main UA (under the local IP) + * 2) Fills contact list with discovered hosts + * + * NOTE: This module is experimental. + * + */ + +#include <re.h> +#include <baresip.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <avahi-common/simple-watch.h> +#include <avahi-client/lookup.h> +#include <avahi-client/publish.h> +#include <avahi-common/error.h> + +/* for if_nametoindex */ +#include <net/if.h> + +/* gethostname, getaddrinfo */ +#define __USE_XOPEN2K +#include <unistd.h> +#include <netdb.h> + + +struct avahi_st { + AvahiSimplePoll* poll; + AvahiClient* client; + AvahiEntryGroup* group; + AvahiServiceBrowser* browser; + struct ua* local_ua; + struct tmr poll_timer; +}; + +static struct avahi_st* avahi = NULL; + + +static void group_callback(AvahiEntryGroup* group, + AvahiEntryGroupState state, void* userdata) +{ + (void)group; + (void)userdata; + + switch (state) { + + case AVAHI_ENTRY_GROUP_ESTABLISHED: + info ("avahi: Service Registration completed\n"); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + case AVAHI_ENTRY_GROUP_COLLISION: + warning("avahi: Service Registration failed\n"); + /* TODO: Think of smart way to handle collision? */ + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + /* Do nothing */ + break; + } +} + + +static void create_services(AvahiClient *client) +{ + int err; + char buf[128] = ""; + char hostname[128] = ""; + + int if_idx = AVAHI_IF_UNSPEC; + int af = AVAHI_PROTO_INET; + + struct sa laddr; + + /* Build announced sipuri as username@hostname */ + strncpy(hostname, avahi_client_get_host_name_fqdn(client), + sizeof (hostname)); + re_snprintf(buf, sizeof(buf), "<sip:%s@%s>;regint=0", + sys_username(), + hostname); + + info("avahi: Creating local UA %s\n", buf); + err = ua_alloc(&avahi->local_ua, buf); + + if (err) { + warning("avahi: Could not create UA %s: %m\n", buf, err); + return; + } + + re_snprintf(buf, sizeof(buf), "sip:%s@%s", + sys_username(), + hostname); + + debug("avahi: Announcing URI: %s\n", buf); + + /* Get interface number of baresip interface */ + if (str_isset(conf_config()->net.ifname)) { + if_idx = if_nametoindex(conf_config()->net.ifname); + } + + if (net_af(baresip_network()) == AF_INET6) { + af = AVAHI_PROTO_INET6; + } + + err |= sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, 0); + if (err) { + warning("avahi: Can not find local SIP address\n"); + } + + /* TODO: Query enabled transports and register these */ + avahi->group = avahi_entry_group_new(client, group_callback, NULL); + err = avahi_entry_group_add_service(avahi->group, + if_idx, af, 0, + buf, "_sipuri._udp", + NULL, NULL, + ntohs(laddr.u.in.sin_port), NULL); + err |= avahi_entry_group_commit(avahi->group); + + if (err) { + warning("avahi: Error in registering service"); + } +} + + +static void client_callback(AvahiClient *c, AvahiClientState state, + AVAHI_GCC_UNUSED void * userdata) +{ + (void)c; + + switch (state) { + + case AVAHI_CLIENT_S_RUNNING: + info("avahi: Avahi Daemon running\n", state); + break; + default: + warning("avahi: unknown client_callback: %d\n", state); + break; + } +} + + +static void add_contact(const char* uri, + const AvahiAddress *address, uint16_t port) +{ + int err; + struct pl addr; + char buf[128]; + struct contact *c; + struct sa sa; + struct sip_addr sipaddr; + + /* Parse SIPURI to get username and stuff... */ + pl_set_str(&addr, uri); + if (sip_addr_decode(&sipaddr, &addr)) { + warning("avahi: could not decode sipuri %s\n", uri); + return; + } + + if (address->proto == AVAHI_PROTO_INET6) {; + sa_set_in6(&sa, address->data.ipv6.address, port); + } + else { + sa_set_in(&sa, htonl(address->data.ipv4.address), port); + } + + re_snprintf(buf, sizeof(buf), + "\"%r@%r\" <sip:%r@%J>;presence=p2p", + &sipaddr.uri.user, &sipaddr.uri.host, + &sipaddr.uri.user, &sa); + pl_set_str(&addr, buf); + + err = contact_add(baresip_contacts(), &c, &addr); + if (err) { + warning("Could not add contact %s: %m\n", buf, err); + } +} + + +static void remove_contact_by_dname(const char* dname) +{ + const struct list *lst; + struct le *le; + + /* remove sip: scheme for comparison */ + if (0 != re_regex(dname, str_len(dname), "^sip:")) { + dname += 4; + } + + lst = contact_list(baresip_contacts()); + + for (le = list_head(lst); le; le = le->next) { + struct contact *c = le->data; + const struct sip_addr* addr = contact_addr(c); + + if (pl_strcmp(&addr->dname, dname) == 0) { + contact_remove(baresip_contacts(), c); + return; + } + } + + warning("avahi: Could not remove contact %s\n", dname); +} + + +static void resolve_callback( + AvahiServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *hostname, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata) +{ + (void)r; + (void)interface; + (void)txt; + (void)userdata; + + info("avahi: resolve %s %s %s %s\n", name, type, domain, hostname); + + if (event == AVAHI_RESOLVER_FOUND) { + if (protocol != address->proto) { + warning("avahi: Resolved address type ambiguous\n"); + } + + /* TODO: Process TXT field */ + if (!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN)) { + add_contact(name, address, port); + } + } + else { + warning("avahi: Resolver Error on %s: %s\n", name, + avahi_strerror(avahi_client_errno(avahi->client))); + } + + avahi_service_resolver_free(r); +} + + +static void browse_callback( + AvahiServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + void* userdata) +{ + int proto = AVAHI_PROTO_INET; + (void)b; + (void)userdata; + + switch (event) { + case AVAHI_BROWSER_NEW: + debug("avahi: browse_callback if=%d proto=%d %s\n", + interface, protocol, name); + if (net_af(baresip_network()) == AF_INET6) { + proto = AVAHI_PROTO_INET6; + } + + if (!(avahi_service_resolver_new(avahi->client, + interface, protocol, + name, type, domain, + proto, 0, resolve_callback, + avahi->client))) { + warning("avahi: Error resolving %s\n", name); + } + break; + + case AVAHI_BROWSER_REMOVE: + remove_contact_by_dname(name); + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + debug("avahi: (Browser) %s\n", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? + "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + default: + warning("avahi: browse_callback %d %s\n", event, name); + break; + } +} + + +static void avahi_update(void* arg) +{ + (void)arg; + + avahi_simple_poll_iterate(avahi->poll, 0); + tmr_start(&avahi->poll_timer, 250, avahi_update, 0); +} + + +static void destructor(void* arg) +{ + struct avahi_st* a = arg; + + tmr_cancel(&a->poll_timer); + + mem_deref(a->local_ua); + + /* Calling these destructor commands would be correct, but they + * spew out a lot of ugly D-Bus warning */ + if (a->browser) { + avahi_service_browser_free(avahi->browser); + } + + if (a->group) { + avahi_entry_group_free(avahi->group); + } + + if (a->client) { + avahi_client_free(avahi->client); + } +} + + +static int module_init(void) +{ + int err; + avahi = mem_zalloc(sizeof(struct avahi_st), destructor); + if (!avahi) { + return ENOMEM; + } + + avahi->poll = avahi_simple_poll_new(); + avahi->client = avahi_client_new( + avahi_simple_poll_get(avahi->poll), + 0, client_callback, NULL, &err); + + /* Check wether creating the client object succeeded */ + if (!avahi->client) { + warning("Failed to create client: %s\n", avahi_strerror(err)); + return err; + } + + avahi->browser = avahi_service_browser_new(avahi->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_sipuri._udp", NULL, + 0, browse_callback, 0); + + tmr_init(&avahi->poll_timer); + avahi_update(0); + + /* Register services when UA is ready */ + if (!avahi->group) { + create_services(avahi->client); + } + + return 0; +} + + +static int module_close(void) +{ + debug("avahi: module_close\n"); + + avahi = mem_deref(avahi); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(avahi) = { + "avahi", + "application", + module_init, + module_close +}; diff --git a/modules/avahi/module.mk b/modules/avahi/module.mk new file mode 100644 index 0000000..0d796e1 --- /dev/null +++ b/modules/avahi/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := avahi +$(MOD)_SRCS += avahi.c +$(MOD)_LFLAGS += $(shell pkg-config --libs avahi-client) +$(MOD)_CFLAGS += $(shell pkg-config --cflags avahi-client) + +include mk/mod.mk diff --git a/modules/avcapture/avcapture.m b/modules/avcapture/avcapture.m new file mode 100644 index 0000000..f276c96 --- /dev/null +++ b/modules/avcapture/avcapture.m @@ -0,0 +1,398 @@ +/** + * @file avcapture.m AVFoundation video capture + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <AVFoundation/AVFoundation.h> + + +/** + * @defgroup avcapture avcapture + * + * Video source using OSX/iOS AVFoundation + */ + + +static struct vidsrc *vidsrc; + + +@interface avcap : NSObject < AVCaptureVideoDataOutputSampleBufferDelegate > +{ + AVCaptureSession *sess; + AVCaptureDeviceInput *input; + AVCaptureVideoDataOutput *output; + struct vidsrc_st *vsrc; +} +- (void)setCamera:(const char *)name; +@end + + +struct vidsrc_st { + const 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: + warning("avcapture: unknown 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]; +} + + +static int alloc(struct vidsrc_st **stp, const 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 = 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"); + int err = 0; + if (!cls) + return ENOSYS; + + pool = [NSAutoreleasePool new]; + + /* populate devices */ + for (dev in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + + const char *name = [[dev localizedName] UTF8String]; + + debug("avcapture: found video device '%s'\n", name); + } + + err = vidsrc_register(&vidsrc, baresip_vidsrcl(), + "avcapture", alloc, update); + + [pool drain]; + + return err; +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + + 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..bfcde82 --- /dev/null +++ b/modules/avcodec/avcodec.c @@ -0,0 +1,258 @@ +/** + * @file avcodec.c Video codecs using libavcodec + * + * Copyright (C) 2010 - 2016 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" + + +/** + * @defgroup avcodec avcodec + * + * Video codecs using libavcodec + * + * This module implements H.263, H.264 and MPEG4 video codecs + * using libavcodec from FFmpeg or libav projects, and libx264. + * + * Build options: + * + \verbatim + $ make USE_X264=1 ; enable direct usage of libx264 + $ make USE_X264= ; use H.264 encoder from libavcodec + \endverbatim + * + * Config options: + * + \verbatim + avcodec_h264enc <NAME> ; e.g. h264_nvenc, h264_videotoolbox + avcodec_h264dec <NAME> ; e.g. h264_cuvid, h264_vda, h264_qsv + \endverbatim + * + * References: + * + * http://ffmpeg.org + * + * https://libav.org + * + * RTP Payload Format for H.264 Video + * https://tools.ietf.org/html/rfc6184 + */ + + +const uint8_t h264_level_idc = 0x1f; +AVCodec *avcodec_h264enc; /* optional; specified H.264 encoder */ +AVCodec *avcodec_h264dec; /* optional; specified H.264 decoder */ + + +int avcodec_resolve_codecid(const char *s) +{ + if (0 == str_casecmp(s, "H263")) + return AV_CODEC_ID_H263; + else if (0 == str_casecmp(s, "H264")) + return AV_CODEC_ID_H264; + else if (0 == str_casecmp(s, "MP4V-ES")) + return AV_CODEC_ID_MPEG4; + else + return AV_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 = { + LE_INIT, + NULL, + "H264", + "packetization-mode=0", + NULL, + encode_update, +#ifdef USE_X264 + encode_x264, +#else + encode, +#endif + decode_update, + decode_h264, + h264_fmtp_enc, + h264_fmtp_cmp, +}; + +static struct vidcodec h263 = { + LE_INIT, + "34", + "H263", + NULL, + NULL, + encode_update, + encode, + decode_update, + decode_h263, + h263_fmtp_enc, + NULL, +}; + +static struct vidcodec mpg4 = { + LE_INIT, + NULL, + "MP4V-ES", + NULL, + NULL, + encode_update, + encode, + decode_update, + decode_mpeg4, + mpg4_fmtp_enc, + NULL, +}; + + +static int module_init(void) +{ + struct list *vidcodecl = baresip_vidcodecl(); + char h264enc[64]; + char h264dec[64]; + +#ifdef USE_X264 + debug("avcodec: x264 build %d\n", X264_BUILD); +#else + debug("avcodec: using libavcodec H.264 encoder\n"); +#endif + +#if LIBAVCODEC_VERSION_INT < ((53<<16)+(10<<8)+0) + avcodec_init(); +#endif + + avcodec_register_all(); + + if (0 == conf_get_str(conf_cur(), "avcodec_h264dec", + h264dec, sizeof(h264dec))) { + + info("avcodec: using h264 decoder by name (%s)\n", h264dec); + + avcodec_h264dec = avcodec_find_decoder_by_name(h264dec); + if (!avcodec_h264dec) { + warning("avcodec: h264 decoder not found (%s)\n", + h264dec); + return ENOENT; + } + vidcodec_register(vidcodecl, &h264); + } + else { + if (avcodec_find_decoder(AV_CODEC_ID_H264)) + vidcodec_register(vidcodecl, &h264); + } + + if (avcodec_find_decoder(AV_CODEC_ID_H263)) + vidcodec_register(vidcodecl, &h263); + + if (avcodec_find_decoder(AV_CODEC_ID_MPEG4)) + vidcodec_register(vidcodecl, &mpg4); + + if (0 == conf_get_str(conf_cur(), "avcodec_h264enc", + h264enc, sizeof(h264enc))) { + + info("avcodec: using h264 encoder by name (%s)\n", h264enc); + + avcodec_h264enc = avcodec_find_encoder_by_name(h264enc); + if (!avcodec_h264enc) { + warning("avcodec: h264 encoder not found (%s)\n", + h264enc); + return ENOENT; + } + } + + 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..f3a2b70 --- /dev/null +++ b/modules/avcodec/avcodec.h @@ -0,0 +1,60 @@ +/** + * @file avcodec.h Video codecs using libavcodec -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +#if LIBAVCODEC_VERSION_INT < ((54<<16)+(25<<8)+0) +#define AVCodecID CodecID + +#define AV_CODEC_ID_NONE CODEC_ID_NONE +#define AV_CODEC_ID_H263 CODEC_ID_H263 +#define AV_CODEC_ID_H264 CODEC_ID_H264 +#define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 + +#endif + + +extern const uint8_t h264_level_idc; +extern AVCodec *avcodec_h264enc; +extern AVCodec *avcodec_h264dec; + + +/* + * Encode + */ + +struct videnc_state; + +int encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int encode(struct videnc_state *st, bool update, const struct vidframe *frame); +#ifdef USE_X264 +int encode_x264(struct videnc_state *st, bool update, + const struct vidframe *frame); +#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 *intra, bool eof, uint16_t seq, struct mbuf *src); +int decode_h264(struct viddec_state *st, struct vidframe *frame, + bool *intra, bool eof, uint16_t seq, struct mbuf *src); +int decode_mpeg4(struct viddec_state *st, struct vidframe *frame, + bool *intra, bool eof, uint16_t seq, struct mbuf *src); + + +int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name, + const struct pl *val); + + +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..77e6e77 --- /dev/null +++ b/modules/avcodec/decode.c @@ -0,0 +1,556 @@ +/** + * @file avcodec/decode.c Video codecs using libavcodec -- decoder + * + * Copyright (C) 2010 - 2013 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#include <libavutil/avutil.h> +#include <libavutil/mem.h> +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) +#include <libavutil/pixdesc.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P +#define AV_PIX_FMT_NV12 PIX_FMT_NV12 +#endif + + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct viddec_state { + AVCodec *codec; + AVCodecContext *ctx; + AVFrame *pict; + struct mbuf *mb; + bool got_keyframe; + size_t frag_start; + bool frag; + uint16_t frag_seq; + + struct { + unsigned n_key; + unsigned n_lost; + } stats; +}; + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +static inline void fragment_rewind(struct viddec_state *vds) +{ + vds->mb->pos = vds->frag_start; + vds->mb->end = vds->frag_start; +} + + +static void destructor(void *arg) +{ + struct viddec_state *st = arg; + + debug("avcodec: decoder stats" + " (keyframes:%u, lost_fragments:%u)\n", + st->stats.n_key, st->stats.n_lost); + + 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 AVCodecID codec_id; + + codec_id = avcodec_resolve_codecid(name); + if (codec_id == AV_CODEC_ID_NONE) + return EINVAL; + + /* + * Special handling of H.264 decoder + */ + if (codec_id == AV_CODEC_ID_H264 && avcodec_h264dec) { + st->codec = avcodec_h264dec; + info("avcodec: h264 decoder activated\n"); + } + else { + 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; +} + + +static int ffdecode(struct viddec_state *st, struct vidframe *frame) +{ + int i, got_picture, ret; + int err = 0; + + st->mb->pos = 0; + + if (!st->got_keyframe) { + debug("avcodec: waiting for key frame ..\n"); + return 0; + } + +#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100) + + do { + AVPacket avpkt; + + av_init_packet(&avpkt); + avpkt.data = st->mb->buf; + avpkt.size = (int)st->mb->end; + + ret = avcodec_send_packet(st->ctx, &avpkt); + if (ret < 0) { + warning("avcodec_send_packet error ret=%d\n", ret); + err = EBADMSG; + goto out; + } + + ret = avcodec_receive_frame(st->ctx, st->pict); + if (ret == AVERROR(EAGAIN)) { + goto out; + } + else if (ret < 0) { + warning("avcodec_receive_frame error ret=%d\n", ret); + err = EBADMSG; + goto out; + } + + got_picture = true; + + } while (0); + +#elif LIBAVCODEC_VERSION_INT <= ((52<<16)+(23<<8)+0) + ret = avcodec_decode_video(st->ctx, st->pict, &got_picture, + st->mb->buf, + (int)st->mb->end); +#else + do { + AVPacket avpkt; + + av_init_packet(&avpkt); + avpkt.data = st->mb->buf; + avpkt.size = (int)st->mb->end; + + ret = avcodec_decode_video2(st->ctx, st->pict, + &got_picture, &avpkt); + } while (0); +#endif + + if (ret < 0) { + err = EBADMSG; + goto out; + } + + if (got_picture) { + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) + switch (st->pict->format) { + + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: + frame->fmt = VID_FMT_YUV420P; + break; + + case AV_PIX_FMT_NV12: + frame->fmt = VID_FMT_NV12; + break; + + default: + warning("avcodec: decode: bad pixel format" + " (%i) (%s)\n", + st->pict->format, + av_get_pix_fmt_name(st->pict->format)); + goto out; + } +#else + frame->fmt = VID_FMT_YUV420P; +#endif + + 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; + } + + out: + return err; +} + + +int decode_h264(struct viddec_state *st, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *src) +{ + struct h264_hdr h264_hdr; + const uint8_t nal_seq[3] = {0, 0, 1}; + int err; + + *intra = false; + + err = h264_hdr_decode(&h264_hdr, src); + if (err) + return err; + +#if 0 + re_printf("avcodec: decode: %s %s type=%2d \n", + marker ? "[M]" : " ", + h264_is_keyframe(h264_hdr.type) ? "<KEY>" : " ", + h264_hdr.type); +#endif + + if (h264_hdr.f) { + info("avcodec: H264 forbidden bit set!\n"); + return EBADMSG; + } + + if (st->frag && h264_hdr.type != H264_NAL_FU_A) { + debug("avcodec: lost fragments; discarding previous NAL\n"); + fragment_rewind(st); + st->frag = false; + ++st->stats.n_lost; + } + + /* handle NAL types */ + if (1 <= h264_hdr.type && h264_hdr.type <= 23) { + + if (h264_is_keyframe(h264_hdr.type)) + *intra = true; + + --src->pos; + + /* prepend H.264 NAL start sequence */ + err = mbuf_write_mem(st->mb, nal_seq, 3); + + err |= mbuf_write_mem(st->mb, mbuf_buf(src), + mbuf_get_left(src)); + if (err) + goto out; + } + else if (H264_NAL_FU_A == h264_hdr.type) { + struct h264_fu fu; + + err = h264_fu_hdr_decode(&fu, src); + if (err) + return err; + h264_hdr.type = fu.type; + + if (fu.s) { + if (st->frag) { + debug("avcodec: lost fragments;" + " ignoring NAL\n"); + fragment_rewind(st); + ++st->stats.n_lost; + } + + st->frag_start = st->mb->pos; + st->frag = true; + + if (h264_is_keyframe(fu.type)) + *intra = true; + + /* 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 (!st->frag) { + debug("avcodec: ignoring fragment\n"); + ++st->stats.n_lost; + return 0; + } + + if (seq_diff(st->frag_seq, seq) != 1) { + debug("avcodec: lost fragments detected\n"); + fragment_rewind(st); + st->frag = false; + ++st->stats.n_lost; + return 0; + } + } + + err = mbuf_write_mem(st->mb, mbuf_buf(src), + mbuf_get_left(src)); + if (err) + goto out; + + if (fu.e) + st->frag = false; + + st->frag_seq = seq; + } + else { + warning("avcodec: unknown NAL type %u\n", h264_hdr.type); + return EBADMSG; + } + + if (*intra) { + st->got_keyframe = true; + ++st->stats.n_key; + } + + if (!marker) { + + if (st->mb->end > DECODE_MAXSZ) { + warning("avcodec: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + if (st->frag) { + err = EPROTO; + goto out; + } + + err = ffdecode(st, frame); + if (err) + goto out; + + out: + mbuf_rewind(st->mb); + st->frag = false; + + return err; +} + + +int decode_mpeg4(struct viddec_state *st, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *src) +{ + int err; + + if (!src) + return 0; + + (void)seq; + + *intra = false; /* XXX */ + + /* let the decoder handle this */ + st->got_keyframe = true; + + err = mbuf_write_mem(st->mb, mbuf_buf(src), + mbuf_get_left(src)); + if (err) + goto out; + + if (!marker) { + + if (st->mb->end > DECODE_MAXSZ) { + warning("avcodec: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + err = ffdecode(st, frame); + if (err) + goto out; + + out: + mbuf_rewind(st->mb); + + return err; +} + + +int decode_h263(struct viddec_state *st, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *src) +{ + struct h263_hdr hdr; + int err; + + if (!st || !frame || !intra) + return EINVAL; + + *intra = false; + + 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 (st->mb->pos == 0) { + *intra = 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; + } + + err = mbuf_write_mem(st->mb, mbuf_buf(src), + mbuf_get_left(src)); + if (err) + goto out; + + if (!marker) { + + if (st->mb->end > DECODE_MAXSZ) { + warning("avcodec: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + if (!hdr.i) { + ++st->stats.n_key; + } + + err = ffdecode(st, frame); + if (err) + goto out; + + out: + mbuf_rewind(st->mb); + + return err; +} diff --git a/modules/avcodec/encode.c b/modules/avcodec/encode.c new file mode 100644 index 0000000..d0d5d83 --- /dev/null +++ b/modules/avcodec/encode.c @@ -0,0 +1,805 @@ +/** + * @file avcodec/encode.c Video codecs using 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> +#if LIBAVUTIL_VERSION_INT >= ((50<<16)+(29<<8)+0) +#include <libavutil/opt.h> +#else +#include <libavcodec/opt.h> +#endif +#ifdef USE_X264 +#include <x264.h> +#endif +#include "h26x.h" +#include "avcodec.h" + + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#define AV_PIX_FMT_NV12 PIX_FMT_NV12 +#endif + + +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 AVCodecID codec_id; + videnc_packet_h *pkth; + void *arg; + + 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); +#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0) + av_opt_free(st->ctx); +#endif + 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) +{ + /* + * Special handling of H.264 encoder + */ + if (st->codec_id == AV_CODEC_ID_H264 && avcodec_h264enc) { + +#ifdef USE_X264 + warning("avcodec: h264enc specified, but using libx264\n"); + return EINVAL; +#else + st->codec = avcodec_h264enc; + + info("avcodec: h264 encoder activated\n"); + + return 0; +#endif + } + + 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 pix_fmt) +{ + int err = 0; + + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); +#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0) + av_opt_free(st->ctx); +#endif + 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; + } + + av_opt_set_defaults(st->ctx); + + 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; + st->ctx->time_base.num = 1; + st->ctx->time_base.den = prm->fps; + + /* params to avoid libavcodec/x264 default preset error */ + if (st->codec_id == AV_CODEC_ID_H264) { + st->ctx->me_range = 16; + st->ctx->qmin = 10; + st->ctx->qmax = 51; + st->ctx->max_qdiff = 4; + +#ifndef USE_X264 + if (st->codec == avcodec_find_encoder_by_name("nvenc_h264") || + st->codec == avcodec_find_encoder_by_name("h264_nvenc")) { + +#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(21<<8)+0) + err = av_opt_set(st->ctx->priv_data, + "preset", "llhp", 0); + + if (err < 0) { + debug("avcodec: h264 nvenc setting preset " + "\"llhp\" failed; error: %u\n", err); + } + else { + debug("avcodec: h264 nvenc preset " + "\"llhp\" selected\n"); + } + err = av_opt_set_int(st->ctx->priv_data, + "2pass", 1, 0); + + if (err < 0) { + debug("avcodec: h264 nvenc option " + "\"2pass\" failed; error: %u\n", err); + } + else { + debug("avcodec: h264 nvenc option " + "\"2pass\" selected\n"); + } +#endif + } +#endif + } + +#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 + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) + st->pict->format = pix_fmt; + st->pict->width = size->w; + st->pict->height = size->h; +#endif + + out: + if (err) { + if (st->ctx) { + if (st->ctx->codec) + avcodec_close(st->ctx); +#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0) + av_opt_free(st->ctx); +#endif + 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 == AV_CODEC_ID_H263) + (void)decode_sdpparam_h263(st, name, val); + else if (st->codec_id == AV_CODEC_ID_H264) + (void)decode_sdpparam_h264(st, name, val); +} + + +static int general_packetize(uint32_t rtp_ts, 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, rtp_ts, NULL, 0, mbuf_buf(mb), sz, + arg); + + mbuf_advance(mb, sz); + } + + return err; +} + + +static int h263_packetize(struct videnc_state *st, + uint32_t rtp_ts, 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, rtp_ts, 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, int csp) +{ + x264_param_t xprm; + + if (x264_param_default_preset(&xprm, "ultrafast", "zerolatency")) + return ENOSYS; + + x264_param_apply_profile(&xprm, "baseline"); + + xprm.i_level_idc = h264_level_idc; + xprm.i_width = size->w; + xprm.i_height = size->h; + xprm.i_csp = csp; + xprm.i_fps_num = prm->fps; + xprm.i_fps_den = 1; + xprm.rc.i_bitrate = prm->bitrate / 1000; /* kbit/s */ + xprm.rc.i_rc_method = X264_RC_ABR; + 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, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *st; + int err = 0; + + if (!vesp || !vc || !prm || !pkth) + return EINVAL; + + if (*vesp) + return 0; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->encprm = *prm; + st->pkth = pkth; + st->arg = arg; + + st->codec_id = avcodec_resolve_codecid(vc->name); + if (st->codec_id == AV_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 == AV_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) +{ + x264_picture_t pic_in, pic_out; + x264_nal_t *nal; + int i_nal; + int i, err, ret; + int csp, pln; + uint32_t ts; + + if (!st || !frame) + return EINVAL; + + switch (frame->fmt) { + + case VID_FMT_YUV420P: + csp = X264_CSP_I420; + pln = 3; + break; + + case VID_FMT_NV12: + csp = X264_CSP_NV12; + pln = 2; + break; + + default: + warning("avcodec: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + return ENOTSUP; + } + + if (!st->x264 || !vidsz_cmp(&st->encsize, &frame->size)) { + + err = open_encoder_x264(st, &st->encprm, &frame->size, csp); + 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 = csp; + pic_in.img.i_plane = pln; + for (i=0; i<pln; 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) { + warning("avcodec: x264 [error]: x264_encoder_encode failed\n"); + } + if (i_nal == 0) + return 0; + + ts = video_calc_rtp_timestamp(pic_out.i_pts, st->encprm.fps); + + 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, ts, + nal[i].p_payload + offset, + nal[i].i_payload - offset, + st->encprm.pktsize, + st->pkth, st->arg); + } + + return err; +} +#endif + + +int encode(struct videnc_state *st, bool update, const struct vidframe *frame) +{ + int i, err, ret; + int pix_fmt; + uint32_t ts; + + if (!st || !frame) + return EINVAL; + + switch (frame->fmt) { + + case VID_FMT_YUV420P: + pix_fmt = AV_PIX_FMT_YUV420P; + break; + + case VID_FMT_NV12: + pix_fmt = AV_PIX_FMT_NV12; + break; + + default: + warning("avcodec: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + return ENOTSUP; + } + + if (!st->ctx || !vidsz_cmp(&st->encsize, &frame->size)) { + + err = open_encoder(st, &st->encprm, &frame->size, pix_fmt); + 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 >= ((57<<16)+(37<<8)+100) + do { + AVPacket *pkt; + + ret = avcodec_send_frame(st->ctx, st->pict); + if (ret < 0) + return EBADMSG; + + pkt = av_packet_alloc(); + if (!pkt) + return ENOMEM; + + ret = avcodec_receive_packet(st->ctx, pkt); + if (ret < 0) { + av_packet_free(&pkt); + return 0; + } + + ts = video_calc_rtp_timestamp(pkt->dts, st->encprm.fps); + + err = mbuf_write_mem(st->mb, pkt->data, pkt->size); + st->mb->pos = 0; + + av_packet_free(&pkt); + + if (err) + return err; + } while (0); +#elif 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); + + ts = video_calc_rtp_timestamp(avpkt.dts, st->encprm.fps); + + } 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); + + ts = video_calc_rtp_timestamp(st->pict->pts, st->encprm.fps); +#endif + + switch (st->codec_id) { + + case AV_CODEC_ID_H263: + err = h263_packetize(st, ts, st->mb, st->pkth, st->arg); + break; + + case AV_CODEC_ID_H264: + err = h264_packetize(ts, st->mb->buf, st->mb->end, + st->encprm.pktsize, + st->pkth, st->arg); + break; + + case AV_CODEC_ID_MPEG4: + err = general_packetize(ts, st->mb, st->encprm.pktsize, + st->pkth, st->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..5ee016e --- /dev/null +++ b/modules/avcodec/h263.c @@ -0,0 +1,188 @@ +/** + * @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 + * + * @param p Input bit-stream + * @param size Number of bytes in bit-stream + * + * @return Pointer to PSC if found, otherwise NULL + */ +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 + * + * @param hdr H.263 header to be written to + * @param s H.263 stream 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/h26x.h b/modules/avcodec/h26x.h new file mode 100644 index 0000000..faff489 --- /dev/null +++ b/modules/avcodec/h26x.h @@ -0,0 +1,104 @@ +/** + * @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 + */ + +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..9e5f25c --- /dev/null +++ b/modules/avcodec/module.mk @@ -0,0 +1,21 @@ +# +# 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 encode.c decode.c +$(MOD)_LFLAGS += -lavcodec -lavutil +CFLAGS += -DUSE_AVCODEC +ifneq ($(USE_X264),) +CFLAGS += -DUSE_X264 +$(MOD)_LFLAGS += -lx264 +endif +$(MOD)_CFLAGS += -isystem /usr/local/include + +include mk/mod.mk diff --git a/modules/avformat/avformat.c b/modules/avformat/avformat.c new file mode 100644 index 0000000..68ef088 --- /dev/null +++ b/modules/avformat/avformat.c @@ -0,0 +1,448 @@ +/** + * @file avformat.c libavformat video-source + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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> +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) +#include <libavutil/pixdesc.h> +#endif + + +/** + * @defgroup avformat avformat + * + * Video source using FFmpeg/libav libavformat + * + * + * Example config: + \verbatim + video_source avformat,/tmp/testfile.mp4 + \endverbatim + */ + + +/* backward compat */ +#if LIBAVCODEC_VERSION_MAJOR>52 || LIBAVCODEC_VERSION_INT>=((52<<16)+(64<<8)) +#define LIBAVCODEC_HAVE_AVMEDIA_TYPES 1 +#endif +#ifndef LIBAVCODEC_HAVE_AVMEDIA_TYPES +#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO +#endif + + +#if LIBAVCODEC_VERSION_INT < ((54<<16)+(25<<8)+0) +#define AVCodecID CodecID +#define AV_CODEC_ID_NONE CODEC_ID_NONE +#endif + + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P +#endif + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + pthread_t thread; + bool run; + AVFormatContext *ic; + AVCodec *codec; + AVCodecContext *ctx; + AVRational time_base; + 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->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 + } +} + + +static void handle_packet(struct vidsrc_st *st, AVPacket *pkt) +{ + AVFrame *frame = NULL; + struct vidframe vf; + struct vidsz sz; + unsigned i; + + if (st->codec) { + int got_pict, ret; + +#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100) + frame = av_frame_alloc(); +#else + frame = avcodec_alloc_frame(); +#endif + +#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100) + + ret = avcodec_send_packet(st->ctx, pkt); + if (ret < 0) + goto out; + + ret = avcodec_receive_frame(st->ctx, frame); + if (ret < 0) + goto out; + + got_pict = true; + +#elif 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) + goto out; + + sz.w = st->ctx->width; + sz.h = st->ctx->height; + + /* check if size changed */ + if (!vidsz_cmp(&sz, &st->sz)) { + info("avformat: size changed: %d x %d ---> %d x %d\n", + st->sz.w, st->sz.h, sz.w, sz.h); + st->sz = sz; + } + } + else { + /* No-codec option is not supported */ + return; + } + +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) + switch (frame->format) { + + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: + vf.fmt = VID_FMT_YUV420P; + break; + + default: + warning("avformat: decode: bad pixel format" + " (%i) (%s)\n", + frame->format, + av_get_pix_fmt_name(frame->format)); + goto out; + } +#else + vf.fmt = VID_FMT_YUV420P; +#endif + + vf.size = sz; + for (i=0; i<4; i++) { + vf.data[i] = frame->data[i]; + vf.linesize[i] = frame->linesize[i]; + } + + st->frameh(&vf, st->arg); + + out: + if (frame) { +#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100) + av_frame_free(&frame); +#else + av_free(frame); +#endif + } +} + + +static void *read_thread(void *data) +{ + struct vidsrc_st *st = data; + + uint64_t now, ts = tmr_jiffies(); + + while (st->run) { + AVPacket pkt; + int ret; + + sys_msleep(4); + now = tmr_jiffies(); + + if (ts > now) + continue; + + av_init_packet(&pkt); + + ret = av_read_frame(st->ic, &pkt); + if (ret < 0) { + debug("avformat: rewind stream (ret=%d)\n", ret); + sys_msleep(1000); + av_seek_frame(st->ic, -1, 0, 0); + continue; + } + + if (pkt.stream_index != st->sindex) + goto out; + + handle_packet(st, &pkt); + + ts += (uint64_t) 1000 * pkt.duration * av_q2d(st->time_base); + + out: +#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(12<<8)+100) + av_packet_unref(&pkt); +#else + av_free_packet(&pkt); +#endif + } + + return NULL; +} + + +static int alloc(struct vidsrc_st **stp, const 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; + int input_fps = 0; + + (void)mctx; + (void)errorh; + + if (!stp || !vs || !prm || !size || !frameh) + return EINVAL; + + debug("avformat: alloc dev='%s'\n", dev); + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = vs; + st->sz = *size; + st->frameh = frameh; + st->arg = arg; + st->fps = prm->fps; + + /* + * 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); + if (ret < 0) { + warning("avformat: avformat_open_input(%s) failed (ret=%d)\n", + dev, ret); + err = ENOENT; + goto out; + } +#else + + /* Params */ + memset(&prms, 0, sizeof(prms)); + + prms.time_base = (AVRational){1, prm->fps}; + prms.channels = 1; + prms.width = size->w; + prms.height = size->h; + prms.pix_fmt = AV_PIX_FMT_YUV420P; + prms.channel = 0; + + ret = av_open_input_file(&st->ic, dev, av_find_input_format(fmt), + 0, &prms); + if (ret < 0) { + warning("avformat: av_open_input_file(%s) failed (ret=%d)\n", + dev, ret); + err = ENOENT; + goto out; + } +#endif + + +#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; + double dfps; + +#if LIBAVFORMAT_VERSION_INT >= ((57<<16) + (33<<8) + 100) + + ctx = avcodec_alloc_context3(NULL); + if (!ctx) { + err = ENOMEM; + goto out; + } + + ret = avcodec_parameters_to_context(ctx, strm->codecpar); + if (ret < 0) { + warning("avformat: avcodec_parameters_to_context\n"); + err = EPROTO; + goto out; + } + +#else + ctx = strm->codec; +#endif + + if (ctx->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + debug("avformat: stream %u: %u x %u " + " time_base=%d/%d\n", + i, ctx->width, ctx->height, + strm->time_base.num, strm->time_base.den); + + st->sz.w = ctx->width; + st->sz.h = ctx->height; + st->ctx = ctx; + st->sindex = strm->index; + st->time_base = strm->time_base; + + dfps = av_q2d(strm->avg_frame_rate); + input_fps = (int)dfps; + if (st->fps != input_fps) { + info("avformat: updating %i fps from config to native " + "input material fps %i\n", st->fps, input_fps); + st->fps = input_fps; +#if LIBAVFORMAT_VERSION_INT < ((52<<16) + (110<<8) + 0) + prms.time_base = (AVRational){1, st->fps}; +#endif + } + + if (ctx->codec_id != AV_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(); + +#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (13<<8) + 0) + avformat_network_init(); +#endif + + av_register_all(); + + return vidsrc_register(&mod_avf, baresip_vidsrcl(), + "avformat", alloc, NULL); +} + + +static int module_close(void) +{ + mod_avf = mem_deref(mod_avf); + +#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (13<<8) + 0) + avformat_network_deinit(); +#endif + + 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..07d9c9e --- /dev/null +++ b/modules/avformat/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := avformat +$(MOD)_SRCS += avformat.c +$(MOD)_LFLAGS += -lavdevice -lavformat -lavcodec -lavutil +CFLAGS += -DUSE_AVFORMAT + +include mk/mod.mk diff --git a/modules/b2bua/b2bua.c b/modules/b2bua/b2bua.c new file mode 100644 index 0000000..7386a29 --- /dev/null +++ b/modules/b2bua/b2bua.c @@ -0,0 +1,246 @@ +/** + * @file b2bua.c Back-to-Back User-Agent (B2BUA) module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup b2bua b2bua + * + * Back-to-Back User-Agent (B2BUA) module + * + * NOTE: This module is experimental. + * + * N session objects + * 1 session object has 2 call objects (left, right leg) + */ + + +struct session { + struct le le; + struct call *call_in, *call_out; +}; + + +static struct list sessionl; +static struct ua *ua_in, *ua_out; + + +static struct call *other_call(struct session *sess, const struct call *call) +{ + if (sess->call_in == call) return sess->call_out; + if (sess->call_out == call) return sess->call_in; + + return NULL; +} + + +static void destructor(void *arg) +{ + struct session *sess = arg; + + debug("b2bua: session destroyed (in=%p, out=%p)\n", + sess->call_in, sess->call_out); + + list_unlink(&sess->le); + mem_deref(sess->call_out); + mem_deref(sess->call_in); +} + + +static void call_event_handler(struct call *call, enum call_event ev, + const char *str, void *arg) +{ + struct session *sess = arg; + struct call *call2 = other_call(sess, call); + + switch (ev) { + + case CALL_EVENT_ESTABLISHED: + debug("b2bua: CALL_ESTABLISHED: peer_uri=%s\n", + call_peeruri(call)); + ua_answer(call_get_ua(call2), call2); + break; + + case CALL_EVENT_CLOSED: + debug("b2bua: CALL_CLOSED: %s\n", str); + + mem_ref(call2); + + ua_hangup(call_get_ua(call2), call2, call_scode(call), ""); + mem_deref(sess); + break; + + default: + break; + } +} + + +static void call_dtmf_handler(struct call *call, char key, void *arg) +{ + struct session *sess = arg; + + debug("b2bua: relaying DTMF event: key = '%c'\n", key ? key : '.'); + + call_send_digit(other_call(sess, call), key); +} + + +static int new_session(struct call *call) +{ + struct session *sess; + char a[64], b[64]; + int err; + + sess = mem_zalloc(sizeof(*sess), destructor); + if (!sess) + return ENOMEM; + + sess->call_in = call; + err = ua_connect(ua_out, &sess->call_out, call_peeruri(call), + call_localuri(call), NULL, + call_has_video(call) ? VIDMODE_ON : VIDMODE_OFF); + if (err) { + warning("b2bua: ua_connect failed (%m)\n", err); + goto out; + } + + re_snprintf(a, sizeof(a), "A-%x", sess); + re_snprintf(b, sizeof(b), "B-%x", sess); + + /* connect the audio/video-bridge devices */ + audio_set_devicename(call_audio(sess->call_in), a, b); + audio_set_devicename(call_audio(sess->call_out), b, a); + video_set_devicename(call_video(sess->call_in), a, b); + video_set_devicename(call_video(sess->call_out), b, a); + + call_set_handlers(sess->call_in, call_event_handler, + call_dtmf_handler, sess); + call_set_handlers(sess->call_out, call_event_handler, + call_dtmf_handler, sess); + + list_append(&sessionl, &sess->le, sess); + + out: + if (err) + mem_deref(sess); + + return err; +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + int err; + (void)prm; + (void)arg; + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + debug("b2bua: CALL_INCOMING: peer=%s --> local=%s\n", + call_peeruri(call), call_localuri(call)); + + err = new_session(call); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + default: + break; + } +} + + +static int b2bua_status(struct re_printf *pf, void *arg) +{ + struct le *le; + int err = 0; + (void)arg; + + err |= re_hprintf(pf, "B2BUA status:\n"); + err |= re_hprintf(pf, " inbound: %s\n", ua_aor(ua_in)); + err |= re_hprintf(pf, " outbound: %s\n", ua_aor(ua_out)); + + err |= re_hprintf(pf, "sessions:\n"); + + for (le = sessionl.head; le; le = le->next) { + + struct session *sess = le->data; + + err |= re_hprintf(pf, "%-42s ---> %42s\n", + call_peeruri(sess->call_in), + call_peeruri(sess->call_out)); + + err |= re_hprintf(pf, " %H\n", call_status, sess->call_in); + err |= re_hprintf(pf, " %H\n", call_status, sess->call_out); + } + + return err; +} + + +static const struct cmd cmdv[] = { + {"b2bua", 0, 0, "b2bua status", b2bua_status }, +}; + + +static int module_init(void) +{ + int err; + + ua_in = uag_find_param("b2bua", "inbound"); + ua_out = uag_find_param("b2bua", "outbound"); + + if (!ua_in) { + warning("b2bua: inbound UA not found\n"); + return ENOENT; + } + if (!ua_out) { + warning("b2bua: outbound UA not found\n"); + return ENOENT; + } + + err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + err = uag_event_register(ua_event_handler, 0); + if (err) + return err; + + debug("b2bua: module loaded\n"); + + return 0; +} + + +static int module_close(void) +{ + debug("b2bua: module closing..\n"); + + if (!list_isempty(&sessionl)) { + + info("b2bua: flushing %u sessions\n", list_count(&sessionl)); + list_flush(&sessionl); + } + + uag_event_unregister(ua_event_handler); + cmd_unregister(baresip_commands(), cmdv); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(b2bua) = { + "b2bua", + "application", + module_init, + module_close +}; diff --git a/modules/b2bua/module.mk b/modules/b2bua/module.mk new file mode 100644 index 0000000..8afe230 --- /dev/null +++ b/modules/b2bua/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := b2bua +$(MOD)_SRCS += b2bua.c + +include mk/mod.mk diff --git a/modules/bv32/bv32.c b/modules/bv32/bv32.c new file mode 100644 index 0000000..d3dd45c --- /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, 16000, 1, NULL, + encode_update, encode, + decode_update, decode, plc, + NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(baresip_aucodecl(), &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..2af2a12 --- /dev/null +++ b/modules/cairo/cairo.c @@ -0,0 +1,345 @@ +/** + * @file cairo.c Cairo module + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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 + + +/** + * @defgroup cairo cairo + * + * Cairo video-source module is a video generator for testing + * and demo purposes. + * + * Note: This module is very experimental! + * + * Use Cairo library to draw graphics into a frame buffer + */ + + +enum { + FONT_SIZE = 18 +}; + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + + struct vidsrc_prm prm; + struct vidsz size; + cairo_surface_t *surface; + cairo_t *cr; + cairo_surface_t *surface_logo; + cairo_t *cr_logo; + double logo_width; + double logo_height; + 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); + + if (st->cr_logo) + cairo_destroy(st->cr_logo); + if (st->surface_logo) + cairo_surface_destroy(st->surface_logo); +} + + +static void draw_background(cairo_t *cr, double color_step, + int width, int height) +{ + cairo_pattern_t *pat; + double grey, r, g, b; + + grey = 0.1 + fabs(sin(3 * color_step)); + r = grey; + g = grey; + b = grey; + + 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); +} + + +static void draw_text(struct vidsrc_st *st, int x, int y, + const char *fmt, ...) +{ + char buf[4096] = ""; + va_list ap; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + cairo_set_source_rgb(st->cr, 1.0, 1.0, 1.0); /* white */ + + cairo_set_font_size(st->cr, FONT_SIZE); + cairo_move_to(st->cr, x, y); + cairo_show_text(st->cr, buf); +} + + +static void draw_logo(struct vidsrc_st *st) +{ + double x, y; + + x = (st->size.w - st->logo_width) * (sin(10 * st->step) + 1)/2; + y = (st->size.h - st->logo_height)* (1 - fabs(sin(30 * st->step))); + + cairo_set_source_surface(st->cr, st->surface_logo, x, y); + cairo_paint(st->cr); +} + + +static void process(struct vidsrc_st *st) +{ + struct vidframe f; + unsigned xoffs = 2, yoffs = 24; + + draw_background(st->cr, st->step, st->size.w, st->size.h); + + draw_text(st, xoffs, yoffs + FONT_SIZE, "%H", fmt_gmtime, NULL); + + draw_text(st, xoffs, yoffs + FONT_SIZE*2, "%u x %u @ %d fps", + st->size.w, st->size.h, st->prm.fps); + + draw_logo(st); + + 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 load_logo(struct vidsrc_st *st, const char *filename) +{ + cairo_surface_t *logo; + double lw; + double scale; + int err = 0; + + logo = cairo_image_surface_create_from_png(filename); + if (!logo) { + warning("cairo: failed to load PNG logo\n"); + err = ENOENT; + goto out; + } + + if (!cairo_image_surface_get_width(logo) || + !cairo_image_surface_get_height(logo)) { + warning("cairo: invalid logo (%s)\n", filename); + err = ENOENT; + goto out; + } + + st->logo_width = st->size.w / 2; + lw = cairo_image_surface_get_width(logo); + scale = (double)st->logo_width / (double)lw; + + st->logo_height = cairo_image_surface_get_height(logo) * scale; + + /* create a scaled-down logo with same aspect ratio */ + + st->surface_logo = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + st->logo_width, + st->logo_height); + if (!st->surface_logo) { + err = ENOMEM; + goto out; + } + + st->cr_logo = cairo_create(st->surface_logo); + if (!st->cr_logo) { + err = ENOMEM; + goto out; + } + + cairo_scale(st->cr_logo, scale, scale); + + cairo_set_source_surface(st->cr_logo, logo, 0, 0); + cairo_paint(st->cr_logo); + + info("cairo: scaling logo '%s' from %d x %d to %f x %f\n", + filename, + cairo_image_surface_get_width(logo), + cairo_image_surface_get_height(logo), + st->logo_width, + st->logo_height); + + out: + if (logo) + cairo_surface_destroy(logo); + return err; +} + + +static int alloc(struct vidsrc_st **stp, const 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 config *cfg; + struct vidsrc_st *st; + char logo[256]; + int err = 0; + + (void)ctx; + (void)fmt; + (void)dev; + (void)errorh; + + if (!stp || !prm || !size || !frameh) + return EINVAL; + + cfg = conf_config(); + if (!cfg) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vs = 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); + if (!st->surface) { + err = ENOMEM; + goto out; + } + + st->cr = cairo_create(st->surface); + if (!st->cr) { + err = ENOMEM; + goto out; + } + + cairo_select_font_face(st->cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + + 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; + + re_snprintf(logo, sizeof(logo), "%s/logo.png", cfg->audio.audio_path); + + err = load_logo(st, logo); + 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 module_init(void) +{ + return vidsrc_register(&vidsrc, baresip_vidsrcl(), + "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..13f1632 --- /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 += $(shell pkg-config --libs cairo) +$(MOD)_CFLAGS += $(shell pkg-config --cflags cairo) + +include mk/mod.mk diff --git a/modules/codec2/codec2.c b/modules/codec2/codec2.c new file mode 100644 index 0000000..b6911dc --- /dev/null +++ b/modules/codec2/codec2.c @@ -0,0 +1,202 @@ +/** + * @file codec2.c CODEC2 audio codec + * + * Copyright (C) 2015 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <codec2/codec2.h> + + +/** + * @defgroup codec2 codec2 + * + * The CODEC2 audio codec + * + * https://en.wikipedia.org/wiki/Codec2 + */ + + +enum { + CODEC2_MODE = CODEC2_MODE_2400 +}; + + +struct auenc_state { + struct CODEC2 *c2; +}; + +struct audec_state { + struct CODEC2 *c2; +}; + + +static void encode_destructor(void *data) +{ + struct auenc_state *st = data; + + if (st->c2) + codec2_destroy(st->c2); +} + + +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->c2 = codec2_create(CODEC2_MODE); + if (!st->c2) { + err = ENOMEM; + goto out; + } + + info("codec2: %d samples per frame, %d bits per frame\n", + codec2_samples_per_frame(st->c2), + codec2_bits_per_frame(st->c2)); + + out: + if (err) + mem_deref(st); + else + *aesp = st; + + return err; +} + + +static void decode_destructor(void *data) +{ + struct audec_state *st = data; + + if (st->c2) + codec2_destroy(st->c2); +} + + +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->c2 = codec2_create(CODEC2_MODE); + if (!st->c2) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *adsp = st; + + return err; +} + + +static int encode(struct auenc_state *aes, uint8_t *buf, + size_t *len, const int16_t *sampv, size_t sampc) +{ + if (!buf || !len || !sampv) + return EINVAL; + + if (*len < (size_t)codec2_bits_per_frame(aes->c2)/8) + return ENOMEM; + if (sampc != (size_t)codec2_samples_per_frame(aes->c2)) + return EPROTO; + + codec2_encode(aes->c2, buf, (short *)sampv); + + *len = codec2_bits_per_frame(aes->c2)/8; + + return 0; +} + + +static int decode(struct audec_state *ads, int16_t *sampv, + size_t *sampc, const uint8_t *buf, size_t len) +{ + if (!sampv || !sampc || !buf) + return EINVAL; + + if (*sampc < (size_t)codec2_samples_per_frame(ads->c2)) + return ENOMEM; + if (len < (size_t)codec2_bits_per_frame(ads->c2)/8) + return EPROTO; + + codec2_decode(ads->c2, sampv, buf); + + *sampc = codec2_samples_per_frame(ads->c2); + + return 0; +} + + +static struct aucodec codec2 = { + LE_INIT, + NULL, + "CODEC2", + 8000, + 8000, + 1, + NULL, + encode_update, + encode, + decode_update, + decode, + NULL, + NULL, + NULL +}; + + +static int module_init(void) +{ + aucodec_register(baresip_aucodecl(), &codec2); + + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&codec2); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(codec2) = { + "codec2", + "audio codec", + module_init, + module_close, +}; diff --git a/modules/codec2/module.mk b/modules/codec2/module.mk new file mode 100644 index 0000000..2d4d5d8 --- /dev/null +++ b/modules/codec2/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := codec2 +$(MOD)_SRCS += codec2.c +$(MOD)_LFLAGS += -lcodec2 + +include mk/mod.mk diff --git a/modules/cons/cons.c b/modules/cons/cons.c new file mode 100644 index 0000000..fc80f81 --- /dev/null +++ b/modules/cons/cons.c @@ -0,0 +1,274 @@ +/** + * @file cons.c Socket-based command-line console + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup cons cons + * + * Console User-Interface (UI) using UDP/TCP sockets + * + * + * This module implements a simple console for connecting to Baresip via + * UDP or TCP-based sockets. You can use programs like telnet or netcat to + * connect to the command-line interface. + * + * Example, with the cons-module listening on default port 5555: + * + \verbatim + $ netcat -u 127.0.0.1 5555 + \endverbatim + * + * The following options can be configured: + * + \verbatim + cons_listen 0.0.0.0:5555 # IP-address and port to listen on + \endverbatim + */ + + +enum {CONS_PORT = 5555}; + +struct ui_st { + struct udp_sock *us; + struct tcp_sock *ts; + struct tcp_conn *tc; + struct sa udp_peer; +}; + + +static struct ui_st *cons = 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; + + st->udp_peer = *src; + + pf.vph = print_handler; + pf.arg = mbr; + + while (mbuf_get_left(mb)) { + char ch = mbuf_read_u8(mb); + + if (ch == '\r') + ch = '\n'; + + ui_input_key(baresip_uis(), ch, &pf); + } + + if (mbr->end > 0) { + 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); +} + + +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) { + + char ch = mbuf_read_u8(mb); + + if (ch == '\r') + ch = '\n'; + + ui_input_key(baresip_uis(), ch, &pf); + } +} + + +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, const struct sa *laddr) +{ + struct ui_st *st; + int err; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), cons_destructor); + if (!st) + return ENOMEM; + + err = udp_listen(&st->us, laddr, udp_recv, st); + if (err) { + warning("cons: failed to listen on UDP %J (%m)\n", + laddr, err); + goto out; + } + + err = tcp_listen(&st->ts, laddr, tcp_conn_handler, st); + if (err) { + warning("cons: failed to listen on TCP %J (%m)\n", + laddr, err); + goto out; + } + + debug("cons: UI console listening on %J\n", laddr); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int output_handler(const char *str) +{ + struct mbuf *mb; + int err = 0; + + if (!str) + return EINVAL; + + mb = mbuf_alloc(256); + if (!mb) + return ENOMEM; + + mbuf_write_str(mb, str); + + if (sa_isset(&cons->udp_peer, SA_ALL)) { + mb->pos = 0; + err |= udp_send(cons->us, &cons->udp_peer, mb); + } + + if (cons->tc) { + mb->pos = 0; + err |= tcp_send(cons->tc, mb); + } + + mem_deref(mb); + + return err; +} + + +/* + * Relay log-messages to all active UDP/TCP connections + */ +static void log_handler(uint32_t level, const char *msg) +{ + (void)level; + + output_handler(msg); +} + + +static struct ui ui_cons = { + LE_INIT, + "cons", + output_handler +}; + + +static struct log lg = { + .h = log_handler, +}; + + +static int cons_init(void) +{ + struct sa laddr; + int err; + + if (conf_get_sa(conf_cur(), "cons_listen", &laddr)) { + sa_set_str(&laddr, "0.0.0.0", CONS_PORT); + } + + err = cons_alloc(&cons, &laddr); + if (err) + return err; + + ui_register(baresip_uis(), &ui_cons); + + log_register_handler(&lg); + + return 0; +} + + +static int cons_close(void) +{ + log_unregister_handler(&lg); + + ui_unregister(&ui_cons); + 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..2bb506d --- /dev/null +++ b/modules/contact/contact.c @@ -0,0 +1,255 @@ +/** + * @file modules/contact/contact.c Contacts module + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup contact contact + * + * Contact module reading contacts from a file + * + * - 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, void *arg) +{ + struct contacts *contacts = arg; + return contact_add(contacts, NULL, addr); +} + + +static int cmd_contact(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct contacts *contacts = baresip_contacts(); + 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(contacts)); 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 print_contacts(struct re_printf *pf, void *unused) +{ + (void)unused; + return contacts_print(pf, baresip_contacts()); +} + + +static void send_resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + (void)arg; + + 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); + } +} + + +static int cmd_message(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err; + + if (!str_isset(chat_peer)) { + return re_hprintf(pf, "contact: chat peer is not set\n"); + } + + err = message_send(uag_current(), chat_peer, carg->prm, + send_resp_handler, NULL); + if (err) { + (void)re_hprintf(pf, "contact: message_send() failed (%m)\n", + err); + } + + return err; +} + + +static const struct cmd cmdv[] = { +{"dialcontact", '|', CMD_IPRM, "Dial from contacts", cmd_contact }, +{"chatpeer", '=', CMD_IPRM, "Select chat peer", cmd_contact }, +{"contacts", 'C', 0, "List contacts", print_contacts }, +{"message", '-', 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(baresip_network()); + 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" + "# ;access={allow,block}\n" + "#\n" + "\n" + "\n" + "\"Echo Server\" <sip:echo@creytiv.com>\n" + "\"%s\" <sip:%s@%s>;presence=p2p\n" + "\n" + "# Access rules\n" + "#\"Catch All\" <sip:*@*>;access=block\n" + "\"Good Friend\" <sip:good@friend.com>;access=allow\n" + "\n" + , + user, user, domain); + + if (f) + (void)fclose(f); + + return 0; +} + + +static int module_init(void) +{ + struct contacts *contacts = baresip_contacts(); + 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, contacts); + if (err) + return err; + + err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + info("Populated %u contacts\n", + list_count(contact_list(contacts))); + + return err; +} + + +static int module_close(void) +{ + cmd_unregister(baresip_commands(), cmdv); + list_flush(contact_list(baresip_contacts())); + + 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..e1af629 --- /dev/null +++ b/modules/coreaudio/coreaudio.c @@ -0,0 +1,111 @@ +/** + * @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" + + +/** + * @defgroup coreaudio coreaudio + * + * Audio driver module for OSX CoreAudio + */ + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0 +static void interruptionListener(void *data, UInt32 inInterruptionState) +{ + (void)data; + + /* XXX: 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, baresip_auplayl(), + "coreaudio", coreaudio_player_alloc); + err |= ausrc_register(&ausrc, baresip_ausrcl(), + "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..a4d4edd --- /dev/null +++ b/modules/coreaudio/coreaudio.h @@ -0,0 +1,18 @@ +/** + * @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 coreaudio_player_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int coreaudio_recorder_alloc(struct ausrc_st **stp, const 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..7d247cd --- /dev/null +++ b/modules/coreaudio/player.c @@ -0,0 +1,165 @@ +/** + * @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 { + const 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); + } + + 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; + + wh(outQB->mAudioData, outQB->mAudioDataByteSize/2, arg); + + AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL); +} + + +int coreaudio_player_alloc(struct auplay_st **stp, const 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; + + if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = 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 = kAudioFormatLinearPCM; + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * 2; + fmt.mBytesPerPacket = prm->ch * 2; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 16; + + 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 * 2; + + 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..9ceae60 --- /dev/null +++ b/modules/coreaudio/recorder.c @@ -0,0 +1,176 @@ +/** + * @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 { + const struct ausrc *as; /* inheritance */ + AudioQueueRef queue; + AudioQueueBufferRef buf[BUFC]; + pthread_mutex_t mutex; + 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); + } + + 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; + unsigned int ptime; + ausrc_read_h *rh; + 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; + + rh(inQB->mAudioData, inQB->mAudioDataByteSize/2, arg); + + 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, const 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 || prm->fmt != AUFMT_S16LE) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->ptime = prm->ptime; + st->as = as; + st->rh = rh; + st->arg = arg; + + sampc = prm->srate * prm->ch * prm->ptime / 1000; + bytc = sampc * 2; + + 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 = kAudioFormatLinearPCM; + fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; +#ifdef __BIG_ENDIAN__ + fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian; +#endif + + fmt.mFramesPerPacket = 1; + fmt.mBytesPerFrame = prm->ch * 2; + fmt.mBytesPerPacket = prm->ch * 2; + fmt.mChannelsPerFrame = prm->ch; + fmt.mBitsPerChannel = 16; + + 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/daala/daala.c b/modules/daala/daala.c new file mode 100644 index 0000000..3ea7e27 --- /dev/null +++ b/modules/daala/daala.c @@ -0,0 +1,63 @@ +/** + * @file daala.c Experimental video-codec using Daala + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <daala/codec.h> +#include "daala.h" + + +/** + * @defgroup daala daala + * + * Very experimental video-codec using Daala + * + * + * External libraries: + * + * daala version 0.0-1564-g79787c7 (or later) + * + * References: + * + * https://wiki.xiph.org/Daala + * + * NOTE! Now deprecated in favour of AV1 video codec + */ + + +static struct vidcodec daala = { + .name = "daala", + .encupdh = daala_encode_update, + .ench = daala_encode, + .decupdh = daala_decode_update, + .dech = daala_decode, +}; + + +static int module_init(void) +{ + info("daala: using version '%s'\n", daala_version_string()); + + vidcodec_register(baresip_vidcodecl(), &daala); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&daala); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(daala) = { + "daala", + "video codec", + module_init, + module_close, +}; diff --git a/modules/daala/daala.h b/modules/daala/daala.h new file mode 100644 index 0000000..323ba88 --- /dev/null +++ b/modules/daala/daala.h @@ -0,0 +1,20 @@ +/** + * @file daala.h Experimental video-codec using Daala -- internal api + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + + +/* Encode */ +int daala_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int daala_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + + +/* Decode */ +int daala_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int daala_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb); diff --git a/modules/daala/decode.c b/modules/daala/decode.c new file mode 100644 index 0000000..062fb60 --- /dev/null +++ b/modules/daala/decode.c @@ -0,0 +1,181 @@ +/** + * @file daala/decode.c Experimental video-codec using Daala -- decoder + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <daala/daaladec.h> +#include "daala.h" + + +struct viddec_state { + daala_dec_ctx *dec; + + bool got_headers; + + daala_info di; + daala_comment dc; + daala_setup_info *ds; + + struct { + bool valid; + size_t n_frame; + size_t n_header; + size_t n_keyframe; + size_t n_packet; + } stats; +}; + + +static void dump_stats(const struct viddec_state *vds) +{ + re_printf("~~~~~ Daala Decoder stats ~~~~~\n"); + re_printf("num frames: %zu\n", vds->stats.n_frame); + re_printf("num headers: %zu\n", vds->stats.n_header); + re_printf("key-frames packets: %zu\n", vds->stats.n_keyframe); + re_printf("total packets: %zu\n", vds->stats.n_packet); +} + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + if (vds->stats.valid) + dump_stats(vds); + + if (vds->dec) + daala_decode_free(vds->dec); + + if (vds->ds) + daala_setup_free(vds->ds); + daala_comment_clear(&vds->dc); + daala_info_clear(&vds->di); +} + + +int daala_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + 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; + + daala_info_init(&vds->di); + daala_comment_init(&vds->dc); + + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +int daala_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb) +{ + daala_packet dp; + bool ishdr; + int i, r, err = 0; + (void)seq; + + if (!vds || !frame || !mb) + return EINVAL; + + *intra = false; + + ++vds->stats.n_packet; + ++vds->stats.valid; + + memset(&dp, 0, sizeof(dp)); + + dp.packet = mbuf_buf(mb); + dp.bytes = mbuf_get_left(mb); + dp.b_o_s = marker; + + ishdr = daala_packet_isheader(&dp); + + if (ishdr) + ++vds->stats.n_header; + else if (daala_packet_iskeyframe(&dp) > 0) { + ++vds->stats.n_keyframe; + *intra = true; + } + + if (daala_packet_isheader(&dp)) { + + r = daala_decode_header_in(&vds->di, &vds->dc, &vds->ds, + &dp); + if (r < 0) { + warning("daala: decoder: decode_header_in failed" + " (ret = %d)\n", + r); + return EPROTO; + } + else if (r == 0) { + vds->got_headers = true; + info("daala: all headers received\n"); + + vds->dec = daala_decode_create(&vds->di, vds->ds); + if (!vds->dec) { + warning("daala: decoder: alloc failed\n"); + return ENOMEM; + } + } + else { + /* waiting for more headers */ + } + } + else { + daala_image img; + + if (!vds->got_headers) { + warning("daala: decode: still waiting for headers\n"); + return EPROTO; + } + + r = daala_decode_packet_in(vds->dec, &dp); + if (r < 0) { + warning("daala: decode: packet_in error (%d)\n", r); + return EPROTO; + } + + r = daala_decode_img_out(vds->dec, &img); + if (r != 1) { + warning("daala: decode: img_out error (%d)\n", r); + return EPROTO; + } + + for (i=0; i<3; i++) { + frame->data[i] = img.planes[i].data; + frame->linesize[i] = img.planes[i].ystride; + } + + frame->size.w = img.width; + frame->size.h = img.height; + frame->fmt = VID_FMT_YUV420P; + + ++vds->stats.n_frame; + } + + return err; +} diff --git a/modules/daala/encode.c b/modules/daala/encode.c new file mode 100644 index 0000000..d8abe4d --- /dev/null +++ b/modules/daala/encode.c @@ -0,0 +1,292 @@ +/** + * @file daala/encode.c Experimental video-codec using Daala -- encoder + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <daala/daalaenc.h> +#include "daala.h" + + +struct videnc_state { + struct vidsz size; + daala_enc_ctx *enc; + int64_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + videnc_packet_h *pkth; + void *arg; + + struct { + bool valid; + size_t n_frame; + size_t n_header; + size_t n_keyframe; + size_t n_packet; + } stats; +}; + + +static void dump_stats(const struct videnc_state *ves) +{ + re_printf("~~~~~ Daala Encoder stats ~~~~~\n"); + re_printf("num frames: %zu\n", ves->stats.n_frame); + re_printf("num headers: %zu\n", ves->stats.n_header); + re_printf("key-frames packets: %zu\n", ves->stats.n_keyframe); + re_printf("total packets: %zu\n", ves->stats.n_packet); +} + + +static int send_packet(struct videnc_state *ves, bool marker, + const uint8_t *pld, size_t pld_len) +{ + daala_packet dp; + int err; + + memset(&dp, 0, sizeof(dp)); + + dp.packet = (uint8_t *)pld; + dp.bytes = pld_len; + dp.b_o_s = marker; + + err = ves->pkth(marker, NULL, 0, pld, pld_len, ves->arg); + if (err) + return err; + + ++ves->stats.n_packet; + ++ves->stats.valid; + + if (daala_packet_isheader(&dp)) + ++ves->stats.n_header; + else if (daala_packet_iskeyframe(&dp) > 0) + ++ves->stats.n_keyframe; + + return 0; +} + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + + if (ves->stats.valid) + dump_stats(ves); + + if (ves->enc) + daala_encode_free(ves->enc); +} + + +int daala_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *ves; + (void)fmtp; + + if (!vesp || !vc || !prm || prm->pktsize < 3 || !pkth) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), destructor); + if (!ves) + return ENOMEM; + + *vesp = ves; + } + else { + if (ves->enc && (ves->bitrate != prm->bitrate || + ves->pktsize != prm->pktsize || + ves->fps != prm->fps)) { + + info("daala: encoder: params changed\n"); + + daala_encode_free(ves->enc); + ves->enc = NULL; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + ves->pkth = pkth; + ves->arg = arg; + + return 0; +} + + +static int open_encoder(struct videnc_state *ves, const struct vidsz *size) +{ + daala_info di; + daala_comment dc; + daala_packet dp; + int err = 0; + int complexity = 7; + int video_q = 30; + int bitrate = ves->bitrate; + + info("daala: open encoder (%d x %d, %d bps)\n", + size->w, size->h, bitrate); + + if (ves->enc) { + debug("daala: re-opening encoder\n"); + daala_encode_free(ves->enc); + } + + daala_info_init(&di); + daala_comment_init(&dc); + + di.pic_width = size->w; + di.pic_height = size->h; + di.timebase_numerator = 1; + di.timebase_denominator = ves->fps; + di.frame_duration = 1; + di.pixel_aspect_numerator = -1; + di.pixel_aspect_denominator = -1; + di.nplanes = 3; + di.plane_info[0].xdec = 0; /* YUV420P */ + di.plane_info[0].ydec = 0; + di.plane_info[1].xdec = 1; + di.plane_info[1].ydec = 1; + di.plane_info[2].xdec = 1; + di.plane_info[2].ydec = 1; + + di.keyframe_rate = 100; + + info("daala: open encoder with bitstream version %u.%u.%u\n", + di.version_major, di.version_minor, di.version_sub); + + ves->enc = daala_encode_create(&di); + if (!ves->enc) { + warning("daala: failed to open DAALA encoder\n"); + return ENOMEM; + } + + daala_encode_ctl(ves->enc, OD_SET_QUANT, + &video_q, sizeof(video_q)); + + daala_encode_ctl(ves->enc, OD_SET_COMPLEXITY, + &complexity, sizeof(complexity)); + + daala_encode_ctl(ves->enc, OD_SET_BITRATE, + &bitrate, sizeof(bitrate)); + + for (;;) { + int r; + + r = daala_encode_flush_header(ves->enc, &dc, &dp); + if (r < 0) { + warning("daala: flush_header returned %d\n", r); + break; + } + else if (r == 0) + break; + + debug("daala: header: %lld bytes header=%d key=%d\n", + dp.bytes, + daala_packet_isheader(&dp), + daala_packet_iskeyframe(&dp)); + +#if 0 + re_printf("bos=%lld, eos=%lld, granule=%lld, packetno=%lld\n", + dp.b_o_s, + dp.e_o_s, + dp.granulepos, + dp.packetno); +#endif + + err = send_packet(ves, dp.b_o_s, dp.packet, dp.bytes); + if (err) + break; + } + + daala_info_clear(&di); + daala_comment_clear(&dc); + + return err; +} + + +int daala_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + int r, err = 0; + daala_image img; + unsigned i; + (void)update; /* XXX: how to force a KEY-frame? */ + + if (!ves || !frame || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + ++ves->stats.n_frame; + + if (!ves->enc || !vidsz_cmp(&ves->size, &frame->size)) { + + err = open_encoder(ves, &frame->size); + if (err) + return err; + + ves->size = frame->size; + } + + img.planes[0].data = frame->data[0]; + img.planes[0].xdec = 0; + img.planes[0].ydec = 0; + img.planes[0].xstride = 1; + img.planes[0].ystride = frame->linesize[0]; + + img.planes[1].data = frame->data[1]; + img.planes[1].xdec = 1; + img.planes[1].ydec = 1; + img.planes[1].xstride = 1; + img.planes[1].ystride = frame->linesize[1]; + + img.planes[2].data = frame->data[2]; + img.planes[2].xdec = 1; + img.planes[2].ydec = 1; + img.planes[2].xstride = 1; + img.planes[2].ystride = frame->linesize[2]; + + for (i=0; i<3; i++) + img.planes[i].bitdepth = 8; + + img.nplanes = 3; + + img.width = frame->size.w; + img.height = frame->size.h; + + r = daala_encode_img_in(ves->enc, &img, 0); + if (r != 0) { + warning("daala: encoder: encode_img_in failed (ret = %d)\n", + r); + return EPROTO; + } + + for (;;) { + daala_packet dp; + + r = daala_encode_packet_out(ves->enc, 0, &dp); + if (r < 0) { + warning("daala: encoder: packet_out ret=%d\n", r); + break; + } + else if (r == 0) { + break; + } + + err = send_packet(ves, dp.b_o_s, dp.packet, dp.bytes); + if (err) + break; + } + + return 0; +} diff --git a/modules/daala/module.mk b/modules/daala/module.mk new file mode 100644 index 0000000..93f88ac --- /dev/null +++ b/modules/daala/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := daala +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += daala.c +$(MOD)_LFLAGS += -ldaalaenc -ldaaladec -ldaalabase + +include mk/mod.mk diff --git a/modules/debug_cmd/debug_cmd.c b/modules/debug_cmd/debug_cmd.c new file mode 100644 index 0000000..faee4ee --- /dev/null +++ b/modules/debug_cmd/debug_cmd.c @@ -0,0 +1,164 @@ +/** + * @file debug_cmd.c Debug commands + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <stdlib.h> +#include <time.h> +#ifdef USE_OPENSSL +#include <openssl/crypto.h> +#endif +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup debug_cmd debug_cmd + * + * Advanced debug commands + */ + + +static uint64_t start_ticks; /**< Ticks when app started */ +static time_t start_time; /**< Start time of application */ + + +static int cmd_net_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return net_debug(pf, baresip_network()); +} + + +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 + +#ifdef USE_OPENSSL + err |= re_hprintf(pf, " OpenSSL: %s\n", + SSLeay_version(SSLEAY_VERSION)); +#endif + + return err; +} + + +static int cmd_config_print(struct re_printf *pf, void *unused) +{ + (void)unused; + return config_print(pf, conf_config()); +} + + +static int cmd_ua_debug(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_debug(pf, uag_current()); +} + + +static int cmd_play_file(struct re_printf *pf, void *arg) +{ + struct cmd_arg *carg = arg; + const char *filename = carg->prm; + int err; + + err = re_hprintf(pf, "playing audio file \"%s\" ..\n", filename); + if (err) + return err; + + err = play_file(NULL, baresip_player(), filename, 0); + if (err) { + warning("debug_cmd: play_file(%s) failed (%m)\n", + filename, err); + return err; + } + + return err; +} + + +static int reload_config(struct re_printf *pf, void *arg) +{ + int err; + (void)arg; + + err = re_hprintf(pf, "reloading config file ..\n"); + if (err) + return err; + + err = conf_configure(); + if (err) { + (void)re_hprintf(pf, "reload_config failed: %m\n", err); + return err; + } + + (void)re_hprintf(pf, "done\n"); + + return 0; +} + + +static const struct cmd debugcmdv[] = { +{"main", 0, 0, "Main loop debug", re_debug }, +{"config", 0, 0, "Print configuration", cmd_config_print }, +{"sipstat", 'i', 0, "SIP debug", ua_print_sip_status }, +{"modules", 0, 0, "Module debug", mod_debug }, +{"netstat", 'n', 0, "Network debug", cmd_net_debug }, +{"sysinfo", 's', 0, "System info", print_system_info }, +{"timers", 0, 0, "Timer debug", tmr_status }, +{"uastat", 'u', 0, "UA debug", cmd_ua_debug }, +{"memstat", 'y', 0, "Memory status", mem_status }, +{"play", 0, CMD_PRM, "Play audio file", cmd_play_file }, +{"conf_reload",0, 0, "Reload config file", reload_config }, +}; + + +static int module_init(void) +{ + int err; + + start_ticks = tmr_jiffies(); + (void)time(&start_time); + + err = cmd_register(baresip_commands(), + debugcmdv, ARRAY_SIZE(debugcmdv)); + + return err; +} + + +static int module_close(void) +{ + cmd_unregister(baresip_commands(), debugcmdv); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(debug_cmd) = { + "debug_cmd", + "application", + module_init, + module_close +}; diff --git a/modules/debug_cmd/module.mk b/modules/debug_cmd/module.mk new file mode 100644 index 0000000..3680314 --- /dev/null +++ b/modules/debug_cmd/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := debug_cmd +$(MOD)_SRCS += debug_cmd.c + +include mk/mod.mk diff --git a/modules/directfb/directfb.c b/modules/directfb/directfb.c new file mode 100644 index 0000000..0dfb122 --- /dev/null +++ b/modules/directfb/directfb.c @@ -0,0 +1,190 @@ +/** + * @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 { + const 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); +} + + +static int alloc(struct vidisp_st **stp, const 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 = 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, baresip_vidispl(), + "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..57fa045 --- /dev/null +++ b/modules/directfb/module.mk @@ -0,0 +1,14 @@ +# +# 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 += $(shell pkg-config --libs directfb) +$(MOD)_CFLAGS += $(shell pkg-config --cflags directfb \ + | sed -e 's/-I/-isystem/g') + +include mk/mod.mk diff --git a/modules/dshow/dshow.cpp b/modules/dshow/dshow.cpp new file mode 100644 index 0000000..d081f69 --- /dev/null +++ b/modules/dshow/dshow.cpp @@ -0,0 +1,536 @@ +/** + * @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 <commctrl.h> +#include <dshow.h> + +#pragma comment(lib, "strmiids.lib") + + +/** + * @defgroup dshow dshow + * + * Windows DirectShow video-source + * + * + * References: + * + * http://www.alsa-project.org/main/index.php/Main_Page + */ + + +/* a piece from Google WebM's qedit.h: + * + * https://code.google.com/p/webm/source/browse/qedit.h?repo=udpsample + */ +static const +IID IID_ISampleGrabber = { + 0x6b652fff, 0x11fe, 0x4fce, + { 0x92, 0xad, 0x02, 0x66, 0xb5, 0xd7, 0xc7, 0x8f } +}; + +static const +IID IID_ISampleGrabberCB = { + 0x0579154a, 0x2b53, 0x4994, + { 0xb0, 0xd0, 0xe7, 0x73, 0x14, 0x8e, 0xff, 0x85 } +}; + +#include "qedit.h" + +/* +const CLSID CLSID_SampleGrabber = { 0xc1f400a0, 0x3f08, 0x11d3, + { 0x9f, 0x0b, 0x00, 0x60, 0x08, 0x03, 0x9e, 0x37 } +}; +*/ + +class Grabber; + +struct vidsrc_st { + const 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) { + info("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) { + warning("dshow: 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; +} + + +static int alloc(struct vidsrc_st **stp, const 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 = 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)) { + warning("dshow: 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)) { + warning("dshow: alloc: IID_ICaptureGraphBuilder2: %ld\n", hr); + err = ENODEV; + goto out; + } + + hr = st->capture->SetFiltergraph(st->graph); + if (FAILED(hr)) { + warning("dshow: 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)) { + warning("dshow: 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)) { + warning("dshow: 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)) { + warning("dshow: alloc: RenderStream failed\n"); + err = ENODEV; + goto out; + } + + hr = st->graph->QueryInterface(IID_IMediaControl, + (void **) &st->mc); + if (FAILED(hr)) { + warning("dshow: alloc: IMediaControl failed\n"); + err = ENODEV; + goto out; + } + + hr = st->mc->Run(); + if (FAILED(hr)) { + warning("dshow: 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, baresip_vidsrcl(), + "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..1b78255 --- /dev/null +++ b/modules/dtls_srtp/dtls.c @@ -0,0 +1,51 @@ +/** + * @file dtls.c DTLS functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls) +{ + uint8_t md[20]; + unsigned int i; + int err = 0; + + if (!tls) + return EINVAL; + + err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA1, md, sizeof(md)); + if (err) + return err; + + for (i=0; i<sizeof(md); 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[32]; + unsigned int i; + int err = 0; + + if (!tls) + return EINVAL; + + err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA256, md, sizeof(md)); + if (err) + return err; + + for (i=0; i<sizeof(md); 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..b92ff06 --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.c @@ -0,0 +1,507 @@ +/** + * @file dtls_srtp.c DTLS-SRTP media encryption + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <string.h> +#include "dtls_srtp.h" + + +/** + * @defgroup dtls_srtp dtls_srtp + * + * DTLS-SRTP media encryption module + * + * This module implements end-to-end media encryption using DTLS-SRTP + * which is now mandatory for WebRTC endpoints. + * + * DTLS-SRTP can be enabled in ~/.baresip/accounts: + * + \verbatim + <sip:user@domain.com>;mediaenc=dtls_srtp + <sip:user@domain.com>;mediaenc=dtls_srtpf + <sip:user@domain.com>;mediaenc=srtp-mandf + \endverbatim + * + * + * Internally the protocol stack diagram looks something like this: + * + \verbatim + * application + * | + * | + * [DTLS] [SRTP] + * \ / + * \ / + * \ / + * \/ + * ( TURN/ICE ) + * | + * | + * [socket] + \endverbatim + * + */ + +struct menc_sess { + struct sdp_session *sdp; + bool offerer; + menc_error_h *errorh; + void *arg; +}; + +/* media */ +struct dtls_srtp { + struct comp compv[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 comp *c = &st->compv[i]; + + mem_deref(c->uh_srtp); + mem_deref(c->tls_conn); + mem_deref(c->dtls_sock); + mem_deref(c->app_sock); /* must be freed last */ + mem_deref(c->tx); + mem_deref(c->rx); + } + + mem_deref(st->sdpm); +} + + +static bool verify_fingerprint(const struct sdp_session *sess, + const struct sdp_media *media, + struct tls_conn *tc) +{ + struct pl hash; + uint8_t md_sdp[32], md_dtls[32]; + size_t sz_sdp = sizeof(md_sdp); + size_t sz_dtls; + enum tls_fingerprint type; + int err; + + if (sdp_fingerprint_decode(sdp_media_session_rattr(media, sess, + "fingerprint"), + &hash, md_sdp, &sz_sdp)) + return false; + + if (0 == pl_strcasecmp(&hash, "sha-1")) { + type = TLS_FINGERPRINT_SHA1; + sz_dtls = 20; + } + else if (0 == pl_strcasecmp(&hash, "sha-256")) { + type = TLS_FINGERPRINT_SHA256; + sz_dtls = 32; + } + else { + warning("dtls_srtp: unknown fingerprint '%r'\n", &hash); + return false; + } + + err = tls_peer_fingerprint(tc, type, md_dtls, sizeof(md_dtls)); + if (err) { + warning("dtls_srtp: could not get DTLS fingerprint (%m)\n", + err); + return false; + } + + if (sz_sdp != sz_dtls || 0 != memcmp(md_sdp, md_dtls, sz_sdp)) { + warning("dtls_srtp: %r fingerprint mismatch\n", &hash); + info("SDP: %w\n", md_sdp, sz_sdp); + info("DTLS: %w\n", md_dtls, sz_dtls); + return false; + } + + info("dtls_srtp: verified %r fingerprint OK\n", &hash); + + return true; +} + + +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-256 %H", + dtls_print_sha256_fingerprint, tls); + if (err) + goto out; + + out: + if (err) + mem_deref(sess); + else + *sessp = sess; + + return err; +} + + +static void dtls_estab_handler(void *arg) +{ + struct comp *comp = arg; + const struct dtls_srtp *ds = comp->ds; + enum srtp_suite suite; + uint8_t cli_key[30], srv_key[30]; + int err; + + if (!verify_fingerprint(ds->sess->sdp, ds->sdpm, comp->tls_conn)) { + warning("dtls_srtp: could not verify remote fingerprint\n"); + if (ds->sess->errorh) + ds->sess->errorh(EPIPE, ds->sess->arg); + return; + } + + err = tls_srtp_keyinfo(comp->tls_conn, &suite, + cli_key, sizeof(cli_key), + srv_key, sizeof(srv_key)); + if (err) { + warning("dtls_srtp: could not get SRTP keyinfo (%m)\n", err); + return; + } + + comp->negotiated = true; + + info("dtls_srtp: ---> DTLS-SRTP complete (%s/%s) Profile=%s\n", + sdp_media_name(ds->sdpm), + comp->is_rtp ? "RTP" : "RTCP", srtp_suite_name(suite)); + + err |= srtp_stream_add(&comp->tx, suite, + ds->active ? cli_key : srv_key, 30, true); + err |= srtp_stream_add(&comp->rx, suite, + ds->active ? srv_key : cli_key, 30, false); + + err |= srtp_install(comp); + if (err) { + warning("dtls_srtp: srtp_install: %m\n", err); + } + + /* todo: notify application that crypto is up and running */ +} + + +static void dtls_close_handler(int err, void *arg) +{ + struct comp *comp = arg; + + info("dtls_srtp: dtls-connection closed (%m)\n", err); + + comp->tls_conn = mem_deref(comp->tls_conn); + + if (!comp->negotiated) { + + if (comp->ds->sess->errorh) + comp->ds->sess->errorh(err, comp->ds->sess->arg); + } +} + + +static void dtls_conn_handler(const struct sa *peer, void *arg) +{ + struct comp *comp = arg; + int err; + (void)peer; + + info("dtls_srtp: incoming DTLS connect from %J\n", peer); + + err = dtls_accept(&comp->tls_conn, tls, comp->dtls_sock, + dtls_estab_handler, NULL, dtls_close_handler, comp); + if (err) { + warning("dtls_srtp: dtls_accept failed (%m)\n", err); + return; + } +} + + +static int component_start(struct comp *comp, struct sdp_media *sdpm) +{ + struct sa raddr; + int err = 0; + + if (!comp->app_sock || comp->negotiated || comp->dtls_sock) + return 0; + + if (comp->is_rtp) + raddr = *sdp_media_raddr(sdpm); + else + sdp_media_raddr_rtcp(sdpm, &raddr); + + err = dtls_listen(&comp->dtls_sock, NULL, + comp->app_sock, 2, LAYER_DTLS, + dtls_conn_handler, comp); + if (err) { + warning("dtls_srtp: dtls_listen failed (%m)\n", err); + return err; + } + + if (sa_isset(&raddr, SA_ALL)) { + + if (comp->ds->active && !comp->tls_conn) { + + err = dtls_connect(&comp->tls_conn, tls, + comp->dtls_sock, &raddr, + dtls_estab_handler, NULL, + dtls_close_handler, comp); + if (err) { + warning("dtls_srtp: dtls_connect()" + " failed (%m)\n", err); + return err; + } + } + } + + return err; +} + + +static int media_start(struct dtls_srtp *st, struct sdp_media *sdpm) +{ + int err = 0; + + if (st->started) + return 0; + + info("dtls_srtp: media=%s -- start DTLS %s\n", + sdp_media_name(sdpm), st->active ? "client" : "server"); + + if (!sdp_media_has_media(sdpm)) + return 0; + + err = component_start(&st->compv[0], sdpm); + + if (!st->mux) + err |= component_start(&st->compv[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->compv[0].app_sock = mem_ref(rtpsock); + st->compv[1].app_sock = mem_ref(rtcpsock); + + for (i=0; i<2; i++) + st->compv[i].ds = st; + + st->compv[0].is_rtp = true; + st->compv[1].is_rtp = false; + + err = sdp_media_set_alt_protos(st->sdpm, 4, + "RTP/SAVP", + "RTP/SAVPF", + "UDP/TLS/RTP/SAVP", + "UDP/TLS/RTP/SAVPF"); + if (err) + goto out; + + out: + if (err) { + mem_deref(st); + return err; + } + else + *mp = (struct menc_media *)st; + + setup: + st->mux = (rtpsock == rtcpsock) || (rtcpsock == NULL); + + setup = sdp_media_session_rattr(st->sdpm, st->sess->sdp, "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_media_session_rattr(st->sdpm, st->sess->sdp, + "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) +{ + struct list *mencl = baresip_mencl(); + int err; + + err = tls_alloc(&tls, TLS_METHOD_DTLSV1, NULL, NULL); + if (err) { + warning("dtls_srtp: failed to create DTLS context (%m)\n", + err); + return err; + } + + err = tls_set_selfsigned(tls, "dtls@baresip"); + if (err) { + warning("dtls_srtp: failed to self-sign certificate (%m)\n", + err); + return err; + } + + tls_set_verify_client(tls); + + err = tls_set_srtp(tls, srtp_profiles); + if (err) { + warning("dtls_srtp: failed to enable SRTP profile (%m)\n", + err); + return err; + } + + menc_register(mencl, &dtls_srtpf); + menc_register(mencl, &dtls_srtp); + menc_register(mencl, &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); + + 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..531134c --- /dev/null +++ b/modules/dtls_srtp/dtls_srtp.h @@ -0,0 +1,33 @@ +/** + * @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 comp { + const struct dtls_srtp *ds; /* parent */ + struct dtls_sock *dtls_sock; + struct tls_conn *tls_conn; + struct srtp_stream *tx; + struct srtp_stream *rx; + struct udp_helper *uh_srtp; + void *app_sock; + bool negotiated; + bool is_rtp; +}; + +/* dtls.c */ +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, enum srtp_suite suite, + const uint8_t *key, size_t key_size, bool tx); +int srtp_install(struct comp *comp); diff --git a/modules/dtls_srtp/module.mk b/modules/dtls_srtp/module.mk new file mode 100644 index 0000000..4fb3628 --- /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 srtp.c dtls.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/dtls_srtp/srtp.c b/modules/dtls_srtp/srtp.c new file mode 100644 index 0000000..c5ca52a --- /dev/null +++ b/modules/dtls_srtp/srtp.c @@ -0,0 +1,150 @@ +/** + * @file dtls_srtp/srtp.c Secure RTP + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "dtls_srtp.h" + + +struct srtp_stream { + struct srtp *srtp; +}; + + +/* + * 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 void destructor(void *arg) +{ + struct srtp_stream *s = arg; + + mem_deref(s->srtp); +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct comp *comp = arg; + (void)dst; + + if (!is_rtp_or_rtcp(mb)) + return false; + + if (is_rtcp_packet(mb)) { + *err = srtcp_encrypt(comp->tx->srtp, mb); + if (*err) { + warning("srtp: srtcp_encrypt failed (%m)\n", *err); + } + } + else { + *err = srtp_encrypt(comp->tx->srtp, mb); + if (*err) { + warning("srtp: srtp_encrypt failed (%m)\n", *err); + } + } + + + return *err ? true : false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct comp *comp = arg; + int err; + (void)src; + + if (!is_rtp_or_rtcp(mb)) + return false; + + if (is_rtcp_packet(mb)) { + err = srtcp_decrypt(comp->rx->srtp, mb); + } + else { + err = srtp_decrypt(comp->rx->srtp, mb); + } + + if (err) { + warning("srtp: recv: failed to decrypt %s-packet (%m)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", err); + return true; /* error - drop packet */ + } + + return false; /* continue processing */ +} + + +int srtp_stream_add(struct srtp_stream **sp, enum srtp_suite suite, + const uint8_t *key, size_t key_size, bool tx) +{ + struct srtp_stream *s; + int err = 0; + (void)tx; + + if (!sp || !key) + return EINVAL; + + s = mem_zalloc(sizeof(*s), destructor); + if (!s) + return ENOMEM; + + err = srtp_alloc(&s->srtp, suite, key, key_size, 0); + if (err) { + warning("srtp: srtp_alloc() failed (%m)\n", err); + goto out; + } + + out: + if (err) + mem_deref(s); + else + *sp = s; + + return err; +} + + +int srtp_install(struct comp *comp) +{ + return udp_register_helper(&comp->uh_srtp, comp->app_sock, + LAYER_SRTP, + send_handler, recv_handler, comp); +} diff --git a/modules/dtmfio/dtmfio.c b/modules/dtmfio/dtmfio.c new file mode 100644 index 0000000..8df0a22 --- /dev/null +++ b/modules/dtmfio/dtmfio.c @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* Copyright (c) 2014, Aaron Herting 'qwertos' <aaron@herting.cc> */ +/* Based upon code licensed under the same license by 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 COPYRIGHT HOLDERS AND CONTRIBUTORS */ +/* "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 COPYRIGHT */ +/* HOLDER OR CONTRIBUTORS 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. */ +/*************************************************************************/ + + +/** + * @defgroup dtmfio dtmfio + * + * DTMF input/output module + * + * + * # DTMFIO Module + * + * ## Description + * + * Writes received dtmf button presses to a FIFO located at /tmp/dtmf.out. + * + * Also, will write an 'E' when a call is established and an 'F' when the + * call is finished. + * + * ## To Do + * + * + Proper error handling + * + Using a dtmf.in file, be able to send DTMF signals + * + Use a filename specified by the user in the config file + * + Clean up build output so there aren't errors regarding unused vars + */ + +#include <unistd.h> +#include <stdio.h> +#include <re.h> +#include <baresip.h> +#include <sys/types.h> +#include <sys/stat.h> + + +static FILE *fd; +static const char *DTMF_OUT = "/tmp/dtmf.out"; + + +static void dtmf_handler(struct call *call, char key, void *arg) +{ + (void)call; + (void)arg; + + if ( key != 0 ) { + fprintf(fd, "%c", key); + fflush(fd); + } +} + + +static void ua_event_handler(struct ua *ua, + enum ua_event ev, + struct call *call, + const char *prm, + void *arg ) +{ + (void)ua; + (void)prm; + (void)arg; + + if ( ev == UA_EVENT_CALL_ESTABLISHED ) { + fprintf(fd, "E"); + fflush(fd); + call_set_handlers( call, NULL, dtmf_handler, NULL); + } + + if (ev == UA_EVENT_CALL_CLOSED ) { + fprintf(fd, "F"); + fflush(fd); + } +} + + +static int module_init(void) +{ + if ( mkfifo( DTMF_OUT, S_IWUSR | S_IRUSR ) ) { + int err = errno; + warning("Creation of the FIFO errored." + " This might cause issues. (%m)\n", err); + return err; + } + + fd = fopen( DTMF_OUT , "w+" ); + + if ( fd == 0 ){ + warning("Opening of the FIFO errored." + " This might cause issues.\n"); + } + + uag_event_register( ua_event_handler, NULL ); + + return 0; +} + + +static int module_close(void) +{ + uag_event_unregister(ua_event_handler); + + if (fd) { + fclose(fd); + fd = NULL; + } + + unlink(DTMF_OUT); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(dtmfio) = { + "dtmfio", + "application", + module_init, + module_close +}; diff --git a/modules/dtmfio/module.mk b/modules/dtmfio/module.mk new file mode 100644 index 0000000..cb1264b --- /dev/null +++ b/modules/dtmfio/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# Modified by Aaron Herting <aaron@herting.cc> +# + +MOD := dtmfio +$(MOD)_SRCS += dtmfio.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/echo/echo.c b/modules/echo/echo.c new file mode 100644 index 0000000..91e587c --- /dev/null +++ b/modules/echo/echo.c @@ -0,0 +1,158 @@ +/** + * @file echo.c Echo module + */ +#include <re.h> +#include <baresip.h> + +/** + * + * Multi Call Echo module + * + * REQUIRES: aubridge + * NOTE: This module is experimental. + * + */ + +struct session { + struct le le; + struct call *call_in; +}; + + +static struct list sessionl; + + +static void destructor(void *arg) +{ + struct session *sess = arg; + + debug("echo: session destroyed (in=%p)\n", + sess->call_in); + + list_unlink(&sess->le); + mem_deref(sess->call_in); +} + + +static void call_event_handler(struct call *call, enum call_event ev, + const char *str, void *arg) +{ + struct session *sess = arg; + (void)call; + + switch (ev) { + + case CALL_EVENT_CLOSED: + debug("echo: CALL_CLOSED: %s\n", str); + mem_deref(sess); + break; + + default: + break; + } +} + + +static void call_dtmf_handler(struct call *call, char key, void *arg) +{ + (void)arg; + + debug("echo: relaying DTMF event: key = '%c'\n", key ? key : '.'); + + call_send_digit(call, key); +} + + +static int new_session(struct call *call) +{ + struct session *sess; + char a[64]; + int err = 0; + + sess = mem_zalloc(sizeof(*sess), destructor); + if (!sess) + return ENOMEM; + + sess->call_in = call; + + re_snprintf(a, sizeof(a), "A-%x", sess); + + audio_set_devicename(call_audio(sess->call_in), a, a); + + call_set_handlers(sess->call_in, call_event_handler, + call_dtmf_handler, sess); + + list_append(&sessionl, &sess->le, sess); + ua_answer(uag_current(), NULL); + + if (err) + mem_deref(sess); + + return err; +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + int err; + (void)prm; + (void)arg; + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + debug("echo: CALL_INCOMING: peer=%s --> local=%s\n", + call_peeruri(call), + call_localuri(call)); + + err = new_session(call); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + default: + break; + } +} + + +static int module_init(void) +{ + int err; + + list_init(&sessionl); + + err = uag_event_register(ua_event_handler, 0); + if (err) + return err; + + debug("echo: module loaded\n"); + + return 0; +} + + +static int module_close(void) +{ + debug("echo: module closing..\n"); + + if (!list_isempty(&sessionl)) { + + info("echo: flushing %u sessions\n", list_count(&sessionl)); + list_flush(&sessionl); + } + + uag_event_unregister(ua_event_handler); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(echo) = { + "echo", + "application", + module_init, + module_close +}; diff --git a/modules/echo/module.mk b/modules/echo/module.mk new file mode 100644 index 0000000..0c7ee6f --- /dev/null +++ b/modules/echo/module.mk @@ -0,0 +1,9 @@ +# +# module.mk +# +# + +MOD := echo +$(MOD)_SRCS += echo.c + +include mk/mod.mk diff --git a/modules/evdev/evdev.c b/modules/evdev/evdev.c new file mode 100644 index 0000000..7d27f1e --- /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" + + +/** + * @defgroup evdev evdev + * + * User-Interface (UI) module using the Linux input subsystem. + * + * The following options can be configured: + * + \verbatim + evdev_device /dev/input/event0 # Name of the input device to use + \endverbatim + */ + + +struct ui_st { + int fd; +}; + + +static struct ui_st *evdev; +static char evdev_device[64] = "/dev/input/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); +} + + +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) +{ + static struct re_printf pf_stderr = {stderr_handler, NULL}; + (void)st; + + ui_input_key(baresip_uis(), ascii, &pf_stderr); +} + + +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) { + warning("evdev: fd handler: FD_EXCEPT - device unplugged?\n"); + evdev_close(st); + return; + } + + n = read(st->fd, evv, sizeof(evv)); + + if (n < (int) sizeof(struct input_event)) { + warning("evdev: 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]; + + 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) { + warning("evdev: unhandled key code %u\n", + ev->code); + } + else + reportkey(st, ascii); + modifier = 0; + } + else if (0 == ev->value) { + reportkey(st, KEYCODE_REL); + } + } +} + + +static int evdev_alloc(struct ui_st **stp, const char *dev) +{ + struct ui_st *st; + int err = 0; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), evdev_destructor); + if (!st) + return ENOMEM; + + st->fd = open(dev, O_RDWR); + if (st->fd < 0) { + err = errno; + warning("evdev: failed to open device '%s' (%m)\n", dev, err); + 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)) { + warning("evdev: 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; + + 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) { + warning("evdev: output: write fd=%d (%m)\n", st->fd, errno); + } + + return errno; +} + + +static int evdev_output(const char *str) +{ + struct ui_st *st = evdev; + int err = 0; + + if (!st || !str) + return EINVAL; + + while (*str) { + switch (*str++) { + + case '\a': + err |= buzz(st, 1); + break; + + default: + err |= buzz(st, 0); + break; + } + } + + return err; +} + + +static struct ui ui_evdev = { + .name = "evdev", + .outputh = evdev_output +}; + + +static int module_init(void) +{ + int err; + + conf_get_str(conf_cur(), "evdev_device", + evdev_device, sizeof(evdev_device)); + + err = evdev_alloc(&evdev, evdev_device); + if (err) + return err; + + ui_register(baresip_uis(), &ui_evdev); + + return 0; +} + + +static int module_close(void) +{ + ui_unregister(&ui_evdev); + 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..4c6cc77 --- /dev/null +++ b/modules/evdev/print.c @@ -0,0 +1,513 @@ +/** + * @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.h> +#include <baresip.h> +#include "print.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"); + } + + info("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) { + 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/fakevideo/fakevideo.c b/modules/fakevideo/fakevideo.c new file mode 100644 index 0000000..e0552d6 --- /dev/null +++ b/modules/fakevideo/fakevideo.c @@ -0,0 +1,199 @@ +/** + * @file fakevideo.c Fake video source and video display + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#define _BSD_SOURCE 1 +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup fakevideo fakevideo + * + * Fake video source and display module + * + * This module can be used to generate fake video input frames, and to + * send output video frames to a fake non-existant display. + * + * Example config: + \verbatim + video_source fakevideo,nil + video_display fakevideo,nil + \endverbatim + */ + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + struct vidframe *frame; + pthread_t thread; + bool run; + int fps; + vidsrc_frame_h *frameh; + void *arg; +}; + +struct vidisp_st { + const struct vidisp *vd; /* inheritance */ +}; + + +static struct vidsrc *vidsrc; +static struct vidisp *vidisp; + + +static void *read_thread(void *arg) +{ + struct vidsrc_st *st = arg; + uint64_t ts = tmr_jiffies(); + + while (st->run) { + + if (tmr_jiffies() < ts) { + sys_msleep(4); + continue; + } + + st->frameh(st->frame, st->arg); + + ts += (1000/st->fps); + } + + return NULL; +} + + +static void src_destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + mem_deref(st->frame); +} + + +static void disp_destructor(void *arg) +{ + struct vidisp_st *st = arg; + (void)st; +} + + +static int src_alloc(struct vidsrc_st **stp, const 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), src_destructor); + if (!st) + return ENOMEM; + + st->vs = vs; + st->fps = prm->fps; + st->frameh = frameh; + st->arg = arg; + + err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, 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 disp_alloc(struct vidisp_st **stp, const struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + if (!stp || !vd) + return EINVAL; + + st = mem_zalloc(sizeof(*st), disp_destructor); + if (!st) + return ENOMEM; + + st->vd = vd; + + *stp = st; + + return 0; +} + + +static int display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + (void)st; + (void)title; + (void)frame; + + return 0; +} + + +static int module_init(void) +{ + int err = 0; + err |= vidsrc_register(&vidsrc, baresip_vidsrcl(), + "fakevideo", src_alloc, NULL); + err |= vidisp_register(&vidisp, baresip_vidispl(), + "fakevideo", disp_alloc, NULL, + display, NULL); + return err; +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + vidisp = mem_deref(vidisp); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(fakevideo) = { + "fakevideo", + "fakevideo", + module_init, + module_close +}; diff --git a/modules/fakevideo/module.mk b/modules/fakevideo/module.mk new file mode 100644 index 0000000..fbe1e63 --- /dev/null +++ b/modules/fakevideo/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := fakevideo +$(MOD)_SRCS += fakevideo.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/g711/g711.c b/modules/g711/g711.c new file mode 100644 index 0000000..7c01b61 --- /dev/null +++ b/modules/g711/g711.c @@ -0,0 +1,133 @@ +/** + * @file g711.c G.711 Audio Codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup g711 g711 + * + * The G.711 audio codec + */ + + +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, 8000, 1, NULL, + NULL, pcmu_encode, NULL, pcmu_decode, NULL, NULL, NULL +}; + +static struct aucodec pcma = { + LE_INIT, "8", "PCMA", 8000, 8000, 1, NULL, + NULL, pcma_encode, NULL, pcma_decode, NULL, NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(baresip_aucodecl(), &pcmu); + aucodec_register(baresip_aucodecl(), &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..16d1f98 --- /dev/null +++ b/modules/g722/g722.c @@ -0,0 +1,191 @@ +/** + * @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> + + +/** + * @defgroup g722 g722 + * + * The G.722 audio codec + * + * ## 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. + + ## Reference: + + http://www.soft-switch.org/spandsp-modules.html + + */ + + +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)) { + 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)) { + 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) { + return EPROTO; + } + else if (n > (int)*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) + return EPROTO; + + *sampc = n; + + return 0; +} + + +static struct aucodec g722 = { + LE_INIT, "9", "G722", 16000, 8000, 1, NULL, + encode_update, encode, + decode_update, decode, NULL, + NULL, NULL +}; + + +static int module_init(void) +{ + aucodec_register(baresip_aucodecl(), &g722); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&g722); + return 0; +} + + +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..0f7155f --- /dev/null +++ b/modules/g7221/decode.c @@ -0,0 +1,70 @@ +/** + * @file g7221/decode.c G.722.1 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + +#define G722_1_EXPOSE_INTERNAL_STRUCTURES + +#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..8345f5f --- /dev/null +++ b/modules/g7221/encode.c @@ -0,0 +1,71 @@ +/** + * @file g7221/encode.c G.722.1 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + +#define G722_1_EXPOSE_INTERNAL_STRUCTURES + +#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..3e2de02 --- /dev/null +++ b/modules/g7221/g7221.c @@ -0,0 +1,50 @@ +/** + * @file g7221.c G.722.1 Audio 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, + .crate = 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(baresip_aucodecl(), (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..7bfc62c --- /dev/null +++ b/modules/g7221/sdp.c @@ -0,0 +1,54 @@ +/** + * @file g7221/sdp.c G.722.1 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..fd4e462 --- /dev/null +++ b/modules/g726/g726.c @@ -0,0 +1,208 @@ +/** + * @file g726.c G.726 Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include <spandsp.h> + + +/** + * @defgroup g726 g726 + * + * The G.726 audio codec + */ + + +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, 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 40000 + }, + { + { + LE_INIT, 0, "G726-32", 8000, 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 32000 + }, + { + { + LE_INIT, 0, "G726-24", 8000, 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 24000 + }, + { + { + LE_INIT, 0, "G726-16", 8000, 8000, 1, NULL, + encode_update, encode, decode_update, decode, 0, 0, 0 + }, + 16000 + } +}; + + +static int module_init(void) +{ + struct list *aucodecl = baresip_aucodecl(); + size_t i; + + for (i=0; i<ARRAY_SIZE(g726); i++) + aucodec_register(aucodecl, (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..be225d8 --- /dev/null +++ b/modules/gsm/gsm.c @@ -0,0 +1,178 @@ +/** + * @file gsm.c GSM Audio Codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <gsm.h> /* please report if you have problems finding this file */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup gsm gsm + * + * The GSM audio codec + */ + + +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, 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(baresip_aucodecl(), &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..92219dd --- /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 +$(MOD)_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..138b3ed --- /dev/null +++ b/modules/gst/dump.c @@ -0,0 +1,65 @@ +/** + * @file gst/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..d729b70 --- /dev/null +++ b/modules/gst/gst.c @@ -0,0 +1,454 @@ +/** + * @file gst/gst.c Gstreamer playbin pipeline + * + * Copyright (C) 2010 - 2015 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" + + +/** + * @defgroup gst gst + * + * Audio source module using gstreamer as input + * + * The module 'gst' is using the Gstreamer framework to play external + * media and provide this as an internal audio source. + * + * Example config: + \verbatim + audio_source gst,http://relay.slayradio.org:8000/ + \endverbatim + */ + + +/** + * 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 { + const 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 */ + size_t psize; /**< Packet size in bytes */ + size_t sampc; + + /* 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. */ + 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: + /* 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); + + warning("gst: Error: %d(%m) message=%s\n", err->code, + err->code, err->message); + warning("gst: 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)) { + info("gst: 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) { + warning("gst: expected %u Hz (got %u Hz)\n", st->prm.srate, + rate); + } + if (st->prm.ch != channels) { + warning("gst: expected %d channels (got %d)\n", + st->prm.ch, channels); + } + if (16 != width) { + warning("gst: expected 16-bit width (got %d)\n", width); + } + if (!sign) { + warning("gst: expected signed 16-bit format\n"); + } +} + + +static void play_packet(struct ausrc_st *st) +{ + int16_t buf[st->sampc]; + + /* timed read from audio-buffer */ + if (aubuf_get_samp(st->aubuf, st->prm.ptime, buf, st->sampc)) + return; + + /* call read handler */ + if (st->rh) + st->rh(buf, st->sampc, 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) { + warning("gst: 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) { + warning("gst: failed to create pipeline element\n"); + return ENOMEM; + } + + /********************* Player BIN **************************/ + + st->source = gst_element_factory_make("playbin", "source"); + if (!st->source) { + warning("gst: 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) { + warning("gst: failed to create capsfilter element\n"); + return ENOMEM; + } + + set_caps(st); + + st->sink = gst_element_factory_make("fakesink", "sink"); + if (!st->sink) { + warning("gst: 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); +} + + +static int gst_alloc(struct ausrc_st **stp, const 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; + + if (!device) + device = gst_uri; + + if (!prm) + return EINVAL; + if (prm->fmt != AUFMT_S16LE) + return ENOTSUP; + + st = mem_zalloc(sizeof(*st), gst_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = str_dup(&st->uri, device); + if (err) + goto out; + + st->prm = *prm; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->psize = 2 * st->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(); + + info("gst: init: %s\n", s); + + g_free(s); + + return ausrc_register(&ausrc, baresip_ausrcl(), "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..37faac9 --- /dev/null +++ b/modules/gst/gst.h @@ -0,0 +1,9 @@ +/** + * @file gst/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..61de6f6 --- /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 += $(shell pkg-config --libs gstreamer-0.10) +$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-0.10) + +include mk/mod.mk diff --git a/modules/gst1/gst.c b/modules/gst1/gst.c new file mode 100644 index 0000000..380334a --- /dev/null +++ b/modules/gst1/gst.c @@ -0,0 +1,464 @@ +/** + * @file gst1/gst.c Gstreamer 1.0 playbin pipeline + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <pthread.h> +#include <gst/gst.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup gst1 gst1 + * + * Audio source module using gstreamer 1.0 as input + * + * The module 'gst1' is using the Gstreamer framework to play external + * media and provide this as an internal audio source. + * + * Example config: + \verbatim + audio_source gst,http://relay.slayradio.org:8000/ + \endverbatim + */ + + +/** + * 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 { + const 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 */ + size_t psize; /**< Packet size in bytes */ + size_t sampc; + + /* 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. */ + 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: + /* 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); + + warning("gst: Error: %d(%m) message=\"%s\"\n", err->code, + err->code, err->message); + warning("gst: 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)) { + info("gst: 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) { + warning("gst: expected %u Hz (got %u Hz)\n", st->prm.srate, + rate); + } + if (st->prm.ch != channels) { + warning("gst: expected %d channels (got %d)\n", + st->prm.ch, channels); + } + if (16 != width) { + warning("gst: expected 16-bit width (got %d)\n", width); + } + if (!sign) { + warning("gst: expected signed 16-bit format\n"); + } +} + + +static void play_packet(struct ausrc_st *st) +{ + int16_t buf[st->sampc]; + + /* timed read from audio-buffer */ + if (aubuf_get_samp(st->aubuf, st->prm.ptime, buf, st->sampc)) + return; + + /* call read handler */ + if (st->rh) + st->rh(buf, st->sampc, st->arg); +} + + +/* Expected format: 16-bit signed PCM */ +static void packet_handler(struct ausrc_st *st, GstBuffer *buffer) +{ + GstMapInfo info; + int err; + + if (!st->run) + return; + + /* NOTE: When streaming from files, the buffer will be filled up + * pretty quickly.. + */ + + if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) { + warning("gst: gst_buffer_map failed\n"); + return; + } + + err = aubuf_write(st->aubuf, info.data, info.size); + if (err) { + warning("gst: aubuf_write: %m\n", err); + } + + gst_buffer_unmap(buffer, &info); + + /* 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; + GstCaps *caps; + (void)fakesink; + + caps = gst_pad_get_current_caps(pad); + + format_check(st, gst_caps_get_structure(caps, 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", + "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); + + 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) { + warning("gst: failed to create pipeline element\n"); + return ENOMEM; + } + + /********************* Player BIN **************************/ + + st->source = gst_element_factory_make("playbin", "source"); + if (!st->source) { + warning("gst: 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) { + warning("gst: failed to create capsfilter element\n"); + return ENOMEM; + } + + set_caps(st); + + st->sink = gst_element_factory_make("fakesink", "sink"); + if (!st->sink) { + warning("gst: 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_static_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); +} + + +static int gst_alloc(struct ausrc_st **stp, const 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; + + if (!device) + device = gst_uri; + + if (!prm) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("gst: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), gst_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = str_dup(&st->uri, device); + if (err) + goto out; + + st->prm = *prm; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->psize = 2 * st->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(); + + info("gst: init: %s\n", s); + + g_free(s); + + return ausrc_register(&ausrc, baresip_ausrcl(), "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(gst1) = { + "gst1", + "sound", + mod_gst_init, + mod_gst_close +}; diff --git a/modules/gst1/module.mk b/modules/gst1/module.mk new file mode 100644 index 0000000..5e67f69 --- /dev/null +++ b/modules/gst1/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst1 +$(MOD)_SRCS += gst.c +$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-1.0) +$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-1.0) +$(MOD)_CFLAGS += -Wno-cast-align + +include mk/mod.mk diff --git a/modules/gst_video/encode.c b/modules/gst_video/encode.c new file mode 100644 index 0000000..b6dfe8a --- /dev/null +++ b/modules/gst_video/encode.c @@ -0,0 +1,540 @@ +/** + * @file gst_video/encode.c Video codecs using Gstreamer video pipeline + * + * Copyright (C) 2010 - 2013 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ +#define _DEFAULT_SOURCE 1 +#define __USE_POSIX199309 +#define _BSD_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/app/gstappsrc.h> +#include "gst_video.h" + + +struct videnc_state { + + struct vidsz size; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + + 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; + + videnc_packet_h *pkth; + void *pkth_arg; + + /* Gstreamer */ + GstElement *pipeline, *source, *sink; + GstBus *bus; + gulong need_data_handler; + gulong enough_data_handler; + gulong new_buffer_handler; + bool gst_inited; + + /* Main loop thread. */ + int run; + pthread_t tid; + + /* Thread synchronization. */ + pthread_mutex_t mutex; + pthread_cond_t wait; + int bwait; +}; + + +static void gst_encoder_close(struct videnc_state *st); + + +static void internal_bus_watch_handler(struct videnc_state *st) +{ + GError *err; + gchar *d; + GstMessage *msg = gst_bus_pop(st->bus); + + if (!msg) { + /* take a nap (300ms) */ + usleep(300 * 1000); + return; + } + + switch (GST_MESSAGE_TYPE(msg)) { + + case GST_MESSAGE_EOS: + + /* XXX decrementing repeat count? */ + + /* Re-start stream */ + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + break; + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &d); + + warning("gst_video: Error: %d(%m) message=%s\n", err->code, + err->code, err->message); + warning("gst_video: Debug: %s\n", d); + + g_free(d); + g_error_free(err); + + st->run = FALSE; + break; + + default: + break; + } + + gst_message_unref(msg); +} + + +static void *internal_thread(void *arg) +{ + struct videnc_state *st = arg; + + /* Now set to playing and iterate. */ + debug("gst_video: Setting pipeline to PLAYING\n"); + + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + + while (st->run) { + internal_bus_watch_handler(st); + } + + debug("gst_video: Pipeline thread was stopped.\n"); + + return NULL; +} + + +static void internal_appsrc_start_feed(GstElement * pipeline, guint size, + struct videnc_state *st) +{ + (void)pipeline; + (void)size; + + if (!st) + return; + + pthread_mutex_lock(&st->mutex); + st->bwait = FALSE; + pthread_cond_signal(&st->wait); + pthread_mutex_unlock(&st->mutex); +} + + +static void internal_appsrc_stop_feed(GstElement * pipeline, + struct videnc_state *st) +{ + (void)pipeline; + + if (!st) + return; + + pthread_mutex_lock(&st->mutex); + st->bwait = TRUE; + pthread_mutex_unlock(&st->mutex); +} + + +/* The appsink has received a buffer */ +static void internal_appsink_new_buffer(GstElement *sink, + struct videnc_state *st) +{ + GstBuffer *buffer; + + if (!st) + return; + + /* Retrieve the buffer */ + g_signal_emit_by_name(sink, "pull-buffer", &buffer); + + if (buffer) { + GstClockTime ts; + uint32_t rtp_ts; + + guint8 *data = GST_BUFFER_DATA(buffer); + guint size = GST_BUFFER_SIZE(buffer); + + ts = GST_BUFFER_TIMESTAMP(buffer); + + rtp_ts = (uint32_t)((90000ULL*ts) / 1000000000UL ); + + h264_packetize(rtp_ts, data, size, st->pktsize, + st->pkth, st->pkth_arg); + + gst_buffer_unref(buffer); + } +} + + +/** + * Set up the Gstreamer pipeline. Appsrc gets raw frames, and appsink takes + * encoded frames. + * + * The pipeline looks like this: + * + * <pre> + * .--------. .-----------. .----------. + * | appsrc | | x264enc | | appsink | + * | .----| |----. .---| |----. | + * | |src |-->|sink| |src|-->|sink|-----+-->handoff + * | '----| |----' '---| |----' | handler + * '--------' '-----------' '----------' + * </pre> + */ +static int gst_encoder_init(struct videnc_state *st, int width, int height, + int framerate, int bitrate) +{ + GError* gerror = NULL; + char pipeline[1024]; + int err = 0; + + gst_encoder_close(st); + + snprintf(pipeline, sizeof(pipeline), + "appsrc name=source is-live=TRUE block=TRUE do-timestamp=TRUE ! " + "videoparse width=%d height=%d format=i420 framerate=%d/1 ! " + "x264enc byte-stream=TRUE rc-lookahead=0" + " sync-lookahead=0 bitrate=%d ! " + "appsink name=sink emit-signals=TRUE drop=TRUE", + width, height, framerate, bitrate / 1000 /* kbit/s */); + + debug("gst_video: format: yu12 = yuv420p = i420\n"); + + /* Initialize pipeline. */ + st->pipeline = gst_parse_launch(pipeline, &gerror); + if (gerror) { + warning("gst_video: launch error: %s: %s\n", + gerror->message, pipeline); + err = gerror->code; + g_error_free(gerror); + goto out; + } + + st->source = gst_bin_get_by_name(GST_BIN(st->pipeline), "source"); + st->sink = gst_bin_get_by_name(GST_BIN(st->pipeline), "sink"); + if (!st->source || !st->sink) { + warning("gst_video: failed to get source or sink" + " pipeline elements\n"); + err = ENOMEM; + goto out; + } + + /* Configure appsource */ + st->need_data_handler = g_signal_connect(st->source, "need-data", + G_CALLBACK(internal_appsrc_start_feed), st); + st->enough_data_handler = g_signal_connect(st->source, "enough-data", + G_CALLBACK(internal_appsrc_stop_feed), st); + + /* Configure appsink. */ + st->new_buffer_handler = g_signal_connect(st->sink, "new-buffer", + G_CALLBACK(internal_appsink_new_buffer), st); + + /********************* Misc **************************/ + + /* Bus watch */ + st->bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline)); + + /********************* Thread **************************/ + + /* Synchronization primitives. */ + pthread_mutex_init(&st->mutex, NULL); + pthread_cond_init(&st->wait, NULL); + st->bwait = FALSE; + + err = gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + if (GST_STATE_CHANGE_FAILURE == err) { + g_warning("set state returned GST_STATE_CHANGE_FAILUER\n"); + } + + /* Launch thread with gstreamer loop. */ + st->run = true; + err = pthread_create(&st->tid, NULL, internal_thread, st); + if (err) { + st->run = false; + goto out; + } + + st->gst_inited = true; + + out: + return err; +} + + +static int gst_video_push(struct videnc_state *st, const uint8_t *src, + size_t size) +{ + GstBuffer *buffer; + int ret = 0; + + if (!st) { + return EINVAL; + } + + if (!size) { + warning("gst_video: push: eos returned %d at %d\n", + ret, __LINE__); + gst_app_src_end_of_stream((GstAppSrc *)st->source); + return ret; + } + + /* Wait "start feed". */ + pthread_mutex_lock(&st->mutex); + if (st->bwait) { +#define WAIT_TIME_SECONDS 5 + struct timespec ts; + struct timeval tp; + gettimeofday(&tp, NULL); + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec += WAIT_TIME_SECONDS; + /* Wait. */ + ret = pthread_cond_timedwait(&st->wait, &st->mutex, &ts); + if (ETIMEDOUT == ret) { + warning("gst_video: Raw frame is lost" + " because of timeout\n"); + return ret; + } + } + pthread_mutex_unlock(&st->mutex); + + /* Create a new empty buffer */ + buffer = gst_buffer_new(); + GST_BUFFER_MALLOCDATA(buffer) = (guint8 *)src; + GST_BUFFER_SIZE(buffer) = (guint)size; + GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer); + + ret = gst_app_src_push_buffer((GstAppSrc *)st->source, buffer); + + if (ret != GST_FLOW_OK) { + warning("gst_video: push buffer returned" + " %d for %d bytes \n", ret, size); + return ret; + } + + return ret; +} + + +static void gst_encoder_close(struct videnc_state *st) +{ + if (!st) + return; + + st->gst_inited = false; + + /* Remove asynchronous callbacks to prevent using gst_video_t + context ("st") after releasing. */ + if (st->source) { + g_signal_handler_disconnect(st->source, + st->need_data_handler); + g_signal_handler_disconnect(st->source, + st->enough_data_handler); + } + if (st->sink) { + g_signal_handler_disconnect(st->sink, st->new_buffer_handler); + } + + /* Stop thread. */ + if (st->run) { + st->run = false; + pthread_join(st->tid, NULL); + } + + if (st->source) { + gst_object_unref(GST_OBJECT(st->source)); + st->source = NULL; + } + if (st->sink) { + gst_object_unref(GST_OBJECT(st->sink)); + st->sink = NULL; + } + if (st->bus) { + gst_object_unref(GST_OBJECT(st->bus)); + st->bus = NULL; + } + + if (st->pipeline) { + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(st->pipeline)); + st->pipeline = NULL; + } +} + + +static void encode_destructor(void *arg) +{ + struct videnc_state *st = arg; + + gst_encoder_close(st); +} + + +static int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name, + const struct pl *val) +{ + if (0 == pl_strcasecmp(name, "packetization-mode")) { + st->h264.packetization_mode = pl_u32(val); + + if (st->h264.packetization_mode != 0) { + warning("gst_video: illegal packetization-mode %u\n", + st->h264.packetization_mode); + return EPROTO; + } + } + else if (0 == pl_strcasecmp(name, "profile-level-id")) { + struct pl prof = *val; + if (prof.l != 6) { + warning("gst_video: invalid profile-level-id (%r)\n", + val); + return EPROTO; + } + + prof.l = 2; + st->h264.profile_idc = pl_x32(&prof); prof.p += 2; + st->h264.profile_iop = pl_x32(&prof); prof.p += 2; + st->h264.level_idc = pl_x32(&prof); + } + else if (0 == pl_strcasecmp(name, "max-fs")) { + st->h264.max_fs = pl_u32(val); + } + else if (0 == pl_strcasecmp(name, "max-smbps")) { + st->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; + + (void)decode_sdpparam_h264(st, name, val); +} + + +int gst_video_encode_update(struct videnc_state **vesp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *ves; + int err = 0; + + if (!vesp || !vc || !prm) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), encode_destructor); + if (!ves) + return ENOMEM; + + *vesp = ves; + } + else { + if (ves->gst_inited && (ves->bitrate != prm->bitrate || + ves->pktsize != prm->pktsize || + ves->fps != prm->fps)) { + gst_encoder_close(ves); + } + } + + if (str_isset(fmtp)) { + struct pl sdp_fmtp; + + pl_set_str(&sdp_fmtp, fmtp); + + fmt_param_apply(&sdp_fmtp, param_handler, ves); + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + ves->pkth = pkth; + ves->pkth_arg = arg; + + info("gst_video: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, prm->fps, prm->bitrate, prm->pktsize); + + return err; +} + + +int gst_video_encode(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + uint8_t *data; + size_t size; + int height; + int err; + + if (!st || !frame || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + if (!st->gst_inited || !vidsz_cmp(&st->size, &frame->size)) { + + err = gst_encoder_init(st, frame->size.w, frame->size.h, + st->fps, st->bitrate); + + if (err) { + warning("gst_video codec: gst_video_alloc failed\n"); + return err; + } + + /* To detect if requested size was changed. */ + st->size = frame->size; + } + + if (update) { + debug("gst_video: gstreamer picture update" + ", it's not implemented...\n"); + } + + height = frame->size.h; + + /* NOTE: I420 (YUV420P): hardcoded. */ + size = frame->linesize[0] * height + + frame->linesize[1] * height * 0.5 + + frame->linesize[2] * height * 0.5; + + data = malloc(size); /* XXX: memory-leak ? */ + if (!data) + return ENOMEM; + + size = 0; + + /* XXX: avoid memcpy here ? */ + memcpy(&data[size], frame->data[0], frame->linesize[0] * height); + size += frame->linesize[0] * height; + memcpy(&data[size], frame->data[1], frame->linesize[1] * height * 0.5); + size += frame->linesize[1] * height * 0.5; + memcpy(&data[size], frame->data[2], frame->linesize[2] * height * 0.5); + size += frame->linesize[2] * height * 0.5; + + return gst_video_push(st, data, size); +} diff --git a/modules/gst_video/gst_video.c b/modules/gst_video/gst_video.c new file mode 100644 index 0000000..8807caf --- /dev/null +++ b/modules/gst_video/gst_video.c @@ -0,0 +1,64 @@ +/** + * @file gst_video/gst_video.c Video codecs using Gstreamer + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst_video.h" + + +/** + * @defgroup gst_video gst_video + * + * This module implements video codecs using Gstreamer. + * + * Currently only H.264 encoding is supported, but this can be extended + * if needed. No decoding is done by this module, so that must be done by + * another video-codec module. + * + * Thanks to Victor Sergienko and Fadeev Alexander for the + * initial version, which was based on avcodec module. + */ + + +static struct vidcodec h264 = { + .name = "H264", + .variant = "packetization-mode=0", + .encupdh = gst_video_encode_update, + .ench = gst_video_encode, + .fmtp_ench = gst_video_fmtp_enc, + .fmtp_cmph = gst_video_fmtp_cmp, +}; + + +static int module_init(void) +{ + gst_init(NULL, NULL); + + vidcodec_register(baresip_vidcodecl(), &h264); + + info("gst_video: using gstreamer H.264 encoder\n"); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&h264); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst_video) = { + "gst_video", + "vidcodec", + module_init, + module_close +}; diff --git a/modules/gst_video/gst_video.h b/modules/gst_video/gst_video.h new file mode 100644 index 0000000..b1edd59 --- /dev/null +++ b/modules/gst_video/gst_video.h @@ -0,0 +1,24 @@ +/** + * @file gst_video/gst_video.h Gstreamer video pipeline -- internal API + * + * Copyright (C) 2010 - 2014 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + + +/* Encode */ +struct videnc_state; + +int gst_video_encode_update(struct videnc_state **vesp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int gst_video_encode(struct videnc_state *st, bool update, + const struct vidframe *frame); + + +/* SDP */ +uint32_t gst_video_h264_packetization_mode(const char *fmtp); +int gst_video_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool gst_video_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data); diff --git a/modules/gst_video/module.mk b/modules/gst_video/module.mk new file mode 100644 index 0000000..bb233f4 --- /dev/null +++ b/modules/gst_video/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst_video +$(MOD)_SRCS += gst_video.c encode.c sdp.c +$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-0.10 gstreamer-app-0.10) +$(MOD)_CFLAGS += \ + $(shell pkg-config --cflags gstreamer-0.10 gstreamer-app-0.10) + +include mk/mod.mk diff --git a/modules/gst_video/sdp.c b/modules/gst_video/sdp.c new file mode 100644 index 0000000..294d319 --- /dev/null +++ b/modules/gst_video/sdp.c @@ -0,0 +1,56 @@ +/** + * @file gst_video/sdp.c H.264 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "gst_video.h" + + +static const uint8_t h264_level_idc = 0x0c; + + +uint32_t gst_video_h264_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; +} + + +int gst_video_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); +} + + +bool gst_video_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data) +{ + (void)data; + + return gst_video_h264_packetization_mode(fmtp1) == + gst_video_h264_packetization_mode(fmtp2); +} diff --git a/modules/gst_video1/encode.c b/modules/gst_video1/encode.c new file mode 100644 index 0000000..d0a3d44 --- /dev/null +++ b/modules/gst_video1/encode.c @@ -0,0 +1,613 @@ +/** + * @file gst_video/encode.c Video codecs using Gstreamer video pipeline + * + * Copyright (C) 2010 - 2013 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + * Copyright (C) 2015 Thomas Strobel + */ + +#define __USE_POSIX199309 +#define _DEFAULT_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/app/gstappsrc.h> +#include <gst/app/gstappsink.h> +#include "gst_video.h" + + +struct videnc_state { + + struct { + struct vidsz size; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + } encoder; + + 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; + + videnc_packet_h *pkth; + void *arg; + + /* Gstreamer */ + struct { + bool valid; + + GstElement *pipeline; + GstAppSrc *source; + + GstAppSrcCallbacks appsrcCallbacks; + GstAppSinkCallbacks appsinkCallbacks; + + struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + int flag; + } eos; + + /* Thread synchronization. */ + struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + /* 0: no-wait, 1: wait, -1: pipeline destroyed */ + int flag; + } wait; + } streamer; +}; + + +static void appsrc_need_data_cb(GstAppSrc *src, guint size, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + (void)size; + + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 1){ + st->streamer.wait.flag = 0; + pthread_cond_signal(&st->streamer.wait.cond); + } + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +static void appsrc_enough_data_cb(GstAppSrc *src, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 0) + st->streamer.wait.flag = 1; + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +static void appsrc_destroy_notify_cb(struct videnc_state *st) +{ + pthread_mutex_lock(&st->streamer.wait.mutex); + st->streamer.wait.flag = -1; + pthread_cond_signal(&st->streamer.wait.cond); + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +/* The appsink has received a sample */ +static GstFlowReturn appsink_new_sample_cb(GstAppSink *sink, + gpointer user_data) +{ + struct videnc_state *st = user_data; + GstSample *sample; + GstBuffer *buffer; + GstMapInfo info; + guint8 *data; + gsize size; + + /* Retrieve the sample */ + sample = gst_app_sink_pull_sample(sink); + + if (sample) { + GstClockTime ts; + uint32_t rtp_ts; + + buffer = gst_sample_get_buffer(sample); + gst_buffer_map( buffer, &info, (GstMapFlags)(GST_MAP_READ) ); + + data = info.data; + size = info.size; + + ts = GST_BUFFER_DTS_OR_PTS(buffer); + + if (ts == GST_CLOCK_TIME_NONE) { + warning("gst_video: timestamp is unknown\n"); + rtp_ts = 0; + } + else { + /* convert from nanoseconds to RTP clock */ + rtp_ts = (uint32_t)((90000ULL * ts) / 1000000000UL); + } + + h264_packetize(rtp_ts, data, size, st->encoder.pktsize, + st->pkth, st->arg); + + gst_buffer_unmap(buffer, &info); + gst_sample_unref(sample); + } + + return GST_FLOW_OK; +} + + +static void appsink_end_of_stream_cb(GstAppSink *src, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + + pthread_mutex_lock(&st->streamer.eos.mutex); + if (st->streamer.eos.flag == 0) { + st->streamer.eos.flag = 1; + pthread_cond_signal(&st->streamer.eos.cond); + } + pthread_mutex_unlock(&st->streamer.eos.mutex); +} + + +static void appsink_destroy_notify_cb(struct videnc_state *st) +{ + pthread_mutex_lock(&st->streamer.eos.mutex); + st->streamer.eos.flag = -1; + pthread_cond_signal(&st->streamer.eos.cond); + pthread_mutex_unlock(&st->streamer.eos.mutex); +} + + +static GstBusSyncReply bus_sync_handler_cb(GstBus *bus, GstMessage *msg, + struct videnc_state *st) +{ + (void)bus; + + if ((GST_MESSAGE_TYPE (msg)) == GST_MESSAGE_ERROR) { + GError *err = NULL; + gchar *dbg_info = NULL; + gst_message_parse_error (msg, &err, &dbg_info); + warning("gst_video: Error: %d(%m) message=%s\n", + err->code, err->code, err->message); + warning("gst_video: Debug: %s\n", dbg_info); + g_error_free (err); + g_free (dbg_info); + + /* mark pipeline as broked */ + st->streamer.valid = false; + } + + gst_message_unref(msg); + return GST_BUS_DROP; +} + + +static void bus_destroy_notify_cb(struct videnc_state *st) +{ + (void)st; +} + + +/** + * Set up the Gstreamer pipeline. Appsrc gets raw frames, and appsink takes + * encoded frames. + * + * The pipeline looks like this: + * + * <pre> + * .--------. .-----------. .----------. + * | appsrc | | x264enc | | appsink | + * | .----| |----. .---| |----. | + * | |src |-->|sink| |src|-->|sink|-----+-->handoff + * | '----| |----' '---| |----' | handler + * '--------' '-----------' '----------' + * </pre> + */ +static int pipeline_init(struct videnc_state *st, const struct vidsz *size) +{ + GstAppSrc *source; + GstAppSink *sink; + GstBus *bus; + GError* gerror = NULL; + char pipeline[1024]; + GstStateChangeReturn ret; + int err = 0; + + if (!st || !size) + return EINVAL; + + snprintf(pipeline, sizeof(pipeline), + "appsrc name=source is-live=TRUE block=TRUE " + "do-timestamp=TRUE max-bytes=1000000 ! " + "videoparse width=%d height=%d format=i420 framerate=%d/1 ! " + "x264enc byte-stream=TRUE rc-lookahead=0 " + "tune=zerolatency speed-preset=ultrafast " + "sync-lookahead=0 bitrate=%d ! " + "appsink name=sink emit-signals=TRUE drop=TRUE", + size->w, size->h, + st->encoder.fps, st->encoder.bitrate / 1000 /* kbit/s */); + + /* Initialize pipeline. */ + st->streamer.pipeline = gst_parse_launch(pipeline, &gerror); + + if (gerror) { + warning("gst_video: launch error: %d: %s: %s\n", + gerror->code, gerror->message, pipeline); + err = gerror->code; + g_error_free(gerror); + return err; + } + + /* Configure appsource */ + source = GST_APP_SRC(gst_bin_get_by_name( + GST_BIN(st->streamer.pipeline), "source")); + gst_app_src_set_callbacks(source, &(st->streamer.appsrcCallbacks), + st, (GDestroyNotify)appsrc_destroy_notify_cb); + + /* Configure appsink. */ + sink = GST_APP_SINK(gst_bin_get_by_name( + GST_BIN(st->streamer.pipeline), "sink")); + gst_app_sink_set_callbacks(sink, &(st->streamer.appsinkCallbacks), + st, (GDestroyNotify)appsink_destroy_notify_cb); + gst_object_unref(GST_OBJECT(sink)); + + /* Bus watch */ + bus = gst_pipeline_get_bus(GST_PIPELINE(st->streamer.pipeline)); + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)bus_sync_handler_cb, + st, (GDestroyNotify)bus_destroy_notify_cb); + gst_object_unref(GST_OBJECT(bus)); + + /* Set start values of locks */ + pthread_mutex_lock(&st->streamer.wait.mutex); + st->streamer.wait.flag = 0; + pthread_mutex_unlock(&st->streamer.wait.mutex); + + pthread_mutex_lock(&st->streamer.eos.mutex); + st->streamer.eos.flag = 0; + pthread_mutex_unlock(&st->streamer.eos.mutex); + + /* Start pipeline */ + ret = gst_element_set_state(st->streamer.pipeline, GST_STATE_PLAYING); + if (GST_STATE_CHANGE_FAILURE == ret) { + g_warning("set state returned GST_STATE_CHANGE_FAILURE\n"); + err = EPROTO; + goto out; + } + + st->streamer.source = source; + + /* Mark pipeline as working */ + st->streamer.valid = true; + + out: + return err; +} + + +static void pipeline_close(struct videnc_state *st) +{ + if (!st) + return; + + st->streamer.valid = false; + + if (st->streamer.source) { + gst_object_unref(GST_OBJECT(st->streamer.source)); + st->streamer.source = NULL; + } + + if (st->streamer.pipeline) { + gst_element_set_state(st->streamer.pipeline, GST_STATE_NULL); + + /* pipeline */ + gst_object_unref(GST_OBJECT(st->streamer.pipeline)); + st->streamer.pipeline = NULL; + } +} + + +static void destruct_resources(void *data) +{ + struct videnc_state *st = data; + + /* close pipeline */ + pipeline_close(st); + + /* destroy locks */ + pthread_mutex_destroy(&st->streamer.eos.mutex); + pthread_cond_destroy(&st->streamer.eos.cond); + + pthread_mutex_destroy(&st->streamer.wait.mutex); + pthread_cond_destroy(&st->streamer.wait.cond); +} + + +static int allocate_resources(struct videnc_state **stp) +{ + struct videnc_state *st; + + st = mem_zalloc(sizeof(*st), destruct_resources); + if (!st) + return ENOMEM; + + *stp = st; + + /* initialize locks */ + pthread_mutex_init(&st->streamer.eos.mutex, NULL); + pthread_cond_init(&st->streamer.eos.cond, NULL); + + pthread_mutex_init(&st->streamer.wait.mutex, NULL); + pthread_cond_init(&st->streamer.wait.cond, NULL); + + + /* Set appsource callbacks. */ + st->streamer.appsrcCallbacks.need_data = &appsrc_need_data_cb; + st->streamer.appsrcCallbacks.enough_data = &appsrc_enough_data_cb; + + /* Set appsink callbacks. */ + st->streamer.appsinkCallbacks.new_sample = &appsink_new_sample_cb; + st->streamer.appsinkCallbacks.eos = &appsink_end_of_stream_cb; + + return 0; +} + + +/* + decode sdpparameter for h264 +*/ +static void param_handler(const struct pl *name, const struct pl *val, + void *arg) +{ + struct videnc_state *st = arg; + + if (0 == pl_strcasecmp(name, "packetization-mode")) { + st->h264.packetization_mode = pl_u32(val); + + if (st->h264.packetization_mode != 0) { + warning("gst_video: illegal packetization-mode %u\n", + st->h264.packetization_mode); + return; + } + } + else if (0 == pl_strcasecmp(name, "profile-level-id")) { + struct pl prof = *val; + if (prof.l != 6) { + warning("gst_video: invalid profile-level-id (%r)\n", + val); + return; + } + + prof.l = 2; + st->h264.profile_idc = pl_x32(&prof); prof.p += 2; + st->h264.profile_iop = pl_x32(&prof); prof.p += 2; + st->h264.level_idc = pl_x32(&prof); + } + else if (0 == pl_strcasecmp(name, "max-fs")) { + st->h264.max_fs = pl_u32(val); + } + else if (0 == pl_strcasecmp(name, "max-smbps")) { + st->h264.max_smbps = pl_u32(val); + } + + return; +} + + +int gst_video1_encoder_set(struct videnc_state **stp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *st = *stp; + int err = 0; + + if (!stp || !vc || !prm || !pkth) + return EINVAL; + + if (!st) { + err = allocate_resources(stp); + if (err) { + warning("gst_video: resource allocation failed\n"); + return err; + } + st = *stp; + + st->pkth = pkth; + st->arg = arg; + } + else { + if (!st->streamer.valid) { + warning("gst_video codec: trying to work" + " with invalid pipeline\n"); + return EINVAL; + } + + if ((st->encoder.bitrate != prm->bitrate || + st->encoder.pktsize != prm->pktsize || + st->encoder.fps != prm->fps)) { + + pipeline_close(st); + } + } + + st->encoder.bitrate = prm->bitrate; + st->encoder.pktsize = prm->pktsize; + st->encoder.fps = prm->fps; + + if (str_isset(fmtp)) { + struct pl sdp_fmtp; + pl_set_str(&sdp_fmtp, fmtp); + + /* store new parameters */ + fmt_param_apply(&sdp_fmtp, param_handler, st); + } + + info("gst_video: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, st->encoder.fps, + st->encoder.bitrate, st->encoder.pktsize); + + return err; +} + + +/* + * couple gstreamer tightly by lock-stepping + */ +static int pipeline_push(struct videnc_state *st, const struct vidframe *frame) +{ + GstBuffer *buffer; + uint8_t *data; + size_t size; + GstFlowReturn ret; + int err = 0; + +#if 1 + /* XXX: should not block the function here */ + + /* + * Wait "start feed". + */ + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 1) { + pthread_cond_wait(&st->streamer.wait.cond, + &st->streamer.wait.mutex); + } + if (st->streamer.eos.flag == -1) + /* error */ + err = ENODEV; + pthread_mutex_unlock(&st->streamer.wait.mutex); + if (err) + return err; +#endif + + /* + * Copy frame into buffer for gstreamer + */ + + /* NOTE: I420 (YUV420P): hardcoded. */ + size = frame->linesize[0] * frame->size.h + + frame->linesize[1] * frame->size.h * 0.5 + + frame->linesize[2] * frame->size.h * 0.5; + + /* allocate memory; memory is freed within callback of + gst_memory_new_wrapped of gst_video_push */ + data = g_try_malloc(size); + if (!data) + return ENOMEM; + + /* copy content of frame */ + size = 0; + memcpy(&data[size], frame->data[0], + frame->linesize[0] * frame->size.h); + size += frame->linesize[0] * frame->size.h; + memcpy(&data[size], frame->data[1], + frame->linesize[1] * frame->size.h * 0.5); + size += frame->linesize[1] * frame->size.h * 0.5; + memcpy(&data[size], frame->data[2], + frame->linesize[2] * frame->size.h * 0.5); + size += frame->linesize[2] * frame->size.h * 0.5; + + /* Wrap memory in a gstreamer buffer */ + buffer = gst_buffer_new(); + gst_buffer_insert_memory(buffer, -1, + gst_memory_new_wrapped (0, data, size, 0, + size, data, g_free)); + + /* + * Push data and EOS into gstreamer. + */ + + ret = gst_app_src_push_buffer(st->streamer.source, buffer); + if (ret != GST_FLOW_OK) { + warning("gst_video: pushing buffer failed\n"); + err = EPROTO; + goto out; + } + +#if 0 + ret = gst_app_src_end_of_stream(st->streamer.source); + if (ret != GST_FLOW_OK) { + warning("gst_video: pushing EOS failed\n"); + err = EPROTO; + goto out; + } +#endif + + +#if 0 + /* + * Wait "processing done". + */ + pthread_mutex_lock(&st->streamer.eos.mutex); + if (st->streamer.eos.flag == 0) + /* will returns with EOS (1) or error (-1) */ + pthread_cond_wait(&st->streamer.wait.cond, + &st->streamer.wait.mutex); + if (st->streamer.eos.flag == 1) + /* reset eos */ + st->streamer.eos.flag = 0; + else + /* error */ + err = -1; + pthread_mutex_unlock(&st->streamer.wait.mutex); +#endif + + + out: + return err; +} + + +int gst_video1_encode(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + int err; + + if (!st || !frame || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + if (!st->streamer.valid || + !vidsz_cmp(&st->encoder.size, &frame->size)) { + + pipeline_close(st); + + err = pipeline_init(st, &frame->size); + if (err) { + warning("gst_video: pipeline initialization failed\n"); + return err; + } + + st->encoder.size = frame->size; + } + + if (update) { + debug("gst_video: gstreamer picture update" + ", it's not implemented...\n"); + } + + /* + * Push frame into pipeline. + * Function call will return once frame has been processed completely. + */ + err = pipeline_push(st, frame); + + return err; +} diff --git a/modules/gst_video1/gst_video.c b/modules/gst_video1/gst_video.c new file mode 100644 index 0000000..c15efac --- /dev/null +++ b/modules/gst_video1/gst_video.c @@ -0,0 +1,66 @@ +/** + * @file gst_video1/gst_video.c Video codecs using Gstreamer 1.0 + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst_video.h" + + +/** + * @defgroup gst_video1 gst_video1 + * + * This module implements video codecs using Gstreamer 1.0 + * + * Currently only H.264 encoding is supported, but this can be extended + * if needed. No decoding is done by this module, so that must be done by + * another video-codec module. + * + * Thanks to Victor Sergienko and Fadeev Alexander for the + * initial version, which was based on avcodec module. + */ + + +static struct vidcodec h264 = { + .name = "H264", + .variant = "packetization-mode=0", + .encupdh = gst_video1_encoder_set, + .ench = gst_video1_encode, + .fmtp_ench = gst_video1_fmtp_enc, + .fmtp_cmph = gst_video1_fmtp_cmp, +}; + + +static int module_init(void) +{ + gst_init(NULL, NULL); + + vidcodec_register(baresip_vidcodecl(), &h264); + + info("gst_video: using gstreamer (%s)\n", gst_version_string()); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&h264); + + gst_deinit(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst_video1) = { + "gst_video1", + "vidcodec", + module_init, + module_close +}; diff --git a/modules/gst_video1/gst_video.h b/modules/gst_video1/gst_video.h new file mode 100644 index 0000000..150f5be --- /dev/null +++ b/modules/gst_video1/gst_video.h @@ -0,0 +1,24 @@ +/** + * @file gst_video1/gst_video.h Gstreamer video pipeline -- internal API + * + * Copyright (C) 2010 - 2014 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + + +/* Encode */ +struct videnc_state; + +int gst_video1_encoder_set(struct videnc_state **stp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int gst_video1_encode(struct videnc_state *st, bool update, + const struct vidframe *frame); + + +/* SDP */ +uint32_t gst_video1_h264_packetization_mode(const char *fmtp); +int gst_video1_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data); diff --git a/modules/gst_video1/module.mk b/modules/gst_video1/module.mk new file mode 100644 index 0000000..d60beb3 --- /dev/null +++ b/modules/gst_video1/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst_video1 +$(MOD)_SRCS += gst_video.c encode.c sdp.c +$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-1.0 gstreamer-app-1.0) +$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-1.0 gstreamer-app-1.0) +$(MOD)_CFLAGS += -Wno-cast-align + +include mk/mod.mk diff --git a/modules/gst_video1/sdp.c b/modules/gst_video1/sdp.c new file mode 100644 index 0000000..7896b05 --- /dev/null +++ b/modules/gst_video1/sdp.c @@ -0,0 +1,57 @@ +/** + * @file gst_video/sdp.c H.264 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "gst_video.h" + + +static const uint8_t gst_video_h264_level_idc = 0x0c; + + +uint32_t gst_video1_h264_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; +} + + +int gst_video1_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, + gst_video_h264_level_idc); +} + + +bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data) +{ + (void)data; + + return gst_video1_h264_packetization_mode(fmtp1) == + gst_video1_h264_packetization_mode(fmtp2); +} diff --git a/modules/gtk/call_window.c b/modules/gtk/call_window.c new file mode 100644 index 0000000..6744510 --- /dev/null +++ b/modules/gtk/call_window.c @@ -0,0 +1,522 @@ +/** + * @file gtk/call_window.c GTK+ call window + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + + +struct call_window { + struct gtk_mod *mod; + struct call *call; + + /** for communicating from gtk thread to main thread */ + struct mqueue *mq; + + struct { + struct vumeter_dec *dec; + struct vumeter_enc *enc; + } vu; + struct transfer_dialog *transfer_dialog; + GtkWidget *window; + GtkLabel *status; + GtkLabel *duration; + struct { + GtkWidget *hangup, *transfer, *hold, *mute; + } buttons; + struct { + GtkProgressBar *enc, *dec; + } progress; + guint duration_timer_tag; + guint vumeter_timer_tag; + bool closed; + int cur_key; +}; + +enum call_window_events { + MQ_HANGUP, + MQ_CLOSE, + MQ_HOLD, + MQ_MUTE, + MQ_TRANSFER, +}; + +static struct call_window *last_call_win = NULL; +static struct vumeter_dec *last_dec = NULL; +static struct vumeter_enc *last_enc = NULL; + + +static void call_window_update_duration(struct call_window *win) +{ + gchar buf[32]; + + const uint32_t dur = call_duration(win->call); + const uint32_t sec = dur%60%60; + const uint32_t min = dur/60%60; + const uint32_t hrs = dur/60/60; + + re_snprintf(buf, sizeof buf, "%u:%02u:%02u", hrs, min, sec); + gtk_label_set_text(win->duration, buf); +} + + +static void call_window_update_vumeters(struct call_window *win) +{ + double value; + + if (win->vu.enc && win->vu.enc->started) { + value = min((double)win->vu.enc->avg_rec / 0x4000, 1); + gtk_progress_bar_set_fraction(win->progress.enc, value); + } + if (win->vu.dec && win->vu.dec->started) { + value = min((double)win->vu.dec->avg_play / 0x4000, 1); + gtk_progress_bar_set_fraction(win->progress.dec, value); + } +} + + +static gboolean call_timer(gpointer arg) +{ + struct call_window *win = arg; + call_window_update_duration(win); + return G_SOURCE_CONTINUE; +} + + +static gboolean vumeter_timer(gpointer arg) +{ + struct call_window *win = arg; + call_window_update_vumeters(win); + return G_SOURCE_CONTINUE; +} + + +static void vumeter_timer_start(struct call_window *win) +{ + if (!win->vumeter_timer_tag) + win->vumeter_timer_tag = + g_timeout_add(100, vumeter_timer, win); + if (win->vu.enc) + win->vu.enc->avg_rec = 0; + if (win->vu.dec) + win->vu.dec->avg_play = 0; +} + + +static void vumeter_timer_stop(struct call_window *win) +{ + if (win->vumeter_timer_tag) { + g_source_remove(win->vumeter_timer_tag); + win->vumeter_timer_tag = 0; + } + gtk_progress_bar_set_fraction(win->progress.enc, 0); + gtk_progress_bar_set_fraction(win->progress.dec, 0); +} + + +static void call_window_set_vu_dec(struct call_window *win, + struct vumeter_dec *dec) +{ + if (win->vu.dec) + mem_deref(win->vu.dec); + win->vu.dec = mem_ref(dec); + vumeter_timer_start(win); +} + + +static void call_window_set_vu_enc(struct call_window *win, + struct vumeter_enc *enc) +{ + if (win->vu.enc) + mem_deref(win->vu.enc); + win->vu.enc = mem_ref(enc); + vumeter_timer_start(win); +} + + +/* This is a hack to associate a call with its vumeters */ + +void call_window_got_vu_dec(struct vumeter_dec *dec) +{ + if (last_call_win) + call_window_set_vu_dec(last_call_win, dec); + else + last_dec = dec; +} + + +void call_window_got_vu_enc(struct vumeter_enc *enc) +{ + if (last_call_win) + call_window_set_vu_enc(last_call_win, enc); + else + last_enc = enc; +} + + +static void got_call_window(struct call_window *win) +{ + if (last_enc) + call_window_set_vu_enc(win, last_enc); + if (last_dec) + call_window_set_vu_dec(win, last_dec); + if (!last_enc || !last_dec) + last_call_win = win; +} + + +static void call_on_hangup(GtkToggleButton *btn, struct call_window *win) +{ + (void)btn; + mqueue_push(win->mq, MQ_CLOSE, win); +} + + +static void call_on_hold_toggle(GtkToggleButton *btn, struct call_window *win) +{ + bool hold = gtk_toggle_button_get_active(btn); + if (hold) + vumeter_timer_stop(win); + else + vumeter_timer_start(win); + mqueue_push(win->mq, MQ_HOLD, (void *)(size_t)hold); +} + + +static void call_on_mute_toggle(GtkToggleButton *btn, struct call_window *win) +{ + bool mute = gtk_toggle_button_get_active(btn); + mqueue_push(win->mq, MQ_MUTE, (void *)(size_t)mute); +} + + +static void call_on_transfer(GtkToggleButton *btn, struct call_window *win) +{ + (void)btn; + if (!win->transfer_dialog) + win->transfer_dialog = transfer_dialog_alloc(win); + else + transfer_dialog_show(win->transfer_dialog); +} + +static gboolean call_on_window_close(GtkWidget *widget, GdkEventAny *event, + struct call_window *win) +{ + (void)event; + (void)widget; + mqueue_push(win->mq, MQ_CLOSE, NULL); + return TRUE; +} + + +static gboolean call_on_key_press(GtkWidget *window, GdkEvent *ev, + struct call_window *win) +{ + gchar key = ev->key.string[0]; + (void)window; + + switch (key) { + + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + case '*': case '0': case '#': + win->cur_key = key; + call_send_digit(win->call, key); + return TRUE; + + default: + return FALSE; + } +} + + +static gboolean call_on_key_release(GtkWidget *window, GdkEvent *ev, + struct call_window *win) +{ + (void)window; + + if (win->cur_key && win->cur_key == ev->key.string[0]) { + win->cur_key = 0; + call_send_digit(win->call, 0); + return TRUE; + } + + return FALSE; +} + + +static void call_window_set_status(struct call_window *win, + const char *status) +{ + gtk_label_set_text(win->status, status); +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct call_window *win = arg; + + switch ((enum call_window_events)id) { + + case MQ_HANGUP: + ua_hangup(uag_current(), win->call, 0, NULL); + break; + + case MQ_CLOSE: + if (!win->closed) { + ua_hangup(uag_current(), win->call, 0, NULL); + win->closed = true; + } + mem_deref(win); + break; + + case MQ_MUTE: + audio_mute(call_audio(win->call), (size_t)data); + break; + + case MQ_HOLD: + call_hold(win->call, (size_t)data); + break; + + case MQ_TRANSFER: + call_transfer(win->call, data); + break; + } +} + + +static void call_window_destructor(void *arg) +{ + struct call_window *window = arg; + + gdk_threads_enter(); + gtk_mod_call_window_closed(window->mod, window); + gtk_widget_destroy(window->window); + mem_deref(window->transfer_dialog); + gdk_threads_leave(); + + mem_deref(window->call); + mem_deref(window->mq); + mem_deref(window->vu.enc); + mem_deref(window->vu.dec); + if (window->duration_timer_tag) + g_source_remove(window->duration_timer_tag); + if (window->vumeter_timer_tag) + g_source_remove(window->vumeter_timer_tag); + /* TODO: avoid race conditions here */ + last_call_win = NULL; +} + + +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod) +{ + struct call_window *win; + GtkWidget *window, *label, *status, *button, *progress, *image; + GtkWidget *button_box, *vbox, *hbox; + GtkWidget *duration; + int err = 0; + + win = mem_zalloc(sizeof(*win), call_window_destructor); + if (!win) + return NULL; + + mqueue_alloc(&win->mq, mqueue_handler, win); + if (err) + goto out; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), call_peeruri(call)); + gtk_window_set_type_hint(GTK_WINDOW(window), + GDK_WINDOW_TYPE_HINT_DIALOG); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + /* Peer name and URI */ + label = gtk_label_new(call_peername(call)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + label = gtk_label_new(call_peeruri(call)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + /* Call duration */ + duration = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(vbox), duration, FALSE, FALSE, 0); + + /* Status */ + status = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0); + + /* Progress bars */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_set_spacing(GTK_BOX(hbox), 6); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 5); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Encoding vumeter */ + image = gtk_image_new_from_icon_name("audio-input-microphone", + GTK_ICON_SIZE_BUTTON); + progress = gtk_progress_bar_new(); + win->progress.enc = GTK_PROGRESS_BAR(progress); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), progress, FALSE, FALSE, 0); + + /* Decoding vumeter */ + image = gtk_image_new_from_icon_name("audio-headphones", + GTK_ICON_SIZE_BUTTON); + progress = gtk_progress_bar_new(); + win->progress.dec = GTK_PROGRESS_BAR(progress); + gtk_box_pack_end(GTK_BOX(hbox), progress, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), image, FALSE, FALSE, 0); + + /* Buttons */ + button_box = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), + GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(button_box), 6); + gtk_container_set_border_width(GTK_CONTAINER(button_box), 5); + gtk_box_pack_end(GTK_BOX(vbox), button_box, FALSE, TRUE, 0); + + /* Hang up */ + button = gtk_button_new_with_label("Hangup"); + win->buttons.hangup = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "clicked", + G_CALLBACK(call_on_hangup), win); + image = gtk_image_new_from_icon_name("call-stop", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Transfer */ + button = gtk_button_new_with_label("Transfer"); + win->buttons.transfer = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "clicked", G_CALLBACK(call_on_transfer), win); + image = gtk_image_new_from_icon_name("forward", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Hold */ + button = gtk_toggle_button_new_with_label("Hold"); + win->buttons.hold = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "toggled", + G_CALLBACK(call_on_hold_toggle), win); + image = gtk_image_new_from_icon_name("player_pause", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Mute */ + button = gtk_toggle_button_new_with_label("Mute"); + win->buttons.mute = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "toggled", + G_CALLBACK(call_on_mute_toggle), win); + image = gtk_image_new_from_icon_name("microphone-sensitivity-muted", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + gtk_widget_show_all(window); + gtk_window_present(GTK_WINDOW(window)); + + g_signal_connect(window, "delete_event", + G_CALLBACK(call_on_window_close), win); + g_signal_connect(window, "key-press-event", + G_CALLBACK(call_on_key_press), win); + g_signal_connect(window, "key-release-event", + G_CALLBACK(call_on_key_release), win); + + win->call = mem_ref(call); + win->mod = mod; + win->window = window; + win->transfer_dialog = NULL; + win->status = GTK_LABEL(status); + win->duration = GTK_LABEL(duration); + win->closed = false; + win->duration_timer_tag = 0; + win->vumeter_timer_tag = 0; + win->vu.enc = NULL; + win->vu.dec = NULL; + + got_call_window(win); + +out: + if (err) + mem_deref(win); + + return win; +} + + +void call_window_transfer(struct call_window *win, const char *uri) +{ + mqueue_push(win->mq, MQ_TRANSFER, (char *)uri); +} + + +void call_window_closed(struct call_window *win, const char *reason) +{ + char buf[256]; + const char *status; + + vumeter_timer_stop(win); + if (win->duration_timer_tag) { + g_source_remove(win->duration_timer_tag); + win->duration_timer_tag = 0; + } + gtk_widget_set_sensitive(win->buttons.transfer, FALSE); + gtk_widget_set_sensitive(win->buttons.hold, FALSE); + gtk_widget_set_sensitive(win->buttons.mute, FALSE); + + if (reason && reason[0]) { + re_snprintf(buf, sizeof buf, "closed: %s", reason); + status = buf; + } + else { + status = "closed"; + } + call_window_set_status(win, status); + win->transfer_dialog = mem_deref(win->transfer_dialog); + win->closed = true; +} + + +void call_window_ringing(struct call_window *win) +{ + call_window_set_status(win, "ringing"); +} + + +void call_window_progress(struct call_window *win) +{ + win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win); + last_call_win = win; + call_window_set_status(win, "progress"); +} + + +void call_window_established(struct call_window *win) +{ + call_window_update_duration(win); + win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win); + last_call_win = win; + call_window_set_status(win, "established"); +} + + +void call_window_transfer_failed(struct call_window *win, const char *reason) +{ + if (win->transfer_dialog) { + transfer_dialog_fail(win->transfer_dialog, reason); + } +} + + +bool call_window_is_for_call(struct call_window *win, struct call *call) +{ + return win->call == call; +} diff --git a/modules/gtk/dial_dialog.c b/modules/gtk/dial_dialog.c new file mode 100644 index 0000000..340362b --- /dev/null +++ b/modules/gtk/dial_dialog.c @@ -0,0 +1,96 @@ +/** + * @file gtk/dial_dialog.c GTK+ dial dialog + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <stdlib.h> +#include <pthread.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +struct dial_dialog { + struct gtk_mod *mod; + GtkWidget *dialog; + GtkComboBox *uri_combobox; +}; + +static void dial_dialog_on_response(GtkDialog *dialog, gint response_id, + gpointer arg) +{ + struct dial_dialog *dd = arg; + char *uri; + + if (response_id == GTK_RESPONSE_ACCEPT) { + uri = (char *)uri_combo_box_get_text(dd->uri_combobox); + gtk_mod_connect(dd->mod, uri); + } + + gtk_widget_hide(GTK_WIDGET(dialog)); +} + + +static void destructor(void *arg) +{ + struct dial_dialog *dd = arg; + + gtk_widget_destroy(dd->dialog); +} + +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod) +{ + struct dial_dialog *dd; + GtkWidget *dial; + GtkWidget *content, *button, *image; + GtkWidget *uri_combobox; + + dd = mem_zalloc(sizeof(*dd), destructor); + if (!dd) + return NULL; + + dial = gtk_dialog_new_with_buttons("Dial", NULL, 0, NULL); + + /* Cancel */ + button = gtk_button_new_with_label("Cancel"); + image = gtk_image_new_from_icon_name("call-stop", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dial), button, + GTK_RESPONSE_REJECT); + + /* Call */ + button = gtk_button_new_with_label("Call"); + image = gtk_image_new_from_icon_name("call-start", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dial), button, + GTK_RESPONSE_ACCEPT); + gtk_widget_set_can_default (button, TRUE); + + gtk_dialog_set_default_response(GTK_DIALOG(dial), + GTK_RESPONSE_ACCEPT); + uri_combobox = uri_combo_box_new(); + + content = gtk_dialog_get_content_area(GTK_DIALOG(dial)); + gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5); + gtk_widget_show_all(content); + + g_signal_connect(G_OBJECT(dial), "response", + G_CALLBACK(dial_dialog_on_response), dd); + g_signal_connect(G_OBJECT(dial), "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), dd); + + dd->dialog = dial; + dd->uri_combobox = GTK_COMBO_BOX(uri_combobox); + dd->mod = mod; + + return dd; +} + +void dial_dialog_show(struct dial_dialog *dd) +{ + gtk_window_present(GTK_WINDOW(dd->dialog)); + gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(dd->uri_combobox))); +} diff --git a/modules/gtk/gtk_mod.c b/modules/gtk/gtk_mod.c new file mode 100644 index 0000000..dbd4a5b --- /dev/null +++ b/modules/gtk/gtk_mod.c @@ -0,0 +1,1053 @@ +/** + * @file gtk/gtk_mod.c GTK+ UI module + * + * Copyright (C) 2015 Charles E. Lehner + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <stdlib.h> +#include <pthread.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include "gtk_mod.h" + +#ifdef USE_LIBNOTIFY +#include <libnotify/notify.h> +#endif + +#if GLIB_CHECK_VERSION(2,40,0) || defined(USE_LIBNOTIFY) +#define USE_NOTIFICATIONS 1 +#endif + +/* About */ +#define COPYRIGHT " Copyright (C) 2010 - 2015 Alfred E. Heggestad et al." +#define COMMENTS "A modular SIP User-Agent with audio and video support" +#define WEBSITE "http://www.creytiv.com/baresip.html" +#define LICENSE "BSD" + +/** + * @defgroup gtk_mod gtk_mod + * + * GTK+ Menu-based User-Interface module + * + * Creates a tray icon with a menu for making calls. + * + */ + +struct gtk_mod { + pthread_t thread; + bool run; + bool contacts_inited; + bool accounts_inited; + struct message_lsnr *message; + struct mqueue *mq; + GApplication *app; + GtkStatusIcon *status_icon; + GtkWidget *app_menu; + GtkWidget *contacts_menu; + GtkWidget *accounts_menu; + GtkWidget *status_menu; + GSList *accounts_menu_group; + struct dial_dialog *dial_dialog; + GSList *call_windows; + GSList *incoming_call_menus; +}; + +static struct gtk_mod mod_obj; + +enum gtk_mod_events { + MQ_POPUP, + MQ_CONNECT, + MQ_QUIT, + MQ_ANSWER, + MQ_HANGUP, + MQ_SELECT_UA, +}; + +static void answer_activated(GSimpleAction *, GVariant *, gpointer); +static void reject_activated(GSimpleAction *, GVariant *, gpointer); +static void denotify_incoming_call(struct gtk_mod *, struct call *); + +static GActionEntry app_entries[] = { + {"answer", answer_activated, "x", NULL, NULL, {0} }, + {"reject", reject_activated, "x", NULL, NULL, {0} }, +}; + +static struct call *get_call_from_gvariant(GVariant *param) +{ + gint64 call_ptr; + struct call *call; + struct list *calls = ua_calls(uag_current()); + struct le *le; + + call_ptr = g_variant_get_int64(param); + call = GINT_TO_POINTER(call_ptr); + + for (le = list_head(calls); le; le = le->next) + if (le->data == call) + return call; + + return NULL; +} + + +static void menu_on_about(GtkMenuItem *menuItem, gpointer arg) +{ + (void)menuItem; + (void)arg; + + gtk_show_about_dialog(NULL, + "program-name", "baresip", + "version", BARESIP_VERSION, + "logo-icon-name", "call-start", + "copyright", COPYRIGHT, + "comments", COMMENTS, + "website", WEBSITE, + "license", LICENSE, + NULL); +} + + +static void menu_on_quit(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + (void)menuItem; + + gtk_widget_destroy(GTK_WIDGET(mod->app_menu)); + g_object_unref(G_OBJECT(mod->status_icon)); + + mqueue_push(mod->mq, MQ_QUIT, 0); + info("quit from gtk\n"); +} + + +static void menu_on_dial(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + (void)menuItem; + if (!mod->dial_dialog) + mod->dial_dialog = dial_dialog_alloc(mod); + dial_dialog_show(mod->dial_dialog); +} + + +static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + const char *uri = gtk_menu_item_get_label(menuItem); + /* Queue dial from the main thread */ + mqueue_push(mod->mq, MQ_CONNECT, (char *)uri); +} + + +static void init_contacts_menu(struct gtk_mod *mod) +{ + struct contacts *contacts = baresip_contacts(); + struct le *le; + GtkWidget *item; + GtkMenuShell *contacts_menu = GTK_MENU_SHELL(mod->contacts_menu); + + /* Add contacts to submenu */ + for (le = list_head(contact_list(contacts)); le; le = le->next) { + struct contact *c = le->data; + item = gtk_menu_item_new_with_label(contact_str(c)); + gtk_menu_shell_append(contacts_menu, item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_dial_contact), mod); + } +} + + +static void menu_on_account_toggled(GtkCheckMenuItem *menu_item, + struct gtk_mod *mod) +{ + struct ua *ua = g_object_get_data(G_OBJECT(menu_item), "ua"); + if (menu_item->active) + mqueue_push(mod->mq, MQ_SELECT_UA, ua); +} + + +static void menu_on_presence_set(GtkMenuItem *item, struct gtk_mod *mod) +{ + struct le *le; + void *type = g_object_get_data(G_OBJECT(item), "presence"); + enum presence_status status = GPOINTER_TO_UINT(type); + (void)mod; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + ua_presence_status_set(ua, status); + } +} + + +#ifdef USE_NOTIFICATIONS +static void menu_on_incoming_call_answer(GtkMenuItem *menuItem, + struct gtk_mod *mod) +{ + struct call *call = g_object_get_data(G_OBJECT(menuItem), "call"); + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_ANSWER, call); +} + + +static void menu_on_incoming_call_reject(GtkMenuItem *menuItem, + struct gtk_mod *mod) +{ + struct call *call = g_object_get_data(G_OBJECT(menuItem), "call"); + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_HANGUP, call); +} +#endif + + +static GtkMenuItem *accounts_menu_add_item(struct gtk_mod *mod, + struct ua *ua) +{ + GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu); + GtkWidget *item; + GSList *group = mod->accounts_menu_group; + struct ua *ua_current = uag_current(); + char buf[256]; + + re_snprintf(buf, sizeof buf, "%s%s", ua_aor(ua), + ua_isregistered(ua) ? " (OK)" : ""); + item = gtk_radio_menu_item_new_with_label(group, buf); + group = gtk_radio_menu_item_get_group( + GTK_RADIO_MENU_ITEM (item)); + if (ua == ua_current) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), + TRUE); + g_object_set_data(G_OBJECT(item), "ua", ua); + g_signal_connect(item, "toggled", + G_CALLBACK(menu_on_account_toggled), mod); + gtk_menu_shell_append(accounts_menu, item); + mod->accounts_menu_group = group; + + return GTK_MENU_ITEM(item); +} + + +static GtkMenuItem *accounts_menu_get_item(struct gtk_mod *mod, + struct ua *ua) +{ + GtkMenuItem *item; + GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu); + GList *items = accounts_menu->children; + + for (; items; items = items->next) { + item = items->data; + if (ua == g_object_get_data(G_OBJECT(item), "ua")) + return item; + } + + /* Add new account not yet in menu */ + return accounts_menu_add_item(mod, ua); +} + + +static void update_current_accounts_menu_item(struct gtk_mod *mod) +{ + GtkMenuItem *item = accounts_menu_get_item(mod, + uag_current()); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); +} + + +static void update_ua_presence(struct gtk_mod *mod) +{ + GtkCheckMenuItem *item = 0; + enum presence_status cur_status; + void *status; + GtkMenuShell *status_menu = GTK_MENU_SHELL(mod->status_menu); + GList *items = status_menu->children; + + cur_status = ua_presence_status(uag_current()); + + for (; items; items = items->next) { + item = items->data; + status = g_object_get_data(G_OBJECT(item), "presence"); + if (cur_status == GPOINTER_TO_UINT(status)) + break; + } + if (!item) + return; + + gtk_check_menu_item_set_active(item, TRUE); +} + + +static const char *ua_event_reg_str(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: return "registering"; + case UA_EVENT_REGISTER_OK: return "OK"; + case UA_EVENT_REGISTER_FAIL: return "ERR"; + case UA_EVENT_UNREGISTERING: return "unregistering"; + default: return "?"; + } +} + + +static void accounts_menu_set_status(struct gtk_mod *mod, + struct ua *ua, enum ua_event ev) +{ + GtkMenuItem *item = accounts_menu_get_item(mod, ua); + char buf[256]; + re_snprintf(buf, sizeof buf, "%s (%s)", ua_aor(ua), + ua_event_reg_str(ev)); + gtk_menu_item_set_label(item, buf); +} + + +#ifdef USE_NOTIFICATIONS +static void notify_incoming_call(struct gtk_mod *mod, + struct call *call) +{ + static const char *title = "Incoming call"; + const char *msg = call_peeruri(call); + GtkWidget *call_menu; + GtkWidget *menu_item; +#if defined(USE_LIBNOTIFY) + NotifyNotification *notification; + + if (!notify_is_initted()) + return; + notification = notify_notification_new(title, msg, "baresip"); + notify_notification_set_urgency(notification, NOTIFY_URGENCY_CRITICAL); + notify_notification_show(notification, NULL); + g_object_unref(notification); + +#elif GLIB_CHECK_VERSION(2,40,0) + char id[64]; + GVariant *target; + GNotification *notification = g_notification_new(title); + + re_snprintf(id, sizeof id, "incoming-call-%p", call); + id[sizeof id - 1] = '\0'; + +#if GLIB_CHECK_VERSION(2,42,0) + g_notification_set_priority(notification, + G_NOTIFICATION_PRIORITY_URGENT); +#else + g_notification_set_urgent(notification, TRUE); +#endif + + target = g_variant_new_int64(GPOINTER_TO_INT(call)); + g_notification_set_body(notification, msg); + g_notification_add_button_with_target_value(notification, + "Answer", "app.answer", target); + g_notification_add_button_with_target_value(notification, + "Reject", "app.reject", target); + g_application_send_notification(mod->app, id, notification); + g_object_unref(notification); + +#else + (void)msg; + (void)title; +#endif + + /* Add incoming call to the app menu */ + call_menu = gtk_menu_new(); + menu_item = gtk_menu_item_new_with_mnemonic("_Incoming call"); + g_object_set_data(G_OBJECT(menu_item), "call", call); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), + call_menu); + gtk_menu_shell_prepend(GTK_MENU_SHELL(mod->app_menu), menu_item); + mod->incoming_call_menus = g_slist_append(mod->incoming_call_menus, + menu_item); + + menu_item = gtk_menu_item_new_with_label(call_peeruri(call)); + gtk_widget_set_sensitive(menu_item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item); + + menu_item = gtk_menu_item_new_with_mnemonic("_Accept"); + g_object_set_data(G_OBJECT(menu_item), "call", call); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(menu_on_incoming_call_answer), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item); + + menu_item = gtk_menu_item_new_with_mnemonic("_Reject"); + g_object_set_data(G_OBJECT(menu_item), "call", call); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(menu_on_incoming_call_reject), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item); +} +#endif + + +static void denotify_incoming_call(struct gtk_mod *mod, struct call *call) +{ + GSList *item, *next; + +#if GLIB_CHECK_VERSION(2,40,0) + char id[64]; + + re_snprintf(id, sizeof id, "incoming-call-%p", call); + id[sizeof id - 1] = '\0'; + g_application_withdraw_notification(mod->app, id); +#endif + + /* Remove call submenu */ + for (item = mod->incoming_call_menus; item; item = next) { + GtkWidget *menu_item = item->data; + next = item->next; + + if (call == g_object_get_data(G_OBJECT(menu_item), "call")) { + gtk_widget_destroy(menu_item); + mod->incoming_call_menus = + g_slist_delete_link(mod->incoming_call_menus, + item); + } + } +} + + +static void answer_activated(GSimpleAction *action, GVariant *parameter, + gpointer arg) +{ + struct gtk_mod *mod = arg; + struct call *call = get_call_from_gvariant(parameter); + (void)action; + + if (call) { + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_ANSWER, call); + } +} + + +static void reject_activated(GSimpleAction *action, GVariant *parameter, + gpointer arg) +{ + struct gtk_mod *mod = arg; + struct call *call = get_call_from_gvariant(parameter); + (void)action; + + if (call) { + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_HANGUP, call); + } +} + + +static struct call_window *new_call_window(struct gtk_mod *mod, + struct call *call) +{ + struct call_window *win = call_window_new(call, mod); + if (call) { + mod->call_windows = g_slist_append(mod->call_windows, win); + } + return win; +} + + +static struct call_window *get_call_window(struct gtk_mod *mod, + struct call *call) +{ + GSList *wins; + + for (wins = mod->call_windows; wins; wins = wins->next) { + struct call_window *win = wins->data; + if (call_window_is_for_call(win, call)) + return win; + } + return NULL; +} + + +static struct call_window *get_create_call_window(struct gtk_mod *mod, + struct call *call) +{ + struct call_window *win = get_call_window(mod, call); + if (!win) + win = new_call_window(mod, call); + return win; +} + + +void gtk_mod_call_window_closed(struct gtk_mod *mod, struct call_window *win) +{ + if (!mod) + return; + mod->call_windows = g_slist_remove(mod->call_windows, win); +} + + +static void ua_event_handler(struct ua *ua, + enum ua_event ev, + struct call *call, + const char *prm, + void *arg ) +{ + struct gtk_mod *mod = arg; + struct call_window *win; + + gdk_threads_enter(); + + switch (ev) { + + case UA_EVENT_REGISTERING: + case UA_EVENT_UNREGISTERING: + case UA_EVENT_REGISTER_OK: + case UA_EVENT_REGISTER_FAIL: + accounts_menu_set_status(mod, ua, ev); + break; + +#ifdef USE_NOTIFICATIONS + case UA_EVENT_CALL_INCOMING: + notify_incoming_call(mod, call); + break; +#endif + + case UA_EVENT_CALL_CLOSED: + win = get_call_window(mod, call); + if (win) + call_window_closed(win, prm); + else + denotify_incoming_call(mod, call); + break; + + case UA_EVENT_CALL_RINGING: + win = get_create_call_window(mod, call); + if (win) + call_window_ringing(win); + break; + + case UA_EVENT_CALL_PROGRESS: + win = get_create_call_window(mod, call); + if (win) + call_window_progress(win); + break; + + case UA_EVENT_CALL_ESTABLISHED: + win = get_create_call_window(mod, call); + if (win) + call_window_established(win); + break; + + case UA_EVENT_CALL_TRANSFER_FAILED: + win = get_create_call_window(mod, call); + if (win) + call_window_transfer_failed(win, prm); + break; + + default: + break; + } + + gdk_threads_leave(); +} + + +#ifdef USE_NOTIFICATIONS +static void message_handler(const struct pl *peer, const struct pl *ctype, + struct mbuf *body, void *arg) +{ + struct gtk_mod *mod = arg; + char title[128]; + char msg[512]; + +#if GLIB_CHECK_VERSION(2,40,0) + GNotification *notification; +#elif defined(USE_LIBNOTIFY) + NotifyNotification *notification; +#endif + + (void)ctype; + + + /* Display notification of chat */ + + re_snprintf(title, sizeof title, "Chat from %r", peer); + title[sizeof title - 1] = '\0'; + + re_snprintf(msg, sizeof msg, "%b", + mbuf_buf(body), mbuf_get_left(body)); + +#if GLIB_CHECK_VERSION(2,40,0) + notification = g_notification_new(title); + g_notification_set_body(notification, msg); + g_application_send_notification(mod->app, NULL, notification); + g_object_unref(notification); + +#elif defined(USE_LIBNOTIFY) + (void)mod; + + if (!notify_is_initted()) + return; + notification = notify_notification_new(title, msg, "baresip"); + notify_notification_show(notification, NULL); + g_object_unref(notification); +#endif +} +#endif + + +static void popup_menu(struct gtk_mod *mod, GtkMenuPositionFunc position, + gpointer position_arg, guint button, guint32 activate_time) +{ + if (!mod->contacts_inited) { + init_contacts_menu(mod); + mod->contacts_inited = TRUE; + } + + /* Update things that may have been changed through another UI */ + update_current_accounts_menu_item(mod); + update_ua_presence(mod); + + gtk_widget_show_all(mod->app_menu); + + gtk_menu_popup(GTK_MENU(mod->app_menu), NULL, NULL, + position, position_arg, + button, activate_time); +} + + +static gboolean status_icon_on_button_press(GtkStatusIcon *status_icon, + GdkEventButton *event, + struct gtk_mod *mod) +{ + popup_menu(mod, gtk_status_icon_position_menu, status_icon, + event->button, event->time); + return TRUE; +} + + +void gtk_mod_connect(struct gtk_mod *mod, const char *uri) +{ + if (!mod) + return; + mqueue_push(mod->mq, MQ_CONNECT, (char *)uri); +} + + +static void warning_dialog(const char *title, const char *fmt, ...) +{ + va_list ap; + char msg[512]; + GtkWidget *dialog; + + va_start(ap, fmt); + (void)re_vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + + dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, "%s", title); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + "%s", msg); + g_signal_connect_swapped(G_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), dialog); + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_widget_show(dialog); +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct gtk_mod *mod = arg; + const char *uri; + struct call *call; + int err; + struct ua *ua = uag_current(); + (void)mod; + + switch ((enum gtk_mod_events)id) { + + case MQ_POPUP: + gdk_threads_enter(); + popup_menu(mod, NULL, NULL, 0, GPOINTER_TO_UINT(data)); + gdk_threads_leave(); + break; + + case MQ_CONNECT: + uri = data; + err = ua_connect(ua, &call, NULL, uri, NULL, VIDMODE_ON); + if (err) { + gdk_threads_enter(); + warning_dialog("Call failed", + "Connecting to \"%s\" failed.\n" + "Error: %m", uri, err); + gdk_threads_leave(); + break; + } + gdk_threads_enter(); + err = new_call_window(mod, call) == NULL; + gdk_threads_leave(); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + case MQ_HANGUP: + call = data; + ua_hangup(ua, call, 0, NULL); + break; + + case MQ_QUIT: + ua_stop_all(false); + break; + + case MQ_ANSWER: + call = data; + err = ua_answer(ua, call); + if (err) { + gdk_threads_enter(); + warning_dialog("Call failed", + "Answering the call " + "from \"%s\" failed.\n" + "Error: %m", + call_peername(call), err); + gdk_threads_leave(); + break; + } + + gdk_threads_enter(); + err = new_call_window(mod, call) == NULL; + gdk_threads_leave(); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + case MQ_SELECT_UA: + ua = data; + uag_current_set(ua); + break; + } +} + + +static void *gtk_thread(void *arg) +{ + struct gtk_mod *mod = arg; + GtkMenuShell *app_menu; + GtkWidget *item; + GError *err = NULL; + struct le *le; + + gdk_threads_init(); + gtk_init(0, NULL); + + g_set_application_name("baresip"); + mod->app = g_application_new ("com.creytiv.baresip", + G_APPLICATION_FLAGS_NONE); + + g_application_register (G_APPLICATION (mod->app), NULL, &err); + if (err != NULL) { + warning ("Unable to register GApplication: %s", + err->message); + g_error_free (err); + err = NULL; + } + +#ifdef USE_LIBNOTIFY + notify_init("baresip"); +#endif + + mod->status_icon = gtk_status_icon_new_from_icon_name("call-start"); + gtk_status_icon_set_tooltip_text (mod->status_icon, "baresip"); + + g_signal_connect(G_OBJECT(mod->status_icon), + "button_press_event", + G_CALLBACK(status_icon_on_button_press), mod); + gtk_status_icon_set_visible(mod->status_icon, TRUE); + + mod->contacts_inited = false; + mod->dial_dialog = NULL; + mod->call_windows = NULL; + mod->incoming_call_menus = NULL; + + /* App menu */ + mod->app_menu = gtk_menu_new(); + app_menu = GTK_MENU_SHELL(mod->app_menu); + + /* Account submenu */ + mod->accounts_menu = gtk_menu_new(); + mod->accounts_menu_group = NULL; + item = gtk_menu_item_new_with_mnemonic("_Account"); + gtk_menu_shell_append(app_menu, item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), + mod->accounts_menu); + + /* Add accounts to submenu */ + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + accounts_menu_add_item(mod, ua); + } + + /* Status submenu */ + mod->status_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_mnemonic("_Status"); + gtk_menu_shell_append(GTK_MENU_SHELL(app_menu), item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), mod->status_menu); + + /* Open */ + item = gtk_radio_menu_item_new_with_label(NULL, "Open"); + g_object_set_data(G_OBJECT(item), "presence", + GINT_TO_POINTER(PRESENCE_OPEN)); + g_signal_connect(item, "activate", + G_CALLBACK(menu_on_presence_set), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); + + /* Closed */ + item = gtk_radio_menu_item_new_with_label_from_widget( + GTK_RADIO_MENU_ITEM(item), "Closed"); + g_object_set_data(G_OBJECT(item), "presence", + GINT_TO_POINTER(PRESENCE_CLOSED)); + g_signal_connect(item, "activate", + G_CALLBACK(menu_on_presence_set), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* Dial */ + item = gtk_menu_item_new_with_mnemonic("_Dial..."); + gtk_menu_shell_append(app_menu, item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_dial), mod); + + /* Dial contact */ + mod->contacts_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_mnemonic("Dial _contact"); + gtk_menu_shell_append(app_menu, item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), + mod->contacts_menu); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* About */ + item = gtk_menu_item_new_with_mnemonic("A_bout"); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_about), mod); + gtk_menu_shell_append(app_menu, item); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* Quit */ + item = gtk_menu_item_new_with_mnemonic("_Quit"); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_quit), mod); + gtk_menu_shell_append(app_menu, item); + + g_action_map_add_action_entries(G_ACTION_MAP(mod->app), + app_entries, G_N_ELEMENTS(app_entries), mod); + + info("gtk_menu starting\n"); + + uag_event_register( ua_event_handler, mod ); + mod->run = true; + gtk_main(); + mod->run = false; + uag_event_unregister(ua_event_handler); + + if (mod->dial_dialog) { + mem_deref(mod->dial_dialog); + mod->dial_dialog = NULL; + } + + return NULL; +} + + +static void vu_enc_destructor(void *arg) +{ + struct vumeter_enc *st = arg; + + list_unlink(&st->af.le); +} + + +static void vu_dec_destructor(void *arg) +{ + struct vumeter_dec *st = arg; + + list_unlink(&st->af.le); +} + + +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 vu_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), vu_enc_destructor); + if (!st) + return ENOMEM; + + gdk_threads_enter(); + call_window_got_vu_enc(st); + gdk_threads_leave(); + + *stp = (struct aufilt_enc_st *)st; + + return 0; +} + + +static int vu_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), vu_dec_destructor); + if (!st) + return ENOMEM; + + gdk_threads_enter(); + call_window_got_vu_dec(st); + gdk_threads_leave(); + + *stp = (struct aufilt_dec_st *)st; + + return 0; +} + + +static int vu_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); + vu->started = true; + + return 0; +} + + +static int vu_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); + vu->started = true; + + return 0; +} + + +static struct aufilt vumeter = { + LE_INIT, "gtk_vumeter", + vu_encode_update, vu_encode, + vu_decode_update, vu_decode +}; + + +static int cmd_popup_menu(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + mqueue_push(mod_obj.mq, MQ_POPUP, GUINT_TO_POINTER(GDK_CURRENT_TIME)); + + return 0; +} + + +static const struct cmd cmdv[] = { + {"gtk", 'G', 0, "Pop up GTK+ menu", cmd_popup_menu }, +}; + + +static int module_init(void) +{ + int err = 0; + + err = mqueue_alloc(&mod_obj.mq, mqueue_handler, &mod_obj); + if (err) + return err; + + aufilt_register(baresip_aufiltl(), &vumeter); + +#ifdef USE_NOTIFICATIONS + err = message_listen(&mod_obj.message, baresip_message(), + message_handler, &mod_obj); + if (err) { + warning("gtk: message_init failed (%m)\n", err); + return err; + } +#endif + + err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + /* start the thread last */ + err = pthread_create(&mod_obj.thread, NULL, gtk_thread, + &mod_obj); + if (err) + return err; + + return err; +} + + +static int module_close(void) +{ + cmd_unregister(baresip_commands(), cmdv); + if (mod_obj.run) { + gdk_threads_enter(); + gtk_main_quit(); + gdk_threads_leave(); + } + if (mod_obj.thread) + pthread_join(mod_obj.thread, NULL); + mod_obj.mq = mem_deref(mod_obj.mq); + aufilt_unregister(&vumeter); + mod_obj.message = mem_deref(mod_obj.message); + +#ifdef USE_LIBNOTIFY + if (notify_is_initted()) + notify_uninit(); +#endif + + g_slist_free(mod_obj.accounts_menu_group); + g_slist_free(mod_obj.call_windows); + g_slist_free(mod_obj.incoming_call_menus); + + uag_event_unregister(ua_event_handler); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gtk) = { + "gtk", + "application", + module_init, + module_close, +}; diff --git a/modules/gtk/gtk_mod.h b/modules/gtk/gtk_mod.h new file mode 100644 index 0000000..ab123ca --- /dev/null +++ b/modules/gtk/gtk_mod.h @@ -0,0 +1,51 @@ +/** + * @file gtk/gtk_mod.h GTK+ UI module -- internal API + * + * Copyright (C) 2015 Charles E. Lehner + */ + +struct gtk_mod; +struct call_window; +struct dial_dialog; +struct transfer_dialog; + +struct vumeter_enc { + struct aufilt_enc_st af; /* inheritance */ + int16_t avg_rec; + volatile bool started; +}; + +struct vumeter_dec { + struct aufilt_dec_st af; /* inheritance */ + int16_t avg_play; + volatile bool started; +}; + +/* Main menu */ +void gtk_mod_connect(struct gtk_mod *, const char *uri); +void gtk_mod_call_window_closed(struct gtk_mod *, struct call_window *); + +/* Call Window */ +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod); +void call_window_got_vu_dec(struct vumeter_dec *); +void call_window_got_vu_enc(struct vumeter_enc *); +void call_window_transfer(struct call_window *, const char *uri); +void call_window_closed(struct call_window *, const char *reason); +void call_window_ringing(struct call_window *); +void call_window_progress(struct call_window *); +void call_window_established(struct call_window *); +void call_window_transfer_failed(struct call_window *, const char *reason); +bool call_window_is_for_call(struct call_window *, struct call *); + +/* Dial Dialog */ +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *); +void dial_dialog_show(struct dial_dialog *); + +/* Call transfer dialog */ +struct transfer_dialog *transfer_dialog_alloc(struct call_window *); +void transfer_dialog_show(struct transfer_dialog *); +void transfer_dialog_fail(struct transfer_dialog *, const char *reason); + +/* URI entry combo box */ +GtkWidget *uri_combo_box_new(void); +const char *uri_combo_box_get_text(GtkComboBox *box); diff --git a/modules/gtk/module.mk b/modules/gtk/module.mk new file mode 100644 index 0000000..3735cf8 --- /dev/null +++ b/modules/gtk/module.mk @@ -0,0 +1,22 @@ +# +# module.mk - GTK+ Menu-based UI +# +# Copyright (C) 2010 Creytiv.com +# Copyright (C) 2015 Charles E. Lehner +# + +MOD := gtk +$(MOD)_SRCS += gtk_mod.c call_window.c dial_dialog.c transfer_dialog.c \ + uri_entry.c +$(MOD)_LFLAGS += $(shell pkg-config --libs gtk+-2.0 $($(MOD)_EXTRA)) +$(MOD)_CFLAGS += \ + $(shell pkg-config --cflags gtk+-2.0 $($(MOD)_EXTRA) | \ + sed -e 's/-I/-isystem/g' ) +$(MOD)_CFLAGS += -Wno-strict-prototypes + +ifneq ($(USE_LIBNOTIFY),) +$(MOD)_EXTRA = libnotify +$(MOD)_CFLAGS += -DUSE_LIBNOTIFY=1 +endif + +include mk/mod.mk diff --git a/modules/gtk/transfer_dialog.c b/modules/gtk/transfer_dialog.c new file mode 100644 index 0000000..2bd199f --- /dev/null +++ b/modules/gtk/transfer_dialog.c @@ -0,0 +1,134 @@ +/** + * @file transfer_dialog.c GTK+ call transfer dialog + * + * Copyright (C) 2015 Charles E. Lehner + */ +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +struct transfer_dialog { + struct call_window *call_win; + GtkWidget *dialog; + GtkComboBox *uri_combobox; + GtkLabel *status_label; + GtkWidget *spinner; +}; + +static const char *status_progress = "progress"; + + +static void set_status(struct transfer_dialog *td, const char *status) +{ + if (status == status_progress) { + gtk_widget_show(td->spinner); + gtk_spinner_start(GTK_SPINNER(td->spinner)); + gtk_label_set_text(td->status_label, NULL); + } + else { + gtk_widget_hide(td->spinner); + gtk_spinner_stop(GTK_SPINNER(td->spinner)); + gtk_label_set_text(td->status_label, status); + } +} + + +static void on_dialog_response(GtkDialog *dialog, gint response_id, + struct transfer_dialog *win) +{ + char *uri; + + if (response_id == GTK_RESPONSE_ACCEPT) { + uri = (char *)uri_combo_box_get_text(win->uri_combobox); + set_status(win, status_progress); + call_window_transfer(win->call_win, uri); + } + else { + set_status(win, NULL); + gtk_widget_hide(GTK_WIDGET(dialog)); + } +} + + +static void destructor(void *arg) +{ + struct transfer_dialog *td = arg; + + gtk_widget_destroy(td->dialog); +} + + +struct transfer_dialog *transfer_dialog_alloc(struct call_window *call_win) +{ + struct transfer_dialog *win; + GtkWidget *dialog, *content, *button, *image, *hbox, *spinner, *label; + GtkWidget *uri_combobox; + + win = mem_zalloc(sizeof(*win), destructor); + if (!win) + return NULL; + + dialog = gtk_dialog_new_with_buttons("Transfer", NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + + /* Transfer button */ + button = gtk_button_new_with_label("Transfer"); + image = gtk_image_new_from_icon_name("forward", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, + GTK_RESPONSE_ACCEPT); + gtk_widget_set_can_default(button, TRUE); + + gtk_dialog_set_default_response(GTK_DIALOG(dialog), + GTK_RESPONSE_ACCEPT); + /* Label */ + content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + label = gtk_label_new("Transfer call to:"); + gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0); + + /* URI entry */ + uri_combobox = uri_combo_box_new(); + gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5); + + g_signal_connect(dialog, "response", + G_CALLBACK(on_dialog_response), win); + g_signal_connect(dialog, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), win); + + /* Spinner and status */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 0); + + spinner = gtk_spinner_new(); + gtk_box_pack_start(GTK_BOX(hbox), spinner, TRUE, TRUE, 0); + + label = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0); + win->status_label = GTK_LABEL(label); + + win->dialog = dialog; + win->uri_combobox = GTK_COMBO_BOX(uri_combobox); + win->call_win = call_win; + win->spinner = spinner; + + gtk_widget_show_all(dialog); + gtk_widget_hide(spinner); + + return win; +} + +void transfer_dialog_show(struct transfer_dialog *td) +{ + gtk_window_present(GTK_WINDOW(td->dialog)); + gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(td->uri_combobox))); + set_status(td, NULL); +} + +void transfer_dialog_fail(struct transfer_dialog *td, const char *reason) +{ + char buf[256]; + re_snprintf(buf, sizeof buf, "Transfer failed: %s", reason); + set_status(td, buf); +} diff --git a/modules/gtk/uri_entry.c b/modules/gtk/uri_entry.c new file mode 100644 index 0000000..0ca1a3d --- /dev/null +++ b/modules/gtk/uri_entry.c @@ -0,0 +1,45 @@ +/** + * @file uri_entry.c GTK+ URI entry combo box + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +/** + * Create a URI combox box. + * + * The combo box has a menu of contacts, and a text entry for a URI. + * + * @return the combo box + */ +GtkWidget *uri_combo_box_new(void) +{ + struct contacts *contacts = baresip_contacts(); + struct le *le; + GtkEntry *uri_entry; + GtkWidget *uri_combobox; + + uri_combobox = gtk_combo_box_text_new_with_entry(); + uri_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uri_combobox))); + gtk_entry_set_activates_default(uri_entry, TRUE); + + for (le = list_head(contact_list(contacts)); le; le = le->next) { + struct contact *c = le->data; + gtk_combo_box_text_append_text( + GTK_COMBO_BOX_TEXT(uri_combobox), + contact_str(c)); + } + + return uri_combobox; +} + +const char *uri_combo_box_get_text(GtkComboBox *box) +{ + GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(box))); + GtkEntryBuffer *buf = gtk_entry_get_buffer(entry); + return gtk_entry_buffer_get_text(buf); +} diff --git a/modules/gzrtp/gzrtp.cpp b/modules/gzrtp/gzrtp.cpp new file mode 100644 index 0000000..19aaf9d --- /dev/null +++ b/modules/gzrtp/gzrtp.cpp @@ -0,0 +1,220 @@ +/** + * @file gzrtp.cpp GNU ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <string.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "session.h" +#include "stream.h" + + +/** + * @defgroup gzrtp gzrtp + * + * ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Experimental support for ZRTP + * + * See http://tools.ietf.org/html/rfc6189 + * + * + * This module is using GNU ZRTP C++ library + * + * https://github.com/wernerd/ZRTPCPP + * + * Configuration options: + * + \verbatim + zrtp_parallel {yes,no} # Start all streams at once + \endverbatim + * + */ + + +static ZRTPConfig *s_zrtp_config = NULL; + + +struct menc_sess { + Session *session; +}; + + +struct menc_media { + Stream *stream; +}; + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = (struct menc_sess *)arg; + + delete st->session; +} + + +static void media_destructor(void *arg) +{ + struct menc_media *st = (struct menc_media *)arg; + + delete st->stream; +} + + +static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, + bool offerer, menc_error_h *errorh, void *arg) +{ + struct menc_sess *st; + (void)offerer; + (void)errorh; + (void)arg; + int err = 0; + + if (!sessp || !sdp) + return EINVAL; + + st = (struct menc_sess *)mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + st->session = new Session(*s_zrtp_config); + if (!st->session) + err = ENOMEM; + + 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; + int err = 0; + StreamMediaType med_type; + const char *med_name; + + if (!stp || !sess || !sess->session || proto != IPPROTO_UDP) + return EINVAL; + + st = *stp; + if (st) + goto start; + + st = (struct menc_media *)mem_zalloc(sizeof(*st), media_destructor); + if (!st) + return ENOMEM; + + med_name = sdp_media_name(sdpm); + if (str_cmp(med_name, "audio") == 0) + med_type = MT_AUDIO; + else if (str_cmp(med_name, "video") == 0) + med_type = MT_VIDEO; + else if (str_cmp(med_name, "text") == 0) + med_type = MT_TEXT; + else if (str_cmp(med_name, "application") == 0) + med_type = MT_APPLICATION; + else if (str_cmp(med_name, "message") == 0) + med_type = MT_MESSAGE; + else + med_type = MT_UNKNOWN; + + st->stream = sess->session->create_stream( + *s_zrtp_config, + (struct udp_sock *)rtpsock, + (struct udp_sock *)rtcpsock, + rtp_sess_ssrc(rtp), med_type); + if (!st->stream) { + err = ENOMEM; + goto out; + } + + st->stream->sdp_encode(sdpm); + + out: + if (err) { + mem_deref(st); + return err; + } + else + *stp = st; + + start: + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + st->stream->sdp_decode(sdpm); + err = sess->session->start_stream(st->stream); + if (err) { + warning("zrtp: stream start failed: %d\n", err); + } + } + + return err; +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static const struct cmd cmdv[] = { + {"zrtp_verify", 0, CMD_PRM, "Verify ZRTP SAS <session ID>", + Session::cmd_verify_sas }, + {"zrtp_unverify", 0, CMD_PRM, "Unverify ZRTP SAS <session ID>", + Session::cmd_unverify_sas }, +}; + + +static int module_init(void) +{ + char config_path[256]; + int err = 0; + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + + s_zrtp_config = new ZRTPConfig(conf_cur(), config_path); + if (!s_zrtp_config) + return ENOMEM; + + menc_register(baresip_mencl(), &menc_zrtp); + + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + delete s_zrtp_config; + s_zrtp_config = NULL; + + cmd_unregister(baresip_commands(), cmdv); + + menc_unregister(&menc_zrtp); + + return 0; +} + + +extern "C" EXPORT_SYM const struct mod_export DECL_EXPORTS(gzrtp) = { + "gzrtp", + "menc", + module_init, + module_close +}; diff --git a/modules/gzrtp/messages.cpp b/modules/gzrtp/messages.cpp new file mode 100644 index 0000000..db3a7db --- /dev/null +++ b/modules/gzrtp/messages.cpp @@ -0,0 +1,256 @@ +/** + * @file messages.cpp GNU ZRTP: Engine messages + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "stream.h" + + +using namespace GnuZrtpCodes; + + +#define NO_MESSAGE "NO MESSAGE DEFINED" + + +static const char *info_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case InfoHelloReceived: + msg = "Hello received and prepared a Commit, " + "ready to get peer's hello hash"; + break; + case InfoCommitDHGenerated: + msg = "Commit: Generated a public DH key"; + break; + case InfoRespCommitReceived: + msg = "Responder: Commit received, preparing DHPart1"; + break; + case InfoDH1DHGenerated: + msg = "DH1Part: Generated a public DH key"; + break; + case InfoInitDH1Received: + msg = "Initiator: DHPart1 received, preparing DHPart2"; + break; + case InfoRespDH2Received: + msg = "Responder: DHPart2 received, preparing Confirm1"; + break; + case InfoInitConf1Received: + msg = "Initiator: Confirm1 received, preparing Confirm2"; + break; + case InfoRespConf2Received: + msg = "Responder: Confirm2 received, preparing Conf2Ack"; + break; + case InfoRSMatchFound: + msg = "At least one retained secret matches - security OK"; + break; + case InfoSecureStateOn: + msg = "Entered secure state"; + break; + case InfoSecureStateOff: + msg = "No more security for this session"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *warning_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case WarningDHAESmismatch: + msg = "Commit contains an AES256 cipher but does not offer a " + "Diffie-Helman 4096 - not used DH4096 was discarded"; + break; + case WarningGoClearReceived: + msg = "Received a GoClear message"; + break; + case WarningDHShort: + msg = "Hello offers an AES256 cipher but does not offer a " + "Diffie-Helman 4096- not used DH4096 was discarded"; + break; + case WarningNoRSMatch: + msg = "No retained shared secrets available - must verify SAS"; + break; + case WarningCRCmismatch: + msg = "Internal ZRTP packet checksum mismatch - " + "packet dropped"; + break; + case WarningSRTPauthError: + msg = "Dropping packet because SRTP authentication failed!"; + break; + case WarningSRTPreplayError: + msg = "Dropping packet because SRTP replay check failed!"; + break; + case WarningNoExpectedRSMatch: + msg = "Valid retained shared secrets availabe but no matches " + "found - must verify SAS"; + break; + case WarningNoExpectedAuxMatch: + msg = "Our AUX secret was set but the other peer's AUX secret " + "does not match ours"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *severe_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case SevereHelloHMACFailed: + msg = "Hash HMAC check of Hello failed!"; + break; + case SevereCommitHMACFailed: + msg = "Hash HMAC check of Commit failed!"; + break; + case SevereDH1HMACFailed: + msg = "Hash HMAC check of DHPart1 failed!"; + break; + case SevereDH2HMACFailed: + msg = "Hash HMAC check of DHPart2 failed!"; + break; + case SevereCannotSend: + msg = "Cannot send data - connection or peer down?"; + break; + case SevereProtocolError: + msg = "Internal protocol error occured!"; + break; + case SevereNoTimer: + msg = "Cannot start a timer - internal resources exhausted?"; + break; + case SevereTooMuchRetries: + msg = "Too much retries during ZRTP negotiation - connection " + "or peer down?"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *zrtp_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case MalformedPacket: + msg = "Malformed packet (CRC OK, but wrong structure)"; + break; + case CriticalSWError: + msg = "Critical software error"; + break; + case UnsuppZRTPVersion: + msg = "Unsupported ZRTP version"; + break; + case HelloCompMismatch: + msg = "Hello components mismatch"; + break; + case UnsuppHashType: + msg = "Hash type not supported"; + break; + case UnsuppCiphertype: + msg = "Cipher type not supported"; + break; + case UnsuppPKExchange: + msg = "Public key exchange not supported"; + break; + case UnsuppSRTPAuthTag: + msg = "SRTP auth. tag not supported"; + break; + case UnsuppSASScheme: + msg = "SAS scheme not supported"; + break; + case NoSharedSecret: + msg = "No shared secret available, DH mode required"; + break; + case DHErrorWrongPV: + msg = "DH Error: bad pvi or pvr ( == 1, 0, or p-1)"; + break; + case DHErrorWrongHVI: + msg = "DH Error: hvi != hashed data"; + break; + case SASuntrustedMiTM: + msg = "Received relayed SAS from untrusted MiTM"; + break; + case ConfirmHMACWrong: + msg = "Auth. Error: Bad Confirm pkt HMAC"; + break; + case NonceReused: + msg = "Nonce reuse"; + break; + case EqualZIDHello: + msg = "Equal ZIDs in Hello"; + break; + case GoCleatNotAllowed: + msg = "GoClear packet received, but not allowed"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +void Stream::print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode) +{ + switch (severity) { + case Info: + debug("zrtp: INFO<%s>: %s\n", + media_name(), info_msg(subcode)); + break; + case Warning: + warning("zrtp: WARNING<%s>: %s\n", + media_name(), warning_msg(subcode)); + break; + case Severe: + warning("zrtp: SEVERE<%s>: %s\n", + media_name(), severe_msg(subcode)); + break; + case ZrtpError: + warning("zrtp: ZRTP_ERR<%s>: %s\n", + media_name(), zrtp_msg(subcode)); + break; + default: + return; + } +} + + +const char *Stream::media_name() const +{ + switch (m_media_type) { + case MT_AUDIO: return "audio"; + case MT_VIDEO: return "video"; + case MT_TEXT: return "text"; + case MT_APPLICATION: return "application"; + case MT_MESSAGE: return "message"; + default: return "UNKNOWN"; + } +} diff --git a/modules/gzrtp/module.mk b/modules/gzrtp/module.mk new file mode 100644 index 0000000..42e408e --- /dev/null +++ b/modules/gzrtp/module.mk @@ -0,0 +1,38 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2017 Creytiv.com +# + +# +# To build libzrtpcppcore run the following commands: +# +# git clone https://github.com/wernerd/ZRTPCPP.git +# cd ZRTPCPP +# mkdir build +# cd build +# cmake -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DCORE_LIB=1 -DSDES=1 \ +# -DBUILD_STATIC=1 .. +# make +# + +# GNU ZRTP C++ library (ZRTPCPP) source directory +ZRTP_PATH ?= ../ZRTPCPP + +ZRTP_LIB := $(shell find $(ZRTP_PATH) -name libzrtpcppcore.a) + +MOD := gzrtp +$(MOD)_SRCS += gzrtp.cpp session.cpp stream.cpp messages.cpp srtp.cpp +$(MOD)_LFLAGS += $(ZRTP_LIB) -lstdc++ +$(MOD)_CXXFLAGS += \ + -I$(ZRTP_PATH) \ + -I$(ZRTP_PATH)/zrtp \ + -I$(ZRTP_PATH)/srtp + +$(MOD)_CXXFLAGS += -O2 -Wall -fPIC + +# Uncomment this if you want to use libre SRTP facilities instead of the ones +# provided by ZRTPCPP. In this case only standard ciphers (AES) are supported. +#$(MOD)_CXXFLAGS += -DGZRTP_USE_RE_SRTP=1 + +include mk/mod.mk diff --git a/modules/gzrtp/session.cpp b/modules/gzrtp/session.cpp new file mode 100644 index 0000000..422bee4 --- /dev/null +++ b/modules/gzrtp/session.cpp @@ -0,0 +1,214 @@ +/** + * @file session.h GNU ZRTP: Session class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include "session.h" + + +std::vector<Session *> Session::s_sessl; + + +Session::Session(const ZRTPConfig& config) + : m_start_parallel(config.start_parallel) + , m_master(NULL) + , m_encrypted(0) +{ + int newid = 1; + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() >= newid) + newid = (*it)->id() + 1; + } + + m_id = newid; + + s_sessl.push_back(this); + + debug("zrtp: New session <%d>\n", id()); +} + + +Session::~Session() +{ + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if (*it == this) { + s_sessl.erase(it); + break; + } + } + + debug("zrtp: Session <%d> is destroyed\n", id()); +} + + +Stream *Session::create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type) +{ + int err = 0; + + Stream *st = new Stream (err, config, this, rtpsock, rtcpsock, + local_ssrc, media_type); + if (!st || err) { + delete st; + return NULL; + } + + return st; +} + + +int Session::start_stream(Stream *stream) +{ + if (stream->started()) + return 0; + + m_streams.push_back(stream); + + // Start all streams in parallel using DH mode. This is a kind of + // probing. The first stream to receive HelloACK will be the master + // stream. If disabled, only the first stream starts in DH (master) + // mode. + if (m_start_parallel) { + if (m_master && m_encrypted) + // If we already have a master in secure state, + // start in multistream mode + return stream->start(m_master); + else + // Start a new stream in DH mode + return stream->start(NULL); + } + else { + if (!m_master) { + // Start the first stream in DH mode + m_master = stream; + return stream->start(NULL); + } + else if (m_encrypted) { + // Master is in secure state; multistream + return stream->start(m_master); + } + } + + return 0; +} + + +bool Session::request_master(Stream *stream) +{ + if (!m_start_parallel) + return true; + + if (m_master) + return false; + + // This is the first stream to receive HelloACK. It will be + // used as the master for the other streams in the session. + m_master = stream; + // Stop other DH-mode streams. They will be started in the + // multistream mode after the master enters secure state. + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->stop(); + } + } + + return true; +} + + +void Session::on_secure(Stream *stream) +{ + ++m_encrypted; + + if (m_encrypted == m_streams.size() && m_master) { + info("zrtp: All streams are encrypted (%s), " + "SAS is [%s] (%s)\n", + m_master->get_ciphers(), + m_master->get_sas(), + (m_master->sas_verified())? "verified" : "NOT VERIFIED"); + return; + } + + if (stream != m_master) + return; + + // Master stream has just entered secure state. Start other + // streams in the multistream mode. + + debug("zrtp: Starting other streams (%d)\n", m_streams.size() - 1); + + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->start(m_master); + } + } +} + + +int Session::cmd_verify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(true, pf, arg); +} + + +int Session::cmd_unverify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(false, pf, arg); +} + + +int Session::cmd_sas(bool verify, struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = (struct cmd_arg *)arg; + (void)pf; + int id = -1; + Session *sess = NULL; + + if (str_isset(carg->prm)) + id = atoi(carg->prm); + + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() == id) { + sess = *it; + break; + } + } + + if (!sess) { + warning("zrtp: No session with id %d\n", id); + return EINVAL; + } + + if (!sess->m_master) { + warning("zrtp: No master stream for the session with id %d\n", + sess->id()); + return EFAULT; + } + + sess->m_master->verify_sas(verify); + + info("zrtp: Session <%d>: SAS [%s] is %s\n", sess->id(), + sess->m_master->get_sas(), + (sess->m_master->sas_verified())? "verified" : "NOT VERIFIED"); + + return 0; +} + diff --git a/modules/gzrtp/session.h b/modules/gzrtp/session.h new file mode 100644 index 0000000..90c12d6 --- /dev/null +++ b/modules/gzrtp/session.h @@ -0,0 +1,50 @@ +/** + * @file session.h GNU ZRTP: Session class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __SESSION_H +#define __SESSION_H + + +#include "stream.h" + + +class Stream; +class ZRTPConfig; + +class Session { +public: + Session(const ZRTPConfig& config); + + ~Session(); + + Stream *create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type); + + int start_stream(Stream *stream); + int id() const { return m_id; } + + bool request_master(Stream *stream); + void on_secure(Stream *stream); + + static int cmd_verify_sas(struct re_printf *pf, void *arg); + static int cmd_unverify_sas(struct re_printf *pf, void *arg); + static int cmd_sas(bool verify, struct re_printf *pf, void *arg); + +private: + static std::vector<Session *> s_sessl; + + const bool m_start_parallel; + int m_id; + std::vector<Stream *> m_streams; + Stream *m_master; + unsigned int m_encrypted; +}; + + +#endif // __SESSION_H + diff --git a/modules/gzrtp/srtp.cpp b/modules/gzrtp/srtp.cpp new file mode 100644 index 0000000..9aaa4b3 --- /dev/null +++ b/modules/gzrtp/srtp.cpp @@ -0,0 +1,327 @@ +/** + * @file srtp.cpp GNU ZRTP: SRTP processing + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#ifdef GZRTP_USE_RE_SRTP +#include <string.h> +#else +#include <srtp/CryptoContext.h> +#include <srtp/CryptoContextCtrl.h> +#include <srtp/SrtpHandler.h> +#endif + +#include "srtp.h" + + +Srtp::Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part) + +{ + const uint8_t *key, *salt; + uint32_t key_len, salt_len; + + err = EPERM; + +#ifdef GZRTP_USE_RE_SRTP + m_srtp = NULL; +#else + m_cc = NULL; + m_cc_ctrl = NULL; +#endif + + if (part == ForSender) { + // To encrypt packets: intiator uses initiator keys, + // responder uses responder keys + if (secrets->role == Initiator) { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + else { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + } + else if (part == ForReceiver) { + // To decrypt packets: intiator uses responder keys, + // responder initiator keys + if (secrets->role == Initiator) { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + else { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + } + else { + err = EINVAL; + return; + } + +#ifdef GZRTP_USE_RE_SRTP + + uint8_t key_buf[32 + 14]; // max key + salt + enum srtp_suite suite; + struct srtp *st; + + if (secrets->symEncAlgorithm == Aes && + secrets->authAlgorithm == Sha1) { + + if (key_len == 16 && secrets->srtpAuthTagLen == 32) + suite = SRTP_AES_CM_128_HMAC_SHA1_32; + + else if (key_len == 16 && secrets->srtpAuthTagLen == 80) + suite = SRTP_AES_CM_128_HMAC_SHA1_80; + + else if (key_len == 32 && secrets->srtpAuthTagLen == 32) + suite = SRTP_AES_256_CM_HMAC_SHA1_32; + + else if (key_len == 32 && secrets->srtpAuthTagLen == 80) + suite = SRTP_AES_256_CM_HMAC_SHA1_80; + + else { + err = ENOTSUP; + return; + } + } + else { + err = ENOTSUP; + return; + } + + if (salt_len != 14) { + err = EINVAL; + return; + } + + memcpy(key_buf, key, key_len); + memcpy(key_buf + key_len, salt, salt_len); + + err = srtp_alloc(&st, suite, key_buf, key_len + salt_len, 0); + if (err) + return; + + m_auth_tag_len = secrets->srtpAuthTagLen / 8; + m_srtp = st; + + err = 0; +#else + + CryptoContext *cc = NULL; + CryptoContextCtrl *cc_ctrl = NULL; + int cipher; + int authn; + int auth_key_len; + + switch (secrets->authAlgorithm) { + case Sha1: + authn = SrtpAuthenticationSha1Hmac; + auth_key_len = 20; + break; + case Skein: + authn = SrtpAuthenticationSkeinHmac; + auth_key_len = 32; + break; + default: + err = ENOTSUP; + return; + } + + switch (secrets->symEncAlgorithm) { + case Aes: + cipher = SrtpEncryptionAESCM; + break; + case TwoFish: + cipher = SrtpEncryptionTWOCM; + break; + default: + err = ENOTSUP; + return; + } + + cc = new CryptoContext( + 0, // SSRC (used for lookup) + 0, // Roll-Over-Counter (ROC) + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + cc_ctrl = new CryptoContextCtrl( + 0, // SSRC (used for lookup) + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + if (!cc || !cc_ctrl) { + delete cc; + delete cc_ctrl; + + err = ENOMEM; + return; + } + + cc->deriveSrtpKeys(0L); + cc_ctrl->deriveSrtcpKeys(); + + m_cc = cc; + m_cc_ctrl = cc_ctrl; + + err = 0; +#endif +} + + +Srtp::~Srtp() +{ +#ifdef GZRTP_USE_RE_SRTP + mem_deref(m_srtp); +#else + delete m_cc; + delete m_cc_ctrl; +#endif +} + + +int Srtp::protect_int(struct mbuf *mb, bool control) +{ + size_t len = mbuf_get_left(mb); + + int32_t extra = (mbuf_get_space(mb) > len)? + mbuf_get_space(mb) - len : 0; + +#ifdef GZRTP_USE_RE_SRTP + if (m_auth_tag_len + (control? 4 : 0) > extra) + return ENOMEM; + + if (control) + return srtcp_encrypt(m_srtp, mb); + else + return srtp_encrypt(m_srtp, mb); +#else + if (control) { + if (m_cc_ctrl->getTagLength() + 4 + + m_cc_ctrl->getMkiLength() > extra) + return ENOMEM; + } + else { + if (m_cc->getTagLength() + + m_cc->getMkiLength() > extra) + return ENOMEM; + } + + bool rc; + + if (control) + rc = SrtpHandler::protectCtrl(m_cc_ctrl, mbuf_buf(mb), + len, &len); + else + rc = SrtpHandler::protect(m_cc, mbuf_buf(mb), len, &len); + if (!rc) + return EPROTO; + + if (len > mbuf_get_space(mb)) { + // this should never happen + error_msg("zrtp: protect: length > space (%u > %u)\n", + len, mbuf_get_space(mb)); + abort(); + } + + mb->end = mb->pos + len; + + return 0; +#endif +} + + +int Srtp::protect(struct mbuf *mb) +{ + return protect_int(mb, false); +} + + +int Srtp::protect_ctrl(struct mbuf *mb) +{ + return protect_int(mb, true); +} + + +// return value: +// 0 - OK +// EBADMSG - SRTP/RTP packet decode error +// EAUTH - SRTP authentication failed +// EALREADY - SRTP replay check failed +// other errors +int Srtp::unprotect_int(struct mbuf *mb, bool control) +{ +#ifdef GZRTP_USE_RE_SRTP + if (control) + return srtcp_decrypt(m_srtp, mb); + else + return srtp_decrypt(m_srtp, mb); +#else + size_t len = mbuf_get_left(mb); + uint32_t rc; + int err; + + if (control) + rc = SrtpHandler::unprotectCtrl(m_cc_ctrl, mbuf_buf(mb), + len, &len); + else + rc = SrtpHandler::unprotect(m_cc, mbuf_buf(mb), + len, &len, NULL); + + switch (rc) { + case 1: err = 0; break; + case 0: err = EBADMSG; break; + case -1: err = EAUTH; break; + case -2: err = EALREADY; break; + default: err = EINVAL; + } + + if (!err) + mb->end = mb->pos + len; + + return err; +#endif +} + + +int Srtp::unprotect(struct mbuf *mb) +{ + return unprotect_int(mb, false); +} + + +int Srtp::unprotect_ctrl(struct mbuf *mb) +{ + return unprotect_int(mb, true); +} + diff --git a/modules/gzrtp/srtp.h b/modules/gzrtp/srtp.h new file mode 100644 index 0000000..d42f4cf --- /dev/null +++ b/modules/gzrtp/srtp.h @@ -0,0 +1,46 @@ +/** + * @file srtp.h GNU ZRTP: SRTP processing + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __SRTP_H +#define __SRTP_H + + +#include <libzrtpcpp/ZrtpCallback.h> + + +#ifdef GZRTP_USE_RE_SRTP +struct srtp; +#else +class CryptoContext; +class CryptoContextCtrl; +#endif + + +class Srtp { +public: + Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part); + ~Srtp(); + + int protect(struct mbuf *mb); + int protect_ctrl(struct mbuf *mb); + int unprotect(struct mbuf *mb); + int unprotect_ctrl(struct mbuf *mb); + +private: + int protect_int(struct mbuf *mb, bool control); + int unprotect_int(struct mbuf *mb, bool control); + +#ifdef GZRTP_USE_RE_SRTP + int32_t m_auth_tag_len; + struct srtp *m_srtp; +#else + CryptoContext *m_cc; + CryptoContextCtrl *m_cc_ctrl; +#endif +}; + + +#endif // __SRTP_H + diff --git a/modules/gzrtp/stream.cpp b/modules/gzrtp/stream.cpp new file mode 100644 index 0000000..84ad5e4 --- /dev/null +++ b/modules/gzrtp/stream.cpp @@ -0,0 +1,707 @@ +/** + * @file stream.cpp GNU ZRTP: Stream class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> +#include <pthread.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> +#include <libzrtpcpp/ZrtpStateClass.h> + +#include "session.h" +#include "stream.h" +#include "srtp.h" + + +// A burst of SRTP/SRTCP errors enough to display a warning +// Set to 1 to display all warnings +#define SRTP_ERR_BURST_THRESHOLD 20 + + +enum { + PRESZ = 36 /* Preamble size for TURN/STUN header */ +}; + + +enum pkt_type { + PKT_TYPE_UNKNOWN = 0, + PKT_TYPE_RTP = 1, + PKT_TYPE_RTCP = 2, + PKT_TYPE_ZRTP = 4 +}; + + +static enum pkt_type get_packet_type(const struct mbuf *mb) +{ + uint8_t b, pt; + uint32_t magic; + + if (mbuf_get_left(mb) < 8) + return PKT_TYPE_UNKNOWN; + + b = mbuf_buf(mb)[0]; + + if (127 < b && b < 192) { + pt = mbuf_buf(mb)[1] & 0x7f; + if (72 <= pt && pt <= 76) + return PKT_TYPE_RTCP; + else + return PKT_TYPE_RTP; + } + else { + memcpy(&magic, &mbuf_buf(mb)[4], 4); + magic = ntohl(magic); + if (magic == ZRTP_MAGIC) + return PKT_TYPE_ZRTP; + } + + return PKT_TYPE_UNKNOWN; +} + + +ZRTPConfig::ZRTPConfig(const struct conf *conf, const char *conf_dir) +{ +#ifdef GZRTP_USE_RE_SRTP + // Standard ciphers only + zrtp.clear(); + + zrtp.addAlgo(HashAlgorithm, zrtpHashes.getByName(s256)); + + zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes3)); + zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes1)); + + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec25)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh3k)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec38)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh2k)); + zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(mult)); + + zrtp.addAlgo(SasType, zrtpSasTypes.getByName(b32)); + + zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs32)); + zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs80)); +#else + zrtp.setStandardConfig(); +#endif + + str_ncpy(client_id, "baresip/gzrtp", sizeof(client_id)); + + re_snprintf(zid_filename, sizeof(zid_filename), + "%s/gzrtp.zid", conf_dir); + + start_parallel = true; + (void)conf_get_bool(conf, "zrtp_parallel", &start_parallel); +} + +SRTPStat::SRTPStat(const Stream *st, bool srtcp, uint64_t threshold) + : m_stream(st) + , m_control(srtcp) + , m_threshold(threshold) +{ + reset(); +} + + +void SRTPStat::update(int ret_code, bool quiet) +{ + const char *err_msg; + uint64_t *burst; + + // Srtp::unprotect/unprotect_ctrl return codes + switch (ret_code) { + case 0: + ++m_ok; + m_decode_burst = 0; + m_auth_burst = 0; + m_replay_burst = 0; + return; + case EBADMSG: + ++m_decode; + burst = &m_decode_burst; + err_msg = "packet decode error"; + break; + case EAUTH: + ++m_auth; + burst = &m_auth_burst; + err_msg = "authentication failed"; + break; + case EALREADY: + ++m_replay; + burst = &m_replay_burst; + err_msg = "replay check failed"; + break; + default: + warning("zrtp: %s unprotect failed: %m\n", + (m_control)? "SRTCP" : "SRTP", ret_code); + return; + } + + ++(*burst); + if (*burst == m_threshold) { + *burst = 0; + + if (!quiet) + warning("zrtp: Stream <%s>: %s %s, %d packets\n", + m_stream->media_name(), + (m_control)? "SRTCP" : "SRTP", + err_msg, + m_threshold); + } +} + + +void SRTPStat::reset() +{ + m_ok = 0; + m_decode = 0; m_auth = 0; m_replay = 0; + m_decode_burst = 0; m_auth_burst = 0; m_replay_burst = 0; +} + + +Stream::Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type) + : m_session(session) + , m_zrtp(NULL) + , m_started(false) + , m_local_ssrc(local_ssrc) + , m_peer_ssrc(0) + , m_rtpsock(NULL) + , m_rtcpsock(NULL) + , m_uh_rtp(NULL) + , m_uh_rtcp(NULL) + , m_media_type(media_type) + , m_send_srtp(NULL) + , m_recv_srtp(NULL) + , m_srtp_stat(this, false, SRTP_ERR_BURST_THRESHOLD) + , m_srtcp_stat(this, true, SRTP_ERR_BURST_THRESHOLD) +{ + err = 0; + + m_zrtp_seq = 1; // TODO: randomize + sa_init(&m_raddr, AF_INET); + tmr_init(&m_zrtp_timer); + + pthread_mutexattr_t attr; + err = pthread_mutexattr_init(&attr); + err |= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + err |= pthread_mutex_init(&m_zrtp_mutex, &attr); + err |= pthread_mutex_init(&m_send_mutex, &attr); + if (err) + return; + + int layer = 10; // above zero + if (rtpsock) { + m_rtpsock = (struct udp_sock *)mem_ref(rtpsock); + err |= udp_register_helper(&m_uh_rtp, rtpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (rtcpsock && (rtcpsock != rtpsock)) { + m_rtcpsock = (struct udp_sock *)mem_ref(rtcpsock); + err |= udp_register_helper(&m_uh_rtcp, rtcpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (err) + return; + + ZIDCache* zf = getZidCacheInstance(); + if (!zf->isOpen()) { + if (zf->open((char *)config.zid_filename) == -1) { + warning("zrtp: Couldn't open/create ZID file %s\n", + config.zid_filename); + err = ENOENT; + return; + } + } + + m_zrtp = new ZRtp((uint8_t *)zf->getZid(), this, config.client_id, + (ZrtpConfigure *)&config.zrtp, false, false); + if (!m_zrtp) { + err = ENOMEM; + return; + } + + return; +} + + +Stream::~Stream() +{ + stop(); + + delete m_zrtp; + + mem_deref(m_uh_rtp); + mem_deref(m_uh_rtcp); + mem_deref(m_rtpsock); + mem_deref(m_rtcpsock); + + pthread_mutex_destroy(&m_zrtp_mutex); + pthread_mutex_destroy(&m_send_mutex); + + tmr_cancel(&m_zrtp_timer); +} + + +int Stream::start(Stream *master) +{ + if (started()) + return EPERM; + + if (master) { + ZRtp *zrtp_master; + + std::string params = + master->m_zrtp->getMultiStrParams(&zrtp_master); + if (params.empty()) + return EPROTO; + + m_zrtp->setMultiStrParams(params, zrtp_master); + } + + debug("zrtp: Starting <%s> stream%s\n", media_name(), + (m_zrtp->isMultiStream())? " (multistream)" : ""); + + m_srtp_stat.reset(); + m_srtcp_stat.reset(); + m_sas.clear(); + m_ciphers.clear(); + + m_started = true; + m_zrtp->startZrtpEngine(); + + return 0; +} + + +void Stream::stop() +{ + if (!started()) + return; + + m_started = false; + + // If we got only a small amount of valid SRTP packets after ZRTP + // negotiation then assume that our peer couldn't store the RS data, + // thus make sure we have a second retained shared secret available. + // Refer to RFC 6189bis, chapter 4.6.1 50 packets are about 1 second + // of audio data + if (!m_zrtp->isMultiStream() && m_recv_srtp && m_srtp_stat.ok() < 20) { + + debug("zrtp: Stream <%s>: received too few valid SRTP " + "packets (%u), storing RS2\n", + media_name(), m_srtp_stat.ok()); + + m_zrtp->setRs2Valid(); + } + + debug("zrtp: Stopping <%s> stream\n", media_name()); + + m_zrtp->stopZrtp(); + + pthread_mutex_lock(&m_send_mutex); + delete m_send_srtp; + m_send_srtp = NULL; + pthread_mutex_unlock(&m_send_mutex); + + delete m_recv_srtp; + m_recv_srtp = NULL; + + debug("zrtp: Stream <%s> stopped\n", media_name()); +} + + +int Stream::sdp_encode(struct sdp_media *sdpm) +{ + // TODO: signaling hash + return 0; +} + + +int Stream::sdp_decode(const struct sdp_media *sdpm) +{ + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + m_raddr = *sdp_media_raddr(sdpm); + } + // TODO: signaling hash + + return 0; +} + + +bool Stream::udp_helper_send_cb(int *err, struct sa *src, struct mbuf *mb, + void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_send(err, src, mb); + + return false; +} + + +bool Stream::udp_helper_send(int *err, struct sa *src, struct mbuf *mb) +{ + bool ret = false; + enum pkt_type ptype = get_packet_type(mb); + size_t len = mbuf_get_left(mb); + int rerr = 0; + + pthread_mutex_lock(&m_send_mutex); + + if (ptype == PKT_TYPE_RTCP && m_send_srtp && len > 8) { + + rerr = m_send_srtp->protect_ctrl(mb); + } + else if (ptype == PKT_TYPE_RTP && m_send_srtp && + len > RTP_HEADER_SIZE) { + + rerr = m_send_srtp->protect(mb); + } + else + goto out; + + if (rerr) { + warning("zrtp: protect/protect_ctrl failed (len=%u): %m\n", + len, rerr); + + if (rerr == ENOMEM) + *err = rerr; + // drop + ret = true; + } + + out: + pthread_mutex_unlock(&m_send_mutex); + + return ret; +} + + +bool Stream::udp_helper_recv_cb(struct sa *src, struct mbuf *mb, void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_recv(src, mb); + + return false; +} + + +bool Stream::udp_helper_recv(struct sa *src, struct mbuf *mb) +{ + if (!started()) + return false; + + enum pkt_type ptype = get_packet_type(mb); + int err = 0; + + if (ptype == PKT_TYPE_RTCP && m_recv_srtp) { + + err = m_recv_srtp->unprotect_ctrl(mb); + + m_srtcp_stat.update(err); + } + else if (ptype == PKT_TYPE_RTP && m_recv_srtp) { + + err = m_recv_srtp->unprotect(mb); + + m_srtp_stat.update(err); + + if (!err) { + // Got a good SRTP, check state and if in WaitConfAck + // (an Initiator state) then simulate a conf2Ack, + // refer to RFC 6189, chapter 4.6, last paragraph + if (m_zrtp->inState(WaitConfAck)) + m_zrtp->conf2AckSecure(); + } + } + else if (ptype == PKT_TYPE_ZRTP) { + return recv_zrtp(mb); + } + else + return false; + + if (err) + // drop + return true; + + return false; +} + + +// <RTP> + <ext. header> + <ZRTP message type> + CRC32 +#define ZRTP_MIN_PACKET_LENGTH (RTP_HEADER_SIZE + 4 + 8 + 4) + +bool Stream::recv_zrtp(struct mbuf *mb) +{ + uint32_t crc32; + uint8_t *buf = mbuf_buf(mb); + size_t size = mbuf_get_left(mb); + + if (size < ZRTP_MIN_PACKET_LENGTH) { + warning("zrtp: incoming packet size (%d) is too small\n", + size); + return false; + } + + // check CRC + memcpy(&crc32, buf + size - 4, 4); + crc32 = ntohl(crc32); + if (!zrtpCheckCksum(buf, size - 4, crc32)) { + sendInfo(GnuZrtpCodes::Warning, + GnuZrtpCodes::WarningCRCmismatch); + return false; + } + + // store peer's SSRC for creating the CryptoContext + memcpy(&m_peer_ssrc, buf + 8, 4); + m_peer_ssrc = ntohl(m_peer_ssrc); + + m_zrtp->processZrtpMessage(buf + RTP_HEADER_SIZE, m_peer_ssrc, size); + + return true; +} + + +void Stream::verify_sas(bool verify) +{ + if (verify) + m_zrtp->SASVerified(); + else + m_zrtp->resetSASVerified(); +} + + +bool Stream::sas_verified() +{ + return m_zrtp->isSASVerified(); +} + + +// +// callbacks +// + + +int32_t Stream::sendDataZRTP(const uint8_t* data, int32_t length) +{ + struct mbuf *mb; + uint8_t *crc_buf; + uint32_t crc32; + size_t start_pos = PRESZ; + int err = 0; + + if (!sa_isset(&m_raddr, SA_ALL)) + return 0; + + mb = mbuf_alloc(start_pos + RTP_HEADER_SIZE + length); + if (!mb) + return 0; + + mbuf_set_end(mb, start_pos); + mbuf_set_pos(mb, start_pos); + crc_buf = mbuf_buf(mb); + + // write RTP header + err = mbuf_write_u8(mb, 0x10); + err |= mbuf_write_u8(mb, 0x00); + err |= mbuf_write_u16(mb, htons(m_zrtp_seq++)); + err |= mbuf_write_u32(mb, htonl(ZRTP_MAGIC)); + err |= mbuf_write_u32(mb, htonl(m_local_ssrc)); + + // copy ZRTP message data + err |= mbuf_write_mem(mb, data, length - 4); + + // compute CRC + crc32 = zrtpGenerateCksum(crc_buf, RTP_HEADER_SIZE + length - 4); + crc32 = zrtpEndCksum(crc32); + + // store CRC + err |= mbuf_write_u32(mb, htonl(crc32)); + if (err) + goto out; + + // send ZRTP packet using RTP socket + mbuf_set_pos(mb, start_pos); + err = udp_send_helper(m_rtpsock, &m_raddr, mb, m_uh_rtp); + if (err) + warning("zrtp: udp_send_helper: %m\n", err); + + out: + mem_deref(mb); + + return (err == 0); +} + + +void Stream::zrtp_timer_cb(void *arg) +{ + Stream *s = (Stream *)arg; + + s->m_zrtp->processTimeout(); +} + + +int32_t Stream::activateTimer(int32_t time) +{ + tmr_start(&m_zrtp_timer, time, &Stream::zrtp_timer_cb, this); + return 1; +} + + +int32_t Stream::cancelTimer() +{ + tmr_cancel(&m_zrtp_timer); + return 1; +} + + +void Stream::sendInfo(GnuZrtpCodes::MessageSeverity severity, int32_t subCode) +{ + print_message(severity, subCode); + + if (severity == GnuZrtpCodes::Info) { + if (subCode == GnuZrtpCodes::InfoSecureStateOn) { + m_session->on_secure(this); + } + else if (subCode == GnuZrtpCodes::InfoHelloReceived && + !m_zrtp->isMultiStream()) { + + m_session->request_master(this); + } + } +} + + +bool Stream::srtpSecretsReady(SrtpSecret_t* secrets, EnableSecurity part) +{ + Srtp *s; + int err = 0; + + debug("zrtp: Stream <%s>: secrets are ready for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + s = new Srtp(err, secrets, part); + if (!s || err) { + warning("zrtp: Stream <%s>: Srtp creation failed: %m\n", + media_name(), err); + delete s; + return false; + } + + if (part == ForSender) { + pthread_mutex_lock(&m_send_mutex); + m_send_srtp = s; + pthread_mutex_unlock(&m_send_mutex); + } + else if (part == ForReceiver) + m_recv_srtp = s; + else + return false; + + return true; +} + + +void Stream::srtpSecretsOff(EnableSecurity part) +{ + debug("zrtp: Stream <%s>: secrets are off for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + if (part == ForSender) { + pthread_mutex_lock(&m_send_mutex); + delete m_send_srtp; + m_send_srtp = NULL; + pthread_mutex_unlock(&m_send_mutex); + } + + if (part == ForReceiver) { + delete m_recv_srtp; + m_recv_srtp = NULL; + } +} + + +void Stream::srtpSecretsOn(std::string c, std::string s, bool verified) +{ + m_sas = s; + m_ciphers = c; + + if (s.empty()) { + info("zrtp: Stream <%s> is encrypted (%s)\n", + media_name(), c.c_str()); + } + else { + info("zrtp: Stream <%s> is encrypted (%s), " + "SAS is [%s] (%s)\n", + media_name(), c.c_str(), s.c_str(), + (verified)? "verified" : "NOT VERIFIED"); + if (!verified) + warning("zrtp: SAS is not verified, type " + "'/zrtp_verify %d' to verify\n", + m_session->id()); + } +} + + +void Stream::handleGoClear() +{ +} + + +void Stream::zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode) +{ +} + + +void Stream::zrtpNotSuppOther() +{ +} + + +void Stream::synchEnter() +{ + pthread_mutex_lock(&m_zrtp_mutex); +} + + +void Stream::synchLeave() +{ + pthread_mutex_unlock(&m_zrtp_mutex); +} + + +void Stream::zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::signSAS(uint8_t* sasHash) +{ +} + + +bool Stream::checkSASSignature(uint8_t* sasHash) +{ + return true; +} + diff --git a/modules/gzrtp/stream.h b/modules/gzrtp/stream.h new file mode 100644 index 0000000..d25a5c1 --- /dev/null +++ b/modules/gzrtp/stream.h @@ -0,0 +1,138 @@ +/** + * @file stream.h GNU ZRTP: Stream class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __STREAM_H +#define __STREAM_H + + +#include <libzrtpcpp/ZRtp.h> + + +enum StreamMediaType { + MT_UNKNOWN = 0, + MT_AUDIO, + MT_VIDEO, + MT_TEXT, + MT_APPLICATION, + MT_MESSAGE +}; + + +class ZRTPConfig { +public: + ZRTPConfig(const struct conf *conf, const char *conf_dir); +private: + friend class Stream; + friend class Session; + + ZrtpConfigure zrtp; + + char client_id[CLIENT_ID_SIZE + 1]; + char zid_filename[256]; + + bool start_parallel; +}; + + +class Stream; + +class SRTPStat { +public: + SRTPStat(const Stream *st, bool srtcp, uint64_t threshold); + void update(int ret_code, bool quiet = false); + void reset(); + uint64_t ok() { return m_ok; } +private: + const Stream *m_stream; + const bool m_control; + const uint64_t m_threshold; + uint64_t m_ok, m_decode, m_auth, m_replay; + uint64_t m_decode_burst, m_auth_burst, m_replay_burst; +}; + + +class Session; +class Srtp; + +class Stream : public ZrtpCallback { +public: + Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type); + + virtual ~Stream(); + + int start(Stream *master); + void stop(); + bool started() { return m_started; } + + int sdp_encode(struct sdp_media *sdpm); + int sdp_decode(const struct sdp_media *sdpm); + + const char *media_name() const; + + const char *get_sas() const { return m_sas.c_str(); } + const char *get_ciphers() const { return m_ciphers.c_str(); } + bool sas_verified(); + void verify_sas(bool verify); + +private: + static void zrtp_timer_cb(void *arg); + static bool udp_helper_send_cb(int *err, struct sa *src, + struct mbuf *mb, void *arg); + static bool udp_helper_recv_cb(struct sa *src, struct mbuf *mb, + void *arg); + + bool udp_helper_send(int *err, struct sa *src, struct mbuf *mb); + bool udp_helper_recv(struct sa *src, struct mbuf *mb); + bool recv_zrtp(struct mbuf *mb); + + void print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode); + + Session *m_session; + ZRtp *m_zrtp; + bool m_started; + struct tmr m_zrtp_timer; + pthread_mutex_t m_zrtp_mutex; + uint16_t m_zrtp_seq; + uint32_t m_local_ssrc, m_peer_ssrc; + struct sa m_raddr; + struct udp_sock *m_rtpsock, *m_rtcpsock; + struct udp_helper *m_uh_rtp; + struct udp_helper *m_uh_rtcp; + StreamMediaType m_media_type; + Srtp *m_send_srtp, *m_recv_srtp; + pthread_mutex_t m_send_mutex; + SRTPStat m_srtp_stat, m_srtcp_stat; + std::string m_sas, m_ciphers; + +protected: + virtual int32_t sendDataZRTP(const uint8_t* data, int32_t length); + virtual int32_t activateTimer(int32_t time); + virtual int32_t cancelTimer(); + virtual void sendInfo(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual bool srtpSecretsReady(SrtpSecret_t* secrets, + EnableSecurity part); + virtual void srtpSecretsOff(EnableSecurity part); + virtual void srtpSecretsOn(std::string c, std::string s, + bool verified); + virtual void handleGoClear(); + virtual void zrtpNegotiationFailed( + GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual void zrtpNotSuppOther(); + virtual void synchEnter(); + virtual void synchLeave(); + virtual void zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void signSAS(uint8_t* sasHash); + virtual bool checkSASSignature(uint8_t* sasHash); +}; + + +#endif // __STREAM_H + diff --git a/modules/h265/README b/modules/h265/README new file mode 100644 index 0000000..75967c0 --- /dev/null +++ b/modules/h265/README @@ -0,0 +1,29 @@ +README h265 module +------------------ + + +Steps for building and testing: + + +1. build and install x265 from https://github.com/mirror/x265.git +2. build and install ffmpeg from git://source.ffmpeg.org/ffmpeg.git + + $ ./configure --disable-everything --enable-decoder=hevc \ + && make -j4 && sudo make install + +3. build baresip with H265 module: + + $ cd baresip + $ make EXTRA_MODULES=h265 + +4. add h265.so to $HOME/.baresip/config + module h265.so + +5. start baresip with the "vidloop" module, to test a local loop + from a suitable vidsrc and vidisp module: + + $ ./baresip -evv + + + +[END] diff --git a/modules/h265/TODO b/modules/h265/TODO new file mode 100644 index 0000000..e4d03dc --- /dev/null +++ b/modules/h265/TODO @@ -0,0 +1,5 @@ +TODO: + +done - get encoder working +done - get decoder working +done - add timestamp to vidcodec API diff --git a/modules/h265/decode.c b/modules/h265/decode.c new file mode 100644 index 0000000..95d7102 --- /dev/null +++ b/modules/h265/decode.c @@ -0,0 +1,337 @@ +/** + * @file h265/decode.c H.265 Decode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libavutil/pixdesc.h> +#include <libavcodec/avcodec.h> +#include "h265.h" + + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + + +enum { + FU_HDR_SIZE = 1 +}; + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct fu { + unsigned s:1; + unsigned e:1; + unsigned type:6; +}; + +struct viddec_state { + AVCodecContext *ctx; + AVFrame *pict; + struct mbuf *mb; + size_t frag_start; + bool frag; + uint16_t frag_seq; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + if (vds->ctx) { + avcodec_close(vds->ctx); + av_free(vds->ctx); + } + + if (vds->pict) + av_free(vds->pict); + + mem_deref(vds->mb); +} + + +int h265_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp) +{ + struct viddec_state *vds; + AVCodec *codec; + int err = 0; + (void)vc; + (void)fmtp; + + if (!vdsp) + return EINVAL; + + vds = *vdsp; + + if (vds) + return 0; + + /* HEVC = H.265 */ + codec = avcodec_find_decoder(AV_CODEC_ID_HEVC); + if (!codec) { + warning("h265: could not find H265 decoder\n"); + return ENOSYS; + } + + vds = mem_zalloc(sizeof(*vds), destructor); + if (!vds) + return ENOMEM; + + vds->mb = mbuf_alloc(1024); + if (!vds->mb) { + err = ENOMEM; + goto out; + } + + vds->pict = av_frame_alloc(); + if (!vds->pict) { + err = ENOMEM; + goto out; + } + + vds->ctx = avcodec_alloc_context3(codec); + if (!vds->ctx) { + err = ENOMEM; + goto out; + } + + if (avcodec_open2(vds->ctx, codec, NULL) < 0) { + err = ENOMEM; + goto out; + } + + out: + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +static inline int fu_decode(struct fu *fu, struct mbuf *mb) +{ + uint8_t v; + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + v = mbuf_read_u8(mb); + + fu->s = v>>7 & 0x1; + fu->e = v>>6 & 0x1; + fu->type = v>>0 & 0x3f; + + return 0; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +static inline void fragment_rewind(struct viddec_state *vds) +{ + vds->mb->pos = vds->frag_start; + vds->mb->end = vds->frag_start; +} + + +int h265_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb) +{ + static const uint8_t nal_seq[3] = {0, 0, 1}; + int err, ret, got_picture, i; + struct h265_nal hdr; + AVPacket avpkt; + enum vidfmt fmt; + + if (!vds || !frame || !intra || !mb) + return EINVAL; + + *intra = false; + + err = h265_nal_decode(&hdr, mbuf_buf(mb)); + if (err) + return err; + + mbuf_advance(mb, H265_HDR_SIZE); + +#if 0 + debug("h265: decode: %s type=%2d %s\n", + h265_is_keyframe(hdr.nal_unit_type) ? "<KEY>" : " ", + hdr.nal_unit_type, + h265_nalunit_name(hdr.nal_unit_type)); +#endif + + if (vds->frag && hdr.nal_unit_type != H265_NAL_FU) { + debug("h265: lost fragments; discarding previous NAL\n"); + fragment_rewind(vds); + vds->frag = false; + } + + /* handle NAL types */ + if (0 <= hdr.nal_unit_type && hdr.nal_unit_type <= 40) { + + if (h265_is_keyframe(hdr.nal_unit_type)) + *intra = true; + + mb->pos -= H265_HDR_SIZE; + + err = mbuf_write_mem(vds->mb, nal_seq, 3); + err |= mbuf_write_mem(vds->mb, mbuf_buf(mb),mbuf_get_left(mb)); + if (err) + goto out; + } + else if (H265_NAL_FU == hdr.nal_unit_type) { + + struct fu fu; + + err = fu_decode(&fu, mb); + if (err) + return err; + + if (fu.s) { + if (h265_is_keyframe(fu.type)) + *intra = true; + + if (vds->frag) { + debug("h265: lost fragments; ignoring NAL\n"); + fragment_rewind(vds); + } + + vds->frag_start = vds->mb->pos; + vds->frag = true; + + hdr.nal_unit_type = fu.type; + + err = mbuf_write_mem(vds->mb, nal_seq, 3); + err = h265_nal_encode_mbuf(vds->mb, &hdr); + if (err) + goto out; + } + else { + if (!vds->frag) { + debug("h265: ignoring fragment\n"); + return 0; + } + + if (seq_diff(vds->frag_seq, seq) != 1) { + debug("h265: lost fragments detected\n"); + fragment_rewind(vds); + vds->frag = false; + return 0; + } + } + + err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb)); + if (err) + goto out; + + if (fu.e) + vds->frag = false; + + vds->frag_seq = seq; + } + else { + warning("h265: unknown NAL type %u (%s) [%zu bytes]\n", + hdr.nal_unit_type, + h265_nalunit_name(hdr.nal_unit_type), + mbuf_get_left(mb)); + return EPROTO; + } + + if (!marker) { + + if (vds->mb->end > DECODE_MAXSZ) { + warning("h265: decode buffer size exceeded\n"); + err = ENOMEM; + goto out; + } + + return 0; + } + + if (vds->frag) { + err = EPROTO; + goto out; + } + + av_init_packet(&avpkt); + avpkt.data = vds->mb->buf; + avpkt.size = (int)vds->mb->end; + +#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100) + + ret = avcodec_send_packet(vds->ctx, &avpkt); + if (ret < 0) { + err = EBADMSG; + goto out; + } + + ret = avcodec_receive_frame(vds->ctx, vds->pict); + if (ret < 0) { + err = EBADMSG; + goto out; + } + + got_picture = true; + +#else + ret = avcodec_decode_video2(vds->ctx, vds->pict, &got_picture, &avpkt); + if (ret < 0) { + debug("h265: decode error\n"); + err = EPROTO; + goto out; + } +#endif + + if (!got_picture) { + /* debug("h265: no picture\n"); */ + goto out; + } + + switch (vds->pict->format) { + + case AV_PIX_FMT_YUV420P: + fmt = VID_FMT_YUV420P; + break; + + case AV_PIX_FMT_YUV444P: + fmt = VID_FMT_YUV444P; + break; + + default: + warning("h265: decode: bad pixel format (%i) (%s)\n", + vds->pict->format, + av_get_pix_fmt_name(vds->pict->format)); + goto out; + } + + for (i=0; i<4; i++) { + frame->data[i] = vds->pict->data[i]; + frame->linesize[i] = vds->pict->linesize[i]; + } + + frame->size.w = vds->ctx->width; + frame->size.h = vds->ctx->height; + frame->fmt = fmt; + + out: + mbuf_rewind(vds->mb); + vds->frag = false; + + return err; +} diff --git a/modules/h265/encode.c b/modules/h265/encode.c new file mode 100644 index 0000000..f4835a3 --- /dev/null +++ b/modules/h265/encode.c @@ -0,0 +1,292 @@ +/** + * @file h265/encode.c H.265 Encode + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <x265.h> +#include "h265.h" + + +struct videnc_state { + struct vidsz size; + x265_param *param; + x265_encoder *x265; + int64_t pts; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + videnc_packet_h *pkth; + void *arg; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *st = arg; + + if (st->x265) + x265_encoder_close(st->x265); + if (st->param) + x265_param_free(st->param); +} + + +static int set_params(struct videnc_state *st, unsigned fps, unsigned bitrate) +{ + st->param = x265_param_alloc(); + if (!st->param) { + warning("h265: x265_param_alloc failed\n"); + return ENOMEM; + } + + x265_param_default(st->param); + + if (0 != x265_param_apply_profile(st->param, "main")) { + warning("h265: x265_param_apply_profile failed\n"); + return EINVAL; + } + + if (0 != x265_param_default_preset(st->param, + "ultrafast", "zerolatency")) { + + warning("h265: x265_param_default_preset error\n"); + return EINVAL; + } + + st->param->fpsNum = fps; + st->param->fpsDenom = 1; + + /* VPS, SPS and PPS headers should be output with each keyframe */ + st->param->bRepeatHeaders = 1; + + /* Rate Control */ + st->param->rc.rateControlMode = X265_RC_CRF; + st->param->rc.bitrate = bitrate / 1000; + st->param->rc.vbvMaxBitrate = bitrate / 1000; + st->param->rc.vbvBufferSize = 2 * bitrate / fps; + + return 0; +} + + +int h265_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *ves; + int err = 0; + (void)fmtp; + + if (!vesp || !vc || !prm || prm->pktsize < 3 || !pkth) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), destructor); + if (!ves) + return ENOMEM; + + *vesp = ves; + } + else { + if (ves->x265 && (ves->bitrate != prm->bitrate || + ves->pktsize != prm->pktsize || + ves->fps != prm->fps)) { + + x265_encoder_close(ves->x265); + ves->x265 = NULL; + } + } + + ves->bitrate = prm->bitrate; + ves->pktsize = prm->pktsize; + ves->fps = prm->fps; + ves->pkth = pkth; + ves->arg = arg; + + err = set_params(ves, prm->fps, prm->bitrate); + if (err) + return err; + + return 0; +} + + +static int open_encoder(struct videnc_state *st, const struct vidsz *size) +{ + if (st->x265) { + debug("h265: re-opening encoder\n"); + x265_encoder_close(st->x265); + } + + st->param->sourceWidth = size->w; + st->param->sourceHeight = size->h; + + st->x265 = x265_encoder_open(st->param); + if (!st->x265) { + warning("h265: x265_encoder_open failed\n"); + return ENOMEM; + } + + return 0; +} + + +static inline int packetize(bool marker, const uint8_t *buf, size_t len, + size_t maxlen, uint32_t rtp_ts, + videnc_packet_h *pkth, void *arg) +{ + int err = 0; + + if (len <= maxlen) { + err = pkth(marker, rtp_ts, NULL, 0, buf, len, arg); + } + else { + struct h265_nal nal; + uint8_t fu_hdr[3]; + const size_t flen = maxlen - sizeof(fu_hdr); + + err = h265_nal_decode(&nal, buf); + if (err) { + warning("h265: encode: could not decode" + " NAL of %zu bytes (%m)\n", len, err); + return err; + } + + h265_nal_encode(fu_hdr, H265_NAL_FU, + nal.nuh_temporal_id_plus1); + + fu_hdr[2] = 1<<7 | nal.nal_unit_type; + + buf+=2; + len-=2; + + while (len > flen) { + err |= pkth(false, rtp_ts, fu_hdr, 3, buf, flen, + arg); + + buf += flen; + len -= flen; + fu_hdr[2] &= ~(1 << 7); /* clear Start bit */ + } + + fu_hdr[2] |= 1<<6; /* set END bit */ + + err |= pkth(marker, rtp_ts, fu_hdr, 3, buf, len, + arg); + } + + return err; +} + + +int h265_encode(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + x265_picture *pic_in = NULL, pic_out; + x265_nal *nalv; + uint32_t i, nalc = 0; + int colorspace; + int n, err = 0; + uint32_t ts; + + if (!st || !frame) + return EINVAL; + + switch (frame->fmt) { + + case VID_FMT_YUV420P: + colorspace = X265_CSP_I420; + break; + + case VID_FMT_YUV444P: + colorspace = X265_CSP_I444; + break; + + default: + warning("h265: encode: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + return EINVAL; + } + + if (!st->x265 || !vidsz_cmp(&st->size, &frame->size) || + st->param->internalCsp != colorspace) { + + debug("h265: encoder: reset %u x %u (%s)\n", + frame->size.w, frame->size.h, vidfmt_name(frame->fmt)); + + st->param->internalCsp = colorspace; + + err = open_encoder(st, &frame->size); + if (err) + return err; + + st->size = frame->size; + } + + if (update) { + debug("h265: encode: picture update was requested\n"); + } + + pic_in = x265_picture_alloc(); + if (!pic_in) { + warning("h265: x265_picture_alloc failed\n"); + return ENOMEM; + } + + x265_picture_init(st->param, pic_in); + + pic_in->sliceType = update ? X265_TYPE_IDR : X265_TYPE_AUTO; + pic_in->pts = ++st->pts; /* XXX: add PTS to API */ + pic_in->colorSpace = colorspace; + + for (i=0; i<3; i++) { + pic_in->planes[i] = frame->data[i]; + pic_in->stride[i] = frame->linesize[i]; + } + + /* NOTE: important to get the PTS of the "out" picture */ + n = x265_encoder_encode(st->x265, &nalv, &nalc, pic_in, &pic_out); + if (n <= 0) + goto out; + + ts = video_calc_rtp_timestamp(pic_out.pts, st->fps); + + for (i=0; i<nalc; i++) { + + x265_nal *nal = &nalv[i]; + uint8_t *p = nal->payload; + size_t len = nal->sizeBytes; + bool marker; + +#if 0 + debug("h265: encode: %s type=%2d %s\n", + h265_is_keyframe(nal->type) ? "<KEY>" : " ", + nal->type, h265_nalunit_name(nal->type)); +#endif + + h265_skip_startcode(&p, &len); + + /* XXX: use pic_out.pts */ + + marker = (i+1)==nalc; /* last NAL */ + + err = packetize(marker, p, len, st->pktsize, + ts, st->pkth, st->arg); + if (err) + goto out; + } + + out: + if (pic_in) + x265_picture_free(pic_in); + + return err; +} diff --git a/modules/h265/fmt.c b/modules/h265/fmt.c new file mode 100644 index 0000000..ac71b10 --- /dev/null +++ b/modules/h265/fmt.c @@ -0,0 +1,165 @@ +/** + * @file h265/fmt.c H.265 Video Codec -- protocol format + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "h265.h" + + +/* +1.1.4 NAL Unit Header + + HEVC maintains the NAL unit concept of H.264 with modifications. + HEVC uses a two-byte NAL unit header, as shown in Figure 1. The + payload of a NAL unit refers to the NAL unit excluding the NAL unit + header. + + +---------------+---------------+ + |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |F| Type | LayerId | TID | + +-------------+-----------------+ + + Figure 1 The structure of HEVC NAL unit header +*/ + + +void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type, + unsigned nuh_temporal_id_plus1) +{ + if (!buf) + return; + + buf[0] = (nal_unit_type & 0x3f) << 1; + buf[1] = nuh_temporal_id_plus1 & 0x07; +} + + +int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal) +{ + uint8_t buf[2]; + + h265_nal_encode(buf, nal->nal_unit_type, nal->nuh_temporal_id_plus1); + + return mbuf_write_mem(mb, buf, sizeof(buf)); +} + + +int h265_nal_decode(struct h265_nal *nal, const uint8_t *p) +{ + bool forbidden_zero_bit; + unsigned nuh_layer_id; + + if (!nal || !p) + return EINVAL; + + forbidden_zero_bit = p[0] >> 7; + nal->nal_unit_type = (p[0] >> 1) & 0x3f; + nuh_layer_id = (p[0]&1)<<5 | p[1] >> 3; + nal->nuh_temporal_id_plus1 = p[1] & 0x07; + + if (forbidden_zero_bit) { + warning("h265: nal_decode: FORBIDDEN bit set\n"); + return EBADMSG; + } + if (nuh_layer_id != 0) { + warning("h265: nal_decode: LayerId MUST be zero\n"); + return EBADMSG; + } + + return 0; +} + + +void h265_nal_print(const struct h265_nal *nal) +{ + re_printf("type=%u(%s), TID=%u\n", + nal->nal_unit_type, + h265_nalunit_name(nal->nal_unit_type), + nal->nuh_temporal_id_plus1); +} + + +static const uint8_t sc3[3] = {0, 0, 1}; +static const uint8_t sc4[4] = {0, 0, 0, 1}; + + +void h265_skip_startcode(uint8_t **p, size_t *n) +{ + if (*n < 4) + return; + + if (0 == memcmp(*p, sc4, 4)) { + (*p) += 4; + *n -= 4; + } + else if (0 == memcmp(*p, sc3, 3)) { + (*p) += 3; + *n -= 3; + } +} + + +bool h265_have_startcode(const uint8_t *p, size_t len) +{ + if (len >= 4 && 0 == memcmp(p, sc4, 4)) return true; + if (len >= 3 && 0 == memcmp(p, sc3, 3)) return true; + + return false; +} + + +bool h265_is_keyframe(enum h265_naltype type) +{ + /* between 16 and 21 (inclusive) */ + switch (type) { + + case H265_NAL_BLA_W_LP: + case H265_NAL_BLA_W_RADL: + case H265_NAL_BLA_N_LP: + case H265_NAL_IDR_W_RADL: + case H265_NAL_IDR_N_LP: + case H265_NAL_CRA_NUT: + return true; + default: + return false; + } +} + + +const char *h265_nalunit_name(enum h265_naltype type) +{ + switch (type) { + + /* VCL class */ + case H265_NAL_TRAIL_N: return "TRAIL_N"; + case H265_NAL_TRAIL_R: return "TRAIL_R"; + + case H265_NAL_RASL_N: return "RASL_N"; + case H265_NAL_RASL_R: return "RASL_R"; + + case H265_NAL_BLA_W_LP: return "BLA_W_LP"; + case H265_NAL_BLA_W_RADL: return "BLA_W_RADL"; + case H265_NAL_BLA_N_LP: return "BLA_N_LP"; + case H265_NAL_IDR_W_RADL: return "IDR_W_RADL"; + case H265_NAL_IDR_N_LP: return "IDR_N_LP"; + case H265_NAL_CRA_NUT: return "CRA_NUT"; + + /* non-VCL class */ + case H265_NAL_VPS_NUT: return "VPS_NUT"; + case H265_NAL_SPS_NUT: return "SPS_NUT"; + case H265_NAL_PPS_NUT: return "PPS_NUT"; + case H265_NAL_PREFIX_SEI_NUT: return "PREFIX_SEI_NUT"; + case H265_NAL_SUFFIX_SEI_NUT: return "SUFFIX_SEI_NUT"; + + /* draft-ietf-payload-rtp-h265 */ + case H265_NAL_AP: return "H265_NAL_AP"; + case H265_NAL_FU: return "H265_NAL_FU"; + } + + return "???"; +} diff --git a/modules/h265/h265.c b/modules/h265/h265.c new file mode 100644 index 0000000..fc289e2 --- /dev/null +++ b/modules/h265/h265.c @@ -0,0 +1,67 @@ +/** + * @file h265.c H.265 Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include <libavcodec/avcodec.h> +#include <x265.h> +#include "h265.h" + + +/** + * @defgroup h265 h265 + * + * The H.265 video codec (aka HEVC) + * + * This is an experimental module adding support for H.265 video codec. + * The encoder is using x265 and the decoder is using libavcodec. + * + * + * References: + * + * https://tools.ietf.org/html/rfc7798 + * http://x265.org/ + * https://www.ffmpeg.org/ + */ + + +static struct vidcodec h265 = { + .name = "H265", + .fmtp = "profile-id=1", + .encupdh = h265_encode_update, + .ench = h265_encode, + .decupdh = h265_decode_update, + .dech = h265_decode, +}; + + +static int module_init(void) +{ + info("h265: using x265 %s %s\n", + x265_version_str, x265_build_info_str); + + avcodec_register_all(); + + vidcodec_register(baresip_vidcodecl(), &h265); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&h265); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(h265) = { + "h265", + "vidcodec", + module_init, + module_close, +}; diff --git a/modules/h265/h265.h b/modules/h265/h265.h new file mode 100644 index 0000000..1b1571f --- /dev/null +++ b/modules/h265/h265.h @@ -0,0 +1,70 @@ +/** + * @file h265.h H.265 Video Codec -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +/* + * H.265 format + */ +enum { + H265_HDR_SIZE = 2 +}; + +enum h265_naltype { + /* VCL class */ + H265_NAL_TRAIL_N = 0, + H265_NAL_TRAIL_R = 1, + + H265_NAL_RASL_N = 8, + H265_NAL_RASL_R = 9, + + H265_NAL_BLA_W_LP = 16, + H265_NAL_BLA_W_RADL = 17, + H265_NAL_BLA_N_LP = 18, + H265_NAL_IDR_W_RADL = 19, + H265_NAL_IDR_N_LP = 20, + H265_NAL_CRA_NUT = 21, + + /* non-VCL class */ + H265_NAL_VPS_NUT = 32, + H265_NAL_SPS_NUT = 33, + H265_NAL_PPS_NUT = 34, + H265_NAL_PREFIX_SEI_NUT = 39, + H265_NAL_SUFFIX_SEI_NUT = 40, + + /* draft-ietf-payload-rtp-h265 */ + H265_NAL_AP = 48, /* Aggregation Packets */ + H265_NAL_FU = 49, +}; + +struct h265_nal { + unsigned nal_unit_type:6; /* NAL unit type (0-40) */ + unsigned nuh_temporal_id_plus1:3; /* temporal identifier plus 1 */ +}; + +void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type, + unsigned nuh_temporal_id_plus1); +int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal); +int h265_nal_decode(struct h265_nal *nal, const uint8_t *p); +void h265_nal_print(const struct h265_nal *nal); + +bool h265_have_startcode(const uint8_t *p, size_t len); +void h265_skip_startcode(uint8_t **p, size_t *n); +bool h265_is_keyframe(enum h265_naltype type); +const char *h265_nalunit_name(enum h265_naltype type); + + +/* encoder */ +int h265_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int h265_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + +/* decoder */ +int h265_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int h265_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb); diff --git a/modules/h265/module.mk b/modules/h265/module.mk new file mode 100644 index 0000000..12980bf --- /dev/null +++ b/modules/h265/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := h265 +$(MOD)_SRCS += h265.c encode.c decode.c fmt.c +$(MOD)_LFLAGS += -lavcodec -lavutil -lx265 + +include mk/mod.mk diff --git a/modules/h265/notes b/modules/h265/notes new file mode 100644 index 0000000..0f01c5a --- /dev/null +++ b/modules/h265/notes @@ -0,0 +1,75 @@ +notes: +----- + + +x265 [info]: HEVC encoder version 1.4-253-g920d714 +x265 [info]: build info [Linux][GCC 4.9.1][64 bit] 8bpp +x265 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX +x265 [info]: Main profile, Level-2 (Main tier) +x265 [info]: WPP streams / frame threads / pool : 8 / 2 / 4 +x265 [info]: CTU size / RQT depth inter / intra : 32 / 1 / 1 +x265 [info]: ME / range / subpel / merge : dia / 25 / 0 / 2 +x265 [info]: Keyframe min / max / scenecut : 25 / 250 / 0 +x265 [info]: Lookahead / bframes / badapt : 10 / 4 / 0 +x265 [info]: b-pyramid / weightp / weightb / refs: 1 / 0 / 0 / 1 +x265 [info]: Rate Control / AQ-Strength / CUTree : CRF-28.0 / 0.0 / 0 +x265 [info]: VBV/HRD buffer / max-rate / init : 40960 / 512 / 0.900 +x265 [info]: tools: rd=2 early-skip fast-intra tmvp + + +x265 [info]: frame I: 1, Avg QP:25.40 kb/s: 639.60 +x265 [info]: frame P: 3, Avg QP:30.29 kb/s: 256.93 +x265 [info]: frame B: 12, Avg QP:32.92 kb/s: 58.68 +x265 [info]: global : 16, Avg QP:31.96 kb/s: 132.16 +x265 [info]: consecutive B-frames: 20.0% 0.0% 0.0% 0.0% 80.0% + + + + +h265: decode: type=32 VPS_NUT +h265: decode: type=33 SPS_NUT +h265: decode: type=34 PPS_NUT +h265: decode: type=39 PREFIX_SEI_NUT +h265: decode: type=39 PREFIX_SEI_NUT +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU +h265: decode: type=49 H265_NAL_FU + + + + +Test-call between 2 peers (A) and (B): + + +(A): + +video Transmit: Receive: +packets: 435 2001 +avg. bitrate: 56.0 416.0 (kbit/s) +errors: 0 0 +pkt.report: 417 1939 +lost: 0 0 +jitter: 23.3 0.4 (ms) + + +(B): + +video Transmit: Receive: +packets: 2012 435 +avg. bitrate: 416.0 56.0 (kbit/s) +errors: 0 0 +pkt.report: 1939 417 +lost: 0 0 +jitter: 0.4 23.3 (ms) + + + +[END] diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c new file mode 100644 index 0000000..f194d54 --- /dev/null +++ b/modules/httpd/httpd.c @@ -0,0 +1,181 @@ +/** + * @file httpd.c Webserver UI module + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup httpd httpd + * + * HTTP Server module for the User-Interface + * + * Open your favourite web browser and point it to http://127.0.0.1:8000/ + * + * Example URLs: + \verbatim + http://127.0.0.1:8000?h -- Print the Help menu + http://127.0.0.1:8000?d1234@target.com -- Make an outgoing call + \endverbatim + */ + + +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 pl *prm) +{ + struct pl params; + + if (!pf || !prm) + return EINVAL; + + if (pl_isset(prm)) { + params.p = prm->p + 1; + params.l = 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 int html_print_raw(struct re_printf *pf, const struct pl *prm) +{ + struct pl params; + + if (!pf || !prm) + return EINVAL; + + if (pl_isset(prm)) { + params.p = prm->p + 1; + params.l = prm->l - 1; + } + else { + params.p = "h"; + params.l = 1; + } + + return re_hprintf(pf, + "%H", + ui_input_pl, ¶ms); +} + +static void http_req_handler(struct http_conn *conn, + const struct http_msg *msg, void *arg) +{ + int err; + char *buf = NULL; + struct pl nprm; + (void)arg; + + err = re_sdprintf(&buf, "%H", uri_header_unescape, &msg->prm); + if (err) + goto error; + + pl_set_str(&nprm, buf); + + if (0 == pl_strcasecmp(&msg->path, "/")) { + + http_creply(conn, 200, "OK", + "text/html;charset=UTF-8", + "%H", html_print_cmd, &nprm); + } + else if (0 == pl_strcasecmp(&msg->path, "/raw/")) { + + http_creply(conn, 200, "OK", + "text/plain;charset=UTF-8", + "%H", html_print_raw, &nprm); + } + else { + goto error; + } + mem_deref(buf); + + return; + + error: + mem_deref(buf); + http_ereply(conn, 404, "Not Found"); +} + + +static int output_handler(const char *str) +{ + (void)str; + + /* XXX: print 'str' to all active HTTP connections */ + + return 0; +} + + +static struct ui ui_http = { + .name = "http", + .outputh = output_handler +}; + + +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; + + ui_register(baresip_uis(), &ui_http); + + info("httpd: listening on %J\n", &laddr); + + return 0; +} + + +static int module_close(void) +{ + ui_unregister(&ui_http); + + 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..64e316c --- /dev/null +++ b/modules/ice/ice.c @@ -0,0 +1,989 @@ +/** + * @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 + */ + + +enum { + ICE_LAYER = 0 +}; + + +struct mnat_sess { + struct list medial; + struct sa srv; + struct stun_dns *dnsq; + struct sdp_session *sdp; + char lufrag[8]; + char lpwd[32]; + uint64_t tiebrk; + bool offerer; + char *user; + char *pass; + int mediac; + bool started; + bool send_reinvite; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + struct comp { + struct mnat_media *m; /* pointer to parent */ + struct stun_ctrans *ct_gath; + struct sa laddr; + unsigned id; + void *sock; + } compv[2]; + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + struct icem *icem; + bool complete; + bool terminated; + int nstun; /**< Number of pending STUN candidates */ +}; + + +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 void call_gather_handler(int err, struct mnat_media *m, uint16_t scode, + const char *reason) +{ + + /* No more pending requests? */ + if (m->nstun != 0) + return; + + debug("ice: all components gathered.\n"); + + if (err) + goto out; + + /* Eliminate redundant local candidates */ + icem_cand_redund_elim(m->icem); + + err = icem_comps_set_default_cand(m->icem); + if (err) { + warning("ice: set default cands failed (%m)\n", err); + goto out; + } + + out: + gather_handler(err, scode, reason, m); +} + + +static void stun_resp_handler(int err, uint16_t scode, const char *reason, + const struct stun_msg *msg, void *arg) +{ + struct comp *comp = arg; + struct mnat_media *m = comp->m; + struct stun_attr *attr; + struct ice_cand *lcand; + + if (m->terminated) + return; + + --m->nstun; + + if (err || scode > 0) { + warning("ice: comp %u: STUN Request failed: %m\n", + comp->id, err); + goto out; + } + + debug("ice: srflx gathering for comp %u complete.\n", comp->id); + + /* base candidate */ + lcand = icem_cand_find(icem_lcandl(m->icem), comp->id, NULL); + if (!lcand) + goto out; + + attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR); + if (!attr) + attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR); + if (!attr) { + warning("ice: no Mapped Address in Response\n"); + err = EPROTO; + goto out; + } + + err = icem_lcand_add(m->icem, icem_lcand_base(lcand), + ICE_CAND_TYPE_SRFLX, + &attr->v.sa); + + out: + call_gather_handler(err, m, scode, reason); +} + + +/** Gather Server Reflexive address */ +static int send_binding_request(struct mnat_media *m, struct comp *comp) +{ + int err; + + if (comp->ct_gath) + return EALREADY; + + debug("ice: gathering srflx for comp %u ..\n", comp->id); + + err = stun_request(&comp->ct_gath, icem_stun(m->icem), IPPROTO_UDP, + comp->sock, &m->sess->srv, 0, + STUN_METHOD_BINDING, + NULL, false, 0, + stun_resp_handler, comp, 1, + STUN_ATTR_SOFTWARE, stun_software); + if (err) + return err; + + ++m->nstun; + + return 0; +} + + +static void turnc_handler(int err, uint16_t scode, const char *reason, + const struct sa *relay, const struct sa *mapped, + const struct stun_msg *msg, void *arg) +{ + struct comp *comp = arg; + struct mnat_media *m = comp->m; + struct ice_cand *lcand; + (void)msg; + + --m->nstun; + + /* TURN failed, so we destroy the client */ + if (err || scode) { + icem_set_turn_client(m->icem, comp->id, NULL); + } + + if (err) { + warning("{%u} TURN Client error: %m\n", + comp->id, err); + goto out; + } + + if (scode) { + warning("{%u} TURN Client error: %u %s\n", + comp->id, scode, reason); + err = send_binding_request(m, comp); + if (err) + goto out; + return; + } + + debug("ice: relay gathered for comp %u (%u %s)\n", + comp->id, scode, reason); + + lcand = icem_cand_find(icem_lcandl(m->icem), comp->id, NULL); + if (!lcand) + goto out; + + if (!sa_cmp(relay, icem_lcand_addr(icem_lcand_base(lcand)), SA_ALL)) { + err = icem_lcand_add(m->icem, icem_lcand_base(lcand), + ICE_CAND_TYPE_RELAY, relay); + } + + if (mapped) { + err |= icem_lcand_add(m->icem, icem_lcand_base(lcand), + ICE_CAND_TYPE_SRFLX, mapped); + } + else { + err |= send_binding_request(m, comp); + } + + out: + call_gather_handler(err, m, scode, reason); +} + + +static int cand_gather_relayed(struct mnat_media *m, struct comp *comp, + const char *username, const char *password) +{ + struct turnc *turnc = NULL; + const int layer = ICE_LAYER - 10; /* below ICE stack */ + int err; + + err = turnc_alloc(&turnc, stun_conf(icem_stun(m->icem)), + IPPROTO_UDP, comp->sock, layer, &m->sess->srv, + username, password, + 60, turnc_handler, comp); + if (err) + return err; + + err = icem_set_turn_client(m->icem, comp->id, turnc); + if (err) + goto out; + + ++m->nstun; + + out: + mem_deref(turnc); + + return err; +} + + +static int start_gathering(struct mnat_media *m, + const char *username, const char *password) +{ + unsigned i; + int err = 0; + + if (ice.mode != ICE_MODE_FULL) + return EINVAL; + + /* for each component */ + for (i=0; i<2; i++) { + struct comp *comp = &m->compv[i]; + + if (!comp->sock) + continue; + + if (username && password) { + err |= cand_gather_relayed(m, comp, + username, password); + } + else + err |= send_binding_request(m, comp); + } + + return err; +} + + +static int icem_gather_srflx(struct mnat_media *m) +{ + if (!m) + return EINVAL; + + return start_gathering(m, NULL, NULL); +} + + +static int icem_gather_relay(struct mnat_media *m, + const char *username, const char *password) +{ + if (!m || !username || !password) + return EINVAL; + + return start_gathering(m, username, password); +} + + +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->sdp); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + unsigned i; + + m->terminated = true; + + list_unlink(&m->le); + mem_deref(m->sdpm); + mem_deref(m->icem); + for (i=0; i<2; i++) { + mem_deref(m->compv[i].ct_gath); + 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, + sess->user, sess->pass); + } + else { + err = icem_gather_srflx(m); + } + break; + + case ICE_MODE_LITE: + err = icem_lite_set_default_candidates(m->icem); + if (err) { + warning("ice: could not set" + " default candidates (%m)\n", err); + return err; + } + + 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; + + debug("ice: resolved %s-server to address %J\n", + ice.turn ? "TURN" : "STUN", srv); + + 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; + + info("ice: new session with %s-server at %s (username=%s)\n", + ice.turn ? "TURN" : "STUN", + srv, user); + + 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; + + rand_str(sess->lufrag, sizeof(sess->lufrag)); + rand_str(sess->lpwd, sizeof(sess->lpwd)); + sess->tiebrk = rand_u64(); + sess->offerer = offerer; + + 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, sess->lufrag); + err |= sdp_session_set_lattr(ss, true, + ice_attr_pwd, sess->lpwd); + 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; + mnat_estab_h *estabh = m->sess->estabh; + + 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; + } + + if (err || scode) + m->sess->estabh = NULL; + + if (estabh) + 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; + + /* Update SDP media */ + if (sess->started) { + + LIST_FOREACH(&sess->medial, le) { + struct mnat_media *m = le->data; + + ice_printf(NULL, "ICE Start: %H", + icem_debug, m->icem); + + 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; + + /* set the pair states + -- first media stream only */ + if (sess->medial.head == le) { + ice_candpair_set_states(m->icem); + } + } + } + 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; + enum ice_role role; + 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); + + if (sess->offerer) + role = ICE_ROLE_CONTROLLING; + else + role = ICE_ROLE_CONTROLLED; + + err = icem_alloc(&m->icem, ice.mode, role, + proto, ICE_LAYER, + sess->tiebrk, sess->lufrag, sess->lpwd, + conncheck_handler, m); + if (err) + goto out; + + icem_conf(m->icem)->nom = ice.nom; + icem_conf(m->icem)->debug = ice.debug; + icem_conf(m->icem)->rc = 4; + + icem_set_conf(m->icem, icem_conf(m->icem)); + + icem_set_name(m->icem, sdp_media_name(sdpm)); + + for (i=0; i<2; i++) { + m->compv[i].m = m; + m->compv[i].id = i+1; + 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; + struct le *le; + + for (le = sess->medial.head; le; le = le->next) { + struct mnat_media *m = le->data; + + (void)ice_sdp_decode(m->icem, name, value); + } + + return false; +} + + +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); + return EINVAL; + } + } + 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); + return EINVAL; + } + } +#endif + + return mnat_register(&mnat, baresip_mnatl(), + "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..3628e11 --- /dev/null +++ b/modules/ilbc/ilbc.c @@ -0,0 +1,357 @@ +/** + * @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> + + +/** + * @defgroup ilbc ilbc + * + * iLBC audio codec + * + * 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, 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(baresip_aucodecl(), &ilbc); + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&ilbc); + return 0; +} + + +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..eb4bbdd --- /dev/null +++ b/modules/isac/isac.c @@ -0,0 +1,226 @@ +/** + * @file isac.c iSAC audio codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "isac.h" + + +/** + * @defgroup isac isac + * + * iSAC audio codec + * + * 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, 32000, 1, NULL, + encode_update, encode, decode_update, decode, plc, NULL, NULL + }, + { + LE_INIT, 0, "isac", 16000, 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(baresip_aucodecl(), &isacv[i]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(isacv); + + while (i--) + aucodec_unregister(&isacv[i]); + + return 0; +} + + +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/jack/jack.c b/modules/jack/jack.c new file mode 100644 index 0000000..5bf9a10 --- /dev/null +++ b/modules/jack/jack.c @@ -0,0 +1,43 @@ +/** + * @file jack.c JACK audio driver + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "mod_jack.h" + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + int err = 0; + + err |= auplay_register(&auplay, baresip_auplayl(), + "jack", jack_play_alloc); + err |= ausrc_register(&ausrc, baresip_ausrcl(), + "jack", jack_src_alloc); + + return err; +} + + +static int module_close(void) +{ + auplay = mem_deref(auplay); + ausrc = mem_deref(ausrc); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(jack) = { + "jack", + "sound", + module_init, + module_close +}; diff --git a/modules/jack/jack_play.c b/modules/jack/jack_play.c new file mode 100644 index 0000000..f890ddd --- /dev/null +++ b/modules/jack/jack_play.c @@ -0,0 +1,239 @@ +/** + * @file jack.c JACK audio driver -- player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <jack/jack.h> +#include "mod_jack.h" + + +struct auplay_st { + const struct auplay *ap; /* pointer to base-class (inheritance) */ + + struct auplay_prm prm; + int16_t *sampv; + size_t sampc; /* includes number of channels */ + auplay_write_h *wh; + void *arg; + + jack_client_t *client; + jack_port_t *portv[2]; + jack_nframes_t nframes; /* num frames per port (channel) */ +}; + + +static inline float ausamp_short2float(int16_t in) +{ + float out; + + out = (float) (in / (1.0 * 0x8000)); + + return out; +} + + +/** + * The process callback for this JACK application is called in a + * special realtime thread once for each audio cycle. + * + * This client does nothing more than copy data from its input + * port to its output port. It will exit when stopped by + * the user (e.g. using Ctrl-C on a unix-ish operating system) + * + * XXX avoid memory allocations in this function + */ +static int process_handler(jack_nframes_t nframes, void *arg) +{ + struct auplay_st *st = arg; + size_t sampc = nframes * st->prm.ch; + size_t ch, j; + + /* 1. read data from app (signed 16-bit) interleaved */ + st->wh(st->sampv, sampc, st->arg); + + /* 2. convert from 16-bit to float and copy to Jack */ + + /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */ + for (ch = 0; ch < st->prm.ch; ch++) { + + jack_default_audio_sample_t *buffer; + + buffer = jack_port_get_buffer(st->portv[ch], st->nframes); + + for (j = 0; j < nframes; j++) { + int16_t samp = st->sampv[j*st->prm.ch + ch]; + buffer[j] = ausamp_short2float(samp); + } + } + + return 0; +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + info("jack: destroy\n"); + + if (st->client) + jack_client_close(st->client); + + mem_deref(st->sampv); +} + + +static int start_jack(struct auplay_st *st) +{ + const char **ports; + const char *client_name = "baresip"; + const char *server_name = NULL; + jack_options_t options = JackNullOption; + jack_status_t status; + unsigned ch; + jack_nframes_t engine_srate; + + /* open a client connection to the JACK server */ + + st->client = jack_client_open(client_name, options, + &status, server_name); + if (st->client == NULL) { + warning("jack: jack_client_open() failed, " + "status = 0x%2.0x\n", status); + + if (status & JackServerFailed) { + warning("jack: Unable to connect to JACK server\n"); + } + return ENODEV; + } + if (status & JackServerStarted) { + info("jack: JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(st->client); + info("jack: unique name `%s' assigned\n", client_name); + } + + jack_set_process_callback(st->client, process_handler, st); + + engine_srate = jack_get_sample_rate(st->client); + st->nframes = jack_get_buffer_size(st->client); + + info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n", + engine_srate, st->nframes); + + /* currently the application must use the same sample-rate + as the jack server backend */ + if (engine_srate != st->prm.srate) { + warning("jack: samplerate %uHz expected\n", engine_srate); + return EINVAL; + } + + /* create one port per channel */ + for (ch=0; ch<st->prm.ch; ch++) { + + char buf[32]; + re_snprintf(buf, sizeof(buf), "output_%u", ch+1); + + st->portv[ch] = jack_port_register (st->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if ( st->portv[ch] == NULL) { + warning("jack: no more JACK ports available\n"); + return ENODEV; + } + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + + if (jack_activate (st->client)) { + warning("jack: cannot activate client"); + return ENODEV; + } + + /* Connect the ports. You can't do this before the client is + * activated, because we can't make connections to clients + * that aren't running. Note the confusing (but necessary) + * orientation of the driver backend ports: playback ports are + * "input" to the backend, and capture ports are "output" from + * it. + */ + + ports = jack_get_ports (st->client, NULL, NULL, + JackPortIsInput); + if (ports == NULL) { + warning("jack: no physical playback ports\n"); + return ENODEV; + } + + for (ch=0; ch<st->prm.ch; ch++) { + + if (jack_connect (st->client, jack_port_name (st->portv[ch]), + ports[ch])) { + warning("jack: cannot connect output ports\n"); + } + } + + jack_free(ports); + + return 0; +} + + +int jack_play_alloc(struct auplay_st **stp, const 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; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + info("jack: play %uHz,%uch\n", prm->srate, prm->ch); + + if (prm->ch > ARRAY_SIZE(st->portv)) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("jack: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->prm = *prm; + st->ap = ap; + st->wh = wh; + st->arg = arg; + + err = start_jack(st); + if (err) + goto out; + + st->sampc = st->nframes * prm->ch; + st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + info("jack: sampc=%zu\n", st->sampc); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/jack/jack_src.c b/modules/jack/jack_src.c new file mode 100644 index 0000000..0ef1e42 --- /dev/null +++ b/modules/jack/jack_src.c @@ -0,0 +1,234 @@ +/** + * @file jack_src.c JACK audio driver -- source + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <math.h> +#include <jack/jack.h> +#include "mod_jack.h" + + +struct ausrc_st { + const struct ausrc *as; /* pointer to base-class (inheritance) */ + + struct ausrc_prm prm; + int16_t *sampv; + size_t sampc; /* includes number of channels */ + ausrc_read_h *rh; + void *arg; + + jack_client_t *client; + jack_port_t *portv[2]; + jack_nframes_t nframes; /* num frames per port (channel) */ +}; + + +static inline int16_t ausamp_float2short(float in) +{ + double scaled_value; + int16_t out; + + scaled_value = in * (8.0 * 0x10000000); + + if (scaled_value >= (1.0 * 0x7fffffff)) { + out = 32767; + } + else if (scaled_value <= (-8.0 * 0x10000000)) { + out = -32768; + } + else + out = (short) (lrint (scaled_value) >> 16); + + return out; +} + + +static int process_handler(jack_nframes_t nframes, void *arg) +{ + struct ausrc_st *st = arg; + size_t sampc = nframes * st->prm.ch; + size_t ch, j; + + /* 2. convert from 16-bit to float and copy to Jack */ + + /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */ + for (ch = 0; ch < st->prm.ch; ch++) { + + const jack_default_audio_sample_t *buffer; + + buffer = jack_port_get_buffer(st->portv[ch], st->nframes); + + for (j = 0; j < nframes; j++) { + int16_t samp; + samp = ausamp_float2short(buffer[j]); + st->sampv[j*st->prm.ch + ch] = samp; + } + } + + /* 1. read data from app (signed 16-bit) interleaved */ + st->rh(st->sampv, sampc, st->arg); + + return 0; +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + info("jack: source destroy\n"); + + if (st->client) + jack_client_close(st->client); + + mem_deref(st->sampv); +} + + +static int start_jack(struct ausrc_st *st) +{ + const char **ports; + const char *client_name = "baresip"; + const char *server_name = NULL; + jack_options_t options = JackNullOption; + jack_status_t status; + unsigned ch; + jack_nframes_t engine_srate; + + /* open a client connection to the JACK server */ + + st->client = jack_client_open(client_name, options, + &status, server_name); + if (st->client == NULL) { + warning("jack: jack_client_open() failed, " + "status = 0x%2.0x\n", status); + + if (status & JackServerFailed) { + warning("jack: Unable to connect to JACK server\n"); + } + return ENODEV; + } + if (status & JackServerStarted) { + info("jack: JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(st->client); + info("jack: unique name `%s' assigned\n", client_name); + } + + jack_set_process_callback(st->client, process_handler, st); + + engine_srate = jack_get_sample_rate(st->client); + st->nframes = jack_get_buffer_size(st->client); + + info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n", + engine_srate, st->nframes); + + /* currently the application must use the same sample-rate + as the jack server backend */ + if (engine_srate != st->prm.srate) { + warning("jack: samplerate %uHz expected\n", engine_srate); + return EINVAL; + } + + /* create one port per channel */ + for (ch=0; ch<st->prm.ch; ch++) { + + char buf[32]; + re_snprintf(buf, sizeof(buf), "input_%u", ch+1); + + st->portv[ch] = jack_port_register(st->client, buf, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + if ( st->portv[ch] == NULL) { + warning("jack: no more JACK ports available\n"); + return ENODEV; + } + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + + if (jack_activate (st->client)) { + warning("jack: cannot activate client"); + return ENODEV; + } + + ports = jack_get_ports (st->client, NULL, NULL, + JackPortIsOutput); + if (ports == NULL) { + warning("jack: no physical playback ports\n"); + return ENODEV; + } + + for (ch=0; ch<st->prm.ch; ch++) { + + if (jack_connect(st->client, ports[ch], + jack_port_name(st->portv[ch]))) { + warning("jack: cannot connect output ports\n"); + } + } + + jack_free(ports); + + return 0; +} + + +int jack_src_alloc(struct ausrc_st **stp, const 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; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + if (prm->ch > ARRAY_SIZE(st->portv)) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("jack: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->prm = *prm; + st->as = as; + st->rh = rh; + st->arg = arg; + + err = start_jack(st); + if (err) + goto out; + + st->sampc = st->nframes * prm->ch; + st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + info("jack: source sampc=%zu\n", st->sampc); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/jack/mod_jack.h b/modules/jack/mod_jack.h new file mode 100644 index 0000000..b154ebf --- /dev/null +++ b/modules/jack/mod_jack.h @@ -0,0 +1,14 @@ +/** + * @file mod_jack.h JACK audio driver -- internal api + * + * Copyright (C) 2010 Creytiv.com + */ + + +int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int jack_src_alloc(struct ausrc_st **stp, const 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/jack/module.mk b/modules/jack/module.mk new file mode 100644 index 0000000..486ffd6 --- /dev/null +++ b/modules/jack/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := jack +$(MOD)_SRCS += jack.c jack_play.c jack_src.c +$(MOD)_CFLAGS += $(shell pkg-config --cflags jack) +$(MOD)_LFLAGS += $(shell pkg-config --libs jack) + +include mk/mod.mk diff --git a/modules/l16/l16.c b/modules/l16/l16.c new file mode 100644 index 0000000..204a8f5 --- /dev/null +++ b/modules/l16/l16.c @@ -0,0 +1,104 @@ +/** + * @file l16.c 16-bit linear codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup l16 l16 + * + * Linear 16-bit audio codec + */ + + +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, 44100, 2, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 32000, 32000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 16000, 16000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 8000, 8000, 2, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, "11", "L16", 44100, 44100, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 32000, 32000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 16000, 16000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +{LE_INIT, 0, "L16", 8000, 8000, 1, 0, 0, encode, 0, decode, 0, 0, 0}, +}; + + +static int module_init(void) +{ + struct list *aucodecl = baresip_aucodecl(); + size_t i; + + for (i=0; i<NR_CODECS; i++) + aucodec_register(aucodecl, &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/libsrtp/module.mk b/modules/libsrtp/module.mk new file mode 100644 index 0000000..d24ad60 --- /dev/null +++ b/modules/libsrtp/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := libsrtp +$(MOD)_SRCS += srtp.c sdes.c +$(MOD)_LFLAGS += -lsrtp + +include mk/mod.mk diff --git a/modules/libsrtp/sdes.c b/modules/libsrtp/sdes.c new file mode 100644 index 0000000..1a90f3f --- /dev/null +++ b/modules/libsrtp/sdes.c @@ -0,0 +1,46 @@ +/** + * @file libsrtp/sdes.c SDP Security Descriptions (RFC 4568) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +static const char sdp_attr_crypto[] = "crypto"; + + +int libsrtp_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 libsrtp_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/libsrtp/sdes.h b/modules/libsrtp/sdes.h new file mode 100644 index 0000000..8c2c1f9 --- /dev/null +++ b/modules/libsrtp/sdes.h @@ -0,0 +1,22 @@ +/** + * @file libsrtp/sdes.h SDP Security Descriptions (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 */ +}; + + +int libsrtp_sdes_encode_crypto(struct sdp_media *m, uint32_t tag, + const char *suite, + const char *key, size_t key_len); +int libsrtp_sdes_decode_crypto(struct crypto *c, const char *val); diff --git a/modules/libsrtp/srtp.c b/modules/libsrtp/srtp.c new file mode 100644 index 0000000..0534a60 --- /dev/null +++ b/modules/libsrtp/srtp.c @@ -0,0 +1,474 @@ +/** + * @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 <srtp/crypto_kernel.h> +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +/* + * NOTE: this module is deprecated, please use the 'srtp' module instead. + */ + + +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 { + warning("srtp: 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) { + warning("srtp: 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) { + warning("srtp: 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) { + warning("srtp: 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) { + warning("srtp: 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) { + warning("srtp: 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 libsrtp_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) { + warning("srtp: 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 (libsrtp_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) { + warning("srtp: 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) +{ + struct list *mencl = baresip_mencl(); + err_status_t err; + + err = srtp_init(); + if (err_status_ok != err) { + warning("srtp: srtp_init() failed (%H)\n", + errstatus_print, err); + return ENOSYS; + } + + menc_register(mencl, &menc_srtp_opt); + menc_register(mencl, &menc_srtp_mand); + menc_register(mencl, &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(libsrtp) = { + "libsrtp", + "menc", + mod_srtp_init, + mod_srtp_close +}; diff --git a/modules/menu/menu.c b/modules/menu/menu.c new file mode 100644 index 0000000..c7f1646 --- /dev/null +++ b/modules/menu/menu.c @@ -0,0 +1,1162 @@ +/** + * @file menu.c Interactive menu + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <time.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup menu menu + * + * Interactive menu + * + * This module must be loaded if you want to use the interactive menu + * to control the Baresip application. + */ + + +/** Defines the status modes */ +enum statmode { + STATMODE_CALL = 0, + STATMODE_OFF, +}; + + +static uint64_t start_ticks; /**< Ticks when app started */ +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 struct { + struct play *play; + struct message_lsnr *message; + bool bell; + bool ringback_disabled; /**< no ringback on sip 180 respons */ + struct tmr tmr_redial; /**< Timer for auto-reconnect */ + uint32_t redial_delay; /**< Redial delay in [seconds] */ + uint32_t redial_attempts; /**< Number of re-dial attempts */ + uint32_t current_attempts; /**< Current number of re-dials */ +} menu; + + +static int menu_set_incall(bool incall); +static void update_callstatus(void); +static void alert_stop(void); +static int switch_audio_source(struct re_printf *pf, void *arg); +static int switch_audio_player(struct re_printf *pf, void *arg); + + +static void redial_reset(void) +{ + tmr_cancel(&menu.tmr_redial); + menu.current_attempts = 0; +} + + +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 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 */ + ui_output(baresip_uis(), + "\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) +{ + return uag_current(); +} + + +/* 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; +} + + +/** + * 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 void options_resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + (void)arg; + + if (err) { + warning("options reply error: %m\n", err); + return; + } + + if (msg->scode < 200) + return; + + if (msg->scode < 300) { + + mbuf_set_pos(msg->mb, 0); + info("----- OPTIONS of %r -----\n%b", + &(msg->to.auri), mbuf_buf(msg->mb), + mbuf_get_left(msg->mb)); + return; + } + + info("%r: OPTIONS failed: %u %r\n", &(msg->to.auri), + msg->scode, &msg->reason); +} + + +static int options_command(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_options_send(uag_cur(), carg->prm, + options_resp_handler, NULL); + } + else if (dialbuf->end > 0) { + + char *uri; + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return err; + + err = ua_options_send(uag_cur(), uri, + options_resp_handler, NULL); + + mem_deref(uri); + } + + if (err) { + warning("menu: ua_options failed: %m\n", err); + } + + return err; +} + + +static int cmd_answer(struct re_printf *pf, void *unused) +{ + struct ua *ua = uag_cur(); + int err; + (void)unused; + + err = re_hprintf(pf, "%s: Answering incoming call\n", ua_aor(ua)); + + /* Stop any ongoing ring-tones */ + menu.play = mem_deref(menu.play); + + ua_hold_answer(ua, NULL); + + return err; +} + + +static int cmd_hangup(struct re_printf *pf, void *unused) +{ + (void)pf; + (void)unused; + + /* Stop any ongoing ring-tones */ + menu.play = mem_deref(menu.play); + alert_stop(); + + ua_hangup(uag_cur(), NULL, 0, NULL); + + /* note: must be called after ua_hangup() */ + menu_set_incall(have_active_calls()); + + return 0; +} + + +static int create_ua(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct le *le; + int err = 0; + + (void)pf; + + if (str_isset(carg->prm)) { + + mbuf_rewind(dialbuf); + (void)mbuf_write_str(dialbuf, carg->prm); + + (void)re_hprintf(pf, "Creating UA for %s ...\n", carg->prm); + err = ua_alloc(NULL, carg->prm); + + + } + else if (dialbuf->end > 0) { + + char *uri; + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return err; + + (void)re_hprintf(pf, "Creating UA for %s ...\n", uri); + err |= ua_alloc(NULL, uri); + + mem_deref(uri); + } + + 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"); + + + if (err) { + (void)re_hprintf(pf, "menu: create_ua failed: %m\n", err); + } + + + return err; +} + + +static int cmd_ua_next(struct re_printf *pf, void *unused) +{ + int err; + + (void)pf; + (void)unused; + + if (!le_cur) + le_cur = list_head(uag_list()); + if (!le_cur) + return 0; + + le_cur = le_cur->next ? le_cur->next : list_head(uag_list()); + + err = re_hprintf(pf, "ua: %s\n", ua_aor(list_ledata(le_cur))); + + uag_current_set(list_ledata(le_cur)); + + update_callstatus(); + + return err; +} + + +static int print_commands(struct re_printf *pf, void *unused) +{ + (void)unused; + return cmd_print(pf, baresip_commands()); +} + + +static int cmd_print_calls(struct re_printf *pf, void *unused) +{ + (void)unused; + return ua_print_calls(pf, uag_cur()); +} + + +static const char about_fmt[] = + ".------------------------------------------------------------.\n" + "| " + "\x1b[34;1m" "bare" + "\x1b[31;1m" "sip" + "\x1b[;m" + " %-10s |\n" + "| |\n" + "| Baresip is a portable and modular SIP User-Agent |\n" + "| with audio and video support |\n" + "| |\n" + "| License: BSD |\n" + "| Homepage: https://github.com/alfredh/baresip |\n" + "| |\n" + "'------------------------------------------------------------'\n" + ; + + +static int about_box(struct re_printf *pf, void *unused) +{ + (void)unused; + + return re_hprintf(pf, about_fmt, BARESIP_VERSION); +} + + +static const struct cmd cmdv[] = { + +{"accept", 'a', 0, "Accept incoming call", cmd_answer }, +{"hangup", 'b', 0, "Hangup call", cmd_hangup }, +{"callstat", 'c', 0, "Call status", ua_print_call_status }, +{"dial", 'd', CMD_PRM, "Dial", dial_handler }, +{"help", 'h', 0, "Help menu", print_commands }, +{"listcalls", 'l', 0, "List active calls", cmd_print_calls }, +{"options", 'o', CMD_PRM, "Options", options_command }, +{"reginfo", 'r', 0, "Registration info", ua_print_reg_status }, +{NULL, KEYCODE_ESC,0, "Hangup call", cmd_hangup }, +{"uanext", 'T', 0, "Toggle UAs", cmd_ua_next }, +{"uanew", 0, CMD_PRM, "Create User-Agent", create_ua }, +{"ausrc", 0, CMD_IPRM, "Switch audio source", switch_audio_source }, +{"auplay", 0, CMD_IPRM, "Switch audio player", switch_audio_player }, +{"about", 0, 0, "About box", about_box }, + +}; + +static const struct cmd dialcmdv[] = { +/* Numeric keypad inputs: */ +{NULL, '#', CMD_PRM, NULL, dial_handler }, +{NULL, '*', CMD_PRM, NULL, dial_handler }, +{NULL, '0', CMD_PRM, NULL, dial_handler }, +{NULL, '1', CMD_PRM, NULL, dial_handler }, +{NULL, '2', CMD_PRM, NULL, dial_handler }, +{NULL, '3', CMD_PRM, NULL, dial_handler }, +{NULL, '4', CMD_PRM, NULL, dial_handler }, +{NULL, '5', CMD_PRM, NULL, dial_handler }, +{NULL, '6', CMD_PRM, NULL, dial_handler }, +{NULL, '7', CMD_PRM, NULL, dial_handler }, +{NULL, '8', CMD_PRM, NULL, dial_handler }, +{NULL, '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) +{ + struct audio *audio = call_audio(ua_call(uag_cur())); + bool muted = !audio_ismuted(audio); + (void)unused; + + (void)re_hprintf(pf, "\ncall %smuted\n", muted ? "" : "un-"); + audio_mute(audio, 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 cmd_call_hold(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + return call_hold(ua_call(uag_cur()), true); +} + + +static int cmd_call_resume(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + return call_hold(ua_call(uag_cur()), false); +} + + +static int hold_prev_call(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + (void)pf; + + return call_hold(ua_prev_call(uag_cur()), 'H' == carg->key); +} + + +static int switch_audio_player(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct pl pl_driver, pl_device; + struct config_audio *aucfg; + struct config *cfg; + struct audio *a; + struct le *le; + char driver[16], device[128] = ""; + int err = 0; + + static bool switch_aud_inprogress; + + if (!switch_aud_inprogress && !carg->complete) { + re_hprintf(pf, + "\rPlease enter audio device (driver,device)\n"); + } + + switch_aud_inprogress = true; + + if (carg->complete) { + + switch_aud_inprogress = false; + + if (re_regex(carg->prm, str_len(carg->prm), "[^,]+,[~]*", + &pl_driver, &pl_device)) { + + return re_hprintf(pf, "\rFormat should be:" + " driver,device\n"); + } + + pl_strcpy(&pl_driver, driver, sizeof(driver)); + pl_strcpy(&pl_device, device, sizeof(device)); + + if (!auplay_find(baresip_auplayl(), driver)) { + re_hprintf(pf, "no such audio-player: %s\n", driver); + return 0; + } + + re_hprintf(pf, "switch audio player: %s,%s\n", + driver, device); + + cfg = conf_config(); + if (!cfg) { + return re_hprintf(pf, "no config object\n"); + } + + aucfg = &cfg->audio; + + str_ncpy(aucfg->play_mod, driver, sizeof(aucfg->play_mod)); + str_ncpy(aucfg->play_dev, device, sizeof(aucfg->play_dev)); + + str_ncpy(aucfg->alert_mod, driver, sizeof(aucfg->alert_mod)); + str_ncpy(aucfg->alert_dev, device, sizeof(aucfg->alert_dev)); + + for (le = list_tail(ua_calls(uag_cur())); le; le = le->prev) { + + struct call *call = le->data; + + a = call_audio(call); + + err = audio_set_player(a, driver, device); + if (err) { + re_hprintf(pf, "failed to set audio-player" + " (%m)\n", err); + break; + } + } + } + + return 0; +} + + +static int switch_audio_source(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + struct pl pl_driver, pl_device; + struct config_audio *aucfg; + struct config *cfg; + struct audio *a; + struct le *le; + char driver[16], device[128] = ""; + int err = 0; + + static bool switch_aud_inprogress; + + if (!switch_aud_inprogress && !carg->complete) { + re_hprintf(pf, + "\rPlease enter audio device (driver,device)\n"); + } + + switch_aud_inprogress = true; + + if (carg->complete) { + + switch_aud_inprogress = false; + + if (re_regex(carg->prm, str_len(carg->prm), "[^,]+,[~]*", + &pl_driver, &pl_device)) { + + return re_hprintf(pf, "\rFormat should be:" + " driver,device\n"); + } + + pl_strcpy(&pl_driver, driver, sizeof(driver)); + pl_strcpy(&pl_device, device, sizeof(device)); + + if (!ausrc_find(baresip_ausrcl(), driver)) { + re_hprintf(pf, "no such audio-source: %s\n", driver); + return 0; + } + + re_hprintf(pf, "switch audio device: %s,%s\n", + driver, device); + + cfg = conf_config(); + if (!cfg) { + return re_hprintf(pf, "no config object\n"); + } + + aucfg = &cfg->audio; + + str_ncpy(aucfg->src_mod, driver, sizeof(aucfg->src_mod)); + str_ncpy(aucfg->src_dev, device, sizeof(aucfg->src_dev)); + + for (le = list_tail(ua_calls(uag_cur())); le; le = le->prev) { + + struct call *call = le->data; + + a = call_audio(call); + + err = audio_set_source(a, driver, device); + if (err) { + re_hprintf(pf, "failed to set audio-source" + " (%m)\n", err); + break; + } + } + } + + return 0; +} + + +#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 int set_current_call(struct re_printf *pf, void *arg) +{ + struct cmd_arg *carg = arg; + struct call *call; + uint32_t linenum = atoi(carg->prm); + int err; + + call = call_find_linenum(ua_calls(uag_cur()), linenum); + if (call) { + err = re_hprintf(pf, "setting current call: line %u\n", + linenum); + call_set_current(ua_calls(uag_cur()), call); + } + else { + err = re_hprintf(pf, "call not found\n"); + } + + return err; +} + + +static const struct cmd callcmdv[] = { +{"reinvite", 'I', 0, "Send re-INVITE", call_reinvite }, +{"resume", 'X', 0, "Call resume", cmd_call_resume }, +{"audio_debug",'A', 0, "Audio stream", call_audio_debug }, +{"audio_cycle",'e', 0, "Cycle audio encoder", call_audioenc_cycle }, +{"mute", 'm', 0, "Call mute/un-mute", call_mute }, +{"transfer", 't', CMD_IPRM, "Transfer call", call_xfer }, +{"hold", 'x', 0, "Call hold", cmd_call_hold }, +{"", 'H', 0, "Hold previous call", hold_prev_call }, +{"", 'L', 0, "Resume previous call",hold_prev_call }, + +#ifdef USE_VIDEO +{"video_cycle", 'E', 0, "Cycle video encoder", call_videoenc_cycle }, +{"video_debug", 'V', 0, "Video stream", call_video_debug }, +#endif + +/* Numeric keypad for DTMF events: */ +{NULL, '#', 0, NULL, digit_handler }, +{NULL, '*', 0, NULL, digit_handler }, +{NULL, '0', 0, NULL, digit_handler }, +{NULL, '1', 0, NULL, digit_handler }, +{NULL, '2', 0, NULL, digit_handler }, +{NULL, '3', 0, NULL, digit_handler }, +{NULL, '4', 0, NULL, digit_handler }, +{NULL, '5', 0, NULL, digit_handler }, +{NULL, '6', 0, NULL, digit_handler }, +{NULL, '7', 0, NULL, digit_handler }, +{NULL, '8', 0, NULL, digit_handler }, +{NULL, '9', 0, NULL, digit_handler }, +{NULL, KEYCODE_REL, 0, NULL, digit_handler }, + +{NULL, 'S', 0, "Statusmode toggle", toggle_statmode }, +{"line",'@', CMD_PRM, "Set current call <line>", set_current_call }, +}; + + +static int menu_set_incall(bool incall) +{ + struct commands *commands = baresip_commands(); + int err = 0; + + /* Dynamic menus */ + if (incall) { + cmd_unregister(commands, dialcmdv); + + if (!cmds_find(commands, callcmdv)) { + err = cmd_register(commands, + callcmdv, ARRAY_SIZE(callcmdv)); + } + } + else { + cmd_unregister(commands, callcmdv); + + if (!cmds_find(commands, dialcmdv)) { + err = cmd_register(baresip_commands(), dialcmdv, + ARRAY_SIZE(dialcmdv)); + } + } + if (err) { + warning("menu: set_incall: cmd_register failed (%m)\n", err); + } + + return err; +} + + +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 (ui_isediting(baresip_uis())) + return; + + 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; + + if (!menu.bell) + return; + + ui_output(baresip_uis(), "\033[10;1000]\033[11;1000]\a"); + + tmr_start(&tmr_alert, 1000, alert_start, NULL); +} + + +static void alert_stop(void) +{ + if (!menu.bell) + return; + + if (tmr_isrunning(&tmr_alert)) + ui_output(baresip_uis(), "\r"); + + tmr_cancel(&tmr_alert); +} + + +static void redial_handler(void *arg) +{ + char *uri = NULL; + int err; + (void)arg; + + info("now: redialing now. current_attempts=%u, max_attempts=%u\n", + menu.current_attempts, + menu.redial_attempts); + + if (menu.current_attempts > menu.redial_attempts) { + + info("menu: redial: too many attemptes -- giving up\n"); + return; + } + + if (dialbuf->end == 0) { + warning("menu: redial: dialbuf is empty\n"); + return; + } + + dialbuf->pos = 0; + err = mbuf_strdup(dialbuf, &uri, dialbuf->end); + if (err) + return; + + err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON); + if (err) { + warning("menu: redial: ua_connect failed (%m)\n", err); + } + + mem_deref(uri); + +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct player *player = baresip_player(); + + (void)call; + (void)prm; + (void)arg; + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + + /* set the current User-Agent to the one with the call */ + uag_current_set(ua); + + info("%s: Incoming call from: %s %s -" + " (press 'a' to accept)\n", + ua_aor(ua), call_peername(call), call_peeruri(call)); + + /* stop any ringtones */ + menu.play = mem_deref(menu.play); + + /* Only play the ringtones if answermode is "Manual". + * If the answermode is "auto" then be silent. + */ + if (ANSWERMODE_MANUAL == account_answermode(ua_account(ua))) { + + if (list_count(ua_calls(ua)) > 1) { + (void)play_file(&menu.play, player, + "callwaiting.wav", 3); + } + else { + /* Alert user */ + (void)play_file(&menu.play, player, + "ring.wav", -1); + } + + if (menu.bell) + alert_start(0); + } + break; + + case UA_EVENT_CALL_RINGING: + /* stop any ringtones */ + menu.play = mem_deref(menu.play); + + if (menu.ringback_disabled) { + info("\nRingback disabled\n"); + } + else { + (void)play_file(&menu.play, player, + "ringback.wav",-1); + } + break; + + case UA_EVENT_CALL_ESTABLISHED: + /* stop any ringtones */ + menu.play = mem_deref(menu.play); + + alert_stop(); + + /* We must stop the re-dialing if the call was + established */ + redial_reset(); + break; + + case UA_EVENT_CALL_CLOSED: + /* stop any ringtones */ + menu.play = mem_deref(menu.play); + + if (call_scode(call)) { + const char *tone; + tone = translate_errorcode(call_scode(call)); + if (tone) { + (void)play_file(&menu.play, player, + tone, 1); + } + } + + alert_stop(); + + /* Activate the re-dialing if: + * + * - redial_attempts must be enabled in config + * - the closed call must be of outgoing direction + * - the closed call must fail with special code 701 + */ + if (menu.redial_attempts) { + + if (menu.current_attempts + || + (call_is_outgoing(call) && + call_scode(call) == 701)) { + + info("menu: call closed" + " -- redialing in %u seconds\n", + menu.redial_delay); + + ++menu.current_attempts; + + tmr_start(&menu.tmr_redial, + menu.redial_delay*1000, + redial_handler, NULL); + } + else { + info("menu: call closed -- not redialing\n"); + } + } + + 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; + + ui_output(baresip_uis(), "\r%r: \"%b\"\n", + peer, mbuf_buf(body), mbuf_get_left(body)); + + (void)play_file(NULL, baresip_player(), "message.wav", 0); +} + + +static int module_init(void) +{ + struct pl val; + int err; + + /* + * Read the config values + */ + conf_get_bool(conf_cur(), "menu_bell", &menu.bell); + conf_get_bool(conf_cur(), "ringback_disabled", + &menu.ringback_disabled); + + if (0 == conf_get(conf_cur(), "redial_attempts", &val) && + 0 == pl_strcasecmp(&val, "inf")) { + menu.redial_attempts = (uint32_t)-1; + } + else { + conf_get_u32(conf_cur(), "redial_attempts", + &menu.redial_attempts); + } + conf_get_u32(conf_cur(), "redial_delay", &menu.redial_delay); + + if (menu.redial_attempts) { + info("menu: redial enabled with %u attempts and" + " %u seconds delay\n", + menu.redial_attempts, + menu.redial_delay); + } + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + start_ticks = tmr_jiffies(); + tmr_init(&tmr_alert); + statmode = STATMODE_CALL; + + err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); + err |= cmd_register(baresip_commands(), dialcmdv, + ARRAY_SIZE(dialcmdv)); + if (err) + return err; + + err = uag_event_register(ua_event_handler, NULL); + if (err) + return err; + + err = message_listen(&menu.message, baresip_message(), + message_handler, NULL); + if (err) + return err; + + return err; +} + + +static int module_close(void) +{ + debug("menu: close (redial current_attempts=%d)\n", + menu.current_attempts); + + menu.message = mem_deref(menu.message); + uag_event_unregister(ua_event_handler); + cmd_unregister(baresip_commands(), cmdv); + cmd_unregister(baresip_commands(), dialcmdv); + cmd_unregister(baresip_commands(), callcmdv); + + tmr_cancel(&tmr_alert); + tmr_cancel(&tmr_stat); + dialbuf = mem_deref(dialbuf); + + le_cur = NULL; + + menu.play = mem_deref(menu.play); + + tmr_cancel(&menu.tmr_redial); + + 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/mpa/decode.c b/modules/mpa/decode.c new file mode 100644 index 0000000..4e2a720 --- /dev/null +++ b/modules/mpa/decode.c @@ -0,0 +1,217 @@ +/** + * @file mpa/decode.c mpa Decode + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include <re.h> +#include <baresip.h> +#include <mpg123.h> +#include <speex/speex_resampler.h> +#include <string.h> +#include "mpa.h" + +struct audec_state { + mpg123_handle *dec; + SpeexResamplerState *resampler; + int channels; + int16_t intermediate_buffer[MPA_FRAMESIZE*2]; + int start; +}; + + +static void destructor(void *arg) +{ + struct audec_state *ads = arg; + + if (ads->resampler) + speex_resampler_destroy(ads->resampler); + + mpg123_close(ads->dec); + mpg123_delete(ads->dec); +#ifdef DEBUG + debug("MPA dec destroyed\n"); +#endif +} + + +int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp) +{ + struct audec_state *ads; + int result, err=0; + (void)fmtp; + + if (!adsp || !ac || !ac->ch) + return EINVAL; + + ads = *adsp; + +#ifdef DEBUG + debug("MPA dec created %s\n",fmtp); +#endif + + if (!ads) { + ads = mem_zalloc(sizeof(*ads), destructor); + if (!ads) + return ENOMEM; + } + else { + memset(ads,0,sizeof(*ads)); + } + ads->channels = 0; + ads->resampler = NULL; + ads->start = 0; + + ads->dec = mpg123_new(NULL,&result); + if (!ads->dec) { + warning("MPA dec create: %s\n", + mpg123_plain_strerror(result)); + err = ENOMEM; + goto out; + } + +#ifdef DEBUG + result = mpg123_param(ads->dec, MPG123_VERBOSE, 4, 4.); +#else + result = mpg123_param(ads->dec, MPG123_VERBOSE, 0, 0.); +#endif + if (result != MPG123_OK) { + warning("MPA dec param error %s\n", + mpg123_plain_strerror(result)); + err = EINVAL; + goto out; + } + + + result = mpg123_format_all(ads->dec); + if (result != MPG123_OK) { + warning("MPA dec format error %s\n", + mpg123_plain_strerror(result)); + err = EINVAL; + goto out; + } + + result = mpg123_open_feed(ads->dec); + if (result != MPG123_OK) { + warning("MPA dec open feed error %s\n", + mpg123_plain_strerror(result)); + err = EINVAL; + goto out; + } + + out: + if (err) + mem_deref(ads); + else + *adsp = ads; + + return err; +} + + +int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int result, channels, encoding, i; + long samplerate; + size_t n; + spx_uint32_t intermediate_len; + spx_uint32_t out_len; + +#ifdef DEBUG + debug("MPA dec start %d %ld\n",len, *sampc); +#endif + + if (!ads || !sampv || !sampc || !buf || len<=4) + return EINVAL; + + if (*(uint32_t*)(void *)buf != 0) { + warning("MPA dec header is not zero %08X, not supported yet\n", + *(uint32_t*)(void *)buf); + return EPROTO; + } + + n = 0; + result = mpg123_decode(ads->dec, buf+4, len-4, + (unsigned char*)ads->intermediate_buffer, + sizeof(ads->intermediate_buffer), &n); + /* n counts bytes */ +#ifdef DEBUG + debug("MPA dec %d %d %d %d\n",result, len-4, n, ads->channels); +#endif + + if (result == MPG123_NEW_FORMAT) { + mpg123_getformat(ads->dec, &samplerate, &channels, &encoding); + info("MPA dec format change %d %d %04X\n",samplerate + ,channels,encoding); + + ads->channels = channels; + ads->start = 0; + if (ads->resampler) + speex_resampler_destroy(ads->resampler); + if (samplerate != MPA_IORATE) { + ads->resampler = speex_resampler_init(channels, + (uint32_t)samplerate, MPA_IORATE, + 3, &result); + if (result!=RESAMPLER_ERR_SUCCESS + || ads->resampler==NULL) { + warning("MPA dec upsampler failed %d\n", + result); + return EINVAL; + } + } + else + ads->resampler = NULL; + } + else if (result == MPG123_NEED_MORE) + ; /* workaround: do nothing */ + else if (result != MPG123_OK) { + warning("MPA dec feed error %d %s\n", result, + mpg123_plain_strerror(result)); + return EPROTO; + } + + if (ads->resampler) { + intermediate_len = (uint32_t)(n / 2 / ads->channels); + /* intermediate_len counts samples per channel */ + out_len = (uint32_t)(*sampc / 2); + + result=speex_resampler_process_interleaved_int( + ads->resampler, ads->intermediate_buffer, + &intermediate_len, sampv, &out_len); + if (result!=RESAMPLER_ERR_SUCCESS) { + warning("MPA dec upsample error: %s %d %d\n", + strerror(result), out_len, *sampc/2); + return EPROTO; + } + if (ads->channels==1) { + for (i=out_len-1;i>=0;i--) + sampv[i+i+1]=sampv[i+i]=sampv[i]; + *sampc = out_len * 2; + } + else + *sampc = out_len * ads->channels; + } + else { + n /= 2; + if (ads->channels!=1) { + for (i=0;(unsigned)i<n;i++) + sampv[i]=ads->intermediate_buffer[i]; + *sampc = n; + } + else { + for (i=0;(unsigned)i<n;i++) + sampv[i*2]=sampv[i*2+1]= + ads->intermediate_buffer[i]; + *sampc = n * 2; + } + +#ifdef DEBUG + debug("MPA dec done %d\n",*sampc); +#endif + } + + return 0; +} + diff --git a/modules/mpa/encode.c b/modules/mpa/encode.c new file mode 100644 index 0000000..d13bc0a --- /dev/null +++ b/modules/mpa/encode.c @@ -0,0 +1,196 @@ +/** + * @file mpa/encode.c mpa Encode + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include <re.h> +#include <baresip.h> +#include <twolame.h> +#include <string.h> +#include <speex/speex_resampler.h> +#include "mpa.h" + + +struct auenc_state { + twolame_options *enc; + int channels, samplerate; + SpeexResamplerState *resampler; + int16_t intermediate_buffer[MPA_FRAMESIZE*6]; + uint32_t timestamp; +}; + + +static void destructor(void *arg) +{ + struct auenc_state *aes = arg; + + if (aes->resampler) { + speex_resampler_destroy(aes->resampler); + aes->resampler = NULL; + } + + if (aes->enc) + twolame_close(&aes->enc); +#ifdef DEBUG + debug("MPA enc destroyed\n"); +#endif +} + + +int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *param, const char *fmtp) +{ + struct auenc_state *aes; + struct mpa_param prm; + int result,err=0; + + (void)param; + + if (!aesp || !ac || !ac->ch) + return EINVAL; + + aes = *aesp; + if (!aes) { + aes = mem_zalloc(sizeof(*aes), destructor); + if (!aes) + return ENOMEM; + + } + else + memset(aes,0,sizeof(*aes)); + + aes->enc = twolame_init(); + if (!aes->enc) { + warning("MPA enc create failed\n"); + mem_deref(aes); + return ENOMEM; + } +#ifdef DEBUG + debug("MPA enc created %s\n",fmtp); +#endif + aes->channels = ac->ch; + aes->timestamp = rand_u32(); + + prm.samplerate = 48000; + prm.bitrate = 128000; + prm.layer = 2; + prm.mode = SINGLE_CHANNEL; + mpa_decode_fmtp(&prm, fmtp); + aes->samplerate = prm.samplerate; + + result = 0; +#ifdef DEBUG + result |= twolame_set_verbosity(aes->enc, 5); +#else + result |= twolame_set_verbosity(aes->enc, 0); +#endif + + result |= twolame_set_mode(aes->enc, + prm.mode == SINGLE_CHANNEL ? TWOLAME_MONO : + prm.mode == DUAL_CHANNEL ? TWOLAME_DUAL_CHANNEL : + prm.mode == JOINT_STEREO ? TWOLAME_JOINT_STEREO : + prm.mode == STEREO ? TWOLAME_STEREO : TWOLAME_AUTO_MODE); + result |= twolame_set_version(aes->enc, + prm.samplerate < 32000 ? TWOLAME_MPEG2 : TWOLAME_MPEG1); + result |= twolame_set_bitrate(aes->enc, prm.bitrate/1000); + result |= twolame_set_in_samplerate(aes->enc, prm.samplerate); + result |= twolame_set_out_samplerate(aes->enc, prm.samplerate); + result |= twolame_set_num_channels(aes->enc, 2); + if (result!=0) { + warning("MPA enc set failed\n"); + err=EINVAL; + goto out; + } + + result = twolame_init_params(aes->enc); + if (result!=0) { + warning("MPA enc init params failed\n"); + err=EINVAL; + goto out; + } +#ifdef DEBUG + twolame_print_config(aes->enc); +#endif + if (prm.samplerate != MPA_IORATE) { + aes->resampler = speex_resampler_init(2, MPA_IORATE, + prm.samplerate, 3, &result); + if (result!=RESAMPLER_ERR_SUCCESS) { + warning("MPA enc resampler init failed %d\n",result); + err=EINVAL; + goto out; + } + + } + else + aes->resampler = NULL; + +out: + if (err) + mem_deref(aes); + else + *aesp = aes; + + return err; +} + + +int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc) +{ + int n; + spx_uint32_t intermediate_len,in_len; + + if (!aes || !buf || !len || !sampv) + return EINVAL; + + if (aes->resampler) { + in_len = (uint32_t)sampc/2; + intermediate_len = sizeof(aes->intermediate_buffer) + / sizeof(aes->intermediate_buffer[0]); + n=speex_resampler_process_interleaved_int(aes->resampler, + sampv, &in_len, aes->intermediate_buffer, + &intermediate_len); + if (n!=RESAMPLER_ERR_SUCCESS || in_len != sampc/2) { + warning("MPA enc downsample error: %s %d %d\n", + strerror(n), in_len, sampc/2); + return EPROTO; + } + n = twolame_encode_buffer_interleaved(aes->enc, + aes->intermediate_buffer, intermediate_len, + buf+4, (int)(*len)-4); +#ifdef DEBUG + debug("MPA enc %d %d %d %d %d %p\n",intermediate_len,sampc, + aes->channels,*len,n,aes->enc); +#endif + } + else { + n = twolame_encode_buffer_interleaved(aes->enc, + sampv, (int)(sampc/2), + buf+4, (int)(*len)-4); +#ifdef DEBUG + debug("MPA enc %d %d %d %d\n",sampc, + aes->channels,*len,n); +#endif + } + if (n < 0) { + warning("MPA enc error %s\n", strerror((int)n)); + return EPROTO; + } + + if (n > 0) { + *(uint32_t*)(void *)buf = 0; + *len = n+4; + } + else + *len = 0; + +#ifdef DEBUG + debug("MPA enc done %d %d %d %d %p\n",sampc,aes->channels, + *len,n,aes->enc); +#endif + aes->timestamp += ((MPA_FRAMESIZE*MPA_RTPRATE)<<4) / aes->samplerate; + + return 0x00010000 | ((aes->timestamp>>4) & 0x0000ffff); +} + diff --git a/modules/mpa/module.mk b/modules/mpa/module.mk new file mode 100644 index 0000000..b060f12 --- /dev/null +++ b/modules/mpa/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2016 Symonics GmbH +# + +MOD := mpa +$(MOD)_SRCS += mpa.c +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += sdp.c +$(MOD)_SRCS += encode.c +$(MOD)_LFLAGS += -ltwolame -lmpg123 -lspeexdsp -lm + +include mk/mod.mk diff --git a/modules/mpa/mpa.c b/modules/mpa/mpa.c new file mode 100644 index 0000000..d36e07d --- /dev/null +++ b/modules/mpa/mpa.c @@ -0,0 +1,205 @@ +/** + * @file mpa.c mpa Audio Codec + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include <re.h> +#include <baresip.h> +#include <ctype.h> +#include <string.h> +#include "mpa.h" +#include <mpg123.h> + +/** + * @defgroup mpa mpa + * + * The mpa audio codec + * + * Supported version: + * libmpg123 1.16.0 or later + * libtwolame 0.3.13 or later + * + * References: + * + * RFC 2250 RTP Payload Format for the mpa Speech and Audio Codec + * + */ + +/* +4.1.17. Registration of MIME media type audio/MPA + + MIME media type name: audio + + MIME subtype name: MPA (MPEG audio) + + Required parameters: None + + Optional parameters: + layer: which layer of MPEG audio encoding; permissible values + are 1, 2, 3. + + samplerate: the rate at which audio is sampled. MPEG-1 audio + supports sampling rates of 32, 44.1, and 48 kHz; MPEG-2 + supports sampling rates of 16, 22.05 and 24 kHz. This parameter + is separate from the RTP timestamp clock rate which is always + 90000 Hz for MPA. + + mode: permissible values are "stereo", "joint_stereo", + "single_channel", "dual_channel". The "channels" parameter + does not apply to MPA. It is undefined to put a number of + channels in the SDP rtpmap attribute for MPA. + + bitrate: the data rate for the audio bit stream. + + ptime: RECOMMENDED duration of each packet in milliseconds. + + maxptime: maximum duration of each packet in milliseconds. + + Parameters which are omitted are left to the encoder to choose + based on the session bandwidth, configuration information, or + other constraints. The selected layer as well as the sampling + rate and mode are indicated in the payload so receivers can + process the data without these parameters being specified + externally. + + Encoding considerations: + This type is only defined for transfer via RTP [RFC 3550]. + + Security considerations: See Section 5 of RFC 3555 + + Interoperability considerations: none + + Published specification: RFC 3551 + + Applications which use this media type: + Audio and video streaming and conferencing tools. + +*/ + + +static struct aucodec mpa = { + .pt = "14", + .name = "MPA", + .srate = MPA_IORATE, + .crate = MPA_RTPRATE, + .ch = 1, +/* MPA does not expect channels count, even those it is stereo */ + .fmtp = "layer=2", + .encupdh = mpa_encode_update, + .ench = mpa_encode_frm, + .decupdh = mpa_decode_update, + .dech = mpa_decode_frm, +}; + + +static int module_init(void) +{ + struct conf *conf = conf_cur(); + uint32_t value; + static char fmtp[256]; + static char mode[30]; + int res; + + /** generate fmtp string based on config file */ + + strcpy(mode,mpa.fmtp); + + if (0 == conf_get_u32(conf, "mpa_bitrate", &value)) { + if (value<8000 || value>384000) { + warning("MPA bitrate between 8000 and " + "384000 are allowed.\n"); + return -1; + } + + (void)re_snprintf(fmtp+strlen(fmtp), + sizeof(fmtp)-strlen(fmtp), + "; bitrate=%d", value); + } + if (0 == conf_get_u32(conf, "mpa_layer", &value)) { + if (value<1 || value>4) { + warning("MPA layer 1, 2 or 3 are allowed."); + return -1; + } + (void)re_snprintf(fmtp+strlen(fmtp), + sizeof(fmtp)-strlen(fmtp), + "; layer=%d", value); + } + if (0 == conf_get_u32(conf, "mpa_samplerate", &value)) { + switch (value) { + case 32000: + case 44100: + case 48000: + case 16000: + case 22050: + case 24000: + break; + default: + warning("MPA samplerates of 16, 22.05, 24, 32, " + "44.1, and 48 kHz are allowed.\n"); + return -1; + } + (void)re_snprintf(fmtp+strlen(fmtp), + sizeof(fmtp)-strlen(fmtp), + "; samplerate=%d", value); + } + if (0 == conf_get_str(conf, "mpa_mode", mode, sizeof(mode))) { + char *p = mode; + while (*p) { + *p = tolower(*p); + ++p; + } + + if (strcmp(mode,"stereo") + && strcmp(mode,"joint_stereo") + && strcmp(mode,"single_channel") + && strcmp(mode,"dual_channel")) { + warning("MPA mode: Permissible values are stereo, " + "joint_stereo, single_channel, dual_channel.\n"); + return -1; + } + + (void)re_snprintf(fmtp+strlen(fmtp), + sizeof(fmtp)-strlen(fmtp), + "; mode=%s", mode); + } + + if (fmtp[0]==';' && fmtp[1]==' ') + mpa.fmtp = fmtp+2; + else + mpa.fmtp = fmtp; + + /* init decoder library */ + res = mpg123_init(); + if (res != MPG123_OK) { + warning("MPA libmpg123 init error %s\n", + mpg123_plain_strerror(res)); + return -1; + } + + aucodec_register(baresip_aucodecl(), &mpa); + +#ifdef DEBUG + info("MPA init with %s\n",mpa.fmtp); +#endif + return 0; +} + + +static int module_close(void) +{ + aucodec_unregister(&mpa); + + mpg123_exit(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(mpa) = { + "MPA", + "audio codec", + module_init, + module_close, +}; + diff --git a/modules/mpa/mpa.h b/modules/mpa/mpa.h new file mode 100644 index 0000000..0db2528 --- /dev/null +++ b/modules/mpa/mpa.h @@ -0,0 +1,37 @@ +/** + * @file mpa.h Private mpa Interface + * + * Copyright (C) 2016 Symonics GmbH + */ + +#define MPA_FRAMESIZE 1152 +#define MPA_IORATE 48000 +#define MPA_RTPRATE 90000 +#define BARESIP_FRAMESIZE (MPA_IORATE/50*2) + +#undef DEBUG + +struct mpa_param { + unsigned samplerate; + unsigned bitrate; + unsigned layer; + enum { AUTO=0, STEREO, JOINT_STEREO, SINGLE_CHANNEL, DUAL_CHANNEL } + mode; +}; + + +/* Encode */ +int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac, + struct auenc_param *prm, const char *fmtp); +int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len, + const int16_t *sampv, size_t sampc); + + +/* Decode */ +int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac, + const char *fmtp); +int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len); + +/* SDP */ +void mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp); diff --git a/modules/mpa/sdp.c b/modules/mpa/sdp.c new file mode 100644 index 0000000..a4e432c --- /dev/null +++ b/modules/mpa/sdp.c @@ -0,0 +1,55 @@ +/** + * @file mpa/sdp.c mpa SDP Functions + * + * Copyright (C) 2016 Symonics GmbH + */ + +#include <re.h> +#include <baresip.h> +#include <string.h> +#include "mpa.h" + + +static void assign_if (uint32_t *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 mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp) +{ + struct pl pl, val; + + if (!prm || !fmtp) + return; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "bitrate", &val)) + assign_if (&prm->bitrate, &val, 8000, 384000); + + if (fmt_param_get(&pl, "samplerate", &val)) + assign_if (&prm->samplerate, &val, 16000, 48000); + + if (fmt_param_get(&pl, "layer", &val)) + assign_if (&prm->layer, &val, 1, 3); + + if (fmt_param_get(&pl, "mode", &val)) { + + if (!strncmp("stereo",val.p,val.l)) + prm->mode = STEREO; + else if (!strncmp("joint_stereo",val.p,val.l)) + prm->mode = JOINT_STEREO; + else if (!strncmp("single_channel",val.p,val.l)) + prm->mode = SINGLE_CHANNEL; + else if (!strncmp("dual_channel",val.p,val.l)) + prm->mode = DUAL_CHANNEL; + } +} + diff --git a/modules/mqtt/README.md b/modules/mqtt/README.md new file mode 100644 index 0000000..6a86b84 --- /dev/null +++ b/modules/mqtt/README.md @@ -0,0 +1,59 @@ +README +------ + + +This module implements an MQTT (Message Queue Telemetry Transport) client +for publishing and subscribing to topics. + + +The module is using libmosquitto + + +Starting the MQTT broker: + +``` +$ /usr/local/sbin/mosquitto -v +``` + + +Subscribing to all topics: + +``` +$ mosquitto_sub -t /baresip/+ +``` + + +Publishing to the topic: + +``` +$ mosquitto_pub -t /baresip/xxx -m foo=42 +``` + + +## Topic patterns + +(Outgoing direction is from baresip mqtt module to broker, + incoming direction is from broker to baresip mqtt module) + +* /baresip/event Outgoing events from ua_event +* /baresip/command Incoming long command request +* /baresip/command_resp Outgoing long command response + + +## Examples + +``` +/baresip/event sip:aeh@iptel.org,REGISTERING +/baresip/event sip:aeh@iptel.org,REGISTER_OK +/baresip/event sip:aeh@iptel.org,SHUTDOWN +``` + +``` +mosquitto_pub -t /baresip/command -m "/dial music" + +/baresip/command /dial music +/baresip/command_resp (null) +/baresip/event sip:aeh@iptel.org,CALL_ESTABLISHED +/baresip/event sip:aeh@iptel.org,CALL_CLOSED +``` + diff --git a/modules/mqtt/module.mk b/modules/mqtt/module.mk new file mode 100644 index 0000000..cb4c379 --- /dev/null +++ b/modules/mqtt/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := mqtt +$(MOD)_SRCS += mqtt.c +$(MOD)_SRCS += publish.c +$(MOD)_SRCS += subscribe.c +$(MOD)_LFLAGS += -lmosquitto +$(MOD)_CFLAGS += + +include mk/mod.mk diff --git a/modules/mqtt/mqtt.c b/modules/mqtt/mqtt.c new file mode 100644 index 0000000..a927959 --- /dev/null +++ b/modules/mqtt/mqtt.c @@ -0,0 +1,157 @@ +/** + * @file mqtt.c Message Queue Telemetry Transport (MQTT) client + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +static char broker_host[256] = "127.0.0.1"; +static uint32_t broker_port = 1883; + +static struct mqtt s_mqtt; + + +static void fd_handler(int flags, void *arg) +{ + struct mqtt *mqtt = arg; + + mosquitto_loop_read(mqtt->mosq, 1); + + mosquitto_loop_write(mqtt->mosq, 1); +} + + +/* XXX: use mosquitto_socket and fd_listen instead? */ +static void tmr_handler(void *data) +{ + struct mqtt *mqtt = data; + int ret; + + tmr_start(&mqtt->tmr, 500, tmr_handler, mqtt); + + ret = mosquitto_loop_misc(mqtt->mosq); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: error in loop (%s)\n", mosquitto_strerror(ret)); + } +} + + +/* + * This is called when the broker sends a CONNACK message + * in response to a connection. + */ +static void connect_callback(struct mosquitto *mosq, void *obj, int result) +{ + struct mqtt *mqtt = obj; + int err; + (void)mqtt; + + if (result != MOSQ_ERR_SUCCESS) { + warning("mqtt: could not connect to broker (%s)\n", + mosquitto_strerror(result)); + return; + } + + info("mqtt: connected to broker at %s:%d\n", + broker_host, broker_port); + + err = mqtt_subscribe_start(mqtt); + if (err) { + warning("mqtt: subscribe_init failed (%m)\n", err); + } +} + + +static int module_init(void) +{ + const int keepalive = 60; + int ret; + int err = 0; + + tmr_init(&s_mqtt.tmr); + + mosquitto_lib_init(); + + conf_get_str(conf_cur(), "mqtt_broker_host", + broker_host, sizeof(broker_host)); + conf_get_u32(conf_cur(), "mqtt_broker_port", &broker_port); + + s_mqtt.mosq = mosquitto_new("baresip", true, &s_mqtt); + if (!s_mqtt.mosq) { + warning("mqtt: failed to create client instance\n"); + return ENOMEM; + } + + err = mqtt_subscribe_init(&s_mqtt); + if (err) + return err; + + mosquitto_connect_callback_set(s_mqtt.mosq, connect_callback); + + ret = mosquitto_connect(s_mqtt.mosq, broker_host, broker_port, + keepalive); + if (ret != MOSQ_ERR_SUCCESS) { + + err = ret == MOSQ_ERR_ERRNO ? errno : EIO; + + warning("mqtt: failed to connect to %s:%d (%s)\n", + broker_host, broker_port, + mosquitto_strerror(ret)); + return err; + } + + tmr_start(&s_mqtt.tmr, 1, tmr_handler, &s_mqtt); + + err = mqtt_publish_init(&s_mqtt); + if (err) + return err; + + s_mqtt.fd = mosquitto_socket(s_mqtt.mosq); + + err = fd_listen(s_mqtt.fd, FD_READ, fd_handler, &s_mqtt); + if (err) + return err; + + info("mqtt: module loaded\n"); + + return err; +} + + +static int module_close(void) +{ + fd_close(s_mqtt.fd); + + mqtt_publish_close(); + + mqtt_subscribe_close(); + + tmr_cancel(&s_mqtt.tmr); + + if (s_mqtt.mosq) { + + mosquitto_disconnect(s_mqtt.mosq); + + mosquitto_destroy(s_mqtt.mosq); + s_mqtt.mosq = NULL; + } + + mosquitto_lib_cleanup(); + + info("mqtt: module unloaded\n"); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(mqtt) = { + "mqtt", + "application", + module_init, + module_close +}; diff --git a/modules/mqtt/mqtt.h b/modules/mqtt/mqtt.h new file mode 100644 index 0000000..e79fb6e --- /dev/null +++ b/modules/mqtt/mqtt.h @@ -0,0 +1,26 @@ + + +struct mqtt { + struct mosquitto *mosq; + struct tmr tmr; + int fd; +}; + + +/* + * Subscribe direction (incoming) + */ + +int mqtt_subscribe_init(struct mqtt *mqtt); +int mqtt_subscribe_start(struct mqtt *mqtt); +void mqtt_subscribe_close(void); + + +/* + * Publish direction (outgoing) + */ + +int mqtt_publish_init(struct mqtt *mqtt); +void mqtt_publish_close(void); +int mqtt_publish_message(struct mqtt *mqtt, const char *topic, + const char *fmt, ...); diff --git a/modules/mqtt/publish.c b/modules/mqtt/publish.c new file mode 100644 index 0000000..524b0a2 --- /dev/null +++ b/modules/mqtt/publish.c @@ -0,0 +1,104 @@ +/** + * @file publish.c MQTT client -- publish + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +/* + * This file contains functions for sending outgoing messages + * from baresip to broker (publish) + */ + + +/* + * Relay UA events as publish messages to the Broker + * + * XXX: move JSON encoding to baresip core + */ +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct mqtt *mqtt = arg; + struct odict *od = NULL; + int err; + + err = odict_alloc(&od, 8); + if (err) + return; + + err = event_encode_dict(od, ua, ev, call, prm); + if (err) + goto out; + + err = mqtt_publish_message(mqtt, "/baresip/event", "%H", + json_encode_odict, od); + if (err) { + warning("mqtt: failed to publish message (%m)\n", err); + goto out; + } + + out: + mem_deref(od); +} + + +int mqtt_publish_message(struct mqtt *mqtt, const char *topic, + const char *fmt, ...) +{ + char *message; + va_list ap; + int ret; + int err = 0; + + if (!mqtt || !topic || !fmt) + return EINVAL; + + va_start(ap, fmt); + err = re_vsdprintf(&message, fmt, ap); + va_end(ap); + + if (err) + return err; + + ret = mosquitto_publish(mqtt->mosq, + NULL, + topic, + (int)str_len(message), + message, + 0, + false); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: failed to publish (%s)\n", + mosquitto_strerror(ret)); + err = EINVAL; + goto out; + } + + out: + mem_deref(message); + return err; +} + + +int mqtt_publish_init(struct mqtt *mqtt) +{ + int err; + + err = uag_event_register(ua_event_handler, mqtt); + if (err) + return err; + + return err; +} + + +void mqtt_publish_close(void) +{ + uag_event_unregister(&ua_event_handler); +} diff --git a/modules/mqtt/subscribe.c b/modules/mqtt/subscribe.c new file mode 100644 index 0000000..d36ece2 --- /dev/null +++ b/modules/mqtt/subscribe.c @@ -0,0 +1,146 @@ +/** + * @file subscribe.c MQTT client -- subscribe + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <mosquitto.h> +#include <re.h> +#include <baresip.h> +#include "mqtt.h" + + +static const char *subscription_pattern = "/baresip/+"; + + +static int print_handler(const char *p, size_t size, void *arg) +{ + struct mbuf *mb = arg; + + return mbuf_write_mem(mb, (void *)p, size); +} + + +static void handle_command(struct mqtt *mqtt, const struct pl *msg) +{ + struct mbuf *resp = mbuf_alloc(1024); + struct re_printf pf = {print_handler, resp}; + struct odict *od = NULL; + const struct odict_entry *oe_cmd, *oe_prm, *oe_tok; + char buf[256], resp_topic[256]; + int err; + + /* XXX: add transaction ID ? */ + + err = json_decode_odict(&od, 32, msg->p, msg->l, 16); + if (err) { + warning("mqtt: failed to decode JSON with %zu bytes (%m)\n", + msg->l, err); + return; + } + + oe_cmd = odict_lookup(od, "command"); + oe_prm = odict_lookup(od, "params"); + oe_tok = odict_lookup(od, "token"); + if (!oe_cmd) { + warning("mqtt: missing json entries\n"); + goto out; + } + + debug("mqtt: handle_command: cmd='%s', token='%s'\n", + oe_cmd ? oe_cmd->u.str : "", + oe_tok ? oe_tok->u.str : ""); + + re_snprintf(buf, sizeof(buf), "%s%s%s", + oe_cmd->u.str, + oe_prm ? " " : "", + oe_prm ? oe_prm->u.str : ""); + + /* Relay message to long commands */ + err = cmd_process_long(baresip_commands(), + buf, + str_len(buf), + &pf, NULL); + if (err) { + warning("mqtt: error processing command (%m)\n", err); + } + + /* NOTE: the command will now write the response + to the resp mbuf, send it back to broker */ + + re_snprintf(resp_topic, sizeof(resp_topic), + "/baresip/command_resp/%s", + oe_tok ? oe_tok->u.str : "nil"); + + err = mqtt_publish_message(mqtt, resp_topic, + "%b", + resp->buf, resp->end); + if (err) { + warning("mqtt: failed to publish message (%m)\n", err); + goto out; + } + + out: + mem_deref(resp); + mem_deref(od); +} + + +/* + * This is called when a message is received from the broker. + */ +static void message_callback(struct mosquitto *mosq, void *obj, + const struct mosquitto_message *message) +{ + struct mqtt *mqtt = obj; + struct pl msg; + bool match = false; + + info("mqtt: got message '%b' for topic '%s'\n", + (char*) message->payload, (size_t)message->payloadlen, + message->topic); + + msg.p = message->payload; + msg.l = message->payloadlen; + + mosquitto_topic_matches_sub("/baresip/command", message->topic, + &match); + if (match) { + info("mqtt: got message for '%s' topic\n", message->topic); + + handle_command(mqtt, &msg); + } +} + + +int mqtt_subscribe_init(struct mqtt *mqtt) +{ + if (!mqtt) + return EINVAL; + + mosquitto_message_callback_set(mqtt->mosq, message_callback); + + return 0; +} + + +int mqtt_subscribe_start(struct mqtt *mqtt) +{ + int ret; + + ret = mosquitto_subscribe(mqtt->mosq, NULL, subscription_pattern, 0); + if (ret != MOSQ_ERR_SUCCESS) { + warning("mqtt: failed to subscribe (%s)\n", + mosquitto_strerror(ret)); + return EPROTO; + } + + info("mqtt: subscribed to pattern '%s'\n", subscription_pattern); + + return 0; +} + + +void mqtt_subscribe_close(void) +{ +} 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..4bae5f5 --- /dev/null +++ b/modules/mwi/mwi.c @@ -0,0 +1,225 @@ +/** + * @file mwi.c Message Waiting Indication (RFC 3842) + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup mwi mwi + * + * Message Waiting Indication + * + */ + + +struct mwi { + struct le le; + struct sipsub *sub; + struct ua *ua; + struct tmr tmr; + bool shutdown; +}; + +static struct tmr tmr; +static struct list mwil; + + +static void destructor(void *arg) +{ + struct mwi *mwi = arg; + + tmr_cancel(&mwi->tmr); + list_unlink(&mwi->le); + mem_deref(mwi->sub); + mem_deref(mwi->ua); +} + + +static void deref_handler(void *arg) +{ + struct mwi *mwi = arg; + mem_deref(mwi); +} + + +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)) { + struct ui_sub *uis = baresip_uis(); + ui_output(uis, "----- MWI for %s -----\n", ua_aor(mwi->ua)); + ui_output(uis, "%b\n", mbuf_buf(msg->mb), + mbuf_get_left(msg->mb)); + } + + (void)sip_treply(NULL, sip, msg, 200, "OK"); + + if (mwi->shutdown) + mem_deref(mwi); +} + + +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 = mem_ref(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_account(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 struct mwi *mwi_find(const struct ua *ua) +{ + struct le *le; + + for (le = mwil.head; le; le = le->next) { + + struct mwi *mwi = le->data; + + if (mwi->ua == ua) + return mwi; + } + + return NULL; +} + + +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; + + if (ev == UA_EVENT_REGISTER_OK) { + + if (!mwi_find(ua)) + mwi_subscribe(ua); + } + else if (ev == UA_EVENT_SHUTDOWN) { + + struct le *le; + + info("mwi: shutdown\n"); + + le = list_head(&mwil); + while (le) { + struct mwi *mwi = le->data; + le = le->next; + + mwi->shutdown = true; + + if (mwi->sub) { + mwi->sub = mem_deref(mwi->sub); + tmr_start(&mwi->tmr, 500, deref_handler, mwi); + } + else + mem_deref(mwi); + } + } +} + + +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; + struct account *acc = ua_account(ua); + + if (account_regint(acc) == 0) { + mwi_subscribe(ua); + } + } +} + + +static int module_init(void) +{ + list_init(&mwil); + tmr_start(&tmr, 1, tmr_handler, 0); + + return uag_event_register(ua_event_handler, NULL); +} + + +static int module_close(void) +{ + uag_event_unregister(ua_event_handler); + 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..2b2efff --- /dev/null +++ b/modules/natbd/natbd.c @@ -0,0 +1,509 @@ +/** + * @file natbd.c NAT Behavior Discovery Module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup natbd natbd + * + * 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. + * + * The STUN server used must be compliant with RFC 5780 + */ + + +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) { + warning("natbd: 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) { + warning("natbd: 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) { + warning("natbd: 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) { + warning("natbd: Generic ALG detection failed: %m\n", err); + goto out; + } + else if (scode) { + warning("natbd: 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) +{ + struct network *net = baresip_network(); + 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) { + warning("natbd: nat_hairpinning_start() failed (%m)\n", + err); + } + } + + if (!natbd->nm) { + err |= nat_mapping_alloc(&natbd->nm, + net_laddr_af(net, net_af(net)), + &natbd->stun_srv, natbd->proto, NULL, + nat_mapping_handler, natbd); + err |= nat_mapping_start(natbd->nm); + if (err) { + warning("natbd: 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) { + warning("natbd: 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) { + warning("natbd: natbd_init: %m\n", err); + } + err |= nat_genalg_start(natbd->ga); + if (err) { + warning("natbd: 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) { + warning("natbd: 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) { + warning("natbd: 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(baresip_network()), + stun_usage_binding, + proto_str, net_af(baresip_network()), + natbd->host, natbd->port, + dns_handler, natbd); + if (err) + goto out; + + out: + if (err) { + warning("natbd: 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 { + warning("natbd: 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[] = { + {"natbd", 'z', 0, "NAT status", status} +}; + + +static int module_init(void) +{ + char server[256] = ""; + uint32_t interval = 3600; + int err; + + err = cmd_register(baresip_commands(), 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]) { + warning("natbd: 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) { + warning("natbd: 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(baresip_commands(), 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..45c7809 --- /dev/null +++ b/modules/natpmp/natpmp.c @@ -0,0 +1,387 @@ +/** + * @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 comp { + struct natpmp_req *natpmp; + struct mnat_media *media; /* pointer to parent */ + struct tmr tmr; + uint16_t int_port; + uint32_t lifetime; + unsigned id; + bool granted; + } compv[2]; + unsigned compc; + + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; +}; + + +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; + unsigned i; + + list_unlink(&m->le); + + for (i=0; i<m->compc; i++) { + struct comp *comp = &m->compv[i]; + + /* Destroy the mapping */ + if (comp->granted) { + (void)natpmp_mapping_request(NULL, &natpmp_srv, + comp->int_port, 0, 0, + NULL, NULL); + } + + tmr_cancel(&comp->tmr); + mem_deref(comp->natpmp); + } + + mem_deref(m->sdpm); +} + + +static void complete(struct mnat_sess *sess, int err) +{ + mnat_estab_h *estabh = sess->estabh; + + if (sess->estabh) { + + sess->estabh = NULL; + + estabh(err, 0, "done", sess->arg); + } +} + + +static bool all_components_granted(const struct mnat_media *m) +{ + unsigned i; + + if (!m || !m->compc) + return false; + + for (i=0; i<m->compc; i++) { + const struct comp *comp = &m->compv[i]; + if (!comp->granted) + return false; + } + + return true; +} + +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 (!all_components_granted(m)) + return; + } + + complete(sess, 0); +} + + +static void refresh_timeout(void *arg) +{ + struct comp *comp = arg; + + comp->natpmp = mem_deref(comp->natpmp); + (void)natpmp_mapping_request(&comp->natpmp, &natpmp_srv, + comp->int_port, 0, comp->lifetime, + natpmp_resp_handler, comp); +} + + +static void natpmp_resp_handler(int err, const struct natpmp_resp *resp, + void *arg) +{ + struct comp *comp = arg; + struct mnat_media *m = comp->media; + struct sa map_addr; + + if (err) { + warning("natpmp: response error: %m\n", err); + complete(m->sess, 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); + complete(m->sess, EPROTO); + return; + } + + if (resp->u.map.int_port != comp->int_port) { + info("natpmp: ignoring response for internal_port=%u\n", + resp->u.map.int_port); + return; + } + + info("natpmp: mapping granted for comp %u:" + " internal_port=%u, external_port=%u, lifetime=%u\n", + comp->id, + 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); + comp->lifetime = resp->u.map.lifetime; + + /* Update SDP media with external IP-address mapping */ + if (comp->id == 1) + sdp_media_set_laddr(m->sdpm, &map_addr); + else + sdp_media_set_laddr_rtcp(m->sdpm, &map_addr); + + comp->granted = true; + + tmr_start(&comp->tmr, comp->lifetime * 1000 * 3/4, + refresh_timeout, comp); + + 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 comp_alloc(struct comp *comp, void *sock) +{ + struct sa laddr; + int err; + + err = udp_local_get(sock, &laddr); + if (err) + goto out; + + comp->int_port = sa_port(&laddr); + + info("natpmp: `%s' stream comp %u local UDP port is %u\n", + sdp_media_name(comp->media->sdpm), comp->id, comp->int_port); + + err = natpmp_mapping_request(&comp->natpmp, &natpmp_srv, + comp->int_port, 0, comp->lifetime, + natpmp_resp_handler, comp); + if (err) + goto out; + + out: + 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; + unsigned i; + int err = 0; + (void)sock2; + + if (!mp || !sess || !sdpm || proto != IPPROTO_UDP) + return EINVAL; + if (!sock1) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + m->compc = sock2 ? 2 : 1; + + list_append(&sess->medial, &m->le, m); + m->sess = sess; + m->sdpm = mem_ref(sdpm); + + for (i=0; i<m->compc; i++) { + + struct comp *comp = &m->compv[i]; + + comp->id = i+1; + comp->media = m; + comp->lifetime = LIFETIME; + + err = comp_alloc(comp, i==0 ? sock1 : sock2); + 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, baresip_mnatl(), "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/omx/README b/modules/omx/README new file mode 100644 index 0000000..e24c274 --- /dev/null +++ b/modules/omx/README @@ -0,0 +1,17 @@ +README +------ + +This module implements support for the VideoCore4 of +the Raspberry Pi A/B/2/3. +Currently it only does video playback. + +EXAMPLE CONFIG +-------------- + +# Video +video_display omx,nil + +# Video codec Modules (in order) +module omx.so + + diff --git a/modules/omx/module.c b/modules/omx/module.c new file mode 100644 index 0000000..a5b6fa8 --- /dev/null +++ b/modules/omx/module.c @@ -0,0 +1,137 @@ +/** + * @file omx/module.c Raspberry Pi VideoCoreIV OpenMAX interface + * + * Copyright (C) 2016 - 2017 Creytiv.com + * Copyright (C) 2016 - 2017 Jonathan Sieber + */ + + +#include "omx.h" + +#include <stdlib.h> + +#include <re.h> +#include <rem.h> +#include <baresip.h> + +int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd, + struct vidisp_prm *prm, const char *dev, vidisp_resize_h *resizeh, + void *arg); +int omx_vidisp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame); + +struct vidisp_st { + const struct vidisp *vd; /* inheritance */ + struct vidsz size; + struct omx_state* omx; +}; + +static struct vidisp* vid; + +static struct omx_state omx; + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + omx_display_disable(st->omx); +} + + +int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + + /* Not used by OMX */ + (void) prm; + (void) dev; + (void) resizeh; + (void) arg; + + info("omx: vidisp_alloc\n"); + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = vd; + *vp = st; + + st->omx = &omx; + + return 0; +} + + +int omx_vidisp_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + int err = 0; + void* buf; + uint32_t len; + + struct vidframe omx_frame; + + (void)title; + + if (frame->fmt != VID_FMT_YUV420P) { + return EINVAL; + } + + if (!vidsz_cmp(&st->size, &frame->size)) { + info("omx: new frame size: w=%d h=%d\n", + frame->size.w, frame->size.h); + info("omx: linesize[0]=%d\tlinesize[1]=%d\tlinesize[2]=%d\n", + frame->linesize[0], frame->linesize[1], + frame->linesize[2]); + err = omx_display_enable(st->omx, + frame->size.w, frame->size.h, frame->size.w); + if (err) { + warning("omx_display_enable failed"); + return err; + } + st->size = frame->size; + } + + /* Get Buffer Pointer */ + omx_display_input_buffer(st->omx, &buf, &len); + + vidframe_init_buf(&omx_frame, VID_FMT_YUV420P, &frame->size, + buf); + + vidconv(&omx_frame, frame, 0); + + omx_display_flush_buffer(st->omx); + return 0; +} + + +static int module_init(void) +{ + if (omx_init(&omx) != 0) { + warning("Could not initialize OpenMAX"); + return ENODEV; + } + + return vidisp_register(&vid, baresip_vidispl(), "omx", + omx_vidisp_alloc, NULL, omx_vidisp_display, NULL); +} + + +static int module_close(void) +{ + /* HACK: not deinitializing OMX because of a hangup */ + /* omx_deinit(&omx) */ + vid = mem_deref(vid); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(omx) = { + "omx", + "vidisp", + module_init, + module_close +}; diff --git a/modules/omx/module.mk b/modules/omx/module.mk new file mode 100644 index 0000000..958a1a5 --- /dev/null +++ b/modules/omx/module.mk @@ -0,0 +1,23 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2015 Creytiv.com +# + +MOD := omx +$(MOD)_SRCS += omx.c module.c + +ifneq ($(USE_OMX_RPI),) +$(MOD)_CFLAGS := -DRASPBERRY_PI -DOMX_SKIP64BIT \ + -I/usr/local/include/interface/vmcs_host/linux/ \ + -I /usr/local/include/interface/vcos/pthreads/ \ + -I /opt/vc/include -I /opt/vc/include/interface/vmcs_host/linux \ + -I /opt/vc/include/interface/vcos/pthreads +$(MOD)_LFLAGS += -lvcos -lbcm_host -lopenmaxil -L /opt/vc/lib +endif + +ifneq ($(USE_OMX_BELLAGIO),) +$(MOD)_LFLAGS += -lomxil-bellagio +endif + +include mk/mod.mk diff --git a/modules/omx/omx.c b/modules/omx/omx.c new file mode 100644 index 0000000..6b08d19 --- /dev/null +++ b/modules/omx/omx.c @@ -0,0 +1,349 @@ +/** + * @file omx.c Raspberry Pi VideoCoreIV OpenMAX interface + * + * Copyright (C) 2016 - 2017 Creytiv.com + * Copyright (C) 2016 - 2017 Jonathan Sieber + */ + +#include "omx.h" + +#include <re.h> +#include <rem.h> +#include <baresip.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Avoids a VideoCore header warning about clock_gettime() */ +#include <time.h> +#include <sys/time.h> + +/** + * @defgroup omx omx + * + * TODO: + * * Proper sync OMX events across threads, instead of busy waiting + */ + +#ifdef RASPBERRY_PI +static const int VIDEO_RENDER_PORT = 90; +#else +static const int VIDEO_RENDER_PORT = 0; +#endif + +/* +static void setHeader(OMX_PTR header, OMX_U32 size) { + OMX_VERSIONTYPE* ver = (OMX_VERSIONTYPE*)(header + sizeof(OMX_U32)); + *((OMX_U32*)header) = size; + + ver->s.nVersionMajor = VERSIONMAJOR; + ver->s.nVersionMinor = VERSIONMINOR; + ver->s.nRevision = VERSIONREVISION; + ver->s.nStep = VERSIONSTEP; +} +* */ + + +static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE hComponent, OMX_PTR pAppData, + OMX_EVENTTYPE eEvent, OMX_U32 nData1, OMX_U32 nData2, + OMX_PTR pEventData) +{ + (void) hComponent; + + switch (eEvent) { + + case OMX_EventCmdComplete: + debug("omx.EventHandler: Previous command completed\n" + "d1=%x\td2=%x\teventData=%p\tappdata=%p\n", + nData1, nData2, pEventData, pAppData); + /* TODO: Put these event into a multithreaded queue, + * properly wait for them in the issuing code */ + break; + + case OMX_EventError: + warning("omx.EventHandler: Error event type " + "data1=%x\tdata2=%x\n", nData1, nData2); + break; + + default: + warning("omx.EventHandler: Unknown event type %d\t" + "data1=%x data2=%x\n", eEvent, nData1, nData2); + return -1; + break; + } + + return 0; +} + + +static OMX_ERRORTYPE EmptyBufferDone(OMX_HANDLETYPE hComponent, + OMX_PTR pAppData, + OMX_BUFFERHEADERTYPE* pBuffer) +{ + (void) hComponent; + (void) pAppData; + (void) pBuffer; + + /* TODO: Wrap every call that can generate an event, + * and panic if an unexpected event arrives */ + return 0; +} + + +static OMX_ERRORTYPE FillBufferDone(OMX_HANDLETYPE hComponent, + OMX_PTR pAppData, OMX_BUFFERHEADERTYPE* pBuffer) +{ + (void) hComponent; + (void) pAppData; + (void) pBuffer; + debug("FillBufferDone\n"); + return 0; +} + + +static struct OMX_CALLBACKTYPE callbacks = { + EventHandler, + EmptyBufferDone, + &FillBufferDone +}; + + +int omx_init(struct omx_state* st) +{ + OMX_ERRORTYPE err; +#ifdef RASPBERRY_PI + bcm_host_init(); +#endif + + st->buffers = NULL; + + err = OMX_Init(); +#ifdef RASPBERRY_PI + err |= OMX_GetHandle(&st->video_render, + "OMX.broadcom.video_render", 0, &callbacks); +#else + err |= OMX_GetHandle(&st->video_render, + "OMX.st.video.xvideosink", 0, &callbacks); +#endif + + if (!st->video_render || err != 0) { + warning("Failed to create OMX video_render component\n"); + return ENOENT; + } + else { + info("created video_render component\n"); + return 0; + } +} + + +/* Some busy loops to verify we're running in order */ +static void block_until_state_changed(OMX_HANDLETYPE hComponent, + OMX_STATETYPE wanted_eState) +{ + OMX_STATETYPE eState; + unsigned int i = 0; + while (i++ == 0 || eState != wanted_eState) { + OMX_GetState(hComponent, &eState); + if (eState != wanted_eState) { + sys_usleep(10000); + } + } +} + + +void omx_deinit(struct omx_state* st) +{ + info("omx_deinit"); + OMX_SendCommand(st->video_render, + OMX_CommandStateSet, OMX_StateIdle, NULL); + block_until_state_changed(st->video_render, OMX_StateIdle); + OMX_SendCommand(st->video_render, + OMX_CommandStateSet, OMX_StateLoaded, NULL); + block_until_state_changed(st->video_render, OMX_StateLoaded); + OMX_FreeHandle(st->video_render); + OMX_Deinit(); +} + + +void omx_display_disable(struct omx_state* st) +{ + (void)st; + + #ifdef RASPBERRY_PI + OMX_ERRORTYPE err; + OMX_CONFIG_DISPLAYREGIONTYPE config; + memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE)); + config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE); + config.nVersion.nVersion = OMX_VERSION; + config.nPortIndex = VIDEO_RENDER_PORT; + config.fullscreen = 0; + config.set = OMX_DISPLAY_SET_FULLSCREEN; + + err = OMX_SetParameter(st->video_render, + OMX_IndexConfigDisplayRegion, &config); + + if (err != 0) { + warning("omx_display_disable command failed"); + } + + #endif +} + + +static void block_until_port_changed(OMX_HANDLETYPE hComponent, + OMX_U32 nPortIndex, OMX_BOOL bEnabled) { + + OMX_ERRORTYPE r; + OMX_PARAM_PORTDEFINITIONTYPE portdef; + OMX_U32 i = 0; + + memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE)); + portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); + portdef.nVersion.nVersion = OMX_VERSION; + portdef.nPortIndex = nPortIndex; + + while (i++ == 0 || portdef.bEnabled != bEnabled) { + r = OMX_GetParameter(hComponent, + OMX_IndexParamPortDefinition, &portdef); + if (r != OMX_ErrorNone) { + warning("block_until_port_changed: OMX_GetParameter " + " failed with Result=%d\n", r); + } + if (portdef.bEnabled != bEnabled) { + sys_usleep(10000); + } + } +} + + +int omx_display_enable(struct omx_state* st, + int width, int height, int stride) +{ + unsigned int i; + OMX_PARAM_PORTDEFINITIONTYPE portdef; +#ifdef RASPBERRY_PI + OMX_CONFIG_DISPLAYREGIONTYPE config; +#endif + OMX_ERRORTYPE err = 0; + + info("omx_update_size %d %d\n", width, height); + + #ifdef RASPBERRY_PI + memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE)); + config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE); + config.nVersion.nVersion = OMX_VERSION; + config.nPortIndex = VIDEO_RENDER_PORT; + config.fullscreen = 1; + config.set = OMX_DISPLAY_SET_FULLSCREEN; + + err |= OMX_SetParameter(st->video_render, + OMX_IndexConfigDisplayRegion, &config); + + #endif + + memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE)); + portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); + portdef.nVersion.nVersion = OMX_VERSION; + portdef.nPortIndex = VIDEO_RENDER_PORT; + + /* specify buffer requirements */ + err |= OMX_GetParameter(st->video_render, + OMX_IndexParamPortDefinition, &portdef); + if (err != 0) { + warning("omx_display_enable: couldn't retrieve port def\n"); + err = ENOMEM; + goto exit; + } + + info("omx port definition: h=%d w=%d s=%d sh=%d\n", + portdef.format.video.nFrameWidth, + portdef.format.video.nFrameHeight, + portdef.format.video.nStride, + portdef.format.video.nSliceHeight); + + portdef.format.video.nFrameWidth = width; + portdef.format.video.nFrameHeight = height; + portdef.format.video.nStride = stride; + portdef.format.video.nSliceHeight = height; + portdef.bEnabled = 1; + + err |= OMX_SetParameter(st->video_render, + OMX_IndexParamPortDefinition, &portdef); + + if (err) { + warning("omx_display_enable: could not set port definition\n"); + } + block_until_port_changed(st->video_render, VIDEO_RENDER_PORT, true); + + err |= OMX_GetParameter(st->video_render, + OMX_IndexParamPortDefinition, &portdef); + + if (err != 0 || !portdef.bEnabled) { + warning("omx_display_enable: failed to set up video port\n"); + err = ENOMEM; + goto exit; + } + + /* HACK: This state-change sometimes hangs for unknown reasons, + * so we just send the state command and wait 50 ms */ + /* block_until_state_changed(st->video_render, OMX_StateIdle); */ + + OMX_SendCommand(st->video_render, OMX_CommandStateSet, + OMX_StateIdle, NULL); + sys_usleep(50000); + + if (!st->buffers) { + st->buffers = + malloc(portdef.nBufferCountActual * sizeof(void*)); + st->num_buffers = portdef.nBufferCountActual; + st->current_buffer = 0; + + for (i = 0; i < portdef.nBufferCountActual; i++) { + err = OMX_AllocateBuffer(st->video_render, + &st->buffers[i], VIDEO_RENDER_PORT, + st, portdef.nBufferSize); + if (err) { + warning("OMX_AllocateBuffer failed: %d\n", + err); + err = ENOMEM; + goto exit; + } + } + } + + debug("omx_update_size: send to execute state"); + OMX_SendCommand(st->video_render, OMX_CommandStateSet, + OMX_StateExecuting, NULL); + block_until_state_changed(st->video_render, OMX_StateExecuting); + +exit: + return err; +} + + +int omx_display_input_buffer(struct omx_state* st, + void** pbuf, uint32_t* plen) +{ + if (!st->buffers) return EINVAL; + + *pbuf = st->buffers[0]->pBuffer; + *plen = st->buffers[0]->nAllocLen; + + st->buffers[0]->nFilledLen = *plen; + st->buffers[0]->nOffset = 0; + + return 0; +} + + +int omx_display_flush_buffer(struct omx_state* st) +{ + if (OMX_EmptyThisBuffer(st->video_render, st->buffers[0]) + != OMX_ErrorNone) { + warning("OMX_EmptyThisBuffer error"); + } + + return 0; +} diff --git a/modules/omx/omx.h b/modules/omx/omx.h new file mode 100644 index 0000000..13d8928 --- /dev/null +++ b/modules/omx/omx.h @@ -0,0 +1,46 @@ +/** + * @file omx.h Raspberry Pi VideoCoreIV OpenMAX interface + * + * Copyright (C) 2016 - 2017 Creytiv.com + * Copyright (C) 2016 - 2017 Jonathan Sieber + */ + +#ifdef RASPBERRY_PI +#include <IL/OMX_Core.h> +#include <IL/OMX_Video.h> +#include <IL/OMX_Broadcom.h> +#else +#include <OMX_Core.h> +#include <OMX_Component.h> +#include <OMX_Video.h> + +#undef OMX_VERSION +#define OMX_VERSION 0x01010101 +#define OMX_ERROR_NONE 0 +#endif + +#include <pthread.h> +#include <stdint.h> +#include <string.h> + +/* Needed for usleep to appear */ +#define _BSD_SOURCE +#include <unistd.h> + +struct omx_state { + OMX_HANDLETYPE video_render; + OMX_BUFFERHEADERTYPE** buffers; + int num_buffers; + int current_buffer; +}; + +int omx_init(struct omx_state* st); +void omx_deinit(struct omx_state* st); + +int omx_display_input_buffer(struct omx_state* st, + void** pbuf, uint32_t* plen); +int omx_display_flush_buffer(struct omx_state* st); + +int omx_display_enable(struct omx_state* st, + int width, int height, int stride); +void omx_display_disable(struct omx_state* st); 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..a95649c --- /dev/null +++ b/modules/opengl/opengl.m @@ -0,0 +1,534 @@ +/** + * @file opengl.m Video driver for OpenGL on MacOSX + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <Cocoa/Cocoa.h> +#include <OpenGL/gl.h> +#include <OpenGL/glext.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup opengl opengl + * + * Video display module for OpenGL on MacOSX + */ + + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) +#define NSTitledWindowMask NSWindowStyleMaskTitled +#define NSClosableWindowMask NSWindowStyleMaskClosable +#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable +#endif + + +struct vidisp_st { + const 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 close]; + + if (st->PHandle) { + glUseProgramObjectARB(0); + glDeleteObjectARB(st->PHandle); + } + + mem_deref(st->prog); + + [pool release]; +} + + +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]; + + 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, const 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 = 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, baresip_vidispl(), + "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..1284de8 --- /dev/null +++ b/modules/opengles/opengles.c @@ -0,0 +1,297 @@ +/** + * @file opengles.c Video driver for OpenGLES + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <OpenGLES/ES1/gl.h> +#include <OpenGLES/ES1/glext.h> +#include "opengles.h" + + +/** + * @defgroup opengles opengles + * + * Video display module for OpenGLES on Android + */ + + +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); +} + + +static int opengles_alloc(struct vidisp_st **stp, const 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 = 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) { + warning("opengles: 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, baresip_vidispl(), + "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..6c61a1e --- /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 { + const struct vidisp *vd; /* pointer to base-class (inheritance) */ + 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..2201bf2 --- /dev/null +++ b/modules/opensles/opensles.c @@ -0,0 +1,79 @@ +/** + * @file opensles.c OpenSLES audio driver + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +/** + * @defgroup opensles opensles + * + * Audio driver module for Android OpenSLES + */ + + +SLObjectItf engineObject = NULL; +SLEngineItf engineEngine; + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + SLEngineOption engineOption[] = { + { (SLuint32) SL_ENGINEOPTION_THREADSAFE, + (SLuint32) SL_BOOLEAN_TRUE }, + }; + SLresult r; + int err; + + r = slCreateEngine(&engineObject, 1, engineOption, 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, baresip_auplayl(), + "opensles", opensles_player_alloc); + err |= ausrc_register(&ausrc, baresip_ausrcl(), + "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..a3641e7 --- /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, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int opensles_recorder_alloc(struct ausrc_st **stp, const 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..59964f0 --- /dev/null +++ b/modules/opensles/player.c @@ -0,0 +1,203 @@ +/** + * @file opensles/player.c OpenSLES audio driver -- playback + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define N_PLAY_QUEUE_BUFFERS 2 +#define PTIME 10 + + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + auplay_write_h *wh; + void *arg; + int16_t *sampv[N_PLAY_QUEUE_BUFFERS]; + size_t sampc; + uint8_t bufferId; + + 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); + + st->bufferId = 0; + for (int i=0; i<N_PLAY_QUEUE_BUFFERS; i++) { + mem_deref(st->sampv[i]); + } +} + + +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct auplay_st *st = context; + + st->wh(st->sampv[st->bufferId], st->sampc, st->arg); + + (*st->BufferQueue)->Enqueue(bq /*st->BufferQueue*/, + st->sampv[st->bufferId], st->sampc * 2); + + st->bufferId = ( st->bufferId + 1 ) % N_PLAY_QUEUE_BUFFERS; +} + + +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 + }; + uint32_t ch_mask = prm->ch == 2 + ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT + : SL_SPEAKER_FRONT_CENTER; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + ch_mask, + 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) { + warning("opensles: 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, const 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; + + if (prm->fmt != AUFMT_S16LE) { + warning("opensles: player: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + debug("opensles: opening player %uHz, %uchannels\n", + prm->srate, prm->ch); + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = ap; + st->wh = wh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * PTIME / 1000; + + st->bufferId = 0; + for (int i=0; i<N_PLAY_QUEUE_BUFFERS; i++) { + st->sampv[i] = mem_zalloc(2 * st->sampc, NULL); + if (!st->sampv[i]) { + err = ENOMEM; + goto out; + } + } + + 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..b26190b --- /dev/null +++ b/modules/opensles/recorder.c @@ -0,0 +1,214 @@ +/** + * @file opensles/recorder.c OpenSLES audio driver -- recording + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <string.h> +#include <SLES/OpenSLES.h> +#include "SLES/OpenSLES_Android.h" +#include "opensles.h" + + +#define N_REC_QUEUE_BUFFERS 2 +#define PTIME 10 + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + + int16_t *sampv[N_REC_QUEUE_BUFFERS]; + size_t sampc; + uint8_t bufferId; + 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->recObject != NULL) { + SLuint32 state; + + if (SL_OBJECT_STATE_UNREALIZED != + (*st->recObject)->GetState(st->recObject,&state)) { + (*st->recObject)->Destroy(st->recObject); + } + } + + st->bufferId = 0; + for (int i=0; i<N_REC_QUEUE_BUFFERS; i++) { + mem_deref(st->sampv[i]); + } +} + + +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct ausrc_st *st = context; + + (void)bq; + + st->rh(st->sampv[st->bufferId], st->sampc, st->arg); + + st->bufferId = ( st->bufferId + 1 ) % N_REC_QUEUE_BUFFERS; + + memset(st->sampv[st->bufferId], 0, st->sampc * 2); + + (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->sampv[st->bufferId], + st->sampc * 2); +} + + +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 + }; + int speakers = prm->ch > 1 + ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT + : SL_SPEAKER_FRONT_CENTER; + SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch, + prm->srate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + speakers, + 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) { + warning("opensles: CreateAudioRecorder failed: r = %d\n", r); + return ENODEV; + } + + r = (*st->recObject)->Realize(st->recObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != 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); + + st->bufferId = 0; + r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue, + st->sampv[st->bufferId], + st->sampc * 2); + if (SL_RESULT_SUCCESS != r) + return ENODEV; + + 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, const 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; + + if (prm->fmt != AUFMT_S16LE) { + warning("opensles: record: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + debug("opensles: opening recorder %uHz, %uchannels\n", + prm->srate, prm->ch); + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * PTIME / 1000; + st->bufferId = 0; + for (int i=0; i<N_REC_QUEUE_BUFFERS; i++) { + st->sampv[i] = mem_zalloc(2 * st->sampc, NULL); + if (!st->sampv[i]) { + err = ENOMEM; + goto out; + } + } + + err = createAudioRecorder(st, prm); + if (err) + goto out; + + err = startRecording(st); + if (err) { + warning("opensles: failed to start recorder\n"); + 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..7ee1ca2 --- /dev/null +++ b/modules/opus/encode.c @@ -0,0 +1,187 @@ +/** + * @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, conf_prm; + opus_int32 fch, vbr; + const struct aucodec *auc = ac; + + (void)param; + + if (!aesp || !ac || !ac->ch) + return EINVAL; + + debug("opus: encoder fmtp (%s)\n", fmtp); + + /* Save the incoming OPUS parameters from SDP offer */ + if (str_isset(fmtp)) { + opus_mirror_params(fmtp); + } + + 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); + + conf_prm.bitrate = OPUS_AUTO; + opus_decode_fmtp(&conf_prm, auc->fmtp); + + if ((prm.bitrate == OPUS_AUTO) || + ((conf_prm.bitrate != OPUS_AUTO) && + (conf_prm.bitrate < prm.bitrate))) + prm.bitrate = conf_prm.bitrate; + + 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..7acccec --- /dev/null +++ b/modules/opus/opus.c @@ -0,0 +1,173 @@ +/** + * @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 + * + * Supported version: libopus 1.0.0 or later + * + * Configuration options: + * + \verbatim + opus_bitrate 128000 # Average bitrate in [bps] + opus_cbr {yes,no} # Constant Bitrate (inverse of VBR) + opus_inbandfec {yes,no} # Enable inband Forward Error Correction (FEC) + opus_dtx {yes,no} # Enable Discontinuous Transmission (DTX) + \endverbatim + * + * References: + * + * RFC 6716 Definition of the Opus Audio Codec + * RFC 7587 RTP Payload Format for the Opus Speech and Audio Codec + * + * http://opus-codec.org/downloads/ + */ + + +static bool opus_mirror; +static char fmtp[256] = ""; +static char fmtp_mirror[256]; + + +static int opus_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + bool mirror; + + (void)arg; + (void)offer; + + if (!mb || !fmt) + return 0; + + mirror = !offer && str_isset(fmtp_mirror); + + return mbuf_printf(mb, "a=fmtp:%s %s\r\n", + fmt->id, mirror ? fmtp_mirror : fmtp); +} + + +static struct aucodec opus = { + .name = "opus", + .srate = 48000, + .crate = 48000, + .ch = 2, + .fmtp = fmtp, + .encupdh = opus_encode_update, + .ench = opus_encode_frm, + .decupdh = opus_decode_update, + .dech = opus_decode_frm, + .plch = opus_decode_pkloss, +}; + + +void opus_mirror_params(const char *x) +{ + if (!opus_mirror) + return; + + info("opus: mirror parameters: \"%s\"\n", x); + + str_ncpy(fmtp_mirror, x, sizeof(fmtp_mirror)); +} + + +static int module_init(void) +{ + struct conf *conf = conf_cur(); + uint32_t value; + char *p = fmtp + str_len(fmtp); + bool b, stereo = true, sprop_stereo = true; + int n = 0; + + conf_get_bool(conf, "opus_stereo", &stereo); + conf_get_bool(conf, "opus_sprop_stereo", &sprop_stereo); + + /* always set stereo parameter first */ + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + "stereo=%d;sprop-stereo=%d", stereo, sprop_stereo); + if (n <= 0) + return ENOMEM; + + p += n; + + if (0 == conf_get_u32(conf, "opus_bitrate", &value)) { + + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + ";maxaveragebitrate=%d", value); + if (n <= 0) + return ENOMEM; + + p += n; + } + + if (0 == conf_get_bool(conf, "opus_cbr", &b)) { + + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + ";cbr=%d", b); + if (n <= 0) + return ENOMEM; + + p += n; + } + + if (0 == conf_get_bool(conf, "opus_inbandfec", &b)) { + + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + ";useinbandfec=%d", b); + if (n <= 0) + return ENOMEM; + + p += n; + } + + if (0 == conf_get_bool(conf, "opus_dtx", &b)) { + + n = re_snprintf(p, sizeof(fmtp) - str_len(p), + ";usedtx=%d", b); + if (n <= 0) + return ENOMEM; + + p += n; + } + + (void)conf_get_bool(conf, "opus_mirror", &opus_mirror); + + if (opus_mirror) { + opus.fmtp = NULL; + opus.fmtp_ench = opus_fmtp_enc; + } + + debug("opus: fmtp=\"%s\"\n", fmtp); + + aucodec_register(baresip_aucodecl(), &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..d521652 --- /dev/null +++ b/modules/opus/opus.h @@ -0,0 +1,37 @@ +/** + * @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); + + +void opus_mirror_params(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..0d47dd1 --- /dev/null +++ b/modules/oss/oss.c @@ -0,0 +1,364 @@ +/** + * @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 +#ifdef SOLARIS +#include <sys/filio.h> +#endif + + +/** + * @defgroup oss oss + * + * Open Sound System (OSS) audio driver module + * + * + * References: + * + * http://www.4front-tech.com/linux.html + */ + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + pthread_t thread; + bool run; + int fd; + int16_t *sampv; + size_t sampc; + ausrc_read_h *rh; + ausrc_error_h *errh; + void *arg; +}; + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + pthread_t thread; + bool run; + int fd; + int16_t *sampv; + size_t sampc; + 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_NE; /* native endian */ + 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: %d Hz %d ch, blocksize=%d\n", + 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) { + (void)close(st->fd); + } + + mem_deref(st->sampv); +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + pthread_join(st->thread, NULL); + } + + if (-1 != st->fd) { + (void)close(st->fd); + } + + mem_deref(st->sampv); +} + + +static void *record_thread(void *arg) +{ + struct ausrc_st *st = arg; + int n; + + while (st->run) { + + n = read(st->fd, st->sampv, st->sampc*2); + if (n <= 0) + continue; + + st->rh(st->sampv, n/2, st->arg); + } + + return NULL; +} + + +static void *play_thread(void *arg) +{ + struct auplay_st *st = arg; + int n; + + while (st->run) { + + st->wh(st->sampv, st->sampc, st->arg); + + n = write(st->fd, st->sampv, st->sampc*2); + if (n < 0) { + warning("oss: write: %m\n", errno); + break; + } + } + + return NULL; +} + + +static int src_alloc(struct ausrc_st **stp, const 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)errh; + + if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE || !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; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sampv = mem_alloc(2 * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + st->fd = open(device, O_RDONLY); + if (st->fd < 0) { + err = errno; + goto out; + } + + err = oss_reset(st->fd, prm->srate, prm->ch, st->sampc, 0); + if (err) + goto out; + + st->as = as; + + 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; +} + + +static int play_alloc(struct auplay_st **stp, const 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 || prm->fmt != AUFMT_S16LE || !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; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sampv = mem_alloc(st->sampc * 2, NULL); + if (!st->sampv) { + 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, st->sampc, 0); + if (err) + goto out; + + st->ap = 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, baresip_ausrcl(), "oss", src_alloc); + err |= auplay_register(&auplay, baresip_auplayl(), "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/pcp/listener.c b/modules/pcp/listener.c new file mode 100644 index 0000000..7747672 --- /dev/null +++ b/modules/pcp/listener.c @@ -0,0 +1,124 @@ +/** + * @file listener.c Port Control Protocol module -- multicast listener + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <re.h> +#include <rew.h> +#include <baresip.h> +#include "pcp.h" + + +/* + * Listen for incoming notifications on unicast/multicast port 5350 + */ + + +struct pcp_listener { + struct udp_sock *us; + struct sa srv; + struct sa group; + pcp_msg_h *msgh; + void *arg; +}; + + +static void destructor(void *arg) +{ + struct pcp_listener *pl = arg; + + if (sa_isset(&pl->group, SA_ADDR)) + (void)udp_multicast_leave(pl->us, &pl->group); + + mem_deref(pl->us); +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct pcp_listener *pl = arg; + struct pcp_msg *msg; + int err; + +#if 0 + if (!sa_cmp(src, &pl->srv, SA_ADDR)) { + debug("pcp: listener: ignore %zu bytes from non-server %J\n", + mb->end, src); + return; + } +#endif + + err = pcp_msg_decode(&msg, mb); + if (err) + return; + + /* Validate PCP request */ + if (!msg->hdr.resp) { + info("pcp: listener: ignore request from %J\n", src); + goto out; + } + + if (pl->msgh) + pl->msgh(msg, pl->arg); + + out: + mem_deref(msg); +} + + +int pcp_listen(struct pcp_listener **plp, const struct sa *srv, + pcp_msg_h *msgh, void *arg) +{ + struct pcp_listener *pl; + struct sa laddr; + int err; + + if (!plp || !srv || !msgh) + return EINVAL; + + pl = mem_zalloc(sizeof(*pl), destructor); + if (!pl) + return ENOMEM; + + pl->srv = *srv; + pl->msgh = msgh; + pl->arg = arg; + + /* note: must listen on ANY to get multicast working */ + sa_init(&laddr, sa_af(srv)); + sa_set_port(&laddr, PCP_PORT_CLI); + + err = udp_listen(&pl->us, &laddr, udp_recv, pl); + if (err) + goto out; + + switch (sa_af(&laddr)) { + + case AF_INET: + err = sa_set_str(&pl->group, "224.0.0.1", 0); + break; + + case AF_INET6: + err = sa_set_str(&pl->group, "ff02::1", 0); + break; + + default: + err = EAFNOSUPPORT; + break; + } + if (err) + goto out; + + err = udp_multicast_join(pl->us, &pl->group); + if (err) + goto out; + + out: + if (err) + mem_deref(pl); + else + *plp = pl; + + return err; +} diff --git a/modules/pcp/module.mk b/modules/pcp/module.mk new file mode 100644 index 0000000..99a7c34 --- /dev/null +++ b/modules/pcp/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := pcp +$(MOD)_SRCS += pcp.c listener.c +$(MOD)_CFLAGS += -I$(SYSROOT)/local/include/rew +$(MOD)_LFLAGS += -lrew + +include mk/mod.mk diff --git a/modules/pcp/pcp.c b/modules/pcp/pcp.c new file mode 100644 index 0000000..4fbc41c --- /dev/null +++ b/modules/pcp/pcp.c @@ -0,0 +1,365 @@ +/** + * @file pcp.c Port Control Protocol for Media NAT-traversal + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rew.h> +#include <baresip.h> +#include "pcp.h" + + +/** + * @defgroup pcp pcp + * + * Port Control Protocol (PCP) + * + * This module implements the medianat interface with PCP, which is + * the successor of the NAT-PMP protocol. + */ + + +enum { + LIFETIME = 120 /* seconds */ +}; + +struct mnat_sess { + struct le le; + struct list medial; + mnat_estab_h *estabh; + void *arg; +}; + +struct mnat_media { + + struct comp { + struct pcp_request *pcp; + struct mnat_media *media; /* pointer to parent */ + unsigned id; + bool granted; + } compv[2]; + unsigned compc; + + struct le le; + struct mnat_sess *sess; + struct sdp_media *sdpm; + uint32_t srv_epoch; +}; + + +static struct mnat *mnat; +static struct sa pcp_srv; +static struct list sessl; +static struct pcp_listener *lsnr; + + +static void session_destructor(void *arg) +{ + struct mnat_sess *sess = arg; + + list_unlink(&sess->le); + list_flush(&sess->medial); +} + + +static void media_destructor(void *arg) +{ + struct mnat_media *m = arg; + unsigned i; + + list_unlink(&m->le); + + for (i=0; i<m->compc; i++) { + struct comp *comp = &m->compv[i]; + + mem_deref(comp->pcp); + } + + mem_deref(m->sdpm); +} + + +static void complete(struct mnat_sess *sess, int err, const char *reason) +{ + mnat_estab_h *estabh = sess->estabh; + void *arg = sess->arg; + + sess->estabh = NULL; + + if (estabh) { + estabh(err, 0, reason, arg); + } +} + + +static bool all_components_granted(const struct mnat_media *m) +{ + unsigned i; + + if (!m || !m->compc) + return false; + + for (i=0; i<m->compc; i++) { + const struct comp *comp = &m->compv[i]; + if (!comp->granted) + return false; + } + + return true; +} + + +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 (!all_components_granted(m)) + return; + } + + complete(sess, 0, "done"); +} + + +/* todo: detect multiple responses */ +static void pcp_resp_handler(int err, struct pcp_msg *msg, void *arg) +{ + struct comp *comp = arg; + struct mnat_media *m = comp->media; + const struct pcp_map *map; + + if (err) { + warning("pcp: mapping error: %m\n", err); + + complete(m->sess, err, NULL); + return; + } + else if (msg->hdr.result != PCP_SUCCESS) { + warning("pcp: mapping error: %s\n", + pcp_result_name(msg->hdr.result)); + + re_printf("%H\n", pcp_msg_print, msg); + + complete(m->sess, EPROTO, "pcp error"); + return; + } + + map = pcp_msg_payload(msg); + + info("pcp: %s: mapping for %s:" + " internal_port=%u, external_addr=%J\n", + sdp_media_name(m->sdpm), + comp->id==1 ? "RTP" : "RTCP", + map->int_port, &map->ext_addr); + + /* Update SDP media with external IP-address mapping */ + if (comp->id == 1) + sdp_media_set_laddr(m->sdpm, &map->ext_addr); + else + sdp_media_set_laddr_rtcp(m->sdpm, &map->ext_addr); + + comp->granted = true; + m->srv_epoch = msg->hdr.epoch; + + 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; + + list_append(&sessl, &sess->le, 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; + struct sa laddr; + struct pcp_map map; + unsigned i; + int err = 0; + + if (!mp || !sess || !sdpm || proto != IPPROTO_UDP) + return EINVAL; + + m = mem_zalloc(sizeof(*m), media_destructor); + if (!m) + return ENOMEM; + + m->compc = sock2 ? 2 : 1; + + list_append(&sess->medial, &m->le, m); + m->sess = sess; + m->sdpm = mem_ref(sdpm); + + for (i=0; i<m->compc; i++) { + struct comp *comp = &m->compv[i]; + + comp->id = i+1; + comp->media = m; + + err = udp_local_get(i==0 ? sock1 : sock2, &laddr); + if (err) + goto out; + + rand_bytes(map.nonce, sizeof(map.nonce)); + map.proto = proto; + map.int_port = sa_port(&laddr); + /* note: using same address-family as the PCP server */ + sa_init(&map.ext_addr, sa_af(&pcp_srv)); + + info("pcp: %s: internal port for %s is %u\n", + sdp_media_name(sdpm), + i==0 ? "RTP" : "RTCP", + map.int_port); + + err = pcp_request(&comp->pcp, NULL, &pcp_srv, PCP_MAP, + LIFETIME, &map, pcp_resp_handler, comp, 0); + if (err) + goto out; + } + + out: + if (err) + mem_deref(m); + else if (mp) { + *mp = m; + } + + return err; +} + + +static void media_refresh(struct mnat_media *media) +{ + unsigned i; + + for (i=0; i<media->compc; i++) { + struct comp *comp = &media->compv[i]; + + pcp_force_refresh(comp->pcp); + } +} + + +static void refresh_session(struct mnat_sess *sess, uint32_t epoch_time) +{ + struct le *le; + + for (le = sess->medial.head; le; le = le->next) { + + struct mnat_media *m = le->data; + + if (epoch_time < m->srv_epoch) { + info("pcp: detected PCP Server reboot!\n"); + media_refresh(m); + } + + m->srv_epoch = epoch_time; + } +} + + +static void pcp_msg_handler(const struct pcp_msg *msg, void *arg) +{ + struct le *le; + + (void)arg; + + info("pcp: received notification: %H\n", pcp_msg_print, msg); + + if (msg->hdr.opcode == PCP_ANNOUNCE) { + + for (le = sessl.head; le; le = le->next) { + + struct mnat_sess *sess = le->data; + + refresh_session(sess, msg->hdr.epoch); + } + } +} + + +static int module_init(void) +{ + struct pl pl; + int err; + + if (0 == conf_get(conf_cur(), "pcp_server", &pl)) { + err = sa_decode(&pcp_srv, pl.p, pl.l); + if (err) + return err; + } + else { + err = net_default_gateway_get(net_af(baresip_network()), + &pcp_srv); + if (err) + return err; + sa_set_port(&pcp_srv, PCP_PORT_SRV); + } + + info("pcp: using PCP server at %J\n", &pcp_srv); + + /* NOTE: if multiple applications are listening on port 5350 + then this will not work */ + err = pcp_listen(&lsnr, &pcp_srv, pcp_msg_handler, 0); + if (err) { + info("pcp: could not enable listener: %m\n", err); + err = 0; + } + + return mnat_register(&mnat, baresip_mnatl(), "pcp", NULL, + session_alloc, media_alloc, NULL); +} + + +static int module_close(void) +{ + lsnr = mem_deref(lsnr); + mnat = mem_deref(mnat); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(pcp) = { + "pcp", + "mnat", + module_init, + module_close, +}; diff --git a/modules/pcp/pcp.h b/modules/pcp/pcp.h new file mode 100644 index 0000000..6f9e8a9 --- /dev/null +++ b/modules/pcp/pcp.h @@ -0,0 +1,15 @@ +/** + * @file pcp.h Port Control Protocol module -- internal interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + + +/* listener */ + +struct pcp_listener; + +typedef void (pcp_msg_h)(const struct pcp_msg *msg, void *arg); + +int pcp_listen(struct pcp_listener **plp, const struct sa *srv, + pcp_msg_h *msgh, void *arg); 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..8b220ed --- /dev/null +++ b/modules/plc/plc.c @@ -0,0 +1,120 @@ +/** + * @file plc.c PLC -- Packet Loss Concealment + * + * Copyright (C) 2010 Creytiv.com + */ + +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES + +#include <spandsp.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup plc plc + * + * Packet Loss Concealment (PLC) audio-filter using spandsp + * + */ + + +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(baresip_aufiltl(), &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..b856502 --- /dev/null +++ b/modules/portaudio/portaudio.c @@ -0,0 +1,346 @@ +/** + * @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> + + +/** + * @defgroup portaudio portaudio + * + * Portaudio audio driver + * + * (portaudio v19 is required) + * + * + * References: + * + * http://www.portaudio.com/ + */ + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + PaStream *stream_rd; + ausrc_read_h *rh; + void *arg; + volatile bool ready; + unsigned ch; +}; + +struct auplay_st { + const 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; + size_t sampc; + + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->rh(inputBuffer, 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; + size_t sampc; + + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (!st->ready) + return paAbort; + + sampc = frameCount * st->ch; + + st->wh(outputBuffer, sampc, st->arg); + + return paContinue; +} + + +static PaSampleFormat aufmt_to_pasampleformat(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return paInt16; + case AUFMT_FLOAT: return paFloat32; + default: return 0; + } +} + + +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 = aufmt_to_pasampleformat(prm->fmt); + 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 = aufmt_to_pasampleformat(prm->fmt); + 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); + } +} + + +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); + } +} + + +static int src_alloc(struct ausrc_st **stp, const 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(); + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = 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, const 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(); + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = 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, baresip_ausrcl(), + "portaudio", src_alloc); + + if (paNoDevice != Pa_GetDefaultOutputDevice()) + err |= auplay_register(&auplay, baresip_auplayl(), + "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..b9b6a22 --- /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 publisher.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..7fc0a67 --- /dev/null +++ b/modules/presence/notifier.c @@ -0,0 +1,219 @@ +/** + * @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 sipnot *not; + struct ua *ua; +}; + +static struct list notifierl; + + +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); + } + + mem_deref(not); +} + + +static void destructor(void *arg) +{ + struct notifier *not = arg; + + list_unlink(¬->le); + mem_deref(not->not); + 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, + const struct sip_msg *msg, + const struct sipevent_event *se, struct ua *ua) +{ + struct notifier *not; + int err; + + if (!msg || !se) + return EINVAL; + + not = mem_zalloc(sizeof(*not), destructor); + if (!not) + return ENOMEM; + + not->ua = mem_ref(ua); + + err = sipevent_accept(¬->not, uag_sipevent_sock(), + msg, NULL, se, 200, "OK", + 600, 600, 600, + ua_cuser(not->ua), "application/pidf+xml", + auth_handler, ua_account(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(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(¬, msg, &se, ua); + if (err) + return err; + + (void)notify(not, ua_presence_status(ua)); + + return 0; +} + + +void notifier_update_status(struct ua *ua) +{ + struct le *le; + + for (le = notifierl.head; le; le = le->next) { + + struct notifier *not = le->data; + + if (not->ua == ua) + (void)notify(not, ua_presence_status(not->ua)); + } +} + + +static bool sub_handler(const struct sip_msg *msg, void *arg) +{ + struct ua *ua = arg; + + if (notifier_add(msg, ua)) + (void)sip_treply(NULL, uag_sip(), msg, 400, "Bad Presence"); + + return true; +} + + +int notifier_init(void) +{ + uag_set_sub_handler(sub_handler); + + return 0; +} + + +void notifier_close(void) +{ + list_flush(¬ifierl); + uag_set_sub_handler(NULL); +} diff --git a/modules/presence/presence.c b/modules/presence/presence.c new file mode 100644 index 0000000..9fcdf83 --- /dev/null +++ b/modules/presence/presence.c @@ -0,0 +1,123 @@ +/** + * @file presence.c Presence module + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +static int status_update(struct ua *current_ua, + const enum presence_status new_status) +{ + if (ua_presence_status(current_ua) == new_status) + return 0; + + info("presence: update status of '%s' from '%s' to '%s'\n", + ua_aor(current_ua), + contact_presence_str(ua_presence_status(current_ua)), + contact_presence_str(new_status)); + + ua_presence_status_set(current_ua, new_status); + + publisher_update_status(current_ua); + notifier_update_status(current_ua); + + return 0; +} + + +static int cmd_online(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + return status_update(uag_current(), PRESENCE_OPEN); +} + + +static int cmd_offline(struct re_printf *pf, void *arg) +{ + (void)pf; + (void)arg; + + return status_update(uag_current(), PRESENCE_CLOSED); +} + + +static const struct cmd cmdv[] = { + {"presence_online", '[', 0, "Set presence online", cmd_online }, + {"presence_offline", ']', 0, "Set presence offline", cmd_offline }, +}; + + +static void event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + (void)call; + (void)prm; + (void)arg; + + debug("presence: ua=%p got event %d (%s)\n", ua, ev, + uag_event_str(ev)); + + if (ev == UA_EVENT_SHUTDOWN) { + + publisher_close(); + notifier_close(); + subscriber_close_all(); + } +} + + +static int module_init(void) +{ + int err; + + err = subscriber_init(); + if (err) + return err; + + err = publisher_init(); + if (err) + return err; + + err = notifier_init(); + if (err) + return err; + + err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); + if (err) + return err; + + err = uag_event_register(event_handler, NULL); + if (err) + return err; + + return err; +} + + +static int module_close(void) +{ + uag_event_unregister(event_handler); + + cmd_unregister(baresip_commands(), cmdv); + + publisher_close(); + + 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..75d8e48 --- /dev/null +++ b/modules/presence/presence.h @@ -0,0 +1,19 @@ +/** + * @file presence.h Presence module interface + * + * Copyright (C) 2010 Creytiv.com + */ + +int subscriber_init(void); +void subscriber_close(void); +void subscriber_close_all(void); + + +int notifier_init(void); +void notifier_close(void); +void notifier_update_status(struct ua *ua); + + +int publisher_init(void); +void publisher_close(void); +void publisher_update_status(struct ua *ua); diff --git a/modules/presence/publisher.c b/modules/presence/publisher.c new file mode 100644 index 0000000..f443852 --- /dev/null +++ b/modules/presence/publisher.c @@ -0,0 +1,309 @@ +/** + * @file publisher.c Presence Publisher (RFC 3903) + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2014 Juha Heinanen + */ + +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "presence.h" + + +struct publisher { + struct le le; + struct tmr tmr; + unsigned failc; + char *etag; + unsigned int expires; + unsigned int refresh; + struct ua *ua; +}; + +static struct list publ = LIST_INIT; + +static void tmr_handler(void *arg); +static int publish(struct publisher *pub); + + +static void response_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct publisher *pub = arg; + const struct sip_hdr *etag_hdr; + + if (err) + return; + + if (msg->scode < 200) { + return; + } + + if (msg->scode < 300) { + + if (pub->expires == 0) + return; + + etag_hdr = sip_msg_xhdr(msg, "SIP-ETag"); + if (etag_hdr) { + mem_deref(pub->etag); + pl_strdup(&(pub->etag), &(etag_hdr->val)); + pub->refresh = 1; + tmr_start(&pub->tmr, pub->expires * 900, + tmr_handler, pub); + } + else { + warning("%s: publisher got 200 OK without etag\n", + ua_aor(pub->ua)); + } + } + else if (msg->scode == 412) { + + mem_deref(pub->etag); + pub->etag = NULL; + pub->refresh = 0; + publish(pub); + + } + else { + warning("%s: publisher got error response %u %r\n", + ua_aor(pub->ua), msg->scode, &msg->reason); + } + + return; +} + + +/* move this to presence.c */ +static const char *presence_status_str(enum presence_status st) +{ + switch (st) { + + case PRESENCE_OPEN: return "open"; + case PRESENCE_CLOSED: return "closed"; + case PRESENCE_UNKNOWN: return "unknown"; + default: return "?"; + } +} + + +static int publish(struct publisher *pub) +{ + int err; + const char *aor = ua_aor(pub->ua); + struct mbuf *mb; + + mb = mbuf_alloc(1024); + if (!mb) + return ENOMEM; + + if (pub->expires && !pub->refresh) + 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(ua_presence_status(pub->ua)), aor); + else + err = mbuf_printf(mb, ""); + if (err) + goto out; + + mb->pos = 0; + + /* XXX: can be simplified with 1 function call, by adding a + print-handler that prints "SIP-If-Match: ETAG" */ + if (pub->etag) + err = sip_req_send(pub->ua, "PUBLISH", aor, + pub->expires ? response_handler : NULL, + pub, + "%s" + "Event: presence\r\n" + "Expires: %u\r\n" + "SIP-If-Match: %s\r\n" + "Content-Length: %zu\r\n" + "\r\n" + "%b", + pub->expires + ? "Content-Type: application/pidf+xml\r\n" + : "", + + pub->expires, + pub->etag, + mbuf_get_left(mb), + mbuf_buf(mb), + mbuf_get_left(mb)); + else + err = sip_req_send(pub->ua, "PUBLISH", aor, + pub->expires ? response_handler : NULL, + pub, + "%s" + "Event: presence\r\n" + "Expires: %u\r\n" + "Content-Length: %zu\r\n" + "\r\n" + "%b", + pub->expires + ? "Content-Type: application/pidf+xml\r\n" + : "", + pub->expires, + mbuf_get_left(mb), + mbuf_buf(mb), + mbuf_get_left(mb)); + if (err) { + warning("publisher: send PUBLISH: (%m)\n", err); + } + +out: + mem_deref(mb); + + return err; +} + + +/* move to presence.c */ +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 tmr_handler(void *arg) +{ + struct publisher *pub = arg; + + if (publish(pub)) + tmr_start(&pub->tmr, wait_fail(++pub->failc) * 1000, + tmr_handler, pub); + else + pub->failc = 0; +} + + +static void destructor(void *arg) +{ + struct publisher *pub = arg; + + list_unlink(&pub->le); + tmr_cancel(&pub->tmr); + mem_deref(pub->ua); + mem_deref(pub->etag); +} + + +void publisher_update_status(struct ua *ua) +{ + struct le *le; + + for (le = publ.head; le; le = le->next) { + + struct publisher *pub = le->data; + + if (pub->ua == ua) { + pub->refresh = 0; + publish(pub); + } + } +} + + +static int publisher_alloc(struct ua *ua) +{ + struct publisher *pub; + + pub = mem_zalloc(sizeof(*pub), destructor); + if (!pub) + return ENOMEM; + + pub->ua = mem_ref(ua); + pub->expires = account_pubint(ua_account(ua)); + + tmr_init(&pub->tmr); + tmr_start(&pub->tmr, 10, tmr_handler, pub); + + list_append(&publ, &pub->le, pub); + + return 0; +} + + +static void pub_ua_event_handler(struct ua *ua, + enum ua_event ev, + struct call *call, + const char *prm, + void *arg ) +{ + (void)call; + (void)prm; + (void)arg; + + if (account_pubint(ua_account(ua)) == 0) + return; + + if (ev == UA_EVENT_REGISTER_OK) { + if (ua_presence_status(ua) == PRESENCE_UNKNOWN) { + ua_presence_status_set(ua, PRESENCE_OPEN); + publisher_update_status(ua); + } + } +} + + +int publisher_init(void) +{ + struct le *le; + int err = 0; + + uag_event_register(pub_ua_event_handler, NULL); + + for (le = list_head(uag_list()); le; le = le->next) { + + struct ua *ua = le->data; + struct account *acc = ua_account(ua); + + if (account_pubint(acc) == 0) + continue; + + err |= publisher_alloc(ua); + } + + if (err) + return err; + + return 0; +} + + +void publisher_close(void) +{ + struct le *le; + + uag_event_unregister(pub_ua_event_handler); + + for (le = list_head(&publ); le; le = le->next) { + + struct publisher *pub = le->data; + + ua_presence_status_set(pub->ua, PRESENCE_CLOSED); + pub->expires = 0; + publish(pub); + } + + list_flush(&publ); +} diff --git a/modules/presence/subscriber.c b/modules/presence/subscriber.c new file mode 100644 index 0000000..11da6d4 --- /dev/null +++ b/modules/presence/subscriber.c @@ -0,0 +1,382 @@ +/** + * @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. + */ + + +/** Constants */ +enum { + SHUTDOWN_DELAY = 500 /**< Delay before un-registering [ms] */ +}; + + +struct presence { + struct le le; + struct sipsub *sub; + struct tmr tmr; + enum presence_status status; + unsigned failc; + struct contact *contact; + struct ua *ua; + bool shutdown; +}; + +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 *type_hdr, *length_hdr; + struct pl pl; + + if (pres->shutdown) + goto done; + + pres->failc = 0; + + type_hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_TYPE); + + if (!type_hdr) { + + length_hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_LENGTH); + if (0 == pl_strcmp(&length_hdr->val, "0")) { + + status = PRESENCE_UNKNOWN; + goto done; + } + } + + if (!type_hdr || + 0 != pl_strcasecmp(&type_hdr->val, "application/pidf+xml")) { + + if (type_hdr) + warning("presence: unsupported content-type: '%r'\n", + &type_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), + "<basic[ \t]*>[^<]+</basic[ \t]*>", 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[ \t]*/>", NULL)) { + + status = PRESENCE_CLOSED; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:busy[ \t]*/>", NULL)) { + + status = PRESENCE_BUSY; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "<rpid:on-the-phone[ \t]*/>", NULL)) { + + status = PRESENCE_BUSY; + } + +done: + (void)sip_treply(NULL, sip, msg, 200, "OK"); + + contact_set_presence(pres->contact, status); + + if (pres->shutdown) + mem_deref(pres); +} + + +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; + + debug("presence: subscriber destroyed\n"); + + list_unlink(&pres->le); + tmr_cancel(&pres->tmr); + mem_deref(pres->contact); + mem_deref(pres->sub); + mem_deref(pres->ua); +} + + +static void deref_handler(void *arg) +{ + struct presence *pres = arg; + mem_deref(pres); +} + + +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; + } + + mem_deref(pres->ua); + pres->ua = mem_ref(ua); + + 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_account(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; +} + + +static void contact_handler(struct contact *contact, + bool removed, void *arg) +{ + struct le *le; + struct pl val; + struct presence *pres = NULL; + struct sip_addr *addr = contact_addr(contact); + (void)arg; + + if (0 == msg_param_decode(&addr->params, "presence", &val) && + 0 == pl_strcasecmp(&val, "p2p")) { + if (!removed) { + if (presence_alloc(contact) != 0) { + warning("presence: presence_alloc failed\n"); + return; + } + } + else { + /* Find matching presence element for contact */ + for (le = list_head(&presencel); le; le = le->next) { + pres = (struct presence*)le->data; + if (pres->contact == contact) { + break; + } + pres = NULL; + } + + if (pres) { + mem_deref(pres); + } + else { + warning("presence: No contact to remove\n"); + } + } + } +} + + +int subscriber_init(void) +{ + struct contacts *contacts = baresip_contacts(); + struct le *le; + int err = 0; + + for (le = list_head(contact_list(contacts)); le; le = le->next) { + + struct contact *c = le->data; + struct sip_addr *addr = contact_addr(c); + struct pl val; + + if (0 == msg_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)); + + contact_set_update_handler(contacts, contact_handler, NULL); + + return err; +} + + +void subscriber_close(void) +{ + contact_set_update_handler(baresip_contacts(), NULL, NULL); + list_flush(&presencel); +} + + +void subscriber_close_all(void) +{ + struct le *le; + + info("presence: subscriber: closing %u subs\n", + list_count(&presencel)); + + contact_set_update_handler(baresip_contacts(), NULL, NULL); + + le = presencel.head; + while (le) { + + struct presence *pres = le->data; + le = le->next; + + debug("presence: shutdown: sub=%p\n", pres->sub); + + pres->shutdown = true; + if (pres->sub) { + pres->sub = mem_deref(pres->sub); + tmr_start(&pres->tmr, SHUTDOWN_DELAY, + deref_handler, pres); + } + else + mem_deref(pres); + } +} diff --git a/modules/pulse/module.mk b/modules/pulse/module.mk new file mode 100644 index 0000000..d5f7698 --- /dev/null +++ b/modules/pulse/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2016 Creytiv.com +# + +MOD := pulse +$(MOD)_SRCS += pulse.c +$(MOD)_SRCS += player.c +$(MOD)_SRCS += recorder.c +$(MOD)_LFLAGS += $(shell pkg-config --libs libpulse-simple) +$(MOD)_CFLAGS += $(shell pkg-config --cflags libpulse-simple) + +include mk/mod.mk diff --git a/modules/pulse/player.c b/modules/pulse/player.c new file mode 100644 index 0000000..d65e7a8 --- /dev/null +++ b/modules/pulse/player.c @@ -0,0 +1,153 @@ +/** + * @file pulse/player.c Pulseaudio sound driver - player + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <pulse/pulseaudio.h> +#include <pulse/simple.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "pulse.h" + + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + + pa_simple *s; + pthread_t thread; + bool run; + void *sampv; + size_t sampc; + size_t sampsz; + auplay_write_h *wh; + void *arg; +}; + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + /* Wait for termination of other thread */ + if (st->run) { + debug("pulse: stopping playback thread\n"); + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->s) + pa_simple_free(st->s); + + mem_deref(st->sampv); +} + + +static void *write_thread(void *arg) +{ + struct auplay_st *st = arg; + const size_t num_bytes = st->sampc * st->sampsz; + int ret, pa_error = 0; + + while (st->run) { + + st->wh(st->sampv, st->sampc, st->arg); + + ret = pa_simple_write(st->s, st->sampv, num_bytes, &pa_error); + if (ret < 0) { + warning("pulse: pa_simple_write error (%s)\n", + pa_strerror(pa_error)); + } + } + + return NULL; +} + + +static int aufmt_to_pulse_format(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return PA_SAMPLE_S16NE; + case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; + default: return 0; + } +} + + +int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + pa_sample_spec ss; + pa_buffer_attr attr; + int err = 0, pa_error = 0; + + if (!stp || !ap || !prm || !wh) + return EINVAL; + + debug("pulse: opening player (%u Hz, %d channels, device '%s')\n", + prm->srate, prm->ch, device); + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = ap; + st->wh = wh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); + + st->sampv = mem_alloc(st->sampsz * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + ss.format = aufmt_to_pulse_format(prm->fmt); + ss.channels = prm->ch; + ss.rate = prm->srate; + + attr.maxlength = (uint32_t)-1; + attr.tlength = (uint32_t)pa_usec_to_bytes(prm->ptime * 1000, &ss); + attr.prebuf = (uint32_t)-1; + attr.minreq = (uint32_t)-1; + attr.fragsize = (uint32_t)-1; + + st->s = pa_simple_new(NULL, + "Baresip", + PA_STREAM_PLAYBACK, + str_isset(device) ? device : 0, + "VoIP Playback", + &ss, + NULL, + &attr, + &pa_error); + if (!st->s) { + warning("pulse: could not connect to server (%s)\n", + pa_strerror(pa_error)); + err = ENODEV; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, write_thread, st); + if (err) { + st->run = false; + goto out; + } + + debug("pulse: playback started\n"); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} diff --git a/modules/pulse/pulse.c b/modules/pulse/pulse.c new file mode 100644 index 0000000..01f6914 --- /dev/null +++ b/modules/pulse/pulse.c @@ -0,0 +1,54 @@ +/** + * @file pulse.c Pulseaudio sound driver + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "pulse.h" + + +/** + * @defgroup pulse pulse + * + * Audio driver module for Pulseaudio + * + * This module is experimental and work-in-progress. It is using + * the pulseaudio "simple" interface. + */ + + +static struct auplay *auplay; +static struct ausrc *ausrc; + + +static int module_init(void) +{ + int err; + + err = auplay_register(&auplay, baresip_auplayl(), + "pulse", pulse_player_alloc); + err |= ausrc_register(&ausrc, baresip_ausrcl(), + "pulse", pulse_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(pulse) = { + "pulse", + "audio", + module_init, + module_close, +}; diff --git a/modules/pulse/pulse.h b/modules/pulse/pulse.h new file mode 100644 index 0000000..51c392f --- /dev/null +++ b/modules/pulse/pulse.h @@ -0,0 +1,14 @@ +/** + * @file pulse.h Pulseaudio sound driver -- internal API + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + + +int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg); +int pulse_recorder_alloc(struct ausrc_st **stp, const 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/pulse/recorder.c b/modules/pulse/recorder.c new file mode 100644 index 0000000..ca6fb6a --- /dev/null +++ b/modules/pulse/recorder.c @@ -0,0 +1,193 @@ +/** + * @file pulse/recorder.c Pulseaudio sound driver - recorder + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <pulse/pulseaudio.h> +#include <pulse/simple.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "pulse.h" + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + + pa_simple *s; + pthread_t thread; + bool run; + void *sampv; + size_t sampc; + size_t sampsz; + uint32_t ptime; + ausrc_read_h *rh; + void *arg; +}; + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + /* Wait for termination of other thread */ + if (st->run) { + debug("pulse: stopping record thread\n"); + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->s) + pa_simple_free(st->s); + + mem_deref(st->sampv); +} + + +static void *read_thread(void *arg) +{ + struct ausrc_st *st = arg; + const size_t num_bytes = st->sampc * st->sampsz; + int ret, pa_error = 0; + uint64_t now, last_read, diff; + unsigned dropped = 0; + bool init = true; + + if (pa_simple_flush(st->s, &pa_error)) { + warning("pulse: pa_simple_flush error (%s)\n", + pa_strerror(pa_error)); + } + + last_read = tmr_jiffies(); + + while (st->run) { + + ret = pa_simple_read(st->s, st->sampv, num_bytes, &pa_error); + if (ret < 0) { + warning("pulse: pa_simple_write error (%s)\n", + pa_strerror(pa_error)); + continue; + } + + /* Some devices might send a burst of samples right after the + initialization - filter them out */ + if (init) { + now = tmr_jiffies(); + diff = (now > last_read)? now - last_read : 0; + + if (diff < st->ptime / 2) { + last_read = now; + ++dropped; + continue; + } + else { + init = false; + + if (dropped) + debug("pulse: dropped %u frames of " + "garbage at the beginning of " + "the recording\n", dropped); + } + } + + st->rh(st->sampv, st->sampc, st->arg); + } + + return NULL; +} + + +static int aufmt_to_pulse_format(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return PA_SAMPLE_S16NE; + case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; + default: return 0; + } +} + + +int pulse_recorder_alloc(struct ausrc_st **stp, const 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; + pa_sample_spec ss; + pa_buffer_attr attr; + int pa_error; + int err; + + (void)ctx; + (void)device; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + debug("pulse: opening recorder (%u Hz, %d channels, device '%s')\n", + prm->srate, prm->ch, device); + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); + st->ptime = prm->ptime; + + st->sampv = mem_alloc(st->sampsz * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + ss.format = aufmt_to_pulse_format(prm->fmt); + ss.channels = prm->ch; + ss.rate = prm->srate; + + attr.maxlength = (uint32_t)-1; + attr.tlength = (uint32_t)-1; + attr.prebuf = (uint32_t)-1; + attr.minreq = (uint32_t)-1; + attr.fragsize = (uint32_t)pa_usec_to_bytes(prm->ptime * 1000, &ss); + + st->s = pa_simple_new(NULL, + "Baresip", + PA_STREAM_RECORD, + str_isset(device) ? device : 0, + "VoIP Record", + &ss, + NULL, + &attr, + &pa_error); + if (!st->s) { + warning("pulse: could not connect to server (%s)\n", + pa_strerror(pa_error)); + err = ENODEV; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) { + st->run = false; + goto out; + } + + debug("pulse: recording started\n"); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} 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..e4fddc2 --- /dev/null +++ b/modules/qtcapture/qtcapture.m @@ -0,0 +1,402 @@ +/** + * @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 { + const 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); +} + + +#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, const 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 = 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, baresip_vidsrcl(), + "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/rst/audio.c b/modules/rst/audio.c new file mode 100644 index 0000000..4bb7e0d --- /dev/null +++ b/modules/rst/audio.c @@ -0,0 +1,273 @@ +/** + * @file rst/audio.c MP3/ICY HTTP Audio Source + * + * Copyright (C) 2011 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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 { + const struct ausrc *as; /* pointer to base-class (inheritance) */ + 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 ptime; + size_t sampc; + size_t sampsz; +}; + + +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); +} + + +static void *play_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct ausrc_st *st = arg; + void *sampv; + size_t num_bytes = st->sampc * st->sampsz; + + sampv = mem_alloc(num_bytes, NULL); + if (!sampv) + return NULL; + + while (st->run) { + + sys_msleep(4); + + now = tmr_jiffies(); + + if (ts > now) + continue; +#if 1 + if (now > ts + 100) { + debug("rst: cpu lagging behind (%u ms)\n", + now - ts); + } +#endif + + aubuf_read(st->aubuf, sampv, num_bytes); + + st->rh(sampv, st->sampc, st->arg); + + ts += st->ptime; + } + + mem_deref(sampv); + + 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); + info("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: + warning("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 aufmt_to_encoding(enum aufmt fmt) +{ + switch (fmt) { + + case AUFMT_S16LE: return MPG123_ENC_SIGNED_16; + case AUFMT_FLOAT: return MPG123_ENC_FLOAT_32; + case AUFMT_S24_3LE: return MPG123_ENC_SIGNED_24; /* NOTE: endian */ + default: return 0; + } +} + + +static int alloc_handler(struct ausrc_st **stp, const 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; + int err; + + if (!stp || !as || !prm || !rh) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->as = 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) { + warning("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, + aufmt_to_encoding(prm->fmt)); + mpg123_volume(st->mp3, 0.3); + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->sampsz = aufmt_sample_size(prm->fmt); + + st->ptime = prm->ptime; + + info("rst: audio ptime=%u sampc=%zu aubuf=[%u:%u]\n", + st->ptime, st->sampc, + prm->srate * prm->ch * 2, + prm->srate * prm->ch * 40); + + /* 1 - 20 seconds of audio */ + err = aubuf_alloc(&st->aubuf, + prm->srate * prm->ch * st->sampsz, + prm->srate * prm->ch * st->sampsz * 20); + 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) { + warning("rst: mpg123_init: %s\n", mpg123_plain_strerror(err)); + return ENODEV; + } + + return ausrc_register(&ausrc, baresip_ausrcl(), "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..a52fdbe --- /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 += $(shell pkg-config --libs cairo libmpg123) +$(MOD)_CFLAGS += $(shell 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..83298a9 --- /dev/null +++ b/modules/rst/rst.c @@ -0,0 +1,423 @@ +/** + * @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" + + +/** + * @defgroup rst rst + * + * Audio and video source module using mpg123 as input + * + * The module 'rst' is using the mpg123 to play streaming + * media (MP3) and provide this as an internal audio/video source. + * + * Example config: + \verbatim + audio_source rst,http://relay.slayradio.org:8000/ + video_source rst,http://relay.slayradio.org:8000/ + \endverbatim + */ + + +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) { + warning("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) { + info("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; + + info("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 + info("rst: metadata %zu bytes\n", n); +#endif + if (rst->bytec >= rst->metasz) { +#if 0 + info("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 + info("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 + info("rst: metalength %zu bytes\n", rst->metasz); +#endif + } + } +} + + +static void estab_handler(void *arg) +{ + struct rst *rst = arg; + struct mbuf *mb; + int err; + + info("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) { + warning("rst: error sending HTTP request: %m\n", err); + } + + mem_deref(mb); +} + + +static void close_handler(int err, void *arg) +{ + struct rst *rst = arg; + + info("rst: tcp closed: %m\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) { + warning("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) { + warning("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) { + warning("rst: tcp connect error: %m\n", err); + } + } + else { + err = dnsc_query(&rst->dnsq, net_dnsc(baresip_network()), + rst->host, DNS_TYPE_A, + DNS_CLASS_IN, true, dns_handler, rst); + if (err) { + warning("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)) { + warning("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..bf59daf --- /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 _DEFAULT_SOURCE 1 +#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 { + const struct vidsrc *vs; /* pointer to base-class (inheritance) */ + 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); +} + + +static void *video_thread(void *arg) +{ + uint64_t now, ts = tmr_jiffies(); + struct vidsrc_st *st = arg; + + while (st->run) { + + sys_msleep(4); + + 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, const 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 = 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, baresip_vidsrcl(), + "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..9e45104 --- /dev/null +++ b/modules/sdl/module.mk @@ -0,0 +1,18 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := sdl +$(MOD)_SRCS += sdl.c +$(MOD)_SRCS += util.c + +$(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..02facd7 --- /dev/null +++ b/modules/sdl/sdl.c @@ -0,0 +1,327 @@ +/** + * @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" + + +/** + * @defgroup sdl sdl + * + * Video display using Simple DirectMedia Layer (SDL) + */ + + +/** Local constants */ +enum { + KEY_RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct vidisp_st { + const 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_key(baresip_uis(), KEYCODE_REL, NULL); +} + + +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_key(baresip_uis(), ch, NULL); + } + break; + } + + break; + + case SDL_VIDEORESIZE: + handle_resize(event.resize.w, event.resize.h); + break; + + case SDL_QUIT: + ui_input_key(baresip_uis(), 'q', NULL); + 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; + (void)st; + + sdl_close(); +} + + +static int alloc(struct vidisp_st **stp, const 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 = 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, baresip_vidispl(), + "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..c2413bb --- /dev/null +++ b/modules/sdl2/sdl.c @@ -0,0 +1,343 @@ +/** + * @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> + + +/** + * @defgroup sdl2 sdl2 + * + * Video display using Simple DirectMedia Layer version 2 (SDL2) + */ + + +struct vidisp_st { + const 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 */ + enum vidfmt fmt; /**< Current pixel format */ + bool fullscreen; /**< Fullscreen flag */ + struct tmr tmr; + Uint32 flags; +}; + + +static struct vidisp *vid; + + +static void event_handler(void *arg); + + +static uint32_t match_fmt(enum vidfmt fmt) +{ + switch (fmt) { + + case VID_FMT_YUV420P: return SDL_PIXELFORMAT_IYUV; +#if SDL_VERSION_ATLEAST(2, 0, 4) + case VID_FMT_NV12: return SDL_PIXELFORMAT_NV12; +#endif + case VID_FMT_RGB32: return SDL_PIXELFORMAT_ARGB8888; + default: return SDL_PIXELFORMAT_UNKNOWN; + } +} + + +static uint32_t chroma_step(enum vidfmt fmt) +{ + switch (fmt) { + + case VID_FMT_YUV420P: return 2; + case VID_FMT_NV12: return 1; + case VID_FMT_RGB32: return 0; + default: return 0; + } +} + + +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 event_handler(void *arg) +{ + struct vidisp_st *st = arg; + SDL_Event event; + + tmr_start(&st->tmr, 100, event_handler, st); + + /* NOTE: events must be checked from main thread */ + while (SDL_PollEvent(&event)) { + + if (event.type == SDL_KEYDOWN) { + + switch (event.key.keysym.sym) { + + case SDLK_f: + /* press key 'f' to toggle fullscreen */ + st->fullscreen = !st->fullscreen; + info("sdl: %sable fullscreen mode\n", + st->fullscreen ? "en" : "dis"); + + if (st->fullscreen) + st->flags |= + SDL_WINDOW_FULLSCREEN_DESKTOP; + else + st->flags &= + ~SDL_WINDOW_FULLSCREEN_DESKTOP; + + SDL_SetWindowFullscreen(st->window, st->flags); + break; + + default: + break; + } + } + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + tmr_cancel(&st->tmr); + sdl_reset(st); +} + + +static int alloc(struct vidisp_st **stp, const 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)dev; + (void)resizeh; + (void)arg; + + if (!stp || !vd) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->vd = vd; + st->fullscreen = prm ? prm->fullscreen : false; + + tmr_start(&st->tmr, 100, event_handler, st); + + 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 *d; + int dpitch, ret; + unsigned i, h; + uint32_t format; + + if (!st || !frame) + return EINVAL; + + format = match_fmt(frame->fmt); + if (format == SDL_PIXELFORMAT_UNKNOWN) { + warning("sdl2: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + return ENOTSUP; + } + + if (!vidsz_cmp(&st->size, &frame->size) || frame->fmt != st->fmt) { + if (st->size.w && st->size.h) { + info("sdl: reset size:" + " %s %u x %u ---> %s %u x %u\n", + vidfmt_name(st->fmt), st->size.w, st->size.h, + vidfmt_name(frame->fmt), + frame->size.w, frame->size.h); + } + sdl_reset(st); + } + + if (!st->window) { + char capt[256]; + + st->flags = SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS; + + if (st->fullscreen) + st->flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + 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, + st->flags); + if (!st->window) { + warning("sdl: unable to create sdl window: %s\n", + SDL_GetError()); + return ENODEV; + } + + st->size = frame->size; + st->fmt = frame->fmt; + + 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, + format, + 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, &dpitch); + if (ret != 0) { + warning("sdl: unable to lock texture (ret=%d)\n", ret); + return ENODEV; + } + + d = pixels; + for (i=0; i<3; i++) { + + const uint8_t *s = frame->data[i]; + unsigned sz, dsz, hstep, wstep; + + if (!frame->data[i] || !frame->linesize[i]) + break; + + hstep = i==0 ? 1 : 2; + wstep = i==0 ? 1 : chroma_step(frame->fmt); + + dsz = dpitch / wstep; + sz = min(frame->linesize[i], dsz); + + for (h = 0; h < frame->size.h; h += hstep) { + + memcpy(d, s, sz); + + s += frame->linesize[i]; + d += dsz; + } + } + + 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, baresip_vidispl(), + "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..5cc1bed --- /dev/null +++ b/modules/selfview/selfview.c @@ -0,0 +1,280 @@ +/** + * @file selfview.c Selfview Video-Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup selfview selfview + * + * Show a selfview of the captured video stream + * + * Example config: + \verbatim + video_selfview pip # {window,pip} + selfview_size 64x64 + \endverbatim + */ + + +/* 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, baresip_vidispl(), + 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 = PL("pip"); + + (void)conf_get(conf_cur(), "video_selfview", &pl); + + if (0 == pl_strcasecmp(&pl, "window")) + vidfilt_register(baresip_vidfiltl(), &selfview_win); + else if (0 == pl_strcasecmp(&pl, "pip")) + vidfilt_register(baresip_vidfiltl(), &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..dee296c --- /dev/null +++ b/modules/silk/silk.c @@ -0,0 +1,262 @@ +/** + * @file silk.c Skype SILK audio codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <silk/SKP_Silk_SDK_API.h> + + +/** + * @defgroup silk silk + * + * The Skype SILK audio codec + * + * 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, 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(baresip_aucodecl(), &silk[0]); + + return 0; +} + + +static int module_close(void) +{ + int i = ARRAY_SIZE(silk); + + while (i--) + aucodec_unregister(&silk[i]); + + return 0; +} + + +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..6c5c18a --- /dev/null +++ b/modules/snapshot/png_vf.c @@ -0,0 +1,189 @@ +/** + * @file png_vf.c Write vidframe to a PNG-file + * + * Author: Doug Blewett + * Review: Alfred E. Heggestad + */ +#define _DEFAULT_SOURCE 1 +#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..4dd5735 --- /dev/null +++ b/modules/snapshot/snapshot.c @@ -0,0 +1,104 @@ +/** + * @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" + + +/** + * @defgroup snapshot snapshot + * + * Take snapshot of the video stream and save it as PNG-files + * + * + * Commands: + * + \verbatim + snapshot Take video snapshot + \endverbatim + */ + + +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[] = { + {"snapshot", 0, 0, "Take video snapshot", do_snapshot }, +}; + + +static int module_init(void) +{ + vidfilt_register(baresip_vidfiltl(), &snapshot); + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + vidfilt_unregister(&snapshot); + cmd_unregister(baresip_commands(), 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..ac31bbb --- /dev/null +++ b/modules/sndfile/sndfile.c @@ -0,0 +1,200 @@ +/** + * @file sndfile.c Audio dumper using libsndfile + * + * Copyright (C) 2010 Creytiv.com + */ +#include <sndfile.h> +#include <time.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup sndfile sndfile + * + * Audio filter that writes audio samples to WAV-file + * + * Example Configuration: + \verbatim + snd_path /tmp/ + \endverbatim + */ + + +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 char file_path[256] = "."; + + +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), + "%s/dump-%H-%s.wav", + file_path, + 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(baresip_aufiltl(), &sndfile); + + conf_get_str(conf_cur(), "snd_path", file_path, sizeof(file_path)); + + info("sndfile: saving files in %s\n", file_path); + + 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/sndio/module.mk b/modules/sndio/module.mk new file mode 100644 index 0000000..79ace67 --- /dev/null +++ b/modules/sndio/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2014 Creytiv.com +# + +MOD := sndio +$(MOD)_SRCS += sndio.c +$(MOD)_LFLAGS += -lsndio + +include mk/mod.mk diff --git a/modules/sndio/sndio.c b/modules/sndio/sndio.c new file mode 100644 index 0000000..6ac4b67 --- /dev/null +++ b/modules/sndio/sndio.c @@ -0,0 +1,319 @@ +/** + * @file sndio.c SndIO sound driver + * + * Copyright (C) 2014 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <sndio.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup sndio sndio + * + * This module implements audio driver for OpenBSD sndio + */ + + +struct ausrc_st { + const struct ausrc *as; /* pointer to base-class */ + struct sio_hdl *hdl; + pthread_t thread; + int16_t *sampv; + size_t sampc; + int run; + ausrc_read_h *rh; + void *arg; +}; + +struct auplay_st { + const struct auplay *ap; /* pointer to base-class */ + struct sio_hdl *hdl; + pthread_t thread; + int16_t *sampv; + size_t sampc; + int run; + auplay_write_h *wh; + void *arg; +}; + +static struct ausrc *ausrc; +static struct auplay *auplay; + + +static struct sio_par *sndio_initpar(uint32_t srate, uint8_t ch) +{ + struct sio_par *par = NULL; + + if ((par = mem_zalloc(sizeof(*par), NULL)) == NULL) + return NULL; + + sio_initpar(par); + + /* sndio doesn't support a-low and u-low */ + par->bits = 16; + par->bps = SIO_BPS(par->bits); + par->sig = 1; + par->le = SIO_LE_NATIVE; + + par->rchan = ch; + par->pchan = ch; + par->rate = srate; + + return par; +} + + +static void *read_thread(void *arg) +{ + struct ausrc_st *st = arg; + + if (!sio_start(st->hdl)) { + warning("sndio: could not start record\n"); + goto out; + } + + while (st->run) { + size_t n = sio_read(st->hdl, st->sampv, st->sampc*2); + st->rh(st->sampv, n/2, st->arg); + } + + out: + return NULL; +} + + +static void *write_thread(void *arg) +{ + struct auplay_st *st = arg; + + if (!sio_start(st->hdl)) { + warning("sndio: could not start playback\n"); + goto out; + } + + while (st->run) { + st->wh(st->sampv, st->sampc, st->arg); + sio_write(st->hdl, st->sampv, st->sampc*2); + } + + out: + return NULL; +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->hdl) + sio_close(st->hdl); + + mem_deref(st->sampv); +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + if (st->run) { + st->run = false; + (void)pthread_join(st->thread, NULL); + } + + if (st->hdl) + sio_close(st->hdl); + + mem_deref(st->sampv); +} + + +static int src_alloc(struct ausrc_st **stp, const 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; + struct sio_par *par = NULL; + int err; + const char *name; + + (void)ctx; + (void)errh; + + if (!stp || !as || !prm) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("sndio: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + name = (str_isset(device)) ? device : SIO_DEVANY; + + if ((st = mem_zalloc(sizeof(*st), ausrc_destructor)) == NULL) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->arg = arg; + st->hdl = sio_open(name, SIO_REC, 0); + + if (!st->hdl) { + warning("sndio: could not open ausrc device '%s'\n", name); + err = EINVAL; + goto out; + } + + par = sndio_initpar(prm->srate, prm->ch); + if (!par) { + err = ENOMEM; + goto out; + } + + if (!sio_setpar(st->hdl, par)) { + err = EINVAL; + goto out; + } + + if (!sio_getpar(st->hdl, par)) { + err = EINVAL; + goto out; + } + + st->sampc = par->bufsz / 2; + + st->sampv = mem_alloc(2 * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, read_thread, st); + if (err) + st->run = false; + + out: + mem_deref(par); + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int play_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + struct sio_par *par = NULL; + int err; + const char *name; + + if (!stp || !ap || !prm) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("sndio: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + name = (str_isset(device)) ? device : SIO_DEVANY; + + if ((st = mem_zalloc(sizeof(*st), auplay_destructor)) == NULL) + return ENOMEM; + + st->ap = ap; + st->wh = wh; + st->arg = arg; + st->hdl = sio_open(name, SIO_PLAY, 0); + + if (!st->hdl) { + warning("sndio: could not open auplay device '%s'\n", name); + err = EINVAL; + goto out; + } + + par = sndio_initpar(prm->srate, prm->ch); + if (!par) { + err = ENOMEM; + goto out; + } + + if (!sio_setpar(st->hdl, par)) { + err = EINVAL; + goto out; + } + + if (!sio_getpar(st->hdl, par)) { + err = EINVAL; + goto out; + } + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sampv = mem_alloc(2 * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + st->run = true; + err = pthread_create(&st->thread, NULL, write_thread, st); + if (err) + st->run = false; + + out: + mem_deref(par); + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int sndio_init(void) +{ + int err = 0; + + err |= ausrc_register(&ausrc, baresip_ausrcl(), "sndio", src_alloc); + err |= auplay_register(&auplay, baresip_auplayl(), + "sndio", play_alloc); + + return err; +} + + +static int sndio_close(void) +{ + ausrc = mem_deref(ausrc); + auplay = mem_deref(auplay); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(sndio) = { + "sndio", + "sound", + sndio_init, + sndio_close +}; diff --git a/modules/speex/module.mk b/modules/speex/module.mk new file mode 100644 index 0000000..81c8b18 --- /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 +$(MOD)_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..33b52e2 --- /dev/null +++ b/modules/speex/speex.c @@ -0,0 +1,519 @@ +/** + * @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> + + +/** + * @defgroup speex speex + * + * The Speex audio codec + * + * NOTE: The Speex codec has been obsoleted by Opus. + */ + + +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_nb[128]; +static char speex_fmtp_wb[128]; + + +/** Speex configuration */ +static struct { + int quality; + int complexity; + int enhancement; + int mode_nb; + int mode_wb; + int vbr; + int vad; +} sconf = { + 3, /* 0-10 */ + 2, /* 0-10 */ + 0, /* 0 or 1 */ + 3, /* 1-6 */ + 6, /* 1-6 */ + 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,any} + */ + 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_mode_nb", &v)) + sconf.mode_nb = v; + if (0 == conf_get_u32(conf, "speex_mode_wb", &v)) + sconf.mode_wb = 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, 32000, 2, speex_fmtp_wb, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 16000, 2, speex_fmtp_wb, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 8000, 2, speex_fmtp_nb, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + + /* Standard Speex */ + {LE_INIT, 0, "speex", 32000, 32000, 1, speex_fmtp_wb, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 16000, 16000, 1, speex_fmtp_wb, + encode_update, encode, decode_update, decode, pkloss, 0, 0}, + {LE_INIT, 0, "speex", 8000, 8000, 1, speex_fmtp_nb, + 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_nb, sizeof(speex_fmtp_nb), + "mode=\"%d\";vbr=%s;cng=on", sconf.mode_nb, + sconf.vad ? "vad" : (sconf.vbr ? "on" : "off")); + + (void)re_snprintf(speex_fmtp_wb, sizeof(speex_fmtp_wb), + "mode=\"%d\";vbr=%s;cng=on", sconf.mode_wb, + sconf.vad ? "vad" : (sconf.vbr ? "on" : "off")); + + for (i=0; i<ARRAY_SIZE(speexv); i++) + aucodec_register(baresip_aucodecl(), &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..5377a06 --- /dev/null +++ b/modules/speex_aec/speex_aec.c @@ -0,0 +1,229 @@ +/** + * @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> + + +/** + * @defgroup speex_aec speex_aec + * + * Acoustic Echo Cancellation (AEC) from libspeexdsp + */ + + +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(baresip_aufiltl(), &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..d82e575 --- /dev/null +++ b/modules/speex_pp/speex_pp.c @@ -0,0 +1,161 @@ +/** + * @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> + + +/** + * @defgroup speex_pp speex_pp + * + * Audio pre-processor from libspeexdsp + */ + + +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(baresip_aufiltl(), &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..285f4ed --- /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 += + +include mk/mod.mk diff --git a/modules/srtp/sdes.c b/modules/srtp/sdes.c new file mode 100644 index 0000000..49b32aa --- /dev/null +++ b/modules/srtp/sdes.c @@ -0,0 +1,45 @@ +/** + * @file /srtp/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..a5637bc --- /dev/null +++ b/modules/srtp/sdes.h @@ -0,0 +1,22 @@ +/** + * @file /srtp/sdes.h SDP Security Descriptions for Media Streams 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..217e5bf --- /dev/null +++ b/modules/srtp/srtp.c @@ -0,0 +1,420 @@ +/** + * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +/** + * @defgroup srtp srtp + * + * Secure Real-time Transport Protocol module + * + * This module implements media encryption using SRTP and SDES. + * + * SRTP can be enabled in ~/.baresip/accounts: + * + \verbatim + <sip:user@domain.com>;mediaenc=srtp + <sip:user@domain.com>;mediaenc=srtp-mand + \endverbatim + * + */ + + +#define SRTP_MASTER_KEY_LEN 30 + + +struct menc_st { + /* one SRTP session per media line */ + uint8_t key_tx[32]; + uint8_t key_rx[32]; + struct srtp *srtp_tx, *srtp_rx; + bool use_srtp; + bool got_sdp; + 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 const char *preferred_suite = 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); + + mem_deref(st->srtp_tx); + mem_deref(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; +} + + +/* + * 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 enum srtp_suite resolve_suite(const char *suite) +{ + if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) + return SRTP_AES_CM_128_HMAC_SHA1_32; + if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) + return SRTP_AES_CM_128_HMAC_SHA1_80; + + return -1; +} + + +static int start_srtp(struct menc_st *st, const char *suite_name) +{ + enum srtp_suite suite; + int err; + + suite = resolve_suite(suite_name); + + /* allocate and initialize the SRTP session */ + if (!st->srtp_tx) { + err = srtp_alloc(&st->srtp_tx, suite, st->key_tx, 30, 0); + if (err) { + warning("srtp: srtp_alloc TX failed (%m)\n", err); + return err; + } + } + + if (!st->srtp_rx) { + err = srtp_alloc(&st->srtp_rx, suite, st->key_rx, 30, 0); + if (err) { + warning("srtp: srtp_alloc RX failed (%m)\n", err); + return err; + } + } + + /* use SRTP for this stream/session */ + st->use_srtp = true; + + return 0; +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + size_t len = mbuf_get_left(mb); + int lerr = 0; + (void)dst; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + if (is_rtcp_packet(mb)) { + lerr = srtcp_encrypt(st->srtp_tx, mb); + } + else { + lerr = srtp_encrypt(st->srtp_tx, mb); + } + + if (lerr) { + warning("srtp: failed to encrypt %s-packet" + " with %zu bytes (%m)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, lerr); + *err = lerr; + return false; + } + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + size_t len = mbuf_get_left(mb); + int err = 0; + (void)src; + + if (!st->got_sdp) + return true; /* drop the packet */ + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + if (is_rtcp_packet(mb)) { + err = srtcp_decrypt(st->srtp_rx, mb); + if (err) { + warning("srtp: failed to decrypt RTCP packet" + " with %zu bytes (%m)\n", len, err); + } + } + else { + err = srtp_decrypt(st->srtp_rx, mb); + if (err) { + warning("srtp: failed to decrypt RTP packet" + " with %zu bytes (%m)\n", len, err); + } + } + + return err ? true : false; +} + + +/* 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) { + warning("srtp: 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, preferred_suite); + if (err) + goto out; + + rand_bytes(st->key_tx, SRTP_MASTER_KEY_LEN); + } + + /* SDP handling */ + + if (sdp_media_rport(sdpm)) + st->got_sdp = true; + + if (sdp_media_rattr(st->sdpm, "crypto")) { + + rattr = sdp_media_rattr_apply(st->sdpm, "crypto", + sdp_attr_handler, st); + if (!rattr) { + warning("srtp: no valid a=crypto attribute from" + " remote peer\n"); + } + } + + if (!rattr) + err = sdp_enc(st, sdpm, 1, 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) +{ + struct list *mencl = baresip_mencl(); + + menc_register(mencl, &menc_srtp_opt); + menc_register(mencl, &menc_srtp_mand); + menc_register(mencl, &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); + + 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..179484e --- /dev/null +++ b/modules/stdio/stdio.c @@ -0,0 +1,193 @@ +/** + * @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> + + +/** + * @defgroup stdio stdio + * + * User-Interface (UI) module for standard input/output + * + * This module sets up the terminal in raw mode, and reads characters from the + * input to the UI subsystem. The module is indented for Unix-based systems. + */ + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct tmr tmr; + struct termios term; + bool term_set; +}; + + +/* We only allow one instance */ +static struct ui_st *ui_state; + + +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); +} + + +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) +{ + static struct re_printf pf_stderr = {print_handler, NULL}; + (void)ui; + + ui_input_key(baresip_uis(), key, &pf_stderr); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, KEYCODE_REL); +} + + +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_st *st; + int err; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ui_destructor); + if (!st) + return ENOMEM; + + 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; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int output_handler(const char *str) +{ + return print_handler(str, str_len(str), NULL); +} + + +static struct ui ui_stdio = { + .name = "stdio", + .outputh = output_handler +}; + + +static int module_init(void) +{ + int err; + + err = ui_alloc(&ui_state); + if (err) + return err; + + ui_register(baresip_uis(), &ui_stdio); + + return 0; +} + + +static int module_close(void) +{ + ui_unregister(&ui_stdio); + ui_state = mem_deref(ui_state); + + 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..48dffe7 --- /dev/null +++ b/modules/stun/stun.c @@ -0,0 +1,252 @@ +/** + * @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, baresip_mnatl(), + "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/swscale/module.mk b/modules/swscale/module.mk new file mode 100644 index 0000000..73b49e0 --- /dev/null +++ b/modules/swscale/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := swscale +$(MOD)_SRCS += swscale.c +$(MOD)_LFLAGS += -lswscale + +include mk/mod.mk diff --git a/modules/swscale/swscale.c b/modules/swscale/swscale.c new file mode 100644 index 0000000..5cd8905 --- /dev/null +++ b/modules/swscale/swscale.c @@ -0,0 +1,197 @@ +/** + * @file swscale.c Video filter for scaling and pixel conversion + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <libswscale/swscale.h> + + +struct swscale_enc { + struct vidfilt_enc_st vf; /**< Inheritance */ + + struct SwsContext *sws; + struct vidframe *frame; + struct vidsz dst_size; +}; + + +static enum vidfmt swscale_format = VID_FMT_YUV420P; /* XXX: configurable */ + + +static enum AVPixelFormat vidfmt_to_avpixfmt(enum vidfmt fmt) +{ + switch (fmt) { + + case VID_FMT_YUV420P: return AV_PIX_FMT_YUV420P; + case VID_FMT_NV12: return AV_PIX_FMT_NV12; + case VID_FMT_NV21: return AV_PIX_FMT_NV21; + default: return AV_PIX_FMT_NONE; + } +} + + +static void encode_destructor(void *arg) +{ + struct swscale_enc *st = arg; + + list_unlink(&st->vf.le); + + mem_deref(st->frame); + sws_freeContext(st->sws); +} + + +static int encode_update(struct vidfilt_enc_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct swscale_enc *st; + struct config *config = conf_config(); + int err = 0; + + if (!config) { + warning("swscale: no config\n"); + return EINVAL; + } + + if (!stp || !ctx || !vf) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + st->dst_size.w = config->video.width; + st->dst_size.h = config->video.height; + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_enc_st *)st; + + return err; +} + + +static int encode_process(struct vidfilt_enc_st *st, struct vidframe *frame) +{ + struct swscale_enc *enc = (struct swscale_enc *)st; + enum AVPixelFormat avpixfmt, avpixfmt_dst; + const uint8_t *srcSlice[4]; + uint8_t *dst[4]; + int srcStride[4], dstStride[4]; + int width, height, i, h; + int err = 0; + + if (!st) + return EINVAL; + + if (!frame) + return 0; + + width = frame->size.w; + height = frame->size.h; + + avpixfmt = vidfmt_to_avpixfmt(frame->fmt); + if (avpixfmt == AV_PIX_FMT_NONE) { + warning("swscale: unknown pixel-format (%s)\n", + vidfmt_name(frame->fmt)); + return EINVAL; + } + + avpixfmt_dst = vidfmt_to_avpixfmt(swscale_format); + if (avpixfmt_dst == AV_PIX_FMT_NONE) { + warning("swscale: unknown pixel-format (%s)\n", + vidfmt_name(swscale_format)); + return EINVAL; + } + + if (!enc->sws) { + + struct SwsContext *sws; + int flags = 0; + + sws = sws_getContext(width, height, avpixfmt, + enc->dst_size.w, enc->dst_size.h, + avpixfmt_dst, + flags, NULL, NULL, NULL); + if (!sws) { + warning("swscale: sws_getContext error\n"); + return ENOMEM; + } + + enc->sws = sws; + + info("swscale: created SwsContext:" + " `%s' %d x %d --> `%s' %u x %u\n", + vidfmt_name(frame->fmt), width, height, + vidfmt_name(swscale_format), + enc->dst_size.w, enc->dst_size.h); + } + + if (!enc->frame) { + + err = vidframe_alloc(&enc->frame, swscale_format, + &enc->dst_size); + if (err) { + warning("swscale: vidframe_alloc error (%m)\n", err); + return err; + } + } + + for (i=0; i<4; i++) { + srcSlice[i] = frame->data[i]; + srcStride[i] = frame->linesize[i]; + dst[i] = enc->frame->data[i]; + dstStride[i] = enc->frame->linesize[i]; + } + + h = sws_scale(enc->sws, srcSlice, srcStride, + 0, height, dst, dstStride); + if (h <= 0) { + warning("swscale: sws_scale error (%d)\n", h); + return EPROTO; + } + + /* Copy the converted frame back to the input frame */ + for (i=0; i<4; i++) { + frame->data[i] = enc->frame->data[i]; + frame->linesize[i] = enc->frame->linesize[i]; + } + frame->size = enc->frame->size; + frame->fmt = enc->frame->fmt; + + return 0; +} + + +static struct vidfilt vf_swscale = { + LE_INIT, "swscale", encode_update, encode_process, NULL, NULL +}; + + +static int module_init(void) +{ + vidfilt_register(baresip_vidfiltl(), &vf_swscale); + return 0; +} + + +static int module_close(void) +{ + vidfilt_unregister(&vf_swscale); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(swscale) = { + "swscale", + "vidfilt", + 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..7c9b054 --- /dev/null +++ b/modules/syslog/syslog.c @@ -0,0 +1,76 @@ +/** + * @file syslog.c Syslog module + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <syslog.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup syslog syslog + * + * This module implements a logging handler for output to syslog + */ + + +#define DEBUG_MODULE "" +#define DEBUG_LEVEL 0 +#include <re_dbg.h> + + +static const int lmap[] = { LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR }; + + +static void log_handler(uint32_t level, const char *msg) +{ + syslog(lmap[MIN(level, ARRAY_SIZE(lmap)-1)], "%s", msg); +} + + +static struct log lg = { + .h = log_handler, +}; + + +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); + + dbg_init(DBG_INFO, DBG_NONE); + dbg_handler_set(syslog_handler, NULL); + + log_register_handler(&lg); + + return 0; +} + + +static int module_close(void) +{ + log_unregister_handler(&lg); + + dbg_handler_set(NULL, NULL); + + closelog(); + + return 0; +} + + +const struct mod_export DECL_EXPORTS(syslog) = { + "syslog", + "application", + 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..85fe84b --- /dev/null +++ b/modules/turn/turn.c @@ -0,0 +1,301 @@ +/** + * @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, baresip_mnatl(), + "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..d82836b --- /dev/null +++ b/modules/uuid/module.mk @@ -0,0 +1,10 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := uuid +$(MOD)_SRCS += uuid.c + +include mk/mod.mk diff --git a/modules/uuid/uuid.c b/modules/uuid/uuid.c new file mode 100644 index 0000000..ba1daa4 --- /dev/null +++ b/modules/uuid/uuid.c @@ -0,0 +1,117 @@ +/** + * @file modules/uuid/uuid.c Generate and load UUID + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup uuid uuid + * + * UUID generator and loader + */ + + +enum { UUID_LEN = 36 }; + + +static int generate_random_uuid(FILE *f) +{ + if (re_fprintf(f, "%08x-%04x-%04x-%04x-%08x%04x", + rand_u32(), rand_u16(), rand_u16(), rand_u16(), + rand_u32(), rand_u16()) != UUID_LEN) + return ENOMEM; + + return 0; +} + + +static int uuid_init(const char *file) +{ + 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; + } + + err = generate_random_uuid(f); + if (err) { + warning("uuid: generate random UUID failed (%m)\n", err); + goto out; + } + + info("uuid: generated new UUID in %s\n", file); + + 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); + + debug("uuid: loaded UUID %s from file %s\n", uuid, file); + + 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..a171021 --- /dev/null +++ b/modules/v4l/v4l.c @@ -0,0 +1,270 @@ +/** + * @file v4l.c Video4Linux video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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> + + +/** + * @defgroup v4l v4l + * + * Video4Linux video-source module + */ + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz size; + struct mbuf *mb; + enum vidfmt fmt; + 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; + } + + switch (pic.palette) { + + case VIDEO_PALETTE_RGB24: + st->fmt = VID_FMT_RGB32; + break; + + case VIDEO_PALETTE_YUYV: + st->fmt = VID_FMT_YUYV422; + break; + + default: + warning("v4l: unsupported palette %d\n", pic.palette); + return ENODEV; + } + + info("v4l: pixel format is %s\n", vidfmt_name(st->fmt)); + + 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, st->fmt, &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); +} + + +static int alloc(struct vidsrc_st **stp, const 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 = 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; + + /* allocate buffer for the picture */ + st->mb = mbuf_alloc(vidframe_size(st->fmt, &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, baresip_vidsrcl(), "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..8fefae4 --- /dev/null +++ b/modules/v4l2/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := v4l2 +$(MOD)_SRCS += v4l2.c +ifneq ($(HAVE_LIBV4L2),) +$(MOD)_LFLAGS += -lv4l2 +$(MOD)_CFLAGS += -DHAVE_LIBV4L2 +endif + +include mk/mod.mk diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c new file mode 100644 index 0000000..983ad53 --- /dev/null +++ b/modules/v4l2/v4l2.c @@ -0,0 +1,513 @@ +/** + * @file v4l2.c Video4Linux2 video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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 <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#if defined (OPENBSD) || defined (NETBSD) +#include <sys/videoio.h> +#else +#include <linux/videodev2.h> +#endif + +#ifdef HAVE_LIBV4L2 +#include <libv4l2.h> +#else +#define v4l2_open open +#define v4l2_read read +#define v4l2_ioctl ioctl +#define v4l2_mmap mmap +#define v4l2_munmap munmap +#define v4l2_close close +#endif + + +/** + * @defgroup v4l2 v4l2 + * + * V4L2 (Video for Linux 2) video-source module + */ + + +struct buffer { + void *start; + size_t length; +}; + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + + int fd; + pthread_t thread; + bool run; + struct vidsz sz; + u_int32_t pixfmt; + struct buffer *buffers; + unsigned int n_buffers; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static struct vidsrc *vidsrc; + + +static enum vidfmt match_fmt(u_int32_t fmt) +{ + switch (fmt) { + + case V4L2_PIX_FMT_YUV420: return VID_FMT_YUV420P; + case V4L2_PIX_FMT_YUYV: return VID_FMT_YUYV422; + case V4L2_PIX_FMT_UYVY: return VID_FMT_UYVY422; + case V4L2_PIX_FMT_RGB32: return VID_FMT_RGB32; + case V4L2_PIX_FMT_RGB565: return VID_FMT_RGB565; + case V4L2_PIX_FMT_RGB555: return VID_FMT_RGB555; + case V4L2_PIX_FMT_NV12: return VID_FMT_NV12; + case V4L2_PIX_FMT_NV21: return VID_FMT_NV21; + default: return VID_FMT_N; + } +} + + +static void print_video_input(const struct vidsrc_st *st) +{ + struct v4l2_input input; + + memset(&input, 0, sizeof(input)); + +#ifndef OPENBSD + if (-1 == v4l2_ioctl(st->fd, VIDIOC_G_INPUT, &input.index)) { + warning("v4l2: VIDIOC_G_INPUT: %m\n", errno); + return; + } +#endif + + 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_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 = mem_zalloc(req.count * sizeof(*st->buffers), NULL); + 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, + int width, int height) +{ + struct v4l2_capability cap; + struct v4l2_format fmt; + struct v4l2_fmtdesc fmts; + 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; + } + + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + warning("v4l2: %s does not support streaming i/o\n", + dev_name); + return ENOSYS; + } + + /* Negotiate video format */ + memset(&fmts, 0, sizeof(fmts)); + + fmts.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + for (fmts.index=0; !v4l2_ioctl(st->fd, VIDIOC_ENUM_FMT, &fmts); + fmts.index++) { + if (match_fmt(fmts.pixelformat) != VID_FMT_N) { + st->pixfmt = fmts.pixelformat; + break; + } + } + + if (!st->pixfmt) { + warning("v4l2: format negotiation failed: %m\n", errno); + return errno; + } + + /* Select video input, video standard and tune here. */ + + memset(&fmt, 0, sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = width; + fmt.fmt.pix.height = height; + fmt.fmt.pix.pixelformat = st->pixfmt; + 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; + + err = init_mmap(st, dev_name); + if (err) + return err; + + pix = (char *)&fmt.fmt.pix.pixelformat; + + if (st->pixfmt != fmt.fmt.pix.pixelformat) { + warning("v4l2: %s: unexpectedly 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; + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + xioctl(st->fd, VIDIOC_STREAMOFF, &type); +} + + +static void uninit_device(struct vidsrc_st *st) +{ + unsigned int i; + + for (i=0; i<st->n_buffers; ++i) { + v4l2_munmap(st->buffers[i].start, st->buffers[i].length); + } + + st->buffers = mem_deref(st->buffers); + st->n_buffers = 0; +} + + +static int start_capturing(struct vidsrc_st *st) +{ + unsigned int i; + enum v4l2_buf_type type; + + 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 (st->fd, VIDIOC_QBUF, &buf)) + return errno; + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (-1 == xioctl (st->fd, VIDIOC_STREAMON, &type)) + return errno; + + return 0; +} + + +static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf) +{ + struct vidframe frame; + + vidframe_init_buf(&frame, match_fmt(st->pixfmt), &st->sz, buf); + + st->frameh(&frame, st->arg); +} + + +static int read_frame(struct vidsrc_st *st) +{ + struct v4l2_buffer buf; + + 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; + } + + return 0; +} + + +static int vd_open(struct vidsrc_st *st, const char *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; + + debug("v4l2: stopping video source..\n"); + + 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); +} + + +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, const 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 = vs; + st->fd = -1; + st->sz = *size; + st->frameh = frameh; + st->arg = arg; + st->pixfmt = 0; + + err = vd_open(st, dev); + if (err) + goto out; + + err = v4l2_init_device(st, dev, size->w, size->h); + if (err) + goto out; + + print_video_input(st); + + err = start_capturing(st); + 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, baresip_vidsrcl(), + "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/v4l2_codec/README b/modules/v4l2_codec/README new file mode 100644 index 0000000..501647c --- /dev/null +++ b/modules/v4l2_codec/README @@ -0,0 +1,41 @@ +README +------ + +This module is using V4L2 (Video for Linux 2) as a codec module +for devices that supports compressed formats such as H.264 +The module implements both the vidsrc API and the vidcodec API. + + +- encoder/decoder: Encoder only +- codec formats: H.264 +- keyframe refresh: Not supported + + + + +EXAMPLE CONFIG +-------------- + +# Video +video_source v4l2_codec,/dev/video0 +video_size 640x480 + + +# Video codec Modules (in order) +module v4l2_codec.so + + + + +SUPPORTED DEVICES +----------------- + +This webcam supports H.264 hardware acceleration: + +HD Pro Webcam C920 (usb-0000:00:1a.0-1.5): + /dev/video0 + +ELP-USB100W04H-L36 ARC International - (Product ID : 05a3:9420) + This device provide 2 /dev/video sub-devices. + The first is a YUV and MJPEG device (/dev/video0), the second is for the H264 stream (/dev/video1). + You must use the second the second (/dev/video1). diff --git a/modules/v4l2_codec/module.mk b/modules/v4l2_codec/module.mk new file mode 100644 index 0000000..b0b7f3e --- /dev/null +++ b/modules/v4l2_codec/module.mk @@ -0,0 +1,11 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2015 Creytiv.com +# + +MOD := v4l2_codec +$(MOD)_SRCS += v4l2_codec.c +$(MOD)_LFLAGS += + +include mk/mod.mk diff --git a/modules/v4l2_codec/v4l2_codec.c b/modules/v4l2_codec/v4l2_codec.c new file mode 100644 index 0000000..814a477 --- /dev/null +++ b/modules/v4l2_codec/v4l2_codec.c @@ -0,0 +1,603 @@ +/** + * @file v4l2_codec.c Video4Linux2 video-source and video-codec + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#define _DEFAULT_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 <re.h> +#include <rem.h> +#include <baresip.h> +#if defined (OPENBSD) || defined (NETBSD) +#include <sys/videoio.h> +#else +#include <linux/videodev2.h> +#endif + + +/** + * @defgroup v4l2_codec v4l2_codec + * + * V4L2 (Video for Linux 2) video-codec and source hybrid module + * + * This module is using V4L2 (Video for Linux 2) as a codec module + * for devices that supports compressed formats such as H.264. + * The module implements both the vidsrc API and the vidcodec API. + * + * + * TODO: + * + * - timestamp syncronization + * - how to configure the wanted bitrate and framerate + * - how to handle Key-frame requests + */ + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + + uint8_t *buffer; + size_t buffer_len; + int fd; + struct { + unsigned n_key; + unsigned n_delta; + } stats; +}; + +struct videnc_state { + struct le le; + struct videnc_param encprm; + videnc_packet_h *pkth; + void *arg; +}; + + +/* TODO: global data, move to per vidsrc instance */ +static struct { + /* List of encoder-states (struct videnc_state) */ + struct list encoderl; +} v4l2; + + +static struct vidsrc *vidsrc; + + +static int xioctl(int fd, unsigned long int request, void *arg) +{ + int r; + + do r = ioctl (fd, request, arg); + while (-1 == r && EINTR == errno); + + return r; +} + + +static int print_caps(int fd, unsigned width, unsigned height) +{ + struct v4l2_capability caps; + struct v4l2_fmtdesc fmtdesc; + struct v4l2_format fmt; + bool support_h264 = false; + char fourcc[5] = {0}; + char c; + int err; + + memset(&caps, 0, sizeof(caps)); + memset(&fmtdesc, 0, sizeof(fmtdesc)); + memset(&fmt, 0, sizeof(fmt)); + + if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &caps)) { + err = errno; + warning("v4l2_codec: error Querying Capabilities (%m)\n", err); + return err; + } + + info("v4l2_codec: Driver Caps:\n" + " Driver: \"%s\"\n" + " Card: \"%s\"\n" + " Bus: \"%s\"\n" + " Version: %d.%d\n" + " Capabilities: 0x%08x\n", + caps.driver, + caps.card, + caps.bus_info, + (caps.version>>16) & 0xff, + (caps.version>>24) & 0xff, + caps.capabilities); + + + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + info(" Formats:\n"); + + while (0 == xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) { + bool selected = false; + + strncpy(fourcc, (char *)&fmtdesc.pixelformat, 4); + +#ifdef V4L2_PIX_FMT_H264 + if (fmtdesc.pixelformat == V4L2_PIX_FMT_H264) { + support_h264 = true; + selected = true; + } +#endif + + c = fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED ? 'C' : ' '; + + info(" %c %s: %c '%s'\n", + selected ? '>' : ' ', + fourcc, c, fmtdesc.description); + + fmtdesc.index++; + } + + info("\n"); + + if (!support_h264) { + warning("v4l2_codec: Doesn't support H264.\n"); + return ENODEV; + } + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = width; + fmt.fmt.pix.height = height; +#ifdef V4L2_PIX_FMT_H264 + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_H264; +#endif + fmt.fmt.pix.field = V4L2_FIELD_NONE; + + if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) { + err = errno; + warning("v4l2_codec: Setting Pixel Format (%m)\n", err); + return err; + } + + strncpy(fourcc, (char *)&fmt.fmt.pix.pixelformat, 4); + info("v4l2_codec: Selected Camera Mode:\n" + " Width: %d\n" + " Height: %d\n" + " PixFmt: %s\n" + " Field: %d\n", + fmt.fmt.pix.width, + fmt.fmt.pix.height, + fourcc, + fmt.fmt.pix.field); + + return 0; +} + + +static int init_mmap(struct vidsrc_st *st, int fd) +{ + struct v4l2_requestbuffers req; + struct v4l2_buffer buf; + int err; + + memset(&req, 0, sizeof(req)); + memset(&buf, 0, sizeof(buf)); + + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { + err = errno; + warning("v4l2_codec: Requesting Buffer (%m)\n", err); + return err; + } + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) { + err = errno; + warning("v4l2_codec: Querying Buffer (%m)\n", err); + return err; + } + + st->buffer = mmap(NULL, buf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, buf.m.offset); + if (st->buffer == MAP_FAILED) { + err = errno; + warning("v4l2_codec: mmap failed (%m)\n", err); + return err; + } + st->buffer_len = buf.length; + + return 0; +} + + +static int query_buffer(int fd) +{ + struct v4l2_buffer buf; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) + return errno; + + return 0; +} + + +static int start_streaming(int fd) +{ + struct v4l2_buffer buf; + int err; + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(fd, VIDIOC_STREAMON, &buf.type)) { + err = errno; + warning("v4l2_codec: Start Capture (%m)\n", err); + return err; + } + + return 0; +} + + +static void stop_capturing(int fd) +{ + enum v4l2_buf_type type; + + if (fd < 0) + return; + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + xioctl(fd, VIDIOC_STREAMOFF, &type); +} + + +static void enc_destructor(void *arg) +{ + struct videnc_state *st = arg; + + list_unlink(&st->le); +} + + +static void encoders_read(uint32_t rtp_ts, const uint8_t *buf, size_t sz) +{ + struct le *le; + int err; + + for (le = v4l2.encoderl.head; le; le = le->next) { + struct videnc_state *st = le->data; + + err = h264_packetize(rtp_ts, buf, sz, + st->encprm.pktsize, + st->pkth, st->arg); + if (err) { + warning("h264_packetize error (%m)\n", err); + } + } +} + + +static void read_handler(int flags, void *arg) +{ + struct vidsrc_st *st = arg; + struct v4l2_buffer buf; + bool keyframe = false; + struct timeval ts; + uint32_t rtp_ts; + int err; + + if (flags & FD_EXCEPT) { + warning("v4l2_codec: device error\n"); + return; + } + + memset(&buf, 0, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + + if (-1 == xioctl(st->fd, VIDIOC_DQBUF, &buf)) { + err = errno; + warning("v4l2_codec: Retrieving Frame (%m)\n", err); + return; + } + + + { + struct mbuf mb = {0,0,0,0}; + struct h264_hdr hdr; + + mb.buf = st->buffer; + mb.pos = 4; + mb.end = buf.bytesused - 4; + mb.size = buf.bytesused; + + err = h264_hdr_decode(&hdr, &mb); + if (err) { + warning("could not decode H.264 header\n"); + } + else { + keyframe = h264_is_keyframe(hdr.type); + if (keyframe) { + ++st->stats.n_key; + } + else + ++st->stats.n_delta; + } + } + + ts = buf.timestamp; + rtp_ts = (90000ULL * (1000000*ts.tv_sec + ts.tv_usec)) / 1000000; + +#if 0 + debug("v4l2_codec: %s frame captured at %ldsec, %ldusec (%zu bytes)\n", + keyframe ? "KEY" : " ", + buf.timestamp.tv_sec, buf.timestamp.tv_usec, + (size_t)buf.bytesused); +#endif + + /* pass the frame to the encoders */ + encoders_read(rtp_ts, st->buffer, buf.bytesused); + + err = query_buffer(st->fd); + if (err) { + warning("v4l2_codec: query_buffer failed (%m)\n", err); + } +} + + +static int open_encoder(struct vidsrc_st *st, const char *device, + unsigned width, unsigned height) +{ + int err; + + debug("v4l2_codec: opening video-encoder device (device=%s)\n", + device); + + st->fd = open(device, O_RDWR); + if (st->fd == -1) { + err = errno; + warning("Opening video device (%m)\n", err); + goto out; + } + + err = print_caps(st->fd, width, height); + if (err) + goto out; + + err = init_mmap(st, st->fd); + if (err) + goto out; + + err = query_buffer(st->fd); + if (err) + goto out; + + err = start_streaming(st->fd); + if (err) + goto out; + + err = fd_listen(st->fd, FD_READ, read_handler, st); + if (err) + goto out; + +out: + return err; +} + + +static int encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *st; + int err = 0; + (void)fmtp; + + if (!vesp || !vc || !prm || !pkth) + return EINVAL; + + if (*vesp) + return 0; + + st = mem_zalloc(sizeof(*st), enc_destructor); + if (!st) + return ENOMEM; + + st->encprm = *prm; + st->pkth = pkth; + st->arg = arg; + + list_append(&v4l2.encoderl, &st->le, st); + + info("v4l2_codec: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, prm->fps, prm->bitrate, prm->pktsize); + + if (err) + mem_deref(st); + else + *vesp = st; + + return err; +} + + +/* note: dummy function, the input is unused */ +static int encode_packet(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + (void)st; + (void)update; + (void)frame; + return 0; +} + + +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; + static const uint8_t h264_level_idc = 0x0c; + (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 void src_destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + if (st->fd >=0 ) { + info("v4l2_codec: encoder stats" + " (keyframes:%u, deltaframes:%u)\n", + st->stats.n_key, st->stats.n_delta); + } + + stop_capturing(st->fd); + + if (st->buffer) + munmap(st->buffer, st->buffer_len); + + if (st->fd >= 0) { + fd_close(st->fd); + close(st->fd); + } +} + + +static int src_alloc(struct vidsrc_st **stp, const 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)prm; + (void)fmt; + (void)errorh; + (void)arg; + + if (!stp || !size || !frameh) + return EINVAL; + + if (!str_isset(dev)) + dev = "/dev/video0"; + + debug("v4l2_codec: video-source alloc (device=%s)\n", dev); + + st = mem_zalloc(sizeof(*st), src_destructor); + if (!st) + return ENOMEM; + + st->vs = vs; + + err = open_encoder(st, dev, size->w, size->h); + if (err) + goto out; + +out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static struct vidcodec h264 = { + LE_INIT, + NULL, + "H264", + "packetization-mode=0", + NULL, + encode_update, + encode_packet, + NULL, + NULL, + h264_fmtp_enc, + h264_fmtp_cmp, +}; + + +static int module_init(void) +{ + info("v4l2_codec inited\n"); + + vidcodec_register(baresip_vidcodecl(), &h264); + return vidsrc_register(&vidsrc, baresip_vidsrcl(), + "v4l2_codec", src_alloc, NULL); +} + + +static int module_close(void) +{ + vidsrc = mem_deref(vidsrc); + vidcodec_unregister(&h264); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2_codec) = { + "v4l2_codec", + "vidcodec", + module_init, + module_close +}; diff --git a/modules/vidbridge/disp.c b/modules/vidbridge/disp.c new file mode 100644 index 0000000..be1e8e5 --- /dev/null +++ b/modules/vidbridge/disp.c @@ -0,0 +1,94 @@ +/** + * @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); +} + + +int vidbridge_disp_alloc(struct vidisp_st **stp, const 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 = 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..5e7d08a --- /dev/null +++ b/modules/vidbridge/src.c @@ -0,0 +1,93 @@ +/** + * @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); +} + + +int vidbridge_src_alloc(struct vidsrc_st **stp, const 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 = 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..557e175 --- /dev/null +++ b/modules/vidbridge/vidbridge.c @@ -0,0 +1,78 @@ +/** + * @file vidbridge.c Video bridge + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidbridge.h" + + +/** + * @defgroup vidbridge vidbridge + * + * Video bridge module + * + * This module can be used to connect two video devices together, + * so that all output to VIDISP device is bridged as the input to + * a VIDSRC device. + * + * Sample config: + * + \verbatim + video_display vidbridge,pseudo0 + video_source vidbridge,pseudo0 + \endverbatim + */ + + +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, baresip_vidispl(), + "vidbridge", vidbridge_disp_alloc, + NULL, vidbridge_disp_display, 0); + if (err) + return err; + + err = vidsrc_register(&vidsrc, baresip_vidsrcl(), + "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..0fbf68e --- /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 { + const struct vidsrc *vs; /* inheritance (1st) */ + + struct le le; + struct vidisp_st *vidisp; + char *device; + vidsrc_frame_h *frameh; + void *arg; +}; + + +struct vidisp_st { + const 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, const 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, const 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/vidinfo/module.mk b/modules/vidinfo/module.mk new file mode 100644 index 0000000..a9cb4b0 --- /dev/null +++ b/modules/vidinfo/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vidinfo +$(MOD)_SRCS += vidinfo.c panel.c +$(MOD)_LFLAGS += $(shell pkg-config --libs cairo) +$(MOD)_CFLAGS += $(shell pkg-config --cflags cairo) + +include mk/mod.mk diff --git a/modules/vidinfo/panel.c b/modules/vidinfo/panel.c new file mode 100644 index 0000000..93c475b --- /dev/null +++ b/modules/vidinfo/panel.c @@ -0,0 +1,289 @@ +/** + * @file panel.c Video-info filter -- panel + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidinfo.h" + + +static void rrd_append(struct panel *panel, uint64_t val) +{ + if (!panel) + return; + + panel->rrdv[panel->rrdc++] = val; + panel->rrd_sum += val; + + if (panel->rrdc >= panel->rrdsz) { + panel->rrdc = 0; + panel->rrd_sum = 0; + } +} + + +static int rrd_get_average(struct panel *panel, uint64_t *average) +{ + if (!panel->rrdc) + return ENOENT; + + *average = panel->rrd_sum / panel->rrdc; + + return 0; +} + + +static void tmr_handler(void *arg) +{ + struct panel *panel = arg; + uint64_t now = tmr_jiffies(); + + tmr_start(&panel->tmr, 2000, tmr_handler, panel); + + if (panel->ts) { + panel->fps = 1000.0 * panel->nframes / (now - panel->ts); + } + panel->nframes = 0; + + panel->ts = now; +} + + +static void destructor(void *arg) +{ + struct panel *panel = arg; + + tmr_cancel(&panel->tmr); + mem_deref(panel->label); + mem_deref(panel->rrdv); + + if (panel->cr) + cairo_destroy(panel->cr); + if (panel->surface) + cairo_surface_destroy(panel->surface); +} + + +int panel_alloc(struct panel **panelp, const char *label, + unsigned yoffs, int width, int height) +{ + struct panel *panel; + int err; + + if (!panelp || !width || !height) + return EINVAL; + + panel = mem_zalloc(sizeof(*panel), destructor); + if (!panel) + return ENOMEM; + + err = str_dup(&panel->label, label); + if (err) + goto out; + + panel->size.w = width; + panel->size.h = height; + panel->yoffs = yoffs; + panel->xoffs = TEXT_WIDTH; + + panel->size_text.w = TEXT_WIDTH; + panel->size_text.h = height; + + panel->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + panel->size_text.w, + panel->size_text.h); + panel->cr = cairo_create(panel->surface); + if (!panel->surface || !panel->cr) { + warning("vidinfo: cairo error\n"); + return ENOMEM; + } + + cairo_select_font_face (panel->cr, "Hyperfont", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size (panel->cr, height-2); + + panel->rrdc = 0; + panel->rrdsz = (width - TEXT_WIDTH) / 2; + panel->rrdv = mem_reallocarray(NULL, panel->rrdsz, + sizeof(*panel->rrdv), NULL); + if (!panel->rrdv) { + err = ENOMEM; + goto out; + } + + tmr_start(&panel->tmr, 0, tmr_handler, panel); + + info("new panel '%s' (%u x %u) with RRD size %u\n", + label, width, height, panel->rrdsz); + + out: + if (err) + mem_deref(panel); + else + *panelp = panel; + + return err; +} + + +static void overlay(struct vidframe *dst, unsigned yoffs, struct vidframe *src) +{ + uint8_t *pdst, *psrc; + unsigned x, y; + + pdst = dst->data[0] + yoffs * dst->linesize[0]; + psrc = src->data[0]; + + for (y=0; y<src->size.h; y++) { + + for (x=0; x<src->size.w; x++) { + + /* copy the luma component if visible */ + if (psrc[x] > 16) + pdst[x] = psrc[x]; + } + + pdst += dst->linesize[0]; + psrc += src->linesize[0]; + } +} + + +static int draw_text(struct panel *panel, struct vidframe *frame) +{ + char buf[256]; + int width = panel->size_text.w; + int height = panel->size_text.h; + struct vidframe f; + struct vidframe *f2 = NULL; + cairo_t *cr = panel->cr; + double tx, ty; + int err; + + tx = 1; + ty = height - 3; + + /* draw background */ + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_fill (cr); + + /* Draw text */ + if (re_snprintf(buf, sizeof(buf), "%s %2.2f fps", + panel->label, panel->fps) < 0) + return ENOMEM; + + cairo_move_to (cr, tx, ty); + cairo_text_path (cr, buf); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_fill_preserve (cr); + cairo_set_line_width (cr, 0.6); + cairo_stroke (cr); + + vidframe_init_buf(&f, VID_FMT_RGB32, &panel->size_text, + cairo_image_surface_get_data(panel->surface)); + + err = vidframe_alloc(&f2, frame->fmt, &panel->size_text); + if (err) + goto out; + + vidconv(f2, &f, 0); + + overlay(frame, panel->yoffs, f2); + + out: + mem_deref(f2); + return err; +} + + +static void dim_frame(struct vidframe *frame, unsigned yoffs, unsigned height) +{ + unsigned x, y; + uint8_t *p; + bool lower = (yoffs > 0); + double grade = lower ? 1.00 : (1.00 - PANEL_HEIGHT/100.0); + + p = frame->data[0] + yoffs * frame->linesize[0]; + + /* first dim the background */ + for (y = 0; y < height; y++) { + + for (x = 0; x < frame->size.w; x++) { + p[x] = p[x] * grade; + } + + p += frame->linesize[0]; + + if (lower) + grade -= 0.01; + else + grade += 0.01; + } +} + + +static void draw_graph(struct panel *panel, struct vidframe *frame) +{ + uint64_t avg; + unsigned y0 = panel->yoffs; + size_t i; + + if (rrd_get_average(panel, &avg)) + return; + + for (i=0; i<panel->rrdc; i++) { + + uint64_t value; + double ratio; + unsigned pixels; + unsigned x = panel->xoffs + (unsigned)i * 2; + unsigned y; + value = panel->rrdv[i]; + + ratio = (double)value / (double)avg; + + pixels = (unsigned)((double)panel->size.h * ratio * 0.5f); + + pixels = min(pixels, panel->size.h); + + y = y0 + panel->size.h - pixels; + + vidframe_draw_vline(frame, x, y, pixels, 220, 220, 220); + } +} + + +int panel_draw(struct panel *panel, struct vidframe *frame) +{ + int err; + + if (!panel || !frame) + return EINVAL; + + dim_frame(frame, panel->yoffs, panel->size.h); + + err = draw_text(panel, frame); + if (err) + return err; + draw_graph(panel, frame); + + return 0; +} + + +void panel_add_frame(struct panel *panel, uint64_t pts) +{ + if (!panel) + return; + + if (panel->pts_prev) { + rrd_append(panel, pts - panel->pts_prev); + } + + panel->nframes++; + panel->pts_prev = pts; +} diff --git a/modules/vidinfo/vidinfo.c b/modules/vidinfo/vidinfo.c new file mode 100644 index 0000000..f460a2d --- /dev/null +++ b/modules/vidinfo/vidinfo.c @@ -0,0 +1,177 @@ +/** + * @file vidinfo.c Video-info filter + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vidinfo.h" + + +/** + * @defgroup vidinfo vidinfo + * + * Display video-info overlay on the encode/decode streams + * + * Displays info like framerate and packet timing, this is mainly + * for development and debugging. + */ + + +struct vidinfo_enc { + struct vidfilt_enc_st vf; /* base member (inheritance) */ + + struct panel *panel; +}; + + +struct vidinfo_dec { + struct vidfilt_dec_st vf; /* base member (inheritance) */ + + struct panel *panel; +}; + + +static void encode_destructor(void *arg) +{ + struct vidinfo_enc *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->panel); +} + + +static void decode_destructor(void *arg) +{ + struct vidinfo_dec *st = arg; + + list_unlink(&st->vf.le); + mem_deref(st->panel); +} + + +static int encode_update(struct vidfilt_enc_st **stp, void **ctx, + const struct vidfilt *vf) +{ + struct vidinfo_enc *st; + int err = 0; + + if (!stp || !ctx || !vf) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), encode_destructor); + if (!st) + return ENOMEM; + + 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 vidinfo_dec *st; + int err = 0; + + if (!stp || !ctx || !vf) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), decode_destructor); + if (!st) + return ENOMEM; + + if (err) + mem_deref(st); + else + *stp = (struct vidfilt_dec_st *)st; + + return err; +} + + +static int encode(struct vidfilt_enc_st *_st, struct vidframe *frame) +{ + struct vidinfo_enc *st = (struct vidinfo_enc *)_st; + int err = 0; + + if (!st->panel) { + + unsigned width = frame->size.w; + unsigned height = MIN(PANEL_HEIGHT, frame->size.h); + + err = panel_alloc(&st->panel, "encode", 0, width, height); + if (err) + return err; + } + + panel_add_frame(st->panel, tmr_jiffies()); + + panel_draw(st->panel, frame); + + return 0; +} + + +static int decode(struct vidfilt_dec_st *_st, struct vidframe *frame) +{ + struct vidinfo_dec *st = (struct vidinfo_dec *)_st; + int err = 0; + + if (!st->panel) { + + unsigned width = frame->size.w; + unsigned height = MIN(PANEL_HEIGHT, frame->size.h); + unsigned yoffs = frame->size.h - PANEL_HEIGHT; + + err = panel_alloc(&st->panel, "decode", yoffs, width, height); + if (err) + return err; + } + + panel_add_frame(st->panel, tmr_jiffies()); + + panel_draw(st->panel, frame); + + return 0; +} + + +static struct vidfilt vidinfo = { + LE_INIT, "vidinfo", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + vidfilt_register(baresip_vidfiltl(), &vidinfo); + + return 0; +} + + +static int module_close(void) +{ + vidfilt_unregister(&vidinfo); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidinfo) = { + "vidinfo", + "vidfilt", + module_init, + module_close +}; diff --git a/modules/vidinfo/vidinfo.h b/modules/vidinfo/vidinfo.h new file mode 100644 index 0000000..7f540e4 --- /dev/null +++ b/modules/vidinfo/vidinfo.h @@ -0,0 +1,42 @@ +/** + * @file vidinfo.h Video-info filter + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + + +#include <cairo/cairo.h> + + +#define PANEL_HEIGHT 24 +#define TEXT_WIDTH 220 + + +struct panel { + struct vidsz size; + struct vidsz size_text; + unsigned yoffs; + unsigned xoffs; + char *label; + + uint64_t *rrdv; + size_t rrdsz; + size_t rrdc; + uint64_t rrd_sum; + + unsigned nframes; + uint64_t ts; + double fps; + struct tmr tmr; + + uint64_t pts_prev; + + /* cairo backend: */ + cairo_surface_t *surface; + cairo_t *cr; +}; + +int panel_alloc(struct panel **panelp, const char *label, + unsigned yoffs, int width, int height); +void panel_add_frame(struct panel *panel, uint64_t pts); +int panel_draw(struct panel *panel, 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..d99fa49 --- /dev/null +++ b/modules/vidloop/vidloop.c @@ -0,0 +1,505 @@ +/** + * @file vidloop.c Video loop + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#define _BSD_SOURCE 1 +#include <string.h> +#include <time.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +/** + * @defgroup vidloop vidloop + * + * A video-loop module for testing + * + * Simple test module that loops back the video frames from a + * video-source to a video-display, optionally via a video codec. + * + * Example usage without codec: + \verbatim + baresip -e/vidloop + \endverbatim + * + * Example usage with codec: + \verbatim + baresip -e"/vidloop h264" + \endverbatim + */ + + +/** Internal pixel-format */ +#ifndef VIDLOOP_INTERNAL_FMT +#define VIDLOOP_INTERNAL_FMT (VID_FMT_YUV420P) +#endif + + +/** Video Statistics */ +struct vstat { + uint64_t tsamp; + uint32_t frames; + size_t bytes; + uint32_t bitrate; + double efps; + size_t n_intra; +}; + + +/** Video loop */ +struct video_loop { + const struct vidcodec *vc_enc; + const struct vidcodec *vc_dec; + 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; + bool need_conv; + int err; +}; + + +static struct video_loop *gvl; + + +static int display(struct video_loop *vl, struct vidframe *frame) +{ + struct vidframe *frame_filt = NULL; + 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; + + /* Some video decoders keeps the displayed video frame + * in memory and we should not write to that frame. + */ + if (!frame_filt) { + + err = vidframe_alloc(&frame_filt, frame->fmt, + &frame->size); + if (err) + return err; + + vidframe_copy(frame_filt, frame); + + frame = frame_filt; + } + + if (st->vf->dech) + err |= st->vf->dech(st, frame); + } + + if (err) { + warning("vidloop: error in video-filters (%m)\n", err); + } + + /* display frame */ + err = vidisp_display(vl->vidisp, "Video Loop", frame); + if (err == ENODEV) { + info("vidloop: video-display was closed\n"); + vl->vidisp = mem_deref(vl->vidisp); + vl->err = err; + } + + mem_deref(frame_filt); + + return err; +} + + +static int packet_handler(bool marker, uint32_t rtp_ts, + 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; + bool intra; + int err = 0; + (void)rtp_ts; + + 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->vc_dec && vl->dec) { + err = vl->vc_dec->dech(vl->dec, &frame, &intra, + marker, vl->seq++, mb); + if (err) { + warning("vidloop: codec decode: %m\n", err); + goto out; + } + + if (intra) + ++vl->stat.n_intra; + } + + if (vidframe_isvalid(&frame)) { + + 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 != VIDLOOP_INTERNAL_FMT) { + + if (!vl->need_conv) { + info("vidloop: NOTE: pixel-format conversion" + " needed: %s --> %s\n", + vidfmt_name(frame->fmt), + vidfmt_name(VIDLOOP_INTERNAL_FMT)); + vl->need_conv = true; + } + + if (vidframe_alloc(&f2, VIDLOOP_INTERNAL_FMT, &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->vc_enc && vl->enc) { + (void)vl->vc_enc->ench(vl->enc, false, frame); + } + 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->enc); + mem_deref(vl->dec); + mem_deref(vl->vidisp); + list_flush(&vl->filtencl); + list_flush(&vl->filtdecl); +} + + +static int enable_codec(struct video_loop *vl, const char *name) +{ + struct list *vidcodecl = baresip_vidcodecl(); + struct videnc_param prm; + int err; + + prm.fps = vl->cfg.fps; + prm.pktsize = 1480; + prm.bitrate = vl->cfg.bitrate; + prm.max_fs = -1; + + /* Use the first video codec */ + + vl->vc_enc = vidcodec_find_encoder(vidcodecl, name); + if (!vl->vc_enc) { + warning("vidloop: could not find encoder (%s)\n", name); + return ENOENT; + } + + info("vidloop: enabled encoder %s (%u fps, %u bit/s)\n", + vl->vc_enc->name, prm.fps, prm.bitrate); + + vl->vc_dec = vidcodec_find_decoder(vidcodecl, name); + if (!vl->vc_dec) { + warning("vidloop: could not find decoder (%s)\n", name); + return ENOENT; + } + + info("vidloop: enabled decoder %s\n", vl->vc_dec->name); + + err = vl->vc_enc->encupdh(&vl->enc, vl->vc_enc, &prm, NULL, + packet_handler, vl); + if (err) { + warning("vidloop: update encoder failed: %m\n", err); + return err; + } + + if (vl->vc_dec->decupdh) { + err = vl->vc_dec->decupdh(&vl->dec, vl->vc_dec, NULL); + if (err) { + warning("vidloop: update decoder failed: %m\n", err); + return err; + } + } + + return 0; +} + + +static void print_status(struct video_loop *vl) +{ + (void)re_fprintf(stdout, + "\rstatus:" + " [%s] [%s] intra=%zu " + " EFPS=%.1f %u kbit/s \r", + vl->vc_enc ? vl->vc_enc->name : "", + vl->vc_dec ? vl->vc_dec->name : "", + vl->stat.n_intra, + vl->stat.efps, vl->stat.bitrate); + fflush(stdout); +} + + +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; + + if (vl->err) { + info("error in video-loop -- closing (%m)\n", vl->err); + gvl = mem_deref(gvl); + return; + } + + tmr_start(&vl->tmr_bw, 2000, 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 at %u fps\n", + vl->cfg.src_mod, vl->cfg.src_dev, + sz->w, sz->h, vl->cfg.fps); + + prm.orient = VIDORIENT_PORTRAIT; + prm.fps = vl->cfg.fps; + + vl->vsrc = mem_deref(vl->vsrc); + err = vidsrc_alloc(&vl->vsrc, baresip_vidsrcl(), + vl->cfg.src_mod, NULL, &prm, sz, + NULL, vl->cfg.src_dev, vidsrc_frame_handler, + NULL, vl); + if (err) { + warning("vidloop: vidsrc '%s' failed: %m\n", + vl->cfg.src_dev, err); + } + + return err; +} + + +static int video_loop_alloc(struct video_loop **vlp) +{ + 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(baresip_vidfiltl()); 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); + } + } + + info("vidloop: open video display (%s.%s)\n", + vl->cfg.disp_mod, vl->cfg.disp_dev); + + err = vidisp_alloc(&vl->vidisp, baresip_vidispl(), + vl->cfg.disp_mod, NULL, + vl->cfg.disp_dev, 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) +{ + const struct cmd_arg *carg = arg; + struct vidsz size; + struct config *cfg = conf_config(); + const char *codec_name = carg->prm; + int err = 0; + + size.w = cfg->video.width; + size.h = cfg->video.height; + + if (gvl) { + return re_hprintf(pf, "video-loop already running.\n"); + } + + (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); + if (err) { + warning("vidloop: alloc: %m\n", err); + return err; + } + + if (str_isset(codec_name)) { + + err = enable_codec(gvl, codec_name); + if (err) { + gvl = mem_deref(gvl); + return err; + } + + (void)re_hprintf(pf, "%sabled codec: %s\n", + gvl->vc_enc ? "En" : "Dis", + gvl->vc_enc ? gvl->vc_enc->name : ""); + } + + /* Start video source, after codecs are created */ + err = vsrc_reopen(gvl, &size); + if (err) { + gvl = mem_deref(gvl); + return 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[] = { + {"vidloop", 0, CMD_PRM, "Start video-loop <codec>", vidloop_start}, + {"vidloop_stop",0, 0, "Stop video-loop", vidloop_stop }, +}; + + +static int module_init(void) +{ + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + gvl = mem_deref(gvl); + cmd_unregister(baresip_commands(), cmdv); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = { + "vidloop", + "application", + module_init, + module_close, +}; diff --git a/modules/vp8/decode.c b/modules/vp8/decode.c new file mode 100644 index 0000000..4399c0b --- /dev/null +++ b/modules/vp8/decode.c @@ -0,0 +1,290 @@ +/** + * @file vp8/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 bool is_keyframe(struct mbuf *mb) +{ + if (mbuf_get_left(mb) < 1) + return false; + + if (mb->buf[mb->pos] & 0x01) + return false; + + return true; +} + + +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 *intra, 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 || !intra || !mb) + return EINVAL; + + *intra = false; + + 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) { + + if (is_keyframe(mb)) + *intra = true; + + 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/vp8/encode.c b/modules/vp8/encode.c new file mode 100644 index 0000000..83f136b --- /dev/null +++ b/modules/vp8/encode.c @@ -0,0 +1,272 @@ +/** + * @file vp8/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; + videnc_packet_h *pkth; + void *arg; +}; + + +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, + videnc_packet_h *pkth, void *arg) +{ + 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; + ves->pkth = pkth; + ves->arg = arg; + + 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; + vpx_codec_flags_t flags = 0; + + 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; +#ifdef VPX_ERROR_RESILIENT_DEFAULT + cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; +#endif + 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; + } + +#ifdef VPX_CODEC_USE_OUTPUT_PARTITION + flags |= VPX_CODEC_USE_OUTPUT_PARTITION; +#endif + + res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp8_cx_algo, &cfg, + flags); + 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, uint32_t rtp_ts, + 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, rtp_ts, hdr, sizeof(hdr), buf, maxlen, + arg); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, noref, start, partid, picid); + + err |= pkth(marker, rtp_ts, hdr, sizeof(hdr), buf, len, arg); + + return err; +} + + +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + 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 || 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; + uint32_t ts; + + 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; + +#ifdef VPX_FRAME_IS_FRAGMENT + if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) + marker = false; + + if (pkt->data.frame.partition_id >= 0) + partid = pkt->data.frame.partition_id; +#endif + + ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps); + + err = packetize(marker, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, !keyframe, partid, ves->picid, + ts, + ves->pkth, ves->arg); + if (err) + return err; + } + + return 0; +} diff --git a/modules/vp8/module.mk b/modules/vp8/module.mk new file mode 100644 index 0000000..4142af5 --- /dev/null +++ b/modules/vp8/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vp8 +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += vp8.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lvpx + +include mk/mod.mk diff --git a/modules/vp8/sdp.c b/modules/vp8/sdp.c new file mode 100644 index 0000000..8d2ecca --- /dev/null +++ b/modules/vp8/sdp.c @@ -0,0 +1,39 @@ +/** + * @file vp8/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/vp8/vp8.c b/modules/vp8/vp8.c new file mode 100644 index 0000000..18937d3 --- /dev/null +++ b/modules/vp8/vp8.c @@ -0,0 +1,62 @@ +/** + * @file vp8.c VP8 Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vp8.h" + + +/** + * @defgroup vp8 vp8 + * + * The VP8 video codec + * + * This module implements the VP8 video codec that is compatible + * with the WebRTC standard. + * + * References: + * + * http://www.webmproject.org/ + * + * https://tools.ietf.org/html/rfc7741 + */ + + +static struct vp8_vidcodec vp8 = { + .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(baresip_vidcodecl(), (struct vidcodec *)&vp8); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister((struct vidcodec *)&vp8); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vp8) = { + "vp8", + "codec", + module_init, + module_close +}; diff --git a/modules/vp8/vp8.h b/modules/vp8/vp8.h new file mode 100644 index 0000000..c0e262a --- /dev/null +++ b/modules/vp8/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, + videnc_packet_h *pkth, void *arg); +int vp8_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + + +/* 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 *intra, 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/vp9/decode.c b/modules/vp9/decode.c new file mode 100644 index 0000000..f69b240 --- /dev/null +++ b/modules/vp9/decode.c @@ -0,0 +1,287 @@ +/** + * @file vp9/decode.c VP9 Decode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_decoder.h> +#include <vpx/vp8dx.h> +#include "vp9.h" + + +enum { + DECODE_MAXSZ = 524288, +}; + + +struct hdr { + /* header: */ + unsigned i:1; /* I: Picture ID (PID) present */ + unsigned p:1; /* P: Inter-picture predicted layer frame */ + unsigned l:1; /* L: Layer indices present */ + unsigned f:1; /* F: Flexible mode */ + unsigned b:1; /* B: Start of a layer frame */ + unsigned e:1; /* E: End of a layer frame */ + unsigned v:1; /* V: Scalability structure (SS) data present */ + + /* extension fields */ + uint16_t picid; +}; + +struct viddec_state { + vpx_codec_ctx_t ctx; + struct mbuf *mb; + bool ctxup; + bool started; + uint16_t seq; + + unsigned n_frames; + size_t n_bytes; +}; + + +static void destructor(void *arg) +{ + struct viddec_state *vds = arg; + + if (vds->ctxup) { + debug("vp9: decoder stats: frames=%u, bytes=%zu\n", + vds->n_frames, vds->n_bytes); + + vpx_codec_destroy(&vds->ctx); + } + + mem_deref(vds->mb); +} + + +int vp9_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_vp9_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->i = v>>7 & 0x1; + hdr->p = v>>6 & 0x1; + hdr->l = v>>5 & 0x1; + hdr->f = v>>4 & 0x1; + hdr->b = v>>3 & 0x1; + hdr->e = v>>2 & 0x1; + hdr->v = v>>1 & 0x1; + + if (hdr->p) { + warning("vp9: decode: P-bit not supported\n"); + return EPROTO; + } + if (hdr->l) { + warning("vp9: decode: L-bit not supported\n"); + return EPROTO; + } + if (hdr->f) { + warning("vp9: decode: F-bit not supported\n"); + return EPROTO; + } + if (hdr->v) { + warning("vp9: decode: V-bit not supported\n"); + return EPROTO; + } + + 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; + } + } + + return 0; +} + + +static inline bool is_keyframe(const struct mbuf *mb) +{ + vpx_codec_stream_info_t si; + vpx_codec_err_t ret; + + memset(&si, 0, sizeof(si)); + si.sz = sizeof(si); + + ret = vpx_codec_peek_stream_info(&vpx_codec_vp9_dx_algo, + mbuf_buf(mb), + (unsigned int)mbuf_get_left(mb), &si); + if (ret != VPX_CODEC_OK) + return false; + + return si.is_kf; +} + + +static inline int16_t seq_diff(uint16_t x, uint16_t y) +{ + return (int16_t)(y - x); +} + + +int vp9_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, 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 || !intra || !mb) + return EINVAL; + + *intra = false; + + vds->n_bytes += mbuf_get_left(mb); + + err = hdr_decode(&hdr, mb); + if (err) + return err; + +#if 0 + debug("vp9: [%c] header: i=%u start=%u end=%u picid=%u \n", + marker ? 'M' : ' ', hdr.i, hdr.b, hdr.e, hdr.picid); +#endif + + if (hdr.b) { + + if (is_keyframe(mb)) + *intra = true; + + 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("vp9: 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("vp9: 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("vp9: no picture\n"); + goto out; + } + + if (img->fmt != VPX_IMG_FMT_I420) { + warning("vp9: 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; + + ++vds->n_frames; + + out: + mbuf_rewind(vds->mb); + vds->started = false; + + return err; +} diff --git a/modules/vp9/encode.c b/modules/vp9/encode.c new file mode 100644 index 0000000..9de4d73 --- /dev/null +++ b/modules/vp9/encode.c @@ -0,0 +1,317 @@ +/** + * @file vp9/encode.c VP9 Encode + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <vpx/vpx_encoder.h> +#include <vpx/vp8cx.h> +#include "vp9.h" + + +enum { + HDR_SIZE = 3, +}; + + +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; + videnc_packet_h *pkth; + void *arg; + + unsigned n_frames; + unsigned n_key_frames; + size_t n_bytes; +}; + + +static void destructor(void *arg) +{ + struct videnc_state *ves = arg; + + if (ves->ctxup) { + + debug("vp9: encoder stats:" + " frames=%u, key_frames=%u, bytes=%zu\n", + ves->n_frames, + ves->n_key_frames, + ves->n_bytes); + + vpx_codec_destroy(&ves->ctx); + } +} + + +int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + const struct vp9_vidcodec *vp9 = (struct vp9_vidcodec *)vc; + struct videnc_state *ves; + uint32_t max_fs; + (void)vp9; + + 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; + ves->pkth = pkth; + ves->arg = arg; + + max_fs = vp9_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_vp9_cx_algo, &cfg, 0); + if (res) + return EPROTO; + + /* + Profile 0 = 8 bit yuv420p + Profile 1 = 8 bit yuv422/440/444p + Profile 2 = 10/12 bit yuv420p + Profile 3 = 10/12 bit yuv422/440/444p + */ + + cfg.g_profile = 0; + cfg.g_w = size->w; + cfg.g_h = size->h; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = ves->fps; + cfg.rc_target_bitrate = ves->bitrate / 1000; + 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.kf_mode = VPX_KF_AUTO; + + if (ves->ctxup) { + debug("vp9: re-opening encoder\n"); + vpx_codec_destroy(&ves->ctx); + ves->ctxup = false; + } + + res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp9_cx_algo, &cfg, + 0); + if (res) { + warning("vp9: enc init: %s\n", vpx_codec_err_to_string(res)); + return EPROTO; + } + + ves->ctxup = true; + + res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 8); + if (res) { + warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } +#ifdef VP9E_SET_NOISE_SENSITIVITY + res = vpx_codec_control(&ves->ctx, VP9E_SET_NOISE_SENSITIVITY, 0); + if (res) { + warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res)); + } +#endif + + info("vp9: encoder opened, picture size %u x %u\n", size->w, size->h); + + return 0; +} + + +static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool start, bool end, + uint16_t picid) +{ + hdr[0] = 1<<7 | start<<3 | end<<2; + hdr[1] = 1<<7 | (picid>>8 & 0x7f); + hdr[2] = picid & 0xff; +} + + +static int send_packet(struct videnc_state *ves, bool marker, + const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len, + uint32_t rtp_ts) +{ + ves->n_bytes += (hdr_len + pld_len); + + return ves->pkth(marker, rtp_ts, hdr, hdr_len, pld, pld_len, + ves->arg); +} + + +static inline int packetize(struct videnc_state *ves, + bool marker, const uint8_t *buf, size_t len, + size_t maxlen, uint16_t picid, + uint32_t rtp_ts) +{ + uint8_t hdr[HDR_SIZE]; + bool start = true; + int err = 0; + + maxlen -= sizeof(hdr); + + while (len > maxlen) { + + hdr_encode(hdr, start, false, picid); + + err |= send_packet(ves, false, hdr, sizeof(hdr), buf, maxlen, + rtp_ts); + + buf += maxlen; + len -= maxlen; + start = false; + } + + hdr_encode(hdr, start, true, picid); + + err |= send_packet(ves, marker, hdr, sizeof(hdr), buf, len, + rtp_ts); + + return err; +} + + +int vp9_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + vpx_enc_frame_flags_t flags = 0; + vpx_codec_iter_t iter = NULL; + vpx_codec_err_t res; + vpx_image_t *img = NULL; + vpx_img_fmt_t img_fmt; + int err, i; + + if (!ves || !frame) + return EINVAL; + + switch (frame->fmt) { + + case VID_FMT_YUV420P: + img_fmt = VPX_IMG_FMT_I420; + break; + + default: + warning("vp9: pixel format not supported (%s)\n", + vidfmt_name(frame->fmt)); + 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; + } + + ++ves->n_frames; + + if (update) { + /* debug("vp9: picture update\n"); */ + flags |= VPX_EFLAG_FORCE_KF; + } + + img = vpx_img_wrap(NULL, img_fmt, frame->size.w, frame->size.h, + 16, NULL); + if (!img) { + warning("vp9: encoder: could not allocate image\n"); + err = ENOMEM; + goto out; + } + + 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("vp9: enc error: %s\n", vpx_codec_err_to_string(res)); + err = ENOMEM; + goto out; + } + + ++ves->picid; + + for (;;) { + bool marker = true; + const vpx_codec_cx_pkt_t *pkt; + uint32_t ts; + + 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) { + ++ves->n_key_frames; + } + + if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) + marker = false; + + ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps); + + err = packetize(ves, + marker, + pkt->data.frame.buf, + pkt->data.frame.sz, + ves->pktsize, ves->picid, + ts); + if (err) + return err; + } + + out: + if (img) + vpx_img_free(img); + + return err; +} diff --git a/modules/vp9/module.mk b/modules/vp9/module.mk new file mode 100644 index 0000000..41879a2 --- /dev/null +++ b/modules/vp9/module.mk @@ -0,0 +1,14 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := vp9 +$(MOD)_SRCS += decode.c +$(MOD)_SRCS += encode.c +$(MOD)_SRCS += vp9.c +$(MOD)_SRCS += sdp.c +$(MOD)_LFLAGS += -lvpx + +include mk/mod.mk diff --git a/modules/vp9/sdp.c b/modules/vp9/sdp.c new file mode 100644 index 0000000..25a3d12 --- /dev/null +++ b/modules/vp9/sdp.c @@ -0,0 +1,39 @@ +/** + * @file vp9/sdp.c VP9 SDP Functions + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "vp9.h" + + +uint32_t vp9_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 vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + const struct vp9_vidcodec *vp9 = arg; + (void)offer; + + if (!mb || !fmt || !vp9 || !vp9->max_fs) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n", + fmt->id, vp9->max_fs); +} diff --git a/modules/vp9/vp9.c b/modules/vp9/vp9.c new file mode 100644 index 0000000..44a7481 --- /dev/null +++ b/modules/vp9/vp9.c @@ -0,0 +1,64 @@ +/** + * @file vp9.c VP9 video codec + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "vp9.h" + + +/** + * @defgroup vp9 vp9 + * + * The VP9 video codec + * + * This module implements the VP9 video codec that is compatible + * with the WebRTC standard. + * + * Libvpx version 1.3.0 or later is required. + * + * + * References: + * + * http://www.webmproject.org/ + * + * draft-ietf-payload-vp9-02 + */ + + +static struct vp9_vidcodec vp9 = { + .vc = { + .name = "VP9", + .encupdh = vp9_encode_update, + .ench = vp9_encode, + .decupdh = vp9_decode_update, + .dech = vp9_decode, + .fmtp_ench = vp9_fmtp_enc, + }, + .max_fs = 3600 +}; + + +static int module_init(void) +{ + vidcodec_register(baresip_vidcodecl(), (struct vidcodec *)&vp9); + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister((struct vidcodec *)&vp9); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(vp9) = { + "vp9", + "codec", + module_init, + module_close +}; diff --git a/modules/vp9/vp9.h b/modules/vp9/vp9.h new file mode 100644 index 0000000..53899a1 --- /dev/null +++ b/modules/vp9/vp9.h @@ -0,0 +1,30 @@ +/** + * @file vp9.h Private VP9 Interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +struct vp9_vidcodec { + struct vidcodec vc; + uint32_t max_fs; +}; + +/* Encode */ +int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int vp9_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame); + + +/* Decode */ +int vp9_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc, + const char *fmtp); +int vp9_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb); + + +/* SDP */ +uint32_t vp9_max_fs(const char *fmtp); +int vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); 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..5b2c33f --- /dev/null +++ b/modules/vumeter/vumeter.c @@ -0,0 +1,203 @@ +/** + * @file vumeter.c VU-meter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <stdlib.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup vumeter vumeter + * + * Simple ASCII VU-meter for the audio-signal. + * + * The Volume unit (VU) meter module takes the audio-signal as input + * and prints a simple ASCII-art bar for the recording and playback levels. + * It is using the aufilt API to get the audio samples. + */ + + +struct vumeter_enc { + struct aufilt_enc_st af; /* inheritance */ + struct tmr tmr; + double avg_rec; + volatile bool started; +}; + +struct vumeter_dec { + struct aufilt_dec_st af; /* inheritance */ + struct tmr tmr; + double avg_play; + volatile bool started; +}; + + +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 int audio_print_vu(struct re_printf *pf, double *level) +{ + char buf[16]; + size_t res; + double x; + + x = (*level + -AULEVEL_MIN) / -AULEVEL_MIN; + + res = min(sizeof(buf) * x, + sizeof(buf)-1); + + memset(buf, '=', res); + buf[res] = '\0'; + + return re_hprintf(pf, "[%-16s]", buf); +} + + +static void print_vumeter(int pos, int color, double 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); + + if (st->started) + 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); + + if (st->started) + 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; + + tmr_start(&st->tmr, 100, enc_tmr_handler, st); + + *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; + + tmr_start(&st->tmr, 100, dec_tmr_handler, st); + + *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 = (void *)st; + + vu->avg_rec = aulevel_calc_dbov(sampv, *sampc); + vu->started = true; + + return 0; +} + + +static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_dec *vu = (void *)st; + + vu->avg_play = aulevel_calc_dbov(sampv, *sampc); + vu->started = true; + + return 0; +} + + +static struct aufilt vumeter = { + LE_INIT, "vumeter", encode_update, encode, decode_update, decode +}; + + +static int module_init(void) +{ + aufilt_register(baresip_aufiltl(), &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..641ceb0 --- /dev/null +++ b/modules/wincons/wincons.c @@ -0,0 +1,217 @@ +/** + * @file wincons.c Windows console input + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <winsock2.h> +#include <re.h> +#include <baresip.h> + + +/** + * @defgroup wincons wincons + * + * User-Interface (UI) module for Windows Console + */ + + +/** Local constants */ +enum { + RELEASE_VAL = 250 /**< Key release value in [ms] */ +}; + +struct ui_st { + struct tmr tmr; + struct mqueue *mq; + HANDLE hThread; + bool run; + HANDLE hstdin; + DWORD mode; +}; + + +static struct ui_st *wincons; + + +static void destructor(void *arg) +{ + struct ui_st *st = arg; + + /* Restore the console to its previous state */ + if (st->mode) + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), st->mode); + + st->run = false; + WaitForSingleObject(st->hThread, 5000); + CloseHandle(st->hThread); + + tmr_cancel(&st->tmr); + mem_deref(st->mq); +} + + +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) +{ + static struct re_printf pf_stderr = {print_handler, NULL}; + (void)ui; + + ui_input_key(baresip_uis(), key, &pf_stderr); +} + + +static void timeout(void *arg) +{ + struct ui_st *st = arg; + + /* Emulate key-release */ + report_key(st, KEYCODE_REL); +} + + +static DWORD WINAPI input_thread(LPVOID arg) +{ + struct ui_st *st = arg; + + /* Switch to raw mode */ + SetConsoleMode(st->hstdin, 0); + + while (st->run) { + + INPUT_RECORD buf[4]; + DWORD i, count = 0; + + ReadConsoleInput(st->hstdin, buf, ARRAY_SIZE(buf), &count); + + for (i=0; i<count; i++) { + + if (buf[i].EventType != KEY_EVENT) + continue; + + if (buf[i].Event.KeyEvent.bKeyDown) { + + int ch = buf[i].Event.KeyEvent.uChar.AsciiChar; + + if (ch == '\r') + ch = '\n'; + + /* Special handling of 'q' (quit) */ + if (ch == 'q') + st->run = false; + + /* + * The keys are read from a thread so we have + * to send them to the RE main event loop via + * a message queue + */ + if (ch) + mqueue_push(st->mq, ch, NULL); + } + } + } + + 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_st *st; + DWORD threadID; + int err = 0; + + if (!stp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + tmr_init(&st->tmr); + + err = mqueue_alloc(&st->mq, mqueue_handler, st); + if (err) + goto out; + + st->hstdin = GetStdHandle(STD_INPUT_HANDLE); + + /* save the current console mode */ + GetConsoleMode(st->hstdin, &st->mode); + + 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 output_handler(const char *str) +{ + return print_handler(str, str_len(str), NULL); +} + + +static struct ui ui_wincons = { + LE_INIT, + "wincons", + output_handler +}; + + +static int module_init(void) +{ + int err; + + err = ui_alloc(&wincons); + if (err) + return err; + + ui_register(baresip_uis(), &ui_wincons); + + return 0; +} + + +static int module_close(void) +{ + ui_unregister(&ui_wincons); + 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..e987fc7 --- /dev/null +++ b/modules/winwave/play.c @@ -0,0 +1,236 @@ +/** + * @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 WRITE_BUFFERS 4 +#define INC_WPOS(a) ((a) = (((a) + 1) % WRITE_BUFFERS)) + + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + struct dspbuf bufs[WRITE_BUFFERS]; + int pos; + HWAVEOUT waveout; + volatile 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); + + waveOutReset(st->waveout); + + for (i = 0; i < WRITE_BUFFERS; i++) { + waveOutUnprepareHeader(st->waveout, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + waveOutClose(st->waveout); +} + + +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((void *)mb->buf, mb->size/2, 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) + warning("winwave: 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 unsigned int find_dev(const char *name) +{ + WAVEOUTCAPS wic; + unsigned int i, nInDevices = waveOutGetNumDevs(); + + if (!str_isset(name)) + return WAVE_MAPPER; + + for (i=0; i<nInDevices; i++) { + if (waveOutGetDevCaps(i, &wic, + sizeof(WAVEOUTCAPS))==MMSYSERR_NOERROR) { + + if (0 == str_cmp(name, wic.szPname)) { + return i; + } + } + } + + return WAVE_MAPPER; +} + + +static int write_stream_open(struct auplay_st *st, + const struct auplay_prm *prm, + unsigned int dev) +{ + 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, dev, &wfmt, + (DWORD_PTR) waveOutCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + warning("winwave: waveOutOpen: failed %d\n", res); + return EINVAL; + } + + return 0; +} + + +int winwave_play_alloc(struct auplay_st **stp, const struct auplay *ap, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay_st *st; + int i, err; + + if (!stp || !ap || !prm) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("winwave: playback: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = ap; + st->wh = wh; + st->arg = arg; + + err = write_stream_open(st, prm, find_dev(device)); + 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..6240899 --- /dev/null +++ b/modules/winwave/src.c @@ -0,0 +1,226 @@ +/** + * @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 READ_BUFFERS 4 +#define INC_RPOS(a) ((a) = (((a) + 1) % READ_BUFFERS)) + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + struct dspbuf bufs[READ_BUFFERS]; + int pos; + HWAVEIN wavein; + volatile 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); + + for (i = 0; i < READ_BUFFERS; i++) { + waveInUnprepareHeader(st->wavein, &st->bufs[i].wh, + sizeof(WAVEHDR)); + mem_deref(st->bufs[i].mb); + } + + waveInClose(st->wavein); +} + + +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) { + warning("winwave: 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 < (READ_BUFFERS-1)) + add_wave_in(st); + + st->rh((void *)wh->lpData, wh->dwBytesRecorded/2, st->arg); + + waveInUnprepareHeader(st->wavein, wh, sizeof(*wh)); + st->inuse--; + break; + + default: + break; + } +} + + +static unsigned int find_dev(const char *name) +{ + WAVEINCAPS wic; + unsigned int i, nInDevices = waveInGetNumDevs(); + + if (!str_isset(name)) + return WAVE_MAPPER; + + for (i=0; i<nInDevices; i++) { + if (waveInGetDevCaps(i, &wic, + sizeof(WAVEINCAPS))==MMSYSERR_NOERROR) { + + if (0 == str_casecmp(name, wic.szPname)) { + return i; + } + } + } + + return WAVE_MAPPER; +} + + +static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm, + unsigned int dev) +{ + 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, dev, &wfmt, + (DWORD_PTR) waveInCallback, + (DWORD_PTR) st, + CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT); + if (res != MMSYSERR_NOERROR) { + warning("winwave: 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, const 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)errh; + + if (!stp || !as || !prm) + return EINVAL; + + if (prm->fmt != AUFMT_S16LE) { + warning("winwave: source: unsupported sample format (%s)\n", + aufmt_name(prm->fmt)); + return ENOTSUP; + } + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->arg = arg; + + err = read_stream_open(st, prm, find_dev(device)); + + 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..469f709 --- /dev/null +++ b/modules/winwave/winwave.c @@ -0,0 +1,60 @@ +/** + * @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" + + +/** + * @defgroup winwave winwave + * + * Windows audio driver module + * + */ + + +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, baresip_ausrcl(), + "winwave", winwave_src_alloc); + err |= auplay_register(&auplay, baresip_auplayl(), + "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..2a49378 --- /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, const 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, const 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..6ccdb5a --- /dev/null +++ b/modules/x11/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11 +$(MOD)_SRCS += x11.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext +$(MOD)_CFLAGS += -Wno-variadic-macros + +include mk/mod.mk diff --git a/modules/x11/x11.c b/modules/x11/x11.c new file mode 100644 index 0000000..eafb0d6 --- /dev/null +++ b/modules/x11/x11.c @@ -0,0 +1,452 @@ +/** + * @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> + +/* + * DO_REDIRECT has this program handle all of the window manager operations + * and displays a borderless window. That window does not take keyboard + * focus - which means the keyboard input to baresip continues. Clicking + * on the window allows one to drag the window around. + * Blewett + */ +#define DO_REDIRECT 1 + + +/** + * @defgroup x11 x11 + * + * X11 video-display module + */ + + +struct vidisp_st { + const 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; + Atom XwinDeleted; + int button_is_down; + Time last_time; +}; + + +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 close_window(struct vidisp_st *st) +{ + if (st->gc && st->disp) { + XFreeGC(st->disp, st->gc); + st->gc = NULL; + } + + if (st->xshmat && st->disp) { + XShmDetach(st->disp, &st->shm); + } + + if (st->shm.shmaddr != (char *)-1) { + shmdt(st->shm.shmaddr); + st->shm.shmaddr = (char *)-1; + } + + 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); + st->win = 0; + } + + XCloseDisplay(st->disp); + st->disp = NULL; + } +} + + +static void destructor(void *arg) +{ + struct vidisp_st *st = arg; + + if (st->image) { + st->image->data = NULL; + XDestroyImage(st->image); + } + + close_window(st); +} + + +static int create_window(struct vidisp_st *st, const struct vidsz *sz) +{ +#ifdef DO_REDIRECT + XSetWindowAttributes attr; +#endif + 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; + } + +#ifdef DO_REDIRECT + /* + * set override rediect to avoid the "kill window" button + * we need to set masks to allow for mouse tracking, etc. + * to control the window - making us the window manager + */ + attr.override_redirect = true; + attr.event_mask = SubstructureRedirectMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | Button1MotionMask; + + XChangeWindowAttributes(st->disp, st->win, + CWOverrideRedirect | CWEventMask , &attr); +#endif + XClearWindow(st->disp, st->win); + XMapRaised(st->disp, st->win); + + /* + * setup to catch window deletion + */ + st->XwinDeleted = XInternAtom(st->disp, "WM_DELETE_WINDOW", True); + XSetWMProtocols(st->disp, st->win, &st->XwinDeleted, 1); + + 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, const 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 = 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 (!st->disp) + return ENODEV; + + /* + * check for window delete - without blocking + * the switch handles both the override redirect window + * and the "standard" window manager managed window. + */ + while (XPending(st->disp)) { + + XEvent e; + + XNextEvent(st->disp, &e); + + switch (e.type) { + + case ClientMessage: + if ((Atom) e.xclient.data.l[0] == st->XwinDeleted) { + + info("x11: window deleted\n"); + + /* + * we have to bail as all of the display + * pointers are bad. + */ + close_window(st); + return ENODEV; + } + break; + + case ButtonPress: + st->button_is_down = 1; + break; + + case ButtonRelease: + st->button_is_down = 0; + break; + + case MotionNotify: + if (st->button_is_down == 0) + break; + if ((e.xmotion.time - st->last_time) < 32) + break; + + XMoveWindow(st->disp, st->win, + e.xmotion.x_root - 16, + e.xmotion.y_root - 16); + st->last_time = e.xmotion.time; + break; + + default: + break; + } + } + + 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, baresip_vidispl(), + "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..e4f2f7d --- /dev/null +++ b/modules/x11grab/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := x11grab +$(MOD)_SRCS += x11grab.c +$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext +$(MOD)_CFLAGS += -Wno-variadic-macros + +include mk/mod.mk diff --git a/modules/x11grab/x11grab.c b/modules/x11grab/x11grab.c new file mode 100644 index 0000000..d3aa287 --- /dev/null +++ b/modules/x11grab/x11grab.c @@ -0,0 +1,223 @@ +/** + * @file x11grab.c X11 grabbing video-source + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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> + + +/** + * @defgroup x11grab x11grab + * + * X11 window-grabbing video-source module + * + * + * XXX: add option to select a specific X window and x,y offset + */ + + +struct vidsrc_st { + const 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); +} + + +static int alloc(struct vidsrc_st **stp, const 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 = 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, baresip_vidsrcl(), + "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..5670b98 --- /dev/null +++ b/modules/zrtp/module.mk @@ -0,0 +1,13 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := zrtp +$(MOD)_SRCS += zrtp.c +$(MOD)_LFLAGS += -lzrtp -lbn +$(MOD)_CFLAGS += -isystem /usr/local/include/libzrtp +$(MOD)_CFLAGS += -Wno-strict-prototypes + +include mk/mod.mk diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c new file mode 100644 index 0000000..28ae48f --- /dev/null +++ b/modules/zrtp/zrtp.c @@ -0,0 +1,661 @@ +/** + * @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> + +#include <string.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/juha-h/libzrtp + * + * Thanks: + * + * Ingo Feinerer + * + * Configuration options: + * + \verbatim + zrtp_hash {yes,no} # Enable SDP zrtp-hash (recommended) + \endverbatim + * + */ + + +enum { + PRESZ = 36 /* Preamble size for TURN/STUN header */ +}; + +struct menc_sess { + zrtp_session_t *zrtp_session; + menc_error_h *errorh; + void *arg; + struct tmr abort_timer; + int err; +}; + +struct menc_media { + struct menc_sess *sess; + struct udp_helper *uh_rtp; + struct udp_helper *uh_rtcp; + struct sa raddr; + void *rtpsock; + void *rtcpsock; + zrtp_stream_t *zrtp_stream; +}; + + +static zrtp_global_t *zrtp_global; +static zrtp_config_t zrtp_config; +static zrtp_zid_t zid; + + +/* RFC 6189, section 8.1. */ +static bool use_sig_hash = true; + + +enum pkt_type { + PKT_TYPE_UNKNOWN = 0, + PKT_TYPE_RTP = 1, + PKT_TYPE_RTCP = 2, + PKT_TYPE_ZRTP = 4 +}; + + +static enum pkt_type get_packet_type(const struct mbuf *mb) +{ + uint8_t b, pt; + uint32_t magic; + + if (mbuf_get_left(mb) < 8) + return PKT_TYPE_UNKNOWN; + + b = mbuf_buf(mb)[0]; + + if (127 < b && b < 192) { + pt = mbuf_buf(mb)[1] & 0x7f; + if (72 <= pt && pt <= 76) + return PKT_TYPE_RTCP; + else + return PKT_TYPE_RTP; + } + else { + memcpy(&magic, &mbuf_buf(mb)[4], 4); + magic = ntohl(magic); + if (magic == ZRTP_PACKETS_MAGIC) + return PKT_TYPE_ZRTP; + } + + return PKT_TYPE_UNKNOWN; +} + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = arg; + + tmr_cancel(&st->abort_timer); + + 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_rtp); + mem_deref(st->uh_rtcp); + mem_deref(st->rtpsock); + mem_deref(st->rtcpsock); + + if (st->zrtp_stream) + zrtp_stream_stop(st->zrtp_stream); +} + + +static void abort_timer_h(void *arg) +{ + struct menc_sess *sess = arg; + + if (sess->errorh) { + sess->errorh(sess->err, sess->arg); + sess->errorh = NULL; + } +} + + +static void abort_call(struct menc_sess *sess) +{ + if (!sess->err) { + sess->err = EPIPE; + tmr_start(&sess->abort_timer, 0, abort_timer_h, sess); + } +} + + +static bool drop_packets(const struct menc_media *st) +{ + return (st)? st->sess->err != 0 : true; +} + + +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; + const char *proto_name = "rtp"; + enum pkt_type ptype = get_packet_type(mb); + + if (drop_packets(st)) + return true; + + length = (unsigned int)mbuf_get_left(mb); + + /* only RTP/RTCP packets should be processed */ + if (ptype == PKT_TYPE_RTCP) { + proto_name = "rtcp"; + s = zrtp_process_rtcp(st->zrtp_stream, + (char *)mbuf_buf(mb), &length); + } + else if (ptype == PKT_TYPE_RTP) { + s = zrtp_process_rtp(st->zrtp_stream, + (char *)mbuf_buf(mb), &length); + } + else + return false; + + if (s != zrtp_status_ok) { + + if (s == zrtp_status_drop) + return true; + + warning("zrtp: send(port=%d): zrtp_process_%s failed" + " (status = %d '%s')\n", + sa_port(dst), proto_name, s, zrtp_log_status2str(s)); + return false; + } + + /* make sure target buffer is large enough */ + if (length > mbuf_get_space(mb)) { + warning("zrtp: zrtp_process_%s: length > space (%u > %u)\n", + proto_name, 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; + const char *proto_name = "srtp"; + enum pkt_type ptype = get_packet_type(mb); + + if (drop_packets(st)) + return true; + + length = (unsigned int)mbuf_get_left(mb); + + if (ptype == PKT_TYPE_RTCP) { + proto_name = "srtcp"; + s = zrtp_process_srtcp(st->zrtp_stream, + (char *)mbuf_buf(mb), &length); + } + else if (ptype == PKT_TYPE_RTP || ptype == PKT_TYPE_ZRTP) { + s = zrtp_process_srtp(st->zrtp_stream, + (char *)mbuf_buf(mb), &length); + } + else + return false; + + if (s != zrtp_status_ok) { + + if (s == zrtp_status_drop) + return true; + + warning("zrtp: recv(port=%d): zrtp_process_%s: %d '%s'\n", + sa_port(src), proto_name, s, zrtp_log_status2str(s)); + return false; + } + + mb->end = mb->pos + length; + + return false; +} + + +static int sig_hash_encode(struct zrtp_stream_t *stream, + struct sdp_media *m) +{ + char buf[ZRTP_SIGN_ZRTP_HASH_LENGTH + 1]; + zrtp_status_t s; + int err = 0; + + s = zrtp_signaling_hash_get(stream, buf, sizeof(buf)); + if (s != zrtp_status_ok) { + warning("zrtp: zrtp_signaling_hash_get: status = %d\n", s); + return EINVAL; + } + + err = sdp_media_set_lattr(m, true, "zrtp-hash", "%s %s", + ZRTP_PROTOCOL_VERSION, buf); + if (err) { + warning("zrtp: sdp_media_set_lattr: %d\n", err); + } + + return err; +} + + +static void sig_hash_decode(struct zrtp_stream_t *stream, + const struct sdp_media *m) +{ + const char *attr_val; + struct pl major, minor, hash; + uint32_t version; + int err; + zrtp_status_t s; + + attr_val = sdp_media_rattr(m, "zrtp-hash"); + if (!attr_val) + return; + + err = re_regex(attr_val, strlen(attr_val), + "[0-9]+.[0-9]2 [0-9a-f]+", + &major, &minor, &hash); + if (err || hash.l < ZRTP_SIGN_ZRTP_HASH_LENGTH) { + warning("zrtp: malformed zrtp-hash attribute, ignoring...\n"); + return; + } + + version = pl_u32(&major) * 100 + pl_u32(&minor); + /* more version checks? */ + if (version < 110) { + warning("zrtp: zrtp-hash: version (%d) is too low, " + "ignoring...", version); + } + + s = zrtp_signaling_hash_set(stream, hash.p, (uint32_t)hash.l); + if (s != zrtp_status_ok) + warning("zrtp: zrtp_signaling_hash_set: status = %d\n", s); +} + + +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; + + if (!sessp || !sdp) + return EINVAL; + + st = mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + st->errorh = errorh; + st->arg = arg; + st->err = 0; + tmr_init(&st->abort_timer); + + s = zrtp_session_init(zrtp_global, NULL, zid, + 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 layer = 10; /* above zero */ + 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; + if (rtpsock) { + st->rtpsock = mem_ref(rtpsock); + err |= udp_register_helper(&st->uh_rtp, rtpsock, layer, + udp_helper_send, udp_helper_recv, st); + } + if (rtcpsock && (rtcpsock != rtpsock)) { + st->rtcpsock = mem_ref(rtcpsock); + err |= udp_register_helper(&st->uh_rtcp, rtcpsock, layer, + 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); + + if (use_sig_hash) { + err = sig_hash_encode(st->zrtp_stream, sdpm); + if (err) + goto out; + } + + 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); + + if (use_sig_hash) + sig_hash_decode(st->zrtp_stream, 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 on_send_packet(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 (drop_packets(st)) + return zrtp_status_ok; + + if (!sa_isset(&st->raddr, SA_ALL)) + return zrtp_status_ok; + + mb = mbuf_alloc(PRESZ + rtp_packet_length); + if (!mb) + return zrtp_status_alloc_fail; + + mb->pos = PRESZ; + (void)mbuf_write_mem(mb, (void *)rtp_packet, rtp_packet_length); + mb->pos = PRESZ; + + err = udp_send_helper(st->rtpsock, &st->raddr, mb, st->uh_rtp); + if (err) { + warning("zrtp: udp_send %u bytes (%m)\n", + rtp_packet_length, err); + } + + mem_deref(mb); + + return zrtp_status_ok; +} + + +static void on_zrtp_secure(zrtp_stream_t *stream) +{ + const struct menc_media *st = zrtp_stream_get_userdata(stream); + const struct menc_sess *sess = st->sess; + zrtp_session_info_t sess_info; + + zrtp_session_get(sess->zrtp_session, &sess_info); + if (!sess_info.sas_is_verified && sess_info.sas_is_ready) { + info("zrtp: verify SAS <%s> <%s> for remote peer %w" + " (type /zrtp %w to verify)\n", + sess_info.sas1.buffer, + sess_info.sas2.buffer, + sess_info.peer_zid.buffer, + (size_t)sess_info.peer_zid.length, + sess_info.peer_zid.buffer, + (size_t)sess_info.peer_zid.length); + } + else if (sess_info.sas_is_verified) { + info("zrtp: secure session with verified remote peer %w\n", + sess_info.peer_zid.buffer, + (size_t)sess_info.peer_zid.length); + } +} + + +static void on_zrtp_security_event(zrtp_stream_t *stream, + zrtp_security_event_t event) +{ + if (event == ZRTP_EVENT_WRONG_SIGNALING_HASH) { + const struct menc_media *st = zrtp_stream_get_userdata(stream); + + warning("zrtp: Attack detected!!! Signaling hash from the " + "zrtp-hash SDP attribute doesn't match the hash of " + "the Hello message. Aborting the call.\n"); + + /* As this was called from zrtp_process_xxx(), we need + a safe shutdown. */ + abort_call(st->sess); + } +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static int verify_sas(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + (void)pf; + + if (str_isset(carg->prm)) { + char rzid[ZRTP_STRING16] = ""; + zrtp_status_t s; + zrtp_string16_t local_zid = ZSTR_INIT_EMPTY(local_zid); + zrtp_string16_t remote_zid = ZSTR_INIT_EMPTY(remote_zid); + + zrtp_zstrncpyc(ZSTR_GV(local_zid), (const char*)zid, + sizeof(zrtp_zid_t)); + + if (str_len(carg->prm) != 24) { + warning("zrtp: invalid remote ZID (%s)\n", carg->prm); + return EINVAL; + } + + (void) str2hex(carg->prm, (int) str_len(carg->prm), + rzid, sizeof(rzid)); + zrtp_zstrncpyc(ZSTR_GV(remote_zid), (const char*)rzid, + sizeof(zrtp_zid_t)); + + s = zrtp_verified_set(zrtp_global, &local_zid, &remote_zid, + true); + if (s == zrtp_status_ok) + info("zrtp: SAS for peer %s verified\n", carg->prm); + else { + warning("zrtp: zrtp_verified_set" + " failed (status = %d)\n", s); + return EINVAL; + } + } + + return 0; +} + + +static void zrtp_log(int level, char *data, int len, int offset) +{ + (void)offset; + + if (level == 1) { + warning("%b\n", data, len); + } + else if (level == 2) { + info("%b\n", data, len); + } + else { + debug("%b\n", data, len); + } +} + + +static const struct cmd cmdv[] = { + {"zrtp", 0, CMD_PRM, "Verify ZRTP SAS <remote ZID>", verify_sas }, +}; + + +static int module_init(void) +{ + zrtp_status_t s; + char config_path[256] = ""; + char zrtp_zid_path[256] = ""; + FILE *f; + int ret, err; + + (void)conf_get_bool(conf_cur(), "zrtp_hash", &use_sig_hash); + + zrtp_log_set_log_engine(zrtp_log); + + zrtp_config_defaults(&zrtp_config); + + str_ncpy(zrtp_config.client_id, "baresip/zrtp", + sizeof(zrtp_config.client_id)); + + zrtp_config.lic_mode = ZRTP_LICENSE_MODE_UNLIMITED; + + zrtp_config.cb.misc_cb.on_send_packet = on_send_packet; + zrtp_config.cb.event_cb.on_zrtp_secure = on_zrtp_secure; + zrtp_config.cb.event_cb.on_zrtp_security_event = + on_zrtp_security_event; + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + ret = re_snprintf(zrtp_config.def_cache_path.buffer, + zrtp_config.def_cache_path.max_length, + "%s/zrtp_cache.dat", config_path); + if (ret < 0) { + warning("zrtp: could not write cache path\n"); + return ENOMEM; + } + zrtp_config.def_cache_path.length = ret; + + if (re_snprintf(zrtp_zid_path, + sizeof(zrtp_zid_path), + "%s/zrtp_zid", config_path) < 0) + return ENOMEM; + if ((f = fopen(zrtp_zid_path, "rb")) != NULL) { + if (fread(zid, sizeof(zid), 1, f) != 1) { + if (feof(f) || ferror(f)) { + warning("zrtp: invalid zrtp_zid file\n"); + } + } + } + else if ((f = fopen(zrtp_zid_path, "wb")) != NULL) { + rand_bytes(zid, sizeof(zid)); + if (fwrite(zid, sizeof(zid), 1, f) != 1) { + warning("zrtp: zrtp_zid file write failed\n"); + } + info("zrtp: generated new persistent ZID (%s)\n", + zrtp_zid_path); + } + else { + err = errno; + warning("zrtp: fopen() %s (%m)\n", zrtp_zid_path, err); + } + if (f) + (void) fclose(f); + + 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(baresip_mencl(), &menc_zrtp); + + debug("zrtp: cache_file: %s\n", zrtp_config.def_cache_path.buffer); + debug(" zid_file: %s\n", zrtp_zid_path); + debug(" zid: %w\n", + zid, sizeof(zid)); + + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + cmd_unregister(baresip_commands(), cmdv); + 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..de6170f --- /dev/null +++ b/rpm/baresip.spec @@ -0,0 +1,51 @@ +%define name baresip +%define ver 0.5.7 +%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..9d0ffec --- /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..483daca --- /dev/null +++ b/share/error.wav diff --git a/share/logo.png b/share/logo.png Binary files differnew file mode 100644 index 0000000..cf8a57c --- /dev/null +++ b/share/logo.png diff --git a/share/message.wav b/share/message.wav Binary files differnew file mode 100644 index 0000000..bd91094 --- /dev/null +++ b/share/message.wav diff --git a/share/notfound.wav b/share/notfound.wav Binary files differnew file mode 100644 index 0000000..285c61d --- /dev/null +++ b/share/notfound.wav diff --git a/share/ring.wav b/share/ring.wav Binary files differnew file mode 100644 index 0000000..09ec1bd --- /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..2a99e58 --- /dev/null +++ b/src/account.c @@ -0,0 +1,703 @@ +/** + * @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->outboundv); i++) + mem_deref(acc->outboundv[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 (msg_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 (msg_param_decode(params, name, &pl)) + return 0; + + *v = pl_u32(&pl); + + return 0; +} + + +/* + * Decode STUN parameters, inspired by RFC 7064 + * + * See RFC 3986: + * + * Use of the format "user:password" in the userinfo field is + * deprecated. + * + */ +static int stunsrv_decode(struct account *acc, const struct sip_addr *aor) +{ + struct pl srv, tmp; + struct uri uri; + int err; + + if (!acc || !aor) + return EINVAL; + + memset(&uri, 0, sizeof(uri)); + + if (0 == msg_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 (0 == msg_param_exists(&aor->params, "stunuser", &tmp)) + err |= param_dstr(&acc->stun_user, &aor->params, "stunuser"); + else if (pl_isset(&uri.user)) + err |= pl_strdup(&acc->stun_user, &uri.user); + else + err |= pl_strdup(&acc->stun_user, &aor->uri.user); + + if (0 == msg_param_exists(&aor->params, "stunpass", &tmp)) + err |= param_dstr(&acc->stun_pass, &aor->params, "stunpass"); + else if (pl_isset(&uri.password)) + err |= pl_strdup(&acc->stun_pass, &uri.password); + else if (acc->auth_pass) + err |= str_dup(&acc->stun_pass, acc->auth_pass); + + 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 == msg_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 list *aucodecl = baresip_aucodecl(); + struct pl tmp; + + if (!acc || !prm) + return EINVAL; + + list_init(&acc->aucodecl); + + if (0 == msg_param_exists(prm, "audio_codecs", &tmp)) { + struct pl acs; + char cname[64]; + unsigned i = 0; + + if (msg_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(aucodecl, + 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 list *vidcodecl = baresip_vidcodecl(); + struct pl tmp; + + if (!acc || !prm) + return EINVAL; + + list_init(&acc->vidcodecl); + + if (0 == msg_param_exists(prm, "video_codecs", &tmp)) { + struct pl vcs; + char cname[64]; + unsigned i = 0; + + if (msg_param_decode(prm, "video_codecs", &vcs)) + return 0; + + while (0 == csl_parse(&vcs, cname, sizeof(cname))) { + struct vidcodec *vc; + + vc = (struct vidcodec *)vidcodec_find(vidcodecl, + 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"); + + acc->pubint = 0; + err |= param_u32(&acc->pubint, &aor->params, "pubint"); + + err |= param_dstr(&acc->regq, &aor->params, "regq"); + + for (i=0; i<ARRAY_SIZE(acc->outboundv); i++) { + + char expr[16] = "outbound"; + + expr[8] = i + 1 + 0x30; + expr[9] = '\0'; + + err |= param_dstr(&acc->outboundv[i], &aor->params, expr); + } + + /* backwards compat */ + if (!acc->outboundv[0]) { + err |= param_dstr(&acc->outboundv[0], &aor->params, + "outbound"); + } + + err |= param_dstr(&acc->sipnat, &aor->params, "sipnat"); + + if (0 == msg_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); +} + + +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: error parsing 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 = re_sdprintf(&acc->auth_pass, "%H", + uri_password_unescape, + &acc->laddr.uri.password); + if (err) + goto out; + } + else if (0 == msg_param_decode(&acc->laddr.params, "auth_pass", &pl)) { + err = pl_strdup(&acc->auth_pass, &pl); + if (err) + goto out; + } + + err = stunsrv_decode(acc, &acc->laddr); + if (err) + goto out; + + if (acc->mnatid) { + acc->mnat = mnat_find(baresip_mnatl(), acc->mnatid); + if (!acc->mnat) { + warning("account: medianat not found: `%s'\n", + acc->mnatid); + } + } + + if (acc->mencid) { + acc->menc = menc_find(baresip_mencl(), 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; +} + + +int account_set_auth_pass(struct account *acc, const char *pass) +{ + if (!acc) + return EINVAL; + + acc->auth_pass = mem_deref(acc->auth_pass); + + if (pass) + return str_dup(&acc->auth_pass, pass); + + return 0; +} + + +/** + * 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 : baresip_aucodecl(); +} + + +#ifdef USE_VIDEO +struct list *account_vidcodecl(const struct account *acc) +{ + return (acc && !list_isempty(&acc->vidcodecl)) + ? (struct list *)&acc->vidcodecl : baresip_vidcodecl(); +} +#endif + + +struct sip_addr *account_laddr(const struct account *acc) +{ + return acc ? (struct sip_addr *)&acc->laddr : NULL; +} + + +uint32_t account_regint(const struct account *acc) +{ + return acc ? acc->regint : 0; +} + + +uint32_t account_pubint(const struct account *acc) +{ + return acc ? acc->pubint : 0; +} + + +enum answermode account_answermode(const struct account *acc) +{ + return acc ? acc->answermode : ANSWERMODE_MANUAL; +} + + +const char *account_aor(const struct account *acc) +{ + return acc ? acc->aor : NULL; +} + + +/** + * Get the authentication username of an account + * + * @param acc User-Agent account + * + * @return Authentication username + */ +const char *account_auth_user(const struct account *acc) +{ + return acc ? acc->auth_user : NULL; +} + + +/** + * Get the SIP authentication password of an account + * + * @param acc User-Agent account + * + * @return Authentication password + */ +const char *account_auth_pass(const struct account *acc) +{ + return acc ? acc->auth_pass : NULL; +} + + +/** + * Get the outbound SIP server of an account + * + * @param acc User-Agent account + * @param ix Index starting at zero + * + * @return Outbound SIP proxy, NULL if not configured + */ +const char *account_outbound(const struct account *acc, unsigned ix) +{ + if (!acc || ix >= ARRAY_SIZE(acc->outboundv)) + return NULL; + + return acc->outboundv[ix]; +} + + +/** + * Get the audio packet-time (ptime) of an account + * + * @param acc User-Agent account + * + * @return Packet-time (ptime) + */ +uint32_t account_ptime(const struct account *acc) +{ + return acc ? acc->ptime : 0; +} + + +/** + * Get the STUN username of an account + * + * @param acc User-Agent account + * + * @return STUN username + */ +const char *account_stun_user(const struct account *acc) +{ + return acc ? acc->stun_user : NULL; +} + + +/** + * Get the STUN password of an account + * + * @param acc User-Agent account + * + * @return STUN password + */ +const char *account_stun_pass(const struct account *acc) +{ + return acc ? acc->stun_pass : NULL; +} + + +/** + * Get the STUN hostname of an account + * + * @param acc User-Agent account + * + * @return STUN hostname + */ +const char *account_stun_host(const struct account *acc) +{ + return acc ? acc->stun_host : 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->outboundv); i++) { + if (acc->outboundv[i]) { + err |= re_hprintf(pf, " outbound%d: %s\n", + i+1, acc->outboundv[i]); + } + } + err |= re_hprintf(pf, " ptime: %u\n", acc->ptime); + err |= re_hprintf(pf, " regint: %u\n", acc->regint); + err |= re_hprintf(pf, " pubint: %u\n", acc->pubint); + 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..35076ed --- /dev/null +++ b/src/aucodec.c @@ -0,0 +1,66 @@ +/** + * @file aucodec.c Audio Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** + * Register an Audio Codec + * + * @param aucodecl List of audio-codecs + * @param ac Audio Codec object + */ +void aucodec_register(struct list *aucodecl, struct aucodec *ac) +{ + if (!aucodecl || !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 struct list *aucodecl, + const char *name, uint32_t srate, + uint8_t ch) +{ + struct le *le; + + for (le=list_head(aucodecl); 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; +} diff --git a/src/audio.c b/src/audio.c new file mode 100644 index 0000000..17eb5f6 --- /dev/null +++ b/src/audio.c @@ -0,0 +1,2081 @@ +/** + * @file src/audio.c Audio stream + * + * Copyright (C) 2010 Creytiv.com + * \ref GenericAudioStream + */ +#define _DEFAULT_SOURCE 1 +#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 = 3*1920 /* Max samples, 48000Hz 2ch at 60ms */ +}; + + +/** + * Audio transmit/encoder + * + * + \verbatim + + Processing encoder pipeline: + + . .-------. .-------. .--------. .--------. .--------. + | | | | | | | | | | | + |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP + | | | | | | | | | | | + ' '-------' '-------' '--------' '--------' '--------' + + \endverbatim + * + */ +struct autx { + struct ausrc_st *ausrc; /**< Audio Source */ + struct ausrc_prm ausrc_prm; /**< Audio Source parameters */ + const struct aucodec *ac; /**< Current audio encoder */ + struct auenc_state *enc; /**< Audio encoder state (optional) */ + struct aubuf *aubuf; /**< Packetize outgoing stream */ + size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */ + volatile bool aubuf_started; + 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]; /**< Audio source device name */ + int16_t *sampv; /**< Sample buffer */ + int16_t *sampv_rs; /**< Sample buffer for resampler */ + uint32_t ptime; /**< Packet time for sending */ + uint64_t ts_ext; /**< Ext. Timestamp for outgoing RTP */ + uint32_t ts_base; + uint32_t ts_tel; /**< Timestamp for Telephony Events */ + size_t psize; /**< Packet size for sending */ + bool marker; /**< Marker bit for outgoing RTP */ + bool muted; /**< Audio source is muted */ + int cur_key; /**< Currently transmitted event */ + enum aufmt src_fmt; + bool need_conv; + + struct { + uint64_t aubuf_overrun; + uint64_t aubuf_underrun; + } stats; + +#ifdef HAVE_PTHREAD + union { + struct { + pthread_t tid;/**< Audio transmit thread */ + bool run; /**< Audio transmit thread running */ + } thr; + } u; +#endif +}; + + +/** + * Audio receive/decoder + * + \verbatim + + Processing decoder pipeline: + + .--------. .-------. .--------. .--------. .--------. + |\ | | | | | | | | | | + | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP + |/ | | | | | | | | | | + '--------' '-------' '--------' '--------' '--------' + + \endverbatim + */ +struct aurx { + struct auplay_st *auplay; /**< Audio Player */ + struct auplay_prm auplay_prm; /**< Audio Player parameters */ + const struct aucodec *ac; /**< Current audio decoder */ + struct audec_state *dec; /**< Audio decoder state (optional) */ + struct aubuf *aubuf; /**< Incoming audio buffer */ + size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */ + volatile bool aubuf_started; + struct auresamp resamp; /**< Optional resampler for DSP */ + struct list filtl; /**< Audio filters in decoding order */ + char device[64]; /**< Audio player device name */ + 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 */ + double level_last; + bool level_set; + enum aufmt play_fmt; + bool need_conv; + struct timestamp_recv ts_recv; + uint64_t n_discard; + + struct { + uint64_t aubuf_overrun; + uint64_t aubuf_underrun; + } stats; +}; + + +/** 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 */ + bool level_enabled; /**< Audio level RTP ext. enabled */ + unsigned extmap_aulevel; /**< ID Range 1-14 inclusive */ + audio_event_h *eventh; /**< Event handler */ + audio_err_h *errh; /**< Audio error handler */ + void *arg; /**< Handler argument */ +}; + + +/* RFC 6464 */ +static const char *uri_aulevel = "urn:ietf:params:rtp-hdrext:ssrc-audio-level"; + + +static double audio_calc_seconds(uint64_t rtp_ts, uint32_t clock_rate) +{ + double timestamp; + + /* convert from RTP clockrate to seconds */ + timestamp = (double)rtp_ts / (double)clock_rate; + + return timestamp; +} + + +static double autx_calc_seconds(const struct autx *autx) +{ + uint64_t dur; + + if (!autx->ac) + return .0; + + dur = autx->ts_ext - autx->ts_base; + + return audio_calc_seconds(dur, autx->ac->crate); +} + + +static double aurx_calc_seconds(const struct aurx *aurx) +{ + uint64_t dur; + + if (!aurx->ac) + return .0; + + dur = timestamp_duration(&aurx->ts_recv); + + return audio_calc_seconds(dur, aurx->ac->crate); +} + + +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: + if (tx->u.thr.run) { + tx->u.thr.run = false; + pthread_join(tx->u.thr.tid, NULL); + } + break; +#endif + 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; +} + + +static inline double calc_ptime(size_t nsamp, uint32_t srate, uint8_t channels) +{ + double ptime; + + ptime = 1000.0 * (double)nsamp / (double)(srate * channels); + + return ptime; +} + + +/** + * Get the DSP samplerate for an audio-codec (exception for G.722 and MPA) + */ +static inline uint32_t get_srate(const struct aucodec *ac) +{ + if (!ac) + return 0; + + return ac->srate; +} + + +/** + * Get the DSP channels for an audio-codec (exception for MPA) + */ +static inline uint32_t get_ch(const struct aucodec *ac) +{ + if (!ac) + return 0; + + return !str_casecmp(ac->name, "MPA") ? 2 : ac->ch; +} + + +static inline uint32_t get_framesize(const struct aucodec *ac, + uint32_t ptime) +{ + if (!ac) + return 0; + + return calc_nsamp(get_srate(ac), get_ch(ac), 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) && get_ch(a) == get_ch(b); +} + + +static int add_audio_codec(struct audio *a, struct sdp_media *m, + struct aucodec *ac) +{ + if (!in_range(&a->cfg.srate, get_srate(ac))) { + debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n", + get_srate(ac), a->cfg.srate.min, a->cfg.srate.max); + return 0; + } + + if (!in_range(&a->cfg.channels, get_ch(ac))) { + debug("audio: skip codec with %uch (audio range %uch-%uch)\n", + get_ch(ac), a->cfg.channels.min, a->cfg.channels.max); + return 0; + } + + if (ac->crate < 8000) { + warning("audio: illegal clock rate %u\n", ac->crate); + return EINVAL; + } + + return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->crate, + ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false, + "%s", ac->fmtp); +} + + +static int append_rtpext(struct audio *au, struct mbuf *mb, + int16_t *sampv, size_t sampc) +{ + uint8_t data[1]; + double level; + int err; + + /* audio level must be calculated from the audio samples that + * are actually sent on the network. */ + level = aulevel_calc_dbov(sampv, sampc); + + data[0] = (int)-level & 0x7f; + + err = rtpext_encode(mb, au->extmap_aulevel, 1, data); + if (err) { + warning("audio: rtpext_encode failed (%m)\n", err); + return err; + } + + return err; +} + + +/** + * 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 frame_size; /* number of samples per channel */ + size_t sampc_rtp; + size_t len; + size_t ext_len = 0; + int err; + + if (!tx->ac || !tx->ac->ench) + return; + + tx->mb->pos = tx->mb->end = STREAM_PRESZ; + + if (a->level_enabled) { + + /* skip the extension header */ + tx->mb->pos += RTPEXT_HDR_SIZE; + + err = append_rtpext(a, tx->mb, sampv, sampc); + if (err) + return; + + ext_len = tx->mb->pos - STREAM_PRESZ; + + /* write the Extension header at the beginning */ + tx->mb->pos = STREAM_PRESZ; + + err = rtpext_hdr_encode(tx->mb, ext_len - RTPEXT_HDR_SIZE); + if (err) + return; + + tx->mb->pos = STREAM_PRESZ + ext_len; + tx->mb->end = STREAM_PRESZ + ext_len; + } + + len = mbuf_get_space(tx->mb); + + err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc); + if ((err & 0xffff0000) == 0x00010000) { + /* MPA needs some special treatment here */ + tx->ts_ext = err & 0xffff; + err = 0; + } + else 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 + ext_len + len; + + if (mbuf_get_left(tx->mb)) { + + uint32_t rtp_ts = tx->ts_ext & 0xffffffff; + + if (len) { + err = stream_send(a->strm, ext_len!=0, tx->marker, -1, + rtp_ts, tx->mb); + if (err) + goto out; + } + } + + /* Convert from audio samplerate to RTP clockrate */ + sampc_rtp = sampc * tx->ac->crate / tx->ac->srate; + + /* The RTP clock rate used for generating the RTP timestamp is + * independent of the number of channels and the encoding + * However, MPA support variable packet durations. Thus, MPA + * should update the ts according to its current internal state. + */ + frame_size = sampc_rtp / get_ch(tx->ac); + + tx->ts_ext += (uint32_t)frame_size; + + 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; + size_t sz; + size_t num_bytes; + struct le *le; + int err = 0; + + sz = aufmt_sample_size(tx->src_fmt); + if (!sz) + return; + + num_bytes = tx->psize; + sampc = tx->psize / sz; + + /* timed read from audio-buffer */ + + if (tx->src_fmt == AUFMT_S16LE) { + + aubuf_read(tx->aubuf, (uint8_t *)tx->sampv, num_bytes); + } + else { + /* Convert from ausrc format to 16-bit format */ + + void *tmp_sampv; + + if (!tx->need_conv) { + info("audio: NOTE: source sample conversion" + " needed: %s --> %s\n", + aufmt_name(tx->src_fmt), aufmt_name(AUFMT_S16LE)); + tx->need_conv = true; + } + + tmp_sampv = mem_zalloc(num_bytes, NULL); + if (!tmp_sampv) + return; + + aubuf_read(tx->aubuf, tmp_sampv, num_bytes); + + auconv_to_s16(sampv, tx->src_fmt, tmp_sampv, sampc); + + mem_deref(tmp_sampv); + } + + /* 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 = (uint32_t)tx->ts_ext; + + fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt); + if (!fmt) + return; + + tx->mb->pos = STREAM_PRESZ; + err = stream_send(a->strm, false, 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 + * + * @note The sample format is set in rx->play_fmt + * + * @param buf Buffer to fill with audio samples + * @param sz Number of bytes in buffer + * @param arg Handler argument + */ +static void auplay_write_handler(void *sampv, size_t sampc, void *arg) +{ + struct aurx *rx = arg; + size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt); + + if (rx->aubuf_started && aubuf_cur_size(rx->aubuf) < num_bytes) { + + ++rx->stats.aubuf_underrun; + + debug("audio: rx aubuf underrun (total %llu)\n", + rx->stats.aubuf_underrun); + } + + aubuf_read(rx->aubuf, sampv, num_bytes); +} + + +/** + * 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 void *sampv, size_t sampc, void *arg) +{ + struct audio *a = arg; + struct autx *tx = &a->tx; + size_t num_bytes = sampc * aufmt_sample_size(tx->src_fmt); + + if (tx->muted) + memset((void *)sampv, 0, num_bytes); + + if (aubuf_cur_size(tx->aubuf) >= tx->aubuf_maxsz) { + + ++tx->stats.aubuf_overrun; + + debug("audio: tx aubuf overrun (total %llu)\n", + tx->stats.aubuf_overrun); + } + + (void)aubuf_write(tx->aubuf, sampv, num_bytes); + + tx->aubuf_started = true; + + if (a->cfg.txmode == AUDIO_MODE_POLL) { + unsigned i; + + for (i=0; i<16; i++) { + + if (aubuf_cur_size(tx->aubuf) < tx->psize) + break; + + 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) { + sampc = rx->ac->srate * rx->ac->ch * rx->ptime / 1000; + + 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; + } + + if (aubuf_cur_size(rx->aubuf) >= rx->aubuf_maxsz) { + + ++rx->stats.aubuf_overrun; + + debug("audio: rx aubuf overrun (total %llu)\n", + rx->stats.aubuf_overrun); + } + + if (rx->play_fmt == AUFMT_S16LE) { + err = aubuf_write_samp(rx->aubuf, sampv, sampc); + if (err) + goto out; + } + else { + + /* Convert from 16-bit to auplay format */ + + void *tmp_sampv; + size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt); + + if (!rx->need_conv) { + info("audio: NOTE: playback sample conversion" + " needed: %s --> %s\n", + aufmt_name(AUFMT_S16LE), + aufmt_name(rx->play_fmt)); + rx->need_conv = true; + } + + tmp_sampv = mem_zalloc(num_bytes, NULL); + if (!tmp_sampv) + return ENOMEM; + + auconv_from_s16(rx->play_fmt, tmp_sampv, sampv, sampc); + + err = aubuf_write(rx->aubuf, tmp_sampv, num_bytes); + + mem_deref(tmp_sampv); + + if (err) + goto out; + } + + rx->aubuf_started = true; + + out: + return err; +} + + +/* Handle incoming stream data from the network */ +static void stream_recv_handler(const struct rtp_header *hdr, + struct rtpext *extv, size_t extc, + struct mbuf *mb, void *arg) +{ + struct audio *a = arg; + struct aurx *rx = &a->rx; + bool discard = false; + size_t i; + int wrap; + int err; + + if (!mb) + goto out; + + /* Telephone event? */ + if (hdr->pt != rx->pt) { + const struct sdp_format *fmt; + + fmt = sdp_media_lformat(stream_sdpmedia(a->strm), hdr->pt); + + if (fmt && !str_casecmp(fmt->name, "telephone-event")) { + 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; + } + + /* RFC 5285 -- A General Mechanism for RTP Header Extensions */ + for (i=0; i<extc; i++) { + + if (extv[i].id == a->extmap_aulevel) { + + a->rx.level_last = -(double)(extv[i].data[0] & 0x7f); + a->rx.level_set = true; + } + else { + info("audio: rtp header ext ignored (id=%u)\n", + extv[i].id); + } + } + + /* Save timestamp for incoming RTP packets */ + + if (rx->ts_recv.is_set) { + + uint64_t ext_last, ext_now; + + ext_last = calc_extended_timestamp(rx->ts_recv.num_wraps, + rx->ts_recv.last); + + ext_now = calc_extended_timestamp(rx->ts_recv.num_wraps, + hdr->ts); + + if (ext_now <= ext_last) { + uint64_t delta; + + delta = ext_last - ext_now; + + warning("audio: [time=%.3f]" + " discard old frame (%.3f seconds old)\n", + aurx_calc_seconds(rx), + audio_calc_seconds(delta, rx->ac->crate)); + + discard = true; + } + } + else { + rx->ts_recv.first = hdr->ts; + rx->ts_recv.last = hdr->ts; + rx->ts_recv.is_set = true; + } + + wrap = timestamp_wrap(hdr->ts, rx->ts_recv.last); + + switch (wrap) { + + case -1: + warning("audio: rtp timestamp wraps backwards" + " (delta = %d) -- discard\n", + (int32_t)(rx->ts_recv.last - hdr->ts)); + discard = true; + break; + + case 0: + break; + + case 1: + ++rx->ts_recv.num_wraps; + break; + + default: + break; + } + + rx->ts_recv.last = hdr->ts; + +#if 0 + re_printf("[time=%.3f] wrap=%d discard=%d\n", + aurx_calc_seconds(rx), wrap, discard); +#endif + + if (discard) { + ++a->rx.n_discard; + 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; + + return err; +} + + +/* + * EBU ACIP (Audio Contribution over IP) Profile + * + * Ref: https://tech.ebu.ch/docs/tech/tech3368.pdf + */ +static int set_ebuacip_params(struct audio *au, uint32_t ptime) +{ + struct sdp_media *sdp = stream_sdpmedia(au->strm); + const struct config_avt *avt = &au->strm->cfg; + char str[64]; + int jbvalue = 0; + int jb_id = 0; + int err = 0; + + /* set ebuacip version fixed value 0 for now. */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "version %i", 0); + + /* set jb option, only one in our case */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "jb %i", jb_id); + + /* define jb value in option */ + if (0 == conf_get_str(conf_cur(), "ebuacip_jb_type",str,sizeof(str))) { + + if (0 == str_cmp(str, "auto")) { + + err |= sdp_media_set_lattr(sdp, false, + "ebuacip", + "jbdef %i auto %d-%d", + jb_id, + avt->jbuf_del.min * ptime, + avt->jbuf_del.max * ptime); + } + else if (0 == str_cmp(str, "fixed")) { + + /* define jb value in option */ + jbvalue = avt->jbuf_del.max * ptime; + + err |= sdp_media_set_lattr(sdp, false, + "ebuacip", + "jbdef %i fixed %d", + jb_id, jbvalue); + } + } + + /* set QOS recomendation use tos / 4 to set DSCP value */ + err |= sdp_media_set_lattr(sdp, false, "ebuacip", "qosrec %u", + avt->rtp_tos / 4); + + /* EBU ACIP FEC:: NOT SET IN BARESIP */ + + return err; +} + + +int audio_alloc(struct audio **ap, const struct stream_param *stream_prm, + 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, bool offerer, + audio_event_h *eventh, audio_err_h *errh, void *arg) +{ + struct audio *a; + struct autx *tx; + struct aurx *rx; + struct le *le; + int err; + (void)offerer; + + 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; + + tx->src_fmt = cfg->audio.src_fmt; + rx->play_fmt = cfg->audio.play_fmt; + + err = stream_alloc(&a->strm, stream_prm, &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; + + if (cfg->audio.level && offerer) { + + a->extmap_aulevel = 1; + + err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true, + "extmap", + "%u %s", + a->extmap_aulevel, uri_aulevel); + if (err) + goto out; + } + + if (cfg->sdp.ebuacip) { + + err = set_ebuacip_params(a, 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 * sizeof(int16_t), NULL); + rx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL); + if (!tx->mb || !tx->sampv || !rx->sampv) { + err = ENOMEM; + goto out; + } + + err = telev_alloc(&a->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_ext = tx->ts_base = rand_u16(); + 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; + + out: + if (err) + mem_deref(a); + else + *ap = a; + + return err; +} + + +#ifdef HAVE_PTHREAD +static void *tx_thread(void *arg) +{ + struct audio *a = arg; + struct autx *tx = &a->tx; + uint64_t ts = 0; + + while (a->tx.u.thr.run) { + + uint64_t now; + + sys_msleep(4); + + if (!tx->aubuf_started) + continue; + + if (!a->tx.u.thr.run) + break; + + now = tmr_jiffies(); + if (!ts) + ts = now; + + if (ts > now) + continue; + + /* Now is the time to send */ + + if (aubuf_cur_size(tx->aubuf) >= tx->psize) { + + poll_aubuf_tx(a); + } + else { + ++tx->stats.aubuf_underrun; + + debug("audio: thread: tx aubuf underrun" + " (total %llu)\n", tx->stats.aubuf_underrun); + } + + ts += tx->ptime; + } + + return NULL; +} +#endif + + +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 = get_ch(ac); + 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(baresip_aufiltl()); 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 = get_ch(ac); + + 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), get_ch(ac), 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), get_ch(ac), + 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(baresip_auplayl(), NULL)) { + + struct auplay_prm prm; + + prm.srate = srate_dsp; + prm.ch = channels_dsp; + prm.ptime = rx->ptime; + prm.fmt = rx->play_fmt; + + if (!rx->aubuf) { + size_t psize; + size_t sz = aufmt_sample_size(rx->play_fmt); + + psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime); + + rx->aubuf_maxsz = psize * 8; + + err = aubuf_alloc(&rx->aubuf, psize * 1, + rx->aubuf_maxsz); + if (err) + return err; + } + + err = auplay_alloc(&rx->auplay, baresip_auplayl(), + 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; + } + + rx->auplay_prm = prm; + + info("audio: player started with sample format %s\n", + aufmt_name(rx->play_fmt)); + } + + 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 = get_ch(ac); + + 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), get_ch(ac), 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), get_ch(ac)); + if (err) { + warning("audio: could not setup ausrc resampler" + " (%m)\n", err); + return err; + } + } + + /* Start Audio Source */ + if (!tx->ausrc && ausrc_find(baresip_ausrcl(), NULL)) { + + struct ausrc_prm prm; + size_t sz; + + prm.srate = srate_dsp; + prm.ch = channels_dsp; + prm.ptime = tx->ptime; + prm.fmt = tx->src_fmt; + + sz = aufmt_sample_size(tx->src_fmt); + + tx->psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime); + + tx->aubuf_maxsz = tx->psize * 30; + + if (!tx->aubuf) { + err = aubuf_alloc(&tx->aubuf, tx->psize, + tx->aubuf_maxsz); + if (err) + return err; + } + + err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(), + NULL, a->cfg.src_mod, + &prm, tx->device, + ausrc_read_handler, ausrc_error_handler, a); + if (err) { + warning("audio: start_source failed (%s.%s): %m\n", + a->cfg.src_mod, tx->device, err); + return err; + } + + switch (a->cfg.txmode) { +#ifdef HAVE_PTHREAD + case AUDIO_MODE_THREAD: + 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 + + default: + break; + } + + tx->ausrc_prm = prm; + + info("audio: source started with sample format %s\n", + aufmt_name(tx->src_fmt)); + } + + 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(baresip_aufiltl())) { + 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), get_ch(ac)); + + /* Audio source must be stopped first */ + if (reset) { + tx->ausrc = mem_deref(tx->ausrc); + } + + 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, ac->crate, ac->crate); + stream_update_encoder(a->strm, pt_tx); + + telev_set_srate(a->telev, ac->crate); + + 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), get_ch(ac)); + + 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, ac->crate, ac->crate); + + 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 != KEYCODE_REL) { + int event = telev_digit2code(key); + info("audio: send DTMF digit: '%c'\n", key); + + if (event == -1) { + warning("audio: invalid DTMF digit (0x%02x)\n", key); + return EINVAL; + } + + err = telev_send(a->telev, event, false); + } + else if (a->tx.cur_key && a->tx.cur_key != KEYCODE_REL) { + /* 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 source (i.e. Microphone) + * + * @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; +} + + +/** + * Get the mute state of an audio source + * + * @param a Audio stream + * + * @return True if muted, otherwise false + */ +bool audio_ismuted(const struct audio *a) +{ + if (!a) + return false; + + return a->tx.muted; +} + + +static bool extmap_handler(const char *name, const char *value, void *arg) +{ + struct audio *au = arg; + struct sdp_extmap extmap; + int err; + (void)name; + + err = sdp_extmap_decode(&extmap, value); + if (err) { + warning("audio: sdp_extmap_decode error (%m)\n", err); + return false; + } + + if (0 == pl_strcasecmp(&extmap.name, uri_aulevel)) { + + if (extmap.id < RTPEXT_ID_MIN || extmap.id > RTPEXT_ID_MAX) { + warning("audio: extmap id out of range (%u)\n", + extmap.id); + return false; + } + + au->extmap_aulevel = extmap.id; + + err = sdp_media_set_lattr(stream_sdpmedia(au->strm), true, + "extmap", + "%u %s", + au->extmap_aulevel, + uri_aulevel); + if (err) + return false; + + au->level_enabled = true; + info("audio: client-to-mixer audio levels enabled\n"); + } + + return false; +} + + +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 %ums -> %ums\n", + a->tx.ptime, ptime_tx); + + tx->ptime = ptime_tx; + + if (tx->ac) { + tx->psize = 2 * get_framesize(tx->ac, + ptime_tx); + } + } + } + + /* Client-to-Mixer Audio Level Indication */ + if (a->cfg.level) { + sdp_media_rattr_apply(stream_sdpmedia(a->strm), + "extmap", + extmap_handler, a); + } +} + + +/** + * Get the last value of the audio level from incoming RTP packets + * + * @param au Audio object + * @param levelp Pointer to where to write audio level value + * + * @return 0 if success, otherwise errorcode + */ +int audio_level_get(const struct audio *au, double *levelp) +{ + if (!au) + return EINVAL; + + if (!au->level_enabled) + return ENOTSUP; + + if (!au->rx.level_set) + return ENOENT; + + if (levelp) + *levelp = au->rx.level_last; + + return 0; +} + + +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), get_ch(ac)); +} + + +int audio_debug(struct re_printf *pf, const struct audio *a) +{ + const struct autx *tx; + const struct aurx *rx; + size_t sztx, szrx; + int err; + + if (!a) + return 0; + + tx = &a->tx; + rx = &a->rx; + + sztx = aufmt_sample_size(tx->src_fmt); + szrx = aufmt_sample_size(rx->play_fmt); + + err = re_hprintf(pf, "\n--- Audio stream ---\n"); + + err |= re_hprintf(pf, " tx: %H ptime=%ums\n", + aucodec_print, tx->ac, + tx->ptime); + err |= re_hprintf(pf, " aubuf: %H" + " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n", + aubuf_debug, tx->aubuf, + calc_ptime(aubuf_cur_size(tx->aubuf)/sztx, + tx->ausrc_prm.srate, + tx->ausrc_prm.ch), + calc_ptime(tx->aubuf_maxsz/sztx, + tx->ausrc_prm.srate, + tx->ausrc_prm.ch), + tx->stats.aubuf_overrun, + tx->stats.aubuf_underrun); + + err |= re_hprintf(pf, " time = %.3f sec\n", + autx_calc_seconds(tx)); + + err |= re_hprintf(pf, + " rx: %H\n" + " ptime=%ums pt=%d\n", + aucodec_print, rx->ac, + rx->ptime, rx->pt); + err |= re_hprintf(pf, " aubuf: %H" + " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n", + aubuf_debug, rx->aubuf, + calc_ptime(aubuf_cur_size(rx->aubuf)/szrx, + rx->auplay_prm.srate, + rx->auplay_prm.ch), + calc_ptime(rx->aubuf_maxsz/szrx, + rx->auplay_prm.srate, + rx->auplay_prm.ch), + rx->stats.aubuf_overrun, + rx->stats.aubuf_underrun + ); + + err |= re_hprintf(pf, " n_discard:%llu\n", + rx->n_discard); + if (rx->level_set) { + err |= re_hprintf(pf, " level %.3f dBov\n", + rx->level_last); + } + if (rx->ts_recv.is_set) { + err |= re_hprintf(pf, " time = %.3f sec\n", + aurx_calc_seconds(rx)); + } + else { + err |= re_hprintf(pf, " time = (not started)\n"); + } + + 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)); +} + + +int audio_set_source(struct audio *au, const char *mod, const char *device) +{ + struct autx *tx; + int err; + + if (!au) + return EINVAL; + + tx = &au->tx; + + /* stop the audio device first */ + tx->ausrc = mem_deref(tx->ausrc); + + err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(), + NULL, mod, &tx->ausrc_prm, device, + ausrc_read_handler, ausrc_error_handler, au); + if (err) { + warning("audio: set_source failed (%s.%s): %m\n", + mod, device, err); + return err; + } + + return 0; +} + + +int audio_set_player(struct audio *au, const char *mod, const char *device) +{ + struct aurx *rx; + int err; + + if (!au) + return EINVAL; + + rx = &au->rx; + + /* stop the audio device first */ + rx->auplay = mem_deref(rx->auplay); + + err = auplay_alloc(&rx->auplay, baresip_auplayl(), + mod, &rx->auplay_prm, device, + auplay_write_handler, rx); + if (err) { + warning("audio: set_player failed (%s.%s): %m\n", + mod, device, err); + return err; + } + + return 0; +} + + +/* + * Reference: + * + * https://www.avm.de/de/Extern/files/x-rtp/xrtpv32.pdf + */ +int audio_print_rtpstat(struct re_printf *pf, const struct audio *a) +{ + const struct stream *s; + const struct rtcp_stats *rtcp; + int srate_tx = 8000; + int srate_rx = 8000; + int err; + + if (!a) + return 1; + + s = a->strm; + rtcp = &s->rtcp_stats; + + if (!rtcp->tx.sent) + return 1; + + if (a->tx.ac) + srate_tx = get_srate(a->tx.ac); + if (a->rx.ac) + srate_rx = get_srate(a->rx.ac); + + err = re_hprintf(pf, + "EX=BareSip;" /* Reporter Identifier */ + "CS=%d;" /* Call Setup in milliseconds */ + "CD=%d;" /* Call Duration in seconds */ + "PR=%u;PS=%u;" /* Packets RX, TX */ + "PL=%d,%d;" /* Packets Lost RX, TX */ + "PD=%d,%d;" /* Packets Discarded, RX, TX */ + "JI=%.1f,%.1f;" /* Jitter RX, TX in timestamp units */ + "IP=%J,%J" /* Local, Remote IPs */ + , + call_setup_duration(s->call) * 1000, + call_duration(s->call), + + s->metric_rx.n_packets, + s->metric_tx.n_packets, + + rtcp->rx.lost, rtcp->tx.lost, + + s->metric_rx.n_err, s->metric_tx.n_err, + + /* timestamp units (ie: 8 ts units = 1 ms @ 8KHZ) */ + 1.0 * rtcp->rx.jit/1000 * (srate_rx/1000), + 1.0 * rtcp->tx.jit/1000 * (srate_tx/1000), + + sdp_media_laddr(s->sdp), + sdp_media_raddr(s->sdp) + ); + + if (a->tx.ac) { + err |= re_hprintf(pf, ";EN=%s/%d", a->tx.ac->name, srate_tx ); + } + if (a->rx.ac) { + err |= re_hprintf(pf, ";DE=%s/%d", a->rx.ac->name, srate_rx ); + } + + return err; +} diff --git a/src/aufilt.c b/src/aufilt.c new file mode 100644 index 0000000..240edee --- /dev/null +++ b/src/aufilt.c @@ -0,0 +1,28 @@ +/** + * @file aufilt.c Audio Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +void aufilt_register(struct list *aufiltl, struct aufilt *af) +{ + if (!aufiltl || !af) + return; + + list_append(aufiltl, &af->le, af); + + info("aufilt: %s\n", af->name); +} + + +void aufilt_unregister(struct aufilt *af) +{ + if (!af) + return; + + list_unlink(&af->le); +} diff --git a/src/aulevel.c b/src/aulevel.c new file mode 100644 index 0000000..bc9a27a --- /dev/null +++ b/src/aulevel.c @@ -0,0 +1,85 @@ +/** + * @file src/aulevel.c Audio level + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <math.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** + * Generic routine to calculate RMS (Root-Mean-Square) from + * a set of signed 16-bit values + * + * \verbatim + + .--------------- + | N-1 + | ----. + | \ + | \ 2 + | | s[n] + | / + | / + _ | ----' + \ | n=0 + \ | ------------ + \| N + + \endverbatim + * + * @param data Array of signed 16-bit values + * @param len Number of values + * + * @return RMS value from 0 to 32768 + */ +static double calc_rms(const int16_t *data, size_t len) +{ + double sum = 0; + size_t i; + + if (!data || !len) + return .0; + + for (i = 0; i < len; i++) { + const double sample = data[i]; + + sum += sample * sample; + } + + return sqrt(sum / (double)len); +} + + +/** + * Calculate the audio level in dBov from a set of audio samples. + * dBov is the level, in decibels, relative to the overload point + * of the system + * + * @param sampv Audio samples + * @param sampc Number of audio samples + * + * @return Audio level expressed in dBov + */ +double aulevel_calc_dbov(const int16_t *sampv, size_t sampc) +{ + static const double peak = 32767.0; + double rms, dbov; + + if (!sampv || !sampc) + return AULEVEL_MIN; + + rms = calc_rms(sampv, sampc) / peak; + + dbov = 20 * log10(rms); + + if (dbov < AULEVEL_MIN) + dbov = AULEVEL_MIN; + else if (dbov > AULEVEL_MAX) + dbov = AULEVEL_MAX; + + return dbov; +} diff --git a/src/auplay.c b/src/auplay.c new file mode 100644 index 0000000..38a7010 --- /dev/null +++ b/src/auplay.c @@ -0,0 +1,109 @@ +/** + * @file auplay.c Audio Player + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +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 auplayl List of Audio Players + * @param name Audio Player name + * @param alloch Allocation handler + * + * @return 0 if success, otherwise errorcode + */ +int auplay_register(struct auplay **app, struct list *auplayl, + 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 auplayl List of Audio Players + * @param name Name of the Audio Player to find + * + * @return Matching Audio Player if found, otherwise NULL + */ +const struct auplay *auplay_find(const struct list *auplayl, const char *name) +{ + struct le *le; + + for (le=list_head(auplayl); 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 auplayl List of Audio Players + * @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, struct list *auplayl, + const char *name, + struct auplay_prm *prm, const char *device, + auplay_write_h *wh, void *arg) +{ + struct auplay *ap; + + ap = (struct auplay *)auplay_find(auplayl, 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..c1ca416 --- /dev/null +++ b/src/ausrc.c @@ -0,0 +1,108 @@ +/** + * @file ausrc.c Audio Source + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +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 ausrcl List of Audio Sources + * @param name Audio Source name + * @param alloch Allocation handler + * + * @return 0 if success, otherwise errorcode + */ +int ausrc_register(struct ausrc **asp, struct list *ausrcl, + 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 ausrcl List of Audio Sources + * @param name Name of the Audio Source to find + * + * @return Matching Audio Source if found, otherwise NULL + */ +const struct ausrc *ausrc_find(const struct list *ausrcl, const char *name) +{ + struct le *le; + + for (le=list_head(ausrcl); 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 ausrcl List of Audio Sources + * @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 list *ausrcl, + 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(ausrcl, name); + if (!as) + return ENOENT; + + return as->alloch(stp, as, ctx, prm, device, rh, errh, arg); +} diff --git a/src/baresip.c b/src/baresip.c new file mode 100644 index 0000000..54a8c2c --- /dev/null +++ b/src/baresip.c @@ -0,0 +1,248 @@ +/** + * @file baresip.c Top-level baresip struct + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/* + * Top-level struct that holds all other subsystems + * (move this instance to main.c later) + */ +static struct baresip { + struct network *net; + struct contacts contacts; + struct commands *commands; + struct player *player; + struct message *message; + struct list mnatl; + struct list mencl; + struct list aucodecl; + struct list ausrcl; + struct list auplayl; + struct list aufiltl; + struct list vidcodecl; + struct list vidsrcl; + struct list vidispl; + struct list vidfiltl; + struct ui_sub uis; +} baresip; + + +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 int insmod_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + int err; + + err = module_load(carg->prm); + if (err) { + return re_hprintf(pf, "insmod: ERROR: could not load module" + " '%s': %m\n", carg->prm, err); + } + + return re_hprintf(pf, "loaded module %s\n", carg->prm); +} + + +static int rmmod_handler(struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = arg; + (void)pf; + + module_unload(carg->prm); + + return 0; +} + + +static const struct cmd corecmdv[] = { + {"quit", 'q', 0, "Quit", cmd_quit }, + {"insmod", 0, CMD_PRM, "Load module", insmod_handler }, + {"rmmod", 0, CMD_PRM, "Unload module", rmmod_handler }, +}; + + +int baresip_init(struct config *cfg, bool prefer_ipv6) +{ + int err; + + if (!cfg) + return EINVAL; + + baresip.net = mem_deref(baresip.net); + + list_init(&baresip.mnatl); + list_init(&baresip.mencl); + list_init(&baresip.aucodecl); + list_init(&baresip.ausrcl); + list_init(&baresip.auplayl); + list_init(&baresip.vidcodecl); + list_init(&baresip.vidsrcl); + list_init(&baresip.vidispl); + list_init(&baresip.vidfiltl); + + /* Initialise Network */ + err = net_alloc(&baresip.net, &cfg->net, + prefer_ipv6 ? AF_INET6 : AF_INET); + if (err) { + warning("ua: network init failed: %m\n", err); + return err; + } + + err = contact_init(&baresip.contacts); + if (err) + return err; + + err = cmd_init(&baresip.commands); + if (err) + return err; + + err = play_init(&baresip.player); + if (err) + return err; + + err = message_init(&baresip.message); + if (err) { + warning("baresip: message init failed: %m\n", err); + return err; + } + + err = cmd_register(baresip.commands, corecmdv, ARRAY_SIZE(corecmdv)); + if (err) + return err; + + return 0; +} + + +void baresip_close(void) +{ + cmd_unregister(baresip.commands, corecmdv); + + baresip.message = mem_deref(baresip.message); + baresip.player = mem_deref(baresip.player); + baresip.commands = mem_deref(baresip.commands); + contact_close(&baresip.contacts); + + baresip.net = mem_deref(baresip.net); + + ui_reset(&baresip.uis); +} + + +struct network *baresip_network(void) +{ + return baresip.net; +} + + +struct contacts *baresip_contacts(void) +{ + return &baresip.contacts; +} + + +struct commands *baresip_commands(void) +{ + return baresip.commands; +} + + +struct player *baresip_player(void) +{ + return baresip.player; +} + + +struct list *baresip_mnatl(void) +{ + return &baresip.mnatl; +} + + +struct list *baresip_mencl(void) +{ + return &baresip.mencl; +} + + +struct message *baresip_message(void) +{ + return baresip.message; +} + + +/** + * Get the list of Audio Codecs + * + * @return List of audio-codecs + */ +struct list *baresip_aucodecl(void) +{ + return &baresip.aucodecl; +} + + +struct list *baresip_ausrcl(void) +{ + return &baresip.ausrcl; +} + + +struct list *baresip_auplayl(void) +{ + return &baresip.auplayl; +} + + +struct list *baresip_aufiltl(void) +{ + return &baresip.aufiltl; +} + + +struct list *baresip_vidcodecl(void) +{ + return &baresip.vidcodecl; +} + + +struct list *baresip_vidsrcl(void) +{ + return &baresip.vidsrcl; +} + + +struct list *baresip_vidispl(void) +{ + return &baresip.vidispl; +} + + +struct list *baresip_vidfiltl(void) +{ + return &baresip.vidfiltl; +} + + +struct ui_sub *baresip_uis(void) +{ + return &baresip.uis; +} 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..99bec4b --- /dev/null +++ b/src/call.c @@ -0,0 +1,1877 @@ +/** + * @file src/call.c Call Control + * + * Copyright (C) 2010 Creytiv.com + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** Magic number */ +#define MAGIC 0xca11ca11 +#include "magic.h" + + +#define FOREACH_STREAM \ + for (le = call->streaml.head; le; le = le->next) + +/** Call constants */ +enum { + PTIME = 20, /**< Packet time for audio */ +}; + + +/** 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_conn; /**< Time when call initiated */ + time_t time_stop; /**< Time when call stopped */ + bool outgoing; /**< True if outgoing, false if incoming */ + bool got_offer; /**< Got SDP Offer from Peer */ + bool on_hold; /**< True if call is on hold */ + 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 */ + + struct config_avt config_avt; /**< AVT config */ + struct config_call config_call; /**< Call config */ + + uint32_t rtp_timeout_ms; /**< RTP Timeout in [ms] */ + uint32_t linenum; /**< Line number from 1 to N */ +}; + + +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); + if (err) { + warning("call: start:" + " audio_encoder_set error: %m\n", err); + } + err |= audio_decoder_set(call->audio, sc->data, + sc->pt, sc->params); + if (err) { + warning("call: start:" + " audio_decoder_set error: %m\n", err); + } + + if (!err) { + err = audio_start(call->audio); + if (err) { + warning("call: start:" + " audio_start error: %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 && !video_is_started(call->video)) { + err = video_start(call->video, call->peer_uri); + } + if (err) { + warning("call: video stream error: %m\n", err); + } + } + else if (call->video) { + info("call: video stream is disabled..\n"); + } + + if (call->bfcp) { + err = bfcp_start(call->bfcp); + if (err) { + warning("call: could not start BFCP: %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, call->config_call.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) { + warning("call: medianat '%s' failed: %m\n", + call->acc->mnatid, err); + call_event_handler(call, CALL_EVENT_CLOSED, "%m", err); + return; + } + else if (scode) { + warning("call: medianat failed: %u %s\n", scode, reason); + call_event_handler(call, CALL_EVENT_CLOSED, "%u %s", + scode, reason); + return; + } + + info("call: media-nat `%s' established\n", call->acc->mnatid); + + /* 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; + + debug("call: update media\n"); + + /* 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) { + warning("call: video stream error: %m\n", err); + return err; + } + + if (!video_is_started(call->video)) { + err = video_start(call->video, call->peer_uri); + if (err) { + warning("call: update: failed to" + " start video (%m)\n", err); + } + } + } + else if (call->video) { + info("video stream is disabled..\n"); + video_stop(call->video); + } +#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 ? KEYCODE_REL : key, call->arg); +} + + +static void audio_error_handler(int err, const char *str, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + if (err) { + warning("call: audio device error: %m (%s)\n", err, str); + } + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, str); +} + + +#ifdef USE_VIDEO +static void video_error_handler(int err, const char *str, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + warning("call: video device error: %m (%s)\n", err, str); + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, str); +} +#endif + + +static void menc_error_handler(int err, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + warning("call: mediaenc '%s' error: %m\n", call->acc->mencid, err); + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed"); +} + + +static void stream_error_handler(struct stream *strm, int err, void *arg) +{ + struct call *call = arg; + MAGIC_CHECK(call); + + info("call: error in \"%s\" rtp stream (%m)\n", + sdp_media_name(stream_sdpmedia(strm)), err); + + call->scode = 701; + set_state(call, STATE_TERMINATED); + + call_stream_stop(call); + call_event_handler(call, CALL_EVENT_CLOSED, "rtp stream error"); +} + + +static int assign_linenum(uint32_t *linenum, const struct list *lst) +{ + uint32_t num; + + for (num=CALL_LINENUM_MIN; num<CALL_LINENUM_MAX; num++) { + + if (!call_find_linenum(lst, num)) { + *linenum = num; + return 0; + } + } + + return ENOENT; +} + + +/** + * 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 dnsc DNS Client + * @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, + struct dnsc *dnsc, + call_event_h *eh, void *arg) +{ + struct call *call; + struct le *le; + struct stream_param stream_prm; + 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 || !prm) + return EINVAL; + + debug("call: alloc with params laddr=%j, af=%s, use_rtp=%d\n", + &prm->laddr, net_af2name(prm->af), prm->use_rtp); + + memset(&stream_prm, 0, sizeof(stream_prm)); + stream_prm.use_rtp = prm->use_rtp; + + call = mem_zalloc(sizeof(*call), call_destructor); + if (!call) + return ENOMEM; + + MAGIC_INIT(call); + + call->config_avt = cfg->avt; + call->config_call = cfg->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, &prm->laddr); + 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, + dnsc, call->af, + acc->stun_host, acc->stun_port, + acc->stun_user, acc->stun_pass, + call->sdp, !got_offer, + mnat_handler, call); + if (err) { + warning("call: medianat 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) { + warning("call: mediaenc session: %m\n", err); + goto out; + } + } + } + + /* Audio stream */ + err = audio_alloc(&call->audio, &stream_prm, cfg, call, + call->sdp, ++label, + acc->mnat, call->mnats, acc->menc, call->mencs, + acc->ptime, account_aucodecl(call->acc), !got_offer, + 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(baresip_vidsrcl(), NULL) + || NULL != vidisp_find(baresip_vidispl(), NULL)); + + debug("call: use_video=%d\n", use_video); + + /* Video stream */ + if (use_video) { + err = video_alloc(&call->video, &stream_prm, cfg, + call, call->sdp, ++label, + acc->mnat, call->mnats, + acc->menc, call->mencs, + "main", + account_vidcodecl(call->acc), + video_error_handler, call); + 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); + } + + FOREACH_STREAM { + struct stream *strm = le->data; + stream_set_error_handler(strm, stream_error_handler, call); + } + + if (cfg->avt.rtp_timeout) { + call_enable_rtp_timeout(call, cfg->avt.rtp_timeout*1000); + } + + err = assign_linenum(&call->linenum, lst); + if (err) { + warning("call: could not assign linenumber\n"); + goto out; + } + + /* NOTE: The new call must always be added to the tail of list, + * which indicates the current call. + */ + 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); + + call->outgoing = true; + + /* 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; + + if (call->config_avt.rtp_stats) + call_set_xrtpstat(call); + + 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 '%s' with %s\n", + sip_dialog_callid(sipsess_dialog(call->sess)), + 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; + + tmr_cancel(&call->tmr_inv); + + 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) { + info("call: answer: call is not in incoming state (%s)\n", + state_name(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; + + if (hold == call->on_hold) + return 0; + + info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri); + + call->on_hold = hold; + + 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)); + err |= re_hprintf(pf, " direction: %s\n", + call->outgoing ? "Outgoing" : "Incoming"); + + /* 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, "[line %u] %H %9s %s %s", call->linenum, + print_duration, call, + state_name(call->state), + call->on_hold ? "(on hold)" : " ", + call->peer_uri); +} + + +/** + * Send a DTMF digit to the peer + * + * @param call Call object + * @param key DTMF digit to send (KEYCODE_REL 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; +} + + +struct account *call_account(const struct call *call) +{ + return call ? call->acc : 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) { + warning("call: reinvite: could not decode SDP offer:" + " %m\n", 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); + + if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed")) + (void)sdp_decode_multipart(&msg->ctyp.params, msg->mb); + + err = sdp_decode(call->sdp, msg->mb, false); + if (err) { + warning("call: could not decode SDP answer: %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_stream_start(call, true); + + if (call->rtp_timeout_ms) { + + struct le *le; + + FOREACH_STREAM { + struct stream *strm = le->data; + stream_enable_rtp_timeout(strm, call->rtp_timeout_ms); + } + } + + /* the transferor will hangup this call */ + if (call->not) { + (void)call_notify_sipfrag(call, 200, "OK"); + } + + /* must be done last, the handler might deref this call */ + call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri); +} + + +#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, KEYCODE_REL, call->arg); +} + + +static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg, + void *arg) +{ + struct call *call = arg; + + if (msg_ctype_cmp(&msg->ctyp, "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*#a-d]+", &sig); + err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur); + + if (err || !pl_isset(&sig) || sig.l == 0) { + (void)sip_reply(sip, msg, 400, "Bad Request"); + } + else { + char s = toupper(sig.p[0]); + uint32_t duration = pl_u32(&dur); + + 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 (msg_ctype_cmp(&msg->ctyp, + "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) { + warning("call: 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) { + warning("call: 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); +} + + +static bool have_common_audio_codecs(const struct call *call) +{ + const struct sdp_format *sc; + struct aucodec *ac; + + sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL); + if (!sc) + return false; + + ac = sc->data; /* note: this will exclude telephone-event */ + + return ac != NULL; +} + + +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; + + call->outgoing = false; + + 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) { + struct sdp_media *m; + const struct sa *raddr; + + err = sdp_decode(call->sdp, msg->mb, true); + if (err) + return err; + + call->got_offer = true; + + /* + * Each media description in the SDP answer MUST + * use the same network type as the corresponding + * media description in the offer. + * + * See RFC 6157 + */ + m = stream_sdpmedia(audio_strm(call->audio)); + raddr = sdp_media_raddr(m); + + if (sa_af(raddr) != call->af) { + info("call: incompatible address-family" + " (local=%s, remote=%s)\n", + net_af2name(call->af), + net_af2name(sa_af(raddr))); + + sip_treply(NULL, uag_sip(), msg, + 488, "Not Acceptable Here"); + + call_event_handler(call, CALL_EVENT_CLOSED, + "Wrong address family"); + return 0; + } + + /* Check if we have any common audio codecs, after + * the SDP offer has been parsed + */ + if (!have_common_audio_codecs(call)) { + info("call: no common audio codecs - rejected\n"); + + sip_treply(NULL, uag_sip(), msg, + 488, "Not Acceptable Here"); + + call_event_handler(call, CALL_EVENT_CLOSED, + "No audio codecs"); + + return 0; + } + } + + 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) { + warning("call: sipsess_accept: %m\n", err); + return err; + } + + set_state(call, STATE_INCOMING); + + /* New call */ + if (call->config_call.local_timeout) { + tmr_start(&call->tmr_inv, call->config_call.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/%r)\n", + msg->scode, &msg->reason, &msg->ctyp.type, &msg->ctyp.subtype); + + 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 (msg_ctype_cmp(&msg->ctyp, "application", "sdp") + && mbuf_get_left(msg->mb) + && !sdp_decode(call->sdp, msg->mb, false)) { + media = true; + } + else if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed") && + !sdp_decode_multipart(&msg->ctyp.params, 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; + } + + call_stream_stop(call); + + if (media) + call_stream_start(call, false); + + if (media) + call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri); + else + call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri); + + if (media) + update_media(call); +} + + +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) { + warning("call: sipsess_connect: %m\n", err); + } + + /* save call setup timer */ + call->time_conn = time(NULL); + + 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 current call setup time in seconds + * + * @param call Call object + * + * @return Call setup in seconds + */ +uint32_t call_setup_duration(const struct call *call) +{ + if (!call || !call->time_conn || call->time_conn <= 0 ) + return 0; + + return (uint32_t)(call->time_start - call->time_conn); +} + + +/** + * 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, const struct sa *laddr) +{ + if (!call) + return EINVAL; + + sdp_session_set_laddr(call->sdp, laddr); + + 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) { + warning("call: transfer failed: %u %r\n", sc, &reason); + call_event_handler(call, CALL_EVENT_TRANSFER_FAILED, + "%u %r", 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); + call_event_handler(call, CALL_EVENT_TRANSFER_FAILED, + "%u %r", 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) { + warning("call: 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; + + if (eh) + call->eh = eh; + + if (dtmfh) + call->dtmfh = dtmfh; + + if (arg) + call->arg = arg; +} + + +void call_set_xrtpstat(struct call *call) +{ + if (!call) + return; + + sipsess_set_close_headers(call->sess, + "X-RTP-Stat: %H\r\n", + audio_print_rtpstat, call->audio); +} + + +bool call_is_onhold(const struct call *call) +{ + return call ? call->on_hold : false; +} + + +bool call_is_outgoing(const struct call *call) +{ + return call ? call->outgoing : false; +} + + +void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms) +{ + if (!call) + return; + + call->rtp_timeout_ms = timeout_ms; +} + + +/** + * Get the line number for this call + * + * @param call Call object + * + * @return Line number from 1 to N + */ +uint32_t call_linenum(const struct call *call) +{ + return call ? call->linenum : 0; +} + + +struct call *call_find_linenum(const struct list *calls, uint32_t linenum) +{ + struct le *le; + + for (le = list_head(calls); le; le = le->next) { + struct call *call = le->data; + + if (linenum == call->linenum) + return call; + } + + return NULL; +} + + +void call_set_current(struct list *calls, struct call *call) +{ + if (!calls || !call) + return; + + list_unlink(&call->le); + list_append(calls, &call->le, call); +} diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..c9d1745 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,768 @@ +/** + * @file src/cmd.c Command Interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <ctype.h> +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +enum { + KEYCODE_DEL = 0x7f, + LONG_PREFIX = '/' +}; + + +struct cmds { + struct le le; + const struct cmd *cmdv; + size_t cmdc; +}; + +struct cmd_ctx { + struct mbuf *mb; + const struct cmd *cmd; + bool is_long; +}; + +struct commands { + struct list cmdl; /**< List of command blocks (struct cmds) */ +}; + + +static int cmd_print_all(struct re_printf *pf, + const struct commands *commands, + bool print_long, bool print_short, + const char *match, size_t match_len); + + +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 void commands_destructor(void *data) +{ + struct commands *commands = data; + + list_flush(&commands->cmdl); +} + + +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; +} + + +/** + * Find a command block + * + * @param commands Commands container + * @param cmdv Command vector + * + * @return Command block if found, otherwise NULL + */ +struct cmds *cmds_find(const struct commands *commands, + const struct cmd *cmdv) +{ + struct le *le; + + if (!commands || !cmdv) + return NULL; + + for (le = commands->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(const struct commands *commands, + char key) +{ + struct le *le; + + if (!commands) + return NULL; + + for (le = commands->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 KEYCODE_ESC: return "ESC"; + } + + buf[0] = cmd->key; + buf[1] = '\0'; + + if (cmd->flags & CMD_PRM) + strncat(buf, " ..", sz-1); + + return buf; +} + + +static size_t get_match_long(const struct commands *commands, + const struct cmd **cmdp, + const char *str, size_t len) +{ + struct le *le; + size_t nmatch = 0; + + if (!commands) + return 0; + + for (le = commands->cmdl.head; le; le = le->next) { + + struct cmds *cmds = le->data; + size_t i; + + for (i=0; i<cmds->cmdc; i++) { + + const struct cmd *cmd = &cmds->cmdv[i]; + + if (!str_isset(cmd->name)) + continue; + + if (str_len(cmd->name) >= len && + 0 == memcmp(cmd->name, str, len)) { + + ++nmatch; + *cmdp = cmd; + } + } + } + + return nmatch; +} + + +static int editor_input(struct commands *commands, struct mbuf *mb, char key, + struct re_printf *pf, bool *del, bool is_long) +{ + int err = 0; + + switch (key) { + + case KEYCODE_ESC: + *del = true; + return re_hprintf(pf, "\nCancel\n"); + + case KEYCODE_NONE: + case KEYCODE_REL: + break; + + case '\n': + *del = true; + return re_hprintf(pf, "\n"); + + case '\b': + case KEYCODE_DEL: + if (mb->pos > 0) { + err |= re_hprintf(pf, "\b "); + mb->pos = mb->end = (mb->pos - 1); + } + break; + + case '\t': + if (is_long) { + const struct cmd *cmd = NULL; + size_t n; + + err = re_hprintf(pf, + "TAB completion for \"%b\":\n", + mb->buf, mb->end); + if (err) + return err; + + /* Find all long commands that matches the N + * first characters of the input string. + * + * If the number of matches is exactly one, + * we can regard it as TAB completion. + */ + + err = cmd_print_all(pf, commands, true, false, + (char *)mb->buf, mb->end); + if (err) + return err; + + n = get_match_long(commands, &cmd, + (char *)mb->buf, mb->end); + if (n == 1 && cmd) { + + mb->pos = 0; + mbuf_write_str(mb, cmd->name); + } + else if (n == 0) { + err = re_hprintf(pf, "(none)\n"); + } + } + else { + err = mbuf_write_u8(mb, key); + } + break; + + default: + err = mbuf_write_u8(mb, key); + break; + } + + if (is_long) { + err |= re_hprintf(pf, "\r/%b", + mb->buf, mb->end); + } + else + 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, void *data) +{ + struct cmd_arg arg; + int err; + + memset(&arg, 0, sizeof(arg)); + + mb->pos = 0; + err = mbuf_strdup(mb, &arg.prm, mb->end); + if (err) + return err; + + arg.key = cmd->key; + arg.complete = compl; + arg.data = data; + + err = cmd->h(pf, &arg); + + mem_deref(arg.prm); + + return err; +} + + +/** + * Process long commands + * + * @param commands Commands container + * @param str Input string + * @param len Length of input string + * @param pf_resp Print function for response + * @param data Application data + * + * @return 0 if success, otherwise errorcode + */ +int cmd_process_long(struct commands *commands, const char *str, size_t len, + struct re_printf *pf_resp, void *data) +{ + struct cmd_arg arg; + const struct cmd *cmd_long; + char *name = NULL, *prm = NULL; + struct pl pl_name, pl_prm; + int err; + + if (!str || !len) + return EINVAL; + + memset(&arg, 0, sizeof(arg)); + + err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm); + if (err) { + return err; + } + + err = pl_strdup(&name, &pl_name); + if (pl_isset(&pl_prm)) + err |= pl_strdup(&prm, &pl_prm); + if (err) + goto out; + + cmd_long = cmd_find_long(commands, name); + if (cmd_long) { + + arg.key = LONG_PREFIX; + arg.prm = prm; + arg.complete = true; + arg.data = data; + + if (cmd_long->h) + err = cmd_long->h(pf_resp, &arg); + } + else { + err = re_hprintf(pf_resp, "command not found (%s)\n", name); + } + + out: + mem_deref(name); + mem_deref(prm); + + return err; +} + + +static int cmd_process_edit(struct commands *commands, + struct cmd_ctx **ctxp, char key, + struct re_printf *pf, void *data) +{ + struct cmd_ctx *ctx; + bool compl = (key == '\n'), del = false; + int err; + + if (!ctxp) + return EINVAL; + + ctx = *ctxp; + + err = editor_input(commands, ctx->mb, key, pf, &del, ctx->is_long); + if (err) + return err; + + if (ctx->is_long) { + + if (compl) { + + err = cmd_process_long(commands, + (char *)ctx->mb->buf, + ctx->mb->end, + pf, data); + } + } + else { + if (compl || + (ctx->cmd && ctx->cmd->flags & CMD_PROG)) + err = cmd_report(ctx->cmd, pf, ctx->mb, compl, data); + } + + if (del) + *ctxp = mem_deref(*ctxp); + + return err; +} + + +/** + * Register commands + * + * @param commands Commands container + * @param cmdv Array of commands + * @param cmdc Number of commands + * + * @return 0 if success, otherwise errorcode + */ +int cmd_register(struct commands *commands, + const struct cmd *cmdv, size_t cmdc) +{ + struct cmds *cmds; + size_t i; + + if (!commands || !cmdv || !cmdc) + return EINVAL; + + cmds = cmds_find(commands, cmdv); + if (cmds) + return EALREADY; + + /* verify that command is not registered */ + for (i=0; i<cmdc; i++) { + const struct cmd *cmd = &cmdv[i]; + + if (cmd->key) { + const struct cmd *x = cmd_find_by_key(commands, + cmd->key); + if (x) { + warning("short command '%c' already" + " registered as \"%s\"\n", + x->key, x->desc); + return EALREADY; + } + } + + if (cmd->key == LONG_PREFIX) { + warning("cmd: cannot register command with" + " short key '%c'\n", cmd->key); + return EINVAL; + } + + if (str_isset(cmd->name) && + cmd_find_long(commands, cmd->name)) { + warning("cmd: long command '%s' already registered\n", + cmd->name); + return EINVAL; + } + } + + cmds = mem_zalloc(sizeof(*cmds), destructor); + if (!cmds) + return ENOMEM; + + cmds->cmdv = cmdv; + cmds->cmdc = cmdc; + + list_append(&commands->cmdl, &cmds->le, cmds); + + return 0; +} + + +/** + * Unregister commands + * + * @param commands Commands container + * @param cmdv Array of commands + */ +void cmd_unregister(struct commands *commands, const struct cmd *cmdv) +{ + mem_deref(cmds_find(commands, cmdv)); +} + + +/** + * Find a long command + * + * @param commands Commands container + * @param name Name of command, excluding prefix + * + * @return Command if found, NULL if not found + */ +const struct cmd *cmd_find_long(const struct commands *commands, + const char *name) +{ + struct le *le; + + if (!commands || !name) + return NULL; + + for (le = commands->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 (0 == str_casecmp(name, cmd->name) && cmd->h) + return cmd; + } + } + + return NULL; +} + + +/** + * Process input characters to the command system + * + * @param commands Commands container + * @param ctxp Pointer to context for editor (optional) + * @param key Input character + * @param pf Print function + * @param data Application data + * + * @return 0 if success, otherwise errorcode + */ +int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key, + struct re_printf *pf, void *data) +{ + const struct cmd *cmd; + + if (!commands) + return EINVAL; + + if (key == KEYCODE_NONE) { + warning("cmd: process: illegal keycode NONE\n"); + return EINVAL; + } + + /* are we in edit-mode? */ + if (ctxp && *ctxp) { + + if (key == KEYCODE_REL) + return 0; + + return cmd_process_edit(commands, ctxp, key, pf, data); + } + + cmd = cmd_find_by_key(commands, key); + if (cmd) { + struct cmd_arg arg; + + /* check for parameters */ + if (cmd->flags & CMD_PRM) { + + int err = 0; + + if (ctxp) { + err = ctx_alloc(ctxp, cmd); + if (err) + return err; + } + + key = isdigit(key) ? key : KEYCODE_REL; + + return cmd_process_edit(commands, ctxp, key, pf, data); + } + + arg.key = key; + arg.prm = NULL; + arg.complete = true; + arg.data = data; + + return cmd->h(pf, &arg); + } + else if (key == LONG_PREFIX) { + + int err; + + err = re_hprintf(pf, "%c", LONG_PREFIX); + if (err) + return err; + + if (!ctxp) { + warning("cmd: ctxp is required\n"); + return EINVAL; + } + + err = ctx_alloc(ctxp, cmd); + if (err) + return err; + + (*ctxp)->is_long = true; + + return 0; + } + else if (key == '\t') { + return cmd_print_all(pf, commands, false, true, NULL, 0); + } + + if (key == KEYCODE_REL) + return 0; + + return cmd_print(pf, commands); +} + + +struct cmd_sort { + struct le le; + const struct cmd *cmd; +}; + + +static bool sort_handler(struct le *le1, struct le *le2, void *arg) +{ + struct cmd_sort *cs1 = le1->data; + struct cmd_sort *cs2 = le2->data; + const struct cmd *cmd1 = cs1->cmd; + const struct cmd *cmd2 = cs2->cmd; + bool print_long = *(bool *)arg; + + if (print_long) { + return str_casecmp(cs2->cmd->name ? cs2->cmd->name : "", + cs1->cmd->name ? cs1->cmd->name : "") >= 0; + } + else { + return tolower(cmd2->key) >= tolower(cmd1->key); + } +} + + +static int cmd_print_all(struct re_printf *pf, + const struct commands *commands, + bool print_long, bool print_short, + const char *match, size_t match_len) +{ + struct list sortedl = LIST_INIT; + struct le *le; + size_t width_long = 1; + size_t width_short = 5; + char fmt[64]; + char buf[16]; + int err = 0; + + if (!commands) + return EINVAL; + + for (le = commands->cmdl.head; le; le = le->next) { + + struct cmds *cmds = le->data; + size_t i; + + for (i=0; i<cmds->cmdc; i++) { + + const struct cmd *cmd = &cmds->cmdv[i]; + struct cmd_sort *cs; + + if (match && match_len) { + + if (str_len(cmd->name) >= match_len && + 0 == memcmp(cmd->name, match, match_len)) { + /* Match */ + } + else { + continue; + } + } + + if (!str_isset(cmd->desc)) + continue; + + if (print_short && !print_long) { + + if (cmd->key == KEYCODE_NONE) + continue; + } + + cs = mem_zalloc(sizeof(*cs), NULL); + if (!cs) { + err = ENOMEM; + goto out; + } + cs->cmd = cmd; + + list_append(&sortedl, &cs->le, cs); + + width_long = max(width_long, 1+str_len(cmd->name)+3); + } + } + + list_sort(&sortedl, sort_handler, &print_long); + + if (re_snprintf(fmt, sizeof(fmt), + " %%-%zus %%-%zus %%s\n", + width_long, width_short) < 0) { + err = ENOMEM; + goto out; + } + + for (le = sortedl.head; le; le = le->next) { + struct cmd_sort *cs = le->data; + const struct cmd *cmd = cs->cmd; + char namep[64] = ""; + + if (print_long && str_isset(cmd->name)) { + re_snprintf(namep, sizeof(namep), "%c%s%s", + LONG_PREFIX, cmd->name, + (cmd->flags & CMD_PRM) ? " .." : ""); + } + + err |= re_hprintf(pf, fmt, + namep, + (print_short && cmd->key) + ? cmd_name(buf, sizeof(buf), cmd) + : "", + cmd->desc); + } + + err |= re_hprintf(pf, "\n"); + + out: + list_flush(&sortedl); + return err; +} + + +/** + * Print a list of available commands + * + * @param pf Print function + * @param commands Commands container + * + * @return 0 if success, otherwise errorcode + */ +int cmd_print(struct re_printf *pf, const struct commands *commands) +{ + int err = 0; + + if (!pf) + return EINVAL; + + err |= re_hprintf(pf, "--- Help ---\n"); + err |= cmd_print_all(pf, commands, true, true, NULL, 0); + err |= re_hprintf(pf, "\n"); + + return err; +} + + +/** + * Initialize the commands subsystem. + * + * @param commandsp Pointer to allocated commands + * + * @return 0 if success, otherwise errorcode + */ +int cmd_init(struct commands **commandsp) +{ + struct commands *commands; + + if (!commandsp) + return EINVAL; + + commands = mem_zalloc(sizeof(*commands), commands_destructor); + if (!commands) + return ENOMEM; + + list_init(&commands->cmdl); + + *commandsp = commands; + + return 0; +} diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000..2046216 --- /dev/null +++ b/src/conf.c @@ -0,0 +1,386 @@ +/** + * @file conf.c Configuration utils + * + * Copyright (C) 2010 Creytiv.com + */ +#define _DEFAULT_SOURCE 1 +#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) +#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 + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int conf_parse(const char *filename, confline_h *ch, void *arg) +{ + 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, arg); + } + + 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[FS_PATH_MAX]; + int err; + + /* Use explicit conf path */ + if (conf_path) { + if (re_snprintf(path, sz, "%s", conf_path) < 0) + return ENOMEM; + return 0; + } + +#ifdef CONFIG_PATH + str_ncpy(buf, CONFIG_PATH, sizeof(buf)); + (void)err; +#else + err = fs_gethome(buf, sizeof(buf)); + if (err) + return err; +#endif + + 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; + + /* note: second value may be quoted */ + 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[FS_PATH_MAX], file[FS_PATH_MAX]; + int err; + +#if defined (WIN32) + 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; + } + + conf_obj = mem_deref(conf_obj); + err = conf_alloc(&conf_obj, file); + if (err) + goto out; + + err = config_parse_conf(conf_config(), conf_obj); + if (err) + goto out; + + out: + return err; +} + + +/** + * Load all modules from config file + * + * @return 0 if success, otherwise errorcode + * + * @note conf_configure must be called first + */ +int conf_modules(void) +{ + int err; + + err = module_init(conf_obj); + if (err) { + warning("conf: configure module parse error (%m)\n", err); + goto out; + } + + print_populated("audio codec", list_count(baresip_aucodecl())); + print_populated("audio filter", list_count(baresip_aufiltl())); +#ifdef USE_VIDEO + print_populated("video codec", list_count(baresip_vidcodecl())); + print_populated("video filter", list_count(baresip_vidfiltl())); +#endif + + out: + return err; +} + + +/** + * Get the current configuration object + * + * @return Config object + * + * @note It is only available after init and before conf_close() + */ +struct conf *conf_cur(void) +{ + if (!conf_obj) { + warning("conf: no config object\n"); + } + return conf_obj; +} + + +void conf_close(void) +{ + conf_obj = mem_deref(conf_obj); +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..ce748d3 --- /dev/null +++ b/src/config.c @@ -0,0 +1,925 @@ +/** + * @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 */ + + +#undef SA_INIT +#define SA_INIT { { {0} }, 0} + + +#ifndef PREFIX +#define PREFIX "/usr" +#endif + + +/** Core Run-time Configuration - populated from config file */ +static struct config core_config = { + + /** SIP User-Agent */ + { + 16, + "", + "", + "" + }, + + /** Call config */ + { + 120, + 4 + }, + + /** Audio */ + { + PREFIX "/share/baresip", + "","", + "","", + "","", + {8000, 48000}, + {1, 2}, + 0, + 0, + 0, + 0, + false, + AUDIO_MODE_POLL, + false, + AUFMT_S16LE, + AUFMT_S16LE, + }, + +#ifdef USE_VIDEO + /** Video */ + { + "", "", + "", "", + 352, 288, + 500000, + 25, + true, + }, +#endif + + /** Audio/Video Transport */ + { + 0xb8, + {1024, 49152}, + {0, 0}, + true, + false, + {5, 10}, + false, + 0 + }, + + /* Network */ + { + "", + { {""} }, + 0 + }, + +#ifdef USE_VIDEO + /* BFCP */ + { + "" + }, +#endif + + /* SDP */ + { + false + }, +}; + + +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 config_net *cfg = arg; + const size_t max_count = ARRAY_SIZE(cfg->nsv); + int err; + + if (cfg->nsc >= max_count) { + warning("config: too many DNS nameservers (max %zu)\n", + max_count); + return EOVERFLOW; + } + + /* Append dns_server to the network config */ + err = pl_strcpy(pl, cfg->nsv[cfg->nsc].addr, + sizeof(cfg->nsv[0].addr)); + if (err) { + warning("config: dns_server: could not copy string (%r)\n", + pl); + return err; + } + + ++cfg->nsc; + + return 0; +} + + +static enum aufmt resolve_aufmt(const struct pl *fmt) +{ + if (0 == pl_strcasecmp(fmt, "s16")) return AUFMT_S16LE; + if (0 == pl_strcasecmp(fmt, "float")) return AUFMT_FLOAT; + if (0 == pl_strcasecmp(fmt, "s24_3le")) return AUFMT_S24_3LE; + + /* XXX remove this after librem is fixed */ + if (0 == pl_strcasecmp(fmt, "s16le")) return AUFMT_S16LE; + + return (enum aufmt)-1; +} + + +static int conf_get_aufmt(const struct conf *conf, const char *name, + int *fmtp) +{ + struct pl pl; + int fmt; + int err; + + err = conf_get(conf, name, &pl); + if (err) + return err; + + fmt = resolve_aufmt(&pl); + if (fmt == -1) { + warning("config: %s: sample format not supported" + " (%r)\n", name, &pl); + return EINVAL; + } + + *fmtp = fmt; + + return 0; +} + + +/** + * Parse the core configuration file and update baresip core config + * + * @param cfg Baresip core config to update + * @param conf Configuration file to parse + * + * @return 0 if success, otherwise errorcode + */ +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}; + struct pl txmode; + 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); + } + } + + /* 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)); + + /* Call */ + (void)conf_get_u32(conf, "call_local_timeout", + &cfg->call.local_timeout); + (void)conf_get_u32(conf, "call_max_calls", + &cfg->call.max_calls); + + /* Audio */ + (void)conf_get_str(conf, "audio_path", cfg->audio.audio_path, + sizeof(cfg->audio.audio_path)); + (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; + + if (0 == conf_get(conf, "audio_txmode", &txmode)) { + + if (0 == pl_strcasecmp(&txmode, "poll")) + cfg->audio.txmode = AUDIO_MODE_POLL; + else if (0 == pl_strcasecmp(&txmode, "thread")) + cfg->audio.txmode = AUDIO_MODE_THREAD; + else { + warning("unsupported audio txmode (%r)\n", &txmode); + } + } + + (void)conf_get_bool(conf, "audio_level", &cfg->audio.level); + + conf_get_aufmt(conf, "ausrc_format", &cfg->audio.src_fmt); + conf_get_aufmt(conf, "auplay_format", &cfg->audio.play_fmt); + +#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); + (void)conf_get_bool(conf, "video_fullscreen", &cfg->video.fullscreen); +#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 *= 1000; + cfg->avt.rtp_bw.max *= 1000; + } + (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); + (void)conf_get_u32(conf, "rtp_timeout", &cfg->avt.rtp_timeout); + + if (err) { + warning("config: configure parse error (%m)\n", err); + } + + /* Network */ + (void)conf_apply(conf, "dns_server", dns_server_handler, &cfg->net); + (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 + + /* SDP */ + (void)conf_get_bool(conf, "sdp_ebuacip", &cfg->sdp.ebuacip); + + return err; +} + + +/** + * Print the baresip core config + * + * @param pf Print function + * @param cfg Baresip core config + * + * @return 0 if success, otherwise errorcode + */ +int config_print(struct re_printf *pf, const struct config *cfg) +{ + int err; + + if (!cfg) + return 0; + + err = re_hprintf(pf, + "\n" + "# SIP\n" + "sip_trans_bsize\t\t%u\n" + "sip_listen\t\t%s\n" + "sip_certificate\t%s\n" + "\n" + "# Call\n" + "call_local_timeout\t%u\n" + "call_max_calls\t%u\n" + "\n" + "# Audio\n" + "audio_path\t\t%s\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" + "audio_level\t\t%s\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" + "rtp_timeout\t\t%u # in seconds\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->sip.trans_bsize, cfg->sip.local, cfg->sip.cert, + + cfg->call.local_timeout, + cfg->call.max_calls, + + cfg->audio.audio_path, + 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, + cfg->audio.level ? "yes" : "no", + +#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->avt.rtp_timeout, + + cfg->net.ifname + +#ifdef USE_VIDEO + ,cfg->bfcp.proto +#endif + ); + + return err; +} + + +static const char *default_audio_device(void) +{ +#if defined (ANDROID) + return "opensles,nil"; +#elif defined (DARWIN) + return "coreaudio,nil"; +#elif defined (FREEBSD) + return "oss,/dev/dsp"; +#elif defined (OPENBSD) + return "sndio,default"; +#elif defined (WIN32) + return "winwave,nil"; +#else + return "alsa,default"; +#endif +} + + +#ifdef USE_VIDEO +static const char *default_video_device(void) +{ +#ifdef DARWIN + +#ifdef QTCAPTURE_RUNLOOP + return "qtcapture,nil"; +#else + return "avcapture,nil"; +#endif + +#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" +#ifdef HAVE_EPOLL + ", epoll .." +#endif +#ifdef HAVE_KQUEUE + ", kqueue .." +#endif + "\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" + "# Call\n" + "call_local_timeout\t%u\n" + "call_max_calls\t%u\n" + "\n" + "# Audio\n" +#if defined (PREFIX) + "#audio_path\t\t" PREFIX "/share/baresip\n" +#else + "#audio_path\t\t/usr/share/baresip\n" +#endif + "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" + "#audio_txmode\t\tpoll\t\t# poll, thread\n" + "audio_level\t\tno\n" + "ausrc_format\t\ts16\t\t# s16, float, ..\n" + "auplay_format\t\ts16\t\t# s16, float, ..\n" + , + poll_method_name(poll_method_best()), + cfg->call.local_timeout, + cfg->call.max_calls, + 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" + "video_fullscreen\tyes\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" + "#rtp_timeout\t\t60\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; + size_t i; + + for (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; +} + + +/** + * Write the baresip core config template to a file + * + * @param file Filename of output file + * @param cfg Baresip core config + * + * @return 0 if success, otherwise errorcode + */ +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, "#module\t\t\t" MOD_PRE "mpa" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "codec2" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "ilbc" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "isac" 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 (ANDROID) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opensles" MOD_EXT "\n"); +#elif defined (DARWIN) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "audiounit" MOD_EXT "\n"); +#elif defined (FREEBSD) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n"); +#elif defined (OPENBSD) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "sndio" MOD_EXT "\n"); +#elif defined (WIN32) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n"); +#else + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "pulse" MOD_EXT "\n"); +#endif + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "jack" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aubridge" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aufile" MOD_EXT "\n"); + +#ifdef USE_VIDEO + + (void)re_fprintf(f, "\n# Video codec Modules (in order)\n"); +#ifdef USE_AVCODEC + (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 "vp8" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vp9" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "h265" 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, "#module\t\t\t" MOD_PRE "snapshot" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "swscale" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vidinfo" MOD_EXT "\n"); + + (void)re_fprintf(f, "\n# Video source modules\n"); +#if defined (DARWIN) + +#ifdef QTCAPTURE_RUNLOOP + (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 "avcapture" MOD_EXT "\n"); +#endif + +#elif defined (WIN32) + (void)re_fprintf(f, "module\t\t\t" MOD_PRE "dshow" 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"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2_codec" MOD_EXT "\n"); +#endif +#ifdef USE_AVFORMAT + (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, "#module\t\t\t" MOD_PRE "vidbridge" 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 +#ifdef LINUX + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "directfb" 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"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "fakevideo" MOD_EXT "\n"); + +#endif /* USE_VIDEO */ + + (void)re_fprintf(f, "\n# Audio/Video source modules\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst1" MOD_EXT "\n"); + (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst_video1" 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, "#module\t\t\t" MOD_PRE "zrtp" 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 "debug_cmd"MOD_EXT"\n"); +#ifdef LINUX + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "dtmfio"MOD_EXT"\n"); +#endif + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "echo"MOD_EXT"\n"); + (void)re_fprintf(f, "#module_app\t\t\t" MOD_PRE "gtk" 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"); + (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mqtt" 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"); + (void)re_fprintf(f, "cons_listen\t\t0.0.0.0:5555\n"); + + (void)re_fprintf(f, "\n"); + (void)re_fprintf(f, "http_listen\t\t0.0.0.0:8000\n"); + + (void)re_fprintf(f, "\n"); + (void)re_fprintf(f, "evdev_device\t\t/dev/input/event0\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_mode_nb\t\t3 # 1-6\n"); + (void)re_fprintf(f, "speex_mode_wb\t\t6 # 1-6\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# Opus codec parameters\n"); + (void)re_fprintf(f, "opus_bitrate\t\t28000 # 6000-510000\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"); + + (void)re_fprintf(f, + "\n# ZRTP\n" + "#zrtp_hash\t\tno # Disable SDP zrtp-hash " + "(not recommended)\n"); + + (void)re_fprintf(f, + "\n# Menu\n" + "#redial_attempts\t\t3 # Num or <inf>\n" + "#redial_delay\t\t5 # Delay in seconds\n"); + + if (f) + (void)fclose(f); + + return err; +} + + +/** + * Get the baresip core config + * + * @return Core config + */ +struct config *conf_config(void) +{ + return &core_config; +} diff --git a/src/contact.c b/src/contact.c new file mode 100644 index 0000000..a84890c --- /dev/null +++ b/src/contact.c @@ -0,0 +1,338 @@ +/** + * @file src/contact.c Contacts handling + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> + + +enum access { + ACCESS_UNKNOWN = 0, + ACCESS_BLOCK, + ACCESS_ALLOW +}; + +struct contact { + struct le le; + struct le he; /* hash-element with key 'auri' */ + struct sip_addr addr; + char *buf; + enum presence_status status; + enum access access; +}; + + +static void destructor(void *arg) +{ + struct contact *c = arg; + + hash_unlink(&c->he); + list_unlink(&c->le); + mem_deref(c->buf); +} + + +/** + * Add a contact + * + * @param contacts Contacts container + * @param contactp Pointer to allocated contact (optional) + * @param addr Contact in SIP address format + * + * @return 0 if success, otherwise errorcode + */ +int contact_add(struct contacts *contacts, + struct contact **contactp, const struct pl *addr) +{ + struct contact *c; + struct pl pl; + int err; + + if (!contacts) + return EINVAL; + + 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; + } + + if (0 == msg_param_decode(&c->addr.params, "access", &pl)) { + + if (0 == pl_strcasecmp(&pl, "block")) { + c->access = ACCESS_BLOCK; + } + else if (0 == pl_strcasecmp(&pl, "allow")) { + c->access = ACCESS_ALLOW; + } + else { + warning("contact: unknown 'access=%r' for '%r'\n", + &pl, addr); + err = EINVAL; + goto out; + } + } + else + c->access = ACCESS_UNKNOWN; + + c->status = PRESENCE_UNKNOWN; + + list_append(&contacts->cl, &c->le, c); + hash_append(contacts->cht, hash_joaat_pl(&c->addr.auri), &c->he, c); + + if (contacts->handler) + contacts->handler(c, false, contacts->handler_arg); + + out: + if (err) + mem_deref(c); + else if (contactp) + *contactp = c; + + return err; +} + + +/** + * Remove a contact + * + * @param contacts Contacts container + * @param contact Contact to be removed + */ +void contact_remove(struct contacts *contacts, struct contact *contact) +{ + if (!contacts || !contact) + return; + + if (contacts->handler) + contacts->handler(contact, true, contacts->handler_arg); + + hash_unlink(&contact->he); + list_unlink(&contact->le); + + mem_deref(contact); +} + + +void contact_set_update_handler(struct contacts *contacts, + contact_update_h *updateh, void *arg) +{ + if (!contacts) { + return; + } + + contacts->handler = updateh; + contacts->handler_arg = arg; +} + + +/** + * 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 + * + * @param contacts Contacts container + * + * @return List of contacts + */ +struct list *contact_list(const struct contacts *contacts) +{ + if (!contacts) + return NULL; + + return (struct list *)&contacts->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; +} + +enum presence_status contact_presence(const struct contact *c) +{ + if (!c) + return PRESENCE_UNKNOWN; + + return c->status; +} + +const char *contact_presence_str(enum presence_status status) +{ + switch (status) { + + default: + case PRESENCE_UNKNOWN: return "\x1b[32mUnknown\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, const struct contacts *contacts) +{ + const struct list *lst; + struct le *le; + int err; + + if (!contacts) + return 0; + + lst = contact_list(contacts); + + err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n", + list_count(lst)); + + for (le = list_head(lst); 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; +} + + +/** + * Initialise the contacts sub-system + * + * @param contacts Contacts container + * + * @return 0 if success, otherwise errorcode + */ +int contact_init(struct contacts *contacts) +{ + int err = 0; + + if (!contacts) + return EINVAL; + + memset(contacts, 0, sizeof(*contacts)); + + list_init(&contacts->cl); + + err = hash_alloc(&contacts->cht, 32); + + return err; +} + + +/** + * @param contacts Contacts container + * + * Close the contacts sub-system + */ +void contact_close(struct contacts *contacts) +{ + if (!contacts) + return; + + hash_clear(contacts->cht); + contacts->cht = mem_deref(contacts->cht); + list_flush(&contacts->cl); +} + + +static bool find_handler(struct le *le, void *arg) +{ + struct contact *c = le->data; + + return 0 == pl_strcmp(&c->addr.auri, arg); +} + + +/** + * Lookup a SIP uri in all registered contacts + * + * @param contacts Contacts container + * @param uri SIP uri to lookup + * + * @return Matching contact if found, otherwise NULL + */ +struct contact *contact_find(const struct contacts *contacts, const char *uri) +{ + if (!contacts) + return NULL; + + return list_ledata(hash_lookup(contacts->cht, hash_joaat_str(uri), + find_handler, (void *)uri)); +} + + +/** + * Check the access parameter of a SIP uri + * + * - Matching uri has first presedence + * - Global <sip:*@*> uri has second presedence + * + * @param contacts Contacts container + * @param uri SIP uri to check for access + * + * @return True if blocked, false if allowed + */ +bool contact_block_access(const struct contacts *contacts, const char *uri) +{ + struct contact *c; + + c = contact_find(contacts, uri); + if (c && c->access != ACCESS_UNKNOWN) + return c->access == ACCESS_BLOCK; + + c = contact_find(contacts, "sip:*@*"); + if (c && c->access != ACCESS_UNKNOWN) + return c->access == ACCESS_BLOCK; + + return false; +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..a390e0c --- /dev/null +++ b/src/core.h @@ -0,0 +1,541 @@ +/** + * @file core.h Internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +#include <limits.h> + + +/* max bytes in pathname */ +#if defined (PATH_MAX) +#define FS_PATH_MAX PATH_MAX +#elif defined (_POSIX_PATH_MAX) +#define FS_PATH_MAX _POSIX_PATH_MAX +#else +#define FS_PATH_MAX 512 +#endif + + +/** + * 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 */ +}; + + +/* forward declarations */ +struct stream_param; + + +/* + * Account + */ + + +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 *outboundv[2]; /**< Optional SIP outbound proxies */ + uint32_t ptime; /**< Configured packet time in [ms] */ + uint32_t regint; /**< Registration interval in [seconds] */ + uint32_t pubint; /**< Publication 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 { + const 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 stream_param *stream_prm, + 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, bool offerer, + 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); +int audio_send_digit(struct audio *a, char key); +void audio_sdp_attr_decode(struct audio *a); +int audio_print_rtpstat(struct re_printf *pf, const struct audio *au); + + +/* + * 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 + */ + +enum { + CALL_LINENUM_MIN = 1, + CALL_LINENUM_MAX = 256 +}; + +struct call; + +/** Call parameters */ +struct call_prm { + struct sa laddr; + enum vidmode vidmode; + int af; + bool use_rtp; +}; + +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, + struct dnsc *dnsc, + 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, const struct sa *laddr); +int call_notify_sipfrag(struct call *call, uint16_t scode, + const char *reason, ...); +int call_af(const struct call *call); +void call_set_xrtpstat(struct call *call); +struct account *call_account(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 struct list *mnatl, 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_init(struct metric *metric); +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); + + +/* + * 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_debug(struct re_printf *pf, const struct reg *reg); +int reg_status(struct re_printf *pf, const struct reg *reg); + + +/* + * RTP Header Extensions + */ + +#define RTPEXT_HDR_SIZE 4 +#define RTPEXT_TYPE_MAGIC 0xbede + +enum { + RTPEXT_ID_MIN = 1, + RTPEXT_ID_MAX = 14, +}; + +enum { + RTPEXT_LEN_MIN = 1, + RTPEXT_LEN_MAX = 16, +}; + +struct rtpext { + unsigned id:4; + unsigned len:4; + uint8_t data[RTPEXT_LEN_MAX]; +}; + + +int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes); +int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len, + const uint8_t *data); +int rtpext_decode(struct rtpext *ext, struct mbuf *mb); + + +/* + * 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); + + +/* + * SDP + */ + +int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb); +const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m); + + +/* + * 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 rtpext *extv, size_t extc, + struct mbuf *mb, void *arg); +typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg); + +typedef void (stream_error_h)(struct stream *strm, int err, void *arg); + +/** Common parameters for media stream */ +struct stream_param { + bool use_rtp; +}; + +/** Defines a generic media stream */ +struct stream { + 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; /**< RTCP Canonical end-point identifier */ + 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 */ + stream_error_h *errorh; /**< Stream error handler */ + void *errorh_arg; /**< Error handler argument */ + struct tmr tmr_rtp; /**< Timer for detecting RTP timeout */ + uint64_t ts_last; /**< Timestamp of last received RTP pkt */ + bool terminated; /**< Stream is terminated flag */ + uint32_t rtp_timeout_ms; /**< RTP Timeout value in [ms] */ +}; + +int stream_alloc(struct stream **sp, const struct stream_param *prm, + 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 ext, 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); +void stream_set_error_handler(struct stream *strm, + stream_error_h *errorh, void *arg); +int stream_debug(struct re_printf *pf, const struct stream *s); +int stream_print(struct re_printf *pf, const struct stream *s); +void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms); + + +/* + * 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; + +typedef void (video_err_h)(int err, const char *str, void *arg); + +int video_alloc(struct video **vp, const struct stream_param *stream_prm, + 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, + video_err_h *errh, void *arg); +int video_start(struct video *v, const char *peer); +void video_stop(struct video *v); +bool video_is_started(const 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); +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); + + +/* + * Timestamp helpers + */ + + +/** + * This struct is used to keep track of timestamps for + * incoming RTP packets. + */ +struct timestamp_recv { + uint32_t first; + uint32_t last; + bool is_set; + unsigned num_wraps; +}; + + +static inline uint64_t calc_extended_timestamp(uint32_t num_wraps, uint32_t ts) +{ + uint64_t ext_ts; + + ext_ts = (uint64_t)num_wraps * 0x100000000ULL; + ext_ts += (uint64_t)ts; + + return ext_ts; +} + + +static inline uint64_t timestamp_duration(const struct timestamp_recv *ts) +{ + uint64_t last_ext; + + if (!ts || !ts->is_set) + return 0; + + last_ext = calc_extended_timestamp(ts->num_wraps, ts->last); + + return last_ext - ts->first; +} + + +/* + * -1 backwards wrap-around + * 0 no wrap-around + * 1 forward wrap-around + */ +static inline int timestamp_wrap(uint32_t ts_new, uint32_t ts_old) +{ + int32_t delta; + + if (ts_new < ts_old) { + + delta = (int32_t)ts_new - (int32_t)ts_old; + + if (delta > 0) + return 1; + } + else if ((int32_t)(ts_old - ts_new) > 0) { + + return -1; + } + + return 0; +} diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..b29ab8b --- /dev/null +++ b/src/event.c @@ -0,0 +1,171 @@ +/** + * @file src/event.c Baresip event handling + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static const char *event_class_name(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: + case UA_EVENT_REGISTER_OK: + case UA_EVENT_REGISTER_FAIL: + case UA_EVENT_UNREGISTERING: + return "register"; + + case UA_EVENT_SHUTDOWN: + case UA_EVENT_EXIT: + return "application"; + + case UA_EVENT_CALL_INCOMING: + case UA_EVENT_CALL_RINGING: + case UA_EVENT_CALL_PROGRESS: + case UA_EVENT_CALL_ESTABLISHED: + case UA_EVENT_CALL_CLOSED: + case UA_EVENT_CALL_TRANSFER_FAILED: + case UA_EVENT_CALL_DTMF_START: + case UA_EVENT_CALL_DTMF_END: + case UA_EVENT_CALL_RTCP: + return "call"; + + default: + return "other"; + } +} + + +static int add_rtcp_stats(struct odict *od_parent, const struct rtcp_stats *rs) +{ + struct odict *od = NULL, *tx = NULL, *rx = NULL; + int err = 0; + + if (!od_parent || !rs) + return EINVAL; + + err = odict_alloc(&od, 8); + err |= odict_alloc(&tx, 8); + err |= odict_alloc(&rx, 8); + if (err) + goto out; + + err = odict_entry_add(tx, "sent", ODICT_INT, (int64_t)rs->tx.sent); + err |= odict_entry_add(tx, "lost", ODICT_INT, (int64_t)rs->tx.lost); + err |= odict_entry_add(tx, "jit", ODICT_INT, (int64_t)rs->tx.jit); + if (err) + goto out; + + err = odict_entry_add(rx, "sent", ODICT_INT, (int64_t)rs->rx.sent); + err |= odict_entry_add(rx, "lost", ODICT_INT, (int64_t)rs->rx.lost); + err |= odict_entry_add(rx, "jit", ODICT_INT, (int64_t)rs->rx.jit); + if (err) + goto out; + + err = odict_entry_add(od, "tx", ODICT_OBJECT, tx); + err |= odict_entry_add(od, "rx", ODICT_OBJECT, rx); + err |= odict_entry_add(od, "rtt", ODICT_INT, (int64_t)rs->rtt); + if (err) + goto out; + + /* add object to the parent */ + err = odict_entry_add(od_parent, "rtcp_stats", ODICT_OBJECT, od); + if (err) + goto out; + + out: + mem_deref(od); + + return err; +} + + +int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev, + struct call *call, const char *prm) +{ + const char *event_str = uag_event_str(ev); + int err = 0; + + if (!od) + return EINVAL; + + err |= odict_entry_add(od, "type", ODICT_STRING, event_str); + err |= odict_entry_add(od, "class", + ODICT_STRING, event_class_name(ev)); + err |= odict_entry_add(od, "accountaor", ODICT_STRING, ua_aor(ua)); + if (err) + goto out; + + if (call) { + + const char *dir; + + dir = call_is_outgoing(call) ? "outgoing" : "incoming"; + + err |= odict_entry_add(od, "direction", ODICT_STRING, dir); + err |= odict_entry_add(od, "peeruri", + ODICT_STRING, call_peeruri(call)); + if (err) + goto out; + } + + if (str_isset(prm)) { + err = odict_entry_add(od, "param", ODICT_STRING, prm); + if (err) + goto out; + } + + if (ev == UA_EVENT_CALL_RTCP) { + struct stream *strm = NULL; + + if (0 == str_casecmp(prm, "audio")) + strm = audio_strm(call_audio(call)); +#ifdef USE_VIDEO + else if (0 == str_casecmp(prm, "video")) + strm = video_strm(call_video(call)); +#endif + + err = add_rtcp_stats(od, stream_rtcp_stats(strm)); + if (err) + goto out; + } + + out: + + return err; +} + + +/** + * 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_SHUTDOWN: return "SHUTDOWN"; + case UA_EVENT_EXIT: return "EXIT"; + 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"; + case UA_EVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED"; + case UA_EVENT_CALL_DTMF_START: return "CALL_DTMF_START"; + case UA_EVENT_CALL_DTMF_END: return "CALL_DTMF_END"; + case UA_EVENT_CALL_RTCP: return "CALL_RTCP"; + default: return "?"; + } +} diff --git a/src/h264.c b/src/h264.c new file mode 100644 index 0000000..2bb4a26 --- /dev/null +++ b/src/h264.c @@ -0,0 +1,182 @@ +/** + * @file src/h264.c H.264 video codec packetization (RFC 3984) + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> + + +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 h264_fu_hdr_encode(const struct h264_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 h264_fu_hdr_decode(struct h264_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, uint32_t rtp_ts, + videnc_packet_h *pkth, void *arg) +{ + return pkth(eof, rtp_ts, hdr, hdr_sz, buf, sz, arg); +} + + +int h264_nal_send(bool first, bool last, + bool marker, uint32_t ihdr, uint32_t rtp_ts, + 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, rtp_ts, + 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, + rtp_ts, + 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, + rtp_ts, + pkth, arg); + } + + return err; +} + + +int h264_packetize(uint32_t rtp_ts, const uint8_t *buf, size_t len, + size_t pktsize, videnc_packet_h *pkth, void *arg) +{ + const uint8_t *start = buf; + const uint8_t *end = buf + len; + const uint8_t *r; + int err = 0; + + r = h264_find_startcode(start, 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], + rtp_ts, r+1, r1-r-1, pktsize, + pkth, arg); + r = r1; + } + + return err; +} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..650689d --- /dev/null +++ b/src/log.c @@ -0,0 +1,153 @@ +/** + * @file log.c Logging + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + + +static struct { + struct list logl; + bool debug; + bool info; + bool stder; +} lg = { + LIST_INIT, + false, + true, + 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_info(bool enable) +{ + lg.info = 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 == LEVEL_WARN || level == LEVEL_ERROR; + + if (color) + (void)re_fprintf(stdout, "\x1b[31m"); /* Red */ + + (void)re_fprintf(stdout, "%s", buf); + + if (color) + (void)re_fprintf(stdout, "\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 ((LEVEL_DEBUG == level) && !lg.debug) + return; + + if ((LEVEL_INFO == level) && !lg.info) + 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(LEVEL_DEBUG, fmt, ap); + va_end(ap); +} + + +void info(const char *fmt, ...) +{ + va_list ap; + + if (!lg.info) + return; + + va_start(ap, fmt); + vlog(LEVEL_INFO, fmt, ap); + va_end(ap); +} + + +void warning(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(LEVEL_WARN, fmt, ap); + va_end(ap); +} + + +void error_msg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(LEVEL_ERROR, fmt, ap); + va_end(ap); +} diff --git a/src/magic.h b/src/magic.h new file mode 100644 index 0000000..523d38a --- /dev/null +++ b/src/magic.h @@ -0,0 +1,38 @@ +/** + * @file magic.h Interface to magic macros + * + * Copyright (C) 2010 Creytiv.com + */ + + +#ifndef RELEASE + +#ifndef MAGIC +#error "macro MAGIC must be defined" +#endif + + +/* + * Any C compiler conforming to C99 or later MUST support __func__ + */ +#if __STDC_VERSION__ >= 199901L +#define __MAGIC_FUNC__ (const char *)__func__ +#else +#define __MAGIC_FUNC__ __FUNCTION__ +#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", \ + __MAGIC_FUNC__, 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..b789648 --- /dev/null +++ b/src/main.c @@ -0,0 +1,265 @@ +/** + * @file src/main.c Main application code + * + * Copyright (C) 2010 - 2015 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) { + mod_close(); + exit(0); + } + + term = true; + + info("terminated by signal %d\n", sig); + + ua_stop_all(false); +} + + +static void ua_exit_handler(void *arg) +{ + (void)arg; + debug("ua exited -- stopping main runloop\n"); + + /* The main run-loop can be stopped now */ + re_cancel(); +} + + +static void usage(void) +{ + (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> Execute commands (repeat)\n" + "\t-f <path> Config path\n" + "\t-m <module> Pre-load modules (repeat)\n" + "\t-p <path> Audio files\n" + "\t-h -? Help\n" + "\t-t Test and exit\n" + "\t-u <parameters> Extra UA parameters\n" + "\t-v Verbose debug\n" + ); +} + + +int main(int argc, char *argv[]) +{ + bool prefer_ipv6 = false, run_daemon = false, test = false; + const char *ua_eprm = NULL; + const char *execmdv[16]; + const char *audio_path = NULL; + const char *modv[16]; + size_t execmdc = 0; + size_t modc = 0; + size_t i; + int err; + + (void)re_fprintf(stdout, "baresip v%s" + " Copyright (C) 2010 - 2017" + " Alfred E. Heggestad et al.\n", + BARESIP_VERSION); + + (void)sys_coredump_set(true); + + err = libre_init(); + if (err) + goto out; + +#ifdef HAVE_GETOPT + for (;;) { + const int c = getopt(argc, argv, "6de:f:p:hu:vtm:"); + if (0 > c) + break; + + switch (c) { + + case '?': + case 'h': + usage(); + return -2; + +#if HAVE_INET6 + case '6': + prefer_ipv6 = true; + break; +#endif + + case 'd': + run_daemon = true; + break; + + case 'e': + if (execmdc >= ARRAY_SIZE(execmdv)) { + warning("max %zu commands\n", + ARRAY_SIZE(execmdv)); + err = EINVAL; + goto out; + } + execmdv[execmdc++] = optarg; + break; + + case 'f': + conf_path_set(optarg); + break; + + case 'm': + if (modc >= ARRAY_SIZE(modv)) { + warning("max %zu modules\n", + ARRAY_SIZE(modv)); + err = EINVAL; + goto out; + } + modv[modc++] = optarg; + break; + + case 'p': + audio_path = optarg; + break; + + case 't': + test = true; + break; + + case 'u': + ua_eprm = optarg; + break; + + case 'v': + log_enable_debug(true); + break; + + default: + break; + } + } +#else + (void)argc; + (void)argv; +#endif + + err = conf_configure(); + if (err) { + warning("main: configure failed: %m\n", err); + goto out; + } + + /* + * Initialise the top-level baresip struct, must be + * done AFTER configuration is complete. + */ + err = baresip_init(conf_config(), prefer_ipv6); + if (err) { + warning("main: baresip init failed (%m)\n", err); + goto out; + } + + /* Set audio path preferring the one given in -p argument (if any) */ + if (audio_path) + play_set_path(baresip_player(), audio_path); + else if (str_isset(conf_config()->audio.audio_path)) { + play_set_path(baresip_player(), + conf_config()->audio.audio_path); + } + + /* NOTE: must be done after all arguments are processed */ + if (modc) { + + info("pre-loading modules: %zu\n", modc); + + for (i=0; i<modc; i++) { + + err = module_preload(modv[i]); + if (err) { + re_fprintf(stderr, + "could not pre-load module" + " '%s' (%m)\n", modv[i], err); + } + } + } + + /* Initialise User Agents */ + err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")", + true, true, true, prefer_ipv6); + if (err) + goto out; + + uag_set_exit_handler(ua_exit_handler, NULL); + + if (ua_eprm) { + err = uag_set_extra_params(ua_eprm); + if (err) + goto out; + } + + if (test) + 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"); + + /* Execute any commands from input arguments */ + for (i=0; i<execmdc; i++) { + ui_input_str(execmdv[i]); + } + + /* Main loop */ + err = re_main(signal_handler); + + out: + if (err) + ua_stop_all(true); + + ua_close(); + conf_close(); + + baresip_close(); + + /* NOTE: modules must be unloaded after all application + * activity has stopped. + */ + debug("main: unloading modules..\n"); + 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..1e1c78f --- /dev/null +++ b/src/menc.c @@ -0,0 +1,65 @@ +/** + * @file menc.c Media encryption + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** + * Register a new Media encryption module + * + * @param mencl List of Media-encryption modules + * @param menc Media encryption module + */ +void menc_register(struct list *mencl, struct menc *menc) +{ + if (!mencl || !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 mencl List of Media-encryption modules + * @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 struct list *mencl, const char *id) +{ + struct le *le; + + if (!mencl) + return NULL; + + 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..e47f182 --- /dev/null +++ b/src/message.c @@ -0,0 +1,192 @@ +/** + * @file src/message.c SIP MESSAGE -- RFC 3428 + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +struct message { + struct list lsnrl; + struct sip_lsnr *sip_lsnr; +}; + +struct message_lsnr { + struct le le; + message_recv_h *recvh; + void *arg; +}; + + +static void destructor(void *data) +{ + struct message *message = data; + + list_flush(&message->lsnrl); + mem_deref(message->sip_lsnr); +} + + +static void listener_destructor(void *data) +{ + struct message_lsnr *lsnr = data; + + list_unlink(&lsnr->le); +} + + +static void handle_message(struct message_lsnr *lsnr, struct ua *ua, + const struct sip_msg *msg) +{ + static const char ctype_text[] = "text/plain"; + struct pl ctype_pl = {ctype_text, sizeof(ctype_text)-1}; + (void)ua; + + if (msg_ctype_cmp(&msg->ctyp, "text", "plain") && lsnr->recvh) { + + lsnr->recvh(&msg->from.auri, &ctype_pl, + msg->mb, lsnr->arg); + + (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 message *message = arg; + struct ua *ua; + struct le *le = message->lsnrl.head; + bool hdld = false; + + 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; + } + + while (le) { + struct message_lsnr *lsnr = le->data; + + le = le->next; + + handle_message(lsnr, ua, msg); + + hdld = true; + } + + return hdld; +} + + +int message_init(struct message **messagep) +{ + struct message *message; + int err = 0; + + if (!messagep) + return EINVAL; + + message = mem_zalloc(sizeof(*message), destructor); + if (!message) + return ENOMEM; + + /* note: cannot create sip listener here, there is not UAs yet */ + + if (err) + mem_deref(message); + else + *messagep = message; + + return err; +} + + +int message_listen(struct message_lsnr **lsnrp, struct message *message, + message_recv_h *recvh, void *arg) +{ + struct message_lsnr *lsnr; + int err = 0; + + if (!message || !recvh) + return EINVAL; + + /* create the SIP listener if it does not exist */ + if (!message->sip_lsnr) { + + err = sip_listen(&message->sip_lsnr, uag_sip(), true, + request_handler, message); + if (err) + goto out; + } + + lsnr = mem_zalloc(sizeof(*lsnr), listener_destructor); + + lsnr->recvh = recvh; + lsnr->arg = arg; + + list_append(&message->lsnrl, &lsnr->le, lsnr); + + if (lsnrp) + *lsnrp = lsnr; + + out: + return err; +} + + +/** + * Send SIP instant MESSAGE to a peer + * + * @param ua User-Agent object + * @param peer Peer SIP Address + * @param msg Message to send + * @param resph Response handler + * @param arg Handler argument + * + * @return 0 if success, otherwise errorcode + */ +int message_send(struct ua *ua, const char *peer, const char *msg, + sip_resp_h *resph, void *arg) +{ + 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, resph, arg, + "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..f3e8d06 --- /dev/null +++ b/src/metric.c @@ -0,0 +1,90 @@ +/** + * @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 (!metric->started) + return; + + 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(); + + metric->started = true; +} + + +void metric_init(struct metric *metric) +{ + if (!metric) + return; + + tmr_start(&metric->tmr, 100, tmr_handler, metric); +} + + +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..4349589 --- /dev/null +++ b/src/mnat.c @@ -0,0 +1,90 @@ +/** + * @file mnat.c Media NAT + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "core.h" + + +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 mnatl List of Media-NAT modules + * @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, struct list *mnatl, + 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 mnatl List of Media-NAT modules + * @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 struct list *mnatl, const char *id) +{ + struct mnat *mnat; + struct le *le; + + if (!mnatl) + return NULL; + + 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..68d469e --- /dev/null +++ b/src/module.c @@ -0,0 +1,258 @@ +/** + * @file src/module.c Module loading + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/* + * Append module extension, if not exist + * + * input: foobar + * output: foobar.so + * + */ +static void append_extension(char *buf, size_t sz, const char *name) +{ + if (0 == re_regex(name, str_len(name), "[^.]+"MOD_EXT, NULL)) { + + str_ncpy(buf, name, sz); + } + else { + re_snprintf(buf, sz, "%s"MOD_EXT, name); + } +} + + +#ifdef STATIC + +/* Declared in static.c */ +extern const struct mod_export *mod_table[]; + +static const struct mod_export *lookup_static_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[FS_PATH_MAX]; + char namestr[256]; + struct mod *m = NULL; + int err = 0; + + if (!name) + return EINVAL; + +#ifdef STATIC + /* Try static first */ + pl_strcpy(name, namestr, sizeof(namestr)); + + if (mod_find(namestr)) { + info("static module already loaded: %r\n", name); + return EALREADY; + } + + err = mod_add(&m, lookup_static_module(name)); + if (!err) + goto out; +#else + (void)namestr; +#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 mod *mod = NULL; + const struct mod_export *me; + + debug("module: loading app %r\n", val); + + if (load_module(&mod, arg, val)) { + return 0; + } + + me = mod_export(mod); + if (0 != str_casecmp(me->type, "application")) { + warning("module_app %r should be type application (%s)\n", + val, me->type); + } + + 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) +{ + struct le *le = list_tail(mod_list()); + + /* unload in reverse order */ + while (le) { + struct mod *mod = le->data; + const struct mod_export *me = mod_export(mod); + + le = le->prev; + + if (me && 0 == str_casecmp(me->type, "application")) { + debug("module: unloading app %s\n", me->name); + mem_deref(mod); + } + } +} + + +int module_preload(const char *module) +{ + struct pl path, name; + + if (!module) + return EINVAL; + + pl_set_str(&path, "."); + pl_set_str(&name, module); + + return load_module(NULL, &path, &name); +} + + +/** + * Load a module by name or by filename + * + * @param name Module name incl/excl extension, excluding module path + * + * @return 0 if success, otherwise errorcode + * + * example: "foo" + * example: "foo.so" + */ +int module_load(const char *name) +{ + char filename[256]; + struct pl path, pl_name; + int err; + + if (!str_isset(name)) + return EINVAL; + + append_extension(filename, sizeof(filename), name); + + pl_set_str(&pl_name, filename); + + if (conf_get(conf_cur(), "module_path", &path)) + pl_set_str(&path, "."); + + err = load_module(NULL, &path, &pl_name); + + return err; +} + + +/** + * Unload a module by name or by filename + * + * @param name module name incl/excl extension, excluding module path + * + * example: "foo" + * example: "foo.so" + */ +void module_unload(const char *name) +{ + char filename[256]; + struct mod *mod; + + if (!str_isset(name)) + return; + + append_extension(filename, sizeof(filename), name); + + mod = mod_find(filename); + if (mod) { + info("unloading module: %s\n", filename); + mem_deref(mod); + return; + } + + info("ERROR: Module %s is not currently loaded\n", name); +} diff --git a/src/mos.c b/src/mos.c new file mode 100644 index 0000000..d86bbc1 --- /dev/null +++ b/src/mos.c @@ -0,0 +1,63 @@ +/** + * @file src/mos.c MOS (Mean Opinion Score) + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +static double rfactor_to_mos(double r) +{ + double mos; + + mos = 1 + (0.035) * (r) + (0.000007) * (r) * ((r) - 60) * (100 - (r)); + + if (mos > 5) + mos = 5; + + return mos; +} + + +/** + * Calculate Pseudo-MOS (Mean Opinion Score) + * + * @param r_factor Pointer to where R-factor is written (optional) + * @param rtt Average roundtrip time + * @param jitter Jitter + * @param num_packets_lost Number of packets lost + * + * @return The calculated MOS value from 1 to 5 + * + * Reference: https://metacpan.org/pod/Algorithm::MOS + */ +double mos_calculate(double *r_factor, double rtt, + double jitter, uint32_t num_packets_lost) +{ + double effective_latency = rtt + (jitter * 2) + 10; + double mos_val; + double r; + + if (effective_latency < 160) { + r = 93.2 - (effective_latency / 40); + } + else { + r = 93.2 - (effective_latency - 120) / 10; + } + + r = r - (num_packets_lost * 2.5); + + if (r > 100) + r = 100; + else if (r < 0) + r = 0; + + mos_val = rfactor_to_mos(r); + + if (r_factor) + *r_factor = r; + + return mos_val; +} diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..dff8444 --- /dev/null +++ b/src/net.c @@ -0,0 +1,561 @@ +/** + * @file src/net.c Networking code + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +struct network { + 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[NET_MAX_NS];/**< 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; +}; + + +static int net_dnssrv_add(struct network *net, const struct sa *sa) +{ + if (!net) + return EINVAL; + + if (net->nsn >= ARRAY_SIZE(net->nsv)) + return E2BIG; + + sa_cpy(&net->nsv[net->nsn++], sa); + + return 0; +} + + +static int net_dns_srv_get(const struct network *net, + struct sa *srvv, uint32_t *n, bool *from_sys) +{ + struct sa nsv[NET_MAX_NS]; + uint32_t i, nsn = ARRAY_SIZE(nsv); + int err; + + err = dns_srv_get(NULL, 0, nsv, &nsn); + if (err) { + nsn = 0; + } + + if (net->nsn) { + + if (net->nsn > *n) + return E2BIG; + + /* Use any configured nameservers */ + for (i=0; i<net->nsn; i++) { + srvv[i] = net->nsv[i]; + } + + *n = net->nsn; + + if (from_sys) + *from_sys = false; + } + else { + if (nsn > *n) + return E2BIG; + + for (i=0; i<nsn; i++) + srvv[i] = nsv[i]; + + *n = nsn; + + if (from_sys) + *from_sys = true; + } + + return 0; +} + + +/** + * Check for DNS Server updates + */ +static void dns_refresh(struct network *net) +{ + struct sa nsv[NET_MAX_NS]; + uint32_t nsn; + int err; + + nsn = ARRAY_SIZE(nsv); + + err = net_dns_srv_get(net, nsv, &nsn, NULL); + if (err) + return; + + (void)dnsc_srv_set(net->dnsc, nsv, nsn); +} + + +/** + * Detect changes in IP address(es) + */ +static void ipchange_handler(void *arg) +{ + struct network *net = arg; + bool change; + + tmr_start(&net->tmr, net->interval * 1000, ipchange_handler, net); + + dns_refresh(net); + + change = net_check(net); + if (change && net->ch) { + net->ch(net->arg); + } +} + + +/** + * Check if local IP address(es) changed + * + * @param net Network instance + * + * @return True if changed, otherwise false + */ +bool net_check(struct network *net) +{ + struct sa laddr = net->laddr; +#ifdef HAVE_INET6 + struct sa laddr6 = net->laddr6; +#endif + bool change = false; + + if (!net) + return 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(struct network *net) +{ + struct sa nsv[NET_MAX_NS]; + uint32_t nsn = ARRAY_SIZE(nsv); + int err; + + err = net_dns_srv_get(net, nsv, &nsn, NULL); + if (err) + return err; + + 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); +} + + +static void net_destructor(void *data) +{ + struct network *net = data; + + tmr_cancel(&net->tmr); + mem_deref(net->dnsc); +} + + +/** + * Initialise networking + * + * @param netp Pointer to allocated network instance + * @param cfg Network configuration + * @param af Preferred address family + * + * @return 0 if success, otherwise errorcode + */ +int net_alloc(struct network **netp, const struct config_net *cfg, int af) +{ + struct network *net; + struct sa nsv[NET_MAX_NS]; + uint32_t nsn = ARRAY_SIZE(nsv); + char buf4[128] = "", buf6[128] = ""; + int err; + + if (!netp || !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_msg("libre was compiled without IPv6-support" + ", but baresip was compiled with\n"); + return EAFNOSUPPORT; + } +#else + if (check_ipv6()) { + error_msg("libre was compiled with IPv6-support" + ", but baresip was compiled without\n"); + return EAFNOSUPPORT; + } +#endif + + net = mem_zalloc(sizeof(*net), net_destructor); + if (!net) + return ENOMEM; + + net->cfg = *cfg; + net->af = af; + + tmr_init(&net->tmr); + + if (cfg->nsc) { + size_t i; + + for (i=0; i<cfg->nsc; i++) { + + const char *ns = cfg->nsv[i].addr; + struct sa sa; + + err = sa_decode(&sa, ns, str_len(ns)); + if (err) { + warning("net: dns_server:" + " could not decode `%s' (%m)\n", + ns, err); + goto out; + } + + err = net_dnssrv_add(net, &sa); + if (err) { + warning("net: failed to add nameserver: %m\n", + err); + goto out; + } + } + } + + /* Initialise DNS resolver */ + err = dns_init(net); + if (err) { + warning("net: dns_init: %m\n", err); + goto out; + } + + 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); + err = EADDRNOTAVAIL; + goto out; + } + } + 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 + } + + if (sa_isset(&net->laddr, SA_ADDR)) { + re_snprintf(buf4, sizeof(buf4), " IPv4=%s:%j", + net->ifname, &net->laddr); + } +#ifdef HAVE_INET6 + if (sa_isset(&net->laddr6, SA_ADDR)) { + re_snprintf(buf6, sizeof(buf6), " IPv6=%s:%j", + net->ifname6, &net->laddr6); + } +#endif + + (void)dns_srv_get(net->domain, sizeof(net->domain), nsv, &nsn); + + info("Local network address: %s %s\n", + buf4, buf6); + + out: + if (err) + mem_deref(net); + else + *netp = net; + + return err; +} + + +/** + * Use a specific DNS server + * + * @param net Network instance + * @param ns DNS Server IP address and port + * + * @return 0 if success, otherwise errorcode + */ +int net_use_nameserver(struct network *net, const struct sa *ns) +{ + struct dnsc *dnsc; + int err; + + if (!net || !ns) + return EINVAL; + + err = dnsc_alloc(&dnsc, NULL, ns, 1); + if (err) + return err; + + mem_deref(net->dnsc); + net->dnsc = dnsc; + + return 0; +} + + +/** + * Check for networking changes with a regular interval + * + * @param net Network instance + * @param interval Interval in seconds + * @param ch Handler called when a change was detected + * @param arg Handler argument + */ +void net_change(struct network *net, uint32_t interval, + net_change_h *ch, void *arg) +{ + if (!net) + return; + + net->interval = interval; + net->ch = ch; + net->arg = arg; + + if (interval) + tmr_start(&net->tmr, interval * 1000, ipchange_handler, net); + else + tmr_cancel(&net->tmr); +} + + +void net_force_change(struct network *net) +{ + if (net && net->ch) { + net->ch(net->arg); + } +} + + +static int dns_debug(struct re_printf *pf, const struct network *net) +{ + struct sa nsv[NET_MAX_NS]; + uint32_t i, nsn = ARRAY_SIZE(nsv); + bool from_sys = false; + int err; + + if (!net) + return 0; + + err = net_dns_srv_get(net, nsv, &nsn, &from_sys); + if (err) + nsn = 0; + + err = re_hprintf(pf, " DNS Servers from %s: (%u)\n", + from_sys ? "System" : "Config", nsn); + for (i=0; i<nsn; i++) + err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]); + + return err; +} + + +int net_af(const struct network *net) +{ + if (!net) + return AF_UNSPEC; + + return net->af; +} + + +/** + * Print networking debug information + * + * @param pf Print handler for debug output + * @param net Network instance + * + * @return 0 if success, otherwise errorcode + */ +int net_debug(struct re_printf *pf, const struct network *net) +{ + int err; + + if (!net) + return 0; + + 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 |= re_hprintf(pf, " Domain: %s\n", net->domain); + + err |= net_if_debug(pf, NULL); + + err |= net_rt_debug(pf, NULL); + + err |= dns_debug(pf, net); + + return err; +} + + +/** + * Get the local IP Address for a specific Address Family (AF) + * + * @param net Network instance + * @param af Address Family + * + * @return Local IP Address + */ +const struct sa *net_laddr_af(const struct network *net, int af) +{ + if (!net) + return NULL; + + 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 + * + * @param net Network instance + * + * @return DNS Client + */ +struct dnsc *net_dnsc(const struct network *net) +{ + if (!net) + return NULL; + + return net->dnsc; +} + + +/** + * Get the network domain name + * + * @param net Network instance + * + * @return Network domain + */ +const char *net_domain(const struct network *net) +{ + if (!net) + return NULL; + + return net->domain[0] ? net->domain : NULL; +} diff --git a/src/play.c b/src/play.c new file mode 100644 index 0000000..aa0b59f --- /dev/null +++ b/src/play.c @@ -0,0 +1,352 @@ +/** + * @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 {PTIME = 40}; + +/** 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 const char default_play_path[FS_PATH_MAX] = PREFIX "/share/baresip"; + + +struct player { + struct list playl; + char play_path[FS_PATH_MAX]; +}; + + +static void tmr_polling(void *arg); + + +static void tmr_stop(void *arg) +{ + struct play *play = arg; + debug("play: player complete.\n"); + mem_deref(play); +} + + +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) + tmr_start(&play->tmr, 1, tmr_stop, arg); + } + + lock_rel(play->lock); +} + + +/** + * NOTE: DSP cannot be destroyed inside handler + */ +static void write_handler(void *sampv, size_t sampc, void *arg) +{ + struct play *play = arg; + size_t sz = sampc * 2; + size_t pos = 0; + size_t left; + size_t count; + + lock_write_get(play->lock); + + if (play->eof) + goto silence; + + while (pos < sz) { + left = mbuf_get_left(play->mb); + count = (left > sz - pos) ? sz - pos : left; + + (void)mbuf_read_mem(play->mb, (uint8_t *)sampv + pos, count); + + pos += count; + + if (pos < sz) { + if (play->repeat > 0) + play->repeat--; + + if (play->repeat == 0) { + play->eof = true; + goto silence; + } + + play->mb->pos = 0; + } + } + + silence: + if (play->eof) + memset((uint8_t *)sampv + pos, 0, sz - pos); + + lock_rel(play->lock); +} + + +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; + int16_t *p = (void *)buf; + + n = sizeof(buf); + + err = aufile_read(af, buf, &n); + if (err || !n) + break; + + switch (prm.fmt) { + + case AUFMT_S16LE: + /* convert from Little-Endian to Native-Endian */ + for (i=0; i<n/2; i++) { + int16_t s = sys_ltohs(*p++); + err |= mbuf_write_u16(mb, s); + } + + 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 player Audio-file player + * @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 player *player, + struct mbuf *tone, uint32_t srate, + uint8_t ch, int repeat) +{ + struct auplay_prm wprm; + struct play *play; + struct config *cfg; + int err; + + if (!player) + return EINVAL; + 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.ch = ch; + wprm.srate = srate; + wprm.ptime = PTIME; + wprm.fmt = AUFMT_S16LE; + + err = auplay_alloc(&play->auplay, baresip_auplayl(), + cfg->audio.alert_mod, &wprm, + cfg->audio.alert_dev, write_handler, play); + if (err) + goto out; + + list_append(&player->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 player Audio-file player + * @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, struct player *player, + const char *filename, int repeat) +{ + struct mbuf *mb; + char path[FS_PATH_MAX]; + uint32_t srate = 0; + uint8_t ch = 0; + int err; + + if (!player) + return EINVAL; + if (playp && *playp) + return EALREADY; + + if (re_snprintf(path, sizeof(path), "%s/%s", + player->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, player, mb, srate, ch, repeat); + + out: + mem_deref(mb); + + return err; +} + + +static void player_destructor(void *data) +{ + struct player *player = data; + + list_flush(&player->playl); +} + + +int play_init(struct player **playerp) +{ + struct player *player; + + if (!playerp) + return EINVAL; + + player = mem_zalloc(sizeof(*player), player_destructor); + if (!player) + return ENOMEM; + + list_init(&player->playl); + + str_ncpy(player->play_path, default_play_path, + sizeof(player->play_path)); + + *playerp = player; + + return 0; +} + + +void play_set_path(struct player *player, const char *path) +{ + if (!player) + return; + + str_ncpy(player->play_path, path, sizeof(player->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..23bb337 --- /dev/null +++ b/src/reg.c @@ -0,0 +1,265 @@ +/** + * @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 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_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 bool contact_handler(const struct sip_hdr *hdr, + const struct sip_msg *msg, void *arg) +{ + struct reg *reg = arg; + struct sip_addr addr; + (void)msg; + + if (sip_addr_decode(&addr, &hdr->val)) + return false; + + /* match our contact */ + return 0 == pl_strcasecmp(&addr.uri.user, ua_local_cuser(reg->ua)); +} + + +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->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; + + hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT, + contact_handler, reg); + if (hdr) { + struct sip_addr addr; + struct pl pval; + + if (0 == sip_addr_decode(&addr, &hdr->val) && + 0 == msg_param_decode(&addr.params, "pub-gruu", + &pval)) { + ua_pub_gruu_set(reg->ua, &pval); + } + } + + 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; + + 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; + + 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_local_cuser(reg->ua), + routev[0] ? routev : NULL, + routev[0] ? 1 : 0, + reg->id, + sip_auth_handler, ua_account(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->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; +} + + +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); + + 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/rtpext.c b/src/rtpext.c new file mode 100644 index 0000000..82a6f6e --- /dev/null +++ b/src/rtpext.c @@ -0,0 +1,112 @@ +/** + * @file rtpext.c RTP Header Extensions + * + * Copyright (C) 2017 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/* + * RFC 5285 A General Mechanism for RTP Header Extensions + * + * - One-Byte Header: Supported + * - Two-Byte Header: Not supported + */ + + +int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes) +{ + int err = 0; + + if (!mb || !num_bytes) + return EINVAL; + + if (num_bytes & 0x3) { + warning("rtpext: hdr_encode: num_bytes (%zu) must be multiple" + " of 4\n", num_bytes); + return EINVAL; + } + + err |= mbuf_write_u16(mb, htons(RTPEXT_TYPE_MAGIC)); + err |= mbuf_write_u16(mb, htons(num_bytes / 4)); + + return err; +} + + +int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len, + const uint8_t *data) +{ + size_t start; + int err; + + if (!mb || !data) + return EINVAL; + + if (id < RTPEXT_ID_MIN || id > RTPEXT_ID_MAX) + return EINVAL; + if (len < RTPEXT_LEN_MIN || len > RTPEXT_LEN_MAX) + return EINVAL; + + start = mb->pos; + + err = mbuf_write_u8(mb, id << 4 | (len-1)); + err |= mbuf_write_mem(mb, data, len); + if (err) + return err; + + /* padding */ + while ((mb->pos - start) & 0x03) + err |= mbuf_write_u8(mb, 0x00); + + return err; +} + + +int rtpext_decode(struct rtpext *ext, struct mbuf *mb) +{ + uint8_t v; + int err; + + if (!ext || !mb) + return EINVAL; + + if (mbuf_get_left(mb) < 1) + return EBADMSG; + + memset(ext, 0, sizeof(*ext)); + + v = mbuf_read_u8(mb); + + ext->id = v >> 4; + ext->len = (v & 0x0f) + 1; + + if (ext->id < RTPEXT_ID_MIN || ext->id > RTPEXT_ID_MAX) { + warning("rtpext: invalid ID %u\n", ext->id); + return EBADMSG; + } + if (ext->len > mbuf_get_left(mb)) { + warning("rtpext: short read\n"); + return ENODATA; + } + + err = mbuf_read_mem(mb, ext->data, ext->len); + if (err) + return err; + + /* skip padding */ + while (mbuf_get_left(mb)) { + uint8_t pad = mbuf_buf(mb)[0]; + + if (pad != 0x00) + break; + + mbuf_advance(mb, 1); + } + + return 0; +} diff --git a/src/rtpkeep.c b/src/rtpkeep.c new file mode 100644 index 0000000..6b6cf81 --- /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, + 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..da4889c --- /dev/null +++ b/src/sdp.c @@ -0,0 +1,191 @@ +/** + * @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 + * + * @param ctype_prm Content type parameter + * @param mb Mbuffer containing the SDP + * + * @return 0 if success, otherwise errorcode + */ +int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb) +{ + struct pl bnd, s, e, p; + char expr[64]; + int err; + + if (!ctype_prm || !mb) + return EINVAL; + + /* fetch the boundary tag, excluding quotes */ + err = re_regex(ctype_prm->p, ctype_prm->l, + "boundary=[~]+", &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..314f45c --- /dev/null +++ b/src/sipreq.c @@ -0,0 +1,150 @@ +/** + * @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: + if (sr->resph) + 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 || !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_account(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..a35e0d8 --- /dev/null +++ b/src/srcs.mk @@ -0,0 +1,55 @@ +# +# srcs.mk All application source files. +# +# Copyright (C) 2010 Creytiv.com +# + +SRCS += account.c +SRCS += aucodec.c +SRCS += audio.c +SRCS += aufilt.c +SRCS += aulevel.c +SRCS += auplay.c +SRCS += ausrc.c +SRCS += baresip.c +SRCS += call.c +SRCS += cmd.c +SRCS += conf.c +SRCS += config.c +SRCS += contact.c +SRCS += event.c +SRCS += log.c +SRCS += menc.c +SRCS += message.c +SRCS += metric.c +SRCS += mnat.c +SRCS += module.c +SRCS += mos.c +SRCS += net.c +SRCS += play.c +SRCS += realtime.c +SRCS += reg.c +SRCS += rtpext.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 += h264.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..38db2d6 --- /dev/null +++ b/src/stream.c @@ -0,0 +1,733 @@ +/** + * @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" + + +enum { + RTP_RECV_SIZE = 8192, + RTP_CHECK_INTERVAL = 1000 /* how often to check for RTP [ms] */ +}; + + +static void stream_close(struct stream *strm, int err) +{ + stream_error_h *errorh = strm->errorh; + + strm->terminated = true; + strm->errorh = NULL; + + if (errorh) { + errorh(strm, err, strm->errorh_arg); + } +} + + +static void check_rtp_handler(void *arg) +{ + struct stream *strm = arg; + const uint64_t now = tmr_jiffies(); + int diff_ms; + + tmr_start(&strm->tmr_rtp, RTP_CHECK_INTERVAL, + check_rtp_handler, strm); + + /* If no RTP was received at all, check later */ + if (!strm->ts_last) + return; + + /* We are in sendrecv mode, check when the last RTP packet + * was received. + */ + if (sdp_media_dir(strm->sdp) == SDP_SENDRECV) { + + diff_ms = (int)(now - strm->ts_last); + + debug("stream: last \"%s\" RTP packet: %d milliseconds\n", + sdp_media_name(strm->sdp), diff_ms); + + /* check for large jumps in time */ + if (diff_ms > (3600 * 1000)) { + strm->ts_last = 0; + return; + } + + if (diff_ms > (int)strm->rtp_timeout_ms) { + + info("stream: no %s RTP packets received for" + " %d milliseconds\n", + sdp_media_name(strm->sdp), diff_ms); + + stream_close(strm, ETIMEDOUT); + } + } + else { + re_printf("check_rtp: not checking (dir=%s)\n", + sdp_dir_name(sdp_media_dir(strm->sdp))); + } +} + + +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(const struct stream *s) +{ + bool started = s->metric_tx.n_packets>0 || s->metric_rx.n_packets>0; + + if (!started) + return; + + info("\n%-9s Transmit: Receive:\n" + "packets: %7u %7u\n" + "avg. bitrate: %7.1f %7.1f (kbit/s)\n" + "errors: %7d %7d\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, + s->metric_tx.n_err, s->metric_rx.n_err + ); + + 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); + + tmr_cancel(&s->tmr_rtp); + 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 handle_rtp(struct stream *s, const struct rtp_header *hdr, + struct mbuf *mb) +{ + struct rtpext extv[8]; + size_t extc = 0; + + /* RFC 5285 -- A General Mechanism for RTP Header Extensions */ + if (hdr->ext && hdr->x.len && mb) { + + const size_t pos = mb->pos; + const size_t end = mb->end; + const size_t ext_stop = mb->pos; + size_t ext_len; + size_t i; + int err; + + if (hdr->x.type != RTPEXT_TYPE_MAGIC) { + info("stream: unknown ext type ignored (0x%04x)\n", + hdr->x.type); + goto handler; + } + + ext_len = hdr->x.len*sizeof(uint32_t); + if (mb->pos < ext_len) { + warning("stream: corrupt rtp packet," + " not enough space for rtpext of %zu bytes\n", + ext_len); + return; + } + + mb->pos = mb->pos - ext_len; + mb->end = ext_stop; + + for (i=0; i<ARRAY_SIZE(extv) && mbuf_get_left(mb); i++) { + + err = rtpext_decode(&extv[i], mb); + if (err) { + warning("stream: rtpext_decode failed (%m)\n", + err); + return; + } + } + + extc = i; + + mb->pos = pos; + mb->end = end; + } + + handler: + s->rtph(hdr, extv, extc, mb, s->arg); + +} + + +static void rtp_handler(const struct sa *src, const struct rtp_header *hdr, + struct mbuf *mb, void *arg) +{ + struct stream *s = arg; + bool flush = false; + int err; + + s->ts_last = tmr_jiffies(); + + 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) + handle_rtp(s, hdr, NULL); + + handle_rtp(s, &hdr2, mb2); + + mem_deref(mb2); + } + else { + if (lostcalc(s, hdr->seq) > 0) + handle_rtp(s, hdr, NULL); + + handle_rtp(s, hdr, mb); + } +} + + +static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg) +{ + struct stream *s = arg; + (void)src; + + s->ts_last = tmr_jiffies(); + + 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); + + if (s->cfg.rtp_stats) + call_set_xrtpstat(s->call); + + ua_event(call_get_ua(s->call), UA_EVENT_CALL_RTCP, s->call, + "%s", sdp_media_name(stream_sdpmedia(s))); + 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_handler, rtcp_handler, s); + if (err) { + warning("stream: rtp_listen failed: af=%s ports=%u-%u" + " (%m)\n", net_af2name(af), + s->cfg.rtp_ports.min, s->cfg.rtp_ports.max, 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 stream_param *prm, + 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 || !prm || !cfg || !call || !rtph) + return EINVAL; + + s = mem_zalloc(sizeof(*s), stream_destructor); + if (!s) + return ENOMEM; + + s->cfg = *cfg; + s->call = call; + s->rtph = rtph; + s->rtcph = rtcph; + s->arg = arg; + s->pseq = -1; + s->rtcp = s->cfg.rtcp_enable; + + if (prm->use_rtp) { + err = stream_sock_alloc(s, call_af(call)); + if (err) { + warning("stream: failed to create socket" + " for media '%s' (%m)\n", name, 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, + s->rtp ? sa_port(rtp_local(s->rtp)) : 9, + (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 && s->rtp) { + 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->rtp) { + 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; + + metric_init(&s->metric_tx); + metric_init(&s->metric_rx); + + 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 = call_account(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 ext, 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), ext, + 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 (!s) + return; + + 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 / 1000); +} + + +void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms) +{ + if (!strm) + return; + + strm->rtp_timeout_ms = timeout_ms; + + tmr_cancel(&strm->tmr_rtp); + + if (timeout_ms) { + + info("stream: Enable RTP timeout (%u milliseconds)\n", + timeout_ms); + + strm->ts_last = tmr_jiffies(); + tmr_start(&strm->tmr_rtp, 10, check_rtp_handler, strm); + } +} + + +void stream_set_error_handler(struct stream *strm, + stream_error_h *errorh, void *arg) +{ + if (!strm) + return; + + strm->errorh = errorh; + strm->errorh_arg = arg; +} + + +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, " local: %J, remote: %J/%J\n", + sdp_media_laddr(s->sdp), + 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); +} + + +const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm) +{ + return strm ? &strm->rtcp_stats : NULL; +} diff --git a/src/ua.c b/src/ua.c new file mode 100644 index 0000000..0467e35 --- /dev/null +++ b/src/ua.c @@ -0,0 +1,1906 @@ +/** + * @file src/ua.c User-Agent + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" +#include <ctype.h> + + +/** Magic number */ +#define MAGIC 0x0a0a0a0a +#include "magic.h" + + +/** 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 pl extensionv[8]; /**< Vector of SIP extensions */ + size_t extensionc; /**< Number of SIP extensions */ + char *cuser; /**< SIP Contact username */ + char *pub_gruu; /**< SIP Public GRUU */ + int af; /**< Preferred Address Family */ + int af_media; /**< Preferred Address Family for media */ + enum presence_status my_status; /**< Presence Status */ +}; + +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 */ + sip_msg_h *subh; /**< Subscribe handler */ + ua_exit_h *exith; /**< UA Exit handler */ + void *arg; /**< UA Exit handler argument */ + char *eprm; /**< Extra UA parameters */ +#ifdef USE_TLS + struct tls *tls; /**< TLS Context */ +#endif +} uag = { + NULL, + LIST_INIT, + LIST_INIT, + NULL, + NULL, + NULL, + NULL, + NULL, + true, + true, + true, + false, + NULL, + NULL, + NULL, + NULL, +#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, + bool use_rtp); + + +/* This function is called when all SIP transactions are done */ +static void exit_handler(void *arg) +{ + (void)arg; + + ua_event(NULL, UA_EVENT_EXIT, NULL, NULL); + + debug("ua: sip-stack exit\n"); + + if (uag.exith) + uag.exith(uag.arg); +} + + +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; + + va_start(ap, fmt); + (void)re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* send event to all clients */ + le = uag.ehl.head; + while (le) { + struct ua_eh *eh = le->data; + le = le->next; + + 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 = NULL; + char params[256] = ""; + unsigned i; + int err; + + if (!ua) + return EINVAL; + + acc = ua->acc; + uri = ua->acc->luri; + uri.user = uri.password = pl_null; + + err = re_sdprintf(®_uri, "%H", uri_encode, &uri); + if (err) + goto out; + + if (uag.cfg && str_isset(uag.cfg->uuid)) { + if (re_snprintf(params, sizeof(params), + ";+sip.instance=\"<urn:uuid:%s>\"", + uag.cfg->uuid) < 0) { + err = ENOMEM; + goto out; + } + } + + if (acc->regq) { + if (re_snprintf(¶ms[strlen(params)], + sizeof(params) - strlen(params), + ";q=%s", acc->regq) < 0) { + err = ENOMEM; + goto out; + } + } + + if (acc->mnat && acc->mnat->ftag) { + if (re_snprintf(¶ms[strlen(params)], + sizeof(params) - strlen(params), + ";%s", acc->mnat->ftag) < 0) { + err = ENOMEM; + goto out; + } + } + + 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->outboundv[i]); + if (err) { + warning("ua: SIP register failed: %m\n", err); + goto out; + } + } + + out: + mem_deref(reg_uri); + + return err; +} + + +/** + * Unregister all Register clients of a User-Agent + * + * @param ua User-Agent + */ +void ua_unregister(struct ua *ua) +{ + struct le *le; + + if (!ua) + return; + + if (!list_isempty(&ua->regl)) + ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL); + + for (le = ua->regl.head; le; le = le->next) { + struct reg *reg = le->data; + + reg_unregister(reg); + } +} + + +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 struct call *ua_find_call_onhold(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; + + if (call_is_onhold(call)) + return call; + } + + return NULL; +} + + +static void resume_call(struct ua *ua) +{ + struct call *call; + + call = ua_find_call_onhold(ua); + if (call) { + ua_printf(ua, "resuming previous call with '%s'\n", + call_peeruri(call)); + call_hold(call, false); + } +} + + +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); + + switch (ev) { + + case CALL_EVENT_INCOMING: + + if (contact_block_access(baresip_contacts(), + peeruri)) { + + info("ua: blocked access: \"%s\"\n", peeruri); + + ua_event(ua, UA_EVENT_CALL_CLOSED, call, str); + mem_deref(call); + break; + } + + 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: + ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri); + break; + } + break; + + case CALL_EVENT_RINGING: + 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: + ua_event(ua, UA_EVENT_CALL_CLOSED, call, str); + mem_deref(call); + + resume_call(ua); + 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), true); + if (!err) { + struct pl pl; + + pl_set_str(&pl, str); + + err = call_connect(call2, &pl); + if (err) { + warning("ua: transfer: connect error: %m\n", + err); + } + } + + if (err) { + (void)call_notify_sipfrag(call, 500, "Call Error"); + mem_deref(call2); + } + break; + + case CALL_EVENT_TRANSFER_FAILED: + ua_event(ua, UA_EVENT_CALL_TRANSFER_FAILED, call, str); + break; + } +} + + +static void call_dtmf_handler(struct call *call, char key, void *arg) +{ + struct ua *ua = arg; + char key_str[2]; + + MAGIC_CHECK(ua); + + if (key != '\0') { + + key_str[0] = key; + key_str[1] = '\0'; + + ua_event(ua, UA_EVENT_CALL_DTMF_START, call, key_str); + } + else { + ua_event(ua, UA_EVENT_CALL_DTMF_END, call, NULL); + } +} + + +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, + bool use_rtp) +{ + const struct network *net = baresip_network(); + struct call_prm cprm; + int af = AF_UNSPEC; + int err; + + if (*callp) { + warning("ua: call_alloc: call is already allocated\n"); + return EALREADY; + } + + /* 1. if AF_MEDIA is set, we prefer it + * 2. otherwise fall back to SIP AF + */ + if (ua->af_media) { + af = ua->af_media; + } + else if (ua->af) { + af = ua->af; + } + + memset(&cprm, 0, sizeof(cprm)); + + sa_cpy(&cprm.laddr, net_laddr_af(net, af)); + cprm.vidmode = vidmode; + cprm.af = af; + cprm.use_rtp = use_rtp; + + err = call_alloc(callp, conf_config(), &ua->calls, + ua->acc->dispname, + local_uri ? local_uri : ua->acc->aor, + ua->acc, ua, &cprm, + msg, xcall, + net_dnsc(net), + call_event_handler, ua); + if (err) + return err; + + call_set_handlers(*callp, NULL, call_dtmf_handler, ua); + + return 0; +} + + +static void handle_options(struct ua *ua, const struct sip_msg *msg) +{ + struct sip_contact contact; + struct call *call = NULL; + struct mbuf *desc = NULL; + const struct sip_hdr *hdr; + bool accept_sdp = true; + int err; + + debug("ua: incoming OPTIONS message from %r (%J)\n", + &msg->from.auri, &msg->src); + + /* application/sdp is the default if the + Accept header field is not present */ + hdr = sip_msg_hdr(msg, SIP_HDR_ACCEPT); + if (hdr) { + accept_sdp = 0==pl_strcasecmp(&hdr->val, "application/sdp"); + } + + if (accept_sdp) { + + err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL, + false); + if (err) { + (void)sip_treply(NULL, uag.sip, msg, + 500, "Call Error"); + return; + } + + err = call_sdp_get(call, &desc, true); + if (err) + goto out; + } + + sip_contact_set(&contact, ua_cuser(ua), &msg->dst, msg->tp); + + err = sip_treplyf(NULL, NULL, uag.sip, + msg, true, 200, "OK", + "Allow: %s\r\n" + "%H" + "%H" + "%s" + "Content-Length: %zu\r\n" + "\r\n" + "%b", + uag_allowed_methods(), + ua_print_supported, ua, + sip_contact_print, &contact, + desc ? "Content-Type: application/sdp\r\n" : "", + desc ? mbuf_get_left(desc) : (size_t)0, + desc ? mbuf_buf(desc) : NULL, + desc ? mbuf_get_left(desc) : (size_t)0); + if (err) { + warning("ua: 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); + + if (!list_isempty(&ua->regl)) + ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL); + + list_flush(&ua->calls); + list_flush(&ua->regl); + mem_deref(ua->cuser); + mem_deref(ua->pub_gruu); + mem_deref(ua->acc); + + if (list_isempty(&uag.ual)) { + sip_close(uag.sip, false); + } +} + + +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)) { + warning("ua: 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; + char *buf = NULL; + 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 + + /* Decode SIP address */ + if (uag.eprm) { + err = re_sdprintf(&buf, "%s;%s", aor, uag.eprm); + if (err) + goto out; + aor = buf; + } + + err = account_alloc(&ua->acc, aor); + if (err) + goto out; + + /* generate a unique contact-user, this is needed to route + incoming requests when using multiple useragents */ + err = re_sdprintf(&ua->cuser, "%r-%p", &ua->acc->luri.user, ua); + 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 (uag.cfg && str_isset(uag.cfg->uuid)) + add_extension(ua, "gruu"); + + 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)) { + + warning("ua: outbound requires valid UUID!\n"); + err = ENOSYS; + goto out; + } + + for (i=0; i<ARRAY_SIZE(ua->acc->outboundv); i++) { + + if (ua->acc->outboundv[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: + mem_deref(buf); + if (err) + mem_deref(ua); + else if (uap) { + *uap = ua; + + ua->uap = uap; + } + + return err; +} + + +static int uri_complete(struct ua *ua, struct mbuf *buf, const char *uri) +{ + size_t len; + int err = 0; + + /* Skip initial whitespace */ + while (isspace(*uri)) + ++uri; + + len = str_len(uri); + + /* Append sip: scheme if missing */ + if (0 != re_regex(uri, len, "sip:")) + err |= mbuf_printf(buf, "sip:"); + + err |= mbuf_write_str(buf, 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(buf, "@[%r]", + &ua->acc->luri.host); + else +#endif + err |= mbuf_printf(buf, "@%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(buf, ":%u", ua->acc->luri.port); + break; + } + } + + 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; + int err = 0; + + if (!ua || !str_isset(uri)) + return EINVAL; + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + if (params) + err |= mbuf_printf(dialbuf, "<"); + + err |= uri_complete(ua, dialbuf, uri); + + 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, true); + 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; + } + + (void)call_hangup(call, scode, reason); + + ua_event(ua, UA_EVENT_CALL_CLOSED, call, reason); + + mem_deref(call); + + resume_call(ua); +} + + +/** + * Answer an incoming call + * + * @param ua User-Agent + * @param call Call to answer, 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; + } + + return call_answer(call, 200); +} + + +int ua_progress(struct ua *ua, struct call *call) +{ + if (!ua) + return EINVAL; + + if (!call) { + call = ua_call(ua); + if (!call) + return ENOENT; + } + + return call_progress(call); +} + + +/** + * Put the current call on hold and answer the incoming call + * + * @param ua User-Agent + * @param call Call to answer, or NULL for current call + * + * @return 0 if success, otherwise errorcode + */ +int ua_hold_answer(struct ua *ua, struct call *call) +{ + struct call *pcall; + int err; + + if (!ua) + return EINVAL; + + if (!call) { + call = ua_call(ua); + if (!call) + return ENOENT; + } + + /* put previous call on-hold */ + pcall = ua_prev_call(ua); + if (pcall) { + ua_printf(ua, "putting call with '%s' on hold\n", + call_peeruri(pcall)); + + err = call_hold(pcall, true); + if (err) + return err; + } + + return ua_answer(ua, call); +} + + +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) +{ + struct mbuf *dialbuf; + int err = 0; + + if (!ua || !str_isset(uri)) + return EINVAL; + + dialbuf = mbuf_alloc(64); + if (!dialbuf) + return ENOMEM; + + err = uri_complete(ua, dialbuf, uri); + if (err) + goto out; + + dialbuf->buf[dialbuf->end] = '\0'; + + err = sip_req_send(ua, "OPTIONS", (char *)dialbuf->buf, resph, arg, + "Accept: application/sdp\r\n" + "Content-Length: 0\r\n" + "\r\n"); + if (err) { + warning("ua: send options: (%m)\n", err); + } + + out: + mem_deref(dialbuf); + + 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 ? account_aor(ua->acc) : NULL; +} + + +/** + * Get presence status of a User-Agent + * + * @param ua User-Agent object + * + * @return presence status + */ +enum presence_status ua_presence_status(const struct ua *ua) +{ + return ua ? ua->my_status : PRESENCE_UNKNOWN; +} + + +/** + * Set presence status of a User-Agent + * + * @param ua User-Agent object + * @param status Presence status + */ +void ua_presence_status_set(struct ua *ua, const enum presence_status status) +{ + if (!ua) + return; + + ua->my_status = status; +} + + +/** + * 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->outboundv[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). + */ +struct call *ua_call(const struct ua *ua) +{ + if (!ua) + return NULL; + + return list_ledata(list_tail(&ua->calls)); +} + + +struct call *ua_prev_call(const struct ua *ua) +{ + struct le *le; + int prev = 0; + + if (!ua) + return NULL; + + for (le = ua->calls.tail; le; le = le->prev) { + if ( prev == 1) { + struct call *call = le->data; + return call; + } + if ( prev == 0) + prev = 1; + } + + 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, " nrefs: %u\n", mem_nrefs(ua)); + err |= re_hprintf(pf, " cuser: %s\n", ua->cuser); + err |= re_hprintf(pf, " pub-gruu: %s\n", ua->pub_gruu); + 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) { + warning("ua: 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) { + warning("ua: 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) { + warning("ua: 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) { + warning("ua: SIP/TLS transport failed: %m\n", err); + return err; + } + } +#endif + + return err; +} + + +static int ua_add_transp(struct network *net) +{ + int err = 0; + + if (!uag.prefer_ipv6) { + if (sa_isset(net_laddr_af(net, AF_INET), SA_ADDR)) + err |= add_transp_af(net_laddr_af(net, AF_INET)); + } + +#if HAVE_INET6 + if (sa_isset(net_laddr_af(net, AF_INET6), SA_ADDR)) + err |= add_transp_af(net_laddr_af(net, 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) +{ + struct config *config = conf_config(); + const struct sip_hdr *hdr; + struct ua *ua; + struct call *call = NULL; + char to_uri[256]; + int err; + + (void)arg; + + ua = uag_find(&msg->uri.user); + if (!ua) { + warning("ua: %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 (config->call.max_calls && + list_count(&ua->calls) + 1 > config->call.max_calls) { + + info("ua: rejected call from %r (maximum %d calls)\n", + &msg->from.auri, config->call.max_calls); + (void)sip_treply(NULL, uag.sip, msg, 486, "Max Calls"); + 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, true); + if (err) { + warning("ua: call_alloc: %m\n", err); + goto error; + } + + err = call_accept(call, uag.sock, msg); + if (err) + goto error; + + return; + + error: + mem_deref(call); + (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error"); +} + + +static void net_change_handler(void *arg) +{ + (void)arg; + + info("IP-address changed: %j\n", + net_laddr_af(baresip_network(), AF_INET)); + + (void)uag_reset_transp(true, true); +} + + +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("subscribe: no UA found for %r\n", &msg->uri.user); + (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found"); + return true; + } + + if (uag.subh) + uag.subh(msg, ua); + + return true; +} + + +/** + * 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(); + struct network *net = baresip_network(); + uint32_t bsize; + int err; + + if (!net) { + warning("ua: no network\n"); + return EINVAL; + } + + uag.cfg = &cfg->sip; + bsize = cfg->sip.trans_bsize; + + 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(net), bsize, bsize, bsize, + software, exit_handler, NULL); + if (err) { + warning("ua: sip stack failed: %m\n", err); + goto out; + } + + err = ua_add_transp(net); + 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, + sub_handler, NULL); + if (err) + goto out; + + net_change(net, 60, net_change_handler, NULL); + + out: + if (err) { + warning("ua: init failed (%m)\n", err); + ua_close(); + } + return err; +} + + +/** + * Close all active User-Agents + */ +void ua_close(void) +{ + uag.evsock = mem_deref(uag.evsock); + uag.sock = mem_deref(uag.sock); + uag.lsnr = mem_deref(uag.lsnr); + uag.sip = mem_deref(uag.sip); + uag.eprm = mem_deref(uag.eprm); + +#ifdef USE_TLS + uag.tls = mem_deref(uag.tls); +#endif + + list_flush(&uag.ual); + list_flush(&uag.ehl); + + /* note: must be done before mod_close() */ + module_app_unload(); +} + + +/** + * Stop all User-Agents + * + * @param forced True to force, otherwise false + */ +void ua_stop_all(bool forced) +{ + struct le *le; + bool ext_ref = false; + + info("ua: stop all (forced=%d)\n", forced); + + /* check if someone else has grabbed a ref to ua */ + le = uag.ual.head; + while (le) { + + struct ua *ua = le->data; + le = le->next; + + if (mem_nrefs(ua) > 1) { + + list_unlink(&ua->le); + list_flush(&ua->calls); + mem_deref(ua); + + ext_ref = true; + } + + ua_event(ua, UA_EVENT_SHUTDOWN, NULL, NULL); + } + + if (ext_ref) { + info("ua: ext_ref -> cannot unload mods\n"); + return; + } + else { + 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); +} + + +/** + * Set the global UA exit handler. The exit handler will be called + * asyncronously when the SIP stack has exited. + * + * @param exith Exit handler + * @param arg Handler argument + */ +void uag_set_exit_handler(ua_exit_h *exith, void *arg) +{ + uag.exith = exith; + uag.arg = arg; +} + + +/** + * 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 network *net = baresip_network(); + struct le *le; + int err; + + /* Update SIP transports */ + sip_transp_flush(uag.sip); + + (void)net_check(net); + err = ua_add_transp(net); + 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; + const struct sa *laddr; + + laddr = net_laddr_af(net, call_af(call)); + + err |= call_reset_transp(call, laddr); + } + } + } + + 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 + * + * @param pf Print handler for debug output + * @param ua User-Agent + * + * @return 0 if success, otherwise errorcode + */ +int ua_print_calls(struct re_printf *pf, const struct ua *ua) +{ + uint32_t n, count=0; + uint32_t linenum; + int err = 0; + + if (!ua) { + err |= re_hprintf(pf, "\n--- No active calls ---\n"); + return err; + } + + n = list_count(&ua->calls); + + err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n", + n); + + for (linenum=CALL_LINENUM_MIN; linenum<CALL_LINENUM_MAX; linenum++) { + + const struct call *call; + + call = call_find_linenum(&ua->calls, linenum); + if (call) { + ++count; + + err |= re_hprintf(pf, " %c %H\n", + call == ua_call(ua) ? '>' : ' ', + call_info, call); + } + + if (count >= n) + break; + } + + 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 +} + + +/** + * 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 == msg_param_decode(&laddr->params, name, &val) + && + 0 == pl_strcasecmp(&val, value)) { + return ua; + } + } + else { + if (0 == msg_param_exists(&laddr->params, name, &val)) + return ua; + } + } + + return NULL; +} + + +/** + * Get the contact user/uri of a User-Agent (UA) + * + * If the Public GRUU is set, it will be returned. + * Otherwise the local contact-user (cuser) will be returned. + * + * @param ua User-Agent + * + * @return Contact user + */ +const char *ua_cuser(const struct ua *ua) +{ + if (!ua) + return NULL; + + if (str_isset(ua->pub_gruu)) + return ua->pub_gruu; + + return ua->cuser; +} + + +const char *ua_local_cuser(const struct ua *ua) +{ + return ua ? ua->cuser : NULL; +} + + +/** + * Get Account of a User-Agent + * + * @param ua User-Agent + * + * @return Pointer to UA's account + */ +struct account *ua_account(const struct ua *ua) +{ + return ua ? ua->acc : NULL; +} + + +/** + * Set Public GRUU of a User-Agent (UA) + * + * @param ua User-Agent + * @param pval Public GRUU + */ +void ua_pub_gruu_set(struct ua *ua, const struct pl *pval) +{ + if (!ua) + return; + + ua->pub_gruu = mem_deref(ua->pub_gruu); + (void)pl_strdup(&ua->pub_gruu, pval); +} + + +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,MESSAGE"; +} + + +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 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; + + uag_event_unregister(h); + + 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_set_sub_handler(sip_msg_h *subh) +{ + uag.subh = subh; +} + + +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; +} + + +void ua_set_media_af(struct ua *ua, int af_media) +{ + if (!ua) + return; + + ua->af_media = af_media; +} + + +int uag_set_extra_params(const char *eprm) +{ + uag.eprm = mem_deref(uag.eprm); + + if (eprm) + return str_dup(&uag.eprm, eprm); + + return 0; +} diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..40f476b --- /dev/null +++ b/src/ui.c @@ -0,0 +1,195 @@ +/** + * @file ui.c User Interface + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "core.h" + + +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 uis UI Subsystem + * @param ui The User-Interface (UI) module to register + */ +void ui_register(struct ui_sub *uis, struct ui *ui) +{ + if (!uis || !ui) + return; + + list_append(&uis->uil, &ui->le, ui); + + debug("ui: %s\n", ui->name); +} + + +/** + * Un-register a User-Interface (UI) module + * + * @param ui The User-Interface (UI) module to un-register + */ +void ui_unregister(struct ui *ui) +{ + if (!ui) + return; + + list_unlink(&ui->le); +} + + +/** + * Send an input key to the UI subsystem, with a print function for response + * + * @param uis UI Subsystem + * @param key Input character + * @param pf Print function for the response + */ +void ui_input_key(struct ui_sub *uis, char key, struct re_printf *pf) +{ + if (!uis) + return; + + (void)cmd_process(baresip_commands(), &uis->uictx, key, pf, NULL); +} + + +/** + * 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; + struct commands *commands = baresip_commands(); + size_t i; + int err = 0; + + if (!pf || !pl) + return EINVAL; + + for (i=0; i<pl->l; i++) { + err |= cmd_process(commands, &ctx, pl->p[i], pf, NULL); + } + + if (pl->l > 1 && ctx) + err |= cmd_process(commands, &ctx, '\n', pf, NULL); + + return err; +} + + +/** + * Send output to all modules registered in the UI subsystem + * + * @param uis UI Subsystem + * @param fmt Formatted output string + */ +void ui_output(struct ui_sub *uis, const char *fmt, ...) +{ + char buf[512]; + struct le *le; + va_list ap; + int n; + + if (!uis) + return; + + va_start(ap, fmt); + n = re_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (n < 0) + return; + + for (le = uis->uil.head; le; le = le->next) { + const struct ui *ui = le->data; + + if (ui->outputh) + ui->outputh(buf); + } +} + + +/** + * Reset the state of the UI subsystem, free resources + * + * @param uis UI Subsystem + */ +void ui_reset(struct ui_sub *uis) +{ + if (!uis) + return; + + uis->uictx = mem_deref(uis->uictx); +} + + +bool ui_isediting(const struct ui_sub *uis) +{ + if (!uis) + return false; + + return uis->uictx != NULL; +} + + +int ui_password_prompt(char **passwordp) +{ + char pwd[64]; + char *nl; + int err; + + if (!passwordp) + return EINVAL; + + /* 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(passwordp, pwd); + if (err) + return err; + + return 0; +} diff --git a/src/vidcodec.c b/src/vidcodec.c new file mode 100644 index 0000000..cd335ad --- /dev/null +++ b/src/vidcodec.c @@ -0,0 +1,126 @@ +/** + * @file vidcodec.c Video Codec + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> + + +/** + * Register a Video Codec + * + * @param vidcodecl List of video-codecs + * @param vc Video Codec + */ +void vidcodec_register(struct list *vidcodecl, struct vidcodec *vc) +{ + if (!vidcodecl || !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 vidcodecl List of video-codecs + * @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 struct list *vidcodecl, + const char *name, const char *variant) +{ + struct le *le; + + for (le=list_head(vidcodecl); 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; +} + + +/** + * Find a Video Encoder by name + * + * @param vidcodecl List of video-codecs + * @param name Name of the Video Encoder to find + * + * @return Matching Video Encoder if found, otherwise NULL + */ +const struct vidcodec *vidcodec_find_encoder(const struct list *vidcodecl, + const char *name) +{ + struct le *le; + + for (le=list_head(vidcodecl); le; le=le->next) { + + struct vidcodec *vc = le->data; + + if (name && 0 != str_casecmp(name, vc->name)) + continue; + + if (vc->ench) + return vc; + } + + return NULL; +} + + +/** + * Find a Video Decoder by name + * + * @param vidcodecl List of video-codecs + * @param name Name of the Video Decoder to find + * + * @return Matching Video Decoder if found, otherwise NULL + */ +const struct vidcodec *vidcodec_find_decoder(const struct list *vidcodecl, + const char *name) +{ + struct le *le; + + for (le=list_head(vidcodecl); le; le=le->next) { + + struct vidcodec *vc = le->data; + + if (name && 0 != str_casecmp(name, vc->name)) + continue; + + if (vc->dech) + return vc; + } + + return NULL; +} diff --git a/src/video.c b/src/video.c new file mode 100644 index 0000000..f191215 --- /dev/null +++ b/src/video.c @@ -0,0 +1,1421 @@ +/** + * @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" + + +/** Magic number */ +#define MAGIC 0x00070d10 +#include "magic.h" + + +/** Internal video-encoder format */ +#ifndef VIDENC_INTERNAL_FMT +#define VIDENC_INTERNAL_FMT (VID_FMT_YUV420P) +#endif + + +enum { + SRATE = 90000, + MAX_MUTED_FRAMES = 3, +}; + +/** Video transmit parameters */ +enum { + MEDIA_POLL_RATE = 250, /**< in [Hz] */ + BURST_MAX = 8192, /**< in bytes */ + RTP_PRESZ = 4 + RTP_HEADER_SIZE, /**< TURN and RTP header */ + RTP_TRAILSZ = 12 + 4, /**< SRTP/SRTCP trailer */ + PICUP_INTERVAL = 500, +}; + + +/** + * \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 lock *lock_tx; /**< Protect the sendq */ + struct list sendq; /**< Tx-Queue (struct vidqent) */ + struct tmr tmr_rtp; /**< Timer for sending RTP */ + unsigned skipc; /**< Number of frames skipped */ + struct list filtl; /**< Filters in encoding order */ + char device[128]; /**< Source device name */ + int muted_frames; /**< # of muted frames sent */ + uint32_t ts_offset; /**< Random timestamp offset */ + bool picup; /**< Send picture update */ + bool muted; /**< Muted flag */ + int frames; /**< Number of frames sent */ + int efps; /**< Estimated frame-rate */ + uint32_t ts_min; + uint32_t ts_max; +}; + + +/** + * 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 */ + struct tmr tmr_picup; /**< Picture update timer */ + struct vidsz size; /**< Incoming video resolution */ + enum vidorient orient; /**< Display orientation */ + char device[128]; /**< Display device name */ + int pt_rx; /**< Incoming RTP payload type */ + int frames; /**< Number of frames received */ + int efps; /**< Estimated frame-rate */ + unsigned n_intra; /**< Intra-frames decoded */ + unsigned n_picup; /**< Picture updates sent */ + uint32_t ts_min; + uint32_t ts_max; +}; + + +/** 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 */ + bool started; /**< True if video is started */ + char *peer; /**< Peer URI */ + bool nack_pli; /**< Send NACK/PLI to peer */ + video_err_h *errh; /**< Error handler */ + void *arg; /**< Error handler argument */ +}; + + +struct vidqent { + struct le le; + struct sa dst; + bool marker; + uint8_t pt; + uint32_t ts; + struct mbuf *mb; +}; + + +static void request_picture_update(struct vrx *vrx); + + +static void vidqent_destructor(void *arg) +{ + struct vidqent *qent = arg; + + list_unlink(&qent->le); + mem_deref(qent->mb); +} + + +static int vidqent_alloc(struct vidqent **qentp, + bool marker, uint8_t pt, uint32_t ts, + const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len) +{ + struct vidqent *qent; + int err = 0; + + if (!qentp || !pld) + return EINVAL; + + qent = mem_zalloc(sizeof(*qent), vidqent_destructor); + if (!qent) + return ENOMEM; + + qent->marker = marker; + qent->pt = pt; + qent->ts = ts; + + qent->mb = mbuf_alloc(RTP_PRESZ + hdr_len + pld_len + RTP_TRAILSZ); + if (!qent->mb) { + err = ENOMEM; + goto out; + } + + qent->mb->pos = qent->mb->end = RTP_PRESZ; + + if (hdr) + (void)mbuf_write_mem(qent->mb, hdr, hdr_len); + + (void)mbuf_write_mem(qent->mb, pld, pld_len); + + qent->mb->pos = RTP_PRESZ; + + out: + if (err) + mem_deref(qent); + else + *qentp = qent; + + return err; +} + + +static void vidqueue_poll(struct vtx *vtx, uint64_t jfs, uint64_t prev_jfs) +{ + size_t burst, sent; + uint64_t bandwidth_kbps; + struct le *le; + + if (!vtx) + return; + + lock_write_get(vtx->lock_tx); + + le = vtx->sendq.head; + if (!le) + goto out; + + /* + * time [ms] * bitrate [kbps] / 8 = bytes + */ + bandwidth_kbps = vtx->video->cfg.bitrate / 1000; + burst = (1 + jfs - prev_jfs) * bandwidth_kbps / 4; + + burst = min(burst, BURST_MAX); + sent = 0; + + while (le) { + + struct vidqent *qent = le->data; + + sent += mbuf_get_left(qent->mb); + + stream_send(vtx->video->strm, false, qent->marker, qent->pt, + qent->ts, qent->mb); + + le = le->next; + mem_deref(qent); + + if (sent > burst) { + break; + } + } + + out: + lock_rel(vtx->lock_tx); +} + + +static void rtp_tmr_handler(void *arg) +{ + struct vtx *vtx = arg; + uint64_t pjfs; + + pjfs = vtx->tmr_rtp.jfs; + + tmr_start(&vtx->tmr_rtp, 1000/MEDIA_POLL_RATE, rtp_tmr_handler, vtx); + + vidqueue_poll(vtx, vtx->tmr_rtp.jfs, pjfs); +} + + +static void video_destructor(void *arg) +{ + struct video *v = arg; + struct vtx *vtx = &v->vtx; + struct vrx *vrx = &v->vrx; + + /* transmit */ + lock_write_get(vtx->lock_tx); + list_flush(&vtx->sendq); + lock_rel(vtx->lock_tx); + mem_deref(vtx->lock_tx); + + tmr_cancel(&vtx->tmr_rtp); + mem_deref(vtx->vsrc); + lock_write_get(vtx->lock); + mem_deref(vtx->frame); + mem_deref(vtx->mute_frame); + mem_deref(vtx->enc); + list_flush(&vtx->filtl); + lock_rel(vtx->lock); + mem_deref(vtx->lock); + + /* receive */ + tmr_cancel(&vrx->tmr_picup); + 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, uint32_t ts, + const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t pld_len, + void *arg) +{ + struct vtx *vtx = arg; + struct stream *strm = vtx->video->strm; + struct vidqent *qent; + uint32_t rtp_ts; + int err; + + /* NOTE: does not handle timestamp wrap around */ + if (ts < vtx->ts_min) + vtx->ts_min = ts; + if (ts > vtx->ts_max) + vtx->ts_max = ts; + + /* add random timestamp offset */ + rtp_ts = vtx->ts_offset + ts; + + err = vidqent_alloc(&qent, marker, strm->pt_enc, rtp_ts, + hdr, hdr_len, pld, pld_len); + if (err) + return err; + + lock_write_get(vtx->lock_tx); + qent->dst = *sdp_media_raddr(strm->sdp); + list_append(&vtx->sendq, &qent->le, qent); + lock_rel(vtx->lock_tx); + + 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; + bool sendq_empty; + + if (!vtx->enc) + return; + + lock_write_get(vtx->lock_tx); + sendq_empty = (vtx->sendq.head == NULL); + lock_rel(vtx->lock_tx); + + if (!sendq_empty) { + ++vtx->skipc; + return; + } + + lock_write_get(vtx->lock); + + /* Convert image */ + if (frame->fmt != VIDENC_INTERNAL_FMT) { + + vtx->vsrc_size = frame->size; + + if (!vtx->frame) { + + err = vidframe_alloc(&vtx->frame, VIDENC_INTERNAL_FMT, + &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); + if (err) + return; + + 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; + + warning("video: 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); + err |= lock_alloc(&vtx->lock_tx); + if (err) + return err; + + tmr_init(&vtx->tmr_rtp); + + vtx->video = video; + + /* The initial value of the timestamp SHOULD be random */ + vtx->ts_offset = rand_u16(); + + str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device)); + + tmr_start(&vtx->tmr_rtp, 1, rtp_tmr_handler, vtx); + + vtx->ts_min = ~0; + + 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)); + + vrx->ts_min = ~0; + + return err; +} + + +static void picup_tmr_handler(void *arg) +{ + struct vrx *vrx = arg; + + request_picture_update(vrx); +} + + +static void request_picture_update(struct vrx *vrx) +{ + struct video *v = vrx->video; + + if (tmr_isrunning(&vrx->tmr_picup)) + return; + + tmr_start(&vrx->tmr_picup, PICUP_INTERVAL, picup_tmr_handler, vrx); + + /* send RTCP FIR to peer */ + stream_send_fir(v->strm, v->nack_pli); + + /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */ + + ++vrx->n_picup; +} + + +/** + * 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_filt = NULL; + struct vidframe frame_store, *frame = &frame_store; + struct le *le; + bool intra; + int err = 0; + + if (!hdr || !mbuf_get_left(mb)) + return 0; + + lock_write_get(vrx->lock); + + /* No decoder set */ + if (!vrx->dec) { + warning("video: No video decoder!\n"); + goto out; + } + + /* todo: check if RTP timestamp wraps */ + + if (hdr->ts < vrx->ts_min) + vrx->ts_min = hdr->ts; + if (hdr->ts > vrx->ts_max) + vrx->ts_max = hdr->ts; + + frame->data[0] = NULL; + err = vrx->vc->dech(vrx->dec, frame, &intra, hdr->m, hdr->seq, mb); + if (err) { + + if (err != EPROTO) { + warning("video: %s decode error" + " (seq=%u, %u bytes): %m\n", + vrx->vc->name, hdr->seq, + mbuf_get_left(mb), err); + } + + request_picture_update(vrx); + + goto out; + } + + if (intra) { + tmr_cancel(&vrx->tmr_picup); + ++vrx->n_intra; + } + + /* Got a full picture-frame? */ + if (!vidframe_isvalid(frame)) + goto out; + + vrx->size = frame->size; + + if (!list_isempty(&vrx->filtl)) { + + err = vidframe_alloc(&frame_filt, frame->fmt, &frame->size); + if (err) + goto out; + + vidframe_copy(frame_filt, frame); + + frame = frame_filt; + } + + /* 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); + frame_filt = mem_deref(frame_filt); + if (err == ENODEV) { + warning("video: video-display was closed\n"); + vrx->vidisp = mem_deref(vrx->vidisp); + + lock_rel(vrx->lock); + + if (v->errh) { + v->errh(err, "display closed", v->arg); + } + + return err; + } + + ++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 rtpext *extv, size_t extc, + struct mbuf *mb, void *arg) +{ + struct video *v = arg; + int err; + (void)extv; + (void)extc; + + 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 stream_param *stream_prm, + 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, + video_err_h *errh, void *arg) +{ + 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, stream_prm, + &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; + + v->errh = errh; + v->arg = arg; + + 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(baresip_vidfiltl()); 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) { + warning("video: 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; + vrx->vidisp_prm.fullscreen = vrx->video->cfg.fullscreen; + + vd = (struct vidisp *)vidisp_find(baresip_vidispl(), + 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(baresip_vidsrcl(), + 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, VIDENC_INTERNAL_FMT, 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); + + if (vidisp_find(baresip_vidispl(), NULL)) { + err = set_vidisp(&v->vrx); + if (err) { + warning("video: could not set vidisp '%s': %m\n", + v->vrx.device, err); + } + } + else { + info("video: no video display\n"); + } + + if (vidsrc_find(baresip_vidsrcl(), NULL)) { + 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) { + warning("video: could not set encoder format to" + " [%u x %u] %m\n", + size.w, size.h, err); + } + } + else { + info("video: no video source\n"); + } + + 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); + } + + v->started = true; + + return 0; +} + + +void video_stop(struct video *v) +{ + if (!v) + return; + + debug("video: stopping video source ..\n"); + + v->started = false; + v->vtx.vsrc = mem_deref(v->vtx.vsrc); +} + + +bool video_is_started(const struct video *v) +{ + return v ? v->started : false; +} + + +/** + * 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->vidisp_prm.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.vidisp_prm.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->encupdh) { + info("video: vidcodec '%s' has no encoder\n", vc->name); + return ENOENT; + } + + 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, + packet_handler, vtx); + if (err) { + warning("video: 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; + + /* handle vidcodecs without a decoder */ + if (!vc->decupdh) { + struct list *vidcodecl = baresip_vidcodecl(); + struct vidcodec *vcd; + + info("video: vidcodec '%s' has no decoder\n", vc->name); + + vcd = (struct vidcodec *)vidcodec_find_decoder(vidcodecl, + vc->name); + if (!vcd) { + warning("video: could not find decoder (%s)\n", + vc->name); + return ENOENT; + } + + vc = vcd; + } + + 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) { + warning("video: 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, " started: %s\n", v->started ? "yes" : "no"); + + 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, " skipc=%u\n", vtx->skipc); + err |= re_hprintf(pf, " time = %.3f sec\n", + video_calc_seconds(vtx->ts_max - vtx->ts_min)); + + err |= re_hprintf(pf, " rx: %u x %u\n", vrx->size.w, vrx->size.h); + err |= re_hprintf(pf, " pt=%d\n", vrx->pt_rx); + + err |= re_hprintf(pf, " n_intra=%u, n_picup=%u\n", + vrx->n_intra, vrx->n_picup); + err |= re_hprintf(pf, " time = %.3f sec\n", + video_calc_seconds(vrx->ts_max - vrx->ts_min)); + + if (!list_isempty(baresip_vidfiltl())) { + 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(baresip_vidsrcl(), + 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)); +} + + +/** + * Calculate the RTP timestamp from Presentation Time Stamp (PTS) + * or Decoding Time Stamp (DTS) and framerate. + * + * @note The calculated RTP Timestamp may wrap around. + * + * @param pts Presentation Time Stamp (PTS) + * @param fps Framerate in [frames per second] + * + * @return RTP Timestamp + */ +uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps) +{ + uint64_t rtp_ts; + + if (!fps) + return 0; + + rtp_ts = ((uint64_t)SRATE * pts) / fps; + + return (uint32_t)rtp_ts; +} + + +double video_calc_seconds(uint32_t rtp_ts) +{ + double timestamp; + + /* convert from RTP clockrate to seconds */ + timestamp = (double)rtp_ts / (double)SRATE; + + return timestamp; +} diff --git a/src/vidfilt.c b/src/vidfilt.c new file mode 100644 index 0000000..c55a1e5 --- /dev/null +++ b/src/vidfilt.c @@ -0,0 +1,103 @@ +/** + * @file vidfilt.c Video Filter + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "core.h" + + +/** + * Register a new Video Filter + * + * @param vidfiltl List of Video-Filters + * @param vf Video Filter to register + */ +void vidfilt_register(struct list *vidfiltl, struct vidfilt *vf) +{ + if (!vf) + return; + + list_append(vidfiltl, &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); +} + + +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..d267f58 --- /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 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 vidispl List of Video-displays + * @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, struct list *vidispl, const char *name, + vidisp_alloc_h *alloch, vidisp_update_h *updateh, + vidisp_disp_h *disph, vidisp_hide_h *hideh) +{ + struct vidisp *vd; + + if (!vp || !vidispl) + 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 struct list *vidispl, const char *name) +{ + struct le *le; + + for (le = list_head(vidispl); 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 vidispl List of Video-displays + * @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, struct list *vidispl, + const char *name, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp *vd = (struct vidisp *)vidisp_find(vidispl, 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..2c8f9ff --- /dev/null +++ b/src/vidsrc.c @@ -0,0 +1,125 @@ +/** + * @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 void destructor(void *arg) +{ + struct vidsrc *vs = arg; + + list_unlink(&vs->le); +} + + +/** + * Register a Video Source + * + * @param vsp Pointer to allocated Video Source + * @param vidsrcl List of Video Sources + * @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, struct list *vidsrcl, + const char *name, + vidsrc_alloc_h *alloch, vidsrc_update_h *updateh) +{ + struct vidsrc *vs; + + if (!vsp || !vidsrcl) + 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 vidsrcl List of Video Sources + * @param name Name of the Video Source to find + * + * @return Matching Video Source if found, otherwise NULL + */ +const struct vidsrc *vidsrc_find(const struct list *vidsrcl, const char *name) +{ + struct le *le; + + for (le=list_head(vidsrcl); 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 vidsrcl List of Video Sources + * @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, struct list *vidsrcl, + 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(vidsrcl, name); + if (!vs) + return ENOENT; + + return vs->alloch(stp, vs, ctx, prm, size, fmt, dev, + frameh, errorh, arg); +} + + +struct vidsrc *vidsrc_get(struct vidsrc_st *st) +{ + return st ? st->vs : NULL; +} diff --git a/test/account.c b/test/account.c new file mode 100644 index 0000000..bf03b03 --- /dev/null +++ b/test/account.c @@ -0,0 +1,69 @@ +/** + * @file test/account.c Tests for account + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +#define DEBUG_MODULE "account" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +static const char str[] = + "\"Mr User\" <sip:user:pass@domain.com>" + ";answermode=auto" + ";auth_user=xuser" + ";outbound=\"sip:edge.domain.com\"" + ";ptime=10" + ";regint=600" + ";pubint=700" + ";sipnat=outbound" + ";stunuser=bob@bob.com" + ";stunpass=taj:aa" + ";stunserver=\"stun:stunserver.org\"" + ; + + +int test_account(void) +{ + struct account *acc = NULL; + struct sip_addr *addr; + int err = 0; + + err = account_alloc(&acc, str); + TEST_ERR(err); + ASSERT_TRUE(acc != NULL); + + /* verify the decoded SIP aor */ + addr = account_laddr(acc); + ASSERT_TRUE(addr != NULL); + TEST_STRCMP("Mr User", 7, addr->dname.p, addr->dname.l); + TEST_STRCMP("sip", 3, addr->uri.scheme.p, addr->uri.scheme.l); + TEST_STRCMP("user", 4, addr->uri.user.p, addr->uri.user.l); + TEST_STRCMP("pass", 4, addr->uri.password.p, addr->uri.password.l); + TEST_STRCMP("domain.com", 10, addr->uri.host.p, addr->uri.host.l); + ASSERT_EQ(0, addr->uri.params.l); + ASSERT_TRUE(addr->params.l > 0); + + /* verify all decoded parameters */ + ASSERT_TRUE(ANSWERMODE_AUTO == account_answermode(acc)); + ASSERT_STREQ("xuser", account_auth_user(acc)); + ASSERT_STREQ("sip:edge.domain.com", account_outbound(acc, 0)); + ASSERT_TRUE(NULL == account_outbound(acc, 1)); + ASSERT_TRUE(NULL == account_outbound(acc, 333)); + ASSERT_EQ(10, account_ptime(acc)); + ASSERT_EQ(600, account_regint(acc)); + ASSERT_EQ(700, account_pubint(acc)); + ASSERT_STREQ("bob@bob.com", account_stun_user(acc)); + ASSERT_STREQ("taj:aa", account_stun_pass(acc)); + ASSERT_STREQ("stunserver.org", account_stun_host(acc)); + + out: + mem_deref(acc); + return err; +} diff --git a/test/aulevel.c b/test/aulevel.c new file mode 100644 index 0000000..6d083f6 --- /dev/null +++ b/test/aulevel.c @@ -0,0 +1,61 @@ +/** + * @file test/aulevel.c Baresip selftest -- audio levels + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "test.h" + + +#define DEBUG_MODULE "aulevel" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define PREC .6 + + +int test_aulevel(void) +{ + static const struct { + int16_t sampv[2]; + double level; + } testv[] = { + + { { 0, -0}, -96.0 }, + { { 0, 1}, -93.0 }, + { { 1, -1}, -90.0 }, + { { 2, -2}, -84.0 }, + { { 4, -4}, -78.0 }, + { { 8, -8}, -72.0 }, + { { 16, -16}, -66.0 }, + { { 32, -32}, -60.0 }, + { { 64, -64}, -54.0 }, + { { 128, -128}, -48.0 }, + { { 256, -256}, -42.0 }, + { { 512, -512}, -36.0 }, + { { 1024, -1024}, -30.0 }, + { { 2048, -2048}, -24.0 }, + { { 4096, -4096}, -18.0 }, + { { 8192, -8192}, -12.0 }, + { {16384, -16384}, -6.0 }, + { {32767, -32768}, 0.0 }, + }; + size_t i; + int err = 0; + + for (i=0; i<ARRAY_SIZE(testv); i++) { + + double level; + + level = aulevel_calc_dbov(testv[i].sampv, + ARRAY_SIZE(testv[i].sampv)); + + ASSERT_DOUBLE_EQ(testv[i].level, level, PREC); + } + + out: + return err; +} diff --git a/test/call.c b/test/call.c new file mode 100644 index 0000000..34c5fd5 --- /dev/null +++ b/test/call.c @@ -0,0 +1,906 @@ +/** + * @file test/call.c Baresip selftest -- call + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "test.h" + + +#define MAGIC 0x7004ca11 + + +enum behaviour { + BEHAVIOUR_ANSWER = 0, + BEHAVIOUR_PROGRESS, + BEHAVIOUR_REJECT +}; + +enum action { + ACTION_RECANCEL = 0, + ACTION_HANGUP_A, + ACTION_HANGUP_B, + ACTION_NOTHING +}; + +struct agent { + struct fixture *fix; /* pointer to parent */ + struct agent *peer; + struct ua *ua; + uint16_t close_scode; + bool failed; + + unsigned n_incoming; + unsigned n_progress; + unsigned n_established; + unsigned n_closed; + unsigned n_dtmf_recv; +}; + +struct fixture { + uint32_t magic; + struct agent a, b; + struct sa laddr_sip; + enum behaviour behaviour; + enum action estab_action; + char buri[256]; + int err; + unsigned exp_estab; + unsigned exp_closed; +}; + + +#define fixture_init_prm(f, prm) \ + memset(f, 0, sizeof(*f)); \ + \ + f->a.fix = f; \ + f->b.fix = f; \ + \ + err = ua_init("test", true, true, true, false); \ + TEST_ERR(err); \ + \ + f->magic = MAGIC; \ + f->exp_estab = 1; \ + f->exp_closed = 1; \ + mock_aucodec_register(); \ + \ + err = ua_alloc(&f->a.ua, \ + "A <sip:a@127.0.0.1>;regint=0" prm); \ + TEST_ERR(err); \ + err = ua_alloc(&f->b.ua, \ + "B <sip:b@127.0.0.1>;regint=0" prm); \ + TEST_ERR(err); \ + \ + f->a.peer = &f->b; \ + f->b.peer = &f->a; \ + \ + err = uag_event_register(event_handler, f); \ + TEST_ERR(err); \ + \ + err = sip_transp_laddr(uag_sip(), &f->laddr_sip, \ + SIP_TRANSP_UDP, NULL); \ + TEST_ERR(err); \ + \ + re_snprintf(f->buri, sizeof(f->buri), "sip:b@%J", &f->laddr_sip); + + +#define fixture_init(f) \ + fixture_init_prm((f), ""); + + +#define fixture_close(f) \ + mem_deref(f->b.ua); \ + mem_deref(f->a.ua); \ + \ + mock_aucodec_unregister(); \ + \ + uag_event_unregister(event_handler); \ + \ + ua_stop_all(true); \ + ua_close(); + +#define fixture_abort(f, error) \ + do { \ + (f)->err = (error); \ + re_cancel(); \ + } while (0) + + +static void event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct fixture *f = arg; + struct agent *ag; + int err = 0; + (void)prm; + +#if 1 + info("test: [ %s ] event: %s (%s)\n", + ua_aor(ua), uag_event_str(ev), prm); +#endif + + ASSERT_TRUE(f != NULL); + ASSERT_EQ(MAGIC, f->magic); + + if (ua == f->a.ua) + ag = &f->a; + else if (ua == f->b.ua) + ag = &f->b; + else { + return; + } + + switch (ev) { + + case UA_EVENT_CALL_INCOMING: + ++ag->n_incoming; + + switch (f->behaviour) { + + case BEHAVIOUR_ANSWER: + err = ua_answer(ua, call); + if (err) { + warning("ua_answer failed (%m)\n", err); + goto out; + } + break; + + case BEHAVIOUR_PROGRESS: + err = ua_progress(ua, call); + if (err) { + warning("ua_progress failed (%m)\n", err); + goto out; + } + break; + + case BEHAVIOUR_REJECT: + ua_hangup(ua, call, 0, 0); + call = NULL; + ag->failed = true; + break; + + default: + break; + } + break; + + case UA_EVENT_CALL_PROGRESS: + ++ag->n_progress; + + re_cancel(); + break; + + case UA_EVENT_CALL_ESTABLISHED: + ++ag->n_established; + + /* are both agents established? */ + if (ag->n_established >= f->exp_estab && + ag->peer->n_established >= f->exp_estab) { + + switch (f->estab_action) { + + case ACTION_RECANCEL: + re_cancel(); + break; + + case ACTION_HANGUP_A: + f->a.failed = true; + ua_hangup(f->a.ua, NULL, 0, 0); + break; + + case ACTION_HANGUP_B: + f->b.failed = true; + ua_hangup(f->b.ua, NULL, 0, 0); + break; + + case ACTION_NOTHING: + /* Do nothing, wait */ + break; + } + } + break; + + case UA_EVENT_CALL_CLOSED: + ++ag->n_closed; + + ag->close_scode = call_scode(call); + + if (ag->close_scode) + ag->failed = true; + + if (ag->n_closed >= f->exp_closed && + ag->peer->n_closed >= f->exp_closed) { + + re_cancel(); + } + break; + + default: + break; + } + + if (ag->failed && ag->peer->failed) { + info("test: re_cancel on call failed\n"); + re_cancel(); + return; + } + + out: + if (err) { + warning("error in event-handler (%m)\n", err); + f->err = err; + re_cancel(); + } +} + + +int test_call_answer(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(0, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(0, fix.b.n_closed); + + out: + fixture_close(f); + + return err; +} + + +int test_call_reject(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_REJECT; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(0, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(0, fix.b.n_established); + + out: + fixture_close(f); + + return err; +} + + +int test_call_af_mismatch(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + ua_set_media_af(f->a.ua, AF_INET6); + ua_set_media_af(f->b.ua, AF_INET); + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(0, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(488, fix.a.close_scode); + + ASSERT_EQ(0, fix.b.n_incoming); + ASSERT_EQ(0, fix.b.n_established); + ASSERT_EQ(1, fix.b.n_closed); + + out: + fixture_close(f); + + return err; +} + + +int test_call_answer_hangup_a(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + f->estab_action = ACTION_HANGUP_A; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(1, fix.b.n_closed); + ASSERT_EQ(0, fix.b.close_scode); + + out: + fixture_close(f); + + return err; +} + + +int test_call_answer_hangup_b(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + f->estab_action = ACTION_HANGUP_B; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(1, fix.b.n_closed); + ASSERT_EQ(0, fix.b.close_scode); + + out: + fixture_close(f); + + return err; +} + + +int test_call_rtp_timeout(void) +{ +#define RTP_TIMEOUT_MS 1 + struct fixture fix, *f = &fix; + struct call *call; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + f->estab_action = ACTION_NOTHING; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + call = ua_call(f->a.ua); + ASSERT_TRUE(call != NULL); + + call_enable_rtp_timeout(call, RTP_TIMEOUT_MS); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(701, fix.a.close_scode); /* verify timeout */ + + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(1, fix.b.n_closed); + ASSERT_EQ(0, fix.b.close_scode); + + out: + fixture_close(f); + + return err; +} + + +/* veriy that line-numbers are in sequence */ +static bool linenum_are_sequential(const struct ua *ua) +{ + uint32_t linenum = 0; + struct le *le; + + for (le = list_head(ua_calls(ua)) ; le ; le = le->next) { + struct call *call = le->data; + + if (call_linenum(call) <= linenum) + return false; + + linenum = call_linenum(call); + } + + return true; +} + + +int test_call_multiple(void) +{ + struct fixture fix, *f = &fix; + struct le *le; + unsigned i; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + f->exp_estab = 4; + + /* + * Step 1 -- make 4 calls from A to B + */ + for (i=0; i<4; i++) { + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + } + + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(4, fix.a.n_established); + ASSERT_EQ(0, fix.a.n_closed); + + ASSERT_EQ(4, fix.b.n_incoming); + ASSERT_EQ(4, fix.b.n_established); + ASSERT_EQ(0, fix.b.n_closed); + + ASSERT_EQ(4, list_count(ua_calls(f->a.ua))); + ASSERT_EQ(4, list_count(ua_calls(f->b.ua))); + ASSERT_TRUE(linenum_are_sequential(f->a.ua)); + ASSERT_TRUE(linenum_are_sequential(f->b.ua)); + + + /* + * Step 2 -- hangup calls with even line-number + */ + + f->exp_closed = 2; + + le = list_head(ua_calls(f->a.ua)); + while (le) { + struct call *call = le->data; + le = le->next; + + if (!(call_linenum(call) % 2)) { + ua_hangup(f->a.ua, call, 0, 0); + } + } + + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(2, list_count(ua_calls(f->a.ua))); + ASSERT_EQ(2, list_count(ua_calls(f->b.ua))); + ASSERT_TRUE(linenum_are_sequential(f->a.ua)); + ASSERT_TRUE(linenum_are_sequential(f->b.ua)); + + + /* + * Step 3 -- make 2 calls from A to B + */ + + f->a.n_established = 0; + f->b.n_established = 0; + f->exp_estab = 2; + for (i=0; i<2; i++) { + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + } + + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(4, list_count(ua_calls(f->a.ua))); + ASSERT_EQ(4, list_count(ua_calls(f->b.ua))); + + out: + fixture_close(f); + + return err; +} + + +int test_call_max(void) +{ + struct fixture fix, *f = &fix; + unsigned i; + int err = 0; + + /* Set the max-calls limit */ + conf_config()->call.max_calls = 1; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_ANSWER; + + /* Make 2 calls, one should work and one should fail */ + for (i=0; i<2; i++) { + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + } + + f->b.failed = true; /* tiny hack to stop the runloop */ + + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.a.n_closed); + ASSERT_EQ(486, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(0, fix.b.n_closed); + + out: + fixture_close(f); + + return err; +} + + +static const char dtmf_digits[] = "123"; + + +static void dtmf_handler(struct call *call, char key, void *arg) +{ + struct agent *ag = arg; + int err = 0; + (void)call; + + /* ignore key-release */ + if (key == KEYCODE_REL) + return; + + ASSERT_EQ(dtmf_digits[ag->n_dtmf_recv], key); + ++ag->n_dtmf_recv; + + if (ag->n_dtmf_recv >= str_len(dtmf_digits)) { + re_cancel(); + } + + out: + if (err) { + fixture_abort(ag->fix, err); + } +} + + +int test_call_dtmf(void) +{ + struct fixture fix, *f = &fix; + struct ausrc *ausrc = NULL; + size_t i, n = str_len(dtmf_digits); + int err = 0; + + /* Use a low packet time, so the test completes quickly */ + fixture_init_prm(f, ";ptime=1"); + + /* audio-source is needed for dtmf/telev to work */ + err = mock_ausrc_register(&ausrc); + TEST_ERR(err); + + f->behaviour = BEHAVIOUR_ANSWER; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + call_set_handlers(ua_call(f->a.ua), NULL, dtmf_handler, &f->a); + call_set_handlers(ua_call(f->b.ua), NULL, dtmf_handler, &f->b); + + /* send some DTMF digits from A to B .. */ + for (i=0; i<n; i++) { + err = call_send_digit(ua_call(f->a.ua), dtmf_digits[i]); + TEST_ERR(err); + } + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_dtmf_recv); + ASSERT_EQ(n, fix.b.n_dtmf_recv); + + out: + fixture_close(f); + mem_deref(ausrc); + + return err; +} + + +#ifdef USE_VIDEO +int test_call_video(void) +{ + struct fixture fix, *f = &fix; + struct vidsrc *vidsrc = NULL; + struct vidisp *vidisp = NULL; + int err = 0; + + conf_config()->video.fps = 100; + + fixture_init(f); + + /* to enable video, we need one vidsrc and vidcodec */ + mock_vidcodec_register(); + err = mock_vidsrc_register(&vidsrc); + TEST_ERR(err); + err = mock_vidisp_register(&vidisp); + TEST_ERR(err); + + f->behaviour = BEHAVIOUR_ANSWER; + f->estab_action = ACTION_NOTHING; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_ON); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(10000); + TEST_ERR(err); + TEST_ERR(fix.err); + + /* verify that video was enabled for this call */ + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(1, fix.b.n_established); + + ASSERT_TRUE(call_has_video(ua_call(f->a.ua))); + ASSERT_TRUE(call_has_video(ua_call(f->b.ua))); + + out: + fixture_close(f); + mem_deref(vidisp); + mem_deref(vidsrc); + mock_vidcodec_unregister(); + + return err; +} +#endif + + +static void mock_sample_handler(const void *sampv, size_t sampc, void *arg) +{ + struct fixture *fix = arg; + bool got_aulevel; + (void)sampv; + (void)sampc; + + got_aulevel = + 0 == audio_level_get(call_audio(ua_call(fix->a.ua)), NULL) && + 0 == audio_level_get(call_audio(ua_call(fix->b.ua)), NULL); + + if (got_aulevel) + re_cancel(); +} + + +int test_call_aulevel(void) +{ + struct fixture fix, *f = &fix; + struct ausrc *ausrc = NULL; + struct auplay *auplay = NULL; + double lvl; + int err = 0; + + /* Use a low packet time, so the test completes quickly */ + fixture_init_prm(f, ";ptime=1"); + + conf_config()->audio.level = true; + + err = mock_ausrc_register(&ausrc); + TEST_ERR(err); + err = mock_auplay_register(&auplay, mock_sample_handler, f); + TEST_ERR(err); + + f->estab_action = ACTION_NOTHING; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + /* verify audio silence */ + err = audio_level_get(call_audio(ua_call(f->a.ua)), &lvl); + TEST_ERR(err); + ASSERT_EQ(-96, lvl); + err = audio_level_get(call_audio(ua_call(f->b.ua)), &lvl); + TEST_ERR(err); + ASSERT_EQ(-96, lvl); + + out: + conf_config()->audio.level = false; + + fixture_close(f); + mem_deref(auplay); + mem_deref(ausrc); + + return err; +} + + +int test_call_progress(void) +{ + struct fixture fix, *f = &fix; + int err = 0; + + fixture_init(f); + + f->behaviour = BEHAVIOUR_PROGRESS; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(1, fix.a.n_progress); + ASSERT_EQ(0, fix.a.n_established); + ASSERT_EQ(0, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(0, fix.b.n_progress); + ASSERT_EQ(0, fix.b.n_established); + ASSERT_EQ(0, fix.b.n_closed); + + out: + fixture_close(f); + + return err; +} + + +static void float_sample_handler(const void *sampv, size_t sampc, void *arg) +{ + struct fixture *fix = arg; + (void)sampv; + (void)sampc; + + if (sampc && fix->a.n_established && fix->b.n_established) + re_cancel(); +} + + +static int test_media_base(enum audio_mode txmode) +{ + struct fixture fix, *f = &fix; + struct ausrc *ausrc = NULL; + struct auplay *auplay = NULL; + int err = 0; + + fixture_init(f); + + conf_config()->audio.txmode = txmode; + + conf_config()->audio.src_fmt = AUFMT_FLOAT; + conf_config()->audio.play_fmt = AUFMT_FLOAT; + + err = mock_ausrc_register(&ausrc); + TEST_ERR(err); + err = mock_auplay_register(&auplay, float_sample_handler, f); + TEST_ERR(err); + + f->estab_action = ACTION_NOTHING; + + f->behaviour = BEHAVIOUR_ANSWER; + + /* Make a call from A to B */ + err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + TEST_ERR(err); + TEST_ERR(fix.err); + + ASSERT_EQ(0, fix.a.n_incoming); + ASSERT_EQ(1, fix.a.n_established); + ASSERT_EQ(0, fix.a.n_closed); + ASSERT_EQ(0, fix.a.close_scode); + + ASSERT_EQ(1, fix.b.n_incoming); + ASSERT_EQ(1, fix.b.n_established); + ASSERT_EQ(0, fix.b.n_closed); + + out: + conf_config()->audio.src_fmt = AUFMT_S16LE; + conf_config()->audio.play_fmt = AUFMT_S16LE; + + fixture_close(f); + mem_deref(auplay); + mem_deref(ausrc); + + if (fix.err) + return fix.err; + + return err; +} + + +int test_call_format_float(void) +{ + int err; + + err = test_media_base(AUDIO_MODE_POLL); + ASSERT_EQ(0, err); + + err = test_media_base(AUDIO_MODE_THREAD); + ASSERT_EQ(0, err); + + conf_config()->audio.txmode = AUDIO_MODE_POLL; + + out: + return err; +} diff --git a/test/cmd.c b/test/cmd.c new file mode 100644 index 0000000..2df09ec --- /dev/null +++ b/test/cmd.c @@ -0,0 +1,161 @@ +/** + * @file test/cmd.c Baresip selftest -- cmd + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +struct test { + unsigned cmd_called; +}; + +static int cmd_test(struct re_printf *pf, void *arg) +{ + struct cmd_arg *carg = arg; + struct test *test = carg->data; + int err = 0; + (void)pf; + + ASSERT_EQ('@', carg->key); + ASSERT_TRUE(NULL == carg->prm); + ASSERT_EQ(true, carg->complete); + + ++test->cmd_called; + + out: + return err; +} + + +static const struct cmd cmdv[] = { + {NULL, '@', 0, "Test command", cmd_test}, +}; + + +static int vprintf_null(const char *p, size_t size, void *arg) +{ + (void)p; + (void)size; + (void)arg; + return 0; +} + + +static struct re_printf pf_null = {vprintf_null, 0}; + + +int test_cmd(void) +{ + struct commands *commands = NULL; + struct cmd_ctx *ctx = 0; + struct test test; + int err = 0; + + memset(&test, 0, sizeof(test)); + + err = cmd_init(&commands); + ASSERT_EQ(0, err); + + err = cmd_register(commands, cmdv, ARRAY_SIZE(cmdv)); + ASSERT_EQ(0, err); + + /* it is not possible to register same block twice */ + ASSERT_EQ(EALREADY, cmd_register(commands, cmdv, ARRAY_SIZE(cmdv))); + + /* issue a different command */ + err = cmd_process(commands, &ctx, 'h', &pf_null, &test); + ASSERT_EQ(0, err); + ASSERT_EQ(0, test.cmd_called); + + /* issue our command, expect handler to be called */ + err = cmd_process(commands, &ctx, '@', &pf_null, &test); + ASSERT_EQ(0, err); + ASSERT_EQ(1, test.cmd_called); + + cmd_unregister(commands, cmdv); + + /* verify that context was not created */ + ASSERT_TRUE(NULL == ctx); + + out: + mem_deref(commands); + return err; +} + + +static int long_handler(struct re_printf *pf, void *arg) +{ + struct cmd_arg *carg = arg; + struct test *test = carg->data; + int err = 0; + (void)pf; + + ASSERT_STREQ("123", carg->prm); + + ++test->cmd_called; + + out: + return err; +} + + +static const struct cmd longcmdv[] = { + { "test", 0, 0, "Test Command", long_handler}, +}; + + +int test_cmd_long(void) +{ + struct commands *commands = NULL; + struct test test; + const struct cmd *cmd; + static const char *input_str = "/test 123\n"; + struct cmd_ctx *ctx = NULL; + size_t i; + int err; + + memset(&test, 0, sizeof(test)); + + err = cmd_init(&commands); + ASSERT_EQ(0, err); + + /* Verify that the command does not exist */ + cmd = cmd_find_long(commands, "test"); + ASSERT_TRUE(cmd == NULL); + + /* Register and verify command */ + err = cmd_register(commands, longcmdv, ARRAY_SIZE(longcmdv)); + ASSERT_EQ(0, err); + + cmd = cmd_find_long(commands, "test"); + ASSERT_TRUE(cmd != NULL); + + /* Feed it some input data .. */ + + for (i=0; i<strlen(input_str); i++) { + + err = cmd_process(commands, &ctx, input_str[i], + &pf_null, &test); + ASSERT_EQ(0, err); + } + + err = cmd_process_long(commands, "test 123", 8, &pf_null, &test); + ASSERT_EQ(0, err); + + ASSERT_EQ(2, test.cmd_called); + + /* Cleanup .. */ + + cmd_unregister(commands, longcmdv); + + cmd = cmd_find_long(commands, "test"); + ASSERT_TRUE(cmd == NULL); + + out: + mem_deref(commands); + return err; +} diff --git a/test/contact.c b/test/contact.c new file mode 100644 index 0000000..5b357ff --- /dev/null +++ b/test/contact.c @@ -0,0 +1,55 @@ +/** + * @file test/contact.c Baresip selftest -- contacts + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +int test_contact(void) +{ + struct contacts contacts; + struct contact *c; + const char *addr = "sip:neil@young.com"; + struct pl pl_addr; + int err; + + err = contact_init(&contacts); + ASSERT_EQ(0, err); + + /* Verify that we have no contacts */ + + ASSERT_EQ(0, list_count(contact_list(&contacts))); + + c = contact_find(&contacts, "sip:null@void.com"); + ASSERT_TRUE(c == NULL); + + /* Add one contact, list should have one entry and + find should return the added contact */ + + pl_set_str(&pl_addr, addr); + err = contact_add(&contacts, &c, &pl_addr); + ASSERT_EQ(0, err); + ASSERT_TRUE(c != NULL); + + ASSERT_EQ(1, list_count(contact_list(&contacts))); + + c = contact_find(&contacts, addr); + ASSERT_TRUE(c != NULL); + + ASSERT_STREQ(addr, contact_str(c)); + + /* Delete 1 contact, verify that list is empty */ + + mem_deref(c); + + ASSERT_EQ(0, list_count(contact_list(&contacts))); + + out: + contact_close(&contacts); + + return err; +} diff --git a/test/cplusplus.cpp b/test/cplusplus.cpp new file mode 100644 index 0000000..e6ec89f --- /dev/null +++ b/test/cplusplus.cpp @@ -0,0 +1,22 @@ +/** + * @file test/cplusplus.cpp Baresip selftest -- C++ compatibility + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "test.h" + + +int test_cplusplus(void) +{ + const char *version = sys_libre_version_get(); + int err = 0; + + ASSERT_TRUE(str_isset(version)); + info("c++ ok\n"); + +out: + return err; +} diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..e917f2d --- /dev/null +++ b/test/main.c @@ -0,0 +1,271 @@ +/** + * @file test/main.c Selftest for Baresip core + * + * Copyright (C) 2010 Creytiv.com + */ +#ifdef SOLARIS +#define __EXTENSIONS__ 1 +#endif +#include <getopt.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +typedef int (test_exec_h)(void); + +struct test { + test_exec_h *exec; + const char *name; +}; + +#define TEST(a) {a, #a} + +static const struct test tests[] = { + TEST(test_account), + TEST(test_aulevel), + TEST(test_call_af_mismatch), + TEST(test_call_answer), + TEST(test_call_answer_hangup_a), + TEST(test_call_answer_hangup_b), + TEST(test_call_reject), + TEST(test_call_rtp_timeout), + TEST(test_call_multiple), + TEST(test_call_max), + TEST(test_call_dtmf), + TEST(test_call_aulevel), + TEST(test_call_progress), + TEST(test_call_format_float), +#ifdef USE_VIDEO + TEST(test_call_video), + TEST(test_video), +#endif + TEST(test_cmd), + TEST(test_cmd_long), + TEST(test_contact), + TEST(test_cplusplus), + TEST(test_message), + TEST(test_mos), + TEST(test_network), + TEST(test_play), + TEST(test_ua_alloc), + TEST(test_ua_options), + TEST(test_ua_register), + TEST(test_ua_register_dns), + TEST(test_ua_register_auth), + TEST(test_ua_register_auth_dns), + TEST(test_uag_find_param), +}; + + +static int run_one_test(const struct test *test) +{ + int err; + + re_printf("[ RUN ] %s\n", test->name); + + err = test->exec(); + if (err) { + warning("%s: test failed (%m)\n", + test->name, err); + return err; + } + + re_printf("[ OK ]\n"); + + return 0; +} + + +static int run_tests(void) +{ + size_t i; + int err; + + for (i=0; i<ARRAY_SIZE(tests); i++) { + + re_printf("[ RUN ] %s\n", tests[i].name); + + err = tests[i].exec(); + if (err) { + warning("%s: test failed (%m)\n", + tests[i].name, err); + return err; + } + + re_printf("[ OK ]\n"); + } + + return 0; +} + + +static void test_listcases(void) +{ + size_t i, n; + + n = ARRAY_SIZE(tests); + + (void)re_printf("\n%zu test cases:\n", n); + + for (i=0; i<(n+1)/2; i++) { + + (void)re_printf(" %-32s %s\n", + tests[i].name, + (i+(n+1)/2) < n ? tests[i+(n+1)/2].name : ""); + } + + (void)re_printf("\n"); +} + + +static const struct test *find_test(const char *name) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(tests); i++) { + + if (0 == str_casecmp(name, tests[i].name)) + return &tests[i]; + } + + return NULL; +} + + +static void ua_exit_handler(void *arg) +{ + (void)arg; + + debug("ua exited -- stopping main runloop\n"); + re_cancel(); +} + + +static void usage(void) +{ + (void)re_fprintf(stderr, + "Usage: selftest [options] <testcases..>\n" + "options:\n" + "\t-l List all testcases and exit\n" + "\t-v Verbose output (INFO level)\n" + ); +} + + +int main(int argc, char *argv[]) +{ + struct config *config; + size_t i, ntests; + bool verbose = false; + int err; + + err = libre_init(); + if (err) + return err; + + log_enable_info(false); + + for (;;) { + const int c = getopt(argc, argv, "hlv"); + if (0 > c) + break; + + switch (c) { + + case '?': + case 'h': + usage(); + return -2; + + case 'l': + test_listcases(); + return 0; + + case 'v': + if (verbose) + log_enable_debug(true); + else + log_enable_info(true); + verbose = true; + break; + + default: + break; + } + } + + if (argc >= (optind + 1)) + ntests = argc - optind; + else + ntests = ARRAY_SIZE(tests); + + re_printf("running baresip selftest version %s with %zu tests\n", + BARESIP_VERSION, ntests); + + /* note: run SIP-traffic on localhost */ + config = conf_config(); + if (!config) { + err = ENOENT; + goto out; + } + + err = baresip_init(config, false); + if (err) + goto out; + + str_ncpy(config->sip.local, "127.0.0.1:0", sizeof(config->sip.local)); + + uag_set_exit_handler(ua_exit_handler, NULL); + + if (argc >= (optind + 1)) { + + for (i=0; i<ntests; i++) { + const char *name = argv[optind + i]; + const struct test *test; + + test = find_test(name); + if (test) { + err = run_one_test(test); + if (err) + goto out; + } + else { + re_fprintf(stderr, + "testcase not found: `%s'\n", + name); + err = ENOENT; + goto out; + } + } + } + else { + err = run_tests(); + if (err) + goto out; + } + +#if 1 + ua_stop_all(true); +#endif + + re_printf("\x1b[32mOK. %zu tests passed successfully\x1b[;m\n", + ntests); + + out: + if (err) { + warning("test failed (%m)\n", err); + re_printf("%H\n", re_debug, 0); + } + ua_stop_all(true); + ua_close(); + + baresip_close(); + + libre_close(); + + tmr_debug(); + mem_debug(); + + return err; +} diff --git a/test/message.c b/test/message.c new file mode 100644 index 0000000..ddad3ce --- /dev/null +++ b/test/message.c @@ -0,0 +1,232 @@ +/** + * @file test/message.c Baresip selftest -- message sending + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +struct test { + enum sip_transp transp; + int err; +}; + +struct endpoint { + struct test *test; + struct endpoint *other; + struct message *message; + struct ua *ua; + char uri[256]; + unsigned n_msg; + unsigned n_resp; +}; + + +static const char dummy_msg[] = "hei paa deg"; +static const char text_plain[] = "text/plain"; + + +static bool endpoint_is_complete(const struct endpoint *ep) +{ + return ep->n_msg >= 1 || ep->n_resp >= 1; +} + + +static bool test_is_complete(struct endpoint *ep) +{ + return endpoint_is_complete(ep) && + endpoint_is_complete(ep->other); +} + + +static void message_recv_handler(const struct pl *peer, const struct pl *ctype, + struct mbuf *body, void *arg) +{ + struct endpoint *ep = arg; + int err = 0; + + info("[ %s ] recv msg from %r: \"%b\"\n", ep->uri, peer, + mbuf_buf(body), mbuf_get_left(body)); + + TEST_STRCMP(text_plain, strlen(text_plain), + ctype->p, ctype->l); + + TEST_STRCMP(dummy_msg, str_len(dummy_msg), + mbuf_buf(body), mbuf_get_left(body)); + + ++ep->n_msg; + + if (test_is_complete(ep)) { + re_cancel(); + return; + } + + out: + if (err) { + ep->test->err = err; + re_cancel(); + } +} + + +static void send_resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct endpoint *ep = arg; + + ++ep->n_resp; + + if (err) { + warning("sending failed: %m\n", err); + goto out; + } + + if (msg->scode >= 300) { + warning("sending failed: %u %r\n", msg->scode, &msg->reason); + err = EPROTO; + goto out; + } + + info("[ %s ] message sent OK\n", ep->uri); + + ASSERT_EQ(ep->test->transp, msg->tp); + ASSERT_EQ(200, msg->scode); + + if (test_is_complete(ep)) { + re_cancel(); + return; + } + + out: + if (err) { + ep->test->err = err; + re_cancel(); + } +} + + +static void endpoint_destructor(void *data) +{ + struct endpoint *ep = data; + + mem_deref(ep->message); + mem_deref(ep->ua); +} + + +static int endpoint_alloc(struct endpoint **epp, struct test *test, + const char *name, enum sip_transp transp) +{ + struct endpoint *ep = NULL; + struct sa laddr; + char aor[256]; + int err = 0; + + err = sip_transp_laddr(uag_sip(), &laddr, transp, NULL); + TEST_ERR(err); + + ep = mem_zalloc(sizeof(*ep), endpoint_destructor); + if (!ep) + return ENOMEM; + + ep->test = test; + + if (re_snprintf(aor, sizeof(aor), + "%s <sip:%s@127.0.0.1;transport=%s>;regint=0", + name, name, + sip_transp_name(transp)) < 0) { + err = ENOMEM; + goto out; + } + + if (re_snprintf(ep->uri, sizeof(ep->uri), "sip:%s@%J;transport=%s", + name, + &laddr, sip_transp_name(transp)) < 0) { + err = ENOMEM; + goto out; + } + + err = ua_alloc(&ep->ua, aor); + if (err) + goto out; + + err = message_init(&ep->message); + TEST_ERR(err); + + out: + if (err) + mem_deref(ep); + else + *epp = ep; + + return err; +} + + +static int test_message_transp(enum sip_transp transp) +{ + struct test test; + struct endpoint *a = NULL, *b = NULL; + bool enable_udp, enable_tcp; + int err = 0; + + enable_udp = transp == SIP_TRANSP_UDP; + enable_tcp = transp == SIP_TRANSP_TCP; + + memset(&test, 0, sizeof(test)); + + test.transp = transp; + + err = ua_init("test", enable_udp, enable_tcp, false, false); + TEST_ERR(err); + + err = endpoint_alloc(&a, &test, "a", transp); + TEST_ERR(err); + + err = endpoint_alloc(&b, &test, "b", transp); + TEST_ERR(err); + + a->other = b; + b->other = a; + + /* NOTE: can only listen to one global instance for now */ + err = message_listen(NULL, b->message, message_recv_handler, b); + TEST_ERR(err); + + /* Send a message from A to B */ + err = message_send(a->ua, b->uri, dummy_msg, send_resp_handler, a); + TEST_ERR(err); + + err = re_main_timeout(1000); + TEST_ERR(err); + + TEST_ERR(test.err); + ASSERT_EQ(0, a->n_msg); + ASSERT_EQ(1, a->n_resp); + ASSERT_EQ(1, b->n_msg); + ASSERT_EQ(0, b->n_resp); + + out: + mem_deref(b); + mem_deref(a); + ua_close(); + + return err; +} + + +int test_message(void) +{ + int err = 0; + + err = test_message_transp(SIP_TRANSP_UDP); + TEST_ERR(err); + + err |= test_message_transp(SIP_TRANSP_TCP); + TEST_ERR(err); + + out: + return err; +} diff --git a/test/mock/cert.c b/test/mock/cert.c new file mode 100644 index 0000000..a711082 --- /dev/null +++ b/test/mock/cert.c @@ -0,0 +1,82 @@ +/** + * @file cert.c TLS Certificate + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include "../test.h" + + +/** + * Dummy certificate for testing. + * + * + * It was generated like this: + * + * $ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \ + * -days 36500 -nodes + * + * + * Dumping information: + * + * $ openssl x509 -subject -dates -fingerprint -in cert.pem + * subject= /C=NO/ST=Retest/O=Retest AS/CN=Mr Retest/emailAddress=re@test.org + * notBefore=Nov 23 18:40:38 2014 GMT + * notAfter=Oct 30 18:40:38 2114 GMT + * Fingerprint=49:A4:E9:F4:80:3A:D4:38:84:F1:64:C3:B9:4B:F9:BB:80:F7:07:76 + */ + +const char test_certificate[] = + +"-----BEGIN CERTIFICATE-----\r\n" +"MIIDmTCCAoGgAwIBAgIJAIt1/MAlTpB7MA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV\r\n" +"BAYTAk5PMQ8wDQYDVQQIDAZSZXRlc3QxEjAQBgNVBAoMCVJldGVzdCBBUzESMBAG\r\n" +"A1UEAwwJTXIgUmV0ZXN0MRowGAYJKoZIhvcNAQkBFgtyZUB0ZXN0Lm9yZzAgFw0x\r\n" +"NDExMjMxODQwMzhaGA8yMTE0MTAzMDE4NDAzOFowYjELMAkGA1UEBhMCTk8xDzAN\r\n" +"BgNVBAgMBlJldGVzdDESMBAGA1UECgwJUmV0ZXN0IEFTMRIwEAYDVQQDDAlNciBS\r\n" +"ZXRlc3QxGjAYBgkqhkiG9w0BCQEWC3JlQHRlc3Qub3JnMIIBIjANBgkqhkiG9w0B\r\n" +"AQEFAAOCAQ8AMIIBCgKCAQEAqnX4j6WK6tcN/X+C8C9ZSSlhVT2OdPB/lAPa3T3w\r\n" +"eB3wu2C9gnZcCSvekBhyFKSi4w0Az5HNjJWWQqpSeYW2MCEKI97DIu0hg/5qn2le\r\n" +"2sDjo4u/SNdH0CQHaLD2Xu0hhvZ/dTIulxpy5hLVmxs8/UZ8QKZ3vxDFE92p4LBL\r\n" +"tLYz6+TvWovmUqYL97J+2muXUMcZCCTbk8DQSGLBbsawXejVF8RgPiFHCvefybUQ\r\n" +"JCbtTDTfMykVnMEv3yMmfcXG/mwG8CLDRv7y8wh632aDdWfKN+g70gH0CFdjn070\r\n" +"eIZyJ3TyRi4b55RSC4FAdP2YKToOUH55N86wrbprnHb8swIDAQABo1AwTjAdBgNV\r\n" +"HQ4EFgQUcaZeVPUmMPFvKwYzn27b8BUJV3AwHwYDVR0jBBgwFoAUcaZeVPUmMPFv\r\n" +"KwYzn27b8BUJV3AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAbsMt\r\n" +"zruNpBUZv08vdoWN9QWaJrmv8fvcx/RcuVLRuAaLYExEUJnoz3+dNFbR38BvncVC\r\n" +"LlDcSIK06JIHX6E7gJegWQdECoO/YgQGCwoIoQJtNCybxtZccb5uAGY/+qO3uOx0\r\n" +"Vx1NxrAMh5cpOIhZ8XiSYA2+JB71prW97diSQS+cU9xWCJxPU7UqQ10nV7PbfSmp\r\n" +"XTnj+togZPXYzJmSQR4RoM4Vqu27syo7xYQ90twoRKpRYTPdDTArpkTn6KuUuCJ1\r\n" +"t2v9LOkkxOvF11rLY6rLf0BG4XYkhnz4CLLt428wvAPykPcs95Q9TwpF/nKEwyfh\r\n" +"J+cC3FZTiBf/YmPPaA==\r\n" +"-----END CERTIFICATE-----\r\n" + +"-----BEGIN PRIVATE KEY-----\r\n" +"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCqdfiPpYrq1w39\r\n" +"f4LwL1lJKWFVPY508H+UA9rdPfB4HfC7YL2CdlwJK96QGHIUpKLjDQDPkc2MlZZC\r\n" +"qlJ5hbYwIQoj3sMi7SGD/mqfaV7awOOji79I10fQJAdosPZe7SGG9n91Mi6XGnLm\r\n" +"EtWbGzz9RnxApne/EMUT3angsEu0tjPr5O9ai+ZSpgv3sn7aa5dQxxkIJNuTwNBI\r\n" +"YsFuxrBd6NUXxGA+IUcK95/JtRAkJu1MNN8zKRWcwS/fIyZ9xcb+bAbwIsNG/vLz\r\n" +"CHrfZoN1Z8o36DvSAfQIV2OfTvR4hnIndPJGLhvnlFILgUB0/ZgpOg5Qfnk3zrCt\r\n" +"umucdvyzAgMBAAECggEAC1xxhKFz8NMEi7DD+V4uhUHMyvGfXQvqdOMM41INhPP5\r\n" +"54M7HkblO3dBDjmS4O1YLenf8/WzzXrq2OahOJhA3FRXaKygNOO5KCL82EMdn1bb\r\n" +"1TqrNR+kGatNEx04TntfkK89L4J4uHl6zvrSYdQe7IKWJXjy4jkr6XcMq30Ujqa6\r\n" +"Val03Cr40VL0ZSYXnwTf/P65GtQTdyTOemYkUbMH9qRoxHmE7ZsPRpbXU4k30JWh\r\n" +"6VYy+7h5XmjrX+VdIiia6sMjy0mbtsxJb6DOo19ro4DfF5JmFZpXnBhsJGhMxiEM\r\n" +"94QEC5Tv6b0hWomFpOm3I5jOnktavCFQ1NsNHUspgQKBgQDUQm2+F06YdC6Pgt+9\r\n" +"COnd9rz2lB2TjGRIJvis6MW7EfQ7XHkH9y/sDGLzINBVn6DkaHmQZDrIg77Mey+H\r\n" +"LG0QL6+7WK7c1X/Zga6LKvkLlmcMWq6i1uu+Q3UODu7XcFh6f8kDThP+BubfUWpw\r\n" +"rCRq74gF1JzQISoKqmyW/AXMZQKBgQDNln0jzgh/ySWRjJbuh6GqPQunDQsh3K3I\r\n" +"4WDHK40NHFgIzPomKO+pqsOu3V/X81NARqfUyoIp/455YRheLHBUJg7maLfxrBq/\r\n" +"qQPEUwSAI6lheMz1WNWri65GvwBlVENajVuMh/xfmKVL1KVV+LXGO5L1UClWITCM\r\n" +"VSC0QT8XNwKBgQC7bYUWS+JdAIp0su36MDrCgzPM0HFlbpzGkZMYq9qeG3Z8TGWb\r\n" +"QQyR9UYSxjDwyqn5xr9BXyABG0SJr2UCiZosps8YMXEHE4d3eumzfdi4ALEx2YlH\r\n" +"xVwZf9uG9Gy21D9svBW102YX8+Q94diJcZgezTBhZaKqrf4/uMl2cUh1eQKBgQDB\r\n" +"heZYXOqdN0A5CUlOUbg5YutkHaAcCPpBvP33niRRchvgdOsIHsKzSL6ZDWPaCP+V\r\n" +"4qy7XsE2PYzk7yQcCeLXI1glRe/Y+3PWdIfKN4dmA6u+yBLO5QeFSqALkmISAEbC\r\n" +"p4vE9oD3j94RSqM0EUEy0ANfDk1K+UUU5FE7vKth8wKBgQCoKvPNfC3FmyhLK7EK\r\n" +"zgfIYAcoi52EFu6xQ9PvZHg60ptYvq1L+H6LR8cxYfcrPTt3aDbFf41RahPkokNh\r\n" +"2HDxiHd4HwWkAGiqZXCTA2znb+rnJ9fheI6g/Wb3p+oGeCFHMcdUcsl1qWBFDtax\r\n" +"ygS/tEFgy1z2dVMLbLKqEUscsA==\r\n" +"-----END PRIVATE KEY-----\r\n" +; diff --git a/test/mock/dnssrv.c b/test/mock/dnssrv.c new file mode 100644 index 0000000..e5ac188 --- /dev/null +++ b/test/mock/dnssrv.c @@ -0,0 +1,245 @@ +/** + * @file mock/dnssrv.c Mock DNS server + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include "../test.h" + + +#define DEBUG_MODULE "mock/dnssrv" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define LOCAL_PORT 0 + + +static void dns_server_match(struct dns_server *srv, struct list *rrl, + const char *name, uint16_t type) +{ + struct dnsrr *rr0 = NULL; + struct le *le; + + le = srv->rrl.head; + while (le) { + + struct dnsrr *rr = le->data; + le = le->next; + + if (type == rr->type && 0==str_casecmp(name, rr->name)) { + + if (!rr0) + rr0 = rr; + list_append(rrl, &rr->le_priv, rr); + } + } + + /* If rotation is enabled, then rotate multiple entries + in a deterministic way (no randomness please) */ + if (srv->rotate && rr0) { + list_unlink(&rr0->le); + list_append(&srv->rrl, &rr0->le, rr0); + } +} + + +static void decode_dns_query(struct dns_server *srv, + const struct sa *src, struct mbuf *mb) +{ + struct list rrl = LIST_INIT; + struct dnshdr hdr; + struct le *le; + char *qname = NULL; + size_t start, end; + uint16_t type, dnsclass; + int err = 0; + + start = mb->pos; + end = mb->end; + + if (dns_hdr_decode(mb, &hdr) || hdr.qr || hdr.nq != 1) { + DEBUG_WARNING("unable to decode query header\n"); + return; + } + + err = dns_dname_decode(mb, &qname, start); + if (err) { + DEBUG_WARNING("unable to decode query name\n"); + goto out; + } + + if (mbuf_get_left(mb) < 4) { + err = EBADMSG; + DEBUG_WARNING("unable to decode query type/class\n"); + goto out; + } + + type = ntohs(mbuf_read_u16(mb)); + dnsclass = ntohs(mbuf_read_u16(mb)); + + DEBUG_INFO("dnssrv: type=%s query-name='%s'\n", + dns_rr_typename(type), qname); + + if (dnsclass == DNS_CLASS_IN) { + dns_server_match(srv, &rrl, qname, type); + } + + hdr.qr = true; + hdr.tc = false; + hdr.rcode = DNS_RCODE_OK; + hdr.nq = 1; + hdr.nans = list_count(&rrl); + + mb->pos = start; + + err = dns_hdr_encode(mb, &hdr); + if (err) + goto out; + + mb->pos = end; + + DEBUG_INFO("dnssrv: @@ found %u answers for %s\n", + list_count(&rrl), qname); + + for (le = rrl.head; le; le = le->next) { + struct dnsrr *rr = le->data; + + err = dns_rr_encode(mb, rr, 0, NULL, start); + if (err) + goto out; + } + + mb->pos = start; + + (void)udp_send(srv->us, src, mb); + + out: + list_clear(&rrl); + mem_deref(qname); +} + + +static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg) +{ + struct dns_server *srv = arg; + + decode_dns_query(srv, src, mb); +} + + +static void destructor(void *arg) +{ + struct dns_server *srv = arg; + + list_flush(&srv->rrl); + mem_deref(srv->us); +} + + +int dns_server_alloc(struct dns_server **srvp, bool rotate) +{ + struct dns_server *srv; + int err; + + if (!srvp) + return EINVAL; + + srv = mem_zalloc(sizeof(*srv), destructor); + if (!srv) + return ENOMEM; + + err = sa_set_str(&srv->addr, "127.0.0.1", LOCAL_PORT); + if (err) + goto out; + + err = udp_listen(&srv->us, &srv->addr, udp_recv, srv); + if (err) + goto out; + + err = udp_local_get(srv->us, &srv->addr); + if (err) + goto out; + + srv->rotate = rotate; + + out: + if (err) + mem_deref(srv); + else + *srvp = srv; + + return err; +} + + +int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr) +{ + struct dnsrr *rr; + int err; + + if (!srv || !name) + return EINVAL; + + rr = dns_rr_alloc(); + if (!rr) + return ENOMEM; + + err = str_dup(&rr->name, name); + if (err) + goto out; + + rr->type = DNS_TYPE_A; + rr->dnsclass = DNS_CLASS_IN; + rr->ttl = 3600; + rr->rdlen = 0; + + rr->rdata.a.addr = addr; + + list_append(&srv->rrl, &rr->le, rr); + + out: + if (err) + mem_deref(rr); + + return err; +} + + +int dns_server_add_srv(struct dns_server *srv, const char *name, + uint16_t pri, uint16_t weight, uint16_t port, + const char *target) +{ + struct dnsrr *rr; + int err; + + if (!srv || !name || !port || !target) + return EINVAL; + + rr = dns_rr_alloc(); + if (!rr) + return ENOMEM; + + err = str_dup(&rr->name, name); + if (err) + goto out; + + rr->type = DNS_TYPE_SRV; + rr->dnsclass = DNS_CLASS_IN; + rr->ttl = 3600; + rr->rdlen = 0; + + rr->rdata.srv.pri = pri; + rr->rdata.srv.weight = weight; + rr->rdata.srv.port = port; + str_dup(&rr->rdata.srv.target, target); + + list_append(&srv->rrl, &rr->le, rr); + + out: + if (err) + mem_deref(rr); + + return err; +} diff --git a/test/mock/mock_aucodec.c b/test/mock/mock_aucodec.c new file mode 100644 index 0000000..fd91721 --- /dev/null +++ b/test/mock/mock_aucodec.c @@ -0,0 +1,94 @@ +/** + * @file mock/mock_aucodec.c Mock audio codec + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +/* A dummy protocol header */ +#define L16_HEADER 0x1616 + + +static int mock_l16_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 = 2 + sampc*2; + + *p++ = L16_HEADER; + + while (sampc--) + *p++ = htons(*sampv++); + + return 0; +} + + +static int mock_l16_decode(struct audec_state *st, + int16_t *sampv, size_t *sampc, + const uint8_t *buf, size_t len) +{ + int16_t *p = (void *)buf; + uint16_t hdr; + (void)st; + + if (!buf || !len || !sampv) + return EINVAL; + + if (len < 2) + return EINVAL; + + if (*sampc < len/2) + return ENOMEM; + + *sampc = (len - 2)/2; + + hdr = *p++; + if (L16_HEADER != hdr) { + warning("mock_aucodec: invalid L16 header" + " 0x%04x (len=%zu)\n", hdr, len); + return EPROTO; + } + + len = len/2 - 2; + while (len--) + *sampv++ = ntohs(*p++); + + return 0; +} + + +static struct aucodec ac_dummy = { + .name = "FOO16", + .srate = 8000, + .crate = 8000, + .ch = 1, + .ench = mock_l16_encode, + .dech = mock_l16_decode, +}; + + +void mock_aucodec_register(void) +{ + aucodec_register(baresip_aucodecl(), &ac_dummy); +} + + +void mock_aucodec_unregister(void) +{ + aucodec_unregister(&ac_dummy); +} diff --git a/test/mock/mock_auplay.c b/test/mock/mock_auplay.c new file mode 100644 index 0000000..2a4ffcc --- /dev/null +++ b/test/mock/mock_auplay.c @@ -0,0 +1,102 @@ +/** + * @file mock/mock_auplay.c Mock audio player + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +struct auplay_st { + const struct auplay *ap; /* inheritance */ + + struct tmr tmr; + struct auplay_prm prm; + void *sampv; + size_t sampc; + auplay_write_h *wh; + void *arg; +}; + + +static struct { + mock_sample_h *sampleh; + void *arg; +} mock; + + +static void tmr_handler(void *arg) +{ + struct auplay_st *st = arg; + + tmr_start(&st->tmr, st->prm.ptime, tmr_handler, st); + + if (st->wh) + st->wh(st->sampv, st->sampc, st->arg); + + /* feed the audio-samples back to the test */ + if (mock.sampleh) + mock.sampleh(st->sampv, st->sampc, mock.arg); +} + + +static void auplay_destructor(void *arg) +{ + struct auplay_st *st = arg; + + tmr_cancel(&st->tmr); + mem_deref(st->sampv); +} + + +static int mock_auplay_alloc(struct auplay_st **stp, const 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; + + if (!stp || !ap || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), auplay_destructor); + if (!st) + return ENOMEM; + + st->ap = ap; + st->prm = *prm; + st->wh = wh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + tmr_start(&st->tmr, 0, tmr_handler, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int mock_auplay_register(struct auplay **auplayp, + mock_sample_h *sampleh, void *arg) +{ + mock.sampleh = sampleh; + mock.arg = arg; + + return auplay_register(auplayp, baresip_auplayl(), + "mock-auplay", mock_auplay_alloc); +} diff --git a/test/mock/mock_ausrc.c b/test/mock/mock_ausrc.c new file mode 100644 index 0000000..070cbfc --- /dev/null +++ b/test/mock/mock_ausrc.c @@ -0,0 +1,91 @@ +/** + * @file mock/mock_ausrc.c Mock audio source + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +struct ausrc_st { + const struct ausrc *as; /* inheritance */ + + struct tmr tmr; + struct ausrc_prm prm; + void *sampv; + size_t sampc; + ausrc_read_h *rh; + void *arg; +}; + + +static void tmr_handler(void *arg) +{ + struct ausrc_st *st = arg; + + tmr_start(&st->tmr, st->prm.ptime, tmr_handler, st); + + if (st->rh) + st->rh(st->sampv, st->sampc, st->arg); +} + + +static void ausrc_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + tmr_cancel(&st->tmr); + mem_deref(st->sampv); +} + + +static int mock_ausrc_alloc(struct ausrc_st **stp, const 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; + + if (!stp || !as || !prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), ausrc_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->prm = *prm; + st->rh = rh; + st->arg = arg; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + + st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL); + if (!st->sampv) { + err = ENOMEM; + goto out; + } + + tmr_start(&st->tmr, 0, tmr_handler, st); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int mock_ausrc_register(struct ausrc **ausrcp) +{ + return ausrc_register(ausrcp, baresip_ausrcl(), + "mock-ausrc", mock_ausrc_alloc); +} diff --git a/test/mock/mock_vidcodec.c b/test/mock/mock_vidcodec.c new file mode 100644 index 0000000..9609e65 --- /dev/null +++ b/test/mock/mock_vidcodec.c @@ -0,0 +1,210 @@ +/** + * @file mock/mock_vidcodec.c Mock video codec + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +#define HDR_SIZE 12 + + +struct hdr { + enum vidfmt fmt; + unsigned width; + unsigned height; +}; + +struct videnc_state { + int64_t pts; + unsigned fps; + videnc_packet_h *pkth; + void *arg; +}; + +struct viddec_state { + struct vidframe *frame; +}; + + +static int hdr_decode(struct hdr *hdr, struct mbuf *mb) +{ + if (mbuf_get_left(mb) < HDR_SIZE) + return EBADMSG; + + hdr->fmt = ntohl(mbuf_read_u32(mb)); + hdr->width = ntohl(mbuf_read_u32(mb)); + hdr->height = ntohl(mbuf_read_u32(mb)); + + return 0; +} + + +static void decode_destructor(void *arg) +{ + struct viddec_state *vds = arg; + + mem_deref(vds->frame); +} + + +static int mock_encode_update(struct videnc_state **vesp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *ves; + (void)fmtp; + + if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1)) + return EINVAL; + + ves = *vesp; + + if (!ves) { + + ves = mem_zalloc(sizeof(*ves), NULL); + if (!ves) + return ENOMEM; + + *vesp = ves; + } + + ves->fps = prm->fps; + ves->pkth = pkth; + ves->arg = arg; + + return 0; +} + + +static int mock_encode(struct videnc_state *ves, bool update, + const struct vidframe *frame) +{ + struct mbuf *hdr; + uint8_t payload[2] = {0,0}; + uint32_t rtp_ts; + int err; + (void)update; + + if (!ves || !frame) + return EINVAL; + + hdr = mbuf_alloc(16); + + err = mbuf_write_u32(hdr, htonl(frame->fmt)); + err |= mbuf_write_u32(hdr, htonl(frame->size.w)); + err |= mbuf_write_u32(hdr, htonl(frame->size.h)); + if (err) + goto out; + + rtp_ts = video_calc_rtp_timestamp(++ves->pts, ves->fps); + + err = ves->pkth(true, rtp_ts, hdr->buf, hdr->end, + payload, sizeof(payload), ves->arg); + if (err) + goto out; + + out: + mem_deref(hdr); + + return err; +} + + +static int mock_decode_update(struct viddec_state **vdsp, + const struct vidcodec *vc, const char *fmtp) +{ + struct viddec_state *vds; + int err = 0; + (void)vc; + (void)fmtp; + + if (!vdsp) + return EINVAL; + + vds = *vdsp; + + if (vds) + return 0; + + vds = mem_zalloc(sizeof(*vds), decode_destructor); + if (!vds) + return ENOMEM; + + if (err) + mem_deref(vds); + else + *vdsp = vds; + + return err; +} + + +static int mock_decode(struct viddec_state *vds, struct vidframe *frame, + bool *intra, bool marker, uint16_t seq, struct mbuf *mb) +{ + struct vidsz size; + struct hdr hdr; + int err, i; + (void)marker; + (void)seq; + + if (!vds || !frame || !intra || !mb) + return EINVAL; + + *intra = false; + + err = hdr_decode(&hdr, mb); + if (err) { + warning("mock_vidcodec: could not decode header (%m)\n", err); + return err; + } + + size.w = hdr.width; + size.h = hdr.height; + + if (!vds->frame) { + err = vidframe_alloc(&vds->frame, hdr.fmt, &size); + if (err) + goto out; + } + + for (i=0; i<4; i++) { + frame->data[i] = vds->frame->data[i]; + frame->linesize[i] = vds->frame->linesize[i]; + } + + frame->size.w = vds->frame->size.w; + frame->size.h = vds->frame->size.h; + frame->fmt = vds->frame->fmt; + + out: + return err; +} + + +static struct vidcodec vc_dummy = { + .name = "H266", + .encupdh = mock_encode_update, + .ench = mock_encode, + .decupdh = mock_decode_update, + .dech = mock_decode, +}; + + +void mock_vidcodec_register(void) +{ + vidcodec_register(baresip_vidcodecl(), &vc_dummy); +} + + +void mock_vidcodec_unregister(void) +{ + vidcodec_unregister(&vc_dummy); +} diff --git a/test/mock/mock_vidisp.c b/test/mock/mock_vidisp.c new file mode 100644 index 0000000..322dd1c --- /dev/null +++ b/test/mock/mock_vidisp.c @@ -0,0 +1,97 @@ +/** + * @file mock/mock_vidisp.c Mock video display + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +#define MAX_WIDTH 65536 +#define MAX_HEIGHT 65536 + + +struct vidisp_st { + const struct vidisp *vd; /* inheritance */ + unsigned n_frame; +}; + + +static void disp_destructor(void *arg) +{ + struct vidisp_st *st = arg; + (void)st; +} + + +static int mock_disp_alloc(struct vidisp_st **stp, const struct vidisp *vd, + struct vidisp_prm *prm, const char *dev, + vidisp_resize_h *resizeh, void *arg) +{ + struct vidisp_st *st; + (void)prm; + (void)dev; + (void)resizeh; + (void)arg; + + if (!stp || !vd) + return EINVAL; + + st = mem_zalloc(sizeof(*st), disp_destructor); + if (!st) + return ENOMEM; + + st->vd = vd; + + *stp = st; + + return 0; +} + + +static int mock_display(struct vidisp_st *st, const char *title, + const struct vidframe *frame) +{ + unsigned width, height; + (void)title; + + if (!st || !frame) + return EINVAL; + + width = frame->size.w; + height = frame->size.h; + + if (!vidframe_isvalid(frame)) { + warning("mock_vidisp: got invalid frame\n"); + return EPROTO; + } + + /* verify that the video frame is good */ + if (frame->fmt >= VID_FMT_N) + return EPROTO; + if (width == 0 || width > MAX_WIDTH) + return EPROTO; + if (height == 0 || height > MAX_HEIGHT) + return EPROTO; + if (frame->linesize[0] == 0) + return EPROTO; + + ++st->n_frame; + + if (st->n_frame >= 10) { + info("mock_vidisp: got %u frames -- stopping re_main\n", + st->n_frame); + re_cancel(); /* XXX use a callback handler instead */ + } + + return 0; +} + + +int mock_vidisp_register(struct vidisp **vidispp) +{ + return vidisp_register(vidispp, baresip_vidispl(), "mock-vidisp", + mock_disp_alloc, NULL, mock_display, NULL); +} diff --git a/test/mock/mock_vidsrc.c b/test/mock/mock_vidsrc.c new file mode 100644 index 0000000..8f92bc4 --- /dev/null +++ b/test/mock/mock_vidsrc.c @@ -0,0 +1,91 @@ +/** + * @file mock/mock_vidsrc.c Mock video source + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "../test.h" + + +struct vidsrc_st { + const struct vidsrc *vs; /* inheritance */ + + struct vidframe *frame; + struct tmr tmr; + int fps; + vidsrc_frame_h *frameh; + void *arg; +}; + + +static void tmr_handler(void *arg) +{ + struct vidsrc_st *st = arg; + + tmr_start(&st->tmr, 1000/st->fps, tmr_handler, st); + + if (st->frameh) + st->frameh(st->frame, st->arg); +} + + +static void vidsrc_destructor(void *arg) +{ + struct vidsrc_st *st = arg; + + tmr_cancel(&st->tmr); + mem_deref(st->frame); +} + + +static int mock_vidsrc_alloc(struct vidsrc_st **stp, const 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), vidsrc_destructor); + if (!st) + return ENOMEM; + + st->vs = vs; + st->fps = prm->fps; + st->frameh = frameh; + st->arg = arg; + + err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size); + if (err) + goto out; + + tmr_start(&st->tmr, 0, tmr_handler, st); + + info("mock_vidsrc: new instance with size %u x %u (%d fps)\n", + size->w, size->h, prm->fps); + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +int mock_vidsrc_register(struct vidsrc **vidsrcp) +{ + return vidsrc_register(vidsrcp, baresip_vidsrcl(), "mock-vidsrc", + mock_vidsrc_alloc, NULL); +} diff --git a/test/mos.c b/test/mos.c new file mode 100644 index 0000000..20b13c2 --- /dev/null +++ b/test/mos.c @@ -0,0 +1,53 @@ +/** + * @file test/mos.c Test the MOS (Mean Opinion Score) calculator + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include "test.h" + + +#define DEBUG_MODULE "mos" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +int test_mos(void) +{ +#define PRECISION 0.001 + static struct { + /* input: */ + double rtt; + double jitter; + uint32_t packet_loss; + + /* output: */ + double r_factor; + double mos; + } testv[] = { + { 0.0, 0.0, 0, 92.95, 4.404 }, + { 500.0, 0.0, 0, 54.20, 2.796 }, + { 1000.0, 0.0, 0, 4.20, 0.990 }, + { 0.0, 100.0, 0, 84.20, 4.172 }, + { 0.0, 200.0, 0, 64.20, 3.315 }, + { 0.0, 0.0, 1, 90.45, 4.350 }, + { 0.0, 0.0, 10, 67.95, 3.499 }, + { 10.0, 10.0, 10, 67.20, 3.463 }, + }; + size_t i; + int err = 0; + + for (i=0; i<ARRAY_SIZE(testv); i++) { + double r_factor, mos; + + mos = mos_calculate(&r_factor, testv[i].rtt, testv[i].jitter, + testv[i].packet_loss); + + ASSERT_DOUBLE_EQ(testv[i].r_factor, r_factor, PRECISION); + ASSERT_DOUBLE_EQ(testv[i].mos, mos, PRECISION); + } + + out: + return err; +} diff --git a/test/net.c b/test/net.c new file mode 100644 index 0000000..170fc3b --- /dev/null +++ b/test/net.c @@ -0,0 +1,46 @@ +/** + * @file test/net.c Baresip selftest -- networking + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +static struct config_net default_config; + + +static void net_change_handler(void *arg) +{ + unsigned *count = arg; + ++*count; + info("network changed\n"); +} + + +int test_network(void) +{ + struct network *net = NULL; + unsigned change_count = 0; + int err; + + err = net_alloc(&net, &default_config, AF_INET); + TEST_ERR(err); + ASSERT_TRUE(net != NULL); + + ASSERT_EQ(AF_INET, net_af(net)); + + net_change(net, 1, net_change_handler, &change_count); + + ASSERT_EQ(0, change_count); + + net_force_change(net); + + ASSERT_EQ(1, change_count); + + out: + mem_deref(net); + return err; +} diff --git a/test/play.c b/test/play.c new file mode 100644 index 0000000..82c8813 --- /dev/null +++ b/test/play.c @@ -0,0 +1,99 @@ +/** + * @file test/play.c Baresip selftest -- audio file player + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +#define NUM_SAMPLES 320 /* 8000 Hz, 1 channel, 40ms */ + + +struct test { + struct mbuf *mb_samp; +}; + + +static struct mbuf *generate_tone(void) +{ + struct mbuf *mb; + unsigned i; + int err = 0; + + mb = mbuf_alloc(NUM_SAMPLES * 2); + if (!mb) + return NULL; + + for (i=0; i<NUM_SAMPLES; i++) + err |= mbuf_write_u16(mb, i); + + mb->pos = 0; + + if (err) + return mem_deref(mb); + else + return mb; +} + + +static void sample_handler(const void *sampv, size_t sampc, void *arg) +{ + struct test *test = arg; + size_t bytec = sampc * 2; + int err = 0; + + if (!test->mb_samp) { + test->mb_samp = mbuf_alloc(bytec); + ASSERT_TRUE(test->mb_samp != NULL); + } + + /* save the samples that was played */ + err = mbuf_write_mem(test->mb_samp, (void *)sampv, bytec); + + out: + /* stop the test? */ + if (err || test->mb_samp->end >= (NUM_SAMPLES*2)) + re_cancel(); +} + + +int test_play(void) +{ + struct auplay *auplay = NULL; + struct player *player = NULL; + struct play *play = NULL; + struct mbuf *mb_tone = NULL; + struct test test = {0}; + int err; + + /* use a mock audio-driver to save the audio-samples */ + err = mock_auplay_register(&auplay, sample_handler, &test); + ASSERT_EQ(0, err); + + err = play_init(&player); + ASSERT_EQ(0, err); + + mb_tone = generate_tone(); + ASSERT_TRUE(mb_tone != NULL); + + err = play_tone(&play, player, mb_tone, 8000, 1, 0); + ASSERT_EQ(0, err); + + err = re_main_timeout(10000); + ASSERT_EQ(0, err); + + /* verify the audio-samples that was played */ + TEST_MEMCMP(mb_tone->buf, NUM_SAMPLES*2, + test.mb_samp->buf, test.mb_samp->end); + + out: + mem_deref(test.mb_samp); + mem_deref(mb_tone); + mem_deref(play); + mem_deref(player); + mem_deref(auplay); + return err; +} diff --git a/test/sip/aor.c b/test/sip/aor.c new file mode 100644 index 0000000..2d7e0d3 --- /dev/null +++ b/test/sip/aor.c @@ -0,0 +1,98 @@ +/** + * @file sip/aor.c Mock SIP server -- SIP Address of Record + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include "sipsrv.h" + + +static void destructor(void *arg) +{ + struct aor *aor = arg; + + list_flush(&aor->locl); + hash_unlink(&aor->he); + mem_deref(aor->uri); +} + + +static int uri_canon(char **curip, const struct uri *uri) +{ + if (pl_isset(&uri->user)) + return re_sdprintf(curip, "%r:%H@%r", + &uri->scheme, + uri_user_unescape, &uri->user, + &uri->host); + else + return re_sdprintf(curip, "%r:%r", + &uri->scheme, + &uri->host); +} + + +int aor_create(struct sip_server *srv, struct aor **aorp, + const struct uri *uri) +{ + struct aor *aor; + int err; + + if (!aorp || !uri) + return EINVAL; + + aor = mem_zalloc(sizeof(*aor), destructor); + if (!aor) + return ENOMEM; + + err = uri_canon(&aor->uri, uri); + if (err) + goto out; + + hash_append(srv->ht_aor, hash_joaat_str_ci(aor->uri), &aor->he, aor); + + out: + if (err) + mem_deref(aor); + else + *aorp = aor; + + return err; +} + + +int aor_find(struct sip_server *srv, struct aor **aorp, const struct uri *uri) +{ + struct list *lst; + struct aor *aor = NULL; + struct le *le; + char *curi; + int err; + + if (!uri) + return EINVAL; + + err = uri_canon(&curi, uri); + if (err) + return err; + + lst = hash_list(srv->ht_aor, hash_joaat_str_ci(curi)); + + for (le=list_head(lst); le; le=le->next) { + + aor = le->data; + + if (!str_casecmp(curi, aor->uri)) + break; + } + + mem_deref(curi); + + if (!le) + return ENOENT; + + if (aorp) + *aorp = aor; + + return 0; +} diff --git a/test/sip/auth.c b/test/sip/auth.c new file mode 100644 index 0000000..5d3e224 --- /dev/null +++ b/test/sip/auth.c @@ -0,0 +1,91 @@ +/** + * @file sip/auth.c Mock SIP server -- authentication + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <string.h> +#include <time.h> +#include <re.h> +#include "sipsrv.h" + + +enum { + NONCE_MIN_SIZE = 33, +}; + + +int auth_print(struct re_printf *pf, const struct auth *auth) +{ + uint8_t key[MD5_SIZE]; + uint64_t nv[2]; + + if (!auth) + return EINVAL; + + nv[0] = time(NULL); + nv[1] = auth->srv->secret; + + md5((uint8_t *)nv, sizeof(nv), key); + + return re_hprintf(pf, + "Digest realm=\"%s\", nonce=\"%w%llx\", " + "qop=\"auth\"%s", + auth->realm, + key, sizeof(key), nv[0], + auth->stale ? ", stale=true" : ""); +} + + +int auth_chk_nonce(struct sip_server *srv, + const struct pl *nonce, uint32_t expires) +{ + uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE]; + uint64_t nv[2]; + struct pl pl; + int64_t age; + unsigned i; + + if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE) + return EINVAL; + + pl = *nonce; + + for (i=0; i<sizeof(nkey); i++) { + nkey[i] = ch_hex(*pl.p++) << 4; + nkey[i] += ch_hex(*pl.p++); + pl.l -= 2; + } + + nv[0] = pl_x64(&pl); + nv[1] = srv->secret; + + md5((uint8_t *)nv, sizeof(nv), ckey); + + if (memcmp(nkey, ckey, MD5_SIZE)) + return EAUTH; + + age = time(NULL) - nv[0]; + + if (age < 0 || age > expires) + return ETIMEDOUT; + + return 0; +} + + +int auth_set_realm(struct auth *auth, const char *realm) +{ + size_t len; + + if (!auth || !realm) + return EINVAL; + + len = strlen(realm); + if (len >= sizeof(auth->realm)) + return ENOMEM; + + memcpy(auth->realm, realm, len); + auth->realm[len] = '\0'; + + return 0; +} diff --git a/test/sip/domain.c b/test/sip/domain.c new file mode 100644 index 0000000..063e3b9 --- /dev/null +++ b/test/sip/domain.c @@ -0,0 +1,187 @@ +/** + * @file sip/domain.c Mock SIP server -- domain handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +enum { + NONCE_EXPIRES = 300, +}; + + +static void destructor(void *arg) +{ + struct domain *dom = arg; + + hash_unlink(&dom->he); + hash_flush(dom->ht_usr); + mem_deref(dom->ht_usr); + mem_deref(dom->name); +} + + +static struct domain *lookup(struct sip_server *srv, const struct pl *name) +{ + struct list *lst; + struct le *le; + + lst = hash_list(srv->ht_dom, hash_joaat_ci(name->p, name->l)); + + for (le=list_head(lst); le; le=le->next) { + + struct domain *dom = le->data; + + if (pl_strcasecmp(name, dom->name)) + continue; + + return dom; + } + + return NULL; +} + + +int domain_add(struct sip_server *srv, const char *name) +{ + struct domain *dom; + int err; + + dom = mem_zalloc(sizeof(*dom), destructor); + if (!dom) + return ENOMEM; + + err = str_dup(&dom->name, name); + if (err) + goto out; + + err = hash_alloc(&dom->ht_usr, 32); + if (err) + return err; + + hash_append(srv->ht_dom, hash_joaat_str_ci(name), &dom->he, dom); + + out: + if (err) + mem_deref(dom); + + return err; +} + + +int domain_find(struct sip_server *srv, const struct uri *uri) +{ + int err = ENOENT; + struct sa addr; + + if (!uri) + return EINVAL; + + if (!sa_set(&addr, &uri->host, uri->port)) { + + if (!uri->port) { + + uint16_t port = SIP_PORT; + + if (!pl_strcasecmp(&uri->scheme, "sips")) + port = SIP_PORT_TLS; + + sa_set_port(&addr, port); + } + + if (sip_transp_isladdr(srv->sip, SIP_TRANSP_NONE, &addr)) + return 0; + + return ENOENT; + } + + err = lookup(srv, &uri->host) ? 0 : ENOENT; + + return err; +} + + +int domain_auth(struct sip_server *srv, + const struct uri *uri, bool user_match, + const struct sip_msg *msg, enum sip_hdrid hdrid, + struct auth *auth) +{ + struct domain *dom; + struct list *lst; + struct le *le; + int err = ENOENT; + + if (!uri || !msg || !auth) + return EINVAL; + + dom = lookup(srv, &uri->host); + if (!dom) { + DEBUG_WARNING("domain not found (%r)\n", &uri->host); + return ENOENT; + } + + err = auth_set_realm(auth, dom->name); + if (err) + return err; + + auth->stale = false; + + lst = hash_list(msg->hdrht, hdrid); + + for (le=list_head(lst); le; le=le->next) { + + const struct sip_hdr *hdr = le->data; + struct httpauth_digest_resp resp; + const struct user *usr; + + if (hdr->id != hdrid) + continue; + + if (httpauth_digest_response_decode(&resp, &hdr->val)) + continue; + + if (pl_strcasecmp(&resp.realm, dom->name)) + continue; + + if (auth_chk_nonce(srv, &resp.nonce, NONCE_EXPIRES)) { + auth->stale = true; + continue; + } + + auth->stale = false; + + usr = user_find(dom->ht_usr, &resp.username); + if (!usr) { + DEBUG_WARNING("user not found (%r)\n", &resp.username); + break; + } + + err = httpauth_digest_response_auth(&resp, &msg->met,usr->ha1); + if (err) + return err; + + if (user_match && pl_cmp(&resp.username, &uri->user)) + return EPERM; + + return 0; + } + + return EAUTH; +} + + +struct domain *domain_lookup(struct sip_server *srv, const char *name) +{ + struct pl pl; + + pl_set_str(&pl, name); + + return lookup(srv, &pl); +} diff --git a/test/sip/location.c b/test/sip/location.c new file mode 100644 index 0000000..be2bd66 --- /dev/null +++ b/test/sip/location.c @@ -0,0 +1,188 @@ +/** + * @file sip/location.c Mock SIP server -- location handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <time.h> +#include <re.h> +#include "sipsrv.h" + + +struct loctmp { + struct sa src; + struct uri duri; + char *uri; + char *callid; + uint32_t expires; + uint32_t cseq; + double q; +}; + + +static void destructor_loctmp(void *arg) +{ + struct loctmp *tmp = arg; + + mem_deref(tmp->uri); + mem_deref(tmp->callid); +} + + +static void destructor_location(void *arg) +{ + struct location *loc = arg; + + list_unlink(&loc->le); + mem_deref(loc->uri); + mem_deref(loc->callid); + mem_deref(loc->tmp); +} + + +static bool cmp_handler(struct le *le, void *arg) +{ + struct location *loc = le->data; + + return uri_cmp(&loc->duri, arg); +} + + +int location_update(struct list *locl, const struct sip_msg *msg, + const struct sip_addr *contact, uint32_t expires) +{ + struct location *loc, *loc_new = NULL; + struct loctmp *tmp; + struct pl pl; + int err; + + if (!locl || !msg || !contact) + return EINVAL; + + loc = list_ledata(list_apply(locl, true, cmp_handler, + (void *)&contact->uri)); + if (!loc) { + if (expires == 0) + return 0; + + loc = loc_new = mem_zalloc(sizeof(*loc), destructor_location); + if (!loc) + return ENOMEM; + + list_append(locl, &loc->le, loc); + } + else { + if (!pl_strcmp(&msg->callid, loc->callid) && + msg->cseq.num <= loc->cseq) + return EPROTO; + + if (expires == 0) { + loc->rm = true; + return 0; + } + } + + tmp = mem_zalloc(sizeof(*tmp), destructor_loctmp); + if (!tmp) { + err = ENOMEM; + goto out; + } + + err = pl_strdup(&tmp->uri, &contact->auri); + if (err) + goto out; + + pl_set_str(&pl, tmp->uri); + + if (uri_decode(&tmp->duri, &pl)) { + err = EBADMSG; + goto out; + } + + err = pl_strdup(&tmp->callid, &msg->callid); + if (err) + goto out; + + + if (!msg_param_decode(&contact->params, "q", &pl)) + tmp->q = pl_float(&pl); + else + tmp->q = 1; + + tmp->cseq = msg->cseq.num; + tmp->expires = expires; + tmp->src = msg->src; + + out: + if (err) { + mem_deref(loc_new); + mem_deref(tmp); + } + else { + mem_deref(loc->tmp); + loc->tmp = tmp; + } + + return err; +} + + +void location_commit(struct list *locl) +{ + time_t now = time(NULL); + struct le *le; + + if (!locl) + return; + + for (le=locl->head; le; ) { + + struct location *loc = le->data; + + le = le->next; + + if (loc->rm) { + list_unlink(&loc->le); + mem_deref(loc); + } + else if (loc->tmp) { + + mem_deref(loc->uri); + mem_deref(loc->callid); + + loc->uri = mem_ref(loc->tmp->uri); + loc->callid = mem_ref(loc->tmp->callid); + loc->duri = loc->tmp->duri; + loc->cseq = loc->tmp->cseq; + loc->expires = loc->tmp->expires + now; + loc->src = loc->tmp->src; + loc->q = loc->tmp->q; + + loc->tmp = mem_deref(loc->tmp); + } + } +} + + +void location_rollback(struct list *locl) +{ + struct le *le; + + if (!locl) + return; + + for (le=locl->head; le; ) { + + struct location *loc = le->data; + + le = le->next; + + if (!loc->uri) { + list_unlink(&loc->le); + mem_deref(loc); + } + else { + loc->tmp = mem_deref(loc->tmp); + loc->rm = false; + } + } +} diff --git a/test/sip/sipsrv.c b/test/sip/sipsrv.c new file mode 100644 index 0000000..374ea3b --- /dev/null +++ b/test/sip/sipsrv.c @@ -0,0 +1,330 @@ +/** + * @file sip/sipsrv.c Mock SIP server + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <string.h> +#include <time.h> +#include <re.h> +#include <baresip.h> +#include "../test.h" +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +#define LOCAL_PORT 0 +#define LOCAL_SECURE_PORT 0 +#define EXPIRES_MIN 60 +#define EXPIRES_MAX 3600 + + +static int print_contact(struct re_printf *pf, const struct aor *aor) +{ + const uint64_t now = (uint64_t)time(NULL); + struct le *le; + int err = 0; + + for (le=aor->locl.head; le; le=le->next) { + + const struct location *loc = le->data; + + if (loc->expires < now) + continue; + + err |= re_hprintf(pf, "Contact: <%s>;expires=%lli\r\n", + loc->uri, loc->expires - now); + } + + return err; +} + + +static bool handle_register(struct sip_server *srv, const struct sip_msg *msg) +{ + struct auth auth = { srv, "", false }; + struct sip *sip = srv->sip; + struct list *lst; + struct aor *aor; + struct le *le; + int err; + + /* Request URI */ + err = domain_find(srv, &msg->uri); + if (err) { + if (err == ENOENT) { + warning("domain not found\n"); + return false; + } + + sip_reply(sip, msg, 500, strerror(err)); + warning("domain find error: %s\n", strerror(err)); + return true; + } + + /* Authorize */ + if (srv->auth_enabled) + err = domain_auth(srv, &msg->to.uri, true, + msg, SIP_HDR_AUTHORIZATION, &auth); + else + err = domain_find(srv, &msg->to.uri); + + if (err && err != EAUTH) { + DEBUG_NOTICE("domain auth/find error: %m\n", err); + } + + switch (err) { + + case 0: + break; + + case EAUTH: + sip_replyf(sip, msg, 401, "Unauthorized", + "WWW-Authenticate: %H\r\n" + "Content-Length: 0\r\n\r\n", + auth_print, &auth); + return true; + + case EPERM: + sip_reply(sip, msg, 403, "Forbidden"); + return true; + + case ENOENT: + sip_reply(sip, msg, 404, "Not Found"); + return true; + + default: + sip_reply(sip, msg, 500, strerror(err)); + warning("domain error: %s\n", strerror(err)); + return true; + } + + /* Find AoR */ + err = aor_find(srv, &aor, &msg->to.uri); + if (err) { + if (err != ENOENT) { + sip_reply(sip, msg, 500, strerror(err)); + warning("aor find error: %s\n", strerror(err)); + return true; + } + + err = aor_create(srv, &aor, &msg->to.uri); + if (err) { + sip_reply(sip, msg, 500, strerror(err)); + warning("aor create error: %s\n", strerror(err)); + return true; + } + } + + /* Process Contacts */ + lst = hash_list(msg->hdrht, SIP_HDR_CONTACT); + + for (le=list_head(lst); le; le=le->next) { + + const struct sip_hdr *hdr = le->data; + struct sip_addr contact; + uint32_t expires; + struct pl pl; + + if (hdr->id != SIP_HDR_CONTACT) + continue; + + err = sip_addr_decode(&contact, &hdr->val); + if (err) { + sip_reply(sip, msg, 400, "Bad Contact"); + goto fail; + } + + if (!msg_param_decode(&contact.params, "expires", &pl)) + expires = pl_u32(&pl); + else if (pl_isset(&msg->expires)) + expires = pl_u32(&msg->expires); + else + expires = 3600; + + if (expires > 0 && expires < EXPIRES_MIN) { + sip_replyf(sip, msg, 423, "Interval Too Brief", + "Min-Expires: %u\r\n" + "Content-Length: 0\r\n\r\n", + EXPIRES_MIN); + goto fail; + } + + expires = min(expires, EXPIRES_MAX); + + err = location_update(&aor->locl, msg, &contact, expires); + if (err) { + sip_reply(sip, msg, 500, strerror(err)); + if (err != EPROTO) + warning("location update error: %s\n", + strerror(err)); + goto fail; + } + } + + location_commit(&aor->locl); + + sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK", + "%H" + "Date: %H\r\n" + "Content-Length: 0\r\n\r\n", + print_contact, aor, + fmt_gmtime, NULL); + + return true; + + fail: + location_rollback(&aor->locl); + + return true; +} + + +static bool sip_msg_handler(const struct sip_msg *msg, void *arg) +{ + struct sip_server *srv = arg; + int err = 0; + +#if 0 + DEBUG_NOTICE("[%u] recv %r via %s\n", srv->instance, + &msg->met, sip_transp_name(msg->tp)); +#endif + + srv->tp_last = msg->tp; + + if (0 == pl_strcmp(&msg->met, "REGISTER")) { + ++srv->n_register_req; + if (handle_register(srv, msg)) + goto out; + + sip_reply(srv->sip, msg, 503, "Server Error"); + } + else { + DEBUG_NOTICE("method not handled (%r)\n", &msg->met); + return false; + } + + if (srv->terminate) + err = sip_reply(srv->sip, msg, 503, "Server Error"); + + if (err) { + DEBUG_WARNING("could not reply: %m\n", err); + } + + out: + if (srv->terminate) + re_cancel(); /* XXX: avoid this */ + + return true; +} + + +static void destructor(void *arg) +{ + struct sip_server *srv = arg; + + srv->terminate = true; + + sip_close(srv->sip, false); + mem_deref(srv->sip); + + hash_flush(srv->ht_aor); + mem_deref(srv->ht_aor); + + hash_flush(srv->ht_dom); + mem_deref(srv->ht_dom); +} + + +int sip_server_alloc(struct sip_server **srvp) +{ + struct sip_server *srv; + struct sa laddr, laddrs; + struct tls *tls = NULL; + int err; + + if (!srvp) + return EINVAL; + + srv = mem_zalloc(sizeof *srv, destructor); + if (!srv) + return ENOMEM; + + err = sa_set_str(&laddr, "127.0.0.1", LOCAL_PORT); + err |= sa_set_str(&laddrs, "127.0.0.1", LOCAL_SECURE_PORT); + if (err) + goto out; + + err = sip_alloc(&srv->sip, NULL, 16, 16, 16, + "mock SIP server", NULL, NULL); + if (err) + goto out; + + err |= sip_transp_add(srv->sip, SIP_TRANSP_UDP, &laddr); + err |= sip_transp_add(srv->sip, SIP_TRANSP_TCP, &laddr); + if (err) + goto out; + +#ifdef USE_TLS + err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); + if (err) + goto out; + + err = tls_set_certificate(tls, test_certificate, + strlen(test_certificate)); + if (err) + goto out; + + err |= sip_transp_add(srv->sip, SIP_TRANSP_TLS, &laddrs, tls); +#endif + if (err) + goto out; + + err = sip_listen(&srv->lsnr, srv->sip, true, sip_msg_handler, srv); + if (err) + goto out; + + srv->secret = rand_u64(); + + err = hash_alloc(&srv->ht_dom, 32); + if (err) + goto out; + + err = hash_alloc(&srv->ht_aor, 32); + if (err) + goto out; + + out: + mem_deref(tls); + if (err) + mem_deref(srv); + else + *srvp = srv; + + return err; +} + + +int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, + enum sip_transp tp) +{ + struct sa laddr; + int err; + + if (!srv || !uri || !sz) + return EINVAL; + + err = sip_transp_laddr(srv->sip, &laddr, tp, NULL); + if (err) + return err; + + /* NOTE: angel brackets needed to parse ;transport parameter */ + if (re_snprintf(uri, sz, "<sip:x:x@%J%s>", + &laddr, sip_transp_param(tp)) < 0) + return ENOMEM; + + return 0; +} diff --git a/test/sip/sipsrv.h b/test/sip/sipsrv.h new file mode 100644 index 0000000..a4b8bbf --- /dev/null +++ b/test/sip/sipsrv.h @@ -0,0 +1,122 @@ +/** + * @file sip/sipsrv.h Mock SIP server -- interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + + +struct auth; + + +/* + * SIP Server + */ + +struct sip_server { + struct sip *sip; + struct sip_lsnr *lsnr; + bool auth_enabled; + bool terminate; + unsigned instance; + + unsigned n_register_req; + enum sip_transp tp_last; + + uint64_t secret; + struct hash *ht_dom; + struct hash *ht_aor; +}; + +int sip_server_alloc(struct sip_server **srvp); +int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, + enum sip_transp tp); + + +/* + * AoR + */ + +struct aor { + struct le he; + struct list locl; + char *uri; +}; + +int aor_create(struct sip_server *srv, struct aor **aorp, + const struct uri *uri); +int aor_find(struct sip_server *srv, struct aor **aorp, + const struct uri *uri); + + +/* + * Auth + */ + +struct auth { + const struct sip_server *srv; + char realm[256]; + bool stale; +}; + +int auth_print(struct re_printf *pf, const struct auth *auth); +int auth_chk_nonce(struct sip_server *srv, + const struct pl *nonce, uint32_t expires); +int auth_set_realm(struct auth *auth, const char *realm); + + +/* + * Domain + */ + +struct domain { + struct le he; + struct hash *ht_usr; + char *name; +}; + + +int domain_add(struct sip_server *srv, const char *name); +int domain_find(struct sip_server *srv, const struct uri *uri); +int domain_auth(struct sip_server *srv, + const struct uri *uri, bool user_match, + const struct sip_msg *msg, enum sip_hdrid hdrid, + struct auth *auth); +struct domain *domain_lookup(struct sip_server *srv, const char *name); + + +/* + * Location + */ + +struct location { + struct le le; + struct sa src; + struct uri duri; + char *uri; + char *callid; + struct loctmp *tmp; + uint64_t expires; + uint32_t cseq; + double q; + bool rm; +}; + +int location_update(struct list *locl, const struct sip_msg *msg, + const struct sip_addr *contact, uint32_t expires); +void location_commit(struct list *locl); +void location_rollback(struct list *locl); + + +/* + * User + */ + +struct user { + struct le he; + uint8_t ha1[MD5_SIZE]; + char *name; +}; + +int user_add(struct hash *ht, const char *username, const char *password, + const char *realm); +struct user *user_find(struct hash *ht, const struct pl *name); diff --git a/test/sip/user.c b/test/sip/user.c new file mode 100644 index 0000000..99fb47d --- /dev/null +++ b/test/sip/user.c @@ -0,0 +1,73 @@ +/** + * @file sip/user.c Mock SIP server -- user handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include <re.h> +#include "sipsrv.h" + + +static void destructor(void *arg) +{ + struct user *usr = arg; + + hash_unlink(&usr->he); + mem_deref(usr->name); +} + + +int user_add(struct hash *ht, const char *username, const char *password, + const char *realm) +{ + struct user *usr; + int err; + + usr = mem_zalloc(sizeof(*usr), destructor); + if (!usr) { + err = ENOMEM; + goto out; + } + + err = str_dup(&usr->name, username); + if (err) { + goto out; + } + + err = md5_printf(usr->ha1, "%s:%s:%s", username, realm, password); + if (err) { + goto out; + } + + hash_append(ht, hash_joaat_str(username), &usr->he, usr); + + out: + if (err) { + mem_deref(usr); + } + + return err; +} + + +struct user *user_find(struct hash *ht, const struct pl *name) +{ + struct list *lst; + struct le *le; + + if (!ht || !name) + return NULL; + + lst = hash_list(ht, hash_joaat((uint8_t *)name->p, name->l)); + + for (le=list_head(lst); le; le=le->next) { + + struct user *usr = le->data; + + if (pl_strcmp(name, usr->name)) + continue; + + return usr; + } + + return NULL; +} diff --git a/test/srcs.mk b/test/srcs.mk new file mode 100644 index 0000000..0bcb6de --- /dev/null +++ b/test/srcs.mk @@ -0,0 +1,54 @@ +# +# srcs.mk All application source files. +# +# Copyright (C) 2010 Creytiv.com +# + + +# +# Test-cases: +# +TEST_SRCS += account.c +TEST_SRCS += aulevel.c +TEST_SRCS += call.c +TEST_SRCS += cmd.c +TEST_SRCS += contact.c +TEST_SRCS += cplusplus.c +TEST_SRCS += message.c +TEST_SRCS += mos.c +TEST_SRCS += net.c +TEST_SRCS += play.c +TEST_SRCS += ua.c +ifneq ($(USE_VIDEO),) +TEST_SRCS += video.c +endif + + +# +# Mocks +# +TEST_SRCS += mock/dnssrv.c + +TEST_SRCS += sip/aor.c +TEST_SRCS += sip/auth.c +TEST_SRCS += sip/domain.c +TEST_SRCS += sip/location.c +TEST_SRCS += sip/sipsrv.c +TEST_SRCS += sip/user.c + +ifneq ($(USE_TLS),) +TEST_SRCS += mock/cert.c +endif + +TEST_SRCS += mock/mock_aucodec.c +TEST_SRCS += mock/mock_auplay.c +TEST_SRCS += mock/mock_ausrc.c +ifneq ($(USE_VIDEO),) +TEST_SRCS += mock/mock_vidsrc.c +TEST_SRCS += mock/mock_vidcodec.c +TEST_SRCS += mock/mock_vidisp.c +endif + +TEST_SRCS += test.c + +TEST_SRCS += main.c diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..d1fa3ad --- /dev/null +++ b/test/test.c @@ -0,0 +1,109 @@ +#include <math.h> +#include <re.h> +#include <baresip.h> +#include "test.h" + + +static void timeout_handler(void *arg) +{ + int *err = arg; + + warning("selftest: re_main() loop timed out -- test hung..\n"); + + *err = ETIMEDOUT; + + re_cancel(); +} + + +static void signal_handler(int sig) +{ + re_fprintf(stderr, "test interrupted by signal %d\n", sig); + re_cancel(); +} + + +int re_main_timeout(uint32_t timeout_ms) +{ + struct tmr tmr; + int err = 0; + + tmr_init(&tmr); + + tmr_start(&tmr, timeout_ms, timeout_handler, &err); + re_main(signal_handler); + + tmr_cancel(&tmr); + return err; +} + + +bool test_cmp_double(double a, double b, double precision) +{ + return fabs(a - b) < precision; +} + + +void test_hexdump_dual(FILE *f, + const void *ep, size_t elen, + const void *ap, size_t alen) +{ + const uint8_t *ebuf = ep; + const uint8_t *abuf = ap; + size_t i, j, len; +#define WIDTH 8 + + if (!f || !ep || !ap) + return; + + len = max(elen, alen); + + (void)re_fprintf(f, "\nOffset: Expected (%zu bytes): " + " Actual (%zu bytes):\n", elen, alen); + + for (i=0; i < len; i += WIDTH) { + + (void)re_fprintf(f, "0x%04zx ", i); + + for (j=0; j<WIDTH; j++) { + const size_t pos = i+j; + if (pos < elen) { + bool wrong = pos >= alen; + + if (wrong) + (void)re_fprintf(f, "\x1b[35m"); + (void)re_fprintf(f, " %02x", ebuf[pos]); + if (wrong) + (void)re_fprintf(f, "\x1b[;m"); + } + else + (void)re_fprintf(f, " "); + } + + (void)re_fprintf(f, " "); + + for (j=0; j<WIDTH; j++) { + const size_t pos = i+j; + if (pos < alen) { + bool wrong; + + if (pos < elen) + wrong = ebuf[pos] != abuf[pos]; + else + wrong = true; + + if (wrong) + (void)re_fprintf(f, "\x1b[33m"); + (void)re_fprintf(f, " %02x", abuf[pos]); + if (wrong) + (void)re_fprintf(f, "\x1b[;m"); + } + else + (void)re_fprintf(f, " "); + } + + (void)re_fprintf(f, "\n"); + } + + (void)re_fprintf(f, "\n"); +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..f276a8d --- /dev/null +++ b/test/test.h @@ -0,0 +1,224 @@ +/** + * @file test.h Selftest for Baresip core -- internal API + * + * Copyright (C) 2010 Creytiv.com + */ + + +#define ASSERT_TRUE(cond) \ + if (!(cond)) { \ + warning("selftest: ASSERT_TRUE: %s:%u:\n", \ + __FILE__, __LINE__); \ + err = EINVAL; \ + goto out; \ + } + +#define ASSERT_EQ(expected, actual) \ + if ((expected) != (actual)) { \ + warning("selftest: ASSERT_EQ: %s:%u:" \ + " expected=%d, actual=%d\n", \ + __FILE__, __LINE__, \ + (int)(expected), (int)(actual)); \ + err = EINVAL; \ + goto out; \ + } + +#define ASSERT_DOUBLE_EQ(expected, actual, prec) \ + if (!test_cmp_double((expected), (actual), (prec))) { \ + warning("selftest: ASSERT_DOUBLE_EQ: %s:%u:" \ + " expected=%f, actual=%f\n", \ + __FILE__, __LINE__, \ + (double)(expected), (double)(actual)); \ + err = EINVAL; \ + goto out; \ + } + +#define ASSERT_STREQ(expected, actual) \ + if (0 != str_cmp((expected), (actual))) { \ + warning("selftest: ASSERT_STREQ: %s:%u:" \ + " expected = '%s', actual = '%s'\n", \ + __FILE__, __LINE__, \ + (expected), (actual)); \ + err = EBADMSG; \ + goto out; \ + } + +#define TEST_ERR(err) \ + if ((err)) { \ + (void)re_fprintf(stderr, "\n"); \ + warning("TEST_ERR: %s:%u:" \ + " (%m)\n", \ + __FILE__, __LINE__, \ + (err)); \ + goto out; \ + } + +#define TEST_MEMCMP(expected, expn, actual, actn) \ + if (expn != actn || \ + 0 != memcmp((expected), (actual), (expn))) { \ + (void)re_fprintf(stderr, "\n"); \ + warning("TEST_MEMCMP: %s:%u:" \ + " %s(): failed\n", \ + __FILE__, __LINE__, __func__); \ + test_hexdump_dual(stderr, \ + expected, expn, \ + actual, actn); \ + err = EINVAL; \ + goto out; \ + } + +#define TEST_STRCMP(expected, expn, actual, actn) \ + if (expn != actn || \ + 0 != memcmp((expected), (actual), (expn))) { \ + (void)re_fprintf(stderr, "\n"); \ + warning("TEST_STRCMP: %s:%u:" \ + " failed\n", \ + __FILE__, __LINE__); \ + (void)re_fprintf(stderr, \ + "expected string: (%zu bytes)\n" \ + "\"%b\"\n", \ + (size_t)(expn), \ + (expected), (size_t)(expn)); \ + (void)re_fprintf(stderr, \ + "actual string: (%zu bytes)\n" \ + "\"%b\"\n", \ + (size_t)(actn), \ + (actual), (size_t)(actn)); \ + err = EINVAL; \ + goto out; \ + } + + +/* helpers */ + +int re_main_timeout(uint32_t timeout_ms); +bool test_cmp_double(double a, double b, double precision); +void test_hexdump_dual(FILE *f, + const void *ep, size_t elen, + const void *ap, size_t alen); + + +#ifdef USE_TLS +extern const char test_certificate[]; +#endif + + +/* + * Mock DNS-Server + */ + +struct dns_server { + struct udp_sock *us; + struct sa addr; + struct list rrl; + bool rotate; +}; + +int dns_server_alloc(struct dns_server **srvp, bool rotate); +int dns_server_add_a(struct dns_server *srv, + const char *name, uint32_t addr); +int dns_server_add_srv(struct dns_server *srv, const char *name, + uint16_t pri, uint16_t weight, uint16_t port, + const char *target); + +/* + * Mock Audio-codec + */ + +void mock_aucodec_register(void); +void mock_aucodec_unregister(void); + +/* + * Mock Audio-source + */ + +struct ausrc; + +int mock_ausrc_register(struct ausrc **ausrcp); + + +/* + * Mock Audio-player + */ + +struct auplay; + +typedef void (mock_sample_h)(const void *sampv, size_t sampc, void *arg); + +int mock_auplay_register(struct auplay **auplayp, + mock_sample_h *sampleh, void *arg); + + +/* + * Mock Video-source + */ + +struct vidsrc; + +int mock_vidsrc_register(struct vidsrc **vidsrcp); + + +/* + * Mock Video-codec + */ + +void mock_vidcodec_register(void); +void mock_vidcodec_unregister(void); + + +/* + * Mock Video-display + */ + +struct vidisp; + +int mock_vidisp_register(struct vidisp **vidispp); + + +/* test cases */ + +int test_account(void); +int test_aulevel(void); +int test_cmd(void); +int test_cmd_long(void); +int test_contact(void); +int test_ua_alloc(void); +int test_uag_find_param(void); +int test_ua_register(void); +int test_ua_register_dns(void); +int test_ua_register_auth(void); +int test_ua_register_auth_dns(void); +int test_ua_options(void); +int test_message(void); +int test_mos(void); +int test_network(void); +int test_play(void); + +int test_call_answer(void); +int test_call_reject(void); +int test_call_af_mismatch(void); +int test_call_answer_hangup_a(void); +int test_call_answer_hangup_b(void); +int test_call_rtp_timeout(void); +int test_call_multiple(void); +int test_call_max(void); +int test_call_dtmf(void); +int test_call_video(void); +int test_call_aulevel(void); +int test_call_progress(void); +int test_call_format_float(void); + +#ifdef USE_VIDEO +int test_video(void); +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +int test_cplusplus(void); + +#ifdef __cplusplus +} +#endif diff --git a/test/ua.c b/test/ua.c new file mode 100644 index 0000000..4f04e4f --- /dev/null +++ b/test/ua.c @@ -0,0 +1,714 @@ +/** + * @file test/ua.c Baresip selftest -- User-Agent (UA) + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <baresip.h> +#include "test.h" +#include "sip/sipsrv.h" + + +#define MAGIC 0x9044bbfc + + +struct test { + struct sip_server *srvv[16]; + size_t srvc; + struct ua *ua; + int err; + unsigned got_register_ok; + unsigned n_resp; + uint32_t magic; +}; + + +static void test_init(struct test *t) +{ + memset(t, 0, sizeof(*t)); + t->magic = MAGIC; +} + + +static void test_reset(struct test *t) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(t->srvv); i++) + mem_deref(t->srvv[i]); + mem_deref(t->ua); + + memset(t, 0, sizeof(*t)); +} + + +static void test_abort(struct test *t, int err) +{ + t->err = err; + re_cancel(); +} + + +static void ua_event_handler(struct ua *ua, enum ua_event ev, + struct call *call, const char *prm, void *arg) +{ + struct test *t = arg; + size_t i; + int err = 0; + (void)call; + (void)prm; + + ASSERT_TRUE(t != NULL); + + if (ua != t->ua) + return; + + if (ev == UA_EVENT_REGISTER_OK) { + + info("event: Register OK!\n"); + + ++t->got_register_ok; + + /* verify register success */ + ASSERT_TRUE(ua_isregistered(t->ua)); + + /* Terminate SIP Server, then De-REGISTER */ + for (i=0; i<t->srvc; i++) + t->srvv[i]->terminate = true; + + t->ua = mem_deref(t->ua); + } + else if (ev == UA_EVENT_REGISTER_FAIL) { + + err = EAUTH; + re_cancel(); + } + + out: + if (err) { + warning("selftest: event handler error: %m\n", err); + t->err = err; + } +} + + +static int reg(enum sip_transp tp) +{ + struct test t; + char aor[256]; + int err; + + memset(&t, 0, sizeof t); + + err = sip_server_alloc(&t.srvv[0]); + if (err) { + warning("failed to create sip server (%d/%m)\n", err, err); + goto out; + } + + err = sip_server_uri(t.srvv[0], aor, sizeof(aor), tp); + TEST_ERR(err); + + err = ua_alloc(&t.ua, aor); + TEST_ERR(err); + + err = uag_event_register(ua_event_handler, &t); + if (err) + goto out; + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + if (err) + goto out; + + if (t.err) + err = t.err; + + ASSERT_TRUE(t.srvv[0]->n_register_req > 0); + ASSERT_EQ(tp, t.srvv[0]->tp_last); + ASSERT_TRUE(t.got_register_ok > 0); + + out: + if (err) { + warning("selftest: ua_register test failed (%m)\n", err); + } + uag_event_unregister(ua_event_handler); + test_reset(&t); + + return err; +} + + +int test_ua_register(void) +{ + int err = 0; + + err = ua_init("test", true, true, true, false); + TEST_ERR(err); + + err |= reg(SIP_TRANSP_UDP); + err |= reg(SIP_TRANSP_TCP); +#ifdef USE_TLS + err |= reg(SIP_TRANSP_TLS); +#endif + + ua_close(); + + out: + return err; +} + + +int test_ua_alloc(void) +{ + struct ua *ua; + uint32_t n_uas = list_count(uag_list()); + int err = 0; + + /* make sure we dont have that UA already */ + ASSERT_TRUE(NULL == uag_find_aor("sip:user@127.0.0.1")); + + err = ua_alloc(&ua, "Foo <sip:user@127.0.0.1>;regint=0"); + if (err) + return err; + + /* verify this UA-instance */ + ASSERT_TRUE(!ua_isregistered(ua)); + ASSERT_STREQ("sip:user@127.0.0.1", ua_aor(ua)); + ASSERT_TRUE(NULL == ua_call(ua)); + + /* verify global UA keeper */ + ASSERT_EQ((n_uas + 1), list_count(uag_list())); + ASSERT_TRUE(ua == uag_find_aor("sip:user@127.0.0.1")); + + mem_deref(ua); + + ASSERT_EQ((n_uas), list_count(uag_list())); + + out: + return err; +} + + +int test_uag_find_param(void) +{ + struct ua *ua1 = NULL, *ua2 = NULL; + int err = 0; + + ASSERT_TRUE(NULL == uag_find_param("not", "found")); + + err = ua_alloc(&ua1, "<sip:x@127.0.0.1>;regint=0;abc"); + err |= ua_alloc(&ua2, "<sip:x@127.0.0.1>;regint=0;def=123"); + if (err) + goto out; + + ASSERT_TRUE(ua1 == uag_find_param("abc", NULL)); + ASSERT_TRUE(NULL == uag_find_param("abc", "123")); + ASSERT_TRUE(ua2 == uag_find_param("def", NULL)); + ASSERT_TRUE(ua2 == uag_find_param("def", "123")); + + ASSERT_TRUE(NULL == uag_find_param("not", "found")); + + out: + mem_deref(ua2); + mem_deref(ua1); + + return err; +} + + +static const char *_sip_transp_srvid(enum sip_transp tp) +{ + switch (tp) { + + case SIP_TRANSP_UDP: return "_sip._udp"; + case SIP_TRANSP_TCP: return "_sip._tcp"; + case SIP_TRANSP_TLS: return "_sips._tcp"; + default: return "???"; + } +} + + +static int reg_dns(enum sip_transp tp) +{ + struct dns_server *dnssrv = NULL; + struct test t; + const char *domain = "test.invalid"; + struct network *net = baresip_network(); + unsigned server_count = 1; + char aor[256]; + char srv[256]; + size_t i; + int err; + + memset(&t, 0, sizeof t); + + /* + * Setup server-side mocks: + */ + + err = dns_server_alloc(&dnssrv, true); + TEST_ERR(err); + + info("| DNS-server on %J\n", &dnssrv->addr); + + /* NOTE: must be done before ua_init() */ + err = net_use_nameserver(net, &dnssrv->addr); + TEST_ERR(err); + + for (i=0; i<server_count; i++) { + struct sa sip_addr; + char arec[256]; + + err = sip_server_alloc(&t.srvv[i]); + if (err) { + warning("failed to create sip server (%d/%m)\n", + err, err); + goto out; + } + + err = domain_add(t.srvv[0], domain); + if (err) + goto out; + + err = sip_transp_laddr(t.srvv[i]->sip, &sip_addr, tp, NULL); + TEST_ERR(err); + + info("| SIP-server on %J\n", &sip_addr); + + re_snprintf(arec, sizeof(arec), + "alpha%u.%s", i+1, domain); + + re_snprintf(srv, sizeof(srv), + "%s.%s", _sip_transp_srvid(tp), domain); + err = dns_server_add_srv(dnssrv, srv, + 20, 0, sa_port(&sip_addr), + arec); + TEST_ERR(err); + + err = dns_server_add_a(dnssrv, arec, sa_in(&sip_addr)); + TEST_ERR(err); + } + t.srvc = server_count; + + /* NOTE: angel brackets needed to parse ;transport parameter */ + if (re_snprintf(aor, sizeof(aor), "<sip:x:x@%s;transport=%s>", + domain, sip_transp_name(tp)) < 0) + return ENOMEM; + + /* + * Start SIP client: + */ + + err = ua_init("test", true, true, true, false); + TEST_ERR(err); + + err = ua_alloc(&t.ua, aor); + TEST_ERR(err); + + err = uag_event_register(ua_event_handler, &t); + if (err) + goto out; + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + if (err) + goto out; + + if (t.err) + err = t.err; + + /* verify that all SIP requests was sent to the first + * SIP-server. + */ + ASSERT_TRUE(t.srvv[0]->n_register_req > 0); + ASSERT_EQ(tp, t.srvv[0]->tp_last); + ASSERT_TRUE(t.got_register_ok > 0); + + out: + if (err) { + warning("selftest: ua_register test failed (%m)\n", err); + } + uag_event_unregister(ua_event_handler); + + test_reset(&t); + + ua_stop_all(true); + ua_close(); + + mem_deref(dnssrv); + + return err; +} + + +int test_ua_register_dns(void) +{ + int err = 0; + + err |= reg_dns(SIP_TRANSP_UDP); + TEST_ERR(err); + err |= reg_dns(SIP_TRANSP_TCP); + TEST_ERR(err); +#ifdef USE_TLS + err |= reg_dns(SIP_TRANSP_TLS); + TEST_ERR(err); +#endif + + out: + return err; +} + + +#define USER "alfredh" +#define PASS "pass%40word" /* NOTE: url-encoded */ +#define DOMAIN "localhost" + +static int reg_auth(enum sip_transp tp) +{ + struct sa laddr; + struct test t; + char aor[256]; + int err; + + memset(&t, 0, sizeof t); + + err = sip_server_alloc(&t.srvv[0]); + if (err) { + warning("failed to create sip server (%d/%m)\n", err, err); + goto out; + } + + err = domain_add(t.srvv[0], DOMAIN); + TEST_ERR(err); + + err = user_add(domain_lookup(t.srvv[0], DOMAIN)->ht_usr, + "alfredh", "pass@word", DOMAIN); + TEST_ERR(err); + + t.srvv[0]->auth_enabled = true; + + err = sip_transp_laddr(t.srvv[0]->sip, &laddr, tp, NULL); + if (err) + return err; + + /* NOTE: angel brackets needed to parse ;transport parameter */ + if (re_snprintf(aor, sizeof(aor), + "<sip:%s:%s@%s>;outbound=\"sip:%J;transport=%s\"", + USER, + PASS, + DOMAIN, + &laddr, + sip_transp_name(tp)) < 0) + return ENOMEM; + + err = ua_alloc(&t.ua, aor); + TEST_ERR(err); + + err = uag_event_register(ua_event_handler, &t); + if (err) + goto out; + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + if (err) + goto out; + + if (t.err) { + err = t.err; + goto out; + } + + ASSERT_TRUE(t.srvv[0]->n_register_req > 0); + ASSERT_EQ(tp, t.srvv[0]->tp_last); + ASSERT_TRUE(t.got_register_ok > 0); + + out: + if (err) { + warning("selftest: ua_register test failed (%m)\n", err); + } + uag_event_unregister(ua_event_handler); + test_reset(&t); + + + return err; +} + + +int test_ua_register_auth(void) +{ + int err; + + err = ua_init("test", true, true, true, false); + TEST_ERR(err); + + err |= reg_auth(SIP_TRANSP_UDP); + TEST_ERR(err); + err |= reg_auth(SIP_TRANSP_TCP); + TEST_ERR(err); +#ifdef USE_TLS + err |= reg_auth(SIP_TRANSP_TLS); + TEST_ERR(err); +#endif + + out: + ua_stop_all(true); + ua_close(); + + return err; +} + + +static int reg_auth_dns(enum sip_transp tp) +{ + struct network *net = baresip_network(); + struct dns_server *dnssrv = NULL; + struct test t; + const char *username = "alfredh"; + const char *password = "password"; + const char *domain = "test.invalid"; + unsigned server_count = 2; + char aor[256]; + char srv[256]; + unsigned i; + unsigned total_req = 0; + int err; + + memset(&t, 0, sizeof t); + + /* + * Setup server-side mocks: + */ + + err = dns_server_alloc(&dnssrv, true); + TEST_ERR(err); + + info("| DNS-server on %J\n", &dnssrv->addr); + + /* NOTE: must be done before ua_init() */ + err = net_use_nameserver(net, &dnssrv->addr); + TEST_ERR(err); + + for (i=0; i<server_count; i++) { + struct sa sip_addr; + char arec[256]; + + err = sip_server_alloc(&t.srvv[i]); + if (err) { + warning("failed to create sip server (%d/%m)\n", + err, err); + goto out; + } + + t.srvv[i]->instance = i; + +#if 1 + /* Comment this out to have different random secrets + * on each SIP-Server instance */ + t.srvv[i]->secret = 42; +#endif + + err = domain_add(t.srvv[i], domain); + if (err) + goto out; + + err = user_add(domain_lookup(t.srvv[i], domain)->ht_usr, + username, password, domain); + TEST_ERR(err); + + t.srvv[i]->auth_enabled = true; + + err = sip_transp_laddr(t.srvv[i]->sip, &sip_addr, tp, NULL); + TEST_ERR(err); + + info("| SIP-server on %J\n", &sip_addr); + + re_snprintf(arec, sizeof(arec), + "alpha%u.%s", i+1, domain); + + re_snprintf(srv, sizeof(srv), + "%s.%s", _sip_transp_srvid(tp), domain); + err = dns_server_add_srv(dnssrv, srv, + 20, 0, sa_port(&sip_addr), + arec); + TEST_ERR(err); + + err = dns_server_add_a(dnssrv, arec, sa_in(&sip_addr)); + TEST_ERR(err); + } + t.srvc = server_count; + + /* NOTE: angel brackets needed to parse ;transport parameter */ + if (re_snprintf(aor, sizeof(aor), "<sip:%s:%s@%s;transport=%s>", + username, password, domain, sip_transp_name(tp)) < 0) + return ENOMEM; + + /* + * Start SIP client: + */ + + err = ua_init("test", true, true, true, false); + TEST_ERR(err); + + err = ua_alloc(&t.ua, aor); + TEST_ERR(err); + + err = uag_event_register(ua_event_handler, &t); + if (err) + goto out; + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + if (err) + goto out; + + if (t.err) { + err = t.err; + goto out; + } + + /* verify that all SIP requests was sent to the + * SIP-servers. + */ + for (i=0; i<server_count; i++) { + + total_req += t.srvv[i]->n_register_req; + + if (t.srvv[i]->n_register_req) { + ASSERT_EQ(tp, t.srvv[i]->tp_last); + } + } + ASSERT_TRUE(total_req >= 2); + ASSERT_TRUE(t.got_register_ok > 0); + + out: + if (err) { + warning("selftest: ua_register test failed (%m)\n", err); + } + uag_event_unregister(ua_event_handler); + + test_reset(&t); + + ua_stop_all(true); + ua_close(); + + mem_deref(dnssrv); + + return err; +} + + +int test_ua_register_auth_dns(void) +{ + int err = 0; + + err |= reg_auth_dns(SIP_TRANSP_UDP); + TEST_ERR(err); + err |= reg_auth_dns(SIP_TRANSP_TCP); + TEST_ERR(err); +#ifdef USE_TLS + err |= reg_auth_dns(SIP_TRANSP_TLS); + TEST_ERR(err); +#endif + + out: + return err; +} + + +static void options_resp_handler(int err, const struct sip_msg *msg, void *arg) +{ + struct test *t = arg; + const struct sip_hdr *hdr; + struct pl content; + uint32_t clen; + + ASSERT_EQ(MAGIC, t->magic); + + if (err) { + test_abort(t, err); + return; + } + if (msg->scode != 200) { + test_abort(t, EPROTO); + return; + } + + ++t->n_resp; + + /* Verify SIP headers */ + + ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "INVITE")); + ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "ACK")); + ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "BYE")); + ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "CANCEL")); + + hdr = sip_msg_hdr(msg, SIP_HDR_CONTACT); + ASSERT_TRUE(hdr != NULL); + ASSERT_TRUE(hdr->val.l != 0); + + ASSERT_EQ(0, pl_strcasecmp(&msg->ctyp.type, "application")); + ASSERT_EQ(0, pl_strcasecmp(&msg->ctyp.subtype, "sdp")); + + clen = pl_u32(&msg->clen); + ASSERT_TRUE(clen > 0); + + /* Verify the SDP content */ + + pl_set_mbuf(&content, msg->mb); + + ASSERT_EQ(0, re_regex(content.p, content.l, "v=0")); + ASSERT_EQ(0, re_regex(content.p, content.l, "a=tool:baresip")); + ASSERT_EQ(0, re_regex(content.p, content.l, "m=audio")); + + out: + if (err) + t->err = err; + re_cancel(); +} + + +int test_ua_options(void) +{ + struct test t; + struct sa laddr; + char uri[256]; + int n, err = 0; + + test_init(&t); + + err = ua_init("test", true, false, false, false); + TEST_ERR(err); + + err = sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, NULL); + TEST_ERR(err); + + err = ua_alloc(&t.ua, "Foo <sip:user@127.0.0.1>;regint=0"); + TEST_ERR(err); + + n = re_snprintf(uri, sizeof(uri), + "sip:user@127.0.0.1:%u", sa_port(&laddr)); + ASSERT_TRUE(n > 0); + + err = ua_options_send(t.ua, uri, options_resp_handler, &t); + TEST_ERR(err); + + /* run main-loop with timeout, wait for events */ + err = re_main_timeout(5000); + if (err) + goto out; + + TEST_ERR(t.err); + + /* verify after test is complete */ + ASSERT_EQ(1, t.n_resp); + + out: + test_reset(&t); + + ua_stop_all(true); + ua_close(); + + return err; +} diff --git a/test/video.c b/test/video.c new file mode 100644 index 0000000..09c5e77 --- /dev/null +++ b/test/video.c @@ -0,0 +1,38 @@ +/** + * @file test/video.c Baresip selftest -- video + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "test.h" + + +#define DEBUG_MODULE "video" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +int test_video(void) +{ + int err = 0; + + /* test with framerate of zero */ + ASSERT_EQ(0, video_calc_rtp_timestamp(1, 0)); + + ASSERT_EQ( 0, video_calc_rtp_timestamp( 0, 30)); + ASSERT_EQ( 3000, video_calc_rtp_timestamp( 1, 30)); + ASSERT_EQ( 30000, video_calc_rtp_timestamp( 10, 30)); + ASSERT_EQ( 300000, video_calc_rtp_timestamp( 100, 30)); + ASSERT_EQ( 3000000, video_calc_rtp_timestamp( 1000, 30)); + ASSERT_EQ( 30000000, video_calc_rtp_timestamp( 10000, 30)); + ASSERT_EQ( 300000000, video_calc_rtp_timestamp( 100000, 30)); + ASSERT_EQ(3000000000, video_calc_rtp_timestamp(1000000, 30)); + + ASSERT_EQ(4294965000, video_calc_rtp_timestamp(1431655, 30)); + ASSERT_EQ( 704, video_calc_rtp_timestamp(1431656, 30)); + + out: + return err; +} |