1. 声音的本质
2. 记录震动(WAV格式、PCM编码)
3. 制作一个WAV文件
4. 验证这个WAV文件
5. 总结
在理解ALSA框架之前,可能首先需要先理解一下声音本身,以及声音是如何被记录的。
一直以来我都没有能够理解频率是如何记录的,就在上周五才恍然大悟,今天验证了我的想法,现在将这个过程记录下来。
1. 声音的本质
声音其实就是震动,耳朵感知这个震动在大脑中形成了声音。好像记得初中的时候提及到声音的几个特征:响度,频率,音色。
响度: 声音的大小。
频率: 声音的粗细。
音色: 不同频率的声音按照一定比例混在一起,形成独有的特点。比如,都是“多”这个音,钢琴和笛子听起来不一样。
由于声音的大小在播放时可以调整,在这里为了简化问题,我们忽略掉响度这个特征。那么我们只需要记录频率和音色就可以了。
下面我们来一个假象实验:
我们把注意力集中在喇叭上面的一个点,当喇叭不出声的时候,这个点没有震动,不会推动空气形成波,耳朵不会听到。
当喇叭出声的时候,喇叭上的这个点推动空气,在空气中形成波,波传到耳朵,耳朵就听到声音了。
这个波就是声音,我们需要注意的是,喇叭上的这个点,他的震动,和这个波是一模一样的。(这个点推动空气形成的波)
如果设置 x 轴是时间, y 轴是那个点相对于静止时的位移,那么当这个点震动一次,应该会形成一个 sin( x ) 的正弦图像。
声音很复杂,音色是很多频率的波混合成的,假如一个1Hz的波和一个2Hz的波,在1秒内混在一起,是什么样的呢?
是这个点在1秒内,既震动1次,又震动2次吗?不是的,应该按照波的合成,也就是能量的叠加来计算的。
1Hz 是 sin( x ) 的话, 2Hz 应该就是 sin( 2x )。因此我们直接看 sin( x ) + sin( 2x ) 的图像,应该就是这个点的震动图像。
幸好有 gnuplot 这个软件可以方便的查看数学函数图像,我们来看看 sin(x),sin(2x),以及sin(x)+sin(2x)的图像:
gnuplot> set xrange[0:6.28]
gnuplot> plot sin(x),sin(2*x),sin(x)+sin(2*x),0
设置 x 轴是 0 到 2PI,也就是一个周期,在咱们这个环境中就是 1秒, 然后画了4个图,分别为:
y=sin(x)
y=sin(2*x)
y=sin(x)+sin(2*x)
y=0
我们可以看到:
朱红色: y=0 的图像,是一条直线,也就是那个点在一秒内都是静止的,没有动。
大红色: y=sin(x)的图像,也就是1Hz的震动:那个点先向上振动到最高,然后落下来落到最低,最后又回到原点。
绿色 : y=sin(2x)的图像,也就是2Hz的振动:和1Hz差不多,无非就是振动了2次。
蓝色 : y=sin(x)+sin(2*x)的图像,当两种频率的波叠加的时候,振动变成高低不平的了。
注意:蓝色图像其实就是我们的那个点随着时间的流动,相对于禁止时候的位移。
到这个时候,一切应该很清晰了,无论声音如何复杂,我们只需要记录某一个时间点那个点相对于不动点的位移就可以了。
当我们让这个点按照记录的位置重新来过一遍的时候,一定能够像记录那个时候一样地推动空气形成和记录那个时候一样的波,也就是一样的声音。
由于计算机是离散的,无法记录连续的东西,因此我们只能隔一个很小的时间去看一下那个点在那个位置,这就叫做采样,一秒钟来看多少次就是采样率。
那个点移动的最大位置可以被分成多少份,就叫采样精度,比如 4个bits 可能表示 16个数,也就是那个点的位移可以精确到 16分之一。
某一个采样点举例:当到达最高点的时候,用8表示;当到达原点的时候,用0表示,当到达最低点的时候,用-7表示。
2. 记录震动(WAV格式、PCM编码)
到目前为止,上面的理解还有很多不知道正确与否的地方,需要我们来验证一下。
windows 的 wav 文件就是记录原始采样的数据文件。我们可以分析一下他的文件格式,看看采样数据到底是啥样的。
http://baike.baidu.com/view/8033.htm
百度百科上有一篇关于 WAV 文件的文章比较详细,介绍了WAV文件格式,下面是载录的关键数据域:
从 windows 中获取一个 wav文件 C:\WINDOWS\Media\ding.wav 用二进制方式打开,查看里面的数据:
按照格式进行分析得到如下信息:
我们看到第一个采样数据是 FFF8 和 0001,这两个数都非常接近于0,我们来看看波形图吧:
有点看不清楚,再放大一点看:
这时候可以看到刚开始的采样差不多都是0的样子,但仍然不够清楚。
干脆我们自己制作一个 WAV 文件,验证一下自己的想法对不对。
3. 制作一个WAV文件
其实到目前为止我们的疑问点还是在于采样数据是什么样子的?是有符号还是无符号的?
在后来的确认中,发现 audacity 这个软件的“导出”对话框中有一点信息:
采样数据是有符号的,那我们先按照有符号来做成数据吧。
WAV 文件是通过自己写的软件生成的:
* 自动写入WAV文件头,单声道,每秒采样22050次,每一个采样数据2bytes,也就是采样精度是 16 bits。
* 自动写入DATA头,一共分为3个部分,每个部分为固定单一频率一秒钟。
* 每一个频率都是以正弦波开始的。
那3个频率参考了维基百科中的 音高部分的 C4/D4/E4:
http://zh.wikipedia.org/wiki/音高
gcc -lm test.c 生成 a.out
./a.out 执行后,就会生成 test.wav 文件。
复制代码-
- test.c
- -------------------------------------
- #include <stdlib.h>
- #include <stdio.h>
- #include <math.h>
-
- #define PI 3.1416
- #define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ) )
-
- typedef struct {
- char RIFF[4];
- unsigned int len;
- char WAV[4];
- char fmt[4];
- unsigned int filter;
- short int liner_pcm;
- short int channels;
- unsigned int frequence;
- unsigned int Bps;
- short int align;
- short int sample_bits;
- } wav_hdr_t;
-
- typedef struct {
- char DATA[4];
- unsigned int len;
- } data_hdr_t;
-
- double pitch[] = {
- 261.63, /* C4 */
- 293.66, /* D4 */
- 329.63, /* E4 */
- };
-
- int main( int argc, char *argv[] )
- {
- wav_hdr_t wav_hdr;
- data_hdr_t data_hdr;
- short int *data;
- unsigned int sample;
- unsigned int loop;
- FILE *fp;
- size_t wsize;
- double value;
- char *filename = "test.wav";
-
- /* init wave header */
- wav_hdr.RIFF[0] = 'R';
- wav_hdr.RIFF[1] = 'I';
- wav_hdr.RIFF[2] = 'F';
- wav_hdr.RIFF[3] = 'F';
- wav_hdr.WAV[0] = 'W';
- wav_hdr.WAV[1] = 'A';
- wav_hdr.WAV[2] = 'V';
- wav_hdr.WAV[3] = 'E';
- wav_hdr.fmt[0] = 'f';
- wav_hdr.fmt[1] = 'm';
- wav_hdr.fmt[2] = 't';
- wav_hdr.fmt[3] = 0x20;
- wav_hdr.filter = 0x10;
- wav_hdr.liner_pcm = 1; /* liner PCM */
- wav_hdr.channels = 1; /* mono */
- wav_hdr.frequence = 22050;
- wav_hdr.align = 2;
- wav_hdr.sample_bits = 16;
- wav_hdr.Bps = wav_hdr.frequence * wav_hdr.channels * wav_hdr.sample_bits / 8 ; /* 44100 */
- wav_hdr.len = wav_hdr.Bps * ARRAY_SIZE( pitch ) + sizeof( data_hdr ) + sizeof( wav_hdr ) - 8 ;
-
- /* init data header */
- data_hdr.DATA[0] = 'd';
- data_hdr.DATA[1] = 'a';
- data_hdr.DATA[2] = 't';
- data_hdr.DATA[3] = 'a';
- data_hdr.len = wav_hdr.Bps * ARRAY_SIZE( pitch );
-
- /* create test.wav */
- fp = fopen( filename, "w" );
- if( !fp ){
- printf( "fopen %s failed\n", filename );
- goto fopen_failed;
- }
-
- /* malloc data for samples */
- data = malloc( wav_hdr.Bps );
- if( !data ){
- printf( "malloc failed\n" );
- goto malloc_failed;
- }
-
- /* write wav_hdar */
- wsize = fwrite( &wav_hdr, 1, sizeof( wav_hdr ), fp );
- if( wsize != sizeof( wav_hdr ) ){
- printf( "fwrite wav_hdr failed\n" );
- goto fwrite_failed;
- }
- printf( "wav_hdr %d bytes write success\n", wsize );
-
- /* write data_hdr */
- wsize = fwrite( &data_hdr, 1, sizeof( data_hdr ), fp );
- if( wsize != sizeof( data_hdr) ){
- printf( "fwrite data_hdr failed\n" );
- goto fwrite_failed;
- }
- printf( "data_hdr %d bytes write success\n", wsize );
-
- for( loop = 0; loop < ARRAY_SIZE( pitch ); loop++ ){
-
- /* create every sample */
- for( sample = 0; sample< wav_hdr.frequence; sample++ ){
-
- value = sin( ( pitch[loop] * 2.0 * PI ) * (double)sample / ( double)wav_hdr.frequence );
-
- data[ sample ] = ( short int ) ( ( double )0x7FFF * value );
-
- }
- /* write data */
- wsize = fwrite( data, 1, wav_hdr.Bps, fp );
- if( wsize != wav_hdr.Bps ){
- printf( "fwrite data failed\n" );
- goto fwrite_failed;
- }
- printf( "data %d bytes write success\n", wsize );
- }
-
- fwrite_failed:
- fclose( fp );
- malloc_failed:
- free( data );
- fopen_failed:
- return 0;
- }
|
4. 验证这个WAV文件
通过 audacity 这个软件,我们可以打开刚才制作的 WAV 文件,查看波形,以及进行播放。
test.wav 这个文件的波形如下所示:
差不多可以看到3秒的波形,以及前面部分频率低一些(不是那么密),后面部分频率高一些。
再放大看看:
我们看到从时间点0开始,是规则的正弦波图像,这个和我们的设计是一致的,看来采样数据做成应该是对的。
5. 总结
至此,我们理解了声音本身,以及验证了采样数据的波形以及播放,但我们还没有提及如何得到采样数据。
其实采样数据的做成,和采样数据的播放刚好是两个相反的过程。
人说话->振动空气->波通过空气振动麦克风->麦克风将振动变成电信号->AD转化并采样-> 采样数据。
采样数据->DSP处理还原成平滑的信号->DA转换成变化得电信号->电信号驱动喇叭振动->喇叭推动空气振动->波通过空气振动耳朵->人听到。
这个 test.wav 比较小,数据容易识别,也许可以给后续理解 ALSA 用。
[ 此帖被happyzlz在2012-12-17 21:22重新编辑 ]