背景:本人负责公司android平台的app开发,最近要开发一个语音助手类的app,类似于灵犀语音助手、虫洞语音助手等。其中有两个蓝牙耳机下的语音识别问题,比较折腾人,问题描述:1.蓝牙耳机连接下捕获蓝牙按键事件,启动语音识别;2.正常启动识别时也必须通过蓝牙耳机录入音频进行语音识别。这两个问题,测试发现灵犀语音助手都解决了,所以本人负责的这个app也必须解决。网上搜了相关的资料,基本上是凤毛麟角,因此本人在此贡献一点小发现供大家参考,如有不对的地方欢迎指正。
针对第一个问题,蓝牙耳机的按键监听,墙内墙外的资料搜遍,没有发现完美的解决方案(这里看到有人提出的解决办法:http://blog.csdn.net/kangear/article/details/40430673,感觉有点另类,而且也不适合我的app的应用场景,所以没尝试),虽然接听键(该键还有很多功能,不细说,以下都称接听键)的单按、双按没法监听,但是长按却是可以捕获到,默认情况下,已经连接到android手机的蓝牙耳机,长按接听键几秒后会系统会发出一个action=android.intent.action.VOICE_COMMAND的Intent,灵犀语音助手就是使用这个来监听长按的,既然如此,我就仿照灵犀来做吧:
1.首先,在AndroidManifest.xml中指定的一个activity (用于捕获蓝牙耳机长按事件的activity,以下以A代替之)中添加:
<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.VOICE_COMMAND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
当在连接了蓝牙耳机的情况下,长按接听键几秒,出现提示音后(请戴着蓝牙耳机按,要不听不见,一不小心就成关机了),马上松开,就会弹出一个选择启动某个app的对话框,凡是添加了以上intent-filter的activity的app都会出现对话框中,这需要引导用户选择你的app并选择始终启动你的app(注意A的launchMode,我这里建议设成singleTask),选中确定之后你的app就会被启动,如果A还没有创建,那A自然会被创建啦,如果A已经被创建了,则调用A的onNewIntent(Intent intent)方法,因此你只要在A中检查接收到的intent的action就能监听蓝牙耳机的长按事件了。
2.关于蓝牙耳机下的识别问题,本app用的语音识别sdk是讯飞的,针对这个问题讯飞有给出解决方法:
在调用语音识别引擎识别前,打开sco,关键代码:
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
识别启动并识别完成后,关闭sco:
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
按照这个方法,便可以实现音频录入。
当然你会问why,这里简单的介绍一下蓝牙耳机的两种链路,A2DP及SCO。android的api表明:A2DP是一种单向的高品质音频数据传输链路,通常用于播放立体声音乐;而SCO则是一种双向的音频数据的传输链路,该链路只支持8K及16K单声道的音频数据,只能用于普通语音的传输,若用于播放音乐那就只能呵呵了。两者的主要区别是:A2DP只能播放,默认是打开的,而SCO既能录音也能播放,默认是关闭的。既然要录音肯定要打开sco啦,因此识别前调用上面的代码就可以通过蓝牙耳机录音了,录完记得要关闭。
虽然上面的方法能够实现录音,但测试中发现一个问题:startBluetoothSco()和stopBluetoothSco()时,蓝牙耳机都会有一个提示音,如果识别本身就有提示音,那么加上蓝牙的提示音就会让人莫名其妙了,在体验上很不友好。而本人在测试灵犀的蓝牙功能时竟发现没有提示音?为了完整的复制,必须把提示音去掉,然后我又上网搜了一遍,资料真的是凤毛麟角,没什么收获。无奈中翻翻android关于蓝牙部分的api,发现打开及关闭sco还有另外一种办法,那就是android.bluetooth.BluetoothHeadset类的startVoiceRecognition(BluetoothDevice device)及stopVoiceRecognition(BluetoothDevice device),经过测试发现,通过这两个方法打开sco及关闭sco蓝牙耳机是不会有提示音,题外说一句:讯飞真会坑!!!下面列出关键代码:
//以下代码用于在已经连接蓝牙耳机的状态下获取BluetoothHeadset,监听蓝牙耳机的连接只需接收action=BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED的广播即可,此处不再赘述。
private BluetoothHeadset bluetoothHeadset;
BluetoothProfile.ServiceListener blueHeadsetListener=new BluetoothProfile.ServiceListener() {
@Override
public void onServiceDisconnected(int profile) {
Log.i("blueHeadsetListener", "onServiceDisconnected:"+profile);
if(profile==BluetoothProfile.HEADSET){
bluetoothHeadset=null;
}
}@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.i("blueHeadsetListener", "onServiceConnected:"+profile);
if(profile==BluetoothProfile.HEADSET){
bluetoothHeadset=(BluetoothHeadset) proxy;
}
}
};private void initBlueToothHeadset(){
BluetoothAdapter adapter;
if(android.os.Build.VERSION.SDK_INT<android.os.Build.VERSION_CODES.JELLY_BEAN_MR2){//android4.3之前直接用BluetoothAdapter.getDefaultAdapter()就能得到BluetoothAdapter
adapter=BluetoothAdapter.getDefaultAdapter();
}
else{
BluetoothManager bm=(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
adapter=bm.getAdapter();
}
adapter.getProfileProxy(context, blueHeadsetListener, BluetoothProfile.HEADSET);
}