多媒体的应用,往往离不开音频文件,DELPHI偏偏不带音量控制的控件,虽然可以安装第三方的音量控制控件,不过对于我们这些编程的超级菜鸟来说,自己动手才是丰脑之道,这里,我利用API函数就实现了WAVE文件的总音量控制及左右声道音量分别控制。
为了调用Windows函数,先在Uses中加上MMSystem,Delphi提供了调用WaveOutSetVolume 函数的Pascal语言的接口,不信看看MMSystem.pas文件*&^@*<。
我编了演示程序,界面如下:
加入三个TrackBar,分别命名为TB_vol,TB_l,TB_r,将Max值设为255(即8位二进制数的最大值),Frequency的值设为10。
先看看总音量控制的实现:
procedure TForm1.TB_volChange(Sender: TObject);
var
pos,vol:longint;
begin
pos:=TB_vol.Position;
vol:=(pos shl 8)or(pos shl 24);
waveoutsetvolume(0,vol);
end;
首先我们得到TrackBar的位置,用这个值来记录音量的大小,第二句转换Windows函数中对应的正确形式,末一句设置音量。
WaveOutSetVolume函数有两个参数:
第一个参数为波形文件输出设备的标识符,这里的标识符为0。
第二个参数为音量的大小,他是一个32位的整数,低16位表示左声道的音量,高16位表示右声道的音量。
由于TrackBar只用8位二进制数表示音量,因而先把得到的8位音量值pos左移8位,变成左声道16为中的高8位,然后将pos右移24位,变成右声道16为中的高8位,将两个值进行or运算赋给vol,将vol加给WaveOutSetVolume函数的第二个参数,这样就可以实现同时改变两个声道的音量了。
理解上述方法后,分别控制左右声道就不难了,看看左声道的实现:
procedure TForm1.TB_lChange(Sender: TObject);
var
pos,vol:longint;
begin
pos:=TB_l.Position;
waveoutgetvolume(0,@vol);
vol:=vol and $ffff0000 or (pos shl 8);
waveoutsetvolume(0,vol);
end;
由于只修改左声道的值,为了保存右声道的音量,即保存得到音量值pos的高16位,我们将vol和$ffff0000(16进制数)进行or运算,这样,vol的低16位被清零,高16位保持不变,再将低16位左移8位表示新的左声道的音量。
右声道同理实现
procedure TForm1.TB_rChange(Sender: TObject);
var
pos,vol:longint;
begin
pos:=TB_r.Position;
waveoutgetvolume(0,@vol);
vol:=vol and $0000ffff or (pos shl 24);
waveoutsetvolume(0,vol);
end;
同时,我们为了程序一运行滑块就能到当前的音量(完善完善嘛),需要在Form的Create事件中加入以下语句:
procedure TForm1.FormCreate(Sender: TObject);
var
vol:longint;
begin
waveoutgetvolume(0,@vol);
TB_l.Position :=hi(vol); {设置左声道音量}
TB_r.Position :=hi(vol shr 16); {设置右声道音量}
end;
2007年12月21日星期五
2007年12月17日星期一
语音编程 学习资料
http://www.cnblogs.com/AndyHai/archive/2007/07/07/809883.html
http://www.vckbase.com/document/
http://www.vckbase.com/document/
MMIO编程
MMIO(MultiMedia file Input and Output多媒体文件的输入与输出)比标准操作系统的I/O服务提供更多的功能,包括缓冲区I/O、RIFF文件、内存文件和定制的存储系统等。另外,多媒体文件I/O服务还对性能敏感的应用程序进行了优化。
MMIO定义了2个数据类型和2个结构类型、并提供了18个函数和1个宏,还有7个专用消息。由于篇幅所限,本节只针对RIFF文件(主要是WAV格式)的读入,介绍其中若干常用的结构和函数。
本节先介绍RIFF文件的结构和相关定义,然后给出MMIO的若干基本I/O和RIFF I/O函数,最后编写读取WAV文件和资源的内容和信息的实例代码。
12.6.1 RIFF文件
RIFF(Resource Interchange File Format资源交换文件格式)是微软为Windows设计的一类多媒体文件格式,主要包括WAV(Waveform Audio File Format波形音频文件格式)和AVI(Audio/Video Interleaved File Format音频视频交互文件格式)两种。音频文件格式WAV已经在3.3.1中介绍过,而视频文件格式AVI也在6.2.3中介绍过。
文件结构
RIFF文件由文件头、数据类型标识及若干块组成。块(chunk)是RIFF文件的基本结构,每个块都包括如下三个域:
用于标识块类型的4字符代码,如’RIFF’、’LIST’和’fmt ’、’data’等;
数据块大小的字节数
数据域本身
RIFF文件中的块是可以嵌套的,块的数据域可含若干子块,子块又可含孙块。例如,RIFF文件本身就是一个RIFF块,其数据域可以是WAV子块或AVI子块,而WAV子块又包含格式块('fmt ‘)和数据块('data’),AVI文件的AVI子块,包含格式列表块(’LIST’)、数据列表块(’LIST’)和可选的索引块(’ind1’),……。
RIFF文件具有相同的结构:
’RIFF’<文件大小><格式类型><块>……
例如,WAV文件的结构为:
’RIFF’<文件大小>’WAVE’<格式块><数据块>
而AVI文件的结构为:
’RIFF’<文件大小>’AVI ’<格式列表块><数据列表块>[<索引块>]
为了读写和处理RIFF文件,在多媒体SDK和MMIO中,定义了若干数据类型、结构和宏。下面给出它们的定义和使用方法:
四字符代码类型和其构造宏
为了读写和比较RIFF文件的由4字符组成的块类型标识代码,定义了一个FOURCC(FOUR Character Code,四字符代码)数据类型:
typedef DWORD FOURCC;
还定义了一个由4个字符来构造FOURCC类型值的宏mmioFOURCC:
FOURCC mmioFOURCC( // 构造四字符代码
CHAR ch0, // 四字符代码的第1个字符,typedef char CHAR;
CHAR ch1, // 四字符代码的第2个字符
CHAR ch2, // 四字符代码的第3个字符
CHAR ch3 // 四字符代码的第4个字符
);
该宏是利用MAKEFOURCC宏来定义的:
#define mmioFOURCC(ch0, ch1, ch2, ch3) MAKEFOURCC(ch0, ch1, ch2, ch3);
而MAKEFOURCC宏的定义为:
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ));
例如:
#define ID_RIFF mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE mmioFOURCC('W', 'A', 'V', 'E')
#define ID_fmt mmioFOURCC('f', 'm', 't', ' ')
#define ID_data mmioFOURCC('d', 'a', 't', 'a')
FOURCC id;
ar >> id;
if (id != ID_RIFF) ……
块结构定义
typedef struct {
FOURCC ckid; // 块ID
DWORD cksize; // 块的字节大小
FOURCC fccType; // (RIFF和LIST块的)格式类型(如’WAVE’)
DWORD dwDataOffset; // 块的数据部分的偏移量
DWORD dwFlags; // 用于MMIO函数的标志,可取0或MMIO_DIRTY(脏)
} MMCKINFO;
12.6.2 基本与RIFF I/O函数
下面是若干通用的MMIO基本I/O函数和两个用于RIFF I/O的定位函数:
打开/关闭文件
mmioOpen函数可以用来打开缓冲和非缓冲的I/O文件,这里的文件可以是标准文件、内存文件和定制存储系统的元素。该函数的原型为:
HMMIO mmioOpen( // 返回北打开文件的句柄,出错返回NULL
LPSTR szFilename, // 文件名串,对内存文件为NULL
LPMMIOINFO lpmmioinfo, // 指向包含额外参数的MMIOINFO结构的指针
// 一般为NULL,除非是打开内存文件,指定缓冲I/O的缓冲区大小
DWORD dwOpenFlags // 打开操作的标志位组合,取值见表12-11
);
其中,MMIOINFO结构的定义为:
typedef struct {
DWORD dwFlags; // 打开文件方式标志,取值见表12-11
FOURCC fccIOProc; // 标识文件IO过程的四字符代码,无IO过程时为NULL
LPMMIOPROC pIOProc; // 件IO过程的指针
UINT wErrorRet; // 扩展的错误值,只用于打开函数
HTASK hTask; // 局部IO过程的句柄
LONG cchBuffer; // 文件缓冲区的字节大小,对非缓冲文件为0
HPSTR pchBuffer; // 指向文件缓冲区的指针,对非缓冲文件为NULL
HPSTR pchNext; // 指向缓冲区的下一读/写位置的指针
HPSTR pchEndRead; // 指向缓冲区内的可读位置尾
HPSTR pchEndWrite; // 指向缓冲区内的可写位置尾
LONG lBufOffset; // 保留
LONG lDiskOffset; // 当前文件位置(从文件头的偏移字节数)
DWORD adwInfo[4]; // IO过程维护的状态信息
DWORD dwReserved1; // 保留
DWORD dwReserved2; // 保留
HMMIO hmmio; // 由打开函数返回的文件句柄
} MMIOINFO;
其中,HPSTR类型的定义为:
typedef char _huge * HPSTR; /* a huge version of LPSTR */
表12-11 打开文件操作的标志符号常量
=========================================================
值 含义
MMIO_ALLOCBUF 打开缓冲文件
MMIO_COMPAT 打开兼容模式文件
MMIO_CREATE 创建新文件
MMIO_DELETE 删除文件
MMIO_DENYNONE 打开共享文件
MMIO_DENYREAD 打开禁止其他过程读访问的文件
MMIO_DENYWRITE 打开禁止其他过程写访问的文件
MMIO_EXCLUSIVE 打开禁止其他过程读写访问的文件
MMIO_EXIST 判断指定文件是否存在,存在时返回TRUE
MMIO_GETTEMP 创建临时文件名
MMIO_PARSE 创建完整文件名
MMIO_READ 打开只读文件
MMIO_READWRITE 打开读写文件
MMIO_WRITE 打开只写文件
=========================================================
由mmioOpen函数打开的文件,在使用完后必须调用函数mmioClose关闭它。该关闭函数对缓冲文件,还会调用mmioFlush函数将缓冲区的数据写回磁盘。关闭函数的原型为:
MMRESULT mmioClose( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
UINT wFlags // 关闭操作标志,可取0或MMIO_FHOPEN(对非MMIO文件句柄)
);
例如:
HMMIO hmmio = mmioOpen(L"sample.wav", NULL, MMIO_READ);
…… // 使用和处理数据
mmioClose(hmmio, 0);
读/写数据
// 从文件中读取数据
LONG mmioRead( // 返回实际读出的字节数
HMMIO hmmio, // 文件句柄
HPSTR pch, // 存放读入数据的缓冲区指针
LONG cch // 读取数据的字节数
);
// 写入数据到文件
LONG mmioWrite( // 返回实际写入的字节数
HMMIO hmmio, // 文件句柄
HPSTR pch, // 存放写入数据的缓冲区指针
LONG cch // 写入数据的字节数
);
例如:
WAVEFORMATEX wfFormat;
mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX));
又如:
Write(const char* pData, LONG lLen) {
ASSERT(m_hmmio != NULL);
return ::mmioWrite(m_hmmio, pData, lLen);
}
定位文件
// 定位:改变文件的当前位置
LONG mmioSeek( // 返回新位置(相对于文件头的字节数),出错返回-1
HMMIO hmmio, // 文件句柄
LONG lOffset, // 改变位置的偏移量
int iOrigin // 偏移类型标志,可取值有:SEEK_CUR(从当前起算)、
// SEEK_END(从文件尾起算)、SEEK_SET(从文件头起算)
);
// 下降:进入RIFF文件中的(特定)块
MMRESULT mmioDescend( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
LPMMCKINFO lpck, // 指向接收MMCKINFO结构的缓冲区指针
LPMMCKINFO lpckParent, // 指向指定父块之MMCKINFO结构的指针
// 无父块时取为NULL
UINT wFlags // 搜索标志,可取值有:0(当前位置)、MMIO_FINDCHUNK
// (指定块标识符)、MMIO_FINDLIST(列表块内的指定标识符块)、
// MMIO_FINDRIFF(RIFF块内的指定标识符块)
);
// 上升:移出RIFF文件中的当前块
MMRESULT mmioAscend( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
LPMMCKINFO lpck, // 指向(已被mmioDescend填充的)MMCKINFO结构的指针
UINT wFlags // 保留,必须为0
);
例如:
MMCKINFO ckInfo; // 定义块信息结构变量
mmioSeek(hmmio, 12, SEEK_SET); // 定位到’fmt ‘块的开始处
mmioDescend(hmmio, &ckInfo, NULL, 0); // 获取’fmt ‘块信息
…… // 读取操作
mmioAscend(hmmio, &ckInfo, 0); // 跳出’fmt ‘块
LONG currentPosition = mmioSeek(hmmio, 0, SEEK_CUR); // 获取当前位置
又例如:
// Set the position to the begining of the file
if ( mmioSeek(hmmio, 0, SEEK_SET) == -1) {
MessageBox(L"Seek to the begining of file error!", L"Error");
return false;
}
// Find the RIFF chunk
MMCKINFO mmckRiffInfo;
mmckRiffInfo.ckid = mmioFOURCC('R','I','F','F');
if ( mmioDescend(hmmio, &mmckRiffInfo, NULL, MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to RIFF chunk error!", L"Error");
return false;
}
// Find the WAVE chunk
MMCKINFO mmckWaveInfo;
mmckWaveInfo.ckid = mmioFOURCC('W','A','V','E');
if ( mmioSeek(hmmio, -4, SEEK_CUR) == -1) {
MessageBox(L"Seek to the begining of 'WAVE' error!", L"Error");
return false;
}
if ( mmioDescend(hmmio, &mmckWaveInfo, &mmckRiffInfo,
MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to WAVE chunk error!", L"Error");
return false;
}
// Find and read the format subchunk
MMCKINFO mmckFmtInfo;
mmckFmtInfo.ckid = mmioFOURCC('f','m','t',' ');
if ( mmioSeek(hmmio, 12, SEEK_SET) == -1) {
MessageBox(L"Seek to the begining of 'fmt ' error!", L"Error");
return false;
}
if ( mmioDescend(hmmio, &mmckFmtInfo, &mmckWaveInfo,
MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to fmt chunk error!", L"Error");
return false;
}
if ( mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX))
< sizeof(WAVEFORMATEX) ) {
MessageBox(L"Read format struct error!", L"Error");
return false;
}
if ( mmioAscend(hmmio, &mmckFmtInfo, 0) != 0 ) {
MessageBox(L"Ascend from fmt chunk error!", L"Error");
return false;
}
12.6.3 读取WAV文件和资源
可以利用MMIO,来读取WAV文件和资源中的数据内容与参数信息。下面给出若干有关函数和代码段,这些内容,在后面的13.3节(DirectX声音编程)中会用到。
读取WAV文件
函数OpenWave,用于打开WAV波形音频文件,获取文件的句柄:
// 打开WAV文件
HMMIO OpenWave(LPWSTR strFilePath) {// Open WAV file
return mmioOpen(strFilePath, NULL, MMIO_READ);
}
函数GetWaveData,利用文件句柄,来获取波形音频格式结构内容和数据块长度:
// 获取波形数据:格式结构和数据块长度
bool GetWaveData(HMMIO hmmio, WAVEFORMATEX& wfFormat,
DWORD& dwDataLen) {
// Find the RIFF chunk and WAVE form type
MMCKINFO mmckInfo;
if ( mmioDescend(hmmio, &mmckInfo, NULL, 0) != 0
|| mmckInfo.ckid != FOURCC_RIFF
|| mmckInfo.fccType != mmioFOURCC('W','A','V','E') ) {
MessageBox(L"Not a WAV file !", L"Error");
return false;
}
// Find and read the format subchunk
if ( mmioDescend(hmmio, &mmckInfo, NULL, 0) != 0
|| mmckInfo.ckid != mmioFOURCC('f','m','t',' ') ) {
MessageBox(L"fmt chunk error!", L"Error");
return false;
}
if ( mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX))
< sizeof(WAVEFORMATEX) ) {
MessageBox(L"Read format struct error!", L"Error");
return false;
}
if ( mmioAscend(hmmio, &mmckInfo, 0) != 0 ) {
MessageBox(L"Ascend from fmt chunk error!", L"Error");
return false;
}
// Find and get the size of the data subchunk
mmckInfo.ckid = mmioFOURCC('d','a','t','a');
if ( mmioDescend(hmmio, &mmckInfo, NULL, MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"data chunk error!", L"Error");
return false;
}
dwDataLen = mmckInfo.cksize;
return true;
}
函数GetWave,调用OpenWave和GetWaveData函数,由文件路径串来打开波形音频文件,获取文件句柄、格式结构和数据块长度:
// 获取波形:文件句柄、格式结构和数据块长度
bool GetWave(LPWSTR strFilePath, HMMIO& hmmio,
WAVEFORMATEX& wfFormat, DWORD& dwDataLen) {
// Open WAV file
hmmio = OpenWave(strFilePath);
if ( hmmio == NULL ) {
MessageBox(L"Open WAV file error!", L"Error");
return false;
}
// Get WAV data
if ( !GetWaveData(hmmio, wfFormat, dwDataLen) ) {
MessageBox(L"Get wave data error!", L"Error");
return false;
}
return true;
}
读取WAV资源
为了读取WAV资源,需要调用若干SDK函数来定位资源位置、装入资源到全局内存、锁定资源并获得资源地址、获取资源大小等。下面逐个加以介绍:
// 确定指定模块中指定类型和名称之资源的位置
HRSRC FindResource( // 成功返回指定资源的信息块句柄,失败返回NULL
HMODULE hModule, // 其可执行文件包含资源之模块的句柄,NULL为当前模块
LPCTSTR lpName, // 资源名,一般取MAKEINTRESOURCE(资源ID)
LPCTSTR lpType // 指定资源类型,取值可为RT_BITMAP、RT_CURSOR、
// RT_ICON、……,对波形音频资源为L"WAVE"
);
其中,hModule也可取值为MFC的全局函数AfxGetInstanceHandle的返回值:
HINSTANCE AFXAPI AfxGetInstanceHandle( ); // 返回当前应用程序的实例句柄
// 将指定资源装入全局内存
HGLOBAL LoadResource( // 成功返回资源数据句柄,失败返回NULL
HMODULE hModule, // 模块的句柄,NULL为当前模块
HRSRC hResInfo // 资源信息块句柄
);
// 释放资源
BOOL FreeResource( // 成功返回0
HGLOBAL hglbResource // 资源数据句柄
);
// 锁定资源,获得资源地址
LPVOID LockResource( // 成功返回资源头指针,失败返回NULL
HGLOBAL hResData // 资源数据句柄
);
// 获取资源字节大小
DWORD SizeofResource( // 返回资源字节数,失败返回0
HMODULE hModule, // 模块的句柄,NULL为当前模块
HRSRC hResInfo // 资源信息块句柄
);
读取WAV资源例子
下面利用这些SDK函数,来打开波形音频资源:
// 打开WAV资源
HMMIO OpenWave(UINT uiResID) { // Open WAV resource
HMMIO hmmio = NULL;
BYTE* m_pImageData;
DWORD m_dwImageLen;
// Find the wave resource
HRSRC hresInfo = FindResource(NULL, MAKEINTRESOURCE(uiResID),
L"WAVE");
if ( hresInfo == NULL) return NULL;
// Load the wave resource
HGLOBAL hgmemWave = LoadResource(NULL, hresInfo);
// Get wave file image
if (hgmemWave != NULL) {
// Get pointer to and length of the wave image data
m_pImageData= (BYTE*)LockResource(hgmemWave);
m_dwImageLen = SizeofResource(NULL, hresInfo);
} else return NULL;
// Set the MMIOINFO struct
MMIOINFO mmioInfo;
ZeroMemory(&mmioInfo, sizeof(MMIOINFO));
mmioInfo.pIOProc = NULL;
mmioInfo.fccIOProc = FOURCC_MEM;
mmioInfo.pchBuffer = (HPSTR)m_pImageData;
mmioInfo.cchBuffer = m_dwImageLen;
// Open MMIO memory file
hmmio = mmioOpen(NULL, &mmioInfo, MMIO_READWRITE);
if ( hmmio == NULL ) {
MessageBox(L"Open memory file error!", L"Error");
return NULL;
}
return hmmio;
}
其中,用到了一个SDK宏:
// 用0填充内存块
void ZeroMemory(
PVOID Destination, // 块头地址
SIZE_T Length // 块的字节大小
);
#define ZeroMemory RtlZeroMemory
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
函数GetWave,调用OpenWave和GetWaveData函数,由资源ID来打开波形音频文件,获取文件句柄、格式结构和数据块长度:(与WAV文件的类似,其中的GetWaveData函数相同)
// 获取波形:文件句柄、格式结构和数据块长度
bool GetWave(UINT uiResID, HMMIO& hmmio, WAVEFORMATEX& wfFormat,
DWORD& dwDataLen) {
// Open WAV resource
hmmio = OpenWave(uiResID);
if ( hmmio == NULL ) {
MessageBox(L"Open WAV resource error!", L"Error");
return false;
}
// Get WAV data
if ( !GetWaveData(hmmio, wfFormat, dwDataLen) ) {
MessageBox(L"Get wave data error!", L"Error");
return false;
}
return true;
}
MMIO定义了2个数据类型和2个结构类型、并提供了18个函数和1个宏,还有7个专用消息。由于篇幅所限,本节只针对RIFF文件(主要是WAV格式)的读入,介绍其中若干常用的结构和函数。
本节先介绍RIFF文件的结构和相关定义,然后给出MMIO的若干基本I/O和RIFF I/O函数,最后编写读取WAV文件和资源的内容和信息的实例代码。
12.6.1 RIFF文件
RIFF(Resource Interchange File Format资源交换文件格式)是微软为Windows设计的一类多媒体文件格式,主要包括WAV(Waveform Audio File Format波形音频文件格式)和AVI(Audio/Video Interleaved File Format音频视频交互文件格式)两种。音频文件格式WAV已经在3.3.1中介绍过,而视频文件格式AVI也在6.2.3中介绍过。
文件结构
RIFF文件由文件头、数据类型标识及若干块组成。块(chunk)是RIFF文件的基本结构,每个块都包括如下三个域:
用于标识块类型的4字符代码,如’RIFF’、’LIST’和’fmt ’、’data’等;
数据块大小的字节数
数据域本身
RIFF文件中的块是可以嵌套的,块的数据域可含若干子块,子块又可含孙块。例如,RIFF文件本身就是一个RIFF块,其数据域可以是WAV子块或AVI子块,而WAV子块又包含格式块('fmt ‘)和数据块('data’),AVI文件的AVI子块,包含格式列表块(’LIST’)、数据列表块(’LIST’)和可选的索引块(’ind1’),……。
RIFF文件具有相同的结构:
’RIFF’<文件大小><格式类型><块>……
例如,WAV文件的结构为:
’RIFF’<文件大小>’WAVE’<格式块><数据块>
而AVI文件的结构为:
’RIFF’<文件大小>’AVI ’<格式列表块><数据列表块>[<索引块>]
为了读写和处理RIFF文件,在多媒体SDK和MMIO中,定义了若干数据类型、结构和宏。下面给出它们的定义和使用方法:
四字符代码类型和其构造宏
为了读写和比较RIFF文件的由4字符组成的块类型标识代码,定义了一个FOURCC(FOUR Character Code,四字符代码)数据类型:
typedef DWORD FOURCC;
还定义了一个由4个字符来构造FOURCC类型值的宏mmioFOURCC:
FOURCC mmioFOURCC( // 构造四字符代码
CHAR ch0, // 四字符代码的第1个字符,typedef char CHAR;
CHAR ch1, // 四字符代码的第2个字符
CHAR ch2, // 四字符代码的第3个字符
CHAR ch3 // 四字符代码的第4个字符
);
该宏是利用MAKEFOURCC宏来定义的:
#define mmioFOURCC(ch0, ch1, ch2, ch3) MAKEFOURCC(ch0, ch1, ch2, ch3);
而MAKEFOURCC宏的定义为:
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ));
例如:
#define ID_RIFF mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE mmioFOURCC('W', 'A', 'V', 'E')
#define ID_fmt mmioFOURCC('f', 'm', 't', ' ')
#define ID_data mmioFOURCC('d', 'a', 't', 'a')
FOURCC id;
ar >> id;
if (id != ID_RIFF) ……
块结构定义
typedef struct {
FOURCC ckid; // 块ID
DWORD cksize; // 块的字节大小
FOURCC fccType; // (RIFF和LIST块的)格式类型(如’WAVE’)
DWORD dwDataOffset; // 块的数据部分的偏移量
DWORD dwFlags; // 用于MMIO函数的标志,可取0或MMIO_DIRTY(脏)
} MMCKINFO;
12.6.2 基本与RIFF I/O函数
下面是若干通用的MMIO基本I/O函数和两个用于RIFF I/O的定位函数:
打开/关闭文件
mmioOpen函数可以用来打开缓冲和非缓冲的I/O文件,这里的文件可以是标准文件、内存文件和定制存储系统的元素。该函数的原型为:
HMMIO mmioOpen( // 返回北打开文件的句柄,出错返回NULL
LPSTR szFilename, // 文件名串,对内存文件为NULL
LPMMIOINFO lpmmioinfo, // 指向包含额外参数的MMIOINFO结构的指针
// 一般为NULL,除非是打开内存文件,指定缓冲I/O的缓冲区大小
DWORD dwOpenFlags // 打开操作的标志位组合,取值见表12-11
);
其中,MMIOINFO结构的定义为:
typedef struct {
DWORD dwFlags; // 打开文件方式标志,取值见表12-11
FOURCC fccIOProc; // 标识文件IO过程的四字符代码,无IO过程时为NULL
LPMMIOPROC pIOProc; // 件IO过程的指针
UINT wErrorRet; // 扩展的错误值,只用于打开函数
HTASK hTask; // 局部IO过程的句柄
LONG cchBuffer; // 文件缓冲区的字节大小,对非缓冲文件为0
HPSTR pchBuffer; // 指向文件缓冲区的指针,对非缓冲文件为NULL
HPSTR pchNext; // 指向缓冲区的下一读/写位置的指针
HPSTR pchEndRead; // 指向缓冲区内的可读位置尾
HPSTR pchEndWrite; // 指向缓冲区内的可写位置尾
LONG lBufOffset; // 保留
LONG lDiskOffset; // 当前文件位置(从文件头的偏移字节数)
DWORD adwInfo[4]; // IO过程维护的状态信息
DWORD dwReserved1; // 保留
DWORD dwReserved2; // 保留
HMMIO hmmio; // 由打开函数返回的文件句柄
} MMIOINFO;
其中,HPSTR类型的定义为:
typedef char _huge * HPSTR; /* a huge version of LPSTR */
表12-11 打开文件操作的标志符号常量
=========================================================
值 含义
MMIO_ALLOCBUF 打开缓冲文件
MMIO_COMPAT 打开兼容模式文件
MMIO_CREATE 创建新文件
MMIO_DELETE 删除文件
MMIO_DENYNONE 打开共享文件
MMIO_DENYREAD 打开禁止其他过程读访问的文件
MMIO_DENYWRITE 打开禁止其他过程写访问的文件
MMIO_EXCLUSIVE 打开禁止其他过程读写访问的文件
MMIO_EXIST 判断指定文件是否存在,存在时返回TRUE
MMIO_GETTEMP 创建临时文件名
MMIO_PARSE 创建完整文件名
MMIO_READ 打开只读文件
MMIO_READWRITE 打开读写文件
MMIO_WRITE 打开只写文件
=========================================================
由mmioOpen函数打开的文件,在使用完后必须调用函数mmioClose关闭它。该关闭函数对缓冲文件,还会调用mmioFlush函数将缓冲区的数据写回磁盘。关闭函数的原型为:
MMRESULT mmioClose( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
UINT wFlags // 关闭操作标志,可取0或MMIO_FHOPEN(对非MMIO文件句柄)
);
例如:
HMMIO hmmio = mmioOpen(L"sample.wav", NULL, MMIO_READ);
…… // 使用和处理数据
mmioClose(hmmio, 0);
读/写数据
// 从文件中读取数据
LONG mmioRead( // 返回实际读出的字节数
HMMIO hmmio, // 文件句柄
HPSTR pch, // 存放读入数据的缓冲区指针
LONG cch // 读取数据的字节数
);
// 写入数据到文件
LONG mmioWrite( // 返回实际写入的字节数
HMMIO hmmio, // 文件句柄
HPSTR pch, // 存放写入数据的缓冲区指针
LONG cch // 写入数据的字节数
);
例如:
WAVEFORMATEX wfFormat;
mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX));
又如:
Write(const char* pData, LONG lLen) {
ASSERT(m_hmmio != NULL);
return ::mmioWrite(m_hmmio, pData, lLen);
}
定位文件
// 定位:改变文件的当前位置
LONG mmioSeek( // 返回新位置(相对于文件头的字节数),出错返回-1
HMMIO hmmio, // 文件句柄
LONG lOffset, // 改变位置的偏移量
int iOrigin // 偏移类型标志,可取值有:SEEK_CUR(从当前起算)、
// SEEK_END(从文件尾起算)、SEEK_SET(从文件头起算)
);
// 下降:进入RIFF文件中的(特定)块
MMRESULT mmioDescend( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
LPMMCKINFO lpck, // 指向接收MMCKINFO结构的缓冲区指针
LPMMCKINFO lpckParent, // 指向指定父块之MMCKINFO结构的指针
// 无父块时取为NULL
UINT wFlags // 搜索标志,可取值有:0(当前位置)、MMIO_FINDCHUNK
// (指定块标识符)、MMIO_FINDLIST(列表块内的指定标识符块)、
// MMIO_FINDRIFF(RIFF块内的指定标识符块)
);
// 上升:移出RIFF文件中的当前块
MMRESULT mmioAscend( // 成功返回MMSYSERR_NOERROR(= 0)
HMMIO hmmio, // 文件句柄
LPMMCKINFO lpck, // 指向(已被mmioDescend填充的)MMCKINFO结构的指针
UINT wFlags // 保留,必须为0
);
例如:
MMCKINFO ckInfo; // 定义块信息结构变量
mmioSeek(hmmio, 12, SEEK_SET); // 定位到’fmt ‘块的开始处
mmioDescend(hmmio, &ckInfo, NULL, 0); // 获取’fmt ‘块信息
…… // 读取操作
mmioAscend(hmmio, &ckInfo, 0); // 跳出’fmt ‘块
LONG currentPosition = mmioSeek(hmmio, 0, SEEK_CUR); // 获取当前位置
又例如:
// Set the position to the begining of the file
if ( mmioSeek(hmmio, 0, SEEK_SET) == -1) {
MessageBox(L"Seek to the begining of file error!", L"Error");
return false;
}
// Find the RIFF chunk
MMCKINFO mmckRiffInfo;
mmckRiffInfo.ckid = mmioFOURCC('R','I','F','F');
if ( mmioDescend(hmmio, &mmckRiffInfo, NULL, MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to RIFF chunk error!", L"Error");
return false;
}
// Find the WAVE chunk
MMCKINFO mmckWaveInfo;
mmckWaveInfo.ckid = mmioFOURCC('W','A','V','E');
if ( mmioSeek(hmmio, -4, SEEK_CUR) == -1) {
MessageBox(L"Seek to the begining of 'WAVE' error!", L"Error");
return false;
}
if ( mmioDescend(hmmio, &mmckWaveInfo, &mmckRiffInfo,
MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to WAVE chunk error!", L"Error");
return false;
}
// Find and read the format subchunk
MMCKINFO mmckFmtInfo;
mmckFmtInfo.ckid = mmioFOURCC('f','m','t',' ');
if ( mmioSeek(hmmio, 12, SEEK_SET) == -1) {
MessageBox(L"Seek to the begining of 'fmt ' error!", L"Error");
return false;
}
if ( mmioDescend(hmmio, &mmckFmtInfo, &mmckWaveInfo,
MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"Descend to fmt chunk error!", L"Error");
return false;
}
if ( mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX))
< sizeof(WAVEFORMATEX) ) {
MessageBox(L"Read format struct error!", L"Error");
return false;
}
if ( mmioAscend(hmmio, &mmckFmtInfo, 0) != 0 ) {
MessageBox(L"Ascend from fmt chunk error!", L"Error");
return false;
}
12.6.3 读取WAV文件和资源
可以利用MMIO,来读取WAV文件和资源中的数据内容与参数信息。下面给出若干有关函数和代码段,这些内容,在后面的13.3节(DirectX声音编程)中会用到。
读取WAV文件
函数OpenWave,用于打开WAV波形音频文件,获取文件的句柄:
// 打开WAV文件
HMMIO OpenWave(LPWSTR strFilePath) {// Open WAV file
return mmioOpen(strFilePath, NULL, MMIO_READ);
}
函数GetWaveData,利用文件句柄,来获取波形音频格式结构内容和数据块长度:
// 获取波形数据:格式结构和数据块长度
bool GetWaveData(HMMIO hmmio, WAVEFORMATEX& wfFormat,
DWORD& dwDataLen) {
// Find the RIFF chunk and WAVE form type
MMCKINFO mmckInfo;
if ( mmioDescend(hmmio, &mmckInfo, NULL, 0) != 0
|| mmckInfo.ckid != FOURCC_RIFF
|| mmckInfo.fccType != mmioFOURCC('W','A','V','E') ) {
MessageBox(L"Not a WAV file !", L"Error");
return false;
}
// Find and read the format subchunk
if ( mmioDescend(hmmio, &mmckInfo, NULL, 0) != 0
|| mmckInfo.ckid != mmioFOURCC('f','m','t',' ') ) {
MessageBox(L"fmt chunk error!", L"Error");
return false;
}
if ( mmioRead(hmmio, (HPSTR)&wfFormat, sizeof(WAVEFORMATEX))
< sizeof(WAVEFORMATEX) ) {
MessageBox(L"Read format struct error!", L"Error");
return false;
}
if ( mmioAscend(hmmio, &mmckInfo, 0) != 0 ) {
MessageBox(L"Ascend from fmt chunk error!", L"Error");
return false;
}
// Find and get the size of the data subchunk
mmckInfo.ckid = mmioFOURCC('d','a','t','a');
if ( mmioDescend(hmmio, &mmckInfo, NULL, MMIO_FINDCHUNK) != 0 ) {
MessageBox(L"data chunk error!", L"Error");
return false;
}
dwDataLen = mmckInfo.cksize;
return true;
}
函数GetWave,调用OpenWave和GetWaveData函数,由文件路径串来打开波形音频文件,获取文件句柄、格式结构和数据块长度:
// 获取波形:文件句柄、格式结构和数据块长度
bool GetWave(LPWSTR strFilePath, HMMIO& hmmio,
WAVEFORMATEX& wfFormat, DWORD& dwDataLen) {
// Open WAV file
hmmio = OpenWave(strFilePath);
if ( hmmio == NULL ) {
MessageBox(L"Open WAV file error!", L"Error");
return false;
}
// Get WAV data
if ( !GetWaveData(hmmio, wfFormat, dwDataLen) ) {
MessageBox(L"Get wave data error!", L"Error");
return false;
}
return true;
}
读取WAV资源
为了读取WAV资源,需要调用若干SDK函数来定位资源位置、装入资源到全局内存、锁定资源并获得资源地址、获取资源大小等。下面逐个加以介绍:
// 确定指定模块中指定类型和名称之资源的位置
HRSRC FindResource( // 成功返回指定资源的信息块句柄,失败返回NULL
HMODULE hModule, // 其可执行文件包含资源之模块的句柄,NULL为当前模块
LPCTSTR lpName, // 资源名,一般取MAKEINTRESOURCE(资源ID)
LPCTSTR lpType // 指定资源类型,取值可为RT_BITMAP、RT_CURSOR、
// RT_ICON、……,对波形音频资源为L"WAVE"
);
其中,hModule也可取值为MFC的全局函数AfxGetInstanceHandle的返回值:
HINSTANCE AFXAPI AfxGetInstanceHandle( ); // 返回当前应用程序的实例句柄
// 将指定资源装入全局内存
HGLOBAL LoadResource( // 成功返回资源数据句柄,失败返回NULL
HMODULE hModule, // 模块的句柄,NULL为当前模块
HRSRC hResInfo // 资源信息块句柄
);
// 释放资源
BOOL FreeResource( // 成功返回0
HGLOBAL hglbResource // 资源数据句柄
);
// 锁定资源,获得资源地址
LPVOID LockResource( // 成功返回资源头指针,失败返回NULL
HGLOBAL hResData // 资源数据句柄
);
// 获取资源字节大小
DWORD SizeofResource( // 返回资源字节数,失败返回0
HMODULE hModule, // 模块的句柄,NULL为当前模块
HRSRC hResInfo // 资源信息块句柄
);
读取WAV资源例子
下面利用这些SDK函数,来打开波形音频资源:
// 打开WAV资源
HMMIO OpenWave(UINT uiResID) { // Open WAV resource
HMMIO hmmio = NULL;
BYTE* m_pImageData;
DWORD m_dwImageLen;
// Find the wave resource
HRSRC hresInfo = FindResource(NULL, MAKEINTRESOURCE(uiResID),
L"WAVE");
if ( hresInfo == NULL) return NULL;
// Load the wave resource
HGLOBAL hgmemWave = LoadResource(NULL, hresInfo);
// Get wave file image
if (hgmemWave != NULL) {
// Get pointer to and length of the wave image data
m_pImageData= (BYTE*)LockResource(hgmemWave);
m_dwImageLen = SizeofResource(NULL, hresInfo);
} else return NULL;
// Set the MMIOINFO struct
MMIOINFO mmioInfo;
ZeroMemory(&mmioInfo, sizeof(MMIOINFO));
mmioInfo.pIOProc = NULL;
mmioInfo.fccIOProc = FOURCC_MEM;
mmioInfo.pchBuffer = (HPSTR)m_pImageData;
mmioInfo.cchBuffer = m_dwImageLen;
// Open MMIO memory file
hmmio = mmioOpen(NULL, &mmioInfo, MMIO_READWRITE);
if ( hmmio == NULL ) {
MessageBox(L"Open memory file error!", L"Error");
return NULL;
}
return hmmio;
}
其中,用到了一个SDK宏:
// 用0填充内存块
void ZeroMemory(
PVOID Destination, // 块头地址
SIZE_T Length // 块的字节大小
);
#define ZeroMemory RtlZeroMemory
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
函数GetWave,调用OpenWave和GetWaveData函数,由资源ID来打开波形音频文件,获取文件句柄、格式结构和数据块长度:(与WAV文件的类似,其中的GetWaveData函数相同)
// 获取波形:文件句柄、格式结构和数据块长度
bool GetWave(UINT uiResID, HMMIO& hmmio, WAVEFORMATEX& wfFormat,
DWORD& dwDataLen) {
// Open WAV resource
hmmio = OpenWave(uiResID);
if ( hmmio == NULL ) {
MessageBox(L"Open WAV resource error!", L"Error");
return false;
}
// Get WAV data
if ( !GetWaveData(hmmio, wfFormat, dwDataLen) ) {
MessageBox(L"Get wave data error!", L"Error");
return false;
}
return true;
}
MIDI编程
本节先介绍使用MCI播放MIDI文件的方法,然后介绍MMAPI中的基本MIDI函数,最后通过一个虚拟电子琴的实例来演示MIDI的功能。
12.5.1 用MCI播放MIDI文件
MIDI所对应的MCI设备名为sequencer(音序器),似12.3节,也可以使用MCI的命令串方式:
mciSendString(命令串, NULL, 0, NULL);
来打开MIDI设备和播放MIDI文件。
打开MIDI设备的命令串常用格式为:
open MIDI文件路径 alias 别名 type sequencer
播放MIDI文件的命令串一般格式为:
play 别名 from 位置1 to 位置2
位置的单位为毫秒。若无“from 位置1”则从头播放,若无“to 位置2”则播放到文件尾。如“play 别名”是从头到尾播放。
例如:
CString str;
str += L"open \"" + ar.GetFile()->GetFilePath() + L"\" alias midi type sequencer";
wchar_t buf[256];
MCIERROR err = mciSendString(str, NULL, 0, NULL); // 打开MIDI文件
if (err) {
if (mciGetErrorString(err, buf, 256))
MessageBox(NULL, buf, L"Error", MB_OK); // 获得并显示错误串
return;
}
mciSendString(L"play midi", NULL, 0, NULL); // 播放MIDI文件
其他常用的命令还有:暂停播放(pause 别名)、恢复播放(resume 别名)、停止播放(stop 别名)、关闭设备(close 别名)等。
12.5.2 MMAPI中的基本MIDI函数
在12.4.2中已经列出了MMAPI中含有的41个MIDI函数的名称,下面将介绍其中常用的一些基本MIDI函数的功能、原型和参数。
查询MIDI设备
可以利用midiOutGetNumDevs和midiOutGetDevCaps函数来查询当前系统的MIDI设备和性能:
UINT midiOutGetNumDevs(VOID); // 返回MIDI输出设备的数目(非0表示支持MIDI)
MMRESULT midiOutGetDevCaps( // 检查MIDI输出设备的性能
UINT_PTR uDeviceID, // 设备ID(0 ~ 设备数-1)
LPMIDIOUTCAPS lpMidiOutCaps, // 指向MIDI输出设备的性能结构的指针
UINT cbMidiOutCaps // 性能结构的大小(字节数)
); // 成功时返回MMSYSERR_NOERROR(=0)
typedef struct { // MIDI输出设备的性能结构
WORD wMid; // 厂商ID
WORD wPid; // 产品ID
MMVERSION vDriverVersion; // 驱动程序版本号(高/低位字节分别为主/次版本号)
CHAR szPname[MAXPNAMELEN]; // 产品名称
WORD wTechnology; // 设备类型
WORD wVoices; // 同时音色数
WORD wNotes; // 复音数
WORD wChannelMask; // 通道数
DWORD dwSupport; // 支持的可选功能
} MIDIOUTCAPS;
其中,typedef UINT MMVERSION;,设备类型wTechnology的取值见表12-7:
表12-7 MIDI设备类型
==============================================
wTechnology 数值 设备类型
MOD_MIDIPORT 1 MIDI硬件端口
MOD_SYNTH 2 合成器
MOD_SQSYNTH 3 方波合成器
MOD_FMSYNTH 4 调频合成器
MOD_MAPPER 5 微软MIDI映射器
MOD_WAVETABLE 6 硬件波表合成器
MOD_SWSYNTH 7 软件合成器
=============================================
支持的可选功能dwSupport的取值可为表12-8中值的组合。
======================================================
dwSupport 数值 功能
MIDICAPS_VOLUME 1 支持音量控制
MIDICAPS_LRVOLUME 2 支持分离的左右音量控制
MIDICAPS_CACHE 4 支持音色(patch)缓冲
MIDICAPS_STREAM 8 提供对midiStreamOut函数的直接支持
========================================================
例如:(查询并输出MIDI设备的性能)
UINT dn = midiOutGetNumDevs();
if (dn == 0) MessageBox(L"No MIDI device!", L"Error");
else { // dn > 0
CString str;
MIDIOUTCAPS moc;
for (UINT i = 0; i < dn; i++) {
if (midiOutGetDevCaps(i, &moc, sizeof(moc)) == MMSYSERR_NOERROR) {
str.Format(L"DevNum = %d, M-ID = %d, P-ID = %d, Ver = %.4X, \
P-Name = %s, Tech = %d, Voices = %d, Notes = %d, Channels = %d, Support = %lX",
i, moc.wMid, moc.wPid, moc.vDriverVersion,moc.szPname,
moc.wTechnology, moc.wVoices, moc.wNotes, moc.wChannelMask,
moc.dwSupport);
pDC->TextOut(10, 10 + i*20, str);
}
}
输出结果如:

例如,其中设备号为1的MIDI设备:制造商ID为1(创新公司)、产品ID为102、版本号为5.10、产品名为SB Live! MIDI Synth、采用硬件波表合成器技术、可同时有32种音色和32个复音、有64K个通道、并支持对[主]音量和左右音量的控制。
播放MIDI数据
为了播放MIDI数据,必须首先调用函数midiOutOpen打开指定序号的MIDI输出设备,利用它所返回的设备句柄指针,再来调用函数midiOutPrepareHeader来准备信息头缓冲区,然后才可以调用函数midiOutLongMsg来播放MIDI数据。
在完成播放后,还必须调用函数midiOutUnprepareHeader来清除信息头缓冲区,最后调用函数midiOutClose来关闭MIDI输出设备。
在播放MIDI数据的过程中,还可以调用函数midiOutReset来停止播放。
这里所用的MIDI数据,可以是利用MMIO函数获取的MIDI文件中的数据,也可以是用户自己动态创建的定制MIDI数据。
下面是这些函数的原型和参数含义:
MMRESULT midiOutOpen( // 打开MIDI输出设备,成功返回MMSYSERR_NOERROR
LPHMIDIOUT lphmo, // 设备句柄指针(用于返回值)
UINT_PTR uDeviceID, // 设备ID(一般取为MIDI_MAPPER[=1])
DWORD_PTR dwCallback, // 回调函数指针(一般取为NULL[0])
DWORD_PTR dwCallbackInstance, // 回调函数使用的数据(一般取为NULL[0])
DWORD dwFlags // 打开设备标志(用于回调函数参数的类型,(一般取为0)
);
MMRESULT midiOutPrepareHeader( // 准备输出MIDI音频用的信息头
HMIDIOUT hmo, // (由midiOutOpen获得的)设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
typedef struct { // MIDI信息头结构
LPSTR lpData; // 指向MIDI数据缓冲区的指针
DWORD dwBufferLength; // 缓冲区的大小
DWORD dwBytesRecorded; // 录音时缓冲区中数据的实际数量
// 必须≤dwBufferLength
DWORD_PTR dwUser; // 用户定制的数据
DWORD dwFlags; // 关于缓冲区信息的标志
struct midihdr_tag far * lpNext;
DWORD_PTR reserved;
DWORD dwOffset;
DWORD_PTR dwReserved[4];
} MIDIHDR;
MMRESULT midiOutLongMsg( // 发送系统专用的MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutUnprepareHeader( // 清除输出MIDI音频用的信息头
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutReset( // 停止播放
HMIDIOUT hmo // 设备句柄指针
);
MMRESULT midiOutClose( // 关闭MIDI输出设备
HMIDIOUT hmo // 设备句柄指针
);
发送短消息
在打开MIDI输出设备后,可以调用函数midiOutShortMsg来发送短消息,从而能够动态控制MIDI输出设备和播放乐音。
该函数的原型为:
MMRESULT midiOutShortMsg( // 发送短MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
DWORD dwMsg // MIDI消息
);
其中,MIDI消息为4字节无符号整数,其高字的高位字节保留为0、低位字节为MIDI数据的第2个字节,低字的高位字节MIDI数据的第1个字节、低位字节为MIDI的状态。因为在Intel CPU中,多字节整数的低位字节在前,所以实际的MIDI消息的字节顺序见图12-4。
====================================================
字节序号: 0 1 2 3
字节内容: 状态 数据1 数据2 保留为0
==================================================
图12-4 MIDI消息的字节
例如:
union {
DWORD dwData;
BYTE bData[4];
} midi;
midi.bData[0] = status;
midi.bData[1] = data1;
midi.bData[2] = data2;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
至于MIDI消息的具体内容,会在下面的12.6.3小节中进行介绍。
其他常用函数
MMRESULT midiOutSetVolume( // 设置MIDI输出设备的音量
HMIDIOUT hmo, // 设备句柄指针
DWORD dwVolume // 低/高字为左[单]/右声道的音量 = 0 ~ 0xFFFF
);
MMRESULT midiOutGetVolume( // 获取MIDI输出设备的当前音量
HMIDIOUT hmo, // 设备句柄指针
LPDWORD lpdwVolume // 指向音量双字变量的指针
);
上面只是简单的介绍了对MIDI输出设备进行操作的一些基本MMAPI函数,还有对MIDI输入设备和MIDI流设备进行操作的各种MMAPI函数。有兴趣的读者,可以参看Visual Studio的帮助文档。
2.5.3 MIDI消息
MIDI应用程序,通过调用MMAPI函数midiOutShortMsg和midiOutLongMsg,向MIDI设备发送各种消息,来使用和控制MIDI设备、播放MIDI文件和输出MIDI数据。参见图12-5

MIDI消息分为两大类:通道消息和系统消息(参见图12-6),前者由函数midiOutShortMsg发送,后者则由函数midiOutLongMsg来发送。最常用的是通道声音消息中的音符开/关和程序变换(选择音色/乐器种类)消息。

图12-6 MIDI消息的分类
MIDI消息由若干(2~3个)字节组成,第一个字节为状态字节,后跟1~2个数据字节。为了能区分状态字节和数据字节,MIDI规定状态字节的高位为1(即值≥128[0x80]),而各数据字节的高位为0(即值<128)。
对通道声音消息,状态字节的高4位用来表示种类(8~E)、而其低4位则用来表示通道序号(n = 0~F/15)。表12-9为MIDI通道声音消息的状态字节类型和所对应的数据字节的个数和含义。
========================================
功能描述 状态字节 数据字节
英文 中文 (n = 0~F) 数量 含义
Note Off 音符关 0x8n 2 1:音高、2:速率(音量)
Note On 音符开 0x9n 2 1:音高、2:速率(音量)
Key Pressure 按键力度 0xAn 2 1:音高、2:速率(音量)
Control Change 控制改变 0xBn 2 1:控制器编号、2:控制值
Program Change 程序改变 0xCn 1 1:程序编号(音色/乐器种类)
Channel Pressure 通道力度 0xDn 1 1:压力值
Pitch Bend 音高弯度 0xEn 2 1:粗调、2:微调
========================================
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
表12-10 GM旋律乐器音色编号
分组 序号 英文名 中文名
Piano
================钢琴===============
0 Acoustic Grand Piano [非电声]三角钢琴
1 Bright Acoustic Piano 明亮[非电声]钢琴
2 Electric Grand Piano 电三角钢琴
3 Honky-Tonk Piano 酒吧钢琴
4 Electric Piano 1 (Rhodes) 电钢琴1(罗得斯/柔和)
5 Electric Piano 2 (Chorused) 电钢琴2(合唱电钢琴)
6 Harpsichord 大键琴(拨弦古钢琴)
7 Clavichord 翼琴(击弦古钢琴)
Acoustic
Percussion
=============[非电]打击乐器 ============
8 Celesta 钢片琴
9 Glockenspiel 钟琴
10 Music Box 八音盒
11 Vibraphone 颤音琴
12 Marimba 马林巴琴
13 Xylophone 木琴
14 Tubular Bells 管钟
15 Dulcimer 洋琴
Organ
==================风琴=================
16 Drawbar(Hammond) Organ 拉杆式(哈蒙德)管风琴
17 Percussive Organ 敲击式管风琴
18 Rock Organ 摇滚管风琴
19 Church Organ 教堂管风琴
20 Reed Organ 簧风琴
21 Accordion 手风琴
22 Harmonica 口琴
23 Tango Accordion 探戈口琴
Guitar
=================吉他=================
24 Nylon String Guitar 尼龙弦吉他
25 Steel String Guitar 钢弦吉他
26 Electric Jazz Guitar 电爵士吉他
27 Electric Clean Guitar 电清音吉他
28 Electric Muted Guitar 电闷音吉他
29 Overdriven Guitar 过激吉他
30 Distortion Guitar 失真吉他
31 Guitar Harmonics 吉他和声
Bass
===================贝司============
32 Acoustic Bass [非电声]贝司
33 Electric Bass(finger) 电贝司(指弹)
34 Electric Bass(pick) 电贝司(拨片)
35 Fretless Bass 无品贝司
36 Slap Bass 1 击弦贝司1
37 Slap Bass 2 击弦贝司2
38 Synth Bass 1 合成贝司1
39 Synth Bass 2 合成贝司2
Solo Strings
===============独奏弦乐器============
40 Violin 小提琴
41 Viola 中提琴
42 Cello 大提琴
43 Contrabass 低音大提琴
44 Tremolo Strings 颤音弦乐
45 Pizzicato Strings 弹拨弦乐
46 Orchestral Harp 竖琴
47 Timpani 定音鼓
Ensemble
=================合奏/合唱==============
48 String Ensemble 1 弦乐合奏1
49 String Ensemble 2 弦乐合奏2
50 Synth Strings 1 合成弦乐1
51 Synth Strings 2 合成弦乐2
52 Choir Aahs 合唱“啊”
53 Voice Oohs 人声“[u:]”
54 Synth Voice 合成人声
55 Orchestra Hit 管弦乐齐奏
Brass
===================铜管乐器==========
56 Trumpet 小号
57 Trombone 长号
58 Tuba 大号
59 Muted Trumpet 弱音小号
60 French Horn 法国号(圆号)
61 Brass Section 铜管乐合奏
62 SynthBrass 1 合成铜管乐1
63 SynthBrass 2 合成铜管乐2
Reed
============簧片乐器==============
64 Soprano Sax 高音萨克斯
65 Alto Sax 中音萨克斯
66 Tenor Sax 次中音萨克斯
67 Baritone Sax 低音萨克斯
68 Oboe 双簧管
69 English Horn 英国号
70 Bassoon 巴松(大管)
71 Clarinet 单簧管(黑管)
Pipe
============木管乐器==============
72 Piccolo 短笛
73 Flute 长笛
74 Recorder 竖笛
75 Pan Flute 排箫
76 Blown Bottle 吹瓶
77 Skakuhachi 日本笛
78 Whistle 口哨
79 Ocarina 洋埙
Synth Lead
===============合成导音================
80 Lead 1 (square) 导音1(方波)
81 Lead 2 (sawtooth) 导音2(锯齿波)
82 Lead 3 (calliope) 导音3(汽笛风琴)
83 Lead 4 (chiff) 导音4(夜莺)
84 Lead 5 (charang) 导音5(精灵)
85 Lead 6 (voice) 导音6(人声)
86 Lead 7 (fifths) 导音7(平行五度)
87 Lead 8 (bass+lead) 导音8(低音+导音)
Synth Pad
===================合成垫音=================
88 Pad 1 (new age) 垫音1(新时代)
89 Pad 2 (warm) 垫音2(温暖)
90 Pad 3 (polysynth) 垫音3(复音合成)
91 Pad 4 (choir) 垫音4(合唱)
92 Pad 5 (bowed) 垫音5(弓弦)
93 Pad 6 (metallic) 垫音6(金属)
94 Pad 7 (halo) 垫音7(光环)
95 Pad 8 (sweep) 垫音8(风声)
Synth Effects
================合成效果=================
96 FX 1 (rain) 效果1(雨声)
97 FX 2 (soundtrack) 效果2(音轨)
98 FX 3 (crystal) 效果3(水晶)
99 FX 4 (atmosphere) 效果4(大气)
100 FX 5 (brightness) 效果5(明亮)
101 FX 6 (goblins) 效果6(诡异)
102 FX 7 (echoes) 效果7(回声)
103 FX 8 (sci-fi) 效果8(科幻)
Ethnic
===================民族乐器==============
104 Sitar 西班达(印度)
105 Banjo 班卓琴(美洲)
106 Shamisen 三昧线(日本)
107 Koto 十三弦筝(日本)
108 Kalimba 卡林巴(非洲)
109 Bagpipe 风笛(苏格兰)
110 Fiddle 提琴
111 Shanai 山奈
Percussive
==================打击乐器================
112 Tinkle Bell 叮当铃
113 Agogo 摇摆舞铃
114 Steel Drums 钢鼓
115 Woodblock 木鱼
116 Taiko Drum 太科鼓
117 Melodic Tom 通通鼓
118 Synth Drum 合成鼓
119 Reverse Cymbal 铜钹
一Sound Eff2141265ects
===============声效=================
120 Guitar Fret Noise 吉他换把
121 Breath Noise 呼吸
122 Seashore 海浪
123 Bird Tweet 鸟鸣
124 Telephone Ring 电话铃
125 Helicopter 直升飞机
126 Applause 掌声
127 Gunshot 枪声
例如:
midi.bData[0] = 0xC0; // 设置通道0的音色/乐器种类
midi.bData[1] = 40; // 小提琴(缺省为0:三角钢琴)
midi.bData[2] = 0; // 未使用字节
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 设置通道0中的乐器为小提琴
12.5.4 虚拟电子琴实例
为了帮助大家理解MIDI消息的编程,本小节给出一个利用MIDI接口来模拟电子琴的简单VC实例的核心代码段。
创建项目、修改配置
创建一个名为Midi的VC05的MFC单文档项目,在项目属性中添加对多媒体库winmm.lib链接。具体做法为:
在VS05中,选“项目/Midi属性”菜单项,打开项目的属性页窗口,先选“所有配置”,再选“配置属性/链接器/输入”项,在右边上部的“附加依赖项”栏的右边,键入winmm.lib(参见图12-7)后按“应用”钮,最后按“确定”钮关闭对话框。

图12-7 添加对多媒体库winmm.lib的链接
包含多媒体头文件、添加类变量
在视图类CMidiView的头文件中,包含多媒体头文件,并添加若干类变量:
#include
#pragma once
class CMidiView : public CView {
……
protected:
UINT dn; // MIDI输出设备的数目
HMIDIOUT hmo; // MIDI输出设备的句柄
union { // MIDI消息联合
DWORD dwData;
BYTE bData[4];
} midi;
BYTE chann, patch, pitch, intens; // 通道、音色、音高、音强
DWORD vol; // [左右]音量
……
}
初始化和清除
在视图类的构造函数中设置若干初值,如:
CMidiView::CMidiView() {
dn = 0;
chann = 0; // 通道0
patch = 40; // 小提琴
pitch = 60; // 中音1
intens = 127; // 最大音强
vol = 0xFFFFFFFFl; // 最大音量
}
在视图类的初始化函数OnInitialUpdate中,判断系统是否支持MIDI,并打开标准的MIDI输出设备(MIDI_MAPPER[=1]):
void CMidiView::OnInitialUpdate() {
CView::OnInitialUpdate();
dn = midiOutGetNumDevs(); // 获取MIDI设备数目
if (dn > 0) midiOutOpen(&hmo, MIDI_MAPPER, NULL, NULL, 0);
else MessageBox(L"No MIDI device!", L"Error"); // dn == 0
}
在视图类的析构函数中关闭MIDI输出设备:
CMidiView::~CMidiView() {
if (dn > 0) midiOutClose(hmo);
}
设计对话框
可以自己设计若干对话框,让用户来选择MIDI通道、设置各通道的乐器种类和[左右]音量的大小等。
添加键盘消息响应、播放/停止音符
为视图类CMidiView添加对Windows消息WM_KEYDOWN和WM_KEYUP的消息响应函数OnKeyDown和OnKeyUp。
在其中添加对用户按1~7键(对应的虚拟键值为0x31~0x37)的响应,并发送MIDI输出的短消息来播放乐音。设用户
只按1~7键——播放中音1~7;
在按1~7键的同时按住Shift键——播放高音 ;
在按1~7键的同时按住Ctrl键——播放低音 ;
在按1~7键的同时按住Caps Lock键——播放超高音 ;
在按1~7键的同时按住左窗口键——播放超低音 。
在OnKeyDown函数中添加语句:
if (dn > 0) PlayStopPitch(0x90, nChar, nFlags); // play note
在OnKeyUp函数中添加语句:
if (dn > 0) PlayStopPitch(0x80, nChar, nFlags); // stop note
其中,PlayStopPitch是自己为视图类添加的成员函数:
void CMidiView::PlayStopPitch(BYTE status, UINT nChar, UINT nFlags) {
BYTE pitch0 = 60; // 初始音高(c1 = 中音1)
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11}; // 8度音阶(12个半音)
if (nChar >= 0x31 && nChar <= 0x37) { // paly or stop note
if (GetKeyState(VK_CAPITAL) & 1<<15) pitch0 += 24; // 高16度
else if (GetKeyState(VK_SHIFT) & 1<<15) pitch0 += 12; // 高8度
else if(GetKeyState(VK_CONTROL) & 1<<15) pitch0 -= 12; // 低8度
else if(GetKeyState(VK_LWIN) & 1<<15) pitch0 -= 24; // 低16度
midi.bData[0] = status + chann; // 播放/停止音符
midi.bData[1] = pitch0 + pitchs[nChar - 0x31]; // 音高
midi.bData[2] = intens; // 音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 发送短消息
}
}
播放连续音阶
除了响应用户按键来播放音符外,还可以设计各种自动播放音符的方法,例如播放连续的音阶(音高从0到127)。
为此,可以给视图类添加类变量(循环音高)n和(老音高)pitch0,在某个菜单或按键的消息响应中,设置计时器来启动连续音阶的播放。如:
n = 0;
SetTimer(1, 1000, NULL);
并为视图类添加计时器消息的响应函数:
void CMidiView::OnTimer(UINT_PTR nIDEvent) {
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11};
if (n > 0) { // stop note
midi.bData[0] = 0x80;
midi.bData[1] = pitch0;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
}
UINT pitch = 12 * (n / 7) + pitchs[n % 7];
if (pitch > 127) {KillTimer(1); return;} // stop timer
midi.bData[0] = 0x90; // play note
midi.bData[1] = pitch;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
n++;
pitch0 = pitch;
CView::OnTimer(nIDEvent);
}
12.5.1 用MCI播放MIDI文件
MIDI所对应的MCI设备名为sequencer(音序器),似12.3节,也可以使用MCI的命令串方式:
mciSendString(命令串, NULL, 0, NULL);
来打开MIDI设备和播放MIDI文件。
打开MIDI设备的命令串常用格式为:
open MIDI文件路径 alias 别名 type sequencer
播放MIDI文件的命令串一般格式为:
play 别名 from 位置1 to 位置2
位置的单位为毫秒。若无“from 位置1”则从头播放,若无“to 位置2”则播放到文件尾。如“play 别名”是从头到尾播放。
例如:
CString str;
str += L"open \"" + ar.GetFile()->GetFilePath() + L"\" alias midi type sequencer";
wchar_t buf[256];
MCIERROR err = mciSendString(str, NULL, 0, NULL); // 打开MIDI文件
if (err) {
if (mciGetErrorString(err, buf, 256))
MessageBox(NULL, buf, L"Error", MB_OK); // 获得并显示错误串
return;
}
mciSendString(L"play midi", NULL, 0, NULL); // 播放MIDI文件
其他常用的命令还有:暂停播放(pause 别名)、恢复播放(resume 别名)、停止播放(stop 别名)、关闭设备(close 别名)等。
12.5.2 MMAPI中的基本MIDI函数
在12.4.2中已经列出了MMAPI中含有的41个MIDI函数的名称,下面将介绍其中常用的一些基本MIDI函数的功能、原型和参数。
查询MIDI设备
可以利用midiOutGetNumDevs和midiOutGetDevCaps函数来查询当前系统的MIDI设备和性能:
UINT midiOutGetNumDevs(VOID); // 返回MIDI输出设备的数目(非0表示支持MIDI)
MMRESULT midiOutGetDevCaps( // 检查MIDI输出设备的性能
UINT_PTR uDeviceID, // 设备ID(0 ~ 设备数-1)
LPMIDIOUTCAPS lpMidiOutCaps, // 指向MIDI输出设备的性能结构的指针
UINT cbMidiOutCaps // 性能结构的大小(字节数)
); // 成功时返回MMSYSERR_NOERROR(=0)
typedef struct { // MIDI输出设备的性能结构
WORD wMid; // 厂商ID
WORD wPid; // 产品ID
MMVERSION vDriverVersion; // 驱动程序版本号(高/低位字节分别为主/次版本号)
CHAR szPname[MAXPNAMELEN]; // 产品名称
WORD wTechnology; // 设备类型
WORD wVoices; // 同时音色数
WORD wNotes; // 复音数
WORD wChannelMask; // 通道数
DWORD dwSupport; // 支持的可选功能
} MIDIOUTCAPS;
其中,typedef UINT MMVERSION;,设备类型wTechnology的取值见表12-7:
表12-7 MIDI设备类型
==============================================
wTechnology 数值 设备类型
MOD_MIDIPORT 1 MIDI硬件端口
MOD_SYNTH 2 合成器
MOD_SQSYNTH 3 方波合成器
MOD_FMSYNTH 4 调频合成器
MOD_MAPPER 5 微软MIDI映射器
MOD_WAVETABLE 6 硬件波表合成器
MOD_SWSYNTH 7 软件合成器
=============================================
支持的可选功能dwSupport的取值可为表12-8中值的组合。
======================================================
dwSupport 数值 功能
MIDICAPS_VOLUME 1 支持音量控制
MIDICAPS_LRVOLUME 2 支持分离的左右音量控制
MIDICAPS_CACHE 4 支持音色(patch)缓冲
MIDICAPS_STREAM 8 提供对midiStreamOut函数的直接支持
========================================================
例如:(查询并输出MIDI设备的性能)
UINT dn = midiOutGetNumDevs();
if (dn == 0) MessageBox(L"No MIDI device!", L"Error");
else { // dn > 0
CString str;
MIDIOUTCAPS moc;
for (UINT i = 0; i < dn; i++) {
if (midiOutGetDevCaps(i, &moc, sizeof(moc)) == MMSYSERR_NOERROR) {
str.Format(L"DevNum = %d, M-ID = %d, P-ID = %d, Ver = %.4X, \
P-Name = %s, Tech = %d, Voices = %d, Notes = %d, Channels = %d, Support = %lX",
i, moc.wMid, moc.wPid, moc.vDriverVersion,moc.szPname,
moc.wTechnology, moc.wVoices, moc.wNotes, moc.wChannelMask,
moc.dwSupport);
pDC->TextOut(10, 10 + i*20, str);
}
}
输出结果如:
例如,其中设备号为1的MIDI设备:制造商ID为1(创新公司)、产品ID为102、版本号为5.10、产品名为SB Live! MIDI Synth、采用硬件波表合成器技术、可同时有32种音色和32个复音、有64K个通道、并支持对[主]音量和左右音量的控制。
播放MIDI数据
为了播放MIDI数据,必须首先调用函数midiOutOpen打开指定序号的MIDI输出设备,利用它所返回的设备句柄指针,再来调用函数midiOutPrepareHeader来准备信息头缓冲区,然后才可以调用函数midiOutLongMsg来播放MIDI数据。
在完成播放后,还必须调用函数midiOutUnprepareHeader来清除信息头缓冲区,最后调用函数midiOutClose来关闭MIDI输出设备。
在播放MIDI数据的过程中,还可以调用函数midiOutReset来停止播放。
这里所用的MIDI数据,可以是利用MMIO函数获取的MIDI文件中的数据,也可以是用户自己动态创建的定制MIDI数据。
下面是这些函数的原型和参数含义:
MMRESULT midiOutOpen( // 打开MIDI输出设备,成功返回MMSYSERR_NOERROR
LPHMIDIOUT lphmo, // 设备句柄指针(用于返回值)
UINT_PTR uDeviceID, // 设备ID(一般取为MIDI_MAPPER[=1])
DWORD_PTR dwCallback, // 回调函数指针(一般取为NULL[0])
DWORD_PTR dwCallbackInstance, // 回调函数使用的数据(一般取为NULL[0])
DWORD dwFlags // 打开设备标志(用于回调函数参数的类型,(一般取为0)
);
MMRESULT midiOutPrepareHeader( // 准备输出MIDI音频用的信息头
HMIDIOUT hmo, // (由midiOutOpen获得的)设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
typedef struct { // MIDI信息头结构
LPSTR lpData; // 指向MIDI数据缓冲区的指针
DWORD dwBufferLength; // 缓冲区的大小
DWORD dwBytesRecorded; // 录音时缓冲区中数据的实际数量
// 必须≤dwBufferLength
DWORD_PTR dwUser; // 用户定制的数据
DWORD dwFlags; // 关于缓冲区信息的标志
struct midihdr_tag far * lpNext;
DWORD_PTR reserved;
DWORD dwOffset;
DWORD_PTR dwReserved[4];
} MIDIHDR;
MMRESULT midiOutLongMsg( // 发送系统专用的MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutUnprepareHeader( // 清除输出MIDI音频用的信息头
HMIDIOUT hmo, // 设备句柄指针
LPMIDIHDR lpMidiOutHdr, // 指向头结构的指针
UINT cbMidiOutHdr // 头结构的大小
);
MMRESULT midiOutReset( // 停止播放
HMIDIOUT hmo // 设备句柄指针
);
MMRESULT midiOutClose( // 关闭MIDI输出设备
HMIDIOUT hmo // 设备句柄指针
);
发送短消息
在打开MIDI输出设备后,可以调用函数midiOutShortMsg来发送短消息,从而能够动态控制MIDI输出设备和播放乐音。
该函数的原型为:
MMRESULT midiOutShortMsg( // 发送短MIDI消息到指定的MIDI输出设备
HMIDIOUT hmo, // 设备句柄指针
DWORD dwMsg // MIDI消息
);
其中,MIDI消息为4字节无符号整数,其高字的高位字节保留为0、低位字节为MIDI数据的第2个字节,低字的高位字节MIDI数据的第1个字节、低位字节为MIDI的状态。因为在Intel CPU中,多字节整数的低位字节在前,所以实际的MIDI消息的字节顺序见图12-4。
====================================================
字节序号: 0 1 2 3
字节内容: 状态 数据1 数据2 保留为0
==================================================
图12-4 MIDI消息的字节
例如:
union {
DWORD dwData;
BYTE bData[4];
} midi;
midi.bData[0] = status;
midi.bData[1] = data1;
midi.bData[2] = data2;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
至于MIDI消息的具体内容,会在下面的12.6.3小节中进行介绍。
其他常用函数
MMRESULT midiOutSetVolume( // 设置MIDI输出设备的音量
HMIDIOUT hmo, // 设备句柄指针
DWORD dwVolume // 低/高字为左[单]/右声道的音量 = 0 ~ 0xFFFF
);
MMRESULT midiOutGetVolume( // 获取MIDI输出设备的当前音量
HMIDIOUT hmo, // 设备句柄指针
LPDWORD lpdwVolume // 指向音量双字变量的指针
);
上面只是简单的介绍了对MIDI输出设备进行操作的一些基本MMAPI函数,还有对MIDI输入设备和MIDI流设备进行操作的各种MMAPI函数。有兴趣的读者,可以参看Visual Studio的帮助文档。
2.5.3 MIDI消息
MIDI应用程序,通过调用MMAPI函数midiOutShortMsg和midiOutLongMsg,向MIDI设备发送各种消息,来使用和控制MIDI设备、播放MIDI文件和输出MIDI数据。参见图12-5
MIDI消息分为两大类:通道消息和系统消息(参见图12-6),前者由函数midiOutShortMsg发送,后者则由函数midiOutLongMsg来发送。最常用的是通道声音消息中的音符开/关和程序变换(选择音色/乐器种类)消息。
图12-6 MIDI消息的分类
MIDI消息由若干(2~3个)字节组成,第一个字节为状态字节,后跟1~2个数据字节。为了能区分状态字节和数据字节,MIDI规定状态字节的高位为1(即值≥128[0x80]),而各数据字节的高位为0(即值<128)。
对通道声音消息,状态字节的高4位用来表示种类(8~E)、而其低4位则用来表示通道序号(n = 0~F/15)。表12-9为MIDI通道声音消息的状态字节类型和所对应的数据字节的个数和含义。
========================================
功能描述 状态字节 数据字节
英文 中文 (n = 0~F) 数量 含义
Note Off 音符关 0x8n 2 1:音高、2:速率(音量)
Note On 音符开 0x9n 2 1:音高、2:速率(音量)
Key Pressure 按键力度 0xAn 2 1:音高、2:速率(音量)
Control Change 控制改变 0xBn 2 1:控制器编号、2:控制值
Program Change 程序改变 0xCn 1 1:程序编号(音色/乐器种类)
Channel Pressure 通道力度 0xDn 1 1:压力值
Pitch Bend 音高弯度 0xEn 2 1:粗调、2:微调
========================================
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
下面只对其中最常用的音符开关(发出/停止乐音)和程序改变(选择音色/乐器种类)消息的具体内容加以说明:
0x9n状态:音符开(发出乐音)——使MIDI通道n中的乐器发出指定音高和音强的乐音。
通道序号n = 0~15,每个通道可选择一种当前音色(乐器种类)
音高(pitch)= 0(C3 = 8.16Hz)~ 127(g6 = 12543.84Hz),采用12平均律,半音阶。如中音的1~7对应于60(c1 = 1)、62(d1 = 2)、64(e1 = 3)、65(f1 = 4)、67(g1 = 5)、69(a1 = 6)、71(b1 = 7) [72(c2 = )]
音强(sound intensity)——键被按下的速率(velocity)常对应于音符的强度 = 0(无声) ~ 127(最强声)
例如:
midi.bData[0] = 0x90; // 由通道0发出乐音
midi.bData[1] = 60; // 中音C(c1 = 1)
midi.bData[2] = 127; // 最大音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 播放指定乐音
0x8n状态:音符关(停止乐音)——停止MIDI通道n中的乐器所发出的指定音高和音强的乐音。通道n、音高和音强的含义同上。
0xCn状态:程序改变(选择音色)——设置MIDI通道n中的乐器为指定的乐器种类(音色)。状态字节0xCn后跟的单个数据字节为音色/乐器种类。初始的乐器序号为0(三角钢琴)。
GM(General MIDI,通用MIDI)中定义了128种标准的音色/乐器(patch / timbre / voice),见表12-10。
表12-10 GM旋律乐器音色编号
分组 序号 英文名 中文名
Piano
================钢琴===============
0 Acoustic Grand Piano [非电声]三角钢琴
1 Bright Acoustic Piano 明亮[非电声]钢琴
2 Electric Grand Piano 电三角钢琴
3 Honky-Tonk Piano 酒吧钢琴
4 Electric Piano 1 (Rhodes) 电钢琴1(罗得斯/柔和)
5 Electric Piano 2 (Chorused) 电钢琴2(合唱电钢琴)
6 Harpsichord 大键琴(拨弦古钢琴)
7 Clavichord 翼琴(击弦古钢琴)
Acoustic
Percussion
=============[非电]打击乐器 ============
8 Celesta 钢片琴
9 Glockenspiel 钟琴
10 Music Box 八音盒
11 Vibraphone 颤音琴
12 Marimba 马林巴琴
13 Xylophone 木琴
14 Tubular Bells 管钟
15 Dulcimer 洋琴
Organ
==================风琴=================
16 Drawbar(Hammond) Organ 拉杆式(哈蒙德)管风琴
17 Percussive Organ 敲击式管风琴
18 Rock Organ 摇滚管风琴
19 Church Organ 教堂管风琴
20 Reed Organ 簧风琴
21 Accordion 手风琴
22 Harmonica 口琴
23 Tango Accordion 探戈口琴
Guitar
=================吉他=================
24 Nylon String Guitar 尼龙弦吉他
25 Steel String Guitar 钢弦吉他
26 Electric Jazz Guitar 电爵士吉他
27 Electric Clean Guitar 电清音吉他
28 Electric Muted Guitar 电闷音吉他
29 Overdriven Guitar 过激吉他
30 Distortion Guitar 失真吉他
31 Guitar Harmonics 吉他和声
Bass
===================贝司============
32 Acoustic Bass [非电声]贝司
33 Electric Bass(finger) 电贝司(指弹)
34 Electric Bass(pick) 电贝司(拨片)
35 Fretless Bass 无品贝司
36 Slap Bass 1 击弦贝司1
37 Slap Bass 2 击弦贝司2
38 Synth Bass 1 合成贝司1
39 Synth Bass 2 合成贝司2
Solo Strings
===============独奏弦乐器============
40 Violin 小提琴
41 Viola 中提琴
42 Cello 大提琴
43 Contrabass 低音大提琴
44 Tremolo Strings 颤音弦乐
45 Pizzicato Strings 弹拨弦乐
46 Orchestral Harp 竖琴
47 Timpani 定音鼓
Ensemble
=================合奏/合唱==============
48 String Ensemble 1 弦乐合奏1
49 String Ensemble 2 弦乐合奏2
50 Synth Strings 1 合成弦乐1
51 Synth Strings 2 合成弦乐2
52 Choir Aahs 合唱“啊”
53 Voice Oohs 人声“[u:]”
54 Synth Voice 合成人声
55 Orchestra Hit 管弦乐齐奏
Brass
===================铜管乐器==========
56 Trumpet 小号
57 Trombone 长号
58 Tuba 大号
59 Muted Trumpet 弱音小号
60 French Horn 法国号(圆号)
61 Brass Section 铜管乐合奏
62 SynthBrass 1 合成铜管乐1
63 SynthBrass 2 合成铜管乐2
Reed
============簧片乐器==============
64 Soprano Sax 高音萨克斯
65 Alto Sax 中音萨克斯
66 Tenor Sax 次中音萨克斯
67 Baritone Sax 低音萨克斯
68 Oboe 双簧管
69 English Horn 英国号
70 Bassoon 巴松(大管)
71 Clarinet 单簧管(黑管)
Pipe
============木管乐器==============
72 Piccolo 短笛
73 Flute 长笛
74 Recorder 竖笛
75 Pan Flute 排箫
76 Blown Bottle 吹瓶
77 Skakuhachi 日本笛
78 Whistle 口哨
79 Ocarina 洋埙
Synth Lead
===============合成导音================
80 Lead 1 (square) 导音1(方波)
81 Lead 2 (sawtooth) 导音2(锯齿波)
82 Lead 3 (calliope) 导音3(汽笛风琴)
83 Lead 4 (chiff) 导音4(夜莺)
84 Lead 5 (charang) 导音5(精灵)
85 Lead 6 (voice) 导音6(人声)
86 Lead 7 (fifths) 导音7(平行五度)
87 Lead 8 (bass+lead) 导音8(低音+导音)
Synth Pad
===================合成垫音=================
88 Pad 1 (new age) 垫音1(新时代)
89 Pad 2 (warm) 垫音2(温暖)
90 Pad 3 (polysynth) 垫音3(复音合成)
91 Pad 4 (choir) 垫音4(合唱)
92 Pad 5 (bowed) 垫音5(弓弦)
93 Pad 6 (metallic) 垫音6(金属)
94 Pad 7 (halo) 垫音7(光环)
95 Pad 8 (sweep) 垫音8(风声)
Synth Effects
================合成效果=================
96 FX 1 (rain) 效果1(雨声)
97 FX 2 (soundtrack) 效果2(音轨)
98 FX 3 (crystal) 效果3(水晶)
99 FX 4 (atmosphere) 效果4(大气)
100 FX 5 (brightness) 效果5(明亮)
101 FX 6 (goblins) 效果6(诡异)
102 FX 7 (echoes) 效果7(回声)
103 FX 8 (sci-fi) 效果8(科幻)
Ethnic
===================民族乐器==============
104 Sitar 西班达(印度)
105 Banjo 班卓琴(美洲)
106 Shamisen 三昧线(日本)
107 Koto 十三弦筝(日本)
108 Kalimba 卡林巴(非洲)
109 Bagpipe 风笛(苏格兰)
110 Fiddle 提琴
111 Shanai 山奈
Percussive
==================打击乐器================
112 Tinkle Bell 叮当铃
113 Agogo 摇摆舞铃
114 Steel Drums 钢鼓
115 Woodblock 木鱼
116 Taiko Drum 太科鼓
117 Melodic Tom 通通鼓
118 Synth Drum 合成鼓
119 Reverse Cymbal 铜钹
一Sound Eff2141265ects
===============声效=================
120 Guitar Fret Noise 吉他换把
121 Breath Noise 呼吸
122 Seashore 海浪
123 Bird Tweet 鸟鸣
124 Telephone Ring 电话铃
125 Helicopter 直升飞机
126 Applause 掌声
127 Gunshot 枪声
例如:
midi.bData[0] = 0xC0; // 设置通道0的音色/乐器种类
midi.bData[1] = 40; // 小提琴(缺省为0:三角钢琴)
midi.bData[2] = 0; // 未使用字节
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 设置通道0中的乐器为小提琴
12.5.4 虚拟电子琴实例
为了帮助大家理解MIDI消息的编程,本小节给出一个利用MIDI接口来模拟电子琴的简单VC实例的核心代码段。
创建项目、修改配置
创建一个名为Midi的VC05的MFC单文档项目,在项目属性中添加对多媒体库winmm.lib链接。具体做法为:
在VS05中,选“项目/Midi属性”菜单项,打开项目的属性页窗口,先选“所有配置”,再选“配置属性/链接器/输入”项,在右边上部的“附加依赖项”栏的右边,键入winmm.lib(参见图12-7)后按“应用”钮,最后按“确定”钮关闭对话框。
图12-7 添加对多媒体库winmm.lib的链接
包含多媒体头文件、添加类变量
在视图类CMidiView的头文件中,包含多媒体头文件,并添加若干类变量:
#include
#pragma once
class CMidiView : public CView {
……
protected:
UINT dn; // MIDI输出设备的数目
HMIDIOUT hmo; // MIDI输出设备的句柄
union { // MIDI消息联合
DWORD dwData;
BYTE bData[4];
} midi;
BYTE chann, patch, pitch, intens; // 通道、音色、音高、音强
DWORD vol; // [左右]音量
……
}
初始化和清除
在视图类的构造函数中设置若干初值,如:
CMidiView::CMidiView() {
dn = 0;
chann = 0; // 通道0
patch = 40; // 小提琴
pitch = 60; // 中音1
intens = 127; // 最大音强
vol = 0xFFFFFFFFl; // 最大音量
}
在视图类的初始化函数OnInitialUpdate中,判断系统是否支持MIDI,并打开标准的MIDI输出设备(MIDI_MAPPER[=1]):
void CMidiView::OnInitialUpdate() {
CView::OnInitialUpdate();
dn = midiOutGetNumDevs(); // 获取MIDI设备数目
if (dn > 0) midiOutOpen(&hmo, MIDI_MAPPER, NULL, NULL, 0);
else MessageBox(L"No MIDI device!", L"Error"); // dn == 0
}
在视图类的析构函数中关闭MIDI输出设备:
CMidiView::~CMidiView() {
if (dn > 0) midiOutClose(hmo);
}
设计对话框
可以自己设计若干对话框,让用户来选择MIDI通道、设置各通道的乐器种类和[左右]音量的大小等。
添加键盘消息响应、播放/停止音符
为视图类CMidiView添加对Windows消息WM_KEYDOWN和WM_KEYUP的消息响应函数OnKeyDown和OnKeyUp。
在其中添加对用户按1~7键(对应的虚拟键值为0x31~0x37)的响应,并发送MIDI输出的短消息来播放乐音。设用户
只按1~7键——播放中音1~7;
在按1~7键的同时按住Shift键——播放高音 ;
在按1~7键的同时按住Ctrl键——播放低音 ;
在按1~7键的同时按住Caps Lock键——播放超高音 ;
在按1~7键的同时按住左窗口键——播放超低音 。
在OnKeyDown函数中添加语句:
if (dn > 0) PlayStopPitch(0x90, nChar, nFlags); // play note
在OnKeyUp函数中添加语句:
if (dn > 0) PlayStopPitch(0x80, nChar, nFlags); // stop note
其中,PlayStopPitch是自己为视图类添加的成员函数:
void CMidiView::PlayStopPitch(BYTE status, UINT nChar, UINT nFlags) {
BYTE pitch0 = 60; // 初始音高(c1 = 中音1)
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11}; // 8度音阶(12个半音)
if (nChar >= 0x31 && nChar <= 0x37) { // paly or stop note
if (GetKeyState(VK_CAPITAL) & 1<<15) pitch0 += 24; // 高16度
else if (GetKeyState(VK_SHIFT) & 1<<15) pitch0 += 12; // 高8度
else if(GetKeyState(VK_CONTROL) & 1<<15) pitch0 -= 12; // 低8度
else if(GetKeyState(VK_LWIN) & 1<<15) pitch0 -= 24; // 低16度
midi.bData[0] = status + chann; // 播放/停止音符
midi.bData[1] = pitch0 + pitchs[nChar - 0x31]; // 音高
midi.bData[2] = intens; // 音强
midi.bData[3] = 0; // 保留为0
midiOutShortMsg(hmo, midi.dwData); // 发送短消息
}
}
播放连续音阶
除了响应用户按键来播放音符外,还可以设计各种自动播放音符的方法,例如播放连续的音阶(音高从0到127)。
为此,可以给视图类添加类变量(循环音高)n和(老音高)pitch0,在某个菜单或按键的消息响应中,设置计时器来启动连续音阶的播放。如:
n = 0;
SetTimer(1, 1000, NULL);
并为视图类添加计时器消息的响应函数:
void CMidiView::OnTimer(UINT_PTR nIDEvent) {
BYTE pitchs[7] = {0, 2, 4, 5, 7, 9, 11};
if (n > 0) { // stop note
midi.bData[0] = 0x80;
midi.bData[1] = pitch0;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
}
UINT pitch = 12 * (n / 7) + pitchs[n % 7];
if (pitch > 127) {KillTimer(1); return;} // stop timer
midi.bData[0] = 0x90; // play note
midi.bData[1] = pitch;
midi.bData[2] = 127;
midi.bData[3] = 0;
midiOutShortMsg(hmo, midi.dwData);
n++;
pitch0 = pitch;
CView::OnTimer(nIDEvent);
}
MMAPI简介
MMAPI(MultiMedia API)是Windows的底层多媒体编程接口,包括波形音频处理(45个函数)、MIDI操作(41个函数)、多媒体计时器管理(8个函数)、操作杆控制(8个函数)和多媒体文件I/O(19个函数),共121个函数、44条消息和14个数据结构。
本节只是列出各类MMAPI函数的名称,其中的MMIO和MIDI部分会在后面两节中介绍,其他MMAPI的函数和功能可参见Visual C++的帮助系统。
在Visual Studio 2005的帮助系统中,MCI和MMAPI的帮助位于“目录\Win32和COM开发\Graphics and Multimedia\Windows Multimedia\SDK Documentation\Windows Multimedia”的诸子目录中。
12.4.1 波形音频处理
MMAPI中含有45个波形音频处理函数(5个aux*+2个* PlaySound+38个waveIn/Out*):
auxGetDevCaps
auxGetNumDevs
auxGetVolume
auxOutMessage
auxSetVolume
PlaySound
sndPlaySound
waveInAddBuffer
waveInClose
waveInGetDevCaps
waveInGetErrorText
waveInGetID
waveInGetNumDevs
waveInGetPosition
waveInMessage
waveInOpen
waveInPrepareHeader
waveInProc
waveInReset
waveInStart
waveInStop
waveInUnprepareHeader
waveOutBreakLoop
waveOutClose
waveOutGetDevCaps
waveOutGetErrorText
waveOutGetID
waveOutGetNumDevs
waveOutGetPitch
waveOutGetPlaybackRate
waveOutGetPosition
waveOutGetVolume
waveOutMessage
waveOutOpen
waveOutPause
waveOutPrepareHeader
waveOutProc
waveOutReset
waveOutRestart
waveOutSetPitch
waveOutSetPlaybackRate
waveOutSetVolume
waveOutUnprepareHeader
waveOutWrite
12.4.2 MIDI
MMAPI中含有41个MIDI函数:
midiConnect
midiDisconnect
midiInAddBuffer
midiInClose
midiInGetDevCaps
midiInGetErrorText
midiInGetID
midiInGetNumDevs
midiInMessage
midiInOpen
midiInPrepareHeader
midiInProc
midiInReset
midiInStart
midiInStop
midiInUnprepareHeader
midiOutCacheDrumPatches
midiOutCachePatches
midiOutClose
midiOutGetDevCaps
midiOutGetErrorText
midiOutGetID
midiOutGetNumDevs
midiOutGetVolume
midiOutLongMsg
midiOutMessage
midiOutOpen
midiOutPrepareHeader
MidiOutProc
midiOutReset
midiOutSetVolume
midiOutShortMsg
midiOutUnprepareHeader
midiStreamClose
midiStreamOpen
midiStreamOut
midiStreamPause
midiStreamPosition
midiStreamProperty
midiStreamRestart
midiStreamStop
12.4.3 计时器
MMAPI中含有8个多媒体时间函数:
timeBeginPeriod
timeEndPeriod
timeGetDevCaps
timeGetSystemTime
timeGetTime
timeKillEvent
TimeProc
timeSetEvent
12.4.4 操作杆
MMAPI中含有8个操作杆(joystick)函数:
joyGetDevCaps
joyGetNumDevs
joyGetPos
joyGetPosEx
joyGetThreshold
joyReleaseCapture
joySetCapture
joySetThreshold
12.4.5 多媒体文件I/O
这里只罗列MMIO定义的2个数据类型和2个结构,以及它所提供的18个函数和1个宏,还有7个专用消息。有关MMIO的详细内容,将在后面的12.6节中作部分介绍。
数据类型
FOURCC; // 4字符代码
MMRESULT; // 错误返回代码(0表示正确)
结构:
MMIOINFO; // I/O信息
MMCKINFO; // 块信息
函数或宏
基本I/O(6个):mmioClose、mmioOpen、mmioRead、mmioRename、mmioSeek、mmioWrite
缓冲I/O(5个):mmioAdvance、mmioFlush、mmioGetInfo、mmioSetBuffer、mmioSetInfo
RIFF I/O(5个):mmioAscend、mmioCreateChunk、mmioDescend、mmioFOURCC(宏)、mmioStringToFOURCC
定制I/O 过程(3个):IOProc、mmioInstallIOProc、mmioSendMessage
其中,常用的用于读取和播放波形/AVI文件的函数有:mmioOpen、mmioClose、mmioRead、mmioDescend、mmioAscend。
消息
MMIO的专用消息有(字母序):MMIOM_CLOSE、MMIOM_OPEN、MMIOM_READ、MMIOM_RENAME、MMIOM_SEEK、MMIOM_WRITE、MMIOM_WRITEFLUSH,这些消息都用于定制的I/O过程。
本节只是列出各类MMAPI函数的名称,其中的MMIO和MIDI部分会在后面两节中介绍,其他MMAPI的函数和功能可参见Visual C++的帮助系统。
在Visual Studio 2005的帮助系统中,MCI和MMAPI的帮助位于“目录\Win32和COM开发\Graphics and Multimedia\Windows Multimedia\SDK Documentation\Windows Multimedia”的诸子目录中。
12.4.1 波形音频处理
MMAPI中含有45个波形音频处理函数(5个aux*+2个* PlaySound+38个waveIn/Out*):
auxGetDevCaps
auxGetNumDevs
auxGetVolume
auxOutMessage
auxSetVolume
PlaySound
sndPlaySound
waveInAddBuffer
waveInClose
waveInGetDevCaps
waveInGetErrorText
waveInGetID
waveInGetNumDevs
waveInGetPosition
waveInMessage
waveInOpen
waveInPrepareHeader
waveInProc
waveInReset
waveInStart
waveInStop
waveInUnprepareHeader
waveOutBreakLoop
waveOutClose
waveOutGetDevCaps
waveOutGetErrorText
waveOutGetID
waveOutGetNumDevs
waveOutGetPitch
waveOutGetPlaybackRate
waveOutGetPosition
waveOutGetVolume
waveOutMessage
waveOutOpen
waveOutPause
waveOutPrepareHeader
waveOutProc
waveOutReset
waveOutRestart
waveOutSetPitch
waveOutSetPlaybackRate
waveOutSetVolume
waveOutUnprepareHeader
waveOutWrite
12.4.2 MIDI
MMAPI中含有41个MIDI函数:
midiConnect
midiDisconnect
midiInAddBuffer
midiInClose
midiInGetDevCaps
midiInGetErrorText
midiInGetID
midiInGetNumDevs
midiInMessage
midiInOpen
midiInPrepareHeader
midiInProc
midiInReset
midiInStart
midiInStop
midiInUnprepareHeader
midiOutCacheDrumPatches
midiOutCachePatches
midiOutClose
midiOutGetDevCaps
midiOutGetErrorText
midiOutGetID
midiOutGetNumDevs
midiOutGetVolume
midiOutLongMsg
midiOutMessage
midiOutOpen
midiOutPrepareHeader
MidiOutProc
midiOutReset
midiOutSetVolume
midiOutShortMsg
midiOutUnprepareHeader
midiStreamClose
midiStreamOpen
midiStreamOut
midiStreamPause
midiStreamPosition
midiStreamProperty
midiStreamRestart
midiStreamStop
12.4.3 计时器
MMAPI中含有8个多媒体时间函数:
timeBeginPeriod
timeEndPeriod
timeGetDevCaps
timeGetSystemTime
timeGetTime
timeKillEvent
TimeProc
timeSetEvent
12.4.4 操作杆
MMAPI中含有8个操作杆(joystick)函数:
joyGetDevCaps
joyGetNumDevs
joyGetPos
joyGetPosEx
joyGetThreshold
joyReleaseCapture
joySetCapture
joySetThreshold
12.4.5 多媒体文件I/O
这里只罗列MMIO定义的2个数据类型和2个结构,以及它所提供的18个函数和1个宏,还有7个专用消息。有关MMIO的详细内容,将在后面的12.6节中作部分介绍。
数据类型
FOURCC; // 4字符代码
MMRESULT; // 错误返回代码(0表示正确)
结构:
MMIOINFO; // I/O信息
MMCKINFO; // 块信息
函数或宏
基本I/O(6个):mmioClose、mmioOpen、mmioRead、mmioRename、mmioSeek、mmioWrite
缓冲I/O(5个):mmioAdvance、mmioFlush、mmioGetInfo、mmioSetBuffer、mmioSetInfo
RIFF I/O(5个):mmioAscend、mmioCreateChunk、mmioDescend、mmioFOURCC(宏)、mmioStringToFOURCC
定制I/O 过程(3个):IOProc、mmioInstallIOProc、mmioSendMessage
其中,常用的用于读取和播放波形/AVI文件的函数有:mmioOpen、mmioClose、mmioRead、mmioDescend、mmioAscend。
消息
MMIO的专用消息有(字母序):MMIOM_CLOSE、MMIOM_OPEN、MMIOM_READ、MMIOM_RENAME、MMIOM_SEEK、MMIOM_WRITE、MMIOM_WRITEFLUSH,这些消息都用于定制的I/O过程。
MCI编程
MCI(Media Control Interface媒体控制接口)是Windows中控制多媒体设备的高层命令接口,用于事件协调及与MCI设备驱动程序的通信。
MCI的主要特点是其设备独立性。通过设备驱动程序,MCI提供了与设备无关的虚拟接口。
多媒体应用程序
||
\/
MCI命令
||
\/
MCI设备驱动程序
||
\/
多媒体设备
MCI的核心是MCI的设备驱动程序,它用于解释和执行MCI的命令,可分成如下三类:
直接控制媒体设备硬件,如CD音乐、影碟机
间接控制目标设备,通过低层MMAPI间接控制,如MIDI、WAV
提供DLL高层接口,如AVI/MPEG影片播放器
12.3.1 MCI设备
MCI设备可分为如下两种类型:
简单设备——不需要提供数据文件,如CD音乐、激光视盘机
复合设备——需要提供数据文件,如MIDI音序器(.MID)、波形音频设备(.WAV)、影片播放器(.AVI/.MPG)
每类MCI设备都有对应的命令集,但也有若干同类MCI驱动程序共用同一命令集,为了区分不同的驱动程序,MCI引入设备名:(按字母序排列)
animation——动画播放设备
cdaudio——CD音乐播放设备
dat——数字音频磁带机(DAT = Digital Audio Tape数字录音带)
digitalvideo——数字视频(不基于GUI)
mmmovie——多媒体影片播放设备
other——未定义的MCI设备(扩展用)
overlay——窗口中的模拟视频接口(基于GUI)
scanner——图像扫描仪
sequencer——MIDI音序器
vcr——盒式磁带录像机(VCR = Video Cassette Recorder)
videodisc——影碟机
waveaudio——波形音频设备
这些设备名所对应的设备驱动程序在Win32/64的注册表或老版本Windows的win.ini中定义。如win.ini的mci段:
[mci]
cdaudio=mcicda.drv
waveaudio=mciwave.drv
sequencer=mciseq.drv
mmmovie=mcimmp.drv
12.3.2 接口、函数与命令
多媒体应用程序通过发送命令来控制多媒体设备,MCI有串和消息两种命令接口方式。这些命令接口,又由若干MCI函数来具体实现。
接口
应用程序通过向MCI设备发送命令来控制它,这种命令有两种接口方式:(功能等价)
命令串方式——用近似英语的句法与MCI设备通信,直观、面向用户。是一种文本形式的接口,常用于VB等可视化编程平台和ToolBook等多媒体著作工具。如
mciExcute(L"play cdaudio from 2 to 5");
命令消息方式——利用消息和数据结构与MCI设备通信,快速、面向程序员。常用于C/C++语言的应用程序,以直接控制多媒体设备。如:
MCI_PLAY_PARMS playParms;
playParms.dwFrom = MCI_MAKE_TMSF(2, 0, 0, 0);
playParms.dwTo = MCI_MAKE_TMSF(6, 0, 0, 0);
mciSendCommand(wDevID, MCI_PLAY, MCI_FROM | MCI_TO,
(DWORD)(LPVOID)&playParms);
函数
MCI函数中,有些只适用于一种接口方式,有些则两种接口都可以用。
1)仅命令串方式
只适用于命令串接口方式的有下列2个函数:(命令串中不区分大小写)
mciSendString(发送命令串),函数原型为:
MCIERROR mciSendString( // 出错返回非零值(错误代码)
// 可用mciGetErrorString获得错误的文本描述
LPCTSTR lpszCommand, // 指向以null结尾的命令字符串:”命令 设备[ 参数]”
LPTSTR lpszReturnString,// 指向接收返回信息的缓冲区,为NULL时不返回信息
UINT cchReturn, // 上述缓冲区的大小
HANDLE hwndCallback // 在命令串中含notify时,它指定一个回调窗口的句柄
// 一般为NULL
);
如:mciSendString(L"open sample.wav type waveaudio alias wave", NULL, 0, NULL);
mciExcute(执行)是mciSendString的简化形式:
BOOL mciExcute(LPCTSTR lpszCommand); // 成功返回TRUE
该函数只有Win16支持,Win32不支持,可自写一函数实现:
BOOL mciExcute(LPCTSTR lpszCommand) {
if (mciSendString(lpszCommand, NULL, 0, NULL)) return FALSE;
return TRUE;
}
如:mciExcute(L"open sample.wav type waveaudio alias wave");
2)仅命令消息方式
只适用于命令消息接口方式的只有一个函数:
mciSendCommand(发送命令),函数原型为:
MCIERROR mciSendCommand( // 出错返回非零值(低字错误代码,高字驱动程序ID)
// 也可用mciGetErrorString获得错误的文本描述
MCIDEVICEID IDDevice, // 接收命令消息的MCI设备ID
// 对MCI_OPEN消息为NULL
UINT uMsg, // 命令消息
DWORD fdwCommand, // 命令消息的附加标志
DWORD dwParam // 命令消息参数的结构指针(可统一函数形式)
);
如:
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"waveaudio";
openParms.lpstrElementName = L"sample.wav";
openParms.lpstrAlias = L"wave";
mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT
| MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
MCIDEVICEID wDevID = openParms.wDeviceID;
3)两种接口方式
两种接口方式都支持的函数有五个:
mciGetErrorString(获得出错文本串)
BOOL mciGetErrorString( // 成功返回TRUE
DWORD fdwError, // 由mciSendString或mciSendCommand返回的错误代码
LPTSTR lpszErrorText, // 指向接收返回错误信息的文本缓冲区
UINT cchErrorText // 上述缓冲区的长度
);
如:
MCIERROR err = mciSendString(L"open sample.wav type waveaudio alias wave",
NULL, 0, NULL);
if (err) {
wchar_t errStr[256];
if (mciGetErrorString(err, errStr, 256)) MessageBox(errStr, L"Error");
}
mciGetDeviceID(获得设备ID),函数原型为:
MCIDEVICEID mciGetDeviceID( // 出错返回非零
LPCTSTR lpszDevice // 已经打开的设备[别]名
);
如:
wDevID = mciGetDeviceID(L"waveaudio");
或
wDevID = mciGetDeviceID(L"wave");
mciSetYieldProc(设置等待时调用的过程)
UINT mciSetYieldProc(// 成功返回非零值
MCIDEVICEID IDDevice, // 指定设备ID
YIELDPROC yp, // 等待时调用的过程
DWORD dwYieldData // 传送给过程的参数
);
mciGetYieldProc(获得等待时调用的过程)
YIELDPROC mciGetYieldProc( // 成功返回回调函数,失败返回NULL
MCIDEVICEID IDDevice, // 被监控的MCI设备的ID
LPDWORD lpdwYieldData // 指向传送给过程的参数之缓冲区,可为NULL
);
mciGetCreatorTask(获取创建任务)
HANDLE mciGetCreatorTask( // 成功返回创建指定设备的任务句柄,失败返回NULL
MCIDEVICEID IDDevice // 已经打开的设备ID
);
其中,后三个函数少用。
命令
MCI的命令很多,可以分为如下四类:
系统命令——直接由MCI系统解释和处理,不传送到MCI设备。如break或MCI_BREAK
通用命令——所有MCI设备都支持的命令。如open或MCI_OPEN
可选命令——MCI设备可选择使用的命令。如play或MCI_PLAY
专用命令——为某类MCI设备[集]专有。如list或MCI_LIST(DV/VCR)
前三类中部分命令参见表12-5:
类型 消息 串 说明
系统 MCI_BREAK break 为指定MCI设备设置终止键
系统MCI_SOUND sound 播放Windows指定的系统声音
系统MCI_SYSINFO sysinfo 返回有关MCI设备的信息
通用 MCI_CLOSE close 关闭MCI设备
通用MCI_GETDEVCAPS getdevcaps 获得MCI设备的性能参数
通用MCI_INFO info 获得MCI设备的有关信息
通用MCI_OPEN open 打开(初始化)MCI设备
通用MCI_STATUS status 返回MCI设备的状态信息
可选 MCI_LOAD load 从文件中加载数据
可选MCI_PAUSE pause 暂停播放/记录
可选MCI_PLAY play 开始播放数据
可选MCI_RECORD record 开始记录数据
可选MCI_RESUME resume 重新开始播放/记录
可选MCI_SAVE save 保存数据到文件
可选MCI_SEEK seek 改变当前位置
可选MCI_SET set 改变控制设置
可选MCI_STOP stop 停止播放/记录
标志 MCI_WAIT wait MCI命令执行完后才返回
标志MCI_NOTIFY notify MCI命令执行完后向应用程序发送MM_MCINOTIFY消息
注:接收MM_MCINOTIFY消息的窗口句柄由mciSendString的最后一个输入参数指定,或由mciSendCommand的最后一个输入参数——MCIDEVICEID结构的第一个域指定。
附加标志与参数结构
复杂的命令一般都有附加标志,并需要设置相应数据结构。如打开命令MCI_OPEN消息的附加标志见表12-6(可以位或|)
=========================================
标志 含义(在MCI_OPEN_PARMS结构的)
===========================================
MCI_OPEN_ALIAS lpstrAlias域指定了别名
MCI_OPEN_ELEMENT lpstrElementName域指定了元素(文件)名
MCI_OPEN_SHAREABLE 按共享方式打开MCI设备
MCI_OPEN_TYPE lpstrDeviceType域指定了设备类型
MCI_OPEN_TYPE_ID wDeviceID域指定了设备类型的ID
====================================================
其中,结构MCI_OPEN_PARMS的定义为:
typedef struct {
DWORD dwCallback;
MCIDEVICEID wDeviceID;
LPCSTR lpstrDeviceType;
LPCSTR lpstrElementName;
LPCSTR lpstrAlias;
} MCI_OPEN_PARMS;
12.3.3 编程
下面以播放CD音乐与波形音频文件为例,介绍简单与复杂MCI设备的多媒体编程。
准备
为了使用MCI编程,必须包含多媒体头文件:
#include
并在项目属性中添加对多媒体库winmm.lib链接。
命令串
1)CD
wchar_t buf[256];
MCIERROR err = mciSendString(L"open cdaudio alias cd", NULL, 0, NULL); // 打开CD
if (err) {
if (mciGetErrorString(err, buf, 256))
MessageBox(buf, L"Error"); // 获得并显示错误串
return;
}
mciSendString(L"status cd number of tracks", buf, 256, NULL); // 获得音轨总数
m_nTracks = wcstol(buf, NULL, 10); // 这里使用了宽字符版的字符串到整数的
// 转换函数:long wcstol( const wchar_t *nptr, wchar_t **endptr, int base );
wsprintf(buf, L"play cd from %d to %d", m_nFrom, m_nTo);
mciSendString(buf, NULL, 0, NULL); // 从音轨m_nFrom头播放到音轨m_nTo–1尾
其他常用命令:
mciSendString(L"set cd time format tmsf", NULL,0,NULL); // 设置时间格式为
// 轨(<100):分(<100):秒(<60):帧(<75),一秒=75块,一块=98帧
mciSendString(L"set cd door open", NULL, 0, NULL); // 打开光驱
mciSendString(L"set cd door closed", NULL, 0, NULL); // 关闭光驱
mciSendString(L"pause cd", NULL, 0, NULL); // 暂停播放
mciSendString(L"resume cd", NULL, 0, NULL); // 恢复播放
mciSendString(L"stop cd", NULL, 0, NULL); // 停止播放
mciSendString(L"close cd", NULL, 0, NULL); // 关闭CD设备
mciSendString(L"status cd length", buf, 256, NULL); // 获得整个CD的音轨总长度
mciSendString(L"status cd length track 5", buf, 256, NULL); // 获得第5个音轨的长度
mciSendString(L"status cd current track", buf, 256, NULL); // 获得当前音轨号
注意:
"play cd from m_nFrom to m_nTo"是完整形式,它还有其他形式:
"play cd from m_nFrom"从m_nFrom播放到盘尾
"play cd to m_nTo"从当前位置播放到指定音轨
"play cd" 从当前位置播放到盘尾
例如播放最后一首歌:"play cd from m_nLast"
2)WAV
wchar_t buf[256];
wsprintf(buf, L"open \"%s\" alias sound type waveaudio", pDoc->strFilePath);
MCIERROR err = mciSendString(buf, NULL, 0, NULL);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L“Error”);
return;
}
// t1和t2为用户指定的起止时间(单位为秒,浮点变量)
DWORD start = (DWORD)(t1 * 1000 + 0.5), end = (DWORD)(t2 * 1000 + 0.5);
wsprintf(buf, L"play sound from %ld to %ld", start, end);
mciSendString(buf, NULL, 0, NULL);
其他常用命令:
mciSendString(L"status sound length", buf, 256, NULL);
mciSendString(L"pause sound", NULL, 0, NULL);
mciSendString(L"play sound", NULL, 0, NULL); // 从当前位置播放到文件尾
mciSendString(L"stop sound", NULL, 0, NULL);
mciSendString(L"close sound", NULL, 0, NULL);
命令消息
1)CD
// 打开CD
wchar_t buf[256];
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"cdaudio";
openParms.lpstrAlias = L"cd";
MCIERROR err = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE
| MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L"Error");
return;
}
MCIDEVICEID wDevID = openParms.wDeviceID;
// 设置时间格式
MCI_SET_PARMS setParms;
setParms.dwTimeFormat = MCI_FORMAT_TMSF;
mciSendCommand(wDevID, MCI_SET, MCI_SET_TIME_FORMAT,
(DWORD)(LPVOID)&setParms);
// 播放CD
MCI_PLAY_PARMS playParms;
playParms.dwFrom = MCI_MAKE_TMSF(m_nFrom, 0, 0, 0);
playParms.dwTo = MCI_MAKE_TMSF(m_nTo, 0, 0, 0);
mciSendCommand(wDevID, MCI_PLAY, MCI_FROM | MCI_TO,
(DWORD)(LPVOID)&playParms);
其中,设置结构MCI_SET_PARMS的定义为:
typedef struct {
DWORD dwCallback;
DWORD dwTimeFormat;
DWORD dwAudio;
} MCI_SET_PARMS;
播放结构MCI_PLAY_PARMS的定义为:
typedef struct {
DWORD dwCallback;
DWORD dwFrom;
DWORD dwTo;
} MCI_PLAY_PARMS;
2)WAV
// 打开
wchar_t buf[256];
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"waveaudio";
openParms.lpstrElementName = L"sample.wav";
openParms.lpstrAlias = L"wave";
MCIERROR err = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE |
MCI_OPEN_ELEMENT | MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L"Error");
return;
}
MCIDEVICEID wDevID = openParms.wDeviceID;
mciSendCommand(wDevID, MCI_PLAY, NULL, NULL); // 播放
MCI的主要特点是其设备独立性。通过设备驱动程序,MCI提供了与设备无关的虚拟接口。
多媒体应用程序
||
\/
MCI命令
||
\/
MCI设备驱动程序
||
\/
多媒体设备
MCI的核心是MCI的设备驱动程序,它用于解释和执行MCI的命令,可分成如下三类:
直接控制媒体设备硬件,如CD音乐、影碟机
间接控制目标设备,通过低层MMAPI间接控制,如MIDI、WAV
提供DLL高层接口,如AVI/MPEG影片播放器
12.3.1 MCI设备
MCI设备可分为如下两种类型:
简单设备——不需要提供数据文件,如CD音乐、激光视盘机
复合设备——需要提供数据文件,如MIDI音序器(.MID)、波形音频设备(.WAV)、影片播放器(.AVI/.MPG)
每类MCI设备都有对应的命令集,但也有若干同类MCI驱动程序共用同一命令集,为了区分不同的驱动程序,MCI引入设备名:(按字母序排列)
animation——动画播放设备
cdaudio——CD音乐播放设备
dat——数字音频磁带机(DAT = Digital Audio Tape数字录音带)
digitalvideo——数字视频(不基于GUI)
mmmovie——多媒体影片播放设备
other——未定义的MCI设备(扩展用)
overlay——窗口中的模拟视频接口(基于GUI)
scanner——图像扫描仪
sequencer——MIDI音序器
vcr——盒式磁带录像机(VCR = Video Cassette Recorder)
videodisc——影碟机
waveaudio——波形音频设备
这些设备名所对应的设备驱动程序在Win32/64的注册表或老版本Windows的win.ini中定义。如win.ini的mci段:
[mci]
cdaudio=mcicda.drv
waveaudio=mciwave.drv
sequencer=mciseq.drv
mmmovie=mcimmp.drv
12.3.2 接口、函数与命令
多媒体应用程序通过发送命令来控制多媒体设备,MCI有串和消息两种命令接口方式。这些命令接口,又由若干MCI函数来具体实现。
接口
应用程序通过向MCI设备发送命令来控制它,这种命令有两种接口方式:(功能等价)
命令串方式——用近似英语的句法与MCI设备通信,直观、面向用户。是一种文本形式的接口,常用于VB等可视化编程平台和ToolBook等多媒体著作工具。如
mciExcute(L"play cdaudio from 2 to 5");
命令消息方式——利用消息和数据结构与MCI设备通信,快速、面向程序员。常用于C/C++语言的应用程序,以直接控制多媒体设备。如:
MCI_PLAY_PARMS playParms;
playParms.dwFrom = MCI_MAKE_TMSF(2, 0, 0, 0);
playParms.dwTo = MCI_MAKE_TMSF(6, 0, 0, 0);
mciSendCommand(wDevID, MCI_PLAY, MCI_FROM | MCI_TO,
(DWORD)(LPVOID)&playParms);
函数
MCI函数中,有些只适用于一种接口方式,有些则两种接口都可以用。
1)仅命令串方式
只适用于命令串接口方式的有下列2个函数:(命令串中不区分大小写)
mciSendString(发送命令串),函数原型为:
MCIERROR mciSendString( // 出错返回非零值(错误代码)
// 可用mciGetErrorString获得错误的文本描述
LPCTSTR lpszCommand, // 指向以null结尾的命令字符串:”命令 设备[ 参数]”
LPTSTR lpszReturnString,// 指向接收返回信息的缓冲区,为NULL时不返回信息
UINT cchReturn, // 上述缓冲区的大小
HANDLE hwndCallback // 在命令串中含notify时,它指定一个回调窗口的句柄
// 一般为NULL
);
如:mciSendString(L"open sample.wav type waveaudio alias wave", NULL, 0, NULL);
mciExcute(执行)是mciSendString的简化形式:
BOOL mciExcute(LPCTSTR lpszCommand); // 成功返回TRUE
该函数只有Win16支持,Win32不支持,可自写一函数实现:
BOOL mciExcute(LPCTSTR lpszCommand) {
if (mciSendString(lpszCommand, NULL, 0, NULL)) return FALSE;
return TRUE;
}
如:mciExcute(L"open sample.wav type waveaudio alias wave");
2)仅命令消息方式
只适用于命令消息接口方式的只有一个函数:
mciSendCommand(发送命令),函数原型为:
MCIERROR mciSendCommand( // 出错返回非零值(低字错误代码,高字驱动程序ID)
// 也可用mciGetErrorString获得错误的文本描述
MCIDEVICEID IDDevice, // 接收命令消息的MCI设备ID
// 对MCI_OPEN消息为NULL
UINT uMsg, // 命令消息
DWORD fdwCommand, // 命令消息的附加标志
DWORD dwParam // 命令消息参数的结构指针(可统一函数形式)
);
如:
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"waveaudio";
openParms.lpstrElementName = L"sample.wav";
openParms.lpstrAlias = L"wave";
mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT
| MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
MCIDEVICEID wDevID = openParms.wDeviceID;
3)两种接口方式
两种接口方式都支持的函数有五个:
mciGetErrorString(获得出错文本串)
BOOL mciGetErrorString( // 成功返回TRUE
DWORD fdwError, // 由mciSendString或mciSendCommand返回的错误代码
LPTSTR lpszErrorText, // 指向接收返回错误信息的文本缓冲区
UINT cchErrorText // 上述缓冲区的长度
);
如:
MCIERROR err = mciSendString(L"open sample.wav type waveaudio alias wave",
NULL, 0, NULL);
if (err) {
wchar_t errStr[256];
if (mciGetErrorString(err, errStr, 256)) MessageBox(errStr, L"Error");
}
mciGetDeviceID(获得设备ID),函数原型为:
MCIDEVICEID mciGetDeviceID( // 出错返回非零
LPCTSTR lpszDevice // 已经打开的设备[别]名
);
如:
wDevID = mciGetDeviceID(L"waveaudio");
或
wDevID = mciGetDeviceID(L"wave");
mciSetYieldProc(设置等待时调用的过程)
UINT mciSetYieldProc(// 成功返回非零值
MCIDEVICEID IDDevice, // 指定设备ID
YIELDPROC yp, // 等待时调用的过程
DWORD dwYieldData // 传送给过程的参数
);
mciGetYieldProc(获得等待时调用的过程)
YIELDPROC mciGetYieldProc( // 成功返回回调函数,失败返回NULL
MCIDEVICEID IDDevice, // 被监控的MCI设备的ID
LPDWORD lpdwYieldData // 指向传送给过程的参数之缓冲区,可为NULL
);
mciGetCreatorTask(获取创建任务)
HANDLE mciGetCreatorTask( // 成功返回创建指定设备的任务句柄,失败返回NULL
MCIDEVICEID IDDevice // 已经打开的设备ID
);
其中,后三个函数少用。
命令
MCI的命令很多,可以分为如下四类:
系统命令——直接由MCI系统解释和处理,不传送到MCI设备。如break或MCI_BREAK
通用命令——所有MCI设备都支持的命令。如open或MCI_OPEN
可选命令——MCI设备可选择使用的命令。如play或MCI_PLAY
专用命令——为某类MCI设备[集]专有。如list或MCI_LIST(DV/VCR)
前三类中部分命令参见表12-5:
类型 消息 串 说明
系统 MCI_BREAK break 为指定MCI设备设置终止键
系统MCI_SOUND sound 播放Windows指定的系统声音
系统MCI_SYSINFO sysinfo 返回有关MCI设备的信息
通用 MCI_CLOSE close 关闭MCI设备
通用MCI_GETDEVCAPS getdevcaps 获得MCI设备的性能参数
通用MCI_INFO info 获得MCI设备的有关信息
通用MCI_OPEN open 打开(初始化)MCI设备
通用MCI_STATUS status 返回MCI设备的状态信息
可选 MCI_LOAD load 从文件中加载数据
可选MCI_PAUSE pause 暂停播放/记录
可选MCI_PLAY play 开始播放数据
可选MCI_RECORD record 开始记录数据
可选MCI_RESUME resume 重新开始播放/记录
可选MCI_SAVE save 保存数据到文件
可选MCI_SEEK seek 改变当前位置
可选MCI_SET set 改变控制设置
可选MCI_STOP stop 停止播放/记录
标志 MCI_WAIT wait MCI命令执行完后才返回
标志MCI_NOTIFY notify MCI命令执行完后向应用程序发送MM_MCINOTIFY消息
注:接收MM_MCINOTIFY消息的窗口句柄由mciSendString的最后一个输入参数指定,或由mciSendCommand的最后一个输入参数——MCIDEVICEID结构的第一个域指定。
附加标志与参数结构
复杂的命令一般都有附加标志,并需要设置相应数据结构。如打开命令MCI_OPEN消息的附加标志见表12-6(可以位或|)
=========================================
标志 含义(在MCI_OPEN_PARMS结构的)
===========================================
MCI_OPEN_ALIAS lpstrAlias域指定了别名
MCI_OPEN_ELEMENT lpstrElementName域指定了元素(文件)名
MCI_OPEN_SHAREABLE 按共享方式打开MCI设备
MCI_OPEN_TYPE lpstrDeviceType域指定了设备类型
MCI_OPEN_TYPE_ID wDeviceID域指定了设备类型的ID
====================================================
其中,结构MCI_OPEN_PARMS的定义为:
typedef struct {
DWORD dwCallback;
MCIDEVICEID wDeviceID;
LPCSTR lpstrDeviceType;
LPCSTR lpstrElementName;
LPCSTR lpstrAlias;
} MCI_OPEN_PARMS;
12.3.3 编程
下面以播放CD音乐与波形音频文件为例,介绍简单与复杂MCI设备的多媒体编程。
准备
为了使用MCI编程,必须包含多媒体头文件:
#include
并在项目属性中添加对多媒体库winmm.lib链接。
命令串
1)CD
wchar_t buf[256];
MCIERROR err = mciSendString(L"open cdaudio alias cd", NULL, 0, NULL); // 打开CD
if (err) {
if (mciGetErrorString(err, buf, 256))
MessageBox(buf, L"Error"); // 获得并显示错误串
return;
}
mciSendString(L"status cd number of tracks", buf, 256, NULL); // 获得音轨总数
m_nTracks = wcstol(buf, NULL, 10); // 这里使用了宽字符版的字符串到整数的
// 转换函数:long wcstol( const wchar_t *nptr, wchar_t **endptr, int base );
wsprintf(buf, L"play cd from %d to %d", m_nFrom, m_nTo);
mciSendString(buf, NULL, 0, NULL); // 从音轨m_nFrom头播放到音轨m_nTo–1尾
其他常用命令:
mciSendString(L"set cd time format tmsf", NULL,0,NULL); // 设置时间格式为
// 轨(<100):分(<100):秒(<60):帧(<75),一秒=75块,一块=98帧
mciSendString(L"set cd door open", NULL, 0, NULL); // 打开光驱
mciSendString(L"set cd door closed", NULL, 0, NULL); // 关闭光驱
mciSendString(L"pause cd", NULL, 0, NULL); // 暂停播放
mciSendString(L"resume cd", NULL, 0, NULL); // 恢复播放
mciSendString(L"stop cd", NULL, 0, NULL); // 停止播放
mciSendString(L"close cd", NULL, 0, NULL); // 关闭CD设备
mciSendString(L"status cd length", buf, 256, NULL); // 获得整个CD的音轨总长度
mciSendString(L"status cd length track 5", buf, 256, NULL); // 获得第5个音轨的长度
mciSendString(L"status cd current track", buf, 256, NULL); // 获得当前音轨号
注意:
"play cd from m_nFrom to m_nTo"是完整形式,它还有其他形式:
"play cd from m_nFrom"从m_nFrom播放到盘尾
"play cd to m_nTo"从当前位置播放到指定音轨
"play cd" 从当前位置播放到盘尾
例如播放最后一首歌:"play cd from m_nLast"
2)WAV
wchar_t buf[256];
wsprintf(buf, L"open \"%s\" alias sound type waveaudio", pDoc->strFilePath);
MCIERROR err = mciSendString(buf, NULL, 0, NULL);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L“Error”);
return;
}
// t1和t2为用户指定的起止时间(单位为秒,浮点变量)
DWORD start = (DWORD)(t1 * 1000 + 0.5), end = (DWORD)(t2 * 1000 + 0.5);
wsprintf(buf, L"play sound from %ld to %ld", start, end);
mciSendString(buf, NULL, 0, NULL);
其他常用命令:
mciSendString(L"status sound length", buf, 256, NULL);
mciSendString(L"pause sound", NULL, 0, NULL);
mciSendString(L"play sound", NULL, 0, NULL); // 从当前位置播放到文件尾
mciSendString(L"stop sound", NULL, 0, NULL);
mciSendString(L"close sound", NULL, 0, NULL);
命令消息
1)CD
// 打开CD
wchar_t buf[256];
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"cdaudio";
openParms.lpstrAlias = L"cd";
MCIERROR err = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE
| MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L"Error");
return;
}
MCIDEVICEID wDevID = openParms.wDeviceID;
// 设置时间格式
MCI_SET_PARMS setParms;
setParms.dwTimeFormat = MCI_FORMAT_TMSF;
mciSendCommand(wDevID, MCI_SET, MCI_SET_TIME_FORMAT,
(DWORD)(LPVOID)&setParms);
// 播放CD
MCI_PLAY_PARMS playParms;
playParms.dwFrom = MCI_MAKE_TMSF(m_nFrom, 0, 0, 0);
playParms.dwTo = MCI_MAKE_TMSF(m_nTo, 0, 0, 0);
mciSendCommand(wDevID, MCI_PLAY, MCI_FROM | MCI_TO,
(DWORD)(LPVOID)&playParms);
其中,设置结构MCI_SET_PARMS的定义为:
typedef struct {
DWORD dwCallback;
DWORD dwTimeFormat;
DWORD dwAudio;
} MCI_SET_PARMS;
播放结构MCI_PLAY_PARMS的定义为:
typedef struct {
DWORD dwCallback;
DWORD dwFrom;
DWORD dwTo;
} MCI_PLAY_PARMS;
2)WAV
// 打开
wchar_t buf[256];
MCI_OPEN_PARMS openParms;
openParms.lpstrDeviceType = L"waveaudio";
openParms.lpstrElementName = L"sample.wav";
openParms.lpstrAlias = L"wave";
MCIERROR err = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE |
MCI_OPEN_ELEMENT | MCI_OPEN_ALIAS, (DWORD)(LPVOID)&playParms);
if (err) {
if (mciGetErrorString(err, buf, 256)) MessageBox(buf, L"Error");
return;
}
MCIDEVICEID wDevID = openParms.wDeviceID;
mciSendCommand(wDevID, MCI_PLAY, NULL, NULL); // 播放
播放波形音频文件
本节介绍Windows中只可用来播放音频文件的几种高层函数,包括只能播放系统声音的MessageBeep、Win16中就有的可以播放波形文件的老函数sndPlaySound、以及Win32引进的功能强 大的播放波形文件的新函数PlaySound。而MCI中的声音播放将在下节介绍。
Windows音频服务的层次关系参见图12-2。

12.2.1 MessageBeep
函数原型为:
BOOL MessageBeep(
UINT uType // sound type
);
该函数调用后马上返回,若成功返回TRUE,否则返回FALSE。其中,参数uType为声音类型,其可取值见表12-2:
===============================================
uType值 || 声音
===============================================
0xFFFFFFFF(-1) ||用计算机喇叭发出标准嘟声
===============================================
MB_ICONASTERISK || 系统星号
===============================================
MB_ICONEXCLAMATION || 系统感叹号
===============================================
MB_ICONHAND || 系统手
===============================================
MB_ICONQUESTION || 系统问号
===============================================
MB_OK || 系统缺省
===============================================
这些系统事件的声音,可用控制面板中声音与音频设备程序来设置和指定,保存在注册表中。老版本的Windows将这些设置信息保存在win.ini文件的Sounds段中。如:
[Sounds]
SystemAsterisk=chord.wav, Asterisk
SystemExclamation=chord.wav, Exclamation
SystemHand=chord.wav, Critical Stop
SystemQuestion=chord.wav, Question
SystemDefault=ding.wav, Default Beep
SystemStart=data.wav, Windows Start
SystemExit=c:\windows/glass.wav, Windows Exit
若系统没有安装音频设备,则会使用计算机本身的小喇叭来发声;否则,会调用声卡来播放对应的波形文件。
可以用SDK函数waveOutGetNumDevs来判断系统是否安装了音频设备,其原型为:
UINT waveOutGetNumDevs(VOID); // 返回系统中安装的音频设备数
如
if (waveOutGetNumDevs() == 0) {MessageBox(L"No audio device!", L"Error" ); return;}
12.2.2 sndPlaySound
函数原型为:
BOOL sndPlaySound(
LPCSTR lpszSound,
UINT fuSound
);
其中,lpszSound可为注册系统声音的表项,也可为盘中的波形文件,为NULL则停止播放正在播放的任何声音。fuSound为标志参数(无符号整数),可取值为表12-3中所列诸符号常量的或:
表12-3 sndPlaySound函数中的fuSound参数
=============================================================
fuSound值 ||对应数值 ||含义
=============================================================
SND_ASYNC ||0x01 ||异步播放,调用后立即返回(最常用)
=============================================================
SND_LOOP ||0x08 ||循环播放,必须与SND_ASYNC标志同用
=============================================================
SND_MEMORY ||0x04 ||lpszSound指向内存中波形声音映像(可以动态生产声音)
=============================================================
SND_NODEFAULT ||0x02 ||找不到指定声音时,不播放缺省的声音
=============================================================
SND_NOSTOP ||0x10 ||如果有声音正在播放,则不播放指定的声音而直接返回
=============================================================
SND_SYNC ||0x00 ||同步播放,直到声音播完后调用才返回(缺省值)
=============================================================
为了结束异步或循环播放,可以用lpszSound=NULL来调用sndPlaySound函数。
如:
sndPlaySound(L"SystemAsterisk", SND_ASYNC);
sndPlaySound(strWaveFile, SND_LOOP | SND_ASYNC);
sndPlaySound(NULL, 0);
又如:
void CMTestDlg::OnSndplayPlay() {
char filters[] = L"波形音频文件(*.wav)|*.wav|所有文件(*.*)|*.*||";
CFileDialog fDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filters);
if (fDlg.DoModal() == IDOK) {
CString strWavFile = fDlg.GetPathName();
UINT flags = 0;
if (IsDlgButtonChecked(IDC_SNDPLAY_ASYNC)) flags |= SND_ASYNC;
if (IsDlgButtonChecked(IDC_SNDPLAY_LOOP)) flags |= SND_LOOP;
if (IsDlgButtonChecked(IDC_SNDPLAY_NOSTOP)) flags |= SND_NOSTOP;
sndPlaySound(strWavFile, flags);
}
}
void CMTestDlg::OnSndplayStop() {
sndPlaySound(NULL, 0);
}
注意:对波形文件,系统先在当前目录中查找,若没有,再到标准的搜索路径中去查找。若没有找到指定的系统声音或波形文件,则播放系统缺省声音;若没有定义系统缺省声音,则不播放任何声音并返回FALSE。
另外,为了使包含sndPlaySound、PlaySound或MCI函数的程序能够编译通过,必须包含多媒体头文件:
#include
并在项目属性中添加对多媒体库winmm.lib的链接。
12.2.3 PlaySound
sndPlaySound是为了与Win16兼容而保留的老函数,而PlaySound是Win32引进的新函数,它的功能更强大,sndPlaySound只不过是它的子集。其函数原型为:
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,
DWORD fdwSound
);
其中参数
pszSound——含义与fdwSound的设置有关。
若fdwSound标志设置为SND_ALIAS、SND_FILENAME或SND_RESOURCE,则pszSound为系统事件的别 名、文件名或资源ID
若fdwSound标志没有设置这些值,则先在注册表或win.ini中寻找串为pszSound的声音,若没有,则视其为文件名
若pszSound=NULL,则停止播放正在播放的任何声音(同sndPlaySound)。若要停止非波形格式的声音,必须设置fdwSound的标志SND_PURGE
hmod
若设置了fdwSound的标志SND_RESOURCE,则hmod为包含pszSound所指定资源的可执行文件的句柄
若没有设置fdwSound的标志SND_RESOURCE,则hmod必须为NULL
fdwSound——似sndPlaySound的fuSound为标志参数,它除了可取fuSound可取的6个值外,还可以取表12-4中所列的值。
表12-4 PlaySound函数中的fdwSound参数的部分值
=========================================================
fdwSound值 ||对应数值 ||含义
===========================================================
SND_NOWAIT ||0x002000 ||若设备忙,则不等待(不播放声音,立即返回)
============================================================
SND_ ALIAS ||0x010000 ||pszSound为注册项的别名
===========================================================
SND_ ALIAS_ID ||0x110000 ||别名是一个预定义的ID
=============================================================
SND_ FILENAME ||0x020000 ||pszSound为文件名
==========================================================
SND_ RESOURCE ||0x040004 ||pszSound为资源名或原子(atom)
===============================================================
SND_ PURGE ||0x40 ||清除任务的非静止事件
===============================================================
SND_ APPLICATION ||0x80 ||使用应用程序指定关联程序来播放声音
=================================================================
例如:
PlaySound(L"c:\\sounds\\sample.wav", NULL, SND_ASYNC);
若定义了波形资源:
IDR_SAMPLE WAVE "res\\sample.wav"
则可用下面的语句来播放:
PlaySound(MAKEINTRESOURCE(IDR_SAMPLE),
AfxGetInstanceHandle(), SND_ RESOURCE);
也可以用PlaySound来播放系统声音,如
PlaySound(L"SystemHand", NULL, SND_ASYNC);
Windows音频服务的层次关系参见图12-2。

12.2.1 MessageBeep
函数原型为:
BOOL MessageBeep(
UINT uType // sound type
);
该函数调用后马上返回,若成功返回TRUE,否则返回FALSE。其中,参数uType为声音类型,其可取值见表12-2:
===============================================
uType值 || 声音
===============================================
0xFFFFFFFF(-1) ||用计算机喇叭发出标准嘟声
===============================================
MB_ICONASTERISK || 系统星号
===============================================
MB_ICONEXCLAMATION || 系统感叹号
===============================================
MB_ICONHAND || 系统手
===============================================
MB_ICONQUESTION || 系统问号
===============================================
MB_OK || 系统缺省
===============================================
这些系统事件的声音,可用控制面板中声音与音频设备程序来设置和指定,保存在注册表中。老版本的Windows将这些设置信息保存在win.ini文件的Sounds段中。如:
[Sounds]
SystemAsterisk=chord.wav, Asterisk
SystemExclamation=chord.wav, Exclamation
SystemHand=chord.wav, Critical Stop
SystemQuestion=chord.wav, Question
SystemDefault=ding.wav, Default Beep
SystemStart=data.wav, Windows Start
SystemExit=c:\windows/glass.wav, Windows Exit
若系统没有安装音频设备,则会使用计算机本身的小喇叭来发声;否则,会调用声卡来播放对应的波形文件。
可以用SDK函数waveOutGetNumDevs来判断系统是否安装了音频设备,其原型为:
UINT waveOutGetNumDevs(VOID); // 返回系统中安装的音频设备数
如
if (waveOutGetNumDevs() == 0) {MessageBox(L"No audio device!", L"Error" ); return;}
12.2.2 sndPlaySound
函数原型为:
BOOL sndPlaySound(
LPCSTR lpszSound,
UINT fuSound
);
其中,lpszSound可为注册系统声音的表项,也可为盘中的波形文件,为NULL则停止播放正在播放的任何声音。fuSound为标志参数(无符号整数),可取值为表12-3中所列诸符号常量的或:
表12-3 sndPlaySound函数中的fuSound参数
=============================================================
fuSound值 ||对应数值 ||含义
=============================================================
SND_ASYNC ||0x01 ||异步播放,调用后立即返回(最常用)
=============================================================
SND_LOOP ||0x08 ||循环播放,必须与SND_ASYNC标志同用
=============================================================
SND_MEMORY ||0x04 ||lpszSound指向内存中波形声音映像(可以动态生产声音)
=============================================================
SND_NODEFAULT ||0x02 ||找不到指定声音时,不播放缺省的声音
=============================================================
SND_NOSTOP ||0x10 ||如果有声音正在播放,则不播放指定的声音而直接返回
=============================================================
SND_SYNC ||0x00 ||同步播放,直到声音播完后调用才返回(缺省值)
=============================================================
为了结束异步或循环播放,可以用lpszSound=NULL来调用sndPlaySound函数。
如:
sndPlaySound(L"SystemAsterisk", SND_ASYNC);
sndPlaySound(strWaveFile, SND_LOOP | SND_ASYNC);
sndPlaySound(NULL, 0);
又如:
void CMTestDlg::OnSndplayPlay() {
char filters[] = L"波形音频文件(*.wav)|*.wav|所有文件(*.*)|*.*||";
CFileDialog fDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filters);
if (fDlg.DoModal() == IDOK) {
CString strWavFile = fDlg.GetPathName();
UINT flags = 0;
if (IsDlgButtonChecked(IDC_SNDPLAY_ASYNC)) flags |= SND_ASYNC;
if (IsDlgButtonChecked(IDC_SNDPLAY_LOOP)) flags |= SND_LOOP;
if (IsDlgButtonChecked(IDC_SNDPLAY_NOSTOP)) flags |= SND_NOSTOP;
sndPlaySound(strWavFile, flags);
}
}
void CMTestDlg::OnSndplayStop() {
sndPlaySound(NULL, 0);
}
注意:对波形文件,系统先在当前目录中查找,若没有,再到标准的搜索路径中去查找。若没有找到指定的系统声音或波形文件,则播放系统缺省声音;若没有定义系统缺省声音,则不播放任何声音并返回FALSE。
另外,为了使包含sndPlaySound、PlaySound或MCI函数的程序能够编译通过,必须包含多媒体头文件:
#include
并在项目属性中添加对多媒体库winmm.lib的链接。
12.2.3 PlaySound
sndPlaySound是为了与Win16兼容而保留的老函数,而PlaySound是Win32引进的新函数,它的功能更强大,sndPlaySound只不过是它的子集。其函数原型为:
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,
DWORD fdwSound
);
其中参数
pszSound——含义与fdwSound的设置有关。
若fdwSound标志设置为SND_ALIAS、SND_FILENAME或SND_RESOURCE,则pszSound为系统事件的别 名、文件名或资源ID
若fdwSound标志没有设置这些值,则先在注册表或win.ini中寻找串为pszSound的声音,若没有,则视其为文件名
若pszSound=NULL,则停止播放正在播放的任何声音(同sndPlaySound)。若要停止非波形格式的声音,必须设置fdwSound的标志SND_PURGE
hmod
若设置了fdwSound的标志SND_RESOURCE,则hmod为包含pszSound所指定资源的可执行文件的句柄
若没有设置fdwSound的标志SND_RESOURCE,则hmod必须为NULL
fdwSound——似sndPlaySound的fuSound为标志参数,它除了可取fuSound可取的6个值外,还可以取表12-4中所列的值。
表12-4 PlaySound函数中的fdwSound参数的部分值
=========================================================
fdwSound值 ||对应数值 ||含义
===========================================================
SND_NOWAIT ||0x002000 ||若设备忙,则不等待(不播放声音,立即返回)
============================================================
SND_ ALIAS ||0x010000 ||pszSound为注册项的别名
===========================================================
SND_ ALIAS_ID ||0x110000 ||别名是一个预定义的ID
=============================================================
SND_ FILENAME ||0x020000 ||pszSound为文件名
==========================================================
SND_ RESOURCE ||0x040004 ||pszSound为资源名或原子(atom)
===============================================================
SND_ PURGE ||0x40 ||清除任务的非静止事件
===============================================================
SND_ APPLICATION ||0x80 ||使用应用程序指定关联程序来播放声音
=================================================================
例如:
PlaySound(L"c:\\sounds\\sample.wav", NULL, SND_ASYNC);
若定义了波形资源:
IDR_SAMPLE WAVE "res\\sample.wav"
则可用下面的语句来播放:
PlaySound(MAKEINTRESOURCE(IDR_SAMPLE),
AfxGetInstanceHandle(), SND_ RESOURCE);
也可以用PlaySound来播放系统声音,如
PlaySound(L"SystemHand", NULL, SND_ASYNC);
订阅:
博文 (Atom)