其实要说在Linux系统下播放音乐,确实是一件让人非常抓狂的事情,抛开各种音频格式的商业授权不说,即使提供给你相应的解码库,能玩儿得转的人那又是少之又少。可能有些盆友说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系统中的音频设备文件:
其实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,就可以完成对底层音频硬件的控制:
下面,我们首先看一下OSS下如何播放wav文件:
点击(此处)折叠或打开
- /*playsound.c*/
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <linux/soundcard.h>
-
- #define AUDIO_DEVICE "/dev/dsp"
-
- int play_sound(char *filename,int rate,int bits){
- struct stat stat_buf;
- unsigned char *buf = NULL;
- int result,arg,status,handler,fd;
-
- fd = open(filename,O_RDONLY);
- if(fd<0)
- return -1;
-
- if(fstat(fd,&stat_buf))
- {
- close(fd);
- return -1;
- }
-
- if(!stat_buf.st_size)
- {
- close(fd);
- return -1;
- }
-
- buf=malloc(stat_buf.st_size);
- if(!buf){
- close(fd);
- return -1;
- }
-
- if(read(fd,buf,stat_buf.st_size)<0){
- free(buf);
- close(fd);
- return -1;
- }
-
- handler = open(AUDIO_DEVICE,O_WRONLY);
- if(-1 == handler){
- return -1;
- }
-
- arg = rate*2;
- status = ioctl(handler,SOUND_PCM_WRITE_RATE,&arg);
- if(-1 == status)
- return -1;
-
- arg = bits;
- status = ioctl(handler,SOUND_PCM_WRITE_BITS,&arg);
- if(-1 == status)
- return -1;
-
- result = write(handler,buf,stat_buf.st_size);
- if(-1 == result)
- return -1;
-
- free(buf);
- close(fd);
- close(handler);
- return result;
-
- }
-
- int main(int argc,char** argv){
- play_sound(argv[1],atoi(argv[2]),atoi(argv[3]));
- return 0;
- }
点击(此处)折叠或打开
- #!/bin/sh
- [ "$#" -eq 0 ] && {
- echo "Usage: $0 filename"
- exit
- }
- BITS=`file $1 | cut -d' ' -f9`
- RATE=`file $1 | cut -d' ' -f12`
-
- echo "Playing...$(file $1)"
- ./playsound $1 $RATE $BITS
内核版本2.6.32,看一下/dev目录下确实没有dsp和mixer设备文件了,取而代之的/dev/snd目录。在centos5.3里我们也见到过这个目录,但当时还只是试用阶段,现在alsa已经完全扶正了:
点击(此处)折叠或打开
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <linux/soundcard.h>
- #include <alsa/asoundlib.h>
-
- #define ALSA_MAX_BUF_SIZE 65535
-
- int play_sound(char* filename,int rate,int bits,int channel,int order)
- {
- long loops;
- int rc,size,dir;
- snd_pcm_t *handle;
- snd_pcm_hw_params_t *params;
- snd_pcm_uframes_t frames,periodsize;
- snd_mixer_t *mixer;
- snd_mixer_elem_t *pcm_element;
-
- char *buffer;
- unsigned int val;
- FILE *fp = fopen(filename,"rb");
- rc = snd_pcm_open(&handle,"default",SND_PCM_STREAM_PLAYBACK,0);
-
- snd_pcm_hw_params_alloca(¶ms);
- snd_pcm_hw_params_any(handle,params);
- snd_pcm_hw_params_set_access(handle,params,SND_PCM_ACCESS_RW_INTERLEAVED);
- switch(order){
- case 1:
- snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_S16_LE);
- break;
- case 2:
- snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_S16_BE);
- break;
- defualt:
- break;
- }
- snd_pcm_hw_params_set_channels(handle,params,channel);
-
- val = rate;
- snd_pcm_hw_params_set_rate_near(handle,params,&val,0);
- snd_pcm_hw_params_get_buffer_size_max(params,&frames);
- frames = frames < ALSA_MAX_BUF_SIZE? frames:ALSA_MAX_BUF_SIZE;
- rc = snd_pcm_hw_params_set_buffer_size_near(handle,params,&frames);
- snd_pcm_hw_params_get_period_size_min(params,&periodsize,NULL);
- if(!periodsize){
- periodsize=size/4;
- }
- rc = snd_pcm_hw_params_set_period_size_near(handle,params,&periodsize,NULL);
- rc = snd_pcm_hw_params(handle,params);
-
- snd_mixer_open(&mixer,0);
- snd_mixer_attach(mixer,"default");
- snd_mixer_selem_register(mixer,NULL,NULL);
- snd_mixer_load(mixer);
- for(pcm_element = snd_mixer_first_elem(mixer);pcm_element;pcm_element=snd_mixer_elem_next(pcm_element))
- {
- if(snd_mixer_elem_get_type(pcm_element)==SND_MIXER_ELEM_SIMPLE && snd_mixer_selem_is_active(pcm_element))
- {
- if(!strcmp(snd_mixer_selem_get_name(pcm_element),"Master"))
- {
- snd_mixer_selem_set_playback_volume_range(pcm_element,0,100);
- snd_mixer_selem_set_playback_volume_all(pcm_element,(long)100);
- }
- }
- }
-
- buffer = (char*)malloc(size);
- while(1)
- {
- rc = fread(buffer,1,size,fp);
- if(0== rc)
- break;
- while((rc = snd_pcm_writei(handle,buffer,size))<0)
- {
- usleep(200);
- if(-EPIPE == rc)
- snd_pcm_prepare(handle);
- else if(0 > rc)
- printf("error fomr writei\n");
- }
- }
- snd_pcm_drain(handle);
- snd_pcm_close(handle);
- free(buffer);
- snd_mixer_close(mixer);
- fclose(fp);
- return 0;
- }
-
-
- int main(int argc,char** argv){
- play_sound(argv[1],atoi(argv[2]),atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));
- return 0;
- }
点击(此处)折叠或打开
- #!/bin/sh
- [ "$#" -eq 0 ] && {
- echo "Usage: $0 filename"
- exit
- }
- ORDER=`file $1 | cut -d' ' -f3`
- BITS=`file $1 | cut -d' ' -f9`
- CHANNEL=`file $1 | cut -d' ' -f11`
- RATE=`file $1 | cut -d' ' -f12`
- #channel
- if [ "$CHANNEL" == "stereo" ]; then
- CHANNEL=2
- else
- CHANNEL=1
- fi
- #platform-byte-order
- if [ "$ORDER" == "(little-endian)" ]; then
- ORDER=1
- else
- ORDER=2
- fi
- echo "Playing...$(file $1)"
- ./playsound $1 $RATE $BITS $CHANNEL $ORDER
编译C文件时,由于我们用了alsa库,所以gcc的编译选项要加上-lasound才可以。如果播放时声音很小,可以用amixer来调节音量。如果不幸的是你系统里找不到amixer命令的话,就用yum install alsa-utils或者下载alsa源码来安装吧。
附件是测试用的音频文件,另外,后面我会将完整支持OSS和ALSA两种架构的最终播放代码放在 github上,有需要的盆友到时候可以拿去鼓捣鼓捣,今天就先到这里吧。
附件: news.wav
原文《多媒体技术基础之---Come on!来点儿音乐吧 》,转自:http://zzjlzx.blog.chinaunix.net/uid-23069658-id-4008256.html