sunlight

DirectShow and MPEG Layer III

Introduction

In this part of the tutorial, we will cover:

  • Playing MPEG Layer III-encoded audio files (MP3).

Previous View Code Download Code Next

DirectShow

DirectX Audio is designed for high-performance, low-overhead audio. Using small WAV files for sound effects and MIDI files for soundtracks results in the lowest possible overhead for your application, giving you more time to handle the rest.

However, it's not geared up for large digital audio soundtracks - MIDI is not always adequate (especially if the soundtrack was pre-recorded), and large WAV-format files are impractical. The MP3 (MPEG Layer III) format solves this problem, by packing large quantities of audio into a small space. MP3 is not useful for everything; playing MP3s involves much more overhead than MIDI or WAV files, making it unsuitable for sound effects. It is also significantly larger than MIDI.

MP3 is a non-trivial format, and so it is not supported directly by DirectX Audio. In order to support MP3, we must use DirectShow, a DirectX component used for complex audio and video needs.

DirectShow is based on the concept of filter graphs, which are several blocks of code that pass data between themselves. DirectShow filters include an MP3 decoder and a sound output block. 

Initialising DirectShow 

The first thing we need are four interfaces: IGraphBuilder (to build the DirectShow graph), IMediaControl (to handle play and stop controls), IMediaSeeking (to allow us to seek back and forth in the audio stream) and IMediaEventEx (to let us know when the playback has finished). All these interfaces are part of the FilterGraph object, so we need only create one and query for the others.

    // Create an IGraphBuilder object, through which 
    //  we will create a DirectShow graph.
    CoCreateInstance(CLSID_FilterGraph, NULL,
                                      CLSCTX_INPROC, IID_IGraphBuilder,
                                      (void **)&g_pGraphBuilder);
    // Get the IMediaControl Interface
    g_pGraphBuilder->QueryInterface(IID_IMediaControl,
                                 (void **)&g_pMediaControl);
    // Get the IMediaSeeking Interface
    g_pGraphBuilder->QueryInterface(IID_IMediaSeeking,
                                 (void **)&g_pMediaSeeking);
    // Get the IMediaEventEx Interface
    g_pGraphBuilder->QueryInterface(IID_IMediaEventEx,
                                 (void **)&g_pMediaEventEx);

Next, we tell DirectShow that any events it has to give us can be sent to the main window, with a message ID we specify.

    g_pMediaEventEx->SetNotifyWindow((OAHWND)hWndMain, WM_APP, 0);

Now we can load an MP3 into the filter graph. We do this by creating a source filter. Note that this is not specific to MP3s; we could use Windows Media format files here, AU, AIFF, MIDI, WAV or any other audio format supported by DirectShow. (Due to the extra overhead imposed by DirectShow, it is better to play MIDI and WAV files with DirectX Audio.)

IBaseFilter *LoadMP3(LPCWSTR wszFilename)
{
    IBaseFilter *pSource;
    HRESULT     h;

    // Add the new source filter to the graph.
    h = g_pGraphBuilder->AddSourceFilter(wszFilename, wszFilename, &pSource);
    if (FAILED(h))
        return NULL;

    return pSource;
}

Playing Filters

Playing this file is more complex. First, we must find the output pin of the filter. Audio sources generally have only one output pin.

    pSource->FindPin(L"Output", &pPin);   

Next, we stop the graph:

    g_pMediaControl->Stop();

At this point, we must consider what happens if a filter was currently running. If it was, we must break the filter connections. We can do this by removing and then adding each of the filters in the graph.

    g_pGraphBuilder->EnumFilters(&pFilterEnum);
    
    // Need to know how many filters. If we add/remove filters during the
    // enumeration we'll invalidate the enumerator
    for (iFiltCount = 0; pFilterEnum->Skip(1) == S_OK; iFiltCount++)
        ;

    // Allocate space, then pull out all of the 
    ppFilters = (IBaseFilter **)_alloca(sizeof(IBaseFilter *) * iFiltCount);

    pFilterEnum->Reset();

    for (iPos = 0; pFilterEnum->Next(1, &ppFilters[iPos], NULL) == S_OK; iPos++)
        ;
    
    pFilterEnum->Release();

    for (iPos = 0; iPos < iFiltCount; iPos++) 
    {
        g_pGraphBuilder->RemoveFilter(ppFilters[iPos]);
        
        // Put the filter back
        g_pGraphBuilder->AddFilter(ppFilters[iPos], NULL);

        ppFilters[iPos]->Release();
    }

Making the new filter connections is relatively simple: all we do is call Render.

    g_pGraphBuilder->Render(pPin);

    pPin->Release();

Now, we seek back to the start and run the graph again.

    LONGLONG llPos = 0;
    g_pMediaSeeking->SetPositions(&llPos, AM_SEEKING_AbsolutePositioning,
                                &llPos, AM_SEEKING_NoPositioning);

    g_pMediaControl->Run();

Stopping the graph is merely a case of calling

        g_pMediaControl->Stop();

We can replay a stopped MP3 by seeking back to the start and running, just as above.

Handling Events

DirectShow will only play the file back once. When playback is complete, it will send our window a WM_APP message, as we specified. When we get this message, we must read each of the events that DirectShow has posted:

void HandleMP3Events()
{
    long    evCode, param1, param2;
    HRESULT h;

    for (;;)
    { 
        h = g_pMediaEventEx->GetEvent(&evCode, &param1, &param2, 0);
        if (FAILED(h))
            return;
        
        g_pMediaEventEx->FreeEventParams(evCode, param1, param2);
        OnMP3Finish(evCode);
    } 
}

We are now able to restart the music when it stops:

void OnMP3Finish(long)
{
    // Restart background music if it has stopped
    if (g_bMusicEnabled)
        ReplayMP3();
}

The sample program plays an MP3 file as background music, rather than using MIDI and DirectMusic. We won't use the MP3 functions in future samples, since MIDI is just as useful and a lot smaller (and therefore easier for you to download!), but the files will still be provided.

Xonix, a sample game presented here, shows how to create a new application from this framework. It demonstrates many useful techniques necessary to make a real application, rather than a sample. 

In the next section, we'll move over to DirectX Graphics for our 2D graphics, yielding several very useful advantages over DirectDraw.

 

Copyright © David McCabe, 1998 - 2001. All rights reserved.

You will need to download and install the m-math control to display any equations on this Web site. Without this control, you will not see most of the equations. Please do not e-mail me asking why the equations do not display!