Sink elements ------------- Sink elements consume data and normally have no source pads. Typical sink elements include: - audio/video renderers - network sinks - filesinks Sinks are harder to construct than other element types as they are treated specially by the GStreamer core. state changes ~~~~~~~~~~~~~ A sink always returns ASYNC from the state change to PAUSED, this includes a state change from READY->PAUSED and PLAYING->PAUSED. The reason for this is that this way we can detect when the first buffer or event arrives in the sink when the state change completes. A sink should block on the first EOS event or buffer received in the READY->PAUSED state before commiting the state to PAUSED. FLUSHING events have to be handled out of sync with the buffer flow and take no part in the preroll procedure. Events other than EOS do not complete the preroll stage. sink overview ~~~~~~~~~~~~~ - TODO: PREROLL_LOCK can be removed and we can safely use the STREAM_LOCK. # Commit the state. We return TRUE if we can continue # streaming, FALSE in the case we go to a READY or NULL state. # if we go to PLAYING, we don't need to block on preroll. commit { LOCK switch (pending) case PLAYING: need_preroll = FALSE break case PAUSED: break case READY: case NULL: return FALSE case VOID: return TRUE # update state state = pending next = VOID pending = VOID UNLOCK return TRUE } # Sync an object. We have to wait for the element to reach # the PLAYING state before we can wait on the clock. # Some items do not need synchronisation (most events) so the # get_times method returns FALSE (not syncable) # need_preroll indicates that we are not in the PLAYING state # and therefore need to commit and potentially block on preroll # if our clock_wait got interrupted we commit and block again. # The reason for this is that the current item being rendered is # not yet finished and we can use that item to finish preroll. do_sync (obj) { # get timing information for this object syncable = get_times (obj, &start, &stop) if (!syncable) return OK; again: while (need_preroll) if (need_commit) need_commit = FALSE if (!commit) return FLUSHING if (need_preroll) # release PREROLL_LOCK and wait. prerolled can be observed # and will be TRUE prerolled = TRUE PREROLL_WAIT (releasing PREROLL_LOCK) prerolled = FALSE if (flushing) return FLUSHING if (valid (start || stop)) PREROLL_UNLOCK end_time = stop ret = wait_clock (obj,start) PREROLL_LOCK if (flushing) return FLUSHING # if the clock was unscheduled, we redo the # preroll if (ret == UNSCHEDULED) goto again } # render a prerollable item (EOS or buffer). It is # always called with the PREROLL_LOCK helt. render_object (obj) { ret = do_sync (obj) if (ret != OK) return ret; # preroll and syncing done, now we can render render(obj) } | # sinks that sync on buffer contents do like this | while (more_to_render) | ret = render | if (ret == interrupted) | prerolled = TRUE render (buffer) ----->| PREROLL_WAIT (releasing PREROLL_LOCK) | prerolled = FALSE | if (flushing) | return FLUSHING | # queue a prerollable item (EOS or buffer). It is # always called with the PREROLL_LOCK helt. # This function will commit the state when receiving the # first prerollable item. # items are then added to the rendering queue or rendered # right away if no preroll is needed. queue (obj, prerollable) { if (need_preroll) if (prerollable) queuelen++ # first item in the queue while we need preroll # will complete state change and call preroll if (queuelen == 1) preroll (obj) if (need_commit) need_commit = FALSE if (!commit) return FLUSHING # then see if we need more preroll items before we # can block if (need_preroll) if (queuelen <= maxqueue) queue.add (obj) return OK # now clear the queue and render each item before # rendering the current item. while (queue.hasItem) render_object (queue.remove()) render_object (obj) queuelen = 0 } # various event functions event EOS: # events must complete preroll too STREAM_LOCK PREROLL_LOCK if (flushing) return FALSE ret = queue (event, TRUE) if (ret == FLUSHING) return FALSE PREROLL_UNLOCK STREAM_UNLOCK break SEGMENT: # the segment must be used to clip incoming # buffers. Then then go into the queue as non-prerollable # items used for syncing the buffers STREAM_LOCK PREROLL_LOCK if (flushing) return FALSE set_clip ret = queue (event, FALSE) if (ret == FLUSHING) return FALSE PREROLL_UNLOCK STREAM_UNLOCK break FLUSH_START: # set flushing and unblock all that is waiting event ----> subclasses can interrupt render PREROLL_LOCK flushing = TRUE unlock_clock PREROLL_SIGNAL PREROLL_UNLOCK STREAM_LOCK lost_state STREAM_UNLOCK break FLUSH_END: # unset flushing and clear all data and eos STREAM_LOCK event PREROLL_LOCK queue.clear queuelen = 0 flushing = FALSE eos = FALSE PREROLL_UNLOCK STREAM_UNLOCK break # the chain function checks the buffer falls within the # configured segment and queues the buffer for preroll and # rendering chain STREAM_LOCK PREROLL_LOCK if (flushing) return FLUSHING if (clip) queue (buffer, TRUE) PREROLL_UNLOCK STREAM_UNLOCK state switch (transition) READY_PAUSED: # no datapassing is going on so we always return ASYNC ret = ASYNC need_commit = TRUE eos = FALSE flushing = FALSE need_preroll = TRUE prerolled = FALSE break PAUSED_PLAYING: # we grab the preroll lock. This we can only do if the # chain function is either doing some clock sync, we are # waiting for preroll or the chain function is not being called. PREROLL_LOCK if (prerolled || eos) ret = OK need_commit = FALSE need_preroll = FALSE if (eos) post_eos else PREROLL_SIGNAL else need_preroll = TRUE need_commit = TRUE ret = ASYNC PREROLL_UNLOCK break PLAYING_PAUSED: ---> subclass can interrupt render # we grab the preroll lock. This we can only do if the # chain function is either doing some clock sync # or the chain function is not being called. PREROLL_LOCK need_preroll = TRUE unlock_clock if (prerolled || eos) ret = OK else ret = ASYNC PREROLL_UNLOCK break PAUSED_READY: ---> subclass can interrupt render # we grab the preroll lock. Set to flushing and unlock # everything. This should exit the chain functions and stop # streaming. PREROLL_LOCK flushing = TRUE unlock_clock queue.clear queuelen = 0 PREROLL_SIGNAL ret = OK PREROLL_UNLOCK break