Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Matthias1912

#1
Hi, mhh, what should I say. This is a hard decision.

I gave you 2 working solutions and spent some hours too. I do not know how your code looks like but I think I can help you if you let me, but you made such a secret about your text to speech part. But ok, I have to accept your decision.

Just as an hint, locus is now an abonnement version and I think this is a base feature (against your opinion) if you want to use it as a car navi too. If you are willing to add this feature somehow in the future you will get a new gold user.

Thanks for trying.

Kind regards
Matthias
#2
Hi Menion,

your modification Is not working. It is like before. (SCO enabled successfully, speach comes successfully but after finishing speaking there is no SCO release). I think you are not calling "abandonAudioFocus(null)" from your AudioManager instance. Can you please look inside my function "onPlaybackDoneOrError". When I do not call "abandonAudioFocus(null)" I have the same behavior like your implementation.

For testing, you can use almost every Bluetooth communication headset. All headsets have HFP support (using SCO). With enabled SCO support (of your code), your headset use this interrupt channel and you can test your implementation.

Test condition for SCO without car radio are similar to A2DP normal music audio modus:
run amazon music or other music player in background and call your SCO speech output. It should interrupt (stop) music, speech output should appear and after finishing speaking the music should come back.

When this SCO release problem is solved, your implementation will work perfectly.

Kind regards
Matthias
#3
Hi Menion,
sorry but I cannot agree with you. Your SCO code is almost ready, it is now fine tuning.

I think I am not the only one want to have this feature. The most people don't talk to the programmers they just move to another app. Osmand for example has this feature but I'm a locus user since the first hour and I like to see that locus has this feature too.

By the way, I changed my code to an event version. I tried to assume what is in your functions. There is only one sleep command left, and this is only for a timeout condition.



package com.example.mysay;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import java.util.Calendar;
import java.util.Locale;
import static android.content.Context.AUDIO_SERVICE;

public class mySay
{
/* Please define the following in AndroidManifest.xml
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
*/

/* Using this class

Global define: private mySay _say;
in onCreate: _say = new mySay(this);
in onDestroy: _say.TTS_Close();
in functions: _say.say("Das ist eine Testausgabe", Locale.GERMANY, true);
*/

private Context context;

public class TTS_Params_STRUCT
{
public TextToSpeech speech;
public AudioManager am;
public BroadcastReceiver receiver;
public boolean use_SCO;
public boolean SCO_available;
public boolean SCO_connected;
public Locale locale;
public int OLD_AUDIO_MODE;
}
public TTS_Params_STRUCT TTS_Params = new TTS_Params_STRUCT();

private long get_timestamp_ms()
{
Calendar cal_today = Calendar.getInstance();
java.util.Date date_today = cal_today.getTime();
return date_today.getTime();
}

public mySay(Context c)
{
context = c;

onPlaybackInit();
}

private void onPlaybackInit()
{
try
{
TTS_Params.speech = new TextToSpeech(context, new TextToSpeech.OnInitListener()
{
@Override
public void onInit(int status)
{
if (status == TextToSpeech.SUCCESS)
{
TTS_Params.speech.setOnUtteranceProgressListener(new UtteranceProgressListener()
{
@Override
public void onStart(String utteranceId) {
onPlaybackStarted(utteranceId);
}

@Override
public void onDone(String utteranceId) {
onPlaybackDoneOrError(utteranceId);
}

@Override
public void onError(String utteranceId) {
onPlaybackDoneOrError(utteranceId);
}
});

TTS_Params.receiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction() == AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
{
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
switch (state)
{
case AudioManager.SCO_AUDIO_STATE_CONNECTING:
break;
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
TTS_Params.SCO_connected = true;
break;
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
case AudioManager.SCO_AUDIO_STATE_ERROR:
TTS_Params.SCO_connected = false;
break;
default:
break;
}
}
}
};

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
context.registerReceiver(TTS_Params.receiver, intentFilter);
}
}
});

TTS_Params.am = (AudioManager)context.getSystemService(AUDIO_SERVICE);

TTS_Params.use_SCO = false;
TTS_Params.SCO_connected = false;
TTS_Params.locale = Locale.getDefault();
TTS_Params.OLD_AUDIO_MODE = -1;

// Check if HFP (SCO) is available
TTS_Params.SCO_available = false;
AudioDeviceInfo[] devices =  TTS_Params.am.getDevices (AudioManager.GET_DEVICES_OUTPUTS);
for (int i=0; i<devices.length; i++)
{
if (devices[i].getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) TTS_Params.SCO_available = true;
}
}
catch (Exception ex) { }
}

public void TTS_Close()
{
try { TTS_Params.speech.stop(); }
catch (Exception ex) { }

try { TTS_Params.speech.shutdown(); }
catch (Exception ex) { }

try { context.unregisterReceiver(TTS_Params.receiver); }
catch (Exception ex) { }

TTS_Params.speech = null;
}

private void onPlaybackStarted(String utteranceId)
{
// onPlaybackStarted is called simultaneously from android with the TTS.speak(...) command
// if you want to use SCO, this function isn't waiting for SCO connected
// all the SCO function calls must be started bevore speak command is called
// -> all this is done in the function say(..)
}

private void onPlaybackDoneOrError(String utteranceId)
{
try
{
TTS_Params.am.stopBluetoothSco();
TTS_Params.am.setBluetoothScoOn(false);
if (TTS_Params.OLD_AUDIO_MODE != -1) TTS_Params.am.setMode(TTS_Params.OLD_AUDIO_MODE);
//TTS_Params.am.setSpeakerphoneOn(true);

TTS_Params.am.abandonAudioFocus(null);
}
catch (Exception ex) { }
}

public void say(final String text, Locale locale, boolean use_SCO)
{
// use SCO = true (HFP Mode = CALL Mode)
// use SCO = false (A2DP Mode = Music Mode)
TTS_Params.use_SCO = use_SCO;

TTS_Params.locale = locale;

new Thread(new Runnable()
{
public void run()
{
try
{
TTS_Params.speech.setSpeechRate(1.0f);
TTS_Params.speech.setPitch(1.0f);
int result = TTS_Params.speech.setLanguage(TTS_Params.locale);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED)
{
TTS_Params.speech.setLanguage(Locale.getDefault());
}

TTS_Params.OLD_AUDIO_MODE = TTS_Params.am.getMode();

if (TTS_Params.use_SCO && TTS_Params.SCO_available)
{
TTS_Params.am.setMode(AudioManager.MODE_IN_CALL);        // MODE_IN_CALL, MODE_IN_COMMUNICATION
TTS_Params.am.setBluetoothScoOn(true);
TTS_Params.am.startBluetoothSco();
//TTS_Params.am.setSpeakerphoneOn(false);

TTS_Params.am.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

long timeout = get_timestamp_ms() + 2000; // Wait for SCO is connected (comes from Broadcast Event) but wait max 2s
while (!TTS_Params.SCO_connected && get_timestamp_ms() < timeout)
{
try { Thread.sleep(100); } // Sleep to save CPU power during waiting
catch (Exception ex) { }
}
}
else
{
TTS_Params.am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}

TTS_Params.speech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "UtteranceIdTextToSpeechOutput1234");
}
catch (Exception ex) { }
}
}).start();
}
}

#4
Hi Menion,
thanks for your answer. Yes of course, the Thread.sleep method is quick and dirty, but it was only for demonstration. Can you please share some more code to see how you do it and to find the differences. Or maybe you are willing to create a short app?

I want to understand your implementation so that I can help because I need this functionality. I think the setOnUtteranceProgressListener is defined in the onInit part where my speech_init_finished = true is set. But what is behind your onPlaybackStarted and onPlaybackDone functions? How do you start the tts? Where do you set the AudioManager / SCO parameter? Are you using the same tts implementation for the SCO and "normal music channel" parts (switching by a variable like mine: "use_HFP")?
#5
Hi Menion,
so I tested my code. I do not know how much you are using from my sample. I got it working with the following changes.

100ms delay after each "speak" command
and after finishing speaking, I added a 100ms delay too and then I called "am.abandonAudioFocus(null);" to release the Audio Focus.


public boolean say(final String text, final Locale locale, final boolean use_HFP, final int audio_delay_HFP_ms, final int SCO_timeout_ms)
{
    if (speech_busy) return false;
    if (speech == null) return false;
    speech_busy = true;

    Toast.makeText(context, "active", Toast.LENGTH_SHORT).show();

    new Thread(new Runnable()
    {
        public void run()
        {
            BroadcastReceiver receiver = null;

            try
            {
                if (speech_init_finished != true) throw new Exception();

                speech.setSpeechRate(1.0f);
                speech.setPitch(1.0f);
                int result = speech.setLanguage(locale);
                if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) throw new Exception();

                AudioManager am = (AudioManager)context.getSystemService(AUDIO_SERVICE);
                int OLD_AUDIO_MODE = am.getMode();

                // Check if HFP (SCO) is available
                boolean SCO_available = false;
                AudioDeviceInfo[] devices = am.getDevices (AudioManager.GET_DEVICES_OUTPUTS);
                for (int i=0; i<devices.length; i++)
                {
                    if (devices[i].getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) SCO_available = true;
                }

                if (use_HFP && SCO_available)
                {
                    speech_SCO_connected = false;

                    receiver = new BroadcastReceiver()
                    {
                        @Override
                        public void onReceive(Context context, Intent intent)
                        {
                            if (intent.getAction() == AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
                            {
                                int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
                                if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) speech_SCO_connected = true;
                                else speech_SCO_connected = false;
                            }
                        }
                    };
                    IntentFilter intentFilter = new IntentFilter();
                    intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
                    context.registerReceiver(receiver, intentFilter);

                    am.setMode(AudioManager.MODE_IN_COMMUNICATION);        // MODE_IN_CALL, MODE_IN_COMMUNICATION
                    am.setBluetoothScoOn(true);
                    am.startBluetoothSco();
                    am.setSpeakerphoneOn(false);

                    am.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

                    // wait for SCO Connected with a max. timeout of 2,5s
                    long timestamp_ms_Start = get_timestamp_ms();
                    while (speech_SCO_connected == false && (get_timestamp_ms() - timestamp_ms_Start) < SCO_timeout_ms)
                    {
                        // wait
                        try { Thread.sleep(100); }
                        catch (Exception ex) { }
                    }

                    if (speech_SCO_connected == true)
                    {
                        // additional delay to prevent missing the initial words
                        try { Thread.sleep(audio_delay_HFP_ms); }
                        catch (Exception ex) { }

                        speech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "UtteranceIdTextToSpeechOutput");
                        try { Thread.sleep(100); } // delay ms
                        catch (Exception ex) { }
                        while (speech.isSpeaking()) { } // warten bis das sprechen fertig ist
                    }

                    am.stopBluetoothSco();
                    //am.setBluetoothScoOn(false);
                    am.setMode(OLD_AUDIO_MODE);
                    am.setSpeakerphoneOn(true);
                }
                else
                {
                    am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                    speech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "UtteranceIdTextToSpeechOutput");
                    try { Thread.sleep(100); } // delay ms
                    catch (Exception ex) { }
                    while (speech.isSpeaking()) { } // warten bis das sprechen fertig ist
                }

                try { Thread.sleep(100); } // delay ms
                catch (Exception ex) { }
                am.abandonAudioFocus(null);
            }
            catch (Exception ex) { }

            try
            {
                if (receiver != null) context.unregisterReceiver(receiver);
            }
            catch (Exception ex) { }

            speech_busy = false;

        }
    }).start();

    return true;
}


Can you please change this in locus pro code, so I can test it again? I will not wait another 3 years again, I promise.  ;)
#6
Hi Menion,
Yes, 3 years, shame on me. I checked my code and you are right, there is no SCO release.
I will test my code with my car radio this evening.
I think I know what is missing I will inform you when I finished my test.
#7
Hi Menion,
sorry for my late answer, I got no email notification from your second post. Last week I ended up with the same problem (BT SCO) again and I found my own post 😉
So short answer, I tested your implementation and TTS over BT SCO works perfect. But you have to release the BT SCO channel after finishing the TTS, otherwise the channel stays open and it does not switch back to car music.
If you modify your code, will this modification be only available in Locus 4 or also to Locus Pro?
#8
Hi developer(s),

I do have a problem with the bluetooth audio mode. Since garmin kicked the navigon app for car navigation I like to use locus as a replacement. But my problem is that the audio output need to route over BT HFP so that my car radio can interrupt the radio for the navigation speech output. For now the only working mode is A2DP. But there is now interrupt mode, my car radio need to stay in Bluetooth music mode. In this mode it is impossible to listen music with the car radio when there is now navigation instruction. For that I wrote a little class to show you what I mean or how to solve this problem.

Can you please include the functionality for Bluetooth HFP, so that the navigation route instructions are played by speech output over Bluetooth HFP?

Best regards from Germany
Matthias


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import java.util.Calendar;
import java.util.Locale;
import static android.content.Context.AUDIO_SERVICE;

public class mySay
{
/* Please define the following in AndroidManifest.xml
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
*/

/* Using this class

Global define: private mySay _say;
in onCreate: _say = new mySay(this);
in functions: _say.say("Das ist eine Testausgabe", Locale.GERMANY, true, 2000, 2500);
*/

private Context context;

private TextToSpeech speech = null;
private boolean speech_init_finished = false;
private boolean speech_busy = false;
private boolean speech_SCO_connected = false;

public mySay(Context c)
{
context = c;

speech = null;
speech_init_finished = false;
speech_busy = false;
speech_SCO_connected = false;

speech = new TextToSpeech(context, new TextToSpeech.OnInitListener()
{
@Override
public void onInit(int status)
{
if (status == TextToSpeech.SUCCESS) speech_init_finished = true;
}
});
}

public boolean isInitialized()
{
return speech_init_finished;
}

public boolean isBusy()
{
return speech_busy;
}

public void close()
{
try { speech.stop(); }
catch (Exception ex) { }

try { speech.shutdown(); }
catch (Exception ex) { }

speech_init_finished = false;
speech = null;
}

private long get_timestamp_ms()
{
Calendar cal_today = Calendar.getInstance();
java.util.Date date_today = cal_today.getTime();
return date_today.getTime();
}

public boolean say(final String text, final Locale locale, final boolean use_HFP, final int audio_delay_HFP_ms, final int SCO_timeout_ms)
{
if (speech_busy) return false;
if (speech == null) return false;
speech_busy = true;

new Thread(new Runnable()
{
public void run()
{
BroadcastReceiver receiver = null;

try
{
if (speech_init_finished != true) throw new Exception();

speech.setSpeechRate(1.0f);
speech.setPitch(1.0f);
int result = speech.setLanguage(locale);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) throw new Exception();

AudioManager am = (AudioManager)context.getSystemService(AUDIO_SERVICE);
int OLD_AUDIO_MODE = am.getMode();

// Check if HFP (SCO) is available
boolean SCO_available = false;
AudioDeviceInfo[] devices = am.getDevices (AudioManager.GET_DEVICES_OUTPUTS);
for (int i=0; i<devices.length; i++)
{
if (devices[i].getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) SCO_available = true;
}

if (use_HFP && SCO_available)
{
speech_SCO_connected = false;

receiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction() == AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
{
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) speech_SCO_connected = true;
else speech_SCO_connected = false;
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
context.registerReceiver(receiver, intentFilter);

am.setMode(AudioManager.MODE_IN_COMMUNICATION);        // MODE_IN_CALL, MODE_IN_COMMUNICATION
am.setBluetoothScoOn(true);
am.startBluetoothSco();
am.setSpeakerphoneOn(false);

am.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

// wait for SCO Connected with a max. timeout of 2,5s
long timestamp_ms_Start = get_timestamp_ms();
while (speech_SCO_connected == false && (get_timestamp_ms() - timestamp_ms_Start) < SCO_timeout_ms)
{
// wait
try { Thread.sleep(100); }
catch (Exception ex) { }
}

if (speech_SCO_connected == true)
{
// additional delay to prevent missing the initial words
try { Thread.sleep(audio_delay_HFP_ms); }
catch (Exception ex) { }

speech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "UtteranceIdTextToSpeechOutput");
while (speech.isSpeaking()) { } // warten bis das sprechen fertig ist
}

am.stopBluetoothSco();
am.setMode(OLD_AUDIO_MODE);
am.setSpeakerphoneOn(true);
}
else
{
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
speech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "UtteranceIdTextToSpeechOutput");
while (speech.isSpeaking()) { } // warten bis das sprechen fertig ist
}
}
catch (Exception ex) { }

try
{
if (receiver != null) context.unregisterReceiver(receiver);
}
catch (Exception ex) { }

speech_busy = false;
}
}).start();

return true;
}
}