【问题标题】:Using the Android RecognizerIntent with a bluetooth headset将 Android RecognizerIntent 与蓝牙耳机一起使用
【发布时间】:2013-02-20 22:29:00
【问题描述】:

我使用以下代码在 Android 中启动语音识别:

PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);

if (activities.size() == 0) {
    displayWarning("This device does not support speech recognition");
    return;
}

Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);

这很好用。但是,它似乎不接受来自使用“电话音频”配置文件配对和连接的蓝牙耳机的语音输入。

我可以使用名为SoundAbout 的应用程序将“媒体音频”强制为“蓝牙(单声道)(SCO)”。有了这个应用程序集,我的语音识别现在可以从我的耳机中获取我的语音输入。

如何使用 RecognizerIntent 并从蓝牙耳机获取语音输入?

我看到在 API 级别 16 中有一个新的意图操作 ACTION_VOICE_SEARCH_HANDS_FREE。这对我来说太新了,但是这能解决我的问题吗?

我是否必须在 AudioManager 中混混(就像我假设 SoundAbout 正在做的那样)才能使用 setBluetoothScoOn() 或 startBluetoothSco() 路由音频输入?

【问题讨论】:

    标签: android bluetooth speech-recognition


    【解决方案1】:

    清单权限

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />  
    

    ActivityService 中创建一个内部类BluetoothHelper extends BluetoothHeadSetUtils。声明一个类成员mBluetoothHelper并在onCreate()中实例化它

    BluetoothHelper mBluetoothHelper;
    
    @Override
    public void onCreate()
    { 
         mBluetoothHelper = new BluetoothHelper(this);
    }  
    
    @Override
    onResume()
    { 
        mBluetoothHelper.start();
    }
    
    @Override
    onPause()
    {
        mBluetoothHelper.stop();
    }
    
    // inner class
    // BluetoothHeadsetUtils is an abstract class that has
    // 4 abstracts methods that need to be implemented.
    private class BluetoothHelper extends BluetoothHeadSetUtils
    {
        public BluetoothHelper(Context context)
        {
            super(context);
        }
    
        @Override
        public void onScoAudioDisconnected()
        {
            // Cancel speech recognizer if desired
        }
    
        @Override
        public void onScoAudioConnected()
        {           
            // Should start speech recognition here if not already started  
        }
    
        @Override
        public void onHeadsetDisconnected()
        {
    
        }
    
        @Override
        public void onHeadsetConnected()
        {
    
        }
    }
    

    要将蓝牙耳机与 Text To Speech 一起使用,您需要在调用 speak 之前将 AudioManager 设置为 STREAM_VOICE_CALL。或者使用下面的代码

    protected void speak(String text)
    {
    
        HashMap<String, String> myHashRender = new HashMap<String, String>();
    
        if (mBluetoothHelper.isOnHeadsetSco())
        {
            myHashRender.put(TextToSpeech.Engine.KEY_PARAM_STREAM, 
                String.valueOf(AudioManager.STREAM_VOICE_CALL));
        }
        mTts.speak(text, TextToSpeech.QUEUE_FLUSH, myHashRender);
    }
    

    将 BluetoothHeadsetUtils 类复制到您的项目中。

    import java.util.List;
    
    import android.annotation.SuppressLint;
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothClass;
    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothHeadset;
    import android.bluetooth.BluetoothProfile;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.media.AudioManager;
    import android.os.Build;
    import android.os.CountDownTimer;
    import android.util.Log;
    
    /**
     * This is a utility to detect bluetooth headset connection and establish audio connection
     * for android API >= 8. This includes a work around for  API < 11 to detect already connected headset
     * before the application starts. This work around would only fails if Sco audio
     * connection is accepted but the connected device is not a headset.
     * 
     * @author Hoan Nguyen
     *
     */
    public abstract class BluetoothHeadsetUtils
    {
        private  Context mContext;
    
        private BluetoothAdapter mBluetoothAdapter;
        private BluetoothHeadset mBluetoothHeadset;
        private BluetoothDevice mConnectedHeadset;
    
        private AudioManager mAudioManager;
    
        private boolean mIsCountDownOn;
        private boolean mIsStarting;
        private boolean mIsOnHeadsetSco;
        private boolean mIsStarted;
    
        private static final String TAG = "BluetoothHeadsetUtils"; //$NON-NLS-1$
    
        /**
         * Constructor
         * @param context
         */
        public BluetoothHeadsetUtils(Context context)
        {
            mContext = context;
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        }
    
        /**
         * Call this to start BluetoothHeadsetUtils functionalities.
         * @return  The return value of startBluetooth() or startBluetooth11()
         */
        public boolean start()
        {
            if (!mIsStarted)
            {
                mIsStarted = true;
    
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
                {
                    mIsStarted = startBluetooth();
                }
                else
                {
                    mIsStarted = startBluetooth11();
                }
            }
    
            return mIsStarted;
        }
    
        /**
         * Should call this on onResume or onDestroy.
         * Unregister broadcast receivers and stop Sco audio connection
         * and cancel count down.
         */
        public void stop()
        {
            if (mIsStarted)
            {
                mIsStarted = false;
    
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
                {
                    stopBluetooth();
                }
                else
                {
                    stopBluetooth11();
                }
            }
        }
    
        /**
         * 
         * @return  true if audio is connected through headset.
         */
        public boolean isOnHeadsetSco()
        {
            return mIsOnHeadsetSco;
        }
    
        public abstract void onHeadsetDisconnected();
    
        public abstract void onHeadsetConnected();
    
        public abstract void onScoAudioDisconnected();
    
        public abstract void onScoAudioConnected();
    
        /**
         * Register for bluetooth headset connection states and Sco audio states.
         * Try to connect to bluetooth headset audio by calling startBluetoothSco(). 
         * This is a work around for API < 11 to detect if a headset is connected before 
         * the application starts.
         * 
         * The official documentation for startBluetoothSco() states
         * 
         * "This method can be used by applications wanting to send and received audio to/from 
         *  a bluetooth SCO headset while the phone is not in call."
         *  
         * Does this mean that startBluetoothSco() would fail if the connected bluetooth device
         * is not a headset?
         * 
         * Thus if a call to startBluetoothSco() is successful, i.e mBroadcastReceiver will receive
         * an ACTION_SCO_AUDIO_STATE_CHANGED with intent extra SCO_AUDIO_STATE_CONNECTED, then
         * we assume that a headset is connected.
         * 
         * @return  false if device does not support bluetooth or current platform does not supports
         *                use of SCO for off call.
         */
        @SuppressWarnings("deprecation")
        private boolean startBluetooth()
        {
            Log.d(TAG, "startBluetooth"); //$NON-NLS-1$
    
            // Device support bluetooth
            if (mBluetoothAdapter != null)
            {       
                if (mAudioManager.isBluetoothScoAvailableOffCall())
                {
                    mContext.registerReceiver(mBroadcastReceiver, 
                                            new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
                    mContext.registerReceiver(mBroadcastReceiver, 
                                            new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
                    mContext.registerReceiver(mBroadcastReceiver, 
                                            new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
    
                    // Need to set audio mode to MODE_IN_CALL for call to startBluetoothSco() to succeed.
                    mAudioManager.setMode(AudioManager.MODE_IN_CALL);
    
                    mIsCountDownOn = true;
                    // mCountDown repeatedly tries to start bluetooth Sco audio connection.
                    mCountDown.start();
    
                    // need for audio sco, see mBroadcastReceiver
                    mIsStarting = true;
    
                    return true;
                }
            }
    
            return false;
        }
    
        /**
         * Register a headset profile listener
         * @return false    if device does not support bluetooth or current platform does not supports
         *                  use of SCO for off call or error in getting profile proxy.
         */
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        private boolean startBluetooth11()
        {
            Log.d(TAG, "startBluetooth11"); //$NON-NLS-1$
    
            // Device support bluetooth
            if (mBluetoothAdapter != null)
            {
                if (mAudioManager.isBluetoothScoAvailableOffCall())
                {
                    // All the detection and audio connection are done in mHeadsetProfileListener
                    if (mBluetoothAdapter.getProfileProxy(mContext, 
                                                        mHeadsetProfileListener, 
                                                        BluetoothProfile.HEADSET))
                    {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        /**
         * API < 11
         * Unregister broadcast receivers and stop Sco audio connection
         * and cancel count down.
         */
        private void stopBluetooth()
        {
            Log.d(TAG, "stopBluetooth"); //$NON-NLS-1$
    
            if (mIsCountDownOn)
            {
                mIsCountDownOn = false;
                mCountDown.cancel();
            }
    
            // Need to stop Sco audio connection here when the app
            // change orientation or close with headset still turns on.
            mContext.unregisterReceiver(mBroadcastReceiver);
            mAudioManager.stopBluetoothSco();
            mAudioManager.setMode(AudioManager.MODE_NORMAL);
        }
    
        /**
         * API >= 11
         * Unregister broadcast receivers and stop Sco audio connection
         * and cancel count down.
         */
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        protected void stopBluetooth11()
        {
            Log.d(TAG, "stopBluetooth11"); //$NON-NLS-1$
    
            if (mIsCountDownOn)
            {
                mIsCountDownOn = false;
                mCountDown11.cancel();
            }
    
            if (mBluetoothHeadset != null)
            {
                // Need to call stopVoiceRecognition here when the app
                // change orientation or close with headset still turns on.
                mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
                mContext.unregisterReceiver(mHeadsetBroadcastReceiver);
                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
                mBluetoothHeadset = null;
            }
        }
    
        /**
         * Broadcast receiver for API < 11
         * Handle headset and Sco audio connection states.
         */
        private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver()
        {
            @SuppressWarnings({"deprecation", "synthetic-access"})
            @Override
            public void onReceive(Context context, Intent intent)
            {
                String action = intent.getAction();
    
                if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED))
                {       
                    mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    BluetoothClass bluetoothClass = mConnectedHeadset.getBluetoothClass();
                    if (bluetoothClass != null)
                    {
                        // Check if device is a headset. Besides the 2 below, are there other 
                        // device classes also qualified as headset?
                        int deviceClass = bluetoothClass.getDeviceClass();
                        if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE 
                            || deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)
                        {
                            // start bluetooth Sco audio connection.
                            // Calling startBluetoothSco() always returns faIL here, 
                            // that why a count down timer is implemented to call
                            // startBluetoothSco() in the onTick.
                            mAudioManager.setMode(AudioManager.MODE_IN_CALL);
                            mIsCountDownOn = true;
                            mCountDown.start();
    
                            // override this if you want to do other thing when the device is connected.
                            onHeadsetConnected();
                        }
                    }
    
                    Log.d(TAG, mConnectedHeadset.getName() + " connected"); //$NON-NLS-1$
                }
                else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))
                {
                    Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$
    
                    if (mIsCountDownOn)
                    {
                        mIsCountDownOn = false;
                        mCountDown.cancel();
                    }
    
                    mAudioManager.setMode(AudioManager.MODE_NORMAL);
    
                    // override this if you want to do other thing when the device is disconnected.
                    onHeadsetDisconnected();
                }
                else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED))
                {
                    int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 
                                                    AudioManager.SCO_AUDIO_STATE_ERROR);
    
                    if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED)
                    {
                        mIsOnHeadsetSco = true;
    
                        if (mIsStarting)
                        {
                            // When the device is connected before the application starts,
                            // ACTION_ACL_CONNECTED will not be received, so call onHeadsetConnected here
                            mIsStarting = false;
                            onHeadsetConnected();
                        }
    
                        if (mIsCountDownOn)
                        {
                            mIsCountDownOn = false;
                            mCountDown.cancel();
                        }
    
                        // override this if you want to do other thing when Sco audio is connected.
                        onScoAudioConnected();
    
                        Log.d(TAG, "Sco connected"); //$NON-NLS-1$
                    }
                    else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED)
                    {
                        Log.d(TAG, "Sco disconnected"); //$NON-NLS-1$
    
                        // Always receive SCO_AUDIO_STATE_DISCONNECTED on call to startBluetooth()
                        // which at that stage we do not want to do anything. Thus the if condition.
                        if (!mIsStarting)
                        {
                            mIsOnHeadsetSco = false;
    
                            // Need to call stopBluetoothSco(), otherwise startBluetoothSco()
                            // will not be successful.
                            mAudioManager.stopBluetoothSco();
    
                            // override this if you want to do other thing when Sco audio is disconnected.
                            onScoAudioDisconnected();
                        } 
                    }
                }
            }
        };
    
        /**
         * API < 11
         * Try to connect to audio headset in onTick.
         */
        private CountDownTimer mCountDown = new CountDownTimer(10000, 1000)
        {
    
            @SuppressWarnings("synthetic-access")
            @Override
            public void onTick(long millisUntilFinished)
            {
                // When this call is successful, this count down timer will be canceled.
                mAudioManager.startBluetoothSco();
    
                Log.d(TAG, "\nonTick start bluetooth Sco"); //$NON-NLS-1$
            }
    
            @SuppressWarnings("synthetic-access")
            @Override
            public void onFinish()
            {
                // Calls to startBluetoothSco() in onStick are not successful.
                // Should implement something to inform user of this failure
                mIsCountDownOn = false;
                mAudioManager.setMode(AudioManager.MODE_NORMAL);
    
                Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
            }
        };
    
        /**
         * API >= 11
         * Check for already connected headset and if so start audio connection.
         * Register for broadcast of headset and Sco audio connection states.
         */
        private BluetoothProfile.ServiceListener mHeadsetProfileListener = new BluetoothProfile.ServiceListener()
        {
    
            /**
             * This method is never called, even when we closeProfileProxy on onPause.
             * When or will it ever be called???
             */
            @Override
            public void onServiceDisconnected(int profile)
            {
                Log.d(TAG, "Profile listener onServiceDisconnected"); //$NON-NLS-1$
                stopBluetooth11();
            }
    
            @SuppressWarnings("synthetic-access")
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy)
            {
                Log.d(TAG, "Profile listener onServiceConnected"); //$NON-NLS-1$
    
                // mBluetoothHeadset is just a headset profile, 
                // it does not represent a headset device.
                mBluetoothHeadset = (BluetoothHeadset) proxy;
    
                // If a headset is connected before this application starts,
                // ACTION_CONNECTION_STATE_CHANGED will not be broadcast. 
                // So we need to check for already connected headset.
                List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
                if (devices.size() > 0)
                {
                    // Only one headset can be connected at a time, 
                    // so the connected headset is at index 0.
                    mConnectedHeadset = devices.get(0);
    
                    onHeadsetConnected();
    
                    // Should not need count down timer, but just in case.
                    // See comment below in mHeadsetBroadcastReceiver onReceive()
                    mIsCountDownOn = true;
                    mCountDown11.start();
    
                    Log.d(TAG, "Start count down"); //$NON-NLS-1$
                }
    
                // During the active life time of the app, a user may turn on and off the headset.
                // So register for broadcast of connection states.
                mContext.registerReceiver(mHeadsetBroadcastReceiver, 
                                new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
                // Calling startVoiceRecognition does not result in immediate audio connection.
                // So register for broadcast of audio connection states. This broadcast will
                // only be sent if startVoiceRecognition returns true.
                mContext.registerReceiver(mHeadsetBroadcastReceiver, 
                                new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
            }
        };
    
        /**
         *  API >= 11
         *  Handle headset and Sco audio connection states.
         */
        private BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver()
        {
    
            @SuppressWarnings("synthetic-access")
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @Override
            public void onReceive(Context context, Intent intent)
            {
                String action = intent.getAction();
                int state;
                if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED))
                {
                    state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 
                                                BluetoothHeadset.STATE_DISCONNECTED);
                    Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                    if (state == BluetoothHeadset.STATE_CONNECTED)
                    {
                        mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
                        // Calling startVoiceRecognition always returns false here, 
                        // that why a count down timer is implemented to call
                        // startVoiceRecognition in the onTick.
                        mIsCountDownOn = true;
                        mCountDown11.start();
    
                        // override this if you want to do other thing when the device is connected.
                        onHeadsetConnected();
    
                        Log.d(TAG, "Start count down"); //$NON-NLS-1$
                    }
                    else if (state == BluetoothHeadset.STATE_DISCONNECTED)
                    {
                        // Calling stopVoiceRecognition always returns false here
                        // as it should since the headset is no longer connected.
                        if (mIsCountDownOn)
                        {
                            mIsCountDownOn = false;
                            mCountDown11.cancel();
                        }
                        mConnectedHeadset = null;
    
                        // override this if you want to do other thing when the device is disconnected.
                        onHeadsetDisconnected();
    
                        Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$
                    }
                }
                else // audio
                {
                    state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                    Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                    if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED)
                    {
                        Log.d(TAG, "\nHeadset audio connected");  //$NON-NLS-1$
    
                        mIsOnHeadsetSco = true;
    
                        if (mIsCountDownOn)
                        {
                            mIsCountDownOn = false;
                            mCountDown11.cancel();
                        }
    
                        // override this if you want to do other thing when headset audio is connected.
                        onScoAudioConnected();
                    }
                    else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
                    {
                        mIsOnHeadsetSco = false;
    
                        // The headset audio is disconnected, but calling
                        // stopVoiceRecognition always returns true here.
                        mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
    
                        // override this if you want to do other thing when headset audio is disconnected.
                        onScoAudioDisconnected();
    
                        Log.d(TAG, "Headset audio disconnected"); //$NON-NLS-1$
                    }
                }   
            }
        };
    
        /**
         * API >= 11
         * Try to connect to audio headset in onTick.
         */
        private CountDownTimer mCountDown11 = new CountDownTimer(10000, 1000)
        {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @SuppressWarnings("synthetic-access")
            @Override
            public void onTick(long millisUntilFinished)
            {
                // First stick calls always returns false. The second stick
                // always returns true if the countDownInterval is set to 1000.
                // It is somewhere in between 500 to a 1000.
                mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset);
    
                Log.d(TAG, "onTick startVoiceRecognition"); //$NON-NLS-1$
            }
    
            @SuppressWarnings("synthetic-access")
            @Override
            public void onFinish()
            {
                // Calls to startVoiceRecognition in onStick are not successful.
                // Should implement something to inform user of this failure
                mIsCountDownOn = false;
                Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
            }
        };
    
    }  
    

    (2013 年 4 月 30 日)在必要时进行编辑以更改为 @TargetApi(Build.VERSION_CODES.HONEYCOMB)。如果您对 API 8 或 9 的 java.lang.NoClassDefFoundError 有问题,只需删除所有 API >= 11 代码。 startBluetoothSco() 适用于所有 API 版本。

    【讨论】:

    • 感谢您的详细回答。这是一个演示,我们决定此时不需要耳机,所以我没有进行测试。但是,这个答案中有很多细节,我相信它会有所帮助。
    • 使 BluetoothHeadsetUtils 抽象并修复 start() 方法中的一个小错误。
    • else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED))如何触发
    • ACTION_SCO_AUDIO_STATE_CHANGED 永远不会执行。将在旧手机中执行的操作
    • 您需要 API 8 或更高版本,我不知道您是否可以使用 API
    【解决方案2】:

    我认为您所要做的就是更改应用程序的音频设置。

    当您想通过蓝牙接收和发送耳机的声音时,您应该输入以下代码。

     AudioManager audioManager;
    
    audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    
    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    audioManager.startBluetoothSco();
    audioManager.setBluetoothScoOn(true);
    

    不使用蓝牙的时候别忘了按以下方法恢复手机的正常设置。

    audioManager.setMode(AudioManager.MODE_NORMAL);
    audioManager.stopBluetoothSco();
    audioManager.setBluetoothScoOn(false);
    

    所需的权限如下:

    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    

    很简单!

    【讨论】:

    • 非常好——这个答案对我来说是最好的。我现在只需要某种方法来检测蓝牙耳机是否已连接。
    • 你可以用 BroadcastReceiver 做到这一点: public class BTReceiver extends BroadcastReceiver { int state = 0; @Override public void onReceive(Context context, Intent intent) { Log.d("Z", "Received: Bluetooth");尝试 { 捆绑附加服务 = intent.getExtras(); if (extras != null) { //做点什么 } } catch (NullPointerException e) { Log.d("NPE", "NullPointerException " + e.toString()); } }
    • 您还需要在清单中添加:
    • 以及以下权限:
    • 感谢您的这篇文章。 setMode 是我的最后一个障碍。这解决了我获取语音输入或 TTS 的问题。在设置模式之前,我无法同时获得两者。为了使用第 3 方 TTS (Nuance) 工作,我需要添加它。 Google 运行良好,但一些坚固的设备不安装 Google 语音文件,而且我们的应用程序没有互联网来安装它们。
    【解决方案3】:

    即使蓝牙耳机已配对并连接到电话音频配置文件 (HF/HS),实际音频连接 (SCO) 也仅在来电并被接听时建立。

    为了让您的 VR 应用程序接受来自蓝牙耳机的语音输入,您的应用程序必须在某些 VR 输入触发器上建立到耳机的 SCO, 您将需要使用以下内容 - isBluetoothScoAvailableOffCall 检查平台是否支持此功能,然后使用 startBluetoothSco 和 stopBluetoothSco 启动 SCO 到耳机。

    【讨论】:

    • 谢谢。我已经用 startBluetoothSco 方法解决了我的问题,但这仅适用于 VR,但不适用于 TTS。还有另一种方法可以使 TTS 对耳机讲话?
    【解决方案4】:

    startBluetoothSco需要很长时间才能建立,如果你需要用它来进行语音控制,这是个问题。

    有没有一种快速的方法可以使用蓝牙麦克风听,然后在听完后将其关闭?

    如果连接一直处于开启状态,则无法通过 A2DP 流式传输音频。

    所以,理想世界:

    通过 A2DP 音频输出。当它开始收听时,它使用 SCO 蓝牙麦克风。任何响应都是 A2DP。

    事实上,如果它已经连接 - 你可以通过将媒体流更改为调用流来即时切换吗?如果有,是否有明显的延迟?

    【讨论】:

      【解决方案5】:

      添加这些权限:

      <uses-permission android:name="android.permission.BLUETOOTH" />
      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
      <uses-permission android:name="android.permission.BROADCAST_STICKY" />
      <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
      

      然后为蓝牙创建一个广播接收器。

      public class BTReceiver extends BroadcastReceiver {
          private static final String TAG = "BTReceiver";
          int state = 0;
          AudioManager audioManager;
      
          @Override
          public void onReceive(Context context, Intent intent) {
              Log.d("Z", "Received: Bluetooth");
              try {
                  Bundle extras = intent.getExtras();
                  if (extras != null) { //Do something
                      audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
      
                      String action = intent.getAction();
                      Toast.makeText(context, action, Toast.LENGTH_LONG).show();
                      int state;
                      if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                          state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                                  BluetoothHeadset.STATE_DISCONNECTED);
                          Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                          if (state == BluetoothHeadset.STATE_CONNECTED) {
                              setModeBluetooth();
                          } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
                              // Calling stopVoiceRecognition always returns false here
                              // as it should since the headset is no longer connected.
                              setModeNormal();
      
                              Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$
                          }
                      } else // audio
                      {
                          state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                          Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                          if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
                              Log.d(TAG, "\nHeadset audio connected");  //$NON-NLS-1$
                              setModeBluetooth();
                          } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                              setModeNormal();
                              Log.d(TAG, "Headset audio disconnected"); //$NON-NLS-1$
                          }
                      }
                  }
              } catch (Exception e) {
                  Log.d("Exception", "Exception " + e.toString());
              }
          }
      
      
          private void setModeBluetooth() {
              try {
      
                  audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
                  audioManager.startBluetoothSco();
                  audioManager.setBluetoothScoOn(true);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          private void setModeNormal() {
              try {
                  audioManager.setMode(AudioManager.MODE_NORMAL);
                  audioManager.stopBluetoothSco();
                  audioManager.setBluetoothScoOn(false);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
      }
      

      在 Manifest 中注册接收者:

       <receiver
                  android:name=".speech.BTReceiver"
                  android:enabled="true"
                  android:permission="android.permission.BLUETOOTH">
                  <intent-filter>
                      <action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
                  </intent-filter>
              </receiver>
      

      在您的活动中注册和取消注册此接收器:

      首先初始化它,在onCreate中

       mHeadsetBroadcastReceiver = new BTReceiver();
      

      然后

      @Override
      protected void onResume() {
          super.onResume();
      
          registerReceiver(mHeadsetBroadcastReceiver,
                  new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
          // Calling startVoiceRecognition does not result in immediate audio connection.
          // So register for broadcast of audio connection states. This broadcast will
          // only be sent if startVoiceRecognition returns true.
          registerReceiver(mHeadsetBroadcastReceiver,
                  new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
      }
      
      @Override
      protected void onPause() {
          unregisterReceiver(mHeadsetBroadcastReceiver);
      
          super.onPause();
      }
      

      【讨论】:

        【解决方案6】:

        Hoan Nguyen 做得很好!
        测试他的代码我注意到在某些情况下 stopBluetoothSco() 没有被调用。

        我建议对 CountDownTimer 做一点改动:

        private CountDownTimer mCountDown11 = new CountDownTimer(10000, 1000)
        {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @SuppressWarnings("synthetic-access")
            @Override
            public void onTick(long millisUntilFinished)
            {
        
                mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset);
        
                Log.d(TAG, "onTick startVoiceRecognition"); //$NON-NLS-1$
            }
        
            @SuppressWarnings("synthetic-access")
            @Override
            public void onFinish()
            {
                mIsCountDownOn = false;
        
                /* START EDIT: Unregister broadcast receivers and stop Sco audio connection
                   and cancel count down if fails to connect. */
        
                stopBluetooth11();
        
                /* END EDIT */
        
                Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
            }
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-25
          相关资源
          最近更新 更多