欢迎来到学术参考网

DOS下DSP播音的编程

发布时间:2015-05-30 09:14

  在dos下编程,将声音转化为数据记录下来,或将数据转化为声音,通过声卡上配置的喇叭回放出来,是一项很有实用价值和开发魅力的技术。时下流行的声卡,如sound blaster pro及其兼容卡,都配有数字声音处理器dsp芯片(digital sound processor),专门用于对声音进行数字记录及回放,是声音数字处理的基础硬件。而wav文件、voc文件等,则都是这些数据记载的具体形式。creative公司为了方便用户,提供了一组ct-voice驱动程序,专门针对voc文件,作为开发利用dsp功能的软接口,使用比较方便。但是,也造成了某些限制。对于开发者而言,直接对dsp硬件编程,实现其功能,也许是更有吸引力的。

  声音,无论是从扬声器输出的,还是从话筒输入的,都是模拟量。

  而数据,无论是内存里操作的,还是磁盘上存储的都是数字量。因此,微机处理声音,大多离不开adc与dac两种转换。由于声音数据的数据量比较大,在声音的数字处理中,除直接由cpu进行传输外,批量数据常采用dma方式传输,以节省较多的cpu时间。

  总括起来,adc与dac两种转换方式,直接传输和dma传输这两种传输方式,再加上不同的压缩方式,如喇叭控制、静寂等等,所有这些的不同组合,就构成了dsp的各种功能。根据dsp的硬件原理,其各种功能都规定了一定的操作步骤。

  一、dsp编程要点

  在dsp编程中,主要注意命令与端口两个层次的操作。

  命令。dsp的功能一般以一个操作码(称作命令号)的写操作为中心,按规定的步骤,配合若干必要的辅助操作,构成一串操作的组合,称为dsp命令。如8位直接播放功能命令号为10h,8位直接录音功能命令号为20h,喇叭的通断功能命令号分别为d1h与d3h等等。

  2.端口操作。dsp命令主要靠端口操作来实现。端口操作包括dsp初始化、写dsp命令(即发dsp命令)、读dsp状态参数、dsp中断等。所涉及的端口地址及相应的用途如表1。

  表1 dsp端口及用途

  端口地址由基址2x0h加6、0ah、0ch、0eh等形成,其中,x可取值1、2、3、4、5、6等,具体情况随硬件设置而定,多数卡在出厂被默认设置为2,即基址为220h。通过跳线,可改变此值,避免与其它设备口地址冲突。

  二、编程实例

  dsp的功能是比较丰富的,限于篇幅,本文只简要介绍其中的8位直接播放功能,由此举一反三,其它功能的用法不难得知。各功能的规定操作可参考文献1和2。

  1.命令操作步骤。8位直接播放功能的操作步骤如下:

  ·写命令号10h;

  ·写数据字节(即播放声音的8位数据);

  ·按采样率所需时间周期延时。

  以此三步操作为循环体,进行n次循环,即完成播放。其中,n为声音数据字节数。

  2.2xch端口写操作。在dsp编程中,无论是发送命令,还是发送数据,都是通过写端口2xch来完成的。在写端口2xch之前,应先读此端口,直到所得值的bit7为0,这才表明此端口处于可写状态,才能进行写操作。此过程的c语言形式如下:

  while (inportb(0x22c)&0x80);

  outportb(0x22c,byte);

  这里假定端口基址为220h。句中byte可以是命令号,也可以是数据。

  3.定时器。为使播放按一定的采样率进行,需对数据发送进行定时控制。这一般是借用主机定时中断int8,将其调用频率提高到与采样率相当的程度,利用其监视、控制数据发送的时间,来满足播音频率的要求。关于定时中断的编程技术已有过许多介绍,限于篇幅,不再赘述,读读文后的程序清单,即一目了然。应该说明的是,对于cpu较慢的机型如386,由于计时代码本身的执行时间可能已经超过采样率对应的时间周期,定时控制就达不到预期的效果。这种情况下,用一个空循环来定时,调整循环次数,即可满足频率要求。此法的缺点是定时精度差,参数因cpu速度而异。所幸的是,目前多数配置多媒体的pc机,其cpu都在486以上。

  4.内存利用。人耳可辨声音的最高频率可达20khz以上,因此dsp的采样率至少也要达到与此相当的水平,而为了容纳立体声双声道信息,采样率还要再翻一倍。常见的wav声音的采样率有44100、22050、11025等。在这么高的采样率下,声音的数据量自然很大,如44k采样率下,20秒的录音数据长达800多k。为在dos常规内存内处理这种规模的数据,实例程序采取了分块处理的方式,将数据分成以当前剩余自由内存大小为单位的块,将其逐次读入,逐次处理。同时,由于c语言的read()函数每次读操作的字节数最多不过64k-1,因此,每一个分块又需分

  若干次读入。实例表明,经此法处理的播放程序不受wav文件长度的限制,笔者在windows下录制的长达5m多的wav文件(11k采样率,约8分钟)也照播不误。

  5.声音文件。本文提供的程序实例其声音数据取自wav文件,其实,对于voc文件,本播放技术也一样适用,只不过数据的读取格式有所不同而已。关于wav文件的格式,可参考文献3,voc文件的格式参考献1和2。

  实例程序用borland c++ 3.1编译,在配置opti 386主板、海洋48

  6主板及多种与soundblaster pro兼容声卡的兼容机上运行通过。

  三、源程序清单

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include"timer.h"

  #define n1 20

  #define n2 100

  struct wavhead

  {

  char riff[4];

  long size0;

  char wavefmt[8];

  long size1;

  int fmttag;

  int channel;

  long samplespersec;

  long bytespersec;

  int blockalign;

  int bitspersample;

  char flg[4];

  }whead;

  unsigned port=0x210;

  char found=0;

  unsigned cnt1,cnt2;

  void portreset();

  void outwave(un

  signed char huge *,long);

  void writeportc(unsigned char);

  void errexit(char *);

  void main()

  {

  int fp;

  unsigned n,r,nn,i,j;

  char name[32];

  long fermem,rr,datasize;

  unsigned char huge *data,huge *p;

  if(argc<2)errexit("miss file name\n");

  strcpy(name,argv[1]);strcat(name,".wav");

  fp=-open(name,0-rdonly);if(fp=-1)errexit("error open fil

  e\n");

  -read(fp,&whead,sizeof(wavhead));

  if(lign=1 && strncmp(,"data",4)==0)

  {

  -read(fp,&datasize,4);//单声道wav数据

  }

  else if(lign=2 && strncmp(,"fact"

  ,4)==0)

  {

  lseek(fp,12l,1);

  -read(fp,&datasize,4);//双声道wav数据

  }

  else errexit("error file struct\n");

  farmem=farcoreleft();

  portreset();//初始化dsp端口

  counter=0;//开始计时

  settimer(newtimer,44100);//调整时间中断频率

  writeportc(0xd1);//接通喇叭

  if(farmem≥datasize)//数据量不超过内存容量

  {

  p=data=(unsigned char huge *)farmalloc(datasize);

  n=datasize/32768;r=datasize%32768;

  for(i=0;i

  -read(fp,p,r);

  outwave(data,datasize);

  }

  else//数据量超过内存容量

  {

  nn=datasize/farmem;//分块操作的块数

  rr=datasize%farmem;//最后一块的大小

  n=farmem/32768;//每块read次数

  r=farmem%32768;//read余零尾数

  data=(unsigned char huge *)farmalloc(farmem);

  for(i=0;i

  {

  p=data;

  for(j=0;j

  -read(fp,p,r);

  //读入内存

  outwave(data,farmem);//发送声音数据

  }

  p=data;

  n=rr/32768;r=rr%32768;//最后块的操作

  for(i=0;i

  -read(fp,p,r);

  //读入

  outwave(data,rr);//发送

  }

  writeportc(0xd3);//断开喇叭

  restoretimer();//恢复时间中断

  farfree(data);

  -close(fp);

  }

  void portreset()//初始化dsp端口

  {

  cnt1=n1;

  while(port≤0x260)&&!found)

  {//测端口基址

  outportb(port+6,1);

  outportb(port+6,0);

  cnt2=n2;

  while(cnt2>2 && inportb(port+0xe)<128)--cnt2;

  if(cnt2=0||inportb(port+0xa)!=oxaa)

  {

  --cnt1;

  if(cnt1==0)

  {

  cnt1=n1;

  port=port+0x10;

  }

  }

  else found=1;//找到基址

  }

  if(!found)errexit("reset failed\n");//找不到基址

  }

  void outwave(unsigned char huge *p,long len)

  {//发送声音数据

  long i;

  int smpl;

  smpl=lign;

  //采样周期系数

  for(i=0;i

  {

  writeportc(0x10);//发送命令

  writeportc(p[i]);//发送数据

  while(counter

  }

  }

  void writeportc(unsigned char v)

  {

  while(inportb(port+0xc)&0x80);//等待写有效状态

  outportb(port+0xc,v);//写端口(发送)

  }

  void errexit(char *msg)

  {

  -ax=3;

  asm int 10h

  printf(msg);

  exit(0);

  }

  //timer.h

  #includ

  #define oldtimerint 0x60

  unsigned long counter;

  unsigned counterint8,fpi8;

  void settimer(void interrupt(*rout)(…),unsigned freq)

  {//设置新频率的定时中断

  int icnt;

  fpi8=(freq+9)/18;//新旧频率的倍数

  asm cli

  icnt=1193180/freq;

  outportb(0x43,0x36);

  outportb(0x40,icnt & 255);

  outportb(0x40,icnt》8);

  setvect(oldtimerint,getvect(

  8));//保存旧定时中断

  setvect(8,rout);//置新的定时中断

  sam sti;

  }

  void restoretimer()

  {

  asm cli

  outportb(0x43,0x36);

  outportb(0x40,0);

  outportb(0x40,0);

  setvect(8,getvect(oldtimerint));//恢复原定时中断

  asm sti

  }

  void interrupt newtimer(…)

  {//新定时中断

  regpack r;

  counter++;//给应用程序提供新频率的计数

  if(--counterint8=0)

  {

  intr(oldtimerint,&r);//按原频率走动时钟

  counterint8=fpi8;//用新旧频率的倍数分频

  }

  else outportb(0x20,0x20);//退出中断

  }

  参考文献

  1 阎小兵等.多媒体开发工具.北京:电子工业出版社,1994.

  2 josha munnik等著,敬万钧等译.声霸--原理与应用.北京:电子工业出版社,1995.

  3 石宁等.在dos下使用windows *.wav文件.计算机世界月刊,1995(3)44-46.

上一篇:DNS解析“风暴”现象分析以及预防

下一篇:DOS真彩色模式下真彩色图像显示技术解析