| /* |
| * Copyright 2020 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 "devices.h" |
| |
| #include <bluetooth/log.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <log/log.h> |
| |
| #include "btif_storage_mock.h" |
| #include "btm_api_mock.h" |
| #include "device_groups.h" |
| #include "hardware/bt_le_audio.h" |
| #include "hci/controller_interface_mock.h" |
| #include "le_audio/le_audio_utils.h" |
| #include "le_audio_set_configuration_provider.h" |
| #include "le_audio_types.h" |
| #include "mock_codec_manager.h" |
| #include "mock_csis_client.h" |
| #include "os/log.h" |
| #include "stack/btm/btm_int_types.h" |
| #include "test/mock/mock_main_shim_entry.h" |
| |
| tACL_CONN* btm_bda_to_acl(const RawAddress& bda, tBT_TRANSPORT transport) { |
| return nullptr; |
| } |
| |
| namespace bluetooth { |
| namespace le_audio { |
| namespace internal { |
| namespace { |
| |
| using ::bluetooth::le_audio::DeviceConnectState; |
| using ::bluetooth::le_audio::LeAudioDevice; |
| using ::bluetooth::le_audio::LeAudioDeviceGroup; |
| using ::bluetooth::le_audio::LeAudioDevices; |
| using ::bluetooth::le_audio::types::AseState; |
| using ::bluetooth::le_audio::types::AudioContexts; |
| using ::bluetooth::le_audio::types::AudioLocations; |
| using ::bluetooth::le_audio::types::BidirectionalPair; |
| using ::bluetooth::le_audio::types::CisType; |
| using ::bluetooth::le_audio::types::LeAudioContextType; |
| using testing::_; |
| using testing::Invoke; |
| using testing::NiceMock; |
| using testing::Return; |
| using testing::Test; |
| |
| auto constexpr kVendorCodecIdOne = bluetooth::le_audio::types::LeAudioCodecId( |
| {.coding_format = types::kLeAudioCodingFormatVendorSpecific, |
| .vendor_company_id = 0xF00D, |
| .vendor_codec_id = 0x0001}); |
| |
| set_configurations::CodecConfigSetting kVendorCodecOne = { |
| .id = kVendorCodecIdOne, |
| .params = types::LeAudioLtvMap({ |
| // Add the Sampling Freq and AudioChannelAllocation which are |
| // mandatory even for the Vendor codec provider (multicodec AIDL) |
| {codec_spec_conf::kLeAudioLtvTypeSamplingFreq, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioSamplingFreq16000Hz)}, |
| }), |
| // Some opaque data buffer |
| .vendor_params = std::vector<uint8_t>({0x01, 0xC0, 0xDE, 0xF0, 0x0D}), |
| .channel_count_per_iso_stream = 1, |
| }; |
| |
| set_configurations::CodecConfigSetting kVendorCodecOneSwb = { |
| .id = kVendorCodecIdOne, |
| .params = types::LeAudioLtvMap({ |
| // Add the Sampling Freq and AudioChannelAllocation which are |
| // mandatory even for the Vendor codec provider (multicodec AIDL) |
| {codec_spec_conf::kLeAudioLtvTypeSamplingFreq, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioSamplingFreq32000Hz)}, |
| }), |
| // Some opaque data buffer |
| .vendor_params = std::vector<uint8_t>({0x01, 0xC0, 0xDE, 0xF0, 0x0F}), |
| .channel_count_per_iso_stream = 1, |
| }; |
| |
| RawAddress GetTestAddress(int index) { |
| EXPECT_LT(index, UINT8_MAX); |
| RawAddress result = { |
| {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}}; |
| return result; |
| } |
| |
| class LeAudioDevicesTest : public Test { |
| protected: |
| void SetUp() override { |
| __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE); |
| devices_ = new LeAudioDevices(); |
| bluetooth::manager::SetMockBtmInterface(&btm_interface); |
| bluetooth::storage::SetMockBtifStorageInterface(&mock_btif_storage_); |
| } |
| |
| void TearDown() override { |
| bluetooth::manager::SetMockBtmInterface(nullptr); |
| bluetooth::storage::SetMockBtifStorageInterface(nullptr); |
| delete devices_; |
| } |
| |
| LeAudioDevices* devices_ = nullptr; |
| bluetooth::manager::MockBtmInterface btm_interface; |
| bluetooth::storage::MockBtifStorageInterface mock_btif_storage_; |
| }; |
| |
| TEST_F(LeAudioDevicesTest, test_add) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| ASSERT_EQ((size_t)0, devices_->Size()); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| ASSERT_EQ((size_t)1, devices_->Size()); |
| devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER, 1); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER, 2); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_remove) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_1 = GetTestAddress(1); |
| devices_->Add(test_address_1, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_2 = GetTestAddress(2); |
| devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER); |
| ASSERT_EQ((size_t)3, devices_->Size()); |
| devices_->Remove(test_address_0); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| devices_->Remove(GetTestAddress(3)); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| devices_->Remove(test_address_0); |
| ASSERT_EQ((size_t)2, devices_->Size()); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_find_by_address_success) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_1 = GetTestAddress(1); |
| devices_->Add(test_address_1, DeviceConnectState::DISCONNECTED); |
| RawAddress test_address_2 = GetTestAddress(2); |
| devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER); |
| LeAudioDevice* device = devices_->FindByAddress(test_address_1); |
| ASSERT_NE(nullptr, device); |
| ASSERT_EQ(test_address_1, device->address_); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_find_by_address_failed) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_2 = GetTestAddress(2); |
| devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER); |
| LeAudioDevice* device = devices_->FindByAddress(GetTestAddress(1)); |
| ASSERT_EQ(nullptr, device); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_get_by_address_success) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_1 = GetTestAddress(1); |
| devices_->Add(test_address_1, DeviceConnectState::DISCONNECTED); |
| RawAddress test_address_2 = GetTestAddress(2); |
| devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER); |
| std::shared_ptr<LeAudioDevice> device = |
| devices_->GetByAddress(test_address_1); |
| ASSERT_NE(nullptr, device); |
| ASSERT_EQ(test_address_1, device->address_); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_get_by_address_failed) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_2 = GetTestAddress(2); |
| devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER); |
| std::shared_ptr<LeAudioDevice> device = |
| devices_->GetByAddress(GetTestAddress(1)); |
| ASSERT_EQ(nullptr, device); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_find_by_conn_id_success) { |
| devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER); |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| devices_->Add(GetTestAddress(4), DeviceConnectState::CONNECTING_BY_USER); |
| LeAudioDevice* device = devices_->FindByAddress(test_address_0); |
| device->conn_id_ = 0x0005; |
| ASSERT_EQ(device, devices_->FindByConnId(0x0005)); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_find_by_conn_id_failed) { |
| devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER); |
| devices_->Add(GetTestAddress(0), DeviceConnectState::CONNECTING_BY_USER); |
| devices_->Add(GetTestAddress(4), DeviceConnectState::CONNECTING_BY_USER); |
| ASSERT_EQ(nullptr, devices_->FindByConnId(0x0006)); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_get_device_model_name_success) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| std::shared_ptr<LeAudioDevice> device = |
| devices_->GetByAddress(test_address_0); |
| ASSERT_NE(nullptr, device); |
| device->model_name_ = "Test"; |
| ON_CALL(mock_btif_storage_, GetRemoteDeviceProperty(_, _)) |
| .WillByDefault(Return(BT_STATUS_SUCCESS)); |
| device->GetDeviceModelName(); |
| ASSERT_EQ("", device->model_name_); |
| } |
| |
| TEST_F(LeAudioDevicesTest, test_get_device_model_name_failed) { |
| RawAddress test_address_0 = GetTestAddress(0); |
| devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER); |
| std::shared_ptr<LeAudioDevice> device = |
| devices_->GetByAddress(test_address_0); |
| ASSERT_NE(nullptr, device); |
| device->model_name_ = "Test"; |
| ON_CALL(mock_btif_storage_, GetRemoteDeviceProperty(_, _)) |
| .WillByDefault(Return(BT_STATUS_FAIL)); |
| device->GetDeviceModelName(); |
| ASSERT_EQ("Test", device->model_name_); |
| } |
| |
| /* TODO: Add FindByCisConnHdl test cases (ASE) */ |
| |
| } // namespace |
| |
| namespace { |
| using namespace ::bluetooth::le_audio::codec_spec_caps; |
| using namespace ::bluetooth::le_audio::set_configurations; |
| using namespace ::bluetooth::le_audio::types; |
| |
| static const hdl_pair hdl_pair_nil = hdl_pair(0x0000, 0x0000); |
| |
| enum class Lc3SettingId { |
| _BEGIN, |
| LC3_8_1 = _BEGIN, |
| LC3_8_2, |
| LC3_16_1, |
| LC3_16_2, |
| LC3_24_1, |
| LC3_24_2, |
| LC3_32_1, |
| LC3_32_2, |
| LC3_441_1, |
| LC3_441_2, |
| LC3_48_1, |
| LC3_48_2, |
| LC3_48_3, |
| LC3_48_4, |
| LC3_48_5, |
| LC3_48_6, |
| LC3_VND_1, |
| _END, |
| UNSUPPORTED = _END, |
| }; |
| static constexpr int Lc3SettingIdBegin = static_cast<int>(Lc3SettingId::_BEGIN); |
| static constexpr int Lc3SettingIdEnd = static_cast<int>(Lc3SettingId::_END); |
| |
| bool IsLc3SettingSupported(LeAudioContextType context_type, Lc3SettingId id) { |
| /* Update those values, on any change of codec linked with content type */ |
| switch (context_type) { |
| case LeAudioContextType::RINGTONE: |
| case LeAudioContextType::CONVERSATIONAL: |
| if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 || |
| id == Lc3SettingId::LC3_24_1 || id == Lc3SettingId::LC3_24_2 || |
| id == Lc3SettingId::LC3_32_1 || id == Lc3SettingId::LC3_32_2 || |
| id == Lc3SettingId::LC3_48_1 || id == Lc3SettingId::LC3_48_2 || |
| id == Lc3SettingId::LC3_48_3 || id == Lc3SettingId::LC3_48_4 || |
| id == Lc3SettingId::LC3_VND_1) |
| return true; |
| |
| break; |
| |
| case LeAudioContextType::MEDIA: |
| case LeAudioContextType::ALERTS: |
| case LeAudioContextType::INSTRUCTIONAL: |
| case LeAudioContextType::NOTIFICATIONS: |
| case LeAudioContextType::EMERGENCYALARM: |
| case LeAudioContextType::UNSPECIFIED: |
| if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 || |
| id == Lc3SettingId::LC3_48_4 || id == Lc3SettingId::LC3_48_1 || |
| id == Lc3SettingId::LC3_48_2 || id == Lc3SettingId::LC3_VND_1 || |
| id == Lc3SettingId::LC3_24_2) |
| return true; |
| |
| break; |
| |
| default: |
| if (id == Lc3SettingId::LC3_16_2) return true; |
| |
| break; |
| }; |
| |
| return false; |
| } |
| |
| static constexpr uint8_t kLeAudioSamplingFreqRfu = 0x0E; |
| uint8_t GetSamplingFrequency(Lc3SettingId id) { |
| switch (id) { |
| case Lc3SettingId::LC3_8_1: |
| case Lc3SettingId::LC3_8_2: |
| return ::bluetooth::le_audio::codec_spec_conf::kLeAudioSamplingFreq8000Hz; |
| case Lc3SettingId::LC3_16_1: |
| case Lc3SettingId::LC3_16_2: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioSamplingFreq16000Hz; |
| case Lc3SettingId::LC3_24_1: |
| case Lc3SettingId::LC3_24_2: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioSamplingFreq24000Hz; |
| case Lc3SettingId::LC3_32_1: |
| case Lc3SettingId::LC3_32_2: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioSamplingFreq32000Hz; |
| case Lc3SettingId::LC3_441_1: |
| case Lc3SettingId::LC3_441_2: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioSamplingFreq44100Hz; |
| case Lc3SettingId::LC3_48_1: |
| case Lc3SettingId::LC3_48_2: |
| case Lc3SettingId::LC3_48_3: |
| case Lc3SettingId::LC3_48_4: |
| case Lc3SettingId::LC3_48_5: |
| case Lc3SettingId::LC3_48_6: |
| case Lc3SettingId::LC3_VND_1: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioSamplingFreq48000Hz; |
| case Lc3SettingId::UNSUPPORTED: |
| return kLeAudioSamplingFreqRfu; |
| } |
| } |
| |
| static constexpr uint8_t kLeAudioCodecFrameDurRfu = 0x02; |
| uint8_t GetFrameDuration(Lc3SettingId id) { |
| switch (id) { |
| case Lc3SettingId::LC3_8_1: |
| case Lc3SettingId::LC3_16_1: |
| case Lc3SettingId::LC3_24_1: |
| case Lc3SettingId::LC3_32_1: |
| case Lc3SettingId::LC3_441_1: |
| case Lc3SettingId::LC3_48_1: |
| case Lc3SettingId::LC3_48_3: |
| case Lc3SettingId::LC3_48_5: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioCodecFrameDur7500us; |
| case Lc3SettingId::LC3_8_2: |
| case Lc3SettingId::LC3_16_2: |
| case Lc3SettingId::LC3_24_2: |
| case Lc3SettingId::LC3_32_2: |
| case Lc3SettingId::LC3_441_2: |
| case Lc3SettingId::LC3_48_2: |
| case Lc3SettingId::LC3_48_4: |
| case Lc3SettingId::LC3_48_6: |
| case Lc3SettingId::LC3_VND_1: |
| return ::bluetooth::le_audio::codec_spec_conf:: |
| kLeAudioCodecFrameDur10000us; |
| case Lc3SettingId::UNSUPPORTED: |
| return kLeAudioCodecFrameDurRfu; |
| } |
| } |
| |
| static constexpr uint8_t kLeAudioCodecLC3OctetsPerCodecFrameInvalid = 0; |
| uint16_t GetOctetsPerCodecFrame(Lc3SettingId id) { |
| switch (id) { |
| case Lc3SettingId::LC3_8_1: |
| return 26; |
| case Lc3SettingId::LC3_8_2: |
| case Lc3SettingId::LC3_16_1: |
| return 30; |
| case Lc3SettingId::LC3_16_2: |
| return 40; |
| case Lc3SettingId::LC3_24_1: |
| return 45; |
| case Lc3SettingId::LC3_24_2: |
| case Lc3SettingId::LC3_32_1: |
| return 60; |
| case Lc3SettingId::LC3_32_2: |
| return 80; |
| case Lc3SettingId::LC3_441_1: |
| return 97; |
| case Lc3SettingId::LC3_441_2: |
| return 130; |
| case Lc3SettingId::LC3_48_1: |
| return 75; |
| case Lc3SettingId::LC3_48_2: |
| case Lc3SettingId::LC3_VND_1: |
| return 100; |
| case Lc3SettingId::LC3_48_3: |
| return 90; |
| case Lc3SettingId::LC3_48_4: |
| return 120; |
| case Lc3SettingId::LC3_48_5: |
| return 116; |
| case Lc3SettingId::LC3_48_6: |
| return 155; |
| case Lc3SettingId::UNSUPPORTED: |
| return kLeAudioCodecLC3OctetsPerCodecFrameInvalid; |
| } |
| } |
| |
| class PublishedAudioCapabilitiesBuilder { |
| public: |
| PublishedAudioCapabilitiesBuilder() {} |
| |
| void Add(LeAudioCodecId codec_id, uint8_t conf_sampling_frequency, |
| uint8_t conf_frame_duration, uint8_t audio_channel_counts, |
| uint16_t octets_per_frame, uint8_t codec_frames_per_sdu = 0) { |
| uint16_t sampling_frequencies = |
| SamplingFreqConfig2Capability(conf_sampling_frequency); |
| uint8_t frame_durations = |
| FrameDurationConfig2Capability(conf_frame_duration); |
| uint8_t max_codec_frames_per_sdu = codec_frames_per_sdu; |
| uint32_t octets_per_frame_range = |
| octets_per_frame | (octets_per_frame << 16); |
| |
| auto ltv_map = LeAudioLtvMap(); |
| ltv_map |
| .Add(kLeAudioLtvTypeSupportedSamplingFrequencies, |
| (uint16_t)sampling_frequencies) |
| .Add(kLeAudioLtvTypeSupportedFrameDurations, (uint8_t)frame_durations) |
| .Add(kLeAudioLtvTypeSupportedAudioChannelCounts, |
| (uint8_t)audio_channel_counts) |
| .Add(kLeAudioLtvTypeSupportedOctetsPerCodecFrame, |
| (uint32_t)octets_per_frame_range) |
| .Add(kLeAudioLtvTypeSupportedMaxCodecFramesPerSdu, |
| (uint8_t)max_codec_frames_per_sdu); |
| |
| auto record = acs_ac_record( |
| {.codec_id = codec_id, |
| .codec_spec_caps = |
| (codec_id.coding_format != kLeAudioCodingFormatVendorSpecific |
| ? ltv_map |
| : LeAudioLtvMap()), |
| .codec_spec_caps_raw = ltv_map.RawPacket(), |
| .metadata = std::vector<uint8_t>(0)}); |
| pac_records_.push_back(record); |
| } |
| |
| void Add(LeAudioCodecId codec_id, uint16_t capa_sampling_frequency, |
| uint8_t capa_frame_duration, uint8_t audio_channel_counts, |
| uint16_t octets_per_frame_min, uint16_t ocets_per_frame_max, |
| uint8_t codec_frames_per_sdu = 1) { |
| uint32_t octets_per_frame_range = |
| octets_per_frame_min | (ocets_per_frame_max << 16); |
| |
| auto ltv_map = LeAudioLtvMap({ |
| {kLeAudioLtvTypeSupportedSamplingFrequencies, |
| UINT16_TO_VEC_UINT8(capa_sampling_frequency)}, |
| {kLeAudioLtvTypeSupportedFrameDurations, |
| UINT8_TO_VEC_UINT8(capa_frame_duration)}, |
| {kLeAudioLtvTypeSupportedAudioChannelCounts, |
| UINT8_TO_VEC_UINT8(audio_channel_counts)}, |
| {kLeAudioLtvTypeSupportedOctetsPerCodecFrame, |
| UINT32_TO_VEC_UINT8(octets_per_frame_range)}, |
| {kLeAudioLtvTypeSupportedMaxCodecFramesPerSdu, |
| UINT8_TO_VEC_UINT8(codec_frames_per_sdu)}, |
| }); |
| pac_records_.push_back(acs_ac_record( |
| {.codec_id = codec_id, |
| // Transparent LTV map capabilities only for the LC3 codec |
| .codec_spec_caps = (codec_id.coding_format == kLeAudioCodingFormatLC3) |
| ? ltv_map |
| : LeAudioLtvMap(), |
| .codec_spec_caps_raw = ltv_map.RawPacket(), |
| .metadata = std::vector<uint8_t>(0)})); |
| } |
| |
| void Add(LeAudioCodecId codec_id, const std::vector<uint8_t>& vendor_data, |
| uint8_t audio_channel_counts) { |
| pac_records_.push_back(acs_ac_record( |
| {.codec_id = codec_id, |
| .codec_spec_caps = LeAudioLtvMap({ |
| {kLeAudioLtvTypeSupportedAudioChannelCounts, |
| UINT8_TO_VEC_UINT8(audio_channel_counts)}, |
| }), |
| // For now assume that vendor representation of codec capabilities |
| // equals the representation of codec settings |
| .codec_spec_caps_raw = vendor_data, |
| .metadata = std::vector<uint8_t>(0)})); |
| } |
| |
| void Add(const CodecConfigSetting& setting, uint8_t audio_channel_counts) { |
| if (setting.id != LeAudioCodecIdLc3) { |
| Add(setting.id, setting.vendor_params, audio_channel_counts); |
| return; |
| } |
| |
| const LeAudioCoreCodecConfig core_config = |
| setting.params.GetAsCoreCodecConfig(); |
| Add(setting.id, *core_config.sampling_frequency, |
| *core_config.frame_duration, audio_channel_counts, |
| *core_config.octets_per_codec_frame); |
| } |
| |
| void Reset() { pac_records_.clear(); } |
| |
| PublishedAudioCapabilities Get() { |
| return PublishedAudioCapabilities({{hdl_pair_nil, pac_records_}}); |
| } |
| |
| private: |
| std::vector<acs_ac_record> pac_records_; |
| }; |
| |
| struct TestGroupAseConfigurationData { |
| LeAudioDevice* device; |
| uint8_t audio_channel_counts_snk; |
| uint8_t audio_channel_counts_src; |
| |
| /* Note, do not confuse ASEs with channels num. */ |
| uint8_t expected_active_channel_num_snk; |
| uint8_t expected_active_channel_num_src; |
| }; |
| |
| class LeAudioAseConfigurationTest |
| : public Test, |
| public ::testing::WithParamInterface<uint16_t> { |
| protected: |
| uint16_t codec_coding_format_ = 0x0000; |
| |
| void SetUp() override { |
| __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE); |
| codec_coding_format_ = GetParam(); |
| |
| group_ = new LeAudioDeviceGroup(group_id_); |
| desired_group_size_ = -1; |
| |
| bluetooth::manager::SetMockBtmInterface(&btm_interface_); |
| bluetooth::hci::testing::mock_controller_ = &controller_interface_; |
| |
| auto codec_location = ::bluetooth::le_audio::types::CodecLocation::HOST; |
| bluetooth::le_audio::AudioSetConfigurationProvider::Initialize( |
| codec_location); |
| MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_); |
| ON_CALL(mock_csis_client_module_, Get()) |
| .WillByDefault(Return(&mock_csis_client_module_)); |
| ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) |
| .WillByDefault(Return(true)); |
| ON_CALL(mock_csis_client_module_, GetDeviceList(_)) |
| .WillByDefault(Invoke([this](int group_id) { return addresses_; })); |
| ON_CALL(mock_csis_client_module_, GetDesiredSize(_)) |
| .WillByDefault(Invoke([this](int group_id) { |
| return desired_group_size_ > 0 ? desired_group_size_ |
| : (int)(addresses_.size()); |
| })); |
| SetUpMockCodecManager(codec_location); |
| } |
| |
| static std::vector<AseConfiguration> |
| GetVendorAseConfigurationsForRequirements( |
| const bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements& |
| requirements, |
| const CodecConfigSetting& codec, uint8_t direction) { |
| std::vector<AseConfiguration> ase_confs; |
| |
| auto const& required_pacs = (direction == kLeAudioDirectionSink) |
| ? requirements.sink_pacs |
| : requirements.source_pacs; |
| auto direction_requirements = (direction == kLeAudioDirectionSink) |
| ? requirements.sink_requirements |
| : requirements.source_requirements; |
| |
| if (std::count_if( |
| required_pacs->begin(), required_pacs->end(), |
| [](auto const& pac) { return pac.codec_spec_caps_raw.empty(); })) { |
| return ase_confs; |
| } |
| |
| if (!required_pacs.has_value() || (required_pacs->size() == 0)) { |
| return ase_confs; |
| } |
| |
| AseConfiguration endpoint_cfg( |
| codec, {.target_latency = kTargetLatencyLower, |
| .retransmission_number = 3, |
| .max_transport_latency = kMaxTransportLatencyMin}); |
| |
| // Finding the max channel count |
| uint32_t target_max_channel_counts_per_ase_bitmap = |
| 0b1; // bit 0 - one channel |
| for (auto const& pac : *required_pacs) { |
| auto caps = pac.codec_spec_caps.GetAsCoreCodecCapabilities(); |
| if (caps.HasSupportedAudioChannelCounts()) { |
| auto new_counts = caps.supported_audio_channel_counts.value(); |
| if (new_counts > target_max_channel_counts_per_ase_bitmap) { |
| target_max_channel_counts_per_ase_bitmap = new_counts; |
| } |
| } |
| } |
| |
| uint8_t target_max_channel_counts_per_ase = 0; |
| while (target_max_channel_counts_per_ase_bitmap) { |
| ++target_max_channel_counts_per_ase; |
| target_max_channel_counts_per_ase_bitmap = |
| target_max_channel_counts_per_ase_bitmap >> 1; |
| } |
| |
| // For sink we always put a requirement here, but for source there are |
| // some conditions |
| auto sourceAsesNeeded = |
| (!kLeAudioContextAllRemoteSinkOnly.test( |
| requirements.audio_context_type) || |
| (requirements.audio_context_type == LeAudioContextType::RINGTONE)) && |
| (requirements.audio_context_type != |
| types::LeAudioContextType::UNSPECIFIED); |
| if ((direction == kLeAudioDirectionSink) || sourceAsesNeeded) { |
| // Create ASE configurations with the proper audio channel allocation |
| uint8_t count = 0; |
| uint32_t allocations = 0; |
| for (auto const& req : *direction_requirements) { |
| auto req_allocations = VEC_UINT8_TO_UINT32(req.params.At( |
| codec_spec_conf::kLeAudioLtvTypeAudioChannelAllocation)); |
| |
| // Create the list of requested audio allocations |
| std::list<uint32_t> split_allocations; |
| uint8_t bit_pos = 0; |
| while (req_allocations) { |
| if (req_allocations & 0b1) { |
| split_allocations.push_back(1 << bit_pos); |
| } |
| req_allocations = req_allocations >> 1; |
| bit_pos++; |
| } |
| |
| // Pick a number of allocations from the list (depending on supported |
| // channel counts per ASE) and create an ASE configuration. |
| while (split_allocations.size()) { |
| auto num_of_allocations_per_ase = |
| std::min(target_max_channel_counts_per_ase, |
| (uint8_t)split_allocations.size()); |
| // Note: This is very important to set for the unit test |
| // Configuration verifier |
| endpoint_cfg.codec.channel_count_per_iso_stream = |
| num_of_allocations_per_ase; |
| |
| // Consume the `num_of_allocations_per_ase` amount of allocations for |
| // this particular ASE |
| uint32_t ase_allocations = 0; |
| while (num_of_allocations_per_ase) { |
| ase_allocations |= split_allocations.front(); |
| split_allocations.pop_front(); |
| --num_of_allocations_per_ase; |
| } |
| endpoint_cfg.codec.params.Add( |
| codec_spec_conf::kLeAudioLtvTypeAudioChannelAllocation, |
| ase_allocations); |
| |
| // Add the ASE configuration |
| ase_confs.push_back(endpoint_cfg); |
| } |
| } |
| } |
| |
| return ase_confs; |
| } |
| |
| static auto MockVendorCodecProvider( |
| const bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements& |
| requirements) { |
| AudioSetConfiguration cfg = { |
| .name = "Example Vendor Codec Configuration", |
| .packing = bluetooth::hci::kIsoCigPackingSequential, |
| .confs = {.sink = {}, .source = {}}, |
| }; |
| |
| CodecConfigSetting codec = bluetooth::le_audio::CodecManager::GetInstance() |
| ->IsDualBiDirSwbSupported() |
| ? kVendorCodecOneSwb |
| : kVendorCodecOne; |
| if (requirements.sink_requirements) { |
| cfg.confs.sink = GetVendorAseConfigurationsForRequirements( |
| requirements, codec, kLeAudioDirectionSink); |
| } |
| |
| if (requirements.source_requirements) { |
| cfg.confs.source = GetVendorAseConfigurationsForRequirements( |
| requirements, codec, kLeAudioDirectionSource); |
| } |
| |
| log::debug("snk confs size: {}", cfg.confs.sink.size()); |
| log::debug("src confs size: {}", cfg.confs.source.size()); |
| return (!cfg.confs.sink.empty() || !cfg.confs.source.empty()) |
| ? std::make_unique<AudioSetConfiguration>(cfg) |
| : nullptr; |
| } |
| |
| void SetUpMockCodecManager( |
| bluetooth::le_audio::types::CodecLocation location) { |
| codec_manager_ = bluetooth::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((void*)mock_codec_manager_, (void*)codec_manager_); |
| ASSERT_NE(mock_codec_manager_, nullptr); |
| ON_CALL(*mock_codec_manager_, GetCodecLocation()) |
| .WillByDefault(Return(location)); |
| |
| // Set up the config provider for the Lc3 codec |
| if (codec_coding_format_ == kLeAudioCodingFormatLC3) { |
| // Regardless of the codec location, return all the possible |
| // configurations |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(true)); |
| ON_CALL(*mock_codec_manager_, GetCodecConfig) |
| .WillByDefault(Invoke( |
| [](const bluetooth::le_audio::CodecManager:: |
| UnicastConfigurationRequirements& requirements, |
| bluetooth::le_audio::CodecManager::UnicastConfigurationVerifier |
| verifier) { |
| auto filtered = |
| *bluetooth::le_audio::AudioSetConfigurationProvider::Get() |
| ->GetConfigurations(requirements.audio_context_type); |
| // Filter out the dual bidir SWB configurations |
| if (!bluetooth::le_audio::CodecManager::GetInstance() |
| ->IsDualBiDirSwbSupported()) { |
| filtered.erase( |
| std::remove_if( |
| filtered.begin(), filtered.end(), |
| [](auto const& el) { |
| if (el->confs.source.empty()) return false; |
| return AudioSetConfigurationProvider::Get() |
| ->CheckConfigurationIsDualBiDirSwb(*el); |
| }), |
| filtered.end()); |
| } |
| auto cfg = verifier(requirements, &filtered); |
| if (cfg == nullptr) { |
| return std::unique_ptr<AudioSetConfiguration>(nullptr); |
| } |
| return std::make_unique<AudioSetConfiguration>(*cfg); |
| })); |
| } else { |
| // Provide a configuration for the vendor codec |
| ON_CALL(*mock_codec_manager_, GetCodecConfig) |
| .WillByDefault(Invoke( |
| [](const bluetooth::le_audio::CodecManager:: |
| UnicastConfigurationRequirements& requirements, |
| bluetooth::le_audio::CodecManager::UnicastConfigurationVerifier |
| verifier) { |
| return MockVendorCodecProvider(requirements); |
| })); |
| } |
| |
| ON_CALL(*mock_codec_manager_, CheckCodecConfigIsBiDirSwb) |
| .WillByDefault(Invoke([](const bluetooth::le_audio::set_configurations:: |
| AudioSetConfiguration& config) { |
| return AudioSetConfigurationProvider::Get() |
| ->CheckConfigurationIsBiDirSwb(config); |
| })); |
| ON_CALL(*mock_codec_manager_, CheckCodecConfigIsDualBiDirSwb) |
| .WillByDefault(Invoke([](const bluetooth::le_audio::set_configurations:: |
| AudioSetConfiguration& config) { |
| return AudioSetConfigurationProvider::Get() |
| ->CheckConfigurationIsDualBiDirSwb(config); |
| })); |
| } |
| |
| void TearDown() override { |
| bluetooth::manager::SetMockBtmInterface(nullptr); |
| devices_.clear(); |
| addresses_.clear(); |
| delete group_; |
| ::bluetooth::le_audio::AudioSetConfigurationProvider::Cleanup(); |
| |
| if (mock_codec_manager_) { |
| testing::Mock::VerifyAndClearExpectations(mock_codec_manager_); |
| } |
| if (codec_manager_) { |
| codec_manager_->Stop(); |
| } |
| } |
| |
| LeAudioDevice* AddTestDevice( |
| int snk_ase_num, int src_ase_num, int snk_ase_num_cached = 0, |
| int src_ase_num_cached = 0, bool invert_ases_emplacement = false, |
| bool out_of_range_device = false, |
| uint8_t snk_allocation = codec_spec_conf::kLeAudioLocationFrontLeft | |
| codec_spec_conf::kLeAudioLocationFrontRight, |
| uint8_t src_allocation = codec_spec_conf::kLeAudioLocationFrontLeft | |
| codec_spec_conf::kLeAudioLocationFrontRight) { |
| int index = group_->Size() + 1; |
| auto device = (std::make_shared<LeAudioDevice>( |
| GetTestAddress(index), DeviceConnectState::DISCONNECTED)); |
| devices_.push_back(device); |
| addresses_.push_back(device->address_); |
| log::info("Number of devices {}", (int)(addresses_.size())); |
| |
| if (out_of_range_device == false) { |
| group_->AddNode(device); |
| } |
| |
| int ase_id = 1; |
| for (int i = 0; i < (invert_ases_emplacement ? snk_ase_num : src_ase_num); |
| i++) { |
| device->ases_.emplace_back(0x0000, 0x0000, |
| invert_ases_emplacement |
| ? kLeAudioDirectionSink |
| : kLeAudioDirectionSource, |
| ase_id++); |
| } |
| |
| for (int i = 0; i < (invert_ases_emplacement ? src_ase_num : snk_ase_num); |
| i++) { |
| device->ases_.emplace_back(0x0000, 0x0000, |
| invert_ases_emplacement |
| ? kLeAudioDirectionSource |
| : kLeAudioDirectionSink, |
| ase_id++); |
| } |
| |
| for (int i = 0; i < (invert_ases_emplacement ? snk_ase_num_cached |
| : src_ase_num_cached); |
| i++) { |
| struct ase ase(0x0000, 0x0000, |
| invert_ases_emplacement ? kLeAudioDirectionSink |
| : kLeAudioDirectionSource, |
| ase_id++); |
| ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; |
| device->ases_.push_back(ase); |
| } |
| |
| for (int i = 0; i < (invert_ases_emplacement ? src_ase_num_cached |
| : snk_ase_num_cached); |
| i++) { |
| struct ase ase(0x0000, 0x0000, |
| invert_ases_emplacement ? kLeAudioDirectionSource |
| : kLeAudioDirectionSink, |
| ase_id++); |
| ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; |
| device->ases_.push_back(ase); |
| } |
| |
| device->SetSupportedContexts( |
| {.sink = AudioContexts(kLeAudioContextAllTypes), |
| .source = AudioContexts(kLeAudioContextAllTypes)}); |
| device->SetAvailableContexts( |
| {.sink = AudioContexts(kLeAudioContextAllTypes), |
| .source = AudioContexts(kLeAudioContextAllTypes)}); |
| device->snk_audio_locations_ = snk_allocation; |
| device->src_audio_locations_ = src_allocation; |
| |
| device->conn_id_ = index; |
| device->SetConnectionState(out_of_range_device |
| ? DeviceConnectState::DISCONNECTED |
| : DeviceConnectState::CONNECTED); |
| group_->ReloadAudioDirections(); |
| group_->ReloadAudioLocations(); |
| return device.get(); |
| } |
| |
| bool TestGroupAseConfigurationVerdict( |
| const TestGroupAseConfigurationData& data, uint8_t directions_to_verify) { |
| BidirectionalPair<uint8_t> active_channel_num = {0, 0}; |
| |
| if (directions_to_verify == 0) return false; |
| if (data.device->HaveActiveAse() == 0) return false; |
| |
| for (ase* ase = data.device->GetFirstActiveAse(); ase; |
| ase = data.device->GetNextActiveAse(ase)) { |
| active_channel_num.get(ase->direction) += ase->channel_count; |
| } |
| |
| bool result = true; |
| if (directions_to_verify & kLeAudioDirectionSink) { |
| result &= (data.expected_active_channel_num_snk == |
| active_channel_num.get(kLeAudioDirectionSink)); |
| } |
| if (directions_to_verify & kLeAudioDirectionSource) { |
| result &= (data.expected_active_channel_num_src == |
| active_channel_num.get(kLeAudioDirectionSource)); |
| } |
| return result; |
| } |
| |
| void SetCisInformationToActiveAse(void) { |
| uint8_t cis_id = 1; |
| uint16_t cis_conn_hdl = 0x0060; |
| |
| for (auto& device : devices_) { |
| for (auto& ase : device->ases_) { |
| if (ase.active) { |
| ase.cis_id = cis_id++; |
| ase.cis_conn_hdl = cis_conn_hdl++; |
| } |
| } |
| } |
| } |
| |
| void TestSingleAseConfiguration(LeAudioContextType context_type, |
| TestGroupAseConfigurationData* data, |
| uint8_t data_size, |
| const AudioSetConfiguration* audio_set_conf, |
| uint8_t directions_to_verify) { |
| // the configuration should fail if there are no active ases expected |
| bool success_expected = data_size > 0; |
| uint8_t configuration_directions = 0; |
| |
| for (int i = 0; i < data_size; i++) { |
| success_expected &= (data[i].expected_active_channel_num_snk + |
| data[i].expected_active_channel_num_src) > 0; |
| |
| /* Prepare PAC's */ |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| for (const auto& entry : (*audio_set_conf).confs.sink) { |
| configuration_directions |= kLeAudioDirectionSink; |
| snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk); |
| } |
| for (const auto& entry : (*audio_set_conf).confs.source) { |
| configuration_directions |= kLeAudioDirectionSource; |
| src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src); |
| } |
| |
| data[i].device->snk_pacs_ = snk_pac_builder.Get(); |
| data[i].device->src_pacs_ = src_pac_builder.Get(); |
| } |
| |
| BidirectionalPair<AudioContexts> group_audio_locations = { |
| .sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)}; |
| |
| /* Stimulate update of available context map */ |
| group_->UpdateAudioContextAvailability(); |
| |
| ASSERT_EQ(success_expected, |
| group_->Configure(context_type, group_audio_locations)); |
| |
| bool result = true; |
| for (int i = 0; i < data_size; i++) { |
| result &= TestGroupAseConfigurationVerdict( |
| data[i], directions_to_verify & configuration_directions); |
| } |
| ASSERT_TRUE(result); |
| } |
| |
| int getNumOfAses(LeAudioDevice* device, uint8_t direction) { |
| return std::count_if( |
| device->ases_.begin(), device->ases_.end(), |
| [direction](auto& a) { return a.direction == direction; }); |
| } |
| |
| void TestGroupAseVendorConfiguration( |
| LeAudioContextType context_type, TestGroupAseConfigurationData* data, |
| uint8_t data_size, |
| uint8_t directions_to_verify = kLeAudioDirectionSink | |
| kLeAudioDirectionSource) { |
| for (int i = 0; i < data_size; i++) { |
| /* Add PACs and check if each of the devices has activated ASEs as |
| * expected */ |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| |
| // Prepare the PACs |
| for (auto direction : {kLeAudioDirectionSink, kLeAudioDirectionSource}) { |
| auto const& data_channel_counts = |
| (direction == kLeAudioDirectionSink) |
| ? data[i].audio_channel_counts_snk |
| : data[i].audio_channel_counts_src; |
| |
| PublishedAudioCapabilitiesBuilder pac_builder; |
| for (auto codec : {kVendorCodecOne, kVendorCodecOneSwb}) { |
| codec.channel_count_per_iso_stream = data_channel_counts; |
| pac_builder.Add(codec, data_channel_counts); |
| } |
| |
| // Set the PACs |
| auto& dest_pacs = (direction == kLeAudioDirectionSink) |
| ? data[i].device->snk_pacs_ |
| : data[i].device->src_pacs_; |
| dest_pacs = pac_builder.Get(); |
| } |
| } |
| |
| // Verify if ASEs are configured |
| BidirectionalPair<AudioContexts> metadata = { |
| .sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)}; |
| ASSERT_EQ(true, group_->Configure(context_type, metadata)); |
| |
| for (int i = 0; i < data_size; i++) { |
| ASSERT_TRUE( |
| TestGroupAseConfigurationVerdict(data[i], directions_to_verify)); |
| } |
| |
| group_->Deactivate(); |
| TestAsesInactive(); |
| } |
| |
| void TestGroupAseConfiguration( |
| LeAudioContextType context_type, TestGroupAseConfigurationData* data, |
| uint8_t data_size, |
| uint8_t directions_to_verify = kLeAudioDirectionSink | |
| kLeAudioDirectionSource) { |
| if (codec_coding_format_ != kLeAudioCodingFormatLC3) { |
| return TestGroupAseVendorConfiguration(context_type, data, data_size, |
| directions_to_verify); |
| } |
| |
| const auto* configurations = |
| ::bluetooth::le_audio::AudioSetConfigurationProvider::Get() |
| ->GetConfigurations(context_type); |
| |
| bool success_expected = directions_to_verify != 0; |
| int num_of_matching_configurations = 0; |
| for (const auto& audio_set_conf : *configurations) { |
| bool interesting_configuration = true; |
| uint8_t configuration_directions = 0; |
| |
| // the configuration should fail if there are no active ases expected |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| snk_pac_builder.Reset(); |
| src_pac_builder.Reset(); |
| |
| /* Let's go thru devices in the group and configure them*/ |
| for (int i = 0; i < data_size; i++) { |
| BidirectionalPair<int> num_of_ase{0, 0}; |
| |
| /* Prepare PAC's for each device. Also make sure configuration is in our |
| * interest to test */ |
| for (auto direction : |
| {kLeAudioDirectionSink, kLeAudioDirectionSource}) { |
| auto const& ase_confs = audio_set_conf->confs.get(direction); |
| auto strategy = bluetooth::le_audio::utils::GetStrategyForAseConfig( |
| ase_confs, data_size); |
| auto const ase_cnt = ase_confs.size(); |
| |
| if (ase_cnt == 0) { |
| // Skip the direction if not available |
| continue; |
| } |
| |
| /* Make sure the strategy is the expected one */ |
| if (direction == kLeAudioDirectionSink && |
| group_->GetGroupSinkStrategy() != strategy) { |
| log::debug("Sink strategy mismatch group!=cfg.entry ({}!={})", |
| static_cast<int>(group_->GetGroupSinkStrategy()), |
| static_cast<int>(strategy)); |
| interesting_configuration = false; |
| } |
| |
| configuration_directions |= direction; |
| |
| auto& pac_builder = (direction == kLeAudioDirectionSink) |
| ? snk_pac_builder |
| : src_pac_builder; |
| auto& dest_pacs = (direction == kLeAudioDirectionSink) |
| ? data[i].device->snk_pacs_ |
| : data[i].device->src_pacs_; |
| auto const& data_channel_counts = |
| (direction == kLeAudioDirectionSink) |
| ? data[i].audio_channel_counts_snk |
| : data[i].audio_channel_counts_src; |
| |
| for (const auto& entry : ase_confs) { |
| num_of_ase.get(direction)++; |
| pac_builder.Add(entry.codec, data_channel_counts); |
| dest_pacs = pac_builder.Get(); |
| } |
| num_of_ase.get(direction) /= data_size; |
| } |
| |
| /* Make sure configuration can satisfy number of expected active ASEs*/ |
| if (num_of_ase.sink > |
| data[i].device->GetAseCount(kLeAudioDirectionSink)) { |
| interesting_configuration = false; |
| } |
| |
| if (num_of_ase.source > |
| data[i].device->GetAseCount(kLeAudioDirectionSource)) { |
| interesting_configuration = false; |
| } |
| } |
| |
| BidirectionalPair<AudioContexts> group_audio_locations = { |
| .sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)}; |
| |
| /* Stimulate update of available context map */ |
| group_->UpdateAudioContextAvailability(); |
| group_->UpdateAudioSetConfigurationCache(context_type); |
| |
| auto configuration_result = |
| group_->Configure(context_type, group_audio_locations); |
| |
| /* In case of configuration #ase is same as the one we expected to be |
| * activated verify, ASEs are actually active */ |
| if (interesting_configuration && |
| (directions_to_verify == configuration_directions)) { |
| ASSERT_TRUE(configuration_result); |
| |
| bool matching_conf = true; |
| /* Check if each of the devices has activated ASEs as expected */ |
| for (int i = 0; i < data_size; i++) { |
| matching_conf &= TestGroupAseConfigurationVerdict( |
| data[i], configuration_directions); |
| } |
| |
| if (matching_conf) num_of_matching_configurations++; |
| } |
| group_->Deactivate(); |
| |
| TestAsesInactive(); |
| } |
| |
| if (success_expected) { |
| ASSERT_TRUE((num_of_matching_configurations > 0)); |
| } else { |
| ASSERT_TRUE(num_of_matching_configurations == 0); |
| } |
| } |
| |
| void TestAsesActive(LeAudioCodecId codec_id, uint8_t sampling_frequency, |
| uint8_t frame_duration, uint16_t octets_per_frame) { |
| bool active_ase = false; |
| |
| for (const auto& device : devices_) { |
| for (const auto& ase : device->ases_) { |
| if (!ase.active) continue; |
| |
| /* Configure may request only partial ases to be activated */ |
| if (!active_ase && ase.active) active_ase = true; |
| |
| ASSERT_EQ(ase.codec_id, codec_id); |
| |
| /* FIXME: Validate other codec parameters than LC3 if any */ |
| ASSERT_EQ(ase.codec_id, LeAudioCodecIdLc3); |
| if (ase.codec_id == LeAudioCodecIdLc3) { |
| auto core_config = ase.codec_config.GetAsCoreCodecConfig(); |
| ASSERT_EQ(core_config.sampling_frequency, sampling_frequency); |
| ASSERT_EQ(core_config.frame_duration, frame_duration); |
| ASSERT_EQ(core_config.octets_per_codec_frame, octets_per_frame); |
| } |
| } |
| } |
| |
| ASSERT_TRUE(active_ase); |
| } |
| |
| void TestActiveAses(void) { |
| for (auto& device : devices_) { |
| for (const auto& ase : device->ases_) { |
| if (ase.active) { |
| ASSERT_FALSE(ase.cis_id == ::bluetooth::le_audio::kInvalidCisId); |
| } |
| } |
| } |
| } |
| |
| void TestAsesInactivated(const LeAudioDevice* device) { |
| for (const auto& ase : device->ases_) { |
| ASSERT_FALSE(ase.active); |
| ASSERT_TRUE(ase.cis_id == ::bluetooth::le_audio::kInvalidCisId); |
| ASSERT_TRUE(ase.cis_conn_hdl == 0); |
| } |
| } |
| |
| void TestAsesInactive() { |
| for (const auto& device : devices_) { |
| for (const auto& ase : device->ases_) { |
| ASSERT_FALSE(ase.active); |
| } |
| } |
| } |
| |
| void TestLc3CodecConfig(LeAudioContextType context_type) { |
| for (int i = Lc3SettingIdBegin; i < Lc3SettingIdEnd; i++) { |
| // test each configuration parameter against valid and invalid value |
| std::array<Lc3SettingId, 2> test_variants = {static_cast<Lc3SettingId>(i), |
| Lc3SettingId::UNSUPPORTED}; |
| |
| const bool is_lc3_setting_supported = |
| IsLc3SettingSupported(context_type, static_cast<Lc3SettingId>(i)); |
| |
| for (const auto sf_variant : test_variants) { |
| uint8_t sampling_frequency = GetSamplingFrequency(sf_variant); |
| for (const auto fd_variant : test_variants) { |
| uint8_t frame_duration = GetFrameDuration(fd_variant); |
| for (const auto opcf_variant : test_variants) { |
| uint16_t octets_per_frame = GetOctetsPerCodecFrame(opcf_variant); |
| |
| PublishedAudioCapabilitiesBuilder pac_builder; |
| pac_builder.Add(LeAudioCodecIdLc3, sampling_frequency, |
| frame_duration, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| octets_per_frame); |
| for (auto& device : devices_) { |
| /* For simplicity configure both PACs with the same |
| parameters*/ |
| device->snk_pacs_ = pac_builder.Get(); |
| device->src_pacs_ = pac_builder.Get(); |
| } |
| |
| bool success_expected = is_lc3_setting_supported; |
| if (is_lc3_setting_supported && |
| (sf_variant == Lc3SettingId::UNSUPPORTED || |
| fd_variant == Lc3SettingId::UNSUPPORTED || |
| opcf_variant == Lc3SettingId::UNSUPPORTED)) { |
| success_expected = false; |
| } |
| |
| /* Stimulate update of available context map */ |
| group_->UpdateAudioContextAvailability(); |
| group_->UpdateAudioSetConfigurationCache(context_type); |
| BidirectionalPair<AudioContexts> group_audio_locations = { |
| .sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)}; |
| ASSERT_EQ(success_expected, |
| group_->Configure(context_type, group_audio_locations)); |
| if (success_expected) { |
| TestAsesActive(LeAudioCodecIdLc3, sampling_frequency, |
| frame_duration, octets_per_frame); |
| group_->Deactivate(); |
| } |
| |
| TestAsesInactive(); |
| } |
| } |
| } |
| } |
| } |
| |
| void TestSingleDevDualBidir(LeAudioDevice* device, |
| LeAudioContextType context_type) { |
| // Build PACs for device |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| snk_pac_builder.Reset(); |
| src_pac_builder.Reset(); |
| |
| const uint32_t supported_octets_per_codec_frame_80 = 80; |
| const uint32_t supported_octets_per_codec_frame_40 = 40; |
| const uint32_t supported_codec_frames_per_sdu = 1; |
| CodecConfigSetting swb = { |
| .id = LeAudioCodecIdLc3, |
| .params = LeAudioLtvMap({ |
| {codec_spec_conf::kLeAudioLtvTypeSamplingFreq, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioSamplingFreq32000Hz)}, |
| {codec_spec_conf::kLeAudioLtvTypeFrameDuration, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioCodecFrameDur10000us)}, |
| {codec_spec_conf::kLeAudioLtvTypeOctetsPerCodecFrame, |
| UINT16_TO_VEC_UINT8(supported_octets_per_codec_frame_80)}, |
| {codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, |
| UINT8_TO_VEC_UINT8(supported_codec_frames_per_sdu)}, |
| }), |
| .channel_count_per_iso_stream = 1}; |
| |
| auto swb_config = AudioSetConfiguration({ |
| .name = "Two-OneChan-SnkAse-Lc3_32_2-Two-OneChan-SrcAse-Lc3_32_2_SWB", |
| .confs = {.sink = {AseConfiguration(swb), AseConfiguration(swb)}, |
| .source = {AseConfiguration(swb), AseConfiguration(swb)}}, |
| }); |
| |
| auto swb_config_single = AudioSetConfiguration({ |
| .name = "One-OneChan-SnkAse-Lc3_32_2-One-OneChan-SrcAse-Lc3_32_2_SWB", |
| .confs = {.sink = |
| { |
| AseConfiguration(swb), |
| }, |
| .source = |
| { |
| AseConfiguration(swb), |
| }}, |
| }); |
| |
| ASSERT_FALSE(swb.params.IsEmpty()); |
| ASSERT_TRUE(swb.params.Find(codec_spec_conf::kLeAudioLtvTypeSamplingFreq) |
| .has_value()); |
| |
| CodecConfigSetting non_swb = { |
| .id = LeAudioCodecIdLc3, |
| .params = LeAudioLtvMap({ |
| {codec_spec_conf::kLeAudioLtvTypeSamplingFreq, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioSamplingFreq16000Hz)}, |
| {codec_spec_conf::kLeAudioLtvTypeFrameDuration, |
| UINT8_TO_VEC_UINT8(codec_spec_conf::kLeAudioCodecFrameDur10000us)}, |
| {codec_spec_conf::kLeAudioLtvTypeOctetsPerCodecFrame, |
| UINT16_TO_VEC_UINT8(supported_octets_per_codec_frame_40)}, |
| {codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, |
| UINT8_TO_VEC_UINT8(supported_codec_frames_per_sdu)}, |
| }), |
| .channel_count_per_iso_stream = 1}; |
| auto non_swb_config = AudioSetConfiguration({ |
| .name = |
| "Two-OneChan-SnkAse-Lc3_16_2-Two-OneChan-SrcAse-Lc3_16_2_NON_SWB", |
| .confs = {.sink = {AseConfiguration(non_swb), |
| AseConfiguration(non_swb)}, |
| .source = {AseConfiguration(non_swb), |
| AseConfiguration(non_swb)}}, |
| }); |
| auto non_swb_config_single = AudioSetConfiguration({ |
| .name = |
| "One-OneChan-SnkAse-Lc3_16_2-One-OneChan-SrcAse-Lc3_16_2_NON_SWB", |
| .confs = {.sink = {AseConfiguration(non_swb)}, |
| .source = {AseConfiguration(non_swb)}}, |
| }); |
| AudioSetConfigurations configs = {{&swb_config, &swb_config_single, |
| &non_swb_config, |
| &non_swb_config_single}}; |
| |
| // Support single channel per ASE to activate two ASES on both direction |
| for (auto config : configs) { |
| for (const auto& entry : config->confs.sink) { |
| snk_pac_builder.Add(entry.codec, |
| kLeAudioCodecChannelCountSingleChannel); |
| } |
| for (const auto& entry : config->confs.source) { |
| src_pac_builder.Add(entry.codec, |
| kLeAudioCodecChannelCountSingleChannel); |
| } |
| } |
| |
| // Inject `configs` as there's no such config in the json file |
| ON_CALL(*mock_codec_manager_, GetCodecConfig) |
| .WillByDefault(Invoke( |
| [&configs]( |
| const bluetooth::le_audio::CodecManager:: |
| UnicastConfigurationRequirements& requirements, |
| bluetooth::le_audio::CodecManager::UnicastConfigurationVerifier |
| verifier) { |
| auto filtered = configs; |
| // Filter out the dual bidir SWB configurations |
| if (!bluetooth::le_audio::CodecManager::GetInstance() |
| ->IsDualBiDirSwbSupported()) { |
| filtered.erase( |
| std::remove_if(filtered.begin(), filtered.end(), |
| [](auto const& el) { |
| if (el->confs.source.empty()) return false; |
| return AudioSetConfigurationProvider::Get() |
| ->CheckConfigurationIsDualBiDirSwb( |
| *el); |
| }), |
| filtered.end()); |
| } |
| auto cfg = verifier(requirements, &filtered); |
| if (cfg == nullptr) { |
| return std::unique_ptr<AudioSetConfiguration>(nullptr); |
| } |
| return std::make_unique<AudioSetConfiguration>(*cfg); |
| })); |
| |
| // Make two ASES available in both directions with equal capabilities |
| device->snk_pacs_ = snk_pac_builder.Get(); |
| device->src_pacs_ = src_pac_builder.Get(); |
| |
| ASSERT_TRUE(group_->Configure(context_type, |
| {.sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)})); |
| |
| // Verify Dual-Bidir - the amount of ASES configured |
| TestGroupAseConfigurationData data[] = { |
| {device, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, 2}}; |
| TestGroupAseConfigurationVerdict( |
| data[0], kLeAudioDirectionSink | kLeAudioDirectionSource); |
| } |
| |
| /* Helper */ |
| static const AudioSetConfiguration* getSpecificConfiguration( |
| const char* config_name, LeAudioContextType context) { |
| auto all_configurations = |
| ::bluetooth::le_audio::AudioSetConfigurationProvider::Get() |
| ->GetConfigurations(context); |
| |
| if (all_configurations == nullptr) return nullptr; |
| if (all_configurations->end() == all_configurations->begin()) |
| return nullptr; |
| |
| auto iter = |
| std::find_if(all_configurations->begin(), all_configurations->end(), |
| [config_name](auto& configuration) { |
| return configuration->name == config_name; |
| }); |
| if (iter == all_configurations->end()) return nullptr; |
| return *iter; |
| } |
| |
| void TestDualDevDualBidir(LeAudioDevice* left, LeAudioDevice* right, |
| LeAudioContextType context_type) { |
| // Build PACs for device |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| snk_pac_builder.Reset(); |
| src_pac_builder.Reset(); |
| |
| /* Create PACs for conversational scenario, SWB and non SWB */ |
| for (auto config : |
| {getSpecificConfiguration( |
| "Two-OneChan-SnkAse-Lc3_16_2-Two-OneChan-SrcAse-Lc3_16_2_1", |
| context_type), |
| getSpecificConfiguration( |
| "Two-OneChan-SnkAse-Lc3_32_2-Two-OneChan-SrcAse-Lc3_32_2_1", |
| context_type)}) { |
| ASSERT_NE(nullptr, config); |
| for (const auto& entry : (*config).confs.sink) { |
| snk_pac_builder.Add(entry.codec, |
| kLeAudioCodecChannelCountSingleChannel); |
| } |
| for (const auto& entry : (*config).confs.source) { |
| src_pac_builder.Add(entry.codec, |
| kLeAudioCodecChannelCountSingleChannel); |
| } |
| } |
| |
| // Add pacs for remote to support the configs above |
| for (auto& dev : {left, right}) { |
| dev->snk_pacs_ = snk_pac_builder.Get(); |
| dev->src_pacs_ = src_pac_builder.Get(); |
| } |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| ASSERT_TRUE(group_->Configure(context_type, |
| {.sink = AudioContexts(context_type), |
| .source = AudioContexts(context_type)})); |
| |
| // Verify the amount of ASES configured |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}}; |
| TestGroupAseConfigurationVerdict( |
| data[0], kLeAudioDirectionSink | kLeAudioDirectionSource); |
| TestGroupAseConfigurationVerdict( |
| data[1], kLeAudioDirectionSink | kLeAudioDirectionSource); |
| } |
| |
| void SetAsesToCachedConfiguration(LeAudioDevice* device, |
| LeAudioContextType context_type, |
| uint8_t directions) { |
| for (struct ase& ase : device->ases_) { |
| if (ase.direction & directions) { |
| ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED; |
| ase.active = false; |
| ase.configured_for_context_type = context_type; |
| } |
| } |
| } |
| |
| const int group_id_ = 6; |
| int desired_group_size_ = -1; |
| |
| std::vector<std::shared_ptr<LeAudioDevice>> devices_; |
| std::vector<RawAddress> addresses_; |
| LeAudioDeviceGroup* group_ = nullptr; |
| bluetooth::manager::MockBtmInterface btm_interface_; |
| MockCsisClient mock_csis_client_module_; |
| NiceMock<bluetooth::hci::testing::MockControllerInterface> |
| controller_interface_; |
| |
| bluetooth::le_audio::CodecManager* codec_manager_; |
| MockCodecManager* mock_codec_manager_; |
| }; |
| |
| TEST_P(LeAudioAseConfigurationTest, test_context_update) { |
| LeAudioDevice* left = AddTestDevice(1, 1); |
| LeAudioDevice* right = AddTestDevice(1, 1); |
| ASSERT_EQ(2, group_->Size()); |
| |
| /* Change locations */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| /* Put the PACS */ |
| auto conversational_configuration = getSpecificConfiguration( |
| "Two-OneChan-SnkAse-Lc3_16_2-One-OneChan-SrcAse-Lc3_16_2_Low_Latency", |
| LeAudioContextType::CONVERSATIONAL); |
| auto media_configuration = |
| getSpecificConfiguration("One-TwoChan-SnkAse-Lc3_48_4_High_Reliability", |
| LeAudioContextType::MEDIA); |
| ASSERT_NE(nullptr, conversational_configuration); |
| ASSERT_NE(nullptr, media_configuration); |
| |
| /* Create PACs for conversational and media scenarios */ |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| for (auto const& cfg : {conversational_configuration, media_configuration}) { |
| for (const auto& entry : cfg->confs.sink) { |
| snk_pac_builder.Add(entry.codec, 1); |
| } |
| for (const auto& entry : cfg->confs.source) { |
| src_pac_builder.Add(entry.codec, 1); |
| } |
| } |
| left->snk_pacs_ = snk_pac_builder.Get(); |
| left->src_pacs_ = src_pac_builder.Get(); |
| right->snk_pacs_ = snk_pac_builder.Get(); |
| right->src_pacs_ = src_pac_builder.Get(); |
| |
| /* UNSPECIFIED must be supported, MEDIA is on the remote sink only... */ |
| auto remote_snk_supp_contexts = AudioContexts( |
| LeAudioContextType::MEDIA | LeAudioContextType::CONVERSATIONAL | |
| LeAudioContextType::SOUNDEFFECTS | LeAudioContextType::UNSPECIFIED); |
| auto remote_src_supp_contexts = AudioContexts( |
| LeAudioContextType::CONVERSATIONAL | LeAudioContextType::UNSPECIFIED); |
| |
| left->SetSupportedContexts( |
| {.sink = remote_snk_supp_contexts, .source = remote_src_supp_contexts}); |
| |
| auto right_bud_only_context = LeAudioContextType::ALERTS; |
| right->SetSupportedContexts( |
| {.sink = remote_snk_supp_contexts | right_bud_only_context, |
| .source = remote_src_supp_contexts | right_bud_only_context}); |
| |
| /* ...but UNSPECIFIED and SOUNDEFFECTS are unavailable */ |
| auto remote_snk_avail_contexts = AudioContexts( |
| LeAudioContextType::MEDIA | LeAudioContextType::CONVERSATIONAL); |
| auto remote_src_avail_contexts = |
| AudioContexts(LeAudioContextType::CONVERSATIONAL); |
| |
| left->SetAvailableContexts( |
| {.sink = remote_snk_avail_contexts, .source = remote_src_avail_contexts}); |
| ASSERT_EQ(left->GetAvailableContexts(), |
| remote_snk_avail_contexts | remote_src_avail_contexts); |
| |
| // Make an additional context available on the right earbud sink |
| right->SetAvailableContexts( |
| {.sink = remote_snk_avail_contexts | right_bud_only_context, |
| .source = remote_src_avail_contexts}); |
| ASSERT_EQ(right->GetAvailableContexts(), remote_snk_avail_contexts | |
| remote_src_avail_contexts | |
| right_bud_only_context); |
| |
| /* Now add the right earbud contexts - mind the extra context on that bud */ |
| group_->UpdateAudioContextAvailability(); |
| ASSERT_NE(group_->GetAvailableContexts(), left->GetAvailableContexts()); |
| ASSERT_EQ(group_->GetAvailableContexts(), |
| left->GetAvailableContexts() | right->GetAvailableContexts()); |
| |
| /* Since no device is being added or removed from the group this should not |
| * change the configuration set. |
| */ |
| group_->UpdateAudioContextAvailability(); |
| ASSERT_EQ(group_->GetAvailableContexts(), |
| left->GetAvailableContexts() | right->GetAvailableContexts()); |
| |
| /* MEDIA Available on remote sink direction only */ |
| auto config = group_->GetConfiguration(LeAudioContextType::MEDIA); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_FALSE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| ASSERT_EQ( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .at(0) |
| .codec.GetChannelCountPerIsoStream(), |
| ::bluetooth::le_audio::LeAudioCodecConfiguration::kChannelNumberMono); |
| |
| /* CONVERSATIONAL Available on both directions */ |
| config = group_->GetConfiguration(LeAudioContextType::CONVERSATIONAL); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| |
| /* UNSPECIFIED Unavailable yet supported */ |
| config = group_->GetConfiguration(LeAudioContextType::UNSPECIFIED); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_FALSE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| |
| /* SOUNDEFFECTS Unavailable yet supported on sink only */ |
| config = group_->GetConfiguration(LeAudioContextType::SOUNDEFFECTS); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_FALSE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| |
| /* INSTRUCTIONAL Unavailable and not supported, while UNSPECIFIED not |
| * available */ |
| config = group_->GetConfiguration(LeAudioContextType::INSTRUCTIONAL); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_FALSE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| |
| /* ALERTS on sink only */ |
| config = group_->GetConfiguration(LeAudioContextType::ALERTS); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_FALSE( |
| config->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSource) |
| .size()); |
| |
| /* We should get the config for ALERTS for both channels as the other has |
| * UNSPECIFIED context supported. |
| */ |
| auto sink_configs = |
| group_->GetConfiguration(LeAudioContextType::ALERTS) |
| ->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink); |
| ASSERT_EQ(2lu, sink_configs.size()); |
| ASSERT_TRUE( |
| group_->IsAudioSetConfigurationAvailable(LeAudioContextType::ALERTS)); |
| |
| /* Turn off the ALERTS context */ |
| right->SetAvailableContexts( |
| {.sink = right->GetAvailableContexts( |
| ::bluetooth::le_audio::types::kLeAudioDirectionSink) & |
| ~AudioContexts(LeAudioContextType::ALERTS), |
| .source = right->GetAvailableContexts( |
| ::bluetooth::le_audio::types::kLeAudioDirectionSource)}); |
| |
| /* Right one was changed but the config exist, just not available */ |
| group_->UpdateAudioContextAvailability(); |
| ASSERT_EQ(group_->GetAvailableContexts(), |
| left->GetAvailableContexts() | right->GetAvailableContexts()); |
| ASSERT_FALSE(group_->GetAvailableContexts().test(LeAudioContextType::ALERTS)); |
| ASSERT_TRUE(group_->GetConfiguration(LeAudioContextType::ALERTS) |
| ->confs.get(bluetooth::le_audio::types::kLeAudioDirectionSink) |
| .size()); |
| ASSERT_TRUE( |
| group_->IsAudioSetConfigurationAvailable(LeAudioContextType::ALERTS)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_mono_speaker_ringtone) { |
| LeAudioDevice* mono_speaker = AddTestDevice(1, 0); |
| TestGroupAseConfigurationData data( |
| {mono_speaker, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}); |
| |
| /* mono, change location as by default it is stereo */ |
| mono_speaker->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_mono_speaker_conversational) { |
| LeAudioDevice* mono_speaker = AddTestDevice(1, 0); |
| TestGroupAseConfigurationData data({mono_speaker, |
| kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountNone, 1, 0}); |
| |
| /* mono, change location as by default it is stereo */ |
| mono_speaker->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| /* Microphone should be used on the phone */ |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_mono_speaker_media) { |
| LeAudioDevice* mono_speaker = AddTestDevice(1, 0); |
| TestGroupAseConfigurationData data({mono_speaker, |
| kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountNone, 1, 0}); |
| |
| /* mono, change location as by default it is stereo */ |
| mono_speaker->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_banded_headphones_ringtone) { |
| LeAudioDevice* banded_headphones = AddTestDevice(2, 0); |
| TestGroupAseConfigurationData data( |
| {banded_headphones, kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, 0}); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_banded_headphones_conversational) { |
| LeAudioDevice* banded_headphones = AddTestDevice(2, 0); |
| TestGroupAseConfigurationData data({banded_headphones, |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountNone, 2, 0}); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_banded_headphones_media) { |
| LeAudioDevice* banded_headphones = AddTestDevice(2, 0); |
| TestGroupAseConfigurationData data({banded_headphones, |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountNone, 2, 0}); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1, |
| direction_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_banded_headset_ringtone_mono_microphone) { |
| LeAudioDevice* banded_headset = AddTestDevice( |
| 2, 1, 0, 0, false, false, codec_spec_conf::kLeAudioLocationStereo, |
| codec_spec_conf::kLeAudioLocationFrontLeft); |
| TestGroupAseConfigurationData data( |
| {banded_headset, kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, 1}); |
| |
| /* mono, change location as by default it is stereo */ |
| banded_headset->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_banded_headset_ringtone_stereo_microphone) { |
| LeAudioDevice* banded_headset = AddTestDevice(2, 2); |
| TestGroupAseConfigurationData data({banded_headset, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| 2, 2}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_earbuds_conversational_stereo_microphone_no_swb) { |
| // Turn off the dual bidir SWB support |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(false)); |
| ASSERT_FALSE(CodecManager::GetInstance()->IsDualBiDirSwbSupported()); |
| |
| const auto context_type = LeAudioContextType::CONVERSATIONAL; |
| TestDualDevDualBidir(AddTestDevice(1, 1), AddTestDevice(1, 1), context_type); |
| |
| // Verify non-SWB config was selected |
| auto config = group_->GetCachedConfiguration(context_type).get(); |
| ASSERT_NE(nullptr, config); |
| ASSERT_FALSE( |
| CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_earbuds_conversational_stereo_microphone_no_swb_one_bonded) { |
| /* There will be 2 eabuds eventually but for the moment only 1 is bonded |
| * Turn off the dual bidir SWB support |
| */ |
| desired_group_size_ = 2; |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(false)); |
| ASSERT_FALSE(CodecManager::GetInstance()->IsDualBiDirSwbSupported()); |
| |
| const auto context_type = LeAudioContextType::CONVERSATIONAL; |
| TestSingleDevDualBidir( |
| AddTestDevice(1, 1, 0, 0, false, false, |
| codec_spec_conf::kLeAudioLocationFrontLeft, |
| codec_spec_conf::kLeAudioLocationFrontLeft), |
| context_type); |
| |
| // Verify non-SWB config was selected |
| auto config = group_->GetCachedConfiguration(context_type).get(); |
| ASSERT_NE(nullptr, config); |
| ASSERT_FALSE( |
| CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*config)); |
| ASSERT_FALSE( |
| CodecManager::GetInstance()->CheckCodecConfigIsBiDirSwb(*config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_earbuds_conversational_stereo_microphone_swb) { |
| // Turn on the dual bidir SWB support |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(true)); |
| ASSERT_TRUE(CodecManager::GetInstance()->IsDualBiDirSwbSupported()); |
| |
| const auto context_type = LeAudioContextType::CONVERSATIONAL; |
| TestDualDevDualBidir(AddTestDevice(1, 1), AddTestDevice(1, 1), context_type); |
| |
| // Verify SWB config was selected |
| auto config = group_->GetCachedConfiguration(context_type).get(); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_banded_headset_ringtone_stereo_microphone_no_swb) { |
| // Turn off the dual bidir SWB support |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(false)); |
| ASSERT_FALSE(CodecManager::GetInstance()->IsDualBiDirSwbSupported()); |
| |
| // Verify non-SWB config was selected |
| auto context_type = LeAudioContextType::CONVERSATIONAL; |
| TestSingleDevDualBidir(AddTestDevice(2, 2), context_type); |
| auto config = group_->GetCachedConfiguration(context_type).get(); |
| ASSERT_NE(nullptr, config); |
| ASSERT_FALSE( |
| CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_banded_headset_ringtone_stereo_microphone_swb) { |
| // Turn on the dual bidir SWB support |
| ON_CALL(*mock_codec_manager_, IsDualBiDirSwbSupported) |
| .WillByDefault(Return(true)); |
| ASSERT_TRUE(CodecManager::GetInstance()->IsDualBiDirSwbSupported()); |
| |
| // Verify SWB config was selected |
| auto context_type = LeAudioContextType::CONVERSATIONAL; |
| TestSingleDevDualBidir(AddTestDevice(2, 2), context_type); |
| auto config = group_->GetCachedConfiguration(context_type).get(); |
| ASSERT_NE(nullptr, config); |
| ASSERT_TRUE( |
| CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_banded_headset_conversational) { |
| LeAudioDevice* banded_headset = AddTestDevice(2, 1); |
| TestGroupAseConfigurationData data( |
| {banded_headset, kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, 1}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_banded_headset_media) { |
| LeAudioDevice* banded_headset = AddTestDevice(2, 1); |
| TestGroupAseConfigurationData data( |
| {banded_headset, kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, 0}); |
| |
| uint8_t directions_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1, |
| directions_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_earbuds_ringtone) { |
| LeAudioDevice* left = AddTestDevice(1, 1); |
| LeAudioDevice* right = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}}; |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, data, 2); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_earbuds_conversational) { |
| LeAudioDevice* left = AddTestDevice(1, 1); |
| LeAudioDevice* right = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}}; |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, data, 2); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_earbuds_media) { |
| LeAudioDevice* left = AddTestDevice(1, 1); |
| LeAudioDevice* right = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}}; |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| uint8_t directions_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, data, 2, |
| directions_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_handsfree_mono_ringtone) { |
| LeAudioDevice* handsfree = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data( |
| {handsfree, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}); |
| |
| handsfree->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| handsfree->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_handsfree_stereo_ringtone) { |
| LeAudioDevice* handsfree = |
| AddTestDevice(1, 1, 0, 0, false, false, |
| codec_spec_conf::kLeAudioLocationFrontLeft | |
| codec_spec_conf::kLeAudioLocationFrontRight, |
| codec_spec_conf::kLeAudioLocationFrontLeft); |
| TestGroupAseConfigurationData data({handsfree, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, |
| 1}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_handsfree_mono_conversational) { |
| LeAudioDevice* handsfree = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data( |
| {handsfree, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 1}); |
| |
| handsfree->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| handsfree->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_handsfree_stereo_conversational) { |
| LeAudioDevice* handsfree = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data({handsfree, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, |
| 1}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_handsfree_full_cached_conversational) { |
| LeAudioDevice* handsfree = AddTestDevice(0, 0, 1, 1); |
| TestGroupAseConfigurationData data({handsfree, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, |
| 1}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_handsfree_partial_cached_conversational) { |
| LeAudioDevice* handsfree = AddTestDevice(1, 0, 0, 1); |
| TestGroupAseConfigurationData data({handsfree, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, |
| 1}); |
| |
| TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, &data, 1); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_handsfree_media_two_channels_allocation_stereo) { |
| LeAudioDevice* handsfree = AddTestDevice(1, 1); |
| TestGroupAseConfigurationData data({handsfree, |
| kLeAudioCodecChannelCountSingleChannel | |
| kLeAudioCodecChannelCountTwoChannel, |
| kLeAudioCodecChannelCountSingleChannel, 2, |
| 0}); |
| |
| uint8_t directions_to_verify = kLeAudioDirectionSink; |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, &data, 1, |
| directions_to_verify); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_lc3_config_ringtone) { |
| if (codec_coding_format_ != kLeAudioCodingFormatLC3) GTEST_SKIP(); |
| |
| AddTestDevice(1, 1); |
| |
| TestLc3CodecConfig(LeAudioContextType::RINGTONE); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_lc3_config_conversational) { |
| if (codec_coding_format_ != kLeAudioCodingFormatLC3) GTEST_SKIP(); |
| |
| AddTestDevice(1, 1); |
| |
| TestLc3CodecConfig(LeAudioContextType::CONVERSATIONAL); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_lc3_config_media) { |
| if (codec_coding_format_ != kLeAudioCodingFormatLC3) GTEST_SKIP(); |
| |
| AddTestDevice(1, 1); |
| |
| TestLc3CodecConfig(LeAudioContextType::MEDIA); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_unsupported_codec) { |
| if (codec_coding_format_ == kLeAudioCodingFormatVendorSpecific) GTEST_SKIP(); |
| |
| const LeAudioCodecId UnsupportedCodecId = { |
| .coding_format = kLeAudioCodingFormatVendorSpecific, |
| .vendor_company_id = 0xBAD, |
| .vendor_codec_id = 0xC0DE, |
| }; |
| |
| LeAudioDevice* device = AddTestDevice(1, 0); |
| |
| PublishedAudioCapabilitiesBuilder pac_builder; |
| pac_builder.Add(UnsupportedCodecId, |
| GetSamplingFrequency(Lc3SettingId::LC3_16_2), |
| GetFrameDuration(Lc3SettingId::LC3_16_2), |
| kLeAudioCodecChannelCountSingleChannel, |
| GetOctetsPerCodecFrame(Lc3SettingId::LC3_16_2)); |
| device->snk_pacs_ = pac_builder.Get(); |
| device->src_pacs_ = pac_builder.Get(); |
| |
| ASSERT_FALSE( |
| group_->Configure(LeAudioContextType::RINGTONE, |
| {AudioContexts(LeAudioContextType::RINGTONE), |
| AudioContexts(LeAudioContextType::RINGTONE)})); |
| TestAsesInactive(); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_reconnection_media) { |
| LeAudioDevice* left = AddTestDevice(2, 1); |
| LeAudioDevice* right = AddTestDevice(2, 1); |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}}; |
| |
| auto all_configurations = |
| ::bluetooth::le_audio::AudioSetConfigurationProvider::Get() |
| ->GetConfigurations(LeAudioContextType::MEDIA); |
| ASSERT_NE(nullptr, all_configurations); |
| ASSERT_NE(all_configurations->end(), all_configurations->begin()); |
| auto configuration = *all_configurations->begin(); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| TestSingleAseConfiguration(LeAudioContextType::MEDIA, data, 2, configuration, |
| direction_to_verify); |
| |
| // Get the proper configuration for the group |
| configuration = group_->GetConfiguration(LeAudioContextType::MEDIA).get(); |
| |
| /* Generate CISes, symulate CIG creation and assign cis handles to ASEs.*/ |
| group_->cig.GenerateCisIds(LeAudioContextType::MEDIA); |
| std::vector<uint16_t> handles = {0x0012, 0x0013}; |
| group_->cig.AssignCisConnHandles(handles); |
| group_->cig.AssignCisIds(left); |
| group_->cig.AssignCisIds(right); |
| |
| TestActiveAses(); |
| /* Left got disconnected */ |
| left->DeactivateAllAses(); |
| |
| /* Unassign from the group*/ |
| group_->cig.UnassignCis(left); |
| |
| TestAsesInactivated(left); |
| |
| /* Prepare reconfiguration */ |
| uint8_t number_of_active_ases = 1; // Right one |
| auto* ase = right->GetFirstActiveAseByDirection(kLeAudioDirectionSink); |
| ASSERT_NE(nullptr, ase); |
| |
| auto core_config = ase->codec_config.GetAsCoreCodecConfig(); |
| BidirectionalPair<AudioLocations> group_audio_locations = { |
| .sink = *core_config.audio_channel_allocation, |
| .source = *core_config.audio_channel_allocation}; |
| |
| /* Get entry for the sink direction and use it to set configuration */ |
| BidirectionalPair<std::vector<uint8_t>> ccid_lists = {{}, {}}; |
| BidirectionalPair<AudioContexts> audio_contexts = {AudioContexts(), |
| AudioContexts()}; |
| if (!configuration->confs.sink.empty()) { |
| left->ConfigureAses(configuration, group_->Size(), kLeAudioDirectionSink, |
| group_->GetConfigurationContextType(), |
| &number_of_active_ases, |
| group_audio_locations.get(kLeAudioDirectionSink), |
| audio_contexts.get(kLeAudioDirectionSink), |
| ccid_lists.get(kLeAudioDirectionSink), false); |
| } |
| if (!configuration->confs.source.empty()) { |
| left->ConfigureAses(configuration, group_->Size(), kLeAudioDirectionSource, |
| group_->GetConfigurationContextType(), |
| &number_of_active_ases, |
| group_audio_locations.get(kLeAudioDirectionSource), |
| audio_contexts.get(kLeAudioDirectionSource), |
| ccid_lists.get(kLeAudioDirectionSource), false); |
| } |
| |
| ASSERT_EQ(number_of_active_ases, 2); |
| ASSERT_EQ(group_audio_locations.sink, kChannelAllocationStereo); |
| |
| uint8_t directions_to_verify = |
| ::bluetooth::le_audio::types::kLeAudioDirectionSink; |
| for (int i = 0; i < 2; i++) { |
| TestGroupAseConfigurationVerdict(data[i], directions_to_verify); |
| } |
| |
| /* Before device is rejoining, and group already exist, cis handles are |
| * assigned before sending codec config |
| */ |
| group_->cig.AssignCisIds(left); |
| group_->AssignCisConnHandlesToAses(left); |
| |
| TestActiveAses(); |
| } |
| |
| /* |
| * Failure happens when restarting conversational scenario and when |
| * remote device uses caching. |
| * |
| * Failing scenario. |
| * 1. Conversational scenario set up with |
| * - ASE 1 and ASE 5 using bidirectional CIS 0 |
| * - ASE 2 being unidirectional on CIS 1 |
| * 2. Stop stream and go to CONFIGURED STATE. |
| * 3. Trying to configure ASES again would end up in incorrectly assigned |
| * CISes |
| * - ASE 1 and ASE 5 set to CIS 0 |
| * - ASE 2 stay on CIS 1 but ASE 5 got reassigned to CIS 1 (error) |
| * |
| * The problem is finding matching_bidir_ase which shall not be just next |
| * active ase with different direction, but it shall be also available (Cis |
| * not assigned) or assigned to the same CIS ID as the opposite direction. |
| */ |
| TEST_P(LeAudioAseConfigurationTest, test_reactivation_conversational) { |
| LeAudioDevice* tws_headset = AddTestDevice(0, 0, 2, 1, true); |
| |
| /* Change location as by default it is stereo */ |
| tws_headset->snk_audio_locations_ = kChannelAllocationStereo; |
| tws_headset->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| group_->ReloadAudioLocations(); |
| |
| auto conversational_configuration = getSpecificConfiguration( |
| "Two-OneChan-SnkAse-Lc3_16_2-One-OneChan-SrcAse-Lc3_16_2_Low_Latency", |
| LeAudioContextType::CONVERSATIONAL); |
| ASSERT_NE(nullptr, conversational_configuration); |
| |
| // Build PACs for device |
| PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder; |
| snk_pac_builder.Reset(); |
| src_pac_builder.Reset(); |
| |
| /* Create PACs for conversational scenario which covers also media. Single |
| * PAC for each direction is enough. |
| */ |
| for (const auto& entry : (*conversational_configuration).confs.sink) { |
| snk_pac_builder.Add(entry.codec, 1); |
| } |
| for (const auto& entry : (*conversational_configuration).confs.source) { |
| src_pac_builder.Add(entry.codec, 1); |
| } |
| |
| tws_headset->snk_pacs_ = snk_pac_builder.Get(); |
| tws_headset->src_pacs_ = src_pac_builder.Get(); |
| |
| ::bluetooth::le_audio::types::AudioLocations group_snk_audio_locations = 0; |
| ::bluetooth::le_audio::types::AudioLocations group_src_audio_locations = 0; |
| BidirectionalPair<uint8_t> number_of_already_active_ases = {0, 0}; |
| |
| BidirectionalPair<AudioLocations> group_audio_locations = { |
| .sink = group_snk_audio_locations, .source = group_src_audio_locations}; |
| |
| /* Get entry for the sink direction and use it to set configuration */ |
| BidirectionalPair<std::vector<uint8_t>> ccid_lists = {{}, {}}; |
| BidirectionalPair<AudioContexts> audio_contexts = {AudioContexts(), |
| AudioContexts()}; |
| |
| /* Get entry for the sink direction and use it to set configuration */ |
| if (!conversational_configuration->confs.sink.empty()) { |
| tws_headset->ConfigureAses( |
| conversational_configuration, group_->Size(), kLeAudioDirectionSink, |
| group_->GetConfigurationContextType(), |
| &number_of_already_active_ases.get(kLeAudioDirectionSink), |
| group_audio_locations.get(kLeAudioDirectionSink), |
| audio_contexts.get(kLeAudioDirectionSink), |
| ccid_lists.get(kLeAudioDirectionSink), false); |
| } |
| if (!conversational_configuration->confs.source.empty()) { |
| tws_headset->ConfigureAses( |
| conversational_configuration, group_->Size(), kLeAudioDirectionSource, |
| group_->GetConfigurationContextType(), |
| &number_of_already_active_ases.get(kLeAudioDirectionSource), |
| group_audio_locations.get(kLeAudioDirectionSource), |
| audio_contexts.get(kLeAudioDirectionSource), |
| ccid_lists.get(kLeAudioDirectionSource), false); |
| } |
| |
| /* Generate CISes, simulate CIG creation and assign cis handles to ASEs.*/ |
| std::vector<uint16_t> handles = {0x0012, 0x0013}; |
| group_->cig.GenerateCisIds(LeAudioContextType::CONVERSATIONAL); |
| group_->cig.AssignCisConnHandles(handles); |
| group_->cig.AssignCisIds(tws_headset); |
| |
| TestActiveAses(); |
| |
| /* Simulate stopping stream with caching codec configuration in ASEs */ |
| group_->cig.UnassignCis(tws_headset); |
| SetAsesToCachedConfiguration(tws_headset, LeAudioContextType::CONVERSATIONAL, |
| kLeAudioDirectionSink | kLeAudioDirectionSource); |
| |
| /* As context type is the same as previous and no changes were made in PACs |
| * the same CIS ID can be used. This would lead to only activating group |
| * without reconfiguring CIG. |
| */ |
| group_->Activate(LeAudioContextType::CONVERSATIONAL, audio_contexts, |
| ccid_lists); |
| |
| TestActiveAses(); |
| |
| /* Verify ASEs assigned CISes by counting assigned to bi-directional CISes */ |
| int bi_dir_ases_count = std::count_if( |
| tws_headset->ases_.begin(), tws_headset->ases_.end(), [this](auto& ase) { |
| return this->group_->cig.cises[ase.cis_id].type == |
| CisType::CIS_TYPE_BIDIRECTIONAL; |
| }); |
| |
| /* Only two ASEs can be bonded to one bi-directional CIS */ |
| ASSERT_EQ(bi_dir_ases_count, 2); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_num_of_connected) { |
| auto device1 = AddTestDevice(2, 1); |
| auto device2 = AddTestDevice(2, 1); |
| ASSERT_EQ(2, group_->NumOfConnected()); |
| |
| // Drop the ACL connection |
| device1->conn_id_ = GATT_INVALID_CONN_ID; |
| ASSERT_EQ(1, group_->NumOfConnected()); |
| |
| // Fully disconnect the other device |
| device2->SetConnectionState(DeviceConnectState::DISCONNECTING); |
| ASSERT_EQ(0, group_->NumOfConnected()); |
| } |
| |
| /* |
| * Failure happens when there is no matching single device scenario for dual |
| * device scanario. Stereo location for single earbud seems to be invalid but |
| * possible and stack should handle it. |
| * |
| * Failing scenario: |
| * 1. Connect two - stereo location earbuds |
| * 2. Disconnect one of earbud |
| * 3. CIS generator will look for dual device scenario with matching strategy |
| * 4. There is no dual device scenario with strategy stereo channels per device |
| */ |
| TEST_P(LeAudioAseConfigurationTest, test_getting_cis_count) { |
| /* Set desired size to 2 */ |
| desired_group_size_ = 2; |
| |
| LeAudioDevice* left = AddTestDevice(2, 1); |
| LeAudioDevice* right = AddTestDevice(0, 0, 0, 0, false, true); |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = kChannelAllocationStereo; |
| right->snk_audio_locations_ = kChannelAllocationStereo; |
| group_->ReloadAudioLocations(); |
| |
| auto media_configuration = |
| getSpecificConfiguration("One-TwoChan-SnkAse-Lc3_48_4_High_Reliability", |
| LeAudioContextType::MEDIA); |
| ASSERT_NE(nullptr, media_configuration); |
| |
| // Build PACs for device |
| PublishedAudioCapabilitiesBuilder snk_pac_builder; |
| snk_pac_builder.Reset(); |
| |
| /* Create PACs for media. Single PAC for each direction is enough. |
| */ |
| if (media_configuration->confs.sink.size()) { |
| snk_pac_builder.Add(LeAudioCodecIdLc3, 0x00b5, 0x03, 0x03, 0x001a, 0x00f0, |
| 2); |
| } |
| |
| left->snk_pacs_ = snk_pac_builder.Get(); |
| right->snk_pacs_ = snk_pac_builder.Get(); |
| |
| ::bluetooth::le_audio::types::AudioLocations group_snk_audio_locations = 3; |
| ::bluetooth::le_audio::types::AudioLocations group_src_audio_locations = 0; |
| uint8_t number_of_already_active_ases = 0; |
| |
| BidirectionalPair<AudioLocations> group_audio_locations = { |
| .sink = group_snk_audio_locations, .source = group_src_audio_locations}; |
| |
| /* Get entry for the sink direction and use it to set configuration */ |
| BidirectionalPair<std::vector<uint8_t>> ccid_lists = {{}, {}}; |
| BidirectionalPair<AudioContexts> audio_contexts = {AudioContexts(), |
| AudioContexts()}; |
| |
| /* Get entry for the sink direction and use it to set configuration */ |
| if (!media_configuration->confs.sink.empty()) { |
| left->ConfigureAses( |
| media_configuration, group_->Size(), kLeAudioDirectionSink, |
| group_->GetConfigurationContextType(), &number_of_already_active_ases, |
| group_audio_locations.get(kLeAudioDirectionSink), |
| audio_contexts.get(kLeAudioDirectionSink), |
| ccid_lists.get(kLeAudioDirectionSink), false); |
| } |
| |
| /* Generate CIS, simulate CIG creation and assign cis handles to ASEs.*/ |
| std::vector<uint16_t> handles = {0x0012}; |
| group_->cig.GenerateCisIds(LeAudioContextType::MEDIA); |
| |
| /* Verify prepared CISes by counting generated entries */ |
| int snk_cis_count = |
| std::count_if(this->group_->cig.cises.begin(), |
| this->group_->cig.cises.end(), [](auto& cis) { |
| return cis.type == CisType::CIS_TYPE_UNIDIRECTIONAL_SINK; |
| }); |
| |
| /* Two CIS should be prepared for dual dev expected set */ |
| ASSERT_EQ(snk_cis_count, 2); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, test_config_support) { |
| LeAudioDevice* left = AddTestDevice(2, 1); |
| LeAudioDevice* right = AddTestDevice(0, 0, 0, 0, false, true); |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = kChannelAllocationStereo; |
| right->snk_audio_locations_ = kChannelAllocationStereo; |
| group_->ReloadAudioLocations(); |
| |
| auto test_config = getSpecificConfiguration( |
| "One-OneChan-SnkAse-Lc3_48_4-One-OneChan-SrcAse-Lc3_16_2_Balanced_" |
| "Reliability", |
| LeAudioContextType::VOICEASSISTANTS); |
| ASSERT_NE(nullptr, test_config); |
| |
| /* Create PACs for sink */ |
| PublishedAudioCapabilitiesBuilder snk_pac_builder; |
| snk_pac_builder.Reset(); |
| for (const auto& entry : (*test_config).confs.sink) { |
| snk_pac_builder.Add(entry.codec, 1); |
| } |
| left->snk_pacs_ = snk_pac_builder.Get(); |
| right->snk_pacs_ = snk_pac_builder.Get(); |
| |
| ASSERT_FALSE(left->IsAudioSetConfigurationSupported(test_config)); |
| ASSERT_FALSE(right->IsAudioSetConfigurationSupported(test_config)); |
| |
| /* Create PACs for source */ |
| PublishedAudioCapabilitiesBuilder src_pac_builder; |
| src_pac_builder.Reset(); |
| for (const auto& entry : (*test_config).confs.source) { |
| src_pac_builder.Add(entry.codec, 1); |
| } |
| left->src_pacs_ = src_pac_builder.Get(); |
| right->src_pacs_ = src_pac_builder.Get(); |
| |
| ASSERT_TRUE(left->IsAudioSetConfigurationSupported(test_config)); |
| ASSERT_TRUE(right->IsAudioSetConfigurationSupported(test_config)); |
| } |
| |
| TEST_P(LeAudioAseConfigurationTest, |
| test_vendor_codec_configure_incomplete_group) { |
| // A group of two earbuds |
| LeAudioDevice* left = AddTestDevice(2, 1); |
| LeAudioDevice* right = AddTestDevice(2, 1); |
| |
| /* Change location as by default it is stereo */ |
| left->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| left->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft; |
| right->snk_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| right->src_audio_locations_ = |
| ::bluetooth::le_audio::codec_spec_conf::kLeAudioLocationFrontRight; |
| group_->ReloadAudioLocations(); |
| |
| // The Right earbud is currently disconnected |
| right->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| |
| uint8_t direction_to_verify = kLeAudioDirectionSink; |
| uint8_t devices_to_verify = 1; |
| TestGroupAseConfigurationData data[] = { |
| {left, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 1, 0}, |
| {right, kLeAudioCodecChannelCountSingleChannel, |
| kLeAudioCodecChannelCountSingleChannel, 0, 0}}; |
| |
| TestGroupAseConfiguration(LeAudioContextType::MEDIA, data, devices_to_verify, |
| direction_to_verify); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(Test, LeAudioAseConfigurationTest, |
| ::testing::Values(kLeAudioCodingFormatLC3, |
| kLeAudioCodingFormatVendorSpecific)); |
| |
| } // namespace |
| } // namespace internal |
| } // namespace le_audio |
| } // namespace bluetooth |