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

Commit d2cc4123 authored by Jakub Pawłowski's avatar Jakub Pawłowski Committed by Gerrit Code Review
Browse files

Merge changes I577c1db4,Idd648186,I7a607d3d,I5c14c5ac

* changes:
  leaudio: Fix race between ASE state and CIS disconnection
  leaudio: Add allocation and more ase data to dumpsys
  leaudio: Remove missleading logs
  leaudio: Fix invalid CIG parameters
parents 349305f1 9ddba66b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -461,6 +461,8 @@ class LeAudioClientImpl : public LeAudioClient {
        ToString(group->GetTargetState()).c_str());
    group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);

    group->PrintDebugState();

    /* There is an issue with a setting up stream or any other operation which
     * are gatt operations. It means peer is not responsable. Lets close ACL
     */
+177 −63
Original line number Diff line number Diff line
@@ -882,16 +882,12 @@ bool LeAudioDeviceGroup::IsGroupStreamReady(void) {
  return iter == leAudioDevices_.end();
}

bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) {
  auto iter =
      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
        if (d.expired())
          return false;
        else
          return !(((d.lock()).get())->HaveAllAsesCisDisc());
      });

  return iter == leAudioDevices_.end();
bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) {
  for (auto const dev : leAudioDevices_) {
    if (dev.expired()) continue;
    if (dev.lock().get()->HaveAnyCisConnected()) return false;
  }
  return true;
}

uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
@@ -1384,7 +1380,6 @@ static uint32_t GetFirstLeft(const types::AudioLocations& audio_locations) {
  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround)
    return codec_spec_conf::kLeAudioLocationLeftSurround;

  LOG_WARN("Can't find device able to render left audio channel");
  return 0;
}

@@ -1422,15 +1417,15 @@ static uint32_t GetFirstRight(const types::AudioLocations& audio_locations) {
  if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround)
    return codec_spec_conf::kLeAudioLocationRightSurround;

  LOG_WARN("Can't find device able to render right audio channel");
  return 0;
}

uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
                           types::AudioLocations device_locations,
                           types::AudioLocations* group_locations) {
  LOG_DEBUG("strategy: %d, locations: %lx, group locations: %lx", (int)strategy,
            device_locations.to_ulong(), group_locations->to_ulong());
  LOG_DEBUG("strategy: %d, locations: 0x%lx, group locations: 0x%lx",
            (int)strategy, device_locations.to_ulong(),
            group_locations->to_ulong());

  auto is_left_not_yet_assigned =
      !(group_locations->to_ulong() & codec_spec_conf::kLeAudioLocationAnyLeft);
@@ -1439,6 +1434,10 @@ uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
  uint32_t left_device_loc = GetFirstLeft(device_locations);
  uint32_t right_device_loc = GetFirstRight(device_locations);

  if (left_device_loc == 0 && right_device_loc == 0) {
    LOG_WARN("Can't find device able to render left  and right audio channel");
  }

  switch (strategy) {
    case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
    case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
@@ -1496,6 +1495,12 @@ bool LeAudioDevice::ConfigureAses(
    return false;
  }

  /* The number_of_already_active_group_ase keeps all the active ases
   * in other devices in the group.
   * This function counts active ases only for this device, and we count here
   * new active ases and already active ases which we want to reuse in the
   * scenario
   */
  uint8_t active_ases = *number_of_already_active_group_ase;
  uint8_t max_required_ase_per_dev =
      ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
@@ -1523,6 +1528,12 @@ bool LeAudioDevice::ConfigureAses(
    ase->configured_for_context_type = context_type;
    active_ases++;

    /* In case of late connect, we could be here for STREAMING ase.
     * in such case, it is needed to mark ase as known active ase which
     * is important to validate scenario and is done already few lines above.
     * Nothing more to do is needed here.
     */
    if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
      if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
        ase->reconfigure = true;

@@ -1535,7 +1546,8 @@ bool LeAudioDevice::ConfigureAses(
      ase->codec_config.audio_channel_allocation =
          PickAudioLocation(strategy, audio_locations, group_audio_locations);

    /* Get default value if no requirement for specific frame blocks per sdu */
      /* Get default value if no requirement for specific frame blocks per sdu
       */
      if (!ase->codec_config.codec_frames_blocks_per_sdu) {
        ase->codec_config.codec_frames_blocks_per_sdu =
            GetMaxCodecFramesPerSduFromPac(pac);
@@ -1558,6 +1570,7 @@ bool LeAudioDevice::ConfigureAses(
            GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
                        std::vector<uint8_t>());
      }
    }

    LOG_DEBUG(
        "device=%s, activated ASE id=%d, direction=%s, max_sdu_size=%d, "
@@ -1961,6 +1974,61 @@ bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type,
}

LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }

void LeAudioDeviceGroup::PrintDebugState(void) {
  auto* active_conf = GetActiveConfiguration();
  std::stringstream debug_str;

  debug_str << "\n Groupd id: " << group_id_
            << ", state: " << bluetooth::common::ToString(GetState())
            << ", target state: "
            << bluetooth::common::ToString(GetTargetState())
            << ", cig state: " << bluetooth::common::ToString(cig_state_)
            << ", \n group available contexts: "
            << bluetooth::common::ToString(GetAvailableContexts())
            << ", \n configuration context type: "
            << bluetooth::common::ToString(GetConfigurationContextType())
            << ", \n active configuration name: "
            << (active_conf ? active_conf->name : " not set");

  if (cises_.size() > 0) {
    LOG_INFO("\n Allocated CISes: %d", static_cast<int>(cises_.size()));
    for (auto cis : cises_) {
      LOG_INFO("\n cis id: %d, type: %d, conn_handle %d, addr: %s", cis.id,
               cis.type, cis.conn_handle, cis.addr.ToString().c_str());
    }
  }

  if (GetFirstActiveDevice() != nullptr) {
    uint32_t sink_delay = 0;
    uint32_t source_delay = 0;
    GetPresentationDelay(&sink_delay, le_audio::types::kLeAudioDirectionSink);
    GetPresentationDelay(&source_delay,
                         le_audio::types::kLeAudioDirectionSource);
    auto phy_mtos = GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
    auto phy_stom = GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
    auto max_transport_latency_mtos = GetMaxTransportLatencyMtos();
    auto max_transport_latency_stom = GetMaxTransportLatencyStom();
    auto sdu_mts = GetSduInterval(le_audio::types::kLeAudioDirectionSink);
    auto sdu_stom = GetSduInterval(le_audio::types::kLeAudioDirectionSource);

    debug_str << "\n resentation_delay for sink (speaker): " << +sink_delay
              << " us, presentation_delay for source (microphone): "
              << +source_delay << "us, \n MtoS transport latency:  "
              << +max_transport_latency_mtos
              << ", StoM transport latency: " << +max_transport_latency_stom
              << ", \n MtoS Phy: " << loghex(phy_mtos)
              << ", MtoS sdu: " << loghex(phy_stom)
              << " \n MtoS sdu: " << +sdu_mts << ", StoM sdu: " << +sdu_stom;
  }

  LOG_INFO("%s", debug_str.str().c_str());

  for (const auto& device_iter : leAudioDevices_) {
    device_iter.lock()->PrintDebugState();
  }
}

void LeAudioDeviceGroup::Dump(int fd, int active_group_id) {
  bool is_active = (group_id_ == active_group_id);
  std::stringstream stream;
@@ -2329,13 +2397,15 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) {
  return iter == ases_.end();
}

bool LeAudioDevice::HaveAllAsesCisDisc(void) {
  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
    return ase.active &&
           (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
  });

  return iter == ases_.end();
bool LeAudioDevice::HaveAnyCisConnected(void) {
  /* Pending and Disconnecting is considered as connected in this function */
  for (auto const ase : ases_) {
    if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED &&
        ase.data_path_state != AudioStreamDataPathState::IDLE) {
      return true;
    }
  }
  return false;
}

bool LeAudioDevice::HasCisId(uint8_t id) {
@@ -2455,28 +2525,72 @@ void LeAudioDevice::SetSupportedContexts(AudioContexts snk_contexts,
  supp_contexts_.source = src_contexts;
}

void LeAudioDevice::PrintDebugState(void) {
  std::stringstream debug_str;

  debug_str << " address: " << address_ << ", "
            << bluetooth::common::ToString(connection_state_)
            << ", conn_id: " << +conn_id_ << ", mtu: " << +mtu_
            << ", num_of_ase: " << static_cast<int>(ases_.size());

  if (ases_.size() > 0) {
    debug_str << "\n  == ASEs == ";
    for (auto& ase : ases_) {
      debug_str << "\n  id: " << +ase.id << ", active: " << ase.active
                << ", dir: "
                << (ase.direction == types::kLeAudioDirectionSink ? "sink"
                                                                  : "source")
                << ", cis_id: " << +ase.cis_id
                << ", cis_handle: " << +ase.cis_conn_hdl << ", state: "
                << bluetooth::common::ToString(ase.data_path_state)
                << "\n ase max_latency: " << +ase.max_transport_latency
                << ", rtn: " << +ase.retrans_nb
                << ", max_sdu: " << +ase.max_sdu_size
                << ", target latency: " << +ase.target_latency;
    }
  }

  LOG_INFO("%s", debug_str.str().c_str());
}

void LeAudioDevice::Dump(int fd) {
  uint16_t acl_handle = BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
  std::string location = "unknown location";

  if (snk_audio_locations_.to_ulong() &
      codec_spec_conf::kLeAudioLocationAnyLeft) {
    std::string location_left = "left";
    location.swap(location_left);
  } else if (snk_audio_locations_.to_ulong() &
             codec_spec_conf::kLeAudioLocationAnyRight) {
    std::string location_right = "right";
    location.swap(location_right);
  }

  std::stringstream stream;
  stream << std::boolalpha;
  stream << "\n\taddress: " << ADDRESS_TO_LOGGABLE_STR(address_)
         << ": " << connection_state_ << ": "
  stream << "\n\taddress: " << ADDRESS_TO_LOGGABLE_STR(address_) << ": "
         << connection_state_ << ": "
         << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
         << ", acl_handle: " << std::to_string(acl_handle) << ",\t"
         << (encrypted_ ? "Encrypted" : "Unecrypted")
         << ", acl_handle: " << std::to_string(acl_handle) << ", " << location
         << ",\t" << (encrypted_ ? "Encrypted" : "Unecrypted")
         << ",mtu: " << std::to_string(mtu_)
         << "\n\tnumber of ases_: " << static_cast<int>(ases_.size());

  if (ases_.size() > 0) {
    stream << "\n\t  == ASEs == ";
    stream << "\n\t== ASEs == \n\t";
    stream
        << "id  active dir     cis_id  cis_handle  sdu  latency rtn  state";
    for (auto& ase : ases_) {
      stream << "\n\t  id: " << static_cast<int>(ase.id)
             << ",\tactive: " << ase.active << ", dir: "
      stream << std::setfill('\xA0') << "\n\t" << std::left << std::setw(4)
             << static_cast<int>(ase.id) << std::left << std::setw(7)
             << (ase.active ? "true" : "false") << std::left << std::setw(8)
             << (ase.direction == types::kLeAudioDirectionSink ? "sink"
                                                               : "source")
             << ",\tcis_id: " << static_cast<int>(ase.cis_id)
             << ",\tcis_handle: " << ase.cis_conn_hdl << ",\tstate: "
             << std::left << std::setw(8) << static_cast<int>(ase.cis_id)
             << std::left << std::setw(12) << ase.cis_conn_hdl << std::left
             << std::setw(5) << ase.max_sdu_size << std::left << std::setw(8)
             << ase.max_transport_latency << std::left << std::setw(5)
             << static_cast<int>(ase.retrans_nb) << std::left << std::setw(12)
             << bluetooth::common::ToString(ase.data_path_state);
    }
  }
+7 −2
Original line number Diff line number Diff line
@@ -155,7 +155,7 @@ class LeAudioDevice {
  bool IsReadyToCreateStream(void);
  bool IsReadyToSuspendStream(void);
  bool HaveAllActiveAsesCisEst(void);
  bool HaveAllAsesCisDisc(void);
  bool HaveAnyCisConnected(void);
  bool HasCisId(uint8_t id);
  uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
  const struct types::acs_ac_record* GetCodecConfigurationSupportedPac(
@@ -179,7 +179,10 @@ class LeAudioDevice {
                                            types::AudioContexts src_cont_val);
  void DeactivateAllAses(void);
  bool ActivateConfiguredAses(types::LeAudioContextType context_type);

  void PrintDebugState(void);
  void Dump(int fd);

  void DisconnectAcl(void);
  std::vector<uint8_t> GetMetadata(types::AudioContexts context_type,
                                   const std::vector<uint8_t>& ccid_list);
@@ -283,7 +286,7 @@ class LeAudioDeviceGroup {
  bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
  bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
  bool IsGroupStreamReady(void);
  bool HaveAllActiveDevicesCisDisc(void);
  bool HaveAllCisesDisconnected(void);
  uint8_t GetFirstFreeCisId(void);
  uint8_t GetFirstFreeCisId(types::CisType cis_type);
  void CigGenerateCisIds(types::LeAudioContextType context_type);
@@ -370,6 +373,8 @@ class LeAudioDeviceGroup {

  bool IsInTransition(void);
  bool IsReleasingOrIdle(void);

  void PrintDebugState(void);
  void Dump(int fd, int active_group_id);

 private:
+7 −0
Original line number Diff line number Diff line
@@ -627,6 +627,13 @@ struct ase {
        data_path_state(AudioStreamDataPathState::IDLE),
        configured_for_context_type(LeAudioContextType::UNINITIALIZED),
        preferred_phy(0),
        max_sdu_size(0),
        retrans_nb(0),
        max_transport_latency(0),
        pres_delay_min(0),
        pres_delay_max(0),
        preferred_pres_delay_min(0),
        preferred_pres_delay_max(0),
        state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}

  struct hdl_pair hdls;
+122 −19
Original line number Diff line number Diff line
@@ -634,7 +634,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    LOG_DEBUG(
        " device: %s, group connected: %d, all active ase disconnected:: %d",
        ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_),
        group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc());
        group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected());

    /* Update the current group audio context availability which could change
     * due to disconnected group member.
@@ -645,8 +645,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
     * If there is active CIS, do nothing here. Just update the available
     * contexts table.
     */
    if (group->IsAnyDeviceConnected() &&
        !group->HaveAllActiveDevicesCisDisc()) {
    if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) {
      if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
        /* We keep streaming but want others to let know user that it might be
         * need to update offloader with new CIS configuration
@@ -689,7 +688,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
       * or pending. If CIS is established, this will be handled in disconnected
       * complete event
       */
      if (group->HaveAllActiveDevicesCisDisc()) {
      if (group->HaveAllCisesDisconnected()) {
        RemoveCigForGroup(group);
      }

@@ -833,7 +832,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
         * If there is other device connected and streaming, just leave it as it
         * is, otherwise stop the stream.
         */
        if (!group->HaveAllActiveDevicesCisDisc()) {
        if (!group->HaveAllCisesDisconnected()) {
          /* There is ASE streaming for some device. Continue streaming. */
          LOG_WARN(
              "Group member disconnected during streaming. Cis handle 0x%04x",
@@ -859,7 +858,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
         */
        if ((group->GetState() ==
             AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
            group->HaveAllActiveDevicesCisDisc()) {
            group->HaveAllCisesDisconnected()) {
          /* No more transition for group */
          alarm_cancel(watchdog_);

@@ -869,15 +868,58 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
        /* Those two are used when closing the stream and CIS disconnection is
         * expected */
        if (group->HaveAllActiveDevicesCisDisc()) {
          RemoveCigForGroup(group);
        if (!group->HaveAllCisesDisconnected()) {
          LOG_DEBUG(
              "Still waiting for all CISes being disconnected for group:%d",
              group->group_id_);
          return;
        }

        break;
        auto current_group_state = group->GetState();
        LOG_INFO("group %d current state: %s, target state: %s",
                 group->group_id_,
                 bluetooth::common::ToString(current_group_state).c_str(),
                 bluetooth::common::ToString(target_state).c_str());
        /* It might happen that controller notified about CIS disconnection
         * later, after ASE state already changed.
         * In such an event, there is need to notify upper layer about state
         * from here.
         */
        if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
          LOG_INFO(
              "Cises disconnected for group %d, we are good in Idle state.",
              group->group_id_);
          ReleaseCisIds(group);
          state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                   GroupStreamStatus::IDLE);
        } else if (current_group_state ==
                   AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
          auto reconfig = group->IsPendingConfiguration();
          LOG_INFO(
              "Cises disconnected for group: %d, we are good in Configured "
              "state, reconfig=%d.",
              group->group_id_, reconfig);
          if (reconfig) {
            group->ClearPendingConfiguration();
            state_machine_callbacks_->StatusReportCb(
                group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
            /* No more transition for group */
            alarm_cancel(watchdog_);
          } else {
            /* This is Autonomous change if both, target and current state
             * is CODEC_CONFIGURED
             */
            if (target_state == current_group_state) {
              state_machine_callbacks_->StatusReportCb(
                  group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
            }
          }
        }
        RemoveCigForGroup(group);
      } break;
      default:
        break;
    }
@@ -1271,6 +1313,15 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      }
    }

    if ((sdu_interval_mtos == 0 && sdu_interval_stom == 0) ||
        (max_trans_lat_mtos == le_audio::types::kMaxTransportLatencyMin &&
         max_trans_lat_stom == le_audio::types::kMaxTransportLatencyMin) ||
        (max_sdu_size_mtos == 0 && max_sdu_size_stom == 0)) {
      LOG_ERROR(" Trying to create invalid group");
      group->PrintDebugState();
      return false;
    }

    bluetooth::hci::iso_manager::cig_create_params param = {
        .sdu_itv_mtos = sdu_interval_mtos,
        .sdu_itv_stom = sdu_interval_stom,
@@ -1455,9 +1506,21 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          PrepareAndSendRelease(leAudioDeviceNext);
        } else {
          /* Last node is in releasing state*/
          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);

          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);

          group->PrintDebugState();
          /* If all CISes are disconnected, notify upper layer about IDLE state,
           * otherwise wait for */
          if (!group->HaveAllCisesDisconnected()) {
            LOG_WARN(
                "Not all CISes removed before going to IDLE for group %d, "
                "waiting...",
                group->group_id_);
            group->PrintDebugState();
            return;
          }

          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
          ReleaseCisIds(group);
          state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                   GroupStreamStatus::IDLE);
@@ -1638,6 +1701,19 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                  AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
              group->IsPendingConfiguration()) {
            LOG_INFO(" Configured state completed ");

            /* If all CISes are disconnected, notify upper layer about IDLE
             * state, otherwise wait for */
            if (!group->HaveAllCisesDisconnected()) {
              LOG_WARN(
                  "Not all CISes removed before going to CONFIGURED for group "
                  "%d, "
                  "waiting...",
                  group->group_id_);
              group->PrintDebugState();
              return;
            }

            group->ClearPendingConfiguration();
            state_machine_callbacks_->StatusReportCb(
                group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
@@ -1776,7 +1852,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
          PrepareAndSendRelease(leAudioDeviceNext);
        } else {
          /* Last node is in releasing state*/
          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);

          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
          /* Remote device has cache and keep staying in configured state after
@@ -1784,6 +1859,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
           * remote device.
           */
          group->SetTargetState(group->GetState());

          if (!group->HaveAllCisesDisconnected()) {
            LOG_WARN(
                "Not all CISes removed before going to IDLE for group %d, "
                "waiting...",
                group->group_id_);
            group->PrintDebugState();
            return;
          }

          state_machine_callbacks_->StatusReportCb(
              group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
        }
@@ -1872,7 +1957,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

        group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

        if (!group->HaveAllActiveDevicesCisDisc()) return;
        if (!group->HaveAllCisesDisconnected()) return;

        if (group->GetTargetState() ==
            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
@@ -1968,6 +2053,9 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                               LeAudioDevice* leAudioDevice) {
    std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs;

    bool validate_transport_latency = false;
    bool validate_max_sdu_size = false;

    for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
         ase = leAudioDevice->GetNextActiveAse(ase)) {
      LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
@@ -1984,14 +2072,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      conf.max_sdu = ase->max_sdu_size;
      conf.retrans_nb = ase->retrans_nb;
      if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) {
        LOG(ERROR) << __func__ << ", inconsistent presentation delay for group";
        LOG_ERROR("inconsistent presentation delay for group");
        group->PrintDebugState();
        StopStream(group);
        return;
      }

      conf.sdu_interval = group->GetSduInterval(ase->direction);
      if (!conf.sdu_interval) {
        LOG(ERROR) << __func__ << ", unsupported SDU interval for group";
        LOG_ERROR("unsupported SDU interval for group");
        group->PrintDebugState();
        StopStream(group);
        return;
      }
@@ -2001,15 +2091,28 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      } else {
        conf.max_transport_latency = group->GetMaxTransportLatencyStom();
      }

      if (conf.max_transport_latency >
          le_audio::types::kMaxTransportLatencyMin) {
        validate_transport_latency = true;
      }

      if (conf.max_sdu > 0) {
        validate_max_sdu_size = true;
      }
      confs.push_back(conf);
    }

    LOG_ASSERT(confs.size() > 0)
        << __func__ << " shouldn't be called without an active ASE";
    if (confs.size() == 0 || !validate_transport_latency ||
        !validate_max_sdu_size) {
      LOG_ERROR("Invalid configuration or latency or sdu size");
      group->PrintDebugState();
      StopStream(group);
      return;
    }

    std::vector<uint8_t> value;
    le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value);

    BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
                                      leAudioDevice->ctp_hdls_.val_hdl, value,
                                      GATT_WRITE_NO_RSP, NULL, NULL);
Loading