Table of Contents

Reggae Basic Concepts

BOOPSI and its Extensions in Reggae

Reggae system is object oriented, so it must use some OOP (Object Oriented Programming) skeleton. It uses BOOPSI (Basic Object Oriented Programming System for Intuition), commonly used in MorphOS. A programmer is accustomed to a situation, where programming language used (like C++ or Java) delivers OOP support (classes, objects). With BOOPSI it is operating system delivering OOP services. The advantage of this is BOOPSI independency of programming language (it can be used in assembler even). The disadvantage is difficulty of cooperation between BOOPSI and object oriented programming languages (can be done however). Another advantage of using BOOPSI is that MUI (MorphOS default object oriented GUI library) uses it as well.

Reggae objects are just BOOPSI objects, they all are indirect subclasses of rootclass. There is no special creation or disposition functions, media objects use ordinary OM_NEW() and OM_DISPOSE() methods, or their usual wrappers – NewObject() and DisposeObject() from intuition.library. The same for setting and getting attributes, there is no special methods other than OM_SET() and OM_GET(), and a programmer can use GetAttr() and SetAttrs() as usual. The only additional thing Reggae objects have are ports. A port is identified by its object address and a port number. Every media object processes some data formed into stream. The stream comes to the object input port, data from stream are pulled, processed and then passed to the output port. Object can have more than one input and more than one output. Then a few objects may be used to form a processing pipeline (or rather processing tree, as it can be branched). A port has an ability to connect to a port of other object, input to output or output to input. Port connections form the structure of the processing tree. How is it done? Every port is represented by a record (C structure to be exact) in the object instance. This record contains identifcation of port connected to the represented one (the identification is object address and port number). It is explained on a figure below.


Three Reggae objects connected with ports.

Objects are not connected by hand, but with MediaConnectTagList() function available in the library API of multimedia.class. This function just connects two filter ports together.

BOOL MediaConnectTagList(Object *obj1, LONG port1, Object *obj2, LONG port2, struct TagItem *taglist);

The function returns TRUE if the connection succeeds. It may fail if you try to connect two outputs or two inputs together, or data formats of ports do not match. Taglist will be used in the future to control format matching, no tags are defined for now.

The main use of ports is datastream transport. Reggae design is pull-driven. It means data are actively pulled from output ports. MMM_Pull() method requires three parameters: number of port to pull data from, a buffer where pulled data will be stored, and data length in bytes. An object receiving pull request on a port, calculates first how much of input data is needed to satisfy pull request. It allocates buffer for input data and call MMM_Pull() on its input. How it is possible to pull from input? It is done for simplifying media objects implementation. Pull on input is converted on the fly by multimedia.class into pull from connected output. This way an object need not to check what hangs on its input, the object just pull from it. Then it process the data, processing result is placed into buffer passed, then buffer allocated for input data is freed, and method is finished. If many objects are connected into processing pipeline, pull on its end generates a "pull wave" propagating to the first object (it is usually a stream object – source of data), then the wave "reflects" and comes back with data processed by every pipeline stage. Let's look at some real life example – a streaming MP3 player.


Example pipeline of Reggae network radio application.

The audio.output object, as the last one in the pipeline, acts as a data "pump" and controls data flow. It just plays sound data through the audio subsystem of MorphOS, and issues a MMM_Pull() request to tone.filter object every time play buffer gets exhausted (of course audio.output uses doublebuffering, so playback is not interrupted). Let's assume play buffer has 16 384 bytes. The tone.filter object receives a pull request of 16 384 bytes. As it is just a set of low- high- and bandpass filters it producess exactly the same number of sound samples as it pulls from input. Knowing that it issues 16 384 bytes pull request to mpegaudio.decoder. Every MP3 frame decodes to 1 152 samples. Let's assume we have stereo 16-bit sound, so every frame decoded yelds 4 608 bytes. Then mpegaudio.decoder need to pull 4 MPEG frames, and some decoded samples will be left in its internal buffer. The mpegaudio.demuxer object then is bothered with MMM_Pull() of 4 frames (byte size depends on bitrate). As it does not process the frames (demuxers usually only process headers, in case of MP3 it will be Xing VBR tag and whatever else), it just pass the same byte size of request to http.stream. Note that no data have been processed yet, only pull request propagated through the pipeline. Now http.stream object fetches requested amount of data from the network to a buffer prepared by mpegaudio.demuxer object (or rather mpegaudio.decoder, as demuxer does not process the data, it can pass decoder buffer directly avoiding unnecessary copying). Then mpegaudio.decoder receives requested frames, decodes to a buffer allocated by tone.filter, then frees its own buffer (used by demuxer and stream). The tone.filter object performs filtering and places filtered data in audio.output buffer. At last pull request comes back to audio.output object.

Note: In fact tone.filter class does not exist, it is only an example. Also for radio streams shoutcast.demuxer object may be necessary, to extract (and remove) metadata interleaved with MPEG stream.

Ports and their Attributes

Every port has a set of attributes, all of them can be set and get. Usually an application need not to mess with them directly, port attributes are used internally by multimedia.class. They are listed here however just for completness:

Getting and Setting Attributes Through Ports

Ports and connections between them determine processing structure built of media objects. Processed data goes through processing paths defined by connected ports. The data usually require accompanying sideband information to be passed with. It can be done with OM_GET and OM_SET methods, but then must be actively supported by application. That is why Reggae provides dedicated methods for sideband informations, those can be sent parallelly to data in a standarized and automated way.

Two methods MMM_GetPort() and MMM_SetPort() may be used by applications to send and retrieve sideband info. There are also two helper methods for classes implementors: MMM_GetPortFwd() and MMM_SetPortFwd() described later.

How it Works

An application can issue MMM_GetPort() or MMM_SetPort() method on any port of any Reggae object. Then this object performs three stages of processing this request. Stage A is applying passed attribute to itself. An object may "understand" an attribute or not. If it knows the attribute, applies it to itself (if set) or returns the value (if get). If the attribute has no meaning to the object, stage B is performed. It is a simple step, method is just passed to the superclass. This way port attributes are handled (see above). If superclass has not recognized the attribute as a port attribute, it performs stage C, named forwarding stage (it is performed automatically by multimedia.class, so neither application programmers nor class implementors need to care about it). In this stage object forwards the request from the port of arrival to other ports. To which ports exactly it is forwarded? It depends on object ports. The rule is simple: a method on a input is forwarded to all outputs (if any outputs exist) and vice versa – a method on an output is forwarded to all inputs (if any inputs exist). This way sideband information flow is uniform: always along with data, or always backward to data, no looping is possible (unless the data flow is looped, but this is forbidden). Below some typical examples of forwarding are described:

MMM_GetPort

This method is used to query a port (or associated data stream) for an attribute. The syntax resembles well known OM_GET(). MMM_GetPort() uses following structure as the method message:

struct mmopGetPort
{
  ULONG MethodID;    // MMM_GetPort
  LONG Port;         // port number
  ULONG Attribute;   // attribute to get
  LONG *Storage;     // pointer to variable where attr value will be stored
};

Example of use:

LONG value;

success = DoMethod(obj, MMM_GetPort, port, attr, &value);

Attribute value is not returned by the method, but stored at a given place (the same way as OM_GET() does). There is a macro defined, which returns the value directly, for programmer convenience. Note however, you are loosing MMM_GetPort() return value this way. This value is TRUE when the attrribute has been recognized, FALSE otherwise (exactly like in OM_GET()).

value = MediaGetPort(obj, port, attr);

Note that in spite of 'Storage' defined as a pointer to 32-bit int, bigger numbers, or even data structures can be passed. Especially 64-bit values are useful, just pointer to QUAD is passed instead of pointer to LONG. Another macro can be used for getting 64-bit attributes:

QUAD value;

value = MediaGetPort64(obj, port, attr);

Of course using this macro makes sense only for attributes documented as being 64-bit. No port attributes are, but some data stream attributes are 64-bit.

MMM_SetPort

This method is used for setting port or data stream attributes. Unlike well known OM_SET(), attribute value is passed by pointer, not directly. It allows for passing 64-bit numbers, as well as bigger entities. MMM_SetPort() uses following message structure:
struct mmopSetPort
{
  ULONG MethodID;
  LONG Port;
  ULONG Attribute;
  LONG *Value;
};

Example of use:

success = DoMethod(obj, MMM_SetPort, port, attr, &val);

Return value of the method is TRUE if the attribute was recognized and set succesfully. Attribute value is passed indirectly (by pointer), it is the same as in MMM_GetPort(), but different to OM_SET(), where 32-bit value is passed directly. Two macros taking direct 32 or 64-bit value are defined for programmer convenience:

LONG val32;
QUAD val64;

MediaSetPort(obj, port, attr, val32);
MediaSetPort64(obj, port, attr, val64);

MMM_GetPortFwd and MMM_SetPortFwd

While implementing a Reggae class, a programmer can encounter some problems with MMM_GetPort() and MMM_SetPort() called from inside of the class code. One can expect that calling one of those methods on an object input will be forwarded to the object connected to this input, it means the previous one in the pipeline. This is not the case however, because (as described earlier) the method will be forwarded from object input to its output and later to the next object in the pipeline instead of the previous one. The result is opposite to programmer expectations. Design flaw? Not really. The problem is object cannot know if the method comes to a port from the "outside world", or if just the object asks its own port. To resolve this problem, two special methods MMM_GetPortFwd() and MMM_SetPortFwd() are implemented. Those use the same message structures as their counterparts MMM_GetPort() and MMM_SetPort(). The difference is in forwarding. Forwarded methods aren't called on the passed object at all. They instead perform their non-forwarding counterparts on port (of different object) connected to the one specified in method parameters. The picture below illustrates the difference between ordinary and forwarded set and get.


Difference between plain and forwarding version of MMM_GetPort()/MMM_SetPort().

Non-forwarding set/get as already described before, will try to apply the attribute to the object itself, then superclass will try to apply the attribute to the port, and at last method will be forwarded. Forwarding set/get omits two first stages, but instead calls non-forwarding method on a port connected to the one passed with the call.

MMM_Pull

As the name says, this method is used to pull data from processing pipeline to a buffer. It uses following method message:

struct mmopPull
{
  ULONG MethodID;  // MMM_Pull
  LONG Port;       // Port to pull from
  APTR  Buffer;    // Buffer allocated by application, where processed data will be placed
  LONG Length;     // How many bytes to pull
};

The specified buffer should be allocated with MediaAllocVec() function of multimedia.class library API. After use free it with MediaFreeVec(). These functions ensure proper buffer alignment for AltiVec machines and also provide memory tracking. If the method is succesfull it returns the number of bytes pulled. In case of error MMA_ErrorCode atribute is set. Here is an example of use:

bytes_pulled = DoMethod(obj, MMM_Pull, port, buffer, len);

MMM_Seek

Seeking a media stream (be it audio or video) is an obvious functionality with not that obvious implementation. Reggae system tries to support seeking in every stream where it is technically possible. There are some limitations however. Certain data streams (sources) are not seekable, it means they can be only read sequentially. In such case only forward seeking is possible, which is simulated by reads. This simulated forward seek can take some time because of limited reading speed. It can be especially a problem with network streams on low bandwidth connection. For random access streams like disk file or memory there is no such limitation. Seek position can be specified in bytes, frames (audio or video) or time (in microseconds). This position is always absolute, referenced to the start of stream, or, if the stream is continuous (like network radio one), the first frame received. MMM_Seek() method uses following message structure:

struct mmopSeek
{
  ULONG MethodID;
  LONG Port;
  LONG Type;
  QUAD *Position;
};

Seek is only possible on output ports. Seek type is one of the following:

Allowed seek types depend on the data format on a port. Reggae decoded (common) formats allow only for time and frame seek. Encoded and raw stream formats allow only for byte seek. See documentation of classes for details. MMM_Seek() method returns value of success (TRUE or FALSE). As seek position is specified as 64-bit number, an auxiliary variable is usually needed:

QUAD seekpos = 30000;

DoMethod(obj, MMM_Seek, 1, MMM_SEEK_FRAMES, &seekpos);
Passing a number directly as the position is a common mistake. It does not work obviously.
DoMethod(obj, MMM_Seek, 1, MMM_SEEK_FRAMES, 30000);    // THIS IS WRONG!