From 326d36b8d872c7e14b8ee7eba383843f67db1567 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Mar 2005 14:54:33 +0000 Subject: [PATCH] Added state change code. 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. --- ChangeLog | 51 ++ configure.ac | 5 +- docs/design/part-gstelement.txt | 18 +- docs/design/part-negotiation.txt | 152 ++++++ docs/design/part-preroll.txt | 49 ++ docs/design/part-scheduling.txt | 201 +++++++ docs/design/part-states.txt | 151 ++++++ gst/Makefile.am | 4 +- gst/base/Makefile.am | 23 + gst/base/README | 16 + gst/base/gstbasesink.c | 873 +++++++++++++++++++++++++++++++ gst/base/gstbasesink.h | 89 ++++ gst/elements/Makefile.am | 2 +- gst/elements/gstfakesink.c | 225 ++++---- gst/elements/gstfakesink.h | 7 +- gst/gstbin.c | 330 ++++++++++-- gst/gstelement.c | 324 +++++++++++- gst/gstpad.c | 112 +++- gst/gstpipeline.c | 96 +++- libs/gst/base/Makefile.am | 23 + libs/gst/base/README | 16 + libs/gst/base/gstbasesink.c | 873 +++++++++++++++++++++++++++++++ libs/gst/base/gstbasesink.h | 89 ++++ plugins/elements/Makefile.am | 2 +- plugins/elements/gstfakesink.c | 225 ++++---- plugins/elements/gstfakesink.h | 7 +- 26 files changed, 3601 insertions(+), 362 deletions(-) create mode 100644 docs/design/part-negotiation.txt create mode 100644 docs/design/part-preroll.txt create mode 100644 docs/design/part-scheduling.txt create mode 100644 docs/design/part-states.txt create mode 100644 gst/base/Makefile.am create mode 100644 gst/base/README create mode 100644 gst/base/gstbasesink.c create mode 100644 gst/base/gstbasesink.h create mode 100644 libs/gst/base/Makefile.am create mode 100644 libs/gst/base/README create mode 100644 libs/gst/base/gstbasesink.c create mode 100644 libs/gst/base/gstbasesink.h diff --git a/ChangeLog b/ChangeLog index 0f53d9e361..fd8d1c93fe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,54 @@ +2005-03-28 Wim Taymans + + * 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 * gst/Makefile.am: remove gstcpu.[ch]. The gst_cpu functionality diff --git a/configure.ac b/configure.ac index c19851a470..0de6f806d9 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/docs/design/part-gstelement.txt b/docs/design/part-gstelement.txt index 139bf65da4..6266518f58 100644 --- a/docs/design/part-gstelement.txt +++ b/docs/design/part-gstelement.txt @@ -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 *. +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) -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. +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 index 0000000000..b0cf9362ca --- /dev/null +++ b/docs/design/part-negotiation.txt @@ -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 index 0000000000..3f4df531e1 --- /dev/null +++ b/docs/design/part-preroll.txt @@ -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 index 0000000000..386cf54a03 --- /dev/null +++ b/docs/design/part-scheduling.txt @@ -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 index 0000000000..a901632666 --- /dev/null +++ b/docs/design/part-states.txt @@ -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 +----- + + + + + diff --git a/gst/Makefile.am b/gst/Makefile.am index 55974e4dc3..68a8c9c091 100644 --- a/gst/Makefile.am +++ b/gst/Makefile.am @@ -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 index 0000000000..613124910f --- /dev/null +++ b/gst/base/Makefile.am @@ -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 index 0000000000..efd6e572a9 --- /dev/null +++ b/gst/base/README @@ -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 index 0000000000..a476071fd3 --- /dev/null +++ b/gst/base/gstbasesink.c @@ -0,0 +1,873 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans + * + * 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 + +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 index 0000000000..3c64f78ed3 --- /dev/null +++ b/gst/base/gstbasesink.h @@ -0,0 +1,89 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000 Wim Taymans + * + * 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 + +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__ */ diff --git a/gst/elements/Makefile.am b/gst/elements/Makefile.am index db5cb1fcea..4d6d5a8c52 100644 --- a/gst/elements/Makefile.am +++ b/gst/elements/Makefile.am @@ -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 \ diff --git a/gst/elements/gstfakesink.c b/gst/elements/gstfakesink.c index 5491678618..b8d483c6ff 100644 --- a/gst/elements/gstfakesink.c +++ b/gst/elements/gstfakesink.c @@ -1,6 +1,6 @@ /* GStreamer * Copyright (C) 1999,2000 Erik Walthinsen - * 2000 Wim Taymans + * 2005 Wim Taymans * * 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 "); + "Erik Walthinsen , " + "Wim Taymans , " + "Mr. 'frag-me-more' Vanderwingo "); /* 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 (!fakesink->silent) { - g_free (fakesink->last_message); - - fakesink->last_message = - g_strdup_printf ("chain ******* (%s:%s)E (type: %d) %p", - GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE (event), event); - - g_object_notify (G_OBJECT (fakesink), "last_message"); + if (sink->sync) { + GST_BASESINK_CLASS (parent_class)->get_times (bsink, buffer, start, end); } +} - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_DISCONTINUOUS: - default: - gst_pad_event_default (pad, event); - break; +static void +gst_fakesink_event (GstBaseSink * bsink, GstEvent * event) +{ + GstFakeSink *sink = GST_FAKESINK (bsink); + + if (!sink->silent) { + g_free (sink->last_message); + + sink->last_message = + g_strdup_printf ("chain ******* E (type: %d) %p", + GST_EVENT_TYPE (event), event); + + 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); - fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad)); + if (!sink->silent) { + g_free (sink->last_message); - if (fakesink->sync && fakesink->clock) { - //gst_element_wait (GST_ELEMENT (fakesink), GST_BUFFER_TIMESTAMP (buf)); + sink->last_message = g_strdup_printf ("preroll ******* "); + + 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)); diff --git a/gst/elements/gstfakesink.h b/gst/elements/gstfakesink.h index f41ad61d75..ecb7e9e76a 100644 --- a/gst/elements/gstfakesink.h +++ b/gst/elements/gstfakesink.h @@ -25,6 +25,7 @@ #define __GST_FAKESINK_H__ #include +#include 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); diff --git a/gst/gstbin.c b/gst/gstbin.c index 5ee6967084..f15e10f515 100644 --- a/gst/gstbin.c +++ b/gst/gstbin.c @@ -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); diff --git a/gst/gstelement.c b/gst/gstelement.c index 1fbe1aa5bc..6eaecf1e22 100644 --- a/gst/gstelement.c +++ b/gst/gstelement.c @@ -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 */ diff --git a/gst/gstpad.c b/gst/gstpad.c index d5508471eb..72ffefaaf5 100644 --- a/gst/gstpad.c +++ b/gst/gstpad.c @@ -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)) { diff --git a/gst/gstpipeline.c b/gst/gstpipeline.c index 087ae7b6b9..1446fbd8ba 100644 --- a/gst/gstpipeline.c +++ b/gst/gstpipeline.c @@ -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 index 0000000000..613124910f --- /dev/null +++ b/libs/gst/base/Makefile.am @@ -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 index 0000000000..efd6e572a9 --- /dev/null +++ b/libs/gst/base/README @@ -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 index 0000000000..a476071fd3 --- /dev/null +++ b/libs/gst/base/gstbasesink.c @@ -0,0 +1,873 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans + * + * 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 + +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 index 0000000000..3c64f78ed3 --- /dev/null +++ b/libs/gst/base/gstbasesink.h @@ -0,0 +1,89 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000 Wim Taymans + * + * 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 + +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__ */ diff --git a/plugins/elements/Makefile.am b/plugins/elements/Makefile.am index db5cb1fcea..4d6d5a8c52 100644 --- a/plugins/elements/Makefile.am +++ b/plugins/elements/Makefile.am @@ -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 \ diff --git a/plugins/elements/gstfakesink.c b/plugins/elements/gstfakesink.c index 5491678618..b8d483c6ff 100644 --- a/plugins/elements/gstfakesink.c +++ b/plugins/elements/gstfakesink.c @@ -1,6 +1,6 @@ /* GStreamer * Copyright (C) 1999,2000 Erik Walthinsen - * 2000 Wim Taymans + * 2005 Wim Taymans * * 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 "); + "Erik Walthinsen , " + "Wim Taymans , " + "Mr. 'frag-me-more' Vanderwingo "); /* 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 (!fakesink->silent) { - g_free (fakesink->last_message); - - fakesink->last_message = - g_strdup_printf ("chain ******* (%s:%s)E (type: %d) %p", - GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE (event), event); - - g_object_notify (G_OBJECT (fakesink), "last_message"); + if (sink->sync) { + GST_BASESINK_CLASS (parent_class)->get_times (bsink, buffer, start, end); } +} - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_DISCONTINUOUS: - default: - gst_pad_event_default (pad, event); - break; +static void +gst_fakesink_event (GstBaseSink * bsink, GstEvent * event) +{ + GstFakeSink *sink = GST_FAKESINK (bsink); + + if (!sink->silent) { + g_free (sink->last_message); + + sink->last_message = + g_strdup_printf ("chain ******* E (type: %d) %p", + GST_EVENT_TYPE (event), event); + + 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); - fakesink = GST_FAKESINK (GST_OBJECT_PARENT (pad)); + if (!sink->silent) { + g_free (sink->last_message); - if (fakesink->sync && fakesink->clock) { - //gst_element_wait (GST_ELEMENT (fakesink), GST_BUFFER_TIMESTAMP (buf)); + sink->last_message = g_strdup_printf ("preroll ******* "); + + 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)); diff --git a/plugins/elements/gstfakesink.h b/plugins/elements/gstfakesink.h index f41ad61d75..ecb7e9e76a 100644 --- a/plugins/elements/gstfakesink.h +++ b/plugins/elements/gstfakesink.h @@ -25,6 +25,7 @@ #define __GST_FAKESINK_H__ #include +#include 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);