debian/0000775000000000000000000000000012311057136007167 5ustar debian/changelog0000664000000000000000000032551412311057115011050 0ustar gst-plugins-good0.10 (0.10.31-3+nmu1ubuntu5) trusty; urgency=medium * Cherry-pick patch from upstream to fix gtk-docs FTBFS. (LP: #1289818) -- Dimitri John Ledkov Sat, 15 Mar 2014 14:06:05 +0000 gst-plugins-good0.10 (0.10.31-3+nmu1ubuntu4) trusty; urgency=medium * Use dh_autoreconf --as-needed rather than disabling libtoolize and manually patching ltmain.sh, to pick up other libtool macro changes for new ports. -- Colin Watson Tue, 24 Dec 2013 02:35:47 +0000 gst-plugins-good0.10 (0.10.31-3+nmu1ubuntu3) saucy; urgency=low * d/p/0001-v4l2-fix-compilation-against-newer-kernel-headers-as.patch: Cherry-pick upstream patch to fix building against newer v4l2 headers. -- Iain Lane Fri, 14 Jun 2013 10:58:09 +0100 gst-plugins-good0.10 (0.10.31-3+nmu1ubuntu2) raring; urgency=low * debian/patches/0001-fix-v4l2_munmap.patch: Cherry-pick a commit from upstream to resolve an issue where applications freeze when using v4l2src. (LP: #1041432) -- Iain Lane Mon, 11 Feb 2013 11:18:03 +0000 gst-plugins-good0.10 (0.10.31-3+nmu1ubuntu1) raring; urgency=low * Merge from Debian unstable. Remaining changes: - 04_move_farsight_plugins_to_good.patch: Import autoconvert, dtmf, liveadder, rptmux from -plugins-bad. - 05_move_shm_to_good.patch: Import shm from -plugins-bad. - 07_move-camerabin.patch: Import camerabin, camerabin2, jpegformat and basecamerabinsrc from -plugins-bad. - debian/control*: + Drop dependency from gstreamer0.10-plugins-good on gstreamer0.10-gconf. It pulls gconf and gtk3 onto the Kubuntu cd. + Use Breaks instead of Conflicts. + Add a 'Pre-Depends: ${misc:Pre-Depends}' to the plugin package, since we're shipping shared libraries in the package that Debian isn't. - debian/patches/git_ring_buffer_null_check.patch: segfault fix. - debian/patches/git_new_v4l_building.patch: "v4l2: fix build with recent kernels, the v4l2_buffer input field was removed". -- Logan Rosen Sat, 08 Dec 2012 15:15:42 -0500 gst-plugins-good0.10 (0.10.31-3+nmu1) unstable; urgency=medium * Non-maintainer upload. * Don't provide sinks and sources in gstreamer0.10-conf (closes: #689120) - Patch thanks to Gregor Herrmann. -- Michael Gilbert Tue, 13 Nov 2012 02:27:42 +0000 gst-plugins-good0.10 (0.10.31-3ubuntu3) raring; urgency=low * debian/patches/git_new_v4l_building.patch: "v4l2: fix build with recent kernels, the v4l2_buffer input field was removed" -- Sebastien Bacher Fri, 16 Nov 2012 15:06:05 +0100 gst-plugins-good0.10 (0.10.31-3ubuntu2) raring; urgency=low * debian/patches/git_ring_buffer_null_check.patch: segfault fix, thanks Erik Botö for backporting and testing the change (lp: #1068155) -- Sebastien Bacher Fri, 16 Nov 2012 12:51:49 +0100 gst-plugins-good0.10 (0.10.31-3ubuntu1) quantal; urgency=low * Merge from Debian experimental, remaining changes: - 04_move_farsight_plugins_to_good.patch Import autoconvert, dtmf, liveadder, rptmux from -plugins-bad - 05_move_shm_to_good.patch Import shm from -plugins-bad. - 07_move-camerabin.patch Import camerabin, camerabin2, jpegformat and basecamerabinsrc from -plugins-bad. - control*: * Drop dependency from gstreamer0.10-plugins-good on gstreamer0.10-gconf. It pulls gconf and gtk3 onto the Kubuntu cd. * Use Breaks instead of Conflicts. * Add a 'Pre-Depends: ${misc:Pre-Depends}' to the plugin package, since we're shipping shared libraries in the package that Debian isn't. -- Iain Lane Mon, 16 Jul 2012 08:34:48 +0200 gst-plugins-good0.10 (0.10.31-3) unstable; urgency=low * d/p/0001-pulsesrc-Listen-to-source-output-events-not-sink-inp.patch + Added. Listen to the right type of events in pulsesrc for volume and mute changes. (From upstream git) -- Sjoerd Simons Tue, 22 May 2012 10:12:46 +0200 gst-plugins-good0.10 (0.10.31-2) unstable; urgency=low * debian/build-deps.in: + Build-depend on libpng-dev instead of libpng12-dev (Closes: #662365). -- Sebastian Dröge Mon, 23 Apr 2012 09:04:37 +0200 gst-plugins-good0.10 (0.10.31-1ubuntu1) precise; urgency=low * Merge from Debian unstable. -- Timo Aaltonen Wed, 22 Feb 2012 11:31:28 +0200 gst-plugins-good0.10 (0.10.31-1) unstable; urgency=low * New upstream release, "Faster": + debian/rules, debian/build-deps.in: - (Build-) depend on GStreamer core/base >= 0.10.36. -- Sebastian Dröge Tue, 21 Feb 2012 10:45:15 +0100 gst-plugins-good0.10 (0.10.30.3-1ubuntu1) precise; urgency=low * Merge from Debian experimental, remaining changes: - 04_move_farsight_plugins_to_good.patch Import autoconvert, dtmf, liveadder, rptmux from -plugins-bad - 05_move_shm_to_good.patch Import shm from -plugins-bad. - 07_move-camerabin.patch Import camerabin, camerabin2, jpegformat and basecamerabinsrc from -plugins-bad. - control*: * Drop dependency from gstreamer0.10-plugins-good on gstreamer0.10-gconf. It pulls gconf and gtk3 onto the Kubuntu cd. * Use Breaks instead of Conflicts. * Add a 'Pre-Depends: ${misc:Pre-Depends}' to the plugin package, since we're shipping shared libraries in the package that Debian isn't. * Update the patches by pulling new version of the code from -plugins-bad 0.10.22.3. -- Timo Aaltonen Thu, 09 Feb 2012 16:44:53 +0200 gst-plugins-good0.10 (0.10.30.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Mon, 06 Feb 2012 09:59:53 +0100 gst-plugins-good0.10 (0.10.30.2-3) experimental; urgency=low * debian/control.in: + Build-depend on automake (>= 1.10), autoconf (>= 2.60) and libtool (>= 2.0) to make dh-autoreconf actually work. -- Sebastian Dröge Wed, 14 Dec 2011 12:58:12 +0100 gst-plugins-good0.10 (0.10.30.2-2ubuntu2) precise; urgency=low * Drop dependency from gstreamer0.10-plugins-good on gstreamer0.10-gconf. It pulls gconf and gtk3 onto the Kubuntu cd. ubuntu-desktop still depends on gstreamer0.10-gconf through gnome-media. -- Felix Geyer Thu, 15 Dec 2011 16:08:29 +0100 gst-plugins-good0.10 (0.10.30.2-2ubuntu1) precise; urgency=low * Merge from Debian experimental, remaining changes: - debian/patches/04_move_farsight_plugins_to_good.patch * move the farsight plugins into gst-plugins-good - debian/patches/05_move_shm_to_good.patch * Moved shm from -bad to -good - debian/patches/06_shm_fixes.patch * shm related fixes from upstream - debian/patches/07_move-camerabin.patch * Move jpegformat into gst-plugins-good for camerabin. - debian/control: always use versioned Breaks instead of versioned Conflicts. * Drop changes to autogenerated autotools files, since we're using dh-autoreconf. * Add a 'Pre-Depends: ${misc:Pre-Depends}' to the plugin package, since we're shipping shared libraries in the package that Debian isn't. -- Steve Langasek Tue, 13 Dec 2011 12:04:21 -0800 gst-plugins-good0.10 (0.10.30.2-2) experimental; urgency=low * debian/control.in: + Depend on gstreamer0.10-pulseaudio | gstreamer0.10-audiosink instead of only depending on the virtual package. -- Sebastian Dröge Mon, 12 Dec 2011 10:54:47 +0100 gst-plugins-good0.10 (0.10.30.2-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps.in, debian/rules: - Build-depend on GStreamer core/base 0.10.35.2. - Build-depend on PulseAudio 1.0. + debian/patches/04_rtpgst722pay-compensate-for-clockrate.patch: - Dropped, merged upstream. + debian/patches/03_pulse-rank.patch: - Dropped, merged upstream. + debian/patches/99_ltmain_as-needed.patch: - Refreshed to apply cleanly again. * debian/control.in, debian/build-deps.in: + ACK NMU 0.10.30.2-2.1 and 0.10.30.2-2.2 (Closes: #646325, #633438). + Correctly change control file from the last two NMUs. * debian/build-deps.in, debian/compat, debian/control.in, debian/gstreamer-gconf.install, debian/gstreamer-plugins-good.install, debian/gstreamer-pulseaudio.install, debian/rules: + Transition package to multi arch (Closes: #647486). Patch taken from the Ubuntu package. -- Sebastian Dröge Mon, 12 Dec 2011 09:34:40 +0100 gst-plugins-good0.10 (0.10.30-2.2) unstable; urgency=low * Non-maintainer upload. * Transition to libjpeg8 by build-depending against libjpeg-dev (instead of libjpeg62-dev), closes: #633438 -- Philipp Kaluza Sat, 03 Dec 2011 13:34:20 +0100 gst-plugins-good0.10 (0.10.30-2.1ubuntu1) precise; urgency=low * Merged changes from debian unstable, remaining differences: - debian/patches/05_move_shm_to_good.patch * Moved shm from -bad to -good (LP: #739351) - debian/patches/06_shm_fixes.patch * shm related fixes from upstream - debian/patches/04_move_farsight_plugins_to_good.patch * move the farsight plugins into gst-plugins-good - debian/patches/07_move-camerabin.patch * Move jpegformat into gst-plugins-good for camerabin. -- Ken VanDine Fri, 09 Dec 2011 14:33:15 -0500 gst-plugins-good0.10 (0.10.30-2.1) unstable; urgency=low * Non-maintainer upload. * Add Replaces/Breaks to gstreamer0.10-gconf for old gstreamer0.10-plugins-good (Closes: #646325) -- Philipp Kaluza Sat, 03 Dec 2011 10:34:02 +0000 gst-plugins-good0.10 (0.10.30-2) unstable; urgency=low [ Sebastian Dröge ] * debian/control.in: + Let gstreamer0.10-plugins-good depend on gstreamer0.10-gconf for wheezy to guarantee correct upgrades to wheezy (Closes: #625567). [ Sjoerd Simons ] * debian/patches/04_rtpgst722pay-compensate-for-clockrate.patch: + Correct clockrate vs. samplerate difference for G722. Fixes people sounding like chipmunks on voip calls with G722 (From upstream git) -- Sjoerd Simons Wed, 09 Nov 2011 14:20:22 +0000 gst-plugins-good0.10 (0.10.30-1ubuntu7) oneiric; urgency=low * Bump breaks/replaces on gst-plugins-bad (LP: #831897). -- Evan Dandrea Wed, 24 Aug 2011 12:17:00 +0100 gst-plugins-good0.10 (0.10.30-1ubuntu6) oneiric; urgency=low * Move jpegformat into gst-plugins-good for camerabin. -- Evan Dandrea Mon, 22 Aug 2011 15:44:01 +0100 gst-plugins-good0.10 (0.10.30-1ubuntu5) oneiric; urgency=low * Bump Breaks/Replaces on -bad to 0.10.22-2ubuntu2 for camerabin move (LP: #828845). -- Evan Dandrea Fri, 19 Aug 2011 10:03:49 +0100 gst-plugins-good0.10 (0.10.30-1ubuntu4) oneiric; urgency=low * Move camerabin into gst-plugins-good. -- Evan Dandrea Wed, 17 Aug 2011 10:32:17 +0100 gst-plugins-good0.10 (0.10.30-1ubuntu3) oneiric; urgency=low * Use breaks+replaces, *not* conflicts; never, ever use Conflicts <<. -- Steve Langasek Tue, 09 Aug 2011 21:07:47 -0700 gst-plugins-good0.10 (0.10.30-1ubuntu2) oneiric; urgency=low * debian/control.in - Make gstreamer0.10-gconf conflict with gstreamer0.10-plugins-good << 0.10.30-1 (LP: #815418) -- Ken VanDine Tue, 26 Jul 2011 00:44:55 -0400 gst-plugins-good0.10 (0.10.30-1ubuntu1) oneiric; urgency=low * Merged changes from debian unstable, remaining differences: - debian/patches/05_move_shm_to_good.patch * Moved shm from -bad to -good (LP: #739351) - debian/patches/06_shm_fixes.patch * shm related fixes from upstream -- Ken VanDine Tue, 19 Jul 2011 13:41:08 -0400 gst-plugins-good0.10 (0.10.30-1) unstable; urgency=low * New upstream release, "Adagio": + debian/patches/01_path-max.patch: - Dropped, merged upstream. -- Sebastian Dröge Thu, 23 Jun 2011 09:44:29 +0200 gst-plugins-good0.10 (0.10.29-2) unstable; urgency=low * debian/patches/01_path-max.patch: + Define PATH_MAX if it isn't defined, e.g. on GNU Hurd. Thanks to Pino Toscano for the patch (Closes: #626698). * debian/rules, debian/gstreamer-plugins-good.install: + Don't build the oss4 plugin on GNU Hurd, it does not compile because of the way how ioctl() works there. Thanks to Pino Toscano for the patch (Closes: #626698). -- Sebastian Dröge Mon, 16 May 2011 09:10:14 +0200 gst-plugins-good0.10 (0.10.29-1) unstable; urgency=low * New upstream release, "Soft Cheese Enthusiast": + debian/gstreamer-plugins-good.install: - The quicktime plugin was renamed to isomp4 because of trademark concerns. + debian/rules, debian/build-deps.in: - Build-depend on GStreamer core/base 0.10.33. -- Sebastian Dröge Tue, 10 May 2011 15:52:01 +0200 gst-plugins-good0.10 (0.10.28.3-1) experimental; urgency=low * New upstream pre-release: + debian/gstreamer-plugins-good.install: - The qtdemux plugin was renamed to quicktime because it contains a muxer too now. -- Sebastian Dröge Wed, 27 Apr 2011 15:48:22 +0200 gst-plugins-good0.10 (0.10.28.2-1) experimental; urgency=low [ Alessandro Decina ] * debian/build-deps.in: + Add dependency on dh-autoreconf * debian/rules: + Include dh-autoreconf cdbs rule [ Loïc Minier ] * Use linux-any in build-deps instead of type-handling. * Rework sed calls to avoid sed | sed. [ Sebastian Dröge ] * debian/control.in, debian/build-deps.in, debian/gstreamer-esd.install, debian/rules: + Drop ESD plugin, nothing is using it anymore, it is unmaintained upstream and pulseaudio is a better replacement for it. * New upstream pre-release: + debian/build-deps.in, debian/rules: - Build-depend on GStreamer core/base >= 0.10.32.2. + debian/build-deps.in: - Build-depend on libcairo2-dev >= 1.10.0. + debian/gstreamer-plugins-good.install, debian/control.in: - Add audioparsers plugin, which was moved from -bad and Conflict with -bad << 0.10.21.2. -- Sebastian Dröge Sun, 17 Apr 2011 10:55:39 +0200 gst-plugins-good0.10 (0.10.28-3) unstable; urgency=low * debian/control.in, debian/rules, debian/gstreamer-plugins-good.install, debian/gstreamer-gconf.install: + Split the GConf plugin into it's own package. GStreamer is used as the default phonon backend nowadays and there's no reason why KDE should depend on half of GNOME (Closes: #595651). -- Sebastian Dröge Fri, 01 Apr 2011 10:13:13 +0200 gst-plugins-good0.10 (0.10.28-2) unstable; urgency=low * Upload to unstable. -- Sebastian Dröge Tue, 22 Mar 2011 15:06:25 +0100 gst-plugins-good0.10 (0.10.28-1) experimental; urgency=low * New upstream bugfix release, "Inconvenienced by the Solar System": + Fixes build with kernels that have v4l2 support but not v4l support. -- Sebastian Dröge Sun, 13 Mar 2011 13:03:07 +0100 gst-plugins-good0.10 (0.10.28-0ubuntu7) natty; urgency=low * debian/patches/07_pulsesink_allow_larger_packets.patch: + Drop this patch again, it causes underuns which result in glitches, cracking sound, etc. This happens more often when audio codecs with large frame sizes are used (e.g. WMA). See https://bugzilla.gnome.org/show_bug.cgi?id=647326 and duplicates. -- Sebastian Dröge Fri, 15 Apr 2011 19:47:50 +0200 gst-plugins-good0.10 (0.10.28-0ubuntu6) natty; urgency=low * debian/rules: + Set AUTOPOINT=true to prevent autoreconf from breaking the patched gettext setup used by gstreamer. -- Sebastian Dröge Tue, 05 Apr 2011 15:20:55 +0200 gst-plugins-good0.10 (0.10.28-0ubuntu5) natty; urgency=low [ Alessandro Decina ] * debian/control.in: + Add build dependency on dh-autoreconf. * debian/rules: + Include dh-autoreconf cdbs rule. -- Sebastian Dröge Tue, 05 Apr 2011 10:58:14 +0200 gst-plugins-good0.10 (0.10.28-0ubuntu4) natty; urgency=low * debian/control.in - Use a Breaks and Replace instead of Conflicts and Replace (LP: #739551) - Added a Breaks and Replace for the -dbg package as well -- Ken VanDine Tue, 22 Mar 2011 08:59:42 -0400 gst-plugins-good0.10 (0.10.28-0ubuntu3) natty; urgency=low * debian/control.in - Dropped Breaks and made sure to Conflict and Replace -bad (LP: #739551) -- Ken VanDine Mon, 21 Mar 2011 14:09:27 -0400 gst-plugins-good0.10 (0.10.28-0ubuntu2) natty; urgency=low * debian/patches/05_move_shm_to_good.patch - Moved shm from -bad to -good (LP: #739351) * debian/patches/06_shm_fixes.patch - shm related fixes from upstream * debian/gstreamer0.10-plugins-good.install - Added shm - Removed valve, it is in libgstreamer0.10-0 now * debian/control.in - Changed the conflicts to a breaks for gstreamer0.10-plugins-bad << 0.10.21-1ubuntu5 * debian/patches/04_move_farsight_plugins_to_good.patch - Refreshed farsight plugins from -bad - Removed valve, it is in core now -- Ken VanDine Thu, 17 Mar 2011 15:50:07 -0400 gst-plugins-good0.10 (0.10.28-0ubuntu1) natty; urgency=low * New upstream release -- Robert Ancell Wed, 09 Mar 2011 12:13:46 +1100 gst-plugins-good0.10 (0.10.27-1ubuntu2) natty; urgency=low * debian/control: * debian/build-deps: - Rebuild from the .in files -- Robert Ancell Mon, 07 Feb 2011 11:30:59 +1100 gst-plugins-good0.10 (0.10.27-1ubuntu1) natty; urgency=low * Merge with Debian experimental, remaining Ubuntu changes: * debian/build-deps: - Depends on dh-autoreconf, autopoint * debian/control: - Use standards-version 3.9.1 * debian/gstreamer-plugins-good.install, debian/patches/04_move_farsight_plugins_to_good.patch, - Install the farsight plugins with good until upstream does the change * debian/patches/07_pulsesink_allow_larger_packets.patch: - allow larger packets to be sent, prevents pulseaudio from crashing on low-end machines * debian/rules: - Use autoreconf.mk - Set the audiosrc default to pulsesrc -- Robert Ancell Mon, 07 Feb 2011 10:15:17 +1100 gst-plugins-good0.10 (0.10.27-1) experimental; urgency=low * New upstream stable release, "Some Kind of Temporal Blend": + debian/build-deps, debian/rules: - Require GStreamer, gst-plugins-base >= 0.10.32. -- Sebastian Dröge Sat, 22 Jan 2011 13:35:36 +0100 gst-plugins-good0.10 (0.10.26.4-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps, debian/rules: - Require GStreamer, gst-plugins-base >= 0.10.31.4. -- Sebastian Dröge Wed, 19 Jan 2011 21:16:40 +0100 gst-plugins-good0.10 (0.10.26.3-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps, debian/rules: - Require GStreamer, gst-plugins-base >= 0.10.31.3. -- Sebastian Dröge Thu, 13 Jan 2011 13:07:01 +0100 gst-plugins-good0.10 (0.10.26.2-1) experimental; urgency=low [ Sebastian Dröge ] * New upstream pre-release: + debian/control.in: - Conflict/Replace gstreamer0.10-plugins-bad (<< 0.10.20.2) because of the moved JACK plugin. + debian/build-deps.in, debian/gstreamer-plugins-good.install: - Add the JACK plugin which was moved from gst-plugins-bad. + debian/build-deps, debian/rules: - Require GStreamer, gst-plugins-base >= 0.10.31.2. - Require GLib >= 2.22. [ Emilio Pozuelo Monfort ] * debian/build-deps.in, debian/rules: + Use dpkg-vendor instead of lsb_release. -- Sebastian Dröge Mon, 10 Jan 2011 15:11:27 +0100 gst-plugins-good0.10 (0.10.26-1ubuntu3) natty; urgency=low * debian/patches/06_pulsesink_dont_uncork_in_start.patch, debian/patches/07_pulsesink_allow_larger_packets.patch: - allow larger packets to be sent, prevents pulseaudio from crashing on low-end machines (LP: #644644) -- David Henningsson Thu, 20 Jan 2011 13:10:34 +0100 gst-plugins-good0.10 (0.10.26-1ubuntu2) natty; urgency=low * debian/build-deps: - Build-depends on autopoint -- Robert Ancell Wed, 22 Dec 2010 12:16:03 +1100 gst-plugins-good0.10 (0.10.26-1ubuntu1) natty; urgency=low * Merge with Debian experimental, remaining Ubuntu changes: * debian/build-deps: - Depends on dh-autoreconf * debian/control: - Use standards-version 3.9.1 * debian/gstreamer-plugins-good.install, debian/patches/04_move_farsight_plugins_to_good.patch, - Install the farsight plugins with good until upstream does the change * debian/rules: - Use autoreconf.mk - Set the audiosrc default to pulsesrc -- Robert Ancell Wed, 22 Dec 2010 10:41:47 +1100 gst-plugins-good0.10 (0.10.26-1) experimental; urgency=low * New upstream release, "Escapades". -- Sebastian Dröge Thu, 02 Dec 2010 10:35:38 +0100 gst-plugins-good0.10 (0.10.25.5-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Fri, 19 Nov 2010 11:30:21 +0100 gst-plugins-good0.10 (0.10.25.4-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Mon, 01 Nov 2010 20:18:27 +0100 gst-plugins-good0.10 (0.10.25.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Fri, 22 Oct 2010 19:58:54 +0200 gst-plugins-good0.10 (0.10.25.2-1) experimental; urgency=low * New upstream pre-release: + debian/patches/01_alphacolor-passthrough.patch, debian/patches/02_qtdemux-eos-handling.patch, debian/patches/04_qtdemux-eos-handling.patch: - Dropped, merged upstream. + debian/rules, debian/build-deps.in: - Require GStreamer core/plugins-base >= 0.10.30.2. - Require ORC >= 0.4.11. -- Sebastian Dröge Sat, 16 Oct 2010 11:29:22 +0200 gst-plugins-good0.10 (0.10.25-5) experimental; urgency=low * debian/build-deps.in: + Add the epoch to the orc build dependency to get dependencies on the correct versions. -- Sebastian Dröge Fri, 17 Sep 2010 13:24:49 +0200 gst-plugins-good0.10 (0.10.25-4) experimental; urgency=low * debian/patches/02_qtdemux-eos-handling.patch: + Another patch from upstream GIT to fix EOS handling in the QT/MP4 demuxer even more. -- Sebastian Dröge Thu, 16 Sep 2010 11:18:01 +0200 gst-plugins-good0.10 (0.10.25-3) experimental; urgency=low * debian/patches/02_qtdemux-eos-handling.patch: + Patch from upstream GIT to fix EOS handling in the QT/MP4 demuxer. -- Sebastian Dröge Wed, 15 Sep 2010 08:21:56 +0200 gst-plugins-good0.10 (0.10.25-2) experimental; urgency=low * debian/patches/01_alphacolor-passthrough.patch: + Simple patch from upstream GIT to fix passthrough mode of the alphacolor element. -- Sebastian Dröge Fri, 10 Sep 2010 11:40:40 +0200 gst-plugins-good0.10 (0.10.25-1) experimental; urgency=low * New upstream release, "Woe to You Oh Earth and Sea". -- Sebastian Dröge Fri, 03 Sep 2010 12:36:18 +0200 gst-plugins-good0.10 (0.10.24.5-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Wed, 25 Aug 2010 22:12:39 +0200 gst-plugins-good0.10 (0.10.24.4-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Sat, 21 Aug 2010 21:59:12 +0200 gst-plugins-good0.10 (0.10.24.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Wed, 11 Aug 2010 21:19:47 +0200 gst-plugins-good0.10 (0.10.24.2-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Wed, 11 Aug 2010 09:12:22 +0200 gst-plugins-good0.10 (0.10.24-1) unstable; urgency=low * New upstream stable release, "Taking Liberties": + debian/build-deps.in, debian/rules: - Build depend on GStreamer 0.10.30. -- Sebastian Dröge Thu, 15 Jul 2010 13:54:14 +0200 gst-plugins-good0.10 (0.10.23.4-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Wed, 07 Jul 2010 15:42:43 +0200 gst-plugins-good0.10 (0.10.23.3-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps.in, debian/rules: - Build depend on GStreamer 0.10.29.3. + debian/patches/01_jpeg-libgstbase-linking.patch: - Dropped, merged upstream. -- Sebastian Dröge Wed, 30 Jun 2010 10:43:51 +0200 gst-plugins-good0.10 (0.10.23.2-1) experimental; urgency=low * New upstream pre-release: + debian/rules, debian/compat, debian/control.in, debian/source/format, debian/patches/*: - Update to debhelper compat level 7. - Update to source format 3.0 (quilt). + debian/build-deps.in, debian/rules: - Build depend on GLib 2.20 and GStreamer 0.10.29.2. - Build depend on libraw1394 2.0.0. - Build depend on ORC instead of liboil. + debian/patches/02_pulsesink-reuse.patch, debian/patches/04_theora_delivery_method.patch, debian/patches/05_rtph264pay_short_startcodes.patch: - Dropped, merged upstream. + debian/patches/01_jpeg-libgstbase-linking.patch: - Link the jpeg plugin with libgstbase to fix FTBFS. -- Sebastian Dröge Sat, 26 Jun 2010 21:38:03 +0200 gst-plugins-good0.10 (0.10.23-4) unstable; urgency=low * debian/patches/05_rtph264pay_short_startcodes.patch: + Patch from upstream GIT to let rtph264 cope with the shorter startcodes that are now used by by x264enc. This fixes interoperability with Google Video and Nokia N900 among others. -- Sjoerd Simons Fri, 04 Jun 2010 12:51:01 +0100 gst-plugins-good0.10 (0.10.23-3) unstable; urgency=low * debian/patches/02_pulsesink-reuse.patch: + Patch from upstream GIT to fix reuse of pulsesink. -- Sebastian Dröge Wed, 02 Jun 2010 10:54:42 +0200 gst-plugins-good0.10 (0.10.23-2) unstable; urgency=low * debian/patches/04_theora_delivery_method.patch + Added. Let the theora payloader advertise delivery-method in its caps. Needed for backwards compatibility with older gstreamer/farsight versions. (From Gnome bugzilla #618940) -- Sjoerd Simons Mon, 31 May 2010 22:31:06 +0100 gst-plugins-good0.10 (0.10.23-1) unstable; urgency=low * New upstream release, "Stylish Kids in Riot". -- Sebastian Dröge Mon, 31 May 2010 05:42:46 +0200 gst-plugins-good0.10 (0.10.22.3-1) unstable; urgency=low * New upstream pre-release: + debian/patches/*: - Drop all VP8/WebM patches and other patches that are upstream now. -- Sebastian Dröge Wed, 26 May 2010 15:34:01 +0200 gst-plugins-good0.10 (0.10.22.2-3) unstable; urgency=low * Upload to unstable. -- Sebastian Dröge Thu, 20 May 2010 13:55:29 +0200 gst-plugins-good0.10 (0.10.22.2-2) experimental; urgency=low * debian/patches/00*: + Add support for VP8 in the AVI, Matroska and QT/MP4 plugins and fix some Matroska issues. * debian/patches/04-pulse-0001-pulse-Don-t-lock-the-mainloop-in-NULL.patch: + Don't try to use a NULL pulse mainloop. -- Sebastian Dröge Tue, 18 May 2010 20:59:55 +0200 gst-plugins-good0.10 (0.10.22.2-1) experimental; urgency=low * New upstream pre-release: + debian/gstreamer-plugins-good.install, debian/control.in: - Ship the imagefreeze and oss4audio plugins here, they were moved from gstreamer0.10-plugins-bad. - Add conflicts for gstreamer0.10-plugins-bad because of the moved capssetter element. * debian/patches/01_efence-configure-check.patch: + Fix configure check for efence. -- Sebastian Dröge Sat, 15 May 2010 09:55:08 +0200 gst-plugins-good0.10 (0.10.22-1) unstable; urgency=low * New upstream release, "Square One": + debian/build-deps.in, debian/rules: - Build depend on GStreamer core/base >= 0.10.29. -- Sebastian Dröge Wed, 28 Apr 2010 09:44:17 +0200 gst-plugins-good0.10 (0.10.21.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Mon, 26 Apr 2010 08:01:09 +0200 gst-plugins-good0.10 (0.10.21.2-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps.in, debian/rules: - Build depend on GStreamer core/base >= 0.10.28.2. * debian/build-deps.in, debian/control.in: + (Build-) depend on GStreamer core/base documentation to get documentation cross references right. -- Sebastian Dröge Thu, 15 Apr 2010 13:56:53 +0200 gst-plugins-good0.10 (0.10.21-1) unstable; urgency=low * New upstream stable release, "Lemons". -- Sebastian Dröge Tue, 09 Mar 2010 09:48:28 +0000 gst-plugins-good0.10 (0.10.19-1) unstable; urgency=low * New upstream stable release, "Closer to the Edit": + debian/rules, debian/build-deps.in: - Build depend on gstreamer and gst-plugins-base >= 0.10.27. -- Sebastian Dröge Mon, 08 Mar 2010 10:01:27 +0000 gst-plugins-good0.10 (0.10.18.3-1) experimental; urgency=low * New upstream pre-release: + debian/rules, debian/build-deps.in: - Build depend on gstreamer and gst-plugins-base >= 0.10.26.3. -- Sebastian Dröge Thu, 25 Feb 2010 08:27:57 +0100 gst-plugins-good0.10 (0.10.18.2-1) experimental; urgency=low * New upstream pre-release: + debian/gstreamer-plugins-good.install, debian/control.in: - Add shapewipe plugin, which moved from gst-plugins-bad. Also add Replaces for gst-plugins-bad << 0.10.17.2. + debian/build-deps.in: - Build depend on gst-plugins-base 0.10.26.2. -- Sebastian Dröge Fri, 19 Feb 2010 13:51:58 +0100 gst-plugins-good0.10 (0.10.18-1) unstable; urgency=low * New upstream release, "Short Circuit": + debian/rules, debian/build-deps.in: - Update GStreamer and gst-plugins-base build dependencies to >= 0.10.26. -- Sebastian Dröge Thu, 11 Feb 2010 10:31:48 +0100 gst-plugins-good0.10 (0.10.17.3-1) experimental; urgency=low * debian/build-deps.in, debian/gstreamer-plugins-good.install: + Remove HAL plugin, HAL will most probably be dropped soon and nobody uses this plugin anymore. * New upstream pre-release. -- Sebastian Dröge Fri, 05 Feb 2010 10:10:51 +0100 gst-plugins-good0.10 (0.10.17.2-1) experimental; urgency=low * New upstream pre-release: + debian/rules, debian/control.in: - Build depend on GStreamer 0.10.25.2, gst-plugins-base 0.10.25.2 and GLib 2.18. + Fixes demuxing of some MOV files (Closes: #561227). + Fixes FTBFS with gcc 4.5 (Closes: #564998). * debian/control.in: + Fix typo in package description (Closes: #557368). + Remove David I. Lehn from Uploaders (Closes: #557285). -- Sebastian Dröge Wed, 27 Jan 2010 07:56:17 +0100 gst-plugins-good0.10 (0.10.17-1) unstable; urgency=low * debian/build-deps.in: + Build depend on pulseaudio >= 0.9.20 for additional features. * New upstream release, "They used to sparkle". -- Sebastian Dröge Tue, 17 Nov 2009 09:17:35 +0100 gst-plugins-good0.10 (0.10.16.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Fri, 13 Nov 2009 07:58:08 +0100 gst-plugins-good0.10 (0.10.16.2-1) experimental; urgency=low * New upstream pre-release: + debian/patches/00*pulse*.patch, debian/patches/00*equalizer*.patch, debian/patches/04_oss-rank.patch: - Dropped, merged upstream. + debian/build-deps.in, debian/rules: - Update build dependencies. -- Sebastian Dröge Tue, 10 Nov 2009 10:28:49 +0100 gst-plugins-good0.10 (0.10.16-5) unstable; urgency=low * debian/patches/0021-pulsesink-Only-set-the-volume-on-stream-connection-i.patch: + Only set the volume on stream connection if pulse >= 0.9.20 is available, this prevents weird volume changes in totem. -- Sebastian Dröge Fri, 30 Oct 2009 10:33:31 +0100 gst-plugins-good0.10 (0.10.16-4) unstable; urgency=low * debian/patches/000{1,2}-equalizer*.patch: + Patches from upstream GIT to fix equalizer in stereo mode and to use better filters for the first and last band. * debian/patches/00[1-20]-pulse*.patch: + Patches from upstream GIT to improve PulseAudio plugin and fix some major issues, like decreasing the volume after every track in some cases. -- Sebastian Dröge Sun, 18 Oct 2009 14:00:56 +0200 gst-plugins-good0.10 (0.10.16-3) unstable; urgency=low * debian/patches/04_oss-rank.patch: + Downgrade the rank of osssrc to secondary * debian/rules: Change default audio input to autoaudiosrc -- Sjoerd Simons Wed, 07 Oct 2009 22:33:02 +0100 gst-plugins-good0.10 (0.10.16-2) unstable; urgency=low * debian/rules: + Use libgudev on Linux for v4l2 device detection. -- Sebastian Dröge Wed, 30 Sep 2009 08:26:07 +0200 gst-plugins-good0.10 (0.10.16-1) unstable; urgency=low * New upstream release, 'Secret Handshakes'. -- Sebastian Dröge Sun, 30 Aug 2009 11:02:26 +0200 gst-plugins-good0.10 (0.10.15.5-1) experimental; urgency=low * New upstream pre-release. * debian/control.in: + Update Standards-version to 3.8.3, no additional changes needed. -- Sebastian Dröge Wed, 26 Aug 2009 17:39:36 +0200 gst-plugins-good0.10 (0.10.15.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Mon, 17 Aug 2009 08:11:59 +0200 gst-plugins-good0.10 (0.10.15.2-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps.in, debian/rules: - Update build dependencies. + debian/control.in, debian/gstreamer-plugins-good.install: - Add the rtpmanager plugin which was moved from gst-plugins-bad. For this also add Replaces for gst-plugins-bad << 0.10.13.2. + debian/patches/01_equalizer-integer-arithmetic-distortions.patch, debian/patches/02_SA35205-pngdec-integer-overflow.patch: - Dropped, merged upstream. * debian/control.in: + Update Standards-version to 3.8.2, no additional changes needed. -- Sebastian Dröge Wed, 12 Aug 2009 07:13:08 +0200 gst-plugins-good0.10 (0.10.15-2) unstable; urgency=high * debian/patches/01_equalizer-integer-arithmetic-distortions.patch: + Patch from upstream GIT to fix distortions when the integer arithmetic mode of the equalizer is used. * debian/patches/02_SA35205-pngdec-integer-overflow.patch: + SECURITY: SA35205 - PNG Processing Integer Overflow Vulnerability Patch from upstream GIT to fix an integer overflow in pngdec: A malformed (or simply huge) PNG file can lead to integer overflow in calculating the size of the output buffer, leading to crashes or buffer overflows later (Closes: #531631). -- Sebastian Dröge Wed, 03 Jun 2009 08:22:36 +0200 gst-plugins-good0.10 (0.10.15-1) unstable; urgency=low * New upstream release, 'I've been up all night'. -- Sebastian Dröge Wed, 20 May 2009 23:19:36 +0200 gst-plugins-good0.10 (0.10.14.3-1) experimental; urgency=low * New upstream pre-release: + Fixes regression in AVI seeking (Closes: #528813). + debian/patches/01_rtp-libm-linking.patch: - Dropped, merged upstream. + debian/gstreamer-plugins-good.install, debian/control.in: - Add the flv, deinterlace and y4menc plugins that were moved from gst-plugins-bad. * debian/control.in: + Change section of the debug package to debug and add ${misc:Depends} to it's dependencies. -- Sebastian Dröge Sat, 16 May 2009 11:28:14 +0200 gst-plugins-good0.10 (0.10.14.2-1) experimental; urgency=low * New upstream pre-release: + debian/patches/01_gconf-notifications.patch: - Dropped, merged upstream. + debian/rules, debian/build-deps.in: - Updated build dependencies. + debian/patches/01_rtp-libm-linking.patch: - Link with -lm to fix unresolved symbols. * debian/control.in: + Update Standards-version to 3.8.1, no additional changes needed. -- Sebastian Dröge Tue, 12 May 2009 08:59:05 +0200 gst-plugins-good0.10 (0.10.14-2) unstable; urgency=low * Upload to unstable. -- Sebastian Dröge Fri, 06 Mar 2009 23:41:50 +0100 gst-plugins-good0.10 (0.10.14-1) experimental; urgency=low * New upstream release, 'Disaffected Affectation'. * debian/patches/02_no-Werror.patch, debian/rules: + Pass -Wno-error via C(XX)FLAGS instead of patching configure. * debian/patches/01_gconf-notifications.patch: + Disconnect GConf notifications after usage, patch from upstream GIT. -- Sebastian Dröge Fri, 20 Feb 2009 10:31:38 +0100 gst-plugins-good0.10 (0.10.13.3-1) experimental; urgency=low * debian/build-deps.in: + Build depend on gstreamer0.10-plugins-base 0.10.22 for the new videotestsrc colors to fix the videocrop unit test. * debian/gstreamer-plugins-good.install: + Include the new equalizer presets. * New upstream pre-release. -- Sebastian Dröge Mon, 16 Feb 2009 15:30:35 +0100 gst-plugins-good0.10 (0.10.13.2-1) experimental; urgency=low * New upstream pre-release: + debian/patches/04_equalizer-coefficients.patch, debian/patches/05_soup-live-source.patch, debian/patches/06_libv4l.patch, debian/patches/07_pulse-hang-and-thread-leak.patch, debian/patches/99_autoreconf.patch: - Dropped, merged upstream. + debian/rules, debian/build-deps.in: - Build depend on gstreamer and gst-plugins-base 0.10.22. - Build depend on pulseaudio 0.9.13 to enable some optional features in the pulse plugin. -- Sebastian Dröge Sat, 07 Feb 2009 20:36:03 +0100 gst-plugins-good0.10 (0.10.13-2) experimental; urgency=low * debian/patches/07_pulse-hang-and-thread-leak.patch: + Patches from upstream GIT to fix some deadlocks when the pulseaudio daemon disappears and also fix a memory leak. -- Sebastian Dröge Mon, 26 Jan 2009 15:41:46 +0100 gst-plugins-good0.10 (0.10.13-1) experimental; urgency=low * New upstream security release, 'Blatant Discouragement': + Fix potential buffer overflows while reading quicktime headers. Security issue noticed by Tobias Klein (TKADV2009-0xx). + debian/patches/99_autoreconf.patch: - Updated for the new release. * debian/build-deps.in: + Build depend on libcairo2-dev instead of the virtual package libcairo-dev. * - -- Sebastian Dröge Mon, 26 Jan 2009 15:41:45 +0100 gst-plugins-good0.10 (0.10.11-2) experimental; urgency=low * debian/patches/06_libv4l.patch, debian/patches/99_autoreconf.patch, debian/rules: + Patch from upstream CVS to use libv4l for the v4l2 plugin instead of direct access to the v4l device. This adds support for a lot more v4l2 devices. -- Sebastian Dröge Wed, 05 Nov 2008 12:38:36 +0100 gst-plugins-good0.10 (0.10.11-1) experimental; urgency=low * New upstream release, 'Secondary Consideration'. -- Sebastian Dröge Sat, 25 Oct 2008 09:39:23 +0200 gst-plugins-good0.10 (0.10.10.4-1) experimental; urgency=low [ Loic Minier ] * Set gstreamer0.10-pulseaudio's Section to sound to match overrides. [ Sebastian Dröge ] * New upstream pre-release. * debian/patches/04_equalizer-coefficients.patch: + Patch from upstream bugtracker to not recalculate the equalizer coefficients for every single buffer but only when it's needed. * debian/patches/05_soup-live-source.patch: + Patch from upstream bugtracker to allow souphttpsrc to work as live source and have it provide timestamps. -- Sebastian Dröge Wed, 22 Oct 2008 11:08:03 +0200 gst-plugins-good0.10 (0.10.10.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Sat, 18 Oct 2008 10:23:40 +0200 gst-plugins-good0.10 (0.10.10.2-1) experimental; urgency=low * New upstream pre-release: + debian/rules, debian/build-deps.in: - Build depend on gstreamer and gst-plugins-base >= 0.10.21. + debian/build-deps.in: - Build depend on FLAC >= 1.1.3. - Build depend on bzip2. -- Sebastian Dröge Sat, 11 Oct 2008 15:42:49 +0200 gst-plugins-good0.10 (0.10.10-1) experimental; urgency=low * New upstream release, 'Barely Moving'. -- Sebastian Dröge Thu, 28 Aug 2008 10:29:42 +0200 gst-plugins-good0.10 (0.10.9.2-1) experimental; urgency=low * New upstream pre-release: + debian/build-deps.in, debian/gstreamer-plugins-good.install: - Remove the cdio plugin, it moved to gst-plugins-ugly because libcdio is GPL licensed. * debian/rules: + Build depend on gstreamer >= 0.10.20-3 for the new virtual package names. * debian/control.in: + Wrap control fields. + Depend on gstreamer0.10-plugins-base as some plugins need it. -- Sebastian Dröge Fri, 15 Aug 2008 09:28:10 +0200 gst-plugins-good0.10 (0.10.9-2) experimental; urgency=low * debian/control.in, debian/rules: + Use new automatic codec installation infrastructure. -- Sebastian Dröge Sat, 09 Aug 2008 16:56:46 +0200 gst-plugins-good0.10 (0.10.9-1) experimental; urgency=low * New upstream release, 'Steam Train Rolling'. -- Sebastian Dröge Fri, 01 Aug 2008 11:32:53 +0200 gst-plugins-good0.10 (0.10.8.4-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Tue, 29 Jul 2008 12:01:45 +0200 gst-plugins-good0.10 (0.10.8.3-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Sat, 26 Jul 2008 12:34:57 +0200 gst-plugins-good0.10 (0.10.8.2-1) experimental; urgency=low * New upstream pre-release: + debian/patches/10_speex_caps.patch, debian/patches/11_rtsp_fdleak.patch, debian/patches/12_matroskamux_track_duration.patch, debian/patches/13_equalizer.patch: - Dropped, merged upstream. + debian/build-deps.in, debian/control.in, debian/gstreamer-pulseaudio.install, debian/rules: - Add the pulseaudio plugin. + debian/gstreamer-plugins-good.install, debian/control.in: - Add the interleave and replaygain plugins. + debian/build-deps.in, debian/rules: - Update gstreamer, gst-plugins-base and libcdio build dependencies. * debian/control.in: + Depend on gstreamer0.10-audiosink instead of a specific audiosink plugin (Closes: #482796). * debian/patches/03_pulse-rank.patch: + Update the rank of the pulse elements to PRIMARY+10. * debian/control.in: + Update Standards-version to 3.8.0, no additional changes needed. -- Sebastian Dröge Sun, 20 Jul 2008 11:59:00 +0200 gst-plugins-good0.10 (0.10.8-4) unstable; urgency=low * debian/patches/13_equalizer.patch: + Fix clipping in integer mode, correctly implement passthrough mode if all bands have a gain of 0dB and delay filter coefficient calculation until they're really needed. Patch from upstream CVS. -- Sebastian Dröge Mon, 02 Jun 2008 13:44:41 +0200 gst-plugins-good0.10 (0.10.8-3) unstable; urgency=low * debian/patches/11_rtsp_fdleak.patch - Added. Fix filedescriptor leak on errors. (From upstream CVS) * debian/patches/12_matroskamux_track_duration.patch - Added. Fix track time calculation when muxing matroska files (From upstream CVS) -- Sjoerd Simons Sun, 01 Jun 2008 16:15:48 +0200 gst-plugins-good0.10 (0.10.8-2) unstable; urgency=low * debian/patches/10_speex_caps.patch - Added. Fix speexenc and rtpspeexpay caps negotiation (From gnome bugzilla #465146) -- Sjoerd Simons Fri, 25 Apr 2008 21:51:20 +0200 gst-plugins-good0.10 (0.10.8-1) unstable; urgency=low * New upstream bugfix release, 'One For The Money'. -- Sebastian Dröge Thu, 24 Apr 2008 07:41:30 +0200 gst-plugins-good0.10 (0.10.7.4-1) experimental; urgency=low * New upstream pre-release. -- Sebastian Dröge Tue, 22 Apr 2008 10:28:29 +0200 gst-plugins-good0.10 (0.10.7.3-1) experimental; urgency=low * New upstream pre-release: + debian/patches/01_goom-missing-header.patch: - Dropped, merged upstream. + debian/build-deps.in: - flex and bison are not necessary anymore. -- Sebastian Dröge Fri, 18 Apr 2008 10:35:19 +0200 gst-plugins-good0.10 (0.10.7.2-2) experimental; urgency=low * debian/patches/02_no-Werror.patch: + Don't build with -Werror to fix FTBFS on some architectures. -- Sebastian Dröge Tue, 15 Apr 2008 05:29:28 +0200 gst-plugins-good0.10 (0.10.7.2-1) experimental; urgency=low * New upstream pre-release: + debian/control.in: - Adjust conflicts for gst-plugins-bad because of moved plugins. + debian/rules, debian/build-deps.in: - Update build dependencies. + debian/gstreamer-plugins-good.install: - Ship soup and goom2k1 plugins. + debian/rules: - Set default audio/video sinks/srcs depending on the platform. + debian/patches/02_v4l2_default.patch, debian/patches/05_speexenc_double_unref.patch, debian/patches/75_build_docs_without_python_xml.patch, debian/patches/80_unit-tests.patch: - Dropped, merged upstream. + debian/patches/01_goom-missing-header.patch: - Add missing header file that was forgotten. -- Sebastian Dröge Mon, 14 Apr 2008 10:59:00 +0200 gst-plugins-good0.10 (0.10.7-3) unstable; urgency=low * debian/patches/05_speexenc_double_unref.patch: + Added. Don't unref a buffer twice when hitting a not-negotiated error in speexenc (from upstream CVS) (Closes: #472096) -- Sjoerd Simons Sat, 22 Mar 2008 02:35:15 +0100 gst-plugins-good0.10 (0.10.7-2) unstable; urgency=low * debian/patches/02_v4l2_default.patch: + Patch by Mario Limonciello to use v4l2 as default video source instead of the nowadays deprecated v4l (Closes: #468073). * debian/patches/75_build_docs_without_python_xml.patch, debian/build-deps.in: Stop using pyxml for building the docs (Closes: #468630). -- Sebastian Dröge Tue, 11 Mar 2008 05:14:10 +0100 gst-plugins-good0.10 (0.10.7-1) unstable; urgency=low * New upstream release, "Red Door Black": + debian/patches/01_linking-fixes.patch: - Dropped, merged upstream. -- Sebastian Dröge Thu, 21 Feb 2008 10:48:23 +0100 gst-plugins-good0.10 (0.10.6.4-1) experimental; urgency=low * New upstream pre-release. * debian/patches/99_ltmain_as-needed.patch, debian/rules: + Add -Wl,-z,defs -Wl,-O1 -Wl,--as-needed to LDFLAGS to remove some unnecessary dependencies on various packages. * debian/patches/01_linking-fixes.patch: + Link gstalpha with libgstbase. * debian/build-deps.in: + Build depend on libxv-dev to get Xv support in ximagsink. -- Sebastian Dröge Tue, 19 Feb 2008 06:54:55 +0100 gst-plugins-good0.10 (0.10.6.3-1) experimental; urgency=low * New upstream pre-release. * debian/build-deps.in: + Build depend on gstreamer0.10-plugins-base for the unit tests. * debian/patches/80_unit-tests.patch: + Disable gconfaudiosrc for the generic/states unit test. -- Sebastian Dröge Thu, 14 Feb 2008 13:14:55 +0100 gst-plugins-good0.10 (0.10.6.2-1) experimental; urgency=low [ Loic Minier ] * Bump up type-handling build-dep to >= 0.2.14 and call it with two arguments again. [ Emilio Pozuelo Monfort ] * debian/rules: - Decide the package name and url depending on the distribution. * debian/build-deps.in: - Build-Depend on lsb-release. [ Sebastian Dröge ] * New upstream pre-release: + Fixes FTBFS if built twice in a row (Closes: #424398). + Fixes playback of realaudio streams (Closes: #430364). + Fixes switching of sink after song changes (Closes: #444769). + debian/gstreamer-plugins-good.install: - Add new equalizer, spectrum and multifile plugins and sort alphabetically. + debian/build-deps.in, debian/rules: - Update liboil and gstreamer build dependencies. + debian/patches/20_gconf-state-change.patch, debian/patches/30_id3_gst_tag_defines.patch: - Dropped, merged upstream. + debian/control.in: - Update Replaces on gst-plugins-bad for the plugin moves. - Update Standards-Version to 3.7.3, no additional changes needed. * debian/rules: + Run the unit test suite but don't fail the build on failures. -- Sebastian Dröge Sat, 09 Feb 2008 12:11:21 +0100 gst-plugins-good0.10 (0.10.6-4) unstable; urgency=low * debian/patches/30_id3_gst_tag_defines.patch: + Added. Use GST_TAG_ARTIST_SORTNAME instead of the deprecated GST_TAG_MUSICBRAINZ_SORTNAME (Closes: #452671) + Also add support for GST_TAG_ALBUM_SORTNAME, GST_TAG_TITLE_SORTNAME and GST_TAG_COMPOSER + Based on the current CVS version of gstid3v2mux -- Sjoerd Simons Sat, 24 Nov 2007 22:07:19 +0100 gst-plugins-good0.10 (0.10.6-3) unstable; urgency=low * debian/rules: + Set GST_REGISTRY before the dh_gstscancodecs call to save the registry somewhere on buildds without writable home and speed things up a bit. * debian/build-deps.in: + Remove check from build dependencies. This is only an indirect build dependency that is already satisfied by libgstreamer0.10-dev. configure only checks for libgstcheck, not check. * debian/patches/20_gconf-state-change.patch: + Fix errors while changing the state of the gconf sink. Patch from upstream CVS, see http://bugzilla.gnome.org/show_bug.cgi?id=471364 . -- Sebastian Dröge Tue, 25 Sep 2007 15:31:31 +0200 gst-plugins-good0.10 (0.10.6-2) unstable; urgency=low * debian/control.in: + Add Replaces on gstreamer0.10-plugins-really-bad (<< 0.10.4.2), which is maintained in the Debian Multimedia project and also contained the WavPack plugin. * debian/build-deps.in, debian/rules: + Call dh_gstinstallcodecs to generate the codecs database. * debian/gstreamer-plugins-good.install, debian/gstreamer-plugins-good-doc.install, debian/control.in: + Move translations from the docs package to the plugin package. -- Sebastian Dröge Fri, 31 Aug 2007 09:27:06 +0200 gst-plugins-good0.10 (0.10.6-1) unstable; urgency=low * New upstream release, "Wobble Board": + Fixes muxing of raw audio in Matroska files (Closes: #360536). + Fixes memory leak in cutter and level plugins (Closes: #425114). + debian/patches/20_gstavidemux-error-out-on-pull_range.patch, debian/patches/30_speex-rtp-fixes.patch, debian/patches/40_flac1.1.3.patch, debian/patches/99_autoreconf.patch: - Dropped, merged upstream. + debian/build-deps.in, debian/rules: - Require gstreamer and gst-plugins-base >= 0.10.13. + debian/gstreamer-plugins-good.install, debian/build-deps.in, debian/control.in: - Add wavpack, qtdemux, videocrop, monoscope and gamma plugins. For this add Replaces on gstreamer0.10-plugins-bad (<< 0.10.4.2). -- Sebastian Dröge Tue, 19 Jun 2007 19:28:52 +0200 gst-plugins-good0.10 (0.10.5-7) unstable; urgency=low * debian/control.in: + Use ${binary:Version} instead of ${Source-Version} to make lintian happy. * debian/patches/40_flac1.1.3.patch: + Patch from upstream CVS to work with flac >= 1.1.3. (Closes: #427744, #426647). http://bugzilla.gnome.org/show_bug.cgi?id=385887 * debian/patches/99_autoreconf.patch: + Regenerate configure for the above change. -- Sebastian Dröge Sun, 10 Jun 2007 22:58:34 +0200 gst-plugins-good0.10 (0.10.5-6) unstable; urgency=low * debian/patches/30_speex-rtp-fixes.patch + Added. Fix the speex rtp payloader and depayloader. (From upstream CVS) -- Sjoerd Simons Thu, 10 May 2007 13:27:36 +0200 gst-plugins-good0.10 (0.10.5-5) unstable; urgency=low * Upload to unstable * Merge experimental branch: [ Loic Minier ] + Build-depend on libpng12-dev instead of libpng12-0-dev to get the libpng12.pc file. [ Sebastian Dröge ] + New upstream release 0.10.5, "The Path of Thorns": - Fixes playback of some internet radio streams (Closes: #384377) - debian/patches/12_gstcacasink-header-include.patch, debian/patches/13_separate-handle-for-avc-ops.patch: . Dropped, merged upstream - debian/rules, - debian/build-deps.in: . Build depend on gstreamer / gst-plugins-base >= 0.10.10.1 - debian/gstreamer-plugins-good.install: . Add audiofx plugin + debian/patches/11_esdsink-priority.patch: - Dropped, priorities are cached in the gst registry, thus this patch had almost no effect. This should somehow be solved by a determined priority order for sinks + debian/control: - Updated to use my debian.org mail address + debian/control, debian/gstreamer-plugins-good.install: - Only build the video4linux2 plugin on Linux. Fixes FTBFS everywhere else + debian/build-deps.in, debian/rules: - Use type-handling to only depend on libraw1394 and friends on Linux [ Sjoerd Simons ] + Enable experimental plugins (the v4l2src plugin) + Conflict with gstreamer0.10-plugins-bad < 0.10.4 which used to provide the v4l2src plugin. + Add myself to uploaders * debian/patches/20_gstavidemux-error-out-on-pull_range.patch: + Updated for the new upstream version -- Sebastian Dröge Tue, 10 Apr 2007 20:34:53 +0200 gst-plugins-good0.10 (0.10.4-3) unstable; urgency=high * New patch, 13_separate-handle-for-avc-ops, to use a separate handle on raw1394 for AVC operations. * Urgency high as the dv plugin is unusable without this patch. * Merge 0.10.4-2. -- Loic Minier Wed, 25 Oct 2006 16:07:07 +0200 gst-plugins-good0.10 (0.10.4-2) experimental; urgency=low * Re-add -dbg package and target at experimental. -- Loic Minier Fri, 13 Oct 2006 12:27:45 +0200 gst-plugins-good0.10 (0.10.4-1) unstable; urgency=low [ Loic Minier ] * New patch, 11_esdsink-priority, taken from Ubuntu to include esdsink in the candidates of autoaudiosink. (Closes: #373703) [ Sebastian Dröge ] * New upstream release, "Dear Leader". - Fixes reading and parsing of some id3v2 tags. (Closes: #361310) - Tries esdsink but does not autospawn esound. (Closes: #361841) - Fixes crash of dv1394src by using a separate handle for AVC operations. (Closes: #369936) - debian/patches/10_fix-h263-caps.patch: + dropped, merged upstream - debian/patches/11_esdsink-priority.patch: + Updated, partially upstream - debian/rules: + Require libraw1394-dev (>= 1.2.1) and libiec61883-dev (>= 1.0.0) * debian/control.in: + Added myself to Uploaders * debian/gstreamer-plugins-good.install: + Remove the duplicated entries for the dv1394 element * debian/compat, debian/build-deps.in: + Update to debhelper compat level 5 * debian/rules, debian/control.in: + Add a -dbg package [ Loic Minier ] * Rename patch 11_gstcacasink-header-include to 12_gstcacasink-header-include. * Drop -dbg package for now, this version is for etch. -- Loic Minier Fri, 13 Oct 2006 12:13:08 +0200 gst-plugins-good0.10 (0.10.3-3) unstable; urgency=medium * New patch, 11_gstcacasink-header-include, to fix building of the libcaca plugin; thanks Sam Hocevar. (Closes: #386169) -- Loic Minier Sat, 9 Sep 2006 20:32:43 +0200 gst-plugins-good0.10 (0.10.3-2) unstable; urgency=low * Bump up Standards-Version to 3.7.2. [debian/control, debian/control.in] * New patch from upstream to fix caps of H263 RTP streams, thanks Paul van Tilburg. [debian/patches/10_fix-h263-caps.patch] * Export OIL_CPU_FLAGS=0 for commands launched during the build process as it can cause build failures on buildds with specific hardware at build time. [debian/rules] -- Loic Minier Wed, 17 May 2006 23:05:56 +0200 gst-plugins-good0.10 (0.10.3-1) unstable; urgency=low * New upstream release, "Desplazado". - Bump libgstreamer0.10-dev build-dep to 0.10.4.1. [debian/control, debian/rules] - Bump libgstreamer-plugins-base0.10-dev build-dep to 0.10.5.1. [debian/build-deps.in, debian/build-deps, debian/control] - New ximagesrc plugin. . Add libxdamage-dev, libxext-dev, and libxfixes-dev build-deps. [debian/build-deps.in, debian/build-deps, debian/control] . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New annodex plugin. . Add libxml2-dev build-dep. [debian/build-deps.in, debian/build-deps, debian/control] . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New gdkpixbuf plugin. . Add libgtk2.0-dev build-dep. [debian/build-deps.in, debian/build-deps, debian/control] . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New halelements plugin. . Add libdbus-1-dev (>= 0.32) and libhal-dev (>= 0.5.6) build-deps. [debian/build-deps.in, debian/build-deps, debian/control] . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New taglib plugin. . Add libtag1-dev build-dep. [debian/build-deps.in, debian/build-deps, debian/control] . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New videobalance plugin, installed in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] - New icydemux plugin. . Install in gstreamer-plugins-good. [debian/gstreamer-plugins-good.install] -- Loic Minier Sat, 6 May 2006 11:52:57 +0200 gst-plugins-good0.10 (0.10.2-2) unstable; urgency=low * Depend on the alsa package instead of recommending it, since package managers don't honor Recommends: in all cases. (Closes: #352212) [debian/control, debian/control.in] -- Loic Minier Thu, 16 Feb 2006 14:45:38 +0100 gst-plugins-good0.10 (0.10.2-1) unstable; urgency=low * New upstream release, "Papa was a rolling stone". -- Loic Minier Tue, 14 Feb 2006 10:44:13 +0100 gst-plugins-good0.10 (0.10.1.2-1) unstable; urgency=low * New upstream pre-release. - Bump up libgstreamer0.10-dev build-dep to >= 0.10.2.2. [debian/control, debian/rules] - Bump up libgstreamer-plugins-base0.10-dev to >= 0.10.2.2. [debian/build-deps.in, debian/build-deps, debian/control] - Add apetag plugin. . Install plugin. [debian/gstreamer-plugins-good.install] - Add cdio plugin. . Add libcdio-dev >= 0.71 build-dep. . Install plugin. [debian/build-deps, debian/build-deps.in, debian/control, debian/gstreamer-plugins-good.install] * Use upstream descriptions in packages descriptions. [debian/control, debian/control.in] -- Loic Minier Sat, 11 Feb 2006 17:30:38 +0100 gst-plugins-good0.10 (0.10.1-2) unstable; urgency=low * Recommend gstreamer0.10-alsa and gstreamer0.10-x, as these are used by default upstream -- in the GConf schemas -- and used from the autodetect audio and videosinks. [debian/control, debian/control.in] -- Loic Minier Sun, 5 Feb 2006 15:08:53 +0100 gst-plugins-good0.10 (0.10.1-1) unstable; urgency=low * New upstream release, "Li". - Bump libgstreamer-plugins-base0.10-dev build-dep to >= 0.10.1. [debian/build-deps.in, debian/control] - Bump libgstreamer0.10-dev build-dep to >= 0.10.1. [debian/control, debian/rules] - Add id3demux plugin. [debian/gstreamer-plugins-good.install] - Add translated strings from /usr/share/locale. [debian/gstreamer-plugins-good.install] * Drop useless gst_plugins_base_lib_dev_dep, gst_plugins_base_lib_dev, gst_plugins_base_lib, and gst_plugins_good_version definitions. [debian/rules] -- Loic Minier Sun, 15 Jan 2006 18:04:49 +0100 gst-plugins-good0.10 (0.10.0-1) unstable; urgency=low [ Sebastien Bacher ] * New package: - clean patches [debian/patches/50_cdparanoia-fix-eos-detection-of-last-title.patch] - updated Build-Depends [debian/build-deps.in] - updated documentation [debian/README.Debian, debian/TODO.Debian] - updated packages list [debian/control.in, debian/gstreamer-alsa.install, debian/gstreamer-gnomevfs.install, debian/gstreamer-misc.install, debian/gstreamer-plugins-base-apps.instal, debian/gstreamer-plugins-base-apps.manpages, debian/gstreamer-plugins-base-doc.install, debian/gstreamer-x.install, debian/libgstreamer-plugins-base-dev.install, debian/libgstreamer-plugins-base.install, debian/gstreamer-aa.install, debian/gstreamer-auto.install, debian/gstreamer-caca.install, debian/gstreamer-esd.install, debian/gstreamer-misc-good.install, debian/gstreamer-oss.install, debian/gstreamer-plugins-good-doc.install, debian/rules] - updated upstream location [debian/watch] - don't use type-handling it's not useful [debian/build-deps.in, debian/control.in, debian/rules] [ Loic Minier ] * Merge aa, auto, caca, and oss packages and plugins in misc and let it provide audio and videosinks; use a couple of @GST_ABI@s where appropriate. [debian/control, debian/control.in, debian/gstreamer-aa.install, debian/gstreamer-caca.install, debian/gstreamer-misc-good.install, debian/gstreamer-oss.install, debian/rules] * Rename for good misc-good in good. [debian/control debian/control.in debian/gstreamer-misc-good.install debian/rules] * Downgrade cdbs build-dep for Debian. [debian/build-deps, debian/build-deps.in, debian/control] * Rename gstreamer0.10-good in gstreamer0.10-plugins-good. [debian/control, debian/control.in, debian/gstreamer-good.install, debian/gstreamer-plugins-good.install, debian/rules] * Add a build-dep on check. [debian/build-deps, debian/build-deps.in, debian/control] * Remove python-twisted build-dep. [debian/build-deps, debian/build-deps.in, debian/control] -- Loic Minier Wed, 21 Dec 2005 17:37:46 +0100 gst-plugins-base0.10 (0.10.0-1) unstable; urgency=low [ Sebastien Bacher ] * New package: - build the documentation [debian/rules] - no action needed to register the plugins with the new version [debian/gstreamer-plugin-template.postinst, debian/gstreamer-plugin-template.postrm, debian/libgstreamer-plugins.postinst, debian/libgstreamer-plugins.postrm, debian/rules] - drop mechanism to build extra packages for other distributions around, it's not useful for base [debian/extras, debian/rules] - drop transitionnal workaround [debian/gstreamer-plugin-template.preinst] - new gstreamer-plugin-base-doc package [debian/control.in, debian/rules] - remove obsolete patches [debian/patches/10_wavpack-high-quality-segfault.patch, debian/patches/30_alsa-verify-accepted-period-size.patch, debian/patches/31_alsa-advanced-probing.patch, debian/patches/40_audioscale-timestamps-and-durations.patch, debian/patches/50_ladspa-quiet.patch] - updated the Build-Depends [debian/build-dep.in] - updated the packages descriptions [debian/control.in] - updated the packages names/list [debian/control.in, debian/gstreamer-a52dec.install, debian/gstreamer-aa.install, debian/gstreamer-artsd.install, debian/gstreamer-audiofile.install, debian/gstreamer-avifile.install, debian/gstreamer-caca.install, debian/gstreamer-cdio.install, debian/gstreamer-dvd.install, debian/gstreamer-dv.install, debian/gstreamer-esd.install, debian/gstreamer-festival.install, debian/gstreamer-flac.install, debian/gstreamer-gsm.install, debian/gstreamer-gtk.install, debian/gstreamer-hermes.install, debian/gstreamer-jpeg.install, debian/gstreamer-lame.install, debian/gstreamer-mad.install, debian/gstreamer-mikmod.install, debian/gstreamer-mms.install, debian/gstreamer-mpeg2dec.install, debian/gstreamer-oss.install, debian/gstreamer-sdl.install, debian/gstreamer-sid.install, debian/gstreamer-speex.install, debian/gstreamer-swfdec.install, debian/libgstreamer-gconf.install, debian/libgstreamer-gconf-dev.install, debian/gstreamer-vorbis.install, debian/rules] - updated the packages lists [debian/gstreamer-misc.install, debian/libgstreamer-plugins-base-dev.install, debian/libgstreamer-plugins-base.install] - updated packages content [debian/gstreamer-misc.install, debian/gstreamer-x.install] - updated the version [debian/rules] - updated watch file [debian/watch] [ Loic Minier ] * Minor cleanups. [debian/rules] * Add Sebastien Bacher to Uploaders. [debian/control, debian/control.in] -- Loic Minier Sat, 17 Dec 2005 18:11:03 +0100 gst-plugins0.8 (0.8.11-3) unstable; urgency=medium * New CDIO plugin package. . Add libcdio-dev build-dep for CDIO support. [debian/build-deps.in, debian/build-deps, debian/control] . Add package description and file listing. [debian/gstreamer-cdio.install, debian/control.in, debian/control] . List package in plugins build-list. [debian/rules] . List package in gstreamer-plugins deps. [debian/control.in, debian/control] * New MMS plugin package. (Closes: #301246) . Add libmms-dev build-dep for mms:// and mmsh:// support. [debian/build-deps.in, debian/build-deps, debian/control] . Add package description and file listing. [debian/gstreamer-cdio.install, debian/control.in, debian/control] . List package in plugins build-list. [debian/rules] . List package in gstreamer-plugins deps. [debian/control.in, debian/control] * Fix the homepage of the mikmod page and stop linking to an adult web site. [debian/control, debian/control.in] * Remove "Section: libs" from binary packages. [debian/control, debian/control.in] * Exit with non-zero code when requesting an unknown plugin. [debian/extra] * Add sample code to permit other distros to build additional plugins. [debian/rules] * Minor cleanups. [debian/rules] * New Gtk / Gdk package to split out this dep-tree for KDE folks, from Ubuntu, thanks Sebastien Bacher. . Add package description and file listing, remove it from -misc. [debian/control.in, debian/control, debian/gstreamer-gtk.install, debian/gstreamer-misc.install] . List package in plugins build-list. [debian/rules] . List package in gstreamer-plugins deps. [debian/control.in, debian/control] . Only "Replace" with -misc in the first version doing the split (0.8.11-0ubuntu3), no Conflict needed. * Add wavpack support, build the wavpack plugin and ship it in -misc. (Closes: #339598) . Add a libwavpack-dev build-dep. [debian/build-deps, debian/build-deps.in] . Ship plugin in -misc. [debian/gstreamer-misc.install] . Adjust the size of the internal decode buffer dynamically instead of assuming 0.5 seconds are enough; fixes a segfault when playing files encoded with -h; upstream bug: #317774; fix committed in branch BRANCH-GSTREAMER-0_8. [debian/patches/10_wavpack-high-quality-segfault.patch] * Backport some interesting upstream fixes from CVS fixing most resampling issues for non-standard bitrates and for complex ALSA configurations (especially dmix), thanks Tim-Philipp Müller, Luca Ognibene, and others. (Closes: #323447, #324163, #326135, #340038) - When doing _set_period_size_near(), see what period size was actually set in the end and continue working with that value instead of just assuming the desired period size was accepted; upstream bug #318767; fix committed in branch BRANCH-GSTREAMER-0_8. [debian/patches/30_alsa-verify-accepted-period-size.patch] - Don't mess up timestamps and durations when resampling by more than a factor of 2 (e.g. 8kHz => 48kHz); upstream bug #318273; fix committed in branch BRANCH-GSTREAMER-0_8. [debian/patches/40_audioscale-timestamps-and-durations.patch] - When the default device is being used, try to probe the caps of the underlying device instead if possible. This should give more narrowly defined caps that are closer to the hardware's capabilities. This is enabled by default, but can be switched off via the new 'advanced-probing' property; upstream bug #315121; fix committed in branch BRANCH-GSTREAMER-0_8. [debian/patches/31_alsa-advanced-probing.patch] * Drop the polypaudio plugin package. (Closes: #342278) . Drop libpolyp-dev (>= 0.7) build-dep. [debian/build-deps.in, debian/build-deps, debian/control] . Remove package description and file listing. [debian/gstreamer-polypaudio.install, debian/control.in, debian/control] . Remove package from plugins build-list. [debian/rules] . Remove package from gstreamer-plugins deps. [debian/control.in, debian/control] * Add libgconf2-dev, libglib2.0-dev, liborbit2-dev, libpopt-dev, libxml2-dev deps to libgstreamer-gconf0.8-dev as listed in its .la files. [debian/control, debian/control.in] * Add libglib2.0-dev, libpopt-dev, libxml2-dev deps to libgstreamer-plugins0.8-dev as listed in its .la files. [debian/control, debian/control.in] * Fix EOS detection for last title (fixes gnome-cd hanging after last track ends), thanks Gustavo Noronha Silva; upstream bug #317630; fix committed in branch BRANCH-GSTREAMER-0_8. (Closes: #330954) [debian/patches/50_cdparanoia-fix-eos-detection-of-last-title.patch] * Don't overwrite DEB_CONFIGURE_EXTRA_FLAGS. [debian/rules] -- Loic Minier Sun, 11 Dec 2005 14:52:38 +0100 gst-plugins0.8 (0.8.11-2) unstable; urgency=high * Add misc:depends to all binary packages. (Closes: #329759) -- Loic Minier Mon, 17 Oct 2005 21:41:54 +0200 gst-plugins0.8 (0.8.11-1) unstable; urgency=low * Override gstreamer-dv section to extra because it depends on libavc1394-0 which is in extra. [debian/control, debian/control.in] * Bump libflac-dev build-dependency for the latest flac soname change (libflac6 -> libflac7). (Closes: #325940) * New upstream release, "... And Thanks For All The Fix". - New plugin imagemixer. [debian/gstreamer-misc.install] - New plugin dvdsubdec. [debian/gstreamer-misc.install] - Drop obsolete artsd patch, merged upstream. [debian/patches/25_artds-no-name.patch] * Update FSF address. [debian/copyright] * Add cairo plugin. [debian/build-deps, debian/build-deps.in, debian/control, debian/gstreamer-misc.install] -- Loic Minier Sun, 4 Sep 2005 21:19:47 +0200 gst-plugins0.8 (0.8.10-3) unstable; urgency=high * Urgency high because this fixes some RC bugs and 0.8.10-2 was caught in the C++ transition. * Bump up build-deps to get versions past the C++ transition. [debian/build-deps, debian/build-deps.in, debian/control] - libsdl1.2-dev - libsidplay1-dev (Closes: #321315) - libarts1-dev, libartsc0-dev * Change X11 build-deps for the Xorg transition. [debian/build-deps, debian/build-deps.in, debian/control] - remove xlibs-dev and xlibs-pic. - add libx11-dev, libxext-dev, and libxv-dev. * Drop jack plugin. [debian/build-deps, debian/build-deps.in, debian/control, debian/control.in, debian/gstreamer-jack.install, debian/rules] (Closes: #321648) * Add libgstglimagesink plugin. [debian/gstreamer-x.install] * Add libgstfreeze plugin. (Closes: #318146) [debian/gstreamer-misc.install] * Let libgstreamer-gconf suggest gnome-media. (Closes: #294490) [debian/control, debian/control.in] -- Loic Minier Fri, 12 Aug 2005 18:36:58 +0200 gst-plugins0.8 (0.8.10-2) unstable; urgency=medium [ Sebastien Bacher ] * debian/build-deps.in: - list libpolyp-dev. * debian/control.in: - gstreamer0.8-plugins depends on gstreamer0.8-polypaudio. - gstreamer0.8-polypaudio description. * gstreamer-polypaudio.install: - install libpolypaudio.so. * debian/rules: - list polypaudio. [ Loic Minier ] * Update jack dependency. (Closes: #317197) [debian/build-deps, debian/build-deps.in, debian/control] * Update aalib dependency. (Closes: #320886) [debian/build-deps, debian/build-deps.in, debian/control] * Urgency medium because of RC. -- Loic Minier Sun, 10 Jul 2005 19:27:08 +0200 gst-plugins0.8 (0.8.10-1) unstable; urgency=low * Loic Minier: * New upstream release "Jangle". - Bump inter-dependencies to >= 0.8.10. * Use upstream fix for the "name" property of the artsdsink element. [debian/patches/25_artds-no-name.patch] -- Loic Minier Sat, 2 Jul 2005 21:04:40 +0200 gst-plugins0.8 (0.8.9-2) unstable; urgency=medium * Loic Minier: * Urgency medium as last gstreamer0.8 release exposed an artsdsink problem fixed here. * Add a versioned dependency with >= current-upstream-version to all current shlibs inter-dependencies to ensure consistency of symbols. (Closes: #315556) [debian/control, debian/control.in, debian/rules] * Remove the "name" property in the artsd plugin as it interferes with the usage of this property within GStreamer. (Closes: #314762) [debian/patches/25_artds-no-name.patch] * Call gst-register and gst-compprep with GST_REGISTRY in their environment to override the default behavior of writing to /root/.gstreamer-0.8, waiting for an upstream fix. [debian/gstreamer-plugin-template.postinst, debian/gstreamer-plugin-template.postrm] * Add a postinst/postrm snipset to register plugins in libgstreamer-plugins0.8. (Closes: #283658) [debian/libgstreamer-plugins.postrm, debian/libgstreamer-plugins.postinst, debian/changelog, debian/rules] * Remove left over /root/.gstreamer-0.8 tree if it hasn't been modified. [debian/gstreamer-plugin-template.preinst, debian/rules] * Set Maintainer to group. [debian/control, debian/control.in] * Bump Standards-Version to 3.6.2, no change needed. [debian/control, debian/control.in] * Workaround type-handling bug #315761. [debian/control, debian/control.in, debian/rules] -- Loic Minier Mon, 27 Jun 2005 15:44:35 +0200 gst-plugins0.8 (0.8.9-1) unstable; urgency=low * Loic Minier: * New upstream release "Old Hat". - Fix SIGFPE in alsasrc. (Closes: #279399) - New Musepack plugin package. . Add libmpcdec-dev build-dep for Musepack support, this is now detected in a Debian compatible manner via mpcdec/mpcdec.h and -lmpcdec. [debian/build-deps.in, debian/build-deps, debian/control] . Add package description and file listing. [debian/gstreamer-musepack.install, debian/control.in, debian/control] . List package in plugins build-list. [debian/rules] . List package in gstreamer-plugins deps. [debian/control.in, debian/control] - Renamed and updated "ladspa" patch. [debian/patches/ladspa-quiet.patch, debian/patches/50_ladspa-quiet.patch] - Add video4linuxradio plugin to the gstreamer-misc package. [debian/rules] * Add compatibility block for older dpkg and use DEB_HOST_ARCH_OS. * Add myself as uploader. [debian/control.in, debian/control] * Fix indentation and executable permission of package maintaining helpers. [debian/extra, debian/maint, debian/mk.control] * Remove more bits from the arts plugin, left over in 0.8.1-2. [debian/extra, debian/gstreamer-arts.install] * Document the role of most files. [debian/HACKING.Debian] * Add a TODO list for the Debian package. [debian/TODO.Debian] -- Loic Minier Tue, 21 Jun 2005 07:47:04 +0200 gst-plugins0.8 (0.8.8-3) unstable; urgency=low * debian/build-deps.in: * Bump liboil dependency to liboil0.3-dev. gst-plugins doesn't yet use 0.3 itself but the swfdec plugin build requires it. -- David I. Lehn Wed, 30 Mar 2005 19:08:07 -0500 gst-plugins0.8 (0.8.8-2) unstable; urgency=low * debian/control.in: * gstreamer0.8-vorbis depends on gstreamer0.8-misc >= 0.8.8-1 (Closes: #300082, #299954, #299921) -- David I. Lehn Thu, 17 Mar 2005 12:53:38 -0500 gst-plugins0.8 (0.8.8-1) unstable; urgency=low * New upstream * debian/patches/flac.patch: * Remove: included upstream * debian/gstreamer-misc.install: * Add new plugins: autodetect, dvdlpcmdec, puzzle, rfbsrc, subparse * Add new plugins with lib dependencies: gconfelements, mng, shout2 (Closes: #292011) * debian/build-deps.in: * Add: libmng-dev, libshout3-dev * Upstream fixes: * Xv falls back to X11 (Closes: #296848) * Better audio sink selection (Closes: #284210) * alsasink crash (Closes: #296751) -- David I. Lehn Wed, 16 Mar 2005 01:00:39 -0500 gst-plugins0.8 (0.8.7-3) unstable; urgency=low * debian/rules, debian/build-deps.in * Apply additional kfreebsd-gnu patch (Closes: #272568) * debian/patches/flac.patch: * Upstream fix for FLAC API change (Closes: #290784) -- David I. Lehn Mon, 17 Jan 2005 17:53:42 -0500 gst-plugins0.8 (0.8.7-2) unstable; urgency=low * debian/rules: * Bump libgstreamer0.8-dev dependency to >= 0.8.7.1 -- David I. Lehn Sat, 8 Jan 2005 15:50:45 -0500 gst-plugins0.8 (0.8.7-1) unstable; urgency=low * New upstream * debian/build-deps.in, debian/control.in, debian/rules, debian/gstreamer-dv.install, debian/gstreamer-misc.install: * Patch build system for kfreebsd-gnu (Closes: #272568) * debian/rules: * Distribute NEWS (as requested in #275717) * debian/gstreamer-misc.install: * Add new plugins: apetag, tta * debian/patches/configure-speex.patch: * Remove patch, included upstream * debian/build-deps.in: * Build against latest libflac-dev -- David I. Lehn Fri, 7 Jan 2005 13:52:27 -0500 gst-plugins0.8 (0.8.6-1) unstable; urgency=low * New upstream * Rhythmbox hangs/crashes fixes (Closes: #245757, #277146) * Rhythmbox silence after resume play fixes (Closes: #261841) * Ogg seeking fixes (Closes: #277396) * debian/build-deps.in: * Bump libswfdec to libswfdec0.3-dev * Add libavc1394-dev * Add liboil0.2-dev * Versioned libspeex-dev * debian/gstreamer-misc.install: * Add new plugin: equalizer * debian/watch: * Add watch file * debian/rules: * Update GStreamer core dev dependency to 0.8.4 * ext/ladspa/gstladspa.c, debian/patches/ladspa-quiet.patch: * Move patch to debian/patches/ -- David I. Lehn Mon, 29 Nov 2004 04:02:43 -0500 gst-plugins0.8 (0.8.5-1) unstable; urgency=low * New upstream * debian/rules: * Use CDBS simple-patchsys * debian/control: to debian/control:: for newer CDBS * debian/patches/configure-speex.diff: * Fix speex detection -- David I. Lehn Wed, 6 Oct 2004 20:15:52 -0400 gst-plugins0.8 (0.8.4-1) unstable; urgency=low * New upstream * debian/control.in: * Add -theora package to -plugins package * debian/rules: * Drop upstream ChangeLog from plugin packages due to size * debian/README.Debian: * Add note about ChangeLog -- David I. Lehn Thu, 2 Sep 2004 23:03:44 -0400 gst-plugins0.8 (0.8.3-1) unstable; urgency=low * New upstream * debian/control.in, debian/build-deps.in, debian/gstreamer-theora.install, debian/rules: * New theora plugin * debian/gstreamer-misc.install: * Add new plugins: alphacolor, decodebin, multifilesink, playbin -- David I. Lehn Sat, 7 Aug 2004 09:50:10 -0400 gst-plugins0.8 (0.8.2-3) unstable; urgency=high * debian/libgstreamer-gconf.postinst: Remove. schema install now handled by dh_gconf which moved the schema location causing old hardcoded /etc path to fail. (Closes: #259538, #259119, #259205) * Urgency high: GNOME team wants this in sarge now. -- David I. Lehn Fri, 16 Jul 2004 09:17:18 -0400 gst-plugins0.8 (0.8.2-2) unstable; urgency=low * debian/rules: * Bump libgstreamer0.8-dev dep up to get fixed shlibs * Rebuild with proper shlibs (Closes: #256181, #256494) -- David I. Lehn Wed, 7 Jul 2004 01:16:48 -0400 gst-plugins0.8 (0.8.2-1) unstable; urgency=medium * New upstream * debian/build-deps.in: * Add libdts-dev * ext/Makefile.{am,in}: * Fix so dts dir is built * debian/gstreamer-misc.install: * Add DTS plugin * Added alpha, audiorate, dtsdec, multipart, videobox, videomixer, and videorate * debian/rules: * Update libgstreamer0.8-dev build dependency to 0.8.3-2 * debian/control.in: * Remove explicit libgstreamer deps, autodetected now -- David I. Lehn Wed, 23 Jun 2004 23:50:53 -0400 gst-plugins0.8 (0.8.1-4) unstable; urgency=low * gst-libs/gst/riff/riff-media.c: * caps type typo fix from CVS * gst-libs/gst/resample/private.h: * disabled ppc code fix from CVS (Closes: #252991) * sys/v4l/gstv4lsrc.c: * caps fix NULL->any from CVS -- David I. Lehn Wed, 16 Jun 2004 01:24:41 -0400 gst-plugins0.8 (0.8.1-3) unstable; urgency=low * rerun autoget.sh to fix arm builds (Closes: #251592) * gconf/gstreamer.schemas.in, gcon/gstreamer-0.8.schemas: s/xvideosink/xvimagesink/ (Closes: #250575) -- David I. Lehn Tue, 1 Jun 2004 10:10:37 -0400 gst-plugins0.8 (0.8.1-2) unstable; urgency=low * debian/build-deps.in: * Update "libdv2-dev" to "libdv4-dev | libdv-dev" * debian/control.in, debian/extra, debian/rules: * Remove arts plugin. Detection, flags, and include directory build code is too buggy for plugin to build at the moment. -- David I. Lehn Thu, 29 Apr 2004 18:10:48 -0400 gst-plugins0.8 (0.8.1-1) unstable; urgency=low * New upstream * debian/gstreamer-misc.install: * add libgstdebug.so -- David I. Lehn Sun, 18 Apr 2004 01:04:36 -0400 gst-plugins0.8 (0.8.0-2) unstable; urgency=low * Bump to -2 to ease upgrades for early -1 testers * debian/build-deps.in: * Add libgtk2.0-dev -- David I. Lehn Thu, 15 Apr 2004 17:50:14 -0400 gst-plugins0.8 (0.8.0-1) unstable; urgency=low * New upstream (Closes: #234071, #240663) * debian/rules: * Switch to CDBS * Version now at 0.8 * Convert various control files to versioned ones for build * Add/rename/remove generated plugins files * debian/control.in: * Update Standards-Version to 3.6.1 * Add -caca and -speex plugins * Remove -http plugin * debian/build-deps.in: * Add cdbs * Update debhelper >= 4.1.0 * Add caca, speex, and pango deps * Remove libghttp-dev * debian/gstreamer-http.install * Remove * debian/gstreamer-{speex|caca}.install: * Add new plugins * debian/*.install: * Append debian/tmp for CDBS * General cleanups to support versioned files and locations * debian/gstreamer-esd.install: * Remove libgstesdmod * Change libgstesdsink to libgstesd * debian/gstreamer-plugin-libs.install: * Stick locale info in here for lack of a better place * debian/gstreamer-plugin-libs[-dev].install: * Add support for colorbalance, mixer, navigation, propertyprobe, tag, tuner, xoverlay, and xwindowlistener * Add pkgconfig support for plugins, interfaces, and media-info * debian/gstreamer-gnomevfs.install: * s/libgstgnomevfs{src|sink}.so/libgstgnomevfs.so/ * debian/gstreamer-colorspace.install: * Rename to gstreamer-hermes.install * Move generic and ffmpeg based colorspace plugins to -misc * Provide gstreamerX.Y-colorspace * debian/gstreamer-misc.install: * v4l/v4l2 support merged to libgstvideo4linux[2].so * Add gdkpixbuf, interleave, nassink, ogg, smoothwave, tagedit, textoverlay, timeoverlay, typefindfunctions, videobalance, videodrop, videofilter, videoflip * Move generic and ffmpeg based colorspace plugins from -hermes * Provide gstreamerX.Y-colorspace * debian/gstreamer-plugin-libs.install: * Rename to libgstreamer-plugins.install * debian/gstreamer-plugin-libs-dev.install: * Rename to libgstreamer-plugins-dev.install * debian/gstreamer-gconf.install: * Rename libgstreamer-gconf.install * Remove dev files * debian/gstreamer-gconf.postinst: * Rename to libgstreamer-gconf.postinst * debian/libgstreamer-gconf-dev.install: * Added * Add dev parts from gstreamer-gconf.install * configure.ac, configure: * Patch from CVS to get arts to detect on Debian boxen * ext/speex/gstspeex{dec,enc}.c: * Upstream patch for static pad templates (caused gst-compprep-0.8 bugs) * ext/ladspa/gstladspa.c: * Change g_warning to DEBUG_OBJ to silence gst-compprep * ext/libcaca/gstcacsink.c: * Upstream patch to avoid cacasink windows with gst-inspect/compprep -- David I. Lehn Wed, 14 Apr 2004 19:14:14 -0400 gst-plugins0.7 (0.7.6-1) unstable; urgency=low * New upstream -- David I. Lehn Wed, 17 Mar 2004 19:06:48 -0500 gst-plugins0.7 (0.7.5-1) unstable; urgency=low * New upstream -- David I. Lehn Wed, 17 Mar 2004 18:47:19 -0500 gst-plugins0.7 (0.7.4-1) unstable; urgency=low * New upstream -- David I. Lehn Wed, 17 Mar 2004 18:46:27 -0500 gst-plugins0.7 (0.7.3-1) unstable; urgency=low * New upstream -- David I. Lehn Wed, 17 Mar 2004 18:42:05 -0500 gst-plugins0.7 (0.7.1-1) unstable; urgency=low * New upstream * Versioning package as 0.7 -- David I. Lehn Wed, 17 Mar 2004 18:31:51 -0500 gst-plugins (0.6.4-4) unstable; urgency=low * ext/alsa/gstalsa.h: * Patch to compile with alsa-lib 1.0.x (Closes: #231870) -- David I. Lehn Sat, 14 Feb 2004 17:49:01 -0500 gst-plugins (0.6.4-3) unstable; urgency=low * debian/build-deps.in: * Update jack dependency to libjack0.80.0-dev (>= 0.94.0) (Closes: #221620, #228784) * Update libmpeg2 dependency to libmpeg2-4-dev (>= 0.4.0b) * ext/mpeg2dec/gstmpeg2dec.c: Patch for libmpeg2 0.4.0b -- David I. Lehn Mon, 26 Jan 2004 18:21:45 -0500 gst-plugins (0.6.4-2) unstable; urgency=low * debian/rules: Disable ffmpeg for everything but i386 due to PIC issues (Closes: #219284) -- David I. Lehn Thu, 6 Nov 2003 16:30:35 -0500 gst-plugins (0.6.4-1) unstable; urgency=low * New upstream * ESD updates (Closes: #218736) * Acknowledge NMU (Closes: #217981, #213811) * Rebuild to fix gconf liblinc1 dependency (Closes: #217771) * gstreamer-misc: add v4l2 support (Closes: #199900) * Use pre-Linux-2.6 videodev.h to compile v4l support -- David I. Lehn Mon, 3 Nov 2003 15:10:59 -0500 gst-plugins (0.6.3-1.1) unstable; urgency=low * NMU - Patch from Jordi Mallach * debian/build-deps.in: libgconf2-dev and libgnomevfs2-dev need GNOME 2.4 versions for the liblinc1 transition (closes: #217981). * debian/rules: gstreamer build-dep should be versioned (>= 0.6.3) (closes: #213811). -- LaMont Jones Thu, 30 Oct 2003 09:12:11 -0700 gst-plugins (0.6.3-1) unstable; urgency=low * New upstream * Includes ESD seeking fix (Closes: #201171) -- David I. Lehn Mon, 1 Sep 2003 23:15:49 -0400 gst-plugins (0.6.2-2) unstable; urgency=low * Fix ffmpeg makefile install rule to work even when ffmpeg build is disabled (Closes: #198148, #199072) * Apply partial patches from running autogen.sh to fix arm builds (excluding other unrelated generated file changes) (Closes: #199872) * Update to support libdvdnav 0.1.9 API -- David I. Lehn Fri, 11 Jul 2003 01:01:15 -0400 gst-plugins (0.6.2-1) unstable; urgency=low * New upstream * Add dv1394src to gstreamer-dv -- David I. Lehn Thu, 12 Jun 2003 11:39:51 -0400 gst-plugins (0.6.1-2) unstable; urgency=low * (unreleased) * Adapt to gstreamer package merge: remove -core, -core-libs, -core-libs-dev dependencies as needed. Fixes missing scheduler bugs. (Closes: #181503, #191720) * Use versioned gst-{register,compprep}-0.6 * Update jack dependency to libjack0.71.2-dev (Closes: #195401) * Update libdvdnav dependency to (>= 0.1.7) and revert patch for compatibility with 0.1.3. * Update libdvdread2 dependency to libdvdread3-dev * Remove DISPLAY unset hacks from postinst/postrm scripts and fix non-X installs with 0.6.2 CVS xvideosink patch. (Closes: #165372, #168438) * Link libgstplay to libgstcontrol. (Closes: #194107) -- David I. Lehn Thu, 5 Jun 2003 02:29:44 -0400 gst-plugins (0.6.1-1) unstable; urgency=low * New upstream * Update maintainer address to @debian.org * gstreamer-gconf libs properly linked upstream (Closes: Bug#187353) * debian/control.in: * gstreamer-vorbis depends on gstreamer-core-libs (Closes: Bug#188606) * Apply gnomevfs patch from http://bugzilla.gnome.org/show_bug.cgi?id=94113 * ffmpeg plugin only builds, and now is only packaged, on i386/powerpc (Yes this is bad - better shared lib support needed.) (Closes: Bug#186525) * Revert dvdnav support to libdvdnav 0.1.3 API * Unset DISPLAY before calling gst-register/gst-compprep * ext/ladspa/gstladspa.c:472: s/g_warning/GST_DEBUG/ to avoid printing of a warning on every plugin package install when swh-plugins is installed. (sinCos plugin executes this code path) -- David I. Lehn Sat, 3 May 2003 18:30:16 -0400 gst-plugins (0.6.0-4) unstable; urgency=low * debian/build-deps.in: * Updated libvorbis-dev to (>= 1.0.0-2) (Closes: Bug#184671) -- David I. Lehn Fri, 14 Mar 2003 11:12:21 -0500 gst-plugins (0.6.0-3) unstable; urgency=low * debian/build-deps.in: * Add libartsc0-dev so artsc-config is present so arts[d] plugins get built so the arts[d] packages actually include the arts[d] plugins (Closes: Bug#181438) * Update JACK dependency to 0.50.0 -- David I. Lehn Fri, 7 Mar 2003 23:51:46 -0500 gst-plugins (0.6.0-2) unstable; urgency=low * NMU (with maintainer's permission) * Change libarts-dev build-dep to libarts1-dev (Closes: #180537) -- David Schleef Mon, 10 Feb 2003 21:33:47 -0800 gst-plugins (0.6.0-1) unstable; urgency=low * New upstream -- David I. Lehn Sat, 1 Feb 2003 21:51:54 -0500 gst-plugins (0.5.2.3-1) unstable; urgency=low * New upstream -- David I. Lehn Thu, 30 Jan 2003 23:52:08 -0500 gst-plugins (0.5.2.2-1) unstable; urgency=low * New upstream * pre-release for 0.6.0 * debian/gstreamer-gconf.install: * add gstreamer-gconf-*.pc * debian/gstreamer-misc.install: * remove libgstaviparse.so -- David I. Lehn Wed, 29 Jan 2003 15:07:06 -0500 gst-plugins (0.5.2-1) unstable; urgency=low * New upstream * debian/build-deps.in: - Update swfdec dependency to 0.2.0 * debian/gstreamer-misc.install: - add libgstaviparse.so -- David I. Lehn Thu, 23 Jan 2003 22:17:31 -0500 gst-plugins (0.5.1-1) unstable; urgency=low * New upstream * Update GStreamer dependency to 0.5.1 * Update JACK dependency to 0.44.0 * Remove gstreamer-avifile package (avifile not supported on many architectures and upstream is deprecating it in favor of ffmpeg) * Add support for building unsupported plugin packages (avifile, lame, etc) * Tighten shlib deps for gstreamer-plugin-libs and gstreamer-gconf packages (may split off true lib* packages eventually, was trying to avoid even more packages) * gstreamer-misc: add oneton and vbidec * Rebuild for newer sid libs (Closes: Bug#177410) * Various small fixes from CVS -- David I. Lehn Mon, 20 Jan 2003 11:57:26 -0500 gst-plugins (0.5.0-1) unstable; urgency=low * New upstream * Update libpng dependency to libpng12-0-dev * Patch from CVS to support mpeg2dec 0.3.1 * Patch from CVS to improve X error handling and remote X usage * gstreamer-plugin-libs[-dev]: add media-info lib * gstreamer-misc: add png plugin * Use swfdec 0.1.3 API: s/swf_init/swfdec_decoder_new/ to avoid symbol conflicts with avifile plugin -- David I. Lehn Tue, 17 Dec 2002 20:27:42 -0500 gst-plugins (0.4.2-2) unstable; urgency=low * Update JACK dependency to 0.40.1 * Apply gst-launch-ext perlism patch (Closes: #170736) * Apply patch from Erik Meusel to allow building with other KDE arts packages (Closes: Bug#167538) * Update mpeg2dec dependency to 0.3.0, convert over to gstmpeg2deccvs plugin code, and adjust mpeg2dec check * Add Provides: for virtual gstreamer-{audio,video}sink packages to make it easier for applications to ensure output sinks are available. This isn't foolproof and it's possible that a rare user might not need the standard packaged audio/video sinks. For now those users can use the "equivs" package to get around the problem. (Closes: Bug#169968) -- David I. Lehn Wed, 4 Dec 2002 02:25:41 -0500 gst-plugins (0.4.2-1.1) unstable; urgency=low * NMU * Depend on swfdec-0.1.2-3, since it had a bug. -- David Schleef Tue, 12 Nov 2002 17:43:48 -0800 gst-plugins (0.4.2-1) unstable; urgency=low * New upstream * Add libpng2-dev to Build-Depends for snapshot * Add smpte, snapshot, and wavenc to gstreamer-misc * Add video and play libs to gstreamer-plugin-libs{-dev} * Add gstreamer-swfdec plugin package * Add hack to swfdec plugin to not segfault when avifile also loaded due to symbols conflicts. This annoyance will spew errors during gst-compprep and probably will cause errors in apps that use both swfdec and avifile. * Tighten dependency on libgstreamer until upstream code can deal with multiple plugin versions and upgrades better * Move gstreamer-plugin-apps to Section: utils from x11 -- David I. Lehn Wed, 6 Nov 2002 21:41:01 -0500 gst-plugins (0.4.1-1) unstable; urgency=low * New upstream * Update FLAC dependency to 1.0.4 and add support patch from CVS * Rebuild for libflac4 (Closes: Bug#163549) * Build gconf code as a library rather than a plugin * Improved plugin descriptions * Added to -misc: cdplayer, videocrop, videotestsrc, mixmatrix * Add autotools-dev to Build-Depends * Update config.{guess,sub} -- David I. Lehn Mon, 7 Oct 2002 17:51:43 -0400 gst-plugins (0.4.0.2-cvs20020919-1) unstable; urgency=low * CVS snapshot, release branch * gstreamer-gconf.files: libgstgconf.so moved to /usr/lib * added control.in, build-deps, and mk.control script. Edit control.in and/or build-deps, and run 'debian/rules debian/control' from toplevel directory to recreate debian/control. This should make it a lot easier to diff build dependencies. -- David Schleef Thu, 19 Sep 2002 15:18:41 -0700 gst-plugins (0.4.0-5) unstable; urgency=low * Update Build-Depends * Use pkg-config checking vs m4 macros for libdv -- David I. Lehn Fri, 23 Aug 2002 10:33:29 -0400 gst-plugins (0.4.0-4) unstable; urgency=low * Fixes from CVS for gstgnomevfssrc.c and endian issue in vorbisfile.c -- David I. Lehn Fri, 2 Aug 2002 20:51:38 -0400 gst-plugins (0.4.0-3) unstable; urgency=low * Add ladspa-sdk to Build-Depends * Removed qcam plugin from -misc. It's i386 only and probably not widely used. Will figure out how to package in the future if someone needs it. -- David I. Lehn Mon, 29 Jul 2002 16:32:33 -0400 gst-plugins (0.4.0-2) unstable; urgency=low * Clean up the Build-Depends: - stricter versioning - remove gnome deps (used for examples which are not build now) * Fix segfault in vorbis.m4 check -- David I. Lehn Mon, 29 Jul 2002 10:47:12 -0400 gst-plugins (0.4.0-1) unstable; urgency=low * New upstream * FLAC compile fixes from CVS * DV comiled fixes * Upated ltmain hack * autogen.sh with more recent tools * Update config.{guess,sub} * Added plugin packages: -gconf, -http, -jack, -dv * Added dvdnav plugin to -dvd * Removed osshelper plugin from -oss * Added xvideosink back to -x * Added effectv, filter, and qtdemux plugins to -misc * Rename gstreamer-lib-misc{-dev} to gstreamer-plugin-libs{-dev} to match gstreamer core. * Added gstreamer-plugin-apps package for a lonely 2 scripts * Renamed gstreamer-all to gstreamer-plugins -- David I. Lehn Tue, 16 Jul 2002 02:10:07 -0400 gst-plugins (0.3.4-2) unstable; urgency=low * Rebuild for new avifile -- David I. Lehn Wed, 24 Apr 2002 14:06:46 -0400 gst-plugins (0.3.4-1) unstable; urgency=low * New upstream * Fix mpeg2dec.m4 to use newer mpeg2dec libs -- David I. Lehn Mon, 15 Apr 2002 03:34:21 -0400 gst-plugins (0.3.3-2) unstable; urgency=low * Port a52dec plugin to 0.7.3 API (applied upstream) * Use a52dec packages in Debian vs current external ones * This switches a52dec plugin to static linking * Remove LAME support * No LAME packages in Debian to depend on * Added explanation in README.Debian since this will likely cause no end of "Why is there no LAME plugin?" * Depend on and rebuild for mpeg2dec 0.2.1 * Depend on and rebuild for glib 2.0 * Remove gtk from Build-Depends: (not needed for the built plugins) -- David I. Lehn Sat, 23 Mar 2002 17:52:03 -0500 gst-plugins (0.3.3-1) unstable; urgency=low * New upstream version * SDL fixed upstream, remove patch * Require libid3tag for mad support * Remove xvideosink from gstreamer-x package in favor of videosink Needed to be removed due to symbol conflicts with videosink * Sync with upstream changes: * Added videosink to gstreamer-x package * parseau renamed to auparse in -misc * Added various plugins to -misc: goom, monoscope, modplug, ... * Note: new upstream scripts gst-launch-ext and gst-visualise not packaged yet -- David I. Lehn Thu, 21 Mar 2002 02:06:21 -0500 gst-plugins (0.3.2-3) unstable; urgency=low * Rebuild for glib/gtk 1.3.15 -- David I. Lehn Mon, 25 Feb 2002 00:06:51 -0500 gst-plugins (0.3.2-2) unstable; urgency=low * Build against liblinc1 and newer gnomevfs -- David I. Lehn Sun, 17 Feb 2002 15:52:57 -0500 gst-plugins (0.3.2-1) unstable; urgency=low * New upstream release * Many diffs merged upstream * Unversioned plugins upstream, just packaging .so (no .la, .a) * Added cdxa plugin to -misc * Various plugins in -misc moved around and merged upstream -- David I. Lehn Fri, 15 Feb 2002 21:54:27 -0500 gst-plugins (0.3.1-4) unstable; urgency=low * Rebuild for glib/gtk 1.3.13 -- David I. Lehn Wed, 6 Feb 2002 02:16:16 -0500 gst-plugins (0.3.1-3) unstable; urgency=low * Fix arts symbol problem (add -lartsflow -lartsflow_idl) * Replace m4/gst-sdl.m4 with Debian's sdl.m4 * Use SDL_LIBS_FOR_PLUGINS instead of SDL_LIBS -- David I. Lehn Wed, 23 Jan 2002 12:37:00 -0500 gst-plugins (0.3.1-2) unstable; urgency=low * Rebuild against fixed gstreamer.pc in libgst-dev 0.3.1-2 Should just link to libxml2 now rather than v1 and v2 -- David I. Lehn Wed, 23 Jan 2002 02:29:49 -0500 gst-plugins (0.3.1-1) unstable; urgency=low * Upstream split plugins into new package * Upstream removed mpg123 plugin - use mad plugin * Upstream removed ac3dec plugin - use a52dec * Added -lib-misc and -lib-misc-dev packages for library plugins and headers * Patch to link all plugins to GST_LIBS -- David I. Lehn Fri, 11 Jan 2002 11:22:01 -0500 gstreamer (0.3.0-3) unstable; urgency=low * Remove upstream ChangeLog from packages: 128k changelog.gz per plugin package for 40 packages is too much -- David I. Lehn Tue, 25 Dec 2001 23:36:28 -0500 gstreamer (0.3.0-2) unstable; urgency=low * Fix bug that slipped into 0.3.0: s/aasink/xvideosink/ in gstplay/gstplay.c -- David I. Lehn Tue, 25 Dec 2001 17:56:29 -0500 gstreamer (0.3.0-1) unstable; urgency=low * New upstream release * Attempt to update various Build-Depends versions * Added plugin packages: -a52dec, -dvd, -mikmod, -sid * Renamed -elements to -core * Added to -core: gstbasicscheduler * Moved from -common to -core: gsttypes, autoplug related * Renamed -common to -misc * Added to -misc: speed, qcam, bytesteram, control, silence, sinesrc, mpegstream, playondemand, resample * Added gstreamer-guilaunch to gstreamer-tools package * Added dependencies on unofficial LAME packages * Use PIC libs for Xv * Disable broken building of PDF/PS docs * Renamed -all-plugins to -all * Disable docs -- too hard to build -- David I. Lehn Fri, 21 Dec 2001 12:00:02 -0500 gstreamer (0.2.1-4) unstable; urgency=low * Fix some problems reported from lintian 1.20.14: * copyright-lists-upstream-authors-like-dh_make * debian-changelog-file-contains-user-emacs-settings * Patch from CVS to link libgst into plugins Plugins now properly depend on libgst package * Use RedHat Gtk+-1.3 hack to fix relink issues with ltmain.sh * Patch from CVS for xvideosink segfault when no DISPLAY set * Remove builddir references from gstreamer-config.in * Move libgstelements.la from libgst-dev to gstreamer-elements -- David I. Lehn Tue, 28 Aug 2001 20:05:28 -0400 gstreamer (0.2.1-3) unstable; urgency=low * Update build dependencies to FLAC 1.0 * Enable debug features * Fixup broken doc build -scan voodoo with link into .libs/ dir -- David I. Lehn Sun, 5 Aug 2001 23:04:28 -0400 gstreamer (0.2.1-2) unstable; urgency=low * Fix lib deps: run debhelper tools in the right order * Added arts dir to Makefile.am SUBDIRS so it builds again * Changed libmpeg2dec to libgstmpeg2dec to avoid naming issues when using -lmpeg2dec * Updated system_encode/ with CVS segfaulter bug fix -- David I. Lehn Thu, 19 Jul 2001 15:47:24 -0400 gstreamer (0.2.1-1) unstable; urgency=low * New upstream 0.2.1: "Return of the Sedi Master" * New plugin packages: -festival, -flac, -avifile, -x * New plugins in -common: chart, deinterlace, udp * Added some post-0.2.1 fixes for FLAC, build system, ALSA -- David I. Lehn Thu, 28 Jun 2001 20:15:15 -0400 gstreamer (0.2.0-6) unstable; urgency=low * Move -compprep to -runtime and call it same places as -register * Do -register and -compprep in postrm instead of prerm * Make -arts plugin actually build all the source (sent upstream) * Purge of -runtime removes /etc/gstreamer -- David I. Lehn Tue, 19 Jun 2001 13:09:32 -0400 gstreamer (0.2.0-5) unstable; urgency=low * Added element package dependencies to libgstmediaplay0 -- David I. Lehn Mon, 18 Jun 2001 11:18:53 -0400 gstreamer (0.2.0-4) unstable; urgency=low * Add --gst-mask=0 to -runtime.postinst -- David I. Lehn Fri, 15 Jun 2001 11:47:24 -0400 gstreamer (0.2.0-3) unstable; urgency=low * Fix the plugin control file symlink creation * Add audiofile to Build-Depends -- David I. Lehn Fri, 15 Jun 2001 05:22:28 -0400 gstreamer (0.2.0-2) unstable; urgency=low * Fixed ALSA checks to not include -lasound in -every- link * Update LAME plugin to use latest CVS API * Removed OSS src/sink from -common.files (was in -oss too) * Swapped -arts.files and -artsd.files contents -- David I. Lehn Fri, 15 Jun 2001 04:02:21 -0400 gstreamer (0.2.0-1) unstable; urgency=low * Added gstreamer-compprep manpage * Upgrade to 0.2.0 -- David I. Lehn Thu, 7 Jun 2001 12:53:59 -0400 gstreamer (0.2.0-0.3) unstable; urgency=low * GStreamer 0.2.0-pre3 -- David I. Lehn Wed, 6 Jun 2001 15:09:59 -0400 gstreamer (0.2.0-0.2) unstable; urgency=low * GStreamer 0.2.0-pre2 + CVS 20010604 * Added -artsd (vs -arts), -audiofile, -gnomevfs, -gsm, -jpeg, -oss, and -sdl plugin packages * Added osshelper lib to oss package * Added more AVI related plugins and autoplug libs to -common * Added pkgconfig file to libgst-dev * Added gstreamer-all-plugins pseudo package that depends on all other plugin pacakges -- David I. Lehn Mon, 4 Jun 2001 17:33:20 -0400 gstreamer (0.2.0-0.1) unstable; urgency=low * GStreamer 0.2.0-pre1 -- David I. Lehn Thu, 31 May 2001 17:16:23 -0400 gstreamer (0.1.1.20010504-1) unstable; urgency=low * Latest CVS code -- David I. Lehn Fri, 4 May 2001 21:48:45 -0400 gstreamer (0.1.1.20010430-2) unstable; urgency=low * Added -colorspace package for Hermes dependent conversion * Added -arts package for aRts sink -- David I. Lehn Tue, 1 May 2001 19:46:08 -0400 gstreamer (0.1.1.20010430-1) unstable; urgency=low * Latest CVS code * Added -aa package for aasink output * Added -mad package for mad mp3 decoder -- David I. Lehn Mon, 30 Apr 2001 18:25:52 -0400 gstreamer (0.1.1.20010320-1) unstable; urgency=low * Latest CVS code * enable main docs * disable broken plugin docs with new option -- David I. Lehn Tue, 20 Mar 2001 18:15:19 -0500 gstreamer (0.1.1.20010315-1) unstable; urgency=low * Latest CVS code * Added man pages * Split mpeg2dec to seperate plugin * libgst Architectures updated to cothread supported archs -- David I. Lehn Thu, 15 Mar 2001 20:17:19 -0500 gstreamer (0.1.1-1) unstable; urgency=low * New upstream release * disable docs build, broken at the momemnt -- David I. Lehn Sun, 25 Feb 2001 17:58:25 -0500 gstreamer (0.1.0-2) unstable; urgency=low * debian/rules: call configure instead of autogen.sh -- David I. Lehn Sat, 24 Feb 2001 18:31:36 -0500 gstreamer (0.1.0-1) unstable; urgency=low * Initial Release. -- David I. Lehn Mon, 15 Jan 2001 18:25:18 -0500 debian/gbp.conf0000664000000000000000000000022412061315514010603 0ustar [DEFAULT] upstream-branch = upstream debian-branch = master pristine-tar = True upstream-tag = upstream/%(version)s debian-tag = debian/%(version)s debian/control0000664000000000000000000001530012256171424010576 0ustar Source: gst-plugins-good0.10 Section: libs Priority: optional Maintainer: Ubuntu Desktop Team XSBC-Original-Maintainer: Maintainers of GStreamer packages Uploaders: Loic Minier , Sebastian Dröge , Sjoerd Simons Build-Depends: libgstreamer0.10-dev (>= 0.10.36), libraw1394-dev (>= 2.0.0) [linux-any] , libiec61883-dev (>= 1.0.0) [linux-any] , libavc1394-dev [linux-any] , libv4l-dev [linux-any] , libgudev-1.0-dev (>= 143) [linux-any], libgstreamer-plugins-base0.10-dev (>= 0.10.36), autotools-dev, dh-autoreconf, automake (>= 1.10), autoconf (>= 2.60), libtool (>= 2.0), autopoint (>= 0.17), cdbs (>= 0.4.93), debhelper (>= 8.1.3), dpkg-dev (>= 1.15.1), pkg-config (>= 0.11.0), gtk-doc-tools, gconf2, libglib2.0-dev (>= 2.24), liborc-0.4-dev (>= 1:0.4.11), libcairo2-dev (>= 1.10.0), libcaca-dev, libspeex-dev (>= 1.1.6), libpng-dev, libshout3-dev, libjpeg-dev, libaa1-dev (>= 1.4p5), libflac-dev (>= 1.1.4), libdv4-dev | libdv-dev, libgconf2-dev, libxdamage-dev, libxext-dev, libxfixes-dev, libxv-dev, libxml2-dev, libgtk2.0-dev (>= 2.8), libtag1-dev (>= 1.5), libwavpack-dev (>= 4.20), gstreamer-tools (>= 0.10.30), gstreamer0.10-plugins-base (>= 0.10.36), libsoup-gnome2.4-dev (>= 2.26), libpulse-dev (>= 1.0), libbz2-dev, gstreamer0.10-doc, gstreamer0.10-plugins-base-doc, libjack-dev (>= 1:0.99.10) Standards-Version: 3.8.4 Package: gstreamer0.10-plugins-good-doc Architecture: all Section: doc Depends: gstreamer0.10-doc, gstreamer0.10-plugins-base-doc, ${misc:Depends} Description: GStreamer documentation for plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains documentation for plugins from the "good" set, a set of good-quality plug-ins under the LGPL license. Package: gstreamer0.10-pulseaudio Architecture: any Multi-Arch: same Section: sound Depends: ${misc:Depends}, ${shlibs:Depends} XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Provides: ${gstreamer:Provides} Description: GStreamer plugin for PulseAudio GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugin for PulseAudio, a sound server for POSIX and WIN32 systems. Package: gstreamer0.10-gconf Architecture: any Multi-Arch: same Section: sound Depends: ${misc:Depends}, ${shlibs:Depends} XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Replaces: gstreamer0.10-plugins-good (<< 0.10.30-2) Breaks: gstreamer0.10-plugins-good (<< 0.10.30-2) Description: GStreamer plugin for getting the sink/source information from GConf GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugin for getting the information about which sources or sinks should be used for audio and video from GConf. Package: gstreamer0.10-plugins-good Architecture: any Multi-Arch: same Section: libs Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends}, gstreamer0.10-pulseaudio | gstreamer0.10-audiosink, gstreamer0.10-plugins-base Recommends: gstreamer0.10-x Replaces: gstreamer0.10-plugins-bad (<< 0.10.21.2), gstreamer0.10-plugins-really-bad (<< 0.10.21.2), gstreamer0.10-plugins-good-doc (<< 0.10.6-2) Breaks: gstreamer0.10-plugins-bad (<< 0.10.21.2), gstreamer0.10-plugins-really-bad (<< 0.10.21.2) XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Provides: ${gstreamer:Provides} Description: GStreamer plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugins from the "good" set, a set of good-quality plug-ins under the LGPL license. Package: gstreamer0.10-plugins-good-dbg Architecture: any Multi-Arch: same Section: debug Priority: extra Depends: gstreamer0.10-plugins-good (= ${binary:Version}), gstreamer0.10-pulseaudio (= ${binary:Version}), gstreamer0.10-gconf (= ${binary:Version}), ${misc:Depends} Replaces: gstreamer0.10-plugins-bad-dbg (<< 0.10.21.2) Breaks: gstreamer0.10-plugins-bad-dbg (<< 0.10.21.2) Description: GStreamer plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains unstripped shared libraries. It is provided primarily to provide a backtrace with names in a debugger, this makes it somewhat easier to interpret core dumps. The libraries are installed in /usr/lib/debug and are automatically used by gdb. debian/compat0000664000000000000000000000000212061315514010364 0ustar 9 debian/patches/0000775000000000000000000000000012311057141010612 5ustar debian/patches/577d87300906f961d13f76b44ba60fc40f0c637a.patch0000664000000000000000000000271312311057024017060 0ustar From 577d87300906f961d13f76b44ba60fc40f0c637a Mon Sep 17 00:00:00 2001 From: William Jon McCann Date: Fri, 14 Feb 2014 20:27:20 +0000 Subject: docs: fix mismatched para tags newer gtkdoc is more sensitive to mismatched docbook tags. This fixes the build in master. --- diff --git a/gst/audiofx/audiocheblimit.c b/gst/audiofx/audiocheblimit.c index 549a9d2..e278886 100644 --- a/gst/audiofx/audiocheblimit.c +++ b/gst/audiofx/audiocheblimit.c @@ -46,12 +46,12 @@ * be at most this value. A lower ripple value will allow a faster rolloff. * * As a special case, a Chebyshev type 1 filter with no ripple is a Butterworth filter. - * + * * * Be warned that a too large number of poles can produce noise. The most poles are possible with * a cutoff frequency at a quarter of the sampling rate. * - * + * * * Example launch line * |[ diff --git a/gst/udp/gstudpsrc.c b/gst/udp/gstudpsrc.c index c2cbd92..7a24339 100644 --- a/gst/udp/gstudpsrc.c +++ b/gst/udp/gstudpsrc.c @@ -78,8 +78,7 @@ * * The message is typically used to detect that no UDP arrives in the receiver * because it is blocked by a firewall. - * - * + * * A custom file descriptor can be configured with the * #GstUDPSrc:sockfd property. The socket will be closed when setting the * element to READY by default. This behaviour can be -- cgit v0.9.0.2-2-gbebe debian/patches/05_move_shm_to_good.patch0000664000000000000000000022233212061315514015475 0ustar commit d3af4317503519facada153ee20f0d97ffcbbfff Author: Ken VanDine Date: Wed Mar 16 16:38:04 2011 -0400 Moved shm from gst-plugins-bad Index: gst-plugins-good0.10/sys/Makefile.am =================================================================== --- gst-plugins-good0.10.orig/sys/Makefile.am 2012-02-09 13:53:18.304916162 +0200 +++ gst-plugins-good0.10/sys/Makefile.am 2012-02-09 13:57:02.791633689 +0200 @@ -77,8 +77,14 @@ XIMAGE_DIR= endif -SUBDIRS=$(DIRECTSOUND_DIR) $(OSS_DIR) $(OSS4_DIR) $(OSX_AUDIO_DIR) $(OSX_VIDEO_DIR) $(SUNAUDIO_DIR) $(V4L2_DIR) $(XIMAGE_DIR) +if USE_SHM +SHM_DIR=shm +else +SHM_DIR= +endif + +SUBDIRS=$(DIRECTSOUND_DIR) $(OSS_DIR) $(OSS4_DIR) $(OSX_AUDIO_DIR) $(OSX_VIDEO_DIR) $(SUNAUDIO_DIR) $(V4L2_DIR) $(XIMAGE_DIR) $(SHM_DIR) -DIST_SUBDIRS=directsound oss oss4 osxaudio osxvideo sunaudio v4l2 waveform ximage +DIST_SUBDIRS=directsound oss oss4 osxaudio osxvideo sunaudio v4l2 waveform ximage shm include $(top_srcdir)/common/parallel-subdirs.mak Index: gst-plugins-good0.10/sys/shm/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/Makefile.am 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,13 @@ +glib_enum_prefix = gst_shm + +include $(top_srcdir)/common/glib-gen.mak + +plugin_LTLIBRARIES = libgstshm.la + +libgstshm_la_SOURCES = shmpipe.c shmalloc.c gstshm.c gstshmsrc.c gstshmsink.c +libgstshm_la_CFLAGS = $(GST_CFLAGS) -DSHM_PIPE_USE_GLIB +libgstshm_la_LIBADD = -lrt +libgstshm_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) +libgstshm_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstshmsrc.h gstshmsink.h shmpipe.h shmalloc.h Index: gst-plugins-good0.10/sys/shm/gstshm.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/gstshm.c 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,42 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstshmsrc.h" +#include "gstshmsink.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "shmsrc", + GST_RANK_NONE, GST_TYPE_SHM_SRC) && + gst_element_register (plugin, "shmsink", + GST_RANK_NONE, GST_TYPE_SHM_SINK); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "shm", + "shared memory sink source", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/sys/shm/gstshmsink.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/gstshmsink.c 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,659 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstshmsink.h" + +#include + +#include + +/* signals */ +enum +{ + SIGNAL_CLIENT_CONNECTED, + SIGNAL_CLIENT_DISCONNECTED, + LAST_SIGNAL +}; + +/* properties */ +enum +{ + PROP_0, + PROP_SOCKET_PATH, + PROP_PERMS, + PROP_SHM_SIZE, + PROP_WAIT_FOR_CONNECTION, + PROP_BUFFER_TIME +}; + +struct GstShmClient +{ + ShmClient *client; + GstPollFD pollfd; +}; + +#define DEFAULT_SIZE ( 256 * 1024 ) +#define DEFAULT_WAIT_FOR_CONNECTION (TRUE) +/* Default is user read/write, group read */ +#define DEFAULT_PERMS ( S_IRUSR | S_IWUSR | S_IRGRP ) + + +GST_DEBUG_CATEGORY_STATIC (shmsink_debug); +#define GST_CAT_DEFAULT shmsink_debug + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_BOILERPLATE (GstShmSink, gst_shm_sink, GstBaseSink, GST_TYPE_BASE_SINK); + +static void gst_shm_sink_finalize (GObject * object); +static void gst_shm_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_shm_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_shm_sink_start (GstBaseSink * bsink); +static gboolean gst_shm_sink_stop (GstBaseSink * bsink); +static GstFlowReturn gst_shm_sink_render (GstBaseSink * bsink, GstBuffer * buf); +static GstFlowReturn gst_shm_sink_buffer_alloc (GstBaseSink * sink, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** out_buf); + +static gboolean gst_shm_sink_event (GstBaseSink * bsink, GstEvent * event); +static gboolean gst_shm_sink_unlock (GstBaseSink * bsink); +static gboolean gst_shm_sink_unlock_stop (GstBaseSink * bsink); + +static gpointer pollthread_func (gpointer data); + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +gst_shm_sink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + + gst_element_class_set_details_simple (element_class, + "Shared Memory Sink", + "Sink", + "Send data over shared memory to the matching source", + "Olivier Crete "); +} + +static void +gst_shm_sink_init (GstShmSink * self, GstShmSinkClass * g_class) +{ + self->cond = g_cond_new (); + self->size = DEFAULT_SIZE; + self->wait_for_connection = DEFAULT_WAIT_FOR_CONNECTION; + self->perms = DEFAULT_PERMS; +} + +static void +gst_shm_sink_class_init (GstShmSinkClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSinkClass *gstbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + + gobject_class->finalize = gst_shm_sink_finalize; + gobject_class->set_property = gst_shm_sink_set_property; + gobject_class->get_property = gst_shm_sink_get_property; + + gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_shm_sink_start); + gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_shm_sink_stop); + gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_shm_sink_render); + gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_shm_sink_event); + gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_shm_sink_unlock); + gstbasesink_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_shm_sink_unlock_stop); + gstbasesink_class->buffer_alloc = + GST_DEBUG_FUNCPTR (gst_shm_sink_buffer_alloc); + + g_object_class_install_property (gobject_class, PROP_SOCKET_PATH, + g_param_spec_string ("socket-path", + "Path to the control socket", + "The path to the control socket used to control the shared memory" + " transport", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PERMS, + g_param_spec_uint ("perms", + "Permissions on the shm area", + "Permissions to set on the shm area", + 0, 07777, DEFAULT_PERMS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SHM_SIZE, + g_param_spec_uint ("shm-size", + "Size of the shm area", + "Size of the shared memory area", + 0, G_MAXUINT, DEFAULT_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_WAIT_FOR_CONNECTION, + g_param_spec_boolean ("wait-for-connection", + "Wait for a connection until rendering", + "Block the stream until the shm pipe is connected", + DEFAULT_WAIT_FOR_CONNECTION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_BUFFER_TIME, + g_param_spec_uint64 ("buffer-time", + "Buffer Time of the shm buffer", + "Maximum Size of the shm buffer in nanoseconds (-1 to disable)", + 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + signals[SIGNAL_CLIENT_CONNECTED] = g_signal_new ("client-connected", + GST_TYPE_SHM_SINK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); + + signals[SIGNAL_CLIENT_DISCONNECTED] = g_signal_new ("client-disconnected", + GST_TYPE_SHM_SINK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); + + GST_DEBUG_CATEGORY_INIT (shmsink_debug, "shmsink", 0, "Shared Memory Sink"); +} + +static void +gst_shm_sink_finalize (GObject * object) +{ + GstShmSink *self = GST_SHM_SINK (object); + + g_cond_free (self->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/* + * Set the value of a property for the server sink. + */ +static void +gst_shm_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstShmSink *self = GST_SHM_SINK (object); + int ret = 0; + + switch (prop_id) { + case PROP_SOCKET_PATH: + GST_OBJECT_LOCK (object); + g_free (self->socket_path); + self->socket_path = g_value_dup_string (value); + GST_OBJECT_UNLOCK (object); + break; + case PROP_PERMS: + GST_OBJECT_LOCK (object); + self->perms = g_value_get_uint (value); + if (self->pipe) + ret = sp_writer_setperms_shm (self->pipe, self->perms); + GST_OBJECT_UNLOCK (object); + if (ret < 0) + GST_WARNING_OBJECT (object, "Could not set permissions on pipe: %s", + strerror (ret)); + break; + case PROP_SHM_SIZE: + GST_OBJECT_LOCK (object); + if (self->pipe) { + if (sp_writer_resize (self->pipe, g_value_get_uint (value)) < 0) + GST_DEBUG_OBJECT (self, "Resized shared memory area from %u to " + "%u bytes", self->size, g_value_get_uint (value)); + else + GST_WARNING_OBJECT (self, "Could not resize shared memory area from" + "%u to %u bytes", self->size, g_value_get_uint (value)); + } + self->size = g_value_get_uint (value); + GST_OBJECT_UNLOCK (object); + break; + case PROP_WAIT_FOR_CONNECTION: + GST_OBJECT_LOCK (object); + self->wait_for_connection = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (object); + g_cond_broadcast (self->cond); + break; + case PROP_BUFFER_TIME: + GST_OBJECT_LOCK (object); + self->buffer_time = g_value_get_uint64 (value); + GST_OBJECT_UNLOCK (object); + g_cond_broadcast (self->cond); + break; + default: + break; + } +} + +static void +gst_shm_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstShmSink *self = GST_SHM_SINK (object); + + GST_OBJECT_LOCK (object); + + switch (prop_id) { + case PROP_SOCKET_PATH: + g_value_set_string (value, self->socket_path); + break; + case PROP_PERMS: + self->perms = g_value_get_uint (value); + if (self->pipe) { + int ret; + + ret = sp_writer_setperms_shm (self->pipe, self->perms); + if (ret < 0) + GST_WARNING_OBJECT (object, "Could not set permissions on pipe: %s", + strerror (ret)); + } + break; + case PROP_SHM_SIZE: + g_value_set_uint (value, self->size); + break; + case PROP_WAIT_FOR_CONNECTION: + g_value_set_boolean (value, self->wait_for_connection); + break; + case PROP_BUFFER_TIME: + g_value_set_uint64 (value, self->buffer_time); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_OBJECT_UNLOCK (object); +} + + + +static gboolean +gst_shm_sink_start (GstBaseSink * bsink) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + + self->stop = FALSE; + + if (!self->socket_path) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE, + ("Could not open socket."), (NULL)); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "Creating new socket at %s" + " with shared memory of %d bytes", self->socket_path, self->size); + + self->pipe = sp_writer_create (self->socket_path, self->size, self->perms); + + if (!self->pipe) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE, + ("Could not open socket."), (NULL)); + return FALSE; + } + + sp_set_data (self->pipe, self); + g_free (self->socket_path); + self->socket_path = g_strdup (sp_writer_get_path (self->pipe)); + + GST_DEBUG ("Created socket at %s", self->socket_path); + + self->poll = gst_poll_new (TRUE); + gst_poll_fd_init (&self->serverpollfd); + self->serverpollfd.fd = sp_get_fd (self->pipe); + gst_poll_add_fd (self->poll, &self->serverpollfd); + gst_poll_fd_ctl_read (self->poll, &self->serverpollfd, TRUE); + + self->pollthread = g_thread_create (pollthread_func, self, TRUE, NULL); + + if (!self->pollthread) + goto thread_error; + + return TRUE; + +thread_error: + + sp_close (self->pipe); + self->pipe = NULL; + gst_poll_free (self->poll); + + GST_ELEMENT_ERROR (self, CORE, THREAD, ("Could not srart thread"), (NULL)); + return FALSE; +} + + +static gboolean +gst_shm_sink_stop (GstBaseSink * bsink) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + + self->stop = TRUE; + gst_poll_set_flushing (self->poll, TRUE); + + g_thread_join (self->pollthread); + self->pollthread = NULL; + + GST_DEBUG_OBJECT (self, "Stopping"); + + while (self->clients) { + struct GstShmClient *client = self->clients->data; + self->clients = g_list_remove (self->clients, client); + sp_writer_close_client (self->pipe, client->client); + g_signal_emit (self, signals[SIGNAL_CLIENT_DISCONNECTED], 0, + client->pollfd.fd); + g_slice_free (struct GstShmClient, client); + } + + gst_poll_free (self->poll); + self->poll = NULL; + + sp_close (self->pipe); + self->pipe = NULL; + + return TRUE; +} + +static gboolean +gst_shm_sink_can_render (GstShmSink * self, GstClockTime time) +{ + ShmBuffer *b; + + if (time == GST_CLOCK_TIME_NONE || self->buffer_time == GST_CLOCK_TIME_NONE) + return TRUE; + + b = sp_writer_get_pending_buffers (self->pipe); + for (; b != NULL; b = sp_writer_get_next_buffer (b)) { + GstClockTime t = sp_writer_buf_get_tag (b); + if (GST_CLOCK_DIFF (time, t) > self->buffer_time) + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_shm_sink_render (GstBaseSink * bsink, GstBuffer * buf) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + int rv; + + GST_OBJECT_LOCK (self); + while (self->wait_for_connection && !self->clients) { + g_cond_wait (self->cond, GST_OBJECT_GET_LOCK (self)); + if (self->unlock) { + GST_OBJECT_UNLOCK (self); + return GST_FLOW_WRONG_STATE; + } + } + + while (!gst_shm_sink_can_render (self, GST_BUFFER_TIMESTAMP (buf))) { + g_cond_wait (self->cond, GST_OBJECT_GET_LOCK (self)); + if (self->unlock) { + GST_OBJECT_UNLOCK (self); + return GST_FLOW_WRONG_STATE; + } + } + + rv = sp_writer_send_buf (self->pipe, (char *) GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf), GST_BUFFER_TIMESTAMP (buf)); + + if (rv == -1) { + ShmBlock *block = NULL; + gchar *shmbuf = NULL; + while ((block = sp_writer_alloc_block (self->pipe, + GST_BUFFER_SIZE (buf))) == NULL) { + g_cond_wait (self->cond, GST_OBJECT_GET_LOCK (self)); + if (self->unlock) { + GST_OBJECT_UNLOCK (self); + return GST_FLOW_WRONG_STATE; + } + } + while (self->wait_for_connection && !self->clients) { + g_cond_wait (self->cond, GST_OBJECT_GET_LOCK (self)); + if (self->unlock) { + sp_writer_free_block (block); + GST_OBJECT_UNLOCK (self); + return GST_FLOW_WRONG_STATE; + } + } + + shmbuf = sp_writer_block_get_buf (block); + memcpy (shmbuf, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + sp_writer_send_buf (self->pipe, shmbuf, GST_BUFFER_SIZE (buf), + GST_BUFFER_TIMESTAMP (buf)); + sp_writer_free_block (block); + } + + GST_OBJECT_UNLOCK (self); + + return GST_FLOW_OK; +} + +static void +gst_shm_sink_free_buffer (gpointer data) +{ + ShmPipe *pipe; + ShmBlock *block = data; + GstShmSink *self; + + pipe = sp_writer_block_get_pipe (block); + self = sp_get_data (pipe); + + GST_OBJECT_LOCK (self); + sp_writer_free_block (block); + GST_OBJECT_UNLOCK (self); + g_object_unref (self); +} + +static GstFlowReturn +gst_shm_sink_buffer_alloc (GstBaseSink * sink, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** out_buf) +{ + GstShmSink *self = GST_SHM_SINK (sink); + GstBuffer *buffer; + ShmBlock *block = NULL; + gpointer buf = NULL; + + GST_OBJECT_LOCK (self); + block = sp_writer_alloc_block (self->pipe, size); + if (block) { + buf = sp_writer_block_get_buf (block); + g_object_ref (self); + } + GST_OBJECT_UNLOCK (self); + + if (block) { + buffer = gst_buffer_new (); + GST_BUFFER_DATA (buffer) = buf; + GST_BUFFER_MALLOCDATA (buffer) = (guint8 *) block; + GST_BUFFER_FREE_FUNC (buffer) = + GST_DEBUG_FUNCPTR (gst_shm_sink_free_buffer); + GST_BUFFER_SIZE (buffer) = size; + GST_LOG_OBJECT (self, + "Allocated buffer of %u bytes from shared memory at %p", size, buf); + } else { + buffer = gst_buffer_new_and_alloc (size); + GST_LOG_OBJECT (self, "Not enough shared memory for buffer of %u bytes, " + "allocating using standard allocator", size); + } + + GST_BUFFER_OFFSET (buffer) = offset; + gst_buffer_set_caps (buffer, caps); + + *out_buf = buffer; + + return GST_FLOW_OK; +} + +static gpointer +pollthread_func (gpointer data) +{ + GstShmSink *self = GST_SHM_SINK (data); + GList *item; + + while (!self->stop) { + + if (gst_poll_wait (self->poll, GST_CLOCK_TIME_NONE) < 0) + return NULL; + + if (self->stop) + return NULL; + + if (gst_poll_fd_has_closed (self->poll, &self->serverpollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed read from shmsink"), + ("Control socket has closed")); + return NULL; + } + + if (gst_poll_fd_has_error (self->poll, &self->serverpollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed to read from shmsink"), + ("Control socket has error")); + return NULL; + } + + if (gst_poll_fd_can_read (self->poll, &self->serverpollfd)) { + ShmClient *client; + struct GstShmClient *gclient; + + GST_OBJECT_LOCK (self); + client = sp_writer_accept_client (self->pipe); + GST_OBJECT_UNLOCK (self); + + if (!client) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, + ("Failed to read from shmsink"), + ("Control socket returns wrong data")); + return NULL; + } + + gclient = g_slice_new (struct GstShmClient); + gclient->client = client; + gst_poll_fd_init (&gclient->pollfd); + gclient->pollfd.fd = sp_writer_get_client_fd (client); + gst_poll_add_fd (self->poll, &gclient->pollfd); + gst_poll_fd_ctl_read (self->poll, &gclient->pollfd, TRUE); + self->clients = g_list_prepend (self->clients, gclient); + g_signal_emit (self, signals[SIGNAL_CLIENT_CONNECTED], 0, + gclient->pollfd.fd); + /* we need to call gst_poll_wait before calling gst_poll_* status + functions on that new descriptor, so restart the loop, so _wait + will have been called on all elements of self->poll, whether + they have just been added or not. */ + continue; + } + + again: + for (item = self->clients; item; item = item->next) { + struct GstShmClient *gclient = item->data; + + if (gst_poll_fd_has_closed (self->poll, &gclient->pollfd)) { + GST_WARNING_OBJECT (self, "One client is gone, closing"); + goto close_client; + } + + if (gst_poll_fd_has_error (self->poll, &gclient->pollfd)) { + GST_WARNING_OBJECT (self, "One client fd has error, closing"); + goto close_client; + } + + if (gst_poll_fd_can_read (self->poll, &gclient->pollfd)) { + int rv; + + GST_OBJECT_LOCK (self); + rv = sp_writer_recv (self->pipe, gclient->client); + GST_OBJECT_UNLOCK (self); + + if (rv < 0) { + GST_WARNING_OBJECT (self, "One client has read error," + " closing (retval: %d errno: %d)", rv, errno); + goto close_client; + } + } + continue; + close_client: + GST_OBJECT_LOCK (self); + sp_writer_close_client (self->pipe, gclient->client); + GST_OBJECT_UNLOCK (self); + + gst_poll_remove_fd (self->poll, &gclient->pollfd); + self->clients = g_list_remove (self->clients, gclient); + + g_signal_emit (self, signals[SIGNAL_CLIENT_DISCONNECTED], 0, + gclient->pollfd.fd); + g_slice_free (struct GstShmClient, gclient); + + goto again; + } + + g_cond_broadcast (self->cond); + } + + return NULL; +} + +static gboolean +gst_shm_sink_event (GstBaseSink * bsink, GstEvent * event) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + GST_OBJECT_LOCK (self); + while (self->wait_for_connection && sp_writer_pending_writes (self->pipe) + && !self->unlock) + g_cond_wait (self->cond, GST_OBJECT_GET_LOCK (self)); + GST_OBJECT_UNLOCK (self); + break; + default: + break; + } + + return TRUE; +} + + +static gboolean +gst_shm_sink_unlock (GstBaseSink * bsink) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + + GST_OBJECT_LOCK (self); + self->unlock = TRUE; + GST_OBJECT_UNLOCK (self); + + g_cond_broadcast (self->cond); + return TRUE; +} + +static gboolean +gst_shm_sink_unlock_stop (GstBaseSink * bsink) +{ + GstShmSink *self = GST_SHM_SINK (bsink); + + GST_OBJECT_LOCK (self); + self->unlock = FALSE; + GST_OBJECT_UNLOCK (self); + + return TRUE; +} Index: gst-plugins-good0.10/sys/shm/gstshmsink.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/gstshmsink.h 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_SHM_SINK_H__ +#define __GST_SHM_SINK_H__ + +#include +#include + +#include "shmpipe.h" + +G_BEGIN_DECLS +#define GST_TYPE_SHM_SINK \ + (gst_shm_sink_get_type()) +#define GST_SHM_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SHM_SINK,GstShmSink)) +#define GST_SHM_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SHM_SINK,GstShmSinkClass)) +#define GST_IS_SHM_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SHM_SINK)) +#define GST_IS_SHM_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SHM_SINK)) +typedef struct _GstShmSink GstShmSink; +typedef struct _GstShmSinkClass GstShmSinkClass; + +struct _GstShmSink +{ + GstBaseSink element; + + gchar *socket_path; + + ShmPipe *pipe; + + guint perms; + guint size; + + GList *clients; + + GThread *pollthread; + GstPoll *poll; + GstPollFD serverpollfd; + + gboolean wait_for_connection; + gboolean stop; + gboolean unlock; + GstClockTime buffer_time; + + GCond *cond; +}; + +struct _GstShmSinkClass +{ + GstBaseSinkClass parent_class; +}; + +GType gst_shm_sink_get_type (void); + +G_END_DECLS +#endif /* __GST_SHM_SINK_H__ */ Index: gst-plugins-good0.10/sys/shm/gstshmsrc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/gstshmsrc.c 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,445 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstshmsrc.h" + +#include + +#include + +/* signals */ +enum +{ + LAST_SIGNAL +}; + +/* properties */ +enum +{ + PROP_0, + PROP_SOCKET_PATH, + PROP_IS_LIVE +}; + +struct GstShmBuffer +{ + char *buf; + GstShmPipe *pipe; +}; + + +GST_DEBUG_CATEGORY_STATIC (shmsrc_debug); +#define GST_CAT_DEFAULT shmsrc_debug + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_BOILERPLATE (GstShmSrc, gst_shm_src, GstPushSrc, GST_TYPE_PUSH_SRC); + +static void gst_shm_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_shm_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_shm_src_finalize (GObject * object); +static gboolean gst_shm_src_start (GstBaseSrc * bsrc); +static gboolean gst_shm_src_stop (GstBaseSrc * bsrc); +static GstFlowReturn gst_shm_src_create (GstPushSrc * psrc, + GstBuffer ** outbuf); +static gboolean gst_shm_src_unlock (GstBaseSrc * bsrc); +static gboolean gst_shm_src_unlock_stop (GstBaseSrc * bsrc); +static GstStateChangeReturn gst_shm_src_change_state (GstElement * element, + GstStateChange transition); + +static void gst_shm_pipe_inc (GstShmPipe * pipe); +static void gst_shm_pipe_dec (GstShmPipe * pipe); + +// static guint gst_shm_src_signals[LAST_SIGNAL] = { 0 }; + + +static void +gst_shm_src_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + + gst_element_class_set_details_simple (element_class, + "Shared Memory Source", + "Source", + "Receive data from the sharem memory sink", + "Olivier Crete "); +} + +static void +gst_shm_src_class_init (GstShmSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + GstPushSrcClass *gstpush_src_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstpush_src_class = (GstPushSrcClass *) klass; + + gobject_class->set_property = gst_shm_src_set_property; + gobject_class->get_property = gst_shm_src_get_property; + gobject_class->finalize = gst_shm_src_finalize; + + gstelement_class->change_state = gst_shm_src_change_state; + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_shm_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_shm_src_stop); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_shm_src_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_shm_src_unlock_stop); + + gstpush_src_class->create = gst_shm_src_create; + + g_object_class_install_property (gobject_class, PROP_SOCKET_PATH, + g_param_spec_string ("socket-path", + "Path to the control socket", + "The path to the control socket used to control the shared memory" + " transport", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_IS_LIVE, + g_param_spec_boolean ("is-live", "Is this a live source", + "True if the element cannot produce data in PAUSED", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + GST_DEBUG_CATEGORY_INIT (shmsrc_debug, "shmsrc", 0, "Shared Memory Source"); +} + +static void +gst_shm_src_init (GstShmSrc * self, GstShmSrcClass * g_class) +{ + self->poll = gst_poll_new (TRUE); + gst_poll_fd_init (&self->pollfd); +} + +static void +gst_shm_src_finalize (GObject * object) +{ + GstShmSrc *self = GST_SHM_SRC (object); + + gst_poll_free (self->poll); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +static void +gst_shm_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstShmSrc *self = GST_SHM_SRC (object); + + switch (prop_id) { + case PROP_SOCKET_PATH: + GST_OBJECT_LOCK (object); + if (self->pipe) { + GST_WARNING_OBJECT (object, "Can not modify socket path while the " + "element is playing"); + } else { + g_free (self->socket_path); + self->socket_path = g_value_dup_string (value); + } + GST_OBJECT_UNLOCK (object); + break; + case PROP_IS_LIVE: + gst_base_src_set_live (GST_BASE_SRC (object), + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_shm_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstShmSrc *self = GST_SHM_SRC (object); + + switch (prop_id) { + case PROP_SOCKET_PATH: + GST_OBJECT_LOCK (object); + g_value_set_string (value, self->socket_path); + GST_OBJECT_UNLOCK (object); + break; + case PROP_IS_LIVE: + g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (object))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_shm_src_start_reading (GstShmSrc * self) +{ + GstShmPipe *gstpipe = g_slice_new0 (GstShmPipe); + + gstpipe->use_count = 1; + gstpipe->src = gst_object_ref (self); + + if (!self->socket_path) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("No path specified for socket."), (NULL)); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "Opening socket %s", self->socket_path); + + GST_OBJECT_LOCK (self); + gstpipe->pipe = sp_client_open (self->socket_path); + GST_OBJECT_UNLOCK (self); + + if (!gstpipe->pipe) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE, + ("Could not open socket %s: %d %s", self->socket_path, errno, + strerror (errno)), (NULL)); + gst_shm_pipe_dec (gstpipe); + return FALSE; + } + + self->pipe = gstpipe; + + gst_poll_set_flushing (self->poll, FALSE); + + gst_poll_fd_init (&self->pollfd); + self->pollfd.fd = sp_get_fd (self->pipe->pipe); + gst_poll_add_fd (self->poll, &self->pollfd); + gst_poll_fd_ctl_read (self->poll, &self->pollfd, TRUE); + + return TRUE; +} + +static void +gst_shm_src_stop_reading (GstShmSrc * self) +{ + GST_DEBUG_OBJECT (self, "Stopping %p", self); + + if (self->pipe) { + gst_shm_pipe_dec (self->pipe); + self->pipe = NULL; + } + + gst_poll_remove_fd (self->poll, &self->pollfd); + gst_poll_fd_init (&self->pollfd); + + gst_poll_set_flushing (self->poll, TRUE); +} + +static gboolean +gst_shm_src_start (GstBaseSrc * bsrc) +{ + if (gst_base_src_is_live (bsrc)) + return TRUE; + else + return gst_shm_src_start_reading (GST_SHM_SRC (bsrc)); +} + +static gboolean +gst_shm_src_stop (GstBaseSrc * bsrc) +{ + if (!gst_base_src_is_live (bsrc)) + gst_shm_src_stop_reading (GST_SHM_SRC (bsrc)); + + return TRUE; +} + + +static void +free_buffer (gpointer data) +{ + struct GstShmBuffer *gsb = data; + g_return_if_fail (gsb->pipe != NULL); + g_return_if_fail (gsb->pipe->src != NULL); + + GST_LOG ("Freeing buffer %p", gsb->buf); + + GST_OBJECT_LOCK (gsb->pipe->src); + sp_client_recv_finish (gsb->pipe->pipe, gsb->buf); + GST_OBJECT_UNLOCK (gsb->pipe->src); + + gst_shm_pipe_dec (gsb->pipe); + + g_slice_free (struct GstShmBuffer, gsb); +} + +static GstFlowReturn +gst_shm_src_create (GstPushSrc * psrc, GstBuffer ** outbuf) +{ + GstShmSrc *self = GST_SHM_SRC (psrc); + gchar *buf = NULL; + int rv = 0; + struct GstShmBuffer *gsb; + + do { + if (gst_poll_wait (self->poll, GST_CLOCK_TIME_NONE) < 0) { + if (errno == EBUSY) + return GST_FLOW_WRONG_STATE; + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed to read from shmsrc"), + ("Poll failed on fd: %s", strerror (errno))); + return GST_FLOW_ERROR; + } + + if (self->unlocked) + return GST_FLOW_WRONG_STATE; + + if (gst_poll_fd_has_closed (self->poll, &self->pollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed to read from shmsrc"), + ("Control socket has closed")); + return GST_FLOW_ERROR; + } + + if (gst_poll_fd_has_error (self->poll, &self->pollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed to read from shmsrc"), + ("Control socket has error")); + return GST_FLOW_ERROR; + } + + if (gst_poll_fd_can_read (self->poll, &self->pollfd)) { + buf = NULL; + GST_LOG_OBJECT (self, "Reading from pipe"); + GST_OBJECT_LOCK (self); + rv = sp_client_recv (self->pipe->pipe, &buf); + GST_OBJECT_UNLOCK (self); + if (rv < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Failed to read from shmsrc"), + ("Error reading control data: %d", rv)); + return GST_FLOW_ERROR; + } + } + } while (buf == NULL); + + GST_LOG_OBJECT (self, "Got buffer %p of size %d", buf, rv); + + gsb = g_slice_new0 (struct GstShmBuffer); + gsb->buf = buf; + gsb->pipe = self->pipe; + gst_shm_pipe_inc (self->pipe); + + *outbuf = gst_buffer_new (); + GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_READONLY); + GST_BUFFER_DATA (*outbuf) = (guint8 *) buf; + GST_BUFFER_SIZE (*outbuf) = rv; + GST_BUFFER_MALLOCDATA (*outbuf) = (guint8 *) gsb; + GST_BUFFER_FREE_FUNC (*outbuf) = free_buffer; + + return GST_FLOW_OK; +} + +static GstStateChangeReturn +gst_shm_src_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstShmSrc *self = GST_SHM_SRC (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + if (gst_base_src_is_live (GST_BASE_SRC (element))) + if (!gst_shm_src_start_reading (self)) + return GST_STATE_CHANGE_FAILURE; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + if (gst_base_src_is_live (GST_BASE_SRC (element))) + gst_shm_src_stop_reading (self); + default: + break; + } + + return ret; +} + +static gboolean +gst_shm_src_unlock (GstBaseSrc * bsrc) +{ + GstShmSrc *self = GST_SHM_SRC (bsrc); + + self->unlocked = TRUE; + gst_poll_set_flushing (self->poll, TRUE); + + return TRUE; +} + +static gboolean +gst_shm_src_unlock_stop (GstBaseSrc * bsrc) +{ + GstShmSrc *self = GST_SHM_SRC (bsrc); + + self->unlocked = FALSE; + gst_poll_set_flushing (self->poll, FALSE); + + return TRUE; +} + +static void +gst_shm_pipe_inc (GstShmPipe * pipe) +{ + g_return_if_fail (pipe); + g_return_if_fail (pipe->src); + g_return_if_fail (pipe->use_count > 0); + + GST_OBJECT_LOCK (pipe->src); + pipe->use_count++; + GST_OBJECT_UNLOCK (pipe->src); +} + +static void +gst_shm_pipe_dec (GstShmPipe * pipe) +{ + g_return_if_fail (pipe); + g_return_if_fail (pipe->src); + g_return_if_fail (pipe->use_count > 0); + + GST_OBJECT_LOCK (pipe->src); + pipe->use_count--; + + if (pipe->use_count > 0) { + GST_OBJECT_UNLOCK (pipe->src); + return; + } + + if (pipe->pipe) + sp_close (pipe->pipe); + GST_OBJECT_UNLOCK (pipe->src); + + gst_object_unref (pipe->src); + g_slice_free (GstShmPipe, pipe); +} Index: gst-plugins-good0.10/sys/shm/gstshmsrc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/gstshmsrc.h 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_SHM_SRC_H__ +#define __GST_SHM_SRC_H__ + +#include +#include +#include + +#include "shmpipe.h" + +G_BEGIN_DECLS +#define GST_TYPE_SHM_SRC \ + (gst_shm_src_get_type()) +#define GST_SHM_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SHM_SRC,GstShmSrc)) +#define GST_SHM_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SHM_SRC,GstShmSrcClass)) +#define GST_IS_SHM_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SHM_SRC)) +#define GST_IS_SHM_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SHM_SRC)) +typedef struct _GstShmSrc GstShmSrc; +typedef struct _GstShmSrcClass GstShmSrcClass; +typedef struct _GstShmPipe GstShmPipe; + +struct _GstShmSrc +{ + GstPushSrc element; + + gchar *socket_path; + + GstShmPipe *pipe; + GstPoll *poll; + GstPollFD pollfd; + + + GstFlowReturn flow_return; + gboolean unlocked; +}; + +struct _GstShmSrcClass +{ + GstPushSrcClass parent_class; +}; + +GType gst_shm_src_get_type (void); + +struct _GstShmPipe { + int use_count; + + GstShmSrc *src; + ShmPipe *pipe; +}; + +G_END_DECLS +#endif /* __GST_SHM_SRC_H__ */ Index: gst-plugins-good0.10/sys/shm/shmalloc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/shmalloc.c 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,184 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "shmalloc.h" + +#include +#include +#include + +/* This is the allocated space to hold multiple blocks */ +struct _ShmAllocSpace +{ + /* The total size of this space */ + size_t size; + + /* chained list of the blocks contained in this space */ + ShmAllocBlock *blocks; +}; + +/* A single block of data */ +struct _ShmAllocBlock +{ + int use_count; + + /* Pointer back to the AllocSpace where this block is */ + ShmAllocSpace *space; + + /* The offset of this block in the alloc space */ + unsigned long offset; + /* The size of the block */ + unsigned long size; + + /* Pointer to the next block in the chain */ + ShmAllocBlock *next; +}; + + +ShmAllocSpace * +shm_alloc_space_new (size_t size) +{ + ShmAllocSpace *self = spalloc_new (ShmAllocSpace); + + memset (self, 0, sizeof (ShmAllocSpace)); + + self->size = size; + + return self; +} + +void +shm_alloc_space_free (ShmAllocSpace * self) +{ + assert (self && self->blocks == NULL); + spalloc_free (ShmAllocSpace, self); +} + + +ShmAllocBlock * +shm_alloc_space_alloc_block (ShmAllocSpace * self, unsigned long size) +{ + ShmAllocBlock *block; + ShmAllocBlock *item = NULL; + ShmAllocBlock *prev_item = NULL; + unsigned long prev_end_offset = 0; + + + for (item = self->blocks; item; item = item->next) { + unsigned long max_size = 0; + + max_size = item->offset - prev_end_offset; + + if (max_size >= size) + break; + + prev_end_offset = item->offset + item->size; + prev_item = item; + } + + /* Did not find space before an existing block */ + if (self->blocks && !item) { + /* Return NULL if there is no big enough space, otherwise, there is space + * at the end */ + if (self->size - prev_end_offset < size) + return NULL; + } + + block = spalloc_new (ShmAllocBlock); + memset (block, 0, sizeof (ShmAllocBlock)); + block->offset = prev_end_offset; + block->size = size; + block->use_count = 1; + block->space = self; + + if (prev_item) + prev_item->next = block; + else + self->blocks = block; + + block->next = item; + + return block; +} + +unsigned long +shm_alloc_space_alloc_block_get_offset (ShmAllocBlock * block) +{ + return block->offset; +} + +static void +shm_alloc_space_free_block (ShmAllocBlock * block) +{ + ShmAllocBlock *item = NULL; + ShmAllocBlock *prev_item = NULL; + ShmAllocSpace *self = block->space; + + for (item = self->blocks; item; item = item->next) { + if (item == block) { + if (prev_item) + prev_item->next = item->next; + else + self->blocks = item->next; + break; + } + prev_item = item; + } + + spalloc_free (ShmAllocBlock, block); +} + +ShmAllocBlock * +shm_alloc_space_block_get (ShmAllocSpace * self, unsigned long offset) +{ + ShmAllocBlock *block = NULL; + + for (block = self->blocks; block; block = block->next) { + if (block->offset <= offset && (block->offset + block->size) > offset) + return block; + } + + return NULL; +} + + +void +shm_alloc_space_block_inc (ShmAllocBlock * block) +{ + block->use_count++; +} + +void +shm_alloc_space_block_dec (ShmAllocBlock * block) +{ + block->use_count--; + + if (block->use_count <= 0) + shm_alloc_space_free_block (block); +} Index: gst-plugins-good0.10/sys/shm/shmalloc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/shmalloc.h 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,74 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#ifndef __SHMALLOC_H__ +#define __SHMALLOC_H__ + +#ifdef SHM_PIPE_USE_GLIB +#include + +#define spalloc_new(type) g_slice_new (type) +#define spalloc_alloc(size) g_slice_alloc (size) + +#define spalloc_free(type, buf) g_slice_free (type, buf) +#define spalloc_free1(size, buf) g_slice_free1 (size, buf) + +#else + +#define spalloc_new(type) malloc (sizeof (type)) +#define spalloc_alloc(size) malloc (size) + +#define spalloc_free(type, buf) free (buf) +#define spalloc_free1(size, buf) free (buf) + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ShmAllocSpace ShmAllocSpace; +typedef struct _ShmAllocBlock ShmAllocBlock; + +ShmAllocSpace *shm_alloc_space_new (size_t size); +void shm_alloc_space_free (ShmAllocSpace * self); + + +ShmAllocBlock *shm_alloc_space_alloc_block (ShmAllocSpace * self, + unsigned long size); +unsigned long shm_alloc_space_alloc_block_get_offset (ShmAllocBlock *block); + +void shm_alloc_space_block_inc (ShmAllocBlock * block); +void shm_alloc_space_block_dec (ShmAllocBlock * block); +ShmAllocBlock * shm_alloc_space_block_get (ShmAllocSpace * space, + unsigned long offset); + + +#ifdef __cplusplus +} +#endif + +#endif /* __SHMALLOC_H__ */ Index: gst-plugins-good0.10/sys/shm/shmpipe.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/shmpipe.c 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,914 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "shmpipe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shmalloc.h" + +/* + * The protocol over the pipe is in packets + * + * The defined types are: + * type 1: new shm area + * Area length + * Size of path (followed by path) + * + * type 2: Close shm area: + * No payload + * + * type 3: shm buffer + * offset + * bufsize + * + * type 4: ack buffer + * offset + * + * Type 4 goes from the client to the server + * The rest are from the server to the client + * The client should never write in the SHM + */ + + +#define LISTEN_BACKLOG 10 + +enum +{ + COMMAND_NEW_SHM_AREA = 1, + COMMAND_CLOSE_SHM_AREA = 2, + COMMAND_NEW_BUFFER = 3, + COMMAND_ACK_BUFFER = 4 +}; + +typedef struct _ShmArea ShmArea; + +struct _ShmArea +{ + int id; + + int use_count; + + int shm_fd; + + char *shm_area_buf; + size_t shm_area_len; + + char *shm_area_name; + + ShmAllocSpace *allocspace; + + ShmArea *next; +}; + +struct _ShmBuffer +{ + int use_count; + + ShmArea *shm_area; + unsigned long offset; + size_t size; + + ShmAllocBlock *ablock; + + ShmBuffer *next; + + int num_clients; + int clients[0]; + + uint64_t tag; +}; + + +struct _ShmPipe +{ + int main_socket; + char *socket_path; + int use_count; + void *data; + + ShmArea *shm_area; + + int next_area_id; + + ShmBuffer *buffers; + + int num_clients; + ShmClient *clients; + + mode_t perms; +}; + +struct _ShmClient +{ + int fd; + + ShmClient *next; +}; + +struct _ShmBlock +{ + ShmPipe *pipe; + ShmArea *area; + ShmAllocBlock *ablock; +}; + +struct CommandBuffer +{ + unsigned int type; + int area_id; + + union + { + struct + { + size_t size; + unsigned int path_size; + /* Followed by path */ + } new_shm_area; + struct + { + unsigned long offset; + unsigned long size; + } buffer; + struct + { + unsigned long offset; + } ack_buffer; + } payload; +}; + +static ShmArea *sp_open_shm (char *path, int id, mode_t perms, size_t size); +static void sp_close_shm (ShmArea * area); +static int sp_shmbuf_dec (ShmPipe * self, ShmBuffer * buf, + ShmBuffer * prev_buf); +static void sp_shm_area_dec (ShmPipe * self, ShmArea * area); + + + +#define RETURN_ERROR(format, ...) do { \ + fprintf (stderr, format, __VA_ARGS__); \ + sp_close (self); \ + return NULL; \ + } while (0) + +ShmPipe * +sp_writer_create (const char *path, size_t size, mode_t perms) +{ + ShmPipe *self = spalloc_new (ShmPipe); + int flags; + struct sockaddr_un sock_un; + int i = 0; + + memset (self, 0, sizeof (ShmPipe)); + + self->main_socket = socket (PF_UNIX, SOCK_STREAM, 0); + self->use_count = 1; + + if (self->main_socket < 0) + RETURN_ERROR ("Could not create socket (%d): %s\n", errno, + strerror (errno)); + + flags = fcntl (self->main_socket, F_GETFL, 0); + if (flags < 0) + RETURN_ERROR ("fcntl(F_GETFL) failed (%d): %s\n", errno, strerror (errno)); + + if (fcntl (self->main_socket, F_SETFL, flags | O_NONBLOCK | FD_CLOEXEC) < 0) + RETURN_ERROR ("fcntl(F_SETFL) failed (%d): %s\n", errno, strerror (errno)); + + sock_un.sun_family = AF_UNIX; + strncpy (sock_un.sun_path, path, sizeof (sock_un.sun_path) - 1); + + while (bind (self->main_socket, (struct sockaddr *) &sock_un, + sizeof (struct sockaddr_un)) < 0) { + if (errno != EADDRINUSE) + RETURN_ERROR ("bind() failed (%d): %s\n", errno, strerror (errno)); + + if (i > 256) + RETURN_ERROR ("Could not find a free socket name for %s", path); + + snprintf (sock_un.sun_path, sizeof (sock_un.sun_path), "%s.%d", path, i); + i++; + } + + self->socket_path = strdup (sock_un.sun_path); + + if (listen (self->main_socket, LISTEN_BACKLOG) < 0) + RETURN_ERROR ("listen() failed (%d): %s\n", errno, strerror (errno)); + + self->shm_area = sp_open_shm (NULL, ++self->next_area_id, perms, size); + + self->perms = perms; + + if (!self->shm_area) + RETURN_ERROR ("Could not open shm area (%d): %s", errno, strerror (errno)); + + return self; +} + +#undef RETURN_ERROR + +#define RETURN_ERROR(format, ...) do { \ + fprintf (stderr, format, __VA_ARGS__); \ + area->use_count--; \ + sp_close_shm (area); \ + return NULL; \ + } while (0) + +/** + * sp_open_shm: + * @path: Path of the shm area for a reader, + * NULL if this is a writer (then it will allocate its own path) + * + * Opens a ShmArea + */ + +static ShmArea * +sp_open_shm (char *path, int id, mode_t perms, size_t size) +{ + ShmArea *area = spalloc_new (ShmArea); + char tmppath[32]; + int flags; + int prot; + int i = 0; + + memset (area, 0, sizeof (ShmArea)); + + area->use_count = 1; + + area->shm_area_len = size; + + + if (path) + flags = O_RDONLY; + else + flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL; + + area->shm_fd = -1; + + if (path) { + area->shm_fd = shm_open (path, flags, perms); + } else { + do { + snprintf (tmppath, sizeof (tmppath), "/shmpipe.%5d.%5d", getpid (), i++); + area->shm_fd = shm_open (tmppath, flags, perms); + } while (area->shm_fd < 0 && errno == EEXIST); + } + + if (area->shm_fd < 0) + RETURN_ERROR ("shm_open failed on %s (%d): %s\n", + path ? path : tmppath, errno, strerror (errno)); + + if (!path) { + area->shm_area_name = strdup (tmppath); + + if (ftruncate (area->shm_fd, size)) + RETURN_ERROR ("Could not resize memory area to header size," + " ftruncate failed (%d): %s\n", errno, strerror (errno)); + + prot = PROT_READ | PROT_WRITE; + } else { + prot = PROT_READ; + } + + area->shm_area_buf = mmap (NULL, size, prot, MAP_SHARED, area->shm_fd, 0); + + if (area->shm_area_buf == MAP_FAILED) + RETURN_ERROR ("mmap failed (%d): %s\n", errno, strerror (errno)); + + area->id = id; + + if (!path) + area->allocspace = shm_alloc_space_new (area->shm_area_len); + + return area; +} + +#undef RETURN_ERROR + +static void +sp_close_shm (ShmArea * area) +{ + assert (area->use_count == 0); + + if (area->allocspace) + shm_alloc_space_free (area->allocspace); + + if (area->shm_area_buf != MAP_FAILED) + munmap (area->shm_area_buf, area->shm_area_len); + + if (area->shm_fd >= 0) + close (area->shm_fd); + + if (area->shm_area_name) { + shm_unlink (area->shm_area_name); + free (area->shm_area_name); + } + + spalloc_free (ShmArea, area); +} + +static void +sp_shm_area_inc (ShmArea * area) +{ + area->use_count++; +} + +static void +sp_shm_area_dec (ShmPipe * self, ShmArea * area) +{ + assert (area->use_count > 0); + area->use_count--; + + if (area->use_count == 0) { + ShmArea *item = NULL; + ShmArea *prev_item = NULL; + + for (item = self->shm_area; item; item = item->next) { + if (item == area) { + if (prev_item) + prev_item->next = item->next; + else + self->shm_area = item->next; + break; + } + prev_item = item; + } + assert (item); + + sp_close_shm (area); + } +} + +void * +sp_get_data (ShmPipe * self) +{ + return self->data; +} + +void +sp_set_data (ShmPipe * self, void *data) +{ + self->data = data; +} + +static void +sp_inc (ShmPipe * self) +{ + self->use_count++; +} + +static void +sp_dec (ShmPipe * self) +{ + self->use_count--; + + if (self->use_count > 0) + return; + + while (self->shm_area) + sp_shm_area_dec (self, self->shm_area); + + spalloc_free (ShmPipe, self); +} + +void +sp_close (ShmPipe * self) +{ + if (self->main_socket >= 0) + close (self->main_socket); + + if (self->socket_path) { + unlink (self->socket_path); + free (self->socket_path); + } + + while (self->clients) + sp_writer_close_client (self, self->clients); + + sp_dec (self); +} + +int +sp_writer_setperms_shm (ShmPipe * self, mode_t perms) +{ + int ret = 0; + ShmArea *area; + + self->perms = perms; + for (area = self->shm_area; area; area = area->next) + ret |= fchmod (area->shm_fd, perms); + + return ret; +} + +static int +send_command (int fd, struct CommandBuffer *cb, unsigned short int type, + int area_id) +{ + cb->type = type; + cb->area_id = area_id; + + if (send (fd, cb, sizeof (struct CommandBuffer), MSG_NOSIGNAL) != + sizeof (struct CommandBuffer)) + return 0; + + return 1; +} + +int +sp_writer_resize (ShmPipe * self, size_t size) +{ + ShmArea *newarea; + ShmArea *old_current; + ShmClient *client; + int c = 0; + int pathlen; + + if (self->shm_area->shm_area_len == size) + return 0; + + newarea = sp_open_shm (NULL, ++self->next_area_id, self->perms, size); + + if (!newarea) + return -1; + + old_current = self->shm_area; + newarea->next = self->shm_area; + self->shm_area = newarea; + + pathlen = strlen (newarea->shm_area_name) + 1; + + for (client = self->clients; client; client = client->next) { + struct CommandBuffer cb = { 0 }; + + if (!send_command (client->fd, &cb, COMMAND_CLOSE_SHM_AREA, + old_current->id)) + continue; + + cb.payload.new_shm_area.size = newarea->shm_area_len; + cb.payload.new_shm_area.path_size = pathlen; + if (!send_command (client->fd, &cb, COMMAND_NEW_SHM_AREA, newarea->id)) + continue; + + if (send (client->fd, newarea->shm_area_name, pathlen, MSG_NOSIGNAL) != + pathlen) + continue; + c++; + } + + sp_shm_area_dec (self, old_current); + + + return c; +} + +ShmBlock * +sp_writer_alloc_block (ShmPipe * self, size_t size) +{ + ShmBlock *block; + ShmAllocBlock *ablock = + shm_alloc_space_alloc_block (self->shm_area->allocspace, size); + + if (!ablock) + return NULL; + + block = spalloc_new (ShmBlock); + sp_shm_area_inc (self->shm_area); + block->pipe = self; + block->area = self->shm_area; + block->ablock = ablock; + sp_inc (self); + return block; +} + +char * +sp_writer_block_get_buf (ShmBlock * block) +{ + return block->area->shm_area_buf + + shm_alloc_space_alloc_block_get_offset (block->ablock); +} + +ShmPipe * +sp_writer_block_get_pipe (ShmBlock * block) +{ + return block->pipe; +} + +void +sp_writer_free_block (ShmBlock * block) +{ + shm_alloc_space_block_dec (block->ablock); + sp_shm_area_dec (block->pipe, block->area); + sp_dec (block->pipe); + spalloc_free (ShmBlock, block); +} + +/* Returns the number of client this has successfully been sent to */ + +int +sp_writer_send_buf (ShmPipe * self, char *buf, size_t size, uint64_t tag) +{ + ShmArea *area = NULL; + unsigned long offset = 0; + unsigned long bsize = size; + ShmBuffer *sb; + ShmClient *client = NULL; + ShmAllocBlock *ablock = NULL; + int i = 0; + int c = 0; + + if (self->num_clients == 0) + return 0; + + for (area = self->shm_area; area; area = area->next) { + if (buf >= area->shm_area_buf && + buf < (area->shm_area_buf + area->shm_area_len)) { + offset = buf - area->shm_area_buf; + ablock = shm_alloc_space_block_get (area->allocspace, offset); + assert (ablock); + break; + } + } + + if (!ablock) + return -1; + + sb = spalloc_alloc (sizeof (ShmBuffer) + sizeof (int) * self->num_clients); + memset (sb, 0, sizeof (ShmBuffer)); + memset (sb->clients, -1, sizeof (int) * self->num_clients); + sb->shm_area = area; + sb->offset = offset; + sb->size = size; + sb->num_clients = self->num_clients; + sb->ablock = ablock; + sb->tag = tag; + + for (client = self->clients; client; client = client->next) { + struct CommandBuffer cb = { 0 }; + cb.payload.buffer.offset = offset; + cb.payload.buffer.size = bsize; + if (!send_command (client->fd, &cb, COMMAND_NEW_BUFFER, self->shm_area->id)) + continue; + sb->clients[i++] = client->fd; + c++; + } + + if (c == 0) { + spalloc_free1 (sizeof (ShmBuffer) + sizeof (int) * sb->num_clients, sb); + return 0; + } + + sp_shm_area_inc (area); + shm_alloc_space_block_inc (ablock); + + sb->use_count = c; + + sb->next = self->buffers; + self->buffers = sb; + + return c; +} + +static int +recv_command (int fd, struct CommandBuffer *cb) +{ + int retval; + + retval = recv (fd, cb, sizeof (struct CommandBuffer), MSG_DONTWAIT); + if (retval == sizeof (struct CommandBuffer)) { + return 1; + } else { + return 0; + } +} + +long int +sp_client_recv (ShmPipe * self, char **buf) +{ + char *area_name = NULL; + ShmArea *newarea; + ShmArea *area; + struct CommandBuffer cb; + int retval; + + if (!recv_command (self->main_socket, &cb)) + return -1; + + switch (cb.type) { + case COMMAND_NEW_SHM_AREA: + assert (cb.payload.new_shm_area.path_size > 0); + assert (cb.payload.new_shm_area.size > 0); + + area_name = malloc (cb.payload.new_shm_area.path_size); + retval = recv (self->main_socket, area_name, + cb.payload.new_shm_area.path_size, 0); + if (retval != cb.payload.new_shm_area.path_size) { + free (area_name); + return -3; + } + + newarea = sp_open_shm (area_name, cb.area_id, 0, + cb.payload.new_shm_area.size); + free (area_name); + if (!newarea) + return -4; + + newarea->next = self->shm_area; + self->shm_area = newarea; + break; + + case COMMAND_CLOSE_SHM_AREA: + for (area = self->shm_area; area; area = area->next) { + if (area->id == cb.area_id) { + sp_shm_area_dec (self, area); + break; + } + } + break; + + case COMMAND_NEW_BUFFER: + assert (buf); + for (area = self->shm_area; area; area = area->next) { + if (area->id == cb.area_id) { + *buf = area->shm_area_buf + cb.payload.buffer.offset; + sp_shm_area_inc (area); + return cb.payload.buffer.size; + } + } + return -23; + + default: + return -99; + } + + return 0; +} + +int +sp_writer_recv (ShmPipe * self, ShmClient * client) +{ + ShmBuffer *buf = NULL, *prev_buf = NULL; + struct CommandBuffer cb; + + if (!recv_command (client->fd, &cb)) + return -1; + + switch (cb.type) { + case COMMAND_ACK_BUFFER: + + for (buf = self->buffers; buf; buf = buf->next) { + if (buf->shm_area->id == cb.area_id && + buf->offset == cb.payload.ack_buffer.offset) { + sp_shmbuf_dec (self, buf, prev_buf); + break; + } + prev_buf = buf; + } + + if (!buf) + return -2; + + break; + default: + return -99; + } + + return 0; +} + +int +sp_client_recv_finish (ShmPipe * self, char *buf) +{ + ShmArea *shm_area = NULL; + unsigned long offset; + struct CommandBuffer cb = { 0 }; + + for (shm_area = self->shm_area; shm_area; shm_area = shm_area->next) { + if (buf >= shm_area->shm_area_buf && + buf < shm_area->shm_area_buf + shm_area->shm_area_len) + break; + } + + assert (shm_area); + + offset = buf - shm_area->shm_area_buf; + + sp_shm_area_dec (self, shm_area); + + cb.payload.ack_buffer.offset = offset; + return send_command (self->main_socket, &cb, COMMAND_ACK_BUFFER, + self->shm_area->id); +} + +ShmPipe * +sp_client_open (const char *path) +{ + ShmPipe *self = spalloc_new (ShmPipe); + struct sockaddr_un sock_un; + + memset (self, 0, sizeof (ShmPipe)); + + self->main_socket = socket (PF_UNIX, SOCK_STREAM, 0); + self->use_count = 1; + + if (self->main_socket < 0) + goto error; + + sock_un.sun_family = AF_UNIX; + strncpy (sock_un.sun_path, path, sizeof (sock_un.sun_path) - 1); + + if (connect (self->main_socket, (struct sockaddr *) &sock_un, + sizeof (struct sockaddr_un)) < 0) + goto error; + + return self; + +error: + sp_close (self); + return NULL; +} + + +ShmClient * +sp_writer_accept_client (ShmPipe * self) +{ + ShmClient *client = NULL; + int fd; + struct CommandBuffer cb = { 0 }; + int pathlen = strlen (self->shm_area->shm_area_name) + 1; + + + fd = accept (self->main_socket, NULL, NULL); + + if (fd < 0) { + fprintf (stderr, "Could not client connection"); + return NULL; + } + + cb.payload.new_shm_area.size = self->shm_area->shm_area_len; + cb.payload.new_shm_area.path_size = pathlen; + if (!send_command (fd, &cb, COMMAND_NEW_SHM_AREA, self->shm_area->id)) { + fprintf (stderr, "Sending new shm area failed: %s", strerror (errno)); + goto error; + } + + if (send (fd, self->shm_area->shm_area_name, pathlen, MSG_NOSIGNAL) != + pathlen) { + fprintf (stderr, "Sending new shm area path failed: %s", strerror (errno)); + goto error; + } + + client = spalloc_new (ShmClient); + client->fd = fd; + + /* Prepend ot linked list */ + client->next = self->clients; + self->clients = client; + self->num_clients++; + + return client; + +error: + close (fd); + return NULL; +} + +static int +sp_shmbuf_dec (ShmPipe * self, ShmBuffer * buf, ShmBuffer * prev_buf) +{ + buf->use_count--; + + if (buf->use_count == 0) { + /* Remove from linked list */ + if (prev_buf) + prev_buf->next = buf->next; + else + self->buffers = buf->next; + + shm_alloc_space_block_dec (buf->ablock); + sp_shm_area_dec (self, buf->shm_area); + spalloc_free1 (sizeof (ShmBuffer) + sizeof (int) * buf->num_clients, buf); + return 0; + } + + return 1; +} + +void +sp_writer_close_client (ShmPipe * self, ShmClient * client) +{ + ShmBuffer *buffer = NULL, *prev_buf = NULL; + ShmClient *item = NULL, *prev_item = NULL; + + close (client->fd); + +again: + for (buffer = self->buffers; buffer; buffer = buffer->next) { + int i; + + for (i = 0; i < buffer->num_clients; i++) { + if (buffer->clients[i] == client->fd) { + buffer->clients[i] = -1; + if (!sp_shmbuf_dec (self, buffer, prev_buf)) + goto again; + break; + } + prev_buf = buffer; + } + } + + for (item = self->clients; item; item = item->next) { + if (item == client) + break; + prev_item = item; + } + assert (item); + + if (prev_item) + prev_item->next = client->next; + else + self->clients = client->next; + + self->num_clients--; + + spalloc_free (ShmClient, client); +} + +int +sp_get_fd (ShmPipe * self) +{ + return self->main_socket; +} + +int +sp_writer_get_client_fd (ShmClient * client) +{ + return client->fd; +} + +int +sp_writer_pending_writes (ShmPipe * self) +{ + return (self->buffers != NULL); +} + +const char * +sp_writer_get_path (ShmPipe * pipe) +{ + return pipe->socket_path; +} + +ShmBuffer * +sp_writer_get_pending_buffers (ShmPipe * self) +{ + return self->buffers; +} + +ShmBuffer * +sp_writer_get_next_buffer (ShmBuffer * buffer) +{ + return buffer->next; +} + +uint64_t +sp_writer_buf_get_tag (ShmBuffer * buffer) +{ + return buffer->tag; +} Index: gst-plugins-good0.10/sys/shm/shmpipe.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/sys/shm/shmpipe.h 2012-02-09 13:57:29.572435080 +0200 @@ -0,0 +1,117 @@ +/* GStreamer + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete Nokia Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * None of this code is thread safe, if you want to use it in a + * multi-threaded context, please protect it with a mutex. + * + * First, create a writer with sp_writer_create(), then select() on + * the socket returned by sp_get_fd(). If the socket is closed or any + * function returns an error, the app should call sp_close() and + * assume the other side is dead. The writer calls + * sp_writer_accept_client() when there is something to read from the + * main server fd. This returns a new ShmClient (representing a client + * connection), the writer needs to do a select() on the socket + * returned by sp_writer_get_client_fd(). If it gets an error on that + * socket, it calls sp_writer_close_client(). If there is something to + * read, it calls sp_writer_recv(). + * + * The writer allocates a block containing a free buffer with + * sp_writer_alloc_block(), then writes something in the buffer + * (retrieved with sp_writer_block_get_buf(), then calls + * sp_writer_send_buf() to send the buffer or a subsection to the + * other side. When it is done with the block, it calls + * sp_writer_free_block(). If alloc fails, then the server must wait + * for events on the client fd (the ones where sp_writer_recv() is + * called), and then try to re-alloc. + * + * The reader (client) connect to the writer with sp_client_open() And + * select()s on the fd from sp_get_fd() until there is something to + * read. Then they must read using sp_client_recv() which will return + * the size of the buffer (positive) if there is a valid buffer (which + * is read only). It will return 0 if it is an internal message and a + * negative number if there was an error. If there was an error, the + * application must close the pipe with sp_close() and assume that all + * buffers are no longer valid. If was valid buffer was received, the + * client must release it with sp_client_recv_finish() when it is done + * reading from it. + */ + + +#ifndef __SHMPIPE_H__ +#define __SHMPIPE_H__ + +#include +#include +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ShmClient ShmClient; +typedef struct _ShmPipe ShmPipe; +typedef struct _ShmBlock ShmBlock; +typedef struct _ShmBuffer ShmBuffer; + +ShmPipe *sp_writer_create (const char *path, size_t size, mode_t perms); +const char *sp_writer_get_path (ShmPipe *pipe); +void sp_close (ShmPipe * self); +void *sp_get_data (ShmPipe * self); +void sp_set_data (ShmPipe * self, void *data); + +int sp_writer_setperms_shm (ShmPipe * self, mode_t perms); +int sp_writer_resize (ShmPipe * self, size_t size); + +int sp_get_fd (ShmPipe * self); +int sp_writer_get_client_fd (ShmClient * client); + +ShmBlock *sp_writer_alloc_block (ShmPipe * self, size_t size); +void sp_writer_free_block (ShmBlock *block); +int sp_writer_send_buf (ShmPipe * self, char *buf, size_t size, uint64_t tag); +char *sp_writer_block_get_buf (ShmBlock *block); +ShmPipe *sp_writer_block_get_pipe (ShmBlock *block); + +ShmClient * sp_writer_accept_client (ShmPipe * self); +void sp_writer_close_client (ShmPipe *self, ShmClient * client); +int sp_writer_recv (ShmPipe * self, ShmClient * client); + +int sp_writer_pending_writes (ShmPipe * self); + +ShmPipe *sp_client_open (const char *path); +long int sp_client_recv (ShmPipe * self, char **buf); +int sp_client_recv_finish (ShmPipe * self, char *buf); + +ShmBuffer *sp_writer_get_pending_buffers (ShmPipe * self); +ShmBuffer *sp_writer_get_next_buffer (ShmBuffer * buffer); +uint64_t sp_writer_buf_get_tag (ShmBuffer * buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* __SHMPIPE_H__ */ Index: gst-plugins-good0.10/configure.ac =================================================================== --- gst-plugins-good0.10.orig/configure.ac 2012-02-09 13:57:01.663599934 +0200 +++ gst-plugins-good0.10/configure.ac 2012-02-09 13:57:02.791633689 +0200 @@ -702,6 +702,13 @@ AG_GST_CHECK_XV ]) +dnl check for shm_open (for shm plugin) +translit(dnm, m, l) AM_CONDITIONAL(USE_SHM, true) +AG_GST_CHECK_FEATURE(SHM, [POSIX shared memory source and sink], shm, [ + AC_CHECK_LIB(rt, shm_open, HAVE_SHM=yes, HAVE_SHM=no) +]) + + dnl *** ext plug-ins *** dnl keep this list sorted alphabetically ! @@ -1187,6 +1194,7 @@ sys/v4l2/Makefile sys/waveform/Makefile sys/ximage/Makefile +sys/shm/Makefile farsight/Makefile farsight/liveadder/Makefile farsight/dtmf/Makefile debian/patches/series0000664000000000000000000000053712311057042012034 0ustar 0001-pulsesrc-Listen-to-source-output-events-not-sink-inp.patch 04_move_farsight_plugins_to_good.patch 05_move_shm_to_good.patch 07_move-camerabin.patch git_ring_buffer_null_check.patch git_new_v4l_building.patch 0001-fix-v4l2_munmap.patch 0001-v4l2-fix-compilation-against-newer-kernel-headers-as.patch 577d87300906f961d13f76b44ba60fc40f0c637a.patch debian/patches/04_move_farsight_plugins_to_good.patch0000664000000000000000000106563412061315514020270 0ustar Description: Move plugins required by farsight to -good Bug-Ubuntu: https://bugs.launchpad.net/bugs/388898 Bug: http://bugzilla.gnome.org/show_bug.cgi?id=587219 Origin: vendor, https://bugzilla.redhat.com/show_bug.cgi?id=507009 Origin: rebased on gst-plugins-bad 0.10.18 Index: gst-plugins-good0.10/configure.ac =================================================================== --- gst-plugins-good0.10.orig/configure.ac 2012-02-09 13:53:21.489011440 +0200 +++ gst-plugins-good0.10/configure.ac 2012-02-09 13:54:47.443583536 +0200 @@ -1187,6 +1187,11 @@ sys/v4l2/Makefile sys/waveform/Makefile sys/ximage/Makefile +farsight/Makefile +farsight/liveadder/Makefile +farsight/dtmf/Makefile +farsight/rtpmux/Makefile +farsight/autoconvert/Makefile po/Makefile.in tests/Makefile tests/check/Makefile Index: gst-plugins-good0.10/farsight/autoconvert/gstautoconvert.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/gstautoconvert.c 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,1544 @@ +/* GStreamer + * + * Copyright 2007-2008 Collabora Ltd + * @author: Olivier Crete + * Copyright 2007-2008 Nokia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-autoconvert + * + * The #autoconvert element has one sink and one source pad. It will look for + * other elements that also have one sink and one source pad. + * It will then pick an element that matches the caps on both sides. + * If the caps change, it may change the selected element if the current one + * no longer matches the caps. + * + * The list of element it will look into can be specified in the + * #GstAutoConvert::factories property, otherwise it will look at all available + * elements. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstautoconvert.h" + +#include + +GST_DEBUG_CATEGORY (autoconvert_debug); +#define GST_CAT_DEFAULT (autoconvert_debug) + +#define GST_AUTOCONVERT_LOCK(ac) GST_OBJECT_LOCK (ac) +#define GST_AUTOCONVERT_UNLOCK(ac) GST_OBJECT_UNLOCK (ac) + +/* elementfactory information */ +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sink_internal_template = +GST_STATIC_PAD_TEMPLATE ("sink_internal", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_internal_template = +GST_STATIC_PAD_TEMPLATE ("src_internal", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +/* GstAutoConvert signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_FACTORIES +}; + +static void gst_auto_convert_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_auto_convert_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_auto_convert_dispose (GObject * object); + +static GstStateChangeReturn gst_auto_convert_change_state (GstElement * element, + GstStateChange transition); + +static GstElement *gst_auto_convert_get_subelement (GstAutoConvert * + autoconvert, gboolean query_only); +static GstPad *gst_auto_convert_get_internal_sinkpad (GstAutoConvert * + autoconvert); +static GstPad *gst_auto_convert_get_internal_srcpad (GstAutoConvert * + autoconvert); + +static GstIterator *gst_auto_convert_iterate_internal_links (GstPad * pad); + +static gboolean gst_auto_convert_sink_setcaps (GstPad * pad, GstCaps * caps); +static GstCaps *gst_auto_convert_sink_getcaps (GstPad * pad); +static GstFlowReturn gst_auto_convert_sink_chain (GstPad * pad, + GstBuffer * buffer); +static gboolean gst_auto_convert_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_auto_convert_sink_query (GstPad * pad, GstQuery * query); +static const GstQueryType *gst_auto_convert_sink_query_type (GstPad * pad); +static GstFlowReturn gst_auto_convert_sink_buffer_alloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); +static void gst_auto_convert_sink_fixatecaps (GstPad * pad, GstCaps * caps); + +static gboolean gst_auto_convert_src_event (GstPad * pad, GstEvent * event); +static gboolean gst_auto_convert_src_query (GstPad * pad, GstQuery * query); +static const GstQueryType *gst_auto_convert_src_query_type (GstPad * pad); + +static GstFlowReturn gst_auto_convert_internal_sink_chain (GstPad * pad, + GstBuffer * buffer); +static gboolean gst_auto_convert_internal_sink_event (GstPad * pad, + GstEvent * event); +static gboolean gst_auto_convert_internal_sink_query (GstPad * pad, + GstQuery * query); +static const GstQueryType *gst_auto_convert_internal_sink_query_type (GstPad * + pad); +static GstCaps *gst_auto_convert_internal_sink_getcaps (GstPad * pad); +static GstFlowReturn gst_auto_convert_internal_sink_buffer_alloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); +static void gst_auto_convert_internal_sink_fixatecaps (GstPad * pad, + GstCaps * caps); + +static gboolean gst_auto_convert_internal_src_event (GstPad * pad, + GstEvent * event); +static gboolean gst_auto_convert_internal_src_query (GstPad * pad, + GstQuery * query); +static const GstQueryType *gst_auto_convert_internal_src_query_type (GstPad * + pad); + +static GList *gst_auto_convert_load_factories (GstAutoConvert * autoconvert); +static GstElement + * gst_auto_convert_get_or_make_element_from_factory (GstAutoConvert * + autoconvert, GstElementFactory * factory); +static gboolean gst_auto_convert_activate_element (GstAutoConvert * autoconvert, + GstElement * element, GstCaps * caps); + +static GQuark internal_srcpad_quark = 0; +static GQuark internal_sinkpad_quark = 0; +static GQuark parent_quark = 0; + +static void +gst_auto_convert_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (autoconvert_debug, "autoconvert", 0, + "Auto convert based on caps"); + + internal_srcpad_quark = g_quark_from_static_string ("internal_srcpad"); + internal_sinkpad_quark = g_quark_from_static_string ("internal_sinkpad"); + parent_quark = g_quark_from_static_string ("parent"); +} + +GST_BOILERPLATE_FULL (GstAutoConvert, gst_auto_convert, GstBin, + GST_TYPE_BIN, gst_auto_convert_do_init); + +static void +gst_auto_convert_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + + gst_element_class_set_details_simple (element_class, + "Select convertor based on caps", "Generic/Bin", + "Selects the right transform element based on the caps", + "Olivier Crete "); +} + +static void +gst_auto_convert_class_init (GstAutoConvertClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_auto_convert_dispose); + + gobject_class->set_property = gst_auto_convert_set_property; + gobject_class->get_property = gst_auto_convert_get_property; + + g_object_class_install_property (gobject_class, PROP_FACTORIES, + g_param_spec_pointer ("factories", + "GList of GstElementFactory", + "GList of GstElementFactory objects to pick from (the element takes" + " ownership of the list (NULL means it will go through all possible" + " elements), can only be set once", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_auto_convert_change_state); +} + +static void +gst_auto_convert_init (GstAutoConvert * autoconvert, + GstAutoConvertClass * klass) +{ + autoconvert->sinkpad = + gst_pad_new_from_static_template (&sinktemplate, "sink"); + autoconvert->srcpad = gst_pad_new_from_static_template (&srctemplate, "src"); + + gst_pad_set_setcaps_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_setcaps)); + gst_pad_set_getcaps_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_getcaps)); + gst_pad_set_chain_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_chain)); + gst_pad_set_event_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_event)); + gst_pad_set_query_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_query)); + gst_pad_set_query_type_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_query_type)); + gst_pad_set_bufferalloc_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_sink_buffer_alloc)); + gst_pad_set_iterate_internal_links_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_iterate_internal_links)); + + gst_pad_set_event_function (autoconvert->srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_src_event)); + gst_pad_set_query_function (autoconvert->srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_src_query)); + gst_pad_set_query_type_function (autoconvert->srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_src_query_type)); + gst_pad_set_iterate_internal_links_function (autoconvert->sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_iterate_internal_links)); + + gst_element_add_pad (GST_ELEMENT (autoconvert), autoconvert->sinkpad); + gst_element_add_pad (GST_ELEMENT (autoconvert), autoconvert->srcpad); + + gst_segment_init (&autoconvert->sink_segment, GST_FORMAT_UNDEFINED); +} + +static void +gst_auto_convert_dispose (GObject * object) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object); + + gst_pad_set_fixatecaps_function (autoconvert->sinkpad, NULL); + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->current_subelement) { + gst_object_unref (autoconvert->current_subelement); + autoconvert->current_subelement = NULL; + autoconvert->current_internal_sinkpad = NULL; + autoconvert->current_internal_srcpad = NULL; + } + + g_list_foreach (autoconvert->cached_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (autoconvert->cached_events); + autoconvert->cached_events = NULL; + + if (autoconvert->factories) { + gst_plugin_feature_list_free (autoconvert->factories); + autoconvert->factories = NULL; + } + GST_AUTOCONVERT_UNLOCK (autoconvert); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_auto_convert_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + case PROP_FACTORIES: + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->factories == NULL) { + GList *factories = g_value_get_pointer (value); + autoconvert->factories = g_list_copy (factories); + g_list_foreach (autoconvert->factories, (GFunc) g_object_ref, NULL); + } else + GST_WARNING_OBJECT (object, "Can not reset factories after they" + " have been set or auto-discovered"); + GST_AUTOCONVERT_UNLOCK (autoconvert); + break; + } +} + +static void +gst_auto_convert_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + case PROP_FACTORIES: + GST_AUTOCONVERT_LOCK (autoconvert); + g_value_set_pointer (value, &autoconvert->factories); + GST_AUTOCONVERT_UNLOCK (autoconvert); + break; + } +} + +static GstStateChangeReturn +gst_auto_convert_change_state (GstElement * element, GstStateChange transition) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (element); + GstStateChangeReturn ret; + + switch (transition) { + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + g_list_foreach (autoconvert->cached_events, (GFunc) gst_mini_object_unref, + NULL); + g_list_free (autoconvert->cached_events); + autoconvert->cached_events = NULL; + break; + default: + break; + } + + return ret; +} + +static GstElement * +gst_auto_convert_get_element_by_type (GstAutoConvert * autoconvert, GType type) +{ + GstIterator *iter = NULL; + GstElement *elem = NULL; + gboolean done; + + g_return_val_if_fail (type != 0, NULL); + + iter = gst_bin_iterate_elements (GST_BIN (autoconvert)); + + if (!iter) + return NULL; + + done = FALSE; + while (!done) { + switch (gst_iterator_next (iter, (gpointer) & elem)) { + case GST_ITERATOR_OK: + if (G_OBJECT_TYPE (elem) == type) + done = TRUE; + else + gst_object_unref (elem); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + elem = NULL; + break; + case GST_ITERATOR_ERROR: + GST_ERROR ("Error iterating elements in bin"); + elem = NULL; + done = TRUE; + break; + case GST_ITERATOR_DONE: + elem = NULL; + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + return elem; +} + +/** + * get_pad_by_direction: + * @element: The Element + * @direction: The direction + * + * Gets a #GstPad that goes in the requested direction. I will return NULL + * if there is no pad or if there is more than one pad in this direction + */ + +static GstPad * +get_pad_by_direction (GstElement * element, GstPadDirection direction) +{ + GstIterator *iter = gst_element_iterate_pads (element); + GstPad *pad = NULL; + GstPad *selected_pad = NULL; + gboolean done; + + if (!iter) + return NULL; + + done = FALSE; + while (!done) { + switch (gst_iterator_next (iter, (gpointer) & pad)) { + case GST_ITERATOR_OK: + if (gst_pad_get_direction (pad) == direction) { + /* We check if there is more than one pad in this direction, + * if there is, we return NULL so that the element is refused + */ + if (selected_pad) { + done = TRUE; + gst_object_unref (selected_pad); + selected_pad = NULL; + } else { + selected_pad = pad; + } + } else { + gst_object_unref (pad); + } + break; + case GST_ITERATOR_RESYNC: + if (selected_pad) { + gst_object_unref (selected_pad); + selected_pad = NULL; + } + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_ERROR ("Error iterating pads of element %s", + GST_OBJECT_NAME (element)); + gst_object_unref (selected_pad); + selected_pad = NULL; + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + if (!selected_pad) + GST_ERROR ("Did not find pad of direction %d in %s", + direction, GST_OBJECT_NAME (element)); + + return selected_pad; +} + +static GstElement * +gst_auto_convert_get_subelement (GstAutoConvert * autoconvert, + gboolean query_only) +{ + GstElement *element = NULL; + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->current_subelement) + element = gst_object_ref (autoconvert->current_subelement); + GST_AUTOCONVERT_UNLOCK (autoconvert); + + return element; +} + +static GstPad * +gst_auto_convert_get_internal_sinkpad (GstAutoConvert * autoconvert) +{ + GstPad *pad = NULL; + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->current_internal_sinkpad) + pad = gst_object_ref (autoconvert->current_internal_sinkpad); + GST_AUTOCONVERT_UNLOCK (autoconvert); + + return pad; +} + +static GstPad * +gst_auto_convert_get_internal_srcpad (GstAutoConvert * autoconvert) +{ + GstPad *pad = NULL; + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->current_internal_srcpad) + pad = gst_object_ref (autoconvert->current_internal_srcpad); + GST_AUTOCONVERT_UNLOCK (autoconvert); + + return pad; +} + +/* + * This function creates and adds an element to the GstAutoConvert + * it then creates the internal pads and links them + * + */ + +static GstElement * +gst_auto_convert_add_element (GstAutoConvert * autoconvert, + GstElementFactory * factory) +{ + GstElement *element = NULL; + GstPad *internal_sinkpad = NULL; + GstPad *internal_srcpad = NULL; + GstPad *sinkpad = NULL; + GstPad *srcpad = NULL; + GstPadLinkReturn padlinkret; + + GST_DEBUG_OBJECT (autoconvert, "Adding element %s to the autoconvert bin", + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory))); + + element = gst_element_factory_create (factory, NULL); + if (!element) + return NULL; + + if (!gst_bin_add (GST_BIN (autoconvert), element)) { + GST_ERROR_OBJECT (autoconvert, "Could not add element %s to the bin", + GST_OBJECT_NAME (element)); + gst_object_unref (element); + return NULL; + } + + srcpad = get_pad_by_direction (element, GST_PAD_SRC); + if (!srcpad) { + GST_ERROR_OBJECT (autoconvert, "Could not find source in %s", + GST_OBJECT_NAME (element)); + goto error; + } + + sinkpad = get_pad_by_direction (element, GST_PAD_SINK); + if (!sinkpad) { + GST_ERROR_OBJECT (autoconvert, "Could not find sink in %s", + GST_OBJECT_NAME (element)); + goto error; + } + + internal_sinkpad = + gst_pad_new_from_static_template (&sink_internal_template, + "sink_internal"); + internal_srcpad = + gst_pad_new_from_static_template (&src_internal_template, "src_internal"); + + if (!internal_sinkpad || !internal_srcpad) { + GST_ERROR_OBJECT (autoconvert, "Could not create internal pads"); + if (internal_srcpad) + gst_object_unref (internal_srcpad); + if (internal_sinkpad) + gst_object_unref (internal_sinkpad); + goto error; + } + + g_object_weak_ref (G_OBJECT (element), (GWeakNotify) gst_object_unref, + internal_sinkpad); + g_object_weak_ref (G_OBJECT (element), (GWeakNotify) gst_object_unref, + internal_srcpad); + + gst_pad_set_active (internal_sinkpad, TRUE); + gst_pad_set_active (internal_srcpad, TRUE); + + g_object_set_qdata (G_OBJECT (internal_srcpad), parent_quark, autoconvert); + g_object_set_qdata (G_OBJECT (internal_sinkpad), parent_quark, autoconvert); + + gst_pad_set_chain_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_chain)); + gst_pad_set_event_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_event)); + gst_pad_set_query_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_query)); + gst_pad_set_query_type_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_query_type)); + gst_pad_set_getcaps_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_getcaps)); + gst_pad_set_bufferalloc_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_buffer_alloc)); + gst_pad_set_fixatecaps_function (internal_sinkpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_fixatecaps)); + + gst_pad_set_event_function (internal_srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_event)); + gst_pad_set_query_function (internal_srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_query)); + gst_pad_set_query_type_function (internal_srcpad, + GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_query_type)); + + padlinkret = gst_pad_link (internal_srcpad, sinkpad); + if (GST_PAD_LINK_FAILED (padlinkret)) { + GST_WARNING_OBJECT (autoconvert, "Could not links pad %s:%s to %s:%s" + " for reason %d", + GST_DEBUG_PAD_NAME (internal_srcpad), + GST_DEBUG_PAD_NAME (sinkpad), padlinkret); + goto error; + } + + padlinkret = gst_pad_link (srcpad, internal_sinkpad); + if (GST_PAD_LINK_FAILED (padlinkret)) { + GST_WARNING_OBJECT (autoconvert, "Could not links pad %s:%s to %s:%s" + " for reason %d", + GST_DEBUG_PAD_NAME (internal_srcpad), + GST_DEBUG_PAD_NAME (sinkpad), padlinkret); + goto error; + } + + g_object_set_qdata (G_OBJECT (element), + internal_srcpad_quark, internal_srcpad); + g_object_set_qdata (G_OBJECT (element), + internal_sinkpad_quark, internal_sinkpad); + + /* Iffy */ + gst_element_sync_state_with_parent (element); + + /* Increment the reference count we will return to the caller */ + gst_object_ref (element); + + /* unref sink and src pad */ + gst_object_unref (srcpad); + gst_object_unref (sinkpad); + return element; + +error: + gst_bin_remove (GST_BIN (autoconvert), element); + + if (srcpad) + gst_object_unref (srcpad); + if (sinkpad) + gst_object_unref (sinkpad); + + return NULL; +} + +static GstElement * +gst_auto_convert_get_or_make_element_from_factory (GstAutoConvert * autoconvert, + GstElementFactory * factory) +{ + GstElement *element = NULL; + GstElementFactory *loaded_factory = + GST_ELEMENT_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE + (factory))); + + if (!loaded_factory) + return NULL; + + element = gst_auto_convert_get_element_by_type (autoconvert, + gst_element_factory_get_element_type (loaded_factory)); + + if (!element) { + element = gst_auto_convert_add_element (autoconvert, loaded_factory); + } + + gst_object_unref (loaded_factory); + + return element; +} + +/* + * This function checks if there is one and only one pad template on the + * factory that can accept the given caps. If there is one and only one, + * it returns TRUE, otherwise, its FALSE + */ + +static gboolean +factory_can_intersect (GstAutoConvert * autoconvert, + GstElementFactory * factory, GstPadDirection direction, GstCaps * caps) +{ + GList *templates; + gint has_direction = FALSE; + gboolean ret = FALSE; + + g_return_val_if_fail (factory != NULL, FALSE); + g_return_val_if_fail (caps != NULL, FALSE); + + templates = factory->staticpadtemplates; + + while (templates) { + GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data; + + if (template->direction == direction) { + GstCaps *tmpl_caps = NULL; + gboolean intersect; + + /* If there is more than one pad in this direction, we return FALSE + * Only transform elements (with one sink and one source pad) + * are accepted + */ + if (has_direction) { + GST_DEBUG_OBJECT (autoconvert, "Factory %" GST_PTR_FORMAT + " has more than one static template with dir %d", + template, direction); + return FALSE; + } + has_direction = TRUE; + + tmpl_caps = gst_static_caps_get (&template->static_caps); + intersect = gst_caps_can_intersect (tmpl_caps, caps); + GST_DEBUG_OBJECT (autoconvert, "Factories %" GST_PTR_FORMAT + " static caps %" GST_PTR_FORMAT " and caps %" GST_PTR_FORMAT + " can%s intersect", factory, tmpl_caps, caps, + intersect ? "" : " not"); + gst_caps_unref (tmpl_caps); + + ret |= intersect; + } + templates = g_list_next (templates); + } + + return ret; +} + +static gboolean +gst_auto_convert_activate_element (GstAutoConvert * autoconvert, + GstElement * element, GstCaps * caps) +{ + GstPad *internal_srcpad = g_object_get_qdata (G_OBJECT (element), + internal_srcpad_quark); + GstPad *internal_sinkpad = g_object_get_qdata (G_OBJECT (element), + internal_sinkpad_quark); + + if (caps) { + /* check if the element can really accept said caps */ + if (!gst_pad_peer_accept_caps (internal_srcpad, caps)) { + GST_DEBUG_OBJECT (autoconvert, "Could not set %s:%s to %" + GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (internal_srcpad), caps); + return FALSE; + } + } + + gst_pad_set_fixatecaps_function (autoconvert->sinkpad, + gst_auto_convert_sink_fixatecaps); + GST_AUTOCONVERT_LOCK (autoconvert); + autoconvert->current_subelement = element; + autoconvert->current_internal_srcpad = internal_srcpad; + autoconvert->current_internal_sinkpad = internal_sinkpad; + GST_AUTOCONVERT_UNLOCK (autoconvert); + + GST_INFO_OBJECT (autoconvert, + "Selected element %s", + GST_OBJECT_NAME (GST_OBJECT (autoconvert->current_subelement))); + + /* Send new-segment event if we have one */ + if (autoconvert->sink_segment.format != GST_FORMAT_UNDEFINED) { + GstEvent *event; + GstSegment *seg = &autoconvert->sink_segment; + event = gst_event_new_new_segment_full (TRUE, + seg->rate, seg->applied_rate, seg->format, seg->start, + seg->stop, seg->time); + + autoconvert->drop_newseg = TRUE; + gst_pad_push_event (internal_srcpad, event); + autoconvert->drop_newseg = FALSE; + } + + return TRUE; +} + +static GstIterator * +gst_auto_convert_iterate_internal_links (GstPad * pad) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstIterator *it = NULL; + GstPad *internal; + + if (!autoconvert) + return NULL; + + if (pad == autoconvert->sinkpad) + internal = gst_auto_convert_get_internal_srcpad (autoconvert); + else + internal = gst_auto_convert_get_internal_sinkpad (autoconvert); + + if (internal) { + it = gst_iterator_new_single (GST_TYPE_PAD, internal, + (GstCopyFunction) gst_object_ref, (GFreeFunc) gst_object_unref); + gst_object_unref (internal); + } + + gst_object_unref (autoconvert); + + return it; +} + +/* + * If there is already an internal element, it will try to call set_caps on it + * + * If there isn't an internal element or if the set_caps() on the internal + * element failed, it will try to find another element where it would succeed + * and will change the internal element. + */ + +static gboolean +gst_auto_convert_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GList *elem; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + GstCaps *other_caps = NULL; + GstPad *peer; + GList *factories; + + g_return_val_if_fail (autoconvert != NULL, FALSE); + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + if (gst_pad_peer_accept_caps (autoconvert->current_internal_srcpad, caps)) { + /* If we can set the new caps on the current element, + * then we just get out + */ + GST_DEBUG_OBJECT (autoconvert, "Could set %s:%s to %" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (autoconvert->current_internal_srcpad), caps); + gst_object_unref (subelement); + goto get_out; + } else { + /* If the current element doesn't work, + * then we remove the current element before finding a new one. + * By unsetting the fixatecaps function, we go back to the default one + */ + gst_pad_set_fixatecaps_function (autoconvert->sinkpad, NULL); + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->current_subelement == subelement) { + gst_object_unref (autoconvert->current_subelement); + autoconvert->current_subelement = NULL; + autoconvert->current_internal_srcpad = NULL; + autoconvert->current_internal_sinkpad = NULL; + } + GST_AUTOCONVERT_UNLOCK (autoconvert); + gst_object_unref (subelement); + } + } + + peer = gst_pad_get_peer (autoconvert->srcpad); + if (peer) { + other_caps = gst_pad_get_caps (peer); + gst_object_unref (peer); + } + + GST_AUTOCONVERT_LOCK (autoconvert); + factories = autoconvert->factories; + GST_AUTOCONVERT_UNLOCK (autoconvert); + + if (!factories) + factories = gst_auto_convert_load_factories (autoconvert); + + for (elem = factories; elem; elem = g_list_next (elem)) { + GstElementFactory *factory = GST_ELEMENT_FACTORY (elem->data); + GstElement *element; + + /* Lets first check if according to the static pad templates on the factory + * these caps have any chance of success + */ + if (!factory_can_intersect (autoconvert, factory, GST_PAD_SINK, caps)) { + GST_LOG_OBJECT (autoconvert, "Factory %s does not accept sink caps %" + GST_PTR_FORMAT, + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)), caps); + continue; + } + if (other_caps != NULL) { + if (!factory_can_intersect (autoconvert, factory, GST_PAD_SRC, + other_caps)) { + GST_LOG_OBJECT (autoconvert, + "Factory %s does not accept src caps %" GST_PTR_FORMAT, + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)), + other_caps); + continue; + } + } + + /* The element had a chance of success, lets make it */ + element = + gst_auto_convert_get_or_make_element_from_factory (autoconvert, + factory); + if (!element) + continue; + + /* And make it the current child */ + if (gst_auto_convert_activate_element (autoconvert, element, caps)) + break; + else + gst_object_unref (element); + } + +get_out: + if (other_caps) + gst_caps_unref (other_caps); + gst_object_unref (autoconvert); + + if (autoconvert->current_subelement) { + return TRUE; + } else { + GST_WARNING_OBJECT (autoconvert, + "Could not find a matching element for caps"); + return FALSE; + } +} + +/* + * This function filters the pad pad templates, taking only transform element + * (with one sink and one src pad) + */ + +static gboolean +gst_auto_convert_default_filter_func (GstPluginFeature * feature, + gpointer user_data) +{ + GstElementFactory *factory = NULL; + const GList *static_pad_templates, *tmp; + GstStaticPadTemplate *src = NULL, *sink = NULL; + + if (!GST_IS_ELEMENT_FACTORY (feature)) + return FALSE; + + factory = GST_ELEMENT_FACTORY (feature); + + static_pad_templates = gst_element_factory_get_static_pad_templates (factory); + + for (tmp = static_pad_templates; tmp; tmp = g_list_next (tmp)) { + GstStaticPadTemplate *template = tmp->data; + GstCaps *caps; + + if (template->presence == GST_PAD_SOMETIMES) + return FALSE; + + if (template->presence != GST_PAD_ALWAYS) + continue; + + switch (template->direction) { + case GST_PAD_SRC: + if (src) + return FALSE; + src = template; + break; + case GST_PAD_SINK: + if (sink) + return FALSE; + sink = template; + break; + default: + return FALSE; + } + + caps = gst_static_pad_template_get_caps (template); + + if (gst_caps_is_any (caps) || gst_caps_is_empty (caps)) + return FALSE; + } + + if (!src || !sink) + return FALSE; + + return TRUE; +} + +/* function used to sort element features + * Copy-pasted from decodebin */ +static gint +compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2) +{ + gint diff; + const gchar *rname1, *rname2; + + diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1); + if (diff != 0) + return diff; + + rname1 = gst_plugin_feature_get_name (f1); + rname2 = gst_plugin_feature_get_name (f2); + + diff = strcmp (rname2, rname1); + + return diff; +} + +static GList * +gst_auto_convert_load_factories (GstAutoConvert * autoconvert) +{ + GList *all_factories; + GList *out_factories; + + all_factories = + gst_default_registry_feature_filter (gst_auto_convert_default_filter_func, + FALSE, NULL); + + all_factories = g_list_sort (all_factories, (GCompareFunc) compare_ranks); + + g_assert (all_factories); + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->factories == NULL) { + autoconvert->factories = all_factories; + all_factories = NULL; + } + out_factories = autoconvert->factories; + GST_AUTOCONVERT_UNLOCK (autoconvert); + + if (all_factories) { + /* In this case, someone set the property while we were looking! */ + gst_plugin_feature_list_free (all_factories); + } + + return out_factories; +} + +/* In this case, we should almost always have an internal element, because + * set_caps() should have been called first + */ + +static GstFlowReturn +gst_auto_convert_sink_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_NOT_NEGOTIATED; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstPad *internal_srcpad; + + internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert); + if (internal_srcpad) { + GList *events = NULL; + GList *l; + + GST_AUTOCONVERT_LOCK (autoconvert); + if (autoconvert->cached_events) { + events = g_list_reverse (autoconvert->cached_events); + autoconvert->cached_events = NULL; + } + GST_AUTOCONVERT_UNLOCK (autoconvert); + + if (events) { + GST_DEBUG_OBJECT (autoconvert, "Sending cached events downstream"); + for (l = events; l; l = l->next) + gst_pad_push_event (internal_srcpad, l->data); + g_list_free (events); + } + + ret = gst_pad_push (internal_srcpad, buffer); + gst_object_unref (internal_srcpad); + if (ret != GST_FLOW_OK) { + GstElement *child = gst_auto_convert_get_subelement (autoconvert, TRUE); + GST_DEBUG_OBJECT (autoconvert, + "Child element %" GST_PTR_FORMAT "returned flow %s", child, + gst_flow_get_name (ret)); + if (child) + gst_object_unref (child); + } + } else { + GST_ERROR_OBJECT (autoconvert, "Got buffer without an negotiated element," + " returning not-negotiated"); + } + + gst_object_unref (autoconvert); + + return ret; +} + +static gboolean +gst_auto_convert_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = TRUE; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstPad *internal_srcpad; + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + gboolean update; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + GST_DEBUG_OBJECT (autoconvert, + "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT + ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, + update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (time)); + + /* Store the values for feeding to sub-elements */ + gst_segment_set_newsegment_full (&autoconvert->sink_segment, update, + rate, arate, format, start, stop, time); + } + + internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert); + if (internal_srcpad) { + ret = gst_pad_push_event (internal_srcpad, event); + gst_object_unref (internal_srcpad); + } else { + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + GST_AUTOCONVERT_LOCK (autoconvert); + g_list_foreach (autoconvert->cached_events, + (GFunc) gst_mini_object_unref, NULL); + g_list_free (autoconvert->cached_events); + autoconvert->cached_events = NULL; + GST_AUTOCONVERT_UNLOCK (autoconvert); + /* fall through */ + case GST_EVENT_FLUSH_START: + ret = gst_pad_push_event (autoconvert->srcpad, event); + break; + default: + GST_AUTOCONVERT_LOCK (autoconvert); + autoconvert->cached_events = + g_list_prepend (autoconvert->cached_events, event); + ret = TRUE; + GST_AUTOCONVERT_UNLOCK (autoconvert); + break; + } + } + + gst_object_unref (autoconvert); + + return ret; +} + +/* TODO Properly test that this code works well for queries */ +static gboolean +gst_auto_convert_sink_query (GstPad * pad, GstQuery * query) +{ + gboolean ret = TRUE; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + GstPad *sub_sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK); + + ret = gst_pad_query (sub_sinkpad, query); + + gst_object_unref (sub_sinkpad); + gst_object_unref (subelement); + } else { + GST_WARNING_OBJECT (autoconvert, "Got query while no element was selected," + "letting through"); + ret = gst_pad_peer_query (autoconvert->srcpad, query); + } + + gst_object_unref (autoconvert); + + return ret; +} + +/* TODO Test that this code works properly for queries */ +static const GstQueryType * +gst_auto_convert_sink_query_type (GstPad * pad) +{ + const GstQueryType *ret = NULL; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + GstPad *sub_sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK); + + ret = gst_pad_get_query_types (sub_sinkpad); + + gst_object_unref (sub_sinkpad); + gst_object_unref (subelement); + } else { + ret = gst_pad_get_query_types_default (pad); + } + + gst_object_unref (autoconvert); + + return ret; +} + +static void +gst_auto_convert_sink_fixatecaps (GstPad * pad, GstCaps * caps) +{ + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + GstPad *sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK); + gst_pad_fixate_caps (sinkpad, caps); + gst_object_unref (sinkpad); + gst_object_unref (subelement); + } +} + +/** + * gst_auto_convert_sink_getcaps: + * @pad: the sink #GstPad + * + * This function returns the union of the caps of all the possible element + * factories, based on the static pad templates. + * It also checks does a getcaps on the downstream element and ignores all + * factories whose static caps can not satisfy it. + * + * It does not try to use each elements getcaps() function + */ + +static GstCaps * +gst_auto_convert_sink_getcaps (GstPad * pad) +{ + GstCaps *caps = NULL, *other_caps = NULL; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstPad *peer; + GList *elem, *factories; + + caps = gst_caps_new_empty (); + + peer = gst_pad_get_peer (autoconvert->srcpad); + if (peer) { + other_caps = gst_pad_get_caps (peer); + gst_object_unref (peer); + } + + GST_DEBUG_OBJECT (autoconvert, + "Lets find all the element that can fit here with src caps %" + GST_PTR_FORMAT, other_caps); + + if (other_caps && gst_caps_is_empty (other_caps)) { + goto out; + } + + GST_AUTOCONVERT_LOCK (autoconvert); + factories = autoconvert->factories; + GST_AUTOCONVERT_UNLOCK (autoconvert); + + if (!factories) + factories = gst_auto_convert_load_factories (autoconvert); + + for (elem = factories; elem; elem = g_list_next (elem)) { + GstElementFactory *factory = GST_ELEMENT_FACTORY (elem->data); + GstElement *element = NULL; + GstCaps *element_caps; + GstPad *internal_srcpad = NULL; + + if (other_caps != NULL) { + if (!factory_can_intersect (autoconvert, factory, GST_PAD_SRC, + other_caps)) { + GST_LOG_OBJECT (autoconvert, + "Factory %s does not accept src caps %" GST_PTR_FORMAT, + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)), + other_caps); + continue; + } + } + + if (other_caps) { + + element = + gst_auto_convert_get_or_make_element_from_factory (autoconvert, + factory); + if (!element) + continue; + + internal_srcpad = g_object_get_qdata (G_OBJECT (element), + internal_srcpad_quark); + + element_caps = gst_pad_peer_get_caps (internal_srcpad); + + if (element_caps) { + if (!gst_caps_is_any (element_caps) && + !gst_caps_is_empty (element_caps)) { + gst_caps_merge (caps, element_caps); + } else { + gst_caps_unref (element_caps); + } + } + + gst_object_unref (element); + } else { + const GList *tmp; + + for (tmp = gst_element_factory_get_static_pad_templates (factory); + tmp; tmp = g_list_next (tmp)) { + GstStaticPadTemplate *template = tmp->data; + GstCaps *static_caps = gst_static_pad_template_get_caps (template); + + if (static_caps && !gst_caps_is_any (static_caps) && + !gst_caps_is_empty (static_caps)) { + gst_caps_merge (caps, static_caps); + } + } + } + } + + GST_DEBUG_OBJECT (autoconvert, "Returning unioned caps %" GST_PTR_FORMAT, + caps); + +out: + gst_object_unref (autoconvert); + + if (other_caps) + gst_caps_unref (other_caps); + + return caps; +} + + +static GstFlowReturn +gst_auto_convert_sink_buffer_alloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstPad *internal_srcpad; + + g_return_val_if_fail (autoconvert != NULL, GST_FLOW_ERROR); + + internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert); + if (internal_srcpad) { + ret = gst_pad_alloc_buffer (internal_srcpad, offset, size, caps, buf); + gst_object_unref (internal_srcpad); + } else + /* Fallback to the default */ + *buf = NULL; + + gst_object_unref (autoconvert); + + return ret; +} + +static gboolean +gst_auto_convert_src_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = TRUE; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstPad *internal_sinkpad; + + internal_sinkpad = gst_auto_convert_get_internal_sinkpad (autoconvert); + if (internal_sinkpad) { + ret = gst_pad_push_event (internal_sinkpad, event); + gst_object_unref (internal_sinkpad); + } else { + GST_WARNING_OBJECT (autoconvert, + "Got upstream event while no element was selected," "forwarding."); + ret = gst_pad_push_event (autoconvert->sinkpad, event); + } + + gst_object_unref (autoconvert); + + return ret; +} + +/* TODO Properly test that this code works well for queries */ +static gboolean +gst_auto_convert_src_query (GstPad * pad, GstQuery * query) +{ + gboolean ret = TRUE; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + GstPad *sub_srcpad = get_pad_by_direction (subelement, GST_PAD_SRC); + + ret = gst_pad_query (sub_srcpad, query); + + gst_object_unref (sub_srcpad); + gst_object_unref (subelement); + } else { + GST_WARNING_OBJECT (autoconvert, + "Got upstream query while no element was selected," "forwarding."); + ret = gst_pad_peer_query (autoconvert->sinkpad, query); + } + + gst_object_unref (autoconvert); + + return ret; +} + +/* TODO Properly test that this code works well for queries */ +static const GstQueryType * +gst_auto_convert_src_query_type (GstPad * pad) +{ + const GstQueryType *ret = NULL; + GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad)); + GstElement *subelement; + + subelement = gst_auto_convert_get_subelement (autoconvert, TRUE); + if (subelement) { + GstPad *sub_srcpad = get_pad_by_direction (subelement, GST_PAD_SRC); + + ret = gst_pad_get_query_types (sub_srcpad); + + gst_object_unref (sub_srcpad); + gst_object_unref (subelement); + } else { + ret = gst_pad_get_query_types_default (pad); + } + + gst_object_unref (autoconvert); + + return ret; +} + +static GstFlowReturn +gst_auto_convert_internal_sink_chain (GstPad * pad, GstBuffer * buffer) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + + return gst_pad_push (autoconvert->srcpad, buffer); +} + +static gboolean +gst_auto_convert_internal_sink_event (GstPad * pad, GstEvent * event) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + if (autoconvert->drop_newseg) { + GST_DEBUG_OBJECT (autoconvert, "Dropping primer newsegment event"); + gst_event_unref (event); + return TRUE; + } + } + + return gst_pad_push_event (autoconvert->srcpad, event); +} + +static gboolean +gst_auto_convert_internal_sink_query (GstPad * pad, GstQuery * query) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + GstPad *peerpad = gst_pad_get_peer (autoconvert->srcpad); + gboolean ret = FALSE; + + if (peerpad) { + ret = gst_pad_query (peerpad, query); + gst_object_unref (peerpad); + } + + return ret; +} + +static const GstQueryType * +gst_auto_convert_internal_sink_query_type (GstPad * pad) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + GstPad *peerpad = gst_pad_get_peer (autoconvert->srcpad); + const GstQueryType *ret = NULL; + + if (peerpad) { + ret = gst_pad_get_query_types (peerpad); + gst_object_unref (peerpad); + } else + ret = gst_pad_get_query_types_default (pad); + + return ret; +} + +static GstCaps * +gst_auto_convert_internal_sink_getcaps (GstPad * pad) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + + return gst_pad_peer_get_caps (autoconvert->srcpad); +} + +static void +gst_auto_convert_internal_sink_fixatecaps (GstPad * pad, GstCaps * caps) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad); + + if (peerpad) { + gst_pad_fixate_caps (peerpad, caps); + gst_object_unref (peerpad); + } +} + +static GstFlowReturn +gst_auto_convert_internal_sink_buffer_alloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + + return gst_pad_alloc_buffer (autoconvert->srcpad, offset, size, caps, buf); +} + +static gboolean +gst_auto_convert_internal_src_event (GstPad * pad, GstEvent * event) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + + return gst_pad_push_event (autoconvert->sinkpad, event); +} + +static gboolean +gst_auto_convert_internal_src_query (GstPad * pad, GstQuery * query) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad); + gboolean ret = FALSE; + + if (peerpad) { + ret = gst_pad_query (peerpad, query); + gst_object_unref (peerpad); + } + + return ret; +} + +static const GstQueryType * +gst_auto_convert_internal_src_query_type (GstPad * pad) +{ + GstAutoConvert *autoconvert = + GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad), + parent_quark)); + GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad); + const GstQueryType *ret = NULL; + + if (peerpad) { + ret = gst_pad_get_query_types (peerpad); + gst_object_unref (peerpad); + } else { + ret = gst_pad_get_query_types_default (pad); + } + + return ret; +} + +/* +static gboolean +gst_auto_convert_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "autoconvert", + GST_RANK_NONE, GST_TYPE_AUTO_CONVERT); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "autoconvert", + "Selects convertor element based on caps", + gst_auto_convert_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) +*/ Index: gst-plugins-good0.10/farsight/autoconvert/gstautoconvert.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/gstautoconvert.h 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,69 @@ +/* GStreamer + * + * Copyright 2007 Collabora Ltd + * @author: Olivier Crete + * Copyright 2007-2008 Nokia + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_AUTO_CONVERT_H__ +#define __GST_AUTO_CONVERT_H__ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_AUTO_CONVERT (gst_auto_convert_get_type()) +#define GST_AUTO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AUTO_CONVERT,GstAutoConvert)) +#define GST_AUTO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AUTO_CONVERT,GstAutoConvertClass)) +#define GST_IS_AUTO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AUTO_CONVERT)) +#define GST_IS_AUTO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AUTO_CONVERT)) +typedef struct _GstAutoConvert GstAutoConvert; +typedef struct _GstAutoConvertClass GstAutoConvertClass; + +struct _GstAutoConvert +{ + /*< private >*/ + GstBin bin; /* we extend GstBin */ + + /* Protected by the object lock too */ + GList *factories; + + GstPad *sinkpad; + GstPad *srcpad; + + /* Have to be set all at once + * Protected by the object lock */ + GstElement *current_subelement; + GstPad *current_internal_srcpad; + GstPad *current_internal_sinkpad; + + GList *cached_events; + GstSegment sink_segment; + gboolean drop_newseg; +}; + +struct _GstAutoConvertClass +{ + GstBinClass parent_class; +}; + +GType gst_auto_convert_get_type (void); + +G_END_DECLS +#endif /* __GST_AUTO_CONVERT_H__ */ Index: gst-plugins-good0.10/farsight/autoconvert/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/Makefile.am 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,24 @@ +plugin_LTLIBRARIES = libgstautoconvert.la + +libgstautoconvert_la_SOURCES = gstautoconvert.c gstautovideoconvert.c plugin.c + +libgstautoconvert_la_CFLAGS = $(GST_CFLAGS) +libgstautoconvert_la_LIBADD = $(GST_LIBS) +libgstautoconvert_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstautoconvert_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstautoconvert.h gstautovideoconvert.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstautoconvert -:SHARED libgstautoconvert \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstautoconvert_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstautoconvert_la_CFLAGS) \ + -:LDFLAGS $(libgstautoconvert_la_LDFLAGS) \ + $(libgstautoconvert_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ Index: gst-plugins-good0.10/farsight/dtmf/gstdtmf.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmf.c 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,33 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtmfdetect.h" +#include "gstdtmfsrc.h" +#include "gstrtpdtmfsrc.h" +#include "gstrtpdtmfdepay.h" + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_dtmf_detect_plugin_init (plugin)) + return FALSE; + + if (!gst_dtmf_src_plugin_init (plugin)) + return FALSE; + + if (!gst_rtp_dtmf_src_plugin_init (plugin)) + return FALSE; + + if (!gst_rtp_dtmf_depay_plugin_init (plugin)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "dtmf", "DTMF plugins", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/farsight/dtmf/gstdtmfdetect.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmfdetect.c 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,281 @@ +/* + * GStreamer - DTMF Detection + * + * Copyright 2009 Nokia Corporation + * Copyright 2009 Collabora Ltd, + * @author: Olivier Crete + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +/** + * SECTION:element-dtmfdetect + * @short_description: Detects DTMF tones + * + * This element will detect DTMF tones and emit messages. + * + * The message is called "dtmf-event" and has + * the following fields: + * + * + * + * gint type (0-1): + * The application uses this field to specify which of the two methods + * specified in RFC 2833 to use. The value should be 0 for tones and 1 for + * named events. Tones are specified by their frequencies and events are + * specfied by their number. This element can only take events as input. + * Do not confuse with "method" which specified the output. + * + * + * + * + * gint number (0-16): + * The event number. + * + * + * + * + * gint method (2): + * This field will always been 2 (ie sound) from this element. + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtmfdetect.h" + +#include + +GST_DEBUG_CATEGORY (dtmf_detect_debug); +#define GST_CAT_DEFAULT (dtmf_detect_debug) + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "width = (int) 16, " + "depth = (int) 16, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (bool) true, rate = (int) 8000, channels = (int) 1")); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "width = (int) 16, " + "depth = (int) 16, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (bool) true, rate = (int) 8000, channels = (int) 1")); + +/* signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, +}; + +static gboolean gst_dtmf_detect_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps); +static GstFlowReturn gst_dtmf_detect_transform_ip (GstBaseTransform * trans, + GstBuffer * buf); +static gboolean gst_dtmf_detect_event (GstBaseTransform * trans, + GstEvent * event); + +static void +_do_init (GType type) +{ + GST_DEBUG_CATEGORY_INIT (dtmf_detect_debug, "dtmfdetect", 0, "dtmfdetect"); +} + +GST_BOILERPLATE_FULL (GstDtmfDetect, gst_dtmf_detect, GstBaseTransform, + GST_TYPE_BASE_TRANSFORM, _do_init); + +static void +gst_dtmf_detect_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + + gst_element_class_set_details_simple (element_class, "DTMF detector element", + "Filter/Analyzer/Audio", + "This element detects DTMF tones", + "Olivier Crete "); +} + +static void +gst_dtmf_detect_class_init (GstDtmfDetectClass * klass) +{ + GstBaseTransformClass *gstbasetransform_class; + + gstbasetransform_class = (GstBaseTransformClass *) klass; + + gstbasetransform_class->set_caps = + GST_DEBUG_FUNCPTR (gst_dtmf_detect_set_caps); + gstbasetransform_class->transform_ip = + GST_DEBUG_FUNCPTR (gst_dtmf_detect_transform_ip); + gstbasetransform_class->event = GST_DEBUG_FUNCPTR (gst_dtmf_detect_event); +} + +static void +gst_dtmf_detect_init (GstDtmfDetect * dtmfdetect, GstDtmfDetectClass * klass) +{ + gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (dtmfdetect), TRUE); + gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (dtmfdetect), TRUE); +} + +static gboolean +gst_dtmf_detect_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstDtmfDetect *self = GST_DTMF_DETECT (trans); + + zap_dtmf_detect_init (&self->dtmf_state); + + return TRUE; +} + + +static GstFlowReturn +gst_dtmf_detect_transform_ip (GstBaseTransform * trans, GstBuffer * buf) +{ + GstDtmfDetect *self = GST_DTMF_DETECT (trans); + gint dtmf_count; + gchar dtmfbuf[MAX_DTMF_DIGITS] = ""; + gint i; + + if (GST_BUFFER_IS_DISCONT (buf)) + zap_dtmf_detect_init (&self->dtmf_state); + if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)) + return GST_FLOW_OK; + + zap_dtmf_detect (&self->dtmf_state, (gint16 *) GST_BUFFER_DATA (buf), + GST_BUFFER_SIZE (buf) / 2, FALSE); + + dtmf_count = zap_dtmf_get (&self->dtmf_state, dtmfbuf, MAX_DTMF_DIGITS); + + if (dtmf_count) + GST_DEBUG_OBJECT (self, "Got %d DTMF events: %s", dtmf_count, dtmfbuf); + else + GST_LOG_OBJECT (self, "Got no DTMF events"); + + for (i = 0; i < dtmf_count; i++) { + GstMessage *dtmf_message = NULL; + GstStructure *structure; + gint dtmf_payload_event; + + GST_DEBUG_OBJECT (self, "Got DTMF event %c", dtmfbuf[i]); + + switch (dtmfbuf[i]) { + case '0': + dtmf_payload_event = 0; + break; + case '1': + dtmf_payload_event = 1; + break; + case '2': + dtmf_payload_event = 2; + break; + case '3': + dtmf_payload_event = 3; + break; + case '4': + dtmf_payload_event = 4; + break; + case '5': + dtmf_payload_event = 5; + break; + case '6': + dtmf_payload_event = 6; + break; + case '7': + dtmf_payload_event = 7; + break; + case '8': + dtmf_payload_event = 8; + break; + case '9': + dtmf_payload_event = 9; + break; + case '*': + dtmf_payload_event = 10; + break; + case '#': + dtmf_payload_event = 11; + break; + case 'A': + dtmf_payload_event = 12; + break; + case 'B': + dtmf_payload_event = 13; + break; + case 'C': + dtmf_payload_event = 14; + break; + case 'D': + dtmf_payload_event = 15; + break; + default: + continue; + } + + structure = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, + "number", G_TYPE_INT, dtmf_payload_event, + "method", G_TYPE_INT, 2, NULL); + dtmf_message = gst_message_new_element (GST_OBJECT (self), structure); + gst_element_post_message (GST_ELEMENT (self), dtmf_message); + } + + return GST_FLOW_OK; +} + + +static gboolean +gst_dtmf_detect_event (GstBaseTransform * trans, GstEvent * event) +{ + GstDtmfDetect *self = GST_DTMF_DETECT (trans); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + zap_dtmf_detect_init (&self->dtmf_state); + break; + default: + break; + } + + return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_TRANSFORM_CLASS, event, + (trans, event), TRUE); +} + + +gboolean +gst_dtmf_detect_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "dtmfdetect", + GST_RANK_MARGINAL, GST_TYPE_DTMF_DETECT); +} Index: gst-plugins-good0.10/farsight/dtmf/gstdtmfdetect.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmfdetect.h 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,71 @@ +/* + * GStreamer - DTMF Detection + * + * Copyright 2009 Nokia Corporation + * Copyright 2009 Collabora Ltd, + * @author: Olivier Crete + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GST_DTMF_DETECT_H__ +#define __GST_DTMF_DETECT_H__ + +#include +#include + +#include "tone_detect.h" + +G_BEGIN_DECLS + +/* #define's don't like whitespacey bits */ +#define GST_TYPE_DTMF_DETECT \ + (gst_dtmf_detect_get_type()) +#define GST_DTMF_DETECT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GST_TYPE_DTMF_DETECT,GstDtmfDetect)) +#define GST_DTMF_DETECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + GST_TYPE_DTMF_DETECT,GstDtmfDetectClass)) +#define GST_IS_DTMF_DETECT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DTMF_DETECT)) +#define GST_IS_DTMF_DETECT_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DTMF_DETECT)) + +typedef struct _GstDtmfDetect GstDtmfDetect; +typedef struct _GstDtmfDetectClass GstDtmfDetectClass; +typedef struct _GstDtmfDetectPrivate GstDtmfDetectPrivate; + +struct _GstDtmfDetect +{ + GstBaseTransform parent; + + dtmf_detect_state_t dtmf_state; +}; + +struct _GstDtmfDetectClass +{ + GstBaseTransformClass parent_class; +}; + +GType gst_dtmf_detect_get_type (void); + +gboolean gst_dtmf_detect_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_DTMF_DETECT_H__ */ Index: gst-plugins-good0.10/farsight/dtmf/gstdtmfsrc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmfsrc.c 2012-02-09 13:54:47.443583536 +0200 @@ -0,0 +1,958 @@ +/* GStreamer DTMF source + * + * gstdtmfsrc.c: + * + * Copyright (C) <2007> Collabora. + * Contact: Youness Alaoui + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-dtmfsrc + * @see_also: rtpdtmsrc, rtpdtmfmuxx + * + * The DTMFSrc element generates DTMF (ITU-T Q.23 Specification) tone packets on request + * from application. The application communicates the beginning and end of a + * DTMF event using custom upstream gstreamer events. To report a DTMF event, an + * application must send an event of type GST_EVENT_CUSTOM_UPSTREAM, having a + * structure of name "dtmf-event" with fields set according to the following + * table: + * + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * type + * G_TYPE_INT + * 0-1 + * The application uses this field to specify which of the two methods + * specified in RFC 2833 to use. The value should be 0 for tones and 1 for + * named events. Tones are specified by their frequencies and events are specied + * by their number. This element can only take events as input. Do not confuse + * with "method" which specified the output. + * + * + * + * number + * G_TYPE_INT + * 0-15 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * This field describes the power level of the tone, expressed in dBm0 + * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of + * valid DTMF is from 0 to -36 dBm0. Can be omitted if start is set to FALSE. + * + * + * + * start + * G_TYPE_BOOLEAN + * True or False + * Whether the event is starting or ending. + * + * + * method + * G_TYPE_INT + * 2 + * The method used for sending event, this element will react if this + * field is absent or 2. + * + * + * + * + * + * + * For example, the following code informs the pipeline (and in turn, the + * DTMFSrc element inside the pipeline) about the start of a DTMF named + * event '1' of volume -25 dBm0: + * + * + * structure = gst_structure_new ("dtmf-event", + * "type", G_TYPE_INT, 1, + * "number", G_TYPE_INT, 1, + * "volume", G_TYPE_INT, 25, + * "start", G_TYPE_BOOLEAN, TRUE, NULL); + * + * event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, structure); + * gst_element_send_event (pipeline, event); + * + * + * When a DTMF tone actually starts or stop, a "dtmf-event-processed" + * element #GstMessage with the same fields as the "dtmf-event" + * #GstEvent that was used to request the event. Also, if any event + * has not been processed when the element goes from the PAUSED to the + * READY state, then a "dtmf-event-dropped" message is posted on the + * #GstBus in the order that they were received. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "gstdtmfcommon.h" + +#include "gstdtmfsrc.h" + +#define GST_TONE_DTMF_TYPE_EVENT 1 +#define DEFAULT_PACKET_INTERVAL 50 /* ms */ +#define MIN_PACKET_INTERVAL 10 /* ms */ +#define MAX_PACKET_INTERVAL 50 /* ms */ +#define DEFAULT_SAMPLE_RATE 8000 +#define SAMPLE_SIZE 16 +#define CHANNELS 1 +#define MIN_DUTY_CYCLE (MIN_INTER_DIGIT_INTERVAL + MIN_PULSE_DURATION) + + +typedef struct st_dtmf_key +{ + const char *event_name; + int event_encoding; + float low_frequency; + float high_frequency; +} DTMF_KEY; + +static const DTMF_KEY DTMF_KEYS[] = { + {"DTMF_KEY_EVENT_0", 0, 941, 1336}, + {"DTMF_KEY_EVENT_1", 1, 697, 1209}, + {"DTMF_KEY_EVENT_2", 2, 697, 1336}, + {"DTMF_KEY_EVENT_3", 3, 697, 1477}, + {"DTMF_KEY_EVENT_4", 4, 770, 1209}, + {"DTMF_KEY_EVENT_5", 5, 770, 1336}, + {"DTMF_KEY_EVENT_6", 6, 770, 1477}, + {"DTMF_KEY_EVENT_7", 7, 852, 1209}, + {"DTMF_KEY_EVENT_8", 8, 852, 1336}, + {"DTMF_KEY_EVENT_9", 9, 852, 1477}, + {"DTMF_KEY_EVENT_S", 10, 941, 1209}, + {"DTMF_KEY_EVENT_P", 11, 941, 1477}, + {"DTMF_KEY_EVENT_A", 12, 697, 1633}, + {"DTMF_KEY_EVENT_B", 13, 770, 1633}, + {"DTMF_KEY_EVENT_C", 14, 852, 1633}, + {"DTMF_KEY_EVENT_D", 15, 941, 1633}, +}; + +#define MAX_DTMF_EVENTS 16 + +enum +{ + DTMF_KEY_EVENT_1 = 1, + DTMF_KEY_EVENT_2 = 2, + DTMF_KEY_EVENT_3 = 3, + DTMF_KEY_EVENT_4 = 4, + DTMF_KEY_EVENT_5 = 5, + DTMF_KEY_EVENT_6 = 6, + DTMF_KEY_EVENT_7 = 7, + DTMF_KEY_EVENT_8 = 8, + DTMF_KEY_EVENT_9 = 9, + DTMF_KEY_EVENT_0 = 0, + DTMF_KEY_EVENT_STAR = 10, + DTMF_KEY_EVENT_POUND = 11, + DTMF_KEY_EVENT_A = 12, + DTMF_KEY_EVENT_B = 13, + DTMF_KEY_EVENT_C = 14, + DTMF_KEY_EVENT_D = 15, +}; + +GST_DEBUG_CATEGORY_STATIC (gst_dtmf_src_debug); +#define GST_CAT_DEFAULT gst_dtmf_src_debug + +enum +{ + PROP_0, + PROP_INTERVAL, +}; + +static GstStaticPadTemplate gst_dtmf_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "width = (int) 16, " + "depth = (int) 16, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1") + ); + +GST_BOILERPLATE (GstDTMFSrc, gst_dtmf_src, GstBaseSrc, GST_TYPE_BASE_SRC); + +static void gst_dtmf_src_finalize (GObject * object); + +static void gst_dtmf_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_dtmf_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean gst_dtmf_src_handle_event (GstBaseSrc * src, GstEvent * event); +static gboolean gst_dtmf_src_send_event (GstElement * src, GstEvent * event); +static GstStateChangeReturn gst_dtmf_src_change_state (GstElement * element, + GstStateChange transition); +static GstFlowReturn gst_dtmf_src_create (GstBaseSrc * basesrc, + guint64 offset, guint length, GstBuffer ** buffer); +static void gst_dtmf_src_add_start_event (GstDTMFSrc * dtmfsrc, + gint event_number, gint event_volume); +static void gst_dtmf_src_add_stop_event (GstDTMFSrc * dtmfsrc); + +static gboolean gst_dtmf_src_unlock (GstBaseSrc * src); + +static gboolean gst_dtmf_src_unlock_stop (GstBaseSrc * src); +static gboolean gst_dtmf_src_negotiate (GstBaseSrc * basesrc); + +static void +gst_dtmf_src_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + GST_DEBUG_CATEGORY_INIT (gst_dtmf_src_debug, "dtmfsrc", 0, "dtmfsrc element"); + + gst_element_class_add_static_pad_template (element_class, + &gst_dtmf_src_template); + + gst_element_class_set_details_simple (element_class, "DTMF tone generator", + "Source/Audio", + "Generates DTMF tones", + "Youness Alaoui "); +} + +static void +gst_dtmf_src_class_init (GstDTMFSrcClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSrcClass *gstbasesrc_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + + gobject_class->finalize = gst_dtmf_src_finalize; + gobject_class->set_property = gst_dtmf_src_set_property; + gobject_class->get_property = gst_dtmf_src_get_property; + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INTERVAL, + g_param_spec_uint ("interval", "Interval between tone packets", + "Interval in ms between two tone packets", MIN_PACKET_INTERVAL, + MAX_PACKET_INTERVAL, DEFAULT_PACKET_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_dtmf_src_change_state); + gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_dtmf_src_send_event); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_dtmf_src_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_dtmf_src_unlock_stop); + + gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_dtmf_src_handle_event); + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_dtmf_src_create); + gstbasesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_dtmf_src_negotiate); +} + +static void +event_free (GstDTMFSrcEvent * event) +{ + if (event) + g_slice_free (GstDTMFSrcEvent, event); +} + +static void +gst_dtmf_src_init (GstDTMFSrc * dtmfsrc, GstDTMFSrcClass * g_class) +{ + /* we operate in time */ + gst_base_src_set_format (GST_BASE_SRC (dtmfsrc), GST_FORMAT_TIME); + gst_base_src_set_live (GST_BASE_SRC (dtmfsrc), TRUE); + + dtmfsrc->interval = DEFAULT_PACKET_INTERVAL; + + dtmfsrc->event_queue = g_async_queue_new_full ((GDestroyNotify) event_free); + dtmfsrc->last_event = NULL; + + dtmfsrc->sample_rate = DEFAULT_SAMPLE_RATE; + + GST_DEBUG_OBJECT (dtmfsrc, "init done"); +} + +static void +gst_dtmf_src_finalize (GObject * object) +{ + GstDTMFSrc *dtmfsrc; + + dtmfsrc = GST_DTMF_SRC (object); + + if (dtmfsrc->event_queue) { + g_async_queue_unref (dtmfsrc->event_queue); + dtmfsrc->event_queue = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_dtmf_src_handle_dtmf_event (GstDTMFSrc * dtmfsrc, + const GstStructure * event_structure) +{ + gint event_type; + gboolean start; + gint method; + GstClockTime last_stop; + gint event_number; + gint event_volume; + gboolean correct_order; + + if (!gst_structure_get_int (event_structure, "type", &event_type) || + !gst_structure_get_boolean (event_structure, "start", &start) || + (start == TRUE && event_type != GST_TONE_DTMF_TYPE_EVENT)) + goto failure; + + if (gst_structure_get_int (event_structure, "method", &method)) { + if (method != 2) { + goto failure; + } + } + + if (start) + if (!gst_structure_get_int (event_structure, "number", &event_number) || + !gst_structure_get_int (event_structure, "volume", &event_volume)) + goto failure; + + + GST_OBJECT_LOCK (dtmfsrc); + if (gst_structure_get_clock_time (event_structure, "last-stop", &last_stop)) + dtmfsrc->last_stop = last_stop; + else + dtmfsrc->last_stop = GST_CLOCK_TIME_NONE; + correct_order = (start != dtmfsrc->last_event_was_start); + dtmfsrc->last_event_was_start = start; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (!correct_order) + goto failure; + + if (start) { + GST_DEBUG_OBJECT (dtmfsrc, "Received start event %d with volume %d", + event_number, event_volume); + gst_dtmf_src_add_start_event (dtmfsrc, event_number, event_volume); + } + + else { + GST_DEBUG_OBJECT (dtmfsrc, "Received stop event"); + gst_dtmf_src_add_stop_event (dtmfsrc); + } + + return TRUE; +failure: + return FALSE; +} + +static gboolean +gst_dtmf_src_handle_custom_upstream (GstDTMFSrc * dtmfsrc, GstEvent * event) +{ + gboolean result = FALSE; + const GstStructure *structure; + GstState state; + GstStateChangeReturn ret; + + ret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0); + if (ret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) { + GST_DEBUG_OBJECT (dtmfsrc, "Received event while not in PLAYING state"); + goto ret; + } + + GST_DEBUG_OBJECT (dtmfsrc, "Received event is of our interest"); + structure = gst_event_get_structure (event); + if (structure && gst_structure_has_name (structure, "dtmf-event")) + result = gst_dtmf_src_handle_dtmf_event (dtmfsrc, structure); + +ret: + return result; +} + +static gboolean +gst_dtmf_src_handle_event (GstBaseSrc * src, GstEvent * event) +{ + GstDTMFSrc *dtmfsrc; + gboolean result = FALSE; + + dtmfsrc = GST_DTMF_SRC (src); + + GST_DEBUG_OBJECT (dtmfsrc, "Received an event on the src pad"); + if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) { + result = gst_dtmf_src_handle_custom_upstream (dtmfsrc, event); + } + + return result; +} + + +static gboolean +gst_dtmf_src_send_event (GstElement * element, GstEvent * event) +{ + + if (gst_dtmf_src_handle_event (GST_BASE_SRC (element), event)) + return TRUE; + + return GST_ELEMENT_CLASS (parent_class)->send_event (element, event); +} + +static void +gst_dtmf_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstDTMFSrc *dtmfsrc; + + dtmfsrc = GST_DTMF_SRC (object); + + switch (prop_id) { + case PROP_INTERVAL: + dtmfsrc->interval = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dtmf_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstDTMFSrc *dtmfsrc; + + dtmfsrc = GST_DTMF_SRC (object); + + switch (prop_id) { + case PROP_INTERVAL: + g_value_set_uint (value, dtmfsrc->interval); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dtmf_prepare_timestamps (GstDTMFSrc * dtmfsrc) +{ + GstClockTime last_stop; + GstClockTime timestamp; + + GST_OBJECT_LOCK (dtmfsrc); + last_stop = dtmfsrc->last_stop; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (GST_CLOCK_TIME_IS_VALID (last_stop)) { + timestamp = last_stop; + } else { + GstClock *clock; + + /* If there is no valid start time, lets use now as the start time */ + + clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc)); + if (clock != NULL) { + timestamp = gst_clock_get_time (clock) + - gst_element_get_base_time (GST_ELEMENT (dtmfsrc)); + gst_object_unref (clock); + } else { + gchar *dtmf_name = gst_element_get_name (dtmfsrc); + GST_ERROR_OBJECT (dtmfsrc, "No clock set for element %s", dtmf_name); + dtmfsrc->timestamp = GST_CLOCK_TIME_NONE; + g_free (dtmf_name); + return; + } + } + + /* Make sure the timestamp always goes forward */ + if (timestamp > dtmfsrc->timestamp) + dtmfsrc->timestamp = timestamp; +} + +static void +gst_dtmf_src_add_start_event (GstDTMFSrc * dtmfsrc, gint event_number, + gint event_volume) +{ + + GstDTMFSrcEvent *event = g_slice_new0 (GstDTMFSrcEvent); + event->event_type = DTMF_EVENT_TYPE_START; + event->sample = 0; + event->event_number = CLAMP (event_number, MIN_EVENT, MAX_EVENT); + event->volume = CLAMP (event_volume, MIN_VOLUME, MAX_VOLUME); + + g_async_queue_push (dtmfsrc->event_queue, event); +} + +static void +gst_dtmf_src_add_stop_event (GstDTMFSrc * dtmfsrc) +{ + + GstDTMFSrcEvent *event = g_slice_new0 (GstDTMFSrcEvent); + event->event_type = DTMF_EVENT_TYPE_STOP; + event->sample = 0; + event->event_number = 0; + event->volume = 0; + + g_async_queue_push (dtmfsrc->event_queue, event); +} + +static void +gst_dtmf_src_generate_silence (GstBuffer * buffer, float duration, + gint sample_rate) +{ + gint buf_size; + + /* Create a buffer with data set to 0 */ + buf_size = ((duration / 1000) * sample_rate * SAMPLE_SIZE * CHANNELS) / 8; + GST_BUFFER_SIZE (buffer) = buf_size; + GST_BUFFER_MALLOCDATA (buffer) = g_malloc0 (buf_size); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + +} + +static void +gst_dtmf_src_generate_tone (GstDTMFSrcEvent * event, DTMF_KEY key, + float duration, GstBuffer * buffer, gint sample_rate) +{ + gint16 *p; + gint tone_size; + double i = 0; + double amplitude, f1, f2; + double volume_factor; + + /* Create a buffer for the tone */ + tone_size = ((duration / 1000) * sample_rate * SAMPLE_SIZE * CHANNELS) / 8; + GST_BUFFER_SIZE (buffer) = tone_size; + GST_BUFFER_MALLOCDATA (buffer) = g_malloc (tone_size); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + + p = (gint16 *) GST_BUFFER_MALLOCDATA (buffer); + + volume_factor = pow (10, (-event->volume) / 20); + + /* + * For each sample point we calculate 'x' as the + * the amplitude value. + */ + for (i = 0; i < (tone_size / (SAMPLE_SIZE / 8)); i++) { + /* + * We add the fundamental frequencies together. + */ + f1 = sin (2 * M_PI * key.low_frequency * (event->sample / sample_rate)); + f2 = sin (2 * M_PI * key.high_frequency * (event->sample / sample_rate)); + + amplitude = (f1 + f2) / 2; + + /* Adjust the volume */ + amplitude *= volume_factor; + + /* Make the [-1:1] interval into a [-32767:32767] interval */ + amplitude *= 32767; + + /* Store it in the data buffer */ + *(p++) = (gint16) amplitude; + + (event->sample)++; + } +} + + + +static GstBuffer * +gst_dtmf_src_create_next_tone_packet (GstDTMFSrc * dtmfsrc, + GstDTMFSrcEvent * event) +{ + GstBuffer *buf = NULL; + gboolean send_silence = FALSE; + GstPad *srcpad = GST_BASE_SRC_PAD (dtmfsrc); + + GST_LOG_OBJECT (dtmfsrc, "Creating buffer for tone %s", + DTMF_KEYS[event->event_number].event_name); + + /* create buffer to hold the tone */ + buf = gst_buffer_new (); + + if (event->packet_count * dtmfsrc->interval < MIN_INTER_DIGIT_INTERVAL) { + send_silence = TRUE; + } + + if (send_silence) { + GST_LOG_OBJECT (dtmfsrc, "Generating silence"); + gst_dtmf_src_generate_silence (buf, dtmfsrc->interval, + dtmfsrc->sample_rate); + } else { + GST_LOG_OBJECT (dtmfsrc, "Generating tone"); + gst_dtmf_src_generate_tone (event, DTMF_KEYS[event->event_number], + dtmfsrc->interval, buf, dtmfsrc->sample_rate); + } + event->packet_count++; + + + /* timestamp and duration of GstBuffer */ + GST_BUFFER_DURATION (buf) = dtmfsrc->interval * GST_MSECOND; + GST_BUFFER_TIMESTAMP (buf) = dtmfsrc->timestamp; + + GST_LOG_OBJECT (dtmfsrc, "Creating new buffer with event %u duration " + " gst: %" GST_TIME_FORMAT " at %" GST_TIME_FORMAT, + event->event_number, GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + dtmfsrc->timestamp += GST_BUFFER_DURATION (buf); + + /* Set caps on the buffer before pushing it */ + gst_buffer_set_caps (buf, GST_PAD_CAPS (srcpad)); + + return buf; +} + +static void +gst_dtmf_src_post_message (GstDTMFSrc * dtmfsrc, const gchar * message_name, + GstDTMFSrcEvent * event) +{ + GstStructure *s = NULL; + + switch (event->event_type) { + case DTMF_EVENT_TYPE_START: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, + "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, TRUE, + "number", G_TYPE_INT, event->event_number, + "volume", G_TYPE_INT, event->volume, NULL); + break; + case DTMF_EVENT_TYPE_STOP: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case DTMF_EVENT_TYPE_PAUSE_TASK: + return; + } + + if (s) + gst_element_post_message (GST_ELEMENT (dtmfsrc), + gst_message_new_element (GST_OBJECT (dtmfsrc), s)); +} + +static GstFlowReturn +gst_dtmf_src_create (GstBaseSrc * basesrc, guint64 offset, + guint length, GstBuffer ** buffer) +{ + GstBuffer *buf = NULL; + GstDTMFSrcEvent *event; + GstDTMFSrc *dtmfsrc; + GstClock *clock; + GstClockID *clockid; + GstClockReturn clockret; + + dtmfsrc = GST_DTMF_SRC (basesrc); + + do { + + if (dtmfsrc->last_event == NULL) { + GST_DEBUG_OBJECT (dtmfsrc, "popping"); + event = g_async_queue_pop (dtmfsrc->event_queue); + + GST_DEBUG_OBJECT (dtmfsrc, "popped %d", event->event_type); + + switch (event->event_type) { + case DTMF_EVENT_TYPE_STOP: + GST_WARNING_OBJECT (dtmfsrc, + "Received a DTMF stop event when already stopped"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + case DTMF_EVENT_TYPE_START: + gst_dtmf_prepare_timestamps (dtmfsrc); + + event->packet_count = 0; + dtmfsrc->last_event = event; + event = NULL; + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", + dtmfsrc->last_event); + break; + case DTMF_EVENT_TYPE_PAUSE_TASK: + /* + * We're pushing it back because it has to stay in there until + * the task is really paused (and the queue will then be flushed) + */ + GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task..."); + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) { + g_async_queue_push (dtmfsrc->event_queue, event); + goto paused_locked; + } + GST_OBJECT_UNLOCK (dtmfsrc); + break; + } + if (event) + g_slice_free (GstDTMFSrcEvent, event); + } else if (dtmfsrc->last_event->packet_count * dtmfsrc->interval >= + MIN_DUTY_CYCLE) { + event = g_async_queue_try_pop (dtmfsrc->event_queue); + + if (event != NULL) { + + switch (event->event_type) { + case DTMF_EVENT_TYPE_START: + GST_WARNING_OBJECT (dtmfsrc, + "Received two consecutive DTMF start events"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + case DTMF_EVENT_TYPE_STOP: + g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event); + dtmfsrc->last_event = NULL; + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", event); + break; + case DTMF_EVENT_TYPE_PAUSE_TASK: + /* + * We're pushing it back because it has to stay in there until + * the task is really paused (and the queue will then be flushed) + */ + GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task..."); + + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) { + g_async_queue_push (dtmfsrc->event_queue, event); + goto paused_locked; + } + GST_OBJECT_UNLOCK (dtmfsrc); + + break; + } + g_slice_free (GstDTMFSrcEvent, event); + } + } + } while (dtmfsrc->last_event == NULL); + + GST_LOG_OBJECT (dtmfsrc, "end event check, now wait for the proper time"); + + clock = gst_element_get_clock (GST_ELEMENT (basesrc)); + + clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp + + gst_element_get_base_time (GST_ELEMENT (dtmfsrc))); + gst_object_unref (clock); + + GST_OBJECT_LOCK (dtmfsrc); + if (!dtmfsrc->paused) { + dtmfsrc->clockid = clockid; + GST_OBJECT_UNLOCK (dtmfsrc); + + clockret = gst_clock_id_wait (clockid, NULL); + + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) + clockret = GST_CLOCK_UNSCHEDULED; + } else { + clockret = GST_CLOCK_UNSCHEDULED; + } + gst_clock_id_unref (clockid); + dtmfsrc->clockid = NULL; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (clockret == GST_CLOCK_UNSCHEDULED) { + goto paused; + } + + buf = gst_dtmf_src_create_next_tone_packet (dtmfsrc, dtmfsrc->last_event); + + GST_LOG_OBJECT (dtmfsrc, "Created buffer of size %d", GST_BUFFER_SIZE (buf)); + *buffer = buf; + + return GST_FLOW_OK; + +paused_locked: + GST_OBJECT_UNLOCK (dtmfsrc); + +paused: + + if (dtmfsrc->last_event) { + GST_DEBUG_OBJECT (dtmfsrc, "Stopping current event"); + /* Don't forget to release the stream lock */ + g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event); + dtmfsrc->last_event = NULL; + } + + return GST_FLOW_WRONG_STATE; + +} + +static gboolean +gst_dtmf_src_unlock (GstBaseSrc * src) +{ + GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (src); + GstDTMFSrcEvent *event = NULL; + + GST_DEBUG_OBJECT (dtmfsrc, "Called unlock"); + + GST_OBJECT_LOCK (dtmfsrc); + dtmfsrc->paused = TRUE; + if (dtmfsrc->clockid) { + gst_clock_id_unschedule (dtmfsrc->clockid); + } + GST_OBJECT_UNLOCK (dtmfsrc); + + GST_DEBUG_OBJECT (dtmfsrc, "Pushing the PAUSE_TASK event on unlock request"); + event = g_slice_new0 (GstDTMFSrcEvent); + event->event_type = DTMF_EVENT_TYPE_PAUSE_TASK; + g_async_queue_push (dtmfsrc->event_queue, event); + + return TRUE; +} + + +static gboolean +gst_dtmf_src_unlock_stop (GstBaseSrc * src) +{ + GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (src); + + GST_DEBUG_OBJECT (dtmfsrc, "Unlock stopped"); + + GST_OBJECT_LOCK (dtmfsrc); + dtmfsrc->paused = FALSE; + GST_OBJECT_UNLOCK (dtmfsrc); + + return TRUE; +} + + +static gboolean +gst_dtmf_src_negotiate (GstBaseSrc * basesrc) +{ + GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (basesrc); + GstCaps *caps; + GstStructure *s; + gboolean ret; + + caps = gst_pad_get_allowed_caps (GST_BASE_SRC_PAD (basesrc)); + + if (!caps) + caps = + gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD + (basesrc))); + + if (gst_caps_is_empty (caps)) + return FALSE; + + gst_caps_truncate (caps); + s = gst_caps_get_structure (caps, 0); + + gst_structure_fixate_field_nearest_int (s, "rate", DEFAULT_SAMPLE_RATE); + + if (!gst_structure_get_int (s, "rate", &dtmfsrc->sample_rate)) { + GST_ERROR_OBJECT (dtmfsrc, "Could not get rate"); + gst_caps_unref (caps); + return FALSE; + } + + ret = gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), caps); + + gst_caps_unref (caps); + + return ret; +} + +static GstStateChangeReturn +gst_dtmf_src_change_state (GstElement * element, GstStateChange transition) +{ + GstDTMFSrc *dtmfsrc; + GstStateChangeReturn result; + gboolean no_preroll = FALSE; + GstDTMFSrcEvent *event = NULL; + + dtmfsrc = GST_DTMF_SRC (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + /* Flushing the event queue */ + event = g_async_queue_try_pop (dtmfsrc->event_queue); + + while (event != NULL) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + g_slice_free (GstDTMFSrcEvent, event); + event = g_async_queue_try_pop (dtmfsrc->event_queue); + } + dtmfsrc->last_event_was_start = FALSE; + dtmfsrc->timestamp = 0; + no_preroll = TRUE; + break; + default: + break; + } + + if ((result = + GST_ELEMENT_CLASS (parent_class)->change_state (element, + transition)) == GST_STATE_CHANGE_FAILURE) + goto failure; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + no_preroll = TRUE; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT (dtmfsrc, "Flushing event queue"); + /* Flushing the event queue */ + event = g_async_queue_try_pop (dtmfsrc->event_queue); + + while (event != NULL) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + g_slice_free (GstDTMFSrcEvent, event); + event = g_async_queue_try_pop (dtmfsrc->event_queue); + } + dtmfsrc->last_event_was_start = FALSE; + + break; + default: + break; + } + + if (no_preroll && result == GST_STATE_CHANGE_SUCCESS) + result = GST_STATE_CHANGE_NO_PREROLL; + + return result; + + /* ERRORS */ +failure: + { + GST_ERROR_OBJECT (dtmfsrc, "parent failed state change"); + return result; + } +} + +gboolean +gst_dtmf_src_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "dtmfsrc", + GST_RANK_NONE, GST_TYPE_DTMF_SRC); +} Index: gst-plugins-good0.10/farsight/dtmf/gstdtmfsrc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmfsrc.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,101 @@ +/* GStreamer DTMF source + * + * gstdtmfsrc.h: + * + * Copyright (C) <2007> Collabora. + * Contact: Youness Alaoui + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2005> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_DTMF_SRC_H__ +#define __GST_DTMF_SRC_H__ + +#include +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_DTMF_SRC (gst_dtmf_src_get_type()) +#define GST_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DTMF_SRC,GstDTMFSrc)) +#define GST_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DTMF_SRC,GstDTMFSrcClass)) +#define GST_DTMF_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_DTMF_SRC, GstDTMFSrcClass)) +#define GST_IS_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DTMF_SRC)) +#define GST_IS_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DTMF_SRC)) +#define GST_DTMF_SRC_CAST(obj) ((GstDTMFSrc *)(obj)) +typedef struct _GstDTMFSrc GstDTMFSrc; +typedef struct _GstDTMFSrcClass GstDTMFSrcClass; + +enum _GstDTMFEventType +{ + DTMF_EVENT_TYPE_START, + DTMF_EVENT_TYPE_STOP, + DTMF_EVENT_TYPE_PAUSE_TASK +}; + +typedef enum _GstDTMFEventType GstDTMFEventType; + +struct _GstDTMFSrcEvent +{ + GstDTMFEventType event_type; + double sample; + guint16 event_number; + guint16 volume; + guint32 packet_count; +}; + +typedef struct _GstDTMFSrcEvent GstDTMFSrcEvent; + +/** + * GstDTMFSrc: + * @element: the parent element. + * + * The opaque #GstDTMFSrc data structure. + */ +struct _GstDTMFSrc +{ + /*< private >*/ + GstBaseSrc parent; + GAsyncQueue *event_queue; + GstDTMFSrcEvent *last_event; + gboolean last_event_was_start; + + guint16 interval; + GstClockTime timestamp; + + gboolean paused; + GstClockID clockid; + + GstClockTime last_stop; + + gint sample_rate; +}; + + +struct _GstDTMFSrcClass +{ + GstBaseSrcClass parent_class; +}; + +GType gst_dtmf_src_get_type (void); + +gboolean gst_dtmf_src_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_DTMF_SRC_H__ */ Index: gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfcommon.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfcommon.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,23 @@ + +#ifndef __GST_RTP_DTMF_COMMON_H__ +#define __GST_RTP_DTMF_COMMON_H__ + + +typedef struct +{ + unsigned event:8; /* Current DTMF event */ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + unsigned volume:6; /* power level of the tone, in dBm0 */ + unsigned r:1; /* Reserved-bit */ + unsigned e:1; /* End-bit */ +#elif G_BYTE_ORDER == G_BIG_ENDIAN + unsigned e:1; /* End-bit */ + unsigned r:1; /* Reserved-bit */ + unsigned volume:6; /* power level of the tone, in dBm0 */ +#else +#error "G_BYTE_ORDER should be big or little endian." +#endif + unsigned duration:16; /* Duration of digit, in timestamp units */ +} GstRTPDTMFPayload; + +#endif /* __GST_RTP_DTMF_COMMON_H__ */ Index: gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfdepay.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfdepay.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,539 @@ +/* GstRtpDtmfDepay + * + * Copyright (C) 2008 Collabora Limited + * Copyright (C) 2008 Nokia Corporation + * Contact: Youness Alaoui + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-rtpdtmfdepay + * @see_also: rtpdtmfsrc, rtpdtmfmux + * + * This element takes RTP DTMF packets and produces sound. It also emits a + * message on the #GstBus. + * + * The message is called "dtmf-event" and has the following fields + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * + * G_TYPE_INT + * 0-1 + * Which of the two methods + * specified in RFC 2833 to use. The value should be 0 for tones and 1 for + * named events. Tones are specified by their frequencies and events are specied + * by their number. This element currently only recognizes events. + * Do not confuse with "method" which specified the output. + * + * + * + * number + * G_TYPE_INT + * 0-16 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * This field describes the power level of the tone, expressed in dBm0 + * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of + * valid DTMF is from 0 to -36 dBm0. + * + * + * + * method + * G_TYPE_INT + * 1 + * This field will always been 1 (ie RTP event) from this element. + * + * + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include "gstrtpdtmfdepay.h" + +#define DEFAULT_PACKET_INTERVAL 50 /* ms */ +#define MIN_PACKET_INTERVAL 10 /* ms */ +#define MAX_PACKET_INTERVAL 50 /* ms */ +#define SAMPLE_RATE 8000 +#define SAMPLE_SIZE 16 +#define CHANNELS 1 +#define MIN_DUTY_CYCLE (MIN_INTER_DIGIT_INTERVAL + MIN_PULSE_DURATION) + +#define MIN_UNIT_TIME 0 +#define MAX_UNIT_TIME 1000 +#define DEFAULT_UNIT_TIME 0 + +#define DEFAULT_MAX_DURATION 0 + +typedef struct st_dtmf_key +{ + const char *event_name; + int event_encoding; + float low_frequency; + float high_frequency; +} DTMF_KEY; + +static const DTMF_KEY DTMF_KEYS[] = { + {"DTMF_KEY_EVENT_0", 0, 941, 1336}, + {"DTMF_KEY_EVENT_1", 1, 697, 1209}, + {"DTMF_KEY_EVENT_2", 2, 697, 1336}, + {"DTMF_KEY_EVENT_3", 3, 697, 1477}, + {"DTMF_KEY_EVENT_4", 4, 770, 1209}, + {"DTMF_KEY_EVENT_5", 5, 770, 1336}, + {"DTMF_KEY_EVENT_6", 6, 770, 1477}, + {"DTMF_KEY_EVENT_7", 7, 852, 1209}, + {"DTMF_KEY_EVENT_8", 8, 852, 1336}, + {"DTMF_KEY_EVENT_9", 9, 852, 1477}, + {"DTMF_KEY_EVENT_S", 10, 941, 1209}, + {"DTMF_KEY_EVENT_P", 11, 941, 1477}, + {"DTMF_KEY_EVENT_A", 12, 697, 1633}, + {"DTMF_KEY_EVENT_B", 13, 770, 1633}, + {"DTMF_KEY_EVENT_C", 14, 852, 1633}, + {"DTMF_KEY_EVENT_D", 15, 941, 1633}, +}; + +#define MAX_DTMF_EVENTS 16 + +enum +{ + DTMF_KEY_EVENT_1 = 1, + DTMF_KEY_EVENT_2 = 2, + DTMF_KEY_EVENT_3 = 3, + DTMF_KEY_EVENT_4 = 4, + DTMF_KEY_EVENT_5 = 5, + DTMF_KEY_EVENT_6 = 6, + DTMF_KEY_EVENT_7 = 7, + DTMF_KEY_EVENT_8 = 8, + DTMF_KEY_EVENT_9 = 9, + DTMF_KEY_EVENT_0 = 0, + DTMF_KEY_EVENT_STAR = 10, + DTMF_KEY_EVENT_POUND = 11, + DTMF_KEY_EVENT_A = 12, + DTMF_KEY_EVENT_B = 13, + DTMF_KEY_EVENT_C = 14, + DTMF_KEY_EVENT_D = 15, +}; + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_depay_debug); +#define GST_CAT_DEFAULT gst_rtp_dtmf_depay_debug + +enum +{ + + + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_UNIT_TIME, + PROP_MAX_DURATION +}; + +enum +{ + ARG_0 +}; + +static GstStaticPadTemplate gst_rtp_dtmf_depay_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "width = (int) 16, " + "depth = (int) 16, " + "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", " + "signed = (boolean) true, " + "rate = (int) [0, MAX], " "channels = (int) 1") + ); + +static GstStaticPadTemplate gst_rtp_dtmf_depay_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) [ 0, MAX ], " + "encoding-name = (string) \"TELEPHONE-EVENT\"") + ); + +GST_BOILERPLATE (GstRtpDTMFDepay, gst_rtp_dtmf_depay, GstBaseRTPDepayload, + GST_TYPE_BASE_RTP_DEPAYLOAD); + +static void gst_rtp_dtmf_depay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtp_dtmf_depay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static GstBuffer *gst_rtp_dtmf_depay_process (GstBaseRTPDepayload * depayload, + GstBuffer * buf); +gboolean gst_rtp_dtmf_depay_setcaps (GstBaseRTPDepayload * filter, + GstCaps * caps); + +static void +gst_rtp_dtmf_depay_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, + &gst_rtp_dtmf_depay_src_template); + gst_element_class_add_static_pad_template (element_class, + &gst_rtp_dtmf_depay_sink_template); + + + GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_depay_debug, + "rtpdtmfdepay", 0, "rtpdtmfdepay element"); + gst_element_class_set_details_simple (element_class, + "RTP DTMF packet depayloader", "Codec/Depayloader/Network", + "Generates DTMF Sound from telephone-event RTP packets", + "Youness Alaoui "); +} + +static void +gst_rtp_dtmf_depay_class_init (GstRtpDTMFDepayClass * klass) +{ + GObjectClass *gobject_class; + GstBaseRTPDepayloadClass *gstbasertpdepayload_class; + + gobject_class = (GObjectClass *) klass; + gstbasertpdepayload_class = (GstBaseRTPDepayloadClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_get_property); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_UNIT_TIME, + g_param_spec_uint ("unit-time", "Duration unittime", + "The smallest unit (ms) the duration must be a multiple of (0 disables it)", + MIN_UNIT_TIME, MAX_UNIT_TIME, DEFAULT_UNIT_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_DURATION, + g_param_spec_uint ("max-duration", "Maximum duration", + "The maxumimum duration (ms) of the outgoing soundpacket. " + "(0 = no limit)", 0, G_MAXUINT, DEFAULT_MAX_DURATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstbasertpdepayload_class->process = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_process); + gstbasertpdepayload_class->set_caps = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_setcaps); + +} + +static void +gst_rtp_dtmf_depay_init (GstRtpDTMFDepay * rtpdtmfdepay, + GstRtpDTMFDepayClass * klass) +{ + rtpdtmfdepay->unit_time = DEFAULT_UNIT_TIME; +} + +static void +gst_rtp_dtmf_depay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpDTMFDepay *rtpdtmfdepay; + + rtpdtmfdepay = GST_RTP_DTMF_DEPAY (object); + + switch (prop_id) { + case PROP_UNIT_TIME: + rtpdtmfdepay->unit_time = g_value_get_uint (value); + break; + case PROP_MAX_DURATION: + rtpdtmfdepay->max_duration = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_dtmf_depay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpDTMFDepay *rtpdtmfdepay; + + rtpdtmfdepay = GST_RTP_DTMF_DEPAY (object); + + switch (prop_id) { + case PROP_UNIT_TIME: + g_value_set_uint (value, rtpdtmfdepay->unit_time); + break; + case PROP_MAX_DURATION: + g_value_set_uint (value, rtpdtmfdepay->max_duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_rtp_dtmf_depay_setcaps (GstBaseRTPDepayload * filter, GstCaps * caps) +{ + GstCaps *srccaps; + GstStructure *structure = gst_caps_get_structure (caps, 0); + gint clock_rate = 8000; /* default */ + + gst_structure_get_int (structure, "clock-rate", &clock_rate); + filter->clock_rate = clock_rate; + + srccaps = gst_caps_new_simple ("audio/x-raw-int", + "width", G_TYPE_INT, 16, + "depth", G_TYPE_INT, 16, + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "signed", G_TYPE_BOOLEAN, TRUE, + "channels", G_TYPE_INT, 1, "rate", G_TYPE_INT, clock_rate, NULL); + gst_pad_set_caps (GST_BASE_RTP_DEPAYLOAD_SRCPAD (filter), srccaps); + gst_caps_unref (srccaps); + + return TRUE; +} + +static void +gst_dtmf_src_generate_tone (GstRtpDTMFDepay * rtpdtmfdepay, + GstRTPDTMFPayload payload, GstBuffer * buffer) +{ + gint16 *p; + gint tone_size; + double i = 0; + double amplitude, f1, f2; + double volume_factor; + DTMF_KEY key = DTMF_KEYS[payload.event]; + guint32 clock_rate = 8000 /* default */ ; + GstBaseRTPDepayload *depayload = GST_BASE_RTP_DEPAYLOAD (rtpdtmfdepay); + gint volume; + + clock_rate = depayload->clock_rate; + + /* Create a buffer for the tone */ + tone_size = (payload.duration * SAMPLE_SIZE * CHANNELS) / 8; + GST_BUFFER_SIZE (buffer) = tone_size; + GST_BUFFER_MALLOCDATA (buffer) = g_malloc (tone_size); + GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer); + GST_BUFFER_DURATION (buffer) = payload.duration * GST_SECOND / clock_rate; + volume = payload.volume; + + p = (gint16 *) GST_BUFFER_MALLOCDATA (buffer); + + volume_factor = pow (10, (-volume) / 20); + + /* + * For each sample point we calculate 'x' as the + * the amplitude value. + */ + for (i = 0; i < (tone_size / (SAMPLE_SIZE / 8)); i++) { + /* + * We add the fundamental frequencies together. + */ + f1 = sin (2 * M_PI * key.low_frequency * (rtpdtmfdepay->sample / + clock_rate)); + f2 = sin (2 * M_PI * key.high_frequency * (rtpdtmfdepay->sample / + clock_rate)); + + amplitude = (f1 + f2) / 2; + + /* Adjust the volume */ + amplitude *= volume_factor; + + /* Make the [-1:1] interval into a [-32767:32767] interval */ + amplitude *= 32767; + + /* Store it in the data buffer */ + *(p++) = (gint16) amplitude; + + (rtpdtmfdepay->sample)++; + } +} + + +static GstBuffer * +gst_rtp_dtmf_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf) +{ + + GstRtpDTMFDepay *rtpdtmfdepay = NULL; + GstBuffer *outbuf = NULL; + gint payload_len; + guint8 *payload = NULL; + guint32 timestamp; + GstRTPDTMFPayload dtmf_payload; + gboolean marker; + GstStructure *structure = NULL; + GstMessage *dtmf_message = NULL; + + rtpdtmfdepay = GST_RTP_DTMF_DEPAY (depayload); + + if (!gst_rtp_buffer_validate (buf)) + goto bad_packet; + + payload_len = gst_rtp_buffer_get_payload_len (buf); + payload = gst_rtp_buffer_get_payload (buf); + + if (payload_len != sizeof (GstRTPDTMFPayload)) + goto bad_packet; + + memcpy (&dtmf_payload, payload, sizeof (GstRTPDTMFPayload)); + + if (dtmf_payload.event > MAX_EVENT) + goto bad_packet; + + + marker = gst_rtp_buffer_get_marker (buf); + + timestamp = gst_rtp_buffer_get_timestamp (buf); + + dtmf_payload.duration = g_ntohs (dtmf_payload.duration); + + /* clip to whole units of unit_time */ + if (rtpdtmfdepay->unit_time) { + guint unit_time_clock = + (rtpdtmfdepay->unit_time * depayload->clock_rate) / 1000; + if (dtmf_payload.duration % unit_time_clock) { + /* Make sure we don't overflow the duration */ + if (dtmf_payload.duration < G_MAXUINT16 - unit_time_clock) + dtmf_payload.duration += unit_time_clock - + (dtmf_payload.duration % unit_time_clock); + else + dtmf_payload.duration -= dtmf_payload.duration % unit_time_clock; + } + } + + /* clip to max duration */ + if (rtpdtmfdepay->max_duration) { + guint max_duration_clock = + (rtpdtmfdepay->max_duration * depayload->clock_rate) / 1000; + + if (max_duration_clock < G_MAXUINT16 && + dtmf_payload.duration > max_duration_clock) + dtmf_payload.duration = max_duration_clock; + } + + GST_DEBUG_OBJECT (depayload, "Received new RTP DTMF packet : " + "marker=%d - timestamp=%u - event=%d - duration=%d", + marker, timestamp, dtmf_payload.event, dtmf_payload.duration); + + GST_DEBUG_OBJECT (depayload, + "Previous information : timestamp=%u - duration=%d", + rtpdtmfdepay->previous_ts, rtpdtmfdepay->previous_duration); + + /* First packet */ + if (marker || rtpdtmfdepay->previous_ts != timestamp) { + rtpdtmfdepay->sample = 0; + rtpdtmfdepay->previous_ts = timestamp; + rtpdtmfdepay->previous_duration = dtmf_payload.duration; + rtpdtmfdepay->first_gst_ts = GST_BUFFER_TIMESTAMP (buf); + + structure = gst_structure_new ("dtmf-event", + "number", G_TYPE_INT, dtmf_payload.event, + "volume", G_TYPE_INT, dtmf_payload.volume, + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, NULL); + if (structure) { + dtmf_message = + gst_message_new_element (GST_OBJECT (depayload), structure); + if (dtmf_message) { + if (!gst_element_post_message (GST_ELEMENT (depayload), dtmf_message)) { + GST_ERROR_OBJECT (depayload, + "Unable to send dtmf-event message to bus"); + } + } else { + GST_ERROR_OBJECT (depayload, "Unable to create dtmf-event message"); + } + } else { + GST_ERROR_OBJECT (depayload, "Unable to create dtmf-event structure"); + } + } else { + guint16 duration = dtmf_payload.duration; + dtmf_payload.duration -= rtpdtmfdepay->previous_duration; + /* If late buffer, ignore */ + if (duration > rtpdtmfdepay->previous_duration) + rtpdtmfdepay->previous_duration = duration; + } + + GST_DEBUG_OBJECT (depayload, "new previous duration : %d - new duration : %d" + " - diff : %d - clock rate : %d - timestamp : %" G_GUINT64_FORMAT, + rtpdtmfdepay->previous_duration, dtmf_payload.duration, + (rtpdtmfdepay->previous_duration - dtmf_payload.duration), + depayload->clock_rate, GST_BUFFER_TIMESTAMP (buf)); + + /* If late or duplicate packet (like the redundant end packet). Ignore */ + if (dtmf_payload.duration > 0) { + outbuf = gst_buffer_new (); + gst_dtmf_src_generate_tone (rtpdtmfdepay, dtmf_payload, outbuf); + + + GST_BUFFER_TIMESTAMP (outbuf) = rtpdtmfdepay->first_gst_ts + + (rtpdtmfdepay->previous_duration - dtmf_payload.duration) * + GST_SECOND / depayload->clock_rate; + GST_BUFFER_OFFSET (outbuf) = + (rtpdtmfdepay->previous_duration - dtmf_payload.duration) * + GST_SECOND / depayload->clock_rate; + GST_BUFFER_OFFSET_END (outbuf) = rtpdtmfdepay->previous_duration * + GST_SECOND / depayload->clock_rate; + + GST_DEBUG_OBJECT (depayload, + "timestamp : %" G_GUINT64_FORMAT " - time %" GST_TIME_FORMAT, + GST_BUFFER_TIMESTAMP (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + } + + return outbuf; + + +bad_packet: + GST_ELEMENT_WARNING (rtpdtmfdepay, STREAM, DECODE, + ("Packet did not validate"), (NULL)); + return NULL; +} + +gboolean +gst_rtp_dtmf_depay_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "rtpdtmfdepay", + GST_RANK_MARGINAL, GST_TYPE_RTP_DTMF_DEPAY); +} Index: gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfdepay.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfdepay.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,68 @@ +/* GstRtpDtmfDepay + * + * Copyright (C) 2008 Collabora Limited + * Copyright (C) 2008 Nokia Corporation + * Contact: Youness Alaoui + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RTP_DTMF_DEPAY_H__ +#define __GST_RTP_DTMF_DEPAY_H__ + +#include +#include +#include + +#include "gstdtmfcommon.h" + +G_BEGIN_DECLS +#define GST_TYPE_RTP_DTMF_DEPAY \ + (gst_rtp_dtmf_depay_get_type()) +#define GST_RTP_DTMF_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_DEPAY,GstRtpDTMFDepay)) +#define GST_RTP_DTMF_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_DEPAY,GstRtpDTMFDepayClass)) +#define GST_IS_RTP_DTMF_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_DEPAY)) +#define GST_IS_RTP_DTMF_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_DEPAY)) +typedef struct _GstRtpDTMFDepay GstRtpDTMFDepay; +typedef struct _GstRtpDTMFDepayClass GstRtpDTMFDepayClass; + +struct _GstRtpDTMFDepay +{ + /*< private >*/ + GstBaseRTPDepayload depayload; + double sample; + guint32 previous_ts; + guint16 previous_duration; + GstClockTime first_gst_ts; + guint unit_time; + guint max_duration; +}; + +struct _GstRtpDTMFDepayClass +{ + GstBaseRTPDepayloadClass parent_class; +}; + +GType gst_rtp_dtmf_depay_get_type (void); + +gboolean gst_rtp_dtmf_depay_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_RTP_DTMF_DEPAY_H__ */ Index: gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfsrc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfsrc.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,1152 @@ +/* GStreamer RTP DTMF source + * + * gstrtpdtmfsrc.c: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-rtpdtmfsrc + * @see_also: dtmfsrc, rtpdtmfdepay, rtpdtmfmux + * + * The RTPDTMFSrc element generates RTP DTMF (RFC 2833) event packets on request + * from application. The application communicates the beginning and end of a + * DTMF event using custom upstream gstreamer events. To report a DTMF event, an + * application must send an event of type GST_EVENT_CUSTOM_UPSTREAM, having a + * structure of name "dtmf-event" with fields set according to the following + * table: + * + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * type + * G_TYPE_INT + * 0-1 + * The application uses this field to specify which of the two methods + * specified in RFC 2833 to use. The value should be 0 for tones and 1 for + * named events. Tones are specified by their frequencies and events are specied + * by their number. This element can only take events as input. Do not confuse + * with "method" which specified the output. + * + * + * + * number + * G_TYPE_INT + * 0-15 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * This field describes the power level of the tone, expressed in dBm0 + * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of + * valid DTMF is from 0 to -36 dBm0. Can be omitted if start is set to FALSE. + * + * + * + * start + * G_TYPE_BOOLEAN + * True or False + * Whether the event is starting or ending. + * + * + * method + * G_TYPE_INT + * 1 + * The method used for sending event, this element will react if this + * field is absent or 1. + * + * + * + * + * + * + * For example, the following code informs the pipeline (and in turn, the + * RTPDTMFSrc element inside the pipeline) about the start of an RTP DTMF named + * event '1' of volume -25 dBm0: + * + * + * structure = gst_structure_new ("dtmf-event", + * "type", G_TYPE_INT, 1, + * "number", G_TYPE_INT, 1, + * "volume", G_TYPE_INT, 25, + * "start", G_TYPE_BOOLEAN, TRUE, NULL); + * + * event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, structure); + * gst_element_send_event (pipeline, event); + * + * + * When a DTMF tone actually starts or stop, a "dtmf-event-processed" + * element #GstMessage with the same fields as the "dtmf-event" + * #GstEvent that was used to request the event. Also, if any event + * has not been processed when the element goes from the PAUSED to the + * READY state, then a "dtmf-event-dropped" message is posted on the + * #GstBus in the order that they were received. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "gstrtpdtmfsrc.h" + +#define GST_RTP_DTMF_TYPE_EVENT 1 +#define DEFAULT_PTIME 40 /* ms */ +#define DEFAULT_SSRC -1 +#define DEFAULT_PT 96 +#define DEFAULT_TIMESTAMP_OFFSET -1 +#define DEFAULT_SEQNUM_OFFSET -1 +#define DEFAULT_CLOCK_RATE 8000 + +#define DEFAULT_PACKET_REDUNDANCY 1 +#define MIN_PACKET_REDUNDANCY 1 +#define MAX_PACKET_REDUNDANCY 5 + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_src_debug); +#define GST_CAT_DEFAULT gst_rtp_dtmf_src_debug + +/* signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_SSRC, + PROP_TIMESTAMP_OFFSET, + PROP_SEQNUM_OFFSET, + PROP_PT, + PROP_CLOCK_RATE, + PROP_TIMESTAMP, + PROP_SEQNUM, + PROP_REDUNDANCY +}; + +static GstStaticPadTemplate gst_rtp_dtmf_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) [ 96, 127 ], " + "clock-rate = (int) [ 0, MAX ], " + "ssrc = (int) [ 0, MAX ], " + "encoding-name = (string) \"TELEPHONE-EVENT\"") + /* "events = (string) \"0-15\" */ + ); + + +GST_BOILERPLATE (GstRTPDTMFSrc, gst_rtp_dtmf_src, GstBaseSrc, + GST_TYPE_BASE_SRC); + +static void gst_rtp_dtmf_src_finalize (GObject * object); + +static void gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtp_dtmf_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc, + GstEvent * event); +static GstStateChangeReturn gst_rtp_dtmf_src_change_state (GstElement * element, + GstStateChange transition); +static void gst_rtp_dtmf_src_add_start_event (GstRTPDTMFSrc * dtmfsrc, + gint event_number, gint event_volume); +static void gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc); + +static gboolean gst_rtp_dtmf_src_unlock (GstBaseSrc * src); +static gboolean gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src); +static GstFlowReturn gst_rtp_dtmf_src_create (GstBaseSrc * basesrc, + guint64 offset, guint length, GstBuffer ** buffer); +static gboolean gst_rtp_dtmf_src_negotiate (GstBaseSrc * basesrc); + + +static void +gst_rtp_dtmf_src_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_src_debug, + "rtpdtmfsrc", 0, "rtpdtmfsrc element"); + + gst_element_class_add_static_pad_template (element_class, + &gst_rtp_dtmf_src_template); + + gst_element_class_set_details_simple (element_class, + "RTP DTMF packet generator", "Source/Network", + "Generates RTP DTMF packets", "Zeeshan Ali "); +} + +static void +gst_rtp_dtmf_src_class_init (GstRTPDTMFSrcClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSrcClass *gstbasesrc_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_finalize); + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_get_property); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMESTAMP, + g_param_spec_uint ("timestamp", "Timestamp", + "The RTP timestamp of the last processed packet", + 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM, + g_param_spec_uint ("seqnum", "Sequence number", + "The RTP sequence number of the last processed packet", + 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_TIMESTAMP_OFFSET, g_param_spec_int ("timestamp-offset", + "Timestamp Offset", + "Offset to add to all outgoing timestamps (-1 = random)", -1, + G_MAXINT, DEFAULT_TIMESTAMP_OFFSET, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM_OFFSET, + g_param_spec_int ("seqnum-offset", "Sequence number Offset", + "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXINT, + DEFAULT_SEQNUM_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CLOCK_RATE, + g_param_spec_uint ("clock-rate", "clockrate", + "The clock-rate at which to generate the dtmf packets", + 0, G_MAXUINT, DEFAULT_CLOCK_RATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSRC, + g_param_spec_uint ("ssrc", "SSRC", + "The SSRC of the packets (-1 == random)", + 0, G_MAXUINT, DEFAULT_SSRC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT, + g_param_spec_uint ("pt", "payload type", + "The payload type of the packets", + 0, 0x80, DEFAULT_PT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_REDUNDANCY, + g_param_spec_uint ("packet-redundancy", "Packet Redundancy", + "Number of packets to send to indicate start and stop dtmf events", + MIN_PACKET_REDUNDANCY, MAX_PACKET_REDUNDANCY, + DEFAULT_PACKET_REDUNDANCY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_change_state); + + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_unlock); + gstbasesrc_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_unlock_stop); + + gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_handle_event); + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_create); + gstbasesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_negotiate); +} + +static void +gst_rtp_dtmf_src_event_free (GstRTPDTMFSrcEvent * event) +{ + if (event) { + if (event->payload) + g_slice_free (GstRTPDTMFPayload, event->payload); + g_slice_free (GstRTPDTMFSrcEvent, event); + } +} + +static void +gst_rtp_dtmf_src_init (GstRTPDTMFSrc * object, GstRTPDTMFSrcClass * g_class) +{ + gst_base_src_set_format (GST_BASE_SRC (object), GST_FORMAT_TIME); + gst_base_src_set_live (GST_BASE_SRC (object), TRUE); + + object->ssrc = DEFAULT_SSRC; + object->seqnum_offset = DEFAULT_SEQNUM_OFFSET; + object->ts_offset = DEFAULT_TIMESTAMP_OFFSET; + object->pt = DEFAULT_PT; + object->clock_rate = DEFAULT_CLOCK_RATE; + object->ptime = DEFAULT_PTIME; + object->packet_redundancy = DEFAULT_PACKET_REDUNDANCY; + + object->event_queue = + g_async_queue_new_full ((GDestroyNotify) gst_rtp_dtmf_src_event_free); + object->payload = NULL; + + GST_DEBUG_OBJECT (object, "init done"); +} + +static void +gst_rtp_dtmf_src_finalize (GObject * object) +{ + GstRTPDTMFSrc *dtmfsrc; + + dtmfsrc = GST_RTP_DTMF_SRC (object); + + if (dtmfsrc->event_queue) { + g_async_queue_unref (dtmfsrc->event_queue); + dtmfsrc->event_queue = NULL; + } + + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_rtp_dtmf_src_handle_dtmf_event (GstRTPDTMFSrc * dtmfsrc, + const GstStructure * event_structure) +{ + gint event_type; + gboolean start; + gint method; + GstClockTime last_stop; + gint event_number; + gint event_volume; + gboolean correct_order; + + if (!gst_structure_get_int (event_structure, "type", &event_type) || + !gst_structure_get_boolean (event_structure, "start", &start) || + event_type != GST_RTP_DTMF_TYPE_EVENT) + goto failure; + + if (gst_structure_get_int (event_structure, "method", &method)) { + if (method != 1) { + goto failure; + } + } + + if (start) + if (!gst_structure_get_int (event_structure, "number", &event_number) || + !gst_structure_get_int (event_structure, "volume", &event_volume)) + goto failure; + + GST_OBJECT_LOCK (dtmfsrc); + if (gst_structure_get_clock_time (event_structure, "last-stop", &last_stop)) + dtmfsrc->last_stop = last_stop; + else + dtmfsrc->last_stop = GST_CLOCK_TIME_NONE; + correct_order = (start != dtmfsrc->last_event_was_start); + dtmfsrc->last_event_was_start = start; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (!correct_order) + goto failure; + + if (start) { + if (!gst_structure_get_int (event_structure, "number", &event_number) || + !gst_structure_get_int (event_structure, "volume", &event_volume)) + goto failure; + + GST_DEBUG_OBJECT (dtmfsrc, "Received start event %d with volume %d", + event_number, event_volume); + gst_rtp_dtmf_src_add_start_event (dtmfsrc, event_number, event_volume); + } + + else { + GST_DEBUG_OBJECT (dtmfsrc, "Received stop event"); + gst_rtp_dtmf_src_add_stop_event (dtmfsrc); + } + + return TRUE; +failure: + return FALSE; +} + +static gboolean +gst_rtp_dtmf_src_handle_custom_upstream (GstRTPDTMFSrc * dtmfsrc, + GstEvent * event) +{ + gboolean result = FALSE; + gchar *struct_str; + const GstStructure *structure; + + GstState state; + GstStateChangeReturn ret; + + ret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0); + if (ret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) { + GST_DEBUG_OBJECT (dtmfsrc, "Received event while not in PLAYING state"); + goto ret; + } + + GST_DEBUG_OBJECT (dtmfsrc, "Received event is of our interest"); + structure = gst_event_get_structure (event); + struct_str = gst_structure_to_string (structure); + GST_DEBUG_OBJECT (dtmfsrc, "Event has structure %s", struct_str); + g_free (struct_str); + if (structure && gst_structure_has_name (structure, "dtmf-event")) + result = gst_rtp_dtmf_src_handle_dtmf_event (dtmfsrc, structure); + +ret: + return result; +} + +static gboolean +gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc, GstEvent * event) +{ + GstRTPDTMFSrc *dtmfsrc; + gboolean result = FALSE; + + dtmfsrc = GST_RTP_DTMF_SRC (basesrc); + + GST_DEBUG_OBJECT (dtmfsrc, "Received an event on the src pad"); + if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) { + result = gst_rtp_dtmf_src_handle_custom_upstream (dtmfsrc, event); + } + + return result; +} + +static void +gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRTPDTMFSrc *dtmfsrc; + + dtmfsrc = GST_RTP_DTMF_SRC (object); + + switch (prop_id) { + case PROP_TIMESTAMP_OFFSET: + dtmfsrc->ts_offset = g_value_get_int (value); + break; + case PROP_SEQNUM_OFFSET: + dtmfsrc->seqnum_offset = g_value_get_int (value); + break; + case PROP_CLOCK_RATE: + dtmfsrc->clock_rate = g_value_get_uint (value); + dtmfsrc->dirty = TRUE; + break; + case PROP_SSRC: + dtmfsrc->ssrc = g_value_get_uint (value); + break; + case PROP_PT: + dtmfsrc->pt = g_value_get_uint (value); + dtmfsrc->dirty = TRUE; + break; + case PROP_REDUNDANCY: + dtmfsrc->packet_redundancy = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_dtmf_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstRTPDTMFSrc *dtmfsrc; + + dtmfsrc = GST_RTP_DTMF_SRC (object); + + switch (prop_id) { + case PROP_TIMESTAMP_OFFSET: + g_value_set_int (value, dtmfsrc->ts_offset); + break; + case PROP_SEQNUM_OFFSET: + g_value_set_int (value, dtmfsrc->seqnum_offset); + break; + case PROP_CLOCK_RATE: + g_value_set_uint (value, dtmfsrc->clock_rate); + break; + case PROP_SSRC: + g_value_set_uint (value, dtmfsrc->ssrc); + break; + case PROP_PT: + g_value_set_uint (value, dtmfsrc->pt); + break; + case PROP_TIMESTAMP: + g_value_set_uint (value, dtmfsrc->rtp_timestamp); + break; + case PROP_SEQNUM: + g_value_set_uint (value, dtmfsrc->seqnum); + break; + case PROP_REDUNDANCY: + g_value_set_uint (value, dtmfsrc->packet_redundancy); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_rtp_dtmf_prepare_timestamps (GstRTPDTMFSrc * dtmfsrc) +{ + GstClockTime last_stop; + + GST_OBJECT_LOCK (dtmfsrc); + last_stop = dtmfsrc->last_stop; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (GST_CLOCK_TIME_IS_VALID (last_stop)) { + dtmfsrc->start_timestamp = last_stop; + } else { + GstClock *clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc)); + + if (clock == NULL) + return FALSE; + + dtmfsrc->start_timestamp = gst_clock_get_time (clock) + - gst_element_get_base_time (GST_ELEMENT (dtmfsrc)); + gst_object_unref (clock); + } + + /* If the last stop was in the past, then lets add the buffers together */ + if (dtmfsrc->start_timestamp < dtmfsrc->timestamp) + dtmfsrc->start_timestamp = dtmfsrc->timestamp; + + dtmfsrc->timestamp = dtmfsrc->start_timestamp; + + dtmfsrc->rtp_timestamp = dtmfsrc->ts_base + + gst_util_uint64_scale_int (gst_segment_to_running_time (&GST_BASE_SRC + (dtmfsrc)->segment, GST_FORMAT_TIME, dtmfsrc->timestamp), + dtmfsrc->clock_rate, GST_SECOND); + + return TRUE; +} + + +static void +gst_rtp_dtmf_src_add_start_event (GstRTPDTMFSrc * dtmfsrc, gint event_number, + gint event_volume) +{ + + GstRTPDTMFSrcEvent *event = g_slice_new0 (GstRTPDTMFSrcEvent); + event->event_type = RTP_DTMF_EVENT_TYPE_START; + + event->payload = g_slice_new0 (GstRTPDTMFPayload); + event->payload->event = CLAMP (event_number, MIN_EVENT, MAX_EVENT); + event->payload->volume = CLAMP (event_volume, MIN_VOLUME, MAX_VOLUME); + event->payload->duration = dtmfsrc->ptime * dtmfsrc->clock_rate / 1000; + + g_async_queue_push (dtmfsrc->event_queue, event); +} + +static void +gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc) +{ + + GstRTPDTMFSrcEvent *event = g_slice_new0 (GstRTPDTMFSrcEvent); + event->event_type = RTP_DTMF_EVENT_TYPE_STOP; + + g_async_queue_push (dtmfsrc->event_queue, event); +} + + +static void +gst_rtp_dtmf_prepare_rtp_headers (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf) +{ + gst_rtp_buffer_set_ssrc (buf, dtmfsrc->current_ssrc); + gst_rtp_buffer_set_payload_type (buf, dtmfsrc->pt); + /* Only the very first packet gets a marker */ + if (dtmfsrc->first_packet) { + gst_rtp_buffer_set_marker (buf, TRUE); + } else if (dtmfsrc->last_packet) { + dtmfsrc->payload->e = 1; + } + + dtmfsrc->seqnum++; + gst_rtp_buffer_set_seq (buf, dtmfsrc->seqnum); + + /* timestamp of RTP header */ + gst_rtp_buffer_set_timestamp (buf, dtmfsrc->rtp_timestamp); +} + +static void +gst_rtp_dtmf_prepare_buffer_data (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf) +{ + GstRTPDTMFPayload *payload; + + gst_rtp_dtmf_prepare_rtp_headers (dtmfsrc, buf); + + /* timestamp and duration of GstBuffer */ + /* Redundant buffer have no duration ... */ + if (dtmfsrc->redundancy_count > 1) + GST_BUFFER_DURATION (buf) = 0; + else + GST_BUFFER_DURATION (buf) = dtmfsrc->ptime * GST_MSECOND; + GST_BUFFER_TIMESTAMP (buf) = dtmfsrc->timestamp; + + + payload = (GstRTPDTMFPayload *) gst_rtp_buffer_get_payload (buf); + + /* copy payload and convert to network-byte order */ + g_memmove (payload, dtmfsrc->payload, sizeof (GstRTPDTMFPayload)); + + payload->duration = g_htons (payload->duration); + + if (dtmfsrc->redundancy_count <= 1 && dtmfsrc->last_packet) { + GstClockTime inter_digit_interval = MIN_INTER_DIGIT_INTERVAL; + + if (inter_digit_interval % dtmfsrc->ptime != 0) + inter_digit_interval += dtmfsrc->ptime - + (MIN_INTER_DIGIT_INTERVAL % dtmfsrc->ptime); + + GST_BUFFER_DURATION (buf) += inter_digit_interval * GST_MSECOND; + } + + GST_LOG_OBJECT (dtmfsrc, "Creating new buffer with event %u duration " + " gst: %" GST_TIME_FORMAT " at %" GST_TIME_FORMAT "(rtp ts:%u dur:%u)", + dtmfsrc->payload->event, GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), dtmfsrc->rtp_timestamp, + dtmfsrc->payload->duration); + + /* duration of DTMF payloadfor the NEXT packet */ + /* not updated for redundant packets */ + if (dtmfsrc->redundancy_count <= 1) + dtmfsrc->payload->duration += dtmfsrc->ptime * dtmfsrc->clock_rate / 1000; + + if (GST_CLOCK_TIME_IS_VALID (dtmfsrc->timestamp)) + dtmfsrc->timestamp += GST_BUFFER_DURATION (buf); + +} + +static GstBuffer * +gst_rtp_dtmf_src_create_next_rtp_packet (GstRTPDTMFSrc * dtmfsrc) +{ + GstBuffer *buf = NULL; + + /* create buffer to hold the payload */ + buf = gst_rtp_buffer_new_allocate (sizeof (GstRTPDTMFPayload), 0, 0); + + gst_rtp_dtmf_prepare_buffer_data (dtmfsrc, buf); + + /* Set caps on the buffer before pushing it */ + gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (dtmfsrc))); + + return buf; +} + + +static void +gst_dtmf_src_post_message (GstRTPDTMFSrc * dtmfsrc, const gchar * message_name, + GstRTPDTMFSrcEvent * event) +{ + GstStructure *s = NULL; + + switch (event->event_type) { + case RTP_DTMF_EVENT_TYPE_START: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, + "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, TRUE, + "number", G_TYPE_INT, event->payload->event, + "volume", G_TYPE_INT, event->payload->volume, NULL); + break; + case RTP_DTMF_EVENT_TYPE_STOP: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case RTP_DTMF_EVENT_TYPE_PAUSE_TASK: + return; + } + + if (s) + gst_element_post_message (GST_ELEMENT (dtmfsrc), + gst_message_new_element (GST_OBJECT (dtmfsrc), s)); +} + + +static GstFlowReturn +gst_rtp_dtmf_src_create (GstBaseSrc * basesrc, guint64 offset, + guint length, GstBuffer ** buffer) +{ + GstRTPDTMFSrcEvent *event; + GstRTPDTMFSrc *dtmfsrc; + GstClock *clock; + GstClockID *clockid; + GstClockReturn clockret; + + dtmfsrc = GST_RTP_DTMF_SRC (basesrc); + + do { + + if (dtmfsrc->payload == NULL) { + GST_DEBUG_OBJECT (dtmfsrc, "popping"); + event = g_async_queue_pop (dtmfsrc->event_queue); + + GST_DEBUG_OBJECT (dtmfsrc, "popped %d", event->event_type); + + switch (event->event_type) { + case RTP_DTMF_EVENT_TYPE_STOP: + GST_WARNING_OBJECT (dtmfsrc, + "Received a DTMF stop event when already stopped"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + + case RTP_DTMF_EVENT_TYPE_START: + dtmfsrc->first_packet = TRUE; + dtmfsrc->last_packet = FALSE; + /* Set the redundancy on the first packet */ + dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy; + if (!gst_rtp_dtmf_prepare_timestamps (dtmfsrc)) + goto no_clock; + + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", event); + dtmfsrc->payload = event->payload; + event->payload = NULL; + break; + + case RTP_DTMF_EVENT_TYPE_PAUSE_TASK: + /* + * We're pushing it back because it has to stay in there until + * the task is really paused (and the queue will then be flushed + */ + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) { + g_async_queue_push (dtmfsrc->event_queue, event); + goto paused_locked; + } + GST_OBJECT_UNLOCK (dtmfsrc); + break; + } + + gst_rtp_dtmf_src_event_free (event); + } else if (!dtmfsrc->first_packet && !dtmfsrc->last_packet && + (dtmfsrc->timestamp - dtmfsrc->start_timestamp) / GST_MSECOND >= + MIN_PULSE_DURATION) { + GST_DEBUG_OBJECT (dtmfsrc, "try popping"); + event = g_async_queue_try_pop (dtmfsrc->event_queue); + + + if (event != NULL) { + GST_DEBUG_OBJECT (dtmfsrc, "try popped %d", event->event_type); + + switch (event->event_type) { + case RTP_DTMF_EVENT_TYPE_START: + GST_WARNING_OBJECT (dtmfsrc, + "Received two consecutive DTMF start events"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + + case RTP_DTMF_EVENT_TYPE_STOP: + dtmfsrc->first_packet = FALSE; + dtmfsrc->last_packet = TRUE; + /* Set the redundancy on the last packet */ + dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy; + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", event); + break; + + case RTP_DTMF_EVENT_TYPE_PAUSE_TASK: + /* + * We're pushing it back because it has to stay in there until + * the task is really paused (and the queue will then be flushed) + */ + GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task..."); + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) { + g_async_queue_push (dtmfsrc->event_queue, event); + goto paused_locked; + } + GST_OBJECT_UNLOCK (dtmfsrc); + break; + } + gst_rtp_dtmf_src_event_free (event); + } + } + } while (dtmfsrc->payload == NULL); + + + GST_DEBUG_OBJECT (dtmfsrc, "Processed events, now lets wait on the clock"); + + clock = gst_element_get_clock (GST_ELEMENT (basesrc)); + if (!clock) + goto no_clock; + clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp + + gst_element_get_base_time (GST_ELEMENT (dtmfsrc))); + gst_object_unref (clock); + + GST_OBJECT_LOCK (dtmfsrc); + if (!dtmfsrc->paused) { + dtmfsrc->clockid = clockid; + GST_OBJECT_UNLOCK (dtmfsrc); + + clockret = gst_clock_id_wait (clockid, NULL); + + GST_OBJECT_LOCK (dtmfsrc); + if (dtmfsrc->paused) + clockret = GST_CLOCK_UNSCHEDULED; + } else { + clockret = GST_CLOCK_UNSCHEDULED; + } + gst_clock_id_unref (clockid); + dtmfsrc->clockid = NULL; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (clockret == GST_CLOCK_UNSCHEDULED) { + goto paused; + } + +send_last: + + if (dtmfsrc->dirty) + if (!gst_rtp_dtmf_src_negotiate (basesrc)) + return GST_FLOW_NOT_NEGOTIATED; + + /* create buffer to hold the payload */ + *buffer = gst_rtp_dtmf_src_create_next_rtp_packet (dtmfsrc); + + if (dtmfsrc->redundancy_count) + dtmfsrc->redundancy_count--; + + /* Only the very first one has a marker */ + dtmfsrc->first_packet = FALSE; + + /* This is the end of the event */ + if (dtmfsrc->last_packet == TRUE && dtmfsrc->redundancy_count == 0) { + + g_slice_free (GstRTPDTMFPayload, dtmfsrc->payload); + dtmfsrc->payload = NULL; + + dtmfsrc->last_packet = FALSE; + } + + return GST_FLOW_OK; + +paused_locked: + + GST_OBJECT_UNLOCK (dtmfsrc); + +paused: + + if (dtmfsrc->payload) { + dtmfsrc->first_packet = FALSE; + dtmfsrc->last_packet = TRUE; + /* Set the redundanc on the last packet */ + dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy; + goto send_last; + } else { + return GST_FLOW_WRONG_STATE; + } + +no_clock: + GST_ELEMENT_ERROR (dtmfsrc, STREAM, MUX, ("No available clock"), + ("No available clock")); + gst_pad_pause_task (GST_BASE_SRC_PAD (dtmfsrc)); + return GST_FLOW_ERROR; +} + + +static gboolean +gst_rtp_dtmf_src_negotiate (GstBaseSrc * basesrc) +{ + GstCaps *srccaps, *peercaps; + GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (basesrc); + gboolean ret; + + /* fill in the defaults, there properties cannot be negotiated. */ + srccaps = gst_caps_new_simple ("application/x-rtp", + "media", G_TYPE_STRING, "audio", + "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", NULL); + + /* the peer caps can override some of the defaults */ + peercaps = gst_pad_peer_get_caps (GST_BASE_SRC_PAD (basesrc)); + if (peercaps == NULL) { + /* no peer caps, just add the other properties */ + gst_caps_set_simple (srccaps, + "payload", G_TYPE_INT, dtmfsrc->pt, + "ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc, + "clock-base", G_TYPE_UINT, dtmfsrc->ts_base, + "clock-rate", G_TYPE_INT, dtmfsrc->clock_rate, + "seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base, NULL); + + GST_DEBUG_OBJECT (dtmfsrc, "no peer caps: %" GST_PTR_FORMAT, srccaps); + } else { + GstCaps *temp; + GstStructure *s; + const GValue *value; + gint pt; + gint clock_rate; + + /* peer provides caps we can use to fixate, intersect. This always returns a + * writable caps. */ + temp = gst_caps_intersect (srccaps, peercaps); + gst_caps_unref (srccaps); + gst_caps_unref (peercaps); + + if (!temp) { + GST_DEBUG_OBJECT (dtmfsrc, "Could not get intersection with peer caps"); + return FALSE; + } + + if (gst_caps_is_empty (temp)) { + GST_DEBUG_OBJECT (dtmfsrc, "Intersection with peer caps is empty"); + gst_caps_unref (temp); + return FALSE; + } + + /* now fixate, start by taking the first caps */ + gst_caps_truncate (temp); + srccaps = temp; + + /* get first structure */ + s = gst_caps_get_structure (srccaps, 0); + + if (gst_structure_get_int (s, "payload", &pt)) { + /* use peer pt */ + dtmfsrc->pt = pt; + GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt); + } else { + if (gst_structure_has_field (s, "payload")) { + /* can only fixate if there is a field */ + gst_structure_fixate_field_nearest_int (s, "payload", dtmfsrc->pt); + gst_structure_get_int (s, "payload", &pt); + GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt); + } else { + /* no pt field, use the internal pt */ + pt = dtmfsrc->pt; + gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal pt %d", pt); + } + } + + if (gst_structure_get_int (s, "clock-rate", &clock_rate)) { + dtmfsrc->clock_rate = clock_rate; + GST_LOG_OBJECT (dtmfsrc, "using clock-rate from caps %d", + dtmfsrc->clock_rate); + } else { + GST_LOG_OBJECT (dtmfsrc, "using existing clock-rate %d", + dtmfsrc->clock_rate); + } + gst_structure_set (s, "clock-rate", G_TYPE_INT, dtmfsrc->clock_rate, NULL); + + + if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "ssrc"); + dtmfsrc->current_ssrc = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer ssrc %08x", dtmfsrc->current_ssrc); + } else { + /* FIXME, fixate_nearest_uint would be even better */ + gst_structure_set (s, "ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc, NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal ssrc %08x", + dtmfsrc->current_ssrc); + } + + if (gst_structure_has_field_typed (s, "clock-base", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "clock-base"); + dtmfsrc->ts_base = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer clock-base %u", dtmfsrc->ts_base); + } else { + /* FIXME, fixate_nearest_uint would be even better */ + gst_structure_set (s, "clock-base", G_TYPE_UINT, dtmfsrc->ts_base, NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal clock-base %u", + dtmfsrc->ts_base); + } + if (gst_structure_has_field_typed (s, "seqnum-base", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "seqnum-base"); + dtmfsrc->seqnum_base = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer seqnum-base %u", + dtmfsrc->seqnum_base); + } else { + /* FIXME, fixate_nearest_uint would be even better */ + gst_structure_set (s, "seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base, + NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal seqnum-base %u", + dtmfsrc->seqnum_base); + } + + if (gst_structure_has_field_typed (s, "ptime", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "ptime"); + dtmfsrc->ptime = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer ptime %u", dtmfsrc->ptime); + } else if (gst_structure_has_field_typed (s, "maxptime", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "maxptime"); + dtmfsrc->ptime = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer maxptime as ptime %u", + dtmfsrc->ptime); + } else { + /* FIXME, fixate_nearest_uint would be even better */ + gst_structure_set (s, "ptime", G_TYPE_UINT, dtmfsrc->ptime, NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal ptime %u", dtmfsrc->ptime); + } + + + GST_DEBUG_OBJECT (dtmfsrc, "with peer caps: %" GST_PTR_FORMAT, srccaps); + } + + ret = gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), srccaps); + gst_caps_unref (srccaps); + + dtmfsrc->dirty = FALSE; + + return ret; + +} + + +static void +gst_rtp_dtmf_src_ready_to_paused (GstRTPDTMFSrc * dtmfsrc) +{ + if (dtmfsrc->ssrc == -1) + dtmfsrc->current_ssrc = g_random_int (); + else + dtmfsrc->current_ssrc = dtmfsrc->ssrc; + + if (dtmfsrc->seqnum_offset == -1) + dtmfsrc->seqnum_base = g_random_int_range (0, G_MAXUINT16); + else + dtmfsrc->seqnum_base = dtmfsrc->seqnum_offset; + dtmfsrc->seqnum = dtmfsrc->seqnum_base; + + if (dtmfsrc->ts_offset == -1) + dtmfsrc->ts_base = g_random_int (); + else + dtmfsrc->ts_base = dtmfsrc->ts_offset; + + dtmfsrc->timestamp = 0; +} + +static GstStateChangeReturn +gst_rtp_dtmf_src_change_state (GstElement * element, GstStateChange transition) +{ + GstRTPDTMFSrc *dtmfsrc; + GstStateChangeReturn result; + gboolean no_preroll = FALSE; + GstRTPDTMFSrcEvent *event = NULL; + + dtmfsrc = GST_RTP_DTMF_SRC (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_rtp_dtmf_src_ready_to_paused (dtmfsrc); + + /* Flushing the event queue */ + while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + gst_rtp_dtmf_src_event_free (event); + } + dtmfsrc->last_event_was_start = FALSE; + + no_preroll = TRUE; + break; + default: + break; + } + + if ((result = + GST_ELEMENT_CLASS (parent_class)->change_state (element, + transition)) == GST_STATE_CHANGE_FAILURE) + goto failure; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + no_preroll = TRUE; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + + /* Flushing the event queue */ + while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + gst_rtp_dtmf_src_event_free (event); + } + dtmfsrc->last_event_was_start = FALSE; + + /* Indicate that we don't do PRE_ROLL */ + break; + + default: + break; + } + + if (no_preroll && result == GST_STATE_CHANGE_SUCCESS) + result = GST_STATE_CHANGE_NO_PREROLL; + + return result; + + /* ERRORS */ +failure: + { + GST_ERROR_OBJECT (dtmfsrc, "parent failed state change"); + return result; + } +} + + +static gboolean +gst_rtp_dtmf_src_unlock (GstBaseSrc * src) +{ + GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src); + GstRTPDTMFSrcEvent *event = NULL; + + GST_DEBUG_OBJECT (dtmfsrc, "Called unlock"); + + GST_OBJECT_LOCK (dtmfsrc); + dtmfsrc->paused = TRUE; + if (dtmfsrc->clockid) { + gst_clock_id_unschedule (dtmfsrc->clockid); + } + GST_OBJECT_UNLOCK (dtmfsrc); + + GST_DEBUG_OBJECT (dtmfsrc, "Pushing the PAUSE_TASK event on unlock request"); + event = g_slice_new0 (GstRTPDTMFSrcEvent); + event->event_type = RTP_DTMF_EVENT_TYPE_PAUSE_TASK; + g_async_queue_push (dtmfsrc->event_queue, event); + + return TRUE; +} + + +static gboolean +gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src) +{ + GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src); + + GST_DEBUG_OBJECT (dtmfsrc, "Unlock stopped"); + + GST_OBJECT_LOCK (dtmfsrc); + dtmfsrc->paused = FALSE; + GST_OBJECT_UNLOCK (dtmfsrc); + + return TRUE; +} + +gboolean +gst_rtp_dtmf_src_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "rtpdtmfsrc", + GST_RANK_NONE, GST_TYPE_RTP_DTMF_SRC); +} Index: gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfsrc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstrtpdtmfsrc.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,115 @@ +/* GStreamer RTP DTMF source + * + * gstrtpdtmfsrc.h: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2005> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RTP_DTMF_SRC_H__ +#define __GST_RTP_DTMF_SRC_H__ + +#include +#include +#include + +#include "gstdtmfcommon.h" + +G_BEGIN_DECLS +#define GST_TYPE_RTP_DTMF_SRC (gst_rtp_dtmf_src_get_type()) +#define GST_RTP_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_SRC,GstRTPDTMFSrc)) +#define GST_RTP_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_SRC,GstRTPDTMFSrcClass)) +#define GST_RTP_DTMF_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTP_DTMF_SRC, GstRTPDTMFSrcClass)) +#define GST_IS_RTP_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_SRC)) +#define GST_IS_RTP_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_SRC)) +#define GST_RTP_DTMF_SRC_CAST(obj) ((GstRTPDTMFSrc *)(obj)) +typedef struct _GstRTPDTMFSrc GstRTPDTMFSrc; +typedef struct _GstRTPDTMFSrcClass GstRTPDTMFSrcClass; + + + +enum _GstRTPDTMFEventType +{ + RTP_DTMF_EVENT_TYPE_START, + RTP_DTMF_EVENT_TYPE_STOP, + RTP_DTMF_EVENT_TYPE_PAUSE_TASK +}; + +typedef enum _GstRTPDTMFEventType GstRTPDTMFEventType; + +struct _GstRTPDTMFSrcEvent +{ + GstRTPDTMFEventType event_type; + GstRTPDTMFPayload *payload; +}; + +typedef struct _GstRTPDTMFSrcEvent GstRTPDTMFSrcEvent; + +/** + * GstRTPDTMFSrc: + * @element: the parent element. + * + * The opaque #GstRTPDTMFSrc data structure. + */ +struct _GstRTPDTMFSrc +{ + /*< private >*/ + GstBaseSrc basesrc; + + GAsyncQueue *event_queue; + GstClockID clockid; + gboolean paused; + GstRTPDTMFPayload *payload; + + GstClockTime timestamp; + GstClockTime start_timestamp; + gboolean first_packet; + gboolean last_packet; + guint32 ts_base; + guint16 seqnum_base; + gint16 seqnum_offset; + guint16 seqnum; + gint32 ts_offset; + guint32 rtp_timestamp; + guint pt; + guint ssrc; + guint current_ssrc; + guint16 ptime; + guint16 packet_redundancy; + guint32 clock_rate; + gboolean last_event_was_start; + + GstClockTime last_stop; + + gboolean dirty; + guint16 redundancy_count; +}; + +struct _GstRTPDTMFSrcClass +{ + GstBaseSrcClass parent_class; +}; + +GType gst_rtp_dtmf_src_get_type (void); + +gboolean gst_rtp_dtmf_src_plugin_init (GstPlugin * plugin); + + +G_END_DECLS +#endif /* __GST_RTP_DTMF_SRC_H__ */ Index: gst-plugins-good0.10/farsight/dtmf/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/Makefile.am 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,36 @@ +plugin_LTLIBRARIES = libgstdtmf.la + +libgstdtmf_la_SOURCES = gstdtmfsrc.c \ + gstdtmfdetect.c \ + gstrtpdtmfsrc.c \ + gstrtpdtmfdepay.c \ + tone_detect.c \ + gstdtmf.c + +noinst_HEADERS = gstdtmfsrc.h \ + gstdtmfdetect.h \ + gstrtpdtmfsrc.h \ + gstrtpdtmfdepay.h \ + gstdtmfcommon.h \ + tone_detect.h + +libgstdtmf_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \ + -DEXTERN_BUF -DRTP_SUPPORT +libgstdtmf_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgstdtmf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdtmf_la_LIBTOOLFLAGS = --tag=disable-static + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstdtmf -:SHARED libgstdtmf \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstdtmf_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstdtmf_la_CFLAGS) \ + -:LDFLAGS $(libgstdtmf_la_LDFLAGS) \ + $(libgstdtmf_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ \ No newline at end of file Index: gst-plugins-good0.10/farsight/dtmf/tone_detect.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/tone_detect.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,503 @@ +/* + * DTMF Receiver module, part of: + * BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01 + * + * Part of the "Zapata" Computer Telephony Technology. + * + * See http://www.bsdtelephony.com.mx + * + * + * The technologies, software, hardware, designs, drawings, scheumatics, board + * layouts and/or artwork, concepts, methodologies (including the use of all + * of these, and that which is derived from the use of all of these), all other + * intellectual properties contained herein, and all intellectual property + * rights have been and shall continue to be expressly for the benefit of all + * mankind, and are perpetually placed in the public domain, and may be used, + * copied, and/or modified by anyone, in any manner, for any legal purpose, + * without restriction. + * + * This module written by Stephen Underwood. + */ + +/* + tone_detect.c - General telephony tone detection, and specific + detection of DTMF. + + Copyright (C) 2001 Steve Underwood + + Despite my general liking of the GPL, I place this code in the + public domain for the benefit of all mankind - even the slimy + ones who might try to proprietize my work and use it to my + detriment. +*/ + +#include +#include +#include +#include +#include +#include "tone_detect.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +//#define USE_3DNOW + +/* Basic DTMF specs: + * + * Minimum tone on = 40ms + * Minimum tone off = 50ms + * Maximum digit rate = 10 per second + * Normal twist <= 8dB accepted + * Reverse twist <= 4dB accepted + * S/N >= 15dB will detect OK + * Attenuation <= 26dB will detect OK + * Frequency tolerance +- 1.5% will detect, +-3.5% will reject + */ + +#define SAMPLE_RATE 8000.0 + +#define DTMF_THRESHOLD 8.0e7 +#define FAX_THRESHOLD 8.0e7 +#define FAX_2ND_HARMONIC 2.0 /* 4dB */ +#define DTMF_NORMAL_TWIST 6.3 /* 8dB */ +#define DTMF_REVERSE_TWIST ((isradio) ? 4.0 : 2.5) /* 4dB normal */ +#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */ +#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */ +#define DTMF_2ND_HARMONIC_ROW ((isradio) ? 1.7 : 2.5) /* 4dB normal */ +#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */ + +static tone_detection_descriptor_t dtmf_detect_row[4]; +static tone_detection_descriptor_t dtmf_detect_col[4]; +static tone_detection_descriptor_t dtmf_detect_row_2nd[4]; +static tone_detection_descriptor_t dtmf_detect_col_2nd[4]; +static tone_detection_descriptor_t fax_detect; +static tone_detection_descriptor_t fax_detect_2nd; + +static float dtmf_row[] = { + 697.0, 770.0, 852.0, 941.0 +}; + +static float dtmf_col[] = { + 1209.0, 1336.0, 1477.0, 1633.0 +}; + +static float fax_freq = 1100.0; + +static char dtmf_positions[] = "123A" "456B" "789C" "*0#D"; + +static void +goertzel_init (goertzel_state_t * s, tone_detection_descriptor_t * t) +{ + s->v2 = s->v3 = 0.0; + s->fac = t->fac; +} + +/*- End of function --------------------------------------------------------*/ + +#if defined(USE_3DNOW) +static inline void +_dtmf_goertzel_update (goertzel_state_t * s, float x[], int samples) +{ + int n; + float v; + int i; + float vv[16]; + + vv[4] = s[0].v2; + vv[5] = s[1].v2; + vv[6] = s[2].v2; + vv[7] = s[3].v2; + vv[8] = s[0].v3; + vv[9] = s[1].v3; + vv[10] = s[2].v3; + vv[11] = s[3].v3; + vv[12] = s[0].fac; + vv[13] = s[1].fac; + vv[14] = s[2].fac; + vv[15] = s[3].fac; + + //v1 = s->v2; + //s->v2 = s->v3; + //s->v3 = s->fac*s->v2 - v1 + x[0]; + + __asm__ __volatile__ (" femms;\n" + " movq 16(%%edx),%%mm2;\n" + " movq 24(%%edx),%%mm3;\n" + " movq 32(%%edx),%%mm4;\n" + " movq 40(%%edx),%%mm5;\n" + " movq 48(%%edx),%%mm6;\n" + " movq 56(%%edx),%%mm7;\n" + " jmp 1f;\n" + " .align 32;\n" + " 1: ;\n" + " prefetch (%%eax);\n" + " movq %%mm3,%%mm1;\n" + " movq %%mm2,%%mm0;\n" + " movq %%mm5,%%mm3;\n" + " movq %%mm4,%%mm2;\n" + " pfmul %%mm7,%%mm5;\n" + " pfmul %%mm6,%%mm4;\n" + " pfsub %%mm1,%%mm5;\n" + " pfsub %%mm0,%%mm4;\n" + " movq (%%eax),%%mm0;\n" + " movq %%mm0,%%mm1;\n" + " punpckldq %%mm0,%%mm1;\n" + " add $4,%%eax;\n" + " pfadd %%mm1,%%mm5;\n" + " pfadd %%mm1,%%mm4;\n" + " dec %%ecx;\n" + " jnz 1b;\n" + " movq %%mm2,16(%%edx);\n" + " movq %%mm3,24(%%edx);\n" + " movq %%mm4,32(%%edx);\n" + " movq %%mm5,40(%%edx);\n" + " femms;\n"::"c" (samples), "a" (x), "d" (vv) + :"memory", "eax", "ecx"); + + s[0].v2 = vv[4]; + s[1].v2 = vv[5]; + s[2].v2 = vv[6]; + s[3].v2 = vv[7]; + s[0].v3 = vv[8]; + s[1].v3 = vv[9]; + s[2].v3 = vv[10]; + s[3].v3 = vv[11]; +} +#endif +/*- End of function --------------------------------------------------------*/ + +void +zap_goertzel_update (goertzel_state_t * s, int16_t x[], int samples) +{ + int i; + float v1; + + for (i = 0; i < samples; i++) { + v1 = s->v2; + s->v2 = s->v3; + s->v3 = s->fac * s->v2 - v1 + x[i]; + } +} + +/*- End of function --------------------------------------------------------*/ + +float +zap_goertzel_result (goertzel_state_t * s) +{ + return s->v3 * s->v3 + s->v2 * s->v2 - s->v2 * s->v3 * s->fac; +} + +/*- End of function --------------------------------------------------------*/ + +void +zap_dtmf_detect_init (dtmf_detect_state_t * s) +{ + int i; + float theta; + + s->hit1 = s->hit2 = 0; + + for (i = 0; i < 4; i++) { + theta = 2.0 * G_PI * (dtmf_row[i] / SAMPLE_RATE); + dtmf_detect_row[i].fac = 2.0 * cos (theta); + + theta = 2.0 * G_PI * (dtmf_col[i] / SAMPLE_RATE); + dtmf_detect_col[i].fac = 2.0 * cos (theta); + + theta = 2.0 * G_PI * (dtmf_row[i] * 2.0 / SAMPLE_RATE); + dtmf_detect_row_2nd[i].fac = 2.0 * cos (theta); + + theta = 2.0 * G_PI * (dtmf_col[i] * 2.0 / SAMPLE_RATE); + dtmf_detect_col_2nd[i].fac = 2.0 * cos (theta); + + goertzel_init (&s->row_out[i], &dtmf_detect_row[i]); + goertzel_init (&s->col_out[i], &dtmf_detect_col[i]); + goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]); + goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]); + + s->energy = 0.0; + } + + /* Same for the fax dector */ + theta = 2.0 * G_PI * (fax_freq / SAMPLE_RATE); + fax_detect.fac = 2.0 * cos (theta); + goertzel_init (&s->fax_tone, &fax_detect); + + /* Same for the fax dector 2nd harmonic */ + theta = 2.0 * G_PI * (fax_freq * 2.0 / SAMPLE_RATE); + fax_detect_2nd.fac = 2.0 * cos (theta); + goertzel_init (&s->fax_tone2nd, &fax_detect_2nd); + + s->current_sample = 0; + s->detected_digits = 0; + s->lost_digits = 0; + s->digits[0] = '\0'; + s->mhit = 0; +} + +/*- End of function --------------------------------------------------------*/ + +int +zap_dtmf_detect (dtmf_detect_state_t * s, + int16_t amp[], int samples, int isradio) +{ + + float row_energy[4]; + float col_energy[4]; + float fax_energy; + float fax_energy_2nd; + float famp; + float v1; + int i; + int j; + int sample; + int best_row; + int best_col; + int hit; + int limit; + + hit = 0; + for (sample = 0; sample < samples; sample = limit) { + /* 102 is optimised to meet the DTMF specs. */ + if ((samples - sample) >= (102 - s->current_sample)) + limit = sample + (102 - s->current_sample); + else + limit = samples; +#if defined(USE_3DNOW) + _dtmf_goertzel_update (s->row_out, amp + sample, limit - sample); + _dtmf_goertzel_update (s->col_out, amp + sample, limit - sample); + _dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample); + _dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample); + /* XXX Need to fax detect for 3dnow too XXX */ +#warning "Fax Support Broken" +#else + /* The following unrolled loop takes only 35% (rough estimate) of the + time of a rolled loop on the machine on which it was developed */ + for (j = sample; j < limit; j++) { + famp = amp[j]; + + s->energy += famp * famp; + + /* With GCC 2.95, the following unrolled code seems to take about 35% + (rough estimate) as long as a neat little 0-3 loop */ + v1 = s->row_out[0].v2; + s->row_out[0].v2 = s->row_out[0].v3; + s->row_out[0].v3 = s->row_out[0].fac * s->row_out[0].v2 - v1 + famp; + + v1 = s->col_out[0].v2; + s->col_out[0].v2 = s->col_out[0].v3; + s->col_out[0].v3 = s->col_out[0].fac * s->col_out[0].v2 - v1 + famp; + + v1 = s->row_out[1].v2; + s->row_out[1].v2 = s->row_out[1].v3; + s->row_out[1].v3 = s->row_out[1].fac * s->row_out[1].v2 - v1 + famp; + + v1 = s->col_out[1].v2; + s->col_out[1].v2 = s->col_out[1].v3; + s->col_out[1].v3 = s->col_out[1].fac * s->col_out[1].v2 - v1 + famp; + + v1 = s->row_out[2].v2; + s->row_out[2].v2 = s->row_out[2].v3; + s->row_out[2].v3 = s->row_out[2].fac * s->row_out[2].v2 - v1 + famp; + + v1 = s->col_out[2].v2; + s->col_out[2].v2 = s->col_out[2].v3; + s->col_out[2].v3 = s->col_out[2].fac * s->col_out[2].v2 - v1 + famp; + + v1 = s->row_out[3].v2; + s->row_out[3].v2 = s->row_out[3].v3; + s->row_out[3].v3 = s->row_out[3].fac * s->row_out[3].v2 - v1 + famp; + + v1 = s->col_out[3].v2; + s->col_out[3].v2 = s->col_out[3].v3; + s->col_out[3].v3 = s->col_out[3].fac * s->col_out[3].v2 - v1 + famp; + + v1 = s->col_out2nd[0].v2; + s->col_out2nd[0].v2 = s->col_out2nd[0].v3; + s->col_out2nd[0].v3 = + s->col_out2nd[0].fac * s->col_out2nd[0].v2 - v1 + famp; + + v1 = s->row_out2nd[0].v2; + s->row_out2nd[0].v2 = s->row_out2nd[0].v3; + s->row_out2nd[0].v3 = + s->row_out2nd[0].fac * s->row_out2nd[0].v2 - v1 + famp; + + v1 = s->col_out2nd[1].v2; + s->col_out2nd[1].v2 = s->col_out2nd[1].v3; + s->col_out2nd[1].v3 = + s->col_out2nd[1].fac * s->col_out2nd[1].v2 - v1 + famp; + + v1 = s->row_out2nd[1].v2; + s->row_out2nd[1].v2 = s->row_out2nd[1].v3; + s->row_out2nd[1].v3 = + s->row_out2nd[1].fac * s->row_out2nd[1].v2 - v1 + famp; + + v1 = s->col_out2nd[2].v2; + s->col_out2nd[2].v2 = s->col_out2nd[2].v3; + s->col_out2nd[2].v3 = + s->col_out2nd[2].fac * s->col_out2nd[2].v2 - v1 + famp; + + v1 = s->row_out2nd[2].v2; + s->row_out2nd[2].v2 = s->row_out2nd[2].v3; + s->row_out2nd[2].v3 = + s->row_out2nd[2].fac * s->row_out2nd[2].v2 - v1 + famp; + + v1 = s->col_out2nd[3].v2; + s->col_out2nd[3].v2 = s->col_out2nd[3].v3; + s->col_out2nd[3].v3 = + s->col_out2nd[3].fac * s->col_out2nd[3].v2 - v1 + famp; + + v1 = s->row_out2nd[3].v2; + s->row_out2nd[3].v2 = s->row_out2nd[3].v3; + s->row_out2nd[3].v3 = + s->row_out2nd[3].fac * s->row_out2nd[3].v2 - v1 + famp; + + /* Update fax tone */ + v1 = s->fax_tone.v2; + s->fax_tone.v2 = s->fax_tone.v3; + s->fax_tone.v3 = s->fax_tone.fac * s->fax_tone.v2 - v1 + famp; + + v1 = s->fax_tone.v2; + s->fax_tone2nd.v2 = s->fax_tone2nd.v3; + s->fax_tone2nd.v3 = s->fax_tone2nd.fac * s->fax_tone2nd.v2 - v1 + famp; + } +#endif + s->current_sample += (limit - sample); + if (s->current_sample < 102) + continue; + + /* Detect the fax energy, too */ + fax_energy = zap_goertzel_result (&s->fax_tone); + + /* We are at the end of a DTMF detection block */ + /* Find the peak row and the peak column */ + row_energy[0] = zap_goertzel_result (&s->row_out[0]); + col_energy[0] = zap_goertzel_result (&s->col_out[0]); + + for (best_row = best_col = 0, i = 1; i < 4; i++) { + row_energy[i] = zap_goertzel_result (&s->row_out[i]); + if (row_energy[i] > row_energy[best_row]) + best_row = i; + col_energy[i] = zap_goertzel_result (&s->col_out[i]); + if (col_energy[i] > col_energy[best_col]) + best_col = i; + } + hit = 0; + /* Basic signal level test and the twist test */ + if (row_energy[best_row] >= DTMF_THRESHOLD + && + col_energy[best_col] >= DTMF_THRESHOLD + && + col_energy[best_col] < row_energy[best_row] * DTMF_REVERSE_TWIST + && col_energy[best_col] * DTMF_NORMAL_TWIST > row_energy[best_row]) { + /* Relative peak test */ + for (i = 0; i < 4; i++) { + if ((i != best_col + && col_energy[i] * DTMF_RELATIVE_PEAK_COL > + col_energy[best_col]) + || (i != best_row + && row_energy[i] * DTMF_RELATIVE_PEAK_ROW > + row_energy[best_row])) { + break; + } + } + /* ... and second harmonic test */ + if (i >= 4 + && + (row_energy[best_row] + col_energy[best_col]) > 42.0 * s->energy + && + zap_goertzel_result (&s->col_out2nd[best_col]) * + DTMF_2ND_HARMONIC_COL < col_energy[best_col] + && zap_goertzel_result (&s->row_out2nd[best_row]) * + DTMF_2ND_HARMONIC_ROW < row_energy[best_row]) { + hit = dtmf_positions[(best_row << 2) + best_col]; + /* Look for two successive similar results */ + /* The logic in the next test is: + We need two successive identical clean detects, with + something different preceeding it. This can work with + back to back differing digits. More importantly, it + can work with nasty phones that give a very wobbly start + to a digit. */ + if (hit == s->hit3 && s->hit3 != s->hit2) { + s->mhit = hit; + s->digit_hits[(best_row << 2) + best_col]++; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } + } + } + if (!hit && (fax_energy >= FAX_THRESHOLD) + && (fax_energy > s->energy * 21.0)) { + fax_energy_2nd = zap_goertzel_result (&s->fax_tone2nd); + if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) { +#if 0 + printf ("Fax energy/Second Harmonic: %f/%f\n", fax_energy, + fax_energy_2nd); +#endif + /* XXX Probably need better checking than just this the energy XXX */ + hit = 'f'; + s->fax_hits++; + } /* Don't reset fax hits counter */ + } else { + if (s->fax_hits > 5) { + s->mhit = 'f'; + s->detected_digits++; + if (s->current_digits < MAX_DTMF_DIGITS) { + s->digits[s->current_digits++] = hit; + s->digits[s->current_digits] = '\0'; + } else { + s->lost_digits++; + } + } + s->fax_hits = 0; + } + s->hit1 = s->hit2; + s->hit2 = s->hit3; + s->hit3 = hit; + /* Reinitialise the detector for the next block */ + for (i = 0; i < 4; i++) { + goertzel_init (&s->row_out[i], &dtmf_detect_row[i]); + goertzel_init (&s->col_out[i], &dtmf_detect_col[i]); + goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]); + goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]); + } + goertzel_init (&s->fax_tone, &fax_detect); + goertzel_init (&s->fax_tone2nd, &fax_detect_2nd); + s->energy = 0.0; + s->current_sample = 0; + } + if ((!s->mhit) || (s->mhit != hit)) { + s->mhit = 0; + return (0); + } + return (hit); +} + +/*- End of function --------------------------------------------------------*/ + +int +zap_dtmf_get (dtmf_detect_state_t * s, char *buf, int max) +{ + if (max > s->current_digits) + max = s->current_digits; + if (max > 0) { + memcpy (buf, s->digits, max); + memmove (s->digits, s->digits + max, s->current_digits - max); + s->current_digits -= max; + } + buf[max] = '\0'; + return max; +} + +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ Index: gst-plugins-good0.10/farsight/dtmf/tone_detect.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/tone_detect.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,95 @@ +/* + * Header file for DTMF Receiver module, part of: + * BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01 + * + * Part of the "Zapata" Computer Telephony Technology. + * + * See http://www.bsdtelephony.com.mx + * + * + * The technologies, software, hardware, designs, drawings, scheumatics, board + * layouts and/or artwork, concepts, methodologies (including the use of all + * of these, and that which is derived from the use of all of these), all other + * intellectual properties contained herein, and all intellectual property + * rights have been and shall continue to be expressly for the benefit of all + * mankind, and are perpetually placed in the public domain, and may be used, + * copied, and/or modified by anyone, in any manner, for any legal purpose, + * without restriction. + * + * This module written by Stephen Underwood. + */ +/* + tone_detect.h - General telephony tone detection, and specific + detection of DTMF. + + Copyright (C) 2001 Steve Underwood + + Despite my general liking of the GPL, I place this code in the + public domain for the benefit of all mankind - even the slimy + ones who might try to proprietize my work and use it to my + detriment. +*/ + +#ifndef __TONE_DETECT_H__ +#define __TONE_DETECT_H__ + +#include "_stdint.h" + +#include + +typedef struct +{ + float v2; + float v3; + float fac; +} goertzel_state_t; + +#define MAX_DTMF_DIGITS 128 + +typedef struct +{ + int hit1; + int hit2; + int hit3; + int hit4; + int mhit; + + goertzel_state_t row_out[4]; + goertzel_state_t col_out[4]; + goertzel_state_t row_out2nd[4]; + goertzel_state_t col_out2nd[4]; + goertzel_state_t fax_tone; + goertzel_state_t fax_tone2nd; + float energy; + + int current_sample; + char digits[MAX_DTMF_DIGITS + 1]; + int current_digits; + int detected_digits; + int lost_digits; + int digit_hits[16]; + int fax_hits; +} dtmf_detect_state_t; + +typedef struct +{ + float fac; +} tone_detection_descriptor_t; + +void zap_goertzel_update(goertzel_state_t *s, + gint16 x[], + int samples); +float zap_goertzel_result (goertzel_state_t *s); + +void zap_dtmf_detect_init (dtmf_detect_state_t *s); +int zap_dtmf_detect (dtmf_detect_state_t *s, + gint16 amp[], + int samples, + int isradio); +int zap_dtmf_get (dtmf_detect_state_t *s, + char *buf, + int max); + +#endif /* __TONE_DETECT_H__ */ + +/*- End of file ------------------------------------------------------------*/ Index: gst-plugins-good0.10/farsight/liveadder/liveadder.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/liveadder/liveadder.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,1539 @@ +/* + * Farsight Voice+Video library + * + * Copyright 2008 Collabora Ltd + * Copyright 2008 Nokia Corporation + * @author: Olivier Crete + * + * With parts copied from the adder plugin which is + * Copyright (C) 1999,2000 Erik Walthinsen + * 2001 Thomas + * 2005,2006 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +/** + * SECTION:element-liveadder + * @see_also: adder + * + * The live adder allows to mix several streams into one by adding the data. + * Mixed data is clamped to the min/max values of the data format. + * + * Unlike the adder, the liveadder mixes the streams according the their + * timestamps and waits for some milli-seconds before trying doing the mixing. + * + * Last reviewed on 2008-02-10 (0.10.11) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "liveadder.h" + +#include + +#include + +#define DEFAULT_LATENCY_MS 60 + +GST_DEBUG_CATEGORY_STATIC (live_adder_debug); +#define GST_CAT_DEFAULT (live_adder_debug) + +static GstStaticPadTemplate gst_live_adder_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; " + GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS) + ); + +static GstStaticPadTemplate gst_live_adder_src_template = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; " + GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS) + ); + +/* Valve signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_LATENCY, +}; + +typedef struct _GstLiveAdderPadPrivate +{ + GstSegment segment; + gboolean eos; + + GstClockTime expected_timestamp; + +} GstLiveAdderPadPrivate; + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (live_adder_debug, "liveadder", 0, "Live Adder"); + +GST_BOILERPLATE_FULL (GstLiveAdder, gst_live_adder, GstElement, + GST_TYPE_ELEMENT, _do_init); + + +static void gst_live_adder_finalize (GObject * object); +static void +gst_live_adder_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void +gst_live_adder_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstPad *gst_live_adder_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * unused); +static void gst_live_adder_release_pad (GstElement * element, GstPad * pad); +static GstStateChangeReturn +gst_live_adder_change_state (GstElement * element, GstStateChange transition); + +static gboolean gst_live_adder_setcaps (GstPad * pad, GstCaps * caps); +static GstCaps *gst_live_adder_sink_getcaps (GstPad * pad); +static gboolean +gst_live_adder_src_activate_push (GstPad * pad, gboolean active); +static gboolean gst_live_adder_src_event (GstPad * pad, GstEvent * event); + +static void gst_live_adder_loop (gpointer data); +static gboolean gst_live_adder_query (GstPad * pad, GstQuery * query); +static gboolean gst_live_adder_sink_event (GstPad * pad, GstEvent * event); + + +static void reset_pad_private (GstPad * pad); + +/* clipping versions */ +#define MAKE_FUNC(name,type,ttype,min,max) \ +static void name (type *out, type *in, gint bytes) { \ + gint i; \ + for (i = 0; i < bytes / sizeof (type); i++) \ + out[i] = CLAMP ((ttype)out[i] + (ttype)in[i], min, max); \ +} + +/* non-clipping versions (for float) */ +#define MAKE_FUNC_NC(name,type,ttype) \ +static void name (type *out, type *in, gint bytes) { \ + gint i; \ + for (i = 0; i < bytes / sizeof (type); i++) \ + out[i] = (ttype)out[i] + (ttype)in[i]; \ +} + +/* *INDENT-OFF* */ +MAKE_FUNC (add_int32, gint32, gint64, G_MININT32, G_MAXINT32) +MAKE_FUNC (add_int16, gint16, gint32, G_MININT16, G_MAXINT16) +MAKE_FUNC (add_int8, gint8, gint16, G_MININT8, G_MAXINT8) +MAKE_FUNC (add_uint32, guint32, guint64, 0, G_MAXUINT32) +MAKE_FUNC (add_uint16, guint16, guint32, 0, G_MAXUINT16) +MAKE_FUNC (add_uint8, guint8, guint16, 0, G_MAXUINT8) +MAKE_FUNC_NC (add_float64, gdouble, gdouble) +MAKE_FUNC_NC (add_float32, gfloat, gfloat) +/* *INDENT-ON* */ + + +static void +gst_live_adder_base_init (gpointer klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + + gst_element_class_add_static_pad_template (gstelement_class, + &gst_live_adder_src_template); + gst_element_class_add_static_pad_template (gstelement_class, + &gst_live_adder_sink_template); + gst_element_class_set_details_simple (gstelement_class, "Live Adder element", + "Generic/Audio", + "Mixes live/discontinuous audio streams", + "Olivier Crete "); +} + +static void +gst_live_adder_class_init (GstLiveAdderClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + + gobject_class->finalize = gst_live_adder_finalize; + gobject_class->set_property = gst_live_adder_set_property; + gobject_class->get_property = gst_live_adder_get_property; + + gstelement_class->request_new_pad = gst_live_adder_request_new_pad; + gstelement_class->release_pad = gst_live_adder_release_pad; + gstelement_class->change_state = gst_live_adder_change_state; + + g_object_class_install_property (gobject_class, PROP_LATENCY, + g_param_spec_uint ("latency", "Buffer latency in ms", + "Amount of data to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_live_adder_init (GstLiveAdder * adder, GstLiveAdderClass * klass) +{ + adder->srcpad = + gst_pad_new_from_static_template (&gst_live_adder_src_template, "src"); + gst_pad_set_getcaps_function (adder->srcpad, + GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps)); + gst_pad_set_setcaps_function (adder->srcpad, + GST_DEBUG_FUNCPTR (gst_live_adder_setcaps)); + gst_pad_set_query_function (adder->srcpad, + GST_DEBUG_FUNCPTR (gst_live_adder_query)); + gst_pad_set_event_function (adder->srcpad, + GST_DEBUG_FUNCPTR (gst_live_adder_src_event)); + gst_pad_set_activatepush_function (adder->srcpad, + GST_DEBUG_FUNCPTR (gst_live_adder_src_activate_push)); + gst_element_add_pad (GST_ELEMENT (adder), adder->srcpad); + + adder->format = GST_LIVE_ADDER_FORMAT_UNSET; + adder->padcount = 0; + adder->func = NULL; + adder->not_empty_cond = g_cond_new (); + + adder->next_timestamp = GST_CLOCK_TIME_NONE; + + adder->latency_ms = DEFAULT_LATENCY_MS; + + adder->buffers = g_queue_new (); +} + + +static void +gst_live_adder_finalize (GObject * object) +{ + GstLiveAdder *adder = GST_LIVE_ADDER (object); + + g_cond_free (adder->not_empty_cond); + + g_queue_foreach (adder->buffers, (GFunc) gst_mini_object_unref, NULL); + while (g_queue_pop_head (adder->buffers)) { + } + g_queue_free (adder->buffers); + + g_list_free (adder->sinkpads); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +static void +gst_live_adder_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstLiveAdder *adder = GST_LIVE_ADDER (object); + + switch (prop_id) { + case PROP_LATENCY: + { + guint64 new_latency, old_latency; + + new_latency = g_value_get_uint (value); + + GST_OBJECT_LOCK (adder); + old_latency = adder->latency_ms; + adder->latency_ms = new_latency; + GST_OBJECT_UNLOCK (adder); + + /* post message if latency changed, this will inform the parent pipeline + * that a latency reconfiguration is possible/needed. */ + if (new_latency != old_latency) { + GST_DEBUG_OBJECT (adder, "latency changed to: %" GST_TIME_FORMAT, + GST_TIME_ARGS (new_latency)); + + gst_element_post_message (GST_ELEMENT_CAST (adder), + gst_message_new_latency (GST_OBJECT_CAST (adder))); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_live_adder_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstLiveAdder *adder = GST_LIVE_ADDER (object); + + switch (prop_id) { + case PROP_LATENCY: + GST_OBJECT_LOCK (adder); + g_value_set_uint (value, adder->latency_ms); + GST_OBJECT_UNLOCK (adder); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* we can only accept caps that we and downstream can handle. */ +static GstCaps * +gst_live_adder_sink_getcaps (GstPad * pad) +{ + GstLiveAdder *adder; + GstCaps *result, *peercaps, *sinkcaps; + + adder = GST_LIVE_ADDER (GST_PAD_PARENT (pad)); + + /* get the downstream possible caps */ + peercaps = gst_pad_peer_get_caps (adder->srcpad); + /* get the allowed caps on this sinkpad, we use the fixed caps function so + * that it does not call recursively in this function. */ + sinkcaps = gst_pad_get_fixed_caps_func (pad); + if (peercaps) { + /* if the peer has caps, intersect */ + GST_DEBUG_OBJECT (adder, "intersecting peer and template caps"); + result = gst_caps_intersect (peercaps, sinkcaps); + gst_caps_unref (peercaps); + gst_caps_unref (sinkcaps); + } else { + /* the peer has no caps (or there is no peer), just use the allowed caps + * of this sinkpad. */ + GST_DEBUG_OBJECT (adder, "no peer caps, using sinkcaps"); + result = sinkcaps; + } + + return result; +} + +/* the first caps we receive on any of the sinkpads will define the caps for all + * the other sinkpads because we can only mix streams with the same caps. + * */ +static gboolean +gst_live_adder_setcaps (GstPad * pad, GstCaps * caps) +{ + GstLiveAdder *adder; + GList *pads; + GstStructure *structure; + const char *media_type; + + adder = GST_LIVE_ADDER (GST_PAD_PARENT (pad)); + + GST_LOG_OBJECT (adder, "setting caps on pad %p,%s to %" GST_PTR_FORMAT, pad, + GST_PAD_NAME (pad), caps); + + /* FIXME, see if the other pads can accept the format. Also lock the + * format on the other pads to this new format. */ + GST_OBJECT_LOCK (adder); + pads = GST_ELEMENT (adder)->pads; + while (pads) { + GstPad *otherpad = GST_PAD (pads->data); + + if (otherpad != pad) + gst_caps_replace (&GST_PAD_CAPS (otherpad), caps); + + pads = g_list_next (pads); + } + + /* parse caps now */ + structure = gst_caps_get_structure (caps, 0); + media_type = gst_structure_get_name (structure); + if (strcmp (media_type, "audio/x-raw-int") == 0) { + GST_DEBUG_OBJECT (adder, "parse_caps sets adder to format int"); + adder->format = GST_LIVE_ADDER_FORMAT_INT; + gst_structure_get_int (structure, "width", &adder->width); + gst_structure_get_int (structure, "depth", &adder->depth); + gst_structure_get_int (structure, "endianness", &adder->endianness); + gst_structure_get_boolean (structure, "signed", &adder->is_signed); + + if (adder->endianness != G_BYTE_ORDER) + goto not_supported; + + switch (adder->width) { + case 8: + adder->func = (adder->is_signed ? + (GstLiveAdderFunction) add_int8 : (GstLiveAdderFunction) add_uint8); + break; + case 16: + adder->func = (adder->is_signed ? + (GstLiveAdderFunction) add_int16 : (GstLiveAdderFunction) + add_uint16); + break; + case 32: + adder->func = (adder->is_signed ? + (GstLiveAdderFunction) add_int32 : (GstLiveAdderFunction) + add_uint32); + break; + default: + goto not_supported; + } + } else if (strcmp (media_type, "audio/x-raw-float") == 0) { + GST_DEBUG_OBJECT (adder, "parse_caps sets adder to format float"); + adder->format = GST_LIVE_ADDER_FORMAT_FLOAT; + gst_structure_get_int (structure, "width", &adder->width); + + switch (adder->width) { + case 32: + adder->func = (GstLiveAdderFunction) add_float32; + break; + case 64: + adder->func = (GstLiveAdderFunction) add_float64; + break; + default: + goto not_supported; + } + } else { + goto not_supported; + } + + gst_structure_get_int (structure, "channels", &adder->channels); + gst_structure_get_int (structure, "rate", &adder->rate); + /* precalc bps */ + adder->bps = (adder->width / 8) * adder->channels; + + GST_OBJECT_UNLOCK (adder); + return TRUE; + + /* ERRORS */ +not_supported: + { + GST_OBJECT_UNLOCK (adder); + GST_DEBUG_OBJECT (adder, "unsupported format set as caps"); + return FALSE; + } +} + +static void +gst_live_adder_flush_start (GstLiveAdder * adder) +{ + GST_DEBUG_OBJECT (adder, "Disabling pop on queue"); + + GST_OBJECT_LOCK (adder); + /* mark ourselves as flushing */ + adder->srcresult = GST_FLOW_WRONG_STATE; + + /* Empty the queue */ + g_queue_foreach (adder->buffers, (GFunc) gst_mini_object_unref, NULL); + while (g_queue_pop_head (adder->buffers)); + + /* unlock clock, we just unschedule, the entry will be released by the + * locking streaming thread. */ + if (adder->clock_id) + gst_clock_id_unschedule (adder->clock_id); + + g_cond_broadcast (adder->not_empty_cond); + GST_OBJECT_UNLOCK (adder); +} + +static gboolean +gst_live_adder_src_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = TRUE; + GstLiveAdder *adder = NULL; + + adder = GST_LIVE_ADDER (gst_pad_get_parent (pad)); + + if (active) { + /* Mark as non flushing */ + GST_OBJECT_LOCK (adder); + adder->srcresult = GST_FLOW_OK; + GST_OBJECT_UNLOCK (adder); + + /* start pushing out buffers */ + GST_DEBUG_OBJECT (adder, "Starting task on srcpad"); + gst_pad_start_task (adder->srcpad, + (GstTaskFunction) gst_live_adder_loop, adder); + } else { + /* make sure all data processing stops ASAP */ + gst_live_adder_flush_start (adder); + + /* NOTE this will hardlock if the state change is called from the src pad + * task thread because we will _join() the thread. */ + GST_DEBUG_OBJECT (adder, "Stopping task on srcpad"); + result = gst_pad_stop_task (pad); + } + + gst_object_unref (adder); + + return result; +} + +static gboolean +gst_live_adder_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret = TRUE; + GstLiveAdder *adder = NULL; + GstLiveAdderPadPrivate *padprivate = NULL; + + adder = GST_LIVE_ADDER (gst_pad_get_parent (pad)); + + padprivate = gst_pad_get_element_private (pad); + + if (!padprivate) + return FALSE; + + GST_LOG_OBJECT (adder, "received %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_NEWSEGMENT: + { + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + gboolean update; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + gst_event_unref (event); + + /* we need time for now */ + if (format != GST_FORMAT_TIME) + goto newseg_wrong_format; + + GST_DEBUG_OBJECT (adder, + "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT + ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT, + update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop), + GST_TIME_ARGS (time)); + + /* now configure the values, we need these to time the release of the + * buffers on the srcpad. */ + GST_OBJECT_LOCK (adder); + gst_segment_set_newsegment_full (&padprivate->segment, update, + rate, arate, format, start, stop, time); + GST_OBJECT_UNLOCK (adder); + break; + } + case GST_EVENT_FLUSH_START: + gst_live_adder_flush_start (adder); + ret = gst_pad_push_event (adder->srcpad, event); + break; + case GST_EVENT_FLUSH_STOP: + GST_OBJECT_LOCK (adder); + adder->segment_pending = TRUE; + adder->next_timestamp = GST_CLOCK_TIME_NONE; + reset_pad_private (pad); + adder->segment_pending = TRUE; + GST_OBJECT_UNLOCK (adder); + ret = gst_pad_push_event (adder->srcpad, event); + ret = gst_live_adder_src_activate_push (adder->srcpad, TRUE); + break; + case GST_EVENT_EOS: + { + GST_OBJECT_LOCK (adder); + + ret = adder->srcresult == GST_FLOW_OK; + if (ret && !padprivate->eos) { + GST_DEBUG_OBJECT (adder, "queuing EOS"); + padprivate->eos = TRUE; + g_cond_broadcast (adder->not_empty_cond); + } else if (padprivate->eos) { + GST_DEBUG_OBJECT (adder, "dropping EOS, we are already EOS"); + } else { + GST_DEBUG_OBJECT (adder, "dropping EOS, reason %s", + gst_flow_get_name (adder->srcresult)); + } + + GST_OBJECT_UNLOCK (adder); + + gst_event_unref (event); + break; + } + default: + ret = gst_pad_push_event (adder->srcpad, event); + break; + } + +done: + gst_object_unref (adder); + + return ret; + + /* ERRORS */ +newseg_wrong_format: + { + GST_DEBUG_OBJECT (adder, "received non TIME newsegment"); + ret = FALSE; + goto done; + } +} + +static gboolean +gst_live_adder_query_pos_dur (GstLiveAdder * adder, GstFormat informat, + gboolean position, gint64 * outvalue) +{ + gint64 max = G_MININT64; + gboolean res = TRUE; + GstIterator *it; + gboolean done = FALSE; + + + it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (adder)); + while (!done) { + GstIteratorResult ires; + gpointer item; + GstFormat format = informat; + + ires = gst_iterator_next (it, &item); + switch (ires) { + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_OK: + { + GstPad *pad = GST_PAD_CAST (item); + gint64 value; + gboolean curres; + + /* ask sink peer for duration */ + if (position) + curres = gst_pad_query_peer_position (pad, &format, &value); + else + curres = gst_pad_query_peer_duration (pad, &format, &value); + + /* take max from all valid return values */ + /* Only if the format is the one we requested, otherwise ignore it ? + */ + + if (curres && format == informat) { + res &= curres; + + /* valid unknown length, stop searching */ + if (value == -1) { + max = value; + done = TRUE; + } else if (value > max) { + max = value; + } + } + break; + } + case GST_ITERATOR_RESYNC: + max = -1; + res = TRUE; + break; + default: + res = FALSE; + done = TRUE; + break; + } + } + gst_iterator_free (it); + + if (res) + *outvalue = max; + + return res; +} + +/* FIXME: + * + * When we add a new stream (or remove a stream) the duration might + * also become invalid again and we need to post a new DURATION + * message to notify this fact to the parent. + * For now we take the max of all the upstream elements so the simple + * cases work at least somewhat. + */ +static gboolean +gst_live_adder_query_duration (GstLiveAdder * adder, GstQuery * query) +{ + GstFormat format; + gint64 max; + gboolean res; + + /* parse format */ + gst_query_parse_duration (query, &format, NULL); + + res = gst_live_adder_query_pos_dur (adder, format, FALSE, &max); + + if (res) { + /* and store the max */ + gst_query_set_duration (query, format, max); + } + + return res; +} + +static gboolean +gst_live_adder_query_position (GstLiveAdder * adder, GstQuery * query) +{ + GstFormat format; + gint64 max; + gboolean res; + + /* parse format */ + gst_query_parse_position (query, &format, NULL); + + res = gst_live_adder_query_pos_dur (adder, format, TRUE, &max); + + if (res) { + /* and store the max */ + gst_query_set_position (query, format, max); + } + + return res; +} + + + +static gboolean +gst_live_adder_query (GstPad * pad, GstQuery * query) +{ + GstLiveAdder *adder; + gboolean res = FALSE; + + adder = GST_LIVE_ADDER (gst_pad_get_parent (pad)); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + /* We need to send the query upstream and add the returned latency to our + * own */ + GstClockTime min_latency = 0, max_latency = G_MAXUINT64; + gpointer item; + GstIterator *iter = NULL; + gboolean done = FALSE; + + iter = gst_element_iterate_sink_pads (GST_ELEMENT (adder)); + + while (!done) { + switch (gst_iterator_next (iter, &item)) { + case GST_ITERATOR_OK: + { + GstPad *sinkpad = item; + GstClockTime pad_min_latency, pad_max_latency; + gboolean pad_us_live; + + if (gst_pad_peer_query (sinkpad, query)) { + gst_query_parse_latency (query, &pad_us_live, &pad_min_latency, + &pad_max_latency); + + res = TRUE; + + GST_DEBUG_OBJECT (adder, "Peer latency for pad %s: min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, + GST_PAD_NAME (sinkpad), + GST_TIME_ARGS (pad_min_latency), + GST_TIME_ARGS (pad_max_latency)); + + min_latency = MAX (pad_min_latency, min_latency); + max_latency = MIN (pad_max_latency, max_latency); + } + gst_object_unref (item); + } + break; + case GST_ITERATOR_RESYNC: + min_latency = 0; + max_latency = G_MAXUINT64; + + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_ERROR_OBJECT (adder, "Error looping sink pads"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + if (res) { + GstClockTime my_latency = adder->latency_ms * GST_MSECOND; + GST_OBJECT_LOCK (adder); + adder->peer_latency = min_latency; + min_latency += my_latency; + GST_OBJECT_UNLOCK (adder); + + /* Make sure we don't risk an overflow */ + if (max_latency < G_MAXUINT64 - my_latency) + max_latency += my_latency; + else + max_latency = G_MAXUINT64; + gst_query_set_latency (query, TRUE, min_latency, max_latency); + GST_DEBUG_OBJECT (adder, "Calculated total latency : min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + } + break; + } + case GST_QUERY_DURATION: + res = gst_live_adder_query_duration (adder, query); + break; + case GST_QUERY_POSITION: + res = gst_live_adder_query_position (adder, query); + break; + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (adder); + + return res; +} + +static gboolean +forward_event_func (GstPad * pad, GValue * ret, GstEvent * event) +{ + gst_event_ref (event); + GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event)); + if (!gst_pad_push_event (pad, event)) { + g_value_set_boolean (ret, FALSE); + GST_WARNING_OBJECT (pad, "Sending event %p (%s) failed.", + event, GST_EVENT_TYPE_NAME (event)); + } else { + GST_LOG_OBJECT (pad, "Sent event %p (%s).", + event, GST_EVENT_TYPE_NAME (event)); + } + + /* unref the pad because of a FIXME in gst_iterator_unfold + * it does a gst_iterator_next which refs the pad, but it never unrefs it + */ + gst_object_unref (pad); + return TRUE; +} + +/* forwards the event to all sinkpads, takes ownership of the + * event + * + * Returns: TRUE if the event could be forwarded on all + * sinkpads. + */ +static gboolean +forward_event (GstLiveAdder * adder, GstEvent * event) +{ + gboolean ret; + GstIterator *it; + GValue vret = { 0 }; + + GST_LOG_OBJECT (adder, "Forwarding event %p (%s)", event, + GST_EVENT_TYPE_NAME (event)); + + ret = TRUE; + + g_value_init (&vret, G_TYPE_BOOLEAN); + g_value_set_boolean (&vret, TRUE); + it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (adder)); + gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret, + event); + gst_iterator_free (it); + + ret = g_value_get_boolean (&vret); + + return ret; +} + + +static gboolean +gst_live_adder_src_event (GstPad * pad, GstEvent * event) +{ + GstLiveAdder *adder; + gboolean result; + + adder = GST_LIVE_ADDER (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_QOS: + /* TODO : QoS might be tricky */ + result = FALSE; + break; + case GST_EVENT_NAVIGATION: + /* TODO : navigation is rather pointless. */ + result = FALSE; + break; + default: + /* just forward the rest for now */ + result = forward_event (adder, event); + break; + } + + gst_event_unref (event); + gst_object_unref (adder); + + return result; +} + +static guint +gst_live_adder_length_from_duration (GstLiveAdder * adder, + GstClockTime duration) +{ + guint64 ret = (duration * adder->rate / GST_SECOND) * adder->bps; + + return (guint) ret; +} + +static GstFlowReturn +gst_live_live_adder_chain (GstPad * pad, GstBuffer * buffer) +{ + GstLiveAdder *adder = GST_LIVE_ADDER (gst_pad_get_parent_element (pad)); + GstLiveAdderPadPrivate *padprivate = NULL; + GstFlowReturn ret = GST_FLOW_OK; + GList *item = NULL; + GstClockTime skip = 0; + gint64 drift = 0; /* Positive if new buffer after old buffer */ + + GST_OBJECT_LOCK (adder); + + ret = adder->srcresult; + + GST_DEBUG ("Incoming buffer time:%" GST_TIME_FORMAT " duration:%" + GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (adder, "Passing non-ok result from src: %s", + gst_flow_get_name (ret)); + gst_buffer_unref (buffer); + goto out; + } + + padprivate = gst_pad_get_element_private (pad); + + if (!padprivate) { + ret = GST_FLOW_NOT_LINKED; + gst_buffer_unref (buffer); + goto out; + } + + if (padprivate->eos) { + GST_DEBUG_OBJECT (adder, "Received buffer after EOS"); + ret = GST_FLOW_UNEXPECTED; + gst_buffer_unref (buffer); + goto out; + } + + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + goto invalid_timestamp; + + if (padprivate->segment.format == GST_FORMAT_UNDEFINED) { + GST_WARNING_OBJECT (adder, "No new-segment received," + " initializing segment with time 0..-1"); + gst_segment_init (&padprivate->segment, GST_FORMAT_TIME); + gst_segment_set_newsegment (&padprivate->segment, + FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0); + } + + if (padprivate->segment.format != GST_FORMAT_TIME) + goto invalid_segment; + + buffer = gst_buffer_make_metadata_writable (buffer); + + drift = GST_BUFFER_TIMESTAMP (buffer) - padprivate->expected_timestamp; + + /* Just see if we receive invalid timestamp/durations */ + if (GST_CLOCK_TIME_IS_VALID (padprivate->expected_timestamp) && + !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT) && + (drift != 0)) { + GST_LOG_OBJECT (adder, + "Timestamp discontinuity without the DISCONT flag set" + " (expected %" GST_TIME_FORMAT ", got %" GST_TIME_FORMAT + " drift:%" G_GINT64_FORMAT "ms)", + GST_TIME_ARGS (padprivate->expected_timestamp), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), drift / GST_MSECOND); + + /* We accept drifts of 10ms */ + if (ABS (drift) < (10 * GST_MSECOND)) { + GST_DEBUG ("Correcting minor drift"); + GST_BUFFER_TIMESTAMP (buffer) = padprivate->expected_timestamp; + } + } + + + /* If there is no duration, lets set one */ + if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { + GST_BUFFER_DURATION (buffer) = + gst_audio_duration_from_pad_buffer (pad, buffer); + padprivate->expected_timestamp = GST_CLOCK_TIME_NONE; + } else { + padprivate->expected_timestamp = GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer); + } + + + /* + * Lets clip the buffer to the segment (so we don't have to worry about + * cliping afterwards). + * This should also guarantee us that we'll have valid timestamps and + * durations afterwards + */ + + buffer = gst_audio_buffer_clip (buffer, &padprivate->segment, adder->rate, + adder->bps); + + /* buffer can be NULL if it's completely outside of the segment */ + if (!buffer) { + GST_DEBUG ("Buffer completely outside of configured segment, dropping it"); + goto out; + } + + /* + * Make sure all incoming buffers share the same timestamping + */ + GST_BUFFER_TIMESTAMP (buffer) = + gst_segment_to_running_time (&padprivate->segment, + padprivate->segment.format, GST_BUFFER_TIMESTAMP (buffer)); + + + if (GST_CLOCK_TIME_IS_VALID (adder->next_timestamp) && + GST_BUFFER_TIMESTAMP (buffer) < adder->next_timestamp) { + if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) < + adder->next_timestamp) { + GST_DEBUG_OBJECT (adder, "Buffer is late, dropping (ts: %" GST_TIME_FORMAT + " duration: %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); + gst_buffer_unref (buffer); + goto out; + } else { + skip = adder->next_timestamp - GST_BUFFER_TIMESTAMP (buffer); + GST_DEBUG_OBJECT (adder, "Buffer is partially late, skipping %" + GST_TIME_FORMAT, GST_TIME_ARGS (skip)); + } + } + + /* If our new buffer's head is higher than the queue's head, lets wake up, + * we may not have to wait for as long + */ + if (adder->clock_id && + g_queue_peek_head (adder->buffers) != NULL && + GST_BUFFER_TIMESTAMP (buffer) + skip < + GST_BUFFER_TIMESTAMP (g_queue_peek_head (adder->buffers))) + gst_clock_id_unschedule (adder->clock_id); + + for (item = g_queue_peek_head_link (adder->buffers); + item; item = g_list_next (item)) { + GstBuffer *oldbuffer = item->data; + GstClockTime old_skip = 0; + GstClockTime mix_duration = 0; + GstClockTime mix_start = 0; + GstClockTime mix_end = 0; + + /* We haven't reached our place yet */ + if (GST_BUFFER_TIMESTAMP (buffer) + skip >= + GST_BUFFER_TIMESTAMP (oldbuffer) + GST_BUFFER_DURATION (oldbuffer)) + continue; + + /* We're past our place, lets insert ouselves here */ + if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) <= + GST_BUFFER_TIMESTAMP (oldbuffer)) + break; + + /* if we reach this spot, we have overlap, so we must mix */ + + /* First make a subbuffer with the non-overlapping part */ + if (GST_BUFFER_TIMESTAMP (buffer) + skip < GST_BUFFER_TIMESTAMP (oldbuffer)) { + GstBuffer *subbuffer = NULL; + GstClockTime subbuffer_duration = GST_BUFFER_TIMESTAMP (oldbuffer) - + (GST_BUFFER_TIMESTAMP (buffer) + skip); + + subbuffer = gst_buffer_create_sub (buffer, + gst_live_adder_length_from_duration (adder, skip), + gst_live_adder_length_from_duration (adder, subbuffer_duration)); + + GST_BUFFER_TIMESTAMP (subbuffer) = GST_BUFFER_TIMESTAMP (buffer) + skip; + GST_BUFFER_DURATION (subbuffer) = subbuffer_duration; + + skip += subbuffer_duration; + + g_queue_insert_before (adder->buffers, item, subbuffer); + } + + /* Now we are on the overlapping part */ + oldbuffer = gst_buffer_make_writable (oldbuffer); + item->data = oldbuffer; + + old_skip = GST_BUFFER_TIMESTAMP (buffer) + skip - + GST_BUFFER_TIMESTAMP (oldbuffer); + + mix_start = GST_BUFFER_TIMESTAMP (oldbuffer) + old_skip; + + if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) < + GST_BUFFER_TIMESTAMP (oldbuffer) + GST_BUFFER_DURATION (oldbuffer)) + mix_end = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); + else + mix_end = GST_BUFFER_TIMESTAMP (oldbuffer) + + GST_BUFFER_DURATION (oldbuffer); + + mix_duration = mix_end - mix_start; + + adder->func (GST_BUFFER_DATA (oldbuffer) + + gst_live_adder_length_from_duration (adder, old_skip), + GST_BUFFER_DATA (buffer) + + gst_live_adder_length_from_duration (adder, skip), + gst_live_adder_length_from_duration (adder, mix_duration)); + + skip += mix_duration; + } + + g_cond_broadcast (adder->not_empty_cond); + + if (skip == GST_BUFFER_DURATION (buffer)) { + gst_buffer_unref (buffer); + } else { + if (skip) { + GstClockTime subbuffer_duration = GST_BUFFER_DURATION (buffer) - skip; + GstClockTime subbuffer_ts = GST_BUFFER_TIMESTAMP (buffer) + skip; + GstBuffer *new_buffer = gst_buffer_create_sub (buffer, + gst_live_adder_length_from_duration (adder, skip), + gst_live_adder_length_from_duration (adder, subbuffer_duration)); + gst_buffer_unref (buffer); + buffer = new_buffer; + GST_BUFFER_TIMESTAMP (buffer) = subbuffer_ts; + GST_BUFFER_DURATION (buffer) = subbuffer_duration; + } + + if (item) + g_queue_insert_before (adder->buffers, item, buffer); + else + g_queue_push_tail (adder->buffers, buffer); + } + +out: + + GST_OBJECT_UNLOCK (adder); + gst_object_unref (adder); + + return ret; + +invalid_timestamp: + + GST_OBJECT_UNLOCK (adder); + gst_buffer_unref (buffer); + GST_ELEMENT_ERROR (adder, STREAM, FAILED, + ("Buffer without a valid timestamp received"), + ("Invalid timestamp received on buffer")); + + return GST_FLOW_ERROR; + +invalid_segment: + { + const gchar *format = gst_format_get_name (padprivate->segment.format); + GST_OBJECT_UNLOCK (adder); + gst_buffer_unref (buffer); + GST_ELEMENT_ERROR (adder, STREAM, FAILED, + ("This element only supports TIME segments, received other type"), + ("Received a segment of type %s, only support time segment", format)); + + return GST_FLOW_ERROR; + } + +} + +/* + * This only works because the GstObject lock is taken + * + * It checks if all sink pads are EOS + */ +static gboolean +check_eos_locked (GstLiveAdder * adder) +{ + GList *item; + + /* We can't be EOS if we have no sinkpads */ + if (adder->sinkpads == NULL) + return FALSE; + + for (item = adder->sinkpads; item; item = g_list_next (item)) { + GstPad *pad = item->data; + GstLiveAdderPadPrivate *padprivate = gst_pad_get_element_private (pad); + + if (padprivate && padprivate->eos != TRUE) + return FALSE; + } + + return TRUE; +} + +static void +gst_live_adder_loop (gpointer data) +{ + GstLiveAdder *adder = GST_LIVE_ADDER (data); + GstClockTime buffer_timestamp = 0; + GstClockTime sync_time = 0; + GstClock *clock = NULL; + GstClockID id = NULL; + GstClockReturn ret; + GstBuffer *buffer = NULL; + GstFlowReturn result; + GstEvent *newseg_event = NULL; + + GST_OBJECT_LOCK (adder); + +again: + + for (;;) { + if (adder->srcresult != GST_FLOW_OK) + goto flushing; + if (!g_queue_is_empty (adder->buffers)) + break; + if (check_eos_locked (adder)) + goto eos; + g_cond_wait (adder->not_empty_cond, GST_OBJECT_GET_LOCK (adder)); + } + + buffer_timestamp = GST_BUFFER_TIMESTAMP (g_queue_peek_head (adder->buffers)); + + clock = GST_ELEMENT_CLOCK (adder); + + /* If we have no clock, then we can't do anything.. error */ + if (!clock) { + if (adder->playing) + goto no_clock; + else + goto push_buffer; + } + + GST_DEBUG_OBJECT (adder, "sync to timestamp %" GST_TIME_FORMAT, + GST_TIME_ARGS (buffer_timestamp)); + + sync_time = buffer_timestamp + GST_ELEMENT_CAST (adder)->base_time; + /* add latency, this includes our own latency and the peer latency. */ + sync_time += adder->latency_ms * GST_MSECOND; + sync_time += adder->peer_latency; + + /* create an entry for the clock */ + id = adder->clock_id = gst_clock_new_single_shot_id (clock, sync_time); + GST_OBJECT_UNLOCK (adder); + + ret = gst_clock_id_wait (id, NULL); + + GST_OBJECT_LOCK (adder); + + /* and free the entry */ + gst_clock_id_unref (id); + adder->clock_id = NULL; + + /* at this point, the clock could have been unlocked by a timeout, a new + * head element was added to the queue or because we are shutting down. Check + * for shutdown first. */ + + if (adder->srcresult != GST_FLOW_OK) + goto flushing; + + if (ret == GST_CLOCK_UNSCHEDULED) { + GST_DEBUG_OBJECT (adder, + "Wait got unscheduled, will retry to push with new buffer"); + goto again; + } + + if (ret != GST_CLOCK_OK && ret != GST_CLOCK_EARLY) + goto clock_error; + +push_buffer: + + buffer = g_queue_pop_head (adder->buffers); + + if (!buffer) + goto again; + + /* + * We make sure the timestamps are exactly contiguous + * If its only small skew (due to rounding errors), we correct it + * silently. Otherwise we put the discont flag + */ + if (GST_CLOCK_TIME_IS_VALID (adder->next_timestamp) && + GST_BUFFER_TIMESTAMP (buffer) != adder->next_timestamp) { + GstClockTimeDiff diff = GST_CLOCK_DIFF (GST_BUFFER_TIMESTAMP (buffer), + adder->next_timestamp); + if (diff < 0) + diff = -diff; + + if (diff < GST_SECOND / adder->rate) { + GST_BUFFER_TIMESTAMP (buffer) = adder->next_timestamp; + GST_DEBUG_OBJECT (adder, "Correcting slight skew"); + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + GST_DEBUG_OBJECT (adder, "Expected buffer at %" GST_TIME_FORMAT + ", but is at %" GST_TIME_FORMAT ", setting discont", + GST_TIME_ARGS (adder->next_timestamp), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + } + } else { + GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT); + } + + GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE; + GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE; + + if (GST_BUFFER_DURATION_IS_VALID (buffer)) + adder->next_timestamp = GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer); + else + adder->next_timestamp = GST_CLOCK_TIME_NONE; + + if (adder->segment_pending) { + /* + * We set the start at 0, because we re-timestamps to the running time + */ + newseg_event = gst_event_new_new_segment_full (FALSE, 1.0, 1.0, + GST_FORMAT_TIME, 0, -1, 0); + + adder->segment_pending = FALSE; + } + + GST_OBJECT_UNLOCK (adder); + + if (newseg_event) + gst_pad_push_event (adder->srcpad, newseg_event); + + GST_LOG_OBJECT (adder, "About to push buffer time:%" GST_TIME_FORMAT + " duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_DURATION (buffer))); + + result = gst_pad_push (adder->srcpad, buffer); + if (result != GST_FLOW_OK) + goto pause; + + return; + +flushing: + { + GST_DEBUG_OBJECT (adder, "we are flushing"); + gst_pad_pause_task (adder->srcpad); + GST_OBJECT_UNLOCK (adder); + return; + } + +clock_error: + { + gst_pad_pause_task (adder->srcpad); + GST_OBJECT_UNLOCK (adder); + GST_ELEMENT_ERROR (adder, STREAM, MUX, ("Error with the clock"), + ("Error with the clock: %d", ret)); + GST_ERROR_OBJECT (adder, "Error with the clock: %d", ret); + return; + } + +no_clock: + { + gst_pad_pause_task (adder->srcpad); + GST_OBJECT_UNLOCK (adder); + GST_ELEMENT_ERROR (adder, STREAM, MUX, ("No available clock"), + ("No available clock")); + GST_ERROR_OBJECT (adder, "No available clock"); + return; + } + +pause: + { + GST_DEBUG_OBJECT (adder, "pausing task, reason %s", + gst_flow_get_name (result)); + + GST_OBJECT_LOCK (adder); + + /* store result */ + adder->srcresult = result; + /* we don't post errors or anything because upstream will do that for us + * when we pass the return value upstream. */ + gst_pad_pause_task (adder->srcpad); + GST_OBJECT_UNLOCK (adder); + return; + } + +eos: + { + /* store result, we are flushing now */ + GST_DEBUG_OBJECT (adder, "We are EOS, pushing EOS downstream"); + adder->srcresult = GST_FLOW_UNEXPECTED; + gst_pad_pause_task (adder->srcpad); + GST_OBJECT_UNLOCK (adder); + gst_pad_push_event (adder->srcpad, gst_event_new_eos ()); + return; + } +} + +static GstPad * +gst_live_adder_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * unused) +{ + gchar *name; + GstLiveAdder *adder; + GstPad *newpad; + gint padcount; + GstLiveAdderPadPrivate *padprivate = NULL; + + if (templ->direction != GST_PAD_SINK) + goto not_sink; + + adder = GST_LIVE_ADDER (element); + + /* increment pad counter */ +#if GLIB_CHECK_VERSION(2,29,5) + padcount = g_atomic_int_add (&adder->padcount, 1); +#else + padcount = g_atomic_int_exchange_and_add (&adder->padcount, 1); +#endif + + name = g_strdup_printf ("sink%d", padcount); + newpad = gst_pad_new_from_template (templ, name); + GST_DEBUG_OBJECT (adder, "request new pad %s", name); + g_free (name); + + gst_pad_set_getcaps_function (newpad, + GST_DEBUG_FUNCPTR (gst_live_adder_sink_getcaps)); + gst_pad_set_setcaps_function (newpad, + GST_DEBUG_FUNCPTR (gst_live_adder_setcaps)); + gst_pad_set_event_function (newpad, + GST_DEBUG_FUNCPTR (gst_live_adder_sink_event)); + + padprivate = g_new0 (GstLiveAdderPadPrivate, 1); + + gst_segment_init (&padprivate->segment, GST_FORMAT_UNDEFINED); + padprivate->eos = FALSE; + padprivate->expected_timestamp = GST_CLOCK_TIME_NONE; + + gst_pad_set_element_private (newpad, padprivate); + + gst_pad_set_chain_function (newpad, gst_live_live_adder_chain); + + + if (!gst_pad_set_active (newpad, TRUE)) + goto could_not_activate; + + /* takes ownership of the pad */ + if (!gst_element_add_pad (GST_ELEMENT (adder), newpad)) + goto could_not_add; + + GST_OBJECT_LOCK (adder); + adder->sinkpads = g_list_prepend (adder->sinkpads, newpad); + GST_OBJECT_UNLOCK (adder); + + return newpad; + + /* errors */ +not_sink: + { + g_warning ("gstadder: request new pad that is not a SINK pad\n"); + return NULL; + } +could_not_add: + { + GST_DEBUG_OBJECT (adder, "could not add pad"); + g_free (padprivate); + gst_object_unref (newpad); + return NULL; + } +could_not_activate: + { + GST_DEBUG_OBJECT (adder, "could not activate new pad"); + g_free (padprivate); + gst_object_unref (newpad); + return NULL; + } +} + +static void +gst_live_adder_release_pad (GstElement * element, GstPad * pad) +{ + GstLiveAdder *adder; + GstLiveAdderPadPrivate *padprivate; + + adder = GST_LIVE_ADDER (element); + + GST_DEBUG_OBJECT (adder, "release pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + GST_OBJECT_LOCK (element); + padprivate = gst_pad_get_element_private (pad); + gst_pad_set_element_private (pad, NULL); + adder->sinkpads = g_list_remove_all (adder->sinkpads, pad); + GST_OBJECT_UNLOCK (element); + + g_free (padprivate); + + gst_element_remove_pad (element, pad); +} + +static void +reset_pad_private (GstPad * pad) +{ + GstLiveAdderPadPrivate *padprivate; + + padprivate = gst_pad_get_element_private (pad); + + if (!padprivate) + return; + + gst_segment_init (&padprivate->segment, GST_FORMAT_UNDEFINED); + + padprivate->expected_timestamp = GST_CLOCK_TIME_NONE; + padprivate->eos = FALSE; +} + +static GstStateChangeReturn +gst_live_adder_change_state (GstElement * element, GstStateChange transition) +{ + GstLiveAdder *adder; + GstStateChangeReturn ret; + + adder = GST_LIVE_ADDER (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_OBJECT_LOCK (adder); + adder->segment_pending = TRUE; + adder->peer_latency = 0; + adder->next_timestamp = GST_CLOCK_TIME_NONE; + g_list_foreach (adder->sinkpads, (GFunc) reset_pad_private, NULL); + GST_OBJECT_UNLOCK (adder); + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + GST_OBJECT_LOCK (adder); + adder->playing = FALSE; + GST_OBJECT_UNLOCK (adder); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + GST_OBJECT_LOCK (adder); + adder->playing = TRUE; + GST_OBJECT_UNLOCK (adder); + break; + default: + break; + } + + return ret; +} + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "liveadder", GST_RANK_NONE, + GST_TYPE_LIVE_ADDER)) { + return FALSE; + } + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "liveadder", + "Adds multiple live discontinuous streams", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/farsight/liveadder/liveadder.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/liveadder/liveadder.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,108 @@ +/* + * Farsight Voice+Video library + * + * Copyright 2008 Collabora Ltd + * Copyright 2008 Nokia Corporation + * @author: Olivier Crete + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + + + +#ifndef __GST_LIVE_ADDER_H__ +#define __GST_LIVE_ADDER_H__ + +#include + +G_BEGIN_DECLS +#define GST_TYPE_LIVE_ADDER (gst_live_adder_get_type()) +#define GST_LIVE_ADDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LIVE_ADDER,GstLiveAdder)) +#define GST_IS_LIVE_ADDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LIVE_ADDER)) +#define GST_LIVE_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_LIVE_ADDER,GstLiveAdderClass)) +#define GST_IS_LIVE_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_LIVE_ADDER)) +#define GST_LIVE_ADDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_LIVE_ADDER,GstLiveAdderClass)) +typedef struct _GstLiveAdder GstLiveAdder; +typedef struct _GstLiveAdderClass GstLiveAdderClass; + +typedef enum +{ + GST_LIVE_ADDER_FORMAT_UNSET, + GST_LIVE_ADDER_FORMAT_INT, + GST_LIVE_ADDER_FORMAT_FLOAT +} GstLiveAdderFormat; + +typedef void (*GstLiveAdderFunction) (gpointer out, gpointer in, guint size); + +/** + * GstLiveAdder: + * + * The adder object structure. + */ +struct _GstLiveAdder +{ + /*< private >*/ + GstElement element; + + GstPad *srcpad; + /* pad counter, used for creating unique request pads */ + gint padcount; + GList *sinkpads; + + GstFlowReturn srcresult; + GstClockID clock_id; + + /* the queue is ordered head to tail */ + GQueue *buffers; + GCond *not_empty_cond; + + GstClockTime next_timestamp; + + /* the next are valid for both int and float */ + GstLiveAdderFormat format; + gint rate; + gint channels; + gint width; + gint endianness; + + /* the next are valid only for format == GST_LIVE_ADDER_FORMAT_INT */ + gint depth; + gboolean is_signed; + + /* number of bytes per sample, actually width/8 * channels */ + gint bps; + + /* function to add samples */ + GstLiveAdderFunction func; + + GstClockTime latency_ms; + GstClockTime peer_latency; + + gboolean segment_pending; + + gboolean playing; +}; + +struct _GstLiveAdderClass +{ + GstElementClass parent_class; +}; + +GType gst_live_adder_get_type (void); + +G_END_DECLS +#endif /* __GST_LIVE_ADDER_H__ */ Index: gst-plugins-good0.10/farsight/liveadder/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/liveadder/Makefile.am 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,25 @@ +plugin_LTLIBRARIES = libgstliveadder.la + +libgstliveadder_la_SOURCES = liveadder.c +libgstliveadder_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstliveadder_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstaudio-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) $(GST_LIBS) +libgstliveadder_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstliveadder_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = liveadder.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstliveadder -:SHARED libgstliveadder \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstliveadder_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstliveadder_la_CFLAGS) \ + -:LDFLAGS $(libgstliveadder_la_LDFLAGS) \ + $(libgstliveadder_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ Index: gst-plugins-good0.10/farsight/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/Makefile.am 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,2 @@ +SUBDIRS = autoconvert dtmf liveadder rtpmux + Index: gst-plugins-good0.10/farsight/rtpmux/gstrtpdtmfmux.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/gstrtpdtmfmux.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,233 @@ +/* RTP DTMF muxer element for GStreamer + * + * gstrtpdtmfmux.c: + * + * Copyright (C) <2007-2010> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2007-2010> Collabora Ltd + * Contact: Olivier Crete + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-rtpdtmfmux + * @see_also: rtpdtmfsrc, dtmfsrc, rtpmux + * + * The RTP "DTMF" Muxer muxes multiple RTP streams into a valid RTP + * stream. It does exactly what it's parent (#rtpmux) does, except + * that it prevent buffers coming over a regular sink_%%d pad from going through + * for the duration of buffers that came in a priority_sink_%%d pad. + * + * This is especially useful if a discontinuous source like dtmfsrc or + * rtpdtmfsrc are connected to the priority sink pads. This way, the generated + * DTMF signal can replace the recorded audio while the tone is being sent. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstrtpdtmfmux.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_mux_debug); +#define GST_CAT_DEFAULT gst_rtp_dtmf_mux_debug + +static GstStaticPadTemplate priority_sink_factory = +GST_STATIC_PAD_TEMPLATE ("priority_sink_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstPad *gst_rtp_dtmf_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static GstStateChangeReturn gst_rtp_dtmf_mux_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gst_rtp_dtmf_mux_accept_buffer_locked (GstRTPMux * rtp_mux, + GstRTPMuxPadPrivate * padpriv, GstBuffer * buffer); +static gboolean gst_rtp_dtmf_mux_src_event (GstRTPMux * rtp_mux, + GstEvent * event); + +GST_BOILERPLATE (GstRTPDTMFMux, gst_rtp_dtmf_mux, GstRTPMux, GST_TYPE_RTP_MUX); + +static void +gst_rtp_dtmf_mux_init (GstRTPDTMFMux * object, GstRTPDTMFMuxClass * g_class) +{ +} + +static void +gst_rtp_dtmf_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, + &priority_sink_factory); + + gst_element_class_set_details_simple (element_class, "RTP muxer", + "Codec/Muxer", + "mixes RTP DTMF streams into other RTP streams", + "Zeeshan Ali "); +} + +static void +gst_rtp_dtmf_mux_class_init (GstRTPDTMFMuxClass * klass) +{ + GstElementClass *gstelement_class; + GstRTPMuxClass *gstrtpmux_class; + + gstelement_class = (GstElementClass *) klass; + gstrtpmux_class = (GstRTPMuxClass *) klass; + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_request_new_pad); + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_change_state); + gstrtpmux_class->accept_buffer_locked = gst_rtp_dtmf_mux_accept_buffer_locked; + gstrtpmux_class->src_event = gst_rtp_dtmf_mux_src_event; +} + +static gboolean +gst_rtp_dtmf_mux_accept_buffer_locked (GstRTPMux * rtp_mux, + GstRTPMuxPadPrivate * padpriv, GstBuffer * buffer) +{ + GstRTPDTMFMux *mux = GST_RTP_DTMF_MUX (rtp_mux); + GstClockTime running_ts; + + running_ts = GST_BUFFER_TIMESTAMP (buffer); + + if (GST_CLOCK_TIME_IS_VALID (running_ts)) { + if (padpriv && padpriv->segment.format == GST_FORMAT_TIME) + running_ts = gst_segment_to_running_time (&padpriv->segment, + GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (buffer)); + + if (padpriv && padpriv->priority) { + if (GST_BUFFER_DURATION_IS_VALID (buffer)) { + if (GST_CLOCK_TIME_IS_VALID (mux->last_priority_end)) + mux->last_priority_end = + MAX (running_ts + GST_BUFFER_DURATION (buffer), + mux->last_priority_end); + else + mux->last_priority_end = running_ts + GST_BUFFER_DURATION (buffer); + GST_LOG_OBJECT (mux, "Got buffer %p on priority pad, " + " blocking regular pads until %" GST_TIME_FORMAT, buffer, + GST_TIME_ARGS (mux->last_priority_end)); + } else { + GST_WARNING_OBJECT (mux, "Buffer %p has an invalid duration," + " not blocking other pad", buffer); + } + } else { + if (GST_CLOCK_TIME_IS_VALID (mux->last_priority_end) && + running_ts < mux->last_priority_end) { + GST_LOG_OBJECT (mux, "Dropping buffer %p because running time" + " %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT, buffer, + GST_TIME_ARGS (running_ts), GST_TIME_ARGS (mux->last_priority_end)); + return FALSE; + } + } + } else { + GST_LOG_OBJECT (mux, "Buffer %p has an invalid timestamp," + " letting through", buffer); + } + + return TRUE; +} + + +static GstPad * +gst_rtp_dtmf_mux_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name) +{ + GstPad *pad; + + pad = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, request_new_pad, + (element, templ, name), NULL); + + if (pad) { + GstRTPMuxPadPrivate *padpriv; + + GST_OBJECT_LOCK (element); + padpriv = gst_pad_get_element_private (pad); + + if (gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (element), + "priority_sink_%d") == gst_pad_get_pad_template (pad)) + padpriv->priority = TRUE; + GST_OBJECT_UNLOCK (element); + } + + return pad; +} + +static gboolean +gst_rtp_dtmf_mux_src_event (GstRTPMux * rtp_mux, GstEvent * event) +{ + if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) { + const GstStructure *s = gst_event_get_structure (event); + + if (s && gst_structure_has_name (s, "dtmf-event")) { + GST_OBJECT_LOCK (rtp_mux); + if (GST_CLOCK_TIME_IS_VALID (rtp_mux->last_stop)) { + event = (GstEvent *) + gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (event)); + s = gst_event_get_structure (event); + gst_structure_set ((GstStructure *) s, + "last-stop", G_TYPE_UINT64, rtp_mux->last_stop, NULL); + } + GST_OBJECT_UNLOCK (rtp_mux); + } + } + + return GST_RTP_MUX_CLASS (parent_class)->src_event (rtp_mux, event); +} + + +static GstStateChangeReturn +gst_rtp_dtmf_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstRTPDTMFMux *mux = GST_RTP_DTMF_MUX (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + { + GST_OBJECT_LOCK (mux); + mux->last_priority_end = GST_CLOCK_TIME_NONE; + GST_OBJECT_UNLOCK (mux); + break; + } + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + return ret; +} + +gboolean +gst_rtp_dtmf_mux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_mux_debug, "rtpdtmfmux", 0, + "rtp dtmf muxer"); + + return gst_element_register (plugin, "rtpdtmfmux", GST_RANK_NONE, + GST_TYPE_RTP_DTMF_MUX); +} Index: gst-plugins-good0.10/farsight/rtpmux/gstrtpdtmfmux.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/gstrtpdtmfmux.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,67 @@ +/* RTP muxer element for GStreamer + * + * gstrtpdtmfmux.h: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RTP_DTMF_MUX_H__ +#define __GST_RTP_DTMF_MUX_H__ + +#include +#include "gstrtpmux.h" + +G_BEGIN_DECLS +#define GST_TYPE_RTP_DTMF_MUX (gst_rtp_dtmf_mux_get_type()) +#define GST_RTP_DTMF_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_MUX, GstRTPDTMFMux)) +#define GST_RTP_DTMF_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_MUX, GstRTPDTMFMux)) +#define GST_IS_RTP_DTMF_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_MUX)) +#define GST_IS_RTP_DTMF_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_MUX)) +typedef struct _GstRTPDTMFMux GstRTPDTMFMux; +typedef struct _GstRTPDTMFMuxClass GstRTPDTMFMuxClass; + +/** + * GstRTPDTMFMux: + * + * The opaque #GstRTPDTMFMux structure. + */ +struct _GstRTPDTMFMux +{ + GstRTPMux mux; + + /* Protected by object lock */ + GstClockTime last_priority_end; +}; + +struct _GstRTPDTMFMuxClass +{ + GstRTPMuxClass parent_class; + + /* signals */ + void (*locking) (GstElement * element, GstPad * pad); + void (*unlocked) (GstElement * element, GstPad * pad); +}; + +GType gst_rtp_dtmf_mux_get_type (void); +gboolean gst_rtp_dtmf_mux_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_RTP_DTMF_MUX_H__ */ Index: gst-plugins-good0.10/farsight/rtpmux/gstrtpmux.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/gstrtpmux.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,882 @@ +/* RTP muxer element for GStreamer + * + * gstrtpmux.c: + * + * Copyright (C) <2007-2010> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2007-2010> Collabora Ltd + * Contact: Olivier Crete + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-rtpmux + * @see_also: rtpdtmfmux + * + * The rtp muxer takes multiple RTP streams having the same clock-rate and + * muxes into a single stream with a single SSRC. + * + * + * Example pipelines + * |[ + * gst-launch rtpmux name=mux ! udpsink host=127.0.0.1 port=8888 \ + * alsasrc ! alawenc ! rtppcmapay ! \ + * application/x-rtp, payload=8, rate=8000 ! mux.sink_0 \ + * audiotestsrc is-live=1 ! \ + * mulawenc ! rtppcmupay ! \ + * application/x-rtp, payload=0, rate=8000 ! mux.sink_1 + * ]| + * In this example, an audio stream is captured from ALSA and another is + * generated, both are encoded into different payload types and muxed together + * so they can be sent on the same port. + * + * + * Last reviewed on 2010-09-30 (0.10.21) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "gstrtpmux.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_mux_debug); +#define GST_CAT_DEFAULT gst_rtp_mux_debug + +enum +{ + ARG_0, + PROP_TIMESTAMP_OFFSET, + PROP_SEQNUM_OFFSET, + PROP_SEQNUM, + PROP_SSRC +}; + +#define DEFAULT_TIMESTAMP_OFFSET -1 +#define DEFAULT_SEQNUM_OFFSET -1 +#define DEFAULT_SSRC -1 + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtp") + ); + +static GstPad *gst_rtp_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_rtp_mux_release_pad (GstElement * element, GstPad * pad); +static GstFlowReturn gst_rtp_mux_chain (GstPad * pad, GstBuffer * buffer); +static GstFlowReturn gst_rtp_mux_chain_list (GstPad * pad, + GstBufferList * bufferlist); +static gboolean gst_rtp_mux_setcaps (GstPad * pad, GstCaps * caps); +static GstCaps *gst_rtp_mux_getcaps (GstPad * pad); +static gboolean gst_rtp_mux_sink_event (GstPad * pad, GstEvent * event); + +static GstStateChangeReturn gst_rtp_mux_change_state (GstElement * + element, GstStateChange transition); + +static void gst_rtp_mux_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtp_mux_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_rtp_mux_dispose (GObject * object); + +static gboolean gst_rtp_mux_src_event_real (GstRTPMux *rtp_mux, + GstEvent * event); + +GST_BOILERPLATE (GstRTPMux, gst_rtp_mux, GstElement, GST_TYPE_ELEMENT); + +static void +gst_rtp_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, &src_factory); + gst_element_class_add_static_pad_template (element_class, &sink_factory); + + gst_element_class_set_details_simple (element_class, "RTP muxer", + "Codec/Muxer", + "multiplex N rtp streams into one", "Zeeshan Ali "); +} + +static void +gst_rtp_mux_class_init (GstRTPMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->get_property = gst_rtp_mux_get_property; + gobject_class->set_property = gst_rtp_mux_set_property; + gobject_class->dispose = gst_rtp_mux_dispose; + + klass->src_event = gst_rtp_mux_src_event_real; + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_TIMESTAMP_OFFSET, g_param_spec_int ("timestamp-offset", + "Timestamp Offset", + "Offset to add to all outgoing timestamps (-1 = random)", -1, + G_MAXINT, DEFAULT_TIMESTAMP_OFFSET, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM_OFFSET, + g_param_spec_int ("seqnum-offset", "Sequence number Offset", + "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXINT, + DEFAULT_SEQNUM_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM, + g_param_spec_uint ("seqnum", "Sequence number", + "The RTP sequence number of the last processed packet", + 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSRC, + g_param_spec_uint ("ssrc", "SSRC", + "The SSRC of the packets (-1 == random)", + 0, G_MAXUINT, DEFAULT_SSRC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_rtp_mux_request_new_pad); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_rtp_mux_release_pad); + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_mux_change_state); +} + +static void +gst_rtp_mux_dispose (GObject * object) +{ + GList *item; + +restart: + for (item = GST_ELEMENT_PADS (object); item; item = g_list_next (item)) { + GstPad *pad = GST_PAD (item->data); + if (GST_PAD_IS_SINK (pad)) { + gst_element_release_request_pad (GST_ELEMENT (object), pad); + goto restart; + } + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static gboolean +gst_rtp_mux_src_event (GstPad * pad, GstEvent * event) +{ + GstRTPMux *rtp_mux; + GstRTPMuxClass *klass; + gboolean ret = FALSE; + + rtp_mux = (GstRTPMux *) gst_pad_get_parent_element (pad); + g_return_val_if_fail (rtp_mux != NULL, FALSE); + klass = GST_RTP_MUX_GET_CLASS (rtp_mux); + + ret = klass->src_event (rtp_mux, event); + + gst_object_unref (rtp_mux); + + return ret; +} + +static gboolean +gst_rtp_mux_src_event_real (GstRTPMux *rtp_mux, GstEvent * event) +{ + GstIterator *iter; + GstPad *sinkpad; + gboolean result = FALSE; + gboolean done = FALSE; + + iter = gst_element_iterate_sink_pads (GST_ELEMENT (rtp_mux)); + + while (!done) { + switch (gst_iterator_next (iter, (gpointer) & sinkpad)) { + case GST_ITERATOR_OK: + gst_event_ref (event); + result |= gst_pad_push_event (sinkpad, event); + gst_object_unref (sinkpad); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + result = FALSE; + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (rtp_mux, "Error iterating sinkpads"); + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); + gst_event_unref (event); + + return result; +} + +static void +gst_rtp_mux_init (GstRTPMux * object, GstRTPMuxClass * g_class) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (object); + + object->srcpad = + gst_pad_new_from_template (gst_element_class_get_pad_template (klass, + "src"), "src"); + gst_pad_set_event_function (object->srcpad, + GST_DEBUG_FUNCPTR (gst_rtp_mux_src_event)); + gst_element_add_pad (GST_ELEMENT (object), object->srcpad); + + object->ssrc = DEFAULT_SSRC; + object->ts_offset = DEFAULT_TIMESTAMP_OFFSET; + object->seqnum_offset = DEFAULT_SEQNUM_OFFSET; + + object->segment_pending = TRUE; + object->last_stop = GST_CLOCK_TIME_NONE; +} + +static void +gst_rtp_mux_setup_sinkpad (GstRTPMux * rtp_mux, GstPad * sinkpad) +{ + GstRTPMuxPadPrivate *padpriv = g_slice_new0 (GstRTPMuxPadPrivate); + + /* setup some pad functions */ + gst_pad_set_setcaps_function (sinkpad, gst_rtp_mux_setcaps); + gst_pad_set_getcaps_function (sinkpad, gst_rtp_mux_getcaps); + gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_rtp_mux_chain)); + gst_pad_set_chain_list_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_rtp_mux_chain_list)); + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_rtp_mux_sink_event)); + + + gst_segment_init (&padpriv->segment, GST_FORMAT_UNDEFINED); + + gst_pad_set_element_private (sinkpad, padpriv); + + gst_pad_set_active (sinkpad, TRUE); + gst_element_add_pad (GST_ELEMENT (rtp_mux), sinkpad); +} + +static GstPad * +gst_rtp_mux_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * req_name) +{ + GstRTPMux *rtp_mux; + GstPad *newpad; + + g_return_val_if_fail (templ != NULL, NULL); + g_return_val_if_fail (GST_IS_RTP_MUX (element), NULL); + + rtp_mux = GST_RTP_MUX (element); + + if (templ->direction != GST_PAD_SINK) { + GST_WARNING_OBJECT (rtp_mux, "request pad that is not a SINK pad"); + return NULL; + } + + newpad = gst_pad_new_from_template (templ, req_name); + if (newpad) + gst_rtp_mux_setup_sinkpad (rtp_mux, newpad); + else + GST_WARNING_OBJECT (rtp_mux, "failed to create request pad"); + + return newpad; +} + +static void +gst_rtp_mux_release_pad (GstElement * element, GstPad * pad) +{ + GstRTPMuxPadPrivate *padpriv; + + GST_OBJECT_LOCK (element); + padpriv = gst_pad_get_element_private (pad); + gst_pad_set_element_private (pad, NULL); + GST_OBJECT_UNLOCK (element); + + gst_element_remove_pad (element, pad); + + if (padpriv) { + gst_caps_replace (&padpriv->out_caps, NULL); + g_slice_free (GstRTPMuxPadPrivate, padpriv); + } +} + +/* Put our own clock-base on the buffer */ +static void +gst_rtp_mux_readjust_rtp_timestamp_locked (GstRTPMux * rtp_mux, + GstRTPMuxPadPrivate * padpriv, GstBuffer * buffer) +{ + guint32 ts; + guint32 sink_ts_base = 0; + + if (padpriv && padpriv->have_clock_base) + sink_ts_base = padpriv->clock_base; + + ts = gst_rtp_buffer_get_timestamp (buffer) - sink_ts_base + rtp_mux->ts_base; + GST_LOG_OBJECT (rtp_mux, "Re-adjusting RTP ts %u to %u", + gst_rtp_buffer_get_timestamp (buffer), ts); + gst_rtp_buffer_set_timestamp (buffer, ts); +} + +static gboolean +process_buffer_locked (GstRTPMux * rtp_mux, GstRTPMuxPadPrivate * padpriv, + GstBuffer * buffer) +{ + GstRTPMuxClass *klass = GST_RTP_MUX_GET_CLASS (rtp_mux); + + if (klass->accept_buffer_locked) + if (!klass->accept_buffer_locked (rtp_mux, padpriv, buffer)) + return FALSE; + + rtp_mux->seqnum++; + gst_rtp_buffer_set_seq (buffer, rtp_mux->seqnum); + + gst_rtp_buffer_set_ssrc (buffer, rtp_mux->current_ssrc); + gst_rtp_mux_readjust_rtp_timestamp_locked (rtp_mux, padpriv, buffer); + GST_LOG_OBJECT (rtp_mux, "Pushing packet size %d, seq=%d, ts=%u", + GST_BUFFER_SIZE (buffer), rtp_mux->seqnum, + gst_rtp_buffer_get_timestamp (buffer)); + + if (padpriv) { + gst_buffer_set_caps (buffer, padpriv->out_caps); + if (padpriv->segment.format == GST_FORMAT_TIME) + GST_BUFFER_TIMESTAMP (buffer) = + gst_segment_to_running_time (&padpriv->segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buffer)); + } + + return TRUE; +} + +static GstFlowReturn +gst_rtp_mux_chain_list (GstPad * pad, GstBufferList * bufferlist) +{ + GstRTPMux *rtp_mux; + GstFlowReturn ret; + GstBufferListIterator *it; + GstRTPMuxPadPrivate *padpriv; + GstEvent *newseg_event = NULL; + gboolean drop = TRUE; + + rtp_mux = GST_RTP_MUX (gst_pad_get_parent (pad)); + + if (!gst_rtp_buffer_list_validate (bufferlist)) { + GST_ERROR_OBJECT (rtp_mux, "Invalid RTP buffer"); + gst_object_unref (rtp_mux); + return GST_FLOW_ERROR; + } + + GST_OBJECT_LOCK (rtp_mux); + + padpriv = gst_pad_get_element_private (pad); + if (!padpriv) { + GST_OBJECT_UNLOCK (rtp_mux); + ret = GST_FLOW_NOT_LINKED; + gst_buffer_list_unref (bufferlist); + goto out; + } + + bufferlist = gst_buffer_list_make_writable (bufferlist); + it = gst_buffer_list_iterate (bufferlist); + while (gst_buffer_list_iterator_next_group (it)) { + GstBuffer *rtpbuf; + + rtpbuf = gst_buffer_list_iterator_next (it); + rtpbuf = gst_buffer_make_writable (rtpbuf); + + drop = !process_buffer_locked (rtp_mux, padpriv, rtpbuf); + + if (drop) + break; + + gst_buffer_list_iterator_take (it, rtpbuf); + + do { + if (GST_BUFFER_DURATION_IS_VALID (rtpbuf) && + GST_BUFFER_TIMESTAMP_IS_VALID (rtpbuf)) + rtp_mux->last_stop = GST_BUFFER_TIMESTAMP (rtpbuf) + + GST_BUFFER_DURATION (rtpbuf); + else + rtp_mux->last_stop = GST_CLOCK_TIME_NONE; + + gst_buffer_list_iterator_take (it, rtpbuf); + + } while ((rtpbuf = gst_buffer_list_iterator_next (it)) != NULL); + + + } + gst_buffer_list_iterator_free (it); + + if (!drop && rtp_mux->segment_pending) { + /* + * We set the start at 0, because we re-timestamps to the running time + */ + newseg_event = gst_event_new_new_segment_full (FALSE, 1.0, 1.0, + GST_FORMAT_TIME, 0, -1, 0); + + rtp_mux->segment_pending = FALSE; + } + + GST_OBJECT_UNLOCK (rtp_mux); + + if (newseg_event) + gst_pad_push_event (rtp_mux->srcpad, newseg_event); + + if (drop) { + gst_buffer_list_unref (bufferlist); + ret = GST_FLOW_OK; + } else { + ret = gst_pad_push_list (rtp_mux->srcpad, bufferlist); + } + +out: + + gst_object_unref (rtp_mux); + + return ret; +} + +static GstFlowReturn +gst_rtp_mux_chain (GstPad * pad, GstBuffer * buffer) +{ + GstRTPMux *rtp_mux; + GstFlowReturn ret; + GstRTPMuxPadPrivate *padpriv; + GstEvent *newseg_event = NULL; + gboolean drop; + + rtp_mux = GST_RTP_MUX (GST_OBJECT_PARENT (pad)); + + if (!gst_rtp_buffer_validate (buffer)) { + gst_buffer_unref (buffer); + GST_ERROR_OBJECT (rtp_mux, "Invalid RTP buffer"); + return GST_FLOW_ERROR; + } + + GST_OBJECT_LOCK (rtp_mux); + padpriv = gst_pad_get_element_private (pad); + + if (!padpriv) { + GST_OBJECT_UNLOCK (rtp_mux); + gst_buffer_unref (buffer); + return GST_FLOW_NOT_LINKED; + } + + buffer = gst_buffer_make_writable (buffer); + + drop = !process_buffer_locked (rtp_mux, padpriv, buffer); + + if (!drop) { + if (rtp_mux->segment_pending) { + /* + * We set the start at 0, because we re-timestamps to the running time + */ + newseg_event = gst_event_new_new_segment_full (FALSE, 1.0, 1.0, + GST_FORMAT_TIME, 0, -1, 0); + + rtp_mux->segment_pending = FALSE; + } + + if (GST_BUFFER_DURATION_IS_VALID (buffer) && + GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + rtp_mux->last_stop = GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer); + else + rtp_mux->last_stop = GST_CLOCK_TIME_NONE; + } + + GST_OBJECT_UNLOCK (rtp_mux); + + if (newseg_event) + gst_pad_push_event (rtp_mux->srcpad, newseg_event); + + if (drop) { + gst_buffer_unref (buffer); + ret = GST_FLOW_OK; + } else { + ret = gst_pad_push (rtp_mux->srcpad, buffer); + } + + return ret; +} + +static gboolean +gst_rtp_mux_setcaps (GstPad * pad, GstCaps * caps) +{ + GstRTPMux *rtp_mux; + GstStructure *structure; + gboolean ret = FALSE; + GstRTPMuxPadPrivate *padpriv; + + rtp_mux = GST_RTP_MUX (gst_pad_get_parent (pad)); + + structure = gst_caps_get_structure (caps, 0); + + if (!structure) + goto out; + + GST_OBJECT_LOCK (rtp_mux); + padpriv = gst_pad_get_element_private (pad); + if (padpriv && + gst_structure_get_uint (structure, "clock-base", &padpriv->clock_base)) { + padpriv->have_clock_base = TRUE; + } + GST_OBJECT_UNLOCK (rtp_mux); + + caps = gst_caps_copy (caps); + + gst_caps_set_simple (caps, + "clock-base", G_TYPE_UINT, rtp_mux->ts_base, + "seqnum-base", G_TYPE_UINT, rtp_mux->seqnum_base, NULL); + + GST_DEBUG_OBJECT (rtp_mux, + "setting caps %" GST_PTR_FORMAT " on src pad..", caps); + ret = gst_pad_set_caps (rtp_mux->srcpad, caps); + + if (rtp_mux->ssrc == -1) { + if (gst_structure_has_field_typed (structure, "ssrc", G_TYPE_UINT)) { + rtp_mux->current_ssrc = g_value_get_uint + (gst_structure_get_value (structure, "ssrc")); + } + } + + if (ret) { + GST_OBJECT_LOCK (rtp_mux); + padpriv = gst_pad_get_element_private (pad); + if (padpriv) + gst_caps_replace (&padpriv->out_caps, caps); + GST_OBJECT_UNLOCK (rtp_mux); + } + gst_caps_unref (caps); + +out: + gst_object_unref (rtp_mux); + + return ret; +} + +static void +clear_caps (GstCaps * caps, gboolean only_clock_rate) +{ + gint i, j; + + /* Lets only match on the clock-rate */ + for (i = 0; i < gst_caps_get_size (caps); i++) { + GstStructure *s = gst_caps_get_structure (caps, i); + + for (j = 0; j < gst_structure_n_fields (s); j++) { + const gchar *name = gst_structure_nth_field_name (s, j); + + if (strcmp (name, "clock-rate") && (only_clock_rate || + (strcmp (name, "ssrc")))) { + gst_structure_remove_field (s, name); + j--; + } + } + } +} + +static gboolean +same_clock_rate_fold (gpointer item, GValue * ret, gpointer user_data) +{ + GstPad *mypad = user_data; + GstPad *pad = item; + GstCaps *peercaps; + GstCaps *othercaps; + const GstCaps *accumcaps; + GstCaps *intersect; + + if (pad == mypad) { + gst_object_unref (pad); + return TRUE; + } + + peercaps = gst_pad_peer_get_caps (pad); + if (!peercaps) { + gst_object_unref (pad); + return TRUE; + } + + othercaps = gst_caps_intersect (peercaps, + gst_pad_get_pad_template_caps (pad)); + gst_caps_unref (peercaps); + + accumcaps = gst_value_get_caps (ret); + + clear_caps (othercaps, TRUE); + + intersect = gst_caps_intersect (accumcaps, othercaps); + + g_value_take_boxed (ret, intersect); + + gst_caps_unref (othercaps); + gst_object_unref (pad); + + return !gst_caps_is_empty (intersect); +} + +static GstCaps * +gst_rtp_mux_getcaps (GstPad * pad) +{ + GstRTPMux *mux = GST_RTP_MUX (gst_pad_get_parent (pad)); + GstCaps *caps = NULL; + GstIterator *iter = NULL; + GValue v = { 0 }; + GstIteratorResult res; + GstCaps *peercaps = gst_pad_peer_get_caps (mux->srcpad); + GstCaps *othercaps = NULL; + + if (peercaps) { + othercaps = gst_caps_intersect (peercaps, + gst_pad_get_pad_template_caps (pad)); + gst_caps_unref (peercaps); + } else { + othercaps = gst_caps_copy (gst_pad_get_pad_template_caps (mux->srcpad)); + } + + clear_caps (othercaps, FALSE); + + g_value_init (&v, GST_TYPE_CAPS); + + iter = gst_element_iterate_sink_pads (GST_ELEMENT (mux)); + do { + gst_value_set_caps (&v, othercaps); + res = gst_iterator_fold (iter, same_clock_rate_fold, &v, pad); + } while (res == GST_ITERATOR_RESYNC); + gst_iterator_free (iter); + + caps = (GstCaps *) gst_value_get_caps (&v); + + if (res == GST_ITERATOR_ERROR) { + gst_caps_unref (caps); + caps = gst_caps_new_empty (); + } + + if (othercaps) + gst_caps_unref (othercaps); + gst_object_unref (mux); + + return caps; +} + + +static void +gst_rtp_mux_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstRTPMux *rtp_mux; + + rtp_mux = GST_RTP_MUX (object); + + switch (prop_id) { + case PROP_TIMESTAMP_OFFSET: + g_value_set_int (value, rtp_mux->ts_offset); + break; + case PROP_SEQNUM_OFFSET: + g_value_set_int (value, rtp_mux->seqnum_offset); + break; + case PROP_SEQNUM: + GST_OBJECT_LOCK (rtp_mux); + g_value_set_uint (value, rtp_mux->seqnum); + GST_OBJECT_UNLOCK (rtp_mux); + break; + case PROP_SSRC: + g_value_set_uint (value, rtp_mux->ssrc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_mux_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstRTPMux *rtp_mux; + + rtp_mux = GST_RTP_MUX (object); + + switch (prop_id) { + case PROP_TIMESTAMP_OFFSET: + rtp_mux->ts_offset = g_value_get_int (value); + break; + case PROP_SEQNUM_OFFSET: + rtp_mux->seqnum_offset = g_value_get_int (value); + break; + case PROP_SSRC: + rtp_mux->ssrc = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_rtp_mux_sink_event (GstPad * pad, GstEvent * event) +{ + + GstRTPMux *mux; + gboolean ret = FALSE; + gboolean forward = TRUE; + + mux = GST_RTP_MUX (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + { + GstRTPMuxPadPrivate *padpriv; + + GST_OBJECT_LOCK (mux); + mux->last_stop = GST_CLOCK_TIME_NONE; + mux->segment_pending = TRUE; + padpriv = gst_pad_get_element_private (pad); + if (padpriv) + gst_segment_init (&padpriv->segment, GST_FORMAT_UNDEFINED); + GST_OBJECT_UNLOCK (mux); + } + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + GstRTPMuxPadPrivate *padpriv; + + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + GST_OBJECT_LOCK (mux); + padpriv = gst_pad_get_element_private (pad); + + if (padpriv) { + if (format == GST_FORMAT_TIME) + gst_segment_set_newsegment_full (&padpriv->segment, update, + rate, applied_rate, format, start, stop, position); + else + gst_segment_init (&padpriv->segment, GST_FORMAT_UNDEFINED); + } + GST_OBJECT_UNLOCK (mux); + gst_event_unref (event); + forward = FALSE; + ret = TRUE; + break; + } + default: + break; + } + + if (forward) + ret = gst_pad_push_event (mux->srcpad, event); + + gst_object_unref (mux); + return ret; +} + + +static void +clear_segment (gpointer data, gpointer user_data) +{ + GstPad *pad = data; + GstRTPMux *mux = user_data; + GstRTPMuxPadPrivate *padpriv; + + GST_OBJECT_LOCK (mux); + padpriv = gst_pad_get_element_private (pad); + if (padpriv) + gst_segment_init (&padpriv->segment, GST_FORMAT_UNDEFINED); + GST_OBJECT_UNLOCK (mux); + + gst_object_unref (pad); +} + + +static void +gst_rtp_mux_ready_to_paused (GstRTPMux * rtp_mux) +{ + GstIterator *iter; + + iter = gst_element_iterate_sink_pads (GST_ELEMENT (rtp_mux)); + while (gst_iterator_foreach (iter, clear_segment, rtp_mux) == + GST_ITERATOR_RESYNC); + gst_iterator_free (iter); + + GST_OBJECT_LOCK (rtp_mux); + rtp_mux->segment_pending = TRUE; + + if (rtp_mux->ssrc == -1) + rtp_mux->current_ssrc = g_random_int (); + else + rtp_mux->current_ssrc = rtp_mux->ssrc; + + if (rtp_mux->seqnum_offset == -1) + rtp_mux->seqnum_base = g_random_int_range (0, G_MAXUINT16); + else + rtp_mux->seqnum_base = rtp_mux->seqnum_offset; + rtp_mux->seqnum = rtp_mux->seqnum_base; + + if (rtp_mux->ts_offset == -1) + rtp_mux->ts_base = g_random_int (); + else + rtp_mux->ts_base = rtp_mux->ts_offset; + + rtp_mux->last_stop = GST_CLOCK_TIME_NONE; + + GST_DEBUG_OBJECT (rtp_mux, "set clock-base to %u", rtp_mux->ts_base); + + GST_OBJECT_UNLOCK (rtp_mux); +} + +static GstStateChangeReturn +gst_rtp_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstRTPMux *rtp_mux; + + rtp_mux = GST_RTP_MUX (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_rtp_mux_ready_to_paused (rtp_mux); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + default: + break; + } + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + +gboolean +gst_rtp_mux_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_rtp_mux_debug, "rtpmux", 0, "rtp muxer"); + + return gst_element_register (plugin, "rtpmux", GST_RANK_NONE, + GST_TYPE_RTP_MUX); +} Index: gst-plugins-good0.10/farsight/rtpmux/gstrtpmuxer.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/gstrtpmuxer.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,48 @@ +/* GStreamer RTP Muxer Plugins + * + * gstrtpdtmf.c: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gstrtpmux.h" +#include "gstrtpdtmfmux.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_rtp_mux_plugin_init (plugin)) + return FALSE; + if (!gst_rtp_dtmf_mux_plugin_init (plugin)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "rtpmux", + "RTP Muxer plugins", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/farsight/rtpmux/gstrtpmux.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/gstrtpmux.h 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,96 @@ +/* RTP muxer element for GStreamer + * + * gstrtpmux.h: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_RTP_MUX_H__ +#define __GST_RTP_MUX_H__ + +#include + +G_BEGIN_DECLS +#define GST_TYPE_RTP_MUX (gst_rtp_mux_get_type()) +#define GST_RTP_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_MUX, GstRTPMux)) +#define GST_RTP_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_MUX, GstRTPMuxClass)) +#define GST_RTP_MUX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTP_MUX, GstRTPMuxClass)) +#define GST_IS_RTP_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_MUX)) +#define GST_IS_RTP_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_MUX)) +typedef struct _GstRTPMux GstRTPMux; +typedef struct _GstRTPMuxClass GstRTPMuxClass; + + +typedef struct +{ + gboolean have_clock_base; + guint clock_base; + + GstCaps *out_caps; + + GstSegment segment; + + gboolean priority; +} GstRTPMuxPadPrivate; + + +/** + * GstRTPMux: + * + * The opaque #GstRTPMux structure. + */ +struct _GstRTPMux +{ + GstElement element; + + /* pad */ + GstPad *srcpad; + + guint32 ts_base; + guint16 seqnum_base; + + gint32 ts_offset; + gint16 seqnum_offset; + guint16 seqnum; /* protected by object lock */ + guint ssrc; + guint current_ssrc; + + gboolean segment_pending; + + GstClockTime last_stop; +}; + +struct _GstRTPMuxClass +{ + GstElementClass parent_class; + + gboolean (*accept_buffer_locked) (GstRTPMux *rtp_mux, + GstRTPMuxPadPrivate * padpriv, GstBuffer * buffer); + + gboolean (*src_event) (GstRTPMux *rtp_mux, GstEvent *event); +}; + + +GType gst_rtp_mux_get_type (void); +gboolean gst_rtp_mux_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_RTP_MUX_H__ */ Index: gst-plugins-good0.10/farsight/rtpmux/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/rtpmux/Makefile.am 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,26 @@ +plugin_LTLIBRARIES = libgstrtpmux.la + +libgstrtpmux_la_SOURCES = gstrtpmuxer.c gstrtpmux.c gstrtpdtmfmux.c + +libgstrtpmux_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \ + -DEXTERN_BUF -DRTP_SUPPORT +libgstrtpmux_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) $(GST_LIBS) +libgstrtpmux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstrtpmux_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstrtpmux.h gstrtpdtmfmux.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstrtpmux -:SHARED libgstrtpmux \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstrtpmux_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstrtpmux_la_CFLAGS) \ + -:LDFLAGS $(libgstrtpmux_la_LDFLAGS) \ + $(libgstrtpmux_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ \ No newline at end of file Index: gst-plugins-good0.10/Makefile.am =================================================================== --- gst-plugins-good0.10.orig/Makefile.am 2012-02-09 13:53:21.481011203 +0200 +++ gst-plugins-good0.10/Makefile.am 2012-02-09 13:54:47.447583656 +0200 @@ -8,6 +8,7 @@ ALWAYS_SUBDIRS = \ gst sys ext \ + farsight \ tests \ docs \ po \ Index: gst-plugins-good0.10/farsight/autoconvert/gstautovideoconvert.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/gstautovideoconvert.c 2012-02-09 13:54:47.447583656 +0200 @@ -0,0 +1,287 @@ +/* GStreamer + * Copyright 2010 ST-Ericsson SA + * @author: Benjamin Gaignard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + * test autovideoconvert: + * if rgb2bayer is present + * gst-launch videotestsrc num-buffers=2 ! "video/x-raw-rgb,width=100,height=100,framerate=10/1" ! autovideoconvert ! "video/x-raw-bayer,width=100,height=100,format=bggr,framerate=10/1" ! fakesink -v + * if bayer2rgb is present + * gst-launch videotestsrc num-buffers=2 ! "video/x-raw-bayer,width=100,height=100,format=bggr,framerate=10/1" ! autovideoconvert ! "video/x-raw-rgb,width=100,height=100,framerate=10/1" ! fakesink -v + * test with ffmpegvideoconvert + * gst-launch videotestsrc num-buffers=2 ! "video/x-raw-rgb,bpp=32,width=100,height=100,framerate=10/1" ! autovideoconvert ! "video/x-raw-rgb,bpp=16,width=100,height=100,framerate=10/1" ! fakesink -v + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstautovideoconvert.h" + +GST_DEBUG_CATEGORY (autovideoconvert_debug); +#define GST_CAT_DEFAULT (autovideoconvert_debug) + +GStaticMutex factories_mutex = G_STATIC_MUTEX_INIT; +guint32 factories_cookie = 0; /* Cookie from last time when factories was updated */ +GList *factories = NULL; /* factories we can use for selecting elements */ + +/* element factory information */ +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + +static GstStateChangeReturn gst_auto_video_convert_change_state (GstElement * + element, GstStateChange transition); + +void gst_auto_video_convert_update_factory_list (GstAutoVideoConvert * + autovideoconvert); + +static gboolean +gst_auto_video_convert_element_filter (GstPluginFeature * feature, + GstAutoVideoConvert * autovideoconvert) +{ + const gchar *klass; + + /* we only care about element factories */ + if (G_UNLIKELY (!GST_IS_ELEMENT_FACTORY (feature))) + return FALSE; + + klass = gst_element_factory_get_klass (GST_ELEMENT_FACTORY_CAST (feature)); + /* only select color space converter */ + if (strstr (klass, "Filter") && + strstr (klass, "Converter") && strstr (klass, "Video")) { + GST_DEBUG_OBJECT (autovideoconvert, + "gst_auto_video_convert_element_filter found %s\n", + gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (feature))); + return TRUE; + } + return FALSE; +} + + +static GList * +gst_auto_video_convert_create_factory_list (GstAutoVideoConvert * + autovideoconvert) +{ + GList *result = NULL; + + /* get the feature list using the filter */ + result = gst_default_registry_feature_filter ((GstPluginFeatureFilter) + gst_auto_video_convert_element_filter, FALSE, autovideoconvert); + + /* sort on rank and name */ + result = g_list_sort (result, gst_plugin_feature_rank_compare_func); + + return result; +} + +void +gst_auto_video_convert_update_factory_list (GstAutoVideoConvert * + autovideoconvert) +{ + /* use a static mutex to protect factories list and factories cookie */ + g_static_mutex_lock (&factories_mutex); + + /* test if a factories list already exist or not */ + if (!factories) { + /* no factories list create it */ + factories_cookie = gst_default_registry_get_feature_list_cookie (); + factories = gst_auto_video_convert_create_factory_list (autovideoconvert); + } else { + /* a factories list exist but is it up to date? */ + if (factories_cookie != gst_default_registry_get_feature_list_cookie ()) { + /* we need to update the factories list */ + /* first free the old one */ + gst_plugin_feature_list_free (factories); + /* then create an updated one */ + factories_cookie = gst_default_registry_get_feature_list_cookie (); + factories = gst_auto_video_convert_create_factory_list (autovideoconvert); + } + } + + g_static_mutex_unlock (&factories_mutex); +} + +GST_BOILERPLATE (GstAutoVideoConvert, gst_auto_video_convert, GstBin, + GST_TYPE_BIN); + +static void +gst_auto_video_convert_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &srctemplate); + gst_element_class_add_static_pad_template (element_class, &sinktemplate); + + gst_element_class_set_details_simple (element_class, + "Select color space convertor based on caps", "Generic/Bin", + "Selects the right color space convertor based on the caps", + "Benjamin Gaignard "); +} + +static void +gst_auto_video_convert_dispose (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_auto_video_convert_class_init (GstAutoVideoConvertClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *gstelement_class = (GstElementClass *) klass; + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_auto_video_convert_dispose); + + GST_DEBUG_CATEGORY_INIT (autovideoconvert_debug, "autovideoconvert", 0, + "Auto color space converter"); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_auto_video_convert_change_state); + +} + +static gboolean +gst_auto_video_convert_add_autoconvert (GstAutoVideoConvert * autovideoconvert) +{ + GstPad *pad; + + if (autovideoconvert->autoconvert) + return TRUE; + + autovideoconvert->autoconvert = + gst_element_factory_make ("autoconvert", "autoconvertchild"); + if (!autovideoconvert->autoconvert) { + GST_ERROR_OBJECT (autovideoconvert, + "Could not create autoconvert instance"); + return FALSE; + } + + /* first add autoconvert in bin */ + gst_bin_add (GST_BIN (autovideoconvert), + gst_object_ref (autovideoconvert->autoconvert)); + + /* get sinkpad and link it to ghost sink pad */ + pad = gst_element_get_static_pad (autovideoconvert->autoconvert, "sink"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (autovideoconvert->sinkpad), + pad); + gst_object_unref (pad); + + /* get srcpad and link it to ghost src pad */ + pad = gst_element_get_static_pad (autovideoconvert->autoconvert, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (autovideoconvert->srcpad), pad); + gst_object_unref (pad); + + return TRUE; +} + +static void +gst_auto_video_convert_remove_autoconvert (GstAutoVideoConvert * + autovideoconvert) +{ + if (!autovideoconvert->autoconvert) + return; + + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (autovideoconvert->srcpad), + NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (autovideoconvert->sinkpad), + NULL); + + gst_bin_remove (GST_BIN (autovideoconvert), autovideoconvert->autoconvert); + gst_object_unref (autovideoconvert->autoconvert); + autovideoconvert->autoconvert = NULL; +} + +static void +gst_auto_video_convert_init (GstAutoVideoConvert * autovideoconvert, + GstAutoVideoConvertClass * klass) +{ + GstPadTemplate *pad_tmpl; + + /* get sink pad template */ + pad_tmpl = gst_static_pad_template_get (&sinktemplate); + autovideoconvert->sinkpad = + gst_ghost_pad_new_no_target_from_template ("sink", pad_tmpl); + /* add sink ghost pad */ + gst_element_add_pad (GST_ELEMENT (autovideoconvert), + autovideoconvert->sinkpad); + gst_object_unref (pad_tmpl); + + /* get src pad template */ + pad_tmpl = gst_static_pad_template_get (&srctemplate); + autovideoconvert->srcpad = + gst_ghost_pad_new_no_target_from_template ("src", pad_tmpl); + /* add src ghost pad */ + gst_element_add_pad (GST_ELEMENT (autovideoconvert), + autovideoconvert->srcpad); + gst_object_unref (pad_tmpl); + + return; +} + +static GstStateChangeReturn +gst_auto_video_convert_change_state (GstElement * element, + GstStateChange transition) +{ + GstAutoVideoConvert *autovideoconvert = GST_AUTO_VIDEO_CONVERT (element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + { + /* create and add autoconvert in bin */ + if (!gst_auto_video_convert_add_autoconvert (autovideoconvert)) { + ret = GST_STATE_CHANGE_FAILURE; + return ret; + } + /* get an updated list of factories */ + gst_auto_video_convert_update_factory_list (autovideoconvert); + GST_DEBUG_OBJECT (autovideoconvert, "set factories list"); + /* give factory list to autoconvert */ + g_object_set (GST_ELEMENT (autovideoconvert->autoconvert), "factories", + factories, NULL); + break; + } + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + { + gst_auto_video_convert_remove_autoconvert (autovideoconvert); + break; + } + default: + break; + } + + return ret; +} Index: gst-plugins-good0.10/farsight/autoconvert/gstautovideoconvert.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/gstautovideoconvert.h 2012-02-09 13:54:47.451583775 +0200 @@ -0,0 +1,55 @@ +/* GStreamer + * Copyright 2010 ST-Ericsson SA + * @author: Benjamin Gaignard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_AUTO_VIDEO_CONVERT_H__ +#define __GST_AUTO_VIDEO_CONVERT_H__ + +#include +#include +#include "gstautoconvert.h" + +G_BEGIN_DECLS +#define GST_TYPE_AUTO_VIDEO_CONVERT (gst_auto_video_convert_get_type()) +#define GST_AUTO_VIDEO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AUTO_VIDEO_CONVERT,GstAutoVideoConvert)) +#define GST_AUTO_VIDEO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AUTO_VIDEO_CONVERT,GstAutoVideoConvertClass)) +#define GST_IS_AUTO_VIDEO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AUTO_VIDEO_CONVERT)) +#define GST_IS_AUTO_VIDEO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AUTO_VIDEO_CONVERT)) +typedef struct _GstAutoVideoConvert GstAutoVideoConvert; +typedef struct _GstAutoVideoConvertClass GstAutoVideoConvertClass; + +struct _GstAutoVideoConvert +{ + /*< private > */ + GstBin bin; /* we extend GstBin */ + + GstElement *autoconvert; + GstPad *sinkpad; + GstPad *srcpad; +}; + +struct _GstAutoVideoConvertClass +{ + GstBinClass parent_class; +}; + +GType gst_auto_video_convert_get_type (void); + +G_END_DECLS +#endif /* __GST_AUTO_VIDEO_CONVERT_H__ */ Index: gst-plugins-good0.10/farsight/autoconvert/plugin.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/autoconvert/plugin.c 2012-02-09 13:54:47.451583775 +0200 @@ -0,0 +1,46 @@ +/* GStreamer + * Copyright 2010 ST-Ericsson SA + * @author: Benjamin Gaignard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstautoconvert.h" +#include "gstautovideoconvert.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret; + + ret = gst_element_register (plugin, "autoconvert", + GST_RANK_NONE, GST_TYPE_AUTO_CONVERT); + + ret &= gst_element_register (plugin, "autovideoconvert", + GST_RANK_NONE, GST_TYPE_AUTO_VIDEO_CONVERT); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "autoconvert", + "Selects convertor element based on caps", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/farsight/dtmf/gstdtmfcommon.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/farsight/dtmf/gstdtmfcommon.h 2012-02-09 13:42:37.305732970 +0200 @@ -0,0 +1,37 @@ + +#ifndef __GST_RTP_DTMF_COMMON_H__ +#define __GST_RTP_DTMF_COMMON_H__ + +#define MIN_INTER_DIGIT_INTERVAL 100 /* ms */ +#define MIN_PULSE_DURATION 250 /* ms */ + +#define MIN_VOLUME 0 +#define MAX_VOLUME 36 + +#define MIN_EVENT 0 +#define MAX_EVENT 15 +#define MIN_EVENT_STRING "0" +#define MAX_EVENT_STRING "15" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +typedef struct +{ + unsigned event:8; /* Current DTMF event */ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + unsigned volume:6; /* power level of the tone, in dBm0 */ + unsigned r:1; /* Reserved-bit */ + unsigned e:1; /* End-bit */ +#elif G_BYTE_ORDER == G_BIG_ENDIAN + unsigned e:1; /* End-bit */ + unsigned r:1; /* Reserved-bit */ + unsigned volume:6; /* power level of the tone, in dBm0 */ +#else +#error "G_BYTE_ORDER should be big or little endian." +#endif + unsigned duration:16; /* Duration of digit, in timestamp units */ +} GstRTPDTMFPayload; + +#endif /* __GST_RTP_DTMF_COMMON_H__ */ debian/patches/git_new_v4l_building.patch0000664000000000000000000000265112061315514015740 0ustar From bfb1ac952b4c3caaba0297bbe7dc73ea92f9b292 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 09 Aug 2012 07:35:23 +0000Subject: v4l2: fix build with recent kernels, the v4l2_buffer input field was removed This was unused apparently and removed in the kernel in commit: From 2b719d7baf490e24ce7d817c6337b7c87fda84c1 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 2 May 2012 09:40:03 -0300 Subject: [PATCH] [media] v4l: drop v4l2_buffer.input and V4L2_BUF_FLAG_INPUT Remove input field in struct v4l2_buffer and flag V4L2_BUF_FLAG_INPUT which tells the former is valid. The flag is used by no driver currently. https://bugzilla.gnome.org/show_bug.cgi?id=681491 --- Index: gst-plugins-good0.10-0.10.31/sys/v4l2/gstv4l2bufferpool.c =================================================================== --- gst-plugins-good0.10-0.10.31.orig/sys/v4l2/gstv4l2bufferpool.c 2011-12-30 14:59:13.000000000 +0100 +++ gst-plugins-good0.10-0.10.31/sys/v4l2/gstv4l2bufferpool.c 2012-11-16 15:05:59.798974401 +0100 @@ -181,7 +181,6 @@ GST_LOG_OBJECT (pool->v4l2elem, " MMAP offset: %u", ret->vbuffer.m.offset); GST_LOG_OBJECT (pool->v4l2elem, " length: %u", ret->vbuffer.length); - GST_LOG_OBJECT (pool->v4l2elem, " input: %u", ret->vbuffer.input); data = (guint8 *) v4l2_mmap (0, ret->vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, pool->video_fd, debian/patches/git_ring_buffer_null_check.patch0000664000000000000000000000311612061315514017161 0ustar --- gst-plugins-good0.10-0.10.31.orig/ext/pulse/pulsesink.c 2012-01-23 19:21:30.000000000 +0100 +++ gst-plugins-good0.10-0.10.31/ext/pulse/pulsesink.c 2012-11-14 13:58:56.010714615 +0100 @@ -2053,8 +2053,8 @@ gst_pulsesink_pad_acceptcaps (GstPad * pad, GstCaps * caps) { GstPulseSink *psink = GST_PULSESINK (gst_pad_get_parent_element (pad)); - GstPulseRingBuffer *pbuf = GST_PULSERING_BUFFER_CAST (GST_BASE_AUDIO_SINK - (psink)->ringbuffer); + + GstPulseRingBuffer *pbuf = NULL; GstCaps *pad_caps; GstStructure *st; gboolean ret = FALSE; @@ -2073,8 +2073,8 @@ gst_caps_unref (pad_caps); } - /* Either template caps didn't match, or we're still in NULL state */ - if (!ret || !pbuf->context) + /* Template caps didn't match */ + if (!ret) goto done; /* If we've not got fixed caps, creating a stream might fail, so let's just @@ -2082,10 +2082,23 @@ if (!gst_caps_is_fixed (caps)) goto done; - ret = FALSE; + GST_OBJECT_LOCK (psink); + pbuf = GST_PULSERING_BUFFER_CAST (GST_BASE_AUDIO_SINK (psink)->ringbuffer); + if (pbuf != NULL) + gst_object_ref (pbuf); + GST_OBJECT_UNLOCK (psink); + + /* We're still in NULL state */ + if (pbuf == NULL) + goto done; pa_threaded_mainloop_lock (mainloop); + if (pbuf->context == NULL) + goto out; + + ret = FALSE; + spec.latency_time = GST_BASE_AUDIO_SINK (psink)->latency_time; if (!gst_ring_buffer_parse_caps (&spec, caps)) goto out; @@ -2169,6 +2182,8 @@ pa_threaded_mainloop_unlock (mainloop); + gst_object_unref (pbuf); + done: gst_object_unref (psink); return ret; debian/patches/0001-v4l2-fix-compilation-against-newer-kernel-headers-as.patch0000664000000000000000000000135412156564235024153 0ustar From 8e633d2059cb835448021cf79becb487aff10975 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 18 Mar 2013 14:59:35 +0000 Subject: [PATCH] v4l2: fix compilation against newer kernel headers as on FC19 --- sys/v4l2/v4l2_calls.c | 4 ++++ 1 file changed, 4 insertions(+) Index: b/sys/v4l2/v4l2_calls.c =================================================================== --- a/sys/v4l2/v4l2_calls.c +++ b/sys/v4l2/v4l2_calls.c @@ -294,8 +294,12 @@ break; case V4L2_CID_HFLIP: case V4L2_CID_VFLIP: +#ifndef V4L2_CID_PAN_RESET case V4L2_CID_HCENTER: +#endif +#ifndef V4L2_CID_TILT_RESET case V4L2_CID_VCENTER: +#endif #ifdef V4L2_CID_PAN_RESET case V4L2_CID_PAN_RESET: #endif debian/patches/0001-fix-v4l2_munmap.patch0000664000000000000000000000443512106151535015153 0ustar From 4cd9255f0a8a9e15d81561f00f02d275b5095f70 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel (Alexey Fisher) Date: Thu, 01 Mar 2012 13:15:29 +0000 Subject: v4l2src: fix v4l2_munmap() for compressed formats Make sure we always call munmap() with the same size we called mmap() with before. Current v4l2src uses the same structure for VIDIOC_QUERYBUF, VIDIOC_QBUF and v4l2_munmap calls. The problem is that the video buffer size (length) may vary for compressed or emulated bufs. VIDIOC_QBUF will change it if we pass the pointer of a v4l2_buffer. This is why we should avoid using same variable for mmap and video buffers. https://bugzilla.gnome.org/show_bug.cgi?id=671126 --- (limited to 'sys/v4l2') Index: b/sys/v4l2/gstv4l2bufferpool.c =================================================================== --- a/sys/v4l2/gstv4l2bufferpool.c +++ b/sys/v4l2/gstv4l2bufferpool.c @@ -106,9 +106,9 @@ if (!resuscitated) { GST_LOG_OBJECT (pool->v4l2elem, "buffer %p (data %p, len %u) not recovered, unmapping", - buffer, GST_BUFFER_DATA (buffer), buffer->vbuffer.length); + buffer, GST_BUFFER_DATA (buffer), buffer->mmap_length); gst_mini_object_unref (GST_MINI_OBJECT (pool)); - v4l2_munmap ((void *) GST_BUFFER_DATA (buffer), buffer->vbuffer.length); + v4l2_munmap ((void *) GST_BUFFER_DATA (buffer), buffer->mmap_length); GST_MINI_OBJECT_CLASS (v4l2buffer_parent_class)->finalize (GST_MINI_OBJECT (buffer)); @@ -182,6 +182,7 @@ ret->vbuffer.m.offset); GST_LOG_OBJECT (pool->v4l2elem, " length: %u", ret->vbuffer.length); + ret->mmap_length = ret->vbuffer.length; data = (guint8 *) v4l2_mmap (0, ret->vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, pool->video_fd, ret->vbuffer.m.offset); Index: b/sys/v4l2/gstv4l2bufferpool.h =================================================================== --- a/sys/v4l2/gstv4l2bufferpool.h +++ b/sys/v4l2/gstv4l2bufferpool.h @@ -70,6 +70,9 @@ GstBuffer buffer; struct v4l2_buffer vbuffer; + /* warning: the size of mmap buffer and + * the actual frame-buffer can be different. */ + size_t mmap_length; /* FIXME: have GstV4l2Src* instead, as this has GstV4l2BufferPool* */ /* FIXME: do we really want to fix this if GstV4l2Buffer/Pool is shared debian/patches/0001-pulsesrc-Listen-to-source-output-events-not-sink-inp.patch0000664000000000000000000000165212061315514024416 0ustar From c5196f6b1bf67b21d91d87a06f07934276558d89 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Mon, 21 May 2012 11:47:07 +0200 Subject: [PATCH] pulsesrc: Listen to source output events, not sink input --- ext/pulse/pulsesrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) Index: b/ext/pulse/pulsesrc.c =================================================================== --- a/ext/pulse/pulsesrc.c +++ b/ext/pulse/pulsesrc.c @@ -1428,7 +1428,7 @@ /* enable event notifications */ GST_LOG_OBJECT (pulsesrc, "subscribing to context events"); if (!(o = pa_context_subscribe (pulsesrc->context, - PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL))) { + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, NULL, NULL))) { GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("pa_context_subscribe() failed: %s", pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); debian/patches/07_move-camerabin.patch0000664000000000000000000232767512061315514015057 0ustar Index: gst-plugins-good0.10/Makefile.am =================================================================== --- gst-plugins-good0.10.orig/Makefile.am 2012-02-09 14:44:25.708660265 +0200 +++ gst-plugins-good0.10/Makefile.am 2012-02-09 14:44:42.765169676 +0200 @@ -7,7 +7,8 @@ endif ALWAYS_SUBDIRS = \ - gst sys ext \ + gst-libs \ + gst sys ext \ farsight \ tests \ docs \ @@ -36,11 +37,6 @@ DISTCLEANFILES = _stdint.h -noinst_HEADERS = \ - gst-libs/gst/gettext.h \ - gst-libs/gst/gst-i18n-plugin.h \ - gst-libs/gst/glib-compat-private.h - ACLOCAL_AMFLAGS = -I m4 -I common/m4 include $(top_srcdir)/common/release.mak Index: gst-plugins-good0.10/configure.ac =================================================================== --- gst-plugins-good0.10.orig/configure.ac 2012-02-09 14:44:26.616687386 +0200 +++ gst-plugins-good0.10/configure.ac 2012-02-09 14:44:27.396710680 +0200 @@ -150,6 +150,9 @@ dnl check if we have ANSI C header files AC_HEADER_STDC +dnl used by camerabin +AC_CHECK_HEADERS([sys/time.h]) + dnl used by ext/wavpack AX_CREATE_STDINT_H @@ -287,6 +290,10 @@ AC_DEFINE_UNQUOTED(GST_LICENSE, "$GST_LICENSE", [GStreamer license]) AC_SUBST(GST_LICENSE) +dnl define LIBDIR so we can inform people where we live +AS_AC_EXPAND(LIBDIR, $libdir) +AC_DEFINE_UNQUOTED(LIBDIR, "$LIBDIR", [library dir]) + dnl set location of plugin directory AG_GST_SET_PLUGINDIR @@ -338,6 +345,8 @@ AG_GST_CHECK_PLUGIN(auparse) AG_GST_CHECK_PLUGIN(autodetect) AG_GST_CHECK_PLUGIN(avi) +AG_GST_CHECK_PLUGIN(camerabin) +AG_GST_CHECK_PLUGIN(camerabin2) AG_GST_CHECK_PLUGIN(cutter) AG_GST_CHECK_PLUGIN(debugutils) AG_GST_CHECK_PLUGIN(deinterlace) @@ -352,6 +361,7 @@ AG_GST_CHECK_PLUGIN(goom2k1) AG_GST_CHECK_PLUGIN(imagefreeze) AG_GST_CHECK_PLUGIN(isomp4) +AG_GST_CHECK_PLUGIN(jpegformat) AG_GST_CHECK_PLUGIN(law) AG_GST_CHECK_PLUGIN(level) AG_GST_CHECK_PLUGIN(matroska) @@ -1128,6 +1138,8 @@ gst/auparse/Makefile gst/autodetect/Makefile gst/avi/Makefile +gst/camerabin/Makefile +gst/camerabin2/Makefile gst/cutter/Makefile gst/deinterlace/Makefile gst/debugutils/Makefile @@ -1141,6 +1153,7 @@ gst/imagefreeze/Makefile gst/interleave/Makefile gst/isomp4/Makefile +gst/jpegformat/Makefile gst/law/Makefile gst/level/Makefile gst/matroska/Makefile @@ -1163,6 +1176,10 @@ gst/wavparse/Makefile gst/flx/Makefile gst/y4m/Makefile +gst-libs/Makefile +gst-libs/gst/Makefile +gst-libs/gst/basecamerabinsrc/Makefile +gst-libs/gst/interfaces/Makefile ext/Makefile ext/aalib/Makefile ext/annodex/Makefile Index: gst-plugins-good0.10/gst-libs/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/Makefile.am 2012-02-09 14:44:27.396710680 +0200 @@ -0,0 +1 @@ +SUBDIRS = gst Index: gst-plugins-good0.10/gst-libs/gst/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/Makefile.am 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,9 @@ +if BUILD_EXPERIMENTAL +EXPERIMENTAL_LIBS=basecamerabinsrc +endif + +SUBDIRS = interfaces $(EXPERIMENTAL_LIBS) + +noinst_HEADERS = gst-i18n-plugin.h gettext.h +DIST_SUBDIRS = interfaces basecamerabinsrc + Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/Makefile.am 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,39 @@ + +lib_LTLIBRARIES = libgstbasecamerabinsrc-@GST_MAJORMINOR@.la + +CLEANFILES = $(BUILT_SOURCES) + +libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_SOURCES = \ + gstcamerabin-enum.c \ + gstcamerabinpreview.c \ + gstbasecamerasrc.c + +libgstbasecamerabinsrc_@GST_MAJORMINOR@includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/basecamerabinsrc +libgstbasecamerabinsrc_@GST_MAJORMINOR@include_HEADERS = \ + gstcamerabin-enum.h \ + gstcamerabinpreview.h \ + gstbasecamerasrc.h + +libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + -DGST_USE_UNSTABLE_API \ + $(GST_CFLAGS) +libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_LIBADD = \ + -lgstapp-$(GST_MAJORMINOR) $(GST_BASE_LIBS) $(GST_LIBS) + +libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) + +Android.mk: Makefile.am + androgenizer -:PROJECT libgstbasecamerabinsrc -:STATIC libgstbasecamerabinsrc-@GST_MAJORMINOR@ \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_SOURCES) \ + -:CFLAGS $(DEFS) $(libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_CFLAGS) \ + -:LDFLAGS $(libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_LDFLAGS) \ + $(libgstbasecamerabinsrc_@GST_MAJORMINOR@_la_LIBADD) \ + -ldl \ + -:HEADER_TARGET gstreamer-@GST_MAJORMINOR@/gst/basecamerabinsrc \ + -:HEADERS $(libgstbasecamerabinsrcinclude_HEADERS) \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + > $@ Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,566 @@ +/* + * GStreamer + * Copyright (C) 2010 Texas Instruments, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + + +/** + * SECTION:element-basecamerasrc + * + * Base class for the camera source bin used by camerabin for capture. + * Sophisticated camera hardware can derive from this baseclass and map the + * features to this interface. + * + * The design mandates that the subclasses implement the following features and + * behaviour: + * + * + * 3 pads: viewfinder, image capture, video capture + * + * + * + * + * + * During construct_pipeline() vmethod a subclass can add several elements into + * the bin and expose 3 srcs pads as ghostpads implementing the 3 pad templates. + * + * It is also possible to add regular pads from the subclass and implement the + * dataflow methods on these pads. This way all functionality can be implemneted + * directly in the subclass without extra elements. + * + * The src will receive the capture mode from #GstCameraBin2 on the + * #GstBaseCameraSrc:mode property. Possible capture modes are defined in + * #GstCameraBinMode. + */ + + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gstbasecamerasrc.h" + +enum +{ + PROP_0, + PROP_MODE, + PROP_ZOOM, + PROP_MAX_ZOOM, + PROP_READY_FOR_CAPTURE, + PROP_POST_PREVIEW, + PROP_PREVIEW_CAPS, + PROP_PREVIEW_FILTER +}; + +enum +{ + /* action signals */ + START_CAPTURE_SIGNAL, + STOP_CAPTURE_SIGNAL, + /* emit signals */ + LAST_SIGNAL +}; + +#define DEFAULT_POST_PREVIEW TRUE + +static guint basecamerasrc_signals[LAST_SIGNAL]; + +GST_DEBUG_CATEGORY (base_camera_src_debug); +#define GST_CAT_DEFAULT base_camera_src_debug + +GST_BOILERPLATE (GstBaseCameraSrc, gst_base_camera_src, GstBin, GST_TYPE_BIN); + +static GstStaticPadTemplate vfsrc_template = +GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME, + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate imgsrc_template = +GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME, + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate vidsrc_template = +GST_STATIC_PAD_TEMPLATE (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME, + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +/* NOTE: we could provide a vmethod for derived class to overload to provide + * it's own implementation of interface.. but in all cases I can think of at + * moment, either the camerasrc itself, or some element within the bin, will + * be implementing the interface.. + */ + +/** + * gst_base_camera_src_set_mode: + * @self: the camerasrc bin + * @mode: the mode + * + * Set the chosen #GstCameraBinMode capture mode. + */ +gboolean +gst_base_camera_src_set_mode (GstBaseCameraSrc * self, GstCameraBinMode mode) +{ + GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self); + + g_return_val_if_fail (bclass->set_mode, FALSE); + + if (bclass->set_mode (self, mode)) { + self->mode = mode; + return TRUE; + } + return FALSE; +} + +/** + * gst_base_camera_src_setup_zoom: + * @self: camerasrc object + * + * Apply zoom configured to camerabin to capture. + */ +void +gst_base_camera_src_setup_zoom (GstBaseCameraSrc * self) +{ + GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self); + + g_return_if_fail (self->zoom); + g_return_if_fail (bclass->set_zoom); + + bclass->set_zoom (self, self->zoom); +} + +/** + * gst_base_camera_src_setup_preview: + * @self: camerasrc bin + * @preview_caps: preview caps to set + * + * Apply preview caps to preview pipeline and to video source. + */ +void +gst_base_camera_src_setup_preview (GstBaseCameraSrc * self, + GstCaps * preview_caps) +{ + GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self); + + if (self->preview_pipeline) { + GST_DEBUG_OBJECT (self, + "Setting preview pipeline caps %" GST_PTR_FORMAT, self->preview_caps); + gst_camerabin_preview_set_caps (self->preview_pipeline, preview_caps); + } + + if (bclass->set_preview) + bclass->set_preview (self, preview_caps); +} + +static void +gst_base_camera_src_start_capture (GstBaseCameraSrc * src) +{ + GstBaseCameraSrcClass *klass = GST_BASE_CAMERA_SRC_GET_CLASS (src); + + g_return_if_fail (klass->start_capture != NULL); + + GST_DEBUG_OBJECT (src, "Starting capture"); + + g_mutex_lock (src->capturing_mutex); + if (src->capturing) { + GST_WARNING_OBJECT (src, "Capturing already ongoing"); + g_mutex_unlock (src->capturing_mutex); + + /* post a warning to notify camerabin2 that the capture failed */ + GST_ELEMENT_WARNING (src, RESOURCE, BUSY, (NULL), (NULL)); + return; + } + + src->capturing = TRUE; + g_object_notify (G_OBJECT (src), "ready-for-capture"); + if (klass->start_capture (src)) { + GST_DEBUG_OBJECT (src, "Capture started"); + } else { + src->capturing = FALSE; + g_object_notify (G_OBJECT (src), "ready-for-capture"); + GST_WARNING_OBJECT (src, "Failed to start capture"); + } + g_mutex_unlock (src->capturing_mutex); +} + +static void +gst_base_camera_src_stop_capture (GstBaseCameraSrc * src) +{ + GstBaseCameraSrcClass *klass = GST_BASE_CAMERA_SRC_GET_CLASS (src); + + g_return_if_fail (klass->stop_capture != NULL); + + g_mutex_lock (src->capturing_mutex); + if (!src->capturing) { + GST_DEBUG_OBJECT (src, "No ongoing capture"); + g_mutex_unlock (src->capturing_mutex); + return; + } + klass->stop_capture (src); + g_mutex_unlock (src->capturing_mutex); +} + +void +gst_base_camera_src_finish_capture (GstBaseCameraSrc * self) +{ + GST_DEBUG_OBJECT (self, "Finishing capture"); + g_return_if_fail (self->capturing); + self->capturing = FALSE; + g_object_notify (G_OBJECT (self), "ready-for-capture"); +} + +static void +gst_base_camera_src_dispose (GObject * object) +{ + GstBaseCameraSrc *src = GST_BASE_CAMERA_SRC_CAST (object); + + g_mutex_free (src->capturing_mutex); + + if (src->preview_pipeline) { + gst_camerabin_destroy_preview_pipeline (src->preview_pipeline); + src->preview_pipeline = NULL; + } + + if (src->preview_caps) + gst_caps_replace (&src->preview_caps, NULL); + + if (src->preview_filter) { + gst_object_unref (src->preview_filter); + src->preview_filter = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_base_camera_src_finalize (GstBaseCameraSrc * self) +{ + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (self)); +} + +static void +gst_base_camera_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (object); + + switch (prop_id) { + case PROP_MODE: + gst_base_camera_src_set_mode (GST_BASE_CAMERA_SRC (self), + g_value_get_enum (value)); + break; + case PROP_ZOOM:{ + self->zoom = g_value_get_float (value); + /* limit to max-zoom */ + if (self->zoom > self->max_zoom) { + GST_DEBUG_OBJECT (self, "Clipping zoom %f to max-zoom %f", self->zoom, + self->max_zoom); + self->zoom = self->max_zoom; + } + /* does not set it if in NULL, the src is not created yet */ + if (GST_STATE (self) != GST_STATE_NULL) + gst_base_camera_src_setup_zoom (self); + break; + } + case PROP_POST_PREVIEW: + self->post_preview = g_value_get_boolean (value); + break; + case PROP_PREVIEW_CAPS:{ + GstCaps *new_caps = NULL; + new_caps = (GstCaps *) gst_value_get_caps (value); + if (!gst_caps_is_equal (self->preview_caps, new_caps)) { + gst_caps_replace (&self->preview_caps, new_caps); + gst_base_camera_src_setup_preview (self, new_caps); + } else { + GST_DEBUG_OBJECT (self, "New preview caps equal current preview caps"); + } + } + break; + case PROP_PREVIEW_FILTER: + if (self->preview_filter) + gst_object_unref (self->preview_filter); + self->preview_filter = g_value_dup_object (value); + if (!gst_camerabin_preview_set_filter (self->preview_pipeline, + self->preview_filter)) { + GST_WARNING_OBJECT (self, + "Cannot change preview filter, is element in NULL state?"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static void +gst_base_camera_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum (value, self->mode); + break; + case PROP_READY_FOR_CAPTURE: + g_value_set_boolean (value, !self->capturing); + break; + case PROP_ZOOM: + g_value_set_float (value, self->zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, self->max_zoom); + break; + case PROP_POST_PREVIEW: + g_value_set_boolean (value, self->post_preview); + break; + case PROP_PREVIEW_CAPS: + if (self->preview_caps) + gst_value_set_caps (value, self->preview_caps); + break; + case PROP_PREVIEW_FILTER: + if (self->preview_filter) + g_value_set_object (value, self->preview_filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static gboolean +construct_pipeline (GstBaseCameraSrc * self) +{ + GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self); + + if (bclass->construct_pipeline) { + if (!bclass->construct_pipeline (self)) { + GST_ERROR_OBJECT (self, "pipeline construction failed"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +setup_pipeline (GstBaseCameraSrc * self) +{ + GstBaseCameraSrcClass *bclass = GST_BASE_CAMERA_SRC_GET_CLASS (self); + if (bclass->setup_pipeline) + return bclass->setup_pipeline (self); + return TRUE; +} + +static GstStateChangeReturn +gst_base_camera_src_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstBaseCameraSrc *self = GST_BASE_CAMERA_SRC (element); + + GST_DEBUG_OBJECT (self, "%d -> %d", + GST_STATE_TRANSITION_CURRENT (transition), + GST_STATE_TRANSITION_NEXT (transition)); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!construct_pipeline (self)) + return GST_STATE_CHANGE_FAILURE; + + if (self->preview_pipeline == NULL) { + /* failed to create preview pipeline, fail state change */ + return GST_STATE_CHANGE_FAILURE; + } + + if (self->preview_caps) { + GST_DEBUG_OBJECT (self, + "Setting preview pipeline caps %" GST_PTR_FORMAT, + self->preview_caps); + gst_camerabin_preview_set_caps (self->preview_pipeline, + self->preview_caps); + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (!setup_pipeline (self)) + return GST_STATE_CHANGE_FAILURE; + /* without this the preview pipeline will not post buffer + * messages on the pipeline */ + gst_element_set_state (self->preview_pipeline->pipeline, + GST_STATE_PLAYING); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_element_set_state (self->preview_pipeline->pipeline, GST_STATE_READY); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_element_set_state (self->preview_pipeline->pipeline, GST_STATE_NULL); + break; + default: + break; + } + + return ret; +} + +static void +gst_base_camera_src_base_init (gpointer g_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + GST_DEBUG_CATEGORY_INIT (base_camera_src_debug, "base_camera_src", 0, + "Base camera src"); + + gst_element_class_set_details_simple (gstelement_class, + "Base class for camerabin src bin", "Source/Video", + "Abstracts capture device for camerabin2", "Rob Clark "); + + gst_element_class_add_static_pad_template (gstelement_class, &vfsrc_template); + + gst_element_class_add_static_pad_template (gstelement_class, + &imgsrc_template); + + gst_element_class_add_static_pad_template (gstelement_class, + &vidsrc_template); +} + +static void +gst_base_camera_src_class_init (GstBaseCameraSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->dispose = gst_base_camera_src_dispose; + gobject_class->finalize = (GObjectFinalizeFunc) gst_base_camera_src_finalize; + gobject_class->set_property = gst_base_camera_src_set_property; + gobject_class->get_property = gst_base_camera_src_get_property; + + g_object_class_install_property (gobject_class, PROP_MODE, + g_param_spec_enum ("mode", "Mode", + "The capture mode (still image capture or video recording)", + GST_TYPE_CAMERABIN_MODE, MODE_IMAGE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_ZOOM, + g_param_spec_float ("zoom", "Zoom", + "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT, + DEFAULT_ZOOM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", "Maximum zoom level (note: may change " + "depending on resolution/implementation)", + "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT, + MAX_ZOOM, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstBaseCameraSrc:post-previews: + * + * When %TRUE, preview images should be posted to the bus when + * captures are made + */ + g_object_class_install_property (gobject_class, PROP_POST_PREVIEW, + g_param_spec_boolean ("post-previews", "Post Previews", + "If capture preview images should be posted to the bus", + DEFAULT_POST_PREVIEW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PREVIEW_CAPS, + g_param_spec_boxed ("preview-caps", "Preview caps", + "The caps of the preview image to be posted", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_PREVIEW_FILTER, + g_param_spec_object ("preview-filter", "Preview filter", + "A custom preview filter to process preview image data", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstBaseCameraSrc:ready-for-capture: + * + * When TRUE new capture can be prepared. If FALSE capturing is ongoing + * and starting a new capture immediately is not possible. + * + * Note that calling start-capture from the notify callback of this property + * will cause a deadlock. If you need to react like this on the notify + * function, please schedule a new thread to do it. If you're using glib's + * mainloop you can use g_idle_add() for example. + */ + g_object_class_install_property (gobject_class, PROP_READY_FOR_CAPTURE, + g_param_spec_boolean ("ready-for-capture", "Ready for capture", + "Informs this element is ready for starting another capture", + TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + + /* Signals */ + basecamerasrc_signals[START_CAPTURE_SIGNAL] = + g_signal_new_class_handler ("start-capture", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_base_camera_src_start_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + basecamerasrc_signals[STOP_CAPTURE_SIGNAL] = + g_signal_new_class_handler ("stop-capture", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (gst_base_camera_src_stop_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + gstelement_class->change_state = gst_base_camera_src_change_state; +} + +static void +gst_base_camera_src_init (GstBaseCameraSrc * self, + GstBaseCameraSrcClass * klass) +{ + self->width = DEFAULT_WIDTH; + self->height = DEFAULT_HEIGHT; + self->zoom = DEFAULT_ZOOM; + self->max_zoom = MAX_ZOOM; + self->mode = MODE_IMAGE; + + self->capturing = FALSE; + self->capturing_mutex = g_mutex_new (); + + self->post_preview = DEFAULT_POST_PREVIEW; + + self->preview_pipeline = + gst_camerabin_create_preview_pipeline (GST_ELEMENT_CAST (self), NULL); +} + +void +gst_base_camera_src_post_preview (GstBaseCameraSrc * self, GstBuffer * buf) +{ + if (self->post_preview) { + gst_camerabin_preview_pipeline_post (self->preview_pipeline, buf); + } else { + GST_DEBUG_OBJECT (self, "Previews not enabled, not posting"); + } +} Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstbasecamerasrc.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,142 @@ +/* + * GStreamer + * Copyright (C) 2010 Texas Instruments, Inc + * Copyright (C) 2011 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_BASE_CAMERA_SRC_H__ +#define __GST_BASE_CAMERA_SRC_H__ + +#ifndef GST_USE_UNSTABLE_API +#warning "GstBaseCameraSrc is unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include +#include +#include "gstcamerabin-enum.h" +#include "gstcamerabinpreview.h" + +G_BEGIN_DECLS +#define GST_TYPE_BASE_CAMERA_SRC \ + (gst_base_camera_src_get_type()) +#define GST_BASE_CAMERA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASE_CAMERA_SRC,GstBaseCameraSrc)) +#define GST_BASE_CAMERA_SRC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BASE_CAMERA_SRC, GstBaseCameraSrcClass)) +#define GST_BASE_CAMERA_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASE_CAMERA_SRC,GstBaseCameraSrcClass)) +#define GST_IS_BASE_CAMERA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASE_CAMERA_SRC)) +#define GST_IS_BASE_CAMERA_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_CAMERA_SRC)) +#define GST_BASE_CAMERA_SRC_CAST(obj) \ + ((GstBaseCameraSrc *) (obj)) +GType gst_base_camera_src_get_type (void); + +typedef struct _GstBaseCameraSrc GstBaseCameraSrc; +typedef struct _GstBaseCameraSrcClass GstBaseCameraSrcClass; + +#define GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME "vfsrc" +#define GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME "imgsrc" +#define GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME "vidsrc" + +#define GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME "preview-image" + +/** + * GstBaseCameraSrc: + */ +struct _GstBaseCameraSrc +{ + GstBin parent; + + GstCameraBinMode mode; + + gboolean capturing; + GMutex *capturing_mutex; + + /* Preview convert pipeline */ + GstCaps *preview_caps; + gboolean post_preview; + GstElement *preview_filter; + GstCameraBinPreviewPipelineData *preview_pipeline; + + /* Resolution of the buffers configured to camerabin */ + gint width; + gint height; + + gfloat zoom; + gfloat max_zoom; + + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + + +/** + * GstBaseCameraSrcClass: + * @construct_pipeline: construct pipeline + * @setup_pipeline: configure pipeline for the chosen settings + * @set_zoom: set the zoom + * @set_mode: set the mode + */ +struct _GstBaseCameraSrcClass +{ + GstBinClass parent; + + /* Construct pipeline. (called in GST_STATE_CHANGE_NULL_TO_READY) Optional. */ + gboolean (*construct_pipeline) (GstBaseCameraSrc *self); + + /* (called in GST_STATE_CHANGE_READY_TO_PAUSED). Optional. */ + gboolean (*setup_pipeline) (GstBaseCameraSrc *self); + + /* Set the zoom. If set, called when changing 'zoom' property. Optional. */ + void (*set_zoom) (GstBaseCameraSrc *self, gfloat zoom); + + /* Set the mode. If set, called when changing 'mode' property. Optional. */ + gboolean (*set_mode) (GstBaseCameraSrc *self, + GstCameraBinMode mode); + + /* Set preview caps. If set, called called when setting new 'preview-caps'. Optional. */ + gboolean (*set_preview) (GstBaseCameraSrc *self, + GstCaps *preview_caps); + + /* Called by the handler for 'start-capture'. Mandatory. */ + gboolean (*start_capture) (GstBaseCameraSrc * src); + + /* Called by the handler for 'stop-capture'. Mandatory. */ + void (*stop_capture) (GstBaseCameraSrc * src); + + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + + +#define MIN_ZOOM 1.0f +#define MAX_ZOOM 10.0f +#define ZOOM_1X MIN_ZOOM + +gboolean gst_base_camera_src_set_mode (GstBaseCameraSrc *self, GstCameraBinMode mode); +void gst_base_camera_src_setup_zoom (GstBaseCameraSrc * self); +void gst_base_camera_src_setup_preview (GstBaseCameraSrc * self, GstCaps * preview_caps); +void gst_base_camera_src_finish_capture (GstBaseCameraSrc *self); + + +void gst_base_camera_src_post_preview (GstBaseCameraSrc *self, GstBuffer * buf); +// XXX add methods to get/set img capture and vid capture caps.. + +#endif /* __GST_BASE_CAMERA_SRC_H__ */ Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,39 @@ +/* + * GStreamer + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gstcamerabin-enum.h" + +GType +gst_camerabin_mode_get_type (void) +{ + static GType gtype = 0; + + if (gtype == 0) { + static const GEnumValue values[] = { + /* {MODE_PREVIEW, "Preview mode (should be default?)", "mode-preview"}, */ + {MODE_IMAGE, "Still image capture (default)", "mode-image"}, + {MODE_VIDEO, "Video recording", "mode-video"}, + {0, NULL, NULL} + }; + + gtype = g_enum_register_static ("GstCameraBin2Mode", values); + } + return gtype; +} Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabin-enum.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,62 @@ +/* + * GStreamer + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_CAMERABIN_ENUM_H__ +#define __GST_CAMERABIN_ENUM_H__ + +#ifndef GST_USE_UNSTABLE_API +#warning "camerabin enums are unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include + +G_BEGIN_DECLS + +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_CAPTURE_WIDTH 800 +#define DEFAULT_CAPTURE_HEIGHT 600 +#define DEFAULT_FPS_N 0 /* makes it use the default */ +#define DEFAULT_FPS_D 1 +#define DEFAULT_ZOOM MIN_ZOOM + + +/** + * GstCameraBinMode: + * @MODE_IMAGE: image capture + * @MODE_VIDEO: video capture + * + * Capture mode to use. + */ +typedef enum +{ + /* MODE_PREVIEW = 0, No use for this */ + MODE_IMAGE = 1, + MODE_VIDEO = 2, +} GstCameraBinMode; + + +#define GST_TYPE_CAMERABIN_MODE (gst_camerabin_mode_get_type ()) +GType gst_camerabin_mode_get_type (void); + +G_END_DECLS + +#endif /* #ifndef __GST_CAMERABIN_ENUM_H__ */ Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,404 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:camerabingeneral + * @short_description: helper functions for #GstCameraBin and it's modules + * + * Common helper functions for #GstCameraBin, #GstCameraBinImage and + * #GstCameraBinVideo. + * + */ +#include +#include +#include "gstcamerabinpreview.h" +#include "gstbasecamerasrc.h" + +GST_DEBUG_CATEGORY_EXTERN (base_camera_src_debug); +#define GST_CAT_DEFAULT base_camera_src_debug + +static void _gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * + preview, GstCaps * caps); + +static gboolean +bus_callback (GstBus * bus, GstMessage * message, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ERROR:{ + GError *err; + GstCameraBinPreviewPipelineData *data; + + data = user_data; + + gst_message_parse_error (message, &err, NULL); + GST_WARNING ("Error from preview pipeline: %s", err->message); + g_error_free (err); + + /* TODO Not sure if we should post an Error or Warning here */ + GST_ELEMENT_ERROR (data, CORE, FAILED, + ("fatal error in preview pipeline, disposing the pipeline"), (NULL)); + + /* Possible error situations: + * 1) cond_wait pending. prevent deadlock by signalling the cond + * 2) preview_pipeline_post called with new buffer to handle. returns + * because data->pipeline is set to null + * 3) new preview caps incoming. returns because data->pipeline is null + */ + + if (data->pipeline) { + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->pipeline); + data->pipeline = NULL; + } + + g_cond_signal (data->processing_cond); + + break; + } + default: + break; + } + return TRUE; +} + +static GstFlowReturn +gst_camerabin_preview_pipeline_new_buffer (GstAppSink * appsink, + gpointer user_data) +{ + GstBuffer *buffer; + GstStructure *s; + GstMessage *msg; + GstCameraBinPreviewPipelineData *data; + + data = user_data; + + buffer = gst_app_sink_pull_buffer (appsink); + s = gst_structure_new (GST_BASE_CAMERA_SRC_PREVIEW_MESSAGE_NAME, + "buffer", GST_TYPE_BUFFER, buffer, NULL); + gst_buffer_unref (buffer); + msg = gst_message_new_element (GST_OBJECT (data->element), s); + + GST_DEBUG_OBJECT (data->element, "sending message with preview image"); + if (gst_element_post_message (data->element, msg) == FALSE) { + GST_WARNING_OBJECT (data->element, + "This element has no bus, therefore no message sent!"); + } + + g_mutex_lock (data->processing_lock); + + data->processing--; + if (data->processing == 0) + g_cond_signal (data->processing_cond); + + g_mutex_unlock (data->processing_lock); + + return GST_FLOW_OK; +} + +/** + * gst_camerabin_create_preview_pipeline: + * @element: Owner of this pipeline + * @filter: Custom filter to process preview data (an extra ref is taken) + * + * Creates a new previewing pipeline that can receive buffers + * to be posted as camerabin preview messages for @element + * + * Returns: The newly created #GstCameraBinPreviewPipelineData + */ +GstCameraBinPreviewPipelineData * +gst_camerabin_create_preview_pipeline (GstElement * element, + GstElement * filter) +{ + GstCameraBinPreviewPipelineData *data; + GstElement *csp; + GstElement *vscale; + gboolean added = FALSE; + gboolean linkfail = FALSE; + GstBus *bus; + GstAppSinkCallbacks callbacks = { 0, }; + + data = g_new0 (GstCameraBinPreviewPipelineData, 1); + + data->pipeline = gst_pipeline_new ("preview-pipeline"); + data->appsrc = gst_element_factory_make ("appsrc", "preview-appsrc"); + data->appsink = gst_element_factory_make ("appsink", "preview-appsink"); + csp = gst_element_factory_make ("ffmpegcolorspace", "preview-csp"); + vscale = gst_element_factory_make ("videoscale", "preview-vscale"); + + if (!data->appsrc || !data->appsink || !csp || !vscale) { + goto error; + } + + g_object_set (data->appsrc, "emit-signals", FALSE, NULL); + g_object_set (data->appsink, "sync", FALSE, "enable-last-buffer", + FALSE, NULL); + + gst_bin_add_many (GST_BIN (data->pipeline), data->appsrc, + data->appsink, csp, vscale, NULL); + if (filter) + gst_bin_add (GST_BIN (data->pipeline), gst_object_ref (filter)); + added = TRUE; + + if (filter) { + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src", + filter, NULL, GST_PAD_LINK_CHECK_NOTHING)); + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL, + vscale, "sink", GST_PAD_LINK_CHECK_CAPS)); + } else { + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (data->appsrc, "src", + vscale, "sink", GST_PAD_LINK_CHECK_NOTHING)); + } + + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (vscale, "src", csp, + "sink", GST_PAD_LINK_CHECK_NOTHING)); + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (csp, "src", + data->appsink, "sink", GST_PAD_LINK_CHECK_NOTHING)); + + if (linkfail) { + GST_WARNING ("Failed to link preview pipeline elements"); + goto error; + } + + callbacks.new_buffer = gst_camerabin_preview_pipeline_new_buffer; + gst_app_sink_set_callbacks ((GstAppSink *) data->appsink, &callbacks, data, + NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline)); + gst_bus_add_watch (bus, bus_callback, data); + gst_object_unref (bus); + + g_object_set (data->appsink, "sync", FALSE, NULL); + + data->element = element; + data->filter = filter; + data->vscale = vscale; + + data->processing_lock = g_mutex_new (); + data->processing_cond = g_cond_new (); + + data->pending_preview_caps = NULL; + data->processing = 0; + + return data; +error: + GST_WARNING ("Failed to create camerabin's preview pipeline"); + if (!added) { + if (csp) + gst_object_unref (csp); + if (vscale) + gst_object_unref (vscale); + if (data->appsrc) + gst_object_unref (data->appsrc); + if (data->appsink) + gst_object_unref (data->appsink); + } + gst_camerabin_destroy_preview_pipeline (data); + return NULL; +} + +/** + * gst_camerabin_destroy_preview_pipeline: + * @preview: the #GstCameraBinPreviewPipelineData + * + * Frees a #GstCameraBinPreviewPipelineData + */ +void +gst_camerabin_destroy_preview_pipeline (GstCameraBinPreviewPipelineData * + preview) +{ + g_return_if_fail (preview != NULL); + + if (preview->processing_lock) { + g_mutex_free (preview->processing_lock); + preview->processing_lock = NULL; + } + if (preview->processing_cond) { + g_cond_free (preview->processing_cond); + preview->processing_cond = NULL; + } + if (preview->pipeline) { + gst_element_set_state (preview->pipeline, GST_STATE_NULL); + gst_object_unref (preview->pipeline); + } + g_free (preview); +} + +/** + * gst_camerabin_preview_pipeline_post: + * @preview: the #GstCameraBinPreviewPipelineData + * @buffer: the buffer to be posted as a preview + * + * Converts the @buffer to the desired format and posts the preview + * message to the bus. + * + * Returns: %TRUE on success + */ +gboolean +gst_camerabin_preview_pipeline_post (GstCameraBinPreviewPipelineData * preview, + GstBuffer * buffer) +{ + g_return_val_if_fail (preview != NULL, FALSE); + g_return_val_if_fail (preview->pipeline != NULL, FALSE); + g_return_val_if_fail (buffer, FALSE); + + g_mutex_lock (preview->processing_lock); + g_return_val_if_fail (preview->pipeline != NULL, FALSE); + + if (preview->pending_preview_caps) { + if (preview->processing > 0) { + g_cond_wait (preview->processing_cond, preview->processing_lock); + } + _gst_camerabin_preview_set_caps (preview, preview->pending_preview_caps); + gst_caps_replace (&preview->pending_preview_caps, NULL); + } + + preview->processing++; + + gst_app_src_push_buffer ((GstAppSrc *) preview->appsrc, + gst_buffer_ref (buffer)); + + g_mutex_unlock (preview->processing_lock); + + return TRUE; +} + +static void +_gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview, + GstCaps * caps) +{ + GstState state, pending; + GstStateChangeReturn ret; + + g_return_if_fail (preview != NULL); + g_return_if_fail (preview->pipeline != NULL); + + ret = gst_element_get_state (preview->pipeline, &state, &pending, 0); + if (ret == GST_STATE_CHANGE_FAILURE) { + /* make it try again */ + state = GST_STATE_PLAYING; + pending = GST_STATE_VOID_PENDING; + } + gst_element_set_state (preview->pipeline, GST_STATE_NULL); + g_object_set (preview->appsink, "caps", caps, NULL); + if (pending != GST_STATE_VOID_PENDING) + state = pending; + gst_element_set_state (preview->pipeline, state); +} + +/** + * gst_camerabin_preview_set_caps: + * @preview: the #GstCameraBinPreviewPipelineData + * @caps: the #GstCaps to be set (a new ref will be taken) + * + * The caps that preview buffers should have when posted + * on the bus + */ +void +gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview, + GstCaps * caps) +{ + g_return_if_fail (preview != NULL); + + g_mutex_lock (preview->processing_lock); + + if (preview->processing == 0) { + _gst_camerabin_preview_set_caps (preview, caps); + } else { + GST_DEBUG ("Preview pipeline busy, storing new caps as pending"); + gst_caps_replace (&preview->pending_preview_caps, caps); + } + g_mutex_unlock (preview->processing_lock); +} + +/** + * gst_camerabin_preview_set_filter: + * @preview: the #GstCameraBinPreviewPipelineData + * @filter: Custom filter to process preview data (an extra ref is taken) + * + * Set the filter element into preview pipeline. + * + * Returns: %TRUE on success + */ +gboolean +gst_camerabin_preview_set_filter (GstCameraBinPreviewPipelineData * preview, + GstElement * filter) +{ + gboolean ret = TRUE; + GstState current; + + g_return_val_if_fail (preview != NULL, FALSE); + + GST_DEBUG ("Preview pipeline setting new filter %p", filter); + + g_mutex_lock (preview->processing_lock); + + gst_element_get_state (preview->pipeline, ¤t, NULL, 0); + + if (preview->processing == 0 && current == GST_STATE_NULL) { + gboolean linkfail = FALSE; + + if (preview->filter) { + /* Unlink and remove old filter */ + gst_element_unlink (preview->appsrc, preview->filter); + gst_element_unlink (preview->filter, preview->vscale); + gst_bin_remove (GST_BIN (preview->pipeline), preview->filter); + } else { + /* Make room for filter by breaking the link between appsrc and vcale */ + gst_element_unlink (preview->appsrc, preview->vscale); + } + + if (filter) { + /* Add and link the new filter between appsrc and vscale */ + gst_bin_add (GST_BIN (preview->pipeline), gst_object_ref (filter)); + + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc, + "src", filter, NULL, GST_PAD_LINK_CHECK_NOTHING)); + + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (filter, NULL, + preview->vscale, "sink", GST_PAD_LINK_CHECK_CAPS)); + } else { + /* No filter was given. Just link the appsrc to vscale directly */ + linkfail |= + GST_PAD_LINK_FAILED (gst_element_link_pads_full (preview->appsrc, + "src", preview->vscale, "sink", GST_PAD_LINK_CHECK_NOTHING)); + } + + if (linkfail) { + GST_WARNING ("Linking the filter to pipeline failed"); + ret = FALSE; + } else { + GST_DEBUG ("Linking the filter to pipeline successful"); + preview->filter = filter; + } + } else { + GST_WARNING ("Cannot change filter when pipeline is running"); + ret = FALSE; + } + g_mutex_unlock (preview->processing_lock); + + return ret; +} Index: gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/basecamerabinsrc/gstcamerabinpreview.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,56 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_PREVIEW_H_ +#define __CAMERABIN_PREVIEW_H_ + +#ifndef GST_USE_UNSTABLE_API +#warning "camera bin preview is unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include + +typedef struct +{ + GstElement *pipeline; + + GstElement *appsrc; + GstElement *filter; + GstElement *appsink; + GstElement *vscale; + + GstElement *element; + + GstCaps *pending_preview_caps; + guint processing; + GMutex *processing_lock; + GCond *processing_cond; + +} GstCameraBinPreviewPipelineData; + +GstCameraBinPreviewPipelineData *gst_camerabin_create_preview_pipeline (GstElement * element, GstElement * filter); +void gst_camerabin_destroy_preview_pipeline (GstCameraBinPreviewPipelineData * preview); +gboolean gst_camerabin_preview_pipeline_post (GstCameraBinPreviewPipelineData * preview, GstBuffer * buffer); +void gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * preview, GstCaps * caps); +gboolean gst_camerabin_preview_set_filter (GstCameraBinPreviewPipelineData * preview, GstElement * filter); + +#endif /* #ifndef __CAMERABIN_PREVIEW_H_ */ Index: gst-plugins-good0.10/gst-libs/gst/interfaces/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/interfaces/Makefile.am 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,61 @@ +lib_LTLIBRARIES = libgstphotography-@GST_MAJORMINOR@.la +libgstphotographyincludedir = \ + $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/interfaces + +headers_photography = \ + photography.h + +# variables used for enum/marshal generation +glib_enum_headers=$(headers_photography) +glib_enum_define=GST_PHOTOGRAPHY +glib_gen_prefix=gst_photography +glib_gen_basename=photography + +built_sources = \ + photography-enumtypes.c + +built_headers = \ + photography-enumtypes.h + +libgstphotographyinclude_HEADERS = \ + $(headers_photography) + +nodist_libgstphotographyinclude_HEADERS = \ + photography-enumtypes.h + +libgstphotography_@GST_MAJORMINOR@_la_SOURCES = \ + photography.c + +nodist_libgstphotography_@GST_MAJORMINOR@_la_SOURCES = \ + $(built_sources) + +libgstphotography_@GST_MAJORMINOR@_la_CFLAGS = \ + -DGST_USE_UNSTABLE_API \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_CFLAGS) +libgstphotography_@GST_MAJORMINOR@_la_LIBADD = $(GST_LIBS) +libgstphotography_@GST_MAJORMINOR@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) + +BUILT_SOURCES = \ + $(built_sources) \ + $(built_headers) + +CLEANFILES = $(BUILT_SOURCES) + +include $(top_srcdir)/common/gst-glib-gen.mak + +Android.mk: $(BUILT_SOURCES) Makefile.am + androgenizer -:PROJECT libgstphotography -:STATIC libgstphotography-@GST_MAJORMINOR@ \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstphotography_@GST_MAJORMINOR@_la_SOURCES) \ + $(built_sources) \ + -:CFLAGS $(DEFS) $(libgstphotography_@GST_MAJORMINOR@_la_CFLAGS) \ + -:LDFLAGS $(libgstphotography_@GST_MAJORMINOR@_la_LDFLAGS) \ + $(libgstphotography_@GST_MAJORMINOR@_la_LIBADD) \ + -ldl \ + -:HEADER_TARGET gstreamer-@GST_MAJORMINOR@/gst/photography \ + -:HEADERS $(libgstphotographyinclude_HEADERS) \ + $(built_headers) \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + > $@ Index: gst-plugins-good0.10/gst-libs/gst/interfaces/photography.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/interfaces/photography.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,624 @@ +/* GStreamer + * + * Copyright (C) 2008 Nokia Corporation + * + * photography.c: photography interface for digital imaging + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "photography.h" + +/** + * SECTION:gstphotography + * @short_description: Interface for digital image capture elements + * + * The interface allows access to some common digital image capture parameters. + * + * + * The GstPhotography interface is unstable API and may change in future. + * One can define GST_USE_UNSTABLE_API to acknowledge and avoid this warning. + * + */ + +static void gst_photography_iface_base_init (GstPhotographyInterface * iface); +static void gst_photography_iface_class_init (gpointer g_class); + +GType +gst_photography_get_type (void) +{ + static GType gst_photography_type = 0; + + if (!gst_photography_type) { + static const GTypeInfo gst_photography_info = { + sizeof (GstPhotographyInterface), + (GBaseInitFunc) gst_photography_iface_base_init, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gst_photography_iface_class_init, /* class_init */ + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL, /* instance_init */ + }; + + gst_photography_type = g_type_register_static (G_TYPE_INTERFACE, + "GstPhotography", &gst_photography_info, 0); + g_type_interface_add_prerequisite (gst_photography_type, + GST_TYPE_IMPLEMENTS_INTERFACE); + } + + return gst_photography_type; +} + +static void +gst_photography_iface_base_init (GstPhotographyInterface * iface) +{ + /* default virtual functions */ + iface->get_ev_compensation = NULL; + iface->get_iso_speed = NULL; + iface->get_aperture = NULL; + iface->get_exposure = NULL; + iface->get_white_balance_mode = NULL; + iface->get_colour_tone_mode = NULL; + iface->get_scene_mode = NULL; + iface->get_flash_mode = NULL; + iface->get_noise_reduction = NULL; + iface->get_zoom = NULL; + iface->get_flicker_mode = NULL; + iface->get_focus_mode = NULL; + + iface->set_ev_compensation = NULL; + iface->set_iso_speed = NULL; + iface->set_aperture = NULL; + iface->set_exposure = NULL; + iface->set_white_balance_mode = NULL; + iface->set_colour_tone_mode = NULL; + iface->set_scene_mode = NULL; + iface->set_flash_mode = NULL; + iface->set_noise_reduction = NULL; + iface->set_zoom = NULL; + iface->set_flicker_mode = NULL; + iface->set_focus_mode = NULL; + + iface->get_capabilities = NULL; + iface->prepare_for_capture = NULL; + iface->set_autofocus = NULL; + iface->set_config = NULL; + iface->get_config = NULL; +} + +#define GST_PHOTOGRAPHY_FUNC_TEMPLATE(function_name, param_type) \ +gboolean \ +gst_photography_set_ ## function_name (GstPhotography * photo, param_type param) \ +{ \ + GstPhotographyInterface *iface; \ + g_return_val_if_fail (photo != NULL, FALSE); \ + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); \ + if (iface->set_ ## function_name) { \ + return iface->set_ ## function_name (photo, param); \ + } \ + return FALSE; \ +} \ +gboolean \ +gst_photography_get_ ## function_name (GstPhotography * photo, param_type * param) \ +{ \ + GstPhotographyInterface *iface; \ + g_return_val_if_fail (photo != NULL, FALSE); \ + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); \ + if (iface->get_ ## function_name) { \ + return iface->get_ ## function_name (photo, param); \ + } \ + return FALSE; \ +} + + +/** + * gst_photography_set_ev_compensation: + * @photo: #GstPhotography interface of a #GstElement + * @ev_comp: ev compensation value to set + * + * Set the ev compensation value for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_ev_compensation: + * @photo: #GstPhotography interface of a #GstElement + * @ev_comp: ev compensation value to get + * + * Get the ev compensation value for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (ev_compensation, gfloat); + +/** + * gst_photography_set_iso_speed: + * @photo: #GstPhotography interface of a #GstElement + * @iso_speed: ISO speed value to set + * + * Set the ISO value (light sensivity) for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_iso_speed: + * @photo: #GstPhotography interface of a #GstElement + * @iso_speed: ISO speed value to get + * + * Get the ISO value (light sensivity) for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (iso_speed, guint); + +/** + * gst_photography_set_aperture: + * @photo: #GstPhotography interface of a #GstElement + * @aperture: aperture value to set + * + * Set the aperture value for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_aperture: + * @photo: #GstPhotography interface of a #GstElement + * @aperture: aperture value to get + * + * Get the aperture value for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (aperture, guint); + +/** + * gst_photography_set_exposure: + * @photo: #GstPhotography interface of a #GstElement + * @exposure: exposure time to set + * + * Set the fixed exposure time (in us) for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_exposure: + * @photo: #GstPhotography interface of a #GstElement + * @exposure: exposure time to get + * + * Get the fixed exposure time (in us) for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (exposure, guint32); + +/** + * gst_photography_set_white_balance_mode: + * @photo: #GstPhotography interface of a #GstElement + * @wb_mode: #GstWhiteBalanceMode to set + * + * Set the white balance mode for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_white_balance_mode: + * @photo: #GstPhotography interface of a #GstElement + * @wb_mode: #GstWhiteBalanceMode to get + * + * Get the white balance mode for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (white_balance_mode, GstWhiteBalanceMode); + +/** + * gst_photography_set_colour_tone_mode: + * @photo: #GstPhotography interface of a #GstElement + * @tone_mode: #GstColourToneMode to set + * + * Set the colour tone mode for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_colour_tone_mode: + * @photo: #GstPhotography interface of a #GstElement + * @tone_mode: #GstColourToneMode to get + * + * Get the colour tone mode for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (colour_tone_mode, GstColourToneMode); + +/** + * gst_photography_set_scene_mode: + * @photo: #GstPhotography interface of a #GstElement + * @scene_mode: #GstSceneMode to set + * + * Set the scene mode for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_scene_mode: + * @photo: #GstPhotography interface of a #GstElement + * @scene_mode: #GstSceneMode to get + * + * Get the scene mode for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (scene_mode, GstSceneMode); + +/** + * gst_photography_set_flash_mode: + * @photo: #GstPhotography interface of a #GstElement + * @flash_mode: #GstFlashMode to set + * + * Set the flash mode for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_flash_mode: + * @photo: #GstPhotography interface of a #GstElement + * @flash_mode: #GstFlashMode to get + * + * Get the flash mode for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (flash_mode, GstFlashMode); + +/** + * gst_photography_set_noise_reduction: + * @photo: #GstPhotography interface of a #GstElement + * @noise_reduction: #GstNoiseReductionMode to set + * + * Set the noise reduction mode for the #GstElement + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + * + * Since: 0.10.21 + */ +/** + * gst_photography_get_noise_reduction: + * @photo: #GstPhotography interface of a #GstElement + * @noise_reduction: #GstNoiseReductionMode to get + * + * Get the noise reduction mode for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + * + * Since: 0.10.21 + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (noise_reduction, GstPhotographyNoiseReduction); + +/** + * gst_photography_set_zoom: + * @photo: #GstPhotography interface of a #GstElement + * @zoom: zoom value to set + * + * Set the zoom value for the #GstElement. + * E.g. 1.0 to get original image and 3.0 for 3x zoom and so on. + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_zoom: + * @photo: #GstPhotography interface of a #GstElement + * @zoom: zoom value to get + * + * Get the zoom value for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (zoom, gfloat); + +/** + * gst_photography_set_flicker_mode: + * @photo: #GstPhotography interface of a #GstElement + * @flicker_mode: flicker mode value to set + * + * Set the flicker mode value for the #GstElement. + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_flicker_mode: + * @photo: #GstPhotography interface of a #GstElement + * @flicker_mode: flicker mode value to get + * + * Get the flicker mode value for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (flicker_mode, GstFlickerReductionMode); + +/** + * gst_photography_set_focus_mode: + * @photo: #GstPhotography interface of a #GstElement + * @focus_mode: focus mode value to set + * + * Set the focus mode value for the #GstElement. + * + * Returns: %TRUE if setting the value succeeded, %FALSE otherwise + */ +/** + * gst_photography_get_focus_mode: + * @photo: #GstPhotography interface of a #GstElement + * @focus_mode: focus_mode value to get + * + * Get the focus mode value for the #GstElement + * + * Returns: %TRUE if getting the value succeeded, %FALSE otherwise + */ +GST_PHOTOGRAPHY_FUNC_TEMPLATE (focus_mode, GstFocusMode); + +/** + * gst_photography_get_capabilities: + * @photo: #GstPhotography interface of a #GstElement + * + * Get #GstPhotoCaps bitmask value that indicates what photography + * interface features the #GstElement supports + * + * Returns: #GstPhotoCaps value + */ +GstPhotoCaps +gst_photography_get_capabilities (GstPhotography * photo) +{ + GstPhotographyInterface *iface; + g_return_val_if_fail (photo != NULL, GST_PHOTOGRAPHY_CAPS_NONE); + + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); + if (iface->get_capabilities) { + return iface->get_capabilities (photo); + } else { + return GST_PHOTOGRAPHY_CAPS_NONE; + } +} + +/** + * gst_photography_prepare_for_capture: + * @photo: #GstPhotography interface of a #GstElement + * @func: callback that is called after capturing has been prepared + * @caps: #GstCaps defining the desired format of the captured image + * @user_data: user data that will be passed to the callback @func + * + * Start preparations for capture. @func callback is called after + * preparations are done. + * + * Returns: TRUE if preparations were started (caps were OK), otherwise FALSE. + */ +gboolean +gst_photography_prepare_for_capture (GstPhotography * photo, + GstPhotoCapturePrepared func, GstCaps * capture_caps, gpointer user_data) +{ + GstPhotographyInterface *iface; + gboolean ret = TRUE; + + g_return_val_if_fail (photo != NULL, FALSE); + + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); + if (iface->prepare_for_capture) { + ret = iface->prepare_for_capture (photo, func, capture_caps, user_data); + } + + return ret; +} + +/** + * gst_photography_set_autofocus: + * @photo: #GstPhotography interface of a #GstElement + * @on: %TRUE to start autofocusing, %FALSE to stop autofocusing + * + * Start or stop autofocusing. %GST_PHOTOGRAPHY_AUTOFOCUS_DONE + * message is posted to bus when autofocusing has finished. + */ +void +gst_photography_set_autofocus (GstPhotography * photo, gboolean on) +{ + GstPhotographyInterface *iface; + g_return_if_fail (photo != NULL); + + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); + if (iface->set_autofocus) { + iface->set_autofocus (photo, on); + } +} + +/** + * gst_photography_set_config: + * @photo: #GstPhotography interface of a #GstElement + * @config: #GstPhotoSettings containg the configuration + * + * Set all configuration settings at once. + * + * Returns: TRUE if configuration was set successfully, otherwise FALSE. + */ +gboolean +gst_photography_set_config (GstPhotography * photo, GstPhotoSettings * config) +{ + GstPhotographyInterface *iface; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); + if (iface->set_config) { + ret = iface->set_config (photo, config); + } + + return ret; +} + +/** + * gst_photography_get_config: + * @photo: #GstPhotography interface of a #GstElement + * @config: #GstPhotoSettings containg the configuration + * + * Get all configuration settings at once. + * + * Returns: TRUE if configuration was got successfully, otherwise FALSE. + */ +gboolean +gst_photography_get_config (GstPhotography * photo, GstPhotoSettings * config) +{ + GstPhotographyInterface *iface; + gboolean ret = FALSE; + + g_return_val_if_fail (photo != NULL, FALSE); + + iface = GST_PHOTOGRAPHY_GET_IFACE (photo); + if (iface->get_config) { + ret = iface->get_config (photo, config); + } + + return ret; +} + +/* Photography class initialization stuff */ +static void +gst_photography_iface_class_init (gpointer g_class) +{ + /* create interface signals and properties here. */ + + /* White balance */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_WB_MODE, + "White balance mode property", + "White balance affects the color temperature of the photo", + GST_TYPE_WHITE_BALANCE_MODE, + GST_PHOTOGRAPHY_WB_MODE_AUTO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Colour tone */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_COLOUR_TONE, + "Colour tone mode property", + "Colour tone setting changes colour shading in the photo", + GST_TYPE_COLOUR_TONE_MODE, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_NORMAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Scene mode */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_SCENE_MODE, + "Scene mode property", + "Scene mode works as a preset for different photo shooting mode settings", + GST_TYPE_SCENE_MODE, + GST_PHOTOGRAPHY_SCENE_MODE_AUTO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Flash mode */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FLASH_MODE, + "Flash mode property", + "Flash mode defines how the flash light should be used", + GST_TYPE_FLASH_MODE, + GST_PHOTOGRAPHY_FLASH_MODE_AUTO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Flicker reduction mode */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FLICKER_MODE, + "Flicker reduction mode property", + "Flicker reduction mode defines a line frequency for flickering prevention", + GST_TYPE_FLICKER_REDUCTION_MODE, + GST_PHOTOGRAPHY_FLICKER_REDUCTION_OFF, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Focus mode */ + g_object_interface_install_property (g_class, + g_param_spec_enum (GST_PHOTOGRAPHY_PROP_FOCUS_MODE, + "Focus mode property", + "Focus mode defines the range of focal lengths to use in autofocus search", + GST_TYPE_FOCUS_MODE, + GST_PHOTOGRAPHY_FOCUS_MODE_AUTO, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Capabilities */ + g_object_interface_install_property (g_class, + g_param_spec_ulong (GST_PHOTOGRAPHY_PROP_CAPABILITIES, + "Photo capabilities bitmask", + "Tells the photo capabilities of the device", + 0, G_MAXULONG, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* EV_compensation */ + g_object_interface_install_property (g_class, + g_param_spec_float (GST_PHOTOGRAPHY_PROP_EV_COMP, + "EV compensation property", + "EV compensation affects the brightness of the image", + -2.5, 2.5, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* ISO value */ + g_object_interface_install_property (g_class, + g_param_spec_uint (GST_PHOTOGRAPHY_PROP_ISO_SPEED, + "ISO speed property", + "ISO speed defines the light sensitivity (0 = auto)", + 0, 6400, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Aperture */ + g_object_interface_install_property (g_class, + g_param_spec_uint (GST_PHOTOGRAPHY_PROP_APERTURE, + "Aperture property", + "Aperture defines the size of lens opening (0 = auto)", + 0, G_MAXUINT8, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Exposure */ + g_object_interface_install_property (g_class, + g_param_spec_uint (GST_PHOTOGRAPHY_PROP_EXPOSURE, + "Exposure time in milliseconds", + "Exposure time defines how long the shutter will stay open (0 = auto)", + 0, G_MAXUINT32, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Image capture caps */ + g_object_interface_install_property (g_class, + g_param_spec_boxed (GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS, + "Image capture supported caps", + "Caps describing supported image capture formats", GST_TYPE_CAPS, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* Image preview caps */ + g_object_interface_install_property (g_class, + g_param_spec_boxed (GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS, + "Image preview supported caps", + "Caps describing supported image preview formats", GST_TYPE_CAPS, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* Zoom */ + g_object_interface_install_property (g_class, + g_param_spec_float (GST_PHOTOGRAPHY_PROP_ZOOM, + "Zoom property", + "How much the resulted image will be zoomed", + 1.0f, 10.0f, 1.0f, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Noise Reduction, Bayer an YCC noise reduction are enabled by default */ + g_object_interface_install_property (g_class, + g_param_spec_flags (GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION, + "Noise Reduction settings", + "Which noise reduction modes are enabled (0 = disabled)", + GST_TYPE_PHOTOGRAPHY_NOISE_REDUCTION, + 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} Index: gst-plugins-good0.10/gst-libs/gst/interfaces/photography.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst-libs/gst/interfaces/photography.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,389 @@ +/* GStreamer + * + * Copyright (C) 2008 Nokia Corporation + * + * photography.h: photography interface for digital imaging + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_PHOTOGRAPHY_H__ +#define __GST_PHOTOGRAPHY_H__ + +#ifndef GST_USE_UNSTABLE_API +#warning "The GstPhotography interface is unstable API and may change in future." +#warning "You can define GST_USE_UNSTABLE_API to avoid this warning." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PHOTOGRAPHY \ + (gst_photography_get_type ()) +#define GST_PHOTOGRAPHY(obj) \ + (GST_IMPLEMENTS_INTERFACE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PHOTOGRAPHY, GstPhotography)) +#define GST_IS_PHOTOGRAPHY(obj) \ + (GST_IMPLEMENTS_INTERFACE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PHOTOGRAPHY)) +#define GST_PHOTOGRAPHY_GET_IFACE(inst) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_PHOTOGRAPHY, GstPhotographyInterface)) + + +/* Custom GstMessage name that will be sent to GstBus when autofocusing + is complete */ +#define GST_PHOTOGRAPHY_AUTOFOCUS_DONE "autofocus-done" + +/* Custom GstMessage name that will be sent to GstBus when shake risk changes */ +#define GST_PHOTOGRAPHY_SHAKE_RISK "shake-risk" + +/* Interface property names */ +#define GST_PHOTOGRAPHY_PROP_WB_MODE "white-balance-mode" +#define GST_PHOTOGRAPHY_PROP_COLOUR_TONE "colour-tone-mode" +#define GST_PHOTOGRAPHY_PROP_SCENE_MODE "scene-mode" +#define GST_PHOTOGRAPHY_PROP_FLASH_MODE "flash-mode" +#define GST_PHOTOGRAPHY_PROP_NOISE_REDUCTION "noise-reduction" +#define GST_PHOTOGRAPHY_PROP_FOCUS_STATUS "focus-status" +#define GST_PHOTOGRAPHY_PROP_CAPABILITIES "capabilities" +#define GST_PHOTOGRAPHY_PROP_SHAKE_RISK "shake-risk" +#define GST_PHOTOGRAPHY_PROP_EV_COMP "ev-compensation" +#define GST_PHOTOGRAPHY_PROP_ISO_SPEED "iso-speed" +#define GST_PHOTOGRAPHY_PROP_APERTURE "aperture" +#define GST_PHOTOGRAPHY_PROP_EXPOSURE "exposure" +#define GST_PHOTOGRAPHY_PROP_IMAGE_CAPTURE_SUPPORTED_CAPS \ + "image-capture-supported-caps" +#define GST_PHOTOGRAPHY_PROP_IMAGE_PREVIEW_SUPPORTED_CAPS \ + "image-preview-supported-caps" +#define GST_PHOTOGRAPHY_PROP_FLICKER_MODE "flicker-mode" +#define GST_PHOTOGRAPHY_PROP_FOCUS_MODE "focus-mode" +#define GST_PHOTOGRAPHY_PROP_ZOOM "zoom" + +/** + * GstPhotography: + * + * Opaque #GstPhotography data structure. + */ +typedef struct _GstPhotography GstPhotography; + +/** + * GstPhotographyNoiseReduction: + * @GST_PHOTOGRAPHY_NOISE_REDUCTION_BAYER: Adaptive noise reduction on Bayer + * format + * @GST_PHOTOGRAPHY_NOISE_REDUCTION_YCC: reduces the noise on Y and 2-chroma + * images. + * @GST_PHOTOGRAPHY_NOISE_REDUCTION_TEMPORAL: Multi-frame adaptive NR, + * provided for the video mode + * @GST_PHOTOGRAPHY_NOISE_REDUCTION_FPN: Fixed Pattern Noise refers to noise + * that does not change between frames. The noise is removed from the sensor + * image, by subtracting a previously-captured black image in memory. + * @GST_PHOTOGRAPHY_NOISE_REDUCTION_EXTRA: Extra Noise Reduction. In the case + * of high-ISO capturing, some noise remains after YCC NR. XNR reduces this + * remaining noise. + * + * Noise Reduction features of a photography capture or filter element. + * + * Since: 0.10.21 + */ +typedef enum +{ + GST_PHOTOGRAPHY_NOISE_REDUCTION_BAYER = ( 1<<0 ), + GST_PHOTOGRAPHY_NOISE_REDUCTION_YCC = ( 1<<1 ), + GST_PHOTOGRAPHY_NOISE_REDUCTION_TEMPORAL= ( 1<< 2), + GST_PHOTOGRAPHY_NOISE_REDUCTION_FIXED = (1 << 3), + GST_PHOTOGRAPHY_NOISE_REDUCTION_EXTRA = (1 << 4) +} GstPhotographyNoiseReduction; + +typedef enum +{ + GST_PHOTOGRAPHY_WB_MODE_AUTO = 0, + GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT, + GST_PHOTOGRAPHY_WB_MODE_CLOUDY, + GST_PHOTOGRAPHY_WB_MODE_SUNSET, + GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN, + GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT +} GstWhiteBalanceMode; + +typedef enum +{ + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_NORMAL = 0, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_SEPIA, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_NEGATIVE, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_GRAYSCALE, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_NATURAL, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_VIVID, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_COLORSWAP, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_SOLARIZE, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_OUT_OF_FOCUS, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_SKY_BLUE, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_GRASS_GREEN, + GST_PHOTOGRAPHY_COLOUR_TONE_MODE_SKIN_WHITEN +} GstColourToneMode; + +typedef enum +{ + GST_PHOTOGRAPHY_SCENE_MODE_MANUAL = 0, + GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP, + GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT, + GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE, + GST_PHOTOGRAPHY_SCENE_MODE_SPORT, + GST_PHOTOGRAPHY_SCENE_MODE_NIGHT, + GST_PHOTOGRAPHY_SCENE_MODE_AUTO +} GstSceneMode; + +typedef enum +{ + GST_PHOTOGRAPHY_FLASH_MODE_AUTO = 0, + GST_PHOTOGRAPHY_FLASH_MODE_OFF, + GST_PHOTOGRAPHY_FLASH_MODE_ON, + GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN, + GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE +} GstFlashMode; + +typedef enum +{ + GST_PHOTOGRAPHY_FOCUS_STATUS_NONE = 0, + GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING, + GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL, + GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS +} GstFocusStatus; + +typedef enum +{ + GST_PHOTOGRAPHY_CAPS_NONE = (0 << 0), + GST_PHOTOGRAPHY_CAPS_EV_COMP = (1 << 0), + GST_PHOTOGRAPHY_CAPS_ISO_SPEED = (1 << 1), + GST_PHOTOGRAPHY_CAPS_WB_MODE = (1 << 2), + GST_PHOTOGRAPHY_CAPS_TONE = (1 << 3), + GST_PHOTOGRAPHY_CAPS_SCENE = (1 << 4), + GST_PHOTOGRAPHY_CAPS_FLASH = (1 << 5), + GST_PHOTOGRAPHY_CAPS_ZOOM = (1 << 6), + GST_PHOTOGRAPHY_CAPS_FOCUS = (1 << 7), + GST_PHOTOGRAPHY_CAPS_APERTURE = (1 << 8), + GST_PHOTOGRAPHY_CAPS_EXPOSURE = (1 << 9), + GST_PHOTOGRAPHY_CAPS_SHAKE = (1 << 10), + GST_PHOTOGRAPHY_CAPS_NOISE_REDUCTION = (1 << 11), + GST_PHOTOGRAPHY_CAPS_FLICKER_REDUCTION = (1 << 12), + GST_PHOTOGRAPHY_CAPS_ALL = (~0) +} GstPhotoCaps; + +typedef enum +{ + GST_PHOTOGRAPHY_SHAKE_RISK_LOW = 0, + GST_PHOTOGRAPHY_SHAKE_RISK_MEDIUM, + GST_PHOTOGRAPHY_SHAKE_RISK_HIGH, +} GstPhotoShakeRisk; + +typedef enum +{ + GST_PHOTOGRAPHY_FLICKER_REDUCTION_OFF = 0, + GST_PHOTOGRAPHY_FLICKER_REDUCTION_50HZ, + GST_PHOTOGRAPHY_FLICKER_REDUCTION_60HZ, + GST_PHOTOGRAPHY_FLICKER_REDUCTION_AUTO, +} GstFlickerReductionMode; + +typedef enum { + GST_PHOTOGRAPHY_FOCUS_MODE_AUTO = 0, + GST_PHOTOGRAPHY_FOCUS_MODE_MACRO, + GST_PHOTOGRAPHY_FOCUS_MODE_PORTRAIT, + GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY, + GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL, + GST_PHOTOGRAPHY_FOCUS_MODE_EXTENDED, + GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL, + GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_EXTENDED, +} GstFocusMode; + +typedef struct +{ + GstWhiteBalanceMode wb_mode; + GstColourToneMode tone_mode; + GstSceneMode scene_mode; + GstFlashMode flash_mode; + guint32 exposure; + guint aperture; + gfloat ev_compensation; + guint iso_speed; + gfloat zoom; + GstFlickerReductionMode flicker_mode; + GstFocusMode focus_mode; + GstPhotographyNoiseReduction noise_reduction; +} GstPhotoSettings; + +/** + * GstPhotoCapturePrepared: + * @data: user data that has been given, when registering the callback + * @configured_caps: #GstCaps defining the configured capture format. + * Ownership of these caps stays in the element. + * + * This callback will be called when the element has finished preparations + * for photo capture. + */ +typedef void (*GstPhotoCapturePrepared) (gpointer data, + const GstCaps *configured_caps); + +/** + * GstPhotographyInterface: + * @parent: parent interface type. + * @get_ev_compensation: vmethod to get ev exposure compensation value + * @get_iso_speed: vmethod to get iso speed (light sensitivity) value + * @get_aperture: vmethod to get aperture value + * @get_exposure: vmethod to get exposure time value + * @get_white_balance_mode: vmethod to get white balance mode value + * @get_colour_tone_mode: vmethod to get colour tone mode value + * @get_scene_mode: vmethod to get scene mode value + * @get_flash_mode: vmethod to get flash mode value + * @get_noise_reduction: vmethod to get noise reduction mode value + * @get_zoom: vmethod to get zoom factor value + * @set_ev_compensation: vmethod to set ev exposure compensation value + * @set_iso_speed: vmethod to set iso speed (light sensitivity) value + * @set_aperture: vmethod to set aperture value + * @set_exposure: vmethod to set exposure time value + * @set_white_balance_mode: vmethod to set white balance mode value + * @set_colour_tone_mode: vmethod to set colour tone mode value + * @set_scene_mode: vmethod to set scene mode value + * @set_flash_mode: vmethod to set flash mode value + * @set_noise_reduction: vmethod to set noise reduction mode value + * @set_zoom: vmethod to set zoom factor value + * @get_capabilities: vmethod to get supported capabilities of the interface + * @prepare_for_capture: vmethod to tell the element to prepare for capturing + * @set_autofocus: vmethod to set autofocus on/off + * @set_config: vmethod to set all configuration parameters at once + * @get_config: vmethod to get all configuration parameters at once + * @get_image_capture_supported_caps: vmethod to get caps describing supported image capture formats + * + * #GstPhotographyInterface interface. + */ +typedef struct _GstPhotographyInterface +{ + GTypeInterface parent; + + /* virtual functions */ + gboolean (*get_ev_compensation) (GstPhotography * photo, gfloat * ev_comp); + gboolean (*get_iso_speed) (GstPhotography * photo, guint * iso_speed); + gboolean (*get_aperture) (GstPhotography * photo, guint * aperture); + gboolean (*get_exposure) (GstPhotography * photo, guint32 * exposure); + gboolean (*get_white_balance_mode) (GstPhotography * photo, + GstWhiteBalanceMode * wb_mode); + gboolean (*get_colour_tone_mode) (GstPhotography * photo, + GstColourToneMode * tone_mode); + gboolean (*get_scene_mode) (GstPhotography * photo, + GstSceneMode * scene_mode); + gboolean (*get_flash_mode) (GstPhotography * photo, + GstFlashMode * flash_mode); + gboolean (*get_zoom) (GstPhotography * photo, gfloat * zoom); + gboolean (*get_flicker_mode) (GstPhotography * photo, + GstFlickerReductionMode * flicker_mode); + gboolean (*get_focus_mode) (GstPhotography * photo, + GstFocusMode * focus_mode); + + gboolean (*set_ev_compensation) (GstPhotography * photo, gfloat ev_comp); + gboolean (*set_iso_speed) (GstPhotography * photo, guint iso_speed); + gboolean (*set_aperture) (GstPhotography * photo, guint aperture); + gboolean (*set_exposure) (GstPhotography * photo, guint32 exposure); + gboolean (*set_white_balance_mode) (GstPhotography * photo, + GstWhiteBalanceMode wb_mode); + gboolean (*set_colour_tone_mode) (GstPhotography * photo, + GstColourToneMode tone_mode); + gboolean (*set_scene_mode) (GstPhotography * photo, + GstSceneMode scene_mode); + gboolean (*set_flash_mode) (GstPhotography * photo, + GstFlashMode flash_mode); + gboolean (*set_zoom) (GstPhotography * photo, gfloat zoom); + gboolean (*set_flicker_mode) (GstPhotography * photo, + GstFlickerReductionMode flicker_mode); + gboolean (*set_focus_mode) (GstPhotography * photo, + GstFocusMode focus_mode); + + GstPhotoCaps (*get_capabilities) (GstPhotography * photo); + gboolean (*prepare_for_capture) (GstPhotography * photo, + GstPhotoCapturePrepared func, GstCaps *capture_caps, gpointer user_data); + void (*set_autofocus) (GstPhotography * photo, gboolean on); + gboolean (*set_config) (GstPhotography * photo, GstPhotoSettings * config); + gboolean (*get_config) (GstPhotography * photo, GstPhotoSettings * config); + + gboolean (*get_noise_reduction) (GstPhotography * photo, + GstPhotographyNoiseReduction * noise_reduction); + gboolean (*set_noise_reduction) (GstPhotography * photo, + GstPhotographyNoiseReduction noise_reduction); + + /*< private > */ + gpointer _gst_reserved[GST_PADDING]; +} GstPhotographyInterface; + +GType gst_photography_get_type (void); + +/* virtual class function wrappers */ +gboolean gst_photography_get_ev_compensation (GstPhotography * photo, + gfloat * ev_comp); +gboolean gst_photography_get_iso_speed (GstPhotography * photo, + guint * iso_speed); +gboolean gst_photography_get_aperture (GstPhotography * photo, + guint * aperture); +gboolean gst_photography_get_exposure (GstPhotography * photo, + guint32 * exposure); +gboolean gst_photography_get_white_balance_mode (GstPhotography * photo, + GstWhiteBalanceMode * wb_mode); +gboolean gst_photography_get_colour_tone_mode (GstPhotography * photo, + GstColourToneMode * tone_mode); +gboolean gst_photography_get_scene_mode (GstPhotography * photo, + GstSceneMode * scene_mode); +gboolean gst_photography_get_flash_mode (GstPhotography * photo, + GstFlashMode * flash_mode); +gboolean gst_photography_get_noise_reduction (GstPhotography * photo, + GstPhotographyNoiseReduction * noise_reduction); +gboolean gst_photography_get_zoom (GstPhotography * photo, gfloat * zoom); +gboolean gst_photography_get_flicker_mode (GstPhotography * photo, + GstFlickerReductionMode *mode); +gboolean gst_photography_get_focus_mode (GstPhotography * photo, + GstFocusMode *mode); + +gboolean gst_photography_set_ev_compensation (GstPhotography * photo, + gfloat ev_comp); +gboolean gst_photography_set_iso_speed (GstPhotography * photo, + guint iso_speed); +gboolean gst_photography_set_aperture (GstPhotography * photo, guint aperture); +gboolean gst_photography_set_exposure (GstPhotography * photo, guint exposure); +gboolean gst_photography_set_white_balance_mode (GstPhotography * photo, + GstWhiteBalanceMode wb_mode); +gboolean gst_photography_set_colour_tone_mode (GstPhotography * photo, + GstColourToneMode tone_mode); +gboolean gst_photography_set_scene_mode (GstPhotography * photo, + GstSceneMode scene_mode); +gboolean gst_photography_set_flash_mode (GstPhotography * photo, + GstFlashMode flash_mode); +gboolean gst_photography_set_noise_reduction (GstPhotography * photo, + GstPhotographyNoiseReduction noise_reduction); +gboolean gst_photography_set_zoom (GstPhotography * photo, gfloat zoom); +gboolean gst_photography_set_flicker_mode (GstPhotography * photo, + GstFlickerReductionMode mode); +gboolean gst_photography_set_focus_mode (GstPhotography * photo, + GstFocusMode mode); + +GstPhotoCaps gst_photography_get_capabilities (GstPhotography * photo); + +gboolean gst_photography_prepare_for_capture (GstPhotography * photo, + GstPhotoCapturePrepared func, GstCaps *capture_caps, gpointer user_data); + +void gst_photography_set_autofocus (GstPhotography * photo, gboolean on); + +gboolean gst_photography_set_config (GstPhotography * photo, + GstPhotoSettings * config); +gboolean gst_photography_get_config (GstPhotography * photo, + GstPhotoSettings * config); + +G_END_DECLS + +#endif /* __GST_PHOTOGRAPHY_H__ */ Index: gst-plugins-good0.10/gst/camerabin/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/Makefile.am 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,63 @@ +glib_gen_prefix = __gst_camerabin +glib_gen_basename = gstcamerabin + +include $(top_srcdir)/common/gst-glib-gen.mak + +built_sources = gstcamerabin-marshal.c +built_headers = gstcamerabin-marshal.h + +BUILT_SOURCES = $(built_sources) $(built_headers) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = gstcamerabin-marshal.list + +plugin_LTLIBRARIES = libgstcamerabin.la + +libgstcamerabin_la_SOURCES = gstcamerabin.c \ + gstcamerabincolorbalance.c \ + gstinputselector.c \ + camerabinimage.c \ + camerabinvideo.c \ + camerabingeneral.c \ + camerabinpreview.c \ + gstcamerabin-enum.c + +nodist_libgstcamerabin_la_SOURCES = $(built_sources) +libgstcamerabin_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ + -DGST_USE_UNSTABLE_API +libgstcamerabin_la_LIBADD = \ + $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-$(GST_MAJORMINOR).la \ + $(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) \ + -lgstinterfaces-$(GST_MAJORMINOR) -lgsttag-$(GST_MAJORMINOR) + +libgstcamerabin_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstcamerabin_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstcamerabin.h \ + gstcamerabincolorbalance.h \ + gstinputselector.h \ + camerabinimage.h \ + camerabinvideo.h \ + camerabindebug.h \ + camerabingeneral.h \ + camerabinpreview.h \ + gstcamerabin-enum.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstcamerabin -:SHARED libgstcamerabin \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstcamerabin_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstcamerabin_la_CFLAGS) \ + -:LDFLAGS $(libgstcamerabin_la_LDFLAGS) \ + $(libgstcamerabin_la_LIBADD) \ + -ldl \ + -:LIBFILTER_STATIC gstphotography-@GST_MAJORMINOR@ \ + gstbasecamerabinsrc-@GST_MAJORMINOR@ \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ Index: gst-plugins-good0.10/gst/camerabin/TODO =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/TODO 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,31 @@ += Cleanups = +* often two g_object_set for same object one after the other +* use GST_*_OBJECT () more often +* there are two gst_element_set_state() one after each other + += Renaming = +* internal use of img->image, vid->video + += Refactorisation = +* gstcamerabin:gst_camerabin_rewrite_tags + - sounds fishy, should use normal tagsetter method + - gst_camerabin_rewrite_tags_to_bin(9 why don't we just send a tag-event? + +* file-name property + - supplying an already opened filedeskriptor would be more safe + - need to check what filesink does if the file exists and cannot be overwritten + +* imagbin + - we want async operation here (especialy for burst mode capture) + - right now, its a bit fragile as we muck with locked_state + - main problem is that the location for filesink can only be set in NULL/READY + and we need to do that sync'ed with the dataflow. we can't use multifilesink + as it does a file per pad_push + - one problem of the current approach is that we can't have an image in e.g, + postprocessing while anotherone is beeing saved + - we could use a pool of imagebins: + - configure one (set filename) + - push_buffer + - on eos, put it back to the pool + - for this we need to check that we can have multiple instances of e.g. + dsp jpeg encoders Index: gst-plugins-good0.10/gst/camerabin/camerabindebug.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabindebug.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,30 @@ +/* + * GStreamer + * Copyright (C) 2010 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_DEBUG_H_ +#define __CAMERABIN_DEBUG_H_ + +#include + +/* debug logging category */ +GST_DEBUG_CATEGORY_EXTERN (gst_camerabin_debug); +#define GST_CAT_DEFAULT gst_camerabin_debug + +#endif /* #ifndef __CAMERABIN_DEBUG_H_ */ Index: gst-plugins-good0.10/gst/camerabin/camerabingeneral.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabingeneral.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,263 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:camerabingeneral + * @short_description: helper functions for #GstCameraBin and it's modules + * + * Common helper functions for #GstCameraBin, #GstCameraBinImage and + * #GstCameraBinVideo. + * + */ +#include +#include + +#include "camerabingeneral.h" +#include "gstinputselector.h" + +GST_DEBUG_CATEGORY (gst_camerabin_debug); + +/** + * gst_camerabin_add_element: + * @bin: add an element to this bin + * @new_elem: new element to be added + * + * Adds given element to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. Raises an error if adding + * or linking failed. Unrefs the element in the case of an error. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_add_element (GstBin * bin, GstElement * new_elem) +{ + gboolean ret; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (new_elem, FALSE); + + ret = gst_camerabin_try_add_element (bin, new_elem); + + if (!ret) { + gchar *elem_name = gst_element_get_name (new_elem); + GST_ELEMENT_ERROR (bin, CORE, NEGOTIATION, (NULL), + ("linking %s failed", elem_name)); + g_free (elem_name); + gst_object_unref (new_elem); + } + + return ret; +} + +/** + * gst_camerabin_try_add_element: + * @bin: tries adding an element to this bin + * @new_elem: new element to be added + * + * Adds given element to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_try_add_element (GstBin * bin, GstElement * new_elem) +{ + GstPad *bin_pad; + GstElement *bin_elem; + gboolean ret = TRUE; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (new_elem, FALSE); + + /* Get pads for linking */ + bin_pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC); + /* Add to bin */ + gst_bin_add (GST_BIN (bin), new_elem); + /* Link, if unconnected pad was found, otherwise just add it to bin */ + if (bin_pad) { + GST_DEBUG_OBJECT (bin, "linking %s to %s:%s", GST_OBJECT_NAME (new_elem), + GST_DEBUG_PAD_NAME (bin_pad)); + bin_elem = gst_pad_get_parent_element (bin_pad); + gst_object_unref (bin_pad); + if (!gst_element_link_pads_full (bin_elem, NULL, new_elem, NULL, + GST_PAD_LINK_CHECK_CAPS)) { + gst_object_ref (new_elem); + gst_bin_remove (bin, new_elem); + ret = FALSE; + } + gst_object_unref (bin_elem); + } else { + GST_INFO_OBJECT (bin, "no unlinked source pad in bin"); + } + + return ret; +} + +/** + * gst_camerabin_create_and_add_element: + * @bin: tries adding an element to this bin + * @elem_name: name of the element to be created + * @instance_name: name of the instance of the element to be created + * + * Creates an element according to given name and + * adds it to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. + * + * Returns: pointer to the new element if successful, NULL otherwise. + */ +GstElement * +gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name, + const gchar * instance_name) +{ + GstElement *new_elem; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (elem_name, FALSE); + + if (strcmp (elem_name, "input-selector") == 0) { + /* we ship our own copy of input-selector because we still use the + * "select-all" property which was removed when input-selector was + * moved to core */ + new_elem = g_object_new (GST_TYPE_INPUT_SELECTOR, NULL); + } else { + new_elem = gst_element_factory_make (elem_name, NULL); + } + + if (!new_elem) { + GST_ELEMENT_ERROR (bin, CORE, MISSING_PLUGIN, (NULL), + ("could not create \"%s\" element.", elem_name)); + } else if (!gst_camerabin_add_element (bin, new_elem)) { + new_elem = NULL; + } + + return new_elem; +} + +/* try to change the state of an element. This function returns the element when + * the state change could be performed. When this function returns NULL an error + * occured and the element is unreffed if @unref is TRUE. */ +static GstElement * +try_element (GstElement * bin, GstElement * element, gboolean unref) +{ + GstStateChangeReturn ret; + + if (element) { + ret = gst_element_set_state (element, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_DEBUG_OBJECT (bin, "failed state change.."); + gst_element_set_state (element, GST_STATE_NULL); + if (unref) + gst_object_unref (element); + element = NULL; + } + } + return element; +} + +GstElement * +gst_camerabin_setup_default_element (GstBin * bin, GstElement * user_elem, + const gchar * auto_elem_name, const gchar * default_elem_name) +{ + GstElement *elem; + + if (user_elem) { + GST_DEBUG_OBJECT (bin, "trying configured element"); + elem = try_element (GST_ELEMENT_CAST (bin), user_elem, FALSE); + } else { + /* only try fallback if no specific sink was chosen */ + GST_DEBUG_OBJECT (bin, "trying %s", auto_elem_name); + elem = gst_element_factory_make (auto_elem_name, NULL); + elem = try_element (GST_ELEMENT_CAST (bin), elem, TRUE); + if (elem == NULL) { + /* if default sink from config.h is different then try it too */ + if (strcmp (default_elem_name, auto_elem_name)) { + GST_DEBUG_OBJECT (bin, "trying %s", default_elem_name); + elem = gst_element_factory_make (default_elem_name, NULL); + elem = try_element (GST_ELEMENT_CAST (bin), elem, TRUE); + } + } + } + return elem; +} + +/** + * gst_camerabin_remove_elements_from_bin: + * @bin: removes all elements from this bin + * + * Removes all elements from this @bin. + */ +void +gst_camerabin_remove_elements_from_bin (GstBin * bin) +{ + GstIterator *iter = NULL; + gpointer data = NULL; + GstElement *elem = NULL; + gboolean done = FALSE; + + iter = gst_bin_iterate_elements (bin); + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK: + elem = GST_ELEMENT (data); + gst_bin_remove (bin, elem); + gst_element_set_state (GST_ELEMENT (elem), GST_STATE_NULL); + /* Iterator increased the element refcount, so unref */ + gst_object_unref (elem); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (bin, "error in iterating elements"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); +} + +/** + * gst_camerabin_drop_eos_probe: + * @pad: pad receiving the event + * @event: received event + * @u_data: not used + * + * Event probe that drop all eos events. + * + * Returns: FALSE to drop the event, TRUE otherwise + */ +gboolean +gst_camerabin_drop_eos_probe (GstPad * pad, GstEvent * event, gpointer u_data) +{ + gboolean ret = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + GST_DEBUG ("dropping eos in %s:%s", GST_DEBUG_PAD_NAME (pad)); + ret = FALSE; + break; + default: + break; + } + return ret; +} Index: gst-plugins-good0.10/gst/camerabin/camerabingeneral.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabingeneral.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,36 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_GENERAL_H_ +#define __CAMERABIN_GENERAL_H_ + +#include + +gboolean gst_camerabin_try_add_element (GstBin * bin, GstElement * new_elem); +gboolean gst_camerabin_add_element (GstBin * bin, GstElement * new_elem); +GstElement *gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name, const gchar * instance_name); + +GstElement * gst_camerabin_setup_default_element (GstBin * bin, GstElement *user_elem, const gchar *auto_elem_name, const gchar *default_elem_name); + +void gst_camerabin_remove_elements_from_bin (GstBin * bin); + +gboolean gst_camerabin_drop_eos_probe (GstPad * pad, GstEvent * event, gpointer u_data); + +#endif /* #ifndef __CAMERABIN_GENERAL_H_ */ Index: gst-plugins-good0.10/gst/camerabin/camerabinimage.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinimage.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,775 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:camerabinimage + * @short_description: image capturing module of #GstCameraBin + * + * + * + * + * The pipeline for this module is: + * + * + * + *----------------------------------------------------------------------------- + * + * -> [post proc] -> csp -> imageenc -> metadatamuxer -> filesink + * + *----------------------------------------------------------------------------- + * + * + * + * The image bin opens file for image writing in READY to PAUSED state change. + * The image bin closes the file in PAUSED to READY state change. + * + * + * + */ + +/* + * includes + */ + +#include + +#include "camerabinimage.h" +#include "camerabindebug.h" +#include "camerabingeneral.h" +#include "gstcamerabin-enum.h" + +#include "string.h" + +/* default internal element names */ + +#define DEFAULT_SINK "filesink" +#define DEFAULT_ENC "jpegenc" +#define DEFAULT_FORMATTER "jifmux" +#define DEFAULT_FLAGS GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION + +enum +{ + PROP_0, + PROP_FILENAME +}; + +static gboolean gst_camerabin_image_create_elements (GstCameraBinImage * img); +static void gst_camerabin_image_destroy_elements (GstCameraBinImage * img); + +static void gst_camerabin_image_dispose (GstCameraBinImage * sink); +static GstStateChangeReturn +gst_camerabin_image_change_state (GstElement * element, + GstStateChange transition); +static gboolean gst_camerabin_image_send_event (GstElement * element, + GstEvent * event); +static void gst_camerabin_image_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_camerabin_image_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static gboolean metadata_write_probe (GstPad * pad, GstBuffer * buffer, + gpointer u_data); +static gboolean prepare_element (GList ** result, + const gchar * default_element_name, GstElement * app_elem, + GstElement ** res_elem); + + +GST_BOILERPLATE (GstCameraBinImage, gst_camerabin_image, GstBin, GST_TYPE_BIN); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static void +gst_camerabin_image_base_init (gpointer klass) +{ + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (eklass, &sink_template); + gst_element_class_set_details_simple (eklass, + "Image capture bin for camerabin", "Bin/Image", + "Process and store image data", + "Edgard Lima , " + "Nokia Corporation "); +} + +static void +gst_camerabin_image_class_init (GstCameraBinImageClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = + (GObjectFinalizeFunc) GST_DEBUG_FUNCPTR (gst_camerabin_image_dispose); + eklass->change_state = GST_DEBUG_FUNCPTR (gst_camerabin_image_change_state); + eklass->send_event = GST_DEBUG_FUNCPTR (gst_camerabin_image_send_event); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_camerabin_image_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_camerabin_image_get_property); + + /** + * GstCameraBinImage:filename + * + * This property can be used to specify the filename of the image. + * + **/ + g_object_class_install_property (gobject_class, PROP_FILENAME, + g_param_spec_string ("filename", "Filename", + "Filename of the image to save", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_camerabin_image_init (GstCameraBinImage * img, + GstCameraBinImageClass * g_class) +{ + img->filename = g_string_new (""); + + img->post = NULL; + img->csp = NULL; + img->enc = NULL; + img->app_enc = NULL; + img->formatter = NULL; + img->app_formatter = NULL; + img->sink = NULL; + + /* Create src and sink ghost pads */ + img->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); + gst_element_add_pad (GST_ELEMENT (img), img->sinkpad); + + img->flags = DEFAULT_FLAGS; +} + +static void +gst_camerabin_image_dispose (GstCameraBinImage * img) +{ + GST_DEBUG_OBJECT (img, "disposing"); + + g_string_free (img->filename, TRUE); + img->filename = NULL; + + if (img->elements) { + g_list_free (img->elements); + img->elements = NULL; + } + + if (img->sink) { + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->sink), GST_OBJECT_REFCOUNT_VALUE (img->sink)); + gst_object_unref (img->sink); + img->sink = NULL; + } + + if (img->formatter) { + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->formatter), + GST_OBJECT_REFCOUNT_VALUE (img->formatter)); + gst_object_unref (img->formatter); + img->formatter = NULL; + } + + if (img->app_formatter) { + gst_object_sink (img->app_formatter); + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->app_formatter), + GST_OBJECT_REFCOUNT_VALUE (img->app_formatter)); + gst_object_unref (img->app_formatter); + img->app_formatter = NULL; + } + + if (img->enc) { + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->enc), GST_OBJECT_REFCOUNT_VALUE (img->enc)); + gst_object_unref (img->enc); + img->enc = NULL; + } + + if (img->csp) { + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->csp), GST_OBJECT_REFCOUNT_VALUE (img->csp)); + gst_object_unref (img->csp); + img->csp = NULL; + } + + /* Note: if imagebin was never set to READY state the + ownership of elements created by application were never + taken by bin and therefore gst_object_sink is called for + these elements (they may still be in floating state + and not unreffed properly without sinking first) + */ + if (img->app_enc) { + gst_object_sink (img->app_enc); + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->app_enc), + GST_OBJECT_REFCOUNT_VALUE (img->app_enc)); + gst_object_unref (img->app_enc); + img->app_enc = NULL; + } + + if (img->post) { + gst_object_sink (img->post); + GST_LOG_OBJECT (img, "disposing %s with refcount %d", + GST_ELEMENT_NAME (img->post), GST_OBJECT_REFCOUNT_VALUE (img->post)); + gst_object_unref (img->post); + img->post = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) img); +} + +static GstStateChangeReturn +gst_camerabin_image_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCameraBinImage *img = GST_CAMERABIN_IMAGE (element); + + GST_DEBUG_OBJECT (element, "changing state: %s -> %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_camerabin_image_create_elements (img)) { + return GST_STATE_CHANGE_FAILURE; + } + /* Allow setting filename when image bin in READY state */ + gst_element_set_locked_state (img->sink, TRUE); + GST_INFO_OBJECT (img, "locking imagebin->sink state to %s", + gst_element_state_get_name (GST_STATE (img->sink))); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (!g_str_equal (img->filename->str, "")) { + GST_INFO_OBJECT (img, "preparing image with filename: %s", + img->filename->str); + gst_element_set_locked_state (img->sink, FALSE); + } else { + GST_INFO_OBJECT (img, "keep sink locked, we have no filename yet"); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Set sink to NULL in order to write the file _now_ */ + GST_INFO_OBJECT (img, "write image with filename: %s", + img->filename->str); + gst_element_set_locked_state (img->sink, TRUE); + gst_element_set_state (img->sink, GST_STATE_NULL); + g_string_assign (img->filename, ""); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* Write debug graph to file */ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (GST_ELEMENT_PARENT (img)), + GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | + GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, "imagebin.playing"); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_camerabin_image_destroy_elements (img); + break; + default: + break; + } + + GST_DEBUG_OBJECT (element, "changed state: %s -> %s = %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)), + gst_element_state_change_return_get_name (ret)); + + return ret; +} + +gboolean +gst_camerabin_image_send_event (GstElement * element, GstEvent * event) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (element); + gboolean ret = FALSE; + + GST_INFO ("got %s event", GST_EVENT_TYPE_NAME (event)); + + if (GST_EVENT_IS_DOWNSTREAM (event)) { + ret = gst_pad_send_event (bin->sinkpad, event); + } else { + if (bin->sink) { + ret = gst_element_send_event (bin->sink, event); + } else { + GST_WARNING ("upstream event handling failed"); + } + } + + return ret; +} + +static void +gst_camerabin_image_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_string_assign (bin->filename, g_value_get_string (value)); + GST_INFO_OBJECT (bin, "received filename: '%s'", bin->filename->str); + if (bin->sink) { + if (!g_str_equal (bin->filename->str, "")) { + g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str, + NULL); + gst_element_set_locked_state (bin->sink, FALSE); + gst_element_sync_state_with_parent (bin->sink); + } else { + GST_INFO_OBJECT (bin, "empty filename"); + } + } else { + GST_INFO_OBJECT (bin, "no sink, not setting name yet"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camerabin_image_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBinImage *bin = GST_CAMERABIN_IMAGE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, bin->filename->str); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* + * gst_camerabin_image_prepare_elements: + * @imagebin: a pointer to #GstCameraBinImage object + * + * This function creates an ordered list of elements configured for imagebin + * pipeline and creates the elements if necessary. It also stores pointers + * to created elements for re-using them. + * + * Image bin: + * img->sinkpad ! [ post process !] [ csp !] encoder ! metadata ! filesink + * + * Returns: %FALSE if there was error creating element, %TRUE otherwise + */ +gboolean +gst_camerabin_image_prepare_elements (GstCameraBinImage * imagebin) +{ + gboolean ret = FALSE; + GstPad *sinkpad = NULL; + + g_return_val_if_fail (imagebin != NULL, FALSE); + + GST_DEBUG_OBJECT (imagebin, "preparing image capture elements"); + + if (imagebin->elements != NULL) { + g_list_free (imagebin->elements); + imagebin->elements = NULL; + } + + /* Create file sink element */ + if (!prepare_element (&imagebin->elements, DEFAULT_SINK, NULL, + &imagebin->sink)) { + goto done; + } else { + g_object_set (G_OBJECT (imagebin->sink), "location", + imagebin->filename->str, "async", FALSE, "buffer-mode", 2, + /* non buffered io */ NULL); + } + + /* Create metadata muxer element */ + if (!prepare_element (&imagebin->elements, DEFAULT_FORMATTER, + imagebin->app_formatter, &imagebin->formatter)) { + goto done; + } else if (!imagebin->metadata_probe_id) { + /* Add probe for default XMP metadata writing */ + sinkpad = gst_element_get_static_pad (imagebin->formatter, "sink"); + imagebin->metadata_probe_id = + gst_pad_add_buffer_probe (sinkpad, G_CALLBACK (metadata_write_probe), + imagebin); + gst_object_unref (sinkpad); + } + + /* Create image encoder element */ + if (!prepare_element (&imagebin->elements, DEFAULT_ENC, imagebin->app_enc, + &imagebin->enc)) { + goto done; + } + + /* Create optional colorspace conversion element */ + if (imagebin->flags & GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION) { + if (!prepare_element (&imagebin->elements, "ffmpegcolorspace", NULL, + &imagebin->csp)) { + goto done; + } + } + + /* Add optional image post processing element */ + if (!prepare_element (&imagebin->elements, NULL, imagebin->post, + &imagebin->post)) { + goto done; + } + + ret = TRUE; + +done: + GST_DEBUG_OBJECT (imagebin, "preparing finished %s", ret ? "OK" : "NOK"); + return ret; +} + + +/* + * static helper functions implementation + */ + +/* + * metadata_write_probe: + * @pad: sink pad of metadata muxer + * @buffer: received buffer + * @u_data: image bin object + * + * Buffer probe that sets Xmp.dc.type and Xmp.dc.format tags + * to metadata muxer based on preceding element src pad caps. + * + * Returns: TRUE always + */ +static gboolean +metadata_write_probe (GstPad * pad, GstBuffer * buffer, gpointer u_data) +{ + /* Add XMP tags */ + GstCameraBinImage *img = NULL; + GstTagSetter *setter = NULL; + GstPad *srcpad = NULL; + GstCaps *caps = NULL; + GstStructure *st = NULL; + + img = GST_CAMERABIN_IMAGE (u_data); + + g_return_val_if_fail (img != NULL, TRUE); + + if (GST_IS_TAG_SETTER (img->formatter)) { + setter = GST_TAG_SETTER (img->formatter); + } + + if (!setter) { + GST_WARNING_OBJECT (img, "setting tags failed"); + goto done; + } + + /* Xmp.dc.type tag */ + gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE, + GST_TAG_CODEC, "Image", NULL); + /* Xmp.dc.format tag */ + if (img->enc) { + srcpad = gst_element_get_static_pad (img->enc, "src"); + } + GST_LOG_OBJECT (img, "srcpad:%" GST_PTR_FORMAT, srcpad); + if (srcpad) { + caps = gst_pad_get_negotiated_caps (srcpad); + GST_LOG_OBJECT (img, "caps:%" GST_PTR_FORMAT, caps); + if (caps) { + /* If there are many structures, we can't know which one to use */ + if (gst_caps_get_size (caps) != 1) { + GST_WARNING_OBJECT (img, "can't decide structure for format tag"); + goto done; + } + st = gst_caps_get_structure (caps, 0); + if (st) { + GST_DEBUG_OBJECT (img, "Xmp.dc.format:%s", gst_structure_get_name (st)); + gst_tag_setter_add_tags (setter, GST_TAG_MERGE_REPLACE, + GST_TAG_VIDEO_CODEC, gst_structure_get_name (st), NULL); + } + } + } +done: + if (caps) + gst_caps_unref (caps); + if (srcpad) + gst_object_unref (srcpad); + + return TRUE; +} + +/* + * prepare_element: + * @result: result list address + * @default_element_name: name of default element to be created + * @app_elem: pointer to application set element + * @res_elem: pointer to current element to be replaced if needed + * + * This function chooses given image capture element or creates a new one and + * and prepends it to @result list. + * + * Returns: %FALSE if there was error creating new element, %TRUE otherwise + */ +static gboolean +prepare_element (GList ** result, const gchar * default_element_name, + GstElement * app_elem, GstElement ** res_elem) +{ + GstElement *elem = NULL; + gboolean ret = TRUE; + + if (app_elem) { + /* Prefer application set element */ + elem = app_elem; + } else if (*res_elem) { + /* Use existing element if any */ + elem = *res_elem; + } else if (default_element_name) { + /* Create new element */ + if (!(elem = gst_element_factory_make (default_element_name, NULL))) { + GST_WARNING ("creating %s failed", default_element_name); + ret = FALSE; + } + } + + if (*res_elem != elem) { + /* Keep reference and store pointer to chosen element, which can be re-used + until imagebin is disposed or new image capture element is chosen. */ + gst_object_replace ((GstObject **) res_elem, (GstObject *) elem); + } + if (elem) { + *result = g_list_prepend (*result, elem); + } + + return ret; +} + +/* + * gst_camerabin_image_link_first_element: + * @img: a pointer to #GstCameraBinImage object + * @elem: first element to be linked on imagebin + * + * Adds given element to imagebin and links it to imagebin's ghost sink pad. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise + */ +static gboolean +gst_camerabin_image_link_first_element (GstCameraBinImage * imagebin, + GstElement * elem) +{ + GstPad *first_sinkpad = NULL; + gboolean ret = FALSE; + + g_return_val_if_fail (imagebin != NULL, FALSE); + /* Link given element to imagebin ghost sink pad */ + if (gst_bin_add (GST_BIN (imagebin), elem)) { + first_sinkpad = gst_element_get_static_pad (elem, "sink"); + if (first_sinkpad) { + if (gst_ghost_pad_set_target (GST_GHOST_PAD (imagebin->sinkpad), + first_sinkpad)) { + ret = TRUE; + } else { + GST_WARNING ("linking first element failed"); + } + gst_object_unref (first_sinkpad); + } else { + GST_WARNING ("no sink pad in first element"); + } + } else { + GST_WARNING ("adding element failed"); + } + return ret; +} + +/* + * gst_camerabin_image_link_elements: + * @imagebin: a pointer to #GstCameraBinImage object + * + * Link elements configured to imagebin elements list. + * + * Returns %TRUE if linking succeeded, %FALSE otherwise. + */ +static gboolean +gst_camerabin_image_link_elements (GstCameraBinImage * imagebin) +{ + GList *prev = NULL; + GList *next = NULL; + gboolean ret = FALSE; + + GST_DEBUG_OBJECT (imagebin, "linking image elements"); + + if (!imagebin->elements) { + GST_WARNING ("no elements to link"); + goto done; + } + + /* Link the elements in list */ + prev = imagebin->elements; + next = g_list_next (imagebin->elements); + for (; next != NULL; next = g_list_next (next)) { + /* Link first element in list to imagebin ghost sink pad */ + if (prev == imagebin->elements + && !gst_camerabin_image_link_first_element (imagebin, + GST_ELEMENT (prev->data))) { + goto done; + } + if (!gst_bin_add (GST_BIN (imagebin), GST_ELEMENT (next->data))) { + GST_WARNING_OBJECT (imagebin, "adding element failed"); + goto done; + } + GST_LOG_OBJECT (imagebin, "linking %s - %s", + GST_ELEMENT_NAME (GST_ELEMENT (prev->data)), + GST_ELEMENT_NAME (GST_ELEMENT (next->data))); + if (!gst_element_link (GST_ELEMENT (prev->data), GST_ELEMENT (next->data))) { + GST_WARNING_OBJECT (imagebin, "linking element failed"); + goto done; + } + + prev = next; + } + + ret = TRUE; + +done: + + if (!ret) { + gst_camerabin_remove_elements_from_bin (GST_BIN (imagebin)); + } + + GST_DEBUG_OBJECT (imagebin, "linking finished %s", ret ? "OK" : "NOK"); + + return ret; +} + +/* + * gst_camerabin_image_create_elements: + * @img: a pointer to #GstCameraBinImage object + * + * This function creates needed elements, adds them to + * imagebin and links them. + * + * Returns %TRUE if success, %FALSE otherwise. + */ +static gboolean +gst_camerabin_image_create_elements (GstCameraBinImage * img) +{ + gboolean ret = FALSE; + g_return_val_if_fail (img != NULL, FALSE); + + if (gst_camerabin_image_prepare_elements (img)) { + ret = gst_camerabin_image_link_elements (img); + } + + return ret; +} + +/* + * gst_camerabin_image_destroy_elements: + * @img: a pointer to #GstCameraBinImage object + * + * This function releases resources allocated in + * gst_camerabin_image_create_elements. + * + */ +static void +gst_camerabin_image_destroy_elements (GstCameraBinImage * img) +{ + GST_LOG ("destroying image elements"); + + gst_ghost_pad_set_target (GST_GHOST_PAD (img->sinkpad), NULL); + + gst_camerabin_remove_elements_from_bin (GST_BIN (img)); +} + +void +gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder) +{ + GST_DEBUG ("setting image encoder %" GST_PTR_FORMAT, encoder); + if (img->app_enc) + gst_object_unref (img->app_enc); + if (encoder) + gst_object_ref (encoder); + + img->app_enc = encoder; +} + +void +gst_camerabin_image_set_postproc (GstCameraBinImage * img, + GstElement * postproc) +{ + GST_DEBUG ("setting image postprocessing element %" GST_PTR_FORMAT, postproc); + if (img->post) + gst_object_unref (img->post); + if (postproc) + gst_object_ref (postproc); + + img->post = postproc; +} + +void +gst_camerabin_image_set_formatter (GstCameraBinImage * img, + GstElement * formatter) +{ + GstElement **app_formatter; + GST_DEBUG ("setting image formatter %" GST_PTR_FORMAT, formatter); + + app_formatter = &img->app_formatter; + GST_OBJECT_LOCK (img); + gst_object_replace ((GstObject **) app_formatter, GST_OBJECT (formatter)); + GST_OBJECT_UNLOCK (img); +} + +void +gst_camerabin_image_set_flags (GstCameraBinImage * img, GstCameraBinFlags flags) +{ + GST_DEBUG_OBJECT (img, "setting image flags: %d", flags); + img->flags = flags; +} + +GstElement * +gst_camerabin_image_get_encoder (GstCameraBinImage * img) +{ + GstElement *enc; + + if (img->app_enc) { + enc = img->app_enc; + } else { + enc = img->enc; + } + + return enc; +} + +GstElement * +gst_camerabin_image_get_formatter (GstCameraBinImage * img) +{ + /* Prefer formatter that is currently in use */ + return img->formatter ? img->formatter : img->app_formatter; +} + +GstElement * +gst_camerabin_image_get_postproc (GstCameraBinImage * img) +{ + return img->post; +} Index: gst-plugins-good0.10/gst/camerabin/camerabinimage.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinimage.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,96 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_IMAGE_H__ +#define __CAMERABIN_IMAGE_H__ + +#include + +#include "gstcamerabin-enum.h" + +G_BEGIN_DECLS +#define GST_TYPE_CAMERABIN_IMAGE (gst_camerabin_image_get_type()) +#define GST_CAMERABIN_IMAGE_CAST(obj) ((GstCameraBinImage*)(obj)) +#define GST_CAMERABIN_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImage)) +#define GST_CAMERABIN_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN_IMAGE,GstCameraBinImageClass)) +#define GST_IS_CAMERABIN_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN_IMAGE)) +#define GST_IS_CAMERABIN_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_IMAGE)) +/** + * GstCameraBinImage: + * + * The opaque #GstCameraBinImage structure. + */ +typedef struct _GstCameraBinImage GstCameraBinImage; +typedef struct _GstCameraBinImageClass GstCameraBinImageClass; + +struct _GstCameraBinImage +{ + GstBin parent; + GString *filename; + + /* Ghost pads of image bin */ + GstPad *sinkpad; + + /* Ordered list of elements configured to imagebin */ + GList *elements; + /* Imagebin elements */ + GstElement *post; + GstElement *csp; + GstElement *enc; + GstElement *app_enc; + GstElement *formatter; + GstElement *app_formatter; + GstElement *sink; + + GstCameraBinFlags flags; + gulong metadata_probe_id; +}; + +struct _GstCameraBinImageClass +{ + GstBinClass parent_class; +}; + +GType gst_camerabin_image_get_type (void); + +void +gst_camerabin_image_set_encoder (GstCameraBinImage * img, GstElement * encoder); + +void +gst_camerabin_image_set_postproc (GstCameraBinImage * img, + GstElement * postproc); + +void +gst_camerabin_image_set_formatter (GstCameraBinImage * img, GstElement * formatter); + +void +gst_camerabin_image_set_flags (GstCameraBinImage * img, + GstCameraBinFlags flags); + +GstElement *gst_camerabin_image_get_encoder (GstCameraBinImage * img); + +GstElement *gst_camerabin_image_get_postproc (GstCameraBinImage * img); + +GstElement *gst_camerabin_image_get_formatter (GstCameraBinImage * img); + +gboolean gst_camerabin_image_prepare_elements (GstCameraBinImage * imagebin); + +G_END_DECLS +#endif /* #ifndef __CAMERABIN_IMAGE_H__ */ Index: gst-plugins-good0.10/gst/camerabin/camerabinpreview.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinpreview.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,364 @@ +/* +* GStreamer +* Copyright (C) 2009 Nokia Corporation +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "camerabindebug.h" +#include "camerabingeneral.h" +#include "camerabinpreview.h" + +static void +save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer **p_buf = (GstBuffer **) data; + + *p_buf = gst_buffer_ref (buf); + + GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT, + *p_buf, GST_BUFFER_CAPS (*p_buf)); +} + +static gboolean +create_element (const gchar * factory_name, const gchar * elem_name, + GstElement ** element, GError ** err) +{ + *element = gst_element_factory_make (factory_name, elem_name); + if (*element) + return TRUE; + + if (err && *err == NULL) { + *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "cannot create element '%s' - please check your GStreamer installation", + factory_name); + } + + return FALSE; +} + + +/** + * gst_camerabin_preview_create_pipeline: + * @element: #GstCameraBin element + * @caps: pointer to the caps used in pipeline + * @src_filter: source filter element + * + * Create a preview converter pipeline that outputs the format defined in + * @caps parameter. + * + * Returns: New pipeline data structure, or NULL if error occured. + */ +GstCameraBinPreviewPipelineData * +gst_camerabin_preview_create_pipeline (GstElement * element, GstCaps * caps, + GstElement * src_filter) +{ + GstElement *csp = NULL, *vscale = NULL; + GError *error = NULL; + GstCameraBinPreviewPipelineData *data; + + g_return_val_if_fail (caps != NULL, NULL); + + GST_DEBUG ("creating elements"); + + data = g_new (GstCameraBinPreviewPipelineData, 1); + + /* We have multiple pipelines created by using this function, so we can't + * give a name to them. Another way would to ensure the uniqueness of the + * name here*/ + data->pipeline = gst_pipeline_new (NULL); + if (!data->pipeline) + goto create_error; + + if (!create_element ("appsrc", "prev_src", &data->appsrc, &error) || + !create_element ("videoscale", NULL, &vscale, &error) || + !create_element ("ffmpegcolorspace", NULL, &csp, &error) || + !create_element ("capsfilter", NULL, &data->capsfilter, &error) || + !create_element ("fakesink", "prev_sink", &data->appsink, &error)) + goto create_error; + + GST_DEBUG ("adding elements"); + gst_bin_add_many (GST_BIN (data->pipeline), data->appsrc, csp, + data->capsfilter, vscale, data->appsink, NULL); + if (src_filter) { + gst_bin_add (GST_BIN (data->pipeline), src_filter); + } + + data->element = element; + + GST_DEBUG ("preview format is: %" GST_PTR_FORMAT, caps); + + g_object_set (data->capsfilter, "caps", caps, NULL); + g_object_set (data->appsink, "preroll-queue-len", 1, "signal-handoffs", TRUE, + NULL); + g_object_set (vscale, "method", 0, NULL); + + GST_DEBUG ("linking src->vscale"); + if (!gst_element_link_pads (data->appsrc, "src", vscale, "sink")) + goto link_error; + + if (src_filter) { + GST_DEBUG ("linking vscale->src_filter"); + if (!gst_element_link_pads (vscale, "src", src_filter, "sink")) { + goto link_error; + } + GST_DEBUG ("linking filter->csp"); + if (!gst_element_link_pads (src_filter, "src", csp, "sink")) { + goto link_error; + } + } else { + GST_DEBUG ("linking vscale->csp"); + if (!gst_element_link_pads (vscale, "src", csp, "sink")) + goto link_error; + } + + GST_DEBUG ("linking csp->capsfilter"); + if (!gst_element_link_pads (csp, "src", data->capsfilter, "sink")) + goto link_error; + + GST_DEBUG ("linking capsfilter->sink"); + if (!gst_element_link_pads (data->capsfilter, "src", data->appsink, "sink")) + goto link_error; + + return data; + +create_error: + if (error) { + GST_WARNING ("Preview pipeline element creation failed: %s", + error->message); + g_error_free (error); + } + if (csp) + gst_object_unref (csp); + if (vscale) + gst_object_unref (vscale); + if (data->appsrc) + gst_object_unref (data->appsrc); + if (data->capsfilter) + gst_object_unref (data->capsfilter); + if (data->appsink) + gst_object_unref (data->appsink); + +link_error: + GST_WARNING ("Could not create preview pipeline"); + gst_camerabin_preview_destroy_pipeline (data); + + return NULL; +} + + +/** + * gst_camerabin_preview_destroy_pipeline: + * @data: the pipeline data to be destroyed + * + * Destroy preview converter pipeline. + */ +void +gst_camerabin_preview_destroy_pipeline (GstCameraBinPreviewPipelineData * data) +{ + if (data->pipeline) { + gst_element_set_state (data->pipeline, GST_STATE_NULL); + gst_object_unref (data->pipeline); + } + g_free (data); +} + + +/** + * gst_camerabin_preview_convert: + * @data: preview pipeline data to use + * @buf: #GstBuffer that contains the frame to be converted + * + * Create a preview image of the given frame. + * + * Returns: converted preview image, or NULL if operation failed. + */ +GstBuffer * +gst_camerabin_preview_convert (GstCameraBinPreviewPipelineData * data, + GstBuffer * buf) +{ + GstMessage *msg; + GstBuffer *result = NULL; + GError *error = NULL; + GstBus *bus; + GstElement *src, *sink; + GstBufferFlag bflags; + GstFlowReturn fret; + + g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL); + g_return_val_if_fail (data != NULL, NULL); + + if (data->pipeline == NULL) { + GST_WARNING ("pipeline is NULL"); + goto no_pipeline; + } + + src = gst_bin_get_by_name (GST_BIN (data->pipeline), "prev_src"); + sink = gst_bin_get_by_name (GST_BIN (data->pipeline), "prev_sink"); + + if (!src || !sink) { + GST_WARNING ("pipeline doesn't have src / sink elements"); + goto missing_elements; + } + + g_object_set (src, "size", (gint64) GST_BUFFER_SIZE (buf), + "blocksize", (guint32) GST_BUFFER_SIZE (buf), + "caps", GST_BUFFER_CAPS (buf), "num-buffers", 1, NULL); + + g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result); + + bflags = GST_BUFFER_FLAGS (buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_READONLY); + + GST_DEBUG ("running conversion pipeline, source is: %" GST_PTR_FORMAT, + GST_BUFFER_CAPS (buf)); + gst_element_set_state (data->pipeline, GST_STATE_PLAYING); + + g_signal_emit_by_name (src, "push-buffer", buf, &fret); + + bus = gst_element_get_bus (data->pipeline); + msg = gst_bus_timed_pop_filtered (bus, (25 * GST_SECOND), + GST_MESSAGE_ERROR | GST_MESSAGE_EOS); + gst_object_unref (bus); + + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS:{ + if (result) { + GST_DEBUG ("preview image successful: result = %p", result); + } else { + GST_WARNING ("EOS but no result frame?!"); + } + break; + } + case GST_MESSAGE_ERROR:{ + gchar *dbg = NULL; + + gst_message_parse_error (msg, &error, &dbg); + if (error) { + g_warning ("Could not make preview image: %s", error->message); + GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg)); + g_error_free (error); + } else { + g_warning ("Could not make preview image (and NULL error!)"); + } + g_free (dbg); + result = NULL; + break; + } + default:{ + g_return_val_if_reached (NULL); + } + } + gst_message_unref (msg); + } else { + g_warning ("Could not make preview image: %s", "timeout during conversion"); + result = NULL; + } + + g_signal_handlers_disconnect_by_func (sink, G_CALLBACK (save_result), + &result); + gst_element_set_state (data->pipeline, GST_STATE_READY); + + GST_BUFFER_FLAGS (buf) = bflags; + +done: + if (src) + gst_object_unref (src); + if (sink) + gst_object_unref (sink); + + return result; + + /* ERRORS */ +missing_elements: + { + g_warning ("Could not make preview image: %s", + "missing elements in pipeline (unknown error)"); + goto done; + } +no_pipeline: + { + g_warning ("Could not make preview image: %s", + "no pipeline (unknown error)"); + return NULL; + } +} + +/** + * gst_camerabin_preview_send_event: + * @data: preview pipeline data to use + * @evt: The #GstEvent to be pushed, takes ownership + * + * Pushes an event to the preview pipeline. + * + * Returns: True if the event was handled + */ +gboolean +gst_camerabin_preview_send_event (GstCameraBinPreviewPipelineData * data, + GstEvent * evt) +{ + GstElement *src; + + src = gst_bin_get_by_name (GST_BIN (data->pipeline), "prev_src"); + if (!src) { + GST_WARNING ("Preview pipeline doesn't have src element, can't push event"); + gst_event_unref (evt); + return FALSE; + } + + GST_DEBUG_OBJECT (data->element, "Pushing event %p to preview pipeline", evt); + + return gst_element_send_event (src, evt); +} + +/** + * gst_camerabin_preview_set_caps: + * @data: preview pipeline data to use + * @caps: New #GstCaps to be set for the pipeline + * + * Sets new caps for the preview pipeline + */ +void +gst_camerabin_preview_set_caps (GstCameraBinPreviewPipelineData * data, + GstCaps * caps) +{ + GstState state, pending; + GstStateChangeReturn ret; + + g_return_if_fail (data->pipeline != NULL); + g_return_if_fail (caps != NULL); + + ret = gst_element_get_state (data->pipeline, &state, &pending, 0); + if (ret == GST_STATE_CHANGE_FAILURE) { + /* make it try again */ + state = GST_STATE_PLAYING; + pending = GST_STATE_VOID_PENDING; + } + + gst_element_set_state (data->pipeline, GST_STATE_NULL); + g_object_set (data->capsfilter, "caps", caps, NULL); + if (pending != GST_STATE_VOID_PENDING) + state = pending; + gst_element_set_state (data->pipeline, state); +} Index: gst-plugins-good0.10/gst/camerabin/camerabinpreview.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinpreview.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,57 @@ +/* +* GStreamer +* Copyright (C) 2009 Nokia Corporation +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Library General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This library 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 +* Library General Public License for more details. +* +* You should have received a copy of the GNU Library General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 59 Temple Place - Suite 330, +* Boston, MA 02111-1307, USA. +*/ + +#ifndef __CAMERABINPREVIEW_H__ +#define __CAMERABINPREVIEW_H__ + +#include + +G_BEGIN_DECLS + +typedef struct +{ + GstElement *pipeline; + + GstElement *appsrc; + GstElement *capsfilter; + GstElement *appsink; + + GstElement *element; +} GstCameraBinPreviewPipelineData; + + +GstCameraBinPreviewPipelineData * gst_camerabin_preview_create_pipeline ( + GstElement *element, GstCaps *caps, GstElement *src_filter); + +void gst_camerabin_preview_destroy_pipeline ( + GstCameraBinPreviewPipelineData *data); + +GstBuffer *gst_camerabin_preview_convert ( + GstCameraBinPreviewPipelineData *data, GstBuffer *buf); + +gboolean gst_camerabin_preview_send_event ( + GstCameraBinPreviewPipelineData *pipeline, GstEvent *event); + +void gst_camerabin_preview_set_caps ( + GstCameraBinPreviewPipelineData *pipeline, GstCaps *caps); + +G_END_DECLS + +#endif /* __CAMERABINPREVIEW_H__ */ Index: gst-plugins-good0.10/gst/camerabin/camerabinvideo.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinvideo.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,843 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:camerabinvideo + * @short_description: video recording module of #GstCameraBin + * + * + * + * + * The pipeline for this module is: + * + * + * + *----------------------------------------------------------------------------- + * audiosrc -> audio_queue -> audioconvert -> volume -> audioenc + * > videomux -> filesink + * video_queue -> [timeoverlay] -> [csp] -> videoenc -> queue + * -> [post proc] -> tee < + * queue -> + *----------------------------------------------------------------------------- + * + * + * + * The properties of elements are: + * + * queue - "leaky", 2 (Leaky on downstream (old buffers)) + * + * + * + */ + +/* + * includes + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "camerabindebug.h" +#include "camerabingeneral.h" + +#include "camerabinvideo.h" + +/* + * defines and static global vars + */ + +/* internal element names */ + +#define DEFAULT_AUD_ENC "vorbisenc" +#define DEFAULT_VID_ENC "theoraenc" +#define DEFAULT_MUX "oggmux" +#define DEFAULT_SINK "filesink" + +#define DEFAULT_FLAGS 0 + +enum +{ + PROP_0, + PROP_FILENAME +}; + +static void gst_camerabin_video_dispose (GstCameraBinVideo * sink); +static void gst_camerabin_video_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_camerabin_video_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn +gst_camerabin_video_change_state (GstElement * element, + GstStateChange transition); + +static + gboolean camerabin_video_pad_tee_src0_have_buffer (GstPad * pad, + GstBuffer * buffer, gpointer u_data); +static gboolean camerabin_video_sink_have_event (GstPad * pad, GstEvent * event, + gpointer u_data); +static gboolean gst_camerabin_video_create_elements (GstCameraBinVideo * vid); +static void gst_camerabin_video_destroy_elements (GstCameraBinVideo * vid); + +GST_BOILERPLATE (GstCameraBinVideo, gst_camerabin_video, GstBin, GST_TYPE_BIN); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + +/* GObject methods implementation */ + +static void +gst_camerabin_video_base_init (gpointer klass) +{ + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_static_pad_template (eklass, &sink_template); + gst_element_class_add_static_pad_template (eklass, &src_template); + gst_element_class_set_details_simple (eklass, + "Video capture bin for camerabin", "Bin/Video", + "Process and store video data", + "Edgard Lima , " + "Nokia Corporation "); +} + +static void +gst_camerabin_video_class_init (GstCameraBinVideoClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *eklass = GST_ELEMENT_CLASS (klass); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = + (GObjectFinalizeFunc) GST_DEBUG_FUNCPTR (gst_camerabin_video_dispose); + eklass->change_state = GST_DEBUG_FUNCPTR (gst_camerabin_video_change_state); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_camerabin_video_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_camerabin_video_get_property); + + /** + * GstCameraBinVideo:filename: + * + * This property can be used to specify the filename of the video. + * + **/ + g_object_class_install_property (gobject_class, PROP_FILENAME, + g_param_spec_string ("filename", "Filename", + "Filename of the video to save", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_camerabin_video_init (GstCameraBinVideo * vid, + GstCameraBinVideoClass * g_class) +{ + vid->filename = g_string_new (""); + + vid->app_post = NULL; + vid->app_vid_enc = NULL; + vid->app_aud_enc = NULL; + vid->app_aud_src = NULL; + vid->app_mux = NULL; + + vid->aud_src = NULL; + vid->sink = NULL; + vid->tee = NULL; + vid->volume = NULL; + vid->video_queue = NULL; + + vid->tee_video_srcpad = NULL; + vid->tee_vf_srcpad = NULL; + + vid->pending_eos = NULL; + + vid->mute = ARG_DEFAULT_MUTE; + vid->flags = DEFAULT_FLAGS; + + vid->vid_src_probe_id = 0; + vid->vid_tee_probe_id = 0; + vid->vid_sink_probe_id = 0; + + /* Create src and sink ghost pads */ + vid->sinkpad = gst_ghost_pad_new_no_target ("sink", GST_PAD_SINK); + gst_element_add_pad (GST_ELEMENT (vid), vid->sinkpad); + + vid->srcpad = gst_ghost_pad_new_no_target ("src", GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (vid), vid->srcpad); + + /* Add probe for handling eos when stopping recording */ + vid->vid_sink_probe_id = gst_pad_add_event_probe (vid->sinkpad, + G_CALLBACK (camerabin_video_sink_have_event), vid); +} + +static void +gst_camerabin_video_dispose (GstCameraBinVideo * vid) +{ + GST_DEBUG_OBJECT (vid, "disposing"); + + g_string_free (vid->filename, TRUE); + vid->filename = NULL; + + if (vid->vid_sink_probe_id) { + gst_pad_remove_event_probe (vid->sinkpad, vid->vid_sink_probe_id); + vid->vid_sink_probe_id = 0; + } + + /* Note: if videobin was never set to READY state the + ownership of elements created by application were never + taken by bin and therefore gst_object_sink is called for + these elements (they may still be in floating state + and not unreffed properly without sinking first) + */ + if (vid->app_post) { + gst_object_sink (vid->app_post); + gst_object_unref (vid->app_post); + vid->app_post = NULL; + } + + if (vid->app_vid_enc) { + gst_object_sink (vid->app_vid_enc); + gst_object_unref (vid->app_vid_enc); + vid->app_vid_enc = NULL; + } + + if (vid->app_aud_enc) { + gst_object_sink (vid->app_aud_enc); + gst_object_unref (vid->app_aud_enc); + vid->app_aud_enc = NULL; + } + + if (vid->app_aud_src) { + gst_object_sink (vid->app_aud_src); + gst_object_unref (vid->app_aud_src); + vid->app_aud_src = NULL; + } + + if (vid->app_mux) { + gst_object_sink (vid->app_mux); + gst_object_unref (vid->app_mux); + vid->app_mux = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) vid); +} + + +static void +gst_camerabin_video_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBinVideo *bin = GST_CAMERABIN_VIDEO (object); + + switch (prop_id) { + case PROP_FILENAME: + g_string_assign (bin->filename, g_value_get_string (value)); + GST_INFO_OBJECT (bin, "received filename: '%s'", bin->filename->str); + if (bin->sink) { + g_object_set (G_OBJECT (bin->sink), "location", bin->filename->str, + NULL); + } else { + GST_INFO_OBJECT (bin, "no sink, not setting name yet"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camerabin_video_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBinVideo *bin = GST_CAMERABIN_VIDEO (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, bin->filename->str); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_camerabin_video_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCameraBinVideo *vid = GST_CAMERABIN_VIDEO (element); + + GST_DEBUG_OBJECT (element, "changing state: %s -> %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_camerabin_video_create_elements (vid)) { + return GST_STATE_CHANGE_FAILURE; + } + /* Don't change sink to READY yet to allow changing the + filename in READY state. */ + gst_element_set_locked_state (vid->sink, TRUE); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + vid->calculate_adjust_ts_video = TRUE; + g_object_set (G_OBJECT (vid->sink), "async", FALSE, NULL); + gst_element_set_locked_state (vid->sink, FALSE); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + vid->calculate_adjust_ts_video = TRUE; + break; + + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Set sink to NULL in order to write the file _now_ */ + GST_INFO ("write video file: %s", vid->filename->str); + gst_element_set_locked_state (vid->sink, TRUE); + gst_element_set_state (vid->sink, GST_STATE_NULL); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* Write debug graph to file */ + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (GST_ELEMENT_PARENT (vid)), + GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | + GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS, "videobin.playing"); + + if (vid->pending_eos) { + /* Video bin is still paused, so push eos directly to video queue */ + GST_DEBUG_OBJECT (vid, "pushing pending eos"); + gst_pad_push_event (vid->tee_video_srcpad, vid->pending_eos); + vid->pending_eos = NULL; + } + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* Reset counters related to timestamp rewriting */ + vid->adjust_ts_video = 0; + vid->last_ts_video = 0; + + if (vid->pending_eos) { + gst_event_unref (vid->pending_eos); + vid->pending_eos = NULL; + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_camerabin_video_destroy_elements (vid); + break; + default: + break; + } + + GST_DEBUG_OBJECT (element, "changed state: %s -> %s = %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)), + gst_element_state_change_return_get_name (ret)); + + return ret; +} + +/* + * static helper functions implementation + */ + +/* + * camerabin_video_pad_tee_src0_have_buffer: + * @pad: tee src pad leading to video encoding + * @event: received buffer + * @u_data: video bin object + * + * Buffer probe for rewriting video buffer timestamps. + * + * Returns: TRUE always + */ +static gboolean +camerabin_video_pad_tee_src0_have_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBinVideo *vid = (GstCameraBinVideo *) u_data; + + GST_LOG ("buffer in with size %d ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + + if (G_UNLIKELY (vid->calculate_adjust_ts_video)) { + GstEvent *event; + GstObject *tee; + GstPad *sinkpad; + + vid->adjust_ts_video = GST_BUFFER_TIMESTAMP (buffer) - vid->last_ts_video; + vid->calculate_adjust_ts_video = FALSE; + event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + 0, GST_CLOCK_TIME_NONE, vid->last_ts_video); + /* Send the newsegment to both view finder and video bin */ + tee = gst_pad_get_parent (pad); + sinkpad = gst_element_get_static_pad (GST_ELEMENT (tee), "sink"); + gst_pad_send_event (sinkpad, event); + gst_object_unref (tee); + gst_object_unref (sinkpad); + GST_LOG_OBJECT (vid, "vid ts adjustment: %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid->adjust_ts_video)); + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + } + GST_BUFFER_TIMESTAMP (buffer) -= vid->adjust_ts_video; + vid->last_ts_video = GST_BUFFER_TIMESTAMP (buffer); + if (GST_BUFFER_DURATION_IS_VALID (buffer)) + vid->last_ts_video += GST_BUFFER_DURATION (buffer); + + GST_LOG ("buffer out with size %d ts %" GST_TIME_FORMAT, + GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer))); + return TRUE; +} + +/* + * camerabin_video_sink_have_event: + * @pad: video bin sink pad + * @event: received event + * @u_data: video bin object + * + * Event probe for video bin eos handling. + * Copies the eos event to audio branch of video bin. + * + * Returns: FALSE to drop the event, TRUE otherwise + */ +static gboolean +camerabin_video_sink_have_event (GstPad * pad, GstEvent * event, + gpointer u_data) +{ + GstCameraBinVideo *vid = (GstCameraBinVideo *) u_data; + gboolean ret = TRUE; + + GST_DEBUG_OBJECT (vid, "got videobin sink event: %s", + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + if (vid->aud_src) { + GST_DEBUG_OBJECT (vid, "copying %s to audio branch", + GST_EVENT_TYPE_NAME (event)); + gst_element_send_event (vid->aud_src, gst_event_copy (event)); + } + + /* If we're paused, we can't pass eos to video now to avoid blocking. + Instead send eos when changing to playing next time. */ + if (GST_STATE (GST_ELEMENT (vid)) == GST_STATE_PAUSED) { + GST_DEBUG_OBJECT (vid, "paused, delay eos sending"); + vid->pending_eos = gst_event_ref (event); + ret = FALSE; /* Drop the event */ + } + break; + default: + break; + } + return ret; +} + +/* + * gst_camerabin_video_create_elements: + * @vid: a pointer to #GstCameraBinVideo + * + * This function creates the needed #GstElements and resources to record videos. + * Use gst_camerabin_video_destroy_elements() to free these resources. + * + * Returns: %TRUE if succeeded or FALSE if failed + */ +static gboolean +gst_camerabin_video_create_elements (GstCameraBinVideo * vid) +{ + GstPad *vid_sinkpad = NULL, *vid_srcpad = NULL; + GstBin *vidbin = GST_BIN (vid); + GstElement *queue = NULL; + + vid->adjust_ts_video = 0; + vid->last_ts_video = 0; + vid->calculate_adjust_ts_video = FALSE; + + /* Add video post processing element if any */ + if (vid->app_post) { + if (!gst_camerabin_add_element (vidbin, vid->app_post)) { + goto error; + } + vid_sinkpad = gst_element_get_static_pad (vid->app_post, "sink"); + } + + /* Add tee element */ + if (!(vid->tee = + gst_camerabin_create_and_add_element (vidbin, "tee", "video-tee"))) { + goto error; + } + + /* Set up sink ghost pad for video bin */ + if (!vid_sinkpad) { + vid_sinkpad = gst_element_get_static_pad (vid->tee, "sink"); + } + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->sinkpad), vid_sinkpad); + gst_object_unref (vid_sinkpad); + + /* Add queue element for video */ + vid->tee_video_srcpad = gst_element_get_request_pad (vid->tee, "src%d"); + + vid->video_queue = gst_element_factory_make ("queue", "video-queue"); + if (!gst_camerabin_add_element (vidbin, vid->video_queue)) { + goto error; + } + g_object_set (vid->video_queue, "silent", TRUE, NULL); + + /* Add probe for rewriting video timestamps */ + vid->vid_tee_probe_id = gst_pad_add_buffer_probe (vid->tee_video_srcpad, + G_CALLBACK (camerabin_video_pad_tee_src0_have_buffer), vid); + + if (vid->flags & GST_CAMERABIN_FLAG_VIDEO_COLOR_CONVERSION) { + /* Add colorspace converter */ + if (gst_camerabin_create_and_add_element (vidbin, + "ffmpegcolorspace", "video-ffmpegcolorspace") == NULL) { + goto error; + } + } + + /* Add user set or default video encoder element */ + if (vid->app_vid_enc) { + vid->vid_enc = vid->app_vid_enc; + if (!gst_camerabin_add_element (vidbin, vid->vid_enc)) { + goto error; + } + } else if (!(vid->vid_enc = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_VID_ENC, + "video-encoder"))) { + goto error; + } + + /* Add application set or default muxer element */ + if (vid->app_mux) { + vid->muxer = vid->app_mux; + if (!gst_camerabin_add_element (vidbin, vid->muxer)) { + goto error; + } + } else if (!(vid->muxer = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_MUX, + "video-muxer"))) { + goto error; + } + + /* Add sink element for storing the video */ + if (!(vid->sink = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_SINK, + "video-sink"))) { + goto error; + } + g_object_set (G_OBJECT (vid->sink), "location", vid->filename->str, "buffer-mode", 2, /* non buffered io */ + NULL); + + if (!(vid->flags & GST_CAMERABIN_FLAG_DISABLE_AUDIO)) { + /* Add application set or default audio source element */ + if (!(vid->aud_src = gst_camerabin_setup_default_element (vidbin, + vid->app_aud_src, "autoaudiosrc", DEFAULT_AUDIOSRC))) { + vid->aud_src = NULL; + goto error; + } else { + if (!gst_camerabin_add_element (vidbin, vid->aud_src)) + goto error; + } + + /* Add queue element for audio */ + queue = gst_element_factory_make ("queue", "audio-queue"); + if (!gst_camerabin_add_element (vidbin, queue)) { + goto error; + } + g_object_set (queue, "silent", TRUE, NULL); + + /* Add optional audio conversion and volume elements and + raise no errors if adding them fails */ + if (vid->flags & GST_CAMERABIN_FLAG_AUDIO_CONVERSION) { + if (!gst_camerabin_try_add_element (vidbin, + gst_element_factory_make ("audioconvert", NULL))) { + GST_WARNING_OBJECT (vid, "unable to add audio conversion element"); + /* gst_camerabin_try_add_element() destroyed the element */ + } + } + + vid->volume = gst_element_factory_make ("volume", NULL); + if (!gst_camerabin_try_add_element (vidbin, vid->volume)) { + GST_WARNING_OBJECT (vid, "unable to add volume element"); + /* gst_camerabin_try_add_element() destroyed the element */ + vid->volume = NULL; + } else { + g_object_set (vid->volume, "mute", vid->mute, NULL); + } + + /* Add application set or default audio encoder element */ + if (vid->app_aud_enc) { + vid->aud_enc = vid->app_aud_enc; + if (!gst_camerabin_add_element (vidbin, vid->aud_enc)) { + goto error; + } + } else if (!(vid->aud_enc = + gst_camerabin_create_and_add_element (vidbin, DEFAULT_AUD_ENC, + "audio-encoder"))) { + goto error; + } + + /* Link audio part to the muxer */ + if (!gst_element_link_pads_full (vid->aud_enc, NULL, vid->muxer, NULL, + GST_PAD_LINK_CHECK_CAPS)) { + GST_ELEMENT_ERROR (vid, CORE, NEGOTIATION, (NULL), + ("linking audio encoder and muxer failed")); + goto error; + } + } + /* Add queue leading out of the video bin and to view finder */ + vid->tee_vf_srcpad = gst_element_get_request_pad (vid->tee, "src%d"); + queue = gst_element_factory_make ("queue", "viewfinder-queue"); + if (!gst_camerabin_add_element (vidbin, queue)) { + goto error; + } + /* Set queue leaky, we don't want to block video encoder feed, but + prefer leaking view finder buffers instead. */ + g_object_set (G_OBJECT (queue), "leaky", 2, "max-size-buffers", 1, "silent", + TRUE, NULL); + + /* Set up src ghost pad for video bin */ + vid_srcpad = gst_element_get_static_pad (queue, "src"); + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->srcpad), vid_srcpad); + /* Never let video bin eos events reach view finder */ + vid->vid_src_probe_id = gst_pad_add_event_probe (vid_srcpad, + G_CALLBACK (gst_camerabin_drop_eos_probe), vid); + gst_object_unref (vid_srcpad); + + /* audio source is not always present and might be set to NULL during operation */ + if (vid->aud_src + && g_object_class_find_property (G_OBJECT_GET_CLASS (vid->aud_src), + "provide-clock")) { + g_object_set (vid->aud_src, "provide-clock", FALSE, NULL); + } + + GST_DEBUG ("created video elements"); + + return TRUE; + +error: + + gst_camerabin_video_destroy_elements (vid); + + return FALSE; + +} + +/* + * gst_camerabin_video_destroy_elements: + * @vid: a pointer to #GstCameraBinVideo + * + * This function destroys all the elements created by + * gst_camerabin_video_create_elements(). + * + */ +static void +gst_camerabin_video_destroy_elements (GstCameraBinVideo * vid) +{ + GST_DEBUG ("destroying video elements"); + + /* Remove EOS event probe from videobin srcpad (queue's srcpad) */ + if (vid->vid_src_probe_id) { + GstPad *pad = gst_ghost_pad_get_target (GST_GHOST_PAD (vid->srcpad)); + if (pad) { + gst_pad_remove_event_probe (pad, vid->vid_src_probe_id); + gst_object_unref (pad); + } + vid->vid_src_probe_id = 0; + } + + /* Remove buffer probe from video tee srcpad */ + if (vid->vid_tee_probe_id) { + gst_pad_remove_buffer_probe (vid->tee_video_srcpad, vid->vid_tee_probe_id); + vid->vid_tee_probe_id = 0; + } + + /* Release tee request pads */ + if (vid->tee_video_srcpad) { + gst_element_release_request_pad (vid->tee, vid->tee_video_srcpad); + gst_object_unref (vid->tee_video_srcpad); + vid->tee_video_srcpad = NULL; + } + if (vid->tee_vf_srcpad) { + gst_element_release_request_pad (vid->tee, vid->tee_vf_srcpad); + gst_object_unref (vid->tee_vf_srcpad); + vid->tee_vf_srcpad = NULL; + } + + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->sinkpad), NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD (vid->srcpad), NULL); + + gst_camerabin_remove_elements_from_bin (GST_BIN (vid)); + + vid->aud_src = NULL; + vid->sink = NULL; + vid->tee = NULL; + vid->volume = NULL; + vid->video_queue = NULL; + vid->vid_enc = NULL; + vid->aud_enc = NULL; + vid->muxer = NULL; + + if (vid->pending_eos) { + gst_event_unref (vid->pending_eos); + vid->pending_eos = NULL; + } +} + +/* + * Set & get mute and video capture elements + */ + +void +gst_camerabin_video_set_mute (GstCameraBinVideo * vid, gboolean mute) +{ + g_return_if_fail (vid != NULL); + + GST_DEBUG_OBJECT (vid, "setting mute %s", mute ? "on" : "off"); + vid->mute = mute; + if (vid->volume) { + g_object_set (vid->volume, "mute", mute, NULL); + } +} + +void +gst_camerabin_video_set_post (GstCameraBinVideo * vid, GstElement * post) +{ + GstElement **app_post; + GST_DEBUG_OBJECT (vid, "setting video post processing: %" GST_PTR_FORMAT, + post); + GST_OBJECT_LOCK (vid); + app_post = &vid->app_post; + gst_object_replace ((GstObject **) app_post, GST_OBJECT (post)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_video_enc (GstCameraBinVideo * vid, + GstElement * video_enc) +{ + GstElement **app_vid_enc; + GST_DEBUG_OBJECT (vid, "setting video encoder: %" GST_PTR_FORMAT, video_enc); + GST_OBJECT_LOCK (vid); + app_vid_enc = &vid->app_vid_enc; + gst_object_replace ((GstObject **) app_vid_enc, GST_OBJECT (video_enc)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_audio_enc (GstCameraBinVideo * vid, + GstElement * audio_enc) +{ + GstElement **app_aud_enc; + GST_DEBUG_OBJECT (vid, "setting audio encoder: %" GST_PTR_FORMAT, audio_enc); + GST_OBJECT_LOCK (vid); + app_aud_enc = &vid->app_aud_enc; + gst_object_replace ((GstObject **) app_aud_enc, GST_OBJECT (audio_enc)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_muxer (GstCameraBinVideo * vid, GstElement * muxer) +{ + GstElement **app_mux; + GST_DEBUG_OBJECT (vid, "setting muxer: %" GST_PTR_FORMAT, muxer); + GST_OBJECT_LOCK (vid); + app_mux = &vid->app_mux; + gst_object_replace ((GstObject **) app_mux, GST_OBJECT (muxer)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_audio_src (GstCameraBinVideo * vid, + GstElement * audio_src) +{ + GstElement **app_aud_src; + GST_DEBUG_OBJECT (vid, "setting audio source: %" GST_PTR_FORMAT, audio_src); + GST_OBJECT_LOCK (vid); + app_aud_src = &vid->app_aud_src; + gst_object_replace ((GstObject **) app_aud_src, GST_OBJECT (audio_src)); + GST_OBJECT_UNLOCK (vid); +} + +void +gst_camerabin_video_set_flags (GstCameraBinVideo * vid, GstCameraBinFlags flags) +{ + GST_DEBUG_OBJECT (vid, "setting video flags: %d", flags); + GST_OBJECT_LOCK (vid); + vid->flags = flags; + GST_OBJECT_UNLOCK (vid); +} + + +gboolean +gst_camerabin_video_get_mute (GstCameraBinVideo * vid) +{ + g_return_val_if_fail (vid != NULL, FALSE); + + if (vid->volume) { + g_object_get (vid->volume, "mute", &vid->mute, NULL); + } + + return vid->mute; +} + +GstElement * +gst_camerabin_video_get_post (GstCameraBinVideo * vid) +{ + return vid->app_post; +} + +GstElement * +gst_camerabin_video_get_video_enc (GstCameraBinVideo * vid) +{ + return vid->vid_enc ? vid->vid_enc : vid->app_vid_enc; +} + +GstElement * +gst_camerabin_video_get_audio_enc (GstCameraBinVideo * vid) +{ + return vid->aud_enc ? vid->aud_enc : vid->app_aud_enc; +} + +GstElement * +gst_camerabin_video_get_muxer (GstCameraBinVideo * vid) +{ + return vid->muxer ? vid->muxer : vid->app_mux; +} + +GstElement * +gst_camerabin_video_get_audio_src (GstCameraBinVideo * vid) +{ + return vid->aud_src ? vid->aud_src : vid->app_aud_src; +} Index: gst-plugins-good0.10/gst/camerabin/camerabinvideo.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/camerabinvideo.h 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,139 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_VIDEO_H__ +#define __CAMERABIN_VIDEO_H__ + +#include + +#include "gstcamerabin-enum.h" + +G_BEGIN_DECLS +#define ARG_DEFAULT_MUTE FALSE +#define GST_TYPE_CAMERABIN_VIDEO (gst_camerabin_video_get_type()) +#define GST_CAMERABIN_VIDEO_CAST(obj) ((GstCameraBinVideo*)(obj)) +#define GST_CAMERABIN_VIDEO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN_VIDEO,GstCameraBinVideo)) +#define GST_CAMERABIN_VIDEO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN_VIDEO,GstCameraBinVideoClass)) +#define GST_IS_CAMERABIN_VIDEO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN_VIDEO)) +#define GST_IS_CAMERABIN_VIDEO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN_VIDEO)) +/** + * GstCameraBinVideo: + * + * The opaque #GstCameraBinVideo structure. + */ +typedef struct _GstCameraBinVideo GstCameraBinVideo; +typedef struct _GstCameraBinVideoClass GstCameraBinVideoClass; + +struct _GstCameraBinVideo +{ + GstBin parent; + + GString *filename; + + /* A/V timestamp rewriting */ + guint64 adjust_ts_video; + guint64 last_ts_video; + gboolean calculate_adjust_ts_video; + + /* Sink and src pads of video bin */ + GstPad *sinkpad; + GstPad *srcpad; + + /* Tee src pads leading to video encoder and view finder */ + GstPad *tee_video_srcpad; + GstPad *tee_vf_srcpad; + + /* Application set elements */ + GstElement *app_post; /* Video post processing */ + GstElement *app_vid_enc; + GstElement *app_aud_enc; + GstElement *app_aud_src; + GstElement *app_mux; + + /* Other elements */ + GstElement *aud_src; /* Audio source */ + GstElement *sink; /* Sink for recorded video */ + GstElement *tee; /* Split output to view finder and recording sink */ + GstElement *volume; /* Volume for muting */ + GstElement *video_queue; /* Buffer for raw video frames */ + GstElement *vid_enc; /* Video encoder */ + GstElement *aud_enc; /* Audio encoder */ + GstElement *muxer; /* Muxer */ + + GstEvent *pending_eos; + + /* Probe IDs */ + gulong vid_src_probe_id; + gulong vid_tee_probe_id; + gulong vid_sink_probe_id; + + gboolean mute; + GstCameraBinFlags flags; +}; + +struct _GstCameraBinVideoClass +{ + GstBinClass parent_class; +}; + +GType gst_camerabin_video_get_type (void); + +/* + * external function prototypes + */ + +void gst_camerabin_video_set_mute (GstCameraBinVideo * vid, gboolean mute); + +void gst_camerabin_video_set_post (GstCameraBinVideo * vid, GstElement * post); + +void +gst_camerabin_video_set_video_enc (GstCameraBinVideo * vid, + GstElement * video_enc); + +void +gst_camerabin_video_set_audio_enc (GstCameraBinVideo * vid, + GstElement * audio_enc); + +void +gst_camerabin_video_set_muxer (GstCameraBinVideo * vid, GstElement * muxer); + +void +gst_camerabin_video_set_audio_src (GstCameraBinVideo * vid, + GstElement * audio_src); + +void +gst_camerabin_video_set_flags (GstCameraBinVideo * vid, + GstCameraBinFlags flags); + + +gboolean gst_camerabin_video_get_mute (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_post (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_video_enc (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_audio_enc (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_muxer (GstCameraBinVideo * vid); + +GstElement *gst_camerabin_video_get_audio_src (GstCameraBinVideo * vid); + +G_END_DECLS +#endif /* #ifndef __CAMERABIN_VIDEO_H__ */ Index: gst-plugins-good0.10/gst/camerabin/gstcamerabin-enum.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabin-enum.c 2012-02-09 14:44:27.400710798 +0200 @@ -0,0 +1,62 @@ +/* + * GStreamer + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gstcamerabin-enum.h" + +#define C_FLAGS(v) ((guint) v) + +static void +register_gst_camerabin_flags (GType * id) +{ + static const GFlagsValue values[] = { + {C_FLAGS (GST_CAMERABIN_FLAG_SOURCE_RESIZE), + "Enable source crop and scale", "source-resize"}, + {C_FLAGS (GST_CAMERABIN_FLAG_SOURCE_COLOR_CONVERSION), + "Enable colorspace conversion for video source", + "source-colorspace-conversion"}, + {C_FLAGS (GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION), + "Enable colorspace conversion for viewfinder", + "viewfinder-colorspace-conversion"}, + {C_FLAGS (GST_CAMERABIN_FLAG_VIEWFINDER_SCALE), + "Enable scale for viewfinder", "viewfinder-scale"}, + {C_FLAGS (GST_CAMERABIN_FLAG_AUDIO_CONVERSION), + "Enable audio conversion for video capture", "audio-conversion"}, + {C_FLAGS (GST_CAMERABIN_FLAG_DISABLE_AUDIO), + "Disable audio elements for video capture", "disable-audio"}, + {C_FLAGS (GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION), + "Enable colorspace conversion for still image", + "image-colorspace-conversion"}, + {C_FLAGS (GST_CAMERABIN_FLAG_VIDEO_COLOR_CONVERSION), + "Enable colorspace conversion for video capture", + "video-colorspace-conversion"}, + {0, NULL, NULL} + }; + *id = g_flags_register_static ("GstCameraBinFlags", values); +} + +GType +gst_camerabin_flags_get_type (void) +{ + static GType id; + static GOnce once = G_ONCE_INIT; + + g_once (&once, (GThreadFunc) register_gst_camerabin_flags, &id); + return id; +} Index: gst-plugins-good0.10/gst/camerabin/gstcamerabin-enum.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabin-enum.h 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,112 @@ +/* + * GStreamer + * Copyright (C) 2009 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_CAMERABIN_ENUM_H__ +#define __GST_CAMERABIN_ENUM_H__ + +#include + +G_BEGIN_DECLS + +enum +{ + ARG_0, + ARG_FILENAME, + ARG_MODE, + ARG_FLAGS, + ARG_MUTE, + ARG_ZOOM, + ARG_IMAGE_POST, + ARG_IMAGE_ENC, + ARG_IMAGE_FORMATTER, + ARG_VIDEO_POST, + ARG_VIDEO_ENC, + ARG_AUDIO_ENC, + ARG_VIDEO_MUX, + ARG_VF_SINK, + ARG_VIDEO_SRC, + ARG_AUDIO_SRC, + ARG_INPUT_CAPS, + ARG_FILTER_CAPS, + ARG_PREVIEW_CAPS, + ARG_WB_MODE, + ARG_COLOUR_TONE, + ARG_SCENE_MODE, + ARG_FLASH_MODE, + ARG_FOCUS_STATUS, + ARG_CAPABILITIES, + ARG_SHAKE_RISK, + ARG_EV_COMP, + ARG_ISO_SPEED, + ARG_APERTURE, + ARG_EXPOSURE, + ARG_VIDEO_SOURCE_FILTER, + ARG_IMAGE_CAPTURE_SUPPORTED_CAPS, + ARG_VIEWFINDER_FILTER, + ARG_FLICKER_MODE, + ARG_FOCUS_MODE, + ARG_BLOCK_VIEWFINDER, + ARG_IMAGE_CAPTURE_WIDTH, + ARG_IMAGE_CAPTURE_HEIGHT, + ARG_VIDEO_CAPTURE_WIDTH, + ARG_VIDEO_CAPTURE_HEIGHT, + ARG_VIDEO_CAPTURE_FRAMERATE, + ARG_PREVIEW_SOURCE_FILTER, + ARG_READY_FOR_CAPTURE, + ARG_IDLE +}; + +/** + * GstCameraBinFlags: + * @GST_CAMERABIN_FLAG_SOURCE_RESIZE: enable video crop and scale + * after capture + * @GST_CAMERABIN_FLAG_SOURCE_COLOR_CONVERSION: enable conversion + * of native video format by enabling ffmpegcolorspace + * @GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION: enable color + * conversion for viewfinder element + * @GST_CAMERABIN_FLAG_VIEWFINDER_SCALE: enable scaling in + * viewfinder element retaining aspect ratio + * @GST_CAMERABIN_FLAG_AUDIO_CONVERSION: enable audioconvert and + * audioresample elements + * @GST_CAMERABIN_FLAG_DISABLE_AUDIO: disable audio elements + * @GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION: enable color + * conversion for image output element + * @GST_CAMERABIN_FLAG_VIDEO_COLOR_CONVERSION: enable color + * conversion for video encoder element + * + * Extra flags to configure the behaviour of the sinks. + */ +typedef enum { + GST_CAMERABIN_FLAG_SOURCE_RESIZE = (1 << 0), + GST_CAMERABIN_FLAG_SOURCE_COLOR_CONVERSION = (1 << 1), + GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION = (1 << 2), + GST_CAMERABIN_FLAG_VIEWFINDER_SCALE = (1 << 3), + GST_CAMERABIN_FLAG_AUDIO_CONVERSION = (1 << 4), + GST_CAMERABIN_FLAG_DISABLE_AUDIO = (1 << 5), + GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION = (1 << 6), + GST_CAMERABIN_FLAG_VIDEO_COLOR_CONVERSION = (1 << 7) +} GstCameraBinFlags; + +#define GST_TYPE_CAMERABIN_FLAGS (gst_camerabin_flags_get_type()) +GType gst_camerabin_flags_get_type (void); + +G_END_DECLS + +#endif /* #ifndef __GST_CAMERABIN_ENUM_H__ */ Index: gst-plugins-good0.10/gst/camerabin/gstcamerabin-marshal.list =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabin-marshal.list 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,8 @@ +# glib-genmarshal --header --prefix=gst_camerabin camerabin_marshal.marshal > camerabin_marshal.h +# glib-genmarshal --body --prefix=gst_camerabin camerabin_marshal.marshal > camerabin_marshal.c + +VOID:INT,INT,INT,INT +VOID:INT,INT +BOOLEAN:STRING +INT64:VOID +VOID:OBJECT,INT64,INT64 Index: gst-plugins-good0.10/gst/camerabin/gstcamerabin.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabin.c 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,4323 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-camerabin + * + * GstCameraBin is a high-level camera object that encapsulates the gstreamer + * internals and provides a task based API for the application. It consists of + * three main data paths: view-finder, image capture and video capture. + * + * + * + * + * CameraBin structure + * Structural decomposition of CameraBin object. + * + * + * + * + * Example launch line + * |[ + * gst-launch -v -m camerabin + * ]| + * + * + * Image capture + * + * Image capture is selected by switching #GstCameraBin:mode to %MODE_IMAGE. + * Taking still images is initiated with the #GstCameraBin::capture-start action + * signal. Once the image has been captured, "image-captured" gst message is + * posted to the bus and capturing another image is possible. If application + * has set #GstCameraBin:preview-caps property, then a "preview-image" gst + * message is posted to bus containing preview image formatted according to + * specified caps. Eventually when image has been saved + * #GstCameraBin::image-done signal is emitted. + * + * Available resolutions can be taken from the #GstCameraBin:video-source-caps + * property. Image capture resolution can be set with + * #GstCameraBin::set-image-resolution action signal. + * + * Some video source elements implement the #GstPhotography interface, which contains + * functions and properties for setting photography parameters. One can use + * gst_bin_iterate_all_by_interface() to get a reference to it. + * + * + * + * + * Video capture + * + * Video capture is selected by switching #GstCameraBin:mode to %MODE_VIDEO. + * The capture is started with the #GstCameraBin::capture-start action signal + * too. In addition to image capture one can use #GstCameraBin::capture-pause to + * pause recording and #GstCameraBin::capture-stop to end recording. + * + * Available resolutions and fps can be taken from the + * #GstCameraBin:video-source-caps property. + * #GstCameraBin::set-video-resolution-fps action signal can be used to set + * frame rate and resolution for the video recording and view finder as well. + * + * + * + * States + * + * Elements within GstCameraBin are created and destroyed when switching + * between NULL and READY states. Therefore element properties should be set + * in NULL state. User set elements are not unreffed until GstCameraBin is + * unreffed or replaced by a new user set element. Initially only elements + * needed for view finder mode are created to speed up startup. Image bin and + * video bin elements are created when setting the mode or starting capture. + * GstCameraBin must be in the PLAYING state before #GstCameraBin::capture-start + * is called. + * + * + * + * Video and image previews + * + * GstCameraBin contains #GstCameraBin:preview-caps property, which is used to + * determine whether the application wants a preview image of the captured + * picture or video. When set, a GstMessage named "preview-image" will be sent. + * This message will contain a GstBuffer holding the preview image, converted + * to a format defined by those preview caps. The ownership of the preview + * image is kept in GstCameraBin, so application should ref the preview buffer + * object if it needs to use it elsewhere than in message handler. + * + * Defining preview caps is done by selecting the capturing #GstCameraBin:mode + * first and then setting the property. Camerabin remembers caps separately for + * both modes, so it is not necessary to set the caps again after changing the + * mode. + * + * + * + * + * + * Since the muxers tested so far have problems with discontinous buffers, QoS + * has been disabled, and then in order to record video, you MUST ensure that + * there is enough CPU to encode the video. Thus choose smart resolution and + * frames per second values. It is also highly recommended to avoid color + * conversions; make sure all the elements involved work with the same + * colorspace (i.e. rgb or yuv i420 or whatelse). + * + * + * + */ + +/* + * The pipeline in the camerabin is + * + * videosrc [ ! ffmpegcsp ] ! capsfilter ! crop ! scale ! capsfilter ! \ + * [ video_filter ! ] out-sel name=osel ! queue name=img_q + * + * View finder: + * osel. ! in-sel name=isel ! scale ! capsfilter [ ! ffmpegcsp ] ! vfsink + * + * Image bin: + * img_q. [ ! ipp ] ! ffmpegcsp ! imageenc ! metadatamux ! filesink + * + * Video bin: + * osel. ! tee name=t ! queue ! videoenc ! videomux name=mux ! filesink + * t. ! queue ! isel. + * audiosrc ! queue ! audioconvert ! volume ! audioenc ! mux. + * + * The properties of elements are: + * + * vfsink - "sync", FALSE, "qos", FALSE, "async", FALSE + * output-selector - "resend-latest", FALSE + * input-selector - "select-all", TRUE + */ + +/* + * includes + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include +#include +/* FIXME: include #include and use _(" ") */ + +#include "gstcamerabin.h" +#include "gstcamerabincolorbalance.h" + +#include "camerabindebug.h" +#include "camerabingeneral.h" +#include "camerabinpreview.h" + +#include "gstcamerabin-marshal.h" + +/* + * enum and types + */ + +enum +{ + /* action signals */ + CAPTURE_START_SIGNAL, + CAPTURE_STOP_SIGNAL, + CAPTURE_PAUSE_SIGNAL, + SET_VIDEO_RESOLUTION_FPS_SIGNAL, + SET_IMAGE_RESOLUTION_SIGNAL, + /* emit signals */ + IMG_DONE_SIGNAL, + LAST_SIGNAL +}; + + +/* + * defines and static global vars + */ + +static guint camerabin_signals[LAST_SIGNAL]; + +#define GST_TYPE_CAMERABIN_MODE (gst_camerabin_mode_get_type ()) + +/* default and range values for args */ + +#define DEFAULT_MODE MODE_IMAGE +#define DEFAULT_ZOOM 1.0 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_CAPTURE_WIDTH 800 +#define DEFAULT_CAPTURE_HEIGHT 600 +#define DEFAULT_FPS_N 0 /* makes it use the default */ +#define DEFAULT_FPS_D 1 + +#define CAMERABIN_DEFAULT_VF_CAPS "video/x-raw-yuv,format=(fourcc)I420" +#define CAMERABIN_MAX_VF_WIDTH 848 +#define CAMERABIN_MAX_VF_HEIGHT 848 + +#define DEFAULT_FLAGS GST_CAMERABIN_FLAG_SOURCE_RESIZE | \ + GST_CAMERABIN_FLAG_VIEWFINDER_SCALE | \ + GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION | \ + GST_CAMERABIN_FLAG_IMAGE_COLOR_CONVERSION | \ + GST_CAMERABIN_FLAG_VIDEO_COLOR_CONVERSION | \ + GST_CAMERABIN_FLAG_AUDIO_CONVERSION + +/* Using "bilinear" as default zoom method */ +#define CAMERABIN_DEFAULT_ZOOM_METHOD 1 + +#define MIN_ZOOM 1.0 +#define MAX_ZOOM 10.0 +#define ZOOM_1X MIN_ZOOM + +/* FIXME: this is v4l2camsrc specific */ +#define DEFAULT_V4L2CAMSRC_DRIVER_NAME "omap3cam" + +#define DEFAULT_BLOCK_VIEWFINDER FALSE +#define DEFAULT_READY_FOR_CAPTURE TRUE + +/* message names */ +#define PREVIEW_MESSAGE_NAME "preview-image" +#define IMG_CAPTURED_MESSAGE_NAME "image-captured" + +#define CAMERABIN_PROCESSING_INC_UNLOCKED(c) \ + (c)->processing_counter += 1; \ + GST_DEBUG_OBJECT ((c), "Processing counter incremented to: %d", \ + (c)->processing_counter); \ + if ((c)->processing_counter == 1) \ + g_object_notify (G_OBJECT (c), "idle"); \ + +#define CAMERABIN_PROCESSING_DEC_UNLOCKED(c) \ + (c)->processing_counter -= 1; \ + GST_DEBUG_OBJECT ((c), "Processing counter decremented to: %d", \ + (c)->processing_counter); \ + g_assert ((c)->processing_counter >= 0); \ + if ((c)->processing_counter == 0) \ + g_object_notify (G_OBJECT (c), "idle"); \ + +#define CAMERABIN_PROCESSING_INC(c) \ + g_mutex_lock ((c)->capture_mutex); \ + CAMERABIN_PROCESSING_INC_UNLOCKED ((c)); \ + g_mutex_unlock ((c)->capture_mutex); \ + +#define CAMERABIN_PROCESSING_DEC(c) \ + g_mutex_lock ((c)->capture_mutex); \ + CAMERABIN_PROCESSING_DEC_UNLOCKED ((c)); \ + g_mutex_unlock ((c)->capture_mutex); \ + +/* + * static helper functions declaration + */ + +static void camerabin_setup_src_elements (GstCameraBin * camera); + +static gboolean camerabin_create_src_elements (GstCameraBin * camera); + +static void camerabin_setup_view_elements (GstCameraBin * camera); + +static gboolean camerabin_create_view_elements (GstCameraBin * camera); + +static gboolean camerabin_create_elements (GstCameraBin * camera); + +static void camerabin_destroy_elements (GstCameraBin * camera); + +static void camerabin_dispose_elements (GstCameraBin * camera); + +static void gst_camerabin_change_mode (GstCameraBin * camera, gint mode); + +static void +gst_camerabin_set_flags (GstCameraBin * camera, GstCameraBinFlags flags); + +static void +gst_camerabin_change_filename (GstCameraBin * camera, const gchar * name); + +static void gst_camerabin_setup_zoom (GstCameraBin * camera); + +static GstCaps *gst_camerabin_get_allowed_input_caps (GstCameraBin * camera); + +static void gst_camerabin_rewrite_tags (GstCameraBin * camera); + +static void +gst_camerabin_set_capsfilter_caps (GstCameraBin * camera, GstCaps * new_caps); + +static void gst_camerabin_start_image_capture (GstCameraBin * camera); + +static void gst_camerabin_start_video_recording (GstCameraBin * camera); + +static void +camerabin_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data); + +static gboolean +gst_camerabin_have_img_buffer (GstPad * pad, GstMiniObject * obj, + gpointer u_data); +static gboolean +gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data); +static gboolean +gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj, + gpointer u_data); +static gboolean +gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data); + +static void gst_camerabin_reset_to_view_finder (GstCameraBin * camera); + +static void gst_camerabin_do_stop (GstCameraBin * camera); + +static void +gst_camerabin_set_allowed_framerate (GstCameraBin * camera, + GstCaps * filter_caps); + +static guint32 get_srcpad_current_format (GstElement * element); + +static const GValue *gst_camerabin_find_better_framerate (GstCameraBin * camera, + GstStructure * st, const GValue * orig_framerate); + +static void +gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps); + +static void gst_camerabin_finish_image_capture (GstCameraBin * camera); +static void gst_camerabin_adapt_image_capture (GstCameraBin * camera, + GstCaps * new_caps); +static void gst_camerabin_scene_mode_notify_cb (GObject * video_source, + GParamSpec * pspec, gpointer user_data); +static void gst_camerabin_zoom_notify_cb (GObject * video_source, + GParamSpec * pspec, gpointer user_data); +static void gst_camerabin_monitor_video_source_properties (GstCameraBin * + camera); +static void gst_camerabin_configure_format (GstCameraBin * camera, + GstCaps * caps); +static gboolean +copy_missing_fields (GQuark field_id, const GValue * value, gpointer user_data); + +/* + * GObject callback functions declaration + */ + +static void gst_camerabin_dispose (GObject * object); + +static void gst_camerabin_finalize (GObject * object); + +static void gst_camerabin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static void gst_camerabin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +/* + * GstElement function declarations + */ + +static GstStateChangeReturn +gst_camerabin_change_state (GstElement * element, GstStateChange transition); + +static GstClock *gst_camerabin_provide_clock (GstElement * element); + +/* + * GstBin function declarations + */ +static void +gst_camerabin_handle_message_func (GstBin * bin, GstMessage * message); + + +/* + * Action signal function declarations + */ + +static void gst_camerabin_capture_start (GstCameraBin * camera); + +static void gst_camerabin_capture_stop (GstCameraBin * camera); + +static void gst_camerabin_capture_pause (GstCameraBin * camera); + +static void +gst_camerabin_set_image_capture_caps (GstCameraBin * camera, gint width, + gint height); + +static void +gst_camerabin_set_video_resolution_fps (GstCameraBin * camera, gint width, + gint height, gint fps_n, gint fps_d); +static void +do_set_video_resolution_fps (GstCameraBin * camera, gint width, + gint height, gint fps_n, gint fps_d); + +static void +gst_camerabin_set_image_resolution (GstCameraBin * camera, gint width, + gint height); + + +/* + * GST BOILERPLATE and GObject types + */ + +static GType +gst_camerabin_mode_get_type (void) +{ + static GType gtype = 0; + + if (gtype == 0) { + static const GEnumValue values[] = { + {MODE_IMAGE, "Still image capture (default)", "mode-image"}, + {MODE_VIDEO, "Video recording", "mode-video"}, + {0, NULL, NULL} + }; + + gtype = g_enum_register_static ("GstCameraBinMode", values); + } + return gtype; +} + + +static gboolean +gst_camerabin_iface_supported (GstImplementsInterface * iface, GType iface_type) +{ + GstCameraBin *camera = GST_CAMERABIN (iface); + + if (iface_type == GST_TYPE_COLOR_BALANCE) { + if (camera->src_vid_src) { + return GST_IS_COLOR_BALANCE (camera->src_vid_src); + } + } else if (iface_type == GST_TYPE_TAG_SETTER) { + /* Note: Tag setter elements aren't + present when image and video bin in NULL */ + GstElement *setter; + setter = gst_bin_get_by_interface (GST_BIN (camera), iface_type); + if (setter) { + gst_object_unref (setter); + return TRUE; + } else { + return FALSE; + } + } + return FALSE; +} + +static void +gst_camerabin_interface_init (GstImplementsInterfaceClass * klass) +{ + /* + * default virtual functions + */ + klass->supported = gst_camerabin_iface_supported; +} + +static void +camerabin_init_interfaces (GType type) +{ + + static const GInterfaceInfo camerabin_info = { + (GInterfaceInitFunc) gst_camerabin_interface_init, + NULL, + NULL, + }; + + static const GInterfaceInfo camerabin_color_balance_info = { + (GInterfaceInitFunc) gst_camerabin_color_balance_init, + NULL, + NULL, + }; + + static const GInterfaceInfo camerabin_tagsetter_info = { + NULL, + NULL, + NULL, + }; + + g_type_add_interface_static (type, + GST_TYPE_IMPLEMENTS_INTERFACE, &camerabin_info); + + g_type_add_interface_static (type, GST_TYPE_COLOR_BALANCE, + &camerabin_color_balance_info); + + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, + &camerabin_tagsetter_info); +} + +GST_BOILERPLATE_FULL (GstCameraBin, gst_camerabin, GstPipeline, + GST_TYPE_PIPELINE, camerabin_init_interfaces); + +/* + * static helper functions implementation + */ + +/* + * camerabin_setup_src_elements: + * @camera: camerabin object + * + * This function updates camerabin capsfilters according + * to fps, resolution and zoom that have been configured + * to camerabin. + */ +static void +camerabin_setup_src_elements (GstCameraBin * camera) +{ + GstStructure *st; + GstCaps *new_caps; + gboolean detect_framerate = FALSE; + + /* clear video update status */ + camera->video_capture_caps_update = FALSE; + + if (!camera->view_finder_caps) { + st = gst_structure_from_string (CAMERABIN_DEFAULT_VF_CAPS, NULL); + } else { + st = gst_structure_copy (gst_caps_get_structure (camera->view_finder_caps, + 0)); + } + + gst_camerabin_monitor_video_source_properties (camera); + + if (camera->app_width > 0 && camera->app_height > 0) { + gst_structure_set (st, + "width", G_TYPE_INT, camera->app_width, + "height", G_TYPE_INT, camera->app_height, NULL); + } + + if (camera->app_fps_n > 0 && camera->app_fps_d > 0) { + if (camera->night_mode) { + GST_INFO_OBJECT (camera, "night mode, lowest allowed fps will be forced"); + camera->pre_night_fps_n = camera->app_fps_n; + camera->pre_night_fps_d = camera->app_fps_d; + detect_framerate = TRUE; + } else { + gst_structure_set (st, + "framerate", GST_TYPE_FRACTION, camera->app_fps_n, + camera->app_fps_d, NULL); + new_caps = gst_caps_new_full (st, NULL); + } + } else { + GST_DEBUG_OBJECT (camera, "no framerate specified"); + detect_framerate = TRUE; + } + + if (detect_framerate) { + GST_DEBUG_OBJECT (camera, "detecting allowed framerate"); + /* Remove old framerate if any */ + if (gst_structure_has_field (st, "framerate")) { + gst_structure_remove_field (st, "framerate"); + } + new_caps = gst_caps_new_full (st, NULL); + + /* Set allowed framerate for the resolution */ + gst_camerabin_set_allowed_framerate (camera, new_caps); + } + + /* Set default zoom method */ + if (camera->src_zoom_scale) { + g_object_set (camera->src_zoom_scale, "method", + CAMERABIN_DEFAULT_ZOOM_METHOD, NULL); + } + /* we create new caps in any way and they take ownership of the structure st */ + gst_caps_replace (&camera->view_finder_caps, new_caps); + gst_caps_unref (new_caps); + + /* Set caps for view finder mode */ + /* This also sets zoom */ + gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); +} + +/* + * camerabin_create_src_elements: + * @camera: camerabin object + * + * This function creates and links upstream side elements for camerabin. + * videosrc ! cspconv ! capsfilter ! crop ! scale ! capsfilter ! out-sel ! + * + * Returns: TRUE, if elements were successfully created, FALSE otherwise + */ +static gboolean +camerabin_create_src_elements (GstCameraBin * camera) +{ + gboolean ret = FALSE; + GstBin *cbin = GST_BIN (camera); + gchar *driver_name = NULL; + + /* Add application set or default video src element */ + if (!(camera->src_vid_src = gst_camerabin_setup_default_element (cbin, + camera->app_vid_src, "autovideosrc", DEFAULT_VIDEOSRC))) { + camera->src_vid_src = NULL; + goto done; + } else { + if (!gst_camerabin_add_element (cbin, camera->src_vid_src)) + goto done; + } + if (camera->flags & GST_CAMERABIN_FLAG_SOURCE_COLOR_CONVERSION) { + if (!gst_camerabin_create_and_add_element (cbin, "ffmpegcolorspace", + "src-ffmpegcolorspace")) + goto done; + } + if (!(camera->src_filter = + gst_camerabin_create_and_add_element (cbin, "capsfilter", + "src-capsfilter"))) + goto done; + if (camera->flags & GST_CAMERABIN_FLAG_SOURCE_RESIZE) { + if (!(camera->src_zoom_crop = + gst_camerabin_create_and_add_element (cbin, "videocrop", + "src-videocrop"))) + goto done; + if (!(camera->src_zoom_scale = + gst_camerabin_create_and_add_element (cbin, "videoscale", + "src-videoscale"))) + goto done; + if (!(camera->src_zoom_filter = + gst_camerabin_create_and_add_element (cbin, "capsfilter", + "src-resize-capsfilter"))) + goto done; + } + if (camera->app_video_filter) { + if (!gst_camerabin_add_element (cbin, camera->app_video_filter)) { + goto done; + } + } + if (!(camera->src_out_sel = + gst_camerabin_create_and_add_element (cbin, "output-selector", NULL))) + goto done; + + /* Set pad-negotiation-mode to active */ + g_object_set (camera->src_out_sel, "pad-negotiation-mode", 2, NULL); + + /* Set default "driver-name" for v4l2camsrc if not set */ + /* FIXME: v4l2camsrc specific */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), + "driver-name")) { + g_object_get (G_OBJECT (camera->src_vid_src), "driver-name", + &driver_name, NULL); + if (!driver_name) { + g_object_set (G_OBJECT (camera->src_vid_src), "driver-name", + DEFAULT_V4L2CAMSRC_DRIVER_NAME, NULL); + } + } + + ret = TRUE; +done: + return ret; +} + +/* + * camerabin_setup_view_elements: + * @camera: camerabin object + * + * This function configures properties for view finder sink element. + */ +static void +camerabin_setup_view_elements (GstCameraBin * camera) +{ + GST_DEBUG_OBJECT (camera, "setting view finder properties"); + g_object_set (G_OBJECT (camera->view_in_sel), "select-all", TRUE, NULL); + /* Set properties for view finder sink */ + /* Find the actual sink if using bin like autovideosink */ + if (GST_IS_BIN (camera->view_sink)) { + GList *child = NULL, *children = GST_BIN_CHILDREN (camera->view_sink); + for (child = children; child != NULL; child = g_list_next (child)) { + GObject *ch = G_OBJECT (child->data); + if (g_object_class_find_property (G_OBJECT_GET_CLASS (ch), "sync")) { + g_object_set (G_OBJECT (ch), "sync", FALSE, "qos", FALSE, "async", + FALSE, NULL); + } + } + } else { + g_object_set (G_OBJECT (camera->view_sink), "sync", FALSE, "qos", FALSE, + "async", FALSE, NULL); + } +} + +/* + * camerabin_create_view_elements: + * @camera: camerabin object + * + * This function creates and links downstream side elements for camerabin. + * ! scale ! cspconv ! view finder sink + * + * Returns: TRUE, if elements were successfully created, FALSE otherwise + */ +static gboolean +camerabin_create_view_elements (GstCameraBin * camera) +{ + const GList *pads; + GstBin *cbin = GST_BIN (camera); + + if (!(camera->view_in_sel = + gst_camerabin_create_and_add_element (cbin, "input-selector", + NULL))) { + goto error; + } + + /* Look for recently added input selector sink pad, we need to release it later */ + pads = GST_ELEMENT_PADS (camera->view_in_sel); + while (pads != NULL + && (GST_PAD_DIRECTION (GST_PAD (pads->data)) != GST_PAD_SINK)) { + pads = g_list_next (pads); + } + camera->pad_view_src = GST_PAD (pads->data); + + /* Add videoscale in case we need to downscale frame for view finder */ + if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_SCALE) { + if (!(camera->view_scale = + gst_camerabin_create_and_add_element (cbin, "videoscale", + "vf-videoscale"))) { + goto error; + } + + /* Add capsfilter to maintain aspect ratio while scaling */ + if (!(camera->aspect_filter = + gst_camerabin_create_and_add_element (cbin, "capsfilter", + "vf-scale-capsfilter"))) { + goto error; + } + } + if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_COLOR_CONVERSION) { + if (!gst_camerabin_create_and_add_element (cbin, "ffmpegcolorspace", + "vf-ffmpegcolorspace")) { + goto error; + } + } + + if (camera->app_viewfinder_filter) { + if (!gst_camerabin_add_element (GST_BIN (camera), + camera->app_viewfinder_filter)) { + goto error; + } + } + + /* Add application set or default video sink element */ + if (!(camera->view_sink = gst_camerabin_setup_default_element (cbin, + camera->app_vf_sink, "autovideosink", DEFAULT_VIDEOSINK))) { + camera->view_sink = NULL; + goto error; + } else { + if (!gst_camerabin_add_element (cbin, camera->view_sink)) + goto error; + } + + return TRUE; +error: + return FALSE; +} + +/* + * camerabin_create_elements: + * @camera: camerabin object + * + * This function creates and links all elements for camerabin, + * + * Returns: TRUE, if elements were successfully created, FALSE otherwise + */ +static gboolean +camerabin_create_elements (GstCameraBin * camera) +{ + gboolean ret = FALSE; + GstPadLinkReturn link_ret = GST_PAD_LINK_REFUSED; + GstPad *unconnected_pad; + + GST_LOG_OBJECT (camera, "creating elements"); + + /* Create "src" elements */ + if (!camerabin_create_src_elements (camera)) { + goto done; + } + + camera->pad_src_img = + gst_element_get_request_pad (camera->src_out_sel, "src%d"); + + gst_pad_add_data_probe (camera->pad_src_img, + G_CALLBACK (gst_camerabin_have_img_buffer), camera); + + /* Add queue leading to image bin */ + camera->img_queue = gst_element_factory_make ("queue", "image-queue"); + if (!gst_camerabin_add_element (GST_BIN (camera), camera->img_queue)) { + goto done; + } + + /* To avoid deadlock, we won't restrict the image queue size */ + /* FIXME: actually we would like to have some kind of restriction here (size), + but deadlocks must be handled somehow... */ + g_object_set (G_OBJECT (camera->img_queue), "max-size-buffers", 0, + "max-size-bytes", 0, "max-size-time", G_GUINT64_CONSTANT (0), NULL); + g_object_set (camera->img_queue, "silent", TRUE, NULL); + + camera->pad_src_queue = gst_element_get_static_pad (camera->img_queue, "src"); + + gst_pad_add_data_probe (camera->pad_src_queue, + G_CALLBACK (gst_camerabin_have_queue_data), camera); + + /* Add image bin */ + if (!gst_camerabin_add_element (GST_BIN (camera), camera->imgbin)) { + goto done; + } + + camera->pad_src_view = + gst_element_get_request_pad (camera->src_out_sel, "src%d"); + + /* Create view finder elements */ + if (!camerabin_create_view_elements (camera)) { + GST_WARNING_OBJECT (camera, "creating view finder elements failed"); + goto done; + } + + /* Set view finder active as default */ + g_object_set (G_OBJECT (camera->src_out_sel), "active-pad", + camera->pad_src_view, NULL); + + /* Add video bin */ + camera->pad_src_vid = + gst_element_get_request_pad (camera->src_out_sel, "src%d"); + if (!gst_camerabin_add_element (GST_BIN (camera), camera->vidbin)) { + goto done; + } + gst_pad_add_buffer_probe (camera->pad_src_vid, + G_CALLBACK (gst_camerabin_have_vid_buffer), camera); + + /* Link video bin ! view finder */ + unconnected_pad = gst_bin_find_unlinked_pad (GST_BIN (camera), GST_PAD_SRC); + camera->pad_view_vid = + gst_element_get_request_pad (camera->view_in_sel, "sink%d"); + link_ret = + gst_pad_link_full (unconnected_pad, camera->pad_view_vid, + GST_PAD_LINK_CHECK_CAPS); + gst_object_unref (unconnected_pad); + if (GST_PAD_LINK_FAILED (link_ret)) { + GST_ELEMENT_ERROR (camera, CORE, NEGOTIATION, (NULL), + ("linking video bin and view finder failed")); + goto done; + } + + ret = TRUE; + +done: + + if (FALSE == ret) + camerabin_destroy_elements (camera); + + return ret; +} + +/* + * camerabin_destroy_elements: + * @camera: camerabin object + * + * This function removes all elements from camerabin. + */ +static void +camerabin_destroy_elements (GstCameraBin * camera) +{ + GST_DEBUG_OBJECT (camera, "destroying elements"); + + /* Release request pads */ + if (camera->pad_view_vid) { + gst_element_release_request_pad (camera->view_in_sel, camera->pad_view_vid); + gst_object_unref (camera->pad_view_vid); + camera->pad_view_vid = NULL; + } + if (camera->pad_src_vid) { + gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_vid); + gst_object_unref (camera->pad_src_vid); + camera->pad_src_vid = NULL; + } + if (camera->pad_src_img) { + gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_img); + gst_object_unref (camera->pad_src_img); + camera->pad_src_img = NULL; + } + if (camera->pad_view_src) { + gst_element_release_request_pad (camera->view_in_sel, camera->pad_view_src); + /* don't unref, we have not requested it */ + camera->pad_view_src = NULL; + } + if (camera->pad_src_view) { + gst_element_release_request_pad (camera->src_out_sel, camera->pad_src_view); + gst_object_unref (camera->pad_src_view); + camera->pad_src_view = NULL; + } + + if (camera->pad_src_queue) { + gst_object_unref (camera->pad_src_queue); + camera->pad_src_queue = NULL; + } + + /* view finder elements */ + camera->view_in_sel = NULL; + camera->view_scale = NULL; + camera->aspect_filter = NULL; + camera->view_sink = NULL; + + /* source elements */ + camera->src_vid_src = NULL; + camera->src_filter = NULL; + camera->src_zoom_crop = NULL; + camera->src_zoom_scale = NULL; + camera->src_zoom_filter = NULL; + camera->src_out_sel = NULL; + + camera->active_bin = NULL; + + /* Reset caps data as the elements might be completely different next + * time we 'start' */ + if (camera->view_finder_caps) { + gst_caps_replace (&camera->view_finder_caps, NULL); + } + gst_caps_replace (&camera->allowed_caps, NULL); + camera->fps_n = camera->fps_d = 0; + camera->width = camera->height = 0; + + /* Remove elements */ + gst_camerabin_remove_elements_from_bin (GST_BIN (camera)); +} + +/* + * camerabin_dispose_elements: + * @camera: camerabin object + * + * This function releases all allocated camerabin resources. + */ +static void +camerabin_dispose_elements (GstCameraBin * camera) +{ + GST_INFO ("cleaning"); + + if (camera->capture_mutex) { + g_mutex_free (camera->capture_mutex); + camera->capture_mutex = NULL; + } + if (camera->cond) { + g_cond_free (camera->cond); + camera->cond = NULL; + } + if (camera->filename) { + g_string_free (camera->filename, TRUE); + camera->filename = NULL; + } + /* Unref application set elements */ + if (camera->app_vf_sink) { + gst_object_unref (camera->app_vf_sink); + camera->app_vf_sink = NULL; + } + if (camera->app_vid_src) { + gst_object_unref (camera->app_vid_src); + camera->app_vid_src = NULL; + } + + if (camera->app_video_filter) { + gst_object_unref (camera->app_video_filter); + camera->app_video_filter = NULL; + } + + if (camera->app_viewfinder_filter) { + gst_object_unref (camera->app_viewfinder_filter); + camera->app_viewfinder_filter = NULL; + } + + if (camera->app_preview_source_filter) { + gst_object_unref (camera->app_preview_source_filter); + camera->app_preview_source_filter = NULL; + } + + if (camera->app_video_preview_source_filter) { + gst_object_unref (camera->app_video_preview_source_filter); + camera->app_video_preview_source_filter = NULL; + } + + /* Free caps */ + gst_caps_replace (&camera->image_capture_caps, NULL); + gst_caps_replace (&camera->view_finder_caps, NULL); + gst_caps_replace (&camera->allowed_caps, NULL); + gst_caps_replace (&camera->preview_caps, NULL); + gst_caps_replace (&camera->video_preview_caps, NULL); + gst_buffer_replace (&camera->video_preview_buffer, NULL); + + if (camera->event_tags) { + gst_tag_list_free (camera->event_tags); + camera->event_tags = NULL; + } +} + +/* + * gst_camerabin_image_capture_continue: + * @camera: camerabin object + * @filename: filename of the finished image + * + * Notify application that image has been saved with a signal. + * + * Returns TRUE if another image should be captured, FALSE otherwise. + */ +static gboolean +gst_camerabin_image_capture_continue (GstCameraBin * camera, + const gchar * filename) +{ + gboolean cont = FALSE; + + GST_DEBUG_OBJECT (camera, "emitting img_done signal, filename: %s", filename); + g_signal_emit (G_OBJECT (camera), camerabin_signals[IMG_DONE_SIGNAL], 0, + filename, &cont); + + /* If the app wants to continue make sure new filename has been set */ + if (cont && g_str_equal (camera->filename->str, "")) { + GST_ELEMENT_ERROR (camera, RESOURCE, NOT_FOUND, + ("cannot continue capture, no filename has been set"), (NULL)); + cont = FALSE; + } + + return cont; +} + +/* + * gst_camerabin_change_mode: + * @camera: camerabin object + * @mode: image or video mode + * + * Change camerabin mode between image and video capture. + * Changing mode will stop ongoing capture. + */ +static void +gst_camerabin_change_mode (GstCameraBin * camera, gint mode) +{ + if (camera->mode != mode || !camera->active_bin) { + GstState state, pending_state; + + GST_DEBUG_OBJECT (camera, "setting mode: %d (old_mode=%d)", + mode, camera->mode); + /* Interrupt ongoing capture */ + gst_camerabin_do_stop (camera); + + /* reset night-mode stored values */ + camera->pre_night_fps_n = 0; + camera->pre_night_fps_d = 1; + + camera->mode = mode; + gst_element_get_state (GST_ELEMENT (camera), &state, &pending_state, 0); + if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING || + pending_state == GST_STATE_PAUSED + || pending_state == GST_STATE_PLAYING) { + if (camera->active_bin) { + GST_DEBUG_OBJECT (camera, "stopping active bin"); + gst_element_set_state (camera->active_bin, GST_STATE_READY); + } + if (camera->mode == MODE_IMAGE) { + GstStateChangeReturn state_ret; + + camera->active_bin = camera->imgbin; + state_ret = + gst_element_set_state (camera->active_bin, GST_STATE_PAUSED); + + if (state_ret == GST_STATE_CHANGE_FAILURE) { + GST_WARNING_OBJECT (camera, "state change failed"); + gst_element_set_state (camera->active_bin, GST_STATE_NULL); + camera->active_bin = NULL; + } + } else if (camera->mode == MODE_VIDEO) { + camera->active_bin = camera->vidbin; + } + gst_camerabin_reset_to_view_finder (camera); + } else if (camera->mode == MODE_IMAGE) { + /* Prepare needed elements for image processing */ + gst_camerabin_image_prepare_elements (GST_CAMERABIN_IMAGE + (camera->imgbin)); + } + } +} + +/* + * gst_camerabin_set_flags: + * @camera: camerabin object + * @flags: flags for camerabin, videobin and imagebin + * + * Change camerabin capture flags. + */ +static void +gst_camerabin_set_flags (GstCameraBin * camera, GstCameraBinFlags flags) +{ + g_return_if_fail (camera != NULL); + + GST_DEBUG_OBJECT (camera, "setting flags: %d", flags); + + GST_OBJECT_LOCK (camera); + camera->flags = flags; + GST_OBJECT_UNLOCK (camera); + + gst_camerabin_video_set_flags (GST_CAMERABIN_VIDEO (camera->vidbin), flags); + gst_camerabin_image_set_flags (GST_CAMERABIN_IMAGE (camera->imgbin), flags); +} + +/* + * gst_camerabin_change_filename: + * @camera: camerabin object + * @name: new filename for capture + * + * Change filename for image or video capture. + */ +static void +gst_camerabin_change_filename (GstCameraBin * camera, const gchar * name) +{ + if (name == NULL) + name = ""; + + if (0 != strcmp (camera->filename->str, name)) { + GST_DEBUG_OBJECT (camera, "changing filename from '%s' to '%s'", + camera->filename->str, name); + g_string_assign (camera->filename, name); + } +} + +static gboolean +gst_camerabin_set_videosrc_zoom (GstCameraBin * camera, gfloat zoom) +{ + gboolean ret = FALSE; + + /* Try with photography interface zooming */ + if (GST_IS_ELEMENT (camera->src_vid_src) && + gst_element_implements_interface (camera->src_vid_src, + GST_TYPE_PHOTOGRAPHY)) { + gst_photography_set_zoom (GST_PHOTOGRAPHY (camera->src_vid_src), zoom); + ret = TRUE; + } + return ret; +} + + +static gboolean +gst_camerabin_set_element_zoom (GstCameraBin * camera, gfloat zoom) +{ + gint w2_crop = 0, h2_crop = 0; + GstPad *pad_zoom_sink = NULL; + gboolean ret = FALSE; + gint left = camera->base_crop_left; + gint right = camera->base_crop_right; + gint top = camera->base_crop_top; + gint bottom = camera->base_crop_bottom; + + if (camera->src_zoom_crop) { + /* Update capsfilters to apply the zoom */ + GST_INFO_OBJECT (camera, "zoom: %f, orig size: %dx%d", zoom, + camera->width, camera->height); + + if (zoom != ZOOM_1X) { + w2_crop = (camera->width - (camera->width * ZOOM_1X / zoom)) / 2; + h2_crop = (camera->height - (camera->height * ZOOM_1X / zoom)) / 2; + + left += w2_crop; + right += w2_crop; + top += h2_crop; + bottom += h2_crop; + + /* force number of pixels cropped from left to be even, to avoid slow code + * path on videoscale */ + left &= 0xFFFE; + } + + pad_zoom_sink = gst_element_get_static_pad (camera->src_zoom_crop, "sink"); + + GST_INFO_OBJECT (camera, + "sw cropping: left:%d, right:%d, top:%d, bottom:%d", left, right, top, + bottom); + + GST_PAD_STREAM_LOCK (pad_zoom_sink); + g_object_set (camera->src_zoom_crop, "left", left, "right", right, "top", + top, "bottom", bottom, NULL); + GST_PAD_STREAM_UNLOCK (pad_zoom_sink); + gst_object_unref (pad_zoom_sink); + ret = TRUE; + } + return ret; +} + +/* + * gst_camerabin_setup_zoom: + * @camera: camerabin object + * + * Apply zoom configured to camerabin to capture. + */ +static void +gst_camerabin_setup_zoom (GstCameraBin * camera) +{ + gfloat zoom; + + g_return_if_fail (camera != NULL); + + zoom = camera->zoom; + + g_return_if_fail (zoom); + + GST_INFO_OBJECT (camera, "setting zoom %f", zoom); + + if (gst_camerabin_set_videosrc_zoom (camera, zoom)) { + gst_camerabin_set_element_zoom (camera, ZOOM_1X); + GST_INFO_OBJECT (camera, "zoom set using videosrc"); + } else if (gst_camerabin_set_element_zoom (camera, zoom)) { + GST_INFO_OBJECT (camera, "zoom set using gst elements"); + } else { + GST_INFO_OBJECT (camera, "setting zoom failed"); + } +} + +/* + * gst_camerabin_get_allowed_input_caps: + * @camera: camerabin object + * + * Retrieve caps from videosrc describing formats it supports + * + * Returns: caps object from videosrc + */ +static GstCaps * +gst_camerabin_get_allowed_input_caps (GstCameraBin * camera) +{ + GstCaps *caps = NULL; + GstPad *pad = NULL, *peer_pad = NULL; + GstState state; + GstElement *videosrc; + + g_return_val_if_fail (camera != NULL, NULL); + + videosrc = camera->src_vid_src ? camera->src_vid_src : camera->app_vid_src; + + if (!videosrc) { + GST_WARNING_OBJECT (camera, "no videosrc, can't get allowed caps"); + goto failed; + } + + if (camera->allowed_caps) { + GST_DEBUG_OBJECT (camera, "returning cached caps"); + goto done; + } + + pad = gst_element_get_static_pad (videosrc, "src"); + + if (!pad) { + GST_WARNING_OBJECT (camera, "no srcpad in videosrc"); + goto failed; + } + + state = GST_STATE (videosrc); + + /* Make this function work also in NULL state */ + if (state == GST_STATE_NULL) { + GST_DEBUG_OBJECT (camera, "setting videosrc to ready temporarily"); + peer_pad = gst_pad_get_peer (pad); + if (peer_pad) { + gst_pad_unlink (pad, peer_pad); + } + /* Set videosrc to READY to open video device */ + gst_element_set_locked_state (videosrc, TRUE); + gst_element_set_state (videosrc, GST_STATE_READY); + } + + camera->allowed_caps = gst_pad_get_caps (pad); + + /* Restore state and re-link if necessary */ + if (state == GST_STATE_NULL) { + GST_DEBUG_OBJECT (camera, "restoring videosrc state %d", state); + /* Reset videosrc to NULL state, some drivers seem to need this */ + gst_element_set_state (videosrc, GST_STATE_NULL); + if (peer_pad) { + gst_pad_link_full (pad, peer_pad, GST_PAD_LINK_CHECK_CAPS); + gst_object_unref (peer_pad); + } + gst_element_set_locked_state (videosrc, FALSE); + } + + gst_object_unref (pad); + +done: + if (camera->allowed_caps) { + caps = gst_caps_copy (camera->allowed_caps); + } + GST_DEBUG_OBJECT (camera, "allowed caps:%" GST_PTR_FORMAT, caps); +failed: + return caps; +} + +/* + * gst_camerabin_send_img_queue_event: + * @camera: camerabin object + * @event: event to be sent + * + * Send the given event to image queue. + */ +static void +gst_camerabin_send_img_queue_event (GstCameraBin * camera, GstEvent * event) +{ + GstPad *queue_sink; + + g_return_if_fail (camera != NULL); + g_return_if_fail (event != NULL); + + queue_sink = gst_element_get_static_pad (camera->img_queue, "sink"); + gst_pad_send_event (queue_sink, event); + gst_object_unref (queue_sink); +} + +/* + * gst_camerabin_send_img_queue_custom_event: + * @camera: camerabin object + * @ev_struct: event structure to be sent + * + * Generate and send a custom event to image queue. + */ +static void +gst_camerabin_send_img_queue_custom_event (GstCameraBin * camera, + GstStructure * ev_struct) +{ + GstEvent *event; + + g_return_if_fail (camera != NULL); + g_return_if_fail (ev_struct != NULL); + + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, ev_struct); + gst_camerabin_send_img_queue_event (camera, event); +} + +/* + * gst_camerabin_rewrite_tags_to_bin: + * @bin: bin holding tag setter elements + * @list: tag list to be written + * + * This function looks for certain tag setters from given bin + * and REPLACES ALL setter tags with given tag list + * + */ +static void +gst_camerabin_rewrite_tags_to_bin (GstBin * bin, const GstTagList * list) +{ + GstElement *setter; + GstIterator *iter; + GstIteratorResult res = GST_ITERATOR_OK; + gpointer data; + + iter = gst_bin_iterate_all_by_interface (bin, GST_TYPE_TAG_SETTER); + + while (res == GST_ITERATOR_OK || res == GST_ITERATOR_RESYNC) { + res = gst_iterator_next (iter, &data); + switch (res) { + case GST_ITERATOR_DONE: + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING ("error iterating tag setters"); + break; + case GST_ITERATOR_OK: + setter = GST_ELEMENT (data); + GST_LOG ("iterating tag setters: %" GST_PTR_FORMAT, setter); + GST_DEBUG ("replacement tags %" GST_PTR_FORMAT, list); + gst_tag_setter_merge_tags (GST_TAG_SETTER (setter), list, + GST_TAG_MERGE_REPLACE_ALL); + gst_object_unref (setter); + break; + default: + break; + } + } + + gst_iterator_free (iter); +} + +/* + * gst_camerabin_get_internal_tags: + * @camera: the camera bin element + * + * Returns tag list containing metadata from camerabin + * and it's elements + */ +static GstTagList * +gst_camerabin_get_internal_tags (GstCameraBin * camera) +{ + GstTagList *list = gst_tag_list_new (); + GstColorBalance *balance = NULL; + const GList *controls = NULL, *item; + GstColorBalanceChannel *channel; + gint min_value, max_value, mid_value, cur_value; + + if (camera->active_bin == camera->vidbin) { + /* FIXME: check if internal video tag setting is needed */ + goto done; + } + + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_CAPTURING_DIGITAL_ZOOM_RATIO, (gdouble) camera->zoom, NULL); + + if (gst_element_implements_interface (GST_ELEMENT (camera), + GST_TYPE_COLOR_BALANCE)) { + balance = GST_COLOR_BALANCE (camera); + } + + if (balance) { + controls = gst_color_balance_list_channels (balance); + } + for (item = controls; item; item = g_list_next (item)) { + channel = item->data; + min_value = channel->min_value; + max_value = channel->max_value; + /* the default value would probably better */ + mid_value = min_value + ((max_value - min_value) / 2); + cur_value = gst_color_balance_get_value (balance, channel); + + if (!g_ascii_strcasecmp (channel->label, "brightness")) { + /* The value of brightness. The unit is the APEX value (Additive System of Photographic Exposure). + * Ordinarily it is given in the range of -99.99 to 99.99. Note that + * if the numerator of the recorded value is 0xFFFFFFFF, Unknown shall be indicated. + * + * BrightnessValue (Bv) = log2 ( B/NK ) + * Note that: B:cd/cm² (candela per square centimeter), N,K: constant + * + * http://johnlind.tripod.com/science/scienceexposure.html + * + */ +/* + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + "capture-brightness", cur_value, 1, NULL); +*/ + } else if (!g_ascii_strcasecmp (channel->label, "contrast")) { + /* 0 = Normal, 1 = Soft, 2 = Hard */ + + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_CAPTURING_CONTRAST, + (cur_value == mid_value) ? "normal" : ((cur_value < mid_value) + ? "soft" : "hard"), NULL); + } else if (!g_ascii_strcasecmp (channel->label, "gain")) { + /* 0 = Normal, 1 = Low Up, 2 = High Up, 3 = Low Down, 4 = Hight Down */ + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_CAPTURING_GAIN_ADJUSTMENT, + (cur_value == mid_value) ? "normal" : ((cur_value < + mid_value) ? "low-gain-up" : "low-gain-down"), NULL); + } else if (!g_ascii_strcasecmp (channel->label, "saturation")) { + /* 0 = Normal, 1 = Low, 2 = High */ + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_CAPTURING_SATURATION, + (cur_value == mid_value) ? "normal" : ((cur_value < mid_value) + ? "low-saturation" : "high-saturation"), NULL); + } + } + +done: + + return list; +} + +/* + * gst_camerabin_rewrite_tags: + * @camera: the camera bin element + * + * Merges application set tags to camerabin internal tags, + * and writes them using image or video bin tag setters. + */ +static void +gst_camerabin_rewrite_tags (GstCameraBin * camera) +{ + const GstTagList *app_tag_list = NULL; + GstTagList *list = NULL; + + /* Get application set tags */ + app_tag_list = gst_tag_setter_get_tag_list (GST_TAG_SETTER (camera)); + + /* Get tags from camerabin and it's elements */ + list = gst_camerabin_get_internal_tags (camera); + + if (app_tag_list) { + gst_tag_list_insert (list, app_tag_list, GST_TAG_MERGE_REPLACE); + } + + /* Write tags */ + if (camera->active_bin == camera->vidbin) { + gst_camerabin_rewrite_tags_to_bin (GST_BIN (camera->active_bin), list); + } else { + /* Image tags need to be sent as a serialized event into image queue */ + GstEvent *tagevent = gst_event_new_tag (gst_tag_list_copy (list)); + gst_camerabin_send_img_queue_event (camera, tagevent); + } + + gst_tag_list_free (list); +} + +/* + * gst_camerabin_set_capsfilter_caps: + * @camera: camerabin object + * @new_caps: pointer to caps object to set + * + * Set given caps to camerabin capsfilters. + */ +static void +gst_camerabin_set_capsfilter_caps (GstCameraBin * camera, GstCaps * new_caps) +{ + GST_INFO_OBJECT (camera, "new_caps:%" GST_PTR_FORMAT, new_caps); + + gst_camerabin_configure_format (camera, new_caps); + + /* Update zoom */ + gst_camerabin_setup_zoom (camera); + + /* Update capsfilters */ + g_object_set (G_OBJECT (camera->src_filter), "caps", new_caps, NULL); + if (camera->src_zoom_filter) + g_object_set (G_OBJECT (camera->src_zoom_filter), "caps", new_caps, NULL); + gst_camerabin_update_aspect_filter (camera, new_caps); + GST_INFO_OBJECT (camera, "udpated"); +} + +/* + * img_capture_prepared: + * @data: camerabin object + * @caps: caps describing the prepared image format + * + * Callback which is called after image capture has been prepared. + */ +static void +img_capture_prepared (gpointer data, GstCaps * caps) +{ + GstCameraBin *camera = GST_CAMERABIN (data); + + GST_INFO_OBJECT (camera, "image capture prepared"); + + /* It is possible we are about to get something else that we requested */ + if (!gst_caps_is_equal (camera->image_capture_caps, caps)) { + gst_camerabin_adapt_image_capture (camera, caps); + } else { + gst_camerabin_set_capsfilter_caps (camera, camera->image_capture_caps); + } + + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE, + "active-pad", camera->pad_src_img, NULL); +} + +/* + * gst_camerabin_start_image_capture: + * @camera: camerabin object + * + * Initiates image capture. + */ +static void +gst_camerabin_start_image_capture (GstCameraBin * camera) +{ + gboolean wait_for_prepare = FALSE, ret = FALSE; + + GST_INFO_OBJECT (camera, "starting image capture"); + + if (GST_IS_ELEMENT (camera->src_vid_src) && + gst_element_implements_interface (camera->src_vid_src, + GST_TYPE_PHOTOGRAPHY)) { + /* Start image capture preparations using photography iface */ + wait_for_prepare = TRUE; + g_mutex_lock (camera->capture_mutex); + + /* Enable still image capture mode in v4l2camsrc */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), + "capture-mode")) { + g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 1, NULL); + } + + if (!camera->image_capture_caps || camera->image_capture_caps_update) { + if (camera->image_capture_width && camera->image_capture_height) { + /* Resolution is set, but it isn't in use yet */ + gst_camerabin_set_image_capture_caps (camera, + camera->image_capture_width, camera->image_capture_height); + } else { + /* Capture resolution not set. Use viewfinder resolution */ + camera->image_capture_caps = gst_caps_copy (camera->view_finder_caps); + camera->image_capture_caps_update = FALSE; + } + } + + /* Start preparations for image capture */ + GST_DEBUG_OBJECT (camera, "prepare image capture caps %" GST_PTR_FORMAT, + camera->image_capture_caps); + ret = + gst_photography_prepare_for_capture (GST_PHOTOGRAPHY + (camera->src_vid_src), (GstPhotoCapturePrepared) img_capture_prepared, + camera->image_capture_caps, camera); + camera->capturing = TRUE; + g_mutex_unlock (camera->capture_mutex); + } + + if (!wait_for_prepare) { + g_mutex_lock (camera->capture_mutex); + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", TRUE, + "active-pad", camera->pad_src_img, NULL); + camera->capturing = TRUE; + ret = TRUE; + g_mutex_unlock (camera->capture_mutex); + } + + if (!ret) { + CAMERABIN_PROCESSING_DEC_UNLOCKED (camera); + GST_WARNING_OBJECT (camera, "starting image capture failed"); + } +} + + /* + * FIXME ideally a caps renegotiation is better here + */ +static void +reset_video_capture_caps (GstCameraBin * camera) +{ + GstState state, pending; + GstPad *activepad = NULL; + + GST_INFO_OBJECT (camera, "switching resolution to %dx%d and fps to %d/%d", + camera->width, camera->height, camera->fps_n, camera->fps_d); + + /* Interrupt ongoing capture */ + gst_camerabin_do_stop (camera); + + gst_element_get_state (GST_ELEMENT (camera), &state, &pending, 0); + if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING) { + GST_INFO_OBJECT (camera, + "changing to READY to initialize videosrc with new format"); + g_object_get (G_OBJECT (camera->src_out_sel), "active-pad", &activepad, + NULL); + gst_element_set_state (GST_ELEMENT (camera), GST_STATE_READY); + } + if (pending != GST_STATE_VOID_PENDING) { + GST_LOG_OBJECT (camera, "restoring pending state: %s", + gst_element_state_get_name (pending)); + state = pending; + } + + /* Re-set the active pad since switching camerabin to READY state clears this + * setting in output-selector */ + if (activepad) { + GST_INFO_OBJECT (camera, "re-setting active pad in output-selector"); + + g_object_set (G_OBJECT (camera->src_out_sel), "active-pad", activepad, + NULL); + } + + gst_element_set_state (GST_ELEMENT (camera), state); +} + +/* + * gst_camerabin_start_video_recording: + * @camera: camerabin object + * + * Initiates video recording. + */ +static void +gst_camerabin_start_video_recording (GstCameraBin * camera) +{ + GstStateChangeReturn state_ret; + GstCameraBinVideo *vidbin = (GstCameraBinVideo *) camera->vidbin; + /* FIXME: how to ensure resolution and fps is supported by CPU? + * use a queue overrun signal? + */ + GST_INFO_OBJECT (camera, "starting video capture"); + + /* check if need to update video capture caps */ + if (camera->video_capture_caps_update) { + reset_video_capture_caps (camera); + } + + gst_camerabin_rewrite_tags (camera); + + /* Pause the pipeline in order to distribute new clock in paused_to_playing */ + /* Audio source needs to go to null to reset the ringbuffer */ + if (vidbin->aud_src) + gst_element_set_state (vidbin->aud_src, GST_STATE_NULL); + state_ret = gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED); + + if (state_ret != GST_STATE_CHANGE_FAILURE) { + GstClock *clock = gst_element_get_clock (GST_ELEMENT (camera)); + + g_mutex_lock (camera->capture_mutex); + camera->capturing = TRUE; + g_mutex_unlock (camera->capture_mutex); + gst_element_set_locked_state (camera->vidbin, FALSE); + /* ensure elements activated before feeding data into it */ + state_ret = gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED); + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE, + "active-pad", camera->pad_src_vid, NULL); + + /* Enable video mode in v4l2camsrc */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), + "capture-mode")) { + g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 2, NULL); + } + + /* Clock might be distributed as NULL to audiosrc, messing timestamping */ + if (vidbin->aud_src) + gst_element_set_clock (vidbin->aud_src, clock); + gst_object_unref (clock); + + /* videobin will not go to playing if file is not writable */ + if (gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + GST_ELEMENT_ERROR (camera, CORE, STATE_CHANGE, + ("Setting videobin to PLAYING failed"), (NULL)); + gst_element_set_state (camera->vidbin, GST_STATE_NULL); + gst_element_set_locked_state (camera->vidbin, TRUE); + g_mutex_lock (camera->capture_mutex); + camera->capturing = FALSE; + g_mutex_unlock (camera->capture_mutex); + gst_camerabin_reset_to_view_finder (camera); + } else { + gst_element_set_locked_state (camera->vidbin, TRUE); + } + } else { + GST_WARNING_OBJECT (camera, "videobin state change failed"); + gst_element_set_state (camera->vidbin, GST_STATE_NULL); + gst_camerabin_reset_to_view_finder (camera); + + CAMERABIN_PROCESSING_DEC (camera); + } +} + +/* + * gst_camerabin_send_video_eos: + * @camera: camerabin object + * + * Generate and send eos event to video bin in order to + * finish recording properly. + */ +static void +gst_camerabin_send_video_eos (GstCameraBin * camera) +{ + GstPad *videopad; + + g_return_if_fail (camera != NULL); + + if (!camera->eos_handled) { + /* Send eos event to video bin */ + GST_INFO_OBJECT (camera, "sending eos to videobin"); + videopad = gst_element_get_static_pad (camera->vidbin, "sink"); + gst_pad_send_event (videopad, gst_event_new_eos ()); + gst_object_unref (videopad); + /* Block viewfinder after capturing if requested by application */ + GST_OBJECT_LOCK (camera); + if (camera->block_viewfinder_trigger) { + gst_pad_set_blocked_async (camera->pad_src_view, TRUE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + } + GST_OBJECT_UNLOCK (camera); + camera->eos_handled = TRUE; + } else { + GST_INFO_OBJECT (camera, "dropping duplicate EOS"); + } +} + +/* + * camerabin_pad_blocked: + * @pad: pad to block/unblock + * @blocked: TRUE to block, FALSE to unblock + * @u_data: camera bin object + * + * Callback function for blocking a pad. + */ +static void +camerabin_pad_blocked (GstPad * pad, gboolean blocked, gpointer user_data) +{ + GstCameraBin *camera; + + camera = (GstCameraBin *) user_data; + + GST_DEBUG_OBJECT (camera, "%s %s:%s", + blocked ? "blocking" : "unblocking", GST_DEBUG_PAD_NAME (pad)); +} + +/* + * gst_camerabin_send_preview: + * @camera: camerabin object + * @buffer: received buffer + * + * Convert given buffer to desired preview format and send is as a #GstMessage + * to application. + * + * Returns: TRUE always + */ +static gboolean +gst_camerabin_send_preview (GstCameraBin * camera, GstBuffer * buffer) +{ + GstCameraBinPreviewPipelineData *data; + GstBuffer *prev = NULL; + GstStructure *s; + GstMessage *msg; + gboolean ret = FALSE; + + GST_DEBUG_OBJECT (camera, "creating preview"); + + data = (camera->mode == MODE_IMAGE) ? + camera->preview_pipeline : camera->video_preview_pipeline; + prev = gst_camerabin_preview_convert (data, buffer); + + GST_DEBUG_OBJECT (camera, "preview created: %p", prev); + + if (prev) { + s = gst_structure_new (PREVIEW_MESSAGE_NAME, + "buffer", GST_TYPE_BUFFER, prev, NULL); + gst_buffer_unref (prev); + + msg = gst_message_new_element (GST_OBJECT (camera), s); + + GST_DEBUG_OBJECT (camera, "sending message with preview image"); + + if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) { + GST_WARNING_OBJECT (camera, + "This element has no bus, therefore no message sent!"); + } + ret = TRUE; + } + + return ret; +} + +/* + * gst_camerabin_have_img_buffer: + * @pad: output-selector src pad leading to image bin + * @buffer: still image frame + * @u_data: camera bin object + * + * Buffer probe called before sending each buffer to image queue. + * Generates and sends preview image as gst message if requested. + */ +static gboolean +gst_camerabin_have_img_buffer (GstPad * pad, GstMiniObject * obj, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + + if (GST_IS_BUFFER (obj)) { + GstBuffer *buffer = GST_BUFFER_CAST (obj); + GstStructure *fn_ev_struct = NULL; + GstPad *os_sink = NULL; + + GST_LOG ("got buffer %p with size %d", buffer, GST_BUFFER_SIZE (buffer)); + + if (camera->preview_caps) { + gst_camerabin_send_preview (camera, buffer); + } + + /* Image filename should be set by now */ + if (g_str_equal (camera->filename->str, "")) { + GST_DEBUG_OBJECT (camera, "filename not set, dropping buffer"); + CAMERABIN_PROCESSING_DEC_UNLOCKED (camera); + goto done; + } + + gst_camerabin_rewrite_tags (camera); + + /* Send a custom event which tells the filename to image queue */ + /* NOTE: This needs to be THE FIRST event to be sent to queue for + every image. It triggers imgbin state change to PLAYING. */ + fn_ev_struct = gst_structure_new ("img-filename", + "filename", G_TYPE_STRING, camera->filename->str, NULL); + GST_DEBUG_OBJECT (camera, "sending filename event to image queue"); + gst_camerabin_send_img_queue_custom_event (camera, fn_ev_struct); + + /* Add buffer probe to outputselector's sink pad. It sends + EOS event to image queue. */ + os_sink = gst_element_get_static_pad (camera->src_out_sel, "sink"); + camera->image_captured_id = gst_pad_add_buffer_probe (os_sink, + G_CALLBACK (gst_camerabin_have_src_buffer), camera); + gst_object_unref (os_sink); + + done: + + /* HACK: v4l2camsrc changes to view finder resolution automatically + after one captured still image */ + gst_camerabin_finish_image_capture (camera); + + GST_DEBUG_OBJECT (camera, "image captured, switching to viewfinder"); + + gst_camerabin_reset_to_view_finder (camera); + + GST_DEBUG_OBJECT (camera, "switched back to viewfinder"); + + return TRUE; + } else if (GST_IS_EVENT (obj)) { + GstEvent *event = GST_EVENT_CAST (obj); + + GST_DEBUG_OBJECT (camera, "Received event in image pipeline"); + + /* forward tag events to preview pipeline */ + if (camera->preview_caps && GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + GstCameraBinPreviewPipelineData *data; + + data = (camera->mode == MODE_IMAGE) ? + camera->preview_pipeline : camera->video_preview_pipeline; + gst_camerabin_preview_send_event (data, gst_event_ref (event)); + } + } + + return TRUE; +} + +/* + * gst_camerabin_have_vid_buffer: + * @pad: output-selector src pad leading to video bin + * @buffer: buffer pushed to the pad + * @u_data: camerabin object + * + * Buffer probe for src pad leading to video bin. + * Sends eos event to video bin if stop requested and drops + * all buffers after this. + */ +static gboolean +gst_camerabin_have_vid_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + gboolean ret = TRUE; + GST_LOG ("got video buffer %p with size %d", + buffer, GST_BUFFER_SIZE (buffer)); + + if (!camera->video_preview_buffer && camera->video_preview_caps) { + GST_DEBUG ("storing video preview %p", buffer); + camera->video_preview_buffer = gst_buffer_copy (buffer); + } + + if (G_UNLIKELY (camera->stop_requested)) { + gst_camerabin_send_video_eos (camera); + ret = FALSE; /* Drop buffer */ + } + + return ret; +} + +/* + * gst_camerabin_have_src_buffer: + * @pad: output-selector sink pad which receives frames from video source + * @buffer: buffer pushed to the pad + * @u_data: camerabin object + * + * Buffer probe for sink pad. It sends custom eos event to image queue and + * notifies application by sending a "image-captured" message to GstBus. + * This probe is installed after image has been captured and it disconnects + * itself after EOS has been sent. + */ +static gboolean +gst_camerabin_have_src_buffer (GstPad * pad, GstBuffer * buffer, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + GstMessage *msg; + + GST_LOG_OBJECT (camera, "got image buffer %p with size %d", + buffer, GST_BUFFER_SIZE (buffer)); + + g_mutex_lock (camera->capture_mutex); + camera->capturing = FALSE; + g_cond_signal (camera->cond); + g_mutex_unlock (camera->capture_mutex); + + msg = gst_message_new_element (GST_OBJECT (camera), + gst_structure_new (IMG_CAPTURED_MESSAGE_NAME, NULL)); + + GST_DEBUG_OBJECT (camera, "sending 'image captured' message"); + + if (gst_element_post_message (GST_ELEMENT (camera), msg) == FALSE) { + GST_WARNING_OBJECT (camera, + "This element has no bus, therefore no message sent!"); + } + + /* We can't send real EOS event, since it would switch the image queue + into "draining mode". Therefore we send our own custom eos and + catch & drop it later in queue's srcpad data probe */ + GST_DEBUG_OBJECT (camera, "sending img-eos to image queue"); + gst_camerabin_send_img_queue_custom_event (camera, + gst_structure_new ("img-eos", NULL)); + + /* Prevent video source from pushing frames until we want them */ + GST_OBJECT_LOCK (camera); + if (camera->block_viewfinder_trigger) { + gst_pad_set_blocked_async (camera->pad_src_view, TRUE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + } + GST_OBJECT_UNLOCK (camera); + + /* our work is done, disconnect */ + gst_pad_remove_buffer_probe (pad, camera->image_captured_id); + + /* Image captured, notify that preparing a new capture is possible */ + g_object_notify (G_OBJECT (camera), "ready-for-capture"); + + return TRUE; +} + +/* + * gst_camerabin_have_queue_data: + * @pad: image queue src pad leading to image bin + * @mini_obj: buffer or event pushed to the pad + * @u_data: camerabin object + * + * Buffer probe for image queue src pad leading to image bin. It sets imgbin + * into PLAYING mode when image buffer is passed to it. This probe also + * monitors our internal custom events and handles them accordingly. + */ +static gboolean +gst_camerabin_have_queue_data (GstPad * pad, GstMiniObject * mini_obj, + gpointer u_data) +{ + GstCameraBin *camera = (GstCameraBin *) u_data; + gboolean ret = TRUE; + + if (GST_IS_BUFFER (mini_obj)) { + GstEvent *tagevent; + + GST_LOG_OBJECT (camera, "queue sending image buffer to imagebin"); + + tagevent = gst_event_new_tag (gst_tag_list_copy (camera->event_tags)); + gst_element_send_event (camera->imgbin, tagevent); + gst_tag_list_free (camera->event_tags); + camera->event_tags = gst_tag_list_new (); + } else if (GST_IS_EVENT (mini_obj)) { + const GstStructure *evs; + GstEvent *event; + + event = GST_EVENT_CAST (mini_obj); + evs = gst_event_get_structure (event); + + GST_LOG_OBJECT (camera, "got event %s", GST_EVENT_TYPE_NAME (event)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) { + GstTagList *tlist; + + GST_DEBUG_OBJECT (camera, "queue sending taglist to image pipeline"); + gst_event_parse_tag (event, &tlist); + gst_tag_list_insert (camera->event_tags, tlist, GST_TAG_MERGE_REPLACE); + ret = FALSE; + } else if (evs && gst_structure_has_name (evs, "img-filename")) { + const gchar *fname; + + GST_DEBUG_OBJECT (camera, "queue setting image filename to imagebin"); + fname = gst_structure_get_string (evs, "filename"); + g_object_set (G_OBJECT (camera->imgbin), "filename", fname, NULL); + + /* imgbin fails to start unless the filename is set or file + cannot be written */ + if (gst_element_set_state (camera->imgbin, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + GST_ELEMENT_ERROR (camera, CORE, STATE_CHANGE, + ("Setting imagebin to PLAYING failed"), (NULL)); + gst_element_set_state (camera->imgbin, GST_STATE_NULL); + } else { + GST_LOG_OBJECT (camera, "Set imagebin to PLAYING"); + } + + ret = FALSE; + } else if (evs && gst_structure_has_name (evs, "img-eos")) { + GST_DEBUG_OBJECT (camera, "queue sending EOS to image pipeline"); + gst_pad_set_blocked_async (camera->pad_src_queue, TRUE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + gst_element_send_event (camera->imgbin, gst_event_new_eos ()); + ret = FALSE; + } + } + + return ret; +} + +/* + * gst_camerabin_reset_to_view_finder: + * @camera: camerabin object + * + * Stop capturing and set camerabin to view finder mode. + * Reset capture counters and flags. + */ +static void +gst_camerabin_reset_to_view_finder (GstCameraBin * camera) +{ + GstStateChangeReturn state_ret; + GST_DEBUG_OBJECT (camera, "resetting"); + + if (camera->src_out_sel) { + /* Set selector to forward data to view finder */ + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE, + "active-pad", camera->pad_src_view, NULL); + } + + /* Set video bin to READY state */ + if (camera->active_bin == camera->vidbin) { + state_ret = gst_element_set_state (camera->active_bin, GST_STATE_READY); + if (state_ret == GST_STATE_CHANGE_FAILURE) { + GST_WARNING_OBJECT (camera, "state change failed"); + gst_element_set_state (camera->active_bin, GST_STATE_NULL); + camera->active_bin = NULL; + } + } + + /* Reset counters and flags */ + camera->stop_requested = FALSE; + camera->paused = FALSE; + camera->eos_handled = FALSE; + if (camera->video_preview_buffer) { + gst_buffer_unref (camera->video_preview_buffer); + camera->video_preview_buffer = NULL; + } + + /* Enable view finder mode in v4l2camsrc */ + if (camera->src_vid_src && + g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src_vid_src), + "capture-mode")) { + g_object_set (G_OBJECT (camera->src_vid_src), "capture-mode", 0, NULL); + } + + GST_DEBUG_OBJECT (camera, "reset done"); +} + +/* + * gst_camerabin_do_stop: + * @camera: camerabin object + * + * Raise flag to indicate to image and video bin capture stop. + * Stopping paused video recording handled as a special case. + * Wait for ongoing capturing to finish. + */ +static void +gst_camerabin_do_stop (GstCameraBin * camera) +{ + gboolean video_preview_sent = FALSE; + g_mutex_lock (camera->capture_mutex); + if (camera->capturing) { + GST_DEBUG_OBJECT (camera, "mark stop"); + camera->stop_requested = TRUE; + + /* Post preview image ASAP and don't wait that video recording + finishes as it may take time. */ + if (camera->video_preview_buffer) { + gst_camerabin_send_preview (camera, camera->video_preview_buffer); + video_preview_sent = TRUE; + } + + /* Take special care when stopping paused video capture */ + if ((camera->active_bin == camera->vidbin) && camera->paused) { + /* Send eos event to video bin before setting it to playing */ + gst_camerabin_send_video_eos (camera); + /* We must change to playing now in order to get video bin eos events + and buffered data through and finish recording properly */ + gst_element_set_state (GST_ELEMENT (camera->vidbin), GST_STATE_PLAYING); + camera->paused = FALSE; + } + + GST_DEBUG_OBJECT (camera, "waiting for capturing to finish"); + g_cond_wait (camera->cond, camera->capture_mutex); + GST_DEBUG_OBJECT (camera, "capturing finished"); + + if (camera->video_preview_buffer) { + /* Double check that preview image has been sent. This is useful + in a corner case where capture-stop is issued immediately after + start before a single video buffer is actually recorded */ + if (video_preview_sent == FALSE) { + gst_camerabin_send_preview (camera, camera->video_preview_buffer); + } + } + } + g_mutex_unlock (camera->capture_mutex); +} + +/* + * gst_camerabin_default_signal_img_done: + * @camera: camerabin object + * @fname: filename of the recently saved image + * + * Default handler for #GstCameraBin::image-done signal, + * stops always capture. + * + * Returns: FALSE always + */ +static gboolean +gst_camerabin_default_signal_img_done (GstCameraBin * camera, + const gchar * fname) +{ + return FALSE; +} + +/* + * gst_camerabin_set_allowed_framerate: + * @camera: camerabin object + * @filter_caps: update allowed framerate to these caps + * + * Find allowed frame rate from video source that matches with + * resolution in @filter_caps. Set found frame rate to @filter_caps. + */ +static void +gst_camerabin_set_allowed_framerate (GstCameraBin * camera, + GstCaps * filter_caps) +{ + GstStructure *structure; + GstCaps *allowed_caps = NULL, *intersect = NULL, *tmp_caps = NULL; + const GValue *framerate = NULL; + guint caps_size, i; + guint32 format = 0; + + GST_INFO_OBJECT (camera, "filter caps:%" GST_PTR_FORMAT, filter_caps); + + structure = gst_structure_copy (gst_caps_get_structure (filter_caps, 0)); + + /* Set fourcc format according to current videosrc format */ + format = get_srcpad_current_format (camera->src_vid_src); + if (format) { + GST_DEBUG_OBJECT (camera, + "using format %" GST_FOURCC_FORMAT " for matching", + GST_FOURCC_ARGS (format)); + gst_structure_set (structure, "format", GST_TYPE_FOURCC, format, NULL); + } else { + GST_DEBUG_OBJECT (camera, "not matching against fourcc format"); + gst_structure_remove_field (structure, "format"); + } + + tmp_caps = gst_caps_new_full (structure, NULL); + + /* Get supported caps from video src that matches with new filter caps */ + allowed_caps = gst_camerabin_get_allowed_input_caps (camera); + intersect = gst_caps_intersect (allowed_caps, tmp_caps); + GST_INFO_OBJECT (camera, "intersect caps:%" GST_PTR_FORMAT, intersect); + + /* Find the best framerate from the caps */ + caps_size = gst_caps_get_size (intersect); + for (i = 0; i < caps_size; i++) { + structure = gst_caps_get_structure (intersect, i); + framerate = + gst_camerabin_find_better_framerate (camera, structure, framerate); + } + + /* Set found frame rate to original caps */ + if (GST_VALUE_HOLDS_FRACTION (framerate)) { + gst_caps_set_simple (filter_caps, + "framerate", GST_TYPE_FRACTION, + gst_value_get_fraction_numerator (framerate), + gst_value_get_fraction_denominator (framerate), NULL); + } + + /* Unref helper caps */ + if (allowed_caps) { + gst_caps_unref (allowed_caps); + } + if (intersect) { + gst_caps_unref (intersect); + } + if (tmp_caps) { + gst_caps_unref (tmp_caps); + } +} + + +/** + * get_srcpad_current_format: + * @element: element to get the format from + * + * Helper function to get the negotiated fourcc + * format from @element src pad. + * + * Returns: negotiated format (fourcc), 0 if not found + */ +static guint32 +get_srcpad_current_format (GstElement * element) +{ + GstPad *srcpad = NULL; + GstCaps *srccaps = NULL; + GstStructure *structure; + guint32 format = 0; + + g_return_val_if_fail (element != NULL, 0); + + if ((srcpad = gst_element_get_static_pad (element, "src")) == NULL) { + goto no_pad; + } + + if ((srccaps = gst_pad_get_negotiated_caps (srcpad)) == NULL) { + goto no_caps; + } + + GST_LOG ("negotiated caps %" GST_PTR_FORMAT, srccaps); + + structure = gst_caps_get_structure (srccaps, 0); + if (gst_structure_has_field (structure, "format")) { + gst_structure_get_fourcc (structure, "format", &format); + } + + gst_caps_unref (srccaps); +no_caps: + gst_object_unref (srcpad); +no_pad: + GST_DEBUG ("current format for %" GST_PTR_FORMAT ": %" GST_FOURCC_FORMAT, + element, GST_FOURCC_ARGS (format)); + return format; +} + +/* + * gst_camerabin_find_better_framerate: + * @camera: camerabin object + * @st: structure that contains framerate candidates + * @orig_framerate: best framerate so far + * + * Looks for framerate better than @orig_framerate from @st structure. + * In night mode lowest framerate is considered best, otherwise highest is + * best. + * + * Returns: @orig_framerate or better if found + */ +static const GValue * +gst_camerabin_find_better_framerate (GstCameraBin * camera, GstStructure * st, + const GValue * orig_framerate) +{ + const GValue *framerate = NULL; + guint i, i_best, list_size; + gint res, comparison; + + if (camera->night_mode) { + GST_LOG_OBJECT (camera, "finding min framerate in %" GST_PTR_FORMAT, st); + comparison = GST_VALUE_LESS_THAN; + } else { + GST_LOG_OBJECT (camera, "finding max framerate in %" GST_PTR_FORMAT, st); + comparison = GST_VALUE_GREATER_THAN; + } + + if (gst_structure_has_field (st, "framerate")) { + framerate = gst_structure_get_value (st, "framerate"); + /* Handle framerate lists */ + if (GST_VALUE_HOLDS_LIST (framerate)) { + list_size = gst_value_list_get_size (framerate); + GST_LOG_OBJECT (camera, "finding framerate from list"); + for (i = 0, i_best = 0; i < list_size; i++) { + res = gst_value_compare (gst_value_list_get_value (framerate, i), + gst_value_list_get_value (framerate, i_best)); + if (comparison == res) { + i_best = i; + } + } + GST_LOG_OBJECT (camera, "found best framerate from index %d", i_best); + framerate = gst_value_list_get_value (framerate, i_best); + } + /* Handle framerate ranges */ + if (GST_VALUE_HOLDS_FRACTION_RANGE (framerate)) { + if (camera->night_mode) { + GST_LOG_OBJECT (camera, "getting min framerate from range"); + framerate = gst_value_get_fraction_range_min (framerate); + } else { + GST_LOG_OBJECT (camera, "getting max framerate from range"); + framerate = gst_value_get_fraction_range_max (framerate); + } + } + } + + /* Check if we found better framerate */ + if (orig_framerate && framerate) { + res = gst_value_compare (orig_framerate, framerate); + if (comparison == res) { + GST_LOG_OBJECT (camera, "original framerate was the best"); + framerate = orig_framerate; + } + } + + return framerate; +} + +/* + * gst_camerabin_update_aspect_filter: + * @camera: camerabin object + * @new_caps: new caps of next buffers arriving to view finder sink element + * + * Updates aspect ratio capsfilter to maintain aspect ratio, if we need to + * scale frames for showing them in view finder. + */ +static void +gst_camerabin_update_aspect_filter (GstCameraBin * camera, GstCaps * new_caps) +{ + if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_SCALE) { + GstCaps *sink_caps, *ar_caps; + GstStructure *st; + gint in_w = 0, in_h = 0, sink_w = 0, sink_h = 0, target_w = 0, target_h = 0; + gdouble ratio_w, ratio_h; + GstPad *sink_pad; + const GValue *range; + + sink_pad = gst_element_get_static_pad (camera->view_sink, "sink"); + + if (sink_pad) { + sink_caps = gst_pad_get_caps (sink_pad); + gst_object_unref (sink_pad); + if (sink_caps) { + if (!gst_caps_is_any (sink_caps)) { + GST_DEBUG_OBJECT (camera, "sink element caps %" GST_PTR_FORMAT, + sink_caps); + /* Get maximum resolution that view finder sink accepts */ + st = gst_caps_get_structure (sink_caps, 0); + if (gst_structure_has_field_typed (st, "width", GST_TYPE_INT_RANGE)) { + range = gst_structure_get_value (st, "width"); + sink_w = gst_value_get_int_range_max (range); + } + if (gst_structure_has_field_typed (st, "height", GST_TYPE_INT_RANGE)) { + range = gst_structure_get_value (st, "height"); + sink_h = gst_value_get_int_range_max (range); + } + GST_DEBUG_OBJECT (camera, "sink element accepts max %dx%d", sink_w, + sink_h); + + /* Get incoming frames' resolution */ + if (sink_h && sink_w) { + st = gst_caps_get_structure (new_caps, 0); + gst_structure_get_int (st, "width", &in_w); + gst_structure_get_int (st, "height", &in_h); + GST_DEBUG_OBJECT (camera, "new caps with %dx%d", in_w, in_h); + } + } + gst_caps_unref (sink_caps); + } + } + + /* If we get bigger frames than view finder sink accepts, then we scale. + If we scale we need to adjust aspect ratio capsfilter caps in order + to maintain aspect ratio while scaling. */ + if (in_w && in_h && (in_w > sink_w || in_h > sink_h)) { + ratio_w = (gdouble) sink_w / in_w; + ratio_h = (gdouble) sink_h / in_h; + + if (ratio_w < ratio_h) { + target_w = sink_w; + target_h = (gint) (ratio_w * in_h); + } else { + target_w = (gint) (ratio_h * in_w); + target_h = sink_h; + } + + GST_DEBUG_OBJECT (camera, "setting %dx%d filter to maintain aspect ratio", + target_w, target_h); + ar_caps = gst_caps_copy (new_caps); + gst_caps_set_simple (ar_caps, "width", G_TYPE_INT, target_w, "height", + G_TYPE_INT, target_h, NULL); + } else { + GST_DEBUG_OBJECT (camera, "no scaling"); + ar_caps = new_caps; + } + + GST_DEBUG_OBJECT (camera, "aspect ratio filter caps %" GST_PTR_FORMAT, + ar_caps); + g_object_set (G_OBJECT (camera->aspect_filter), "caps", ar_caps, NULL); + if (ar_caps != new_caps) + gst_caps_unref (ar_caps); + } +} + +/* + * gst_camerabin_finish_image_capture: + * @camera: camerabin object + * + * Perform finishing operations after image capture is done and + * returning back to view finder mode. + */ +static void +gst_camerabin_finish_image_capture (GstCameraBin * camera) +{ + if (camera->image_capture_caps) { + /* If we used specific caps for image capture we need to + restore the caps and zoom/crop for view finder mode */ + if (camera->src_zoom_crop) { + GST_DEBUG_OBJECT (camera, "resetting crop in camerabin"); + g_object_set (camera->src_zoom_crop, "left", 0, "right", 0, + "top", 0, "bottom", 0, NULL); + } + camera->base_crop_left = 0; + camera->base_crop_right = 0; + camera->base_crop_top = 0; + camera->base_crop_bottom = 0; + gst_camerabin_set_capsfilter_caps (camera, camera->view_finder_caps); + } +} + +/* + * gst_camerabin_adapt_image_capture: + * @camera: camerabin object + * @in_caps: caps object that describes incoming image format + * + * Adjust capsfilters and crop according image capture caps if necessary. + * The captured image format from video source might be different from + * what application requested, so we can try to fix that in camerabin. + * + */ +static void +gst_camerabin_adapt_image_capture (GstCameraBin * camera, GstCaps * in_caps) +{ + GstStructure *in_st, *new_st, *req_st; + gint in_width = 0, in_height = 0, req_width = 0, req_height = 0, crop = 0; + gdouble ratio_w, ratio_h; + GstCaps *filter_caps = NULL; + + GST_LOG_OBJECT (camera, "in caps: %" GST_PTR_FORMAT, in_caps); + GST_LOG_OBJECT (camera, "requested caps: %" GST_PTR_FORMAT, + camera->image_capture_caps); + + in_st = gst_caps_get_structure (in_caps, 0); + gst_structure_get_int (in_st, "width", &in_width); + gst_structure_get_int (in_st, "height", &in_height); + + req_st = gst_caps_get_structure (camera->image_capture_caps, 0); + gst_structure_get_int (req_st, "width", &req_width); + gst_structure_get_int (req_st, "height", &req_height); + + GST_INFO_OBJECT (camera, "we requested %dx%d, and got %dx%d", req_width, + req_height, in_width, in_height); + + new_st = gst_structure_copy (req_st); + /* If new fields have been added, we need to copy them */ + gst_structure_foreach (in_st, copy_missing_fields, new_st); + + if (!(camera->flags & GST_CAMERABIN_FLAG_SOURCE_RESIZE)) { + GST_DEBUG_OBJECT (camera, + "source-resize flag disabled, unable to adapt resolution"); + gst_structure_set (new_st, "width", G_TYPE_INT, in_width, "height", + G_TYPE_INT, in_height, NULL); + } + + GST_LOG_OBJECT (camera, "new image capture caps: %" GST_PTR_FORMAT, new_st); + + /* Crop if requested aspect ratio differs from incoming frame aspect ratio */ + if (camera->src_zoom_crop) { + + ratio_w = (gdouble) in_width / req_width; + ratio_h = (gdouble) in_height / req_height; + + if (ratio_w < ratio_h) { + crop = in_height - (req_height * ratio_w); + camera->base_crop_top = crop / 2; + camera->base_crop_bottom = crop / 2; + } else { + crop = in_width - (req_width * ratio_h); + camera->base_crop_left = crop / 2; + camera->base_crop_right += crop / 2; + } + + GST_INFO_OBJECT (camera, + "setting base crop: left:%d, right:%d, top:%d, bottom:%d", + camera->base_crop_left, camera->base_crop_right, camera->base_crop_top, + camera->base_crop_bottom); + g_object_set (G_OBJECT (camera->src_zoom_crop), "top", + camera->base_crop_top, "bottom", camera->base_crop_bottom, "left", + camera->base_crop_left, "right", camera->base_crop_right, NULL); + } + + /* Update capsfilters */ + gst_caps_replace (&camera->image_capture_caps, + gst_caps_new_full (new_st, NULL)); + gst_camerabin_set_capsfilter_caps (camera, camera->image_capture_caps); + + /* Adjust the capsfilter before crop and videoscale elements if necessary */ + if (in_width == camera->width && in_height == camera->height) { + GST_DEBUG_OBJECT (camera, "no adaptation with resolution needed"); + } else { + GST_DEBUG_OBJECT (camera, + "changing %" GST_PTR_FORMAT " from %dx%d to %dx%d", camera->src_filter, + camera->width, camera->height, in_width, in_height); + /* Apply the width and height to filter caps */ + g_object_get (G_OBJECT (camera->src_filter), "caps", &filter_caps, NULL); + filter_caps = gst_caps_make_writable (filter_caps); + gst_caps_set_simple (filter_caps, "width", G_TYPE_INT, in_width, "height", + G_TYPE_INT, in_height, NULL); + g_object_set (G_OBJECT (camera->src_filter), "caps", filter_caps, NULL); + gst_caps_unref (filter_caps); + } +} + +/* + * gst_camerabin_handle_scene_mode: + * @camera: camerabin object + * scene_mode: scene mode + * + * Handle scene mode if night mode was selected/deselected in video-source + * + */ +static void +gst_camerabin_handle_scene_mode (GstCameraBin * camera, GstSceneMode scene_mode) +{ + if (scene_mode == GST_PHOTOGRAPHY_SCENE_MODE_NIGHT) { + if (!camera->night_mode) { + GST_DEBUG ("enabling night mode, lowering fps"); + /* Make camerabin select the lowest allowed frame rate */ + camera->night_mode = TRUE; + /* Remember frame rate before setting night mode */ + camera->pre_night_fps_n = camera->fps_n; + camera->pre_night_fps_d = camera->fps_d; + do_set_video_resolution_fps (camera, camera->width, camera->height, 0, 1); + } else { + GST_DEBUG ("night mode already enabled"); + } + } else { + if (camera->night_mode) { + GST_DEBUG ("disabling night mode, restoring fps to %d/%d", + camera->pre_night_fps_n, camera->pre_night_fps_d); + camera->night_mode = FALSE; + do_set_video_resolution_fps (camera, camera->width, camera->height, + camera->pre_night_fps_n, camera->pre_night_fps_d); + } + } +} + +/* + * gst_camerabin_scene_mode_notify_cb: + * @video_source: videosrc object + * @pspec: GParamSpec for property + * @user_data: camerabin object + * + * Update framerate if scene mode was updated in video-source + * + */ +static void +gst_camerabin_scene_mode_notify_cb (GObject * video_source, GParamSpec * pspec, + gpointer user_data) +{ + GstSceneMode scene_mode; + const gchar *name = g_param_spec_get_name (pspec); + GstCameraBin *camera = GST_CAMERABIN (user_data); + + g_object_get (video_source, name, &scene_mode, NULL); + gst_camerabin_handle_scene_mode (camera, scene_mode); +} + + /* + * gst_camerabin_zoom_notify_cb: + * @video_source: videosrc object + * @pspec: GParamSpec for property + * @user_data: camerabin object + * + * Update zoom value if video-source updated its zoom + * + */ +static void +gst_camerabin_zoom_notify_cb (GObject * video_source, GParamSpec * pspec, + gpointer user_data) +{ + gfloat zoom; + const gchar *name = g_param_spec_get_name (pspec); + GstCameraBin *camera = GST_CAMERABIN (user_data); + + g_object_get (video_source, name, &zoom, NULL); + + camera->zoom = zoom; + g_object_notify (G_OBJECT (camera), "zoom"); +} + +/* + * gst_camerabin_monitor_video_source_properties: + * @camera: camerabin object + * + * Monitor notify signals from video source photography interface + * property scene mode. + * + */ +static void +gst_camerabin_monitor_video_source_properties (GstCameraBin * camera) +{ + GST_DEBUG_OBJECT (camera, "checking for photography interface support"); + if (GST_IS_ELEMENT (camera->src_vid_src) && + gst_element_implements_interface (camera->src_vid_src, + GST_TYPE_PHOTOGRAPHY)) { + gint scene_mode; + GST_DEBUG_OBJECT (camera, + "connecting to %" GST_PTR_FORMAT " - notify::scene-mode", + camera->src_vid_src); + g_signal_connect (G_OBJECT (camera->src_vid_src), "notify::scene-mode", + (GCallback) gst_camerabin_scene_mode_notify_cb, camera); + g_object_get (G_OBJECT (camera->src_vid_src), "scene-mode", &scene_mode, + NULL); + camera->night_mode = scene_mode == GST_PHOTOGRAPHY_SCENE_MODE_NIGHT; + + GST_DEBUG_OBJECT (camera, + "connecting to %" GST_PTR_FORMAT " - notify::zoom", + camera->src_vid_src); + g_signal_connect (G_OBJECT (camera->src_vid_src), "notify::zoom", + (GCallback) gst_camerabin_zoom_notify_cb, camera); + } +} + +/* + * gst_camerabin_configure_format: + * @camera: camerabin object + * @caps: caps describing new format + * + * Configure internal video format for camerabin. + * + */ +static void +gst_camerabin_configure_format (GstCameraBin * camera, GstCaps * caps) +{ + GstStructure *st; + + st = gst_caps_get_structure (caps, 0); + + gst_structure_get_int (st, "width", &camera->width); + gst_structure_get_int (st, "height", &camera->height); + + if (gst_structure_has_field_typed (st, "framerate", GST_TYPE_FRACTION)) { + gst_structure_get_fraction (st, "framerate", &camera->fps_n, + &camera->fps_d); + } +} + +static gboolean +copy_missing_fields (GQuark field_id, const GValue * value, gpointer user_data) +{ + GstStructure *st = (GstStructure *) user_data; + const GValue *val = gst_structure_id_get_value (st, field_id); + + if (G_UNLIKELY (val == NULL)) { + gst_structure_id_set_value (st, field_id, value); + } + + return TRUE; +} + +/* +* gst_camerabin_change_viewfinder_blocking: +* @camera: camerabin object +* @blocked: new viewfinder blocking state +* +* Handle viewfinder blocking parameter change. +*/ +static void +gst_camerabin_change_viewfinder_blocking (GstCameraBin * camera, + gboolean blocked) +{ + gboolean old_value; + + GST_OBJECT_LOCK (camera); + old_value = camera->block_viewfinder_prop; + camera->block_viewfinder_prop = blocked; + if (blocked == FALSE) { + camera->block_viewfinder_trigger = FALSE; + } + GST_OBJECT_UNLOCK (camera); + + /* "block_viewfinder_prop" is now set and will be checked after capture */ + GST_DEBUG_OBJECT (camera, "viewfinder blocking set to %d, was %d", + camera->block_viewfinder_prop, old_value); + + if (old_value == blocked) + return; + + if (!blocked && camera->pad_src_view + && gst_pad_is_blocked (camera->pad_src_view)) { + /* Unblock viewfinder: the pad is blocked and we need to unblock it */ + gst_pad_set_blocked_async (camera->pad_src_view, FALSE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + } +} + +/* + * GObject callback functions implementation + */ + +static void +gst_camerabin_base_init (gpointer gclass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + + gst_tag_register_musicbrainz_tags (); + + gst_element_class_set_details_simple (element_class, "Camera Bin", + "Generic/Bin/Camera", + "Handle lot of features present in DSC", + "Nokia Corporation , " + "Edgard Lima "); +} + +static void +gst_camerabin_class_init (GstCameraBinClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBinClass *gstbin_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + gstbin_class = GST_BIN_CLASS (klass); + + /* gobject */ + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_camerabin_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_camerabin_finalize); + + gobject_class->set_property = gst_camerabin_set_property; + gobject_class->get_property = gst_camerabin_get_property; + + /** + * GstCameraBin:filename: + * + * Set filename for the still image capturing or video capturing. + */ + + g_object_class_install_property (gobject_class, ARG_FILENAME, + g_param_spec_string ("filename", "Filename", + "Filename of the image or video to save", "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:mode: + * + * Set the mode of operation: still image capturing or video recording. + * Setting the mode will create and destroy image bin or video bin elements + * according to the mode. You can set this property at any time, changing + * the mode will stop ongoing capture. + */ + + g_object_class_install_property (gobject_class, ARG_MODE, + g_param_spec_enum ("mode", "Mode", + "The capture mode (still image capture or video recording)", + GST_TYPE_CAMERABIN_MODE, DEFAULT_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:flags + * + * Control the behaviour of camerabin. + */ + g_object_class_install_property (gobject_class, ARG_FLAGS, + g_param_spec_flags ("flags", "Flags", "Flags to control behaviour", + GST_TYPE_CAMERABIN_FLAGS, DEFAULT_FLAGS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:mute: + * + * Mute audio in video recording mode. + * Set this property only when #GstCameraBin is in READY, PAUSED or PLAYING. + */ + + g_object_class_install_property (gobject_class, ARG_MUTE, + g_param_spec_boolean ("mute", "Mute", + "True to mute the recording. False to record with audio", + ARG_DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:zoom: + * + * Set up the zoom applied to the frames. + * Set this property only when #GstCameraBin is in READY, PAUSED or PLAYING. + */ + + g_object_class_install_property (gobject_class, ARG_ZOOM, + g_param_spec_float ("zoom", "Zoom", + "The zoom. 1.0 for 1x, 2.0 for 2x and so on", + MIN_ZOOM, MAX_ZOOM, DEFAULT_ZOOM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:image-post-processing: + * + * Set up an element to do image post processing. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + g_object_class_install_property (gobject_class, ARG_IMAGE_POST, + g_param_spec_object ("image-post-processing", + "Image post processing element", + "Image Post-Processing GStreamer element (default is NULL)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:image-encoder: + * + * Set up an image encoder (for example, jpegenc or pngenc) element. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_IMAGE_ENC, + g_param_spec_object ("image-encoder", "Image encoder", + "Image encoder GStreamer element (default is jpegenc)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:image-formatter: + * + * Set up an image formatter (for example, jifmux) element. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_IMAGE_FORMATTER, + g_param_spec_object ("image-formatter", "Image formatter", + "Image formatter GStreamer element (default is jifmux)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-post-processing: + * + * Set up an element to do video post processing. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIDEO_POST, + g_param_spec_object ("video-post-processing", + "Video post processing element", + "Video post processing GStreamer element (default is NULL)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-encoder: + * + * Set up a video encoder element. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIDEO_ENC, + g_param_spec_object ("video-encoder", "Video encoder", + "Video encoder GStreamer element (default is theoraenc)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:audio-encoder: + * + * Set up an audio encoder element. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_AUDIO_ENC, + g_param_spec_object ("audio-encoder", "Audio encoder", + "Audio encoder GStreamer element (default is vorbisenc)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-muxer: + * + * Set up a video muxer element. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIDEO_MUX, + g_param_spec_object ("video-muxer", "Video muxer", + "Video muxer GStreamer element (default is oggmux)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:viewfinder-sink: + * + * Set up a sink element to render frames in view finder. + * By default "autovideosink" or DEFAULT_VIDEOSINK will be used. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VF_SINK, + g_param_spec_object ("viewfinder-sink", "Viewfinder sink", + "Viewfinder sink GStreamer element (NULL = default video sink)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-source: + * + * Set up a video source element. + * By default "autovideosrc" or DEFAULT_VIDEOSRC will be used. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIDEO_SRC, + g_param_spec_object ("video-source", "Video source element", + "Video source GStreamer element (NULL = default video src)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstCameraBin:audio-source: + * + * Set up an audio source element. + * By default "autoaudiosrc" or DEFAULT_AUDIOSRC will be used. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_AUDIO_SRC, + g_param_spec_object ("audio-source", "Audio source element", + "Audio source GStreamer element (NULL = default audio src)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-source-filter: + * + * Set up optional video filter element, all frames from video source + * will be processed by this element. e.g. An application might add + * image enhancers/parameter adjustment filters here to improve captured + * image/video results, or add analyzers to give feedback on capture + * the application. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIDEO_SOURCE_FILTER, + g_param_spec_object ("video-source-filter", "video source filter element", + "Optional video filter GStreamer element, filters all frames from" + "the video source", GST_TYPE_ELEMENT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-source-caps: + * + * The allowed modes of operation of the video source. Have in mind that it + * doesn't mean #GstCameraBin can operate in all those modes, + * it depends also on the other elements in the pipeline. Remember to + * gst_caps_unref after using it. + */ + + g_object_class_install_property (gobject_class, ARG_INPUT_CAPS, + g_param_spec_boxed ("video-source-caps", "Video source caps", + "The allowed modes of the video source operation", + GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:filter-caps: + * + * Caps applied to capsfilter element after videosrc [ ! ffmpegcsp ]. + * You can use this e.g. to make sure video color format matches with + * encoders and other elements configured to camerabin and/or change + * resolution and frame rate. + */ + + g_object_class_install_property (gobject_class, ARG_FILTER_CAPS, + g_param_spec_boxed ("filter-caps", "Filter caps", + "Filter video data coming from videosrc element", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:preview-caps: + * + * If application wants to receive a preview image, it needs to + * set this property to depict the desired image format caps. When + * this property is not set (NULL), message containing the preview + * image is not sent. + */ + + g_object_class_install_property (gobject_class, ARG_PREVIEW_CAPS, + g_param_spec_boxed ("preview-caps", "Preview caps", + "Caps defining the preview image format", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:preview-source-filter: + * Set up preview filter element, all frames coming from appsrc + * element will be processed by this element. + * Applications can use this to overlay text/images for preview frame, + * for example. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_PREVIEW_SOURCE_FILTER, + g_param_spec_object ("preview-source-filter", + "preview source filter element", + "Optional preview source filter GStreamer element", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:viewfinder-filter: + * Set up viewfinder filter element, all frames going to viewfinder sink + * element will be processed by this element. + * Applications can use this to overlay text/images in the screen, or + * plug facetracking algorithms, for example. + * This property can only be set while #GstCameraBin is in NULL state. + * The ownership of the element will be taken by #GstCameraBin. + */ + + g_object_class_install_property (gobject_class, ARG_VIEWFINDER_FILTER, + g_param_spec_object ("viewfinder-filter", "viewfinder filter element", + "viewfinder filter GStreamer element", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:block-after-capture: + * + * Block viewfinder after capture. + * If it is TRUE when 'capture-start' is issued, camerabin will prepare to + * block and freeze the viewfinder after capturing. Setting it to FALSE will + * abort the blocking if it hasn't happened yet, or will enable again the + * viewfinder if it is already blocked. Note that setting this property + * to TRUE after 'capture-start' will only work for the next capture. This + * makes possible for applications to set the property to FALSE to abort + * the current blocking and already set it back to TRUE again to block at + * the next capture. + * + * This is useful if application wants to display the preview image + * and running the viewfinder at the same time would be just a waste of + * CPU cycles. + */ + + g_object_class_install_property (gobject_class, ARG_BLOCK_VIEWFINDER, + g_param_spec_boolean ("block-after-capture", + "Block viewfinder after capture", + "Block viewfinder after capturing an image or video", + DEFAULT_BLOCK_VIEWFINDER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:image-capture-width: + * + * The width to be used when capturing still images. If 0, the + * viewfinder's width will be used. + */ + g_object_class_install_property (gobject_class, ARG_IMAGE_CAPTURE_WIDTH, + g_param_spec_int ("image-capture-width", + "The width used for image capture", + "The width used for image capture", 0, G_MAXINT16, + DEFAULT_CAPTURE_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:image-capture-height: + * + * The height to be used when capturing still images. If 0, the + * viewfinder's height will be used. + */ + g_object_class_install_property (gobject_class, ARG_IMAGE_CAPTURE_HEIGHT, + g_param_spec_int ("image-capture-height", + "The height used for image capture", + "The height used for image capture", 0, G_MAXINT16, + DEFAULT_CAPTURE_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-capture-width: + * + * The width to be used when capturing video. + */ + g_object_class_install_property (gobject_class, ARG_VIDEO_CAPTURE_WIDTH, + g_param_spec_int ("video-capture-width", + "The width used for video capture", + "The width used for video capture", 0, G_MAXINT16, + DEFAULT_CAPTURE_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-capture-height: + * + * The height to be used when capturing video. + */ + g_object_class_install_property (gobject_class, ARG_VIDEO_CAPTURE_HEIGHT, + g_param_spec_int ("video-capture-height", + "The height used for video capture", + "The height used for video capture", 0, G_MAXINT16, + DEFAULT_CAPTURE_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:video-capture-framerate: + * + * The framerate to be used when capturing video. + */ + g_object_class_install_property (gobject_class, ARG_VIDEO_CAPTURE_FRAMERATE, + gst_param_spec_fraction ("video-capture-framerate", + "The framerate used for video capture", + "The framerate used for video capture", 0, 1, G_MAXINT32, 1, + DEFAULT_FPS_N, DEFAULT_FPS_D, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:ready-for-capture: + * + * When TRUE new capture can be prepared. If FALSE capturing is ongoing + * and starting a new capture immediately is not possible. + */ + + g_object_class_install_property (gobject_class, ARG_READY_FOR_CAPTURE, + g_param_spec_boolean ("ready-for-capture", + "Indicates if preparing a new capture is possible", + "Indicates if preparing a new capture is possible", + DEFAULT_READY_FOR_CAPTURE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:idle: + * + * When TRUE no capturing/encoding/saving is running and it is safe to set + * camerabin to NULL to release resources without losing data. + * + * In case of errors, this property is made unreliable. Set the pipeline + * back to READY or NULL to make it reliable again. + */ + g_object_class_install_property (gobject_class, ARG_IDLE, + g_param_spec_boolean ("idle", + "Indicates if data is being processed (recording/capturing/saving)", + "Indicates if data is being processed (recording/capturing/saving)", + TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin::capture-start: + * @camera: the camera bin element + * + * Starts image capture or video recording depending on the Mode. + * If there is a capture already going on, does nothing. + * Resumes video recording if it has been paused. + */ + + camerabin_signals[CAPTURE_START_SIGNAL] = + g_signal_new ("capture-start", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBinClass, capture_start), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GstCameraBin::capture-stop: + * @camera: the camera bin element + * + * Stops still image preview, continuous image capture and video + * recording and returns to the view finder mode. + */ + + camerabin_signals[CAPTURE_STOP_SIGNAL] = + g_signal_new ("capture-stop", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBinClass, capture_stop), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GstCameraBin::capture-pause: + * @camera: the camera bin element + * + * Pauses video recording or resumes paused video recording. + * If in image mode or not recording, does nothing. + */ + + camerabin_signals[CAPTURE_PAUSE_SIGNAL] = + g_signal_new ("capture-pause", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBinClass, capture_pause), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GstCameraBin::set-video-resolution-fps: + * @camera: the camera bin element + * @width: number of horizontal pixels + * @height: number of vertical pixels + * @fps_n: frames per second numerator + * @fps_d: frames per second denominator + * + * Changes the frame resolution and frames per second of the video source. + * The application must be aware of the resolutions supported by the camera. + * Supported resolutions and frame rates can be get using input-caps property. + * + * Setting @fps_n or @fps_d to 0 configures maximum framerate for the + * given resolution, unless in night mode when minimum is configured. + * + * This is the same as setting the 'video-capture-width', + * 'video-capture-height' and 'video-capture-framerate' properties, but it + * already updates the caps to force use this resolution and framerate. + */ + + camerabin_signals[SET_VIDEO_RESOLUTION_FPS_SIGNAL] = + g_signal_new ("set-video-resolution-fps", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBinClass, set_video_resolution_fps), + NULL, NULL, __gst_camerabin_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4, + G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); + + /** + * GstCameraBin::set-image-resolution: + * @camera: the camera bin element + * @width: number of horizontal pixels + * @height: number of vertical pixels + * + * Changes the resolution used for still image capture. + * Does not affect view finder mode and video recording. + * + * This actually sets the 'image-capture-width' and 'image-capture-height' + * properties. + */ + + camerabin_signals[SET_IMAGE_RESOLUTION_SIGNAL] = + g_signal_new ("set-image-resolution", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBinClass, set_image_resolution), + NULL, NULL, __gst_camerabin_marshal_VOID__INT_INT, G_TYPE_NONE, 2, + G_TYPE_INT, G_TYPE_INT); + + /** + * GstCameraBin::image-done: + * @camera: the camera bin element + * @filename: the name of the file just saved + * + * Signal emitted when the file has just been saved. + * + * Don't call any #GstCameraBin method from this signal, if you do so there + * will be a deadlock. + */ + + camerabin_signals[IMG_DONE_SIGNAL] = + g_signal_new ("image-done", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstCameraBinClass, img_done), + g_signal_accumulator_true_handled, NULL, + __gst_camerabin_marshal_BOOLEAN__STRING, G_TYPE_BOOLEAN, 1, + G_TYPE_STRING); + + klass->capture_start = gst_camerabin_capture_start; + klass->capture_stop = gst_camerabin_capture_stop; + klass->capture_pause = gst_camerabin_capture_pause; + klass->set_video_resolution_fps = gst_camerabin_set_video_resolution_fps; + klass->set_image_resolution = gst_camerabin_set_image_resolution; + + klass->img_done = gst_camerabin_default_signal_img_done; + + /* gstelement */ + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_camerabin_change_state); + + gstelement_class->provide_clock = + GST_DEBUG_FUNCPTR (gst_camerabin_provide_clock); + + /* gstbin */ + /* override handle_message to peek when video or image bin reaches eos */ + gstbin_class->handle_message = + GST_DEBUG_FUNCPTR (gst_camerabin_handle_message_func); + +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_camerabin_init (GstCameraBin * camera, GstCameraBinClass * gclass) +{ + /* GstElementClass *klass = GST_ELEMENT_GET_CLASS (camera); */ + + camera->filename = g_string_new (""); + camera->mode = DEFAULT_MODE; + camera->flags = DEFAULT_FLAGS; + camera->stop_requested = FALSE; + camera->paused = FALSE; + camera->capturing = FALSE; + camera->night_mode = FALSE; + camera->eos_handled = FALSE; + + camera->app_width = camera->width = DEFAULT_WIDTH; + camera->app_height = camera->height = DEFAULT_HEIGHT; + camera->app_fps_n = camera->fps_n = DEFAULT_FPS_N; + camera->app_fps_d = camera->fps_d = DEFAULT_FPS_D; + camera->image_capture_width = 0; + camera->image_capture_height = 0; + camera->base_crop_left = 0; + camera->base_crop_right = 0; + camera->base_crop_top = 0; + camera->base_crop_bottom = 0; + + camera->event_tags = gst_tag_list_new (); + + camera->image_capture_caps = NULL; + camera->view_finder_caps = NULL; + camera->allowed_caps = NULL; + + camera->zoom = DEFAULT_ZOOM; + + /* concurrency control */ + camera->capture_mutex = g_mutex_new (); + camera->cond = g_cond_new (); + camera->processing_counter = 0; + + /* pad names for output and input selectors */ + camera->pad_src_view = NULL; + camera->pad_view_src = NULL; + camera->pad_src_img = NULL; + camera->pad_src_vid = NULL; + camera->pad_view_vid = NULL; + + camera->video_preview_buffer = NULL; + camera->preview_caps = NULL; + camera->video_preview_caps = NULL; + + /* image capture bin */ + camera->imgbin = g_object_new (GST_TYPE_CAMERABIN_IMAGE, NULL); + gst_object_ref (camera->imgbin); + + /* video capture bin */ + camera->vidbin = g_object_new (GST_TYPE_CAMERABIN_VIDEO, NULL); + gst_object_ref (camera->vidbin); + + /* view finder elements */ + camera->view_in_sel = NULL; + camera->view_scale = NULL; + camera->aspect_filter = NULL; + camera->view_sink = NULL; + + camera->app_vf_sink = NULL; + camera->app_viewfinder_filter = NULL; + + /* preview elements */ + camera->app_preview_source_filter = NULL; + camera->app_video_preview_source_filter = NULL; + + /* source elements */ + camera->src_vid_src = NULL; + camera->src_filter = NULL; + camera->src_zoom_crop = NULL; + camera->src_zoom_scale = NULL; + camera->src_zoom_filter = NULL; + camera->src_out_sel = NULL; + + camera->app_video_filter = NULL; + camera->app_vid_src = NULL; + + camera->active_bin = NULL; +} + +static void +gst_camerabin_dispose (GObject * object) +{ + GstCameraBin *camera; + + camera = GST_CAMERABIN (object); + + GST_DEBUG_OBJECT (camera, "disposing"); + + gst_element_set_state (camera->imgbin, GST_STATE_NULL); + gst_object_unref (camera->imgbin); + + gst_element_set_state (camera->vidbin, GST_STATE_NULL); + gst_object_unref (camera->vidbin); + + if (camera->preview_pipeline) { + gst_camerabin_preview_destroy_pipeline (camera->preview_pipeline); + camera->preview_pipeline = NULL; + } + if (camera->video_preview_pipeline) { + gst_camerabin_preview_destroy_pipeline (camera->video_preview_pipeline); + camera->video_preview_pipeline = NULL; + } + + camerabin_destroy_elements (camera); + camerabin_dispose_elements (camera); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_camerabin_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_camerabin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBin *camera = GST_CAMERABIN (object); + + switch (prop_id) { + case ARG_MUTE: + gst_camerabin_video_set_mute (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_boolean (value)); + break; + case ARG_ZOOM: + camera->zoom = g_value_get_float (value); + /* does not set it if in NULL, the src is not created yet */ + if (GST_STATE (camera) != GST_STATE_NULL) + gst_camerabin_setup_zoom (camera); + break; + case ARG_MODE: + gst_camerabin_change_mode (camera, g_value_get_enum (value)); + break; + case ARG_FLAGS: + gst_camerabin_set_flags (camera, g_value_get_flags (value)); + break; + case ARG_FILENAME: + gst_camerabin_change_filename (camera, g_value_get_string (value)); + break; + case ARG_VIDEO_POST: + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next video bin NULL to READY state change"); + } + gst_camerabin_video_set_post (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_object (value)); + break; + case ARG_VIDEO_ENC: + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next video bin NULL to READY state change"); + } + gst_camerabin_video_set_video_enc (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_object (value)); + break; + case ARG_AUDIO_ENC: + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next video bin NULL to READY state change"); + } + gst_camerabin_video_set_audio_enc (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_object (value)); + break; + case ARG_VIDEO_MUX: + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next video bin NULL to READY state change"); + } + gst_camerabin_video_set_muxer (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_object (value)); + break; + case ARG_IMAGE_POST: + if (GST_STATE (camera->imgbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next image bin NULL to READY state change"); + } + gst_camerabin_image_set_postproc (GST_CAMERABIN_IMAGE (camera->imgbin), + g_value_get_object (value)); + break; + case ARG_IMAGE_ENC: + if (GST_STATE (camera->imgbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next image bin NULL to READY state change"); + } + gst_camerabin_image_set_encoder (GST_CAMERABIN_IMAGE (camera->imgbin), + g_value_get_object (value)); + break; + case ARG_IMAGE_FORMATTER: + if (GST_STATE (camera->imgbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next image bin NULL to READY state change"); + } + gst_camerabin_image_set_formatter (GST_CAMERABIN_IMAGE (camera->imgbin), + g_value_get_object (value)); + break; + case ARG_VF_SINK: + if (GST_STATE (camera) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("camerabin must be in NULL state when setting the view finder element"), + (NULL)); + } else { + if (camera->app_vf_sink) + gst_object_unref (camera->app_vf_sink); + camera->app_vf_sink = g_value_get_object (value); + if (camera->app_vf_sink) + gst_object_ref (camera->app_vf_sink); + } + break; + case ARG_VIDEO_SRC: + if (GST_STATE (camera) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("camerabin must be in NULL state when setting the video source element"), + (NULL)); + } else { + if (camera->app_vid_src) + gst_object_unref (camera->app_vid_src); + camera->app_vid_src = g_value_get_object (value); + if (camera->app_vid_src) + gst_object_ref (camera->app_vid_src); + } + break; + case ARG_AUDIO_SRC: + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + GST_WARNING_OBJECT (camera, + "can't use set element until next video bin NULL to READY state change"); + } + gst_camerabin_video_set_audio_src (GST_CAMERABIN_VIDEO (camera->vidbin), + g_value_get_object (value)); + break; + case ARG_VIDEO_SOURCE_FILTER: + if (GST_STATE (camera) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("camerabin must be in NULL state when setting the video filter element"), + (NULL)); + } else { + if (camera->app_video_filter) + gst_object_unref (camera->app_video_filter); + camera->app_video_filter = g_value_dup_object (value); + } + break; + case ARG_FILTER_CAPS: + GST_OBJECT_LOCK (camera); + gst_caps_replace (&camera->view_finder_caps, + (GstCaps *) gst_value_get_caps (value)); + GST_OBJECT_UNLOCK (camera); + if (!camera->view_finder_caps) + camera->view_finder_caps = + gst_caps_from_string (CAMERABIN_DEFAULT_VF_CAPS); + gst_camerabin_configure_format (camera, camera->view_finder_caps); + break; + case ARG_PREVIEW_CAPS: + { + GstCameraBinPreviewPipelineData **prev_pipe = NULL; + GstElement **preview_source_filter = NULL; + GstCaps **prev_caps = NULL; + GstCaps *new_caps = NULL; + + if (camera->mode == MODE_IMAGE) { + prev_pipe = &camera->preview_pipeline; + preview_source_filter = &camera->app_preview_source_filter; + prev_caps = &camera->preview_caps; + } else { /* MODE VIDEO */ + prev_pipe = &camera->video_preview_pipeline; + preview_source_filter = &camera->app_video_preview_source_filter; + prev_caps = &camera->video_preview_caps; + } + + new_caps = (GstCaps *) gst_value_get_caps (value); + + if (prev_caps && !gst_caps_is_equal (*prev_caps, new_caps)) { + GST_DEBUG_OBJECT (camera, + "setting preview caps: %" GST_PTR_FORMAT, new_caps); + + GST_OBJECT_LOCK (camera); + gst_caps_replace (prev_caps, new_caps); + GST_OBJECT_UNLOCK (camera); + + if (new_caps && !gst_caps_is_any (new_caps) && + !gst_caps_is_empty (new_caps)) { + if (!*prev_pipe) { + *prev_pipe = + gst_camerabin_preview_create_pipeline (GST_ELEMENT (camera), + new_caps, *preview_source_filter); + } else { + gst_camerabin_preview_set_caps (*prev_pipe, new_caps); + } + } + } + break; + } + case ARG_PREVIEW_SOURCE_FILTER: + if (GST_STATE (camera) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("camerabin must be in NULL state when setting the preview source filter element"), + (NULL)); + } else { + GstCameraBinPreviewPipelineData **preview_pipe = NULL; + GstElement **preview_source_filter = NULL; + GstCaps *preview_caps = NULL; + + if (camera->mode == MODE_IMAGE) { + preview_pipe = &camera->preview_pipeline; + preview_source_filter = &camera->app_preview_source_filter; + preview_caps = camera->preview_caps; + } else { /* MODE VIDEO */ + preview_pipe = &camera->video_preview_pipeline; + preview_source_filter = &camera->app_video_preview_source_filter; + preview_caps = camera->video_preview_caps; + } + + if (*preview_source_filter) + gst_object_unref (*preview_source_filter); + *preview_source_filter = g_value_dup_object (value); + + if (*preview_pipe) { + gst_camerabin_preview_destroy_pipeline (*preview_pipe); + *preview_pipe = + gst_camerabin_preview_create_pipeline (GST_ELEMENT (camera), + preview_caps, *preview_source_filter); + } + } + break; + case ARG_VIEWFINDER_FILTER: + if (GST_STATE (camera) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("camerabin must be in NULL state when setting the viewfinder filter element"), + (NULL)); + } else { + if (camera->app_viewfinder_filter) + gst_object_unref (camera->app_viewfinder_filter); + camera->app_viewfinder_filter = g_value_dup_object (value); + } + break; + case ARG_BLOCK_VIEWFINDER: + gst_camerabin_change_viewfinder_blocking (camera, + g_value_get_boolean (value)); + break; + case ARG_IMAGE_CAPTURE_WIDTH: + { + gint width = g_value_get_int (value); + + if (width != camera->image_capture_width) { + camera->image_capture_width = width; + camera->image_capture_caps_update = TRUE; + } + } + break; + case ARG_IMAGE_CAPTURE_HEIGHT: + { + gint height = g_value_get_int (value); + + if (height != camera->image_capture_height) { + camera->image_capture_height = height; + camera->image_capture_caps_update = TRUE; + } + } + break; + case ARG_VIDEO_CAPTURE_WIDTH: + { + gint width = g_value_get_int (value); + + camera->app_width = width; + + if (width != camera->width) { + camera->width = width; + camera->video_capture_caps_update = TRUE; + } + } + break; + case ARG_VIDEO_CAPTURE_HEIGHT: + { + gint height = g_value_get_int (value); + + camera->app_height = height; + + if (height != camera->height) { + camera->height = height; + camera->video_capture_caps_update = TRUE; + } + } + break; + case ARG_VIDEO_CAPTURE_FRAMERATE: + { + gint fps_n, fps_d; + + fps_n = gst_value_get_fraction_numerator (value); + fps_d = gst_value_get_fraction_denominator (value); + + camera->app_fps_n = fps_n; + camera->app_fps_d = fps_d; + + if (fps_n != camera->fps_n || fps_d != camera->fps_d) { + camera->fps_n = fps_n; + camera->fps_d = fps_d; + camera->video_capture_caps_update = TRUE; + } + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camerabin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBin *camera = GST_CAMERABIN (object); + + switch (prop_id) { + case ARG_FILENAME: + g_value_set_string (value, camera->filename->str); + break; + case ARG_MODE: + g_value_set_enum (value, camera->mode); + break; + case ARG_FLAGS: + g_value_set_flags (value, camera->flags); + break; + case ARG_MUTE: + g_value_set_boolean (value, + gst_camerabin_video_get_mute (GST_CAMERABIN_VIDEO (camera->vidbin))); + break; + case ARG_ZOOM: + g_value_set_float (value, camera->zoom); + break; + case ARG_IMAGE_POST: + g_value_set_object (value, + gst_camerabin_image_get_postproc (GST_CAMERABIN_IMAGE + (camera->imgbin))); + break; + case ARG_IMAGE_ENC: + g_value_set_object (value, + gst_camerabin_image_get_encoder (GST_CAMERABIN_IMAGE + (camera->imgbin))); + break; + case ARG_IMAGE_FORMATTER: + g_value_set_object (value, + gst_camerabin_image_get_formatter (GST_CAMERABIN_IMAGE + (camera->imgbin))); + break; + case ARG_VIDEO_POST: + g_value_set_object (value, + gst_camerabin_video_get_post (GST_CAMERABIN_VIDEO (camera->vidbin))); + break; + case ARG_VIDEO_ENC: + g_value_set_object (value, + gst_camerabin_video_get_video_enc (GST_CAMERABIN_VIDEO + (camera->vidbin))); + break; + case ARG_AUDIO_ENC: + g_value_set_object (value, + gst_camerabin_video_get_audio_enc (GST_CAMERABIN_VIDEO + (camera->vidbin))); + break; + case ARG_VIDEO_MUX: + g_value_set_object (value, + gst_camerabin_video_get_muxer (GST_CAMERABIN_VIDEO (camera->vidbin))); + break; + case ARG_VF_SINK: + if (camera->view_sink) + g_value_set_object (value, camera->view_sink); + else + g_value_set_object (value, camera->app_vf_sink); + break; + case ARG_VIDEO_SRC: + if (camera->src_vid_src) + g_value_set_object (value, camera->src_vid_src); + else + g_value_set_object (value, camera->app_vid_src); + break; + case ARG_AUDIO_SRC: + g_value_set_object (value, + gst_camerabin_video_get_audio_src (GST_CAMERABIN_VIDEO + (camera->vidbin))); + break; + case ARG_VIDEO_SOURCE_FILTER: + g_value_set_object (value, camera->app_video_filter); + break; + case ARG_INPUT_CAPS: + gst_value_set_caps (value, gst_camerabin_get_allowed_input_caps (camera)); + break; + case ARG_FILTER_CAPS: + gst_value_set_caps (value, camera->view_finder_caps); + break; + case ARG_PREVIEW_CAPS: + if (camera->mode == MODE_IMAGE) + gst_value_set_caps (value, camera->preview_caps); + else if (camera->mode == MODE_VIDEO) + gst_value_set_caps (value, camera->video_preview_caps); + break; + case ARG_PREVIEW_SOURCE_FILTER: + if (camera->mode == MODE_IMAGE) + g_value_set_object (value, camera->app_preview_source_filter); + else if (camera->mode == MODE_VIDEO) + g_value_set_object (value, camera->app_video_preview_source_filter); + break; + case ARG_VIEWFINDER_FILTER: + g_value_set_object (value, camera->app_viewfinder_filter); + break; + case ARG_BLOCK_VIEWFINDER: + g_value_set_boolean (value, camera->block_viewfinder_prop); + break; + case ARG_READY_FOR_CAPTURE: + g_mutex_lock (camera->capture_mutex); + g_value_set_boolean (value, !camera->capturing); + g_mutex_unlock (camera->capture_mutex); + break; + case ARG_IMAGE_CAPTURE_WIDTH: + g_value_set_int (value, camera->image_capture_width); + break; + case ARG_IMAGE_CAPTURE_HEIGHT: + g_value_set_int (value, camera->image_capture_height); + break; + case ARG_VIDEO_CAPTURE_WIDTH: + g_value_set_int (value, camera->app_width); + break; + case ARG_VIDEO_CAPTURE_HEIGHT: + g_value_set_int (value, camera->app_height); + break; + case ARG_VIDEO_CAPTURE_FRAMERATE: + gst_value_set_fraction (value, camera->app_fps_n, camera->app_fps_d); + break; + case ARG_IDLE: + g_value_set_boolean (value, camera->processing_counter == 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* + * GstElement functions implementation + */ + +static GstStateChangeReturn +gst_camerabin_change_state (GstElement * element, GstStateChange transition) +{ + GstCameraBin *camera = GST_CAMERABIN (element); + GstStateChangeReturn ret; + + GST_DEBUG_OBJECT (element, "changing state: %s -> %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!camerabin_create_elements (camera)) { + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + /* Lock to control image and video bin state separately + from view finder */ + gst_element_set_locked_state (camera->imgbin, TRUE); + gst_element_set_locked_state (camera->vidbin, TRUE); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + camerabin_setup_src_elements (camera); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* If using autovideosink, set view finder sink properties + now that actual sink has been created. */ + camerabin_setup_view_elements (camera); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* all processing should stop and those elements could have their state + * locked, so set them explicitly here */ + if (GST_STATE (camera->imgbin) != GST_STATE_NULL) { + gst_element_set_state (camera->imgbin, GST_STATE_READY); + } + if (GST_STATE (camera->vidbin) != GST_STATE_NULL) { + gst_element_set_state (camera->vidbin, GST_STATE_READY); + } + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_element_set_locked_state (camera->imgbin, FALSE); + gst_element_set_locked_state (camera->vidbin, FALSE); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + GST_DEBUG_OBJECT (element, "after chaining up: %s -> %s = %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)), + gst_element_state_change_return_get_name (ret)); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + g_mutex_lock (camera->capture_mutex); + if (camera->capturing) { + GST_WARNING_OBJECT (camera, "was capturing when changing to READY"); + camera->capturing = FALSE; + /* Reset capture and don't wait for capturing to finish properly. + Proper capturing should have been finished before going to READY. */ + gst_camerabin_reset_to_view_finder (camera); + g_cond_signal (camera->cond); + } + + /* reset processing counter */ + GST_DEBUG_OBJECT (camera, "Reset processing counter to 0"); + camera->processing_counter = 0; + g_object_notify (G_OBJECT (camera), "idle"); + g_mutex_unlock (camera->capture_mutex); + + /* unblock the viewfinder, but keep the property as is */ + gst_pad_set_blocked_async (camera->pad_src_view, FALSE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + + g_signal_handlers_disconnect_by_func (camera->src_vid_src, + gst_camerabin_scene_mode_notify_cb, camera); + g_signal_handlers_disconnect_by_func (camera->src_vid_src, + gst_camerabin_zoom_notify_cb, camera); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + camerabin_destroy_elements (camera); + break; + /* In some error situation camerabin may end up being still in NULL + state so we must take care of destroying elements. */ + case GST_STATE_CHANGE_NULL_TO_READY: + if (ret == GST_STATE_CHANGE_FAILURE) + camerabin_destroy_elements (camera); + break; + default: + break; + } + +done: + GST_DEBUG_OBJECT (element, "changed state: %s -> %s = %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)), + gst_element_state_change_return_get_name (ret)); + + return ret; +} + +static GstClock * +gst_camerabin_provide_clock (GstElement * element) +{ + GstClock *clock = NULL; + GstClock *vidbin_clock = NULL; + GstCameraBin *camera = GST_CAMERABIN (element); + GstElement *aud_src = GST_CAMERABIN_VIDEO (camera->vidbin)->aud_src; + + if (aud_src) + vidbin_clock = gst_element_provide_clock (aud_src); + + if (camera->capturing && camera->mode == MODE_VIDEO && vidbin_clock) + clock = vidbin_clock; + else { + clock = GST_ELEMENT_CLASS (parent_class)->provide_clock (element); + if (clock == vidbin_clock) { + /* Do not reuse vidbin_clock if it was current clock */ + clock = gst_system_clock_obtain (); + } + } + + GST_INFO_OBJECT (camera, "Reset pipeline clock to %p(%s)", + clock, GST_ELEMENT_NAME (clock)); + + return clock; +} + +static gpointer +gst_camerabin_imgbin_finished (gpointer u_data) +{ + GstCameraBin *camera = GST_CAMERABIN (u_data); + gchar *filename = NULL; + + /* FIXME: should set a flag (and take a lock) when going to NULL, so we + * short-circuit this bit if we got shut down between thread create and now */ + + GST_DEBUG_OBJECT (camera, "Image encoding finished"); + + /* Get the filename of the finished image */ + g_object_get (G_OBJECT (camera->imgbin), "filename", &filename, NULL); + + /* Close the file of saved image */ + gst_element_set_state (camera->imgbin, GST_STATE_READY); + GST_DEBUG_OBJECT (camera, "Image pipeline set to READY"); + + g_mutex_lock (camera->capture_mutex); + if (camera->processing_counter) { + CAMERABIN_PROCESSING_DEC_UNLOCKED (camera); + } else { + /* Camerabin state change to READY may have reset processing counter to + * zero. This is possible as this functions is scheduled from another + * thread. + */ + GST_WARNING_OBJECT (camera, "camerabin has been forced to idle"); + } + g_mutex_unlock (camera->capture_mutex); + + /* Set image bin back to PAUSED so that buffer-allocs don't fail */ + gst_element_set_state (camera->imgbin, GST_STATE_PAUSED); + + /* Unblock image queue pad to process next buffer */ + GST_STATE_LOCK (camera); + if (camera->pad_src_queue) { + gst_pad_set_blocked_async (camera->pad_src_queue, FALSE, + (GstPadBlockCallback) camerabin_pad_blocked, camera); + GST_DEBUG_OBJECT (camera, "Queue srcpad unblocked"); + } else { + GST_DEBUG_OBJECT (camera, "Queue srcpad unreffed already, doesn't need " + "to unblock"); + } + GST_STATE_UNLOCK (camera); + + /* Send image-done signal */ + gst_camerabin_image_capture_continue (camera, filename); + g_free (filename); + + GST_INFO_OBJECT (camera, "leaving helper thread"); + gst_object_unref (camera); + return NULL; +} + +/* + * GstBin functions implementation + */ + +/* Peek eos messages but don't interfere with bin msg handling */ +static void +gst_camerabin_handle_message_func (GstBin * bin, GstMessage * msg) +{ + GstCameraBin *camera = GST_CAMERABIN (bin); + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS: + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->vidbin)) { + /* Video eos */ + GST_DEBUG_OBJECT (camera, + "got video eos message, stopping video capture"); + g_mutex_lock (camera->capture_mutex); + camera->capturing = FALSE; + g_cond_signal (camera->cond); + + CAMERABIN_PROCESSING_DEC_UNLOCKED (camera); + g_mutex_unlock (camera->capture_mutex); + } else if (GST_MESSAGE_SRC (msg) == GST_OBJECT (camera->imgbin)) { + /* Image eos */ + GST_DEBUG_OBJECT (camera, "got image eos message"); + /* Can't change state here, since we're in the streaming thread */ + if (!g_thread_create (gst_camerabin_imgbin_finished, + gst_object_ref (camera), FALSE, NULL)) { + /* FIXME: what do do if this fails? */ + gst_object_unref (camera); + } + } + break; + case GST_MESSAGE_ERROR: + GST_DEBUG_OBJECT (camera, "error from child %" GST_PTR_FORMAT, + GST_MESSAGE_SRC (msg)); + g_mutex_lock (camera->capture_mutex); + if (camera->capturing) { + camera->capturing = FALSE; + g_cond_signal (camera->cond); + } + + /* Ideally we should check what error was and only decrement the + * counter if the error means that a 'processing' operation failed, + * instead of a setting up error. But this can be quite tricky to do + * and we expect the app to set the whole pipeline to READY/NULL + * when an error happens. For now we just mention that the + * processing counter and the 'idle' property are unreliable */ + GST_DEBUG_OBJECT (camera, "An error makes the processing counter " + "unreliable"); + + g_mutex_unlock (camera->capture_mutex); + break; + default: + break; + } + GST_BIN_CLASS (parent_class)->handle_message (bin, msg); +} + +/* + * Action signal function implementation + */ + +static void +gst_camerabin_capture_start (GstCameraBin * camera) +{ + + GST_INFO_OBJECT (camera, "starting capture"); + if (camera->paused) { + gst_camerabin_capture_pause (camera); + return; + } + + if (!camera->active_bin) { + GST_INFO_OBJECT (camera, "mode not explicitly set by application"); + gst_camerabin_change_mode (camera, camera->mode); + if (!camera->active_bin) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("starting capture failed"), (NULL)); + } + } + + /* We need a filename unless it's a photo and preview_caps is set */ + + if (g_str_equal (camera->filename->str, "")) + if (camera->active_bin == camera->vidbin || !camera->preview_caps) { + GST_ELEMENT_ERROR (camera, CORE, FAILED, + ("set filename before starting capture"), (NULL)); + return; + } + + g_mutex_lock (camera->capture_mutex); + if (camera->capturing) { + GST_WARNING_OBJECT (camera, "capturing \"%s\" ongoing, set new filename", + camera->filename->str); + /* FIXME: we need to send something more to the app, so that it does not for + * for image-done */ + g_mutex_unlock (camera->capture_mutex); + return; + } + CAMERABIN_PROCESSING_INC_UNLOCKED (camera); + g_mutex_unlock (camera->capture_mutex); + + GST_OBJECT_LOCK (camera); + camera->block_viewfinder_trigger = camera->block_viewfinder_prop; + GST_OBJECT_UNLOCK (camera); + + if (camera->active_bin) { + if (camera->active_bin == camera->imgbin) { + GST_INFO_OBJECT (camera, "starting image capture"); + gst_camerabin_start_image_capture (camera); + } else if (camera->active_bin == camera->vidbin) { + GST_INFO_OBJECT (camera, + "setting video filename and starting video capture"); + g_object_set (G_OBJECT (camera->active_bin), "filename", + camera->filename->str, NULL); + gst_camerabin_start_video_recording (camera); + } + } + /* Capturing is now ongoing, notify that new capture isn't possible */ + g_object_notify (G_OBJECT (camera), "ready-for-capture"); +} + +static void +gst_camerabin_capture_stop (GstCameraBin * camera) +{ + if (camera->active_bin == camera->vidbin) { + GST_INFO_OBJECT (camera, "stopping video capture"); + gst_camerabin_do_stop (camera); + gst_camerabin_reset_to_view_finder (camera); + /* Video capture stopped, notify that preparing a new capture is possible */ + g_object_notify (G_OBJECT (camera), "ready-for-capture"); + } else { + GST_INFO_OBJECT (camera, "stopping image capture isn't needed"); + } +} + +static void +gst_camerabin_capture_pause (GstCameraBin * camera) +{ + if (camera->active_bin == camera->vidbin) { + if (!camera->paused) { + GST_INFO_OBJECT (camera, "pausing capture"); + + /* Bring all camerabin elements to PAUSED */ + gst_element_set_locked_state (camera->vidbin, FALSE); + gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED); + + /* Switch to view finder mode */ + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", FALSE, + "active-pad", camera->pad_src_view, NULL); + + /* Set view finder to PLAYING and leave videobin PAUSED */ + gst_element_set_locked_state (camera->vidbin, TRUE); + gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING); + + camera->paused = TRUE; + } else { + GST_INFO_OBJECT (camera, "unpausing capture"); + + /* Bring all camerabin elements to PAUSED */ + gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PAUSED); + + /* Switch to video recording mode */ + g_object_set (G_OBJECT (camera->src_out_sel), "resend-latest", TRUE, + "active-pad", camera->pad_src_vid, NULL); + + /* Bring all camerabin elements to PLAYING */ + gst_element_set_locked_state (camera->vidbin, FALSE); + gst_element_set_state (GST_ELEMENT (camera), GST_STATE_PLAYING); + gst_element_set_locked_state (camera->vidbin, TRUE); + + camera->paused = FALSE; + } + GST_DEBUG_OBJECT (camera, "pause done"); + } else { + GST_WARNING ("pausing in image capture mode disabled"); + } +} + +/* + * Updates the properties (excluding the user preferred width/height/fps) and + * tells camerabin to update the video capture caps. + */ +static void +do_set_video_resolution_fps (GstCameraBin * camera, gint width, + gint height, gint fps_n, gint fps_d) +{ + if (height != camera->height) { + camera->height = height; + camera->video_capture_caps_update = TRUE; + } + if (width != camera->width) { + camera->width = width; + camera->video_capture_caps_update = TRUE; + } + if (fps_n != camera->fps_n) { + camera->fps_n = fps_n; + camera->video_capture_caps_update = TRUE; + } + if (fps_d != camera->fps_d) { + camera->fps_d = fps_d; + camera->video_capture_caps_update = TRUE; + } + + reset_video_capture_caps (camera); +} + +/* + * Updates the properties (including the user preferred width/height/fps) and + * tells camerabin to update the video capture caps. + */ +static void +gst_camerabin_set_video_resolution_fps (GstCameraBin * camera, gint width, + gint height, gint fps_n, gint fps_d) +{ + g_object_set (camera, "video-capture-width", width, "video-capture-height", + height, "video-capture-framerate", fps_n, fps_d, NULL); + + reset_video_capture_caps (camera); +} + +static void +gst_camerabin_set_image_capture_caps (GstCameraBin * camera, gint width, + gint height) +{ + GstStructure *structure; + GstCaps *new_caps = NULL; + + g_return_if_fail (camera != NULL); + + if (width && height && camera->view_finder_caps) { + /* Use view finder mode caps as a basis */ + structure = gst_caps_get_structure (camera->view_finder_caps, 0); + + /* Set new resolution for image capture */ + new_caps = gst_caps_new_simple (gst_structure_get_name (structure), + "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL); + + /* Set allowed framerate for the resolution. */ + gst_camerabin_set_allowed_framerate (camera, new_caps); + } + + GST_INFO_OBJECT (camera, + "init filter caps for image capture %" GST_PTR_FORMAT, new_caps); + gst_caps_replace (&camera->image_capture_caps, new_caps); + camera->image_capture_caps_update = FALSE; + if (new_caps) + gst_caps_unref (new_caps); +} + +static void +gst_camerabin_set_image_resolution (GstCameraBin * camera, gint width, + gint height) +{ + g_object_set (camera, "image-capture-width", (guint16) width, + "image-capture-height", (guint16) height, NULL); +} + +/* entry point to initialize the plug-in + * initialize the plug-in itself + * register the element factories and pad templates + * register the features + */ +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_camerabin_debug, "camerabin", 0, "CameraBin"); + + return gst_element_register (plugin, "camerabin", + GST_RANK_NONE, GST_TYPE_CAMERABIN); +} + +/* this is the structure that gstreamer looks for to register plugins + */ +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "camerabin", + "High level api for DC (Digital Camera) application", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/gst/camerabin/gstcamerabin.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabin.h 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,235 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_CAMERABIN_H__ +#define __GST_CAMERABIN_H__ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "gstcamerabin-enum.h" +#include "camerabinimage.h" +#include "camerabinvideo.h" +#include "camerabinpreview.h" + +G_BEGIN_DECLS +/* #defines don't like whitespacey bits */ +#define GST_TYPE_CAMERABIN \ + (gst_camerabin_get_type()) +#define GST_CAMERABIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERABIN,GstCameraBin)) +#define GST_CAMERABIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERABIN,GstCameraBinClass)) +#define GST_IS_CAMERABIN(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERABIN)) +#define GST_IS_CAMERABIN_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERABIN)) +typedef struct _GstCameraBin GstCameraBin; +typedef struct _GstCameraBinClass GstCameraBinClass; + +/** + * GstCameraBin: + * + * The opaque #GstCameraBin structure. + */ + +struct _GstCameraBin +{ + GstPipeline parent; + + /* private */ + GString *filename; + gint mode; /* MODE_IMAGE or MODE_VIDEO */ + GstCameraBinFlags flags; + gboolean stop_requested; /* TRUE if capturing stop needed */ + gboolean paused; /* TRUE if capturing paused */ + + /* + * Those 2 booleans work together. + * + * 'block_viewfinder_prop' is the property, 'block_viewfinder_trigger' + * is the flag that actually makes the viewfinder block after capture. + * We need both to avoid blocking the viewfinder if the application resets + * the flag after issuing the 'capture-start', but before the actual + * blocking happens. This causes the viewfinder to block even though + * the application resetted the flag to keep it running already. + * + * Here's how this should work: + * When a capture is started, the property is checked, if it is TRUE, the + * trigger is set to TRUE. The blocking will only happen if + * the trigger is TRUE after image capture finishes, ff the property + * is reset before the blocking happens, the trigger goes to + * FALSE and no blocking happens. + */ + gboolean block_viewfinder_prop; /* TRUE if viewfinder blocks after capture */ + gboolean block_viewfinder_trigger; + + /* Resolution of the buffers configured to camerabin */ + gint width; + gint height; + /* Frames per second configured to camerabin */ + gint fps_n; + gint fps_d; + + /* app configured resolution/framerate */ + gint app_width; + gint app_height; + gint app_fps_n; + gint app_fps_d; + + gboolean video_capture_caps_update; + + /* Image capture resolution */ + gint image_capture_width; + gint image_capture_height; + + /* Image tags are collected here first before sending to imgbin */ + GstTagList *event_tags; + + /* Caps applied to capsfilters when taking still image */ + GstCaps *image_capture_caps; + gboolean image_capture_caps_update; + + /* Caps applied to capsfilters when in view finder mode */ + GstCaps *view_finder_caps; + + /* Caps that videosrc supports */ + GstCaps *allowed_caps; + + /* Caps used to create preview image */ + GstCaps *preview_caps; + + /* Caps used to create video preview image */ + GstCaps *video_preview_caps; + + /* The digital zoom (from 1.0 to 10.0) */ + gfloat zoom; + + /* concurrency control */ + GMutex *capture_mutex; + GCond *cond; + gboolean capturing; + gboolean eos_handled; + /* everytime a new capture is started this is incremented, when it is + * finished/fails it is decremented. Used to know if camerabin is idle */ + gint processing_counter; + + /* pad names for output and input selectors */ + GstPad *pad_src_view; + GstPad *pad_view_src; + GstPad *pad_src_img; + GstPad *pad_src_vid; + GstPad *pad_view_vid; + GstPad *pad_src_queue; + + GstElement *img_queue; /* queue for decoupling capture from + image-postprocessing and saving */ + GstElement *imgbin; /* bin that holds image capturing elements */ + GstElement *vidbin; /* bin that holds video capturing elements */ + GstElement *active_bin; /* image or video bin that is currently in use */ + /* pipeline for creating preview images */ + GstCameraBinPreviewPipelineData *preview_pipeline; + /* pipeline for creating video preview image */ + GstCameraBinPreviewPipelineData *video_preview_pipeline; + + GstBuffer *video_preview_buffer; /* buffer for storing video preview */ + + /* source elements */ + GstElement *src_vid_src; + GstElement *src_filter; + GstElement *src_zoom_crop; + GstElement *src_zoom_scale; + GstElement *src_zoom_filter; + GstElement *src_out_sel; + + /* view finder elements */ + GstElement *view_in_sel; + GstElement *aspect_filter; + GstElement *view_scale; + GstElement *view_sink; + + /* Application configurable elements */ + GstElement *app_vid_src; + GstElement *app_vf_sink; + GstElement *app_video_filter; + GstElement *app_viewfinder_filter; + GstElement *app_preview_source_filter; + GstElement *app_video_preview_source_filter; + + /* Night mode handling */ + gboolean night_mode; + gint pre_night_fps_n; + gint pre_night_fps_d; + + /* Buffer probe id for captured image handling */ + gulong image_captured_id; + + /* Optional base crop for frames. Used to crop frames e.g. + due to wrong aspect ratio, before the crop related to zooming. */ + gint base_crop_top; + gint base_crop_bottom; + gint base_crop_left; + gint base_crop_right; +}; + +/** + * GstCameraBinClass: + * + * The #GstCameraBin class structure. + */ +struct _GstCameraBinClass +{ + GstPipelineClass parent_class; + + /* action signals */ + + void (*capture_start) (GstCameraBin * camera); + void (*capture_stop) (GstCameraBin * camera); + void (*capture_pause) (GstCameraBin * camera); + void (*set_video_resolution_fps) (GstCameraBin * camera, gint width, + gint height, gint fps_n, gint fps_d); + void (*set_image_resolution) (GstCameraBin * camera, gint width, gint height); + + /* signals (callback) */ + + gboolean (*img_done) (GstCameraBin * camera, const gchar * filename); +}; + +/** + * GstCameraBinMode: + * @MODE_IMAGE: image capture + * @MODE_VIDEO: video capture + * + * Capture mode to use. + */ +typedef enum +{ + MODE_IMAGE = 0, + MODE_VIDEO +} GstCameraBinMode; + +GType gst_camerabin_get_type (void); + +G_END_DECLS +#endif /* #ifndef __GST_CAMERABIN_H__ */ Index: gst-plugins-good0.10/gst/camerabin/gstcamerabincolorbalance.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabincolorbalance.c 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,81 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Includes + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstcamerabincolorbalance.h" +#include "gstcamerabin.h" + +/* + * static functions implementation + */ + +static const GList * +gst_camerabin_color_balance_list_channels (GstColorBalance * cb) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + return gst_color_balance_list_channels (cbl); + } else { + return NULL; + } +} + +static void +gst_camerabin_color_balance_set_value (GstColorBalance * cb, + GstColorBalanceChannel * channel, gint value) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + gst_color_balance_set_value (cbl, channel, value); + } +} + +static gint +gst_camerabin_color_balance_get_value (GstColorBalance * cb, + GstColorBalanceChannel * channel) +{ + if (cb && GST_CAMERABIN (cb)->src_vid_src) { + GstColorBalance *cbl = GST_COLOR_BALANCE (GST_CAMERABIN (cb)->src_vid_src); + return gst_color_balance_get_value (cbl, channel); + } else { + return 0; + } +} + +/* + * extern functions implementation + */ + +void +gst_camerabin_color_balance_init (GstColorBalanceClass * iface) +{ + /* FIXME: to get the same type as v4l2src */ + GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE; + iface->list_channels = gst_camerabin_color_balance_list_channels; + iface->set_value = gst_camerabin_color_balance_set_value; + iface->get_value = gst_camerabin_color_balance_get_value; +} Index: gst-plugins-good0.10/gst/camerabin/gstcamerabincolorbalance.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstcamerabincolorbalance.h 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,28 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_CAMERA_COLOR_BALANCE_H__ +#define __GST_CAMERA_COLOR_BALANCE_H__ + +#include + +extern void gst_camerabin_color_balance_init (GstColorBalanceClass * iface); + +#endif /* #ifndef __GST_CAMERA_COLOR_BALANCE_H__ */ Index: gst-plugins-good0.10/gst/camerabin/gstinputselector.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstinputselector.c 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,1498 @@ +/* GStreamer + * Copyright (C) 2003 Julien Moutte + * Copyright (C) 2005 Ronald S. Bultje + * Copyright (C) 2005 Jan Schmidt + * Copyright (C) 2007 Wim Taymans + * Copyright (C) 2007 Andy Wingo + * Copyright (C) 2008 Nokia Corporation. (contact ) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-input-selector + * @see_also: #GstOutputSelector + * + * Direct one out of N input streams to the output pad. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstinputselector.h" +#include "gstcamerabin-marshal.h" + +GST_DEBUG_CATEGORY_STATIC (input_selector_debug); +#define GST_CAT_DEFAULT input_selector_debug + +static GstStaticPadTemplate gst_input_selector_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_input_selector_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +enum +{ + PROP_0, + PROP_N_PADS, + PROP_ACTIVE_PAD, + PROP_SELECT_ALL, + PROP_LAST +}; + +#define DEFAULT_PAD_ALWAYS_OK TRUE + +enum +{ + PROP_PAD_0, + PROP_PAD_RUNNING_TIME, + PROP_PAD_TAGS, + PROP_PAD_ACTIVE, + PROP_PAD_ALWAYS_OK, + PROP_PAD_LAST +}; + +enum +{ + /* methods */ + SIGNAL_BLOCK, + SIGNAL_SWITCH, + LAST_SIGNAL +}; +static guint gst_input_selector_signals[LAST_SIGNAL] = { 0 }; + +static inline gboolean gst_input_selector_is_active_sinkpad (GstInputSelector * + sel, GstPad * pad); +static GstPad *gst_input_selector_activate_sinkpad (GstInputSelector * sel, + GstPad * pad); +static GstPad *gst_input_selector_get_linked_pad (GstPad * pad, + gboolean strict); +static gboolean gst_input_selector_check_eos (GstElement * selector); + +#define GST_TYPE_SELECTOR_PAD \ + (gst_selector_pad_get_type()) +#define GST_SELECTOR_PAD(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SELECTOR_PAD, GstSelectorPad)) +#define GST_SELECTOR_PAD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_SELECTOR_PAD, GstSelectorPadClass)) +#define GST_IS_SELECTOR_PAD(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_SELECTOR_PAD)) +#define GST_IS_SELECTOR_PAD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_SELECTOR_PAD)) +#define GST_SELECTOR_PAD_CAST(obj) \ + ((GstSelectorPad *)(obj)) + +typedef struct _GstSelectorPad GstSelectorPad; +typedef struct _GstSelectorPadClass GstSelectorPadClass; + +struct _GstSelectorPad +{ + GstPad parent; + + gboolean active; /* when buffer have passed the pad */ + gboolean eos; /* when EOS has been received */ + gboolean discont; /* after switching we create a discont */ + gboolean always_ok; + GstSegment segment; /* the current segment on the pad */ + GstTagList *tags; /* last tags received on the pad */ + + gboolean segment_pending; +}; + +struct _GstSelectorPadClass +{ + GstPadClass parent; +}; + +static void gst_selector_pad_class_init (GstSelectorPadClass * klass); +static void gst_selector_pad_init (GstSelectorPad * pad); +static void gst_selector_pad_finalize (GObject * object); +static void gst_selector_pad_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_selector_pad_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); + +static GstPadClass *selector_pad_parent_class = NULL; + +static gint64 gst_selector_pad_get_running_time (GstSelectorPad * pad); +static void gst_selector_pad_reset (GstSelectorPad * pad); +static gboolean gst_selector_pad_event (GstPad * pad, GstEvent * event); +static GstCaps *gst_selector_pad_getcaps (GstPad * pad); +static gboolean gst_selector_pad_acceptcaps (GstPad * pad, GstCaps * caps); +static GstIterator *gst_selector_pad_iterate_linked_pads (GstPad * pad); +static GstFlowReturn gst_selector_pad_chain (GstPad * pad, GstBuffer * buf); +static GstFlowReturn gst_selector_pad_bufferalloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); + +static GType +gst_selector_pad_get_type (void) +{ + static GType selector_pad_type = 0; + + if (!selector_pad_type) { + static const GTypeInfo selector_pad_info = { + sizeof (GstSelectorPadClass), + NULL, + NULL, + (GClassInitFunc) gst_selector_pad_class_init, + NULL, + NULL, + sizeof (GstSelectorPad), + 0, + (GInstanceInitFunc) gst_selector_pad_init, + }; + + selector_pad_type = + g_type_register_static (GST_TYPE_PAD, "GstCamerabinSelectorPad", + &selector_pad_info, 0); + } + return selector_pad_type; +} + +static void +gst_selector_pad_class_init (GstSelectorPadClass * klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + selector_pad_parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_selector_pad_finalize; + + gobject_class->get_property = gst_selector_pad_get_property; + gobject_class->set_property = gst_selector_pad_set_property; + + g_object_class_install_property (gobject_class, PROP_PAD_RUNNING_TIME, + g_param_spec_int64 ("running-time", "Running time", + "Running time of stream on pad", 0, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PAD_TAGS, + g_param_spec_boxed ("tags", "Tags", + "The currently active tags on the pad", GST_TYPE_TAG_LIST, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PAD_ACTIVE, + g_param_spec_boolean ("active", "Active", + "If the pad is currently active", FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PAD_ALWAYS_OK, + g_param_spec_boolean ("always-ok", "Always OK", + "Make an inactive pad return OK instead of NOT_LINKED", + DEFAULT_PAD_ALWAYS_OK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_selector_pad_init (GstSelectorPad * pad) +{ + pad->always_ok = DEFAULT_PAD_ALWAYS_OK; + gst_selector_pad_reset (pad); +} + +static void +gst_selector_pad_finalize (GObject * object) +{ + GstSelectorPad *pad; + + pad = GST_SELECTOR_PAD_CAST (object); + + if (pad->tags) + gst_tag_list_free (pad->tags); + + G_OBJECT_CLASS (selector_pad_parent_class)->finalize (object); +} + +static void +gst_selector_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSelectorPad *spad = GST_SELECTOR_PAD_CAST (object); + + switch (prop_id) { + case PROP_PAD_ALWAYS_OK: + GST_OBJECT_LOCK (object); + spad->always_ok = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_selector_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstSelectorPad *spad = GST_SELECTOR_PAD_CAST (object); + + switch (prop_id) { + case PROP_PAD_RUNNING_TIME: + g_value_set_int64 (value, gst_selector_pad_get_running_time (spad)); + break; + case PROP_PAD_TAGS: + GST_OBJECT_LOCK (object); + g_value_set_boxed (value, spad->tags); + GST_OBJECT_UNLOCK (object); + break; + case PROP_PAD_ACTIVE: + { + GstInputSelector *sel; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (spad)); + g_value_set_boolean (value, gst_input_selector_is_active_sinkpad (sel, + GST_PAD_CAST (spad))); + gst_object_unref (sel); + break; + } + case PROP_PAD_ALWAYS_OK: + GST_OBJECT_LOCK (object); + g_value_set_boolean (value, spad->always_ok); + GST_OBJECT_UNLOCK (object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gint64 +gst_selector_pad_get_running_time (GstSelectorPad * pad) +{ + gint64 ret = 0; + + GST_OBJECT_LOCK (pad); + if (pad->active) { + gint64 last_stop = pad->segment.last_stop; + + if (last_stop >= 0) + ret = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME, + last_stop); + } + GST_OBJECT_UNLOCK (pad); + + GST_DEBUG_OBJECT (pad, "running time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ret)); + + return ret; +} + +static void +gst_selector_pad_reset (GstSelectorPad * pad) +{ + GST_OBJECT_LOCK (pad); + pad->active = FALSE; + pad->eos = FALSE; + pad->segment_pending = FALSE; + pad->discont = FALSE; + gst_segment_init (&pad->segment, GST_FORMAT_UNDEFINED); + GST_OBJECT_UNLOCK (pad); +} + +/* strictly get the linked pad from the sinkpad. If the pad is active we return + * the srcpad else we return NULL */ +static GstIterator * +gst_selector_pad_iterate_linked_pads (GstPad * pad) +{ + GstInputSelector *sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + GstPad *otherpad; + GstIterator *it; + + otherpad = gst_input_selector_get_linked_pad (pad, TRUE); + it = gst_iterator_new_single (GST_TYPE_PAD, otherpad, + (GstCopyFunction) gst_object_ref, (GFreeFunc) gst_object_unref); + + if (otherpad) + gst_object_unref (otherpad); + gst_object_unref (sel); + + return it; +} + +static gboolean +gst_selector_pad_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + gboolean forward = TRUE; + GstInputSelector *sel; + GstSelectorPad *selpad; + GstPad *prev_active_sinkpad; + GstPad *active_sinkpad; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + selpad = GST_SELECTOR_PAD_CAST (pad); + + GST_INPUT_SELECTOR_LOCK (sel); + prev_active_sinkpad = sel->active_sinkpad; + active_sinkpad = gst_input_selector_activate_sinkpad (sel, pad); + + /* only forward if we are dealing with the active sinkpad or if select_all + * is enabled */ + if (pad != active_sinkpad && !sel->select_all) + forward = FALSE; + GST_INPUT_SELECTOR_UNLOCK (sel); + + if (prev_active_sinkpad != active_sinkpad && pad == active_sinkpad) + g_object_notify (G_OBJECT (sel), "active-pad"); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + /* FIXME, flush out the waiter */ + break; + case GST_EVENT_FLUSH_STOP: + GST_INPUT_SELECTOR_LOCK (sel); + gst_selector_pad_reset (selpad); + sel->pending_close = FALSE; + GST_INPUT_SELECTOR_UNLOCK (sel); + break; + case GST_EVENT_NEWSEGMENT: + { + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + GST_DEBUG_OBJECT (pad, + "configured NEWSEGMENT update %d, rate %lf, applied rate %lf, " + "format %d, " + "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %" + G_GINT64_FORMAT, update, rate, arate, format, start, stop, time); + + GST_INPUT_SELECTOR_LOCK (sel); + GST_OBJECT_LOCK (selpad); + gst_segment_set_newsegment_full (&selpad->segment, update, + rate, arate, format, start, stop, time); + GST_OBJECT_UNLOCK (selpad); + + /* If we aren't forwarding the event (because the pad is not the + * active_sinkpad, and select_all is not set, then set the flag on the + * that says a segment needs sending if/when that pad is activated. + * For all other cases, we send the event immediately, which makes + * sparse streams and other segment updates work correctly downstream. + */ + if (!forward) + selpad->segment_pending = TRUE; + + GST_INPUT_SELECTOR_UNLOCK (sel); + break; + } + case GST_EVENT_TAG: + { + GstTagList *tags, *oldtags, *newtags; + + gst_event_parse_tag (event, &tags); + + GST_OBJECT_LOCK (selpad); + oldtags = selpad->tags; + + newtags = gst_tag_list_merge (oldtags, tags, GST_TAG_MERGE_REPLACE); + selpad->tags = newtags; + if (oldtags) + gst_tag_list_free (oldtags); + GST_DEBUG_OBJECT (pad, "received tags %" GST_PTR_FORMAT, newtags); + GST_OBJECT_UNLOCK (selpad); + + g_object_notify (G_OBJECT (selpad), "tags"); + break; + } + case GST_EVENT_EOS: + selpad->eos = TRUE; + GST_DEBUG_OBJECT (pad, "received EOS"); + /* don't forward eos in select_all mode until all sink pads have eos */ + if (sel->select_all && !gst_input_selector_check_eos (GST_ELEMENT (sel))) { + forward = FALSE; + } + break; + default: + break; + } + if (forward) { + GST_DEBUG_OBJECT (pad, "forwarding event"); + res = gst_pad_push_event (sel->srcpad, event); + } else + gst_event_unref (event); + + gst_object_unref (sel); + + return res; +} + +static GstCaps * +gst_selector_pad_getcaps (GstPad * pad) +{ + GstInputSelector *sel; + GstCaps *caps; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (sel, "Getting caps of srcpad peer"); + caps = gst_pad_peer_get_caps_reffed (sel->srcpad); + if (caps == NULL) + caps = gst_caps_new_any (); + + gst_object_unref (sel); + + return caps; +} + +static gboolean +gst_selector_pad_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstInputSelector *sel; + gboolean res; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (sel, "Checking acceptcaps of srcpad peer"); + res = gst_pad_peer_accept_caps (sel->srcpad, caps); + gst_object_unref (sel); + + return res; +} + +static GstFlowReturn +gst_selector_pad_bufferalloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf) +{ + GstInputSelector *sel; + GstFlowReturn result; + GstPad *active_sinkpad; + GstPad *prev_active_sinkpad; + GstSelectorPad *selpad; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + selpad = GST_SELECTOR_PAD_CAST (pad); + + GST_LOG_OBJECT (pad, "received alloc"); + + GST_INPUT_SELECTOR_LOCK (sel); + prev_active_sinkpad = sel->active_sinkpad; + active_sinkpad = gst_input_selector_activate_sinkpad (sel, pad); + + if (pad != active_sinkpad) + goto not_active; + + GST_INPUT_SELECTOR_UNLOCK (sel); + + if (prev_active_sinkpad != active_sinkpad && pad == active_sinkpad) + g_object_notify (G_OBJECT (sel), "active-pad"); + + result = gst_pad_alloc_buffer (sel->srcpad, offset, size, caps, buf); + +done: + gst_object_unref (sel); + + return result; + + /* ERRORS */ +not_active: + { + GST_INPUT_SELECTOR_UNLOCK (sel); + + /* unselected pad, perform fallback alloc or return unlinked when + * asked */ + GST_OBJECT_LOCK (selpad); + if (selpad->always_ok) { + GST_DEBUG_OBJECT (pad, "Not selected, performing fallback allocation"); + *buf = NULL; + result = GST_FLOW_OK; + } else { + GST_DEBUG_OBJECT (pad, "Not selected, return NOT_LINKED"); + result = GST_FLOW_NOT_LINKED; + } + GST_OBJECT_UNLOCK (selpad); + + goto done; + } +} + +/* must be called with the SELECTOR_LOCK, will block while the pad is blocked + * or return TRUE when flushing */ +static gboolean +gst_input_selector_wait (GstInputSelector * self, GstPad * pad) +{ + while (self->blocked && !self->flushing) { + /* we can be unlocked here when we are shutting down (flushing) or when we + * get unblocked */ + GST_INPUT_SELECTOR_WAIT (self); + } + return self->flushing; +} + +static GstFlowReturn +gst_selector_pad_chain (GstPad * pad, GstBuffer * buf) +{ + GstInputSelector *sel; + GstFlowReturn res; + GstPad *active_sinkpad; + GstPad *prev_active_sinkpad; + GstSelectorPad *selpad; + GstClockTime start_time; + GstSegment *seg; + GstEvent *close_event = NULL, *start_event = NULL; + GstCaps *caps; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + selpad = GST_SELECTOR_PAD_CAST (pad); + seg = &selpad->segment; + + GST_INPUT_SELECTOR_LOCK (sel); + /* wait or check for flushing */ + if (gst_input_selector_wait (sel, pad)) + goto flushing; + + GST_LOG_OBJECT (pad, "getting active pad"); + + prev_active_sinkpad = sel->active_sinkpad; + active_sinkpad = gst_input_selector_activate_sinkpad (sel, pad); + + /* update the segment on the srcpad */ + start_time = GST_BUFFER_TIMESTAMP (buf); + if (GST_CLOCK_TIME_IS_VALID (start_time)) { + GST_LOG_OBJECT (pad, "received start time %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + if (GST_BUFFER_DURATION_IS_VALID (buf)) + GST_LOG_OBJECT (pad, "received end time %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time + GST_BUFFER_DURATION (buf))); + + GST_OBJECT_LOCK (pad); + gst_segment_set_last_stop (seg, seg->format, start_time); + GST_OBJECT_UNLOCK (pad); + } + + /* Ignore buffers from pads except the selected one */ + if (pad != active_sinkpad) + goto ignore; + + if (G_UNLIKELY (sel->pending_close)) { + GstSegment *cseg = &sel->segment; + + GST_DEBUG_OBJECT (sel, + "pushing close NEWSEGMENT update %d, rate %lf, applied rate %lf, " + "format %d, " + "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %" + G_GINT64_FORMAT, TRUE, cseg->rate, cseg->applied_rate, cseg->format, + cseg->start, cseg->stop, cseg->time); + + /* create update segment */ + close_event = gst_event_new_new_segment_full (TRUE, cseg->rate, + cseg->applied_rate, cseg->format, cseg->start, cseg->stop, cseg->time); + + sel->pending_close = FALSE; + } + /* if we have a pending segment, push it out now */ + if (G_UNLIKELY (selpad->segment_pending)) { + GST_DEBUG_OBJECT (pad, + "pushing pending NEWSEGMENT update %d, rate %lf, applied rate %lf, " + "format %d, " + "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %" + G_GINT64_FORMAT, FALSE, seg->rate, seg->applied_rate, seg->format, + seg->start, seg->stop, seg->time); + + start_event = gst_event_new_new_segment_full (FALSE, seg->rate, + seg->applied_rate, seg->format, seg->start, seg->stop, seg->time); + + selpad->segment_pending = FALSE; + } + GST_INPUT_SELECTOR_UNLOCK (sel); + + if (prev_active_sinkpad != active_sinkpad && pad == active_sinkpad) + g_object_notify (G_OBJECT (sel), "active-pad"); + + if (close_event) + gst_pad_push_event (sel->srcpad, close_event); + + if (start_event) + gst_pad_push_event (sel->srcpad, start_event); + + if (selpad->discont) { + buf = gst_buffer_make_metadata_writable (buf); + + GST_DEBUG_OBJECT (pad, "Marking discont buffer %p", buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + selpad->discont = FALSE; + } + + /* forward */ + GST_LOG_OBJECT (pad, "Forwarding buffer %p", buf); + + if ((caps = GST_BUFFER_CAPS (buf))) { + if (GST_PAD_CAPS (sel->srcpad) != caps) + gst_pad_set_caps (sel->srcpad, caps); + } + + res = gst_pad_push (sel->srcpad, buf); + +done: + gst_object_unref (sel); + return res; + + /* dropped buffers */ +ignore: + { + GST_DEBUG_OBJECT (pad, "Pad not active, discard buffer %p", buf); + /* when we drop a buffer, we're creating a discont on this pad */ + selpad->discont = TRUE; + GST_INPUT_SELECTOR_UNLOCK (sel); + gst_buffer_unref (buf); + + /* figure out what to return upstream */ + GST_OBJECT_LOCK (selpad); + if (selpad->always_ok) + res = GST_FLOW_OK; + else + res = GST_FLOW_NOT_LINKED; + GST_OBJECT_UNLOCK (selpad); + + goto done; + } +flushing: + { + GST_DEBUG_OBJECT (pad, "We are flushing, discard buffer %p", buf); + GST_INPUT_SELECTOR_UNLOCK (sel); + gst_buffer_unref (buf); + res = GST_FLOW_WRONG_STATE; + goto done; + } +} + +static void gst_input_selector_init (GstInputSelector * sel); +static void gst_input_selector_base_init (GstInputSelectorClass * klass); +static void gst_input_selector_class_init (GstInputSelectorClass * klass); + +static void gst_input_selector_dispose (GObject * object); + +static void gst_input_selector_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_input_selector_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstPad *gst_input_selector_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * unused); +static void gst_input_selector_release_pad (GstElement * element, GstPad * pad); + +static GstStateChangeReturn gst_input_selector_change_state (GstElement * + element, GstStateChange transition); + +static GstCaps *gst_input_selector_getcaps (GstPad * pad); +static gboolean gst_input_selector_event (GstPad * pad, GstEvent * event); +static gboolean gst_input_selector_query (GstPad * pad, GstQuery * query); +static gint64 gst_input_selector_block (GstInputSelector * self); +static void gst_input_selector_switch (GstInputSelector * self, + GstPad * pad, gint64 stop_time, gint64 start_time); + +static GstElementClass *parent_class = NULL; + +GType +gst_input_selector_get_type (void) +{ + static GType input_selector_type = 0; + + if (!input_selector_type) { + static const GTypeInfo input_selector_info = { + sizeof (GstInputSelectorClass), + (GBaseInitFunc) gst_input_selector_base_init, + NULL, + (GClassInitFunc) gst_input_selector_class_init, + NULL, + NULL, + sizeof (GstInputSelector), + 0, + (GInstanceInitFunc) gst_input_selector_init, + }; + input_selector_type = + g_type_register_static (GST_TYPE_ELEMENT, + "GstCamerabinInputSelector", &input_selector_info, 0); + GST_DEBUG_CATEGORY_INIT (input_selector_debug, + "camerabin-input-selector", 0, "Camerabin input selector element"); + } + + return input_selector_type; +} + +static void +gst_input_selector_base_init (GstInputSelectorClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_set_details_simple (element_class, + "Input selector", "Generic", + "N-to-1 input stream selectoring", + "Julien Moutte , " + "Jan Schmidt , " + "Wim Taymans "); + gst_element_class_add_static_pad_template (element_class, + &gst_input_selector_sink_factory); + gst_element_class_add_static_pad_template (element_class, + &gst_input_selector_src_factory); +} + +static void +gst_input_selector_class_init (GstInputSelectorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + /* FIXME: remove after confirming it is safe now */ + g_type_class_ref (gst_selector_pad_get_type ()); + + gobject_class->dispose = gst_input_selector_dispose; + + gobject_class->set_property = gst_input_selector_set_property; + gobject_class->get_property = gst_input_selector_get_property; + + g_object_class_install_property (gobject_class, PROP_N_PADS, + g_param_spec_uint ("n-pads", "Number of Pads", + "The number of sink pads", 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD, + g_param_spec_object ("active-pad", "Active pad", + "The currently active sink pad", GST_TYPE_PAD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SELECT_ALL, + g_param_spec_boolean ("select-all", "Select all mode", + "Forwards data from all input pads", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstInputSelector::block: + * @inputselector: the #GstInputSelector + * + * Block all sink pads in preparation for a switch. Returns the stop time of + * the current switch segment, as a running time, or 0 if there is no current + * active pad or the current active pad never received data. + */ + gst_input_selector_signals[SIGNAL_BLOCK] = + g_signal_new ("block", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstInputSelectorClass, block), NULL, NULL, + __gst_camerabin_marshal_INT64__VOID, G_TYPE_INT64, 0); + /** + * GstInputSelector::switch: + * @inputselector: the #GstInputSelector + * @pad: the pad to switch to + * @stop_time: running time at which to close the previous segment, or -1 + * to use the running time of the previously active sink pad + * @start_time: running time at which to start the new segment, or -1 to + * use the running time of the newly active sink pad + * + * Switch to a new feed. The segment opened by the previously active pad, if + * any, will be closed, and a new segment opened before data flows again. + * + * This signal must be emitted when the element has been blocked via the block signal. + * + * If you have a stream with only one switch element, such as an audio-only + * stream, a stream switch should be performed by first emitting the block + * signal, and then emitting the switch signal with -1 for the stop and start + * time values. + * + * The intention of the @stop_time and @start_time arguments is to allow + * multiple switch elements to switch and maintain stream synchronization. + * When switching a stream with multiple feeds, you will need as many switch + * elements as you have feeds. For example, a feed with audio and video will + * have one switch element between the audio feeds and one for video. + * + * A switch over multiple switch elements should be performed as follows: + * First, emit the block + * signal, collecting the returned values. The maximum running time returned + * by block should then be used as the time at which to close the previous + * segment. + * + * Then, query the running times of the new audio and video pads that you will + * switch to. Naturally, these pads are on separate switch elements. Take the + * minimum running time for those streams and use it for the time at which to + * open the new segment. + * + * If @pad is the same as the current active pad, the element will cancel any + * previous block without adjusting segments. + * + * + * the signal changed from accepting the pad name to the pad object. + * + * + * Since: 0.10.7 + */ + gst_input_selector_signals[SIGNAL_SWITCH] = + g_signal_new ("switch", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstInputSelectorClass, switch_), + NULL, NULL, __gst_camerabin_marshal_VOID__OBJECT_INT64_INT64, + G_TYPE_NONE, 3, GST_TYPE_PAD, G_TYPE_INT64, G_TYPE_INT64); + + gstelement_class->request_new_pad = gst_input_selector_request_new_pad; + gstelement_class->release_pad = gst_input_selector_release_pad; + gstelement_class->change_state = gst_input_selector_change_state; + + klass->block = GST_DEBUG_FUNCPTR (gst_input_selector_block); + /* note the underscore because switch is a keyword otherwise */ + klass->switch_ = GST_DEBUG_FUNCPTR (gst_input_selector_switch); +} + +static void +gst_input_selector_init (GstInputSelector * sel) +{ + sel->srcpad = gst_pad_new ("src", GST_PAD_SRC); + gst_pad_set_iterate_internal_links_function (sel->srcpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_iterate_linked_pads)); + gst_pad_set_getcaps_function (sel->srcpad, + GST_DEBUG_FUNCPTR (gst_input_selector_getcaps)); + gst_pad_set_query_function (sel->srcpad, + GST_DEBUG_FUNCPTR (gst_input_selector_query)); + gst_pad_set_event_function (sel->srcpad, + GST_DEBUG_FUNCPTR (gst_input_selector_event)); + gst_element_add_pad (GST_ELEMENT (sel), sel->srcpad); + /* sinkpad management */ + sel->active_sinkpad = NULL; + sel->padcount = 0; + gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED); + + sel->lock = g_mutex_new (); + sel->cond = g_cond_new (); + sel->blocked = FALSE; + + sel->select_all = FALSE; +} + +static void +gst_input_selector_dispose (GObject * object) +{ + GstInputSelector *sel = GST_INPUT_SELECTOR (object); + + if (sel->active_sinkpad) { + gst_object_unref (sel->active_sinkpad); + sel->active_sinkpad = NULL; + } + if (sel->lock) { + g_mutex_free (sel->lock); + sel->lock = NULL; + } + if (sel->cond) { + g_cond_free (sel->cond); + sel->cond = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +/* Solve the following equation for B.timestamp, and set that as the segment + * stop: + * B.running_time = (B.timestamp - NS.start) / NS.abs_rate + NS.accum + */ +static gint64 +gst_segment_get_timestamp (GstSegment * segment, gint64 running_time) +{ + if (running_time <= segment->accum) + return segment->start; + else + return (running_time - segment->accum) * segment->abs_rate + segment->start; +} + +static void +gst_segment_set_stop (GstSegment * segment, gint64 running_time) +{ + segment->stop = gst_segment_get_timestamp (segment, running_time); + segment->last_stop = -1; +} + +static void +gst_segment_set_start (GstSegment * segment, gint64 running_time) +{ + gint64 new_start, duration; + + new_start = gst_segment_get_timestamp (segment, running_time); + + /* this is the duration we skipped */ + duration = new_start - segment->start; + /* add the duration to the accumulated segment time */ + segment->accum += duration; + /* move position in the segment */ + segment->time += duration; + segment->start += duration; +} + +/* this function must be called with the SELECTOR_LOCK. It returns TRUE when the + * active pad changed. */ +static gboolean +gst_input_selector_set_active_pad (GstInputSelector * self, + GstPad * pad, gint64 stop_time, gint64 start_time) +{ + GstSelectorPad *old, *new; + GstPad **active_pad_p; + + if (pad == self->active_sinkpad) + return FALSE; + + old = GST_SELECTOR_PAD_CAST (self->active_sinkpad); + new = GST_SELECTOR_PAD_CAST (pad); + + GST_DEBUG_OBJECT (self, "setting active pad to %s:%s", + GST_DEBUG_PAD_NAME (new)); + + if (!GST_CLOCK_TIME_IS_VALID (stop_time) && old) { + /* no stop time given, get the latest running_time on the active pad to + * close and open the new segment */ + stop_time = start_time = gst_selector_pad_get_running_time (old); + GST_DEBUG_OBJECT (self, "using start/stop of %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + } + + if (old && old->active && !self->pending_close && stop_time >= 0) { + /* schedule a last_stop update if one isn't already scheduled, and a + segment has been pushed before. */ + memcpy (&self->segment, &old->segment, sizeof (self->segment)); + + GST_DEBUG_OBJECT (self, "setting stop_time to %" GST_TIME_FORMAT, + GST_TIME_ARGS (stop_time)); + gst_segment_set_stop (&self->segment, stop_time); + self->pending_close = TRUE; + } + + if (new && new->active && start_time >= 0) { + GST_DEBUG_OBJECT (self, "setting start_time to %" GST_TIME_FORMAT, + GST_TIME_ARGS (start_time)); + /* schedule a new segment push */ + gst_segment_set_start (&new->segment, start_time); + new->segment_pending = TRUE; + } + + active_pad_p = &self->active_sinkpad; + gst_object_replace ((GstObject **) active_pad_p, GST_OBJECT_CAST (pad)); + GST_DEBUG_OBJECT (self, "New active pad is %" GST_PTR_FORMAT, + self->active_sinkpad); + + return TRUE; +} + +static void +gst_input_selector_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstInputSelector *sel = GST_INPUT_SELECTOR (object); + + switch (prop_id) { + case PROP_ACTIVE_PAD: + { + GstPad *pad; + + pad = g_value_get_object (value); + + GST_INPUT_SELECTOR_LOCK (sel); + gst_input_selector_set_active_pad (sel, pad, + GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE); + GST_INPUT_SELECTOR_UNLOCK (sel); + break; + } + case PROP_SELECT_ALL: + GST_INPUT_SELECTOR_LOCK (object); + sel->select_all = g_value_get_boolean (value); + GST_INPUT_SELECTOR_UNLOCK (object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_input_selector_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstInputSelector *sel = GST_INPUT_SELECTOR (object); + + switch (prop_id) { + case PROP_N_PADS: + GST_INPUT_SELECTOR_LOCK (object); + g_value_set_uint (value, sel->n_pads); + GST_INPUT_SELECTOR_UNLOCK (object); + break; + case PROP_ACTIVE_PAD: + GST_INPUT_SELECTOR_LOCK (object); + g_value_set_object (value, sel->active_sinkpad); + GST_INPUT_SELECTOR_UNLOCK (object); + break; + case PROP_SELECT_ALL: + GST_INPUT_SELECTOR_LOCK (object); + g_value_set_boolean (value, sel->select_all); + GST_INPUT_SELECTOR_UNLOCK (object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstPad * +gst_input_selector_get_linked_pad (GstPad * pad, gboolean strict) +{ + GstInputSelector *sel; + GstPad *otherpad = NULL; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + + GST_INPUT_SELECTOR_LOCK (sel); + if (pad == sel->srcpad) + otherpad = sel->active_sinkpad; + else if (pad == sel->active_sinkpad || !strict) + otherpad = sel->srcpad; + if (otherpad) + gst_object_ref (otherpad); + GST_INPUT_SELECTOR_UNLOCK (sel); + + gst_object_unref (sel); + + return otherpad; +} + +static gboolean +gst_input_selector_event (GstPad * pad, GstEvent * event) +{ + gboolean res = FALSE; + GstPad *otherpad; + GstInputSelector *sel; + GstIterator *iter; + gboolean done; + gpointer nextpad; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + otherpad = gst_input_selector_get_linked_pad (pad, TRUE); + + if (otherpad) { + res = gst_pad_push_event (otherpad, event); + + gst_object_unref (otherpad); + } else if (sel->select_all) { + + /* If select-all is set, we should push the event to all pads. + * The result will be TRUE if the push works for any of the pads, even if a + * push fails. This is coherent with the way camerabin uses input-selector, + * but might not be for other uses of it. */ + + iter = gst_element_iterate_sink_pads (GST_ELEMENT (sel)); + + done = FALSE; + while (!done) { + switch (gst_iterator_next (iter, &nextpad)) { + case GST_ITERATOR_OK: + res |= gst_pad_push_event (nextpad, gst_event_ref (event)); + gst_object_unref (nextpad); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (sel, + "Wrong parameters when retrieving sink pads"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_event_unref (event); + gst_iterator_free (iter); + } else + gst_event_unref (event); + + gst_object_unref (sel); + + return res; +} + +/* query on the srcpad. We override this function because by default it will + * only forward the query to one random sinkpad */ +static gboolean +gst_input_selector_query (GstPad * pad, GstQuery * query) +{ + gboolean res = TRUE; + GstInputSelector *sel; + GstPad *otherpad; + + sel = GST_INPUT_SELECTOR (gst_pad_get_parent (pad)); + + otherpad = gst_input_selector_get_linked_pad (pad, TRUE); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + GList *walk; + GstClockTime resmin, resmax; + gboolean reslive; + + resmin = 0; + resmax = -1; + reslive = FALSE; + + /* assume FALSE, we become TRUE if one query succeeds */ + res = FALSE; + + /* perform the query on all sinkpads and combine the results. We take the + * max of min and the min of max for the result latency. */ + GST_INPUT_SELECTOR_LOCK (sel); + for (walk = GST_ELEMENT_CAST (sel)->sinkpads; walk; + walk = g_list_next (walk)) { + GstPad *sinkpad = GST_PAD_CAST (walk->data); + + if (gst_pad_peer_query (sinkpad, query)) { + GstClockTime min, max; + gboolean live; + + /* one query succeeded, we succeed too */ + res = TRUE; + + gst_query_parse_latency (query, &live, &min, &max); + + GST_DEBUG_OBJECT (sinkpad, + "peer latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT + ", live %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max), live); + + if (live) { + if (min > resmin) + resmin = min; + if (resmax == -1) + resmax = max; + else if (max < resmax) + resmax = max; + if (reslive == FALSE) + reslive = live; + } + } + } + GST_INPUT_SELECTOR_UNLOCK (sel); + if (res) { + gst_query_set_latency (query, reslive, resmin, resmax); + + GST_DEBUG_OBJECT (sel, + "total latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT + ", live %d", GST_TIME_ARGS (resmin), GST_TIME_ARGS (resmax), + reslive); + } + + break; + } + default: + if (otherpad) + res = gst_pad_peer_query (otherpad, query); + break; + } + if (otherpad) + gst_object_unref (otherpad); + gst_object_unref (sel); + + return res; +} + +static GstCaps * +gst_input_selector_getcaps (GstPad * pad) +{ + GstPad *otherpad; + GstObject *parent; + GstCaps *caps; + + parent = gst_object_get_parent (GST_OBJECT (pad)); + + otherpad = gst_input_selector_get_linked_pad (pad, FALSE); + + if (!otherpad) { + if (GST_INPUT_SELECTOR (parent)->select_all) { + GST_DEBUG_OBJECT (parent, + "Pad %s:%s not linked, returning merge of caps", + GST_DEBUG_PAD_NAME (pad)); + caps = gst_pad_proxy_getcaps (pad); + } else { + GST_DEBUG_OBJECT (parent, + "Pad %s:%s not linked, returning ANY", GST_DEBUG_PAD_NAME (pad)); + caps = gst_caps_new_any (); + } + } else { + GST_DEBUG_OBJECT (parent, + "Pad %s:%s is linked (to %s:%s), returning peer caps", + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (otherpad)); + /* if the peer has caps, use those. If the pad is not linked, this function + * returns NULL and we return ANY */ + if (!(caps = gst_pad_peer_get_caps_reffed (otherpad))) + caps = gst_caps_new_any (); + gst_object_unref (otherpad); + } + + gst_object_unref (parent); + return caps; +} + +/* check if the pad is the active sinkpad */ +static gboolean +gst_input_selector_is_active_sinkpad (GstInputSelector * sel, GstPad * pad) +{ + gboolean res; + + GST_INPUT_SELECTOR_LOCK (sel); + res = (pad == sel->active_sinkpad); + GST_INPUT_SELECTOR_UNLOCK (sel); + + return res; +} + +/* Get or create the active sinkpad, must be called with SELECTOR_LOCK */ +static GstPad * +gst_input_selector_activate_sinkpad (GstInputSelector * sel, GstPad * pad) +{ + GstPad *active_sinkpad; + GstSelectorPad *selpad; + + selpad = GST_SELECTOR_PAD_CAST (pad); + + selpad->active = TRUE; + active_sinkpad = sel->active_sinkpad; + if (active_sinkpad == NULL || sel->select_all) { + /* first pad we get activity on becomes the activated pad by default, if we + * select all, we also remember the last used pad. */ + if (sel->active_sinkpad) + gst_object_unref (sel->active_sinkpad); + active_sinkpad = sel->active_sinkpad = gst_object_ref (pad); + GST_DEBUG_OBJECT (sel, "Activating pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + } + + return active_sinkpad; +} + +static GstPad * +gst_input_selector_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * unused) +{ + GstInputSelector *sel; + gchar *name = NULL; + GstPad *sinkpad = NULL; + + g_return_val_if_fail (templ->direction == GST_PAD_SINK, NULL); + + sel = GST_INPUT_SELECTOR (element); + + GST_INPUT_SELECTOR_LOCK (sel); + + GST_LOG_OBJECT (sel, "Creating new pad %d", sel->padcount); + name = g_strdup_printf ("sink%d", sel->padcount++); + sinkpad = g_object_new (GST_TYPE_SELECTOR_PAD, + "name", name, "direction", templ->direction, "template", templ, NULL); + g_free (name); + + sel->n_pads++; + + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_event)); + gst_pad_set_getcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_getcaps)); + gst_pad_set_acceptcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_acceptcaps)); + gst_pad_set_chain_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_chain)); + gst_pad_set_iterate_internal_links_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_iterate_linked_pads)); + gst_pad_set_bufferalloc_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_selector_pad_bufferalloc)); + + gst_pad_set_active (sinkpad, TRUE); + gst_element_add_pad (GST_ELEMENT (sel), sinkpad); + GST_INPUT_SELECTOR_UNLOCK (sel); + + return sinkpad; +} + +static void +gst_input_selector_release_pad (GstElement * element, GstPad * pad) +{ + GstInputSelector *sel; + + sel = GST_INPUT_SELECTOR (element); + GST_LOG_OBJECT (sel, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + GST_INPUT_SELECTOR_LOCK (sel); + /* if the pad was the active pad, makes us select a new one */ + if (sel->active_sinkpad == pad) { + GST_DEBUG_OBJECT (sel, "Deactivating pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + gst_object_unref (sel->active_sinkpad); + sel->active_sinkpad = NULL; + } + sel->n_pads--; + + gst_pad_set_active (pad, FALSE); + gst_element_remove_pad (GST_ELEMENT (sel), pad); + GST_INPUT_SELECTOR_UNLOCK (sel); +} + +static void +gst_input_selector_reset (GstInputSelector * sel) +{ + GList *walk; + + GST_INPUT_SELECTOR_LOCK (sel); + /* clear active pad */ + if (sel->active_sinkpad) { + gst_object_unref (sel->active_sinkpad); + sel->active_sinkpad = NULL; + } + /* reset segment */ + gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED); + sel->pending_close = FALSE; + /* reset each of our sinkpads state */ + for (walk = GST_ELEMENT_CAST (sel)->sinkpads; walk; walk = g_list_next (walk)) { + GstSelectorPad *selpad = GST_SELECTOR_PAD_CAST (walk->data); + + gst_selector_pad_reset (selpad); + + if (selpad->tags) { + gst_tag_list_free (selpad->tags); + selpad->tags = NULL; + } + } + GST_INPUT_SELECTOR_UNLOCK (sel); +} + +static GstStateChangeReturn +gst_input_selector_change_state (GstElement * element, + GstStateChange transition) +{ + GstInputSelector *self = GST_INPUT_SELECTOR (element); + GstStateChangeReturn result; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_INPUT_SELECTOR_LOCK (self); + self->blocked = FALSE; + self->flushing = FALSE; + GST_INPUT_SELECTOR_UNLOCK (self); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* first unlock before we call the parent state change function, which + * tries to acquire the stream lock when going to ready. */ + GST_INPUT_SELECTOR_LOCK (self); + self->blocked = FALSE; + self->flushing = TRUE; + GST_INPUT_SELECTOR_BROADCAST (self); + GST_INPUT_SELECTOR_UNLOCK (self); + break; + default: + break; + } + + result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_input_selector_reset (self); + break; + default: + break; + } + + return result; +} + +static gint64 +gst_input_selector_block (GstInputSelector * self) +{ + gint64 ret = 0; + GstSelectorPad *spad; + + GST_INPUT_SELECTOR_LOCK (self); + + if (self->blocked) + GST_WARNING_OBJECT (self, "switch already blocked"); + + self->blocked = TRUE; + spad = GST_SELECTOR_PAD_CAST (self->active_sinkpad); + + if (spad) + ret = gst_selector_pad_get_running_time (spad); + else + GST_DEBUG_OBJECT (self, "no active pad while blocking"); + + GST_INPUT_SELECTOR_UNLOCK (self); + + return ret; +} + +/* stop_time and start_time are running times */ +static void +gst_input_selector_switch (GstInputSelector * self, GstPad * pad, + gint64 stop_time, gint64 start_time) +{ + gboolean changed; + + g_return_if_fail (self->blocked == TRUE); + + GST_INPUT_SELECTOR_LOCK (self); + changed = + gst_input_selector_set_active_pad (self, pad, stop_time, start_time); + + self->blocked = FALSE; + GST_INPUT_SELECTOR_BROADCAST (self); + GST_INPUT_SELECTOR_UNLOCK (self); + + if (changed) + g_object_notify (G_OBJECT (self), "active-pad"); +} + +static gboolean +gst_input_selector_check_eos (GstElement * selector) +{ + GstIterator *it = gst_element_iterate_sink_pads (selector); + GstIteratorResult ires; + gpointer item; + gboolean done = FALSE, is_eos = FALSE; + GstSelectorPad *pad; + + while (!done) { + ires = gst_iterator_next (it, &item); + switch (ires) { + case GST_ITERATOR_DONE: + GST_INFO_OBJECT (selector, "all sink pads have eos"); + done = TRUE; + is_eos = TRUE; + break; + case GST_ITERATOR_OK: + pad = GST_SELECTOR_PAD_CAST (item); + if (!pad->eos) { + done = TRUE; + } + gst_object_unref (pad); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + default: + done = TRUE; + break; + } + } + gst_iterator_free (it); + + return is_eos; +} Index: gst-plugins-good0.10/gst/camerabin/gstinputselector.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin/gstinputselector.h 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,84 @@ +/* GStreamer + * Copyright (C) 2003 Julien Moutte + * Copyright (C) 2005 Ronald S. Bultje + * Copyright (C) 2008 Nokia Corporation. (contact ) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_INPUT_SELECTOR_H__ +#define __GST_INPUT_SELECTOR_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_INPUT_SELECTOR \ + (gst_input_selector_get_type()) +#define GST_INPUT_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_INPUT_SELECTOR, GstInputSelector)) +#define GST_INPUT_SELECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_INPUT_SELECTOR, GstInputSelectorClass)) +#define GST_IS_INPUT_SELECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_INPUT_SELECTOR)) +#define GST_IS_INPUT_SELECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_INPUT_SELECTOR)) + +typedef struct _GstInputSelector GstInputSelector; +typedef struct _GstInputSelectorClass GstInputSelectorClass; + +#define GST_INPUT_SELECTOR_GET_LOCK(sel) (((GstInputSelector*)(sel))->lock) +#define GST_INPUT_SELECTOR_GET_COND(sel) (((GstInputSelector*)(sel))->cond) +#define GST_INPUT_SELECTOR_LOCK(sel) (g_mutex_lock (GST_INPUT_SELECTOR_GET_LOCK(sel))) +#define GST_INPUT_SELECTOR_UNLOCK(sel) (g_mutex_unlock (GST_INPUT_SELECTOR_GET_LOCK(sel))) +#define GST_INPUT_SELECTOR_WAIT(sel) (g_cond_wait (GST_INPUT_SELECTOR_GET_COND(sel), \ + GST_INPUT_SELECTOR_GET_LOCK(sel))) +#define GST_INPUT_SELECTOR_BROADCAST(sel) (g_cond_broadcast (GST_INPUT_SELECTOR_GET_COND(sel))) + +struct _GstInputSelector { + GstElement element; + + GstPad *srcpad; + + GstPad *active_sinkpad; + guint n_pads; + guint padcount; + + GstSegment segment; /* the output segment */ + gboolean pending_close; /* if we should push a close first */ + + GMutex *lock; + GCond *cond; + gboolean blocked; + gboolean flushing; + + /* select all mode, send data from all input pads forward */ + gboolean select_all; +}; + +struct _GstInputSelectorClass { + GstElementClass parent_class; + + gint64 (*block) (GstInputSelector *self); + void (*switch_) (GstInputSelector *self, GstPad *pad, + gint64 stop_time, gint64 start_time); +}; + +GType gst_input_selector_get_type (void); + +G_END_DECLS + +#endif /* __GST_INPUT_SELECTOR_H__ */ Index: gst-plugins-good0.10/gst/camerabin2/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/Makefile.am 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,43 @@ +plugin_LTLIBRARIES = libgstcamerabin2.la + +libgstcamerabin2_la_SOURCES = gstviewfinderbin.c \ + camerabingeneral.c \ + gstwrappercamerabinsrc.c \ + gstcamerabin2.c \ + gstplugin.c + +libgstcamerabin2_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + -DGST_USE_UNSTABLE_API + +libgstcamerabin2_la_LIBADD = \ + $(top_builddir)/gst-libs/gst/interfaces/libgstphotography-$(GST_MAJORMINOR).la \ + $(top_builddir)/gst-libs/gst/basecamerabinsrc/libgstbasecamerabinsrc-$(GST_MAJORMINOR).la \ + $(GST_PLUGINS_BASE_LIBS) -lgstinterfaces-$(GST_MAJORMINOR) -lgsttag-$(GST_MAJORMINOR) -lgstapp-$(GST_MAJORMINOR) -lgstpbutils-$(GST_MAJORMINOR) \ + $(GST_BASE_LIBS) $(GST_LIBS) + +libgstcamerabin2_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstcamerabin2_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstviewfinderbin.h \ + camerabingeneral.h \ + gstwrappercamerabinsrc.h \ + gstcamerabin2.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstcamerabin2 -:SHARED libgstcamerabin2 \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstcamerabin2_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstcamerabin2_la_CFLAGS) \ + -:LDFLAGS $(libgstcamerabin2_la_LDFLAGS) \ + $(libgstcamerabin2_la_LIBADD) \ + -ldl \ + -:LIBFILTER_STATIC gstphotography-@GST_MAJORMINOR@ \ + gstbasecamerabinsrc-@GST_MAJORMINOR@ \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ Index: gst-plugins-good0.10/gst/camerabin2/camerabingeneral.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/camerabingeneral.c 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,260 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:camerabingeneral + * @short_description: helper functions for #GstCameraBin2 and it's modules + * + * Common helper functions for #GstCameraBin2, #GstCameraBin2Image and + * #GstCameraBin2Video. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#include "camerabingeneral.h" + +/** + * gst_camerabin_add_element: + * @bin: add an element to this bin + * @new_elem: new element to be added + * + * Adds given element to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. Raises an error if adding + * or linking failed. Unrefs the element in the case of an error. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_add_element (GstBin * bin, GstElement * new_elem) +{ + return gst_camerabin_add_element_full (bin, NULL, new_elem, NULL); +} + +/** + * gst_camerabin_add_element_full: + * @bin: add an element to this bin + * @srcpad: src pad name, or NULL for any + * @new_elem: new element to be added + * @dstpad: dst pad name, or NULL for any + * + * Adds given element to given @bin. Looks for an unconnected src pad + * (with name @srcpad, if specified) from the @bin and links the element + * to it. Raises an error if adding or linking failed. Unrefs the element + * in the case of an error. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_add_element_full (GstBin * bin, const gchar * srcpad, + GstElement * new_elem, const gchar * dstpad) +{ + gboolean ret; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (new_elem, FALSE); + + ret = gst_camerabin_try_add_element (bin, srcpad, new_elem, dstpad); + + if (!ret) { + gchar *elem_name = gst_element_get_name (new_elem); + GST_ELEMENT_ERROR (bin, CORE, NEGOTIATION, (NULL), + ("linking %s failed", elem_name)); + g_free (elem_name); + gst_object_unref (new_elem); + } + + return ret; +} + +/** + * gst_camerabin_try_add_element: + * @bin: tries adding an element to this bin + * @srcpad: src pad name, or NULL for any + * @new_elem: new element to be added + * @dstpad: dst pad name, or NULL for any + * + * Adds given element to given @bin. Looks for an unconnected src pad + * (with name @srcpad, if specified) from the @bin and links the element to + * it. + * + * Returns: %TRUE if adding and linking succeeded, %FALSE otherwise. + */ +gboolean +gst_camerabin_try_add_element (GstBin * bin, const gchar * srcpad, + GstElement * new_elem, const gchar * dstpad) +{ + GstPad *bin_pad; + GstElement *bin_elem; + gboolean ret = TRUE; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (new_elem, FALSE); + + /* Get pads for linking */ + bin_pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC); + /* Add to bin */ + gst_bin_add (GST_BIN (bin), new_elem); + /* Link, if unconnected pad was found, otherwise just add it to bin */ + if (bin_pad) { + GST_DEBUG_OBJECT (bin, "linking %s to %s:%s", GST_OBJECT_NAME (new_elem), + GST_DEBUG_PAD_NAME (bin_pad)); + bin_elem = gst_pad_get_parent_element (bin_pad); + gst_object_unref (bin_pad); + if (!gst_element_link_pads_full (bin_elem, srcpad, new_elem, dstpad, + GST_PAD_LINK_CHECK_CAPS)) { + gst_object_ref (new_elem); + gst_bin_remove (bin, new_elem); + ret = FALSE; + } + gst_object_unref (bin_elem); + } else { + GST_INFO_OBJECT (bin, "no unlinked source pad in bin"); + } + + return ret; +} + +/** + * gst_camerabin_create_and_add_element: + * @bin: tries adding an element to this bin + * @elem_name: name of the element to be created + * @instance_name: name of the instance of the element to be created + * + * Creates an element according to given name and + * adds it to given @bin. Looks for an unconnected src pad + * from the @bin and links the element to it. + * + * Returns: pointer to the new element if successful, NULL otherwise. + */ +GstElement * +gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name, + const gchar * instance_name) +{ + GstElement *new_elem; + + g_return_val_if_fail (bin, FALSE); + g_return_val_if_fail (elem_name, FALSE); + + new_elem = gst_element_factory_make (elem_name, instance_name); + if (!new_elem) { + GST_ELEMENT_ERROR (bin, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + elem_name), (NULL)); + } else if (!gst_camerabin_add_element (bin, new_elem)) { + new_elem = NULL; + } + + return new_elem; +} + +/* try to change the state of an element. This function returns the element when + * the state change could be performed. When this function returns NULL an error + * occured and the element is unreffed if @unref is TRUE. */ +static GstElement * +try_element (GstElement * bin, GstElement * element, gboolean unref) +{ + GstStateChangeReturn ret; + + if (element) { + ret = gst_element_set_state (element, GST_STATE_READY); + if (ret == GST_STATE_CHANGE_FAILURE) { + GST_DEBUG_OBJECT (bin, "failed state change.."); + gst_element_set_state (element, GST_STATE_NULL); + if (unref) + gst_object_unref (element); + element = NULL; + } + } + return element; +} + +GstElement * +gst_camerabin_setup_default_element (GstBin * bin, GstElement * user_elem, + const gchar * auto_elem_name, const gchar * default_elem_name, + const gchar * instance_name) +{ + GstElement *elem; + + if (user_elem) { + GST_DEBUG_OBJECT (bin, "trying configured element"); + elem = try_element (GST_ELEMENT_CAST (bin), user_elem, FALSE); + } else { + /* only try fallback if no specific sink was chosen */ + GST_DEBUG_OBJECT (bin, "trying %s", auto_elem_name); + elem = gst_element_factory_make (auto_elem_name, instance_name); + elem = try_element (GST_ELEMENT_CAST (bin), elem, TRUE); + if (elem == NULL) { + /* if default sink from config.h is different then try it too */ + if (strcmp (default_elem_name, auto_elem_name)) { + GST_DEBUG_OBJECT (bin, "trying %s", default_elem_name); + elem = gst_element_factory_make (default_elem_name, instance_name); + elem = try_element (GST_ELEMENT_CAST (bin), elem, TRUE); + } + } + } + return elem; +} + +/** + * gst_camerabin_remove_elements_from_bin: + * @bin: removes all elements from this bin + * + * Removes all elements from this @bin. + */ +void +gst_camerabin_remove_elements_from_bin (GstBin * bin) +{ + GstIterator *iter = NULL; + gpointer data = NULL; + GstElement *elem = NULL; + gboolean done = FALSE; + + iter = gst_bin_iterate_elements (bin); + while (!done) { + switch (gst_iterator_next (iter, &data)) { + case GST_ITERATOR_OK: + elem = GST_ELEMENT (data); + gst_bin_remove (bin, elem); + gst_element_set_state (GST_ELEMENT (elem), GST_STATE_NULL); + /* Iterator increased the element refcount, so unref */ + gst_object_unref (elem); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + GST_WARNING_OBJECT (bin, "error in iterating elements"); + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (iter); +} Index: gst-plugins-good0.10/gst/camerabin2/camerabingeneral.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/camerabingeneral.h 2012-02-09 14:44:27.404710917 +0200 @@ -0,0 +1,37 @@ +/* + * GStreamer + * Copyright (C) 2008 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CAMERABIN_GENERAL_H_ +#define __CAMERABIN_GENERAL_H_ + +#include + +gboolean gst_camerabin_try_add_element (GstBin * bin, const gchar * srcpad, GstElement * new_elem, const gchar * dstpad); +gboolean gst_camerabin_add_element (GstBin * bin, GstElement * new_elem); +gboolean gst_camerabin_add_element_full (GstBin * bin, const gchar * srcpad, GstElement * new_elem, const gchar * dstpad); + +GstElement *gst_camerabin_create_and_add_element (GstBin * bin, const gchar * elem_name, const gchar * instance_name); + +GstElement * gst_camerabin_setup_default_element (GstBin * bin, GstElement *user_elem, const gchar *auto_elem_name, const gchar *default_elem_name, + const gchar * instance_elem_name); + +void gst_camerabin_remove_elements_from_bin (GstBin * bin); + +#endif /* #ifndef __CAMERABIN_GENERAL_H_ */ Index: gst-plugins-good0.10/gst/camerabin2/gstcamerabin2.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstcamerabin2.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,2430 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-camerabin2 + * + * CameraBin2 is a high-level camera object that encapsulates gstreamer + * elements, providing an API for controlling a digital camera. + * + * + * Note that camerabin2 is still UNSTABLE and under + * development. + * + * + * CameraBin2 has the following main features: + * + * + * Record videos + * + * + * Capture pictures + * + * + * Display a viewfinder + * + * + * Post preview images for each capture (video and image) + * + * + * + * + * Usage + * + * Camerabin2 can be created using gst_element_factory_make() just like + * any other element. Video or image capture mode can be selected using + * the #GstCameraBin2:mode property and the file to save the capture is + * selected using #GstCameraBin2:location property. + * + * After creating camerabin2, applications might want to do some + * customization (there's a section about this below), then select + * the desired mode and start capturing. + * + * In image capture mode, just send a #GstCameraBin:start-capture and a + * picture will be captured. When the picture is stored on the selected + * location, a %GST_MESSAGE_ELEMENT named 'image-done' will be posted on + * the #GstBus. + * + * In video capture mode, send a #GstCameraBin2:start-capture to start + * recording, then send a #GstCameraBin2:stop-capture to stop recording. + * Note that both signals are asynchronous, so, calling + * #GstCameraBin2:stop-capture doesn't guarantee that the video has been + * properly finished yet. Applications should wait for the 'video-done' + * message to be posted on the bus. + * + * In both modes, if #GstCameraBin2:post-previews is %TRUE, a #GstBuffer + * will be post to the #GstBus in a field named 'buffer', in a + * 'preview-image' message of type %GST_MESSAGE_ELEMENT. + * + * + + * + * Customization + * + * Camerabin2 provides various customization properties, allowing the user + * to set custom filters, selecting the viewfinder sink and formats to + * use to encode the captured images/videos. + * + * #GstEncodingProfiles are used to tell camerabin2 which formats it + * should encode the captures to, those should be set to + * #GstCameraBin2:image-profile and #GstCameraBin2:video-profile. Default is + * jpeg for images, and ogg (theora and vorbis) for video. If a profile without + * an audio stream is set for video, audio will be disabled on recordings. + * + * #GstCameraBin2:preview-caps can be used to select which format preview + * images should be posted on the #GstBus. It has to be a raw video format. + * + * Camerabin2 has a #GstCameraBin2:camera-source property so applications can + * set their source that will provide buffers for the viewfinder and for + * captures. This camera source is a special type of source that has 3 pads. + * To use a 'regular' source with a single pad you should use + * #GstWrapperCameraBinSource, it will adapt your source and provide 3 pads. + * + * Applications can also select the desired viewfinder sink using + * #GstCameraBin2:viewfinder-sink, it is also possible to select the audio + * source using #GstCameraBin2:audio-source. + * + * The viewfinder resolution can be configured using + * #GstCameraBin2:viewfinder-caps, these #GstCaps should be a subset of + * #GstCameraBin2:viewfinder-supported-caps. + * + * To select the desired resolution for captures, camerabin2 provides + * #GstCameraBin2:image-capture-caps and #GstCameraBin2:video-capture-caps, + * these caps must be a subset of what the source can produce. The allowed + * caps can be probed using #GstCameraBin2:image-capture-supported-caps and + * #GstCameraBin2:video-capture-supported-caps. In an analogous way, there + * are #GstCameraBin2:audio-capture-caps and + * #GstCameraBin2:audio-capture-supported-caps. + * + * Camerabin2 also allows applications to insert custom #GstElements on any + * of its branches: video capture, image capture, viewfinder and preview. + * Check #GstCameraBin2:video-filter, #GstCameraBin2:image-filter, + * #GstCameraBin2:viewfinder-filter and #GstCameraBin2:preview-filter. + * + * + * + * + * Example launch line + * + * Unfortunatelly, camerabin2 can't be really used from gst-launch, as you need + * to send signals to control it. The following pipeline might be able + * to show the viewfinder using all the default elements. + * |[ + * gst-launch -v -m camerabin2 + * ]| + * + * + + */ + +/* + * Detail Topics: + * + * videorecordingbin state management (for now on called 'videobin') + * - The problem: keeping videobin state in sync with camerabin will make it + * go to playing when it might not be used, causing its internal + * filesink to open a file that might be left blank. + * - The solution: videobin state is set to locked upon its creation and camerabin + * registers itself on the notify::ready-for-capture of the src. + * Whenever the src readyness goes to FALSE it means a new + * capture is starting. If we are on video mode, the videobin's + * state is set to NULL and then PLAYING (in between this we + * have room to set the destination filename). + * There is no problem to leave it on playing after an EOS, so + * no action is taken on stop-capture. + * + * - TODO: What happens when an error pops? + * - TODO: Should we split properties in image/video variants? We already do so + * for some of them + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include "gstcamerabin2.h" +#include +#include + +#if GLIB_CHECK_VERSION(2,29,6) +#define gst_camerabin2_atomic_int_add g_atomic_int_add +#else +#define gst_camerabin2_atomic_int_add g_atomic_int_exchange_and_add +#endif + +#define GST_CAMERA_BIN2_PROCESSING_INC(c) \ +{ \ + gint bef = gst_camerabin2_atomic_int_add (&c->processing_counter, 1); \ + if (bef == 0) \ + g_object_notify (G_OBJECT (c), "idle"); \ + GST_DEBUG_OBJECT ((c), "Processing counter incremented to: %d", \ + bef + 1); \ +} + +#define GST_CAMERA_BIN2_PROCESSING_DEC(c) \ +{ \ + if (g_atomic_int_dec_and_test (&c->processing_counter)) { \ + g_object_notify (G_OBJECT (c), "idle"); \ + GST_DEBUG_OBJECT ((c), "Camerabin now idle"); \ + } \ + GST_DEBUG_OBJECT ((c), "Processing counter decremented"); \ +} + +#define GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER(c) \ +{ \ + g_atomic_int_set (&c->processing_counter, 0); \ + GST_DEBUG_OBJECT ((c), "Processing counter reset"); \ +} + +GST_DEBUG_CATEGORY_STATIC (gst_camera_bin_debug); +#define GST_CAT_DEFAULT gst_camera_bin_debug + +/* prototypes */ + +enum +{ + PROP_0, + PROP_MODE, + PROP_LOCATION, + PROP_CAMERA_SRC, + PROP_IMAGE_CAPTURE_SUPPORTED_CAPS, + PROP_VIDEO_CAPTURE_SUPPORTED_CAPS, + PROP_IMAGE_CAPTURE_CAPS, + PROP_VIDEO_CAPTURE_CAPS, + PROP_POST_PREVIEWS, + PROP_PREVIEW_CAPS, + PROP_VIDEO_ENCODING_PROFILE, + PROP_IMAGE_FILTER, + PROP_VIDEO_FILTER, + PROP_VIEWFINDER_FILTER, + PROP_PREVIEW_FILTER, + PROP_VIEWFINDER_SINK, + PROP_VIEWFINDER_SUPPORTED_CAPS, + PROP_VIEWFINDER_CAPS, + PROP_AUDIO_SRC, + PROP_MUTE_AUDIO, + PROP_AUDIO_CAPTURE_SUPPORTED_CAPS, + PROP_AUDIO_CAPTURE_CAPS, + PROP_ZOOM, + PROP_MAX_ZOOM, + PROP_IMAGE_ENCODING_PROFILE, + PROP_IDLE, + PROP_FLAGS, + PROP_AUDIO_FILTER +}; + +enum +{ + /* action signals */ + START_CAPTURE_SIGNAL, + STOP_CAPTURE_SIGNAL, + /* emit signals */ + LAST_SIGNAL +}; +static guint camerabin_signals[LAST_SIGNAL]; + +#define DEFAULT_MODE MODE_IMAGE +#define DEFAULT_LOCATION "cap_%d" +#define DEFAULT_POST_PREVIEWS FALSE +#define DEFAULT_MUTE_AUDIO FALSE +#define DEFAULT_IDLE TRUE +#define DEFAULT_FLAGS 0 + +#define DEFAULT_AUDIO_SRC "autoaudiosrc" + +/******************************** + * Standard GObject boilerplate * + * and GObject types * + ********************************/ + +static GstPipelineClass *parent_class; +static void gst_camera_bin_class_init (GstCameraBin2Class * klass); +static void gst_camera_bin_base_init (gpointer klass); +static void gst_camera_bin_init (GstCameraBin2 * camera); +static void gst_camera_bin_dispose (GObject * object); +static void gst_camera_bin_finalize (GObject * object); + +static void gst_camera_bin_handle_message (GstBin * bin, GstMessage * message); +static gboolean gst_camera_bin_send_event (GstElement * element, + GstEvent * event); + +#define C_FLAGS(v) ((guint) v) +#define GST_TYPE_CAM_FLAGS (gst_cam_flags_get_type()) +static GType +gst_cam_flags_get_type (void) +{ + static const GFlagsValue values[] = { + {C_FLAGS (GST_CAM_FLAG_NO_AUDIO_CONVERSION), "Do not use audio conversion " + "elements", "no-audio-conversion"}, + {C_FLAGS (GST_CAM_FLAG_NO_VIDEO_CONVERSION), "Do not use video conversion " + "elements", "no-video-conversion"}, + {C_FLAGS (GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION), + "Do not use viewfinder conversion " "elements", + "no-viewfinder-conversion"}, + {C_FLAGS (GST_CAM_FLAG_NO_IMAGE_CONVERSION), "Do not use image conversion " + "elements", "no-image-conversion"}, + {0, NULL, NULL} + }; + static volatile GType id = 0; + + if (g_once_init_enter ((gsize *) & id)) { + GType _id; + + _id = g_flags_register_static ("GstCamFlags", values); + + g_once_init_leave ((gsize *) & id, _id); + } + + return id; +} + +GType +gst_camera_bin2_get_type (void) +{ + static GType gst_camera_bin_type = 0; + static const GInterfaceInfo camerabin_tagsetter_info = { + NULL, + NULL, + NULL, + }; + + if (!gst_camera_bin_type) { + static const GTypeInfo gst_camera_bin_info = { + sizeof (GstCameraBin2Class), + (GBaseInitFunc) gst_camera_bin_base_init, + NULL, + (GClassInitFunc) gst_camera_bin_class_init, + NULL, + NULL, + sizeof (GstCameraBin2), + 0, + (GInstanceInitFunc) gst_camera_bin_init, + NULL + }; + + gst_camera_bin_type = + g_type_register_static (GST_TYPE_PIPELINE, "GstCameraBin2", + &gst_camera_bin_info, 0); + + g_type_add_interface_static (gst_camera_bin_type, GST_TYPE_TAG_SETTER, + &camerabin_tagsetter_info); + } + + return gst_camera_bin_type; +} + +/* GObject class functions */ +static void gst_camera_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_camera_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +/* Element class functions */ +static GstStateChangeReturn +gst_camera_bin_change_state (GstElement * element, GstStateChange trans); + + +/* Camerabin functions */ + +static GstEvent * +gst_camera_bin_new_event_renegotiate (void) +{ + return gst_event_new_custom (GST_EVENT_CUSTOM_BOTH, + gst_structure_new ("renegotiate", NULL)); +} + +static GstEvent * +gst_camera_bin_new_event_file_location (const gchar * location) +{ + return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_new ("new-location", "location", G_TYPE_STRING, location, + NULL)); +} + +static void +gst_camera_bin_start_capture (GstCameraBin2 * camerabin) +{ + const GstTagList *taglist; + gint capture_index = camerabin->capture_index; + gchar *location = NULL; + GST_DEBUG_OBJECT (camerabin, "Received start-capture"); + + /* check that we have a valid location */ + if (camerabin->mode == MODE_VIDEO) { + if (camerabin->location == NULL) { + GST_ELEMENT_ERROR (camerabin, RESOURCE, OPEN_WRITE, + (_("File location is set to NULL, please set it to a valid filename")), (NULL)); + return; + } + + g_mutex_lock (camerabin->video_capture_mutex); + while (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING) { + g_cond_wait (camerabin->video_state_cond, camerabin->video_capture_mutex); + } + if (camerabin->video_state != GST_CAMERA_BIN_VIDEO_IDLE) { + GST_WARNING_OBJECT (camerabin, "Another video recording is ongoing" + " (state %d), cannot start a new one", camerabin->video_state); + g_mutex_unlock (camerabin->video_capture_mutex); + return; + } + camerabin->video_state = GST_CAMERA_BIN_VIDEO_STARTING; + } + + GST_CAMERA_BIN2_PROCESSING_INC (camerabin); + + if (camerabin->location) + location = g_strdup_printf (camerabin->location, capture_index); + + if (camerabin->mode == MODE_VIDEO) { + if (camerabin->audio_src) { + GstClock *clock = gst_pipeline_get_clock (GST_PIPELINE_CAST (camerabin)); + + gst_element_set_state (camerabin->audio_src, GST_STATE_PAUSED); + + gst_element_set_base_time (camerabin->audio_src, + gst_element_get_base_time (GST_ELEMENT_CAST (camerabin))); + if (clock) { + gst_element_set_clock (camerabin->audio_src, clock); + gst_object_unref (clock); + } + } + } else { + /* store the next capture buffer filename */ + g_mutex_lock (camerabin->image_capture_mutex); + camerabin->image_location_list = + g_slist_append (camerabin->image_location_list, g_strdup (location)); + g_mutex_unlock (camerabin->image_capture_mutex); + } + + if (camerabin->post_previews) { + /* Count processing of preview images too */ + GST_CAMERA_BIN2_PROCESSING_INC (camerabin); + /* store the next preview filename */ + g_mutex_lock (camerabin->preview_list_mutex); + camerabin->preview_location_list = + g_slist_append (camerabin->preview_location_list, location); + g_mutex_unlock (camerabin->preview_list_mutex); + } else { + g_free (location); + } + + g_signal_emit_by_name (camerabin->src, "start-capture", NULL); + if (camerabin->mode == MODE_VIDEO) { + camerabin->audio_send_newseg = TRUE; + if (camerabin->audio_src) + gst_element_set_state (camerabin->audio_src, GST_STATE_PLAYING); + + camerabin->video_state = GST_CAMERA_BIN_VIDEO_RECORDING; + g_mutex_unlock (camerabin->video_capture_mutex); + } + + /* + * We have to push tags after start capture because the video elements + * might be flushing from the previous capture and are reset only on the + * notify from ready for capture going to FALSE + */ + taglist = gst_tag_setter_get_tag_list (GST_TAG_SETTER (camerabin)); + GST_DEBUG_OBJECT (camerabin, "Have tags from application: %" + GST_PTR_FORMAT, taglist); + + if (camerabin->mode == MODE_IMAGE) { + /* Store image tags in a list and push them later, this prevents + start_capture() from blocking in pad_push_event call */ + g_mutex_lock (camerabin->image_capture_mutex); + camerabin->image_tags_list = + g_slist_append (camerabin->image_tags_list, + taglist ? gst_tag_list_copy (taglist) : NULL); + g_mutex_unlock (camerabin->image_capture_mutex); + } else if (taglist) { + GstPad *active_pad; + + active_pad = gst_element_get_static_pad (camerabin->src, + GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME); + gst_pad_push_event (active_pad, + gst_event_new_tag (gst_tag_list_copy (taglist))); + + gst_object_unref (active_pad); + } + + GST_DEBUG_OBJECT (camerabin, "Start-capture end"); +} + +static void +gst_camera_bin_stop_capture (GstCameraBin2 * camerabin) +{ + GST_DEBUG_OBJECT (camerabin, "Received stop-capture"); + if (camerabin->mode == MODE_VIDEO) { + g_mutex_lock (camerabin->video_capture_mutex); + if (camerabin->video_state == GST_CAMERA_BIN_VIDEO_RECORDING) { + if (camerabin->src) + g_signal_emit_by_name (camerabin->src, "stop-capture", NULL); + + camerabin->video_state = GST_CAMERA_BIN_VIDEO_FINISHING; + if (camerabin->audio_src) { + camerabin->audio_drop_eos = FALSE; + gst_element_send_event (camerabin->audio_src, gst_event_new_eos ()); + } + } + g_mutex_unlock (camerabin->video_capture_mutex); + } +} + +static void +gst_camera_bin_change_mode (GstCameraBin2 * camerabin, gint mode) +{ + if (mode == camerabin->mode) + return; + + GST_DEBUG_OBJECT (camerabin, "Changing mode to %d", mode); + + /* stop any ongoing capture */ + gst_camera_bin_stop_capture (camerabin); + camerabin->mode = mode; + if (camerabin->src) + g_object_set (camerabin->src, "mode", mode, NULL); +} + +static void +gst_camera_bin_src_notify_readyforcapture (GObject * obj, GParamSpec * pspec, + gpointer user_data) +{ + GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (user_data); + gboolean ready; + + g_object_get (camera->src, "ready-for-capture", &ready, NULL); + if (!ready) { + gchar *location = NULL; + + if (camera->mode == MODE_VIDEO) { + /* a video recording is about to start, change the filesink location */ + gst_element_set_state (camera->videosink, GST_STATE_NULL); + location = g_strdup_printf (camera->location, camera->capture_index); + GST_DEBUG_OBJECT (camera, "Switching videobin location to %s", location); + g_object_set (camera->videosink, "location", location, NULL); + g_free (location); + if (gst_element_set_state (camera->videosink, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + /* Resets the latest state change return, that would be a failure + * and could cause problems in a camerabin2 state change */ + gst_element_set_state (camera->videosink, GST_STATE_NULL); + } + } + + camera->capture_index++; + } +} + +static void +gst_camera_bin_dispose (GObject * object) +{ + GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (object); + + g_free (camerabin->location); + g_mutex_free (camerabin->preview_list_mutex); + g_mutex_free (camerabin->image_capture_mutex); + g_mutex_free (camerabin->video_capture_mutex); + g_cond_free (camerabin->video_state_cond); + + if (camerabin->src_capture_notify_id) + g_signal_handler_disconnect (camerabin->src, + camerabin->src_capture_notify_id); + if (camerabin->src) + gst_object_unref (camerabin->src); + if (camerabin->user_src) + gst_object_unref (camerabin->user_src); + + if (camerabin->audio_src) + gst_object_unref (camerabin->audio_src); + if (camerabin->user_audio_src) + gst_object_unref (camerabin->user_audio_src); + + if (camerabin->audio_capsfilter) + gst_object_unref (camerabin->audio_capsfilter); + if (camerabin->audio_volume) + gst_object_unref (camerabin->audio_volume); + + if (camerabin->viewfinderbin) + gst_object_unref (camerabin->viewfinderbin); + if (camerabin->viewfinderbin_queue) + gst_object_unref (camerabin->viewfinderbin_queue); + if (camerabin->viewfinderbin_capsfilter) + gst_object_unref (camerabin->viewfinderbin_capsfilter); + + if (camerabin->video_encodebin_signal_id) + g_signal_handler_disconnect (camerabin->video_encodebin, + camerabin->video_encodebin_signal_id); + + if (camerabin->videosink) + gst_object_unref (camerabin->videosink); + if (camerabin->video_encodebin) + gst_object_unref (camerabin->video_encodebin); + if (camerabin->videobin_capsfilter) + gst_object_unref (camerabin->videobin_capsfilter); + + if (camerabin->image_encodebin_signal_id) + g_signal_handler_disconnect (camerabin->image_encodebin, + camerabin->image_encodebin_signal_id); + if (camerabin->imagesink) + gst_object_unref (camerabin->imagesink); + if (camerabin->image_encodebin) + gst_object_unref (camerabin->image_encodebin); + if (camerabin->imagebin_capsfilter) + gst_object_unref (camerabin->imagebin_capsfilter); + + if (camerabin->video_filter) + gst_object_unref (camerabin->video_filter); + if (camerabin->image_filter) + gst_object_unref (camerabin->image_filter); + if (camerabin->viewfinder_filter) + gst_object_unref (camerabin->viewfinder_filter); + if (camerabin->audio_filter) + gst_object_unref (camerabin->audio_filter); + + if (camerabin->user_video_filter) + gst_object_unref (camerabin->user_video_filter); + if (camerabin->user_audio_filter) + gst_object_unref (camerabin->user_audio_filter); + if (camerabin->user_image_filter) + gst_object_unref (camerabin->user_image_filter); + if (camerabin->user_viewfinder_filter) + gst_object_unref (camerabin->user_viewfinder_filter); + + if (camerabin->video_profile) + gst_encoding_profile_unref (camerabin->video_profile); + if (camerabin->image_profile) + gst_encoding_profile_unref (camerabin->image_profile); + + if (camerabin->preview_caps) + gst_caps_replace (&camerabin->preview_caps, NULL); + if (camerabin->preview_filter) { + gst_object_unref (camerabin->preview_filter); + camerabin->preview_filter = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_camera_bin_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_camera_bin_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details_simple (element_class, "CameraBin2", + "Generic/Bin/Camera", "CameraBin2", + "Thiago Santos "); +} + +static void +gst_camera_bin_class_init (GstCameraBin2Class * klass) +{ + GObjectClass *object_class; + GstElementClass *element_class; + GstBinClass *bin_class; + + parent_class = g_type_class_peek_parent (klass); + object_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + bin_class = GST_BIN_CLASS (klass); + + object_class->dispose = gst_camera_bin_dispose; + object_class->finalize = gst_camera_bin_finalize; + object_class->set_property = gst_camera_bin_set_property; + object_class->get_property = gst_camera_bin_get_property; + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_camera_bin_change_state); + element_class->send_event = GST_DEBUG_FUNCPTR (gst_camera_bin_send_event); + + bin_class->handle_message = gst_camera_bin_handle_message; + + klass->start_capture = gst_camera_bin_start_capture; + klass->stop_capture = gst_camera_bin_stop_capture; + + /** + * GstCameraBin2:mode: + * + * Set the mode of operation: still image capturing or video recording. + */ + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_enum ("mode", "Mode", + "The capture mode (still image capture or video recording)", + GST_TYPE_CAMERABIN_MODE, DEFAULT_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_LOCATION, + g_param_spec_string ("location", "Location", + "Location to save the captured files. A %d might be used on the" + "filename as a placeholder for a numeric index of the capture." + "Default is cap_%d", + DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_CAMERA_SRC, + g_param_spec_object ("camera-source", "Camera source", + "The camera source element to be used. It is only taken into use on" + " the next null to ready transition", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_AUDIO_SRC, + g_param_spec_object ("audio-source", "Audio source", + "The audio source element to be used on video recordings. It is only" + " taken into use on the next null to ready transition", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_MUTE_AUDIO, + g_param_spec_boolean ("mute", "Mute", + "If the audio recording should be muted. Note that this still " + "saves audio data to the resulting file, but they are silent. Use " + "a video-profile without audio to disable audio completely", + DEFAULT_MUTE_AUDIO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_AUDIO_CAPTURE_SUPPORTED_CAPS, + g_param_spec_boxed ("audio-capture-supported-caps", + "Audio capture supported caps", + "Formats supported for capturing audio represented as GstCaps", + GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_AUDIO_CAPTURE_CAPS, + g_param_spec_boxed ("audio-capture-caps", + "Audio capture caps", + "Format to capture audio for video recording represented as GstCaps", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IMAGE_CAPTURE_SUPPORTED_CAPS, + g_param_spec_boxed ("image-capture-supported-caps", + "Image capture supported caps", + "Formats supported for capturing images represented as GstCaps", + GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_VIDEO_CAPTURE_SUPPORTED_CAPS, + g_param_spec_boxed ("video-capture-supported-caps", + "Video capture supported caps", + "Formats supported for capturing videos represented as GstCaps", + GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IMAGE_CAPTURE_CAPS, + g_param_spec_boxed ("image-capture-caps", + "Image capture caps", + "Caps for image capture", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_VIDEO_CAPTURE_CAPS, + g_param_spec_boxed ("video-capture-caps", + "Video capture caps", + "Caps for video capture", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_POST_PREVIEWS, + g_param_spec_boolean ("post-previews", "Post Previews", + "If capture preview images should be posted to the bus", + DEFAULT_POST_PREVIEWS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_PREVIEW_CAPS, + g_param_spec_boxed ("preview-caps", "Preview caps", + "The caps of the preview image to be posted", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_VIDEO_ENCODING_PROFILE, + gst_param_spec_mini_object ("video-profile", "Video Profile", + "The GstEncodingProfile to use for video recording. Audio is enabled " + "when this profile supports audio.", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_IMAGE_FILTER, + g_param_spec_object ("image-filter", "Image filter", + "The element that will process captured image frames. (Should be" + " set on NULL state)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_VIDEO_FILTER, + g_param_spec_object ("video-filter", "Video filter", + "The element that will process captured video frames. (Should be" + " set on NULL state)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_VIEWFINDER_FILTER, + g_param_spec_object ("viewfinder-filter", "Viewfinder filter", + "The element that will process frames going to the viewfinder." + " (Should be set on NULL state)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_AUDIO_FILTER, + g_param_spec_object ("audio-filter", "Audio filter", + "The element that will process captured audio buffers when recording" + ". (Should be set on NULL state)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_PREVIEW_FILTER, + g_param_spec_object ("preview-filter", "Preview filter", + "The element that will process preview buffers." + " (Should be set on NULL state)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_VIEWFINDER_SINK, + g_param_spec_object ("viewfinder-sink", "Viewfinder sink", + "The video sink of the viewfinder. It is only taken into use" + " on the next null to ready transition", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_VIEWFINDER_CAPS, + g_param_spec_boxed ("viewfinder-caps", + "Viewfinder caps", + "Restricts the caps that can be used on the viewfinder", + GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_ZOOM, + g_param_spec_float ("zoom", "Zoom", + "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, MAX_ZOOM, + DEFAULT_ZOOM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", "Maximum zoom level (note: may change " + "depending on resolution/implementation)", + "Digital zoom factor (e.g. 1.5 means 1.5x)", MIN_ZOOM, G_MAXFLOAT, + MAX_ZOOM, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* TODO + * Review before stable + * - One problem with using encodebin for images here is how jifmux + * autoplugging works. We need to give it a higher rank and fix its + * caps (it has image/jpeg on sink and src pads). Preliminary tests + * show that jifmux is picked if image/jpeg is the caps of a container + * profile. So this could work. + * - There seems to be a problem with encodebin for images currently as + * it autoplugs a videorate that only starts outputing buffers after + * getting the 2nd buffer. + */ + g_object_class_install_property (object_class, PROP_IMAGE_ENCODING_PROFILE, + gst_param_spec_mini_object ("image-profile", "Image Profile", + "The GstEncodingProfile to use for image captures.", + GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + + g_object_class_install_property (object_class, PROP_IDLE, + g_param_spec_boolean ("idle", "Idle", + "If camerabin2 is idle (not doing captures).", DEFAULT_IDLE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /* TODO review before going stable + * We have viewfinder-supported-caps that returns the caps that the + * camerasrc can produce on its viewfinder pad, this could easily be + * confused with what the viewfinder-sink accepts. + * + * Do we want to add a 'viewfinder-sink-supported-caps' or maybe change + * the name of this property? + */ + g_object_class_install_property (object_class, + PROP_VIEWFINDER_SUPPORTED_CAPS, + g_param_spec_boxed ("viewfinder-supported-caps", + "Camera source Viewfinder pad supported caps", + "The caps that the camera source can produce on the viewfinder pad", + GST_TYPE_CAPS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin:flags + * + * Control the behaviour of encodebin. + */ + g_object_class_install_property (object_class, PROP_FLAGS, + g_param_spec_flags ("flags", "Flags", "Flags to control behaviour", + GST_TYPE_CAM_FLAGS, DEFAULT_FLAGS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstCameraBin2::capture-start: + * @camera: the camera bin element + * + * Starts image capture or video recording depending on the Mode. + */ + camerabin_signals[START_CAPTURE_SIGNAL] = + g_signal_new ("start-capture", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBin2Class, start_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GstCameraBin2::capture-stop: + * @camera: the camera bin element + */ + camerabin_signals[STOP_CAPTURE_SIGNAL] = + g_signal_new ("stop-capture", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstCameraBin2Class, stop_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static void +gst_camera_bin_init (GstCameraBin2 * camera) +{ + camera->post_previews = DEFAULT_POST_PREVIEWS; + camera->mode = DEFAULT_MODE; + camera->location = g_strdup (DEFAULT_LOCATION); + camera->viewfinderbin = gst_element_factory_make ("viewfinderbin", "vf-bin"); + camera->zoom = DEFAULT_ZOOM; + camera->max_zoom = MAX_ZOOM; + camera->flags = DEFAULT_FLAGS; + camera->preview_list_mutex = g_mutex_new (); + camera->image_capture_mutex = g_mutex_new (); + camera->video_capture_mutex = g_mutex_new (); + camera->video_state_cond = g_cond_new (); + + /* capsfilters are created here as we proxy their caps properties and + * this way we avoid having to store the caps while on NULL state to + * set them later */ + camera->videobin_capsfilter = gst_element_factory_make ("capsfilter", + "videobin-capsfilter"); + camera->imagebin_capsfilter = gst_element_factory_make ("capsfilter", + "imagebin-capsfilter"); + camera->viewfinderbin_capsfilter = gst_element_factory_make ("capsfilter", + "viewfinderbin-capsfilter"); + + gst_bin_add_many (GST_BIN (camera), + gst_object_ref (camera->viewfinderbin), + gst_object_ref (camera->videobin_capsfilter), + gst_object_ref (camera->imagebin_capsfilter), + gst_object_ref (camera->viewfinderbin_capsfilter), NULL); + + /* these elements are only added if they are going to be used */ + camera->audio_capsfilter = gst_element_factory_make ("capsfilter", + "audio-capsfilter"); + camera->audio_volume = gst_element_factory_make ("volume", "audio-volume"); +} + +static void +gst_image_capture_bin_post_image_done (GstCameraBin2 * camera, + const gchar * filename) +{ + GstMessage *msg; + + g_return_if_fail (filename != NULL); + + msg = gst_message_new_element (GST_OBJECT_CAST (camera), + gst_structure_new ("image-done", "filename", G_TYPE_STRING, + filename, NULL)); + + if (!gst_element_post_message (GST_ELEMENT_CAST (camera), msg)) + GST_WARNING_OBJECT (camera, "Failed to post image-done message"); +} + +static void +gst_video_capture_bin_post_video_done (GstCameraBin2 * camera) +{ + GstMessage *msg; + + msg = gst_message_new_element (GST_OBJECT_CAST (camera), + gst_structure_new ("video-done", NULL)); + + if (!gst_element_post_message (GST_ELEMENT_CAST (camera), msg)) + GST_WARNING_OBJECT (camera, "Failed to post video-done message"); +} + +static void +gst_camera_bin_skip_next_preview (GstCameraBin2 * camerabin) +{ + gchar *location; + + g_mutex_lock (camerabin->preview_list_mutex); + if (camerabin->preview_location_list) { + location = camerabin->preview_location_list->data; + GST_DEBUG_OBJECT (camerabin, "Skipping preview for %s", location); + g_free (location); + camerabin->preview_location_list = + g_slist_delete_link (camerabin->preview_location_list, + camerabin->preview_location_list); + GST_CAMERA_BIN2_PROCESSING_DEC (camerabin); + } else { + GST_WARNING_OBJECT (camerabin, "No previews to skip"); + } + g_mutex_unlock (camerabin->preview_list_mutex); +} + +static gpointer +gst_camera_bin_video_reset_elements (gpointer u_data) +{ + GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (u_data); + + GST_DEBUG_OBJECT (camerabin, "Resetting video elements state"); + g_mutex_lock (camerabin->video_capture_mutex); + + /* reset element states to clear eos/flushing pads */ + gst_element_set_state (camerabin->video_encodebin, GST_STATE_READY); + gst_element_set_state (camerabin->videobin_capsfilter, GST_STATE_READY); + if (camerabin->video_filter) { + gst_element_set_state (camerabin->video_filter, GST_STATE_READY); + gst_element_sync_state_with_parent (camerabin->video_filter); + } + gst_element_sync_state_with_parent (camerabin->videobin_capsfilter); + gst_element_sync_state_with_parent (camerabin->video_encodebin); + + if (camerabin->audio_src) { + gst_element_set_state (camerabin->audio_capsfilter, GST_STATE_READY); + gst_element_set_state (camerabin->audio_volume, GST_STATE_READY); + + /* FIXME We need to set audiosrc to null to make it resync the ringbuffer + * while bug https://bugzilla.gnome.org/show_bug.cgi?id=648359 isn't + * fixed. + * + * Also, we don't reinit the audiosrc to keep audio devices from being open + * and running until we really need them */ + gst_element_set_state (camerabin->audio_src, GST_STATE_NULL); + + if (camerabin->audio_filter) { + gst_element_set_state (camerabin->audio_filter, GST_STATE_READY); + gst_element_sync_state_with_parent (camerabin->audio_filter); + } + + gst_element_sync_state_with_parent (camerabin->audio_capsfilter); + gst_element_sync_state_with_parent (camerabin->audio_volume); + + } + + GST_DEBUG_OBJECT (camerabin, "Setting video state to idle"); + camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE; + g_cond_signal (camerabin->video_state_cond); + g_mutex_unlock (camerabin->video_capture_mutex); + + gst_object_unref (camerabin); + return NULL; +} + +static void +gst_camera_bin_handle_message (GstBin * bin, GstMessage * message) +{ + GstCameraBin2 *camerabin = GST_CAMERA_BIN2_CAST (bin); + gboolean dec_counter = FALSE; + + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ELEMENT:{ + const GstStructure *structure = gst_message_get_structure (message); + const gchar *filename; + + if (gst_structure_has_name (structure, "GstMultiFileSink")) { + filename = gst_structure_get_string (structure, "filename"); + GST_DEBUG_OBJECT (bin, "Got file save message from multifilesink, " + "image %s has been saved", filename); + if (filename) { + gst_image_capture_bin_post_image_done (GST_CAMERA_BIN2_CAST (bin), + filename); + } + dec_counter = TRUE; + } else if (gst_structure_has_name (structure, "preview-image")) { + gchar *location = NULL; + + g_mutex_lock (camerabin->preview_list_mutex); + if (camerabin->preview_location_list) { + location = camerabin->preview_location_list->data; + camerabin->preview_location_list = + g_slist_delete_link (camerabin->preview_location_list, + camerabin->preview_location_list); + GST_DEBUG_OBJECT (camerabin, "Adding preview location to preview " + "message '%s'", location); + } else { + GST_WARNING_OBJECT (camerabin, "Unexpected preview message received, " + "won't be able to put location field into the message. This can " + "happen if the source is posting previews while camerabin2 is " + "shutting down"); + } + g_mutex_unlock (camerabin->preview_list_mutex); + + if (location) { + GValue value = { 0 }; + g_value_init (&value, G_TYPE_STRING); + g_value_take_string (&value, location); + gst_structure_take_value ((GstStructure *) structure, "location", + &value); + } + + GST_LOG_OBJECT (bin, "received preview-image message"); + dec_counter = TRUE; + } + } + break; + case GST_MESSAGE_WARNING:{ + GError *err = NULL; + gchar *debug = NULL; + + gst_message_parse_warning (message, &err, &debug); + if (err->domain == GST_RESOURCE_ERROR) { + /* some capturing failed */ + GST_WARNING_OBJECT (bin, "Capture failed, reason: %s - %s", + err->message, debug); + if (camerabin->post_previews) { + gst_camera_bin_skip_next_preview (camerabin); + } + dec_counter = TRUE; + } + g_error_free (err); + g_free (debug); + } + break; + case GST_MESSAGE_EOS:{ + GstElement *src = GST_ELEMENT (GST_MESSAGE_SRC (message)); + if (src == GST_CAMERA_BIN2_CAST (bin)->videosink) { + + g_mutex_lock (camerabin->video_capture_mutex); + GST_DEBUG_OBJECT (bin, "EOS from video branch"); + g_assert (camerabin->video_state == GST_CAMERA_BIN_VIDEO_FINISHING); + + gst_video_capture_bin_post_video_done (GST_CAMERA_BIN2_CAST (bin)); + dec_counter = TRUE; + + if (!g_thread_create (gst_camera_bin_video_reset_elements, + gst_object_ref (camerabin), FALSE, NULL)) { + GST_WARNING_OBJECT (camerabin, "Failed to create thread to " + "reset video elements' state, video recordings may not work " + "anymore"); + gst_object_unref (camerabin); + camerabin->video_state = GST_CAMERA_BIN_VIDEO_IDLE; + } + + g_mutex_unlock (camerabin->video_capture_mutex); + } + } + break; + default: + break; + } + if (message) + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + + if (dec_counter) + GST_CAMERA_BIN2_PROCESSING_DEC (camerabin); +} + +/* + * Transforms: + * ... ! previous_element [ ! current_filter ] ! next_element ! ... + * + * into: + * ... ! previous_element [ ! new_filter ] ! next_element ! ... + * + * Where current_filter and new_filter might or might not be NULL + */ +static void +gst_camera_bin_check_and_replace_filter (GstCameraBin2 * camera, + GstElement ** current_filter, GstElement * new_filter, + GstElement * previous_element, GstElement * next_element, + const gchar * prev_elem_pad) +{ + if (*current_filter == new_filter) { + GST_DEBUG_OBJECT (camera, "Current filter is the same as the previous, " + "no switch needed."); + return; + } + + GST_DEBUG_OBJECT (camera, "Replacing current filter (%s) with new filter " + "(%s)", *current_filter ? GST_ELEMENT_NAME (*current_filter) : "null", + new_filter ? GST_ELEMENT_NAME (new_filter) : "null"); + + if (*current_filter) { + gst_bin_remove (GST_BIN_CAST (camera), *current_filter); + gst_object_unref (*current_filter); + *current_filter = NULL; + } else { + /* unlink the pads */ + gst_element_unlink (previous_element, next_element); + } + + if (new_filter) { + *current_filter = gst_object_ref (new_filter); + gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (new_filter)); + } + + if (prev_elem_pad) { + if (new_filter) { + gst_element_link_pads (previous_element, prev_elem_pad, new_filter, NULL); + gst_element_link (new_filter, next_element); + } else { + gst_element_link_pads (previous_element, prev_elem_pad, next_element, + NULL); + } + } else { + if (new_filter) + gst_element_link_many (previous_element, new_filter, next_element, NULL); + else + gst_element_link (previous_element, next_element); + } +} + +static void +encodebin_element_added (GstElement * encodebin, GstElement * new_element, + GstCameraBin2 * camera) +{ + GstElementFactory *factory = gst_element_get_factory (new_element); + + if (factory != NULL) { + if (strcmp (GST_PLUGIN_FEATURE_NAME (factory), "audiorate") == 0 || + strcmp (GST_PLUGIN_FEATURE_NAME (factory), "videorate") == 0) { + g_object_set (new_element, "skip-to-first", TRUE, NULL); + } + } + + if (gst_element_implements_interface (new_element, GST_TYPE_TAG_SETTER)) { + GstTagSetter *tagsetter = GST_TAG_SETTER (new_element); + + gst_tag_setter_set_tag_merge_mode (tagsetter, GST_TAG_MERGE_REPLACE); + } +} + +#define VIDEO_PAD 1 +#define AUDIO_PAD 2 +static GstPad * +encodebin_find_pad (GstCameraBin2 * camera, GstElement * encodebin, + gint pad_type) +{ + GstPad *pad = NULL; + GstIterator *iter; + gboolean done; + + GST_DEBUG_OBJECT (camera, "Looking at encodebin pads, searching for %s pad", + pad_type == VIDEO_PAD ? "video" : "audio"); + + iter = gst_element_iterate_sink_pads (encodebin); + done = FALSE; + while (!done) { + switch (gst_iterator_next (iter, (gpointer *) & pad)) { + case GST_ITERATOR_OK: + if (pad_type == VIDEO_PAD) { + if (strstr (GST_PAD_NAME (pad), "video") != NULL) { + GST_DEBUG_OBJECT (camera, "Found video pad %s", GST_PAD_NAME (pad)); + done = TRUE; + break; + } + } else if (pad_type == AUDIO_PAD) { + if (strstr (GST_PAD_NAME (pad), "audio") != NULL) { + GST_DEBUG_OBJECT (camera, "Found audio pad %s", GST_PAD_NAME (pad)); + done = TRUE; + break; + } + } + gst_object_unref (pad); + pad = NULL; + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (iter); + break; + case GST_ITERATOR_ERROR: + pad = NULL; + done = TRUE; + break; + case GST_ITERATOR_DONE: + pad = NULL; + done = TRUE; + break; + } + } + gst_iterator_free (iter); + + /* no static pad, try requesting one */ + if (pad == NULL) { + GstElementClass *klass; + GstPadTemplate *tmpl; + + GST_DEBUG_OBJECT (camera, "No pads found, trying to request one"); + + klass = GST_ELEMENT_GET_CLASS (encodebin); + tmpl = gst_element_class_get_pad_template (klass, pad_type == VIDEO_PAD ? + "video_%d" : "audio_%d"); + + pad = gst_element_request_pad (encodebin, tmpl, NULL, NULL); + GST_DEBUG_OBJECT (camera, "Got pad: %s", pad ? GST_PAD_NAME (pad) : "null"); + } + + return pad; +} + +static gboolean +gst_camera_bin_video_profile_has_audio (GstCameraBin2 * camera) +{ + const GList *list; + + g_return_val_if_fail (camera->video_profile != NULL, FALSE); + + if (GST_IS_ENCODING_VIDEO_PROFILE (camera->video_profile)) + return FALSE; + + for (list = + gst_encoding_container_profile_get_profiles ((GstEncodingContainerProfile + *) camera->video_profile); list; list = g_list_next (list)) { + GstEncodingProfile *profile = (GstEncodingProfile *) list->data; + + if (GST_IS_ENCODING_AUDIO_PROFILE (profile)) + return TRUE; + } + + return FALSE; +} + +static GstPadLinkReturn +gst_camera_bin_link_encodebin (GstCameraBin2 * camera, GstElement * encodebin, + GstElement * element, gint padtype) +{ + GstPadLinkReturn ret; + GstPad *srcpad; + GstPad *sinkpad = NULL; + + srcpad = gst_element_get_static_pad (element, "src"); + g_assert (srcpad != NULL); + + sinkpad = encodebin_find_pad (camera, encodebin, padtype); + + /* there may be no available sink pad for encodebin in some situations: + * e.g. missing elements or incompatible padtype */ + if (sinkpad == NULL) { + gst_object_unref (srcpad); + return GST_PAD_LINK_REFUSED; + } + + ret = gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_CAPS); + gst_object_unref (sinkpad); + gst_object_unref (srcpad); + + return ret; +} + +static void +gst_camera_bin_src_notify_max_zoom_cb (GObject * self, GParamSpec * pspec, + gpointer user_data) +{ + GstCameraBin2 *camera = (GstCameraBin2 *) user_data; + + g_object_get (self, "max-zoom", &camera->max_zoom, NULL); + GST_DEBUG_OBJECT (camera, "Max zoom updated to %f", camera->max_zoom); + g_object_notify (G_OBJECT (camera), "max-zoom"); +} + +static void +gst_camera_bin_src_notify_zoom_cb (GObject * self, GParamSpec * pspec, + gpointer user_data) +{ + GstCameraBin2 *camera = (GstCameraBin2 *) user_data; + + g_object_get (self, "zoom", &camera->zoom, NULL); + GST_DEBUG_OBJECT (camera, "Zoom updated to %f", camera->zoom); + g_object_notify (G_OBJECT (camera), "zoom"); +} + +static gboolean +gst_camera_bin_image_src_buffer_probe (GstPad * pad, GstBuffer * buf, + gpointer data) +{ + gboolean ret = TRUE; + GstCameraBin2 *camerabin = data; + GstEvent *evt; + gchar *location = NULL; + GstPad *peer; + GstTagList *tags; + + g_mutex_lock (camerabin->image_capture_mutex); + + /* Push pending image tags */ + if (camerabin->image_tags_list) { + tags = camerabin->image_tags_list->data; + camerabin->image_tags_list = + g_slist_delete_link (camerabin->image_tags_list, + camerabin->image_tags_list); + GST_DEBUG_OBJECT (camerabin, "Pushing tags from application: %" + GST_PTR_FORMAT, tags); + if (tags) { + peer = gst_pad_get_peer (pad); + gst_pad_send_event (peer, gst_event_new_tag (tags)); + gst_object_unref (peer); + } + } else { + GST_DEBUG_OBJECT (camerabin, "No tags from application to send"); + } + + /* Push image location event */ + if (camerabin->image_location_list) { + location = camerabin->image_location_list->data; + camerabin->image_location_list = + g_slist_delete_link (camerabin->image_location_list, + camerabin->image_location_list); + GST_DEBUG_OBJECT (camerabin, "Sending image location change to '%s'", + location); + } else { + GST_DEBUG_OBJECT (camerabin, "No filename location change to send"); + g_mutex_unlock (camerabin->image_capture_mutex); + return ret; + } + g_mutex_unlock (camerabin->image_capture_mutex); + + if (location) { + evt = gst_camera_bin_new_event_file_location (location); + peer = gst_pad_get_peer (pad); + gst_pad_send_event (peer, evt); + gst_object_unref (peer); + g_free (location); + } else { + /* This means we don't have to encode the capture, it is used for + * signaling the application just wants the preview */ + ret = FALSE; + GST_CAMERA_BIN2_PROCESSING_DEC (camerabin); + } + + return ret; +} + + +static gboolean +gst_camera_bin_image_sink_event_probe (GstPad * pad, GstEvent * event, + gpointer data) +{ + GstCameraBin2 *camerabin = data; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM:{ + if (gst_event_has_name (event, "new-location")) { + const GstStructure *structure = gst_event_get_structure (event); + const gchar *filename = gst_structure_get_string (structure, + "location"); + + gst_element_set_state (camerabin->imagesink, GST_STATE_NULL); + GST_DEBUG_OBJECT (camerabin, "Setting filename to imagesink: %s", + filename); + g_object_set (camerabin->imagesink, "location", filename, NULL); + if (gst_element_set_state (camerabin->imagesink, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + /* Resets the latest state change return, that would be a failure + * and could cause problems in a camerabin2 state change */ + gst_element_set_state (camerabin->imagesink, GST_STATE_NULL); + } + } + } + break; + default: + break; + } + + return TRUE; +} + +static gboolean +gst_camera_bin_audio_src_data_probe (GstPad * pad, GstMiniObject * obj, + gpointer data) +{ + GstCameraBin2 *camera = data; + gboolean ret = TRUE; + + if (GST_IS_BUFFER (obj)) { + if (G_UNLIKELY (camera->audio_send_newseg)) { + GstBuffer *buf = GST_BUFFER_CAST (obj); + GstClockTime ts = GST_BUFFER_TIMESTAMP (buf); + GstPad *peer; + + if (!GST_CLOCK_TIME_IS_VALID (ts)) { + ts = 0; + } + + peer = gst_pad_get_peer (pad); + g_return_val_if_fail (peer != NULL, TRUE); + + gst_pad_send_event (peer, gst_event_new_new_segment (FALSE, 1.0, + GST_FORMAT_TIME, ts, -1, 0)); + + gst_object_unref (peer); + + camera->audio_send_newseg = FALSE; + } + } else { + GstEvent *event = GST_EVENT_CAST (obj); + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + /* we only let an EOS pass when the user is stopping a capture */ + if (camera->audio_drop_eos) { + ret = FALSE; + } else { + camera->audio_drop_eos = TRUE; + /* should already be false, but reinforce in case no buffers get + * pushed */ + camera->audio_send_newseg = FALSE; + } + } else if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + ret = FALSE; + } + } + + return ret; +} + +/** + * gst_camera_bin_create_elements: + * @param camera: the #GstCameraBin2 + * + * Creates all elements inside #GstCameraBin2 + * + * Each of the pads on the camera source is linked as follows: + * .pad ! queue ! capsfilter ! correspondingbin + * + * Where 'correspondingbin' is the bin appropriate for + * the camera source pad. + */ +static gboolean +gst_camera_bin_create_elements (GstCameraBin2 * camera) +{ + gboolean new_src = FALSE; + gboolean new_audio_src = FALSE; + gboolean has_audio; + gboolean profile_switched = FALSE; + const gchar *missing_element_name; + gint encbin_flags = 0; + + if (!camera->elements_created) { + /* Check that elements created in _init were really created */ + if (!(camera->audio_capsfilter && camera->videobin_capsfilter && + camera->imagebin_capsfilter && camera->viewfinderbin_capsfilter)) { + missing_element_name = "capsfilter"; + goto missing_element; + } + + camera->video_encodebin = + gst_element_factory_make ("encodebin", "video-encodebin"); + if (!camera->video_encodebin) { + missing_element_name = "encodebin"; + goto missing_element; + } + camera->video_encodebin_signal_id = + g_signal_connect (camera->video_encodebin, "element-added", + (GCallback) encodebin_element_added, camera); + + camera->videosink = + gst_element_factory_make ("filesink", "videobin-filesink"); + if (!camera->videosink) { + missing_element_name = "filesink"; + goto missing_element; + } + g_object_set (camera->videosink, "async", FALSE, NULL); + + /* audio elements */ + if (!camera->audio_volume) { + missing_element_name = "volume"; + goto missing_element; + } + + if (camera->video_profile == NULL) { + GstEncodingContainerProfile *prof; + GstCaps *caps; + + caps = gst_caps_new_simple ("application/ogg", NULL); + prof = gst_encoding_container_profile_new ("ogg", "theora+vorbis+ogg", + caps, NULL); + gst_caps_unref (caps); + + caps = gst_caps_new_simple ("video/x-theora", NULL); + if (!gst_encoding_container_profile_add_profile (prof, + (GstEncodingProfile *) gst_encoding_video_profile_new (caps, + NULL, NULL, 1))) { + GST_WARNING_OBJECT (camera, "Failed to create encoding profiles"); + } + gst_caps_unref (caps); + + caps = gst_caps_new_simple ("audio/x-vorbis", NULL); + if (!gst_encoding_container_profile_add_profile (prof, + (GstEncodingProfile *) gst_encoding_audio_profile_new (caps, + NULL, NULL, 1))) { + GST_WARNING_OBJECT (camera, "Failed to create encoding profiles"); + } + gst_caps_unref (caps); + + camera->video_profile = (GstEncodingProfile *) prof; + camera->video_profile_switch = TRUE; + } + + camera->image_encodebin = + gst_element_factory_make ("encodebin", "image-encodebin"); + if (!camera->image_encodebin) { + missing_element_name = "encodebin"; + goto missing_element; + } + /* durations have no meaning for image captures */ + g_object_set (camera->image_encodebin, "queue-time-max", (guint64) 0, NULL); + + camera->image_encodebin_signal_id = + g_signal_connect (camera->image_encodebin, "element-added", + (GCallback) encodebin_element_added, camera); + + camera->imagesink = + gst_element_factory_make ("multifilesink", "imagebin-filesink"); + if (!camera->imagesink) { + missing_element_name = "multifilesink"; + goto missing_element; + } + g_object_set (camera->imagesink, "async", FALSE, "post-messages", TRUE, + NULL); + + if (camera->image_profile == NULL) { + GstEncodingContainerProfile *prof; + GstEncodingVideoProfile *vprof; + GstCaps *caps; + + caps = gst_caps_new_simple ("image/jpeg", NULL); + vprof = gst_encoding_video_profile_new (caps, NULL, NULL, 1); + gst_encoding_video_profile_set_variableframerate (vprof, TRUE); + + prof = gst_encoding_container_profile_new ("jpeg", "jpeg container", caps, + NULL); + gst_encoding_container_profile_add_profile (prof, + (GstEncodingProfile *) vprof); + + gst_caps_unref (caps); + camera->image_profile = (GstEncodingProfile *) prof; + camera->image_profile_switch = TRUE; + } + + camera->viewfinderbin_queue = + gst_element_factory_make ("queue", "viewfinderbin-queue"); + if (!camera->viewfinderbin_queue) { + missing_element_name = "queue"; + goto missing_element; + } + + g_object_set (camera->viewfinderbin_queue, "leaky", 2, "silent", TRUE, + "max-size-time", (guint64) 0, "max-size-bytes", (guint) 0, + "max-size-buffers", (guint) 1, NULL); + + gst_bin_add_many (GST_BIN_CAST (camera), + gst_object_ref (camera->video_encodebin), + gst_object_ref (camera->videosink), + gst_object_ref (camera->image_encodebin), + gst_object_ref (camera->imagesink), + gst_object_ref (camera->viewfinderbin_queue), NULL); + + gst_element_link_pads_full (camera->video_encodebin, "src", + camera->videosink, "sink", GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full (camera->image_encodebin, "src", + camera->imagesink, "sink", GST_PAD_LINK_CHECK_NOTHING); + gst_element_link_pads_full (camera->viewfinderbin_queue, "src", + camera->viewfinderbin_capsfilter, "sink", GST_PAD_LINK_CHECK_CAPS); + gst_element_link_pads_full (camera->viewfinderbin_capsfilter, "src", + camera->viewfinderbin, "sink", GST_PAD_LINK_CHECK_CAPS); + + { + /* set an event probe to watch for custom location changes */ + GstPad *srcpad; + + srcpad = gst_element_get_static_pad (camera->image_encodebin, "src"); + + gst_pad_add_event_probe (srcpad, + (GCallback) gst_camera_bin_image_sink_event_probe, camera); + + gst_object_unref (srcpad); + } + + /* + * Video can't get into playing as its internal filesink will open + * a file for writing and leave it empty if unused. + * + * Its state is managed using the current mode and the source's + * ready-for-capture notify callback. When we are at video mode and + * the source's ready-for-capture goes to FALSE it means it is + * starting recording, so we should prepare the video bin. + */ + gst_element_set_locked_state (camera->videosink, TRUE); + gst_element_set_locked_state (camera->imagesink, TRUE); + + g_object_set (camera->videosink, "location", camera->location, NULL); + g_object_set (camera->imagesink, "location", camera->location, NULL); + } + + /* propagate the flags property by translating appropriate values + * to GstEncFlags values */ + if (camera->flags & GST_CAM_FLAG_NO_AUDIO_CONVERSION) + encbin_flags |= (1 << 0); + if (camera->flags & GST_CAM_FLAG_NO_VIDEO_CONVERSION) + encbin_flags |= (1 << 1); + g_object_set (camera->video_encodebin, "flags", encbin_flags, NULL); + + /* image encodebin has only video branch so disable its conversion elements + * appropriately */ + if (camera->flags & GST_CAM_FLAG_NO_IMAGE_CONVERSION) + g_object_set (camera->image_encodebin, "flags", (1 << 1), NULL); + + g_object_set (camera->viewfinderbin, "disable-converters", + camera->flags & GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION ? TRUE : FALSE, + NULL); + + if (camera->video_profile_switch) { + GST_DEBUG_OBJECT (camera, "Switching video-encodebin's profile"); + g_object_set (camera->video_encodebin, "profile", camera->video_profile, + NULL); + if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera, + camera->video_encodebin, camera->videobin_capsfilter, + VIDEO_PAD))) { + goto fail; + } + camera->video_profile_switch = FALSE; + + /* used to trigger relinking further down */ + profile_switched = TRUE; + } + + if (camera->image_profile_switch) { + GST_DEBUG_OBJECT (camera, "Switching image-encodebin's profile"); + g_object_set (camera->image_encodebin, "profile", camera->image_profile, + NULL); + if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera, + camera->image_encodebin, camera->imagebin_capsfilter, + VIDEO_PAD))) { + goto fail; + } + camera->image_profile_switch = FALSE; + } + + /* check if we need to replace the camera src */ + if (camera->src) { + if (camera->user_src && camera->user_src != camera->src) { + + if (camera->src_capture_notify_id) + g_signal_handler_disconnect (camera->src, + camera->src_capture_notify_id); + + gst_bin_remove (GST_BIN_CAST (camera), camera->src); + gst_object_unref (camera->src); + camera->src = NULL; + } + } + + if (!camera->src) { + if (camera->user_src) { + camera->src = gst_object_ref (camera->user_src); + } else { + camera->src = + gst_element_factory_make ("wrappercamerabinsrc", "camerasrc"); + } + + new_src = TRUE; + } + + g_assert (camera->src != NULL); + g_object_set (camera->src, "mode", camera->mode, NULL); + if (camera->src) { + if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src), + "preview-caps")) { + g_object_set (camera->src, "post-previews", camera->post_previews, + "preview-caps", camera->preview_caps, "preview-filter", + camera->preview_filter, NULL); + } + g_signal_connect (G_OBJECT (camera->src), "notify::zoom", + (GCallback) gst_camera_bin_src_notify_zoom_cb, camera); + g_object_set (camera->src, "zoom", camera->zoom, NULL); + g_signal_connect (G_OBJECT (camera->src), "notify::max-zoom", + (GCallback) gst_camera_bin_src_notify_max_zoom_cb, camera); + } + if (new_src) { + GstPad *imgsrc = gst_element_get_static_pad (camera->src, "imgsrc"); + + gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->src)); + camera->src_capture_notify_id = g_signal_connect (G_OBJECT (camera->src), + "notify::ready-for-capture", + G_CALLBACK (gst_camera_bin_src_notify_readyforcapture), camera); + gst_element_link_pads (camera->src, "vfsrc", camera->viewfinderbin_queue, + "sink"); + + if (!gst_element_link_pads (camera->src, "imgsrc", + camera->imagebin_capsfilter, "sink")) { + GST_ERROR_OBJECT (camera, + "Failed to link camera source's imgsrc pad to image bin capsfilter"); + goto fail; + } + if (!gst_element_link_pads (camera->src, "vidsrc", + camera->videobin_capsfilter, "sink")) { + GST_ERROR_OBJECT (camera, + "Failed to link camera source's vidsrc pad to video bin capsfilter"); + goto fail; + } + + gst_pad_add_buffer_probe (imgsrc, + (GCallback) gst_camera_bin_image_src_buffer_probe, camera); + gst_object_unref (imgsrc); + } + + gst_camera_bin_check_and_replace_filter (camera, &camera->image_filter, + camera->user_image_filter, camera->src, camera->imagebin_capsfilter, + "imgsrc"); + gst_camera_bin_check_and_replace_filter (camera, &camera->video_filter, + camera->user_video_filter, camera->src, camera->videobin_capsfilter, + "vidsrc"); + gst_camera_bin_check_and_replace_filter (camera, &camera->viewfinder_filter, + camera->user_viewfinder_filter, camera->viewfinderbin_queue, + camera->viewfinderbin_capsfilter, NULL); + + /* check if we need to replace the camera audio src */ + has_audio = gst_camera_bin_video_profile_has_audio (camera); + if (camera->audio_src) { + if ((camera->user_audio_src && camera->user_audio_src != camera->audio_src) + || !has_audio) { + gst_bin_remove (GST_BIN_CAST (camera), camera->audio_src); + gst_bin_remove (GST_BIN_CAST (camera), camera->audio_volume); + gst_bin_remove (GST_BIN_CAST (camera), camera->audio_capsfilter); + gst_object_unref (camera->audio_src); + camera->audio_src = NULL; + } + } + + if (!camera->audio_src && has_audio) { + if (camera->user_audio_src) { + camera->audio_src = gst_object_ref (camera->user_audio_src); + } else { + camera->audio_src = + gst_element_factory_make (DEFAULT_AUDIO_SRC, "audiosrc"); + if (!camera->audio_src) { + missing_element_name = DEFAULT_AUDIO_SRC; + goto missing_element; + } + } + + gst_element_set_locked_state (camera->audio_src, TRUE); + new_audio_src = TRUE; + } + + if (new_audio_src) { + GstPad *srcpad; + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (camera->audio_src), + "provide-clock")) { + g_object_set (camera->audio_src, "provide-clock", FALSE, NULL); + } + gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->audio_src)); + gst_bin_add (GST_BIN_CAST (camera), gst_object_ref (camera->audio_volume)); + gst_bin_add (GST_BIN_CAST (camera), + gst_object_ref (camera->audio_capsfilter)); + + gst_element_link_pads_full (camera->audio_src, "src", + camera->audio_volume, "sink", GST_PAD_LINK_CHECK_CAPS); + gst_element_link_pads_full (camera->audio_volume, "src", + camera->audio_capsfilter, "sink", GST_PAD_LINK_CHECK_CAPS); + + srcpad = gst_element_get_static_pad (camera->audio_src, "src"); + + /* 1) drop EOS for audiosrc elements that push them on state_changes + * (basesrc does this) + * 2) Fix newsegment events to have start time = first buffer ts */ + gst_pad_add_data_probe (srcpad, + (GCallback) gst_camera_bin_audio_src_data_probe, camera); + + gst_object_unref (srcpad); + } + if (has_audio) { + gst_camera_bin_check_and_replace_filter (camera, &camera->audio_filter, + camera->user_audio_filter, camera->audio_src, camera->audio_volume, + "src"); + } + + if ((profile_switched && has_audio) || new_audio_src) { + if (GST_PAD_LINK_FAILED (gst_camera_bin_link_encodebin (camera, + camera->video_encodebin, camera->audio_capsfilter, + AUDIO_PAD))) { + goto fail; + } + } + + camera->elements_created = TRUE; + return TRUE; + +missing_element: + gst_element_post_message (GST_ELEMENT_CAST (camera), + gst_missing_element_message_new (GST_ELEMENT_CAST (camera), + missing_element_name)); + GST_ELEMENT_ERROR (camera, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + missing_element_name), (NULL)); + goto fail; + +fail: + /* FIXME properly clean up */ + return FALSE; +} + +static void +_gst_tag_list_free_maybe (GstTagList * taglist) +{ + if (taglist) + gst_tag_list_free (taglist); +} + +static GstStateChangeReturn +gst_camera_bin_change_state (GstElement * element, GstStateChange trans) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (element); + + + switch (trans) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_camera_bin_create_elements (camera)) { + return GST_STATE_CHANGE_FAILURE; + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER (camera); + camera->audio_drop_eos = TRUE; + camera->audio_send_newseg = FALSE; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (GST_STATE (camera->videosink) >= GST_STATE_PAUSED) + gst_element_set_state (camera->videosink, GST_STATE_READY); + if (GST_STATE (camera->imagesink) >= GST_STATE_PAUSED) + gst_element_set_state (camera->imagesink, GST_STATE_READY); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_element_set_state (camera->videosink, GST_STATE_NULL); + gst_element_set_state (camera->imagesink, GST_STATE_NULL); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + switch (trans) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (camera->audio_src && GST_STATE (camera->audio_src) >= GST_STATE_READY) + gst_element_set_state (camera->audio_src, GST_STATE_READY); + + gst_tag_setter_reset_tags (GST_TAG_SETTER (camera)); + GST_CAMERA_BIN2_RESET_PROCESSING_COUNTER (camera); + camera->video_state = GST_CAMERA_BIN_VIDEO_IDLE; + + g_mutex_lock (camera->image_capture_mutex); + g_slist_foreach (camera->image_location_list, (GFunc) g_free, NULL); + g_slist_free (camera->image_location_list); + camera->image_location_list = NULL; + + g_slist_foreach (camera->image_tags_list, + (GFunc) _gst_tag_list_free_maybe, NULL); + g_slist_free (camera->image_tags_list); + camera->image_tags_list = NULL; + g_mutex_unlock (camera->image_capture_mutex); + + g_mutex_lock (camera->preview_list_mutex); + g_slist_foreach (camera->preview_location_list, (GFunc) g_free, NULL); + g_slist_free (camera->preview_location_list); + camera->preview_location_list = NULL; + g_mutex_unlock (camera->preview_list_mutex); + + /* explicitly set to READY as they might be outside of the bin */ + gst_element_set_state (camera->audio_volume, GST_STATE_READY); + gst_element_set_state (camera->audio_capsfilter, GST_STATE_READY); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + if (camera->audio_src) + gst_element_set_state (camera->audio_src, GST_STATE_NULL); + + /* explicitly set to NULL as they might be outside of the bin */ + gst_element_set_state (camera->audio_volume, GST_STATE_NULL); + gst_element_set_state (camera->audio_capsfilter, GST_STATE_NULL); + + break; + default: + break; + } + + return ret; +} + +static gboolean +gst_camera_bin_send_event (GstElement * element, GstEvent * event) +{ + GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (element); + gboolean res; + + /* avoid losing our ref to send_event */ + gst_event_ref (event); + + res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + GstState current; + + if (camera->videosink) { + gst_element_get_state (camera->videosink, ¤t, NULL, 0); + if (current <= GST_STATE_READY) + gst_element_post_message (camera->videosink, + gst_message_new_eos (GST_OBJECT (camera->videosink))); + } + if (camera->imagesink) { + gst_element_get_state (camera->imagesink, ¤t, NULL, 0); + if (current <= GST_STATE_READY) + gst_element_post_message (camera->imagesink, + gst_message_new_eos (GST_OBJECT (camera->imagesink))); + } + break; + } + + default: + break; + } + + gst_event_unref (event); + return res; +} + +static void +gst_camera_bin_set_location (GstCameraBin2 * camera, const gchar * location) +{ + GST_DEBUG_OBJECT (camera, "Setting mode %d location to %s", camera->mode, + location); + g_free (camera->location); + camera->location = g_strdup (location); +} + +static void +gst_camera_bin_set_audio_src (GstCameraBin2 * camera, GstElement * src) +{ + GST_DEBUG_OBJECT (GST_OBJECT (camera), + "Setting audio source %" GST_PTR_FORMAT, src); + + if (camera->user_audio_src) + g_object_unref (camera->user_audio_src); + + if (src) + g_object_ref (src); + camera->user_audio_src = src; +} + +static void +gst_camera_bin_set_camera_src (GstCameraBin2 * camera, GstElement * src) +{ + GST_DEBUG_OBJECT (GST_OBJECT (camera), + "Setting camera source %" GST_PTR_FORMAT, src); + + if (camera->user_src) + g_object_unref (camera->user_src); + + if (src) + g_object_ref (src); + camera->user_src = src; +} + +static void +gst_camera_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (object); + + switch (prop_id) { + case PROP_MODE: + gst_camera_bin_change_mode (camera, g_value_get_enum (value)); + break; + case PROP_LOCATION: + gst_camera_bin_set_location (camera, g_value_get_string (value)); + break; + case PROP_CAMERA_SRC: + gst_camera_bin_set_camera_src (camera, g_value_get_object (value)); + break; + case PROP_AUDIO_SRC: + gst_camera_bin_set_audio_src (camera, g_value_get_object (value)); + break; + case PROP_MUTE_AUDIO: + g_object_set (camera->audio_volume, "mute", g_value_get_boolean (value), + NULL); + break; + case PROP_AUDIO_CAPTURE_CAPS:{ + GST_DEBUG_OBJECT (camera, + "Setting audio capture caps to %" GST_PTR_FORMAT, + gst_value_get_caps (value)); + + if (G_LIKELY (camera->audio_capsfilter)) { + g_object_set (camera->audio_capsfilter, "caps", + gst_value_get_caps (value), NULL); + } else { + GST_WARNING_OBJECT (camera, "Audio capsfilter missing"); + } + } + break; + case PROP_IMAGE_CAPTURE_CAPS:{ + GstPad *pad = NULL; + + if (camera->src) + pad = + gst_element_get_static_pad (camera->src, + GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME); + + GST_DEBUG_OBJECT (camera, + "Setting image capture caps to %" GST_PTR_FORMAT, + gst_value_get_caps (value)); + + if (G_LIKELY (camera->imagebin_capsfilter)) { + g_object_set (camera->imagebin_capsfilter, "caps", + gst_value_get_caps (value), NULL); + } else { + GST_WARNING_OBJECT (camera, "Image capsfilter missing"); + } + + /* set the capsfilter caps and notify the src to renegotiate */ + if (pad) { + GST_DEBUG_OBJECT (camera, "Pushing renegotiate on %s", + GST_PAD_NAME (pad)); + gst_pad_send_event (pad, gst_camera_bin_new_event_renegotiate ()); + gst_object_unref (pad); + } + } + break; + case PROP_VIDEO_CAPTURE_CAPS:{ + GstPad *pad = NULL; + + if (camera->src) + pad = + gst_element_get_static_pad (camera->src, + GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME); + + GST_DEBUG_OBJECT (camera, + "Setting video capture caps to %" GST_PTR_FORMAT, + gst_value_get_caps (value)); + + /* set the capsfilter caps and notify the src to renegotiate */ + if (G_LIKELY (camera->videobin_capsfilter)) { + g_object_set (camera->videobin_capsfilter, "caps", + gst_value_get_caps (value), NULL); + } else { + GST_WARNING_OBJECT (camera, "Video capsfilter missing"); + } + + if (pad) { + GST_DEBUG_OBJECT (camera, "Pushing renegotiate on %s", + GST_PAD_NAME (pad)); + gst_pad_send_event (pad, gst_camera_bin_new_event_renegotiate ()); + gst_object_unref (pad); + } + } + break; + case PROP_VIEWFINDER_CAPS:{ + GstPad *pad = NULL; + + if (camera->src) + pad = + gst_element_get_static_pad (camera->src, + GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME); + + GST_DEBUG_OBJECT (camera, + "Setting viewfinder capture caps to %" GST_PTR_FORMAT, + gst_value_get_caps (value)); + + /* set the capsfilter caps and notify the src to renegotiate */ + if (G_LIKELY (camera->viewfinderbin_capsfilter)) { + g_object_set (camera->viewfinderbin_capsfilter, "caps", + gst_value_get_caps (value), NULL); + } else { + GST_WARNING_OBJECT (camera, "Viewfinder capsfilter missing"); + } + + if (pad) { + GST_DEBUG_OBJECT (camera, "Pushing renegotiate on %s", + GST_PAD_NAME (pad)); + gst_pad_send_event (pad, gst_camera_bin_new_event_renegotiate ()); + gst_object_unref (pad); + } + } + break; + case PROP_POST_PREVIEWS: + camera->post_previews = g_value_get_boolean (value); + if (camera->src + && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src), + "post-previews")) + g_object_set (camera->src, "post-previews", camera->post_previews, + NULL); + break; + case PROP_PREVIEW_CAPS: + gst_caps_replace (&camera->preview_caps, + (GstCaps *) gst_value_get_caps (value)); + if (camera->src + && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src), + "preview-caps")) + g_object_set (camera->src, "preview-caps", camera->preview_caps, NULL); + break; + case PROP_VIDEO_ENCODING_PROFILE: + if (camera->video_profile) + gst_encoding_profile_unref (camera->video_profile); + camera->video_profile = + (GstEncodingProfile *) gst_value_dup_mini_object (value); + camera->video_profile_switch = TRUE; + break; + case PROP_IMAGE_FILTER: + if (camera->user_image_filter) + g_object_unref (camera->user_image_filter); + + camera->user_image_filter = g_value_dup_object (value); + break; + case PROP_VIDEO_FILTER: + if (camera->user_video_filter) + g_object_unref (camera->user_video_filter); + + camera->user_video_filter = g_value_dup_object (value); + break; + case PROP_VIEWFINDER_FILTER: + if (camera->user_viewfinder_filter) + g_object_unref (camera->user_viewfinder_filter); + + camera->user_viewfinder_filter = g_value_dup_object (value); + break; + case PROP_PREVIEW_FILTER: + if (camera->preview_filter) + g_object_unref (camera->preview_filter); + + camera->preview_filter = g_value_dup_object (value); + if (camera->src + && g_object_class_find_property (G_OBJECT_GET_CLASS (camera->src), + "preview-filter")) + g_object_set (camera->src, "preview-filter", camera->preview_filter, + NULL); + break; + case PROP_AUDIO_FILTER: + if (camera->user_audio_filter) + g_object_unref (camera->user_audio_filter); + + camera->user_audio_filter = g_value_dup_object (value); + break; + case PROP_VIEWFINDER_SINK: + g_object_set (camera->viewfinderbin, "video-sink", + g_value_get_object (value), NULL); + break; + case PROP_ZOOM: + camera->zoom = g_value_get_float (value); + /* limit to max-zoom */ + if (camera->zoom > camera->max_zoom) { + GST_DEBUG_OBJECT (camera, "Clipping zoom %f to max-zoom %f", + camera->zoom, camera->max_zoom); + camera->zoom = camera->max_zoom; + } + if (camera->src) + g_object_set (camera->src, "zoom", camera->zoom, NULL); + break; + case PROP_IMAGE_ENCODING_PROFILE: + if (camera->image_profile) + gst_encoding_profile_unref (camera->image_profile); + camera->image_profile = + (GstEncodingProfile *) gst_value_dup_mini_object (value); + camera->image_profile_switch = TRUE; + break; + case PROP_FLAGS: + camera->flags = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_camera_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCameraBin2 *camera = GST_CAMERA_BIN2_CAST (object); + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum (value, camera->mode); + break; + case PROP_LOCATION: + g_value_set_string (value, camera->location); + break; + case PROP_CAMERA_SRC: + g_value_set_object (value, camera->user_src); + break; + case PROP_AUDIO_SRC: + g_value_set_object (value, camera->user_audio_src); + break; + case PROP_MUTE_AUDIO:{ + gboolean mute; + + g_object_get (camera->audio_volume, "mute", &mute, NULL); + g_value_set_boolean (value, mute); + break; + } + case PROP_AUDIO_CAPTURE_SUPPORTED_CAPS: + case PROP_VIDEO_CAPTURE_SUPPORTED_CAPS: + case PROP_VIEWFINDER_SUPPORTED_CAPS: + case PROP_IMAGE_CAPTURE_SUPPORTED_CAPS:{ + GstPad *pad; + GstElement *element; + GstCaps *caps; + const gchar *padname; + + if (prop_id == PROP_VIDEO_CAPTURE_SUPPORTED_CAPS) { + element = camera->src; + padname = GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME; + } else if (prop_id == PROP_IMAGE_CAPTURE_SUPPORTED_CAPS) { + element = camera->src; + padname = GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME; + } else if (prop_id == PROP_VIEWFINDER_SUPPORTED_CAPS) { + element = camera->src; + padname = GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME; + } else { + element = camera->audio_src; + padname = "src"; + } + + if (element) { + pad = gst_element_get_static_pad (element, padname); + + g_assert (pad != NULL); + + /* TODO not sure if we want get_caps or get_allowed_caps to already + * consider the full pipeline scenario and avoid picking a caps that + * won't negotiate. Need to take care on the special case of the + * pad being unlinked. + */ + caps = gst_pad_get_caps_reffed (pad); + if (caps) { + gst_value_set_caps (value, caps); + gst_caps_unref (caps); + } + + gst_object_unref (pad); + } else { + GST_DEBUG_OBJECT (camera, "Source not created, can't get " + "supported caps"); + } + } + break; + case PROP_AUDIO_CAPTURE_CAPS:{ + GstCaps *caps = NULL; + if (G_LIKELY (camera->audio_capsfilter)) { + g_object_get (camera->audio_capsfilter, "caps", &caps, NULL); + } else { + GST_WARNING ("Missing audio capsfilter"); + } + gst_value_set_caps (value, caps); + gst_caps_unref (caps); + } + break; + case PROP_IMAGE_CAPTURE_CAPS:{ + GstCaps *caps = NULL; + if (G_LIKELY (camera->imagebin_capsfilter)) { + g_object_get (camera->imagebin_capsfilter, "caps", &caps, NULL); + } else { + GST_WARNING ("Missing imagebin capsfilter"); + } + gst_value_set_caps (value, caps); + gst_caps_unref (caps); + } + break; + case PROP_VIDEO_CAPTURE_CAPS:{ + GstCaps *caps = NULL; + if (G_LIKELY (camera->videobin_capsfilter)) { + g_object_get (camera->videobin_capsfilter, "caps", &caps, NULL); + } else { + GST_WARNING ("Missing imagebin capsfilter"); + } + gst_value_set_caps (value, caps); + gst_caps_unref (caps); + } + break; + case PROP_VIEWFINDER_CAPS:{ + GstCaps *caps = NULL; + if (G_LIKELY (camera->viewfinderbin_capsfilter)) { + g_object_get (camera->viewfinderbin_capsfilter, "caps", &caps, NULL); + } else { + GST_WARNING ("Missing imagebin capsfilter"); + } + gst_value_set_caps (value, caps); + gst_caps_unref (caps); + } + break; + case PROP_POST_PREVIEWS: + g_value_set_boolean (value, camera->post_previews); + break; + case PROP_PREVIEW_CAPS: + if (camera->preview_caps) + gst_value_set_caps (value, camera->preview_caps); + break; + case PROP_VIDEO_ENCODING_PROFILE: + if (camera->video_profile) { + gst_value_set_mini_object (value, + (GstMiniObject *) camera->video_profile); + } + break; + case PROP_VIDEO_FILTER: + if (camera->user_video_filter) + g_value_set_object (value, camera->user_video_filter); + break; + case PROP_IMAGE_FILTER: + if (camera->user_image_filter) + g_value_set_object (value, camera->user_image_filter); + break; + case PROP_VIEWFINDER_FILTER: + if (camera->user_viewfinder_filter) + g_value_set_object (value, camera->user_viewfinder_filter); + break; + case PROP_AUDIO_FILTER: + if (camera->user_audio_filter) + g_value_set_object (value, camera->user_audio_filter); + break; + case PROP_PREVIEW_FILTER: + if (camera->preview_filter) + g_value_set_object (value, camera->preview_filter); + break; + case PROP_VIEWFINDER_SINK:{ + GstElement *sink; + + g_object_get (camera->viewfinderbin, "video-sink", &sink, NULL); + g_value_take_object (value, sink); + break; + } + case PROP_ZOOM: + g_value_set_float (value, camera->zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, camera->max_zoom); + break; + case PROP_IMAGE_ENCODING_PROFILE: + if (camera->image_profile) { + gst_value_set_mini_object (value, + (GstMiniObject *) camera->image_profile); + } + break; + case PROP_IDLE: + g_value_set_boolean (value, + g_atomic_int_get (&camera->processing_counter) == 0); + break; + case PROP_FLAGS: + g_value_set_flags (value, camera->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_camera_bin2_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_camera_bin_debug, "camerabin2", 0, "CameraBin2"); + + return gst_element_register (plugin, "camerabin2", GST_RANK_NONE, + gst_camera_bin2_get_type ()); +} Index: gst-plugins-good0.10/gst/camerabin2/gstcamerabin2.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstcamerabin2.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,164 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _GST_CAMERA_BIN2_H_ +#define _GST_CAMERA_BIN2_H_ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_CAMERA_BIN2 (gst_camera_bin2_get_type()) +#define GST_CAMERA_BIN2(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CAMERA_BIN2,GstCameraBin2)) +#define GST_CAMERA_BIN2_CAST(obj) ((GstCameraBin2 *) obj) +#define GST_CAMERA_BIN2_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CAMERA_BIN2,GstCameraBin2Class)) +#define GST_IS_CAMERA_BIN2(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CAMERA_BIN2)) +#define GST_IS_CAMERA_BIN2_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CAMERA_BIN2)) + +typedef enum +{ + /* matches GstEncFlags GST_ENC_FLAG_NO_AUDIO_CONVERSION in encodebin */ + GST_CAM_FLAG_NO_AUDIO_CONVERSION = (1 << 0), + /* matches GstEncFlags GST_ENC_FLAG_NO_VIDEO_CONVERSION in encodebin */ + GST_CAM_FLAG_NO_VIDEO_CONVERSION = (1 << 1), + /* maps to 'disable-converters' property in viewfinderbin */ + GST_CAM_FLAG_NO_VIEWFINDER_CONVERSION = (1 << 2), + /* maps to GstEncFlags GST_ENC_FLAG_NO_VIDEO_CONVERSION in the image bin's + * encodebin */ + GST_CAM_FLAG_NO_IMAGE_CONVERSION = (1 << 3) +} GstCamFlags; + + +typedef enum _GstCameraBinVideoState +{ + GST_CAMERA_BIN_VIDEO_IDLE=0, + GST_CAMERA_BIN_VIDEO_STARTING=1, + GST_CAMERA_BIN_VIDEO_RECORDING=2, + GST_CAMERA_BIN_VIDEO_FINISHING=3 +} GstCameraBinVideoState; + +typedef struct _GstCameraBin2 GstCameraBin2; +typedef struct _GstCameraBin2Class GstCameraBin2Class; + +struct _GstCameraBin2 +{ + GstPipeline pipeline; + + GstElement *src; + GstElement *user_src; + gulong src_capture_notify_id; + + GstElement *video_encodebin; + gulong video_encodebin_signal_id; + GstElement *videosink; + GstElement *videobin_capsfilter; + + GstElement *viewfinderbin; + GstElement *viewfinderbin_queue; + GstElement *viewfinderbin_capsfilter; + + GstElement *image_encodebin; + gulong image_encodebin_signal_id; + GstElement *imagesink; + GstElement *imagebin_capsfilter; + + GstElement *video_filter; + GstElement *image_filter; + GstElement *viewfinder_filter; + GstElement *audio_filter; + GstElement *user_video_filter; + GstElement *user_image_filter; + GstElement *user_viewfinder_filter; + GstElement *user_audio_filter; + + GstElement *audio_src; + GstElement *user_audio_src; + GstElement *audio_volume; + GstElement *audio_capsfilter; + + gint processing_counter; /* atomic int */ + + /* Index of the auto incrementing file index for captures */ + gint capture_index; + + GMutex *image_capture_mutex; + /* stores list of image locations to be pushed to the image sink + * as file location change notifications, they are pushed before + * each buffer capture */ + GSList *image_location_list; + /* Store also tags and push them before each captured image */ + GSList *image_tags_list; + + /* + * Similar to above, but used for giving names to previews + * + * Need to protect with a mutex as this list is used when the + * camera-source posts a preview image. As we have no control + * on how the camera-source will behave (we can only tell how + * it should), the preview location list might be used in an + * inconsistent way. + * One example is the camera-source posting a preview image after + * camerabin2 was put to ready, when this preview list will be + * freed and set to NULL. Concurrent access might lead to crashes in + * this situation. (Concurrency from the state-change freeing the + * list and the message handling function looking at preview names) + */ + GSList *preview_location_list; + GMutex *preview_list_mutex; + + gboolean video_profile_switch; + gboolean image_profile_switch; + + gboolean audio_drop_eos; + gboolean audio_send_newseg; + + GMutex *video_capture_mutex; + GCond *video_state_cond; + GstCameraBinVideoState video_state; + + /* properties */ + gint mode; + gchar *location; + gboolean post_previews; + GstCaps *preview_caps; + GstElement *preview_filter; + GstEncodingProfile *video_profile; + GstEncodingProfile *image_profile; + gfloat zoom; + gfloat max_zoom; + GstCamFlags flags; + + gboolean elements_created; +}; + +struct _GstCameraBin2Class +{ + GstPipelineClass pipeline_class; + + /* Action signals */ + void (*start_capture) (GstCameraBin2 * camera); + void (*stop_capture) (GstCameraBin2 * camera); +}; + +GType gst_camera_bin2_get_type (void); +gboolean gst_camera_bin2_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif Index: gst-plugins-good0.10/gst/camerabin2/gstimagecapturebin.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstimagecapturebin.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,352 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-gstimagecapturebin + * + * The gstimagecapturebin element does FIXME stuff. + * + * + * Example launch line + * |[ + * gst-launch -v videotestsrc num-buffers=3 ! imagecapturebin + * ]| + * FIXME Describe what the pipeline does. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstimagecapturebin.h" +#include "camerabingeneral.h" + +/* prototypes */ + + +enum +{ + PROP_0, + PROP_LOCATION, + PROP_ENCODER, + PROP_MUXER +}; + +#define DEFAULT_LOCATION "img_%d" +#define DEFAULT_COLORSPACE "ffmpegcolorspace" +#define DEFAULT_ENCODER "jpegenc" +#define DEFAULT_MUXER "jifmux" +#define DEFAULT_SINK "multifilesink" + +/* pad templates */ + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-yuv; video/x-raw-rgb") + ); + +/* class initialization */ + +GST_BOILERPLATE (GstImageCaptureBin, gst_image_capture_bin, GstBin, + GST_TYPE_BIN); + +/* GObject callbacks */ +static void gst_image_capture_bin_dispose (GObject * object); +static void gst_image_capture_bin_finalize (GObject * object); + +/* Element class functions */ +static GstStateChangeReturn +gst_image_capture_bin_change_state (GstElement * element, GstStateChange trans); + +static void +gst_image_capture_bin_set_encoder (GstImageCaptureBin * imagebin, + GstElement * encoder) +{ + GST_DEBUG_OBJECT (GST_OBJECT (imagebin), + "Setting image encoder %" GST_PTR_FORMAT, encoder); + + if (imagebin->user_encoder) + g_object_unref (imagebin->user_encoder); + + if (encoder) + g_object_ref (encoder); + + imagebin->user_encoder = encoder; +} + +static void +gst_image_capture_bin_set_muxer (GstImageCaptureBin * imagebin, + GstElement * muxer) +{ + GST_DEBUG_OBJECT (GST_OBJECT (imagebin), + "Setting image muxer %" GST_PTR_FORMAT, muxer); + + if (imagebin->user_muxer) + g_object_unref (imagebin->user_muxer); + + if (muxer) + g_object_ref (muxer); + + imagebin->user_muxer = muxer; +} + +static void +gst_image_capture_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstImageCaptureBin *imagebin = GST_IMAGE_CAPTURE_BIN_CAST (object); + + switch (prop_id) { + case PROP_LOCATION: + g_free (imagebin->location); + imagebin->location = g_value_dup_string (value); + GST_DEBUG_OBJECT (imagebin, "setting location to %s", imagebin->location); + if (imagebin->sink) { + g_object_set (imagebin->sink, "location", imagebin->location, NULL); + } + break; + case PROP_ENCODER: + gst_image_capture_bin_set_encoder (imagebin, g_value_get_object (value)); + break; + case PROP_MUXER: + gst_image_capture_bin_set_muxer (imagebin, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_image_capture_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstImageCaptureBin *imagebin = GST_IMAGE_CAPTURE_BIN_CAST (object); + + switch (prop_id) { + case PROP_LOCATION: + g_value_set_string (value, imagebin->location); + break; + case PROP_ENCODER: + g_value_set_object (value, imagebin->encoder); + break; + case PROP_MUXER: + g_value_set_object (value, imagebin->muxer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_image_capture_bin_finalize (GObject * object) +{ + GstImageCaptureBin *imgbin = GST_IMAGE_CAPTURE_BIN_CAST (object); + + g_free (imgbin->location); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_image_capture_bin_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_details_simple (element_class, "Image Capture Bin", + "Sink/Video", "Image Capture Bin used in camerabin2", + "Thiago Santos "); +} + +static void +gst_image_capture_bin_class_init (GstImageCaptureBinClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + gobject_class = G_OBJECT_CLASS (klass); + element_class = GST_ELEMENT_CLASS (klass); + + gobject_class->dispose = gst_image_capture_bin_dispose; + gobject_class->finalize = gst_image_capture_bin_finalize; + gobject_class->set_property = gst_image_capture_bin_set_property; + gobject_class->get_property = gst_image_capture_bin_get_property; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_image_capture_bin_change_state); + + g_object_class_install_property (gobject_class, PROP_LOCATION, + g_param_spec_string ("location", "Location", + "Location to save the captured files. A %%d can be used as a " + "placeholder for a capture count", + DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_ENCODER, + g_param_spec_object ("image-encoder", "Image encoder", + "Image encoder GStreamer element (default is jpegenc)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_MUXER, + g_param_spec_object ("image-muxer", "Image muxer", + "Image muxer GStreamer element (default is jifmux)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_image_capture_bin_init (GstImageCaptureBin * imagebin, + GstImageCaptureBinClass * imagebin_class) +{ + GstPadTemplate *tmpl; + + tmpl = gst_static_pad_template_get (&sink_template); + imagebin->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", tmpl); + gst_object_unref (tmpl); + gst_element_add_pad (GST_ELEMENT_CAST (imagebin), imagebin->ghostpad); + + imagebin->sink = NULL; + + imagebin->location = g_strdup (DEFAULT_LOCATION); + imagebin->encoder = NULL; + imagebin->user_encoder = NULL; + imagebin->muxer = NULL; + imagebin->user_muxer = NULL; +} + +static void +gst_image_capture_bin_dispose (GObject * object) +{ + GstImageCaptureBin *imagebin = GST_IMAGE_CAPTURE_BIN_CAST (object); + + if (imagebin->user_encoder) { + gst_object_unref (imagebin->user_encoder); + imagebin->user_encoder = NULL; + } + + if (imagebin->user_muxer) { + gst_object_unref (imagebin->user_muxer); + imagebin->user_muxer = NULL; + } + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) imagebin); +} + +static gboolean +gst_image_capture_bin_create_elements (GstImageCaptureBin * imagebin) +{ + GstElement *colorspace; + GstPad *pad = NULL; + + if (imagebin->elements_created) + return TRUE; + + /* create elements */ + colorspace = + gst_camerabin_create_and_add_element (GST_BIN (imagebin), + DEFAULT_COLORSPACE, "imagebin-colorspace"); + if (!colorspace) + goto error; + + if (imagebin->user_encoder) { + imagebin->encoder = imagebin->user_encoder; + if (!gst_camerabin_add_element (GST_BIN (imagebin), imagebin->encoder)) { + goto error; + } + } else { + imagebin->encoder = + gst_camerabin_create_and_add_element (GST_BIN (imagebin), + DEFAULT_ENCODER, "imagebin-encoder"); + if (!imagebin->encoder) + goto error; + } + + if (imagebin->user_muxer) { + imagebin->muxer = imagebin->user_muxer; + if (!gst_camerabin_add_element (GST_BIN (imagebin), imagebin->muxer)) { + goto error; + } + } else { + imagebin->muxer = + gst_camerabin_create_and_add_element (GST_BIN (imagebin), + DEFAULT_MUXER, "imagebin-muxer"); + if (!imagebin->muxer) + goto error; + } + + imagebin->sink = + gst_camerabin_create_and_add_element (GST_BIN (imagebin), DEFAULT_SINK, + "imagebin-sink"); + if (!imagebin->sink) + goto error; + + g_object_set (imagebin->sink, "location", imagebin->location, "async", FALSE, + "post-messages", TRUE, NULL); + + /* add ghostpad */ + pad = gst_element_get_static_pad (colorspace, "sink"); + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (imagebin->ghostpad), pad)) + goto error; + gst_object_unref (pad); + + imagebin->elements_created = TRUE; + return TRUE; + +error: + if (pad) + gst_object_unref (pad); + return FALSE; +} + +static GstStateChangeReturn +gst_image_capture_bin_change_state (GstElement * element, GstStateChange trans) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstImageCaptureBin *imagebin = GST_IMAGE_CAPTURE_BIN_CAST (element); + + switch (trans) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_image_capture_bin_create_elements (imagebin)) { + return GST_STATE_CHANGE_FAILURE; + } + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + switch (trans) { + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +gboolean +gst_image_capture_bin_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "imagecapturebin", GST_RANK_NONE, + gst_image_capture_bin_get_type ()); +} Index: gst-plugins-good0.10/gst/camerabin2/gstimagecapturebin.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstimagecapturebin.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _GST_IMAGE_CAPTURE_BIN_H_ +#define _GST_IMAGE_CAPTURE_BIN_H_ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_IMAGE_CAPTURE_BIN (gst_image_capture_bin_get_type()) +#define GST_IMAGE_CAPTURE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IMAGE_CAPTURE_BIN,GstImageCaptureBin)) +#define GST_IMAGE_CAPTURE_BIN_CAST(obj) ((GstImageCaptureBin *) obj) +#define GST_IMAGE_CAPTURE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IMAGE_CAPTURE_BIN,GstImageCaptureBinClass)) +#define GST_IS_IMAGE_CAPTURE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IMAGE_CAPTURE_BIN)) +#define GST_IS_IMAGE_CAPTURE_BIN_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IMAGE_CAPTURE_BIN)) + +typedef struct _GstImageCaptureBin GstImageCaptureBin; +typedef struct _GstImageCaptureBinClass GstImageCaptureBinClass; + +struct _GstImageCaptureBin +{ + GstBin bin; + + GstPad *ghostpad; + GstElement *sink; + + /* props */ + gchar *location; + GstElement *encoder; + GstElement *user_encoder; + GstElement *muxer; + GstElement *user_muxer; + + gboolean elements_created; +}; + +struct _GstImageCaptureBinClass +{ + GstBinClass bin_class; +}; + +GType gst_image_capture_bin_get_type (void); +gboolean gst_image_capture_bin_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif Index: gst-plugins-good0.10/gst/camerabin2/gstplugin.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstplugin.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,46 @@ +/* GStreamer + * Copyright (C) <2010> Thiago Santos + * + * gstplugin.c: camerabin2 plugin registering + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstviewfinderbin.h" +#include "gstwrappercamerabinsrc.h" +#include "gstcamerabin2.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_viewfinder_bin_plugin_init (plugin)) + return FALSE; + if (!gst_wrapper_camera_bin_src_plugin_init (plugin)) + return FALSE; + if (!gst_camera_bin2_plugin_init (plugin)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "camerabin2", "camerabin2", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/gst/camerabin2/gstviewfinderbin.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstviewfinderbin.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,377 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/** + * SECTION:element-gstviewfinderbin + * + * The gstviewfinderbin element is a displaying element for camerabin2. + * + * + * Example launch line + * |[ + * gst-launch -v videotestsrc ! viewfinderbin + * ]| + * Feeds the viewfinderbin with video test data. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstviewfinderbin.h" +#include "camerabingeneral.h" +#include + +#include + +GST_DEBUG_CATEGORY_STATIC (gst_viewfinder_bin_debug); +#define GST_CAT_DEFAULT gst_viewfinder_bin_debug + +enum +{ + PROP_0, + PROP_VIDEO_SINK, + PROP_DISABLE_CONVERTERS +}; + +#define DEFAULT_DISABLE_CONVERTERS FALSE + +/* pad templates */ + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-yuv; video/x-raw-rgb") + ); + +/* class initialization */ + +GST_BOILERPLATE (GstViewfinderBin, gst_viewfinder_bin, GstBin, GST_TYPE_BIN); + +static void gst_viewfinder_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * spec); +static void gst_viewfinder_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * spec); + +static void +gst_viewfinder_bin_set_video_sink (GstViewfinderBin * vfbin, GstElement * sink); + + +/* Element class functions */ +static GstStateChangeReturn +gst_viewfinder_bin_change_state (GstElement * element, GstStateChange trans); + +static void +gst_viewfinder_bin_dispose (GObject * object) +{ + GstViewfinderBin *viewfinderbin = GST_VIEWFINDER_BIN_CAST (object); + + if (viewfinderbin->user_video_sink) { + gst_object_unref (viewfinderbin->user_video_sink); + viewfinderbin->user_video_sink = NULL; + } + + if (viewfinderbin->video_sink) { + gst_object_unref (viewfinderbin->video_sink); + viewfinderbin->video_sink = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) viewfinderbin); +} + +static void +gst_viewfinder_bin_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, + &sink_template); + + gst_element_class_set_details_simple (element_class, "Viewfinder Bin", + "Sink/Video", "Viewfinder Bin used in camerabin2", + "Thiago Santos "); +} + +static void +gst_viewfinder_bin_class_init (GstViewfinderBinClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *element_class; + + gobject_klass = (GObjectClass *) klass; + element_class = GST_ELEMENT_CLASS (klass); + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_viewfinder_bin_change_state); + + gobject_klass->dispose = gst_viewfinder_bin_dispose; + gobject_klass->set_property = gst_viewfinder_bin_set_property; + gobject_klass->get_property = gst_viewfinder_bin_get_property; + + g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK, + g_param_spec_object ("video-sink", "Video Sink", + "the video output element to use (NULL = default)", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_DISABLE_CONVERTERS, + g_param_spec_boolean ("disable-converters", "Disable conversion elements", + "If video converters should be disabled (must be set on NULL)", + DEFAULT_DISABLE_CONVERTERS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_viewfinder_bin_init (GstViewfinderBin * viewfinderbin, + GstViewfinderBinClass * viewfinderbin_class) +{ + GstPadTemplate *templ = gst_static_pad_template_get (&sink_template); + viewfinderbin->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", + templ); + gst_object_unref (templ); + gst_element_add_pad (GST_ELEMENT_CAST (viewfinderbin), + viewfinderbin->ghostpad); + + viewfinderbin->disable_converters = DEFAULT_DISABLE_CONVERTERS; +} + +static gboolean +gst_viewfinder_bin_create_elements (GstViewfinderBin * vfbin) +{ + GstElement *csp = NULL; + GstElement *videoscale = NULL; + GstPad *firstpad = NULL; + const gchar *missing_element_name; + gboolean newsink = FALSE; + gboolean updated_converters = FALSE; + + GST_DEBUG_OBJECT (vfbin, "Creating internal elements"); + + /* First check if we need to add/replace the internal sink */ + if (vfbin->video_sink) { + if (vfbin->user_video_sink && vfbin->video_sink != vfbin->user_video_sink) { + gst_bin_remove (GST_BIN_CAST (vfbin), vfbin->video_sink); + gst_object_unref (vfbin->video_sink); + vfbin->video_sink = NULL; + } + } + + if (!vfbin->video_sink) { + if (vfbin->user_video_sink) + vfbin->video_sink = gst_object_ref (vfbin->user_video_sink); + else { + vfbin->video_sink = gst_element_factory_make ("autovideosink", + "vfbin-sink"); + if (!vfbin->video_sink) { + missing_element_name = "autovideosink"; + goto missing_element; + } + } + + gst_bin_add (GST_BIN_CAST (vfbin), gst_object_ref (vfbin->video_sink)); + newsink = TRUE; + } + + /* check if we want add/remove the conversion elements */ + if (vfbin->elements_created && vfbin->disable_converters) { + /* remove the elements, user doesn't want them */ + + gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), NULL); + csp = gst_bin_get_by_name (GST_BIN_CAST (vfbin), "vfbin-csp"); + videoscale = gst_bin_get_by_name (GST_BIN_CAST (vfbin), "vfbin-videoscale"); + + gst_bin_remove (GST_BIN_CAST (vfbin), csp); + gst_bin_remove (GST_BIN_CAST (vfbin), videoscale); + + gst_object_unref (csp); + gst_object_unref (videoscale); + + updated_converters = TRUE; + } else if (!vfbin->elements_created && !vfbin->disable_converters) { + gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), NULL); + + /* add the elements, user wants them */ + csp = gst_element_factory_make ("ffmpegcolorspace", "vfbin-csp"); + if (!csp) { + missing_element_name = "ffmpegcolorspace"; + goto missing_element; + } + gst_bin_add (GST_BIN_CAST (vfbin), csp); + + videoscale = gst_element_factory_make ("videoscale", "vfbin->videoscale"); + if (!videoscale) { + missing_element_name = "videoscale"; + goto missing_element; + } + gst_bin_add (GST_BIN_CAST (vfbin), videoscale); + + gst_element_link_pads_full (csp, "src", videoscale, "sink", + GST_PAD_LINK_CHECK_NOTHING); + + vfbin->elements_created = TRUE; + GST_DEBUG_OBJECT (vfbin, "Elements succesfully created and linked"); + + updated_converters = TRUE; + } + /* otherwise, just leave it as is */ + + /* if sink was replaced -> link it to the internal converters */ + if (newsink && !vfbin->disable_converters) { + gboolean unref = FALSE; + if (!videoscale) { + videoscale = gst_bin_get_by_name (GST_BIN_CAST (vfbin), + "vfbin-videscale"); + unref = TRUE; + } + + if (!gst_element_link_pads_full (videoscale, "src", vfbin->video_sink, + "sink", GST_PAD_LINK_CHECK_CAPS)) { + GST_ELEMENT_ERROR (vfbin, CORE, NEGOTIATION, (NULL), + ("linking videoscale and viewfindersink failed")); + } + + if (unref) + gst_object_unref (videoscale); + videoscale = NULL; + } + + /* Check if we need a new ghostpad target */ + if (updated_converters || (newsink && vfbin->disable_converters)) { + if (vfbin->disable_converters) { + firstpad = gst_element_get_static_pad (vfbin->video_sink, "sink"); + } else { + /* csp should always exist at this point */ + firstpad = gst_element_get_static_pad (csp, "sink"); + } + } + + /* need to change the ghostpad target if firstpad is set */ + if (firstpad) { + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (vfbin->ghostpad), firstpad)) + goto error; + gst_object_unref (firstpad); + firstpad = NULL; + } + + return TRUE; + +missing_element: + gst_element_post_message (GST_ELEMENT_CAST (vfbin), + gst_missing_element_message_new (GST_ELEMENT_CAST (vfbin), + missing_element_name)); + GST_ELEMENT_ERROR (vfbin, CORE, MISSING_PLUGIN, + (_("Missing element '%s' - check your GStreamer installation."), + missing_element_name), (NULL)); + goto error; + +error: + GST_WARNING_OBJECT (vfbin, "Creating internal elements failed"); + if (firstpad) + gst_object_unref (firstpad); + return FALSE; +} + +static GstStateChangeReturn +gst_viewfinder_bin_change_state (GstElement * element, GstStateChange trans) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (element); + + switch (trans) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_viewfinder_bin_create_elements (vfbin)) { + return GST_STATE_CHANGE_FAILURE; + } + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + switch (trans) { + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static void +gst_viewfinder_bin_set_video_sink (GstViewfinderBin * vfbin, GstElement * sink) +{ + GST_INFO_OBJECT (vfbin, "Setting video sink to %" GST_PTR_FORMAT, sink); + + if (vfbin->user_video_sink != sink) { + if (vfbin->user_video_sink) { + gst_object_unref (vfbin->user_video_sink); + } + vfbin->user_video_sink = sink; + if (sink) + gst_object_ref (sink); + } +} + +static void +gst_viewfinder_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + gst_viewfinder_bin_set_video_sink (vfbin, g_value_get_object (value)); + break; + case PROP_DISABLE_CONVERTERS: + vfbin->disable_converters = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_viewfinder_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstViewfinderBin *vfbin = GST_VIEWFINDER_BIN_CAST (object); + + switch (prop_id) { + case PROP_VIDEO_SINK: + g_value_set_object (value, vfbin->video_sink); + break; + case PROP_DISABLE_CONVERTERS: + g_value_set_boolean (value, vfbin->disable_converters); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_viewfinder_bin_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_viewfinder_bin_debug, "viewfinderbin", 0, + "ViewFinderBin"); + return gst_element_register (plugin, "viewfinderbin", GST_RANK_NONE, + gst_viewfinder_bin_get_type ()); +} Index: gst-plugins-good0.10/gst/camerabin2/gstviewfinderbin.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstviewfinderbin.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,60 @@ +/* GStreamer + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _GST_VIEWFINDER_BIN_H_ +#define _GST_VIEWFINDER_BIN_H_ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VIEWFINDER_BIN (gst_viewfinder_bin_get_type()) +#define GST_VIEWFINDER_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIEWFINDER_BIN,GstViewfinderBin)) +#define GST_VIEWFINDER_BIN_CAST(obj) ((GstViewfinderBin *) obj) +#define GST_VIEWFINDER_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIEWFINDER_BIN,GstViewfinderBinClass)) +#define GST_IS_VIEWFINDER_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIEWFINDER_BIN)) +#define GST_IS_VIEWFINDER_BIN_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIEWFINDER_BIN)) + +typedef struct _GstViewfinderBin GstViewfinderBin; +typedef struct _GstViewfinderBinClass GstViewfinderBinClass; + +struct _GstViewfinderBin +{ + GstBin bin; + + GstPad *ghostpad; + + GstElement *video_sink; + GstElement *user_video_sink; + + gboolean elements_created; + + gboolean disable_converters; +}; + +struct _GstViewfinderBinClass +{ + GstBinClass bin_class; +}; + +GType gst_viewfinder_bin_get_type (void); +gboolean gst_viewfinder_bin_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif Index: gst-plugins-good0.10/gst/camerabin2/gstwrappercamerabinsrc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstwrappercamerabinsrc.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,1155 @@ +/* + * GStreamer + * Copyright (C) 2010 Texas Instruments, Inc + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/** + * SECTION:element-wrappercamerabinsrc + * + * A camera bin src element that wraps a default video source with a single + * pad into the 3pad model that camerabin2 expects. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "gstwrappercamerabinsrc.h" +#include "camerabingeneral.h" + +enum +{ + PROP_0, + PROP_VIDEO_SRC, + PROP_VIDEO_SRC_FILTER +}; + +GST_DEBUG_CATEGORY (wrapper_camera_bin_src_debug); +#define GST_CAT_DEFAULT wrapper_camera_bin_src_debug + +GST_BOILERPLATE (GstWrapperCameraBinSrc, gst_wrapper_camera_bin_src, + GstBaseCameraSrc, GST_TYPE_BASE_CAMERA_SRC); + +static void set_capsfilter_caps (GstWrapperCameraBinSrc * self, + GstCaps * new_caps); + +static void +gst_wrapper_camera_bin_src_dispose (GObject * object) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); + + if (self->app_vid_src) { + gst_object_unref (self->app_vid_src); + self->app_vid_src = NULL; + } + if (self->app_vid_filter) { + gst_object_unref (self->app_vid_filter); + self->app_vid_filter = NULL; + } + gst_caps_replace (&self->image_capture_caps, NULL); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_wrapper_camera_bin_src_finalize (GstWrapperCameraBinSrc * self) +{ + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (self)); +} + +static void +gst_wrapper_camera_bin_src_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); + + switch (prop_id) { + case PROP_VIDEO_SRC: + if (GST_STATE (self) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (self, CORE, FAILED, + ("camerasrc must be in NULL state when setting the video source element"), + (NULL)); + } else { + if (self->app_vid_src) + gst_object_unref (self->app_vid_src); + self->app_vid_src = g_value_get_object (value); + if (self->app_vid_src) + gst_object_ref (self->app_vid_src); + } + break; + case PROP_VIDEO_SRC_FILTER: + if (GST_STATE (self) != GST_STATE_NULL) { + GST_ELEMENT_ERROR (self, CORE, FAILED, + ("camerasrc must be in NULL state when setting the video source filter element"), + (NULL)); + } else { + if (self->app_vid_filter) + gst_object_unref (self->app_vid_filter); + self->app_vid_filter = g_value_get_object (value); + if (self->app_vid_filter) + gst_object_ref (self->app_vid_filter); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static void +gst_wrapper_camera_bin_src_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (object); + + switch (prop_id) { + case PROP_VIDEO_SRC: + if (self->src_vid_src) + g_value_set_object (value, self->src_vid_src); + else + g_value_set_object (value, self->app_vid_src); + break; + case PROP_VIDEO_SRC_FILTER: + if (self->video_filter) + g_value_set_object (value, self->video_filter); + else + g_value_set_object (value, self->app_vid_filter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static void +gst_wrapper_camera_bin_reset_video_src_caps (GstWrapperCameraBinSrc * self, + GstCaps * caps) +{ + GstClock *clock; + gint64 base_time; + + GST_DEBUG_OBJECT (self, "Resetting src caps to %" GST_PTR_FORMAT, caps); + if (self->src_vid_src) { + clock = gst_element_get_clock (self->src_vid_src); + base_time = gst_element_get_base_time (self->src_vid_src); + + gst_element_set_state (self->src_vid_src, GST_STATE_READY); + set_capsfilter_caps (self, caps); + + self->drop_newseg = TRUE; + + GST_DEBUG_OBJECT (self, "Bringing source up"); + gst_element_sync_state_with_parent (self->src_vid_src); + + if (clock) { + gst_element_set_clock (self->src_vid_src, clock); + gst_element_set_base_time (self->src_vid_src, base_time); + + if (GST_IS_BIN (self->src_vid_src)) { + GstIterator *it = + gst_bin_iterate_elements (GST_BIN (self->src_vid_src)); + gpointer item = NULL; + gboolean done = FALSE; + while (!done) { + switch (gst_iterator_next (it, &item)) { + case GST_ITERATOR_OK: + gst_element_set_base_time (GST_ELEMENT (item), base_time); + gst_object_unref (item); + break; + case GST_ITERATOR_RESYNC: + gst_iterator_resync (it); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_DONE: + done = TRUE; + break; + } + } + gst_iterator_free (it); + } + + gst_object_unref (clock); + } + } +} + +/** + * gst_wrapper_camera_bin_src_imgsrc_probe: + * + * Buffer probe called before sending each buffer to image queue. + */ +static gboolean +gst_wrapper_camera_bin_src_imgsrc_probe (GstPad * pad, GstBuffer * buffer, + gpointer data) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); + GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC (data); + gboolean ret = FALSE; + + GST_LOG_OBJECT (self, "Image probe, mode %d, capture count %d", + camerasrc->mode, self->image_capture_count); + + g_mutex_lock (camerasrc->capturing_mutex); + if (self->image_capture_count > 0) { + ret = TRUE; + self->image_capture_count--; + + /* post preview */ + /* TODO This can likely be optimized if the viewfinder caps is the same as + * the preview caps, avoiding another scaling of the same buffer. */ + GST_DEBUG_OBJECT (self, "Posting preview for image"); + gst_base_camera_src_post_preview (camerasrc, buffer); + + if (self->image_capture_count == 0) { + gst_base_camera_src_finish_capture (camerasrc); + } + } + g_mutex_unlock (camerasrc->capturing_mutex); + return ret; +} + +/** + * gst_wrapper_camera_bin_src_vidsrc_probe: + * + * Buffer probe called before sending each buffer to image queue. + */ +static gboolean +gst_wrapper_camera_bin_src_vidsrc_probe (GstPad * pad, GstBuffer * buffer, + gpointer data) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); + GstBaseCameraSrc *camerasrc = GST_BASE_CAMERA_SRC_CAST (self); + gboolean ret = FALSE; + + GST_LOG_OBJECT (self, "Video probe, mode %d, capture status %d", + camerasrc->mode, self->video_rec_status); + + /* TODO do we want to lock for every buffer? */ + /* + * Note that we can use gst_pad_push_event here because we are a buffer + * probe. + */ + /* TODO shouldn't access this directly */ + g_mutex_lock (camerasrc->capturing_mutex); + if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) { + /* NOP */ + } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) { + GstClockTime ts; + + GST_DEBUG_OBJECT (self, "Starting video recording"); + self->video_rec_status = GST_VIDEO_RECORDING_STATUS_RUNNING; + + ts = GST_BUFFER_TIMESTAMP (buffer); + if (!GST_CLOCK_TIME_IS_VALID (ts)) + ts = 0; + gst_pad_push_event (self->vidsrc, gst_event_new_new_segment (FALSE, 1.0, + GST_FORMAT_TIME, ts, -1, 0)); + + /* post preview */ + GST_DEBUG_OBJECT (self, "Posting preview for video"); + gst_base_camera_src_post_preview (camerasrc, buffer); + + ret = TRUE; + } else if (self->video_rec_status == GST_VIDEO_RECORDING_STATUS_FINISHING) { + /* send eos */ + GST_DEBUG_OBJECT (self, "Finishing video recording, pushing eos"); + gst_pad_push_event (pad, gst_event_new_eos ()); + self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; + gst_base_camera_src_finish_capture (camerasrc); + } else { + ret = TRUE; + } + g_mutex_unlock (camerasrc->capturing_mutex); + return ret; +} + +static gboolean +gst_wrapper_camera_bin_src_event (GstPad * pad, GstEvent * event) +{ + GstWrapperCameraBinSrc *src = + GST_WRAPPER_CAMERA_BIN_SRC (GST_PAD_PARENT (pad)); + const GstStructure *structure; + + structure = gst_event_get_structure (event); + if (structure && gst_structure_has_name (structure, "renegotiate")) { + GST_DEBUG_OBJECT (src, "Received renegotiate on pad %s", + GST_PAD_NAME (pad)); + + if (pad == src->imgsrc) { + src->image_renegotiate = TRUE; + } else if (pad == src->vidsrc) { + src->video_renegotiate = TRUE; + } + } + + return src->srcpad_event_func (pad, event); +} + +static gboolean +gst_wrapper_camera_src_src_event_probe (GstPad * pad, GstEvent * evt, + gpointer udata) +{ + gboolean ret = TRUE; + GstWrapperCameraBinSrc *self = udata; + + switch (GST_EVENT_TYPE (evt)) { + case GST_EVENT_EOS: + /* drop */ + ret = FALSE; + break; + case GST_EVENT_NEWSEGMENT: + if (self->drop_newseg) { + ret = FALSE; + self->drop_newseg = FALSE; + } + break; + default: + break; + } + return ret; +} + +static void +gst_wrapper_camera_bin_src_caps_cb (GObject * gobject, GParamSpec * pspec, + gpointer user_data) +{ + GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (user_data); + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (user_data); + GstPad *src_caps_src_pad; + GstCaps *caps = NULL; + GstStructure *in_st = NULL; + + /* get the new caps that were set on the capsfilter that configures the + * source */ + src_caps_src_pad = gst_element_get_static_pad (self->src_filter, "src"); + caps = gst_pad_get_caps_reffed (src_caps_src_pad); + gst_object_unref (src_caps_src_pad); + GST_DEBUG_OBJECT (self, "src-filter caps changed to %s", + gst_caps_to_string (caps)); + + if (gst_caps_get_size (caps)) { + in_st = gst_caps_get_structure (caps, 0); + if (in_st) { + gst_structure_get_int (in_st, "width", &bcamsrc->width); + gst_structure_get_int (in_st, "height", &bcamsrc->height); + + GST_DEBUG_OBJECT (self, "Source dimensions now: %dx%d", bcamsrc->width, + bcamsrc->height); + } + } + + /* Update zoom */ + gst_base_camera_src_setup_zoom (bcamsrc); + + /* Update post-zoom capsfilter */ + if (self->src_zoom_filter) + g_object_set (G_OBJECT (self->src_zoom_filter), "caps", caps, NULL); + + /* drop our ref on the caps */ + gst_caps_unref (caps); +}; + +static void +gst_wrapper_camera_bin_src_max_zoom_cb (GObject * self, GParamSpec * pspec, + gpointer user_data) +{ + GstBaseCameraSrc *bcamsrc = (GstBaseCameraSrc *) user_data; + + g_object_get (self, "max-zoom", &bcamsrc->max_zoom, NULL); + g_object_notify (G_OBJECT (bcamsrc), "max-zoom"); +} + + +/** + * gst_wrapper_camera_bin_src_construct_pipeline: + * @bcamsrc: camerasrc object + * + * This function creates and links the elements of the camerasrc bin + * videosrc ! cspconv ! srcfilter ! cspconv ! capsfilter ! crop ! scale ! \ + * capsfilter ! tee name=t + * t. ! ... (viewfinder pad) + * t. ! output-selector name=outsel + * outsel. ! (image pad) + * outsel. ! (video pad) + * + * Returns: TRUE, if elements were successfully created, FALSE otherwise + */ +static gboolean +gst_wrapper_camera_bin_src_construct_pipeline (GstBaseCameraSrc * bcamsrc) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); + GstBin *cbin = GST_BIN (bcamsrc); + GstElement *tee; + GstElement *filter_csp; + GstElement *src_csp; + GstElement *capsfilter; + gboolean ret = FALSE; + GstPad *vf_pad; + GstPad *tee_capture_pad; + GstPad *src_caps_src_pad; + + if (!self->elements_created) { + + GST_DEBUG_OBJECT (self, "constructing pipeline"); + + /* Add application set or default video src element */ + if (!(self->src_vid_src = gst_camerabin_setup_default_element (cbin, + self->app_vid_src, "autovideosrc", DEFAULT_VIDEOSRC, + "camerasrc-real-src"))) { + self->src_vid_src = NULL; + goto done; + } else { + if (!gst_camerabin_add_element (cbin, self->src_vid_src)) { + goto done; + } + } + /* we lost the reference */ + self->app_vid_src = NULL; + + /* we listen for changes to max-zoom in the video src so that + * we can proxy them to the basecamerasrc property */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (bcamsrc), "max-zoom")) { + g_signal_connect (G_OBJECT (self->src_vid_src), "notify::max-zoom", + (GCallback) gst_wrapper_camera_bin_src_max_zoom_cb, bcamsrc); + } + + /* add a buffer probe to the src elemento to drop EOS from READY->NULL */ + { + GstPad *pad; + pad = gst_element_get_static_pad (self->src_vid_src, "src"); + + self->src_event_probe_id = gst_pad_add_event_probe (pad, + (GCallback) gst_wrapper_camera_src_src_event_probe, self); + gst_object_unref (pad); + } + + if (!gst_camerabin_create_and_add_element (cbin, "ffmpegcolorspace", + "src-colorspace")) + goto done; + + if (!(self->src_filter = + gst_camerabin_create_and_add_element (cbin, "capsfilter", + "src-capsfilter"))) + goto done; + + /* attach to notify::caps on the first capsfilter and use a callback + * to recalculate the zoom properties when these caps change and to + * propagate the caps to the second capsfilter */ + src_caps_src_pad = gst_element_get_static_pad (self->src_filter, "src"); + g_signal_connect (src_caps_src_pad, "notify::caps", + G_CALLBACK (gst_wrapper_camera_bin_src_caps_cb), self); + gst_object_unref (src_caps_src_pad); + + if (!(self->src_zoom_crop = + gst_camerabin_create_and_add_element (cbin, "videocrop", + "zoom-crop"))) + goto done; + if (!(self->src_zoom_scale = + gst_camerabin_create_and_add_element (cbin, "videoscale", + "zoom-scale"))) + goto done; + if (!(self->src_zoom_filter = + gst_camerabin_create_and_add_element (cbin, "capsfilter", + "zoom-capsfilter"))) + goto done; + + if (!(tee = + gst_camerabin_create_and_add_element (cbin, "tee", + "camerasrc-tee"))) + goto done; + + /* viewfinder pad */ + vf_pad = gst_element_get_request_pad (tee, "src%d"); + g_object_set (tee, "alloc-pad", vf_pad, NULL); + gst_ghost_pad_set_target (GST_GHOST_PAD (self->vfsrc), vf_pad); + gst_object_unref (vf_pad); + + /* image/video pad from tee */ + tee_capture_pad = gst_element_get_request_pad (tee, "src%d"); + + self->output_selector = + gst_element_factory_make ("output-selector", "outsel"); + g_object_set (self->output_selector, "pad-negotiation-mode", 0, NULL); + gst_bin_add (GST_BIN (self), self->output_selector); + { + GstPad *pad = gst_element_get_static_pad (self->output_selector, "sink"); + + /* check return TODO */ + gst_pad_link (tee_capture_pad, pad); + gst_object_unref (pad); + } + gst_object_unref (tee_capture_pad); + + /* Create the 2 output pads for video and image */ + self->outsel_vidpad = + gst_element_get_request_pad (self->output_selector, "src%d"); + self->outsel_imgpad = + gst_element_get_request_pad (self->output_selector, "src%d"); + + g_assert (self->outsel_vidpad != NULL); + g_assert (self->outsel_imgpad != NULL); + + gst_pad_add_buffer_probe (self->outsel_imgpad, + G_CALLBACK (gst_wrapper_camera_bin_src_imgsrc_probe), self); + gst_pad_add_buffer_probe (self->outsel_vidpad, + G_CALLBACK (gst_wrapper_camera_bin_src_vidsrc_probe), self); + gst_ghost_pad_set_target (GST_GHOST_PAD (self->imgsrc), + self->outsel_imgpad); + gst_ghost_pad_set_target (GST_GHOST_PAD (self->vidsrc), + self->outsel_vidpad); + + if (bcamsrc->mode == MODE_IMAGE) { + g_object_set (self->output_selector, "active-pad", self->outsel_imgpad, + NULL); + } else { + g_object_set (self->output_selector, "active-pad", self->outsel_vidpad, + NULL); + } + + + + gst_pad_set_active (self->vfsrc, TRUE); + gst_pad_set_active (self->imgsrc, TRUE); /* XXX ??? */ + gst_pad_set_active (self->vidsrc, TRUE); /* XXX ??? */ + } + + /* Do this even if pipeline is constructed */ + + if (self->video_filter) { + /* check if we need to replace the current one */ + if (self->video_filter != self->app_vid_filter) { + gst_bin_remove (cbin, self->video_filter); + gst_object_unref (self->video_filter); + self->video_filter = NULL; + filter_csp = gst_bin_get_by_name (cbin, "filter-colorspace"); + gst_bin_remove (cbin, filter_csp); + gst_object_unref (filter_csp); + filter_csp = NULL; + } + } + + if (!self->video_filter) { + if (self->app_vid_filter) { + self->video_filter = gst_object_ref (self->app_vid_filter); + filter_csp = gst_element_factory_make ("ffmpegcolorspace", + "filter-colorspace"); + gst_bin_add_many (cbin, self->video_filter, filter_csp, NULL); + src_csp = gst_bin_get_by_name (cbin, "src-colorspace"); + capsfilter = gst_bin_get_by_name (cbin, "src-capsfilter"); + if (gst_pad_is_linked (gst_element_get_static_pad (src_csp, "src"))) + gst_element_unlink (src_csp, capsfilter); + if (!gst_element_link_many (src_csp, self->video_filter, filter_csp, + capsfilter, NULL)) + goto done; + } + } + ret = TRUE; + self->elements_created = TRUE; +done: + return ret; +} + +static gboolean +copy_missing_fields (GQuark field_id, const GValue * value, gpointer user_data) +{ + GstStructure *st = (GstStructure *) user_data; + const GValue *val = gst_structure_id_get_value (st, field_id); + + if (G_UNLIKELY (val == NULL)) { + gst_structure_id_set_value (st, field_id, value); + } + + return TRUE; +} + +/** + * adapt_image_capture: + * @self: camerasrc object + * @in_caps: caps object that describes incoming image format + * + * Adjust capsfilters and crop according image capture caps if necessary. + * The captured image format from video source might be different from + * what application requested, so we can try to fix that in camerabin. + * + */ +static void +adapt_image_capture (GstWrapperCameraBinSrc * self, GstCaps * in_caps) +{ + GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (self); + GstStructure *in_st, *new_st, *req_st; + gint in_width = 0, in_height = 0, req_width = 0, req_height = 0, crop = 0; + gdouble ratio_w, ratio_h; + GstCaps *filter_caps = NULL; + + GST_LOG_OBJECT (self, "in caps: %" GST_PTR_FORMAT, in_caps); + GST_LOG_OBJECT (self, "requested caps: %" GST_PTR_FORMAT, + self->image_capture_caps); + + in_st = gst_caps_get_structure (in_caps, 0); + gst_structure_get_int (in_st, "width", &in_width); + gst_structure_get_int (in_st, "height", &in_height); + + req_st = gst_caps_get_structure (self->image_capture_caps, 0); + gst_structure_get_int (req_st, "width", &req_width); + gst_structure_get_int (req_st, "height", &req_height); + + GST_INFO_OBJECT (self, "we requested %dx%d, and got %dx%d", req_width, + req_height, in_width, in_height); + + new_st = gst_structure_copy (req_st); + /* If new fields have been added, we need to copy them */ + gst_structure_foreach (in_st, copy_missing_fields, new_st); + + gst_structure_set (new_st, "width", G_TYPE_INT, in_width, "height", + G_TYPE_INT, in_height, NULL); + + GST_LOG_OBJECT (self, "new image capture caps: %" GST_PTR_FORMAT, new_st); + + /* Crop if requested aspect ratio differs from incoming frame aspect ratio */ + if (self->src_zoom_crop) { + + ratio_w = (gdouble) in_width / req_width; + ratio_h = (gdouble) in_height / req_height; + + if (ratio_w < ratio_h) { + crop = in_height - (req_height * ratio_w); + self->base_crop_top = crop / 2; + self->base_crop_bottom = crop / 2; + } else { + crop = in_width - (req_width * ratio_h); + self->base_crop_left = crop / 2; + self->base_crop_right += crop / 2; + } + + GST_INFO_OBJECT (self, + "setting base crop: left:%d, right:%d, top:%d, bottom:%d", + self->base_crop_left, self->base_crop_right, self->base_crop_top, + self->base_crop_bottom); + g_object_set (G_OBJECT (self->src_zoom_crop), + "top", self->base_crop_top, + "bottom", self->base_crop_bottom, + "left", self->base_crop_left, "right", self->base_crop_right, NULL); + } + + /* Update capsfilters */ + if (self->image_capture_caps) { + gst_caps_unref (self->image_capture_caps); + } + self->image_capture_caps = gst_caps_new_full (new_st, NULL); + set_capsfilter_caps (self, self->image_capture_caps); + + /* Adjust the capsfilter before crop and videoscale elements if necessary */ + if (in_width == bcamsrc->width && in_height == bcamsrc->height) { + GST_DEBUG_OBJECT (self, "no adaptation with resolution needed"); + } else { + GST_DEBUG_OBJECT (self, + "changing %" GST_PTR_FORMAT " from %dx%d to %dx%d", self->src_filter, + bcamsrc->width, bcamsrc->height, in_width, in_height); + /* Apply the width and height to filter caps */ + g_object_get (G_OBJECT (self->src_filter), "caps", &filter_caps, NULL); + filter_caps = gst_caps_make_writable (filter_caps); + gst_caps_set_simple (filter_caps, "width", G_TYPE_INT, in_width, "height", + G_TYPE_INT, in_height, NULL); + g_object_set (G_OBJECT (self->src_filter), "caps", filter_caps, NULL); + gst_caps_unref (filter_caps); + } +} + +/** + * img_capture_prepared: + * @data: camerasrc object + * @caps: caps describing the prepared image format + * + * Callback which is called after image capture has been prepared. + */ +static void +img_capture_prepared (gpointer data, GstCaps * caps) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (data); + + GST_INFO_OBJECT (self, "image capture prepared"); + + /* It is possible we are about to get something else that we requested */ + if (!gst_caps_is_equal (self->image_capture_caps, caps)) { + adapt_image_capture (self, caps); + } else { + set_capsfilter_caps (self, self->image_capture_caps); + } +} + +/** + * + */ +static gboolean +start_image_capture (GstWrapperCameraBinSrc * self) +{ + GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (self); + GstPhotography *photography = + (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc), + GST_TYPE_PHOTOGRAPHY); + gboolean ret = FALSE; + GstCaps *caps; + + GST_DEBUG_OBJECT (self, "Starting image capture"); + + if (self->image_renegotiate) { + /* clean capsfilter caps so they don't interfere here */ + g_object_set (self->src_filter, "caps", NULL, NULL); + if (self->src_zoom_filter) + g_object_set (self->src_zoom_filter, "caps", NULL, NULL); + + caps = gst_pad_get_allowed_caps (self->imgsrc); + + gst_caps_replace (&self->image_capture_caps, caps); + gst_caps_unref (caps); + + self->image_renegotiate = FALSE; + } + + if (photography) { + GST_DEBUG_OBJECT (self, "prepare image capture caps %" GST_PTR_FORMAT, + self->image_capture_caps); + ret = gst_photography_prepare_for_capture (photography, + (GstPhotoCapturePrepared) img_capture_prepared, + self->image_capture_caps, self); + } else { + g_mutex_unlock (bcamsrc->capturing_mutex); + gst_wrapper_camera_bin_reset_video_src_caps (self, + self->image_capture_caps); + g_mutex_lock (bcamsrc->capturing_mutex); + ret = TRUE; + } + + return ret; +} + +static gboolean +gst_wrapper_camera_bin_src_set_mode (GstBaseCameraSrc * bcamsrc, + GstCameraBinMode mode) +{ + GstPhotography *photography = + (GstPhotography *) gst_bin_get_by_interface (GST_BIN_CAST (bcamsrc), + GST_TYPE_PHOTOGRAPHY); + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); + + if (self->output_selector) { + if (mode == MODE_IMAGE) { + self->image_renegotiate = TRUE; + g_object_set (self->output_selector, "active-pad", self->outsel_imgpad, + NULL); + } else { + self->video_renegotiate = TRUE; + g_object_set (self->output_selector, "active-pad", self->outsel_vidpad, + NULL); + } + } + self->mode = mode; + + if (photography) { + if (g_object_class_find_property (G_OBJECT_GET_CLASS (photography), + "capture-mode")) { + g_object_set (G_OBJECT (photography), "capture-mode", mode, NULL); + } + } else { + gst_wrapper_camera_bin_reset_video_src_caps (self, NULL); + } + + return TRUE; +} + +static gboolean +set_videosrc_zoom (GstWrapperCameraBinSrc * self, gfloat zoom) +{ + gboolean ret = FALSE; + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (self->src_vid_src), + "zoom")) { + g_object_set (G_OBJECT (self->src_vid_src), "zoom", zoom, NULL); + ret = TRUE; + } + return ret; +} + +static gboolean +set_element_zoom (GstWrapperCameraBinSrc * self, gfloat zoom) +{ + gboolean ret = FALSE; + GstBaseCameraSrc *bcamsrc = GST_BASE_CAMERA_SRC (self); + gint w2_crop = 0, h2_crop = 0; + GstPad *pad_zoom_sink = NULL; + gint left = self->base_crop_left; + gint right = self->base_crop_right; + gint top = self->base_crop_top; + gint bottom = self->base_crop_bottom; + + if (self->src_zoom_crop) { + /* Update capsfilters to apply the zoom */ + GST_INFO_OBJECT (self, "zoom: %f, orig size: %dx%d", zoom, + bcamsrc->width, bcamsrc->height); + + if (zoom != ZOOM_1X) { + w2_crop = (bcamsrc->width - (gint) (bcamsrc->width * ZOOM_1X / zoom)) / 2; + h2_crop = + (bcamsrc->height - (gint) (bcamsrc->height * ZOOM_1X / zoom)) / 2; + + left += w2_crop; + right += w2_crop; + top += h2_crop; + bottom += h2_crop; + + /* force number of pixels cropped from left to be even, to avoid slow code + * path on videoscale */ + left &= 0xFFFE; + } + + pad_zoom_sink = gst_element_get_static_pad (self->src_zoom_crop, "sink"); + + GST_INFO_OBJECT (self, + "sw cropping: left:%d, right:%d, top:%d, bottom:%d", left, right, top, + bottom); + + GST_PAD_STREAM_LOCK (pad_zoom_sink); + g_object_set (self->src_zoom_crop, "left", left, "right", right, "top", + top, "bottom", bottom, NULL); + GST_PAD_STREAM_UNLOCK (pad_zoom_sink); + gst_object_unref (pad_zoom_sink); + ret = TRUE; + } + return ret; +} + +static void +gst_wrapper_camera_bin_src_set_zoom (GstBaseCameraSrc * bcamsrc, gfloat zoom) +{ + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (bcamsrc); + + GST_INFO_OBJECT (self, "setting zoom %f", zoom); + + if (set_videosrc_zoom (self, zoom)) { + set_element_zoom (self, ZOOM_1X); + GST_INFO_OBJECT (self, "zoom set using videosrc"); + } else if (set_element_zoom (self, zoom)) { + GST_INFO_OBJECT (self, "zoom set using gst elements"); + } else { + GST_INFO_OBJECT (self, "setting zoom failed"); + } +} + +/** + * update_aspect_filter: + * @self: camerasrc object + * @new_caps: new caps of next buffers arriving to view finder sink element + * + * Updates aspect ratio capsfilter to maintain aspect ratio, if we need to + * scale frames for showing them in view finder. + */ +static void +update_aspect_filter (GstWrapperCameraBinSrc * self, GstCaps * new_caps) +{ + // XXX why not instead add a preserve-aspect-ratio property to videoscale? +#if 0 + if (camera->flags & GST_CAMERABIN_FLAG_VIEWFINDER_SCALE) { + GstCaps *sink_caps, *ar_caps; + GstStructure *st; + gint in_w = 0, in_h = 0, sink_w = 0, sink_h = 0, target_w = 0, target_h = 0; + gdouble ratio_w, ratio_h; + GstPad *sink_pad; + const GValue *range; + + sink_pad = gst_element_get_static_pad (camera->view_sink, "sink"); + + if (sink_pad) { + sink_caps = gst_pad_get_caps (sink_pad); + gst_object_unref (sink_pad); + if (sink_caps) { + if (!gst_caps_is_any (sink_caps)) { + GST_DEBUG_OBJECT (camera, "sink element caps %" GST_PTR_FORMAT, + sink_caps); + /* Get maximum resolution that view finder sink accepts */ + st = gst_caps_get_structure (sink_caps, 0); + if (gst_structure_has_field_typed (st, "width", GST_TYPE_INT_RANGE)) { + range = gst_structure_get_value (st, "width"); + sink_w = gst_value_get_int_range_max (range); + } + if (gst_structure_has_field_typed (st, "height", GST_TYPE_INT_RANGE)) { + range = gst_structure_get_value (st, "height"); + sink_h = gst_value_get_int_range_max (range); + } + GST_DEBUG_OBJECT (camera, "sink element accepts max %dx%d", sink_w, + sink_h); + + /* Get incoming frames' resolution */ + if (sink_h && sink_w) { + st = gst_caps_get_structure (new_caps, 0); + gst_structure_get_int (st, "width", &in_w); + gst_structure_get_int (st, "height", &in_h); + GST_DEBUG_OBJECT (camera, "new caps with %dx%d", in_w, in_h); + } + } + gst_caps_unref (sink_caps); + } + } + + /* If we get bigger frames than view finder sink accepts, then we scale. + If we scale we need to adjust aspect ratio capsfilter caps in order + to maintain aspect ratio while scaling. */ + if (in_w && in_h && (in_w > sink_w || in_h > sink_h)) { + ratio_w = (gdouble) sink_w / in_w; + ratio_h = (gdouble) sink_h / in_h; + + if (ratio_w < ratio_h) { + target_w = sink_w; + target_h = (gint) (ratio_w * in_h); + } else { + target_w = (gint) (ratio_h * in_w); + target_h = sink_h; + } + + GST_DEBUG_OBJECT (camera, "setting %dx%d filter to maintain aspect ratio", + target_w, target_h); + ar_caps = gst_caps_copy (new_caps); + gst_caps_set_simple (ar_caps, "width", G_TYPE_INT, target_w, "height", + G_TYPE_INT, target_h, NULL); + } else { + GST_DEBUG_OBJECT (camera, "no scaling"); + ar_caps = new_caps; + } + + GST_DEBUG_OBJECT (camera, "aspect ratio filter caps %" GST_PTR_FORMAT, + ar_caps); + g_object_set (G_OBJECT (camera->aspect_filter), "caps", ar_caps, NULL); + if (ar_caps != new_caps) + gst_caps_unref (ar_caps); + } +#endif +} + + +/** + * set_capsfilter_caps: + * @self: camerasrc object + * @new_caps: pointer to caps object to set + * + * Set given caps to camerabin capsfilters. + */ +static void +set_capsfilter_caps (GstWrapperCameraBinSrc * self, GstCaps * new_caps) +{ + GST_INFO_OBJECT (self, "new_caps:%" GST_PTR_FORMAT, new_caps); + + /* Update zoom */ + gst_base_camera_src_setup_zoom (GST_BASE_CAMERA_SRC (self)); + + /* Update capsfilters */ + g_object_set (G_OBJECT (self->src_filter), "caps", new_caps, NULL); + if (self->src_zoom_filter) + g_object_set (G_OBJECT (self->src_zoom_filter), "caps", new_caps, NULL); + update_aspect_filter (self, new_caps); + GST_INFO_OBJECT (self, "updated"); +} + +static gboolean +gst_wrapper_camera_bin_src_start_capture (GstBaseCameraSrc * camerasrc) +{ + GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc); + + /* TODO should we access this directly? Maybe a macro is better? */ + if (src->mode == MODE_IMAGE) { + start_image_capture (src); + src->image_capture_count = 1; + } else if (src->mode == MODE_VIDEO) { + GstCaps *caps = NULL; + + if (src->video_renegotiate) { + g_mutex_unlock (camerasrc->capturing_mutex); + gst_wrapper_camera_bin_reset_video_src_caps (src, NULL); + g_mutex_lock (camerasrc->capturing_mutex); + + /* clean capsfilter caps so they don't interfere here */ + g_object_set (src->src_filter, "caps", NULL, NULL); + if (src->src_zoom_filter) + g_object_set (src->src_zoom_filter, "caps", NULL, NULL); + + GST_DEBUG_OBJECT (src, "Getting allowed videosrc caps"); + caps = gst_pad_get_allowed_caps (src->vidsrc); + GST_DEBUG_OBJECT (src, "Video src caps %" GST_PTR_FORMAT, caps); + + src->video_renegotiate = FALSE; + g_mutex_unlock (camerasrc->capturing_mutex); + gst_wrapper_camera_bin_reset_video_src_caps (src, caps); + g_mutex_lock (camerasrc->capturing_mutex); + gst_caps_unref (caps); + } + if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_DONE) { + src->video_rec_status = GST_VIDEO_RECORDING_STATUS_STARTING; + } + } else { + g_assert_not_reached (); + return FALSE; + } + return TRUE; +} + +static void +gst_wrapper_camera_bin_src_stop_capture (GstBaseCameraSrc * camerasrc) +{ + GstWrapperCameraBinSrc *src = GST_WRAPPER_CAMERA_BIN_SRC (camerasrc); + + /* TODO shoud we access this directly? Maybe a macro is better? */ + if (src->mode == MODE_VIDEO) { + if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_STARTING) { + GST_DEBUG_OBJECT (src, "Aborting, had not started recording"); + src->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; + + } else if (src->video_rec_status == GST_VIDEO_RECORDING_STATUS_RUNNING) { + GST_DEBUG_OBJECT (src, "Marking video recording as finishing"); + src->video_rec_status = GST_VIDEO_RECORDING_STATUS_FINISHING; + } + } else { + src->image_capture_count = 0; + } +} + +static GstStateChangeReturn +gst_wrapper_camera_bin_src_change_state (GstElement * element, + GstStateChange trans) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstWrapperCameraBinSrc *self = GST_WRAPPER_CAMERA_BIN_SRC (element); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + if (ret == GST_STATE_CHANGE_FAILURE) + goto end; + + switch (trans) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + self->video_renegotiate = TRUE; + self->image_renegotiate = TRUE; + self->drop_newseg = FALSE; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + case GST_STATE_CHANGE_NULL_TO_READY: + break; + default: + break; + } + +end: + return ret; +} + +static void +gst_wrapper_camera_bin_src_base_init (gpointer g_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + GST_DEBUG_CATEGORY_INIT (wrapper_camera_bin_src_debug, "wrappercamerabinsrc", + 0, "V4l2 camera src"); + + gst_element_class_set_details_simple (gstelement_class, + "V4l2 camera src element for camerabin", "Source/Video", + "V4l2 camera src element for camerabin", "Rob Clark "); +} + +static void +gst_wrapper_camera_bin_src_class_init (GstWrapperCameraBinSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseCameraSrcClass *gstbasecamerasrc_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + gstbasecamerasrc_class = GST_BASE_CAMERA_SRC_CLASS (klass); + + gobject_class->dispose = gst_wrapper_camera_bin_src_dispose; + gobject_class->finalize = + (GObjectFinalizeFunc) gst_wrapper_camera_bin_src_finalize; + gobject_class->set_property = gst_wrapper_camera_bin_src_set_property; + gobject_class->get_property = gst_wrapper_camera_bin_src_get_property; + + /* g_object_class_install_property .... */ + g_object_class_install_property (gobject_class, PROP_VIDEO_SRC, + g_param_spec_object ("video-source", "Video source", + "The video source element to be used", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_VIDEO_SRC_FILTER, + g_param_spec_object ("video-source-filter", "Video source filter", + "Optional video source filter element", + GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = gst_wrapper_camera_bin_src_change_state; + + gstbasecamerasrc_class->construct_pipeline = + gst_wrapper_camera_bin_src_construct_pipeline; + gstbasecamerasrc_class->set_zoom = gst_wrapper_camera_bin_src_set_zoom; + gstbasecamerasrc_class->set_mode = gst_wrapper_camera_bin_src_set_mode; + gstbasecamerasrc_class->start_capture = + gst_wrapper_camera_bin_src_start_capture; + gstbasecamerasrc_class->stop_capture = + gst_wrapper_camera_bin_src_stop_capture; +} + +static void +gst_wrapper_camera_bin_src_init (GstWrapperCameraBinSrc * self, + GstWrapperCameraBinSrcClass * klass) +{ + self->vfsrc = + gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIEWFINDER_PAD_NAME, + GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (self), self->vfsrc); + + self->imgsrc = + gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_IMAGE_PAD_NAME, + GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (self), self->imgsrc); + + self->vidsrc = + gst_ghost_pad_new_no_target (GST_BASE_CAMERA_SRC_VIDEO_PAD_NAME, + GST_PAD_SRC); + gst_element_add_pad (GST_ELEMENT (self), self->vidsrc); + + self->srcpad_event_func = GST_PAD_EVENTFUNC (self->vfsrc); + + gst_pad_set_event_function (self->imgsrc, gst_wrapper_camera_bin_src_event); + gst_pad_set_event_function (self->vidsrc, gst_wrapper_camera_bin_src_event); + gst_pad_set_event_function (self->vfsrc, gst_wrapper_camera_bin_src_event); + + /* TODO where are variables reset? */ + self->image_capture_count = 0; + self->video_rec_status = GST_VIDEO_RECORDING_STATUS_DONE; + self->video_renegotiate = TRUE; + self->image_renegotiate = TRUE; + self->mode = GST_BASE_CAMERA_SRC_CAST (self)->mode; + self->app_vid_filter = NULL; +} + +gboolean +gst_wrapper_camera_bin_src_plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "wrappercamerabinsrc", GST_RANK_NONE, + gst_wrapper_camera_bin_src_get_type ()); +} Index: gst-plugins-good0.10/gst/camerabin2/gstwrappercamerabinsrc.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/camerabin2/gstwrappercamerabinsrc.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,131 @@ +/* + * GStreamer + * Copyright (C) 2010 Texas Instruments, Inc + * Copyright (C) 2010 Thiago Santos + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_WRAPPER_CAMERA_BIN_SRC_H__ +#define __GST_WRAPPER_CAMERA_BIN_SRC_H__ + +#include +#include +#include +#include "camerabingeneral.h" + +G_BEGIN_DECLS +#define GST_TYPE_WRAPPER_CAMERA_BIN_SRC \ + (gst_wrapper_camera_bin_src_get_type()) +#define GST_WRAPPER_CAMERA_BIN_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WRAPPER_CAMERA_BIN_SRC,GstWrapperCameraBinSrc)) +#define GST_WRAPPER_CAMERA_BIN_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_WRAPPER_CAMERA_BIN_SRC,GstWrapperCameraBinSrcClass)) +#define GST_IS_WRAPPER_CAMERA_BIN_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_WRAPPER_CAMERA_BIN_SRC)) +#define GST_IS_WRAPPER_CAMERA_BIN_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_WRAPPER_CAMERA_BIN_SRC)) + GType gst_wrapper_camera_bin_src_get_type (void); + +typedef struct _GstWrapperCameraBinSrc GstWrapperCameraBinSrc; +typedef struct _GstWrapperCameraBinSrcClass GstWrapperCameraBinSrcClass; + +enum GstVideoRecordingStatus { + GST_VIDEO_RECORDING_STATUS_DONE, + GST_VIDEO_RECORDING_STATUS_STARTING, + GST_VIDEO_RECORDING_STATUS_RUNNING, + GST_VIDEO_RECORDING_STATUS_FINISHING +}; + + +/** + * GstWrapperCameraBinSrc: + * + */ +struct _GstWrapperCameraBinSrc +{ + GstBaseCameraSrc parent; + + GstCameraBinMode mode; + + GstPad *vfsrc; + GstPad *imgsrc; + GstPad *vidsrc; + + /* video recording controls */ + gint video_rec_status; + + /* image capture controls */ + gint image_capture_count; + + /* source elements */ + GstElement *src_vid_src; + GstElement *video_filter; + GstElement *src_filter; + GstElement *src_zoom_crop; + GstElement *src_zoom_scale; + GstElement *src_zoom_filter; + GstElement *output_selector; + + gboolean elements_created; + + guint src_event_probe_id; + + GstPad *outsel_imgpad; + GstPad *outsel_vidpad; + + GstPadEventFunction srcpad_event_func; + + /* For changing caps without losing timestamps */ + gboolean drop_newseg; + + /* Application configurable elements */ + GstElement *app_vid_src; + GstElement *app_vid_filter; + + /* Caps that videosrc supports */ + GstCaps *allowed_caps; + + /* Optional base crop for frames. Used to crop frames e.g. + due to wrong aspect ratio, before the crop related to zooming. */ + gint base_crop_top; + gint base_crop_bottom; + gint base_crop_left; + gint base_crop_right; + + /* Caps applied to capsfilters when in view finder mode */ + GstCaps *view_finder_caps; + + /* Caps applied to capsfilters when taking still image */ + GstCaps *image_capture_caps; + gboolean image_renegotiate; + gboolean video_renegotiate; +}; + + +/** + * GstWrapperCameraBinSrcClass: + * + */ +struct _GstWrapperCameraBinSrcClass +{ + GstBaseCameraSrcClass parent; +}; + +gboolean gst_wrapper_camera_bin_src_plugin_init (GstPlugin * plugin); + +#endif /* __GST_WRAPPER_CAMERA_BIN_SRC_H__ */ Index: gst-plugins-good0.10/gst/jpegformat/Makefile.am =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/Makefile.am 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,27 @@ +plugin_LTLIBRARIES = libgstjpegformat.la + +libgstjpegformat_la_SOURCES = gstjpegformat.c gstjpegparse.c gstjifmux.c +libgstjpegformat_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstjpegformat_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstinterfaces-@GST_MAJORMINOR@ \ + -lgsttag-@GST_MAJORMINOR@ $(GST_LIBS) $(GST_BASE_LIBS) +libgstjpegformat_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstjpegformat_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstjpegformat.h gstjpegparse.h gstjifmux.h + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstjpegformat -:SHARED libgstjpegformat \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstjpegformat_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstjpegformat_la_CFLAGS) \ + -:LDFLAGS $(libgstjpegformat_la_LDFLAGS) \ + $(libgstjpegformat_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ Index: gst-plugins-good0.10/gst/jpegformat/gstjifmux.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjifmux.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,783 @@ +/* GStreamer + * + * jifmux: JPEG interchange format muxer + * + * Copyright (C) 2010 Stefan Kost + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-jifmux + * @short_description: JPEG interchange format writer + * + * Writes a JPEG image as JPEG/EXIF or JPEG/JFIF including various metadata. The + * jpeg image received on the sink pad should be minimal (e.g. should not + * contain metadata already). + * + * + * Example launch line + * |[ + * gst-launch -v videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=... + * ]| + * The above pipeline renders a frame, encodes to jpeg, adds metadata and writes + * it to disk. + * + */ +/* +jpeg interchange format: +file header : SOI, APPn{JFIF,EXIF,...} +frame header: DQT, SOF +scan header : {DAC,DHT},DRI,SOS + +file trailer: EOI + +tests: +gst-launch videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=test1.jpeg +gst-launch videotestsrc num-buffers=1 ! jpegenc ! taginject tags="comment=\"test image\"" ! jifmux ! filesink location=test2.jpeg +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "gstjifmux.h" + +static GstStaticPadTemplate gst_jif_mux_src_pad_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg") + ); + +static GstStaticPadTemplate gst_jif_mux_sink_pad_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg") + ); + +GST_DEBUG_CATEGORY_STATIC (jif_mux_debug); +#define GST_CAT_DEFAULT jif_mux_debug + +#define COLORSPACE_UNKNOWN (0 << 0) +#define COLORSPACE_GRAYSCALE (1 << 0) +#define COLORSPACE_YUV (1 << 1) +#define COLORSPACE_RGB (1 << 2) +#define COLORSPACE_CMYK (1 << 3) +#define COLORSPACE_YCCK (1 << 4) + +typedef struct _GstJifMuxMarker +{ + guint8 marker; + guint16 size; + + const guint8 *data; + gboolean owned; +} GstJifMuxMarker; + +struct _GstJifMuxPrivate +{ + GstPad *srcpad; + + /* list of GstJifMuxMarker */ + GList *markers; + guint scan_size; + const guint8 *scan_data; +}; + +static void gst_jif_mux_finalize (GObject * object); + +static void gst_jif_mux_reset (GstJifMux * self); +static gboolean gst_jif_mux_sink_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_jif_mux_sink_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_jif_mux_chain (GstPad * pad, GstBuffer * buffer); +static GstStateChangeReturn gst_jif_mux_change_state (GstElement * element, + GstStateChange transition); + + +static void +gst_jif_type_init (GType type) +{ + static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL }; + static const GInterfaceInfo tag_xmp_writer_info = { NULL, NULL, NULL }; + + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info); + g_type_add_interface_static (type, GST_TYPE_TAG_XMP_WRITER, + &tag_xmp_writer_info); + + GST_DEBUG_CATEGORY_INIT (jif_mux_debug, "jifmux", 0, + "JPEG interchange format muxer"); +} + +GST_BOILERPLATE_FULL (GstJifMux, gst_jif_mux, GstElement, GST_TYPE_ELEMENT, + gst_jif_type_init); + +static void +gst_jif_mux_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, + &gst_jif_mux_src_pad_template); + gst_element_class_add_static_pad_template (element_class, + &gst_jif_mux_sink_pad_template); + gst_element_class_set_details_simple (element_class, + "JPEG stream muxer", + "Video/Formatter", + "Remuxes JPEG images with markers and tags", + "Arnout Vandecappelle (Essensium/Mind) "); +} + +static void +gst_jif_mux_class_init (GstJifMuxClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + g_type_class_add_private (gobject_class, sizeof (GstJifMuxPrivate)); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_jif_mux_change_state); + + gobject_class->finalize = gst_jif_mux_finalize; +} + +static void +gst_jif_mux_init (GstJifMux * self, GstJifMuxClass * g_class) +{ + GstPad *sinkpad; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_JIF_MUX, + GstJifMuxPrivate); + + /* create the sink and src pads */ + sinkpad = gst_pad_new_from_static_template (&gst_jif_mux_sink_pad_template, + "sink"); + gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_jif_mux_chain)); + gst_pad_set_setcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jif_mux_sink_setcaps)); + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jif_mux_sink_event)); + gst_element_add_pad (GST_ELEMENT (self), sinkpad); + + self->priv->srcpad = + gst_pad_new_from_static_template (&gst_jif_mux_src_pad_template, "src"); + gst_element_add_pad (GST_ELEMENT (self), self->priv->srcpad); +} + +static void +gst_jif_mux_finalize (GObject * object) +{ + GstJifMux *self = GST_JIF_MUX (object); + + gst_jif_mux_reset (self); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_jif_mux_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstJifMux *self = GST_JIF_MUX_CAST (GST_PAD_PARENT (pad)); + GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar *variant; + + /* should be {combined (default), EXIF, JFIF} */ + if ((variant = gst_structure_get_string (s, "variant")) != NULL) { + GST_INFO_OBJECT (pad, "muxing to '%s'", variant); + /* FIXME: do we want to switch it like this or use a gobject property ? */ + } + + return gst_pad_set_caps (self->priv->srcpad, caps); +} + +static gboolean +gst_jif_mux_sink_event (GstPad * pad, GstEvent * event) +{ + GstJifMux *self = GST_JIF_MUX (GST_PAD_PARENT (pad)); + gboolean ret; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG:{ + GstTagList *list; + GstTagSetter *setter = GST_TAG_SETTER (self); + const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter); + + gst_event_parse_tag (event, &list); + + gst_tag_setter_merge_tags (setter, list, mode); + break; + } + default: + break; + } + ret = gst_pad_event_default (pad, event); + return ret; +} + +static void +gst_jif_mux_marker_free (GstJifMuxMarker * m) +{ + if (m->owned) + g_free ((gpointer) m->data); + + g_slice_free (GstJifMuxMarker, m); +} + +static void +gst_jif_mux_reset (GstJifMux * self) +{ + GList *node; + GstJifMuxMarker *m; + + for (node = self->priv->markers; node; node = g_list_next (node)) { + m = (GstJifMuxMarker *) node->data; + gst_jif_mux_marker_free (m); + } + g_list_free (self->priv->markers); + self->priv->markers = NULL; +} + +static GstJifMuxMarker * +gst_jif_mux_new_marker (guint8 marker, guint16 size, const guint8 * data, + gboolean owned) +{ + GstJifMuxMarker *m = g_slice_new (GstJifMuxMarker); + + m->marker = marker; + m->size = size; + m->data = data; + m->owned = owned; + + return m; +} + +static gboolean +gst_jif_mux_parse_image (GstJifMux * self, GstBuffer * buf) +{ + GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buf); + GstJifMuxMarker *m; + guint8 marker = 0; + guint16 size = 0; + const guint8 *data = NULL; + + GST_LOG_OBJECT (self, "Received buffer of size: %u", GST_BUFFER_SIZE (buf)); + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + + while (marker == 0xff) { + if (!gst_byte_reader_skip (&reader, 1)) + goto error; + + if (!gst_byte_reader_get_uint8 (&reader, &marker)) + goto error; + + switch (marker) { + case RST0: + case RST1: + case RST2: + case RST3: + case RST4: + case RST5: + case RST6: + case RST7: + case SOI: + GST_DEBUG_OBJECT (self, "marker = %x", marker); + m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE); + self->priv->markers = g_list_prepend (self->priv->markers, m); + break; + case EOI: + GST_DEBUG_OBJECT (self, "marker = %x", marker); + m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE); + self->priv->markers = g_list_prepend (self->priv->markers, m); + goto done; + break; + default: + if (!gst_byte_reader_get_uint16_be (&reader, &size)) + goto error; + if (!gst_byte_reader_get_data (&reader, size - 2, &data)) + goto error; + + m = gst_jif_mux_new_marker (marker, size - 2, data, FALSE); + self->priv->markers = g_list_prepend (self->priv->markers, m); + + GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", marker, size); + break; + } + + if (marker == SOS) { + gint eoi_pos = -1; + gint i; + + /* search the last 5 bytes for the EOI marker */ + g_assert (GST_BUFFER_SIZE (buf) >= 5); + for (i = 5; i >= 2; i--) { + if (GST_BUFFER_DATA (buf)[GST_BUFFER_SIZE (buf) - i] == 0xFF && + GST_BUFFER_DATA (buf)[GST_BUFFER_SIZE (buf) - i + 1] == EOI) { + eoi_pos = GST_BUFFER_SIZE (buf) - i; + break; + } + } + + if (eoi_pos == -1) { + GST_WARNING_OBJECT (self, "Couldn't find an EOI marker"); + eoi_pos = GST_BUFFER_SIZE (buf); + } + + /* remaining size except EOI is scan data */ + self->priv->scan_size = eoi_pos - gst_byte_reader_get_pos (&reader); + if (!gst_byte_reader_get_data (&reader, self->priv->scan_size, + &self->priv->scan_data)) + goto error; + + GST_DEBUG_OBJECT (self, "scan data, size = %u", self->priv->scan_size); + } + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + } + GST_INFO_OBJECT (self, "done parsing at 0x%x / 0x%x", + gst_byte_reader_get_pos (&reader), GST_BUFFER_SIZE (buf)); + +done: + self->priv->markers = g_list_reverse (self->priv->markers); + return TRUE; + +error: + GST_WARNING_OBJECT (self, + "Error parsing image header (need more that %u bytes available)", + gst_byte_reader_get_remaining (&reader)); + return FALSE; +} + +static gboolean +gst_jif_mux_mangle_markers (GstJifMux * self) +{ + gboolean modified = FALSE; + GstTagList *tags = NULL; + gboolean cleanup_tags; + GstJifMuxMarker *m; + GList *node, *file_hdr = NULL, *frame_hdr = NULL, *scan_hdr = NULL; + GList *app0_jfif = NULL, *app1_exif = NULL, *app1_xmp = NULL, *com = NULL; + GstBuffer *xmp_data; + gchar *str = NULL; + gint colorspace = COLORSPACE_UNKNOWN; + + /* update the APP markers + * - put any JFIF APP0 first + * - the Exif APP1 next, + * - the XMP APP1 next, + * - the PSIR APP13 next, + * - followed by all other marker segments + */ + + /* find some reference points where we insert before/after */ + file_hdr = self->priv->markers; + for (node = self->priv->markers; node; node = g_list_next (node)) { + m = (GstJifMuxMarker *) node->data; + + switch (m->marker) { + case APP0: + if (m->size > 5 && !memcmp (m->data, "JFIF\0", 5)) { + GST_DEBUG_OBJECT (self, "found APP0 JFIF"); + colorspace |= COLORSPACE_GRAYSCALE | COLORSPACE_YUV; + if (!app0_jfif) + app0_jfif = node; + } + break; + case APP1: + if (m->size > 6 && (!memcmp (m->data, "EXIF\0\0", 6) || + !memcmp (m->data, "Exif\0\0", 6))) { + GST_DEBUG_OBJECT (self, "found APP1 EXIF"); + if (!app1_exif) + app1_exif = node; + } else if (m->size > 29 + && !memcmp (m->data, "http://ns.adobe.com/xap/1.0/\0", 29)) { + GST_INFO_OBJECT (self, "found APP1 XMP, will be replaced"); + if (!app1_xmp) + app1_xmp = node; + } + break; + case APP14: + /* check if this contains RGB */ + /* + * This marker should have: + * - 'Adobe\0' + * - 2 bytes DCTEncodeVersion + * - 2 bytes flags0 + * - 2 bytes flags1 + * - 1 byte ColorTransform + * - 0 means unknown (RGB or CMYK) + * - 1 YCbCr + * - 2 YCCK + */ + + if ((m->size >= 14) + && (strncmp ((gchar *) m->data, "Adobe\0", 6) == 0)) { + switch (m->data[11]) { + case 0: + colorspace |= COLORSPACE_RGB | COLORSPACE_CMYK; + break; + case 1: + colorspace |= COLORSPACE_YUV; + break; + case 2: + colorspace |= COLORSPACE_YCCK; + break; + default: + break; + } + } + + break; + case COM: + GST_INFO_OBJECT (self, "found COM, will be replaced"); + if (!com) + com = node; + break; + case DQT: + case SOF0: + case SOF1: + case SOF2: + case SOF3: + case SOF5: + case SOF6: + case SOF7: + case SOF9: + case SOF10: + case SOF11: + case SOF13: + case SOF14: + case SOF15: + if (!frame_hdr) + frame_hdr = node; + break; + case DAC: + case DHT: + case DRI: + case SOS: + if (!scan_hdr) + scan_hdr = node; + break; + } + } + + /* if we want combined or JFIF */ + /* check if we don't have JFIF APP0 */ + if (!app0_jfif && (colorspace & (COLORSPACE_GRAYSCALE | COLORSPACE_YUV))) { + /* build jfif header */ + static const struct + { + gchar id[5]; + guint8 ver[2]; + guint8 du; + guint8 xd[2], yd[2]; + guint8 tw, th; + } jfif_data = { + "JFIF", { + 1, 2}, 0, { + 0, 1}, /* FIXME: check pixel-aspect from caps */ + { + 0, 1}, 0, 0}; + m = gst_jif_mux_new_marker (APP0, sizeof (jfif_data), + (const guint8 *) &jfif_data, FALSE); + /* insert into self->markers list */ + self->priv->markers = g_list_insert (self->priv->markers, m, 1); + app0_jfif = g_list_nth (self->priv->markers, 1); + } + /* else */ + /* remove JFIF if exists */ + + /* Existing exif tags will be removed and our own will be added */ + if (!tags) { + tags = (GstTagList *) gst_tag_setter_get_tag_list (GST_TAG_SETTER (self)); + cleanup_tags = FALSE; + } + if (!tags) { + tags = gst_tag_list_new (); + cleanup_tags = TRUE; + } + + GST_DEBUG_OBJECT (self, "Tags to be serialized %" GST_PTR_FORMAT, tags); + + /* FIXME: not happy with those + * - else where we would use VIDEO_CODEC = "Jpeg" + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, + GST_TAG_VIDEO_CODEC, "image/jpeg", NULL); + */ + + /* Add EXIF */ + { + GstBuffer *exif_data; + guint8 *data; + GstJifMuxMarker *m; + GList *pos; + + /* insert into self->markers list */ + exif_data = gst_tag_list_to_exif_buffer_with_tiff_header (tags); + if (exif_data && + GST_BUFFER_SIZE (exif_data) + 8 >= G_GUINT64_CONSTANT (65536)) { + GST_WARNING_OBJECT (self, "Exif tags data size exceed maximum size"); + gst_buffer_unref (exif_data); + exif_data = NULL; + } + if (exif_data) { + data = g_malloc0 (GST_BUFFER_SIZE (exif_data) + 6); + memcpy (data, "Exif", 4); + memcpy (data + 6, GST_BUFFER_DATA (exif_data), + GST_BUFFER_SIZE (exif_data)); + m = gst_jif_mux_new_marker (APP1, GST_BUFFER_SIZE (exif_data) + 6, data, + TRUE); + gst_buffer_unref (exif_data); + + if (app1_exif) { + gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_exif->data); + app1_exif->data = m; + } else { + pos = file_hdr; + if (app0_jfif) + pos = app0_jfif; + pos = g_list_next (pos); + + self->priv->markers = + g_list_insert_before (self->priv->markers, pos, m); + if (pos) { + app1_exif = g_list_previous (pos); + } else { + app1_exif = g_list_last (self->priv->markers); + } + } + modified = TRUE; + } + } + + /* add xmp */ + xmp_data = + gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (self), + tags, FALSE); + if (xmp_data) { + guint8 *data, *xmp = GST_BUFFER_DATA (xmp_data); + guint size = GST_BUFFER_SIZE (xmp_data); + GList *pos; + + data = g_malloc (size + 29); + memcpy (data, "http://ns.adobe.com/xap/1.0/\0", 29); + memcpy (&data[29], xmp, size); + m = gst_jif_mux_new_marker (APP1, size + 29, data, TRUE); + + /* + * Replace the old xmp marker and not add a new one. + * There shouldn't be a xmp packet in the input, but it is better + * to be safe than add another one and end up with 2 packets. + */ + if (app1_xmp) { + gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_xmp->data); + app1_xmp->data = m; + } else { + + pos = file_hdr; + if (app1_exif) + pos = app1_exif; + else if (app0_jfif) + pos = app0_jfif; + pos = g_list_next (pos); + + self->priv->markers = g_list_insert_before (self->priv->markers, pos, m); + + } + gst_buffer_unref (xmp_data); + modified = TRUE; + } + + /* add jpeg comment from any of those */ + (void) (gst_tag_list_get_string (tags, GST_TAG_COMMENT, &str) || + gst_tag_list_get_string (tags, GST_TAG_DESCRIPTION, &str) || + gst_tag_list_get_string (tags, GST_TAG_TITLE, &str)); + + if (str) { + GST_DEBUG_OBJECT (self, "set COM marker to '%s'", str); + /* insert new marker into self->markers list */ + m = gst_jif_mux_new_marker (COM, strlen (str) + 1, (const guint8 *) str, + TRUE); + /* FIXME: if we have one already, replace */ + /* this should go before SOS, maybe at the end of file-header */ + self->priv->markers = g_list_insert_before (self->priv->markers, + frame_hdr, m); + + modified = TRUE; + } + + if (tags && cleanup_tags) + gst_tag_list_free (tags); + return modified; +} + +static GstFlowReturn +gst_jif_mux_recombine_image (GstJifMux * self, GstBuffer ** new_buf, + GstBuffer * old_buf) +{ + GstBuffer *buf; + GstByteWriter *writer; + GstFlowReturn fret; + GstJifMuxMarker *m; + GList *node; + guint size = self->priv->scan_size; + gboolean writer_status = TRUE; + + /* iterate list and collect size */ + for (node = self->priv->markers; node; node = g_list_next (node)) { + m = (GstJifMuxMarker *) node->data; + + /* some markers like e.g. SOI are empty */ + if (m->size) { + size += 2 + m->size; + } + /* 0xff */ + size += 2; + } + GST_INFO_OBJECT (self, "old size: %u, new size: %u", + GST_BUFFER_SIZE (old_buf), size); + + /* allocate new buffer */ + fret = gst_pad_alloc_buffer_and_set_caps (self->priv->srcpad, + GST_BUFFER_OFFSET (old_buf), size, GST_PAD_CAPS (self->priv->srcpad), + &buf); + if (fret != GST_FLOW_OK) + goto no_buffer; + + /* copy buffer metadata */ + gst_buffer_copy_metadata (buf, old_buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS); + + /* memcopy markers */ + writer = gst_byte_writer_new_with_buffer (buf, TRUE); + for (node = self->priv->markers; node && writer_status; + node = g_list_next (node)) { + m = (GstJifMuxMarker *) node->data; + + writer_status &= gst_byte_writer_put_uint8 (writer, 0xff); + writer_status &= gst_byte_writer_put_uint8 (writer, m->marker); + + GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", m->marker, m->size + 2); + + if (m->size) { + writer_status &= gst_byte_writer_put_uint16_be (writer, m->size + 2); + writer_status &= gst_byte_writer_put_data (writer, m->data, m->size); + } + + if (m->marker == SOS) { + GST_DEBUG_OBJECT (self, "scan data, size = %u", self->priv->scan_size); + writer_status &= + gst_byte_writer_put_data (writer, self->priv->scan_data, + self->priv->scan_size); + } + } + gst_byte_writer_free (writer); + + if (!writer_status) { + GST_WARNING_OBJECT (self, "Failed to write to buffer, calculated size " + "was probably too short"); + g_assert_not_reached (); + } + + *new_buf = buf; + return GST_FLOW_OK; + +no_buffer: + GST_WARNING_OBJECT (self, "failed to allocate output buffer, flow_ret = %s", + gst_flow_get_name (fret)); + return fret; +} + +static GstFlowReturn +gst_jif_mux_chain (GstPad * pad, GstBuffer * buf) +{ + GstJifMux *self = GST_JIF_MUX (GST_PAD_PARENT (pad)); + GstFlowReturn fret = GST_FLOW_OK; + + if (GST_BUFFER_CAPS (buf) == NULL) { + GST_WARNING_OBJECT (self, "Rejecting buffer without caps"); + gst_buffer_unref (buf); + return GST_FLOW_NOT_NEGOTIATED; + } + + GST_MEMDUMP ("jpeg beg", GST_BUFFER_DATA (buf), 64); + GST_MEMDUMP ("jpeg end", GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf) - 64, + 64); + + /* we should have received a whole picture from SOI to EOI + * build a list of markers */ + if (gst_jif_mux_parse_image (self, buf)) { + /* modify marker list */ + if (gst_jif_mux_mangle_markers (self)) { + /* the list was changed, remux */ + GstBuffer *old = buf; + fret = gst_jif_mux_recombine_image (self, &buf, old); + gst_buffer_unref (old); + } + } + + /* free the marker list */ + gst_jif_mux_reset (self); + + if (fret == GST_FLOW_OK) { + fret = gst_pad_push (self->priv->srcpad, buf); + } + return fret; +} + +static GstStateChangeReturn +gst_jif_mux_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstJifMux *self = GST_JIF_MUX_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_tag_setter_reset_tags (GST_TAG_SETTER (self)); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} Index: gst-plugins-good0.10/gst/jpegformat/gstjifmux.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjifmux.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,61 @@ +/* GStreamer + * + * jifmux: JPEG interchange format muxer + * + * Copyright (C) 2010 Stefan Kost + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_JFIF_MUX_H__ +#define __GST_JFIF_MUX_H__ + +#include + +#include "gstjpegformat.h" + +G_BEGIN_DECLS + +#define GST_TYPE_JIF_MUX \ + (gst_jif_mux_get_type()) +#define GST_JIF_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JIF_MUX,GstJifMux)) +#define GST_JIF_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JIF_MUX,GstJifMuxClass)) +#define GST_IS_JIF_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JIF_MUX)) +#define GST_IS_JIF_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JIF_MUX)) +#define GST_JIF_MUX_CAST(obj) ((GstJifMux *) (obj)) + +typedef struct _GstJifMux GstJifMux; +typedef struct _GstJifMuxPrivate GstJifMuxPrivate; +typedef struct _GstJifMuxClass GstJifMuxClass; + +struct _GstJifMux { + GstElement element; + GstJifMuxPrivate *priv; +}; + +struct _GstJifMuxClass { + GstElementClass parent_class; +}; + +GType gst_jif_mux_get_type (void); + +G_END_DECLS + +#endif /* __GST_JFIF_MUX_H__ */ Index: gst-plugins-good0.10/gst/jpegformat/gstjpegformat.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjpegformat.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,47 @@ +/* GStreamer + * + * jpegformat: a plugin for JPEG Interchange Format + * + * Copyright (C) <2010> Stefan Kost + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstjpegparse.h" +#include "gstjifmux.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "jpegparse", GST_RANK_NONE, + GST_TYPE_JPEG_PARSE)) + return FALSE; + if (!gst_element_register (plugin, "jifmux", GST_RANK_SECONDARY, + GST_TYPE_JIF_MUX)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "jpegformat", + "JPEG interchange format plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) Index: gst-plugins-good0.10/gst/jpegformat/gstjpegformat.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjpegformat.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,92 @@ +/* GStreamer + * + * jpegformat: a plugin for JPEG Interchange Format + * + * Copyright (C) <2010> Stefan Kost + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_JPEG_FORMAT_H__ +#define __GST_JPEG_FORMAT_H__ + +G_BEGIN_DECLS + +/* + * JPEG Markers + */ + +/* Start Of Frame markers, non-differential, Huffman coding */ +#define SOF0 0xc0 /* Baseline DCT */ +#define SOF1 0xc1 /* Extended sequential DCT */ +#define SOF2 0xc2 /* Progressive DCT */ +#define SOF3 0xc3 /* Lossless */ + +/* Start Of Frame markers, differential, Huffman coding */ +#define SOF5 0xc5 +#define SOF6 0xc6 +#define SOF7 0xc7 + +/* Start Of Frame markers, non-differential, arithmetic coding */ +#define JPG 0xc8 /* Reserved */ +#define SOF9 0xc9 +#define SOF10 0xca +#define SOF11 0xcb + +/* Start Of Frame markers, differential, arithmetic coding */ +#define SOF13 0xcd +#define SOF14 0xce +#define SOF15 0xcf + +/* Restart interval termination */ +#define RST0 0xd0 /* Restart ... */ +#define RST1 0xd1 +#define RST2 0xd2 +#define RST3 0xd3 +#define RST4 0xd4 +#define RST5 0xd5 +#define RST6 0xd6 +#define RST7 0xd7 + +#define SOI 0xd8 /* Start of image */ +#define EOI 0xd9 /* End Of Image */ +#define SOS 0xda /* Start Of Scan */ + +#define DHT 0xc4 /* Huffman Table(s) */ +#define DAC 0xcc /* Algorithmic Coding Table */ +#define DQT 0xdb /* Quantisation Table(s) */ +#define DNL 0xdc /* Number of lines */ +#define DRI 0xdd /* Restart Interval */ +#define DHP 0xde /* Hierarchical progression */ +#define EXP 0xdf + +#define APP0 0xe0 /* Application marker */ +#define APP1 0xe1 +#define APP2 0xe2 +#define APP13 0xed +#define APP14 0xee +#define APP15 0xef + +#define JPG0 0xf0 /* Reserved ... */ +#define JPG13 0xfd +#define COM 0xfe /* Comment */ + +#define TEM 0x01 + +G_END_DECLS + +#endif /* __GST_JPEG_FORMAT_H__ */ Index: gst-plugins-good0.10/gst/jpegformat/gstjpegparse.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjpegparse.c 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,1057 @@ +/* GStreamer + * + * jpegparse: a parser for JPEG streams + * + * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) + * Víctor Manuel Jáquez Leal + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-jpegparse + * @short_description: JPEG parser + * + * Parses a JPEG stream into JPEG images. It looks for EOI boundaries to + * split a continuous stream into single-frame buffers. Also reads the + * image header searching for image properties such as width and height + * among others. Jpegparse can also extract metadata (e.g. xmp). + * + * + * Example launch line + * |[ + * gst-launch -v souphttpsrc location=... ! jpegparse ! matroskamux ! filesink location=... + * ]| + * The above pipeline fetches a motion JPEG stream from an IP camera over + * HTTP and stores it in a matroska file. + * + */ +/* FIXME: output plain JFIF APP marker only. This provides best code reuse. + * JPEG decoders would not need to handle this part anymore. Also when remuxing + * (... ! jpegparse ! ... ! jifmux ! ...) metadata consolidation would be + * easier. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "gstjpegparse.h" + +static GstStaticPadTemplate gst_jpeg_parse_src_pad_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg, " + "format = (fourcc) { I420, Y41B, UYVY, YV12 }, " + "width = (int) [ 0, MAX ]," + "height = (int) [ 0, MAX ], " + "interlaced = (boolean) { true, false }, " + "framerate = (fraction) [ 0/1, MAX ], " "parsed = (boolean) true") + ); + +static GstStaticPadTemplate gst_jpeg_parse_sink_pad_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg, parsed = (boolean) false") + ); + +GST_DEBUG_CATEGORY_STATIC (jpeg_parse_debug); +#define GST_CAT_DEFAULT jpeg_parse_debug + +struct _GstJpegParsePrivate +{ + GstPad *srcpad; + + GstAdapter *adapter; + guint last_offset; + guint last_entropy_len; + gboolean last_resync; + + /* negotiated state */ + gint caps_width, caps_height; + gint caps_framerate_numerator; + gint caps_framerate_denominator; + + /* a new segment arrived */ + gboolean new_segment; + + /* the parsed frame size */ + guint16 width, height; + + /* TRUE if the image is interlaced */ + gboolean interlaced; + + /* fourcc color space */ + guint32 fourcc; + + /* TRUE if the src caps sets a specific framerate */ + gboolean has_fps; + + /* the (expected) timestamp of the next frame */ + guint64 next_ts; + + /* duration of the current frame */ + guint64 duration; + + /* video state */ + gint framerate_numerator; + gint framerate_denominator; + + /* tags */ + GstTagList *tags; +}; + +static void gst_jpeg_parse_dispose (GObject * object); + +static GstFlowReturn gst_jpeg_parse_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_jpeg_parse_sink_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_jpeg_parse_sink_event (GstPad * pad, GstEvent * event); +static GstCaps *gst_jpeg_parse_src_getcaps (GstPad * pad); +static GstStateChangeReturn gst_jpeg_parse_change_state (GstElement * element, + GstStateChange transition); + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (jpeg_parse_debug, "jpegparse", 0, "JPEG parser"); + +GST_BOILERPLATE_FULL (GstJpegParse, gst_jpeg_parse, GstElement, + GST_TYPE_ELEMENT, _do_init); + +static void +gst_jpeg_parse_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_static_pad_template (element_class, + &gst_jpeg_parse_src_pad_template); + gst_element_class_add_static_pad_template (element_class, + &gst_jpeg_parse_sink_pad_template); + gst_element_class_set_details_simple (element_class, + "JPEG stream parser", + "Video/Parser", + "Parse JPEG images into single-frame buffers", + "Arnout Vandecappelle (Essensium/Mind) "); +} + +static void +gst_jpeg_parse_class_init (GstJpegParseClass * klass) +{ + GstElementClass *gstelement_class; + GObjectClass *gobject_class; + + gstelement_class = (GstElementClass *) klass; + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (gobject_class, sizeof (GstJpegParsePrivate)); + gobject_class->dispose = gst_jpeg_parse_dispose; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_jpeg_parse_change_state); +} + +static void +gst_jpeg_parse_init (GstJpegParse * parse, GstJpegParseClass * g_class) +{ + GstPad *sinkpad; + + parse->priv = G_TYPE_INSTANCE_GET_PRIVATE (parse, GST_TYPE_JPEG_PARSE, + GstJpegParsePrivate); + + /* create the sink and src pads */ + sinkpad = gst_pad_new_from_static_template (&gst_jpeg_parse_sink_pad_template, + "sink"); + gst_pad_set_chain_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_chain)); + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_sink_event)); + gst_pad_set_setcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_sink_setcaps)); + gst_element_add_pad (GST_ELEMENT (parse), sinkpad); + + parse->priv->srcpad = + gst_pad_new_from_static_template (&gst_jpeg_parse_src_pad_template, + "src"); + gst_pad_set_getcaps_function (parse->priv->srcpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_src_getcaps)); + gst_element_add_pad (GST_ELEMENT (parse), parse->priv->srcpad); + + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + parse->priv->adapter = gst_adapter_new (); +} + +static void +gst_jpeg_parse_dispose (GObject * object) +{ + GstJpegParse *parse = GST_JPEG_PARSE (object); + + if (parse->priv->adapter != NULL) { + gst_adapter_clear (parse->priv->adapter); + gst_object_unref (parse->priv->adapter); + parse->priv->adapter = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +static gboolean +gst_jpeg_parse_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstJpegParse *parse = GST_JPEG_PARSE (GST_OBJECT_PARENT (pad)); + GstStructure *s = gst_caps_get_structure (caps, 0); + const GValue *framerate; + + if ((framerate = gst_structure_get_value (s, "framerate")) != NULL) { + if (GST_VALUE_HOLDS_FRACTION (framerate)) { + parse->priv->framerate_numerator = + gst_value_get_fraction_numerator (framerate); + parse->priv->framerate_denominator = + gst_value_get_fraction_denominator (framerate); + parse->priv->has_fps = TRUE; + GST_DEBUG_OBJECT (parse, "got framerate of %d/%d", + parse->priv->framerate_numerator, parse->priv->framerate_denominator); + } + } + + return TRUE; +} + +static GstCaps * +gst_jpeg_parse_src_getcaps (GstPad * pad) +{ + GstCaps *result; + + if ((result = GST_PAD_CAPS (pad))) { + result = gst_caps_ref (result); + GST_DEBUG_OBJECT (pad, "using pad caps %" GST_PTR_FORMAT, result); + } else { + result = gst_caps_ref (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad))); + GST_DEBUG_OBJECT (pad, "using pad template caps %" GST_PTR_FORMAT, result); + } + return result; +} + +/* + * gst_jpeg_parse_skip_to_jpeg_header: + * @parse: the parser + * + * Flush everything until the next JPEG header. The header is considered + * to be the a start marker SOI (0xff 0xd8) followed by any other marker + * (0xff ...). + * + * Returns: TRUE if the header was found, FALSE if more data is needed. + */ +static gboolean +gst_jpeg_parse_skip_to_jpeg_header (GstJpegParse * parse) +{ + guint available, flush; + gboolean ret = TRUE; + + available = gst_adapter_available (parse->priv->adapter); + if (available < 4) + return FALSE; + + flush = gst_adapter_masked_scan_uint32 (parse->priv->adapter, 0xffffff00, + 0xffd8ff00, 0, available); + if (flush == -1) { + flush = available - 3; /* Last 3 bytes + 1 more may match header. */ + ret = FALSE; + } + if (flush > 0) { + GST_LOG_OBJECT (parse, "Skipping %u bytes.", flush); + gst_adapter_flush (parse->priv->adapter, flush); + } + return ret; +} + +static inline gboolean +gst_jpeg_parse_parse_tag_has_entropy_segment (guint8 tag) +{ + if (tag == SOS || (tag >= RST0 && tag <= RST7)) + return TRUE; + return FALSE; +} + +/* returns image length in bytes if parsed successfully, + * otherwise 0 if more data needed, + * if < 0 the absolute value needs to be flushed */ +static gint +gst_jpeg_parse_get_image_length (GstJpegParse * parse) +{ + guint size; + gboolean resync; + GstAdapter *adapter = parse->priv->adapter; + gint offset, noffset; + + size = gst_adapter_available (adapter); + + /* we expect at least 4 bytes, first of which start marker */ + if (gst_adapter_masked_scan_uint32 (adapter, 0xffff0000, 0xffd80000, 0, 4)) + return 0; + + GST_DEBUG ("Parsing jpeg image data (%u bytes)", size); + + GST_DEBUG ("Parse state: offset=%d, resync=%d, entropy len=%d", + parse->priv->last_offset, parse->priv->last_resync, + parse->priv->last_entropy_len); + + /* offset is 2 less than actual offset; + * - adapter needs at least 4 bytes for scanning, + * - start and end marker ensure at least that much + */ + /* resume from state offset */ + offset = parse->priv->last_offset; + + while (1) { + guint frame_len; + guint32 value; + + noffset = + gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00, 0x0000ff00, + offset, size - offset, &value); + /* lost sync if 0xff marker not where expected */ + if ((resync = (noffset != offset))) { + GST_DEBUG ("Lost sync at 0x%08x, resyncing", offset + 2); + } + /* may have marker, but could have been resyncng */ + resync = resync || parse->priv->last_resync; + /* Skip over extra 0xff */ + while ((noffset >= 0) && ((value & 0xff) == 0xff)) { + noffset++; + noffset = + gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00, 0x0000ff00, + noffset, size - noffset, &value); + } + /* enough bytes left for marker? (we need 0xNN after the 0xff) */ + if (noffset < 0) { + GST_DEBUG ("at end of input and no EOI marker found, need more data"); + goto need_more_data; + } + + /* now lock on the marker we found */ + offset = noffset; + value = value & 0xff; + if (value == 0xd9) { + GST_DEBUG ("0x%08x: EOI marker", offset + 2); + /* clear parse state */ + parse->priv->last_resync = FALSE; + parse->priv->last_offset = 0; + return (offset + 4); + } else if (value == 0xd8) { + /* Skip this frame if we found another SOI marker */ + GST_DEBUG ("0x%08x: SOI marker before EOI, skipping", offset + 2); + /* clear parse state */ + parse->priv->last_resync = FALSE; + parse->priv->last_offset = 0; + return -(offset + 2); + } + + if (value >= 0xd0 && value <= 0xd7) + frame_len = 0; + else { + /* peek tag and subsequent length */ + if (offset + 2 + 4 > size) + goto need_more_data; + else + gst_adapter_masked_scan_uint32_peek (adapter, 0x0, 0x0, offset + 2, 4, + &frame_len); + frame_len = frame_len & 0xffff; + } + GST_DEBUG ("0x%08x: tag %02x, frame_len=%u", offset + 2, value, frame_len); + /* the frame length includes the 2 bytes for the length; here we want at + * least 2 more bytes at the end for an end marker */ + if (offset + 2 + 2 + frame_len + 2 > size) { + goto need_more_data; + } + + if (gst_jpeg_parse_parse_tag_has_entropy_segment (value)) { + guint eseglen = parse->priv->last_entropy_len; + + GST_DEBUG ("0x%08x: finding entropy segment length", offset + 2); + noffset = offset + 2 + frame_len + eseglen; + while (1) { + noffset = gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00, + 0x0000ff00, noffset, size - noffset, &value); + if (noffset < 0) { + /* need more data */ + parse->priv->last_entropy_len = size - offset - 4 - frame_len - 2; + goto need_more_data; + } + if ((value & 0xff) != 0x00) { + eseglen = noffset - offset - frame_len - 2; + break; + } + noffset++; + } + parse->priv->last_entropy_len = 0; + frame_len += eseglen; + GST_DEBUG ("entropy segment length=%u => frame_len=%u", eseglen, + frame_len); + } + if (resync) { + /* check if we will still be in sync if we interpret + * this as a sync point and skip this frame */ + noffset = offset + frame_len + 2; + noffset = gst_adapter_masked_scan_uint32 (adapter, 0x0000ff00, 0x0000ff00, + noffset, 4); + if (noffset < 0) { + /* ignore and continue resyncing until we hit the end + * of our data or find a sync point that looks okay */ + offset++; + continue; + } + GST_DEBUG ("found sync at 0x%x", offset + 2); + } + + offset += frame_len + 2; + } + + /* EXITS */ +need_more_data: + { + parse->priv->last_offset = offset; + parse->priv->last_resync = resync; + return 0; + } +} + +static inline gboolean +gst_jpeg_parse_sof (GstJpegParse * parse, GstByteReader * reader) +{ + guint8 numcomps = 0; /* Number of components in image + (1 for gray, 3 for YUV, etc.) */ + guint8 precision; /* precision (in bits) for the samples */ + guint8 compId[3] G_GNUC_UNUSED; /* unique value identifying each component */ + guint8 qtId[3] G_GNUC_UNUSED; /* quantization table ID to use for this comp */ + guint8 blockWidth[3]; /* Array[numComponents] giving the number of + blocks (horiz) in this component */ + guint8 blockHeight[3]; /* Same for the vertical part of this component */ + guint8 i, value = 0; + gint temp; + + /* flush length field */ + if (!gst_byte_reader_skip (reader, 2)) + return FALSE; + + /* Get sample precision */ + if (!gst_byte_reader_get_uint8 (reader, &precision)) + return FALSE; + + /* Get w and h */ + if (!gst_byte_reader_get_uint16_be (reader, &parse->priv->height)) + return FALSE; + if (!gst_byte_reader_get_uint16_be (reader, &parse->priv->width)) + return FALSE; + + /* Get number of components */ + if (!gst_byte_reader_get_uint8 (reader, &numcomps)) + return FALSE; + + if (numcomps > 3) /* FIXME */ + return FALSE; + + /* Get decimation and quantization table id for each component */ + for (i = 0; i < numcomps; i++) { + /* Get component ID number */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + compId[i] = value; + + /* Get decimation */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + blockWidth[i] = (value & 0xf0) >> 4; + blockHeight[i] = (value & 0x0f); + + /* Get quantization table id */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + qtId[i] = value; + } + + if (numcomps == 1) { + /* gray image - no fourcc */ + parse->priv->fourcc = 0; + } else if (numcomps == 3) { + temp = (blockWidth[0] * blockHeight[0]) / (blockWidth[1] * blockHeight[1]); + + if (temp == 4 && blockHeight[0] == 2) + parse->priv->fourcc = GST_MAKE_FOURCC ('I', '4', '2', '0'); + else if (temp == 4 && blockHeight[0] == 4) + parse->priv->fourcc = GST_MAKE_FOURCC ('Y', '4', '1', 'B'); + else if (temp == 2) + parse->priv->fourcc = GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'); + else if (temp == 1) + parse->priv->fourcc = GST_MAKE_FOURCC ('Y', 'V', '1', '2'); + else + parse->priv->fourcc = 0; + } else { + return FALSE; + } + + GST_DEBUG_OBJECT (parse, "Header parsed"); + + return TRUE; +} + +static inline gboolean +gst_jpeg_parse_remove_marker (GstJpegParse * parse, + GstByteReader * reader, guint8 marker, GstBuffer * buffer) +{ + guint16 size = 0; + guint pos = gst_byte_reader_get_pos (reader); + guint8 *data = GST_BUFFER_DATA (buffer); + + if (!gst_byte_reader_peek_uint16_be (reader, &size)) + return FALSE; + if (gst_byte_reader_get_remaining (reader) < size) + return FALSE; + + GST_LOG_OBJECT (parse, "unhandled marker %x removing %u bytes", marker, size); + + memmove (&data[pos], &data[pos + size], + GST_BUFFER_SIZE (buffer) - (pos + size)); + GST_BUFFER_SIZE (buffer) -= size; + + if (!gst_byte_reader_set_pos (reader, pos - size)) + return FALSE; + + return TRUE; +} + +static inline gboolean +gst_jpeg_parse_skip_marker (GstJpegParse * parse, + GstByteReader * reader, guint8 marker) +{ + guint16 size = 0; + + if (!gst_byte_reader_get_uint16_be (reader, &size)) + return FALSE; + +#ifndef GST_DISABLE_DEBUG + /* We'd pry the id of the skipped application segment */ + if (marker >= APP0 && marker <= APP15) { + const gchar *id_str = NULL; + + if (gst_byte_reader_peek_string_utf8 (reader, &id_str)) { + GST_DEBUG_OBJECT (parse, "unhandled marker %x: '%s' skiping %u bytes", + marker, id_str ? id_str : "(NULL)", size); + } else { + GST_DEBUG_OBJECT (parse, "unhandled marker %x skiping %u bytes", marker, + size); + } + } +#else + GST_DEBUG_OBJECT (parse, "unhandled marker %x skiping %u bytes", marker, + size); +#endif // GST_DISABLE_DEBUG + + if (!gst_byte_reader_skip (reader, size - 2)) + return FALSE; + + return TRUE; +} + +static inline GstTagList * +get_tag_list (GstJpegParse * parse) +{ + if (!parse->priv->tags) + parse->priv->tags = gst_tag_list_new (); + return parse->priv->tags; +} + +static inline void +extract_and_queue_tags (GstJpegParse * parse, guint size, guint8 * data, + GstTagList * (*tag_func) (const GstBuffer * buff)) +{ + GstTagList *tags; + GstBuffer *buf; + + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = data; + GST_BUFFER_SIZE (buf) = size; + + tags = tag_func (buf); + gst_buffer_unref (buf); + + if (tags) { + GstTagList *taglist = parse->priv->tags; + if (taglist) { + gst_tag_list_insert (taglist, tags, GST_TAG_MERGE_REPLACE); + gst_tag_list_free (tags); + } else { + parse->priv->tags = tags; + } + GST_DEBUG_OBJECT (parse, "collected tags: %" GST_PTR_FORMAT, + parse->priv->tags); + } +} + +static inline gboolean +gst_jpeg_parse_app1 (GstJpegParse * parse, GstByteReader * reader) +{ + guint16 size = 0; + const gchar *id_str; + const guint8 *data = NULL; + + if (!gst_byte_reader_get_uint16_be (reader, &size)) + return FALSE; + + size -= 2; /* 2 bytes for the mark */ + if (!gst_byte_reader_peek_string_utf8 (reader, &id_str)) + return FALSE; + + if (!strncmp (id_str, "Exif", 4)) { + + /* skip id + NUL + padding */ + if (!gst_byte_reader_skip (reader, 6)) + return FALSE; + size -= 6; + + /* handle exif metadata */ + if (!gst_byte_reader_get_data (reader, size, &data)) + return FALSE; + + extract_and_queue_tags (parse, size, (guint8 *) data, + gst_tag_list_from_exif_buffer_with_tiff_header); + + GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %u bytes", + APP1, id_str, size); + + } else if (!strncmp (id_str, "http://ns.adobe.com/xap/1.0/", 28)) { + + /* skip the id + NUL */ + if (!gst_byte_reader_skip (reader, 29)) + return FALSE; + size -= 29; + + /* handle xmp metadata */ + if (!gst_byte_reader_get_data (reader, size, &data)) + return FALSE; + + extract_and_queue_tags (parse, size, (guint8 *) data, + gst_tag_list_from_xmp_buffer); + + GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %u bytes", + APP1, id_str, size); + + } else { + if (!gst_jpeg_parse_skip_marker (parse, reader, APP1)) + return FALSE; + } + + return TRUE; +} + +static inline gchar * +get_utf8_from_data (const guint8 * data, guint16 size) +{ + const gchar *env_vars[] = { "GST_JPEG_TAG_ENCODING", + "GST_TAG_ENCODING", NULL + }; + const char *str = (gchar *) data; + + return gst_tag_freeform_string_to_utf8 (str, size, env_vars); +} + +/* read comment and post as tag */ +static inline gboolean +gst_jpeg_parse_com (GstJpegParse * parse, GstByteReader * reader) +{ + const guint8 *data = NULL; + guint16 size = 0; + gchar *comment; + + if (!gst_byte_reader_get_uint16_be (reader, &size)) + return FALSE; + + size -= 2; + if (!gst_byte_reader_get_data (reader, size, &data)) + return FALSE; + + comment = get_utf8_from_data (data, size); + + if (comment) { + GstTagList *taglist = get_tag_list (parse); + gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, + GST_TAG_COMMENT, comment, NULL); + GST_DEBUG_OBJECT (parse, "collected tags: %" GST_PTR_FORMAT, taglist); + g_free (comment); + } + + return TRUE; +} + +static gboolean +gst_jpeg_parse_read_header (GstJpegParse * parse, GstBuffer * buffer) +{ + GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buffer); + guint8 marker = 0; + gboolean foundSOF = FALSE; + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + + while (marker == 0xff) { + if (!gst_byte_reader_skip (&reader, 1)) + goto error; + + if (!gst_byte_reader_get_uint8 (&reader, &marker)) + goto error; + + GST_DEBUG_OBJECT (parse, "marker = %x", marker); + + switch (marker) { + case SOS: /* start of scan (begins compressed data) */ + return foundSOF; + + case SOI: + break; + + case DRI: + if (!gst_byte_reader_skip (&reader, 4)) /* fixed size */ + goto error; + break; + + case COM: + if (!gst_jpeg_parse_com (parse, &reader)) + goto error; + break; + + case APP1: + if (!gst_jpeg_parse_app1 (parse, &reader)) + goto error; + break; + + case DHT: + case DQT: + /* Ignore these codes */ + if (!gst_jpeg_parse_skip_marker (parse, &reader, marker)) + goto error; + break; + + case SOF2: + parse->priv->interlaced = TRUE; + /* fall through */ + case SOF0: + foundSOF = TRUE; + /* parse Start Of Frame */ + if (!gst_jpeg_parse_sof (parse, &reader)) + goto error; + + return TRUE; + + default: + if (marker == JPG || (marker >= JPG0 && marker <= JPG13)) { + /* we'd like to remove them from the buffer */ + if (!gst_jpeg_parse_remove_marker (parse, &reader, marker, buffer)) + goto error; + } else if (marker >= APP0 && marker <= APP15) { + if (!gst_jpeg_parse_skip_marker (parse, &reader, marker)) + goto error; + } else { + GST_WARNING_OBJECT (parse, "unhandled marker %x, leaving", marker); + /* Not SOF or SOI. Must not be a JPEG file (or file pointer + * is placed wrong). In either case, it's an error. */ + return FALSE; + } + } + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + } + + return foundSOF; + +error: + GST_WARNING_OBJECT (parse, + "Error parsing image header (need more than %u bytes available)", + gst_byte_reader_get_remaining (&reader)); + return FALSE; +} + +static gboolean +gst_jpeg_parse_set_new_caps (GstJpegParse * parse, gboolean header_ok) +{ + GstCaps *caps; + gboolean res; + + GST_DEBUG_OBJECT (parse, "setting caps on srcpad (hdr_ok=%d, have_fps=%d)", + header_ok, parse->priv->has_fps); + + caps = gst_caps_new_simple ("image/jpeg", + "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + + if (header_ok == TRUE) { + gst_caps_set_simple (caps, + "format", GST_TYPE_FOURCC, parse->priv->fourcc, + "interlaced", G_TYPE_BOOLEAN, parse->priv->interlaced, + "width", G_TYPE_INT, parse->priv->width, + "height", G_TYPE_INT, parse->priv->height, NULL); + } + + if (parse->priv->has_fps == TRUE) { + /* we have a framerate */ + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, + parse->priv->framerate_numerator, + parse->priv->framerate_denominator, NULL); + + if (!GST_CLOCK_TIME_IS_VALID (parse->priv->duration) + && parse->priv->framerate_numerator != 0) { + parse->priv->duration = gst_util_uint64_scale_int (GST_SECOND, + parse->priv->framerate_numerator, parse->priv->framerate_denominator); + } + } else { + /* unknown duration */ + parse->priv->duration = GST_CLOCK_TIME_NONE; + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, 1, 1, NULL); + } + + GST_DEBUG_OBJECT (parse, + "setting downstream caps on %s:%s to %" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (parse->priv->srcpad), caps); + res = gst_pad_set_caps (parse->priv->srcpad, caps); + gst_caps_unref (caps); + + return res; + +} + +static GstFlowReturn +gst_jpeg_parse_push_buffer (GstJpegParse * parse, guint len) +{ + GstBuffer *outbuf; + GstFlowReturn ret = GST_FLOW_OK; + gboolean header_ok; + + /* reset the offset (only when we flushed) */ + parse->priv->last_offset = 0; + parse->priv->last_entropy_len = 0; + + outbuf = gst_adapter_take_buffer (parse->priv->adapter, len); + if (outbuf == NULL) { + GST_ELEMENT_ERROR (parse, STREAM, DECODE, + ("Failed to take buffer of size %u", len), + ("Failed to take buffer of size %u", len)); + return GST_FLOW_ERROR; + } + + header_ok = gst_jpeg_parse_read_header (parse, outbuf); + + if (parse->priv->new_segment == TRUE + || parse->priv->width != parse->priv->caps_width + || parse->priv->height != parse->priv->caps_height + || parse->priv->framerate_numerator != + parse->priv->caps_framerate_numerator + || parse->priv->framerate_denominator != + parse->priv->caps_framerate_denominator) { + if (!gst_jpeg_parse_set_new_caps (parse, header_ok)) { + GST_ELEMENT_ERROR (parse, CORE, NEGOTIATION, + ("Can't set caps to the src pad"), ("Can't set caps to the src pad")); + return GST_FLOW_ERROR; + } + + if (parse->priv->tags) { + GST_DEBUG_OBJECT (parse, "Pushing tags: %" GST_PTR_FORMAT, + parse->priv->tags); + gst_element_found_tags_for_pad (GST_ELEMENT_CAST (parse), + parse->priv->srcpad, parse->priv->tags); + parse->priv->tags = NULL; + } + + parse->priv->new_segment = FALSE; + parse->priv->caps_width = parse->priv->width; + parse->priv->caps_height = parse->priv->height; + parse->priv->caps_framerate_numerator = parse->priv->framerate_numerator; + parse->priv->caps_framerate_denominator = + parse->priv->framerate_denominator; + } + + GST_BUFFER_TIMESTAMP (outbuf) = parse->priv->next_ts; + + if (parse->priv->has_fps && GST_CLOCK_TIME_IS_VALID (parse->priv->next_ts) + && GST_CLOCK_TIME_IS_VALID (parse->priv->duration)) { + parse->priv->next_ts += parse->priv->duration; + } else { + parse->priv->duration = GST_CLOCK_TIME_NONE; + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + } + + GST_BUFFER_DURATION (outbuf) = parse->priv->duration; + + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (parse->priv->srcpad)); + + GST_LOG_OBJECT (parse, "pushing buffer (ts=%" GST_TIME_FORMAT ", len=%u)", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), len); + + ret = gst_pad_push (parse->priv->srcpad, outbuf); + + return ret; +} + +static GstFlowReturn +gst_jpeg_parse_chain (GstPad * pad, GstBuffer * buf) +{ + GstJpegParse *parse; + gint len; + GstClockTime timestamp, duration; + GstFlowReturn ret = GST_FLOW_OK; + + parse = GST_JPEG_PARSE (GST_PAD_PARENT (pad)); + + timestamp = GST_BUFFER_TIMESTAMP (buf); + duration = GST_BUFFER_DURATION (buf); + + gst_adapter_push (parse->priv->adapter, buf); + + while (ret == GST_FLOW_OK && gst_jpeg_parse_skip_to_jpeg_header (parse)) { + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (parse->priv->next_ts))) + parse->priv->next_ts = timestamp; + + parse->priv->duration = duration; + + /* check if we already have a EOI */ + len = gst_jpeg_parse_get_image_length (parse); + if (len == 0) { + return GST_FLOW_OK; + } else if (len < 0) { + gst_adapter_flush (parse->priv->adapter, -len); + continue; + } + + GST_LOG_OBJECT (parse, "parsed image of size %d", len); + + /* now we have enough in the adapter to process a full jpeg image */ + ret = gst_jpeg_parse_push_buffer (parse, len); + } + + GST_DEBUG_OBJECT (parse, "No further start marker found."); + return ret; +} + +static gboolean +gst_jpeg_parse_sink_event (GstPad * pad, GstEvent * event) +{ + GstJpegParse *parse; + gboolean res = TRUE; + + parse = GST_JPEG_PARSE (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (parse, "event : %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + parse->priv->last_offset = 0; + parse->priv->last_entropy_len = 0; + parse->priv->last_resync = FALSE; + gst_adapter_clear (parse->priv->adapter); + break; + case GST_EVENT_EOS:{ + /* Push the remaining data, even though it's incomplete */ + guint available = gst_adapter_available (parse->priv->adapter); + if (available > 0) + gst_jpeg_parse_push_buffer (parse, available); + res = gst_pad_push_event (parse->priv->srcpad, event); + break; + } + case GST_EVENT_NEWSEGMENT: + /* Discard any data in the adapter. There should have been an EOS before + * to flush it. */ + gst_adapter_clear (parse->priv->adapter); + res = gst_pad_push_event (parse->priv->srcpad, event); + parse->priv->new_segment = TRUE; + break; + case GST_EVENT_TAG:{ + if (!parse->priv->new_segment) + res = gst_pad_event_default (pad, event); + else { + GstTagList *taglist = NULL; + + gst_event_parse_tag (event, &taglist); + /* Hold on to the tags till the srcpad caps are definitely set */ + gst_tag_list_insert (get_tag_list (parse), taglist, + GST_TAG_MERGE_REPLACE); + GST_DEBUG ("collected tags: %" GST_PTR_FORMAT, parse->priv->tags); + gst_event_unref (event); + } + break; + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (parse); + return res; +} + +static GstStateChangeReturn +gst_jpeg_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstJpegParse *parse; + + parse = GST_JPEG_PARSE (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + parse->priv->has_fps = FALSE; + + parse->priv->interlaced = FALSE; + parse->priv->width = parse->priv->height = 0; + parse->priv->framerate_numerator = 0; + parse->priv->framerate_denominator = 1; + + parse->priv->caps_framerate_numerator = + parse->priv->caps_framerate_denominator = 0; + parse->priv->caps_width = parse->priv->caps_height = -1; + + parse->priv->new_segment = FALSE; + + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + + parse->priv->last_offset = 0; + parse->priv->last_entropy_len = 0; + parse->priv->last_resync = FALSE; + + parse->priv->tags = NULL; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret != GST_STATE_CHANGE_SUCCESS) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_adapter_clear (parse->priv->adapter); + if (parse->priv->tags) { + gst_tag_list_free (parse->priv->tags); + parse->priv->tags = NULL; + } + break; + default: + break; + } + + return ret; +} Index: gst-plugins-good0.10/gst/jpegformat/gstjpegparse.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gst-plugins-good0.10/gst/jpegformat/gstjpegparse.h 2012-02-09 14:44:27.408711038 +0200 @@ -0,0 +1,61 @@ +/* GStreamer + * + * jpegparse: a parser for JPEG streams + * + * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_JPEG_PARSE_H__ +#define __GST_JPEG_PARSE_H__ + +#include +#include + +#include "gstjpegformat.h" + +G_BEGIN_DECLS + +#define GST_TYPE_JPEG_PARSE \ + (gst_jpeg_parse_get_type()) +#define GST_JPEG_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JPEG_PARSE,GstJpegParse)) +#define GST_JPEG_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JPEG_PARSE,GstJpegParseClass)) +#define GST_IS_JPEG_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JPEG_PARSE)) +#define GST_IS_JPEG_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JPEG_PARSE)) + +typedef struct _GstJpegParse GstJpegParse; +typedef struct _GstJpegParsePrivate GstJpegParsePrivate; +typedef struct _GstJpegParseClass GstJpegParseClass; + +struct _GstJpegParse { + GstElement element; + GstJpegParsePrivate *priv; +}; + +struct _GstJpegParseClass { + GstElementClass parent_class; +}; + +GType gst_jpeg_parse_get_type (void); + +G_END_DECLS + +#endif /* __GST_JPEG_PARSE_H__ */ debian/source/0000775000000000000000000000000012061315551010467 5ustar debian/source/format0000664000000000000000000000001412061315514011674 0ustar 3.0 (quilt) debian/mk.control0000775000000000000000000000042412061315514011202 0ustar #!/usr/bin/perl -w open BUILDDEPS, "debian/build-deps"; @builddeplist = ; close BUILDDEPS; chomp(@builddeplist); $builddeps = join(", ", @builddeplist); open CONTROLIN, "debian/control.in"; while(){ s/BUILDDEPS/$builddeps/; print; } debian/gstreamer-pulseaudio.install0000664000000000000000000000007012061315514014714 0ustar debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstpulse.so debian/copyright0000664000000000000000000000230512061315514011121 0ustar This package was debianized by David I. Lehn on Mon, 15 Jan 2001 18:21:37 -0500. It was downloaded from http://gstreamer.net/ Upstream Authors: Erik Walthinsen Wim Taymans Richard Boulton and many more... Copyright: This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This package 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian GNU/Linux systems, the complete text of the GNU Lesser General Public License can be found in `/usr/share/common-licenses/LGPL'. debian/HACKING.Debian0000664000000000000000000000731112061315514011340 0ustar Hacking GStreamer for Debian ============================ An addition to the README.Debian that has Debian package hacking notes. Everything should now be versioned. - To update dependencies edit debian/build-deps.in - To rebuild package files from .in files: debian/rules maint - After a version update to check for missing files run: debian/maint missing debian/maint missing-libs debian/maint missing-so - To update package files edit gstreamer-foo then rebuild package files. This will generate the gstreamerX.Y-foo file. Also update debian/rules and debian/control.in as needed. - The packaging tries to stay close of upstream choice. gstreamerX.Y-misc has most of the files, extra packages are made for the sinks and by group of depends (gnome, x, ...) - Provide gstreamerX.Y-videosink and gstreamerX.Y-audiosink as needed. - All interfaces and generic libraries go in libgstreamer-plugins, libraries with external dependencies have their own package such as libgstreamer-gconf. - Applications go in gstreamerX.Y-plugins-base-apps File listing ------------ build-deps: - generated from "build-deps.in" in "rules debian/build-deps:": * "build-deps.in" lists build-depends for Debian main packages - serves generation with "control.in" and "mk.control" of "control" in "rules debian/control::" build-deps.in: - lists build-depends for Debian main packages - serves generation with "extra deps" of "build-deps" in "rules debian/build-deps:" changelog: handled classically compat: handled classically control: - generated for "rules maint" - generated from "control.in" and "build-deps" in "rules debian/control::": * "build-deps" lists build-depends to be inserted in "control Build-Depends:" * "control.in" serves of template for package descriptions of Debian main packages and makes use of special stanzas enclosed in "@" such as "@GST_ABI" which are replaced via sed in "rules debian/control::" control.in: - serves generation of "control" in "rules debian/control::" via "mk.control" called in "rules debian/control::" - template for package descriptions of Debian main packages and makes use of special stanzas enclosed in "@" such as "@GST_ABI" which are replaced via sed in "rules debian/control::" copyright: handled classically gstreamer-$plugin.install: - serves the generation of $gst_pkgname-$plugin.install (for example gstreamer-alsa.install serves the generation of gstreamerX.Y-alsa.install) in "rules pre-build::" via dynamic "@"-enclosed variables replacement - some plugins are handled specially and some special variables are available gstreamer-plugins-base-apps.install: this isn't really a plugin, but the gstreamerX.Y-plugins-base-apps package gstreamer-plugins-base-apps.manpages: man pages for the gstreamerX.Y-plugins-base-apps package HACKING.Debian: this file libgstreamer-plugins-base-dev.install libgstreamer-plugins-base.install: handled similarly has gstreamerX.Y-plugins-base-apps maint: makefile wrapping some targets of "rules" with some sh filtering mk.control: - Perl script serving the generation of "control" which reads "build-deps" and "control.in", and replaces the "BUILDDEPS" stanza with the build-deps patches: handled classically README.Debian: handled classically rules: handled classically, with special targets "maint:", and "debian/build-deps:" watch: handled classically This file is the initial work of: David I. Lehn Tue, 13 Apr 2004 21:28:55 -0400 and had some additions by: Loic Minier Sun, 19 Jun 2005 19:04:58 +0200 Sebastien Bacher Wed, 14 Dec 2005 17:00:21 +0100 debian/gstreamer-plugins-good.install0000664000000000000000000001036612061315514015162 0ustar debian/tmp/usr/share/gstreamer-0.10/presets/ @1394@ debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstaasink.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstalaw.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstalphacolor.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstalpha.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstannodex.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstapetag.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstaudiofx.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstaudioparsers.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstauparse.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstautoconvert.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstautodetect.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstavi.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstcacasink.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstcairo.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstcamerabin.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstcamerabin2.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstcutter.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstdebug.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstdeinterlace.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstdtmf.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstdv.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstefence.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgsteffectv.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstequalizer.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstflac.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstflv.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstflxdec.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstgdkpixbuf.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstgoom.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstgoom2k1.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgsticydemux.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstid3demux.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstimagefreeze.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstinterleave.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstjack.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstjpeg.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstjpegformat.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstlevel.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstliveadder.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstmatroska.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstmonoscope.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstmulaw.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstmultifile.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstmultipart.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstnavigationtest.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstossaudio.so @oss4@ debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstpng.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstisomp4.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstreplaygain.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstrtp.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstrtpmanager.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstrtpmux.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstrtsp.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstshapewipe.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstshm.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstshout2.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstsmpte.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstsouphttpsrc.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstspectrum.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstspeex.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgsttaglib.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstudp.so @video4linux2@ debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstvideobox.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstvideocrop.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstvideofilter.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstvideomixer.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstwavenc.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstwavpack.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstwavparse.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstximagesrc.so debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgsty4menc.so debian/tmp/usr/lib/*/libgstbasecamerabinsrc-@GST_ABI@.so.* debian/tmp/usr/lib/*/libgstphotography-@GST_ABI@.so.* debian/tmp/usr/share/locale debian/README.Debian0000664000000000000000000000276612061315514011242 0ustar Gstreamer for Debian ==================== This package contains the GStreamer plugin distribution. More information can be found at http://gstreamer.net/ As of GStreamer plugins version 0.8.0 all packages are versioned and parallel installable with other releases with other major.minor versions. For example, 0.8.x series is versioned as 0.8 and parallel installable with both unversioned 0.6.x series and future 0.9.x and beyond. The version part is represented as VER below. GStreamer plugins are split into a number of packages: plugins without external dependencies: gstreamerVER-misc-good many independent plugins plugins with external dependencies: gstreamerVER-aa gstreamerVER-auto gstreamerVER-caca gstreamerVER-esd gstreamerVER-oss documentation: gstreamerVER-plugins-good-doc html documentation Notes ===== ChangeLog --------- The upstream ChangeLog is not included in all the plugin packages due to its large size. Please see upstream sources if you are interested in detailed source changes. External tools support ---------------------- Your favorite codec isn't wrapped as a plugin? External programs can be used to process streams. Take a look at "pipefilter" element or try something like this (untested): $ mkfifo fifo $ gst-launch myaudiosrc ! filesink location=fifo & $ cat fifo | my_encoder > output_file David I. Lehn Tue, 23 Mar 2004 04:38:37 -0500 update: Sebastien Bacher Wed, 14 Dec 2005 17:00:21 +0100 debian/rules0000775000000000000000000001351012256171336010256 0ustar #!/usr/bin/make -f include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/gnome.mk include /usr/share/cdbs/1/rules/utils.mk -include /usr/share/cdbs/1/rules/autoreconf.mk DEB_DH_AUTORECONF_ARGS := --as-needed # make autoreconf not call autopoint since we ship a patch for po/Makefile.in.in export AUTOPOINT=true CFLAGS += -Wno-error CXXFLAGS += -Wno-error LDFLAGS += -Wl,-z,defs -Wl,-O1 -Wl,--as-needed DEB_MAKE_CHECK_TARGET = check || true # this is for compatibility with dpkg-dev < 1.13.5, see # DEB_HOST_ARCH_CPU := $(shell dpkg-architecture -qDEB_HOST_ARCH_CPU 2>/dev/null) DEB_HOST_ARCH_OS := $(shell dpkg-architecture -qDEB_HOST_ARCH_OS 2>/dev/null) # Take account of old dpkg-architecture output. ifeq ($(DEB_HOST_ARCH_CPU),) DEB_HOST_ARCH_CPU := $(shell dpkg-architecture -qDEB_HOST_GNU_CPU) ifeq ($(DEB_HOST_ARCH_CPU),x86_64) DEB_HOST_ARCH_CPU := amd64 endif endif ifeq ($(DEB_HOST_ARCH_OS),) DEB_HOST_ARCH_OS := $(subst -gnu,,$(shell dpkg-architecture -qDEB_HOST_GNU_SYSTEM)) ifeq ($(DEB_HOST_ARCH_OS),gnu) DEB_HOST_ARCH_OS := hurd endif endif # end of compatibility block # debian package version version=$(shell dpkg-parsechangelog | grep ^Version: | cut -d ' ' -f 2) # upstream version gst_version=$(shell echo $(version) | cut -d '-' -f 1) gst_major=0 gst_minor=10 gst_abi=$(gst_major).$(gst_minor) # gstreamer library package names gst_lib=libgstreamer$(gst_abi)-0 gst_lib_dev=libgstreamer$(gst_abi)-dev # what gstreamer version is needed gst_lib_dev_dep=$(gst_lib_dev) (>= 0.10.36) gst_pkgname=gstreamer$(gst_abi) gst_deb_abi=$(gst_abi)-0 gst_extra_build_depends = gst_extra_build_depends += libraw1394-dev (>= 2.0.0) [linux-any] gst_extra_build_depends += , libiec61883-dev (>= 1.0.0) [linux-any] gst_extra_build_depends += , libavc1394-dev [linux-any] gst_extra_build_depends += , libv4l-dev [linux-any] gst_extra_build_depends += , libgudev-1.0-dev (>= 143) [linux-any] # debug package DEB_DH_STRIP_ARGS := --dbg-package=$(gst_pkgname)-plugins-good-dbg # disable all CPU specific optimizations in commands launched by this Makefile # using liboil; this is to work around liboil related build failures which # are not specially interesting to catch on buildds as these might run very # specific hardware OIL_CPU_FLAGS=0 export OIL_CPU_FLAGS # The plugins are basically the same. # Link special names to a template file. # still need "*.install" to be done by hand PLUGINS += gconf pulseaudio plugins-good CONFIG_ARGS := ifeq ($(DEB_HOST_ARCH_OS),linux) PLUGINS += endif VERSIONIZE= \ plugins-good-doc.install \ ifeq ($(DEB_HOST_ARCH_OS),linux) 1394 = debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/gstreamer-$(gst_abi)/libgst1394.so video4linux2 = debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/gstreamer-$(gst_abi)/libgstvideo4linux2.so endif ifeq ($(DEB_HOST_ARCH_OS),hurd) CONFIG_ARGS += --disable-oss4 else oss4 = debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/gstreamer-$(gst_abi)/libgstoss4audio.so endif ifeq ($(DEB_HOST_ARCH_OS),linux) DEFAULT_AUDIOSINK = autoaudiosink DEFAULT_AUDIOSRC = autoaudiosrc DEFAULT_VIDEOSINK = autovideosink DEFAULT_VIDEOSRC = v4l2src DEFAULT_VISUALIZER = goom else DEFAULT_AUDIOSINK = autoaudiosink DEFAULT_AUDIOSRC = autoaudiosrc DEFAULT_VIDEOSINK = autovideosink DEFAULT_VIDEOSRC = videotestsrc DEFAULT_VISUALIZER = goom endif # Let's decide the package name and url depending on the distribution DISTRO = "$(shell dpkg-vendor --query vendor)" GST_PACKAGE_NAME := "GStreamer Good Plugins (unknown Debian derivative)" GST_PACKAGE_ORIGIN="http://packages.qa.debian.org/gst-plugins-good$(gst_abi)" ifeq ($(DISTRO),"Debian") GST_PACKAGE_NAME := "GStreamer Good Plugins (Debian)" GST_PACKAGE_ORIGIN="http://packages.qa.debian.org/gst-plugins-good$(gst_abi)" endif ifeq ($(DISTRO),"Ubuntu") GST_PACKAGE_NAME := "GStreamer Good Plugins (Ubuntu)" GST_PACKAGE_ORIGIN="https://launchpad.net/distros/ubuntu/+source/gst-plugins-good$(gst_abi)" endif # setup links for packages pre-build:: for p in $(PLUGINS); do \ rm -f debian/$(gst_pkgname)-$$p.install; \ sed \ -e 's/@GST_ABI@/$(gst_abi)/g' \ -e 's,@1394@,$(1394),g' \ -e 's,@video4linux2@,$(video4linux2),g' \ -e 's,@oss4@,$(oss4),g' \ debian/gstreamer-$$p.install \ > debian/$(gst_pkgname)-$$p.install; \ done for f in $(VERSIONIZE); do \ sed 's/@GST_ABI@/$(gst_abi)/g' debian/gstreamer-$$f \ > debian/$(gst_pkgname)-$$f; \ done maint: debian/control debian/build-deps: debian/build-deps.in debian/rules cat $< > $@ debian/control:: debian/control.in debian/build-deps debian/mk.control debian/rules perl debian/mk.control | sed \ -e 's/@GST_VERSION@/$(gst_version)/g' \ -e 's/@GST_ABI@/$(gst_abi)/g' \ -e 's/@GST_PKGNAME@/$(gst_pkgname)/g' \ -e 's/@GST_LIB@/$(gst_lib)/g' \ -e 's/@GST_LIB_DEV@/$(gst_lib_dev)/g' \ -e 's/@GST_LIB_DEV_DEP@/$(gst_lib_dev_dep)/g' \ -e 's/@GST_EXTRA_BUILD_DEPENDS@/$(gst_extra_build_depends)/g' \ >$@ DEB_CONFIGURE_EXTRA_FLAGS += \ --libdir=\$${prefix}/lib/$(DEB_HOST_MULTIARCH) \ --disable-examples \ --enable-DEBUG \ --enable-debug \ --with-package-name=$(GST_PACKAGE_NAME) \ --with-package-origin=$(GST_PACKAGE_ORIGIN) \ --enable-gtk-doc \ --enable-experimental \ --with-default-audiosink=$(DEFAULT_AUDIOSINK) \ --with-default-audiosrc=$(DEFAULT_AUDIOSRC) \ --with-default-videosink=$(DEFAULT_VIDEOSINK) \ --with-default-videosrc=$(DEFAULT_VIDEOSRC) \ --with-default-visualizer=$(DEFAULT_VISUALIZER) \ $(CONFIG_ARGS) clean:: # get rid of the sym links for i in $(PLUGINS); do \ rm -f debian/$(gst_pkgname)-$$i.install; \ rm -f debian/$(gst_pkgname)-$$i.preinst; \ done for f in $(VERSIONIZE); do \ rm -f debian/$(gst_pkgname)-$$f; \ done common-binary-fixup-arch:: dh_gstscancodecs DEB_INSTALL_DOCS_ALL += debian/README.Debian NEWS # Disable inclusion of large upstream ChangeLog DEB_INSTALL_CHANGELOGS_ALL := .PHONY: maint debian/build-deps0000664000000000000000000000155112256171424011151 0ustar @GST_LIB_DEV_DEP@ @GST_EXTRA_BUILD_DEPENDS@ libgstreamer-plugins-base0.10-dev (>= 0.10.36) autotools-dev dh-autoreconf automake (>= 1.10) autoconf (>= 2.60) libtool (>= 2.0) autopoint (>= 0.17) cdbs (>= 0.4.93) debhelper (>= 8.1.3) dpkg-dev (>= 1.15.1) pkg-config (>= 0.11.0) gtk-doc-tools gconf2 libglib2.0-dev (>= 2.24) liborc-0.4-dev (>= 1:0.4.11) libcairo2-dev (>= 1.10.0) libcaca-dev libspeex-dev (>= 1.1.6) libpng-dev libshout3-dev libjpeg-dev libaa1-dev (>= 1.4p5) libflac-dev (>= 1.1.4) libdv4-dev | libdv-dev libgconf2-dev libxdamage-dev libxext-dev libxfixes-dev libxv-dev libxml2-dev libgtk2.0-dev (>= 2.8) libtag1-dev (>= 1.5) libwavpack-dev (>= 4.20) gstreamer-tools (>= 0.10.30) gstreamer0.10-plugins-base (>= 0.10.36) libsoup-gnome2.4-dev (>= 2.26) libpulse-dev (>= 1.0) libbz2-dev gstreamer0.10-doc gstreamer0.10-plugins-base-doc libjack-dev (>= 1:0.99.10) debian/gstreamer-gconf.install0000664000000000000000000000017112061315514013640 0ustar debian/tmp/etc/gconf/schemas/gstreamer-@GST_ABI@.schemas debian/tmp/usr/lib/*/gstreamer-@GST_ABI@/libgstgconfelements.so debian/build-deps.in0000664000000000000000000000155112061315514011550 0ustar @GST_LIB_DEV_DEP@ @GST_EXTRA_BUILD_DEPENDS@ libgstreamer-plugins-base0.10-dev (>= 0.10.36) autotools-dev dh-autoreconf automake (>= 1.10) autoconf (>= 2.60) libtool (>= 2.0) autopoint (>= 0.17) cdbs (>= 0.4.93) debhelper (>= 8.1.3) dpkg-dev (>= 1.15.1) pkg-config (>= 0.11.0) gtk-doc-tools gconf2 libglib2.0-dev (>= 2.24) liborc-0.4-dev (>= 1:0.4.11) libcairo2-dev (>= 1.10.0) libcaca-dev libspeex-dev (>= 1.1.6) libpng-dev libshout3-dev libjpeg-dev libaa1-dev (>= 1.4p5) libflac-dev (>= 1.1.4) libdv4-dev | libdv-dev libgconf2-dev libxdamage-dev libxext-dev libxfixes-dev libxv-dev libxml2-dev libgtk2.0-dev (>= 2.8) libtag1-dev (>= 1.5) libwavpack-dev (>= 4.20) gstreamer-tools (>= 0.10.30) gstreamer0.10-plugins-base (>= 0.10.36) libsoup-gnome2.4-dev (>= 2.26) libpulse-dev (>= 1.0) libbz2-dev gstreamer0.10-doc gstreamer0.10-plugins-base-doc libjack-dev (>= 1:0.99.10) debian/maint0000775000000000000000000000047612061315514010233 0ustar #!/usr/bin/make -f missing: make -f debian/rules list-missing missing-so: make -f debian/rules list-missing | grep so$$ | cut -c 2- missing-libs: for i in `make -f debian/rules list-missing | grep so$$ | cut -c 2-`; do echo "=== $$i ==="; ldd debian/tmp/$$i; echo; done .PHONY: missing missing-so missing-libs debian/control.in0000664000000000000000000001323012061315530011173 0ustar Source: gst-plugins-good@GST_ABI@ Section: libs Priority: optional Maintainer: Ubuntu Desktop Team XSBC-Original-Maintainer: Maintainers of GStreamer packages Uploaders: Loic Minier , Sebastian Dröge , Sjoerd Simons Build-Depends: BUILDDEPS Standards-Version: 3.8.4 Package: @GST_PKGNAME@-plugins-good-doc Architecture: all Section: doc Depends: gstreamer0.10-doc, gstreamer0.10-plugins-base-doc, ${misc:Depends} Description: GStreamer documentation for plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains documentation for plugins from the "good" set, a set of good-quality plug-ins under the LGPL license. Package: @GST_PKGNAME@-pulseaudio Architecture: any Multi-Arch: same Section: sound Depends: ${misc:Depends}, ${shlibs:Depends} XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Provides: ${gstreamer:Provides} Description: GStreamer plugin for PulseAudio GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugin for PulseAudio, a sound server for POSIX and WIN32 systems. Package: @GST_PKGNAME@-gconf Architecture: any Multi-Arch: same Section: sound Depends: ${misc:Depends}, ${shlibs:Depends} XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Replaces: gstreamer0.10-plugins-good (<< 0.10.30-2) Breaks: gstreamer0.10-plugins-good (<< 0.10.30-2) Description: GStreamer plugin for getting the sink/source information from GConf GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugin for getting the information about which sources or sinks should be used for audio and video from GConf. Package: @GST_PKGNAME@-plugins-good Architecture: any Multi-Arch: same Section: libs Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${shlibs:Depends}, @GST_PKGNAME@-pulseaudio | @GST_PKGNAME@-audiosink, gstreamer0.10-plugins-base Recommends: @GST_PKGNAME@-x Replaces: gstreamer0.10-plugins-bad (<< 0.10.21.2), gstreamer0.10-plugins-really-bad (<< 0.10.21.2), gstreamer0.10-plugins-good-doc (<< 0.10.6-2) Breaks: gstreamer0.10-plugins-bad (<< 0.10.21.2), gstreamer0.10-plugins-really-bad (<< 0.10.21.2) XB-GStreamer-Version: ${gstreamer:Version} XB-GStreamer-Elements: ${gstreamer:Elements} XB-GStreamer-URI-Sources: ${gstreamer:URISources} XB-GStreamer-URI-Sinks: ${gstreamer:URISinks} XB-GStreamer-Encoders: ${gstreamer:Encoders} XB-GStreamer-Decoders: ${gstreamer:Decoders} Provides: ${gstreamer:Provides} Description: GStreamer plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains the GStreamer plugins from the "good" set, a set of good-quality plug-ins under the LGPL license. Package: @GST_PKGNAME@-plugins-good-dbg Architecture: any Multi-Arch: same Section: debug Priority: extra Depends: @GST_PKGNAME@-plugins-good (= ${binary:Version}), @GST_PKGNAME@-pulseaudio (= ${binary:Version}), @GST_PKGNAME@-gconf (= ${binary:Version}), ${misc:Depends} Replaces: gstreamer0.10-plugins-bad-dbg (<< 0.10.21.2) Breaks: gstreamer0.10-plugins-bad-dbg (<< 0.10.21.2) Description: GStreamer plugins from the "good" set GStreamer is a streaming media framework, based on graphs of filters which operate on media data. Applications using this library can do anything from real-time sound processing to playing videos, and just about anything else media-related. Its plugin-based architecture means that new data types or processing capabilities can be added simply by installing new plug-ins. . This package contains unstripped shared libraries. It is provided primarily to provide a backtrace with names in a debugger, this makes it somewhat easier to interpret core dumps. The libraries are installed in /usr/lib/debug and are automatically used by gdb. debian/gstreamer-plugins-good-doc.install0000664000000000000000000000003512061315514015715 0ustar debian/tmp/usr/share/gtk-doc debian/watch0000664000000000000000000000014612061315514010220 0ustar version=2 http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-(0\.10\..*)\.tar\.gz