多媒体应用技术(第2版)
上QQ阅读APP看书,第一时间看更新

2.6 实例——VC++播放声音的实现

实现音频分析的基础是对音频文件的解析。尽管音频文件的存储格式很多,但基本原理是一致的,因此,本节以WAV文件为例,介绍VC++环境中如何实现声音文件的播放。

在VC++中可以根据不同的应用要求,用不同的方法实现声音的播放。第一种方法是直接调用声音播放函数。第二种方法是把声音作为资源进行播放。第三种方法是对声音处理后播放,这种方法在播放之前可以对声音数据进行处理。

1. 直接调用声音播放函数

VC++中的多媒体动态链接库提供了一组与音频设备有关的函数,如果只需要简单地播放声音文件,利用这些函数即可实现。最简单的播放声音的方法就是直接调用VC++中提供的如下声音播放函数:BOOL sndPlaySound(LPCSTR lpszSound,UINT fuSound)或BOOL PlaySound(LPCSTR lpszSound, HMODULE hmod, DWORD fuSound)。

其中参数lpszSound是需要播放声音的.WAV文件的路径和文件名,hmod在这里为NULL,fuSound是播放声音的标志。例如播放C:\sound\music.wav可以用sndPlaySound("c:\\sound\\music.wav", SND_ASYNC)或PlaySound("c:\\sound\\music.wav",NULL, SND_ASYNC|SND_NODEFAULT)。

如果没有找到music.wav文件,第一种格式将播放系统默认的声音,第二种格式不会播放系统默认的声音。

2. 把声音文件作为资源进行播放

除了简单的声音文件的播放之外,还可以将声音文件作为资源进行播放。在VC++的程序设计中,可以利用各种标准的资源(如位图、菜单、对话框等),同时VC++也允许用户自定义资源。因此可以将声音文件作为用户自定义资源加入程序资源文件中,经过编译链接生成EXE文件,实现WAV文件的声音播放。

要实现作为资源的声音文件的播放,首先要在资源管理器中加入待播放的声音文件。假设生成的声音文件资源标识符为IDR_WAVE1。作为资源的声音文件的播放有两种方法。

第一种播放方法是在播放时调用下面的语句:

  PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(),SND_ASYNC|SND_RES
OURCE|SND_NODEFAULT|SND_LOOP);

其中MAKEINTRESOURCE()宏将整数资源标识符转变为字符串,AfxGetResourceHandle()函数返回包含资源的模块句柄,SND_RESOURCE是必需的标志。

第二种播放方法是把资源读入内存后作为内存数据播放。具体步骤如下。

(1)获得包含资源的模块句柄。

  HMODULE hmod=AfxGetResourceHandle();

(2)检索资源块信息。

  HRSRChSndResource=FindResource(hmod,MAKEINTRESOURCE(IDR_WAVE1), _T("WAVE"));

(3)装载资源数据并加锁。

  HGLOBAL hGlobalMem=LoadResource(hmod,hSndResource);
  LPCTSTR lpMemSound=(LPCSTR)LockResource(hGlobalMem);

(4)播放声音文件。

  sndPlaySound(lpMemSound,SND_MEMORY));

(5)释放资源句柄。

  FreeResource(hGlobalMem);

3. 对声音处理后播放

如果需要灵活地对声音文件进行各种处理,就需要利用VC++中提供的一组对音频设备及多媒体文件直接进行操作的函数。

首先介绍几个要用到的数据结构。WAVEFORMATEX结构定义了WAV音频数据文件的格式。WAVEHDR结构定义了波形音频缓冲区。读出的数据首先要填充此缓冲区才能被送到音频设备播放。WAVEOUTCAPS结构描述了音频设备的性能。MMCKINFO结构包含了RIFF文件中一个块的信息。下面给出程序源代码清单,该源化码在VC++环境下可直接使用。

  LPSTR szFileName;//声音文件名
  MMCKINFO mmckinfoParent;
  MMCKINFO mmckinfoSubChunk;
  DWORD dwFmtSize;
  HMMIO m_hmmio;//音频文件句柄
  DWORD m_WaveLong;
  HPSTR lpData;//音频数据
  HANDLE m_hData;
  HANDLE m_hFormat;
  WAVEFORMATEX * lpFormat;
  DWORD m_dwDataOffset;
  DWORD m_dwDataSize;
  WAVEHDR pWaveOutHdr;
  WAVEOUTCAPS pwoc;
  HWAVEOUT hWaveOut;
  //打开波形文件
  if(!(m_hmmio=mmioOpen(szFileName,NULL,MMIO_READ|MMIO_ALLOCBUF)))
  {//File open Error
       Error("Failed to open the file.");//错误处理函数
       return false;
       }
       //检查打开文件是否是声音文件
       mmckinfoParent.fccType =mmioFOURCC('W','A','V','E');
       if(mmioDescend(m_hmmio,(LPMMCKINFO)&mmckinfoParent,NULL,MMIO_FINDRIFF))
       {
       //NOT WAVE FILE AND QUIT
       }
  //寻找 'fmt' 块
       mmckinfoSubChunk.ckid =mmioFOURCC('f','m','t',' ');
       if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
       {
       //Can't find 'fmt' chunk
       }
       //获得 'fmt '块的大小,申请内存
       dwFmtSize=mmckinfoSubChunk.cksize ;
       m_hFormat=LocalAlloc(LMEM_MOVEABLE,LOWORD(dwFmtSize));
       if(!m_hFormat)
       {
       //failed alloc memory
       }
       lpFormat=(WAVEFORMATEX*)LocalLock(m_hFormat);
       if(!lpFormat)
       {
       //failed to lock the memory
       }
       if((unsigned long)mmioRead(m_hmmio,(HPSTR)lpFormat,dwFmtSize)!=dwFmtSize)
       {
       //failed to read format chunk
       }
       //离开 'fmt' 块
       mmioAscend(m_hmmio,&mmckinfoSubChunk,0);
       //寻找 'data' 块
       mmckinfoSubChunk.ckid=mmioFOURCC('d','a','t','a');
       if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
       {
       //Can't find 'data' chunk
       }
       //获得 'data'块的大小
       m_dwDataSize=mmckinfoSubChunk.cksize ;
       m_dwDataOffset =mmckinfoSubChunk.dwDataOffset ;
       if(m_dwDataSize==0L)
       {
       //no data in the 'data' chunk
       }
       //为音频数据分配内存
       lpData=new char[m_dwDataSize];
       if(!lpData)
       {
       fail
       }
       if(mmioSeek(m_hmmio,SoundOffset,SEEK_SET)<0)
       {
       //Failed to read the data chunk
       }
       m_WaveLong=mmioRead(m_hmmio,lpData,SoundLong);
       if(m_WaveLong<0)
       {
       //Failed to read the data chunk
       }
       //检查音频设备,返回音频输出设备的性能
       if(waveOutGetDeVCaps(WAVE_MAPPER,&pwoc,sizeof(WAVEOUTCAPS))!=0)
       {
       //Unable to allocate or lock memory
       }
       //检查音频输出设备是否能播放指定的音频文件
       if(waveOutOpen(&hWaveOut,DevsNum,lpFormat,NULL,NULL,CALLBACK_NULL)!=0)
       {
       //Failed to OPEN the wave out devices
       }
       //准备待播放的数据
       pWaveOutHdr.lpData =(HPSTR)lpData;
       pWaveOutHdr.dwBufferLength =m_WaveLong;
       pWaveOutHdr.dwFlags =0;
       if(waveOutPrepareHeader(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
       {
       //Failed to prepare the wave data buffer
       }
       //播放音频数据文件
       if(waveOutWrite(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
       {
       //Failed to write the wave data buffer
       }
       //关闭音频输出设备,释放内存
       waveOutReset(hWaveOut);
       waveOutClose(hWaveOut);
       LocalUnlock(m_hFormat);
       LocalFree(m_hFormat);
       delete [] lpData;

如下几点说明。

(1)以上使用的音频设备和声音文件操作函数的声明包含在mmsystem.h头文件中,因此在程序中必须用#include "mmsystem.h"语句加入头文件。同时在编译时要加入静态链接导入库winmm.lib,具体实现方法是从Developer Studio的Project菜单中选择Settings,然后在Link选项卡上的Object/Library Modules控制中加入winmm.lib。

(2)在pWaveOutHdr.lpData中指定不同的数据,可以播放音频数据文件中任意指定位置的声音。

(3)以上程序均在VC++6.0中调试通过,文中省略了对错误及异常情况的处理,在实际应用中必须加入。