diff options
-rw-r--r-- | system/bta/le_audio/client.cc | 23 | ||||
-rw-r--r-- | system/bta/le_audio/devices_test.cc | 110 | ||||
-rw-r--r-- | system/bta/le_audio/le_audio_client_test.cc | 257 | ||||
-rw-r--r-- | system/bta/le_audio/le_audio_types.cc | 4 | ||||
-rw-r--r-- | system/bta/le_audio/mock_codec_interface.cc | 24 | ||||
-rw-r--r-- | system/bta/le_audio/mock_codec_interface.h | 4 | ||||
-rw-r--r-- | system/bta/le_audio/state_machine_test.cc | 161 |
7 files changed, 549 insertions, 34 deletions
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc index b865865544..5aa081e8a3 100644 --- a/system/bta/le_audio/client.cc +++ b/system/bta/le_audio/client.cc @@ -1160,12 +1160,19 @@ class LeAudioClientImpl : public LeAudioClient { L2CA_SetEcosystemBaseInterval(frame_duration_us / 1250); - audio_framework_source_config.data_interval_us = frame_duration_us; + // Scale by the codec frame blocks per SDU if set + uint8_t codec_frame_blocks_per_sdu = + group->stream_conf.stream_params.source.codec_frames_blocks_per_sdu + ?: 1; + audio_framework_source_config.data_interval_us = + frame_duration_us * codec_frame_blocks_per_sdu; + le_audio_source_hal_client_->Start(audio_framework_source_config, audioSinkReceiver, dsa_modes); /* We use same frame duration for sink/source */ - audio_framework_sink_config.data_interval_us = frame_duration_us; + audio_framework_sink_config.data_interval_us = + frame_duration_us * codec_frame_blocks_per_sdu; /* If group supports more than 16kHz for the microphone in converstional * case let's use that also for Audio Framework. @@ -3393,6 +3400,12 @@ class LeAudioClientImpl : public LeAudioClient { right_cis_handle = cis_handle; } + if (stream_params.codec_frames_blocks_per_sdu != 1) { + log::error( + "Codec Frame Blocks of {} is not supported by the software encoding", + +stream_params.codec_frames_blocks_per_sdu); + } + uint16_t byte_count = stream_params.octets_per_codec_frame; bool mix_to_mono = (left_cis_handle == 0) || (right_cis_handle == 0); if (mix_to_mono) { @@ -3441,6 +3454,12 @@ class LeAudioClientImpl : public LeAudioClient { return; } + if (stream_params.codec_frames_blocks_per_sdu != 1) { + log::error( + "Codec Frame Blocks of {} is not supported by the software encoding", + +stream_params.codec_frames_blocks_per_sdu); + } + uint16_t byte_count = stream_params.octets_per_codec_frame; bool mix_to_mono = (num_channels == 1); if (mix_to_mono) { diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc index 79290fc1cd..ea6f4421a1 100644 --- a/system/bta/le_audio/devices_test.cc +++ b/system/bta/le_audio/devices_test.cc @@ -718,12 +718,15 @@ class LeAudioAseConfigurationTest // 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) { + } + + ON_CALL(*mock_codec_manager_, GetCodecConfig) + .WillByDefault(Invoke( + [&](const bluetooth::le_audio::CodecManager:: + UnicastConfigurationRequirements& requirements, + bluetooth::le_audio::CodecManager::UnicastConfigurationVerifier + verifier) { + if (codec_coding_format_ == kLeAudioCodingFormatLC3) { auto filtered = *bluetooth::le_audio::AudioSetConfigurationProvider::Get() ->GetConfigurations(requirements.audio_context_type); @@ -745,18 +748,10 @@ class LeAudioAseConfigurationTest 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) { + } else { return MockVendorCodecProvider(requirements); - })); - } + } + })); ON_CALL(*mock_codec_manager_, CheckCodecConfigIsBiDirSwb) .WillByDefault(Invoke([](const bluetooth::le_audio::set_configurations:: @@ -1125,7 +1120,8 @@ class LeAudioAseConfigurationTest } void TestAsesActive(LeAudioCodecId codec_id, uint8_t sampling_frequency, - uint8_t frame_duration, uint16_t octets_per_frame) { + uint8_t frame_duration, uint16_t octets_per_frame, + uint8_t codec_frame_blocks_per_sdu = 1) { bool active_ase = false; for (const auto& device : devices_) { @@ -1144,6 +1140,8 @@ class LeAudioAseConfigurationTest 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_EQ(core_config.codec_frames_blocks_per_sdu.value_or(0), + codec_frame_blocks_per_sdu); } } } @@ -1177,7 +1175,8 @@ class LeAudioAseConfigurationTest } } - void TestLc3CodecConfig(LeAudioContextType context_type) { + void TestLc3CodecConfig(LeAudioContextType context_type, + uint8_t max_codec_frames_per_sdu = 1) { 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), @@ -1198,7 +1197,7 @@ class LeAudioAseConfigurationTest frame_duration, kLeAudioCodecChannelCountSingleChannel | kLeAudioCodecChannelCountTwoChannel, - octets_per_frame); + octets_per_frame, max_codec_frames_per_sdu); for (auto& device : devices_) { /* For simplicity configure both PACs with the same parameters*/ @@ -1224,7 +1223,8 @@ class LeAudioAseConfigurationTest group_->Configure(context_type, group_audio_locations)); if (success_expected) { TestAsesActive(LeAudioCodecIdLc3, sampling_frequency, - frame_duration, octets_per_frame); + frame_duration, octets_per_frame, + max_codec_frames_per_sdu); group_->Deactivate(); } @@ -2072,6 +2072,74 @@ TEST_P(LeAudioAseConfigurationTest, test_lc3_config_media) { TestLc3CodecConfig(LeAudioContextType::MEDIA); } +TEST_P(LeAudioAseConfigurationTest, + test_lc3_config_media_codec_extensibility_fb2) { + if (codec_coding_format_ != kLeAudioCodingFormatLC3) GTEST_SKIP(); + + bool is_fb2_passed_as_requirement = false; + auto max_codec_frames_per_sdu = 2; + + // Mock the configuration provider to give us config with 2 frame blocks per + // SDU if it receives the proper PAC entry in the requirements + // ON_CALL(*mock_codec_manager_, IsUsingCodecExtensibility) + // .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); + } + + auto config = *cfg; + + if (requirements.sink_pacs.has_value()) { + for (auto const& rec : requirements.sink_pacs.value()) { + auto caps = rec.codec_spec_caps.GetAsCoreCodecCapabilities(); + if (caps.HasSupportedMaxCodecFramesPerSdu()) { + if (caps.supported_max_codec_frames_per_sdu.value() == + max_codec_frames_per_sdu) { + // Inject the proper Codec Frames Per SDU as the json + // configs are conservative and will always give us 1 + for (auto& entry : config.confs.sink) { + entry.codec.params.Add( + codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, + (uint8_t)max_codec_frames_per_sdu); + } + is_fb2_passed_as_requirement = true; + } + } + } + } + return std::make_unique<AudioSetConfiguration>(config); + })); + + AddTestDevice(1, 1); + + TestLc3CodecConfig(LeAudioContextType::MEDIA, max_codec_frames_per_sdu); + + // Make sure the CodecManager mock gets the proper PAC record + ASSERT_TRUE(is_fb2_passed_as_requirement); +} + TEST_P(LeAudioAseConfigurationTest, test_unsupported_codec) { if (codec_coding_format_ == kLeAudioCodingFormatVendorSpecific) GTEST_SKIP(); diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc index 8cd07f856d..a216a4acc4 100644 --- a/system/bta/le_audio/le_audio_client_test.cc +++ b/system/bta/le_audio/le_audio_client_test.cc @@ -30,6 +30,7 @@ #include "bta_groups.h" #include "bta_le_audio_api.h" #include "bta_le_audio_broadcaster_api.h" +#include "btif/include/mock_core_callbacks.h" #include "btif_storage_mock.h" #include "btm_api_mock.h" #include "btm_iso_api.h" @@ -41,6 +42,7 @@ #include "hci/controller_interface_mock.h" #include "internal_include/stack_config.h" #include "le_audio/codec_manager.h" +#include "le_audio/mock_codec_interface.h" #include "le_audio_health_status.h" #include "le_audio_set_configuration_provider.h" #include "le_audio_types.h" @@ -1156,6 +1158,8 @@ class UnicastTestNoInit : public Test { stream_conf->stream_params.source .codec_frames_blocks_per_sdu = *core_config.codec_frames_blocks_per_sdu; + stream_conf->stream_params.source.frame_duration_us = + core_config.GetFrameDurationUs(); } else { log::assert_that( stream_conf->stream_params.source @@ -1228,6 +1232,8 @@ class UnicastTestNoInit : public Test { stream_conf->stream_params.sink .codec_frames_blocks_per_sdu = *core_config.codec_frames_blocks_per_sdu; + stream_conf->stream_params.sink.frame_duration_us = + core_config.GetFrameDurationUs(); } else { log::assert_that( stream_conf->stream_params.sink @@ -2347,6 +2353,25 @@ class UnicastTestNoInit : public Test { std::move(ascs), std::move(pacs)); } + struct SampleDatabaseParameters { + uint16_t conn_id; + RawAddress addr; + + uint32_t sink_audio_allocation = codec_spec_conf::kLeAudioLocationStereo; + uint32_t source_audio_allocation = codec_spec_conf::kLeAudioLocationStereo; + uint8_t sink_channel_cnt = 0x03; + uint8_t source_channel_cnt = 0x03; + uint16_t sample_freq_mask = 0x0004; + bool add_csis = true; + bool add_cas = true; + bool add_pacs = true; + int add_ascs_cnt = 1; + uint8_t set_size = 2; + uint8_t rank = 1; + GattStatus gatt_status = GATT_SUCCESS; + uint8_t max_supported_codec_frames_per_sdu = 1; + }; + void SetSampleDatabaseEarbudsValid( uint16_t conn_id, RawAddress addr, uint32_t sink_audio_allocation, uint32_t source_audio_allocation, uint8_t sink_channel_cnt = 0x03, @@ -2354,6 +2379,43 @@ class UnicastTestNoInit : public Test { bool add_csis = true, bool add_cas = true, bool add_pacs = true, int add_ascs_cnt = 1, uint8_t set_size = 2, uint8_t rank = 1, GattStatus gatt_status = GATT_SUCCESS) { + SetSampleDatabaseEarbudsValid(SampleDatabaseParameters{ + .conn_id = conn_id, + .addr = addr, + .sink_audio_allocation = sink_audio_allocation, + .source_audio_allocation = source_audio_allocation, + .sink_channel_cnt = sink_channel_cnt, + .source_channel_cnt = source_channel_cnt, + .sample_freq_mask = sample_freq_mask, + .add_csis = add_csis, + .add_cas = add_cas, + .add_pacs = add_pacs, + .add_ascs_cnt = add_ascs_cnt, + .set_size = set_size, + .rank = rank, + .gatt_status = gatt_status, + .max_supported_codec_frames_per_sdu = 1, + }); + } + + void SetSampleDatabaseEarbudsValid(const SampleDatabaseParameters& params) { + auto conn_id = params.conn_id; + auto addr = params.addr; + auto sink_audio_allocation = params.sink_audio_allocation; + auto source_audio_allocation = params.source_audio_allocation; + auto sink_channel_cnt = params.sink_channel_cnt; + auto source_channel_cnt = params.source_channel_cnt; + auto sample_freq_mask = params.sample_freq_mask; + auto add_csis = params.add_csis; + auto add_cas = params.add_cas; + auto add_pacs = params.add_pacs; + auto add_ascs_cnt = params.add_ascs_cnt; + auto set_size = params.set_size; + auto rank = params.rank; + auto gatt_status = params.gatt_status; + auto max_supported_codec_frames_per_sdu = + params.max_supported_codec_frames_per_sdu; + auto csis = std::make_unique<NiceMock<MockDeviceWrapper::csis_mock>>(); if (add_csis) { // attribute handles @@ -2451,10 +2513,8 @@ class UnicastTestNoInit : public Test { // Set pacs default read values ON_CALL(*peer_devices.at(conn_id)->pacs, OnReadCharacteristic(_, _, _)) - .WillByDefault([this, conn_id, snk_allocation, src_allocation, - sample_freq, sink_channel_cnt, source_channel_cnt, - gatt_status](uint16_t handle, GATT_READ_OP_CB cb, - void* cb_data) { + .WillByDefault([=, this](uint16_t handle, GATT_READ_OP_CB cb, + void* cb_data) { auto& pacs = peer_devices.at(conn_id)->pacs; std::vector<uint8_t> value; if (gatt_status == GATT_SUCCESS) { @@ -2498,7 +2558,7 @@ class UnicastTestNoInit : public Test { 0x00, 0x00, // Codec Spec. Caps. Len - 0x10, + 0x13, 0x03, /* sample freq */ 0x01, 0x80, /* 48kHz */ @@ -2515,6 +2575,9 @@ class UnicastTestNoInit : public Test { 0x00, 0x78, 0x00, + 0x02, /* Max supported codec frames per SDU */ + 0x05, + max_supported_codec_frames_per_sdu, // Metadata Length 0x00, }; @@ -2900,6 +2963,8 @@ class UnicastTest : public UnicastTestNoInit { } void TearDown() override { + MockCodecInterface::ClearMockInstanceHookList(); + // Clear the default actions before the parent class teardown is called Mock::VerifyAndClear(&mock_btm_interface_); Mock::VerifyAndClear(&mock_gatt_interface_); @@ -10882,4 +10947,186 @@ TEST_F_WITH_FLAGS(UnicastTest, NoContextvalidateStreamingRequest, Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); } + +TEST_F(UnicastTest, CodecFrameBlocks2) { + auto const max_codec_frames_per_sdu = 2; + uint32_t data_len = 1920; + + // Register a on-the-fly hook for codec interface mock mutation to prepare the + // codec mock for encoding + std::list<MockCodecInterface*> codec_mocks; + MockCodecInterface::RegisterMockInstanceHook([&](MockCodecInterface* mock, + bool is_destroyed) { + if (is_destroyed) { + log::debug("Codec Interface Destroyed: {}", (long)mock); + codec_mocks.remove(mock); + } else { + log::debug("Codec Interface Created: {}", (long)mock); + ON_CALL(*mock, GetNumOfSamplesPerChannel()).WillByDefault(Return(960)); + ON_CALL(*mock, GetNumOfBytesPerSample()) + .WillByDefault(Return(2)); // 16bits samples + ON_CALL(*mock, Encode(_, _, _, _, _)) + .WillByDefault(Return(CodecInterface::Status::STATUS_OK)); + codec_mocks.push_back(mock); + } + }); + + // Add a frame block PAC passing verifier + bool is_fb2_passed_as_requirement = false; + 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<set_configurations::AudioSetConfiguration>( + nullptr); + } + + auto config = *cfg; + + if (requirements.sink_pacs.has_value()) { + for (auto const& rec : requirements.sink_pacs.value()) { + auto caps = rec.codec_spec_caps.GetAsCoreCodecCapabilities(); + if (caps.HasSupportedMaxCodecFramesPerSdu()) { + if (caps.supported_max_codec_frames_per_sdu.value() == + max_codec_frames_per_sdu) { + // Inject the proper Codec Frames Per SDU as the json + // configs are conservative and will always give us 1 + for (auto& entry : config.confs.sink) { + entry.codec.params.Add( + codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, + (uint8_t)max_codec_frames_per_sdu); + } + is_fb2_passed_as_requirement = true; + } + } + } + } + return std::make_unique<set_configurations::AudioSetConfiguration>( + config); + })); + + types::BidirectionalPair<stream_parameters> codec_manager_stream_params; + ON_CALL(*mock_codec_manager_, UpdateActiveAudioConfig) + .WillByDefault(Invoke( + [&](const types::BidirectionalPair<stream_parameters>& stream_params, + types::BidirectionalPair<uint16_t> delays_ms, + std::function<void(const offload_config& config, + uint8_t direction)> + updater) { codec_manager_stream_params = stream_params; })); + + const RawAddress test_address0 = GetTestAddress(0); + int group_id = bluetooth::groups::kGroupUnknown; + + SampleDatabaseParameters remote_params{ + .conn_id = 1, + .addr = test_address0, + .add_csis = false, + .set_size = 0, + .rank = 0, + .max_supported_codec_frames_per_sdu = 2, + }; + SetSampleDatabaseEarbudsValid(remote_params); + + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnConnectionState(ConnectionState::CONNECTED, test_address0)) + .Times(1); + EXPECT_CALL(mock_audio_hal_client_callbacks_, + OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED)) + .WillOnce(DoAll(SaveArg<1>(&group_id))); + + ConnectLeAudio(test_address0); + ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown); + + constexpr int gmcs_ccid = 1; + constexpr int gtbs_ccid = 2; + + // Audio sessions are started only when device gets active + EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); + EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); + LeAudioClient::Get()->SetCcidInformation(gmcs_ccid, 4 /* Media */); + LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */); + LeAudioClient::Get()->GroupSetActive(group_id); + SyncOnMainLoop(); + + types::BidirectionalPair<std::vector<uint8_t>> ccids = {.sink = {gmcs_ccid}, + .source = {}}; + EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, ccids)).Times(1); + + block_streaming_state_callback = true; + + UpdateLocalSourceMetadata(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC); + LocalAudioSourceResume(false); + + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); + Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); + ASSERT_TRUE(is_fb2_passed_as_requirement); + + // Verify codec fram blocks per SDU has been applied to the device + ASSERT_NE(0lu, streaming_groups.count(group_id)); + uint8_t device_configured_codec_frame_blocks_per_sdu = 0; + auto group = streaming_groups.at(group_id); + for (LeAudioDevice* device = group->GetFirstDevice(); device != nullptr; + device = group->GetNextDevice(device)) { + for (auto& ase : device->ases_) { + if (ase.active) { + auto cfg = ase.codec_config.GetAsCoreCodecConfig(); + ASSERT_TRUE(cfg.codec_frames_blocks_per_sdu.has_value()); + device_configured_codec_frame_blocks_per_sdu = + cfg.codec_frames_blocks_per_sdu.value(); + } + } + } + + // Verify the configured codec frame blocks per SDU + ASSERT_EQ(device_configured_codec_frame_blocks_per_sdu, + remote_params.max_supported_codec_frames_per_sdu); + + EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0); + do_in_main_thread( + FROM_HERE, + base::BindOnce( + [](int group_id, + bluetooth::le_audio::LeAudioGroupStateMachine::Callbacks* + state_machine_callbacks) { + state_machine_callbacks->StatusReportCb( + group_id, GroupStreamStatus::STREAMING); + }, + group_id, base::Unretained(state_machine_callbacks_))); + SyncOnMainLoop(); + Mock::VerifyAndClearExpectations(&mock_state_machine_); + + // Verify Data transfer on one audio source cis + constexpr uint8_t cis_count_out = 1; + constexpr uint8_t cis_count_in = 0; + TestAudioDataTransfer( + group_id, cis_count_out, cis_count_in, + data_len * device_configured_codec_frame_blocks_per_sdu); + + ASSERT_NE(codec_mocks.size(), 0ul); + + // Verify that the initially started session was updated with the new params + ASSERT_EQ(codec_manager_stream_params.sink.codec_frames_blocks_per_sdu, + max_codec_frames_per_sdu); +} + } // namespace bluetooth::le_audio diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc index 67a8a58276..21337cba12 100644 --- a/system/bta/le_audio/le_audio_types.cc +++ b/system/bta/le_audio/le_audio_types.cc @@ -133,7 +133,9 @@ uint32_t CodecConfigSetting::GetSamplingFrequencyHz() const { uint32_t CodecConfigSetting::GetDataIntervalUs() const { switch (id.coding_format) { case kLeAudioCodingFormatLC3: - return params.GetAsCoreCodecConfig().GetFrameDurationUs(); + return params.GetAsCoreCodecConfig().GetFrameDurationUs() * + params.GetAsCoreCodecConfig().codec_frames_blocks_per_sdu.value_or( + 1); default: log::warn(", invalid codec id: 0x{:02x}", id.coding_format); return 0; diff --git a/system/bta/le_audio/mock_codec_interface.cc b/system/bta/le_audio/mock_codec_interface.cc index 71b26e2bfa..0c8af03767 100644 --- a/system/bta/le_audio/mock_codec_interface.cc +++ b/system/bta/le_audio/mock_codec_interface.cc @@ -16,6 +16,11 @@ #include "mock_codec_interface.h" +#include "le_audio/codec_interface.h" + +static std::list<std::function<void(MockCodecInterface*, bool)>> + mock_life_listener_list; + namespace bluetooth::le_audio { struct CodecInterface::Impl : public MockCodecInterface { @@ -31,8 +36,16 @@ struct CodecInterface::Impl : public MockCodecInterface { CodecInterface::CodecInterface(const types::LeAudioCodecId& codec_id) { impl = new Impl(codec_id); + for (auto& foo : mock_life_listener_list) { + foo(impl, false); + } +} +CodecInterface::~CodecInterface() { + for (auto& foo : mock_life_listener_list) { + foo(impl, true); + } + delete impl; } -CodecInterface::~CodecInterface() { delete impl; } bool CodecInterface::IsReady() { return impl->IsReady(); }; CodecInterface::Status CodecInterface::InitEncoder( const LeAudioCodecConfiguration& pcm_config, @@ -65,3 +78,12 @@ uint8_t CodecInterface::GetNumOfBytesPerSample() { return impl->GetNumOfBytesPerSample(); }; } // namespace bluetooth::le_audio + +void MockCodecInterface::RegisterMockInstanceHook( + std::function<void(MockCodecInterface*, bool)> listener) { + mock_life_listener_list.push_back(std::move(listener)); +} + +void MockCodecInterface::ClearMockInstanceHookList() { + mock_life_listener_list.clear(); +} diff --git a/system/bta/le_audio/mock_codec_interface.h b/system/bta/le_audio/mock_codec_interface.h index 974e049779..2b3ecaa958 100644 --- a/system/bta/le_audio/mock_codec_interface.h +++ b/system/bta/le_audio/mock_codec_interface.h @@ -24,6 +24,10 @@ class MockCodecInterface { public: + static void RegisterMockInstanceHook( + std::function<void(MockCodecInterface*, bool)>); + static void ClearMockInstanceHookList(); + MockCodecInterface() = default; MockCodecInterface(const MockCodecInterface&) = delete; MockCodecInterface& operator=(const MockCodecInterface&) = delete; diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc index c23083ec4d..43f08d3fed 100644 --- a/system/bta/le_audio/state_machine_test.cc +++ b/system/bta/le_audio/state_machine_test.cc @@ -140,7 +140,7 @@ constexpr uint8_t kCapTypeSupportedSamplingFrequencies = 0x01; constexpr uint8_t kCapTypeSupportedFrameDurations = 0x02; constexpr uint8_t kCapTypeAudioChannelCount = 0x03; constexpr uint8_t kCapTypeSupportedOctetsPerCodecFrame = 0x04; -// constexpr uint8_t kCapTypeSupportedLc3CodecFramesPerSdu = 0x05; +constexpr uint8_t kCapTypeSupportedLc3CodecFramesPerSdu = 0x05; // constexpr uint8_t kCapSamplingFrequency8000Hz = 0x0001; // constexpr uint8_t kCapSamplingFrequency11025Hz = 0x0002; @@ -247,6 +247,7 @@ class StateMachineTestBase : public Test { uint8_t additional_snk_ases = 0; uint8_t additional_src_ases = 0; uint8_t channel_count_ = kLeAudioCodecChannelCountSingleChannel; + uint8_t codec_frame_blocks_per_sdu_ = 1; uint16_t sample_freq_ = codec_specific::kCapSamplingFrequency16000Hz | codec_specific::kCapSamplingFrequency32000Hz; uint8_t channel_allocations_sink_ = @@ -407,6 +408,7 @@ class StateMachineTestBase : public Test { [this](uint8_t cig_id, bluetooth::hci::iso_manager::cig_create_params p) { log::debug("CreateCig"); + last_cig_params_ = p; auto& group = le_audio_device_groups_[cig_id]; if (group) { @@ -900,6 +902,7 @@ class StateMachineTestBase : public Test { uint8_t audio_channel_count_bitfield, uint16_t supported_octets_per_codec_frame_min, uint16_t supported_octets_per_codec_frame_max, + uint8_t codec_frame_blocks_per_sdu_ = 1, uint8_t coding_format = codec_specific::kLc3CodingFormat, uint16_t vendor_company_id = 0x0000, uint16_t vendor_codec_id = 0x0000, std::vector<uint8_t> metadata = {}) { @@ -921,6 +924,8 @@ class StateMachineTestBase : public Test { (uint8_t)(supported_octets_per_codec_frame_max >> 8), }}, }); + ltv_map.Add(codec_specific::kCapTypeSupportedLc3CodecFramesPerSdu, + (uint8_t)codec_frame_blocks_per_sdu_); recs.push_back({ .codec_id = { @@ -1066,7 +1071,7 @@ class StateMachineTestBase : public Test { codec_specific::kCapFrameDuration10ms | codec_specific::kCapFrameDuration7p5ms | codec_specific::kCapFrameDuration10msPreferred, - channel_count_, 30, 120); + channel_count_, 30, 120, codec_frame_blocks_per_sdu_); types::hdl_pair handle_pair; handle_pair.val_hdl = attr_handle++; @@ -1091,7 +1096,7 @@ class StateMachineTestBase : public Test { codec_specific::kCapFrameDuration10ms | codec_specific::kCapFrameDuration7p5ms | codec_specific::kCapFrameDuration10msPreferred, - 0b00000001, 30, 120); + 0b00000001, 30, 120, codec_frame_blocks_per_sdu_); types::hdl_pair handle_pair; handle_pair.val_hdl = attr_handle++; @@ -1188,7 +1193,7 @@ class StateMachineTestBase : public Test { codec_configured_state_params.framing = ascs::kAseParamFramingUnframedSupported; codec_configured_state_params.preferred_retrans_nb = 0x04; - codec_configured_state_params.max_transport_latency = 0x0010; + codec_configured_state_params.max_transport_latency = 0x0020; codec_configured_state_params.pres_delay_min = 0xABABAB; codec_configured_state_params.pres_delay_max = 0xCDCDCD; codec_configured_state_params.preferred_pres_delay_min = @@ -1605,6 +1610,7 @@ class StateMachineTestBase : public Test { gatt::MockBtaGattQueue gatt_queue; bluetooth::hci::IsoManager* iso_manager_; + bluetooth::hci::iso_manager::cig_create_params last_cig_params_; MockIsoManager* mock_iso_manager_; bluetooth::le_audio::CodecManager* codec_manager_; MockCodecManager* mock_codec_manager_; @@ -1712,6 +1718,153 @@ TEST_F(StateMachineTest, testConfigureCodecSingle) { ASSERT_EQ(0, get_func_call_count("alarm_cancel")); } +TEST_F(StateMachineTest, testConfigureCodecSingleFb2) { + codec_frame_blocks_per_sdu_ = 2; + bool is_fb2_passed_as_sink_requirement = false; + bool is_fb2_passed_as_source_requirement = false; + + ON_CALL(*mock_codec_manager_, GetCodecConfig) + .WillByDefault(Invoke([&](const bluetooth::le_audio::CodecManager:: + UnicastConfigurationRequirements& + requirements, + bluetooth::le_audio::CodecManager:: + UnicastConfigurationVerifier verifier) { + auto configs = + *bluetooth::le_audio::AudioSetConfigurationProvider::Get() + ->GetConfigurations(requirements.audio_context_type); + // Note: This dual bidir SWB exclusion logic has to match the + // CodecManager::GetCodecConfig() implementation. + if (!CodecManager::GetInstance()->IsDualBiDirSwbSupported()) { + configs.erase( + std::remove_if(configs.begin(), configs.end(), + [](auto const& el) { + if (el->confs.source.empty()) return false; + return AudioSetConfigurationProvider::Get() + ->CheckConfigurationIsDualBiDirSwb(*el); + }), + configs.end()); + } + + auto cfg = verifier(requirements, &configs); + if (cfg == nullptr) { + return std::unique_ptr< + bluetooth::le_audio::set_configurations::AudioSetConfiguration>( + nullptr); + } + auto config = *cfg; + + if (requirements.sink_pacs.has_value()) { + for (auto const& rec : requirements.sink_pacs.value()) { + auto caps = rec.codec_spec_caps.GetAsCoreCodecCapabilities(); + if (caps.HasSupportedMaxCodecFramesPerSdu()) { + if (caps.supported_max_codec_frames_per_sdu.value() == + codec_frame_blocks_per_sdu_) { + // Scale by Codec Frames Per SDU = 2 + for (auto& entry : config.confs.sink) { + entry.codec.params.Add( + codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, + (uint8_t)codec_frame_blocks_per_sdu_); + entry.qos.maxSdu *= codec_frame_blocks_per_sdu_; + entry.qos.sduIntervalUs *= codec_frame_blocks_per_sdu_; + entry.qos.max_transport_latency *= + codec_frame_blocks_per_sdu_; + } + is_fb2_passed_as_sink_requirement = true; + } + } + } + } + if (requirements.source_pacs.has_value()) { + for (auto const& rec : requirements.source_pacs.value()) { + auto caps = rec.codec_spec_caps.GetAsCoreCodecCapabilities(); + if (caps.HasSupportedMaxCodecFramesPerSdu()) { + if (caps.supported_max_codec_frames_per_sdu.value() == + codec_frame_blocks_per_sdu_) { + // Scale by Codec Frames Per SDU = 2 + for (auto& entry : config.confs.source) { + entry.codec.params.Add( + codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, + (uint8_t)codec_frame_blocks_per_sdu_); + entry.qos.maxSdu *= codec_frame_blocks_per_sdu_; + entry.qos.sduIntervalUs *= codec_frame_blocks_per_sdu_; + entry.qos.max_transport_latency *= + codec_frame_blocks_per_sdu_; + } + is_fb2_passed_as_source_requirement = true; + } + } + } + } + + return std::make_unique< + bluetooth::le_audio::set_configurations::AudioSetConfiguration>( + config); + })); + + /* Device is banded headphones with 1x snk + 0x src ase + * (1xunidirectional CIS) with channel count 2 (for stereo + */ + const auto context_type = kContextTypeRingtone; + const int leaudio_group_id = 2; + channel_count_ = kLeAudioCodecChannelCountSingleChannel | + kLeAudioCodecChannelCountTwoChannel; + + /* Prepare the fake connected device group */ + auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type); + + /* Since we prepared device with Ringtone context in mind, only one ASE + * should have been configured. + */ + auto* leAudioDevice = group->GetFirstDevice(); + PrepareConfigureCodecHandler(group, 1); + + /* Start the configuration and stream Media content. + * Expect 2 times: for Codec Configure & QoS Configure */ + EXPECT_CALL(gatt_queue, + WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _, + GATT_WRITE_NO_RSP, _, _)) + .Times(2); + + InjectInitialIdleNotification(group); + + EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(1); + ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream( + group, context_type, + {.sink = types::AudioContexts(context_type), + .source = types::AudioContexts(context_type)})); + + /* Check if group has transitioned to a proper state */ + ASSERT_EQ(group->GetState(), + types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED); + + /* Cancel is called when group goes to streaming. */ + ASSERT_EQ(0, get_func_call_count("alarm_cancel")); + + ASSERT_TRUE(is_fb2_passed_as_sink_requirement); + + /* Make sure that data interval is based on the codec frame blocks count */ + auto data_interval = group->GetActiveConfiguration() + ->confs.sink.at(0) + .codec.GetDataIntervalUs(); + ASSERT_EQ(data_interval, group->GetActiveConfiguration() + ->confs.sink.at(0) + .codec.params.GetAsCoreCodecConfig() + .GetFrameDurationUs() * + codec_frame_blocks_per_sdu_); + + /* Verify CIG parameters */ + auto channel_count = group->GetActiveConfiguration() + ->confs.sink.at(0) + .codec.GetChannelCountPerIsoStream(); + auto frame_octets = group->GetActiveConfiguration() + ->confs.sink.at(0) + .codec.GetOctectsPerFrame(); + ASSERT_NE(last_cig_params_.cis_cfgs.size(), 0lu); + ASSERT_EQ(last_cig_params_.sdu_itv_mtos, data_interval); + ASSERT_EQ(last_cig_params_.cis_cfgs.at(0).max_sdu_size_mtos, + codec_frame_blocks_per_sdu_ * channel_count * frame_octets); +} + TEST_F(StateMachineTest, testConfigureCodecMulti) { const auto context_type = kContextTypeMedia; const auto leaudio_group_id = 2; |