Simplifying Media Nodes

Working with media, and processing it in real time is an inherently difficult and complicated task.  Accordingly, our new Media Kit is complex, needing greater attention to detail than most regular BeOS programming.  In preparation for next months Be Developer's Conference, we have been preparing a series of convenience and utility classes for greater ease in programming Media Kit nodes.

Today, I will present one of these utility classes to you, and give you a sneak peak at the direction we are heading for some of our convenience nodes.  Please note that while the functionality of these classes will remain fairly consistent, their interface is still a work in progress, and may change in the final release.

One of the fundamental concepts of the Media Kit is that the media being played or manipulated has some notion of time associated with it.  The job of a node is to handle this media data at the right time to make sure the end-user sees the correct information.  All of the basic Media Kit control messages also have associated times.  A node needs to properly manage all of these timed events.

Enter the new BTimedEventQueue utility class.  The basic concept is that data representing a specific event is pushed to the queue along with a time that event needs to occur (as seen by the user.)  The queue is sorted by the event time, so a node can pop the appropriate event off the queue and handle it at the right time, and it will be sure that the events are handled in the proper order.

A quick look at the main functions of the class are in order.

status_t PushEvent( bigtime_t time, int32 what,
     void *pointer, uint32 pointer_cleanup, int64 data);

An event is currently represented by a performance time, a 'what' value, some pointer data, a flag denoting how that pointer should be cleaned up, and an int64 worth of additional storage.

Events are usually pushed on to the queue in a node hook function.  For example:

MyBufferConsumer::BufferReceived(BBuffer *buffer)
{
  if (!fRunning)
     buffer->Recycle();
     return;

  // add the event to the queue
  status_t error = B_OK;  
  error = fEvents->PushEvent(
     buffer->Header()->start_time,
     BTimedEventQueue::B_HANDLE_BUFFER,
     buffer,
     BTimedEventQueue::B_RECYCLE,
     0);

  if (error != B_OK)
     buffer->Recycle();
}

Here the pointer is used to keep track of the buffer to work with, the cleanup specifies that the buffer should be recycled when during cleanup (if not sent correctly), and the data field is not used.

Events are retreived from the queue through the PopEvent() function, which takes pointers to the data members of the event:

status_t PopEvent(bigtime_t *time, int32 *what,
     void **pointer, uint32 *pointer_cleanup, int64 *data);

When an event is popped off the queue it is usually handed to a HandleEvent() function.  In HandleEvent() the node should look at the 'what' member and decide what action to take.  Whoever handles the event is responsible for cleaning up anything stored in the pointer field, based on the pointer_cleanup information.

It is sometimes necessary to clear a large number of items out of the queue without handling them.  FlushEvents() provides the ability to specify which events should be cleared.  

status_t FlushEvents(bigtime_t time,
     time_direction direction, bool inclusive = true,
     int32 event = B_ANY_EVENT);

When flushing events you can specify a time from which to base your decision, a direction in time, whether to include the specified time in the flush, and the event type to clear out.  The function is very flexible, allowing you to remove all items with a given event type ('what') like this:

fEvents->FlushEvents(B_INFINITE_TIMOUT,
     BTimedEventQueue::B_BEFORE,
     true, BTimedEventQueue::B_HANDLE_BUFFER);

or to clear out all types within a given range in time like this:

fEvents->FlushEvents(someTime, BTimedEventQueue::B_AFTER);

In either case, when events are flushed from the queue, the CleanUpEvent() function of the queue is called.  This looks at the event and cleans up the pointer data appropriately.  If you add your own cleanup type, you will need to subclass the event queue to provide the appropriate cleanup functionality.

There are also two functions for investigating the contents of the queue.

bool HasEvents();
bigtime_t NextEvent(int32 *what);

HasEvents() returns a boolean and is fairly self explanatory.  NextEvent() gives informaton about the next
event that needs to occur.  It returns the time the event
needs to occur, and if passed an int32 pointer, will fill in
the 'what' value of the next event.  If there are no events in the queue NextEvent() returns B_INFINITE_TIMEOUT for the time and B_NO_EVENT for the event type.

The real advantage of this class is that it simplifies the main loop of a node.  A main loop with an event queue now looks something like this in psuedo-code:

MyMediaNode::MainLoop()
{
  while (!timeToQuit) {
     while (noEventsInQueue || notTimeForNextEvent) {
       timedOut= WaitForMessages(untilNextEventInRealTime);
       if (timedOut) break;
     }
     PopEventFromQueue();
     HandleEvent();
  }
}

If the queue does not have any events, or if it is not time for the next event the node loops over the WaitForMessages() call until it is time for the next event.


status_t
MyMediaNode::WaitForMessages(bigtime_t waitUntil)
{
  status_t err = read_port_etc(port, code, message,
     message_size, B_ABSOLUTE_TIMEOUT, waitUntil);
   if (err = B_TIMED_OUT)
     return err;
   /* handle the incoming message */
   HandleMessage(code, message, message_size);
   return B_OK;
}

WaitForMessages() serivices the control loop and handles all messages that come in.  After a message arrives, something is usually added to the queue, and the next event time is evaluated.

When it is finally time to handle an event, the first event is popped off the queue and HandleEvent() is called.

This is a much easier to comprehend main loop, and as such should be easier to get right.

I have packaged up the BTimedEventQueue class and a sample node that uses it.  The ChromeVideo node is derieved from a first pass at a filter node that uses main loop similar to the one above.  It is a simple video filter that takes 32 bit color video and outputs somewhat freaky 8 bit gray video (using an algorithm that takes the red value of a pixel and bitshifts it down by 3 and puts the resulting value in the outgoing buffer.)  The ChromeVideo and the underlying BBufferFilter are works in progress and do not yet do everything they should.  The purpose for distibuting them now is so that you can see the event queue in action.

You can find ChromeVideo at:

<ftp://ftp.be.com/pub/samples/media_kit/ChromeVideo.zip>
Please be sure and read the ReadMe to understand the contents of the package.

To actually see the class in action you need to have a supported video card and the corresponding capture and display nodes.  You can find a alpha version of the Bt848 nodes and supporting application for R4 at Steve Sakoman's site:

<http://www.sakoman.com/>

These nodes are part of the upcoming 4.1 upgrade.


This has been the first look at how we are hoping to simplify the writing of Media Nodes.  If you want to know more, register for the BeDC: Programming with the Media Kit at :

<http://www.be.com/developers/developer_conference/BeDC_info.html>

See you there!