最近学了一些游戏开发必不可少的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, ¶m1, ¶m2, 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
怎么样?很简单吧。其有时间的话,我还会写更多的关于游戏开发的心得体会的。