2.DMA介绍
2.1 DMA控制器编程
DMA(Direct Memory Access,直接内存读取)控制器掌管着I/O设备和内存之间的数据传输,整个过程不需要CPU参与。一个INTEL 8237 DMAC集 成电路被用来控制它,而一个IBM兼容机有两个DMA控制器:一个掌管8位另外一个掌管16位。同外部页面寄存器配对的DMA控制器,可以传输大 于64KB的数据块。
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器 ,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。
一个完整的DMA传输过程必须经过下面的4个步骤。
1.DMA请求CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
2.DMA响应DMA控制器对DMA请求判别优选级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此 时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
3.DMA传输DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传 输。
4.DMA结束 当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方 面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后, 带着本次操作结果及状态继续执行原来的程序。
由此可见,DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条 直接传送数据的通路,使CPU的效率大为提高。
下面是有关I/O端口和必要的关于声卡寄存器的设置:
*DMA地址和计数寄存器的I/O端口地址
控制器 I/O地址 功能
位
从 00 通道0地址
01 通道0计数
02 通道1地址
03 通道1计数
04 通道2地址
05 通道2计数
06 通道3地址
07 通道3计数
位
从 C0 通道4地址
C2 通道4计数
C4 通道5地址
C6 通道5计数
C8 通道6地址
CA 通道6计数
CC 通道7地址
CE 通道7计数
*控制寄存器的I/O端口地址
地址 操作 功能
DMAC1 DMAC2
0A D4 写 写单一掩码寄存器
0B D6 写 写模式寄存器
0C D8 写 清除翻转字节指示器
*页寄存器的I/O端口地址
地址 功能
81 8位DMA通道2页面
82 8位DMA通道3页面
83 8位DMA通道1页面
87 8位DMA通道0页面
89 8位DMA通道6页面
8A 8位DMA通道7页面
8B 8位DMA通道5页面
*模式寄存器的各位含义
位/值 功能
Bits 7:6状态选择 00 查询模式
01 单一模式
10 块模式
11 级联模式
Bit 5 1 地址减少
0 地址增加
Bit 4自动初始化设置位 1 自动初始化DMA
0 单一周期DMA
Bits 3:2位传输 00 验证
01 写(到内存)
10 读(从内存)
11 非法
忽略 如果bits 7:6 = 11
Bits 1:0 00 通道0 (4)
01 通道1 (5)
10 通道2 (6)
11 通道3
(7) |
涉及到的,将在程序代码中详细讨论。
3.声卡介绍
16位声霸卡(Sound Blaster 16,本文简写为SB16)可以处理FM(Frequency Modulation:频率调幅)和数字声音信号。其中数字信号的处理范围 是:从8位5000HZ单声道,到16位44000HZ立体声(译者注:另外一种说法是到48000HZ)。这份常见问题文档,是关于SB16 DSP CT1341芯片数字 音频信号的录制和回放的。理所当然的,有关更早声霸卡的编程知识必不可少。
DSP
DSP(digital signal processor)是一种独特的微处理器,是以数字信号来处理大量信息的器件。其工作原理是接收模拟信号,转换为0或1的数字信号,再对数字信号进行修改、删除、强化,并在其他系统芯片中把数字数据解译回模拟数据或实际环境格式。它不仅具有可编程性, 而且其实时运行速度可达每秒数以千万条复杂指令程序,远远超过通用微处理器,是数字化电子世界中日益重要的电脑芯片。它的强大数据处 理能力和高运行速度,是最值得称道的两大特 {MOD}。
DSP芯片,也称数字信号处理器,是一种特别适合于进行数字信号处理运算的微处理器,其主要应用是实时快速地实现各种数字信号处理 算法。根据数字信号处理的要求,DSP芯片一般具有如下主要特点:
(1)在一个指令周期内可完成一次乘法和一次加法;
(2)程序和数据空间分开,可以同时访问指令和数据;
(3)片内具有快速RAM,通常可通过独立的数据总线在两块中同时访问;
(4)具有低开销或无开销循环及跳转的硬件支持;
(5)快速的中断处理和硬件I/O支持;
(6)具有在单周期内操作的多个硬件地址产生器;
(7)可以并行执行多个操作;
(8)支持流水线操作,使取指、译码和执行等操作可以重叠执行。
当然,与通用微处理器相比,DSP芯片的其他通用功能相对较弱些。
它为当时的高档16位声卡实现180°环绕立体声再现立下了汗马功劳。
SB16的I/O端口
SB16的DSP芯片的可编程I/O服务端口地址,其基址是由主板跳线决定的(译者注:现在一般通过BIOS或者应用程序实现)。在SB16芯片中,有16个I/O端口被用作FM合成音乐,音响混合,DSP编程和CD-ROM访问。而下面列出的五个端口被用做DSP编程:
2x6h - DSP复位
2xAh - DSP读
2xCh - DSP写(命令/数据),DSP写缓冲区状态字(第7位)
2xEh - DSP读缓冲区状态字(第7位), DSP中断应答
2xFh - DSP 16位中断应答
端口中的X为基址,可以取1-6,通常情况下取2,即基址是220h。该基址可以由用户在相关配置文件中指定,也可以在程序中自己检测。
DSP复位
在进行DSP编程之前你必须将DSP复位。复位DSP需要按照以下步骤进行:
1,在复位端口(2X6)写入1
2,等待3毫秒
3,在复位端口(2X6)写入0
4,检测读缓冲区端口(2XE)状态,直到第7位为1。
5,检测读数据端口(2XA)状态,直到接受到AA。
DSP自身初始化通常需要大约100毫秒。经过这段时间以后如果返回的值仍然不是AA,或者没有任何数据返回,说明SB16卡未被安装,或者I/O地址不正确。
写DSP
向SB16写一个字节,需要按照以下步骤进行:
1,读写缓冲区状态端口2XC直到第7位被清除
2,将数值写入写端口2XC
读DSP
由SB16读一个字节,需要按照以下步骤进行:
1,读读缓冲区状态端口2XE直到第7位被设置
2,从读端口2XA读出一个字节
本文程序是通过读取环境变量来确定声卡参数的。
4.现实应用
4.1 WAV文件格式
WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。
RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个
字节便是“RIFF”。
WAVE文件是由若干个Chunk组成的。按照在文件中的出现位置包括:RIFF WAVE
Chunk, Format Chunk, Fact Chunk(可选), Data Chunk。具体见下:
| RIFF WAVE Chunk
| ID = ‘RIFF’
| RiffType = ‘WAVE’
| Format Chunk
| ID = ‘fmt ‘
| Fact Chunk(optional)
| ID = ‘fact’
| Data Chunk
| ID = ‘data’
其中除了Fact Chunk外,其他三个Chunk是必须的。每个Chunk有各自的ID,位
于Chunk最开始位置,作为标示,而且均为4个字节。并且紧跟在ID后面的是Chunk大
小(去除ID和Size所占的字节数后剩下的其他字节数目),4个字节表示,低字节
表示数值低位,高字节表示数值高位。下面具体介绍各个Chunk内容。
PS:
所有数值表示均为低字节表示低位,高字节表示高位。
具体介绍
RIFF WAVE Chunk
所占字节数| 具体内容
ID 4 Bytes RIFF’
Size 4 Bytes
Type 4 Bytes WAVE’
以’FIFF’作为标示,然后紧跟着为size字段,该size是整个wav文件大小减去ID
和Size所占用的字节数,即FileLen - 8 = Size。然后是Type字段,为’WAVE’,表
示是wav文件。
Format Chunk
字节数 具体内容
ID 4 Bytes ‘fmt ‘
Size 4 Bytes 数值为16或18,18则最后又附加信息
FormatTag 2 Bytes 编码方式,一般为0x0001
Channels 2 Bytes 声道数目,1--单声道;2--双声道
SamplesPerSec 4 Bytes 采样频率
AvgBytesPerSec 4 Bytes 每秒所需字节数
BlockAlign 2 Bytes 数据块对齐单位(每个采样需要的字节数)
BitsPerSample 2 Bytes 每个采样需要的bit数
2 Bytes 附加信息(可选,通过Size来判断有无)
以’fmt ‘作为标示。一般情况下Size为16,此时最后附加信息没有;如果为18
则最后多了2个字节的附加信息。主要由一些软件制成的wav格式中含有该2个字节的
附加信息。
补充头文件样例说明:
首先是一串“52 49 46 46”这个是Ascii字符“RIFF”,这部分是固定格式,表明这是一个WAVE文件头。
然后是“E4 3C 00 00”,这个是我这个WAV文件的数据大小,记住这个大小是包括头文件的一部分的,包括除了前面8个字节的所有字节,也 就等于文件总字节数减去8。这是一个DWORD,我这个文件对应是15588。
然后是“57 41 56 45 66 6D 74 20”,也是Ascii字符“WAVEfmt”,这部分是固定格式。
然后是PCMWAVEFORMAT部分,可以对照一下上面的struct定义,首先就是一个WAVEFORMAT的struct。
随后是“10 00 00 00”,这是一个DWORD,对应数字16,这个对应定义中的Sizeof(PCMWAVEFORMAT),后面我们可以看到这个段内容正好是16个字节。
随后的字节是“01 00”,这是一个WORD,对应定义为编码格式“WAVE_FORMAT_PCM”,我们一般用的是这个。
随后的是“01 00”,这是一个WORD,对应数字1,表示声道数为1,这是个单声道Wav。
随后的是“22 56 00 00”,这是一个DWORD,对应数字22050,代表的是采样频率22050。
随后的是“44 AC 00 00”,这是一个DWORD,对应数字44100,代表的是每秒的数据量。
然后是“02 00”,这是一个WORD,对应数字是2,表示块对齐的内容,含义不太清楚。
然后是“10 00”,这是一个WORD,对应WAVE文件的采样大小,数值为16,采样大小为16Bits。
然后是一串“64 61 74 61”,这个是Ascii字符“data”,标示头结束,开始数据区域。
而后是数据区的开头,有一个DWORD,我这里的字符是“C0 3C 00 00”,对应的十进制数为15552,看一下前面正好可以看到,文件大小是15596,其中到“data”标志出现为止的头是40个字节,再减去这个标志的4个字节正好是15552,再往后面就是真正的Wave文件的数据体了, 头文件的解析就到这里。
Fact Chunk
所占字节数 具体内容
ID 4 Bytes ‘fact’
Size 4 Bytes 数值为4
data 4 Bytes
Fact Chunk是可选字段,一般当wav文件由某些软件转化而成,则包含该Chunk。
Data Chunk
所占字节数 具体内容
ID 4 Bytes ‘data’
Size |4 Bytes
data
Data Chunk是真正保存wav数据的地方,以’data’作为该Chunk的标示。然后是
数据的大小。紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,
wav数据的bit位置可以分成以下几种形式:
单声道 取样1 取样2 取样3 取样4
8bit量化 声道0 声道0 声道0 声道0
双声道 取样1 取样2
8bit量化 声道0(左) 声道1(右) 声道0(左) 声道1(右)
单声道 取样1 取样2
16bit量化 声道0(低位字节) 声道0(高位字节) 声道0(低位字节) 声道0(高位字节)
双声道 取样1
16bit量化 声道0(左) (低位字节) 声道0(左) (高位字节) 声道1(右) (低位字节) 声道1(右) (高位字节)
#include
#include
#include
#include
#include
#include
/* 以下是PC机的DMA控制器端口的常量定义 */
#define DMA8_CMD_PORT 0x08 /* 8位DMA写命令寄存器端口 */
#define DMA8_STATU_PORT 0x08 /* 8位DMA独状态寄存器端口 */
#define DMA8_REQUEST_PORT 0x09 /* 8位DMA写请求寄存器端口 */
#define DMA8_MASK_PORT 0x0A /* 8位DMA屏蔽寄存器端口(只写)*/
#define DMA8_MODE_PORT 0x0B /* 8位DMA写模式寄存器端口 */
#define DMA8_CLRPTR_PORT 0x0C /* 8位DMA清先后状态寄存器端口 */
#define DMA8_RESET_PORT 0x0D /* 8位DMA写复位命令端口 *
/* 以下是PC机DMA的7个通道的地址寄存器、计数寄存器和页面寄存器的端口常量定义 */
/* 其中通道0-3用于8位的DMA,通道4-7用于16位DMA */
/* PC机中,规定通道2用于进行软盘的DMA传输,其余通道可供用户使用 */
#define DMA0_ADDR_PORT 0x00 /* 通道0的地址寄存器 */
#define DMA0_COUNT_PORT 0x01 /* 通道0的计数寄存器 */
#define DMA0_PAGE_PORT 0x87 /* 通道0的页面寄存器 */
#define DMA1_ADDR_PORT 0x02 /* 通道1的地址寄存器 */
#define DMA1_COUNT_PORT 0x03 /* 通道1的计数寄存器 */
#define DMA1_PAGE_PORT 0x83 /* 通道1的页面寄存器 */
#define DMA3_ADDR_PORT 0x06 /* 通道3的地址寄存器 */
#define DMA3_COUNT_PORT 0x07 /* 通道3的计数寄存器 */
#define DMA3_PAGE_PORT 0x82 /* 通道3的页面寄存器 */
#define DMA5_ADDR_PORT 0xC4 /* 通道5的地址寄存器 */
#define DMA5_COUNT_PORT 0xC6 /* 通道5的计数寄存器 */
#define DMA5_PAGE_PORT 0x8B /* 通道5的页面寄存器 */
#define DMA6_ADDR_PORT 0xC8 /* 通道6的地址寄存器 */
#define DMA6_COUNT_PORT 0xCA /* 通道6的计数寄存器 */
#define DMA6_PAGE_PORT 0x89 /* 通道6的页面寄存器 */
#define DMA7_ADDR_PORT 0xCC /* 通道7的地址寄存器 */
#define DMA7_COUNT_PORT 0xCE /* 通道7的计数寄存器 */
#define DMA7_PAGE_PORT 0x8A /* 通道7的页面寄存器 */
/* DSP定义 */
#define DSP_RESET_DELAY 10
#define DSP_READY 0xAA
#define DSP_GET_VERSION 0xE1
#define DSP_SET_BLK_SIZE 0x48
#define DSP_START_DMA8 0x1C
#define DSP_PAUSE_DMA8 0xD0
#define DSP_SET_SAM_RATE 0x40
#define PIC_PORT_21H 0x21
#define PIC_PORT_20H 0x20
#define PIC_EOI 0x20
#define DMA_BUFFER_SIZE (8 * 1024)
#define TRUE 1
#define FALSE 0
/*播放参数*/
typedef struct
{
unsigned short wavetype; /* WAVE的类别 */
unsigned short channel; /* 通道数 */
unsigned short samplerate; /* 采样频率 */
unsigned short samplebits; /* 采样位数 */
long int datalen; /* 数据长度 */
long int leftdatalen; /* 剩余数据长度 */
int loops; /* 播放次数 */
FILE *fp;
} WAVE, *PWAVE;
/*定义DSP结构*/
typedef struct
{
char dspenvstr[128];
unsigned short dspversion;
unsigned short dspbaseioport;
unsigned short resetport;
unsigned short writedataport;
unsigned short writestatusport;
unsigned short readdataport;
unsigned short readstatusport;
unsigned short mixerbaseioport;
unsigned short mpu401baseioport;
unsigned char dspirqnum;
unsigned char dspdma8;
unsigned char dspdma16;
} DSP, *PDSP;
/* WAVE文件结构定义 */
typedef struct
{
char RIFF[4]; /* RIFF */
unsigned long int filelen; /* 文件长度 */
char WAVEfmt[8]; /* WAVEfmt */
unsigned long int reserved; /* 保留 */
unsigned short wavetype; /* WAVE的类别 */
unsigned short channel; /* 通道数目 */
unsigned short sampling; /* 采样频率 */
unsigned long int transpeed; /* 数据传输速率 */
unsigned short blkalign; /* 调整数据块 */
unsigned short sampbits; /* 采样位数 */
char data[4]; /* data */
unsigned long int datalen; /* 语音数据长度 */
unsigned char pdata; / 数据区 */
} WAVEFILE, *PWAVEFILE;
/*函数声明*/
int initsound(void);
void closesound(void);
int loadwave(PWAVE pwave, char *file);
void destroywave(PWAVE pwave);
void playwave(PWAVE pwave);
void stopwave(PWAVE pwave);
void pausewave(PWAVE pwave);
int initpic(int irqnum);
void closepic(int irqnum);
void picintdone(void);
int initdsp(PDSP pdsp);
void closedsp(PDSP pdsp);
unsigned char readdsp(PDSP pdsp);
void writedsp(PDSP pdsp, unsigned char byte);
void resetdsp(PDSP pdsp);
void getdspversion(PDSP pdsp);
void setdspblocksize(PDSP pdsp, unsigned short blksize);
void dspintdone(PDSP pdsp);
void dspstartdma8(PDSP pdsp);
void dsppausedma8(PDSP pdsp);
void dspsetsamplerate(PDSP pdsp, unsigned short rate);
int initdma8(int channel, unsigned char far *addr, int size);
void closedma8(int channel);
void interrupt (*old_dsp_int_handle)(void) = NULL;
void interrupt new_dsp_int_handle(void);
void install_dsp_int_handle(void);
void remove_dsp_int_handle(void);
int parse_sb_envstr(char *envstr, char id);
DSP cursbdsp = {0};
PWAVE pcurwave = NULL;
int dma_buf_flag = NULL;
unsigned char far *sound_dma_buf = NULL;
int initdma8(int channel, unsigned char far *addr, int size)
{ /* 页面地址= 段地址+偏移 */
unsigned long phyaddr = FP_SEG(addr) * 0x10L + FP_OFF(addr);
unsigned char page = (unsigned char)(phyaddr >> 16);/*计算页*/
unsigned short offset = (unsigned short)(phyaddr >> 0);/*计算页偏移*/
if (channel > 3 || channel == 2) return FALSE;
outportb(DMA8_MASK_PORT, channel | (1 << 2)); /* 屏蔽该通道 */
outportb(DMA8_MODE_PORT, channel | (1 << 4) | (2 << 2)); /* 请求方式+自动初始化+读传送 */
outportb(DMA8_CLRPTR_PORT, 0);
switch (channel)
{
case 0:
outportb(DMA0_COUNT_PORT, (size - 1)& 0x00FF);
outportb(DMA0_COUNT_PORT, (size - 1) >> 8);
outportb(DMA0_ADDR_PORT, (offset)& 0x00FF);
outportb(DMA0_ADDR_PORT, (offset) >> 8);
outportb(DMA0_PAGE_PORT, page);
break;
case 1:
outportb(DMA1_COUNT_PORT, (size - 1)& 0x00FF);
outportb(DMA1_COUNT_PORT, (size - 1) >> 8);
outportb(DMA1_ADDR_PORT, (offset)& 0x00FF);
outportb(DMA1_ADDR_PORT, (offset) >> 8);
outportb(DMA1_PAGE_PORT, page);
break;
case 3:
outportb(DMA3_COUNT_PORT, (size - 1)& 0x00FF);
outportb(DMA3_COUNT_PORT, (size - 1) >> 8);
outportb(DMA3_ADDR_PORT, (offset)& 0x00FF);
outportb(DMA3_ADDR_PORT, (offset) >> 8);
outportb(DMA3_PAGE_PORT, page);
break;
}
outp(DMA8_MASK_PORT, channel);
return TRUE;
}
void closedma8(int channel)
{
if (channel > 3 || channel == 2) return;
outportb(DMA8_MASK_PORT, channel | (1 << 2)); /* 屏蔽该通道 */
}
int parse_sb_envstr(char *envstr, char id)
{
char buf[32] = “0x”;
int i;
int j;
for (i = 0; envstr[i] != id && envstr[i] != ‘