From 8a3b45aa1f5d6fcff5d8020a6ceddc0b7fc84353 Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Fri, 13 Aug 2010 17:36:38 +0200 Subject: [PATCH] gst: New encoding plugin https://bugzilla.gnome.org/show_bug.cgi?id=627476 --- configure.ac | 2 + docs/plugins/Makefile.am | 3 +- .../gst-plugins-base-plugins-docs.sgml | 2 + .../gst-plugins-base-plugins-sections.txt | 16 + docs/plugins/gst-plugins-base-plugins.args | 100 + .../gst-plugins-base-plugins.hierarchy | 1 + .../gst-plugins-base-plugins.interfaces | 1 + docs/plugins/gst-plugins-base-plugins.signals | 8 + docs/plugins/inspect/plugin-encoding.xml | 46 + docs/plugins/inspect/plugin-libvisual.xml | 168 -- gst/encoding/.gitignore | 1 + gst/encoding/Makefile.am | 41 + gst/encoding/gstencode-marshal.list | 1 + gst/encoding/gstencodebin.c | 1658 +++++++++++++++++ gst/encoding/gstencodebin.h | 39 + gst/encoding/gstsmartencoder.c | 701 +++++++ gst/encoding/gstsmartencoder.h | 71 + gst/encoding/gststreamcombiner.c | 276 +++ gst/encoding/gststreamcombiner.h | 60 + gst/encoding/gststreamsplitter.c | 431 +++++ gst/encoding/gststreamsplitter.h | 62 + tests/check/Makefile.am | 4 + tests/check/elements/.gitignore | 1 + tests/check/elements/encodebin.c | 742 ++++++++ 24 files changed, 4266 insertions(+), 169 deletions(-) create mode 100644 docs/plugins/inspect/plugin-encoding.xml create mode 100644 gst/encoding/.gitignore create mode 100644 gst/encoding/Makefile.am create mode 100644 gst/encoding/gstencode-marshal.list create mode 100644 gst/encoding/gstencodebin.c create mode 100644 gst/encoding/gstencodebin.h create mode 100644 gst/encoding/gstsmartencoder.c create mode 100644 gst/encoding/gstsmartencoder.h create mode 100644 gst/encoding/gststreamcombiner.c create mode 100644 gst/encoding/gststreamcombiner.h create mode 100644 gst/encoding/gststreamsplitter.c create mode 100644 gst/encoding/gststreamsplitter.h create mode 100644 tests/check/elements/encodebin.c diff --git a/configure.ac b/configure.ac index b1df1c68e4..64f877cf65 100644 --- a/configure.ac +++ b/configure.ac @@ -417,6 +417,7 @@ AG_GST_CHECK_PLUGIN(app) AG_GST_CHECK_PLUGIN(audioconvert) AG_GST_CHECK_PLUGIN(audiorate) AG_GST_CHECK_PLUGIN(audiotestsrc) +AG_GST_CHECK_PLUGIN(encoding) AG_GST_CHECK_PLUGIN(ffmpegcolorspace) AG_GST_CHECK_PLUGIN(gdp) AG_GST_CHECK_PLUGIN(playback) @@ -914,6 +915,7 @@ gst/app/Makefile gst/audioconvert/Makefile gst/audiorate/Makefile gst/audiotestsrc/Makefile +gst/encoding/Makefile gst/ffmpegcolorspace/Makefile gst/gdp/Makefile gst/playback/Makefile diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index 4b784337dd..9beea7e55f 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -50,7 +50,7 @@ MKDB_OPTIONS=--sgml-mode # Extra options to supply to gtkdoc-fixref. FIXXREF_OPTIONS=--extra-dir=$(GLIB_PREFIX)/share/gtk-doc/html \ --extra-dir=$(GST_PREFIX)/share/gtk-doc/html \ - --extra-dir=$(datadir)/gtk-doc/html + --extra-dir=$(datadir)/gtk-doc/html # Used for dependencies. HFILE_GLOB=$(DOC_SOURCE_DIR)/*/*/*.h @@ -102,6 +102,7 @@ EXTRA_HFILES = \ $(top_srcdir)/gst/audioconvert/audioconvert.h \ $(top_srcdir)/gst/audioconvert/gstaudioconvert.h \ $(top_srcdir)/gst/audiotestsrc/gstaudiotestsrc.h \ + $(top_srcdir)/gst/encoding/gstencodebin.h \ $(top_srcdir)/gst/ffmpegcolorspace/gstffmpegcolorspace.h \ $(top_srcdir)/gst/gdp/gstgdpdepay.h \ $(top_srcdir)/gst/gdp/gstgdppay.h \ diff --git a/docs/plugins/gst-plugins-base-plugins-docs.sgml b/docs/plugins/gst-plugins-base-plugins-docs.sgml index 9bcbbf895c..ea603d6012 100644 --- a/docs/plugins/gst-plugins-base-plugins-docs.sgml +++ b/docs/plugins/gst-plugins-base-plugins-docs.sgml @@ -31,6 +31,7 @@ + @@ -80,6 +81,7 @@ + diff --git a/docs/plugins/gst-plugins-base-plugins-sections.txt b/docs/plugins/gst-plugins-base-plugins-sections.txt index e241371f65..e94c450985 100644 --- a/docs/plugins/gst-plugins-base-plugins-sections.txt +++ b/docs/plugins/gst-plugins-base-plugins-sections.txt @@ -247,6 +247,22 @@ GstDecodeBin2 +
+element-encodebin +encodebin +GstEncodeBin + +GST_ENCODE_BIN +GST_ENCODE_BIN_CLASS +GST_IS_ENCODE_BIN +GST_IS_ENCODE_BIN_CLASS +GST_TYPE_ENCODE_BIN +GstEncodeBinClass +gst_encode_bin_get_type +
+ + +
element-ffmpegcolorspace ffmpegcolorspace diff --git a/docs/plugins/gst-plugins-base-plugins.args b/docs/plugins/gst-plugins-base-plugins.args index 05ef80fc0d..d3a40c5156 100644 --- a/docs/plugins/gst-plugins-base-plugins.args +++ b/docs/plugins/gst-plugins-base-plugins.args @@ -158,6 +158,26 @@ TRUE + +GstXvImageSink::window-height +guint64 + +r +window-height +Height of the window. +0 + + + +GstXvImageSink::window-width +guint64 + +r +window-width +Width of the window. +0 + + GstXImageSink::display gchar* @@ -218,6 +238,26 @@ TRUE + +GstXImageSink::window-height +guint64 + +r +window-height +Height of the window. +0 + + + +GstXImageSink::window-width +guint64 + +r +window-width +Width of the window. +0 + + GstV4lSrc::autoprobe gboolean @@ -3378,3 +3418,63 @@ NULL + +GstEncodeBin::audio-jitter-tolerance +guint64 + +rw +Audio jitter tolerance +Amount of timestamp jitter/imperfection to allow on audio streams before inserting/dropping samples (ns). +20000000 + + + +GstEncodeBin::avoid-reencoding +gboolean + +rw +Avoid re-encoding +Whether to re-encode portions of compatible video streams that lay on segment boundaries. +FALSE + + + +GstEncodeBin::profile +GstEncodingProfile* + +rw +Profile +The GstEncodingProfile to use. + + + + +GstEncodeBin::queue-buffers-max +guint + +rw +Max. size (buffers) +Max. number of buffers in the queue (0=disable). +200 + + + +GstEncodeBin::queue-bytes-max +guint + +rw +Max. size (kB) +Max. amount of data in the queue (bytes, 0=disable). +10485760 + + + +GstEncodeBin::queue-time-max +guint64 + +rw +Max. size (ns) +Max. amount of data in the queue (in ns, 0=disable). +1000000000 + + diff --git a/docs/plugins/gst-plugins-base-plugins.hierarchy b/docs/plugins/gst-plugins-base-plugins.hierarchy index 4a355e6596..80aed2e9a5 100644 --- a/docs/plugins/gst-plugins-base-plugins.hierarchy +++ b/docs/plugins/gst-plugins-base-plugins.hierarchy @@ -56,6 +56,7 @@ GObject GstBin GstDecodeBin GstDecodeBin2 + GstEncodeBin GstPipeline GstPlayBaseBin GstPlayBin diff --git a/docs/plugins/gst-plugins-base-plugins.interfaces b/docs/plugins/gst-plugins-base-plugins.interfaces index 36963b528f..630084dd30 100644 --- a/docs/plugins/gst-plugins-base-plugins.interfaces +++ b/docs/plugins/gst-plugins-base-plugins.interfaces @@ -8,6 +8,7 @@ GstPlaySink GstChildProxy GstSubtitleOverlay GstChildProxy GstDecodeBin2 GstChildProxy GstURIDecodeBin GstChildProxy +GstEncodeBin GstChildProxy GstCddaBaseSrc GstURIHandler GstCdParanoiaSrc GstURIHandler GstAlsaSrc GstImplementsInterface GstMixer GstPropertyProbe diff --git a/docs/plugins/gst-plugins-base-plugins.signals b/docs/plugins/gst-plugins-base-plugins.signals index 2f5fab5ae5..621f880b3f 100644 --- a/docs/plugins/gst-plugins-base-plugins.signals +++ b/docs/plugins/gst-plugins-base-plugins.signals @@ -471,3 +471,11 @@ GstPlaySink *gstplaysink GstCaps *arg1 + +GstEncodeBin::request-pad +GstPad* +la +GstEncodeBin *gstencodebin +GstCaps *arg1 + + diff --git a/docs/plugins/inspect/plugin-encoding.xml b/docs/plugins/inspect/plugin-encoding.xml new file mode 100644 index 0000000000..ab61ef771b --- /dev/null +++ b/docs/plugins/inspect/plugin-encoding.xml @@ -0,0 +1,46 @@ + + encoding + various encoding-related elements + ../../gst/encoding/.libs/libgstencodebin.so + libgstencodebin.so + 0.10.31.1 + LGPL + gst-plugins-base + GStreamer Base Plug-ins git + Unknown package origin + + + encodebin + Encoder Bin + Generic/Bin/Encoder + Convenience encoding/muxing element + Edward Hervey <edward.hervey@collabora.co.uk> + + + audio_%d + sink + request +
ANY
+
+ + private_%d + sink + request +
ANY
+
+ + video_%d + sink + request +
ANY
+
+ + src + source + always +
ANY
+
+
+
+
+
\ No newline at end of file diff --git a/docs/plugins/inspect/plugin-libvisual.xml b/docs/plugins/inspect/plugin-libvisual.xml index 22f4e8f048..0af3fc75d5 100644 --- a/docs/plugins/inspect/plugin-libvisual.xml +++ b/docs/plugins/inspect/plugin-libvisual.xml @@ -9,173 +9,5 @@ GStreamer Base Plug-ins git Unknown package origin - - libvisual_bumpscope - libvisual Bumpscope plugin plugin v.0.0.1 - Visualization - Bumpscope visual plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_corona - libvisual libvisual corona plugin plugin v.0.1 - Visualization - Libvisual corona plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_infinite - libvisual infinite plugin plugin v.0.1 - Visualization - Infinite visual plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_jakdaw - libvisual Jakdaw plugin plugin v.0.0.1 - Visualization - jakdaw visual plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_jess - libvisual jess plugin plugin v.0.1 - Visualization - Jess visual plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_lv_analyzer - libvisual libvisual analyzer plugin v.1.0 - Visualization - Libvisual analyzer plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_lv_scope - libvisual libvisual scope plugin v.0.1 - Visualization - Libvisual scope plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
- - libvisual_oinksie - libvisual oinksie plugin plugin v.0.1 - Visualization - Libvisual Oinksie visual plugin - Benjamin Otte <otte@gnome.org> - - - sink - sink - always -
audio/x-raw-int, width=(int)16, depth=(int)16, endianness=(int)1234, signed=(boolean)true, channels=(int){ 1, 2 }, rate=(int){ 8000, 11250, 22500, 32000, 44100, 48000, 96000 }
-
- - src - source - always -
video/x-raw-rgb, bpp=(int)32, depth=(int)24, endianness=(int)4321, red_mask=(int)65280, green_mask=(int)16711680, blue_mask=(int)-16777216, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)24, depth=(int)24, endianness=(int)4321, red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]; video/x-raw-rgb, bpp=(int)16, depth=(int)16, endianness=(int)1234, red_mask=(int)63488, green_mask=(int)2016, blue_mask=(int)31, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]
-
-
-
\ No newline at end of file diff --git a/gst/encoding/.gitignore b/gst/encoding/.gitignore new file mode 100644 index 0000000000..ff44545891 --- /dev/null +++ b/gst/encoding/.gitignore @@ -0,0 +1 @@ +gstencode-marshal.[ch] diff --git a/gst/encoding/Makefile.am b/gst/encoding/Makefile.am new file mode 100644 index 0000000000..0e287fad13 --- /dev/null +++ b/gst/encoding/Makefile.am @@ -0,0 +1,41 @@ +# variables used for enum/marshal generation +glib_enum_define = GST_ENCODE +glib_gen_prefix = gst_encode +glib_gen_basename = gstencode + +built_sources = gstencode-marshal.c +built_headers = gstencode-marshal.h + +plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@ + +plugin_LTLIBRARIES = libgstencodebin.la + +libgstencodebin_la_SOURCES = \ + gstencodebin.c \ + gstsmartencoder.c \ + gststreamcombiner.c \ + gststreamsplitter.c + +nodist_libgstencodebin_la_SOURCES = $(built_sources) +libgstencodebin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstencodebin_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstencodebin_la_LIBADD = \ + $(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la \ + $(GST_LIBS) +libgstencodebin_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = \ + gstencodebin.h \ + gststreamcombiner.h \ + gststreamsplitter.h \ + gstsmartencoder.h + + +BUILT_SOURCES = $(built_headers) $(built_sources) + +EXTRA_DIST = gstencode-marshal.list + +CLEANFILES = $(BUILT_SOURCES) + +include $(top_srcdir)/common/gst-glib-gen.mak + diff --git a/gst/encoding/gstencode-marshal.list b/gst/encoding/gstencode-marshal.list new file mode 100644 index 0000000000..00f26ed2bc --- /dev/null +++ b/gst/encoding/gstencode-marshal.list @@ -0,0 +1 @@ +OBJECT:BOXED diff --git a/gst/encoding/gstencodebin.c b/gst/encoding/gstencodebin.c new file mode 100644 index 0000000000..3ba5a403dd --- /dev/null +++ b/gst/encoding/gstencodebin.c @@ -0,0 +1,1658 @@ +/* GStreamer encoding bin + * Copyright (C) 2009 Edward Hervey + * (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 "gstencodebin.h" +#include "gstsmartencoder.h" +#include "gststreamsplitter.h" +#include "gststreamcombiner.h" +#include + +/** + * SECTION:element-encodebin + * + * encodebin provides a bin for encoding/muxing various streams according to + * a specified #GstEncodingProfile. + * + * + */ + + +/* TODO/FIXME + * + * Handling mp3!xing!idv3 and theora!ogg tagsetting scenarios: + * Once we have chosen a muxer: + * When a new stream is requested: + * If muxer is 'Formatter' OR doesn't have a TagSetter interface: + * Find a Formatter for the given stream (preferably with TagSetter) + * Insert that before muxer + **/ + +#define fast_pad_link(a,b) gst_pad_link_full((a),(b),GST_PAD_LINK_CHECK_NOTHING) +#define fast_element_link(a,b) gst_element_link_pads_full((a),"src",(b),"sink",GST_PAD_LINK_CHECK_NOTHING) + +/* generic templates */ +static GstStaticPadTemplate muxer_src_template = +GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate video_sink_template = +GST_STATIC_PAD_TEMPLATE ("video_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate audio_sink_template = +GST_STATIC_PAD_TEMPLATE ("audio_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); +/* static GstStaticPadTemplate text_sink_template = */ +/* GST_STATIC_PAD_TEMPLATE ("text_%d", */ +/* GST_PAD_SINK, */ +/* GST_PAD_REQUEST, */ +/* GST_STATIC_CAPS_ANY); */ +static GstStaticPadTemplate private_sink_template = +GST_STATIC_PAD_TEMPLATE ("private_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +struct _GstEncodeBin +{ + GstBin parent; + + /* the profile field is only valid if it could be entirely setup */ + GstEncodingProfile *profile; + + GList *streams; /* List of StreamGroup, not sorted */ + + GstElement *muxer; + GstPad *srcpad; + + /* TRUE if in PAUSED/PLAYING */ + gboolean active; + + /* available muxers, encoders and parsers */ + GList *muxers; + GList *encoders; + GList *parsers; + + /* Increasing counter for unique pad name */ + guint last_pad_id; + + /* Cached caps for identification */ + GstCaps *raw_video_caps; + GstCaps *raw_audio_caps; + /* GstCaps *raw_text_caps; */ + + guint queue_buffers_max; + guint queue_bytes_max; + guint64 queue_time_max; + + guint64 tolerance; + gboolean avoid_reencoding; +}; + +struct _GstEncodeBinClass +{ + GstBinClass parent; + + /* Action Signals */ + GstPad *(*request_pad) (GstEncodeBin * encodebin, GstCaps * caps); +}; + +typedef struct _StreamGroup StreamGroup; + +struct _StreamGroup +{ + GstEncodeBin *ebin; + GstEncodingProfile *profile; + GstPad *ghostpad; /* Sink ghostpad */ + GstElement *inqueue; /* Queue just after the ghostpad */ + GstElement *splitter; + GList *converters; /* List of conversion GstElement */ + GstElement *capsfilter; /* profile->restriction (if non-NULL/ANY) */ + GstElement *encoder; /* Encoder (can be NULL) */ + GstElement *combiner; + GstElement *parser; + GstElement *smartencoder; + GstElement *outfilter; /* Output capsfilter (streamprofile.format) */ + GstElement *outqueue; /* Queue just before the muxer */ +}; + +/* Default for queues (same defaults as queue element) */ +#define DEFAULT_QUEUE_BUFFERS_MAX 200 +#define DEFAULT_QUEUE_BYTES_MAX 10 * 1024 * 1024 +#define DEFAULT_QUEUE_TIME_MAX GST_SECOND +#define DEFAULT_AUDIO_JITTER_TOLERANCE 20 * GST_MSECOND +#define DEFAULT_AVOID_REENCODING FALSE + +#define DEFAULT_RAW_CAPS \ + "video/x-raw-yuv; " \ + "video/x-raw-rgb; " \ + "video/x-raw-gray; " \ + "audio/x-raw-int; " \ + "audio/x-raw-float; " \ + "text/plain; " \ + "text/x-pango-markup; " \ + "video/x-dvd-subpicture; " \ + "subpicture/x-pgs" + +/* Properties */ +enum +{ + PROP_0, + PROP_PROFILE, + PROP_QUEUE_BUFFERS_MAX, + PROP_QUEUE_BYTES_MAX, + PROP_QUEUE_TIME_MAX, + PROP_AUDIO_JITTER_TOLERANCE, + PROP_AVOID_REENCODING, + PROP_LAST +}; + +/* Signals */ +enum +{ + SIGNAL_REQUEST_PAD, + LAST_SIGNAL +}; + +static guint gst_encode_bin_signals[LAST_SIGNAL] = { 0 }; + +static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS); + +GST_DEBUG_CATEGORY_STATIC (gst_encode_bin_debug); +#define GST_CAT_DEFAULT gst_encode_bin_debug + +G_DEFINE_TYPE (GstEncodeBin, gst_encode_bin, GST_TYPE_BIN); + +static void gst_encode_bin_dispose (GObject * object); +static void gst_encode_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_encode_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static GstStateChangeReturn gst_encode_bin_change_state (GstElement * element, + GstStateChange transition); + +static GstPad *gst_encode_bin_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_encode_bin_release_pad (GstElement * element, GstPad * pad); + +static gboolean +gst_encode_bin_set_profile (GstEncodeBin * ebin, GstEncodingProfile * profile); +static void gst_encode_bin_tear_down_profile (GstEncodeBin * ebin); +static gboolean gst_encode_bin_setup_profile (GstEncodeBin * ebin, + GstEncodingProfile * profile); + +static StreamGroup *_create_stream_group (GstEncodeBin * ebin, + GstEncodingProfile * sprof, const gchar * sinkpadname, GstCaps * sinkcaps); +static void stream_group_remove (GstEncodeBin * ebin, StreamGroup * sgroup); +static GstPad *gst_encode_bin_request_pad_signal (GstEncodeBin * encodebin, + GstCaps * caps); + +static void +gst_encode_bin_class_init (GstEncodeBinClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *gstelement_klass; + + gobject_klass = (GObjectClass *) klass; + gstelement_klass = (GstElementClass *) klass; + + gobject_klass->dispose = gst_encode_bin_dispose; + gobject_klass->set_property = gst_encode_bin_set_property; + gobject_klass->get_property = gst_encode_bin_get_property; + + /* Properties */ + g_object_class_install_property (gobject_klass, PROP_PROFILE, + gst_param_spec_mini_object ("profile", "Profile", + "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_QUEUE_BUFFERS_MAX, + g_param_spec_uint ("queue-bytes-max", "Max. size (kB)", + "Max. amount of data in the queue (bytes, 0=disable)", + 0, G_MAXUINT, DEFAULT_QUEUE_BYTES_MAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_QUEUE_BYTES_MAX, + g_param_spec_uint ("queue-buffers-max", "Max. size (buffers)", + "Max. number of buffers in the queue (0=disable)", 0, G_MAXUINT, + DEFAULT_QUEUE_BUFFERS_MAX, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_QUEUE_TIME_MAX, + g_param_spec_uint64 ("queue-time-max", "Max. size (ns)", + "Max. amount of data in the queue (in ns, 0=disable)", 0, G_MAXUINT64, + DEFAULT_QUEUE_TIME_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_AUDIO_JITTER_TOLERANCE, + g_param_spec_uint64 ("audio-jitter-tolerance", "Audio jitter tolerance", + "Amount of timestamp jitter/imperfection to allow on audio streams before inserting/dropping samples (ns)", + 0, G_MAXUINT64, DEFAULT_AUDIO_JITTER_TOLERANCE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_klass, PROP_AVOID_REENCODING, + g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", + "Whether to re-encode portions of compatible video streams that lay on segment boundaries", + DEFAULT_AVOID_REENCODING, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /* Signals */ + gst_encode_bin_signals[SIGNAL_REQUEST_PAD] = + g_signal_new ("request-pad", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstEncodeBinClass, + request_pad), NULL, NULL, gst_encode_marshal_OBJECT__BOXED, + GST_TYPE_PAD, 1, GST_TYPE_CAPS); + + klass->request_pad = gst_encode_bin_request_pad_signal; + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&muxer_src_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&video_sink_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&audio_sink_template)); + /* gst_element_class_add_pad_template (gstelement_klass, */ + /* gst_static_pad_template_get (&text_sink_template)); */ + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&private_sink_template)); + + gstelement_klass->change_state = + GST_DEBUG_FUNCPTR (gst_encode_bin_change_state); + gstelement_klass->request_new_pad = + GST_DEBUG_FUNCPTR (gst_encode_bin_request_new_pad); + gstelement_klass->release_pad = + GST_DEBUG_FUNCPTR (gst_encode_bin_release_pad); + + gst_element_class_set_details_simple (gstelement_klass, + "Encoder Bin", + "Generic/Bin/Encoder", + "Convenience encoding/muxing element", + "Edward Hervey "); +} + +static void +gst_encode_bin_dispose (GObject * object) +{ + GstEncodeBin *ebin = (GstEncodeBin *) object; + + if (ebin->muxers) + gst_plugin_feature_list_free (ebin->muxers); + + if (ebin->encoders) + gst_plugin_feature_list_free (ebin->encoders); + + if (ebin->parsers) + gst_plugin_feature_list_free (ebin->parsers); + + gst_encode_bin_tear_down_profile (ebin); + + if (ebin->raw_video_caps) + gst_caps_unref (ebin->raw_video_caps); + if (ebin->raw_audio_caps) + gst_caps_unref (ebin->raw_audio_caps); + /* if (ebin->raw_text_caps) */ + /* gst_caps_unref (ebin->raw_text_caps); */ + + G_OBJECT_CLASS (gst_encode_bin_parent_class)->dispose (object); +} + +static void +gst_encode_bin_init (GstEncodeBin * encode_bin) +{ + GstPadTemplate *tmpl; + + encode_bin->muxers = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_MUXER, + GST_RANK_MARGINAL); + encode_bin->encoders = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_ENCODER, + GST_RANK_MARGINAL); + encode_bin->parsers = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_PARSER, + GST_RANK_MARGINAL); + + encode_bin->raw_video_caps = + gst_caps_from_string ("video/x-raw-yuv;video/x-raw-rgb;video/x-raw-gray"); + encode_bin->raw_audio_caps = + gst_caps_from_string ("audio/x-raw-int;audio/x-raw-float"); + /* encode_bin->raw_text_caps = */ + /* gst_caps_from_string ("text/plain;text/x-pango-markup"); */ + + encode_bin->queue_buffers_max = DEFAULT_QUEUE_BUFFERS_MAX; + encode_bin->queue_bytes_max = DEFAULT_QUEUE_BYTES_MAX; + encode_bin->queue_time_max = DEFAULT_QUEUE_TIME_MAX; + encode_bin->tolerance = DEFAULT_AUDIO_JITTER_TOLERANCE; + encode_bin->avoid_reencoding = DEFAULT_AVOID_REENCODING; + + tmpl = gst_static_pad_template_get (&muxer_src_template); + encode_bin->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl); + gst_object_unref (tmpl); + gst_element_add_pad (GST_ELEMENT_CAST (encode_bin), encode_bin->srcpad); +} + +static void +gst_encode_bin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstEncodeBin *ebin = (GstEncodeBin *) object; + + switch (prop_id) { + case PROP_PROFILE: + gst_encode_bin_set_profile (ebin, + (GstEncodingProfile *) gst_value_get_mini_object (value)); + break; + case PROP_QUEUE_BUFFERS_MAX: + ebin->queue_buffers_max = g_value_get_uint (value); + break; + case PROP_QUEUE_BYTES_MAX: + ebin->queue_bytes_max = g_value_get_uint (value); + break; + case PROP_QUEUE_TIME_MAX: + ebin->queue_time_max = g_value_get_uint64 (value); + break; + case PROP_AUDIO_JITTER_TOLERANCE: + ebin->tolerance = g_value_get_uint64 (value); + break; + case PROP_AVOID_REENCODING: + ebin->avoid_reencoding = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_encode_bin_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstEncodeBin *ebin = (GstEncodeBin *) object; + + switch (prop_id) { + case PROP_PROFILE: + gst_value_set_mini_object (value, (GstMiniObject *) ebin->profile); + break; + case PROP_QUEUE_BUFFERS_MAX: + g_value_set_uint (value, ebin->queue_buffers_max); + break; + case PROP_QUEUE_BYTES_MAX: + g_value_set_uint (value, ebin->queue_bytes_max); + break; + case PROP_QUEUE_TIME_MAX: + g_value_set_uint64 (value, ebin->queue_time_max); + break; + case PROP_AUDIO_JITTER_TOLERANCE: + g_value_set_uint64 (value, ebin->tolerance); + break; + case PROP_AVOID_REENCODING: + g_value_set_boolean (value, ebin->avoid_reencoding); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static inline gboolean +are_raw_caps (const GstCaps * caps) +{ + GstCaps *raw = gst_static_caps_get (&default_raw_caps); + + if (gst_caps_can_intersect (caps, raw)) { + gst_caps_unref (raw); + return TRUE; + } + gst_caps_unref (raw); + return FALSE; +} + +/* Returns the number of time a given stream profile is currently used + * in encodebin */ +static inline guint +stream_profile_used_count (GstEncodeBin * ebin, GstEncodingProfile * sprof) +{ + guint nbprofused = 0; + GList *tmp; + + for (tmp = ebin->streams; tmp; tmp = tmp->next) { + StreamGroup *sgroup = (StreamGroup *) tmp->data; + + if (sgroup->profile == sprof) + nbprofused++; + } + + return nbprofused; +} + +static inline GstEncodingProfile * +next_unused_stream_profile (GstEncodeBin * ebin, GType ptype, GstCaps * caps) +{ + GST_DEBUG_OBJECT (ebin, "ptype:%d, caps:%" GST_PTR_FORMAT, ptype, caps); + + if (G_UNLIKELY (ptype == G_TYPE_NONE && caps != NULL)) { + /* Identify the profile type based on raw caps */ + if (gst_caps_can_intersect (ebin->raw_video_caps, caps)) + ptype = GST_TYPE_ENCODING_VIDEO_PROFILE; + else if (gst_caps_can_intersect (ebin->raw_audio_caps, caps)) + ptype = GST_TYPE_ENCODING_AUDIO_PROFILE; + /* else if (gst_caps_can_intersect (ebin->raw_text_caps, caps)) */ + /* ptype = GST_TYPE_ENCODING_TEXT_PROFILE; */ + GST_DEBUG_OBJECT (ebin, "Detected profile type as being %d", ptype); + } + + if (GST_IS_ENCODING_CONTAINER_PROFILE (ebin->profile)) { + const GList *tmp; + + for (tmp = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); tmp; + tmp = tmp->next) { + GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data; + + /* Pick an available Stream profile for which: + * * either it is of the compatibly raw type, + * * OR we can pass it through directly without encoding + */ + if (G_TYPE_FROM_INSTANCE (sprof) == ptype) { + guint presence = gst_encoding_profile_get_presence (sprof); + GST_DEBUG ("Found a stream profile with the same type"); + if ((presence == 0) + || (presence > stream_profile_used_count (ebin, sprof))) + return sprof; + } else if ((caps != NULL) && (ptype == G_TYPE_NONE)) { + GstCaps *outcaps; + gboolean res; + + outcaps = gst_encoding_profile_get_output_caps (sprof); + GST_DEBUG ("Unknown stream, seeing if it's compatible with %" + GST_PTR_FORMAT, outcaps); + res = gst_caps_can_intersect (outcaps, caps); + gst_caps_unref (outcaps); + + if (res) + return sprof; + } + } + } + + return NULL; +} + +static GstPad * +gst_encode_bin_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name) +{ + GstEncodeBin *ebin = (GstEncodeBin *) element; + GType ptype; + StreamGroup *sgroup; + GstEncodingProfile *sprof; + + GST_DEBUG_OBJECT (element, "templ:%s, name:%s", templ->name_template, name); + + /* Identify the stream group */ + + if (!strcmp (templ->name_template, "video_%d")) + ptype = GST_TYPE_ENCODING_VIDEO_PROFILE; + else if (!strcmp (templ->name_template, "audio_%d")) + ptype = GST_TYPE_ENCODING_AUDIO_PROFILE; + /* else if (!strcmp (templ->name_template, "text_%d")) */ + /* ptype = GST_TYPE_ENCODING_TEXT_PROFILE; */ + else + ptype = G_TYPE_NONE; + + /* FIXME : Check uniqueness of pad */ + /* FIXME : Check that the requested number is the last one, and if not, + * update the last_pad_id variable so that we don't create a pad with + * the same name/number in the future */ + + /* Find GstEncodingProfile which we need */ + sprof = next_unused_stream_profile (ebin, ptype, NULL); + if (G_UNLIKELY (sprof == NULL)) + goto no_stream_profile; + + sgroup = _create_stream_group (ebin, sprof, name, NULL); + if (G_UNLIKELY (sgroup == NULL)) + goto no_stream_group; + + return sgroup->ghostpad; + +no_stream_profile: + { + GST_WARNING_OBJECT (ebin, "Couldn't find a compatible stream profile"); + return NULL; + } + +no_stream_group: + { + GST_WARNING_OBJECT (ebin, "Couldn't create a StreamGroup"); + return NULL; + } +} + +static GstPad * +gst_encode_bin_request_pad_signal (GstEncodeBin * encodebin, GstCaps * caps) +{ + GstEncodingProfile *sprof; + StreamGroup *sgroup; + + GST_DEBUG_OBJECT (encodebin, "caps:%" GST_PTR_FORMAT, caps); + + /* Figure out if we have a unused GstEncodingProfile we can use for + * these caps */ + sprof = next_unused_stream_profile (encodebin, G_TYPE_NONE, caps); + if (G_UNLIKELY (sprof == NULL)) + goto no_stream_profile; + + sgroup = _create_stream_group (encodebin, sprof, NULL, caps); + if (G_UNLIKELY (sgroup == NULL)) + goto no_stream_group; + + return sgroup->ghostpad; + +no_stream_profile: + { + GST_WARNING_OBJECT (encodebin, "Couldn't find a compatible stream profile"); + return NULL; + } + +no_stream_group: + { + GST_WARNING_OBJECT (encodebin, "Couldn't create a StreamGroup"); + return NULL; + } +} + +static inline StreamGroup * +find_stream_group_from_pad (GstEncodeBin * ebin, GstPad * pad) +{ + GList *tmp; + + for (tmp = ebin->streams; tmp; tmp = tmp->next) { + StreamGroup *sgroup = (StreamGroup *) tmp->data; + if (G_UNLIKELY (sgroup->ghostpad == pad)) + return sgroup; + } + + return NULL; +} + +static void +gst_encode_bin_release_pad (GstElement * element, GstPad * pad) +{ + GstEncodeBin *ebin = (GstEncodeBin *) element; + StreamGroup *sgroup; + + /* Find the associated StreamGroup */ + + sgroup = find_stream_group_from_pad (ebin, pad); + if (G_UNLIKELY (sgroup == NULL)) + goto no_stream_group; + + /* Release objects/data associated with the StreamGroup */ + stream_group_remove (ebin, sgroup); + + return; + +no_stream_group: + { + GST_WARNING_OBJECT (ebin, "Couldn't find corresponding StreamGroup"); + return; + } +} + +/* Create a parser for the given stream profile */ +static inline GstElement * +_get_parser (GstEncodeBin * ebin, GstEncodingProfile * sprof) +{ + GList *parsers1, *parsers, *tmp; + GstElement *parser = NULL; + GstElementFactory *parserfact = NULL; + const GstCaps *format; + + format = gst_encoding_profile_get_format (sprof); + + GST_DEBUG ("Getting list of parsers for format %" GST_PTR_FORMAT, format); + + /* FIXME : requesting twice the parsers twice is a bit ugly, we should + * have a method to request on more than one condition */ + parsers1 = + gst_element_factory_list_filter (ebin->parsers, format, + GST_PAD_SRC, FALSE); + parsers = + gst_element_factory_list_filter (parsers1, format, GST_PAD_SINK, FALSE); + gst_plugin_feature_list_free (parsers1); + + if (G_UNLIKELY (parsers == NULL)) { + GST_DEBUG ("Couldn't find any compatible parsers"); + return NULL; + } + + for (tmp = parsers; tmp; tmp = tmp->next) { + /* FIXME : We're only picking the first one so far */ + /* FIXME : signal the user if he wants this */ + parserfact = (GstElementFactory *) tmp->data; + break; + } + + if (parserfact) + parser = gst_element_factory_create (parserfact, NULL); + + gst_plugin_feature_list_free (parsers); + + return parser; +} + +static GstElement * +_create_element_and_set_preset (GstElementFactory * factory, + const gchar * preset, const gchar * name) +{ + GstElement *res = NULL; + + GST_DEBUG ("Creating element from factory %s", + GST_PLUGIN_FEATURE_NAME (factory)); + res = gst_element_factory_create (factory, name); + if (preset && GST_IS_PRESET (res) && + !gst_preset_load_preset (GST_PRESET (res), preset)) { + GST_WARNING ("Couldn't set preset [%s] on element [%s]", + preset, GST_PLUGIN_FEATURE_NAME (factory)); + gst_object_unref (res); + res = NULL; + } + + return res; +} + +/* Create the encoder for the given stream profile */ +static inline GstElement * +_get_encoder (GstEncodeBin * ebin, GstEncodingProfile * sprof) +{ + GList *encoders, *tmp; + GstElement *encoder = NULL; + GstElementFactory *encoderfact = NULL; + const GstCaps *format; + const gchar *preset; + + format = gst_encoding_profile_get_format (sprof); + preset = gst_encoding_profile_get_preset (sprof); + + GST_DEBUG ("Getting list of encoders for format %" GST_PTR_FORMAT, format); + + /* If stream caps are raw, return identity */ + if (G_UNLIKELY (are_raw_caps (format))) { + GST_DEBUG ("Stream format is raw, returning identity as the encoder"); + encoder = gst_element_factory_make ("identity", NULL); + goto beach; + } + + encoders = + gst_element_factory_list_filter (ebin->encoders, format, + GST_PAD_SRC, FALSE); + + if (G_UNLIKELY (encoders == NULL)) { + GST_DEBUG ("Couldn't find any compatible encoders"); + goto beach; + } + + for (tmp = encoders; tmp; tmp = tmp->next) { + encoderfact = (GstElementFactory *) tmp->data; + if ((encoder = _create_element_and_set_preset (encoderfact, preset, NULL))) + break; + } + + gst_plugin_feature_list_free (encoders); + +beach: + return encoder; +} + +static GstPad * +gst_element_request_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name) +{ + GstPad *newpad = NULL; + GstElementClass *oclass; + + oclass = GST_ELEMENT_GET_CLASS (element); + + if (oclass->request_new_pad) + newpad = (oclass->request_new_pad) (element, templ, name); + + if (newpad) + gst_object_ref (newpad); + + return newpad; +} + +static GstPad * +gst_element_get_pad_from_template (GstElement * element, GstPadTemplate * templ) +{ + GstPad *ret = NULL; + GstPadPresence presence; + + /* If this function is ever exported, we need check the validity of `element' + * and `templ', and to make sure the template actually belongs to the + * element. */ + + presence = GST_PAD_TEMPLATE_PRESENCE (templ); + + switch (presence) { + case GST_PAD_ALWAYS: + case GST_PAD_SOMETIMES: + ret = gst_element_get_static_pad (element, templ->name_template); + if (!ret && presence == GST_PAD_ALWAYS) + g_warning + ("Element %s has an ALWAYS template %s, but no pad of the same name", + GST_OBJECT_NAME (element), templ->name_template); + break; + + case GST_PAD_REQUEST: + ret = gst_element_request_pad (element, templ, NULL); + break; + } + + return ret; +} + +/* FIXME : Improve algorithm for finding compatible muxer sink pad */ +static inline GstPad * +get_compatible_muxer_sink_pad (GstEncodeBin * ebin, GstElement * encoder, + const GstCaps * sinkcaps) +{ + GstPad *sinkpad; + GstPadTemplate *srctempl = NULL; + GstPadTemplate *sinktempl; + + if (encoder) { + GstPad *srcpad; + srcpad = gst_element_get_static_pad (encoder, "src"); + + srctempl = gst_pad_get_pad_template (srcpad); + + GST_DEBUG_OBJECT (ebin, + "Attempting to find pad from muxer %s compatible with %s:%s", + GST_ELEMENT_NAME (ebin->muxer), GST_DEBUG_PAD_NAME (srcpad)); + + gst_object_unref (srcpad); + sinktempl = gst_element_get_compatible_pad_template (ebin->muxer, srctempl); + } else { + srctempl = + gst_pad_template_new ("whatever", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_caps_copy (sinkcaps)); + g_assert (srctempl != NULL); + sinktempl = gst_element_get_compatible_pad_template (ebin->muxer, srctempl); + g_object_unref (srctempl); + } + + if (G_UNLIKELY (sinktempl == NULL)) + goto no_template; + + sinkpad = gst_element_get_pad_from_template (ebin->muxer, sinktempl); + + return sinkpad; + +no_template: + { + GST_WARNING_OBJECT (ebin, "No compatible pad available on muxer"); + return NULL; + } +} + +/* FIXME : Add handling of streams that don't need encoding */ +/* FIXME : Add handling of streams that don't require conversion elements */ +/* + * Create the elements, StreamGroup, add the sink pad, link it to the muxer + * + * sinkpadname: If non-NULL, that name will be assigned to the sink ghost pad + * sinkcaps: If non-NULL will be used to figure out how to setup the group */ +static StreamGroup * +_create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, + const gchar * sinkpadname, GstCaps * sinkcaps) +{ + StreamGroup *sgroup = NULL; + GstPad *sinkpad, *srcpad, *muxerpad; + /* Element we will link to the encoder */ + GstElement *last = NULL; + GList *tmp, *tosync = NULL; + const GstCaps *format; + const GstCaps *restriction; + + format = gst_encoding_profile_get_format (sprof); + restriction = gst_encoding_profile_get_restriction (sprof); + + GST_DEBUG ("Creating group. format %" GST_PTR_FORMAT ", for caps %" + GST_PTR_FORMAT, format, sinkcaps); + GST_DEBUG ("avoid_reencoding:%d", ebin->avoid_reencoding); + + sgroup = g_slice_new0 (StreamGroup); + sgroup->ebin = ebin; + sgroup->profile = sprof; + + /* NOTE for people reading this code: + * + * We construct the group starting by the furthest downstream element + * and making our way up adding/syncing/linking as we go. + * + * There are two parallel paths: + * * One for raw data which goes through converters and encoders + * * One for already encoded data + */ + + /* Exception to the rule above: + * We check if we have an available encoder so we can abort early */ + /* FIXME : What if we only want to do passthrough ??? */ + GST_LOG ("Checking for encoder availability"); + sgroup->encoder = _get_encoder (ebin, sprof); + if (G_UNLIKELY (sgroup->encoder == NULL)) + goto no_encoder; + + /* Muxer + * We first figure out if the muxer has a sinkpad compatible with the selected + * profile */ + muxerpad = get_compatible_muxer_sink_pad (ebin, NULL, format); + if (G_UNLIKELY (muxerpad == NULL)) + goto no_muxer_pad; + + /* Output Queue. + * We only use a 1buffer long queue here, the actual queueing will be done + * in the intput queue */ + last = sgroup->outqueue = gst_element_factory_make ("queue", NULL); + g_object_set (sgroup->outqueue, "max-size-buffers", (guint32) 1, + "max-size-bytes", (guint32) 0, "max-size-time", (guint64) 0, NULL); + + gst_bin_add (GST_BIN (ebin), sgroup->outqueue); + tosync = g_list_append (tosync, sgroup->outqueue); + srcpad = gst_element_get_static_pad (sgroup->outqueue, "src"); + if (G_UNLIKELY (fast_pad_link (srcpad, muxerpad) != GST_PAD_LINK_OK)) { + goto muxer_link_failure; + } + gst_object_unref (srcpad); + gst_object_unref (muxerpad); + + /* Output capsfilter + * This will receive the format caps from the streamprofile */ + GST_DEBUG ("Adding output capsfilter for %" GST_PTR_FORMAT, format); + sgroup->outfilter = gst_element_factory_make ("capsfilter", NULL); + g_object_set (sgroup->outfilter, "caps", format, NULL); + + gst_bin_add (GST_BIN (ebin), sgroup->outfilter); + tosync = g_list_append (tosync, sgroup->outfilter); + if (G_UNLIKELY (!fast_element_link (sgroup->outfilter, last))) + goto outfilter_link_failure; + last = sgroup->outfilter; + + + /* FIXME : + * + * The usage of parsers in encoding/muxing scenarios is + * just too undefined to just use as-is. + * + * Take the use-case where you want to re-mux a stream of type + * "my/media". You create a StreamEncodingProfile with that type + * as the target (as-is). And you use decodebin2/uridecodebin + * upstream. + * + * * demuxer exposes "my/media" + * * a parser is available for "my/media" which has a source pad + * caps of "my/media,parsed=True" + * * decodebin2/uridecodebin exposes a new pad with the parsed caps + * * You request a new stream from encodebin, which will match the + * streamprofile and creates a group (i.e. going through this method) + * There is a matching parser (the same used in the decoder) whose + * source pad caps intersects with the stream profile caps, you + * therefore use it... + * * ... but that parser has a "my/media,parsed=False" sink pad caps + * * ... and you can't link your decodebin pad to encodebin. + * + * In the end, it comes down to parsers only taking into account the + * decoding use-cases. + * + * One way to solve that might be to : + * * Make parsers sink pad caps be "framed={False,True}" and the + * source pad caps be "framed=True" + * * Modify decodebin2 accordingly to avoid looping and chaining + * an infinite number of parsers + * + * Another way would be to have "well-known" caps properties to specify + * whether a stream has been parsed or not. + * * currently we fail. aacparse uses 'framed' and mp3parse uses 'parsed' + */ + /* FIXME : Re-enable once parser situation is un-$#*@(%$#ed */ +#if 0 + /* Parser. + * FIXME : identify smart parsers (used for re-encoding) */ + sgroup->parser = _get_parser (ebin, sprof); + + if (sgroup->parser != NULL) { + GST_DEBUG ("Got a parser %s", GST_ELEMENT_NAME (sgroup->parser)); + gst_bin_add (GST_BIN (ebin), sgroup->parser); + tosync = g_list_append (tosync, sgroup->parser); + if (G_UNLIKELY (!gst_element_link (sgroup->parser, last))) + goto parser_link_failure; + last = sgroup->parser; + } +#endif + + /* Stream combiner */ + sgroup->combiner = g_object_new (GST_TYPE_STREAM_COMBINER, NULL); + + gst_bin_add (GST_BIN (ebin), sgroup->combiner); + tosync = g_list_append (tosync, sgroup->combiner); + if (G_UNLIKELY (!fast_element_link (sgroup->combiner, last))) + goto combiner_link_failure; + + + /* Stream splitter */ + sgroup->splitter = g_object_new (GST_TYPE_STREAM_SPLITTER, NULL); + + gst_bin_add (GST_BIN (ebin), sgroup->splitter); + tosync = g_list_append (tosync, sgroup->splitter); + + /* Input queue + * FIXME : figure out what max-size to use for the input queue */ + sgroup->inqueue = gst_element_factory_make ("queue", NULL); + g_object_set (sgroup->inqueue, "max-size-buffers", + (guint32) ebin->queue_buffers_max, "max-size-bytes", + (guint32) ebin->queue_bytes_max, "max-size-time", + (guint64) ebin->queue_time_max, NULL); + + gst_bin_add (GST_BIN (ebin), sgroup->inqueue); + tosync = g_list_append (tosync, sgroup->inqueue); + if (G_UNLIKELY (!fast_element_link (sgroup->inqueue, sgroup->splitter))) + goto splitter_link_failure; + + /* Expose input queue sink pad as ghostpad */ + sinkpad = gst_element_get_static_pad (sgroup->inqueue, "sink"); + if (sinkpadname == NULL) { + gchar *pname = + g_strdup_printf ("%s_%d", gst_encoding_profile_get_type_nick (sprof), + ebin->last_pad_id++); + GST_DEBUG ("Adding ghost pad %s", pname); + sgroup->ghostpad = gst_ghost_pad_new (pname, sinkpad); + g_free (pname); + } else + sgroup->ghostpad = gst_ghost_pad_new (sinkpadname, sinkpad); + gst_object_unref (sinkpad); + + + /* Path 1 : Already-encoded data */ + sinkpad = gst_element_request_pad (sgroup->combiner, NULL, "passthroughsink"); + if (G_UNLIKELY (sinkpad == NULL)) + goto no_combiner_sinkpad; + + if (ebin->avoid_reencoding) { + GstCaps *tmpcaps; + + GST_DEBUG ("Asked to use Smart Encoder"); + sgroup->smartencoder = g_object_new (GST_TYPE_SMART_ENCODER, NULL); + + /* Check if stream format is compatible */ + srcpad = gst_element_get_static_pad (sgroup->smartencoder, "src"); + tmpcaps = gst_pad_get_caps (srcpad); + if (!gst_caps_can_intersect (tmpcaps, format)) { + GST_DEBUG ("We don't have a smart encoder for the stream format"); + gst_object_unref (sgroup->smartencoder); + sgroup->smartencoder = NULL; + } else { + gst_bin_add ((GstBin *) ebin, sgroup->smartencoder); + fast_pad_link (srcpad, sinkpad); + tosync = g_list_append (tosync, sgroup->smartencoder); + sinkpad = gst_element_get_static_pad (sgroup->smartencoder, "sink"); + } + gst_caps_unref (tmpcaps); + g_object_unref (srcpad); + } + + srcpad = gst_element_request_pad (sgroup->splitter, NULL, "passthroughsrc"); + if (G_UNLIKELY (srcpad == NULL)) + goto no_splitter_srcpad; + + /* Go straight to splitter */ + if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) + goto passthrough_link_failure; + g_object_unref (sinkpad); + g_object_unref (srcpad); + + + /* Path 2 : Conversion / Encoding */ + + /* 1. Create the encoder */ + GST_LOG ("Adding encoder"); + last = sgroup->encoder; + gst_bin_add ((GstBin *) ebin, sgroup->encoder); + tosync = g_list_append (tosync, sgroup->encoder); + + sinkpad = gst_element_request_pad (sgroup->combiner, NULL, "encodingsink"); + if (G_UNLIKELY (sinkpad == NULL)) + goto no_combiner_sinkpad; + srcpad = gst_element_get_static_pad (sgroup->encoder, "src"); + if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) + goto encoder_link_failure; + g_object_unref (sinkpad); + g_object_unref (srcpad); + + + /* 3. Create the conversion/restriction elements */ + /* 3.1. capsfilter */ + if (restriction && !gst_caps_is_any (restriction)) { + GST_LOG ("Adding capsfilter for restriction caps : %" GST_PTR_FORMAT, + restriction); + + last = sgroup->capsfilter = gst_element_factory_make ("capsfilter", NULL); + g_object_set (sgroup->capsfilter, "caps", restriction, NULL); + gst_bin_add ((GstBin *) ebin, sgroup->capsfilter); + tosync = g_list_append (tosync, sgroup->capsfilter); + fast_element_link (sgroup->capsfilter, sgroup->encoder); + } + + /* 3.2. restriction elements */ + /* FIXME : Once we have properties for specific converters, use those */ + if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) { + GstElement *cspace, *scale, *vrate, *cspace2; + + GST_LOG ("Adding conversion elements for video stream"); + + cspace = gst_element_factory_make ("ffmpegcolorspace", NULL); + scale = gst_element_factory_make ("videoscale", NULL); + /* 4-tap scaling and black borders */ + g_object_set (scale, "method", 2, "add-borders", TRUE, NULL); + cspace2 = gst_element_factory_make ("ffmpegcolorspace", NULL); + + gst_bin_add_many ((GstBin *) ebin, cspace, scale, cspace2, NULL); + tosync = g_list_append (tosync, cspace); + tosync = g_list_append (tosync, scale); + tosync = g_list_append (tosync, cspace2); + + sgroup->converters = g_list_prepend (sgroup->converters, cspace); + sgroup->converters = g_list_prepend (sgroup->converters, scale); + sgroup->converters = g_list_prepend (sgroup->converters, cspace2); + + if (!fast_element_link (cspace, scale) || + !fast_element_link (scale, cspace2)) + goto converter_link_failure; + + if (!gst_encoding_video_profile_get_variableframerate + (GST_ENCODING_VIDEO_PROFILE (sprof))) { + vrate = gst_element_factory_make ("videorate", NULL); + gst_bin_add ((GstBin *) ebin, vrate); + tosync = g_list_prepend (tosync, vrate); + sgroup->converters = g_list_prepend (sgroup->converters, vrate); + if (!fast_element_link (cspace2, vrate) || + !fast_element_link (vrate, last)) + goto converter_link_failure; + } else if (!fast_element_link (cspace2, last)) + goto converter_link_failure; + + last = cspace; + + } else if (GST_IS_ENCODING_AUDIO_PROFILE (sprof)) { + GstElement *aconv, *ares, *arate; + + GST_LOG ("Adding conversion elements for audio stream"); + + arate = gst_element_factory_make ("audiorate", NULL); + g_object_set (arate, "tolerance", (guint64) ebin->tolerance, NULL); + aconv = gst_element_factory_make ("audioconvert", NULL); + ares = gst_element_factory_make ("audioresample", NULL); + + gst_bin_add_many ((GstBin *) ebin, arate, aconv, ares, NULL); + tosync = g_list_append (tosync, arate); + tosync = g_list_append (tosync, aconv); + tosync = g_list_append (tosync, ares); + if (!fast_element_link (arate, aconv) || + !fast_element_link (aconv, ares) || !fast_element_link (ares, last)) + goto converter_link_failure; + + sgroup->converters = g_list_prepend (sgroup->converters, arate); + sgroup->converters = g_list_prepend (sgroup->converters, aconv); + sgroup->converters = g_list_prepend (sgroup->converters, ares); + + last = arate; + } + + /* Link to stream splitter */ + sinkpad = gst_element_get_static_pad (last, "sink"); + srcpad = gst_element_request_pad (sgroup->splitter, NULL, "encodingsrc"); + if (G_UNLIKELY (srcpad == NULL)) + goto no_splitter_srcpad; + if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) + goto splitter_encoding_failure; + g_object_unref (sinkpad); + g_object_unref (srcpad); + + /* End of Stream 2 setup */ + + /* Sync all elements to parent state */ + for (tmp = tosync; tmp; tmp = tmp->next) + gst_element_sync_state_with_parent ((GstElement *) tmp->data); + g_list_free (tosync); + + /* Add ghostpad */ + GST_DEBUG ("Adding ghostpad %s:%s", GST_DEBUG_PAD_NAME (sgroup->ghostpad)); + gst_pad_set_active (sgroup->ghostpad, TRUE); + gst_element_add_pad ((GstElement *) ebin, sgroup->ghostpad); + + /* Add StreamGroup to our list of streams */ + + GST_DEBUG + ("Done creating elements, adding StreamGroup to our controlled stream list"); + + ebin->streams = g_list_prepend (ebin->streams, sgroup); + + return sgroup; + +splitter_encoding_failure: + GST_ERROR_OBJECT (ebin, "Error linking splitter to encoding stream"); + goto cleanup; + +no_encoder: + GST_ERROR_OBJECT (ebin, "Couldn't create encoder for format %" GST_PTR_FORMAT, + format); + goto cleanup; + +no_muxer_pad: + GST_ERROR_OBJECT (ebin, + "Couldn't find a compatible muxer pad to link encoder to"); + goto cleanup; + +encoder_link_failure: + GST_ERROR_OBJECT (ebin, "Failed to link the encoder"); + goto cleanup; + +muxer_link_failure: + GST_ERROR_OBJECT (ebin, "Couldn't link encoder to muxer"); + goto cleanup; + +outfilter_link_failure: + GST_ERROR_OBJECT (ebin, "Couldn't link output filter to output queue"); + goto cleanup; + +passthrough_link_failure: + GST_ERROR_OBJECT (ebin, "Failed linking splitter in passthrough mode"); + goto cleanup; + +no_splitter_srcpad: + GST_ERROR_OBJECT (ebin, "Couldn't get a source pad from the splitter"); + goto cleanup; + +no_combiner_sinkpad: + GST_ERROR_OBJECT (ebin, "Couldn't get a sink pad from the combiner"); + goto cleanup; + +splitter_link_failure: + GST_ERROR_OBJECT (ebin, "Failure linking to the splitter"); + goto cleanup; + +combiner_link_failure: + GST_ERROR_OBJECT (ebin, "Failure linking to the combiner"); + goto cleanup; + +#if 0 +parser_link_failure: + GST_ERROR_OBJECT (ebin, "Failure linking the parser"); + goto cleanup; +#endif + +converter_link_failure: + GST_ERROR_OBJECT (ebin, "Failure linking the video converters"); + goto cleanup; + +cleanup: + /* FIXME : Actually properly cleanup everything */ + g_slice_free (StreamGroup, sgroup); + return NULL; +} + +static gboolean +_factory_can_sink_caps (GstElementFactory * factory, const GstCaps * caps) +{ + GList *templates = factory->staticpadtemplates; + + while (templates) { + GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data; + + if (template->direction == GST_PAD_SINK) { + GstCaps *tmp = gst_static_caps_get (&template->static_caps); + + if (gst_caps_can_intersect (tmp, caps)) { + gst_caps_unref (tmp); + return TRUE; + } + gst_caps_unref (tmp); + } + templates = g_list_next (templates); + } + + return FALSE; +} + +static inline GstElement * +_get_muxer (GstEncodeBin * ebin) +{ + GList *muxers, *tmpmux; + GstElement *muxer = NULL; + GstElementFactory *muxerfact = NULL; + const GList *tmp; + const GstCaps *format; + const gchar *preset; + + format = gst_encoding_profile_get_format (ebin->profile); + preset = gst_encoding_profile_get_preset (ebin->profile); + + if (format == NULL) { + GST_DEBUG ("Container-less profile, using identity"); + muxer = gst_element_factory_make ("identity", NULL); + goto beach; + } + + GST_DEBUG ("Getting list of muxers for format %" GST_PTR_FORMAT, format); + + muxers = + gst_element_factory_list_filter (ebin->muxers, format, GST_PAD_SRC, TRUE); + + if (muxers == NULL) + goto beach; + + /* FIXME : signal the user if he wants this */ + for (tmpmux = muxers; tmpmux; tmpmux = tmpmux->next) { + gboolean cansinkstreams = TRUE; + const GList *profiles = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); + + muxerfact = (GstElementFactory *) tmpmux->data; + + GST_DEBUG ("Trying muxer %s", GST_PLUGIN_FEATURE_NAME (muxerfact)); + + /* See if the muxer can sink all of our stream profile caps */ + for (tmp = profiles; tmp; tmp = tmp->next) { + GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data; + + if (!_factory_can_sink_caps (muxerfact, + gst_encoding_profile_get_format (sprof))) { + GST_DEBUG ("Skipping muxer because it can't sink caps %" GST_PTR_FORMAT, + gst_encoding_profile_get_format (sprof)); + cansinkstreams = FALSE; + break; + } + } + + /* Only use a muxer than can use all streams and than can accept the + * preset (which may be present or not) */ + if (cansinkstreams && (muxer = + _create_element_and_set_preset (muxerfact, preset, "muxer"))) + break; + } + + gst_plugin_feature_list_free (muxers); + +beach: + return muxer; +} + +static gboolean +create_elements_and_pads (GstEncodeBin * ebin) +{ + gboolean ret = TRUE; + GstElement *muxer; + GstPad *muxerpad; + const GList *tmp, *profiles; + GstEncodingProfile *sprof; + + GST_DEBUG ("Current profile : %s", + gst_encoding_profile_get_name (ebin->profile)); + + /* 1. Get the compatible muxer */ + muxer = _get_muxer (ebin); + if (G_UNLIKELY (muxer == NULL)) + goto no_muxer; + + /* Record the muxer */ + ebin->muxer = muxer; + gst_bin_add ((GstBin *) ebin, muxer); + + /* 2. Ghost the muxer source pad */ + + /* FIXME : We should figure out if it's a static/request/dyamic pad, + * but for the time being let's assume it's a static pad :) */ + muxerpad = gst_element_get_static_pad (muxer, "src"); + if (G_UNLIKELY (muxerpad == NULL)) + goto no_muxer_pad; + + if (!gst_ghost_pad_set_target (GST_GHOST_PAD (ebin->srcpad), muxerpad)) + goto no_muxer_ghost_pad; + + gst_object_unref (muxerpad); + + /* 3. Activate fixed presence streams */ + profiles = + gst_encoding_container_profile_get_profiles + (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); + for (tmp = profiles; tmp; tmp = tmp->next) { + sprof = (GstEncodingProfile *) tmp->data; + + GST_DEBUG ("Trying stream profile with presence %d", + gst_encoding_profile_get_presence (sprof)); + + if (gst_encoding_profile_get_presence (sprof) != 0) { + if (G_UNLIKELY (_create_stream_group (ebin, sprof, NULL, NULL) == NULL)) + goto stream_error; + } + } + + return ret; + +no_muxer: + { + GST_WARNING ("No available muxer for %" GST_PTR_FORMAT, + gst_encoding_profile_get_format (ebin->profile)); + return FALSE; + } + +no_muxer_pad: + { + GST_WARNING ("Can't get source pad from muxer (%s)", + GST_ELEMENT_NAME (muxer)); + gst_bin_remove (GST_BIN (ebin), muxer); + return FALSE; + } + +no_muxer_ghost_pad: + { + GST_WARNING ("Couldn't set %s:%s as source ghostpad target", + GST_DEBUG_PAD_NAME (muxerpad)); + gst_bin_remove (GST_BIN (ebin), muxer); + gst_object_unref (muxerpad); + return FALSE; + } + +stream_error: + { + GST_WARNING ("Could not create Streams"); + gst_element_remove_pad ((GstElement *) ebin, ebin->srcpad); + gst_bin_remove (GST_BIN (ebin), muxer); + ebin->muxer = NULL; + ebin->srcpad = NULL; + return FALSE; + } +} + +static void +release_pads (GstPad * pad, GstElement * elt) +{ + GstPad *peer = NULL; + + GST_DEBUG_OBJECT (elt, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + /* Unlink from its peer pad */ + if ((peer = gst_pad_get_peer (pad))) { + if (GST_PAD_DIRECTION (peer) == GST_PAD_SRC) + gst_pad_unlink (peer, pad); + else + gst_pad_unlink (pad, peer); + gst_object_unref (peer); + } + + /* Release it from the object */ + gst_element_release_request_pad (elt, pad); + + /* And remove the reference added by the iterator */ + gst_object_unref (pad); +} + +static void inline +stream_group_free (GstEncodeBin * ebin, StreamGroup * sgroup) +{ + GList *tmp; + GstPad *tmppad; + GstPad *pad; + + GST_DEBUG_OBJECT (ebin, "Freeing StreamGroup %p", sgroup); + + /* outqueue - Muxer */ + tmppad = gst_element_get_static_pad (sgroup->outqueue, "src"); + pad = gst_pad_get_peer (tmppad); + + /* Remove muxer request sink pad */ + gst_pad_unlink (tmppad, pad); + gst_element_release_request_pad (ebin->muxer, pad); + gst_object_unref (tmppad); + gst_object_unref (pad); + if (sgroup->outqueue) + gst_element_set_state (sgroup->outqueue, GST_STATE_NULL); + + /* Capsfilter - outqueue */ + gst_element_set_state (sgroup->outfilter, GST_STATE_NULL); + gst_element_unlink (sgroup->outfilter, sgroup->outqueue); + gst_bin_remove (GST_BIN (ebin), sgroup->outqueue); + + /* streamcombiner - parser - capsfilter */ + if (sgroup->parser) { + gst_element_set_state (sgroup->parser, GST_STATE_NULL); + gst_element_unlink (sgroup->parser, sgroup->outfilter); + gst_element_unlink (sgroup->combiner, sgroup->parser); + } + + /* Sink Ghostpad */ + if (sgroup->ghostpad) + gst_element_remove_pad (GST_ELEMENT_CAST (ebin), sgroup->ghostpad); + + if (sgroup->inqueue) + gst_element_set_state (sgroup->inqueue, GST_STATE_NULL); + + if (sgroup->encoder) + gst_element_set_state (sgroup->encoder, GST_STATE_NULL); + if (sgroup->outfilter) + gst_element_set_state (sgroup->outfilter, GST_STATE_NULL); + if (sgroup->smartencoder) + gst_element_set_state (sgroup->smartencoder, GST_STATE_NULL); + + if (sgroup->capsfilter) { + gst_element_set_state (sgroup->capsfilter, GST_STATE_NULL); + gst_element_unlink (sgroup->capsfilter, sgroup->encoder); + gst_bin_remove ((GstBin *) ebin, sgroup->capsfilter); + } + + for (tmp = sgroup->converters; tmp; tmp = tmp->next) { + GstElement *elt = (GstElement *) tmp->data; + + gst_element_set_state (elt, GST_STATE_NULL); + gst_bin_remove ((GstBin *) ebin, elt); + } + if (sgroup->converters) + g_list_free (sgroup->converters); + + if (sgroup->combiner) { + GstIterator *it = gst_element_iterate_sink_pads (sgroup->combiner); + GstIteratorResult itret = GST_ITERATOR_OK; + + while (itret == GST_ITERATOR_OK || itret == GST_ITERATOR_RESYNC) { + itret = gst_iterator_foreach (it, (GFunc) release_pads, sgroup->combiner); + gst_iterator_resync (it); + } + gst_iterator_free (it); + } + + if (sgroup->splitter) { + GstIterator *it = gst_element_iterate_src_pads (sgroup->splitter); + GstIteratorResult itret = GST_ITERATOR_OK; + while (itret == GST_ITERATOR_OK || itret == GST_ITERATOR_RESYNC) { + itret = gst_iterator_foreach (it, (GFunc) release_pads, sgroup->splitter); + gst_iterator_resync (it); + } + gst_iterator_free (it); + } + + if (sgroup->inqueue) + gst_bin_remove ((GstBin *) ebin, sgroup->inqueue); + if (sgroup->encoder) + gst_bin_remove ((GstBin *) ebin, sgroup->encoder); + if (sgroup->smartencoder) + gst_bin_remove ((GstBin *) ebin, sgroup->smartencoder); + + g_slice_free (StreamGroup, sgroup); +} + +static void +stream_group_remove (GstEncodeBin * ebin, StreamGroup * sgroup) +{ + ebin->streams = g_list_remove (ebin->streams, sgroup); + + stream_group_free (ebin, sgroup); +} + +static void +gst_encode_bin_tear_down_profile (GstEncodeBin * ebin) +{ + if (G_UNLIKELY (ebin->profile == NULL)) + return; + + GST_DEBUG ("Tearing down profile %s", + gst_encoding_profile_get_name (ebin->profile)); + + while (ebin->streams) + stream_group_remove (ebin, (StreamGroup *) ebin->streams->data); + + /* free/clear profile */ + gst_encoding_profile_unref (ebin->profile); + ebin->profile = NULL; +} + +static gboolean +gst_encode_bin_setup_profile (GstEncodeBin * ebin, GstEncodingProfile * profile) +{ + gboolean res; + + g_return_val_if_fail (ebin->profile == NULL, FALSE); + + GST_DEBUG ("Setting up profile %s", gst_encoding_profile_get_name (profile)); + + ebin->profile = profile; + gst_mini_object_ref ((GstMiniObject *) ebin->profile); + + /* Create elements */ + res = create_elements_and_pads (ebin); + if (res == FALSE) + gst_encode_bin_tear_down_profile (ebin); + + return res; +} + +static gboolean +gst_encode_bin_set_profile (GstEncodeBin * ebin, GstEncodingProfile * profile) +{ + GST_DEBUG_OBJECT (ebin, "profile : %s", + gst_encoding_profile_get_name (profile)); + + if (G_UNLIKELY (ebin->active)) { + GST_WARNING_OBJECT (ebin, "Element already active, can't change profile"); + return FALSE; + } + + /* If we're not active, we can deactivate the previous profile */ + if (ebin->profile) + gst_encoding_profile_unref (ebin->profile); + ebin->profile = NULL; + + return gst_encode_bin_setup_profile (ebin, profile); +} + +static inline gboolean +gst_encode_bin_activate (GstEncodeBin * ebin) +{ + ebin->active = ebin->profile != NULL; + return ebin->active; +} + +static void +gst_encode_bin_deactivate (GstEncodeBin * ebin) +{ + ebin->active = FALSE; +} + +static GstStateChangeReturn +gst_encode_bin_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstEncodeBin *ebin = (GstEncodeBin *) element; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + if (!gst_encode_bin_activate (ebin)) { + ret = GST_STATE_CHANGE_FAILURE; + goto beach; + } + break; + default: + break; + } + + ret = + GST_ELEMENT_CLASS (gst_encode_bin_parent_class)->change_state (element, + transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto beach; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_encode_bin_deactivate (ebin); + break; + default: + break; + } + +beach: + return ret; +} + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean res; + + GST_DEBUG_CATEGORY_INIT (gst_encode_bin_debug, "encodebin", 0, "encoder bin"); + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +#endif /* ENABLE_NLS */ + + + res = gst_element_register (plugin, "encodebin", GST_RANK_NONE, + GST_TYPE_ENCODE_BIN); + + return res; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "encoding", + "various encoding-related elements", plugin_init, VERSION, GST_LICENSE, + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/encoding/gstencodebin.h b/gst/encoding/gstencodebin.h new file mode 100644 index 0000000000..8082817de3 --- /dev/null +++ b/gst/encoding/gstencodebin.h @@ -0,0 +1,39 @@ +/* GStreamer encoding bin + * Copyright (C) 2009 Edward Hervey + * (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_ENCODEBIN_H__ +#define __GST_ENCODEBIN_H__ + +#include +#include +#include "gstencode-marshal.h" + +#define GST_TYPE_ENCODE_BIN (gst_encode_bin_get_type()) +#define GST_ENCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ENCODE_BIN,GstEncodeBin)) +#define GST_ENCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ENCODE_BIN,GstEncodeBinClass)) +#define GST_IS_ENCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ENCODE_BIN)) +#define GST_IS_ENCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ENCODE_BIN)) + +typedef struct _GstEncodeBin GstEncodeBin; +typedef struct _GstEncodeBinClass GstEncodeBinClass; + +GType gst_encode_bin_get_type(void); + +#endif /* __GST_ENCODEBIN_H__ */ diff --git a/gst/encoding/gstsmartencoder.c b/gst/encoding/gstsmartencoder.c new file mode 100644 index 0000000000..ed0e42bd5c --- /dev/null +++ b/gst/encoding/gstsmartencoder.c @@ -0,0 +1,701 @@ +/* GStreamer Smart Video Encoder element + * Copyright (C) <2010> Edward Hervey + * + * 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. + */ + +/* TODO: + * * Implement get_caps/set_caps (store/forward caps) + * * Adjust template caps to the formats we can support + **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstsmartencoder.h" + +GST_DEBUG_CATEGORY_STATIC (smart_encoder_debug); +#define GST_CAT_DEFAULT smart_encoder_debug + +/* FIXME : Update this with new caps */ +/* WARNING : We can only allow formats with closed-GOP */ +#define ALLOWED_CAPS "video/x-h263;video/x-intel-h263;"\ + "video/mpeg,mpegversion=(int)1,systemstream=(boolean)false;"\ + "video/mpeg,mpegversion=(int)2,systemstream=(boolean)false;" + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (ALLOWED_CAPS) + ); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (ALLOWED_CAPS) + ); + +static GQuark INTERNAL_ELEMENT; + +/* GstSmartEncoder signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +enum +{ + ARG_0 + /* FILL ME */ +}; + +static void +_do_init (void) +{ + INTERNAL_ELEMENT = g_quark_from_string ("internal-element"); +}; + +G_DEFINE_TYPE_EXTENDED (GstSmartEncoder, gst_smart_encoder, GST_TYPE_ELEMENT, 0, + _do_init ()); + +static void gst_smart_encoder_dispose (GObject * object); + +static gboolean setup_recoder_pipeline (GstSmartEncoder * smart_encoder); + +static GstFlowReturn gst_smart_encoder_chain (GstPad * pad, GstBuffer * buf); +static gboolean smart_encoder_sink_event (GstPad * pad, GstEvent * event); +static GstCaps *smart_encoder_sink_getcaps (GstPad * pad); +static GstStateChangeReturn +gst_smart_encoder_change_state (GstElement * element, + GstStateChange transition); + +static void +gst_smart_encoder_class_init (GstSmartEncoderClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + element_class = (GstElementClass *) klass; + gobject_class = G_OBJECT_CLASS (klass); + + gst_smart_encoder_parent_class = g_type_class_peek_parent (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_details_simple (element_class, "Smart Video Encoder", + "Codec/Recoder/Video", + "Re-encodes portions of Video that lay on segment boundaries", + "Edward Hervey "); + + gobject_class->dispose = (GObjectFinalizeFunc) (gst_smart_encoder_dispose); + element_class->change_state = gst_smart_encoder_change_state; + + GST_DEBUG_CATEGORY_INIT (smart_encoder_debug, "smartencoder", 0, + "Smart Encoder"); +} + +static void +smart_encoder_reset (GstSmartEncoder * smart_encoder) +{ + gst_segment_init (smart_encoder->segment, GST_FORMAT_UNDEFINED); + + if (smart_encoder->encoder) { + /* Clean up/remove elements */ + gst_element_set_state (smart_encoder->encoder, GST_STATE_NULL); + gst_element_set_state (smart_encoder->decoder, GST_STATE_NULL); + gst_element_set_bus (smart_encoder->encoder, NULL); + gst_element_set_bus (smart_encoder->decoder, NULL); + gst_pad_set_active (smart_encoder->internal_srcpad, FALSE); + gst_pad_set_active (smart_encoder->internal_sinkpad, FALSE); + gst_object_unref (smart_encoder->encoder); + gst_object_unref (smart_encoder->decoder); + gst_object_unref (smart_encoder->internal_srcpad); + gst_object_unref (smart_encoder->internal_sinkpad); + + smart_encoder->encoder = NULL; + smart_encoder->decoder = NULL; + smart_encoder->internal_sinkpad = NULL; + smart_encoder->internal_srcpad = NULL; + } + + if (smart_encoder->newsegment) { + gst_event_unref (smart_encoder->newsegment); + smart_encoder->newsegment = NULL; + } +} + + +static void +gst_smart_encoder_init (GstSmartEncoder * smart_encoder) +{ + smart_encoder->sinkpad = + gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_chain_function (smart_encoder->sinkpad, gst_smart_encoder_chain); + gst_pad_set_event_function (smart_encoder->sinkpad, smart_encoder_sink_event); + gst_pad_set_getcaps_function (smart_encoder->sinkpad, + smart_encoder_sink_getcaps); + gst_element_add_pad (GST_ELEMENT (smart_encoder), smart_encoder->sinkpad); + + smart_encoder->srcpad = + gst_pad_new_from_static_template (&src_template, "src"); + gst_pad_use_fixed_caps (smart_encoder->srcpad); + gst_element_add_pad (GST_ELEMENT (smart_encoder), smart_encoder->srcpad); + + smart_encoder->segment = gst_segment_new (); + + smart_encoder_reset (smart_encoder); +} + +void +gst_smart_encoder_dispose (GObject * object) +{ + GstSmartEncoder *smart_encoder = (GstSmartEncoder *) object; + + if (smart_encoder->segment) + gst_segment_free (smart_encoder->segment); + smart_encoder->segment = NULL; + if (smart_encoder->available_caps) + gst_caps_unref (smart_encoder->available_caps); + smart_encoder->available_caps = NULL; + G_OBJECT_CLASS (gst_smart_encoder_parent_class)->dispose (object); +} + +static GstFlowReturn +gst_smart_encoder_reencode_gop (GstSmartEncoder * smart_encoder) +{ + GstFlowReturn res = GST_FLOW_OK; + GList *tmp; + + if (smart_encoder->encoder == NULL) { + if (!setup_recoder_pipeline (smart_encoder)) + return GST_FLOW_ERROR; + } + + /* Activate elements */ + /* Set elements to PAUSED */ + gst_element_set_state (smart_encoder->encoder, GST_STATE_PAUSED); + gst_element_set_state (smart_encoder->decoder, GST_STATE_PAUSED); + + GST_INFO ("Pushing Flush start/stop to clean decoder/encoder"); + gst_pad_push_event (smart_encoder->internal_srcpad, + gst_event_new_flush_start ()); + gst_pad_push_event (smart_encoder->internal_srcpad, + gst_event_new_flush_stop ()); + + /* push newsegment */ + GST_INFO ("Pushing newsegment %" GST_PTR_FORMAT, smart_encoder->newsegment); + gst_pad_push_event (smart_encoder->internal_srcpad, + gst_event_ref (smart_encoder->newsegment)); + + /* Push buffers through our pads */ + GST_DEBUG ("Pushing pending buffers"); + + for (tmp = smart_encoder->pending_gop; tmp; tmp = tmp->next) { + GstBuffer *buf = (GstBuffer *) tmp->data; + + res = gst_pad_push (smart_encoder->internal_srcpad, buf); + if (G_UNLIKELY (res != GST_FLOW_OK)) + break; + } + + if (G_UNLIKELY (res != GST_FLOW_OK)) { + GST_WARNING ("Error pushing pending buffers : %s", gst_flow_get_name (res)); + /* Remove pending bfufers */ + for (tmp = smart_encoder->pending_gop; tmp; tmp = tmp->next) { + gst_buffer_unref ((GstBuffer *) tmp->data); + } + } else { + GST_INFO ("Pushing out EOS to flush out decoder/encoder"); + gst_pad_push_event (smart_encoder->internal_srcpad, gst_event_new_eos ()); + } + + /* Activate elements */ + /* Set elements to PAUSED */ + gst_element_set_state (smart_encoder->encoder, GST_STATE_NULL); + gst_element_set_state (smart_encoder->decoder, GST_STATE_NULL); + + g_list_free (smart_encoder->pending_gop); + smart_encoder->pending_gop = NULL; + + return res; +} + +static GstFlowReturn +gst_smart_encoder_push_pending_gop (GstSmartEncoder * smart_encoder) +{ + gint64 cstart, cstop; + GList *tmp; + GstFlowReturn res = GST_FLOW_OK; + + GST_DEBUG ("Pushing pending GOP (%" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT + ")", GST_TIME_ARGS (smart_encoder->gop_start), + GST_TIME_ARGS (smart_encoder->gop_stop)); + + /* If GOP is entirely within segment, just push downstream */ + if (gst_segment_clip (smart_encoder->segment, GST_FORMAT_TIME, + smart_encoder->gop_start, smart_encoder->gop_stop, &cstart, &cstop)) { + if ((cstart != smart_encoder->gop_start) + || (cstop != smart_encoder->gop_stop)) { + GST_DEBUG ("GOP needs to be re-encoded from %" GST_TIME_FORMAT " to %" + GST_TIME_FORMAT, GST_TIME_ARGS (cstart), GST_TIME_ARGS (cstop)); + res = gst_smart_encoder_reencode_gop (smart_encoder); + } else { + /* The whole GOP is within the segment, push all pending buffers downstream */ + GST_DEBUG ("GOP doesn't need to be modified, pushing downstream"); + for (tmp = smart_encoder->pending_gop; tmp; tmp = tmp->next) { + GstBuffer *buf = (GstBuffer *) tmp->data; + res = gst_pad_push (smart_encoder->srcpad, buf); + if (G_UNLIKELY (res != GST_FLOW_OK)) + break; + } + } + } else { + /* The whole GOP is outside the segment, there's most likely + * a bug somewhere. */ + GST_WARNING + ("GOP is entirely outside of the segment, upstream gave us too much data"); + for (tmp = smart_encoder->pending_gop; tmp; tmp = tmp->next) { + gst_buffer_unref ((GstBuffer *) tmp->data); + } + } + + if (smart_encoder->pending_gop) { + g_list_free (smart_encoder->pending_gop); + smart_encoder->pending_gop = NULL; + } + smart_encoder->gop_start = GST_CLOCK_TIME_NONE; + smart_encoder->gop_stop = GST_CLOCK_TIME_NONE; + + return res; +} + +static GstFlowReturn +gst_smart_encoder_chain (GstPad * pad, GstBuffer * buf) +{ + GstSmartEncoder *smart_encoder; + GstFlowReturn res = GST_FLOW_OK; + gboolean discont, keyframe; + + smart_encoder = GST_SMART_ENCODER (gst_object_get_parent (GST_OBJECT (pad))); + + discont = GST_BUFFER_IS_DISCONT (buf); + keyframe = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + + GST_DEBUG ("New buffer %s %s %" GST_TIME_FORMAT, + discont ? "discont" : "", + keyframe ? "keyframe" : "", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + if (keyframe) { + GST_DEBUG ("Got a keyframe"); + + /* If there's a pending GOP, flush it out */ + if (smart_encoder->pending_gop) { + /* Mark gop_stop */ + smart_encoder->gop_stop = GST_BUFFER_TIMESTAMP (buf); + + /* flush pending */ + res = gst_smart_encoder_push_pending_gop (smart_encoder); + if (G_UNLIKELY (res != GST_FLOW_OK)) + goto beach; + } + + /* Mark gop_start for new gop */ + smart_encoder->gop_start = GST_BUFFER_TIMESTAMP (buf); + } + + /* Store buffer */ + smart_encoder->pending_gop = g_list_append (smart_encoder->pending_gop, buf); + /* Update GOP stop position */ + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + smart_encoder->gop_stop = GST_BUFFER_TIMESTAMP (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) + smart_encoder->gop_stop += GST_BUFFER_DURATION (buf); + } + + GST_DEBUG ("Buffer stored , Current GOP : %" GST_TIME_FORMAT " -- %" + GST_TIME_FORMAT, GST_TIME_ARGS (smart_encoder->gop_start), + GST_TIME_ARGS (smart_encoder->gop_stop)); + +beach: + gst_object_unref (smart_encoder); + return res; +} + +static gboolean +smart_encoder_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean res = TRUE; + GstSmartEncoder *smart_encoder = GST_SMART_ENCODER (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + smart_encoder_reset (smart_encoder); + break; + 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_DEBUG_OBJECT (smart_encoder, + "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)); + if (format != GST_FORMAT_TIME) + GST_ERROR + ("smart_encoder can not handle streams not specified in GST_FORMAT_TIME"); + + /* now configure the values */ + gst_segment_set_newsegment_full (smart_encoder->segment, update, + rate, arate, format, start, stop, time); + + /* And keep a copy for further usage */ + if (smart_encoder->newsegment) + gst_event_unref (smart_encoder->newsegment); + smart_encoder->newsegment = gst_event_ref (event); + } + break; + case GST_EVENT_EOS: + GST_DEBUG ("Eos, flushing remaining data"); + gst_smart_encoder_push_pending_gop (smart_encoder); + break; + default: + break; + } + + res = gst_pad_push_event (smart_encoder->srcpad, event); + + gst_object_unref (smart_encoder); + return res; +} + +static GstCaps * +smart_encoder_sink_getcaps (GstPad * pad) +{ + GstCaps *peer, *tmpl, *res; + GstSmartEncoder *smart_encoder = GST_SMART_ENCODER (gst_pad_get_parent (pad)); + + /* Try getting it from downstream */ + peer = gst_pad_peer_get_caps_reffed (smart_encoder->srcpad); + + /* Use computed caps */ + if (smart_encoder->available_caps) + tmpl = gst_caps_ref (smart_encoder->available_caps); + else + tmpl = gst_static_pad_template_get_caps (&src_template); + + if (peer == NULL) { + res = tmpl; + } else { + res = gst_caps_intersect (peer, tmpl); + gst_caps_unref (peer); + gst_caps_unref (tmpl); + } + + gst_object_unref (smart_encoder); + return res; +} + +/***************************************** + * Internal encoder/decoder pipeline * + ******************************************/ + +static GstElementFactory * +get_decoder_factory (GstCaps * caps) +{ + GstElementFactory *fact = NULL; + GList *decoders, *tmp; + + tmp = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DECODER, + GST_RANK_MARGINAL); + decoders = gst_element_factory_list_filter (tmp, caps, GST_PAD_SINK, FALSE); + gst_plugin_feature_list_free (tmp); + + for (tmp = decoders; tmp; tmp = tmp->next) { + /* We just pick the first one */ + fact = (GstElementFactory *) tmp->data; + gst_object_ref (fact); + break; + } + + gst_plugin_feature_list_free (decoders); + + return fact; +} + +static GstElementFactory * +get_encoder_factory (GstCaps * caps) +{ + GstElementFactory *fact = NULL; + GList *encoders, *tmp; + + tmp = + gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_ENCODER, + GST_RANK_MARGINAL); + encoders = gst_element_factory_list_filter (tmp, caps, GST_PAD_SRC, FALSE); + gst_plugin_feature_list_free (tmp); + + for (tmp = encoders; tmp; tmp = tmp->next) { + /* We just pick the first one */ + fact = (GstElementFactory *) tmp->data; + gst_object_ref (fact); + break; + } + + gst_plugin_feature_list_free (encoders); + + return fact; +} + +static GstElement * +get_decoder (GstCaps * caps) +{ + GstElementFactory *fact = get_decoder_factory (caps); + GstElement *res = NULL; + + if (fact) { + res = gst_element_factory_create (fact, "internal-decoder"); + gst_object_unref (fact); + } + return res; +} + +static GstElement * +get_encoder (GstCaps * caps) +{ + GstElementFactory *fact = get_encoder_factory (caps); + GstElement *res = NULL; + + if (fact) { + res = gst_element_factory_create (fact, "internal-encoder"); + gst_object_unref (fact); + } + return res; +} + +static GstFlowReturn +internal_chain (GstPad * pad, GstBuffer * buf) +{ + GstSmartEncoder *smart_encoder = + g_object_get_qdata ((GObject *) pad, INTERNAL_ELEMENT); + + return gst_pad_push (smart_encoder->srcpad, buf); +} + +static gboolean +setup_recoder_pipeline (GstSmartEncoder * smart_encoder) +{ + GstPad *tmppad; + + /* Fast path */ + if (G_UNLIKELY (smart_encoder->encoder)) + return TRUE; + + GST_DEBUG ("Creating internal decoder and encoder"); + + /* Create decoder/encoder */ + smart_encoder->decoder = get_decoder (GST_PAD_CAPS (smart_encoder->sinkpad)); + if (G_UNLIKELY (smart_encoder->decoder == NULL)) + goto no_decoder; + gst_element_set_bus (smart_encoder->decoder, GST_ELEMENT_BUS (smart_encoder)); + + smart_encoder->encoder = get_encoder (GST_PAD_CAPS (smart_encoder->sinkpad)); + if (G_UNLIKELY (smart_encoder->encoder == NULL)) + goto no_encoder; + gst_element_set_bus (smart_encoder->encoder, GST_ELEMENT_BUS (smart_encoder)); + + GST_DEBUG ("Creating internal pads"); + + /* Create internal pads */ + + /* Source pad which we'll use to feed data to decoders */ + smart_encoder->internal_srcpad = gst_pad_new ("internal_src", GST_PAD_SRC); + g_object_set_qdata ((GObject *) smart_encoder->internal_srcpad, + INTERNAL_ELEMENT, smart_encoder); + gst_pad_set_caps (smart_encoder->internal_srcpad, + GST_PAD_CAPS (smart_encoder->sinkpad)); + gst_pad_set_active (smart_encoder->internal_srcpad, TRUE); + + /* Sink pad which will get the buffers from the encoder. + * Note: We don't need an event function since we'll be discarding all + * of them. */ + smart_encoder->internal_sinkpad = gst_pad_new ("internal_sink", GST_PAD_SINK); + g_object_set_qdata ((GObject *) smart_encoder->internal_sinkpad, + INTERNAL_ELEMENT, smart_encoder); + gst_pad_set_caps (smart_encoder->internal_sinkpad, + GST_PAD_CAPS (smart_encoder->sinkpad)); + gst_pad_set_chain_function (smart_encoder->internal_sinkpad, internal_chain); + gst_pad_set_active (smart_encoder->internal_sinkpad, TRUE); + + GST_DEBUG ("Linking pads to elements"); + + /* Link everything */ + tmppad = gst_element_get_static_pad (smart_encoder->encoder, "src"); + if (GST_PAD_LINK_FAILED (gst_pad_link (tmppad, + smart_encoder->internal_sinkpad))) + goto sinkpad_link_fail; + gst_object_unref (tmppad); + + if (!gst_element_link (smart_encoder->decoder, smart_encoder->encoder)) + goto encoder_decoder_link_fail; + + tmppad = gst_element_get_static_pad (smart_encoder->decoder, "sink"); + if (GST_PAD_LINK_FAILED (gst_pad_link (smart_encoder->internal_srcpad, + tmppad))) + goto srcpad_link_fail; + gst_object_unref (tmppad); + + GST_DEBUG ("Done creating internal elements/pads"); + + return TRUE; + +no_decoder: + { + GST_WARNING ("Couldn't find a decoder for %" GST_PTR_FORMAT, + GST_PAD_CAPS (smart_encoder->sinkpad)); + return FALSE; + } + +no_encoder: + { + GST_WARNING ("Couldn't find an encoder for %" GST_PTR_FORMAT, + GST_PAD_CAPS (smart_encoder->sinkpad)); + return FALSE; + } + +srcpad_link_fail: + { + gst_object_unref (tmppad); + GST_WARNING ("Couldn't link internal srcpad to decoder"); + return FALSE; + } + +sinkpad_link_fail: + { + gst_object_unref (tmppad); + GST_WARNING ("Couldn't link encoder to internal sinkpad"); + return FALSE; + } + +encoder_decoder_link_fail: + { + GST_WARNING ("Couldn't link decoder to encoder"); + return FALSE; + } +} + +static GstStateChangeReturn +gst_smart_encoder_find_elements (GstSmartEncoder * smart_encoder) +{ + guint i, n; + GstCaps *tmpl, *st, *res; + GstElementFactory *dec, *enc; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + if (G_UNLIKELY (smart_encoder->available_caps)) + goto beach; + + /* Iterate over all pad template caps and see if we have both an + * encoder and a decoder for those media types */ + tmpl = gst_static_pad_template_get_caps (&src_template); + res = gst_caps_new_empty (); + n = gst_caps_get_size (tmpl); + + for (i = 0; i < n; i++) { + st = gst_caps_copy_nth (tmpl, i); + GST_DEBUG_OBJECT (smart_encoder, + "Checking for available decoder and encoder for %" GST_PTR_FORMAT, st); + if (!(dec = get_decoder_factory (st))) { + gst_caps_unref (st); + continue; + } + gst_object_unref (dec); + if (!(enc = get_encoder_factory (st))) { + gst_caps_unref (st); + continue; + } + gst_object_unref (enc); + GST_DEBUG_OBJECT (smart_encoder, "OK"); + gst_caps_append (res, st); + } + + gst_caps_unref (tmpl); + + if (gst_caps_is_empty (res)) + ret = GST_STATE_CHANGE_FAILURE; + else + smart_encoder->available_caps = res; + + GST_DEBUG_OBJECT (smart_encoder, "Done, available_caps:%" GST_PTR_FORMAT, + smart_encoder->available_caps); + +beach: + return ret; +} + +/****************************************** + * GstElement vmethod implementations * + ******************************************/ + +static GstStateChangeReturn +gst_smart_encoder_change_state (GstElement * element, GstStateChange transition) +{ + GstSmartEncoder *smart_encoder; + GstStateChangeReturn ret; + + g_return_val_if_fail (GST_IS_SMART_ENCODER (element), + GST_STATE_CHANGE_FAILURE); + + smart_encoder = GST_SMART_ENCODER (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* Figure out which elements are available */ + if ((ret = + gst_smart_encoder_find_elements (smart_encoder)) == + GST_STATE_CHANGE_FAILURE) + goto beach; + break; + default: + break; + } + + ret = + GST_ELEMENT_CLASS (gst_smart_encoder_parent_class)->change_state (element, + transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + smart_encoder_reset (smart_encoder); + break; + default: + break; + } + +beach: + return ret; +} diff --git a/gst/encoding/gstsmartencoder.h b/gst/encoding/gstsmartencoder.h new file mode 100644 index 0000000000..15366269c8 --- /dev/null +++ b/gst/encoding/gstsmartencoder.h @@ -0,0 +1,71 @@ +/* GStreamer video re-encoder element + * Copyright (C) <2010> Edward Hervey + * + * 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 __SMART_ENCODER_H__ +#define __SMART_ENCODER_H__ + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_SMART_ENCODER \ + (gst_smart_encoder_get_type()) +#define GST_SMART_ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SMART_ENCODER,GstSmartEncoder)) +#define GST_SMART_ENCODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SMART_ENCODER,GstSmartEncoderClass)) +#define GST_IS_SMART_ENCODER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SMART_ENCODER)) +#define GST_IS_SMART_ENCODER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SMART_ENCODER)) + +typedef struct _GstSmartEncoder GstSmartEncoder; +typedef struct _GstSmartEncoderClass GstSmartEncoderClass; + +struct _GstSmartEncoder { + GstElement element; + + GstPad *sinkpad, *srcpad; + + GstSegment *segment; + GstEvent *newsegment; + + /* Pending GOP to be checked */ + GList *pending_gop; + guint64 gop_start; /* GOP start in running time */ + guint64 gop_stop; /* GOP end in running time */ + + /* Internal recoding elements */ + GstPad *internal_sinkpad; + GstPad *internal_srcpad; + GstElement *decoder; + GstElement *encoder; + + /* Available caps at runtime */ + GstCaps *available_caps; +}; + +struct _GstSmartEncoderClass { + GstElementClass parent_class; +}; + +GType gst_smart_encoder_get_type(void); + +G_END_DECLS + +#endif /* __SMART_ENCODER_H__ */ diff --git a/gst/encoding/gststreamcombiner.c b/gst/encoding/gststreamcombiner.c new file mode 100644 index 0000000000..72d40fe756 --- /dev/null +++ b/gst/encoding/gststreamcombiner.c @@ -0,0 +1,276 @@ +/* GStreamer Stream Combiner + * Copyright (C) 2010 Edward Hervey + * (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 "gststreamcombiner.h" + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gst_stream_combiner_debug); +#define GST_CAT_DEFAULT gst_stream_combiner_debug + +G_DEFINE_TYPE (GstStreamCombiner, gst_stream_combiner, GST_TYPE_ELEMENT); + +#define STREAMS_LOCK(obj) (g_mutex_lock(obj->lock)) +#define STREAMS_UNLOCK(obj) (g_mutex_unlock(obj->lock)) + +static void gst_stream_combiner_dispose (GObject * object); + +static GstPad *gst_stream_combiner_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_stream_combiner_release_pad (GstElement * element, + GstPad * pad); + +static void +gst_stream_combiner_class_init (GstStreamCombinerClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *gstelement_klass; + + gobject_klass = (GObjectClass *) klass; + gstelement_klass = (GstElementClass *) klass; + + gobject_klass->dispose = gst_stream_combiner_dispose; + + GST_DEBUG_CATEGORY_INIT (gst_stream_combiner_debug, "streamcombiner", 0, + "Stream Combiner"); + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&sink_template)); + + gstelement_klass->request_new_pad = + GST_DEBUG_FUNCPTR (gst_stream_combiner_request_new_pad); + gstelement_klass->release_pad = + GST_DEBUG_FUNCPTR (gst_stream_combiner_release_pad); + + gst_element_class_set_details_simple (gstelement_klass, + "streamcombiner", "Generic", + "Recombines streams splitted by the streamsplitter element", + "Edward Hervey "); +} + +static void +gst_stream_combiner_dispose (GObject * object) +{ + GstStreamCombiner *stream_combiner = (GstStreamCombiner *) object; + + if (stream_combiner->lock) { + g_mutex_free (stream_combiner->lock); + stream_combiner->lock = NULL; + } + + G_OBJECT_CLASS (gst_stream_combiner_parent_class)->dispose (object); +} + +static GstFlowReturn +gst_stream_combiner_chain (GstPad * pad, GstBuffer * buf) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + /* FIXME : IMPLEMENT */ + + /* with lock taken, check if we're the active stream, if not drop */ + return gst_pad_push (stream_combiner->srcpad, buf); +} + +static gboolean +gst_stream_combiner_sink_event (GstPad * pad, GstEvent * event) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + /* FIXME : IMPLEMENT */ + + GST_DEBUG_OBJECT (pad, "Got event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + if (gst_event_has_name (event, "stream-switching-eos")) { + gst_event_unref (event); + event = gst_event_new_eos (); + } + break; + default: + break; + } + + /* NEW_SEGMENT : lock, wait for other stream to EOS, select stream, unlock, push */ + /* EOS : lock, mark pad as unused, unlock , drop event */ + /* CUSTOM_REAL_EOS : push EOS downstream */ + /* FLUSH_START : lock, mark as flushing, unlock. if wasn't flushing forward */ + /* FLUSH_STOP : lock, unmark as flushing, unlock, if was flushing forward */ + /* OTHER : if selected pad forward */ + return gst_pad_push_event (stream_combiner->srcpad, event); +} + +static GstCaps * +gst_stream_combiner_sink_getcaps (GstPad * pad) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + + return gst_pad_peer_get_caps_reffed (stream_combiner->srcpad); +} + +static gboolean +gst_stream_combiner_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + GstPad *peer; + gboolean res = FALSE; + + GST_DEBUG_OBJECT (pad, "caps:%" GST_PTR_FORMAT, caps); + + peer = gst_pad_get_peer (stream_combiner->srcpad); + if (peer) { + GST_DEBUG_OBJECT (peer, "Setting caps"); + res = gst_pad_set_caps (peer, caps); + gst_object_unref (peer); + } else + GST_WARNING_OBJECT (stream_combiner, "sourcepad has no peer !"); + return res; +} + +static gboolean +gst_stream_combiner_src_event (GstPad * pad, GstEvent * event) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + GstPad *sinkpad = NULL; + + STREAMS_LOCK (stream_combiner); + if (stream_combiner->current) + sinkpad = stream_combiner->current; + else if (stream_combiner->sinkpads) + sinkpad = (GstPad *) stream_combiner->sinkpads->data; + STREAMS_UNLOCK (stream_combiner); + + if (sinkpad) + /* Forward upstream as is */ + return gst_pad_push_event (sinkpad, event); + return FALSE; +} + +static gboolean +gst_stream_combiner_src_query (GstPad * pad, GstQuery * query) +{ + GstStreamCombiner *stream_combiner = + (GstStreamCombiner *) GST_PAD_PARENT (pad); + + GstPad *sinkpad = NULL; + + STREAMS_LOCK (stream_combiner); + if (stream_combiner->current) + sinkpad = stream_combiner->current; + else if (stream_combiner->sinkpads) + sinkpad = (GstPad *) stream_combiner->sinkpads->data; + STREAMS_UNLOCK (stream_combiner); + + if (sinkpad) + /* Forward upstream as is */ + return gst_pad_peer_query (sinkpad, query); + return FALSE; +} + +static void +gst_stream_combiner_init (GstStreamCombiner * stream_combiner) +{ + stream_combiner->srcpad = + gst_pad_new_from_static_template (&src_template, "src"); + gst_pad_set_event_function (stream_combiner->srcpad, + gst_stream_combiner_src_event); + gst_pad_set_query_function (stream_combiner->srcpad, + gst_stream_combiner_src_query); + gst_element_add_pad (GST_ELEMENT (stream_combiner), stream_combiner->srcpad); + + stream_combiner->lock = g_mutex_new (); +} + +static GstPad * +gst_stream_combiner_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name) +{ + GstStreamCombiner *stream_combiner = (GstStreamCombiner *) element; + GstPad *sinkpad; + + GST_DEBUG_OBJECT (element, "templ:%p, name:%s", templ, name); + + sinkpad = gst_pad_new_from_static_template (&sink_template, name); + /* FIXME : No buffer alloc for the time being, it will resort to the fallback */ + /* gst_pad_set_bufferalloc_function (sinkpad, gst_stream_combiner_buffer_alloc); */ + gst_pad_set_chain_function (sinkpad, gst_stream_combiner_chain); + gst_pad_set_event_function (sinkpad, gst_stream_combiner_sink_event); + gst_pad_set_getcaps_function (sinkpad, gst_stream_combiner_sink_getcaps); + gst_pad_set_setcaps_function (sinkpad, gst_stream_combiner_sink_setcaps); + + STREAMS_LOCK (stream_combiner); + stream_combiner->sinkpads = + g_list_append (stream_combiner->sinkpads, sinkpad); + gst_pad_set_active (sinkpad, TRUE); + gst_element_add_pad (element, sinkpad); + stream_combiner->cookie++; + STREAMS_UNLOCK (stream_combiner); + + GST_DEBUG_OBJECT (element, "Returning pad %p", sinkpad); + + return sinkpad; +} + +static void +gst_stream_combiner_release_pad (GstElement * element, GstPad * pad) +{ + GstStreamCombiner *stream_combiner = (GstStreamCombiner *) element; + GList *tmp; + + GST_DEBUG_OBJECT (element, "pad %s:%s", GST_DEBUG_PAD_NAME (pad)); + + STREAMS_LOCK (stream_combiner); + tmp = g_list_find (stream_combiner->sinkpads, pad); + if (tmp) { + GstPad *pad = (GstPad *) tmp->data; + + stream_combiner->sinkpads = + g_list_delete_link (stream_combiner->sinkpads, tmp); + stream_combiner->cookie++; + + if (pad == stream_combiner->current) { + /* Deactivate current flow */ + GST_DEBUG_OBJECT (element, "Removed pad was the current one"); + stream_combiner->current = NULL; + } + GST_DEBUG_OBJECT (element, "Removing pad from ourself"); + gst_element_remove_pad (element, pad); + } + STREAMS_UNLOCK (stream_combiner); + + return; +} diff --git a/gst/encoding/gststreamcombiner.h b/gst/encoding/gststreamcombiner.h new file mode 100644 index 0000000000..ce67277d72 --- /dev/null +++ b/gst/encoding/gststreamcombiner.h @@ -0,0 +1,60 @@ +/* GStreamer Stream Combiner + * Copyright (C) 2010 Edward Hervey + * (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_STREAMCOMBINER_H__ +#define __GST_STREAMCOMBINER_H__ + +#include + +#define GST_TYPE_STREAM_COMBINER (gst_stream_combiner_get_type()) +#define GST_STREAM_COMBINER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_STREAM_COMBINER,GstStreamCombiner)) +#define GST_STREAM_COMBINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_STREAM_COMBINER,GstStreamCombinerClass)) +#define GST_IS_STREAM_COMBINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_STREAM_COMBINER)) +#define GST_IS_STREAM_COMBINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_STREAM_COMBINER)) + +typedef struct _GstStreamCombiner GstStreamCombiner; +typedef struct _GstStreamCombinerClass GstStreamCombinerClass; + +struct _GstStreamCombiner { + GstElement parent; + + GstPad *srcpad; + + /* lock protects: + * * the current pad + * * the list of srcpads + */ + GMutex *lock; + /* Currently activated srcpad */ + GstPad *current; + GList *sinkpads; + guint32 cookie; + +}; + +struct _GstStreamCombinerClass { + GstElementClass parent; +}; + +GType gst_stream_combiner_get_type(void); + +GstElement *gst_stream_combiner_new (gchar *name); + +#endif /* __GST_STREAMCOMBINER_H__ */ diff --git a/gst/encoding/gststreamsplitter.c b/gst/encoding/gststreamsplitter.c new file mode 100644 index 0000000000..a2fa589f36 --- /dev/null +++ b/gst/encoding/gststreamsplitter.c @@ -0,0 +1,431 @@ +/* GStreamer Stream Splitter + * Copyright (C) 2010 Edward Hervey + * (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 "gststreamsplitter.h" + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE ("src_%d", GST_PAD_SRC, GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (gst_stream_splitter_debug); +#define GST_CAT_DEFAULT gst_stream_splitter_debug + +G_DEFINE_TYPE (GstStreamSplitter, gst_stream_splitter, GST_TYPE_ELEMENT); + +#define STREAMS_LOCK(obj) (g_mutex_lock(obj->lock)) +#define STREAMS_UNLOCK(obj) (g_mutex_unlock(obj->lock)) + +static void gst_stream_splitter_dispose (GObject * object); + +static GstPad *gst_stream_splitter_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name); +static void gst_stream_splitter_release_pad (GstElement * element, + GstPad * pad); + +static void +gst_stream_splitter_class_init (GstStreamSplitterClass * klass) +{ + GObjectClass *gobject_klass; + GstElementClass *gstelement_klass; + + gobject_klass = (GObjectClass *) klass; + gstelement_klass = (GstElementClass *) klass; + + gobject_klass->dispose = gst_stream_splitter_dispose; + + GST_DEBUG_CATEGORY_INIT (gst_stream_splitter_debug, "streamsplitter", 0, + "Stream Splitter"); + + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (gstelement_klass, + gst_static_pad_template_get (&sink_template)); + + gstelement_klass->request_new_pad = + GST_DEBUG_FUNCPTR (gst_stream_splitter_request_new_pad); + gstelement_klass->release_pad = + GST_DEBUG_FUNCPTR (gst_stream_splitter_release_pad); + + gst_element_class_set_details_simple (gstelement_klass, + "streamsplitter", "Generic", + "Splits streams based on their media type", + "Edward Hervey "); +} + +static void +gst_stream_splitter_dispose (GObject * object) +{ + GstStreamSplitter *stream_splitter = (GstStreamSplitter *) object; + + if (stream_splitter->lock) { + g_mutex_free (stream_splitter->lock); + stream_splitter->lock = NULL; + } + + G_OBJECT_CLASS (gst_stream_splitter_parent_class)->dispose (object); +} + +static GstFlowReturn +gst_stream_splitter_chain (GstPad * pad, GstBuffer * buf) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + GstFlowReturn res; + GstPad *srcpad = NULL; + + STREAMS_LOCK (stream_splitter); + if (stream_splitter->current) + srcpad = gst_object_ref (stream_splitter->current); + STREAMS_UNLOCK (stream_splitter); + + if (G_UNLIKELY (srcpad == NULL)) + goto nopad; + + if (G_UNLIKELY (stream_splitter->pending_events)) { + GList *tmp; + GST_DEBUG_OBJECT (srcpad, "Pushing out pending events"); + + for (tmp = stream_splitter->pending_events; tmp; tmp = tmp->next) { + GstEvent *event = (GstEvent *) tmp->data; + gst_pad_push_event (srcpad, event); + } + g_list_free (stream_splitter->pending_events); + stream_splitter->pending_events = NULL; + } + + /* Forward to currently activated stream */ + res = gst_pad_push (srcpad, buf); + gst_object_unref (srcpad); + + return res; + +nopad: + GST_WARNING_OBJECT (stream_splitter, "No output pad was configured"); + return GST_FLOW_ERROR; +} + +static gboolean +gst_stream_splitter_sink_event (GstPad * pad, GstEvent * event) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + gboolean res = TRUE; + gboolean toall = FALSE; + gboolean store = FALSE; + gboolean eos = FALSE; + gboolean flushpending = FALSE; + + /* FLUSH_START/STOP : forward to all + * EOS : transform to CUSTOM_REAL_EOS and forward to all + * INBAND events : store to send in chain function to selected chain + * OUT_OF_BAND events : send to all + */ + + GST_DEBUG_OBJECT (stream_splitter, "Got event %s", + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + flushpending = TRUE; + toall = TRUE; + break; + case GST_EVENT_FLUSH_START: + toall = TRUE; + break; + case GST_EVENT_EOS: + /* Replace with our custom eos event */ + gst_event_unref (event); + event = + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_empty_new ("stream-switching-eos")); + toall = TRUE; + eos = TRUE; + break; + default: + if (GST_EVENT_TYPE (event) & GST_EVENT_TYPE_SERIALIZED) + store = TRUE; + } + + if (flushpending) { + GList *tmp; + for (tmp = stream_splitter->pending_events; tmp; tmp = tmp->next) + gst_event_unref ((GstEvent *) tmp->data); + g_list_free (stream_splitter->pending_events); + stream_splitter->pending_events = NULL; + } + + if (store) { + stream_splitter->pending_events = + g_list_append (stream_splitter->pending_events, event); + } else if (toall || eos) { + GList *tmp; + guint32 cookie; + + /* Send to all pads */ + STREAMS_LOCK (stream_splitter); + resync: + if (G_UNLIKELY (stream_splitter->srcpads == NULL)) { + STREAMS_UNLOCK (stream_splitter); + /* No source pads */ + gst_event_unref (event); + res = FALSE; + goto beach; + } + tmp = stream_splitter->srcpads; + cookie = stream_splitter->cookie; + while (tmp) { + GstPad *srcpad = (GstPad *) tmp->data; + STREAMS_UNLOCK (stream_splitter); + /* In case of EOS, we first push out the real one to flush out + * each streams (but which will be discarded in the streamcombiner) + * before our custom one (which will be converted back to and EOS + * in the streamcombiner) */ + if (eos) + gst_pad_push_event (srcpad, gst_event_new_eos ()); + gst_event_ref (event); + res = gst_pad_push_event (srcpad, event); + STREAMS_LOCK (stream_splitter); + if (G_UNLIKELY (cookie != stream_splitter->cookie)) + goto resync; + tmp = tmp->next; + } + STREAMS_UNLOCK (stream_splitter); + gst_event_unref (event); + } else { + GstPad *pad; + + /* Only send to current pad */ + + STREAMS_LOCK (stream_splitter); + pad = stream_splitter->current; + STREAMS_UNLOCK (stream_splitter); + if (pad) + res = gst_pad_push_event (pad, event); + else { + gst_event_unref (event); + res = FALSE; + } + } + +beach: + return res; +} + +static GstCaps * +gst_stream_splitter_sink_getcaps (GstPad * pad) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + guint32 cookie; + GList *tmp; + GstCaps *res = NULL; + + /* Return the combination of all downstream caps */ + + STREAMS_LOCK (stream_splitter); + +resync: + if (G_UNLIKELY (stream_splitter->srcpads == NULL)) { + res = gst_caps_new_any (); + goto beach; + } + + res = NULL; + cookie = stream_splitter->cookie; + tmp = stream_splitter->srcpads; + + while (tmp) { + GstPad *srcpad = (GstPad *) tmp->data; + + STREAMS_UNLOCK (stream_splitter); + if (res) + gst_caps_merge (res, gst_pad_peer_get_caps_reffed (srcpad)); + else + res = gst_pad_peer_get_caps (srcpad); + STREAMS_LOCK (stream_splitter); + + if (G_UNLIKELY (cookie != stream_splitter->cookie)) { + if (res) + gst_caps_unref (res); + goto resync; + } + tmp = tmp->next; + } + +beach: + STREAMS_UNLOCK (stream_splitter); + return res; +} + +static gboolean +gst_stream_splitter_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + guint32 cookie; + GList *tmp; + gboolean res; + + GST_DEBUG_OBJECT (stream_splitter, "caps %" GST_PTR_FORMAT, caps); + + /* Try on all pads, choose the one that succeeds as the current stream */ + STREAMS_LOCK (stream_splitter); + +resync: + if (G_UNLIKELY (stream_splitter->srcpads == NULL)) { + res = FALSE; + goto beach; + } + + res = FALSE; + tmp = stream_splitter->srcpads; + cookie = stream_splitter->cookie; + + while (tmp) { + GstPad *srcpad = (GstPad *) tmp->data; + GstCaps *peercaps; + + STREAMS_UNLOCK (stream_splitter); + peercaps = gst_pad_peer_get_caps_reffed (srcpad); + if (peercaps) { + res = gst_caps_can_intersect (caps, peercaps); + gst_caps_unref (peercaps); + } + STREAMS_LOCK (stream_splitter); + + if (G_UNLIKELY (cookie != stream_splitter->cookie)) + goto resync; + + if (res) { + /* FIXME : we need to switch properly */ + GST_DEBUG_OBJECT (srcpad, "Setting caps on this pad was succesfull"); + stream_splitter->current = srcpad; + goto beach; + } + tmp = tmp->next; + } + +beach: + STREAMS_UNLOCK (stream_splitter); + return res; +} + +static gboolean +gst_stream_splitter_src_event (GstPad * pad, GstEvent * event) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + + GST_DEBUG_OBJECT (pad, "%s", GST_EVENT_TYPE_NAME (event)); + + /* Forward upstream as is */ + return gst_pad_push_event (stream_splitter->sinkpad, event); +} + +static gboolean +gst_stream_splitter_src_query (GstPad * pad, GstQuery * query) +{ + GstStreamSplitter *stream_splitter = + (GstStreamSplitter *) GST_PAD_PARENT (pad); + + GST_DEBUG_OBJECT (pad, "%s", GST_QUERY_TYPE_NAME (query)); + + /* Forward upstream as is */ + return gst_pad_peer_query (stream_splitter->sinkpad, query); +} + +static void +gst_stream_splitter_init (GstStreamSplitter * stream_splitter) +{ + stream_splitter->sinkpad = + gst_pad_new_from_static_template (&sink_template, "sink"); + /* FIXME : No buffer alloc for the time being, it will resort to the fallback */ + /* gst_pad_set_bufferalloc_function (stream_splitter->sinkpad, */ + /* gst_stream_splitter_buffer_alloc); */ + gst_pad_set_chain_function (stream_splitter->sinkpad, + gst_stream_splitter_chain); + gst_pad_set_event_function (stream_splitter->sinkpad, + gst_stream_splitter_sink_event); + gst_pad_set_getcaps_function (stream_splitter->sinkpad, + gst_stream_splitter_sink_getcaps); + gst_pad_set_setcaps_function (stream_splitter->sinkpad, + gst_stream_splitter_sink_setcaps); + gst_element_add_pad (GST_ELEMENT (stream_splitter), stream_splitter->sinkpad); + + stream_splitter->lock = g_mutex_new (); +} + +static GstPad * +gst_stream_splitter_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name) +{ + GstStreamSplitter *stream_splitter = (GstStreamSplitter *) element; + GstPad *srcpad; + + srcpad = gst_pad_new_from_static_template (&src_template, name); + gst_pad_set_event_function (srcpad, gst_stream_splitter_src_event); + gst_pad_set_query_function (srcpad, gst_stream_splitter_src_query); + + STREAMS_LOCK (stream_splitter); + stream_splitter->srcpads = g_list_append (stream_splitter->srcpads, srcpad); + gst_pad_set_active (srcpad, TRUE); + gst_element_add_pad (element, srcpad); + stream_splitter->cookie++; + STREAMS_UNLOCK (stream_splitter); + + return srcpad; +} + +static void +gst_stream_splitter_release_pad (GstElement * element, GstPad * pad) +{ + GstStreamSplitter *stream_splitter = (GstStreamSplitter *) element; + GList *tmp; + + STREAMS_LOCK (stream_splitter); + tmp = g_list_find (stream_splitter->srcpads, pad); + if (tmp) { + GstPad *pad = (GstPad *) tmp->data; + + stream_splitter->srcpads = + g_list_delete_link (stream_splitter->srcpads, tmp); + stream_splitter->cookie++; + + if (pad == stream_splitter->current) { + /* Deactivate current flow */ + GST_DEBUG_OBJECT (element, "Removed pad was the current one"); + stream_splitter->current = NULL; + } + + gst_element_remove_pad (element, pad); + } + STREAMS_UNLOCK (stream_splitter); + + return; +} diff --git a/gst/encoding/gststreamsplitter.h b/gst/encoding/gststreamsplitter.h new file mode 100644 index 0000000000..b503c00fc9 --- /dev/null +++ b/gst/encoding/gststreamsplitter.h @@ -0,0 +1,62 @@ +/* GStreamer Stream Splitter + * Copyright (C) 2010 Edward Hervey + * (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_STREAMSPLITTER_H__ +#define __GST_STREAMSPLITTER_H__ + +#include + +#define GST_TYPE_STREAM_SPLITTER (gst_stream_splitter_get_type()) +#define GST_STREAM_SPLITTER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_STREAM_SPLITTER,GstStreamSplitter)) +#define GST_STREAM_SPLITTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_STREAM_SPLITTER,GstStreamSplitterClass)) +#define GST_IS_STREAM_SPLITTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_STREAM_SPLITTER)) +#define GST_IS_STREAM_SPLITTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_STREAM_SPLITTER)) + +typedef struct _GstStreamSplitter GstStreamSplitter; +typedef struct _GstStreamSplitterClass GstStreamSplitterClass; + +struct _GstStreamSplitter { + GstElement parent; + + GstPad *sinkpad; + + /* lock protects: + * * the current pad + * * the list of srcpads + */ + GMutex *lock; + /* Currently activated srcpad */ + GstPad *current; + GList *srcpads; + guint32 cookie; + + /* List of pending in-band events */ + GList *pending_events; +}; + +struct _GstStreamSplitterClass { + GstElementClass parent; +}; + +GType gst_stream_splitter_get_type(void); + +GstElement *gst_stream_splitter_new (gchar *name); + +#endif /* __GST_STREAMSPLITTER_H__ */ diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 59ba7403f8..03387af51f 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -106,6 +106,7 @@ check_PROGRAMS = \ elements/audiotestsrc \ elements/decodebin \ elements/decodebin2 \ + elements/encodebin \ elements/ffmpegcolorspace \ elements/gdpdepay \ elements/gdppay \ @@ -298,6 +299,9 @@ elements_playbin2_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) elements_decodebin_LDADD = $(GST_BASE_LIBS) $(LDADD) elements_decodebin_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_encodebin_LDADD = $(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la $(GST_BASE_LIBS) $(LDADD) +elements_encodebin_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) + elements_decodebin2_LDADD = $(GST_BASE_LIBS) $(LDADD) elements_decodebin2_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS) diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index 3e11d9e015..20bd96e23f 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -9,6 +9,7 @@ audioresample audiotestsrc decodebin decodebin2 +encodebin gdpdepay gdppay gnomevfssink diff --git a/tests/check/elements/encodebin.c b/tests/check/elements/encodebin.c new file mode 100644 index 0000000000..b325694009 --- /dev/null +++ b/tests/check/elements/encodebin.c @@ -0,0 +1,742 @@ +/* GStreamer unit test for gstprofile + * + * Copyright (C) <2009> Edward Hervey + * + * 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 + +/* Helper functions to create profiles */ + +static GstEncodingProfile * +create_ogg_vorbis_profile (guint presence, gchar * preset) +{ + GstEncodingContainerProfile *cprof; + GstCaps *ogg, *vorbis; + + ogg = gst_caps_new_simple ("application/ogg", NULL); + cprof = + gst_encoding_container_profile_new ((gchar *) "myprofile", NULL, ogg, + NULL); + gst_caps_unref (ogg); + + vorbis = gst_caps_new_simple ("audio/x-vorbis", NULL); + fail_unless (gst_encoding_container_profile_add_profile (cprof, + (GstEncodingProfile *) gst_encoding_audio_profile_new (vorbis, preset, + NULL, presence))); + gst_caps_unref (vorbis); + + return (GstEncodingProfile *) cprof; +} + +static GstEncodingProfile * +create_ogg_theora_vorbis_profile (guint theorapresence, guint vorbispresence) +{ + GstEncodingContainerProfile *prof; + GstCaps *ogg, *vorbis, *theora; + + ogg = gst_caps_new_simple ("application/ogg", NULL); + prof = + gst_encoding_container_profile_new ((gchar *) "myprofile", NULL, ogg, + NULL); + gst_caps_unref (ogg); + + vorbis = gst_caps_new_simple ("audio/x-vorbis", NULL); + fail_unless (gst_encoding_container_profile_add_profile (prof, + (GstEncodingProfile *) gst_encoding_audio_profile_new (vorbis, NULL, + NULL, vorbispresence))); + gst_caps_unref (vorbis); + + theora = gst_caps_new_simple ("video/x-theora", NULL); + fail_unless (gst_encoding_container_profile_add_profile (prof, + (GstEncodingProfile *) gst_encoding_video_profile_new (theora, NULL, + NULL, theorapresence))); + gst_caps_unref (theora); + + return (GstEncodingProfile *) prof; +} + +GST_START_TEST (test_encodebin_states) +{ + GstElement *ebin; + GstEncodingProfile *prof, *prof2; + GstCaps *ogg; + GstPad *srcpad; + GstPad *target; + + /* Create an encodebin and check that it correctly changes states + * according to whether a profile is set or not */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + + /* At this point, the ghostpad has *NO* target */ + target = gst_ghost_pad_get_target (GST_GHOST_PAD (srcpad)); + fail_unless (target == NULL); + g_object_unref (srcpad); + + /* No profile, + * switching to READY should succeed, + * but switching to PAUSED should fail + */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_READY), + GST_STATE_CHANGE_SUCCESS); + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_FAILURE); + + /* Set a profile on encodebin... */ + ogg = gst_caps_new_simple ("application/ogg", NULL); + prof = (GstEncodingProfile *) gst_encoding_container_profile_new ((gchar *) + "myprofile", NULL, ogg, NULL); + gst_caps_unref (ogg); + + g_object_set (ebin, "profile", prof, NULL); + + /* ... and check the profile has been properly set */ + g_object_get (ebin, "profile", &prof2, NULL); + + fail_unless (gst_encoding_profile_is_equal (prof, prof2)); + + gst_encoding_profile_unref (prof); + gst_encoding_profile_unref (prof2); + + /* Make sure we can go to PAUSED */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* At this point, the source pad *HAS* a target */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + target = gst_ghost_pad_get_target (GST_GHOST_PAD (srcpad)); + fail_unless (target != NULL); + g_object_unref (target); + g_object_unref (srcpad); + + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_static) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstPad *srcpad, *sinkpad; + + /* Create an encodebin and check that it properly creates the sink pads + * for a single-stream profile with fixed presence */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* streamprofile that has a forced presence of 1 */ + prof = create_ogg_vorbis_profile (1, NULL); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + g_object_unref (srcpad); + + /* Check if the audio sink pad was properly created */ + sinkpad = gst_element_get_static_pad (ebin, "audio_0"); + fail_unless (sinkpad != NULL); + g_object_unref (sinkpad); + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_nopreset_static) +{ + GstElement *ebin; + GstEncodingProfile *prof; + + /* Create an encodebin with a bogus preset and check it fails switching states */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* streamprofile that has a forced presence of 1 */ + prof = create_ogg_vorbis_profile (1, (gchar *) "nowaythispresetexists"); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + /* It will go to READY... */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_READY), + GST_STATE_CHANGE_SUCCESS); + /* ... but to not PAUSED */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_FAILURE); + + gst_element_set_state (ebin, GST_STATE_NULL); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_dynamic) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstPad *srcpad, *sinkpad; + GstCaps *sinkcaps; + + /* Create an encodebin and check that it properly creates the sink pads + * for a single-stream profile with a unfixed presence */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* streamprofile that has non-forced presence */ + prof = create_ogg_vorbis_profile (0, NULL); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + g_object_unref (srcpad); + + /* Check if the audio sink pad can be requested */ + sinkpad = gst_element_get_request_pad (ebin, "audio_0"); + fail_unless (sinkpad != NULL); + gst_element_release_request_pad (ebin, sinkpad); + sinkpad = NULL; + + /* Check again with the 'request-pad' signal */ + sinkcaps = gst_caps_new_simple ("audio/x-raw-int", NULL); + g_signal_emit_by_name (ebin, "request-pad", sinkcaps, &sinkpad); + gst_caps_unref (sinkcaps); + fail_unless (sinkpad != NULL); + gst_element_release_request_pad (ebin, sinkpad); + + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_multiple_static) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstPad *srcpad, *sinkpadvorbis, *sinkpadtheora; + + /* Create an encodebin and check that it properly creates the sink pads */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* First try is with a streamprofile that has a forced presence of 1 */ + prof = create_ogg_theora_vorbis_profile (1, 1); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + g_object_unref (srcpad); + + /* Check if the audio sink pad was properly created */ + sinkpadvorbis = gst_element_get_static_pad (ebin, "audio_0"); + fail_unless (sinkpadvorbis != NULL); + g_object_unref (sinkpadvorbis); + + /* Check if the video sink pad was properly created */ + sinkpadtheora = gst_element_get_static_pad (ebin, "video_1"); + fail_unless (sinkpadtheora != NULL); + g_object_unref (sinkpadtheora); + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_multiple_dynamic) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstPad *srcpad, *sinkpadvorbis, *sinkpadtheora; + + /* Create an encodebin and check that it properly creates the sink pads + * for a multiple-stream with unfixed presence */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* multi-stream profile that has non-forced presence */ + prof = create_ogg_theora_vorbis_profile (0, 0); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + g_object_unref (srcpad); + + /* Check if the audio sink pad was properly created */ + sinkpadvorbis = gst_element_get_request_pad (ebin, "audio_0"); + fail_unless (sinkpadvorbis != NULL); + g_object_unref (sinkpadvorbis); + + /* Check if the video sink pad was properly created */ + sinkpadtheora = gst_element_get_request_pad (ebin, "video_1"); + fail_unless (sinkpadtheora != NULL); + g_object_unref (sinkpadtheora); + + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_sink_pads_dynamic_encoder) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstPad *srcpad, *sinkpad = NULL; + GstCaps *vorbiscaps; + + /* Create an encodebin and check that it properly creates the sink pads + * for a single-stream profile with a unfixed presence */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + /* streamprofile that has non-forced presence */ + prof = create_ogg_vorbis_profile (0, NULL); + + g_object_set (ebin, "profile", prof, NULL); + + gst_encoding_profile_unref (prof); + + /* Check if the source pad was properly created */ + srcpad = gst_element_get_static_pad (ebin, "src"); + fail_unless (srcpad != NULL); + g_object_unref (srcpad); + + /* Check if the audio sink pad was properly created */ + vorbiscaps = gst_caps_from_string ("audio/x-vorbis,channels=2,rate=44100"); + g_signal_emit_by_name (ebin, "request-pad", vorbiscaps, &sinkpad); + gst_caps_unref (vorbiscaps); + fail_unless (sinkpad != NULL); + gst_element_release_request_pad (ebin, sinkpad); + + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_SUCCESS); + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + +GST_START_TEST (test_encodebin_render_audio_static) +{ + GstElement *ebin, *pipeline, *audiotestsrc, *fakesink; + GstEncodingProfile *prof; + GstBus *bus; + gboolean done = FALSE; + + /* Create an encodebin and render 5s of vorbis/ogg */ + + pipeline = gst_pipeline_new ("encodebin-pipeline"); + bus = gst_pipeline_get_bus ((GstPipeline *) pipeline); + audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (audiotestsrc, "num-buffers", 10, NULL); + fakesink = gst_element_factory_make ("fakesink", NULL); + + ebin = gst_element_factory_make ("encodebin", NULL); + + prof = create_ogg_vorbis_profile (1, NULL); + g_object_set (ebin, "profile", prof, NULL); + gst_encoding_profile_unref (prof); + + gst_bin_add_many ((GstBin *) pipeline, audiotestsrc, ebin, fakesink, NULL); + + fail_unless (gst_element_link_many (audiotestsrc, ebin, fakesink, NULL)); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + while (!done) { + GstMessage *msg; + + /* poll the bus until we get EOS without any errors */ + msg = gst_bus_timed_pop (bus, GST_SECOND / 10); + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + fail ("GST_MESSAGE_ERROR"); + break; + case GST_MESSAGE_EOS: + done = TRUE; + break; + default: + break; + } + gst_message_unref (msg); + } + } + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + g_object_unref (bus); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_encodebin_render_audio_dynamic) +{ + GstElement *ebin, *pipeline, *audiotestsrc, *fakesink; + GstEncodingProfile *prof; + GstBus *bus; + GstPad *sinkpad, *srcpad; + gboolean done = FALSE; + + /* Create an encodebin and render 5s of vorbis/ogg */ + + pipeline = gst_pipeline_new ("encodebin-pipeline"); + bus = gst_pipeline_get_bus ((GstPipeline *) pipeline); + audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (audiotestsrc, "num-buffers", 10, NULL); + fakesink = gst_element_factory_make ("fakesink", NULL); + + ebin = gst_element_factory_make ("encodebin", NULL); + + prof = create_ogg_vorbis_profile (0, NULL); + g_object_set (ebin, "profile", prof, NULL); + gst_encoding_profile_unref (prof); + + gst_bin_add_many ((GstBin *) pipeline, audiotestsrc, ebin, fakesink, NULL); + + srcpad = gst_element_get_static_pad (audiotestsrc, "src"); + fail_unless (srcpad != NULL); + + sinkpad = gst_element_get_request_pad (ebin, "audio_0"); + fail_unless (sinkpad != NULL); + + fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); + + g_object_unref (srcpad); + g_object_unref (sinkpad); + + fail_unless (gst_element_link (ebin, fakesink)); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + while (!done) { + GstMessage *msg; + + /* poll the bus until we get EOS without any errors */ + msg = gst_bus_timed_pop (bus, GST_SECOND / 10); + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + fail ("GST_MESSAGE_ERROR"); + break; + case GST_MESSAGE_EOS: + done = TRUE; + break; + default: + break; + } + gst_message_unref (msg); + } + } + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + g_object_unref (bus); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_encodebin_render_audio_video_static) +{ + GstElement *ebin, *pipeline, *audiotestsrc, *videotestsrc, *fakesink; + GstEncodingProfile *prof; + GstBus *bus; + gboolean done = FALSE; + + /* Create an encodebin and render 5s of vorbis/ogg */ + + pipeline = gst_pipeline_new ("encodebin-pipeline"); + bus = gst_pipeline_get_bus ((GstPipeline *) pipeline); + audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (audiotestsrc, "num-buffers", 10, NULL); + videotestsrc = gst_element_factory_make ("videotestsrc", NULL); + g_object_set (videotestsrc, "num-buffers", 5, NULL); + fakesink = gst_element_factory_make ("fakesink", NULL); + + ebin = gst_element_factory_make ("encodebin", NULL); + + prof = create_ogg_theora_vorbis_profile (1, 1); + g_object_set (ebin, "profile", prof, NULL); + gst_encoding_profile_unref (prof); + + gst_bin_add_many ((GstBin *) pipeline, audiotestsrc, videotestsrc, ebin, + fakesink, NULL); + + fail_unless (gst_element_link (videotestsrc, ebin)); + fail_unless (gst_element_link_many (audiotestsrc, ebin, fakesink, NULL)); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + while (!done) { + GstMessage *msg; + + /* poll the bus until we get EOS without any errors */ + msg = gst_bus_timed_pop (bus, GST_SECOND / 10); + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + fail ("GST_MESSAGE_ERROR"); + break; + case GST_MESSAGE_EOS: + done = TRUE; + break; + default: + break; + } + gst_message_unref (msg); + } + } + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + g_object_unref (bus); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_encodebin_render_audio_video_dynamic) +{ + GstElement *ebin, *pipeline, *audiotestsrc, *videotestsrc, *fakesink; + GstEncodingProfile *prof; + GstBus *bus; + gboolean done = FALSE; + GstPad *sinkpad, *srcpad; + + /* Create an encodebin and render 5s of vorbis/ogg */ + + pipeline = gst_pipeline_new ("encodebin-pipeline"); + bus = gst_pipeline_get_bus ((GstPipeline *) pipeline); + audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (audiotestsrc, "num-buffers", 10, NULL); + videotestsrc = gst_element_factory_make ("videotestsrc", NULL); + g_object_set (videotestsrc, "num-buffers", 5, NULL); + fakesink = gst_element_factory_make ("fakesink", NULL); + + ebin = gst_element_factory_make ("encodebin", NULL); + + prof = create_ogg_theora_vorbis_profile (0, 0); + g_object_set (ebin, "profile", prof, NULL); + gst_encoding_profile_unref (prof); + + gst_bin_add_many ((GstBin *) pipeline, audiotestsrc, videotestsrc, ebin, + fakesink, NULL); + + fail_unless (gst_element_link (ebin, fakesink)); + + srcpad = gst_element_get_static_pad (audiotestsrc, "src"); + sinkpad = gst_element_get_request_pad (ebin, "audio_0"); + fail_unless (srcpad != NULL); + fail_unless (sinkpad != NULL); + fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); + g_object_unref (srcpad); + g_object_unref (sinkpad); + + srcpad = gst_element_get_static_pad (videotestsrc, "src"); + sinkpad = gst_element_get_request_pad (ebin, "video_1"); + fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK); + g_object_unref (srcpad); + g_object_unref (sinkpad); + + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_PLAYING), + GST_STATE_CHANGE_ASYNC); + + while (!done) { + GstMessage *msg; + + /* poll the bus until we get EOS without any errors */ + msg = gst_bus_timed_pop (bus, GST_SECOND / 10); + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + fail ("GST_MESSAGE_ERROR"); + break; + case GST_MESSAGE_EOS: + done = TRUE; + break; + default: + break; + } + gst_message_unref (msg); + } + } + + /* Set back to NULL */ + fail_unless_equals_int (gst_element_set_state (pipeline, GST_STATE_NULL), + GST_STATE_CHANGE_SUCCESS); + + g_object_unref (bus); + + gst_object_unref (pipeline); +} + +GST_END_TEST; + +GST_START_TEST (test_encodebin_impossible_element_combination) +{ + GstElement *ebin; + GstEncodingProfile *prof; + GstCaps *ogg, *x264; + + ebin = gst_element_factory_make ("x264enc", NULL); + if (ebin == NULL) { + GST_DEBUG ("No available h264 encoder, skipping test"); + return; + } + gst_object_unref (ebin); + + /* Make sure that impossible combinations of encoders and muxer + * properly fail. In this case we try putting h264 in ogg. + * + * To properly test we abort early, we use a presence of zero for the + * h264 stream profile. */ + + ebin = gst_element_factory_make ("encodebin", NULL); + + ogg = gst_caps_new_simple ("application/ogg", NULL); + prof = (GstEncodingProfile *) gst_encoding_container_profile_new ((gchar *) + "myprofile", NULL, ogg, NULL); + gst_caps_unref (ogg); + + x264 = gst_caps_new_simple ("video/x-h264", NULL); + fail_unless (gst_encoding_container_profile_add_profile + (GST_ENCODING_CONTAINER_PROFILE (prof), + (GstEncodingProfile *) gst_encoding_video_profile_new (x264, NULL, + NULL, 0))); + gst_caps_unref (x264); + + g_object_set (ebin, "profile", prof, NULL); + gst_encoding_profile_unref (prof); + + /* It will go to READY... */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_READY), + GST_STATE_CHANGE_SUCCESS); + /* ... but to not PAUSED */ + fail_unless_equals_int (gst_element_set_state (ebin, GST_STATE_PAUSED), + GST_STATE_CHANGE_FAILURE); + + gst_element_set_state (ebin, GST_STATE_NULL); + + gst_object_unref (ebin); +}; + +GST_END_TEST; + + +static Suite * +encodebin_suite (void) +{ + Suite *s = suite_create ("encodebin element"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_encodebin_states); + tcase_add_test (tc_chain, test_encodebin_sink_pads_static); + tcase_add_test (tc_chain, test_encodebin_sink_pads_nopreset_static); + tcase_add_test (tc_chain, test_encodebin_sink_pads_dynamic); + tcase_add_test (tc_chain, test_encodebin_sink_pads_multiple_static); + tcase_add_test (tc_chain, test_encodebin_sink_pads_multiple_dynamic); + tcase_add_test (tc_chain, test_encodebin_sink_pads_dynamic_encoder); + tcase_add_test (tc_chain, test_encodebin_render_audio_static); + tcase_add_test (tc_chain, test_encodebin_render_audio_dynamic); + tcase_add_test (tc_chain, test_encodebin_render_audio_video_static); + tcase_add_test (tc_chain, test_encodebin_render_audio_video_dynamic); + tcase_add_test (tc_chain, test_encodebin_impossible_element_combination); + + return s; +} + +GST_CHECK_MAIN (encodebin);