这篇文章是系列文章的第2篇。第一篇在这里声波配网原理。
在声波传输的发送端,我们要做的事情用下面这张图就可以概括:
也就是通过三步,我们就可以把我们要传输的文本信息变成声音发送出去。
下面我们来一步一步的看:
1.从字符到数组角标的映射:
这一步可以看成是第二步的预处理,从字符到频率映射的一个过渡。把字符对应的频率存储在一个叫FREQUENCIES的数组中:
FREQUENCIES {1760,1810,1870,......}
因为默认是支持ASCII码字符,而键盘能输入的字符其实只在32到127之间,所以把这96个字符映射到FREQUENCIES数组中角标0~95的频率。而我们在用Base64规则转码中文字符时也只需要用到这96个字符中的(0-9,A-Z,a-z,+,-,=)。所以其实用来传递信息的数据区的话,数据区域有96个频率就足够了。
关于重复标志和顺序标志的使用,在后面的文章之进一步优化-提高传输速率中会提到,暂时先忽略~~~
开始标志:需要一个信号告诉我们的接收端在什么时候可以开始解析了,同时这个标志还有一个重要的作用->对齐音节,这个作用在写解码的时候会详细的描述。
结束标志:需要一个信号告诉我们的接收端本次传输结束。
2.根据角标查找频率:
这个不用蠢作者多说了吧~数组查询O(1)~没毛病
3.把频率变成正弦波音频信号:
建立字符和频率的一一映射关系之后,如果我们想发出”单频率”的声音,就需要自己构造特定频率的正弦函数。
1)正弦波生成公式:
energy =
这个是蠢作者采用的正弦波生成公式,其中frame是采样点的位置,sample_rate是采样率(44100Hz),frequency是需要生成正弦波的频率。回忆一下高中数学,这个正弦波的周期应该是:T=sample_rate/frequency。
正弦波的频率F是:F=2*pi*frequency/sample_rate
所以解码端通过快速傅里叶变换解码出正弦波的频率F以后,该正弦波携带的信息频率应该是frequency = F*sample_rate/(2*pi)。
正弦波公式的选取可以随意调整,保证能正确的逆向解析出其中的信息频率就好。
MAX_VOLUME可以用来调节最后生产的音频信号的响度(振幅)大小,识别成功率和播放的音频的响度也有很大的关系。
2)将正弦波存成PCM数据格式:
有了energy公式,相当于有了从频域到时域的传输门,但要把时域上连续的声音信号变成我们的设备能播放的数字信号,我们还需要了解两个基本知识:
NO1.采样率(sample_rate):
是指每一秒要采集声音的次数。因为自然界的音频信号,例如人说话,产生的是模拟信号(时间连续的信号)。如果我们想把声音录制下来的话,并没有办法把整个连续的声音信号都录制下来,只能离散的采样,每隔一段时间采集一次数据,将模拟信号转化成数字信号,这样才能在计算机的数字世界中存储。很明显,采样点的多少大大的影响着录制音频记录声音信息的多少,也就是音频的质量。如果采样点多,那么质量就高,听起来就和原声的差别小;相对的,采样点少,质量就次,听起来就和原声不一样。
NO2.采样定理:
既然采样率高,录音的质量就高,那么,是不是采样率越高越好呢?
当然不是啦。
随着采样率的提高,虽然质量提高了,但是采样的难度也对应的增加了,而且,采样出来的数据需要存储,采样率越高,产生的数据文件就越大,那么在网络中传输用的时间就越长。质量高的音乐比一般的音乐体积大就是这个原因。
那么采样率到底设成多少比较好了?
有一个有名的采样定理“奈奎斯特定理”帮我们解决了这个难题:
如果采样的频率高于信号最高频率的两倍,采样之后的数字信号就可以完整的保留下原始信号中的信息。
因为人的听力范围在20HZ-20000HZ,所以一般采样频率在44.1kHZ,也就是一分钟44100次。选择44100Hz的采样率,肯定能很好的记录我们的正弦波了。
蠢作者建议,我们字符映射的范围在人耳听力的范围内比较好,因为这样一般的硬件设备才能够播放,过高或者过低,就不能兼容所有的手机机型或者其他硬件设备了。当然低频段由于受环境的噪音干扰比较大,也不太建议采用。蠢作者自己用了1700HZ到15000Hz之间的频率,宝宝们可以自己调整。不要小看这些频率的选定哈,对识别的成功率影响很大。因为高频的低频分量很可能会干扰我们正确的解码。
好啦,知道了数字世界通过采样的方式来记录声音信息,那么我们怎么把采样的结果存储下来呢?我选用了PCM文件格式来存储音频数据。
知识点:
NO1.每个音节持续时间(duration)
如果把传输一个字符的正弦波看做是一个音节,那么每个音节的持续时间同样也影响到我们对声音信号的记录和还原。对识别声波的成功率有着重要的影响。当durantion变大时,音节持续时间变成,采样点变多,声音信号的还原度变高,解析出的音频中携带的频率信息出错率变低。同时,采样点变多,PCM文件变大,音节变长,传输过程变长,传输速率降低。
蠢作者选择了0.1S的持续时间,这个宝宝们可以自己定。
NO2.每个音节的采样点unit_sample
我们已经选定了采样率和每个音节的持续时间。很自然的就可以计算出存储每个音节信息的采样点数目unit_sample = duration * sample_rate。
在发送端我们只要把每个采样点都通过energy公式计算出来,存储下来就OK了。
NO3.每个采样点的存储方式
那么每个采样点到底怎么存了?需要稍稍了解一下PCM文件格式。
每个采样数据记录的是振幅,采样精度取决于储存空间的大小:
1字节(也就是8bit)只能记录256个数,也就是只能将振幅划分成256个等级;
2字节(也就是16bit)可以细到65536个数,这已是CD标准了;
4字节(也就是32bit)能把振幅细分到4294967296个等级,好好好厉害有没有~~~~~
所以我们在发送端生成的振幅和接收端录音收到的振幅大小并不能保证完全一样,况且这个值是受播放设备和录音设备两者之间距离的影响的,后面我们分析接收端解码的时候要注意到这一点。
通常我们说的双声道(stereo),意味着采样是双份的,文件也差不多要大一倍。PCM中的声音数据是没有压缩的,如果是单声道的文件,采样数据按时间的先后顺序依次存入。如果是双声道的文件,采样数据按时间先后顺序交叉地存入。
我们采用16位单声道的PCM格式存储就足够了,8位的也太粗糙了一点。
由上述energy公式计算出的值是float的,要转成PCM文件让播放器播放,需要将float先转成16位的整数:
float frame_value_16 = MAX_SHORT * frame_value_float;
MAX_SHORT= 32767.
在接收端也需要从16位整数转回float:
float f_value = (float) tempShort[j]/32768.0;
所以大概的从频率到音频信号的代码大概是这样:
这样生成的PCM文件,我们可以直接丢给播放器播放,也可以写到本地存储,写到本地存储的应用场景是,由服务器生成一段携带文本信息的音频,然后客户端拉取之后播放。
好啦~~~~声波传输编码的过程就是这样啦~~~比较简单~~~
我要用洪荒之力写解码的过程啦~
------------
喜欢的话记得点个喜欢奥~
笔芯