摘 要 该文介绍了Windows声波文件(*.WAV)的格式,然后分析了在DOS下不使用声音适配卡播放声波文件的要害问题,并给出了程序清单。
要害词 DOS应用软件开发 多媒体声波文件在DOS应用软件开发过程中,我们非常希望能在不附加任何硬件设备的条件下实现一些简单的多媒体功能。
过去许多文章中都讨论过Windows图像文件(*.BMP,*.PCX)的格式及其用于美化DOS程序界面的方法。在MS WIMDOWS3.1以后,Windows又提供了标准的声波文件(*.WAV),因此我们可以利用已有的声波文件镶嵌在自己的软件中,在DOS下实现语音或其它音响的播放,提高我们的软件质量。
一、声波文件格式分析
*.WAV文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文Resource Interchange File Format的缩写,每个WAV文件的头四个字节便是“RIFF”。
常见的声波文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。这里,采样率是指:声波信号[模→数]转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声波模拟信号的积分值,在编程播放过程中我们认为它是扬声器在此周期单位时间段的音量。
*.WAV文件由文件头和数据体两大部分组成。其中文件头又分为RIFF/WAV文件标识段和声波数据格式说明段两部分。
WAV文件各部分内容及格式见附表。
对于单声道声波文件,采样数据为八位的短整数(short int 00H-FFH);而对于双声道立体声声波文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。
二、WAV文件编程
在没有声音适配卡的条件下,利用PC机内部扬声器发声需解决几个要害问题。 首先是如何产生按指定采样率要求的标准时间间隔段,以此为基础控制扬声器发声。
由于此时间段要求精确且非常短暂,因此实现起来有一定的难度。解决该问题的思路是修改8253定时器芯片的计数器0(地址:040H)的初始值,改变系统时钟中断频率使其和采样率相一致,建立用户的时钟中断例程,最终产生标准的时间间隔段。但是在我们修改原有系统时钟中断(Int 08H)以后,最终必须恢复原有18.2Hz的系统时钟中断。
其次是如何快速地打开和关闭扬声器。解决这个问题的方法是直接向8255芯片端口(地址:061H)写操作。由于PC机机内扬声器发声只有开/闭两种状态,并不能控制音量大小。
因此还须考虑如何通过开闭扬声器来摸拟实现音量大小的控制。实现方法是:在每个时间单位内通过改变扬声器打开延时的长短代表音量的大小。例如:对于8Bit单声道声波文件,采样数据的最大值是0FFH,那么在每个标准时间单位内扬声器打开时间应为Delay=(采样值/256)*标准时间段长度。在此思想下可以将该方法简化,设扬声器延时只有0、1(时间单位)两种情况,即在每个时间单位内,假如采样值大于128则发声,假如采样值小于128就不发声。显然这样做是以抛弃大量声波信息为代价的,采用的信息量只占原有用信息的1/128,所以这种方法产生的音质较差。
三、程序实例
下面是一个能播放11.025KHz/8Bit/单声道声波文件的演示程序。关于使用*.WAV文件的其它细节,可通过阅读本程序得到。它采用了第二种延时方式,假如读者有爱好提高音质可将其改成使用第一种方法,只需将newint08h中的声音开/关判定(与128比较)部分改成循环等待即可。
循环次数通过i=int(vol[counter]/256)*MAXTIMES得到。
式中MAXTIMES为延长一个标准时间单位的循环次数。
程度运行环境:486兼容机,MS DOS6.0,TC2.0编译系统。
/*/*/*
*.WAV文件播放程序 DEMO.C,石宁 1994.12
*/*/*/
#include "dos.h"
#include "stdio.h"
#include"string.h"
#define MAXSIZE 50000
struct wave-file_head /*声波*/
{ /*文件头*/
char riff_id[4];/*结构体*/
long int size0;
char wave-fmt[8];
lont int sizel;
int fmttag;
int channel;
long int samplespersec;
long int bytepersec;
int blockalign;
int bitpersamples;
} filehead;
long int datasize, counter=0;
unsigned char vol[MAXSIZE];
unsigned clkdiv;
int oldclk=0,running=1;
void soundon();
void soundoff();
void interrupt(*oldint8h)();
void interrupt newint8h()
{ /*用户中断例程*/
if(running)
{
unsigned int i;
disable();/*屏蔽中断*/
running=0;
if(vol[counter]>=128)
{
i=inportb(0x61);/*开扬*/
i=i|0x03;
outportb(0x61,i);/*声器*/
}
else
{
i=inportb(0x61);/*关扬*/
i=i&0x00fc;
outportb(0x61,i);/*声器*/
}
counter+=1;
enable();/*打开中断*/
if(counter>=datasize) counter=0;
outportb(0x20,0x20);
running=1;
}
}
void soundon()
{
clkdiv=1193180/filehead.samplespersec;
/*计算8253计数器0初始值*/
oldint8h=getvect(0x08);/*保存旧的08H中断向量*/
setvect(0x08,newint8h);/*置新的08H中断例程*/
outportb(0x43,0xB6);/*初始化*/
outportb(0x42,1);/*8253计数器3/
outportb(0x42,0);/*初始值*/
outportb(0x43,0x36); /*修改8253*/
outport(0x40,clkdiv&0x00ff);/*计数器0*/
outport(0x40,(clkdiv>>8)&0x00ff);/*初始值*/
}
void soundoff()
{
int i;
setvect(0x08,oldint8h);/*恢复旧的08H中断向量*/
outportb(0x43,0x36);/*恢复正常*/
outport(0x40,0)/*的时钟中*/
outport(0x40,0);/*断频率*/
i=inportb(0x61);/*关扬*/
i=i&0x00fc;
outportb(0x61,i);/*声器*/
}
void main(int argc, char *argv[])
{
long j;
int key;
char *s;
FILE *fp;/
if(argc==1)
{
printf("%s\n","And The WAV Filename In Command Line!");
exit(0);
}
else
{
if ((fp=fopen(argv[l],"rb"))==NULL)
{
printf("Cannot open the Data file %s\n",argv[1];
exit(0);
}
}
if(fread(&filehead,sizeof(struct wave-file-head),1,fp)==NULL)
{
printf("File Read Error!\n");
exit(0);
}
fseek(fp,4,SEEK-CUR);
fread(&datasize,4,1,fp);
for(j=0;j<datasize;j++)vol[j]=getc(fp);
close(fp);
printf("%s\n","Now Sound On, Press ESC to Stop!");
soundon();
for(;;)
{
key=getch();
if(key==27)
{
soundoff();
printf("%s\n","Sound off!");
break;
}
}
}
(作者地址:中国矿业大学研究生部53#,100083)
-------- -----------------------------------------------------------------
根据上面的《在》文中所附的源程序重放WAV文件,发现其音质确实较差;又用文中所提及的第二种延时方法,效果并无改善(大概是循环次数int(vol[counter]/256)≡0)。究其原因,可能是重放时将原WAV文件中127/128的有用信息抛弃了。
实际上,我们是可以把原来抛弃的127/128的有用信息捡回来的。我们知道,WAV文件中所存的是采样时的电压幅值信号,只要将电压信号重现在PC的小喇叭上,就可以逼真地重放WAV文件了。
喇叭上发出的声音,其实就是驱动喇叭的电压变化。而对PC上的数字开关喇叭,只有“0”和“1”两种状态:“1”时喇叭纸盆向外运动,“0”则纸盆向内运动而回复正常位置。但是向喇叭发送“1”信号时,喇叭向外运动有一个延迟时间,当喇叭还没到达最外位置时,立即再发送“0”信号,纸盆来不及运动到最外位置而转向向内运动。根据这一原理,我们控制喇叭向外运动的时间,就可以间接地控制纸盆运动的幅度,从而根据WAV文件的采样值来控制喇叭的声音大小。
对于PC时钟,显然来不及控制纸盆运动的幅度(即使对8253定时器重新编程,也无法使纸盆从正常位置到最外位置有若干个int08h发生),因此,对于WAV文件的延时,只能采用loop指令。实际上,Windows也正是用这种方法来延时的(以标准模式和增强模式分别启动时,Windows要重新建立延迟时间而发出测试的声音)。
程序中读WAV文件头信息和WAV数据与《在》文相同,发声子程序重新改写了。若发出的声音在频率上有失真,可以修改wave函数中cx的初值(386增强模式、386DX33微机上Windows使用0x13)。
程序运行环境:386兼容机,DOS6.2,Turbo C++3.0编译系统。
#include<dos.h>
#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<stdlib.h> お
#define MAXSIZE 50000 お
unsigned int a[]=
{
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,
0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff81,0xff83,
0xff88,0xff8d,0xff92,0xff97,0xff9c,0xffa1,0xffa6,0xffab,
0xffb0,0xffb5,0xffba,0xffbf,0xffc4,0xffc9,0xffce,0xffd3,
0xffd8,0xffdd,0xffe2,0xffe7,0xffec,0xfff1,0xfff6,0xfffb,
0x0000,0x0005,0x000a,0x000f,0x0014,0x0019,0x001e,0x0023,
0x0028,0x002d,0x0032,0x0037,0x003c,0x0041,0x0046,0x004b,
0x0050,0x0055,0x005a,0x005f,0x0064,0x0069,0x006e,0x0073,
0x0078,0x007d,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,
0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f,0x007f
=======================================================
}; お
struct wave-file-head
{
char riff-id[4];
long size0;
char wave-fmt[8];
long size1;
int fmttag;
int channel;
long samplespersec;
long bytepersec;
int blockalign;
int bitpersamples;
} filehead; お
long datasize;
unsigned char vol[MAXSIZE];
void wave(void); お
void wave()
{
unsigned int i; お
for(i=0;i<datasize;i++)
{
_BL=vol[i];
asm {
xor bh,bh
shl bx,1
mov bx,a[bx];
xor di,di;
xor dx,dx;
in al,0x61;
and al,0xfc;
out 0x61,al;
mov cx,17;
}
loc33:
asm{
add di,bx;
jge loc34
and al,0xfc;
sub di,0xff81;
out 0x61,al;
loop loc33;
jmp loc35;
}
loc34:
asm{
or al,3;
sub di,0x7f;
out 0x61,al;
loop loc33;
}
loc35;
}
} お
void main(int argc,char *argv[])
{
long j;
int key;
char *s;
FILE *fp; お
if (argc==1)
{
printf("Required parameter missing\n");
exit(0);
}
if((fp=fopen(argv[1],"rb"))==NULL)
{
printf("File not open\n");
exit(0);
}
if(fread(&filehead,sizeof(struct wave-file-head),1,fp) ==NULL)
{
prinft("File read error\n");
exit(0);
}
fseek(fp,4,SEEK-CUR);
fread(&datasize,4,1,fp); お
for(j=0;j<datasize;j++)
vol[j]=getc(fp);
fclose(fp);
printf("%s\n","Press[ESC] to stop\nSounding...");
while(bioskey(1)==0)wave();
nosound();
}
(作者地址:上海航天八部天梦公司桂林路412号,200233)
Word教程网 | Excel教程网 | Dreamweaver教程网 | Fireworks教程网 | PPT教程网 | FLASH教程网 | PS教程网 |
HTML教程网 | DIV CSS教程网 | FLASH AS教程网 | ACCESS教程网 | SQL SERVER教程网 | C语言教程网 | JAVASCRIPT教程网 |
ASP教程网 | ASP.NET教程网 | CorelDraw教程网 |