所以我正在构建这个应用程序以支持蓝牙耳机。所以这是我的代码:
的AndroidManifest.xml
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private int activitiesCount;
private BluetoothControllerImpl bluetoothController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bluetoothController = new BluetoothControllerImpl(this);
}
@Override
protected void onPause() {
onActivityPaused();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
onActivityResume();
}
protected void onActivityResume() {
if (activitiesCount++ == 0) { // on become foreground
bluetoothController.start();
}
}
protected void onActivityPaused() {
if (--activitiesCount == 0) { // on become background
bluetoothController.stop();
}
}
private boolean isInForeground() {
return activitiesCount > 0;
}
private class BluetoothControllerImpl extends BluetoothController {
public BluetoothControllerImpl(Context context) {
super(context);
}
@Override
public void onHeadsetDisconnected() {
Log.d(TAG, "Bluetooth headset disconnected");
}
@Override
public void onHeadsetConnected() {
Log.d(TAG, "Bluetooth headset connected");
if (isInForeground() && !bluetoothController.isOnHeadsetSco()) {
bluetoothController.start();
}
}
@Override
public void onScoAudioDisconnected() {
Log.d(TAG, "Bluetooth sco audio finished");
bluetoothController.stop();
if (isInForeground()) {
bluetoothController.start();
}
}
@Override
public void onScoAudioConnected() {
Log.d(TAG, "Bluetooth sco audio started");
}
}
}
BluetoothController.java
public abstract class BluetoothController {
private Context mContext;
private BluetoothAdapter mBluetoothAdapter;
private AudioManager mAudioManager;
private boolean mIsCountDownOn;
private boolean mIsStarting;
private boolean mIsOnHeadsetSco;
private boolean mIsStarted;
private static final String TAG = "BluetoothController";
/**
* Constructor
*
* @param context
*/
public BluetoothController(Context context) {
mContext = context;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
/**
* Call this to start BluetoothController functionalities.
*
* @return The return value of startBluetooth()
*/
public boolean start() {
if (!mIsStarted) {
mIsStarted = true;
mIsStarted = startBluetooth();
}
return mIsStarted;
}
/**
* Should call this on onResume or onDestroy.
* Unregister broadcast receivers and stop Sco audio connection
* and cancel count down.
*/
public void stop() {
if (mIsStarted) {
mIsStarted = false;
stopBluetooth();
}
}
/**
* @return true if audio is connected through headset.
*/
public boolean isOnHeadsetSco() {
return mIsOnHeadsetSco;
}
public abstract void onHeadsetDisconnected();
public abstract void onHeadsetConnected();
public abstract void onScoAudioDisconnected();
public abstract void onScoAudioConnected();
/**
* Register for bluetooth headset connection states and Sco audio states.
* Try to connect to bluetooth headset audio by calling startBluetoothSco().
* This is a work around for API < 11 to detect if a headset is connected before
* the application starts.
* <p/>
* The official documentation for startBluetoothSco() states
* <p/>
* "This method can be used by applications wanting to send and received audio to/from
* a bluetooth SCO headset while the phone is not in call."
* <p/>
* Does this mean that startBluetoothSco() would fail if the connected bluetooth device
* is not a headset?
* <p/>
* Thus if a call to startBluetoothSco() is successful, i.e mBroadcastReceiver will receive
* an ACTION_SCO_AUDIO_STATE_CHANGED with intent extra SCO_AUDIO_STATE_CONNECTED, then
* we assume that a headset is connected.
*
* @return false if device does not support bluetooth or current platform does not supports
* use of SCO for off call.
*/
@SuppressWarnings("deprecation")
private boolean startBluetooth() {
Log.d(TAG, "startBluetooth");
// Device support bluetooth
if (mBluetoothAdapter != null) {
if (mAudioManager.isBluetoothScoAvailableOffCall()) {
Log.d(TAG, "isBluetoothScoAvailableOffCall");
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED));
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED));
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
mAudioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "onAudioFocusChange focusChange=" + focusChange);
}
}, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
// Need to set audio mode to MODE_IN_CALL for call to startBluetoothSco() to succeed.
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mIsCountDownOn = true;
// mCountDown repeatedly tries to start bluetooth Sco audio connection.
mCountDown.start();
// need for audio sco, see mBroadcastReceiver
mIsStarting = true;
return true;
}
}
return false;
}
/**
* Unregister broadcast receivers and stop Sco audio connection
* and cancel count down.
*/
private void stopBluetooth() {
Log.d(TAG, "stopBluetooth");
if (mIsCountDownOn) {
mIsCountDownOn = false;
mCountDown.cancel();
}
// Need to stop Sco audio connection here when the app
// change orientation or close with headset still turns on.
mContext.unregisterReceiver(mBroadcastReceiver);
mAudioManager.stopBluetoothSco();
mAudioManager.setMode(AudioManager.MODE_NORMAL);
}
/**
* Handle headset and Sco audio connection states.
*/
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@SuppressWarnings({"deprecation", "synthetic-access"})
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED)) {
Log.d(TAG, "ACTION_ACL_CONNECTED 1");
android.bluetooth.BluetoothDevice mConnectedHeadset = intent.getParcelableExtra(android.bluetooth.BluetoothDevice.EXTRA_DEVICE);
BluetoothClass bluetoothClass = mConnectedHeadset.getBluetoothClass();
if (bluetoothClass != null) {
Log.d(TAG, "ACTION_ACL_CONNECTED 2");
// Check if device is a headset. Besides the 2 below, are there other
// device classes also qualified as headset?
int deviceClass = bluetoothClass.getDeviceClass();
if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
|| deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
Log.d(TAG, "ACTION_ACL_CONNECTED deviceClass=" + deviceClass);
// start bluetooth Sco audio connection.
// Calling startBluetoothSco() always returns faIL here,
// that why a count down timer is implemented to call
// startBluetoothSco() in the onTick.
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mIsCountDownOn = true;
mCountDown.start();
// override this if you want to do other thing when the device is connected.
onHeadsetConnected();
}
}
Log.d(TAG, mConnectedHeadset.getName() + " connected");
} else if (action.equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
Log.d(TAG, "Headset disconnected");
if (mIsCountDownOn) {
mIsCountDownOn = false;
mCountDown.cancel();
}
mAudioManager.setMode(AudioManager.MODE_NORMAL);
// override this if you want to do other thing when the device is disconnected.
onHeadsetDisconnected();
} else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_ERROR);
Log.d(TAG, "ACTION_SCO_AUDIO_STATE_CHANGED state=" + state);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
mIsOnHeadsetSco = true;
if (mIsStarting) {
// When the device is connected before the application starts,
// ACTION_ACL_CONNECTED will not be received, so call onHeadsetConnected here
mIsStarting = false;
onHeadsetConnected();
}
if (mIsCountDownOn) {
mIsCountDownOn = false;
mCountDown.cancel();
}
// override this if you want to do other thing when Sco audio is connected.
onScoAudioConnected();
Log.d(TAG, "Sco connected");
} else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
Log.d(TAG, "Sco disconnected");
// Always receive SCO_AUDIO_STATE_DISCONNECTED on call to startBluetooth()
// which at that stage we do not want to do anything. Thus the if condition.
if (!mIsStarting) {
mIsOnHeadsetSco = false;
// Need to call stopBluetoothSco(), otherwise startBluetoothSco()
// will not be successful.
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
// override this if you want to do other thing when Sco audio is disconnected.
onScoAudioDisconnected();
}
}
}
}
};
/**
* Try to connect to audio headset in onTick.
*/
private CountDownTimer mCountDown = new CountDownTimer(20000, 1000) {
@SuppressWarnings("synthetic-access")
@Override
public void onTick(long millisUntilFinished) {
// When this call is successful, this count down timer will be canceled.
try {
mAudioManager.setSpeakerphoneOn(false);
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
} catch (Exception ignored) {
}
Log.d(TAG, "\nonTick start bluetooth Sco");
}
@SuppressWarnings("synthetic-access")
@Override
public void onFinish() {
// Calls to startBluetoothSco() in onStick are not successful.
// Should implement something to inform user of this failure
mIsCountDownOn = false;
mAudioManager.setMode(AudioManager.MODE_NORMAL);
Log.d(TAG, "\nonFinish fail to connect to headset audio");
}
};
}
所以我确保通过播放YouTube视频和录制声音将蓝牙耳机(Bose无线耳机)连接到我的平板电脑(Acer运行Lollipop)。当我尝试运行上面的代码时,我得到以下输出:
BluetoothController: startBluetooth
BluetoothController: isBluetoothScoAvailableOffCall
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onFinish fail to connect to headset audio
我从未得到启动蓝牙Sco方法的成功。它总是在连接和断开之间跳跃。我做错了什么?
答案 0 :(得分:0)
事实证明我使用的平板电脑在蓝牙设置中不支持手机音频。
因此,为了确保蓝牙连接正常工作,您需要进入设置 - >蓝牙,然后单击配对蓝牙耳机旁边的设置图标。您应该确保选中“电话音频”和“媒体音频”。如果您缺少“电话音频”,您将获得与我的连接断开相同的输出。
希望这有帮助!
答案 1 :(得分:0)
即使设备支持的蓝牙电话音频和设置正确,我也遇到同样的问题。
当我在接收到BluetoothHeadset.STATE_CONNECTED事件后立即调用startBluetoothSco()时,SCO状态从SCO_AUDIO_STATE_CONNECTING变为SCO_AUDIO_STATE_DISCONNECTED。通过在BluetoothHeadset.STATE_CONNECTED事件之后对startBluetoothSco()的调用引入1秒钟延迟来解决此问题:
BluetoothHeadset.STATE_CONNECTED -> {
if (isAudioFocused()) {
Timer("Sco", false).schedule(1000) {
myAudioManager.startBluetoothSco()
}
}