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

Commit f21ef791 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

laudio: Fix choosing inactive ase whe it is in configured stated

Stack keeps cached configuration into the ase structure.
When ASE is used then ase->active is set to true and ase->cis_id is
set valid number.
When stream is released, CIG is destroyed. If ASE stays in configured
state then ase->active is set to false but ase->cis_id keeps previously
assigned cis_id. Thanks to this, stack is able to find configured ASE
and reuse it.
When ASEs states goes to IDLE, then ase->cis_id is cleared.

Bug: 222674521
Bug: 150670922
Test: atest --bluetooth_le_audio_test bluetoot_le_audio_client_test
Change-Id: I5778d588478051b72cae2f074c71e3e9d7d71403
parent 0d33ffe0
Loading
Loading
Loading
Loading
+25 −7
Original line number Diff line number Diff line
@@ -908,8 +908,8 @@ bool LeAudioDevice::ConfigureAses(
    types::LeAudioContextType context_type,
    uint8_t* number_of_already_active_group_ase,
    types::AudioLocations& group_snk_audio_locations,
    types::AudioLocations& group_src_audio_locations, bool reconnect) {
  struct ase* ase = GetFirstInactiveAse(ent.direction, reconnect);
    types::AudioLocations& group_src_audio_locations, bool reuse_cis_id) {
  struct ase* ase = GetFirstInactiveAse(ent.direction, reuse_cis_id);
  if (!ase) return false;

  uint8_t active_ases = *number_of_already_active_group_ase;
@@ -972,7 +972,7 @@ bool LeAudioDevice::ConfigureAses(
               << ", cis_id=" << +ase->cis_id
               << ", target_latency=" << +ent.target_latency;

    ase = GetFirstInactiveAse(ent.direction, reconnect);
    ase = GetFirstInactiveAse(ent.direction, reuse_cis_id);
  }

  *number_of_already_active_group_ase = active_ases;
@@ -989,6 +989,9 @@ bool LeAudioDeviceGroup::ConfigureAses(
          audio_set_conf, NumOfConnected(context_type)))
    return false;

  bool reuse_cis_id =
      GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;

  /* TODO For now: set ase if matching with first pac.
   * 1) We assume as well that devices will match requirements in order
   *    e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc.
@@ -1024,7 +1027,7 @@ bool LeAudioDeviceGroup::ConfigureAses(

      if (!device->ConfigureAses(ent, context_type, &active_ase_num,
                                 group_snk_audio_locations,
                                 group_src_audio_locations))
                                 group_src_audio_locations, reuse_cis_id))
        continue;

      required_device_cnt--;
@@ -1332,16 +1335,31 @@ struct ase* LeAudioDevice::GetFirstActiveAseByDataPathState(
}

struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction,
                                               bool reconnect) {
                                               bool reuse_cis_id) {
  auto iter = std::find_if(ases_.begin(), ases_.end(),
                           [direction, reconnect](const auto& ase) {
                           [direction, reuse_cis_id](const auto& ase) {
                             if (ase.active || (ase.direction != direction))
                               return false;

                             if (!reconnect) return true;
                             if (!reuse_cis_id) return true;

                             return (ase.cis_id != kInvalidCisId);
                           });
  /* If ASE is found, return it */
  if (iter != ases_.end()) return &(*iter);

  /* If reuse was not set, that means there is no inactive ASE available. */
  if (!reuse_cis_id) return nullptr;

  /* Since there is no ASE with assigned CIS ID, it means new configuration
   * needs more ASEs then it was configured before.
   * Let's find just inactive one */
  iter = std::find_if(ases_.begin(), ases_.end(),
                      [direction](const auto& ase) {
                        if (ase.active || (ase.direction != direction))
                          return false;
                        return true;
                      });

  return (iter == ases_.end()) ? nullptr : &(*iter);
}
+12 −7
Original line number Diff line number Diff line
@@ -1114,8 +1114,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    ase = leAudioDevice->GetFirstActiveAse();
    LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
    do {
      uint8_t cis_id = ase->cis_id;
      if (cis_id == le_audio::kInvalidCisId) {
        /* Get completive (to be bi-directional CIS) CIS ID for ASE */
      uint8_t cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase);
        cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase);
        LOG_INFO(" Configure ase_id %d, cis_id %d, ase state %s", ase->id,
                 cis_id, ToString(ase->state).c_str());
        if (cis_id == le_audio::kInvalidCisId) {
          /* Get next free CIS ID for group */
          cis_id = group->GetFirstFreeCisId();
@@ -1125,6 +1129,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
            return;
          }
        }
      }

      ase->cis_id = cis_id;

+41 −16
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ static RawAddress GetTestAddress(uint8_t index) {
}

static uint8_t ase_id_last_assigned;
static uint8_t additional_ases = 0;

class MockLeAudioGroupStateMachineCallbacks
    : public LeAudioGroupStateMachine::Callbacks {
@@ -159,6 +160,7 @@ class StateMachineTest : public Test {
    gatt::SetMockBtaGattQueue(&gatt_queue);

    ase_id_last_assigned = types::ase::kAseIdInvalid;
    additional_ases = 0;
    ::le_audio::AudioSetConfigurationProvider::Initialize();
    LeAudioGroupStateMachine::Initialize(&mock_callbacks_);

@@ -415,6 +417,7 @@ class StateMachineTest : public Test {

    le_audio_devices_.clear();
    cached_codec_configuration_map_.clear();
    cached_ase_to_cis_id_map_.clear();
    LeAudioGroupStateMachine::Cleanup();
    ::le_audio::AudioSetConfigurationProvider::Cleanup();
  }
@@ -651,17 +654,17 @@ class StateMachineTest : public Test {
    uint8_t num_ase_src;
    switch (context_type) {
      case kContextTypeRingtone:
        num_ase_snk = 1;
        num_ase_snk = 1 + additional_ases;
        num_ase_src = 0;
        break;

      case kContextTypeMedia:
        num_ase_snk = 2;
        num_ase_snk = 2 + additional_ases;
        num_ase_src = 0;
        break;

      case kContextTypeConversational:
        num_ase_snk = 1;
        num_ase_snk = 1 + additional_ases;
        num_ase_src = 1;
        break;

@@ -824,10 +827,11 @@ class StateMachineTest : public Test {
  }

  void PrepareConfigureQosHandler(LeAudioDeviceGroup* group,
                                  int verify_ase_count = 0) {
                                  int verify_ase_count = 0,
                                  bool caching = false) {
    ase_ctp_handlers[ascs::kAseCtpOpcodeConfigureQos] =
        [group, verify_ase_count](LeAudioDevice* device,
                                  std::vector<uint8_t> value,
        [group, verify_ase_count, caching, this](
            LeAudioDevice* device, std::vector<uint8_t> value,
            GATT_WRITE_OP_CB cb, void* cb_data) {
          auto num_ase = value[1];

@@ -870,6 +874,24 @@ class StateMachineTest : public Test {
                (uint16_t)((ase_p[0] << 16) | (ase_p[1] << 8) | ase_p[2]);
            ase_p += 3;

            if (caching) {
              LOG(INFO) << __func__ << " Device: " << device->address_;
              if (cached_ase_to_cis_id_map_.count(device->address_) > 0) {
                auto ase_list = cached_ase_to_cis_id_map_.at(device->address_);
                if (ase_list.count(ase_id) > 0) {
                  auto cis_id = ase_list.at(ase_id);
                  ASSERT_EQ(cis_id, qos_configured_state_params.cis_id);
                } else {
                  ase_list[ase_id] = qos_configured_state_params.cis_id;
                }
              } else {
                std::map<int, int> ase_map;
                ase_map[ase_id] = qos_configured_state_params.cis_id;

                cached_ase_to_cis_id_map_[device->address_] = ase_map;
              }
            }

            InjectAseStateNotification(ase, device, group,
                                       ascs::kAseStateQoSConfigured,
                                       &qos_configured_state_params);
@@ -1098,6 +1120,8 @@ class StateMachineTest : public Test {
  std::map<int, client_parser::ascs::ase_codec_configured_state_params>
      cached_codec_configuration_map_;

  std::map<RawAddress, std::map<int, int>> cached_ase_to_cis_id_map_;

  MockLeAudioGroupStateMachineCallbacks mock_callbacks_;
  std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_;
  std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>>
@@ -1792,17 +1816,18 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
  const auto context_type = kContextTypeRingtone;
  const int leaudio_group_id = 4;

  additional_ases = 2;
  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);

  /* Since we prepared device with Ringtone context in mind, only one ASE
   * should have been configured.
   */
  PrepareConfigureCodecHandler(group, 1, true);
  PrepareConfigureQosHandler(group, 1);
  PrepareEnableHandler(group, 1);
  PrepareDisableHandler(group, 1);
  PrepareReleaseHandler(group, 1);
  PrepareConfigureCodecHandler(group, 2, true);
  PrepareConfigureQosHandler(group, 2, true);
  PrepareEnableHandler(group, 2);
  PrepareDisableHandler(group, 2);
  PrepareReleaseHandler(group, 2);

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
@@ -1812,9 +1837,9 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);

  InjectInitialIdleNotification(group);
@@ -1834,7 +1859,7 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(2);

  // Start the configuration and stream Media content
  // Start the configuration and stream Ringtone content
  LeAudioGroupStateMachine::Get()->StartStream(
      group, static_cast<types::LeAudioContextType>(context_type));