2007年12月24日星期一

混音

Symbian 6.1上实现的混音是个比较麻烦的问题,因为程序只能同时播放一个音乐,实现混音就需要程序自己来实现。下面是我从newlc上找到的一个关于PCM脉冲编码的音频信号的混音实现,其中包含了一个关键的混音算法!

Hi !!!!

I am not sure weather I have fully understood your question or not, I persume that you are asking
"How can we mix two or more audio stream", If this is the question then I am explaning below the
mixing of the two audio stream (You Can Mix More Audio Stream),

Step 1,

Get the Raw data of the two files, (Example, of the sample 8bit and 8Kh, means one sample is of
8bit)


Step 2

Let the two audio signal be A and B respectively, the range is between 0 and 255. Where A and B are the
Sample Values (Each raw data) And store the resultant into the Y

If Both the samples Values are possitve

Y = A + B - A * B / 255

Where Y is the resultant signal which contains both signal A and B, merging two audio streams into single
stream by this method solves the problem of overflow and information loss to an extent.


If the range of 8-bit sampling is between -127 to 128

If both A and B are negative Y = A +B - (A * B / (-127))
Else Y = A + B - A * B / 128


Similarly for the nbit (ex 16bit data)

For n-bit sampling audio signal


If both A and B are negative Y = A + B - (A * B / (-(2 pow(n-1) -1)))
Else Y = A + B - (A * B / (2 pow(n-1))


Step 3.

Add the Header to the Resultant (mixed) data and play back.

If some thing is unclear and ambigious let me know.

Regards
Ranjeet Gupta.

还有简单C程序示意代码,但是其中包含了核心算法:
#include
#include
#include
#include

int main(int argc,char *argv[]) {
char mixname[255];
FILE *pcm1, *pcm2, *mix;
char sample1, sample2;
int value;

pcm1 = fopen(argv[1],"r");
pcm2 = fopen(argv[2],"r");

strcpy (mixname, argv[1]);
strcat (mixname, "_temp.wav");
mix = fopen(mixname, "w");

while(!feof(pcm1)) {

sample1 = fgetc(pcm1);
sample2 = fgetc(pcm2);

if ((sample1 < 0) && (sample2 < 0)) {
value = sample1 + sample2 - (sample1 * sample2 / -(pow(2,16-1)-1));
}else{
value = sample1 + sample2 - (sample1 * sample2 / (pow(2,16-1)-1));
}

fputc(value, mix);
}


fclose(pcm1);
fclose(pcm2);
fclose(mix);

return 0;
}

另外,如果只是在Symbian上实现混音,在Nokia 的官方网站上,也有一个SoundMixer的examples
http://www.forum.nokia.com/info/sw.nokia.com/id/70a2bde5-9b14-41b3-89ae-198b0d8d380d/SoundMixer_Example_v1_0.zip.html

2007年12月23日星期日

多路数字语音合成

摘要数字音频混音是嵌入式多媒体设备不可缺少的功能,本文介绍数字混音的基本原理,并提供了几种实际应用方法,并进行了实际的测试,取得了良好的效果。 ---- 关键词 多路语音合成,混音,衰减因子。 1.引言 ---- 随着嵌入式处理器的处理能力不断加强,网络技术的不断进展,嵌入式多媒体通讯已经迅速普及,其应用产品前途很越来越广泛,如IP电话,音频会议产品,多媒体教学产品。这些产品的一个基本功能就……
【文章正文】

摘要 数字音频混音是嵌入式多媒体设备不可缺少的功能,本文介绍数字混音的基本原理,并提供了几种实际应用方法,并进行了实际的测试,取得了良好的效果。
---- 关键词 多路语音合成,混音,衰减因子。
1.引言
---- 随着嵌入式处理器的处理能力不断加强,网络技术的不断进展,嵌入式多媒体通讯已经迅速普及,其应用产品前途很越来越广泛,如IP电话,音频会议产品,多媒体教学产品。这些产品的一个基本功能就是语音采集(数字化),语音传输和实时播放。但由于成本和设计资源的限制,一个终端一般只可能有一路(立体声)输出能力,这在一对一通话是不会产生任何问题。但在多个终端同时通话时(如音频会议),如果接收到的数字语音信号不经特殊处理,就很难模拟出多个与会者在一个会议室进行对话的情形。这就要求终端有能力将收到到多路数字语音合成(混合)成一路输出。因此必须采取多路音频流混合方案。
2.设计思想:
2.1 混音的合理性和必要性:
---- 传输话音这种连续的、时间要求严格的流与传输别的数据不同。这涉及声波转换为数字信号,数字信号恢复为声波。虽然AD/DA由硬件完成,但录音和播放由用户设置。特别需要注意的是,不象典型的视频流在空间/时间域占有唯一的位置,在同一时间和位置的图象元素叠加是无任何意义的,但人耳可以感知在同一空间/ 时间播放多个音频流。这就是混音的合理性和必要性。混音就是用一个输出设备播放多个音频流。
2.2 混音方案的理论依据:
---- 在时域上语音是短时平稳信号,对语音信号进行处理的一个基本概念就是对语音样本以缓冲区为单位处理,即对输入的语音样本分帧。为了满足合理的延时要求,语音帧的长度一般是选10~20ms。
---- 当多个音频源在一个房间播放时,人耳听到的声波是各个声源声波的线性叠加,这正是模拟混音的基础。该事实表明数字话音的混音也应线性相加。假设有n路输入音频流进行混音,Xi(t)是t时刻的第i路输入语音的采样值,则t时刻的混音值为:
---- m(t)=ΣXi(t),i=0,1,…,n-1
---- 数字音频的重要参数是采样率和采样位数,其中各路输入音频流的采样率相同是混音的前提。
3. 混音的实际算法:
考虑到数字音频的采样位数,其每点的的采用的最大最小值是被限定的,如8位采样,则采样值的范围是-127~+127。因此在混音计算时,m(t)=ΣXi (t)必须考虑其溢出的情况。简单的处理溢出的方法是m(t)=(ΣXi(t))/n, 其中n表示参与混音的路数,这样即可以保证每个采样点都不会溢出。
但这种算法的缺点也很明显,即每路音频的音量都被缩小了n倍,没有使每路的音频在不溢出的情况下达到最大的输出(与混音前的每路音频尽量接近)。要达到这种效果,就需要使用另外一种比较复杂的算法: 使用衰减因子f, m(t)=Σ(Xi(t)*f)。其中 (1/n)<= f <= 1, n为混音的路数。
如上面所说,一个语音帧是一个处理单元。初始化时令f=1,每次开始处理一个新的语音帧时,任何一个合成值m(t)超过了范围,将m(t)箝位,并且求得对应的新的f,在时序上位于此点之后的样本都采用新的f。可以看出这样一来,f有越来越小的趋势。为了避免语音不必要的被衰减,因此需要有让f变大的地方,这发生在每个新的语音帧开始处理的入口。新的语音帧样本仍然需要衰减的可能性是很大的,所以f不能每次都又从1开始,而是应该在某种程度上继承过去的值。即在每个新的样本缓冲区的入口处,只要f不等于1,就将其调整为比f稍大一些的值,让它成为新的衰减因子。若样本的确不需要衰减,经过若干帧后f会慢慢变回1。
流程图如下,每次处理语音帧样本长度在10~20ms内选取, 以16位采样为例, f可取1/16, 2/16,… 15/16,1 。 也可以根据不同的应用取不同的取值范围,和不同的 #1, #2 算法。


4.注意事项:
考虑到不同的处理器的处理能力,以及产品对时延的不同要求,我们可以合理的采用上述两种方法。值得注意的是,每点的采样值都是有符号数,不能用简单的无符号数进行处理和运算。
同时,随着需要混合的语音信号路数的增加,在用户不采取任何附加的预防措施的情况下,一些并不需要的信号(例如回声和噪音)就会累积起来并终将导致让人无法接受的质量劣化。这样,每个终端还可以进行输入音频的有声/无声检测,对无有效信号的音道直接丢弃。
5.结束语
---- 在笔者开发音频会议终端(采用ARM7处理器)上,对上述两种方法都进行了测试。结果证明两种方法都能取的比较满意的效果。

2007年12月21日星期五

c#调节音量

多媒体的应用,往往离不开音频文件,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月18日星期二

Socket 错误描述

常数 值 描述
sckOutOfMemory 7 内存不足
sckInvalidPropertyValue 380 属性值无效。
sckGetNotSupported 394 属性不可读。
sckSetNotSupported 383 属性是只读的。
sckBadState 40006 所请求的事务或请求本身的错误协议或者错误连接状态。
sckInvalidArg 40014 传递给函数的参数格式不确定,或者不在指定范围内。
sckSuccess 40017 成功。
sckUnsupported 40018 不受支持的变量类型。
sckInvalidOp 40020 在当前状态下的无效操作
sckOutOfRange 40021 参数越界。
sckWrongProtocol 40026 所请求的事务或请求本身的错误协议
sckOpCanceled 1004 取消操作。
sckInvalidArgument 10014 所请求的地址是广播地址,但未设置标记。
sckWouldBlock 10035 套接字不成块,而指定操作将使之成块。
sckInProgress 10036 制造块的 Winsock 操作在进行之中。
sckAlreadyComplete 10037 完成操作。未进行制造块的操作。
sckNotSocket 10038 描述符不是套接字。
sckMsgTooBig 10040 数据报太大,不适于缓冲区的要求,因而被截断。
sckPortNotSupported 10043 不支持指定的端口。
sckAddressInUse 10048 地址在使用中。
sckAddressNotAvailable 10049 来自本地机器的不可用地址。
sckNetworkSubsystemFailed 10050 网络子系统失败。
sckNetworkUnreachable 10051 此时不能从主机到达网络。
sckNetReset 10052 在设置 SO_KEEPALIVE 时连接超时。
sckConnectAborted 11053 由于超时或者其它失败而中止连接。
sckConnectionReset 10054 通过远端重新设置连接。
sckNoBufferSpace 10055 没有可用的缓冲空间。
sckAlreadyConnected 10056 已连接套接字。
sckNotConnected 10057 未连接套接字。
sckSocketShutdown 10058 已关闭套接字。
sckTimedout 10060 已关闭套接字。
sckConnectionRefused 10061 强行拒绝连接。
sckNotInitialized 10093 应首先调用 WinsockInit。
sckHostNotFound 11001 授权应答:未找到主机。
sckHostNotFoundTryAgain 11002 非授权应答:未找到主机。
sckNonRecoverableError 11003 不可恢复的错误。
sckNoData 11004 无效名,对所请求的类型无数据记录。

2007年12月17日星期一

语音编程 学习资料

http://www.cnblogs.com/AndyHai/archive/2007/07/07/809883.html
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;
}

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

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过程。

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); // 播放

播放波形音频文件

本节介绍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);

WinMM.dll 函数汇总

auxGetDevCaps 查询指定的辅助输出设备以确定其性能
auxGetNumDevs 检取系统中存在的辅助输出设备的数量
auxGetVolume 返回指定的辅助输出设备的当前卷设备
auxOutMessage 向指定的辅助输出设备发送一条消息
auxSetVolume 在指定的辅助输出设备中设置卷
CloseDirver 关闭指定的可安装驱动器
DefDriverProc 为任何不由可安装驱动器处理的消息提供的缺省处理
Drivercallback 调用一个回调函数,发送一条消息给窗口或将一个线程的阻塞解除
DrvGetModuleHandle 返回包含指定可安装驱动器模块的实例句柄
DrvsendMessage 把指定的消息发送给可安装驱动器
GetDriverModuleHandle 返回包含指定可安装驱动器模块的实例句柄
joyGetDevCaps 查询指定的游戏杆设备以确定其性能
joyGetNumDevs 返回系统支持的游戏杆设备的数量
joyGetPos 查询指定的游戏杆设备的位置和活动性
joyGetPosEx 查询一个游戏杆设备的位置和它的按扭状态
joyGetThreshold 查询指定的游戏杆设备的当前移动阈值
joyReleaseCapture 释放由JoySetCapture函数设置的在指定游戏杆设备上的捕获
joySetCapture 发送一个游戏杆消息到指定的窗口
joySetThreshold 设置指定的游戏杆设备的移动阈值
mciGetCreatorTask 为指定的MCI设备检取其创建的任务
mciGetDeviceID 返回和打开设备名相匹配的设备标识符
mciGetErrorString 检取描述指定媒介控制接口错误代码的字符串
mciGetYieldProc 返回和媒介控制接口的WAIT标志相关的回调函数的地址
mciSendCommand 向指定的媒介控制接口设备发送一条命令
mciSendString 向指定的媒介控制接口设备发送一个字符串
mciSetYieldProc 设置一个过程地址,在MCI设备因指定了WAIT标志而等待一个命令完成时,该过程被周期性调用
midiConnect 将指定的MIDI输入设备连接到输出设备
midiDisconnect 断开MIDI输入设备和输出设备的连接
midiInAddBuffer 向指定的音乐仪器数字接口的输入设备增加一个缓冲区
midiInClose 关闭指定的音乐仪器数字接口的输入设备
midiInGetDveCaps 查询指定的音乐仪器数字接口的输入设备,以确定其性能
midiInGetErrorText 检取有关音乐仪器数字接口的输入设备指定错误的文本说明
midiInGetID 获得一个音乐一起数字接口的输入设备的标识符
midiInGetNumDevs 检取系统中音乐仪器数字接口的输入设备的数量
midiInMessage 向指定的音乐仪器数字接口的输入设备驱动器发送一条消息
midiInOpen 打开指定的音乐仪器数字接口的输入设备
midiInPrepareHeader 为音乐仪器数字接口的输入设备准备一个缓冲区
midiInReset 在给定的MIDI输入设备上输入,并将所有挂起的输入缓冲区标记为已执行的
midiInStart 启动在指定的音乐仪器数字接口的输入设备上的输入
midiInStop 停止在给定的音乐仪器数字接口的输入设备上的输入
midiInUnprepareHeader 消除由midiInPrepareHeader函数完成的准备
midiOutCacheDrumPatches 请求内部的一个MIDI合成设备预装指定的基于键的击打音色集
midiOutCachePatches 请求内部的音乐仪器数字接口的合成设备预装指定的音色集
midiOutClose 关闭指定的音乐仪器数字接口的输出设备
midiOutGetDevCaps 查询指定的音乐仪器数字接口的输出设备,以确定其性能
midiOutGetErrorText 检取有关MIDI输出设备指定采取的文本说明
midiOutGetID 检取指定的MIDI输出设备的标识符
midiOutGetNumDevs 检取系统中存在的MIDI输出设备的数量
midiOutGetVolume 返回一个MIDI输出设备的当前卷设置
midiOutLongMsg 向指定的MIDI输出设备发送一条系统专用的MIDI消息
midiOutMessage 向一MIDI输出设备驱动器发送一条消息
midiOutOpen 打开指定的MIDI输出设备进行回放
midiOutPrepareHeader 为MIDI输出设备准备一个缓冲区
midiOutReset 为指定的MIDI输出设备关闭所有MIDI通道上的所有标志
midiOutSetVolume 设置一个MIDI输出设备的卷
midiOutShortMsg 向指定的MIDI输出设备发送一条短MIDI消息
midiOutUnprepareHeader 清除由midiOutPrepareHeader函数完成的准备
midiStreamClose 关闭一个打开的MIDI流
midiStreamOpen 为输出,打开一个MIDI流
midiStreamOut 在MIDI输出设备上播放或排队一个MIDI数据流
midiStreamPause 暂停一个MIDI流的播放
midiStreamPosition 在一个MIDI流中检取当前位置
midiStreamProperty 设置或检取与MIDI输出设备相关MIDI数据流的特性
midiStreamRestart 重新启动一个暂停的MIDI流
midiStreamStop 关掉指定MIDI输出设备的所有MIDI通道
mixerClose 关闭指定的混频器
mixerGetControlDetails 检取和一个声频指线路相关的单一控件的细节
mixerGetDevCaps 查询指定的混频器以确定其性能
mixerGetID 获取指定混频器的标识符
mixerGetLineContrils 检取和一个声频线路相关的一个或多个控件
mixerGetLineInfo 检取混频器有关特有线路的信息
mixerGetNumDevs 返回系统中存在的混频器的数量
mixerMessage 把一个定制混频器驱动器消息直接发送给混频器驱动器
mixerOpen 打开指定的混频器,在应用程序关闭该句柄前保证该设备不被移走
mixerSetControlDetails 设置和一个声频指线路相关的单一控件的细节
mmioAsvance 填充一个文件的IO缓冲区
mmioAscend 取出一个RIFF文件块
mmioClose 关闭有mmioOpen打开的文件
mmioCreateChunk 创建由mmioOpen函数打开的RIFF文件中的一个块
mmioDescend 进入由mmioOpen函数打开的RIFF文件的块中,并查找一个块
mmioFlush 把文件缓冲区的数据写入磁盘中
mmioGetInfo 检取有关由mmioOpen函数创建的RIFF文件的信息
mmioInstallIOProcA 装入或删除一个自定义的IO过程
mmioOpen 为输入输出打开一个文件
mmioRead 从由mmioOpen函数打开的文件中读取指定字节数的数据
mmioRename 重新命名指定的文件
mmioSeek 改变由mmioOpen函数打开的文件中的当前指针位置
mmioSendMessage 向与指定文件相联系的IO过程发送一条消息
mmioSetBuffer 允许或禁止文件缓冲区的IO,或改变这个缓冲区,或改变这个缓冲区的大小
mmioSetInfo 更新从被打开文件中检取的信息
mmioStringToFOURCC 把一个以NULL结束的字符串转换成一个4字符代码
mmioWrite 向由mmioOpen函数打开的文件中写入指定字节数的数据
mmsystemGetVersion 返回多媒体扩展系统软件的当前版本号
OpenDriver 打开一个可安装驱动器实例,并用缺省设置或指定值初始化该实例
PlaySound 播放一个波形声音
SendDriveMessage 向指定的可安装驱动器发送一条消息
SndPlaySound 播放一个由文件名或由登记的[sound]段的入口指定的波形声音
timeBeginPeriod 设置应用程序或驱动程序使用的最小定时器分辨率
timeEndPeriod 清除应用程序或驱动程序使用的最小定时器分辨率
timeGetDevCaps 查询定时器设备以确定其性能
timeGetSystemTime 检取从WINDOWS开始已逝去的毫秒数
timeGetTime 检取从WINDOWS开始已逝去的毫秒数,此函数比上一条函数开销小
timeKillEvent 毁掉指定的定时器回调事件
timeSetEvent 设置一个定时器回调事件
waveInAddBuffer 向波形输入设备添加一个输入缓冲区
WaveInClose 关闭指定的波形输入设置
waveInGetDevCaps 查询指定的波形输入设备以确定其性能
waveInGetErrorText 检取由指定的错误代码标识的文本说明
waveInGetID 获取指定的波形输入设备的标识符
waveInGetNumDevs 返回系统中存在的波形输入设备的数量
waveInGetPosition 检取指定波形输入设备的当前位置
waveInMessage 发送一条消息给波形输入设备的驱动器
waveInOpen 为录音而打开一个波形输入设备
waveInPrepareHeader 为波形输入准备一个输入缓冲区
waveInReset 停止给定的波形输入设备的输入,且将当前位置清零
waveInStart 启动在指定的波形输入设备的输入
waveInStop 停止在指定的波形输入设备上的输入
waveInUnprepareHeader 清除由waveInPrepareHeader函数实现的准备
waveOutBreakLoop 中断给定的波形输出设备上一个循环,并允许播放驱动器列表中的下一个块
waveOutClose 关闭指定的波形输出设备
waveOutGetDevCaps 查询一个指定的波形输出设备以确定其性能
waveOutGetErrorText 检取由指定的错误代码标识的文本说明
waveOutGetID 检取指定的波形输出设备的标识符
waveOutGetNumDevs 检取系统中存在的波形输出设备的数量
waveOutGetPitch 查询一个波形输出设备的当前音调设置
waveOutGetPlaybackRate 查询一个波形输出设备当前播放的速度
waveOutGetPosition 检取指定波形输出设备的当前播放位置
waveOutGetVolume 查询指定波形输出设备的当前音量设置
waveOutMessage 发送一条消息给一个波形输出设备的驱动器
waveOutOpen 为播放打开一个波形输出设备
waveOutPause 暂停指定波形输出设备上的播放
waveOutPrepareHeader 为播放准备一个波形缓冲区
waveOutRestart 重新启动一个被暂停的波形输出设备
waveOutSetPitch 设置一个波形输出设备的音调
waveOutSetPlaybackRate 设置指定波形输出设备的速度
waveOutSetVolume 设置指定的波形输出设备的音量
waveOutUnprepareHeader 清除由waveOutPrepareHeader函数实现的准备
waveOutWrite 向指定的波形输出设备发送一个数据块




Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1544405

2007年12月12日星期三

IP多播概述

除了单播和广播支持,IP(第4版)还提供一种发送和接收IP多播流量的机制。IP多播流量发送到单个目标IP地址,但是由多个IP主机接收和处理,而不管这些主机在IP互联网络上所处的位置。一个主机侦听一个特定的IP多播地址,并接收发送到该IP地址的所有数据包。

对于一对多的数据传输,IP多播要比IP单播和广播更为高效。与单播不同,多播仅发送数据的一个副本。与广播不同,多播流量仅由正在侦听它的计算机进行接收和处理。

IP多播的附加元素如下:

• 侦听特定IP多播地址的那一组主机称为一个主机组。

• 主机组的成员关系是动态的,主机可以在任何时候加入或离开该组。

• 主机组的成员数量没有限制。

• 主机组可以跨越多个网段。这种配置需要IP路由器上的IP多播支持,并要求主机能够将它们对接收多播流量的意愿注册到该路由器。主机注册是使用“Internet组管理协议(Internet Group Management Protocol,IGMP)”来完成的。

• 主机可以向不属于对应的主机组的某个IP多播地址发送流量。


IP多播地址(也称为组地址)在224.0.0.0到239.255.255.255的D类地址范围内,这是通过将前四个高序位设置为1110来定义的。在网络前缀或无类别域间路由(Classless Inter-Domain Routing,CIDR)表示法中,IP多播地址缩写为224.0.0.0/4。 从224.0.0.0到224.0.0.255 (224.0.0.0/24)范围的多播地址保留用于本地子网,而IP报头中的生存时间(Time to Live,TTL)可忽略,它们都不会被IP路由器转发。下面是保留IP多播地址的一些例子:

• 224.0.0.1 - 该子网上的所有主机。

• 224.0.0.2 - 该子网上的所有路由器。

• 224.0.0.5 - 开放最短路径优先(Open Shortest Path First,OSPF)算法第2版,设计用于到达某个网络上的所有OSPF路由器。

• 224.0.0.6 - 开放最短路径优先算法第2版,设计用于到达某个网络上的所有OSPF指定的路由器。

• 224.0.0.9 - 路由信息协议(Routing Information Protocol,RIP)第2版。

• 224.0.1.1 - 网络时间协议(Network Time Protocol)。


返回页首
将IP多播映射到MAC层多播
为了支持IP多播,Internet权威机构把01-00-5E-00-00-00到01-00-5E-7F-FF-FF范围的多播地址保留用于以太网和光纤分布式数据接口(Fiber Distributed Data Interface,FDDI)媒体访问控制(MAC)地址。为了将一个IP多播地址映射到一个MAC层多播地址,IP多播地址的23个低序位被直接映射到MAC层多播地址23个低序位。根据D类地址约定,IP多播地址的前4位是固定的,IP多播地址中有5位没有映射到MAC层多播地址。 因此,某个主机可以接收不是它所属的组的MAC层多播数据包。 然而,一旦确定了目标IP地址,这些数据包就会被IP丢弃。

例如,多播地址224.192.16.1将变成01-00-5E-40-10-01。为了使用那23个低序位,第一个八位组将不会被使用,第二个八位组中仅有最后7位被使用。第三个和第四个八位组将直接转换为十六进制数字。对于第二个八位组,192的二进制表示11000000。如果丢弃高序位,它将变成1000000或64(十进制)或0x40(十六进制)。对于下一个八位组,16的十六进制表示0x10。对于最后一个八位组,1的十六进制表示0x01。因此,对应于224.192.16.1的MAC地址将变成01-00-5E-40-10-01。

令牌环网使用同样的方法进行MAC层多播寻址。然而,许多令牌环网络适配器并不支持它。因此在默认情况下,功能地址0xC0-00-00-04-00-00将用于通过令牌环网发送的所有IP多播流量。 有关令牌环网对IP多播的支持的更多信息,请参见RFC 1469。

返回页首
支持IP多播的Intranet
在支持IP多播的intranet(企业内联网)中,任何主机都能够向任何组地址发送IP多播流量,并且任何主机都能够接收来自任何组地址的IP多播流量,而它们的位置可忽略。为了促进这个功能,intranet的主机和路由器都必须支持IP多播。

主机的IP多播支持

为了使主机能够发送IP多播包,它必须:

• 确定要使用的IP多播地址。

该IP多播地址可由应用程序硬编码,或者通过一种分配唯一多播地址的机制来获得。

• 将IP多播数据包放到媒体上。

发送主机必须构造一个包含预期目标IP多播地址的IP数据包,并将它放到媒介上。对于诸如以太网、FDDI和令牌环网这样的共享访问技术,目标MAC地址是根据先前描述的IP多播地址来创建的。


为了使主机能够接收IP多播数据包,其必须:

• 通知IP接收多播流量。

为了确定要使用的IP多播地址,应用程序必须首先确定是创建一个新的主机组,还是使用某个现有的主机组。为了加入某个现有的组,应用程序可以使用硬编码的多播地址,或使用从某个统一资源定位符(URL)派生而来的地址。

在确定组地址之后,应用程序必须通知IP在某个指定的目标IP多播地址接收多播流量。例如,应用程序可以使用Windows Socket(Windows套接字)函数来通知IP关于所加入的多播组的情况。如果多个应用程序使用相同的IP地址,那么IP必须向每个应用程序传递多播数据包的一个副本。当应用程序加入或离开某个主机组时,IP必须跟踪哪个应用程序在使用哪个多播地址。此外,对于多宿主主机,IP必须跟踪每个子网的主机组的应用程序成员关系。

• 将多播MAC地址注册到网络适配器。

如果所使用的网络技术支持基于硬件的多播,那么网络适配器会被告知将数据包传递给特定的多播地址。对于诸如以太网、FDDI和令牌环网这样的共享访问技术,Windows 2000 NdisRequest函数可用于通知网络适配器响应对应于某个IP多播地址的多播MAC地址。

• 通知本地路由器。

主机必须通知本地子网路由器关于它正在侦听某个特定组地址的多播流量的情况。注册主机组信息的协议是“Internet组管理协议”。目前使用的IGMP有两个版本:IGMP第1版(IGMP v1)和IGMP第2版(IGMP v2)。Windows 2000和Windows XP TCP/IP支持IGMP v2。主机通过发送“IGMP主机成员关系报告”消息,在某个特定的主机组中注册成员关系。


路由器的IP多播支持

为了仅向那些存在组成员的子网转发IP包,IP多播路由器必须能够:

• 接收所有IP多播流量。

• 转发IP多播流量。

• 接收和处理“IGMP主机成员关系报告”消息。

• 查询连接的子网以了解主机成员关系状态。

• 将组成员关系传达给其他IP多播路由器。


接收所有IP多播流量

对于共享访问技术,比如以太网和FDDI,通常的网络适配器侦听模式是单播侦听模式。侦听模式是网络适配器分析传入帧的目标MAC地址,以决定是否进一步处理它们的方式。在单播侦听模式下,唯一被认为需要进一步处理的帧在网络适配上的一个感兴趣的目标MAC地址表中。通常,唯一感兴趣的地址是适配器的广播地址(0xFF-FF-FF-FF-FF-FF)和单播地址,也称为MAC地址。

然而,为了使IP多播路由器能够接收所有IP多播流量,它必须将网络适配器置于一种称为混杂模式(promiscuous mode)的特殊侦听模式。多播混杂模式分析电子和电气工程师协会(IEEE)定义的Individual/Group(单独的/组,I/G)位,以确定该帧是否需要进一步的处理。以太网和FDDI地址的I/G位是目标MAC地址的第一个字节的最后一位。

I/G位的值如下:

• 如果设置为0,那么该地址为单播(或单独的)地址。

• 如果设置为1,那么该地址为多播(或组)地址。广播地址也会设置多播位。


当把网络适配器置于多播混杂侦听模式时,多播位设置为1的所有帧都会上传以作进一步的处理。

多播混杂模式不同于混杂模式。在混杂模式下,所有帧(不管目标MAC地址如何)都会上传以作进一步的处理。混杂模式由协议分析器(也称为网络探测器)应用,比如作为Microsoft Systems Management Server组成部分的完整版本的Microsoft Network Monitor(Microsoft网络监视器)。主机的网络适配器通常不会置于多播混杂模式。

转发IP多播流量

转发IP多播数据包的能力是TCP/IP协议的一个功能,Windows 2000所采用的TCP/IP包括了这个功能。当启用多播转发(通过“路由和远程访问”服务)时,非本地子网IP多播数据包将接受分析以确定该数据包是通过哪个接口转发的。分析是通过将源地址和目标组地址与IP多播转发表中的条目作比较来完成的。当接收到一个非本地IP多播数据包时,IP报头中的生存时间(TTL)将减去1。如果TTL在减去1之后仍大于0,则检查多播转发表。如果在多播转发表中找到一个与目标IP多播地址匹配的条目,该IP多播包将使用新的TTL通过适当的接口转发。

多播转发过程不区分本地连接的子网上正在接收多播流量的主机,或者跨越该子网上另一个路由器的本地连接的子网下游网段上的主机。换句话说,多播路由器可以转发其上没有主机在侦听的子网上的多播数据包。这样的多播数据包被转发是因为,该子网上的另一个路由器指出了在它的方向上有一个主机正在接收多播流量。

多播转发表没有记录每个主机组成员或主机组成员的数量;这只是因为一个特定的主地址至少有一个主机组成员。

接收和处理“IGMP主机成员关系报告”消息

多播路由器从所有本地连接的子网上的所有主机接收“IGMP主机成员关系报告”消息。这个信息用于跟踪主机组成员关系,也就是在多播转发表中放入条目。由于所有多播路由器都在以多播混杂模式侦听,它们将接收发送到任何组地址的“IGMP主机成员关系报告”消息。

为了改进离开延迟(某个子网上的最后一台主机离开该组和再没有多播流量被转发到该子网间的时间),可能是某个子网上的某个组的最后一个成员的主机将发送一条“IGMP离开组”消息。在通过“IGMP离开组”消息向该组发送特定于多播地址的IGMP查询之后,路由器就能够确定该子网上不再有组成员。

查询连接的子网以了解主机成员关系状态

在某个特定的子网上,可能存在IGMP v1和IGMP v2主机的混合。当IGMP v1主机停止接收某个特定组地址的IP多播流量(即这个主机离开该组)时,它不会发送“IGMP离开组”消息来通知本地路由器。结果,这个主机可能离开该组;如果它是这个子网的最后一个成员,那么本地路由器将继续向该子网转发这个组的多播流量。

为弥补IGMP v1主机报告功能的缺乏并避免可能丢失“IGMP主机成员关系报告和离开组”消息,多播路由器定期地向本地子网发送“IGMP主机成员关系”查询,以获得主机成员关系信息。仍然是某个多播组成员的主机将使用一条“IGMP主机成员关系报告”消息来响应该查询。为了防止某个特定子网上的多个主机发送相同组的“IGMP主机成员关系报告”消息,主机使用一个随机响应定时器来推迟“IGMP主机成员关系报告”消息的传输。如果这条消息是该子网上的另一个主机在响应定时器过期之前发送的,则不会发送消息。

对于Windows 2000 Server“路由和远程访问服务”,接收和处理“IGMP主机成员关系报告”消息以及查询连接的子网以获得主机成员关系状态的能力,是通过添加路由协议组件并在一个接口上启用IGMP路由器模式来提供的。

将组成员关系传达给其他IP多播路由器

为了创建支持多播的包含多个路由器的IP互联网络,多播路由器必须相互传达组成员关系信息,以便组成员能够接收IP多播流量,而可以忽略它们在该IP互联网络上的位置。

多播路由器使用某种多播路由协议交换主机成员关系信息,比如距离矢量多播路由协议(Distance Vector Multicast Routing Protocol,DVMRP)、多播开放最短路径优先(MOSPF)或者协议无关多播(PIM)。组成员关系信息要么显性地传递,即交换组地址和子网信息;要么隐性地传递,即通知上游路由器关于多播流量来源的下游是否存在组成员的情况。

多播路由协议的目标包括如下:

• 从源转发出流量以防止循环。

• 最小化或消除发送到不需要该流量的子网的多播流量。

• 最小化路由器上的CPU和内存负载以实现可伸缩性。

• 最小化路由协议的开销。

• 最小化加入延时,即某个子网上的第一个主机成员开始接收组流量所花的时间。


多播路由要比单播路由更加复杂。对于单播路由,单播流量被转发到一个全局唯一的目的地。单播路线总结了全局唯一目的地的范围。网络中的单播路线是相当稳定的,仅在IP互联网络的拓扑结构发生变化时才需要更新。

对于多播路由,多播流量被转发到某个不明确的组目的地。组地址表示单独的组,并且一般不能在多播转发表中总结出来。组成员的位置是不稳定的,每当一个主机成员加入或离开某个主机组,多播路由器的多播转发表就可能需要更新。

正如单播路由协议更新单播IP路由表一样,多播路由协议将更新IP多播转发表。 Windows 2000 Server“路由和远程访问”服务没有包括任何多播路由协议,尽管它提供了一个可以运行第三方协议的平台。 Windows 2000 Server所提供的能够更新多播转发表中的条目的唯一组件是IGMP路由协议组件。

2007年12月7日星期五

负载平衡、状态管理

多服务器的负载平衡问题与状态管理~~

慢慢学吧~!~!~

使用Vs.Net的一些小技巧

1、注释或重复代码片断
在我们写代码的往往要写代码注释,可是在每个类中写这些注释的文字是不是很烦,如果你每次用复制,粘贴的花是不是也是很麻烦,如果我们能想拖控件一样,拖到代码文件中多好啊,其实在在Vs中是可能的,我们只要把我们的代码块选中,然后在代码模式中向工具箱中把这个代码快直接拖到工具箱中就行了,接下来,我们在要注释的地方直接把这个“控件”拖到我们的代码中就行了,是不是很酷!


2、格式化整个块

我们在编写代码,或者从别人的Html源文件中粘贴一段Html代码粘到我们的Vs中的设计模式中的时候,是不是格式都是原模原样的,编写代码的时候,Vs可以很好的给我们用很整齐的格式排列好,方便阅读,可是粘到vs设计模式下的Html代码的就不能保证这么有格式了,以至于我们在查找某个标记是否关闭的时候,全部都是红色的波浪线,特别的头痛。现在有一个方便的方法,让vs为我们做这个排版的工作,我们可以从编辑—高级—下点击设计文档的格式,那么我们的代码或者Html就很整齐的排版好了。快捷键:Ctrl+K+D

(html混乱)
用Ctrl+K+D后

现在的Html标记是不是按着层次排好了!同样适用于cs代码

3、创建区域选择
在VS.NET中通过按住Alt键,拖运鼠标在一个矩形区域上来做一个区域选择。不知道你是否这样选择过区域?这个技巧允许你创造一个区域并不包含其间的行(见图)。通过这个方法非常容易拷贝,剪切,粘贴矩形区域块。

你可能惊奇为什么有人需要这个深奥的特性。事实上,我经常使用它在上特定的文档段落中作一个目标查找-替换操作,但是由于常规的自动换行选项使得我没有办法这样作。我相信你会发现这个特性是非常方便的。

4、查找匹配的标记
某些标识总是成对出现。例如,“{”标识必须用对应的“}”标识关闭。虽然在Vs2005你点击一个{,和他匹配的}就会高亮显示,但是如果代码过长的话就不好找了,同样,编译器指示符“#region”必须有对应的“#endregion”指示符。当导航你的代码时,你有时需要查找对应的标识。通过按Ctrl-]你可以这样做。这个快捷键只有当光标在这些标识符的任何一个的下面时才起作用,它会立即跳转到对应的标识符而不管它是开的或闭的标识。
如果你想显亮两个匹配的标识之间的所有代码时,按Ctrl-Shift-]显亮整个块,并移动光标到开的标识处。这个快捷键只有当光标在任意的标识的下面时才起作用(如光标在区域内它就不会起作用了)。

5、跳转的方法的定义
当你看到你一个类的方法调用时,你极有可能想看看方法体。在VS.NET中通过上下文菜单实现。在文本编辑器中在方法名称内右击,从弹出菜单中选择到定义。VS.NET立即跳转到这个方法的定义处。这个特性的缺省的快捷键是F12。

6、实用预定义的代码片断
代码片断是流行的,预定义的文本模板,开发者能够选择,而不是手工敲入。例如,对Foreach循环,代替敲你需要的所有代码,你能简单地敲foreach。智能感知用新的代码片断图标显示Foreach项(见图)。如果你从智能感知列表中选择代码片项,接着按Tab,VS会插入对于“foreach”的预定义的代码片断。



7、设计视图和代码(cs)文件切换
我们在添加一个.aspx文件的时候,要想切换到后台代码,可以在设计视图下,右击鼠标右键,有一个“查看代码”选项,我们就可以很容易的切换到后台代码中,我们也可以用快捷键F7,来相互切换。

8、自定义的字符串大小写切换
可能这个小标题命名的不太确切,这里的意思就是如果我们定义了一个想这样的字符串:
string strValue = "aaaaaaa";
如果我们想让这个aaaaa变成大写的AAAAAA怎么办呢?删掉在重写?其实有个方便的变法就是用Ctrl+Shift+U,这个只能是小写转换成大写,如果我们把大写转换成小写的话就用Ctrl+U.

9、整行剪切,复制
如果我们在对整行进行剪切,复制的时候,在不选中的情况下怎么复制或剪切呢?我们可以把鼠标移到这一行的前面,然后按下Ctrl+X,或者Ctrl+C,就可以对这整行进行复制,剪切了。(同样适用于粘贴)

10.把鼠标放在你要添加的点上,双击工具箱的控件可实现添加控件(不用拖拉),可增快速度.
或在插入的地方很小时,结合方向键操作能解决鼠标拖拉难定位的问题

11.当aspx有很多XHTML验证错误时
解决方法:工具-->选项-->文本编辑器-->HTML-->验证,把右边的目标改成IE6.0就行

12.用了插件AssistX后,所定义的变量是由多个单词拼起来或者太长的话,下面就会有红色线,
解决方法:Visual Assist X Options > Text Editor > Correction > 不选第二,第三项(有时不选也无效时,可以把出错颜色选成代码编辑器的背影色(如白色))

13.自定义快捷键(最新)
解决方法:工具-->选项-->环境-->键盘-->选择或查找你要修改的命令-->在"按快捷键"下按下你自定义的快捷键,再按"分配"-->"确定"即可

.NET学习资源列表

名称:快速入门
地址:http://chs.gotdotnet.com/quickstart/
描述:本站点是微软.NET技术的快速入门网站,我们不必再安装.NET Framework中的快速入门示例程序,直接在网上查看此示例即看。

名称:微软官方.NET指导站点
地址:http://www.gotdotnet.com/
描述:上面的站点是本站的一个子站点,本站点提供微软.NET官方信息,并且有大量的用户源代码、控件下载,微软.NET开发组的人员也经常在此站点发表一些指导性文章。

名称:SourceForge
地址:http://www.sourceforge.net
描述:世界上最大的Open Source项目在线网站,上面已经有.NET的各种大型Open Source项目上千件,包括SharpDevelop、NDoc、Mono等都是在此站点发布最新源代码信息。

名称:CodeProject
地址:http://www.codeproject.com
描述:很多非官方的中小型示例源代及文章,相当全面,基本上我们想要的各种方面的资料都可以在此处查找。

名称:Fabrice's weblog
地址:http://dotnetweblogs.com/FMARGUERIE/Story/4139.aspx
描述:这是一个WebLog形式的在线日志网站,定期更新,包括.NET相关的工具、混淆器、反编译器等各种信息,十分值得收藏。

名称:
地址:http://www.aspalliance.com/aldotnet/examples/translate.aspx
描述:c#翻译为vb.net,提供一个文本框,将你的C#源代码贴进去,就可以帮你翻译成VB.NET语法。

名称:CSharpHelp
地址:http://www.csharphelp.com
描述: 专业的C#语言在线帮助网站,主要提供C#语言方面的技术文章。专业性很强。

名称:DotNet247
地址:http://www.dotnet247.com
描述:最好的索引网站,分别按照门类及命名空间的索引,也提供了Microsoft KB知识库。

名称:ASP.NET
地址:http://www.asp.net
描述:微软.NET webform的老巢,资料和实例代码都非常难得。

名称:微软.NET Winform
地址:http://www.windowsforms.net/
描述:微软.NET Winform的老巢。

名称:微软 KnowledgeBase
地址:http://support.microsoft.com/
描述:微软知识库,开发的时候遇到的怪问题,可能会在这里找到答案。

名称:MSDN
地址:http://msdn.microsoft.com/
描述:这个就不用多说了吧,虽然出了中文MSDN,但是资料还是不够全,英文的就什么都有了。

名称:HotScripts
地址:http://www.hotscripts.com/
描述:Welcome to HotScripts.com, the net’s largest PHP, CGI, Perl, javascript and ASP script collection and resource web portal. We currently have 24,004 scripts across 11 different programming languages and 1,240 categories, as well as links to books, articles, as well as programming tips and tutorials.

名称:ASPAlliance
地址:http://www.aspalliance.com/
描述:提供相当丰富的文章和示例代码,思路匮乏的时候可以找找思路

名称:CSDN文档中心
地址:http://dev.csdn.net/
描述:中文的,资料还算丰富,可以作为国内首选。

名称:DOTNET中华网
地址:http://www.aspxcn.com/
描述:2002-2003年的时候这个站点很不错的,不过现在好像管理不得力,有点疲软,资料更新也不过及时,论坛里人也不够热心了,因为希望它好起来,所以列出来。资料都比较老,不过有些D版的东西还可以。提供很多学习代码。

名称:中国DotNet俱乐部
地址:http://www.chinaspx.com/
描述:有点公司背景的网站,很健壮,资料更新及时,比较丰富。论坛解答也不错。

名称:【孟宪会之精彩世界】
地址:http://dotnet.aspx.cc/
描述:MS-MVP的个人站点,包括了他所有的经验文章,还是很值得一看的。

名称:dotNET Tools.org
地址:http://www.dotnettools.org
描述:ccboy,也就是CSDN的小气的神的站点,里面有很多关于.NET等的好东东。

名称:博客堂
地址:http://blog.joycode.com/
描述:半官方性质的MS-MVP汇集blog,大家可以在这里接触到最新的技术,了解发展趋势,对技术的探索等等,优秀的文章。

名称:DotNetBips.com - Applying .NET
地址:http://www.dotnetbips.com/
描述:该站点的文章,涉及到了整个.NET,从底层的IL到语言到架构,文章很多,质量还不错。

名称:C# Frequently Asked Questions
地址:http://blogs.msdn.com/csharpfaq/
描述:The C# team posts answers to common questions

名称:正则表达式
地址:http://www.regexplib.com/
描述: 正则表达式学习站点

名称:WINDOW formS FAQ
地址:http://www.syncfusion.com/FAQ/Winforms/
描述:常见的forms faq问题,很多问题都可以在这里找到答案。

名称:ASP.NET 常用类库说明
地址:http://www.123aspx.com/rotor/default.aspx
描述:不用多说,看标题就知道是关于asp.net的名称空间的

名称:ASP.NET System.Web.Mail
地址:http://www.systemwebmail.com/faq/3.8.aspx
描述:邮件发送常见问题解决方法

名称:VB.NET & C# 比较
地址:http://www.harding.edu/USER/fmccown/WWW/vbnet_csharp_comparison.html
描述:VB.NET跟C#语法区别

名称:VB.NET架构师 BLOG
地址:http://panopticoncentral.net/
描述:不用多说,想了解VB.NET的朋友不可不去的站点(PS,不知道我有没有记错是不是这个地址)

名称:索克论坛
地址:http://www.sorke.com/bbs/Boards.asp
描述:我想应该是国内最好的第三方.NET控件的下载基地

VS2005快捷键

VS2005 常用快捷键

Shift+Alt+Enter: 切换全屏编辑

Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全部标签

Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)

Ctrl+Shift+V: 剪贴板循环

Ctrl+左右箭头键: 一次可以移动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标位置。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或展开当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为相同的隐藏或展开状态
Ctrl+M,P: 停止大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 选择矩形文本
Alt+鼠标左按钮: 选择矩形文本

Ctrl+Shift+U: 全部变为大写
Ctrl+U: 全部变为小写

代码快捷键

Ctrl+J / Ctrl+K,L: 列出成员
Ctrl+Shift+空格键 / Ctrl+K,P: 参数信息
Ctrl+K,I: 快速信息

Ctrl+E,C / Ctrl+K,C: 注释选定内容
Ctrl+E,U / Ctrl+K,U: 取消选定注释内容

Ctrl+K,M: 生成方法存根
Ctrl+K,X: 插入代码段
Ctrl+K,S: 插入外侧代码

F12: 转到所调用过程或变量的定义

窗口快捷键

Ctrl+W,W: 浏览器窗口
Ctrl+W,S: 解决方案管理器
Ctrl+W,C: 类视图
Ctrl+W,E: 错误列表
Ctrl+W,O: 输出视图
Ctrl+W,P: 属性窗口
Ctrl+W,T: 任务列表
Ctrl+W,X: 工具箱
Ctrl+W,B: 书签窗口
Ctrl+W,U: 文档大纲

Ctrl+D,B: 断点窗口
Ctrl+D,I: 即时窗口

Ctrl+Tab: 活动窗体切换

Ctrl+Shift+N: 新建项目
Ctrl+Shift+O: 打开项目
Ctrl+Shift+S: 全部保存
Shift+Alt+C: 新建类
Ctrl+Shift+A: 新建项
Shift+Alt+Enter: 切换全屏编辑

Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全部标签

Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)

Ctrl+Shift+V: 剪贴板循环

Ctrl+左右箭头键: 一次可以移动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标位置。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或展开当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为相同的隐藏或展开状态
Ctrl+M,P: 停止大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 选择矩形文本
Alt+鼠标左按钮: 选择矩形文本

Ctrl+Shift+U: 全部变为大写
Ctrl+U: 全部变为小写

代码快捷键

Ctrl+J / Ctrl+K,L: 列出成员
Ctrl+Shift+空格键 / Ctrl+K,P: 参数信息
Ctrl+K,I: 快速信息

Ctrl+E,C / Ctrl+K,C: 注释选定内容
Ctrl+E,U / Ctrl+K,U: 取消选定注释内容

Ctrl+K,M: 生成方法存根
Ctrl+K,X: 插入代码段
Ctrl+K,S: 插入外侧代码

F12: 转到所调用过程或变量的定义

窗口快捷键

Ctrl+W,W: 浏览器窗口
Ctrl+W,S: 解决方案管理器
Ctrl+W,C: 类视图
Ctrl+W,E: 错误列表
Ctrl+W,O: 输出视图
Ctrl+W,P: 属性窗口
Ctrl+W,T: 任务列表
Ctrl+W,X: 工具箱
Ctrl+W,B: 书签窗口
Ctrl+W,U: 文档大纲

Ctrl+D,B: 断点窗口
Ctrl+D,I: 即时窗口

Ctrl+Tab: 活动窗体切换

Ctrl+Shift+N: 新建项目
Ctrl+Shift+O: 打开项目
Ctrl+Shift+S: 全部保存
Shift+Alt+C: 新建类
Ctrl+Shift+A: 新建项


Ctrl+PageUp/PageDown: 切换设计与代码视图页面

网络教学

一、伯克利

加州大学伯克利分校 http://webcast.berkeley.edu/courses.php

作为美国第一的公立大学,伯克利分校提供了许多优秀教授的播客和视频讲座,可以跟踪最新的讲座。想看教授布置的作业和课堂笔记,可以点击该教授的网页,通常,他/她都会第一堂课留下网址。实在不行,用google搜搜吧!

  伯克利的视频都是.rm格式,请注意转换

  二、麻省

麻省理工学院 http://ocw.mit.edu/OcwWeb/web/courses/courses/index.htm

麻省理工是免费开放教育课件的先驱,计划在今年把1800门课程的课件都放在网站上,提供课程与作业的PDF格式下载。三是,麻省理工只提供少数的视频讲座。坐过学生上麻省有一个绝对优势,麻省理工在中国大陆和中国台湾都建立了镜像网站,把麻省的课程都翻译成立中文。鉴于PDF格式,推荐使用FoxIt Reader。

  www.core.org.cn(中国大陆)推荐

  www.myoops.org(中国台湾)

二、卡耐基梅隆 http://www.cmu.edu/oli/

卡耐基梅隆针对初入大学的大学生,提供10门学科的课程视频。与其他大学的免费课程一样,非卡耐基梅隆的学子能学习课程,但是为了使学生能够及时了解自己的课程进度,卡耐基梅隆建议造访者在网站上注册,建立自己的资料库。这样一来,你得在有限的时间内完成一门课程,还要参加几次考试,当然,即使你得了100分,卡耐基梅隆也不会给你开证明,更不会给你学分。


四、犹他

犹他大学 http://ocw.usu.edu/front-page/Courese_listing

犹他大学类似于麻省理工,提供大量的课程课件


五、塔夫茨

塔夫茨大学 http://ocw.tufts.edu

塔夫茨大学也是“开放式教育课程”的先驱之一,初期提供的课程着重在本校专长的生命科学、跨领域方法、国际观点以及对美国地区性、全国性社群服务的基础理论。


六、公开

英国公开大学 http://openlearn.open.ac.uk/course/index.php

英国十几所大学联合起来,组建了英国公开大学。有一部分课程是对注册学生开放的,但是有一批很好的课程是免费的,并提供视频。每门课还设立了论坛,在社区中,大家发表意见,提供其他的学习资源,互相取经。在这个网站里,最能锻炼自学者的能力,因为你要不停地淘,才能找到宝贝。


七、约翰霍普金斯

约翰霍普金斯 http://ocw.jhsph.edu/topics.cfm

只有极少数人能够进入约翰霍普金斯大学就读,但是,现如今有动机的人不用花一分钱,便能通过网站获得该校的前沿知识。约翰霍普金斯提供了本学院最受欢迎的课程,包括青少年健康、行为和健康、生物统计学、环境、一般公共卫生、卫生政策、预防伤害、母亲和儿童健康、心理卫生、营养、人口科学、公共卫生准备和难民卫生等。


八、Connexions

http://cnx.rice.edu

CNX.org由莱斯大学开发,号称是课程资源免费共享图书馆。与其他大学不同的事,CNX邀请教授学者建立自己的社区,把自己的最新成果公布于世,接受大家的评价。可以说CNX开辟了大学资源共享的新天地,尤其适合自学能力超强的大学生。有些课程有中文版。


九、索菲亚

索菲亚大学 http://sofia.ocw.cn/gallery

无论是想当一名管理者、作家、评论员、还是要从事设计和IT业的人,索菲亚大学的免费课程肯定让你受益匪浅。索菲亚大学提供了8门学科的课程,其中《企业网络安全实战》已翻译成中文。


十、华盛顿

华盛顿大学 http://www.cs.washington.edu/education/course-webs.html

华盛顿大学的计算机工程学比较强,相关的几百门课程都已经放到网上。不但本科生能找到所需要的课程,连研究生也能淘到宝贝。该网站还提供特色讲座,比如:妇女、计算机与合作。课程不但提供讲座介绍、课堂笔记、有些课程还提供视频。


牛津、斯坦福、耶鲁大学联合网站

http://www.alllearn.org

哥伦比亚大学

http://ci.columbia.edu/ci

伯克利音乐学院

http://www.berkleeshares.com

杜克大学法律中心

http://www.law.duke.edu/cspd/lectures

圣母大学

http://ocw.nd.edu

英国格雷莎姆学院

http://www.gresham.ac.uk/default.asp

加州大学Irvine分校

http://ocw.uci.edu

富布莱特学校

http://ocw.fetp.edu.vn/home.cfm

日本东京大学

http://ocw.u-tokyo.ac.jp/english

日本早稻田大学

http://www.waseda.jp/ocw/index.html

日本大阪大学

http://ocw.osaka-u.ac.jp/index.php

法国巴黎高科

http://graduateschool.paristech.org





再给个看大学视频播客的



斯坦福大学 http://itunes.stanford.edu

加州大学伯克利分校http://itunes.berkeley.edu

普渡大学http://boilercast.itap.purdue.edu:1013/Boilercast

美国西南理工http://pocast.swtc.edu/lecture/index.php

加州大学洛杉矶分校http://www.bruincast.ucla.edu

西肯塔基大学http://blog.wku.edu/podcasts

Dupage学院http://www.cod.edu/multimedia/podcast/CODcast/Welcome.html

纽约城市大学http://podcast.york.cuny.edu/lectures

莱斯大学http://webcast.rice.edu

加州大学圣地亚哥分校http://podcast.ucsd.edu

剑桥大学http://mediaplayer.group.cam.ac.uk/main/Podcasts.html

美国大学华盛顿法律学院 http://www.wcl.american.edu/podcasts

杜克大学法律学院http://www.law.duke.edu/webcast

乔治敦大学 http://webcast.georgetown.edu

芝加哥大学商学院 http://www.chicagogsb.edu/multimedia/podcast

波士顿学院 http://frontrow.bc.edu

哈佛商学院在线

http://www.hbsp.harbard.edu/b02/en/hbr_ideacast.jhtml;jsessioned=NVHF0YFBS5ZCGAKRGWDR5VQBKE0YIISW

威斯康星麦迪逊大学 http://havenscenter.org/audio/audio.htm

约翰霍普金斯大学 http://www.johnshopkins.edu/podcasts.index1.html

伦敦政治经济学院 http://www.lse.ac.uk/rescources/podcasts/Default.htm

普林斯顿大学 http://uc.princeton.edu/main/index.php

英国泰晤士报MBA http://uc.princeton.edu/main/index.php

耶鲁大学 http://www.yale.edu/opa/podcast/

JavaScript函数库

-------------- 函数检索 --------------
trim函数: trim() ltrim() rtrim()
校验字符串是否为空: checkisnotempty(str)
校验字符串是否为整型: checkisinteger(str)
校验整型最小值: checkintegerminvalue(str,val)
校验整型最大值: checkintegermaxvalue(str,val)
校验整型是否为非负数: isnotnegativeinteger(str)
校验字符串是否为浮点型: checkisdouble(str)
校验浮点型最小值: checkdoubleminvalue(str,val)
校验浮点型最大值: checkdoublemaxvalue(str,val)
校验浮点型是否为非负数: isnotnegativedouble(str)
校验字符串是否为日期型: checkisvaliddate(str)
校验两个日期的先后: checkdateearlier(strstart,strend)
校验字符串是否为email型: checkemail(str)

校验字符串是否为中文: checkischinese(str)
计算字符串的长度,一个汉字两个字符: reallength()
校验字符串是否符合自定义正则表达式: checkmask(str,pat)
得到文件的后缀名: getfilepostfix(ofile)
-------------- 函数检索 --------------
*/

/**
* added by lxcjie 2004.6.25
* 去除多余空格函数
* trim:去除两边空格 ltrim:去除左空格 rtrim: 去除右空格
* 用法:
* var str = " hello ";
* str = str.trim();
*/
string.prototype.trim = function()
{
return this.replace(/(^[\s]*)|([\s]*$)/g, "");
}
string.prototype.ltrim = function()
{
return this.replace(/(^[\s]*)/g, "");
}
string.prototype.rtrim = function()
{
return this.replace(/([\s]*$)/g, "");
}
/********************************** empty **************************************/
/**
*校验字符串是否为空
*返回值:
*如果不为空,定义校验通过,返回true
*如果为空,校验不通过,返回false 参考提示信息:输入域不能为空!
*/
function checkisnotempty(str)
{
if(str.trim() == "")
return false;
else
return true;
}//~~~
/*--------------------------------- empty --------------------------------------*/
/********************************** integer *************************************/
/**
*校验字符串是否为整型
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串全部为数字,校验通过,返回true
*如果校验不通过, 返回false 参考提示信息:输入域必须为数字!
*/
function checkisinteger(str)
{
//如果为空,则通过校验
if(str == "")
return true;
if(/^(\-?)(\d+)$/.test(str))
return true;
else
return false;
}//~~~
/**
*校验整型最小值
*str:要校验的串。 val:比较的值
*
*返回值:
*如果为空,定义校验通过, 返回true
*如果满足条件,大于等于给定值,校验通过,返回true
*如果小于给定值, 返回false 参考提示信息:输入域不能小于给定值!
*/
function checkintegerminvalue(str,val)
{
//如果为空,则通过校验
if(str == "")
return true;
if(typeof(val) != "string")
val = val + "";
if(checkisinteger(str) == true)
{
if(parseint(str,10)>=parseint(val,10))
return true;
else
return false;
}
else
return false;
}//~~~
/**
*校验整型最大值
*str:要校验的串。 val:比较的值
*
*返回值:
*如果为空,定义校验通过, 返回true
*如果满足条件,小于等于给定值,校验通过,返回true
*如果大于给定值, 返回false 参考提示信息:输入值不能大于给定值!
*/
function checkintegermaxvalue(str,val)
{
//如果为空,则通过校验
if(str == "")
return true;
if(typeof(val) != "string")
val = val + "";
if(checkisinteger(str) == true)
{
if(parseint(str,10)<=parseint(val,10))
return true;
else
return false;
}
else
return false;
}//~~~
/**
*校验整型是否为非负数
*str:要校验的串。
*
*返回值:
*如果为空,定义校验通过,返回true
*如果非负数, 返回true
*如果是负数, 返回false 参考提示信息:输入值不能是负数!
*/
function isnotnegativeinteger(str)
{
//如果为空,则通过校验
if(str == "")
return true;
if(checkisinteger(str) == true)
{
if(parseint(str,10) < 0)
return false;
else
return true;
}
else
return false;
}//~~~
/*--------------------------------- integer --------------------------------------*/
/********************************** double ****************************************/
/**
*校验字符串是否为浮点型
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串为浮点型,校验通过, 返回true
*如果校验不通过, 返回false 参考提示信息:输入域不是合法的浮点数!
*/
function checkisdouble(str)
{
//如果为空,则通过校验
if(str == "")
return true;
//如果是整数,则校验整数的有效性
if(str.indexof(".") == -1)
{
if(checkisinteger(str) == true)
return true;
else
return false;
}
else
{
if(/^(\-?)(\d+)(.{1})(\d+)$/g.test(str))
return true;
else
return false;
}
}//~~~
/**
*校验浮点型最小值
*str:要校验的串。 val:比较的值
*
*返回值:
*如果为空,定义校验通过, 返回true
*如果满足条件,大于等于给定值,校验通过,返回true
*如果小于给定值, 返回false 参考提示信息:输入域不能小于给定值!
*/
function checkdoubleminvalue(str,val)
{
//如果为空,则通过校验
if(str == "")
return true;
if(typeof(val) != "string")
val = val + "";
if(checkisdouble(str) == true)
{
if(parsefloat(str)>=parsefloat(val))
return true;
else
return false;
}
else
return false;
}//~~~
/**
*校验浮点型最大值
*str:要校验的串。 val:比较的值
*
*返回值:
*如果为空,定义校验通过, 返回true
*如果满足条件,小于等于给定值,校验通过,返回true
*如果大于给定值, 返回false 参考提示信息:输入值不能大于给定值!
*/
function checkdoublemaxvalue(str,val)
{
//如果为空,则通过校验
if(str == "")
return true;
if(typeof(val) != "string")
val = val + "";
if(checkisdouble(str) == true)
{
if(parsefloat(str)<=parsefloat(val))
return true;
else
return false;
}
else
return false;
}//~~~
/**
*校验浮点型是否为非负数
*str:要校验的串。
*
*返回值:
*如果为空,定义校验通过,返回true
*如果非负数, 返回true
*如果是负数, 返回false 参考提示信息:输入值不能是负数!
*/
function isnotnegativedouble(str)
{
//如果为空,则通过校验
if(str == "")
return true;
if(checkisdouble(str) == true)
{
if(parsefloat(str) < 0)
return false;
else
return true;
}
else
return false;
}//~~~
/*--------------------------------- double ---------------------------------------*/
/********************************** date ******************************************/
/**
*校验字符串是否为日期型
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串为日期型,校验通过, 返回true
*如果日期不合法, 返回false 参考提示信息:输入域的时间不合法!(yyyy-mm-dd)
*/
function checkisvaliddate(str)
{
//如果为空,则通过校验
if(str == "")
return true;
var pattern = /^((\d{4})|(\d{2}))-(\d{1,2})-(\d{1,2})$/g;
if(!pattern.test(str))
return false;
var arrdate = str.split("-");
if(parseint(arrdate[0],10) < 100)
arrdate[0] = 2000 + parseint(arrdate[0],10) + "";
var date = new date(arrdate[0],(parseint(arrdate[1],10) -1)+"",arrdate[2]);
if(date.getyear() == arrdate[0]
&& date.getmonth() == (parseint(arrdate[1],10) -1)+""
&& date.getdate() == arrdate[2])
return true;
else
return false;
}//~~~
/**
*校验两个日期的先后
*返回值:
*如果其中有一个日期为空,校验通过, 返回true
*如果起始日期早于等于终止日期,校验通过, 返回true
*如果起始日期晚于终止日期, 返回false 参考提示信息: 起始日期不能晚于结束日期。
*/
function checkdateearlier(strstart,strend)
{
if(checkisvaliddate(strstart) == false || checkisvaliddate(strend) == false)
return false;
//如果有一个输入为空,则通过检验
if (( strstart == "" ) || ( strend == "" ))
return true;
var arr1 = strstart.split("-");
var arr2 = strend.split("-");
var date1 = new date(arr1[0],parseint(arr1[1].replace(/^0/,""),10) - 1,arr1[2]);
var date2 = new date(arr2[0],parseint(arr2[1].replace(/^0/,""),10) - 1,arr2[2]);
if(arr1[1].length == 1)
arr1[1] = "0" + arr1[1];
if(arr1[2].length == 1)
arr1[2] = "0" + arr1[2];
if(arr2[1].length == 1)
arr2[1] = "0" + arr2[1];
if(arr2[2].length == 1)
arr2[2]="0" + arr2[2];
var d1 = arr1[0] + arr1[1] + arr1[2];
var d2 = arr2[0] + arr2[1] + arr2[2];
if(parseint(d1,10) > parseint(d2,10))
return false;
else
return true;
}//~~~
/*--------------------------------- date -----------------------------------------*/
/********************************** email *****************************************/
/**
*校验字符串是否为email型
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串为email型,校验通过, 返回true
*如果email不合法, 返回false 参考提示信息:email的格式不正確!
*/
function checkemail(str)
{
//如果为空,则通过校验
if(str == "")
return true;
if (str.charat(0) == "." || str.charat(0) == "@" || str.indexof(’@’, 0) == -1
|| str.indexof(’.’, 0) == -1 || str.lastindexof("@") == str.length-1 || str.lastindexof(".") == str.length-1)
return false;
else
return true;
}//~~~
/*--------------------------------- email ----------------------------------------*/
/********************************** chinese ***************************************/
/**
*校验字符串是否为中文
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串为中文,校验通过, 返回true
*如果字串为非中文, 返回false 参考提示信息:必须为中文!
*/
function checkischinese(str)
{
//如果值为空,通过校验
if (str == "")
return true;
var pattern = /^([\u4e00-\u9fa5]|[\ufe30-\uffa0])*$/gi;
if (pattern.test(str))
return true;
else
return false;
}//~~~
/**
* 计算字符串的长度,一个汉字两个字符
*/
string.prototype.reallength = function()
{
return this.replace(/[^\x00-\xff]/g,"**").length;
}
/*--------------------------------- chinese --------------------------------------*/
/********************************** mask ***************************************/
/**
*校验字符串是否符合自定义正则表达式
*str 要校验的字串 pat 自定义的正则表达式
*返回值:
*如果为空,定义校验通过, 返回true
*如果字串符合,校验通过, 返回true
*如果字串不符合, 返回false 参考提示信息:必须满足***模式
*/
function checkmask(str,pat)
{
//如果值为空,通过校验
if (str == "")
return true;
var pattern = new regexp(pat,"gi")
if (pattern.test(str))
return true;
else
return false;
}//~~~
/*--------------------------------- mask --------------------------------------*/
/********************************** file ***************************************/
/**
* added by lxcjie 2004.6.25
* 得到文件的后缀名
* ofile为file控件对象
*/
function getfilepostfix(ofile)
{
if(ofile == null)
return null;
var pattern = /(.*)\.(.*)$/gi;
if(typeof(ofile) == "object")
{
if(ofile.value == null || ofile.value == "")
return null;
var arr = pattern.exec(ofile.value);
return regexp.$2;
}
else if(typeof(ofile) == "string")
{
var arr = pattern.exec(ofile);
return regexp.$2;
}
else
return null;
}//~~~
/*--------------------------------- file --------------------------------------*/

window.close关闭窗口,如何可以不弹出系统提示,直接关闭
private void btnclose_click(object sender, system.eventargs e)
{
response.write("javascript>window.opener=null;window.close() ;");
}
如果是通过子窗体关闭父窗体时怎么做呢, 子窗体(弹出窗体):
response.write("window.opener.top.opener=null;window.opener.top.close()")