Access data from SCO Socket - android

I have 2 Android phones and I have managed to convert one of them as Headset (Yes, I did root the phone for that).
When an incoming call is received to the phone, the headset-emulated phone is able to take headset related actions like - pick call, disconnect call, etc. However, only functionality that's missing is the transfer of Audio from Phone to headset-emulated-device.
Does anyone have prior experience in establishing SCO connection between 2 android phones and access voice data ?
(As of now, I am not concerned of Sound quality).
I came across A2DP, but it's only uni-directional. Only SCO is bidirectional voice data transfer.
Any help or pointers will be highly appreciated.

I have done this with reflection. It only worked on certain devices though. Not all. Although I didn't root the device.
Class cls = Class.forName("android.bluetooth.BluetoothDevice");
Method method = cls.getDeclaredMethod("createScoSocket", (Class[])null);
BluetoothSocket scoSocket = (BluetoothSocket)method.invoke(device, null);
scoSocket.connect();
Also there is the AudioManager.startBluetoothSco() and AudioManager.setBluetoothScoOn(true) methods that you have to use when playing your audio.

Related

Android WebRTC cant send DTMF

In my Android WebRTC client to send DTFM tones I use code like this
val audioSource = peerConnectionFactory.createAudioSource(MediaConstraints())
val localAudioTrack = peerConnectionFactory.createAudioTrack("audio", audioSource)
peerConnection.addTrack(localAudioTrack)
peerConnection?.senders?.firstOrNull()?.let {
it.dtmf()?.insertDtmf(code, 400, 50)
}
But it seems tone does not reach a peer, and there is erro message in logcat
dtmf_sender.cc E (line 126): InsertDtmf is called on DtmfSender that can't send DTMF.
No matter what device I use.
Why could it happen?
There are multiple reasons why this could happen, one of them being that the other party in the WebRTC connection does not support the RTP telephone-event
Also, check this example: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Using_DTMF
(haven't tried it myself though)
Pay attention to this:
"Note, however, that although it's possible to send DTMF using WebRTC, there is currently no way to detect or receive incoming DTMF. WebRTC currently ignores these payloads; this is because WebRTC's DTMF support is primarily intended for use with legacy telephone services"
I have found, problem is that peers are not connected via RTP. I mean WebRTC did not found yet suitable route, basing on ICE candidates gathered, to pass audio traffic.
As only route is constructed, traffic goes on, and sender comes ready to send DTMF tones.
To be ensured that peers are ready to trancieve media, you may look on connection state in PeerConnection.Observer.onIceConnectionChange(), and get sender when state comes to "CONNECTED".

Android communicating to HFP device via AT commands

im trying to control a bluetooth bracelet with vibration function via HFP (hands free profile) in Android. I've been able to connect to the bracelet and access the input- and outputstream.
My goal is to simulate an incoming call so that the bluetooth bracelet starts vibrating (which seems to be the only way to do that). To do this, im using AT commands. In the bluetooth specs at https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=238193 on page 22 you can see the handshake to establish service level connection.
I need to establish this connection to use the "+CIEV" command (see handshake page 48).
But when my bracelet returns the command "AT+CIND=?" I dont know how to respond. I can't find any hints on how to answer with the "CIND:" command. Also I dont know how to send the acknowledgement (is it just "OK"?).
That might even be the completely wrong way to do this. Every suggestion is appreciated. I only found one post on stackoverflow that helped me in some way, rest of the posts I found were unanswered.
By the way, im using a smartphone with Android 4.1.2. The bracelet supports HFP and HSP. Thanks in advance.
UPDATE 10/29/2014
===== Connection through RFCOMM Socket established at this point =====
// read AT+BRSF=0 from device
byte[] buffer = new byte[200];
mBluetoothSocket.getInputStream().read(buffer);
Log.d(TAG, new String(buffer).trim());
//write answer BRSF: ...
mBluetoothSocket.getOutputStream().write("+BRSF=20\r".getBytes());
mBluetoothSocket.getOutputStream().write("OK\r".getBytes());
// read AT+CIND=? command
buffer = new byte[200];
mBluetoothSocket.getInputStream().read(buffer);
Log.d(TAG, new String(buffer).trim());
//write answer CIND: ...
mBluetoothSocket.getOutputStream().write("+CIND: (\"battchg\",(0-5)),(\"signal\",(0-5)),
(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3)),
(\"callheld\",(0-2)),(\"roam\",(0,1))".getBytes());
mBluetoothSocket.getOutputStream().write("OK".getBytes());
// read AT+CIND?
buffer = new byte[200];
mBluetoothSocket.getInputStream().read(buffer);
Log.d(TAG, new String(buffer).trim());
Following the procedure of the protocol, I should receive the "AT+CIND?" command and then I could send the command "+CIND: 5,5,1,0,0,0,0", but...I dont receive the "AT+CIND?" command. Actually im not receiving anything. Am I missing something? Sending an "OK" doesnt change anything btw...
I was fiddeling with exaclty the same problem. After days of trial and error, I finally got it to work.
I think it depends on the speed at wich you answer the HF's commands, as well as on the correct line-endings ("COMMAND").
Here is my DroidScript which works. It's not cleaned up, but it works.
https://gist.github.com/t-oster/68a568ac4c4e133f67ac
Also, the one example I found that seemed to almost work, it's expecting the responses to be top and tailed with crlf:
"\r\n+BRSF=20\r\n"
"\r\nOK\r\n"
Still struggling with the rest of it myself.
refer to bluetooth hfp 1.5 spec in which you can understand CIEV response
normally when not in any call setup, response can be +CIND = 1,0,0,0,5,0,5
Note these values are based on the hfp spec, on incoming call return +CIEV: ,
ind- indicator for callsetup and value as 1 and then RING commands to the bracelet

Route audio to bluetooth A2DP device while ringing [Android]

I am trying to identify how to route a very short audio stream (a notification) to a bluetooth headphone that is already paired with the device, while the device is ringing.
When I play any audio at any time, it is routed to the bluetooth device, no problem.
But if I try to start playing the audio when receiving an android.intent.action.PHONE_STATE, in RINGING state, the audio is not routed as expected.
I can see that the AudioManager's setBluetoothA2dpOn method has became deprecated, but I actually tried it but is seems has no effect.
I have tried the MediaRouter object, but I can see that MediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO) points to the RouteInfo of the Bluetooth device while the device is ringing, and the AudioManager.isBluetoothA2dpOn is true.
So, can any one tell me why the audio route is like this in the ringing moment? is there any way to force the audio to be routed to the Bluetooth device in such case?
[UPDATED]
I have tried again today and I have discovered something that may be the cause of the problem.
I have created a BroadcaseReceiver to detect the change in the android.intent.action.PHONE_STATE. if an intent is received and the state is currently ringing, check for AudioManager's mode and you will find it is MODE_NORMAL. but few seconds later the phone will start actually ringing and the mode is going to be changed into MODE_RINGTONE. trying to manually set the mode using the method setMode(AudioManager.MODE_NORMAL) is useless then, the state remains MODE_RINGTONE even after setting it to MODE_NORMAL.
Now, I think the cause of the problem is that in the MODE_RINGTONE mode, all the streams are directed to the phone speaker and here there is no way offered by the android system to change the mode.
I think the Media player realease the bluetooth connection when the phone is ringing. You can try to obtain the bluetooth audio connection and see if Media player now play through your obtained connection. You can use my class at my answer Using the Android RecognizerIntent with a bluetooth headset and see if it works. The audio in the class is Sco only.
As stated in the JavaDoc for the StartBluetoothSco method:
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.`
I tried to start sco then play a music clip in normal mode, it played to the Bluetooth headset without problems, although I couldnot stop the microphone that caused the input stream plays to the headset. I then tried to call my target phone from another phone while the music is still playing, I found that the stream has got redirected automatically to the phone speaker. After the ringing mode is finished, the stream did not get redirected again to the Bluetooth headset and I think that behavior is normal according to what is stated in the JavaDoc above.
My guessing is that Android tries to protect the ringing and in-call modes as possible in order not to allow any unwanted interference from applications. In other words, when in ringing mode then no sound is going to be played to the headset until the call is accepted, and you cannot even change the AudioManager mode from ringing to another mode, your call for mode setter will be ignored.
I have tried the AudioTrack instead of MediaPlayer, but that makes no difference.
I have then tried the TextToSpeech engine like this:
in the main activity, initialize on create:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textToSpeech=new TextToSpeech(this, this);
textToSpeech.setLanguage(Locale.US);
textToSpeech.addEarcon("[wwww]", "XXXXXXXXXXXXXXXXXXXXXXX", R.raw.a);
}
in the broadcast receiver class when rining starting the Bluetooth utility class and adding the below to the onScoAudioConnected method
textToSpeech.playEarcon("[wwww]", TextToSpeech.QUEUE_FLUSH, null);
This did not work too.

Receive audio via Bluetooth in Android

I want to create an Android application that is capable of receiving an audio stream. I thought of using the A2DP profile, but is seems as if Android doesn't support A2DP sink. Looks like there are a lot of people that's searching for a solution for this problem. But what about receiving an ordinary bit stream, and then convert the data into audio in the application? I was thinking of receiving an PCM or Mp3 data stream via the RFCOMM (SPP Bluetooth profile), and then play it using AudioTrack.
First, how do I receive a bit stream on my Android phone via the RFCOMM? And is it possible to receive a bit stream via RFCOMM as a PCM or Mp3 stream?
Second, if it isn't possible to receive a bit stream via RFCOMM as a PCM or Mp3 stream, how do I convert the received bit stream into audio?
Third, how do I convert the received data into audio AND play the audio simultaneously, in "real time"? Can I just use onDataReceived?
To be clear, I'm not interested of using the A2DP profile! I want to stream the data via the RFCOMM (SPP Bluetooth profile). The received data stream will be in PCM or Mp3. I thought of writing my own app, but if anyone knows of an app to solve this I'd be glad to hear about it! I'm using Android 2.3 Gingerbread.
/Johnny
No. Trying to write an Android application that handles this will not be the solution. At least if you want to use A2DP Sink role.
The fact is that Android, as you mentioned it, does not implement the API calls to BlueZ (the bluetooth stack Android uses till Jelly Bean 4.1) regarding A2DP sink capabilities. You have to implement them yourself. I will try to guide you, as I was also interested in doing this my self in the near past.
Your bluetooth-enabled Android device is advertising itself as an A2DP source device by default. You have to change this first, so nearby devices may recognize your device as a sink. To do this, you must modify the audio.conf file (usally located in /etc/bluetooth/) and make sure the Enable key exists and the value Source is attached to this key, so you will get something like :
Enable=Source
Reboot, nearby devices should now recognize your device as an A2DP sink.
Now you will have to interact with BlueZ to react appropriately when an A2DP source device will start to stream audio to your phone.
Android and BlueZ are talking to each other via D-BUS. In fact, Android connects to the DBUS_SYSTEM channel and listens to every BlueZ advertisement, such as events, file descriptors ...
I remember having successfully bound my self using a native application to this d-bus channel and got access to the various events BlueZ was posting. This is relatively easy to achieve using as reference, the BlueZ API available here. If you go this way, you will have to build a native application (C/C++) and compile it for your platform. You must be able to do this using the Android NDK.
If you find it difficult to use D-BUS, you can try this Java library I just found that handles the communication to D-BUS for you : http://jbluez.sourceforge.net/. I have never used it but it is worth a try in my opinion.
What you really have to do is find out when an A2DP source device is paired to your phone and when he starts to stream music. You can retrieve these events through D-BUS. Once somebody will try to stream music, you need to tell BlueZ that your native application is going to handle it. There is a pretty good document that explains the flow of events that you should handle to do this. This document is accessible here. The part you're interested in comes on page 7. The sink application in the given example is PulseAudio but it could be your application as well.
BlueZ will forward you a UNIX socket when you will call the org.bluez.MediaTransport.Acquire method. Reading on this socket will give you the data that are currently streamed by the remote device. But I remember having been told by a guy working on the BlueZ stack that the data read on this socket are not PCM pure audio, but encoded audio content instead. The data are generally encoded in a format called SBC (Low Complexity Subband Coding).
Decoding SBC is not very difficult, you can find a decoder right here.
The ultimate step would be to forward the PCM audio to your speakers.
To prevent you from getting stuck and in order to test your application in an easier manner, you can use the d-bus binary that should be available on your Android system. He is located in /system/bin.
Quick tests you can make before doing anything of the above might be :
Get Devices list :
dbus-send --system --dest=org.bluez --print-reply /
org.bluez.Manager.GetProperties
This returns an array of adapters with their paths. Once you have these path(s) you can retrieve the list of all the bluetooth devices paired with your adapter(s).
Get paired devices :
dbus-send --system --print-reply --dest=org.bluez
/org/bluez/{pid}/hci0 org.bluez.Adapter.GetProperties
This gives you the list of paired devices whithin the Devices array field.
Once you have the list of devices paired to your Bluetooth Adapter, you can know if it is connected to the AudioSource interface.
Get the devices connected to the AudioSource interface :
dbus-send --system --print-reply --dest=org.bluez
/org/bluez/{pid}/hci0/dev_XX_XX_XX_XX_XX_XX
org.bluez.AudioSource.GetProperties
org.bluez.Manager.GetProperties
Hope this helps.
Another work around is using HandsFreeProfile.
in Android, BluetoothHeadset is working on that.
Wait until status changed to BluetoothHeadset.STATE_AUDIO_CONNECTED.
then you can record audio from bluetooth headset.
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setOutputFile(mFilename);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mMediaRecorder.start();
[Irrelevant but works] This hack serves only mp3 streaming via WIFI hotspot (I use it in my car which has only AUX input):
Install the app AirSong,
Turn on wifi hotspot,
Connect the other device to that hotspot,
Access 192.168.43.1:8088 from the device's browser and you are on.
(wondering why "192.168.43.1" only? because thats the default gateway of any device connected to Android Hotspot)
audio.conf seems to be missing in Android 4.2.2?
To receive pcm audio stream via rfcomm , you can use code flow as a hint explained (Reading Audio file in C and forwarding over bluetooth to play in Android Audio track) , with a change . change freq used while initializing from 44100 to 22050
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,22050,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_8BIT,10000, AudioTrack.MODE_STREAM);
note:This streaming still consists some noise but your
"receiving an PCM data stream via the RFCOMM (SPP Bluetooth profile), and then play it using AudioTrack."
will work.

How to capture or reroute navigation voice stream

There is a problem when using Google Navigation on Android with a Bluetooth device. Navigation sends the audio for the turn-by-turn instructions over the A2DP stream. I have a Motorola T605 Bluetooth car kit and it supports HFP and A2DP. I often just listen to the old fashioned car radio though. In this scenario I never hear turn-by-turn directions because my live A2DP stream is being sent to the radio AUX input but I am listening to the radio instead. My app has a feature where it reads out SMS messages to me. I have it set up so you can pick the stream you want to use for this. I prefer using AudioManager.STREAM_VOICE_CALL since it uses a dedicated separate speaker on the T605.
I have looked into a few ways to handle this:
1) Reroute notifications to Bluetooth SCO instead of A2DP. I have not fund a way to do this. I am able to send TTS messages I create over this path and it works great (I have an SMS reader built into my app). I tried activating SCO but the turn-by-turn instructions stream moves to the phone internal speaker instead of the voice call stream like I would prefer.
2) Capture the navigation messages and echo them back over the SCO path. I can't find a way to intercept or capture the navigation stream though. Does Google Navigation use the TTS engine or its own methods to create the voice output? It would be great to just get the directions in a text string since I could easily send that to the TTS engine and route it where I want like I do with SMS strings.
You can see my app source here: http://code.google.com/p/a2dpvolume/
Any thoughts?
Unfortunately I think you're SOOL when it comes to rerouting the navigation messages to BT SCO. I would expect the navigation app to use either the TTS or NOTIFICATION stream type to play the messages, and both of those stream types follow the MEDIA routing strategy (for NOTIFICATIONs that is at least typically true when there's no active voice call).
Although the behavior is up to each vendor to decide, my guess is that you'll find that in most implementations streams that follow the MEDIA routing strategy won't ever be routed to BT SCO, except during an ongoing voice call that is routed to BT SCO.
The best you could do in terms of routing is to force the routing to the loudspeaker, or to "anything but A2DP" (which means wired headset if one is attached, or the loudspeaker otherwise).
Here's how you could do that (I haven't verified that this works on every phone out there):
Class audioSystemClass = Class.forName("android.media.AudioSystem");
Method setForceUse = audioSystemClass.getMethod("setForceUse",
int.class,
int.class);
// 1 == FOR_MEDIA, 10 == FORCE_NO_BT_A2DP (FORCE_SPEAKER would be 1).
setForceUse.invoke(null, 1, 10);

Categories

Resources