summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--system/bta/le_audio/client.cc23
-rw-r--r--system/bta/le_audio/devices_test.cc110
-rw-r--r--system/bta/le_audio/le_audio_client_test.cc257
-rw-r--r--system/bta/le_audio/le_audio_types.cc4
-rw-r--r--system/bta/le_audio/mock_codec_interface.cc24
-rw-r--r--system/bta/le_audio/mock_codec_interface.h4
-rw-r--r--system/bta/le_audio/state_machine_test.cc161
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;