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

