的解码库,能玩儿得转的人那又是少之又少。可能有些盆友说ubuntu这方面确实做得不错,一旦默认安装好,几乎不用装任何其他东西,常见的是音频文件都可以正常播放了。因为我天生就有股 喜欢折腾的劲儿,所以关于 ubuntu确实不怎么感冒 ,只能说萝卜白菜各有所爱吧。今天我们以 wav文件(也就是上一篇博文所提到的 PCM格式的音频文件 )为例,看看在Linux下怎么播放它,顺便会简单介绍一下Linux系统的音频驱动框架的基础知识。    说到Linux系统下的音频系统驱动框架,最常见的有OSS和ALSA。我们先来简单了解一下这两个框架,以及它们的历史渊源。    OSS全称是Open Sound System,叫做开放式音频系统,最早是Unix系统上一种统一的音频接口。这种基于文件系统的统一访问方式,就意味着对声音的操作完全可以像对普通文件那样执行open,read,write和close等操作,这也正是得益于文件系统的强大有力支撑。OSS中,主要 提供了一下几种音频设备的抽象设备文件:     /dev/mixer:用来 访问声卡中内置的混音器 mixer,用于 调整音量大小和选择音源;     /dev/dsp、 /dev/audio:读这个设备就相当于录音,写这个设备就相当于放音。/dev/dsp与/dev/audio的主要 区别在于所采样的PCM编码方式的 不同,/dev/audio使用的是μ律编码(存在这个设备文件的目的主要是 为了与SunOS兼容,所以在非SunOS系统中尽量不要使用 ),而 /dev/dsp使用8-bit(无符号)的线性编码;     /dev/sequencer、 /dev/sequencer2 :主要用于 访问声卡内置的,或者连接在MIDI接口的合成器 synthesizer。     还有其他的诸如/dev/adsp、/dev/dmmidi、/dev/midi等等,一些不常用的就先不管了。看一下我的CentOS 5.3内核版本2.6.21系统中的音频设备文件:

android接收obu Android接收midi并合成_android接收obu

   我们 可以直接使用Unix/Linux 的命令来放音和录音,例如, 命令cat /dev/dsp >xyz 可用来录音,录音的结果放在xyz文件中;命令cat xyz >/dev/dsp播放声音文件xyz。当然,我们还 可以通过 open、close、read、write、ioctl等这些 文件的操作函数直接控制这些设备,达到对声音 应用程序级别的访问与控制 。那么这么看来OSS应该还算比较完美了,Linux下的声音编程应该没有难度才对,怎么会说Linux下声音变成是一件很头疼的事儿呢?

    其实OSS自从诞生到OSSv3版及其之前,

都是Linux的原始声音系统,并集成在 内核代码里。 当OSS被4Front Technologies收购 后,于 2002年OSSv4作为商业软件的出现时,它的命运就被我们接下来要介绍的ALSA给改写了。其实严格意义上来说, 商业化不是导致OSS没落的根本原因,也有技术层面的因素在,比如OSS的混音功能。由于先天的设计缺陷,OSS对混音的支持非常糟糕,由于当时的声卡本身是支持多路输出的混合,所以OSS就偷懒了,将混音的任务交给了声卡,所以那个年代的程序猿们为了操作混音器,代码里充斥着大量的ioctl函数,现在看起来相当难受。

   ALSA全称是

Advanced Linux Sound Architecture,叫做Linux系统下的高级音频架构,它主要 为声卡提供的驱动组件,以替代原先的 OSS 。 这个项目最早 始于1998年Gravis Ultrasound所开发的驱动,它一直作为一个单独的软件包开发,直到2002年他被引进入Linux内核的开发版本(2.5.4-2.5.5)。自从2.6版本开始ALSA成为Linux内核中默认的标准音频驱动程序集,而OSS则被标记为废弃。 所以,现在看来OSS被ALSA替代,闭源和商业化都只是外因,内因还是其设计的缺陷。虽然2007年4Front又宣布OSSv4重新在GPL协议下重新开源,但已经人去楼空秋已暮了,现在ALSA对OSS的支持也比较好了,不知道OSS还能否王者归来。其实这些都不重要,对于开发者来说,简单、便捷、高效、实用才是王道,优美的框架结构,完善的文档支持强过口水战百倍。

目前ALSA已经成为Linux系统里主流的音频系统框架,在2.6.21的内核里已经看不到OSS的影子了。

在内核设备驱动层面,ALSA提供了alsa-driver,同时在应用层,ALSA也为我们提供了alsa-lib,应用程序只要调用alsa-lib所 提供的API,就可以完成对底层音频硬件的控制:

android接收obu Android接收midi并合成_android接收obu_02

    上图向我们展示了ALSA的一个简单的结构, 用户空间的alsa-lib对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度。内核空间中,alsa-soc其实是对alsa-driver的进一步封装,针对嵌入式设备提供了一些列增强的功能,通常也被叫做ASoC,即Alsa-soc的缩写 ,像 Android系统中底层就用了ASoC。想了解ALSA更多细节的盆友可以访问他们的官网: http://www.alsa-project.org/main/index.php/Main_Page

下面,我们首先看一下OSS下如何播放wav文件:

点击(此处)折叠或打开

1. /*playsound.c*/
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <unistd.h>
5. #include <fcntl.h>
6. #include <sys/types.h>
7. #include <sys/stat.h>
8. #include <linux/soundcard.h>
9. 
10. #define AUDIO_DEVICE "/dev/dsp"
11. 
12. int play_sound(char *filename,int rate,int bits){
13. ;
14. *buf  = NULL;
15. int result,arg,status,handler,fd;
16. 
17. = open(filename,O_RDONLY);
18. if(fd<0)
19. -1;
20. 
21. if(fstat(fd,&stat_buf))
22. {
23. (fd);
24. -1;
25. }
26. 
27. if(!stat_buf.st_size)
28. {
29. (fd);
30. -1;
31. }
32. 
33. =malloc(stat_buf.st_size);
34. if(!buf){
35. (fd);
36. -1;
37. }
38. 
39. if(read(fd,buf,stat_buf.st_size)<0){
40. (buf);
41. (fd);
42. -1;
43. }
44. 
45. = open(AUDIO_DEVICE,O_WRONLY);
46. if(-1 == handler){
47. -1;
48. }
49. 
50. = rate*2;
51. = ioctl(handler,SOUND_PCM_WRITE_RATE,&arg);
52. if(-1 == status)
53. -1;
54. 
55. = bits;
56. = ioctl(handler,SOUND_PCM_WRITE_BITS,&arg);
57. if(-1 == status)
58. -1;
59. 
60. = write(handler,buf,stat_buf.st_size);
61. if(-1 == result)
62. -1;
63. 
64. (buf);
65. (fd);
66. (handler);
67. ;
68. 
69. }
70. 
71. int main(int argc,char** argv){
72. (argv[1],atoi(argv[2]),atoi(argv[3]));
73. ;
74. }
     因为只是演示用,所以错误判断就少了一些。另外,为了让我们的播放程序自动获得音频文件的参数,诸如采样率,量化精度等,我又提供了一个shell脚本player: 

点击(此处)折叠或打开 
 
 
1. #!/bin/sh 
2. 
3. [ "$#" -eq 0 ] && { 
4. echo "Usage: $0 filename" 
5. exit 
6. } 
7. BITS=`file $1 | cut -d' ' -f9` 
8. RATE=`file $1 | cut -d' ' -f12` 
9. 
10. echo "Playing...$(file $1)" 
11. ./playsound $1 $RATE $BITS

    将上述C文件编译,然后,在命令行之./player 文件名,不出意外的话就可以听到声音了,只可惜没办法演示这个过程:

android接收obu Android接收midi并合成_多媒体_03

   我的系统确实可以听到,但是声音比较小,如果你在命令行执行amixer的话,应该可以看到下面的输出信息:

android接收obu Android接收midi并合成_框架_04

   我的声卡音量居然只有75%(因为我用的虚拟机),然后一句“amixer set Master 100%”命令下去,再重新播放声音,应该就很happy了。

android接收obu Android接收midi并合成_linux_05

    其实大家可能有点疑惑,不是前面介绍了半天ALSA的好处了,怎么用OSS来示范,是不是专拣软柿子捏啊。再说了,现在很多人的系统几乎都不支持OSS了,上面的代码有毛用。其实我也很不甘心,所以又重新装了CentOS6.3的虚拟系统,用ALSA的API再来播一下wav看得行不,经过N个小时的折腾,皇天不负有心人---It's OK!(新手入门,大家来找BUG吧 :) )

   内核版本2.6.32,看一下/dev目录下确实没有dsp和mixer设备文件了,取而代之的/dev/snd目录。在centos5.3里我们也见到过这个目录,但当时还只是试用阶段,现在alsa已经完全扶正了:

android接收obu Android接收midi并合成_linux_06

        播放代码如下:   

点击(此处)折叠或打开

   编译C文件时,由于我们用了alsa库,所以gcc的编译选项要加上-lasound才可以。如果播放时声音很小,可以用amixer来调节音量。如果不幸的是你系统里找不到amixer命令的话,就用yum install alsa-utils或者下载alsa源码来安装吧。

1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <fcntl.h>
5. #include <sys/types.h>
6. #include <sys/stat.h>
7. #include <linux/soundcard.h>
8. #include <alsa/asoundlib.h>
9. 
10. #define ALSA_MAX_BUF_SIZE 65535
11. 
12. int play_sound(char* filename,int rate,int bits,int channel,int order)
13. {
14. ;
15. int rc,size,dir;
16. *handle;
17. *params;
18. ,periodsize;
19. *mixer;
20. *pcm_element;
21. 
22. *buffer;
23. int val;
24. *fp  = fopen(filename,"rb");
25. = snd_pcm_open(&handle,"default",SND_PCM_STREAM_PLAYBACK,0);
26. 
27. (¶ms);
28. (handle,params);
29. (handle,params,SND_PCM_ACCESS_RW_INTERLEAVED);
30. (order){
31. case 1:
32. (handle,params,SND_PCM_FORMAT_S16_LE);
33. ;
34. case 2:
35. (handle,params,SND_PCM_FORMAT_S16_BE);
36. ;
37. :
38. ;
39. }
40. (handle,params,channel);
41. 
42. = rate;
43. (handle,params,&val,0);
44. (params,&frames);
45. = frames  < ALSA_MAX_BUF_SIZE? frames:ALSA_MAX_BUF_SIZE;
46. = snd_pcm_hw_params_set_buffer_size_near(handle,params,&frames);
47. (params,&periodsize,NULL);
48. if(!periodsize){
49. =size/4;
50. }
51. = snd_pcm_hw_params_set_period_size_near(handle,params,&periodsize,NULL);
52. = snd_pcm_hw_params(handle,params);
53. 
54. (&mixer,0);
55. (mixer,"default");
56. (mixer,NULL,NULL);
57. (mixer);
58. for(pcm_element = snd_mixer_first_elem(mixer);pcm_element;pcm_element=snd_mixer_elem_next(pcm_element))
59. {
60. if(snd_mixer_elem_get_type(pcm_element)==SND_MIXER_ELEM_SIMPLE && snd_mixer_selem_is_active(pcm_element))
61. {
62. if(!strcmp(snd_mixer_selem_get_name(pcm_element),"Master"))
63. {
64. (pcm_element,0,100);
65. (pcm_element,(long)100);
66. }
67. }
68. }
69. 
70. =  (char*)malloc(size);
71. while(1)
72. {
73. = fread(buffer,1,size,fp);
74. if(0== rc)
75. ;
76. while((rc = snd_pcm_writei(handle,buffer,size))<0)
77. {
78. (200);
79. if(-EPIPE == rc)
80. (handle);
81. else  if(0 > rc)
82. ("error fomr writei\n");
83. }
84. }
85. (handle);
86. (handle);
87. (buffer);
88. (mixer);
89. (fp);
90. ;
91. }
92. 
93. 
94. int main(int argc,char** argv){
95. (argv[1],atoi(argv[2]),atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));
96. ;
97. }
    然后将player脚本也对应修改一下:    

点击(此处)折叠或打开 
 
 
1. #!/bin/sh 
2. 
3. [ "$#" -eq 0 ] && { 
4. echo "Usage: $0 filename" 
5. exit 
6. } 
7. ORDER=`file $1 | cut -d' ' -f3` 
8. BITS=`file $1 | cut -d' ' -f9` 
9. CHANNEL=`file $1 | cut -d' ' -f11` 
10. RATE=`file $1 | cut -d' ' -f12` 
11. 
12. #channel 
13. if [ "$CHANNEL" == "stereo" ]; then 
14. CHANNEL=2 
15. else 
16. CHANNEL=1 
17. fi 
18. 
19. #platform-byte-order 
20. if [ "$ORDER" == "(little-endian)" ]; then 
21. ORDER=1 
22. else 
23. ORDER=2 
24. fi 
25. 
26. 
27. echo "Playing...$(file $1)" 
28. ./playsound $1 $RATE $BITS $CHANNEL $ORDER

  附件是测试用的音频文件,另外,后面我会将完整支持OSS和ALSA两种架构的最终播放代码放在

github上,有需要的盆友到时候可以拿去鼓捣鼓捣,今天就先到这里吧。