DSP播音的編程研究論文

時間:2022-09-17 05:32:00

導(dǎo)語:DSP播音的編程研究論文一文來源于網(wǎng)友上傳,不代表本站觀點,若需要原創(chuàng)文章可咨詢客服老師,歡迎參考。

DSP播音的編程研究論文

摘要該文介紹了dsp編程的基本原則和方法,并給出程序?qū)嵗龓椭斫?讀者可以此為基礎(chǔ)來拓展、生成自己的實用程序。

在DOS下編程,將聲音轉(zhuǎn)化為數(shù)據(jù)記錄下來,或?qū)?shù)據(jù)轉(zhuǎn)化為聲音,通過聲卡上配置的喇叭回放出來,是一項很有實用價值和開發(fā)魅力的技術(shù)。時下流行的聲卡,如SoundBlasterPro及其兼容卡,都配有數(shù)字聲音處理器DSP芯片(DigitalSoundProcessor),專門用于對聲音進行數(shù)字記錄及回放,是聲音數(shù)字處理的基礎(chǔ)硬件。而WAV文件、VOC文件等,則都是這些數(shù)據(jù)記載的具體形式。Creative公司為了方便用戶,提供了一組CT-Voice驅(qū)動程序,專門針對VOC文件,作為開發(fā)利用DSP功能的軟接口,使用比較方便。但是,也造成了某些限制。對于開發(fā)者而言,直接對DSP硬件編程,實現(xiàn)其功能,也許是更有吸引力的。

聲音,無論是從揚聲器輸出的,還是從話筒輸入的,都是模擬量。

而數(shù)據(jù),無論是內(nèi)存里操作的,還是磁盤上存儲的都是數(shù)字量。因此,微機處理聲音,大多離不開ADC與DAC兩種轉(zhuǎn)換。由于聲音數(shù)據(jù)的數(shù)據(jù)量比較大,在聲音的數(shù)字處理中,除直接由CPU進行傳輸外,批量數(shù)據(jù)常采用DMA方式傳輸,以節(jié)省較多的CPU時間。

總括起來,ADC與DAC兩種轉(zhuǎn)換方式,直接傳輸和DMA傳輸這兩種傳輸方式,再加上不同的壓縮方式,如喇叭控制、靜寂等等,所有這些的不同組合,就構(gòu)成了DSP的各種功能。根據(jù)DSP的硬件原理,其各種功能都規(guī)定了一定的操作步驟。

一、DSP編程要點

在DSP編程中,主要注意命令與端口兩個層次的操作。

1.DSP命令。DSP的功能一般以一個操作碼(稱作命令號)的寫操作為中心,按規(guī)定的步驟,配合若干必要的輔助操作,構(gòu)成一串操作的組合,稱為DSP命令。如8位直接播放功能命令號為10h,8位直接錄音功能命令號為20h,喇叭的通斷功能命令號分別為d1h與d3h等等。

2.端口操作。DSP命令主要靠端口操作來實現(xiàn)。端口操作包括DSP初始化、寫DSP命令(即發(fā)DSP命令)、讀DSP狀態(tài)參數(shù)、DSP中斷等。所涉及的端口地址及相應(yīng)的用途如表1。

表1DSP端口及用途

端口地址由基址2x0h加6、0ah、0ch、0eh等形成,其中,x可取值1、2、3、4、5、6等,具體情況隨硬件設(shè)置而定,多數(shù)卡在出廠被默認設(shè)置為2,即基址為220h。通過跳線,可改變此值,避免與其它設(shè)備口地址沖突。

二、編程實例

DSP的功能是比較豐富的,限于篇幅,本文只簡要介紹其中的8位直接播放功能,由此舉一反三,其它功能的用法不難得知。各功能的規(guī)定操作可參考文獻1和2。

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

·寫命令號10h;

·寫數(shù)據(jù)字節(jié)(即播放聲音的8位數(shù)據(jù));

·按采樣率所需時間周期延時。

以此三步操作為循環(huán)體,進行n次循環(huán),即完成播放。其中,n為聲音數(shù)據(jù)字節(jié)數(shù)。

2.2xch端口寫操作。在DSP編程中,無論是發(fā)送命令,還是發(fā)送數(shù)據(jù),都是通過寫端口2xch來完成的。在寫端口2xch之前,應(yīng)先讀此端口,直到所得值的bit7為0,這才表明此端口處于可寫狀態(tài),才能進行寫操作。此過程的c語言形式如下:

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

outportb(0x22c,byte);

這里假定端口基址為220h。句中byte可以是命令號,也可以是數(shù)據(jù)。

3.定時器。為使播放按一定的采樣率進行,需對數(shù)據(jù)發(fā)送進行定時控制。這一般是借用主機定時中斷int8,將其調(diào)用頻率提高到與采樣率相當?shù)某潭?利用其監(jiān)視、控制數(shù)據(jù)發(fā)送的時間,來滿足播音頻率的要求。關(guān)于定時中斷的編程技術(shù)已有過許多介紹,限于篇幅,不再贅述,讀讀文后的程序清單,即一目了然。應(yīng)該說明的是,對于CPU較慢的機型如386,由于計時代碼本身的執(zhí)行時間可能已經(jīng)超過采樣率對應(yīng)的時間周期,定時控制就達不到預(yù)期的效果。這種情況下,用一個空循環(huán)來定時,調(diào)整循環(huán)次數(shù),即可滿足頻率要求。此法的缺點是定時精度差,參數(shù)因CPU速度而異。所幸的是,目前多數(shù)配置多媒體的PC機,其CPU都在486以上。

4.內(nèi)存利用。人耳可辨聲音的最高頻率可達20kHz以上,因此DSP的采樣率至少也要達到與此相當?shù)乃?而為了容納立體聲雙聲道信息,采樣率還要再翻一倍。常見的WAV聲音的采樣率有44100、22050、11025等。在這么高的采樣率下,聲音的數(shù)據(jù)量自然很大,如44k采樣率下,20秒的錄音數(shù)據(jù)長達800多k。為在DOS常規(guī)內(nèi)存內(nèi)處理這種規(guī)模的數(shù)據(jù),實例程序采取了分塊處理的方式,將數(shù)據(jù)分成以當前剩余自由內(nèi)存大小為單位的塊,將其逐次讀入,逐次處理。同時,由于C語言的read()函數(shù)每次讀操作的字節(jié)數(shù)最多不過64k-1,因此,每一個分塊又需分

若干次讀入。實例表明,經(jīng)此法處理的播放程序不受WAV文件長度的限制,筆者在Windows下錄制的長達5M多的WAV文件(11k采樣率,約8分鐘)也照播不誤。

5.聲音文件。本文提供的程序?qū)嵗渎曇魯?shù)據(jù)取自WAV文件,其實,對于VOC文件,本播放技術(shù)也一樣適用,只不過數(shù)據(jù)的讀取格式有所不同而已。關(guān)于WAV文件的格式,可參考文獻3,VOC文件的格式參考獻1和2。

實例程序用BorlandC++3.1編譯,在配置OPTI386主板、海洋48

6主板及多種與SoundBlasterPro兼容聲卡的兼容機上運行通過。

三、源程序清單

#include<io.h>

#include<dos.h>

#include<conio.h>

#include<stdio.h>

#include<fcntl.h>

#include<stdlib.h>

#include<string.h>

#include<alloc.h>

#include"timer.h"

#definen120

#definen2100

structWavHead

{

charriff[4];

longsize0;

charwavefmt[8];

longsize1;

intfmttag;

intchannel;

longsamplespersec;

longbytespersec;

intblockalign;

intbitspersample;

charflg[4];

}whead;

unsignedPort=0x210;

charFound=0;

unsignedcnt1,cnt2;

voidPortReset();

voidoutwave(un

signedcharhuge*,long);

voidWritePortC(unsignedchar);

voiderrexit(char*);

voidmain()

intfp;

unsignedn,r,nn,i,j;

charname[32];

longfermem,rr,datasize;

unsignedcharhuge*data,huge*p;

if(argc<2)errexit("missfilename\n");

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

fp=-open(name,0-RDONLY);if(fp=-1)errexit("Erroropenfil

e\n");

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

if(whead.blockalign=1&&strncmp(whead.flg,"data",4)==0)

{

-read(fp,&datasize,4);//單聲道WAV數(shù)據(jù)

}

elseif(whead.blockalign=2&&strncmp(whead.flg,"fact"

,4)==0)

{

lseek(fp,12l,1);

-read(fp,&datasize,4);//雙聲道WAV數(shù)據(jù)

}

elseerrexit("Errorfilestruct\n");

farmem=farcoreleft();

PortReset();//初始化DSP端口

Counter=0;//開始計時

SetTimer(NewTimer,44100);//調(diào)整時間中斷頻率

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

if(farmem≥datasize)//數(shù)據(jù)量不超過內(nèi)存容量

{

p=data=(unsignedcharhuge*)farmalloc(datasize);

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

for(i=0;i<n;i++,p+=32768)-read(fp,p,32768);

-read(fp,p,r);

outwave(data,datasize);

}

else//數(shù)據(jù)量超過內(nèi)存容量

{

nn=datasize/farmem;//分塊操作的塊數(shù)

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

n=farmem/32768;//每塊read次數(shù)

r=farmem%32768;//read余零尾數(shù)

data=(unsignedcharhuge*)farmalloc(farmem);

for(i=0;i<nn;i++)//逐塊處理

{

p=data;

for(j=0;j<n;j++,p+=32768)-read(fp,p,32768);

-read(fp,p,r);

//讀入內(nèi)存

outwave(data,farmem);//發(fā)送聲音數(shù)據(jù)

}

p=data;

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

for(i=0;i<n;i++,p+=32768)-read(fp,p,32768);

-read(fp,p,r);

//讀入

outwave(data,rr);//發(fā)送

}

WritePortC(0xd3);//斷開喇叭

RestoreTimer();//恢復(fù)時間中斷

farfree(data);

-close(fp);

}

voidPortReset()//初始化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;

}

}

elseFound=1;//找到基址

}

if(!Found)errexit("Resetfailed\n");//找不到基址

}

voidoutwave(unsignedcharhuge*p,longlen)

{//發(fā)送聲音數(shù)據(jù)

longi;

intsmpl;

smpl=44100/whead.samplespersec/whead.blockalign;

//采樣周期系數(shù)

for(i=0;i<len;i++)

{

WritePortC(0x10);//發(fā)送命令

WritePortC(p[i]);//發(fā)送數(shù)據(jù)

while(Counter<smpl);Counter=0;//定時

}

}

voidWritePortC(unsignedcharv)

{

while(inportb(Port+0xc)&0x80);//等待寫有效狀態(tài)

outportb(Port+0xc,v);//寫端口(發(fā)送)

}

voiderrexit(char*msg)

{

-AX=3;

asmint10h

printf(msg);

exit(0);

}

//Timer.h

#includ<dos.h>

#defineOldTimerInt0x60

unsignedlongCounter;

unsignedCounterInt8,fpI8;

voidSetTimer(voidinterrupt(*Rout)(…),unsignedfreq)

{//設(shè)置新頻率的定時中斷

intICnt;

fpI8=(freq+9)/18;//新舊頻率的倍數(shù)

asmcli

ICnt=1193180/freq;

outportb(0x43,0x36);

outportb(0x40,ICnt&255);

outportb(0x40,ICnt》8);

setvect(OldTimerInt,getvect(

8));//保存舊定時中斷

setvect(8,rout);//置新的定時中斷

samsti;

}

voidRestoreTimer()

{

asmcli

outportb(0x43,0x36);

outportb(0x40,0);

outportb(0x40,0);

setvect(8,getvect(OldTimerInt));//恢復(fù)原定時中斷

asmsti

}

voidinterruptNewTimer(…)

{//新定時中斷

REGPACKR;

Counter++;//給應(yīng)用程序提供新頻率的計數(shù)

if(--CounterInt8=0)

{

intr(OldTimerInt,&R);//按原頻率走動時鐘

CounterInt8=fpI8;//用新舊頻率的倍數(shù)分頻

}

elseoutportb(0x20,0x20);//退出中斷

}

參考文獻

1閻小兵等.多媒體開發(fā)工具.北京:電子工業(yè)出版社,1994.

2JoshaMunnik等著,敬萬鈞等譯.聲霸--原理與應(yīng)用.北京:電子工業(yè)出版社,1995.

3石寧等.在DOS下使用Windows*.WAV文件.計算機世界月刊,1995(3)44-46.