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

Commit 5ae44c05 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Łukasz Rymanowski
Browse files

LeAudio: Introduce capabilities struct

This formalizes the audio capabilties and how they are derived from
the LTVs. This patch unifies the approach to parsing capabilities
LTVs with the configuration LTVs.

Bug: 295972694
Test: atest --host bluetooth_le_audio_test bluetooth_le_audio_client_test bluetooth_test_broadcaster bluetooth_test_broadcaster_state_machine bluetooth_le_audio_codec_manager_test
Change-Id: I575510a18df895e93063f2bc414d79bbc7a4f5b9
parent 9eb3b6de
Loading
Loading
Loading
Loading
+55 −93
Original line number Diff line number Diff line
@@ -248,117 +248,63 @@ uint8_t get_num_of_devices_in_configuration(

static bool IsCodecConfigCoreSupported(const types::LeAudioLtvMap& pacs,
                                       const types::LeAudioLtvMap& reqs) {
  uint8_t u8_req_val, u8_pac_val;
  uint16_t u16_req_val, u16_pac_val;
  auto caps = pacs.GetAsCoreCodecCapabilities();
  auto config = reqs.GetAsCoreCodecConfig();

  /* Sampling frequency */
  auto req = reqs.Find(codec_spec_conf::kLeAudioLtvTypeSamplingFreq);
  auto pac =
      pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedSamplingFrequencies);
  if (!req || !pac) {
    LOG_DEBUG(", lack of sampling frequency fields");
  if (!caps.HasSupportedSamplingFrequencies() || !config.sampling_frequency) {
    LOG_DEBUG("Missing supported sampling frequencies capability");
    return false;
  }

  u8_req_val = VEC_UINT8_TO_UINT8(req.value());
  u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());

  /* TODO: Integrate with codec capabilities */
  if (!(u16_pac_val &
        codec_spec_caps::SamplingFreqConfig2Capability(u8_req_val))) {
    /*
     * Note: Requirements are in the codec configuration specification which
     * are values coming from Assigned Numbers: Codec_Specific_Configuration
     */
    LOG_DEBUG(
        " Req:SamplFreq= 0x%04x (Assigned Numbers: "
        "Codec_Specific_Configuration)",
        u8_req_val);
    /* NOTE: Below is Codec specific cababilities comes from Assigned Numbers:
     * Codec_Specific_Capabilities
     */
    LOG_DEBUG(
        " Pac:SamplFreq= 0x%04x  (Assigned numbers: "
        "Codec_Specific_Capabilities - bitfield)",
        u16_pac_val);

    LOG_DEBUG(", sampling frequency not supported");
  if (!caps.IsSamplingFrequencyConfigSupported(
          config.sampling_frequency.value())) {
    LOG_DEBUG("Cfg: SamplingFrequency= 0x%04x",
              config.sampling_frequency.value());
    LOG_DEBUG("Cap: SupportedSamplingFrequencies= 0x%04x",
              caps.supported_sampling_frequencies.value());
    LOG_DEBUG("Sampling frequency not supported");
    return false;
  }

  /* Frame duration */
  req = reqs.Find(codec_spec_conf::kLeAudioLtvTypeFrameDuration);
  pac = pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedFrameDurations);
  if (!req || !pac) {
    LOG_DEBUG(", lack of frame duration fields");
  /* Channel counts */
  if (!caps.IsAudioChannelCountsSupported(
          config.GetChannelCountPerIsoStream())) {
    LOG_DEBUG("Cfg: Allocated channel count= 0x%04x",
              config.GetChannelCountPerIsoStream());
    LOG_DEBUG("Cap: Supported channel counts= 0x%04x",
              caps.supported_audio_channel_counts.value_or(1));
    LOG_DEBUG("Channel count not supported");
    return false;
  }

  u8_req_val = VEC_UINT8_TO_UINT8(req.value());
  u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());

  if ((u8_req_val != codec_spec_conf::kLeAudioCodecFrameDur7500us &&
       u8_req_val != codec_spec_conf::kLeAudioCodecFrameDur10000us) ||
      !(u8_pac_val &
        (codec_spec_caps::FrameDurationConfig2Capability(u8_req_val)))) {
    LOG_DEBUG(" Req:FrameDur=0x%04x", u8_req_val);
    LOG_DEBUG(" Pac:FrameDur=0x%04x", u8_pac_val);
    LOG_DEBUG(", frame duration not supported");
  /* Frame duration */
  if (!caps.HasSupportedFrameDurations() || !config.frame_duration) {
    LOG_DEBUG("Missing supported frame durations capability");
    return false;
  }

  uint8_t required_audio_chan_num =
      reqs.GetAsCoreCodecConfig().GetChannelCountPerIsoStream();
  pac = pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedAudioChannelCounts);

  /*
   * BAP_Validation_r07 1.9.2 Audio channel support requirements
   * "The Unicast Server shall support an Audio_Channel_Counts value of 0x01
   * (0b00000001 = one channel) and may support other values defined by an
   * implementation or by a higher-layer specification."
   *
   * Thus if Audio_Channel_Counts is not present in PAC LTV structure, we assume
   * the Unicast Server supports mandatory one channel.
   */
  if (!pac) {
    LOG_DEBUG(", no Audio_Channel_Counts field in PAC, using default 0x01");
    u8_pac_val = 0x01;
  } else {
    u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());
  }

  if (!((1 << (required_audio_chan_num - 1)) & u8_pac_val)) {
    LOG_DEBUG(" Req:AudioChanCnt=0x%04x", 1 << (required_audio_chan_num - 1));
    LOG_DEBUG(" Pac:AudioChanCnt=0x%04x", u8_pac_val);
    LOG_DEBUG(", channel count warning");
  if (!caps.IsFrameDurationConfigSupported(config.frame_duration.value())) {
    LOG_DEBUG("Cfg: FrameDuration= 0x%04x", config.frame_duration.value());
    LOG_DEBUG("Cap: SupportedFrameDurations= 0x%04x",
              caps.supported_frame_durations.value());
    LOG_DEBUG("Frame duration not supported");
    return false;
  }

  /* Octets per frame */
  req = reqs.Find(codec_spec_conf::kLeAudioLtvTypeOctetsPerCodecFrame);
  pac = pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedOctetsPerCodecFrame);

  if (!req || !pac) {
    LOG_DEBUG(", lack of octet per frame fields");
  if (!caps.HasSupportedOctetsPerCodecFrame() ||
      !config.octets_per_codec_frame) {
    LOG_DEBUG("Missing supported octets per codec frame");
    return false;
  }

  u16_req_val = VEC_UINT8_TO_UINT16(req.value());
  /* Minimal value 0-1 byte */
  u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());
  if (u16_req_val < u16_pac_val) {
    LOG_DEBUG(" Req:OctetsPerFrame=%d", int(u16_req_val));
    LOG_DEBUG(" Pac:MinOctetsPerFrame=%d", int(u16_pac_val));
    LOG_DEBUG(", octet per frame below minimum");
    return false;
  }

  /* Maximal value 2-3 byte */
  u16_pac_val = OFF_VEC_UINT8_TO_UINT16(pac.value(), 2);
  if (u16_req_val > u16_pac_val) {
    LOG_DEBUG(" Req:MaxOctetsPerFrame=%d", int(u16_req_val));
    LOG_DEBUG(" Pac:MaxOctetsPerFrame=%d", int(u16_pac_val));
    LOG_DEBUG(", octet per frame above maximum");
  if (!caps.IsOctetsPerCodecFrameConfigSupported(
          config.octets_per_codec_frame.value())) {
    LOG_DEBUG("Cfg: Octets per frame=%d",
              config.octets_per_codec_frame.value());
    LOG_DEBUG("Cap: Min octets per frame=%d",
              caps.supported_min_octets_per_codec_frame.value());
    LOG_DEBUG("Cap: Max octets per frame=%d",
              caps.supported_max_octets_per_codec_frame.value());
    LOG_DEBUG("Octets per codec frame outside the capabilities");
    return false;
  }

@@ -713,12 +659,28 @@ std::string LeAudioLtvMap::ToString(

const struct LeAudioCoreCodecConfig& LeAudioLtvMap::GetAsCoreCodecConfig()
    const {
  if (core_capabilities) {
    LOG_ERROR("LTVs were already parsed for capabilities!");
  }

  if (!core_config) {
    core_config = LtvMapToCoreCodecConfig(*this);
  }
  return *core_config;
}

const struct LeAudioCoreCodecCapabilities&
LeAudioLtvMap::GetAsCoreCodecCapabilities() const {
  if (core_config) {
    LOG_ERROR("LTVs were already parsed for configurations!");
  }

  if (!core_capabilities) {
    core_capabilities = LtvMapToCoreCodecCapabilities(*this);
  }
  return *core_capabilities;
}

}  // namespace types

void AppendMetadataLtvEntryForCcidList(std::vector<uint8_t>& metadata,
+120 −0
Original line number Diff line number Diff line
@@ -574,6 +574,54 @@ struct LeAudioCoreCodecConfig {
  }
};

struct LeAudioCoreCodecCapabilities {
  bool HasSupportedSamplingFrequencies() const {
    return supported_sampling_frequencies.has_value();
  }
  bool HasSupportedFrameDurations() const {
    return supported_frame_durations.has_value();
  }
  bool HasSupportedOctetsPerCodecFrame() const {
    return supported_min_octets_per_codec_frame.has_value() &&
           supported_max_octets_per_codec_frame.has_value();
  }
  bool HasSupportedAudioChannelCounts() const {
    return supported_audio_channel_counts.has_value();
  }
  bool HasSupportedMaxCodecFramesPerSdu() const {
    return supported_max_codec_frames_per_sdu.has_value();
  }

  bool IsSamplingFrequencyConfigSupported(uint8_t value) const {
    return supported_sampling_frequencies.value_or(0) &
           codec_spec_caps::SamplingFreqConfig2Capability(value);
  }
  bool IsFrameDurationConfigSupported(uint8_t value) const {
    return supported_frame_durations.value_or(0) &
           codec_spec_caps::FrameDurationConfig2Capability(value);
  }
  bool IsAudioChannelCountsSupported(uint8_t value) const {
    if (value > 0)
      return supported_audio_channel_counts.value_or(0) & (0b1 << (value - 1));

    return false;
  }
  bool IsOctetsPerCodecFrameConfigSupported(uint16_t value) const {
    return (value >= supported_min_octets_per_codec_frame.value_or(0)) &&
           (value <= supported_max_octets_per_codec_frame.value_or(0));
  }
  bool IsCodecFramesPerSduSupported(uint8_t value) const {
    return value <= supported_max_codec_frames_per_sdu.value_or(1);
  }

  std::optional<uint16_t> supported_sampling_frequencies;
  std::optional<uint8_t> supported_frame_durations;
  std::optional<uint8_t> supported_audio_channel_counts;
  std::optional<uint16_t> supported_min_octets_per_codec_frame;
  std::optional<uint16_t> supported_max_octets_per_codec_frame;
  std::optional<uint8_t> supported_max_codec_frames_per_sdu;
};

class LeAudioLtvMap {
 public:
  LeAudioLtvMap() {}
@@ -622,6 +670,7 @@ class LeAudioLtvMap {
  }

  const struct LeAudioCoreCodecConfig& GetAsCoreCodecConfig() const;
  const struct LeAudioCoreCodecCapabilities& GetAsCoreCodecCapabilities() const;

  std::string ToString(
      const std::string& indent_string,
@@ -681,9 +730,80 @@ class LeAudioLtvMap {
    return core;
  }

  static LeAudioCoreCodecCapabilities LtvMapToCoreCodecCapabilities(
      LeAudioLtvMap pacs) {
    LeAudioCoreCodecCapabilities core;

    auto pac =
        pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedSamplingFrequencies);
    if (pac &&
        (pac.value().size() ==
         sizeof(decltype(core.supported_sampling_frequencies)::value_type))) {
      core.supported_sampling_frequencies = VEC_UINT8_TO_UINT16(pac.value());
    }

    pac = pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedFrameDurations);
    if (pac && (pac.value().size() ==
                sizeof(decltype(core.supported_frame_durations)::value_type))) {
      core.supported_frame_durations = VEC_UINT8_TO_UINT8(pac.value());
    }

    pac =
        pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedOctetsPerCodecFrame);
    if (pac &&
        (pac.value().size() ==
         (sizeof(
              decltype(core.supported_min_octets_per_codec_frame)::value_type) +
          sizeof(decltype(core.supported_max_octets_per_codec_frame)::
                     value_type)))) {
      core.supported_min_octets_per_codec_frame =
          VEC_UINT8_TO_UINT16(pac.value());
      core.supported_max_octets_per_codec_frame = OFF_VEC_UINT8_TO_UINT16(
          pac.value(),
          sizeof(
              decltype(core.supported_min_octets_per_codec_frame)::value_type));
    }

    /*
     * BAP_1.0.1 4.3.1 Codec_Specific_Capabilities LTV requirements:
     * The absence of the Supported_Audio_Channel_Counts LTV structure shall be
     * interpreted as equivalent to a Supported_Audio_Channel_Counts value of
     * 0x01 (one Audio Channel supported).
     */
    pac =
        pacs.Find(codec_spec_caps::kLeAudioLtvTypeSupportedAudioChannelCounts);
    if (pac &&
        (pac.value().size() ==
         sizeof(decltype(core.supported_audio_channel_counts)::value_type))) {
      core.supported_audio_channel_counts = VEC_UINT8_TO_UINT8(pac.value());
    } else {
      core.supported_audio_channel_counts = 0b1;
    }

    /*
     * BAP_1.0.1 4.3.1 Codec_Specific_Capabilities LTV requirements:
     * The absence of the Supported_Max_Codec_Frames_Per_SDU LTV structure shall
     * be interpreted as equivalent to a Supported_Max_Codec_Frames_Per_SDU
     * value of 1 codec frame per Audio Channel per SDU maximum.
     */
    pac = pacs.Find(
        codec_spec_caps::kLeAudioLtvTypeSupportedMaxCodecFramesPerSdu);
    if (pac &&
        (pac.value().size() ==
         sizeof(
             decltype(core.supported_max_codec_frames_per_sdu)::value_type))) {
      core.supported_max_codec_frames_per_sdu = VEC_UINT8_TO_UINT8(pac.value());
    } else {
      core.supported_max_codec_frames_per_sdu = 1;
    }

    return core;
  }

  std::map<uint8_t, std::vector<uint8_t>> values;
  // Lazy-constructed views of the LTV data
  mutable std::optional<struct LeAudioCoreCodecConfig> core_config;
  mutable std::optional<struct LeAudioCoreCodecCapabilities> core_capabilities;
};

struct LeAudioCodecId {
+118 −0
Original line number Diff line number Diff line
@@ -252,5 +252,123 @@ TEST(LeAudioLtvMapTest, test_configuration_valid) {
  ASSERT_EQ(0x01u, config.codec_frames_blocks_per_sdu.value());
}

TEST(LeAudioLtvMapTest, test_capabilities_valid) {
  // clang-format off
  const std::vector<uint8_t> capabilities_ltv_vec{
      // SupportedSamplingFrequencies = 96000 and 16000
      0x03, 0x01,
          (uint8_t)(codec_spec_caps::kLeAudioSamplingFreq16000Hz) |
              (uint8_t)(codec_spec_caps::kLeAudioSamplingFreq96000Hz),
          (uint8_t)(codec_spec_caps::kLeAudioSamplingFreq16000Hz >> 8) |
              (uint8_t)(codec_spec_caps::kLeAudioSamplingFreq96000Hz >> 8),
      // SupportedFrameDurations = 10ms, 7.5ms, 10ms preferred
      0x02, 0x02, codec_spec_caps::kLeAudioCodecFrameDur7500us |
                      codec_spec_caps::kLeAudioCodecFrameDur10000us |
                      codec_spec_caps::kLeAudioCodecFrameDurPrefer10000us,
      // SupportedAudioChannelCounts = 0b1 | 0b2 (one and two channels)
      0x02, 0x03, 0b01 | 0b10,
      // SupportedOctetsPerCodecFrame = min:40, max:80
      0x05, 0x04, 40, 00, 80, 00,
      // Unknown type entry to ignore
      0x05, 0x06, 0x11, 0x22, 0x33, 0x44,
      // SupportedMaxCodecFramesPerSdu = 2
      0x02, 0x05, 0x02,
  };
  // clang-format on

  // Parse
  bool success = true;
  LeAudioLtvMap ltv_map = LeAudioLtvMap::Parse(
      capabilities_ltv_vec.data(), capabilities_ltv_vec.size(), success);
  ASSERT_TRUE(success);

  // Verify the codec capabilities values
  auto caps = ltv_map.GetAsCoreCodecCapabilities();

  // SupportedSamplingFrequencies = 96000 and 16000
  ASSERT_TRUE(caps.HasSupportedSamplingFrequencies());
  ASSERT_EQ(codec_spec_caps::kLeAudioSamplingFreq16000Hz |
                codec_spec_caps::kLeAudioSamplingFreq96000Hz,
            caps.supported_sampling_frequencies.value());
  // Check config values agains the capabilities
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq8000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq11025Hz));
  ASSERT_TRUE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq16000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq22050Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq24000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq32000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq44100Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq48000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq88200Hz));
  ASSERT_TRUE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq96000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq176400Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq192000Hz));
  ASSERT_FALSE(caps.IsSamplingFrequencyConfigSupported(
      codec_spec_conf::kLeAudioSamplingFreq384000Hz));

  // SupportedFrameDurations = 10ms, 7.5ms, 10ms preferred
  ASSERT_TRUE(caps.HasSupportedFrameDurations());
  ASSERT_EQ(codec_spec_caps::kLeAudioCodecFrameDur7500us |
                codec_spec_caps::kLeAudioCodecFrameDur10000us |
                codec_spec_caps::kLeAudioCodecFrameDurPrefer10000us,
            caps.supported_frame_durations.value());
  // Check config values agains the capabilities
  ASSERT_TRUE(caps.IsFrameDurationConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameDur7500us));
  ASSERT_TRUE(caps.IsFrameDurationConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameDur10000us));

  // SupportedAudioChannelCounts = 0b1 | 0b2 (one and two channels)
  ASSERT_TRUE(caps.HasSupportedAudioChannelCounts());
  ASSERT_EQ(codec_spec_caps::kLeAudioCodecChannelCountSingleChannel |
                codec_spec_caps::kLeAudioCodecChannelCountTwoChannel,
            caps.supported_audio_channel_counts.value());
  // Check config values agains the capabilities
  ASSERT_TRUE(caps.IsAudioChannelCountsSupported(1));
  ASSERT_TRUE(caps.IsAudioChannelCountsSupported(2));
  for (uint8_t i = 3; i < 8; ++i) {
    ASSERT_FALSE(caps.IsAudioChannelCountsSupported(i));
  }

  // SupportedOctetsPerCodecFrame = min:40, max:80
  ASSERT_TRUE(caps.HasSupportedOctetsPerCodecFrame());
  ASSERT_EQ(codec_spec_caps::kLeAudioCodecFrameLen40,
            caps.supported_min_octets_per_codec_frame.value());
  ASSERT_EQ(codec_spec_caps::kLeAudioCodecFrameLen80,
            caps.supported_max_octets_per_codec_frame.value());
  // Check config values agains the capabilities
  ASSERT_FALSE(caps.IsOctetsPerCodecFrameConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameLen30));
  ASSERT_TRUE(caps.IsOctetsPerCodecFrameConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameLen40));
  // Supported since: 40(min) < 60 < 80(max)
  ASSERT_TRUE(caps.IsOctetsPerCodecFrameConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameLen60));
  ASSERT_TRUE(caps.IsOctetsPerCodecFrameConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameLen80));
  ASSERT_FALSE(caps.IsOctetsPerCodecFrameConfigSupported(
      codec_spec_conf::kLeAudioCodecFrameLen120));

  // SupportedMaxCodecFramesPerSdu = 2
  ASSERT_TRUE(caps.HasSupportedMaxCodecFramesPerSdu());
  ASSERT_EQ(2, caps.supported_max_codec_frames_per_sdu.value());
  // Check config values agains the capabilities: {1,2} <= 2(max)
  ASSERT_TRUE(caps.IsCodecFramesPerSduSupported(1));
  ASSERT_TRUE(caps.IsCodecFramesPerSduSupported(2));
  ASSERT_FALSE(caps.IsCodecFramesPerSduSupported(3));
}

}  // namespace types
}  // namespace le_audio