My Android tutorial states that I can explicitly tell the TTS engine which stream to use:
For music playback:
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC));
And for phone calls:
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_VOICE_CALL));
My understanding is that audio routing to a Bluetooth headset works such that STREAM_MUSIC goes to A2DP (aka "media audio" in Android Bluetooth settings) and STREAM_VOICE_CALL goes to HSP (aka "phone audio" in Android Bluetooth settings).
But regardless whether I use STREAM_MUSIC or STREAM_VOICE_CALL in my little application, the audio always goes for some reason to A2DP.
What am I am doing wrong? Is there a way to route TTS output to headset's HSP profile?
I got this working for the most part, on most devices. Here is the part that starts the TTS on the voice call stream using Bluetooth SCO instead of A2DP.
if (mTtsReady) {
myHash = new HashMap<String, String>();
myHash.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "A2DP_Vol");
OLD_AUDIO_MODE = am2.getMode();
if(SMSstream == 1){
if (am2.isBluetoothScoAvailableOffCall()) {
am2.startBluetoothSco();
}
if(!am2.isSpeakerphoneOn()){
speakerPhoneWasOn = false;
am2.setSpeakerphoneOn(true);
}
myHash.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_VOICE_CALL));
am2.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}
else{
am2.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
myHash.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_MUSIC));
}
new CountDownTimer(SMS_DELAY, SMS_DELAY/2) {
#Override
public void onFinish() {
try {
mTts.speak(str, TextToSpeech.QUEUE_ADD,
myHash);
} catch (Exception e) {
Toast.makeText(application,
R.string.TTSNotReady,
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
#Override
public void onTick(long arg0) {
}
}.start();
}
Now I just have a problem getting the stream to revert back when done. It all works fine to read TTS. It will pause any music, play the TTS, and then resume music fine. However, when I exit the app later the stream for media now plays through the phone earpiece until I reboot. I posted that question here: Audio stream stays on earpiece after using AudioManager
You can see my whole project here: http://code.google.com/p/a2dpvolume/
If your headset is compatible with the a2dp profile, then using AudioManager.STREAM_MUSIC and hence playing audio through the stream should get the job done.
I would also add, that if you are currently in a call, and you play audio through the voice stream, then any headset (a2dp or otherwise) can hear the audio. Unfortunately, you need to be in a call.
Unfortunately I have found that setting the mode to MODE_IN_CALL does nothing.
To sum it up:
If all that you are trying to do is play music (when not in a call), then use the AudioManager.STREAM_MUSIC and if the headset is A2DP compatible, then it will hear the music.
Also, take a look at AudioManager.isBluetoothA2dpOn(), to make sure that the system thinks that your headeset is plugged in.
Yes it is possible to play the TTS (text-to-speech) output through a non A2DP headset like jroal says by using SCO.
Use AudioManager startBluetoothSco to enable SCO. Then listen for AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED broadcast with the EXTRA_SCO_AUDIO_STATE set to SCO_AUDIO_STATE_CONNECTED.
If you now do TextToSpeech to the STREAM_VOICE_CALL stream, it will end up in the headset (even the cheap non A2DP devices).
Related
I make a player that should play only through headphones (wired or Bluetooth), but not through the speaker.
If you turn off the headphones during playback, the player automatically pauses.
When (Bluetooth) headphones are connected back and I get the ACTION_AUDIO_STATE_CHANGED event, I resume playback. But for a few seconds, the sound goes through the builtin speaker and only then goes to the headphones. I think to fix this by setting the device to output. I can get the device id from AudioManager. But how to pass it to OpenSL ES?
Or maybe there is a way to completely prevent playback through the builtin speaker?
Or another way to solve this problem?
P.S. Santa, where is you when you are so needed? Help me, please!
Maybe you could listen to this other type of events with a BroadcastReceiver:
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED -> {
state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1)
when (state) {
BluetoothA2dp.STATE_CONNECTED -> {
// Do something
}
BluetoothA2dp.STATE_DISCONNECTED -> {
// Do something else
}
}
}
I have my phone connected to a Bluetooth speaker and the headphones plugged in. Now I'd like to play audio through the Bluetooth speaker.
When I set the audio stream to AudioManager.STREAM_MUSIC it just plays over the headphones.
It doesn't matter if it plays on the headphones as well but I need it to play on the Bluetooth speaker.
How is this possible? The app SoundAbout manages to do that so there must be a way.
EDIT: When I plug in the headphones and only afterwards connect to the Bluetooth speakers all audio plays through the Bluetooth speakers which I want. But I can't expect the user to find that out and before having to show them a complicated message I'd rather find out a way to make the sound always play through BT speakers when connected to some.
Thanks
(Note this is not the same question as this: How to Play audio through speaker even when headset is plugged in?
I want it to play on Bluetooth speakers, not on the integrated speaker of the phone.)
Solution
Suppose you already tested STREAM_RING on your new instance of media player and not directly setting stream type, and it didn't work out, You need a correct profile for your bluetooth device.
Take a look at this article
Read the "Implementing HAL" section, there is alot of source for different profile that you may be able to use.
There is also an easy solution which is to change your device profile to HEADSET in your getServiceConnected() method, it will turn into a Stay connected device but the output will become mono! As I recall, Which is a shame for speakers, A2DP also may not be supported in some hardwares and still interrupted by wire headsets.
I suggest to create a new profile and use it, a little bit tricky working with HAL but will worth it,
Sorry that I can not provide a source code for you at the moment.
If you have your routing logic within your application then based on that you can decide on which output the audio to be played.
I have a test app written for the exact purpose.
My Github Link
You can also route audio as you want based on a requirement.You can refer this github link for routing
Bluetooth connection may work with below state is true .
After receive BluetoothA2dp.STATE_CONNECTED, you can play music as normal.
Java Code Examples for android.bluetooth.BluetoothA2dp.STATE_CONNECTED
public BluetoothHandsfree(Context context, CallManager cm) {
mCM = cm;
mContext = context;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
boolean bluetoothCapable = (adapter != null);
mHeadset = null; // nothing connected yet
mA2dp = new BluetoothA2dp(mContext);
mA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
mA2dpDevice = null;
mA2dpSuspended = false;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
TAG + ":StartCall");
mStartCallWakeLock.setReferenceCounted(false);
mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
TAG + ":VoiceRecognition");
mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
BRSF_AG_EC_NR |
BRSF_AG_REJECT_CALL |
BRSF_AG_ENHANCED_CALL_STATUS;
if (sVoiceCommandIntent == null) {
sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
mLocalBrsf |= BRSF_AG_VOICE_RECOG;
}
mBluetoothPhoneState = new BluetoothPhoneState();
mUserWantsAudio = true;
mPhonebook = new BluetoothAtPhonebook(mContext, this);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
cdmaSetSecondCallState(false);
if (bluetoothCapable) {
resetAtState();
}
}
please find below links : with sample codes it may help you.
Java Code Examples for android.bluetooth.BluetoothHeadset
Programmatically connect to paired Bluetooth speaker and play audio
You need to instantiate a new object of class MediaPlayer and use the following method on it
amediaplayer.setAudioStreamType(AudioManager.STREAM_RING)
Do not forget to check authorization to use bluetooth, you are unable to send anything to speaker via bluetooth without user privilege as you know.
Audiomanager overrides and routes audio to the latest connected device(either wired headset or bluetooth headset). In android, we do not have any option to override this setting unless it is a system app and route the audio wherever we wish to route. However, you can use reflection apis and override this setting. Audiomanager suspends bluetooth connection route(if already connected)if wired headset is connected and vice versa. You can look at the code here.
Hence using reflection apis you can toggle bluetooth audio route by invoking this method.
I'm trying to get continuous voice recognition to work for a custom app on Android, using an ML18 bluetooth headset (by plantronics). I've managed to get rid of the beep produced by my phone by using the answers posted here and managed to route all other media to my bluetooth device using this.
However my bluetooth device is still beeping every time I call SpeechRecognizer.startListening.
Is there something I can do about this, or is this possibly hardwired into the software of the bluetooth device?
If you want to see specific sections of code, let me know. I do not think much of it is relevant however, as the two links above already cover the code that seems most relevant to me. Thanks in advance!
EDIT: So based on the comment below, I've modified my code into the following:
The start listening simply mutes all stream but the alarm stream (undone by unmuting all streams again).
public void startListening() {
if (canListen) {
Log.v(TAG, "Starting Listening");
Intent recognizerIntent = createRecognizerIntent();
recognizer.cancel();
recognizer.startListening(recognizerIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((AudioManager) getApplicationContext().getSystemService(
Context.AUDIO_SERVICE)).setStreamSolo(
AudioManager.STREAM_ALARM, true);
}
}
}
The Bluetooth receiver does this when the device is connected:
AudioManager localAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
localAudioManager.setMode(AudioManager.MODE_NORMAL);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
And then in the ACTION_SCO_AUDIO_STATE_UPDATED it sets the mode to AudioManager.MODE_IN_CALL.
Currently the behaviour is as follows:
When I first start the app and connect the bluetooth device, as well as trigger the continuous recognition loop, all works as expected. Yet after a first reply has been uttered (using TTS), the beep returns... Again, any help would be appreciated :)
After I use the Android AudioManager to set SCO ON and speakerphone ON, then I return it back to where it started, the media stream will then stay on earpiece instead of the phones speaker. Here is where I make the changes to route a stream through the SCO speakerphone (which all works great)
if (am2.isBluetoothScoAvailableOffCall()) {
am2.startBluetoothSco();
}
if(!am2.isSpeakerphoneOn()){
speakerPhoneWasOn = false;
am2.setSpeakerphoneOn(true);
}
myHash.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_VOICE_CALL));
am2.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
Then I try to change it all back after my message is read (I am using TTS)
if(SMSstream == 1){
if (am2.isBluetoothScoAvailableOffCall()) {
am2.stopBluetoothSco();
}
if(!speakerPhoneWasOn){
am2.setSpeakerphoneOn(false);
}
}
else{
}
am2.setMode(OLD_AUDIO_MODE);
am2.abandonAudioFocus(null);
But it stays with the phone earpiece instead of the speaker until I reboot. I saw a few post that had the opposite problem but none with this issue. I have a Droid 3 with Android 2.3.4.
You can see the whole project and source code here: http://code.google.com/p/a2dpvolume/
OK, I finally fixed this problem. Its not pretty. There is some sort of bug in the AudioManager I believe. Even after abandoning focus it would leave the device streams in a mess. Unless the last TTS was read over the music stream just before abandoning focus, it would route streams wrong, mute streams, etc. So, I just have it read a single period if a SMS had been read over any stream except the music stream before abandoning focus. I also cleaned up the order I called things in, and fixed some of the phone mode, etc after reading a TTS. Strangely it seems to work well.
I have a non-A2DP single ear BT headset (Plantronics 510) and would like to use it with my Android HTC Magic to listen to low quality audio like podcasts/audio books.
After much googling I found that only phone call audio can be routed to the non-A2DP BT headsets. (I would like to know if you have found a ready solution to route all kinds of audio to non-A2DP BT headsets)
So I figured, somehow programmatically I can channel the audio to the stream that carries phone call audio. This way I will fool the phone to carry my mp3 audio to my BT headset. I wrote following simple code.
import android.content.*;
import android.app.Activity;
import android.os.Bundle;
import android.media.*;
import java.io.*;
import android.util.Log;
public class BTAudioActivity extends Activity
{
private static final String TAG = "BTAudioActivity";
private MediaPlayer mPlayer = null;
private AudioManager amanager = null;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
amanager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
amanager.setBluetoothScoOn(true);
amanager.setMode(AudioManager.MODE_IN_CALL);
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(new FileInputStream(
"/sdcard/sample.mp3").getFD());
mPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
mPlayer.prepare();
mPlayer.start();
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
#Override
public void onDestroy()
{
mPlayer.stop();
amanager.setMode(AudioManager.MODE_NORMAL);
amanager.setBluetoothScoOn(false);
super.onDestroy();
}
}
As you can see I tried combinations of various methods that I thought will fool the phone to believe my audio is a phone call:
Using MediaPlayer's setAudioStreamType(STREAM_VOICE_CALL)
using AudioManager's setBluetoothScoOn(true)
using AudioManager's setMode(MODE_IN_CALL)
But none of the above worked. If I remove the AudioManager calls in the above code, the audio plays from speaker and if I replace them as shown above then the audio stops coming from speakers, but it doesn't come through the BT headset. So this might be a partial success.
I have checked that the BT headset works alright with phone calls.
There must be a reason for Android not supporting this. But I can't let go of the feeling that it is not possible to programmatically reroute the audio. Any ideas?
P.S. above code needs following permission
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
This thread may be long dead but for those who might be trying the same thing, some notes from the AudioManager docs may be useful. It looks like the missing element is the startBluetoothSco() command but there are restrictions on the use of this channel. From the Android Dev site here:
public void startBluetoothSco () Since: API Level 8 Start bluetooth SCO
audio connection.
Requires Permission:
MODIFY_AUDIO_SETTINGS.
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.
As the SCO connection establishment
can take several seconds, applications
should not rely on the connection to
be available when the method returns
but instead register to receive the
intent ACTION_SCO_AUDIO_STATE_CHANGED
and wait for the state to be
SCO_AUDIO_STATE_CONNECTED.
As the connection is not guaranteed to
succeed, applications must wait for
this intent with a timeout.
When finished with the SCO connection
or if the establishment times out, the
application must call
stopBluetoothSco() to clear the
request and turn down the bluetooth
connection.
Even if a SCO connection is
established, the following
restrictions apply on audio output
streams so that they can be routed to
SCO headset: - the stream type must be
STREAM_VOICE_CALL - the format must be
mono - the sampling must be 16kHz or
8kHz
The following restrictions apply on
input streams: - the format must be
mono - the sampling must be 8kHz
Note that the phone application always
has the priority on the usage of the
SCO connection for telephony. If this
method is called while the phone is in
call it will be ignored. Similarly, if
a call is received or sent while an
application is using the SCO
connection, the connection will be
lost for the application and NOT
returned automatically when the call
ends.
See Also stopBluetoothSco()
ACTION_SCO_AUDIO_STATE_CHANGED
Note that I have not tested this, I'm just passing along a lead I found in researching a similar project. I think Jayesh was close to the solution and the restrictions above may have been what was keeping it from working.
To turn on:
localAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
localAudioManager.setMode(0);
localAudioManager.setBluetoothScoOn(true);
localAudioManager.startBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_IN_CALL);
To turn off:
localAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
localAudioManager.setBluetoothScoOn(false);
localAudioManager.stopBluetoothSco();
localAudioManager.setMode(AudioManager.MODE_NORMAL);
I took it from here
Great Work it is working fine for me please do bit modification in your code it'll work perfectly i.e.
mPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
to
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
There should be a ALSA route path configured for Media player, so that it can open a different audio path and then route your audio to BT headset.
Cannot see a clear accepted working application so putting a new answer. This application routes music and audio to Non A2dp headsets. Try my application and find the source code also in github for reference code. https://github.com/sauravpradhan/AnySound2BT