波形音频播放器
一、
实验目的
1.
了解Wave文件格式,并学习Windows下用MCI函数播放Wave文件。
2.
熟悉多媒体开发工具——
Visual C++
。
3.
制作波形音频播放器。其运行界面如下图所示:
要求此播放器具有打开、播放、录制及保存波形音频文件的功能。
二、
预备知识
1.
VC6编程方法,MCI编程方法。
2.
Wave
文件的结构
用
.wav为扩展名的文件格式称为波形文件格式(WAVE File Format),
它是一种
资源交换文件格式(Resource Interchange File Format,RIFF)
,RIFF格式是面向部分(chunk)的,一个RIFF文件是由一个或多个部分组成的,其中每一个部分都指向下一个部分。下图是一个RIFF文件结构的示意:
波形文件格式支持存储各种采样频率和样本精度的声音数据,并支持声音数据的压缩。一个WAVE文件至少包含三个块RIFF块是其中 最大的,整个WAVE文件就是一个RIFF块。Cksize紧跟在“RIFF”CKID之后出现,它包含一个值,等于文件的大小减去8个字节,这8个字节用来存储RIFF的CKID和CKSIZE。第二和第三块称为子块,包含在RIFF块之中。这些块的第一个块是“fmt”块,包含PCMWAVEFORMAT结构所需要的信息;第二个块“data”块紧跟在“fmt”之后,包含所有的数据波形。RIFF的CKSIZE等于“fmt”块和“data”块所占用的字节之和。
为了读写RIFF文件,用户使用为多媒体块信息准备的叫MMCKINFO的标准的数据结构。在VC中这个结构定义为:
typedef sruct {
FOURCC ckid;
DWORD cksize;
FOURCC fccType;
DWORD dwDataOffset;
DWORD dwFlags;
}MMCKINFO;
设计多媒体程序,关键是对多种多媒体设备的控制和使用,在Windows操作系统中,对多媒体设备进行控制主要有三种方法:第一种方法是使用微软公司窗口系统中对多媒体支持的MCI,即媒体控制接口,MCI是多媒体设备和多媒体应用软件之间进行设备无关的沟通的桥梁。在VB和VC中MCI都得到了很好的支持;第二种方法,通过调用Windows的API(应用程序接口)多媒体相关函数实现媒体控制;第三种方法是使用OLE(Object Linking & Embedding),即对象嵌入和链接技术,它为不同数据之间共享数据和资源提供了有利的手段。
三、
实验步骤:
1.
用VC建立应用程序框架:
a)
打开文件菜单,点击新建,在弹出的对话框中选中
MFC AppWizard (exe)
后,选好你文件所需存储的目录,然后在工程中填入工程文件名,如下图所示:
点击确定按钮在弹出的对话框中,按下图进行设置后,点击“完成”。
b)
编制应用程序界面:
各控件的属性如下表所示:
控件
|
ID
|
标题
|
控件
|
ID
|
标题
|
Group Box
|
IDC_STATIC
|
波形音频信息
|
Static
|
IDC_RESOLUTION
|
|
Static
|
IDC_STATIC
|
波形文件:
|
Static
|
IDC_WAVE_LENGTH
|
|
Static
|
IDC_STATIC
|
声道:
|
Button
|
IDC_OPEN
|
打开
|
Static
|
IDC_STATIC
|
采样率:
|
Button
|
IDC_PLAY
|
播放
|
Static
|
IDC_STATIC
|
音频长度:
|
Button
|
IDC_RECORD
|
录制
|
Static
|
IDC_WAVE_NAME
|
|
Button
|
IDC_SAVE
|
保存
|
Static
|
IDC_CHANNEL
|
|
Button
|
IDC_STOP
|
停止
|
Static
|
IDC_SAMPLE_RATE
|
|
Picture
|
IDC_WAVE_GRAPH
|
|
c)
点击查看
à
建立类向导,在
Message Maps
中建立相应的消息函数
OnOpen(), OnPlay(), OnRecord(), OnSave(), OnStop()
,点击确定。
2.
建立处理波形音频的类
CWaveAudio:
工程
à
添加工程
à
Components and Controls
,选中Wave Audio.ogx文件
à
插入,可重用类
CWaveAudio
便添加到工程中。在C
W
avePlayerDlg类的头文件中将
C
W
aveAudio
的头文件包含进来。
3.
加入所需的功能:
a)
在C
W
avePlayerDlg类中加入以下成员变量
CString path;//文件目录
CWaveAudio m_Wave;//可重用类的对象
int INFO_WAVEWIDE;//绘图框的宽度
int INFO_WAVEDEEP;//绘图框的高度
bool m_bOpen;//判断是否有波形音频文件打开
右键点击
Class
中的C
W
avePlayerDlg,选中
Add Number Function
,如下所示,将绘图函数
void DrawWave(HDC hdc, unsigned int x, unsigned int y, char* path)加到C
W
avePlayerDlg中。
|
b)
在BOOL CWavePlayerDlg::OnInitDialog()中进行初始化:
CWnd* m_pWnd;
m_pWnd=GetDlgItem(IDC_PLAY);
m_pWnd->EnableWindow(false);
m_pWnd=GetDlgItem(IDC_SAVE);
m_pWnd->EnableWindow(false);
c)
在
OnOpen(), OnPlay(), OnRecord(), OnSave(), OnStop()
中添加代码如下:
void CWavePlayerDlg::OnOpen()
{
// TODO: Add your control notification handler code here
CString Filter,str;
Filter= "波形音频文件(*.WAV)|*.WAV||";
CFileDialog FileDlg(true,NULL,NULL,OFN_HIDEREADONLY,Filter);
if(FileDlg.DoModal()==IDOK)
{
if(!m_Wave.Load(FileDlg.GetFileName()))
{
MessageBox("不能打开文件!","错误",MB_OK|MB_ICONSTOP);
}
CWnd* m_pWnd;
m_pWnd=GetDlgItem(IDC_PLAY);
m_pWnd->EnableWindow(true);
}
path=FileDlg.GetPathName();
SetDlgItemText(IDC_WAVE_NAME,FileDlg.GetFileName());
str.Format("%5.3f",m_Wave.GetSampleRate()/1000.);
str+=_T("kHZ");
SetDlgItemText(IDC_SAMPLE_RATE,str);
str.Empty();
switch(m_Wave.GetChannel())
{
case 1:
str="单声道";
break;
case 2:
str="立体声";
break;
}
SetDlgItemText(IDC_CHANNEL,str);
str.Empty();
str.Format("%d",m_Wave.GetResolution());
str+="位";
SetDlgItemText(IDC_RESOLUTION,str);
str.Empty();
str.Format("%2.2u:%02.2f:%02.2u",m_Wave.GetWaveLength()/1000/60,
m_Wave.GetWaveLength()/1000.,m_Wave.GetWaveLength()/1000/3600);
SetDlgItemText(IDC_WAVE_LENGTH,str);
m_bOpen=true;
}
void CWavePlayerDlg::OnPlay()
{
// TODO: Add your control notification handler code here
m_Wave.Play();
}
void CWavePlayerDlg::OnRecord()
{
// TODO: Add your control notification handler code here
CWnd* m_pWnd;
m_pWnd=GetDlgItem(IDC_PLAY);
m_pWnd->EnableWindow(false);
m_pWnd=GetDlgItem(IDC_OPEN);
m_pWnd->EnableWindow(false);
m_pWnd=GetDlgItem(IDC_SAVE);
m_pWnd->EnableWindow(false);
m_Wave.Record();
}
void CWavePlayerDlg::OnSave()
{
// TODO: Add your control notification handler code here
CString Filter;
Filter="Wave File(*.WAV)|*.WAV||";
CFileDialog FileDlg(false,NULL,NULL,OFN_OVERWRITEPROMPT,Filter);
FileDlg.m_ofn.lpstrDefExt="wav";
if(FileDlg.DoModal()==IDOK)
m_Wave.Save(FileDlg.GetPathName());
}
void CWavePlayerDlg::OnStop()
{
// TODO: Add your control notification handler code here
CWnd* m_pWnd;
m_pWnd=GetDlgItem(IDC_PLAY);
m_pWnd->EnableWindow(true);
m_pWnd=GetDlgItem(IDC_OPEN);
m_pWnd->EnableWindow(true);
m_pWnd=GetDlgItem(IDC_SAVE);
m_pWnd->EnableWindow(true);
m_Wave.Stop();
}
现在可以编译运行一下,可以发现你已经实现了绝大多数的功能。
d)
现在,我们将绘图功能添加进去,由于对话框的变动都会触发调用
OnPaint()
函数重绘客户区,我们在
OnPaint()
中加入以下代码:
void CWavePlayerDlg::OnPaint()
{
if (IsIconic())
{
…
…
}
else
{
CDialog::OnPaint();
if(m_bOpen)
{
CWnd* m_pWnd;
CRect rect;
m_pWnd=GetDlgItem(IDC_WAVE_GRAPH);
m_pWnd->GetClientRect(rect);
INFO_WAVEWIDE=rect.Width();
INFO_WAVEDEEP=rect.Height();
CClientDC dc(m_pWnd);
DrawWave(dc.m_hDC,rect.left,rect.top,(char*)path.operator LPCTSTR());
}
}
}
我们就在
OnPaint()
中调用了DrawWave函数绘制波形,DrawWave函数的代码如下:
void CWavePlayerDlg::DrawWave(HDC hdc, unsigned int x, unsigned int y, char*path)
{
HMMIO h;
MMCKINFO mmParent,mmSub;//MMCKINFO结构中包含了有关部分的信息
GLOBALHANDLE gh;
PCMWAVEFORMAT waveformat;//fmt部分结构
char *p;
unsigned long nextsample;
long afactor;
unsigned int i,n,amp;
int *ip;
HPEN OldPen=(HPEN)SelectObject(hdc,GetStockObject(BLACK_PEN));
HBRUSH OldBrush=(HBRUSH)SelectObject(hdc,GetStockObject(WHITE_BRUSH));
Rectangle(hdc,x,y,x+INFO_WAVEWIDE,y+INFO_WAVEDEEP);
if((h=mmioOpen(path,NULL,MMIO_READ))==NULL)
return;
mmParent.fccType=mmioFOURCC('W','A','V','E');
if(mmioDescend(h,(LPMMCKINFO)&mmParent,NULL,MMIO_FINDRIFF))
{
mmioClose(h,0);
return;
}
mmSub.ckid=mmioFOURCC('f','m','t',' ');
if(mmioDescend(h,(LPMMCKINFO)&mmSub,(LPMMCKINFO)&mmParent,MMIO_FINDCHUNK))
{
mmioClose(h,0);
return;
}
n=min((unsigned int)mmSub.cksize,sizeof(PCMWAVEFORMAT));
if(mmioRead(h,(LPSTR)&waveformat,n)!=( int)n)
{
mmioClose(h,0);
return;
}
if(waveformat.wf.wFormatTag!=WAVE_FORMAT_PCM)
{
mmioClose(h,0);
return;
}
mmioAscend(h,&mmSub,0);//当读出一个部分的数据后,退出该部分
mmSub.ckid=mmioFOURCC('d','a','t','a');
if(mmioDescend(h,(LPMMCKINFO)&mmSub,(LPMMCKINFO)&mmParent,MMIO_FINDCHUNK))
{
mmioClose(h,0);
return;
}
if(waveformat.wBitsPerSample==8 && waveformat.wf.nChannels==1)
{
nextsample=mmSub.cksize/(long)INFO_WAVEWIDE;
afactor=2L*(255L/(long)INFO_WAVEDEEP);
}
else if(waveformat.wBitsPerSample==8 && waveformat.wf.nChannels==1)
{
nextsample=2L*((mmSub.cksize/2L)/(long)INFO_WAVEWIDE);
afactor=2L*(255L/(long)INFO_WAVEDEEP);
}
else if(waveformat.wBitsPerSample>8 && waveformat.wf.nChannels==1)
{
nextsample=2L*((mmSub.cksize/(long)INFO_WAVEWIDE))& 0xfffffffeL;
afactor=2L*(65535L/(long)INFO_WAVEDEEP);
}
else
{
nextsample=4L*((mmSub.cksize/4L)/(long)INFO_WAVEWIDE)&0xfffffffeL;
afactor=2L*(65535L/(long)INFO_WAVEDEEP);
}
MoveToEx(hdc,x,y+INFO_WAVEDEEP/2,NULL);
LineTo(hdc,x+INFO_WAVEWIDE,y+INFO_WAVEDEEP/2);
if((gh=GlobalAlloc(GMEM_MOVEABLE,mmSub.cksize))!=NULL)
{
if((p=(char*)GlobalLock(gh))!=NULL)
{
if(mmioRead(h,p,mmSub.cksize)==mmSub.cksize)
{
for(i=0;i
{
ip=(int *)p;
if(waveformat.wBitsPerSample==8&&waveformat.wf.nChannels==1)
amp=(unsigned int)max(labs(((long)p[0]-128L)/afactor),1L);
else if(waveformat.wBitsPerSample==8&&waveformat.wf.nChannels==2)
amp=(unsigned int)max(labs(((long)p[0]-128L+(long)p[1]-128L)/2)
*
/afactor,1L);
else if(waveformat.wBitsPerSample>8&&waveformat.wf.nChannels==1)
amp=(unsigned int)max(labs((long)ip[0]/afactor),1L);
else
amp=(unsigned int)max(labs((((long)ip[0]+(long)ip[1])/2)/afactor),1L);
if(amp>(unsigned int)INFO_WAVEDEEP/2)
amp=INFO_WAVEDEEP/2-3;
MoveToEx(hdc,x+i,y+(INFO_WAVEDEEP/2)-amp,NULL);
LineTo(hdc,x+i,y+(INFO_WAVEDEEP/2)+amp);
i+=2;
p+=nextsample;
}
}
GlobalUnlock(gh);
}
GlobalFree(gh);
}
SelectObject(hdc,OldPen);
(HBRUSH)SelectObject(hdc,OldBrush);
mmioClose(h,0);
return;
}
现在编译执行你的程序,就会发现一个简单的波形音频播放器已经完成了。
实验注意事项:
1.
调试程序时,
Ctrl+F7
为
Compile
,
Build
为
F
7,运行时按
F5
。
2.
注意可重用类
CwaveAudio
的设计。
3.
熟练掌握
MCI
多媒体函数的应用。
编译时,菜单选中“项目”
à
“设置”,要在库的链接中加入
winmm.lib
。