一、背景知识
1. 设计背景
随着社会的不断进步,人们的生活水平不断提高,开始了对精神生活的追求和享受。音乐,作为人们精神世界最直接的食量,能随时随地听音乐成为了大家共同的愿望。于是我们基于C语言开发了一款在电脑上可以随时播放mp3格式音乐文件的音乐播放器。
2.开发平台
本次音乐播放器的设计,我们利用的是Microsoft Visual Studio。VS是美国微软公司的开发工具包系列产品。VS是一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如UML工具、代码管控工具、集成开发环境(IDE)等等。Visual Studio是目前最流行的Windows平台应用程序的集成开发环境。最新版本为 Visual Studio 2019版本,基于.NET Framework 4.7。
MFC(Microsoft Foundation Classes)是微软基础类库的简称,是微软公司实现的一个c++类库,主要封装了大部分的windows API函数,我们可以利用这个库来实现对电脑音频接口的调用MFC 是用来编写 Windows 应用程序的 C++ 类集,其中封装了大部分 Windows API 函数和 Windows 控件,使用 MFC 类库和 Visual C++ 提供的高度可视的应用程序开发工具,可使应用程序开发变得简单,提高代码的可靠性和可重用性。随着多媒体性能的极大提升,程序中经常要播放一段视频或者一段音频,如果能够简单地插入音频和视频,将极大缩短程序的开发时间。本文基于 MFC 在 Visual C++6.0 环境下利用 Windows Media Player 控件实现了音频视频文件的播放 [7] 。Windows 作为一个提供功能强大的应用程序接口编程的操作系统,的确方便了许多程序员,但是传统的 win32 开发(直接使用 Windows 的接口函数 API )对于程序员来说仍然是非常的困难,因为 API 函数实在太多了,并且名称很乱,例如构建一个窗口动辄就是上百行的代码。然而 MFC 是面向对象程序设计与 Application framework 的完美结合,将传统的 API 进行了分类封装,并且创建了程序的一般框架,这大大简化了我们的工作
二、设计思路
1. 整体思路
本次实验目的是编程实现一个简易的音乐播放器。音乐播放器包含:打开音乐,播放音乐,暂停/继续音乐,停止所有正在播放的音乐,上一曲/下一曲,调整音乐音量的大小等基本功能。利用音乐播放器可以对电脑中的音频文件实现上述功能。
首先,利用Visual Studio去设计一个基本的播放器页面窗口,设置实现相应功能的按键。然后针对每个按键,设置相应的函数作为处理事件的子程序,以此来实现播放器不同的功能。最后针对每个按键的功能进行测试并完善,最后实现相应的功能。
本音乐播放器各个功能的实现主要是依赖于Vs中MFC插件以及对系统函数中声卡驱动的调用来实现。需要一定的C/C++、多媒体设备接口的知识。
2. 基本功能模块设计

三、探索过程
①开发平台选择:为什么选择MFC来开发用户界面,而不是用windows来开发?
我们在用户界面的开发界面提出了两种方案,一是采用基于VS开发环境的MFC应用程序,二是采用基于DevC++的#include<windows.h>库函数。方案的选择取决于我们选择的开发环境。经过讨论,由于DevC++本身的原因,用户界面不够美观且操作需要利用键盘,不如MFC应用程序提供的用鼠标操作的用户页面美观且人性化。最终选择了方案一。
②音乐播放器的用户页面的制作:我们通过查找相应资料,由于MFC有固定框架可以比较美观高效的编写相关程序并可以直观的展示各种各样的用户界面,所以我们最终确定了基于Vs中的MFC应用程序设置对话框控件来实现。并通过设置不同的按键来实现音乐播放器不同的功能。
③用户页面的美化:通过对RGB色彩的控制对背景框进行调色,并创建内存DC来实现背景的贴图,一开始我们选择直接贴图,发现不太美观,反而还有些难看,最终我们查阅资料并选择了拉伸贴图函数来实现背景及用户界面的美化。
④方案比选:我们对于歌曲部分列表的实现提出了两种方案,方案一是采用链表来实现,方案二是采用顺序表来实现。由于MFC本身框架的限制(没有main函数),而且在初始化链表的过程中总是出现内存访问错误的问题,在参阅了大量资料以及寻求同学的帮助问题仍无法解决,所以最终我们采用顺序表来实现,通过定义一全局变量来存储歌曲,并通过指针的移动来实现歌曲的切换。
⑤功能的实现:在相关功能模块的实现过程中我们也遇到了许多困难与BUG,比如歌曲加入列表之后APP就莫名其妙闪退了,然后提示内存访问错误,最后经排查是内存溢出的问题;还有音量控制模块不起作用,左右滑动并不能使电脑音量增大或减小,代码经排查没有问题,最后发现是电脑的问题;
⑥查阅资料:我翻阅了大量网站(CSDN、博客园)和相关教学视频来完善我们自己的功能模块和解决我们遇到的各种问题。以下是主要几篇
https://www.bilibili.com/video/BV1Zb411x7AA?t=2613
https://blog.csdn.net/m0_37264397/article/details/78473138
https://blog.csdn.net/mangopp/article/details/80457152
https://www.cnblogs.com/Obelia/p/7256241.html
https://blog.csdn.net/z9l9j9/article/details/44832649
https://bbs.csdn.net/topics/390880184
四、代码分析与功能实现
1.代码分析
(1)头文件部分
#include <mmsystem.h>此头文件为Windows系统中多媒体设备接口的头文件,在代码实现的过程中,需要利用mciSendCommand()进行声卡驱动的调,来实现多媒体设备接口的使用。
#include <Digitalv.h>此头文件为音频文件音量的调节服务。在实现音量调节时需要用到:
mciSendCommand(m_DeviceID,MCI_SETAUDIO,MCI_DGV_SETAUDIO_VALUE|MCI_DGV_SETAUDIO_ITEM),其中宏定义#define MCI_SETAUDIO 0x0873 设置了音量调节对应的多媒体设备接口地址。
(2)子程序部分
歌曲列表结构设计:
方案一:采取双向循环链表结构,结点包含歌曲路径、歌曲名称、歌曲时间、下一结点、一结点。由于框架限制没能实现
方案二:采取一线性表实现(最终方案)

用户界面美化:对字体颜色,字体风格,和背景颜色图片进行了相关设计

打开音乐:通过点击控件1调用子函数void CmusicplayerDlg::OnBnClickedButton1()函数来实现

播放音乐:通过点击控件2调用子函数void CmusicplayerDlg::OnBnClickedButton2()函数来实现

暂停继续音乐:通过点击控件3调用子函数void CmusicplayerDlg::OnBnClickedButton4()函数来实现

上一曲:通过点击控件4调用子函数void CmusicplayerDlg::OnBnClickedButton4()函数来实现

下一曲:通过点击控件5调用子函数void CmusicplayerDlg::OnBnClickedButton5()函数来实现

调整音量:通过滑动控件调用子函数void CmusicplayerDlg::OnCustomdrawSlider1(NMHDR* pNMHDR, LRESULT* pResult)函数来实现

2.功能实现
(1)主页面

共有:打开、播放、暂停/继续、上一曲/下一曲、音量调节5个功能。
(2)打开音频文件

点击Open按键,会弹出打开的窗口,此时可以选择电脑中的音频文件进行播放,可以通过文件名进行文件查找,也可以借助Windows窗口化进行手动查找,另外对文件格式可以进行选择性过滤,方便查找。
(3)播放音频文件
找到要播放的音频文件,点击打开。便会回到主页面,此时再点击Play按键即可播放歌曲。

(4)暂停/继续
在播放音乐的时候点击Pause按键,音乐便会暂停,Pause按键切换到Continue按键。点击Continue按键,音乐便会继续播放,Continue再次变为Pause。以此来实现对音乐暂停和继续的控制。
(5)上一曲/下一曲
先用Open打开几个音频文件,然后点Play进行播放第一首歌曲,点击Last和Next进行上一曲和下一曲的操作,可以循环。或者先用Open打开几个音频文件,点击Last或者Next也可进行播放歌曲。
(6)音量调节
滑动Volume滑块,可将音量从0~100进行调节,以1为单位。0时为静音,100时为音量最大。
五、附主要代码
c++
// musicplayerDlg.cpp: 实现文件
\#include "pch.h"
\#include "malloc.h"
\#include "framework.h"
\#include "musicplayer.h"
\#include "musicplayerDlg.h"
\#include <mmsystem.h>
\#include "afxdialogex.h"
\#pragma comment(lib,"winmm.lib")
\#ifdef _DEBUG
\#define new DEBUG_NEW
\#define MAXSIZE 50
\#endif
typedef struct LNode
{
CString Path; //歌曲路径
char name[50]; //歌曲名称
int time; //歌曲时间
}LNode;
int Current = 0, Rear = 0; //线性表当前指针和尾指针
LNode MusicList[MAXSIZE]; //创建线性表
//歌曲进入播放列表
void Enmusic(LNode* p, CString str) {
(p + Rear)->Path = str;
Rear++;
}
CmusicplayerDlg::CmusicplayerDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MUSICPLAYER_DIALOG, pParent)
, m_soundvalue(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_DeviceID = 0;
}
void CmusicplayerDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_SLIDER1, m_slider);
DDX_Text(pDX, IDC_EDIT1, m_soundvalue);
}
BEGIN_MESSAGE_MAP(CmusicplayerDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, &CmusicplayerDlg::OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, &CmusicplayerDlg::OnBnClickedButton2)
ON_BN_CLICKED(IDC_BUTTON3, &CmusicplayerDlg::OnBnClickedButton3)
ON_BN_CLICKED(IDC_BUTTON4, &CmusicplayerDlg::OnBnClickedButton4)
ON_BN_CLICKED(IDC_BUTTON5, &CmusicplayerDlg::OnBnClickedButton5)
ON_BN_CLICKED(IDC_BUTTON6, &CmusicplayerDlg::OnBnClickedButton6)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_SLIDER1, &CmusicplayerDlg::OnCustomdrawSlider1)
ON_WM_NCHITTEST()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// CmusicplayerDlg 消息处理程序
BOOL CmusicplayerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
m_slider.SetRange(0, 1000);
m_slider.SetPos(500);
UpdateData(FALSE);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CmusicplayerDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this);
//获取客户区大小
CRect rect,captionRect;
GetClientRect(&rect);
captionRect = rect;
captionRect.bottom = 30;
dc.FillSolidRect(&captionRect,RGB(129,88,150));
CFont font;
//设置字体形式
font.CreatePointFont(120, L"微软雅黑");
dc.SelectObject(&font);
//设置字体及字体大小
dc.TextOutW(10, 2, L"MusicPlayer");
//加载图片
CBitmap bmp;
bmp.LoadBitmap(IDB_BITMAP1);
//获取图片宽和高
BITMAP logBmp;
bmp.GetBitmap(&logBmp);
//创建内存DC
CDC memDC;
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(&bmp);
dc.SetStretchBltMode(HALFTONE);
//拉伸贴图
dc.StretchBlt(0, 30, rect.Width(), rect.Height(), &memDC, 0, 0,logBmp.bmWidth,logBmp.bmHeight,SRCCOPY);
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CmusicplayerDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CmusicplayerDlg::OnBnClickedButton1()
{
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, L"MP3文件(*.mp3)|*.mp3|WMA文件(*.wma)|*.wma|WAV文件(*.wav)|*.wav|所有文件(*.*)|*.*||");
dlg.DoModal(); //弹出打开文件对话框
if (IDCANCEL == dlg.DoModal())
{
return;
}
//获取路径并加入播放列表
Enmusic(MusicList, dlg.GetPathName());
}
//播放
void CmusicplayerDlg::OnBnClickedButton2()
{
mciSendCommand(m_DeviceID, MCI_CLOSE, NULL, NULL);
//打开
MCI_OPEN_PARMS mciOpenParams;
mciOpenParams.lpstrElementName = MusicList[Current].Path;//从播放列表中获取当前音乐路径
MCIERROR mciErro = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_WAIT, (DWORD)&mciOpenParams);
if (mciErro) {
wchar_t szErrorMsg[256];
//获取错误信息
mciGetErrorString(mciErro, szErrorMsg, 256);
MessageBox(szErrorMsg, L"MusicPlayer"); //弹出错误提示
return;
}
m_DeviceID = mciOpenParams.wDeviceID;
MCI_PLAY_PARMS mciPlayParams;
mciPlayParams.dwCallback = NULL;
mciPlayParams.dwFrom = 0;
mciSendCommand(m_DeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD)&mciPlayParams); //播放音乐
}
//暂停
void CmusicplayerDlg::OnBnClickedButton3()
{
CString str;
GetDlgItemText (IDC_BUTTON3, str);
if (str == L"Pause")
{
mciSendCommand(m_DeviceID, MCI_PAUSE, 0, 0);
SetDlgItemText(IDC_BUTTON3, L"Continue");
}
else if(str==L"Continue")
{
mciSendCommand(m_DeviceID, MCI_RESUME, 0, 0);
SetDlgItemText(IDC_BUTTON3, L"Pause");
}
}
//上一首
void CmusicplayerDlg::OnBnClickedButton4()
{
mciSendCommand(m_DeviceID, MCI_CLOSE, NULL, NULL);
//当前歌曲指针左移
if (Current == 0) {
Current = Rear - 1;
}
else {
Current--;
}
MCI_OPEN_PARMS mciOpenParams;
mciOpenParams.lpstrElementName = MusicList[Current].Path;
MCIERROR mciErro = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_WAIT, (DWORD)&mciOpenParams);
if (mciErro) {
wchar_t szErrorMsg[256];
//获取错误信息
mciGetErrorString(mciErro, szErrorMsg, 256);
MessageBox(szErrorMsg, L"MusicPlayer");
return;
}
m_DeviceID = mciOpenParams.wDeviceID;
MCI_PLAY_PARMS mciPlayParams;
mciPlayParams.dwCallback = NULL;
mciPlayParams.dwFrom = 0;
mciSendCommand(m_DeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD)&mciPlayParams);
}
//下一首
void CmusicplayerDlg::OnBnClickedButton5()
{
mciSendCommand(m_DeviceID, MCI_CLOSE, NULL, NULL);
//当前歌曲指针右移
if (Current == Rear-1) {
Current = 0;
}
else {
Current++;
}
MCI_OPEN_PARMS mciOpenParams;
mciOpenParams.lpstrElementName = MusicList[Current].Path;
MCIERROR mciErro = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_WAIT, (DWORD)&mciOpenParams);
if (mciErro) {
wchar_t szErrorMsg[256];
//获取错误信息
mciGetErrorString(mciErro, szErrorMsg, 256);
MessageBox(szErrorMsg, L"MusicPlayer");
return;
}
m_DeviceID = mciOpenParams.wDeviceID;
MCI_PLAY_PARMS mciPlayParams;
mciPlayParams.dwCallback = NULL;
mciPlayParams.dwFrom = 0;
mciSendCommand(m_DeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD)&mciPlayParams);
}
//关闭播放器
void CmusicplayerDlg::OnBnClickedButton6()
{
EndDialog(IDOK);
}
void CmusicplayerDlg::OnCustomdrawSlider1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
//获取当前滑动控件位置
m_soundvalue = m_slider.GetPos()/10;
UpdateData(FALSE);
MCI_DGV_SETAUDIO_PARMS mciSetVolumn;
mciSetVolumn.dwCallback = (DWORD)GetSafeHwnd();
mciSetVolumn.dwItem = MCI_DGV_SETAUDIO_VOLUME;
mciSetVolumn.dwValue = m_slider.GetPos();//音量大小
mciSendCommand(m_DeviceID, MCI_SETAUDIO, MCI_DGV_SETAUDIO_VALUE | MCI_DGV_SETAUDIO_ITEM,(DWORD)(LPVOID)&mciSetVolumn);
*pResult = 0;
UpdateData(TRUE);
}
LRESULT CmusicplayerDlg::OnNcHitTest(CPoint point)
{
UINT nHitText = CDialogEx::OnNcHitTest(point);
//获取客户区大小
CRect rect;
GetClientRect(&rect);
rect.bottom = 30;
//判断鼠标位置
ScreenToClient(&point);
if (rect.PtInRect(point))
{
if (nHitText == HTCLIENT)
{
nHitText = HTCAPTION;
}
}
return nHitText;
}