Author Topic: Audio Output over Bluetooth HFP  (Read 201 times)

Offline Matthias1912

  • Newbie
  • *
  • Posts: 1
    • View Profile
Audio Output over Bluetooth HFP
« on: July 27, 2018, 10:04:35 »
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

Code: [Select]
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;
}
}
 

Offline menion

  • Administrator
  • Professor of Locus
  • *****
  • Posts: 10792
  • Thanked: 176 times
    • View Profile
    • http://www.asamm.com
  • Device: SGS7
Re: Audio Output over Bluetooth HFP
« Reply #1 on: July 31, 2018, 18:59:20 »
Good day Matthias,
thank you for a very complex part of TTS system source code. I think I got a point of your solution and most probably understand how it works. Anyway, I also have to say that Locus Map is made mainly for hike & bike usage where your solution has no benefit. For use in car, I see the main problem with additional permission, additional settings (something like "Enable support for BT HFP) that will be in app, and also inability to test such feature (because I do not have a car with BT support).
Usually, new ideas are placed on our help.locusmap.eu web site, but such a complicated idea will be lost there between around 1000 interesting, not yet implemented, ideas.
So sorry, I think that such improvement does not belong directly to scope & plans of Locus Map app.
Thanks for understanding.
Ideas, wishes, problems
Advanced topics, public discussion, sharing of knowledges, testing beta versions: you're here!