标题:[原创]DSP声卡控制 - DOS下播放任意长度WAV文件
只看楼主
jig
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
帖 子:530
专家分:242
注 册:2005-12-27
结帖率:100%
 问题点数:0 回复次数:26 
[原创]DSP声卡控制 - DOS下播放任意长度WAV文件

DSP声卡控制 - DOS下播放任意长度WAV文件
作者:孙靖(Jig) 时间:2006 - 07 - 23
若要转贴或使用本文章介绍的技术,请在你发布的文章或作品中注明出处。

本问所介绍程序单单针对8位数据的WAV文件,所以各位在做实验的时候一定保证WAV文件为采样率为8位。

一:DSP简介
以下内容为我在网络上找的资料,各位也许早看过但在此也做些交代。
1.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。通过跳线,可改变此值,避免与其它设备口地址冲突。


二:简析两种方法
1. 采用0X14,以数据块的形式往DSP芯片送数据,直接设置DSP芯片的采样率,完全把工作交给DSP完成,可由于我们开辟的数据块内存有限,所以播放文件的长度有限制。具体后面详细介绍。
2. 采用0X10,逐个的把数据发往DSP芯片,自己编程实现采样率定时,起对硬件操作较少,起主要作用部分如下:
for (i = 0; i < n; i++)
{
WritePortC(0x10); /* 发送命令 */
WritePortC(p[i]); /* 发送数据 */
while(Counter<smpl);Counter=0;/* 定时, 其中Counter为全局变量,被封装在一个中断中累加记数以实现定时 */
}
我们看,其实这个方法对硬件的操作很少,就是一个0X10命令,再一个发送p[i]数据,可关键在于while(Counter<smpl);Counter=0;这个定时。其实定时所实现的功能就是实现采样率,可问题在于,这个地方是否能很好的完成任务,这个部分的定时的精确度和稳定性直接影响声音的流畅和声音的效果,即使这里我们采用了修改时钟触发频率,然后自己设置一个中断函数以Counter记数来实现精确定时,但我们也不能保证,每次中断的时间间隔是恒定和不受系统资源的影响,但就一般直观认识而言觉得此法可以播放任意长度的WAV文件。

三:以法1播放任意长度WAV文件
原本是想去试试法2,可是要去修改时钟出发频率,设置中断函数,而且还不能保证效果好,我也就一直没去做尝试,总想先用个简单的方法来实现,等来日再去试法二。
我想,各位看到这可能会有这个想法,我们把法1,法2相结合,以指定内存块在相应文件也多次读取出数据送往DSP芯片即可。我何尝不是这样想的,可先前我被一例子码给蒙蔽了。此例子即论坛中 "qingfen" 的一帖子 "[求助]求neosdk里面nsound 的详细资料-->baidu转移" 中我恢复给他贴上一段代码,即这段代码骗了我。(由于篇幅有限,各位朋友可以在论坛中找到 "qingfen" 那篇帖子一看,或下载文后的附件)。大家,看过此段代码,然后依他的函数做如下一段代码:
......
play_sample(wav, 255, 128, 1000, 1);
play_sample(wav, 255, 128, 1000, 1);

getch();
destroy_sample(wav);

remove_sound();
......
如以上代码所示,我连续使用了两个 play_sample(); 那一定要能连续播放两次音乐,我们所想的方法才可能实现,即第一个 play_sample(); 播放前段音乐,然后紧接着第二个 play_sample(); 播放后段音乐,这样才能实现我们前面的想法。可是很遗憾,这样写来的程序,当你播放的时候他也只能播放一遍,所以看其效果就像实现了多线成,也就是说这样我们的音乐就像个后台程序,他播放他的声音,而程序已经运行到后面,大家可以这样更改去看看效果:
......
play_sample(wav, 255, 128, 1000, 1);
while(1)
{
printf("nihao");
}

getch();
destroy_sample(wav);

remove_sound();
......
大家去看看效果,声音播放的同时,也在不断的打印 "nihhao" 字符串,这也说明此法是完全把工作交个DSP芯片了,直到后来我无意知道原委在哪了。
大家可去看看 remove_sound(); 函数,他的内容其实很简单:
void remove_sound(void)
{
/*关闭声卡*/
write_dsp(0xD3);
/*重置DSP*/
reset_dsp();
}
看来,要是在播放一个段文件后就对声卡复位,那就可以实现按顺序播放,我们不防这样一试:
......
play_sample(wav, 255, 128, 1000, 1);
/*重置DSP*/
reset_dsp();
play_sample(wav, 255, 128, 1000, 1);

getch();
destroy_sample(wav);

remove_sound();
......
大家再试,这样就肯定可以顺序的播放两遍音乐了,那只要我们在播放完一段数据后对DSP复位,那就可以接着播放下段数据,好让我们来实现这个想法。


四:程序具体编写
下面我会配以代码详细介绍程序实现过程,其实这个代码就是从我给 "qinfen" 的代码修改简化过来的。
1. 定义WAV文件结构,WAV头信息结构,以及相关参数
#include <stdio.h>
#include <dos.h>

/* wav文件结构 */
typedef struct WaveData
{
unsigned long sample_lenth;
unsigned short rate;
unsigned short channels;
unsigned char time_constant;
char bit_res;
char *name_wav;

char *sample;
} WAV;

/* wav头信息结构 */
typedef struct HeaderType
{
long riff; /*RIFF类资源文件头部*/
unsigned long file_len; /*文件长度*/
char wave[4]; /*"WAVE"标志*/
char fmt [4]; /*"fmt"标志*/
char NI1 [4]; /*过渡字节*/
unsigned short format_type;/*格式类别(10H为PCM形式的声音数据)*/
unsigned short Channels; /*Channels 1 = 单声道; 2 = 立体声*/
long frequency; /*采样频率*/
long trans_speed;/*音频数据传送速率*/
char NI2 [2]; /*过渡字节*/
short sample_bits;/*样本的数据位数(8/16)*/
char data[4]; /*数据标记符"data"*/
unsigned long wav_len; /*语音数据的长度*/
char NI3 [4]; /*过渡字节*/
} HEAD_WAV;

int G_base; /* 记录DSP芯片的基址 */
int G_port; /* 端口的基址 */

此处不必多言,这是必须了解的基础知识,我们眼进行文件类的编程,当然要是事先了解他的文件格式,这个各位朋友可以自行在网上查找资料。

2. DSP初始化函数
int install_DSP()
{
int i, g_address[6] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260};

for (i = 0; i < 6; i++)
{
if (RestDSP(g_address[i]))
{
G_port = g_address[i]; /* 记录端口基地址 */
return 1;
}
}

return 0;
}

/****************************************************************************
检查一个声卡基址是否存在,如果存在则将声卡复位 *
****************************************************************************/
int RestDSP(int Test)
{
/* 重置DSP */
outportb(Test + 0x6, 1);
delay(50); /* 延时是必须的 */
outportb(Test + 0x6, 0);
delay(50);

/* 如果重置成功则检查 */
if ((inportb(Test + 0xE) & 0x80 == 0x80)
&& (inportb(Test + 0xA) == 0xAA))
{
G_base = Test;
return 1;
}
else
{
return 0;
}
}

关于 int RestDSP(int Test) 函数,我想各位只要暂且记下,只要知道这是在对硬件端口进行操作,和其中检测 “重置是否成功” 的方法就行,以后有机会接触具体硬件电子开发的时候会知道其中原理。我的 int install_DSP() 函数中,只列出了6个可能端口地址,大家可以多增加几个,因为我朋友的机器声卡端口就在这6个之外。

3. 加载WAV文件
WAV *Load_wav(char *name_wav)
{
FILE *fp;
HEAD_WAV Wav_file_head;
WAV *Wav_file;


/* 打开声音文件 */
if ((fp = fopen(name_wav, "rb")) == NULL)
{
printf("can't open wav file!");
return NULL;
}

/* 开辟空间 */
if ((Wav_file = (WAV *)malloc(sizeof(WAV))) == NULL)
{
printf("have't mem!");
fclose(fp);
return NULL;
}

/* 读取文件头信息 */
fread(&Wav_file_head, sizeof(HEAD_WAV), 1, fp);

/* 检查RIFF头 0x46464952 其实就是字符串 "RIFF"
这样好处理一些,毕竟比较数字比比较字符串要省事的多 */
if (Wav_file_head.riff != 0x46464952)
{
printf("isn't wav file!");
fclose(fp);
return NULL;
}

/* 获取文件名 */
Wav_file->name_wav = name_wav;

Wav_file->rate = Wav_file_head.frequency; /* 采样频率 */
Wav_file->channels = Wav_file_head.Channels; /* 声道 */
/* 计算真实采样率 */
Wav_file->time_constant = 256 - (1000000L / (Wav_file->rate * Wav_file->channels));


/* 获取声音数据长度 */
Wav_file->sample_lenth = Wav_file_head.file_len - 50;


/* 分配内存,以存放声音数据 */
if ((Wav_file->sample = (char *)malloc(Wav_file->sample_lenth)) == NULL)
{
printf("have't mem!");
fclose(fp);
return NULL;
}


/* 若文件过长 */
if (Wav_file_head.file_len - 50 > 0xC7FF)
{
/* 读取声音数据 */
fread(Wav_file->sample, 0xC7FF, 1, fp);
}
else
{
/* 读取声音数据 */
fread(Wav_file->sample, Wav_file->sample_lenth, 1, fp);
}

fclose(fp);

return Wav_file;
}

次函数,其实也没有什么好才交代,只要仔细看看便明白。只是大家注意最后部分的读取文件长度:
......
/* 若文件过长 */
if (Wav_file_head.file_len - 50 > 0xC7FF)
{
/* 读取声音数据 */
fread(Wav_file->sample, 0xC7FF, 1, fp);
}
else
{
/* 读取声音数据 */
fread(Wav_file->sample, Wav_file->sample_lenth, 1, fp);
}
......
看,我这里设置的数据块大小为 0xC7FF,即 51199bit。若文件长度大于 51199bit,那第一次就应当读满数据快,所以为 fread(Wav_file->sample, 0xC7FF, 1, fp); 而若文件数据长度小于 51199bit,那就以为着只要播放依次即可。





附件下载(内有写好例子和参照程序):

hOZGeppu.rar (1.07 MB) [原创]DSP声卡控制 - DOS下播放任意长度WAV文件




下接:

[此贴子已经被作者于2006-7-23 9:30:33编辑过]

搜索更多相关主题的帖子: DSP WAV 声卡 DOS 网络 
2006-07-23 09:27
jig
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
帖 子:530
专家分:242
注 册:2005-12-27
得分:0 

4. 播放文件
void Play_wav(WAV *Wav_file)
{
FILE *fp;
long LinearAddress;
unsigned short page, offset;
int i;
unsigned int count; /* 记录共分为几块播放 */
unsigned int romd; /* 记录不满一块的数据段长度,以便最后依次播放 */

/* 获取块数,和余数 */
count = (int)(Wav_file->sample_lenth / 51199);
romd = (int)(Wav_file->sample_lenth % 51199);

if (romd != 0)
{
count++;
}


/* 将音频流指针转换成线性地址 */
LinearAddress = FP_SEG(Wav_file->sample);
LinearAddress = (LinearAddress << 4) + FP_OFF(Wav_file->sample);

page = LinearAddress >> 16; /*计算页*/
offset = LinearAddress & 0xFFFF; /*计算页偏移*/


/*注意 :这个操作只能工作于DMA的第一通道*/
outportb (0x0A, 5); /*Mask 锁DMA通道一*/
outportb (0x0C, 0); /*清除DMA内部翻转标志*/
outportb (0x0B, 0x49); /*设置成回(播)放模式*/
/*
模式由下面几项组成:
0x49 = 二进制 01 00 10 01
| | | |
| | | +- DMA通道 01
| | +---- 读操作 (从内存到DSP)
| +------- 单一周期方式
+---------- 块方式
*/

outportb ( 0x83, page); /*将页面写入DMA控制器*/
outportb ( 0x03, 0xC7FF & 0x100);
outportb ( 0x03, 0xC7FF >> 8);

/* 开启声卡 */
WriteDSP(0xD1);

if (count == 1) /* 文件长度 <= 51199bit */
{

WriteDSP(0x40); /* DSP第40h号命令 :设置采样频率 */
WriteDSP(Wav_file->time_constant ); /* Write time constant */

outportb ( 0x02, offset & 0x100); /*将偏移量写入DMA控制器*/
outportb ( 0x02, offset >> 8);

outportb ( 0x0A, 1 ); /*激活DMA通道一*/
WriteDSP( 0x14 ); /*DSP第14h号命令 :单一周期回放*/

WriteDSP( Wav_file->sample_lenth & 0xFF );
WriteDSP( Wav_file->sample_lenth >> 8);
}
else /* 文件长度 > 51199bit */
{
fp = fopen(Wav_file->name_wav, "rb");
fseek(fp, (long)(sizeof(HEAD_WAV) + 0xC7FF), SEEK_SET);

for (i = 0; i < count; i++)
{
WriteDSP(0x40); /* DSP第40h号命令 :设置采样频率 */
WriteDSP(Wav_file->time_constant ); /* Write time constant */

outportb ( 0x02, offset & 0x100); /*将偏移量写入DMA控制器*/
outportb ( 0x02, offset >> 8);

outportb ( 0x0A, 1 ); /*激活DMA通道一*/
WriteDSP( 0x14 ); /*DSP第14h号命令 :单一周期回放*/


if (i != (count - 1))
{
WriteDSP( 0xC7FF & 0xFF );
WriteDSP( 0xC7FF >> 8);

/*重置DSP*/
RestDSP(G_port);

fread(Wav_file->sample, 0xC7FF, 1, fp);
}
else
{
WriteDSP( romd & 0xFF );
WriteDSP( romd >> 8);

/*重置DSP*/
RestDSP(G_port);

fread(Wav_file->sample, romd, 1, fp);
}
}
fclose(fp);
}

/*关闭声卡*/
WriteDSP(0xD3);
/*重置DSP*/
RestDSP(G_port);
}

int WriteDSP(int value)
{
/*等待DSP接收一个字节*/
while ((inportb(G_base + 0xC) & 0x80) == 0x80);
/*发送字节*/
outportb (G_base + 0xC, value);
}

对于 void Play_wav(WAV *Wav_file) 诸多操作,其实就是硬件控制,说句老实话,其中很多端口操作我也不知道他具体做了什么,甚至这些概念我也不清楚,但我还是那句话,暂且不管先记住,以后有机会会知道硬件内部四怎么实现的。至于 int WriteDSP(int value) 中的 while ((inportb(G_base + 0xC) & 0x80) == 0x80); 这也是硬件规定,他必须要等 G_base + 0xC 端口中的数的第7位为1,即 10000000 才允许写,大家也记住就OK。
这里的关键还是在于 void Play_wav(WAV *Wav_file) 中对于长文件的分块处理,其实很简单,就是播放完一块数据,就立即读取下块数据,如此循环。

5. 销毁文件
void Destroy_wav(WAV *Wav_file)
{
if (Wav_file)
{
free(Wav_file->sample);
Wav_file->sample = NULL;
free(Wav_file);
Wav_file = NULL;
}
}

6. 例子
void main(void)
{
WAV *Wav_file; /* 申请音乐文件 */

if (!install_DSP()) /* 初始化DSP */
{
printf("can't install DSP!");
getch();
return(0);
}
/* 加载声音文件 */
if ((Wav_file = Load_wav("win.wav")) == NULL)
{
printf("can't load wav!");
getch();
return(0);
}

Play_wav(Wav_file); /* 播放文件 */
getch();

Destroy_wav(Wav_file); /* 销毁文件 */
}

至此,整个程序介绍完毕,我们可以试试看是否可以播放长文件。其实此程序也有个不大不小的败笔,虽然我们采用以数据块的形式把工作一并交给DSP芯片处理,至使音乐播放得以流畅,音效也得以保证,但就是因为是以数据块来做的,他的流程如下:
开启声卡 -> 读取第1块数据 -> 播放 -> 复位DSP -> 读取第2块数据 -> 播放 -> 复位DSP ...... 读取第n块数据 -> 播放 -> 复位DSP -> 关闭声卡
就是由于数据块与数据块之间要复位DSP,所以在块与块之间就产生了一个 “噗” 的杂音,甚是恼人啊~!还是也去试试法2,自己建立定时器,看看效果如何。

(特别说明:各位朋友,因为声卡控制的资料的确很少,我现在都还没找到比较详实的DSP端口说明,所以我也是看别人的代码加自己的思考猜测来做的以上文章,所以要是各位朋友哪日发现我文中有纰漏错误之处还望指出,大家好一同进步。此篇文章权当有个参考,希望起到抛砖引玉的作用)

[此贴子已经被作者于2006-7-23 9:38:19编辑过]


个人网站 -  http://.h001.
2006-07-23 09:28
baidu
Rank: 3Rank: 3
等 级:新手上路
威 望:8
帖 子:3811
专家分:0
注 册:2005-11-4
得分:0 
真是好东西,想我以前想找DSP资料google几天几夜也没找到 :)

最后听说董大版主有DSP资料,没想到董大版主很小气。。。

偶放弃所有文章版权,偶在BCCN论坛任何贴子,可转贴,可散发,可抄袭,可复制,可被冒名顶替,可被任何人引用到任何文章中且不写出引文出处,偶分文不取。
2006-07-25 16:50
一笔苍穹
Rank: 1
等 级:新手上路
帖 子:640
专家分:0
注 册:2006-5-25
得分:0 
找JIG和找我一样,我最近刚到深圳,忙于找工作,一直没空上来,即使是上来了,资料也都在家里的机器里,还是无法给你,见谅。
2006-07-28 13:29
qingfen
Rank: 1
等 级:新手上路
帖 子:53
专家分:0
注 册:2005-12-23
得分:0 
不错,我也受易不少. 谢了!!!
2006-07-29 21:34
hjj1123
Rank: 1
等 级:新手上路
帖 子:198
专家分:0
注 册:2006-7-29
得分:0 
有没有C的中断大全之类的资料?我很想知道各种中断的用途以及相应的中断号.

qq:674940174
2006-08-17 03:25
一笔苍穹
Rank: 1
等 级:新手上路
帖 子:640
专家分:0
注 册:2006-5-25
得分:0 
好像有,不过是E文的
2006-08-18 15:13
hjj1123
Rank: 1
等 级:新手上路
帖 子:198
专家分:0
注 册:2006-7-29
得分:0 
斑竹能不能上传一下或者发给我一下hjj1123@sina.com 谢谢拉!!!!!!!!

qq:674940174
2006-08-18 18:01
cdmalcl
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:24
帖 子:4091
专家分:524
注 册:2005-9-23
得分:0 

好郁闷啊
我运行的是dsp那个文件 结果:
can't install DSP!
汗 原先运行的好好的 现在突然就不好使了

int install_DSP()
{
int i, g_address[6] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260};

for (i = 0; i < 32; i++)
{
if (RestDSP(g_address[0]+i*0x10))
{
G_port = g_address[0]+i*0x10; /* 记录端口基地址 */
return 1;
}
}

return 0;
}
这个我也改了
怎么还是不好使!!

我重安系统也不行

有的时候直接就跳出了
在:/* 若文件过长 */
if (Wav_file_head.file_len - 50 > 0xC7FF)
{
/* 读取声音数据 */ getch();
fread(Wav_file->sample, 0xC7FF, 1, fp);
}
这个地方跳和、出去的

我现在要纳闷死

孙哥不知道最近忙什么 总是见不到你啊

2006-09-14 14:03
mikecanan
Rank: 1
等 级:新手上路
帖 子:2
专家分:0
注 册:2006-9-13
得分:0 

这个程序是不是只能正常播放采样率是11KHZ和22KHZ,为什么44KHZ的就不能正常播放呢...什么人唱的歌被这个程序一播放就成了老头了

2006-09-19 20:21



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-79791-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.229452 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved