波形音频播放器 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

一、  实验目的

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中。

winmm.lib")//为了要播放声音,必须导入这个库

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<INFO_WAVEWIDE;)

        {

          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