Reggae tutorial: Accessing Reggae in applications

From MorphOS Library

Revision as of 15:34, 26 April 2022 by Polluks (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Reggae is operated from application with two APIs. One of them is generic object oriented BOOPSI API with its DoMethod(), GetAttr(), SetAttrs() etc. The second API is just a shared MorphOS library one, provided by multimedia.class and is, of course, Reggae specific. To use Reggae one must open multimedia.class as every shared library. For BOOPSI API intuition.library must be opened, which almost all programs do anyway.

Opening and closing multimedia.class

The first step is to add needed includes:


#include <proto/multimedia.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <clib/alib_protos.h>


The first file contains definition of multimedia.class library API. It also includes <classes/multimedia/multimedia.h>, containing Reggae structures, constants, tags and macros. The rest of includes are not Reggae specific, in fact most projects include them anyway, as they define basic system services. There are also additional Reggae headers, their including depends on application and will be covered in further tutorials.

Now we are ready for Reggae initialization. All what has to be done is opening multimedia.class just like an ordinary MorphOS shared library. The only noticeable difference is that library name contains path part, as Reggae classes are not directly on library search path:


struct Library* MultimediaBase;

if (MultimediaBase = OpenLibrary("multimedia/multimedia.class", 52))
{
  /* Now Reggae is ready to use until the library is closed. */
  CloseLibrary(MultimediaBase);
}


As with every library, the name of the base is important, as it is implicitly used in all calls to the library API as a hidden parameter. Note also the usual error handling, multimedia.class is disk-based and also performs some disk activity at startup, then checking the library base against NULL is recommended. 52 is the current multimedia.class version. Applications should request this version, as previous ones have some important features missing. Reggae cleanup is done with classic CloseLibrary() call.

Note: Some programmers use automatic library opening and closing by linking with libauto. multimedia.class can be added to the list of automatically opened libraries, but it is not added by default.

A complete example code shows Reggae initialization. The example opens multimedia.class and, if it succeeded, prints the version and revision of multimedia.class to the console.

Opening and closing individual classes

Typical Reggae usage is just calling MediaNewObject() to get a data stream recognized, demultiplexed and decoded down to common formats automatically. In this case Reggae opens (and later closes) all needed classes automatically. Things change, when application builds processing chain by hand, or just uses single components. Then every used class must be opened and closed explicitly. It is done in the same way as with multimedia.class. Let's assume our application uses just http.stream object to download some data from the net. Before object creation class must be opened. It is typically done in application setup code:


struct Library *HttpStreamBase;

HttpStreamBase = OpenLibrary("multimedia/http.stream", 51);


As for every disk based library, checking the base against NULL is highly recommended. The class must be unloaded after use, for example in application cleanup:


CloseLibrary(HttpStreamBase);


Reggae classes are handled just like ordinary shared libraries. Note that "multimedia/" path is added to the class name, reason for this has been explained above. When a class is not used constantly in application, it may be a good idea to open it just before it is needed and close right after it is not needed anymore. For example if http.stream is used only for automatic application updates, there is no need to keep it opened after update checking/downloading. The last remark concerns names of library bases for classes. No Reggae class except of multimedia.class has library-style API. Because of this library base name is not used for anything else than opening and closing the class. That is why, the name is not important and can be anything. In these tutorials, bases names derived from classes names are used, just for improved code readability.

I don't like C/C++, what now?

Reggae can be used from any programming language. It has to fulfill some minimal set of features however. The first one is ability to call functions from standard MorphOS shared libraries, intuition.library and exec.library at least. Then ability to call functions from multimedia.class. Many programming languages used on MorphOS have tools for creating bindings (or stubs, or whatever it is called) to shared libraries, based on C headers and *.fd files. Creating bindings for multimedia.class should not be difficult then. Then the language must have DoMethod() call. For C/C++ this call is provided by libabox, a static library. Another important feature is ability to handle tag-based functions in some sane way. Taglist can be always built by hand, but passing tags as arguments to variable args functions is convenient.

NewObject() and MediaNewObject()

These two functions have similar names, but they are different. Programmers using MUI may think that MediaNewObject() is some kind of wrapper around NewObject(), similarly to MUI_NewObject() being such a wrapper. This is not true for MediaNewObject() however. NewObject() is a basic BOOPSI call, and just creates an object of specified class. Reggae objects are created with this function as well.

MediaNewObject() creates a complete tree of objects needed to decode a media stream to basic formats. The tree consists of stream object, one or more demultiplexer objects, one or more decoder objects and a few auxiliary ones like multiread buffers or processblocks. The whole tree is presented to the application as a single object delivering decoded streams on its output ports.

Another difference between the two functions is automatic loading of Reggae classes. NewObject() call does not load a disk based BOOPSI class into memory automatically. Then an application must load such a class manually with OpenLibrary() before object creation (and unload it after use with CloseLibrary()). On the other hand MediaNewObject() opens all needed classes automatically and takes care of their unloading.

Error Checking

Many operations on Reggae objects may fail for different reasons. It may be just memory shortage, I/O error, wrong arguments passed by application etc. A good programming practice is to verify results of operations and take appropriate actions in case of failure. The first step of verification is checking method return value. Its exact meaning depends on the method itself, and is described in autodocs. The common cases are described below. The second stage of error checking is to get an error code of operation. This is done by getting a MMA_ErrorCode attribute value from an object. Error code values are defined in <classes/multimedia/multimedia.h> header file. The usual way is just getting the attribute from an object, which is done with BOOPSI call GetAttr().

LONG error;

GetAttr(object, MMA_ErrorCode, (ULONG)&error);

Alternatively, one can get the attribute through any of object's port (input or output, it does not matter):

error = MediaGetPort(object, 0, MMA_ErrorCode);

Errors During Object Creation

Creation of an object is a special case in error handling for two reasons. Firstly, this is the most probable place for errors, as creating a new Reggae object may be a complex operation involving creation of many basic objects, connecting them, performing I/O operations on data source, parsing stream headers etc. Secondly – if object creation fails, there is no object to ask for MMA_ErrorCode attribute. That is why MMA_ErrorCode can be passed to OM_NEW() method (that means NewObject() and MediaNewObject() accept it), and its tag value contains a pointer to a variable, where error code will be placed. An example code below shows error handling during object creation:

Object *obj;
LONG error = 0; /* initialization is recommended */

if (obj = MediaNewObjectTags( /* any tags here */ , MMA_ErrorCode, (IPTR)&error, TAG_END))
{
 /* use object */
}
else
{
 /* error code is in 'error' variable, take some action, inform user etc. */
}

Errors During Data Pulling

The first stage of error checking in this case is checking the return value of MMM_Pull() method. When there is no error, it should be equal to the amount of bytes requested. If it is not the case, one should get MMA_ErrorCode value, as described above. There is one case however, when the value returned by MMM_Pull() will be not equal to the request size, and MMA_ErrorCode will be zero. It can happen, when the request size is not aligned to the smallest chunk allowed by Reggae. This chunk is a single pixel for video, and a single sample frame for audio. Such a misaligned pull request is truncated silently and only the whole chunks are returned. That is why aligning request size to the size of a pixel or sample frame should be a rule of thumb while programming Reggae. A code fragment below demonstrates error checking after MMM_Pull():

if ((pulled = DoMethod(MMM_Pull, object, port, buffer, length)) == length)
{
 /* Data pull has been succesfull. */
}
else
{
 LONG error = 0;

 GetAttr(object, MMA_ErrorCode, (IPTR)&error);

 /* Data pull failed, some data can be valid however (number of valid
    data bytes is stored in 'pulled'). Error code is in 'error'. */
}

Displaying Error Messages

Some of Reggae errors (especially those, which are somehow expected, like file or network I/O errors) should be reported to user. Note that for less probable errors advanced users may use MediaLogger, which shows Reggae event log. Basic problems however should be reported by application. Of course displaying some cryptic error code is not recommended. The simplest way is to use MediaFault() function to convert these codes into text messages. A big advantage of MediaFault() is that its messages are localized, so it automatically returns texts in user's preferred language. The function may be for example used as follows:

LONG error;

GetAttr(object, MMA_ErrorCode, (IPTR)&error);

if (error) Printf("Operation failed: %s.\n", MediaFault(error));

This simple example prints message to a console, GUI based programs will pass it to MUI_Request() for example. Strings returned by MediaFault() are not capitalized and have no dot at the end.