Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bd1e0a99 authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Update broadcast audio quality per sinks capabilities

If any connected broadcast receivers could not support HIGH quality,
framework will downgrade the broadcast audio quality to STANDARD.

This change only applied to broadcast feature function which is behind the feature flag.

Bug: 325644399
Bug: 316005152
Test: atest LeAudioBroadcastServiceTest
Test: manual test broadcast
Change-Id: If39a904c942c27c7749147ec97a78162aba97b88
parent 33606294
Loading
Loading
Loading
Loading
+51 −4
Original line number Diff line number Diff line
@@ -92,6 +92,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;
@@ -135,6 +136,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;
@@ -924,16 +935,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
+102 −4
Original line number Diff line number Diff line
@@ -36,8 +36,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.FakeFeatureFlagsImpl;
import com.android.bluetooth.flags.Flags;
@@ -75,7 +77,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;
@@ -96,6 +100,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;
@@ -195,7 +220,8 @@ public class LeAudioBroadcastServiceTest {
        mService.setFeatureFlags(mFakeFlagsImpl);

        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);
@@ -212,14 +238,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);
    }
@@ -397,6 +425,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;
@@ -639,6 +705,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
@@ -982,7 +1053,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)
@@ -996,4 +1068,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);
    }
}