关于安卓音频的经验

谨以此文总结我在苏州的一个月,希望看到的人能够不再踩这些坑。项目背景是一个通过耳机接口和手机相连的通信设备,编程联调。通信原理就是播放音乐和录音,通过音频波形来负载信号,当然这种声音基本没法听。之所以采用这种通信方式,是因为手机接收信息的方式有限,数据线接口不一,而唯一比较通用的就是耳机口和蓝牙,蓝牙的功耗比较大,对小设备不适合,所以采用了耳机口。

初到苏州,我的任务是解码,设备会以曼彻斯特编码发送数据,在手机端以一定的采样率录音就是一串点,如果在excel中用折线连起来就是一个波形,一个波形的上升沿表示bit0,下降沿表示bit1,当然每个bit还必须有一定的宽度。这个大约用了我几天的时间,主要是开始了解需求,以及对各种波形的熟悉过程。不同的手机的波形差别很大,有的噪音比较大,还需要做一些去噪处理。我想通信领域的朋友或许对这个了解的更深吧,包括纠错机制什么的,我都不太了解,只是用最简单的区间统计来找bit,这个方法目前来看还算稳定。

接着是手机发送信号的时序问题,之前约定手机端先发送几个脉冲信号激活设备,然后才开始发送命令,但是手机端的信号用示波器看,经常不是很准确,比较头疼,主要是安卓手机的上层用java,对底层的控制力太弱了,后来采用的解决办法就是脉冲和命令放在一个音频数据块里发,中间的间隔就发送0电平信号,当然硬件那边也要配合,这样就初步解决了时序问题。

手机端接受数据,就是从耳机口录音,但是打开录音设备似乎有延迟(我对安卓不懂,并且之前代码是别人写的),所以采用的方式就是提前打开录音,用另一个线程不停地去抓录音数据,抓过来就扔掉,直到有通信过程,才开始真正的解析数据。要控制好时间段,避免抓取到太多的无用数据,这是通过控制每次通信的信息量来保证的,例如一次传输不超过100 byte。

设备想通过耳机口和手机通信,最重要的就是要模拟一个耳机,好比你拿个火柴棍插耳机口里,手机是不会和火柴棍通信的。这包括两点:

第一,设备要能像普通耳机一样,这样播放音频数据的时候,手机才会把信号传递到耳机口,这其实是一些电路方面的知识,我也不太懂,只是听说手机检测耳机的三种方式是:电压、电流、弹簧片。反正就是在耳机插头要连接上合适的电阻和电容,这个都由硬件工程师解决了。

第二,设备要模拟一个带有MIC的耳机,而不仅仅是普通耳机,这样录音的时候,才会从耳机口录音,否则会用手机本身的MIC录,不走耳机口了。这个问题到最后了才发现,所以解决的不是很完美,耳机的MIC分为国内和国外两种制式,一般国外的偏多一些,有的手机似乎内部可以自动识别耳机的模式,有的则是固定的,为了适应不同手机,我们的设备做了自动切换模式,但是需要用户协作,不是全自动的。


接下来就是安卓的各种坑了,准确的说,是安卓的audio相关的坑,真是坑爹啊。

1 如果查看安卓文档,你会发现写的挺全,但是一没有示例,二没有详细解释,对于一些常量,点进去,只有一个数值,对于我这种对音频没了解过的人,只能是边猜边试,并且很多api你觉得似乎对你有用,但是往往都加了deprecated标记,无奈。

2 播放音乐数据使用的AudioTrack,文档大致意思如下:分为stream和static两种方式,static会预先写入native层,对于需要反复播放又较短的数据用static,对于较大数据用stream模式。我猜大部分做应用的人都是使用了stream模式,并且只是播放音乐,即使失真一点用户也听不出什么,可是对于通信来说一点失真都不行。

2.1 static模式发送两次信号的问题
如果用static模式,那么大致的用法是
track.write...
track.reloadStaticData...
track.play...
但是第一次write的时候,不能调用reload,否则第一次会发送两个信号,所以必须设定个标记位,判断一下这个track是否是第一次write,是不是很坑爹啊?而你看reloadStaticData的文档说明,只是告诉你这个函数是rewind到数据的开始。

2.2 static模式和stream模式发送的信号不一致
从安卓顶层到耳机口真的是包了一层又一层,我没法刨根到底,但是我只能说,从示波器上看,某些手机上两种模式发送相同数据,显示的波形差异很大,所以需要上层针对不同手机型号做适配,不知道是哪一层,对数据做了修改。

2.3 static模式跑一段时间,出现“TrackBase::getBuffer out of range”的错误,以及各种诡异现象
如果你在logCat里面看到了这个错误信息像洪水一样爆发出来,永无止境,那么恭喜你,你也踩到了这个大坑,根据我查到的各种资料,这个问题是安卓底层的一个bug,至今无人解决,具体有几个链接比较重要:
上面的解释大概就是安卓底层把共享内存给写废了,没用多久就溢出。
如果我可以只使用stream该多好啊,那就可以绕过这个大坑,可惜因为2.2提到的原因,绕不过去,最后采用了一个妥协的方法,目前看暂时算是解决了问题:既然需要运行一段时间出错,那我就用几次就release,然后重新new一个新对象。

2.4 拔掉设备,手机发出连续的滴滴声(蜂鸣声,etc)
本来正常通信,只会发出一声短暂的滴(高频信号),但是测试中,模拟正常通信突然拔出设备,有时候会发出连续的滴滴声,似乎程序进入了死循环,而且声音明显和正常信号不同。经分析,是使用过static模式,再切换到stream模式造成的。只使用stream模式就不会有这个问题,这个问题多见于安卓版本4.0.4(更低版本的可能也有,不过没做大量测试)。

2.5 系统音效(杜比、米音,etc)
某些系统会“自作聪明”的替你加一些效果,改善你的听音乐质量,例如降噪、AGC、杜比音效等等,对于一个播放音乐的用户来说,声音有更强的效果确实不错,但是对于通信却是致命的,用示波器看,波形被修改的体无完肤,目前能够想到的解决办法就是提醒用户关闭这些选项,也是一份任重道远的活啊,不同手机的配置都不一样。

3 录音主要是AudioRecord这个类,这个类似乎没什么问题。唯一需要注意的是硬件必需模拟成带MIC的,否则会录到外面。

3.1 录音设备的打开关闭有延时???
这个目前还不是完全确定,因为前期代码很乱,硬件也不行,得出的结论我发现很多时候都是错误的,现在采用一直打开录音的方式,所以无论是否有延迟,都没有关系了。就是如果录音数据会存储在一个buffer里,如果你不及时取走,会出overrun的信息,不过这个没关系。

3.2 录音效果不好
这个现象也是极少的手机中会出现,经查,是波形被系统修改了,最后改成VOICE_RECOGNITION,解决了,但是并不是所有手机都支持VOICE_RECOGNITION,有的手机上来就初始化失败,据说andriod官方白皮书要求厂商对VOICE_RECOGNITION的音频不要做特殊处理,但是支持规范的厂商有多少还真不知道。

最后,发现一个手机(华为P6)的波形无法解析,修改了一下解码算法,也算是解决了。再看另一组iOS的开发人员,那叫一个爽啊,一共就几款苹果手机需要适配,而其实这几款都差不多,想想安卓的这些坑,真的坑爹啊。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值