android蓝牙耳机下的语音(输入/识别)及按键监听

时间:2023-01-02 08:47:26

背景:本人负责公司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);
 }

        
       如果要通过蓝牙耳机的sco链路输出音频,必须要在sco打开的状态下,把streamType设置为AudioManager.STREAM_VOICE_CALL。
 
       最后有两点要注意的:
     1.测试发现,当sco打开时,第1点中提到的蓝牙耳机接听键长按将不会发送android.intent.action.VOICE_COMMAND,必须在sco关闭时长按才会有这一事件。
     2.sco打开及关闭都是比较消耗时间的,特别是打开,大概是几百毫秒-几秒的时间,请注意要在sco彻底打开时再启动语音识别,sco的状态可以通过接收action=AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED的广播监听,或者使用BluetoothHeadset的isAudioConnected(BluetoothDevice device)判断。测试发现BluetoothHeadset的相关方法打开sco比较快(个人感觉,不明真相,求大神科普)。