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

Commit fde3b6c2 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

Broadcaster: Improve broadcast stream metadata

This adds the missing CCID list for the metadata update procedure,
adds proper support for multiple bits in the context map and updates
metadata on track metadata change.

Bug: 240145685
Tag: #feature
Test: atest --host bluetooth_test_broadcaster --no-bazel-mode
Change-Id: Ia14947d3e41c93cd21fc579c60e938b17c6861bc
Merged-In: Ia14947d3e41c93cd21fc579c60e938b17c6861bc
(cherry picked from commit 61cc71be)
parent f3bc0d80
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ cc_library_static {
        "le_audio/state_machine.cc",
        "le_audio/client_parser.cc",
        "le_audio/client_audio.cc",
        "le_audio/le_audio_utils.cc",
        "le_audio/le_audio_set_configuration_provider.cc",
        "le_audio/le_audio_set_configuration_provider_json.cc",
        "le_audio/le_audio_types.cc",
@@ -629,6 +630,7 @@ cc_test {
        "le_audio/client_parser.cc",
        "le_audio/content_control_id_keeper.cc",
        "le_audio/devices.cc",
        "le_audio/le_audio_utils.cc",
        "le_audio/le_audio_client_test.cc",
        "le_audio/le_audio_set_configuration_provider_json.cc",
        "le_audio/le_audio_types.cc",
@@ -772,6 +774,7 @@ cc_test {
        "le_audio/broadcaster/mock_state_machine.cc",
        "le_audio/content_control_id_keeper.cc",
        "le_audio/client_audio.cc",
        "le_audio/le_audio_utils.cc",
        "le_audio/le_audio_types.cc",
        "le_audio/mock_iso_manager.cc",
        "test/common/mock_controller.cc",
+113 −12
Original line number Diff line number Diff line
@@ -20,8 +20,8 @@
#include "bta/include/bta_le_audio_api.h"
#include "bta/include/bta_le_audio_broadcaster_api.h"
#include "bta/le_audio/broadcaster/state_machine.h"
#include "bta/le_audio/content_control_id_keeper.h"
#include "bta/le_audio/le_audio_types.h"
#include "bta/le_audio/le_audio_utils.h"
#include "device/include/controller.h"
#include "embdrv/lc3/include/lc3.h"
#include "gd/common/strings.h"
@@ -38,7 +38,6 @@ using bluetooth::hci::iso_manager::BigCallbacks;
using bluetooth::le_audio::BasicAudioAnnouncementData;
using bluetooth::le_audio::BroadcastId;
using le_audio::CodecManager;
using le_audio::ContentControlIdKeeper;
using le_audio::broadcaster::BigConfig;
using le_audio::broadcaster::BroadcastCodecWrapper;
using le_audio::broadcaster::BroadcastQosConfig;
@@ -49,6 +48,8 @@ using le_audio::types::CodecLocation;
using le_audio::types::kLeAudioCodingFormatLC3;
using le_audio::types::LeAudioContextType;
using le_audio::types::LeAudioLtvMap;
using le_audio::utils::GetAllCcids;
using le_audio::utils::GetAllowedAudioContextsFromSourceMetadata;

namespace {
class LeAudioBroadcasterImpl;
@@ -165,6 +166,80 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
    return announcement;
  }

  void UpdateStreamingContextTypeOnAllSubgroups(uint16_t context_type_map) {
    LOG_DEBUG("%s context_type_map=%d", __func__, context_type_map);

    auto ccids = GetAllCcids(context_type_map);
    if (ccids.empty()) {
      LOG_WARN("%s No content providers available for context_type_map=%d.",
               __func__, context_type_map);
    }

    std::vector<uint8_t> stream_context_vec(2);
    auto pp = stream_context_vec.data();
    UINT16_TO_STREAM(pp, context_type_map);

    for (auto const& kv_it : broadcasts_) {
      auto& broadcast = kv_it.second;
      if (broadcast->GetState() == BroadcastStateMachine::State::STREAMING) {
        auto announcement = broadcast->GetBroadcastAnnouncement();
        bool broadcast_update = false;

        // Replace context type and CCID list
        for (auto& subgroup : announcement.subgroup_configs) {
          auto subgroup_ltv = LeAudioLtvMap(subgroup.metadata);
          bool subgroup_update = false;

          auto existing_context = subgroup_ltv.Find(
              le_audio::types::kLeAudioMetadataTypeStreamingAudioContext);
          if (existing_context) {
            if (memcmp(stream_context_vec.data(), existing_context->data(),
                       existing_context->size()) != 0) {
              subgroup_ltv.Add(
                  le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
                  stream_context_vec);
              subgroup_update = true;
            }
          } else {
            subgroup_ltv.Add(
                le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
                stream_context_vec);
            subgroup_update = true;
          }

          auto existing_ccid_list =
              subgroup_ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
          if (existing_ccid_list) {
            if (ccids.empty()) {
              subgroup_ltv.Remove(
                  le_audio::types::kLeAudioMetadataTypeCcidList);
              subgroup_update = true;

            } else if (!std::is_permutation(ccids.begin(), ccids.end(),
                                            existing_ccid_list->begin())) {
              subgroup_ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
                               ccids);
              subgroup_update = true;
            }
          } else if (!ccids.empty()) {
            subgroup_ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
                             ccids);
            subgroup_update = true;
          }

          if (subgroup_update) {
            subgroup.metadata = subgroup_ltv.Values();
            broadcast_update = true;
          }
        }

        if (broadcast_update) {
          broadcast->UpdateBroadcastAnnouncement(std::move(announcement));
        }
      }
    }
  }

  void UpdateMetadata(uint32_t broadcast_id,
                      std::vector<uint8_t> metadata) override {
    if (broadcasts_.count(broadcast_id) == 0) {
@@ -185,6 +260,22 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
      return;
    }

    uint16_t context_type =
        static_cast<std::underlying_type<LeAudioContextType>::type>(
            LeAudioContextType::MEDIA);
    auto stream_context_vec =
        ltv.Find(le_audio::types::kLeAudioMetadataTypeStreamingAudioContext);
    if (stream_context_vec) {
      auto pp = stream_context_vec.value().data();
      STREAM_TO_UINT16(context_type, pp);
    }

    // Append the CCID list
    auto ccid_vec = GetAllCcids(context_type);
    if (!ccid_vec.empty()) {
      ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList, ccid_vec);
    }

    BasicAudioAnnouncementData announcement =
        prepareAnnouncement(codec_config, std::move(ltv));

@@ -219,12 +310,9 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
    }

    // Append the CCID list
    // TODO: We currently support only one context (and CCID) at a time for both
    //       Unicast and broadcast.
    auto ccid = ContentControlIdKeeper::GetInstance()->GetCcid(context_type);
    if (ccid != -1) {
      ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
              {static_cast<uint8_t>(ccid)});
    auto ccid_vec = GetAllCcids(context_type);
    if (!ccid_vec.empty()) {
      ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList, ccid_vec);
    }

    if (CodecManager::GetInstance()->GetCodecLocation() ==
@@ -744,11 +832,24 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
        const source_metadata_t& source_metadata) override {
      LOG_INFO();
      if (!instance) return;
      do_update_metadata_promise.set_value();
      /* TODO: We probably don't want to change stream type or update the
       * advertized metadata on each call. We should rather make sure we get
       * only a single content audio stream from the media frameworks.

      /* TODO: Should we take supported contexts from ASCS? */
      auto supported_context_types = le_audio::types::kLeAudioContextAllTypes;
      auto contexts = GetAllowedAudioContextsFromSourceMetadata(
          source_metadata,
          static_cast<std::underlying_type<LeAudioContextType>::type>(
              supported_context_types));
      if (contexts.any()) {
        /* NOTICE: We probably don't want to change the stream configuration
         * on each metadata change, so just update the context type metadata.
         * Since we are not able to identify individual track streams and
         * they are all mixed inside a single data stream, we will update
         * the metadata of all BIS subgroups with the same combined context.
         */
        instance->UpdateStreamingContextTypeOnAllSubgroups(contexts.to_ulong());
      }

      do_update_metadata_promise.set_value();
    }

   private:
+103 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hardware/audio.h>

#include <chrono>

@@ -30,6 +31,8 @@
#include "device/include/controller.h"
#include "stack/include/btm_iso_api.h"

using namespace std::chrono_literals;

using le_audio::types::LeAudioContextType;

using testing::_;
@@ -108,6 +111,7 @@ static void cleanup_message_loop_thread() {
namespace le_audio {
namespace broadcaster {
namespace {
static constexpr uint8_t default_ccid = 0xDE;
static constexpr auto default_context =
    static_cast<std::underlying_type<LeAudioContextType>::type>(
        LeAudioContextType::ALERTS);
@@ -203,6 +207,7 @@ class BroadcasterTest : public Test {
                                   base::Bind([]() -> bool { return true; }));

    ContentControlIdKeeper::GetInstance()->Start();
    ContentControlIdKeeper::GetInstance()->SetCcid(0x0004, media_ccid);

    /* Simulate random generator */
    uint8_t random[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
@@ -405,12 +410,29 @@ TEST_F(BroadcasterTest, GetBroadcastAllStates) {

TEST_F(BroadcasterTest, UpdateMetadata) {
  auto broadcast_id = InstantiateBroadcast();

  std::vector<uint8_t> ccid_list;
  EXPECT_CALL(*MockBroadcastStateMachine::GetLastInstance(),
              UpdateBroadcastAnnouncement)
      .Times(1);
      .WillOnce(
          [&](bluetooth::le_audio::BasicAudioAnnouncementData announcement) {
            for (auto subgroup : announcement.subgroup_configs) {
              if (subgroup.metadata.count(
                      types::kLeAudioMetadataTypeCcidList)) {
                ccid_list =
                    subgroup.metadata.at(types::kLeAudioMetadataTypeCcidList);
                break;
              }
            }
          });

  ContentControlIdKeeper::GetInstance()->SetCcid(0x0400, default_ccid);
  LeAudioBroadcaster::Get()->UpdateMetadata(
      broadcast_id, std::vector<uint8_t>({0x02, 0x01, 0x02}));
      broadcast_id,
      std::vector<uint8_t>({0x02, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}));

  ASSERT_EQ(2u, ccid_list.size());
  ASSERT_NE(0, std::count(ccid_list.begin(), ccid_list.end(), media_ccid));
  ASSERT_NE(0, std::count(ccid_list.begin(), ccid_list.end(), default_ccid));
}

static BasicAudioAnnouncementData prepareAnnouncement(
@@ -444,10 +466,87 @@ static BasicAudioAnnouncementData prepareAnnouncement(
  return announcement;
}

TEST_F(BroadcasterTest, UpdateMetadataFromAudioTrackMetadata) {
  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
  auto broadcast_id = InstantiateBroadcast();

  LeAudioClientAudioSinkReceiver* audio_receiver;
  EXPECT_CALL(*mock_audio_source_, Start)
      .WillOnce(DoAll(SaveArg<1>(&audio_receiver), Return(true)));

  LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
  ASSERT_NE(audio_receiver, nullptr);

  auto sm = MockBroadcastStateMachine::GetLastInstance();
  std::vector<uint8_t> ccid_list;
  std::vector<uint8_t> context_types_map;
  EXPECT_CALL(*sm, UpdateBroadcastAnnouncement)
      .WillOnce(
          [&](bluetooth::le_audio::BasicAudioAnnouncementData announcement) {
            for (auto subgroup : announcement.subgroup_configs) {
              if (subgroup.metadata.count(
                      types::kLeAudioMetadataTypeCcidList)) {
                ccid_list =
                    subgroup.metadata.at(types::kLeAudioMetadataTypeCcidList);
              }
              if (subgroup.metadata.count(
                      types::kLeAudioMetadataTypeStreamingAudioContext)) {
                context_types_map = subgroup.metadata.at(
                    types::kLeAudioMetadataTypeStreamingAudioContext);
              }
            }
          });

  std::map<uint8_t, std::vector<uint8_t>> meta = {};
  BroadcastCodecWrapper codec_config(
      {.coding_format = le_audio::types::kLeAudioCodingFormatLC3,
       .vendor_company_id = le_audio::types::kLeAudioVendorCompanyIdUndefined,
       .vendor_codec_id = le_audio::types::kLeAudioVendorCodecIdUndefined},
      {.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
       .sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
       .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
       .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
      32000, 40);
  auto announcement = prepareAnnouncement(codec_config, meta);

  ON_CALL(*sm, GetBroadcastAnnouncement())
      .WillByDefault(ReturnRef(announcement));

  std::promise<void> do_update_metadata_promise;
  struct playback_track_metadata tracks_[] = {
      {AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_SONIFICATION, 0},
      {AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, 0},
      {AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, AUDIO_CONTENT_TYPE_SPEECH,
       0},
      {AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}};

  source_metadata_t multitrack_source_metadata = {.track_count = 4,
                                                  .tracks = &tracks_[0]};

  auto do_update_metadata_future = do_update_metadata_promise.get_future();
  audio_receiver->OnAudioMetadataUpdate(std::move(do_update_metadata_promise),
                                        multitrack_source_metadata);
  do_update_metadata_future.wait_for(3s);

  // Verify ccid
  ASSERT_NE(ccid_list.size(), 0u);
  ASSERT_TRUE(std::find(ccid_list.begin(), ccid_list.end(), media_ccid) !=
              ccid_list.end());

  // Verify context type
  ASSERT_NE(context_types_map.size(), 0u);
  uint16_t context_type;
  auto pp = context_types_map.data();
  STREAM_TO_UINT16(context_type, pp);
  ASSERT_NE(context_type &
                static_cast<std::underlying_type<LeAudioContextType>::type>(
                    LeAudioContextType::MEDIA | LeAudioContextType::GAME),
            0);
}

TEST_F(BroadcasterTest, GetMetadata) {
  auto broadcast_id = InstantiateBroadcast();
  bluetooth::le_audio::BroadcastMetadata metadata;
  // bluetooth::le_audio::BasicAudioAnnouncementData announcement;

  static const uint8_t test_adv_sid = 0x14;
  std::optional<bluetooth::le_audio::BroadcastCode> test_broadcast_code =
+1 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ class MockBroadcastStateMachine
  std::optional<le_audio::broadcaster::BigConfig> big_config_ = std::nullopt;
  le_audio::broadcaster::BroadcastStateMachineConfig cfg;
  le_audio::broadcaster::IBroadcastStateMachineCallbacks* cb;
  void SetExpectedState(BroadcastStateMachine::State state) { SetState(state); }
  void SetExpectedResult(bool result) { result_ = result; }
  void SetExpectedBigConfig(
      std::optional<le_audio::broadcaster::BigConfig> big_cfg) {
+2 −33
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@
#include "gd/common/strings.h"
#include "le_audio_set_configuration_provider.h"
#include "le_audio_types.h"
#include "le_audio_utils.h"
#include "metrics_collector.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
@@ -76,6 +77,7 @@ using le_audio::types::AudioStreamDataPathState;
using le_audio::types::hdl_pair;
using le_audio::types::kDefaultScanDurationS;
using le_audio::types::LeAudioContextType;
using le_audio::utils::AudioContentToLeAudioContext;

using le_audio::client_parser::ascs::
    kCtpResponseCodeInvalidConfigurationParameterValue;
@@ -3105,39 +3107,6 @@ class LeAudioClientImpl : public LeAudioClient {
    }
  }

  LeAudioContextType AudioContentToLeAudioContext(
      audio_content_type_t content_type, audio_usage_t usage) {
    /* Check audio attribute usage of stream */
    switch (usage) {
      case AUDIO_USAGE_MEDIA:
        return LeAudioContextType::MEDIA;
      case AUDIO_USAGE_VOICE_COMMUNICATION:
      case AUDIO_USAGE_CALL_ASSISTANT:
        return LeAudioContextType::CONVERSATIONAL;
      case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
        if (content_type == AUDIO_CONTENT_TYPE_SPEECH)
          return LeAudioContextType::CONVERSATIONAL;
        else
          return LeAudioContextType::MEDIA;
      case AUDIO_USAGE_GAME:
        return LeAudioContextType::GAME;
      case AUDIO_USAGE_NOTIFICATION:
        return LeAudioContextType::NOTIFICATIONS;
      case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
        return LeAudioContextType::RINGTONE;
      case AUDIO_USAGE_ALARM:
        return LeAudioContextType::ALERTS;
      case AUDIO_USAGE_EMERGENCY:
        return LeAudioContextType::EMERGENCYALARM;
      case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
        return LeAudioContextType::INSTRUCTIONAL;
      default:
        break;
    }

    return LeAudioContextType::MEDIA;
  }

  LeAudioContextType ChooseContextType(
      std::vector<LeAudioContextType>& available_contents) {
    /* Mini policy. Voice is prio 1, game prio 2, media is prio 3 */
Loading