刚进项目组,老大给了我篇文档,让我参考。是通过时域脉冲计数来表示开始结束标志位、0和1的,我觉得这种方法很容易受噪声的干扰,因而对声波传输这块进行了调研。
通过调研发现,声波传输大体有三种解决方案:1)频率调制;2)人耳可听见声音传输,如DTMF,国内的蛐蛐儿和国外的Chirp;3)超声传输,如AudiioModem和日本的Infosound等,此外还有数字水印技术、TagPay的NSDT技术、微软的Dhwani技术等。考虑到我们想用常用的扬声器和麦克风作为发送和接收装置,所以我选择用可听见声音作为传输载体。
微软的Dhwani采用了JamSecure技术来防止第三个设备发出同样声音造成的干扰。蛐蛐儿、Chirp和Infosound本质上都是给待传送的文件生成一个id,通过声音传送这个id并将文件上传到服务器,接收端根据解码出的id从服务器下载。其中蛐蛐儿和Chirp都是用人耳可听见的频率,而Infosound用的是超声。此外,AudioModem用的也是超声,18.4kHz~20.8kHz。
在三种调制技术中,幅度调制最简单,但也最容易受噪声干扰;频率调制需要较大的带宽;相位调制则需要相干解调。AudioMedem用的是相位调制中的一种,DBPSK,并采用Barker Code来作同步,并用前向纠错码来纠错。
蛐蛐儿和chirp都是10个字符代表1个id,都有许多个频率。蛐蛐儿用18个字符编码,chirp用20个字符编码(2个开始标志+10个数据+8个校验码);chirp用32种频率代表了32种字符,从蛐蛐儿音频的最低频率和最高频率及频率间隔来看,可能共有10种频率。由于chirp的资料较多,因而打算慢慢采用这种思路来做。这里明显可以看出国外分享精神好很多,而像国内的公司,由于盗版成风,大家发明一个东西第一时间就是申请专利,然后也不会分享,比如蛐蛐儿,我就没找到更多资料来摸索它是怎么实现。当然我也不例外,毕竟签了保密协议,就不能多说,只能说说思路及开发过程,而且这些东西要是专利申请下来,别人也是可以查到的。
4月份在了解了角度调制和DTMF后,就开始用matlab开始设计简单的发送和接收原型。原型中发送端简单的将16进制对应到16个频率,产生相应的声音,基频1760Hz,倍数间隔,采样率44100Hz,一个字符用4096个点表示的正弦信号表示,约93ms;接收端处理帧长为512,对每一帧做fft,求得最大幅度值对应的频率,然后计算其与基频的距离来解码字符。这个原型中没有考虑校验码。4月15日写了初步的《声音传输数据方案》,采样率为44.1kHz,以9A为开始标志,频率范围1000Hz~8000Hz。之后考虑了噪声鲁棒性的问题,加入了RS编码,每次发送16个字符,前2个固定为9A为开始标志,中间8个为有效数据,最后6个是rs(14,8)码,能纠正3个错误。采用rs码是因为它不仅能检查错误,还能纠正错误。并将此版本改写成c++,版本为:
v1.0-beta1,将编解码都写成了类,rs码用了开源代码,并且使用了第三方库SP++中的vector和fft。
5月份继续修改,我是采用快速迭代的方法,而且每次改动我都会保留前版本来做比较。
v1.0-beta2没有多大改动,解码时用的是子帧,即解码帧长小于编码帧长,因而在解码时会有连续的子帧对应相同的频率,通过频率计数来判定某一段音频的频率;由 于声音的多径效应,这种计数方法不够准确,因而
v1.0-beta3改用统计的方法,即统计接收音频某段时间内某个频率最多的判断为这段时间的频率;
v1.0-beta4播放端每个字符对应的频率改为整数值,同时,在接收端不再对每一帧做fft,而是采用goertzel算法,只检测已知16种频率的幅度值,减小计算量,这一思 想来源于DTMF。
v1.1-beta1在v1.0-beta2的基础上修改了录音问题,因为发现播放字符串变长时没有播放完整。
v1.2-beta1抛弃计数和统计子帧的方法,而是帧长直接和播放端一样,这样存在对齐问题。
v1.2-beta2没太大改动,做了变量调整;
v1.2-beta3尝试了rs(15,11)编码,即校验位由6个变成4个,纠正2个错误,效果不如rs(15,9);
v1.2-beta4测试了帧长分别为512、1024、1536、2048、2560、3072的效果,帧长越长,每个频率音持续时间更长,当然更容易解码,但码率就会降低,所以需要 在准确率和码率上做折中。
v1.3-beta1尝试了32种频率代表32个字符。
v1.4-beta1调整了相邻字符的频率间隔;
v1.4-beta2对字符长度位进行了校验,并根据有效数据的长短选择rs(15,9)、rs(15,11)和rs(15,13)码;
v1.4-beta3将rs编解码单独封装,并加入固定校验位确定有效数据的正确性;
v1.4-beta4加入模式位;
v1.4-beta5扩充最长有效字符数到64;
v1.4-beta6将类改写成函数,c++改成c,以便嵌入式移植;
v1.4-beta7将向量改写成数组,避免使用c++ stl,并封装了接口;
v1.4-beta7.1没有太大改动;
v1.4-beta8又去掉了模式位;
v1.4-beta9在编解码模块里输出可直接播放的音频流,而不是数据流,这样录音和播放借口可以直接录音和播放裸音频而无需再转换;
v1.4-beta10优化了播放代码,防止播放音频丢失;
v1.4-beta11进一步优化播放,采用双缓存,并分帧检测频率;
v1.4-beta12修改频率检测算法;
v1.4-beta13调整频率检测算法;
v1.4-beta14新增输出日志;
v1.4-beta14.1修改每次解码成功或失败都退出程序;
v1.4-beta14.2优化了解码流程。
至此,5月份代码更新结束,更新了算法设计文档v1.2。并规范了编码规则:1-9个有效字符分别采用rs(3,1)、rs(4,2)、rs(5,3)、rs(8,4)、rs(9,5)、rs(10,6)、rs(13,7)、rs(14,8)和rs(15,9)编码,当字符串大于9时,将其分段编码。
进入到6月份,继续优化算法,主要是频率检测准确性的问题,因为现实应用中会存在干扰,包括背景环境声音和混响。
v1.5-beta1又修改为子帧计数的方式检测频率,但会存在由于混响而导致连续解码出两个相同频率的问题。
v1.5-beta2为过渡版本。
v1.5-beta3修改16个频率为线性增加,而不是倍数增加,对比下效果。
v1.5-beta4新增编码播放声音的同时输出wav文件。
v1.5-beta5优化了计数方法中有某一子帧检出的频率错误而导致计数错误的问题。
v1.5-beta6继续优化v1.5-beta5,改动不大。
v1.5-beta7修改了频率倍数增长比例,并在频率检测时根据前一帧结果加了权值。
v1.5-beta8优化了用speex降噪,对比降噪效果。
v1.5-beta9修改double数据为float,提高运算速度。
v1.5-beta10优化了编解码的内存使用,调整了声音频率及帧长。
v1.5-beta10.1进一步将float数据转换成int,提高运算速度。
v1.5-beta10.2只修改了解码部分,中间输出数据只在debug时输出,release时不输出。
v1.5-beta10.3同样是修改float数据为int。
v1.5-beta11修改频率,对比效果。
v1.5-beta12进一步将频率修改写成读取配置文件的形式,这样不用每次在程序中修改,无需重新编译。
v1.6-beta1开始尝试双频。
v1.6-beta2修改为将双频用到的两组频率以配置文件形式读取。
v1.6-beta3修改为将两组频率的配置文件写在一个配置文件里。
v1.6-beta4在播放的同时输出wav文件。
v1.6-beta5在编码时加入音乐。
v1.6-beta6为了提高码率,尝试不用rs纠错码会有怎样的影响,效果不好。
至此,6月份代码更新结束,并更新了《声波传输方案设计文档v1.5》。6月份最终出了单频版和双频版两个版本,单频版稳定版为v1.5-beta12,双频版无音乐稳定版为v1.6-beta2,双频版有音乐稳定版为v1.6-beta5。
由开发进度来看,在不断优化得到单频版稳定版后,扩展到双频版并不难,迭代版本也不多,很快就开发出来了。
我将版本迭代比喻为造自行车——>造汽车——>造火车——>造飞机的过程,一开始我以为只要造出自行车就行了,如果在实验室我可能就止步于此了,但在公司里,商用才是第一位,所以要继续造汽车。就在我为了造出汽车而沾沾自喜的时候,老大说还要继续升级。好吧,那就继续升级吧,就在我想继续升级为火车的时候,什么?火车?我们要造的是飞机!
人的潜力真的是可以不断挖掘的。有时我想,是不是没有科学技术解决不了的问题,即使现在解决不了,那也是技术还没到那个水平而已,而并不是没有解决方案。所以,要严格要求自己和产品,像乔布斯那样。