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

Commit 4a6cbce6 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Improve LeAudio switch during phone call

When LeAudio Group is getting Active during phone call it is because
user changed the output device for a phone call.
This operations is quite heavy as it takes Telecom/Audio Framework and
Bluetooth time and resources. The step by step scenario looks like this:
1. Telecom clears current communication device (could be also Bluetooth)
2. Telecom enables Audio for Bluetooth device
3. LeAudioService calls native to set group as active (GroupSetActive)
4. leaudio native stack starts audio session and notifies upper layer
   about that
5. LeAudioService requests Audio Manager to connect LeAudio device
6. Audio Manager reads LeAudio device capabilities
7. Audio Manager notifies about LeAudio device being connected
8. LeAudioService notifies users (and Telecom) about LeAudio device
   being connected
9. Telecom choose new communication device
10. Audio HAL Resumes Bluetooth Encoding session which triggers ASE
    Configuration up to Streaming

The whole thing can take up to 4 sec

With this patch, followining modifications are added in following steps

4. in addition to previous steps, if native stack detects ongoing call,
   it will start ASE configuration for Conversational use case up to QoS
   Configure state
10. Here instade of complete ASE configuration, only Enable ASE is
    left.

This can decrease time by 1.5 sec

Bug: 308510081
Bug: 369322905
Test: atest bluetooth_le_audio_test bluetooth_le_audio_client_test
Flag: com::android::bluetooth:flags::leaudio_improve_switch_during_phone_call

Change-Id: Iaedd00594e6caa17495435c897e76c199c61eca9
parent 84bfcf37
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -1302,6 +1302,35 @@ public:
    active_group_id_ = bluetooth::groups::kGroupUnknown;
  }

  void PrepareStreamForAConversational(LeAudioDeviceGroup* group) {
    if (!com::android::bluetooth::flags::leaudio_improve_switch_during_phone_call()) {
      log::info("Flag leaudio_improve_switch_during_phone_call is not enabled");
      return;
    }

    log::debug("group_id: {}", group->group_id_);

    auto remote_direction = bluetooth::le_audio::types::kLeAudioDirectionSink;
    ReconfigureOrUpdateRemote(group, remote_direction);

    if (configuration_context_type_ != LeAudioContextType::CONVERSATIONAL) {
      log::error("Something went wrong {} != {} ", ToString(configuration_context_type_),
                 ToString(LeAudioContextType::CONVERSATIONAL));
      return;
    }

    BidirectionalPair<std::vector<uint8_t>> ccids = {
            .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids(
                    local_metadata_context_types_.sink),
            .source = ContentControlIdKeeper::GetInstance()->GetAllCcids(
                    local_metadata_context_types_.source)};
    if (!groupStateMachine_->ConfigureStream(group, configuration_context_type_,
                                             local_metadata_context_types_, ccids, true)) {
      log::info("Reconfiguration is needed for group {}", group->group_id_);
      initReconfiguration(group, LeAudioContextType::UNSPECIFIED);
    }
  }

  void GroupSetActive(const int group_id) override {
    log::info("group_id: {}", group_id);

@@ -1418,6 +1447,13 @@ public:
      callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE);
      SendAudioGroupSelectableCodecConfigChanged(group);
    }

    /* If group become active while phone call, let's configure it right away,
     * so when audio framework resumes the stream, it will be almost there.
     */
    if (IsInCall()) {
      PrepareStreamForAConversational(group);
    }
  }

  void SetEnableState(const RawAddress& address, bool enabled) override {
+127 −13
Original line number Diff line number Diff line
@@ -851,15 +851,18 @@ protected:
    ON_CALL(mock_state_machine_, Initialize(_))
            .WillByDefault(SaveArg<0>(&state_machine_callbacks_));
    ON_CALL(mock_state_machine_, ConfigureStream(_, _, _, _))
    ON_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _))
            .WillByDefault([this](LeAudioDeviceGroup* group, types::LeAudioContextType context_type,
                                  types::BidirectionalPair<types::AudioContexts>
                                          metadata_context_types,
                                  types::BidirectionalPair<std::vector<uint8_t>> ccid_lists) {
                                  types::BidirectionalPair<std::vector<uint8_t>> ccid_lists,
                                  bool configure_qos) {
              bool isReconfiguration = group->IsPendingConfiguration();
              log::info("ConfigureStream: group_id {}, context_type {} isReconfiguration {}",
                        group->group_id_, bluetooth::common::ToString(context_type),
              log::info(
                      "ConfigureStream: group_id {}, context_type {}, configure_qos {}, "
                      "isReconfiguration {}",
                      group->group_id_, bluetooth::common::ToString(context_type), configure_qos,
                      isReconfiguration);
              /* Do what ReleaseCisIds(group) does: start */
@@ -885,6 +888,12 @@ protected:
              types::AseState config_state =
                      types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
              if (configure_qos) {
                // Make sure CIG is created
                config_state = types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
                group->cig.SetState(types::CigState::CREATED);
              }
              for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr;
                   device = group->GetNextDevice(device)) {
                if (!group->cig.AssignCisIds(device)) {
@@ -4855,6 +4864,111 @@ TEST_F(UnicastTest, GroupSetActive_and_InactiveDuringStreamConfiguration) {
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}
TEST_F(UnicastTest, GroupSetActive_and_GroupSetInactive_DuringPhoneCall) {
  com::android::bluetooth::flags::provider_->leaudio_improve_switch_during_phone_call(true);
  const RawAddress test_address0 = GetTestAddress(0);
  int group_id = bluetooth::groups::kGroupUnknown;
  /**
   * Scenario:
   * 1. Call is started
   * 2. Group is set active - it is expected the state machine to be instructed to Configure to Qos
   * 3. Group is set to inactive - it is expected that state machine is instructed to stop *
   */
  default_channel_cnt = 1;
  SetSampleDatabaseEarbudsValid(
          1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
          codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004,
          /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/,
          default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnConnectionState(ConnectionState::CONNECTED, test_address0))
          .Times(1);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
          .WillOnce(DoAll(SaveArg<1>(&group_id)));
  ConnectLeAudio(test_address0);
  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
  EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, true)).Times(1);
  log::info("Call is started and group is getting Active");
  LeAudioClient::Get()->SetInCall(true);
  LeAudioClient::Get()->GroupSetActive(group_id);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
  log::info("Group is getting inctive");
  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1);
  LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}
TEST_F(UnicastTest, GroupSetActive_DuringPhoneCall_ThenResume) {
  com::android::bluetooth::flags::provider_->leaudio_improve_switch_during_phone_call(true);
  const RawAddress test_address0 = GetTestAddress(0);
  int group_id = bluetooth::groups::kGroupUnknown;
  /**
   * Scenario:
   * 1. Call is started
   * 2. Group is set active - it is expected the state machine to be instructed to Configure to Qos
   * 3. Audio Framework callse Resume - expect stream is started.
   * 4. Group is set to inactive - it is expected that state machine is instructed to stop *
   */
  default_channel_cnt = 1;
  SetSampleDatabaseEarbudsValid(
          1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
          codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt, default_channel_cnt, 0x0004,
          /* source sample freq 16khz */ false /*add_csis*/, true /*add_cas*/, true /*add_pacs*/,
          default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/, 0 /*rank*/);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnConnectionState(ConnectionState::CONNECTED, test_address0))
          .Times(1);
  EXPECT_CALL(mock_audio_hal_client_callbacks_,
              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
          .WillOnce(DoAll(SaveArg<1>(&group_id)));
  ConnectLeAudio(test_address0);
  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
  EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, true)).Times(1);
  log::info("Call is started and group is getting Active");
  LeAudioClient::Get()->SetInCall(true);
  LeAudioClient::Get()->GroupSetActive(group_id);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
  log::info("AF resumes the stream");
  /* Simulate resume and expect StartStream to be called.
   * Do not expect confirmation on resume, as this part is not mocked on the state machine
   */
  EXPECT_CALL(mock_state_machine_, StartStream(_, LeAudioContextType::CONVERSATIONAL, _, _))
          .Times(1);
  LocalAudioSourceResume(true, false);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
  log::info("Group is getting inactive");
  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1);
  LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
  SyncOnMainLoop();
  Mock::VerifyAndClearExpectations(&mock_state_machine_);
}
TEST_F(UnicastTest, ChangeAvailableContextTypeWhenInCodecConfigured) {
  const RawAddress test_address0 = GetTestAddress(0);
  int group_id = bluetooth::groups::kGroupUnknown;
@@ -8133,7 +8247,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {
          .source = types::AudioContexts()};
  EXPECT_CALL(mock_state_machine_,
              ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts,
                              ccids))
                              ccids, _))
          .Times(1);
  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id, AUDIO_SOURCE_INVALID, true);
@@ -8202,7 +8316,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure_SpeedUpReconfigF
  EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete())
          .Times(1)
          .After(reconfigure);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _)).Times(1);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(1);
  // SetInCall is used by GTBS - and only then we can expect CCID to be set.
  LeAudioClient::Get()->SetInCall(true);
  SyncOnMainLoop();
@@ -8237,7 +8351,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure_SpeedUpReconfigF
  EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete())
          .Times(1)
          .After(reconfigure);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _)).Times(1);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(1);
  LeAudioClient::Get()->SetInCall(false);
  SyncOnMainLoop();
@@ -8253,7 +8367,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure_SpeedUpReconfigF
          .source = types::AudioContexts()};
  EXPECT_CALL(mock_state_machine_,
              ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::MEDIA, contexts,
                              ccids))
                              ccids, _))
          .Times(0);
  EXPECT_CALL(
          mock_state_machine_,
@@ -8405,7 +8519,7 @@ TEST_F(UnicastTest, TwoReconfigureAndVerifyEnableContextType) {
          .source = types::AudioContexts(types::LeAudioContextType::CONVERSATIONAL)};
  EXPECT_CALL(mock_state_machine_,
              ConfigureStream(_, types::LeAudioContextType::CONVERSATIONAL, _, _))
              ConfigureStream(_, types::LeAudioContextType::CONVERSATIONAL, _, _, _))
          .Times(AtLeast(1));
  // Update metadata and resume
@@ -9743,7 +9857,7 @@ TEST_F(UnicastTest, MicrophoneAttachToCurrentMediaScenario) {
  // When the local audio sink resumes we should reconfigure
  EXPECT_CALL(mock_state_machine_,
              ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::LIVE, _, _))
              ConfigureStream(_, bluetooth::le_audio::types::LeAudioContextType::LIVE, _, _, _))
          .Times(1);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()).Times(1);
@@ -10156,7 +10270,7 @@ TEST_F(UnicastTest, UpdateMultipleBidirContextTypes_SpeedUpReconfigFlagEnabled)
  EXPECT_CALL(*mock_le_audio_source_hal_client_, SuspendedForReconfiguration()).Times(0);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, CancelStreamingRequest()).Times(0);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0);
  LeAudioClient::Get()->SetInCall(true);
  SyncOnMainLoop();
@@ -10191,7 +10305,7 @@ TEST_F(UnicastTest, UpdateMultipleBidirContextTypes_SpeedUpReconfigFlagEnabled)
  EXPECT_CALL(*mock_le_audio_source_hal_client_, SuspendedForReconfiguration()).Times(0);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, CancelStreamingRequest()).Times(0);
  EXPECT_CALL(*mock_le_audio_source_hal_client_, ReconfigurationComplete()).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, ConfigureStream(_, _, _, _, _)).Times(0);
  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0);
  LeAudioClient::Get()->SetInCall(false);
+2 −1
Original line number Diff line number Diff line
@@ -41,7 +41,8 @@ public:
               bluetooth::le_audio::types::LeAudioContextType context_type,
               const bluetooth::le_audio::types::BidirectionalPair<
                       bluetooth::le_audio::types::AudioContexts>& metadata_context_types,
               bluetooth::le_audio::types::BidirectionalPair<std::vector<uint8_t>> ccid_lists),
               bluetooth::le_audio::types::BidirectionalPair<std::vector<uint8_t>> ccid_lists,
               bool configure_qos),
              (override));
  MOCK_METHOD((void), StopStream, (bluetooth::le_audio::LeAudioDeviceGroup * group), (override));
  MOCK_METHOD((void), ProcessGattNotifEvent,
+31 −5
Original line number Diff line number Diff line
@@ -301,13 +301,26 @@ public:

  bool ConfigureStream(LeAudioDeviceGroup* group, LeAudioContextType context_type,
                       const BidirectionalPair<AudioContexts>& metadata_context_types,
                       BidirectionalPair<std::vector<uint8_t>> ccid_lists) override {
                       BidirectionalPair<std::vector<uint8_t>> ccid_lists,
                       bool configure_qos) override {
    if (group->GetState() > AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
      log::error("Stream should be stopped or in configured stream. Current state: {}",
                 ToString(group->GetState()));
      return false;
    }

    if (configure_qos) {
      if (group->IsConfiguredForContext(context_type)) {
        if (group->Activate(context_type, metadata_context_types, ccid_lists)) {
          SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
          if (CigCreate(group)) {
            return true;
          }
        }
      }
      log::info("Could not activate device, try to configure it again");
    }

    group->Deactivate();
    ReleaseCisIds(group);

@@ -319,7 +332,11 @@ public:
    }

    group->cig.GenerateCisIds(context_type);
    if (configure_qos) {
      SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
    } else {
      SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
    }
    return PrepareAndSendCodecConfigToTheGroup(group);
  }

@@ -507,7 +524,8 @@ public:
              group->group_id_, ToString(group->cig.GetState()),
              static_cast<int>(conn_handles.size()));

    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
        group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
      /* Group is not going to stream. It happen while CIG was creating.
       * Remove CIG in such a case
       */
@@ -2012,7 +2030,8 @@ private:
        /* Last node configured, process group to codec configured state */
        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);

        if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING ||
            group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
          if (group->cig.GetState() == CigState::CREATED) {
            /* It can happen on the earbuds switch scenario. When one device
             * is getting remove while other is adding to the stream and CIG is
@@ -2224,7 +2243,8 @@ private:
      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
        SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

        if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
            group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
          log::warn("{}, ase_id: {}, target state: {}", leAudioDevice->address_, ase->id,
                    ToString(group->GetTargetState()));
          group->PrintDebugState();
@@ -2252,6 +2272,12 @@ private:

        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

        if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
          cancel_watchdog_if_needed(group->group_id_);
          state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                   GroupStreamStatus::CONFIGURED_BY_USER);
          return;
        }
        PrepareAndSendEnableToTheGroup(group);

        break;
+2 −2
Original line number Diff line number Diff line
@@ -57,8 +57,8 @@ public:
  virtual bool ConfigureStream(
          LeAudioDeviceGroup* group, types::LeAudioContextType context_type,
          const types::BidirectionalPair<types::AudioContexts>& metadata_context_types,
          types::BidirectionalPair<std::vector<uint8_t>> ccid_lists = {.sink = {},
                                                                       .source = {}}) = 0;
          types::BidirectionalPair<std::vector<uint8_t>> ccid_lists = {.sink = {}, .source = {}},
          bool configure_qos = false) = 0;
  virtual void StopStream(LeAudioDeviceGroup* group) = 0;
  virtual void ProcessGattCtpNotification(LeAudioDeviceGroup* group, uint8_t* value,
                                          uint16_t len) = 0;
Loading