开发游戏音频程序——MP3的播放

时间:2021-08-03 17:27:26

最近学了一些游戏开发必不可少的MP3文件播放知识。首先,我的目标机器是windows,所以这些是在windows平台下开发的。随后我学到,要直接解码MP3文件是非常的困难的。因为MP3文件的闭源,我们都没有知道怎样才能一个一个地解码。好在DirectShow帮我们解决了解码的工作,我们需要的只是加入头文件,并且手动链接库文件,使用它的函数就行了。

为了使用它的函数,我们必须加入头文件dshow.h。这个头文件是不容易找到的。以后的DirectX里面都没有了DirectShow的踪影。我只好下载2004年十二月的DirectX版本,这个版本里就有dshow.h。好了,我们加入这个头文件不外乎就是要加一些接口。这里我列举这一些接口的名字:

IGraphBuilder

IMediaControl

IMediaEventEx

 

为了能够解析IGraphBuilder这个符号,我们还要手动地链接Strmiids.lib库。这个库不是特别的显眼,但是没有它我们的程序就无法通过。

 

好了,准备工作做好了后,我们开始写一个类了。这个类很简单,叫CMP3。这个类里面嵌入了另外一个MP3标签类CMP3Tag。下面就是我这个类的代码。

 

class CMP3

{

public:

    CMP3( void )                                            // 默认构造函数

    {

        m_GraphBuilder                  = NULL;

        m_MediaControl                  = NULL;

        m_MediaEventEx                  = NULL;

        m_IsPlaying                     = false;

    }

    ~CMP3( void );                                          // 析构函数

    bool LoadMP3File( TString strFilename );                // 读取MP3文件

    bool Play( void );                                      // 播放MP3文件

    void ProcessNotification( void );                       // 处理通知

    CMP3Tag GetMP3Tag( void ) { return m_tag; }             // 获取MP3标签

    bool IsPlaying( void ) { return m_IsPlaying; }          // 判断是否正在播放

private:

    bool                                m_IsPlaying;

    IGraphBuilder*                      m_GraphBuilder;

    IMediaControl*                      m_MediaControl;

    IMediaEventEx*                      m_MediaEventEx;

    CMP3Tag                             m_tag;

};

这些函数都是能简单明了的。除了一个ProcessNotification。这个函数的作用是检测是否MP3已经播放完毕。如果你不使用多线程的话,这个函数必须在一个循环内调用。

 

而它的被调用者:CMP3Tag类,则是一个储存MP3标签的类:

class CMP3Tag// MP3标签类

{

public:

    bool ReadMP3Tag( TString strFilename );                 // 读取MP3标签

    TString GetGenre( void );                               // 获取流派

    TString GetComment( void ){ return m_Comment; }         // 获取评论

    TString GetYear( void ){ return m_Year; }               // 获取发行年

    TString GetAlbum( void ){ return m_Album; }             // 获取专辑

    TString GetArtist( void ){ return m_Artist; }           // 获取艺术家

    TString GetTitle( void ){ return m_Title; }             // 获取标题

    enum MP3_GENRE

    {

        GENRE_BLUES = 0,         GENRE_CLASSICROCK,      GENRE_COUNTRY,         GENRE_DANCE,

        GENRE_DISCO,             GENRE_FUNK,             GENRE_GRUNGE,          GENRE_HIPHOP,

        GENRE_JAZZ,              GENRE_METAL,            GENRE_NEWAGE,          GENRE_OLDIES,

        GENRE_OTHER,             GENRE_POP,              GENRE_RANDB,           GENRE_RAP,

        GENRE_REGGAE,            GENRE_ROCK,             GENRE_TECHNO,          GENRE_INDUSTRIAL,

        GENRE_ALTERNATIVE,       GENRE_SKA,              GENRE_DEATHMETAL,      GENRE_PRANKS,

        GENRE_SOUNDTRACK,        GENRE_EUROTECHNO,       GENRE_AMBIENT,         GENRE_TRIPHOP,

        GENRE_VOCAL,             GENRE_JAZZANDFUNK,      GENRE_FUSION,          GENRE_TRANCE,

        GENRE_CLASSICAL,         GENRE_INSTRUMENTAL,     GENRE_ACID,            GENRE_HOUSE,

        GENRE_GAME,              GENRE_SOUNDCLIP,        GENRE_GOSPEL,          GENRE_NOISE,

        GENRE_ALTERNROCK,        GENRE_BASS,             GENRE_SOUL,            GENRE_PUNK,

        GENRE_SPACE,             GENRE_MEDITATIVE,       GENRE_INSTRUMENTALPOP, GENRE_INSTRUMENTALROCK,

        GENRE_ETHNIC,            GENRE_GOTHIC,           GENRE_DARKWAVE,        GENRE_TECHNOINDUSTRIAL,

        GENRE_ELECTRONIC,        GENRE_POPFOLK,          GENRE_EURODANCE,       GENRE_DREAM,

        GENRE_SOUTHERNROCK,      GENRE_COMEDY,           GENRE_CULT,            GENRE_GANGSTA,

        GENRE_TOP40,             GENRE_CHRISTIANRAP,     GENRE_POPFUNK,         GENRE_JUNGLE,

        GENRE_NATIVAAMERICAN,    GENRE_CABARET,          GENRE_NEWWAVE,         GENRE_PSYCHADELIC,

        GENRE_RAVE,              GENRE_SHOWTUNES,        GENRE_TRAILER,         GENRE_LOFI,

        GENRE_TRIBAL,            GENRE_ACIDPUNK,         GENRE_ACIDJAZZ,        GENRE_POLKA,

        GENRE_RETRO,             GENRE_MUSICAL,          GENRE_ROCKANDROLL,     GENRE_HARDROCK,

        GENRE_FOLK,              GENRE_FOLKROCK,         GENRE_NATIONALFOLK,    GENRE_SWING,

        GENRE_FASTFUSION,        GENRE_BEBOP,            GENRE_LATIN,           GENRE_REVIVAL,

        GENRE_CELTIC,            GENRE_BLUEGRASS,        GENRE_AVANTGARDE,      GENRE_GOTHICROCK,

        GENRE_PROGRESSIVEROCK,   GENRE_PSYCHEDELICROCK,  GENRE_SYMPHONICROCK,   GENRE_SLOWROCK,

        GENRE_BIGBAND,           GENRE_CHORUS,           GENRE_EASYLISTENING,   GENRE_ACOUSTIC,

        GENRE_HUMOR,             GENRE_SPEECH,           GENRE_CHANSON,         GENRE_OPERA,

        GENRE_CHAMBERMUSIC,      GENRE_SONATA,           GENRE_SYMPHONY,        GENRE_BOOTYBRASS,

        GENRE_PRIMUS,            GENRE_PORNGROOVE,       GENRE_SATIRE,          GENRE_SLOWJAM,

        GENRE_CLUB,              GENRE_TANGO,            GENRE_SAMBA,           GENRE_FOLKLORE,

        GENRE_BALLAD,            GENRE_POWERBALLAD,      GENRE_RTHYMICSOUL,     GENRE_FREESTYLE,

        GENRE_DUET,              GENRE_PUNKROCK,         GENRE_DRUMSOLO,        GENRE_ACAPELA,

        GENRE_EUROHOUSE,         GENRE_DANCEHALL

    };

private:

    char m_Genre;

    TString m_Comment;

    TString m_Year;

    TString m_Album;

    TString m_Artist;

    TString m_Title;

};

看得眼花了吧。其实这些枚举是为了标志MP3的流派的。你看看,为了这样一个小的属性,我们做了不知多少的努力!

 

我们把CMP3以及它的标签类的实现写在一个cpp里面。这个cpp就是CMP3.cpp。下面就是这个源文件的实现:

/*---------------------------------------------------------------------------

蒋轶民制作E-mail:jiangcaiyang123@163.com

最后编辑:年月日:51:26

文件名:CMP3.cpp

作用:MP3类的实现文件

----------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/

// 头文件

#include <fstream>

#include "CMP3.h"

/*--------------------------------------------------------------------------*/

std::wstring MultibyteToWstring( LPCSTR pszSrc )

{

    int nSize = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)pszSrc, (int)strlen( pszSrc ) + 1, 0, 0 );

    if ( nSize <= 0 ) return NULL;                              // 转换失败

    WCHAR* pwszDst = new WCHAR[nSize+1];

    if ( pwszDst == NULL ) return NULL;                         // 分配空间错误

    MultiByteToWideChar( CP_ACP, 0, (LPCSTR)pszSrc, (int)strlen( pszSrc ) + 1, pwszDst, nSize );

    pwszDst[nSize] = 0;

    if ( pwszDst[0] == 0xFEFF )                                 // 跳过标识符xFEFF

    {

        for ( int i = 0; i < nSize; i++ )

            pwszDst[i] = pwszDst[i+1];

    }

    std::wstring wCharString( pwszDst );

    delete pwszDst;

    return wCharString;

}

/*--------------------------------------------------------------------------*/

bool CMP3Tag::ReadMP3Tag( TString strFilename )                 // 读取MP3标签

{

    // 读取文件

    std::ifstream in( strFilename.c_str(), std::ios::in | std::ios::binary );

    if ( !in ) return false;

 

    in.seekg( -128, std::ios::end );                                    // 文件指针移到倒数的字节处

 

    char strTag[4] = { 0 };

    in.read( strTag, 3 );

    if ( strcmp( strTag, "TAG" ) != 0 ) return false;

 

    // 正确地读取到MP3信息标签,开始读取MP3信息

    char genre;

    char comment[31]            = { 0 };

    char year[5]                = { 0 };

    char album[31]              = { 0 };

    char artist[31]             = { 0 };

    char title[31]              = { 0 };

    in.read( title, 30 );

    in.read( artist, 30 );

    in.read( album, 30 );

    in.read( year, 4 );

    in.read( comment, 30 );

    in.read( &genre, 1 );

 

    in.close();                                 // 关闭文件

 

    m_Genre                     = genre;

    m_Comment                   = MultibyteToWstring( comment );

    m_Year                      = MultibyteToWstring( year );

    m_Album                     = MultibyteToWstring( album );

    m_Artist                    = MultibyteToWstring( artist );

    m_Title                     = MultibyteToWstring( title );

 

    return true;

}

/*--------------------------------------------------------------------------*/

TString CMP3Tag::GetGenre( void )                               // 获取流派

{

 

    switch ( m_Genre )

    {

    case GENRE_BLUES:             return TEXT( "Blues" );

    case GENRE_CLASSICROCK:       return TEXT( "Classic Rock" );

    case GENRE_COUNTRY:           return TEXT( "Country" );

    case GENRE_DANCE:             return TEXT( "Dance" );

    case GENRE_DISCO:             return TEXT( "Disco" );

    case GENRE_FUNK:              return TEXT( "Funk" );

    case GENRE_GRUNGE:            return TEXT( "Grunge" );

    case GENRE_HIPHOP:            return TEXT( "Hip-hop" );

    case GENRE_JAZZ:              return TEXT( "Jazz" );

    case GENRE_METAL:             return TEXT( "Metal" );

    case GENRE_NEWAGE:            return TEXT( "New Age" );

    case GENRE_OLDIES:            return TEXT( "Oldies" );

    case GENRE_OTHER:             return TEXT( "Other" );

    case GENRE_POP:               return TEXT( "Pop" );

    case GENRE_RANDB:             return TEXT( "R&B" );

    case GENRE_RAP:               return TEXT( "Rap" );

    case GENRE_REGGAE:            return TEXT( "Reggae" );

    case GENRE_ROCK:              return TEXT( "Rock" );

    case GENRE_TECHNO:            return TEXT( "Techno" );

    case GENRE_INDUSTRIAL:        return TEXT( "Industrial" );

    case GENRE_ALTERNATIVE:       return TEXT( "Alternative" );

    case GENRE_SKA:               return TEXT( "Ska" );

    case GENRE_DEATHMETAL:        return TEXT( "Death Metal" );

    case GENRE_PRANKS:            return TEXT( "Pranks" );

    case GENRE_SOUNDTRACK:        return TEXT( "Soundtrack" );

    case GENRE_EUROTECHNO:        return TEXT( "Eurotechno" );

    case GENRE_AMBIENT:           return TEXT( "Ambient" );

    case GENRE_TRIPHOP:           return TEXT( "Triphop" );

    case GENRE_VOCAL:             return TEXT( "Vocal" );

    case GENRE_JAZZANDFUNK:       return TEXT( "Jazz & Funk" );

    case GENRE_FUSION:            return TEXT( "Fusion" );

    case GENRE_TRANCE:            return TEXT( "Trance" );

    case GENRE_CLASSICAL:         return TEXT( "Classical" );

    case GENRE_INSTRUMENTAL:      return TEXT( "Instrumental" );

    case GENRE_ACID:              return TEXT( "Acid" );

    case GENRE_HOUSE:             return TEXT( "House" );

    case GENRE_GAME:              return TEXT( "Game" );

    case GENRE_SOUNDCLIP:         return TEXT( "Sound Clip" );

    case GENRE_GOSPEL:            return TEXT( "Gospel" );

    case GENRE_NOISE:             return TEXT( "Noise" );

    case GENRE_ALTERNROCK:        return TEXT( "Alternative Rock" );

    case GENRE_BASS:              return TEXT( "Bass" );

    case GENRE_SOUL:              return TEXT( "Soul" );

    case GENRE_PUNK:              return TEXT( "Punk" );

    case GENRE_SPACE:             return TEXT( "Space" );

    case GENRE_MEDITATIVE:        return TEXT( "Meditative" );

    case GENRE_INSTRUMENTALPOP:   return TEXT( "Instrumental Pop" );

    case GENRE_INSTRUMENTALROCK:  return TEXT( "Instrumental Rock" );

    case GENRE_ETHNIC:            return TEXT( "Ethnic" );

    case GENRE_GOTHIC:            return TEXT( "Gothic" );

    case GENRE_DARKWAVE:          return TEXT( "Darkwave" );

    case GENRE_TECHNOINDUSTRIAL:  return TEXT( "Techno-industrial" );

    case GENRE_ELECTRONIC:        return TEXT( "Electronic" );

    case GENRE_POPFOLK:           return TEXT( "Pop Folk" );

    case GENRE_EURODANCE:         return TEXT( "EuroDance" );

    case GENRE_DREAM:             return TEXT( "Dream" );

    case GENRE_SOUTHERNROCK:      return TEXT( "Southern Rock" );

    case GENRE_COMEDY:            return TEXT( "Comedy" );

    case GENRE_CULT:              return TEXT( "Cult" );

    case GENRE_GANGSTA:           return TEXT( "Gangsta" );

    case GENRE_TOP40:             return TEXT( "Top 40" );

    case GENRE_CHRISTIANRAP:      return TEXT( "Christian Rap" );

    case GENRE_POPFUNK:           return TEXT( "Pop Funk" );

    case GENRE_JUNGLE:            return TEXT( "Jungle" );

    case GENRE_NATIVAAMERICAN:    return TEXT( "Native American" );

    case GENRE_CABARET:           return TEXT( "Cabaret" );

    case GENRE_NEWWAVE:           return TEXT( "New Wave" );

    case GENRE_PSYCHADELIC:       return TEXT( "Psychadelic" );

    case GENRE_RAVE:              return TEXT( "Rave" );

    case GENRE_SHOWTUNES:         return TEXT( "Showtunes" );

    case GENRE_TRAILER:           return TEXT( "Trailer" );

    case GENRE_LOFI:              return TEXT( "Lo-fi" );

    case GENRE_TRIBAL:            return TEXT( "Tribal" );

    case GENRE_ACIDPUNK:          return TEXT( "Acid Punk" );

    case GENRE_ACIDJAZZ:          return TEXT( "Acid Jazz" );

    case GENRE_POLKA:             return TEXT( "Polka" );

    case GENRE_RETRO:             return TEXT( "Retro" );

    case GENRE_MUSICAL:           return TEXT( "Musical" );

    case GENRE_ROCKANDROLL:       return TEXT( "Rock & Roll" );

    case GENRE_HARDROCK:          return TEXT( "Hard Rock" );

    case GENRE_FOLK:              return TEXT( "Folk" );

    case GENRE_FOLKROCK:          return TEXT( "Folk Rock" );

    case GENRE_NATIONALFOLK:      return TEXT( "National Folk" );

    case GENRE_SWING:             return TEXT( "Swing" );

    case GENRE_FASTFUSION:        return TEXT( "Fast Fusion" );

    case GENRE_BEBOP:             return TEXT( "Bebop" );

    case GENRE_LATIN:             return TEXT( "Latin" );

    case GENRE_REVIVAL:           return TEXT( "Revival" );

    case GENRE_CELTIC:            return TEXT( "Celtic" );

    case GENRE_BLUEGRASS:         return TEXT( "Bluegrass" );

    case GENRE_AVANTGARDE:        return TEXT( "Avant-Garde" );

    case GENRE_GOTHICROCK:        return TEXT( "Gothic Rock" );

    case GENRE_PROGRESSIVEROCK:   return TEXT( "Progressive Rock" );

    case GENRE_PSYCHEDELICROCK:   return TEXT( "Psychedelic Rock" );

    case GENRE_SYMPHONICROCK:     return TEXT( "Symphonic Rock" );

    case GENRE_SLOWROCK:          return TEXT( "Slow Rock" );

    case GENRE_BIGBAND:           return TEXT( "Big Band" );

    case GENRE_CHORUS:            return TEXT( "Chorus" );

    case GENRE_EASYLISTENING:     return TEXT( "Easy Listening" );

    case GENRE_ACOUSTIC:          return TEXT( "Acoustic" );

    case GENRE_HUMOR:             return TEXT( "Humor" );

    case GENRE_SPEECH:            return TEXT( "Speech" );

    case GENRE_CHANSON:           return TEXT( "Chanson" );

    case GENRE_OPERA:             return TEXT( "Opera" );

    case GENRE_CHAMBERMUSIC:      return TEXT( "Chamber Music" );

    case GENRE_SONATA:            return TEXT( "Sonata" );

    case GENRE_SYMPHONY:          return TEXT( "Symphony" );

    case GENRE_BOOTYBRASS:        return TEXT( "Booty Brass" );

    case GENRE_PRIMUS:            return TEXT( "Primus" );

    case GENRE_PORNGROOVE:        return TEXT( "Porn Groove" );

    case GENRE_SATIRE:            return TEXT( "Satire" );

    case GENRE_SLOWJAM:           return TEXT( "Slow Jam" );

    case GENRE_CLUB:              return TEXT( "Club" );

    case GENRE_TANGO:             return TEXT( "Tango" );

    case GENRE_SAMBA:             return TEXT( "Samba" );

    case GENRE_FOLKLORE:          return TEXT( "Folklore" );

    case GENRE_BALLAD:            return TEXT( "Ballad" );

    case GENRE_POWERBALLAD:       return TEXT( "Power Ballad" );

    case GENRE_RTHYMICSOUL:       return TEXT( "Rthymic Soul" );

    case GENRE_FREESTYLE:         return TEXT( "Freestyle" );

    case GENRE_DUET:              return TEXT( "Duet" );

    case GENRE_PUNKROCK:          return TEXT( "Punk Rock" );

    case GENRE_DRUMSOLO:          return TEXT( "Drum Solo" );

    case GENRE_ACAPELA:           return TEXT( "A Capela" );

    case GENRE_EUROHOUSE:         return TEXT( "EuroHouse" );

    case GENRE_DANCEHALL:         return TEXT( "Dance Hall" );

    }

    return TEXT( "Unknown" );

}                              

/*--------------------------------------------------------------------------*/

bool CMP3::LoadMP3File( TString strFilename )               // 读取MP3文件

{

    m_tag.ReadMP3Tag( strFilename );                        // 读取文件标签

 

    HRESULT hr;                                             // 结果的句柄

 

    // 初始化COM

    hr = CoInitialize( NULL );

 

    // 创建接口

    hr = CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,

        IID_IGraphBuilder, (void **)&m_GraphBuilder );

 

    m_GraphBuilder->QueryInterface( IID_IMediaControl,

        (void** )&m_MediaControl );

    m_GraphBuilder->QueryInterface( IID_IMediaEventEx,

        (void** )&m_MediaEventEx );

 

    // 渲染文件(读取文件)

    m_GraphBuilder->RenderFile( strFilename.c_str(), NULL );

 

    return true;

}

/*--------------------------------------------------------------------------*/

bool CMP3::Play( void )                                         // 播放MP3文件

{

    ThrowIfFailed( m_MediaControl->Run(), "播放(运行)失败。" );

    m_IsPlaying = true;

    return true;

}

/*--------------------------------------------------------------------------*/

CMP3::~CMP3( void )                                             // 析构函数

{

    // 释放成员的空间

    m_GraphBuilder->Release();

    m_MediaControl->Release();

    m_MediaEventEx->Release();

}

/*--------------------------------------------------------------------------*/

void CMP3::ProcessNotification( void )                          // 处理消息,用来判定是否播放结束

{

    long eventcode, param1, param2;

 

    while ( m_MediaEventEx->GetEvent( &eventcode, &param1, &param2, 0 ) == S_OK )

    {

        if ( eventcode == EC_COMPLETE )

        {

            m_IsPlaying = false;

        }

        m_MediaEventEx->FreeEventParams(eventcode, param1, param2);

    }

}

可以很简单地调用这个类:

int main( int, char** )                         // 主函数

{

    CMP3 mp3_1, mp3_2;

    mp3_1.LoadMP3File( TEXT( "09. えがいてあ_そ_ぼ!.mp3" ) );

    mp3_2.LoadMP3File( TEXT( "46.MOETAN CORNER.mp3" ) );

 

    cout << "就绪,按任意键播放音乐:\n";

    if ( getch() )

    {

        mp3_1.Play();

        cout << "第一个音乐:";

        cout << WsToMultibyte( mp3_1.GetMP3Tag().GetTitle() );

        cout << "播放。\n";

    }

    if ( getch() )

    {

        mp3_2.Play();

        cout << "第二个音乐:";

        cout << WsToMultibyte( mp3_2.GetMP3Tag().GetTitle() );

        cout << "播放。\n";

    }

    if ( getch() ) cout << "音乐播放结束,现在关闭程序。\n";

   

    return 0;

}

程序的运行结果是:

 

我这个工程完整的代码的下载地址是:

http://download.csdn.net/detail/jiangcaiyang123/3589169

 

怎么样?很简单吧。其有时间的话,我还会写更多的关于游戏开发的心得体会的。