http://www.cnblogs.com/zhangminaxiang/archive/2013/02/27/2936011.html
缘由:
在改正俄罗斯方块程序的功能的时候,想给这个程序增加一个背景音乐。本想用PlayWave来做的,但想到这个功能十分常用,那还不如封装一个自己的CMusic
类,以备不时之需。本来以为很容易的,可是在真正操作的时候,却出现了一个问题,就是无法准确的知道什么时候音乐播放完成。问题的难道就在于,怎样将类的成员函数作为窗口的回调函数。
原本用thunk来解决这个问题的,但是在解决的时候出现了一个问题,调试了好几天都没有解决。直到最近才解决。(也就是前一篇文章的由来)
代码:(前面定义的宏主要是解决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 邮箱 [email protected]
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.cpp
1 #include "cMusic.h"
2 //程序说明开始
3 //=========================================================//
4 // 功能:类的构造函数,由于本类需要 创建一个隐藏的窗口,用来接收
5 // 播放完成之后的消息。所以在构造类的时候,很自然也需要构建一个
6 // 隐藏的窗口
7 // 参数:无
8 // 返回 :无
9 // 主要思路:
10 // 初始化一些变量 并且调用createThread函数创建一个线程,并且在线程中
11 // 创建一个窗口
12 // 调用方法:系统自动调用
13 // 作者:张敏
14 // 日期:2012-1-11 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
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 邮箱 [email protected]
390 // 说明:
391 //=========================================================//
392 void ZMCMusic::AddPlayList(tstring tstrDir)
393 {
394
395 }
main.cpp
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 }
运行结果:
注意:
这个类还有一些功能没有实现。
其中不是很理解PAUSE STOP CLOSE 这三个命令的区别。还待以后研究……