From 87e9cc4300bb5a013c8c14a94f93470c68e73060 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Mon, 8 Jan 2018 22:22:59 +0530 Subject: Import baresip_0.5.7-1.debian.tar.xz [dgit import tarball baresip 0.5.7-1 baresip_0.5.7-1.debian.tar.xz] --- ARCHS_directfb | 1 + TODO | 4 + archs-update | 51 +++ baresip-core.install | 3 + baresip-core.lintian-overrides | 2 + changelog | 242 +++++++++++++++ clean | 3 + compat | 1 + control | 191 ++++++++++++ control.in | 150 +++++++++ copyright | 115 +++++++ copyright-check | 27 ++ copyright_hints | 553 +++++++++++++++++++++++++++++++++ gbp.conf | 6 + patches/1001_gcc7_compat.patch | 20 ++ patches/2001_drop_libre_so_check.patch | 49 +++ patches/README | 3 + patches/series | 2 + rules | 90 ++++++ source/format | 1 + source/lintian-overrides | 3 + watch | 5 + 22 files changed, 1522 insertions(+) create mode 100644 ARCHS_directfb create mode 100644 TODO create mode 100755 archs-update create mode 100644 baresip-core.install create mode 100644 baresip-core.lintian-overrides create mode 100644 changelog create mode 100644 clean create mode 100644 compat create mode 100644 control create mode 100644 control.in create mode 100644 copyright create mode 100755 copyright-check create mode 100644 copyright_hints create mode 100644 gbp.conf create mode 100644 patches/1001_gcc7_compat.patch create mode 100644 patches/2001_drop_libre_so_check.patch create mode 100644 patches/README create mode 100644 patches/series create mode 100755 rules create mode 100644 source/format create mode 100644 source/lintian-overrides create mode 100644 watch diff --git a/ARCHS_directfb b/ARCHS_directfb new file mode 100644 index 0000000..52edc18 --- /dev/null +++ b/ARCHS_directfb @@ -0,0 +1 @@ +amd64 arm64 armel armhf hurd-i386 i386 kfreebsd-amd64 kfreebsd-i386 mips mips64el mipsel powerpc ppc64el s390x diff --git a/TODO b/TODO new file mode 100644 index 0000000..b31f591 --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ + * Provide library-related binary packages. + * Enable ZRTP module: + + Build-depend on libgzrtp-dev. + + Mention GZRTP in long descriptions. diff --git a/archs-update b/archs-update new file mode 100755 index 0000000..08072f4 --- /dev/null +++ b/archs-update @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright 2008, 2013-2014, 2017 Jonas Smedegaard +# Description: helper script to update architecture-specific hints +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA. +# +# Depends: libwww-perl + +set -e + +pkgsuite="$(dpkg-parsechangelog -S Distribution)" +case "$pkgsuite" in + experimental) suite=unstable,experimental;; + UNRELEASED|'') suite=unstable;; + *-*) suite="$(echo "$suite" | sed -e 's/-.*//')";; + *) suite="$pkgsuite";; +esac +[ "$pkgsuite" = "$suite" ] || echo >&2 "WARNING: using suite \"$suite\"." + +pkgarchs() { + echo >&2 "INFO: Query Debian for package \"$1\" architectures..." + GET "https://qa.debian.org/madison.php?package=$1&table=debian&s=$suite&text=on" \ + | perl -MList::Util=uniq -F'\|\s*' \ + -E 'for (split(/[,\s]+/, pop @F)) {' \ + -E 'next if ($_ eq "source");' \ + -E '$_ = "any" if ($_ eq "all");' \ + -E 'push @a, $_};' \ + -E 'END{if (@a) {say join(" ", uniq sort @a)} else {say "none"}}' +} + +for lib in directfb; do + pkgarchs "lib$lib-dev" > "debian/ARCHS_$lib" + export "ARCHS_$lib=$(cat debian/ARCHS_$lib)" + perl -pi \ + -e "s/\blib$lib-dev(\s*\([^\)]*\))?\K[^,]*,/ [\$ENV{'ARCHS_$lib'}],/;" \ + debian/control +done diff --git a/baresip-core.install b/baresip-core.install new file mode 100644 index 0000000..b2eadef --- /dev/null +++ b/baresip-core.install @@ -0,0 +1,3 @@ +usr/bin/* +usr/lib/baresip +usr/share/baresip diff --git a/baresip-core.lintian-overrides b/baresip-core.lintian-overrides new file mode 100644 index 0000000..7945ec7 --- /dev/null +++ b/baresip-core.lintian-overrides @@ -0,0 +1,2 @@ +# only packaging is GPL licensed +possible-gpl-code-linked-with-openssl diff --git a/changelog b/changelog new file mode 100644 index 0000000..170af87 --- /dev/null +++ b/changelog @@ -0,0 +1,242 @@ +baresip (0.5.7-1) unstable; urgency=medium + + [ upstream ] + * New release. + + [ Jonas Smedegaard ] + * Enable new MQTT module: + + Build-depend on libmosquitto-dev. + + Mention MQTT in long descriptions. + * Declare compliance with Debian Policy 4.1.3. + * Add TODO items about library packages and GZRTP module. + * Update copyright info: Extend coverage for myself. + + -- Jonas Smedegaard Mon, 08 Jan 2018 17:52:59 +0100 + +baresip (0.5.6-1) unstable; urgency=medium + + [ upstream ] + * New release. + + [ Jonas Smedegaard ] + * Update copyright info: Track files copyright Jonathan Sieber. + * Update watch file: Use substitution strings. + * Enable new Avahi module: + + Build-depend on libavahi-client-dev. + + Have baresip-core recommend avahi-daemon. + + Mention Avahi in long descriptions. + * Update copyright-check script to extract png files. + * Fix tighten build-dependency on librem-dev due to changed types. + Closes: Bug#878341. Thanks to Adrian Bunk. + * Declare compliance with Debian Policy 4.1.1. + + -- Jonas Smedegaard Sat, 28 Oct 2017 01:31:09 +0200 + +baresip (0.5.5-1) unstable; urgency=medium + + [ upstream ] + * New release. + + [ Jonas Smedegaard ] + * Advertise DEP3 format in patch headers. + * Tighten lintian overrides regarding License-Reference. + * Declare compliance with Debian Policy 4.1.0. + * Update package relations: Tighten build-dependency on libre-dev. + * Rename patch from NMU 0.5.4-1.1 to fit documented patch naming + micro-policy. + * Unfuzz patches. + + -- Jonas Smedegaard Mon, 11 Sep 2017 10:28:44 +0200 + +baresip (0.5.4-1.1) unstable; urgency=medium + + * Non-maintainer upload. + + [ Steve Langasek ] + * Build with -D_GNU_SOURCE to fix FTBFS with GCC 7. (Closes: #872406) + + -- Sebastian Ramacher Sat, 26 Aug 2017 11:41:57 +0200 + +baresip (0.5.4-1) unstable; urgency=medium + + [ upstream ] + * New release(s). + + Config: + - Enable audio level RTP extension. + + baresip-core: + - Support Client-to-Mixer Audio Level Indication (RFC 6464). + - Support 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. + - 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. + + Update all video modules with API-changes. + + zrtp: check for RTP packet in send handler. + - registered zrtp_log function with zrtp engine. + - improved info message on how to verify remote peer. + - improved setting and printing of zrtp cache file. + + [ Jonas Smedegaard ] + * Update package relations: + + Tighten to build-depend on recent librem-dev. + + Tighten build-dependency on libre-dev. + + Drop obsolete temporary breaks/replaces for older baresip. + + Fix limit build-dependency on libdirectfb-dev to supported archs. + + -- Jonas Smedegaard Mon, 03 Jul 2017 12:57:00 +0200 + +baresip (0.5.3-2) unstable; urgency=medium + + * Declare compliance with Debian Policy 4.0.0. + * Modernize Vcs-Browser field: + + Use git (not cgit) in path. + * Modernize cdbs: + + Resolve archs in maintainer script (not during build). + + Fix maintainer script copyright-check. + + -- Jonas Smedegaard Sat, 24 Jun 2017 08:37:12 +0200 + +baresip (0.5.3-1) experimental; urgency=medium + + [ upstream ] + * New release(s). + + [ Jonas Smedegaard ] + * Modernize cdbs: + + Do copyright-check in maintainer script (not during build). + + Relax to build-depend unversioned on cdbs. + + Stop build-depend on licensecheck. + * Enable module omx. + + Build-depend on libomxil-bellagio-dev. + * Tighten to build-depend on recent libre. + * Stop explicitly enable jack module: Auto-detected now. + + -- Jonas Smedegaard Sun, 21 May 2017 18:09:17 +0200 + +baresip (0.5.1-1) experimental; urgency=medium + + [ upstream ] + * New release. + + [ Jonas Smedegaard ] + * Update copyright info: Extend coverage for main upstream authors. + * Update package relations: Stop build-depend on libsrtp0-dev. + + -- Jonas Smedegaard Mon, 13 Mar 2017 17:15:52 +0100 + +baresip (0.5.0-4) unstable; urgency=medium + + * Update package relations: Fix breaks/replaces for baresip-core. + + -- Jonas Smedegaard Tue, 17 Jan 2017 23:21:28 +0100 + +baresip (0.5.0-3) unstable; urgency=medium + + * Improve short and long descriptions. + * Fix install examples. + * Add binary package baresip-core, and make baresip a metapackage. + * Fix install upstream changelog. + + -- Jonas Smedegaard Tue, 17 Jan 2017 18:38:33 +0100 + +baresip (0.5.0-2) unstable; urgency=medium + + * Fix skip directfb module where unsupported. + * Update package relations: Fix have other packages depend on baresip. + * Enable module sndio. + * Fix list only supported features (and mention additional ones) in + long descriptions. + + -- Jonas Smedegaard Thu, 12 Jan 2017 22:05:39 +0100 + +baresip (0.5.0-1) unstable; urgency=medium + + [ upstream ] + * New release. + + [ Vasudev Kamath ] + * Fix path to wav files: Set variable PREFIX during build. + * Enable more modules: + + gst_video1 + + gtk + + rst + Build-depend on libgtk2.0-dev libgstreamer-plugins-base1.0-dev + libmpg123-dev libx11-dev libxext-dev. + * Add separate binary packages for non-core modules: + + baresip-ffmpeg + + baresip-gstreamer + + baresip-gtk + + baresip-x11 + + [ Jonas Smedegaard ] + * Add myself as uploader (and sort the list while at it). + * Refresh patches with shortening quilt options. + * Add git-buildpackage config: + + Use pristine-tar. + + Sign tags. + + Filter debian dir and any .git* file. + * Fix clean built code. + * Git-ignore quilt .pc dir (and avoid upstream git-ignore items). + * Update copyright info: + + Extend coverage for main upstream authors. + + Add myself as copyright holder. + * Let CDBS resolve build flags. + * Override lintian for possible-gpl-code-linked-with-openssl; False + positive. + * Drop obsolete upstream patches. + * Install README and TODO files with all binary packages. + * Install example files as such. + * Fix stop suppress copyright check of debian dir: May contain code. + * Enable more modules: amr b2bua codec2 directfb echo jack h265 + libsrtp mpa pcp portaudio pulse sdl2 snapshot sndfile sndio speex + speex_aec speex_pp swscale. + * Update package relations: + + Build-depend on libcodec2-dev libjack-dev libopencore-amrnb + libopencore-amrwb libpng-dev libpulse-dev libsdl2-dev + libsndfile1-dev libsndio-dev libspeex-dev libspeexdsp-dev + libsrtp0-dev libswscale-dev libtwolame-dev libx265-dev + portaudio19-dev. + + Build-depend on libdirectfb-dev on supported archs + (queried online in maintainer mode). + + Limit to build-depend on libevdev-dev only on linux archs. + + Fix build-depend explicitly on libssl-dev. + Closes: Bug#849694. Thanks to Logan Rosen. + + Fix have other packages break+replace baresip. + Closes: Bug#850663. Thanks to Axel Beckert. + + Have baresip suggest other packages. + + Have other packages enhance baresip. + + Mention other packages in long description of baresip. + * Fix dollar-escape skeleton variables. + * Update watch file: + + Add usage comment. + + Use github pattern from documentation. + + Add trailing newline. + * Simplify build: + + Drop build flags equal to auto-resolved defaults. + + Let CDBS handle build and install. + * Enable testsuite. + Thanks to Alfred Heggestad. + * Stop override lintian for + package-needs-versioned-debhelper-build-depends: Fixed in lintian. + * Update copyright info: + + Fix double-indent subsequent copyright holders. + + Strip useless copyright pseudo-signs. + + Sort copyright holders. + + Extend coverage of Debian packaging. + + Add license grant to rules. + + -- Jonas Smedegaard Thu, 12 Jan 2017 17:13:18 +0100 + +baresip (0.4.20-1) unstable; urgency=medium + + * Initial Release. + Closes: bug#673881 + + -- Vasudev Kamath Thu, 22 Dec 2016 17:28:27 +0530 diff --git a/clean b/clean new file mode 100644 index 0000000..bca3db6 --- /dev/null +++ b/clean @@ -0,0 +1,3 @@ +build-*/ +*.so +baresip diff --git a/compat b/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/control b/control new file mode 100644 index 0000000..82995b4 --- /dev/null +++ b/control @@ -0,0 +1,191 @@ +Source: baresip +Section: comm +Priority: optional +Build-Depends: cdbs, + debhelper, + dh-buildinfo, + libasound2-dev, + libavahi-client-dev, + libcodec2-dev, + libdirectfb-dev [amd64 arm64 armel armhf hurd-i386 i386 kfreebsd-amd64 kfreebsd-i386 mips mips64el mipsel powerpc ppc64el s390x], + libjack-dev, + libgsm1-dev, + libopus-dev, + libopencore-amrnb-dev, + libopencore-amrwb-dev, + libpng-dev, + libsdl2-dev, + libsndfile1-dev, + libsndio-dev, + libspandsp-dev, + libspeex-dev, + libspeexdsp-dev, + libtwolame-dev, + libvpx-dev, + libssl-dev, + libmosquitto-dev, + libevdev-dev [linux-any], + portaudio19-dev, + pkg-config, + libre-dev (>= 0.5.5), + librem-dev (>= 0.5.2), + libavcodec-dev, + libavformat-dev, + libavdevice-dev, + libswscale-dev, + libx265-dev, + libgstreamer1.0-dev, + libgstreamer-plugins-base1.0-dev, + libgtk2.0-dev, + libcairo2-dev, + libomxil-bellagio-dev, + libmpg123-dev, + libpulse-dev, + libxext-dev, + libx11-dev +Maintainer: Debian VoIP Team +Uploaders: Jonas Smedegaard , + Ramakrishnan Muthhukrishnan , + Vasudev Kamath +Standards-Version: 4.1.3 +Vcs-Git: https://anonscm.debian.org/git/pkg-voip/baresip.git +Vcs-Browser: https://anonscm.debian.org/git/pkg-voip/baresip.git +Homepage: http://www.creytiv.com/baresip.html + +Package: baresip +Architecture: all +Depends: ${cdbs:Depends}, + ${misc:Depends} +Recommends: ${cdbs:Recommends} +Description: portable and modular SIP user-agent - metapackage + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This metapackage will install baresip and all its optional features. + +Package: baresip-core +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends} +Recommends: ${cdbs:Recommends} +Suggests: ${cdbs:Suggests} +Description: portable and modular SIP user-agent - core parts + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + Some of above features are provided in separate packages baresip-gtk, + baresip-ffmpeg, baresip-gstreamer and baresip-x11. + +Package: baresip-gtk +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - GTK+ front-end + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package provides a GTK+ front-end for baresip. + +Package: baresip-ffmpeg +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - FFmpeg codecs and formats + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package integrates FFmpeg codecs and formats with baresip. + +Package: baresip-gstreamer +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - GStreamer pipelines + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package integrates GStreamer pipelines with baresip. + +Package: baresip-x11 +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - X11 features + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package provides various X11-related features for baresip. diff --git a/control.in b/control.in new file mode 100644 index 0000000..25c2706 --- /dev/null +++ b/control.in @@ -0,0 +1,150 @@ +Source: baresip +Section: comm +Priority: optional +Build-Depends: @cdbs@ +Maintainer: Debian VoIP Team +Uploaders: Jonas Smedegaard , + Ramakrishnan Muthhukrishnan , + Vasudev Kamath +Standards-Version: 4.1.3 +Vcs-Git: https://anonscm.debian.org/git/pkg-voip/baresip.git +Vcs-Browser: https://anonscm.debian.org/git/pkg-voip/baresip.git +Homepage: http://www.creytiv.com/baresip.html + +Package: baresip +Architecture: all +Depends: ${cdbs:Depends}, + ${misc:Depends} +Recommends: ${cdbs:Recommends} +Description: portable and modular SIP user-agent - metapackage + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This metapackage will install baresip and all its optional features. + +Package: baresip-core +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends} +Recommends: ${cdbs:Recommends} +Suggests: ${cdbs:Suggests} +Description: portable and modular SIP user-agent - core parts + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + Some of above features are provided in separate packages baresip-gtk, + baresip-ffmpeg, baresip-gstreamer and baresip-x11. + +Package: baresip-gtk +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - GTK+ front-end + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package provides a GTK+ front-end for baresip. + +Package: baresip-ffmpeg +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - FFmpeg codecs and formats + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package integrates FFmpeg codecs and formats with baresip. + +Package: baresip-gstreamer +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - GStreamer pipelines + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package integrates GStreamer pipelines with baresip. + +Package: baresip-x11 +Architecture: any +Depends: ${misc:Depends}, + ${shlibs:Depends}, + baresip-core (= ${binary:Version}) +Enhances: ${cdbs:Enhances} +Description: portable and modular SIP user-agent - X11 features + A modular SIP user-agent with support for audio and video, and many + IETF standards such as SIP, SDP, RTP/RTCP, STUN, TURN, and ICE. + . + Supports both IPv4 and IPv6, and the following features. + * Audio codecs: AMR, G.711, G.722, G.726, GSM, L16, MPA, OPUS, Speex. + * Video codecs: H.263, H.264, H.265, MPEG4, VP8, VP9. + * Audio drivers: Alsa, GStreamer, JACK, OSS, Portaudio, sndio. + * Video sources: FFmpeg avformat, Video4Linux2, X11 Grabber. + * Video output modules: SDL2, X11, DirectFB. + * NAT Traversal modules: STUN, TURN, ICE, NATBD, NAT-PMP, PCP. + * Media encryption modules: SRTP, DTLS-SRTP. + * DNS Service Discovery module: Avahi. + * Telemetry messaging module MQTT. + . + This package provides various X11-related features for baresip. diff --git a/copyright b/copyright new file mode 100644 index 0000000..b4bc1f3 --- /dev/null +++ b/copyright @@ -0,0 +1,115 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: baresip +Upstream-Contact: https://github.com/alfredh/baresip/issues/ + Alfred E. Heggestad +Source: https://github.com/alfredh/baresip + +Files: * +Copyright: 2010-2017, Alfred E. Heggestad + 2010-2017, Creytiv.com + 2010-2017, Richard Aas +License: BSD-3-clause + +Files: modules/gtk/call_window.c + modules/gtk/dial_dialog.c + modules/gtk/gtk_mod.h + modules/gtk/transfer_dialog.c + modules/gtk/uri_entry.c + modules/gtk/gtk_mod.c + modules/gtk/module.mk +Copyright: 2010-2015, Creytiv.com + 2015, Charles E. Lehner +License: BSD-3-clause + +Files: modules/gst_video/encode.c + modules/gst_video/gst_video.c + modules/gst_video/gst_video.h + modules/gst_video1/gst_video.c + modules/gst_video1/gst_video.h +Copyright: 2010-2014, Creytiv.com + 2014, Fadeev Alexander +License: BSD-3-clause + +Files: modules/avahi/avahi.c + modules/omx/module.c + modules/omx/omx.c + modules/omx/omx.h +Copyright: 2010,2016-2017, Creytiv.com + 2016-2017, Jonathan Sieber +License: BSD-3-clause + +Files: modules/gst_video1/encode.c +Copyright: 2010-2013, Creytiv.com + 2014, Fadeev Alexander + 2015, Thomas Strobel +License: BSD-3-clause + +Files: modules/directfb/directfb.c + modules/directfb/module.mk +Copyright: 2010, Creytiv.com + 2013, Andreas Shimokawa +License: BSD-3-clause + +Files: modules/dtmfio/dtmfio.c +Copyright: 2014, Aaron Herting 'qwertos' +License: BSD-3-clause + +Files: modules/dshow/dshow.cpp +Copyright: 2010, Creytiv.com + 2010, Dusan Stevanovic +License: BSD-3-clause + +Files: modules/mpa/* +Copyright: 2016, Symonics GmbH +License: BSD-3-clause + +Files: modules/presence/publisher.c +Copyright: 2010, Creytiv.com + 2014, Juha Heinanen +License: BSD-3-clause + +Files: debian/patches/2001_drop_libre_so_check.patch +Copyright: 2015-2016, Vasudev Kamath +License: BSD-3-clause + +Files: debian/* +Copyright: 2008,2013-2014,2016-2018, Jonas Smedegaard + 2015, Ramakrishnan Muthukrishnan + 2015, Vasudev Kamath +License-Grant: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 3, or (at your option) any + later version. +License: GPL-3+ + +License: GPL-3+ +License-Reference: /usr/share/common-licenses/GPL-3 + +License: BSD-3-clause + 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/copyright-check b/copyright-check new file mode 100755 index 0000000..abde8ec --- /dev/null +++ b/copyright-check @@ -0,0 +1,27 @@ +#!/bin/sh +# Copyright © 2016-2017 Jonas Smedegaard +# Description: helper script to update copyright_hints +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set -eu + +export DEB_COPYRIGHT_EXTRACT_EXTS="png" +export DEB_COPYRIGHT_CHECK_IGNORE_EXTS="wav" + +make -f /usr/share/cdbs/1/rules/utils.mk pre-build || true +make -f /usr/share/cdbs/1/rules/utils.mk clean DEB_COPYRIGHT_CHECK_STRICT=1 + +# unconditionally merge changes - safe to do with git-tracked package +[ ! -f debian/copyright_newhints ] || mv -f debian/copyright_newhints debian/copyright_hints diff --git a/copyright_hints b/copyright_hints new file mode 100644 index 0000000..dfe3084 --- /dev/null +++ b/copyright_hints @@ -0,0 +1,553 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: FIXME +Upstream-Contact: FIXME +Source: FIXME +Disclaimer: Autogenerated by CDBS + +Files: Makefile + README.md + include/baresip.h + mk/mod.mk + mk/modules.mk + modules/account/account.c + modules/account/module.mk + modules/alsa/alsa.c + modules/alsa/alsa.h + modules/alsa/alsa_play.c + modules/alsa/alsa_src.c + modules/alsa/module.mk + modules/amr/amr.c + modules/amr/amr.h + modules/amr/module.mk + modules/amr/sdp.c + modules/aubridge/aubridge.c + modules/aubridge/aubridge.h + modules/aubridge/device.c + modules/aubridge/module.mk + modules/aubridge/play.c + modules/aubridge/src.c + modules/audiounit/audiounit.c + modules/audiounit/audiounit.h + modules/audiounit/module.mk + modules/audiounit/player.c + modules/audiounit/recorder.c + modules/audiounit/sess.c + modules/aufile/aufile.c + modules/aufile/module.mk + modules/auloop/auloop.c + modules/auloop/module.mk + modules/av1/av1.c + modules/av1/av1.h + modules/av1/decode.c + modules/av1/encode.c + modules/av1/module.mk + modules/avahi/module.mk + modules/avcapture/avcapture.m + modules/avcapture/module.mk + modules/avcodec/avcodec.c + modules/avcodec/avcodec.h + modules/avcodec/decode.c + modules/avcodec/encode.c + modules/avcodec/h263.c + modules/avcodec/h26x.h + modules/avcodec/module.mk + modules/avformat/avformat.c + modules/avformat/module.mk + modules/b2bua/b2bua.c + modules/b2bua/module.mk + modules/bv32/bv32.c + modules/bv32/module.mk + modules/cairo/cairo.c + modules/cairo/module.mk + modules/codec2/codec2.c + modules/codec2/module.mk + modules/cons/cons.c + modules/cons/module.mk + modules/contact/contact.c + modules/contact/module.mk + modules/coreaudio/coreaudio.c + modules/coreaudio/coreaudio.h + modules/coreaudio/module.mk + modules/coreaudio/player.c + modules/coreaudio/recorder.c + modules/daala/daala.c + modules/daala/daala.h + modules/daala/decode.c + modules/daala/encode.c + modules/daala/module.mk + modules/debug_cmd/debug_cmd.c + modules/debug_cmd/module.mk + modules/dshow/module.mk + modules/dtls_srtp/dtls.c + modules/dtls_srtp/dtls_srtp.c + modules/dtls_srtp/dtls_srtp.h + modules/dtls_srtp/module.mk + modules/dtls_srtp/srtp.c + modules/dtmfio/module.mk + modules/evdev/evdev.c + modules/evdev/module.mk + modules/evdev/print.h + modules/fakevideo/fakevideo.c + modules/fakevideo/module.mk + modules/g711/g711.c + modules/g711/module.mk + modules/g722/g722.c + modules/g722/module.mk + modules/g7221/decode.c + modules/g7221/encode.c + modules/g7221/g7221.c + modules/g7221/g7221.h + modules/g7221/module.mk + modules/g7221/sdp.c + modules/g726/g726.c + modules/g726/module.mk + modules/gsm/gsm.c + modules/gsm/module.mk + modules/gst/dump.c + modules/gst/gst.c + modules/gst/gst.h + modules/gst/module.mk + modules/gst1/gst.c + modules/gst1/module.mk + modules/gst_video/module.mk + modules/gst_video/sdp.c + modules/gst_video1/module.mk + modules/gst_video1/sdp.c + modules/gzrtp/gzrtp.cpp + modules/gzrtp/messages.cpp + modules/gzrtp/module.mk + modules/gzrtp/session.cpp + modules/gzrtp/session.h + modules/gzrtp/srtp.cpp + modules/gzrtp/srtp.h + modules/gzrtp/stream.cpp + modules/gzrtp/stream.h + modules/h265/decode.c + modules/h265/encode.c + modules/h265/fmt.c + modules/h265/h265.c + modules/h265/h265.h + modules/h265/module.mk + modules/httpd/httpd.c + modules/httpd/module.mk + modules/ice/ice.c + modules/ice/module.mk + modules/ilbc/ilbc.c + modules/ilbc/module.mk + modules/isac/isac.c + modules/isac/module.mk + modules/jack/jack.c + modules/jack/jack_play.c + modules/jack/jack_src.c + modules/jack/mod_jack.h + modules/jack/module.mk + modules/l16/l16.c + modules/l16/module.mk + modules/libsrtp/module.mk + modules/libsrtp/sdes.c + modules/libsrtp/sdes.h + modules/libsrtp/srtp.c + modules/menu/module.mk + modules/mqtt/module.mk + modules/mqtt/mqtt.c + modules/mqtt/publish.c + modules/mqtt/subscribe.c + modules/mwi/module.mk + modules/mwi/mwi.c + modules/natbd/module.mk + modules/natbd/natbd.c + modules/natpmp/libnatpmp.c + modules/natpmp/libnatpmp.h + modules/natpmp/module.mk + modules/natpmp/natpmp.c + modules/omx/module.mk + modules/opengl/module.mk + modules/opengl/opengl.m + modules/opengles/context.m + modules/opengles/module.mk + modules/opengles/opengles.c + modules/opengles/opengles.h + modules/opensles/module.mk + modules/opensles/opensles.c + modules/opensles/opensles.h + modules/opensles/player.c + modules/opensles/recorder.c + modules/opus/decode.c + modules/opus/encode.c + modules/opus/module.mk + modules/opus/opus.c + modules/opus/opus.h + modules/opus/sdp.c + modules/oss/module.mk + modules/oss/oss.c + modules/pcp/listener.c + modules/pcp/module.mk + modules/pcp/pcp.c + modules/pcp/pcp.h + modules/plc/module.mk + modules/plc/plc.c + modules/portaudio/module.mk + modules/portaudio/portaudio.c + modules/presence/module.mk + modules/presence/notifier.c + modules/presence/presence.c + modules/presence/presence.h + modules/presence/subscriber.c + modules/pulse/module.mk + modules/pulse/player.c + modules/pulse/pulse.c + modules/pulse/pulse.h + modules/pulse/recorder.c + modules/qtcapture/module.mk + modules/qtcapture/qtcapture.m + modules/rst/audio.c + modules/rst/module.mk + modules/rst/rst.c + modules/rst/rst.h + modules/rst/video.c + modules/sdl/module.mk + modules/sdl/sdl.c + modules/sdl/sdl.h + modules/sdl/util.c + modules/sdl2/module.mk + modules/sdl2/sdl.c + modules/selfview/module.mk + modules/selfview/selfview.c + modules/silk/module.mk + modules/silk/silk.c + modules/snapshot/module.mk + modules/snapshot/snapshot.c + modules/sndfile/module.mk + modules/sndfile/sndfile.c + modules/sndio/module.mk + modules/sndio/sndio.c + modules/speex/module.mk + modules/speex/speex.c + modules/speex_aec/module.mk + modules/speex_aec/speex_aec.c + modules/speex_pp/module.mk + modules/speex_pp/speex_pp.c + modules/srtp/module.mk + modules/srtp/sdes.c + modules/srtp/sdes.h + modules/srtp/srtp.c + modules/stdio/module.mk + modules/stdio/stdio.c + modules/stun/module.mk + modules/stun/stun.c + modules/swscale/module.mk + modules/swscale/swscale.c + modules/syslog/module.mk + modules/syslog/syslog.c + modules/turn/module.mk + modules/turn/turn.c + modules/uuid/module.mk + modules/uuid/uuid.c + modules/v4l/module.mk + modules/v4l/v4l.c + modules/v4l2/module.mk + modules/v4l2/v4l2.c + modules/v4l2_codec/module.mk + modules/v4l2_codec/v4l2_codec.c + modules/vidbridge/disp.c + modules/vidbridge/module.mk + modules/vidbridge/src.c + modules/vidbridge/vidbridge.c + modules/vidbridge/vidbridge.h + modules/vidinfo/module.mk + modules/vidinfo/panel.c + modules/vidinfo/vidinfo.c + modules/vidinfo/vidinfo.h + modules/vidloop/module.mk + modules/vidloop/vidloop.c + modules/vp8/decode.c + modules/vp8/encode.c + modules/vp8/module.mk + modules/vp8/sdp.c + modules/vp8/vp8.c + modules/vp8/vp8.h + modules/vp9/decode.c + modules/vp9/encode.c + modules/vp9/module.mk + modules/vp9/sdp.c + modules/vp9/vp9.c + modules/vp9/vp9.h + modules/vumeter/module.mk + modules/vumeter/vumeter.c + modules/wincons/module.mk + modules/wincons/wincons.c + modules/winwave/module.mk + modules/winwave/play.c + modules/winwave/src.c + modules/winwave/winwave.c + modules/winwave/winwave.h + modules/x11/module.mk + modules/x11/x11.c + modules/x11grab/module.mk + modules/x11grab/x11grab.c + modules/zrtp/module.mk + modules/zrtp/zrtp.c + src/account.c + src/aucodec.c + src/audio.c + src/aufilt.c + src/aulevel.c + src/auplay.c + src/ausrc.c + src/baresip.c + src/bfcp.c + src/call.c + src/cmd.c + src/conf.c + src/config.c + src/contact.c + src/core.h + src/event.c + src/h264.c + src/log.c + src/magic.h + src/mctrl.c + src/menc.c + src/message.c + src/metric.c + src/mnat.c + src/module.c + src/mos.c + src/net.c + src/play.c + src/realtime.c + src/reg.c + src/rtpext.c + src/rtpkeep.c + src/sdp.c + src/sipreq.c + src/srcs.mk + src/stream.c + src/ua.c + src/ui.c + src/vidcodec.c + src/video.c + src/vidfilt.c + src/vidisp.c + src/vidsrc.c + test/account.c + test/aulevel.c + test/call.c + test/cmd.c + test/contact.c + test/cplusplus.cpp + test/main.c + test/message.c + test/mock/cert.c + test/mock/dnssrv.c + test/mock/mock_aucodec.c + test/mock/mock_auplay.c + test/mock/mock_ausrc.c + test/mock/mock_vidcodec.c + test/mock/mock_vidisp.c + test/mock/mock_vidsrc.c + test/mos.c + test/net.c + test/play.c + test/sip/aor.c + test/sip/auth.c + test/sip/domain.c + test/sip/location.c + test/sip/sipsrv.c + test/sip/sipsrv.h + test/sip/user.c + test/srcs.mk + test/test.h + test/ua.c + test/video.c +Copyright: 2010, Creytiv.com + 2010-2013, Creytiv.com + 2010-2015, Creytiv.com + 2010-2016, Creytiv.com + 2010-2017, Creytiv.com + 2011, Creytiv.com + 2014, Creytiv.com + 2015, Creytiv.com + 2017, Creytiv.com +License: UNKNOWN + FIXME + +Files: debian/ARCHS_directfb + debian/TODO + debian/baresip-core.install + debian/baresip-core.lintian-overrides + debian/clean + debian/compat + debian/control + debian/control.in + debian/gbp.conf + debian/patches/1001_gcc7_compat.patch + debian/patches/2001_drop_libre_so_check.patch + debian/patches/README + debian/patches/series + debian/source/format + debian/watch + docs/ChangeLog + docs/THANKS + docs/TODO + docs/examples/accounts + docs/examples/config + docs/examples/contacts + mk/Doxyfile + mk/win32/baresip.sln + mk/win32/baresip.vcxproj + mk/win32/baresip.vcxproj.filters + mk/win32/static.c + modules/echo/echo.c + modules/echo/module.mk + modules/gst/README + modules/h265/README + modules/h265/TODO + modules/h265/notes + modules/mqtt/README.md + modules/mqtt/mqtt.h + modules/omx/README + modules/snapshot/png_vf.c + modules/snapshot/png_vf.h + modules/v4l2_codec/README + share/logo.png + test/test.c +Copyright: NONE +License: UNKNOWN + FIXME + +Files: modules/mpa/decode.c + modules/mpa/encode.c + modules/mpa/module.mk + modules/mpa/mpa.c + modules/mpa/mpa.h + modules/mpa/sdp.c +Copyright: 2016, Symonics GmbH +License: UNKNOWN + FIXME + +Files: modules/gtk/call_window.c + modules/gtk/dial_dialog.c + modules/gtk/gtk_mod.h + modules/gtk/transfer_dialog.c + modules/gtk/uri_entry.c +Copyright: 2015, Charles E. Lehner +License: UNKNOWN + FIXME + +Files: modules/gst_video/encode.c + modules/gst_video/gst_video.c + modules/gst_video/gst_video.h + modules/gst_video1/gst_video.c + modules/gst_video1/gst_video.h +Copyright: 2010, Creytiv.com + 2010-2013, Creytiv.com + 2010-2014, Creytiv.com + 2014, Fadeev Alexander +License: UNKNOWN + FIXME + +Files: modules/avahi/avahi.c + modules/omx/module.c + modules/omx/omx.c + modules/omx/omx.h +Copyright: 2010, Creytiv.com + 2016-2017, Creytiv.com + 2016-2017, Jonathan Sieber + 2017, Jonathan Sieber +License: UNKNOWN + FIXME + +Files: debian/archs-update + debian/copyright-check + debian/rules +Copyright: 2008, 2013-2014, 2017, Jonas Smedegaard + 2016-2017, Jonas Smedegaard + 2016-2018, Jonas Smedegaard +License: GPL-3+ + FIXME + +Files: modules/dtmfio/dtmfio.c +Copyright: 2014, Aaron Herting 'qwertos' +License: BSD-3-clause + FIXME + +Files: docs/COPYING +Copyright: 2010-2017, Alfred E. Heggestad + 2010-2017, Creytiv.com + 2010-2017, Richard Aas +License: BSD-3-clause + FIXME + +Files: modules/gtk/gtk_mod.c +Copyright: 2010-2015, Alfred E. Heggestad et al." + 2010-2015, Creytiv.com + 2015, Charles E. Lehner +License: BSD~unspecified + FIXME + +Files: modules/menu/menu.c +Copyright: 2010, Creytiv.com +License: BSD~unspecified + FIXME + +Files: rpm/baresip.spec +Copyright: NONE +License: BSD~unspecified + FIXME + +Files: src/main.c +Copyright: 2010-2015, Creytiv.com + 2010-2017, +License: UNKNOWN + FIXME + +Files: modules/directfb/directfb.c +Copyright: 2010, Creytiv.com + 2013, Andreas Shimokawa +License: UNKNOWN + FIXME + +Files: modules/directfb/module.mk +Copyright: 2010, Creytiv.com + 2013, Andreas Shimokawa . +License: UNKNOWN + FIXME + +Files: modules/gtk/module.mk +Copyright: 2010, Creytiv.com + 2015, Charles E. Lehner +License: UNKNOWN + FIXME + +Files: modules/dshow/dshow.cpp +Copyright: 2010, Creytiv.com + 2010, Dusan Stevanovic +License: UNKNOWN + FIXME + +Files: modules/gst_video1/encode.c +Copyright: 2010-2013, Creytiv.com + 2014, Fadeev Alexander + 2015, Thomas Strobel +License: UNKNOWN + FIXME + +Files: modules/presence/publisher.c +Copyright: 2010, Creytiv.com + 2014, Juha Heinanen +License: UNKNOWN + FIXME + +Files: modules/evdev/print.c +Copyright: 2010, Creytiv.com + n"); break; +License: UNKNOWN + FIXME + +Files: debian/source/lintian-overrides +Copyright: GPL-3+ + gpl-3+ +License: UNKNOWN + FIXME + diff --git a/gbp.conf b/gbp.conf new file mode 100644 index 0000000..0ba1661 --- /dev/null +++ b/gbp.conf @@ -0,0 +1,6 @@ +# Configuration file for git-buildpackage and friends + +[DEFAULT] +pristine-tar = True +sign-tags = True +filter = ['*/.git*', 'debian/*'] diff --git a/patches/1001_gcc7_compat.patch b/patches/1001_gcc7_compat.patch new file mode 100644 index 0000000..cce9643 --- /dev/null +++ b/patches/1001_gcc7_compat.patch @@ -0,0 +1,20 @@ +Description: Use -D_GNU_SOURCE for gcc-7 compatibility + baresip fails to build with gcc-7 because libdirectfb-dev needs to know + the size of struct timespec, which is an opaque type unless we're using + GNU extensions, but libre-dev sets -std=c99. Adjust cflags to enable + _GNU_SOURCE. +Author: Steve Langasek +Bug-Debian: https://bugs.debian.org/872406 +Last-Update: 2017-08-26 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +--- a/Makefile ++++ b/Makefile +@@ -49,6 +49,7 @@ + CFLAGS += -I. -Iinclude -I$(LIBRE_INC) -I$(SYSROOT)/include + CFLAGS += -I$(LIBREM_PATH)/include + CFLAGS += -I$(SYSROOT)/local/include/rem -I$(SYSROOT)/include/rem ++CFLAGS += -D_GNU_SOURCE + + CXXFLAGS += -I. -Iinclude -I$(LIBRE_INC) + CXXFLAGS += -I$(LIBREM_PATH)/include diff --git a/patches/2001_drop_libre_so_check.patch b/patches/2001_drop_libre_so_check.patch new file mode 100644 index 0000000..8d06f7e --- /dev/null +++ b/patches/2001_drop_libre_so_check.patch @@ -0,0 +1,49 @@ +Description: Drop checking for LIBRE_SO for empty + LIBRE_SO variable holds path for libre.so library. + This is not really required for package builds as + ld will search system paths for library. + . + This patch might not be suitable for upstream though. +Author: Vasudev Kamath +Last-Update: 2016-02-08 +--- +This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ +--- a/Makefile ++++ b/Makefile +@@ -162,17 +162,13 @@ + @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 $@ ++ $(HIDE)$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -lre $(LIBS) -o $@ + + $(STATICLIB): $(LIB_OBJS) + @echo " AR $@" +@@ -202,7 +198,7 @@ + $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ ../re/libre.a $(LIBS) -o $@ + else + $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ \ +- -L$(LIBRE_SO) -lre $(LIBS) -o $@ ++ -lre $(LIBS) -o $@ + endif + + +@@ -213,7 +209,7 @@ + $(TEST_BIN): $(STATICLIB) $(TEST_OBJS) + @echo " LD $@" + $(HIDE)$(CXX) $(LFLAGS) $(TEST_OBJS) \ +- -L$(LIBRE_SO) -L. \ ++ -L. \ + -l$(PROJECT) -lre $(LIBS) $(TEST_LIBS) -o $@ + + $(BUILD)/%.o: %.c $(BUILD) Makefile $(APP_MK) diff --git a/patches/README b/patches/README new file mode 100644 index 0000000..80c1584 --- /dev/null +++ b/patches/README @@ -0,0 +1,3 @@ +0xxx: Grabbed from upstream development. +1xxx: Possibly relevant for upstream adoption. +2xxx: Only relevant for official Debian release. diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..ebbb227 --- /dev/null +++ b/patches/series @@ -0,0 +1,2 @@ +2001_drop_libre_so_check.patch +1001_gcc7_compat.patch diff --git a/rules b/rules new file mode 100755 index 0000000..b8496ca --- /dev/null +++ b/rules @@ -0,0 +1,90 @@ +#!/usr/bin/make -f +# -*- mode: makefile; coding: utf-8 -*- +# Copyright 2016-2018 Jonas Smedegaard +# Description: Main Debian packaging script for baresip +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +include /usr/share/cdbs/1/class/makefile.mk +include /usr/share/cdbs/1/rules/debhelper.mk + +# Run this to update arch list: debian/rules clean DEB_MAINTAINER_MODE=1 +# (in maintainer mode only, uses network and messes with control file) +ifneq (,$(DEB_MAINTAINER_MODE)) +debian/control:: + debian/archs-update +endif +ARCHS_directfb := $(shell cat debian/ARCHS_directfb) + +# Needs and module hints for core package +core-deps = asound2 avahi-client codec2 directfb jack gsm1 opus +core-deps += opencore-amrnb opencore-amrwb png sdl2 sndfile1 sndio +core-deps += spandsp speex speexdsp twolame vpx ssl mosquitto +core-deps-extra = libevdev-dev [linux-any] +core-deps-extra +=, portaudio19-dev +core-mods-extra = b2bua codec2 echo snapshot sndio +core-mods-extra += $(if $(filter $(ARCHS_directfb),$(DEB_HOST_ARCH)),directfb) + +CDBS_BUILD_DEPENDS +=, $(patsubst %,$(comma) lib%-dev,$(core-deps)) +CDBS_BUILD_DEPENDS +=, $(core-deps-extra), pkg-config +CDBS_BUILD_DEPENDS +=, libre-dev (>= 0.5.5) +CDBS_BUILD_DEPENDS +=, librem-dev (>= 0.5.2) +EXTRA_MODULES += $(core-mods-extra) + +CDBS_DEPENDS_baresip +=, baresip-core + +# Needs and module hints for module packages +ffmpeg-deps = avcodec avformat avdevice swscale x265 +ffmpeg-mods = avformat avcodec h265 +ffmpeg-mods-extra = swscale +gstreamer-deps = gstreamer1.0 gstreamer-plugins-base1.0 +gstreamer-mods = gst1 gst_video1 +gtk-deps = gtk2.0 +gtk-mods = gtk +x11-deps = cairo2 omxil-bellagio mpg123 pulse xext x11 +x11-mods = cairo omx pulse rst sdl2 vidinfo x11 x11grab + +define MODULE_template = +CDBS_BUILD_DEPENDS +=, $$(patsubst %,$$(comma) lib%-dev,$$($1-deps)) +EXTRA_MODULES += $$($1-mods-extra) +DEB_DH_INSTALL_ARGS_baresip-$1 = \ + $$(patsubst %,usr/lib/baresip/modules/%.so,$$($1-mods) $$($1-mods-extra)) \ + usr/lib/baresip/modules +# Do not install these modules as part of baresip +DEB_DH_INSTALL_ARGS_baresip-core += \ + $$(patsubst %,-X%.so,$$($1-mods) $$($1-mods-extra)) +CDBS_RECOMMENDS_baresip +=, baresip-$1 +CDBS_RECOMMENDS_baresip-core +=, avahi-daemon +CDBS_SUGGESTS_baresip-core +=, baresip-$1 +CDBS_ENHANCES_baresip-$1 = baresip-core +endef +$(foreach module,ffmpeg gstreamer gtk x11,\ + $(eval $(call MODULE_template,$(module)))) + +DEB_MAKE_EXTRA_ARGS = V=1 PREFIX=/usr RELEASE=1 \ + EXTRA_MODULES="$(EXTRA_MODULES)" \ + EXTRA_CFLAGS="$(CFLAGS) $(CPPFLAGS)" \ + EXTRA_LFLAGS="$(LDFLAGS)" \ + $(DEB_MAKE_PARALLEL) + +DEB_MAKE_BUILD_TARGET = info all +DEB_MAKE_CHECK_TARGET = test +DEB_MAKE_INSTALL_TARGET = install DESTDIR=$(cdbs_make_curdestdir) + +DEB_INSTALL_CHANGELOGS_ALL = docs/ChangeLog +DEB_INSTALL_DOCS_ALL += README.md docs/TODO +DEB_INSTALL_EXAMPLES_baresip-core = docs/examples/* + +# LIBDIR for installation +LIBDIR=/usr/lib diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/source/lintian-overrides b/source/lintian-overrides new file mode 100644 index 0000000..e5f206a --- /dev/null +++ b/source/lintian-overrides @@ -0,0 +1,3 @@ +# License is in License-Reference field (see bug#786450) +missing-license-paragraph-in-dep5-copyright gpl-3\+ * +missing-license-text-in-dep5-copyright GPL-3\+ * diff --git a/watch b/watch new file mode 100644 index 0000000..cdbfa8b --- /dev/null +++ b/watch @@ -0,0 +1,5 @@ +# run "uscan --report" to check or "gpb import-orig --uscan" to update +version=4 +opts="filenamemangle=s%(?:.*?)?v?(@ANY_VERSION@@ARCHIVE_EXT@)%@PACKAGE@-$1%" \ + https://github.com/alfredh/baresip/tags \ + (?:.*?/)?v?@ANY_VERSION@@ARCHIVE_EXT@ -- cgit v1.2.3 From 766bb4acdda738e450630c92d9c37e6cb42d9423 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Mon, 8 Jan 2018 22:22:59 +0530 Subject: Import baresip_0.5.7.orig.tar.gz [dgit import orig baresip_0.5.7.orig.tar.gz] --- .gitignore | 25 + .travis.yml | 30 + Makefile | 305 ++++++ README.md | 441 ++++++++ debian/changelog | 192 ++++ debian/compat | 1 + debian/control | 34 + debian/copyright | 37 + debian/dirs | 3 + debian/docs | 2 + debian/libbaresip-dev.dirs | 3 + debian/libbaresip-dev.files | 2 + debian/libbaresip.dirs | 1 + debian/libbaresip.files | 1 + debian/rules | 90 ++ docs/COPYING | 31 + docs/ChangeLog | 1611 +++++++++++++++++++++++++++++ docs/THANKS | 47 + docs/TODO | 11 + docs/examples/accounts | 58 ++ docs/examples/config | 177 ++++ docs/examples/contacts | 11 + include/baresip.h | 1211 ++++++++++++++++++++++ mk/Doxyfile | 238 +++++ mk/mod.mk | 105 ++ mk/modules.mk | 467 +++++++++ mk/win32/baresip.sln | 39 + mk/win32/baresip.vcxproj | 204 ++++ mk/win32/baresip.vcxproj.filters | 366 +++++++ mk/win32/static.c | 38 + modules/account/account.c | 213 ++++ modules/account/module.mk | 10 + modules/alsa/alsa.c | 184 ++++ modules/alsa/alsa.h | 20 + modules/alsa/alsa_play.c | 169 ++++ modules/alsa/alsa_src.c | 174 ++++ modules/alsa/module.mk | 11 + modules/amr/amr.c | 344 +++++++ modules/amr/amr.h | 10 + modules/amr/module.mk | 64 ++ modules/amr/sdp.c | 56 + modules/aubridge/aubridge.c | 67 ++ modules/aubridge/aubridge.h | 41 + modules/aubridge/device.c | 187 ++++ modules/aubridge/module.mk | 11 + modules/aubridge/play.c | 58 ++ modules/aubridge/src.c | 61 ++ modules/audiounit/audiounit.c | 97 ++ modules/audiounit/audiounit.h | 27 + modules/audiounit/module.mk | 14 + modules/audiounit/player.c | 212 ++++ modules/audiounit/recorder.c | 263 +++++ modules/audiounit/sess.c | 174 ++++ modules/aufile/aufile.c | 265 +++++ modules/aufile/module.mk | 11 + modules/auloop/auloop.c | 413 ++++++++ modules/auloop/module.mk | 10 + modules/av1/av1.c | 51 + modules/av1/av1.h | 20 + modules/av1/decode.c | 293 ++++++ modules/av1/encode.c | 259 +++++ modules/av1/module.mk | 13 + modules/avahi/avahi.c | 389 +++++++ modules/avahi/module.mk | 12 + modules/avcapture/avcapture.m | 398 ++++++++ modules/avcapture/module.mk | 11 + modules/avcodec/avcodec.c | 258 +++++ modules/avcodec/avcodec.h | 60 ++ modules/avcodec/decode.c | 556 ++++++++++ modules/avcodec/encode.c | 805 +++++++++++++++ modules/avcodec/h263.c | 188 ++++ modules/avcodec/h26x.h | 104 ++ modules/avcodec/module.mk | 21 + modules/avformat/avformat.c | 448 ++++++++ modules/avformat/module.mk | 12 + modules/b2bua/b2bua.c | 246 +++++ modules/b2bua/module.mk | 10 + modules/bv32/bv32.c | 180 ++++ modules/bv32/module.mk | 11 + modules/cairo/cairo.c | 345 +++++++ modules/cairo/module.mk | 12 + modules/codec2/codec2.c | 202 ++++ modules/codec2/module.mk | 11 + modules/cons/cons.c | 274 +++++ modules/cons/module.mk | 10 + modules/contact/contact.c | 255 +++++ modules/contact/module.mk | 10 + modules/coreaudio/coreaudio.c | 111 ++ modules/coreaudio/coreaudio.h | 18 + modules/coreaudio/module.mk | 13 + modules/coreaudio/player.c | 165 +++ modules/coreaudio/recorder.c | 176 ++++ modules/daala/daala.c | 63 ++ modules/daala/daala.h | 20 + modules/daala/decode.c | 181 ++++ modules/daala/encode.c | 292 ++++++ modules/daala/module.mk | 13 + modules/debug_cmd/debug_cmd.c | 164 +++ modules/debug_cmd/module.mk | 10 + modules/directfb/directfb.c | 190 ++++ modules/directfb/module.mk | 14 + modules/dshow/dshow.cpp | 536 ++++++++++ modules/dshow/module.mk | 11 + modules/dtls_srtp/dtls.c | 51 + modules/dtls_srtp/dtls_srtp.c | 507 ++++++++++ modules/dtls_srtp/dtls_srtp.h | 33 + modules/dtls_srtp/module.mk | 11 + modules/dtls_srtp/srtp.c | 150 +++ modules/dtmfio/dtmfio.c | 149 +++ modules/dtmfio/module.mk | 12 + modules/echo/echo.c | 158 +++ modules/echo/module.mk | 9 + modules/evdev/evdev.c | 348 +++++++ modules/evdev/module.mk | 12 + modules/evdev/print.c | 513 ++++++++++ modules/evdev/print.h | 11 + modules/fakevideo/fakevideo.c | 199 ++++ modules/fakevideo/module.mk | 11 + modules/g711/g711.c | 133 +++ modules/g711/module.mk | 10 + modules/g722/g722.c | 191 ++++ modules/g722/module.mk | 11 + modules/g7221/decode.c | 70 ++ modules/g7221/encode.c | 71 ++ modules/g7221/g7221.c | 50 + modules/g7221/g7221.h | 29 + modules/g7221/module.mk | 14 + modules/g7221/sdp.c | 54 + modules/g726/g726.c | 208 ++++ modules/g726/module.mk | 11 + modules/gsm/gsm.c | 178 ++++ modules/gsm/module.mk | 12 + modules/gst/README | 34 + modules/gst/dump.c | 65 ++ modules/gst/gst.c | 454 +++++++++ modules/gst/gst.h | 9 + modules/gst/module.mk | 12 + modules/gst1/gst.c | 464 +++++++++ modules/gst1/module.mk | 13 + modules/gst_video/encode.c | 540 ++++++++++ modules/gst_video/gst_video.c | 64 ++ modules/gst_video/gst_video.h | 24 + modules/gst_video/module.mk | 13 + modules/gst_video/sdp.c | 56 + modules/gst_video1/encode.c | 613 +++++++++++ modules/gst_video1/gst_video.c | 66 ++ modules/gst_video1/gst_video.h | 24 + modules/gst_video1/module.mk | 13 + modules/gst_video1/sdp.c | 57 ++ modules/gtk/call_window.c | 522 ++++++++++ modules/gtk/dial_dialog.c | 96 ++ modules/gtk/gtk_mod.c | 1053 +++++++++++++++++++ modules/gtk/gtk_mod.h | 51 + modules/gtk/module.mk | 22 + modules/gtk/transfer_dialog.c | 134 +++ modules/gtk/uri_entry.c | 45 + modules/gzrtp/gzrtp.cpp | 220 ++++ modules/gzrtp/messages.cpp | 256 +++++ modules/gzrtp/module.mk | 38 + modules/gzrtp/session.cpp | 214 ++++ modules/gzrtp/session.h | 50 + modules/gzrtp/srtp.cpp | 327 ++++++ modules/gzrtp/srtp.h | 46 + modules/gzrtp/stream.cpp | 707 +++++++++++++ modules/gzrtp/stream.h | 138 +++ modules/h265/README | 29 + modules/h265/TODO | 5 + modules/h265/decode.c | 337 ++++++ modules/h265/encode.c | 292 ++++++ modules/h265/fmt.c | 165 +++ modules/h265/h265.c | 67 ++ modules/h265/h265.h | 70 ++ modules/h265/module.mk | 11 + modules/h265/notes | 75 ++ modules/httpd/httpd.c | 181 ++++ modules/httpd/module.mk | 10 + modules/ice/ice.c | 989 ++++++++++++++++++ modules/ice/module.mk | 10 + modules/ilbc/ilbc.c | 357 +++++++ modules/ilbc/module.mk | 11 + modules/isac/isac.c | 226 +++++ modules/isac/module.mk | 11 + modules/jack/jack.c | 43 + modules/jack/jack_play.c | 239 +++++ modules/jack/jack_src.c | 234 +++++ modules/jack/mod_jack.h | 14 + modules/jack/module.mk | 12 + modules/l16/l16.c | 104 ++ modules/l16/module.mk | 10 + modules/libsrtp/module.mk | 11 + modules/libsrtp/sdes.c | 46 + modules/libsrtp/sdes.h | 22 + modules/libsrtp/srtp.c | 474 +++++++++ modules/menu/menu.c | 1162 +++++++++++++++++++++ modules/menu/module.mk | 10 + modules/mpa/decode.c | 217 ++++ modules/mpa/encode.c | 196 ++++ modules/mpa/module.mk | 14 + modules/mpa/mpa.c | 205 ++++ modules/mpa/mpa.h | 37 + modules/mpa/sdp.c | 55 + modules/mqtt/README.md | 59 ++ modules/mqtt/module.mk | 14 + modules/mqtt/mqtt.c | 157 +++ modules/mqtt/mqtt.h | 26 + modules/mqtt/publish.c | 104 ++ modules/mqtt/subscribe.c | 146 +++ modules/mwi/module.mk | 10 + modules/mwi/mwi.c | 225 +++++ modules/natbd/module.mk | 10 + modules/natbd/natbd.c | 509 ++++++++++ modules/natpmp/libnatpmp.c | 235 +++++ modules/natpmp/libnatpmp.h | 53 + modules/natpmp/module.mk | 10 + modules/natpmp/natpmp.c | 387 +++++++ modules/omx/README | 17 + modules/omx/module.c | 137 +++ modules/omx/module.mk | 23 + modules/omx/omx.c | 349 +++++++ modules/omx/omx.h | 46 + modules/opengl/module.mk | 11 + modules/opengl/opengl.m | 534 ++++++++++ modules/opengles/context.m | 115 +++ modules/opengles/module.mk | 16 + modules/opengles/opengles.c | 297 ++++++ modules/opengles/opengles.h | 28 + modules/opensles/module.mk | 13 + modules/opensles/opensles.c | 79 ++ modules/opensles/opensles.h | 18 + modules/opensles/player.c | 203 ++++ modules/opensles/recorder.c | 214 ++++ modules/opus/decode.c | 101 ++ modules/opus/encode.c | 187 ++++ modules/opus/module.mk | 14 + modules/opus/opus.c | 173 ++++ modules/opus/opus.h | 37 + modules/opus/sdp.c | 51 + modules/oss/module.mk | 18 + modules/oss/oss.c | 364 +++++++ modules/pcp/listener.c | 124 +++ modules/pcp/module.mk | 12 + modules/pcp/pcp.c | 365 +++++++ modules/pcp/pcp.h | 15 + modules/plc/module.mk | 11 + modules/plc/plc.c | 120 +++ modules/portaudio/module.mk | 11 + modules/portaudio/portaudio.c | 346 +++++++ modules/presence/module.mk | 11 + modules/presence/notifier.c | 219 ++++ modules/presence/presence.c | 123 +++ modules/presence/presence.h | 19 + modules/presence/publisher.c | 309 ++++++ modules/presence/subscriber.c | 382 +++++++ modules/pulse/module.mk | 14 + modules/pulse/player.c | 153 +++ modules/pulse/pulse.c | 54 + modules/pulse/pulse.h | 14 + modules/pulse/recorder.c | 193 ++++ modules/qtcapture/module.mk | 11 + modules/qtcapture/qtcapture.m | 402 ++++++++ modules/rst/audio.c | 273 +++++ modules/rst/module.mk | 14 + modules/rst/rst.c | 423 ++++++++ modules/rst/rst.h | 26 + modules/rst/video.c | 280 +++++ modules/sdl/module.mk | 18 + modules/sdl/sdl.c | 327 ++++++ modules/sdl/sdl.h | 9 + modules/sdl/util.c | 54 + modules/sdl2/module.mk | 11 + modules/sdl2/sdl.c | 343 +++++++ modules/selfview/module.mk | 11 + modules/selfview/selfview.c | 280 +++++ modules/silk/module.mk | 11 + modules/silk/silk.c | 262 +++++ modules/snapshot/module.mk | 11 + modules/snapshot/png_vf.c | 189 ++++ modules/snapshot/png_vf.h | 6 + modules/snapshot/snapshot.c | 104 ++ modules/sndfile/module.mk | 11 + modules/sndfile/sndfile.c | 200 ++++ modules/sndio/module.mk | 11 + modules/sndio/sndio.c | 319 ++++++ modules/speex/module.mk | 12 + modules/speex/speex.c | 519 ++++++++++ modules/speex_aec/module.mk | 15 + modules/speex_aec/speex_aec.c | 229 +++++ modules/speex_pp/module.mk | 15 + modules/speex_pp/speex_pp.c | 161 +++ modules/srtp/module.mk | 11 + modules/srtp/sdes.c | 45 + modules/srtp/sdes.h | 22 + modules/srtp/srtp.c | 420 ++++++++ modules/stdio/module.mk | 11 + modules/stdio/stdio.c | 193 ++++ modules/stun/module.mk | 10 + modules/stun/stun.c | 252 +++++ modules/swscale/module.mk | 11 + modules/swscale/swscale.c | 197 ++++ modules/syslog/module.mk | 10 + modules/syslog/syslog.c | 76 ++ modules/turn/module.mk | 10 + modules/turn/turn.c | 301 ++++++ modules/uuid/module.mk | 10 + modules/uuid/uuid.c | 117 +++ modules/v4l/module.mk | 11 + modules/v4l/v4l.c | 270 +++++ modules/v4l2/module.mk | 14 + modules/v4l2/v4l2.c | 513 ++++++++++ modules/v4l2_codec/README | 41 + modules/v4l2_codec/module.mk | 11 + modules/v4l2_codec/v4l2_codec.c | 603 +++++++++++ modules/vidbridge/disp.c | 94 ++ modules/vidbridge/module.mk | 11 + modules/vidbridge/src.c | 93 ++ modules/vidbridge/vidbridge.c | 78 ++ modules/vidbridge/vidbridge.h | 47 + modules/vidinfo/module.mk | 12 + modules/vidinfo/panel.c | 289 ++++++ modules/vidinfo/vidinfo.c | 177 ++++ modules/vidinfo/vidinfo.h | 42 + modules/vidloop/module.mk | 10 + modules/vidloop/vidloop.c | 505 +++++++++ modules/vp8/decode.c | 290 ++++++ modules/vp8/encode.c | 272 +++++ modules/vp8/module.mk | 14 + modules/vp8/sdp.c | 39 + modules/vp8/vp8.c | 62 ++ modules/vp8/vp8.h | 30 + modules/vp9/decode.c | 287 ++++++ modules/vp9/encode.c | 317 ++++++ modules/vp9/module.mk | 14 + modules/vp9/sdp.c | 39 + modules/vp9/vp9.c | 64 ++ modules/vp9/vp9.h | 30 + modules/vumeter/module.mk | 11 + modules/vumeter/vumeter.c | 203 ++++ modules/wincons/module.mk | 11 + modules/wincons/wincons.c | 217 ++++ modules/winwave/module.mk | 11 + modules/winwave/play.c | 236 +++++ modules/winwave/src.c | 226 +++++ modules/winwave/winwave.c | 60 ++ modules/winwave/winwave.h | 20 + modules/x11/module.mk | 12 + modules/x11/x11.c | 452 +++++++++ modules/x11grab/module.mk | 12 + modules/x11grab/x11grab.c | 223 ++++ modules/zrtp/module.mk | 13 + modules/zrtp/zrtp.c | 661 ++++++++++++ rpm/baresip.spec | 51 + share/busy.wav | Bin 0 -> 64320 bytes share/callwaiting.wav | Bin 0 -> 80418 bytes share/error.wav | Bin 0 -> 16776 bytes share/logo.png | Bin 0 -> 49325 bytes share/message.wav | Bin 0 -> 6356 bytes share/notfound.wav | Bin 0 -> 16568 bytes share/ring.wav | Bin 0 -> 12180 bytes share/ringback.wav | Bin 0 -> 68084 bytes src/account.c | 703 +++++++++++++ src/aucodec.c | 66 ++ src/audio.c | 2081 ++++++++++++++++++++++++++++++++++++++ src/aufilt.c | 28 + src/aulevel.c | 85 ++ src/auplay.c | 109 ++ src/ausrc.c | 108 ++ src/baresip.c | 248 +++++ src/bfcp.c | 199 ++++ src/call.c | 1877 ++++++++++++++++++++++++++++++++++ src/cmd.c | 768 ++++++++++++++ src/conf.c | 386 +++++++ src/config.c | 925 +++++++++++++++++ src/contact.c | 338 +++++++ src/core.h | 541 ++++++++++ src/event.c | 171 ++++ src/h264.c | 182 ++++ src/log.c | 153 +++ src/magic.h | 38 + src/main.c | 265 +++++ src/mctrl.c | 44 + src/menc.c | 65 ++ src/message.c | 192 ++++ src/metric.c | 90 ++ src/mnat.c | 90 ++ src/module.c | 258 +++++ src/mos.c | 63 ++ src/net.c | 561 ++++++++++ src/play.c | 352 +++++++ src/realtime.c | 100 ++ src/reg.c | 265 +++++ src/rtpext.c | 112 ++ src/rtpkeep.c | 165 +++ src/sdp.c | 191 ++++ src/sipreq.c | 150 +++ src/srcs.mk | 55 + src/stream.c | 733 ++++++++++++++ src/ua.c | 1906 ++++++++++++++++++++++++++++++++++ src/ui.c | 195 ++++ src/vidcodec.c | 126 +++ src/video.c | 1421 ++++++++++++++++++++++++++ src/vidfilt.c | 103 ++ src/vidisp.c | 132 +++ src/vidsrc.c | 125 +++ test/account.c | 69 ++ test/aulevel.c | 61 ++ test/call.c | 906 +++++++++++++++++ test/cmd.c | 161 +++ test/contact.c | 55 + test/cplusplus.cpp | 22 + test/main.c | 271 +++++ test/message.c | 232 +++++ test/mock/cert.c | 82 ++ test/mock/dnssrv.c | 245 +++++ test/mock/mock_aucodec.c | 94 ++ test/mock/mock_auplay.c | 102 ++ test/mock/mock_ausrc.c | 91 ++ test/mock/mock_vidcodec.c | 210 ++++ test/mock/mock_vidisp.c | 97 ++ test/mock/mock_vidsrc.c | 91 ++ test/mos.c | 53 + test/net.c | 46 + test/play.c | 99 ++ test/sip/aor.c | 98 ++ test/sip/auth.c | 91 ++ test/sip/domain.c | 187 ++++ test/sip/location.c | 188 ++++ test/sip/sipsrv.c | 330 ++++++ test/sip/sipsrv.h | 122 +++ test/sip/user.c | 73 ++ test/srcs.mk | 54 + test/test.c | 109 ++ test/test.h | 224 ++++ test/ua.c | 714 +++++++++++++ test/video.c | 38 + 434 files changed, 74411 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/docs create mode 100644 debian/libbaresip-dev.dirs create mode 100644 debian/libbaresip-dev.files create mode 100644 debian/libbaresip.dirs create mode 100644 debian/libbaresip.files create mode 100755 debian/rules create mode 100644 docs/COPYING create mode 100644 docs/ChangeLog create mode 100644 docs/THANKS create mode 100644 docs/TODO create mode 100644 docs/examples/accounts create mode 100644 docs/examples/config create mode 100644 docs/examples/contacts create mode 100644 include/baresip.h create mode 100644 mk/Doxyfile create mode 100644 mk/mod.mk create mode 100644 mk/modules.mk create mode 100644 mk/win32/baresip.sln create mode 100644 mk/win32/baresip.vcxproj create mode 100644 mk/win32/baresip.vcxproj.filters create mode 100644 mk/win32/static.c create mode 100644 modules/account/account.c create mode 100644 modules/account/module.mk create mode 100644 modules/alsa/alsa.c create mode 100644 modules/alsa/alsa.h create mode 100644 modules/alsa/alsa_play.c create mode 100644 modules/alsa/alsa_src.c create mode 100644 modules/alsa/module.mk create mode 100644 modules/amr/amr.c create mode 100644 modules/amr/amr.h create mode 100644 modules/amr/module.mk create mode 100644 modules/amr/sdp.c create mode 100644 modules/aubridge/aubridge.c create mode 100644 modules/aubridge/aubridge.h create mode 100644 modules/aubridge/device.c create mode 100644 modules/aubridge/module.mk create mode 100644 modules/aubridge/play.c create mode 100644 modules/aubridge/src.c create mode 100644 modules/audiounit/audiounit.c create mode 100644 modules/audiounit/audiounit.h create mode 100644 modules/audiounit/module.mk create mode 100644 modules/audiounit/player.c create mode 100644 modules/audiounit/recorder.c create mode 100644 modules/audiounit/sess.c create mode 100644 modules/aufile/aufile.c create mode 100644 modules/aufile/module.mk create mode 100644 modules/auloop/auloop.c create mode 100644 modules/auloop/module.mk create mode 100644 modules/av1/av1.c create mode 100644 modules/av1/av1.h create mode 100644 modules/av1/decode.c create mode 100644 modules/av1/encode.c create mode 100644 modules/av1/module.mk create mode 100644 modules/avahi/avahi.c create mode 100644 modules/avahi/module.mk create mode 100644 modules/avcapture/avcapture.m create mode 100644 modules/avcapture/module.mk create mode 100644 modules/avcodec/avcodec.c create mode 100644 modules/avcodec/avcodec.h create mode 100644 modules/avcodec/decode.c create mode 100644 modules/avcodec/encode.c create mode 100644 modules/avcodec/h263.c create mode 100644 modules/avcodec/h26x.h create mode 100644 modules/avcodec/module.mk create mode 100644 modules/avformat/avformat.c create mode 100644 modules/avformat/module.mk create mode 100644 modules/b2bua/b2bua.c create mode 100644 modules/b2bua/module.mk create mode 100644 modules/bv32/bv32.c create mode 100644 modules/bv32/module.mk create mode 100644 modules/cairo/cairo.c create mode 100644 modules/cairo/module.mk create mode 100644 modules/codec2/codec2.c create mode 100644 modules/codec2/module.mk create mode 100644 modules/cons/cons.c create mode 100644 modules/cons/module.mk create mode 100644 modules/contact/contact.c create mode 100644 modules/contact/module.mk create mode 100644 modules/coreaudio/coreaudio.c create mode 100644 modules/coreaudio/coreaudio.h create mode 100644 modules/coreaudio/module.mk create mode 100644 modules/coreaudio/player.c create mode 100644 modules/coreaudio/recorder.c create mode 100644 modules/daala/daala.c create mode 100644 modules/daala/daala.h create mode 100644 modules/daala/decode.c create mode 100644 modules/daala/encode.c create mode 100644 modules/daala/module.mk create mode 100644 modules/debug_cmd/debug_cmd.c create mode 100644 modules/debug_cmd/module.mk create mode 100644 modules/directfb/directfb.c create mode 100644 modules/directfb/module.mk create mode 100644 modules/dshow/dshow.cpp create mode 100644 modules/dshow/module.mk create mode 100644 modules/dtls_srtp/dtls.c create mode 100644 modules/dtls_srtp/dtls_srtp.c create mode 100644 modules/dtls_srtp/dtls_srtp.h create mode 100644 modules/dtls_srtp/module.mk create mode 100644 modules/dtls_srtp/srtp.c create mode 100644 modules/dtmfio/dtmfio.c create mode 100644 modules/dtmfio/module.mk create mode 100644 modules/echo/echo.c create mode 100644 modules/echo/module.mk create mode 100644 modules/evdev/evdev.c create mode 100644 modules/evdev/module.mk create mode 100644 modules/evdev/print.c create mode 100644 modules/evdev/print.h create mode 100644 modules/fakevideo/fakevideo.c create mode 100644 modules/fakevideo/module.mk create mode 100644 modules/g711/g711.c create mode 100644 modules/g711/module.mk create mode 100644 modules/g722/g722.c create mode 100644 modules/g722/module.mk create mode 100644 modules/g7221/decode.c create mode 100644 modules/g7221/encode.c create mode 100644 modules/g7221/g7221.c create mode 100644 modules/g7221/g7221.h create mode 100644 modules/g7221/module.mk create mode 100644 modules/g7221/sdp.c create mode 100644 modules/g726/g726.c create mode 100644 modules/g726/module.mk create mode 100644 modules/gsm/gsm.c create mode 100644 modules/gsm/module.mk create mode 100644 modules/gst/README create mode 100644 modules/gst/dump.c create mode 100644 modules/gst/gst.c create mode 100644 modules/gst/gst.h create mode 100644 modules/gst/module.mk create mode 100644 modules/gst1/gst.c create mode 100644 modules/gst1/module.mk create mode 100644 modules/gst_video/encode.c create mode 100644 modules/gst_video/gst_video.c create mode 100644 modules/gst_video/gst_video.h create mode 100644 modules/gst_video/module.mk create mode 100644 modules/gst_video/sdp.c create mode 100644 modules/gst_video1/encode.c create mode 100644 modules/gst_video1/gst_video.c create mode 100644 modules/gst_video1/gst_video.h create mode 100644 modules/gst_video1/module.mk create mode 100644 modules/gst_video1/sdp.c create mode 100644 modules/gtk/call_window.c create mode 100644 modules/gtk/dial_dialog.c create mode 100644 modules/gtk/gtk_mod.c create mode 100644 modules/gtk/gtk_mod.h create mode 100644 modules/gtk/module.mk create mode 100644 modules/gtk/transfer_dialog.c create mode 100644 modules/gtk/uri_entry.c create mode 100644 modules/gzrtp/gzrtp.cpp create mode 100644 modules/gzrtp/messages.cpp create mode 100644 modules/gzrtp/module.mk create mode 100644 modules/gzrtp/session.cpp create mode 100644 modules/gzrtp/session.h create mode 100644 modules/gzrtp/srtp.cpp create mode 100644 modules/gzrtp/srtp.h create mode 100644 modules/gzrtp/stream.cpp create mode 100644 modules/gzrtp/stream.h create mode 100644 modules/h265/README create mode 100644 modules/h265/TODO create mode 100644 modules/h265/decode.c create mode 100644 modules/h265/encode.c create mode 100644 modules/h265/fmt.c create mode 100644 modules/h265/h265.c create mode 100644 modules/h265/h265.h create mode 100644 modules/h265/module.mk create mode 100644 modules/h265/notes create mode 100644 modules/httpd/httpd.c create mode 100644 modules/httpd/module.mk create mode 100644 modules/ice/ice.c create mode 100644 modules/ice/module.mk create mode 100644 modules/ilbc/ilbc.c create mode 100644 modules/ilbc/module.mk create mode 100644 modules/isac/isac.c create mode 100644 modules/isac/module.mk create mode 100644 modules/jack/jack.c create mode 100644 modules/jack/jack_play.c create mode 100644 modules/jack/jack_src.c create mode 100644 modules/jack/mod_jack.h create mode 100644 modules/jack/module.mk create mode 100644 modules/l16/l16.c create mode 100644 modules/l16/module.mk create mode 100644 modules/libsrtp/module.mk create mode 100644 modules/libsrtp/sdes.c create mode 100644 modules/libsrtp/sdes.h create mode 100644 modules/libsrtp/srtp.c create mode 100644 modules/menu/menu.c create mode 100644 modules/menu/module.mk create mode 100644 modules/mpa/decode.c create mode 100644 modules/mpa/encode.c create mode 100644 modules/mpa/module.mk create mode 100644 modules/mpa/mpa.c create mode 100644 modules/mpa/mpa.h create mode 100644 modules/mpa/sdp.c create mode 100644 modules/mqtt/README.md create mode 100644 modules/mqtt/module.mk create mode 100644 modules/mqtt/mqtt.c create mode 100644 modules/mqtt/mqtt.h create mode 100644 modules/mqtt/publish.c create mode 100644 modules/mqtt/subscribe.c create mode 100644 modules/mwi/module.mk create mode 100644 modules/mwi/mwi.c create mode 100644 modules/natbd/module.mk create mode 100644 modules/natbd/natbd.c create mode 100644 modules/natpmp/libnatpmp.c create mode 100644 modules/natpmp/libnatpmp.h create mode 100644 modules/natpmp/module.mk create mode 100644 modules/natpmp/natpmp.c create mode 100644 modules/omx/README create mode 100644 modules/omx/module.c create mode 100644 modules/omx/module.mk create mode 100644 modules/omx/omx.c create mode 100644 modules/omx/omx.h create mode 100644 modules/opengl/module.mk create mode 100644 modules/opengl/opengl.m create mode 100644 modules/opengles/context.m create mode 100644 modules/opengles/module.mk create mode 100644 modules/opengles/opengles.c create mode 100644 modules/opengles/opengles.h create mode 100644 modules/opensles/module.mk create mode 100644 modules/opensles/opensles.c create mode 100644 modules/opensles/opensles.h create mode 100644 modules/opensles/player.c create mode 100644 modules/opensles/recorder.c create mode 100644 modules/opus/decode.c create mode 100644 modules/opus/encode.c create mode 100644 modules/opus/module.mk create mode 100644 modules/opus/opus.c create mode 100644 modules/opus/opus.h create mode 100644 modules/opus/sdp.c create mode 100644 modules/oss/module.mk create mode 100644 modules/oss/oss.c create mode 100644 modules/pcp/listener.c create mode 100644 modules/pcp/module.mk create mode 100644 modules/pcp/pcp.c create mode 100644 modules/pcp/pcp.h create mode 100644 modules/plc/module.mk create mode 100644 modules/plc/plc.c create mode 100644 modules/portaudio/module.mk create mode 100644 modules/portaudio/portaudio.c create mode 100644 modules/presence/module.mk create mode 100644 modules/presence/notifier.c create mode 100644 modules/presence/presence.c create mode 100644 modules/presence/presence.h create mode 100644 modules/presence/publisher.c create mode 100644 modules/presence/subscriber.c create mode 100644 modules/pulse/module.mk create mode 100644 modules/pulse/player.c create mode 100644 modules/pulse/pulse.c create mode 100644 modules/pulse/pulse.h create mode 100644 modules/pulse/recorder.c create mode 100644 modules/qtcapture/module.mk create mode 100644 modules/qtcapture/qtcapture.m create mode 100644 modules/rst/audio.c create mode 100644 modules/rst/module.mk create mode 100644 modules/rst/rst.c create mode 100644 modules/rst/rst.h create mode 100644 modules/rst/video.c create mode 100644 modules/sdl/module.mk create mode 100644 modules/sdl/sdl.c create mode 100644 modules/sdl/sdl.h create mode 100644 modules/sdl/util.c create mode 100644 modules/sdl2/module.mk create mode 100644 modules/sdl2/sdl.c create mode 100644 modules/selfview/module.mk create mode 100644 modules/selfview/selfview.c create mode 100644 modules/silk/module.mk create mode 100644 modules/silk/silk.c create mode 100644 modules/snapshot/module.mk create mode 100644 modules/snapshot/png_vf.c create mode 100644 modules/snapshot/png_vf.h create mode 100644 modules/snapshot/snapshot.c create mode 100644 modules/sndfile/module.mk create mode 100644 modules/sndfile/sndfile.c create mode 100644 modules/sndio/module.mk create mode 100644 modules/sndio/sndio.c create mode 100644 modules/speex/module.mk create mode 100644 modules/speex/speex.c create mode 100644 modules/speex_aec/module.mk create mode 100644 modules/speex_aec/speex_aec.c create mode 100644 modules/speex_pp/module.mk create mode 100644 modules/speex_pp/speex_pp.c create mode 100644 modules/srtp/module.mk create mode 100644 modules/srtp/sdes.c create mode 100644 modules/srtp/sdes.h create mode 100644 modules/srtp/srtp.c create mode 100644 modules/stdio/module.mk create mode 100644 modules/stdio/stdio.c create mode 100644 modules/stun/module.mk create mode 100644 modules/stun/stun.c create mode 100644 modules/swscale/module.mk create mode 100644 modules/swscale/swscale.c create mode 100644 modules/syslog/module.mk create mode 100644 modules/syslog/syslog.c create mode 100644 modules/turn/module.mk create mode 100644 modules/turn/turn.c create mode 100644 modules/uuid/module.mk create mode 100644 modules/uuid/uuid.c create mode 100644 modules/v4l/module.mk create mode 100644 modules/v4l/v4l.c create mode 100644 modules/v4l2/module.mk create mode 100644 modules/v4l2/v4l2.c create mode 100644 modules/v4l2_codec/README create mode 100644 modules/v4l2_codec/module.mk create mode 100644 modules/v4l2_codec/v4l2_codec.c create mode 100644 modules/vidbridge/disp.c create mode 100644 modules/vidbridge/module.mk create mode 100644 modules/vidbridge/src.c create mode 100644 modules/vidbridge/vidbridge.c create mode 100644 modules/vidbridge/vidbridge.h create mode 100644 modules/vidinfo/module.mk create mode 100644 modules/vidinfo/panel.c create mode 100644 modules/vidinfo/vidinfo.c create mode 100644 modules/vidinfo/vidinfo.h create mode 100644 modules/vidloop/module.mk create mode 100644 modules/vidloop/vidloop.c create mode 100644 modules/vp8/decode.c create mode 100644 modules/vp8/encode.c create mode 100644 modules/vp8/module.mk create mode 100644 modules/vp8/sdp.c create mode 100644 modules/vp8/vp8.c create mode 100644 modules/vp8/vp8.h create mode 100644 modules/vp9/decode.c create mode 100644 modules/vp9/encode.c create mode 100644 modules/vp9/module.mk create mode 100644 modules/vp9/sdp.c create mode 100644 modules/vp9/vp9.c create mode 100644 modules/vp9/vp9.h create mode 100644 modules/vumeter/module.mk create mode 100644 modules/vumeter/vumeter.c create mode 100644 modules/wincons/module.mk create mode 100644 modules/wincons/wincons.c create mode 100644 modules/winwave/module.mk create mode 100644 modules/winwave/play.c create mode 100644 modules/winwave/src.c create mode 100644 modules/winwave/winwave.c create mode 100644 modules/winwave/winwave.h create mode 100644 modules/x11/module.mk create mode 100644 modules/x11/x11.c create mode 100644 modules/x11grab/module.mk create mode 100644 modules/x11grab/x11grab.c create mode 100644 modules/zrtp/module.mk create mode 100644 modules/zrtp/zrtp.c create mode 100644 rpm/baresip.spec create mode 100644 share/busy.wav create mode 100644 share/callwaiting.wav create mode 100644 share/error.wav create mode 100644 share/logo.png create mode 100644 share/message.wav create mode 100644 share/notfound.wav create mode 100644 share/ring.wav create mode 100644 share/ringback.wav create mode 100644 src/account.c create mode 100644 src/aucodec.c create mode 100644 src/audio.c create mode 100644 src/aufilt.c create mode 100644 src/aulevel.c create mode 100644 src/auplay.c create mode 100644 src/ausrc.c create mode 100644 src/baresip.c create mode 100644 src/bfcp.c create mode 100644 src/call.c create mode 100644 src/cmd.c create mode 100644 src/conf.c create mode 100644 src/config.c create mode 100644 src/contact.c create mode 100644 src/core.h create mode 100644 src/event.c create mode 100644 src/h264.c create mode 100644 src/log.c create mode 100644 src/magic.h create mode 100644 src/main.c create mode 100644 src/mctrl.c create mode 100644 src/menc.c create mode 100644 src/message.c create mode 100644 src/metric.c create mode 100644 src/mnat.c create mode 100644 src/module.c create mode 100644 src/mos.c create mode 100644 src/net.c create mode 100644 src/play.c create mode 100644 src/realtime.c create mode 100644 src/reg.c create mode 100644 src/rtpext.c create mode 100644 src/rtpkeep.c create mode 100644 src/sdp.c create mode 100644 src/sipreq.c create mode 100644 src/srcs.mk create mode 100644 src/stream.c create mode 100644 src/ua.c create mode 100644 src/ui.c create mode 100644 src/vidcodec.c create mode 100644 src/video.c create mode 100644 src/vidfilt.c create mode 100644 src/vidisp.c create mode 100644 src/vidsrc.c create mode 100644 test/account.c create mode 100644 test/aulevel.c create mode 100644 test/call.c create mode 100644 test/cmd.c create mode 100644 test/contact.c create mode 100644 test/cplusplus.cpp create mode 100644 test/main.c create mode 100644 test/message.c create mode 100644 test/mock/cert.c create mode 100644 test/mock/dnssrv.c create mode 100644 test/mock/mock_aucodec.c create mode 100644 test/mock/mock_auplay.c create mode 100644 test/mock/mock_ausrc.c create mode 100644 test/mock/mock_vidcodec.c create mode 100644 test/mock/mock_vidisp.c create mode 100644 test/mock/mock_vidsrc.c create mode 100644 test/mos.c create mode 100644 test/net.c create mode 100644 test/play.c create mode 100644 test/sip/aor.c create mode 100644 test/sip/auth.c create mode 100644 test/sip/domain.c create mode 100644 test/sip/location.c create mode 100644 test/sip/sipsrv.c create mode 100644 test/sip/sipsrv.h create mode 100644 test/sip/user.c create mode 100644 test/srcs.mk create mode 100644 test/test.c create mode 100644 test/test.h create mode 100644 test/ua.c create mode 100644 test/video.c 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 " >> $@ + @echo "#include " >> $@ + @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 Mon, 25 Des 2017 10:00:00 +0100 + +baresip (0.5.6) unstable; urgency=medium + + * version 0.5.6 + + -- Alfred E. Heggestad Sat, 14 Oct 2017 10:00:00 +0200 + +baresip (0.5.5) unstable; urgency=medium + + * version 0.5.5 + + -- Alfred E. Heggestad Thu, 7 Sep 2017 16:00:00 +0200 + +baresip (0.5.4) unstable; urgency=medium + + * version 0.5.4 + + -- Alfred E. Heggestad Sat, 24 Jun 2017 12:00:00 +0200 + +baresip (0.5.3) unstable; urgency=medium + + * version 0.5.3 + + -- Alfred E. Heggestad Sun, 14 May 2017 07:00:00 +0200 + +baresip (0.5.2) unstable; urgency=medium + + * version 0.5.2 + + -- Alfred E. Heggestad Fri, 7 Apr 2017 19:00:00 +0200 + +baresip (0.5.1) unstable; urgency=medium + + * version 0.5.1 + + -- Alfred E. Heggestad Sat, 4 Mar 2017 10:00:00 +0100 + +baresip (0.5.0) unstable; urgency=medium + + * version 0.5.0 + + -- Alfred E. Heggestad Fri, 23 Dec 2016 18:00:00 +0100 + +baresip (0.4.20) unstable; urgency=medium + + * version 0.4.20 + + -- Alfred E. Heggestad Fri, 22 July 2016 20:00:00 +0100 + +baresip (0.4.19) unstable; urgency=medium + + * version 0.4.19 + + -- Alfred E. Heggestad Fri, 20 May 2016 20:00:00 +0100 + +baresip (0.4.18) unstable; urgency=medium + + * version 0.4.18 + + -- Alfred E. Heggestad Sat, 12 Mar 2016 18:00:00 +0100 + +baresip (0.4.17) unstable; urgency=low + + * version 0.4.17 + + -- Alfred E. Heggestad Sun, 17 Jan 2016 12:00:00 +0100 + +baresip (0.4.16) unstable; urgency=low + + * version 0.4.16 + + -- Alfred E. Heggestad Tue, 1 Dec 2015 12:00:00 +0100 + +baresip (0.4.15) unstable; urgency=low + + * version 0.4.15 + + -- Alfred E. Heggestad Sat, 26 Sep 2015 12:00:00 +0100 + +baresip (0.4.14) unstable; urgency=low + + * version 0.4.14 + + -- Alfred E. Heggestad Sat, 8 Aug 2015 12:00:00 +0100 + + +baresip (0.4.13) unstable; urgency=low + + * version 0.4.13 + + -- Alfred E. Heggestad Sat, 20 Jun 2015 20:00:00 +0100 + +baresip (0.4.12) unstable; urgency=low + + * version 0.4.12 + + -- Alfred E. Heggestad Wed, 24 Dec 2014 14:00:00 +0100 + +baresip (0.4.11) unstable; urgency=low + + * version 0.4.11 + + -- Alfred E. Heggestad Sat, 21 Jun 2014 14:00:00 +0100 + +baresip (0.4.10) unstable; urgency=low + + * version 0.4.10 + + -- Alfred E. Heggestad Thu, 23 Jan 2014 16:00:00 +0100 + +baresip (0.4.9) unstable; urgency=low + + * version 0.4.9 + + -- Alfred E. Heggestad Mon, 6 Jan 2014 16:00:00 +0100 + +baresip (0.4.8) unstable; urgency=low + + * version 0.4.8 + + -- Alfred E. Heggestad Fri, 6 Dec 2013 23:00:00 +0100 + +baresip (0.4.7) unstable; urgency=low + + * version 0.4.7 + + -- Alfred E. Heggestad Tue, 12 Nov 2013 22:00:00 +0100 + +baresip (0.4.6) unstable; urgency=low + + * version 0.4.6 + + -- Alfred E. Heggestad Fri, 11 Oct 2013 20:00:00 +0100 + +baresip (0.4.5) unstable; urgency=low + + * version 0.4.5 + + -- Alfred E. Heggestad Sat, 31 Aug 2013 18:00:00 +0100 + +baresip (0.4.4) unstable; urgency=low + + * version 0.4.4 + + -- Alfred E. Heggestad Sat, 18 May 2013 10:00:00 +0100 + +baresip (0.4.3) unstable; urgency=low + + * version 0.4.3 + + -- Alfred E. Heggestad Tue, 1 Jan 2013 01:01:00 +0100 + +baresip (0.4.2) unstable; urgency=low + + * version 0.4.2 + + -- Alfred E. Heggestad Sun, 9 Sept 2012 09:09:00 +0100 + +baresip (0.4.1) unstable; urgency=low + + * version 0.4.1 + + -- Alfred E. Heggestad Sat, 21 Apr 2012 21:04:00 +0100 + +baresip (0.4.0) unstable; urgency=low + + * version 0.4.0 + + -- Alfred E. Heggestad Sun, 25 Dec 2011 12:25:00 +0100 + +baresip (0.3.0) unstable; urgency=low + + * version 0.3.0 + + -- Alfred E. Heggestad Wed, 7 Sept 2011 07:11:00 +0100 + +baresip (0.2.0) unstable; urgency=low + + * version 0.2.0 + + -- Alfred E. Heggestad Fri, 20 May 2011 20:05:00 +0100 + +baresip (0.1.0) unstable; urgency=low + + * version 0.1.0 + + -- Alfred E. Heggestad 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 +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 + +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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 ;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: +# +# +# +# +# + + +# +# A very basic example +# + + + +# +# Use SIP Outbound over TCP, with ICE for Media NAT Traversal, and DTLS-SRTP for encryption +# +;sipnat=outbound;outbound="sip:example.com;transport=tcp";medianat=ice;mediaenc=dtls_srtp + + +# +# Use ICE for Media NAT Traversal, using a specific STUN-server +# +;medianat=ice;stunserver="stun:username:password@stunserver.org" + + +# +# Force audio-codec 'opus' and video-codec 'vp8' +# +;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 ;addr-params +# +# addr-params: +# ;presence={none,p2p} +# + +"Echo Server" +"alfredh" ;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 + * + *
+      +---------------+
+      |0|1|2|3|4|5|6|7|
+      +-+-+-+-+-+-+-+-+
+      |F|NRI|  Type   |
+      +---------------+
+ * 
+ */ +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 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + baresip-win32 + {4B89C2D8-FB32-4D7C-9019-752A5664781C} + Win32Proj + 8.1 + + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ..\..\$(Platform)\$(Configuration)\bin\ + ..\..\$(Platform)\$(Configuration)\tmp\mk\win32\ + false + ..\..\$(Platform)\$(Configuration)\bin\ + ..\..\$(Platform)\$(Configuration)\tmp\mk\win32\ + false + + + + Disabled + include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories) + WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + EditAndContinue + CompileAsC + 4142;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir)%(Filename).obj + + + winmm.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions) + $(OutDir)baresip-win32.exe + %(AdditionalLibraryDirectories) + true + $(OutDir)baresip-win32.pdb + Console + MachineX86 + %(AdditionalDependencies) + + + + + include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories) + WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + ProgramDatabase + 4142;%(DisableSpecificWarnings) + $(IntDir)%(RelativeDir)%(Filename).obj + + + winmm.lib wsock32.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions) + $(OutDir)baresip-win32.exe + %(AdditionalLibraryDirectories) + true + Console + true + true + MachineX86 + %(AdditionalDependencies) + + + + + {3e767371-a72b-4f5c-a695-8f844b0889c5} + + + {40b28df6-4b4a-411a-9eb7-8d80c2a29b9d} + + + + + + + + + + + + + + + + + + + + + + + + CompileAsCpp + CompileAsCpp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + {2334866b-8357-4e18-8898-48db4dfe2a15} + + + {5c30c989-28e5-402f-aaa0-860eafa95cc5} + + + {f5f435ce-121f-4b09-b66f-89e75e8abb40} + + + {7f749aed-9d81-46c6-bdde-690ddc51a0a4} + + + {6c97676c-7c18-404f-85a1-2fded4f104e3} + + + {f528695e-cf16-4c35-89ed-adcc330b63bc} + + + {b8e32268-7d2c-46ae-b6a1-d7b02bbd4026} + + + {0e11ace7-27c2-4ad3-8b5e-b3bf772f0b4d} + + + {f44d6650-15a0-4fb3-8fe3-d91f522be831} + + + {6606d763-a0be-4bcc-a689-a9e5e38df7f5} + + + {785597c1-f018-4f86-ae98-4855e6130bf7} + + + {b139606b-86de-4c90-af91-f44b04bd1330} + + + {4c01b3b6-a748-4cf1-af45-a8d5b7735267} + + + {3c2cd0c3-9de0-42ca-8f3a-6d7d929ce501} + + + {e679754c-643a-4284-9ed6-bf6fdc73e152} + + + {8ca10960-9be4-4d19-9e32-2ae5bc415c0a} + + + {1def0f02-ca40-49ee-b5e1-95aac5d2dda3} + + + {5446737f-2a0f-4b4f-a86e-55d9c20118e7} + + + {ebbcb368-d1fa-4899-80cf-e43ff4fde9b2} + + + {a7b0f22a-b82a-4594-a367-d8b8a9882f0d} + + + {7ceb961c-1033-4259-8f3e-ffa2d28a0f12} + + + {c093ccc3-d662-46e0-83df-2eb34e7e7d21} + + + {c82f29fb-bb8d-45c7-ad53-d25e997c1453} + + + {ed5cc330-666d-4f9a-81a2-db7873b7abaa} + + + {31ac6861-a9e9-467a-82ae-53d1d929e696} + + + {6e5dac26-781e-4683-958f-2e18fdc0b42e} + + + {5a3c72cb-a1a5-4c46-b0b5-761d3677e56a} + + + {a4d5f1d9-5629-479e-86d4-4e7b6d58a38a} + + + {044bc40b-b2a8-4450-b76a-d959559df6b1} + + + {71dc9cb5-19b1-4f74-bc23-02ca6b5a10b3} + + + {bc7810ea-bc52-4bee-a15c-aed12cf2a777} + + + {8e7e8b8a-dbe7-4b58-80f3-e632b40d0e4e} + + + {e3bd480b-289e-4754-b1a2-55ea85a7ef45} + + + + + include + + + src + + + src + + + modules\amr + + + modules\natpmp + + + modules\presence + + + modules\srtp + + + modules\vidbridge + + + modules\winwave + + + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src\video + + + src\video + + + src\video + + + src\video + + + src\video + + + src\video + + + src\video + + + src\video + + + src\static + + + modules\account + + + modules\amr + + + modules\amr + + + modules\auloop + + + modules\b2bua + + + modules\cons + + + modules\contact + + + modules\debug_cmd + + + modules\dshow + + + modules\echo + + + modules\g711 + + + modules\httpd + + + modules\ice + + + modules\l16 + + + modules\menu + + + modules\mwi + + + modules\natbd + + + modules\natpmp + + + modules\natpmp + + + modules\presence + + + modules\presence + + + modules\presence + + + modules\presence + + + modules\selfview + + + modules\srtp + + + modules\srtp + + + modules\stun + + + modules\turn + + + modules\uuid + + + modules\vidbridge + + + modules\vidbridge + + + modules\vidbridge + + + modules\vidloop + + + modules\vumeter + + + modules\wincons + + + modules\winwave + + + modules\winwave + + + modules\winwave + + + \ 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 +#include + +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 +#include + + +/** + * @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" + "User 2 with stored password" + "User 2 with ICE" ;medianat=ice + "User 3 with IPv6" + \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 ;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" + "# \n" + "# \n" + "# \n" + "#\n" + "#\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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#ifdef AMR_NB +#include +#include +#endif +#ifdef AMR_WB +#ifdef _TYPEDEF_H +#define typedef_h +#endif +#include +#include +#endif +#include +#include +#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 +#include +#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 +#include +#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 +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#include + + +/** + * @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; iend/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 +#include +#include +#include + + +/** + * @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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include + +#include +#include +#include +#include + +/* for if_nametoindex */ +#include + +/* gethostname, getaddrinfo */ +#define __USE_XOPEN2K +#include +#include + + +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), ";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\" ;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 +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#ifdef USE_X264 +#include +#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 ; e.g. h264_nvenc, h264_videotoolbox + avcodec_h264dec ; 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 +#include +#include +#include +#include +#include +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) +#include +#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) ? "" : " ", + 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 +#include +#include +#include +#include +#if LIBAVUTIL_VERSION_INT >= ((50<<16)+(29<<8)+0) +#include +#else +#include +#endif +#ifdef USE_X264 +#include +#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; ilinesize[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= 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 +#include +#include +#include +#ifdef USE_X264 +#include +#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 +#include +#include +#include +#include +#include +#define FF_API_OLD_METADATA 0 +#include +#include +#include +#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0) +#include +#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; iic->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 +#include + + +/** + * @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 +#include +#include +#include + + +/* + * 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; ibsc, &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; ibsd); + 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 +#include +#include +#include +#include +#include +#include + + +#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 +#include +#include +#include + + +/** + * @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 +#include + + +/** + * @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 +#include +#include + + +/** + * @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 ;addr-params\n" + "#\n" + "# addr-params:\n" + "# ;presence={none,p2p}\n" + "# ;access={allow,block}\n" + "#\n" + "\n" + "\n" + "\"Echo Server\" \n" + "\"%s\" ;presence=p2p\n" + "\n" + "# Access rules\n" + "#\"Catch All\" ;access=block\n" + "\"Good Friend\" ;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 +#include +#include +#include +#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 +#include +#include +#include +#include +#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; ibuf); 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; ibuf); 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 +#include +#include +#include +#include +#include +#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; ibuf); 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; ibuf); 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 +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#ifdef USE_OPENSSL +#include +#endif +#include +#include + + +/** + * @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 + */ +#include +#include +#include +#include + + +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 . +# + +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 +#include +#include +#include +#include +#include + +#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 +#include +#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 +#include +#include +#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 + ;mediaenc=dtls_srtp + ;mediaenc=dtls_srtpf + ;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 +#include +#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' */ +/* 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 +#include +#include +#include +#include +#include + + +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 +# + +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 +#include + +/** + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include + + +/** + * @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 +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include + + +/** + * @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 +#include + +#define G722_1_EXPOSE_INTERNAL_STRUCTURES + +#include +#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 +#include + +#define G722_1_EXPOSE_INTERNAL_STRUCTURES + +#include +#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 +#include +#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 +#include +#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 +#include +#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1 +#include + + +/** + * @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 /* please report if you have problems finding this file */ +#include +#include + + +/** + * @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 +#include +#include +#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 +#include +#define __USE_POSIX199309 +#include +#include +#include +#include +#include +#include +#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 + * + *
+ *                ptime=variable             ptime=20ms
+ *  .-----------. N kHz          .---------. N kHz
+ *  |           | 1-2 channels   |         | 1-2 channels
+ *  | Gstreamer |--------------->|Packetize|-------------> [read handler]
+ *  |           |                |         |
+ *  '-----------'                '---------'
+ *
+ * 
+ */ +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: + * + *
+ *  .--------------.    .------------------------------------------.
+ *  |    playbin   |    |mybin    .------------.   .------------.  |
+ *  |----.    .----|    |-----.   | capsfilter |   |  fakesink  |  |
+ *  |sink|    |src |--->|ghost|   |----.   .---|   |----.   .---|  |    handoff
+ *  |----'    '----|    |pad  |-->|sink|   |src|-->|sink|   |src|--+--> handler
+ *  |              |    |-----'   '------------'   '------------'  |
+ *  '--------------'    '------------------------------------------'
+ * 
+ * + * @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 +#include +#include +#include +#include +#include +#include +#include + + +/** + * @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 + * + *
+ *                ptime=variable             ptime=20ms
+ *  .-----------. N kHz          .---------. N kHz
+ *  |           | 1-2 channels   |         | 1-2 channels
+ *  | Gstreamer |--------------->|Packetize|-------------> [read handler]
+ *  |           |                |         |
+ *  '-----------'                '---------'
+ *
+ * 
+ */ +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: + * + *
+ *  .--------------.    .------------------------------------------.
+ *  |    playbin   |    |mybin    .------------.   .------------.  |
+ *  |----.    .----|    |-----.   | capsfilter |   |  fakesink  |  |
+ *  |sink|    |src |--->|ghost|   |----.   .---|   |----.   .---|  |    handoff
+ *  |----'    '----|    |pad  |-->|sink|   |src|-->|sink|   |src|--+--> handler
+ *  |              |    |-----'   '------------'   '------------'  |
+ *  '--------------'    '------------------------------------------'
+ * 
+ * + * @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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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: + * + *
+ *  .--------.   .-----------.   .----------.
+ *  | appsrc |   |  x264enc  |   | appsink  |
+ *  |   .----|   |----.  .---|   |----.     |
+ *  |   |src |-->|sink|  |src|-->|sink|-----+-->handoff
+ *  |   '----|   |----'  '---|   |----'     |   handler
+ *  '--------'   '-----------'   '----------'
+ * 
+ */ +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 +#include +#include +#include +#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 +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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: + * + *
+ *  .--------.   .-----------.   .----------.
+ *  | appsrc |   |  x264enc  |   | appsink  |
+ *  |   .----|   |----.  .---|   |----.     |
+ *  |   |src |-->|sink|  |src|-->|sink|-----+-->handoff
+ *  |   '----|   |----'  '---|   |----'     |   handler
+ *  '--------'   '-----------'   '----------'
+ * 
+ */ +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 +#include +#include +#include +#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 +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include "gtk_mod.h" + +#ifdef USE_LIBNOTIFY +#include +#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; iavg_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 +#include +#include +#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 +#include +#include +#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 + +#include +#include + +#include + +#include + +#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::cmd_verify_sas }, + {"zrtp_unverify", 0, CMD_PRM, "Unverify ZRTP SAS ", + 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 + +#include +#include + +#include + +#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 + +#include +#include + +#include "session.h" + + +std::vector 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::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::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::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::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::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 s_sessl; + + const bool m_start_parallel; + int m_id; + std::vector 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 + +#include +#include + +#ifdef GZRTP_USE_RE_SRTP +#include +#else +#include +#include +#include +#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 + + +#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 +#include + +#include +#include + +#include +#include + +#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; +} + + +// + + + 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 + + +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 +#include +#include +#include +#include +#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) ? "" : " ", + 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 +#include +#include +#include +#include +#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; ipayload; + size_t len = nal->sizeBytes; + bool marker; + +#if 0 + debug("h265: encode: %s type=%2d %s\n", + h265_is_keyframe(nal->type) ? "" : " ", + 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 +#include +#include +#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 +#include +#include +#include +#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 +#include + + +/** + * @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, + "\n" + "\n" + "Baresip v" BARESIP_VERSION "\n" + "\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" + "\n" + "
\n"
+			  "%H"
+			  "
\n" + "\n" + "\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 +#include +#endif +#include +#include + + +/** + * @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 +#include +#include +#include +#include + + +/** + * @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; ienc); /* (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; insamp; 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 +#include +#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 +#include +#include +#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 +#include +#include +#include +#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; chprm.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; chprm.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 +#include +#include +#include +#include +#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; chprm.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; chprm.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 +#include + + +/** + * @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 +#include +#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: [] + */ +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 +#include +#include +#include +#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: [] */ +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 +#include +#include +#include + + +/** + * @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 ", 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 +#include +#include +#include +#include +#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)iintermediate_buffer[i]; + *sampc = n; + } + else { + for (i=0;(unsigned)iintermediate_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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include "mpa.h" +#include + +/** + * @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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include + + +/** + * @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 +#include + + +/** + * @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 +#include +#include +#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<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 +#include +#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; icompc; 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; icompc; 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; icompc; 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 + +#include +#include +#include + +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 +#include +#include + +#include +#include +#include + +/* Avoids a VideoCore header warning about clock_gettime() */ +#include +#include + +/** + * @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 +#include +#include +#else +#include +#include +#include + +#undef OMX_VERSION +#define OMX_VERSION 0x01010101 +#define OMX_ERROR_NONE 0 +#endif + +#include +#include +#include + +/* Needed for usleep to appear */ +#define _BSD_SOURCE +#include + +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 +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#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; isampv[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; isampv[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 +#include +#include +#include +#include +#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; isampv[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; isampv[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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#if defined(NETBSD) || defined(OPENBSD) +#include +#elif defined (LINUX) +#include +#else +#include +#endif +#ifdef SOLARIS +#include +#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; irun) { + 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 +#include +#include +#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 +#include +#include +#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; icompc; 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; icompc; 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; icompc; 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; icompc; 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 +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include + + +/** + * @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; iname); + (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 +#include +#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, + "\r\n" + "\r\n" + " \r\n" + " \r\n" + " \r\n" + " %s\r\n" + " \r\n" + " %s\r\n" + " \r\n" + "\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 +#include +#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 +#include +#include +#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, + "\r\n" + "\r\n" + " \r\n" + " \r\n" + " \r\n" + " %s\r\n" + " \r\n" + " %s\r\n" + " \r\n" + "\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 +#include +#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), + "[^<]+", 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), + "", NULL)) { + + status = PRESENCE_CLOSED; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "", NULL)) { + + status = PRESENCE_BUSY; + } + else if (!re_regex((const char *)mbuf_buf(msg->mb), + mbuf_get_left(msg->mb), + "", 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 +#include +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include + + +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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#include + + +/** + * @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 +#include +#include +#include + + +/** + * @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 +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#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: [] + */ +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 +#include +#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 + ;mediaenc=srtp + ;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: [] */ +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 +#include +#include +#include +#include +#include + + +/** + * @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 +#include + + +/** + * @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 +#include +#include +#include + + +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 +#include +#include + + +/** + * @defgroup syslog syslog + * + * This module implements a logging handler for output to syslog + */ + + +#define DEBUG_MODULE "" +#define DEBUG_LEVEL 0 +#include + + +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 +#include + + +/** + * @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 +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include +#include +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include +#include +#include +#include +#include +#include +#include +#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */ +#include +#include +#include +#include +#if defined (OPENBSD) || defined (NETBSD) +#include +#else +#include +#endif + +#ifdef HAVE_LIBV4L2 +#include +#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_buffersn_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; in_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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined (OPENBSD) || defined (NETBSD) +#include +#else +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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; ysize.h; y++) { + + for (x=0; xsize.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; irrdc; 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 +#include +#include +#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 + + +#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 +#include +#include +#include +#include + + +/** + * @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 ", 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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#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 +#include +#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 +#include +#include +#include +#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 +#include +#include +#include + + +/** + * @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 +#include +#include + + +/** + * @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; irun = 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 +#include +#include +#include +#include +#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; iwaveout = 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 +#include +#include +#include +#include +#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; iwavein = 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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +#ifndef SOLARIS +#define _XOPEN_SOURCE 1 +#endif +#include +#include +#include +#include +#include +#include + + +/** + * @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 +#include +#include + +#include + +/** + * @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 ", 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 +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 - +- Initial build. + diff --git a/share/busy.wav b/share/busy.wav new file mode 100644 index 0000000..9d0ffec Binary files /dev/null and b/share/busy.wav differ diff --git a/share/callwaiting.wav b/share/callwaiting.wav new file mode 100644 index 0000000..c9bf9b2 Binary files /dev/null and b/share/callwaiting.wav differ diff --git a/share/error.wav b/share/error.wav new file mode 100644 index 0000000..483daca Binary files /dev/null and b/share/error.wav differ diff --git a/share/logo.png b/share/logo.png new file mode 100644 index 0000000..cf8a57c Binary files /dev/null and b/share/logo.png differ diff --git a/share/message.wav b/share/message.wav new file mode 100644 index 0000000..bd91094 Binary files /dev/null and b/share/message.wav differ diff --git a/share/notfound.wav b/share/notfound.wav new file mode 100644 index 0000000..285c61d Binary files /dev/null and b/share/notfound.wav differ diff --git a/share/ring.wav b/share/ring.wav new file mode 100644 index 0000000..09ec1bd Binary files /dev/null and b/share/ring.wav differ diff --git a/share/ringback.wav b/share/ringback.wav new file mode 100644 index 0000000..d21e20e Binary files /dev/null and b/share/ringback.wav differ 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 +#include +#include +#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; ioutboundv); 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; ioutboundv); 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; ioutboundv); 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 +#include +#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 +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_PTHREAD +#include +#endif +#include +#include +#include +#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. + * + *
+ *            write  read
+ *              |    /|\
+ *             \|/    |
+ * .------.   .---------.    .-------.
+ * |filter|<--|  audio  |--->|encoder|
+ * '------'   |         |    |-------|
+ *            | object  |--->|decoder|
+ *            '---------'    '-------'
+ *              |    /|\
+ *              |     |
+ *             \|/    |
+ *         .------. .-----.
+ *         |auplay| |ausrc|
+ *         '------' '-----'
+ *
+ */ + +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; iextmap_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 +#include +#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 +#include +#include +#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 +#include +#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 +#include +#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 +#include +#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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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; numvidmode : 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 +#include +#include +#include +#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; icmdc; 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; icmdc; 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; ikey) { + 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; icmdc; 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; icmdc; 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 +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_IO_H +#include +#endif +#include +#include +#include +#include "core.h" + + +#define DEBUG_MODULE "" +#define DEBUG_LEVEL 0 +#include + + +#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 +#include +#include +#include +#include +#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 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 \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 +#include +#include + + +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 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 + + +/* 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 +#include +#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 +#include +#include +#include + + +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 +#include + + +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 +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_GETOPT +#include +#endif +#include +#include + + +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 Execute commands (repeat)\n" + "\t-f Config path\n" + "\t-m Pre-load modules (repeat)\n" + "\t-p Audio files\n" + "\t-h -? Help\n" + "\t-t Test and exit\n" + "\t-u 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 +#include +#include "core.h" + + +/* + * RFC 5168 XML Schema for Media Control + * note: deprecated, use RTCP FIR instead + * + * + * Example XML Document: + * + *
+
+   
+     
+       
+         
+           
+	   
+	 
+       
+     
+
+  
+ */ +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 +#include +#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 +#include +#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 +#include +#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 +#include +#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 +#include +#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 +#include +#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 +#include +#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; insn; i++) { + srvv[i] = net->nsv[i]; + } + + *n = net->nsn; + + if (from_sys) + *from_sys = false; + } + else { + if (nsn > *n) + return E2BIG; + + for (i=0; idnsc, 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; insc; 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; iaf; +} + + +/** + * 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 +#include +#include +#include +#include +#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; ipos = 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 +#include +#ifdef DARWIN +#include +#include +#include +#include +#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 +#include +#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 +#include +#include +#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 +#include +#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 +#include +#include +#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 +#include +#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 +#include +#include +#include +#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; ipos = 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 +#include +#include +#include "core.h" +#include + + +/** 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=\"\"", + 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; iacc->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; iextensionc; 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; linenumcalls, 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; iextensionc; 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 +#include +#include +#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; il; 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 +#include + + +/** + * 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 +#include +#include +#include +#include +#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. + * + *
+ *            recv  send
+ *              |    /|\
+ *             \|/    |
+ *            .---------.    .-------.
+ *            |  video  |--->|encoder|
+ *            |         |    |-------|
+ *            | object  |--->|decoder|
+ *            '---------'    '-------'
+ *              |    /|\
+ *              |     |
+ *             \|/    |
+ *        .-------.  .-------.
+ *        |Video  |  |Video  |
+ *        |Display|  |Source |
+ *        '-------'  '-------'
+ *
+ */ + +/** + * 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 +#include +#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 +#include +#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 +#include +#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 +#include +#include +#include "test.h" + + +#define DEBUG_MODULE "account" +#define DEBUG_LEVEL 5 +#include + + +static const char str[] = + "\"Mr User\" " + ";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 +#include +#include "test.h" + + +#define DEBUG_MODULE "aulevel" +#define DEBUG_LEVEL 5 +#include + + +#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 +#include +#include +#include +#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 ;regint=0" prm); \ + TEST_ERR(err); \ + err = ua_alloc(&f->b.ua, \ + "B ;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; ia.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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#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\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 +#include +#include +#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 ;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 +#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 +#include +#include "../test.h" + + +#define DEBUG_MODULE "mock/dnssrv" +#define DEBUG_LEVEL 5 +#include + + +#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 +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 +#include +#include "test.h" + + +#define DEBUG_MODULE "mos" +#define DEBUG_LEVEL 5 +#include + + +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 +#include +#include +#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 +#include +#include +#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; ipos = 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 +#include +#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 +#include +#include +#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; isecret; + + 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 +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include + + +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 +#include +#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 +#include +#include +#include +#include "../test.h" +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include + + +#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, "", + &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 +#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 +#include +#include +#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= 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 +#include +#include +#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; isrvv); 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; isrvc; 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 ;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, ";regint=0;abc"); + err |= ua_alloc(&ua2, ";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; isip, &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), "", + 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), + ";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; iinstance = 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), "", + 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; in_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 ;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 +#include +#include "test.h" + + +#define DEBUG_MODULE "video" +#define DEBUG_LEVEL 5 +#include + + +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; +} -- cgit v1.2.3 From f55ecebeead135c2159ea0b89bd1887c789802ca Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Mon, 8 Jan 2018 22:22:59 +0530 Subject: Drop checking for LIBRE_SO for empty LIBRE_SO variable holds path for libre.so library. This is not really required for package builds as ld will search system paths for library. This patch might not be suitable for upstream though. Last-Update: 2016-02-08 Gbp-Pq: Name 2001_drop_libre_so_check.patch --- Makefile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index f5b4c25..7df1eae 100644 --- a/Makefile +++ b/Makefile @@ -162,17 +162,13 @@ 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 $@ + $(HIDE)$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -lre $(LIBS) -o $@ $(STATICLIB): $(LIB_OBJS) @echo " AR $@" @@ -202,7 +198,7 @@ ifneq ($(GPROF),) $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ ../re/libre.a $(LIBS) -o $@ else $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ \ - -L$(LIBRE_SO) -lre $(LIBS) -o $@ + -lre $(LIBS) -o $@ endif @@ -213,7 +209,7 @@ test: $(TEST_BIN) $(TEST_BIN): $(STATICLIB) $(TEST_OBJS) @echo " LD $@" $(HIDE)$(CXX) $(LFLAGS) $(TEST_OBJS) \ - -L$(LIBRE_SO) -L. \ + -L. \ -l$(PROJECT) -lre $(LIBS) $(TEST_LIBS) -o $@ $(BUILD)/%.o: %.c $(BUILD) Makefile $(APP_MK) -- cgit v1.2.3 From edc500d5bf9d851366b47009741d09efc185722d Mon Sep 17 00:00:00 2001 From: Steve Langasek Date: Mon, 8 Jan 2018 22:22:59 +0530 Subject: Use -D_GNU_SOURCE for gcc-7 compatibility baresip fails to build with gcc-7 because libdirectfb-dev needs to know the size of struct timespec, which is an opaque type unless we're using GNU extensions, but libre-dev sets -std=c99. Adjust cflags to enable _GNU_SOURCE. Bug-Debian: https://bugs.debian.org/872406 Last-Update: 2017-08-26 Gbp-Pq: Name 1001_gcc7_compat.patch --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 7df1eae..2061e9e 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ 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 +CFLAGS += -D_GNU_SOURCE CXXFLAGS += -I. -Iinclude -I$(LIBRE_INC) CXXFLAGS += -I$(LIBREM_PATH)/include -- cgit v1.2.3