From ae425ca614998a59374b39448716c326fd72931e Mon Sep 17 00:00:00 2001 From: Patty Date: Thu, 4 Nov 2021 21:03:32 +0800 Subject: [PATCH] Parse audio config codec capability for LE devices 1. Rename function getHwOffloadEncodingFormatsSupportedForA2DP to getHwOffloadFormatsSupportedForBluetoothMedia 2. Add audio format for LC3 3. Add public class BluetoothLeAudioCodecConfig to store the codec for LE audio 4. Add test case for BluetoothLeAudioCodecConfig Tag: #feature Bug: 203535499 Bug: 150670922 Test: atest BluetoothLeAudioCodecConfigTest BluetoothInstrumentationTests Change-Id: I5f64d9078ca2c07e13cffd83b879dd95468ed313 Merged-In: I5f64d9078ca2c07e13cffd83b879dd95468ed313 --- core/api/current.txt | 14 ++ .../BluetoothLeAudioCodecConfig.java | 129 ++++++++++++++++++ core/jni/android_media_AudioSystem.cpp | 16 +-- .../BluetoothLeAudioCodecConfigTest.java | 59 ++++++++ media/java/android/media/AudioManager.java | 53 +++++-- media/java/android/media/AudioSystem.java | 42 +++++- 6 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java create mode 100644 core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java diff --git a/core/api/current.txt b/core/api/current.txt index a70a0a904290..f9760a4954da 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9452,6 +9452,20 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; } + public final class BluetoothLeAudioCodecConfig { + method @NonNull public String getCodecName(); + method public int getCodecType(); + method public static int getMaxCodecType(); + field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240 + field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0 + } + + public static final class BluetoothLeAudioCodecConfig.Builder { + ctor public BluetoothLeAudioCodecConfig.Builder(); + method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build(); + method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int); + } + public final class BluetoothManager { method public android.bluetooth.BluetoothAdapter getAdapter(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List getConnectedDevices(int); diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java new file mode 100644 index 000000000000..dcaf4b682f44 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 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.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents the codec configuration for a Bluetooth LE Audio source device. + *

Contains the source codec type. + *

The source codec type values are the same as those supported by the + * device hardware. + * + * {@see BluetoothLeAudioCodecConfig} + */ +public final class BluetoothLeAudioCodecConfig { + // Add an entry for each source codec here. + + /** @hide */ + @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { + SOURCE_CODEC_TYPE_LC3, + SOURCE_CODEC_TYPE_INVALID + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SourceCodecType {}; + + public static final int SOURCE_CODEC_TYPE_LC3 = 0; + public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; + + /** + * Represents the count of valid source codec types. Can be accessed via + * {@link #getMaxCodecType}. + */ + private static final int SOURCE_CODEC_TYPE_MAX = 1; + + private final @SourceCodecType int mCodecType; + + /** + * Creates a new BluetoothLeAudioCodecConfig. + * + * @param codecType the source codec type + */ + private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) { + mCodecType = codecType; + } + + @Override + public String toString() { + return "{codecName:" + getCodecName() + "}"; + } + + /** + * Gets the codec type. + * + * @return the codec type + */ + public @SourceCodecType int getCodecType() { + return mCodecType; + } + + /** + * Returns the valid codec types count. + */ + public static int getMaxCodecType() { + return SOURCE_CODEC_TYPE_MAX; + } + + /** + * Gets the codec name. + * + * @return the codec name + */ + public @NonNull String getCodecName() { + switch (mCodecType) { + case SOURCE_CODEC_TYPE_LC3: + return "LC3"; + case SOURCE_CODEC_TYPE_INVALID: + return "INVALID CODEC"; + default: + break; + } + return "UNKNOWN CODEC(" + mCodecType + ")"; + } + + /** + * Builder for {@link BluetoothLeAudioCodecConfig}. + *

By default, the codec type will be set to + * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID} + */ + public static final class Builder { + private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; + + /** + * Set codec type for Bluetooth codec config. + * + * @param codecType of this codec + * @return the same Builder instance + */ + public @NonNull Builder setCodecType(@SourceCodecType int codecType) { + mCodecType = codecType; + return this; + } + + /** + * Build {@link BluetoothLeAudioCodecConfig}. + * @return new BluetoothLeAudioCodecConfig built + */ + public @NonNull BluetoothLeAudioCodecConfig build() { + return new BluetoothLeAudioCodecConfig(mCodecType); + } + } +} diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 4b93363b5b90..c847e4d7654f 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2273,10 +2273,8 @@ android_media_AudioSystem_getMicrophones(JNIEnv *env, jobject thiz, jobject jMic return jStatus; } -static jint -android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( - JNIEnv *env, jobject thiz, jobject jEncodingFormatList) -{ +static jint android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia( + JNIEnv *env, jobject thiz, jint deviceType, jobject jEncodingFormatList) { ALOGV("%s", __FUNCTION__); jint jStatus = AUDIO_JAVA_SUCCESS; if (!env->IsInstanceOf(jEncodingFormatList, gArrayListClass)) { @@ -2284,8 +2282,10 @@ android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( return (jint)AUDIO_JAVA_BAD_VALUE; } std::vector encodingFormats; - status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP( - &encodingFormats); + status_t status = + AudioSystem::getHwOffloadFormatsSupportedForBluetoothMedia(static_cast( + deviceType), + &encodingFormats); if (status != NO_ERROR) { ALOGE("%s: error %d", __FUNCTION__, status); jStatus = nativeToJavaStatus(status); @@ -2810,8 +2810,8 @@ static const JNINativeMethod gMethods[] = {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, - {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I", - (void *)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP}, + {"getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I", + (void *)android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia}, {"setSupportedSystemUsages", "([I)I", (void *)android_media_AudioSystem_setSupportedSystemUsages}, {"setAllowedCapturePolicy", "(II)I", diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java new file mode 100644 index 000000000000..6471492c9663 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +/** + * Unit test cases for {@link BluetoothLeAudioCodecConfig}. + */ +public class BluetoothLeAudioCodecConfigTest extends TestCase { + private int[] mCodecTypeArray = new int[] { + BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID, + }; + + @SmallTest + public void testBluetoothLeAudioCodecConfig_valid_get_methods() { + + for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) { + int codecType = mCodecTypeArray[codecIdx]; + + BluetoothLeAudioCodecConfig leAudioCodecConfig = + buildBluetoothLeAudioCodecConfig(codecType); + + if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) { + assertEquals("LC3", leAudioCodecConfig.getCodecName()); + } + if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName()); + } + + assertEquals(1, leAudioCodecConfig.getMaxCodecType()); + assertEquals(codecType, leAudioCodecConfig.getCodecType()); + } + } + + private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) { + return new BluetoothLeAudioCodecConfig.Builder() + .setCodecType(sourceCodecType) + .build(); + + } +} diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index c7f56961e498..60f4a5a226db 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -32,6 +32,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothCodecConfig; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudioCodecConfig; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -6790,30 +6791,56 @@ public class AudioManager { /** * Returns a list of audio formats that corresponds to encoding formats - * supported on offload path for A2DP playback. + * supported on offload path for A2DP and LE audio playback. * + * @param deviceType Indicates the target device type {@link AudioSystem.DeviceType} * @return a list of {@link BluetoothCodecConfig} objects containing encoding formats - * supported for offload A2DP playback + * supported for offload A2DP playback or a list of {@link BluetoothLeAudioCodecConfig} + * objects containing encoding formats supported for offload LE Audio playback * @hide */ - public List getHwOffloadEncodingFormatsSupportedForA2DP() { + public List getHwOffloadFormatsSupportedForBluetoothMedia( + @AudioSystem.DeviceType int deviceType) { ArrayList formatsList = new ArrayList(); - ArrayList codecConfigList = new ArrayList(); + ArrayList a2dpCodecConfigList = new ArrayList(); + ArrayList leAudioCodecConfigList = + new ArrayList(); - int status = AudioSystem.getHwOffloadEncodingFormatsSupportedForA2DP(formatsList); + if (deviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + && deviceType != AudioSystem.DEVICE_OUT_BLE_HEADSET) { + throw new IllegalArgumentException( + "Illegal devicetype for the getHwOffloadFormatsSupportedForBluetoothMedia"); + } + + int status = AudioSystem.getHwOffloadFormatsSupportedForBluetoothMedia(deviceType, + formatsList); if (status != AudioManager.SUCCESS) { - Log.e(TAG, "getHwOffloadEncodingFormatsSupportedForA2DP failed:" + status); - return codecConfigList; + Log.e(TAG, "getHwOffloadFormatsSupportedForBluetoothMedia for deviceType " + + deviceType + " failed:" + status); + return a2dpCodecConfigList; } - for (Integer format : formatsList) { - int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); - if (btSourceCodec - != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - codecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + if (deviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + for (Integer format : formatsList) { + int btSourceCodec = AudioSystem.audioFormatToBluetoothSourceCodec(format); + if (btSourceCodec != BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + a2dpCodecConfigList.add(new BluetoothCodecConfig(btSourceCodec)); + } } + return a2dpCodecConfigList; + } else if (deviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) { + for (Integer format : formatsList) { + int btLeAudioCodec = AudioSystem.audioFormatToBluetoothLeAudioSourceCodec(format); + if (btLeAudioCodec != BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { + leAudioCodecConfigList.add(new BluetoothLeAudioCodecConfig.Builder() + .setCodecType(btLeAudioCodec) + .build()); + } + } + return leAudioCodecConfigList; } - return codecConfigList; + Log.e(TAG, "Input deviceType " + deviceType + " doesn't support."); + return a2dpCodecConfigList; } // Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 69d1889d5762..f0e42c0550ff 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothLeAudioCodecConfig; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -229,6 +230,9 @@ public class AudioSystem public static final int AUDIO_FORMAT_APTX_HD = 0x21000000; /** @hide */ public static final int AUDIO_FORMAT_LDAC = 0x23000000; + /** @hide */ + public static final int AUDIO_FORMAT_LC3 = 0x2B000000; + /** @hide */ @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = { @@ -238,11 +242,26 @@ public class AudioSystem AUDIO_FORMAT_SBC, AUDIO_FORMAT_APTX, AUDIO_FORMAT_APTX_HD, - AUDIO_FORMAT_LDAC } + AUDIO_FORMAT_LDAC} ) @Retention(RetentionPolicy.SOURCE) public @interface AudioFormatNativeEnumForBtCodec {} + /** @hide */ + @IntDef(flag = false, prefix = "AUDIO_FORMAT_", value = { + AUDIO_FORMAT_LC3} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface AudioFormatNativeEnumForBtLeAudioCodec {} + + /** @hide */ + @IntDef(flag = false, prefix = "DEVICE_", value = { + DEVICE_OUT_BLUETOOTH_A2DP, + DEVICE_OUT_BLE_HEADSET} + ) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceType {} + /** * @hide * Convert audio format enum values to Bluetooth codec values @@ -262,6 +281,21 @@ public class AudioSystem } } + /** + * @hide + * Convert audio format enum values to Bluetooth LE audio codec values + */ + public static int audioFormatToBluetoothLeAudioSourceCodec( + @AudioFormatNativeEnumForBtLeAudioCodec int audioFormat) { + switch (audioFormat) { + case AUDIO_FORMAT_LC3: return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3; + default: + Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat) + + " for conversion to BT LE audio codec"); + return BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; + } + } + /** * @hide * Convert a Bluetooth codec to an audio format enum @@ -1754,10 +1788,10 @@ public class AudioSystem /** * @hide - * Returns a list of audio formats (codec) supported on the A2DP offload path. + * Returns a list of audio formats (codec) supported on the A2DP and LE audio offload path. */ - public static native int getHwOffloadEncodingFormatsSupportedForA2DP( - ArrayList formatList); + public static native int getHwOffloadFormatsSupportedForBluetoothMedia( + @DeviceType int deviceType, ArrayList formatList); /** @hide */ public static native int setSurroundFormatEnabled(int audioFormat, boolean enabled);