playing MP3
(level : easy)


Contents

Introduction
Application's interface
CPlayerMP3 class
Using CPlayerMP3
Appendix : CPlayerMP3's code


Introduction

The goal of this article is not to develop a MPEG decoder, but more modestly to use DirectShow (one of the DirectX components) to play MP3 files under Windows. For that matter I have written a small MFC application, that demonstrates the use of the CPlayerMP3 class which manages the calls to DirectShow.


it's only a picture, don't try to click on it ! ;)

You can of course download the whole project : sources of CPlayerMP3, of the MFC application, and exe file (95 Kb).
(the DirectX SDK is needed to recompile)


Application's interface

Using the application (see picture above) is straightforward : you first have to choose a MP3 file with the "Load" button, then you can play it, pause, stop, go back to the beginning, and tune the volume (0 to 100). Whenever you want you can load another file, without stopping. Some comments :

- the application refuses to load the file if DirectSound is used by another program ; it's because it can't initialize it for its own purpose. This happens for example when Winamp is running, and in the same way Winamp will refuse to play if the MFC application is doing it.

- the previous point doesn't apply to the same program : you can run several instances of the MFC application at the same time, the songs they play will get mixed.

- I have found some .mp3 files the application refuses to load : actually they're in the MPEG 1 layer 1 format, DirectShow seems to "only" accept MPEG 1 layer 3, which represents the majority of musical files found on the internet.

- there's no big difference between "Pause" and "Stop" : for example Stop does not go back to the start of the file automatically. This is the application's job to do it (by calling Rewind) if it is the wanted behaviour, here I have prefered to reflect DirectShow's functioning closely.


CPlayerMP3 class

There's not a lot to say about this class, which is extremely simple. The whole code is located in the appendix, for those who want to have a look without downloading. Concerning the calls to DirectShow, it's worth noting that :

- the functions of CPlayerMP3 use several COM interfaces : IMediaControl mainly, IMediaPosition for Rewind, IBasicAudio for the volume, IMediaEvent to detect the end of the music. There are many others (see the DirectX SDK documentation) but these are the important ones for the subject we're interested in, with the addition of IMediaSeeking.

- each time a pointer to an interface has been retrieved with QueryInterface, and when it's no longer used, don't forget to call its Release method.

- in CPlayerMP3::hrRewind I call put_CurrentPosition(0) to go back to the beginning of the file ; this function allows you to go wherever you want in the music and not only to the start, you can use it to code a "fast forward". The documentation however advises to use IMediaSeeking for this kind of purpose.

- the volume must be given between 0 (maximum) and -10000 (silence) because it's an attenuation in dB (decibels) [more exactly in hundredthes of decibels, between 0 and -100dB]. So the scale is not linear but logarithmic, which means that the heard volume is divided by 2 each time the attenuation is multiplied by 10. It's a bit complex to use, don't be afraid : the application contains a (somehow empiric) conversion to handle this problem.

- there are several ways to detect the music is finished (and go to the next one, or in my program stop it and "release" the Play button). I have chosen the most simple one, by checking from time to time if the corresponding event (EC_COMPLETE) has happened or not. Another method would be for example to ask DirectShow to call a callback, it's feasible but I haven't looked into the details.


Using CPlayerMP3

The MFC application, that I've kept as simple as possible, uses the previous class in the following way :

- in OnInitDialog, it calls CoInitialize(NULL) [needed to use COM and DirectX], creates a CPlayerMP3 object, initializes the buttons state, and creates a timer (called once per second to check for the end of the music - NB: this test could be placed in the OnIdle function of an MDI application too).

- reciprocally, closing the dialog box kills the timer (in OnDestroy because the window must still exist), then the player, and calls CoUninitialize().

- the CPlayMP3Dlg::OnLoad function loads a new file and checks its validity. The methods managing the other buttons of the interface make simple calls to the CPlayerMP3 object.

- the management of the volume in OnDeltaposSpinvolume can be interesting to you : a short formula converts the linear value between 0 and 100 to a logarithmic value expected by DirectShow, the aim being that 100% of the maximum volume sounds more or less 4 times louder than 25%, and so on.

That's all ! Now it's time for you to implement a pretty interface :)


Appendix : CPlayerMP3's code

PlayerMP3.h
PlayerMP3.inl
PlayerMP3.cpp

PlayerMP3.h
/**********************************************************************************************************************/
/*                                                MP3 PLAYER                                                          */
/* 14/12/00, M : creation of the file                                                                                 */
/*               uses DirectShow (DX7)                                                                                */
/**********************************************************************************************************************/

#if !defined(AFX_PLAYERMP3_H__5E8A1F22_D1D0_11D4_9849_00A0CC2C5341__INCLUDED_)
#define AFX_PLAYERMP3_H__5E8A1F22_D1D0_11D4_9849_00A0CC2C5341__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif

/************************************************ INCLUDE *************************************************************/

#include  <strmif.h>                                        // DirectShow
#include  <string>                                          // STL string

/************************************************ CLASSES *************************************************************/

/**********************************************************************************************************************/
/*                                                CPlayerMP3                                                          */
/**********************************************************************************************************************/

class     CPlayerMP3
  {
// public enums

  public:

    typedef enum 
    {
          _UNINITIALIZED_               = 0,
          _STOPPED_,
          _PAUSED_,
          _PLAYING_,
    } PLAYER_STATE;

    typedef enum
    {
          _OK_                          = 0,
          _MP3ERR_CANTPLAY_             = 0x80000000,
          _MP3ERR_CANTPAUSE_,
          _MP3ERR_CANTREWIND_,
          _MP3ERR_CANTSTOP_,
          _MP3ERR_NOVOLUME_,
    } MP3_ERROR;

// public methods

  public:

    // constructors & destructor

    CPlayerMP3                          (void);
   ~CPlayerMP3                          (void);

    HRESULT         hrLoad              (const TCHAR* lpszFile);

    // state

    bool            boCanPlay           (void) const;
    bool            boCanPause          (void) const;
    bool            boCanStop           (void) const;

    bool            boIsInitialized     (void) const;
    bool            boIsStopped         (void) const;
    bool            boIsPaused          (void) const;
    bool            boIsPlaying         (void) const;
    bool            boCompleted         (void) const;

    const TCHAR*    lpszGetFile         (void) const;

    // play

    HRESULT         hrPlay              (void);
    HRESULT         hrPause             (void);
    HRESULT         hrRewind            (void);
    HRESULT         hrStop              (void);
    HRESULT         hrSetVolume         (long lVolume);     // 0=full, -10000=silence, logarithmic

// protected methods

  protected:

    void            vInit               (void);
    void            vClean              (void);
    HRESULT         hrCreateFilterGraph (void);

// protected members

  protected:

    IGraphBuilder*  m_lpGraph;
    std::string     m_strFile;
    DWORD           m_dwState;
  };

/************************************************ INLINE **************************************************************/

#include  "PlayerMP3.inl"

/**********************************************************************************************************************/

#endif // !defined(AFX_PLAYERMP3_H__5E8A1F22_D1D0_11D4_9849_00A0CC2C5341__INCLUDED_)

PlayerMP3.inl
/**********************************************************************************************************************/
/*                                                MP3 PLAYER                                                          */
/* 14/12/00, M : creation of the file                                                                                 */
/*               uses DirectShow (DX7)                                                                                */
/**********************************************************************************************************************/

#ifndef   _PLAYERMP3_INL_
#define   _PLAYERMP3_INL_

/**********************************************************************************************************************/
/*                                                STATE                                                               */
/**********************************************************************************************************************/

inline bool CPlayerMP3::boCanPlay() const
  {
  return((m_dwState == _STOPPED_) || (m_dwState == _PAUSED_));
  }

inline bool CPlayerMP3::boCanStop() const
  {
  return((m_dwState == _PLAYING_) || (m_dwState == _PAUSED_));
  }

inline bool CPlayerMP3::boCanPause() const
  {
  return((m_dwState == _PLAYING_) || (m_dwState == _STOPPED_));
  }

//

inline bool CPlayerMP3::boIsInitialized() const
  {
  return (m_dwState != _UNINITIALIZED_);
  }

inline bool CPlayerMP3::boIsStopped() const
  {
  return (m_dwState == _STOPPED_);
  }

inline bool CPlayerMP3::boIsPaused() const
  {
  return (m_dwState == _PAUSED_);
  }

inline bool CPlayerMP3::boIsPlaying() const
  {
  return (m_dwState == _PLAYING_);
  }

//

inline const TCHAR* CPlayerMP3::lpszGetFile() const
  {
  return m_strFile.c_str();
  }

/**********************************************************************************************************************/

#endif

PlayerMP3.cpp
/**********************************************************************************************************************/
/*                                                MP3 PLAYER                                                          */
/* 14/12/00, M : creation of the file                                                                                 */
/*               uses DirectShow (DX7)                                                                                */
/**********************************************************************************************************************/

/************************************************ INCLUDE *************************************************************/

#include  "stdafx.h"
#include  "PlayerMP3.h"

#include  <control.h>                                       // IMediaControl
#include  <uuids.h>
#include  <evcode.h>                                        // EC_COMPLETE

/**********************************************************************************************************************/
/*                                                CONSTRUCTORS / DESTRUCTOR                                           */
/**********************************************************************************************************************/

CPlayerMP3::CPlayerMP3()
  {
  vInit();
  }

//

CPlayerMP3::~CPlayerMP3()
  {
  vClean();
  }

/************************************************ vInit ***************************************************************/
// initialize members & resources
// 14/12/00, M
// in :
// out:
/**********************************************************************************************************************/

void CPlayerMP3::vInit()
  {
  m_lpGraph = NULL;
  m_dwState = _UNINITIALIZED_;
  }

/************************************************ vClean **************************************************************/
// release memory & resources
// 14/12/00, M
// in :
// out:
/**********************************************************************************************************************/

void CPlayerMP3::vClean()
  {
  if(m_lpGraph) m_lpGraph->Release();
  m_strFile = "";

  vInit();
  }

/************************************************ hrCreateFilterGraph *************************************************/
// initialize DShow object
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrCreateFilterGraph()
  {
  if(m_lpGraph) return true;

  HRESULT hrErr = CoCreateInstance(CLSID_FilterGraph,
                                   NULL,
                                   CLSCTX_INPROC_SERVER,
                                   IID_IGraphBuilder,
                                   (void**)&m_lpGraph);
  if(FAILED(hrErr))
    {
    m_lpGraph = NULL;
    return hrErr;
    }

  return _OK_;
  }

/************************************************ hrLoad **************************************************************/
// load a file with graph object
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrLoad(const TCHAR* lpszFile)
  {
  vClean();

  HRESULT hrErr = hrCreateFilterGraph();
  if(FAILED(hrErr)) return hrErr;

  WCHAR wszFile[MAX_PATH];
  MultiByteToWideChar(CP_ACP,0,lpszFile,-1,wszFile,MAX_PATH);

  hrErr = m_lpGraph->RenderFile(wszFile,NULL);
  if(FAILED(hrErr)) return hrErr;

  m_strFile = lpszFile;
  m_dwState = _STOPPED_;
  return _OK_;
}

/**********************************************************************************************************************/
/*                                                PLAY                                                                */
/**********************************************************************************************************************/

/************************************************ hrPlay **************************************************************/
// start playing
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrPlay()
  {
  if(!boCanPlay()) return _MP3ERR_CANTPLAY_;

  IMediaControl* pMC;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IMediaControl,(void**)&pMC);
  if(FAILED(hrErr)) return hrErr;

  hrErr = pMC->Run();
  pMC->Release();
  if(FAILED(hrErr)) return hrErr;

  m_dwState = _PLAYING_;
  return _OK_;
  }

/************************************************ hrPause *************************************************************/
// pause replay
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrPause()
  {
  if(!boCanPause()) return _MP3ERR_CANTPAUSE_;

  IMediaControl* pMC;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IMediaControl,(void**)&pMC);
  if(FAILED(hrErr)) return hrErr;

  hrErr = pMC->Pause();
  pMC->Release();
  if(FAILED(hrErr)) return hrErr;

  m_dwState = _PAUSED_;
  return _OK_;
  }

/************************************************ hrRewind ************************************************************/
// rewind to the beginning
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrRewind()
  {
  if(!boIsInitialized()) return _MP3ERR_CANTREWIND_;
  hrStop();

  IMediaPosition* pMP;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IMediaPosition,(void**)&pMP);
  if(FAILED(hrErr)) return hrErr;
  
  hrErr = pMP->put_CurrentPosition(0);
  pMP->Release();
  if(FAILED(hrErr)) return hrErr;

  return _OK_;
  }

/************************************************ hrStop **************************************************************/
// stop playing
// 14/12/00, M
// in :
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrStop()
  {
  if(!boCanStop()) return _MP3ERR_CANTSTOP_;

  IMediaControl* pMC;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IMediaControl,(void**)&pMC);
  if(FAILED(hrErr)) return hrErr;

  hrErr = pMC->Stop();
  pMC->Release();
  if(FAILED(hrErr)) return hrErr;

  m_dwState = _STOPPED_;
  return _OK_;
  }

/************************************************ hrSetVolume *********************************************************/
// change volume
// 27/02/02, M
// in : volume (dB*100)
// out: _OK_ or error ID
/**********************************************************************************************************************/

HRESULT CPlayerMP3::hrSetVolume(long lVolume)
  {
  if(!boIsInitialized()) return _MP3ERR_NOVOLUME_;

  IBasicAudio* pBA;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IBasicAudio,(void**)&pBA);
  if(FAILED(hrErr)) return hrErr;

  if(lVolume >      0) lVolume = 0;
  if(lVolume < -10000) lVolume = -10000;
  hrErr = pBA->put_Volume(lVolume);
  pBA->Release();
  if(FAILED(hrErr)) return hrErr;

  return _OK_;
  }

/************************************************ boCompleted *********************************************************/
// check if end of stream is reached
// 28/02/02, M
// in :
// out: end reached ?
/**********************************************************************************************************************/

bool CPlayerMP3::boCompleted() const
  {
  if(!boIsPlaying()) return false;

  IMediaEvent* pME;
  HRESULT hrErr = m_lpGraph->QueryInterface(IID_IMediaEvent,(void**)&pME);
  if(FAILED(hrErr)) return false;

  long lEvent;
  hrErr = pME->WaitForCompletion(0,&lEvent);
  pME->Release();
  if(FAILED(hrErr)) return false;

  return(lEvent == EC_COMPLETE);
  }

back to top