精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>电脑技术>>● Linux>>有待整理文章>>Linux声音设备编程

主题:Linux声音设备编程
发信人: r_hayes(Hayes)
整理人: qiaoqian(2002-05-13 06:53:32), 站内信件
好久不见了,大家还好吗?:) 
     其实 Linux下的声音设备编程比大多数人想象的要简单得多。一般说来,我们常用的声音设备是内部扬声器和声卡,它们都对应/dev目录下的一个或多个设备文件,我们象打开普通文件一样打开它们,用ioctl()函数设置一些参数,然后对这些打开的特殊文件进写操作。 
  由于这些文件不是普通的文件,所以我们不能用ANSI C(标准C)的fopen、fclose等来操作文件,而应该使用系统文件I/O处理函数(open、read、write、lseek和close)来处理这些设备文件。ioctl()或许是Linux下最庞杂的函数,它可以控制各种文件的属性,在Linux声音设备编程中,最重要的就是使用此函数正确设置必要的参数。 
  下面我们举两个实际的例子来说明如何实现Linux下的声音编程。由于此类编程涉及到系统设备的读写,所以,很多时候需要你有root权限,如果你将下面的例子编译后不能正确执行,那么,首先请你检查是否是因为没有操纵某个设备的权限。 

1. 对内部扬声器编程 
  内部扬声器是控制台的一部分,所以它对应的设备文件为/dev/console。变量KIOCSOUND在头文件 /usr /include /linux /kd.h中声明,ioctl函数使用它可以来控制扬声器的发声,使用规则为: 
  ioctl ( fd, KIOCSOUND, (int) tone); 
  fd为文件设备号,tone 是音频值。当tone为0时,终止发声。必须一提的是它所理解的音频和我们平常以为的音频是不同的,由于计算机主板定时器的时钟频率为1.19MHZ,所以要进行正确的发声,必须进行如下的转换: 
  扬声器音频值=1190000/我们期望的音频值。 
  扬声器发声时间的长短我们通过函数usleep(unsigned long usec)来控制。它是在头文件/usr /include /unistd.h中定义的,让程序睡眠usec微秒。下面即是让扬声器按指定的长度和音频发声的程序的完整清单: 

#include < fcntl.h > 
#include < stdio.h > 
#include < stdlib.h > 
#include < string.h > 
#include < unistd.h > 
#include < sys/ioctl.h > 
#include < sys/types.h > 
#include < linux/kd.h > 

/* 设定默认值 */ 
#define DEFAULT_FREQ 440 /* 设定一个合适的频率 */ 
#define DEFAULT_LENGTH 200 /* 200 微秒,发声的长度是以微秒为单位的*/ 
#define DEFAULT_REPS 1 /* 默认不重复发声 */ 
#define DEFAULT_DELAY 100 /* 同样以微秒为单位*/ 

/* 定义一个结构,存储所需的数据*/ 
typedef struct { 
int freq; /* 我们期望输出的频率,单位为Hz */ 
int length; /* 发声长度,以微秒为单位*/ 
int reps; /* 重复的次数*/ 
int delay; /* 两次发声间隔,以微秒为单位*/ 
} beep_parms_t; 


/* 打印帮助信息并退出*/ 
void usage_bail ( const char *executable_name ) { 
printf ( "Usage: \n \t%s [-f frequency] [-l length] [-r reps] [-d delay] \n ", 
executable_name ); 
exit(1); 


/ * 分析运行参数,各项意义如下: 
* "-f <以HZ为单位的频率值 >" 
* "-l <以毫秒为单位的发声时长 >" 
* "-r <重复次数 >" 
* "-d <以毫秒为单位的间歇时长 >" 
*/ 
void parse_command_line(char **argv, beep_parms_t *result) { 
char *arg0 = *(argv++); 
while ( *argv ) { 
if ( !strcmp( *argv,"-f" )) { /*频率*/ 
int freq = atoi ( *( ++argv ) ); 
if ( ( freq <= 0 ) | | ( freq > 10000 ) ) { 
fprintf ( stderr, "Bad parameter: frequency must be from 1..10000\n" ); 
exit (1) ; 
} else { 
result->freq = freq; 
argv++; 

} else if ( ! strcmp ( *argv, "-l" ) ) { /*时长*/ 
int length = atoi ( *(++argv ) ); 
if (length < 0) {
fprintf(stderr, "Bad parameter: length must be >= 0\n"); 
exit(1); 
} else { 
result->length = length; 
argv++; 

} else if (!strcmp(*argv, "-r")) { /*重复次数*/ 
int reps = atoi(*(++argv)); 
if (reps < 0) {
fprintf(stderr, "Bad parameter: reps must be >= 0\n"); 
exit(1); 
} else { 
result->reps = reps; 
argv++; 

} else if (!strcmp(*argv, "-d")) { /* 延时 */ 
int delay = atoi(*(++argv)); 
if (delay < 0) {
fprintf(stderr, "Bad parameter: delay must be >= 0\n"); 
exit(1); 
} else { 
result->delay = delay; 
argv++; 

} else { 
fprintf(stderr, "Bad parameter: %s\n", *argv); 
usage_bail(arg0); 




int main(int argc, char **argv) { 
int console_fd; 
int i; /* 循环计数器 */ 
/* 设发声参数为默认值*/ 
beep_parms_t parms = {DEFAULT_FREQ, DEFAULT_LENGTH, DEFAULT_REPS, 
DEFAULT_DELAY}; 
/* 分析参数,可能的话更新发声参数*/ 
parse_command_line(argv, &parms); 

/* 打开控制台,失败则结束程序*/ 
if ( ( console_fd = open ( "/dev/console", O_WRONLY ) ) == -1 ) { 
fprintf(stderr, "Failed to open console.\n"); 
perror("open"); 
exit(1); 


/* 真正开始让扬声器发声*/ 
for (i = 0; i < parms.reps; i++) {
/* 数字1190000从何而来,不得而知*/
int magical_fairy_number = 1190000/parms.freq;

ioctl(console_fd, KIOCSOUND, magical_fairy_number); /* 开始发声 */
usleep(1000*parms.length); /*等待... */
ioctl(console_fd, KIOCSOUND, 0); /* 停止发声*/
usleep(1000*parms.delay); /* 等待... */
} /* 重复播放*/
return EXIT_SUCCESS;
}
  将上面的例子稍作扩展,用户即可以让扬声器唱歌。只要找到五线谱或简谱的音阶、音长、节拍和频率、发声时长、间隔的对应关系就可以了。我现在还记得以前在DOS下编写出《世上只有妈妈好》时的兴奋。最后,说一些提外话,这其实是一个很简单的程序,但是我们却用了很长的篇幅,希望读者从以上的代码里能体会到写好的程序的一些方法,或许最重要的是添加注释吧。一个程序的注释永远不会嫌多,即便你写的时候觉得它根本是多余,但相信我,相信曾这样告诉我们的许多优秀的程序员:养成写很多注释的习惯。

2. 对声卡编程
  只要我们不是进行诸如驱动设备开发之类的工作,对声卡的编程和上面对扬声器的编程没有什么本质的区别。当你试图来编写诸如CD播放器、MP3播放器之类的复杂的程序时,你的工作是取获得与CDROM控制、MP3解码之类的信息,而读写系统设备的这一步在Linux下超互想象的简单。例如,Linux下最简单的播放wav的程序只有一行:cp $< >/dev/audio。将它写成一个shell文件,同样是一个程序(shell 编程)。 
  我们首先需要知道一台机器上是否有声卡,一个检查的办法是检查文件/dev/sndstat文件,如果打开此文件错误,并且错误号是ENODEV,则说明此机器没有安装声卡。除此之外,试着去打开文件/dev/dsp也可以来检查是否安装了声卡。 
  Linux下和声卡相关的文件有许多,如采集数字样本的/dev/dsp文件,针对混音器的/dev/mixer文件以及用于音序器的/dev/sequencer等。文件/dev/audio是一个基于兼容性考虑的声音设备文件,它实际是到上述数字设备的一个映射,它最大的特色或许是对诸如wav这类文件格式的直接支持。我们下面的例子即使用了此设备文件实现了一个简单的录音机:我们从声卡设备(当然要用麦克风)读取音频数据,并将它存放到文件test.wav中去。要播放这个wav文件,只要如前面所述,使用命令cp test.wav >/dev/audio即可,当然你也可以用Linux下其他的多媒体软件来播放这个文件。 
下面即是完整的程序清单: 

/* 此文件中定义了下面所有形如SND_的变量*/ 
#include <sys/soundcard.h> 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 

main() 

/* id:读取音频文件描述符;fd:写入的文件描述符。i,j为临时变量*/ 
int id,fd,i,j; 
/* 存储音频数据的缓冲区,可以调整*/ 
char testbuf[4096]; 
/* 打开声卡设备,失败则退出*/ 
if ( ( id = open ( "/dev/audio", O_RDWR ) ) < 0 ) {
fprintf (stderr, " Can't open sound device!\n");
exit ( -1 ) ;
}
/* 打开输出文件,失败则退出*/
if ( ( fd = open ("test.wav",O_RDWR))<0){
fprintf ( stderr, " Can't open output file!\n");
exit (-1 );
}
/* 设置适当的参数,使得声音设备工作正常*/
/* 详细情况请参考Linux关于声卡编程的文档*/
i=0;
ioctl (id,SNDCTL_DSP_RESET,(char *)&i) ;
ioctl (id,SNDCTL_DSP_SYNC,(char *)&i);
i=1;
ioctl (id,SNDCTL_DSP_NONBLOCK,(char *)&i);
i=8000;
ioctl (id,SNDCTL_DSP_SPEED,(char *)&i);
i=1;
ioctl (id,SNDCTL_DSP_CHANNELS,(char *)&i);
i=8;
ioctl (id,SNDCTL_DSP_SETFMT,(char *)&i);
i=3;
ioctl (id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
i=3;
ioctl (id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
i=1;
ioctl (id,SNDCTL_DSP_PROFILE,(char *)&i);
/* 读取一定数量的音频数据,并将之写到输出文件中去*/
for ( j=0; j<10;){
i=read(id,testbuf,4096);
if(i>0){ 
write(fd,filebuf,i); 
j++; 


/* 关闭输入、输出文件*/ 
close(fd); 
close(id); 





----

                    灌灌 
                    灌灌 
                    灌灌 
                    灌灌          灌灌 
                    灌灌      灌灌灌 
    灌灌灌灌灌灌灌  灌灌  灌灌灌 
      灌灌灌灌灌灌灌灌灌灌灌灌 
                灌灌灌灌灌灌 
               灌灌灌灌灌灌灌  
              灌灌  灌灌  灌灌 
             灌灌   灌灌    灌灌 
            灌灌    灌灌      灌灌 
          灌灌      灌灌      灌灌灌 
          灌灌      灌灌        灌灌灌灌 
        灌灌        灌灌        灌灌灌灌灌 
      灌灌          灌灌          灌灌灌灌灌  
    灌灌            灌灌            灌灌 
                灌灌灌灌 
                  灌灌灌 
                    灌    

[关闭][返回]