谢谢!!!
其实很简单,只是对声卡DSP芯片个端口的操作而已,这方面的资料的确很少,我这几天也在研究它,希望真的用小内存做出播发任意长度的文件,还想自己做个语音识别的程序。
其实控制声卡DSP芯片播发声音(这里只指播发WAV格式)有两种方式,第一是直接以块的方式,即把声音信息一并送入DSP芯片交给DSP芯片处理,这样的好处是播发流畅,而且不用操心采样率,直接交给DSP芯片就完了,而且在程序中表现想是多线程,即声音播放的同时,其实程序已经跑后面去了,所以很适合在游戏用到他,可不足之处就是因为DOS无大内存给你使用,所以播放的声音长度也就限制了,只能是64K以内,NEO中即使用此种方法。
第二中方法就是,你自己写一个模块实现采样率,再逐个把数据送入DSP芯片,这样的好处是(当然是你先修改时钟出发频率,然后自己设置一个中断,以几数的方式来实现以指定时间间隔往DSP芯片送数据为前提)可以播放任意长度的声音文件,可他的不足就是:因为你是以CPU来模拟实现采样率,所以在此线程中是先播发完文件才能执行后面的代码,最致命的就是播放声音可能不流畅,因为你是自己用实现的修改时钟出发频率,然后自己设置一个中断,进而记数来实现间隔,那在这个过程中你很难保证间隔是一直恒定的(比如要是另个程序当机,很可能回影响到你自己播放程序)......但我鼓励大家可以一试来实现本方法,有挑战些。
下面我给出第一法的一个程序,写的很好,董凯给的,不知道是他些的否?风格很好,很容易看懂
#include <dos.h>
#include <stdio.h>
#ifndef FP_OFF
#define FP_OFF(fp) ((unsigned)(fp))
#endif
#ifndef FP_SEG
#define FP_SEG(fp) ((unsigned)((unsigned long)(fp) >> 16))
#endif
#define DIGI_AUTODETECT 1 /* 让 Allegro 来选择一个数字声音驱动设备*/
#define DIGI_NONE 0 /* 无数字声音*/
#define DIGI_SB 100 /* Sound Blaster (自动检测类型)*/
#define DIGI_SB10 110 /* SB 1.0 (8 位单声道 单一发射 dma)*/
#define DIGI_SB15 115 /* SB 1.5 (8 位单声道 单一发射 dma)*/
#define DIGI_SB20 120 /* SB 2.0 (8 位单声道 自动初始化 dma)*/
#define DIGI_SBPRO 108 /* SB Pro (8 位立体声)*/
#define DIGI_SB16 116 /* SB16 (16 位立体声)*/
#define DIGI_AUDIODRIVE 200 /* ESS AudioDrive*/
#define DIGI_SOUNDSCAPE 300 /* Ensoniq Soundscape*/
/*midi_card 应当是以下值中的一个*/
#define MIDI_AUTODETECT 1 /* 让 Allegro 挑选一个 MIDI 声音驱动程序*/
#define MIDI_NONE 0 /* 无 MIDI 声音*/
#define MIDI_ADLIB /* Adlib 或 SB FM 混合 (自动检测型号)*/
#define MIDI_OPL2 /* OPL2 混合 (单声道, 在 Adlib 和 SB 下使用)*/
#define MIDI_2XOPL2 /* 双 OPL2 混合 (立体声, 在 SB-Pro-I 下使用)*/
#define MIDI_OPL3 /* OPL3 混合 (立体声, SB Pro-II 及以上)*/
#define MIDI_SB_OUT /* SB MIDI 界面*/
#define MIDI_MPU /* MPU-401 MIDI 界面*/
#define MIDI_DIGMID /* 基于采样的软件波表播放器*/
#define MIDI_AWE32
#define PCM_HEAD_LONG 50
#define PLAY_DELAY 100
typedef struct WaveData
{
unsigned short sample_lenth;
unsigned short rate;
unsigned short channels;
unsigned char time_constant;
char bit_res;
char *sample;
}SAMPLE;
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]; /*过渡字节*/
};
unsigned short g_base; /*声卡基址*/
unsigned short g_port;
void write_dsp( unsigned char value );
short ResetDSP ( unsigned short Test );
void set_mixer_reg(short index, char value);
short install_sound(short digi_card, short midi_card, char *cfg_path);
short play_sample(SAMPLE *spl, short vol, short pan, short freq, short loop);
SAMPLE *load_wav(char *filename);
char play_back (SAMPLE *wave);
void stop_sample(SAMPLE *spl);
void destroy_sample(SAMPLE *spl);
void remove_sound(void);
#define reset_dsp() ResetDSP(g_port)
main()
{
SAMPLE *wav;
install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL);set_mixer_reg(0, 0);
wav = load_wav("act.wav");set_mixer_reg(0x26, 0x0);
play_sample(wav, 255, 128, 1000, 1); getch();destroy_sample(wav);
wav = load_wav("win.wav");set_mixer_reg(0x26, 0x0f);
play_sample(wav, 255, 128, 1000, 1); getch();destroy_sample(wav);
wav = load_wav("down.wav");set_mixer_reg(0x26, 0);
play_sample(wav, 255, 128, 1000, 1); getch();destroy_sample(wav);
wav = load_wav("sound.wav");set_mixer_reg(0x26, 0xaa);
play_sample(wav, 255, 128, 1000, 1); getch();destroy_sample(wav);
wav = load_wav("levup.wav");set_mixer_reg(0x26, 0x55);
play_sample(wav, 255, 128, 1000, 1); destroy_sample(wav);
getch();
remove_sound();
}
short install_sound(short digi_card, short midi_card, char *cfg_path)
{
if (digi_card)
{
if ( ResetDSP (0x220) )
{
/*基址为210h*/
g_port = 0x220;
}
else if (ResetDSP (0x230))
{
/*基址为220h*/
g_port = 0x230;
}
else if (ResetDSP (0x240))
{
/*基址为240h*/
g_port = 0x240;
}
else
{
/*基址检测失败*/
g_port = -1;
return 0;
}
}
if (midi_card)
{}
cfg_path += 0; /*只是为了不报警*/
return 1;
}
/****************************************************************************
检查一个声卡基址是否存在,如果存在则将声卡复位 *
****************************************************************************/
short ResetDSP(unsigned short Test)
{
/*重置DSP*/
outportb (Test + 0x6, 1);
delay(50);
outportb (Test + 0x6, 0);
delay(50);
/*如果重置成功则检查*/
if ((inportb(Test + 0xE) & 0x80 == 0x80) && (inportb(Test + 0xA) == 0xAA))
{
/*DSP被找到*/
g_base = Test;
return (1);
}
else
{ /*找不到DSP*/
return (0);
}
}
/****************************************************************************
** 发送一个字节到声卡的DSP(数字信号处理芯片:Digital Signal Processor) **
****************************************************************************/
void write_dsp(unsigned char value)
{
/*等待DSP接收一个字节*/
while ((inportb(g_base + 0xC) & 0x80) == 0x80);
/*发送字节*/
outportb (g_base + 0xC, value);
}
void set_mixer_reg(short index, char value)
{
outportb (g_port + 4, index);
outportb (g_port + 5, value);
}
/****************************************************************************
** 播放内存中的部分音频流 **
****************************************************************************/
char play_back (SAMPLE *wave)
{
long LinearAddress;
unsigned short page, offset;
if ((!wave) || (!wave->sample))
{
printf("no sample!");
return 0;
}
/*开启声卡*/
write_dsp( 0xD1 );
write_dsp( 0x40 ); /*DSP第40h号命令 :设置采样频率*/
write_dsp( wave -> time_constant ); /*Write time constant*/
/*将音频流指针转换成线性地址*/
LinearAddress = FP_SEG ( wave -> sample );
LinearAddress = ( LinearAddress << 4 ) + FP_OFF ( wave->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 ( 0x02, offset & 0x100); /*将偏移量写入DMA控制器*/
outportb ( 0x02, offset >> 8);
outportb ( 0x83, page); /*将页面写入DMA控制器*/
outportb ( 0x03, wave->sample_lenth & 0x100);
outportb ( 0x03, wave->sample_lenth >> 8);
outportb ( 0x0A, 1 ); /*激活DMA通道一*/
write_dsp( 0x14 ); /*DSP第14h号命令 :单一周期回放*/
write_dsp( wave -> sample_lenth & 0xFF );
write_dsp( wave -> sample_lenth >> 8);
return 1;
}
/****************************************************************************
** 将音频文件读入内存 **
** 这个例程能操作标准的PCM文件头部 **
** 它覆行了许多错误检查 **
****************************************************************************/
SAMPLE *load_wav(char *filename)
{
struct HeaderType t_header;
FILE *wav_file;
SAMPLE *voice;
/*如果打不开文件...*/
wav_file = fopen(filename, "rb");
if (wav_file == NULL)
{
printf ("Can't open the file: %s\n", filename);
return (NULL);
}
voice = (SAMPLE *)malloc(sizeof(SAMPLE));
if ( voice == NULL)
{ printf ( " Not mem!\n" );
return NULL;
}
/*读取文件头部*/
fread ( &t_header, sizeof(t_header), 1, wav_file );
/*检查RIFF头*/
if ( t_header.riff != 0x46464952)
{
printf ( " Not a wave file!\n" );
return ( NULL );
}
/*检查通道*/
if ( t_header.Channels != 1 )
{
printf ( "Not a MONO wave file!\n" );
return ( NULL );
}
/*检查采样位数*/
if ( t_header.sample_bits != 8 )
{
printf ( "Not an 8-bit wave file!\n" );
return ( NULL );
}
voice->sample_lenth = (unsigned short)(t_header.file_len - PCM_HEAD_LONG);
voice->time_constant= 256 - (1000000L / ( (voice->rate=t_header.frequency) * (voice->channels=t_header.Channels) ));
if ( t_header.file_len - PCM_HEAD_LONG >= 0xffff )
{
voice->sample_lenth = 0xfffe;
}
if (voice->sample_lenth == 0) voice->sample_lenth = 1;
voice->sample = (char *) malloc ( voice->sample_lenth + 1);/*申请内存*/
if ( voice->sample == NULL)
{
printf ( " Not mem!!\n" );
fclose ( wav_file ); /*关闭文件*/
return NULL;
}
/*fseek(wav_file, 57L, SEEK_SET);*/
/*读取采样数据流*/
fread ( voice->sample, voice->sample_lenth + 1, 1, wav_file );
fclose ( wav_file ); /*关闭文件*/
return voice;
}
short play_sample(SAMPLE *spl, short vol, short pan, short freq, short loop)
{
vol += 0;
pan += 0;
freq+= 0;
loop+= 0;
if (spl)
{
/*开始回放*/
play_back(spl);
delay(PLAY_DELAY);
/*停止 DMA 传送*/
write_dsp(0xD0);
return 1;
}
return 0;
}
void stop_sample(SAMPLE *spl)
{
/* SAMPLE stop;
char sam[10];
stop.sample = sam;
stop.sample_lenth = 9;
stop.time_constant= 9;
play_back(&stop);*/
}
void destroy_sample(SAMPLE *spl)
{
if (spl)
{
free(spl->sample);
spl->sample = NULL;
free(spl);
spl = NULL;
}
}
void remove_sound(void)
{
/*关闭声卡*/
write_dsp(0xD3);
/*重置DSP*/
reset_dsp();
}
第二方法,我这也有个代码,可是错的,根本编译不通过。
我本是早想写个声卡DSP的专题文章,就是因为一直没去把第二法实现所以一直没发,可能过两日我回发给这样的专题,好好的介绍一下声卡的控制......
(当然,我自己也在摸索阶段,因为基本没有关于DSP声卡芯片个端口具体详实的介绍,所以有时候很为难,我们大家也只好尽力而为)