14

In our app, I'd like to connect to a previously paired A2DP Bluetooth Speaker and direct audio playback to it, using Android v4.2 or later.

I can successfully create an A2DP profile object using this code to start the process:


/* Manifest permissions */
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.A2DP)

And the following listener to respond to the connection:

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {

    if (profile == BluetoothProfile.A2DP) {

        mBluetoothSpeaker = (BluetoothA2dp) proxy;

        // no devices are connected         
        List<BluetoothDevice> connectedDevices = mBluetoothSpeaker.getConnectedDevices();

        //the one paired (and disconnected) speaker is returned here
        int[] statesToCheck = {BluetoothA2dp.STATE_DISCONNECTED};           
        List<BluetoothDevice> disconnectedDevices = mBluetoothSpeaker.getDevicesMatchingConnectionStates(statesToCheck);



        BluetoothDevice btSpeaker = disconnectedDevices.get(0); 

        //WHAT NOW?

    }
}
public void onServiceDisconnected(int profile) {
    if (profile == BluetoothProfile.A2DP) {
        mBluetoothSpeaker = null;
    }
}
};

I'm just a little lost as to what to do now, to connect the device, and direct the audio output to it. I've tried connecting to the device, as detailed in the Android docs, with the following code, but the final BluetoothSpeaker.getConnectedDevices() call returns no connected devices.

    BluetoothSocket tmp = null;
    UUID MY_UUID = UUID.fromString("00001108-0000-1000-8000-00805f9b34fb");
    try {           
        tmp = btSpeaker.createInsecureRfcommSocketToServiceRecord(MY_UUID);

    } catch (IOException e1) {
        // TODO Auto-generated catch block
        Log.d("createRfcommSocketToServiceRecord ERROR", e1.getMessage());
    }
    mmSocket = tmp;

    try {
        // Connect the device through the socket. This will block
        // until it succeeds or throws an exception
        mmSocket.connect();
    } catch (IOException connectException) {
        // Unable to connect; close the socket and get out
        try {
            Log.d("connectException", connectException.getMessage());
            mmSocket.close();
        } catch (IOException closeException) { }
        return;
    }

    connectedDevices = mBluetoothSpeaker.getConnectedDevices();

The code does seem to connect to the device in some way though, as when I stop execution, the Bluetooth speaker annouces that it is ready to pair (as it always does when it disconnects from an audio source).

Older versions of the BluetoothA2dp seem to have a connect(BluetoothDevice device) method, but that has now been removed (as of 4.2) and I'm struggling to find any clear examples of how to programmatically connect to an A2DP device, and to direct audio output to it. Any help on how to approach either would be gratefully received.

Any advice on how to approach this would be hugely appreciated.

Ted
  • 2,525
  • 2
  • 37
  • 54
  • I'm assuming you're creating your `mProfileListener` instance *BEFORE* calling `getProfileProxy(...)`? – Squonk Mar 06 '14 at 14:57
  • Yes, the `onServiceConnected` block fires happily. – Ted Mar 06 '14 at 15:36
  • The answer to this question http://stackoverflow.com/questions/12542523/how-to-connect-to-a-bluetooth-a2dp-device and the comments on it don't seem promising. – Squonk Mar 06 '14 at 16:18
  • Thanks Squonk, I can get the device to connect (or at least fire a `BluetoothDevice.ACTION_ACL_CONNECTED` event) by calling `createInsecureRfcommSocketToServiceRecord()`, but if that post is right, then RFcomm is wrong for A2DP broadcasting anyway. What is the right way to connect to a device for A2DP? There seems to be a gaping hole in the Android Bluetooth documentation (or at least my understanding of it). – Ted Mar 06 '14 at 16:50
  • To be honest I don't know as I've never tried with A2DP but I agree there is a gap in the docs. It seems odd there are options for A2DP but no useful methods for using it (or at least no useful documentation explaining how to use it). If I come across anything I'll get back to you. – Squonk Mar 07 '14 at 00:02
  • Thanks Squonk, very much appreciated. It's good to have a bit of confirmation that I'm not just being entirely stupid. If you do spot anything related to this in the near future (and still remember this post!) I'd love to hear about it. I think for now our work around will have to be popping open the OS Bluetooth controls from our app. – Ted Mar 07 '14 at 12:32
  • Hey. Did you find any solution? Because I have the same problem as you. I cant stream the music over the speakers. – silvia_aut Jun 03 '14 at 07:36
  • Hi Silvia, sorry, but since posting this question I've been working on other aspects of this project, and for now users have to connect to Bluetooth speakers manually in Android. Have you tried the code that zxshi posted below? It'd be great to know whether it actually works or not! – Ted Jun 03 '14 at 09:11
  • Yes I tried it, but it doesnt work for me either. It's really strange, I have a code that work really fine on 4.0.3 and 4.1, but not on 4.2 anymore. I read this now several times that this doesnt work on 4.2 anymore but I still found no solution for it. – silvia_aut Jun 03 '14 at 09:26

4 Answers4

10

This one works for me. After receive BluetoothA2dp.STATE_CONNECTED, you can play music as normal.

public class A2DPActivity extends Activity {

protected static final String TAG = "ZS-A2dp";

Button mBtPlay;

BluetoothAdapter mBtAdapter;
BluetoothA2dp mA2dpService;

AudioManager mAudioManager;
MediaPlayer mPlayer;

BroadcastReceiver mReceiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context ctx, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "receive intent for action : " + action);
        if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
            int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED);
            if (state == BluetoothA2dp.STATE_CONNECTED) {
                setIsA2dpReady(true);
                playMusic();
            } else if (state == BluetoothA2dp.STATE_DISCONNECTED) {
                setIsA2dpReady(false);
            }
        } else if (action.equals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED)) {
            int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_NOT_PLAYING);
            if (state == BluetoothA2dp.STATE_PLAYING) {
                Log.d(TAG, "A2DP start playing");
                Toast.makeText(A2DPActivity.this, "A2dp is playing", Toast.LENGTH_SHORT).show();
            } else {
                Log.d(TAG, "A2DP stop playing");
                Toast.makeText(A2DPActivity.this, "A2dp is stopped", Toast.LENGTH_SHORT).show();
            }
        }
    }

};

boolean mIsA2dpReady = false;
void setIsA2dpReady(boolean ready) {
    mIsA2dpReady = ready;
    Toast.makeText(this, "A2DP ready ? " + (ready ? "true" : "false"), Toast.LENGTH_SHORT).show();
}

private ServiceListener mA2dpListener = new ServiceListener() {

    @Override
    public void onServiceConnected(int profile, BluetoothProfile a2dp) {
        Log.d(TAG, "a2dp service connected. profile = " + profile);
        if (profile == BluetoothProfile.A2DP) {
            mA2dpService = (BluetoothA2dp) a2dp;
            if (mAudioManager.isBluetoothA2dpOn()) {
                setIsA2dpReady(true);
                playMusic();
            } else {
                Log.d(TAG, "bluetooth a2dp is not on while service connected");
            }
        }
    }

    @Override
    public void onServiceDisconnected(int profile) {
        setIsA2dpReady(false);
    }

};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    LinearLayout ll = new LinearLayout(this);
    setContentView(ll);

    mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    registerReceiver(mReceiver, new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED));
    registerReceiver(mReceiver, new IntentFilter(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED));

    mBtAdapter = BluetoothAdapter.getDefaultAdapter();
    mBtAdapter.getProfileProxy(this, mA2dpListener , BluetoothProfile.A2DP);

}

@Override
protected void onDestroy() {
    mBtAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpService);
    releaseMediaPlayer();
    unregisterReceiver(mReceiver);
    super.onDestroy();
}

@Override
protected void onPause() {
    releaseMediaPlayer();
    super.onPause();
}

private void releaseMediaPlayer() {
    if (mPlayer != null) {
        mPlayer.release();
        mPlayer = null;
    }
}

private void playMusic() {
    mPlayer = new MediaPlayer();
    AssetManager assetManager = this.getAssets();
    AssetFileDescriptor fd;
    try {
        fd = assetManager.openFd("Radioactive.mp3");
        Log.d(TAG, "fd = " + fd);
        mPlayer.setDataSource(fd.getFileDescriptor());
        mPlayer.prepare();
        Log.d(TAG, "start play music");
        mPlayer.start();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

allprog
  • 16,540
  • 9
  • 56
  • 97
zxshi
  • 357
  • 3
  • 8
  • Zxshi, thank you so much for your answers. I'm glad to hear you've managed to get this working. I'm currently hiding from A2DP, and working on some other aspects of our project, but I promise I'll give these a go soon, and see if I can get them to work too. – Ted Mar 14 '14 at 09:29
  • Thanks, very helpful answer. I have a slightly different requirement. I am trying to use live audio from microphone and send to A2DP bluetooth speaker. Can anyone help me with solution? – antman Oct 25 '17 at 10:56
  • A2DP has low latency. Is there any compress method? – CleanCoder Jul 27 '20 at 10:17
2

Ted,

I have the same issue like you when I tried with BluetoothHeadset. I guess my work around may work with A2DP. Since my headset only support Handsfree profile. I am only test with BluetoothHeadset.

No need to establish RFComm channel.

For me. After you connected to BluetoothHeadsetService, 1. check whether audio is already connected

mBluetoothSpeaker.isAudioConnected(btSpeaker);

2. if not, establish audio connection

mBluetoothSpeaker.startVoiceRecognition(btSpeaker);

3. register BroadcastReceiver for BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED and BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED

registerReceiver(mReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
registerReceiver(mReceiver, new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));

4. BroadcastReceiver

protected BroadcastReceiver mReceiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        int state = BluetoothHeadset.STATE_DISCONNECTED;
        int previousState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, BluetoothHeadset.STATE_DISCONNECTED);
        if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
            state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
            if (state == BluetoothHeadset.STATE_CONNECTED) {
                mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                mInfoTextview.append("\n\nDevice name = " + mConnectedHeadset.getName());

                // Audio should not be connected yet but just to make sure.
                if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset)) {
                    Log.d(TAG, "Headset audio connected already");
                } else {
                    if (!mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset)) {
                        Log.e(TAG, "maybe you do not call stopVoiceRecognition previously");
                        mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
                        mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset);
                    }
                }
            }
            else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
                mConnectedHeadset = null;
            }
        }
        else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED))// audio
        {
            state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
                // Bluetooth audio connected. you send audio stream to headset now!!!
                mAudioManager.setMode(AudioManager.STREAM_VOICE_CALL);
                mMediaPlayer = new MediaPlayer();
                AssetManager assetManager = getAssets();
                try {
        AssetFileDescriptor fd = assetManager.openFd("Radioactive.mp3");
        mMediaPlayer.setDataSource(fd.getFileDescriptor());
                    // set audio stream type to STREAM_VOICE_CALL will send audio to headset
                    // @see <a href="http://developer.android.com/reference/android/media/AudioManager.html#startBluetoothSco()">SCO</a>
                    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
        mMediaPlayer.prepare();
        Log.d(TAG, "start play music");
        mMediaPlayer.start();
        } catch (IOException e) {
        e.printStackTrace();
        }
            }
            else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                if (mMediaPlayer != null) {
                mMediaPlayer.stop();
                mMediaPlayer = null;
                }
                mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
            }
        }   
    }
};
zxshi
  • 357
  • 3
  • 8
1

Had the same problem, but found that older post:

Programmatically connect to paired Bluetooth device

In short, in order to connect to a paired a2dp device, you simply have to invoke BluetoothA2dp.connect(myPairedA2dpDevice), but right now that method is hidden from the public API, which is not helpful. So you access it through Java reflection. It's kind of a hack, but the way Google put it, there doesn't seem to be a clean solution for now.

Community
  • 1
  • 1
Simon
  • 453
  • 5
  • 14
0
mMediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
                    .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                    //.setUsage(AudioAttributes.USAGE_ALARM)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build());

By Commenting .setUsage(AudioAttributes.USAGE_ALARM) line, it work for me.

shubomb
  • 672
  • 7
  • 20