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

最近学了一些游戏开发必不可少的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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值