I have an Android application that makes use of TTS (Text to speech) API. Everything is working perfectly, but now i want to fade in/out or even stop music (in case user is playing music with prebuilt Music Player), when application speaks a text. Right now, i think both music and TTS messages are played on the same stream (MUSIC), and it can be difficult to understand the voice messages.
I've tried to play the text on a different stream, like AudioManager.STREAM_NOTIFICATIONS. It does stop the music, but doesn't come back when the text is spoken, so i didn't achieve the goal. Haven't found anything yet, so i hope someone can help here. Thanks!
I finally got something that is working. Not perfect though. A quite dirty trick. Just in case it can help to someone:
This is fixed on API 8 with requestAudioFocus and abandomAudioFocus methods of AudioManager.
But for former versions, you can try this. Play TTS through a different stream channel, let's say STREAM_NOTIFICATIONS. Then you just need to return audio focus to STREAM_MUSIC. How can you achieve that?. Sending a silence string (" ") to TTS but this time through STREAM_MUSIC. The effect will be: music is stopped, your TTS message gets spoken, and finally your music is back after the voice alert. Not nice or something to feel proud of, but... if someone knows of a different way, i will appreciate it
Here is a way of doing this in Dec-2021
TexToSpeech needs to be initialized and assigned to tts before calling this method
Method 1 (Recommended):
private void speak(String textToSay) {
AudioAttributes mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
//add this below flag if you need the TTS to speak in a louder volume or TTS volume be heard for sure at any cost
//.setFlags(FLAG_AUDIBILITY_ENFORCED)
.build();
tts.setAudioAttributes(mPlaybackAttributes);
AudioFocusRequest mFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(mPlaybackAttributes)
.setAcceptsDelayedFocusGain(false)
.setWillPauseWhenDucked(false)
.build();
AudioManager am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(mFocusRequest);
tts.speak(textToSay, TextToSpeech.QUEUE_FLUSH, null, textToSay);
Handler ttsSpeak = new Handler();
Runnable checkTTSRunning = new Runnable() {
#Override
public void run() {
if (tts.isSpeaking()) {
ttsSpeak.postDelayed(this, 1000);
} else am.abandonAudioFocusRequest(mFocusRequest);
}
};
ttsSpeak.postDelayed(checkTTSRunning, 3000);
}
Method 2: Use this only if you need the TTS to speak in a louder volume and/or TTS volume needs to be heard for sure at any cost
private void speak(String textToSay) {
AudioAttributes mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setFlags(FLAG_AUDIBILITY_ENFORCED) //VERY IMPORTANT
.build();
tts.setAudioAttributes(mPlaybackAttributes);
tts.speak(textToSay, TextToSpeech.QUEUE_FLUSH, null, textToSay);
}
Could you use the TextToSpeech.OnUtteranceCompletedListener along with AudioManager.setStreamVolume to achieve this?
Related
I've been working on a navigation feature for a maps app which has voice instructions. The problem is that when announcing the instructions, the first 500 milliseconds of the instruction gets cut off. For eg, if the instruction is "In 200m turn right", in the bluetooth earphone it ends up sounding like "200m turn right". Or if the instruction is "Continue for 2 kilometers", then it sounds like "tinue for 2 kilometers".
This is the code I'm using for TTS -
//Initialisation happens only once
var textToSpeechEngine = TextToSpeech(this) { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeechEngine?.language = Locale.ENGLISH
textToSpeechEngine?.setSpeechRate(0.8f)
}
}
...
//When text to speak is ready, invoking the speak method
textToSpeechEngine?.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, null, "tts1")
Additionally, I'm also using AudioFocusRequest to request and abandon focus so that any music playing in the background lowers its volume while the instruction is being spoken. This is the code I'm using for that -
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
var focusRequest: AudioFocusRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAcceptsDelayedFocusGain(false)
.build()
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
}
textToSpeechEngine?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
fun abandonFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest?.let { request -> audioManager.abandonAudioFocusRequest(request) }
} else {
audioManager.abandonAudioFocus(null)
}
}
override fun onStart(utteranceId: String?) {}
override fun onDone(utteranceId: String?) {
abandonFocus()
}
override fun onError(utteranceId: String?) {
abandonFocus()
}
})
Point to note here is that this issue is not happening in all the bluetooth devices I've tested with. The issue happened with 2 Chinese brands bluetooth earphones but is not happening with a bluetooth Sony over-the-head headset. Also, the issue does not happen if music is playing in the background while navigation is going on.
As I understand it, it seems that the cheaper bluetooth earphones seem to keep the "connection alive" only when audio is actively coming through else it stops the connection temporarily in order to save battery I guess? However, when music is playing in the background, the connection is kept alive constantly so the instruction speech does not get cut off.
What can I do to fix this or work around it?
note that requestAudioFocus may take a OnAudioFocusChangeListener as first param, you are passing null in there (also Builder have this param). switching focus may take some (short) time, so I would recomend to fire your TTS when you get this callback fired with AUDIOFOCUS_GAIN
Assuming that you're correct that it is purely hardware at fault (which is what it seems like):
You can use the playSilence() or playSilentUtterance() methods of the TextToSpeech class to play silence for 500ms prior to your main speak() command... which should fool the speakers/headphones.
It may help to also use QUEUE_ADD instead of QUEUE_FLUSH for your main speak() request to make sure that it it attached the the previous (silence) with no gap... and that it doesn't prematurely end the first (silent) utterance.
There are a lot of variables at play here. You could consider:
Can you find an app that uses TextToSpeech which DOES work correctly even on these problematic devices? If so, then the problem must be solvable in code, and maybe you could find the source code for the app you tested and look at how they are setting up the AudioManager.
Android Telecom Manager no incoming audio/sound on an Added VOIP call
I am trying to add a VOIP video calls in my app. I have registered the phone account and added the call to TelecomManager. Call is accepted successfully. I have already implemented Connection and ConnectionService.
I am using the below code to add a call.
var uri = Android.Net.Uri.FromParts(PhoneAccount.SchemeSip, voipCallInfo.Payload?.CallerName, null);
extras.PutParcelable(TelecomManager.ExtraIncomingCallAddress, uri);
extras.PutParcelable(TelecomManager.ExtraPhoneAccountHandle, phoneAccountHandle);
telecomManager.AddNewIncomingCall(phoneAccountHandle, extras);
and I am using the below code to accept the ringing call.
var telecomManager = GetTelecomManager();
if (telecomManager == null)
{
logger.Error("Telecom Manager is null, May be permissions not granted");
return;
}
try
{
.
.
.
telecomManager.AcceptRingingCall();
.
.
}
catch (Exception ex)
{
logger.Error("RequestSystemToAnswerCall Exception : " + ex.Message);
}
I have tried to request the audio focus, but when I add a call in the telecom manager my app loses the focus because the phone starts ringing. After I accept the call app doesn't get the focus back I believe Telecom/Call has the focus but I can't hear anything. Another person on the call can hear me without a problem. When I end the call apps get the focus back.
I can see below in the logs.
2020-06-22,14:09:34.831 WebRTCManager Trace IsAudioSubscriptionEnabled True
[AudioManager] Use of stream types is deprecated for operations other than volume control
[AudioManager] See the documentation of requestAudioFocus() for what to use instead with android.media.AudioAttributes to qualify your playback use case
[AUDIO_FOCUS] Audio Focus request DENIED !
Below is the code I am using for requesting Audio.
public bool RequestAudioFocus()
{
var amanager = (AudioManager)GetSystemService(AudioService);
AudioFocusRequest audioFocusRequest;
if (Build.VERSION.SdkInt > BuildVersionCodes.O)
{
audioFocusRequest = amanager.RequestAudioFocus(new AudioFocusRequestClass.Builder(AudioFocus.Gain)
.SetAudioAttributes(new AudioAttributes.Builder().SetLegacyStreamType(Stream.VoiceCall).Build())
.SetOnAudioFocusChangeListener(this)
.Build());
}
else
{
audioFocusRequest = amanager.RequestAudioFocus(this, Stream.VoiceCall, AudioFocus.Gain);
}
Debug.WriteLine("MainActivity RequestAudioFocus audioFocusRequest : " + audioFocusRequest);
if (audioFocusRequest == AudioFocusRequest.Granted)
{
return true;
}
return false;
}
When I establish a VOIP connection without using TelecomManager. Everythings work fine. So I believe something goes wrong when I add and accept the call.
Thanks for any idea or fix.
The reason why you can't hear anything is because OpenTok obeys the result of the RequestAudioFocus. Since the audio focus request fails OpenTok will not play audio. You can either find out why RequestAudioFocus fails or download to Xamarin.OpenTok.Android 2.14.2 in order to play audio even if RequestAudioFocus fails.
You should use AudioManager.AUDIOFOCUS_GAIN or AudioManager.AUDIOFOCUS_GAIN_TRANSIENT to request the audio focus. Here you are going to request for an undefined amount of time when call is received, so using the AudioManager.AUDIOFOCUS_GAIN_TRANSIENT is highly recommended for audio focus request. Here is the code snippet.
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes mAudioAttributes =
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFocusRequest mAudioFocusRequest =
new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mAudioAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(...) // Need to implement listener
.build();
int focusRequest = mAudioManager.requestAudioFocus(mAudioFocusRequest);
switch (focusRequest) {
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
// don’t start playback
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
// actually start playback
}
By setting the listener callback function in setOnAudioFocusChangeListener you can listen to the audio focus change.
On Android N and earlier you can declare this intention without using the AudioFocusRequest object as shown below snippet. You still have to implement AudioManager.OnAudioFocusChangeListener to react to the status change. Here’s the equivalent code to the snippet above.
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int focusRequest = mAudioManager.requestAudioFocus(
..., // Need to implement listener
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
switch (focusRequest) {
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
// don't start
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
// start
}
As a final word, When your app has completed playing it’s audio, then it should abandon audio focus by calling AudioManager.abandonAudioFocus(…).
For your given code, if the OpenTok doesn't handle audio focus, You have to request the audio focus before AcceptRingingCall() method call. You can follow the official android docs.
I have the same problem. Here's a fix that probably isn't the 'right' way to do it, but works for me. In my VOIP call activity, once VOIP connection established, I kill my Connection object with:
mCallConnection.onDisconnect();
mCallConnection.destroy();
And OpenTok gains the audio focus back. Incoming audio works. I don't yet know the potential consequences of killing the Connection object early (can't perform other call functions like hold, interact with other incoming calls, etc.). But until I find another way for Connection to abandon its audio focus, I'll have to do this.
In the documentation, it is said:
The mute command is protected against client process death: if a process with an active mute request on a stream dies, this stream will be unmuted automatically.
The mute requests for a given stream are cumulative: the AudioManager can receive several mute requests from one or more clients and the stream will be unmuted only when the same number of unmute requests are received.
Well, the first paragraph is true; Whenever my process dies, all of the streams I muted are automatically unmuted.
However, no matter how many time I call setStreamMute(someStream, false) it NEVER EVER unmutes.
Last time I tried calling it over 1 million times after muting only ONCE and NOTHING happens!
Just to mention - If i unmute it in the same method I mute it - it stays unmuted. But on the next calls to the same method - it never unmutes.
I am muting in a Broadcast Receiver onReceive method, which I start using an alarm manager. So maybe it because my app was killed during the time between the muting call and the unmuting call? (But my app still stays in the RAM)
Can this problem be because I am not keeping a reference to the AlarmManager (Getting different instances each time?)
Did anyone encounter this problem?
Apperantly, there is a bug in these Android versions; Tested for versions 2.2 and 2.3.3 and the bug exists.
Looks like, if you call setStreamMute on an AudioManager object:
AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
am.setStreamMute(...., true);
and you lose your reference, then get a new reference:
am = null;
am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
No matter how many times you call am.setStreamMute(..., false) now, it will never unmutes.
I think ill report this bug now..
Lesson: Keep a static reference to your AudioManager.
#Michell Bak, thanks for giving me the idea to check whether its the Android software bug :) I've been stuck on this thing for way too much time, and I never had the idea to see if its not my fault.
I've never used that API before, but a quick Google search, returned a few results with it not working. It seems to be a bug that's still present in Android 2.3:
http://code.google.com/p/android/issues/detail?id=4235
I solve the problem by putting the audio manager variable in the application
public class myApplication extends Application {
static AudioManager am;
public void onCreate() {
super.onCreate();
am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
this.setMute(false);
}
}
Then in my activity class add this function:
private AudioManager getAM() {
return ((elpApplication)getApplication()).am;
}
and here is how I use the getAM();
private void toogleMediaPlayerMute() {
//defaultVolumn = am.getStreamVolume(AudioManager.STREAM_MUSIC);
elpApplication app = getElpApp();
Log.d("appmute", String.valueOf(app.isMute()));
if (!app.isMute()) {
getAM().setStreamVolume(AudioManager.STREAM_MUSIC, 0,
AudioManager.FLAG_PLAY_SOUND);
getAM().setStreamMute(AudioManager.STREAM_MUSIC, true);
ismute = true;
} else {
int maxVolume = getAM().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
Log.d("maxvol", String.valueOf(maxVolume));
getAM().setStreamMute(AudioManager.STREAM_MUSIC, false);
getAM().setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume,
AudioManager.FLAG_SHOW_UI);
ismute = false;
// app.setMute(ismute);
}
app.setMute(ismute);
}
I have been having the same problem w/ newer versions of the API. So, to work [around] this issue, I've implemented a bit of 'old-school' solution. If your design is such that you handle getting the byte stream / sending the byte stream directly - send a byte array filled with zeros if mute is selected:
*
...
byte[] whatToSend = realByteData;
byte [] mutedOutput = new byte[recBuffSize];
Array.setByte( mutedOutput, 0, (byte) 0);
...
if ( muteSet )
whatToSend = mutedOutput;
amountWritten = audioTrack.write(whatToSend, 0, amountRead);
*
The Google Voice Search comes with a significant delay from the moment you call it via startActivityForResult() until its dialog box is displayed, ready to take your speech.
This requires the user to always look at the screen, waiting for the dialog box to be displayed, before speaking.
So I was thinking of generating an audible signal instead of the dialog box by implementing RecognitionListener and sounding a DTMF tone in onReadyForSpeech() as in the following snippet:
#Override
public void onReadyForSpeech(Bundle params) {
Log.d(LCTAG, "Called when the endpointer is ready for the user to start speaking.");
mToneGenerator.startTone(ToneGenerator.TONE_DTMF_1);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Log.e(LCTAG, "InterruptedException while in Thread.sleep(50).");
e.printStackTrace();
} // SystemClock.sleep(50);
mToneGenerator.stopTone();
}
The tone sounds beautifully but... it is also "heard" by the microphone, arriving to the voice recognition service and always generating a recognition error ERROR_NO_MATCH.
Is there a way to work around this?
Here is a random idea, and it may very well not work.
Can you try disabling the microphone (maybe via AudioManager.setMicrophoneMute) while the tone is played?
Here's my code that's working for me, put into the onReadyForSpeech() callback of the RecognitionListener.
private void playSpeechReadyTone(){
audioManager.setMicrophoneMute(true);
MediaPlayer mediaPlayer = MediaPlayer.create(JarvisService.this, R.raw.doublebeep);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer arg0) {
audioManager.setMicrophoneMute(false);
}
});
mediaPlayer.start();
}
I'm afraid that there isn't an easy&clean way to do that. As srf appointed, you shouldn't rely on AudioManager.setMicrophoneMute(boolean), so, AFAIK, the possibles are:
Play an audio file before call SpeechRecognizer.startListening(intent):
final MediaPlayer mediaPlayer = MediaPlayer.create(JarvisService.this, R.raw.doublebeep);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer player) {
player.release();
// Safety start Speech Recognizer
mSpeechRecognizer.startListening(getSpeechRecognizerIntent());
}
});
mediaPlayer.start();
However, this solution has a problem... If may receive an RecognitionListener.onError(int error) before RecognitionListener.onReadyForSpeech being called and, in that case, you are still playing a beep sound every time (this will happen, for example, if you are not connected to the Internet and Speech Recognition is configured to work online)!
Besides, you should manage the case of cancelling the speech recognition process during audio (doublebeep) play.
Play the audio in the onReadyForSpeech callback (read original question) but use RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS with an appropriate value. In my case, my beep sound is really short (1 second max) and I set RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS to 4/5 seconds.
Also note that, as Google doc says:
Note also that certain values may cause undesired or unexpected results - use judiciously! Additionally, depending on the recognizer implementation, these values may have no effect.
I have developed a TTS based application. When playing it sound, if any other notification triggered, then TTS playing with sound breaking. (Ex: if SMS received TTS sound breaks)
What are condition under which such an issue can occur?
there is a bug in the Android API < 8. To restore the original sound, just say "nothing".
protected void speak(final String text, final int mode) {
speechParams.clear();
speechParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_NOTIFICATION));
engine.speak(text, TextToSpeech.QUEUE_ADD, speechParams);
//Bug with API level < 8: The original sound isn't restored automatically.
//So we do it by speaking nothing.
speechParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC));
this.engine.speak("", mode, speechParams);
}
}
Hope that helps.