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

Commit 7c062494 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Łukasz Rymanowski
Browse files

LeAudio: Add metadata LTV support

This is the proper support for the LTV data types for the LE Audio
metadata. Until now the metadata was mostly ignored, and not
interpretted. With this change the LTV Map is extended with metadata
support just like it supported the Codec Capabilities and Codec
Configuration types.

This is needed to transparently pass metada over the AIDL interface.

Bug: 340400084
Test: atest --host bluetooth_le_audio_test
Flag: EXEMPT; Data type for the multi-codec feature which is not yet enabled
Change-Id: I8fcd4bdc47685ff9fc56637deebe507f89247146
parent 2f3fa9ec
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
@@ -541,6 +541,7 @@ const struct LeAudioCoreCodecConfig& LeAudioLtvMap::GetAsCoreCodecConfig()
    const {
  log::assert_that(!core_capabilities,
                   "LTVs were already parsed for capabilities!");
  log::assert_that(!metadata, "LTVs were already parsed for metadata!");

  if (!core_config) {
    core_config = LtvMapToCoreCodecConfig(*this);
@@ -552,6 +553,7 @@ const struct LeAudioCoreCodecCapabilities&
LeAudioLtvMap::GetAsCoreCodecCapabilities() const {
  log::assert_that(!core_config,
                   "LTVs were already parsed for configurations!");
  log::assert_that(!metadata, "LTVs were already parsed for metadata!");

  if (!core_capabilities) {
    core_capabilities = LtvMapToCoreCodecCapabilities(*this);
@@ -559,6 +561,18 @@ LeAudioLtvMap::GetAsCoreCodecCapabilities() const {
  return *core_capabilities;
}

const struct LeAudioMetadata& LeAudioLtvMap::GetAsLeAudioMetadata() const {
  log::assert_that(!core_config,
                   "LTVs were already parsed for configurations!");
  log::assert_that(!core_capabilities,
                   "LTVs were already parsed for capabilities!");

  if (!metadata) {
    metadata = LtvMapToMetadata(*this);
  }
  return *metadata;
}

void LeAudioLtvMap::RemoveAllTypes(const LeAudioLtvMap& other) {
  for (auto const& [key, _] : other.values) {
    Remove(key);
@@ -811,6 +825,59 @@ std::ostream& operator<<(std::ostream& os,
  return os;
}

std::ostream& operator<<(std::ostream& os, const LeAudioMetadata& config) {
  os << "LeAudioMetadata{";
  if (config.preferred_audio_context) {
    os << "preferred_audio_context: ";
    os << AudioContexts(config.preferred_audio_context.value());
  }
  if (config.streaming_audio_context) {
    os << ", streaming_audio_context: ";
    os << AudioContexts(config.streaming_audio_context.value());
  }
  if (config.program_info) {
    os << ", program_info: ";
    os << config.program_info.value();
  }
  if (config.language) {
    os << ", language: ";
    os << config.language.value();
  }
  if (config.ccid_list) {
    os << ", ccid_list: ";
    os << base::HexEncode(config.ccid_list.value().data(),
                          config.ccid_list.value().size());
  }
  if (config.parental_rating) {
    os << ", parental_rating: ";
    os << (int)config.parental_rating.value();
  }
  if (config.program_info_uri) {
    os << ", program_info_uri: ";
    os << config.program_info_uri.value();
  }
  if (config.extended_metadata) {
    os << ", extended_metadata: ";
    os << base::HexEncode(config.extended_metadata.value().data(),
                          config.extended_metadata.value().size());
  }
  if (config.vendor_specific) {
    os << ", vendor_specific: ";
    os << base::HexEncode(config.vendor_specific.value().data(),
                          config.vendor_specific.value().size());
  }
  if (config.audio_active_state) {
    os << ", audio_active_state: ";
    os << config.audio_active_state.value();
  }
  if (config.broadcast_audio_immediate_rendering) {
    os << ", broadcast_audio_immediate_rendering: ";
    os << config.broadcast_audio_immediate_rendering.value();
  }
  os << "}";
  return os;
}

template struct BidirectionalPair<AudioContexts>;
template struct BidirectionalPair<AudioLocations>;
template struct BidirectionalPair<CisType>;
+107 −1
Original line number Diff line number Diff line
@@ -296,6 +296,12 @@ constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02;
constexpr uint8_t kLeAudioMetadataTypeProgramInfo = 0x03;
constexpr uint8_t kLeAudioMetadataTypeLanguage = 0x04;
constexpr uint8_t kLeAudioMetadataTypeCcidList = 0x05;
constexpr uint8_t kLeAudioMetadataTypeparentalRating = 0x06;
constexpr uint8_t kLeAudioMetadataTypeProgramInfoUri = 0x07;
constexpr uint8_t kLeAudioMetadataTypeAudioActiveState = 0x08;
constexpr uint8_t kLeAudioMetadataTypeBroadcastAudioImmediateRenderingFlag =
    0x09;
constexpr uint8_t kLeAudioMetadataTypeExtendedMetadata = 0xFE;
constexpr uint8_t kLeAudioMetadataTypeVendorSpecific = 0xFF;

constexpr uint8_t kLeAudioMetadataTypeLen = 1;
@@ -648,6 +654,22 @@ struct LeAudioCoreCodecCapabilities {
  std::optional<uint8_t> supported_max_codec_frames_per_sdu;
};

struct LeAudioMetadata {
  std::optional<uint16_t> preferred_audio_context;
  std::optional<uint16_t> streaming_audio_context;
  std::optional<std::string> program_info;
  std::optional<std::string> language;  // ISO 639-3 (3 lowercase letter codes)
  std::optional<std::vector<uint8_t>> ccid_list;
  std::optional<uint8_t> parental_rating;
  std::optional<std::string> program_info_uri;
  std::optional<std::vector<uint8_t>> extended_metadata;
  std::optional<std::vector<uint8_t>> vendor_specific;
  std::optional<bool> audio_active_state;
  std::optional<bool> broadcast_audio_immediate_rendering;
};

std::ostream& operator<<(std::ostream& os, const LeAudioMetadata& config);

#define LTV_ENTRY_SAMPLING_FREQUENCY(value)                 \
  {                                                         \
    le_audio::codec_spec_conf::kLeAudioLtvTypeSamplingFreq, \
@@ -688,7 +710,8 @@ class LeAudioLtvMap {
      : values(values),
        value_hash(0),
        core_config(std::nullopt),
        core_capabilities(std::nullopt) {}
        core_capabilities(std::nullopt),
        metadata(std::nullopt) {}
  LeAudioLtvMap() = default;
  ~LeAudioLtvMap() = default;

@@ -709,6 +732,16 @@ class LeAudioLtvMap {
    return *this;
  }

  // Add vendor specific data preceded with 2 octets of company ID
  LeAudioLtvMap& Add(uint8_t type, uint16_t vendorCompanyId,
                     std::vector<uint8_t> value) {
    std::vector<uint8_t> data(value.size() + 2);
    auto ptr = data.data();
    UINT16_TO_STREAM(ptr, vendorCompanyId);
    ARRAY_TO_STREAM(ptr, value.data(), (int)value.size());
    return Add(type, data);
  }

  LeAudioLtvMap& Add(uint8_t type, const std::string& value) {
    std::vector<uint8_t> v(value.size());
    auto ptr = v.data();
@@ -771,6 +804,7 @@ class LeAudioLtvMap {

  const struct LeAudioCoreCodecConfig& GetAsCoreCodecConfig() const;
  const struct LeAudioCoreCodecCapabilities& GetAsCoreCodecCapabilities() const;
  const struct LeAudioMetadata& GetAsLeAudioMetadata() const;
  LeAudioLtvMap GetIntersection(const LeAudioLtvMap& other) const;

  std::string ToString(
@@ -791,9 +825,80 @@ class LeAudioLtvMap {
  void invalidate() {
    core_config = std::nullopt;
    core_capabilities = std::nullopt;
    metadata = std::nullopt;
    value_hash = 0;
  }

  static LeAudioMetadata LtvMapToMetadata(const LeAudioLtvMap& ltvs) {
    LeAudioMetadata metadata;

    auto vec_opt = ltvs.Find(types::kLeAudioMetadataTypePreferredAudioContext);
    if (vec_opt &&
        (vec_opt->size() ==
         sizeof(decltype(metadata.preferred_audio_context)::value_type))) {
      auto ptr = vec_opt->data();
      STREAM_TO_UINT16(metadata.preferred_audio_context, ptr);
    }

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeStreamingAudioContext);
    if (vec_opt &&
        (vec_opt->size() ==
         sizeof(decltype(metadata.streaming_audio_context)::value_type))) {
      auto ptr = vec_opt->data();
      STREAM_TO_UINT16(metadata.streaming_audio_context, ptr);
    }

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeProgramInfo);
    if (vec_opt) {
      metadata.program_info = std::string(
          reinterpret_cast<const char*>(vec_opt->data()), vec_opt->size());
    }

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeLanguage);
    if (vec_opt && (vec_opt->size() == 3)) {  // it is always 3 in ISO 639-3
      metadata.language = std::string(
          reinterpret_cast<const char*>(vec_opt->data()), vec_opt->size());
    }

    metadata.ccid_list = ltvs.Find(types::kLeAudioMetadataTypeCcidList);

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeparentalRating);
    if (vec_opt && (vec_opt->size() ==
                    sizeof(decltype(metadata.parental_rating)::value_type))) {
      auto ptr = vec_opt->data();
      STREAM_TO_UINT8(metadata.parental_rating, ptr);
    }

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeProgramInfoUri);
    if (vec_opt) {
      metadata.program_info_uri = std::string(
          reinterpret_cast<const char*>(vec_opt->data()), vec_opt->size());
    }

    vec_opt = ltvs.Find(types::kLeAudioMetadataTypeAudioActiveState);
    if (vec_opt &&
        (vec_opt->size() ==
         sizeof(decltype(metadata.audio_active_state)::value_type))) {
      auto ptr = vec_opt->data();
      uint8_t val;
      STREAM_TO_UINT8(val, ptr);
      metadata.audio_active_state = val ? true : false;
    }

    vec_opt = ltvs.Find(
        types::kLeAudioMetadataTypeBroadcastAudioImmediateRenderingFlag);
    if (vec_opt) {
      metadata.broadcast_audio_immediate_rendering = true;
    }

    metadata.extended_metadata =
        ltvs.Find(types::kLeAudioMetadataTypeExtendedMetadata);
    metadata.vendor_specific =
        ltvs.Find(types::kLeAudioMetadataTypeVendorSpecific);

    return metadata;
  }

  static LeAudioCoreCodecConfig LtvMapToCoreCodecConfig(
      const LeAudioLtvMap& ltvs) {
    LeAudioCoreCodecConfig core;
@@ -927,6 +1032,7 @@ class LeAudioLtvMap {
      std::nullopt;
  mutable std::optional<struct LeAudioCoreCodecCapabilities> core_capabilities =
      std::nullopt;
  mutable std::optional<struct LeAudioMetadata> metadata = std::nullopt;
};

struct LeAudioCodecId {
+112 −0
Original line number Diff line number Diff line
@@ -378,6 +378,118 @@ TEST(LeAudioLtvMapTest, test_capabilities_valid) {
  ASSERT_FALSE(caps.IsCodecFramesPerSduSupported(3));
}

TEST(LeAudioLtvMapTest, test_metadata_use_guard1) {
  auto default_context =
      (uint16_t)bluetooth::le_audio::types::LeAudioContextType::VOICEASSISTANTS;
  static const std::vector<uint8_t> default_metadata = {
      bluetooth::le_audio::types::kLeAudioMetadataStreamingAudioContextLen + 1,
      bluetooth::le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
      (uint8_t)(default_context & 0x00FF),
      (uint8_t)((default_context & 0xFF00) >> 8)};

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

  // Verify the codec capabilities values
  auto metadata = ltv_map.GetAsLeAudioMetadata();

  // Should fail when trying to reinterpret the LTV as configuration
  EXPECT_DEATH(ltv_map.GetAsCoreCodecConfig(), "");
}

TEST(LeAudioLtvMapTest, test_metadata_use_guard2) {
  auto default_context =
      (uint16_t)bluetooth::le_audio::types::LeAudioContextType::VOICEASSISTANTS;
  static const std::vector<uint8_t> default_metadata = {
      bluetooth::le_audio::types::kLeAudioMetadataStreamingAudioContextLen + 1,
      bluetooth::le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
      (uint8_t)(default_context & 0x00FF),
      (uint8_t)((default_context & 0xFF00) >> 8)};

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

  // Verify the codec capabilities values
  auto metadata = ltv_map.GetAsLeAudioMetadata();

  // Should fail when trying to reinterpret the LTV as configuration
  EXPECT_DEATH(ltv_map.GetAsCoreCodecCapabilities(), "");
}

static auto PrepareMetadataLtv() {
  ::bluetooth::le_audio::types::LeAudioLtvMap metadata_ltvs;
  // Prepare the metadata LTVs
  metadata_ltvs
      .Add(::bluetooth::le_audio::types::
               kLeAudioMetadataTypePreferredAudioContext,
           (uint16_t)10)
      .Add(::bluetooth::le_audio::types::
               kLeAudioMetadataTypeStreamingAudioContext,
           (uint16_t)8)
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeProgramInfo,
           std::string{"ProgramInfo"})
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeLanguage,
           std::string{"ice"})
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList,
           std::vector<uint8_t>{1, 2, 3})
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeparentalRating,
           (uint8_t)0x01)
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeProgramInfoUri,
           std::string{"ProgramInfoUri"})
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeAudioActiveState,
           false)
      .Add(::bluetooth::le_audio::types::
               kLeAudioMetadataTypeBroadcastAudioImmediateRenderingFlag,
           true)
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeExtendedMetadata,
           std::vector<uint8_t>{1, 2, 3})
      .Add(::bluetooth::le_audio::types::kLeAudioMetadataTypeVendorSpecific,
           std::vector<uint8_t>{1, 2, 3});
  return metadata_ltvs;
}

TEST(LeAudioLtvMapTest, test_metadata_valid) {
  // Prepare the reference LTV
  auto metadata_ltv = PrepareMetadataLtv();
  auto raw_metadata = metadata_ltv.RawPacket();

  // Check the Parsing
  bool success = true;
  LeAudioLtvMap parsed_ltv_map =
      LeAudioLtvMap::Parse(raw_metadata.data(), raw_metadata.size(), success);
  ASSERT_TRUE(success);

  // Verify the values
  auto metadata = metadata_ltv.GetAsLeAudioMetadata();
  auto parsed_metadata = parsed_ltv_map.GetAsLeAudioMetadata();
  ASSERT_EQ(parsed_metadata.preferred_audio_context.value(),
            metadata.preferred_audio_context.value());
  ASSERT_EQ(parsed_metadata.program_info.value(),
            metadata.program_info.value());
  ASSERT_TRUE(parsed_metadata.language.has_value());
  ASSERT_TRUE(metadata.language.has_value());
  ASSERT_EQ(parsed_metadata.language.value(), metadata.language.value());
  ASSERT_EQ(parsed_metadata.ccid_list.value(), metadata.ccid_list.value());
  ASSERT_EQ(parsed_metadata.parental_rating.value(),
            metadata.parental_rating.value());
  ASSERT_EQ(parsed_metadata.program_info_uri.value(),
            metadata.program_info_uri.value());
  ASSERT_EQ(parsed_metadata.audio_active_state.value(),
            metadata.audio_active_state.value());
  ASSERT_EQ(parsed_metadata.broadcast_audio_immediate_rendering.value(),
            metadata.broadcast_audio_immediate_rendering.value());
  ASSERT_EQ(parsed_metadata.extended_metadata.value(),
            metadata.extended_metadata.value());
  ASSERT_EQ(parsed_metadata.vendor_specific.value(),
            metadata.vendor_specific.value());
}

TEST(LeAudioLtvMapTest, test_adding_types) {
  LeAudioLtvMap ltv_map;
  ltv_map.Add(1, (uint8_t)127);