文章摘要:
  本文讨论并实现了在VC++中使用低级音频函数WaveX播放声音文件的方法。

---------------------------------------------------------------------------------------------------------------------

Windows通过高级音频函数、媒体控制接口MCI设备驱动程序;低级音频函数MIDI Mapper、低级音频设备驱动;以及DirectSound提供了音频服务,可以从声卡获取音频流。

1. 播放声音文件的其它方法
   在介绍wavex系列之前,我先来介绍之外的其它几种方法:

1.1 MCI方法简介
   
    用MCI方法是很方便的,它对媒体设备控制主要通过命令接口函数mciSendCommand()或者字符串接口函数mciSendString()来完成的,这两个函数的作用相同。命令接口函数比命令字符串使用起来要复杂,但它为MCI提供了更为强大的控制能力,两个接口函数的原型:
MCIERROR mciSendCommand(MCIDEVICEID IDDevice,UINT uMsg,DWORD fdwCommand,DWORD dwParam);
MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback);
比如要使用mciSendCommand方法,我们先在MCI_OPEN_PARMS中设置要播放的文件并发送MCI_OPEN命令打开声音设备,发送MCI_PLAY命令消息播放,结束后发送MCI_STOP命令关闭设备。关于它们的具体使用方法可以参考MSDN。

1.2 PlaySound方法
   
    BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound );
    BOOL PlaySound(LPCSTR pszSound,HMODULE hmod, DWORD fdwSound);
    其中参数lpszSound是需要播放声音的.WAV文件的路径和文件名,hmod在这里为NULL,fuSound是播放声音的标志,详细说明请参考VC++中的帮助。 例如播放C:/sound/music.wav可以用sndPlaySound ("c://sound//music.wav",SND_ASYNC);或PlaySound("c://sound//music.wav",NULL, SND_ASYNC|SND_NODEFAULT );如果没有找到music.wav文件,第一种格式将播放系统默认的声音,第二种格式不会播放系统默认的声音[1],这是SND_NODEFAULT标志的作用。
    当然我们也可以将声音文件作为用户自定义资源加入程序资源文件中,经过编译连接生成EXE文件,这样就可以实现无.WAV文件的声音播放。利用上面的函数也很简单,如下,其中IDR_YOUR_WAVE是加入的wave文件资源标识符:
    PlaySound(MAKEINTRESOURCE(IDR_YOUR_WAVE),GetModuleHandle(NULL), SND_RESOURCE);

2. 使用低级音频函数WaveX
   
    下面将进入文章的主题。

2.1 概述

低层音频服务及重要的数据结构低级音频服务控制着不同的音频设备,这些设备包括WAVE、MIDI和辅助音频设备[2]。低级音频服务包括如下内容:(1)查询音频设备;(2)打开和关闭设备驱动程序;(3)分配和准备音频数据块;(4)管理音频数据块;(5)应用MMTIME结构;(6)处理错误。
   
2.2 重要消息及数据结构
   
    使用低级音频函数之所以能够对各个声音数据块操作,要归功于Windows的消息映射,Windows在采集、播放完一个数据块之后就会发送有关的消息。播放声音涉及到的重要消息及触发条件如下:
    MM_WOM_CLOSE:在一个波形声音输出设备关闭时发出,之后该设备句柄不再有效
    MM_WOM_DONE:当给定的输出缓存播放完毕返回给应用程序,或者直接调用waveOutReset函数停止播放并重置管理器
    MM_WOM_OPEN:当给定的波形声音输出设备被打开时
   
    MOM_CLOSE:当MIDI输出设备关闭时
    WOM_DONE:当留缓冲播放完毕并正被返回程序时发到MIDI输出回调函数
    WOM_OPEN:当MIDI输出设备打开时

重要的数据结构:
    波形数据格式 WAVEFORMAT/WAVEFORMATEX
    波形数据缓冲区格式 WAVEHDR
    音频输出设备性能 WAVEOUTCAPS
   
    这些内容都定义在mmsystem.h头文件中,更为具体的信息请参阅MSDN。
   
2.3 wavex播放声音波形文件方法的大致流程

常用mmio函数:
    mmioOpen( ) 打开一个RIFF文件
    mmioDescend ( ) 进入块
    mmioRead( ); 该取RIFF文件
    mmioAscend ( ); 跳出块
    mmioClose( ); 关闭PIFF文件
    对于块来说,进入块和跳出块是配对的。

读取WAV文件的读取过程:
    mmioOpen( ) 打开文件
    ↓
    mmioDescend ("WAVE") 进入"fmt"块
    ↓
    mmioRead( ) 读取WAVE文件格式信息
    ↓
    mmioAscend ( ) 跳出"fmt"块
    ↓ 
    mmioDescend ("data") 进入"data"块
    ↓
    mmioRead( ) 读取WAVE数据信息
    ↓
    mmioClose( ) 关闭文件。 
   
    输出WAV文件的过程:
    WaveOutOpen () 打开一个输出设备
    ↓
    WaveOutPrepareHeader() 准备WAVE数据头。 
    ↓ 
    WaveOutWrite() 将数据写入设备并开始播放 
    ↓ 
    WaveOutReset() 停止播放并重置管理器 
    ↓ 
    WaveOutClose() 并闭播放设备 
    ↓ 
    WaveOutUnpareHeader() 清理用WaveOutPrepareHeader准备的Wave 
   
2.4 主要程序清单

2.4.1 播放部分

void CPlayWaveDlg::OnPlay()
{
 LPSTR szFileName;//声音文件名
 LPSTR szPathName;
 MMCKINFO mmckinfoParent;
 MMCKINFO mmckinfoSubChunk;
 DWORD dwFmtSize;
 DWORD m_WaveLong;
 WAVEFORMATEX* lpFormat;
 DWORD m_dwDataOffset;
 DWORD m_dwDataSize;
 WAVEOUTCAPS pwoc;
 LONG lSoundOffset;
 LONG lSoundLong;

CEdit* pEdit = (CEdit*) GetDlgItem(IDC_FILE);
 pEdit->GetWindowText(m_strFileName);
 
 if (m_strFileName == "")
 {
  ShowMsg("Please select a wave file to play!");
  return;
 }

szPathName = m_strPathName.GetBuffer(0);
 szFileName = m_strFileName.GetBuffer(0);

//打开波形文件
 if (!(m_hmmio = mmioOpen(szPathName, NULL, MMIO_READ | MMIO_ALLOCBUF)))
 {
  /*-------------------------------------------------------------------------------
  信息显示函数ShowMsg():
  void CPlayWaveDlg::ShowMsg(char* szMsg, ...)
  {
   va_list vl;
   char szBuf[256];
 
   va_start(vl, szMsg);
   vsprintf(szBuf, szMsg, vl);
   va_end(vl);
 
   ::MessageBox(NULL, szBuf, "WavePlayer", MB_OK | MB_ICONEXCLAMATION);
  }
  ---------------------------------------------------------------------------------*/
  ShowMsg("Failed to open file: %s", szFileName);
  return;
 }

//进入块,检查打开文件是否是wave文件
 mmckinfoParent.fccType = mmioFOURCC(''''W'''', ''''A'''', ''''V'''', ''''E'''');
 if (mmioDescend(m_hmmio, (LPMMCKINFO) & mmckinfoParent, NULL,
   MMIO_FINDRIFF))
 {
  ShowMsg("%s is an invalid wave file!", szFileName);
  mmioClose(m_hmmio, NULL);
  return;
 }

//寻找 ''''fmt'''' 块
 mmckinfoSubChunk.ckid = mmioFOURCC(''''f'''', ''''m'''', ''''t'''', '''' '''');
 if (mmioDescend(m_hmmio, &mmckinfoSubChunk, &mmckinfoParent,
   MMIO_FINDCHUNK))
 {
  ShowMsg("Cannot find fmt chunk in %s!", szFileName);
  mmioClose(m_hmmio, NULL);
  return;
 }

//获得 ''''fmt ''''块的大小,申请内存
 dwFmtSize = mmckinfoSubChunk.cksize ;
 m_hFormat = LocalAlloc(LMEM_MOVEABLE, LOWORD(dwFmtSize));
 if (!m_hFormat)
 {
  ShowMsg("Alloc memory failed!");
  return;
 }

lpFormat = (WAVEFORMATEX *) LocalLock(m_hFormat);
 if (!lpFormat)
 {
  ShowMsg("Lock memory failed!");
  OnStop();
  return;
 }

if ((unsigned long) mmioRead(m_hmmio, (HPSTR) lpFormat, dwFmtSize) !=
  dwFmtSize)
 {
  ShowMsg("Read format chunk of %s failed!", szFileName);
  OnStop();
  return;
 }
 //离开 fmt 块
 mmioAscend(m_hmmio, &mmckinfoSubChunk, 0);
 //寻找 ''''data'''' 块
 mmckinfoSubChunk.ckid = mmioFOURCC(''''d'''', ''''a'''', ''''t'''', ''''a'''');
 if (mmioDescend(m_hmmio, &mmckinfoSubChunk, &mmckinfoParent,
   MMIO_FINDCHUNK))
 {
  ShowMsg("Cannot find data chunk in: %s", szFileName);
  OnStop();
  return;
 }
 //获得 ''''data''''块的大小
 m_dwDataSize = mmckinfoSubChunk.cksize ;
 m_dwDataOffset = mmckinfoSubChunk.dwDataOffset ;
 if (m_dwDataSize == 0L)
 {
  ShowMsg("%s has no data!", szFileName);
  OnStop();
  return;
 }

//为音频数据分配内存
 lpData = new char[m_dwDataSize];
 if (!lpData)
 {
  ShowMsg("Alloc memory for wave data failed!");
  OnStop();
  return;
 }

lSoundOffset = m_dwDataOffset;
 LONG lSize = mmioSeek(m_hmmio, lSoundOffset, SEEK_SET);
 if (lSize < 0)
 {
  ShowMsg("Seek data chunk of %s failed!", szFileName);
  OnStop();
  return;
 }
 lSoundLong = m_dwDataSize;
 m_WaveLong = mmioRead(m_hmmio, lpData, lSoundLong);
 if (m_WaveLong < 0)
 {
  ShowMsg("Read data chunk of %s failed!", szFileName);
  OnStop();
  return;
 }
 //检查音频设备,返回音频输出设备的性能
 if (waveOutGetDevCaps(WAVE_MAPPER, &pwoc, sizeof(WAVEOUTCAPS)) != 0)
 {
  ShowMsg("waveOutGetDevCaps() failed!");
  OnStop();
  return;
 }
 
 //检查音频输出设备是否能播放指定的音频文件
 /*----------------------------------------------------------------------------------------------
 waveOutOpen函数最后三个参数的设置对消息处理方式起决定性作用,需要特别注意,通常我们用下列处理方法:
 1. 使用窗口作为消息的接收者,则第四个参数设置为该窗口的句柄,则和这次播放有关的消息都将进入该窗口的消息队列,这时           第五个参数为NULL,第六个参数为CALLBACK_WINDOW,表明由窗口的过程来处理消息。
 2. 直接使用回调函数来处理消息,则第四个参数设置为该回调函数的指针,则和这次播放有关的消息都将由该函数处理,这时第           五个参数为传入该函数的参数,第六个参数为CALLBACK_FUNCTION,表明由指定函数来处理消息。
 3. 使用新的线程来处理消息,则第四个参数设置为该线程函数的指针,和这次播放有关的消息都将由该线程处理,这时第五个参           数为传入该函数的参数,第六个参数为CALLBACK_THREAD,表明由线程来处理消息。
 4. 如果你不需要处理消息,这后面三个参数分别为NULL,NULL,CALLBACK_NULL
 ----------------------------------------------------------------------------------------------*/
 if (waveOutOpen(&hWaveOut, WAVE_MAPPER, lpFormat, (ULONG)m_hWnd, NULL, CALLBACK_WINDOW) !=
  0)
 {
  ShowMsg("Open the wave out devices failed!");
  OnStop();
  return;
 }

//准备待播放的数据
 pWaveOutHdr.lpData = (HPSTR) lpData;
 pWaveOutHdr.dwBufferLength = m_WaveLong;
 pWaveOutHdr.dwFlags = 0;
 pWaveOutHdr.dwLoops = 5;
 if (waveOutPrepareHeader(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR)) != 0)
 {
  ShowMsg("Failed to prepare the wave data buffer!");
  OnStop();
 }
 //将数据写入设备并开始播放
 if (waveOutWrite(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR)) != 0)
 {
  ShowMsg("Failed to write the wave data buffer");
  OnStop();
 }
}

2.4.2 停止播放部分

void CPlayWaveDlg::OnStop()
{
 if (m_hmmio != NULL)
 {
  mmioClose(m_hmmio, NULL);
 }
 //停止播放并重置管理器
 waveOutReset(hWaveOut);
 //关闭播放设备
 waveOutClose(hWaveOut);
 //清理用WaveOutPrepareHeader准备的Wave。
 waveOutUnprepareHeader(hWaveOut, &pWaveOutHdr, sizeof(WAVEHDR));

//释放内存
 if (m_hFormat != NULL)
 {
  LocalUnlock(m_hFormat);
  m_hFormat = NULL;
 }
 
 if (m_hFormat != NULL)
 {
  LocalFree(m_hFormat);
  m_hFormat = NULL;
 }
 if (lpData != NULL)
 {
  delete[] lpData;
  lpData = NULL;
 }
}

2.4.3 处理消息部分:

添加消息映射:ON_MESSAGE(MM_WOM_DONE,OnMMWomDone)

void CPlayWaveDlg::OnMMWomDone(UINT wParam, LONG lParam)
{
// ShowMsg("Play finished!");
 OnStop();
}

2.4.4 相关头文件

/*-----------------------------------------------------------------------------------------------------------------------
说明:本文介绍的操作函数的声明包含在mmsystem.h头文件中,因此在程序中必须用#include "mmsystem.h"语句加入头文件。同时在编译时要加入动态连接导入库winmm.lib,具体实现方法有两种:
1. 从Developer Studio的Project菜单中选择Settings,然后在Link选项卡上的Object/Library Modules控制中加入winmm.lib
2. 如下所示在代码中加入#pragma comment(lib, "winmm.lib")
-----------------------------------------------------------------------------------------------------------------------*/
#include "mmsystem.h"
#pragma comment(lib, "winmm.lib")

class CPlayWaveDlg : public CDialog
{

//省略与播放无关部分
//................

protected:

HANDLE m_hData;
 HWAVEOUT hWaveOut;
 WAVEHDR pWaveOutHdr;
 HANDLE m_hFormat;
 HPSTR lpData;//音频数据
 HMMIO m_hmmio;//音频文件句柄

CString m_strPathName;
 CString m_strFileName;

void ShowMsg(char *szMsg, ...);

afx_msg void OnPlay();
 afx_msg void OnStop();
 
 afx_msg void OnMMWomDone(UINT wParam, LONG lParam);
 DECLARE_MESSAGE_MAP()
};

以上代码在Visual C++ 6.0 + windows 2000 pro 上通过。

3. 应用
   
    低级音频函数能够具体的在内存中对各个声音数据块进行细节控制,比如可以通过检测声音的振幅强度进行声音采集的筛选,或者进行声音文件的剪切合并等,这就为声音文件的灵活操作提供了很好的方法;因为它能够操作声音数据块,从而也能为声音的实时传输提供有效的途径。
   
参考文献:
1. 李灿伟 VC++中播放声音的方法
2. 李博轩 Visuanl C++ 6.0多媒体开发指南。北京:清华大学出版社,2000年2月.71-75

VC中使用低级音频函数WaveX播放声音文件相关推荐

  1. ffmpeg-从flv文件中提取AAC音频数据保存为文件

    AAC ADTS格式协议: 从flv文件中提取AAC音频数据保存为文件. 如果需要详细了解AAC ADTS格式,可以查询文档. 原文件: 提取aac文件: main.c #include <st ...

  2. 在VC中使用MATLAB C++函数库

    http://Tech.16C.Cn 在VC中使用MATLAB C/C++函数库 MATLAB广泛应用于线性代数.自动控制理论.数理统计.数字信号处理.时间序列分析.动态系统仿真等领域.因此如果在VC ...

  3. delphi 裁剪mp3_如何在Delphi中读取用于音频处理的MP3文件?[关闭]

    当我想处理字节级的音频时,我总是将其转换为.wav格式,然后进行处理.例如,在上一个项目中,我试图生成一种特殊的音频文件波形图像,以便在视频剪辑中使用.然后我用一个在线工具把我的.mp3文件转换成.w ...

  4. VC中对CString 的读写(ini文件)

    //此处为写 CString a,b; a="as"; b="sa" CString strFileName;  CString strKeyName;  CS ...

  5. 在PHP中,通过filesize函数可以取得文件的大小,文件大小是以字节数表示的。如果要转换文件大小的单位,可以自己定义函数来实现。

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/miaoyitao/article/details/40185347 <?php functio ...

  6. VC中基于 Windows 的精确定时

    方式一:VC中的WM_TIMER消息映射能进行简单的时间控制.首先调用函数SetTimer()设置定时 间隔,如SetTimer(0,200,NULL)即为设置200ms的时间间隔.然后在应用程序中增 ...

  7. VC++中文件类型小结

    为什么80%的码农都做不了架构师?>>>    .dsw---- 这种类型的文件在VC中是级别最高的,称为Workspace文件   .dsp---- 在VC中,应用程序是以Proj ...

  8. VC中MessageBox与AfxMessageBox用法与区别

    一.MessageBox()用法 1.函数原型 Messagebox函数在Win32 API和MFC里的定义有区别. Win32 API的定义如下: int WINAPI MessageBox(HWN ...

  9. VC++中播放声音wav

    因为只需在Windows上执行,先想到用MCI接口.试了一下,用mciSendCommand可以实现基本的播放wav文件的功能.但循环播放wav就麻烦了,必须向窗口传送MM_MCINOTIFY消息. ...

最新文章

  1. YY项目之TabLayout自定义Tab的title
  2. 深入理解C程序内存布局
  3. js 深拷贝,浅拷贝
  4. Django处理MySQL事物的用法
  5. java接口的叙述正确的_下列关于接口的描述中,正确的是:
  6. Word2vector原理
  7. 超级简单的纯js 象棋,看一遍你也会写
  8. GoldenGate Enterprise Manager Plug-In(12.1.0.3.0) 部署文档
  9. php冒泡排序图解,PHP冒泡排序(Bubble Sort)代码实现图解
  10. 光纤与光通信-基础知识
  11. DHTMLXGantt and DHTMLXGantt pro
  12. 总结在CentOS7上搭建CUDA10+cudnn7的Tensorflow-gpu环境的经验
  13. 2021年中国程序员薪资和生活现状调查:年薪5-25万之间占比66.3%
  14. amd cpu排行_2019 CPU天梯图(intel和AMD CPU性能排行)
  15. 端午百望山爬山活动-金山词霸运营团队活动
  16. 《卸甲笔记》-分组统计查询对比
  17. Oracle建表——图书表
  18. 计算机键盘操作指法要求,技巧:计算机键盘的基本指法是什么?
  19. 前端培训班哪些比较靠谱呢?
  20. iStylePDF vb版示例

热门文章

  1. oracle中over()分析函数的用法
  2. 2021巨量引擎连锁经营行业洞察报告
  3. 互联网日报 | 7月19日 星期一 | 美团外卖成立骑手服务部;金山办公发布“文档中台”;一汽-大众奥迪在华销量突破700万辆...
  4. 三公里社区争夺战—2021年社区团购研究报告
  5. java程序课程总结_Java课程总结报告.pdf
  6. 拓展 欧几里得算法 求逆元_ECC椭圆曲线加密算法:有限域和离散对数
  7. vant引入组件报错_强烈推荐优秀的Vue UI组件库
  8. 作者:曾琛(1987-),女,就职于中国科学院计算技术研究所。
  9. 移动应用开发——实验六
  10. 【软件测试】黑盒测试の场景测试法