PHP完成基于文本的莫斯电码生成器

本文由码农网 –
风满楼原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

最近遇到一个基于输入文本生成摩斯代码音频文件的需求。几番搜索无果之后,我决定自己编写一个生成器。

1. 音频简介

介绍

我最近遇到一个基于输入文本生成摩斯代码音频文件的需求。几番搜索无果之后,我决定自己编写一个生成器。

下载源代码 – 2.63
KB

图片 1

因为我希望通过web的方式访问我的摩斯代码音频文件,所以我决定采用PHP作为我主要的编程语言。上面的截图显示了一个开始生成莫斯代码的网页。在下载的zip文件中,包含了用于提交文本的网页以及用于生成和展现音频文件的PHP源文件。如果你想测试PHP代码,你需要将网页和相关的PHP文件复制到启用了PHP的服务器上。

对于许多人来说,莫斯代码就像一些老电影中表现的那样,就是一些“点”和“横线”的序列,或者一连串的哔哔声。显然,如果你想用计算机代码来生成莫斯代码,这样的了解是远远不够的。这篇文章将会介绍生成莫斯代码的要素,如何生成WAVE
格式的音频文件,以及如何用PHP将莫斯代码转化成音频文件。

因为我希望通过web的方式访问我的摩斯代码音频文件,所以我决定采用PHP作为我主要的编程语言。上面的截图显示了一个开始生成莫斯代码的网页。在下载的zip文件中,包含了用于提交文本的网页以及用于生成和展现音频文件的PHP源文件。如果你想测试PHP代码,你需要将网页和相关的PHP文件复制到启用了PHP的服务器上。

 

莫斯代码

莫斯代码是一种文本编码方式。它的优点是编码方便,而且用人耳就能够方便的解码。本质上,是通过音频(或者无线电频)的开和关,从而形成或短或长的音频脉冲,一般称作点(dot)和线(dash),或者用无线电术语称作“嘀”和“嗒”。用现代数字通信术语,莫斯代码是一种振幅键控(amplitude
shift keying ,ASK)。

在莫斯代码中,字符(字母,数字,标点符号和特殊符号)被编码成一个“嘀”和“嗒”的序列。所以为了把文本转化成莫斯代码,我们首先要确定如何来表示“嘀”和“嗒”。一个很显然的选择就是,用0表示“嘀”,用1表示“嗒”,或者反过来。不幸的是,莫斯代码采用的是可变长编码方案。所以我们也必须要使用一种可变长序列,或者采取一种方式,把数据打包成一种计算机内存通用的固定位宽(fixed
bit-size)的格式。另外,需要特别注意的是,莫斯代码并不区分字母大小写,而且对一些特殊符号无法编码。在我们这个实现中,未定义的字符和符号将会被忽略。

在这个项目中,内存占用并不是一个需要特别考虑的问题。所以,我们提出一个简单的编码方案,即用“0”来表示每个“嘀”,用“1”来表示每个“嗒”,并且把他们放在一个字符串关联数组中。定义莫斯代码编码表的PHP代码就像下面这样:

$CWCODE = array ('A'=>'01','B'=>'1000','C'=>'1010','D'=>'100','E'=>'0',
     'F'=>'0010','G'=>'110','H'=>'0000','I'=>'00','J'=>'0111',
     'K'=>'101','L'=>'0100','M'=>'11','N'=>'10', 'O'=>'111',
     'P'=>'0110','Q'=>'1101','R'=>'010','S'=>'000','T'=>'1',
     'U'=>'001','V'=>'0001','W'=>'011','X'=>'1001','Y'=>'1011',
     'Z'=>'1100', '0'=>'11111','1'=>'01111','2'=>'00111',
     '3'=>'00011','4'=>'00001','5'=>'00000','6'=>'10000',
     '7'=>'11000','8'=>'11100','9'=>'11110','.'=>'010101',
     ','=>'110011','/'=>'10010','-'=>'10001','~'=>'01010',
     '?'=>'001100','@'=>'00101');

需要注意的是,如果你特别在意内存占用的话,上面的代码可以解释为位(bit)。给每个代码增加一个开始位,就可以形成一个位的模式,每个字符就可以用一个字节来储存。同时,当解析最终编码的时候,要删除开始位左边的位(bit),从而获得真正的变长编码。

尽管许多人没有意识到,事实上“时间间隔”是定义莫斯代码的主要因素,所以理解这一点是生成莫斯代码的关键。所以,我们要做的第一件事,就是定义莫斯代码的内部码(即“嘀”和“嗒”)的时间间隔。为了方便起见,我们定义一个“嘀”的声音长度为一个时间单位dt,“嘀”和“嗒”之间的间隔也是一个时间单位dt;定义一个“嗒”的长度为3个dt,字符(letters)之间的间隔也是3个dt;定义单词(words)之间的间隔是7个dt。所以,总结起来,我们的时间间隔表就像下面这样:

项目

时间长度

dt

“嘀”/“嗒”之间的间隔

dt

“嗒”

3*dt

字符之间的间隔

3*dt

单词之间的间隔

7*dt

在莫斯代码中,编码声音的“播放速度”通常用 单词数/分钟(WPM)
来表示。由于英文单词有不同的长度,而且字符也有不同数量的“嘀”和“嗒”,所以,从WPM转化成(音频)数字采样并不是看上去那样简单。在一份被国际组织采用的方案中,采用5个字符作为单词的平均长度,同时,一个数字或标点符号被当做2个字符。这样,平均一个单词就是50个时间单位dt。这样,如果你指定了WPM,那么我们总的播放时间就是
50 *
WPM的时间单位/分钟,每个“嘀”(即一个时间单位dt)的长度等于1.2/WPM秒。这样,给出一个“嘀”的时间长度,其他元素的时间长度很容易就能够计算出来。

你可能已经注意到,在上面显示的网页中,对于低于15WPM的选项,我们使用了“Farnsworth
spacing”。那么这个“Farnsworth spacing”又是个什么鬼?

当报务员学习用耳朵来解码莫斯代码的时候,他就会意识到,当播放速度变化的时候,字符出现的节奏也会跟着变化。当播放速度低于10WPM的时候,他能够从容的识别“嘀”和“嗒”,并且知道发送的哪个字符。但是当播放速度超过10WPM的时候,报务员的识别就会出错,他识别出来的字符会多于实际的“嘀”和“嗒”。当一个学习的时候习惯低速莫斯代码的人,在处理高速播放代码的时候,就会出现问题。因为节奏变了,他潜意识的识别就会出错。

为了解决这个问题,“Farnsworth
spacing”就被发明出来了。本质上来讲,字母和符号的播放速度依然采取高于15WPM的速度,同时,通过在字符之间插入更多的空格,来使整体的播放速度降低。这样,报务员就能够以一个合理的速度和节奏来识别每个字符,一旦所有的字符都学习完毕,就可以增加速度,而接收员只需要加快识别字符的速度就可以了。本质上来说,“Farnsworth
spacing”这个技巧解决了节奏变化这个问题,使接收员能够快速学习。

所以,在整个系统中,对于更低的播放速度,都统一成15WPM。相对应的,一个“嘀”的长度是0.08秒,但是字符之间和单词之间的间隔就不再是3个dit或者7个dit,而是进行的调整以适应整体速度。

对于许多人来说,莫斯代码就像一些老电影中表现的那样,就是一些“点”和“横线”的序列,或者一连串的哔哔声。显然,如果你想用计算机代码来生成莫斯代码,这样的了解是远远不够的。这篇文章将会介绍生成莫斯代码的要素,如何生成WAVE
格式的音频文件,以及如何用PHP将莫斯代码转化成音频文件。

经常见到这样的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等.

生成声音

在PHP代码中,一个字符(即前面数组的索引)代表一组由“嘀”、“嗒”和空白间隔组成的莫斯声音。我们用数字采样来组成音频序列,并且将其写入到文件中,同时加上适当的头信息来将其定义成WAVE格式。

生成声音的代码其实相当简单,你可以在项目中PHP文件中找到它们。我发现定义一个“数字振荡器”相当方便。每调用一次osc(),它就会返回一个从正玄波产生的定时采样。运用声音采样和声频规范,生成WAVE格式的音频已经足够了。在产生的正玄波中的-1到+1之间是被移动和调整过的,这样声音的字节数据可以用0到255来表示,同时128表示零振幅。

同时,在生成声音方面我们还要考虑另外一个问题。一般来讲,我们是通过正玄波的开关来生成莫斯代码。但是你直接这样来做的话,就会发现你生成的信号会占用非常大的带宽。所以,通常无线电设备会对其加以修正,以减少带宽占用。

在我们的项目中,也会做这样的修正,只不过是用数字的方式。既然我们已经知道了一个最小声音样本“嘀”的时间长度,那么,可以证明,最小带宽的声幅发生在长度等于“嘀”的正玄波半周期。事实上,我们使用低通滤波器(low
pass
filter)来过滤音频信号也能达到同样的效果。不过,既然我们已经知道所有的信号字符,我们直接简单的过滤一下每一个字符信号就可以了。

生成“嘀”、“嗒”和空白信号的PHP代码就像下面这样:

while ($dt < $DitTime) {
  $x = Osc();
  if ($dt < (0.5*$DitTime)) {
    // Generate the rising part of a dit and dah up to half the dit-time
    $x = $x*sin((M_PI/2.0)*$dt/(0.5*$DitTime));
    $ditstr .= chr(floor(120*$x+128));
    $dahstr .= chr(floor(120*$x+128));
    }
  else if ($dt > (0.5*$DitTime)) {
    // For a dah, the second part of the dit-time is constant amplitude
    $dahstr .= chr(floor(120*$x+128));
    // For a dit, the second half decays with a sine shape
    $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime));
    $ditstr .= chr(floor(120*$x+128));
    }
  else {
    $ditstr .= chr(floor(120*$x+128));
    $dahstr .= chr(floor(120*$x+128));
    }
  // a space has an amplitude of 0 shifted to 128
  $spcstr .= chr(128);
  $dt += $sampleDT;
  }
// At this point the dit sound has been generated
// For another dit-time unit the dah sound has a constant amplitude
$dt = 0;
while ($dt < $DitTime) {
  $x = Osc();
  $dahstr .= chr(floor(120*$x+128));
  $dt += $sampleDT;
  }
// Finally during the 3rd dit-time, the dah sound must be completed
// and decay during the final half dit-time
$dt = 0;
while ($dt < $DitTime) {
  $x = Osc();
  if ($dt > (0.5*$DitTime)) {
    $x = $x*sin((M_PI/2.0)*($DitTime-$dt)/(0.5*$DitTime));
    $dahstr .= chr(floor(120*$x+128));
    }
  else {
    $dahstr .= chr(floor(120*$x+128));
    }
  $dt += $sampleDT;
  }

莫斯代码

44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16
位(2字节)记录, 双声道(立体声);

WAVE格式的文件

WAVE是一种通用的音频格式。从最简单的形式来看,WAVE文件通过在头部包含一个整数序列来表示指定采样率的音频振幅。关于WAVE文件的详细信息请查看这里Audio
File Format Specifications
website。对于产生莫斯代码,我们并不需要用到WAVE格式的所有参数选项,仅仅需要一个8位的单声道就可以了,所以,so
easy。需要注意的是,多字节数据需要采用低位优先(little-endian)的字节顺序。WAVE文件使用一种由叫做“块(chunks)”的记录组成的RIFF格式。

WAVE文件由一个ASCII标识符RIFF开始,紧跟着一个4字节的“块”,然后是一个包含ASCII字符WAVE的头信息,最后是定义格式的数据和声音数据。

在我们的程序中,第一个“块”包含了一个格式说明符,它由ASCII字符fmt和一个4倍字节的“块”。在这里,由于我使用的是普通脉冲编码调制(plain
vanilla
PCM)格式,所以每个“块”都是16字节。然后,我们还需要这些数据:声道数、声音采样/秒、平均字节/秒、一个区块(block)对齐指示器、位(bit)/声音采样。另外,由于我们不需要高质量立体声,我们只采用单声道,我们使用 11050采样/秒(标准的CD质量音频的采样率是 44200采样/秒)的采样率来生成声音,并且用8位(bit)保存。

最后,真实的音频数据储存在接下来的“块”中。其中包含ASCII字符data,一个4字节的“块”,最后是由字节序列(因为我们采用的是8位(bit)/采样)组成的真实音频数据。

在程序中,由8位音频振幅序列组成的声音保存在变量$soundstr中。一旦音频数据生成完毕,就可以计算出所有的“块”大小,然后就可以把它们合并在一起写入磁盘文件中。下面的代码展示了如何生成头信息和音频“块”。需要注意的是,$riffstr表示RIFF头,$fmtstr表示“块”格式,$soundstr表示音频数据“块”。

$riffstr = 'RIFF'.$NSizeStr.'WAVE';
$x = SAMPLERATE;
$SampRateStr = '';
for ($i=0; $i<4; $i++) {
  $SampRateStr .= chr($x % 256);
  $x = floor($x/256);
  }
$fmtstr = 'fmt '.chr(16).chr(0).chr(0).chr(0).chr(1).chr(0).chr(1).chr(0)
          .$SampRateStr.$SampRateStr.chr(1).chr(0).chr(8).chr(0);
$x = $n;
$NSampStr = '';
for ($i=0; $i<4; $i++) {
  $NSampStr .= chr($x % 256);
  $x = floor($x/256);
  }
$soundstr = 'data'.$NSampStr.$soundstr;

莫斯代码是一种文本编码方式。它的优点是编码方便,而且用人耳就能够方便的解码。本质上,是通过音频的开和关,从而形成或短或长的音频脉冲,一般称作点,或者用无线电术语称作“嘀”和“嗒”。用现代数字通信术语,莫斯代码是一种振幅键控(amplitude
shift keying ,ASK)。

22050HZ 8bit  mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1字节)记录,
单声道;

总结和评论

我们的文本莫斯代码生成器目前看起来还不错。当然,我们还可以对它做很多的修改和完善,比如使用其他字符集、直接从文件中读取文本、生成压缩音频等等。因为我们这个项目的目的是使其能够在网络上方便的使用,所以我们这个简单的方案,已经达到我们的目的了。

当然,一如既往的,希望大家对这些简单粗暴的代码提出建议。这些年来虽然一直有人在教我,但我还是缺乏莫斯代码相关背景知识,所以,如果出现任何的错误或遗漏都算是我的错。

在莫斯代码中,字符被编码成一个“嘀”和“嗒”的序列。所以为了把文本转化成莫斯代码,我们首先要确定如何来表示“嘀”和“嗒”。一个很显然的选择就是,用0表示“嘀”,用1表示“嗒”,或者反过来。不幸的是,莫斯代码采用的是可变长编码方案。所以我们也必须要使用一种可变长序列,或者采取一种方式,把数据打包成一种计算机内存通用的固定位宽的格式。另外,需要特别注意的是,莫斯代码并不区分字母大小写,而且对一些特殊符号无法编码。在我们这个实现中,未定义的字符和符号将会被忽略。

 

在这个项目中,内存占用并不是一个需要特别考虑的问题。所以,我们提出一个简单的编码方案,即用“0”来表示每个“嘀”,用“1”来表示每个“嗒”,并且把他们放在一个字符串关联数组中。定义莫斯代码编码表的PHP代码就像下面这样:

当然也可以有 16bit 的单声道或 8bit 的立体声, 等等。

$CWCODE = array ('A'=>'01','B'=>'1000','C'=>'1010','D'=>'100','E'=>'0', 'F'=>'0010','G'=>'110','H'=>'0000','I'=>'00','J'=>'0111', 'K'=>'101','L'=>'0100','M'=>'11','N'=>'10', 'O'=>'111', 'P'=>'0110','Q'=>'1101','R'=>'010','S'=>'000','T'=>'1', 'U'=>'001','V'=>'0001','W'=>'011','X'=>'1001','Y'=>'1011', 'Z'=>'1100', '0'=>'11111','1'=>'01111','2'=>'00111', '3'=>'00011','4'=>'00001','5'=>'00000','6'=>'10000', '7'=>'11000','8'=>'11100','9'=>'11110','.'=>'010101', ','=>'110011','/'=>'10010','-'=>'10001','~'=>'01010', '?'=>'001100','@'=>'00101');

 

需要注意的是,如果你特别在意内存占用的话,上面的代码可以解释为位。给每个代码增加一个开始位,就可以形成一个位的模式,每个字符就可以用一个字节来储存。同时,当解析最终编码的时候,要删除开始位左边的位,从而获得真正的变长编码。

采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。

尽管许多人没有意识到,事实上“时间间隔”是定义莫斯代码的主要因素,所以理解这一点是生成莫斯代码的关键。所以,我们要做的第一件事,就是定义莫斯代码的内部码的时间间隔。为了方便起见,我们定义一个“嘀”的声音长度为一个时间单位dt,“嘀”和“嗒”之间的间隔也是一个时间单位dt;定义一个“嗒”的长度为3个dt,字符之间的间隔也是3个dt;定义单词之间的间隔是7个dt。所以,总结起来,我们的时间间隔表就像下面这样:

 

在莫斯代码中,编码声音的“播放速度”通常用 单词数/分钟
来表示。由于英文单词有不同的长度,而且字符也有不同数量的“嘀”和“嗒”,所以,从WPM转化成数字采样并不是看上去那样简单。在一份被国际组织采用的方案中,采用5个字符作为单词的平均长度,同时,一个数字或标点符号被当做2个字符。这样,平均一个单词就是50个时间单位dt。这样,如果你指定了WPM,那么我们总的播放时间就是
50 *
WPM的时间单位/分钟,每个“嘀”的长度等于1.2/WPM秒。这样,给出一个“嘀”的时间长度,其他元素的时间长度很容易就能够计算出来。

对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);

你可能已经注意到,在上面显示的网页中,对于低于15WPM的选项,我们使用了“Farnsworth
spacing”。那么这个“Farnsworth spacing”又是个什么鬼?

而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位(左声道)和低八位(右声道)分别代表两个声道。

当报务员学习用耳朵来解码莫斯代码的时候,他就会意识到,当播放速度变化的时候,字符出现的节奏也会跟着变化。当播放速度低于10WPM的时候,他能够从容的识别“嘀”和“嗒”,并且知道发送的哪个字符。但是当播放速度超过10WPM的时候,报务员的识别就会出错,他识别出来的字符会多于实际的“嘀”和“嗒”。当一个学习的时候习惯低速莫斯代码的人,在处理高速播放代码的时候,就会出现问题。因为节奏变了,他潜意识的识别就会出错。

 

为了解决这个问题,“Farnsworth
spacing”就被发明出来了。本质上来讲,字母和符号的播放速度依然采取高于15WPM的速度,同时,通过在字符之间插入更多的空格,来使整体的播放速度降低。这样,报务员就能够以一个合理的速度和节奏来识别每个字符,一旦所有的字符都学习完毕,就可以增加速度,而接收员只需要加快识别字符的速度就可以了。本质上来说,“Farnsworth
spacing”这个技巧解决了节奏变化这个问题,使接收员能够快速学习。

人对频率的识别范围是 20HZ – 20000HZ, 如果每秒钟能对声音做 20000 个采样,
回放时就足可以满足人耳的需求. 所以 22050 的采样频率是常用的,
44100已是CD音质, 超过48000的采样对人耳已经没有意义。这和电影的每秒 24
帧图片的道理差不多。

所以,在整个系统中,对于更低的播放速度,都统一成15WPM。相对应的,一个“嘀”的长度是0.08秒,但是字符之间和单词之间的间隔就不再是3个dit或者7个dit,而是进行的调整以适应整体速度。

 

生成声音

每个采样数据记录的是振幅, 采样精度取决于储存空间的大小:

在PHP代码中,一个字符代表一组由“嘀”、“嗒”和空白间隔组成的莫斯声音。我们用数字采样来组成音频序列,并且将其写入到文件中,同时加上适当的头信息来将其定义成WAVE格式。

1 字节(也就是8bit) 只能记录 256 个数, 也就是只能将振幅划分成 256 个等级;

生成声音的代码其实相当简单,你可以在项目中PHP文件中找到它们。我发现定义一个“数字振荡器”相当方便。每调用一次osc(),它就会返回一个从正玄波产生的定时采样。运用声音采样和声频规范,生成WAVE格式的音频已经足够了。在产生的正玄波中的-1到+1之间是被移动和调整过的,这样声音的字节数据可以用0到255来表示,同时128表示零振幅。

2 字节(也就是16bit) 可以细到 65536 个数, 这已是 CD 标准了;

同时,在生成声音方面我们还要考虑另外一个问题。一般来讲,我们是通过正玄波的开关来生成莫斯代码。但是你直接这样来做的话,就会发现你生成的信号会占用非常大的带宽。所以,通常无线电设备会对其加以修正,以减少带宽占用。

4 字节(也就是32bit) 能把振幅细分到 4294967296 个等级, 实在是没必要了.

在我们的项目中,也会做这样的修正,只不过是用数字的方式。既然我们已经知道了一个最小声音样本“嘀”的时间长度,那么,可以证明,最小带宽的声幅发生在长度等于“嘀”的正玄波半周期。事实上,我们使用低通滤波器来过滤音频信号也能达到同样的效果。不过,既然我们已经知道所有的信号字符,我们直接简单的过滤一下每一个字符信号就可以了。

如果是双声道(stereo), 采样就是双份的, 文件也差不多要大一倍.

生成“嘀”、“嗒”和空白信号的PHP代码就像下面这样:

 

while  { $x = Osc(); if  { // Generate the rising part of a dit and dah up to half the dit-time $x = $x*sin*$dt/; $ditstr .= chr; $dahstr .= chr; } else if  { // For a dah, the second part of the dit-time is constant amplitude $dahstr .= chr; // For a dit, the second half decays with a sine shape $x = $x*sin*/; $ditstr .= chr; } else { $ditstr .= chr; $dahstr .= chr; } // a space has an amplitude of 0 shifted to 128 $spcstr .= chr; $dt += $sampleDT; }// At this point the dit sound has been generated// For another dit-time unit the dah sound has a constant amplitude$dt = 0;while  { $x = Osc(); $dahstr .= chr; $dt += $sampleDT; }// Finally during the 3rd dit-time, the dah sound must be completed// and decay during the final half dit-time$dt = 0;while  { $x = Osc(); if  { $x = $x*sin*/; $dahstr .= chr; } else { $dahstr .= chr; } $dt += $sampleDT; }

这样我们就可以根据一个 wav 文件的大小、采样频率和采样大小估算出一个 wav
文件的播放长度。

WAVE格式的文件

 

WAVE是一种通用的音频格式。从最简单的形式来看,WAVE文件通过在头部包含一个整数序列来表示指定采样率的音频振幅。关于WAVE文件的详细信息请查看这里Audio
File Format Specifications
website。对于产生莫斯代码,我们并不需要用到WAVE格式的所有参数选项,仅仅需要一个8位的单声道就可以了,所以,so
easy。需要注意的是,多字节数据需要采用低位优先的字节顺序。WAVE文件使用一种由叫做“块”的记录组成的RIFF格式。

譬如 “Windows XP 启动.wav” 的文件长度是 424,644 字节, 它是 “22050HZ /
16bit / 立体声” 格式(这可以从其 “属性->摘要” 里看到),

WAVE文件由一个ASCII标识符RIFF开始,紧跟着一个4字节的“块”,然后是一个包含ASCII字符WAVE的头信息,最后是定义格式的数据和声音数据。

那么它的每秒的传输速率(位速, 也叫比特率、取样率)是 22050*16*2 =
705600(bit/s), 换算成字节单位就是 705600/8 = 88200(字节/秒), 
播放时间:424644(总字节数) / 88200(每秒字节数) ≈ 4.8145578(秒)。

在我们的程序中,第一个“块”包含了一个格式说明符,它由ASCII字符fmt和一个4倍字节的“块”。在这里,由于我使用的是普通脉冲编码调制格式,所以每个“块”都是16字节。然后,我们还需要这些数据:声道数、声音采样/秒、平均字节/秒、一个区块/声音采样。另外,由于我们不需要高质量立体声,我们只采用单声道,我们使用
11050采样/秒(标准的CD质量音频的采样率是
44200采样/秒)的采样率来生成声音,并且用8位保存。

但是这还不够精确, 包装标准的 PCM 格式的 WAVE 文件(*.wav)中至少带有 42
个字节的头信息, 在计算播放时间时应该将其去掉, 
所以就有:(424644-42) / (22050*16*2/8) ≈ 4.8140816(秒).
这样就比较精确了.

最后,真实的音频数据储存在接下来的“块”中。其中包含ASCII字符data,一个4字节的“块”,最后是由字节序列组成的真实音频数据。

 

在程序中,由8位音频振幅序列组成的声音保存在变量$soundstr中。一旦音频数据生成完毕,就可以计算出所有的“块”大小,然后就可以把它们合并在一起写入磁盘文件中。下面的代码展示了如何生成头信息和音频“块”。需要注意的是,$riffstr表示RIFF头,$fmtstr表示“块”格式,$soundstr表示音频数据“块”。

关于声音文件还有一个概念: “位速”, 也有叫做比特率、取样率,
譬如上面文件的位速是 705.6kbps 或 705600bps, 其中的 b 是 bit, ps
是每秒的意思;

$riffstr = 'RIFF'.$NSizeStr.'WAVE';$x = SAMPLERATE;$SampRateStr = '';for  { $SampRateStr .= chr; $x = floor; }$fmtstr = 'fmt '.chr.chr.chr .$SampRateStr.$SampRateStr.chr.chr;$x = $n;$NSampStr = '';for  { $NSampStr .= chr; $x = floor; }$soundstr = 'data'.$NSampStr.$soundstr;

 

总结和评论

压缩的音频文件常常用位速来表示, 譬如达到 CD 音质的 MP3 是: 128kbps /
44100HZ.

我们的文本莫斯代码生成器目前看起来还不错。当然,我们还可以对它做很多的修改和完善,比如使用其他字符集、直接从文件中读取文本、生成压缩音频等等。因为我们这个项目的目的是使其能够在网络上方便的使用,所以我们这个简单的方案,已经达到我们的目的了。

 

当然,一如既往的,希望大家对这些简单粗暴的代码提出建议。

2. wave文件格式

 

2.1 概述

 

WAVE文件是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform
Audio),由于其扩展名为”*.wav”。

 

WAVE是录音时用的标准的WINDOWS文件格式,文件的扩展名为“WAV”,数据本身的格式为PCM或压缩型。

WAV文件格式是一种由微软和IBM联合开发的用于音频数字存储的标准,它采用RIFF文件格式结构,非常接近于AIFF和IFF格式。符合
PIFF Resource Interchange File
Format规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。

 

WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。

 

多媒体应用中使用了多种数据,包括位图、音频数据、视频数据以及外围设备控制信息等。RIFF为存储这些类型的数据提供了一种方法,RIFF文件所包含的数据类型由该文件的扩展名来标识,能以RIFF文件存储的数据包括:

音频视频交错格式数据(.AVI) 、波形格式数据(.WAV) 、位图格式数据(.RDI)
、MIDI格式数据(.RMI) 、调色板格式(.PAL) 、多媒体电影(.RMN)
、动画光标(.ANI) 、其它RIFF文件(.BND)。

 

wave文件有很多不同的压缩格式,所以,正确而详细地了解各种WAVE文件的内部结构是成功完成压缩和解压缩的基础,也是生成特有音频压缩格式文件的前提。

 

最基本的WAVE文件是PCM(脉冲编码调制)格式的,这种文件直接存储采样的声音数据没有经过任何的压缩,是声卡直接支持的数据格式,要让声卡正确播放其它被压缩的声音数据,就应该先把压缩的数据解压缩成PCM格式,然后再让声卡来播放。

 

2.2 Wave文件的内部结构

 

注:由于WAV格式源自Windows/Intel环境,因而采用Little-Endian字节顺序进行存储。

 

WAVE文件是以RIFF(Resource Interchange File Format,
“资源交互文件格式”)格式来组织内部结构的。

 

RIFF文件结构可以看作是树状结构,其基本构成是称为”块”(Chunk)的单元,最顶端是一个“RIFF”块,下面的每个块有“类型块标识(可选)”、“标志符”、“数据大小”及“数据”等项所组成。块的结构如表1所示:

名称

Size

备注

块标志符

4

4个小写字符(如 "fmt ", "fact", "data" 等)

数据大小

4

DWORD类型,表示后接数据的大小(N Bytes)

数据

N

本块中正式数据部分

表1:基本chunk的内部结构

 

上面说到的“类型块标识”只在部分chunk中用到,如 “WAVE”
chunk中,这时表示下面嵌套有别的chunk。

 

当使用了 “类型块标识”
时,该chunk就没有别的项(如块标志符,数据大小等),它只作为文件读取时的一个标识。先找到这个“类型块标识”,再以它为起点读取它下面嵌套的其它chunk。

 

每个文件最前端写入的是RIFF块,每个文件只有一个RIFF块。从
Wave文件格式详细说明 中可以看到这一点。

发表评论

电子邮件地址不会被公开。 必填项已用*标注