MCI音乐播放

原因:

  在改正俄罗斯方块程序的功能的时候,想给这个程序增长一个背景音乐。本想用PlayWave来作的,但想到这个功能十分经常使用,那还不如封装一个本身的CMusicios

类,以备不时之需。原本觉得很容易的,但是在真正操做的时候,却出现了一个问题,就是没法准确的知道何时音乐播放完成。问题的难道就在于,怎样将类的成员函数做为窗口的回调函数。windows

 本来用thunk来解决这个问题的,可是在解决的时候出现了一个问题,调试了好几天都没有解决。直到最近才解决。(也就是前一篇文章的由来)ide

代码:(前面定义的宏主要是解决Unicode问题)

cMusic.h函数

复制代码
  1 #ifndef CMUSIC_H  2 #define CMUSIC_H  3  4 #ifdef _UNICODE  5 #define tstring wstring  6 #define tcout wcout  7 #define tcin wcin  8 #else  9 #define tstring string  10 #define tcout cout  11 #define tcin cin  12  13 #endif  14  15 #pragma warning(disable:4311)  16  17 #include "TCHAR.h"  18 #include<iostream>  19 #include<windows.h>  20 #include<string>  21 #include<vector>  22 #include<MMSystem.h>  23 #pragma comment(lib,"Winmm.lib")  24 using namespace std;  25  26 typedef LRESULT (*pfaCallBack)(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);  27 #pragma pack(push,1)  28 struct Thunk  29 {  30  BYTE op_movecx;  31  DWORD_PTR val_ecx;  32  BYTE op_call;  33  DWORD_PTR val_address;  34 };  35 #pragma pack(pop)  36  37 #define MY_WM_PLAY WM_USER+1  38 #define MY_WM_PAUSE WM_USER+2  39 #define MY_WM_STOP WM_USER+3  40 #define MY_WM_CLOSE WM_USER+4  41 #define MY_WM_PLAYNEXT WM_USER+5  42 #define MY_WM_PLAYLAST WM_USER+6  43 #define MY_WM_REPLAY WM_USER+7  44 #define MY_WM_PLAY_LOOP WM_USER+8  45 #define MY_WM_RESUME WM_USER+9  46 #define MY_WM_TEST0 WM_USER+10  47  48 //类说明开始  49 //=========================================================//  50 // 功能:播放音乐以及进行相关的控制  51 // 设计思路:  52 // 这个类的实现应该会比较简单,主要是利用MCI开头的函数来进行控制  53 // 最主要实现一下功能:  54 // 播放一个音频文件  55 // 暂停播放  56 // 恢复播放  57 // 获得音频文件的信息  58 // 文件名  59 // 长度  60 // 当前播放的位置  61 // 显示播放列表//一个文件夹中的全部MP3或者是wav文件  62 // 播放上一首  63 // 播放下一首  64 //  65 // 做者:张敏  66 // 日期:2013-1-10 邮箱 zhang19min88@163.com  67 // 注意:实现这个类个人最大的感想就是不要想在一个类中封装全部的函数  68 // 在真正要用的时候再进行继承  69 //也许这样不会焦头乱额  70 //=========================================================//  71 class ZMCMusic  72 {  73 public:  74  friend DWORD WINAPI ThreadProc(LPVOID);  75 public:  76 ZMCMusic();//构造函数  77 ~ZMCMusic();//析构函数  78 public:  79 void Init();  80 void AddPlayList(tstring tstrDir);  81  BOOL Play();  82  BOOL Pause();  83  BOOL Resume();  84  BOOL Stop();  85  BOOL Close();  86  BOOL Replay();  87 BOOL PlayNext();//播放下一曲  88 BOOL playLast();//播放上一曲  89  BOOL GetFileInfo();  90 BOOL LoadMusicFile(tstring const tstrFileNmae);  91 static void ShowError();  92 private:  93 void GetCurPos();  94 void GetFileLenth();  95 int MakeWindow();//产生一个窗口  96 int CreateWindowInThread();//在线程中建立窗口  97 LRESULT ProcWindow(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);//窗口过程的处理函数  98 private:  99  tstring m_tstrFileName; 100 vector<tstring> m_vcPlayList;//保存播放列表 101 int m_nCurPlayIndex;//当前正在播放文件夹的索引 102 int m_nFileLen;//文件的总长度 103 int m_nCurPos;//当前正播放的位置 104 int m_nSound;//播放的音量大小 105 private: 106  HWND m_hWnd; 107  HANDLE m_hThread; 108  HINSTANCE m_hInstance; 109  UINT m_uDeviceID; 110  DWORD m_dwThreadID; 111  tstring m_tstrWinClassName; 112  tstring m_tstrWinCaptionName; 113 private: 114 void InitThunk() 115  { 116 m_thunk.op_movecx=0xB9;// 0xb9 mov ecx,数值的 机器码 117 m_thunk.val_ecx=(DWORD_PTR)this; 118 m_thunk.op_call=0xE9;//0xe9是Jmp 相对地址的机器码 119 DWORD_PTR off=0; 120  _asm 121  { 122  mov eax,ZMCMusic::ProcWindow 123  mov DWORD PTR[off],eax 124  } 125 m_thunk.val_address=off-((DWORD_PTR)(&m_thunk.val_address)+sizeof(DWORD_PTR)); 126  } 127  pfaCallBack GetStaticEntry() 128  { 129 return (pfaCallBack)&m_thunk; 130  } 131 private: 132  Thunk m_thunk; 133 }; 134 135 #endif
复制代码

cMusic.cppthis

复制代码
  1 #include "cMusic.h"  2 //程序说明开始  3 //=========================================================//  4 // 功能:类的构造函数,因为本类须要 建立一个隐藏的窗口,用来接收  5 // 播放完成以后的消息。因此在构造类的时候,很天然也须要构建一个  6 // 隐藏的窗口  7 // 参数:无  8 // 返回 :无  9 // 主要思路:  10 // 初始化一些变量 而且调用createThread函数建立一个线程,而且在线程中  11 // 建立一个窗口  12 // 调用方法:系统自动调用  13 // 做者:张敏  14 // 日期:2012-1-11 邮箱 zhang19min88@163.com  15 // 说明:  16 //=========================================================//  17 ZMCMusic::ZMCMusic()  18 {  19 m_uDeviceID=0;  20 m_tstrFileName=_T("");  21 m_tstrWinCaptionName=_T("MCI");  22 m_tstrWinClassName=_T("CMCI");  23 m_nSound=0;  24 m_nFileLen=0;  25 m_nCurPos=0;  26 m_dwThreadID=0;  27 m_nCurPlayIndex=0;  28 m_hInstance=(HINSTANCE)GetModuleHandle(NULL);  29  InitThunk();  30 }  31 void ZMCMusic::Init()  32 {  33 CreateWindowInThread();//在线程中建立一个窗口  34 }  35 ZMCMusic::~ZMCMusic()  36 {  37  38 }  39 //程序说明开始  40 //=========================================================//  41 // 功能:加载须要播放的文件  42 // 参数:tstrFileName 须要加载的文件名  43 // 返回 :成功执行返回真 不然返回假  44 // 主要思路:  45 // 调用MCISendCommand函数 发送MCI_OPEN命令  46 // 获得一个设备ID  47 // 调用方法:外部接口函数  48 // 做者:张敏  49 // 日期:2013-1-10 邮箱 zhang19min88@163.com  50 // 说明:  51 //=========================================================//  52 BOOL ZMCMusic::LoadMusicFile(tstring const tstrFileNmae)  53 {  54 m_tstrFileName=tstrFileNmae;  55  MCI_OPEN_PARMS mciOPenParms;  56 DWORD dwReturn=0;  57  58 mciOPenParms.lpstrDeviceType=_T("mpegvideo");//使用这种类型,能够播放MP3  59 mciOPenParms.lpstrElementName=m_tstrFileName.c_str();//也就是播放的音频文件的路径名  60 mciOPenParms.dwCallback=(DWORD_PTR)m_hWnd;//该命令成功执行后 想m_hWnd窗口发送一条消息  61  62 dwReturn=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE|MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&mciOPenParms);  63 if(dwReturn!=0)  64  {  65 return FALSE;  66 }//成功打开这个设备  67 m_uDeviceID=mciOPenParms.wDeviceID; //获得一个设备的ID号  68  69 //MCI_STATUS_PARMS mciStatusParms;  70 //ZeroMemory(&mciStatusParms,sizeof(mciStatusParms));  71 //mciStatusParms.dwItem=MCI_STATUS_LENGTH;  72 //if(mciSendCommand( m_uDeviceID , MCI_STATUS , MCI_STATUS_ITEM , (DWORD)(LPMCI_STATUS_PARMS) &mciStatusParms )==0 )  73 //{  74 // m_nFileLen=mciStatusParms.dwReturn;  75 //}  76 return TRUE;  77 }  78  79 //程序说明开始  80 //=========================================================//  81 // 功能:播放加载了的音频文件  82 // 参数:  83 // 返回 :成功执行返回真 不然返回假  84 // 主要思路:  85 // 调用MCISendCommand函数 发送MCI_PLAY命令  86 // 调用方法:外部接口函数  87 // 做者:张敏  88 // 日期:2013-2-26 邮箱 zhang19min88@163.com  89 // 说明:  90 // 在这里并有处理MCI_NOTIFY消息,经过这个消息咱们能够知道播放是否结束  91 // 当歌曲播放完成以后 咱们会收到一个播放完成的消息  92 //=========================================================//  93 BOOL ZMCMusic::Play()  94 {  95 //LoadMusicFile(m_vcPlayList[ m_nCurPlayIndex]);  96  MCI_PLAY_PARMS mciPlayParms;  97 ZeroMemory(&mciPlayParms,sizeof(mciPlayParms));  98 mciPlayParms.dwCallback=(DWORD)m_hWnd;  99 return mciSendCommand(m_uDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&mciPlayParms); 100 } 101 //程序说明开始 102 //=========================================================// 103 // 功能:暂停当前播放的文件 104 // 参数:无 105 // 返回 :成功执行返回真 不然返回假 106 // 主要思路: 107 // 调用MCISendCommand函数 发送MCI_PAUSE命令 108 // 调用方法:外部接口函数 109 // 做者:张敏 110 // 日期:2012-1-10 邮箱 zhang19min88@163.com 111 // 说明: 112 // 在这里并无处理MCI_NOTIFY消息,经过这个消息咱们能够知道播放是否结束 113 //=========================================================// 114 BOOL ZMCMusic::Pause() 115 { 116  MCI_GENERIC_PARMS mciGenericParms; 117 ZeroMemory(&mciGenericParms,sizeof(mciGenericParms)); 118 mciGenericParms.dwCallback=(DWORD_PTR)m_hWnd; 119 return mciSendCommand(m_uDeviceID,MCI_PAUSE,MCI_WAIT,(DWORD)(LPVOID)&mciGenericParms); 120 } 121 //程序说明开始 122 //=========================================================// 123 // 功能:继续播放当前被暂停播放的文件 124 // 参数:无 125 // 返回 :成功执行返回真 不然返回假 126 // 主要思路: 127 // 调用MCISendCommand函数 发送MCI_RESUME命令 128 // 调用方法:外部接口函数 129 // 做者:张敏 130 // 日期:2012-1-10 邮箱 zhang19min88@163.com 131 // 说明: 132 // 133 //=========================================================// 134 BOOL ZMCMusic::Resume() 135 { 136  MCI_GENERIC_PARMS mciGenericParms; 137 ZeroMemory(&mciGenericParms,sizeof(mciGenericParms)); 138 return mciSendCommand(m_uDeviceID,MCI_RESUME,MCI_NOTIFY,(DWORD)(LPVOID)&mciGenericParms); 139 } 140 //程序说明开始 141 //=========================================================// 142 // 功能:中止播放的文件 143 // 参数:无 144 // 返回 :成功执行返回真 不然返回假 145 // 主要思路: 146 // 调用MCISendCommand函数 发送MCI_STOP命令 147 // 调用方法:外部接口函数 148 // 做者:张敏 149 // 日期:2012-1-10 邮箱 zhang19min88@163.com 150 // 说明: 151 // 这个命令和PAUSE是不一样的 pause只是暂停播放,但播放的内容依然在内存中,能够继续播放 152 // 而STOP则是该文件退出资源 不能继续播放 153 //对于这个命令的确切理解MSDN上说 它和Pause的区别是看不一样的设备来讲的 154 //=========================================================// 155 BOOL ZMCMusic::Stop() 156 { 157 return mciSendCommand(m_uDeviceID,MCI_STOP,NULL,NULL); 158 } 159 //程序说明开始 160 //=========================================================// 161 // 功能:关闭播放设备 162 // 参数:无 163 // 返回 :成功执行返回真 不然返回假 164 // 主要思路: 165 // 调用MCISendCommand函数 发送MCI_CLOSE命令 166 // 调用方法:外部接口函数 167 // 做者:张敏 168 // 日期:2013-1-11 邮箱 zhang19min88@163.com 169 // 说明: 170 // 这个命令和PAUSE是不一样的 pause只是暂停播放,但播放的内容依然在内存中,能够继续播放 171 // 而STOP则是该文件退出资源 不能继续播放 172 //=========================================================// 173 BOOL ZMCMusic::Close() 174 { 175 return mciSendCommand(m_uDeviceID,MCI_CLOSE,NULL,NULL); 176 } 177 //程序说明开始 178 //=========================================================// 179 // 功能:从新播放 180 // 参数:无 181 // 返回 :成功执行返回真 不然返回假 182 // 主要思路: 183 // 调用CLose()函数 关闭设备 而后调用LoadMusicFile()函数打开设备 最后调用Play()函数进行播放 184 // 调用方法:外部接口函数 185 // 做者:张敏 186 // 日期:2013-2-27 邮箱 zhang19min88@163.com 187 // 说明: 188 //=========================================================// 189 BOOL ZMCMusic::Replay() 190 { 191  Close(); 192  LoadMusicFile(m_tstrFileName); 193 return Play(); 194 } 195 196 197 198 int ZMCMusic::MakeWindow() 199 { 200  MSG stMsg; 201 TCHAR szClassName[]=_T("MCI"); 202 203  WNDCLASSEX stWC; 204 ZeroMemory(&stWC,sizeof(stWC)); 205 stWC.hCursor=LoadCursor(NULL,IDC_ARROW); 206 stWC.cbSize=sizeof(stWC); 207 stWC.style=CS_HREDRAW|CS_VREDRAW; 208 stWC.lpfnWndProc=(WNDPROC)ZMCMusic::GetStaticEntry();//将类成员函数做为窗口过程函数 209 //stWC.lpfnWndProc=DefWindowProc; 210 stWC.lpszClassName=m_tstrWinClassName.c_str(); 211 stWC.cbClsExtra=0; 212 stWC.hInstance=m_hInstance; 213 214 RegisterClassEx(&stWC); 215 216 m_hWnd=CreateWindowEx(WS_EX_CLIENTEDGE,m_tstrWinClassName.c_str(),m_tstrWinCaptionName.c_str(),WS_OVERLAPPEDWINDOW,100,100,600,700,NULL,NULL,m_hInstance,NULL); 217 if(m_hWnd==NULL) 218  { 219  ShowError(); 220  } 221  ShowWindow(m_hWnd,SW_HIDE); 222  UpdateWindow(m_hWnd); 223 while(GetMessage(&stMsg,NULL,0,0)) 224  { 225 TranslateMessage(&stMsg); 226 DispatchMessage(&stMsg); 227  } 228 return 0 ; 229 } 230 231 //程序说明开始 232 //=========================================================// 233 // 功能:窗口过程的处理函数 234 // 参数:hWNd uMsg wParam lParam 正常窗口回调函数 235 // 返回 :无 236 // 主要思路: 237 // 在这个回调函数中咱们完成的就是处理MM_MCINOTIFY消息,主要是对MCI播放命令的执行结果进行响应 238 // 最关键的一个结果是经过这个消息的处理咱们能够知道何时音乐播放完成 239 // 调用方法:系统自动调用 240 // 做者:张敏 241 // 日期:2012-1-11 邮箱 zhang19min88@163.com 242 // 说明: 243 //=========================================================// 244 245 //这个类该怎样利用还不是很清楚 246 LRESULT ZMCMusic::ProcWindow(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) 247 { 248 switch(uMsg) 249  { 250 case WM_CREATE: 251 // MessageBox(NULL,_T("MCI"),_T("MCI"),MB_OK); 252 break; 253 case MM_MCINOTIFY://当播放完成以后 254 //在这里能够进行不少设置 255 //比喻说设定单曲循环 256 //设置自动播放下一首等等 257 //关键是如何用代码进行封装 258 //MessageBox(NULL,_T("MCI"),_T("Play End!"),MB_OK); 259 this->Replay(); 260 break; 261 case MY_WM_PLAY: break; 262 case MY_WM_PAUSE:break; 263 case MY_WM_STOP:break; 264 case MY_WM_RESUME:break; 265 case MY_WM_REPLAY:break; 266 case MY_WM_PLAYNEXT:break; 267 case MY_WM_PLAYLAST:break; 268 case MY_WM_PLAY_LOOP:break; 269 default: 270 return DefWindowProc(hWnd,uMsg,wParam,lParam); 271  } 272 return 0; 273 } 274 275 //程序说明开始 276 //=========================================================// 277 // 功能:线程函数 278 //在这个函数中咱们接受一个建立线程时的一个参数this 由于线程能够传递一个参数 279 // 在这个程序的处理过程当中 我很好的解决了在内中调用线程的例子 280 // 之前遇到这种问题几回了,都是用一种不是很好的方式解决,今天算是搞清楚了 281 // 在类中建立线程,那么线程函数必须为全局函数,或者是静态函数,由于线程函数只能有一个参数,若是为成员函数,那么就会有一个默认的this 282 // 因此不行 我首先将它定义为友元函数,而后从线程函数中传递参数this这样就能够很好的解决这个问题了 283 // 参数:this指针 284 // 返回 :DWORD 285 // 主要思路: 286 // 调用类的方法建立一个隐藏的窗口 287 // 调用方法:系统自动调用 288 // 做者:张敏 289 // 日期:2012-1-11 邮箱 zhang19min88@163.com 290 // 说明: 291 //=========================================================// 292 DWORD WINAPI ThreadProc(LPVOID lParam) 293 { 294 ZMCMusic *pMusic=NULL; 295 pMusic=(ZMCMusic *)lParam; 296 return pMusic->MakeWindow(); 297 } 298 299 //程序说明开始 300 //=========================================================// 301 // 功能:启动一个新的线程来建立一个窗口 302 // 参数:无 303 // 返回 :int 304 // 主要思路: 305 // 建立一个新的线程 306 // 调用方法:系统自动调用 307 // 做者:张敏 308 // 日期:2012-1-11 邮箱 zhang19min88@163.com 309 // 说明: 310 //=========================================================// 311 int ZMCMusic::CreateWindowInThread() 312 { 313 m_hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,(LPVOID)this,0,&m_dwThreadID); 314 WaitForSingleObject(m_hThread,500); 315 return 0; 316 } 317 318 //程序说明开始 319 //=========================================================// 320 // 功能:输出最近一次的错误信息 321 // 返回 :成功执行返回真 不然返回假 322 // 主要思路: 323 // 调用InternetReadFile函数 324 // 调用方法: 325 // 做者:张敏 326 // 日期:2012-1-7 邮箱 zhang19min88@163.com 327 // 说明: 328 //=========================================================// 329 void ZMCMusic::ShowError() 330 { 331 #define ERROR_BUF 256 332  TCHAR szBuf[ERROR_BUF]; 333  LPVOID lpMsgBuf; 334 DWORD dw=GetLastError(); 335 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,dw,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf,0,NULL); 336 wsprintf(szBuf,_T("执行失败!错误码为:%d. 错误缘由为:%s\n"),dw,lpMsgBuf); 337 MessageBox(NULL,szBuf,_T("错误"),MB_OK); 338 } 339 //程序说明开始 340 //=========================================================// 341 // 功能:获得文件当前播放的位置 342 // 返回 :无 343 // 主要思路: 344 // 发送MCI_STATUS命令得到 345 // 主要是获得状态 346 // 调用方法: 347 // 做者:张敏 348 // 日期:2012-1-7 邮箱 zhang19min88@163.com 349 // 说明: 350 //=========================================================// 351 void ZMCMusic::GetCurPos() 352 { 353  MCI_STATUS_PARMS mciStatusParms; 354 mciStatusParms.dwItem=MCI_STATUS_POSITION; 355 if(mciSendCommand( m_uDeviceID , MCI_STATUS , MCI_STATUS_ITEM , (DWORD)(LPMCI_STATUS_PARMS) &mciStatusParms )==0 ) 356  { 357 m_nCurPos=mciStatusParms.dwReturn; 358  } 359 } 360 //程序说明开始 361 //=========================================================// 362 // 功能:获得文件的长度 363 // 返回 :无 364 // 主要思路: 365 // 发送MCI_STATUS命令得到 366 // 主要是获得状态 367 // 调用方法: 368 // 做者:张敏 369 // 日期:2012-2-27 邮箱 zhang19min88@163.com 370 // 说明: 371 //=========================================================// 372 void ZMCMusic::GetFileLenth() 373 { 374  MCI_STATUS_PARMS mciStatusParms; 375 mciStatusParms.dwItem=MCI_STATUS_LENGTH;//得到文件的长度 376 if(mciSendCommand( m_uDeviceID , MCI_STATUS , MCI_STATUS_ITEM , (DWORD)(LPMCI_STATUS_PARMS) &mciStatusParms )==0 ) 377  { 378 m_nFileLen=mciStatusParms.dwReturn; 379  } 380 } 381 //程序说明开始 382 //=========================================================// 383 // 功能:将文件夹中的*.MP3文件和*.wav文件读取出来按照顺序添加到播放列表 384 // 返回 :无 385 // 主要思路: 386 // 在文件夹中查找*.mp3和*.wav的文件,而后放在容器中 387 // 调用方法: 388 // 做者:张敏 389 // 日期:2012-1-7 邮箱 zhang19min88@163.com 390 // 说明: 391 //=========================================================// 392 void ZMCMusic::AddPlayList(tstring tstrDir) 393 { 394 395 }
复制代码

main.cppspa

复制代码
 1 #include "cMusic.h"  2  3 void ShowMenu()  4 {  5 tcout<<"Press 0 to EXIT"<<endl;  6 tcout<<"Press 1 to PLAY"<<endl;  7 tcout<<"Press 2 to PAUSE"<<endl;  8 tcout<<"Press 3 to RESUSE"<<endl;  9 tcout<<"Press 4 to STOP"<<endl; 10 tcout<<"Press 5 to FULLSCAN"<<endl; 11 tcout<<"Press 6 to CLOSE"<<endl; 12 tcout<<"Press 7 to REPALY"<<endl; 13 tcout<<"Press 8 to SHOWPOS"<<endl; 14 tcout<<"Press 9 to STOP"<<endl; 15 16 } 17 int main() 18 { 19  tstring strFileName; 20 strFileName=_T("D:\\musicTest\\ifLove.mp3"); 21 // strFileName=_T("D:\\musicTest\\msg.mp3"); 22 23  ZMCMusic music; 24  music.Init(); 25 //music.AddPlayList(strFileName); 26  music.LoadMusicFile(strFileName); 27 28 int nKey=0; 29 bool bExit=false; 30 while(!bExit) 31  { 32  ShowMenu(); 33 tcout<<_T("You Command:"); 34 tcin>>nKey; 35 switch(nKey) 36  { 37 case 0:bExit=true;break; 38 case 1:music.Play();break; 39 case 2:music.Pause();break; 40 case 3:music.Resume();break; 41 //case 4:music.Stop();break; 42 case 6:music.Close();break; 43 case 7:music.Replay();break; 44 //case 8:SendMessage(g_hWnd,WM_CLOSE,NULL,NULL);break; 45 default:break; 46  } 47 Sleep(500); 48  } 49 return 0; 50 }
复制代码

运行结果:

注意:

相关文章
相关标签/搜索