Added state change code.
authorWim Taymans <wim.taymans@gmail.com>
Mon, 28 Mar 2005 14:54:33 +0000 (14:54 +0000)
committerWim Taymans <wim.taymans@gmail.com>
Mon, 28 Mar 2005 14:54:33 +0000 (14:54 +0000)
Original commit message from CVS:
Added state change code.
Added/updated docs.
Added sink base class, make fakesink extend the base class.
Small cleanups in GstPipeline.

26 files changed:
ChangeLog
configure.ac
docs/design/part-gstelement.txt
docs/design/part-negotiation.txt [new file with mode: 0644]
docs/design/part-preroll.txt [new file with mode: 0644]
docs/design/part-scheduling.txt [new file with mode: 0644]
docs/design/part-states.txt [new file with mode: 0644]
gst/Makefile.am
gst/base/Makefile.am [new file with mode: 0644]
gst/base/README [new file with mode: 0644]
gst/base/gstbasesink.c [new file with mode: 0644]
gst/base/gstbasesink.h [new file with mode: 0644]
gst/elements/Makefile.am
gst/elements/gstfakesink.c
gst/elements/gstfakesink.h
gst/gstbin.c
gst/gstelement.c
gst/gstpad.c
gst/gstpipeline.c
libs/gst/base/Makefile.am [new file with mode: 0644]
libs/gst/base/README [new file with mode: 0644]
libs/gst/base/gstbasesink.c [new file with mode: 0644]
libs/gst/base/gstbasesink.h [new file with mode: 0644]
plugins/elements/Makefile.am
plugins/elements/gstfakesink.c
plugins/elements/gstfakesink.h

index 0f53d9e..fd8d1c9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2005-03-28  Wim Taymans  <wim@fluendo.com>
+
+       * configure.ac:
+       * docs/design/part-gstelement.txt:
+       * docs/design/part-negotiation.txt:
+       * docs/design/part-preroll.txt:
+       * docs/design/part-scheduling.txt:
+       * docs/design/part-states.txt:
+       * gst/Makefile.am:
+       * gst/base/Makefile.am:
+       * gst/base/README:
+       * gst/base/gstbasesink.c: (gst_basesink_get_template),
+       (gst_basesink_base_init), (gst_basesink_class_init),
+       (gst_basesink_pad_getcaps), (gst_basesink_pad_setcaps),
+       (gst_basesink_pad_buffer_alloc), (gst_basesink_init),
+       (gst_basesink_set_pad_functions),
+       (gst_basesink_set_all_pad_functions), (gst_basesink_set_clock),
+       (gst_basesink_set_property), (gst_basesink_get_property),
+       (gst_base_sink_get_template), (gst_base_sink_get_caps),
+       (gst_base_sink_set_caps), (gst_base_sink_buffer_alloc),
+       (gst_basesink_preroll_queue_push),
+       (gst_basesink_preroll_queue_empty),
+       (gst_basesink_preroll_queue_flush), (gst_basesink_finish_preroll),
+       (gst_basesink_event), (gst_basesink_get_times),
+       (gst_basesink_do_sync), (gst_basesink_handle_buffer),
+       (gst_basesink_chain_unlocked), (gst_basesink_chain),
+       (gst_basesink_loop), (gst_basesink_activate),
+       (gst_basesink_change_state):
+       * gst/base/gstbasesink.h:
+       * gst/elements/Makefile.am:
+       * gst/elements/gstfakesink.c: (gst_fakesink_base_init),
+       (gst_fakesink_class_init), (gst_fakesink_init),
+       (gst_fakesink_set_property), (gst_fakesink_get_property),
+       (gst_fakesink_get_times), (gst_fakesink_event),
+       (gst_fakesink_preroll), (gst_fakesink_render),
+       (gst_fakesink_change_state):
+       * gst/elements/gstfakesink.h:
+       * gst/gstbin.c: (gst_bin_class_init), (gst_bin_set_manager),
+       (gst_bin_get_state), (gst_bin_change_state), (gst_bin_send_event):
+       * gst/gstelement.c: (gst_element_add_pad),
+       (gst_element_get_state_func), (gst_element_abort_state),
+       (gst_element_commit_state), (gst_element_lost_state),
+       (gst_element_set_state), (gst_element_pads_activate):
+       * gst/gstpad.c: (gst_pad_set_active), (gst_pad_event_default):
+       * gst/gstpipeline.c: (gst_pipeline_send_event),
+       (gst_pipeline_change_state):
+       Added state change code.
+       Added/updated docs.
+       Added sink base class, make fakesink extend the base class.
+       Small cleanups in GstPipeline.
+
 2005-03-26  David Schleef  <ds@schleef.org>
 
        * gst/Makefile.am: remove gstcpu.[ch].  The gst_cpu functionality
index c19851a..0de6f80 100644 (file)
@@ -655,7 +655,7 @@ include/Makefile
 gst/Makefile
 gst/gstconfig.h
 gst/gstversion.h
-gst/autoplug/Makefile
+gst/base/Makefile
 gst/indexers/Makefile
 gst/elements/Makefile
 gst/parse/Makefile
@@ -663,7 +663,6 @@ gst/schedulers/Makefile
 gst/registries/Makefile
 libs/Makefile
 libs/gst/Makefile
-libs/gst/bytestream/Makefile
 libs/gst/control/Makefile
 libs/gst/dataprotocol/Makefile
 libs/gst/getbits/Makefile
@@ -679,7 +678,6 @@ tests/sched/Makefile
 tests/threadstate/Makefile
 testsuite/Makefile
 testsuite/bins/Makefile
-testsuite/bytestream/Makefile
 testsuite/caps/Makefile
 testsuite/cleanup/Makefile
 testsuite/clock/Makefile
@@ -690,7 +688,6 @@ testsuite/elements/Makefile
 testsuite/ghostpads/Makefile
 testsuite/indexers/Makefile
 testsuite/negotiation/Makefile
-testsuite/pad/Makefile
 testsuite/parse/Makefile
 testsuite/plugin/Makefile
 testsuite/refcounting/Makefile
index 139bf65..6266518 100644 (file)
@@ -28,18 +28,14 @@ Element.  This allows deeply nested pipelines, and the possibility of
 Name
 ----
 
-All elements are named, and while they should ideally be unique in any given pipeline, the do not have to be.  The only 
-guaranteed unique name for an element is its complete path in the object hierarchy.  Functions are provided to set and 
-get the name of the element.  _set_name() creates a local copy of the string passed.  _get_name() returns the actual 
-element's pointer.  Therefore, the argument to _set_name() is the responsibility of the caller to free if necessary, 
-but the return from _get_name() is definitely not to be messed with by the caller.  Accordingly, _get_name() returns an 
-const gchar *.
-
-Providing a new name to an element causes it to free its internal copy of the name and make a copy of the new name.
-This means that you must consider the pointer returned by _get_name() to be short-lived.  If you must make use of the 
-name beyond the immediate scope, it is suggested that you make yourself a copy of it.  If you know for a fact neither 
-the pointer nor its contents will change, you may retain the original pointer.  If you get odd results when using the 
-returned string, that's the first thing to check.
+All elements are named, and while they should ideally be unique in any given
+pipeline, they do not have to be.  The only guaranteed unique name for an
+element is its complete path in the object hierarchy.  In other words, an
+element's name is unique inside its parent.  (This follows from GstObject's
+name explanation)
+
+This uniqueness is guaranteed through all functions where either parentage
+or name of an element is changed.
 
 
 Pads
diff --git a/docs/design/part-negotiation.txt b/docs/design/part-negotiation.txt
new file mode 100644 (file)
index 0000000..b0cf936
--- /dev/null
@@ -0,0 +1,152 @@
+Negotiation
+-----------
+
+Negotiation happens when elements want to push buffers and need to decide
+on the format. This is called downstream negotiation because the upstream
+element decides the format for the downstream element. This is the most
+common case.
+
+Negotiation can also happen when a downstream element wants to receive 
+another data format from an upstream element. This is called upstream
+negotiation.
+
+The basics of negotiation are as follows:
+
+ - GstCaps (see part-caps.txt) are attached and refcounted before they
+   are attached to a buffer to describe the contents of the buffer.
+   It is possible to add a NULL caps to a buffer, this means that the
+   buffer type did not change relative to the previous buffer. If no
+   previous buffer was received by a downstream element, it is free to
+   discard the buffer.
+
+ - Before receiving a buffer, an element must check if the datatype of
+   the buffer has changed. The element should reconfigure itself to the
+   new format before processing the buffer data. If the data type on
+   the buffer is not acceptable, the element should refuse the buffer.
+
+ - When requesting a buffer from a bufferpool, the prefered type should
+   be passed to the buffer allocation function. After receiving a buffer
+   from a bufferpool, the datatype should be checked again.
+
+ - A bufferpool allocation function should try to allocate a buffer of the
+   prefered type. If there is a good reason to choose another type, the
+   alloc function should see if that other type is accepted by the other
+   element, then allocate a buffer of that type and attach the type to the
+   buffer before returning it.
+
+
+The general flow for a source pad starting the negotiation.
+
+             src              sink
+              |                 |
+              |  accepts?       |
+  type A      |---------------->|
+              |      yes        |
+              |<----------------|
+              |                 |
+ get buffer   |  alloc_buf      |
+ from pool    |---------------->| 
+ with type A  |                 | Create buffer of type A.
+              |                 |
+ check type   |<----------------|
+ and use A    |                 |
+              |  push           |
+ push buffer  |---------------->| Receive type A, reconfigure to
+ with new type|                 | process type A.
+              |                 |
+
+ One possible implementation in pseudo code:
+
+ [element wants to create a buffer]
+ if not format
+   # see what the peer can do
+   peercaps = gst_pad_peer_get_caps (srcpad)
+   # see what we can do
+   ourcaps = gst_pad_get_caps (srcpad)
+
+   # get common formats
+   candidates = gst_caps_intersect (peercaps, ourcaps)
+
+   foreach candidate in candidates
+     # make sure the caps is fixed
+     fixedcaps = gst_pad_fixate_caps (srcpad, candidate)
+
+     # see if the peer accepts it
+     if gst_pad_peer_accept_caps (srcpad, fixedcaps)
+       # store the caps as the negotiated caps, this will
+       # call the setcaps function on the pad
+       gst_pad_set_caps (srcpad, fixedcaps)
+       break
+     endif
+   done
+ endif
+
+ # if the type is different, this will call the setcaps function of 
+ # the pad.
+ buffer = gst_pad_alloc_buffer (srcpad, 0, size, GST_PAD_CAPS (fixedcaps));
+ if buffer
+   [fill buffer and push]
+ elseif
+   [no buffer, either no peer or no acceptable format found]
+ endif
+
+
+The general flow for a sink pad starting a renegotiation.
+
+             src              sink
+              |                 |
+              |  accepts?       |
+             |<----------------| type B
+              |      yes        |
+              |---------------->|
+              |                 |
+ get buffer   |  alloc_buf      |
+ from pool    |---------------->| 
+ with type    |                 | Create buffer of new type B.
+              |                 |
+ check type   |<----------------|
+ and          |                 |
+ reconfigure  |                 |
+              |  push           |
+ push buffer  |---------------->| Receive type B, reconfigure to
+ with new type|                 | process type B.
+              |                 |
+
+             
+
+Use case:
+
+
+videotestsrc ! xvimagesink
+
+  1) Who decides what format to use?
+   - src pad always decides, by convention. sinkpad can suggest a format
+     by putting it high in the getcaps function GstCaps. 
+   - since the src decides, it can always choose something that it can do,
+     so this step can only fail if the sinkpad stated it could accept
+     something while later on it couldn't.
+
+  2) When does negotiation happen?
+   - before srcpad does a push, it figures out a type as stated in 1), then 
+     it calls the pad alloc function with the type. The sinkpad has to 
+     create a buffer of that type, src fills the buffer and sends it to sink.
+   - since the sink stated in 1) it could accept the type, it will be able to
+     create a buffer of the type and handle it.
+   - sink checks media type of buffer and configures itself for this type.
+     
+  3) How can sink request another format?
+   - sink asks if new format is possible for the source.
+   - sink returns buffer with new type in allocfunction.
+   - src receives buffer with new type, reconfigures and pushes.
+   - sink can always select something it can create and handle since it takes
+     the initiative. src should be able to handle the new type since it said
+     it could accept it.
+
+videotestsrc ! queue ! xvimagesink
+
+  - queue implements an allocfunction, proxying all calls to its srcpad peer.
+  - queue proxies all accept and getcaps to the other peer pad.
+  - queue contains buffers with different types.
+
+   
+
diff --git a/docs/design/part-preroll.txt b/docs/design/part-preroll.txt
new file mode 100644 (file)
index 0000000..3f4df53
--- /dev/null
@@ -0,0 +1,49 @@
+Preroll
+-------
+
+A sink element can only complete the state change to PAUSED after a buffer
+has been queued on the input pad or pads. This process is called prerolling
+and is needed to fill the pipeline with buffers so that the transition to
+PLAYING goes as fast as possible with no visual delay for the user.
+
+After receiving a buffer (or EOS) on a pad the chain/event function should
+wait to render the buffers or in the EOS case, wait to post the EOS
+message.
+
+Several things can happen that require the preroll lock to be unlocked. This
+include state changes or flush events.
+
+
+Committing the state
+--------------------
+
+When going to PAUSED and PLAYING a buffer should be queued in the pad. We also
+make this requirement for going to PLAYING since a flush event in the PAUSED
+state could unqueue the buffer again.
+
+The state is commited in the following conditions:
+
+ - a buffer is received on a sinkpad
+ - an EOS is received on a sinkpad.
+
+We require the state change to be commited in EOS as well since an EOS means
+by definition that no buffer is going to arrive anymore.
+
+
+Unlocking the preroll
+---------------------
+
+The following conditions unlock the preroll:
+
+ - a state change
+ - a flush event
+
+Result of the preroll
+---------------------
+
+After the preroll is unlocked, the element can be in the following states:
+
+ - in a state that disallows it to process the data (flushing, pad inactive)
+ - in a state that allows it to process the data.
+
diff --git a/docs/design/part-scheduling.txt b/docs/design/part-scheduling.txt
new file mode 100644 (file)
index 0000000..386cf54
--- /dev/null
@@ -0,0 +1,201 @@
+Scheduling
+----------
+
+The scheduling in GStreamer is based on pads actively pushing (producing) data or
+pad pulling in data (consuming) from other pads.
+
+Pushing
+-------
+
+A pad can produce data and push it to the next pad. A pad that behaves this way
+exposes a loop function that will be called repeadedly until it returns false. 
+The loop function is allowed to block whenever it wants. When the pad is deactivated
+the loop function should unblock though.
+
+A pad operating in the push mode can only produce data to a pad that exposes a
+chain function. This chain function will be called with the buffer produced by
+the pushing pad. 
+
+This method of producing data is called the streaming mode since the producer 
+produces a constant stream of data. 
+
+Pulling
+-------
+
+Pads that operate in pulling mode can only pull data from a pad that exposes the
+pullregion function. In this case, the sink pad exposes a loop function that will be
+called repeadedly until the task is stopped.
+
+After pulling data from the peer pad, the loop function will typically call the
+push function to push the result to the peer sinkpad.
+
+
+Deciding the scheduling mode
+----------------------------
+
+When the core performs the pad activate function, it will select a scheduling mode
+for the pads. Sinkpads that expose a loop function are prefered over source pads 
+with a loop function so that the pull mode is selected when possible. Selecting the
+pull mode is more efficient because it allows for arbitrary seeking and random access
+to the data.
+
+The chain function
+------------------
+
+The chain function will be called when a upstream element perform a _push() on the pad.
+The upstream element can be another chain based element or a pushing source.
+
+The getrange function
+---------------------
+
+The getrange function is called when a peer pad perform a _pullregion() on the pad. This
+downstream pad can be a pulling element or another pullregion() based element.
+
+Plug-in techniques
+------------------
+
+Multi-sink elements
+-------------------
+  
+  Elements with multiple sinks can either expose a loop function on each of the pads to
+  actively pullregion data or they can expose a chain function on each pad.
+
+  Implementing a chain function is usually easy and allows for all possible scheduling
+  methods.
+
+  Pad select
+  ----------
+
+    If the chain based sink wants to wait for one of the pads to receive a buffer, just
+    implement the action to perform in the chain function. Be aware that the action could
+    be performed in different threads and possibly simultaneously so grab the STREAM_LOCK.
+
+  Collect pads
+  ------------
+
+    If the chain based sink pads all require one buffer before the element can operate on
+    the data, collect all the buffers in the chain function and perform the action when
+    all chainpads received the buffer.
+
+    In this case you probably also don't want to accept more data on a pad that has a buffer
+    queued. This can easily be done with the following code snippet:
+
+      static GstFlowReturn _chain (GstPad *pad, GstBuffer *buffer) 
+      {
+        LOCK (mylock);
+        while (pad->store != NULL) {
+         WAIT (mycond, mylock);
+       }
+       pad->store = buffer;
+       SIGNAL (mycond);
+        UNLOCK (mylock);
+
+       return GST_FLOW_OK;
+      }
+
+      static void _pull (GstPad *pad, GstBuffer **buffer) 
+      {
+        LOCK (mylock);
+        while (pad->store == NULL) {
+         WAIT (mycond, mylock);
+       }
+       **buffer = pad->store;
+       pad->store = NULL;
+       SIGNAL (mycond);
+        UNLOCK (mylock);
+      }
+    
+
+Cases
+-----
+   Inside the braces below the pads is stated what function the
+   pad support:
+
+     l: exposes a loop function, so it can act as a pushing source.
+     g: exposes a getrange function
+     c: exposes a chain function
+
+     following scheduling decisions are made based on the scheduling
+     methods exposed by the pads:
+
+     (g) - (l): sinkpad will pull data from src
+     (l) - (c): srcpad actively pushes data to sinkpad
+     ()  - (c): srcpad will push data to sinkpad.
+
+     ()  - () : not schedulable.
+     ()  - (l): not schedulable.
+     (g) - () : not schedulable. 
+     (g) - (c): not schedulable.
+     (l) - () : not schedulable.
+     (l) - (l): not schedulable
+
+     ()  - (g): impossible
+     (g) - (g): impossible.
+     (l) - (g): impossible
+     (c) - () : impossible
+     (c) - (g): impossible
+     (c) - (l): impossible
+     (c) - (c): impossible
+
+    +---------+    +------------+    +-----------+
+    | filesrc |    | mp3decoder |    | audiosink |
+    |        src--sink         src--sink         |
+    +---------+    +------------+    +-----------+
+            (l-g) (c)           ()   (c)
+
+    When activating the pads:
+
+      * audiosink has a chain function and the peer pad has no
+        loop function, no scheduling is done.
+      * mp3decoder and filesrc expose an (l) - (c) connection,
+        a thread is created to call the srcpad loop function.
+
+    +---------+    +------------+    +----------+
+    | filesrc |    | avidemuxer |    | fakesink |
+    |        src--sink         src--sink        |
+    +---------+    +------------+    +----------+
+            (l-g) (l)          ()   (c)
+           
+      * fakesink has a chain function and the peer pad has no
+        loop function, no scheduling is done.
+      * avidemuxer and filesrc expose an (g) - (l) connection,
+        a thread is created to call the sinkpad loop function.
+
+    +---------+    +----------+    +------------+    +----------+
+    | filesrc |    | identity |    | avidemuxer |    | fakesink |
+    |        src--sink       src--sink         src--sink        |
+    +---------+    +----------+    +------------+    +----------+
+            (l-g) (c)        ()   (l)          ()   (c)
+
+      * fakesink has a chain function and the peer pad has no
+        loop function, no scheduling is done.
+      * avidemuxer and identity expose no schedulable connection so
+        this pipeline is not schedulable.
+
+    +---------+    +----------+    +------------+    +----------+
+    | filesrc |    | identity |    | avidemuxer |    | fakesink |
+    |        src--sink       src--sink         src--sink        |
+    +---------+    +----------+    +------------+    +----------+
+            (l-g) (c-l)      (g)  (l)          ()   (c)
+
+      * fakesink has a chain function and the peer pad has no
+        loop function, no scheduling is done.
+      * avidemuxer and identity expose an (g) - (l) connection,
+        a thread is created to call the sinkpad loop function.
+      * identity knows the srcpad is getrange based and uses the
+        thread from avidemux to getrange data from filesrc.
+
+    +---------+    +----------+    +------------+    +----------+
+    | filesrc |    | identity |    | oggdemuxer |    | fakesink |
+    |        src--sink       src--sink         src--sink        |
+    +---------+    +----------+    +------------+    +----------+
+            (l-g) (c)        ()   (l-c)        ()   (c)
+
+      * fakesink has a chain function and the peer pad has no
+        loop function, no scheduling is done.
+      * oggdemuxer and identity expose an () - (l-c) connection,
+        oggdemux has to operate in chain mode.
+      * identity chan only work chain based and so filesrc creates
+        a thread to push data to identity.
+
+
diff --git a/docs/design/part-states.txt b/docs/design/part-states.txt
new file mode 100644 (file)
index 0000000..a901632
--- /dev/null
@@ -0,0 +1,151 @@
+States
+======
+
+Both elements and pads can be in different states. The states of the pads are 
+linked to the state of the element so the design of the states is mainly
+focused around the element states.
+
+An element can be in 4 states. NULL, READY, PAUSED and PLAYING. When an element
+is initially instantiated, it is in the NULL state.
+
+
+State definitions
+-----------------
+
+ - NULL:    This is the initial state of an element. 
+ - READY:   The element should be prepared to go to PAUSED.
+ - PAUSED:  The element should be ready to accept and process data. Sink
+            elements however only accept one buffer and then block.
+ - PLAYING: The same as PAUSED except for sinks, who are now accepting
+            and rendering data.
+
+We call the sequence NULL->PLAYING an upwards state change and PLAYING->NULL
+a downwards state change.
+
+
+State variables
+---------------
+
+An element has a special lock to manage the state changes. This lock is called
+the STATE_LOCK. 
+
+The STATE_LOCK protects 3 element variables:
+
+  - STATE
+  - PENDING_STATE 
+  - STATE_ERROR flag
+
+The STATE always reflects the current state of the element. The PENDING_STATE
+always reflects the required state of the element. The PENDING_STATE can be
+VOID_PENDING if the element is in the right state. The STATE_ERROR flag 
+indicates that an error occured while doing the last state change.
+
+
+Setting state on elements
+-------------------------
+
+The state of an element can be changed with _element_set_state(). When chaning
+the state of an element all intermediate states will also be set on the element
+until the final desired state is set.
+
+The _set_state() function can return 3 possible values:
+
+  GST_STATE_FAILURE: The state change failed for some reason. The plugin should
+                     have posted an error message on the bus with information.
+  
+  GST_STATE_SUCCESS: The state change is completed successfully.
+
+  GST_STATE_ASYNC:   The state change will complete later on. This can happen 
+                     When the element needs a long time to perform the state
+                    change or for sinks that need to receive the first buffer
+                    before they can complete the state change (preroll).
+
+In the case of an async state change, it is not possible to proceed to the next
+state until the current state change completed. After receiving an ASYNC return
+value, you can use _element_get_state() to poll the status of the element.
+
+When setting the state of an element, the PENDING_STATE is set to the required 
+state and the STATE_ERROR flag is cleared. Then the state change function of the
+element is called and the result of that function is used to update the STATE,
+PENDING_STATE and STATE_ERROR flags. If the function returned ASYNC, this result
+is immediatly returned to the caller.
+
+
+Getting state of elements
+-------------------------
+
+The _get_state() function takes 3 arguments, two pointers that will hold the
+current and pending state and one GTimeVal that holds a timeout value. The 
+function returns a GstElementStateReturn.
+
+ - If the element returned SUCCESS to the previous _set_state() function, this
+   function will return the last state set on the element and VOID_PENDING in
+   the pending state value.
+
+ - If the element returned FAILURE to the previous _set_state() call, this 
+   funciton will return FAILURE with the state set to the current state of
+   the element and the pending state set to the value used in the last call
+   of _set_state().
+
+ - If the element returned ASYNC to the previous _set_state() call, this function
+   will wait for the element to complete its state change up to the amount of time
+   specified in the GTimeVal. 
+
+   * If the element does not complete the state change in the specified amount of 
+     time, this function will return ASYNC with the state set to the current state
+     and the pending state set to the pending state.
+
+   * If the element completes the state change within the specified timeout, this 
+     function returns the updated state and VOID_PENDING as the pending state.
+
+   * If the element aborts the ASYNC state change due to an error within the 
+     specified timeout, this function returns FAILURE with the state set to last
+     successfull state and pending set to the last attempt. The element should 
+     also post an error message on the bus with more information about the problem.
+
+
+States in GstBin
+----------------
+
+A GstBin manages the state of its children. It does this by propagating the state
+changes performed on it to all of its children.  The _set_state() function on a 
+bin will call the _set_state() function on all of its children. 
+
+The children are iterated from the sink elements to the source elements. This makes
+sure that when changing the state of an element, the downstream elements are in
+the correct state to process the eventual buffers. In the case of a downwards
+state change, the sink elements will shut down first which makes the upstream
+elements shut down as well since the _push() function returns a GST_FLOW_WRONG_STATE
+error.
+
+If all the children return SUCCESS, the function returns SUCCESS as well. 
+
+If one of the children returns FAILURE, the function returns FAILURE as well. In
+this state it is possible that some elements successfuly changed state. The 
+application can check which elements have a changed state, which were in error
+and which were not affected by iterating the elements and calling _get_state()
+on the elements.
+
+If after calling the state function on all children, one of the children returned
+ASYNC, the function returns ASYNC as well. 
+
+The current state of the bin can be retrieved with _get_state(). This function will
+call the _get_state() function on all the elements. If one of the children returns
+FAILURE or ASYNC, the bin reports FAILURE or ASYNC respectively. The bin also
+updates its state variables after polling its children, this means that the state
+variables of the bin are only updated after calling _get_state() on the bin.
+
+The _get_state() function will be called on the children with the same timout value
+so the function can potentially block timeout*num_children.
+
+
+Implementing states in elements
+-------------------------------
+
+READY
+-----
+
+
+   
+
+
index 55974e4..68a8c9c 100644 (file)
@@ -62,8 +62,8 @@ else
 GST_URI_SRC = gsturi.c
 endif
 
-SUBDIRS = $(GST_PARSE_DIRS) $(GST_REGISTRY_DIRS) . elements schedulers $(GST_INDEX_DIRS) 
-DIST_SUBDIRS = elements parse registries schedulers indexers
+SUBDIRS = $(GST_PARSE_DIRS) $(GST_REGISTRY_DIRS) . base elements schedulers $(GST_INDEX_DIRS) 
+DIST_SUBDIRS = base elements parse registries schedulers indexers
 
 # make variables for all generated source and header files to make the
 # distinction clear
diff --git a/gst/base/Makefile.am b/gst/base/Makefile.am
new file mode 100644 (file)
index 0000000..6131249
--- /dev/null
@@ -0,0 +1,23 @@
+lib_LTLIBRARIES = libgstbase.la
+AS_LIBTOOL_LIB = libgstbase
+
+EXTRA_DIST = $(as_libtool_EXTRA_DIST)
+noinst_DATA = $(as_libtool_noinst_DATA_files)
+
+libgstbase_la_DEPENDENCIES = ../libgstreamer-@GST_MAJORMINOR@.la
+libgstbase_la_SOURCES =        \
+       gstbasesink.c   
+
+libgstbase_la_CFLAGS = $(GST_OBJ_CFLAGS)
+libgstbase_la_LIBADD = $(GST_OBJ_LIBS)
+libgstbase_la_LDFLAGS = $(as_libtool_LDFLAGS)
+
+noinst_HEADERS =       
+       gstbasesink.h   
+
+install-data-local: as-libtool-install-data-local
+
+uninstall-local: as-libtool-uninstall-local
+
+include $(top_srcdir)/common/as-libtool.mak
+
diff --git a/gst/base/README b/gst/base/README
new file mode 100644 (file)
index 0000000..efd6e57
--- /dev/null
@@ -0,0 +1,16 @@
+Base classes
+------------
+
+GstBaseSink
+  Base class for sink elements.
+
+  - one sinkpad
+  - handles state changes
+  - does flushing
+  - preroll with optional preview
+  - pull/push mode
+  - EOS handling
+
+  FIXME: not much point making it operate in pull mode as a generic
+  base class I guess... 
diff --git a/gst/base/gstbasesink.c b/gst/base/gstbasesink.c
new file mode 100644 (file)
index 0000000..a476071
--- /dev/null
@@ -0,0 +1,873 @@
+/* GStreamer
+ * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstbasesink.c: 
+ *
+ * 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 "gstbasesink.h"
+#include <gst/gstmarshal.h>
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gst_basesink_debug);
+#define GST_CAT_DEFAULT gst_basesink_debug
+
+/* #define DEBUGGING */
+#ifdef DEBUGGING
+#define DEBUG(str,args...) g_print (str,##args)
+#else
+#define DEBUG(str,args...)
+#endif
+
+/* BaseSink signals and properties */
+enum
+{
+  /* FILL ME */
+  SIGNAL_HANDOFF,
+  LAST_SIGNAL
+};
+
+#define DEFAULT_SIZE 1024
+#define DEFAULT_HAS_LOOP FALSE
+#define DEFAULT_HAS_CHAIN TRUE
+
+enum
+{
+  PROP_0,
+  PROP_HAS_LOOP,
+  PROP_HAS_CHAIN,
+  PROP_PREROLL_QUEUE_LEN
+};
+
+#define _do_init(bla) \
+    GST_DEBUG_CATEGORY_INIT (gst_basesink_debug, "basesink", 0, "basesink element");
+
+GST_BOILERPLATE_FULL (GstBaseSink, gst_basesink, GstElement, GST_TYPE_ELEMENT,
+    _do_init);
+
+static void gst_basesink_set_clock (GstElement * element, GstClock * clock);
+
+static void gst_basesink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_basesink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstStaticPadTemplate *gst_base_sink_get_template (GstBaseSink * sink);
+static GstCaps *gst_base_sink_get_caps (GstBaseSink * sink);
+static gboolean gst_base_sink_set_caps (GstBaseSink * sink, GstCaps * caps);
+static GstBuffer *gst_base_sink_buffer_alloc (GstBaseSink * sink,
+    guint64 offset, guint size, GstCaps * caps);
+static void gst_basesink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end);
+
+static GstElementStateReturn gst_basesink_change_state (GstElement * element);
+
+static GstFlowReturn gst_basesink_chain_unlocked (GstPad * pad,
+    GstBuffer * buffer);
+static void gst_basesink_loop (GstPad * pad);
+static GstFlowReturn gst_basesink_chain (GstPad * pad, GstBuffer * buffer);
+static gboolean gst_basesink_activate (GstPad * pad, GstActivateMode mode);
+static gboolean gst_basesink_event (GstPad * pad, GstEvent * event);
+static inline void gst_basesink_handle_buffer (GstBaseSink * basesink,
+    GstBuffer * buf);
+
+static GstStaticPadTemplate *
+gst_basesink_get_template (GstBaseSink * bsink)
+{
+  GstStaticPadTemplate *template = NULL;
+  GstBaseSinkClass *bclass;
+
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+
+  if (bclass->get_template)
+    template = bclass->get_template (bsink);
+
+  if (template == NULL) {
+    template = &sinktemplate;
+  }
+  return template;
+}
+
+static void
+gst_basesink_base_init (gpointer g_class)
+{
+  //GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
+
+  /*
+     gst_element_class_add_pad_template (gstelement_class,
+     gst_static_pad_template_get (&sinktemplate));
+   */
+}
+
+static void
+gst_basesink_class_init (GstBaseSinkClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_basesink_set_property);
+  gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_basesink_get_property);
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_LOOP,
+      g_param_spec_boolean ("has-loop", "has-loop",
+          "Enable loop-based operation", DEFAULT_HAS_LOOP,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_CHAIN,
+      g_param_spec_boolean ("has-chain", "has-chain",
+          "Enable chain-based operation", DEFAULT_HAS_CHAIN,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_PREROLL_QUEUE_LEN,
+      g_param_spec_uint ("preroll-queue-len", "preroll-queue-len",
+          "Number of buffers to queue during preroll", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+  gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_basesink_set_clock);
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_basesink_change_state);
+
+  klass->get_caps = GST_DEBUG_FUNCPTR (gst_base_sink_get_caps);
+  klass->set_caps = GST_DEBUG_FUNCPTR (gst_base_sink_set_caps);
+  klass->get_template = GST_DEBUG_FUNCPTR (gst_base_sink_get_template);
+  klass->buffer_alloc = GST_DEBUG_FUNCPTR (gst_base_sink_buffer_alloc);
+  klass->get_times = GST_DEBUG_FUNCPTR (gst_basesink_get_times);
+}
+
+static GstCaps *
+gst_basesink_pad_getcaps (GstPad * pad)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  GstCaps *caps = NULL;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->get_caps)
+    caps = bclass->get_caps (bsink);
+
+  if (caps == NULL) {
+    GstStaticPadTemplate *stemplate;
+    GstPadTemplate *template;
+
+    stemplate = gst_basesink_get_template (bsink);
+    template = gst_static_pad_template_get (stemplate);
+    caps = gst_caps_copy (gst_pad_template_get_caps (template));
+  }
+  return caps;
+}
+
+static gboolean
+gst_basesink_pad_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  gboolean res = FALSE;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->set_caps)
+    res = bclass->set_caps (bsink, caps);
+
+  return res;
+}
+
+static GstBuffer *
+gst_basesink_pad_buffer_alloc (GstPad * pad, guint64 offset, guint size,
+    GstCaps * caps)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  GstBuffer *buffer = NULL;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->buffer_alloc)
+    buffer = bclass->buffer_alloc (bsink, offset, size, caps);
+
+  return buffer;
+}
+
+static void
+gst_basesink_init (GstBaseSink * basesink)
+{
+  GstStaticPadTemplate *template;
+
+  template = gst_basesink_get_template (basesink);
+
+  basesink->sinkpad =
+      gst_pad_new_from_template (gst_static_pad_template_get (template),
+      "sink");
+  gst_pad_set_getcaps_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_getcaps));
+  gst_pad_set_setcaps_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_setcaps));
+  gst_pad_set_bufferalloc_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_buffer_alloc));
+  gst_element_add_pad (GST_ELEMENT (basesink), basesink->sinkpad);
+
+  basesink->pad_mode = GST_ACTIVATE_NONE;
+  GST_RPAD_TASK (basesink->sinkpad) = NULL;
+}
+
+static void
+gst_basesink_set_pad_functions (GstBaseSink * this, GstPad * pad)
+{
+  gst_pad_set_activate_function (pad,
+      GST_DEBUG_FUNCPTR (gst_basesink_activate));
+  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_event));
+
+  if (this->has_chain)
+    gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_chain));
+  else
+    gst_pad_set_chain_function (pad, NULL);
+
+  if (this->has_loop)
+    gst_pad_set_loop_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_loop));
+  else
+    gst_pad_set_loop_function (pad, NULL);
+}
+
+static void
+gst_basesink_set_all_pad_functions (GstBaseSink * this)
+{
+  GList *l;
+
+  for (l = GST_ELEMENT_PADS (this); l; l = l->next)
+    gst_basesink_set_pad_functions (this, (GstPad *) l->data);
+}
+
+static void
+gst_basesink_set_clock (GstElement * element, GstClock * clock)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (element);
+
+  sink->clock = clock;
+}
+
+static void
+gst_basesink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (object);
+
+  GST_LOCK (sink);
+  switch (prop_id) {
+    case PROP_HAS_LOOP:
+      sink->has_loop = g_value_get_boolean (value);
+      gst_basesink_set_all_pad_functions (sink);
+      break;
+    case PROP_HAS_CHAIN:
+      sink->has_chain = g_value_get_boolean (value);
+      gst_basesink_set_all_pad_functions (sink);
+      break;
+    case PROP_PREROLL_QUEUE_LEN:
+      /* preroll lock necessary to serialize with finish_preroll */
+      GST_PREROLL_LOCK (sink->sinkpad);
+      sink->preroll_queue_max_len = g_value_get_uint (value);
+      GST_PREROLL_UNLOCK (sink->sinkpad);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_UNLOCK (sink);
+}
+
+static void
+gst_basesink_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (object);
+
+  GST_LOCK (sink);
+  switch (prop_id) {
+    case PROP_HAS_LOOP:
+      g_value_set_boolean (value, sink->has_loop);
+      break;
+    case PROP_HAS_CHAIN:
+      g_value_set_boolean (value, sink->has_chain);
+      break;
+    case PROP_PREROLL_QUEUE_LEN:
+      g_value_set_uint (value, sink->preroll_queue_max_len);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_UNLOCK (sink);
+}
+
+static GstStaticPadTemplate *
+gst_base_sink_get_template (GstBaseSink * sink)
+{
+  return &sinktemplate;
+}
+
+static GstCaps *
+gst_base_sink_get_caps (GstBaseSink * sink)
+{
+  return NULL;
+}
+
+static gboolean
+gst_base_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
+{
+  return TRUE;
+}
+
+static GstBuffer *
+gst_base_sink_buffer_alloc (GstBaseSink * sink, guint64 offset, guint size,
+    GstCaps * caps)
+{
+  return NULL;
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_push (GstBaseSink * basesink, GstPad * pad,
+    GstBuffer * buffer)
+{
+  if (basesink->preroll_queue->length == 0) {
+    GstBaseSinkClass *bclass = GST_BASESINK_GET_CLASS (basesink);
+
+    if (bclass->preroll)
+      bclass->preroll (basesink, buffer);
+  }
+
+  if (basesink->preroll_queue->length < basesink->preroll_queue_max_len) {
+    DEBUG ("push %p %p\n", basesink, buffer);
+    g_queue_push_tail (basesink->preroll_queue, buffer);
+  } else {
+    /* block until the state changes, or we get a flush, or something */
+    DEBUG ("block %p %p\n", basesink, buffer);
+    GST_DEBUG ("element %s waiting to finish preroll",
+        GST_ELEMENT_NAME (basesink));
+    basesink->need_preroll = FALSE;
+    basesink->have_preroll = TRUE;
+    GST_PREROLL_WAIT (pad);
+    GST_DEBUG ("done preroll");
+    basesink->have_preroll = FALSE;
+  }
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_empty (GstBaseSink * basesink, GstPad * pad)
+{
+  GstBuffer *buf;
+  GQueue *q = basesink->preroll_queue;
+
+  if (q) {
+    DEBUG ("empty queue\n");
+    while ((buf = g_queue_pop_head (q))) {
+      DEBUG ("pop %p\n", buf);
+      gst_basesink_handle_buffer (basesink, buf);
+    }
+    DEBUG ("queue len %p %d\n", basesink, q->length);
+  }
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_flush (GstBaseSink * basesink)
+{
+  GstBuffer *buf;
+  GQueue *q = basesink->preroll_queue;
+
+  DEBUG ("flush %p\n", basesink);
+  if (q) {
+    while ((buf = g_queue_pop_head (q))) {
+      DEBUG ("pop %p\n", buf);
+      gst_buffer_unref (buf);
+    }
+  }
+}
+
+typedef enum
+{
+  PREROLL_QUEUEING,
+  PREROLL_PLAYING,
+  PREROLL_FLUSHING,
+  PREROLL_ERROR
+} PrerollReturn;
+
+/* with STREAM_LOCK */
+PrerollReturn
+gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad,
+    GstBuffer * buffer)
+{
+  gboolean usable;
+
+  DEBUG ("finish preroll %p <\n", basesink);
+  /* lock order is important */
+  GST_STATE_LOCK (basesink);
+  GST_PREROLL_LOCK (pad);
+  DEBUG ("finish preroll %p >\n", basesink);
+  if (!basesink->need_preroll)
+    goto no_preroll;
+
+  gst_element_commit_state (GST_ELEMENT (basesink));
+  GST_STATE_UNLOCK (basesink);
+
+  gst_basesink_preroll_queue_push (basesink, pad, buffer);
+
+  GST_LOCK (pad);
+  usable = !GST_RPAD_IS_FLUSHING (pad) && GST_RPAD_IS_ACTIVE (pad);
+  GST_UNLOCK (pad);
+  if (!usable)
+    goto unusable;
+
+  if (basesink->need_preroll)
+    goto still_queueing;
+
+  GST_DEBUG ("done preroll");
+
+  gst_basesink_preroll_queue_empty (basesink, pad);
+
+  GST_PREROLL_UNLOCK (pad);
+
+  return PREROLL_PLAYING;
+
+no_preroll:
+  {
+    /* maybe it was another sink that blocked in preroll, need to check for
+       buffers to drain */
+    if (basesink->preroll_queue->length)
+      gst_basesink_preroll_queue_empty (basesink, pad);
+    GST_PREROLL_UNLOCK (pad);
+    GST_STATE_UNLOCK (basesink);
+    return PREROLL_PLAYING;
+  }
+unusable:
+  {
+    GST_DEBUG ("pad is flushing");
+    GST_PREROLL_UNLOCK (pad);
+    return PREROLL_FLUSHING;
+  }
+still_queueing:
+  {
+    GST_PREROLL_UNLOCK (pad);
+    return PREROLL_QUEUEING;
+  }
+}
+
+static gboolean
+gst_basesink_event (GstPad * pad, GstEvent * event)
+{
+  GstBaseSink *basesink;
+  gboolean result = TRUE;
+  GstBaseSinkClass *bclass;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  bclass = GST_BASESINK_GET_CLASS (basesink);
+
+  DEBUG ("event %p\n", basesink);
+
+  if (bclass->event)
+    bclass->event (basesink, event);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+    {
+      gboolean need_eos;
+
+      GST_STREAM_LOCK (pad);
+
+      GST_PREROLL_LOCK (pad);
+      gst_basesink_preroll_queue_empty (basesink, pad);
+      GST_PREROLL_UNLOCK (pad);
+
+      GST_LOCK (basesink);
+      need_eos = basesink->eos = TRUE;
+      if (basesink->clock) {
+        /* wait for last buffer to finish if we have a valid end time */
+        if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
+          basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock,
+              basesink->end_time + GST_ELEMENT (basesink)->base_time);
+          GST_UNLOCK (basesink);
+
+          gst_clock_id_wait (basesink->clock_id, NULL);
+
+          GST_LOCK (basesink);
+          if (basesink->clock_id) {
+            gst_clock_id_unref (basesink->clock_id);
+            basesink->clock_id = NULL;
+          }
+          basesink->end_time = GST_CLOCK_TIME_NONE;
+          need_eos = basesink->eos;
+        }
+        GST_UNLOCK (basesink);
+
+        /* if we are still EOS, we can post the EOS message */
+        if (need_eos) {
+          /* ok, now we can post the message */
+          gst_element_post_message (GST_ELEMENT (basesink),
+              gst_message_new_eos (GST_OBJECT (basesink)));
+        }
+      }
+      GST_STREAM_UNLOCK (pad);
+      break;
+    }
+    case GST_EVENT_DISCONTINUOUS:
+      GST_STREAM_LOCK (pad);
+      if (basesink->clock) {
+        //gint64 value = GST_EVENT_DISCONT_OFFSET (event, 0).value;
+      }
+      GST_STREAM_UNLOCK (pad);
+      break;
+    case GST_EVENT_FLUSH:
+      /* make sure we are not blocked on the clock also clear any pending
+       * eos state. */
+      if (!GST_EVENT_FLUSH_DONE (event)) {
+        GST_LOCK (basesink);
+        basesink->eos = FALSE;
+        if (basesink->clock_id) {
+          gst_clock_id_unschedule (basesink->clock_id);
+        }
+        GST_UNLOCK (basesink);
+
+        /* unlock from a possible state change/preroll */
+        GST_PREROLL_LOCK (pad);
+        basesink->need_preroll = TRUE;
+        gst_basesink_preroll_queue_flush (basesink);
+        GST_PREROLL_SIGNAL (pad);
+        GST_PREROLL_UNLOCK (pad);
+      }
+      /* now we are completely unblocked and the _chain method
+       * will return */
+      break;
+    default:
+      result = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  return result;
+}
+
+static void
+gst_basesink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end)
+{
+  GstClockTime timestamp, duration;
+
+  timestamp = GST_BUFFER_TIMESTAMP (buffer);
+  if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
+    duration = GST_BUFFER_DURATION (buffer);
+    if (GST_CLOCK_TIME_IS_VALID (duration)) {
+      *end = timestamp + duration;
+    }
+    *start = timestamp;
+  }
+}
+
+static void
+gst_basesink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
+{
+  if (basesink->clock) {
+    GstClockReturn ret;
+    GstClockTime start, end;
+    GstBaseSinkClass *bclass;
+
+    bclass = GST_BASESINK_GET_CLASS (basesink);
+    start = end = -1;
+    if (bclass->get_times)
+      bclass->get_times (basesink, buffer, &start, &end);
+
+    if (GST_CLOCK_TIME_IS_VALID (start)) {
+      /* save clock id so that we can unlock it if needed */
+      GST_LOCK (basesink);
+      basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock,
+          start + GST_ELEMENT (basesink)->base_time);
+      basesink->end_time = end;
+      GST_UNLOCK (basesink);
+
+      ret = gst_clock_id_wait (basesink->clock_id, NULL);
+
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unref (basesink->clock_id);
+        basesink->clock_id = NULL;
+      }
+      basesink->end_time = GST_CLOCK_TIME_NONE;
+      GST_UNLOCK (basesink);
+
+      GST_LOG_OBJECT (basesink, "clock entry done: %d", ret);
+    }
+  }
+}
+
+static inline void
+gst_basesink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf)
+{
+  GstBaseSinkClass *bclass;
+
+  gst_basesink_do_sync (basesink, buf);
+
+  bclass = GST_BASESINK_GET_CLASS (basesink);
+  if (bclass->render)
+    bclass->render (basesink, buf);
+
+  DEBUG ("unref %p %p\n", basesink, buf);
+  gst_buffer_unref (buf);
+}
+
+static GstFlowReturn
+gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf)
+{
+  GstBaseSink *basesink;
+  PrerollReturn result;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  DEBUG ("chain_unlocked %p\n", basesink);
+
+  result = gst_basesink_finish_preroll (basesink, pad, buf);
+
+  DEBUG ("chain_unlocked %p after\n", basesink);
+
+  switch (result) {
+    case PREROLL_QUEUEING:
+      return GST_FLOW_OK;
+    case PREROLL_PLAYING:
+      gst_basesink_handle_buffer (basesink, buf);
+      return GST_FLOW_OK;
+    case PREROLL_FLUSHING:
+      return GST_FLOW_UNEXPECTED;
+    default:
+      g_assert_not_reached ();
+      return GST_FLOW_UNEXPECTED;
+  }
+}
+
+static GstFlowReturn
+gst_basesink_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstFlowReturn result;
+
+  g_assert (GST_BASESINK (GST_OBJECT_PARENT (pad))->pad_mode ==
+      GST_ACTIVATE_PUSH);
+
+  GST_STREAM_LOCK (pad);
+
+  result = gst_basesink_chain_unlocked (pad, buf);
+
+  GST_STREAM_UNLOCK (pad);
+
+  return result;
+}
+
+static void
+gst_basesink_loop (GstPad * pad)
+{
+  GstBaseSink *basesink;
+  GstBuffer *buf = NULL;
+  GstFlowReturn result;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  g_assert (basesink->pad_mode == GST_ACTIVATE_PULL);
+
+  GST_STREAM_LOCK (pad);
+
+  result = gst_pad_pull_range (pad, basesink->offset, DEFAULT_SIZE, &buf);
+  if (result != GST_FLOW_OK)
+    goto paused;
+
+  result = gst_basesink_chain_unlocked (pad, buf);
+  if (result != GST_FLOW_OK)
+    goto paused;
+
+  /* default */
+  GST_STREAM_UNLOCK (pad);
+  return;
+
+paused:
+  gst_task_pause (GST_RPAD_TASK (pad));
+  GST_STREAM_UNLOCK (pad);
+  return;
+}
+
+static gboolean
+gst_basesink_activate (GstPad * pad, GstActivateMode mode)
+{
+  gboolean result = FALSE;
+  GstBaseSink *basesink;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  switch (mode) {
+    case GST_ACTIVATE_PUSH:
+      g_return_val_if_fail (basesink->has_chain, FALSE);
+      result = TRUE;
+      break;
+    case GST_ACTIVATE_PULL:
+      /* if we have a scheduler we can start the task */
+      g_return_val_if_fail (basesink->has_loop, FALSE);
+      if (GST_ELEMENT_SCHEDULER (basesink)) {
+        GST_STREAM_LOCK (pad);
+        GST_RPAD_TASK (pad) =
+            gst_scheduler_create_task (GST_ELEMENT_SCHEDULER (basesink),
+            (GstTaskFunction) gst_basesink_loop, pad);
+
+        gst_task_start (GST_RPAD_TASK (pad));
+        GST_STREAM_UNLOCK (pad);
+        result = TRUE;
+      }
+      break;
+    case GST_ACTIVATE_NONE:
+      /* step 1, unblock clock sync (if any) or any other blocking thing */
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unschedule (basesink->clock_id);
+      }
+      GST_UNLOCK (basesink);
+
+      /* unlock preroll */
+      GST_PREROLL_LOCK (pad);
+      GST_PREROLL_SIGNAL (pad);
+      GST_PREROLL_UNLOCK (pad);
+
+      /* step 2, make sure streaming finishes */
+      GST_STREAM_LOCK (pad);
+      /* step 3, stop the task */
+      if (GST_RPAD_TASK (pad)) {
+        gst_task_stop (GST_RPAD_TASK (pad));
+        gst_object_unref (GST_OBJECT (GST_RPAD_TASK (pad)));
+        GST_RPAD_TASK (pad) = NULL;
+      }
+      GST_STREAM_UNLOCK (pad);
+
+      result = TRUE;
+      break;
+  }
+  basesink->pad_mode = mode;
+
+  return result;
+}
+
+static GstElementStateReturn
+gst_basesink_change_state (GstElement * element)
+{
+  GstElementStateReturn ret = GST_STATE_SUCCESS;
+  GstBaseSink *basesink = GST_BASESINK (element);
+  GstElementState transition = GST_STATE_TRANSITION (element);
+
+  DEBUG ("state change > %p %x\n", basesink, transition);
+
+  switch (transition) {
+    case GST_STATE_NULL_TO_READY:
+      break;
+    case GST_STATE_READY_TO_PAUSED:
+      /* need to complete preroll before this state change completes, there
+       * is no data flow in READY so we cqn safely assume we need to preroll. */
+      basesink->offset = 0;
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      basesink->preroll_queue = g_queue_new ();
+      basesink->need_preroll = TRUE;
+      basesink->have_preroll = FALSE;
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      ret = GST_STATE_ASYNC;
+      break;
+    case GST_STATE_PAUSED_TO_PLAYING:
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      if (basesink->have_preroll) {
+        /* now let it play */
+        GST_PREROLL_SIGNAL (basesink->sinkpad);
+      } else {
+        /* FIXME. We do not have a preroll and we don't need it anymore 
+         * now, this is a case we want to avoid. One way would be to make
+         * a 'lost state' function that makes get_state return PAUSED with
+         * ASYNC to indicate that we are prerolling again. */
+        basesink->need_preroll = FALSE;
+      }
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      break;
+    default:
+      break;
+  }
+
+  GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+  switch (transition) {
+    case GST_STATE_PLAYING_TO_PAUSED:
+    {
+      gboolean eos;
+
+      /* unlock clock wait if any */
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unschedule (basesink->clock_id);
+      }
+      eos = basesink->eos;
+      GST_UNLOCK (basesink);
+
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      /* if we don't have a preroll buffer and we have not received EOS,
+       * we need to wait for a preroll */
+      if (!basesink->have_preroll && !eos) {
+        basesink->need_preroll = TRUE;
+        ret = GST_STATE_ASYNC;
+      }
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      break;
+    }
+    case GST_STATE_PAUSED_TO_READY:
+      /* flush out the data thread if it's locked in finish_preroll */
+      GST_PREROLL_LOCK (basesink->sinkpad);
+
+      gst_basesink_preroll_queue_flush (basesink);
+      g_queue_free (basesink->preroll_queue);
+      basesink->preroll_queue = NULL;
+
+      if (basesink->have_preroll)
+        GST_PREROLL_SIGNAL (basesink->sinkpad);
+
+      basesink->need_preroll = FALSE;
+      basesink->have_preroll = FALSE;
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+
+      /* make sure the element is finished processing */
+      GST_STREAM_LOCK (basesink->sinkpad);
+      GST_STREAM_UNLOCK (basesink->sinkpad);
+      break;
+    case GST_STATE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  DEBUG ("state change < %p %x\n", basesink, transition);
+  return ret;
+}
diff --git a/gst/base/gstbasesink.h b/gst/base/gstbasesink.h
new file mode 100644 (file)
index 0000000..3c64f78
--- /dev/null
@@ -0,0 +1,89 @@
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ *                    2000 Wim Taymans <wtay@chello.be>
+ *
+ * gstbasesink.h: 
+ *
+ * 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_BASESINK_H__
+#define __GST_BASESINK_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+
+#define GST_TYPE_BASESINK              (gst_basesink_get_type())
+#define GST_BASESINK(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASESINK,GstBaseSink))
+#define GST_BASESINK_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASESINK,GstBaseSinkClass))
+#define GST_BASESINK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BASESINK, GstBaseSinkClass))
+#define GST_IS_BASESINK(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASESINK))
+#define GST_IS_BASESINK_CLASS(obj)     (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASESINK))
+
+#define GST_BASESINK_CLOCK(obj)                (GST_BASESINK (obj)->clock)
+#define GST_BASESINK_PAD(obj)          (GST_BASESINK (obj)->sinkpad)
+
+typedef struct _GstBaseSink GstBaseSink;
+typedef struct _GstBaseSinkClass GstBaseSinkClass;
+
+struct _GstBaseSink {
+  GstElement    element;
+
+  GstPad       *sinkpad;
+  GstActivateMode      pad_mode;
+
+  GQueue       *preroll_queue; /* with PREROLL_LOCK */
+  gint          preroll_queue_max_len; /* with PREROLL_LOCK */
+
+  guint64       offset;
+  gboolean      has_loop;
+  gboolean      has_chain;
+
+  GstClock     *clock;
+  GstClockID     clock_id;
+  GstClockTime   end_time;
+  
+  gboolean       eos;
+  gboolean       need_preroll;
+  gboolean       have_preroll;
+};
+
+struct _GstBaseSinkClass {
+  GstElementClass parent_class;
+
+  GstStaticPadTemplate* (*get_template) (GstBaseSink *sink);
+
+  GstCaps*      (*get_caps)     (GstBaseSink *sink);
+  gboolean      (*set_caps)     (GstBaseSink *sink, GstCaps *caps);
+
+  GstBuffer*    (*buffer_alloc) (GstBaseSink *sink, guint64 offset, guint size,
+                                GstCaps *caps);
+
+  void         (*get_times)    (GstBaseSink *sink, GstBuffer *buffer, 
+                                GstClockTime *start, GstClockTime *end);
+
+  void          (*event)        (GstBaseSink *sink, GstEvent *event);
+  GstFlowReturn (*preroll)      (GstBaseSink *sink, GstBuffer *buffer);
+  GstFlowReturn (*render)       (GstBaseSink *sink, GstBuffer *buffer);
+};
+
+GType gst_basesink_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_BASESINK_H__ */
index db5cb1f..4d6d5a8 100644 (file)
@@ -45,7 +45,7 @@ libgstelements_la_SOURCES =   \
 
 libgstelements_la_CFLAGS = $(GST_OBJ_CFLAGS)
 libgstelements_la_LIBADD = $(GST_OBJ_LIBS)
-libgstelements_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(as_libtool_LDFLAGS)
+libgstelements_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(as_libtool_LDFLAGS) $(top_builddir)/gst/base/libgstbase.la
 
 noinst_HEADERS =               \
        gstaggregator.h         \
index 5491678..b8d483c 100644 (file)
@@ -1,6 +1,6 @@
 /* GStreamer
  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
- *                    2000 Wim Taymans <wtay@chello.be>
+ *                    2005 Wim Taymans <wim@fluendo.com>
  *
  * gstfakesink.c: 
  *
@@ -39,7 +39,9 @@ GST_DEBUG_CATEGORY_STATIC (gst_fakesink_debug);
 GstElementDetails gst_fakesink_details = GST_ELEMENT_DETAILS ("Fake Sink",
     "Sink",
     "Black hole for data",
-    "Erik Walthinsen <omega@cse.ogi.edu>");
+    "Erik Walthinsen <omega@cse.ogi.edu>, "
+    "Wim Taymans <wim@fluendo.com>, "
+    "Mr. 'frag-me-more' Vanderwingo <wingo@fluendo.com>");
 
 
 /* FakeSink signals and args */
@@ -50,23 +52,24 @@ enum
   LAST_SIGNAL
 };
 
+#define DEFAULT_STATE_ERROR FAKESINK_STATE_ERROR_NONE
+#define DEFAULT_SILENT FALSE
+#define DEFAULT_DUMP FALSE
+#define DEFAULT_SYNC FALSE
+#define DEFAULT_SIGNAL_HANDOFFS FALSE
+#define DEFAULT_LAST_MESSAGE NULL
+
 enum
 {
   ARG_0,
   ARG_STATE_ERROR,
-  ARG_NUM_SINKS,
   ARG_SILENT,
   ARG_DUMP,
   ARG_SYNC,
   ARG_SIGNAL_HANDOFFS,
-  ARG_LAST_MESSAGE
+  ARG_LAST_MESSAGE,
 };
 
-GstStaticPadTemplate fakesink_sink_template = GST_STATIC_PAD_TEMPLATE ("sink%d",
-    GST_PAD_SINK,
-    GST_PAD_REQUEST,
-    GST_STATIC_CAPS_ANY);
-
 #define GST_TYPE_FAKESINK_STATE_ERROR (gst_fakesink_state_error_get_type())
 static GType
 gst_fakesink_state_error_get_type (void)
@@ -99,13 +102,9 @@ gst_fakesink_state_error_get_type (void)
 #define _do_init(bla) \
     GST_DEBUG_CATEGORY_INIT (gst_fakesink_debug, "fakesink", 0, "fakesink element");
 
-GST_BOILERPLATE_FULL (GstFakeSink, gst_fakesink, GstElement, GST_TYPE_ELEMENT,
+GST_BOILERPLATE_FULL (GstFakeSink, gst_fakesink, GstBaseSink, GST_TYPE_BASESINK,
     _do_init);
 
-static void gst_fakesink_set_clock (GstElement * element, GstClock * clock);
-static GstPad *gst_fakesink_request_new_pad (GstElement * element,
-    GstPadTemplate * templ, const gchar * unused);
-
 static void gst_fakesink_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 static void gst_fakesink_get_property (GObject * object, guint prop_id,
@@ -113,8 +112,13 @@ static void gst_fakesink_get_property (GObject * object, guint prop_id,
 
 static GstElementStateReturn gst_fakesink_change_state (GstElement * element);
 
-static GstFlowReturn gst_fakesink_chain (GstPad * pad, GstBuffer * buffer);
-static gboolean gst_fakesink_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_fakesink_preroll (GstBaseSink * bsink,
+    GstBuffer * buffer);
+static GstFlowReturn gst_fakesink_render (GstBaseSink * bsink,
+    GstBuffer * buffer);
+static void gst_fakesink_event (GstBaseSink * bsink, GstEvent * event);
+static void gst_fakesink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end);
 
 static guint gst_fakesink_signals[LAST_SIGNAL] = { 0 };
 
@@ -126,8 +130,6 @@ gst_fakesink_base_init (gpointer g_class)
   gst_element_class_add_pad_template (gstelement_class,
       gst_static_pad_template_get (&sinktemplate));
   gst_element_class_set_details (gstelement_class, &gst_fakesink_details);
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&fakesink_sink_template));
 }
 
 static void
@@ -135,36 +137,37 @@ gst_fakesink_class_init (GstFakeSinkClass * klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
+  GstBaseSinkClass *gstbasesink_class;
 
   gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
+  gstbasesink_class = (GstBaseSinkClass *) klass;
 
   gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_fakesink_set_property);
   gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_fakesink_get_property);
 
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NUM_SINKS,
-      g_param_spec_int ("num_sinks", "Number of sinks",
-          "The number of sinkpads", 1, G_MAXINT, 1, G_PARAM_READABLE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATE_ERROR,
       g_param_spec_enum ("state_error", "State Error",
           "Generate a state change error", GST_TYPE_FAKESINK_STATE_ERROR,
-          FAKESINK_STATE_ERROR_NONE, G_PARAM_READWRITE));
+          DEFAULT_STATE_ERROR, G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LAST_MESSAGE,
       g_param_spec_string ("last_message", "Last Message",
-          "The message describing current status", NULL, G_PARAM_READABLE));
+          "The message describing current status", DEFAULT_LAST_MESSAGE,
+          G_PARAM_READABLE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SYNC,
-      g_param_spec_boolean ("sync", "Sync", "Sync on the clock", FALSE,
+      g_param_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
           G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SIGNAL_HANDOFFS,
       g_param_spec_boolean ("signal-handoffs", "Signal handoffs",
-          "Send a signal before unreffing the buffer", FALSE,
+          "Send a signal before unreffing the buffer", DEFAULT_SIGNAL_HANDOFFS,
           G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SILENT,
       g_param_spec_boolean ("silent", "Silent",
-          "Don't produce last_message events", FALSE, G_PARAM_READWRITE));
+          "Don't produce last_message events", DEFAULT_SILENT,
+          G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DUMP,
       g_param_spec_boolean ("dump", "Dump", "Dump received bytes to stdout",
-          FALSE, G_PARAM_READWRITE));
+          DEFAULT_DUMP, G_PARAM_READWRITE));
 
   gst_fakesink_signals[SIGNAL_HANDOFF] =
       g_signal_new ("handoff", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
@@ -172,69 +175,24 @@ gst_fakesink_class_init (GstFakeSinkClass * klass)
       gst_marshal_VOID__BOXED_OBJECT, G_TYPE_NONE, 2,
       GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE, GST_TYPE_PAD);
 
-  gstelement_class->request_new_pad =
-      GST_DEBUG_FUNCPTR (gst_fakesink_request_new_pad);
-  gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_fakesink_set_clock);
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_fakesink_change_state);
+
+  gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_fakesink_event);
+  gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_fakesink_preroll);
+  gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_fakesink_render);
+  gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_fakesink_get_times);
 }
 
 static void
 gst_fakesink_init (GstFakeSink * fakesink)
 {
-  GstPad *pad;
-
-  pad =
-      gst_pad_new_from_template (gst_static_pad_template_get (&sinktemplate),
-      "sink");
-  gst_element_add_pad (GST_ELEMENT (fakesink), pad);
-  gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_fakesink_chain));
-  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_fakesink_event));
-
-  fakesink->silent = FALSE;
-  fakesink->dump = FALSE;
-  fakesink->sync = FALSE;
-  fakesink->last_message = NULL;
-  fakesink->state_error = FAKESINK_STATE_ERROR_NONE;
-  fakesink->signal_handoffs = FALSE;
-}
-
-static void
-gst_fakesink_set_clock (GstElement * element, GstClock * clock)
-{
-  GstFakeSink *sink;
-
-  sink = GST_FAKESINK (element);
-
-  sink->clock = clock;
-}
-
-static GstPad *
-gst_fakesink_request_new_pad (GstElement * element, GstPadTemplate * templ,
-    const gchar * unused)
-{
-  gchar *name;
-  GstPad *sinkpad;
-  GstFakeSink *fakesink;
-
-  g_return_val_if_fail (GST_IS_FAKESINK (element), NULL);
-
-  if (templ->direction != GST_PAD_SINK) {
-    g_warning ("gstfakesink: request new pad that is not a SINK pad\n");
-    return NULL;
-  }
-
-  fakesink = GST_FAKESINK (element);
-
-  name = g_strdup_printf ("sink%d", GST_ELEMENT (fakesink)->numsinkpads);
-
-  sinkpad = gst_pad_new_from_template (templ, name);
-  g_free (name);
-  gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_fakesink_chain));
-
-  gst_element_add_pad (GST_ELEMENT (fakesink), sinkpad);
-
-  return sinkpad;
+  fakesink->silent = DEFAULT_SILENT;
+  fakesink->dump = DEFAULT_DUMP;
+  fakesink->sync = DEFAULT_SYNC;
+  fakesink->last_message = DEFAULT_LAST_MESSAGE;
+  fakesink->state_error = DEFAULT_STATE_ERROR;
+  fakesink->signal_handoffs = DEFAULT_SIGNAL_HANDOFFS;
 }
 
 static void
@@ -243,7 +201,6 @@ gst_fakesink_set_property (GObject * object, guint prop_id,
 {
   GstFakeSink *sink;
 
-  /* it's not null if we got it, but it might not be ours */
   sink = GST_FAKESINK (object);
 
   switch (prop_id) {
@@ -274,15 +231,9 @@ gst_fakesink_get_property (GObject * object, guint prop_id, GValue * value,
 {
   GstFakeSink *sink;
 
-  /* it's not null if we got it, but it might not be ours */
-  g_return_if_fail (GST_IS_FAKESINK (object));
-
   sink = GST_FAKESINK (object);
 
   switch (prop_id) {
-    case ARG_NUM_SINKS:
-      g_value_set_int (value, GST_ELEMENT (sink)->numsinkpads);
-      break;
     case ARG_STATE_ERROR:
       g_value_set_enum (value, sink->state_error);
       break;
@@ -307,79 +258,86 @@ gst_fakesink_get_property (GObject * object, guint prop_id, GValue * value,
   }
 }
 
-static gboolean
-gst_fakesink_event (GstPad * pad, GstEvent * event)
+static void
+gst_fakesink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end)
 {
-  GstFakeSink *fakesink;
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-  fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad));
+  if (sink->sync) {
+    GST_BASESINK_CLASS (parent_class)->get_times (bsink, buffer, start, end);
+  }
+}
 
-  if (!fakesink->silent) {
-    g_free (fakesink->last_message);
+static void
+gst_fakesink_event (GstBaseSink * bsink, GstEvent * event)
+{
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-    fakesink->last_message =
-        g_strdup_printf ("chain   ******* (%s:%s)E (type: %d) %p",
-        GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE (event), event);
+  if (!sink->silent) {
+    g_free (sink->last_message);
 
-    g_object_notify (G_OBJECT (fakesink), "last_message");
-  }
+    sink->last_message =
+        g_strdup_printf ("chain   ******* E (type: %d) %p",
+        GST_EVENT_TYPE (event), event);
 
-  switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_DISCONTINUOUS:
-    default:
-      gst_pad_event_default (pad, event);
-      break;
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
-
-  return TRUE;
 }
 
 static GstFlowReturn
-gst_fakesink_chain (GstPad * pad, GstBuffer * buffer)
+gst_fakesink_preroll (GstBaseSink * bsink, GstBuffer * buffer)
 {
-  GstBuffer *buf = GST_BUFFER (buffer);
-  GstFakeSink *fakesink;
+  GstFakeSink *sink = GST_FAKESINK (bsink);
+
+  if (!sink->silent) {
+    g_free (sink->last_message);
 
-  fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad));
+    sink->last_message = g_strdup_printf ("preroll   ******* ");
 
-  if (fakesink->sync && fakesink->clock) {
-    //gst_element_wait (GST_ELEMENT (fakesink), GST_BUFFER_TIMESTAMP (buf));
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
+  return GST_FLOW_OK;
+}
 
-  if (!fakesink->silent) {
-    g_free (fakesink->last_message);
+static GstFlowReturn
+gst_fakesink_render (GstBaseSink * bsink, GstBuffer * buf)
+{
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-    fakesink->last_message =
-        g_strdup_printf ("chain   ******* (%s:%s)< (%d bytes, timestamp: %"
+  if (!sink->silent) {
+    g_free (sink->last_message);
+
+    sink->last_message =
+        g_strdup_printf ("chain   ******* < (%d bytes, timestamp: %"
         GST_TIME_FORMAT ", duration: %" GST_TIME_FORMAT ", offset: %"
         G_GINT64_FORMAT ", offset_end: %" G_GINT64_FORMAT ", flags: %d) %p",
-        GST_DEBUG_PAD_NAME (pad), GST_BUFFER_SIZE (buf),
+        GST_BUFFER_SIZE (buf),
         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
         GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_BUFFER_OFFSET (buf),
         GST_BUFFER_OFFSET_END (buf), GST_BUFFER_FLAGS (buf), buf);
 
-    g_object_notify (G_OBJECT (fakesink), "last_message");
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
+  if (sink->signal_handoffs)
+    g_signal_emit (G_OBJECT (sink), gst_fakesink_signals[SIGNAL_HANDOFF], 0,
+        buf);
 
-  if (fakesink->signal_handoffs)
-    g_signal_emit (G_OBJECT (fakesink), gst_fakesink_signals[SIGNAL_HANDOFF], 0,
-        buf, pad);
-
-  if (fakesink->dump) {
+  if (sink->dump) {
     gst_util_dump_mem (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
   }
 
-  gst_buffer_unref (buf);
-
   return GST_FLOW_OK;
 }
 
 static GstElementStateReturn
 gst_fakesink_change_state (GstElement * element)
 {
+  GstElementStateReturn ret = GST_STATE_SUCCESS;
   GstFakeSink *fakesink = GST_FAKESINK (element);
+  GstElementState transition = GST_STATE_TRANSITION (element);
 
-  switch (GST_STATE_TRANSITION (element)) {
+  switch (transition) {
     case GST_STATE_NULL_TO_READY:
       if (fakesink->state_error == FAKESINK_STATE_ERROR_NULL_READY)
         goto error;
@@ -406,12 +364,13 @@ gst_fakesink_change_state (GstElement * element)
       g_free (fakesink->last_message);
       fakesink->last_message = NULL;
       break;
+    default:
+      break;
   }
 
-  if (GST_ELEMENT_CLASS (parent_class)->change_state)
-    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
 
-  return GST_STATE_SUCCESS;
+  return ret;
 
 error:
   GST_ELEMENT_ERROR (element, CORE, STATE_CHANGE, (NULL), (NULL));
index f41ad61..ecb7e9e 100644 (file)
@@ -25,6 +25,7 @@
 #define __GST_FAKESINK_H__
 
 #include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
 
 G_BEGIN_DECLS
 
@@ -54,20 +55,18 @@ typedef struct _GstFakeSink GstFakeSink;
 typedef struct _GstFakeSinkClass GstFakeSinkClass;
 
 struct _GstFakeSink {
-  GstElement    element;
+  GstBaseSink   element;
 
   gboolean      silent;
   gboolean      dump;
   gboolean      sync;
   gboolean      signal_handoffs;
-  GstClock     *clock;
   GstFakeSinkStateError state_error;
-
   gchar        *last_message;
 };
 
 struct _GstFakeSinkClass {
-  GstElementClass parent_class;
+  GstBaseSinkClass parent_class;
 
   /* signals */
   void (*handoff) (GstElement *element, GstBuffer *buf, GstPad *pad);
index 5ee6967..f15e10f 100644 (file)
@@ -61,18 +61,17 @@ static GstElementStateReturn gst_bin_get_state (GstElement * element,
 static gboolean gst_bin_add_func (GstBin * bin, GstElement * element);
 static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element);
 
-static void gst_bin_set_manager (GstElement * element, GstPipeline * manager);
-static gboolean gst_bin_send_event (GstElement * element, GstEvent * event);
-
 #ifndef GST_DISABLE_INDEX
 static void gst_bin_set_index_func (GstElement * element, GstIndex * index);
 #endif
 static GstClock *gst_bin_get_clock_func (GstElement * element);
 static void gst_bin_set_clock_func (GstElement * element, GstClock * clock);
 
+static void gst_bin_set_manager (GstElement * element, GstPipeline * manager);
 static void gst_bin_set_bus (GstElement * element, GstBus * bus);
 static void gst_bin_set_scheduler (GstElement * element, GstScheduler * sched);
 
+static gboolean gst_bin_send_event (GstElement * element, GstEvent * event);
 
 #ifndef GST_DISABLE_LOADSAVE
 static xmlNodePtr gst_bin_save_thyself (GstObject * object, xmlNodePtr parent);
@@ -169,8 +168,6 @@ gst_bin_class_init (GstBinClass * klass)
       GST_DEBUG_FUNCPTR (gst_bin_restore_thyself);
 #endif
 
-  gstelement_class->set_manager = GST_DEBUG_FUNCPTR (gst_bin_set_manager);
-  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_bin_send_event);
   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_bin_change_state);
   gstelement_class->get_state = GST_DEBUG_FUNCPTR (gst_bin_get_state);
 #ifndef GST_DISABLE_INDEX
@@ -178,9 +175,12 @@ gst_bin_class_init (GstBinClass * klass)
 #endif
   gstelement_class->get_clock = GST_DEBUG_FUNCPTR (gst_bin_get_clock_func);
   gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_bin_set_clock_func);
+  gstelement_class->set_manager = GST_DEBUG_FUNCPTR (gst_bin_set_manager);
   gstelement_class->set_bus = GST_DEBUG_FUNCPTR (gst_bin_set_bus);
   gstelement_class->set_scheduler = GST_DEBUG_FUNCPTR (gst_bin_set_scheduler);
 
+  gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_bin_send_event);
+
   klass->add_element = GST_DEBUG_FUNCPTR (gst_bin_add_func);
   klass->remove_element = GST_DEBUG_FUNCPTR (gst_bin_remove_func);
 }
@@ -323,6 +323,27 @@ gst_bin_set_scheduler (GstElement * element, GstScheduler * sched)
   GST_UNLOCK (bin);
 }
 
+/* set the manager on all of the children in this bin
+ *
+ * MT safe 
+ */
+static void
+gst_bin_set_manager (GstElement * element, GstPipeline * manager)
+{
+  GstBin *bin = GST_BIN (element);
+  GList *kids;
+  GstElement *kid;
+
+  GST_ELEMENT_CLASS (parent_class)->set_manager (element, manager);
+
+  GST_LOCK (element);
+  for (kids = bin->children; kids != NULL; kids = kids->next) {
+    kid = GST_ELEMENT (kids->data);
+    gst_element_set_manager (kid, manager);
+  }
+  GST_UNLOCK (element);
+}
+
 /* add an element to this bin
  *
  * MT safe 
@@ -747,8 +768,73 @@ static GstElementStateReturn
 gst_bin_get_state (GstElement * element, GstElementState * state,
     GstElementState * pending, GTimeVal * timeout)
 {
-  /* implement me */
-  return GST_STATE_FAILURE;
+  GstBin *bin = GST_BIN (element);
+  GstElementStateReturn ret;
+  GList *children;
+  guint32 children_cookie;
+
+  /* we cannot take the state lock yet as we might block when querying
+   * the children, holding the lock too long for no reason. */
+
+  /* next we poll all children for their state to see if one of them
+   * is still busy with its state change. */
+  GST_LOCK (bin);
+restart:
+  ret = GST_STATE_SUCCESS;
+  children = bin->children;
+  children_cookie = bin->children_cookie;
+  while (children) {
+    GstElement *child = GST_ELEMENT_CAST (children->data);
+
+    gst_object_ref (GST_OBJECT_CAST (child));
+    GST_UNLOCK (bin);
+
+    /* ret is ASYNC if some child is still performing the state change */
+    ret = gst_element_get_state (child, NULL, NULL, timeout);
+
+    gst_object_unref (GST_OBJECT_CAST (child));
+
+    if (ret != GST_STATE_SUCCESS) {
+      /* some child is still busy or in error, we can report that
+       * right away. */
+      goto done;
+    }
+    /* now grab the lock to iterate to the next child */
+    GST_LOCK (bin);
+    if (G_UNLIKELY (children_cookie != bin->children_cookie))
+      /* child added/removed during state change, restart */
+      goto restart;
+
+    children = g_list_next (children);
+  }
+  GST_UNLOCK (bin);
+
+done:
+  /* now we can take the state lock */
+  GST_STATE_LOCK (bin);
+  switch (ret) {
+    case GST_STATE_SUCCESS:
+      /* we can commit the state */
+      gst_element_commit_state (element);
+      break;
+    case GST_STATE_FAILURE:
+      /* some element failed, abort the state change */
+      gst_element_abort_state (element);
+      break;
+    default:
+      /* other cases are just passed along */
+      break;
+  }
+
+  /* and report the state if needed */
+  if (state)
+    *state = GST_STATE (element);
+  if (pending)
+    *pending = GST_STATE_PENDING (element);
+
+  GST_STATE_UNLOCK (bin);
+
+  return ret;
 }
 
 /* this function is called with the STATE_LOCK held. It works
@@ -768,8 +854,178 @@ gst_bin_get_state (GstElement * element, GstElementState * state,
 static GstElementStateReturn
 gst_bin_change_state (GstElement * element)
 {
-  /* implement me */
-  return GST_STATE_FAILURE;
+  GstBin *bin;
+  GstElementStateReturn ret;
+  GstElementState old_state, pending;
+  gboolean have_async = FALSE;
+  GList *children;
+  guint32 children_cookie;
+  GQueue *elem_queue;           /* list of elements waiting for a state change */
+
+  bin = GST_BIN (element);
+
+  /* we don't need to take the STATE_LOCK, it is already taken */
+  old_state = GST_STATE (element);
+  pending = GST_STATE_PENDING (element);
+
+  GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+      "changing state of children from %s to %s",
+      gst_element_state_get_name (old_state),
+      gst_element_state_get_name (pending));
+
+  if (pending == GST_STATE_VOID_PENDING)
+    return GST_STATE_SUCCESS;
+
+  /* all elements added to this queue should have their refcount
+   * incremented */
+  elem_queue = g_queue_new ();
+
+  /* first step, find all sink elements, these are the elements
+   * without (linked) source pads. */
+  GST_LOCK (bin);
+restart:
+  children = bin->children;
+  children_cookie = bin->children_cookie;
+  while (children) {
+    GstElement *child = GST_ELEMENT_CAST (children->data);
+
+    gst_object_ref (GST_OBJECT_CAST (child));
+    GST_UNLOCK (bin);
+
+    if (bin_element_is_sink (child, bin) == 0) {
+      /* this also keeps the refcount on the element, note that
+       * the _is_sink function unrefs the element when it is not
+       * a sink. */
+      g_queue_push_tail (elem_queue, child);
+    }
+
+    GST_LOCK (bin);
+    if (G_UNLIKELY (children_cookie != bin->children_cookie)) {
+      /* undo what we had */
+      g_queue_foreach (elem_queue, (GFunc) gst_object_unref, NULL);
+      while (g_queue_pop_head (elem_queue));
+      goto restart;
+    }
+
+    children = g_list_next (children);
+  }
+  GST_UNLOCK (bin);
+
+  /* second step, change state of elements in the queue */
+  while (!g_queue_is_empty (elem_queue)) {
+    GstElement *qelement = g_queue_pop_head (elem_queue);
+    GList *pads;
+    gboolean locked;
+
+    /* queue all elements connected to the sinkpads of this element */
+    GST_LOCK (qelement);
+    pads = qelement->sinkpads;
+    while (pads) {
+      GstPad *pad = GST_PAD_CAST (pads->data);
+      GstPad *peer;
+
+      GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+          "found sinkpad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+      peer = gst_pad_get_peer (pad);
+      if (peer) {
+        GstObject *peer_elem;
+
+        peer_elem = gst_object_get_parent (GST_OBJECT_CAST (peer));
+
+        if (peer_elem) {
+          GstObject *parent;
+
+          /* see if this element is in the bin we are currently handling */
+          parent = gst_object_get_parent (GST_OBJECT_CAST (peer_elem));
+          if (parent && parent == GST_OBJECT_CAST (bin)) {
+            GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+                "adding element %s to queue", GST_ELEMENT_NAME (peer_elem));
+
+            /* was reffed before pushing on the queue by the 
+             * gst_object_get_parent() call we used to get the element. */
+            g_queue_push_tail (elem_queue, peer_elem);
+          } else {
+            GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+                "not adding element %s to queue, it is in another bin",
+                GST_ELEMENT_NAME (peer_elem));
+          }
+          if (parent) {
+            gst_object_unref (GST_OBJECT_CAST (parent));
+          }
+        }
+        gst_object_unref (GST_OBJECT_CAST (peer));
+      } else {
+        GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+            "pad %s:%s does not have a peer", GST_DEBUG_PAD_NAME (pad));
+      }
+      pads = g_list_next (pads);
+    }
+    /* peel off the locked flag and release the element lock */
+    locked = GST_FLAG_IS_SET (qelement, GST_ELEMENT_LOCKED_STATE);
+    GST_UNLOCK (qelement);
+
+    if (G_UNLIKELY (locked))
+      goto next_element;
+
+    qelement->base_time = element->base_time;
+    ret = gst_element_set_state (qelement, pending);
+    switch (ret) {
+      case GST_STATE_SUCCESS:
+        GST_CAT_DEBUG (GST_CAT_STATES,
+            "child '%s' changed state to %d(%s) successfully",
+            GST_ELEMENT_NAME (qelement), pending,
+            gst_element_state_get_name (pending));
+        break;
+      case GST_STATE_ASYNC:
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+            "child '%s' is changing state asynchronously",
+            GST_ELEMENT_NAME (qelement));
+        have_async = TRUE;
+        break;
+      case GST_STATE_FAILURE:
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+            "child '%s' failed to go to state %d(%s)",
+            GST_ELEMENT_NAME (qelement),
+            pending, gst_element_state_get_name (pending));
+        ret = GST_STATE_FAILURE;
+        /* release refcount of element we popped off the queue */
+        gst_object_unref (GST_OBJECT (qelement));
+        goto exit;
+      default:
+        g_assert_not_reached ();
+        break;
+    }
+  next_element:
+    gst_object_unref (GST_OBJECT (qelement));
+  }
+
+  if (have_async) {
+    ret = GST_STATE_ASYNC;
+  } else {
+    if (parent_class->change_state) {
+      ret = parent_class->change_state (element);
+    } else {
+      ret = GST_STATE_SUCCESS;
+    }
+    if (ret == GST_STATE_SUCCESS) {
+      /* we can commit the state change now */
+      gst_element_commit_state (element);
+    }
+  }
+
+  GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+      "done changing bin's state from %s to %s, now in %s",
+      gst_element_state_get_name (old_state),
+      gst_element_state_get_name (pending),
+      gst_element_state_get_name (GST_STATE (element)));
+
+exit:
+  /* release refcounts in queue, should normally be empty */
+  g_queue_foreach (elem_queue, (GFunc) gst_object_unref, NULL);
+  g_queue_free (elem_queue);
+
+  return ret;
 }
 
 static void
@@ -792,53 +1048,47 @@ gst_bin_dispose (GObject * object)
   G_OBJECT_CLASS (parent_class)->dispose (object);
 }
 
-static void
-gst_bin_set_manager (GstElement * element, GstPipeline * manager)
-{
-  GstBin *bin = GST_BIN (element);
-  GList *kids;
-  GstElement *kid;
-
-  GST_ELEMENT_CLASS (parent_class)->set_manager (element, manager);
-
-  GST_LOCK (element);
-  for (kids = bin->children; kids != NULL; kids = kids->next) {
-    kid = GST_ELEMENT (kids->data);
-    gst_element_set_manager (kid, manager);
-  }
-  GST_UNLOCK (element);
-}
-
 /*
  * This function is a utility event handler for seek events.
- * It will change pipeline state to PAUSED, iterate event
- * over all available sinks, distribute new basetime to the
- * pipeline after preroll is done and then re-set to PLAYING.
+ * It will send the event to all sinks.
  * Applications are free to override this behaviour and
  * implement their own seek handler, but this will work for
  * pretty much all cases in practice.
  */
-
 static gboolean
 gst_bin_send_event (GstElement * element, GstEvent * event)
 {
   GstBin *bin = GST_BIN (element);
   GstIterator *iter;
-  GstElement *sink;
-  gpointer data;
   gboolean res = TRUE;
+  gboolean done = FALSE;
 
   iter = gst_bin_iterate_sinks (bin);
   GST_DEBUG_OBJECT (bin, "Sending event to sink children");
 
-  /* iterate over all sinks; preroll will take care of sync,
-   * discont event handling will take care of proper clock
-   * adjustment. Sweet. */
-  while (gst_iterator_next (iter, &data) == GST_ITERATOR_OK) {
-    gst_event_ref (event);
-    sink = GST_ELEMENT (data);
-    res &= gst_element_send_event (sink, event);
-    gst_object_unref (GST_OBJECT (sink));
+  while (!done) {
+    gpointer data;
+
+    switch (gst_iterator_next (iter, &data)) {
+      case GST_ITERATOR_OK:
+      {
+        GstElement *sink;
+
+        gst_event_ref (event);
+        sink = GST_ELEMENT_CAST (data);
+        res &= gst_element_send_event (sink, event);
+        gst_object_unref (GST_OBJECT (sink));
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (iter);
+        res = TRUE;
+        break;
+      default:
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+    }
   }
   gst_iterator_free (iter);
   gst_event_unref (event);
index 1fbe1aa..6eaecf1 100644 (file)
@@ -508,6 +508,13 @@ gst_element_add_pad (GstElement * element, GstPad * pad)
   element->pads_cookie++;
   GST_UNLOCK (element);
 
+  GST_STATE_LOCK (element);
+  /* activate pad when we are playing */
+  if (GST_STATE (element) == GST_STATE_PLAYING)
+    /* FIXME, figure out mode */
+    gst_pad_set_active (pad, GST_ACTIVATE_PUSH);
+  GST_STATE_UNLOCK (element);
+
   /* emit the NEW_PAD signal */
   g_signal_emit (G_OBJECT (element), gst_element_signals[NEW_PAD], 0, pad);
 
@@ -1603,8 +1610,66 @@ gst_element_get_state_func (GstElement * element,
     GstElementState * state, GstElementState * pending, GTimeVal * timeout)
 {
   GstElementStateReturn ret = GST_STATE_FAILURE;
+  GstElementState old_pending;
+
+  g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
+
+  GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
+
+  GST_STATE_LOCK (element);
+  /* we got an error, report immediatly */
+  if (GST_STATE_ERROR (element))
+    goto done;
+
+  old_pending = GST_STATE_PENDING (element);
+  if (old_pending != GST_STATE_VOID_PENDING) {
+    GTimeVal *timeval, abstimeout;
+
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "wait for pending");
+    if (timeout) {
+      /* make timeout absolute */
+      g_get_current_time (&abstimeout);
+      g_time_val_add (&abstimeout,
+          timeout->tv_sec * G_USEC_PER_SEC + timeout->tv_usec);
+      timeval = &abstimeout;
+    } else {
+      timeval = NULL;
+    }
+    /* we have a pending state change, wait for it to complete */
+    if (!GST_STATE_TIMED_WAIT (element, timeval)) {
+      GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "timeout");
+      /* timeout triggered */
+      ret = GST_STATE_ASYNC;
+    } else {
+      /* could be success or failure */
+      if (old_pending == GST_STATE (element)) {
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "got success");
+        ret = GST_STATE_SUCCESS;
+      } else {
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "got failure");
+        ret = GST_STATE_FAILURE;
+      }
+    }
+  }
+  /* if nothing is pending anymore we can return SUCCESS */
+  if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "nothing pending");
+    ret = GST_STATE_SUCCESS;
+  }
+
+done:
+  if (state)
+    *state = GST_STATE (element);
+  if (pending)
+    *pending = GST_STATE_PENDING (element);
+
+  GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+      "state current: %s, pending: %s",
+      gst_element_state_get_name (GST_STATE (element)),
+      gst_element_state_get_name (GST_STATE_PENDING (element)));
+
+  GST_STATE_UNLOCK (element);
 
-  /* implment me */
   return ret;
 }
 
@@ -1665,7 +1730,24 @@ gst_element_get_state (GstElement * element,
 void
 gst_element_abort_state (GstElement * element)
 {
-  /* implment me */
+  GstElementState pending;
+
+  g_return_if_fail (GST_IS_ELEMENT (element));
+
+  pending = GST_STATE_PENDING (element);
+
+  if (pending != GST_STATE_VOID_PENDING && !GST_STATE_ERROR (element)) {
+    GstElementState old_state = GST_STATE (element);
+
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+        "aborting state from %s to %s", gst_element_state_get_name (old_state),
+        gst_element_state_get_name (pending));
+
+    /* flag error */
+    GST_STATE_ERROR (element) = TRUE;
+
+    GST_STATE_BROADCAST (element);
+  }
 }
 
 /**
@@ -1682,7 +1764,31 @@ gst_element_abort_state (GstElement * element)
 void
 gst_element_commit_state (GstElement * element)
 {
-  /* implement me */
+  GstElementState pending;
+  GstMessage *message;
+
+  g_return_if_fail (GST_IS_ELEMENT (element));
+
+  pending = GST_STATE_PENDING (element);
+
+  if (pending != GST_STATE_VOID_PENDING) {
+    GstElementState old_state = GST_STATE (element);
+
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+        "commiting state from %s to %s", gst_element_state_get_name (old_state),
+        gst_element_state_get_name (pending));
+
+    GST_STATE (element) = pending;
+    GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
+    GST_STATE_ERROR (element) = FALSE;
+
+    g_signal_emit (G_OBJECT (element), gst_element_signals[STATE_CHANGE],
+        0, old_state, pending);
+    message = gst_message_new_state_changed (GST_OBJECT (element),
+        old_state, pending);
+    gst_element_post_message (element, message);
+    GST_STATE_BROADCAST (element);
+  }
 }
 
 /**
@@ -1705,7 +1811,18 @@ gst_element_commit_state (GstElement * element)
 void
 gst_element_lost_state (GstElement * element)
 {
-  /* implement me */
+  g_return_if_fail (GST_IS_ELEMENT (element));
+
+  if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING &&
+      !GST_STATE_ERROR (element)) {
+    GstElementState current_state = GST_STATE (element);
+
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+        "lost state of %s", gst_element_state_get_name (current_state));
+
+    GST_STATE_PENDING (element) = current_state;
+    GST_STATE_ERROR (element) = FALSE;
+  }
 }
 
 /**
@@ -1724,8 +1841,116 @@ gst_element_lost_state (GstElement * element)
 GstElementStateReturn
 gst_element_set_state (GstElement * element, GstElementState state)
 {
-  /* implement me */
-  return GST_STATE_SUCCESS;
+  GstElementClass *oclass;
+  GstElementState current;
+  GstElementStateReturn return_val = GST_STATE_SUCCESS;
+
+  /* get the element state lock */
+  GST_STATE_LOCK (element);
+
+#if 0
+  /* a state change is pending and we are not in error, the element is busy
+   * with a state change and we cannot proceed. 
+   * FIXME, does not work for a bin.*/
+  if (G_UNLIKELY (GST_STATE_PENDING (element) != GST_STATE_VOID_PENDING &&
+          !GST_STATE_ERROR (element)))
+    goto was_busy;
+#endif
+
+  /* clear the error flag */
+  GST_STATE_ERROR (element) = FALSE;
+
+  /* start with the current state */
+  current = GST_STATE (element);
+
+  GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "setting state from %s to %s",
+      gst_element_state_get_name (current), gst_element_state_get_name (state));
+
+  oclass = GST_ELEMENT_GET_CLASS (element);
+
+  /* We always perform at least one state change, even if the 
+   * current state is equal to the required state. This is needed
+   * for bins that sync their children. */
+  do {
+    GstElementState pending;
+
+    /* calculate the pending state */
+    if (current < state)
+      pending = current << 1;
+    else if (current > state)
+      pending = current >> 1;
+    else
+      pending = current;
+
+    /* set the pending state variable */
+    GST_STATE_PENDING (element) = pending;
+
+    GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+        "%s: setting state from %s to %s",
+        (pending != state ? "intermediate" : "final"),
+        gst_element_state_get_name (current),
+        gst_element_state_get_name (pending));
+
+    /* call the state change function so it can set the state */
+    if (oclass->change_state)
+      return_val = (oclass->change_state) (element);
+    else
+      return_val = GST_STATE_FAILURE;
+
+    switch (return_val) {
+      case GST_STATE_FAILURE:
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+            "have failed change_state return");
+        /* state change failure exits the loop */
+        gst_element_abort_state (element);
+        goto exit;
+      case GST_STATE_ASYNC:
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+            "element will change state async");
+        /* an async state change exits the loop, we can only
+         * go to the next state change when this one completes. */
+        goto exit;
+      case GST_STATE_SUCCESS:
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+            "element changed state successfuly");
+        /* we can commit the state now and proceed to the next state */
+        gst_element_commit_state (element);
+        GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "commited state");
+        break;
+      default:
+        goto invalid_return;
+    }
+    /* get the current state of the element and see if we need to do more
+     * state changes */
+    current = GST_STATE (element);
+  }
+  while (current != state);
+
+exit:
+  GST_STATE_UNLOCK (element);
+
+  GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "exit state change");
+
+  return return_val;
+
+  /* ERROR */
+#if 0
+was_busy:
+  {
+    GST_STATE_UNLOCK (element);
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+        "was busy with a state change");
+
+    return GST_STATE_BUSY;
+  }
+#endif
+invalid_return:
+  {
+    GST_STATE_UNLOCK (element);
+    /* somebody added a GST_STATE_ and forgot to do stuff here ! */
+    g_critical ("unkown return value from a state change function");
+    return GST_STATE_FAILURE;
+  }
 }
 
 /* is called with STATE_LOCK
@@ -1739,7 +1964,92 @@ gst_element_set_state (GstElement * element, GstElementState state)
 static gboolean
 gst_element_pads_activate (GstElement * element, gboolean active)
 {
-  return FALSE;
+  GList *pads;
+  gboolean result;
+  guint32 cookie;
+
+  GST_LOCK (element);
+restart:
+  result = TRUE;
+  pads = element->pads;
+  cookie = element->pads_cookie;
+  for (; pads && result; pads = g_list_next (pads)) {
+    GstPad *pad = GST_PAD (pads->data);
+
+    gst_object_ref (GST_OBJECT (pad));
+    GST_UNLOCK (element);
+
+    /* we only care about real pads */
+    if (GST_IS_REAL_PAD (pad)) {
+      GstRealPad *peer;
+      gboolean pad_loop, pad_get;
+      gboolean delay = FALSE;
+
+      /* see if the pad has a loop function and grab
+       * the peer */
+      GST_LOCK (pad);
+      pad_get = GST_RPAD_GETRANGEFUNC (pad) != NULL;
+      pad_loop = GST_RPAD_LOOPFUNC (pad) != NULL;
+      peer = GST_RPAD_PEER (pad);
+      if (peer)
+        gst_object_ref (GST_OBJECT (peer));
+      GST_UNLOCK (pad);
+
+      if (peer) {
+        gboolean peer_loop, peer_get;
+
+        /* see if the peer has a getrange function */
+        peer_get = GST_RPAD_GETRANGEFUNC (peer) != NULL;
+        /* see if the peer has a loop function */
+        peer_loop = GST_RPAD_LOOPFUNC (peer) != NULL;
+
+        /* sinkpads with a loop function are delayed since they
+         * need the srcpad to be active first */
+        if (GST_PAD_IS_SINK (pad) && pad_loop && peer_get) {
+          GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+              "delaying pad %s", GST_OBJECT_NAME (pad));
+          delay = TRUE;
+        } else if (GST_PAD_IS_SRC (pad) && peer_loop && pad_get) {
+          /* If the pad is a source and the peer has a loop function,
+           * we can activate the srcpad and then the loopbased sinkpad */
+          GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+              "%sactivating pad %s", (active ? "" : "(de)"),
+              GST_OBJECT_NAME (pad));
+          result &= gst_pad_set_active (pad,
+              (active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE));
+
+          GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+              "%sactivating delayed pad %s", (active ? "" : "(de)"),
+              GST_OBJECT_NAME (peer));
+          result &= gst_pad_set_active (GST_PAD (peer),
+              (active ? GST_ACTIVATE_PULL : GST_ACTIVATE_NONE));
+
+          /* set flag here since we don't want the code below to activate
+           * the pad again */
+          delay = TRUE;
+        }
+        gst_object_unref (GST_OBJECT (peer));
+      }
+
+      /* all other conditions are just push based pads */
+      if (!delay) {
+        GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+            "%sactivating pad %s", (active ? "" : "(de)"),
+            GST_OBJECT_NAME (pad));
+
+        result &= gst_pad_set_active (pad,
+            (active ? GST_ACTIVATE_PUSH : GST_ACTIVATE_NONE));
+      }
+    }
+    gst_object_unref (GST_OBJECT (pad));
+
+    GST_LOCK (element);
+    if (cookie != element->pads_cookie)
+      goto restart;
+  }
+  GST_UNLOCK (element);
+
+  return result;
 }
 
 /* is called with STATE_LOCK */
index d550847..72ffefa 100644 (file)
@@ -26,7 +26,6 @@
 #include "gstmarshal.h"
 #include "gstutils.h"
 #include "gstelement.h"
-#include "gstpipeline.h"
 #include "gstbin.h"
 #include "gstscheduler.h"
 #include "gstevent.h"
@@ -449,10 +448,101 @@ lost_ghostpad:
 gboolean
 gst_pad_set_active (GstPad * pad, GstActivateMode mode)
 {
-  /* implement me */
-  return FALSE;
-}
+  GstRealPad *realpad;
+  gboolean old;
+  GstPadActivateFunction activatefunc;
+  gboolean active;
+
+  g_return_val_if_fail (GST_IS_PAD (pad), FALSE);
+
+  GST_PAD_REALIZE_AND_LOCK (pad, realpad, lost_ghostpad);
+
+  active = (mode != GST_ACTIVATE_NONE);
+
+  old = GST_PAD_IS_ACTIVE (realpad);
+
+  /* if nothing changed, we can just exit */
+  if (G_UNLIKELY (old == active))
+    goto exit;
+
+  /* make sure data is disallowed when going inactive */
+  if (!active) {
+    GST_CAT_DEBUG (GST_CAT_PADS, "de-activating pad %s:%s",
+        GST_DEBUG_PAD_NAME (realpad));
+    GST_FLAG_UNSET (realpad, GST_PAD_ACTIVE);
+    /* unlock blocked pads so element can resume and stop */
+    GST_PAD_BLOCK_SIGNAL (realpad);
+  }
+
+  if (active) {
+    if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) {
+      if (mode == GST_ACTIVATE_PULL) {
+        if (!realpad->getrangefunc)
+          goto wrong_mode;
+      } else {
+        /* we can push if driven by a chain or loop on the sink pad */
+      }
+    } else {                    /* sink pads */
+      if (mode == GST_ACTIVATE_PULL) {
+        /* the src can drive us with getrange */
+      } else {
+        if (!realpad->chainfunc)
+          goto wrong_mode;
+      }
+    }
+  }
+
+  activatefunc = realpad->activatefunc;
+  if (activatefunc) {
+    gboolean result;
+
+    GST_CAT_DEBUG (GST_CAT_PADS,
+        "calling activate function on pad %s:%s with mode %d",
+        GST_DEBUG_PAD_NAME (realpad), mode);
+
+    /* unlock so element can sync */
+    GST_UNLOCK (realpad);
+    result = activatefunc (GST_PAD_CAST (realpad), mode);
+    /* and lock again */
+    GST_LOCK (realpad);
+    if (result == FALSE)
+      goto activate_error;
+  }
+
+  /* when going to active alow data passing now */
+  if (active) {
+    GST_CAT_DEBUG (GST_CAT_PADS, "activating pad %s:%s",
+        GST_DEBUG_PAD_NAME (realpad));
+    GST_FLAG_SET (realpad, GST_PAD_ACTIVE);
+  }
+
+exit:
+  GST_UNLOCK (realpad);
 
+  return TRUE;
+
+  /* errors */
+lost_ghostpad:
+  {
+    return FALSE;
+  }
+wrong_mode:
+  {
+    GST_CAT_DEBUG (GST_CAT_PADS,
+        "pad %s:%s lacks functions to be active in mode %d",
+        GST_DEBUG_PAD_NAME (realpad), mode);
+    GST_UNLOCK (realpad);
+    return FALSE;
+  }
+activate_error:
+  {
+    GST_CAT_DEBUG (GST_CAT_PADS,
+        "activate function returned FALSE for pad %s:%s",
+        GST_DEBUG_PAD_NAME (realpad));
+    GST_UNLOCK (realpad);
+    return FALSE;
+  }
+}
 
 /**
  * gst_pad_is_active:
@@ -3382,7 +3472,6 @@ done:
  *
  * Returns: TRUE if the event was sent succesfully.
  */
-
 gboolean
 gst_pad_event_default (GstPad * pad, GstEvent * event)
 {
@@ -3390,17 +3479,8 @@ gst_pad_event_default (GstPad * pad, GstEvent * event)
   g_return_val_if_fail (event != NULL, FALSE);
 
   switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_DISCONTINUOUS:{
-      GstElement *element = gst_pad_get_parent (pad);
-      guint64 time;
-
-      if (element && element->clock && GST_ELEMENT_MANAGER (element) &&
-          gst_event_discont_get_value (event, GST_FORMAT_TIME, &time)) {
-        GST_PIPELINE (GST_ELEMENT_MANAGER (element))->stream_time = time;
-      }
-      break;
-    }
-    case GST_EVENT_EOS:{
+    case GST_EVENT_EOS:
+    {
       GstRealPad *rpad = GST_PAD_REALIZE (pad);
 
       if (GST_RPAD_TASK (rpad)) {
index 087ae7b..1446fbd 100644 (file)
@@ -277,16 +277,21 @@ is_eos (GstPipeline * pipeline)
   return result;
 }
 
+/* sending an event on the pipeline pauses the pipeline if it
+ * was playing.
+ */
 static gboolean
 gst_pipeline_send_event (GstElement * element, GstEvent * event)
 {
   gboolean was_playing;
   gboolean res;
+  GstElementState state;
 
-  GST_STATE_LOCK (element);
-  /* hmm... questionable */
-  was_playing = (GST_STATE (element) == GST_STATE_PLAYING);
-  GST_STATE_UNLOCK (element);
+  /* need to call _get_state() since a bin state is only updated
+   * with this call. FIXME, we should probably not block but just
+   * take a snapshot. */
+  gst_element_get_state (element, &state, NULL, NULL);
+  was_playing = state == GST_STATE_PLAYING;
 
   if (was_playing && GST_EVENT_TYPE (event) == GST_EVENT_SEEK)
     gst_element_set_state (element, GST_STATE_PAUSED);
@@ -358,8 +363,87 @@ gst_pipeline_new (const gchar * name)
 static GstElementStateReturn
 gst_pipeline_change_state (GstElement * element)
 {
-  /* implement me */
-  return GST_STATE_FAILURE;
+  GstElementStateReturn result = GST_STATE_SUCCESS;
+  GstPipeline *pipeline = GST_PIPELINE (element);
+  gint transition = GST_STATE_TRANSITION (element);
+
+  switch (transition) {
+    case GST_STATE_NULL_TO_READY:
+      gst_scheduler_setup (GST_ELEMENT_SCHEDULER (pipeline));
+      break;
+    case GST_STATE_READY_TO_PAUSED:
+    {
+      GstClock *clock;
+
+      clock = gst_element_get_clock (element);
+      gst_element_set_clock (element, clock);
+      pipeline->eosed = NULL;
+      break;
+    }
+    case GST_STATE_PAUSED_TO_PLAYING:
+      if (element->clock) {
+        GstClockTime start_time = gst_clock_get_time (element->clock);
+
+        element->base_time = start_time -
+            pipeline->stream_time + pipeline->delay;
+        GST_DEBUG ("stream_time=%" GST_TIME_FORMAT ", start_time=%"
+            GST_TIME_FORMAT, GST_TIME_ARGS (pipeline->stream_time),
+            GST_TIME_ARGS (start_time));
+      } else {
+        element->base_time = 0;
+        GST_DEBUG ("no clock, using base time of 0");
+      }
+      break;
+    case GST_STATE_PLAYING_TO_PAUSED:
+    case GST_STATE_PAUSED_TO_READY:
+    case GST_STATE_READY_TO_NULL:
+      break;
+  }
+
+  result = GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+  switch (transition) {
+    case GST_STATE_READY_TO_PAUSED:
+      pipeline->stream_time = 0;
+      break;
+    case GST_STATE_PAUSED_TO_PLAYING:
+      break;
+    case GST_STATE_PLAYING_TO_PAUSED:
+      if (element->clock) {
+        pipeline->stream_time = gst_clock_get_time (element->clock) -
+            element->base_time;
+      }
+      GST_DEBUG ("stream_time=%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (pipeline->stream_time));
+      break;
+    case GST_STATE_PAUSED_TO_READY:
+      break;
+    case GST_STATE_READY_TO_NULL:
+      break;
+  }
+
+  /* we wait for async state changes ourselves.
+   * FIXME this can block forever, better do this in a worker
+   * thread or use a timeout? */
+  if (result == GST_STATE_ASYNC) {
+    GTimeVal *timeval, timeout;
+
+    GST_STATE_UNLOCK (pipeline);
+
+    GST_LOCK (pipeline);
+    if (pipeline->play_timeout > 0) {
+      GST_TIME_TO_TIMEVAL (pipeline->play_timeout, timeout);
+      timeval = &timeout;
+    } else {
+      timeval = NULL;
+    }
+    GST_UNLOCK (pipeline);
+
+    result = gst_element_get_state (element, NULL, NULL, timeval);
+    GST_STATE_LOCK (pipeline);
+  }
+
+  return result;
 }
 
 /**
diff --git a/libs/gst/base/Makefile.am b/libs/gst/base/Makefile.am
new file mode 100644 (file)
index 0000000..6131249
--- /dev/null
@@ -0,0 +1,23 @@
+lib_LTLIBRARIES = libgstbase.la
+AS_LIBTOOL_LIB = libgstbase
+
+EXTRA_DIST = $(as_libtool_EXTRA_DIST)
+noinst_DATA = $(as_libtool_noinst_DATA_files)
+
+libgstbase_la_DEPENDENCIES = ../libgstreamer-@GST_MAJORMINOR@.la
+libgstbase_la_SOURCES =        \
+       gstbasesink.c   
+
+libgstbase_la_CFLAGS = $(GST_OBJ_CFLAGS)
+libgstbase_la_LIBADD = $(GST_OBJ_LIBS)
+libgstbase_la_LDFLAGS = $(as_libtool_LDFLAGS)
+
+noinst_HEADERS =       
+       gstbasesink.h   
+
+install-data-local: as-libtool-install-data-local
+
+uninstall-local: as-libtool-uninstall-local
+
+include $(top_srcdir)/common/as-libtool.mak
+
diff --git a/libs/gst/base/README b/libs/gst/base/README
new file mode 100644 (file)
index 0000000..efd6e57
--- /dev/null
@@ -0,0 +1,16 @@
+Base classes
+------------
+
+GstBaseSink
+  Base class for sink elements.
+
+  - one sinkpad
+  - handles state changes
+  - does flushing
+  - preroll with optional preview
+  - pull/push mode
+  - EOS handling
+
+  FIXME: not much point making it operate in pull mode as a generic
+  base class I guess... 
diff --git a/libs/gst/base/gstbasesink.c b/libs/gst/base/gstbasesink.c
new file mode 100644 (file)
index 0000000..a476071
--- /dev/null
@@ -0,0 +1,873 @@
+/* GStreamer
+ * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
+ *
+ * gstbasesink.c: 
+ *
+ * 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 "gstbasesink.h"
+#include <gst/gstmarshal.h>
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+GST_DEBUG_CATEGORY_STATIC (gst_basesink_debug);
+#define GST_CAT_DEFAULT gst_basesink_debug
+
+/* #define DEBUGGING */
+#ifdef DEBUGGING
+#define DEBUG(str,args...) g_print (str,##args)
+#else
+#define DEBUG(str,args...)
+#endif
+
+/* BaseSink signals and properties */
+enum
+{
+  /* FILL ME */
+  SIGNAL_HANDOFF,
+  LAST_SIGNAL
+};
+
+#define DEFAULT_SIZE 1024
+#define DEFAULT_HAS_LOOP FALSE
+#define DEFAULT_HAS_CHAIN TRUE
+
+enum
+{
+  PROP_0,
+  PROP_HAS_LOOP,
+  PROP_HAS_CHAIN,
+  PROP_PREROLL_QUEUE_LEN
+};
+
+#define _do_init(bla) \
+    GST_DEBUG_CATEGORY_INIT (gst_basesink_debug, "basesink", 0, "basesink element");
+
+GST_BOILERPLATE_FULL (GstBaseSink, gst_basesink, GstElement, GST_TYPE_ELEMENT,
+    _do_init);
+
+static void gst_basesink_set_clock (GstElement * element, GstClock * clock);
+
+static void gst_basesink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_basesink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstStaticPadTemplate *gst_base_sink_get_template (GstBaseSink * sink);
+static GstCaps *gst_base_sink_get_caps (GstBaseSink * sink);
+static gboolean gst_base_sink_set_caps (GstBaseSink * sink, GstCaps * caps);
+static GstBuffer *gst_base_sink_buffer_alloc (GstBaseSink * sink,
+    guint64 offset, guint size, GstCaps * caps);
+static void gst_basesink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end);
+
+static GstElementStateReturn gst_basesink_change_state (GstElement * element);
+
+static GstFlowReturn gst_basesink_chain_unlocked (GstPad * pad,
+    GstBuffer * buffer);
+static void gst_basesink_loop (GstPad * pad);
+static GstFlowReturn gst_basesink_chain (GstPad * pad, GstBuffer * buffer);
+static gboolean gst_basesink_activate (GstPad * pad, GstActivateMode mode);
+static gboolean gst_basesink_event (GstPad * pad, GstEvent * event);
+static inline void gst_basesink_handle_buffer (GstBaseSink * basesink,
+    GstBuffer * buf);
+
+static GstStaticPadTemplate *
+gst_basesink_get_template (GstBaseSink * bsink)
+{
+  GstStaticPadTemplate *template = NULL;
+  GstBaseSinkClass *bclass;
+
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+
+  if (bclass->get_template)
+    template = bclass->get_template (bsink);
+
+  if (template == NULL) {
+    template = &sinktemplate;
+  }
+  return template;
+}
+
+static void
+gst_basesink_base_init (gpointer g_class)
+{
+  //GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
+
+  /*
+     gst_element_class_add_pad_template (gstelement_class,
+     gst_static_pad_template_get (&sinktemplate));
+   */
+}
+
+static void
+gst_basesink_class_init (GstBaseSinkClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_basesink_set_property);
+  gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_basesink_get_property);
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_LOOP,
+      g_param_spec_boolean ("has-loop", "has-loop",
+          "Enable loop-based operation", DEFAULT_HAS_LOOP,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_CHAIN,
+      g_param_spec_boolean ("has-chain", "has-chain",
+          "Enable chain-based operation", DEFAULT_HAS_CHAIN,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+      PROP_PREROLL_QUEUE_LEN,
+      g_param_spec_uint ("preroll-queue-len", "preroll-queue-len",
+          "Number of buffers to queue during preroll", 0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+  gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_basesink_set_clock);
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_basesink_change_state);
+
+  klass->get_caps = GST_DEBUG_FUNCPTR (gst_base_sink_get_caps);
+  klass->set_caps = GST_DEBUG_FUNCPTR (gst_base_sink_set_caps);
+  klass->get_template = GST_DEBUG_FUNCPTR (gst_base_sink_get_template);
+  klass->buffer_alloc = GST_DEBUG_FUNCPTR (gst_base_sink_buffer_alloc);
+  klass->get_times = GST_DEBUG_FUNCPTR (gst_basesink_get_times);
+}
+
+static GstCaps *
+gst_basesink_pad_getcaps (GstPad * pad)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  GstCaps *caps = NULL;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->get_caps)
+    caps = bclass->get_caps (bsink);
+
+  if (caps == NULL) {
+    GstStaticPadTemplate *stemplate;
+    GstPadTemplate *template;
+
+    stemplate = gst_basesink_get_template (bsink);
+    template = gst_static_pad_template_get (stemplate);
+    caps = gst_caps_copy (gst_pad_template_get_caps (template));
+  }
+  return caps;
+}
+
+static gboolean
+gst_basesink_pad_setcaps (GstPad * pad, GstCaps * caps)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  gboolean res = FALSE;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->set_caps)
+    res = bclass->set_caps (bsink, caps);
+
+  return res;
+}
+
+static GstBuffer *
+gst_basesink_pad_buffer_alloc (GstPad * pad, guint64 offset, guint size,
+    GstCaps * caps)
+{
+  GstBaseSinkClass *bclass;
+  GstBaseSink *bsink;
+  GstBuffer *buffer = NULL;
+
+  bsink = GST_BASESINK (GST_PAD_PARENT (pad));
+  bclass = GST_BASESINK_GET_CLASS (bsink);
+  if (bclass->buffer_alloc)
+    buffer = bclass->buffer_alloc (bsink, offset, size, caps);
+
+  return buffer;
+}
+
+static void
+gst_basesink_init (GstBaseSink * basesink)
+{
+  GstStaticPadTemplate *template;
+
+  template = gst_basesink_get_template (basesink);
+
+  basesink->sinkpad =
+      gst_pad_new_from_template (gst_static_pad_template_get (template),
+      "sink");
+  gst_pad_set_getcaps_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_getcaps));
+  gst_pad_set_setcaps_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_setcaps));
+  gst_pad_set_bufferalloc_function (basesink->sinkpad,
+      GST_DEBUG_FUNCPTR (gst_basesink_pad_buffer_alloc));
+  gst_element_add_pad (GST_ELEMENT (basesink), basesink->sinkpad);
+
+  basesink->pad_mode = GST_ACTIVATE_NONE;
+  GST_RPAD_TASK (basesink->sinkpad) = NULL;
+}
+
+static void
+gst_basesink_set_pad_functions (GstBaseSink * this, GstPad * pad)
+{
+  gst_pad_set_activate_function (pad,
+      GST_DEBUG_FUNCPTR (gst_basesink_activate));
+  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_event));
+
+  if (this->has_chain)
+    gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_chain));
+  else
+    gst_pad_set_chain_function (pad, NULL);
+
+  if (this->has_loop)
+    gst_pad_set_loop_function (pad, GST_DEBUG_FUNCPTR (gst_basesink_loop));
+  else
+    gst_pad_set_loop_function (pad, NULL);
+}
+
+static void
+gst_basesink_set_all_pad_functions (GstBaseSink * this)
+{
+  GList *l;
+
+  for (l = GST_ELEMENT_PADS (this); l; l = l->next)
+    gst_basesink_set_pad_functions (this, (GstPad *) l->data);
+}
+
+static void
+gst_basesink_set_clock (GstElement * element, GstClock * clock)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (element);
+
+  sink->clock = clock;
+}
+
+static void
+gst_basesink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (object);
+
+  GST_LOCK (sink);
+  switch (prop_id) {
+    case PROP_HAS_LOOP:
+      sink->has_loop = g_value_get_boolean (value);
+      gst_basesink_set_all_pad_functions (sink);
+      break;
+    case PROP_HAS_CHAIN:
+      sink->has_chain = g_value_get_boolean (value);
+      gst_basesink_set_all_pad_functions (sink);
+      break;
+    case PROP_PREROLL_QUEUE_LEN:
+      /* preroll lock necessary to serialize with finish_preroll */
+      GST_PREROLL_LOCK (sink->sinkpad);
+      sink->preroll_queue_max_len = g_value_get_uint (value);
+      GST_PREROLL_UNLOCK (sink->sinkpad);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_UNLOCK (sink);
+}
+
+static void
+gst_basesink_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstBaseSink *sink;
+
+  sink = GST_BASESINK (object);
+
+  GST_LOCK (sink);
+  switch (prop_id) {
+    case PROP_HAS_LOOP:
+      g_value_set_boolean (value, sink->has_loop);
+      break;
+    case PROP_HAS_CHAIN:
+      g_value_set_boolean (value, sink->has_chain);
+      break;
+    case PROP_PREROLL_QUEUE_LEN:
+      g_value_set_uint (value, sink->preroll_queue_max_len);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_UNLOCK (sink);
+}
+
+static GstStaticPadTemplate *
+gst_base_sink_get_template (GstBaseSink * sink)
+{
+  return &sinktemplate;
+}
+
+static GstCaps *
+gst_base_sink_get_caps (GstBaseSink * sink)
+{
+  return NULL;
+}
+
+static gboolean
+gst_base_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
+{
+  return TRUE;
+}
+
+static GstBuffer *
+gst_base_sink_buffer_alloc (GstBaseSink * sink, guint64 offset, guint size,
+    GstCaps * caps)
+{
+  return NULL;
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_push (GstBaseSink * basesink, GstPad * pad,
+    GstBuffer * buffer)
+{
+  if (basesink->preroll_queue->length == 0) {
+    GstBaseSinkClass *bclass = GST_BASESINK_GET_CLASS (basesink);
+
+    if (bclass->preroll)
+      bclass->preroll (basesink, buffer);
+  }
+
+  if (basesink->preroll_queue->length < basesink->preroll_queue_max_len) {
+    DEBUG ("push %p %p\n", basesink, buffer);
+    g_queue_push_tail (basesink->preroll_queue, buffer);
+  } else {
+    /* block until the state changes, or we get a flush, or something */
+    DEBUG ("block %p %p\n", basesink, buffer);
+    GST_DEBUG ("element %s waiting to finish preroll",
+        GST_ELEMENT_NAME (basesink));
+    basesink->need_preroll = FALSE;
+    basesink->have_preroll = TRUE;
+    GST_PREROLL_WAIT (pad);
+    GST_DEBUG ("done preroll");
+    basesink->have_preroll = FALSE;
+  }
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_empty (GstBaseSink * basesink, GstPad * pad)
+{
+  GstBuffer *buf;
+  GQueue *q = basesink->preroll_queue;
+
+  if (q) {
+    DEBUG ("empty queue\n");
+    while ((buf = g_queue_pop_head (q))) {
+      DEBUG ("pop %p\n", buf);
+      gst_basesink_handle_buffer (basesink, buf);
+    }
+    DEBUG ("queue len %p %d\n", basesink, q->length);
+  }
+}
+
+/* with PREROLL_LOCK */
+static void
+gst_basesink_preroll_queue_flush (GstBaseSink * basesink)
+{
+  GstBuffer *buf;
+  GQueue *q = basesink->preroll_queue;
+
+  DEBUG ("flush %p\n", basesink);
+  if (q) {
+    while ((buf = g_queue_pop_head (q))) {
+      DEBUG ("pop %p\n", buf);
+      gst_buffer_unref (buf);
+    }
+  }
+}
+
+typedef enum
+{
+  PREROLL_QUEUEING,
+  PREROLL_PLAYING,
+  PREROLL_FLUSHING,
+  PREROLL_ERROR
+} PrerollReturn;
+
+/* with STREAM_LOCK */
+PrerollReturn
+gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad,
+    GstBuffer * buffer)
+{
+  gboolean usable;
+
+  DEBUG ("finish preroll %p <\n", basesink);
+  /* lock order is important */
+  GST_STATE_LOCK (basesink);
+  GST_PREROLL_LOCK (pad);
+  DEBUG ("finish preroll %p >\n", basesink);
+  if (!basesink->need_preroll)
+    goto no_preroll;
+
+  gst_element_commit_state (GST_ELEMENT (basesink));
+  GST_STATE_UNLOCK (basesink);
+
+  gst_basesink_preroll_queue_push (basesink, pad, buffer);
+
+  GST_LOCK (pad);
+  usable = !GST_RPAD_IS_FLUSHING (pad) && GST_RPAD_IS_ACTIVE (pad);
+  GST_UNLOCK (pad);
+  if (!usable)
+    goto unusable;
+
+  if (basesink->need_preroll)
+    goto still_queueing;
+
+  GST_DEBUG ("done preroll");
+
+  gst_basesink_preroll_queue_empty (basesink, pad);
+
+  GST_PREROLL_UNLOCK (pad);
+
+  return PREROLL_PLAYING;
+
+no_preroll:
+  {
+    /* maybe it was another sink that blocked in preroll, need to check for
+       buffers to drain */
+    if (basesink->preroll_queue->length)
+      gst_basesink_preroll_queue_empty (basesink, pad);
+    GST_PREROLL_UNLOCK (pad);
+    GST_STATE_UNLOCK (basesink);
+    return PREROLL_PLAYING;
+  }
+unusable:
+  {
+    GST_DEBUG ("pad is flushing");
+    GST_PREROLL_UNLOCK (pad);
+    return PREROLL_FLUSHING;
+  }
+still_queueing:
+  {
+    GST_PREROLL_UNLOCK (pad);
+    return PREROLL_QUEUEING;
+  }
+}
+
+static gboolean
+gst_basesink_event (GstPad * pad, GstEvent * event)
+{
+  GstBaseSink *basesink;
+  gboolean result = TRUE;
+  GstBaseSinkClass *bclass;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  bclass = GST_BASESINK_GET_CLASS (basesink);
+
+  DEBUG ("event %p\n", basesink);
+
+  if (bclass->event)
+    bclass->event (basesink, event);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_EOS:
+    {
+      gboolean need_eos;
+
+      GST_STREAM_LOCK (pad);
+
+      GST_PREROLL_LOCK (pad);
+      gst_basesink_preroll_queue_empty (basesink, pad);
+      GST_PREROLL_UNLOCK (pad);
+
+      GST_LOCK (basesink);
+      need_eos = basesink->eos = TRUE;
+      if (basesink->clock) {
+        /* wait for last buffer to finish if we have a valid end time */
+        if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
+          basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock,
+              basesink->end_time + GST_ELEMENT (basesink)->base_time);
+          GST_UNLOCK (basesink);
+
+          gst_clock_id_wait (basesink->clock_id, NULL);
+
+          GST_LOCK (basesink);
+          if (basesink->clock_id) {
+            gst_clock_id_unref (basesink->clock_id);
+            basesink->clock_id = NULL;
+          }
+          basesink->end_time = GST_CLOCK_TIME_NONE;
+          need_eos = basesink->eos;
+        }
+        GST_UNLOCK (basesink);
+
+        /* if we are still EOS, we can post the EOS message */
+        if (need_eos) {
+          /* ok, now we can post the message */
+          gst_element_post_message (GST_ELEMENT (basesink),
+              gst_message_new_eos (GST_OBJECT (basesink)));
+        }
+      }
+      GST_STREAM_UNLOCK (pad);
+      break;
+    }
+    case GST_EVENT_DISCONTINUOUS:
+      GST_STREAM_LOCK (pad);
+      if (basesink->clock) {
+        //gint64 value = GST_EVENT_DISCONT_OFFSET (event, 0).value;
+      }
+      GST_STREAM_UNLOCK (pad);
+      break;
+    case GST_EVENT_FLUSH:
+      /* make sure we are not blocked on the clock also clear any pending
+       * eos state. */
+      if (!GST_EVENT_FLUSH_DONE (event)) {
+        GST_LOCK (basesink);
+        basesink->eos = FALSE;
+        if (basesink->clock_id) {
+          gst_clock_id_unschedule (basesink->clock_id);
+        }
+        GST_UNLOCK (basesink);
+
+        /* unlock from a possible state change/preroll */
+        GST_PREROLL_LOCK (pad);
+        basesink->need_preroll = TRUE;
+        gst_basesink_preroll_queue_flush (basesink);
+        GST_PREROLL_SIGNAL (pad);
+        GST_PREROLL_UNLOCK (pad);
+      }
+      /* now we are completely unblocked and the _chain method
+       * will return */
+      break;
+    default:
+      result = gst_pad_event_default (pad, event);
+      break;
+  }
+
+  return result;
+}
+
+static void
+gst_basesink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end)
+{
+  GstClockTime timestamp, duration;
+
+  timestamp = GST_BUFFER_TIMESTAMP (buffer);
+  if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
+    duration = GST_BUFFER_DURATION (buffer);
+    if (GST_CLOCK_TIME_IS_VALID (duration)) {
+      *end = timestamp + duration;
+    }
+    *start = timestamp;
+  }
+}
+
+static void
+gst_basesink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
+{
+  if (basesink->clock) {
+    GstClockReturn ret;
+    GstClockTime start, end;
+    GstBaseSinkClass *bclass;
+
+    bclass = GST_BASESINK_GET_CLASS (basesink);
+    start = end = -1;
+    if (bclass->get_times)
+      bclass->get_times (basesink, buffer, &start, &end);
+
+    if (GST_CLOCK_TIME_IS_VALID (start)) {
+      /* save clock id so that we can unlock it if needed */
+      GST_LOCK (basesink);
+      basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock,
+          start + GST_ELEMENT (basesink)->base_time);
+      basesink->end_time = end;
+      GST_UNLOCK (basesink);
+
+      ret = gst_clock_id_wait (basesink->clock_id, NULL);
+
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unref (basesink->clock_id);
+        basesink->clock_id = NULL;
+      }
+      basesink->end_time = GST_CLOCK_TIME_NONE;
+      GST_UNLOCK (basesink);
+
+      GST_LOG_OBJECT (basesink, "clock entry done: %d", ret);
+    }
+  }
+}
+
+static inline void
+gst_basesink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf)
+{
+  GstBaseSinkClass *bclass;
+
+  gst_basesink_do_sync (basesink, buf);
+
+  bclass = GST_BASESINK_GET_CLASS (basesink);
+  if (bclass->render)
+    bclass->render (basesink, buf);
+
+  DEBUG ("unref %p %p\n", basesink, buf);
+  gst_buffer_unref (buf);
+}
+
+static GstFlowReturn
+gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf)
+{
+  GstBaseSink *basesink;
+  PrerollReturn result;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  DEBUG ("chain_unlocked %p\n", basesink);
+
+  result = gst_basesink_finish_preroll (basesink, pad, buf);
+
+  DEBUG ("chain_unlocked %p after\n", basesink);
+
+  switch (result) {
+    case PREROLL_QUEUEING:
+      return GST_FLOW_OK;
+    case PREROLL_PLAYING:
+      gst_basesink_handle_buffer (basesink, buf);
+      return GST_FLOW_OK;
+    case PREROLL_FLUSHING:
+      return GST_FLOW_UNEXPECTED;
+    default:
+      g_assert_not_reached ();
+      return GST_FLOW_UNEXPECTED;
+  }
+}
+
+static GstFlowReturn
+gst_basesink_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstFlowReturn result;
+
+  g_assert (GST_BASESINK (GST_OBJECT_PARENT (pad))->pad_mode ==
+      GST_ACTIVATE_PUSH);
+
+  GST_STREAM_LOCK (pad);
+
+  result = gst_basesink_chain_unlocked (pad, buf);
+
+  GST_STREAM_UNLOCK (pad);
+
+  return result;
+}
+
+static void
+gst_basesink_loop (GstPad * pad)
+{
+  GstBaseSink *basesink;
+  GstBuffer *buf = NULL;
+  GstFlowReturn result;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  g_assert (basesink->pad_mode == GST_ACTIVATE_PULL);
+
+  GST_STREAM_LOCK (pad);
+
+  result = gst_pad_pull_range (pad, basesink->offset, DEFAULT_SIZE, &buf);
+  if (result != GST_FLOW_OK)
+    goto paused;
+
+  result = gst_basesink_chain_unlocked (pad, buf);
+  if (result != GST_FLOW_OK)
+    goto paused;
+
+  /* default */
+  GST_STREAM_UNLOCK (pad);
+  return;
+
+paused:
+  gst_task_pause (GST_RPAD_TASK (pad));
+  GST_STREAM_UNLOCK (pad);
+  return;
+}
+
+static gboolean
+gst_basesink_activate (GstPad * pad, GstActivateMode mode)
+{
+  gboolean result = FALSE;
+  GstBaseSink *basesink;
+
+  basesink = GST_BASESINK (GST_OBJECT_PARENT (pad));
+
+  switch (mode) {
+    case GST_ACTIVATE_PUSH:
+      g_return_val_if_fail (basesink->has_chain, FALSE);
+      result = TRUE;
+      break;
+    case GST_ACTIVATE_PULL:
+      /* if we have a scheduler we can start the task */
+      g_return_val_if_fail (basesink->has_loop, FALSE);
+      if (GST_ELEMENT_SCHEDULER (basesink)) {
+        GST_STREAM_LOCK (pad);
+        GST_RPAD_TASK (pad) =
+            gst_scheduler_create_task (GST_ELEMENT_SCHEDULER (basesink),
+            (GstTaskFunction) gst_basesink_loop, pad);
+
+        gst_task_start (GST_RPAD_TASK (pad));
+        GST_STREAM_UNLOCK (pad);
+        result = TRUE;
+      }
+      break;
+    case GST_ACTIVATE_NONE:
+      /* step 1, unblock clock sync (if any) or any other blocking thing */
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unschedule (basesink->clock_id);
+      }
+      GST_UNLOCK (basesink);
+
+      /* unlock preroll */
+      GST_PREROLL_LOCK (pad);
+      GST_PREROLL_SIGNAL (pad);
+      GST_PREROLL_UNLOCK (pad);
+
+      /* step 2, make sure streaming finishes */
+      GST_STREAM_LOCK (pad);
+      /* step 3, stop the task */
+      if (GST_RPAD_TASK (pad)) {
+        gst_task_stop (GST_RPAD_TASK (pad));
+        gst_object_unref (GST_OBJECT (GST_RPAD_TASK (pad)));
+        GST_RPAD_TASK (pad) = NULL;
+      }
+      GST_STREAM_UNLOCK (pad);
+
+      result = TRUE;
+      break;
+  }
+  basesink->pad_mode = mode;
+
+  return result;
+}
+
+static GstElementStateReturn
+gst_basesink_change_state (GstElement * element)
+{
+  GstElementStateReturn ret = GST_STATE_SUCCESS;
+  GstBaseSink *basesink = GST_BASESINK (element);
+  GstElementState transition = GST_STATE_TRANSITION (element);
+
+  DEBUG ("state change > %p %x\n", basesink, transition);
+
+  switch (transition) {
+    case GST_STATE_NULL_TO_READY:
+      break;
+    case GST_STATE_READY_TO_PAUSED:
+      /* need to complete preroll before this state change completes, there
+       * is no data flow in READY so we cqn safely assume we need to preroll. */
+      basesink->offset = 0;
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      basesink->preroll_queue = g_queue_new ();
+      basesink->need_preroll = TRUE;
+      basesink->have_preroll = FALSE;
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      ret = GST_STATE_ASYNC;
+      break;
+    case GST_STATE_PAUSED_TO_PLAYING:
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      if (basesink->have_preroll) {
+        /* now let it play */
+        GST_PREROLL_SIGNAL (basesink->sinkpad);
+      } else {
+        /* FIXME. We do not have a preroll and we don't need it anymore 
+         * now, this is a case we want to avoid. One way would be to make
+         * a 'lost state' function that makes get_state return PAUSED with
+         * ASYNC to indicate that we are prerolling again. */
+        basesink->need_preroll = FALSE;
+      }
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      break;
+    default:
+      break;
+  }
+
+  GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+  switch (transition) {
+    case GST_STATE_PLAYING_TO_PAUSED:
+    {
+      gboolean eos;
+
+      /* unlock clock wait if any */
+      GST_LOCK (basesink);
+      if (basesink->clock_id) {
+        gst_clock_id_unschedule (basesink->clock_id);
+      }
+      eos = basesink->eos;
+      GST_UNLOCK (basesink);
+
+      GST_PREROLL_LOCK (basesink->sinkpad);
+      /* if we don't have a preroll buffer and we have not received EOS,
+       * we need to wait for a preroll */
+      if (!basesink->have_preroll && !eos) {
+        basesink->need_preroll = TRUE;
+        ret = GST_STATE_ASYNC;
+      }
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+      break;
+    }
+    case GST_STATE_PAUSED_TO_READY:
+      /* flush out the data thread if it's locked in finish_preroll */
+      GST_PREROLL_LOCK (basesink->sinkpad);
+
+      gst_basesink_preroll_queue_flush (basesink);
+      g_queue_free (basesink->preroll_queue);
+      basesink->preroll_queue = NULL;
+
+      if (basesink->have_preroll)
+        GST_PREROLL_SIGNAL (basesink->sinkpad);
+
+      basesink->need_preroll = FALSE;
+      basesink->have_preroll = FALSE;
+      GST_PREROLL_UNLOCK (basesink->sinkpad);
+
+      /* make sure the element is finished processing */
+      GST_STREAM_LOCK (basesink->sinkpad);
+      GST_STREAM_UNLOCK (basesink->sinkpad);
+      break;
+    case GST_STATE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  DEBUG ("state change < %p %x\n", basesink, transition);
+  return ret;
+}
diff --git a/libs/gst/base/gstbasesink.h b/libs/gst/base/gstbasesink.h
new file mode 100644 (file)
index 0000000..3c64f78
--- /dev/null
@@ -0,0 +1,89 @@
+/* GStreamer
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ *                    2000 Wim Taymans <wtay@chello.be>
+ *
+ * gstbasesink.h: 
+ *
+ * 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_BASESINK_H__
+#define __GST_BASESINK_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+
+#define GST_TYPE_BASESINK              (gst_basesink_get_type())
+#define GST_BASESINK(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASESINK,GstBaseSink))
+#define GST_BASESINK_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASESINK,GstBaseSinkClass))
+#define GST_BASESINK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_BASESINK, GstBaseSinkClass))
+#define GST_IS_BASESINK(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASESINK))
+#define GST_IS_BASESINK_CLASS(obj)     (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASESINK))
+
+#define GST_BASESINK_CLOCK(obj)                (GST_BASESINK (obj)->clock)
+#define GST_BASESINK_PAD(obj)          (GST_BASESINK (obj)->sinkpad)
+
+typedef struct _GstBaseSink GstBaseSink;
+typedef struct _GstBaseSinkClass GstBaseSinkClass;
+
+struct _GstBaseSink {
+  GstElement    element;
+
+  GstPad       *sinkpad;
+  GstActivateMode      pad_mode;
+
+  GQueue       *preroll_queue; /* with PREROLL_LOCK */
+  gint          preroll_queue_max_len; /* with PREROLL_LOCK */
+
+  guint64       offset;
+  gboolean      has_loop;
+  gboolean      has_chain;
+
+  GstClock     *clock;
+  GstClockID     clock_id;
+  GstClockTime   end_time;
+  
+  gboolean       eos;
+  gboolean       need_preroll;
+  gboolean       have_preroll;
+};
+
+struct _GstBaseSinkClass {
+  GstElementClass parent_class;
+
+  GstStaticPadTemplate* (*get_template) (GstBaseSink *sink);
+
+  GstCaps*      (*get_caps)     (GstBaseSink *sink);
+  gboolean      (*set_caps)     (GstBaseSink *sink, GstCaps *caps);
+
+  GstBuffer*    (*buffer_alloc) (GstBaseSink *sink, guint64 offset, guint size,
+                                GstCaps *caps);
+
+  void         (*get_times)    (GstBaseSink *sink, GstBuffer *buffer, 
+                                GstClockTime *start, GstClockTime *end);
+
+  void          (*event)        (GstBaseSink *sink, GstEvent *event);
+  GstFlowReturn (*preroll)      (GstBaseSink *sink, GstBuffer *buffer);
+  GstFlowReturn (*render)       (GstBaseSink *sink, GstBuffer *buffer);
+};
+
+GType gst_basesink_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_BASESINK_H__ */
index db5cb1f..4d6d5a8 100644 (file)
@@ -45,7 +45,7 @@ libgstelements_la_SOURCES =   \
 
 libgstelements_la_CFLAGS = $(GST_OBJ_CFLAGS)
 libgstelements_la_LIBADD = $(GST_OBJ_LIBS)
-libgstelements_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(as_libtool_LDFLAGS)
+libgstelements_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(as_libtool_LDFLAGS) $(top_builddir)/gst/base/libgstbase.la
 
 noinst_HEADERS =               \
        gstaggregator.h         \
index 5491678..b8d483c 100644 (file)
@@ -1,6 +1,6 @@
 /* GStreamer
  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
- *                    2000 Wim Taymans <wtay@chello.be>
+ *                    2005 Wim Taymans <wim@fluendo.com>
  *
  * gstfakesink.c: 
  *
@@ -39,7 +39,9 @@ GST_DEBUG_CATEGORY_STATIC (gst_fakesink_debug);
 GstElementDetails gst_fakesink_details = GST_ELEMENT_DETAILS ("Fake Sink",
     "Sink",
     "Black hole for data",
-    "Erik Walthinsen <omega@cse.ogi.edu>");
+    "Erik Walthinsen <omega@cse.ogi.edu>, "
+    "Wim Taymans <wim@fluendo.com>, "
+    "Mr. 'frag-me-more' Vanderwingo <wingo@fluendo.com>");
 
 
 /* FakeSink signals and args */
@@ -50,23 +52,24 @@ enum
   LAST_SIGNAL
 };
 
+#define DEFAULT_STATE_ERROR FAKESINK_STATE_ERROR_NONE
+#define DEFAULT_SILENT FALSE
+#define DEFAULT_DUMP FALSE
+#define DEFAULT_SYNC FALSE
+#define DEFAULT_SIGNAL_HANDOFFS FALSE
+#define DEFAULT_LAST_MESSAGE NULL
+
 enum
 {
   ARG_0,
   ARG_STATE_ERROR,
-  ARG_NUM_SINKS,
   ARG_SILENT,
   ARG_DUMP,
   ARG_SYNC,
   ARG_SIGNAL_HANDOFFS,
-  ARG_LAST_MESSAGE
+  ARG_LAST_MESSAGE,
 };
 
-GstStaticPadTemplate fakesink_sink_template = GST_STATIC_PAD_TEMPLATE ("sink%d",
-    GST_PAD_SINK,
-    GST_PAD_REQUEST,
-    GST_STATIC_CAPS_ANY);
-
 #define GST_TYPE_FAKESINK_STATE_ERROR (gst_fakesink_state_error_get_type())
 static GType
 gst_fakesink_state_error_get_type (void)
@@ -99,13 +102,9 @@ gst_fakesink_state_error_get_type (void)
 #define _do_init(bla) \
     GST_DEBUG_CATEGORY_INIT (gst_fakesink_debug, "fakesink", 0, "fakesink element");
 
-GST_BOILERPLATE_FULL (GstFakeSink, gst_fakesink, GstElement, GST_TYPE_ELEMENT,
+GST_BOILERPLATE_FULL (GstFakeSink, gst_fakesink, GstBaseSink, GST_TYPE_BASESINK,
     _do_init);
 
-static void gst_fakesink_set_clock (GstElement * element, GstClock * clock);
-static GstPad *gst_fakesink_request_new_pad (GstElement * element,
-    GstPadTemplate * templ, const gchar * unused);
-
 static void gst_fakesink_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
 static void gst_fakesink_get_property (GObject * object, guint prop_id,
@@ -113,8 +112,13 @@ static void gst_fakesink_get_property (GObject * object, guint prop_id,
 
 static GstElementStateReturn gst_fakesink_change_state (GstElement * element);
 
-static GstFlowReturn gst_fakesink_chain (GstPad * pad, GstBuffer * buffer);
-static gboolean gst_fakesink_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_fakesink_preroll (GstBaseSink * bsink,
+    GstBuffer * buffer);
+static GstFlowReturn gst_fakesink_render (GstBaseSink * bsink,
+    GstBuffer * buffer);
+static void gst_fakesink_event (GstBaseSink * bsink, GstEvent * event);
+static void gst_fakesink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end);
 
 static guint gst_fakesink_signals[LAST_SIGNAL] = { 0 };
 
@@ -126,8 +130,6 @@ gst_fakesink_base_init (gpointer g_class)
   gst_element_class_add_pad_template (gstelement_class,
       gst_static_pad_template_get (&sinktemplate));
   gst_element_class_set_details (gstelement_class, &gst_fakesink_details);
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&fakesink_sink_template));
 }
 
 static void
@@ -135,36 +137,37 @@ gst_fakesink_class_init (GstFakeSinkClass * klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
+  GstBaseSinkClass *gstbasesink_class;
 
   gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
+  gstbasesink_class = (GstBaseSinkClass *) klass;
 
   gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_fakesink_set_property);
   gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_fakesink_get_property);
 
-  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_NUM_SINKS,
-      g_param_spec_int ("num_sinks", "Number of sinks",
-          "The number of sinkpads", 1, G_MAXINT, 1, G_PARAM_READABLE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATE_ERROR,
       g_param_spec_enum ("state_error", "State Error",
           "Generate a state change error", GST_TYPE_FAKESINK_STATE_ERROR,
-          FAKESINK_STATE_ERROR_NONE, G_PARAM_READWRITE));
+          DEFAULT_STATE_ERROR, G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LAST_MESSAGE,
       g_param_spec_string ("last_message", "Last Message",
-          "The message describing current status", NULL, G_PARAM_READABLE));
+          "The message describing current status", DEFAULT_LAST_MESSAGE,
+          G_PARAM_READABLE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SYNC,
-      g_param_spec_boolean ("sync", "Sync", "Sync on the clock", FALSE,
+      g_param_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
           G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SIGNAL_HANDOFFS,
       g_param_spec_boolean ("signal-handoffs", "Signal handoffs",
-          "Send a signal before unreffing the buffer", FALSE,
+          "Send a signal before unreffing the buffer", DEFAULT_SIGNAL_HANDOFFS,
           G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SILENT,
       g_param_spec_boolean ("silent", "Silent",
-          "Don't produce last_message events", FALSE, G_PARAM_READWRITE));
+          "Don't produce last_message events", DEFAULT_SILENT,
+          G_PARAM_READWRITE));
   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DUMP,
       g_param_spec_boolean ("dump", "Dump", "Dump received bytes to stdout",
-          FALSE, G_PARAM_READWRITE));
+          DEFAULT_DUMP, G_PARAM_READWRITE));
 
   gst_fakesink_signals[SIGNAL_HANDOFF] =
       g_signal_new ("handoff", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
@@ -172,69 +175,24 @@ gst_fakesink_class_init (GstFakeSinkClass * klass)
       gst_marshal_VOID__BOXED_OBJECT, G_TYPE_NONE, 2,
       GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE, GST_TYPE_PAD);
 
-  gstelement_class->request_new_pad =
-      GST_DEBUG_FUNCPTR (gst_fakesink_request_new_pad);
-  gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_fakesink_set_clock);
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_fakesink_change_state);
+
+  gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_fakesink_event);
+  gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_fakesink_preroll);
+  gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_fakesink_render);
+  gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_fakesink_get_times);
 }
 
 static void
 gst_fakesink_init (GstFakeSink * fakesink)
 {
-  GstPad *pad;
-
-  pad =
-      gst_pad_new_from_template (gst_static_pad_template_get (&sinktemplate),
-      "sink");
-  gst_element_add_pad (GST_ELEMENT (fakesink), pad);
-  gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_fakesink_chain));
-  gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_fakesink_event));
-
-  fakesink->silent = FALSE;
-  fakesink->dump = FALSE;
-  fakesink->sync = FALSE;
-  fakesink->last_message = NULL;
-  fakesink->state_error = FAKESINK_STATE_ERROR_NONE;
-  fakesink->signal_handoffs = FALSE;
-}
-
-static void
-gst_fakesink_set_clock (GstElement * element, GstClock * clock)
-{
-  GstFakeSink *sink;
-
-  sink = GST_FAKESINK (element);
-
-  sink->clock = clock;
-}
-
-static GstPad *
-gst_fakesink_request_new_pad (GstElement * element, GstPadTemplate * templ,
-    const gchar * unused)
-{
-  gchar *name;
-  GstPad *sinkpad;
-  GstFakeSink *fakesink;
-
-  g_return_val_if_fail (GST_IS_FAKESINK (element), NULL);
-
-  if (templ->direction != GST_PAD_SINK) {
-    g_warning ("gstfakesink: request new pad that is not a SINK pad\n");
-    return NULL;
-  }
-
-  fakesink = GST_FAKESINK (element);
-
-  name = g_strdup_printf ("sink%d", GST_ELEMENT (fakesink)->numsinkpads);
-
-  sinkpad = gst_pad_new_from_template (templ, name);
-  g_free (name);
-  gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_fakesink_chain));
-
-  gst_element_add_pad (GST_ELEMENT (fakesink), sinkpad);
-
-  return sinkpad;
+  fakesink->silent = DEFAULT_SILENT;
+  fakesink->dump = DEFAULT_DUMP;
+  fakesink->sync = DEFAULT_SYNC;
+  fakesink->last_message = DEFAULT_LAST_MESSAGE;
+  fakesink->state_error = DEFAULT_STATE_ERROR;
+  fakesink->signal_handoffs = DEFAULT_SIGNAL_HANDOFFS;
 }
 
 static void
@@ -243,7 +201,6 @@ gst_fakesink_set_property (GObject * object, guint prop_id,
 {
   GstFakeSink *sink;
 
-  /* it's not null if we got it, but it might not be ours */
   sink = GST_FAKESINK (object);
 
   switch (prop_id) {
@@ -274,15 +231,9 @@ gst_fakesink_get_property (GObject * object, guint prop_id, GValue * value,
 {
   GstFakeSink *sink;
 
-  /* it's not null if we got it, but it might not be ours */
-  g_return_if_fail (GST_IS_FAKESINK (object));
-
   sink = GST_FAKESINK (object);
 
   switch (prop_id) {
-    case ARG_NUM_SINKS:
-      g_value_set_int (value, GST_ELEMENT (sink)->numsinkpads);
-      break;
     case ARG_STATE_ERROR:
       g_value_set_enum (value, sink->state_error);
       break;
@@ -307,79 +258,86 @@ gst_fakesink_get_property (GObject * object, guint prop_id, GValue * value,
   }
 }
 
-static gboolean
-gst_fakesink_event (GstPad * pad, GstEvent * event)
+static void
+gst_fakesink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+    GstClockTime * start, GstClockTime * end)
 {
-  GstFakeSink *fakesink;
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-  fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad));
+  if (sink->sync) {
+    GST_BASESINK_CLASS (parent_class)->get_times (bsink, buffer, start, end);
+  }
+}
 
-  if (!fakesink->silent) {
-    g_free (fakesink->last_message);
+static void
+gst_fakesink_event (GstBaseSink * bsink, GstEvent * event)
+{
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-    fakesink->last_message =
-        g_strdup_printf ("chain   ******* (%s:%s)E (type: %d) %p",
-        GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE (event), event);
+  if (!sink->silent) {
+    g_free (sink->last_message);
 
-    g_object_notify (G_OBJECT (fakesink), "last_message");
-  }
+    sink->last_message =
+        g_strdup_printf ("chain   ******* E (type: %d) %p",
+        GST_EVENT_TYPE (event), event);
 
-  switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_DISCONTINUOUS:
-    default:
-      gst_pad_event_default (pad, event);
-      break;
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
-
-  return TRUE;
 }
 
 static GstFlowReturn
-gst_fakesink_chain (GstPad * pad, GstBuffer * buffer)
+gst_fakesink_preroll (GstBaseSink * bsink, GstBuffer * buffer)
 {
-  GstBuffer *buf = GST_BUFFER (buffer);
-  GstFakeSink *fakesink;
+  GstFakeSink *sink = GST_FAKESINK (bsink);
+
+  if (!sink->silent) {
+    g_free (sink->last_message);
 
-  fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad));
+    sink->last_message = g_strdup_printf ("preroll   ******* ");
 
-  if (fakesink->sync && fakesink->clock) {
-    //gst_element_wait (GST_ELEMENT (fakesink), GST_BUFFER_TIMESTAMP (buf));
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
+  return GST_FLOW_OK;
+}
 
-  if (!fakesink->silent) {
-    g_free (fakesink->last_message);
+static GstFlowReturn
+gst_fakesink_render (GstBaseSink * bsink, GstBuffer * buf)
+{
+  GstFakeSink *sink = GST_FAKESINK (bsink);
 
-    fakesink->last_message =
-        g_strdup_printf ("chain   ******* (%s:%s)< (%d bytes, timestamp: %"
+  if (!sink->silent) {
+    g_free (sink->last_message);
+
+    sink->last_message =
+        g_strdup_printf ("chain   ******* < (%d bytes, timestamp: %"
         GST_TIME_FORMAT ", duration: %" GST_TIME_FORMAT ", offset: %"
         G_GINT64_FORMAT ", offset_end: %" G_GINT64_FORMAT ", flags: %d) %p",
-        GST_DEBUG_PAD_NAME (pad), GST_BUFFER_SIZE (buf),
+        GST_BUFFER_SIZE (buf),
         GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
         GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_BUFFER_OFFSET (buf),
         GST_BUFFER_OFFSET_END (buf), GST_BUFFER_FLAGS (buf), buf);
 
-    g_object_notify (G_OBJECT (fakesink), "last_message");
+    g_object_notify (G_OBJECT (sink), "last_message");
   }
+  if (sink->signal_handoffs)
+    g_signal_emit (G_OBJECT (sink), gst_fakesink_signals[SIGNAL_HANDOFF], 0,
+        buf);
 
-  if (fakesink->signal_handoffs)
-    g_signal_emit (G_OBJECT (fakesink), gst_fakesink_signals[SIGNAL_HANDOFF], 0,
-        buf, pad);
-
-  if (fakesink->dump) {
+  if (sink->dump) {
     gst_util_dump_mem (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
   }
 
-  gst_buffer_unref (buf);
-
   return GST_FLOW_OK;
 }
 
 static GstElementStateReturn
 gst_fakesink_change_state (GstElement * element)
 {
+  GstElementStateReturn ret = GST_STATE_SUCCESS;
   GstFakeSink *fakesink = GST_FAKESINK (element);
+  GstElementState transition = GST_STATE_TRANSITION (element);
 
-  switch (GST_STATE_TRANSITION (element)) {
+  switch (transition) {
     case GST_STATE_NULL_TO_READY:
       if (fakesink->state_error == FAKESINK_STATE_ERROR_NULL_READY)
         goto error;
@@ -406,12 +364,13 @@ gst_fakesink_change_state (GstElement * element)
       g_free (fakesink->last_message);
       fakesink->last_message = NULL;
       break;
+    default:
+      break;
   }
 
-  if (GST_ELEMENT_CLASS (parent_class)->change_state)
-    return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element);
 
-  return GST_STATE_SUCCESS;
+  return ret;
 
 error:
   GST_ELEMENT_ERROR (element, CORE, STATE_CHANGE, (NULL), (NULL));
index f41ad61..ecb7e9e 100644 (file)
@@ -25,6 +25,7 @@
 #define __GST_FAKESINK_H__
 
 #include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
 
 G_BEGIN_DECLS
 
@@ -54,20 +55,18 @@ typedef struct _GstFakeSink GstFakeSink;
 typedef struct _GstFakeSinkClass GstFakeSinkClass;
 
 struct _GstFakeSink {
-  GstElement    element;
+  GstBaseSink   element;
 
   gboolean      silent;
   gboolean      dump;
   gboolean      sync;
   gboolean      signal_handoffs;
-  GstClock     *clock;
   GstFakeSinkStateError state_error;
-
   gchar        *last_message;
 };
 
 struct _GstFakeSinkClass {
-  GstElementClass parent_class;
+  GstBaseSinkClass parent_class;
 
   /* signals */
   void (*handoff) (GstElement *element, GstBuffer *buf, GstPad *pad);