中国DOS联盟论坛

中国DOS联盟

-- 联合DOS 推动DOS 发展DOS --

联盟域名:www.cn-dos.net  论坛域名:www.cn-dos.net/forum
DOS,代表着自由开放与发展,我们努力起来,学习FreeDOS和Linux的自由开放与GNU精神,共同创造和发展美好的自由与GNU GPL世界吧!

游客:  注册 | 登录 | 命令行 | 会员 | 搜索 | 上传 | 帮助 »
作者:
标题: [原创]在DOS下针对AC'97编程 上一主题 | 下一主题
whowin
初级用户





积分 134
发帖 37
注册 2006-9-28
状态 离线
『楼 主』:  [原创]在DOS下针对AC'97编程

文章是从我的网志中贴过来的,其中的图片可能过不来,看完整内容,请访问我的网志:
点击进入《DOS编程技术》


    AC'97大多数应该听说过,可能有些人把它当成一种声卡,或者是声卡上的芯片等等,其实它仅仅是一种规范,符合AC'97规范的声卡,通常叫做AC'97声卡,但其实上面使用的芯片可能完全不一样。现在很多桥片中甚至已经集成了AC'97的规范进去,就不需要专门的声卡了。本文针对AMD的较新的一种桥片CS5536上集成的AC'97进行编程,进而说明如何对符合AC'97规范的声卡进行编程。以下为书写方便,把AC'97写成AC97。

1、AC97规范介绍

     AC97最重要的三个规范就是:1.使用独立的CODEC芯片,将数字电路和模拟电路分离;2.固定48K的采样率,其他频率的信号必须经过SRC转换处理;3.标准化的CODEC引脚定义。基本上说,符合这个规范的声卡就是AC97声卡。制定AC97规范的主要目的有两个:1.实现数模电路分离,保证音频质量;2.使声卡电路标准化、提高其兼容性能。

    就AC97规范而言是十分复杂的,基本上不可能在这里表达完整,所以我们并不打算在这里完整地解释该规范,我们仅就我们准备举的例子中所需要的内容来介绍。我们的程序范例准备完成一个简单的WAV文件的放音,而且,这个WAV文件还不能是很长的一个WAV文件。

    按照AC97的规范,AC97声卡大致有三个部分:Audio Codec Controller(ACC)、AC Link、Codec,如下图:



    ACC负责与系统相连,然后透过AC Link与Codec通讯,在本例中,ACC通过AMD的GLIU与PCI总线相连,其实不用管什么GLIU,只要知道,本例中,ACC与PCI总线相接就可以了。ACC负责在系统存储器和Codec之间传送数据(使用DMA),本例的ACC与AC Link之间有8个通道,相应地为了支持这8个通道,ACC有8个DMA引擎。

    AC Link是一个5针的数字串行接口,AC97规范定义了这个接口的协议(AC Link Serial Interface Protocol),我们必须简单地介绍这个协议才有可能完成后面的编程。

    先要介绍一下PCM和时分多路复用的概念。PCM(Pulse Code Modulation),中文叫做脉冲编码调制,是一种编码方式,简单的说就是把声音的模拟信号变成狮子信号的一种编码方式,我们使用的CD就是采用的PCM编码。AC97规范传送的声音编码都是PCM编码。所谓“多路复用”就是为了充分利用线路资源,在一条线路上传输几路而不是一路信息,“时分”指的是一种多路复用的方法,大致方法是,把一个传输通道进行时间分割以传送若干话路的信息,比如我们把1秒钟分成10份,则0--1/10秒传第1路数据,1/10--2/10秒传送第2路数据,......,9/10--10/10秒传送第10路数据,大致就是这么个原理,我们把单位时间内分成的每个等分叫做1个时隙(Time Slot),简称Slot,比如上面把1秒分成10份,可以说有10个时隙(Slot),Slot 0--Slot 9。

    回到正题,前面说过,AC Link有5条线,分别是:SYNC、BIT_CLK、RESET#、SDATA_OUT和SDATA_IN,可以看出单方向传送的数据线,只有一条,所以一定要使用多路复用技术,AC Link协议将48KHz(20.8us)分成了13个SLOT,除第Slot 0传送16 bit数据外,其余12个Slot均传送20 bit数据,在此把我们可能用到的信息格式介绍一下。

Slot 0:TAG
Bit 15       1表示该帧数据有效
Bit 14       1表示后面Slot 1中数据有效
Bit 13       1表示后面Slot 2中数据有效
Bit [12:3]   1表示后面Slot 3--12中数据有效
Bit 2        备用
Bit [1:0]    Codec ID field
Slot 1:Command Address
bit 19       读或写标志,0--写标志,1--读标志
bit [18:12]  64个16位寄存器索引号
bit [11:0]   备用
Slot 2:Command Data
bit [19:4]   控制寄存器的写入数据(操作为读时,要用0填满)
bit [3:0]    备用(填0)
Slot 3:PCM放音左声道
高16位有效,未用到的低位填0
Slot 4:PCM放音右声道
高16位有效,未用到的低位填0
Slot 5:MODEM Line 1 DAC
高16位有效,未用到的低位填0
Slot 6:PCM放音中央通道
高16位有效,未用到的低位填0
Slot 7:PCM放音环绕左声道
高16位有效,未用到的低位填0
Slot 8:PCM放音环绕右声道
高16位有效,未用到的低位填0
Slot 9:PCM放音低音效果声道
高16位有效,未用到的低位填0
Slot 10:未用

Slot 11:Modem Headset DAC

Slot 12:GPIO 控制

     另外,规范还详细定义了CODEC的27个寄存器,AC97声卡,如果作为PCI设备,则这些寄存器可以被映射成I/O地址,也可以被映射成存储器地址,从PCI的配置空间中可以得到基地址,然后加上AC97规范中定义的寄存器的偏移便可寻址到所有的CODEC寄存器,由于内容实在是比较多,让我一个一个汉字敲上去实在是太累,所以有关协议的介绍就暂告一段落,建议大家在完成下面的例子之前还是看看AC97规范的有关章节,可以从下面地址下载。

    http://blog.hengch.com/specification/ac97_r23.pdf

2、有关AMD CS5536有关AC97部分的介绍

     CS5536是一颗功能十分强大的芯片,我们仅能就其中我们要用到的功能做一点简要的介绍。

    如果希望详细了解CS5536这颗芯片,可以在这里下载datasheet,篇幅很长,如果不使用这颗芯片,完全没有必要全部阅读。

    http://blog.hengch.com/specification/33238f_cs5536_ds.pdf

    前面说到,CS5536中的Codec控制器有8个通道,控制器负责从内存中向CODEC传输数据,所以在启动DMA之前,我们必须告诉控制器数据存放的内存地址,数据的长度,以及如何终止传输等等,还要告诉控制器启动哪一个或哪几个通道,CS5536的AC97控制器(ACC)为完成这些控制,有一系列的控制寄存器,偏移地址从00h--7Fh,数据均为32位。

    首先说如何告诉ACC数据的存储地址以及与其相关的事情,CS5536上的ACC使用一个叫做PRD(Physical Region Descriptor)表作为传输控制信息的表述,表的结构如下:



     每一个PRD表占8个字节(2个DWORD),前1个DWORD表示数据存储的基地址,后一个DWORD的bit 0--bit 15表示数据的长度,只16bit能表示的最大值为65535,由于音频采样值为16bit,作为单声道音频流,数据必须与2个字节对齐,所以一个内存区域内存储的最大采样值(数据)为65534字节;对于双声道立体声,一个采样点位两个声道,需要4个字节,所以一个内存区域的最大采样数据为65532字节。

    PRD表一般都不止一个,它们在内存中依次放置,就是说,如果第1个PRD表放在0x1000这个内存地址上,则第2个PRD表应该放在0x1008这个位置,第3个PDR放在0x1010,......,以此类推,直到出现最后一个PRD表;最后一个PRD表的标志是EOT位置1或者JMP位置1,不能EOT和JMP同时置1,EOT(End of Transfer)置1表明这是最后一个PRD,这个PRD后停止数据传输;JMP(JUMP)置1表明跳跃到其他PRD,这是PRD的第1个DWORD不是指存放数据的内存地址,而是指JMP要跳跃到的下一个PRD表的内存地址,这种情况下表明Size的16个bit无效;有效地利用JMP位,可以方便地制造出循环播放的效果,我们可以在所有PRD的最后放置一个PRD,该PRD置JMP位,同时把跳跃地址指向第1个PRD,于是循环播放的效果就出来了。

    EOP(End of Page)是在还有下一个PRD时使用,当该页数据传输完毕后,ACC遇到EOP位,就会产生一个中断,同时转到下一个PRD,如果在下一个EOP出现之前,中断没有处理完毕的话,将出现一个错误,通常情况下会利用这个中断来填写另一个PRD表,以此来完成较长音频的播放,否则遇到很长的音频文件,岂不是要耗尽内存;但我们在本例中为了突出重点,不准备播放很长的音频文件,同时,不采用中断方式。

    好了,我们已经了解了PRD表,很显然,我们只要把第一个PRD表的地址告诉ACC就可以了,这要用到ACC的一个寄存器,偏移位置为:24h,ACC有8个这样的寄存器,偏移分别是:24h、2Ch、34h、3Ch、44h、4Ch、54h、5Ch,分别用于ACC的8个通道(是否还记得ACC有8个通道),在本例中,我们只用通道0,所以我们只用偏移位24h的寄存器,这个寄存器的名字叫:Audio Bus Master 7-0 PRD Table Address Registers(ACC_BM[x]_PRD),该寄存器32位长,其中bit 0和bit 1备用,bit 2--bit 31存放第1个PRD表的地址,为什么bit 0和bit 1备用呢?因为规定PRD的地址必须以4字节对齐,所以实际上bit 0和bit 1没有意义。

    如何和Codec进行通讯呢?这里还有两个ACC的寄存器必须要介绍,一个偏移量为08h的Codec状态寄存器(Codec Status Register)(ACC_CODEC_STATUS),另一个是偏移为0Ch的Codec控制寄存器(Codec Control Register)(ACC_CODEC_CNTL)。

    Codec控制寄存器:主要用于向Codec发出控制命令
      bit 31(RW_CMD):0--写Codec寄存器,1--读Codec寄存器
      bit 30:24(CMD_ADD):读/写Codec寄存器的地址,前面说过Codec寄存器地址为7位
      bit 23:22(COMM_SEL):与那个Codec进行通讯,00--Codec 1,01--Codec 2
      bit 16(CMD_NEW):当填写完命令后,将此位置1,当命令发出后,硬件将此位置0
      bit 15:0(CMD_DATA):只有写入命令时有效,欲写入Codec寄存器的数据。

    Codec状态寄存器:
      bit 31:24(STS_ADD):表明STS_DATA数据是哪个寄存器的
      bit 23(PRM_RDY_STS):1--主Codec准备好,如果此位不为1,软件不能存取相应的Codec
      bit 22(SEC_RDY_STS):1--第2个Codec准备好,如果此位不为1,软件不能存取相应的Codec
      bit 17(STS_NEW):当收到一个合法的Codec状态数据后,硬件会将此位置1,
      bit 15:0(STS_DATA):从Codec收到的Codec的状态数据

    在我们的例子中,只用主Codec和通道0。

    在本例中,Codec芯片使用的是ALC202,有关ALC202的datasheet可以在下面网址下载。

    http://blog.hengch.com/specification/alc202.pdf

3、wav文件格式
    由于我们的例子是打开一个WAV文件并放音,所以我们有必要了解一下WAV文件的文件格式。

    WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个字节便是“RIFF”。WAVE文件由文件头和数据体两大部分组成。其中文件头又分为RIFF/WAV文件标识段和声音数据格式说明段两部分。WAVE文件各部分内容及格式见附表。

常见的声音文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。

对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。

WAVE文件数据块包含以脉冲编码调制(PCM)格式表示的样本。WAVE文件是由样本组织而成的。在单声道WAVE文件中,声道0代表左声道,声道1代表右声道。在多声道WAVE文件中,样本是交替出现的。

WAVE文件格式说明表

偏移地址    字节数     数据类型     内 容

00H           4         char      “RIFF”标志

04H           4         long int  文件长度

08H           4         char      “WAVE”标志

0CH           4         char      “fmt”标志

10H           4                   过渡字节(不定)

14H           2         int       格式类别(10H为PCM形式的声音数据)

16H           2         int       通道数,单声道为1,双声道为2

18H           2         int       采样率(每秒样本数),表示每个通道的播放速度,

1CH           4         long int  波形音频数据传送速率,其值为通道数×每秒数据位数×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。

20H           2         int       数据块的调整数(按字节算的),其值为通道数×每样本的数据位值/8。播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整。

22H           2                   每样本的数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。

24H           4         char      数据标记符"data"

28H           4         long int  语音数据的长度

PCM数据的存放方式:
                样本1           样本2            样本3             样本4

8位单声道     0声道           0声道

8位立体声     0声道(左)       1声道(右)        0声道(左)        1声道(右)

16位单声道    0声道低字节      0声道高字节      0声道低字节      0声道高字节

16位立体声    0声道(左)低字节  0声道(左)高字节  1声道(右)低字节  1声道(右)高字节



WAVE文件的每个样本值包含在一个整数i中,i的长度为容纳指定样本长度所需的最小字节数。首先存储低有效字节,表示样本幅度的位放在i的高有效位上,剩下的位置为0,这样8位和16位的PCM波形样本的数据格式如下所示。

样本大小   数据格式        最大值        最小值

8位PCM     unsigned int    255            0

  16位PCM     int            32767         -32767

4、实例

    下面我们编这样一个程序,程序名:playwav.exe,带两个参数,第一个是WAV文件名,第二个是放音音量,实际使用时使用下面格式:playwav filename volume

    大致程序的流程如下,读入两个参数,搜寻AC97设备,初始化AC97,检查wav文件格式,读入文件并建立PRD表,启动AC97放音;为了简化,我们要求放音文件不能很大,因为我们只打算建立三个PRD表,一次性将WAV文件中的数据全部读入内存,下面是程序清单,为了说明方便,增加了行号,程序使用C++完成,在DJGPP下编译通过。

     由于程序太长,无法放在这里,请希望继续阅读的读者先自行下载源程序后再继续,源程序包中包括主程序playwav.cc;三个包含文件,ac97.h中定义了所有的AC97相关寄存器的偏移地址、AC97相关的常量并定义了一个类MYSOUND;typedef.h中为了程序方便定义了一些数据类型,不必过于关注;dosmem.h中主要定义了一个类DOS_MEM,主要在申请内存空间时使用,并不是本文的主题,大致明白怎么用就好了;源程序包中还有一个wav文件1.wav,用于测试。

    源文件下载:http://blog.hengch.com/source/ac97.rar

    程序的关键在ac97.h这个文件,我们将重点介绍其中的类MYSOUND。

    现在我们假定编译出来的可执行文件是playwav.exe,我们这样来执行这个文件以便测试:
      playwav 1.wav 90
    其中:1.wav是音频文件,90是音量。


    我们从MYSOUND的构造函数开始。构造函数主要执行了两个内部函数:CheckPCIBios()和FindCS5536()

CheckPCIBios:检查BIOS是否支持PCI,这个方法我在另一篇博文《遍历PCI设备》中曾经介绍过,如果BIOS不支持PCI,程序将无法运行。
FindCS5536:查找Codec控制器是否存在,前面介绍过,Codec控制器集成在CS5536中,ACC的VENDOR是0X1022(表示AMD公司),Device ID是0X2093,如果我们在PCI设备中能找到符合条件的设备,表明存在ACC,程序可以继续,查找PCI设备的方法在我的另一篇博文《遍历PCI设备》中曾经介绍过。
回到构造函数,在找到ACC后,程序从配置空间中读出一些内容,其中最主要的是基地址,这个变量在后面的程序中经常用到。如何读取PCI的配置空间亦希望读者参考我以前的博文。

    在主程序(playwav.cc)中调用的MYSOUND中的第一个方法是LoadWavFile,现在我们回到ac97.h中分析LoadWavFile这个方法。

    LoadWavFile要求的入口参数只有一个文件名,其实就是我们执行playwav时的第一个参数1.wav,LoadWavFile方法主要调用了三个内部函数:OpenWavFile()、CheckWavFormat()和CreatePRD()

OpenWavFile():仅仅是以只读、二进制的方式打开wav文件,如果成功返回handle,否则返回NULL
CheckWavFormat():检查Wav文件的格式是否正确,该函数读取wav文件的文件头,并放到fileHead这个结构中,然后根据前面介绍的wav文件的格式检查其中的4个标志,如果符合则认为其是一个格式正确的wav文件。
CreatePRD():这个函数很关键,这个函数将按照规则建立音频数据的缓冲区,同时建立PRD表。首先,我们建立的缓冲区中的音频数据是16bits,2通道的,就是说,每一个采样点要站4个字节,前两个字节是左声道,后两个字节是右声道,低字节在前,高字节在后。
一块缓冲区的长度不能超过65536,这在前面已经说过,为稳妥起见,我们决定一块缓冲区中仅存放65528个字节,也就是16382个采样点,我们首先要确定在这个缓冲区中可以从文件中读取多少个字节,对于8bit单声道数据,由于每个字节是一个采样点,所以只能读取16382个字节;对于16bit双声道数据,4个字节一个采样点,所以可以读取65528个字节;对于16bit单声道和8bit双声道数据,由于2个字节为一个采样点,所以可以读取32764个字节;可以读取的字节数存在变量m中,文件中还没有读的数据长度存在变量k中。
wavBuffer用于临时存储wav文件中的音频数据,wavBuf[x]是实际按格式规范好的音频采样数据,本例中x小于8。
我们首先读取整块的数据到wavBuffer中,然后根据其采样宽度和声道数放到wavBuf[x]中,注意,如果是8bit数据需要先扩展成16bit后再放入wavBuf[x]中。
组织好数据后,开始设置PRD表,如果wav文件已经读完,则设置EOT位,否则设置EOP位。
    至此,数据及PRD表均已准备完毕,可以准备AC97设备开始放音了。
    从主程序中看到,在完成了CreatePRD()的调用后,调用了SetVolume()方法,该方法仅仅把命令行的地2个参数放到了MYSOUND类的变量volume中,在初始化AC97时将以此变量的值设置变量。
    下面主程序调用InitialAC97()来初始化AC97。

InitialAC97():该函数首先获得第一个PRD的地址,放到前面说过的位于偏移24h的ACC的PRD地址寄存器中,紧接着两行根据wav文件中的数据设置Codec的采样速率,最后两行设置了放音音量,我们把PCM_OUT的音量设为最大,然后使用主音量控制来控制音量。
    然后主程序调用了StartPlay()方法开始放音。

StartPlay():首先不断读取主Codec的状态,直到它就绪,一般情况下第一次读取就是就绪状态;然后向位于偏移20h的总线命令寄存器写入命令01h,该命令的含义是总线使能,意即开始根据PRD表向Codec传送数据,当数据传输完毕时,这个寄存器的bit 1:0将被自动置为00。
    至此,我们已经启动了AC97的放音,前面我们说过,为了简洁地说明问题,本例不使用中断方式,而采用查询方式来完成放音过程,这主要是为了规避介绍中断例程的编写方法,我们会注意到,当主程序调用完StartPlay()方法后,便进入一个循环,不断地查询MYSOUND的状态标志status,并不停地调用方法Process(),直到status==0为止。所以我们有必要来看一下status的含义和Process()都干了些什么。

status:0--表示MYSOUND目前并没有放音,1--表示MYSOUND目前正在放音。构造函数里,status第一次出现,此时status=0,表明没有放音;StartPlay()里第二次出现,status=1,表示MYSOUND正在放音;Process()里第三次出现,在放音结束后status=0,说明MYSOUNG结束放音过程。
Process():不停地检查总线的命令寄存器(就是当初启动传输的寄存器),前面说过党传输完成后,这个寄存器的bit 1:0将被自动置为00,该方法以此来判断传输是否完成;同时,该程序不停地检查总线IRQ状态寄存器,我们在前面也介绍过,ACC当在PRD中遇到EOP标志时,会产生中断,如果在下一个EOP来临之前不处理这个中断(读这个状态寄存器),则会产生错误,同时DMA的传输暂停,为了不造成这种现象,Process()不停地读这个状态寄存器。
    至此,程序基本介绍完了,过程有些繁琐,由于篇幅原因,很多问题不得不请大家自己去读一些规范,让我一个字一个字地敲上去太难了,希望这篇文章能给你一些帮助。

更多关于DOS编程的文章看我的网志

点击进入《DOS编程技术》

2008-5-9 11:41
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
netwinxp
高级用户





积分 741
发帖 366
注册 2007-7-25
状态 离线
『第 2 楼』:  

嘿嘿,5530、5530A、5535、5536资料都上传了。http://upload.cn-dos.net/img/374.rar
看看前面几个版本你就会发现其实CS系列本来就是来自于NSC,ATI SB系列南桥的SATA/RAID好像就是来自于SII3112R。AMD大部分的咚咚都是买别人现成的,嘿嘿嘿嘿...

[ Last edited by netwinxp on 2008-5-15 at 05:40 AM ]

2008-5-10 21:30
查看资料  发短消息 网志   编辑帖子  回复  引用回复
lqhlqg123
新手上路





积分 4
发帖 2
注册 2008-5-6
状态 离线
『第 3 楼』:  

楼主高手啊,向你学习,不知道用PCBEEP直接播放WAV音乐是个什么原理啊,要怎么实现呢???

2008-5-11 18:17
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复

请注意:您目前尚未注册或登录,请您注册登录以使用论坛的各项功能,例如发表和回复帖子等。


可打印版本 | 推荐给朋友 | 订阅主题 | 收藏主题



论坛跳转: