blob: 1c592eeeba07211207d5071f791c4371d110c5e8 [file]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <bluetooth/log.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hardware/audio.h>
#include <chrono>
#include "bta/include/bta_le_audio_api.h"
#include "bta/include/bta_le_audio_broadcaster_api.h"
#include "bta/le_audio/audio_hal_client/audio_hal_client.h"
#include "bta/le_audio/broadcaster/broadcast_configuration_provider.h"
#include "bta/le_audio/broadcaster/mock_state_machine.h"
#include "bta/le_audio/content_control_id_keeper.h"
#include "bta/le_audio/le_audio_types.h"
#include "bta/le_audio/mock_codec_manager.h"
#include "hci/controller_interface_mock.h"
#include "stack/include/btm_iso_api.h"
#include "test/common/mock_functions.h"
#include "test/mock/mock_main_shim_entry.h"
#include "test/mock/mock_stack_btm_iso.h"
using namespace std::chrono_literals;
using bluetooth::le_audio::types::AudioContexts;
using bluetooth::le_audio::types::LeAudioContextType;
using testing::_;
using testing::AtLeast;
using testing::DoAll;
using testing::Invoke;
using testing::Matcher;
using testing::Mock;
using testing::NotNull;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
using testing::Test;
using namespace bluetooth::le_audio;
using namespace bluetooth;
using bluetooth::le_audio::DsaMode;
using bluetooth::le_audio::LeAudioCodecConfiguration;
using bluetooth::le_audio::LeAudioSourceAudioHalClient;
using bluetooth::le_audio::broadcaster::BigConfig;
using bluetooth::le_audio::broadcaster::BroadcastSubgroupCodecConfig;
// Disables most likely false-positives from base::SplitString()
extern "C" const char* __asan_default_options() {
return "detect_container_overflow=0";
}
static base::Callback<void(BT_OCTET8)> generator_cb;
void btsnd_hcic_ble_rand(base::Callback<void(BT_OCTET8)> cb) {
generator_cb = cb;
}
namespace server_configurable_flags {
std::string GetServerConfigurableFlag(
const std::string& experiment_category_name,
const std::string& experiment_flag_name, const std::string& default_value) {
return "";
}
} // namespace server_configurable_flags
std::atomic<int> num_async_tasks;
bluetooth::common::MessageLoopThread message_loop_thread("test message loop");
bluetooth::common::MessageLoopThread* get_main_thread() {
return &message_loop_thread;
}
void invoke_switch_buffer_size_cb(bool is_low_latency_buffer_size) {}
bt_status_t do_in_main_thread(const base::Location& from_here,
base::OnceClosure task) {
// Wrap the task with task counter so we could later know if there are
// any callbacks scheduled and we should wait before performing some actions
if (!message_loop_thread.DoInThread(
from_here,
base::BindOnce(
[](base::OnceClosure task, std::atomic<int>& num_async_tasks) {
std::move(task).Run();
num_async_tasks--;
},
std::move(task), std::ref(num_async_tasks)))) {
log::error("failed from {}", from_here.ToString());
return BT_STATUS_FAIL;
}
num_async_tasks++;
return BT_STATUS_SUCCESS;
}
static base::MessageLoop* message_loop_;
base::MessageLoop* get_main_message_loop() { return message_loop_; }
static void init_message_loop_thread() {
num_async_tasks = 0;
message_loop_thread.StartUp();
if (!message_loop_thread.IsRunning()) {
FAIL() << "unable to create message loop thread.";
}
if (!message_loop_thread.EnableRealTimeScheduling())
log::error("Unable to set real time scheduling");
message_loop_ = message_loop_thread.message_loop();
if (message_loop_ == nullptr) FAIL() << "unable to get message loop.";
}
static void cleanup_message_loop_thread() {
message_loop_ = nullptr;
message_loop_thread.ShutDown();
}
bool LeAudioClient::IsLeAudioClientRunning(void) { return false; }
namespace bluetooth::le_audio {
namespace broadcaster {
BroadcastConfiguration GetBroadcastConfig(
const std::vector<std::pair<types::LeAudioContextType, uint8_t>>&
subgroup_quality) {
BroadcastConfiguration config = {
.subgroups = {},
.qos = qos_config_4_60, // default QoS value for reliability
.data_path = lc3_data_path,
.sduIntervalUs = 10000,
.phy = 0x02, // PHY_LE_2M
.packing = 0, // Sequential
.framing = 0, // Unframed
};
for (auto [context, quality] : subgroup_quality) {
// Select QoS - Check for low latency contexts
if (AudioContexts(context).test_any(
types::LeAudioContextType::GAME | types::LeAudioContextType::LIVE |
types::LeAudioContextType::INSTRUCTIONAL |
types::LeAudioContextType::SOUNDEFFECTS)) {
config.qos = qos_config_2_10;
}
// Select codec quality
if (quality == bluetooth::le_audio::QUALITY_STANDARD) {
// STANDARD
config.subgroups.push_back(lc3_mono_16_2);
} else {
// HIGH
config.subgroups.push_back(lc3_stereo_48_4);
}
}
return config;
}
} // namespace broadcaster
class MockAudioHalClientEndpoint;
MockAudioHalClientEndpoint* mock_audio_source_;
bool is_audio_hal_acquired;
void (*iso_active_callback)(bool);
std::unique_ptr<LeAudioSourceAudioHalClient>
LeAudioSourceAudioHalClient::AcquireBroadcast() {
if (mock_audio_source_) {
std::unique_ptr<LeAudioSourceAudioHalClient> ptr(
(LeAudioSourceAudioHalClient*)mock_audio_source_);
is_audio_hal_acquired = true;
return std::move(ptr);
}
return nullptr;
}
static constexpr uint8_t default_ccid = 0xDE;
static constexpr auto default_context =
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::ALERTS);
std::vector<uint8_t> default_subgroup_qualities = {
bluetooth::le_audio::QUALITY_STANDARD};
static constexpr BroadcastCode default_code = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
static const std::vector<uint8_t> default_metadata = {
bluetooth::le_audio::types::kLeAudioMetadataStreamingAudioContextLen + 1,
bluetooth::le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
default_context & 0x00FF, (default_context & 0xFF00) >> 8};
static const std::vector<uint8_t> default_public_metadata = {
5, bluetooth::le_audio::types::kLeAudioMetadataTypeProgramInfo,
0x1, 0x2,
0x3, 0x4};
// bit 0: encrypted, bit 1: standard quality present
static const uint8_t test_public_broadcast_features = 0x3;
static constexpr uint8_t media_ccid = 0xC0;
static constexpr auto media_context =
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::MEDIA);
static const std::vector<uint8_t> media_metadata = {
bluetooth::le_audio::types::kLeAudioMetadataStreamingAudioContextLen + 1,
bluetooth::le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
media_context & 0x00FF, (media_context & 0xFF00) >> 8};
static const std::string test_broadcast_name = "Test";
class MockLeAudioBroadcasterCallbacks
: public bluetooth::le_audio::LeAudioBroadcasterCallbacks {
public:
MOCK_METHOD((void), OnBroadcastCreated, (uint32_t broadcast_id, bool success),
(override));
MOCK_METHOD((void), OnBroadcastDestroyed, (uint32_t broadcast_id),
(override));
MOCK_METHOD((void), OnBroadcastStateChanged,
(uint32_t broadcast_id,
bluetooth::le_audio::BroadcastState state),
(override));
MOCK_METHOD((void), OnBroadcastMetadataChanged,
(uint32_t broadcast_id,
const BroadcastMetadata& broadcast_metadata),
(override));
};
class MockAudioHalClientEndpoint : public LeAudioSourceAudioHalClient {
public:
MockAudioHalClientEndpoint() = default;
MOCK_METHOD((bool), Start,
(const LeAudioCodecConfiguration& codecConfiguration,
LeAudioSourceAudioHalClient::Callbacks* audioReceiver,
::bluetooth::le_audio::DsaModes dsa_modes),
(override));
MOCK_METHOD((void), Stop, (), (override));
MOCK_METHOD((void), ConfirmStreamingRequest, (), (override));
MOCK_METHOD((void), CancelStreamingRequest, (), (override));
MOCK_METHOD((void), UpdateRemoteDelay, (uint16_t delay), (override));
MOCK_METHOD((void), UpdateAudioConfigToHal,
(const ::bluetooth::le_audio::offload_config&), (override));
MOCK_METHOD(
(std::optional<broadcaster::BroadcastConfiguration>), GetBroadcastConfig,
((const std::vector<std::pair<types::LeAudioContextType, uint8_t>>&),
(const std::optional<
std::vector<::bluetooth::le_audio::types::acs_ac_record>>&)),
(const override));
MOCK_METHOD(
(std::optional<::le_audio::set_configurations::AudioSetConfiguration>),
GetUnicastConfig,
(const CodecManager::UnicastConfigurationRequirements& requirements),
(const override));
MOCK_METHOD((void), UpdateBroadcastAudioConfigToHal,
(const ::bluetooth::le_audio::broadcast_offload_config&),
(override));
MOCK_METHOD((void), SuspendedForReconfiguration, (), (override));
MOCK_METHOD((void), ReconfigurationComplete, (), (override));
MOCK_METHOD((void), OnDestroyed, ());
virtual ~MockAudioHalClientEndpoint() { OnDestroyed(); }
};
class BroadcasterTest : public Test {
protected:
void SetUp() override {
init_message_loop_thread();
reset_mock_function_count_map();
bluetooth::hci::testing::mock_controller_ = &mock_controller_;
ON_CALL(mock_controller_, SupportsBleIsochronousBroadcaster)
.WillByDefault(Return(true));
iso_manager_ = bluetooth::hci::IsoManager::GetInstance();
ASSERT_NE(iso_manager_, nullptr);
iso_manager_->Start();
mock_iso_manager_ = MockIsoManager::GetInstance();
ON_CALL(*mock_iso_manager_, RegisterBigCallbacks(_))
.WillByDefault(SaveArg<0>(&big_callbacks_));
ConfigAudioHalClientMock();
EXPECT_CALL(*MockIsoManager::GetInstance(),
RegisterOnIsoTrafficActiveCallbacks)
.WillOnce(SaveArg<0>(&iso_active_callback));
ASSERT_FALSE(LeAudioBroadcaster::IsLeAudioBroadcasterRunning());
LeAudioBroadcaster::Initialize(&mock_broadcaster_callbacks_,
base::Bind([]() -> bool { return true; }));
ContentControlIdKeeper::GetInstance()->Start();
ContentControlIdKeeper::GetInstance()->SetCcid(LeAudioContextType::MEDIA,
media_ccid);
/* Simulate random generator */
uint8_t random[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
generator_cb.Run(random);
ConfigCodecManagerMock(types::CodecLocation::HOST);
ON_CALL(*mock_codec_manager_, UpdateActiveUnicastAudioHalClient(_, _, _))
.WillByDefault(Return(true));
ON_CALL(*mock_codec_manager_, UpdateActiveBroadcastAudioHalClient(_, _))
.WillByDefault(Return(true));
ON_CALL(*mock_codec_manager_, GetBroadcastConfig)
.WillByDefault(
Invoke([](const bluetooth::le_audio::CodecManager::
BroadcastConfigurationRequirements& requirements) {
return std::make_unique<broadcaster::BroadcastConfiguration>(
bluetooth::le_audio::broadcaster::GetBroadcastConfig(
requirements.subgroup_quality));
}));
}
void ConfigAudioHalClientMock() {
is_audio_hal_acquired = false;
mock_audio_source_ = new MockAudioHalClientEndpoint();
ON_CALL(*mock_audio_source_, Start).WillByDefault(Return(true));
ON_CALL(*mock_audio_source_, OnDestroyed).WillByDefault([]() {
mock_audio_source_ = nullptr;
is_audio_hal_acquired = false;
});
}
void ConfigCodecManagerMock(types::CodecLocation location) {
codec_manager_ = le_audio::CodecManager::GetInstance();
ASSERT_NE(codec_manager_, nullptr);
std::vector<bluetooth::le_audio::btle_audio_codec_config_t>
mock_offloading_preference(0);
codec_manager_->Start(mock_offloading_preference);
mock_codec_manager_ = MockCodecManager::GetInstance();
ASSERT_NE(mock_codec_manager_, nullptr);
ON_CALL(*mock_codec_manager_, GetCodecLocation())
.WillByDefault(Return(location));
}
void TearDown() override {
// Message loop cleanup should wait for all the 'till now' scheduled calls
// so it should be called right at the very begginning of teardown.
cleanup_message_loop_thread();
// This is required since Stop() and Cleanup() may trigger some callbacks.
Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_);
Mock::VerifyAndClearExpectations(MockIsoManager::GetInstance());
Mock::VerifyAndClearExpectations(
MockBroadcastStateMachine::GetLastInstance());
LeAudioBroadcaster::Stop();
LeAudioBroadcaster::Cleanup();
ASSERT_FALSE(LeAudioBroadcaster::IsLeAudioBroadcasterRunning());
ContentControlIdKeeper::GetInstance()->Stop();
bluetooth::hci::testing::mock_controller_ = nullptr;
delete mock_audio_source_;
iso_active_callback = nullptr;
delete mock_audio_source_;
iso_manager_->Stop();
if (codec_manager_) {
codec_manager_->Stop();
mock_codec_manager_ = nullptr;
}
}
uint32_t InstantiateBroadcast(
std::vector<uint8_t> metadata = default_metadata,
BroadcastCode code = default_code,
std::vector<uint8_t> quality_array = default_subgroup_qualities,
bool is_queued = false) {
uint32_t broadcast_id = LeAudioBroadcaster::kInstanceIdUndefined;
if (!is_queued) {
EXPECT_CALL(mock_broadcaster_callbacks_, OnBroadcastCreated(_, true))
.WillOnce(SaveArg<0>(&broadcast_id));
}
std::vector<std::vector<uint8_t>> metadata_array;
for (uint8_t i = 0; i < quality_array.size(); i++) {
// use the same default_metadata for each subgroup
metadata_array.push_back(metadata);
}
// Add multiple subgroup settings with the same content
LeAudioBroadcaster::Get()->CreateAudioBroadcast(
true, test_broadcast_name, code, default_public_metadata, quality_array,
metadata_array);
return broadcast_id;
}
void InjectBigCreateComplete(uint8_t big_id, uint8_t status) {
std::vector<uint16_t> conn_handles = {0x10, 0x12};
hci::iso_manager::big_create_cmpl_evt evt = {
.status = status,
.big_id = big_id,
.big_sync_delay = 1231,
.transport_latency_big = 1234,
.phy = 2,
.nse = 3,
.bn = 2,
.pto = 2,
.irc = 2,
.max_pdu = 128,
.iso_interval = 10,
.conn_handles = conn_handles,
};
big_callbacks_->OnBigEvent(
bluetooth::hci::iso_manager::kIsoEventBigOnCreateCmpl, &evt);
}
void InjectBigTerminateComplete(uint8_t big_id, uint8_t reason) {
hci::iso_manager::big_terminate_cmpl_evt evt = {.big_id = big_id,
.reason = reason};
big_callbacks_->OnBigEvent(
bluetooth::hci::iso_manager::kIsoEventBigOnTerminateCmpl, &evt);
}
protected:
MockLeAudioBroadcasterCallbacks mock_broadcaster_callbacks_;
bluetooth::hci::testing::MockControllerInterface mock_controller_;
bluetooth::hci::IsoManager* iso_manager_;
MockIsoManager* mock_iso_manager_;
bluetooth::hci::iso_manager::BigCallbacks* big_callbacks_ = nullptr;
le_audio::CodecManager* codec_manager_ = nullptr;
MockCodecManager* mock_codec_manager_ = nullptr;
};
TEST_F(BroadcasterTest, Initialize) {
ASSERT_NE(LeAudioBroadcaster::Get(), nullptr);
ASSERT_TRUE(LeAudioBroadcaster::IsLeAudioBroadcasterRunning());
}
TEST_F(BroadcasterTest, GetStreamingPhy) {
LeAudioBroadcaster::Get()->SetStreamingPhy(1);
ASSERT_EQ(LeAudioBroadcaster::Get()->GetStreamingPhy(), 1);
LeAudioBroadcaster::Get()->SetStreamingPhy(2);
ASSERT_EQ(LeAudioBroadcaster::Get()->GetStreamingPhy(), 2);
}
TEST_F(BroadcasterTest, CreateAudioBroadcast) {
auto broadcast_id = InstantiateBroadcast();
ASSERT_NE(broadcast_id, LeAudioBroadcaster::kInstanceIdUndefined);
ASSERT_EQ(broadcast_id,
MockBroadcastStateMachine::GetLastInstance()->GetBroadcastId());
auto& instance_config = MockBroadcastStateMachine::GetLastInstance()->cfg;
ASSERT_EQ(instance_config.broadcast_code, default_code);
for (auto& subgroup : instance_config.announcement.subgroup_configs) {
ASSERT_EQ(types::LeAudioLtvMap(subgroup.metadata).RawPacket(),
default_metadata);
}
// Note: There shall be a separate test to verify audio parameters
}
TEST_F(BroadcasterTest, CreateAudioBroadcastMultiGroups) {
// Test with two subgroups
auto broadcast_id =
InstantiateBroadcast(default_metadata, default_code,
{bluetooth::le_audio::QUALITY_STANDARD,
bluetooth::le_audio::QUALITY_STANDARD});
ASSERT_NE(broadcast_id, LeAudioBroadcaster::kInstanceIdUndefined);
ASSERT_EQ(broadcast_id,
MockBroadcastStateMachine::GetLastInstance()->GetBroadcastId());
auto& instance_config = MockBroadcastStateMachine::GetLastInstance()->cfg;
ASSERT_EQ(instance_config.broadcast_code, default_code);
ASSERT_EQ(instance_config.announcement.subgroup_configs.size(), (uint8_t) 2);
for (auto& subgroup : instance_config.announcement.subgroup_configs) {
ASSERT_EQ(types::LeAudioLtvMap(subgroup.metadata).RawPacket(),
default_metadata);
}
}
TEST_F(BroadcasterTest, SuspendAudioBroadcast) {
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(1);
auto broadcast_id = InstantiateBroadcast();
LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, BroadcastState::CONFIGURED))
.Times(1);
EXPECT_CALL(*mock_audio_source_, Stop).Times(AtLeast(1));
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, _))
.Times(0);
LeAudioBroadcaster::Get()->SuspendAudioBroadcast(broadcast_id);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
}
TEST_F(BroadcasterTest, StartAudioBroadcast) {
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(1);
auto broadcast_id = InstantiateBroadcast();
Mock::VerifyAndClearExpectations(mock_codec_manager_);
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(0);
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, false))
.Times(0);
LeAudioBroadcaster::Get()->StopAudioBroadcast(broadcast_id);
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING))
.Times(1);
LeAudioSourceAudioHalClient::Callbacks* 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);
// NOTICE: This is really an implementation specific part, we fake the BIG
// config as the mocked state machine does not even call the
// IsoManager to prepare one (and that's good since IsoManager is also
// a mocked one).
BigConfig big_cfg;
big_cfg.big_id =
MockBroadcastStateMachine::GetLastInstance()->GetAdvertisingSid();
big_cfg.connection_handles = {0x10, 0x12};
big_cfg.max_pdu = 128;
MockBroadcastStateMachine::GetLastInstance()->SetExpectedBigConfig(big_cfg);
// Inject the audio and verify call on the Iso manager side.
EXPECT_CALL(*MockIsoManager::GetInstance(), SendIsoData).Times(1);
std::vector<uint8_t> sample_data(320, 0);
audio_receiver->OnAudioDataReady(sample_data);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
}
TEST_F(BroadcasterTest, StartAudioBroadcastMedia) {
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(1);
auto broadcast_id = InstantiateBroadcast(media_metadata, default_code,
{bluetooth::le_audio::QUALITY_HIGH});
Mock::VerifyAndClearExpectations(mock_codec_manager_);
LeAudioBroadcaster::Get()->StopAudioBroadcast(broadcast_id);
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, BroadcastState::STREAMING))
.Times(1);
LeAudioSourceAudioHalClient::Callbacks* audio_receiver;
EXPECT_CALL(*mock_audio_source_, Start)
.WillOnce(DoAll(SaveArg<1>(&audio_receiver), Return(true)));
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(0);
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, false))
.Times(0);
LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
ASSERT_NE(audio_receiver, nullptr);
// NOTICE: This is really an implementation specific part, we fake the BIG
// config as the mocked state machine does not even call the
// IsoManager to prepare one (and that's good since IsoManager is also
// a mocked one).
auto mock_state_machine = MockBroadcastStateMachine::GetLastInstance();
BigConfig big_cfg;
big_cfg.big_id = mock_state_machine->GetAdvertisingSid();
big_cfg.connection_handles = {0x10, 0x12};
big_cfg.max_pdu = 128;
mock_state_machine->SetExpectedBigConfig(big_cfg);
// Inject the audio and verify call on the Iso manager side.
EXPECT_CALL(*MockIsoManager::GetInstance(), SendIsoData).Times(2);
std::vector<uint8_t> sample_data(1920, 0);
audio_receiver->OnAudioDataReady(sample_data);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
}
TEST_F(BroadcasterTest, StopAudioBroadcast) {
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(1);
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, false))
.Times(1);
auto broadcast_id = InstantiateBroadcast();
LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
// NOTICE: This is really an implementation specific part, we fake the BIG
// config as the mocked state machine does not even call the
// IsoManager to prepare one (and that's good since IsoManager is also
// a mocked one).
auto mock_state_machine = MockBroadcastStateMachine::GetLastInstance();
BigConfig big_cfg;
big_cfg.big_id = mock_state_machine->GetAdvertisingSid();
big_cfg.connection_handles = {0x10, 0x12};
big_cfg.max_pdu = 128;
mock_state_machine->SetExpectedBigConfig(big_cfg);
InjectBigCreateComplete(big_cfg.big_id, 0x00);
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, BroadcastState::STOPPED))
.Times(1);
EXPECT_CALL(*mock_audio_source_, Stop).Times(AtLeast(1));
LeAudioBroadcaster::Get()->StopAudioBroadcast(broadcast_id);
InjectBigTerminateComplete(big_cfg.big_id, 0x16);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
}
TEST_F(BroadcasterTest, DestroyAudioBroadcast) {
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, true))
.Times(1);
auto broadcast_id = InstantiateBroadcast();
Mock::VerifyAndClearExpectations(mock_codec_manager_);
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, false))
.Times(1);
EXPECT_CALL(mock_broadcaster_callbacks_, OnBroadcastDestroyed(broadcast_id))
.Times(1);
LeAudioBroadcaster::Get()->DestroyAudioBroadcast(broadcast_id);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
ASSERT_EQ(mock_audio_source_, nullptr);
/* Create a mock again for the test purpose */
ConfigAudioHalClientMock();
EXPECT_CALL(*mock_codec_manager_,
UpdateActiveBroadcastAudioHalClient(mock_audio_source_, _))
.Times(0);
// Expect not being able to interact with this Broadcast
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, _))
.Times(0);
EXPECT_CALL(*mock_audio_source_, Stop).Times(0);
LeAudioBroadcaster::Get()->StopAudioBroadcast(broadcast_id);
EXPECT_CALL(*mock_audio_source_, Start).Times(0);
LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
EXPECT_CALL(*mock_audio_source_, Stop).Times(0);
LeAudioBroadcaster::Get()->SuspendAudioBroadcast(broadcast_id);
Mock::VerifyAndClearExpectations(mock_codec_manager_);
Mock::VerifyAndClearExpectations(&mock_broadcaster_callbacks_);
// Verify the expectations before the CleanUp, which may call Stop()
Mock::VerifyAndClearExpectations(mock_audio_source_);
}
TEST_F(BroadcasterTest, GetBroadcastAllStates) {
auto broadcast_id = InstantiateBroadcast();
auto broadcast_id2 = InstantiateBroadcast();
ASSERT_NE(broadcast_id, LeAudioBroadcaster::kInstanceIdUndefined);
ASSERT_NE(broadcast_id2, LeAudioBroadcaster::kInstanceIdUndefined);
ASSERT_NE(broadcast_id, broadcast_id2);
/* In the current implementation state machine switches to the correct state
* on itself, therefore here when we use mocked state machine this is not
* being verified.
*/
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id, _))
.Times(1);
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastStateChanged(broadcast_id2, _))
.Times(1);
LeAudioBroadcaster::Get()->GetAllBroadcastStates();
}
TEST_F(BroadcasterTest, UpdateMetadata) {
auto broadcast_id = InstantiateBroadcast();
std::vector<uint8_t> ccid_list;
std::vector<uint8_t> expected_public_meta;
std::string expected_broadcast_name;
EXPECT_CALL(*MockBroadcastStateMachine::GetLastInstance(),
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);
break;
}
}
});
EXPECT_CALL(*MockBroadcastStateMachine::GetLastInstance(),
UpdatePublicBroadcastAnnouncement)
.WillOnce([&](uint32_t broadcast_id, const std::string& broadcast_name,
const bluetooth::le_audio::PublicBroadcastAnnouncementData&
announcement) {
expected_broadcast_name = broadcast_name;
expected_public_meta =
types::LeAudioLtvMap(announcement.metadata).RawPacket();
});
ContentControlIdKeeper::GetInstance()->SetCcid(LeAudioContextType::ALERTS,
default_ccid);
LeAudioBroadcaster::Get()->UpdateMetadata(
broadcast_id, test_broadcast_name, default_public_metadata,
{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));
ASSERT_EQ(expected_broadcast_name, test_broadcast_name);
ASSERT_EQ(expected_public_meta, default_public_metadata);
}
static BasicAudioAnnouncementData prepareAnnouncement(
const BroadcastSubgroupCodecConfig& codec_config,
std::map<uint8_t, std::vector<uint8_t>> metadata) {
BasicAudioAnnouncementData announcement;
announcement.presentation_delay_us = 40000;
auto const& codec_id = codec_config.GetLeAudioCodecId();
auto const subgroup_codec_spec = codec_config.GetCommonBisCodecSpecData();
// Note: This is a single subgroup announcement.
announcement.subgroup_configs = {{
.codec_config =
{
.codec_id = codec_id.coding_format,
.vendor_company_id = codec_id.vendor_company_id,
.vendor_codec_id = codec_id.vendor_codec_id,
.codec_specific_params = subgroup_codec_spec.Values(),
},
.metadata = std::move(metadata),
.bis_configs = {},
}};
uint8_t bis_count = 0;
for (uint8_t cfg_idx = 0; cfg_idx < codec_config.GetAllBisConfigCount();
++cfg_idx) {
for (uint8_t bis_num = 0; bis_num < codec_config.GetNumBis(cfg_idx);
++bis_num) {
++bis_count;
// Check for vendor byte array
bluetooth::le_audio::BasicAudioAnnouncementBisConfig bis_config;
auto vendor_config = codec_config.GetBisVendorCodecSpecData(bis_num);
if (vendor_config) {
bis_config.vendor_codec_specific_params = vendor_config.value();
}
// Check for non vendor LTVs
auto config_ltv = codec_config.GetBisCodecSpecData(bis_num, cfg_idx);
if (config_ltv) {
bis_config.codec_specific_params = config_ltv->Values();
}
// Internally BISes are indexed from 0 in each subgroup, but the BT spec
// requires the indices to be indexed from 1 in the entire BIG.
bis_config.bis_index = bis_count;
announcement.subgroup_configs[0].bis_configs.push_back(
std::move(bis_config));
}
}
return announcement;
}
TEST_F(BroadcasterTest, UpdateMetadataFromAudioTrackMetadata) {
ContentControlIdKeeper::GetInstance()->SetCcid(LeAudioContextType::MEDIA,
media_ccid);
auto broadcast_id = InstantiateBroadcast();
LeAudioSourceAudioHalClient::Callbacks* 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 = {};
auto codec_config = broadcaster::lc3_mono_16_2;
auto announcement = prepareAnnouncement(codec_config, meta);
ON_CALL(*sm, GetBroadcastAnnouncement())
.WillByDefault(ReturnRef(announcement));
std::vector<struct playback_track_metadata> multitrack_source_metadata = {
{{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}}};
std::vector<playback_track_metadata_v7> tracks_vec;
tracks_vec.reserve(multitrack_source_metadata.size());
for (const auto& track : multitrack_source_metadata) {
playback_track_metadata_v7 desc_track = {
.base =
{
.usage = static_cast<audio_usage_t>(track.usage),
.content_type =
static_cast<audio_content_type_t>(track.content_type),
.gain = track.gain,
},
};
tracks_vec.push_back(desc_track);
}
audio_receiver->OnAudioMetadataUpdate(std::move(tracks_vec),
DsaMode::DISABLED);
// 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);
AudioContexts context_type;
auto pp = context_types_map.data();
STREAM_TO_UINT16(context_type.value_ref(), pp);
ASSERT_TRUE(context_type.test_all(LeAudioContextType::MEDIA |
LeAudioContextType::GAME));
}
TEST_F(BroadcasterTest, GetMetadata) {
auto broadcast_id = InstantiateBroadcast();
bluetooth::le_audio::BroadcastMetadata metadata;
static const uint8_t test_adv_sid = 0x14;
std::optional<bluetooth::le_audio::BroadcastCode> test_broadcast_code =
bluetooth::le_audio::BroadcastCode({1, 2, 3, 4, 5, 6});
auto sm = MockBroadcastStateMachine::GetLastInstance();
std::map<uint8_t, std::vector<uint8_t>> meta = {};
auto codec_config = broadcaster::lc3_mono_16_2;
auto announcement = prepareAnnouncement(codec_config, meta);
bool is_public_metadata_valid;
types::LeAudioLtvMap public_ltv = types::LeAudioLtvMap::Parse(
default_public_metadata.data(), default_public_metadata.size(),
is_public_metadata_valid);
PublicBroadcastAnnouncementData pb_announcement = {
.features = test_public_broadcast_features,
.metadata = public_ltv.Values()};
ON_CALL(*sm, IsPublicBroadcast()).WillByDefault(Return(true));
ON_CALL(*sm, GetBroadcastName()).WillByDefault(Return(test_broadcast_name));
ON_CALL(*sm, GetBroadcastCode()).WillByDefault(Return(test_broadcast_code));
ON_CALL(*sm, GetAdvertisingSid()).WillByDefault(Return(test_adv_sid));
ON_CALL(*sm, GetBroadcastAnnouncement())
.WillByDefault(ReturnRef(announcement));
ON_CALL(*sm, GetPublicBroadcastAnnouncement())
.WillByDefault(ReturnRef(pb_announcement));
EXPECT_CALL(mock_broadcaster_callbacks_,
OnBroadcastMetadataChanged(broadcast_id, _))
.Times(1)
.WillOnce(SaveArg<1>(&metadata));
LeAudioBroadcaster::Get()->GetBroadcastMetadata(broadcast_id);
ASSERT_NE(LeAudioBroadcaster::kInstanceIdUndefined, metadata.broadcast_id);
ASSERT_EQ(sm->GetBroadcastId(), metadata.broadcast_id);
ASSERT_EQ(sm->GetBroadcastCode(), metadata.broadcast_code);
ASSERT_EQ(sm->GetBroadcastAnnouncement(), metadata.basic_audio_announcement);
ASSERT_EQ(sm->GetPaInterval(), metadata.pa_interval);
ASSERT_EQ(sm->GetOwnAddress(), metadata.addr);
ASSERT_EQ(sm->GetOwnAddressType(), metadata.addr_type);
ASSERT_EQ(sm->GetAdvertisingSid(), metadata.adv_sid);
ASSERT_EQ(sm->IsPublicBroadcast(), metadata.is_public);
ASSERT_EQ(sm->GetBroadcastName(), metadata.broadcast_name);
ASSERT_EQ(sm->GetPublicBroadcastAnnouncement(), metadata.public_announcement);
}
TEST_F(BroadcasterTest, SetStreamingPhy) {
LeAudioBroadcaster::Get()->SetStreamingPhy(2);
// From now on new streams should be using Phy = 2.
InstantiateBroadcast();
ASSERT_EQ(MockBroadcastStateMachine::GetLastInstance()->cfg.streaming_phy, 2);
// From now on new streams should be using Phy = 1.
LeAudioBroadcaster::Get()->SetStreamingPhy(1);
InstantiateBroadcast();
ASSERT_EQ(MockBroadcastStateMachine::GetLastInstance()->cfg.streaming_phy, 1);
ASSERT_EQ(LeAudioBroadcaster::Get()->GetStreamingPhy(), 1);
}
TEST_F(BroadcasterTest, StreamParamsAlerts) {
uint8_t expected_channels = 1u;
InstantiateBroadcast();
auto config = MockBroadcastStateMachine::GetLastInstance()->cfg;
// Check audio configuration
ASSERT_EQ(config.config.subgroups.at(0).GetNumChannelsTotal(),
expected_channels);
// Matches number of bises in the announcement
ASSERT_EQ(config.announcement.subgroup_configs[0].bis_configs.size(),
expected_channels);
// Note: Num of bises at IsoManager level is verified by state machine tests
}
TEST_F(BroadcasterTest, StreamParamsMedia) {
uint8_t expected_channels = 2u;
ContentControlIdKeeper::GetInstance()->SetCcid(LeAudioContextType::MEDIA,
media_ccid);
InstantiateBroadcast(media_metadata, default_code,
{bluetooth::le_audio::QUALITY_HIGH});
auto config = MockBroadcastStateMachine::GetLastInstance()->cfg;
// Check audio configuration
ASSERT_EQ(config.config.subgroups.at(0).GetNumBis(), expected_channels);
ASSERT_EQ(config.config.subgroups.at(0).GetNumChannelsTotal(),
expected_channels);
// Note there is one BIS configuration applied to both (stereo) BISes
ASSERT_EQ(config.config.subgroups.at(0).GetAllBisConfigCount(),
(unsigned long)1);
ASSERT_EQ(config.config.subgroups.at(0).GetNumBis(0),
(unsigned long)expected_channels);
// Matches number of bises in the announcement
ASSERT_EQ(config.announcement.subgroup_configs.size(), 1ul);
auto& announcement_subgroup = config.announcement.subgroup_configs[0];
ASSERT_EQ(announcement_subgroup.bis_configs.size(), expected_channels);
// Verify CCID for Media
auto ccid_list_opt =
types::LeAudioLtvMap(announcement_subgroup.metadata)
.Find(bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList);
ASSERT_TRUE(ccid_list_opt.has_value());
auto ccid_list = ccid_list_opt.value();
ASSERT_EQ(1u, ccid_list.size());
ASSERT_EQ(media_ccid, ccid_list[0]);
// Note: Num of bises at IsoManager level is verified by state machine tests
}
TEST_F(BroadcasterTest, QueuedBroadcast) {
uint32_t broadcast_id = LeAudioBroadcaster::kInstanceIdUndefined;
iso_active_callback(true);
EXPECT_CALL(mock_broadcaster_callbacks_, OnBroadcastCreated(_, true))
.WillOnce(SaveArg<0>(&broadcast_id));
/* Trigger broadcast create but due to active ISO, queue request */
InstantiateBroadcast(default_metadata, default_code,
default_subgroup_qualities, true);
/* Notify about ISO being free, check if broadcast would be created */
iso_active_callback(false);
ASSERT_NE(broadcast_id, LeAudioBroadcaster::kInstanceIdUndefined);
ASSERT_EQ(broadcast_id,
MockBroadcastStateMachine::GetLastInstance()->GetBroadcastId());
auto& instance_config = MockBroadcastStateMachine::GetLastInstance()->cfg;
ASSERT_EQ(instance_config.broadcast_code, default_code);
for (auto& subgroup : instance_config.announcement.subgroup_configs) {
ASSERT_EQ(types::LeAudioLtvMap(subgroup.metadata).RawPacket(),
default_metadata);
}
}
TEST_F(BroadcasterTest, QueuedBroadcastBusyIso) {
iso_active_callback(true);
EXPECT_CALL(mock_broadcaster_callbacks_, OnBroadcastCreated(_, true))
.Times(0);
/* Trigger broadcast create but due to active ISO, queue request */
InstantiateBroadcast(default_metadata, default_code,
default_subgroup_qualities, true);
}
constexpr types::LeAudioCodecId kLeAudioCodecIdVendor1 = {
.coding_format = types::kLeAudioCodingFormatVendorSpecific,
// Not a particualr vendor - just some random numbers
.vendor_company_id = 0xC0,
.vendor_codec_id = 0xDE,
};
static const types::DataPathConfiguration vendor_data_path = {
.dataPathId = bluetooth::hci::iso_manager::kIsoDataPathHci,
.dataPathConfig = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
.isoDataPathConfig =
{
.codecId = kLeAudioCodecIdVendor1,
.isTransparent = true,
.controllerDelayUs = 0x00000000, // irrlevant for transparent mode
.configuration = {0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A, 0x79, 0x88,
0x97, 0xA6, 0xB5, 0xC4, 0xD3, 0xE2, 0xF1},
},
};
// Quality subgroup configurations
static const broadcaster::BroadcastSubgroupCodecConfig vendor_stereo_16_2 =
broadcaster::BroadcastSubgroupCodecConfig(
kLeAudioCodecIdVendor1,
{broadcaster::BroadcastSubgroupBisCodecConfig{
// num_bis
2,
// bis_channel_cnt
1,
// codec_specific
types::LeAudioLtvMap({
LTV_ENTRY_SAMPLING_FREQUENCY(
codec_spec_conf::kLeAudioSamplingFreq16000Hz),
LTV_ENTRY_FRAME_DURATION(
codec_spec_conf::kLeAudioCodecFrameDur10000us),
LTV_ENTRY_OCTETS_PER_CODEC_FRAME(50),
}),
// vendor_codec_specific
std::vector<uint8_t>{0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0,
0xF0},
}},
// bits_per_sample
24,
// vendor_codec_specific
std::vector<uint8_t>{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF});
static const broadcaster::BroadcastConfiguration vendor_stereo_16_2_1 = {
// subgroup list, qos configuration, data path configuration
.subgroups = {vendor_stereo_16_2},
.qos = broadcaster::qos_config_2_10,
.data_path = vendor_data_path,
.sduIntervalUs = 5000,
.maxSduOctets = 128,
.phy = 0x01, // PHY_LE_1M
.packing = 1, // Interleaved
.framing = 1, // Framed
};
TEST_F(BroadcasterTest, SanityTest) {
ASSERT_EQ(broadcaster::lc3_mono_16_2_1, broadcaster::lc3_mono_16_2_1);
ASSERT_EQ(vendor_stereo_16_2_1, vendor_stereo_16_2_1);
}
TEST_F(BroadcasterTest, VendorCodecConfig) {
ON_CALL(*mock_codec_manager_, GetBroadcastConfig)
.WillByDefault(Invoke([](const bluetooth::le_audio::CodecManager::
BroadcastConfigurationRequirements&) {
return std::make_unique<broadcaster::BroadcastConfiguration>(
vendor_stereo_16_2_1);
}));
ContentControlIdKeeper::GetInstance()->SetCcid(LeAudioContextType::MEDIA,
media_ccid);
// iso_active_callback(false);
auto broadcast_id = InstantiateBroadcast(media_metadata, default_code,
{bluetooth::le_audio::QUALITY_HIGH});
ASSERT_NE(LeAudioBroadcaster::kInstanceIdUndefined, broadcast_id);
auto mock_state_machine = MockBroadcastStateMachine::GetLastInstance();
ASSERT_NE(nullptr, mock_state_machine);
// Verify the codec config
ASSERT_EQ(vendor_stereo_16_2_1, mock_state_machine->cfg.config);
// Verify the basic audio announcement
ASSERT_NE(0lu, mock_state_machine->cfg.announcement.presentation_delay_us);
// One subgroup
ASSERT_EQ(1lu, mock_state_machine->cfg.announcement.subgroup_configs.size());
auto const& subgroup =
mock_state_machine->cfg.announcement.subgroup_configs.at(0);
auto const& expected_subgroup_codec_conf =
vendor_stereo_16_2_1.subgroups.at(0);
ASSERT_EQ(expected_subgroup_codec_conf.GetNumBis(),
subgroup.bis_configs.size());
// Subgroup level codec configuration
ASSERT_EQ(expected_subgroup_codec_conf.GetLeAudioCodecId().coding_format,
subgroup.codec_config.codec_id);
ASSERT_EQ(expected_subgroup_codec_conf.GetLeAudioCodecId().vendor_company_id,
subgroup.codec_config.vendor_company_id);
ASSERT_EQ(expected_subgroup_codec_conf.GetLeAudioCodecId().vendor_codec_id,
subgroup.codec_config.vendor_codec_id);
// There should be no common set of parameters in the LTV format if there is
// a vendor specific configuration
ASSERT_TRUE(subgroup.codec_config.codec_specific_params.empty());
ASSERT_TRUE(subgroup.codec_config.vendor_codec_specific_params.has_value());
ASSERT_EQ(
0, memcmp(expected_subgroup_codec_conf.GetVendorCodecSpecData()->data(),
subgroup.codec_config.vendor_codec_specific_params->data(),
subgroup.codec_config.vendor_codec_specific_params->size()));
// Subgroup metadata
ASSERT_NE(0lu, subgroup.metadata.size());
// Verify the BISes
ASSERT_EQ(expected_subgroup_codec_conf.GetNumBis(),
subgroup.bis_configs.size());
// Verify BIS 1
uint8_t bis_idx = 1;
ASSERT_EQ(bis_idx, subgroup.bis_configs.at(0).bis_index);
// Expect only the vendor specific data
ASSERT_TRUE(subgroup.bis_configs.at(0).codec_specific_params.empty());
ASSERT_TRUE(subgroup.bis_configs.at(0)
.vendor_codec_specific_params
.has_value()); // BIS vendor specific parameters
ASSERT_NE(0lu,
subgroup.bis_configs.at(0).vendor_codec_specific_params->size());
ASSERT_EQ(expected_subgroup_codec_conf.GetBisVendorCodecSpecData(0)->size(),
subgroup.bis_configs.at(0).vendor_codec_specific_params->size());
ASSERT_EQ(
0,
memcmp(expected_subgroup_codec_conf.GetBisVendorCodecSpecData(0)->data(),
subgroup.bis_configs.at(0).vendor_codec_specific_params->data(),
subgroup.bis_configs.at(0).vendor_codec_specific_params->size()));
// Verify BIS 2
bis_idx = 2;
ASSERT_EQ(bis_idx, subgroup.bis_configs.at(1).bis_index);
// Expect only the vendor specific data
ASSERT_TRUE(subgroup.bis_configs.at(1).codec_specific_params.empty());
ASSERT_TRUE(subgroup.bis_configs.at(1)
.vendor_codec_specific_params
.has_value()); // BIS vendor specific parameters
ASSERT_NE(0lu,
subgroup.bis_configs.at(1).vendor_codec_specific_params->size());
ASSERT_EQ(expected_subgroup_codec_conf.GetBisVendorCodecSpecData(1)->size(),
subgroup.bis_configs.at(1).vendor_codec_specific_params->size());
ASSERT_EQ(
0,
memcmp(expected_subgroup_codec_conf.GetBisVendorCodecSpecData(1)->data(),
subgroup.bis_configs.at(1).vendor_codec_specific_params->data(),
subgroup.bis_configs.at(1).vendor_codec_specific_params->size()));
}
// TODO: Add tests for:
// ToRawPacket(BasicAudioAnnouncementData const& in, std::vector<uint8_t>& data)
} // namespace bluetooth::le_audio