How can I record the voice from a paired Bluetooth audio device (i.e. Moster Clarity Bluetooth Speaker) in Android.
I've paired with the device from within Android, and I'd like to record the voice from the microphone on the device (as opposed to using the phone's built-in microphone).
Here's the code I'm using for recording:
try {
isRecording = true;
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
if (file == null) {
File rootDir = Environment.getExternalStorageDirectory();
file = File.createTempFile(PREFIX, EXTENSION, rootDir);
}
recorder.setOutputFile(file.getAbsolutePath());
recorder.prepare();
recorder.start();
timDown = new RecordCountDown(10000, 1000);
timDown.start();
} catch (Exception e) {
Log.i("Error Message", "Error Message :" + e.getMessage());
}
How can I do this?
Try this code maybe helpful for you..
am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
Log.d(TAG, "Audio SCO state: " + state);
if (AudioManager.SCO_AUDIO_STATE_CONNECTED == state) {
/*
* Now the connection has been established to the bluetooth device.
* Record audio or whatever (on another thread).With AudioRecord you can record with an object created like this:
* new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
* AudioFormat.ENCODING_PCM_16BIT, audioBufferSize);
*
* After finishing, don't forget to unregister this receiver and
* to stop the bluetooth connection with am.stopBluetoothSco();
*/
unregisterReceiver(this);
}
}
}, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
Log.d(TAG, "starting bluetooth");
am.startBluetoothSco();
code to voice recording from bluetooth headset
public class Recording {
static int count = 0;
static String Shared;
static String bFlag;
public static int TIMEOUT = 5000;
public static int COUNTDOWN_INTERVAL = 1000;
static Context context;
public static void checkAndRecord(Context context,
OnBluetoothRecording BluetoothRecording, boolean resume) {
// Check bluetooth flag And Bluetooth is ON or OFF
if (getBluetoothFlag(context) && isBluetoothON()) {
// Check for bluetooth and Record
startBluetoothRecording(BluetoothRecording, resume, context);
} else {
// If Bluetooth is OFF Show Toast else Dont Show
if (getBluetoothFlag(context) && !isBluetoothON()) {
// false because recording not started
Toast.makeText(context,
"Bluetooth is OFF. Recording from Phone MIC.",
Toast.LENGTH_SHORT).show();
BluetoothRecording.onStartRecording(resume, false);
} else {
// false because recording not started
BluetoothRecording.onStartRecording(resume, false);
}
}
}
private static void startBluetoothRecording(
final OnBluetoothRecording BluetoothRecording,
final boolean resume, Context context) {
// TODO Auto-generated method stub
final int MAX_ATTEPTS_TO_CONNECT = 3;
final AudioManager audioManager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
final CountDownTimer timer = getTimer(BluetoothRecording, audioManager,
resume);
context.registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(
AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (AudioManager.SCO_AUDIO_STATE_CONNECTED == state) {
// cancel Timer
timer.cancel();
context.unregisterReceiver(this);
// pass through and true because
// recording from bluetooth so set 8000kHz
BluetoothRecording.onStartRecording(resume, true);
} else if (AudioManager.SCO_AUDIO_STATE_DISCONNECTED == state) {
if (count > MAX_ATTEPTS_TO_CONNECT) {
context.unregisterReceiver(this);
// Stop BluetoothSCO
audioManager.stopBluetoothSco();
// reset Counter
count = 0;
// stop timer
timer.cancel();
// false because still recording not started
BluetoothRecording.onStartRecording(resume, false);
} else {
// Increment Disconnect state Count
count++;
}
}
}
}, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
// Start the timer
timer.start();
audioManager.startBluetoothSco();
}
// set the Timeout
private static CountDownTimer getTimer(
final OnBluetoothRecording BluetoothRecording,
final AudioManager audioManager, final boolean resume) {
// TODO Auto-generated method stub
return new CountDownTimer(TIMEOUT, COUNTDOWN_INTERVAL) {
#Override
public void onTick(long millisUntilFinished) {
// Do Nothing
}
#Override
public void onFinish() {
// stopBluetoothSCO() and start Normal Recording
audioManager.stopBluetoothSco();
// false because recording button is already clicked but still
// not recording.
BluetoothRecording.onStartRecording(resume, false);
}
};
}
// Return's the bluetooth state
private static boolean isBluetoothON() {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter
.getDefaultAdapter();
return bluetoothAdapter.isEnabled();
}
// Return's the bluetoothFlag state
private static boolean getBluetoothFlag(Context context) {
// shared pref
SharedPreferences sp = context.getSharedPreferences(Shared,
Context.MODE_PRIVATE);
return sp.getBoolean(bFlag, false);
}
}
Interface OnBluetoothRecording.java
public interface OnBluetoothRecording {
void onStartRecording(boolean state,boolean bluetoothFlag);
void onCancelRecording();
}
The key thing is to call audioManager.startBluetoothSco().
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.
This is an asynchronous operation, as such, you can register a BroadcastReceiver to be notified once audio will start being recorded through the bluetooth headset like so
private BroadcastReceiver mBluetoothScoReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
// Start recording audio
}
}
};
#Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
registerReceiver(mBluetoothScoReceiver, intentFilter);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.startBluetoothSco();
}
The docs also mention something very important
the application can check the SCO audio state before calling startBluetoothSco() by reading the intent returned by the receiver registration. If the state is already CONNECTED, no state change will be received via the intent after calling startBluetoothSco().
And...
It is however useful to call startBluetoothSco() so that the connection stays active in case the current initiator stops the connection.
This means you do not have to wait for the receiver to be called if the SCO connection is already active. Here is the updated code snippet.
#Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
Intent intent = registerReceiver(mBluetoothScoReceiver, intentFilter);
if (intent == null) {
Log.e(TAG, "Failed to register bluetooth sco receiver...");
return;
}
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
// Start recording
}
// Ensure the SCO audio connection stays active in case the
// current initiator stops it.
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.startBluetoothSco();
}
You should stop the SCO connection if you are not using it.
private void onPause() {
super.onPause();
unregisterReceiver(mBluetoothScoReceiver);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.stopBluetoothSco();
}
You will also need the following permissions in your AndroidManifest.xml file
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
Related
After connecting with paired devices, Bluetooth is not taking his Mic, instead it is taking mobile's Mic
Below is my code
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;
ExminatingHelper exminatingHelper = ExminatingHelper.getInstance();
private static final String TAG = "BluetoothHeadsetUtils";
/**
* 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;
mIsStarted = startBluetooth();
/*if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
mIsStarted = startBluetooth();
} else {
System.out.println("");
// 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;
stopBluetooth();
/*if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
stopBluetooth();
} else {
System.out.println("");
// 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();
#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(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
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;
}
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;
Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
}
};
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#SuppressWarnings({"deprecation", "synthetic-access"})
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
int getRecrodingDevice = exminatingHelper.getRecordingDevice();
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
|| deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES
|| deviceClass == BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER
) {
// 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();
}
// override this if you want to do other thing when the device is disconnected.
onHeadsetDisconnected();
} else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) && getRecrodingDevice != 2) {
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 things when Sco audio is disconnected.
onScoAudioDisconnected();
}
}
}
}
};
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);
}
}
}
This works well when using the phone in a normal fashion. However, it does not detect the presence of a Bluetooth headset and still uses the phone's own microphone even when the headset is plugged in.
Here's a clip of mine, there was this Bluetooth receiver class shared on SO, but it didn't work well with me (long story short, don't use the (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) because when I trigger it via clicking the button, there's usually a false positive).
Try following the logs..
and on
onHeadsetConnected();
like
private void onHeadsetConnected(){
speech.startListening(theIntent);
}
while to capture when you have "disconnected", instead of counting SCO_AUDIO_STATE_DISCONNECTED, just capture onError, onPartialResult, onResult and recheck whether the device is still there .. as so, or .. you know .. recheck it when you need to listen to it again instead of when it's done ..
mBluetoothHeadset.getConnectionState(mConnectedHeadset) == BluetoothHeadset.STATE_CONNECTED
And I do this to ensure I'm connecting to the right headset (that has a mic!)
if (mBluetoothHeadset.getConnectedDevices().size()>0) {
mConnectedHeadset = null;
for (int i=0;i<mBluetoothHeadset.getConnectedDevices().size(); i ++){
int deviceClass = mBluetoothHeadset.getConnectedDevices().get(i).getBluetoothClass().getDeviceClass();
switch (deviceClass){
case BluetoothClass.Device.Major.PHONE:
case BluetoothClass.Device.Major.AUDIO_VIDEO:
case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE:
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mConnectedHeadset = mBluetoothHeadset.getConnectedDevices().get(i);
registerReceiver(mHeadsetBroadcastReceiver,
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
return;
}
}
}
Hope this helps you / or someone .. since I tried to implement that class, way too much of a hassle, so I just took parts of it into my activity and use what I needed (definitely didn't need support for old android)
My app already does speech recognition. A user told me it didn't work with a Bluetooth headset. I was able to, mostly, get speech recognition working over the Bluetooth headset (thanks to another Stack Overflow contributor, no thanks to the official Android documentation).
There is one annoying problem, however: when switching to use the Bluetooth headset, it can obliterate audio output briefly during the switchover. In particular, it can sometimes omit the beep that ordinarily prompts the user for speech recognition.
My current workaround is to put in a one-second delay, after the Bluetooth headset has been connected to the SCO audio channel, before doing speech recognition.
My question: is there a more reliable method of avoiding this conflict?
static AudioManager sAudioManager;
static BluetoothHeadset sBluetoothHeadset;
static BluetoothDevice sBluetoothDevice;
static public void initBluetooth(Context pContext)
{
sAudioManager = (AudioManager) pContext.getSystemService(Context.AUDIO_SERVICE);
// Register to get notifications about Bluetooth profiles
sBluetoothAdapter.getProfileProxy(
pContext, new BluetoothProfileListener(), BluetoothProfile.HEADSET);
// Register to receive notification when the headset is dis/connected
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
pContext.registerReceiver(
new BluetoothHeadsetBroadcastReceiver(), intentFilter);
}
public static void startSpeechRecognition()
{
sAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
sAudioManager.startBluetoothSco();
sAudioManager.setBluetoothScoOn(true);
sBluetoothHeadset.startVoiceRecognition(sBluetoothDevice);
}
public static void stopSpeechRecognition()
{
sBluetoothHeadset.stopVoiceRecognition(sBluetoothDevice);
sAudioManager.stopBluetoothSco();
sAudioManager.setBluetoothScoOn(false);
sAudioManager.setMode(AudioManager.MODE_NORMAL);
}
static public void doSpeechRecognition(Context pContext)
{
SpeechRecognizer sr = SpeechRecognizer.createSpeechRecognizer(pContext);
Intent intent =
new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
sr.startListening(intent);
}
static public class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context pContext, Intent pIntent)
{
int state = pIntent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED)
{
// CURRENT WORKAROUND
// Delay speech recognition to avoid obliterating beep prompt
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
};
doSpeechRecognition(pContext);
}
}
}
static public class BluetoothProfileListener implements BluetoothProfile.ServiceListener
{
#Override
public void onServiceConnected(int pProfile, BluetoothProfile pProxy)
{
sBluetoothHeadset = (BluetoothHeadset) pProxy;
List<BluetoothDevice> devices = pProxy.getConnectedDevices();
int numDevices = devices.size();
if (numDevices > 0)
{
BluetoothDevice device = devices.get(0);
sBluetoothDevice = device;
}
}
}
I am trying to record audio and analyse it from a Bluetooth headset, to do this I am using the object: android.media.AudioRecord,
My problem is that for some devices in combination with what their Android versions, I get the recording from the phone mic instead of the bluetooth headset.
I can solve this issue in some cases - if when I create this object I pass to it's constructor a different audio source type.
For example I saw that in order to record from the Bluetooth headset in Nexus 6 with Android 5.0, I need to construct the AudioRecord like this:
audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 4410);
But If I try to use the same code for a phone called "THL T6S" that also has Android 5.0, the recording comes from phone mic.
If I want it to be from the headset in this phone I need to write:
audioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 4410);
But off course I can't cover in my code all the possible combinations of phone types and OS versions.
How can I solve it?
Attached all the relevant code.
Thanks.
This is my Bluetooth Sco class
public class BluetoothSco {
private static BluetoothSco instance = null;
// Context
private Context mContext;
private Service service;
// Logcat TAG
private final String TAG = BluetoothSco.class.getSimpleName();
// AudioMode
public static final int MODE_IN_CALL = 1;
public static final int MODE_IN_COMMUNICATION = 2;
public static final int MODE_NORMAL = 3;
// BluetoothAdapter
private final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
protected BluetoothSco() { }
public static BluetoothSco getInstance(Context context) {
if(instance == null) {
instance = new BluetoothSco();
instance.mContext = context;
instance.service = (Service) context;
}
return instance;
}
// Start Sco
public synchronized void startSco(final int audioMode) {
Runnable runnable = new Runnable() {
#Override
public void run() {
if (btAdapter == null) {
Log.i(TAG, "This device does not support Bluetooth");
return;
}
else if (!btAdapter.isEnabled()) {
Log.i(TAG, "Bluetooth headset is disconnected. Would you like to monitor manually?");
return;
}
AudioManager audioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
if (!audioManager.isBluetoothScoAvailableOffCall()) {
Log.i(TAG, "Off-call Bluetooth audio not supported on this device.");
return;
}
if (audioMode == BluetoothSco.MODE_IN_CALL) {
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
else if (audioMode == BluetoothSco.MODE_IN_COMMUNICATION) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
}
else {
audioManager.setMode(AudioManager.MODE_NORMAL);
}
}
else if (audioMode == BluetoothSco.MODE_NORMAL) {
audioManager.setMode(AudioManager.MODE_NORMAL);
}
//Roni - temp code/////////
//audioManager.setMode(AudioManager.MODE_IN_CALL);
////////////////////
audioManager.setBluetoothScoOn(true);
try{
audioManager.startBluetoothSco();
Log.v(TAG, "Bluetooth SCO Started");
}
catch(NullPointerException ex){
Log.i(TAG, "Bluetooth headset is disconnected. Would you like to monitor manually?");
}
}
};
runnable.run();
}
public synchronized void stopSco() {
Runnable runnable = new Runnable() {
#Override
public void run() {
AudioManager audioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
try {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
audioManager.setMode(AudioManager.MODE_NORMAL);
Log.v(TAG, "Bluetooth SCO Stopped");
}
catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
};
runnable.run();
}
}
And here is how I initialize the AudioRecord and Bluetooth Sco
audioRecord = new AudioRecord(Options.audioSource, Options.sampleRate, Options.channelConfig,
Options.audioEncoding, Options.numBuffers * Math.max(minBufSize, bufferSizeBytes));
mBluetoothSco.startSco(BluetoothSco.MODE_NORMAL);
audioRecord.startRecording();
I had the same problem. For me, adding the following fixed this:
audioManager.setMode(AudioManager.VOICE_COMMUNICATION);
Details to pay attention to code(Summary):
Setting AudioManager mode and start SCO
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
AudioRecord use VOICE_COMMUNICATION to initialize,enter link description here
new AudioRecord(VOICE_COMMUNICATION, sampleRateInHz, channelConfig, audioFormat, recordingBufferSize)
Through these, I can use Bluetooth for recording in React Native.
I wrote some codes for detecting the bluetooth headset connection and start audio through the headset. For API 11 and later, one can call startVoiceRecognition when the headset is connected. So a couple of use cases is as follow:
Headset was turned on before the application launches
The application should check for headset connected on start and establishes audio connection.
User turns on headset during lifetime of the application
The application should register for broadcast of headset connection state and start audio connection when receive connected state.
There is a problem with the second use case. When received the connected state, I call startVoiceRecognition, but it always return false. So I have to implement a timer and after about a second the call will return true. I guess the OS and the headset need sometime to have everything ready to work.
Does anybody know how to get headset audio connection without implement a timer. If it is not possible, should it be the OS that should take care of this situation (for example a READY_FOR_AUDIO_CONNECTION broadcast) instead of the application?
Below is the complete working code for API 11 or later.
Manifest permissions
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
Code
public class MainActivity extends Activity
{
protected TextView mInfoTextview;
protected BluetoothAdapter mBluetoothAdapter;
protected BluetoothHeadset mBluetoothHeadset;
protected BluetoothDevice mConnectedHeadset;
protected AudioManager mAudioManager;
private static final String TAG = "Bluetooth Headset"; //$NON-NLS-1$
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mInfoTextview = (TextView) findViewById(R.id.main_textview);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null)
{
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager.isBluetoothScoAvailableOffCall())
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
mBluetoothAdapter.getProfileProxy(this, mHeadsetProfileListener, BluetoothProfile.HEADSET);
}
}
}
}
#Override
protected void onDestroy()
{
super.onDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
if (mBluetoothHeadset != null)
{
// Need to call stopVoiceRecognition here when the app
// change orientation or close with headset still turns on.
mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
unregisterReceiver(mHeadsetBroadcastReceiver);
mCountDown.cancel();
}
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
}
Log.d(TAG, "onDestroy"); //$NON-NLS-1$
}
protected 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$
mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
unregisterReceiver(mHeadsetBroadcastReceiver);
mBluetoothHeadset = null;
}
#Override
public void onServiceConnected(int profile, BluetoothProfile proxy)
{
Log.d(TAG, "Profile listener onServiceConnected"); //$NON-NLS-1$
// mBluetoothHeadset is just a head set profile,
// it does not represent a head set device.
mBluetoothHeadset = (BluetoothHeadset) proxy;
// If a head set is connected before this application starts,
// ACTION_CONNECTION_STATE_CHANGED will not be broadcast.
// So we need to check for already connected head set.
List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
if (devices.size() > 0)
{
// Only one head set can be connected at a time,
// so the connected head set is at index 0.
mConnectedHeadset = devices.get(0);
String log;
// The audio should not yet be connected at this stage.
// But just to make sure we check.
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "Profile listener audio already connected"; //$NON-NLS-1$
}
else
{
// The if statement is just for debug. So far startVoiceRecognition always
// returns true here. What can we do if it returns false? Perhaps the only
// sensible thing is to inform the user.
// Well actually, it only returns true if a call to stopVoiceRecognition is
// call somewhere after a call to startVoiceRecognition. Otherwise, if
// stopVoiceRecognition is never called, then when the application is restarted
// startVoiceRecognition always returns false whenever it is called.
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "Profile listener startVoiceRecognition returns true"; //$NON-NLS-1$
}
else
{
log = "Profile listener startVoiceRecognition returns false"; //$NON-NLS-1$
}
}
mInfoTextview.setText("Device name = " + mConnectedHeadset.getName() //$NON-NLS-1$
+ "\n\n" + log); //$NON-NLS-1$
Log.d(TAG, log);
}
// During the active life time of the app, a user may turn on and off the head set.
// So register for broadcast of connection states.
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));
}
};
protected BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
int state;
int previousState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, BluetoothHeadset.STATE_DISCONNECTED);
String log = ""; //$NON-NLS-1$
if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED))
{
state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
if (state == BluetoothHeadset.STATE_CONNECTED)
{
mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mInfoTextview.append("\n\nDevice name = " + mConnectedHeadset.getName()); //$NON-NLS-1$
// Audio should not be connected yet but just to make sure.
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "Headset connected audio already connected"; //$NON-NLS-1$
}
else
{
// Calling startVoiceRecognition always returns false here,
// that why a count down timer is implemented to call
// startVoiceRecognition in the onTick and onFinish.
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "Headset connected startVoiceRecognition returns true"; //$NON-NLS-1$
}
else
{
log = "Headset connected startVoiceRecognition returns false"; //$NON-NLS-1$
mCountDown.start();
}
}
}
else if (state == BluetoothHeadset.STATE_DISCONNECTED)
{
// Calling stopVoiceRecognition always returns false here
// as it should since the headset is no longer connected.
mConnectedHeadset = null;
}
}
else // audio
{
state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED)
{
log = "Head set audio connected, cancel countdown timer"; //$NON-NLS-1$
mCountDown.cancel();
}
else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
{
// The headset audio is disconnected, but calling
// stopVoiceRecognition always returns true here.
boolean returnValue = mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
log = "Audio disconnected stopVoiceRecognition return " + returnValue; //$NON-NLS-1$
}
}
log += "\nAction = " + action + "\nState = " + state //$NON-NLS-1$ //$NON-NLS-2$
+ " previous state = " + previousState; //$NON-NLS-1$
mInfoTextview.append("\n\n" + log); //$NON-NLS-1$
Log.d(TAG, log);
}
};
protected CountDownTimer mCountDown = new CountDownTimer(10000, 1000)
{
#Override
public void onTick(long millisUntilFinished)
{
String log;
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "\nonTick audio already connected"; //$NON-NLS-1$
}
else
{
// 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.
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "\nonTick startVoiceRecognition returns true"; //$NON-NLS-1$
}
else
{
log = "\nonTick startVoiceRecognition returns false"; //$NON-NLS-1$
}
}
mInfoTextview.append(log);
Log.d(TAG, log);
}
#Override
public void onFinish()
{
String log;
if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
{
log = "\nonFinish audio already connected"; //$NON-NLS-1$
}
else
{
if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
{
log = "\nonFinish startVoiceRecognition returns true"; //$NON-NLS-1$
}
else
{
log = "\nonFinish startVoiceRecognition returns false"; //$NON-NLS-1$
}
}
mInfoTextview.append(log);
Log.d(TAG, log);
}
};
}
Layout File
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/main_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="false" />
</ScrollView>
I use the following code to launch speech recognition in 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);
This works fine. However, it doesn't seem to accept voice input from a bluetooth headset that is paired and connected using the "Phone audio" profile.
I can use an app called SoundAbout to force "Media Audio" to "Bluetooth (mono) (SCO)". With this app set, my voice recognition now works taking my speech input from my headset.
How can I use RecognizerIntent and get speech input from a bluetooth headset?
I see in API level 16 there is a new intent action ACTION_VOICE_SEARCH_HANDS_FREE. This is too new for me to use, but would this solve my problem?
Do I have to muck around in the AudioManager (like I assume SoundAbout is doing) to route the audio input using setBluetoothScoOn() or startBluetoothSco()?
Manifest permission
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
Create an inner class BluetoothHelper extends BluetoothHeadSetUtils in your Activity or Service. Declare a class member mBluetoothHelper and instantiate it in 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()
{
}
}
To use bluetooth headset with Text To Speech you need to set the AudioManager to STREAM_VOICE_CALL before calling speak. Or use the code below
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);
}
Copy the BluetoothHeadsetUtils class to your project.
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$
}
};
}
(April 30 2013) Edit to change to #TargetApi(Build.VERSION_CODES.HONEYCOMB) when necessary. If you have problem with java.lang.NoClassDefFoundError for API 8 or 9, just remove all the API >= 11 codes. The startBluetoothSco() works for all API versions.
I think all you have to do is change the audio settings on your application.
you should put the following code when you want to receive and send the sound from your headset via bluetooth.
AudioManager audioManager;
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
Do not forget to restore the normal settings on the phone in the following way when you're not using the bluetooth.
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
the permissions that are needed are the following:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
it's easy!
Even though the Bluetooth headset is paired and connected to the Phone audio profile (HF/HS), the actual audio connection (SCO) is established only when a call comes in and accepted.
For your VR application to accept the voice input from a Bluetooth Headset your application will have to establish a SCO to the headset on some VR input trigger,
You will need to use the following -
isBluetoothScoAvailableOffCall to check if the platform supports this capability, then use the
startBluetoothSco and stopBluetoothSco to start the SCO to the headset.
Add these permissions:
<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" />
Then create a Broadcast Receiver for Bluetooth.
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();
}
}
}
Register the receiver in 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>
In your activity Register and UnRegister this receiver:
First initialize it, in onCreate
mHeadsetBroadcastReceiver = new BTReceiver();
then
#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();
}
startBluetoothSco takes a long time to establish, which is problem if you need to use it for voice control.
Is there a quick way to use the bluetooth mic to listen and then turn it off after listening?
If the connection is on the whole time then it is not possible to stream audio via A2DP.
So, ideal world:
Audio out via A2DP. When it starts listening, it uses SCO bluetooth mic. Any response is A2DP again.
In fact, if it is already connected - can you switch on the fly by change the media stream to call stream? If so, is there any noticeable delay?
Hoan Nguyen has done a great job!
Testing his code I have noticed that in some cases stopBluetoothSco() is not called.
I propose a little change in 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$
}
};