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

Commit 84bfcf37 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Improve sending QoSConfigure when CIG is already created

There are some cases when ASEs are reconfigured from the QoS Configured
state but CIG does not have to be recreated.
Usually this is when one device connects while another is not yet in
STREAMING state.

This patch improves this handling in the way that

1. StartStream() tries to assigne proper configuration to ASEs when
   detects this.
2. StateMachine will continue with QoSConfigure for whole set, when CIG
   is already there.

Bug: 331775328
Test: atest bluetooth_le_audio_test
Flag: Exempt, regression tested with unit test new test added
Change-Id: I66c6316d7bd1579aef26607f32c77e154e25a205
parent dd2779ef
Loading
Loading
Loading
Loading
+42 −3
Original line number Diff line number Diff line
@@ -241,7 +241,25 @@ public:
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: {
        LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
        if (!leAudioDevice) {
          log::error("group has no active devices");
          group->PrintDebugState();
          log::error("group_id: {} has no active devices", group->group_id_);
          return false;
        }

        if (!group->IsConfiguredForContext(context_type)) {
          if (group->GetConfigurationContextType() == context_type) {
            log::info(
                    "Looks like another device connected in the meantime to group_id: {}, try to "
                    "reconfigure.",
                    group->group_id_);
            if (group->Configure(context_type, metadata_context_types, ccid_lists)) {
              return PrepareAndSendCodecConfigToTheGroup(group);
            }
          }
          log::error("Trying to start stream not configured for the context {} in group_id: {} ",
                     ToString(context_type), group->group_id_);
          group->PrintDebugState();
          StopStream(group);
          return false;
        }

@@ -1864,6 +1882,7 @@ private:
    }

    std::vector<uint8_t> value;
    log::info("{} -> ", leAudioDevice->address_);
    bluetooth::le_audio::client_parser::ascs::PrepareAseCtpCodecConfig(confs, value);
    WriteToControlPoint(leAudioDevice, value);

@@ -1997,8 +2016,12 @@ private:
          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
             * already created */
            PrepareAndSendConfigQos(group, leAudioDevice);
             * already created.
             * Also if one of the set members got reconnected while the other was in QoSConfigured
             * state. In this case, state machine will keep CIG but will send Codec Config to all
             * the set members and when ASEs will move to Codec Configured State, we would like a
             * whole group to move to QoS Configure.*/
            PrepareAndSendQoSToTheGroup(group);
          } else if (!CigCreate(group)) {
            log::error("Could not create CIG. Stop the stream for group {}", group->group_id_);
            StopStream(group);
@@ -2467,12 +2490,23 @@ private:
    msg_stream << kLogAseQoSConfigOp;

    std::stringstream extra_stream;
    int number_of_active_ases = 0;
    int number_of_streaming_ases = 0;

    for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
         ase = leAudioDevice->GetNextActiveAse(ase)) {
      log::debug("device: {}, ase_id: {}, cis_id: {}, ase state: {}", leAudioDevice->address_,
                 ase->id, ase->cis_id, ToString(ase->state));

      /* QoS Config can be done on ASEs which are in Codec Configured and QoS Configured state.
       * If ASE is streaming, it can be skipped.
       */
      number_of_active_ases++;
      if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        number_of_streaming_ases++;
        continue;
      }

      /* Fill in the whole group dependent ASE parameters */
      if (!group->GetPresentationDelay(&ase->qos_config.presentation_delay, ase->direction)) {
        log::error("inconsistent presentation delay for group");
@@ -2523,6 +2557,11 @@ private:
                   << +conf.retrans_nb << "," << +conf.phy << "," << +conf.framing << ";;";
    }

    if (number_of_streaming_ases > 0 && number_of_streaming_ases == number_of_active_ases) {
      log::debug("Device {} is already streaming", leAudioDevice->address_);
      return;
    }

    if (confs.size() == 0 || !validate_transport_latency || !validate_max_sdu_size) {
      log::error("Invalid configuration or latency or sdu size");
      group->PrintDebugState();
+103 −0
Original line number Diff line number Diff line
@@ -709,6 +709,20 @@ protected:
    return &(*group);
  }

  void InjectAclConnected(LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
                          uint16_t conn_id) {
    // Do what the client.cc does when handling the disconnection event
    leAudioDevice->conn_id_ = conn_id;
    leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);

    /* Update all stuff on the group when device got connected */
    group->ReloadAudioLocations();
    group->ReloadAudioDirections();
    group->UpdateAudioContextAvailability();
    group->InvalidateCachedConfigurations();
    group->InvalidateGroupStrategy();
  }

  void InjectAclDisconnected(LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
    // Do what the client.cc does when handling the disconnection event
    leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
@@ -6745,6 +6759,95 @@ TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
          types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING));
}

TEST_F(StateMachineTest, ReconfigureGroupWhenSecondDeviceConnectsAndFirstIsInQoSConfiguredState) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;
  const auto num_devices = 2;

  /**
   * Scenario
   * 1. One set member is connected and configured to QoS
   * 2. Second set member connects and group is configured after that
   * 3. Expect Start stream and expect to get Codec Config and later QoS Config on both devices.
   *
   */
  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);

  // Prepare multiple fake connected devices in a group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
  ASSERT_EQ(group->Size(), num_devices);

  PrepareConfigureCodecHandler(group, 0, true);
  PrepareConfigureQosHandler(group);

  InjectInitialIdleNotification(group);

  auto* leAudioDevice = group->GetFirstDevice();
  LeAudioDevice* firstDevice = leAudioDevice;
  LeAudioDevice* secondDevice = group->GetNextDevice(leAudioDevice);
  uint16_t stored_conn_id = secondDevice->conn_id_;

  log::info("Inject disconnect second device");
  InjectAclDisconnected(group, secondDevice);

  /* Three Writes:
   * 1. Codec configure
   * 2. Codec QoS
   * 3. Enable
   */
  EXPECT_CALL(gatt_queue, WriteCharacteristic(firstDevice->conn_id_, firstDevice->ctp_hdls_.val_hdl,
                                              _, GATT_WRITE_NO_RSP, _, _))
          .Times(3);

  EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(1);

  // Start the configuration and stream Media content
  LeAudioGroupStateMachine::Get()->StartStream(group, context_type,
                                               {.sink = types::AudioContexts(context_type),
                                                .source = types::AudioContexts(context_type)},
                                               {.sink = {}, .source = {}});

  /* Check if group has transitioned to a proper state */
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

  testing::Mock::VerifyAndClearExpectations(&gatt_queue);
  testing::Mock::VerifyAndClearExpectations(mock_iso_manager_);

  log::info("Inject connecting second device");
  InjectAclConnected(group, secondDevice, stored_conn_id);

  PrepareEnableHandler(group);

  EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig).Times(0);

  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::STREAMING));

  /* Three Writes:
   * 1. Codec configure
   * 2. Codec QoS
   * 3. Enable
   */
  EXPECT_CALL(gatt_queue, WriteCharacteristic(firstDevice->conn_id_, firstDevice->ctp_hdls_.val_hdl,
                                              _, GATT_WRITE_NO_RSP, _, _))
          .Times(3);
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(secondDevice->conn_id_, secondDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
          .Times(3);

  // Start the configuration and stream Media content
  LeAudioGroupStateMachine::Get()->StartStream(group, context_type,
                                               {.sink = types::AudioContexts(context_type),
                                                .source = types::AudioContexts(context_type)},
                                               {.sink = {}, .source = {}});

  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
  testing::Mock::VerifyAndClearExpectations(&gatt_queue);
  testing::Mock::VerifyAndClearExpectations(mock_iso_manager_);
}

TEST_F(StateMachineTest, StartStreamAfterConfigure) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;