http://www.developer.com/

Back to article

Video Playbacks on Windows Mobile 5.0: A Guide for Beginners


March 15, 2006

Video on Windows Mobile 5.0

Windows Mobile 5.0 has a lot of new features, both exciting and disappointing. Here, I will discuss its bright side in regard to its multimedia support. Prior to WM 5.0 you have the only option: Windows Medial Player control. You saw it in one of my previous articles. Well, you always had a nice choice to develop your own player from scratch, which is, believe me, a very interesting but quite complicated task; or, you could use third-party libraries or components; for example, TCPMP or PocketTV.

Since WM 5.0 went live, the situation has gotten much better. WMP wasn't bad or useless, but you couldn't play AVI or MPEG files. The new SDK gives you more ways to display video playbacks on a mobile device. Frankly, you might find similar support even on Windows CE.NET 4.2 with some devices—for example, the Fujitsu iPAD—but it really wasn't common practice.

So, what does WM 5.0 offer you in the multimedia arena? The answer is a set of DirectShow interfaces. They enable you to make relatively high quality video playbacks and capture multimedia streams. Theoretically, DirectShow for WM 5.0 provides support for AVI and MPEG-1 along with previously supported WMV, WAV, and other formats. The real situation may be slightly different. For example, if a handheld device has no video camera, you can be quite sure that MPEG-1 files won't be played. As usual, the actual media format support depends on the device vendor. The next thing to note is that recently DirectShow for WM 5.0 is available only in native code.

DirectShow contains many different interfaces. It provides much more control and flexibility than the WMP control. Here, you will focus on how to display video playbacks, hence putting aside such topics as developing various converters and so forth.

As you can learn from the DirectShow documentation, the main building block there is a Filter. This is a COM component that performs all the work on the media stream for you. Filters can be used to read files, decode, and so forth, so this is the real "workhorse" of the DirectShow API.

Typical Application Structure

When utilizing the DirectShow API in your own application, in most cases you will follow some common steps, like these:

  • Create an IGraphBuilder instance
  • Query additional required interfaces through it; for example, IMediaControl, IMediaEvent, and IVideoWindow
  • Control everything by calling IGraphBuilder methods and responding to events
  • Clean up all COM objects

If you need a more complicated or advanced scenario, you may refer to the DirectShow SDK documentation for more details. You do need to remember that video rendering is performed in separate threads because DirectShow creates new threads for IGraphBuilder and all filters. Thus, those threads need to get enough time to run. If your application does some heavy processing at the same time, video playback will not be an acceptable quality.

Sample Application

Now, dig in into the coding. The first code snippet shows how to play a media file within given window:

#include <dshow.h>
CComPtr<IGraphBuilder>   pGraph;
CComPtr<IMediaControl>   pMediaControl;
CComPtr<IVideoWindow>    pVidWin;

void PlayMediaFile(HWND hwndOwner, LPCTSTR lpszFileName)
{
    HRESULT hr = S_OK;

    // Create the filter graph manager.
    hr = pGraph.CoCreateInstance(CLSID_FilterGraph);
    if ( FAILED(hr) )
    {
       // add some error handling here
       return;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl,
                                (void **)&pMediaControl);
    hr = pGraph->QueryInterface(IID_IVideoWindow,
                                (void **)&pVidWin);

    // Build the graph.
    hr = pGraph->RenderFile(lpszFileName, NULL);

    if ( SUCCEEDED(hr) )
    {
       // Set the video window.
       pVidWin->put_Owner((OAHWND)hwndOwner);
       pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

       RECT rc;
       GetClientRect(hwndOwner, &rc);
       pVidWin->SetWindowPosition(0, 0, rc.right, rc.bottom);

       // Run the graph.
       hr = pMediaControl->Run();
    }
    else
    {
    // add here some error processing
    }
}

As you can see, it strictly follows a typical scenario. First, an IGraphBuilder instance is created. If it was successful, the snippet queries the IMediaControl and IVideoWindow pointers. The next step is to build the required filter graph by calling the pGraph->Render() method. Usually, you would want to display a playback within some child window in your application, so an IVideoWindow instance allows you to set a parent for the video window. Finally, IMediaControl starts the actual playback processing.

Note: This operation is handled on separate thread, so you have to take care not to exit before the filter graph is processed.

The IGraphBuilder::Render() method may return an error, such as VFW_E_CANNOT_RENDER, in case problems occur. A full list of error codes can be found in the vfwmsgs.h header file. If rendering was successful, below you may see its result of playing the clock.avi taken from the desktop:

A somewhat more sophisticated scenario may listen for events. The previous sample becomes just a little bit more complicated:

#include <dshow.h>

CComPtr<IGraphBuilder>   pGraph;
CComPtr<IMediaControl>   pMediaControl;
CComPtr<IVideoWindow>    pVidWin;
CComPtr<IMediaEvent>     pMediaEvent;

void PlayMediaFile(HWND hwndOwner, LPCTSTR lpszFileName)
{
    HRESULT hr = S_OK;

    // Create the filter graph manager.
    hr = pGraph.CoCreateInstance(CLSID_FilterGraph);
    if ( FAILED(hr) )
    {
       // add some error handling here
       return;
    }

    hr = pGraph->QueryInterface(IID_IMediaControl,
                                (void **)&pMediaControl);
    hr = pGraph->QueryInterface(IID_IVideoWindow,
                                (void **)&pVidWin);
    hr = pGraph->QueryInterface(IID_IMediaEvent,
                                (void **)&pMediaEvent);

    // Build the graph.
    hr = pGraph->RenderFile(lpszFileName, NULL);

    if ( SUCCEEDED(hr) )
    {
       // Set the video window.
       pVidWin->put_Owner((OAHWND)hwndOwner);
       pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

       RECT rc;
       GetClientRect(hwndOwner, &rc);
       pVidWin->SetWindowPosition(0, 0, rc.right, rc.bottom);

       // Run the graph.
       hr = pMediaControl->Run();
       if ( SUCCEEDED(hr) )
       {
            long lEventCode = 0;
            pMediaEvent->WaitForCompletion(INFINITE,lEventCode);
       }
       else
       {
       // add some error processing here
       }
    }
    else
    {
    // add some error processing here
    }
}

The very next step is to receive filter graph notifications. To achive this, all you need to do is to modify the previous sample as:

// define notification message
#define WM_MEDIA_NOTIFY     WM_USER + 0x1000

// define a global variable or member of some class somewhere
CComPtr<IMediaEventEx> m_pMediaEventEx;
...
void PlayMediaFile(HWND hwndOwner, LPCTSTR lpszFileName)
{

    hr = pGraph->QueryInterface(IID_IMediaEventEx,
                                (void **)&m_pMediaEventEx);
    m_pMediaEventEx->SetNofityWindow((OAHWND)hWndOwner,
                                     WM_MEDIA_NOTIFY,0);
...
}

As a result, your hWndOwner window will get notified by a WM_MEDIA_NOTIFY message, so you can handle it as you want to; for example, as follows in WinProc:

#include <evcode.h>
...
    case WM_MEDIA_NOTIFY:
    {
        if ( m_pMediaEventEx )
        {
            long lEventCode = 0;
            long* pParam1 = NULL, pParam2 = NULL;
            HRESULT hr = S_OK;

            while ( SUCCEEDED(hr = m_pMediaEventEx->GetEvent
                              (&lEventCode,pParam1,pParam2)) )
            {
                m_pMediaEventEx->FreeEventParams(lEventCode,pParam1,
                                                 pParam2);
                switch (lEventCode)
                {
                case EC_COMPLETE:
                    // perform some action, e.g. as following
                    m_pMediaEventEx->Stop();
                    m_pMediaEventEx->WaitForCompletion(INFINITE,
                                                       lEventCode);
                    m_pMediaEventEx->Release();
                    m_pMediaEventEx = NULL;
                    ....
                    break;
                case EC_USERABORT:
                    // perform some action
                    break;
                ...
                }
            }
        }
    }
    break;

or using MFC,

ON_MESSAGE(WM_MEDIA_NOTIFY,OnMediaNotify)
...

Conclusion

The topics in this article give you enough "meat" to build your own simple player. All the surrounding things such as the GUI or error handling were left out to make the story as simple as possible. If I have a chance, I will discuss other streaming related topics—such as video capturing and so forth—in future articles.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date