分类: Windows平台
2015-04-21 15:29:34
平时,你在多媒体软件的设计中是怎样处理声音文件的呢?使用Windows 提供的API函数 sndPlaySound来实现WAV文件的播放?但是,你有没有遇到过这种情况呢:当WAV文件大于可用内存时,sndPlaySound 函数就不能进行播放!!!那么,如何利用MCI播放大型音频文件呢?
本文将介绍一种方法。
Windows支持两种RIFF(resource interchange file format,“资源交互文件格式”)音 频文件:MIDI的RMID文件和波形音频文件格式WAV文件,本文将介绍如何用MCI命令播放大型WAV文件。用sndPlaySound播放音频文件只需要一行代码。比如实现异步播放的方法为 sndPlaySound("c:\windows\ding.wav",SND_ASYNC);
由此可以看到,sndPlaySound 的使用是很简单的。但是用sndPlaySound播放音频文件有 一个限制,即整个音频文件必须全部调入可用的物理内存。因此应用sndPlaySound播放的音频 文件相对较小,最大约100K。要播放大一些的音频文件(在多媒体设计中是经常要遇到的情况 )需要使用MCI的功能。这里创建了一个Cwave类,可以处理播放音频的MCI命令,因为该类能够 执行很多的MCI命令和建立了数据结构,所以只需要简单的成员函数(如OpenDevice, CloseDe vice, Play和Stop)。在CWave类中抽象了特定的MCI命令和数据结构,只含几个简单的成员函 数OpenDevice, CloseDevice, Play和Stop。波形音频设备是一个复合设备,如果打开波形设 备,然后打开并关闭每个波形元素,最后关闭波形设备,这样可以使得播放性能更好。调用C wave::OpenDevice就可以打开波形设备,OpenDevice将MCI_OPEN命令传递给mciSendCommand函 数,如果调用成功,就用数据结构MCI_OPEN_PARMS的wDeviceID成员返回波形设备的标识符, 该标识符保存在一个供以后使用的私有数据成员中。一旦打开了Cwace对象,通过Cwace::Pla y播放WAV文件就就绪了,WAV文件名和一个窗口指针被传递给Play方法以便将MCI通知消息发送 到制定的窗口。
WAV文件的播放首先要通过分配一个MCI_OPEN_PARMS结构并给所要播放的WAV文件设置lpstrElementName成员打开WAV文件。将该结构和MCI_OPEN传递给mciSendCommand,打开WAV文件 并用MCI_OPEN_PARMS结构的wDeviceID成员返回元素标识符。第二步是命令波形音频设备播放 WAV文件。分配了MCI_PLAY_PARMS结构并将dwCallback成员设置为窗口句柄。如果要同步播放 音频波形文件,就增加MCI_WAIT标志并跳过窗口句柄。这样做会使应用程序在mciSendComman d函数返回之前等待WAV文件播放完毕。最可能的情况是异步播放大型WAV文件,可以象下面那 样指定MCI_NOTIFY标志并设置dwCallback成员做到这一点。
跳转至:
使用MCI播放音频文件,MCI为我们提供了两种方式访问MCI设备或文件:一种是基于消息的命令接口函数;另一种是使用字符串接口函数。两者的区别在于基本命令结构和发送信息到设备的原理。
◆、基于消息的MCI
消息命令控制接口使用消息控制MCI设备,将消息和控制信息以数据结构的形式作为函数参数发送,并接收返回的数据,MCI直接把设备消息和控制消息发送到设备。一条基于MCI的命令包含以下3个部分。
- 数据结构:该结构包含可传递给MCI驱动程序的信息和从驱动程序返回的值,指定要执行的MCI命令一个常量,如MCI_OPEN、MCI_CLOSE...
- 一个或一组用来指定MCI信息子选项的标志:这些标志用来确定可以得到什么类型的信息和如何执行函数。
- 一个确定命令附加参数:Windows MDK为使用命令消息接口发送MCI消息提供了3个核心函数。
◆、基于字符串的MCI1、mciSendCommand函数。该函数用于向MCI设备发送一个命令消息,原型为:
MCIERROR mciSendCommand(MCIDEVICEID IDDevice, UINT uMsg, DWORD fdwCommand, DWORD dwParam);
参数说明如下:
IDDevice:用来表示一个MCI设备。MCI使用MCI_OPEN消息打开一个设备时自动创建一个标识号用以唯一标识要操作的设备,以后的命令操作均使用此标识。
uMsg:表示要发出的消息,取值如下所示。
MCI_OPEN 打开一个设备
MCI_CLOSE 关闭一个设备
MCI_PLAY 播放全部或部分音频,从暂停状态恢复播放
MCI_STOP 停止播放
MCI_PAUSE 暂停播放
MCI_SEEK 改变当前位置
MCI_CUE 提示一个设备以最小的延迟开始播放或录制
MCI_RECORD 在一个设备上开始录制
MCI_SAVE 保存一个文件
MCI_INFO 查询设备信息,如产品名称等
MCI_GETDEVCAPS 查询产品特征,如设备类型等
MCI_STATUS 查询设备当前状态,如播放位置、媒体格式等
MCI_SET 设置设备参数,如时间格式、波形数据格式等
fdwCommand:消息指定标志。
dwParam:指定一个指向消息数据结构的指针。
如果mciSendCommand函数调用成功则返回0,否则返回错误代码消息。所返回的DWORD低位字是错误代码,可以将它发送到mciGetErrorString函数,已获得对错误的文本描述。若错误事设备特有的,高位包含了设备ID,否则高位为0。
MMSYSTEM.H头文件中还定义了MCI命令所需要的数据结构类型。以下是MCI命令常用的数据结构。
MCI_OPEN_PARMS MCI_OPEN命令消息参数的数据结构
MCI_PLAY_PARMS MCI_PLAY命令消息参数的数据结构
MCI_RECORD_PARMS MCI_RECORD命令消息参数的数据结构2、mciGetDevicelID函数。当打开一个设备时,该函数用来获得此设备的ID,原型为:
MCIDEVICEID mciGetDevicelID(LPCTSTR lpszDevice);
参数lpszDevice指定要打开的MCI设备名。若函数调用成功,则返回设备的标志号,否则返回0。3、mciGetErrorString函数。该函数用于返回一个错误代码的文本描述,原型为:
BOOL mciGetErrorString(DWORD fdwError, LPTSTR lpszErrorText, UINT cchErrorText);
参数说明如下:
fdwError:上一次mciSendCommand函数调用的返回值。
lpszErrorText:用来接收返回的文本描述的缓冲区指针。
cchErrorText:指定lpszErrorText的长度。
命令字符串接口使用文本命令控制MCI设备。文本串中包含执行一个命令所需要的所有信息。MCI分析文本串,并把它翻译成命令消息接口中的消息和控制信息。由于加入了翻译过程,命令字符串接口的速度要慢于命令消息接口。
Windows也为字符串接口定义了3个核心操作函数。
1、mciSendString函数。该函数用于向一个MCI的设备驱动程序发送一个字符串,原型为:
MCIERROR mciSendString(LPCTSTR lpszCommand, LPTSTR lpszReturnString, UINT cchReturn, HANDLE hwndCallback);
参数说明如下:
lpszCommand:一个以NULL结尾的定义MCI控制命令的字符串,格式为:command device_name argument
lpszReturnString:一个远指针,它指向由应用程序返回的字符串缓冲区。
cchReturn:指定了缓冲区大小。
hwndCallback:用来指定接收并处理MCI向应用程序发出的MM_MCINOTIFY消息窗口的句柄。
函数成功调用,则返回0,否则返回错误代码。可以将错误代码传递给mciGetErrorString函数获得对错误的文本描述。2、mciGetErrorString函数。前面已作了介绍,这里不再叙述。
3、mciExecute函数。实际上是mciSendString函数的简化形式,它不占用缓冲区来返回消息。如果调用失败,则显示出错信息消息框,原型为:
BOOL FAR PASCAL mciExecute(LPCTSTR lpszDevice);
参数lpszDevice与mciSendString函数的第一个参数含义相同。使用MCI对多媒体进行操作实际上是向设备发送相应的命令。下面介绍各种常用的操作。
◆、打开多媒体设备
使用MCI_OPEN命令消息来打开设备。所有的设备对于MCI命令都需要一个指向消息命令数据结构MCI_OPEN_PARMS的指针。该结构格式如下:
typedef struct _MCI_OPEN_PARMS
{
DWORD dwCallback; //设置MCI_NOTIFY标志时的回调窗口句柄
MCIDEVICEID wDeviceID; //打开设备成功时返回的设备标识号
LPCSTR lpstrDeviceType; //指定所要打开设备类型
LPCSTR lpstrElementName; //对于复合设备,指定设备元素
LPCSTR lpstrAlias; //设备别名
} MCI_OPEN_PARMS;简单设备和复合设备打开方式有所不同。打开简单设备时,不需要指定设备元素。可以用3种方法打开一个简单设备。
1、指定设备名。下面一段代码通过指定设备名打开一个光盘设备。
WORD wDeviceID;
MCI_OPEN_PARMS mciOpenParms;
//为MCI_OPEN消息数据结构赋值
mciOpenParms.lpstrDeviceType = "cdaudio";
//通过指定设备名打开光盘设备
if (micSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE, (DWORD)(LPVOID)&mciOpenParms))
//调用失败的处理
else
wDeviceID = mciOpenParms.wDeviceID;2、指定设备驱动程序。下面一段代码通过指定设备的驱动程序打开一个光盘设备。
WORD wDeviceID;
MCI_OPEN_PARMS mciOpenParms;
//为MCI_OPEN消息数据结构赋值
mciOpenParms.lpstrDeviceType = "mcicd.drv";
//通过指定设备名打开光盘设备
if (micSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE, (DWORD)(LPVOID)&mciOpenParms))
//调用失败的处理
else
wDeviceID = mciOpenParms.wDeviceID;3、指定设备类型参数。各个MCI设备的设备类型常量如下所示。
MCI命令能够管理的音频设备类型所对应的字符串和常量
cdaudio MCI_DEVTYPE_CD_AUDIO
waveaudio MCI_DEVTYPE_WAVEFORM_AUDIO
sequencer MCI_DEVTYPE_SEQUENCER
animation MCI_DEVTYPE_ANIMATION
dat MCI_DEVTYPE_DAT
digitalvideo MCI_DEVTYPE_DIGITAL_VIDEO
other MCI_DEVTYPE_OTHER
scanner MCI_DEVTYPE_SCANNER
vcr MCI_DEVTYPE_VCR
videodisc MCI_DEVTYPE_VIDEODISC
下面一段代码通过指定设备的类型参数打开一个光盘设备。
WORD wDeviceID;
MCI_OPEN_PARMS mciOpenParms;
//为MCI_OPEN消息数据结构赋值
mciOpenParms.lpstrDeviceType = (LPRSTR) MCI_DEVTYPE_CD_AUDIO;
//通过指定设备名打开光盘设备
if (micSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE, (DWORD)(LPVOID)&mciOpenParms))
//调用失败的处理
else
wDeviceID = mciOpenParms.wDeviceID;打开复合设备需要指定设备元素,也可以用3种方法打开一个复合设备。
1、只指定设备。该方法类似于打开简单设备,但只适合用于MCI_GETDEVCAPS命令确定设备性能。
2、同时指定设备和设备元素。该方法需要指出设备元素。举例如下:
WORD wDeviceID;
MCI_OPEN_PARMS mciOpenParms;
//为MCI_OPEN消息数据结构赋值
mciOpenParms.lpstrElementName = "c:\windows\media\chorcd.wav";
//通过指定设备元素参数打开复合设备
if (micSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE, (DWORD)(LPVOID)&mciOpenParms))
//调用失败的处理
else
wDeviceID = mciOpenParms.wDeviceID;3、只指定设备元素。在此方式下,MCI根据WIN.INI文件中的[mci extension]字段,从设备的扩展名确定设备。例如:
#define wPtnLength 255; //缓冲区大小
char lpstrRtnString[wPtnLength]; //指向缓冲区指针
WORD result;
result = mciSendString("open cdaudio", lpstrRtnString, wPtnLength, NULL);
//打开CD设备
if (result != 0)
mciGetErrorString(result, lpstrRtnString, wPtnLength)
//事件处理
else
//设备成功打开后事件处理下面一段代码用来打开一个多媒体复合设备。
#define wPtnLength 255; //缓冲区大小
char lpstrRtnString[wPtnLength]; //指向缓冲区指针
WORD result;
result = mciSendString("c:\windows\media\chorcd.wav", lpstrRtnString, wPtnLength, NULL);
//打开CD设备
if (result != 0)
mciGetErrorString(result, lpstrRtnString, wPtnLength)
//事件处理
else
//设备成功打开后事件处理◆、播放多媒体设备
在成功打开设备后,应用程序就可以通过向MCI发送MCI_PLAY或play字符串命令来播放多媒体设备。MCI_PLAY的数据结构MCI_PLAY_PARMS的格式如下:
typedef struc{
DWORD dwCallback; //设置MCI_NOTIFY标志时回调窗口句柄
DWORD dwFrom; //播放的起始位置
DWORD dwTo; //播放的结束位置
} MCI_PLAY_PARMS;
下面一段代码用来播放已打开的MCI设备。WORD wDeviceID;
MCI_PLAY_PARMS mciPlayParams;
//为MCI_PLAY消息数据结构赋值
mciSendCommand(wDeviceID, MCI_PLAY, 0, (DWORD)(LPVOID)&mciPlayParams);若不给MCI_PLAY_PARMS数据结构中的dwFrom和dwTo字段赋值,播放从头至尾进行。
而在使用play字符串命令时,如果play命令不加任何参数,则多媒体设备会从目前的位置播放到媒体或文件的结束。play命令支持From和To两个参数,它们分别指向起始和终止位置。实例如下:#define wPtnLength 255; //缓冲区大小
char lpstrRtnString[wPtnLength]; //指向缓冲区指针
//播放CD从第10秒到第15秒位置
mciSendString("play cdaudio from 10000 to 15000", lpstrRtnString, wPtnLength, NULL);◆、关闭媒体设备
应用程序在使用完一个打开的多媒体设备后,必须向MCI发送一个MCI_CLOSE消息来关闭设备。应该注意的是,在应用程序退出之前,应关闭所有的MCI设备。这时可以使用MCI_ALL_DEVICE_ID常量,只需一条命令就关闭所有的多媒体设备。下面一段代码演示了关闭所有设备。
mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, 0, NULL);
也可以使用close字符串命令关闭使用完的MCI设备。例如:
#define wPtnLength 255; //缓冲区大小
char lpstrRtnString[wPtnLength]; //指向缓冲区指针
mciSendString("close cdaudio", lpstrRtnString, wPtnLength, NULL);与基于消息的命令一样,在退出应用程序之前,应关闭所有的设备,这时可以使用all参数,实例如下:
#define wPtnLength 255; //缓冲区大小
char lpstrRtnString[wPtnLength]; //指向缓冲区指针
mciSendString("close all", lpstrRtnString, wPtnLength, NULL);◆、暂停多媒体播放
暂停多媒体播放可以通过向MCI发送MCI_PAUSE命令实现,实例如下:
WORD wDeviceID;
MCI_PLAY_PARMS mciPlayParam;
mciSendCommand(wDeviceID, MCI_PAUSE, NULL, (DWORD)(LPVOID)&mciPlayParam);◆、获取和设置播放信息
在播放媒体时,很多时候需要获取播放信息,如波形音频的采样率、通道数和量化位数、CD音频文件总长度等。这时,可以通过向MCI发送MCI_STATUS命令实现。下面一段代码可以用来获取波形音频的采样率和通道数。
MCI_STATUS标志:
MCI_STATUS_LENGTH 获得文件长度
MCI_STATUS_MODE 获得文件播放的当前状态
MCI_STATUS_POSITION 获得文件播放的当前位置
MCI_STATUS_TIME_FORMAT 获得当前的时间格式
MCI_SEQ_STATUS_DIVTYPE 判断文件是PPQN类型还是SMPTE类型
MCI_SEQ_STATUS_TEMPO 获得当前播放速度,PQRN类型,
此值为节拍/分,SMPTE类型,此值为祯/秒
WORD wDeviceID;
MCI_STATUS_PARAM StatusParam;
StatusParam.dwItem = MCI_WAVE_STATUS_SAMPLESPERSEC;
mciSendCommand(wDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITTEM, &StatusParam);
DWORD samplerate = StatusParam.dwReturn; //该参数返回采样率
MCI_STATUS_PARMS mcistatusparms;
mcistatusparms.dwCallback=NULL;
mcistatusparms.dwItem=MCI_STATUS_LENGTH;
mcistatusparms.dwReturn=0;
mciSendCommand(m_nElementID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)&mcistatusparms);
time_long = mcistatusparms.dwReturn/1000 //获取音频时间长度(ms)
StatusParam.dwItem = MCI_WAVE_STATUS_CHANNLES;
mciSendCommand(wDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITTEM, &StatusParam);
DWORD channelnumber = StatusParam.dwReturn; //该参数返回通道数也可以采用字符串命令的方式获得播放信息,实例如下:
MCIERROR mciError;
mciError = mciSendString("open waveaudio", buf, strlen(buf), NULL);同样,在进行编程时,有时也需要对媒体设备的一些播放信息进行设置。这可以通过向已经打开的媒体设备发送MCI_SET命令实现。例如下面一段代码用来设置音频的播放格式为毫秒方式。
WORD wDeviceID;
MCI_SET_PARMS SetParam;
SetParam.dwFormat = MCI_FORMAT_MILLISECONDS;
DWORD OpenFlag = mciSendCommand(wDeviceID, MCI_SET, MCI_TIME_FORMT, &SetParam);采用字符串命令设置媒体播放格式的代码如下:
MCIERROR mciError;
mciError = mciSendString("Set file format milliseconds", buf, strlen(buf), NULL);◆、播放的跳转
在媒体播放过程中进行跳转首先需要声明一个MCI_SEEK_PARMS结构,并使用该结构的dwTo字段设定需要播放的位置,该位置以毫秒为单位。然后通过mciSendCommand函数向指定设备以MCI_TO为标志发送MCI_SEEK命令,如果只需要跳转到文件的开始或结束位置,可以直接使用MCI_SEEK_TO_START和MCI_SEEK_TO_END为标识发送MCI_SEEK命令。例如:
WORD wDeviceID;
int nMinute; //欲跳转的时间,以分钟为单位
MCI_SEEK_PARMS SeekParam;
SeekParam.dwTo = (nNinute*60+nSecond)*1000; //跳转的目标时间,以毫秒为单位
mciSendCommand(wDeviceID, MCI_SEEK, MCI_SEEK_TO | MCI_WAIT, (DWORD)(LPVOID)&SetParam);下段代码通过MCI_SEEK命令使CD音频在nTrack轨道,第nMinute分钟,第nSecond秒的nFrame帧进行播放。
int nTrack, nMinute, nSecond;
WORD wDeviceID;
MCI_SEEK_PARMS SeekParam;
SeekParam.dwTo = MCI_MAKE_TWSF(nTrack, nMinute, nSecond, nFrame);
mciSendCommand(wDevice, MCI_SEEK, MCI_SEEK_TO | MCI_WAIT, (DWORD)(LPVOID)&SetParam);◆、波形音频的录制和保存
在进行波形音频编程时,记录音频设备输入的音频信息也能实现录音功能。可以通过向打开的音频设备发送MCI_RECORD命令实现该功能。例如:
WORD wDeviceID;
MCI_RECORD_PARMS RecordParam;
RecFlag = mciSendCommand(wDeviceID, MCI_RECORD, NULL, &RecordParam);此外,保存音频设备的声音数据wav文件可以通过向设备发送MCI_SAVE命令实现。例如:
WORD wDeviceID;
MCI_SAVE_PARMS SaveParam;
SaveParam.lpfilename = (LPCSTR) FileName;
DWORD SaveFlag = mciSendCommand(wDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, &SaveParam);
封装的类:
点击(此处)折叠或打开
这样就开始了WAV文件的播放,并且在播放完毕后,MM_MCINOTIFY消息会发送到指定的窗口。一个WAV文件播放所发生的事件序列为:(1)命令播放WAV文件并立即返回;(2)播放WAV文 件;(3)完成后发送通知消息。
完成播放后关闭WAV文件元素是程序员的责任,简单的调用Cwave类的Stop成员函数就可以了。Stop成员函数将WAV文件标识符和MCI_CLOSE命令传递给mciSendCommand函数,不必为该命令分配一个MCI结构,下述代码关闭了WAV文件:
点击(此处)折叠或打开
播放完所有的WAV文件后必须关闭波形音频设备,Cwave类的析构函数调用Cwave::Close Device自动完成。 将本文中介绍的CWave类加入到自己的程序中,就可以方便的应用它播放音频文件了。
//建立Cwave类,放在Wave.h文件中:
点击(此处)折叠或打开