[古董]用 PC 喇叭播放 Wave 文件的程序

这里有两个程序,其实差不多,playwave.c 是一个命令行的播放程序, wplayer.c 与 playwave.c 差不多,只是增加了播放前以图形方式把音波内容显示出来。

从硬件原理上来看, PC 喇叭是接在一个数字端口上,只能输出 0 和 1 两种信号, 因为它并没有 D/A 转换的功能,理论上是无法播放象 WAV 这种通过 A/D 转换取得的数字音频数据的。 但是,学过硬件或电子技术的人会知道一种通过脉冲宽度调制(PWM)的 D/A 转换方法,其原理也很简单, 就是通过在一个脉冲周期里,高低电平的不同宽度,使这个脉冲有不同的电压平均值,来表示不同的模拟量, 比如 MCS-98 单片机的 D/A 转换就是通过 PWM 来实现的。我于是在程序中通过软件编程实现用 PWM 的 D/A 转换。 这种方法是通过对 8253/8254 定时芯片的编程来精确控制送到 PC 喇叭的脉冲宽度把 WAV 文件数据播放出来, 主要缺点是 CPU 占用率高(不过在 DOS 下这不是问题),以及音量太小。 这个程序最有价值是部分是对未压缩的 WAV 文件的格式处理,如何通过操作硬件端口来实现对 PC 喇叭的控制, 以及用 PWM 的方法来实现 D/A 转换。

我在程序的注释里看到这个:“(AT--U128)”,原来我当初还参考了 AT/286 的电路图呢, 我记得那图纸有二三十页之多啊,我真是太佩服我自己了^_^。

本程序完成于96年8月

/*
PLAYWAVE.C
本程序用于在没有声卡的机器上用PC喇叭播放.WAV文件.
使用: 采样率=22.05kHz/11.025kHz, 8位, 单声道
不足: 22.05kHz时音量较小, 11.025kHz时有采样噪声
在AT/286机+TC2.0上运行通过
*/

#ifndef __COMPACT__
#error No compact model !
#endif

#include
#include
#include
#include
#include

#define KEY_ESC 27

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;

/* 标准.WAV文件头结构 */
struct header{
BYTE hID[4];
DWORD hSize;
BYTE Type[4];
BYTE fID[4];
DWORD fSize;
WORD FTag;
WORD Chans;
DWORD SPS;
DWORD ABPS;
WORD BA;
WORD BPS;
BYTE dID[4];
DWORD dSize;
} wh;

/* Global Variables */
FILE *fp;
WORD bs;
BYTE *buf;
int Freq = 0; /* 采样频率(1 - 11.025kHz, 0 - 22.05kHz) */

/* 文件名变换 */
void nor_fn(char *s)
{
char drv[MAXDRIVE], dir[MAXDIR], name[MAXFILE], ext[MAXEXT];

strupr(s);
fnsplit(s, drv, dir, name, ext);
fnmerge(s, drv, dir, name, ".WAV");
return;
}

/* 检查.WAV文件 */
int chkwave(char *fn)
{
if ((fp = fopen(fn, "rb")) == NULL)
{
printf("WAV file %s can't be open !\n\r", fn);
return (3);
}
if (fread(&wh, sizeof(struct header), 1, fp) != 1)
{
printf("WAV file %s read error !\n\r", fn);
fclose(fp);
return (3);
}
if (strncmp(wh.hID, "RIFF", 4) || strncmp(wh.Type, "WAVE", 4) || \
strncmp(wh.fID, "fmt ", 4) || strncmp(wh.dID, "data", 4) || \
wh.fSize != 0x10 || wh.FTag != 1)
{
printf("Bad WAV file : %s !\n\r", fn);
fclose(fp);
return (3);
}
return (0);
}

/* 输出.WAV文件参数 */
void putwi(char *fn)
{
printf("WAV file : %s\n\r", fn);
printf("Samples Rate : %6.3f kHz", (float)wh.SPS / 1000.0);
printf("\t%2u bits\t\t", wh.BPS);
if (wh.Chans == 1) printf("MONO");
else printf("STEREO");
printf("\n\rLength : %6.3f Seconds\n\r", (float)wh.dSize / (float)wh.ABPS);
return;
}

/* 把.WAV数据转换为22.05kHz/8bits/MONO 格式 */
int convert(void)
{
register WORD i;
WORD j, k;
int *p;

if ((buf = (BYTE *)malloc(wh.dSize)) == NULL) return (4);
if (fread(buf, sizeof(BYTE), wh.dSize, fp) != wh.dSize)
{
puts("Read error !");
return (3);
}
k = (wh.Chans & 2) + (wh.BPS >> 3) - 1; /* 0, 1, 2, 3 */
switch (k) {
case 0 : /* 8 /m */
bs = wh.dSize;
for (i = 0; i < bs; i++)
buf[i] = 54 - (BYTE)(((WORD)buf[i] * 54 + 0x80) >> 8);
/* 54 = 1190kHz / 22.05kHz, + 0x80 为使移位除法的商四舍五入 */
break;
case 1 : /* 16/m */
bs = wh.dSize >> 1;
p = (int *)buf;
for (i = 0; i < bs; i++)
buf[i] = 54 - (BYTE)((((WORD)(p[i] + 32768) >> 8) * 54 + 0x80) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
case 2 : /* 8 /s */
bs = wh.dSize >> 1;
for (i = 0; i < bs; i++)
buf[i] = 54 - (BYTE)((((WORD)buf[i << 1] + buf[(i << 1) + 1]) \
* 27 + 0x80) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
case 3 : /* 16/s */
bs = wh.dSize >> 2;
p = (int *)buf;
for (i = 0; i < bs; i++)
buf[i] = 54 - (BYTE)((((WORD)(p[i << 1] + 32768) >> 8) \
+ ((WORD)(p[(i << 1) + 1] + 32768) >> 8) * 27 + 0x80) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
default: return (3);
}
if (wh.SPS == 44100)
{
bs >>= 1;
for (i = 0; i < bs; i++)
buf[i] = buf[i << 1];
if ((buf = realloc(buf, bs)) == NULL) return (4);
}
return (0);
}

/* 播放.WAV文件 */
void playing(BYTE *buf, WORD bs, int sps)
{
register WORD i, k;
BYTE d;

/*
XT : 8253-5
AT : 8254
0x43 : 8254方式控制口 sc1, sc0, rl1, rl0, m2, m1, m0, BCD
sc1,0 | rl1,0 | m2,1,0
00 T/C0 | 00 锁定 | 000 方式0(计数中断) | 100 方式4(软件启动脉冲)
01 T/C1 | 01 只低字节 | 001 方式1(单脉冲) | 101 方式5(硬件启动脉冲)
10 T/C2 | 10 只高字节 | x10 方式2(单脉宽频率) | 注 : 8253中无回读功能,
11 回读 | 11 先低后高 | x11 方式3(方波频率) | 且方式2,3中x为0(?)
0x94 : T/C2, 只低字节, 单脉宽频率, 二进制
0x90 : T/C2, 只低字节, 计数中断, 二进制
*/
outportb(0x43, 0x90);
/*
XT : 8255
0x63 : 8255的方式字, 0x99(default) - PA-I, PB-O, PC-I, 0x9b - PB-I
AT : ALS175
0x61:8255的PB口(XT),ALS175(AT--U127) /,/,/,/,IOCK,DIP,SpkData,T/C2Gate
0x03 : enable speaker, open T/C2 Gate
open speaker
*/
outportb(0x61, (inportb(0x61) | 3));
if (sps == 1 && (! Freq)) bs <<= 1;
if (sps == 2 && Freq) bs >>= 1;
if (Freq) d = 148;
else d = 202;
disable();
for (i = 0; i < bs; i++)
{
k = *buf++;
if (sps == 1 && (! Freq) && (i & 1)) buf--;
if (sps == 2 && Freq) buf++;
if (Freq) k <<= 1;
if (k)
{
outportb(0x42, k);
/*
<---- 256 - d ----->
<-- k ---X--- x --->
----+ +---------+
+--------+ +---
^ ^ ^
out 42h, k cnt = 0 cnt = k + d = ; x = 256 - (k + d);
d = 256 - fCLK / SPS; fCLK = 1.19MHz;
11k : d = 148, 22k : d = 202
*/
k += d;
/*
XT : 8255
AT : ALS244
0x61:ALS244(AT--U128)口,XT应为0x62(8255的PortC) 各bit为 PCK,IOCK,T/C2Out,SpkOut,/,/,/,/
0x20 : test T/C2Out
*/
while (! ((BYTE)inportb(0x61) & 0x20));
while ((BYTE)inportb(0x42) > k);
}
}
enable();
/* close speaker */
outportb(0x61, (inportb(0x61) & 0xfc));
return;
}

int main(int argc, char *argv[])
{
char fn[80];
int ec;

puts("Play Wave . Ver 0.96");
puts("Copyright (c) 1996 Raptor, Aug.17-96, Aug.27-96\n\r");
if (argc == 1 || (! strcmp(argv[1], "/?")))
{
puts("Say WAV out with PC speaker : 22.05 kHz/8 bits/MONO\n\r");
puts("Usage : PLAYWAVE wavfile[.WAV] [/1]");
puts("\t/1\tby 11.025 kHz");
return (1);
}
for (ec = 1; ec < argc; ec++)
{
if (argv[ec][0] != '/') strcpy(fn, argv[ec]);
else if (argv[ec][1] == '1') Freq = 1;
else
{
puts("Invalid parameters !");
return (2);
}
}
nor_fn(fn);
if ((ec = chkwave(fn)) != 0) return (ec);
putwi(fn);
if ((ec = convert()) != 0)
{
if (ec == 4) puts("Memory allocate error !");
free(buf);
fclose(fp);
return (ec);
}
fclose(fp);
printf("Press to quit, other key to play. . .");

do playing(buf, bs, wh.SPS / 11025);
while ((BYTE)getch() != KEY_ESC);
free(buf);
puts("\n\rOK");
return (0);
}


/*
WPLAYER.C
*/

#include
#include
#include
#include
#include

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;

/* Standard .WAV struct */
struct header{
BYTE hID[4];
DWORD hSize;
BYTE Type[4];
BYTE fID[4];
DWORD fSize;
WORD FTag;
WORD Chans;
DWORD SPS;
DWORD ABPS;
WORD BA;
WORD BPS;
BYTE dID[4];
DWORD dSize;
} wh;

FILE *fp;
WORD bs;
BYTE *buf;

/* file name normalization */
void nor_fn(char *s, char *fn)
{
char *p;

strcpy(s, fn);
strupr(s);
if ((p = strrchr(s, '\\')) == NULL) p = s;
else p++;
if (strchr(p, '.') == NULL) strcat(s, ".WAV");
return;
}

int chkwave(char *fn)
{
if ((fp = fopen(fn, "rb")) == NULL)
{
printf("WAV file %s can't be open !\n\r", fn);
return (3);
}
if (fread(&wh, sizeof(struct header), 1, fp) != 1)
{
printf("WAV file %s read error !\n\r", fn);
fclose(fp);
return (3);
}
if (strncmp(wh.hID, "RIFF", 4) || strncmp(wh.Type, "WAVE", 4) || \
strncmp(wh.fID, "fmt ", 4) || strncmp(wh.dID, "data", 4) || \
wh.fSize != 0x10 || wh.FTag != 1)
{
printf("Bad WAV file : %s !\n\r", fn);
fclose(fp);
return (3);
}
return (0);
}

void putwi(char *fn)
{
printf("WAV file : %s\tFormat : ", fn);
if (wh.Chans == 1) printf("MONO");
else printf("STEREO");
printf("\n\rSamples Rate : %6.3f kHz", (float)wh.SPS / 1000.0);
printf("\t%2u bits\n\r", wh.BPS);
printf("Length : %6.3f Seconds\n\r", (float)wh.dSize / (float)wh.ABPS);
return;
}

int convert(void)
{
register WORD i;
WORD j, k;
WORD *p;

if ((buf = (BYTE *)malloc(wh.dSize)) == NULL) return (4);
if (fread(buf, sizeof(BYTE), wh.dSize, fp) != wh.dSize)
{
puts("Read error !");
return (3);
}
k = (wh.Chans & 2) + (wh.BPS >> 3) - 1; /* 0, 1, 2, 3 */
switch (k) {
case 0 : /* 8 /m */
bs = wh.dSize;
for (i = 0; i < bs; i++)
buf[i] = (BYTE)(((WORD)buf[i] * 54) >> 8);
break;
case 1 : /* 16/m */
bs = wh.dSize >> 1;
p = (WORD *)buf;
for (i = 0; i < bs; i++)
buf[i] = (BYTE)((((p[i] >> 8) + 0x80) * 54) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
case 2 : /* 8 /s */
bs = wh.dSize >> 1;
for (i = 0; i < bs; i++)
buf[i] = (BYTE)((((WORD)buf[i << 1] + buf[(i << 1) + 1]) * 27) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
case 3 : /* 16/s */
bs = wh.dSize >> 2;
p = (WORD *)buf;
for (i = 0; i < bs; i++)
buf[i] = (BYTE)((((p[i << 1] >> 9) + (p[(i << 1) + 1] >> 9) + \
0x80) * 54) >> 8);
if ((buf = realloc(buf, bs)) == NULL) return (4);
break;
default: return (3);
}
if (wh.SPS == 44100)
{
bs >>= 1;
for (i = 0; i < bs; i++)
buf[i] = buf[i << 1];
if ((buf = realloc(buf, bs)) == NULL) return (4);
}
return (0);
}

int putwave(void)
{
int step, y;
register int i;

line(10, 10, 10, 334);
line(0, 172, 719, 172);
moveto(10, 172);
step = (int)(bs / 700);
for (i = 1; i < 700; i++)
{
y = 334 - buf[i * step] * 6;
lineto(i + 10, y);
}
outtextxy(10, 336, "Press to quit, other key to play. . .");
return;
}

void playing(BYTE *buf, WORD bs, int sps)
{
register WORD i, k;

outportb(0x43, 0x90);
outportb(0x61, inportb(0x61) | 3);
disable();
if (sps == 1) bs <<= 1;
for (i = 0; i < bs; i++)
{
k = *buf;
if (sps != 1) buf++;
else if (i & 1) buf++;
if (k)
{
outportb(0x42, k);
k += 202;
while (! ((BYTE)inportb(0x61) & 0x20));
while ((BYTE)inportb(0x42) > k);
}
}
enable();
outportb(0x61, inportb(0x61) & 0xfc);
return;
}

int main(int argc, char *argv[])
{
char fn[80];
int gd = DETECT, gm, ec;

puts("Wave Player. Ver 1.20 .");
puts("Copyright (c) 1996 by Raptor . Aug-17-96 Aug-26-1996\n\r");
// 原为 Dark software corp,Dark 是 Comanche(太可怕) 想到的一个用来称呼我们的名称
#ifdef DEBUG
argc = 2;
#endif
if (argc == 1 || argv[1][0] == '/')
{
puts("Say WAV out with PC speaker : 22.05 kHz/8 bits/MONO\n\r");
puts("Usage : PLAYWAVE wavfile[.WAV]");
return (1);
}
nor_fn(fn, argv[1]);
#ifdef DEBUG
strcpy(fn, "DING.WAV");
#endif
if ((ec = chkwave(fn)) != 0) return (ec);
putwi(fn);
if ((ec = convert()) != 0)
{
if (ec == 4) puts("Memory allocate error !");
free(buf);
fclose(fp);
return (ec);
}
fclose(fp);
initgraph(&gd, &gm, "c:\\bc\\bgi");
if ((ec = graphresult()) != grOk)
{
printf("Graphics error ! %s", grapherrormsg(ec));
return (6);
}
putwave();

/* Play */
while ((BYTE)getch() != 27) playing(buf, bs, wh.SPS / 11025);
closegraph();
free(buf);
puts("\n\rOK");
return (0);
}

/*
参考文献:《Windows音波文件及应用》 上海 臧峥嵘
原载:《中国计算机用户》 94.10 "开发与应用(APPLICATIONS)"
*/