Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +51 −4 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; Loading Loading @@ -136,6 +137,16 @@ public class LeAudioService extends ProfileService { public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_GROUP_ID = "bluetooth_le_broadcast_fallback_active_group_id"; /** * Per PBP 1.0 4.3. High Quality Public Broadcast Audio, Broadcast HIGH quality audio configs * are with sampling frequency 48khz */ private static final BluetoothLeAudioCodecConfig BROADCAST_HIGH_QUALITY_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; Loading Loading @@ -1017,16 +1028,52 @@ public class LeAudioService extends ProfileService { Log.i(TAG, "createBroadcast: isEncrypted=" + (isEncrypted ? "true" : "false")); mAwaitingBroadcastCreateResponse = true; mLeAudioBroadcasterNativeInterface.createBroadcast(broadcastSettings.isPublicBroadcast(), broadcastSettings.getBroadcastName(), broadcastCode, mLeAudioBroadcasterNativeInterface.createBroadcast( broadcastSettings.isPublicBroadcast(), broadcastSettings.getBroadcastName(), broadcastCode, publicMetadata == null ? null : publicMetadata.getRawMetadata(), settingsList.stream() .mapToInt(s -> s.getPreferredQuality()).toArray(), getBroadcastAudioQualityPerSinkCapabilities(settingsList), settingsList.stream() .map(s -> s.getContentMetadata().getRawMetadata()) .toArray(byte[][]::new)); } private int[] getBroadcastAudioQualityPerSinkCapabilities( List<BluetoothLeBroadcastSubgroupSettings> settingsList) { int[] preferredQualityArray = settingsList.stream().mapToInt(s -> s.getPreferredQuality()).toArray(); BassClientService bassClientService = getBassClientService(); if (bassClientService == null) { return preferredQualityArray; } for (BluetoothDevice sink : bassClientService.getConnectedDevices()) { int groupId = getGroupId(sink); if (groupId == LE_AUDIO_GROUP_ID_INVALID) { continue; } BluetoothLeAudioCodecStatus codecStatus = getCodecStatus(groupId); if (codecStatus != null && !codecStatus.isInputCodecConfigSelectable(BROADCAST_HIGH_QUALITY_CONFIG)) { // If any sink device does not support high quality audio config, // set all subgroup audio quality to standard quality for now before multi codec // config support is ready Log.i( TAG, "Sink device doesn't support HIGH broadcast audio quality, use STANDARD" + " quality"); Arrays.fill( preferredQualityArray, BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD); break; } } return preferredQualityArray; } /** * Start LeAudio Broadcast instance. * @param broadcastId broadcast instance identifier Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +102 −4 Original line number Diff line number Diff line Loading @@ -37,8 +37,10 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.bass_client.BassClientService; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; Loading Loading @@ -79,7 +81,9 @@ public class LeAudioBroadcastServiceTest { @Mock private LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface; @Mock private LeAudioNativeInterface mLeAudioNativeInterface; @Mock private LeAudioTmapGattServer mTmapGattServer; @Mock private BassClientService mBassClientService; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); @Spy private ServiceFactory mServiceFactory = new ServiceFactory(); private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55"; private static final int TEST_BROADCAST_ID = 42; Loading @@ -100,6 +104,27 @@ public class LeAudioBroadcastServiceTest { private static final String TEST_LANGUAGE = "deu"; private static final String TEST_BROADCAST_NAME = "Name Test"; private static final BluetoothLeAudioCodecConfig LC3_16KHZ_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000) .build(); private static final BluetoothLeAudioCodecConfig LC3_48KHZ_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG_STANDARD = List.of(LC3_16KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG_STANDARD = List.of(LC3_16KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG_HIGH = List.of(LC3_48KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG_HIGH = List.of(LC3_48KHZ_CONFIG); private boolean mOnBroadcastStartedCalled = false; private boolean mOnBroadcastStartFailedCalled = false; private boolean mOnBroadcastStoppedCalled = false; Loading Loading @@ -192,7 +217,8 @@ public class LeAudioBroadcastServiceTest { startService(); mService.mAudioManager = mAudioManager; mService.mServiceFactory = mServiceFactory; when(mServiceFactory.getBassClientService()).thenReturn(mBassClientService); // Set up the State Changed receiver IntentFilter filter = new IntentFilter(); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); Loading @@ -209,14 +235,16 @@ public class LeAudioBroadcastServiceTest { @After public void tearDown() throws Exception { if (mService == null) { if (mService == null || mAdapter == null) { return; } if (mLeAudioIntentReceiver != null) { mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); } stopService(); LeAudioBroadcasterNativeInterface.setInstance(null); LeAudioNativeInterface.setInstance(null); mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); TestUtils.clearAdapterService(mAdapterService); reset(mAudioManager); } Loading Loading @@ -394,6 +422,44 @@ public class LeAudioBroadcastServiceTest { Assert.assertTrue(mOnBroadcastStartFailedCalled); } @Test public void testCreateBroadcast_updateQualityToStandard() { byte[] code = {0x00, 0x01, 0x00, 0x02}; int groupId = 1; prepareConnectedUnicastDevice(groupId); mService.mBroadcastCallbacks.register(mCallbacks); BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); BluetoothLeAudioContentMetadata meta = meta_builder.build(); BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); when(mBassClientService.getConnectedDevices()).thenReturn(List.of(mDevice)); // update selectable configs to be STANDARD quality injectGroupSelectableCodecConfigChanged( groupId, INPUT_SELECTABLE_CONFIG_STANDARD, OUTPUT_SELECTABLE_CONFIG_STANDARD); injectGroupCurrentCodecConfigChanged(groupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG); mService.createBroadcast(settings); // Test data with only one subgroup // Verify quality is updated to standard per sinks capabilities int[] expectedQualityArray = {BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD}; byte[][] expectedDataArray = { settings.getSubgroupSettings().get(0).getContentMetadata().getRawMetadata() }; verify(mLeAudioBroadcasterNativeInterface, times(1)) .createBroadcast( eq(true), eq(TEST_BROADCAST_NAME), eq(code), eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); } @Test public void testStartStopBroadcastNative() { int broadcastId = 243; Loading Loading @@ -636,6 +702,11 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt4 = srcAudioLocation; create_event.valueInt5 = availableContexts; mService.messageFromNative(create_event); // Set default codec config to HIGH quality injectGroupSelectableCodecConfigChanged( groupId, INPUT_SELECTABLE_CONFIG_HIGH, OUTPUT_SELECTABLE_CONFIG_HIGH); injectGroupCurrentCodecConfigChanged(groupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG); } @Test Loading Loading @@ -979,7 +1050,8 @@ public class LeAudioBroadcastServiceTest { BluetoothLeBroadcastSubgroupSettings.Builder subgroupBuilder = new BluetoothLeBroadcastSubgroupSettings.Builder() .setContentMetadata(contentMetadata); .setContentMetadata(contentMetadata) .setPreferredQuality(BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH); BluetoothLeBroadcastSettings.Builder builder = new BluetoothLeBroadcastSettings.Builder() .setPublicBroadcast(true) Loading @@ -993,4 +1065,30 @@ public class LeAudioBroadcastServiceTest { } return builder.build(); } private void injectGroupCurrentCodecConfigChanged( int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, BluetoothLeAudioCodecConfig outputCodecConfig) { int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED; LeAudioStackEvent groupCodecConfigChangedEvent = new LeAudioStackEvent(eventType); groupCodecConfigChangedEvent.valueInt1 = groupId; groupCodecConfigChangedEvent.valueCodec1 = inputCodecConfig; groupCodecConfigChangedEvent.valueCodec2 = outputCodecConfig; mService.messageFromNative(groupCodecConfigChangedEvent); } private void injectGroupSelectableCodecConfigChanged( int groupId, List<BluetoothLeAudioCodecConfig> inputSelectableCodecConfig, List<BluetoothLeAudioCodecConfig> outputSelectableCodecConfig) { int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED; LeAudioStackEvent groupCodecConfigChangedEvent = new LeAudioStackEvent(eventType); groupCodecConfigChangedEvent.valueInt1 = groupId; groupCodecConfigChangedEvent.valueCodecList1 = inputSelectableCodecConfig; groupCodecConfigChangedEvent.valueCodecList2 = outputSelectableCodecConfig; mService.messageFromNative(groupCodecConfigChangedEvent); } } Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +51 −4 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; Loading Loading @@ -136,6 +137,16 @@ public class LeAudioService extends ProfileService { public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_GROUP_ID = "bluetooth_le_broadcast_fallback_active_group_id"; /** * Per PBP 1.0 4.3. High Quality Public Broadcast Audio, Broadcast HIGH quality audio configs * are with sampling frequency 48khz */ private static final BluetoothLeAudioCodecConfig BROADCAST_HIGH_QUALITY_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; Loading Loading @@ -1017,16 +1028,52 @@ public class LeAudioService extends ProfileService { Log.i(TAG, "createBroadcast: isEncrypted=" + (isEncrypted ? "true" : "false")); mAwaitingBroadcastCreateResponse = true; mLeAudioBroadcasterNativeInterface.createBroadcast(broadcastSettings.isPublicBroadcast(), broadcastSettings.getBroadcastName(), broadcastCode, mLeAudioBroadcasterNativeInterface.createBroadcast( broadcastSettings.isPublicBroadcast(), broadcastSettings.getBroadcastName(), broadcastCode, publicMetadata == null ? null : publicMetadata.getRawMetadata(), settingsList.stream() .mapToInt(s -> s.getPreferredQuality()).toArray(), getBroadcastAudioQualityPerSinkCapabilities(settingsList), settingsList.stream() .map(s -> s.getContentMetadata().getRawMetadata()) .toArray(byte[][]::new)); } private int[] getBroadcastAudioQualityPerSinkCapabilities( List<BluetoothLeBroadcastSubgroupSettings> settingsList) { int[] preferredQualityArray = settingsList.stream().mapToInt(s -> s.getPreferredQuality()).toArray(); BassClientService bassClientService = getBassClientService(); if (bassClientService == null) { return preferredQualityArray; } for (BluetoothDevice sink : bassClientService.getConnectedDevices()) { int groupId = getGroupId(sink); if (groupId == LE_AUDIO_GROUP_ID_INVALID) { continue; } BluetoothLeAudioCodecStatus codecStatus = getCodecStatus(groupId); if (codecStatus != null && !codecStatus.isInputCodecConfigSelectable(BROADCAST_HIGH_QUALITY_CONFIG)) { // If any sink device does not support high quality audio config, // set all subgroup audio quality to standard quality for now before multi codec // config support is ready Log.i( TAG, "Sink device doesn't support HIGH broadcast audio quality, use STANDARD" + " quality"); Arrays.fill( preferredQualityArray, BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD); break; } } return preferredQualityArray; } /** * Start LeAudio Broadcast instance. * @param broadcastId broadcast instance identifier Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +102 −4 Original line number Diff line number Diff line Loading @@ -37,8 +37,10 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.bass_client.BassClientService; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; Loading Loading @@ -79,7 +81,9 @@ public class LeAudioBroadcastServiceTest { @Mock private LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface; @Mock private LeAudioNativeInterface mLeAudioNativeInterface; @Mock private LeAudioTmapGattServer mTmapGattServer; @Mock private BassClientService mBassClientService; @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); @Spy private ServiceFactory mServiceFactory = new ServiceFactory(); private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55"; private static final int TEST_BROADCAST_ID = 42; Loading @@ -100,6 +104,27 @@ public class LeAudioBroadcastServiceTest { private static final String TEST_LANGUAGE = "deu"; private static final String TEST_BROADCAST_NAME = "Name Test"; private static final BluetoothLeAudioCodecConfig LC3_16KHZ_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000) .build(); private static final BluetoothLeAudioCodecConfig LC3_48KHZ_CONFIG = new BluetoothLeAudioCodecConfig.Builder() .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG_STANDARD = List.of(LC3_16KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG_STANDARD = List.of(LC3_16KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG_HIGH = List.of(LC3_48KHZ_CONFIG); private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG_HIGH = List.of(LC3_48KHZ_CONFIG); private boolean mOnBroadcastStartedCalled = false; private boolean mOnBroadcastStartFailedCalled = false; private boolean mOnBroadcastStoppedCalled = false; Loading Loading @@ -192,7 +217,8 @@ public class LeAudioBroadcastServiceTest { startService(); mService.mAudioManager = mAudioManager; mService.mServiceFactory = mServiceFactory; when(mServiceFactory.getBassClientService()).thenReturn(mBassClientService); // Set up the State Changed receiver IntentFilter filter = new IntentFilter(); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); Loading @@ -209,14 +235,16 @@ public class LeAudioBroadcastServiceTest { @After public void tearDown() throws Exception { if (mService == null) { if (mService == null || mAdapter == null) { return; } if (mLeAudioIntentReceiver != null) { mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); } stopService(); LeAudioBroadcasterNativeInterface.setInstance(null); LeAudioNativeInterface.setInstance(null); mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); TestUtils.clearAdapterService(mAdapterService); reset(mAudioManager); } Loading Loading @@ -394,6 +422,44 @@ public class LeAudioBroadcastServiceTest { Assert.assertTrue(mOnBroadcastStartFailedCalled); } @Test public void testCreateBroadcast_updateQualityToStandard() { byte[] code = {0x00, 0x01, 0x00, 0x02}; int groupId = 1; prepareConnectedUnicastDevice(groupId); mService.mBroadcastCallbacks.register(mCallbacks); BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); BluetoothLeAudioContentMetadata meta = meta_builder.build(); BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); when(mBassClientService.getConnectedDevices()).thenReturn(List.of(mDevice)); // update selectable configs to be STANDARD quality injectGroupSelectableCodecConfigChanged( groupId, INPUT_SELECTABLE_CONFIG_STANDARD, OUTPUT_SELECTABLE_CONFIG_STANDARD); injectGroupCurrentCodecConfigChanged(groupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG); mService.createBroadcast(settings); // Test data with only one subgroup // Verify quality is updated to standard per sinks capabilities int[] expectedQualityArray = {BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD}; byte[][] expectedDataArray = { settings.getSubgroupSettings().get(0).getContentMetadata().getRawMetadata() }; verify(mLeAudioBroadcasterNativeInterface, times(1)) .createBroadcast( eq(true), eq(TEST_BROADCAST_NAME), eq(code), eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); } @Test public void testStartStopBroadcastNative() { int broadcastId = 243; Loading Loading @@ -636,6 +702,11 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt4 = srcAudioLocation; create_event.valueInt5 = availableContexts; mService.messageFromNative(create_event); // Set default codec config to HIGH quality injectGroupSelectableCodecConfigChanged( groupId, INPUT_SELECTABLE_CONFIG_HIGH, OUTPUT_SELECTABLE_CONFIG_HIGH); injectGroupCurrentCodecConfigChanged(groupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG); } @Test Loading Loading @@ -979,7 +1050,8 @@ public class LeAudioBroadcastServiceTest { BluetoothLeBroadcastSubgroupSettings.Builder subgroupBuilder = new BluetoothLeBroadcastSubgroupSettings.Builder() .setContentMetadata(contentMetadata); .setContentMetadata(contentMetadata) .setPreferredQuality(BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH); BluetoothLeBroadcastSettings.Builder builder = new BluetoothLeBroadcastSettings.Builder() .setPublicBroadcast(true) Loading @@ -993,4 +1065,30 @@ public class LeAudioBroadcastServiceTest { } return builder.build(); } private void injectGroupCurrentCodecConfigChanged( int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, BluetoothLeAudioCodecConfig outputCodecConfig) { int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED; LeAudioStackEvent groupCodecConfigChangedEvent = new LeAudioStackEvent(eventType); groupCodecConfigChangedEvent.valueInt1 = groupId; groupCodecConfigChangedEvent.valueCodec1 = inputCodecConfig; groupCodecConfigChangedEvent.valueCodec2 = outputCodecConfig; mService.messageFromNative(groupCodecConfigChangedEvent); } private void injectGroupSelectableCodecConfigChanged( int groupId, List<BluetoothLeAudioCodecConfig> inputSelectableCodecConfig, List<BluetoothLeAudioCodecConfig> outputSelectableCodecConfig) { int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED; LeAudioStackEvent groupCodecConfigChangedEvent = new LeAudioStackEvent(eventType); groupCodecConfigChangedEvent.valueInt1 = groupId; groupCodecConfigChangedEvent.valueCodecList1 = inputSelectableCodecConfig; groupCodecConfigChangedEvent.valueCodecList2 = outputSelectableCodecConfig; mService.messageFromNative(groupCodecConfigChangedEvent); } }