From 44a4ef0aa93ebb2912f36d65af42ffbb1bcdbc0f Mon Sep 17 00:00:00 2001 From: Pavlin Radoslavov Date: Wed, 21 Dec 2016 12:05:51 -0800 Subject: Add a mechanism for configuring the A2DP Source codecs * Added a new class BluetoothCodecConfig that contains codec-related configuration or capabilities: codec type, priority, sample rate, bits per sample, channel mode, and codec specific fields. * Extended the Bluetooth A2DP AIDL interface to get/set the current codec configuration * Added new call handleBluetoothA2dpDeviceConfigChange() to the Media Framework that is called when there are changes in the Bluetooth A2DP device configuration - e.g., the A2DP codec is changed. Test: A2DP streaming to headsets, TestPlans/71390 Bug: 30958229 Change-Id: I9a82716cbc2a5efbe77352a031ac80c88f6a2459 --- compiled-classes-phone | 1 + core/java/android/bluetooth/BluetoothA2dp.java | 69 +++++ .../android/bluetooth/BluetoothCodecConfig.aidl | 19 ++ .../android/bluetooth/BluetoothCodecConfig.java | 287 +++++++++++++++++++++ core/java/android/bluetooth/IBluetoothA2dp.aidl | 3 + core/jni/android_media_AudioSystem.cpp | 13 + core/res/AndroidManifest.xml | 2 + media/java/android/media/AudioManager.java | 14 + media/java/android/media/AudioSystem.java | 3 + media/java/android/media/IAudioService.aidl | 2 + .../com/android/server/audio/AudioService.java | 67 ++++- 11 files changed, 468 insertions(+), 12 deletions(-) create mode 100644 core/java/android/bluetooth/BluetoothCodecConfig.aidl create mode 100644 core/java/android/bluetooth/BluetoothCodecConfig.java diff --git a/compiled-classes-phone b/compiled-classes-phone index 221d68739366..ec3371e45711 100644 --- a/compiled-classes-phone +++ b/compiled-classes-phone @@ -632,6 +632,7 @@ android.bluetooth.BluetoothAdapter$LeScanCallback android.bluetooth.BluetoothAudioConfig android.bluetooth.BluetoothClass android.bluetooth.BluetoothClass$1 +android.bluetooth.BluetoothCodecConfig android.bluetooth.BluetoothDevice android.bluetooth.BluetoothDevice$1 android.bluetooth.BluetoothDevice$2 diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 353c6400ffd7..1165fce3ce0a 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -101,6 +101,27 @@ public final class BluetoothA2dp implements BluetoothProfile { public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; + /** + * Intent used to broadcast the change in the Audio Codec state of the + * A2DP Source profile. + * + *

This intent will have 3 extras: + *

+ * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CODEC_CONFIG_CHANGED = + "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; + /** * A2DP sink device is streaming music. This state can be one of * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of @@ -542,6 +563,54 @@ public final class BluetoothA2dp implements BluetoothProfile { return false; } + /** + * Gets the current codec configuration. + * + * @return the current codec configuration + * @hide + */ + public BluetoothCodecConfig getCodecConfig() { + if (DBG) Log.d(TAG, "getCodecConfig"); + try { + mServiceLock.readLock().lock(); + if (mService != null && isEnabled()) { + return mService.getCodecConfig(); + } + if (mService == null) { + Log.w(TAG, "Proxy not attached to service"); + } + return null; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in getCodecConfig()", e); + return null; + } finally { + mServiceLock.readLock().unlock(); + } + } + + /** + * Sets the codec configuration preference. + * + * @param codecConfig the codec configuration preference + * @hide + */ + public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) { + if (DBG) Log.d(TAG, "setCodecConfigPreference"); + try { + mServiceLock.readLock().lock(); + if (mService != null && isEnabled()) { + mService.setCodecConfigPreference(codecConfig); + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return; + } catch (RemoteException e) { + Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e); + return; + } finally { + mServiceLock.readLock().unlock(); + } + } + /** * Helper for converting a state to a string. * diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.aidl b/core/java/android/bluetooth/BluetoothCodecConfig.aidl new file mode 100644 index 000000000000..553e66e1dac5 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothCodecConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothCodecConfig; diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java new file mode 100644 index 000000000000..5cc127766e83 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothCodecConfig.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Represents the codec configuration for a Bluetooth A2DP source device. + * + * {@see BluetoothA2dp} + * + * {@hide} + */ +public final class BluetoothCodecConfig implements Parcelable { + + /** + * Extra for the codec configuration intents of the individual profiles. + * + * This extra represents the current codec configuration of the A2DP + * profile. + */ + public static final String EXTRA_CODEC_CONFIG = "android.bluetooth.codec.extra.CODEC_CONFIG"; + + /** + * Extra for the codec configuration intents of the individual profiles. + * + * This extra represents the previous codec configuration of the A2DP + * profile. + */ + public static final String EXTRA_PREVIOUS_CODEC_CONFIG = + "android.bluetooth.codec.extra.PREVIOUS_CODEC_CONFIG"; + + public static final int SOURCE_CODEC_TYPE_SBC = 0; + public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; + + public static final int CODEC_PRIORITY_DEFAULT = 0; + public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; + + public static final int SAMPLE_RATE_NONE = 0; + public static final int SAMPLE_RATE_44100 = 0x1 << 0; + public static final int SAMPLE_RATE_48000 = 0x1 << 1; + public static final int SAMPLE_RATE_88200 = 0x1 << 2; + public static final int SAMPLE_RATE_96000 = 0x1 << 3; + public static final int SAMPLE_RATE_176400 = 0x1 << 4; + public static final int SAMPLE_RATE_192000 = 0x1 << 5; + + public static final int BITS_PER_SAMPLE_NONE = 0; + public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; + public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; + public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; + + public static final int CHANNEL_MODE_NONE = 0; + public static final int CHANNEL_MODE_MONO = 0x1 << 0; + public static final int CHANNEL_MODE_STEREO = 0x1 << 1; + + private final int mCodecType; + private final int mCodecPriority; + private final int mSampleRate; + private final int mBitsPerSample; + private final int mChannelMode; + private final long mCodecSpecific1; + private final long mCodecSpecific2; + private final long mCodecSpecific3; + private final long mCodecSpecific4; + + public BluetoothCodecConfig(int codecType, int codecPriority, + int sampleRate, int bitsPerSample, + int channelMode,long codecSpecific1, + long codecSpecific2, long codecSpecific3, + long codecSpecific4) { + mCodecType = codecType; + mCodecPriority = codecPriority; + mSampleRate = sampleRate; + mBitsPerSample = bitsPerSample; + mChannelMode = channelMode; + mCodecSpecific1 = codecSpecific1; + mCodecSpecific2 = codecSpecific2; + mCodecSpecific3 = codecSpecific3; + mCodecSpecific4 = codecSpecific4; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothCodecConfig) { + BluetoothCodecConfig other = (BluetoothCodecConfig)o; + return (other.mCodecType == mCodecType && + other.mCodecPriority == mCodecPriority && + other.mSampleRate == mSampleRate && + other.mBitsPerSample == mBitsPerSample && + other.mChannelMode == mChannelMode && + other.mCodecSpecific1 == mCodecSpecific1 && + other.mCodecSpecific2 == mCodecSpecific2 && + other.mCodecSpecific3 == mCodecSpecific3 && + other.mCodecSpecific4 == mCodecSpecific4); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mCodecType, mCodecPriority, mSampleRate, + mBitsPerSample, mChannelMode, mCodecSpecific1, + mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); + } + + @Override + public String toString() { + return "{mCodecType:" + mCodecType + + ",mCodecPriority:" + mCodecPriority + + ",mSampleRate:" + String.format("0x%x", mSampleRate) + + ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) + + ",mChannelMode:" + String.format("0x%x", mChannelMode) + + ",mCodecSpecific1:" + mCodecSpecific1 + + ",mCodecSpecific2:" + mCodecSpecific2 + + ",mCodecSpecific3:" + mCodecSpecific3 + + ",mCodecSpecific4:" + mCodecSpecific4 + "}"; + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BluetoothCodecConfig createFromParcel(Parcel in) { + final int codecType = in.readInt(); + final int codecPriority = in.readInt(); + final int sampleRate = in.readInt(); + final int bitsPerSample = in.readInt(); + final int channelMode = in.readInt(); + final long codecSpecific1 = in.readLong(); + final long codecSpecific2 = in.readLong(); + final long codecSpecific3 = in.readLong(); + final long codecSpecific4 = in.readLong(); + return new BluetoothCodecConfig(codecType, codecPriority, + sampleRate, bitsPerSample, + channelMode, codecSpecific1, + codecSpecific2, codecSpecific3, + codecSpecific4); + } + public BluetoothCodecConfig[] newArray(int size) { + return new BluetoothCodecConfig[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mCodecType); + out.writeInt(mCodecPriority); + out.writeInt(mSampleRate); + out.writeInt(mBitsPerSample); + out.writeInt(mChannelMode); + out.writeLong(mCodecSpecific1); + out.writeLong(mCodecSpecific2); + out.writeLong(mCodecSpecific3); + out.writeLong(mCodecSpecific4); + } + + /** + * Returns the codec type. + * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}. + * + * @return the codec type + */ + public int getCodecType() { + return mCodecType; + } + + /** + * Returns the codec selection priority. + * The codec selection priority is relative to other codecs: larger value + * means higher priority. If 0, reset to default. + * + * @return the codec priority + */ + public int getCodecPriority() { + return mCodecPriority; + } + + /** + * Returns the codec sample rate. The value can be a bitmask with all + * supported sample rates: + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or + * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000} + * + * @return the codec sample rate + */ + public int getSampleRate() { + return mSampleRate; + } + + /** + * Returns the codec bits per sample. The value can be a bitmask with all + * bits per sample supported: + * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or + * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or + * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or + * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32} + * + * @return the codec bits per sample + */ + public int getBitsPerSample() { + return mBitsPerSample; + } + + /** + * Returns the codec channel mode. The value can be a bitmask with all + * supported channel modes: + * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or + * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or + * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO} + * + * @return the codec channel mode + */ + public int getChannelMode() { + return mChannelMode; + } + + /** + * Returns a codec specific value1. + * + * @return a codec specific value1. + */ + public long getCodecSpecific1() { + return mCodecSpecific1; + } + + /** + * Returns a codec specific value2. + * + * @return a codec specific value2 + */ + public long getCodecSpecific2() { + return mCodecSpecific2; + } + + /** + * Returns a codec specific value3. + * + * @return a codec specific value3 + */ + public long getCodecSpecific3() { + return mCodecSpecific3; + } + + /** + * Returns a codec specific value4. + * + * @return a codec specific value4 + */ + public long getCodecSpecific4() { + return mCodecSpecific4; + } + + /** + * Checks whether the audio feeding parameters are same. + * + * @param other the codec config to compare against + * @return true if the audio feeding parameters are same, otherwise false + */ + public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) { + return (other != null && other.mSampleRate == mSampleRate && + other.mBitsPerSample == mBitsPerSample && + other.mChannelMode == mChannelMode); + } +} diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 26ff9e274c39..5b524eb18a5e 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -16,6 +16,7 @@ package android.bluetooth; +import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; /** @@ -36,4 +37,6 @@ interface IBluetoothA2dp { oneway void adjustAvrcpAbsoluteVolume(int direction); oneway void setAvrcpAbsoluteVolume(int volume); boolean isA2dpPlaying(in BluetoothDevice device); + BluetoothCodecConfig getCodecConfig(); + oneway void setCodecConfigPreference(in BluetoothCodecConfig codecConfig); } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 497600212095..d30e6eba48e7 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -456,6 +456,18 @@ android_media_AudioSystem_getDeviceConnectionState(JNIEnv *env, jobject thiz, ji return (jint) state; } +static jint +android_media_AudioSystem_handleDeviceConfigChange(JNIEnv *env, jobject thiz, jint device, jstring device_address, jstring device_name) +{ + const char *c_address = env->GetStringUTFChars(device_address, NULL); + const char *c_name = env->GetStringUTFChars(device_name, NULL); + int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast (device), + c_address, c_name)); + env->ReleaseStringUTFChars(device_address, c_address); + env->ReleaseStringUTFChars(device_name, c_name); + return (jint) status; +} + static jint android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state) { @@ -1757,6 +1769,7 @@ static const JNINativeMethod gMethods[] = { {"newAudioSessionId", "()I", (void *)android_media_AudioSystem_newAudioSessionId}, {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState}, {"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState}, + {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;)I", (void *)android_media_AudioSystem_handleDeviceConfigChange}, {"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState}, {"setForceUse", "(II)I", (void *)android_media_AudioSystem_setForceUse}, {"getForceUse", "(I)I", (void *)android_media_AudioSystem_getForceUse}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 60cf8109edf1..3bd0acc49646 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -166,6 +166,8 @@ android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" /> + 0) { @@ -4866,7 +4884,7 @@ public class AudioService extends IAudioService.Stub { private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state) { if (DEBUG_VOL) { - Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state); + Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice+"state=" + state); } if (btDevice == null) { return; @@ -4877,9 +4895,9 @@ public class AudioService extends IAudioService.Stub { } synchronized (mConnectedDevices) { - String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - btDevice.getAddress()); - DeviceListSpec deviceSpec = mConnectedDevices.get(key); + final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + btDevice.getAddress()); + final DeviceListSpec deviceSpec = mConnectedDevices.get(key); boolean isConnected = deviceSpec != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { @@ -4930,7 +4948,7 @@ public class AudioService extends IAudioService.Stub { private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state) { if (DEBUG_VOL) { - Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state); + Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state); } if (btDevice == null) { return; @@ -4941,8 +4959,8 @@ public class AudioService extends IAudioService.Stub { } synchronized (mConnectedDevices) { - String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); - DeviceListSpec deviceSpec = mConnectedDevices.get(key); + final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + final DeviceListSpec deviceSpec = mConnectedDevices.get(key); boolean isConnected = deviceSpec != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { @@ -4953,6 +4971,31 @@ public class AudioService extends IAudioService.Stub { } } + private void onBluetoothA2dpDeviceConfigChange(BluetoothDevice btDevice) + { + if (DEBUG_VOL) { + Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; + synchronized (mConnectedDevices) { + final String key = makeDeviceListKey(device, address); + final DeviceListSpec deviceSpec = mConnectedDevices.get(key); + if (deviceSpec != null) { + // Device is connected + AudioSystem.handleDeviceConfigChange(device, address, + btDevice.getName()); + } + } + } + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported synchronized (mA2dpAvrcpLock) { -- cgit v1.2.3-59-g8ed1b