多媒体技术基础

--------------http://zzjlzx.blog.chinaunix.net/uid-23069658-id-3995439.html

声音一个最基本的常识就是“它是一种能量”,初中物理课上我们也学过声音的三要素分别是音色、音调和响度。
    音色:简单理解,就是一种声音的固有特征。比如,电子琴和小提琴发出的声音是有明显区别的,笛子和古筝也有各自的声音特征。有些声音模仿秀的选手可以通过训练,达到模仿不同人或者不同乐器的效果。
   音调:也就是我们所说的频率,单位是赫兹Hz,频率越高听起来越刺耳、越尖锐,频率越低听起来越低沉、越浑厚。医学研究表明,人的听觉系统能察觉的最低频率为20Hz,最高为20000Hz,超出这个范围人类一般就听不到了。其实现实生活中根本就不存在完全能听到20Hz~20kHz这样的人,并且随着年龄的增长、体质的变化,人能听到的声音只会是这个区间的一个子集。



    人对不同频率、不同分贝的声音的生理反应也是有差别的,正如我们中医里提到的“五音”(角、徵、宫、商、羽)和身体脏腑(心、肝、脾、肺、肾)以及对人心神(喜、怒、忧、思、悲)的影响是一样的。


    例如“宫”调,风格悠扬沉静、淳厚庄重,根据五音通五脏的理论,宫调入脾,对消化系统的作用比较明显。这就是为什么很多古代电视或者电影里,皇庭寿宴席的时候一般都是奏宫乐。如果对中华文化感兴趣的朋友肯定注意到,我们古代繁体字的药材的“藥”和音乐的“樂”的字根是一样的,可见老祖宗造字时并不是瞎画的,这说明声音的确还是可以治病。现在精通音律的老师傅是越来越少了。感慨一句,中华文明,博大精深,后继者何也?呜呼。。。扯远了,收一下。
    
而人一般能发出的声音频率也是男女有别,大致范围如下:

 

低音

中音

高音

82 Hz~392Hz

123 Hz~493Hz

164 Hz~698Hz

220 Hz~1.1kHz

    响度:就是声音的大小,一般用“分贝”来表示,单位是dB,这个参数说明了声音所携带的能量的大小,声音越大,在相同传播介质里所能传递的距离就远。
   在物理世界里,我们的声音在传输过程中都是连续,像下面这个样子:

     可是如果要让计算机来处理它,就牵扯到我们经常说的数字化了。关于声音在数字化过程中有三个核心步骤:采样、量化和编码。
   采
样:在模拟声音的时间轴上周期性地取点,将时域连续的模拟信号变成离散信号的过程就叫做采样。每秒钟的采样点越多,数字化之后的声音就越接近原模拟声音。每秒钟的采样次数就叫做采样频率,根据奈奎斯特定律,采样频率 f s 和被采样声音的最高频率 f max 的关系如下:


fs≥2fmax

    PS:有些地方把声音的频谱范围也叫做声音的带宽,指的是声音从最低频率到最高频率之间的宽度。
    量化:用于表示在采样点所获取的声音能量值。量化就是将空域连续的模拟信号转换成离散信号的过程。量化精度越高,所能表示的声音采样范围就越大,量化误差就也越小,相应地,所占用的存储空间也就越大。简而言之,就是对于采样所得到的样本点,我们打算用几位二进制数来表示它。例如,如果是8bit的量化精度,那么我们最多能表示的采样点就只有256个;如果是16bit,最多能表示的采样点就可以多达65536个。

    编码:对于经过采样量化后的数据按一定的算法进行编码处理。在计算机里最接近模拟声音的编码方式就是PCM脉冲编码方式。那么对于上述量化结果,我们发现这段音频采样点的量化空间最多也就是11个,我们用4bit就可以完全表示它们了。所以量化精度就是4bit,可表示的样本空间是[0~15],因此,上述编码序列就是{3,5,6,7,8,5,4,8,10,8,5,1,1,2,5}。

 
   当然,真正到了量化阶段时又分均匀量化和非均匀量化,量化的同时就自动编码成PCM格式的数据了。通常意义来说,量化和编码都是同时进行的。
   ITU-T建议的G.711是最早公布的语音编码标准,它规定了A律13折线和u律15折线PCM编码的两种方案。这里就不再继续展开了,都是数学层面的东东,不纠结。中国和欧洲采用的A律13折线的PCM编码方式,北美和日本采用的是u律15折线的PCM编码方式。

 
   在计算机里我们就认为PCM就是数字音频信号的原始无损格式,其存储方式通常是.wav文件,即wav格式的音频文件就是原始的未经任何压缩处理的数字音频文件,这样的文件大部分情况下都来自于录音设备。如果你使用音频格式转换工具将mp3转成wav的话,那么很不幸的是你的这个wav并不是无损格式的文件,因为mp3格式的文件是对原始wav文件经过有损压缩后得来的,而这个过程不是可逆的,即mp3转成的wav只有原始wav的部分信息。但从人的听觉系统来说,一般人是分辨不出来其中的差别,除非用专业发烧级音响设备,再加上一双有着专业特性的耳朵,区别还是很明显的。

        

 
   例如,我们手头现在有款奥林巴斯的LS-14专业数码录音笔,我们将采样频率设为44100Hz,量化精度为16bit,采用双声道的模式进行音频录制,每秒钟所产生的数据量为44100x16x2=176400 bit,那么3分钟将会产生的声音数据约为30.28MB。显然,这个结果显然不太令人满意,接下来就有了各种音频压缩算法的出现,也就是多媒体技术术语里所说的编码器,其实就是压缩算法而已。目的只有一个:在高保真原有音质的前提下,最大限度地对数字化之后的PCM编码文件进行压缩,以降低其所占的磁盘空间。整个过程可以描述如下:


    幸运的是,现在PCM编码方式已经固化在很多音频设备的DSP芯片里了,不需要我们关心。一种编码算法一定对应一种相应的解码算法才行,不然编来有毛用。我们可以看到,整个过程中PCM编码格式充当了各种编解码器之间转换的中间桥梁,这也就是为什么我们说PCM格式的声音文是计算机里的“模拟文件”的原因了。不管是不同音频压缩格式之间的互相转换,还是最终输送给数模转换器的格式都是PCM格式。

    上面几种格式里有个flac和其他几种格式有着本质的区别,flac是无损压缩格式,和它齐名还有家喻户晓的ape格式。什么意思?无损格式的音频文件是在对原始wav文件压缩是没有删减过滤它的任何信息的情况下,完全通过算法活生生的把wav文件的体重给减了下来,而且flac和ape可以完整还原原始wav的所有信息,一个毫毛都不差。ape的压缩比高达55%。这和那些有损压缩的mp3、ogg、aac等是没法相比的,因为人家是无损的,就这么简单。有些人喜欢听CD,而另外一些人则喜欢听mp3,其实他们根本就不是一个级别的,也没有可比性的。最后,献上天王的一首单曲以飨各位看官肯花宝贵的时间听我在这里唧唧歪歪的大半天,配上森海或者AKG的耳机好好享受一下生活吧(不敢保证每个人能都听到那种感觉,毕竟人家mp3也不是盖的)。
   人生不止眼前的代码和BUG,还有耳朵与音乐。

   附件:Billie.Jean-apeBillie.Jean-mp3



222222222222222222222222222222222

   其实要说在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系统中的音频设备文件:

   我们 可以直接使用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,就可以完成对底层音频硬件的控制:
    上图向我们展示了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. #define AUDIO_DEVICE "/dev/dsp"

  10. int play_sound(char *filename,int rate,int bits){
  11.         struct stat stat_buf;
  12.         unsigned char *buf = NULL;
  13.         int result,arg,status,handler,fd;

  14.         fd = open(filename,O_RDONLY);
  15.         if(fd<0)
  16.         return -1;

  17.         if(fstat(fd,&stat_buf))
  18.         {
  19.                 close(fd);
  20.                 return -1;
  21.         }

  22.         if(!stat_buf.st_size)
  23.         {
  24.                 close(fd);
  25.                 return -1;
  26.         }

  27.         buf=malloc(stat_buf.st_size);
  28.         if(!buf){
  29.                 close(fd);
  30.                 return -1;
  31.         }

  32.         if(read(fd,buf,stat_buf.st_size)<0){
  33.                 free(buf);
  34.                 close(fd);
  35.                 return -1;
  36.         }

  37.         handler = open(AUDIO_DEVICE,O_WRONLY);
  38.         if(-== handler){
  39.                 return -1;
  40.         }

  41.         arg = rate*2;
  42.         status = ioctl(handler,SOUND_PCM_WRITE_RATE,&arg);
  43.         if(-== status)
  44.                 return -1;

  45.         arg = bits;
  46.         status = ioctl(handler,SOUND_PCM_WRITE_BITS,&arg);
  47.         if(-== status)
  48.                 return -1;

  49.         result = write(handler,buf,stat_buf.st_size);
  50.         if(-== result)
  51.                 return -1;

  52.         free(buf);
  53.         close(fd);
  54.         close(handler);
  55.         return result;

  56. }

  57. int main(int argc,char** argv){
  58.         play_sound(argv[1],atoi(argv[2]),atoi(argv[3]));
  59.         return 0;
  60. }
    因为只是演示用,所以错误判断就少了一些。另外,为了让我们的播放程序自动获得音频文件的参数,诸如采样率,量化精度等,我又提供了一个shell脚本player:

点击(此处)折叠或打开

  1. #!/bin/sh
  2. [ "$#" -eq 0 ] && {
  3. echo "Usage: $0 filename"
  4. exit
  5. }
  6. BITS=`file $1 | cut -d' ' -f9`
  7. RATE=`file $1 | cut -d' ' -f12`

  8. echo "Playing...$(file $1)"
  9. ./playsound $1 $RATE $BITS
    将上述C文件编译,然后,在命令行之./player 文件名,不出意外的话就可以听到声音了,只可惜没办法演示这个过程:
   我的系统确实可以听到,但是声音比较小,如果你在命令行执行amixer的话,应该可以看到下面的输出信息:
   我的声卡音量居然只有75%(因为我用的虚拟机),然后一句“amixer set Master 100%”命令下去,再重新播放声音,应该就很happy了。
    其实大家可能有点疑惑,不是前面介绍了半天ALSA的好处了,怎么用OSS来示范,是不是专拣软柿子捏啊。再说了,现在很多人的系统几乎都不支持OSS了,上面的代码有毛用。其实我也很不甘心,所以又重新装了CentOS6.3的虚拟系统,用ALSA的API再来播一下wav看得行不,经过N个小时的折腾,皇天不负有心人---It's OK!(新手入门,大家来找BUG吧 :) )
   内核版本2.6.32,看一下/dev目录下确实没有dsp和mixer设备文件了,取而代之的/dev/snd目录。在centos5.3里我们也见到过这个目录,但当时还只是试用阶段,现在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. #define ALSA_MAX_BUF_SIZE 65535

  10. int play_sound(char* filename,int rate,int bits,int channel,int order)
  11. {
  12.         long loops;
  13.         int rc,size,dir;
  14.         snd_pcm_t *handle;
  15.         snd_pcm_hw_params_t *params;
  16.         snd_pcm_uframes_t frames,periodsize;
  17.         snd_mixer_t *mixer;
  18.         snd_mixer_elem_t *pcm_element;

  19.         char *buffer;
  20.         unsigned int val;
  21.         FILE *fp = fopen(filename,"rb");
  22.         rc = snd_pcm_open(&handle,"default",SND_PCM_STREAM_PLAYBACK,0);

  23.         snd_pcm_hw_params_alloca(&params);
  24.         snd_pcm_hw_params_any(handle,params);
  25.         snd_pcm_hw_params_set_access(handle,params,SND_PCM_ACCESS_RW_INTERLEAVED);
  26.         switch(order){
  27.                 case 1:
  28.                         snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_S16_LE);
  29.                         break;
  30.                 case 2:
  31.                         snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_S16_BE);
  32.                         break;
  33.                 defualt:
  34.                         break;
  35.         }
  36.         snd_pcm_hw_params_set_channels(handle,params,channel);

  37.         val = rate;
  38.         snd_pcm_hw_params_set_rate_near(handle,params,&val,0);
  39.         snd_pcm_hw_params_get_buffer_size_max(params,&frames);
  40.         frames = frames < ALSA_MAX_BUF_SIZE? frames:ALSA_MAX_BUF_SIZE;
  41.         rc = snd_pcm_hw_params_set_buffer_size_near(handle,params,&frames);
  42.         snd_pcm_hw_params_get_period_size_min(params,&periodsize,NULL);
  43.         if(!periodsize){
  44.                 periodsize=size/4;
  45.         }
  46.         rc = snd_pcm_hw_params_set_period_size_near(handle,params,&periodsize,NULL);
  47.         rc = snd_pcm_hw_params(handle,params);

  48.         snd_mixer_open(&mixer,0);
  49.         snd_mixer_attach(mixer,"default");
  50.         snd_mixer_selem_register(mixer,NULL,NULL);
  51.         snd_mixer_load(mixer);
  52.         for(pcm_element = snd_mixer_first_elem(mixer);pcm_element;pcm_element=snd_mixer_elem_next(pcm_element))
  53.         {
  54.                 if(snd_mixer_elem_get_type(pcm_element)==SND_MIXER_ELEM_SIMPLE && snd_mixer_selem_is_active(pcm_element))
  55.                 {
  56.                         if(!strcmp(snd_mixer_selem_get_name(pcm_element),"Master"))
  57.                         {
  58.                                 snd_mixer_selem_set_playback_volume_range(pcm_element,0,100);
  59.                                 snd_mixer_selem_set_playback_volume_all(pcm_element,(long)100);
  60.                         }
  61.                 }
  62.         }

  63.         buffer = (char*)malloc(size);
  64.         while(1)
  65.         {
  66.                 rc = fread(buffer,1,size,fp);
  67.                 if(0== rc)
  68.                         break;
  69.                 while((rc = snd_pcm_writei(handle,buffer,size))<0)
  70.                 {
  71.                         usleep(200);
  72.                         if(-EPIPE == rc)
  73.                                 snd_pcm_prepare(handle);
  74.                         else if(> rc)
  75.                                 printf("error fomr writei\n");
  76.                 }
  77.         }
  78.         snd_pcm_drain(handle);
  79.         snd_pcm_close(handle);
  80.         free(buffer);
  81.         snd_mixer_close(mixer);
  82.         fclose(fp);
  83.         return 0;
  84. }


  85. int main(int argc,char** argv){
  86.         play_sound(argv[1],atoi(argv[2]),atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));
  87.         return 0;
  88. }
   然后将player脚本也对应修改一下:   

点击(此处)折叠或打开

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

   编译C文件时,由于我们用了alsa库,所以gcc的编译选项要加上-lasound才可以。如果播放时声音很小,可以用amixer来调节音量。如果不幸的是你系统里找不到amixer命令的话,就用yum install alsa-utils或者下载alsa源码来安装吧。
  附件是测试用的音频文件,另外,后面我会将完整支持OSS和ALSA两种架构的最终播放代码放在 github上,有需要的盆友到时候可以拿去鼓捣鼓捣,今天就先到这里吧。
  附件: news.wav

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值