rom the earliest days of the BREW platform, its designers at Qualcomm have paid close attention to the demands of multimedia presentation. Early versions of BREW supported animated image display through the proprietary BREW Compressed Image (BCI) format as well as sound playback through the ISound, ISoundPlayer, and IRinger interfaces. With the advent of BREW 2.0 a few years ago, multimedia presentation in C and C++ has been streamlined through a single interface?the IMedia interface?which provides a single interface to audio and visual (still and video) multimedia. Imedia consists of an abstract interface (IMedia) and subclasses implementing codecs for popular standards including MIDI, MP3, AAC, 3GPP video, and JPEG, along with a helper class responsible for selecting the appropriate subclass. This makes IMedia the media presentation interface of choice for both existing and new BREW applications.
Understanding BREW’s Multimedia Architecture
At first blush, especially if you’re new to BREW, the IMedia interface and media playback architecture can seem overwhelming. However, it’s really quite simple, being comprised of only three key parts:
- The abstract BREW IMedia interface is responsible for managing media playback. It treats playback as an asynchronous activity, in which the controlling application sets various properties for playback (such as volume and display rectangle), invokes key operations to control playback (such as starting and stopping media presentation), and receives messages from the IMedia interface periodically through a callback function.
- Subclasses of the IMedia interface implement specific codecs for playing back a single media data type, such as MP3, AAC, and MIDI audio, 3GPP video, or BMP or JPEG images. These interfaces can be created in the usual way via ISHELL_CreateInstance with the class id of a specific media player, such as AEECLSID_MEDIAMP3.
- The BREW IMediaUtil interface implements a factory for obtaining a specific IMedia subclass given the data to present.
Using the IMedia interface is far simpler than you might think in just reading the documentation:
- Given the source data to play, create an appropriate subclass of IMedia to present the data.
- Set the IMedia instance’s callback so that the instance can pass information about the media back to your application using IMEDIA_RegisterNotify.
- Configure any playback options (such as default volume or screen rectangle for drawing).
- Begin and control media playback, using handset or programmatic events (key presses, game actions, or so forth) to trigger IMedia methods such as IMEDIA_Play, IMEDIA_Pause, IMEDIA_Resume, and IMEDIA_Stop.
- Monitor the values sent to your application callback for information such as errors or the termination of playback and handle those notifications appropriately (for example, restarting playback if your application is looping audio or video).
- When playback is complete, release the IMedia instance and any other resources you’ve consumed.
While straightforward, it’s instructive to actually see the IMedia methods in sample code to help fit the pieces of the puzzle together.
Creating and Configuring the IMedia Interface
Before anything else can happen, you must first have an instance to an IMedia subclass for your data. While in some applications you might know ahead of time which IMedia class you must create, it’s often just easiest to use the IMediaUtil class to do the work of content type recognition, IMedia instance creation, and passing your multimedia data to the newly created IMedia instance:
#include “AEEMedia.h”MediaUtil *pMediaUtil;IMedia *pIMediaObject;AEEMediaData sMediaData;char szFilename[10] = "TestMedia.mp3";ISHELL_CreateInstance(pIShell, AEECLSID_MEDIAUTIL, (void **)&pMediaUtil);if(!pMediaUtil){ //Error....}sMediaData.clsData = MMD_FILE_NAME;sMediaData.pData = (void *)& szFilename;sMediaData.dwSize = 0;IMEDIAUTIL_CreateMedia(pMediaUtil, &sMediaData, &pIMediaObject);if(!pIMediaObject){ //Error....}IMEDIAUTIL_Release(pMediaUtil);IMEDIA_RegisterNotify(pIMediaObject, (*PFNMEDIANOTIFY)IMediaEventHandler, (void *) pMe);IMEDIA_GetTotalTime(pIMediaObject);
Up first are object definitions. The variables pMediaUtil and pIMediaObject are the two classes I create through BREW’s ISHELL_CreateInstance interface. Next is sMediaData, the structure used to obtain the correct subclass of the IMedia object. Finally, there is szFilename, the name of the media to play.
First, create an instance of IMediaUtil using ISHELL_CreateInstance, checking to ensure the creation succeeds. Once you have your MediaUtil object, you need to build the AEEMediaData structure that describes your media and its source. While IMedia and IMediaUtil can take media data from a file, memory region, or an ISource, using the file system (via the specifier MMD_FILE_NAME) has been the most reliable method on BREW handsets to date. Of course, you can use the method that best fits your design, but don’t expect all three to work on every phone.
Now that you’ve filled in the MediaData structure, you can pass that information into a IMEDIAUTIL_CreateMedia call. In turn, the IMediaUtil allocates the correct media object and fills that data into the pIMediaObject pointer. In addition, by using CreateMedia, you’ve not only created the correct IMedia subclass, but you’ve also started the process of loading the media data into the seleted codec.
After creating your IMedia object, you can release the MediaUtil.
Finally, because nearly all of IMedia’s function API’s are asynchronous, you need to give your IMedia object a way to communicate with your application. The IMEDIA_RegisterNotify provides the IMedia object with a function to call passing status updates.
An example of an IMedia method that returns its value through the callback is IMEDIA_GetTotalTime: it won’t respond right away with the length of the file, but it will call your callback as soon as the media has been loaded.
As you might imagine, the callback you register can get pretty complicated. In sample code it’s reasonable to have business logic for your application in the callback, but, for a production application, it’s often clearer to restrict the callback to a switch/case statement that calls appropriate routines for each notification. The sample code looks like this:
void IMediaEventHandler(void *pUser, AEEMediaCmdNotify *pCmdNotify){ pApp *pMe = (pApp*)pUser; AEERect sRect; switch(pCmdNotify->nCmd) { case MM_CMD_GETTOTALTIME: if(pCmdNotify->nStatus == MM_STATUS_DONE) { pMe->dwTotalMediaTime = (int)pCmdNotify->pData; if( SUCCESS != IMEDIA_Play(pCmdNotify->pIMedia) ) { //Set a timer to attempt the play later } } break; case MM_CMD_PLAY: switch(pCmdNotify->nStatus) { case MM_STATUS_START: //Change UI to reflect play status break; case MM_STATUS_DONE: //Change UI to reflect stop status break; case MM_STATUS_TICK_UPDATE: //Advance your time counter here break; } break; }}
This is about the simplest IMedia callback function you might encouter. It begins by reclaiming the application’s state pointer from the incoming voice pointer, and then declares a rectangle that you can use later.
As was just noted, IMEDIA_GetTotalTime returns its response asynchronously through the callback: it calls the IMediaEventHandler asynchronously. As with all notifications, the incoming AEEMediaCmdNotify includes both the command generating the notification and any arguments; you simply decode this using a switch statement and take appropriate action. For example, when pCmdNotify->nCmd is MM_CMD_GETTOTALTIME, you know both that your media has been loaded and pCmdNotify->pData will be the length of that media in seconds.
Although successful IMedia invocations result in your callback being called, failed ones do not. It’s important to remember that anytime you call an IMedia method, always check the return value. At any point, the IMedia object can return EBADSTATE or EITEMBUSY (even in response to the most basic function calls). Be prepared for this by being ready to repeat any command at a later time when the media object isn’t busy or in a bad state.
This callback function also monitors the status of the MM_CMD_PLAY command over time to keep the UI up do date on the media object’s progress. This is a good way to trigger animations when playing audio, updating a status bar, and so forth.
Controlling the IMedia Interface
With the IMedia interface created and the media loaded, it’s time to begin actually using the media interface. Most operations on IMedia directly impact playback, such as starting and stopping playback, although that’s not always the case. For example, if you’re playing video, you might want to grab frames of the video directly. As you might imagine, this is an asynchronous event. To do this, you might add the following to your IMedia callback:
void IMediaEventHandler(void *pUser, AEEMediaCmdNotify *pCmdNotify){ pApp *pMe = (pApp*)pUser; IBitmap *pFrame; if( pCmdNotify->nStatus == MM_MOVIE_LOADED ) { IMEDIA_EnableFrameCallback(pCmdNotify->pIMedia, TRUE); IMEDIA_Play(pCmdNotify->pIMedia); return; } if(pCmdNotify->nCmd == MM_CMD_PLAY) { switch(pCmdNotify->nStatus) { case MM_PARM_FRAME: IMEDIA_GetFrame(pCmdNotify->pIMedia, &pFrame); if(!pFrame) { //Error return; } //.....Draw the IBitmap to the screen IBITMAP_Release(pFrame); break; } }}
As you can see, the code for getting a frame from a movie is fairly simple, and resides almost entirely in the IMedia callback.
Unfortunately, the status code returned by BREW when media loading has completed prior to fetching frames isn’t defined in all versions of the SDK, so you begin by defining an undocumented status code that is sent to the event handler when the movie is loaded.
Author’s Note: On most phones I’ve worked with, the code is 0x105, but your mileage may vary; be prepared to output all the status commands to the BREW logger during a movie load and playback and watch for hex values greater than 100. |
Next, use the same basic outline for a callback handler showed in the previous example. First, reclaim the app pointer and declare a bitmap pointer to contain the resulting frame. Next, look for media loaded value to indicate that the media has been loaded by the IMedia interface. When it arrives, you’ll know that the movie is buffered and ready in the IMedia object. Then, to get the individual frames from a movie, you’ll need to enable the frame callback using IMEDIA_EnableFrameCallback. With frame-playback enabled, call play and wait for the frames to start coming in.
When the frame is ready, you receive a Play command with the status equal to MM_PARM_FRAME. During that callback it’s safe to call IMEDIA_GetFrame, which allocates and returns an IBitmap object. Next, simply draw the Bitmap to the screen and exit. Of course, you can always simply fetch the first frame, draw the first frame and pause, or perform other transformations such as scaling on the resulting bitmap.
Of course, playing movies does not require using the frame callback. In fact, once the movie is loaded, you can call IMEDIA_SetRect to set a destination rectangle for the movie and IMEDIA_Play, which will render each frame within the rectangle you specify; the IMedia object takes care of the rest.
Most playback control is typically driven by a combination of user events (the user triggers a playback pause by pressing a key, for example) and programmatic events (a game might loop a soundtrack during the help screen). Consequently, the logic behind how to control media playback is domain-dependent, but the IMedia interfaces you use aren’t. For example, an audio or video player might have the following code in the application’s event handler when playback is in process:
#define JUMP_TIME 500switch( wParam ){case AVK_RIGHT: IMEDIA_Seek( pThis->pIMedia, MM_SEEK_CURRENT , JUMP_TIME); break;case AVK_LEFT: IMEDIA_Seek(pThis->pIMedia, MMS_SEEK_CURRENT, -(JUMP_TIME)); break;case AVK_SELECT: if (pThis->bPlaying ) { pThis->bPlaying = FALSE; IMEDIA_Pause( pIMedia ); } else { pThis->bPlaying = TRUE; IMEDIA_Resume( pIMedia ); } break;}
Of course, this code should only be executed with an active media player during playback. It simply maps the left and right keys to skipping 500 ms behind or ahead in the media, and the select key to toggling playback between pause and playing states.
One thing to remember when using Imedia: many codecs won’t behave well if you attempt to play media before it’s fully loaded, an asynchronous operation. Always wait for either a keypress to start playback with IMEDIA_Play, or look for a media loaded event in your callback–or at least attempt to begin playback asynchronously using ISHELL_Resume and a callback function, verifying the result code from IMEDIA_Play. Otherwise, playback may not start on some handsets.
Cleaning Up After IMedia
Because IMedia is just a BREW interface like any other, cleanup is simply a matter of unregistering your callback function and releasing the resources used by IMedia:
IMEDIA_RegisterNotify(pThis->pIMedia, NULL, NULL);IMEDIA_Release(pThis->pIMedia);
While probably not strictly necessary, it’s always a good idea to clear your callback function before releasing your IMedia object.
While on the topic of cleaning up after IMedia, I’ve found that trying to reuse the same IMedia interface across different pieces of media data can cause unpredictable results. Some handsets appear to get confused and play the same data regardless of the source data passed to an IMedia interface. Because creating a new IMedia interface is relatively inexpensive, I recommend always releasing an IMedia interface and obtaining a new one if you want to render a different multimedia file.
Handling Suspend and Resume Events
About the only other thing you must worry about when working with IMedia is what to do with BREW’s suspend and resume events. At the very least, you should pause playback of media on receiving a suspend event using IMEDIA_Pause, and resume playback using IMEDIA_Resume. This will keep the media codec from using memory, CPU, and potentially both audio and video hardware while your application is suspended, which you must do if you want to pass True BREW certification. If it’s appropriate, it’s definitely worth considering releasing the IMedia interface on suspend and creating a new one on resume, because you can offer the underlying system more resources that way.
It’s So Easy!
Although the documentation for IMedia can look overwhelming to the developer new to BREW, it’s not difficult, as you now know. Using IMedia on BREW is easy: simply create an instance of IMedia using IMediaUtil, set a callback, and invoke IMEDIA_Play.