diff --git a/.gitignore b/.gitignore
index e94e83e..19a7e0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,14 @@
*.o
*.lo
*.la
+*.pyc
+*.gcda
+*.gcov
+*.txt
+imgurbash.sh.*
+.coverage
+.gdb_history
+.idea
/INSTALL
/Makefile
/Makefile.in
@@ -17,7 +25,16 @@
/config.sub
/configure
/depcomp
+/docs/_build
+/gstreamer
+/gst-plugins-base
+/gst-plugins-good
+/gst-plugins-bad
+/gst-plugins-base
+/gst-switch.sublime-*
+/libvpx
/html
+/htmlcov
/install-sh
/libtool
/ltmain.sh
@@ -27,6 +44,10 @@
/plugins/.libs/
/plugins/Makefile
/plugins/Makefile.in
+/python-api/imgurbash.sh*
+/python-api/.cache/
+/python-api/reports/
+/python-api/htmlcov/
/stamp-h1
/tools/.deps/
/tools/Makefile
@@ -45,7 +66,9 @@
/timestamped*
/frames
/transition-strips
-/record*.dat
-*.data
-*.log
/tools/gst-switch-ptz
+test-driver
+*.dat*
+*.avi
+*.mkv
+*.log
diff --git a/.travis-run.sh b/.travis-run.sh
new file mode 100755
index 0000000..03ad9d6
--- /dev/null
+++ b/.travis-run.sh
@@ -0,0 +1,67 @@
+#! /bin/bash -ex
+
+./autogen.sh --prefix=/usr || {
+ printf "Failed to do autogen!!!\n"
+ exit -1
+}
+make clean
+make || {
+ printf "make of gst-switch failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of gst-switch failed!!!\n"
+ exit -1
+}
+
+cd python-api
+
+case $TEST in
+
+ python-api )
+ case $TYPE in
+ unittest )
+ make unittests || {
+ printf "Unittests failed!\n"
+ exit -1
+ }
+ coveralls || {
+ printf "Coveralls failed!\n"
+ exit -1
+ }
+ ;;
+ integration )
+ make integration || {
+ printf "Integration tests failed!\n"
+ exit -1
+ }
+ coveralls || {
+ printf "Coveralls failed!\n"
+ exit -1
+ }
+ ;;
+ esac
+ ;;
+ module )
+ case $TYPE in
+ python )
+ make test || {
+ printf "Tests failed!\n"
+ exit -1
+ }
+ coveralls || {
+ printf "Coveralls failed!\n"
+ exit -1
+ }
+ ;;
+ c )
+ make test || {
+ printf "Tests failed!\n"
+ exit -1
+ }
+ coveralls -n -r ../tools
+ esac
+esac || {
+ printf "Failed!\n!\n"
+ exit -1
+}
diff --git a/.travis-setup.sh b/.travis-setup.sh
new file mode 100644
index 0000000..378760e
--- /dev/null
+++ b/.travis-setup.sh
@@ -0,0 +1,28 @@
+#! /bin/bash -ex
+
+# Update / upgrade everything
+sudo apt-get -y update && sudo apt-get -y upgrade
+
+# gst-switch compile stuff
+sudo apt-get -y install build-essential dh-autoreconf
+sudo apt-get -y install libgtk-3-dev
+sudo apt-get -y install gstreamer1.0.* libgstreamer.*1.0.*
+
+# Python stuff for gst-switch API
+sudo apt-get -y install python-software-properties python-pip
+sudo apt-get -y install libglib2.0-dev gir1.2-glib-2.0 libgirepository1.0-dev libglib2.0-0 python-gi
+sudo apt-get -y install python-scipy
+sudo pip install mock --upgrade
+sudo pip install pytest-cov --upgrade
+sudo pip install pytest-pep8 --upgrade
+sudo pip install pylint --upgrade
+
+# Needed for tests
+sudo apt-get -y install wget libav-tools
+
+# Needed to get coverage output
+if [ $TYPE == 'c' ]; then
+ sudo pip install cpp-coveralls
+else
+ sudo pip install python-coveralls
+fi
diff --git a/.travis.yml b/.travis.yml
index 869dbef..2f520a4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,23 @@
-language: c
+language:
+ - c
compiler:
- - clang
- gcc
-before_install:
- - echo 'yes' | sudo add-apt-repository ppa:gstreamer-developers/ppa
- - sudo apt-get update
- - sudo apt-get install .*gstreamer.*1\.0.* libgtk-3-dev
- - ./autogen.sh
+env:
+ matrix:
+ - TEST=python-api TYPE=unittest
+ - TEST=python-api TYPE=integration
+ - TEST=module TYPE=python
+ - TEST=module TYPE=c
+
+install:
+ - wget -q -O- https://raw.githubusercontent.com/mithro/travis-trusty/master/setup.sh | bash
+ - chmod a+rx $PWD/.travis-*.sh
+ - /trusty/run.py $PWD/.travis-setup.sh
+
+script:
+ - /trusty/run.py $PWD/.travis-run.sh
notifications:
email:
diff --git a/Makefile.am b/Makefile.am
index 74e55df..1dc5fb1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,8 +1,8 @@
-SUBDIRS = plugins tools tests
+SUBDIRS = plugins tools tests/unit tests
EXTRA_DIST = autogen.sh
-ACLOCAL_AMFLAGS = -I m4
+ACLOCAL_AMFLAGS = -I build
include tests/names.mk
test: update-test-suite clean-test-data $(TESTS)
diff --git a/README b/README
deleted file mode 100644
index bfaf97a..0000000
--- a/README
+++ /dev/null
@@ -1,6 +0,0 @@
-GstSwitch
-
-This project is intended to be a replacement of DV-switch base on GStreamer.
-
-See README.md.
-
diff --git a/README b/README
new file mode 120000
index 0000000..42061c0
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+README.md
\ No newline at end of file
diff --git a/README.md b/README.md
index b435a10..909996b 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,402 @@
-# GstSwitch
+# Build Status
+[](https://travis-ci.org/timvideos/gst-switch)
+[](https://coveralls.io/r/timvideos/gst-switch)
-## Purpose
+# gst-switch
-This project is intended to be a replacement of DV-switch base on GStreamer.
+gst-switch is a software based live video mixing and capture platform designed
+for fast, reliable and fault tolerant mixing, switching and capture. It is
+intended to replace expensive hardware mixing solutions needed when mixing HD
+video streams.
-## Components
+gst-switch was developed as a HD be a replacement of
+[dvswitch](http://dvswitch.alioth.debian.org/wiki/).
-GstSwitch contains two parts, *gst-switch-srv* and *gst-switch-ui*
-## Quick Manual
+# Quick Start
-### The GstSwitch Server
+ * Use Ubuntu 14.04 (trusty)
+ * Check out code
+ * Run `build-min-trusty.sh`
+ * Run `run-demo.sh`
-The GstSwitch server will open at least three ports for video/audio input, and
+----
+----
+
+# Design
+
+gst-switch is split into multiple parts.
+
+The video mixing is done in a server process which is controlled via an API
+available on DBUS.
+
+Video and Audio is feed to the server using the gstreamer data protocol (GDP)
+over TCP sockets. The resulting mixed output is written to disk and can also be
+read out of the server again using GDP over a TCP socket.
+
+The UI talks to the server via the API and displays the results. The UI is a
+totally separate process (which could run on a different machine) meaning that
+any crashes, stalls or other issues do not effect the mixing or recording.
+
+The Python API exists to allow easy scripting on the server. It has
+functionality for controlling the starting and stopping of the server and
+video/audio feeds. It also can control the mixing, doing anything that the GUI
+can do. The Python API is used extensively for testing, but should also be
+useful for semi and full-automated mixing.
+
+
+
+# Installing
+
+gst-switch is only **tested** on the current
+[Ubuntu LTS](https://wiki.ubuntu.com/LTS).
+
+It **should** work on other Linux versions which include the dependencies (such
+as Debian, RedHat, etc) but is not regularly tested on them.
+
+## Dependencies
+
+gst-switch has minimal dependencies when used just for basic video switching.
+
+The requirements are;
+ * A current version of [gstreamer1.0](http://gstreamer.freedesktop.org/).
+ (Used to do all the actual hard work.)
+ * A current version of dbus. (Used to communicate between the server and UI.)
+ * A current version of gtk3.0 (Used for the GUI.)
+
+The Python API is highly recommended but optional part of gst-switch. The
+Python API requirements are;
+ * python-gi (and thus gir1.2-glib-2.0 and libglib2.0-0).
+ (Used to call gstreamer and dbus Python bindings.)
+ * python-scipy
+ (Used to create and manipulate images.)
+
+
+The test suite requirements are;
+ * pytest (including pytest-pep8 and pytest-cov)
+ * pylint
+ * ffmpeg or avconv command line tools (ffmpeg and libav-tools).
+ * *FIXME: These should be removed and replaced with gst-launch commands.*
+
+There are some optional dependencies for the more advanced features like
+speaker tracking. *FIXME: Add the dependency information here.*
+
+## Ubuntu Trusty
+
+Either run the `build-min-trusty.sh` script or follow the instructions in the
+file.
+
+## Ubuntu Precise
+
+**We are currently in the process of dropping support for Precise in preference
+of Trusty.**
+
+Ubuntu Precise does not ship with gstreamer1.0, so it needs to be build from
+source. The `scripts/install2.sh` script will do this for you and then builds
+gst-switch too.
+
+----
+----
+
+# Manual
+
+gst-switch contains two parts, *gst-switch-srv* and *gst-switch-ui*
+
+## The gst-switch UI
+The gst-switch UI is a graphical controller for controlling the input streams.
+The UI must be started *after* the server is running.
+
+Example gst-switch-ui command line;
+```bash
+gst-switch-ui
+```
+
+### Controls
+
+ * Left Click - Set video as primary video.
+ * Right Click - Set video as secondary video.
+
+| Key | Function |
+| --------------------- | -------------------------------------------- |
+| R | Start recording to new file |
+| Tab | Cycle composite modes |
+| Esc | No composite mode |
+| F1 or P | Compositing mode - Picture-in-Picture |
+| F2 or D | Compositing mode - Side-by-side (preview) |
+| F3 or S | Compositing mode - Side-by-side (equal) |
+| A | When compositing, change the primary video |
+| B | When compositing, change the secondary video |
+| Up/Down | When compositing, select the video |
+| Ctrl + Arrows | When compositing PIP, Adjust PIP position |
+| Ctrl + Shift + Arrows | When compositing PIP, Adjust PIP size |
+
+
+## The gst-switch Server
+
+The gst-switch server will open at least three ports for video/audio input, and
command controls.
-#### Video Input
+Example gst-switch command line:
+```bash
+gst-switch-srv
+```
+*FIXME: Add examples of the recording and resolution options to the above
+example.*
+
+Once the gst-switch server is running, you will then need to feed the server
+audio and video streams. Helper scripts for doing this can be found in the XXXX
+directory.
+
+*FIXME: Add example feed information here.*
+
+
+### Command Line Options
+
+```
+Usage:
+ gst-switch-srv [OPTION...]
+
+Help Options:
+ -h, --help Show help options
+ --help-all Show all help options
+ --help-gst Show GStreamer Options
+
+Application Options:
+ -v, --verbose Prompt more messages
+ -t, --test-switch=OUTPUT Perform switch test
+ -r, --record=FILENAME Enable recorder and record into the specified FILENAME
+ -p, --video-input-port=NUM Specify the video input listen port.
+ -a, --audio-input-port=NUM Specify the audio input listen port.
+ --control-port=NUM Specify the control port.
+```
+
+### Video Input
+
+The default TCP port for video data is *3000*.
+
+Video data should be sent using gstreamer but there are multiple methods for
+doing so (see the sections below).
+
+All video sent **must** be in the resolution the exact aspect ratio, resolution
+and frame rates the server was configured with when starting.
+
+The color space / pixel format must be in [I420](http://www.fourcc.org/yuv.php#IYUV).
+(This limitation comes from the usage of the inter gstreamer elements.)
+
+You can use the following gstreamer plug-ins to convert the video on the
+sender's side;
+ * [videobox](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-videobox.html),
+ to add borders or crop a video (thus changing the resolution or aspect ratio).
+
+ * [videoscale](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-videoscale.html),
+ to change the resolution of video via scaling.
+
+ * [videoconvert](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-videoconvert.html),
+ to convert the pixel format and color space.
+
+ * [videorate](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-videorate.html),
+ to adjust the frame rate (fps).
+
+All video **must** be deinterlaced, use the following plug-ins to convert
+interlaced video on the sender's side;
+ * [deinterlace](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-deinterlace.html),
+ the basic, well coded, stable, deinterlacer included in gstreamer-plugins-good.
+
+ * `avdeinterlace`, the high performance and feature rich deinterlacer from
+ libav (a fork of ffmpeg). Previously called `ffdeinterlace`.
+
+#### Using `gst-launch-1.0` for video input
+
+*FIXME: Put some example gst-launch-1.0 examples here.*
+
+*FIXME: Write some example scripts which show gst-launch-1.0 usage for capturing
+from DV, v4l, decklink cards, etc.*
+
+#### Using gst-switch Python API
+
+*FIXME: Put some Python API examples here.*
+
+#### Using gst-switch-cap for video input
+
+gst-switch-cap is a custom program needed if you wish to use the speaker
+tracking functionality.
+
+Example command line;
+```bash
+gst-switch-cap --device='/dev/ttyUSB0' --protocol='visca'
+```
+
+#### Supported video formats
+
+*FIXME: Actually make sure the tests cover the following formats listed below.*
+
+gst-switch is tested with the following video resolution;
+ * 300x200 - A low resolution only used in testing to increase test speed.
+ * 1024x768 - A 4:3 resolution almost universally supported by computers and
+ projector equipment. Sometimes called XGA.
+ * 1280x720 - Lowest resolution 16:9 HD format, often called
+ [720p](http://en.wikipedia.org/wiki/720p).
+ * 1920x1080 - Most common 16:9 HD format, often called
+ [1080p](http://en.wikipedia.org/wiki/1080p).
+ * 4096x2160 - Ultra HD format with 19:10 (1.9:1) aspect ratio. Sometimes
+ called [4k](http://en.wikipedia.org/wiki/4K_resolution) video or
+ 2160p.
+ **WARNING: Even the fastest computers struggle at this resolution!**
+
+
+gst-switch is tested with the following video frame rates;
+ * 24fps - Common "film" format.
+ * 25fps - "Full" frame rate of TV in PAL based countries (Europe, Australia, etc).
+ * 30fps - "Full" frame rate of TV in NTSC based countries (USA, Japan, etc).
+ * 60fps - Most common frame rate of computer monitors.
+ * 120fps - Frame rate of "High FPS" computer monitors and TVs.
-The video input port is *3000*. Supported input video format: I420
-(video/x-raw), 1280x720 (for debug mode, the video size could be 300x200).
#### Audio Input Port
-The audio input port is *4000*.
+The default audio input port is *4000*.
+
+Audio input must be in;
+```
+ audio/x-raw
+ format: S16LE
+ rate: 48000
+ channels: 2
+ layout: interleaved
+```
+
+##### Example test audio feed
+
+```
+gst-launch-1.0 audiotestsrc is-live=true \
+ ! audioconvert \
+ ! audio/x-raw,rate=48000,channels=2,format=S16LE,layout=interleaved \
+ ! gdppay \
+ ! tcpclientsink port=4000
+```
+
+##### Example real audio feed
+
+
#### Control Port
-The command control port is *5000*.
+The default command control port is *5000*.
-### Controls
+*FIXME: This is a DBUS port of some type?*
+
+
+# gstreamer plug-ins usage
+
+This section lists the plug-ins used by gst-switch system.
+
+*FIXME: Generate this bit automagically. The following command line is kind of a
+start;*
+```shell
+ack --type=cc "g_string_new|g_string_append" * | grep '"' | grep ".c" | sed -e's/.c:[^"]*"/ /' -e's/"[^"]*$//' | sort | uniq > output
+```
+
+### GUI
+
+#### Audio Display
+
+tools/gstaudiovisual - Renders a video of the audio.
+ * *FIXME: Is this GUI or server side?*
+ * audioconvert
+ * videoconvert
+ * faad
+ * gdpdepay
+ * monoscope
+ * queue2
+ * tcpclientsrc
+ * tee
+ * textoverlay
+ * xvimagesink
+
+tools/gstvideodisp - Displays the video on the screen.
+ * cairooverlay
+ * gdpdepay
+ * tcpclientsrc
+ * videoconvert
+ * xvimagesink
+
+### Server
+
+tools/gstswitchserver
+ * gdppay
+ * intervideosrc
+ * tcpserversink
+
+tools/gstcase
+ * *FIXME: What is this?*
+ * faac
+ * gdpdepay
+ * gdpdepay
+ * gdppay
+ * giostreamsrc
+ * interaudiosink
+ * interaudiosrc
+ * intervideosink
+ * intervideosrc
+ * queue2
+ * tcpserversink
+ * tee
+
+tools/gstcomposite - The actual mixer?
+ * *FIXME: I'm sure there is probably more here...*
+ * identity
+ * intervideosink
+ * queue2
+ * tee
-
- Key Bindings Function
- Ctrl+Arrow
- Adjust PIP position.
-
+#### Recording
- Ctrl+Shift+Arrow
- Adjust PIP size.
-
+The output format is an AVI file with VP8 (webm) video and faac audio.
- Arrow Up/Down
- Changge selection of video previews. Selecting a video is the first step for
- switching video/audio input.
-
+tools/gstrecorder - Recording to disk.
+ * avimux
+ * voaacenc
+ * filesink
+ * gdppay
+ * interaudiosrc
+ * intervideosrc
+ * queue2
+ * tcpserversink
+ * tee
+ * vp8enc
- A
- Switch video input for channel A or activate audio input to the selected
- video/audio.
-
+### Speaker Track
- B
- Switch video input for channel B to the selected video.
-
+tools/gstswitchcapture and tools/gstswitchptz
- Tab
- Change a mode of A/B compositing, stroking it repeatly will cycle within modes
- and off.
-
+### Tests
- Esc
- Turn off A/B videos compositing.
-
+Everything under the tests/ directory
- r
- Start a new recording, the *--record _name_* will be used as a template,
- e.g. *record 2013-01-23 131139.dat*
-
-
+ * audiotestsrc
+ * avidemux
+ * faad
+ * fakesink
+ * filesrc
+ * gdpdepay
+ * gdppay
+ * goom2k1
+ * monoscope
+ * tcpclientsrc
+ * textoverlay
+ * videoconvert
+ * videotestsrc
+ * xvimagesink
+###The gst-switch PTZ
-# Current Build Status
+Implements Pan-Tilt-Zoom Camera controller UI.
+```bash
+gst-switch-ptz --device='/dev/ttyUSB0' --protocol='visca' --video='/dev/video0'
+```
-[](https://travis-ci.org/timsvideo/gst-switch)
+*FIXME: Remove this tool and put it into a gst-plugins-ptz repository with the
+associated plug-ins.*
diff --git a/build-min-trusty.sh b/build-min-trusty.sh
new file mode 100755
index 0000000..5bf8a5a
--- /dev/null
+++ b/build-min-trusty.sh
@@ -0,0 +1,17 @@
+#!/bin/bash -ex
+
+sudo apt-get install \
+ build-essential \
+ libglib2.0-dev \
+ libgtk-3-dev \
+ libgstreamer1.0-dev \
+ libgstreamer-plugins-base1.0-dev \
+ gstreamer1.0-plugins-bad \
+ autoconf \
+ libtool
+
+
+
+./autogen.sh
+make
+
diff --git a/m4/.gitignore b/build/.gitignore
similarity index 100%
rename from m4/.gitignore
rename to build/.gitignore
diff --git a/build/Makefile.am.gitignore b/build/Makefile.am.gitignore
new file mode 100644
index 0000000..42992a3
--- /dev/null
+++ b/build/Makefile.am.gitignore
@@ -0,0 +1,33 @@
+# this file should only be used in directories that generate test
+# or example binaries through noinst_PROGRAMS; it is *not* a full
+# generator of Git ignore files, and it's not meant to be used as
+# the top-level Git ignore file generator.
+
+$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/build/Makefile.am.gitignore
+ $(AM_V_GEN)( \
+ echo "*.o" ; \
+ echo ".gitignore" ; \
+ echo "*.trs" ; \
+ echo "*.log" ; \
+ echo "Makefile" ; \
+ echo "Makefile.in" ; \
+ echo ".deps" ; \
+ ) > $(srcdir)/.gitignore ; \
+ for p in $(noinst_PROGRAMS); do \
+ echo "/$$p" >> $(srcdir)/.gitignore ; \
+ done ; \
+ for p in $(check_PROGRAMS); do \
+ echo "/$$p" >> $(srcdir)/.gitignore ; \
+ done
+
+
+gitignore: $(srcdir)/.gitignore
+
+gitignore-clean:
+ @rm -f $(srcdir)/.gitignore
+
+.PHONY: gitignore gitignore-clean
+
+all: gitignore
+
+maintainer-clean: gitignore-clean
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 0000000..89ea825
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1,6 @@
+
+ * [glib-tap.mk](https://git.gnome.org/browse/glib/tree/glib-tap.mk?id=7f2f4ab12df6ddb501900846896f496520871d16) (modified)
+ * [glibtests.m4](https://git.gnome.org/browse/glib/tree/m4macros/glibtests.m4?id=7f2f4ab12df6ddb501900846896f496520871d16)
+ * [tap-driver.sh](https://git.gnome.org/browse/glib/tree/tap-driver.sh?id=7f2f4ab12df6ddb501900846896f496520871d16)
+ * [tap-test](https://git.gnome.org/browse/glib/tree/tap-driver.sh?id=7f2f4ab12df6ddb501900846896f496520871d16)
+ * [Makefile.am.gitignore](https://git.gnome.org/browse/json-glib/tree/build/autotools/Makefile.am.gitignore?id=4ccb9ab6119b81dde81b04208b98c89fc3a7884c) (modified)
diff --git a/build/glib-tap.mk b/build/glib-tap.mk
new file mode 100644
index 0000000..11a34ab
--- /dev/null
+++ b/build/glib-tap.mk
@@ -0,0 +1,135 @@
+# GLIB - Library of useful C routines
+
+TESTS_ENVIRONMENT= \
+ G_TEST_SRCDIR="$(abs_srcdir)" \
+ G_TEST_BUILDDIR="$(abs_builddir)" \
+ G_DEBUG=gc-friendly \
+ MALLOC_CHECK_=2 \
+ MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256))
+LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build/tap-driver.sh
+LOG_COMPILER = $(top_srcdir)/build/tap-test
+
+NULL =
+
+# initialize variables for unconditional += appending
+BUILT_SOURCES =
+BUILT_EXTRA_DIST =
+CLEANFILES = *.log *.trs
+DISTCLEANFILES =
+MAINTAINERCLEANFILES =
+EXTRA_DIST =
+TESTS =
+
+installed_test_LTLIBRARIES =
+installed_test_PROGRAMS =
+installed_test_SCRIPTS =
+nobase_installed_test_DATA =
+
+noinst_LTLIBRARIES =
+noinst_PROGRAMS =
+noinst_SCRIPTS =
+noinst_DATA =
+
+check_LTLIBRARIES =
+check_PROGRAMS =
+check_SCRIPTS =
+check_DATA =
+
+# We support a fairly large range of possible variables. It is expected that all types of files in a test suite
+# will belong in exactly one of the following variables.
+#
+# First, we support the usual automake suffixes, but in lowercase, with the customary meaning:
+#
+# test_programs, test_scripts, test_data, test_ltlibraries
+#
+# The above are used to list files that are involved in both uninstalled and installed testing. The
+# test_programs and test_scripts are taken to be actual testcases and will be run as part of the test suite.
+# Note that _data is always used with the nobase_ automake variable name to ensure that installed test data is
+# installed in the same way as it appears in the package layout.
+#
+# In order to mark a particular file as being only for one type of testing, use 'installed' or 'uninstalled',
+# like so:
+#
+# installed_test_programs, uninstalled_test_programs
+# installed_test_scripts, uninstalled_test_scripts
+# installed_test_data, uninstalled_test_data
+# installed_test_ltlibraries, uninstalled_test_ltlibraries
+#
+# Additionally, we support 'extra' infixes for programs and scripts. This is used for support programs/scripts
+# that should not themselves be run as testcases (but exist to be used from other testcases):
+#
+# test_extra_programs, installed_test_extra_programs, uninstalled_test_extra_programs
+# test_extra_scripts, installed_test_extra_scripts, uninstalled_test_extra_scripts
+#
+# Additionally, for _scripts and _data, we support the customary dist_ prefix so that the named script or data
+# file automatically end up in the tarball.
+#
+# dist_test_scripts, dist_test_data, dist_test_extra_scripts
+# dist_installed_test_scripts, dist_installed_test_data, dist_installed_test_extra_scripts
+# dist_uninstalled_test_scripts, dist_uninstalled_test_data, dist_uninstalled_test_extra_scripts
+#
+# Note that no file is automatically disted unless it appears in one of the dist_ variables. This follows the
+# standard automake convention of not disting programs scripts or data by default.
+#
+# test_programs, test_scripts, uninstalled_test_programs and uninstalled_test_scripts (as well as their disted
+# variants) will be run as part of the in-tree 'make check'. These are all assumed to be runnable under
+# gtester. That's a bit strange for scripts, but it's possible.
+
+TESTS += $(test_programs) $(test_scripts) $(uninstalled_test_programs) $(uninstalled_test_scripts) \
+ $(dist_test_scripts) $(dist_uninstalled_test_scripts)
+
+# Note: build even the installed-only targets during 'make check' to ensure that they still work.
+# We need to do a bit of trickery here and manage disting via EXTRA_DIST instead of using dist_ prefixes to
+# prevent automake from mistreating gmake functions like $(wildcard ...) and $(addprefix ...) as if they were
+# filenames, including removing duplicate instances of the opening part before the space, eg. '$(addprefix'.
+all_test_programs = $(test_programs) $(uninstalled_test_programs) $(installed_test_programs) \
+ $(test_extra_programs) $(uninstalled_test_extra_programs) $(installed_test_extra_programs)
+all_test_scripts = $(test_scripts) $(uninstalled_test_scripts) $(installed_test_scripts) \
+ $(test_extra_scripts) $(uninstalled_test_extra_scripts) $(installed_test_extra_scripts)
+all_dist_test_scripts = $(dist_test_scripts) $(dist_uninstalled_test_scripts) $(dist_installed_test_scripts) \
+ $(dist_test_extra_scripts) $(dist_uninstalled_test_extra_scripts) $(dist_installed_test_extra_scripts)
+all_test_scripts += $(all_dist_test_scripts)
+EXTRA_DIST += $(all_dist_test_scripts)
+all_test_data = $(test_data) $(uninstalled_test_data) $(installed_test_data)
+all_dist_test_data = $(dist_test_data) $(dist_uninstalled_test_data) $(dist_installed_test_data)
+all_test_data += $(all_dist_test_data)
+EXTRA_DIST += $(all_dist_test_data)
+all_test_ltlibs = $(test_ltlibraries) $(uninstalled_test_ltlibraries) $(installed_test_ltlibraries)
+
+if ENABLE_ALWAYS_BUILD_TESTS
+noinst_LTLIBRARIES += $(all_test_ltlibs)
+noinst_PROGRAMS += $(all_test_programs)
+noinst_SCRIPTS += $(all_test_scripts)
+noinst_DATA += $(all_test_data)
+else
+check_LTLIBRARIES += $(all_test_ltlibs)
+check_PROGRAMS += $(all_test_programs)
+check_SCRIPTS += $(all_test_scripts)
+check_DATA += $(all_test_data)
+endif
+
+if ENABLE_INSTALLED_TESTS
+installed_test_PROGRAMS += $(test_programs) $(installed_test_programs) \
+ $(test_extra_programs) $(installed_test_extra_programs)
+installed_test_SCRIPTS += $(test_scripts) $(installed_test_scripts) \
+ $(test_extra_scripts) $(test_installed_extra_scripts)
+installed_test_SCRIPTS += $(dist_test_scripts) $(dist_test_extra_scripts) \
+ $(dist_installed_test_scripts) $(dist_installed_test_extra_scripts)
+nobase_installed_test_DATA += $(test_data) $(installed_test_data)
+nobase_installed_test_DATA += $(dist_test_data) $(dist_installed_test_data)
+installed_test_LTLIBRARIES += $(test_ltlibraries) $(installed_test_ltlibraries)
+installed_testcases = $(test_programs) $(installed_test_programs) \
+ $(test_scripts) $(installed_test_scripts) \
+ $(dist_test_scripts) $(dist_installed_test_scripts)
+
+installed_test_meta_DATA = $(installed_testcases:=.test)
+
+%.test: %$(EXEEXT) Makefile
+ $(AM_V_GEN) (echo '[Test]' > $@.tmp; \
+ echo 'Type=session' >> $@.tmp; \
+ echo 'Exec=$(installed_testdir)/$(notdir $<) --tap' >> $@.tmp; \
+ echo 'Output=TAP' >> $@.tmp; \
+ mv $@.tmp $@)
+
+CLEANFILES += $(installed_test_meta_DATA)
+endif
diff --git a/build/glibtests.m4 b/build/glibtests.m4
new file mode 100644
index 0000000..7d5920a
--- /dev/null
+++ b/build/glibtests.m4
@@ -0,0 +1,28 @@
+dnl GLIB_TESTS
+dnl
+
+AC_DEFUN([GLIB_TESTS],
+[
+ AC_ARG_ENABLE(installed-tests,
+ AS_HELP_STRING([--enable-installed-tests],
+ [Enable installation of some test cases]),
+ [case ${enableval} in
+ yes) ENABLE_INSTALLED_TESTS="1" ;;
+ no) ENABLE_INSTALLED_TESTS="" ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-installed-tests]) ;;
+ esac])
+ AM_CONDITIONAL([ENABLE_INSTALLED_TESTS], test "$ENABLE_INSTALLED_TESTS" = "1")
+ AC_ARG_ENABLE(always-build-tests,
+ AS_HELP_STRING([--enable-always-build-tests],
+ [Enable always building tests during 'make all']),
+ [case ${enableval} in
+ yes) ENABLE_ALWAYS_BUILD_TESTS="1" ;;
+ no) ENABLE_ALWAYS_BUILD_TESTS="" ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-always-build-tests]) ;;
+ esac])
+ AM_CONDITIONAL([ENABLE_ALWAYS_BUILD_TESTS], test "$ENABLE_ALWAYS_BUILD_TESTS" = "1")
+ if test "$ENABLE_INSTALLED_TESTS" = "1"; then
+ AC_SUBST(installed_test_metadir, [${datadir}/installed-tests/]AC_PACKAGE_NAME)
+ AC_SUBST(installed_testdir, [${libexecdir}/installed-tests/]AC_PACKAGE_NAME)
+ fi
+])
diff --git a/build/tap-driver.sh b/build/tap-driver.sh
new file mode 100755
index 0000000..19aa531
--- /dev/null
+++ b/build/tap-driver.sh
@@ -0,0 +1,652 @@
+#! /bin/sh
+# Copyright (C) 2011-2013 Free Software Foundation, Inc.
+#
+# 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 2, 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 .
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to or send patches to
+# .
+
+scriptversion=2011-12-27.17; # UTC
+
+# Make unconditional expansion of undefined variables an error. This
+# helps a lot in preventing typo-related bugs.
+set -u
+
+me=tap-driver.sh
+
+fatal ()
+{
+ echo "$me: fatal: $*" >&2
+ exit 1
+}
+
+usage_error ()
+{
+ echo "$me: $*" >&2
+ print_usage >&2
+ exit 2
+}
+
+print_usage ()
+{
+ cat <
+ #
+ trap : 1 3 2 13 15
+ if test $merge -gt 0; then
+ exec 2>&1
+ else
+ exec 2>&3
+ fi
+ "$@"
+ echo $?
+ ) | LC_ALL=C ${AM_TAP_AWK-awk} \
+ -v me="$me" \
+ -v test_script_name="$test_name" \
+ -v log_file="$log_file" \
+ -v trs_file="$trs_file" \
+ -v expect_failure="$expect_failure" \
+ -v merge="$merge" \
+ -v ignore_exit="$ignore_exit" \
+ -v comments="$comments" \
+ -v diag_string="$diag_string" \
+'
+# FIXME: the usages of "cat >&3" below could be optimized when using
+# FIXME: GNU awk, and/on on systems that supports /dev/fd/.
+
+# Implementation note: in what follows, `result_obj` will be an
+# associative array that (partly) simulates a TAP result object
+# from the `TAP::Parser` perl module.
+
+## ----------- ##
+## FUNCTIONS ##
+## ----------- ##
+
+function fatal(msg)
+{
+ print me ": " msg | "cat >&2"
+ exit 1
+}
+
+function abort(where)
+{
+ fatal("internal error " where)
+}
+
+# Convert a boolean to a "yes"/"no" string.
+function yn(bool)
+{
+ return bool ? "yes" : "no";
+}
+
+function add_test_result(result)
+{
+ if (!test_results_index)
+ test_results_index = 0
+ test_results_list[test_results_index] = result
+ test_results_index += 1
+ test_results_seen[result] = 1;
+}
+
+# Whether the test script should be re-run by "make recheck".
+function must_recheck()
+{
+ for (k in test_results_seen)
+ if (k != "XFAIL" && k != "PASS" && k != "SKIP")
+ return 1
+ return 0
+}
+
+# Whether the content of the log file associated to this test should
+# be copied into the "global" test-suite.log.
+function copy_in_global_log()
+{
+ for (k in test_results_seen)
+ if (k != "PASS")
+ return 1
+ return 0
+}
+
+# FIXME: this can certainly be improved ...
+function get_global_test_result()
+{
+ if ("ERROR" in test_results_seen)
+ return "ERROR"
+ if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
+ return "FAIL"
+ all_skipped = 1
+ for (k in test_results_seen)
+ if (k != "SKIP")
+ all_skipped = 0
+ if (all_skipped)
+ return "SKIP"
+ return "PASS";
+}
+
+function stringify_result_obj(result_obj)
+{
+ if (result_obj["is_unplanned"] || result_obj["number"] != testno)
+ return "ERROR"
+
+ if (plan_seen == LATE_PLAN)
+ return "ERROR"
+
+ if (result_obj["directive"] == "TODO")
+ return result_obj["is_ok"] ? "XPASS" : "XFAIL"
+
+ if (result_obj["directive"] == "SKIP")
+ return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
+
+ if (length(result_obj["directive"]))
+ abort("in function stringify_result_obj()")
+
+ return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
+}
+
+function decorate_result(result)
+{
+ color_name = color_for_result[result]
+ if (color_name)
+ return color_map[color_name] "" result "" color_map["std"]
+ # If we are not using colorized output, or if we do not know how
+ # to colorize the given result, we should return it unchanged.
+ return result
+}
+
+function report(result, details)
+{
+ if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
+ {
+ msg = ": " test_script_name
+ add_test_result(result)
+ }
+ else if (result == "#")
+ {
+ msg = " " test_script_name ":"
+ }
+ else
+ {
+ abort("in function report()")
+ }
+ if (length(details))
+ msg = msg " " details
+ # Output on console might be colorized.
+ print decorate_result(result) msg
+ # Log the result in the log file too, to help debugging (this is
+ # especially true when said result is a TAP error or "Bail out!").
+ print result msg | "cat >&3";
+}
+
+function testsuite_error(error_message)
+{
+ report("ERROR", "- " error_message)
+}
+
+function handle_tap_result()
+{
+ details = result_obj["number"];
+ if (length(result_obj["description"]))
+ details = details " " result_obj["description"]
+
+ if (plan_seen == LATE_PLAN)
+ {
+ details = details " # AFTER LATE PLAN";
+ }
+ else if (result_obj["is_unplanned"])
+ {
+ details = details " # UNPLANNED";
+ }
+ else if (result_obj["number"] != testno)
+ {
+ details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
+ details, testno);
+ }
+ else if (result_obj["directive"])
+ {
+ details = details " # " result_obj["directive"];
+ if (length(result_obj["explanation"]))
+ details = details " " result_obj["explanation"]
+ }
+
+ report(stringify_result_obj(result_obj), details)
+}
+
+# `skip_reason` should be empty whenever planned > 0.
+function handle_tap_plan(planned, skip_reason)
+{
+ planned += 0 # Avoid getting confused if, say, `planned` is "00"
+ if (length(skip_reason) && planned > 0)
+ abort("in function handle_tap_plan()")
+ if (plan_seen)
+ {
+ # Error, only one plan per stream is acceptable.
+ testsuite_error("multiple test plans")
+ return;
+ }
+ planned_tests = planned
+ # The TAP plan can come before or after *all* the TAP results; we speak
+ # respectively of an "early" or a "late" plan. If we see the plan line
+ # after at least one TAP result has been seen, assume we have a late
+ # plan; in this case, any further test result seen after the plan will
+ # be flagged as an error.
+ plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
+ # If testno > 0, we have an error ("too many tests run") that will be
+ # automatically dealt with later, so do not worry about it here. If
+ # $plan_seen is true, we have an error due to a repeated plan, and that
+ # has already been dealt with above. Otherwise, we have a valid "plan
+ # with SKIP" specification, and should report it as a particular kind
+ # of SKIP result.
+ if (planned == 0 && testno == 0)
+ {
+ if (length(skip_reason))
+ skip_reason = "- " skip_reason;
+ report("SKIP", skip_reason);
+ }
+}
+
+function extract_tap_comment(line)
+{
+ if (index(line, diag_string) == 1)
+ {
+ # Strip leading `diag_string` from `line`.
+ line = substr(line, length(diag_string) + 1)
+ # And strip any leading and trailing whitespace left.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+ # Return what is left (if any).
+ return line;
+ }
+ return "";
+}
+
+# When this function is called, we know that line is a TAP result line,
+# so that it matches the (perl) RE "^(not )?ok\b".
+function setup_result_obj(line)
+{
+ # Get the result, and remove it from the line.
+ result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
+ sub("^(not )?ok[ \t]*", "", line)
+
+ # If the result has an explicit number, get it and strip it; otherwise,
+ # automatically assing the next progresive number to it.
+ if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
+ {
+ match(line, "^[0-9]+")
+ # The final `+ 0` is to normalize numbers with leading zeros.
+ result_obj["number"] = substr(line, 1, RLENGTH) + 0
+ line = substr(line, RLENGTH + 1)
+ }
+ else
+ {
+ result_obj["number"] = testno
+ }
+
+ if (plan_seen == LATE_PLAN)
+ # No further test results are acceptable after a "late" TAP plan
+ # has been seen.
+ result_obj["is_unplanned"] = 1
+ else if (plan_seen && testno > planned_tests)
+ result_obj["is_unplanned"] = 1
+ else
+ result_obj["is_unplanned"] = 0
+
+ # Strip trailing and leading whitespace.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+
+ # This will have to be corrected if we have a "TODO"/"SKIP" directive.
+ result_obj["description"] = line
+ result_obj["directive"] = ""
+ result_obj["explanation"] = ""
+
+ if (index(line, "#") == 0)
+ return # No possible directive, nothing more to do.
+
+ # Directives are case-insensitive.
+ rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
+
+ # See whether we have the directive, and if yes, where.
+ pos = match(line, rx "$")
+ if (!pos)
+ pos = match(line, rx "[^a-zA-Z0-9_]")
+
+ # If there was no TAP directive, we have nothing more to do.
+ if (!pos)
+ return
+
+ # Let`s now see if the TAP directive has been escaped. For example:
+ # escaped: ok \# SKIP
+ # not escaped: ok \\# SKIP
+ # escaped: ok \\\\\# SKIP
+ # not escaped: ok \ # SKIP
+ if (substr(line, pos, 1) == "#")
+ {
+ bslash_count = 0
+ for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
+ bslash_count += 1
+ if (bslash_count % 2)
+ return # Directive was escaped.
+ }
+
+ # Strip the directive and its explanation (if any) from the test
+ # description.
+ result_obj["description"] = substr(line, 1, pos - 1)
+ # Now remove the test description from the line, that has been dealt
+ # with already.
+ line = substr(line, pos)
+ # Strip the directive, and save its value (normalized to upper case).
+ sub("^[ \t]*#[ \t]*", "", line)
+ result_obj["directive"] = toupper(substr(line, 1, 4))
+ line = substr(line, 5)
+ # Now get the explanation for the directive (if any), with leading
+ # and trailing whitespace removed.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+ result_obj["explanation"] = line
+}
+
+function get_test_exit_message(status)
+{
+ if (status == 0)
+ return ""
+ if (status !~ /^[1-9][0-9]*$/)
+ abort("getting exit status")
+ if (status < 127)
+ exit_details = ""
+ else if (status == 127)
+ exit_details = " (command not found?)"
+ else if (status >= 128 && status <= 255)
+ exit_details = sprintf(" (terminated by signal %d?)", status - 128)
+ else if (status > 256 && status <= 384)
+ # We used to report an "abnormal termination" here, but some Korn
+ # shells, when a child process die due to signal number n, can leave
+ # in $? an exit status of 256+n instead of the more standard 128+n.
+ # Apparently, both behaviours are allowed by POSIX (2008), so be
+ # prepared to handle them both. See also Austing Group report ID
+ # 0000051
+ exit_details = sprintf(" (terminated by signal %d?)", status - 256)
+ else
+ # Never seen in practice.
+ exit_details = " (abnormal termination)"
+ return sprintf("exited with status %d%s", status, exit_details)
+}
+
+function write_test_results()
+{
+ print ":global-test-result: " get_global_test_result() > trs_file
+ print ":recheck: " yn(must_recheck()) > trs_file
+ print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
+ for (i = 0; i < test_results_index; i += 1)
+ print ":test-result: " test_results_list[i] > trs_file
+ close(trs_file);
+}
+
+BEGIN {
+
+## ------- ##
+## SETUP ##
+## ------- ##
+
+'"$init_colors"'
+
+# Properly initialized once the TAP plan is seen.
+planned_tests = 0
+
+COOKED_PASS = expect_failure ? "XPASS": "PASS";
+COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
+
+# Enumeration-like constants to remember which kind of plan (if any)
+# has been seen. It is important that NO_PLAN evaluates "false" as
+# a boolean.
+NO_PLAN = 0
+EARLY_PLAN = 1
+LATE_PLAN = 2
+
+testno = 0 # Number of test results seen so far.
+bailed_out = 0 # Whether a "Bail out!" directive has been seen.
+
+# Whether the TAP plan has been seen or not, and if yes, which kind
+# it is ("early" is seen before any test result, "late" otherwise).
+plan_seen = NO_PLAN
+
+## --------- ##
+## PARSING ##
+## --------- ##
+
+is_first_read = 1
+
+while (1)
+ {
+ # Involutions required so that we are able to read the exit status
+ # from the last input line.
+ st = getline
+ if (st < 0) # I/O error.
+ fatal("I/O error while reading from input stream")
+ else if (st == 0) # End-of-input
+ {
+ if (is_first_read)
+ abort("in input loop: only one input line")
+ break
+ }
+ if (is_first_read)
+ {
+ is_first_read = 0
+ nextline = $0
+ continue
+ }
+ else
+ {
+ curline = nextline
+ nextline = $0
+ $0 = curline
+ }
+ # Copy any input line verbatim into the log file.
+ print | "cat >&3"
+ # Parsing of TAP input should stop after a "Bail out!" directive.
+ if (bailed_out)
+ continue
+
+ # TAP test result.
+ if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
+ {
+ testno += 1
+ setup_result_obj($0)
+ handle_tap_result()
+ }
+ # TAP plan (normal or "SKIP" without explanation).
+ else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
+ {
+ # The next two lines will put the number of planned tests in $0.
+ sub("^1\\.\\.", "")
+ sub("[^0-9]*$", "")
+ handle_tap_plan($0, "")
+ continue
+ }
+ # TAP "SKIP" plan, with an explanation.
+ else if ($0 ~ /^1\.\.0+[ \t]*#/)
+ {
+ # The next lines will put the skip explanation in $0, stripping
+ # any leading and trailing whitespace. This is a little more
+ # tricky in truth, since we want to also strip a potential leading
+ # "SKIP" string from the message.
+ sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
+ sub("[ \t]*$", "");
+ handle_tap_plan(0, $0)
+ }
+ # "Bail out!" magic.
+ # Older versions of prove and TAP::Harness (e.g., 3.17) did not
+ # recognize a "Bail out!" directive when preceded by leading
+ # whitespace, but more modern versions (e.g., 3.23) do. So we
+ # emulate the latter, "more modern" behaviour.
+ else if ($0 ~ /^[ \t]*Bail out!/)
+ {
+ bailed_out = 1
+ # Get the bailout message (if any), with leading and trailing
+ # whitespace stripped. The message remains stored in `$0`.
+ sub("^[ \t]*Bail out![ \t]*", "");
+ sub("[ \t]*$", "");
+ # Format the error message for the
+ bailout_message = "Bail out!"
+ if (length($0))
+ bailout_message = bailout_message " " $0
+ testsuite_error(bailout_message)
+ }
+ # Maybe we have too look for dianogtic comments too.
+ else if (comments != 0)
+ {
+ comment = extract_tap_comment($0);
+ if (length(comment))
+ report("#", comment);
+ }
+ }
+
+## -------- ##
+## FINISH ##
+## -------- ##
+
+# A "Bail out!" directive should cause us to ignore any following TAP
+# error, as well as a non-zero exit status from the TAP producer.
+if (!bailed_out)
+ {
+ if (!plan_seen)
+ {
+ testsuite_error("missing test plan")
+ }
+ else if (planned_tests != testno)
+ {
+ bad_amount = testno > planned_tests ? "many" : "few"
+ testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
+ bad_amount, planned_tests, testno))
+ }
+ if (!ignore_exit)
+ {
+ # Fetch exit status from the last line.
+ exit_message = get_test_exit_message(nextline)
+ if (exit_message)
+ testsuite_error(exit_message)
+ }
+ }
+
+write_test_results()
+
+exit 0
+
+} # End of "BEGIN" block.
+'
+
+# TODO: document that we consume the file descriptor 3 :-(
+} 3>"$log_file"
+
+test $? -eq 0 || fatal "I/O or internal error"
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/build/tap-test b/build/tap-test
new file mode 100755
index 0000000..481e333
--- /dev/null
+++ b/build/tap-test
@@ -0,0 +1,5 @@
+#! /bin/sh
+
+# run a GTest in tap mode. The test binary is passed as $1
+
+$1 -k --tap
diff --git a/confdefs.h b/confdefs.h
new file mode 100644
index 0000000..aa9bb9f
--- /dev/null
+++ b/confdefs.h
@@ -0,0 +1,21 @@
+/* confdefs.h */
+#define PACKAGE_NAME "gst-switch"
+#define PACKAGE_TARNAME "gst-switch"
+#define PACKAGE_VERSION "0.0.1"
+#define PACKAGE_STRING "gst-switch 0.0.1"
+#define PACKAGE_BUGREPORT ""
+#define PACKAGE_URL ""
+#define PACKAGE "gst-switch"
+#define VERSION "0.0.1"
+#define STDC_HEADERS 1
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRING_H 1
+#define HAVE_MEMORY_H 1
+#define HAVE_STRINGS_H 1
+#define HAVE_INTTYPES_H 1
+#define HAVE_STDINT_H 1
+#define HAVE_UNISTD_H 1
+#define HAVE_DLFCN_H 1
+#define LT_OBJDIR ".libs/"
diff --git a/configure.ac b/configure.ac
index e2cb1b2..4484930 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,13 +8,18 @@ dnl required versions of gstreamer and plugins-base
GLIB_REQUIRED=2.32.0
GST_REQUIRED=1.0.1
GSTPB_REQUIRED=1.0.1
+GTK_REQUIRED=3.10.8
AC_CONFIG_SRCDIR([plugins/gstswitch.c])
AC_CONFIG_HEADERS([config.h])
dnl required version of automake
-AM_INIT_AUTOMAKE([1.10])
-AC_CONFIG_MACRO_DIR([m4])
+AM_INIT_AUTOMAKE([-Wno-portability 1.11 no-dist-gzip dist-xz tar-ustar])
+dnl use pretty build output with automake >= 1.11
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])],
+ [AM_DEFAULT_VERBOSITY=1
+ AC_SUBST(AM_DEFAULT_VERBOSITY)])
+AC_CONFIG_MACRO_DIR([build])
dnl enable mainainer mode by default
AM_MAINTAINER_MODE([enable])
@@ -33,6 +38,7 @@ AC_CHECK_PROG(HAVE_PKGCONFIG, pkg-config, [ ], [
AC_MSG_ERROR([You need to have pkg-config installed!])
])
+dnl === Glib + GIO ============================================================
PKG_CHECK_MODULES(GIO, [
gio-2.0 >= 2.25.0
], [
@@ -49,55 +55,37 @@ PKG_CHECK_MODULES(GLIB, [
AC_MSG_ERROR([
You need to install or upgrade the glib development
packages on your system. The minimum version required
- is $GST_REQUIRED.
+ is $GLIB_REQUIRED.
])
])
-dnl GTK
-HAVE_GTK=NO
-GTK2_REQ=2.14.0
-GTK3_REQ=2.91.3
-AC_MSG_CHECKING([which gtk+ version to compile examples against (optional)])
-AC_ARG_WITH([gtk],
- AS_HELP_STRING([--with-gtk=3.0|2.0],[which gtk+ version to compile against (default: 3.0)]),
- [case "$with_gtk" in
- 2.0) GTK_REQ=$GTK2_REQ ;;
- 3.0) GTK_REQ=$GTK3_REQ ;;
- *) AC_MSG_ERROR([invalid gtk+ version specified]);;
- esac],
- [with_gtk=3.0
- GTK_REQ=$GTK3_REQ])
-AC_MSG_RESULT([$with_gtk (>= $GTK_REQ)])
-PKG_CHECK_MODULES(GTK, gtk+-x11-$with_gtk >= $GTK_REQ, HAVE_GTK=yes, HAVE_GTK=no)
-AC_SUBST(GTK_LIBS)
-AC_SUBST(GTK_CFLAGS)
-AM_CONDITIONAL(HAVE_GTK, test "x$HAVE_GTK" = "xyes")
-
-CFLAGS="$GTK_CFLAGS"
-CPPFLAGS="$GTK_CFLAGS"
-AC_CHECK_HEADER([gdk/gdkx.h], [],
- [AC_MSG_ERROR([GDK X11 header (gdk/gdkx.h) is required])])
+PKG_CHECK_MODULES(GTK, [
+ gtk+-3.0 >= $GTK_REQUIRED
+], [
+ AC_SUBST(GTK_CFLAGS)
+ AC_SUBST(GTK_LIBS)
+], [
+ AC_MSG_ERROR([
+ You need to install or upgrade the gtk development
+ packages on your system. The minimum version required
+ is $GTK_REQUIRED.
+ ])
+])
-AC_ARG_ENABLE(gcov,
- AS_HELP_STRING([--enable-gcov],[enable gcov building]),
- [
- case "${enableval}" in
- yes) BUILD_GCOV=yes ;;
- no) BUILD_GCOV=no ;;
- esac
- ],
- [BUILD_GCOV=yes])
-AC_ARG_ENABLE(gprof,
- AS_HELP_STRING([--enable-gprof],[enable gprof building]),
- [
- case "${enableval}" in
- yes) BUILD_GPROF=yes ;;
- no) BUILD_GPROF=no ;;
- esac
- ],
- [BUILD_GPROF=yes])
-AM_CONDITIONAL(GCOV_ENABLED, test "x$BUILD_GCOV" = "xyes")
-AM_CONDITIONAL(GPROF_ENABLED, test "x$BUILD_GPROF" = "xyes")
+PKG_CHECK_MODULES(GDK, [
+ gdk-3.0 >= $GTK_REQUIRED
+], [
+ AC_SUBST(GDK_CFLAGS)
+ AC_SUBST(GDK_LIBS)
+], [
+ AC_MSG_ERROR([
+ You need to install or upgrade the gdk development
+ packages on your system. The minimum version required
+ is $GTK_REQUIRED.
+ ])
+])
+
+dnl === gstreamer related options =============================================
dnl Check for the required version of GStreamer core (and gst-plugins-base)
dnl This will export GST_CFLAGS and GST_LIBS variables for use in Makefile.am
@@ -141,6 +129,17 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ ], [ ])], [
AC_MSG_RESULT([no])
])
+dnl check if compiler understands -Werror (if yes, add -Werror to GST_CFLAGS)
+AC_MSG_CHECKING([to see if compiler understands -Werror])
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -Werror"
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ ], [ ])], [
+ GST_CFLAGS="$GST_CFLAGS -Werror"
+ AC_MSG_RESULT([yes])
+], [
+ AC_MSG_RESULT([no])
+])
+
dnl set the plugindir where plugins should be installed (for plugins/Makefile.am)
if test "x${prefix}" = "x$HOME"; then
plugindir="$HOME/.gstreamer-1.0/plugins"
@@ -153,10 +152,68 @@ dnl set proper LDFLAGS for plugins
GST_PLUGIN_LDFLAGS='-module -avoid-version -export-symbols-regex [_]*\(gst_\|Gst\|GST_\).*'
AC_SUBST(GST_PLUGIN_LDFLAGS)
+dnl === Testing ===============================================================
+
+GLIB_TESTS
+
+dnl FIXME: Add checking for the testing requirements here.
+
+dnl === Coverage and profiling options ========================================
+AC_ARG_ENABLE(gcov,
+ AS_HELP_STRING([--enable-gcov],[enable gcov building]),
+ [
+ case "${enableval}" in
+ yes) BUILD_GCOV=yes ;;
+ no) BUILD_GCOV=no ;;
+ esac
+ ],
+ [BUILD_GCOV=yes])
+AM_CONDITIONAL(GCOV_ENABLED, test "x$BUILD_GCOV" = "xyes")
+
+AC_ARG_ENABLE(gprof,
+ AS_HELP_STRING([--enable-gprof],[enable gprof building]),
+ [
+ case "${enableval}" in
+ yes) BUILD_GPROF=yes ;;
+ no) BUILD_GPROF=no ;;
+ esac
+ ],
+ [BUILD_GPROF=yes])
+AM_CONDITIONAL(GPROF_ENABLED, test "x$BUILD_GPROF" = "xyes")
+
+dnl === Local options =========================================================
+AC_ARG_ENABLE(speakertrack,
+ AS_HELP_STRING([--enable-speakertrack],[enable speakertrack]),
+ [
+ case "${enableval}" in
+ yes) BUILD_SPEAKERTRACK=yes ;;
+ no) BUILD_SPEAKERTRACK=no ;;
+ esac
+ ],
+ [BUILD_SPEAKERTRACK=no])
+ AM_CONDITIONAL(SPEAKERTRACK_ENABLED, test "x$BUILD_SPEAKERTRACK" = "xyes")
+
+dnl FIXME: Add the Python-API as an option here
+
+dnl === Output ================================================================
+
+AC_ARG_ENABLE(debug,
+AS_HELP_STRING([--enable-debug],
+ [enable debugging, default: no]),
+[case "${enableval}" in
+ yes) debug=true ;;
+ no) debug=false ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
+esac],
+[debug=false])
+
+AM_CONDITIONAL(DEBUG, test x"$debug" = x"true")
+
AC_CONFIG_FILES(
Makefile
plugins/Makefile
tests/Makefile
+tests/unit/Makefile
tools/Makefile
)
diff --git a/docs/FORKING.md b/docs/FORKING.md
new file mode 100644
index 0000000..ba6ad36
--- /dev/null
+++ b/docs/FORKING.md
@@ -0,0 +1,19 @@
+##Forking Gst-Switch
+
+To fork this project, click the "Fork" button at [Gst-Switch Github Page](https://github.com/timvideos/gst-switch "").
+
+
+Refer to the [Github Documentation](https://help.github.com/articles/fork-a-repo "") for further details.
+
+##Running Tests
+
+* Install gst-switch using instructions at [README.md](https://github.com/timvideos/gst-switch/blob/master/README.md "").
+* `cd python-api`
+* make test
+* For specific tests refer to [Python-API Readme](https://github.com/timvideos/gst-switch/blob/master/python-api/README.md "").
+
+##Sending Pull Requests for Changes
+
+* Click the **Compare and Review** button
+* Ensure that the Travis message shows **Success: The Travis Build Passed**
+* Send the pull request
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..210cb4e
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/gstswitch.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/gstswitch.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/gstswitch"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/gstswitch"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/TEST-RESULTS.md b/docs/TEST-RESULTS.md
new file mode 100644
index 0000000..e247cd7
--- /dev/null
+++ b/docs/TEST-RESULTS.md
@@ -0,0 +1,81 @@
+##Coverage
+The Coverage reports can be found at [coveralls.io](https://coveralls.io/r/hyades/gst-switch). The build is currently failing, therefore the coverage of the integration tests and the c code coverage cannot be found in the coveralls report. The coverage reports are generated for the inetgration test and C code. They are not reported to coveralls.
+The coverage are present at the build page of travis.
+
+##Python Integration Tests Coverage
+The current coverage reports are:
+```bash
+Name Stmts Miss Cover
+------------------------------------------
+gstswitch/__init__ 0 0 100%
+gstswitch/connection 183 67 63%
+gstswitch/controller 163 41 75%
+gstswitch/exception 15 0 100%
+gstswitch/helpers 129 44 66%
+gstswitch/server 143 36 75%
+gstswitch/testsource 312 64 79%
+------------------------------------------
+TOTAL 945 252 73%
+```
+
+##C Coverage
+The C Coverage is:
+```bash
+File 'gstcase.c'
+Lines executed:76.65% of 227
+Creating 'gstcase.c.gcov'
+
+File 'gstcomposite.c'
+Lines executed:63.34% of 371
+Creating 'gstcomposite.c.gcov'
+
+File 'gstrecorder.c'
+Lines executed:70.18% of 114
+Creating 'gstrecorder.c.gcov'
+
+File 'gstswitchcontroller.c'
+Lines executed:70.12% of 338
+Creating 'gstswitchcontroller.c.gcov'
+
+File 'gstswitchserver.c'
+Lines executed:69.93% of 765
+Creating 'gstswitchserver.c.gcov'
+
+File 'gstworker.c'
+Lines executed:66.67% of 348
+Creating 'gstworker.c.gcov'
+```
+
+The files which are tested are:
+* gstswitchserver.c
+* gstrecorder.c
+* gstcomposite.c
+* gstswitchcontroller.c
+* gstworker.c
+* gstcase.c
+
+The files left untested are:
+* gstvideodisp.c
+* gstswitchclient.c
+* gstswitchptz.c
+* gstswitchcapture.c
+* gstswitchui.c
+* gstswitchclient.c
+* gstaudiovisual.c
+
+##Untested
+The gst-switch controller can have three roles:
+* UI
+* Capture
+* None
+
+The Python-API communicates with the gst-switch controller over DBus. Hence the role that is selected is None. The methods which use the roles UI and Capture are not tested by the API.
+
+The file gstswitchui.c handles the gst-switch-ui.
+The file gstswitchcapture.c handles the gst-switch-cap.
+The file gstswitchptz.c handles the gst-switch-ptz.
+Since the above executables are not executed by the API, they are left untested.
+
+The files gstvideodisp.c and gstaudiodisp.c handle the video/audio contain functions dealing with the gst-switch-ui.
+
+The gstswitchclient.c is also used by gst-switch-ui and hence remains untested.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..041783a
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+#
+# gstswitch documentation build configuration file, created by
+# sphinx-quickstart on Mon Sep 2 03:55:42 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+sys.path.insert(0, os.path.abspath('..'))
+sys.path.insert(0, os.path.abspath('../python-api'))
+sys.path.insert(0, os.path.abspath('../python-api/gstswitch'))
+sys.path.insert(0, os.path.abspath('../python-api/tests'))
+sys.path.insert(0, os.path.abspath('../python-api/tests/unittests'))
+sys.path.insert(0, os.path.abspath('../python-api/tests/integrationtests'))
+sys.path.insert(0, os.path.abspath('fake-lib'))
+# print sys.path
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'gstswitch'
+copyright = u'2013, hyades'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = ''
+# The full version, including alpha/beta/rc tags.
+release = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'gstswitchdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'gstswitch.tex', u'gstswitch Documentation',
+ u'hyades', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'gstswitch', u'gstswitch Documentation',
+ [u'hyades'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'gstswitch', u'gstswitch Documentation',
+ u'hyades', 'gstswitch', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# -- Options for Epub output ---------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'gstswitch'
+epub_author = u'hyades'
+epub_publisher = u'hyades'
+epub_copyright = u'2013, hyades'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
+#epub_guide = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+#epub_exclude_files = []
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Fix unsupported image types using the PIL.
+#epub_fix_images = False
+
+# Scale large images.
+#epub_max_image_width = 0
+
+# If 'no', URL addresses will not be shown.
+#epub_show_urls = 'inline'
+
+# If false, no index is generated.
+#epub_use_index = True
diff --git a/docs/fake-lib/README b/docs/fake-lib/README
new file mode 100644
index 0000000..b939a74
--- /dev/null
+++ b/docs/fake-lib/README
@@ -0,0 +1,3 @@
+This directory exists so to build documentation without having things
+like gobject-introspection library installed, since they're
+difficult to get working in a virtualenv.
diff --git a/docs/fake-lib/gi/__init__.py b/docs/fake-lib/gi/__init__.py
new file mode 100644
index 0000000..779bda1
--- /dev/null
+++ b/docs/fake-lib/gi/__init__.py
@@ -0,0 +1,2 @@
+def require_version(namespace, version):
+ pass
\ No newline at end of file
diff --git a/docs/fake-lib/gi/repository/GLib.py b/docs/fake-lib/gi/repository/GLib.py
new file mode 100644
index 0000000..3a15192
--- /dev/null
+++ b/docs/fake-lib/gi/repository/GLib.py
@@ -0,0 +1,9 @@
+threads_init = lambda *a, **k: None
+
+class Source(object):
+ pass
+
+class IOCondition:
+ IN = 1
+ ERR = 8
+ HUP = 16
diff --git a/docs/fake-lib/gi/repository/GObject.py b/docs/fake-lib/gi/repository/GObject.py
new file mode 100644
index 0000000..3a15192
--- /dev/null
+++ b/docs/fake-lib/gi/repository/GObject.py
@@ -0,0 +1,9 @@
+threads_init = lambda *a, **k: None
+
+class Source(object):
+ pass
+
+class IOCondition:
+ IN = 1
+ ERR = 8
+ HUP = 16
diff --git a/docs/fake-lib/gi/repository/Gio.py b/docs/fake-lib/gi/repository/Gio.py
new file mode 100644
index 0000000..96e7d9f
--- /dev/null
+++ b/docs/fake-lib/gi/repository/Gio.py
@@ -0,0 +1,9 @@
+threads_init = lambda *a, **k: None
+
+class DBusConnection(object):
+ pass
+
+class DBusConnectionFlags(object):
+ AUTHENTICATION_CLIENT = 1
+ pass
+
diff --git a/docs/fake-lib/gi/repository/Gst.py b/docs/fake-lib/gi/repository/Gst.py
new file mode 100644
index 0000000..32aace4
--- /dev/null
+++ b/docs/fake-lib/gi/repository/Gst.py
@@ -0,0 +1,19 @@
+threads_init = lambda *a, **k: None
+
+def init(a):
+ pass
+
+class Source(object):
+ pass
+
+class Pipeline(object):
+ pass
+
+class ElementFactory(object):
+ def make(self, a, b):
+ pass
+
+class IOCondition:
+ IN = 1
+ ERR = 8
+ HUP = 16
diff --git a/docs/fake-lib/gi/repository/__init__.py b/docs/fake-lib/gi/repository/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/docs/fake-lib/mock.py b/docs/fake-lib/mock.py
new file mode 100644
index 0000000..45bdaf0
--- /dev/null
+++ b/docs/fake-lib/mock.py
@@ -0,0 +1,2 @@
+class Mock:
+ pass
\ No newline at end of file
diff --git a/docs/fake-lib/pytest.py b/docs/fake-lib/pytest.py
new file mode 100644
index 0000000..e69de29
diff --git a/docs/fake-lib/scipy/misc/imread.py b/docs/fake-lib/scipy/misc/imread.py
new file mode 100644
index 0000000..3c11ff3
--- /dev/null
+++ b/docs/fake-lib/scipy/misc/imread.py
@@ -0,0 +1,2 @@
+def imread():
+ pass
\ No newline at end of file
diff --git a/docs/gstswitch.rst b/docs/gstswitch.rst
new file mode 100644
index 0000000..5878adf
--- /dev/null
+++ b/docs/gstswitch.rst
@@ -0,0 +1,59 @@
+gst-switch python API
+=====================
+
+:mod:`gstswitch` Package
+------------------------
+
+.. automodule:: gstswitch.__init__
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`connection` Module
+------------------------
+
+.. automodule:: gstswitch.connection
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`controller` Module
+------------------------
+
+.. automodule:: gstswitch.controller
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`exception` Module
+-----------------------
+
+.. automodule:: gstswitch.exception
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`helpers` Module
+---------------------
+
+.. automodule:: gstswitch.helpers
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`server` Module
+--------------------
+
+.. automodule:: gstswitch.server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`testsource` Module
+------------------------
+
+.. automodule:: gstswitch.testsource
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..c038cd4
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,35 @@
+.. gstswitch documentation master file, created by
+ sphinx-quickstart on Sun Sep 8 17:54:44 2013.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+gst-switch
+==========
+
+A replacement of DVSwitch based on gstreamer
+--------------------------------------------
+
+Python - API:
+
+.. toctree::
+ :maxdepth: 4
+
+ gstswitch
+
+Tests:
+
+.. toctree::
+ :maxdepth: 4
+
+ unittests
+
+ integrationtests
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/integrationtests.rst b/docs/integrationtests.rst
new file mode 100644
index 0000000..7ebb60d
--- /dev/null
+++ b/docs/integrationtests.rst
@@ -0,0 +1,51 @@
+Integration Tests for gstswitch and Python-API
+==============================================
+
+:mod:`integrationtests` Package
+-------------------------------
+
+.. automodule:: integrationtests.__init__
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`compare` Module
+---------------------
+
+.. automodule:: integrationtests.compare
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`generate_reference_frames` Module
+---------------------------------------
+
+.. automodule:: integrationtests.generate_reference_frames
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_controller` Module
+-----------------------------
+
+.. automodule:: integrationtests.test_controller
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_helpers` Module
+--------------------------
+
+.. automodule:: integrationtests.test_helpers
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_server` Module
+-------------------------
+
+.. automodule:: integrationtests.test_server
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..94dbb24
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,242 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^` where ^ is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. xml to make Docutils-native XML files
+ echo. pseudoxml to make pseudoxml-XML files for display purposes
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\gstswitch.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\gstswitch.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdf" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf
+ cd %BUILDDIR%/..
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdfja" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf-ja
+ cd %BUILDDIR%/..
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+if "%1" == "xml" (
+ %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The XML files are in %BUILDDIR%/xml.
+ goto end
+)
+
+if "%1" == "pseudoxml" (
+ %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
+ goto end
+)
+
+:end
diff --git a/docs/unittests.rst b/docs/unittests.rst
new file mode 100644
index 0000000..a854f66
--- /dev/null
+++ b/docs/unittests.rst
@@ -0,0 +1,51 @@
+Unittest for Python-API
+=======================
+
+:mod:`unittests` Package
+------------------------
+
+.. automodule:: unittests.__init__
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_connection_unit` Module
+----------------------------------
+
+.. automodule:: unittests.test_connection_unit
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_controller_unit` Module
+----------------------------------
+
+.. automodule:: unittests.test_controller_unit
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_helpers_unit` Module
+-------------------------------
+
+.. automodule:: unittests.test_helpers_unit
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_server_unit` Module
+------------------------------
+
+.. automodule:: unittests.test_server_unit
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+:mod:`test_testsource_unit` Module
+----------------------------------
+
+.. automodule:: unittests.test_testsource_unit
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
diff --git a/plugins/gstassess.h b/plugins/gstassess.h
index 94a8877..d3491f1 100644
--- a/plugins/gstassess.h
+++ b/plugins/gstassess.h
@@ -63,4 +63,4 @@ GType gst_assess_get_type (void);
G_END_DECLS
-#endif//__GST_ASSESS_H__
+#endif //__GST_ASSESS_H__
diff --git a/plugins/gstconvbin.h b/plugins/gstconvbin.h
index da7fecb..aeb2524 100644
--- a/plugins/gstconvbin.h
+++ b/plugins/gstconvbin.h
@@ -66,4 +66,4 @@ GType gst_conv_bin_get_type (void);
G_END_DECLS
-#endif//__GST_CONV_BIN_H__
+#endif //__GST_CONV_BIN_H__
diff --git a/plugins/gstswitch.h b/plugins/gstswitch.h
index 443aa0b..ba371b9 100644
--- a/plugins/gstswitch.h
+++ b/plugins/gstswitch.h
@@ -63,4 +63,4 @@ GType gst_switch_get_type (void);
G_END_DECLS
-#endif//__GST_SWITCH_H__
+#endif //__GST_SWITCH_H__
diff --git a/plugins/gsttcpmixsrc.c b/plugins/gsttcpmixsrc.c
index 620755a..e8a9bd5 100644
--- a/plugins/gsttcpmixsrc.c
+++ b/plugins/gsttcpmixsrc.c
@@ -53,7 +53,7 @@ GST_DEBUG_CATEGORY_STATIC (tcpmixsrc_debug);
#define TCP_BACKLOG 1 /* client connection queue */
#define TCP_HIGHEST_PORT 65535
#define TCP_DEFAULT_PORT 4953
-#define TCP_DEFAULT_HOST "localhost"
+#define TCP_DEFAULT_HOST "0.0.0.0"
#define TCP_DEFAULT_LISTEN_HOST NULL /* listen on all interfaces */
#define MAX_READ_SIZE 4 * 1024
diff --git a/plugins/gsttcpmixsrc.h b/plugins/gsttcpmixsrc.h
index fe45e58..4df7ec8 100644
--- a/plugins/gsttcpmixsrc.h
+++ b/plugins/gsttcpmixsrc.h
@@ -82,4 +82,4 @@ GType gst_tcp_mix_src_get_type (void);
G_END_DECLS
-#endif /* __GST_TCP_MIX_SRC_H__ */
+#endif //__GST_TCP_MIX_SRC_H__
diff --git a/python-api/.pylintrc_gstswitch b/python-api/.pylintrc_gstswitch
new file mode 100644
index 0000000..e70d8bb
--- /dev/null
+++ b/python-api/.pylintrc_gstswitch
@@ -0,0 +1,248 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+# * E0712(raising-non-exception) - pylint is unable to see glib.GError
+# inherits from Exception.
+disable=E0611,E0202,R0801,E1101,E1002,R0904,E0712,raising-non-exception
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=10
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=20
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=1
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/python-api/.pylintrc_integrationtests b/python-api/.pylintrc_integrationtests
new file mode 100644
index 0000000..8cf9d92
--- /dev/null
+++ b/python-api/.pylintrc_integrationtests
@@ -0,0 +1,246 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+disable=E0611,E0202,R0801,E1101,E1002,R0201,F0401,W0141,R0904,too-few-public-methods
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=10
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=20
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=20
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=1
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/python-api/.pylintrc_unittests b/python-api/.pylintrc_unittests
new file mode 100644
index 0000000..a2c3f9f
--- /dev/null
+++ b/python-api/.pylintrc_unittests
@@ -0,0 +1,248 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+# * E0712(raising-non-exception) - pylint is unable to see glib.GError
+# inherits from Exception.
+disable=E0611,E0202,R0801,E1101,E1002,R0201,W0613,W0212,R0923,R0904,raising-non-exception
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=10
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=20
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=1
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/python-api/Makefile b/python-api/Makefile
new file mode 100644
index 0000000..79337f8
--- /dev/null
+++ b/python-api/Makefile
@@ -0,0 +1,56 @@
+.PHONY: lint unittests pytest integration test clean all
+
+lint: export PYTHONWARNINGS := ignore
+lint: export PYTHONPATH := ${PYTHONPATH}:${PWD}
+lint:
+ @rm -rf tests/unittests/__pycache__
+ @rm -rf tests/integrationtests/__pycache__
+ @rm -rf reports
+ @echo PYTHONWARNINGS=$$PYTHONWARNINGS
+ @echo PYTHONPATH=$$PYTHONPATH
+ pylint --rcfile=.pylintrc_gstswitch gstswitch
+ pylint --rcfile=.pylintrc_unittests tests/unittests/*
+ pylint --rcfile=.pylintrc_integrationtests tests/integrationtests/*.py
+ @echo "Code style is okay!!"
+
+unittests:
+ @rm -rf reports
+ @mkdir -p reports/coverage/unittests
+ py.test --cov gstswitch tests/unittests/ --pep8 -v -s
+
+pep8:
+ pep8 gstswitch
+ @echo "gstswitch is pep8 clean!!"
+ pep8 tests/unittests
+ @echo "unittests are pep8 clean!!"
+ pep8 tests/integrationtests
+ @echo "integration tests are pep8 clean!!"
+
+integration:
+ @rm -f ../tools/*.c.gcov
+ @rm -f ../tools/*.gcda
+ -wget http://imgur.com/tools/imgurbash.sh -O imgurbash.sh
+ -chmod +x imgurbash.sh
+ @rm -rf reports
+ @mkdir -p reports/coverage/integration
+ py.test --cov gstswitch tests/integrationtests/ --pep8 -s -v
+ -@mv htmlcov/*.* reports/coverage/integration
+ -rm imgurbash.sh*
+
+performance:
+ py.test tests/performancetests/*.py -v -s
+ make clean
+
+clean:
+ rm -rf .cache
+ rm -rf reports
+ rm -f *.data*
+ rm -f tests/integrationtests/*.data
+ rm -f *.log
+ rm -f *.html
+ rm -f *.sh.*
+ rm -f *.sh
+ rm -f .coverage
+
+
+test: lint unittests integration clean
diff --git a/python-api/README.md b/python-api/README.md
new file mode 100644
index 0000000..43a6ec5
--- /dev/null
+++ b/python-api/README.md
@@ -0,0 +1,29 @@
+##Running Tests
+```bash
+cd python-api
+```
+
+####Run Unittests
+```bash
+make unittests
+```
+
+####Run Integration Tests
+```bash
+make integration
+```
+
+####Lint and pep8 Tests
+```bash
+make lint
+make pep8
+```
+####Running everything together
+```bash
+make test
+```
+
+
+##Python API Sphinx Docs
+Sphinx documentation can be found at [http://gst-switch.readthedocs.org/en/latest/](http://gst-switch.readthedocs.org/en/latest/).
+
diff --git a/python-api/WRITING_TESTS.md b/python-api/WRITING_TESTS.md
new file mode 100644
index 0000000..be83752
--- /dev/null
+++ b/python-api/WRITING_TESTS.md
@@ -0,0 +1,69 @@
+##Writing Tests Using Python-API
+###Import the Modules
+Ensure that gst-switch/python-api/gstswitch is in PYTHONPATH:
+```python
+import sys
+sys.path.insert(0, install_dir + 'gst-switch/python-api/gstswitch')
+# install_dir is the path where the installation started
+```
+###Start the gst-switch Server
+```python
+from gstswitch.server import Server
+
+PATH = '/usr/bin'
+# The default location is '/usr/bin'. Change to wherever the gst-switch executables are located
+serv = Server(path=PATH, video_port=3000, audio_port=4000)
+serv.run()
+```
+
+###Add Some Sources
+```python
+from gstswitch.helpers import TestSources
+
+sources = TestSources(video_port=video_port, audio_port=audio_port)
+sources.new_test_video()
+sources.new_test_audio()
+```
+
+###Add a Preview to See the Output!!
+```python
+from gstswitch.helpers import PreviewSinks
+
+preview = PreviewSinks(video_port=3000, audio_port=4000)
+preview.run
+```
+
+###Remote Method Call Over DBus
+
+####Initialize the Controller
+```python
+from gstswitch.controller import Controller
+
+controller = Controller()
+controller.establish_connection()
+```
+####Calling Remote Methods
+* Get Compose Port: `port = controller.get_compose_port()`
+* Get Encode Port: `port = controller.get_encode_port()`
+* Get Audio Port: `port = controller.get_audio_port()`
+* Get All Preview Ports: `ports = controller.get_preview_ports()`
+* Change the PIP Mode: `result = controller.set_composite_mode(mode=1)`
+* Move or Adjust the PIP: `result = controller.adjust_pip(xpos=50, ypos=50, 0, 0)`
+* Switch the Channel. Channel is specified as `ord('A')`, `ord('a')` or `ord('b')`. `result = controller.switch(channel=ord('A'), port=3004)`
+* Start a New Record: `result = controller.new_record()`
+
+###Terminating the gst-switch Server
+The `server.run()` should always be kept in a try/finally block, so that if any Exception is caught, all processes terminate safely.
+i.e.
+```python
+server = Server(path=PATH, video_port=3000, audio_port=4000)
+try:
+ server.run()
+ # rest of the code comes here
+ # ......
+ server.terminate()
+finally:
+ if server.proc:
+ server.terminate()
+```
+`server.terminate()` can be replaced by `server.kill()`. In the latter SIGKILL will be sent to the process.
diff --git a/python-api/gstswitch/__init__.py b/python-api/gstswitch/__init__.py
new file mode 100644
index 0000000..9e7ab89
--- /dev/null
+++ b/python-api/gstswitch/__init__.py
@@ -0,0 +1,3 @@
+"""
+Python API for gst-switch
+"""
diff --git a/python-api/gstswitch/connection.py b/python-api/gstswitch/connection.py
new file mode 100644
index 0000000..b5d80ab
--- /dev/null
+++ b/python-api/gstswitch/connection.py
@@ -0,0 +1,485 @@
+"""
+connection deals with all low level method calls over dbus
+The Connection object is capable of invoking remote methods over dbus
+"""
+
+from gi.repository import Gio, GLib
+from .exception import ConnectionError
+import sys
+
+__all__ = ["Connection", ]
+
+
+class Connection(object):
+
+ """Class which makes all remote object class.
+ Deals with lower level connection and remote method invoking
+
+ :default bus-address: unix:abstract=gstswitch
+
+ :param: None
+ """
+ CONNECTION_FLAGS = Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT
+
+ def __init__(
+ self,
+ address="unix:abstract=gstswitch",
+ bus_name='info.duzy.gst.switch.SwitchController',
+ object_path="/info/duzy/gst/switch/SwitchController",
+ default_interface=("info.duzy.gst.switch."
+ "SwitchControllerInterface")):
+
+ super(Connection, self).__init__()
+ self.connection = None
+ self._address = None
+ self._bus_name = None
+ self._object_path = None
+ self._default_interface = None
+
+ self.address = address
+ self.bus_name = bus_name
+ self.object_path = object_path
+ self.default_interface = default_interface
+
+ @property
+ def address(self):
+ """Get the address
+ """
+ return self._address
+
+ @address.setter
+ def address(self, address):
+ """Set the Address
+ http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
+ """
+ if not address:
+ raise ValueError("Address '{0}' cannot be blank")
+ else:
+ adr = str(address)
+ if adr.find(':') > 0:
+ self._address = adr
+ else:
+ raise ValueError("Address must follow specifications mentioned"
+ " at http://dbus.freedesktop.org/doc/"
+ "dbus-specification.html#addresses")
+
+ @property
+ def bus_name(self):
+ """Get the bus name"""
+ if self._bus_name is None:
+ return None
+ return self._bus_name
+
+ @bus_name.setter
+ def bus_name(self, bus_name):
+ """Set the Bus Name
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-names-bus
+ """
+ if bus_name is None:
+ self._bus_name = None
+ return
+ bus = str(bus_name)
+ self._bus_name = bus
+
+ @property
+ def object_path(self):
+ """Get the object path"""
+ return self._object_path
+
+ @object_path.setter
+ def object_path(self, object_path):
+ """Set the object_path
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-marshaling-object-path
+ """
+ if not object_path:
+ raise ValueError("object_path '{0} cannot be blank'")
+ else:
+ obj = str(object_path)
+ if obj[0] == '/':
+ self._object_path = obj
+ else:
+ raise ValueError("object_path must follow specifications"
+ " mentioned at "
+ "http://dbus.freedesktop.org/doc/"
+ "dbus-specification.html"
+ "#message-protocol-marshaling-object-path""")
+
+ @property
+ def default_interface(self):
+ """Get the default interface"""
+ return self._default_interface
+
+ @default_interface.setter
+ def default_interface(self, default_interface):
+ """Set the default_interface
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-names-interface
+ """
+ if not default_interface:
+ raise ValueError("default_interface '{0} cannot be blank'")
+ else:
+ intr = str(default_interface)
+ if intr.count('.') > 1:
+ self._default_interface = intr
+ else:
+ raise ValueError("default_interface must follow "
+ "specifications mentioned at "
+ "http://dbus.freedesktop.org/"
+ "doc/dbus-specification.html"
+ "#message-protocol-names-interface")
+
+ def connect_dbus(self):
+ """Make a new connection using the parameters belonging to the class
+ to the gst-switch-srv over dbus.
+ Sets the self.connection
+
+ :params: None
+ :returns: Nothing
+ :raises ConnectionError: GError occurs while making a connection
+ """
+ try:
+ connection = Gio.DBusConnection.new_for_address_sync(
+ self.address,
+ self.CONNECTION_FLAGS,
+ None,
+ None)
+ self.connection = connection
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{1} ({0})".format(message, self.address)
+ raise ConnectionError(new_message)
+
+ def get_compose_port(self):
+ """get_compose_port(out i port);
+ Calls get_compose_port remotely
+
+ :param: None
+ :returns: tuple with first element compose port number
+ """
+ try:
+ args = None
+ connection = self.connection
+ port = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'get_compose_port',
+ args, GLib.VariantType.new("(i)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return port
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "get_compose_port")
+ raise ConnectionError(new_message)
+
+ def get_encode_port(self):
+ """get_encode_port(out i port);
+ Calls get_encode_port remotely
+
+ :param: None
+ :returns: tuple with first element encode port number
+ """
+ try:
+ args = None
+ connection = self.connection
+ port = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'get_encode_port',
+ args,
+ GLib.VariantType.new("(i)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return port
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "get_encode_port")
+ raise ConnectionError(new_message)
+
+ def get_audio_port(self):
+ """get_audio_port(out i port);
+ Calls get_audio_port remotely
+
+ :param: None
+ :returns: tuple wit first element audio port number
+ """
+ try:
+ args = None
+ connection = self.connection
+ port = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'get_audio_port',
+ args,
+ GLib.VariantType.new("(i)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return port
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "get_audio_port")
+ raise ConnectionError(new_message)
+
+ def get_preview_ports(self):
+ """get_preview_ports(out s ports);
+ Calls get_preview_ports remotely
+
+ :param: None
+ :returns: tuple with first element a string in the form of
+ '[(3002, 1, 7), (3003, 1, 8)]'
+ """
+ try:
+ args = None
+ connection = self.connection
+ ports = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'get_preview_ports',
+ args,
+ GLib.VariantType.new("(s)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return ports
+ except GLib.GError as error:
+ error = sys.exc_info()[1]
+ message = error.message
+ new_message = "{0}: {1}".format(message, "get_preview_ports")
+ raise ConnectionError(new_message)
+
+ def set_composite_mode(self, mode):
+ """set_composite_mode(in i channel,
+ out b result);
+ Calls set_composite_mode remotely
+
+ :param mode: new composite mode
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('(i)', (mode,))
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'set_composite_mode',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE, -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "set_composite_mode")
+ raise ConnectionError(new_message)
+
+ def set_encode_mode(self, channel):
+ """set_encode_mode(in i channel,
+ out b result);
+ Calls set_encode_mode remotely
+ **Does not do anything**
+
+ :param: channel
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('(i)', (channel,))
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'set_encode_mode',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "set_encode_mode")
+ raise ConnectionError(new_message)
+
+ def new_record(self):
+ """new_record(out b result);
+ Calls new_record remotely
+
+ :param: None:
+ returns: tuple with first element True if requested
+ """
+ try:
+ args = None
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'new_record',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "new_record")
+ raise ConnectionError(new_message)
+
+ def adjust_pip(self, xpos, ypos, width, height):
+ """adjust_pip(in i dx,
+ in i dy,
+ in i dw,
+ in i dh,
+ out u result);
+ Calls adjust_pip remotely
+
+ :param xpos: the X position of the PIP
+ :param ypos: the Y position of the PIP
+ :param width: the width of the PIP
+ :param height: the height of the PIP
+ :returns: tuple with first element as result -
+ PIP has been changed succefully
+ """
+ try:
+ args = GLib.Variant('(iiii)', (xpos, ypos, width, height,))
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'adjust_pip',
+ args,
+ GLib.VariantType.new("(u)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "adjust_pip")
+ raise ConnectionError(new_message)
+
+ def switch(self, channel, port):
+ """switch(in i channel,
+ in i port,
+ out b result);
+ Calls switch remotely
+
+ :param channel: The channel to be switched, 'A', 'B', 'a'
+ :param port: The target port number
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('(ii)', (channel, port,))
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'switch',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "switch")
+ raise ConnectionError(new_message)
+
+ def click_video(self, xpos, ypos, width, height):
+ """click_video(in i x,
+ in i y,
+ in i fw,
+ in i fh,
+ out b result);
+ Calls click_video remotely
+
+ :param xpos:
+ :param ypos:
+ :param width:
+ :param height:
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('(iiii)', (xpos, ypos, width, height,))
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'click_video',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "click_video")
+ raise ConnectionError(new_message)
+
+ def mark_face(self, faces):
+ """mark_face(in a(iiii) faces);
+ Calls mark_face remotely
+
+ :param faces: tuple having four elements
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('a(iiii)', faces)
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'mark_face',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "mark_face")
+ raise ConnectionError(new_message)
+
+ def mark_tracking(self, faces):
+ """mark_tracking(in a(iiii) faces);
+ Calls mark_tracking remotely
+
+ :param faces: tuple having four elements
+ :returns: tuple with first element True if requested
+ """
+ try:
+ args = GLib.Variant('a(iiii)', faces)
+ connection = self.connection
+ result = connection.call_sync(
+ self.bus_name,
+ self.object_path,
+ self.default_interface,
+ 'mark_tracking',
+ args,
+ GLib.VariantType.new("(b)"),
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None)
+ return result
+ except GLib.GError as error:
+ message = error.message
+ new_message = "{0}: {1}".format(message, "mark_tracking")
+ raise ConnectionError(new_message)
diff --git a/python-api/gstswitch/controller.py b/python-api/gstswitch/controller.py
new file mode 100644
index 0000000..8e2a6d2
--- /dev/null
+++ b/python-api/gstswitch/controller.py
@@ -0,0 +1,368 @@
+"""
+The controller is the interface for all remote method calls over dbus.
+The Controller class creates the controller,
+which can be used to invoke the remote methods.
+"""
+
+import ast
+from .connection import Connection
+from .exception import ConnectionReturnError
+
+__all__ = ["Controller", ]
+
+
+class Controller(object):
+
+ """A Class to control all interactions with the gst-switch-srv over dbus.
+ Provides the interface for higher level interactions
+
+ :param: None
+ """
+
+ def __init__(
+ self,
+ address="unix:abstract=gstswitch",
+ bus_name='info.duzy.gst.switch.SwitchController',
+ object_path="/info/duzy/gst/switch/SwitchController",
+ default_interface="info.duzy.gst.switch.SwitchControllerInterface"
+ ):
+
+ super(Controller, self).__init__()
+
+ self._address = None
+ self._bus_name = None
+ self._object_path = None
+ self._default_interface = None
+ self.connection = None
+
+ self.address = address
+ self.bus_name = bus_name
+ self.object_path = object_path
+ self.default_interface = default_interface
+
+ @property
+ def address(self):
+ """
+ Get the address
+ """
+ return self._address
+
+ @address.setter
+ def address(self, address):
+ """Set the Address
+ http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
+ """
+ if not address:
+ raise ValueError("Address '{0}' cannot be blank")
+ else:
+ adr = str(address)
+ if adr.find(':') > 0:
+ self._address = adr
+ else:
+ raise ValueError("Address must follow specifications mentioned"
+ " at http://dbus.freedesktop.org/doc/"
+ "dbus-specification.html#addresses")
+
+ @property
+ def bus_name(self):
+ """Get the bus name
+ """
+ if self._bus_name is None:
+ return None
+ return self._bus_name
+
+ @bus_name.setter
+ def bus_name(self, bus_name):
+ """Set the Bus Name
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-names-bus
+ """
+ if bus_name is None:
+ self._bus_name = None
+ return
+ bus = str(bus_name)
+ self._bus_name = bus
+
+ @property
+ def object_path(self):
+ """Get the object path
+ """
+ return self._object_path
+
+ @object_path.setter
+ def object_path(self, object_path):
+ """Set the object_path
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-marshaling-object-path
+ """
+ if not object_path:
+ raise ValueError("object_path '{0} cannot be blank'")
+ else:
+ obj = str(object_path)
+ if obj[0] == '/':
+ self._object_path = obj
+ else:
+ raise ValueError("object_path must follow specifications"
+ " mentioned at "
+ "http://dbus.freedesktop.org/doc/"
+ "dbus-specification.html"
+ "#message-protocol-marshaling-object-path""")
+
+ @property
+ def default_interface(self):
+ """Get the default interface
+ """
+ return self._default_interface
+
+ @default_interface.setter
+ def default_interface(self, default_interface):
+ """Set the default_interface
+ http://dbus.freedesktop.org/doc/dbus-specification.html\
+ #message-protocol-names-interface
+ """
+ if not default_interface:
+ raise ValueError("default_interface '{0} cannot be blank'")
+ else:
+ intr = str(default_interface)
+ if intr.count('.') > 1:
+ self._default_interface = intr
+ else:
+ raise ValueError("default_interface must follow "
+ "specifications mentioned at "
+ "http://dbus.freedesktop.org/"
+ "doc/dbus-specification.html"
+ "#message-protocol-names-interface")
+
+ def establish_connection(self):
+ """Establishes a fresh connection to the dbus
+ Connection stored as self.connection
+
+ :param: None
+ :returns: None
+ """
+ self.connection = Connection(
+ address=self.address,
+ bus_name=self.bus_name,
+ object_path=self.object_path,
+ default_interface=self.default_interface)
+
+ self.connection.connect_dbus()
+
+ def get_compose_port(self):
+ """Get the compose port number
+
+ :param: None
+ :returns: compose port number
+ """
+ conn = self.connection.get_compose_port()
+ try:
+ compose_port = conn.unpack()[0]
+ return compose_port
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values.'
+ 'Should return a GVariant tuple')
+
+ def get_encode_port(self):
+ """Get the encode port number
+
+ :param: None
+ :returns: encode port number
+ """
+ conn = self.connection.get_encode_port()
+ try:
+ encode_port = conn.unpack()[0]
+ return encode_port
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values.'
+ ' Should return a GVariant tuple')
+
+ def get_audio_port(self):
+ """Get the audio port number
+
+ :param: None
+ :returns: audio port number
+ """
+ conn = self.connection.get_audio_port()
+ try:
+ audio_port = conn.unpack()[0]
+ return audio_port
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+
+ def get_preview_ports(self):
+ """Get all the preview ports
+
+ :param: None
+ :returns: list of all preview ports
+ """
+ conn = self.connection.get_preview_ports()
+ try:
+ res = conn.unpack()[0]
+ preview_ports = self.parse_preview_ports(res)
+ return preview_ports
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+
+ def set_composite_mode(self, mode):
+ """Set the current composite mode. Modes between 0 and 3 are allowed.
+
+ :param mode: new composite mode
+ :returns: True when requested
+ """
+ self.establish_connection()
+ # only modes from 0 to 3 are supported
+ if mode >= 0 and mode <= 3:
+ try:
+ conn = self.connection.set_composite_mode(mode)
+ print conn
+ res = conn.unpack()[0]
+ if res is True:
+ print "Set composite mode to %s" % (str(mode))
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid '
+ 'values. Should return a '
+ 'GVariant tuple')
+ else:
+ pass
+ # raise some Exception
+ return res
+
+ def set_encode_mode(self, channel):
+ """Set the encode mode
+ WARNING: THIS DOES NOT WORK.
+
+ :param: channel
+ :returns: True when requested
+ """
+ self.establish_connection()
+ try:
+ conn = self.connection.set_encode_mode(channel)
+ res = conn.unpack()[0]
+ if res is True:
+ print "Set encode mode to %s" % (str(channel))
+ else:
+ pass
+ # raise some exception
+ return res
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+
+ def new_record(self):
+ """Start a new recording
+
+ :param: None
+ """
+ self.establish_connection()
+ try:
+ conn = self.connection.new_record()
+ res = conn.unpack()[0]
+ if res is True:
+ # logging
+ print "New record"
+ else:
+ pass
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+ return res
+
+ def adjust_pip(self, xpos, ypos, width, height):
+ """Change the PIP position and size
+
+ :param xpos: the x position of the PIP
+ :param ypos: the y position of the PIP
+ :param width: the width of the PIP
+ :param height: the height of the PIP
+ :returns: result - PIP has been changed succefully
+ """
+ self.establish_connection()
+ try:
+ conn = self.connection.adjust_pip(xpos, ypos, width, height)
+ res = conn.unpack()[0]
+ print "adjust pip xpos:%s ypos:%s w:%s h:%s" % (
+ str(xpos), str(ypos), str(width), str(height))
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+ # to-do - parse
+ return res
+
+ def switch(self, channel, port):
+ """Switch the channel to the target port
+
+ :param channel: The channel to be switched, 'A', 'B', 'a'
+ :param port: The target port number
+ :returns: True when requested
+ """
+ self.establish_connection()
+ try:
+ conn = self.connection.switch(channel, port)
+ res = conn.unpack()[0]
+ if res is True:
+ print "Switch channel:%s port:%s" % (str(channel), str(port))
+ else:
+ pass
+ return res
+ except AttributeError:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+
+ def click_video(self, xpos, ypos, width, height):
+ """User click on the video
+
+ :param xpos:
+ :param ypos:
+ :param width:
+ :param height:
+ :returns: True when requested
+ """
+ self.establish_connection()
+ try:
+ conn = self.connection.click_video(xpos, ypos, width, height)
+ res = conn.unpack()[0]
+ if res is True:
+ print "Click video: xpos:%s ypos:%s width:%s height:%s" % (
+ str(xpos), str(ypos), str(width), str(height))
+ else:
+ pass
+ except:
+ raise ConnectionReturnError('Connection returned invalid values. '
+ 'Should return a GVariant tuple')
+ return res
+
+ def mark_face(self, faces):
+ """Mark faces
+
+ :param faces: tuple having four elements
+ :returns: True when requested
+ """
+ # faces is list of a tuple of four elements
+ self.establish_connection()
+ self.connection.mark_face(faces)
+
+ def mark_tracking(self, faces):
+ """Mark tracking
+
+ :param faces: tuple having four elements
+ :returns: True when requested
+ """
+ self.establish_connection()
+ self.connection.mark_tracking(faces)
+
+ @classmethod
+ def parse_preview_ports(cls, res):
+ """Parses the preview_ports string"""
+ # res = '[(a, b, c), (a, b, c)*]'
+ try:
+ liststr = ast.literal_eval(res)
+ except (ValueError, SyntaxError):
+ raise ConnectionReturnError(("Connection returned "
+ "invalid values:{0}")
+ .format(res))
+ preview_ports = []
+ for tupl in liststr:
+ preview_ports.append(int(tupl[0]))
+ return preview_ports
diff --git a/python-api/gstswitch/exception.py b/python-api/gstswitch/exception.py
new file mode 100644
index 0000000..6189f38
--- /dev/null
+++ b/python-api/gstswitch/exception.py
@@ -0,0 +1,50 @@
+"""
+All custom exceptions come here
+"""
+
+__all__ = [
+ 'BaseError', 'PathError', 'ServerProcessError', 'ConnectionError',
+ 'ConnectionReturnError', 'RangeError', 'InvalidIndexError',
+]
+
+
+class BaseError(Exception):
+
+ """docstring for BaseError"""
+ pass
+
+
+class PathError(BaseError):
+
+ """docstring for PathError"""
+ pass
+
+
+class ServerProcessError(BaseError):
+
+ """docstring for ServerProcessError"""
+ pass
+
+
+class ConnectionError(BaseError):
+
+ """docstring for ConnectionError"""
+ pass
+
+
+class ConnectionReturnError(BaseError):
+
+ """docstring for ConnectionReturnError"""
+ pass
+
+
+class RangeError(BaseError):
+
+ """docstring for RangeError"""
+ pass
+
+
+class InvalidIndexError(BaseError):
+
+ """docstring for InvalidIndexError"""
+ pass
diff --git a/python-api/gstswitch/helpers.py b/python-api/gstswitch/helpers.py
new file mode 100644
index 0000000..09ce231
--- /dev/null
+++ b/python-api/gstswitch/helpers.py
@@ -0,0 +1,282 @@
+"""
+Has helper classes which create test video and audio sources.
+It is also possible to create a preview out source showing the
+compose port output.
+"""
+
+from gstswitch import testsource
+from .exception import RangeError, InvalidIndexError
+
+
+__all__ = ["TestSources", "PreviewSinks"]
+
+
+class TestSources(object):
+
+ """A Controller of test sources feeding into the
+ gst-switch-srv
+ :param width: The width of the output video
+ :param height: The height of the output video
+ :param pattern: The videotestsrc pattern of the output video
+ :param timeoverlay: True to enable a running time over video
+ :param clockoverlay: True to enable current clock time over video
+ """
+
+ def __init__(self, video_port=None, audio_port=None):
+ super(TestSources, self).__init__()
+ self._running_tests_video = []
+ self._running_tests_audio = []
+ self._audio_port = None
+ self._video_port = None
+
+ if video_port:
+ self.video_port = video_port
+ if audio_port:
+ self.audio_port = audio_port
+
+ @property
+ def video_port(self):
+ """Get the video port"""
+ return self._video_port
+
+ @video_port.setter
+ def video_port(self, video_port):
+ """Set the video port
+ :raises RangeError: Video Port must be range 1 to 65535
+ :raises TypeError: should be a integer or should be able to
+ covert to an integer
+ """
+ try:
+ i = int(video_port)
+ if i < 1 or i > 65535:
+ raise RangeError('video_port must be in range 1 to 65535')
+ else:
+ self._video_port = video_port
+ except TypeError:
+ raise TypeError("video_port must be a string or number,"
+ "not, '{0}'".format(type(video_port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ @property
+ def audio_port(self):
+ """Get the audio port"""
+ return self._audio_port
+
+ @audio_port.setter
+ def audio_port(self, audio_port):
+ """Set the audio port
+ :raises RangeError: audio port must be range 1 to 65535
+ :raises TypeError: should be a integer or should be able to
+ covert to an integer
+ """
+ try:
+ i = int(audio_port)
+ if i < 1 or i > 65535:
+ raise RangeError('audio port must be in range 1 to 65535')
+ else:
+ self._audio_port = audio_port
+ except TypeError:
+ raise TypeError("audio port must be a string or number,"
+ "not, '{0}'".format(type(audio_port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ @property
+ def running_tests_video(self):
+ """Get the currently running test video list"""
+ return self._running_tests_video
+
+ @running_tests_video.setter
+ def running_tests_video(self, tests):
+ """Set the running test video list
+ """
+ self._running_tests_video = tests
+
+ @property
+ def running_tests_audio(self):
+ """Get the currently running test audio list"""
+ return self._running_tests_audio
+
+ @running_tests_audio.setter
+ def running_tests_audio(self, tests):
+ """Set the currently running test audio list"""
+ self._running_tests_audio = tests
+
+ def new_test_video(self,
+ width=300,
+ height=200,
+ pattern=None,
+ timeoverlay=False,
+ clockoverlay=False):
+ """Start a new test video
+ :param port: The port of where the TCP stream will be sent
+ Should be same as video port of gst-switch-src
+ :param width: The width of the output video
+ :param height: The height of the output video
+ :param pattern: The videotestsrc pattern of the output video
+ :param timeoverlay: True to enable a running time over video
+ :param clockoverlay: True to enable current clock time over video
+ """
+ testsrc = testsource.VideoSrc(
+ self.video_port,
+ width,
+ height,
+ pattern,
+ timeoverlay,
+ clockoverlay)
+ testsrc.run()
+ self._running_tests_video.append(testsrc)
+
+ def get_test_video(self):
+ """Returns a list of processes acting as video test sources running
+ :returns: A list containing all video test sources running
+ """
+ i = 0
+ for test in self._running_tests_video:
+ print i, "pattern:", test.pattern
+ i += 1
+ return self._running_tests_video
+
+ def terminate_index_video(self, index):
+ """Terminate video test source specified by index
+ :param index: The index of the video source to terminate
+ Use get_test_video for finding the index
+ """
+ try:
+ testsrc = self._running_tests_video[int(index)]
+ except IndexError:
+ raise InvalidIndexError(
+ ("No video source with index:{0},"
+ "use get_test_video() to determine index")
+ .format(index))
+ except ValueError:
+ raise InvalidIndexError("Index should be a valid integer")
+ except TypeError:
+ raise InvalidIndexError("Index should be a valid integer")
+
+ print 'End source with pattern %s' % (str(testsrc.pattern))
+ testsrc.end()
+ self._running_tests_video.remove(self._running_tests_video[index])
+
+ def terminate_video(self):
+ """Terminate all test video sources
+ """
+ print 'TESTS:', self._running_tests_video
+ for _ in range(len(self._running_tests_video)):
+ self.terminate_index_video(0)
+
+ def new_test_audio(self, freq=110, wave=None):
+ """Start a new test audio
+ :param port: The port of where the TCP stream will be sent
+ Should be same as audio port of gst-switch-src
+ :param width: The width of the output audio
+ :param height: The height of the output audio
+ :param pattern: The audiotestsrc pattern of the output audio
+ :param timeoverlay: True to enable a running time over audio
+ :param clockoverlay: True to enable current clock time over audio
+ """
+ testsrc = testsource.AudioSrc(
+ self.audio_port,
+ freq,
+ wave)
+ testsrc.run()
+ # print testsrc
+ self._running_tests_audio.append(testsrc)
+ # print self._running_tests_audio
+
+ def get_test_audio(self):
+ """Returns a list of processes acting as audio test sources running
+ :returns: A list containing all audio test sources running
+ """
+ i = 0
+ for test in self._running_tests_audio:
+ print i, "wave:", test.wave
+ i += 1
+ return self._running_tests_audio
+
+ def terminate_index_audio(self, index):
+ """Terminate audio test source specified by index
+ :param index: The index of the audio source to terminate
+ Use get_test_audio for finding the index
+ """
+ try:
+ testsrc = self._running_tests_audio[int(index)]
+ except IndexError:
+ raise InvalidIndexError(
+ ("No audio source with index:{0},"
+ "use get_test_audio() to determine index")
+ .format(index))
+ except ValueError:
+ raise InvalidIndexError("Index should be a valid integer")
+ except TypeError:
+ raise InvalidIndexError("Index should be a valid integer")
+
+ print 'End source with wave %s' % (str(testsrc.wave))
+ testsrc.end()
+ self._running_tests_audio.remove(self._running_tests_audio[index])
+
+ def terminate_audio(self):
+ """Terminate all test audio sources
+ """
+ print 'TESTS:', self._running_tests_audio
+ for _ in range(len(self._running_tests_audio)):
+ self.terminate_index_audio(0)
+
+
+class PreviewSinks(object):
+
+ """A Controller for preview sinks to preview ports of gst-switch-srv
+ :param preview_port: The preview port to get the preview
+ """
+
+ def __init__(self, preview_port=3001):
+ super(PreviewSinks, self).__init__()
+ self._preview_port = None
+ self.preview = None
+
+ self.preview_port = preview_port
+
+ @property
+ def preview_port(self):
+ """Get the preview port"""
+ return self._preview_port
+
+ @preview_port.setter
+ def preview_port(self, preview_port):
+ """Set the preview port
+ :raises RangeError: preview port must be in range 1 to 65535
+ :raises TypeError: preview port must be a integer or convertable to
+ an integer
+ """
+ if not preview_port:
+ raise ValueError("Preview_port: '{0}' cannot be blank"
+ .format(preview_port))
+ else:
+ try:
+ i = int(preview_port)
+ if i < 1 or i > 65535:
+ raise RangeError(
+ 'Preview port must be in range 1 to 65535')
+ else:
+ self._preview_port = preview_port
+ except TypeError:
+ raise TypeError("Preview_port must be a string or number,"
+ "not, '{0}'".format(type(preview_port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ def run(self):
+ """Run the Preview Sink"""
+ self.preview = testsource.Preview(self.preview_port)
+ self.preview.run()
+ print 'start preview'
+
+ def terminate(self):
+ """End/Terminate the Preview Sink"""
+ try:
+ self.preview.end()
+ self.preview = None
+ print 'end preview'
+ except AttributeError:
+ raise AttributeError("No preview Sink to terminate")
diff --git a/python-api/gstswitch/server.py b/python-api/gstswitch/server.py
new file mode 100644
index 0000000..31f0deb
--- /dev/null
+++ b/python-api/gstswitch/server.py
@@ -0,0 +1,337 @@
+"""
+The server deals with all operations controlling gst-switch-srv
+These include all OS related tasks
+"""
+
+import os
+import signal
+import subprocess
+from distutils import spawn
+
+from errno import ENOENT
+from .exception import PathError, ServerProcessError
+from time import sleep
+
+
+__all__ = ["Server", ]
+
+
+TOOLS_DIR = '/'.join(os.getcwd().split('/')[:-1]) + '/tools/'
+
+
+class Server(object):
+
+ """Control all server related operations
+
+ :param path: Path where the executable gst-switch-srv
+ is located. Provide the full path.
+ By default looks in the current $PATH.
+ :param video_port: The video port number - default = 3000
+ :param audio_port: The audio port number - default = 4000
+ :param control_port: The control port number - default = 5000
+ :param record_file: The record file format
+ :returns: nothing
+ """
+ SLEEP_TIME = 0.5
+
+ def __init__(
+ self,
+ path=None,
+ video_port=3000,
+ audio_port=4000,
+ control_port=5000,
+ record_file=False):
+
+ super(Server, self).__init__()
+
+ self._path = None
+ self._video_port = None
+ self._audio_port = None
+ self._control_port = None
+ self._record_file = None
+ self.gst_option_string = ''
+
+ self.path = path
+ self.video_port = video_port
+ self.audio_port = audio_port
+ self.control_port = control_port
+ self.record_file = record_file
+
+ self.proc = None
+ self.pid = -1
+
+ @property
+ def path(self):
+ """Get the path"""
+ return self._path
+
+ @path.setter
+ def path(self, path):
+ """Set path
+ :raises ValueError: Path cannot be left blank
+ """
+ self._path = path
+
+ @property
+ def video_port(self):
+ """Get the video port"""
+ return self._video_port
+
+ @video_port.setter
+ def video_port(self, video_port):
+ """Set Video Port
+ :raises ValueError: Video Port cannot be left blank
+ :raises ValueError: Video Port must be in range 1 to 65535
+ :raises TypeError: Video Port must be a string or a number
+ """
+ if not video_port:
+ raise ValueError("Video Port '{0}' cannot be blank"
+ .format(video_port))
+ else:
+ try:
+ i = int(video_port)
+ if i < 1 or i > 65535:
+ raise ValueError('Video Port must be in range 1 to 65535')
+ else:
+ self._video_port = video_port
+ except TypeError:
+ raise TypeError("Video Port must be a string or a number, "
+ "not '{0}'".format(type(video_port)))
+
+ @property
+ def audio_port(self):
+ """Get the audio port"""
+ return self._audio_port
+
+ @audio_port.setter
+ def audio_port(self, audio_port):
+ """Set Audio Port
+ :raises ValueError: Audio Port cannot be left blank
+ :raises ValueError: Audio Port must be in range 1 to 65535
+ :raises TypeError: Audio Port must be a string or a number
+ """
+ if not audio_port:
+ raise ValueError("Audio Port '{0}' cannot be blank"
+ .format(audio_port))
+ else:
+ try:
+ i = int(audio_port)
+ if i < 1 or i > 65535:
+ raise ValueError('Audio Port must be in range 1 to 65535')
+ else:
+ self._audio_port = audio_port
+ except TypeError:
+ raise TypeError("Audio Port must be a string or a number,"
+ " not '{0}'".format(type(audio_port)))
+
+ @property
+ def control_port(self):
+ """Get the control port"""
+ return self._control_port
+
+ @control_port.setter
+ def control_port(self, control_port):
+ """Set Control Port
+ :raises ValueError: Control Port cannot be left blank
+ :raises ValueError: Control Port must be in range 1 to 65535
+ :raises TypeError: Control Port must be a string or a number
+ """
+ if not control_port:
+ raise ValueError("Control Port '{0}' cannot be blank"
+ .format(control_port))
+ else:
+ try:
+ i = int(control_port)
+ if i < 1 or i > 65535:
+ raise ValueError(
+ 'Control Port must be in range 1 to 65535')
+ else:
+ self._control_port = control_port
+ except TypeError:
+ raise TypeError("Control Port must be a string or a number,"
+ " not '{0}'".format(type(control_port)))
+
+ @property
+ def record_file(self):
+ """Get the record file"""
+ return self._record_file
+
+ @record_file.setter
+ def record_file(self, record_file):
+ """Set Record File
+ :raises ValueError: Non-string file format
+ :raises ValueError: Record File cannot have forward slashes
+ """
+ if record_file is False:
+ self._record_file = False
+ elif record_file is True:
+ self._record_file = True
+ else:
+ if not record_file:
+ raise ValueError("Record File: '{0}' "
+ "Non-string file format".format(record_file))
+ else:
+ rec = str(record_file)
+ if rec.find('/') < 0:
+ self._record_file = rec
+ else:
+ raise ValueError("Record File: '{0}' "
+ "cannot have forward slashes".format(rec))
+
+ def run(self, gst_option=''):
+ """Launch the server process
+
+ :param: None
+ :gst-option: Any gstreamer option.
+ Refer to http://www.linuxmanpages.com/man1/gst-launch-0.8.1.php#lbAF.
+ Multiple can be added separated by spaces
+ :returns: nothing
+ :raises IOError: Fail to open /dev/null (os.devnull)
+ :raises PathError: Unable to find gst-switch-srv at path specified
+ :raises ServerProcessError: Running gst-switch-srv
+ gives a OS based error.
+ """
+ self.gst_option_string = gst_option
+ print "Starting server"
+ self.proc = self._run_process()
+ if self.proc:
+ self.pid = self.proc.pid
+ sleep(self.SLEEP_TIME)
+
+ def _run_process(self):
+ """Non-public method: Runs the gst-switch-srv process
+ """
+ cmd = ['']
+ if not self.path:
+ srv_location = spawn.find_executable('gst-switch-srv')
+ if srv_location:
+ cmd[0] = srv_location
+ else:
+ raise PathError("Cannot find gst-switch-srv in $PATH.\
+ Please specify the path.")
+ else:
+ cmd[0] += os.path.join(self.path, 'gst-switch-srv')
+ if self.gst_option_string:
+ cmd += [self.gst_option_string]
+ cmd.append("--video-input-port={0}".format(self.video_port))
+ cmd.append("--audio-input-port={0}".format(self.audio_port))
+ cmd.append("--control-port={0}".format(self.control_port))
+ if self.record_file is False:
+ pass
+ elif self.record_file is True:
+ cmd.append("-r")
+ else:
+ if self.record_file is not False:
+ cmd.append("--record={0}".format(self.record_file))
+
+ proc = self._start_process(cmd)
+ return proc
+
+ def _start_process(self, cmd):
+ """Non-public method: Start a process
+
+ :param cmd: The command which needs to be executed
+ :returns: process created
+ """
+ print 'Creating process %s' % (cmd)
+ try:
+ with open('server.log', 'w') as tempf:
+ process = subprocess.Popen(
+ cmd,
+ stdout=tempf,
+ stderr=tempf,
+ bufsize=-1,
+ shell=False)
+ print cmd
+ return process
+ except OSError as error:
+ if error.errno == ENOENT:
+ raise PathError("Cannot find gst-switch-srv at path:"
+ " '{0}'".format(self.path))
+ else:
+ raise ServerProcessError("Internal error "
+ "while launching process")
+
+ @classmethod
+ def make_coverage(cls):
+ """Generate coverage
+ Calls 'make coverage' to generate coverage in .gcov files
+ """
+ cmd = 'make -C {0} coverage'.format(TOOLS_DIR)
+ print TOOLS_DIR
+ with open(os.devnull, 'w'):
+ proc = subprocess.Popen(
+ cmd.split(),
+ bufsize=-1,
+ shell=False)
+ out, _ = proc.communicate()
+ print out
+
+ def terminate(self, cov=False):
+ """Terminate the server.
+ self.proc is made None on success
+
+ :param: None
+ :returns: True when success
+ :raises ServerProcessError: Process does not exist
+ :raises ServerProcessError: Cannot terminate process. Try killing it
+ """
+ print 'Killing server'
+ proc = self.proc
+ if proc is None:
+ raise ServerProcessError('Server Process does not exist')
+ else:
+ try:
+ if cov:
+ self.gcov_flush()
+ self.make_coverage()
+ proc.terminate()
+ print 'Server Killed'
+ self.proc = None
+ return True
+ except OSError:
+ raise ServerProcessError("Cannot terminate server process. "
+ "Try killing it")
+
+ def kill(self, cov=False):
+ """Kill the server process by sending signal.SIGKILL
+ self.proc is made None on success
+
+ :param: None
+ :returns: True when success
+ :raises ServerProcessError: Process does not exist
+ :raises ServerProcessError: Cannot kill process
+ """
+ if self.proc is None:
+ raise ServerProcessError('Server Process does not exist')
+ else:
+ try:
+ if cov:
+ self.gcov_flush()
+ self.make_coverage()
+ os.kill(self.pid, signal.SIGKILL)
+ self.proc = None
+ return True
+ except OSError:
+ raise ServerProcessError('Cannot kill process')
+
+ def gcov_flush(self):
+ """Generate gcov coverage by sending the signal SIGUSR1
+ The generated gcda files are dumped in tools directory.
+ Does not kill the process
+
+ :param: None
+ :returns: True when success
+ :raises ServerProcessError: If Server is not running
+ :raises ServerProcessError: Unable to send signal
+ """
+
+ if self.proc is None:
+ raise ServerProcessError('Server process does not exist')
+ else:
+ try:
+ print "GCOV FLUSH"
+ os.kill(self.pid, signal.SIGUSR1)
+ return True
+ except OSError:
+ raise ServerProcessError('Unable to send signal')
diff --git a/python-api/gstswitch/testsource.py b/python-api/gstswitch/testsource.py
new file mode 100644
index 0000000..5f88d93
--- /dev/null
+++ b/python-api/gstswitch/testsource.py
@@ -0,0 +1,671 @@
+"""
+The testsource contains all gstreamer pipelines
+It provides the abse for all the other gstreamer
+components are build upon.
+"""
+
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst
+
+GObject.threads_init()
+Gst.init(None)
+
+from .exception import RangeError
+import random
+
+# from pipeline import *
+# IMPORTS
+
+__all__ = ["Preview", "VideoSrc", "AudioSrc"]
+
+
+class BasePipeline(Gst.Pipeline):
+
+ """A Basic pipeline
+ """
+
+ def __init__(self):
+ Gst.Pipeline.__init__(self)
+ self._playing = False
+
+ def play(self):
+ """Set the pipeline as playing"""
+ self._playing = True
+ self.set_state(Gst.State.PLAYING)
+
+ def pause(self):
+ """Pause the pipeline"""
+ self._playing = False
+ self.set_state(Gst.State.PAUSED)
+
+ def disable(self):
+ """Disable the pipeline"""
+ self._playing = False
+ self.set_state(Gst.State.NULL)
+
+ @classmethod
+ def make(cls, elem, description=''):
+ """Make a new gstreamer pipeline element
+ :param elem: The name of the element to be made
+ :param description: Description of the element to be made
+ returns: The element which is made
+ """
+ element = Gst.ElementFactory.make(elem, description)
+ return element
+
+
+class VideoPipeline(BasePipeline):
+
+ """A Video Pipeline which can be used by a Video Test Source
+ :param port: The port of where the TCP stream will be sent
+ Should be same as video port of gst-switch-src
+ :param width: The width of the output video
+ :param height: The height of the output video
+ :param pattern: The videotestsrc pattern of the output video
+ :param timeoverlay: True to enable a running time over video
+ :param clockoverlay: True to enable current clock time over video
+ """
+
+ def __init__(
+ self,
+ port,
+ host='localhost',
+ width=300,
+ height=200,
+ pattern=None,
+ timeoverlay=False,
+ clockoverlay=False):
+ super(VideoPipeline, self).__init__()
+
+ self.host = host
+
+ src = self.make_videotestsrc(pattern)
+ self.add(src)
+ vfilter = self.make_capsfilter(width, height)
+ self.add(vfilter)
+ src.link(vfilter)
+ gdppay = self.make_gdppay()
+ self.add(gdppay)
+ if timeoverlay:
+ _timeoverlay = self.make_timeoverlay()
+ if clockoverlay:
+ _clockoverlay = self.make_clockoverlay()
+ if timeoverlay and clockoverlay:
+ self.add(_timeoverlay)
+ self.add(_clockoverlay)
+ vfilter.link(_timeoverlay)
+ _timeoverlay.link(_clockoverlay)
+ _clockoverlay.link(gdppay)
+ if timeoverlay:
+ self.add(_timeoverlay)
+ vfilter.link(_timeoverlay)
+ _timeoverlay.link(gdppay)
+ if clockoverlay:
+ self.add(_clockoverlay)
+ vfilter.link(_clockoverlay)
+ _clockoverlay.link(gdppay)
+ else:
+ vfilter.link(gdppay)
+
+ sink = self.make_tcpclientsink(port)
+ self.add(sink)
+ gdppay.link(sink)
+
+ def make_videotestsrc(self, pattern):
+ """Return a videotestsrc element
+ :param pattern: The video test source pattern (1-20)
+ :return: A video test source element
+ """
+ element = self.make('videotestsrc', 'src')
+ element.set_property('pattern', int(pattern))
+ return element
+
+ def make_capsfilter(self, width, height):
+ """Return a caps filter
+ :param width: The width of the caps
+ :param height: The height of the caps
+ :returns: A caps filter element
+ """
+ element = self.make("capsfilter", "vfilter")
+ width = str(width)
+ height = str(height)
+ capsstring = "video/x-raw, format=(string)I420, width={0},\
+ height={1}".format(width, height)
+ print capsstring
+ caps = Gst.Caps.from_string(capsstring)
+ element.set_property('caps', caps)
+ return element
+
+ def make_gdppay(self):
+ """Return a gdppay element
+ :returns: A gdppay element
+ """
+ element = self.make('gdppay', 'gdppay')
+ return element
+
+ def make_timeoverlay(self):
+ """Return a time overlay element (Verdana bold 50)
+ :returns: a time overlay element
+ """
+ element = self.make('timeoverlay', 'timeoverlay')
+ element.set_property('font-desc', "Verdana bold 50")
+ return element
+
+ def make_clockoverlay(self):
+ """Return a clock overlay element (Verdana bold 50)
+ :returns: a clock overlay element
+ """
+ element = self.make('clockoverlay', 'clockoverlay')
+ element.set_property('font-desc', "Verdana bold 50")
+ return element
+
+ def make_tcpclientsink(self, port):
+ """Return a TCP client sink element
+ :port: Port to sink
+ :returns: A TCP client sink element
+ """
+ element = self.make('tcpclientsink', 'tcpclientsink')
+ element.set_property('host', self.host)
+ element.set_property('port', int(port))
+ return element
+
+
+class AudioPipeline(BasePipeline):
+
+ """docstring for AudioPipeline"""
+
+ def __init__(
+ self,
+ port,
+ host='localhost',
+ freq=110,
+ wave=None):
+ super(AudioPipeline, self).__init__()
+
+ self.host = host
+
+ src = self.make_audiotestsrc(freq, wave)
+ self.add(src)
+ gdppay = self.make_gdppay()
+ self.add(gdppay)
+ src.link(gdppay)
+ sink = self.make_tcpclientsink(port)
+ self.add(sink)
+ gdppay.link(sink)
+
+ def make_audiotestsrc(self, freq, wave=None):
+ """Return a Audio Source Element
+ :freq: The Frequency
+ :wave: The wave pattern (valid between 1 and 12)
+ """
+ element = self.make('audiotestsrc', 'src')
+ element.set_property('freq', int(freq))
+ element.set_property('wave', int(wave))
+ return element
+
+ def make_gdppay(self):
+ """Return a gdppay element
+ :returns: A gdppay element
+ """
+ element = self.make('gdppay', 'gdppay')
+ return element
+
+ def make_tcpclientsink(self, port):
+ """Return a TCP client sink element
+ :port: Port to sink
+ :returns: A TCP client sink element
+ """
+ element = self.make('tcpclientsink', 'tcpclientsink')
+ element.set_property('host', self.host)
+ element.set_property('port', int(port))
+ return element
+
+
+class PreviewPipeline(BasePipeline):
+
+ """Pipeline for usage by a Preview
+ :param port: The preview port
+ """
+
+ def __init__(self, port):
+ super(PreviewPipeline, self).__init__()
+ self.host = 'localhost'
+ self.preview_port = port
+ src = self.make_tcpclientsrc()
+ self.add(src)
+
+ gdpdepay = self.make_gdpdepay()
+ self.add(gdpdepay)
+ src.link(gdpdepay)
+
+ conv1 = self.make_videoconvert("conv1")
+ self.add(conv1)
+ gdpdepay.link(conv1)
+
+ cairo = self.make_cairooverlay()
+ self.add(cairo)
+ conv1.link(cairo)
+
+ conv2 = self.make_videoconvert("conv2")
+ self.add(conv2)
+ cairo.link(conv2)
+
+ sink = self.make_xvimagesink()
+ self.add(sink)
+ conv2.link(sink)
+
+ def make_tcpclientsrc(self):
+ """Return a TCP Client Source element
+ :param port: The port of the server
+ :returns: A TCP Client Source element
+ """
+ element = self.make('tcpclientsrc', 'tcpclientsrc')
+ element.set_property('host', self.host)
+ element.set_property('port', self.preview_port)
+ return element
+
+ def make_gdpdepay(self):
+ """Return a gdpdepay element
+ :returns: A gdpdepay element
+ """
+ element = self.make('gdpdepay', 'gdpdepay')
+ return element
+
+ def make_videoconvert(self, desc):
+ """Return a videoconvert element
+ :returns: A videoconvert element
+ """
+ element = self.make('videoconvert', desc)
+ return element
+
+ def make_cairooverlay(self):
+ """Return a cairooverlay element
+ :returns: A cairooverlay element
+ """
+ element = self.make('cairooverlay', 'cairooverlay')
+ return element
+
+ def make_xvimagesink(self):
+ """Return a xvimagesink element
+ :returns: A xvimagesink element
+ """
+ element = self.make('xvimagesink', 'xvimagesink')
+ element.set_property('sync', 'false')
+ return element
+
+
+class VideoSrc(object):
+
+ """A Test Video Source
+ :param width: The width of the output video
+ :param height: The height of the output video
+ :param pattern: The videotestsrc pattern of the output video
+ None for random
+ :param timeoverlay: True to enable a running time over video
+ :param clockoverlay: True to enable current clock time over video
+ """
+ HOST = 'localhost'
+
+ def __init__(
+ self,
+ port,
+ width=300,
+ height=200,
+ pattern=None,
+ timeoverlay=False,
+ clockoverlay=False):
+ super(VideoSrc, self).__init__()
+ self._port = None
+ self._width = None
+ self._height = None
+ self._pattern = None
+ self._timeoverlay = None
+ self._clockoverlay = None
+
+ self.port = port
+ self.width = width
+ self.height = height
+ self.pattern = self.generate_pattern(pattern)
+ self.timeoverlay = timeoverlay
+ self.clockoverlay = clockoverlay
+ self.pipeline = VideoPipeline(
+ self.port,
+ self.HOST,
+ self.width,
+ self.height,
+ self.pattern,
+ self.timeoverlay,
+ self.clockoverlay)
+
+ @property
+ def port(self):
+ """Get the Video Port"""
+ return self._port
+
+ @port.setter
+ def port(self, port):
+ """Set the video Port
+ :raises RangeError: Port not in range 1 to 65535
+ :raises TypeError: Port cannot be converted to integer
+ :raises ValueError: Port cannot be left blank
+ """
+ if not port:
+ raise ValueError("Port: '{0}' cannot be blank"
+ .format(port))
+ else:
+ try:
+ i = int(port)
+ if i < 1 or i > 65535:
+ raise RangeError('Port must be in range 1 to 65535')
+ else:
+ self._port = port
+ except TypeError:
+ raise TypeError("Port must be a string or number,"
+ "not, '{0}'".format(type(port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ @property
+ def width(self):
+ """Get the width"""
+ return self._width
+
+ @width.setter
+ def width(self, width):
+ """Set the Width
+ raises ValueError: Width must be a positive value
+ raises ValueError: Width must not be blank
+ raises TypeError: Width must be convertable into a float
+ """
+ if not width:
+ raise ValueError("Width: '{0}' cannot be blank"
+ .format(width))
+ try:
+ i = float(width)
+ if i <= 0:
+ raise ValueError("Width: '{0}' must be a valid positive value")
+ else:
+ self._width = width
+ except TypeError:
+ raise TypeError("Width must be a valid value not '{0}'"
+ .format(type(width)))
+
+ @property
+ def height(self):
+ """Get the height"""
+ return self._height
+
+ @height.setter
+ def height(self, height):
+ """Set the height
+ raises ValueError: Height must be a positive value
+ raises ValueError: Height must not be blank
+ raises TypeError: Height must be convertable into a float
+ """
+ if not height:
+ raise ValueError("Height: '{0}' cannot be blank"
+ .format(height))
+ try:
+ i = float(height)
+ if i <= 0:
+ raise ValueError(
+ "Height: '{0}' must be a valid positive value")
+ else:
+ self._height = height
+ except TypeError:
+ raise TypeError("Height must be a valid value not '{0}'"
+ .format(type(height)))
+
+ @property
+ def pattern(self):
+ """Get the Pattern"""
+ return self._pattern
+
+ @pattern.setter
+ def pattern(self, pattern):
+ """Set the Pattern
+ :raises RangeError: Pattern must be in range 0 to 19
+ :raises TypeError: Pattern must be convertable to an integer
+ """
+ try:
+ i = int(float(pattern))
+ if i < 0 or i > 19:
+ raise RangeError('Pattern must be in range 0 and 19')
+ else:
+ self._pattern = pattern
+ except ValueError:
+ raise TypeError("Pattern must be a valid number")
+
+ @property
+ def timeoverlay(self):
+ """Get the timeoverlay"""
+ return self._timeoverlay
+
+ @timeoverlay.setter
+ def timeoverlay(self, timeoverlay):
+ """Set the timeoverlay
+ :raises ValueError: Timeoverlay must be True or False
+ """
+ timeover = str(timeoverlay)
+ if timeover == 'True' or timeover == 'False':
+ self._timeoverlay = timeoverlay
+ else:
+ raise ValueError("Timeoverlay: '{0}' must be True of False"
+ .format(timeoverlay))
+
+ @property
+ def clockoverlay(self):
+ """Get the Clockoverlay"""
+ return self._clockoverlay
+
+ @clockoverlay.setter
+ def clockoverlay(self, clockoverlay):
+ """Set the Clockoverlay
+ :raises ValueError: Clockoverlay must be True or False
+ """
+ clockover = str(clockoverlay)
+ if clockover == 'True' or clockover == 'False':
+ self._clockoverlay = clockoverlay
+ else:
+ raise ValueError("Clockoverlay: '{0}' must be True of False"
+ .format(clockoverlay))
+
+ def run(self):
+ """Run the pipeline"""
+ self.pipeline.play()
+
+ def pause(self):
+ """End the pipeline"""
+ self.pipeline.pause()
+
+ def end(self):
+ """End/disable the pipeline"""
+ self.pipeline.disable()
+
+ @classmethod
+ def generate_pattern(cls, pattern):
+ """Generate a random pattern if not specified
+ """
+ if pattern is None:
+ pattern = random.randint(0, 19)
+ pattern = str(pattern)
+ return pattern
+
+
+class AudioSrc(object):
+
+ """docstring for AudioSrc"""
+
+ HOST = 'localhost'
+
+ def __init__(
+ self,
+ port,
+ freq=110,
+ wave=None):
+ super(AudioSrc, self).__init__()
+ self._port = None
+ self._freq = None
+ self._wave = None
+
+ self.port = port
+ self.freq = freq
+ self.wave = self.generate_wave(wave)
+ self.pipeline = AudioPipeline(
+ self.port,
+ self.HOST,
+ self.freq,
+ self.wave)
+
+ @property
+ def port(self):
+ """Get the Audio Port"""
+ return self._port
+
+ @port.setter
+ def port(self, port):
+ """Set the Audio Port
+ :raises RangeError: Port not in range 1 to 65535
+ :raises TypeError: Port cannot be converted to integer
+ :raises ValueError: Port cannot be left blank
+ """
+ if not port:
+ raise ValueError("Port: '{0}' cannot be blank"
+ .format(port))
+ else:
+ try:
+ i = int(port)
+ if i < 1 or i > 65535:
+ raise RangeError('Port must be in range 1 to 65535')
+ else:
+ self._port = port
+ except TypeError:
+ raise TypeError("Port must be a string or number,"
+ "not, '{0}'".format(type(port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ @property
+ def freq(self):
+ """Get the Frequency"""
+ return self._freq
+
+ @freq.setter
+ def freq(self, freq):
+ """Set the Frequency
+ raises ValueError: Width must be a positive value
+ raises ValueError: Width must not be blank
+ raises TypeError: Width must be convertable into a float
+ """
+ if not freq:
+ raise ValueError("Frequency: '{0}' cannot be blank"
+ .format(freq))
+ else:
+ try:
+ i = int(freq)
+ if i < 1:
+ raise RangeError("Frequency must be a positive value")
+ else:
+ self._freq = freq
+ except TypeError:
+ raise TypeError("Frequency must be a string or number,"
+ "not, '{0}'".format(type(freq)))
+ except ValueError:
+ raise TypeError("Frequency must be a valid number")
+
+ @property
+ def wave(self):
+ """Get the wave number"""
+ return self._wave
+
+ @wave.setter
+ def wave(self, wave):
+ """Set the Wave number
+ :raises RangeError: Wave must be in range 0 to 12
+ :raises TypeError: Wave must be convertable to integer
+ """
+ try:
+ i = int(float(wave))
+ if i < 0 or i > 12:
+ raise RangeError('Wave must be in range 0 and 12')
+ else:
+ self._wave = wave
+ except ValueError:
+ raise TypeError("Wave must be a valid number")
+
+ def run(self):
+ """Run the pipeline"""
+ self.pipeline.play()
+
+ def pause(self):
+ """End the pipeline"""
+ self.pipeline.pause()
+
+ def end(self):
+ """End/disable the pipeline"""
+ self.pipeline.disable()
+
+ @classmethod
+ def generate_wave(cls, wave):
+ """Generate a random wave if not specified
+ """
+ if wave is None:
+ wave = random.randint(0, 12)
+ wave = str(wave)
+ return wave
+
+
+class Preview(object):
+
+ """A Preview Element
+ :param port: The preview port
+ """
+
+ def __init__(self, port):
+ super(Preview, self).__init__()
+ self._preview_port = None
+
+ self.preview_port = port
+ self.pipeline = PreviewPipeline(self.preview_port)
+
+ @property
+ def preview_port(self):
+ """Get the Preview Port"""
+ return self._preview_port
+
+ @preview_port.setter
+ def preview_port(self, preview_port):
+ """Set the Preview Port
+ :raises RangeError: Port not in range 1 to 65535
+ :raises TypeError: Port cannot be converted to integer
+ :raises ValueError: Port cannot be left blank
+ """
+ if not preview_port:
+ raise ValueError("preview_port: '{0}' cannot be blank"
+ .format(preview_port))
+ else:
+ try:
+ i = int(preview_port)
+ if i < 1 or i > 65535:
+ raise RangeError(
+ 'preview_port must be in range 1 to 65535')
+ else:
+ self._preview_port = preview_port
+ except TypeError:
+ raise TypeError("preview_port must be a string or number,"
+ "not, '{0}'".format(type(preview_port)))
+ except ValueError:
+ raise TypeError("Port must be a valid number")
+
+ def run(self):
+ """Run the pipeline"""
+ self.pipeline.play()
+
+ def pause(self):
+ """Pause the pipeline"""
+ self.pipeline.pause()
+
+ def end(self):
+ """End/disable the pipeline"""
+ self.pipeline.disable()
diff --git a/python-api/scripts/__init__.py b/python-api/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/python-api/scripts/dbusConnect.py b/python-api/scripts/dbusConnect.py
new file mode 100755
index 0000000..2684828
--- /dev/null
+++ b/python-api/scripts/dbusConnect.py
@@ -0,0 +1,19 @@
+ #!usr/bin/env python
+import dbus
+import dbus.mainloop.glib
+import os
+import time
+import gobject
+
+def main():
+ loop = gobject.MainLoop()
+ bus = dbus.SessionBus()
+ obj = bus.get_object("info.duzy.gst_switch.SwitchControllerInterface","/info/duzy/gst_switch/SwitchController")
+ print obj.Introspect()
+ loop.run()
+
+if __name__=="__main__":
+ os.environ["DBUS_SESSION_BUS_ADDRESS"] = "unix:abstract=gstswitch"
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ main()
+
\ No newline at end of file
diff --git a/python-api/scripts/dbusConnect.sh b/python-api/scripts/dbusConnect.sh
new file mode 100755
index 0000000..539d6bd
--- /dev/null
+++ b/python-api/scripts/dbusConnect.sh
@@ -0,0 +1,10 @@
+dbus-send \
+ --address="unix:abstract=gstswitch" \
+ --print-reply=literal \
+ --dest="info.duzy.gst_switch.SwitchClientInterface" \
+ /info/duzy/gst_switch/SwitchController \
+ info.duzy.gst_switch.SwitchControllerInterface.get_compose_port
+
+gdbus introspect --address unix:abstract=gstswitch \
+ --dest info.duzy.gst.switch.SwitchUIInterface \
+ --object-path /info/duzy/gst/switch/SwitchUI
diff --git a/python-api/scripts/dbusConnect2.py b/python-api/scripts/dbusConnect2.py
new file mode 100755
index 0000000..b23ba29
--- /dev/null
+++ b/python-api/scripts/dbusConnect2.py
@@ -0,0 +1,32 @@
+from gi.repository import Gio, GLib
+from time import sleep
+
+address = "unix:abstract=gstswitch"
+name = None
+object_path = "/info/duzy/gst/switch/SwitchController"
+
+
+# a = connection.call_sync(
+# name, object_path, 'org.freedesktop.DBus.Introspectable', 'Introspect',
+# None, GLib.VariantType.new("(s)"), Gio.DBusCallFlags.NONE, -1,
+# None)
+#mode = 1
+#print type(mode), mode
+mode=0
+while mode<=3:
+ print "mode:", mode
+ connection = Gio.DBusConnection.new_for_address_sync(
+ address,
+ Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT,
+ None, None)
+ args = GLib.Variant('(i)',(mode,))
+ print "args:", args
+ b = connection.call_sync(
+ name, object_path, 'info.duzy.gst.switch.SwitchControllerInterface', 'set_composite_mode',
+ args, GLib.VariantType.new("(b)"), Gio.DBusCallFlags.NONE, -1,
+ None)
+ print b.unpack()[0],type(b.unpack()[0])
+ mode += 1
+ if mode==4:
+ mode=0
+ sleep(0.5)
\ No newline at end of file
diff --git a/python-api/scripts/dbusConnection3.py b/python-api/scripts/dbusConnection3.py
new file mode 100644
index 0000000..ba08601
--- /dev/null
+++ b/python-api/scripts/dbusConnection3.py
@@ -0,0 +1,33 @@
+from gi.repository import Gio, GLib
+from time import sleep
+
+address = "unix:abstract=gstswitch"
+name = None
+object_path = "/info/duzy/gst/switch/SwitchController"
+
+
+# a = connection.call_sync(
+# name, object_path, 'org.freedesktop.DBus.Introspectable', 'Introspect',
+# None, GLib.VariantType.new("(s)"), Gio.DBusCallFlags.NONE, -1,
+# None)
+#mode = 1
+#print type(mode), mode
+# mode=0
+# while mode<=3:
+ # print "mode:", mode
+connection = Gio.DBusConnection.new_for_address_sync(
+ address,
+ Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT,
+ None, None)
+# args = GLib.Variant('(i)',(mode,))
+# print "args:", args
+args = None
+b = connection.call_sync(
+ name, object_path, 'info.duzy.gst.switch.SwitchControllerInterface', 'get_preview_ports',
+ args, GLib.VariantType.new("(s)"), Gio.DBusCallFlags.NONE, -1,
+ None)
+print b, b.unpack()[0],type(b.unpack()[0])
+# mode += 1
+# if mode==4:
+ # mode=0
+sleep(0.5)
\ No newline at end of file
diff --git a/python-api/scripts/switch_infinite_test.py b/python-api/scripts/switch_infinite_test.py
new file mode 100755
index 0000000..830f979
--- /dev/null
+++ b/python-api/scripts/switch_infinite_test.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../..")))
+# should be in PYTHONPATH
+from gstswitch.server import Server
+from gstswitch.testsource import BasePipeline
+from gstswitch.helpers import *
+from gstswitch.controller import Controller
+import gi
+import subprocess
+import time
+import psutil
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst
+GObject.threads_init()
+Gst.init(None)
+
+
+
+
+
+PATH = '/usr/bin/'
+INTERVAL = 1.0
+
+
+
+
+s = Server(path=PATH)
+v_port = s.video_port
+a_port = s.audio_port
+try:
+ s.run()
+
+ cmd_vid1 = PATH
+ cmd_vid2 = PATH
+ cmd_vid1 += """gst-launch-1.0 videotestsrc pattern=1 ! \
+ video/x-raw, width=300, height=200 !\
+ gdppay !
+ tcpclientsink port={0}
+ """.format(v_port)
+ print cmd_vid1
+
+ cmd_vid2 += """gst-launch-1.0 videotestsrc ! \
+ video/x-raw, width=300, height=200 !\
+ gdppay !
+ tcpclientsink port={0}
+ """.format(v_port)
+ print cmd_vid2
+
+ vid1 = subprocess.Popen(
+ cmd_vid1.split(),
+ bufsize=-1,
+ shell=False)
+ vid2 = subprocess.Popen(
+ cmd_vid2.split(),
+ bufsize=-1,
+ shell=False)
+ time.sleep(1)
+ sources = TestSources(audio_port=a_port)
+ sources.new_test_audio()
+ sources.new_test_audio()
+
+ controller = Controller()
+ controller.establish_connection()
+
+ cmd = PATH
+ cmd += """gst-switch-ui"""
+ cmd = " ".join(cmd.split())
+ with open('ui.log', 'w') as tempf:
+ ui_process = subprocess.Popen(
+ cmd.split(),
+ stdout=tempf,
+ stderr=tempf,
+ bufsize=-1,
+ shell=False)
+
+ params = [(65,3004), (65, 3003)]
+ i = 0
+ p = psutil.Process(s.pid)
+ cpu_usage_file = open("cpu.log", "w")
+ while 1:
+ # print i
+ cpu = p.get_cpu_percent(interval=INTERVAL)
+ mem = p.get_memory_percent()
+ print cpu
+ print mem
+ cpu_usage_file.write(str(cpu) + '\n' + str(mem) + '\n')
+ controller.switch(params[i][0], params[i][1])
+ # time.sleep(1)
+ i+=1
+ i%=2
+
+ #end all
+ sources.terminate_audio()
+ vid1.terminate()
+ vid2.terminate()
+ s.terminate()
+finally:
+ if s.proc:
+ s.kill()
+
diff --git a/python-api/scripts/test.py b/python-api/scripts/test.py
new file mode 100755
index 0000000..311bfd6
--- /dev/null
+++ b/python-api/scripts/test.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# WARNING:
+# THIS FILE IS ONLY FOR MY PERSONAL TESTING PURPOSES.
+# IT IS NOT MADE TO AND SHOULD NOT BE TESTING THE ENTIRE API
+
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst
+
+GObject.threads_init()
+Gst.init(None)
+from gstswitch.server import Server
+from gstswitch.helpers import *
+from gstswitch.controller import Controller
+import time
+
+import gi
+gi.require_version('Gst', '1.0')
+
+# all executables (gst-launch-1.0, gst-switch-srv, gst-switch-ui, gst-switch-cap) at this path
+path = '/usr/bin/'
+# path = '/usr/bin/'
+s = Server(path)
+try:
+ s.run() # launches the server default parameters
+ video_port = s.video_port
+ audio_port = s.audio_port
+ # connects a gstreamer module to view the output of the gst-switch-srv
+ output = PreviewSinks()
+ output.run()
+ # adding two test video sources
+ sources = TestSources(video_port=video_port, audio_port=audio_port)
+
+ sources.new_test_audio()
+ sources.new_test_audio()
+
+ sources.new_test_video()
+ sources.new_test_video()
+ sources.new_test_video(timeoverlay=True)
+
+ controller = Controller()
+ controller.establish_connection()
+ res = []
+ tests_get = [controller.get_compose_port,
+ controller.get_encode_port,
+ controller.get_audio_port,
+ controller.get_preview_ports,
+ ]
+
+ test_set_composite_mode = controller.set_composite_mode
+
+ # testing random 10 modes
+ for x in tests_get:
+ print x.__name__, x()
+ time.sleep(1)
+ modes = [0, 3, 2, 1, 0, 1, 2, 3, 0]
+ for mode in modes:
+ print 'composite mode=', mode
+ test_set_composite_mode(mode)
+ time.sleep(0.6)
+ output.terminate()
+ sources.terminate_video()
+ sources.terminate_audio()
+ s.terminate()
+
+finally:
+ if s.proc:
+ s.kill()
diff --git a/python-api/tests/integrationtests/__init__.py b/python-api/tests/integrationtests/__init__.py
new file mode 100644
index 0000000..3f89cd7
--- /dev/null
+++ b/python-api/tests/integrationtests/__init__.py
@@ -0,0 +1,3 @@
+"""
+Integration tests for gst-switch and Python API
+"""
diff --git a/python-api/tests/integrationtests/compare.py b/python-api/tests/integrationtests/compare.py
new file mode 100644
index 0000000..48d80d4
--- /dev/null
+++ b/python-api/tests/integrationtests/compare.py
@@ -0,0 +1,139 @@
+"""
+Comparison between two videos
+Generates reference frames and compares test frames with them
+"""
+
+import os
+
+from scipy.misc import imread
+from scipy.linalg import norm
+
+import tempfile
+
+import subprocess
+
+__all__ = [
+ 'GenerateReferenceFrames',
+ 'CompareVideo',
+]
+
+
+CONV = None
+if os.system('which ffmpeg > /dev/null') == 0:
+ CONV = 'ffmpeg'
+elif os.system('which avconv > /dev/null') == 0:
+ CONV = 'avconv'
+else:
+ raise SystemError("Need ffmpeg or avcon tools.")
+
+
+class BaseCompareVideo(object):
+
+ """Base class containing image operations"""
+
+ TESTS = {
+ 'composite_mode_0': 0,
+ 'composite_mode_1': 1,
+ 'composite_mode_2': 2,
+ 'composite_mode_3': 3,
+ 'adjust_pip_4': 4
+ }
+ REF_FRAME_DIR = os.getcwd() + '/tests/integrationtests/reference_frames'
+
+ def __init__(self):
+ pass
+
+ def comp_image(self, image1, image2):
+ """Compare two images"""
+ file1, file2 = (image1, image2)
+ # read images as 2D arrays (convert to grayscale for simplicity)
+ img1 = imread(file1).astype(float)
+ img2 = imread(file2).astype(float)
+ # compare
+ n_0 = self.compare_images(img1, img2)
+ return n_0 * 1.0 / img1.size
+ # print "Manhattan norm:", n_m, "/ per pixel:", n_m/img1.size
+ # print "Zero norm:", n_0, "/ per pixel:", n_0*1.0/img1.size
+
+ def normalize(self, arr):
+ """Normalize an image"""
+ rng = arr.max() - arr.min() + 1e-10
+ amin = arr.min()
+ return (arr - amin) * 255 / rng
+
+ def compare_images(self, img1, img2):
+ """Calculate the zero norm between two images"""
+ # normalize to compensate for exposure difference,
+ # this may be unnecessary
+ # consider disabling it
+ # img1 = self.normalize(img1)
+ # img2 = self.normalize(img2)
+ # calculate the difference and its norms
+ diff = img1 - img2 # elementwise for scipy arrays
+ # m_norm = sum(abs(diff)) # Manhattan norm
+ z_norm = norm(diff.ravel(), 0) # Zero norm
+ return z_norm
+
+ def generate_frames(self, directory=REF_FRAME_DIR):
+ """Generate reference frames in directory"""
+ if not os.path.exists(directory):
+ os.mkdir(directory)
+
+ cmd1 = CONV + " -i {0} -ss 00:00:02.000 -f image2 -vframes 1 \
+ {1}/out{2}_1.png".format(self.video, directory, self.TESTS[self.test])
+ cmd2 = CONV + " -i {0} -ss 00:00:05.000 -f image2 -vframes 1 \
+ {1}/out{2}_2.png".format(self.video, directory, self.TESTS[self.test])
+ # print cmd
+ proc = subprocess.Popen(
+ cmd1.split(),
+ bufsize=-1,
+ shell=False)
+ proc.wait()
+
+ proc = subprocess.Popen(
+ cmd2.split(),
+ bufsize=-1,
+ shell=False)
+ proc.wait()
+
+
+class GenerateReferenceFrames(BaseCompareVideo):
+
+ """Generate the original frames for storage in reference_frames/
+ """
+
+ def __init__(self, test, video):
+ super(GenerateReferenceFrames, self).__init__()
+ self.test = test
+ self.video = video
+
+
+class CompareVideo(BaseCompareVideo):
+
+ """Compare a video with pre-stored frames
+ :parameter test: The test which is conducted.
+ self.TESTS stores these tests and maps them with frames
+ :parameter video: The video which has to be compared
+ """
+
+ def __init__(self, test, video):
+ super(CompareVideo, self).__init__()
+ self.test_frame_dir = tempfile.mkdtemp()
+ self.test = test
+ self.video = video
+ print self.test_frame_dir
+
+ def compare(self):
+ """Compare videos"""
+ img1 = '/out{0}_1.png'.format(self.TESTS[self.test])
+ img2 = '/out{0}_2.png'.format(self.TESTS[self.test])
+
+ self.generate_frames(self.test_frame_dir)
+
+ res1 = self.comp_image(
+ self.REF_FRAME_DIR + img1,
+ self.test_frame_dir + img1)
+ res2 = self.comp_image(
+ self.REF_FRAME_DIR + img2,
+ self.test_frame_dir + img2)
+ return (res1, res2)
diff --git a/python-api/tests/integrationtests/generate_reference_frames.py b/python-api/tests/integrationtests/generate_reference_frames.py
new file mode 100644
index 0000000..a89fa82
--- /dev/null
+++ b/python-api/tests/integrationtests/generate_reference_frames.py
@@ -0,0 +1,50 @@
+"""
+Used for generating reference frames
+Usually should be run just once
+"""
+
+from integrationtests.compare import GenerateReferenceFrames
+from integrationtests.test_controller import TestSetCompositeMode
+from integrationtests.test_controller import TestAdjustPIP
+
+
+def set_composite_mode_ref_frames(mode):
+ """Set the composite mode for indexing the frames"""
+ test = 'composite_mode_{0}'.format(mode)
+ video = 'output-{0}.data'.format(mode)
+ gen = GenerateReferenceFrames(test, video)
+ gen.generate_frames()
+
+
+def adjust_pip_ref_frames(index):
+ """Set the index of frames"""
+ test = 'adjust_pip_{0}'.format(index)
+ video = 'output-{0}.data'.format(index)
+ gen = GenerateReferenceFrames(test, video)
+ gen.generate_frames()
+
+
+def main():
+ """Generate reference frames"""
+ test1 = TestSetCompositeMode()
+ for i in range(4):
+ test1.set_composite_mode(i, True)
+ set_composite_mode_ref_frames(i)
+
+ test2 = TestAdjustPIP()
+ dic = [
+ [50, 75, 0, 0],
+ ]
+ for i in range(4, 5):
+ test2.adjust_pip(
+ dic[i - 4][0],
+ dic[i - 4][1],
+ dic[i - 4][2],
+ dic[i - 4][3],
+ i,
+ True)
+ adjust_pip_ref_frames(i)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python-api/tests/integrationtests/reference_frames/out0_1.png b/python-api/tests/integrationtests/reference_frames/out0_1.png
new file mode 100644
index 0000000..6fd461a
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out0_1.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out0_2.png b/python-api/tests/integrationtests/reference_frames/out0_2.png
new file mode 100644
index 0000000..1251935
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out0_2.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out1_1.png b/python-api/tests/integrationtests/reference_frames/out1_1.png
new file mode 100644
index 0000000..6fd461a
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out1_1.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out1_2.png b/python-api/tests/integrationtests/reference_frames/out1_2.png
new file mode 100644
index 0000000..5b74b08
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out1_2.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out2_1.png b/python-api/tests/integrationtests/reference_frames/out2_1.png
new file mode 100644
index 0000000..6fd461a
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out2_1.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out2_2.png b/python-api/tests/integrationtests/reference_frames/out2_2.png
new file mode 100644
index 0000000..e54510f
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out2_2.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out3_1.png b/python-api/tests/integrationtests/reference_frames/out3_1.png
new file mode 100644
index 0000000..6fd461a
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out3_1.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out3_2.png b/python-api/tests/integrationtests/reference_frames/out3_2.png
new file mode 100644
index 0000000..6fd461a
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out3_2.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out4_1.png b/python-api/tests/integrationtests/reference_frames/out4_1.png
new file mode 100644
index 0000000..5b74b08
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out4_1.png differ
diff --git a/python-api/tests/integrationtests/reference_frames/out4_2.png b/python-api/tests/integrationtests/reference_frames/out4_2.png
new file mode 100644
index 0000000..da44216
Binary files /dev/null and b/python-api/tests/integrationtests/reference_frames/out4_2.png differ
diff --git a/python-api/tests/integrationtests/test_controller.py b/python-api/tests/integrationtests/test_controller.py
new file mode 100644
index 0000000..0936748
--- /dev/null
+++ b/python-api/tests/integrationtests/test_controller.py
@@ -0,0 +1,722 @@
+"""
+Integration Tests for the dbus Controller
+"""
+
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.server import Server
+from gstswitch.helpers import TestSources, PreviewSinks
+from gstswitch.controller import Controller
+import time
+import datetime
+
+from integrationtests.compare import CompareVideo
+
+import subprocess
+
+# PATH = os.getenv("HOME") + '/gst/stage/bin/'
+PATH = '../tools/'
+
+
+class TestEstablishConnection(object):
+
+ """Test establish_connection method"""
+ NUM = 1
+ # fails above 3
+
+ def establish_connection(self):
+ """Create Controller object and call establish_connection"""
+ controller = Controller()
+ controller.establish_connection()
+ # print controller.connection
+ assert controller.connection is not None
+
+ def test_establish(self):
+ """Test for establish_connection"""
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ for i in range(self.NUM):
+ print i
+ self.establish_connection()
+ serv.terminate(1)
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class TestGetComposePort(object):
+
+ """Test get_compose_port method"""
+ NUM = 1
+ FACTOR = 1
+
+ def get_compose_port(self):
+ """Create Controller and call get_compose_port method"""
+ res = []
+ controller = Controller()
+ controller.establish_connection()
+ for _ in range(self.NUM * self.FACTOR):
+ res.append(controller.get_compose_port())
+ return res
+
+ def test_compose_ports(self):
+ """Test get_compose_port"""
+ res = []
+ expected_result = []
+ for i in range(self.NUM):
+ video_port = (i + 7) * 1000
+ expected_result.append([video_port + 1] * self.NUM * self.FACTOR)
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video()
+ sources.new_test_video()
+
+ res.append(self.get_compose_port())
+ sources.terminate_video()
+ serv.terminate(1)
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ set_expected = [tuple(i) for i in expected_result]
+ set_res = [tuple(i) for i in res]
+ assert set(set_expected) == set(set_res)
+
+
+class TestGetEncodePort(object):
+
+ """Test get_encode_port method"""
+ NUM = 1
+ FACTOR = 1
+
+ def get_encode_port(self):
+ """Create a Controller object and call get_encode_port method"""
+ res = []
+ controller = Controller()
+ controller.establish_connection()
+ for _ in range(self.NUM * self.FACTOR):
+ res.append(controller.get_encode_port())
+ return res
+
+ def test_encode_ports(self):
+ """Test get_encode_port"""
+ res = []
+ expected_result = []
+ for i in range(self.NUM):
+ video_port = (i + 8) * 1000
+ expected_result.append([video_port + 2] * self.NUM * self.FACTOR)
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video()
+ sources.new_test_video()
+
+ res.append(self.get_encode_port())
+ sources.terminate_video()
+ serv.terminate(1)
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ set_expected = [tuple(i) for i in expected_result]
+ set_res = [tuple(i) for i in res]
+ assert set(set_expected) == set(set_res)
+
+
+class TestGetAudioPort(object):
+
+ """Test get_audio_port method"""
+ NUM = 1
+ FACTOR = 1
+
+ def get_audio_port(self):
+ """Create Controller object and call get_audio_port method"""
+ res = []
+ controller = Controller()
+ controller.establish_connection()
+ for _ in range(self.NUM * self.FACTOR):
+ res.append(controller.get_audio_port())
+ return res
+
+ def test_audio_ports(self):
+ """Test get_audio_port"""
+ res = []
+ expected_result = []
+ for i in range(1, self.NUM + 1):
+ audio_port = (i + 10) * 1000
+ expected_result.append([3003] * self.NUM * self.FACTOR)
+ serv = Server(path=PATH, video_port=3000, audio_port=audio_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000, audio_port=audio_port)
+ sources.new_test_audio()
+
+ res.append(self.get_audio_port())
+
+ sources.terminate_audio()
+ serv.terminate(1)
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+ # print res
+ # print expected_result
+ set_expected = [tuple(i) for i in expected_result]
+ set_res = [tuple(i) for i in res]
+ assert set(set_expected) == set(set_res)
+
+
+class TestGetPreviewPorts(object):
+
+ """Test get_preview_ports method"""
+ NUM = 1
+ FACTOR = 1
+
+ def get_preview_ports(self):
+ """Create Controller object and call get_preview_ports method"""
+ res = []
+ controller = Controller()
+ controller.establish_connection()
+ for _ in range(self.NUM * self.FACTOR):
+ res.append(controller.get_preview_ports())
+ return res
+
+ def test_get_preview_ports(self):
+ """Test get_preview_ports"""
+
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000, audio_port=4000)
+ for _ in range(self.NUM):
+ sources.new_test_audio()
+ sources.new_test_video()
+ expected_result = map(
+ tuple,
+ [[x for x in range(3003, 3004 + self.NUM)]]
+ * self.NUM * self.FACTOR)
+ res = map(tuple, self.get_preview_ports())
+ print '\n', res, '\n'
+ print expected_result
+ assert set(expected_result) == set(res)
+ sources.terminate_video()
+ sources.terminate_audio()
+ serv.terminate(1)
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class VideoFileSink(object):
+
+ """Sink the video to a file
+ """
+
+ def __init__(self, port, filename):
+ cmd = "gst-launch-1.0 tcpclientsrc port={0} ! gdpdepay ! jpegenc \
+ ! avimux ! filesink location={1}".format(port, filename)
+ with open(os.devnull, 'w') as tempf:
+ self.proc = subprocess.Popen(
+ cmd.split(),
+ stdout=tempf,
+ stderr=tempf,
+ bufsize=-1,
+ shell=False)
+
+ def terminate(self):
+ """Terminate sinking"""
+ self.proc.terminate()
+
+
+class TestSetCompositeMode(object):
+
+ """Test set_composite_mode method"""
+ NUM = 1
+ FACTOR = 1
+
+ def set_composite_mode(self, mode, generate_frames=False):
+ """Create Controller object and call set_composite_mode method"""
+ for _ in range(self.NUM):
+
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+
+ preview = PreviewSinks()
+ preview.run()
+
+ out_file = 'output-{0}.data'.format(mode)
+ video_sink = VideoFileSink(serv.video_port + 1, out_file)
+
+ sources = TestSources(video_port=3000)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+
+ time.sleep(3)
+ # expected_result = [mode != 3] * self.FACTOR
+ # print mode, expected_result
+ controller = Controller()
+ res = controller.set_composite_mode(mode)
+ print res
+ time.sleep(3)
+ video_sink.terminate()
+ preview.terminate()
+ sources.terminate_video()
+ serv.terminate(1)
+ if not generate_frames:
+ if mode == 3:
+ assert res is False
+ else:
+ assert res is True
+ assert self.verify_output(mode, out_file) is True
+ # assert expected_result == res
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def verify_output(self, mode, video):
+ """Verify if the output is correct by comparing key frames"""
+ test = 'composite_mode_{0}'.format(mode)
+ cmpr = CompareVideo(test, video)
+ res1, res2 = cmpr.compare()
+ print "RESULTS", res1, res2
+ folder = cmpr.test_frame_dir
+ cmd = "./imgurbash.sh {0}/*.*".format(folder)
+ print cmd
+ proc = subprocess.Popen(
+ cmd,
+ bufsize=-1,
+ shell=True)
+ print proc.wait()
+ # Experimental Value
+ if res1 <= 0.04 and res2 <= 0.04:
+ return True
+ return False
+
+ def test_set_composite_mode(self):
+ """Test set_composite_mode"""
+ for i in range(4):
+ self.set_composite_mode(i)
+
+
+class TestNewRecord(object):
+
+ """Test new_record method"""
+ NUM = 1
+ FACTOR = 1
+
+ def new_record(self):
+ """Create a Controller object and call new_record method"""
+ res = []
+ controller = Controller()
+ for _ in range(self.NUM * self.FACTOR):
+ res.append(controller.new_record())
+ return res
+
+ def test_new_record(self):
+ """Test new_record"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH, record_file="test-%Y.data")
+ try:
+ serv.run()
+
+ sources = TestSources(video_port=3000)
+ sources.new_test_video()
+ sources.new_test_video()
+
+ curr_time = datetime.datetime.now()
+ time_str = curr_time.strftime('%Y')
+ test_filename = "test-{0}.data".format(time_str)
+
+ res = self.new_record()
+ print res
+ sources.terminate_video()
+ serv.terminate(1)
+ assert os.path.exists(test_filename) is True
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class TestAdjustPIP(object):
+
+ """Test adjust_pip method"""
+ NUM = 1
+ FACTOR = 1
+
+ def adjust_pip(self,
+ xpos,
+ ypos,
+ width,
+ heigth,
+ index,
+ generate_frames=False):
+ """Create Controller object and call adjust_pip"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000)
+ preview = PreviewSinks()
+ preview.run()
+ out_file = "output-{0}.data".format(index)
+ video_sink = VideoFileSink(3001, out_file)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+ controller = Controller()
+ controller.set_composite_mode(1)
+ time.sleep(3)
+ res = controller.adjust_pip(xpos, ypos, width, heigth)
+ time.sleep(3)
+ sources.terminate_video()
+ preview.terminate()
+ video_sink.terminate()
+ serv.terminate(1)
+ if not generate_frames:
+ assert res is not None
+ assert self.verify_output(index, out_file) is True
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def verify_output(self, index, video):
+ """Verify if the output is correct by comparing key frames"""
+ test = 'adjust_pip_{0}'.format(index)
+ cmpr = CompareVideo(test, video)
+ res1, res2 = cmpr.compare()
+ print "RESULTS", res1, res2
+ # Experimental Value
+ if res1 <= 0.04 and res2 <= 0.04:
+ return True
+ return False
+
+ def test_adjust_pip(self):
+ """Test adjust_pip"""
+ dic = [
+ [50, 75, 0, 0],
+ ]
+ for i in range(4, 5):
+ self.adjust_pip(
+ dic[i - 4][0],
+ dic[i - 4][1],
+ dic[i - 4][2],
+ dic[i - 4][3],
+ i)
+
+
+class TestSwitch(object):
+
+ """Test switch method"""
+
+ NUM = 1
+
+ def switch(self, channel, port, index):
+ """Create Controller object and call switch method"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+
+ sources = TestSources(3000)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+ preview = PreviewSinks(3001)
+ preview.run()
+ out_file = "output-{0}.data".format(index)
+ video_sink = VideoFileSink(3001, out_file)
+ time.sleep(3)
+ controller = Controller()
+ res = controller.switch(channel, port)
+ print res
+ time.sleep(3)
+ video_sink.terminate()
+ sources.terminate_video()
+ preview.terminate()
+ serv.terminate(1)
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def test_switch(self):
+ """Test switch"""
+ dic = [
+ [65, 3004]
+ ]
+ start = 5
+ for i in range(start, 6):
+ self.switch(dic[i - start][0], dic[i - start][1], i)
+
+
+class TestClickVideo(object):
+
+ """Test click_video method"""
+ NUM = 1
+ FACTOR = 1
+
+ def click_video(self,
+ xpos,
+ ypos,
+ width,
+ heigth,
+ index,
+ generate_frames=False):
+ """Create Controller object and call click_video method"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000)
+ preview = PreviewSinks()
+ preview.run()
+ out_file = "output-{0}.data".format(index)
+ video_sink = VideoFileSink(3001, out_file)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+ controller = Controller()
+ time.sleep(1)
+ res = controller.click_video(xpos, ypos, width, heigth)
+ print res
+ time.sleep(1)
+ sources.terminate_video()
+ preview.terminate()
+ video_sink.terminate()
+ serv.terminate(1)
+ if not generate_frames:
+ assert res is not None
+ assert self.verify_output(index, out_file) is True
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def verify_output(self, index, video):
+ """Verify if the output is correct by comparing key frames"""
+ test = 'click_video_{0}'.format(index)
+ cmpr = CompareVideo(test, video)
+ res1, res2 = cmpr.compare()
+ print "RESULTS", res1, res2
+ # Experimental Value
+ if res1 <= 0.04 and res2 <= 0.04:
+ return True
+ return False
+
+ def test_click_video(self):
+ """Test click_video"""
+ dic = [
+ [0, 0, 10, 10],
+ ]
+ start = 6
+ for i in range(start, 7):
+ self.click_video(
+ dic[i - start][0],
+ dic[i - start][1],
+ dic[i - start][2],
+ dic[i - start][3],
+ i,
+ True)
+
+
+class TestMarkFace(object):
+
+ """Test mark_face method"""
+ NUM = 1
+
+ def mark_face(self, faces, index, generate_frames=False):
+ """Create the Controller object and call mark_face method"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000)
+ preview = PreviewSinks()
+ preview.run()
+ out_file = "output-{0}.data".format(index)
+ video_sink = VideoFileSink(3001, out_file)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+ controller = Controller()
+ time.sleep(1)
+ res = controller.mark_face(faces)
+ print res
+ time.sleep(1)
+ sources.terminate_video()
+ preview.terminate()
+ video_sink.terminate()
+ serv.terminate(1)
+ if not generate_frames:
+ assert res is not None
+ assert self.verify_output(index, out_file) is True
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def verify_output(self, index, video):
+ """Verify if the output is correct by comparing key frames"""
+ test = 'mark_face_{0}'.format(index)
+ cmpr = CompareVideo(test, video)
+ res1, res2 = cmpr.compare()
+ print "RESULTS", res1, res2
+ # Experimental Value
+ if res1 <= 0.04 and res2 <= 0.04:
+ return True
+ return False
+
+ def test_mark_face(self):
+ """Test mark_face"""
+ dic = [
+ [(1, 1, 1, 1), (10, 10, 1, 1)],
+ ]
+ start = 7
+ for i in range(start, 8):
+ self.mark_face(dic[i - start], i, True)
+
+
+class TestMarkTracking(object):
+
+ """Test mark_tracking method"""
+ NUM = 1
+
+ def mark_tracking(self, faces, index, generate_frames=False):
+ """Create Controller object and call mark_tracking method"""
+ for _ in range(self.NUM):
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ sources = TestSources(video_port=3000)
+ preview = PreviewSinks()
+ preview.run()
+ out_file = "output-{0}.data".format(index)
+ video_sink = VideoFileSink(3001, out_file)
+ sources.new_test_video(pattern=4)
+ sources.new_test_video(pattern=5)
+ controller = Controller()
+ time.sleep(1)
+ res = controller.mark_tracking(faces)
+ print res
+ time.sleep(1)
+ sources.terminate_video()
+ preview.terminate()
+ video_sink.terminate()
+ serv.terminate(1)
+ if not generate_frames:
+ assert res is not None
+ assert self.verify_output(index, out_file) is True
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def verify_output(self, index, video):
+ """Verify if the output is correct by comparing key frames"""
+ test = 'mark_tracking_{0}'.format(index)
+ cmpr = CompareVideo(test, video)
+ res1, res2 = cmpr.compare()
+ print "RESULTS", res1, res2
+ # Experimental Value
+ if res1 <= 0.04 and res2 <= 0.04:
+ return True
+ return False
+
+ def test_mark_tracking(self):
+ """Test mark_tracking"""
+ dic = [
+ [(1, 1, 1, 1), (10, 10, 1, 1)],
+ ]
+ start = 7
+ for i in range(start, 8):
+ self.mark_tracking(dic[i - start], i, True)
diff --git a/python-api/tests/integrationtests/test_helpers.py b/python-api/tests/integrationtests/test_helpers.py
new file mode 100644
index 0000000..aa73378
--- /dev/null
+++ b/python-api/tests/integrationtests/test_helpers.py
@@ -0,0 +1,74 @@
+"""
+Integartion tests for TestSources, PreviewSinks in helpers.py
+"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.server import Server
+from gstswitch.helpers import TestSources, PreviewSinks
+import time
+
+
+# PATH = os.getenv("HOME") + '/gst/stage/bin/'
+PATH = '../tools/'
+
+
+class TestTestSourcesPreviews(object):
+
+ """Test for TestSources and PreviewSinks"""
+ NUM = 1
+
+ def add_video_sources(self, num, video_port):
+ """Add a video source"""
+ sources = TestSources(video_port=video_port)
+ for _ in range(num):
+ sources.new_test_video(pattern=6)
+ # print "done adding" + str(num)
+ time.sleep(2)
+ sources.terminate_video()
+ time.sleep(2)
+
+ def test_video_sources(self):
+ """Test video sources"""
+ video_port = 3000
+ serv = Server(PATH, video_port=video_port)
+ try:
+ serv.run()
+ preview = PreviewSinks()
+ preview.run()
+ for i in range(self.NUM):
+ self.add_video_sources(i * 10 + 1, video_port)
+ preview.terminate()
+ serv.terminate()
+ finally:
+ if serv.proc:
+ serv.kill()
+
+ def add_audio_sources(self, num, audio_port):
+ """Add audio sources"""
+ sources = TestSources(audio_port=audio_port)
+ for _ in range(num):
+ sources.new_test_audio(wave=10)
+ # print "done adding" + str(num)
+ # print sources.get_test_audio()
+ time.sleep(2)
+ sources.terminate_audio()
+ time.sleep(2)
+
+ def test_audio_sources(self):
+ """Test audio sources"""
+ audio_port = 4000
+ serv = Server(PATH, audio_port=audio_port)
+ try:
+ serv.run()
+ preview = PreviewSinks()
+ preview.run()
+ for i in range(self.NUM):
+ self.add_audio_sources(i * 10 + 1, audio_port)
+
+ preview.terminate()
+ serv.terminate()
+ finally:
+ if serv.proc:
+ serv.kill()
diff --git a/python-api/tests/integrationtests/test_server.py b/python-api/tests/integrationtests/test_server.py
new file mode 100644
index 0000000..5b5feb6
--- /dev/null
+++ b/python-api/tests/integrationtests/test_server.py
@@ -0,0 +1,44 @@
+"""
+Integration tests for Server in server.py
+"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.server import Server
+
+# PATH = os.getenv("HOME") + '/gst/stage/bin/'
+PATH = '../tools/'
+
+
+class TestServerStartStop(object):
+
+ """Test Starting and Stopping the Server
+ Run Server and Stop multiple times
+ """
+ NUM = 5
+
+ def startstop(self):
+ """Start and Stop the Server"""
+ serv = Server(path=PATH)
+ try:
+ serv.run()
+ pid = serv.pid
+ assert pid > 0
+ serv.terminate(1)
+ assert serv.proc is None
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ print self.__class__
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(abs(poll))
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+ def test_start_stop(self):
+ """Test Start and Stop the Server"""
+ for _ in range(self.NUM):
+ self.startstop()
diff --git a/python-api/tests/performancetests/performance_dbus.py b/python-api/tests/performancetests/performance_dbus.py
new file mode 100644
index 0000000..24ceb24
--- /dev/null
+++ b/python-api/tests/performancetests/performance_dbus.py
@@ -0,0 +1,381 @@
+"""
+Performance/Torture test for DBus methods
+"""
+
+
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.server import Server
+from gstswitch.helpers import TestSources, PreviewSinks
+from gstswitch.controller import Controller
+import time
+
+PATH = '../tools/'
+
+
+
+def get_compose_port(num):
+ """Test get_compose_port - num times"""
+ video_port = 8000
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video()
+
+ expected_res = [video_port + 1] * num
+
+ controller = Controller()
+ controller.establish_connection()
+ res = []
+ for _ in range(num):
+ res.append(controller.get_compose_port())
+
+ assert expected_res == res
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class TestGetComposePort(object):
+ """Performance test for get_compose_port method - 100 times"""
+
+ def test_100(self):
+ """Performance test for get_compose_port method"""
+ number_test_runs = 100
+ get_compose_port(number_test_runs)
+
+
+ def test_200(self):
+ """Performance test for get_compose_port method"""
+ number_test_runs = 200
+ get_compose_port(number_test_runs)
+
+
+ def test_300(self):
+ """Performance test for get_compose_port method"""
+ number_test_runs = 300
+ get_compose_port(number_test_runs)
+
+
+ def test_400(self):
+ """Performance test for get_compose_port method"""
+ number_test_runs = 400
+ get_compose_port(number_test_runs)
+
+ def test_500(self):
+ """Performance test for get_compose_port method"""
+ number_test_runs = 500
+ get_compose_port(number_test_runs)
+
+
+def get_encode_port(num):
+ """Test get_encode_port - num times"""
+ video_port = 3000
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video()
+
+ expected_res = [video_port + 2] * num
+
+ controller = Controller()
+ controller.establish_connection()
+ res = []
+ for _ in range(num):
+ res.append(controller.get_encode_port())
+
+ assert expected_res == res
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class TestGetEncodePort(object):
+ """Performance test for get_encode_port method"""
+
+ def test_100(self):
+ """Performance test for get_encode_port method - 100 times"""
+ number_test_runs = 100
+ get_encode_port(number_test_runs)
+
+ def test_200(self):
+ """Performance test for get_encode_port method - 200 times"""
+ number_test_runs = 200
+ get_encode_port(number_test_runs)
+
+ def test_300(self):
+ """Performance test for get_encode_port method - 300 times"""
+ number_test_runs = 300
+ get_encode_port(number_test_runs)
+
+ def test_400(self):
+ """Performance test for get_encode_port method - 400 times"""
+ number_test_runs = 400
+ get_encode_port(number_test_runs)
+
+ def test_500(self):
+ """Performance test for get_encode_port method - 500 times"""
+ number_test_runs = 500
+ get_encode_port(number_test_runs)
+
+
+
+def get_audio_port(num):
+ """Test get_audio_port"""
+ audio_port = 8000
+ serv = Server(path=PATH, audio_port=audio_port)
+ try:
+ serv.run()
+ sources = TestSources(audio_port=audio_port)
+ sources.new_test_audio()
+
+ expected_res = [3003] * num
+
+ controller = Controller()
+ controller.establish_connection()
+ res = []
+ for _ in range(num):
+ res.append(controller.get_audio_port())
+
+ assert expected_res == res
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class TestGetAudioPort(object):
+
+ def test_100(self):
+ """Performance test for get_audio_port method - 100 times"""
+ number_test_runs = 100
+ get_audio_port(number_test_runs)
+
+
+ def test_200(self):
+ """Performance test for get_audio_port method - 200 times"""
+ number_test_runs = 200
+ get_audio_port(number_test_runs)
+
+
+ def test_300(self):
+ """Performance test for get_audio_port method - 300 times"""
+ number_test_runs = 300
+ get_audio_port(number_test_runs)
+
+
+ def test_400(self):
+ """Performance test for get_audio_port method - 400 times"""
+ number_test_runs = 400
+ get_audio_port(number_test_runs)
+
+
+ def test_500(self):
+ """Performance test for get_audio_port method - 500 times"""
+ number_test_runs = 500
+ get_audio_port(number_test_runs)
+
+
+def get_preview_ports(num):
+ """Get Preview Ports when num number of sources are added"""
+ video_port = 3000
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+
+
+ expected_res = [ i for i in range(3003, 3003 + num)]
+
+ controller = Controller()
+
+ res = []
+ for _ in range(num):
+ sources.new_test_video()
+ controller.establish_connection()
+ res = controller.get_preview_ports()
+ print res
+
+ assert expected_res == res
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+
+class _TestGetPreviewPorts(object):
+
+ def test_50(self):
+ """Test when 50 sources are added"""
+ number_souces = 50
+ get_preview_ports(number_souces)
+
+ def test_100(self):
+ """Test when 100 sources are added"""
+ number_souces = 100
+ get_preview_ports(number_souces)
+
+ def test_200(self):
+ """Test when 200 sources are added"""
+ number_souces = 200
+ get_preview_ports(number_souces)
+
+ def test_500(self):
+ """Test when 500 sources are added"""
+ number_souces = 500
+ get_preview_ports(number_souces)
+
+ def test_1000(self):
+ """Test when 1000 sources are added"""
+ number_souces = 1000
+ get_preview_ports(number_souces)
+
+
+def permutate_composite_mode(num, delay):
+ """Change composite mode num number of times"""
+ import random
+
+ video_port = 3000
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video(pattern=6)
+ sources.new_test_video(pattern=5)
+ preview = PreviewSinks()
+ preview.run()
+ controller = Controller()
+ res = controller.set_composite_mode(0)
+ assert res is True
+
+ prev_mode = 0
+ for _ in range(num-1):
+
+ time.sleep(delay)
+ mode = random.randint(0,3)
+ expected_res = bool(prev_mode ^ mode)
+ prev_mode = mode
+ assert expected_res == controller.set_composite_mode(mode)
+ preview.terminate()
+ sources.terminate_video()
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+class TestSetCompositeMode(object):
+ """Performance Tests for set_composite_mode"""
+
+ def test_num_20_delay_1(self):
+ delay = 1
+ num = 20
+ permutate_composite_mode(num, delay)
+
+ def test_num_20_delay_point_6(self):
+ delay = 0.6
+ num = 20
+ permutate_composite_mode(num, delay)
+
+ def test_num_20_delay_point_5(self):
+ delay = 0.5
+ num = 20
+ permutate_composite_mode(num, delay)
+
+ def test_num_20_delay_point_2(self):
+ delay = 0.2
+ num = 20
+ permutate_composite_mode(num, delay)
+
+
+def permutate_adjust_pip(num, delay):
+ """Adjust_pip num number of times"""
+ import random
+
+ video_port = 3000
+ serv = Server(path=PATH, video_port=video_port)
+ try:
+ serv.run()
+ sources = TestSources(video_port=video_port)
+ sources.new_test_video(pattern=6)
+ sources.new_test_video(pattern=5)
+ preview = PreviewSinks()
+ preview.run()
+ controller = Controller()
+
+ for _ in range(num):
+
+ xpos = random.randrange(-20, 20)
+ ypos = random.randrange(-20, 20)
+ res = controller.adjust_pip(xpos, ypos, 0, 0)
+ time.sleep(delay)
+ assert res is not None
+ preview.terminate()
+ sources.terminate_video()
+
+ finally:
+ if serv.proc:
+ poll = serv.proc.poll()
+ if poll == -11:
+ print "SEGMENTATION FAULT OCCURRED"
+ print "ERROR CODE - {0}".format(poll)
+ serv.terminate(1)
+ log = open('server.log')
+ print log.read()
+
+class TestAdjustPip(object):
+ """Performance Tests for set_adjust_pip"""
+
+ def test_num_20_delay_1(self):
+ delay = 1
+ num = 20
+ permutate_adjust_pip(num, delay)
+
+ def test_num_20_delay_point_6(self):
+ delay = 0.6
+ num = 20
+ permutate_adjust_pip(num, delay)
+
+ def test_num_20_delay_point_5(self):
+ delay = 0.5
+ num = 20
+ permutate_adjust_pip(num, delay)
+
+ def test_num_20_delay_point_2(self):
+ delay = 0.2
+ num = 20
+ permutate_adjust_pip(num, delay)
+
+
diff --git a/python-api/tests/unittests/__init__.py b/python-api/tests/unittests/__init__.py
new file mode 100644
index 0000000..13dd066
--- /dev/null
+++ b/python-api/tests/unittests/__init__.py
@@ -0,0 +1,3 @@
+"""
+Unittests for gst-switch Python-API
+"""
diff --git a/python-api/tests/unittests/test_connection_unit.py b/python-api/tests/unittests/test_connection_unit.py
new file mode 100644
index 0000000..9604ab2
--- /dev/null
+++ b/python-api/tests/unittests/test_connection_unit.py
@@ -0,0 +1,359 @@
+"""Unittests for Connection class in connection.py"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.connection import Connection
+from gstswitch.exception import ConnectionError
+import pytest
+from gi.repository import Gio, GLib
+from mock import Mock
+
+
+class TestAddress(object):
+
+ """Unittests for address parameter"""
+
+ def test_address_null(self):
+ """Test if address is null"""
+ addresses = ['', None, [], {}]
+ for address in addresses:
+ with pytest.raises(ValueError):
+ Connection(address=address)
+
+ def test_address_colon(self):
+ """Test if address has no colon"""
+ address = 'abcdefghijk'
+ with pytest.raises(ValueError):
+ Connection(address=address)
+
+ def test_address_normal(self):
+ """"Test if address is valid"""
+ address = ['unix:abstract=gstswitch', 'unix:temp=/tmp/abcd/xyz']
+ for addr in address:
+ conn = Connection(address=addr)
+ assert conn.address == addr
+
+
+class TestBusName(object):
+
+ """Unittests for bus_name parameter"""
+
+ def test_normal(self):
+ """Test if bus_name is not null"""
+ names = ['', 'abcd', 12345]
+ for bus in names:
+ conn = Connection(bus_name=bus)
+ assert conn.bus_name == str(bus)
+
+ def test_normal_none(self):
+ """Test if bus_name is null"""
+ name = None
+ conn = Connection(bus_name=name)
+ assert conn.bus_name == name
+
+
+class TestObjectPath(object):
+
+ """Unittests for object_path parameter"""
+
+ def test_object_path_blank(self):
+ """Test if object_path is null"""
+ paths = [None, '', {}, []]
+ for object_path in paths:
+ with pytest.raises(ValueError):
+ Connection(object_path=object_path)
+
+ def test_object_path_slash(self):
+ """Test when object_path doesn't have slash in start"""
+ object_path = 'a/////////'
+ with pytest.raises(ValueError):
+ Connection(object_path=object_path)
+
+ def test_object_path_normal(self):
+ """Test of object_path is valid"""
+ object_path = "/info/duzy/gst/switch/SwitchController"
+ conn = Connection(object_path=object_path)
+ assert conn.object_path == object_path
+
+
+class TestInterface(object):
+
+ """Unittests for default_interface parameter"""
+
+ def test_interface_none(self):
+ """Test if default_interface is null"""
+ default_interface = [None, '', [], {}]
+ for interface_name in default_interface:
+ with pytest.raises(ValueError):
+ Connection(default_interface=interface_name)
+
+ def test_interface_dot(self):
+ """Test when the default_interface has <2 dots"""
+ default_interface = ['.', 'info.', 'info']
+ for interface_name in default_interface:
+ with pytest.raises(ValueError):
+ Connection(default_interface=interface_name)
+
+ def test_interface_normal(self):
+ """Test if default_interface is valid"""
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ assert default_interface == conn.default_interface
+
+
+class TestConnectDBus(object):
+
+ """Unittests for the connect_dbus method of Connection class"""
+
+ def test_bad_address(self):
+ """Test if wrong address is given - 1"""
+ address = 'unix:path=gstswitch'
+ conn = Connection(address=address)
+ with pytest.raises(ConnectionError):
+ conn.connect_dbus()
+
+ def test_bad_address2(self):
+ """Test if wrong address is given - 2"""
+ address = 'unix:temp=gstswitch'
+ conn = Connection(address=address)
+ with pytest.raises(ConnectionError):
+ conn.connect_dbus()
+
+ def test_bad_address3(self):
+ """Test if wrong address is given - 3"""
+ address = 'unix:path'
+ conn = Connection(address=address)
+ with pytest.raises(ConnectionError):
+ conn.connect_dbus()
+
+ def test_mock1(self, monkeypatch):
+ """Test GLib.GError exception"""
+ monkeypatch.setattr(
+ Gio.DBusConnection,
+ 'new_for_address_sync',
+ Mock(side_effect=GLib.GError))
+ conn = Connection()
+ with pytest.raises(ConnectionError):
+ conn.connect_dbus()
+
+ def test_mock2(self, monkeypatch):
+ """Test GLib.GError exception"""
+ monkeypatch.setattr(
+ Gio.DBusConnection, 'new_for_address_sync',
+ Mock(return_value=1))
+ conn = Connection()
+ conn.connect_dbus()
+ assert conn.connection is not None
+
+
+class MockConnection(object):
+
+ """A class which mocks the Connection class"""
+ funs = {
+ 'get_compose_port': (3001,),
+ 'get_encode_port': (3002,),
+ 'get_audio_port': (4000,),
+ 'get_preview_ports': ('[(3002, 1, 7), (3003, 1, 8)]',),
+ 'set_composite_mode': (False,),
+ 'set_encode_mode': (False,),
+ 'new_record': (False,),
+ 'adjust_pip': (1,),
+ 'switch': (True,),
+ 'click_video': (True,),
+ 'mark_face': None,
+ 'mark_tracking': None
+ }
+
+ def __init__(self, method):
+ self.method = method
+ self.return_result = self.funs[method]
+
+ def call_sync(
+ self,
+ bus_name,
+ object_path,
+ interface_name,
+ method_name,
+ parameters,
+ reply_type, flags,
+ timeout_msec,
+ cancellable):
+ """Mock of call_sync method,
+ raises GLib.GError if interface_name invalid"""
+ if interface_name == "info.duzy.gst.switch.SwitchControllerInterface":
+ return self.return_result
+ else:
+ raise GLib.GError('{0}: Test Failed'.format(self.method))
+
+
+def test_get_compose_port():
+ """Test the get_compose_port method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_compose_port')
+ with pytest.raises(ConnectionError):
+ conn.get_compose_port()
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_compose_port')
+ assert conn.get_compose_port() == (3001,)
+
+
+def test_get_encode_port():
+ """Test the get_encode_port method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_encode_port')
+ with pytest.raises(ConnectionError):
+ conn.get_encode_port()
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_encode_port')
+ assert conn.get_encode_port() == (3002,)
+
+
+def test_get_audio_port():
+ """Test the get_audio_port method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_audio_port')
+ with pytest.raises(ConnectionError):
+ conn.get_audio_port()
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_audio_port')
+ assert conn.get_audio_port() == (4000,)
+
+
+def test_get_preview_ports():
+ """Test the get_preview_ports method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_preview_ports')
+ with pytest.raises(ConnectionError):
+ conn.get_preview_ports()
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('get_preview_ports')
+ assert conn.get_preview_ports() == ('[(3002, 1, 7), (3003, 1, 8)]',)
+
+
+def test_set_composite_mode():
+ """Test the set_composite_mode method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('set_composite_mode')
+ with pytest.raises(ConnectionError):
+ conn.set_composite_mode(2)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('set_composite_mode')
+ assert conn.set_composite_mode(2) == (False,)
+
+
+def test_set_encode_mode():
+ """Test the set_encode_mode method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('set_encode_mode')
+ with pytest.raises(ConnectionError):
+ conn.set_encode_mode(2)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('set_encode_mode')
+ assert conn.set_encode_mode(2) == (False,)
+
+
+def test_new_record():
+ """Test the new_record method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('new_record')
+ with pytest.raises(ConnectionError):
+ conn.new_record()
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('new_record')
+ assert conn.new_record() == (False,)
+
+
+def test_adjust_pip():
+ """Test the adjust_pip method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('adjust_pip')
+ with pytest.raises(ConnectionError):
+ conn.adjust_pip(1, 2, 3, 4)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('adjust_pip')
+ assert conn.adjust_pip(1, 2, 3, 4) == (1,)
+
+
+def test_switch():
+ """Test the switch method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('switch')
+ with pytest.raises(ConnectionError):
+ conn.switch(1, 2)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('switch')
+ assert conn.switch(1, 2) == (True,)
+
+
+def test_click_video():
+ """Test the click_video method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('click_video')
+ with pytest.raises(ConnectionError):
+ conn.click_video(1, 2, 3, 4)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('click_video')
+ assert conn.click_video(1, 2, 3, 4) == (True,)
+
+
+def test_mark_face():
+ """Test the mark_face method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('mark_face')
+ with pytest.raises(ConnectionError):
+ face = [(1, 1, 1, 1), (2, 2, 2, 2)]
+ conn.mark_face(face)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('mark_face')
+ face = [(1, 1, 1, 1), (2, 2, 2, 2)]
+ assert conn.mark_face(face) is None
+
+
+def test_mark_tracking():
+ """Test the mark_tracking method"""
+ default_interface = "info.duzy.gst.switch"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('mark_tracking')
+ with pytest.raises(ConnectionError):
+ face = [(1, 1, 1, 1), (2, 2, 2, 2)]
+ conn.mark_tracking(face)
+
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Connection(default_interface=default_interface)
+ conn.connection = MockConnection('mark_tracking')
+ face = [(1, 1, 1, 1), (2, 2, 2, 2)]
+ assert conn.mark_tracking(face) is None
diff --git a/python-api/tests/unittests/test_controller_unit.py b/python-api/tests/unittests/test_controller_unit.py
new file mode 100644
index 0000000..199beaa
--- /dev/null
+++ b/python-api/tests/unittests/test_controller_unit.py
@@ -0,0 +1,445 @@
+"""Unittests for Controller class in controller.py"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.controller import Controller
+from gstswitch.exception import ConnectionReturnError
+import pytest
+from mock import Mock
+from gstswitch.connection import Connection
+from gi.repository import GLib
+
+
+class TestAddress(object):
+
+ """Test the address parameter"""
+
+ def test_address_null(self):
+ """Test if address is null"""
+ address = ['', None, [], {}]
+ for addr in address:
+ with pytest.raises(ValueError):
+ Controller(address=addr)
+
+ def test_address_colon(self):
+ """Test if address has no colon"""
+ address = 'abcdefghijk'
+ with pytest.raises(ValueError):
+ Controller(address=address)
+
+ def test_address_normal(self):
+ """Test if address is valid"""
+ address = ['unix:abstract=gstswitch', 'unix:temp=/tmp/abcd/xyz']
+ for addr in address:
+ conn = Controller(address=addr)
+ assert conn.address == addr
+
+
+class TestBusName(object):
+
+ """Test bus_name parameter"""
+
+ def test_normal(self):
+ """Test when bus_name is not null"""
+ names = ['', 'abcd', 12345]
+ for bus in names:
+ conn = Controller(bus_name=bus)
+ assert conn.bus_name == str(bus)
+
+ def test_normal_none(self):
+ """Test when bus_name is null"""
+ name = None
+ conn = Controller(bus_name=name)
+ assert conn.bus_name == name
+
+
+class TestObjectPath(object):
+
+ """Test object_path parameter"""
+
+ def test_object_path_blank(self):
+ """Test when the object_path is null"""
+ paths = [None, '', {}, []]
+ for object_path in paths:
+ with pytest.raises(ValueError):
+ Controller(object_path=object_path)
+
+ def test_object_path_slash(self):
+ """Test when object_path doesn't have slash in start"""
+ object_path = 'a/////////'
+ with pytest.raises(ValueError):
+ Controller(object_path=object_path)
+
+ def test_object_path_normal(self):
+ """Test when object_path is valid"""
+ object_path = "/info/duzy/gst/switch/SwitchController"
+ conn = Controller(object_path=object_path)
+ assert conn.object_path == object_path
+
+
+class TestInterface(object):
+
+ """Test the default_interface parameter"""
+
+ def test_interface_none(self):
+ """Test when the default_interface is null"""
+ default_interface = [None, '', [], {}]
+ for interface in default_interface:
+ with pytest.raises(ValueError):
+ Controller(default_interface=interface)
+
+ def test_interface_dot(self):
+ """Test when the default_interface has <2 dots"""
+ default_interface = ['.', 'info.', 'info']
+ for interface in default_interface:
+ with pytest.raises(ValueError):
+ Controller(default_interface=interface)
+
+ def test_interface_normal(self):
+ """Test when the interface is valid"""
+ default_interface = "info.duzy.gst.switch.SwitchControllerInterface"
+ conn = Controller(default_interface=default_interface)
+ assert default_interface == conn.default_interface
+
+
+class TestEstablishConnection(object):
+
+ """Test the establish_connection method"""
+
+ def test_normal(self, monkeypatch):
+ """Test if the parameters are valid"""
+ monkeypatch.setattr(Connection, 'connect_dbus', Mock())
+ controller = Controller(address='unix:abstract=abcd')
+ controller.establish_connection()
+ assert controller.connection is not None
+
+
+class MockConnection(object):
+
+ """A class which mocks the Connection class"""
+
+ def __init__(self, mode):
+ self.mode = mode
+
+ def get_compose_port(self):
+ """mock of get_compose_port"""
+ if self.mode is False:
+ return GLib.Variant('(i)', (3001,))
+ else:
+ return (0,)
+
+ def get_encode_port(self):
+ """mock of get_encode_port"""
+ if self.mode is False:
+ return GLib.Variant('(i)', (3002,))
+ else:
+ return (0,)
+
+ def get_audio_port(self):
+ """mock of get_audio_port"""
+ if self.mode is False:
+ return GLib.Variant('(i)', (4000,))
+ else:
+ return (0,)
+
+ def get_preview_ports(self):
+ """mock of get_preview_ports"""
+ if self.mode is False:
+ return GLib.Variant('(s)', ('[(3002, 1, 7), (3003, 1, 8)]',))
+ else:
+ return (0,)
+
+ def set_composite_mode(self, mode):
+ """mock of set_composite_mode"""
+ if self.mode is False:
+ return GLib.Variant('(b)', (True,))
+ else:
+ return (False,)
+
+ def set_encode_mode(self, mode):
+ """mock of get_set_encode_mode"""
+ if self.mode is False:
+ return GLib.Variant('(b)', (True,))
+ else:
+ return (True,)
+
+ def new_record(self):
+ """mock of new_record"""
+ if self.mode is False:
+ return GLib.Variant('(b)', (True,))
+ else:
+ return (True,)
+
+ def adjust_pip(self, xpos, ypos, width, height):
+ """mock of adjust_pip"""
+ if self.mode is False:
+ return GLib.Variant('(u)', (1,))
+ else:
+ return (1,)
+
+ def switch(self, channel, port):
+ """mock of switch"""
+ if self.mode is False:
+ return GLib.Variant('(b)', (True,))
+ else:
+ return (True,)
+
+ def click_video(self, xpos, ypos, width, height):
+ """mock of click_video"""
+ if self.mode is False:
+ return GLib.Variant('(b)', (True,))
+ else:
+ return (True,)
+
+ def mark_face(self, face):
+ """mock of mark_face"""
+ pass
+
+ def mark_tracking(self, face):
+ """mock of mark_tracking"""
+ pass
+
+
+class TestGetComposePort(object):
+
+ """Test the get_compose_port method"""
+
+ def test_unpack(self):
+ """Test when values cant unpack"""
+ controller = Controller(address='unix:abstract=abcdefghijk')
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.get_compose_port()
+
+ def test_normal_unpack(self):
+ """Test when valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.connection = MockConnection(False)
+ assert controller.get_compose_port() == 3001
+
+
+class TestGetEncodePort(object):
+
+ """Test the get_encode_port method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcdefghijk')
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.get_encode_port()
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.connection = MockConnection(False)
+ assert controller.get_encode_port() == 3002
+
+
+class TestGetAudioPort(object):
+
+ """ Test the get_audio_port method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcdefghijk')
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.get_audio_port()
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.connection = MockConnection(False)
+ assert controller.get_audio_port() == 4000
+
+
+class TestGetPreviewPorts(object):
+
+ """Test the get_preview_ports method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcdefghijk')
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.get_preview_ports()
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.connection = MockConnection(False)
+ controller.parse_preview_ports = Mock(return_value=[3001, 3002])
+ assert controller.get_preview_ports() == [3001, 3002]
+
+
+class TestSetCompositeMode(object):
+
+ """Test the set_composite_mode method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcdefghijk')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.set_composite_mode(1)
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.set_composite_mode(1) is True
+
+
+class TestSetEncodeMode(object):
+
+ """Test the set_encode_mode method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.set_encode_mode(1)
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.set_encode_mode(1) is True
+
+
+class TestNewRecord(object):
+
+ """Test the new_record method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.new_record()
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.new_record() is True
+
+
+class TestAdjustPIP(object):
+
+ """Test the adjust_pip method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.adjust_pip(1, 2, 3, 4)
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.adjust_pip(1, 2, 3, 4) == 1
+
+
+class TestSwitch(object):
+
+ """Test the switch method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.switch(1, 2)
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.switch(1, 2) is True
+
+
+class TestClickVideo(object):
+
+ """Test the click_video method"""
+
+ def test_unpack(self):
+ """Test if unpack fails"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ with pytest.raises(ConnectionReturnError):
+ controller.click_video(1, 2, 3, 4)
+
+ def test_normal_unpack(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcdef')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(False)
+ assert controller.click_video(1, 2, 3, 4) is True
+
+
+class TestMarkFaces(object):
+
+ """Tes the mark_face method"""
+
+ def test_normal(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ face = [(1, 2, 3, 4), (1, 1, 1, 1)]
+ controller.mark_face(face)
+
+
+class TestMarkTracking(object):
+
+ """Test the mark_tracking method"""
+
+ def test_normal(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcde')
+ controller.establish_connection = Mock(return_value=None)
+ controller.connection = MockConnection(True)
+ face = [(1, 2, 3, 4), (1, 1, 1, 1)]
+ controller.mark_tracking(face)
+
+
+class TestParsePreviewPorts(object):
+
+ """Test the parse_preview_ports class method"""
+
+ def test_value_error(self):
+ """Test if invalid"""
+ controller = Controller(address='unix:abstract=abcde')
+ test = 1234
+ with pytest.raises(ConnectionReturnError):
+ controller.parse_preview_ports(test)
+
+ def test_syntax_error(self):
+ """Test if syntax error detected"""
+ controller = Controller(address='unix:abstract=abcde')
+ test = '{}[]'
+ with pytest.raises(ConnectionReturnError):
+ controller.parse_preview_ports(test)
+
+ def test_normal(self):
+ """Test if valid"""
+ controller = Controller(address='unix:abstract=abcde')
+ test = '[(1, 2, 3), (2, 2, 2)]'
+ assert controller.parse_preview_ports(test) == [1, 2]
diff --git a/python-api/tests/unittests/test_helpers_unit.py b/python-api/tests/unittests/test_helpers_unit.py
new file mode 100644
index 0000000..0e736ce
--- /dev/null
+++ b/python-api/tests/unittests/test_helpers_unit.py
@@ -0,0 +1,301 @@
+"""Unittests for helper.py"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.helpers import TestSources, PreviewSinks
+from gstswitch.exception import RangeError, InvalidIndexError
+import pytest
+from gstswitch import testsource
+
+
+class TestTestSourcesVideoPort(object):
+
+ """Test for video_port parameter"""
+
+ def test_range(self):
+ """Test when out of range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ TestSources(video_port=test)
+
+ def test_invalid(self):
+ """Test when not integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ TestSources(video_port=test)
+
+ def test_normal(self):
+ """Test when valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = TestSources(video_port=test)
+ assert src.video_port == test
+
+
+class TestTestSourcesAudioPort(object):
+
+ """Test for audio_port parameter"""
+
+ def test_range(self):
+ """Test when out of range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ TestSources(audio_port=test)
+
+ def test_invalid(self):
+ """Test when not integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ TestSources(audio_port=test)
+
+ def test_normal(self):
+ """Test when valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = TestSources(audio_port=test)
+ assert src.audio_port == test
+
+
+class TestTestSources(object):
+
+ """Test for testsources"""
+ class MockVideoSrc(object):
+
+ """A mock video source"""
+
+ def __init__(self,
+ port,
+ width=300,
+ height=200,
+ pattern=None,
+ timeoverlay=False,
+ clockoverlay=False):
+ pass
+
+ def run(self):
+ """Run the mock video source"""
+ pass
+
+ def test_new_test_video(self, monkeypatch):
+ """Test for new_test_video"""
+ test = TestSources(video_port=3000)
+ monkeypatch.setattr(testsource, 'VideoSrc', self.MockVideoSrc)
+ test.new_test_video()
+ assert test.running_tests_video[0] is not None
+ assert len(test.running_tests_video) != 0
+
+ class MockTest(object):
+
+ """A mock test"""
+
+ def __init__(self, pattern):
+ self.pattern = pattern
+
+ def end(self):
+ """End the test"""
+ pass
+
+ def test_get_test_video(self):
+ """Test for get_test_video"""
+ test = TestSources(video_port=3000)
+ test.running_tests_video = [
+ self.MockTest(1),
+ self.MockTest(2),
+ self.MockTest(3),
+ self.MockTest(19)]
+ test.get_test_video()
+
+ def test_terminate_index_error_video(self):
+ """Test for terminate_video"""
+ testsrc = TestSources(video_port=3000)
+ testsrc.running_tests_video = [
+ self.MockTest(1),
+ self.MockTest(2),
+ self.MockTest(3),
+ self.MockTest(19)]
+ tests = [-100, 20, 1e10, "hi", [1, 2, 3]]
+ for test in tests:
+ with pytest.raises(InvalidIndexError):
+ testsrc.terminate_index_video(test)
+
+ def test_terminate_index_normal_video(self):
+ """Test terminate_index_video"""
+ test = TestSources(video_port=3000)
+ test.running_tests_video = [
+ self.MockTest(1),
+ self.MockTest(2),
+ self.MockTest(3),
+ self.MockTest(19)]
+ test.terminate_index_video(0)
+
+ def test_terminate1_video(self):
+ """Test terminate_video multiple"""
+ test = TestSources(video_port=3000)
+ test.running_tests_video = [
+ self.MockTest(1),
+ self.MockTest(2),
+ self.MockTest(3),
+ self.MockTest(19)]
+ test.terminate_video()
+
+ def test_terminate2_video(self):
+ """Test terminate_video none present"""
+ test = TestSources(video_port=3000)
+ test.running_tests_video = []
+ test.terminate_video()
+
+ class MockAudioSrc(object):
+
+ """A Mock audio source"""
+
+ def __init__(self, port, freq=110, wave=None):
+ pass
+
+ def run(self):
+ """Run the source"""
+ pass
+
+ def test_new_test_audio(self, monkeypatch):
+ """Test new_test_audio"""
+ test = TestSources(audio_port=3000)
+ monkeypatch.setattr(testsource, 'AudioSrc', self.MockAudioSrc)
+ test.new_test_audio()
+ assert test.running_tests_audio[0] is not None
+ assert len(test.running_tests_audio) != 0
+
+ class MockTest2(object):
+
+ """A mock audio source"""
+
+ def __init__(self, wave):
+ self.wave = wave
+
+ def end(self):
+ """End the source"""
+ pass
+
+ def test_get_test_audio(self):
+ """Test get_test_audio"""
+ test = TestSources(audio_port=4000)
+ test.running_tests_audio = [
+ self.MockTest2(1),
+ self.MockTest2(2),
+ self.MockTest2(3),
+ self.MockTest2(10)]
+ test.get_test_audio()
+
+ def test_terminate_index_error_audio(self):
+ """Test terminate_index_audio - invalid index"""
+ testsrc = TestSources(audio_port=4000)
+ testsrc.running_tests_audio = [
+ self.MockTest2(1),
+ self.MockTest2(2),
+ self.MockTest2(3),
+ self.MockTest2(10)]
+ tests = [-100, 20, 1e10, "hi", [1, 2, 3]]
+ for test in tests:
+ with pytest.raises(InvalidIndexError):
+ testsrc.terminate_index_audio(test)
+
+ def test_terminate_index_normal_audio(self):
+ """Test terminate_index_audio valid index"""
+ test = TestSources(audio_port=4000)
+ test.running_tests_audio = [
+ self.MockTest2(1),
+ self.MockTest2(2),
+ self.MockTest2(3),
+ self.MockTest2(10)]
+ test.terminate_index_audio(0)
+
+ def test_terminate1_audio(self):
+ """Test terminate_audio multiple"""
+ test = TestSources(audio_port=4000)
+ test.running_tests_audio = [
+ self.MockTest2(1),
+ self.MockTest2(2),
+ self.MockTest2(3),
+ self.MockTest2(10)]
+ test.terminate_audio()
+
+ def test_terminate2_audio(self):
+ """Test terminate_audio none present"""
+ test = TestSources(audio_port=4000)
+ test.running_tests_audio = []
+ test.terminate_audio()
+
+
+class TestPreviewSinksPreviewPort(object):
+
+ """Test preview_port parameter"""
+
+ def test_blank(self):
+ """Test when preview_port is blank"""
+ tests = ['', None, [], {}]
+ for test in tests:
+ with pytest.raises(ValueError):
+ PreviewSinks(preview_port=test)
+
+ def test_range(self):
+ """Test when preview_port is out of range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ PreviewSinks(preview_port=test)
+
+ def test_invalid(self):
+ """Test when preview_port is not a integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ PreviewSinks(preview_port=test)
+
+ def test_normal(self):
+ """Test when preview_port is valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = PreviewSinks(preview_port=test)
+ assert src.preview_port == test
+
+
+class TestPreviewSinks(object):
+
+ """Test Preview Sinks"""
+ class MockPreview(object):
+
+ """A mock preview class"""
+
+ def __init__(self, preview_port=3001):
+ pass
+
+ def run(self):
+ """Run the preview"""
+ pass
+
+ def end(self):
+ """End the preview"""
+ pass
+
+ def test_run(self, monkeypatch):
+ """Test running preview"""
+ preview = PreviewSinks()
+ monkeypatch.setattr(testsource, 'Preview', self.MockPreview)
+ preview.run()
+ assert preview.preview is not None
+
+ def test_terminate_fail(self):
+ """Test terminating a preview when none exists"""
+ preview = PreviewSinks()
+ with pytest.raises(AttributeError):
+ preview.terminate()
+
+ def test_terminate_normal(self, monkeypatch):
+ """Test terminating a preview when valid exists"""
+ preview = PreviewSinks()
+ preview.preview = self.MockPreview()
+ preview.terminate()
+ assert preview.preview is None
diff --git a/python-api/tests/unittests/test_server_unit.py b/python-api/tests/unittests/test_server_unit.py
new file mode 100644
index 0000000..5e0a2b8
--- /dev/null
+++ b/python-api/tests/unittests/test_server_unit.py
@@ -0,0 +1,433 @@
+"""Unittests for Server class in server.py"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.server import Server
+import pytest
+from gstswitch.exception import ServerProcessError
+import subprocess
+from distutils import spawn
+from mock import Mock
+
+
+PATH = '/usr/bin/'
+
+
+class TestPath(object):
+
+ """Test the path parameter"""
+ # Path Tests
+
+ def test_path_provided_slash(self):
+ """Test if a path is provided"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr/'
+ serv = Server(path=path)
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000".split()
+
+ def test_path_provided_no_slash(self):
+ """Test if a path is provided"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path)
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000".split()
+
+ def test_path_empty(self, monkeypatch):
+ """Test if null path is given"""
+
+ def mock_method(arg):
+ "Mocking _start_process"
+ return arg
+
+ def mockreturn(path):
+ "Mocking distutils.spawn.find_executable"
+ return '/usr/gst-switch-srv'
+ monkeypatch.setattr(spawn, 'find_executable', mockreturn)
+ paths = [None, '']
+ for path in paths:
+ serv = Server(path=path)
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000".split()
+
+
+class TestVideoPort(object):
+
+ """Test for video_port parameter"""
+ # Video Port Tests
+
+ def test_invalid_video_port_null(self):
+ """Test when the video_port is null"""
+ video_ports = [None, '', [], {}]
+ for video_port in video_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, video_port=video_port)
+
+ def test_invalid_video_port_type(self):
+ """Test when the video port given is not a valid
+ integral value"""
+ video_ports = [[1, 2, 3], {1: 2, 2: 3}]
+ for video_port in video_ports:
+ with pytest.raises(TypeError):
+ Server(path=PATH, video_port=video_port)
+
+ def test_invalid_video_port_range(self):
+ """Test when the video port is not in range"""
+ video_ports = [-99, -1, 1e6]
+ for video_port in video_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, video_port=video_port)
+
+
+class TestAudioPort(object):
+
+ """Test for audio_port parameter"""
+ # Audio Port Tests
+
+ def test_invalid_audio_port_null(self):
+ """Test when the audio_port is null"""
+ audio_ports = [None, '', [], {}]
+ for audio_port in audio_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, audio_port=audio_port)
+
+ def test_invalid_audio_port_type(self):
+ """Test when the audio port given is not a valid
+ integral value"""
+ audio_ports = [[1, 2, 3], {1: 2, 2: 3}]
+ for audio_port in audio_ports:
+ with pytest.raises(TypeError):
+ Server(path=PATH, audio_port=audio_port)
+
+ def test_invalid_audio_port_range(self):
+ """Test when the audio port is not in range"""
+ audio_ports = [-99, -1, 1e6]
+ for audio_port in audio_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, audio_port=audio_port)
+
+
+class TestControlPort(object):
+
+ """Test the control_port parameter"""
+ # Control Port Tests
+
+ def test_invalid_control_port_null(self):
+ """Test when the control port is null"""
+ control_ports = [None, '', [], {}]
+ for control_port in control_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, control_port=control_port)
+
+ def test_invalid_control_port_type(self):
+ """Test when the control port is not a valid
+ integral value"""
+ control_ports = [[1, 2, 3], {1: 2, 2: 3}]
+ for control_port in control_ports:
+ with pytest.raises(TypeError):
+ Server(path=PATH, control_port=control_port)
+
+ def test_invalid_control_port_range(self):
+ """Test when the control port is not in range"""
+ control_ports = [-99, -1, 1e6]
+ for control_port in control_ports:
+ with pytest.raises(ValueError):
+ Server(path=PATH, control_port=control_port)
+
+
+class TestRecordFile(object):
+
+ """Test the record_file parameter"""
+ # Record File
+
+ def test_record_file_false(self):
+ """Test if record file is False"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path, record_file=False)
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000".split()
+
+ def test_record_file_true(self):
+ """Test if record file is True"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path, record_file=True)
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000 -r".split()
+
+ def test_record_file_valid(self):
+ """Test if record file is valid"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path, record_file="record.data")
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000 --record=record.data".split()
+
+ def test_record_file_valid_date(self):
+ """Test if record file is valid"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path, record_file="record_%Y.data")
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000 \
+--record=record_%Y.data".split()
+
+ def test_record_file_valid_space(self):
+ """Test if record file is valid and has a space"""
+ def mock_method(arg):
+ """Mocking _start_process"""
+ return arg
+ path = '/usr'
+ serv = Server(path=path, record_file='record 1.data')
+ serv._start_process = mock_method
+ assert serv._run_process() == "/usr/gst-switch-srv \
+--video-input-port=3000 --audio-input-port=4000 \
+--control-port=5000".split() + ["--record=record 1.data"]
+
+ def test_record_file_invalid(self):
+ """Test when the record_file is invalid"""
+ files = ['', None, [], {}]
+ for record_file in files:
+ with pytest.raises(ValueError):
+ Server(path=PATH, record_file=record_file)
+
+ def test_record_file_slashes(self):
+ """Test when the record_file has forward slashes"""
+ filename = 'abcd/xyz/'
+ with pytest.raises(ValueError):
+
+ Server(path=PATH, record_file=filename)
+
+
+class TestKillTerminate(object):
+
+ """Test kill, terminate and gcov_flush methods"""
+ # OS Errors
+
+ def test_terminate_fail(self):
+ """Test when terminate fails"""
+ class FakeProc(object):
+
+ """A mock process"""
+
+ def __init__(self):
+ pass
+
+ def terminate(self):
+ """Terminate the mock process"""
+ raise OSError
+
+ serv = Server(path=PATH)
+ serv.proc = FakeProc()
+ with pytest.raises(ServerProcessError):
+ serv.terminate()
+
+ def test_kill_fail(self):
+ """Test when kill fails"""
+ serv = Server(path=PATH)
+ serv.proc = 1
+ serv.pid = -300
+ with pytest.raises(ServerProcessError):
+ serv.kill()
+
+ def test_no_process_kill(self):
+ """Test when no process exists and kill is called"""
+ serv = Server(path=PATH)
+ with pytest.raises(ServerProcessError):
+ serv.kill()
+
+ def test_no_process_terminate(self):
+ """Test when no process exists and terminate is called"""
+ serv = Server(path=PATH)
+ with pytest.raises(ServerProcessError):
+ serv.terminate()
+
+ def test_no_process_gov_flush(self):
+ """Test when no process exists and gcov_flush is called"""
+ serv = Server(path=PATH)
+ with pytest.raises(ServerProcessError):
+ serv.gcov_flush()
+
+ def test_gcov_flush_fail(self):
+ """Test when gcov_flush fails"""
+ serv = Server(path=PATH)
+ serv.proc = 1
+ serv.pid = -300
+ with pytest.raises(ServerProcessError):
+ serv.gcov_flush()
+
+
+class TestRun(object):
+
+ """Test running the server"""
+
+ def test_run(self):
+ """Test the run method"""
+ serv = Server(path='abc')
+ serv._run_process = Mock(return_value=MockProcess())
+ serv.run()
+ assert serv.pid == 1
+ assert serv.proc is not None
+
+ def test_run_process(self):
+ """Test _run_process method"""
+ serv = Server(path='abc')
+ serv._start_process = Mock(return_value=MockProcess())
+ serv.gst_option_string = ''
+ ret = serv._run_process()
+ assert ret is not None
+
+ def test_start_process_error(self, monkeypatch):
+ """Test _start_process method"""
+ serv = Server(path='abc')
+ monkeypatch.setattr(subprocess, 'Popen', Mock(side_effect=OSError))
+ with pytest.raises(ServerProcessError):
+ serv._start_process('cmd')
+
+ def test_start_process_normal(self, monkeypatch):
+ """Test _start_process normally"""
+ serv = Server(path='abc')
+ monkeypatch.setattr(
+ subprocess,
+ 'Popen',
+ Mock(return_value=MockProcess()))
+ serv._start_process('cmd')
+
+
+class MockProcess(object):
+
+ """A mock process"""
+
+ def __init__(self, mode=True):
+ self.mode = mode
+ self.pid = 1
+
+ def terminate(self):
+ """Terminate the mock process"""
+ if self.mode is True:
+ pass
+ if self.mode is False:
+ raise OSError('Testing terminate')
+
+ def kill(self):
+ """Kill the mock process"""
+ if self.mode is True:
+ pass
+ if self.mode is False:
+ raise OSError('Testing kill')
+
+ def make_coverage(self):
+ """Dump coverage"""
+ pass
+
+
+class MockPopen(object):
+
+ """Mock Popen method"""
+
+ def __init__(self, cmd, bufsize, shell):
+ pass
+
+ def communicate(self):
+ """Mock communicate method of Popen"""
+ return 0, 0
+
+
+class TestNormal(object):
+ # Normal Functioning Tests
+
+ """Test the functioning of tests under valid conditions"""
+
+ def test_normal_terminate(self):
+ """Test terminal when normally called"""
+ serv = Server(path='abc')
+ serv.proc = MockProcess(True)
+ serv.terminate()
+ assert serv.proc is None
+
+ def test_normal_kill(self, monkeypatch):
+ """Test kill when normally called"""
+ serv = Server(path='abc')
+ serv.proc = Mock()
+ monkeypatch.setattr(os, 'kill', Mock())
+ res = serv.kill()
+ assert res is True
+ assert serv.proc is None
+
+ def test_terminate(self):
+ """Test terminate ServerProcessError"""
+ serv = Server(path='abc')
+ serv.proc = MockProcess(False)
+ with pytest.raises(ServerProcessError):
+ serv.terminate()
+
+ def test_terminate_cov(self):
+ """Test terminate and gcov_flush ServerProcessError"""
+ serv = Server(path='abc')
+ serv.proc = MockProcess(False)
+ serv.gcov_flush = Mock()
+ serv.make_coverage = Mock()
+ with pytest.raises(ServerProcessError):
+ serv.terminate(True)
+
+ def test_kill(self, monkeypatch):
+ """Test kill ServerProcessError"""
+ serv = Server(path='abc')
+ serv.proc = Mock()
+ monkeypatch.setattr(os, 'kill', Mock(side_effect=OSError))
+ with pytest.raises(ServerProcessError):
+ serv.kill()
+
+ def test_kill_cov(self, monkeypatch):
+ """Test kill and gcov_flush ServerProcessError"""
+ serv = Server(path='abc')
+ serv.proc = MockProcess(False)
+ serv.gcov_flush = Mock()
+ serv.make_coverage = Mock()
+ monkeypatch.setattr(os, 'kill', Mock(side_effect=OSError))
+ with pytest.raises(ServerProcessError):
+ serv.kill(True)
+
+ def test_normal_gcov_flush(self, monkeypatch):
+ """Test gcov_flush"""
+ serv = Server(path='abc')
+ serv.proc = Mock()
+ monkeypatch.setattr(os, 'kill', Mock())
+ res = serv.gcov_flush()
+ assert res is True
+ assert serv.proc is not None
+
+ def test_make_coverage(self, monkeypatch):
+ """Test dumping coverage"""
+ serv = Server(path='abc')
+ monkeypatch.setattr(subprocess, 'Popen', MockPopen)
+ serv.make_coverage()
diff --git a/python-api/tests/unittests/test_testsource_unit.py b/python-api/tests/unittests/test_testsource_unit.py
new file mode 100644
index 0000000..6717540
--- /dev/null
+++ b/python-api/tests/unittests/test_testsource_unit.py
@@ -0,0 +1,468 @@
+"""Unitests for testsource.py"""
+import sys
+import os
+sys.path.insert(0, os.path.abspath(os.path.join(__file__, "../../../")))
+
+from gstswitch.exception import RangeError
+from gstswitch.testsource import Preview, VideoSrc
+from gstswitch.testsource import BasePipeline, VideoPipeline, AudioSrc
+import pytest
+from mock import Mock
+from gi.repository import Gst
+
+
+class TestVideoSrcPort(object):
+
+ """Test the port parameter"""
+
+ def test_blank(self):
+ """Test when the port is null"""
+ tests = ['', None, [], {}]
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=test)
+
+ def test_range(self):
+ """Test when the port is not in range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ VideoSrc(port=test)
+
+ def test_invalid(self):
+ """Test when the port is not a valid integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ VideoSrc(port=test)
+
+ def test_normal(self):
+ """Test when port is a valid value"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = VideoSrc(port=test)
+ assert src.port == test
+
+
+class TestVideoSrcWidth(object):
+
+ """Test the width parameter"""
+
+ def test_blank(self):
+ """Test when the width is null"""
+ tests = ['', None, [], {}]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, width=test)
+
+ def test_non_positive(self):
+ """Test when the width is non-positive"""
+ tests = ['-1.111', -1.111, 0, -1e10]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, width=test)
+
+ def test_invalid(self):
+ """Test when the width is not a valid float value"""
+ tests = [[1, 2, 3], {1: 2, 2: 3}, (1, 2)]
+ port = 1000
+ for test in tests:
+ with pytest.raises(TypeError):
+ VideoSrc(port=port, width=test)
+
+ def test_normal(self):
+ """Test when the width is valid"""
+ tests = [1e6, 300, '200']
+ port = 1000
+ for test in tests:
+ src = VideoSrc(port=port, width=test)
+ assert src.width == test
+
+
+class TestVideoSrcHeight(object):
+
+ """Test for height parameter"""
+
+ def test_blank(self):
+ """Test when the height is a null"""
+ tests = ['', None, [], {}]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, height=test)
+
+ def test_non_positive(self):
+ """Test when heightis non-positive"""
+ tests = ['-1.111', -1.111, 0, -1e10]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, height=test)
+
+ def test_invalid(self):
+ """Test when height is not a valid float value"""
+ tests = [[1, 2, 3], {1: 2, 2: 3}, (1, 2)]
+ port = 1000
+ for test in tests:
+ with pytest.raises(TypeError):
+ VideoSrc(port=port, height=test)
+
+ def test_normal(self):
+ """Test when height is valid"""
+ tests = [1e6, 300, '200']
+ port = 1000
+ for test in tests:
+ src = VideoSrc(port=port, height=test)
+ assert src.height == test
+
+
+class TestVideoSrcPattern(object):
+
+ """Test the pattern parameter"""
+
+ def test_range(self):
+ """Test when pattern is not in range"""
+ tests = [-100, 1e7, 65536, -1, 20]
+ port = 1000
+ for test in tests:
+ with pytest.raises(RangeError):
+ VideoSrc(port=port, pattern=test)
+
+ def test_invalid(self):
+ """Test when pattern is not a valid integer"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}]
+ port = 1000
+ for test in tests:
+ with pytest.raises(TypeError):
+ VideoSrc(port=port, pattern=test)
+
+ def test_normal(self):
+ """Test when pattern is valid"""
+ tests = [1, 0, 19, 10]
+ port = 1000
+ for test in tests:
+ src = VideoSrc(port=port, pattern=test)
+ assert src.pattern == str(test)
+
+
+class TestVideoSrcTimeOverlay(object):
+
+ """Test timeoverlay parameter"""
+
+ def test_fail(self):
+ """Test when timeoverlay is not boolean/valid"""
+ tests = ['', 1234, 'hi', [1, 2], {1: 2}, None, 0, []]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, timeoverlay=test)
+
+ def test_normal(self):
+ """Test when timeoverlay is valid"""
+ tests = [True, False]
+ port = 1000
+ for test in tests:
+ src = VideoSrc(port=port, timeoverlay=test)
+ assert src.timeoverlay == test
+
+
+class TestVideoSrcClockOverlay(object):
+
+ """Test clockoverlay pattern"""
+
+ def test_fail(self):
+ """Test when clockoverlay is not boolean/valid"""
+ tests = ['', 1234, 'hi', [1, 2], {1: 2}, None, 0, []]
+ port = 1000
+ for test in tests:
+ with pytest.raises(ValueError):
+ VideoSrc(port=port, clockoverlay=test)
+
+ def test_normal(self):
+ """Test when clockoverlay is valid"""
+ tests = [True, False]
+ port = 1000
+ for test in tests:
+ src = VideoSrc(port=port, clockoverlay=test)
+ assert src.clockoverlay == test
+
+
+class MockPipeline(object):
+
+ """Mock Pipeline"""
+
+ def play(self):
+ """Play the pipeline"""
+ pass
+
+ def pause(self):
+ """Pause the pipeline"""
+ pass
+
+ def disable(self):
+ """Disable the pipeline"""
+ pass
+
+
+class TestVideoSrcPlay(object):
+
+ """Test Video Source options - play, pause, disable"""
+
+ def test_run(self):
+ """Test run method"""
+ src = VideoSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.run()
+
+ def test_pause(self):
+ """Test pause method"""
+ src = VideoSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.pause()
+
+ def test_end(self):
+ """Test end method"""
+ src = VideoSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.end()
+
+
+class TestPreviewPort(object):
+
+ """Test port parameter"""
+
+ def test_blank(self):
+ """Test when port is null"""
+ tests = ['', None, [], {}]
+ for test in tests:
+ with pytest.raises(ValueError):
+ Preview(port=test)
+
+ def test_range(self):
+ """Test when port is not in range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ Preview(port=test)
+
+ def test_invalid(self):
+ """Test when port is not a valid integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ Preview(port=test)
+
+ def test_normal(self):
+ """Test when port is valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = Preview(port=test)
+ assert src.preview_port == test
+
+
+class TestPreviewPlay(object):
+
+ """Test preview options - play, pause, end"""
+
+ def test_run(self):
+ """Test play method"""
+ src = Preview(port=3001)
+ src.pipeline = MockPipeline()
+ src.run()
+
+ def test_pause(self):
+ """Test pause method"""
+ src = Preview(port=3001)
+ src.pipeline = MockPipeline()
+ src.pause()
+
+ def test_end(self):
+ """Test end method"""
+ src = Preview(port=3001)
+ src.pipeline = MockPipeline()
+ src.end()
+
+
+class TestBasePipeline(object):
+
+ """Test Base Pipeline"""
+
+ def test_play(self, monkeypatch):
+ """Test play method"""
+ monkeypatch.setattr(Gst.Pipeline, '__init__', Mock())
+ pipeline = BasePipeline()
+ pipeline.set_state = Mock()
+ pipeline.play()
+
+ def test_pause(self, monkeypatch):
+ """Test pause method"""
+ monkeypatch.setattr(Gst.Pipeline, '__init__', Mock())
+ pipeline = BasePipeline()
+ pipeline.set_state = Mock()
+ pipeline.pause()
+
+ def test_disable(self, monkeypatch):
+ """Test disable method"""
+ monkeypatch.setattr(Gst.Pipeline, '__init__', Mock())
+ pipeline = BasePipeline()
+ pipeline.set_state = Mock()
+ pipeline.disable()
+
+
+class TestVideoPipeline(object):
+
+ """Test VideoPipeline"""
+
+ def test_permuate_time_clock_1(self):
+ """Test when timeoverlay False and clockoverlay False"""
+ VideoPipeline(
+ port=3000,
+ pattern=10,
+ timeoverlay=False,
+ clockoverlay=False)
+
+ def test_permuate_time_clock_2(self):
+ """test when timeoverlay False and clockoverlay True"""
+ VideoPipeline(
+ port=3000,
+ pattern=10,
+ timeoverlay=False,
+ clockoverlay=True)
+
+ def test_permuate_time_clock_3(self):
+ """Test when timeoverlay True and clockoverlay False"""
+ VideoPipeline(
+ port=3000,
+ pattern=10,
+ timeoverlay=True,
+ clockoverlay=False)
+
+ def test_permuate_time_clock_4(self):
+ """Test when timeoverlay True and clockoverlay True"""
+ VideoPipeline(
+ port=3000,
+ pattern=10,
+ timeoverlay=True,
+ clockoverlay=True)
+
+
+class TestAudioSrcPort(object):
+
+ """Test port parameter"""
+
+ def test_blank(self):
+ """Test when port is null"""
+ tests = ['', None, [], {}]
+ for test in tests:
+ with pytest.raises(ValueError):
+ AudioSrc(port=test)
+
+ def test_range(self):
+ """Test when port is not in range"""
+ tests = [-100, 1e7, 65536]
+ for test in tests:
+ with pytest.raises(RangeError):
+ AudioSrc(port=test)
+
+ def test_invalid(self):
+ """Test when port is not a valid integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ for test in tests:
+ with pytest.raises(TypeError):
+ AudioSrc(port=test)
+
+ def test_normal(self):
+ """Test when port is valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = AudioSrc(port=test)
+ assert src.port == test
+
+
+class TestAudioSrcFreq(object):
+
+ """Test frequency parameter"""
+
+ def test_blank(self):
+ """Test when frequency is null"""
+ tests = ['', None, [], {}, 0]
+ port = 4000
+ for test in tests:
+ with pytest.raises(ValueError):
+ AudioSrc(port=port, freq=test)
+
+ def test_range(self):
+ """Test when frequency is not in range (negative)"""
+ tests = [-100, -1]
+ port = 4000
+ for test in tests:
+ with pytest.raises(RangeError):
+ AudioSrc(port=port, freq=test)
+
+ def test_invalid(self):
+ """Test when frequency is not a valid integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, '1e10']
+ port = 4000
+ for test in tests:
+ with pytest.raises(TypeError):
+ AudioSrc(port=port, freq=test)
+
+ def test_normal(self):
+ """Test when frequency is valid"""
+ tests = [1, 65535, 1000]
+ for test in tests:
+ src = AudioSrc(port=4000, freq=test)
+ assert src.freq == test
+
+
+class TestAudioSrcWave(object):
+
+ """Test wave parameter"""
+
+ def test_range(self):
+ """Test when wave is not in range"""
+ tests = [-100, -1, 13, 1e2, '1e10']
+ port = 4000
+ for test in tests:
+ with pytest.raises(RangeError):
+ AudioSrc(port=port, wave=test)
+
+ def test_invalid(self):
+ """Test when wave is not a valid integral value"""
+ tests = [[1, 2, 3, 4], {1: 2, 2: 3}, ]
+ port = 4000
+ for test in tests:
+ with pytest.raises(TypeError):
+ AudioSrc(port=port, wave=test)
+
+ def test_normal(self):
+ """Test when wave is valid"""
+ tests = [0, 10, 12]
+ for test in tests:
+ src = AudioSrc(port=4000, wave=test)
+ assert src.wave == str(test)
+
+
+class TestAudioSrcPlay(object):
+
+ """Test Audio Source options - play, pause, end"""
+
+ def test_run(self):
+ """Run the audio source"""
+ src = AudioSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.run()
+
+ def test_pause(self):
+ """Pause the audio source"""
+ src = AudioSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.pause()
+
+ def test_end(self):
+ """End the audio source"""
+ src = AudioSrc(port=3000)
+ src.pipeline = MockPipeline()
+ src.end()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..1cbd97f
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+mock
+pytest-cov
+pytest-pep8
+pylint
diff --git a/run-demo.sh b/run-demo.sh
new file mode 100755
index 0000000..f2941f5
--- /dev/null
+++ b/run-demo.sh
@@ -0,0 +1,38 @@
+#!/bin/bash -ex
+
+# FIXME: Rewrite using the Python API so this is reliable.
+
+VIDEO_CAPS="video/x-raw, format=(string)I420, pixel-aspect-ratio=(fraction)1/1, width=(int)300, height=(int)200, framerate=(fraction)25/1"
+AUDIO_CAPS="audio/x-raw, rate=48000, channels=2, format=S16LE, layout=interleaved"
+
+killall gst-switch-srv || true
+sleep 2
+./tools/gst-switch-srv -f "$VIDEO_CAPS" -r &
+sleep 2
+./tools/gst-switch-ui &
+sleep 2
+
+gst-launch-1.0 audiotestsrc is-live=true \
+ ! audioconvert \
+ ! $AUDIO_CAPS \
+ ! gdppay \
+ ! tcpclientsink port=4000 \
+&
+
+gst-launch-1.0 videotestsrc pattern=1 is-live=1 \
+ ! timeoverlay \
+ ! $VIDEO_CAPS \
+ ! gdppay \
+ ! tcpclientsink port=3000 \
+&
+
+gst-launch-1.0 videotestsrc pattern=18 is-live=1 \
+ ! timeoverlay \
+ ! $VIDEO_CAPS \
+ ! gdppay \
+ ! tcpclientsink port=3000 \
+&
+
+wait
+killall gst-switch-srv || true
+killall gst-switch-ui || true
diff --git a/scripts/apps/app_stage.sh b/scripts/apps/app_stage.sh
index 34ce577..5a8b340 100644
--- a/scripts/apps/app_stage.sh
+++ b/scripts/apps/app_stage.sh
@@ -12,7 +12,7 @@ function install-prerequisite()
#local pkginfo=$(get-pkg-info $pkg)
#if [[ "x$pkginfo" == "x" ]]; then
printf "install $pkginfo..\n"
- sudo apt-get install $pkg
+ sudo apt-get -y install $pkg
#else
#printf "package $pkg is ok\n"
#fi
@@ -32,7 +32,15 @@ function install-git-libvpx()
--enable-shared --enable-vp8
make clean || true
- make ${options[make-args]} && make ${options[make-args]} install
+
+ make || {
+ printf "make of $project failed!!!\n"
+ exit -1
+ }
+ make install || {
+ printf "make of $project failed!!!\n"
+ exit -1
+ }
cd $back
@@ -114,7 +122,7 @@ function clone-project()
function clone-duzy-project()
{
- clone-project https://github.com/duzy $1 .git
+ clone-project https://github.com/timvideos $1 .git
}
function clone-gst-project()
@@ -151,7 +159,14 @@ function build-project()
exit -1
}
make clean || true
- make ${options[make-args]} && make ${options[make-args]} install
+ make || {
+ printf "make of $project failed!!!\n"
+ exit -1
+ }
+ make install || {
+ printf "make of $project failed!!!\n"
+ exit -1
+ }
cd $backdir
}
@@ -209,7 +224,6 @@ function main()
gstreamer \
gst-plugins-base \
gst-plugins-good \
- gst-plugins-bad \
gst-plugins-ugly
fi
@@ -219,6 +233,8 @@ function main()
[[ -s scripts/stage ]]; then
cd $back && build-project .
else
+ clone-duzy-project gst-plugins-bad
+ build-gst-project gst-plugins-bad
clone-duzy-project gst-switch
build-project gst-switch
true
diff --git a/scripts/hooks/c-pre-commit b/scripts/hooks/c-pre-commit
new file mode 100755
index 0000000..2bb8cc7
--- /dev/null
+++ b/scripts/hooks/c-pre-commit
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+
+function find-indent () {
+ if [[ -d .git && -h scripts/indent ]]; then
+ echo $PWD/scripts/indent
+ else
+ cd .. && {
+ find-indent
+ cd - > /dev/null
+ }
+ fi
+}
+
+# If there are whitespace errors, print the offending file names and
+files=$(git diff-index --cached --name-only HEAD --diff-filter=ACMR | grep "\.c$")
+indent=$(find-indent)
+
+for file in $files; do
+ nf=`git checkout-index --temp ${file} | cut -f 1`
+ newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1
+ $indent $nf -o $newfile 2>> /dev/null
+ # FIXME: Call indent twice as it tends to do line-breaks
+ # different for every second call.
+ $indent $newfile 2>> /dev/null
+ diff -u -p "${nf}" "${newfile}"
+ r=$?
+ rm "${newfile}"
+ rm "${nf}"
+ if [ $r != 0 ] ; then
+ echo "================================================================================================="
+ echo " Code style error in: $file "
+ echo " "
+ echo " Please fix before committing. Don't forget to run git add before trying to commit again. "
+ echo " If the whole file is to be committed, this should work (run from the top-level directory): "
+ echo " "
+ echo " $indent $file; git add $file; git commit "
+ echo " "
+ echo "================================================================================================="
+ exit 1
+ fi
+ echo "Code style okay: $file"
+done
+
+echo "Code style okay!"
diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit
index 2bb8cc7..10aa931 100755
--- a/scripts/hooks/pre-commit
+++ b/scripts/hooks/pre-commit
@@ -1,50 +1,4 @@
#!/bin/bash
-#
-# An example hook script to verify what is about to be committed.
-# Called by "git commit" with no arguments. The hook should
-# exit with non-zero status after issuing an appropriate message if
-# it wants to stop the commit.
-#
-function find-indent () {
- if [[ -d .git && -h scripts/indent ]]; then
- echo $PWD/scripts/indent
- else
- cd .. && {
- find-indent
- cd - > /dev/null
- }
- fi
-}
-
-# If there are whitespace errors, print the offending file names and
-files=$(git diff-index --cached --name-only HEAD --diff-filter=ACMR | grep "\.c$")
-indent=$(find-indent)
-
-for file in $files; do
- nf=`git checkout-index --temp ${file} | cut -f 1`
- newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1
- $indent $nf -o $newfile 2>> /dev/null
- # FIXME: Call indent twice as it tends to do line-breaks
- # different for every second call.
- $indent $newfile 2>> /dev/null
- diff -u -p "${nf}" "${newfile}"
- r=$?
- rm "${newfile}"
- rm "${nf}"
- if [ $r != 0 ] ; then
- echo "================================================================================================="
- echo " Code style error in: $file "
- echo " "
- echo " Please fix before committing. Don't forget to run git add before trying to commit again. "
- echo " If the whole file is to be committed, this should work (run from the top-level directory): "
- echo " "
- echo " $indent $file; git add $file; git commit "
- echo " "
- echo "================================================================================================="
- exit 1
- fi
- echo "Code style okay: $file"
-done
-
-echo "Code style okay!"
+./scripts/hooks/c-pre-commit
+./scripts/hooks/py-pre-commit
\ No newline at end of file
diff --git a/scripts/hooks/py-pre-commit b/scripts/hooks/py-pre-commit
new file mode 100755
index 0000000..8a51c9c
--- /dev/null
+++ b/scripts/hooks/py-pre-commit
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+#make -C python-api lint pep8
diff --git a/scripts/install.sh b/scripts/install.sh
new file mode 100644
index 0000000..55370ec
--- /dev/null
+++ b/scripts/install.sh
@@ -0,0 +1,146 @@
+#! /bin/bash -ex
+
+sudo apt-get -y install python-software-properties python-pip
+sudo pip install mock
+sudo pip install pytest-cov
+sudo pip install pytest-pep8
+sudo pip install pylint
+sudo apt-get -y install libglib2.0-dev gir1.2-glib-2.0 libgirepository1.0-dev libglib2.0-0 python-gi
+sudo apt-get -y install python-scipy
+sudo apt-get -y install ffmpeg || sudo apt-get -y install libav-tools
+sudo apt-get -y install autoconf automake autopoint libbz2-dev libdv4-dev libfaac-dev libfaad-dev libgtk-3-dev libmjpegtools-dev libtag1-dev libasound2-dev libtool libvpx-dev libxv-dev libx11-dev libogg-dev libvorbis-dev libopencv-dev libcv-dev libhighgui-dev libv4l-dev pkg-config zlib1g-dev gtk-doc-tools yasm bison flex
+export PKG_CONFIG_PATH=/usr/lib/pkgconfig/
+export LD_LIBRARY_PATH=/usr/lib/
+git clone http://git.chromium.org/webm/libvpx.git
+cd libvpx
+git checkout v1.2.0
+./configure --enable-shared --enable-vp8 --prefix=/usr
+[[ -f Makefile ]] || {
+ printf "Configure libvpx failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make
+sudo make install || {
+ printf "make install of libvpx failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone git://anongit.freedesktop.org/gstreamer/gstreamer
+cd gstreamer
+git checkout 6c11da1
+./autogen.sh --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+[[ -f Makefile ]] || {
+ printf "Configure gstreamer failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make clean
+make || {
+ printf "make of $project failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of gstreamer failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone git://anongit.freedesktop.org/gstreamer/gst-plugins-base
+cd gst-plugins-base
+git checkout a8df760
+./autogen.sh --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+[[ -f Makefile ]] || {
+ printf "Configure gst-plugins-base failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make clean
+make || {
+ printf "make of gst-plugins-base failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of $project failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone git://anongit.freedesktop.org/gstreamer/gst-plugins-good
+cd gst-plugins-good
+git checkout d14d4c4
+./autogen.sh --enable-experimental --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+[[ -f Makefile ]] || {
+ printf "Configure gst-plugins-good failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make clean
+make || {
+ printf "make of gst-plugins-good failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of $project failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone git://anongit.freedesktop.org/gstreamer/gst-plugins-ugly
+cd gst-plugins-ugly
+git checkout 68985ba
+./autogen.sh --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+[[ -f Makefile ]] || {
+ printf "Configure gst-plugins-ugly failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make clean
+make || {
+ printf "make of gst-plugins-ugly failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of gst-plugins-ugly failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone https://github.com/hyades/gst-plugins-bad.git
+cd gst-plugins-bad
+./autogen.sh --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+[[ -f Makefile ]] || {
+ printf "Configure gst-plugins-bad failed, no Makefile generated!!!\n"
+ exit -1
+ }
+make clean
+make || {
+ printf "make of gst-plugins-bad failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of gst-plugins-bad failed!!!\n"
+ exit -1
+ }
+cd ..
+git clone https://github.com/hyades/gst-switch.git
+cd gst-switch
+./autogen.sh --prefix=/usr || {
+printf "Failed to do autogen!!!\n"
+exit -1
+}
+make clean
+make || {
+ printf "make of gstswitch failed!!!\n"
+ exit -1
+}
+sudo make install || {
+ printf "make install of gstswitch failed!!!\n"
+ exit -1
+ }
diff --git a/scripts/install2.sh b/scripts/install2.sh
new file mode 100644
index 0000000..116293f
--- /dev/null
+++ b/scripts/install2.sh
@@ -0,0 +1,8 @@
+#!/bin/bash -ex
+mkdir build
+cd build
+sudo apt-get install git python-pip
+git clone https://github.com/timvideos/gst-switch.git
+cd gst-switch
+chmod +x .travis-setup.sh
+./.travis-setup.sh
diff --git a/tests/.gitignore b/tests/.gitignore
index 544b00e..84a202c 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -2,7 +2,7 @@
/Makefile.in
/.deps
/tools
-/test-recording*.data
+/test-recording*.data*
/test-server-*.log
/test-fd-leaks
/tests
diff --git a/tests/faac.supp b/tests/faac.supp
deleted file mode 100644
index 9f5dd99..0000000
--- a/tests/faac.supp
+++ /dev/null
@@ -1,49 +0,0 @@
-{
-
- Memcheck:Cond
- obj:/usr/lib/libfaac.so.0.0.0
- fun:*
-}
-{
-
- Memcheck:Value8
- obj:/usr/lib/libfaac.so.0.0.0
- fun:*
-}
-{
-
- Memcheck:Cond
- ...
- fun:gst_faac_handle_frame
- ...
- fun:start_thread
- fun:clone
-}
-{
-
- Memcheck:Value8
- ...
- fun:gst_faac_handle_frame
- ...
- fun:start_thread
- fun:clone
-}
-{
-
- Memcheck:Leak
- ...
- fun:gst_faac_set_format
- ...
- fun:start_thread
- fun:clone
-}
-{
-
- Memcheck:Leak
- ...
- fun:vp8e_init
- ...
- fun:start_thread
- fun:clone
-}
-
diff --git a/tests/test_switch_server.c b/tests/test_switch_server.c
index 6100a5c..7170d4a 100644
--- a/tests/test_switch_server.c
+++ b/tests/test_switch_server.c
@@ -328,7 +328,7 @@ child_quit (GPid pid, gint status, gpointer data)
INFO ("quit %d", pid);
{
g_mutex_lock (&children_lock);
- children = g_list_remove (children, (gconstpointer) pid);
+ children = g_list_remove (children, (gconstpointer) GINT_TO_POINTER(pid));
g_mutex_unlock (&children_lock);
}
@@ -447,7 +447,7 @@ launch (const gchar * name, ...)
if (ok) {
g_mutex_lock (&children_lock);
- children = g_list_append (children, (gpointer) pid);
+ children = g_list_append (children, (gpointer) GINT_TO_POINTER(pid));
g_mutex_unlock (&children_lock);
}
@@ -883,8 +883,8 @@ testclient_new_mode (testclient * client, gint mode)
{
//INFO ("set-encode-port: %d", port);
client->new_mode_count += 1;
- g_assert_cmpint (mode, >=, COMPOSE_MODE_0);
- g_assert_cmpint (mode, <=, COMPOSE_MODE_3);
+ g_assert_cmpint (mode, >=, COMPOSE_MODE_NONE);
+ g_assert_cmpint (mode, <=, COMPOSE_MODE_DUAL_EQUAL);
}
static void
@@ -2369,7 +2369,7 @@ kill_children (void)
GList *child = NULL;
g_mutex_lock (&children_lock);
for (child = children; child; child = g_list_next (child)) {
- GPid pid = (GPid) child->data;
+ GPid pid = (GPid) GPOINTER_TO_INT(child->data);
//INFO ("kill %d", pid);
kill (pid, SIGKILL);
children = g_list_remove_link (children, child);
diff --git a/tests/test_ui_lag1.sh b/tests/test_ui_lag1.sh
new file mode 100755
index 0000000..06c1b71
--- /dev/null
+++ b/tests/test_ui_lag1.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -ex
+
+# test_ui_lag1.sh
+# demos issue #30: lag between thumb and canvas
+
+cd ../tools
+
+./gst-switch-srv & srvpid=$!
+sleep 5
+./gst-switch-ui & uipid=$!
+
+# srcpid=()
+for i in 1 2 3 4 5 6; do
+ sleep 2
+ gst-launch-1.0 videotestsrc pattern=18 is-live=1 \
+ ! textoverlay text="s ${i}" \
+ halignment=2 valignment=2 font-desc="Sans 40" \
+ ! timeoverlay font-desc="Sans 40" \
+ ! clockoverlay time-format="%S" font-desc="Sans 240" \
+ ! video/x-raw, width=300, height=200 \
+ ! gdppay \
+ ! tcpclientsink port=3000 \
+ & srcpid[i]=$!
+done
+
+read -p "press enter to kill all the processes:"
+
+kill $srvpid $uipid
+for i in 1 2 3 4 5 6; do kill ${srcpid[i]}; done
+
+
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
new file mode 100644
index 0000000..cb794e5
--- /dev/null
+++ b/tests/unit/Makefile.am
@@ -0,0 +1,37 @@
+include $(top_srcdir)/build/glib-tap.mk
+
+LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(GST_LIBS)
+
+test_gstswitchopts_SOURCES = test_gstswitchopts.c
+test_gstswitchopts_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+ $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\""
+test_gstrecorder_filename_SOURCES = test_gstrecorder_filename.c ../../tools/gstworker.c
+test_gstrecorder_filename_CFLAGS = -fprofile-arcs $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+ $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\""
+test_gstrecorder_filename_LDFLAGS = $(GCOV_LFLAGS)
+test_gstcomposite_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+ $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\""
+test_gstcomposite_LDFLAGS = $(GCOV_LFLAGS)
+
+test_gst_pipeline_string_SOURCES = test_gst_pipeline_string.c ../../tools/gstworker.c
+test_gst_pipeline_string_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+ $(GCOV_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tests\""
+test_gst_pipeline_string_LDFLAGS = $(GCOV_LFLAGS)
+
+
+dist_test_data = \
+ $(NULL)
+
+test_programs = \
+ test_gstswitchopts \
+ test_gstrecorder_filename \
+ test_gstcomposite \
+ test_gst_pipeline_string \
+ $(NULL)
+
+if GCOV_ENABLED
+coverage:
+ gcov *.o
+endif
+
+include $(top_srcdir)/build/Makefile.am.gitignore
diff --git a/tests/unit/gstswitchexample.c b/tests/unit/gstswitchexample.c
new file mode 100644
index 0000000..e4afed5
--- /dev/null
+++ b/tests/unit/gstswitchexample.c
@@ -0,0 +1,23 @@
+#include
+
+static void
+success (void)
+{
+ g_assert_cmpint (1234, ==, 1234);
+}
+
+static void
+fail (void)
+{
+ g_assert_cmpint (1234, ==, 1);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions ();
+ g_test_add_func ("/gstswitch/server/options/success", success);
+ g_test_add_func ("/gstswitch/server/options/fail", fail);
+ return g_test_run ();
+}
diff --git a/tests/unit/test_gst_pipeline_string.c b/tests/unit/test_gst_pipeline_string.c
new file mode 100644
index 0000000..b08afa4
--- /dev/null
+++ b/tests/unit/test_gst_pipeline_string.c
@@ -0,0 +1,240 @@
+
+#include "tools/gstcase.c"
+#include
+
+gboolean verbose = FALSE;
+
+static gchar *results[GST_CASE__LAST_TYPE + 1] = {
+ // GST_CASE_UNKNOWN
+ NULL,
+ // GST_CASE_COMPOSITE_VIDEO_A
+ NULL,
+ // GST_CASE_COMPOSITE_VIDEO_B
+ NULL,
+ // GST_CASE_COMPOSITE_AUDIO
+ NULL,
+ // GST_CASE_PREVIEW
+ NULL,
+ // GST_CASE_INPUT_AUDIO
+ NULL,
+ // GST_CASE_INPUT_VIDEO
+ NULL,
+ // GST_CASE_BRANCH_VIDEO_A
+ NULL,
+ // GST_CASE_BRANCH_VIDEO_B
+ NULL,
+ // GST_CASE_BRANCH_AUDIO
+ NULL,
+ // GST_CASE_BRANCH_PREVIEW
+ NULL
+};
+
+static gchar *
+expected_string (int case_type, int serve_type)
+{
+ switch (case_type) {
+ case GST_CASE_PREVIEW: // special case
+ if (serve_type == GST_SERVE_VIDEO_STREAM)
+ return "";
+ return "";
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ case GST_CASE_COMPOSITE_AUDIO:
+ case GST_CASE_INPUT_AUDIO:
+ case GST_CASE_INPUT_VIDEO:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
+ case GST_CASE_BRANCH_AUDIO:
+ case GST_CASE_BRANCH_PREVIEW:
+ return results[case_type];
+ }
+ return NULL;
+}
+
+
+static GstCase *
+new_case (int case_type, int serve_type)
+{
+ switch (serve_type) {
+ case GST_SERVE_AUDIO_STREAM:
+ return GST_CASE (g_object_new (GST_TYPE_CASE,
+ "name", "audio", "type", case_type, "serve", serve_type,
+ "port", 1234, NULL));
+ case GST_SERVE_VIDEO_STREAM:
+ return GST_CASE (g_object_new (GST_TYPE_CASE,
+ "name", "video", "type", case_type, "serve", serve_type,
+ "width", 640, "height", 480, "port", 1234, NULL));
+ }
+ return NULL;
+}
+
+static void
+test_get_pipeline_string_input_audio (void)
+{
+ GstCase *cas = new_case (GST_CASE_INPUT_AUDIO, GST_SERVE_AUDIO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_INPUT_AUDIO, GST_SERVE_AUDIO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_INPUT_AUDIO: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_input_video (void)
+{
+ GstCase *cas = new_case (GST_CASE_INPUT_VIDEO, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_INPUT_VIDEO, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_INPUT_VIDEO: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_preview_video (void)
+{
+ GstCase *cas = new_case (GST_CASE_PREVIEW, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_PREVIEW, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_PREVIEW/V: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_preview_audio (void)
+{
+ GstCase *cas = new_case (GST_CASE_PREVIEW, GST_SERVE_AUDIO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_PREVIEW, GST_SERVE_AUDIO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_PREVIEW/A: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_composite_audio (void)
+{
+ GstCase *cas = new_case (GST_CASE_COMPOSITE_AUDIO, GST_SERVE_AUDIO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_COMPOSITE_AUDIO, GST_SERVE_AUDIO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_COMPOSITE_AUDIO: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_composite_video_a (void)
+{
+ GstCase *cas = new_case (GST_CASE_COMPOSITE_VIDEO_A, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_COMPOSITE_VIDEO_A, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_COMPOSITE_VIDEO_A: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_composite_video_b (void)
+{
+ GstCase *cas = new_case (GST_CASE_COMPOSITE_VIDEO_B, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_COMPOSITE_VIDEO_B, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_COMPOSITE_VIDEO_B: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_branch_video_a (void)
+{
+ GstCase *cas = new_case (GST_CASE_BRANCH_VIDEO_A, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_BRANCH_VIDEO_A, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_BRANCH_VIDEO_A: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_branch_video_b (void)
+{
+ GstCase *cas = new_case (GST_CASE_BRANCH_VIDEO_B, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_BRANCH_VIDEO_B, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_BRANCH_VIDEO_B: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_branch_preview (void)
+{
+ GstCase *cas = new_case (GST_CASE_BRANCH_PREVIEW, GST_SERVE_VIDEO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_BRANCH_PREVIEW, GST_SERVE_VIDEO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_BRANCH_PREVIEW: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+static void
+test_get_pipeline_string_branch_audio (void)
+{
+ GstCase *cas = new_case (GST_CASE_BRANCH_AUDIO, GST_SERVE_AUDIO_STREAM);
+ GString *desc = gst_case_get_pipeline_string (cas);
+ g_assert (desc != NULL && strlen (desc->str) > 0);
+//g_assert_cmpstr(expected_string(GST_CASE_BRANCH_AUDIO, GST_SERVE_AUDIO_STREAM), ==, desc->str);
+ printf ("\nGST_CASE_BRANCH_AUDIO: %s\n", desc->str);
+ g_string_free (desc, TRUE);
+ g_object_unref (cas);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ gst_init (&argc, &argv);
+ g_test_set_nonfatal_assertions ();
+ g_test_add_func ("/gstswitch/server/gstcase/get_pipeline_string/INPUT/AUDIO",
+ test_get_pipeline_string_input_audio);
+ g_test_add_func ("/gstswitch/server/gstcase/get_pipeline_string/INPUT/VIDEO",
+ test_get_pipeline_string_input_video);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/PREVIEW/AUDIO",
+ test_get_pipeline_string_preview_audio);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/PREVIEW/VIDEO",
+ test_get_pipeline_string_preview_video);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/COMPOSITE/AUDIO",
+ test_get_pipeline_string_composite_audio);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/COMPOSITE/VIDEO_A",
+ test_get_pipeline_string_composite_video_a);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/COMPOSITE/VIDEO_B",
+ test_get_pipeline_string_composite_video_b);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/VIDEO_A",
+ test_get_pipeline_string_branch_video_a);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/VIDEO_B",
+ test_get_pipeline_string_branch_video_b);
+ g_test_add_func ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/AUDIO",
+ test_get_pipeline_string_branch_audio);
+ g_test_add_func
+ ("/gstswitch/server/gstcase/get_pipeline_string/BRANCH/PREVIEW",
+ test_get_pipeline_string_branch_preview);
+ return g_test_run ();
+}
diff --git a/tests/unit/test_gstcomposite.c b/tests/unit/test_gstcomposite.c
new file mode 100644
index 0000000..72d7514
--- /dev/null
+++ b/tests/unit/test_gstcomposite.c
@@ -0,0 +1,20 @@
+#include
+
+#include "tools/gstcomposite.h"
+
+static void
+mode_to_string (void)
+{
+ g_assert_cmpstr (gst_composite_mode_to_string (COMPOSE_MODE_NONE), ==,
+ "COMPOSE_MODE_NONE");
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_set_nonfatal_assertions ();
+ g_test_add_func ("/gstswitch/server/composite/mode_to_string",
+ mode_to_string);
+ return g_test_run ();
+}
diff --git a/tests/unit/test_gstrecorder_filename.c b/tests/unit/test_gstrecorder_filename.c
new file mode 100644
index 0000000..ed625fc
--- /dev/null
+++ b/tests/unit/test_gstrecorder_filename.c
@@ -0,0 +1,91 @@
+
+#include "tools/gstrecorder.c"
+#include
+
+#define BASEDIR "/tmp/unittests/"
+
+// fake options struct, not used in these tests
+GstSwitchServerOpts opts;
+gboolean verbose = FALSE;
+
+
+
+static char *
+filepath (char *buf, size_t len, char const *fn)
+{
+ snprintf (buf, len, BASEDIR "%d/%s", getpid (), fn);
+ return buf;
+}
+
+
+static gboolean
+exists (const char *filepath, gboolean dir)
+{
+ struct stat s;
+
+ if (stat (filepath, &s) == 0) {
+ if (!dir || S_ISDIR (s.st_mode))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+touch (const char *filepath)
+{
+ int fd =
+ open (filepath, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd != -1) {
+ close (fd);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+static int
+create_new_directory (const char *name)
+{
+ char buf[256];
+
+ gst_recorder_mkdirs (filepath (buf, sizeof buf, name));
+ return exists (buf, TRUE);
+}
+
+
+static gboolean
+create_capture_file (const char *name)
+{
+ char buf[256];
+ char const *capfilename =
+ gst_recorder_new_filename (filepath (buf, sizeof buf, name));
+ return touch (capfilename);
+}
+
+static void
+test_mkdirs ()
+{
+ g_assert_cmpint (create_new_directory ("testdir1"), !=, 0);
+ g_assert_cmpint (create_new_directory ("testdir2"), !=, 0);
+ g_assert_cmpint (create_new_directory ("testdir1/testdir3"), !=, 0);
+ g_assert_cmpint (create_new_directory ("testdir3/testdir1"), !=, 0);
+}
+
+static void
+test_new_filename ()
+{
+ g_assert_true (create_capture_file ("%Y%m%d_%T"));
+ g_assert_true (create_capture_file ("%Y%m/%d_%T"));
+ g_assert_true (create_capture_file ("%Y/%m/%d/%T"));
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ gst_init (&argc, &argv);
+ g_test_set_nonfatal_assertions ();
+ g_test_add_func ("/gstswitch/options/mkdirs", test_mkdirs);
+ g_test_add_func ("/gstswitch/options/filename", test_new_filename);
+ return g_test_run ();
+}
diff --git a/tests/unit/test_gstswitchopts.c b/tests/unit/test_gstswitchopts.c
new file mode 100644
index 0000000..50ef565
--- /dev/null
+++ b/tests/unit/test_gstswitchopts.c
@@ -0,0 +1,44 @@
+
+#include "tools/gstswitchopts.c"
+
+static void
+test_strings_good (void)
+{
+ g_assert_cmpint (parse_format ("debug", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format ("pal", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format ("720p60", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format ("1024x768@60", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format ("VGA@60", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format ("4k@60", NULL, NULL), ==, 0);
+ g_assert_cmpint (parse_format
+ ("video/x-raw,height=400,width=500,framerate=25/1", NULL, NULL), ==, 0);
+}
+
+static void
+test_strings_bad (void)
+{
+ g_assert_cmpint (parse_format
+ ("video/x-raw,height=[400,800],width=500,framerate=25/1", NULL, NULL), ==,
+ -1);
+ g_assert_cmpint (parse_format ("720p@75", NULL, NULL), ==, -1);
+ g_assert_cmpint (parse_format ("bad-format-string", NULL, NULL), ==, -1);
+ g_assert_cmpint (parse_format
+ ("video/x-raw,height=10,width=500,framerate=25/1", NULL, NULL), ==, -1);
+ g_assert_cmpint (parse_format
+ ("video/x-raw,height=400,width=10,framerate=25/1", NULL, NULL), ==, -1);
+ g_assert_cmpint (parse_format
+ ("video/x-raw,height=400,width=10,framerate=1001/1", NULL, NULL), ==, -1);
+// @FIXME the following actually works but it shouldn't
+// g_assert_cmpint (parse_format("pal@75", NULL, NULL), ==, -1);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ gst_init (&argc, &argv);
+ g_test_set_nonfatal_assertions ();
+ g_test_add_func ("/gstswitch/options/format/good", test_strings_good);
+ g_test_add_func ("/gstswitch/options/format/bad", test_strings_bad);
+ return g_test_run ();
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 8802a5a..b8b10c9 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,4 +1,8 @@
-bin_PROGRAMS = gst-switch-srv gst-switch-ui gst-switch-cap gst-switch-ptz
+if SPEAKERTRACK_ENABLED
+ bin_PROGRAMS = gst-switch-srv gst-switch-ui gst-switch-cap gst-switch-ptz
+else
+ bin_PROGRAMS = gst-switch-srv gst-switch-ui gst-switch-cap
+endif
if GCOV_ENABLED
# --coverage
@@ -11,11 +15,17 @@ GCOV_CFLAGS =
GCOV_LFLAGS =
endif
+if DEBUG
+AM_CFLAGS = -g3 -O0 -gdwarf-4 -fvar-tracking-assignments -fvar-tracking
+else
+AM_CFLAGS = -O2
+endif
+
gst_switch_srv_SOURCES = gstworker.c gstswitchserver.c gstcase.c \
gstcomposite.c gstswitchcontroller.c gstrecorder.c \
- gio/gsocketinputstream.c
+ gio/gsocketinputstream.c gstswitchopts.c
gst_switch_srv_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GCOV_CFLAGS) \
- $(GST_PLUGINS_BASE_CFLAGS) -DLOG_PREFIX="\"./tools\""
+ $(GST_PLUGINS_BASE_CFLAGS) $(AM_CFLAGS) -DLOG_PREFIX="\"gst-switch-srv\""
gst_switch_srv_LDFLAGS = $(GCOV_LFLAGS) $(GST_LIBS) $(GST_BASE_LIBS) \
$(GST_PLUGINS_BASE_LIBS) $(GSTPB_BASE_LIBS)
gst_switch_srv_LDADD = $(GIO_LIBS) $(LIBM)
@@ -23,8 +33,8 @@ gst_switch_srv_LDADD = $(GIO_LIBS) $(LIBM)
gst_switch_ui_SOURCES = gstworker.c gstswitchui.c gstvideodisp.c \
gstaudiovisual.c gstswitchclient.c
gst_switch_ui_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GCOV_CFLAGS) \
- $(GST_PLUGINS_BASE_CFLAGS) $(X_CFLAGS) $(GTK_CFLAGS) \
- -DLOG_PREFIX="\"./tools\""
+ $(GST_PLUGINS_BASE_CFLAGS) $(X_CFLAGS) $(GTK_CFLAGS) $(AM_CFLAGS) \
+ -DLOG_PREFIX="\"gst-switch-ui\""
gst_switch_ui_LDFLAGS = $(GCOV_LFLAGS) $(GST_LIBS) $(GST_BASE_LIBS) \
$(GST_PLUGINS_BASE_LIBS) $(GSTPB_BASE_LIBS) -lm
gst_switch_ui_LDADD = $(GST_LIBS) $(X_LIBS) $(LIBM) $(GTK_LIBS) $(GLIB_LIBS)
@@ -32,7 +42,7 @@ gst_switch_ui_LDADD = $(GST_LIBS) $(X_LIBS) $(LIBM) $(GTK_LIBS) $(GLIB_LIBS)
gst_switch_cap_SOURCES = gstworker.c gstswitchcapture.c gstswitchclient.c
gst_switch_cap_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GCOV_CFLAGS) \
$(GST_PLUGINS_BASE_CFLAGS) $(X_CFLAGS) $(GTK_CFLAGS) \
- -DLOG_PREFIX="\"./tools\""
+ -DLOG_PREFIX="\"gst-switch-cap\""
gst_switch_cap_LDFLAGS = $(GCOV_LFLAGS) $(GST_LIBS) $(GST_BASE_LIBS) \
$(GST_PLUGINS_BASE_LIBS) $(GSTPB_BASE_LIBS) -lm
gst_switch_cap_LDADD = $(GST_LIBS) $(X_LIBS) $(LIBM) $(GTK_LIBS) $(GLIB_LIBS)
@@ -40,12 +50,12 @@ gst_switch_cap_LDADD = $(GST_LIBS) $(X_LIBS) $(LIBM) $(GTK_LIBS) $(GLIB_LIBS)
gst_switch_ptz_SOURCES = gstworker.c gstvideodisp.c gstswitchptz.c
gst_switch_ptz_CFLAGS = -g -ggdb $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GCOV_CFLAGS) \
$(GST_PLUGINS_BASE_CFLAGS) $(X_CFLAGS) $(GTK_CFLAGS) \
- -DLOG_PREFIX="\".\""
+ -DLOG_PREFIX="\"gst-switch-ptz\""
gst_switch_ptz_LDFLAGS = $(GCOV_LFLAGS) $(GST_LIBS) $(GST_BASE_LIBS) \
$(GST_PLUGINS_BASE_LIBS) $(GST_PLUGINS_BAD_LIBS) $(GSTPB_BASE_LIBS)
gst_switch_ptz_LDADD = $(GST_LIBS) $(X_LIBS) $(LIBM) $(GTK_LIBS) \
$(GLIB_LIBS) -lm \
- $(HOME)/gst/stage/lib/libgstcamcontrol-1.0.so
+ $(prefix)/lib/libgstcamcontrol-1.0.so
# -lgstcamcontrol
diff --git a/tools/gio/gsocketinputstream.c b/tools/gio/gsocketinputstream.c
index 6ec2bff..99946ae 100644
--- a/tools/gio/gsocketinputstream.c
+++ b/tools/gio/gsocketinputstream.c
@@ -85,6 +85,21 @@ g_socket_input_stream_set_property (GObject * object,
}
}
+static gboolean
+g_socket_input_stream_close (GInputStream * s,
+ GCancellable * cancellable, GError ** error)
+{
+ GSocketInputStreamX *stream = G_SOCKET_INPUT_STREAM (s);
+ gboolean ret = TRUE;
+
+ if (stream->priv->socket) {
+ ret = g_socket_close (stream->priv->socket, error);
+ g_object_unref (stream->priv->socket);
+ stream->priv->socket = NULL;
+ }
+ return ret;
+}
+
static void
g_socket_input_stream_finalize (GObject * object)
{
@@ -92,15 +107,10 @@ g_socket_input_stream_finalize (GObject * object)
if (stream->priv->socket) {
GError *error = NULL;
- /*
- g_print ("%s:%d: %s, %d\n", __FILE__, __LINE__, __FUNCTION__,
- g_socket_get_fd (stream->priv->socket));
- */
- g_socket_close (stream->priv->socket, &error);
+ g_socket_input_stream_close (&stream->parent_instance, NULL, &error);
if (error) {
//ERROR ("%s", error->message);
}
- g_object_unref (stream->priv->socket);
}
if (G_OBJECT_CLASS (g_socket_input_stream_parent_class)->finalize)
@@ -117,6 +127,7 @@ g_socket_input_stream_read (GInputStream * stream,
buffer, count, TRUE, cancellable, error);
}
+
static void
g_socket_input_stream_class_init (GSocketInputStreamXClass * klass)
{
@@ -130,6 +141,7 @@ g_socket_input_stream_class_init (GSocketInputStreamXClass * klass)
gobject_class->set_property = g_socket_input_stream_set_property;
ginputstream_class->read_fn = g_socket_input_stream_read;
+ ginputstream_class->close_fn = g_socket_input_stream_close;
g_object_class_install_property (gobject_class, PROP_SOCKET,
g_param_spec_object ("socket",
diff --git a/tools/gio/gsocketinputstream.h b/tools/gio/gsocketinputstream.h
index 9d319f4..98ed08b 100644
--- a/tools/gio/gsocketinputstream.h
+++ b/tools/gio/gsocketinputstream.h
@@ -62,4 +62,4 @@ GSocketInputStreamX * _g_socket_input_stream_new (GSocke
G_END_DECLS
#undef __GIO_GIO_H_INSIDE__
-#endif /* __G_SOCKET_INPUT_STREAM_H___ */
+#endif //__G_SOCKET_INPUT_STREAM_H__
diff --git a/tools/gstaudiovisual.c b/tools/gstaudiovisual.c
index 8308fb8..c8553d1 100644
--- a/tools/gstaudiovisual.c
+++ b/tools/gstaudiovisual.c
@@ -238,22 +238,26 @@ gst_audio_visual_get_pipeline_string (GstAudioVisual * visual)
desc = g_string_new ("");
- g_string_append_printf (desc, "tcpclientsrc name=source "
- "port=%d ", visual->port);
- g_string_append_printf (desc, "! gdpdepay ! faad ! tee name=a ");
+ g_string_append_printf (
+ desc, "tcpclientsrc name=source port=%d ", visual->port);
+ g_string_append_printf (desc, "! gdpdepay ! tee name=a\n");
if (visual->active) {
- g_string_append_printf (desc, "a. ! queue2 ! audioconvert "
- "! level name=level message=true " "! alsasink name=play ");
+ g_string_append_printf (
+ desc,
+ "a. ! queue ! audioconvert ! level name=level message=true !"
+ " autoaudiosink name=play sync=false\n");
}
- g_string_append_printf (desc, "a. ! queue2 ! audioconvert ! monoscope ");
+ g_string_append_printf (desc, "a. ! queue ! audioconvert ! monoscope ");
if (visual->active) {
g_string_append_printf (desc, "! textoverlay text=\"active\" "
"font-desc=\"Sans 50\" " "shaded-background=true " "auto-resize=true ");
}
- g_string_append_printf (desc, "! autovideoconvert ");
- g_string_append_printf (desc, "! xvimagesink name=visual ");
+ g_string_append_printf (desc, "! videoconvert ");
+ g_string_append_printf (desc, "! xvimagesink name=visual sync=false ");
+
+ INFO("Audio preview: %s", desc->str);
return desc;
}
@@ -341,12 +345,12 @@ gst_audio_visual_message (GstAudioVisual * visual, GstMessage * message)
INFO (" channel: %d, RMS=%f dB, peak=%f dB, decay=%f dB, "
"normalized-RMS=%f", i, rms_dB, peak_dB, decay_dB, rms);
}
- } else if (G_VALUE_HOLDS (list_rms, G_TYPE_VALUE_ARRAY)) {
- GValueArray *va = (GValueArray *) g_value_get_boxed (list_rms);
+ } else if (G_VALUE_HOLDS (list_rms, G_TYPE_ARRAY)) {
+ GArray *va = (GArray *) g_value_get_boxed (list_rms);
gdouble v = 0.0;
- channels = va->n_values;
+ channels = va->len;
for (i = 0; i < channels; ++i) {
- value = g_value_array_get_nth (va, i);
+ value = g_array_index (va, GValue*, i);
rms_dB = g_value_get_double (value);
/* converting from dB to normal gives us a value between 0.0 and 1.0
diff --git a/tools/gstaudiovisual.h b/tools/gstaudiovisual.h
index f782784..50ffef0 100644
--- a/tools/gstaudiovisual.h
+++ b/tools/gstaudiovisual.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_AUDIO_VISUAL_H__by_Duzy_Chan__
-#define __GST_AUDIO_VISUAL_H__by_Duzy_Chan__ 1
+#ifndef __GST_AUDIO_VISUAL_H__
+#define __GST_AUDIO_VISUAL_H__
+
#include "gstworker.h"
#define GST_TYPE_AUDIO_VISUAL (gst_audio_visual_get_type ())
@@ -56,7 +57,7 @@ struct _GstAudioVisual
gboolean renewing; /*!< Used by GstSwitchUI. */
GMutex endtime_lock; /*!< the lock for %endtime */
- GstClockTime endtime; /*!< the endtime of the last audo sample */
+ GstClockTime endtime; /*!< the endtime of the last audio sample */
GMutex value_lock; /*!< the lock for %value */
gdouble value; /*!< the value of the last audio sample */
};
@@ -76,4 +77,4 @@ GType gst_audio_visual_get_type (void);
GstClockTime gst_audio_visual_get_endtime (GstAudioVisual * visual);
gdouble gst_audio_visual_get_value (GstAudioVisual * visual);
-#endif //__GST_AUDIO_VISUAL_H__by_Duzy_Chan__
+#endif //__GST_AUDIO_VISUAL_H__
diff --git a/tools/gstcase.c b/tools/gstcase.c
index 22e4940..c6db90b 100644
--- a/tools/gstcase.c
+++ b/tools/gstcase.c
@@ -90,24 +90,20 @@ gst_case_init (GstCase * cas)
}
/**
- * @param cas The GstCase instance.
+ * @param cas the GstCase instance.
* @memberof GstCase
*
- * Disposing from it's parent object.
- *
- * @see GObject
+ * Closes/releases resources used by the GstCase
*/
static void
-gst_case_dispose (GstCase * cas)
+gst_case_close (GstCase * cas)
{
if (cas->stream) {
-#if 0
GError *error = NULL;
g_input_stream_close (cas->stream, NULL, &error);
if (error) {
ERROR ("%s", error->message);
}
-#endif
g_object_unref (cas->stream);
cas->stream = NULL;
}
@@ -121,6 +117,20 @@ gst_case_dispose (GstCase * cas)
g_object_unref (cas->branch);
cas->branch = NULL;
}
+}
+
+/**
+ * @param cas The GstR (Case instance.
+ * @memberof GstCase
+ *
+ * Disposing from it's parent object.
+ *
+ * @see GObject
+ */
+static void
+gst_case_dispose (GstCase * cas)
+{
+ gst_case_close (cas);
//INFO ("dispose %p", cas);
G_OBJECT_CLASS (parent_class)->dispose (G_OBJECT (cas));
}
@@ -281,167 +291,83 @@ gst_case_set_property (GstCase * cas, guint property_id,
static GString *
gst_case_get_pipeline_string (GstCase * cas)
{
- GString *desc;
- gchar *channel = NULL;
- gchar *caps = NULL;
- gchar *scale = NULL;
- gchar *srctype = NULL;
- gchar *sink = NULL;
-
- desc = g_string_new ("");
+ gboolean is_audiostream = cas->serve_type == GST_SERVE_AUDIO_STREAM;
+ GString *desc = g_string_new ("");
+ const gchar *caps =
+ is_audiostream ?
+ gst_switch_server_get_audio_caps_str () :
+ gst_switch_server_get_video_caps_str ();
switch (cas->type) {
- case GST_CASE_INPUT_a:
- case GST_CASE_INPUT_v:
- g_string_append_printf (desc, "giostreamsrc name=source ");
+ case GST_CASE_INPUT_AUDIO:
+ g_string_append_printf (desc,
+ "giostreamsrc name=source ! gdpdepay ! %s ! interaudiosink name=sink channel=input_%d",
+ caps, cas->sink_port);
+ break;
+
+ case GST_CASE_INPUT_VIDEO:
+ g_string_append_printf (desc,
+ "giostreamsrc name=source ! gdpdepay ! %s ! intervideosink name=sink channel=input_%d",
+ caps, cas->sink_port);
break;
- case GST_CASE_COMPOSITE_A:
- case GST_CASE_COMPOSITE_B:
- case GST_CASE_COMPOSITE_a:
+
case GST_CASE_PREVIEW:
- if (srctype == NULL)
- srctype = "input";
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
- case GST_CASE_BRANCH_a:
- case GST_CASE_BRANCH_p:
- if (srctype == NULL)
- srctype = "branch";
- if (cas->serve_type == GST_SERVE_AUDIO_STREAM) {
- g_string_append_printf (desc, "interaudiosrc");
+ if (is_audiostream) {
+ g_string_append_printf (desc,
+ "interaudiosrc name=source channel=input_%d ! audioparse raw-format=s16le rate=48000 ! interaudiosink name=sink channel=branch_%d",
+ cas->sink_port, cas->sink_port);
} else {
- g_string_append_printf (desc, "intervideosrc");
+ g_string_append_printf (desc,
+ "intervideosrc name=source channel=input_%d ! %s ! intervideosink name=sink channel=branch_%d",
+ cas->sink_port, caps, cas->sink_port);
}
- g_string_append_printf (desc, " name=source channel=%s_%d ",
- srctype, cas->sink_port);
break;
- default:
- ERROR ("unknown case %d", cas->type);
+
+ case GST_CASE_COMPOSITE_AUDIO:
+ g_string_append_printf (desc,
+ "interaudiosrc name=source channel=input_%d ! audioparse raw-format=s16le rate=48000 ! tee name=s "
+ "s. ! queue ! interaudiosink name=sink1 channel=branch_%d "
+ "s. ! queue ! interaudiosink name=sink2 channel=composite_audio",
+ cas->sink_port, cas->sink_port);
break;
- }
- srctype = NULL;
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ {
+ gchar *channel = cas->type == GST_CASE_COMPOSITE_VIDEO_A ? "a" : "b";
+ g_string_append_printf (desc,
+ "intervideosrc name=source channel=input_%d ! %s ! tee name=s "
+ "s. ! queue ! intervideosink name=sink1 channel=branch_%d "
+ "s. ! queue ! intervideosink name=sink2 channel=composite_%s",
+ cas->sink_port, caps, cas->sink_port, channel);
+ break;
+ }
- switch (cas->type) {
- case GST_CASE_COMPOSITE_A:
- if (channel == NULL)
- channel = "a";
- if (scale == NULL)
- scale =
- g_strdup_printf ("videoscale ! video/x-raw,width=%d,height=%d",
- cas->a_width, cas->a_height);
- case GST_CASE_COMPOSITE_B:
- if (channel == NULL)
- channel = "b";
- if (scale == NULL)
- scale =
- g_strdup_printf ("videoscale ! video/x-raw,width=%d,height=%d",
- cas->b_width, cas->b_height);
- caps =
- g_strdup_printf ("video/x-raw,width=%d,height=%d", cas->width,
- cas->height);
- sink = "intervideosink";
- case GST_CASE_COMPOSITE_a:
- if (channel == NULL)
- channel = "audio";
- if (sink == NULL)
- sink = "interaudiosink";
- g_string_append_printf (desc, "source. ");
- /*
- ASSESS ("assess-composite-%s-source-%d", channel, cas->sink_port);
- */
- if (caps)
- g_string_append_printf (desc, "! %s ", caps);
- g_string_append_printf (desc, "! tee name=s ");
- g_string_append_printf (desc, "s. ! queue2 ");
- /*
- ASSESS ("assess-composite-%s-branch-%d", channel, cas->sink_port);
- */
- g_string_append_printf (desc, "! %s name=sink1 channel=branch_%d ",
- sink, cas->sink_port);
- g_string_append_printf (desc, "s. ! queue2 ");
- if (scale) {
- /*
- g_string_append_printf (desc, "! %s", scale);
- */
- }
- ASSESS ("assess-composite-%s-compose-%d", channel, cas->sink_port);
- g_string_append_printf (desc, "! %s name=sink2 channel=composite_%s ",
- sink, channel);
- if (scale)
- g_free (scale), scale = NULL;
- if (caps)
- g_free (caps), caps = NULL;
+ case GST_CASE_BRANCH_AUDIO:
+ g_string_append_printf (desc,
+ "interaudiosrc name=source channel=branch_%d ! audioparse raw-format=s16le rate=48000 ! gdppay ! tcpserversink name=sink port=%d",
+ cas->sink_port, cas->sink_port);
break;
- case GST_CASE_PREVIEW:
- if (srctype == NULL)
- srctype = "branch";
- case GST_CASE_INPUT_a:
- case GST_CASE_INPUT_v:
- if (srctype == NULL)
- srctype = "input";
- if (cas->serve_type == GST_SERVE_AUDIO_STREAM) {
- g_string_append_printf (desc, "interaudiosink");
- } else {
- g_string_append_printf (desc, "intervideosink");
- }
- g_string_append_printf (desc, " name=sink channel=%s_%d ",
- srctype, cas->sink_port);
-
- if (cas->serve_type == GST_SERVE_AUDIO_STREAM) {
- g_string_append_printf (desc, "source. ");
- /*
- if (cas->type == GST_CASE_PREVIEW)
- ASSESS ("assess-audio-preview-%d", cas->sink_port);
- else
- ASSESS ("assess-audio-input-%d", cas->sink_port);
- */
- //g_string_append_printf (desc, "! gdpdepay ");
- g_string_append_printf (desc, "! sink. ");
- } else if (cas->type == GST_CASE_PREVIEW) {
- g_string_append_printf (desc, "source. " "! video/x-raw,width=%d,height=%d ", cas->width, cas->height); //cas->a_width, cas->a_height);
- /*
- ASSESS ("assess-video-preview-%d", cas->sink_port);
- */
- g_string_append_printf (desc, "! sink. ");
- } else {
- g_string_append_printf (desc, "source. ");
- ASSESS ("assess-video-input-%d", cas->sink_port);
- g_string_append_printf (desc, "! gdpdepay ! sink. ");
- }
+
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
+ g_string_append_printf (desc,
+ "intervideosrc name=source channel=branch_%d ! %s ! gdppay ! tcpserversink name=sink port=%d",
+ cas->sink_port, caps, cas->sink_port);
break;
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
- case GST_CASE_BRANCH_a:
- case GST_CASE_BRANCH_p:
- g_string_append_printf (desc, "tcpserversink name=sink port=%d ",
- cas->sink_port);
- g_string_append_printf (desc, "source. ");
- if (cas->serve_type == GST_SERVE_AUDIO_STREAM) {
- /*
- ASSESS ("assess-branch-source-%d", cas->sink_port);
- */
- g_string_append_printf (desc, "! faac ");
- /*
- ASSESS ("assess-branch-audio-encoded-%d", cas->sink_port);
- */
- } else {
- g_string_append_printf (desc, "! video/x-raw,width=%d,height=%d ", cas->width, cas->height); //cas->a_width, cas->a_height);
- /*
- ASSESS ("assess-branch-source-%d", cas->sink_port);
- */
- }
- g_string_append_printf (desc, "! gdppay ");
- /*
- ASSESS ("assess-branch-payed-%d", cas->sink_port);
- */
- g_string_append_printf (desc, "! sink. ");
+
+ case GST_CASE_BRANCH_PREVIEW:
+ g_string_append_printf (desc,
+ "intervideosrc name=source channel=branch_%d ! %s ! gdppay ! tcpserversink name=sink port=%d",
+ cas->sink_port, caps, cas->sink_port);
break;
- case GST_CASE_UNKNOWN:
+
+ default:
ERROR ("unknown case (%d)", cas->type);
break;
}
+ INFO ("pipeline(%p): %s\n", cas, desc->str);
return desc;
}
@@ -493,8 +419,8 @@ gst_case_prepare (GstCase * cas)
GstWorker *worker = GST_WORKER (cas);
GstElement *source = NULL;
switch (cas->type) {
- case GST_CASE_INPUT_a:
- case GST_CASE_INPUT_v:
+ case GST_CASE_INPUT_AUDIO:
+ case GST_CASE_INPUT_VIDEO:
if (!cas->stream) {
ERROR ("no stream for new case");
return FALSE;
@@ -508,10 +434,10 @@ gst_case_prepare (GstCase * cas)
gst_object_unref (source);
break;
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
- case GST_CASE_BRANCH_a:
- case GST_CASE_BRANCH_p:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
+ case GST_CASE_BRANCH_AUDIO:
+ case GST_CASE_BRANCH_PREVIEW:
{
GstElement *sink = gst_worker_get_element_unlocked (worker, "sink");
@@ -589,45 +515,46 @@ gst_case_class_init (GstCaseClass * klass)
g_param_spec_uint ("width", "Width",
"Output width", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
+ gst_composite_default_width (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_HEIGHT,
g_param_spec_uint ("height", "Height",
"Output height", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
+ gst_composite_default_height (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_A_WIDTH,
g_param_spec_uint ("awidth", "A Width",
"Channel A width", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
+ gst_composite_default_width (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_A_HEIGHT,
g_param_spec_uint ("aheight", "A Height",
"Channel A height", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
+ gst_composite_default_height (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_B_WIDTH,
g_param_spec_uint ("bwidth", "B Width",
"Channel B width", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
+ gst_composite_default_width (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_B_HEIGHT,
g_param_spec_uint ("bheight", "B Height",
"Channel B height", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
+ gst_composite_default_height (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
worker_class->prepare = (GstWorkerPrepareFunc) gst_case_prepare;
worker_class->get_pipeline_string = (GstWorkerGetPipelineStringFunc)
gst_case_get_pipeline_string;
+ worker_class->close = (GstWorkerCloseFunc) gst_case_close;
}
diff --git a/tools/gstcase.h b/tools/gstcase.h
index e2c7c91..a1619dc 100644
--- a/tools/gstcase.h
+++ b/tools/gstcase.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_CASE_H__by_Duzy_Chan__
-#define __GST_CASE_H__by_Duzy_Chan__ 1
+#ifndef __GST_CASE_H__
+#define __GST_CASE_H__
+
#include "gstworker.h"
#include
@@ -44,18 +45,18 @@ typedef struct _GstCaseClass GstCaseClass;
*/
typedef enum
{
- GST_CASE_UNKNOWN, /*!< unknown case */
- GST_CASE_COMPOSITE_A, /*!< special case for composite channel A */
- GST_CASE_COMPOSITE_B, /*!< special case for composite channel B */
- GST_CASE_COMPOSITE_a, /*!< special case for composite channel audio */
- GST_CASE_PREVIEW, /*!< special case for previews */
- GST_CASE_INPUT_a, /*!< special case for audio input */
- GST_CASE_INPUT_v, /*!< special case for video input */
- GST_CASE_BRANCH_A,/*!< special case for branching channel A to output */
- GST_CASE_BRANCH_B,/*!< special case for branching channel B to output */
- GST_CASE_BRANCH_a,/*!< special case for branching active audio to output */
- GST_CASE_BRANCH_p,/*!< special case for branching preview to output */
- GST_CASE__LAST_TYPE = GST_CASE_BRANCH_p
+ GST_CASE_UNKNOWN, /*!< unknown case */
+ GST_CASE_COMPOSITE_VIDEO_A, /*!< special case for composite channel A */
+ GST_CASE_COMPOSITE_VIDEO_B, /*!< special case for composite channel B */
+ GST_CASE_COMPOSITE_AUDIO, /*!< special case for composite channel audio */
+ GST_CASE_PREVIEW, /*!< special case for previews */
+ GST_CASE_INPUT_AUDIO, /*!< Audio input from TCP socket */
+ GST_CASE_INPUT_VIDEO, /*!< Video input from TCP socket */
+ GST_CASE_BRANCH_VIDEO_A, /*!< special case for branching channel A to output */
+ GST_CASE_BRANCH_VIDEO_B, /*!< special case for branching channel B to output */
+ GST_CASE_BRANCH_AUDIO, /*!< special case for branching active audio to output */
+ GST_CASE_BRANCH_PREVIEW, /*!< special case for branching preview to output */
+ GST_CASE__LAST_TYPE = GST_CASE_BRANCH_PREVIEW
} GstCaseType;
/**
@@ -116,4 +117,4 @@ typedef struct _GstCaseClass
GType gst_case_get_type (void);
-#endif //__GST_CASE_H__by_Duzy_Chan__
+#endif //__GST_CASE_H__
diff --git a/tools/gstcomposite.c b/tools/gstcomposite.c
index 99791c4..f7be15a 100644
--- a/tools/gstcomposite.c
+++ b/tools/gstcomposite.c
@@ -156,11 +156,11 @@ gst_composite_set_mode (GstComposite * composite, GstCompositeMode mode)
return;
}
- composite->width = GST_SWITCH_COMPOSITE_DEFAULT_WIDTH;
- composite->height = GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT;
+ composite->width = gst_composite_default_width ();
+ composite->height = gst_composite_default_height ();
switch ((composite->mode = mode)) {
- case COMPOSE_MODE_0:
+ case COMPOSE_MODE_NONE:
composite->a_x = 0;
composite->a_y = 0;
composite->a_width = composite->width;
@@ -172,7 +172,7 @@ gst_composite_set_mode (GstComposite * composite, GstCompositeMode mode)
composite->width = composite->a_width;
composite->height = composite->a_height;
break;
- case COMPOSE_MODE_1:
+ case COMPOSE_MODE_PIP:
composite->a_x = 0;
composite->a_y = 0;
composite->a_width = composite->width;
@@ -184,7 +184,7 @@ gst_composite_set_mode (GstComposite * composite, GstCompositeMode mode)
composite->width = composite->a_width;
composite->height = composite->a_height;
break;
- case COMPOSE_MODE_2:
+ case COMPOSE_MODE_DUAL_PREVIEW:
composite->a_x = 0;
composite->a_y = 0;
composite->a_width = (guint) ((double) composite->width * 0.7 + 0.5);
@@ -196,7 +196,7 @@ gst_composite_set_mode (GstComposite * composite, GstCompositeMode mode)
composite->b_height =
composite->height - composite->a_y - composite->a_height;
break;
- case COMPOSE_MODE_3:
+ case COMPOSE_MODE_DUAL_EQUAL:
composite->a_width = (guint) ((double) composite->width * 0.5 + 0.5);
composite->a_height = (guint) ((double) composite->height * 0.5 + 0.5);
composite->a_x = 0;
@@ -236,7 +236,7 @@ gst_composite_ready_for_transition (GstComposite * composite)
/**
* gst_composite_start_transition:
*
- * Start the new transtition request, this will set the %transition flag into
+ * Start the new transition request, this will set the %transition flag into
* TRUE.
*/
static void
@@ -302,7 +302,7 @@ gst_composite_set_property (GstComposite * composite, guint property_id,
case PROP_MODE:
{
guint mode = g_value_get_uint (value);
- if (COMPOSE_MODE_0 <= mode && mode <= COMPOSE_MODE__LAST) {
+ if (COMPOSE_MODE_NONE <= mode && mode <= COMPOSE_MODE__LAST) {
gst_composite_set_mode (composite, (GstCompositeMode) mode);
} else {
WARN ("invalid composite mode %d", mode);
@@ -425,14 +425,14 @@ gst_composite_get_pipeline_string (GstComposite * composite)
g_string_append_printf (desc,
"intervideosrc name=source_a channel=composite_a_scaled ");
- if (composite->mode == COMPOSE_MODE_0) {
+ if (composite->mode == COMPOSE_MODE_NONE) {
g_string_append_printf (desc,
"source_a. ! video/x-raw,width=%d,height=%d ",
composite->a_width, composite->a_height);
/*
ASSESS ("assess-compose-a-source");
*/
- g_string_append_printf (desc, "! queue2 ");
+ g_string_append_printf (desc, "! queue ");
g_string_append_printf (desc, "! identity name=mix ");
} else {
g_string_append_printf (desc,
@@ -452,7 +452,7 @@ gst_composite_get_pipeline_string (GstComposite * composite)
"source_b. ! video/x-raw,width=%d,height=%d ",
composite->b_width, composite->b_height);
ASSESS ("assess-compose-b-source");
- g_string_append_printf (desc, "! queue2 ");
+ g_string_append_printf (desc, "! queue ");
#if 0
if (composite->width != composite->b_width ||
composite->height != composite->b_height) {
@@ -471,7 +471,7 @@ gst_composite_get_pipeline_string (GstComposite * composite)
"source_a. ! video/x-raw,width=%d,height=%d ",
composite->a_width, composite->a_height);
ASSESS ("assess-compose-a-source");
- g_string_append_printf (desc, "! queue2 ");
+ g_string_append_printf (desc, "! queue ");
#if 0
if (composite->width != composite->a_width ||
composite->height != composite->a_height) {
@@ -491,7 +491,7 @@ gst_composite_get_pipeline_string (GstComposite * composite)
ASSESS ("assess-compose-result");
g_string_append_printf (desc, "! tee name=result ");
- g_string_append_printf (desc, "result. ! queue2 ");
+ g_string_append_printf (desc, "result. ! queue ");
/*
ASSESS ("assess-compose-to-output");
*/
@@ -500,7 +500,7 @@ gst_composite_get_pipeline_string (GstComposite * composite)
"intervideosink name=out channel=composite_out ");
if (opts.record_filename) {
- g_string_append_printf (desc, "result. ! queue2 ");
+ g_string_append_printf (desc, "result. ! queue ");
/*
ASSESS ("assess-compose-to-record");
*/
@@ -536,7 +536,7 @@ gst_composite_get_scaler_string (GstWorker * worker, GstComposite * composite)
g_string_append_printf (desc,
"source_a. ! video/x-raw,width=%d,height=%d ",
composite->width, composite->height);
- g_string_append_printf (desc, "! queue2 ");
+ g_string_append_printf (desc, "! queue ");
/*
g_string_append_printf (desc,
"! videoconvert ! facedetect2 ! speakertrack ! videoconvert ");
@@ -545,7 +545,7 @@ gst_composite_get_scaler_string (GstWorker * worker, GstComposite * composite)
"! videoscale ! video/x-raw,width=%d,height=%d ! sink_a. ",
composite->a_width, composite->a_height);
- if (composite->mode == COMPOSE_MODE_0) {
+ if (composite->mode == COMPOSE_MODE_NONE) {
} else {
g_string_append_printf (desc,
"intervideosrc name=source_b channel=composite_b ");
@@ -555,7 +555,7 @@ gst_composite_get_scaler_string (GstWorker * worker, GstComposite * composite)
g_string_append_printf (desc,
"source_b. ! video/x-raw,width=%d,height=%d ",
composite->width, composite->height);
- g_string_append_printf (desc, "! queue2 ");
+ g_string_append_printf (desc, "! queue ");
/*
g_string_append_printf (desc,
"! videoconvert ! facedetect2 ! speakertrack ! videoconvert ");
@@ -597,7 +597,7 @@ gst_composite_prepare (GstComposite * composite)
/**
* gst_composite_start:
*
- * This is invokved when the composite pipeline started.
+ * This is invoked when the composite pipeline started.
*/
static void
gst_composite_start (GstComposite * composite)
@@ -751,7 +751,7 @@ gst_composite_close_adjustment (GstComposite * composite)
/**
* gst_composite_alive:
*
- * Invoked when the compisite pipeline is online.
+ * Invoked when the composite pipeline is online.
*/
static void
gst_composite_alive (GstComposite * composite)
@@ -794,6 +794,89 @@ gst_composite_null (GstComposite * composite)
return composite->deprecated ? GST_WORKER_NR_END : GST_WORKER_NR_REPLAY;
}
+
+/* gst_caps_get_value:
+ * - the missing API
+ * iterate the gst_structure in the given caps to return a specified field vaue
+ */
+static gboolean
+gst_caps_get_value (GstCaps * caps, const gchar * field, GValue * value)
+{
+ guint i;
+ GstStructure *structure;
+ for (i = 0; (structure = gst_caps_get_structure (caps, i)) != NULL; i++) {
+ const GValue *svalue = gst_structure_get_value (structure, field);
+ if (svalue != NULL) {
+ g_value_copy (svalue, value);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gint
+gst_composite_get_caps_int_value (const gchar * fieldname)
+{
+ GstCaps *caps = gst_switch_server_getcaps ();
+ g_assert (caps != NULL && !gst_caps_is_empty (caps));
+ GValue value = G_VALUE_INIT;
+ g_value_init (&value, G_TYPE_INT);
+ gst_caps_get_value (caps, fieldname, &value);
+ return g_value_get_int (&value);
+}
+
+/* cache these so we don't need to do the above every time */
+static gint cached_default_width = -1;
+static gint cached_default_height = -1;
+
+/* gst_composite_defaut_width:
+ *
+ * @return default width based on current video caps
+ */
+gint
+gst_composite_default_width ()
+{
+ if (cached_default_width == -1)
+ cached_default_width = gst_composite_get_caps_int_value ("width");
+ return cached_default_width;
+}
+
+/* gst_composite_defaut_height:
+ *
+ * @return default height based on current video caps
+ */
+gint
+gst_composite_default_height ()
+{
+ if (cached_default_height == -1)
+ cached_default_height = gst_composite_get_caps_int_value ("height");
+ return cached_default_height;
+}
+
+/* gst_composite_min_pip_width:
+ *
+ * @param pip_w desired width of PIP
+ * @return enforce minimum width of PIP
+ */
+gint
+gst_check_composite_min_pip_width (gint pip_w)
+{
+ gint min_width = gst_composite_default_width () / 4;
+ return pip_w < min_width ? min_width : pip_w;
+}
+
+/* gst_composite_min_pip_height:
+ *
+ * @param pip_w desired height of PIP
+ * @return enforce minimum height of PIP
+ */
+gint
+gst_check_composite_min_pip_height (gint pip_h)
+{
+ gint min_height = gst_composite_default_height () / 3;
+ return pip_h < min_height ? min_height : pip_h;
+}
+
/**
* gst_composite_adjust_pip:
* @param composite The GstComposite instance
@@ -801,7 +884,7 @@ gst_composite_null (GstComposite * composite)
* @param y the Y position of the PIP
* @param w the width of the PIP
* @param h the height of the PIP
- * @return PIP has been changed succefully
+ * @return PIP has been changed successfully
*
* Change the PIP position and size.
*/
@@ -1026,7 +1109,7 @@ gst_composite_class_init (GstCompositeClass * klass)
g_object_class_install_property (object_class, PROP_MODE,
g_param_spec_uint ("mode", "Mode",
"Composite Mode",
- COMPOSE_MODE_0,
+ COMPOSE_MODE_NONE,
COMPOSE_MODE__LAST,
DEFAULT_COMPOSE_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
@@ -1060,15 +1143,15 @@ gst_composite_class_init (GstCompositeClass * klass)
g_param_spec_uint ("awidth", "A Width",
"Channel A frame width",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_width
+ (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_A_HEIGHT,
g_param_spec_uint ("aheight", "A Height",
"Channel A frame height",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_height
+ (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_B_X,
g_param_spec_uint ("bx", "B xpos",
@@ -1084,31 +1167,31 @@ gst_composite_class_init (GstCompositeClass * klass)
g_param_spec_uint ("bwidth", "B Width",
"Channel B frame width",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_width
+ (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_B_HEIGHT,
g_param_spec_uint ("bheight", "B Height",
"Channel B frame height",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_height
+ (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_WIDTH,
g_param_spec_uint ("width",
"Composite Width",
"Output frame width", 1,
G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_width
+ (), G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_HEIGHT,
g_param_spec_uint ("height",
"Composite Height",
"Output frame height",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ gst_composite_default_height
+ (), G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
worker_class->alive = (GstWorkerAliveFunc) gst_composite_alive;
worker_class->null = (GstWorkerNullFunc) gst_composite_null;
diff --git a/tools/gstcomposite.h b/tools/gstcomposite.h
index f6b8813..39ca565 100644
--- a/tools/gstcomposite.h
+++ b/tools/gstcomposite.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_COMPOSITE_H__by_Duzy_Chan__
-#define __GST_COMPOSITE_H__by_Duzy_Chan__ 1
+#ifndef __GST_COMPOSITE_H__
+#define __GST_COMPOSITE_H__
+
#include "gstworker.h"
#define GST_TYPE_COMPOSITE (gst_composite_get_type ())
@@ -35,35 +36,50 @@
#define GST_IS_COMPOSITE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GST_TYPE_COMPOSITE))
#define GST_IS_COMPOSITE_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), GST_TYPE_COMPOSITE))
-#if ENABLE_LOW_RESOLUTION
-#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH LOW_RES_W /* 640 */
-#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT LOW_RES_H /* 480 */
-#define GST_SWITCH_COMPOSITE_MIN_PIP_W 13
-#define GST_SWITCH_COMPOSITE_MIN_PIP_H 7
-#else
-#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH 1280
-#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT 720
-#define GST_SWITCH_COMPOSITE_MIN_PIP_W 320
-#define GST_SWITCH_COMPOSITE_MIN_PIP_H 240
-#endif
+//#if ENABLE_LOW_RESOLUTION
+//#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH LOW_RES_W /* 640 */
+//#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT LOW_RES_H /* 480 */
+//#define GST_SWITCH_COMPOSITE_MIN_PIP_W 13
+//#define GST_SWITCH_COMPOSITE_MIN_PIP_H 7
+//#else
+//#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH 1280
+//#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT 720
+//#define GST_SWITCH_COMPOSITE_MIN_PIP_W 320
+//#define GST_SWITCH_COMPOSITE_MIN_PIP_H 240
+//#endif
#define GST_SWITCH_FACEDETECT_FRAME_WIDTH 150
#define GST_SWITCH_FACEDETECT_FRAME_HEIGHT 100
-#define DEFAULT_COMPOSE_MODE COMPOSE_MODE_3
+#define DEFAULT_COMPOSE_MODE COMPOSE_MODE_DUAL_EQUAL
/**
* @enum GstCompositeMode:
*/
typedef enum
{
- COMPOSE_MODE_0, /*!< none */
- COMPOSE_MODE_1, /*!< picture-in-picture */
- COMPOSE_MODE_2, /*!< side-by-side (preview) */
- COMPOSE_MODE_3, /*!< side-by-side (equal) */
- COMPOSE_MODE__LAST = COMPOSE_MODE_3
+ COMPOSE_MODE_NONE, /*!< none */
+ COMPOSE_MODE_PIP, /*!< picture-in-picture */
+ COMPOSE_MODE_DUAL_PREVIEW, /*!< side-by-side (preview) */
+ COMPOSE_MODE_DUAL_EQUAL, /*!< side-by-side (equal) */
+ COMPOSE_MODE__LAST = COMPOSE_MODE_DUAL_EQUAL
} GstCompositeMode;
+inline static const char* gst_composite_mode_to_string(GstCompositeMode mode) {
+ switch(mode) {
+ case COMPOSE_MODE_NONE:
+ return "COMPOSE_MODE_NONE";
+ case COMPOSE_MODE_PIP:
+ return "COMPOSE_MODE_PIP";
+ case COMPOSE_MODE_DUAL_PREVIEW:
+ return "COMPOSE_MODE_DUAL_PREVIEW";
+ case COMPOSE_MODE_DUAL_EQUAL:
+ return "COMPOSE_MODE_DUAL_EQUAL";
+ }
+ //ASSERT(false);
+ return "COMPOSE_INVALID_VALUE";
+}
+
typedef struct _GstComposite GstComposite;
typedef struct _GstCompositeClass GstCompositeClass;
@@ -89,7 +105,7 @@ typedef struct _GstCompositeClass GstCompositeClass;
* @param adjusting the status of adjusting PIP
* @param transition the status of transiting modes
* @param deprecated (deprecated)
- * @param scaler the scaller for A/B videos
+ * @param scaler the scaler for A/B videos
*/
struct _GstComposite
{
@@ -138,5 +154,10 @@ struct _GstCompositeClass
GType gst_composite_get_type (void);
gboolean gst_composite_adjust_pip (GstComposite * composite,
gint x, gint y, gint w, gint h);
+gint gst_composite_default_width ();
+gint gst_composite_default_height ();
+gint gst_check_composite_min_pip_width (gint pip_w);
+gint gst_check_composite_min_pip_height (gint pip_h);
+
-#endif //__GST_COMPOSITE_H__by_Duzy_Chan__
+#endif //__GST_COMPOSITE_H__
diff --git a/tools/gstrecorder.c b/tools/gstrecorder.c
index 7440778..025fe65 100644
--- a/tools/gstrecorder.c
+++ b/tools/gstrecorder.c
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include "gstswitchserver.h"
#include "gstcomposite.h"
#include "gstrecorder.h"
@@ -71,6 +72,9 @@ gst_recorder_init (GstRecorder * rec)
rec->width = 0;
rec->height = 0;
+ // Recording pipeline needs clean shut-down
+ // via EOS to close out each recording
+ GST_WORKER (rec)->send_eos_on_stop = TRUE;
//INFO ("init %p", rec);
}
@@ -170,45 +174,102 @@ gst_recorder_set_property (GstRecorder * rec, guint property_id,
}
}
+/*
+ * @param dir - directory to create
+ * @return nothing
+ * Create a directory and all intermediary directories
+ * if necessary. Note that errors are ignored here, if
+ * the resulting path is in fact unusable having early
+ * warning here is not necessary
+ */
+static void
+gst_recorder_mkdirs (const char *dir)
+{
+ char tmp[256];
+ strncpy (tmp, dir, sizeof (tmp));
+ size_t len = strlen (tmp);
+ if (len > 0) {
+ if (tmp[len - 1] == '/')
+ tmp[len--] = 0;
+ if (len > 0) {
+ size_t at = 1; // skip leading slash
+ while (at < len) {
+ char *p = strchr (tmp + at, '/');
+ if (p != NULL && *p == '/') {
+ *p = '\0';
+ mkdir (tmp, S_IRWXU);
+ *p = '/';
+ at = p - tmp + 1;
+ } else
+ at = len;
+ }
+ mkdir (tmp, S_IRWXU);
+ }
+ }
+}
+
/**
- * @param rec The GstRecorder instance.
- * @memberof GstRecorder
+ * @param filepath - file file/path of a unix file
+ * @return length of the path potion of the file, excluding the separator
+ */
+static size_t
+gst_recorder_pathlen (const char *filepath)
+{
+ if (filepath != NULL && strlen (filepath) > 0) {
+ char const *sep = strrchr (filepath + 1, '/');
+ if (sep != NULL)
+ return sep - filepath;
+ }
+ return 0;
+}
+
+
+/**
+ * @param filename Template name of the file to save
* @return the file name string, need to be freed after used
*
- * This is used to genearte a new recording file name for the recorder.
+ * This is used to generate a new recording file name for the recorder.
*/
static const gchar *
-gst_recorder_new_filename (GstRecorder * rec)
+gst_recorder_new_filename (const gchar * filename)
{
- time_t t;
- struct tm *tm;
- gchar stamp[128];
- const gchar *dot = NULL;
- const gchar *filename = opts.record_filename;
- if (!filename) {
+ if (!filename)
return NULL;
- }
-
- t = time (NULL);
- tm = localtime (&t);
- if (tm == NULL) {
- static gint num = 0;
- num += 1;
- snprintf (stamp, sizeof (stamp), "%d", num);
- } else {
- strftime (stamp, sizeof (stamp), "%F %H%M%S", tm);
+ gchar fnbuf[256];
+ time_t t = time (NULL);
+ struct tm *tm = localtime (&t);
+ // Note: reserve some space for collision suffix
+ strftime (fnbuf, sizeof (fnbuf) - 5, filename, tm);
+ // We now have a fully built name in our buffer
+ // If there is at least one directory present, make sure they exist
+ size_t pathlen = gst_recorder_pathlen (fnbuf);
+ if (pathlen > 0) {
+ fnbuf[pathlen] = '\0';
+ gst_recorder_mkdirs (fnbuf);
+ fnbuf[pathlen] = '/';
}
-
- if ((dot = g_strrstr (filename, "."))) {
- const gchar *s = g_strndup (filename, dot - filename);
- filename = g_strdup_printf ("%s %s%s", s, stamp, dot);
- g_free ((gpointer) s);
- } else {
- filename = g_strdup_printf ("%s %s.dat", filename, stamp);
+ pathlen = strlen (fnbuf); // reuse for length of file/path
+
+ // handle name collisions by adding a suffix/extension
+ size_t suffix = 0;
+ while (1) {
+ struct stat s;
+ if (-1 == stat (fnbuf, &s)) {
+ if (ENOENT == errno)
+ break;
+ else {
+ perror (fnbuf);
+ return NULL; // can't record
+ }
+ }
+ snprintf (fnbuf + pathlen, 256 - pathlen, ".%03d", (int) suffix++);
+ // can't record if we've used up our additions
+ if (suffix > 999)
+ return NULL;
}
- return filename;
+ return g_strdup (fnbuf);
}
/**
@@ -221,80 +282,43 @@ gst_recorder_new_filename (GstRecorder * rec)
static GString *
gst_recorder_get_pipeline_string (GstRecorder * rec)
{
- const gchar *filename = gst_recorder_new_filename (rec);
+ const gchar *filename =
+ gst_recorder_new_filename (gst_switch_server_get_record_filename ());
GString *desc;
//INFO ("Recording to %s and port %d", filename, rec->sink_port);
desc = g_string_new ("");
- g_string_append_printf (desc, "intervideosrc name=source_video "
- "channel=composite_video ");
- g_string_append_printf (desc, "interaudiosrc name=source_audio "
- "channel=composite_audio ");
+ // Encode the video with lossless jpeg
+ g_string_append_printf (desc,
+ "intervideosrc name=source_video channel=composite_video "
+ "! video/x-raw,width=%d,height=%d "
+ "! queue ! jpegenc quality=100 ! mux. \n", rec->width, rec->height);
+
+ // Don't encode the audio
+ g_string_append_printf (desc,
+ "interaudiosrc name=source_audio channel=composite_audio ! queue ! mux. \n");
+ // Output in streamable mkv format
g_string_append_printf (desc,
- "source_video. ! video/x-raw,width=%d,height=%d ",
- rec->width, rec->height);
- /*
- ASSESS ("assess-record-video-source");
- */
- g_string_append_printf (desc, "! queue2 ");
- /*
- ASSESS ("assess-record-video-encode-queued");
- */
- g_string_append_printf (desc, "! vp8enc ");
- /*
- ASSESS ("assess-record-video-encoded");
- */
- g_string_append_printf (desc, "! mux. ");
-
- g_string_append_printf (desc, "source_audio. ");
- /*
- ASSESS ("assess-record-audio-source");
- */
- g_string_append_printf (desc, "! queue2 ");
- /*
- ASSESS ("assess-record-audio-queued");
- */
- g_string_append_printf (desc, "! faac ");
- /*
- ASSESS ("assess-record-audio-encoded");
- */
- g_string_append_printf (desc, "! mux. ");
-
- g_string_append_printf (desc, "avimux name=mux ");
- /*
- ASSESS ("assess-record-mux-result");
- */
+ "matroskamux name=mux streamable=true "
+ " writing-app='gst-switch' min-index-interval=1000000 ");
+
g_string_append_printf (desc, "! tee name=result ");
+ g_string_append_printf (desc, "\n");
if (filename) {
- g_string_append_printf (desc, "filesink name=disk_sink sync=false "
- "location=\"%s\" ", filename);
+ g_string_append_printf (desc, "result. ! queue max-size-buffers=1 "
+ "! filesink name=disk_sink sync=false location=\"%s\" ", filename);
g_free ((gpointer) filename);
- g_string_append_printf (desc, "result. ");
- /*
- ASSESS ("assess-record-file-to-queue");
- */
- g_string_append_printf (desc, "! queue2 ");
- /*
- ASSESS ("assess-record-file-to-sink");
- */
- g_string_append_printf (desc, "! disk_sink. ");
}
- g_string_append_printf (desc, "tcpserversink name=tcp_sink sync=false "
- "port=%d ", rec->sink_port);
- g_string_append_printf (desc, "result. ");
- /*
- ASSESS ("assess-record-tcp-to-queue");
- */
- g_string_append_printf (desc, "! queue2 ");
- /*
- ASSESS ("assess-record-tcp-to-sink");
- */
- g_string_append_printf (desc, "! gdppay ! tcp_sink. ");
+ g_string_append_printf (desc, "result. ! queue max-size-buffers=1 ! gdppay "
+ "! tcpserversink name=tcp_sink sync=false port=%d ", rec->sink_port);
+
+ INFO ("Recording pipeline\n----\n%s\n---", desc->str);
+
return desc;
}
@@ -384,9 +408,9 @@ gst_recorder_class_init (GstRecorderClass * klass)
g_object_class_install_property (object_class, PROP_MODE,
g_param_spec_uint ("mode", "Mode",
"Composite Mode",
- COMPOSE_MODE_0,
+ COMPOSE_MODE_NONE,
COMPOSE_MODE__LAST,
- COMPOSE_MODE_0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ COMPOSE_MODE_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PORT,
g_param_spec_uint ("port", "Port",
@@ -400,7 +424,7 @@ gst_recorder_class_init (GstRecorderClass * klass)
g_param_spec_uint ("width", "Input Width",
"Input video frame width",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_WIDTH,
+ gst_composite_default_width (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_HEIGHT,
@@ -408,7 +432,7 @@ gst_recorder_class_init (GstRecorderClass * klass)
"Input Height",
"Input video frame height",
1, G_MAXINT,
- GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT,
+ gst_composite_default_height (),
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
worker_class->prepare = (GstWorkerPrepareFunc) gst_recorder_prepare;
diff --git a/tools/gstrecorder.h b/tools/gstrecorder.h
index f21a76b..fb95613 100644
--- a/tools/gstrecorder.h
+++ b/tools/gstrecorder.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_RECORDER_H__by_Duzy_Chan__
-#define __GST_RECORDER_H__by_Duzy_Chan__ 1
+#ifndef __GST_RECORDER_H__
+#define __GST_RECORDER_H__
+
#include "gstworker.h"
#include "gstcomposite.h"
#include
@@ -71,4 +72,4 @@ struct _GstRecorderClass
*/
GType gst_recorder_get_type (void);
-#endif //__GST_RECORDER_H__by_Duzy_Chan__
+#endif //__GST_RECORDER_H__
diff --git a/tools/gstswitchcapture.c b/tools/gstswitchcapture.c
index 0f7ac50..1c73bc1 100644
--- a/tools/gstswitchcapture.c
+++ b/tools/gstswitchcapture.c
@@ -35,6 +35,15 @@
#include
#include "../logutils.h"
+/* @FIXME: moved from gstcomposite which now determines these dynamically */
+#if ENABLE_LOW_RESOLUTION
+#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH LOW_RES_W /* 640 */
+#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT LOW_RES_H /* 480 */
+#else
+#define GST_SWITCH_COMPOSITE_DEFAULT_WIDTH 1280
+#define GST_SWITCH_COMPOSITE_DEFAULT_HEIGHT 720
+#endif
+
#define GST_TYPE_SWITCH_CAPTURE_WORKER (gst_switch_capture_worker_get_type ())
#define GST_SWITCH_CAPTURE_WORKER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GST_TYPE_SWITCH_CAPTURE_WORKER, GstSwitchCaptureWorker))
#define GST_SWITCH_CAPTURE_WORKER_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), GST_TYPE_SWITCH_CAPTURE_WORKER, GstSwitchCaptureWorkerClass))
@@ -132,7 +141,7 @@ gst_switch_capture_pipeline (GstWorker * worker, GstSwitchCapture * capture)
g_string_append_printf (desc, "! tee name=video ");
}
- g_string_append_printf (desc, "video. ! queue2 ");
+ g_string_append_printf (desc, "video. ! queue ");
g_string_append_printf (desc,
"! videoscale ! video/x-raw,width=%d,height=%d ",
GST_SWITCH_FACEDETECT_FRAME_WIDTH, GST_SWITCH_FACEDETECT_FRAME_HEIGHT);
@@ -142,9 +151,9 @@ gst_switch_capture_pipeline (GstWorker * worker, GstSwitchCapture * capture)
g_string_append_printf (desc, "! camcontrol name=camctl "
"device=%s protocol=%s ", device, protocol);
g_string_append_printf (desc, "! videoconvert ");
- g_string_append_printf (desc, "! xvimagesink ");
+ g_string_append_printf (desc, "! xvimagesink sync=false ");
- g_string_append_printf (desc, "video. ! queue2 ");
+ g_string_append_printf (desc, "video. ! queue ");
g_string_append_printf (desc, "! videoconvert ");
g_string_append_printf (desc, "! videoscale "
"! video/x-raw,format=I420,width=%d,height=%d ", w, h);
diff --git a/tools/gstswitchcapture.h b/tools/gstswitchcapture.h
index 244fa97..55188f4 100644
--- a/tools/gstswitchcapture.h
+++ b/tools/gstswitchcapture.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_SWITCH_CAPTURE_H__by_Duzy_Chan__
-#define __GST_SWITCH_CAPTURE_H__by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_CAPTURE_H__
+#define __GST_SWITCH_CAPTURE_H__
+
#include "gstworker.h"
#include "gstswitchclient.h"
@@ -65,4 +66,4 @@ typedef struct _GstSwitchCaptureClass
GType gst_switch_capture_get_type (void);
-#endif//__GST_SWITCH_CAPTURE_H__by_Duzy_Chan__
+#endif //__GST_SWITCH_CAPTURE_H__
diff --git a/tools/gstswitchclient.c b/tools/gstswitchclient.c
index 437ac7e..13de9e8 100644
--- a/tools/gstswitchclient.c
+++ b/tools/gstswitchclient.c
@@ -95,6 +95,7 @@ static const gchar introspection_xml[] =
" " "";
extern gboolean verbose;
+gint gst_switch_client_dbus_timeout = 5000;
/**
* @brief Initialize instances of GstSwitchClient.
@@ -141,7 +142,7 @@ gst_switch_client_call_controller (GstSwitchClient * client,
//INFO ("calling: %s/%s", SWITCH_CONTROLLER_OBJECT_NAME, method_name);
value = g_dbus_connection_call_sync (client->controller, NULL, /* bus_name */
- SWITCH_CONTROLLER_OBJECT_PATH, SWITCH_CONTROLLER_OBJECT_NAME, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, 5000, /* timeout_msec */
+ SWITCH_CONTROLLER_OBJECT_PATH, SWITCH_CONTROLLER_OBJECT_NAME, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, gst_switch_client_dbus_timeout, /* timeout_msec */
NULL /* TODO: cancellable */ ,
&error);
@@ -169,7 +170,7 @@ gst_switch_client_call_controller (GstSwitchClient * client,
}
/**
- * @brief The the compose port number.
+ * @brief The compose port number.
* @memberof GstSwitchClient
*/
gint
@@ -213,7 +214,7 @@ gst_switch_client_get_encode_port (GstSwitchClient * client)
* @param client the GstSwitchClient instance
* @return the audio port number
*
- * The the audio port number.
+ * The audio port number.
*
*/
gint
@@ -356,7 +357,8 @@ gst_switch_client_mark_tracking_remotely (GstSwitchClient * client,
*
*/
gboolean
-gst_switch_client_set_composite_mode (GstSwitchClient * client, gint mode)
+gst_switch_client_set_composite_mode (GstSwitchClient * client,
+ GstCompositeMode mode)
{
gboolean result = FALSE;
GVariant *value = NULL;
@@ -370,7 +372,7 @@ gst_switch_client_set_composite_mode (GstSwitchClient * client, gint mode)
if (!client->changing_composite_mode) {
value = gst_switch_client_call_controller (client,
"set_composite_mode",
- g_variant_new ("(i)", mode), G_VARIANT_TYPE ("(b)"));
+ g_variant_new ("(i)", (gint) mode), G_VARIANT_TYPE ("(b)"));
if (value) {
g_variant_get (value, "(b)", &result);
client->changing_composite_mode = result;
@@ -413,8 +415,8 @@ gst_switch_client_new_record (GstSwitchClient * client)
* @param dy y position to be adjusted
* @param dw w position to be adjusted
* @param dh h position to be adjusted
- * @return a unsign integer indicating which components are changed of per
- * bit.
+ * @return a unsigned integer indicating which components are changed of per
+ * bit.
*
* Adjust the PIP.
*
diff --git a/tools/gstswitchclient.h b/tools/gstswitchclient.h
index 96f3dc2..4eecf19 100644
--- a/tools/gstswitchclient.h
+++ b/tools/gstswitchclient.h
@@ -25,11 +25,14 @@
/*! @file */
-#ifndef __GST_SWITCH_CLIENT_H__by_Duzy_Chan__
-#define __GST_SWITCH_CLIENT_H__by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_CLIENT_H__
+#define __GST_SWITCH_CLIENT_H__
+
#include
#include
+#include "gstcomposite.h"
+
#define GST_TYPE_SWITCH_CLIENT (gst_switch_client_get_type ())
#define GST_SWITCH_CLIENT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GST_TYPE_SWITCH_CLIENT, GstSwitchClient))
#define GST_SWITCH_CLIENT_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), GST_TYPE_SWITCH_CLIENT, GstSwitchClientClass))
@@ -117,7 +120,7 @@ GVariant *gst_switch_client_get_preview_ports (GstSwitchClient * client);
gboolean gst_switch_client_switch (GstSwitchClient * client, gint channel,
gint port);
gboolean gst_switch_client_set_composite_mode (GstSwitchClient * client,
- gint mode);
+ GstCompositeMode mode);
gboolean gst_switch_client_click_video (GstSwitchClient * client,
gint x, gint y, gint fw, gint fh);
void gst_switch_client_mark_face_remotely (GstSwitchClient * client,
@@ -128,4 +131,6 @@ gboolean gst_switch_client_new_record (GstSwitchClient * client);
guint gst_switch_client_adjust_pip (GstSwitchClient * client, gint dx,
gint dy, gint dw, gint dh);
-#endif //__GST_SWITCH_CLIENT_H__by_Duzy_Chan__
+extern gint gst_switch_client_dbus_timeout;
+
+#endif //__GST_SWITCH_CLIENT_H__
diff --git a/tools/gstswitchcontroller.c b/tools/gstswitchcontroller.c
index 1e413c5..a3fb86c 100644
--- a/tools/gstswitchcontroller.c
+++ b/tools/gstswitchcontroller.c
@@ -115,6 +115,8 @@ static const gchar introspection_xml[] =
" "
*/
+gint gst_switch_controller_dbus_timeout = 5000;
+
/**
* @brief Helper function for matching remoting method names.
* @memberof GstSwitchController
@@ -298,6 +300,7 @@ gst_switch_controller_emit_ui_signal (GstSwitchController * controller,
signame, parameters, &error);
if (!res) {
+ g_assert (error != NULL);
ERROR ("emit: (%d) %s", num, error->message);
} else {
++num;
@@ -325,16 +328,16 @@ gst_switch_controller_on_connection_closed (GDBusConnection * connection,
if (error) {
WARN ("close: %s", error->message);
- g_error_free (error);
}
- g_object_unref (connection);
GST_SWITCH_CONTROLLER_LOCK_UIS (controller);
controller->uis = g_list_remove (controller->uis, connection);
GST_SWITCH_CONTROLLER_UNLOCK_UIS (controller);
INFO ("closed: %p, %d (%d uis)", connection, vanished,
g_list_length (controller->uis));
+
+ g_object_unref (connection);
}
static GVariant *gst_switch_controller_call_client (GstSwitchController *
@@ -354,20 +357,18 @@ gst_switch_controller_add_new_client (GstSwitchAddNewClientParams * params)
GDBusConnection *connection = params->connection;
GstSwitchController *controller = params->controller;
gint role = CLIENT_ROLE_NONE;
- {
- gint tests[] = { CLIENT_ROLE_UI, CLIENT_ROLE_CAPTURE, };
- GVariant *parameters = NULL, *res = NULL;
- int n = 0;
- for (; n < sizeof (tests); ++n) {
- role = CLIENT_ROLE_NONE;
- parameters = g_variant_new ("()");
- res = gst_switch_controller_call_client (controller,
- connection, tests[n], "role", parameters, G_VARIANT_TYPE ("(i)"));
- if (res) {
- g_variant_get (res, "(i)", &role);
- g_variant_unref (res);
- break;
- }
+ gint tests[] = { CLIENT_ROLE_UI, CLIENT_ROLE_CAPTURE, };
+ GVariant *parameters = NULL, *res = NULL;
+ int n = 0;
+ for (; n < sizeof (tests) / sizeof (gint); ++n) {
+ role = CLIENT_ROLE_NONE;
+ parameters = g_variant_new ("()");
+ res = gst_switch_controller_call_client (controller,
+ connection, tests[n], "role", parameters, G_VARIANT_TYPE ("(i)"));
+ if (res) {
+ g_variant_get (res, "(i)", &role);
+ g_variant_unref (res);
+ break;
}
}
@@ -413,17 +414,18 @@ gst_switch_controller_on_new_connection (GDBusServer * server,
NULL, /* user_data_free_func */
&error);
- if (register_id <= 0 || error != NULL) {
+ if (error != NULL) {
ERROR ("failed to register controller: %s", error->message);
- return TRUE;
+ g_error_free (error);
+ return FALSE;
+ } else if (register_id <= 0) {
+ ERROR ("register_id invalid (<= 0): %d", register_id);
+ return FALSE;
+ } else {
+ INFO ("registered: %d, %s, %s", register_id,
+ SWITCH_CONTROLLER_OBJECT_PATH, introspection_data->interfaces[0]->name);
}
- /*
- INFO ("registered: %d, %s, %s", register_id,
- SWITCH_CONTROLLER_OBJECT_PATH,
- introspection_data->interfaces[0]->name);
- */
-
g_signal_connect (connection, "closed",
G_CALLBACK (gst_switch_controller_on_connection_closed), controller);
@@ -474,12 +476,13 @@ gst_switch_controller_init (GstSwitchController * controller)
auth_observer = g_dbus_auth_observer_new ();
controller->bus_server = g_dbus_server_new_sync (SWITCH_CONTROLLER_ADDRESS, flags, guid, auth_observer, NULL, /* GCancellable */
&error);
+ if (error != NULL) {
+ g_error ("failed to register controller: %s", error->message);
+ }
+ g_assert (controller->bus_server != NULL);
g_free (guid);
- if (controller->bus_server == NULL)
- goto error_new_server;
-
INFO ("Controller is listening at: %s",
g_dbus_server_get_client_address (controller->bus_server));
@@ -496,14 +499,6 @@ gst_switch_controller_init (GstSwitchController * controller)
// TODO: singleton object
return;
-
- /* Errors Handling */
-error_new_server:
- {
- if (error)
- ERROR ("%s", error->message);
- return;
- }
}
/**
@@ -575,25 +570,25 @@ gst_switch_controller_call_client (GstSwitchController * controller,
break;
}
+ if (G_IS_DBUS_CONNECTION (connection) == 0) {
+ return NULL;
+ }
+
value = g_dbus_connection_call_sync (connection, NULL, /* bus_name */
- path, name, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, 5000, /* timeout_msec */
+ path, name, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, gst_switch_controller_dbus_timeout, /* timeout_msec */
NULL /* TODO: cancellable */ ,
&error);
- if (error != NULL)
- goto error_call_sync;
-
- return value;
-
- /* ERRORS */
-error_call_sync:
- {
+ if (error != NULL) {
ERROR ("%s (%s)", error->message, method_name);
g_error_free (error);
if (value)
g_variant_unref (value);
return NULL;
}
+ g_assert (value != NULL);
+
+ return value;
}
/**
diff --git a/tools/gstswitchcontroller.h b/tools/gstswitchcontroller.h
index b01b70e..87c3504 100644
--- a/tools/gstswitchcontroller.h
+++ b/tools/gstswitchcontroller.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_SWITCH_CONTROLLER_H__by_Duzy_Chan__
-#define __GST_SWITCH_CONTROLLER_H__by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_CONTROLLER_H__
+#define __GST_SWITCH_CONTROLLER_H__
+
#include
#include "../logutils.h"
#include "gstworker.h"
@@ -105,4 +106,6 @@ void gst_switch_controller_show_face_marker (GstSwitchController *controller,
void gst_switch_controller_show_track_marker (GstSwitchController *controller,
GVariant *faces);
-#endif //__GST_SWITCH_CONTROLLER_H__by_Duzy_Chan__
+extern gint gst_switch_controller_dbus_timeout;
+
+#endif //__GST_SWITCH_CONTROLLER_H__
diff --git a/tools/gstswitchopts.c b/tools/gstswitchopts.c
new file mode 100644
index 0000000..a9b0d6d
--- /dev/null
+++ b/tools/gstswitchopts.c
@@ -0,0 +1,247 @@
+#include
+
+#include
+#include
+
+#include
+#include
+
+
+typedef struct
+{
+ const const gchar **shortcuts;
+ const gchar *format;
+} FormatAlias;
+
+#define ADD_FORMAT_ALIASES(format, ...) \
+ { (const gchar*[]){ __VA_ARGS__ , NULL }, format }
+
+static FormatAlias format_aliases[] = {
+ ADD_FORMAT_ALIASES ("300x200@25", "debug"),
+
+ // Computer resolutions. These can be at pretty much any frame rate (common
+ // ones being 60Hz, 75Hz and 80Hz) so the frame rate must still be specified.
+ // For example VGA@75.
+ ADD_FORMAT_ALIASES ("640x480", "VGA"),
+ ADD_FORMAT_ALIASES ("800x600", "SVGA"),
+ ADD_FORMAT_ALIASES ("1024x768", "XGA"),
+
+ // Older Analog TV formats from DV. These formats have only single frame
+ // rate but is interlaced. After deinterlacing, the frame rates are halved.
+ // Both PAL and NSTC also have non-square pixels, which need to be converted
+ // to square pixels.
+ // See http://help.adobe.com/en_US/PremierePro/4.0/WS03BF7479-8C7B-4522-8C75-210AD102524Ea.html#WS082E922A-A5D7-4de1-9A0E-1EFC401EA6D1
+
+ // DV 4:3 PAL, 50 *fields* per second, 720x576 frame size, pixel ratio 59/54 (1.09).
+ // Therefore 788x576@25
+ ADD_FORMAT_ALIASES ("788x576@25", "pal", "pal-4:3", "pal-dv"),
+ // DV 16:9 PAL, 50 *fields* per second, 720x576 frame size, pixel ratio 118/81 (1.46).
+ // Therefore 1050x576@25
+ ADD_FORMAT_ALIASES ("1050x576@25", "pal-16:9", "pal-dvd"),
+
+ // Technically NTSC has a frame rate of 60/1.001 and *either* 486 or 480
+ // horizontal lines, but the dvdec plugin in gstreamer only supports 60Hz and
+ // 480 horizontal lines.
+
+ // DV 4:3 NTSC, 60 *fields* per second, 720x480 frame size, pixel ratio 10/11 (0.91).
+ // Therefore 720x534@30
+ ADD_FORMAT_ALIASES ("720x534@25", "ntsc", "ntsc-4:3", "ntsc-dv"),
+ // DV 16:9 NTSC, 60 *fields* per second, 720x480 frame size, pixel ratio 40/33 (1.21).
+ // Therefore 864x480@30
+ ADD_FORMAT_ALIASES ("864x480@25", "ntsc", "ntsc-16:9", "ntsc-dvd"),
+
+ // None of the HD formats also don't have default frame rates, so the rate
+ // must be specified.
+
+ // In the following resolutions the 'p' is used in place of the '@', IE
+ // 720p60.
+ // Digital TV resolutions.
+ ADD_FORMAT_ALIASES ("1280x720@", "720p"),
+ ADD_FORMAT_ALIASES ("1920x1080@", "1080p"),
+ // Super high resolutions.
+ ADD_FORMAT_ALIASES ("4096x2160@", "2160p"), // DCI 4k
+ ADD_FORMAT_ALIASES ("7680x4320@", "4320p"),
+
+ // Other super high resolution aliases.
+ ADD_FORMAT_ALIASES ("2048x1080", "2k"),
+ ADD_FORMAT_ALIASES ("4096x2160", "4k"),
+ ADD_FORMAT_ALIASES ("7680x4320", "8k"),
+
+ // Terminator
+ ADD_FORMAT_ALIASES (NULL, NULL),
+};
+
+// Requirements for video;
+static const gchar *requirements = "video/x-raw,"
+ // Required by intervideo(sink|src).
+ "format=(string)I420,"
+ // Square pixels, required for sanity.
+ "pixel-aspect-ratio=(fraction)1/1,"
+ // Smallest resolution is 300x200 (required for PIP to work).
+ // Largest is 8k (arbitrarily chosen).
+ "width=(int)[300,7680]," "height=(int)[200,4320],"
+ // Pretty much any frame rate is supported, restricting to 1000fps to catch
+ // user error.
+ "framerate=(fraction)[0/1,1000/1]";
+
+#define TMP_BUF_SIZE 255
+
+// Parse a format string into it's bits.
+// [Resolution X]x[Resolution Y]@[Frame Rate]
+//
+// Both x and y resolution must be whole integers, frame rate can be floating
+// value.
+static gboolean
+parse_short_format (const gchar * format, GstCaps * caps)
+{
+ g_assert (gst_caps_is_writable (caps));
+
+ gint format_width = 0;
+ gint format_height = 0;
+ gdouble format_rate = 0;
+ gint format_rate_num = 0;
+ gint format_rate_den = 1;
+ int r;
+
+ gsize format_len = strlen (format);
+ gchar format_buf[TMP_BUF_SIZE];
+ memset (format_buf, '\0', TMP_BUF_SIZE);
+
+ // Convert an alias into a full format string;
+ // VGA@60 -> 640x480@60
+ // PAL -> 720x576@25
+ // 720p25 -> 1280x720@25
+ gsize i;
+ for (i = 0; format_aliases[i].format != NULL; i++) {
+ const gchar *alias_format = format_aliases[i].format;
+ gsize alias_format_len = strlen (alias_format);
+
+ const gchar **shortcuts = format_aliases[i].shortcuts;
+ gsize j;
+ for (j = 0; shortcuts[j] != NULL; j++) {
+ const gchar *shortcut = shortcuts[j];
+ gsize shortcut_len = strlen (shortcut);
+
+ if (g_ascii_strncasecmp (shortcuts[j], format, shortcut_len) == 0) {
+ //printf("Found alias %p '%s' ('%s') in format '%s'\n", shortcut, shortcut, alias_format, format);
+ strncpy (format_buf, alias_format, alias_format_len);
+ strncpy (format_buf + alias_format_len, format + shortcut_len,
+ format_len - shortcut_len);
+ goto parse_short_format_found_alias;
+ }
+ }
+ }
+
+ // No alias found, just use the given value
+//printf("No alias found!\n");
+ strncpy (format_buf, format, format_len);
+
+parse_short_format_found_alias:
+//printf("Current format string: '%s'\n", format_buf);
+ r = sscanf (format_buf, "%dx%d@%lf/%d", &format_width, &format_height,
+ &format_rate, &format_rate_den);
+
+ switch (r) {
+ case 4: // all 4 args consumed
+ format_rate_num = gst_gdouble_to_guint64 (format_rate);
+ // Check the double was really an int
+ if (gst_guint64_to_gdouble (format_rate_num) != format_rate)
+ return FALSE;
+ break;
+ case 3: // denominator only
+ gst_util_double_to_fraction (format_rate, &format_rate_num,
+ &format_rate_den);
+ // @TODO: this will ignore additional @rate specifiers which
+ // may occur e.g. pal@75, should raise an error
+ break;
+ default: // Wasn't able to parse the format format.
+ return FALSE;
+ }
+
+ gst_caps_set_simple (caps,
+ "framerate", GST_TYPE_FRACTION, format_rate_num, format_rate_den,
+ "width", G_TYPE_INT, format_width,
+ "height", G_TYPE_INT, format_height, NULL);
+
+ return TRUE;
+}
+
+// Smallest resolution is 300x200 (required for PIP to work) and largest is
+// 8k (arbitrarily chosen).
+int
+parse_format (const gchar * format, GstCaps ** caps, GError ** error)
+{
+ const gchar *error_msg = "Invalid video format specified";
+ GstCaps *require_caps = gst_caps_from_string (requirements);
+ GstCaps *incoming_caps = NULL;
+ GstCaps *final_caps = NULL;
+
+ gsize format_end = strlen (format);
+ if (format_end >= TMP_BUF_SIZE) {
+ // Invalid
+ goto parse_format_error;
+ }
+
+ incoming_caps = gst_caps_new_simple ("video/x-raw",
+ "format", G_TYPE_STRING, "I420",
+ "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL);
+ g_assert (incoming_caps);
+
+ // Is the format already in gstreamer caps format, or in the simple
+ // abbreviation format.
+ if (strstr (format, "video/x-raw") == NULL) {
+ if (!parse_short_format (format, incoming_caps)) {
+ goto parse_format_error;
+ }
+ } else {
+ GstCaps *parsed_caps = gst_caps_from_string (format);
+ if (parsed_caps == NULL) {
+// printf("parse failed!?\n");
+ goto parse_format_error;
+ }
+// printf(" parsed-caps: %s\n", gst_caps_to_string(parsed_caps));
+
+ // Check the incoming caps are fully specified (IE only one format, don't
+ // have any ranges, etc).
+ if (!gst_caps_is_fixed (parsed_caps)) {
+// printf("Caps were not fixed!\n");
+ gst_caps_unref (parsed_caps);
+ goto parse_format_error;
+ }
+
+ GstCaps *merged_caps = gst_caps_intersect (incoming_caps, parsed_caps);
+// printf(" merged-caps: %s\n", gst_caps_to_string(merged_caps));
+ incoming_caps = merged_caps;
+// GstCaps *merged_caps = gst_caps_intersect(incoming_caps, parsed_caps);
+ gst_caps_unref (parsed_caps);
+ }
+
+//GST_LOG ("caps are %" GST_PTR_FORMAT, caps);
+//printf(" caps-to-test: %s\n", gst_caps_to_string(incoming_caps));
+//printf("required-caps: %s\n", gst_caps_to_string(require_caps));
+
+//parse_format_ok:
+ final_caps = gst_caps_intersect (require_caps, incoming_caps);
+//printf(" final-caps: %s\n", gst_caps_to_string(final_caps));
+ int r = 0;
+ if (gst_caps_is_empty (final_caps) || !gst_caps_is_fixed (final_caps)) {
+
+ parse_format_error:
+ r = -1;
+ if (error != NULL) {
+ GError *err = g_error_new (G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "%s\n", error_msg);
+ g_propagate_error (error, err);
+ }
+ }
+
+ if (require_caps != NULL)
+ gst_caps_unref (require_caps);
+ if (incoming_caps != NULL)
+ gst_caps_unref (incoming_caps);
+
+ if (caps != NULL)
+ *caps = final_caps;
+
+ return r;
+}
diff --git a/tools/gstswitchptz.c b/tools/gstswitchptz.c
index 4d31c81..b6ffabf 100644
--- a/tools/gstswitchptz.c
+++ b/tools/gstswitchptz.c
@@ -899,7 +899,7 @@ gst_switch_ptz_get_pipeline (GstSwitchPTZ * ptz)
g_string_append_printf (desc, "v4l2src device=%s ", ptz_video_name);
//g_string_append_printf (desc, "! video/x-raw,width=320,height=240 ");
g_string_append_printf (desc, "! videoconvert ! videoscale ");
- g_string_append_printf (desc, "! ximagesink name=sink ");
+ g_string_append_printf (desc, "! ximagesink name=sink sync=false ");
return desc;
}
diff --git a/tools/gstswitchptz.h b/tools/gstswitchptz.h
index 87755b9..8d9d7af 100644
--- a/tools/gstswitchptz.h
+++ b/tools/gstswitchptz.h
@@ -23,8 +23,9 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __GST_SWITCH_PTZ_H____by_Duzy_Chan__
-#define __GST_SWITCH_PTZ_H____by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_PTZ_H__
+#define __GST_SWITCH_PTZ_H__
+
#include
#include
#include "gstworker.h"
@@ -58,4 +59,4 @@ typedef struct _GstSwitchPTZClass
GType gst_switch_ptz_get_type (void);
-#endif//__GST_SWITCH_PTZ_H____by_Duzy_Chan__
+#endif //__GST_SWITCH_PTZ_H__
diff --git a/tools/gstswitchserver.c b/tools/gstswitchserver.c
index 009391e..858b3df 100644
--- a/tools/gstswitchserver.c
+++ b/tools/gstswitchserver.c
@@ -38,12 +38,23 @@
#include "./gio/gsocketinputstream.h"
#include "../logutils.h"
-#define GST_SWITCH_SERVER_DEFAULT_HOST "localhost"
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define GST_SWITCH_SERVER_DEFAULT_HOST "0.0.0.0"
#define GST_SWITCH_SERVER_DEFAULT_VIDEO_ACCEPTOR_PORT 3000
#define GST_SWITCH_SERVER_DEFAULT_AUDIO_ACCEPTOR_PORT 4000
#define GST_SWITCH_SERVER_DEFAULT_CONTROLLER_PORT 5000
#define GST_SWITCH_SERVER_LISTEN_BACKLOG 8 /* client connection queue */
+#define GST_SWITCH_SERVER_HOST_SPEC "%q"
+#define GST_SWITCH_SERVER_DEFAULT_RECORD_FILE "recording-%q-%Y%m%d-%H%M%S"
+#define GST_SWITCH_SERVER_DEFAULT_RECORD_EXT ".mkv"
+
#define GST_SWITCH_SERVER_LOCK_MAIN_LOOP(srv) (g_mutex_lock (&(srv)->main_loop_lock))
#define GST_SWITCH_SERVER_UNLOCK_MAIN_LOOP(srv) (g_mutex_unlock (&(srv)->main_loop_lock))
#define GST_SWITCH_SERVER_LOCK_VIDEO_ACCEPTOR(srv) (g_mutex_lock (&(srv)->video_acceptor_lock))
@@ -71,18 +82,148 @@ GstSwitchServerOpts opts = {
GST_SWITCH_SERVER_DEFAULT_VIDEO_ACCEPTOR_PORT,
GST_SWITCH_SERVER_DEFAULT_AUDIO_ACCEPTOR_PORT,
GST_SWITCH_SERVER_DEFAULT_CONTROLLER_PORT,
+//FALSE,
+ FALSE,
+ NULL, NULL
};
gboolean verbose = FALSE;
+static gboolean
+gparse_record_filename (gchar * name, gchar * value, gpointer data,
+ GError ** error)
+{
+ size_t maxpathlen = 256;
+ gchar fnbuf[maxpathlen + 1];
+ size_t fnlen = 0;
+
+ if (value == NULL || (fnlen = strlen (value)) == 0) {
+ // file not specified, use the default filename.ext
+ strncpy (fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_FILE, sizeof (fnbuf) - 5);
+ strcat (fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_EXT);
+ } else if (fnlen < maxpathlen) {
+ strncpy (fnbuf, value, sizeof (fnbuf));
+ if ((fnlen < 5 || strchr (value + fnlen - 5, '.') == NULL)
+ && fnlen + 4 < maxpathlen)
+ strcat (fnbuf, GST_SWITCH_SERVER_DEFAULT_RECORD_EXT);
+ } else {
+ GError *err = g_error_new (G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "%s path/filename too long: %s\n", name, value);
+ g_propagate_error (error, err);
+ return FALSE;
+ }
+
+ // Do manual substitution of %q => hostname up front since this is static
+ // Time and date tokens are substituted within the recorder as the recording
+ // is started or cut to accurately reflect the date/time of recording
+ char *h = strstr (fnbuf, GST_SWITCH_SERVER_HOST_SPEC);
+ if (h != NULL) {
+ int mnlen = maxpathlen - fnlen; // limit size to what is available
+ char hostname[mnlen + 1];
+ if (gethostname (hostname, mnlen) != 0)
+ strcpy (hostname, GST_SWITCH_SERVER_DEFAULT_HOST);
+ else
+ hostname[mnlen] = '\0';
+ int hnlen = strlen (hostname);
+ hostname[hnlen] = '\0'; // guarantee nul termination
+ fnlen = strlen (fnbuf);
+ do {
+ size_t over = fnlen - (h - fnbuf) + 1;
+ memmove (h + hnlen, h + 2, over);
+ memcpy (h, hostname, hnlen);
+ fnlen += (hnlen - 2); // adjust the result length
+ } while ((h = strstr (fnbuf, GST_SWITCH_SERVER_HOST_SPEC)) != NULL);
+ }
+
+ opts.record_filename = g_strdup (fnbuf);
+ return TRUE;
+}
+
+
+extern int
+parse_format (const gchar * format, GstCaps ** final_caps, GError ** err);
+
+static gboolean
+gparse_video_format (gchar * name, gchar * value, gpointer data,
+ GError ** error)
+{
+ if (parse_format (value, &opts.video_caps, error) == -1)
+ return FALSE;
+ return TRUE;
+}
+
+static gboolean caps_dumped = FALSE;
+
+/* gst_switch_server_getcaps:
+ * @return current video caps
+ */
+GstCaps *
+gst_switch_server_getcaps (void)
+{
+ static const gchar *audio_caps_str =
+ "audio/x-raw, rate=48000, channels=2, format=S16LE, layout=interleaved";
+
+ if (opts.video_caps == NULL) {
+ // use a sane default we know will parse correctly
+ parse_format (opts.low_res ? "VGA@60" : "720p60", &opts.video_caps, NULL);
+ }
+ if (!caps_dumped) {
+ opts.video_caps_str = gst_caps_to_string (opts.video_caps);
+ g_print ("video-caps: %s\n", opts.video_caps_str);
+
+ opts.audio_caps_str = g_strdup (audio_caps_str);
+ g_print ("audio-caps: %s\n", opts.audio_caps_str);
+
+ caps_dumped = TRUE;
+ }
+ return opts.video_caps;
+}
+
+/* gst_switch_server_get_video_caps_str
+ * return video caps as a string
+ */
+const gchar *
+gst_switch_server_get_video_caps_str (void)
+{
+ (void) gst_switch_server_getcaps ();
+ return opts.video_caps_str;
+}
+
+/* gst_switch_server_get_audio_caps_str
+ * return audio caps as a string
+ */
+const gchar *
+gst_switch_server_get_audio_caps_str (void)
+{
+ (void) gst_switch_server_getcaps ();
+ return opts.audio_caps_str;
+}
+
+/* gst_switch_server_get_record_filename:
+ * @return record file template name, if one was set
+ */
+const gchar *
+gst_switch_server_get_record_filename (void)
+{
+ return opts.record_filename;
+}
+
static GOptionEntry entries[] = {
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
"Prompt more messages", NULL},
{"test-switch", 't', 0, G_OPTION_ARG_STRING, &opts.test_switch,
"Perform switch test", "OUTPUT"},
- {"record", 'r', 0, G_OPTION_ARG_STRING, &opts.record_filename,
- "Enable recorder and record into the specified FILENAME",
- "FILENAME"},
+ {"dbus-timeout", 'd', 0, G_OPTION_ARG_INT,
+ &gst_switch_controller_dbus_timeout,
+ "DBus timeout in msec (default 5000)"},
+ {"record", 'r', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ (gpointer) gparse_record_filename,
+ "Enable recorder and record into the specified FILENAME"},
+ {"low-resolution", 'l', 0, G_OPTION_ARG_NONE, &opts.low_res,
+ "Enable low resolution mode (-f overrides)"},
+ {"video-format", 'f', 0, G_OPTION_ARG_CALLBACK,
+ (gpointer) gparse_video_format,
+ "Specify the video format to use (shortcuts supported)"},
{"video-input-port", 'p', 0, G_OPTION_ARG_INT, &opts.video_input_port,
"Specify the video input listen port.", "NUM"},
{"audio-input-port", 'a', 0, G_OPTION_ARG_INT, &opts.audio_input_port,
@@ -95,20 +236,24 @@ static GOptionEntry entries[] = {
/**
* gst_switch_server_parse_args:
*
- * Parsing commiand line parameters.
+ * Parsing command line parameters.
*/
static void
-gst_switch_server_parse_args (int *argc, char **argv[])
+gst_switch_server_parse_args (int argc, char *argv[])
{
GError *error = NULL;
GOptionContext *context;
+ gst_init (NULL, NULL);
context = g_option_context_new ("");
g_option_context_add_main_entries (context, entries, "gst-switch");
g_option_context_add_group (context, gst_init_get_option_group ());
- if (!g_option_context_parse (context, argc, argv, &error)) {
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
ERROR ("option parsing failed: %s", error->message);
exit (1);
+ } else if (argc > 1) {
+ ERROR ("unknown option: %s", argv[1]);
+ exit (1);
}
g_option_context_free (context);
@@ -321,8 +466,8 @@ gst_switch_server_end_case (GstCase * cas, GstSwitchServer * srv)
caseport = cas->sink_port;
g_object_unref (cas);
break;
- case GST_CASE_INPUT_a:
- case GST_CASE_INPUT_v:
+ case GST_CASE_INPUT_AUDIO:
+ case GST_CASE_INPUT_VIDEO:
srv->cases = g_list_remove (srv->cases, cas);
INFO ("Removed %s %p (%d cases left)", GST_WORKER (cas)->name, cas,
g_list_length (srv->cases));
@@ -360,10 +505,10 @@ gst_switch_server_start_case (GstCase * cas, GstSwitchServer * srv)
{
gboolean is_branch = FALSE;
switch (cas->type) {
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
- case GST_CASE_BRANCH_a:
- case GST_CASE_BRANCH_p:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
+ case GST_CASE_BRANCH_AUDIO:
+ case GST_CASE_BRANCH_PREVIEW:
is_branch = TRUE;
default:
break;
@@ -375,7 +520,7 @@ gst_switch_server_start_case (GstCase * cas, GstSwitchServer * srv)
gst_switch_controller_tell_preview_port (srv->controller,
cas->sink_port, cas->serve_type, cas->type);
- if (cas->type == GST_CASE_BRANCH_a) {
+ if (cas->type == GST_CASE_BRANCH_AUDIO) {
gst_switch_controller_tell_audio_port (srv->controller, cas->sink_port);
}
}
@@ -393,9 +538,9 @@ gst_switch_server_suggest_case_type (GstSwitchServer * srv,
GstSwitchServeStreamType serve_type)
{
GstCaseType type = GST_CASE_UNKNOWN;
- gboolean has_composite_A = FALSE;
- gboolean has_composite_B = FALSE;
- gboolean has_composite_a = FALSE;
+ gboolean has_composite_video_A = FALSE;
+ gboolean has_composite_video_B = FALSE;
+ gboolean has_composite_audio = FALSE;
GList *item = srv->cases;
for (; item; item = g_list_next (item)) {
@@ -405,11 +550,11 @@ gst_switch_server_suggest_case_type (GstSwitchServer * srv,
case GST_SERVE_VIDEO_STREAM:
{
switch (cas->type) {
- case GST_CASE_COMPOSITE_A:
- has_composite_A = TRUE;
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ has_composite_video_A = TRUE;
break;
- case GST_CASE_COMPOSITE_B:
- has_composite_B = TRUE;
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ has_composite_video_B = TRUE;
break;
default:
break;
@@ -418,8 +563,8 @@ gst_switch_server_suggest_case_type (GstSwitchServer * srv,
break;
case GST_SERVE_AUDIO_STREAM:
{
- if (cas->type == GST_CASE_COMPOSITE_a)
- has_composite_a = TRUE;
+ if (cas->type == GST_CASE_COMPOSITE_AUDIO)
+ has_composite_audio = TRUE;
}
break;
case GST_SERVE_NOTHING:
@@ -427,14 +572,14 @@ gst_switch_server_suggest_case_type (GstSwitchServer * srv,
}
#else
switch (cas->type) {
- case GST_CASE_COMPOSITE_A:
- has_composite_A = TRUE;
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ has_composite_video_A = TRUE;
break;
- case GST_CASE_COMPOSITE_B:
- has_composite_B = TRUE;
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ has_composite_video_B = TRUE;
break;
- case GST_CASE_COMPOSITE_a:
- has_composite_a = TRUE;
+ case GST_CASE_COMPOSITE_AUDIO:
+ has_composite_audio = TRUE;
break;
default:
break;
@@ -445,16 +590,16 @@ gst_switch_server_suggest_case_type (GstSwitchServer * srv,
switch (serve_type) {
case GST_SERVE_VIDEO_STREAM:
- if (!has_composite_A)
- type = GST_CASE_COMPOSITE_A;
- else if (!has_composite_B)
- type = GST_CASE_COMPOSITE_B;
+ if (!has_composite_video_A)
+ type = GST_CASE_COMPOSITE_VIDEO_A;
+ else if (!has_composite_video_B)
+ type = GST_CASE_COMPOSITE_VIDEO_B;
else
type = GST_CASE_PREVIEW;
break;
case GST_SERVE_AUDIO_STREAM:
- if (!has_composite_a)
- type = GST_CASE_COMPOSITE_a;
+ if (!has_composite_audio)
+ type = GST_CASE_COMPOSITE_AUDIO;
else
type = GST_CASE_PREVIEW;
break;
@@ -494,10 +639,10 @@ gst_switch_server_serve (GstSwitchServer * srv, GSocket * client,
GST_SWITCH_SERVER_LOCK_CASES (srv);
switch (serve_type) {
case GST_SERVE_AUDIO_STREAM:
- inputtype = GST_CASE_INPUT_a;
+ inputtype = GST_CASE_INPUT_AUDIO;
break;
case GST_SERVE_VIDEO_STREAM:
- inputtype = GST_CASE_INPUT_v;
+ inputtype = GST_CASE_INPUT_VIDEO;
break;
default:
goto error_unknown_serve_type;
@@ -505,17 +650,17 @@ gst_switch_server_serve (GstSwitchServer * srv, GSocket * client,
type = gst_switch_server_suggest_case_type (srv, serve_type);
switch (type) {
- case GST_CASE_COMPOSITE_A:
- branchtype = GST_CASE_BRANCH_A;
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ branchtype = GST_CASE_BRANCH_VIDEO_A;
break;
- case GST_CASE_COMPOSITE_B:
- branchtype = GST_CASE_BRANCH_B;
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ branchtype = GST_CASE_BRANCH_VIDEO_B;
break;
- case GST_CASE_COMPOSITE_a:
- branchtype = GST_CASE_BRANCH_a;
+ case GST_CASE_COMPOSITE_AUDIO:
+ branchtype = GST_CASE_BRANCH_AUDIO;
break;
case GST_CASE_PREVIEW:
- branchtype = GST_CASE_BRANCH_p;
+ branchtype = GST_CASE_BRANCH_PREVIEW;
break;
default:
goto error_unknown_case_type;
@@ -939,7 +1084,7 @@ gst_switch_server_get_audio_sink_port (GstSwitchServer * srv)
GList *item;
GST_SWITCH_SERVER_LOCK_CASES (srv);
for (item = srv->cases; item; item = g_list_next (item)) {
- if (GST_CASE (item->data)->type == GST_CASE_COMPOSITE_a) {
+ if (GST_CASE (item->data)->type == GST_CASE_COMPOSITE_AUDIO) {
port = GST_CASE (item->data)->sink_port;
break;
}
@@ -970,9 +1115,9 @@ gst_switch_server_get_preview_sink_ports (GstSwitchServer * srv,
GST_SWITCH_SERVER_LOCK_CASES (srv);
for (item = srv->cases; item; item = g_list_next (item)) {
switch (GST_CASE (item->data)->type) {
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
- case GST_CASE_BRANCH_a:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
+ case GST_CASE_BRANCH_AUDIO:
case GST_CASE_PREVIEW:
a = g_array_append_val (a, GST_CASE (item->data)->sink_port);
if (s)
@@ -1067,7 +1212,7 @@ gst_switch_server_new_record (GstSwitchServer * srv)
/**
* gst_switch_server_adjust_pip:
- * @return: a unsigned number of indicating which compononent (x,y,w,h) has
+ * @return: a unsigned number of indicating which component (x,y,w,h) has
* been changed
*
* Adjust the PIP position and size.
@@ -1089,10 +1234,8 @@ gst_switch_server_adjust_pip (GstSwitchServer * srv,
srv->pip_x = 0;
if (srv->pip_y < 0)
srv->pip_y = 0;
- if (srv->pip_w < GST_SWITCH_COMPOSITE_MIN_PIP_W)
- srv->pip_w = GST_SWITCH_COMPOSITE_MIN_PIP_W;
- if (srv->pip_h < GST_SWITCH_COMPOSITE_MIN_PIP_H)
- srv->pip_h = GST_SWITCH_COMPOSITE_MIN_PIP_H;
+ srv->pip_w = gst_check_composite_min_pip_width (srv->pip_w);
+ srv->pip_h = gst_check_composite_min_pip_height (srv->pip_h);
result = gst_composite_adjust_pip (srv->composite,
srv->pip_x, srv->pip_y, srv->pip_w, srv->pip_h);
@@ -1139,15 +1282,15 @@ gst_switch_server_switch (GstSwitchServer * srv, gint channel, gint port)
GstCase *cas = GST_CASE (item->data);
switch (channel) {
case 'A':
- if (cas->type == GST_CASE_COMPOSITE_A)
+ if (cas->type == GST_CASE_COMPOSITE_VIDEO_A)
goto get_target_stream;
break;
case 'B':
- if (cas->type == GST_CASE_COMPOSITE_B)
+ if (cas->type == GST_CASE_COMPOSITE_VIDEO_B)
goto get_target_stream;
break;
case 'a':
- if (cas->type == GST_CASE_COMPOSITE_a)
+ if (cas->type == GST_CASE_COMPOSITE_AUDIO)
goto get_target_stream;
break;
default:
@@ -1159,9 +1302,9 @@ gst_switch_server_switch (GstSwitchServer * srv, gint channel, gint port)
}
}
switch (cas->type) {
- case GST_CASE_COMPOSITE_A:
- case GST_CASE_COMPOSITE_B:
- case GST_CASE_COMPOSITE_a:
+ case GST_CASE_COMPOSITE_VIDEO_A:
+ case GST_CASE_COMPOSITE_VIDEO_B:
+ case GST_CASE_COMPOSITE_AUDIO:
case GST_CASE_PREVIEW:
if (cas->sink_port == port) {
candidate_case = cas;
@@ -1395,7 +1538,7 @@ gst_switch_server_worker_start (GstWorker * worker, GstSwitchServer * srv)
/**
* gst_switch_server_worker_null:
*
- * Invoked when a worker is trunning into NULL.
+ * Invoked when a worker is running into NULL.
*/
static void
gst_switch_server_worker_null (GstWorker * worker, GstSwitchServer * srv)
@@ -1468,7 +1611,7 @@ gst_switch_server_end_transition (GstWorker * worker, GstSwitchServer * srv)
/**
* gst_switch_server_output_client_socket_added:
*
- * Invoekd when a client socket is added.
+ * Invoked when a client socket is added.
*/
static void
gst_switch_server_output_client_socket_added (GstElement * element,
@@ -1740,12 +1883,35 @@ gst_switch_server_run (GstSwitchServer * srv)
}
}
+static unsigned long long i = 0;
+
+void
+my_handler (int signum)
+{
+ extern void __gcov_flush ();
+ printf ("received signal\n");
+ printf ("%llu\n", i);
+ __gcov_flush (); /* dump coverage data on receiving SIGUSR1 */
+}
+
+
int
main (int argc, char *argv[])
{
+
+ struct sigaction new_action, old_action;
+
+ /* setup signal hander */
+ new_action.sa_handler = my_handler;
+ sigemptyset (&new_action.sa_mask);
+ new_action.sa_flags = 0;
+ sigaction (SIGUSR1, NULL, &old_action);
+ if (old_action.sa_handler != SIG_IGN)
+ sigaction (SIGUSR1, &new_action, NULL);
+
gint exit_code = 0;
GstSwitchServer *srv;
- gst_switch_server_parse_args (&argc, &argv);
+ gst_switch_server_parse_args (argc, argv);
srv = GST_SWITCH_SERVER (g_object_new (GST_TYPE_SWITCH_SERVER, NULL));
diff --git a/tools/gstswitchserver.h b/tools/gstswitchserver.h
index 613ac9b..69c16cb 100644
--- a/tools/gstswitchserver.h
+++ b/tools/gstswitchserver.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_SWITCH_SERVER_H__by_Duzy_Chan__
-#define __GST_SWITCH_SERVER_H__by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_SERVER_H__
+#define __GST_SWITCH_SERVER_H__
+
#include
#include "gstcomposite.h"
#include "gstswitchcontroller.h"
@@ -61,6 +62,12 @@ struct _GstSwitchServerOpts
gint video_input_port;
gint audio_input_port;
gint control_port;
+//should really be in here
+//gboolean verbose;
+ gboolean low_res;
+ GstCaps *video_caps;
+ gchar *video_caps_str;
+ gchar *audio_caps_str;
};
/**
@@ -73,7 +80,7 @@ struct _GstSwitchServerOpts
* @param cancellable
* @param video_acceptor_lock the lock for the video acceptor
* @param video_acceptor the video acceptor thread
- * @param video_acceptor_socket the vidoe acceptor socekt
+ * @param video_acceptor_socket the video acceptor socket
* @param video_acceptor_port the video acceptor port number
* @param audio_acceptor_lock the lock for the audio acceptor
* @param audio_acceptor the audio acceptor thread
@@ -95,8 +102,8 @@ struct _GstSwitchServerOpts
* @param recorder_lock the lock for the %recorder
* @param recorder the recorder instance
* @param pip_lock the lock for PIP
- * @param pip_x the PIP x position
- * @param pip_y the PIP y position
+ * @param pip_x the PIP X position
+ * @param pip_y the PIP Y position
* @param pip_w the PIP width
* @param pip_h the PIP height
* @param clock_lock the lock for %clock
@@ -153,7 +160,7 @@ struct _GstSwitchServer
/**
* GstSwitchServerClass:
- * @param base_calss the parent class
+ * @param base_class the parent class
*/
struct _GstSwitchServerClass
{
@@ -178,6 +185,11 @@ guint gst_switch_server_adjust_pip (GstSwitchServer * srv, gint dx, gint dy,
gint dw, gint dh);
gboolean gst_switch_server_new_record (GstSwitchServer * srv);
+GstCaps *gst_switch_server_getcaps (void);
+const gchar *gst_switch_server_get_audio_caps_str (void);
+const gchar *gst_switch_server_get_video_caps_str (void);
+const gchar *gst_switch_server_get_record_filename (void);
+
extern GstSwitchServerOpts opts;
-#endif //__GST_SWITCH_SERVER_H__by_Duzy_Chan__
+#endif //__GST_SWITCH_SERVER_H__
diff --git a/tools/gstswitchui.c b/tools/gstswitchui.c
index 91544f0..91a5ccb 100644
--- a/tools/gstswitchui.c
+++ b/tools/gstswitchui.c
@@ -36,14 +36,6 @@
#include "gstaudiovisual.h"
#include "gstcase.h"
-#if 3 <= GTK_MAJOR_VERSION && GTK_MINOR_VERSION <= 4
-#define CUSTOM_FRAME_DRAWING 1
-#define gst_switch_ui_update(w) (gtk_widget_queue_draw (w))
-#else
-#define CUSTOM_FRAME_DRAWING 0
-#define gst_switch_ui_update(w) ((void) FALSE)
-#endif
-
#define GST_SWITCH_UI_LOCK_AUDIO(ui) (g_mutex_lock (&(ui)->audio_lock))
#define GST_SWITCH_UI_UNLOCK_AUDIO(ui) (g_mutex_unlock (&(ui)->audio_lock))
#define GST_SWITCH_UI_LOCK_COMPOSE(ui) (g_mutex_lock (&(ui)->compose_lock))
@@ -61,6 +53,8 @@ gboolean verbose;
static GOptionEntry entries[] = {
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL},
+ {"dbus-timeout", 'd', 0, G_OPTION_ARG_INT, &gst_switch_client_dbus_timeout,
+ "DBus timeout in ms (default 5000)", NULL},
{NULL}
};
@@ -196,63 +190,6 @@ gst_switch_ui_compose_view_press (GtkWidget * widget, GdkEventButton * event,
return FALSE;
}
-#if CUSTOM_FRAME_DRAWING
-static gboolean
-gst_switch_ui_draw_preview_frame (GtkWidget * widget, cairo_t * cr,
- GstSwitchUI * ui)
-{
- GtkStyleContext *style;
- gpointer data = NULL;
- double x = 0.5, y = 0.5, width, height, radius, degrees;
- style = gtk_widget_get_style_context (widget);
- width = (double) gtk_widget_get_allocated_width (widget) - 0.5;
- height = (double) gtk_widget_get_allocated_height (widget) - 0.5;
- degrees = 3.14159265359 / 180.0;
- radius = 10.0;
-
- data = g_object_get_data (G_OBJECT (widget), "audio-visual");
- if (!data)
- data = g_object_get_data (G_OBJECT (widget), "video-display");
-
-#if 0
- gtk_style_context_save (style);
- gtk_style_context_add_class (style, "red");
- gtk_render_frame (style, cr, 0, 0, width, height);
- gtk_style_context_remove_class (style, "red");
- gtk_style_context_restore (style);
-#else
- cairo_new_sub_path (cr);
- cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees,
- 0 * degrees);
- cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees,
- 90 * degrees);
- cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees,
- 180 * degrees);
- cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
- cairo_close_path (cr);
- if (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_SELECTED) {
- cairo_set_source_rgba (cr, 0.25, 0.25, 0.85, 0.85);
- } else if (data && GST_IS_VIDEO_DISP (data)) {
- if (gtk_style_context_has_class (style, "active_video_frame")) {
- cairo_set_source_rgba (cr, 0.85, 0.25, 0.25, 0.85);
- } else
- goto default_color;
- } else if (data && GST_IS_AUDIO_VISUAL (data)) {
- if (gtk_style_context_has_class (style, "active_audio_frame")) {
- cairo_set_source_rgba (cr, 0.85, 0.25, 0.25, 0.85);
- } else
- goto default_color;
- } else {
- default_color:
- cairo_set_source_rgba (cr, 0.25, 0.25, 0.25, 0.25);
- }
- cairo_set_line_width (cr, 10.0);
- cairo_stroke (cr);
-#endif
- return TRUE;
-}
-#endif
-
static gboolean gst_switch_ui_key_event (GtkWidget *, GdkEvent *,
GstSwitchUI *);
static gboolean gst_switch_ui_compose_key_event (GtkWidget *, GdkEvent *,
@@ -347,8 +284,7 @@ gst_switch_ui_init (GstSwitchUI * ui)
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_size_request (scrollwin, 130, -1);
gtk_widget_set_vexpand (scrollwin, TRUE);
- gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrollwin),
- ui->preview_box);
+ gtk_container_add (GTK_CONTAINER (scrollwin), ui->preview_box);
gtk_container_add (GTK_CONTAINER (overlay), ui->compose_view);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), ui->compose_overlay);
@@ -754,7 +690,6 @@ gst_switch_ui_set_audio_port (GstSwitchUI * ui, gint port)
visual = gst_switch_ui_renew_audio_visual (ui, frame, visual);
if (visual->active) {
gtk_style_context_add_class (style, "active_audio_frame");
- gst_switch_ui_update (frame);
ui->audio = visual;
}
}
@@ -785,7 +720,6 @@ gst_switch_ui_mark_active_video (GstSwitchUI * ui, gint port, gint type)
} else if (disp->type == type) {
gtk_style_context_remove_class (style, "active_video_frame");
}
- gst_switch_ui_update (frame);
}
}
}
@@ -844,11 +778,11 @@ gst_switch_ui_preview_click (GtkWidget * w, GdkEvent * event, GstSwitchUI * ui)
switch (ev->button) {
case 1: // left button
- newvideotype = GST_CASE_BRANCH_A;
+ newvideotype = GST_CASE_BRANCH_VIDEO_A;
break;
case 3: // right button
if (disp)
- newvideotype = GST_CASE_BRANCH_B;
+ newvideotype = GST_CASE_BRANCH_VIDEO_B;
break;
}
@@ -889,15 +823,14 @@ gst_switch_ui_preview_click (GtkWidget * w, GdkEvent * event, GstSwitchUI * ui)
if (prevframe && prevdisp) {
GtkStyleContext *style = gtk_widget_get_style_context (prevframe);
switch (t) {
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
gtk_style_context_add_class (style, "active_video_frame");
break;
- case GST_CASE_BRANCH_p:
+ case GST_CASE_BRANCH_PREVIEW:
gtk_style_context_remove_class (style, "active_video_frame");
break;
}
- gst_switch_ui_update (prevframe);
prevdisp->type = t;
}
}
@@ -940,18 +873,14 @@ gst_switch_ui_add_preview_port (GstSwitchUI * ui, gint port, gint serve,
gtk_widget_show_all (frame);
-#if CUSTOM_FRAME_DRAWING
- g_signal_connect (frame, "draw",
- G_CALLBACK (gst_switch_ui_draw_preview_frame), ui);
-#endif
g_signal_connect (preview, "button-press-event",
G_CALLBACK (gst_switch_ui_preview_click), ui);
/*
- GST_CASE_BRANCH_A,
- GST_CASE_BRANCH_B,
- GST_CASE_BRANCH_a,
- GST_CASE_BRANCH_p,
+ GST_CASE_BRANCH_VIDEO_A,
+ GST_CASE_BRANCH_VIDEO_B,
+ GST_CASE_BRANCH_AUDIO,
+ GST_CASE_BRANCH_PREVIEW,
*/
switch (serve) {
@@ -962,14 +891,13 @@ gst_switch_ui_add_preview_port (GstSwitchUI * ui, gint port, gint serve,
g_signal_connect (G_OBJECT (disp), "end-worker",
G_CALLBACK (gst_switch_ui_end_video_disp), ui);
switch (type) {
- case GST_CASE_BRANCH_A:
- case GST_CASE_BRANCH_B:
+ case GST_CASE_BRANCH_VIDEO_A:
+ case GST_CASE_BRANCH_VIDEO_B:
style = gtk_widget_get_style_context (frame);
gtk_style_context_add_class (style, "active_video_frame");
gst_switch_ui_mark_active_video (ui, port, type);
break;
}
- gst_switch_ui_update (frame);
break;
case GST_SERVE_AUDIO_STREAM:
visual = gst_switch_ui_new_audio_visual (ui, preview, port);
@@ -980,7 +908,6 @@ gst_switch_ui_add_preview_port (GstSwitchUI * ui, gint port, gint serve,
style = gtk_widget_get_style_context (frame);
gtk_style_context_add_class (style, "active_audio_frame");
}
- gst_switch_ui_update (frame);
break;
default:
gtk_widget_destroy (preview);
@@ -1091,13 +1018,13 @@ gst_switch_ui_switch_unsafe (GstSwitchUI * ui, gint key)
switch (key) {
case GDK_KEY_A:
case GDK_KEY_a:
- type = GST_CASE_BRANCH_A;
+ type = GST_CASE_BRANCH_VIDEO_A;
ok = gst_switch_client_switch (GST_SWITCH_CLIENT (ui), 'A', port);
INFO ("switch-a: %d, %d", port, ok);
break;
case GDK_KEY_B:
case GDK_KEY_b:
- type = GST_CASE_BRANCH_B;
+ type = GST_CASE_BRANCH_VIDEO_B;
ok = gst_switch_client_switch (GST_SWITCH_CLIENT (ui), 'B', port);
INFO ("switch-b: %d, %d", port, ok);
break;
@@ -1150,15 +1077,17 @@ gst_switch_ui_switch (GstSwitchUI * ui, gint key)
* @memberof GstSwitchUI
*/
static void
-gst_switch_ui_next_compose_mode (GstSwitchUI * ui)
+gst_switch_ui_next_compose (GstSwitchUI * ui, GstCompositeMode mode)
{
- gboolean ok = FALSE;
- if (3 < ui->compose_mode)
- ui->compose_mode = 0;
- ok = gst_switch_client_set_composite_mode (GST_SWITCH_CLIENT (ui),
- ui->compose_mode);
- INFO ("set composite mode: %d (%d)", ui->compose_mode, ok);
- ui->compose_mode += 1;
+ gboolean ok =
+ gst_switch_client_set_composite_mode (GST_SWITCH_CLIENT (ui), mode);
+
+ INFO ("set composite mode: new %s (%d), previous %s",
+ gst_composite_mode_to_string (mode),
+ ok, gst_composite_mode_to_string (ui->compose_mode));
+
+ if (ok)
+ ui->compose_mode = mode;
}
/**
@@ -1167,13 +1096,13 @@ gst_switch_ui_next_compose_mode (GstSwitchUI * ui)
* @memberof GstSwitchUI
*/
static void
-gst_switch_ui_next_compose_off (GstSwitchUI * ui)
+gst_switch_ui_next_compose_mode (GstSwitchUI * ui)
{
- gboolean ok = gst_switch_client_set_composite_mode (GST_SWITCH_CLIENT (ui),
- 0);
- INFO ("set composite off (%d)", ok);
- if (ok && ui->compose_mode == 0)
- ui->compose_mode = 1;
+ GstCompositeMode next_mode = ui->compose_mode + 1;
+ if (next_mode > COMPOSE_MODE__LAST)
+ next_mode = 0;
+
+ gst_switch_ui_next_compose (ui, next_mode);
}
/**
@@ -1278,21 +1207,49 @@ gst_switch_ui_key_event (GtkWidget * w, GdkEvent * event, GstSwitchUI * ui)
case GDK_KEY_b:
gst_switch_ui_switch (ui, ke->keyval);
break;
+ case GDK_KEY_R:
+ case GDK_KEY_r:
+ gst_switch_ui_new_record (ui);
+ break;
+
+ // Keys to change the compose mode
+ // ---------------------------------------------------------------
+ // None
+ case GDK_KEY_Escape:
+ gst_switch_ui_next_compose (ui, COMPOSE_MODE_NONE);
+ break;
+
+ // Picture-in-Picture
+ case GDK_KEY_F1:
+ case GDK_KEY_P:
+ case GDK_KEY_p:
+ gst_switch_ui_next_compose (ui, COMPOSE_MODE_PIP);
+ break;
+
+ // Side-by-side (preview)
+ case GDK_KEY_F2:
+ case GDK_KEY_D:
+ case GDK_KEY_d:
+ gst_switch_ui_next_compose (ui, COMPOSE_MODE_DUAL_PREVIEW);
+ break;
+
+ // Side-by-side (equal)
+ case GDK_KEY_F3:
+ case GDK_KEY_S:
+ case GDK_KEY_s:
+ gst_switch_ui_next_compose (ui, COMPOSE_MODE_DUAL_EQUAL);
+ break;
+
+ // Cycle through the modes
case GDK_KEY_Tab:
{
+ // Cycle through them slowly....
if (200 <= ke->time - ui->tabtime) {
ui->tabtime = ke->time;
gst_switch_ui_next_compose_mode (ui);
}
}
break;
- case GDK_KEY_Escape:
- gst_switch_ui_next_compose_off (ui);
- break;
- case GDK_KEY_R:
- case GDK_KEY_r:
- gst_switch_ui_new_record (ui);
- break;
}
}
break;
diff --git a/tools/gstswitchui.h b/tools/gstswitchui.h
index 6b8b350..d51464d 100644
--- a/tools/gstswitchui.h
+++ b/tools/gstswitchui.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_SWITCH_UI_H__by_Duzy_Chan__
-#define __GST_SWITCH_UI_H__by_Duzy_Chan__ 1
+#ifndef __GST_SWITCH_UI_H__
+#define __GST_SWITCH_UI_H__
+
#include
#include
#include "gstswitchclient.h"
@@ -34,6 +35,9 @@
#include "gstvideodisp.h"
#include "gstaudiovisual.h"
+// Only allowed to use enum and to_string methods from this file.
+#include "gstcomposite.h"
+
#define GST_TYPE_SWITCH_UI (gst_switch_ui_get_type ())
#define GST_SWITCH_UI(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GST_TYPE_SWITCH_UI, GstSwitchUI))
#define GST_SWITCH_UI_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), GST_TYPE_SWITCH_UI, GstSwitchUIClass))
@@ -74,7 +78,7 @@ struct _GstSwitchUI
GstVideoDisp *compose;
guint32 tabtime;
- gint compose_mode;
+ GstCompositeMode compose_mode;
gint timer;
GMutex faces_lock, tracking_lock;
@@ -94,4 +98,4 @@ struct _GstSwitchUIClass
GType gst_switch_ui_get_type (void);
-#endif //__GST_SWITCH_UI_H__by_Duzy_Chan__
+#endif //__GST_SWITCH_UI_H__
diff --git a/tools/gstvideodisp.c b/tools/gstvideodisp.c
index 7dbf0ee..f1bc4b5 100644
--- a/tools/gstvideodisp.c
+++ b/tools/gstvideodisp.c
@@ -151,7 +151,7 @@ gst_video_disp_get_pipeline_string (GstVideoDisp * disp)
g_string_append_printf (desc, "! videoconvert ");
g_string_append_printf (desc, "! cairooverlay name=overlay ");
g_string_append_printf (desc, "! videoconvert ");
- g_string_append_printf (desc, "! xvimagesink name=sink ");
+ g_string_append_printf (desc, "! xvimagesink name=sink sync=false ");
return desc;
}
diff --git a/tools/gstvideodisp.h b/tools/gstvideodisp.h
index 0aa0c3c..d930318 100644
--- a/tools/gstvideodisp.h
+++ b/tools/gstvideodisp.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_VIDEO_DISP_H__by_Duzy_Chan__
-#define __GST_VIDEO_DISP_H__by_Duzy_Chan__ 1
+#ifndef __GST_VIDEO_DISP_H__
+#define __GST_VIDEO_DISP_H__
+
#include "gstworker.h"
#define GST_TYPE_VIDEO_DISP (gst_video_disp_get_type ())
@@ -63,4 +64,4 @@ struct _GstVideoDispClass
GType gst_video_disp_get_type (void);
-#endif //__GST_VIDEO_DISP_H__by_Duzy_Chan__
+#endif //__GST_VIDEO_DISP_H__
diff --git a/tools/gstworker.c b/tools/gstworker.c
index a800c61..3a71a83 100644
--- a/tools/gstworker.c
+++ b/tools/gstworker.c
@@ -32,6 +32,8 @@
#include "gstworker.h"
#include "gstswitchserver.h"
+#include
+
#define GST_WORKER_LOCK_PIPELINE(srv) (g_mutex_lock (&(srv)->pipeline_lock))
#define GST_WORKER_UNLOCK_PIPELINE(srv) (g_mutex_unlock (&(srv)->pipeline_lock))
@@ -84,6 +86,7 @@ gst_worker_init (GstWorker * worker)
worker->watch = 0;
g_mutex_init (&worker->pipeline_lock);
+ g_cond_init (&worker->shutdown_cond);
//INFO ("gst_worker init %p", worker);
}
@@ -145,6 +148,7 @@ gst_worker_finalize (GstWorker * worker)
INFO ("gst_worker finalize %p", worker);
g_mutex_clear (&worker->pipeline_lock);
+ g_cond_clear (&worker->shutdown_cond);
g_free (worker->name);
worker->name = NULL;
@@ -378,29 +382,33 @@ gboolean
gst_worker_stop_force (GstWorker * worker, gboolean force)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
+ gboolean no_eos;
g_return_val_if_fail (GST_IS_WORKER (worker), FALSE);
GST_WORKER_LOCK_PIPELINE (worker);
if (worker->pipeline) {
-#if 1
GstState state;
ret = gst_element_get_state (worker->pipeline, &state, NULL,
GST_CLOCK_TIME_NONE);
+ no_eos = (state == GST_STATE_PLAYING) && !worker->send_eos_on_stop;
- if (state == GST_STATE_PLAYING || force) {
+ if (force || no_eos) {
ret = gst_element_set_state (worker->pipeline, GST_STATE_NULL);
gst_bus_set_flushing (worker->bus, TRUE);
g_timeout_add (5,
(GSourceFunc) gst_worker_state_ready_to_null_proxy, worker);
+ } else if (state == GST_STATE_PLAYING) {
+ /* Send an EOS to cleanly shutdown */
+ gst_element_send_event (worker->pipeline, gst_event_new_eos ());
+
+ /* Go to sleep until the EOS handler calls stop_force (worker, TRUE); */
+ g_cond_wait (&worker->shutdown_cond, &worker->pipeline_lock);
}
-#else
- ret = gst_element_set_state (worker->pipeline, GST_STATE_NULL);
-#endif
}
GST_WORKER_UNLOCK_PIPELINE (worker);
@@ -449,7 +457,8 @@ gst_worker_missing_plugin (GstWorker *worker, GstStructure *structure)
static void
gst_worker_handle_eos (GstWorker * worker)
{
- gst_worker_stop (worker);
+ gst_worker_stop_force (worker, TRUE);
+ GST_WORKER_CLASS (G_OBJECT_GET_CLASS (worker))->close (worker);
}
static void
@@ -480,26 +489,28 @@ gst_worker_handle_error (GstWorker * worker, GError * error, const char *debug)
if (error->domain == GST_STREAM_ERROR) {
ERROR ("%s: (STREAM: %d) %s", worker->name, error->code, error->message);
}
-#if 0
ERROR ("DEBUG INFO:\n%s\n", debug);
-#endif
-#if 0
gst_worker_stop (worker);
-#endif
+ GstWorkerClass *worker_class = GST_WORKER_CLASS (G_OBJECT_GET_CLASS (worker));
+ worker_class->close (worker);
}
static void
gst_worker_handle_warning (GstWorker * worker, GError * error,
const char *debug)
{
- WARN ("%s: %s", worker->name, error->message);
+ // kludge: some gstreamer "warnings" are apparently non-recoverable errors
+ if (strstr (error->message, "error:") != NULL)
+ gst_worker_handle_error (worker, error, debug);
+ else
+ WARN ("%s: %s (%s)", worker->name, error->message, debug);
}
static void
gst_worker_handle_info (GstWorker * worker, GError * error, const char *debug)
{
- INFO ("%s: %s", worker->name, error->message);
+ INFO ("%s: %s (%s)", worker->name, error->message, debug);
}
static void
@@ -603,6 +614,21 @@ gst_worker_pipeline_state_changed (GstWorker * worker,
return TRUE;
}
+static GstBusSyncReply
+gst_worker_message_sync (GstBus * bus, GstMessage * message, GstWorker * worker)
+{
+ switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_EOS:
+ /* When we see EOS, wake up and gst_worker_stop that might be waiting */
+ g_cond_signal (&worker->shutdown_cond);
+ break;
+ default:
+ break;
+ }
+
+ return GST_BUS_PASS;
+}
+
static gboolean
gst_worker_message (GstBus * bus, GstMessage * message, GstWorker * worker)
{
@@ -762,6 +788,9 @@ gst_worker_prepare_unsafe (GstWorker * worker)
if (!worker->watch)
goto error_add_watch;
+ gst_bus_set_sync_handler (worker->bus,
+ (GstBusSyncHandler) (gst_worker_message_sync), worker, NULL);
+
if (workerclass->prepare && !workerclass->prepare (worker))
goto error_prepare;
@@ -867,6 +896,12 @@ gst_worker_reset (GstWorker * worker)
return ok;
}
+static void
+gst_worker_close (GstWorker * worker)
+{
+
+}
+
/**
* @brief Initialize GstWorkerClass.
* @param klass The instance of GstWorkerClass.
@@ -912,4 +947,5 @@ gst_worker_class_init (GstWorkerClass * klass)
klass->create_pipeline = gst_worker_create_pipeline;
klass->null = gst_worker_null;
klass->reset = gst_worker_reset;
+ klass->close = gst_worker_close;
}
diff --git a/tools/gstworker.h b/tools/gstworker.h
index 62cca74..3b70fcb 100644
--- a/tools/gstworker.h
+++ b/tools/gstworker.h
@@ -25,8 +25,9 @@
/*! @file */
-#ifndef __GST_WORKER_H__by_Duzy_Chan__
-#define __GST_WORKER_H__by_Duzy_Chan__ 1
+#ifndef __GST_WORKER_H__
+#define __GST_WORKER_H__
+
#include
#include "../logutils.h"
@@ -90,6 +91,13 @@ typedef GstWorkerNullReturn (*GstWorkerNullFunc) (GstWorker * worker);
*/
typedef void (*GstWorkerAliveFunc) (GstWorker * worker);
+/**
+ * @brief worker virtual close function
+ * @param worker The GstWorker instance.
+ */
+
+typedef void (*GstWorkerCloseFunc) (GstWorker * worker);
+
/**
* @class GstWorker
* @struct _GstWorker
@@ -104,6 +112,7 @@ struct _GstWorker
//GstSwitchServer *server; /*!< */
GMutex pipeline_lock; /*!< Mutex for %pipeline */
+ GCond shutdown_cond; /*!< Cond for shutting down pipelines cleanly */
GstElement *pipeline; /*!< The pipeline. */
GstBus *bus; /*!< The pipeline bus. */
@@ -114,6 +123,11 @@ struct _GstWorker
gboolean auto_replay; /*!< The worker should replay if it's TRUE */
gboolean paused_for_buffering; /*!< Mark for buffering pause. */
guint watch; /*!< The watch number of the pipeline bus. */
+
+ /*!< TRUE if the recording pipeline needs clean shut-down
+ * via an EOS event to finish up before stopping
+ */
+ gboolean send_eos_on_stop;
};
/**
@@ -200,6 +214,13 @@ struct _GstWorkerClass
* @param worker The GstWorker instance.
*/
gboolean (*reset) (GstWorker * worker);
+
+ /*
+ * @brief Close the worker, deallocating and closing resources
+ * @param worker The GstWorker instance.
+ */
+
+ void (*close) (GstWorker * worker);
};
/**
@@ -269,4 +290,4 @@ GstElement *gst_worker_get_element_unlocked (GstWorker *worker, const gchar *nam
*/
GstElement *gst_worker_get_element (GstWorker *worker, const gchar * name);
-#endif //__GST_WORKER_H__by_Duzy_Chan__
+#endif //__GST_WORKER_H__