blob: 8dc6e3fb572d609634d26bf9e3d76d4a11d5afb0 [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
* www.ehima.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <base/bind.h>
#include <base/strings/string_number_conversions.h>
#include "advertise_data_parser.h"
#include "bta/csis/csis_types.h"
#include "bta_api.h"
#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "bta_groups.h"
#include "bta_le_audio_api.h"
#include "btif_storage.h"
#include "btm_iso_api.h"
#include "client_audio.h"
#include "client_parser.h"
#include "device/include/controller.h"
#include "devices.h"
#include "embdrv/lc3/Api/Lc3Decoder.hpp"
#include "embdrv/lc3/Api/Lc3Encoder.hpp"
#include "gatt/bta_gattc_int.h"
#include "le_audio_types.h"
#include "osi/include/osi.h"
#include "stack/btm/btm_dev.h"
#include "stack/btm/btm_sec.h"
#include "stack/include/btu.h" // do_in_main_thread
#include "state_machine.h"
using base::Closure;
using bluetooth::Uuid;
using bluetooth::groups::DeviceGroups;
using bluetooth::groups::DeviceGroupsCallbacks;
using bluetooth::hci::IsoManager;
using bluetooth::hci::iso_manager::cig_create_cmpl_evt;
using bluetooth::hci::iso_manager::cig_remove_cmpl_evt;
using bluetooth::hci::iso_manager::CigCallbacks;
using bluetooth::le_audio::ConnectionState;
using bluetooth::le_audio::GroupNodeStatus;
using bluetooth::le_audio::GroupStatus;
using bluetooth::le_audio::GroupStreamStatus;
using le_audio::LeAudioDevice;
using le_audio::LeAudioDeviceGroup;
using le_audio::LeAudioDeviceGroups;
using le_audio::LeAudioDevices;
using le_audio::LeAudioGroupStateMachine;
using le_audio::types::ase;
using le_audio::types::AseState;
using le_audio::types::AudioContexts;
using le_audio::types::AudioLocations;
using le_audio::types::AudioStreamDataPathState;
using le_audio::types::hdl_pair;
using le_audio::types::kDefaultScanDurationS;
using le_audio::types::LeAudioContextType;
using le_audio::client_parser::ascs::
kCtpResponseCodeInvalidConfigurationParameterValue;
using le_audio::client_parser::ascs::kCtpResponseCodeSuccess;
using le_audio::client_parser::ascs::kCtpResponseInvalidAseCisMapping;
using le_audio::client_parser::ascs::kCtpResponseNoReason;
namespace {
void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data);
class LeAudioClientImpl;
LeAudioClientImpl* instance;
LeAudioClientAudioSinkReceiver* audioSinkReceiver;
LeAudioClientAudioSourceReceiver* audioSourceReceiver;
CigCallbacks* stateMachineHciCallbacks;
LeAudioGroupStateMachine::Callbacks* stateMachineCallbacks;
DeviceGroupsCallbacks* device_group_callbacks;
/*
* Coordinatet Set Identification Profile (CSIP) based on CSIP 1.0
* and Coordinatet Set Identification Service (CSIS) 1.0
*
* CSIP allows to organize audio servers into sets e.g. Stereo Set, 5.1 Set
* and speed up connecting it.
*
* Since leaudio has already grouping API it was decided to integrate here CSIS
* and allow it to group devices semi-automatically.
*
* Flow:
* If connected device contains CSIS services, and it is included into CAP
* service, implementation marks device as a set member and waits for the
* bta/csis to learn about groups and notify implementation about assigned
* group id.
*
*/
/* LeAudioClientImpl class represents main implementation class for le audio
* feature in stack. This class implements GATT, le audio and ISO related parts.
*
* This class is represented in single instance and manages a group of devices,
* and devices. All devices calls back static method from it and are dispatched
* to target receivers (e.g. ASEs, devices).
*
* This instance also implements a LeAudioClient which is a upper layer API.
* Also LeAudioClientCallbacks are callbacks for upper layer.
*
* This class may be bonded with Test socket which allows to drive an instance
* for test purposes.
*/
class LeAudioClientImpl : public LeAudioClient {
public:
virtual ~LeAudioClientImpl() = default;
LeAudioClientImpl(
bluetooth::le_audio::LeAudioClientCallbacks* callbacks_,
LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_,
base::Closure initCb)
: gatt_if_(0),
callbacks_(callbacks_),
active_group_id_(bluetooth::groups::kGroupUnknown),
stream_request_started_(false),
current_context_type_(LeAudioContextType::MEDIA),
audio_sink_ready_to_receive(false),
audio_source_ready_to_send(false),
current_source_codec_config({0, 0, 0, 0}),
current_sink_codec_config({0, 0, 0, 0}),
lc3_encoder(nullptr),
lc3_decoder(nullptr),
audio_source_instance_(nullptr),
audio_sink_instance_(nullptr) {
LeAudioGroupStateMachine::Initialize(state_machine_callbacks_);
groupStateMachine_ = LeAudioGroupStateMachine::Get();
BTA_GATTC_AppRegister(
le_audio_gattc_callback,
base::Bind(
[](base::Closure initCb, uint8_t client_id, uint8_t status) {
if (status != GATT_SUCCESS) {
LOG(ERROR) << "Can't start LeAudio profile - no gatt "
"clients left!";
return;
}
instance->gatt_if_ = client_id;
initCb.Run();
},
initCb),
true);
DeviceGroups::Get()->Initialize(device_group_callbacks);
}
void AseInitialStateReadRequest(LeAudioDevice* leAudioDevice) {
int ases_num = leAudioDevice->ases_.size();
void* notify_flag_ptr = NULL;
for (int i = 0; i < ases_num; i++) {
/* Last read ase characteristic should issue connected state callback
* to upper layer
*/
if (leAudioDevice->notify_connected_after_read_ &&
(i == (ases_num - 1))) {
notify_flag_ptr =
INT_TO_PTR(leAudioDevice->notify_connected_after_read_);
}
BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ases_[i].hdls.val_hdl,
OnGattReadRspStatic, notify_flag_ptr);
}
}
void OnGroupAddedCb(const RawAddress& address, const bluetooth::Uuid& uuid,
int group_id) {
LOG(INFO) << __func__ << " address: " << address << " group uuid " << uuid
<< " group_id: " << group_id;
/* We are interested in the groups which are in the context of CAP */
if (uuid != le_audio::uuid::kCapServiceUuid) return;
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) return;
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
LOG(INFO) << __func__
<< " group already set: " << leAudioDevice->group_id_;
return;
}
group_add_node(group_id, address);
}
void OnGroupMemberAddedCb(const RawAddress& address, int group_id) {
LOG(INFO) << __func__ << " address: " << address
<< " group_id: " << group_id;
auto group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__ << " Not interested in group id: " << group_id;
return;
}
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) return;
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
LOG(INFO) << __func__
<< " group already set: " << leAudioDevice->group_id_;
return;
}
group_add_node(group_id, address);
}
void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) {
LOG(INFO) << __func__ << " address: " << address
<< " group_id: " << group_id;
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) return;
if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) {
LOG(INFO) << __func__ << " device already not assigned to the group.";
return;
}
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (group == NULL) {
LOG(INFO) << __func__
<< " device not in the group: " << leAudioDevice->address_
<< ", " << group_id;
return;
}
group_remove_node(group, address);
}
/* This callback happens if kLeAudioDeviceSetStateTimeoutMs timeout happens
* during transition from origin to target state
*/
void OnLeAudioDeviceSetStateTimeout(int group_id) {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (!group) {
/* Group removed */
return;
}
/* Releasement didn't finished in time */
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
if (instance) instance->DisconnectDevice(leAudioDevice, true);
leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
} while (leAudioDevice);
return;
}
LOG(ERROR) << __func__ << ", State not achieved on time, releasing ases";
groupStateMachine_->StopStream(group);
}
void UpdateContextAndLocations(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
std::optional<AudioContexts> new_group_updated_contexts =
group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
if (new_group_updated_contexts || group->ReloadAudioLocations()) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
new_group_updated_contexts->to_ulong());
}
}
void CancelStreamingRequest() {
if (audio_source_ready_to_send) {
LeAudioClientAudioSource::CancelStreamingRequest();
audio_source_ready_to_send = false;
}
if (audio_sink_ready_to_receive) {
LeAudioClientAudioSink::CancelStreamingRequest();
audio_sink_ready_to_receive = false;
}
}
void ControlPointNotificationHandler(
struct le_audio::client_parser::ascs::ctp_ntf& ntf) {
for (auto& entry : ntf.entries) {
switch (entry.response_code) {
case kCtpResponseCodeInvalidConfigurationParameterValue:
switch (entry.reason) {
case kCtpResponseInvalidAseCisMapping:
CancelStreamingRequest();
break;
case kCtpResponseNoReason:
default:
break;
}
break;
case kCtpResponseCodeSuccess:
FALLTHROUGH;
default:
break;
}
}
}
void group_add_node(const int group_id, const RawAddress& address,
bool update_group_module = false) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
LeAudioDeviceGroup* new_group;
LeAudioDeviceGroup* old_group = nullptr;
int old_group_id = bluetooth::groups::kGroupUnknown;
if (!leAudioDevice) {
/* TODO This part possible to remove as this is to handle adding device to
* the group which is unknown and not connected.
*/
LOG(INFO) << __func__ << ", leAudioDevice unknown , address: " << address
<< " group: " << loghex(group_id);
if (group_id == bluetooth::groups::kGroupUnknown) return;
LOG(INFO) << __func__ << "Set member adding ...";
leAudioDevices_.Add(address, true);
leAudioDevice = leAudioDevices_.FindByAddress(address);
} else {
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
old_group = aseGroups_.FindById(leAudioDevice->group_id_);
old_group_id = old_group->group_id_;
}
}
auto id = DeviceGroups::Get()->GetGroupId(address,
le_audio::uuid::kCapServiceUuid);
if (group_id == bluetooth::groups::kGroupUnknown) {
if (id == bluetooth::groups::kGroupUnknown) {
DeviceGroups::Get()->AddDevice(address,
le_audio::uuid::kCapServiceUuid);
/* We will get back here when group will be created */
return;
}
new_group = aseGroups_.Add(id);
if (!new_group) {
LOG(ERROR) << __func__
<< ", can't create group - group is already there?";
return;
}
} else {
LOG_ASSERT(id == group_id)
<< " group id missmatch? leaudio id: " << group_id
<< " groups module " << id;
new_group = aseGroups_.FindById(group_id);
if (!new_group) {
new_group = aseGroups_.Add(group_id);
} else {
if (new_group->IsDeviceInTheGroup(leAudioDevice)) return;
}
}
/* If device was in the group and it was not removed by the application,
* lets do it now
*/
if (old_group) group_remove_node(old_group, address, update_group_module);
new_group->AddNode(leAudioDevices_.GetByAddress(address));
callbacks_->OnGroupNodeStatus(address, new_group->group_id_,
GroupNodeStatus::ADDED);
/* If device is connected and added to the group, lets read ASE states */
if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID)
AseInitialStateReadRequest(leAudioDevice);
/* Group may be destroyed once moved its last node to new group */
if (aseGroups_.FindById(old_group_id) != nullptr) {
/* Removing node from group may touch its context integrity */
std::optional<AudioContexts> old_group_updated_contexts =
old_group->UpdateActiveContextsMap(old_group->GetActiveContexts());
if (old_group_updated_contexts || old_group->ReloadAudioLocations()) {
callbacks_->OnAudioConf(old_group->audio_directions_, old_group_id,
old_group->snk_audio_locations_.to_ulong(),
old_group->src_audio_locations_.to_ulong(),
old_group_updated_contexts->to_ulong());
}
}
UpdateContextAndLocations(new_group, leAudioDevice);
}
void GroupAddNode(const int group_id, const RawAddress& address) override {
auto id = DeviceGroups::Get()->GetGroupId(address,
le_audio::uuid::kCapServiceUuid);
if (id == group_id) return;
if (id != bluetooth::groups::kGroupUnknown) {
DeviceGroups::Get()->RemoveDevice(address, id);
}
DeviceGroups::Get()->AddDevice(address, le_audio::uuid::kCapServiceUuid,
group_id);
}
void remove_group_if_possible(LeAudioDeviceGroup* group) {
if (group && group->IsEmpty() && !group->cig_created_) {
aseGroups_.Remove(group->group_id_);
}
}
void group_remove_node(LeAudioDeviceGroup* group, const RawAddress& address,
bool update_group_module = false) {
int group_id = group->group_id_;
group->RemoveNode(leAudioDevices_.GetByAddress(address));
if (update_group_module) {
int groups_group_id = DeviceGroups::Get()->GetGroupId(
address, le_audio::uuid::kCapServiceUuid);
if (groups_group_id == group_id) {
DeviceGroups::Get()->RemoveDevice(address, group_id);
}
}
callbacks_->OnGroupNodeStatus(address, group_id, GroupNodeStatus::REMOVED);
/* Remove group if this was the last leAudioDevice in this group */
if (group->IsEmpty()) {
remove_group_if_possible(group);
return;
}
/* Removing node from group touch its context integrity */
std::optional<AudioContexts> updated_contexts =
group->UpdateActiveContextsMap(group->GetActiveContexts());
if (updated_contexts || group->ReloadAudioLocations())
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
updated_contexts->to_ulong());
}
void GroupRemoveNode(const int group_id, const RawAddress& address) override {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
LOG(INFO) << __func__ << " group_id: " << group_id
<< " address: " << address;
if (!leAudioDevice) {
LOG(ERROR) << __func__
<< ", Skipping unknown leAudioDevice, address: " << address;
return;
}
if (leAudioDevice->group_id_ != group_id) {
LOG(ERROR) << __func__ << "Device is not in group_id: " << group_id
<< ", but in group_id: " << leAudioDevice->group_id_;
return;
}
if (group == NULL) {
LOG(ERROR) << __func__ << " device not in the group ?!";
return;
}
group_remove_node(group, address, true);
}
void GroupStream(const int group_id, const uint16_t context_type) override {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
auto final_context_type = context_type;
if (context_type >= static_cast<uint16_t>(LeAudioContextType::RFU)) {
LOG(ERROR) << __func__ << ", stream context type is not supported: "
<< loghex(context_type);
CancelStreamingRequest();
return;
}
if (!group) {
LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
CancelStreamingRequest();
return;
}
auto supported_context_type = group->GetActiveContexts();
if (!(context_type & supported_context_type.to_ulong())) {
LOG(ERROR) << " Unsupported context type by remote device: "
<< loghex(context_type) << ". Switching to unspecified";
final_context_type =
static_cast<uint16_t>(LeAudioContextType::UNSPECIFIED);
}
if (!group->IsAnyDeviceConnected()) {
LOG(ERROR) << __func__ << ", group " << group_id << " is not connected ";
CancelStreamingRequest();
return;
}
/* Check if any group is in the transition state. If so, we don't allow to
* start new group to stream */
if (aseGroups_.IsAnyInTransition()) {
LOG(INFO) << __func__ << " some group is already in the transition state";
CancelStreamingRequest();
return;
}
if (groupStateMachine_->StartStream(
group, static_cast<LeAudioContextType>(final_context_type)))
stream_request_started_ = true;
else
ClientAudioIntefraceRelease();
}
void GroupSuspend(const int group_id) override {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
return;
}
if (!group->IsAnyDeviceConnected()) {
LOG(ERROR) << __func__ << ", group is not connected";
return;
}
if (group->IsInTransition()) {
LOG(INFO) << __func__
<< ", group is in transition from: " << group->GetState()
<< ", to: " << group->GetTargetState();
return;
}
if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
LOG(ERROR) << __func__
<< ", invalid current state of group: " << group->GetState();
return;
}
audio_source_ready_to_send = false;
audio_sink_ready_to_receive = false;
groupStateMachine_->SuspendStream(group);
}
void GroupStop(const int group_id) override {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
return;
}
if (group->IsEmpty()) {
LOG(ERROR) << __func__ << ", group is empty";
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
LOG(ERROR) << __func__
<< ", group already stopped: " << group->GetState();
return;
}
groupStateMachine_->StopStream(group);
}
void GroupDestroy(const int group_id) override {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__ << ", unknown group id: " << group_id;
return;
}
// Disconnect and remove each device within the group
auto* dev = group->GetFirstDevice();
while (dev) {
auto* next_dev = group->GetNextDevice(dev);
RemoveDevice(dev->address_);
dev = next_dev;
}
}
void GroupSetActive(const int group_id) override {
DLOG(INFO) << __func__ << " group_id: " << group_id;
if (group_id == bluetooth::groups::kGroupUnknown) {
if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
/* Nothing to do */
return;
}
StopAudio();
GroupStop(active_group_id_);
callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
active_group_id_ = group_id;
return;
}
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(group_id);
return;
}
if (active_group_id_ != bluetooth::groups::kGroupUnknown) {
LOG(WARNING) << __func__ << ", Another group already active: "
<< static_cast<int>(active_group_id_);
return;
}
if (!audio_source_instance_) {
audio_source_instance_ = LeAudioClientAudioSource::Acquire();
if (!audio_source_instance_) {
LOG(ERROR) << __func__ << ", could not acquire audio source interface";
return;
}
}
if (!audio_sink_instance_) {
audio_sink_instance_ = LeAudioClientAudioSink::Acquire();
if (!audio_sink_instance_) {
LOG(ERROR) << __func__ << ", could not acquire audio sink interface";
LeAudioClientAudioSource::Release(audio_source_instance_);
return;
}
}
/* Configure audio HAL sessions with most frequent context */
UpdateCurrentHalSessions(group_id, LeAudioContextType::MEDIA);
if (current_source_codec_config.IsInvalid() &&
current_sink_codec_config.IsInvalid()) {
LOG(WARNING) << __func__ << ", unsupported device configurations";
callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
return;
}
active_group_id_ = group_id;
callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE);
}
void RemoveDevice(const RawAddress& address) override {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
return;
}
if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
Disconnect(address);
leAudioDevice->removing_device_ = true;
return;
}
/* Remove the group assignment if not yet removed. It might happen that the
* group module has already called the appropriate callback and we have
* already removed the group assignment.
*/
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
auto group = aseGroups_.FindById(leAudioDevice->group_id_);
group_remove_node(group, address, true);
}
leAudioDevices_.Remove(address);
}
void Connect(const RawAddress& address) override {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
leAudioDevices_.Add(address, true);
} else {
leAudioDevice->connecting_actively_ = true;
}
BTA_GATTC_Open(gatt_if_, address, true, false);
}
std::vector<RawAddress> GetGroupDevices(const int group_id) override {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
std::vector<RawAddress> all_group_device_addrs;
if (group != nullptr) {
LeAudioDevice* leAudioDevice = group->GetFirstDevice();
while (leAudioDevice) {
all_group_device_addrs.push_back(leAudioDevice->address_);
leAudioDevice = group->GetNextDevice(leAudioDevice);
};
}
return all_group_device_addrs;
}
/* Restore paired device from storage to recreate groups */
void AddFromStorage(const RawAddress& address, bool autoconnect) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
LOG(INFO) << __func__ << ", restoring: " << address;
if (!leAudioDevice) {
leAudioDevices_.Add(address, false);
leAudioDevice = leAudioDevices_.FindByAddress(address);
}
int group_id = DeviceGroups::Get()->GetGroupId(
address, le_audio::uuid::kCapServiceUuid);
if (group_id != bluetooth::groups::kGroupUnknown) {
group_add_node(group_id, address);
}
if (autoconnect) {
BTA_GATTC_Open(gatt_if_, address, false, false);
}
}
void BackgroundConnectIfGroupConnected(LeAudioDevice* leAudioDevice) {
DLOG(INFO) << __func__ << leAudioDevice->address_ ;
auto group = aseGroups_.FindById(leAudioDevice->group_id_);
if (!group) {
DLOG(INFO) << __func__ << " Device is not yet part of the group. ";
return;
}
if (!group->IsAnyDeviceConnected()) {
DLOG(INFO) << __func__ << " group: " << leAudioDevice->group_id_
<< " is not connected";
return;
}
DLOG(INFO) << __func__ << "Add " << leAudioDevice->address_
<< " to background connect to connected group: "
<< leAudioDevice->group_id_;
BTA_GATTC_Open(gatt_if_, leAudioDevice->address_, false, false);
}
void Disconnect(const RawAddress& address) override {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address
<< ")";
return;
}
/* cancel pending direct connect */
if (leAudioDevice->connecting_actively_) {
BTA_GATTC_CancelOpen(gatt_if_, address, true);
leAudioDevice->connecting_actively_ = false;
}
/* Removes all registrations for connection */
BTA_GATTC_CancelOpen(0, address, false);
if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
DisconnectDevice(leAudioDevice);
return;
}
/* If this is a device which is a part of the group which is connected,
* lets start backgroup connect
*/
BackgroundConnectIfGroupConnected(leAudioDevice);
}
void DisconnectDevice(LeAudioDevice* leAudioDevice,
bool acl_force_disconnect = false) {
if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) {
return;
}
if (acl_force_disconnect) {
uint16_t acl_handle =
BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
if (acl_handle != HCI_INVALID_HANDLE) {
acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER);
return;
}
}
BtaGattQueue::Clean(leAudioDevice->conn_id_);
BTA_GATTC_Close(leAudioDevice->conn_id_);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
}
void DeregisterNotifications(LeAudioDevice* leAudioDevice) {
/* GATTC will ommit not registered previously handles */
for (auto pac_tuple : leAudioDevice->snk_pacs_) {
BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
std::get<0>(pac_tuple).val_hdl);
}
for (auto pac_tuple : leAudioDevice->src_pacs_) {
BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
std::get<0>(pac_tuple).val_hdl);
}
if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0)
BTA_GATTC_DeregisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->snk_audio_locations_hdls_.val_hdl);
if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0)
BTA_GATTC_DeregisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->src_audio_locations_hdls_.val_hdl);
if (leAudioDevice->audio_avail_hdls_.val_hdl != 0)
BTA_GATTC_DeregisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->audio_avail_hdls_.val_hdl);
if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0)
BTA_GATTC_DeregisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->audio_supp_cont_hdls_.val_hdl);
if (leAudioDevice->ctp_hdls_.val_hdl != 0)
BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
leAudioDevice->ctp_hdls_.val_hdl);
for (struct ase& ase : leAudioDevice->ases_)
BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
ase.hdls.val_hdl);
}
/* This is a generic read/notify/indicate handler for gatt. Here messages
* are dispatched to correct elements e.g. ASEs, PACs, audio locations etc.
*/
void LeAudioCharValueHandle(uint16_t conn_id, uint16_t hdl, uint16_t len,
uint8_t* value) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
struct ase* ase;
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", no leAudioDevice assigned to connection id: "
<< static_cast<int>(conn_id);
return;
}
ase = leAudioDevice->GetAseByValHandle(hdl);
if (ase) {
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice,
group);
return;
}
auto snk_pac_ent = std::find_if(
leAudioDevice->snk_pacs_.begin(), leAudioDevice->snk_pacs_.end(),
[&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; });
if (snk_pac_ent != leAudioDevice->snk_pacs_.end()) {
std::vector<struct le_audio::types::acs_ac_record> pac_recs;
/* Guard consistency of PAC records structure */
if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value))
return;
LOG(INFO) << __func__ << ", Registering sink PACs";
leAudioDevice->RegisterPACs(&std::get<1>(*snk_pac_ent), &pac_recs);
/* Update supported context types including internal capabilities */
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
/* Active context map should be considered to be updated in response to
* PACs update.
* Read of available context during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
if (group)
group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
return;
}
auto src_pac_ent = std::find_if(
leAudioDevice->src_pacs_.begin(), leAudioDevice->src_pacs_.end(),
[&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; });
if (src_pac_ent != leAudioDevice->src_pacs_.end()) {
std::vector<struct le_audio::types::acs_ac_record> pac_recs;
/* Guard consistency of PAC records structure */
if (!le_audio::client_parser::pacs::ParsePac(pac_recs, len, value))
return;
LOG(INFO) << __func__ << ", Registering source PACs";
leAudioDevice->RegisterPACs(&std::get<1>(*src_pac_ent), &pac_recs);
/* Update supported context types including internal capabilities */
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
/* Active context map should be considered to be updated in response to
* PACs update.
* Read of available context during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
if (group)
group->UpdateActiveContextsMap(leAudioDevice->GetAvailableContexts());
return;
}
if (hdl == leAudioDevice->snk_audio_locations_hdls_.val_hdl) {
AudioLocations snk_audio_locations;
le_audio::client_parser::pacs::ParseAudioLocations(snk_audio_locations,
len, value);
/* Value may not change */
if ((leAudioDevice->audio_directions_ &
le_audio::types::kLeAudioDirectionSink) &&
(leAudioDevice->snk_audio_locations_ ^ snk_audio_locations).none())
return;
/* Presence of PAC characteristic for source means support for source
* audio location. Value of 0x00000000 means mono/unspecified
*/
leAudioDevice->audio_directions_ |=
le_audio::types::kLeAudioDirectionSink;
leAudioDevice->snk_audio_locations_ = snk_audio_locations;
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
/* Read of source audio locations during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
if (group && group->ReloadAudioLocations()) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
group->GetActiveContexts().to_ulong());
}
} else if (hdl == leAudioDevice->src_audio_locations_hdls_.val_hdl) {
AudioLocations src_audio_locations;
le_audio::client_parser::pacs::ParseAudioLocations(src_audio_locations,
len, value);
/* Value may not change */
if ((leAudioDevice->audio_directions_ &
le_audio::types::kLeAudioDirectionSource) &&
(leAudioDevice->src_audio_locations_ ^ src_audio_locations).none())
return;
/* Presence of PAC characteristic for source means support for source
* audio location. Value of 0x00000000 means mono/unspecified
*/
leAudioDevice->audio_directions_ |=
le_audio::types::kLeAudioDirectionSource;
leAudioDevice->src_audio_locations_ = src_audio_locations;
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
/* Read of source audio locations during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
if (group && group->ReloadAudioLocations()) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
group->GetActiveContexts().to_ulong());
}
} else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) {
auto avail_audio_contexts = std::make_unique<
struct le_audio::client_parser::pacs::acs_available_audio_contexts>();
le_audio::client_parser::pacs::ParseAvailableAudioContexts(
*avail_audio_contexts, len, value);
auto updated_avail_contexts = leAudioDevice->SetAvailableContexts(
avail_audio_contexts->snk_avail_cont,
avail_audio_contexts->src_avail_cont);
if (updated_avail_contexts.any()) {
/* Update scenario map considering changed active context types */
LeAudioDeviceGroup* group =
aseGroups_.FindById(leAudioDevice->group_id_);
/* Read of available context during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
if (group) {
std::optional<AudioContexts> updated_contexts =
group->UpdateActiveContextsMap(updated_avail_contexts);
if (updated_contexts) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
updated_contexts->to_ulong());
}
}
}
} else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) {
auto supp_audio_contexts = std::make_unique<
struct le_audio::client_parser::pacs::acs_supported_audio_contexts>();
le_audio::client_parser::pacs::ParseSupportedAudioContexts(
*supp_audio_contexts, len, value);
/* Just store if for now */
leAudioDevice->SetSupportedContexts(supp_audio_contexts->snk_supp_cont,
supp_audio_contexts->src_supp_cont);
} else if (hdl == leAudioDevice->ctp_hdls_.val_hdl) {
auto ntf =
std::make_unique<struct le_audio::client_parser::ascs::ctp_ntf>();
if (ParseAseCtpNotification(*ntf, len, value))
ControlPointNotificationHandler(*ntf);
} else {
LOG(ERROR) << __func__ << ", Unknown attribute read: " << loghex(hdl);
}
}
void OnGattReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl,
uint16_t len, uint8_t* value, void* data) {
LeAudioCharValueHandle(conn_id, hdl, len, value);
}
void OnGattConnected(tGATT_STATUS status, uint16_t conn_id,
tGATT_IF client_if, RawAddress address,
tBT_TRANSPORT transport, uint16_t mtu) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) return;
if (status != GATT_SUCCESS) {
/* autoconnect connection failed, that's ok */
if (!leAudioDevice->connecting_actively_) return;
LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: "
<< +status;
callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
return;
}
if (controller_get_interface()->supports_ble_2m_phy()) {
LOG(INFO) << address << " set preferred PHY to 2M";
BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0);
}
BTM_RequestPeerSCA(leAudioDevice->address_, transport);
leAudioDevice->connecting_actively_ = false;
leAudioDevice->conn_id_ = conn_id;
if (mtu == GATT_DEF_BLE_MTU_SIZE) {
LOG(INFO) << __func__ << ", Configure MTU";
BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, 240);
}
/* If we know services, register for notifications */
if (leAudioDevice->known_service_handles_)
RegisterKnownNotifications(leAudioDevice);
if (BTM_SecIsSecurityPending(address)) {
/* if security collision happened, wait for encryption done
* (BTA_GATTC_ENC_CMPL_CB_EVT) */
return;
}
/* verify bond */
if (BTM_IsEncrypted(address, BT_TRANSPORT_LE)) {
/* if link has been encrypted */
OnEncryptionComplete(address, BTM_SUCCESS);
return;
}
if (BTM_IsLinkKeyKnown(address, BT_TRANSPORT_LE)) {
int result = BTM_SetEncryption(
address, BT_TRANSPORT_LE,
[](const RawAddress* bd_addr, tBT_TRANSPORT transport,
void* p_ref_data, tBTM_STATUS status) {
if (instance) instance->OnEncryptionComplete(*bd_addr, status);
},
nullptr, BTM_BLE_SEC_ENCRYPT);
LOG(INFO) << __func__
<< "Encryption required. Request result: " << result;
return;
}
LOG(ERROR) << __func__ << " Encryption error";
}
void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) {
LOG(INFO) << __func__ << " device: " << leAudioDevice->address_;
/* GATTC will ommit not registered previously handles */
for (auto pac_tuple : leAudioDevice->snk_pacs_) {
BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
std::get<0>(pac_tuple).val_hdl);
}
for (auto pac_tuple : leAudioDevice->src_pacs_) {
BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
std::get<0>(pac_tuple).val_hdl);
}
if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0)
BTA_GATTC_RegisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->snk_audio_locations_hdls_.val_hdl);
if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0)
BTA_GATTC_RegisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->src_audio_locations_hdls_.val_hdl);
if (leAudioDevice->audio_avail_hdls_.val_hdl != 0)
BTA_GATTC_RegisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->audio_avail_hdls_.val_hdl);
if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0)
BTA_GATTC_RegisterForNotifications(
gatt_if_, leAudioDevice->address_,
leAudioDevice->audio_supp_cont_hdls_.val_hdl);
if (leAudioDevice->ctp_hdls_.val_hdl != 0)
BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
leAudioDevice->ctp_hdls_.val_hdl);
for (struct ase& ase : leAudioDevice->ases_)
BTA_GATTC_RegisterForNotifications(gatt_if_, leAudioDevice->address_,
ase.hdls.val_hdl);
}
void OnEncryptionComplete(const RawAddress& address, uint8_t status) {
LOG(INFO) << __func__ << " " << address << "status: " << int{status};
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (leAudioDevice == NULL) {
LOG(WARNING) << "Skipping unknown device" << address;
return;
}
if (status != BTM_SUCCESS) {
LOG(ERROR) << "Encryption failed"
<< " status: " << int{status};
BTA_GATTC_Close(leAudioDevice->conn_id_);
if (leAudioDevice->connecting_actively_) {
callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
}
return;
}
if (leAudioDevice->encrypted_) {
LOG(INFO) << __func__ << " link already encrypted, nothing to do";
return;
}
leAudioDevice->encrypted_ = true;
/* If we know services and read is not ongoing, this is reconnection and
* just notify connected */
if (leAudioDevice->known_service_handles_ &&
!leAudioDevice->notify_connected_after_read_) {
connectionReady(leAudioDevice);
return;
}
BTA_GATTC_ServiceSearchRequest(
leAudioDevice->conn_id_,
&le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
}
void OnGattDisconnected(uint16_t conn_id, tGATT_IF client_if,
RawAddress address, tGATT_DISCONN_REASON reason) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
LOG(ERROR) << ", skipping unknown leAudioDevice, address: " << address;
return;
}
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice);
DeregisterNotifications(leAudioDevice);
callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
leAudioDevice->encrypted_ = false;
if (leAudioDevice->removing_device_) {
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
auto group = aseGroups_.FindById(leAudioDevice->group_id_);
group_remove_node(group, address, true);
}
leAudioDevices_.Remove(address);
}
}
bool subscribe_for_indications(uint16_t conn_id, const RawAddress& address,
uint16_t handle, uint16_t ccc_handle,
bool ntf) {
std::vector<uint8_t> value(2);
uint8_t* ptr = value.data();
if (BTA_GATTC_RegisterForNotifications(gatt_if_, address, handle) !=
GATT_SUCCESS) {
LOG(ERROR) << __func__ << ", cannot register for notification: "
<< static_cast<int>(handle);
return false;
}
UINT16_TO_STREAM(ptr, ntf ? GATT_CHAR_CLIENT_CONFIG_NOTIFICATION
: GATT_CHAR_CLIENT_CONFIG_INDICTION);
BtaGattQueue::WriteDescriptor(
conn_id, ccc_handle, std::move(value), GATT_WRITE,
[](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len,
const uint8_t* value, void* data) {
if (instance) instance->OnGattWriteCcc(conn_id, status, handle, data);
},
nullptr);
return true;
}
/* Find the handle for the client characteristics configuration of a given
* characteristics.
*/
uint16_t find_ccc_handle(const gatt::Characteristic& charac) {
auto iter = std::find_if(
charac.descriptors.begin(), charac.descriptors.end(),
[](const auto& desc) {
return desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG);
});
return iter == charac.descriptors.end() ? 0 : (*iter).handle;
}
void OnServiceChangeEvent(const RawAddress& address) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
DLOG(ERROR) << __func__
<< ", skipping unknown leAudioDevice, address: " << address;
return;
}
LOG(INFO) << __func__ << ": address=" << address;
leAudioDevice->known_service_handles_ = false;
leAudioDevice->csis_member_ = false;
BtaGattQueue::Clean(leAudioDevice->conn_id_);
DeregisterNotifications(leAudioDevice);
}
void OnGattServiceDiscoveryDone(const RawAddress& address) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
DLOG(ERROR) << __func__
<< ", skipping unknown leAudioDevice, address: " << address;
return;
}
if (!leAudioDevice->known_service_handles_)
BTA_GATTC_ServiceSearchRequest(
leAudioDevice->conn_id_,
&le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
}
/* This method is called after connection beginning to identify and initialize
* a le audio device. Any missing mandatory attribute will result in reverting
* and cleaning up device.
*/
void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
if (!leAudioDevice) {
DLOG(ERROR) << __func__ << ", skipping unknown leAudioDevice, conn_id: "
<< loghex(conn_id);
return;
}
LOG(INFO) << __func__ << " test csis_member "
<< leAudioDevice->csis_member_;
if (status != GATT_SUCCESS) {
/* close connection and report service discovery complete with error */
LOG(ERROR) << "Service discovery failed";
DisconnectDevice(leAudioDevice);
return;
}
const std::list<gatt::Service>* services = BTA_GATTC_GetServices(conn_id);
const gatt::Service* pac_svc = nullptr;
const gatt::Service* ase_svc = nullptr;
std::vector<uint16_t> csis_primary_handles;
uint16_t cas_csis_included_handle = 0;
for (const gatt::Service& tmp : *services) {
if (tmp.uuid == le_audio::uuid::kPublishedAudioCapabilityServiceUuid) {
LOG(INFO) << "Found Audio Capability service, handle: "
<< loghex(tmp.handle);
pac_svc = &tmp;
} else if (tmp.uuid == le_audio::uuid::kAudioStreamControlServiceUuid) {
LOG(INFO) << "Found Audio Stream Endpoint service, handle: "
<< loghex(tmp.handle);
ase_svc = &tmp;
} else if (tmp.uuid == bluetooth::csis::kCsisServiceUuid) {
LOG(INFO) << "Found CSIS service, handle: " << loghex(tmp.handle)
<< " is primary? " << tmp.is_primary;
if (tmp.is_primary) csis_primary_handles.push_back(tmp.handle);
} else if (tmp.uuid == le_audio::uuid::kCapServiceUuid) {
LOG(INFO) << "Found CAP Service, handle: " << loghex(tmp.handle);
/* Try to find context for CSIS instances */
for (auto& included_srvc : tmp.included_services) {
if (included_srvc.uuid == bluetooth::csis::kCsisServiceUuid) {
LOG(INFO) << __func__ << " CSIS included into CAS";
if (bluetooth::csis::CsisClient::IsCsisClientRunning())
cas_csis_included_handle = included_srvc.start_handle;
break;
}
}
}
}
/* Check if CAS includes primary CSIS service */
if (!csis_primary_handles.empty() && cas_csis_included_handle) {
auto iter =
std::find(csis_primary_handles.begin(), csis_primary_handles.end(),
cas_csis_included_handle);
if (iter != csis_primary_handles.end())
leAudioDevice->csis_member_ = true;
}
if (!pac_svc || !ase_svc) {
LOG(ERROR) << "No mandatory le audio services found";
DisconnectDevice(leAudioDevice);
return;
}
/* Refresh PACs handles */
leAudioDevice->ClearPACs();
for (const gatt::Characteristic& charac : pac_svc->characteristics) {
if (charac.uuid ==
le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid) {
struct hdl_pair hdl_pair;
hdl_pair.val_hdl = charac.value_handle;
hdl_pair.ccc_hdl = find_ccc_handle(charac);
if (hdl_pair.ccc_hdl == 0) {
LOG(ERROR) << __func__ << ", snk pac char doesn't have ccc";
DisconnectDevice(leAudioDevice);
return;
}
if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
hdl_pair.val_hdl, hdl_pair.ccc_hdl,
true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state of sink PACs */
BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl,
OnGattReadRspStatic, NULL);
leAudioDevice->snk_pacs_.push_back(std::make_tuple(
hdl_pair, std::vector<struct le_audio::types::acs_ac_record>()));
LOG(INFO) << "Found Sink PAC characteristic, handle: "
<< loghex(charac.value_handle)
<< ", ccc handle: " << loghex(hdl_pair.ccc_hdl);
} else if (charac.uuid ==
le_audio::uuid::
kSourcePublishedAudioCapabilityCharacteristicUuid) {
struct hdl_pair hdl_pair;
hdl_pair.val_hdl = charac.value_handle;
hdl_pair.ccc_hdl = find_ccc_handle(charac);
if (hdl_pair.ccc_hdl == 0) {
LOG(ERROR) << __func__ << ", src pac char doesn't have ccc";
DisconnectDevice(leAudioDevice);
return;
}
if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
hdl_pair.val_hdl, hdl_pair.ccc_hdl,
true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state of source PACs */
BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl,
OnGattReadRspStatic, NULL);
leAudioDevice->src_pacs_.push_back(std::make_tuple(
hdl_pair, std::vector<struct le_audio::types::acs_ac_record>()));
LOG(INFO) << "Found Source PAC characteristic, handle: "
<< loghex(charac.value_handle)
<< ", ccc handle: " << loghex(hdl_pair.ccc_hdl);
} else if (charac.uuid ==
le_audio::uuid::kSinkAudioLocationCharacteristicUuid) {
leAudioDevice->snk_audio_locations_hdls_.val_hdl = charac.value_handle;
leAudioDevice->snk_audio_locations_hdls_.ccc_hdl =
find_ccc_handle(charac);
if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl == 0)
LOG(INFO) << __func__
<< ", snk audio locations char doesn't have"
"ccc";
if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl != 0 &&
!subscribe_for_indications(
conn_id, leAudioDevice->address_,
leAudioDevice->snk_audio_locations_hdls_.val_hdl,
leAudioDevice->snk_audio_locations_hdls_.ccc_hdl, true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state of sink audio locations */
BtaGattQueue::ReadCharacteristic(
conn_id, leAudioDevice->snk_audio_locations_hdls_.val_hdl,
OnGattReadRspStatic, NULL);
LOG(INFO) << "Found Sink audio locations characteristic, handle: "
<< loghex(charac.value_handle) << ", ccc handle: "
<< loghex(leAudioDevice->snk_audio_locations_hdls_.ccc_hdl);
} else if (charac.uuid ==
le_audio::uuid::kSourceAudioLocationCharacteristicUuid) {
leAudioDevice->src_audio_locations_hdls_.val_hdl = charac.value_handle;
leAudioDevice->src_audio_locations_hdls_.ccc_hdl =
find_ccc_handle(charac);
if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl == 0)
LOG(INFO) << __func__
<< ", snk audio locations char doesn't have"
"ccc";
if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl != 0 &&
!subscribe_for_indications(
conn_id, leAudioDevice->address_,
leAudioDevice->src_audio_locations_hdls_.val_hdl,
leAudioDevice->src_audio_locations_hdls_.ccc_hdl, true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state of source audio locations */
BtaGattQueue::ReadCharacteristic(
conn_id, leAudioDevice->src_audio_locations_hdls_.val_hdl,
OnGattReadRspStatic, NULL);
LOG(INFO) << "Found Source audio locations characteristic, handle: "
<< loghex(charac.value_handle) << ", ccc handle: "
<< loghex(leAudioDevice->src_audio_locations_hdls_.ccc_hdl);
} else if (charac.uuid ==
le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid) {
leAudioDevice->audio_avail_hdls_.val_hdl = charac.value_handle;
leAudioDevice->audio_avail_hdls_.ccc_hdl = find_ccc_handle(charac);
if (leAudioDevice->audio_avail_hdls_.ccc_hdl == 0) {
LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc";
DisconnectDevice(leAudioDevice);
return;
}
if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
leAudioDevice->audio_avail_hdls_.val_hdl,
leAudioDevice->audio_avail_hdls_.ccc_hdl,
true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state */
BtaGattQueue::ReadCharacteristic(
conn_id, leAudioDevice->audio_avail_hdls_.val_hdl,
OnGattReadRspStatic, NULL);
LOG(INFO) << "Found Audio Availability Context characteristic, handle: "
<< loghex(charac.value_handle) << ", ccc handle: "
<< loghex(leAudioDevice->audio_avail_hdls_.ccc_hdl);
} else if (charac.uuid ==
le_audio::uuid::kAudioSupportedContextCharacteristicUuid) {
leAudioDevice->audio_supp_cont_hdls_.val_hdl = charac.value_handle;
leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = find_ccc_handle(charac);
if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl == 0)
LOG(INFO) << __func__ << ", audio avails char doesn't have ccc";
if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl != 0 &&
!subscribe_for_indications(
conn_id, leAudioDevice->address_,
leAudioDevice->audio_supp_cont_hdls_.val_hdl,
leAudioDevice->audio_supp_cont_hdls_.ccc_hdl, true)) {
DisconnectDevice(leAudioDevice);
return;
}
/* Obtain initial state */
BtaGattQueue::ReadCharacteristic(
conn_id, leAudioDevice->audio_supp_cont_hdls_.val_hdl,
OnGattReadRspStatic, NULL);
LOG(INFO) << "Found Audio Supported Context characteristic, handle: "
<< loghex(charac.value_handle) << ", ccc handle: "
<< loghex(leAudioDevice->audio_supp_cont_hdls_.ccc_hdl);
}
}
/* Refresh ASE handles */
leAudioDevice->ases_.clear();
for (const gatt::Characteristic& charac : ase_svc->characteristics) {
LOG(INFO) << "Found characteristic, uuid: " << charac.uuid.ToString();
if (charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid ||
charac.uuid == le_audio::uuid::kSourceAudioStreamEndpointUuid) {
uint16_t ccc_handle = find_ccc_handle(charac);
if (ccc_handle == 0) {
LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc";
DisconnectDevice(leAudioDevice);
return;
}
if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
charac.value_handle, ccc_handle, true)) {
DisconnectDevice(leAudioDevice);
return;
}
int direction =
charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid
? le_audio::types::kLeAudioDirectionSink
: le_audio::types::kLeAudioDirectionSource;
leAudioDevice->ases_.emplace_back(charac.value_handle, ccc_handle,
direction);
LOG(INFO) << "Found ASE characteristic, handle: "
<< loghex(charac.value_handle)
<< ", ccc handle: " << loghex(ccc_handle)
<< ", direction: " << direction;
} else if (charac.uuid ==
le_audio::uuid::
kAudioStreamEndpointControlPointCharacteristicUuid) {
leAudioDevice->ctp_hdls_.val_hdl = charac.value_handle;
leAudioDevice->ctp_hdls_.ccc_hdl = find_ccc_handle(charac);
if (leAudioDevice->ctp_hdls_.ccc_hdl == 0) {
LOG(ERROR) << __func__ << ", ase ctp doesn't have ccc";
DisconnectDevice(leAudioDevice);
return;
}
if (!subscribe_for_indications(conn_id, leAudioDevice->address_,
leAudioDevice->ctp_hdls_.val_hdl,
leAudioDevice->ctp_hdls_.ccc_hdl,
true)) {
DisconnectDevice(leAudioDevice);
return;
}
LOG(INFO) << "Found ASE Control Point characteristic, handle: "
<< loghex(charac.value_handle) << ", ccc handle: "
<< loghex(leAudioDevice->ctp_hdls_.ccc_hdl);
}
}
leAudioDevice->known_service_handles_ = true;
leAudioDevice->notify_connected_after_read_ = true;
/* If already known group id */
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
AseInitialStateReadRequest(leAudioDevice);
return;
}
/* If device does not belong to any group yet we either add it to the
* group by our selfs now or wait for Csis to do it. In both cases, let's
* check if group is already assigned.
*/
int group_id = DeviceGroups::Get()->GetGroupId(
leAudioDevice->address_, le_audio::uuid::kCapServiceUuid);
if (group_id != bluetooth::groups::kGroupUnknown) {
instance->group_add_node(group_id, leAudioDevice->address_);
return;
}
/* CSIS will trigger adding to group */
if (leAudioDevice->csis_member_) {
LOG(INFO) << __func__ << " waiting for CSIS to create group for device "
<< leAudioDevice->address_;
return;
}
/* If there is no Csis just add device by our own */
DeviceGroups::Get()->AddDevice(leAudioDevice->address_,
le_audio::uuid::kCapServiceUuid);
}
void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl,
void* data) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
std::vector<struct ase>::iterator ase_it;
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", unknown conn_id=" << loghex(conn_id);
return;
}
if (status == GATT_SUCCESS) {
LOG(INFO) << __func__
<< ", successfully registered on ccc: " << loghex(hdl);
return;
}
LOG(ERROR) << __func__
<< ", Failed to register for indications: " << loghex(hdl)
<< ", status: " << loghex((int)(status));
ase_it =
std::find_if(leAudioDevice->ases_.begin(), leAudioDevice->ases_.end(),
[&hdl](const struct ase& ase) -> bool {
return ase.hdls.ccc_hdl == hdl;
});
if (ase_it == leAudioDevice->ases_.end()) {
LOG(ERROR) << __func__
<< ", unknown ccc handle: " << static_cast<int>(hdl);
return;
}
BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_,
ase_it->hdls.val_hdl);
}
void AttachToStreamingGroupIfNeeded(LeAudioDevice* leAudioDevice) {
if (leAudioDevice->group_id_ != active_group_id_) {
LOG(INFO) << __func__ << " group " << leAudioDevice->group_id_
<< " is not streaming. Nothing to do";
return;
}
LOG(INFO) << __func__ << " attaching to group "
<< leAudioDevice->group_id_;
/* Restore configuration */
LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_);
auto* stream_conf = &group->stream_conf;
if (!stream_conf->valid) {
LOG(ERROR) << __func__
<< " Configuration not valid. (btw not sure we need this "
"flag)";
return;
}
le_audio::types::AudioLocations sink_group_audio_locations = 0;
uint8_t sink_num_of_active_ases = 0;
for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
sink_group_audio_locations |= audio_location;
sink_num_of_active_ases++;
}
le_audio::types::AudioLocations source_group_audio_locations = 0;
uint8_t source_num_of_active_ases = 0;
for (auto [cis_handle, audio_location] : stream_conf->source_streams) {
source_group_audio_locations |= audio_location;
source_num_of_active_ases++;
}
for (auto& ent : stream_conf->conf->confs) {
if (ent.direction == le_audio::types::kLeAudioDirectionSink) {
/* Sink*/
if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(),
&sink_num_of_active_ases,
sink_group_audio_locations,
source_group_audio_locations, true)) {
LOG(INFO) << __func__ << " Could not set sink configuration of "
<< stream_conf->conf->name;
return;
}
} else {
/* Source*/
if (!leAudioDevice->ConfigureAses(ent, group->GetCurrentContextType(),
&source_num_of_active_ases,
sink_group_audio_locations,
source_group_audio_locations, true)) {
LOG(INFO) << __func__ << " Could not set source configuration of "
<< stream_conf->conf->name;
return;
}
}
}
groupStateMachine_->AttachToStream(group, leAudioDevice);
}
void connectionReady(LeAudioDevice* leAudioDevice) {
callbacks_->OnConnectionState(ConnectionState::CONNECTED,
leAudioDevice->address_);
if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
UpdateContextAndLocations(group, leAudioDevice);
AttachToStreamingGroupIfNeeded(leAudioDevice);
}
if (leAudioDevice->first_connection_) {
btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true);
leAudioDevice->first_connection_ = false;
}
}
bool IsAseAcceptingAudioData(struct ase* ase) {
if (ase == nullptr) return false;
if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) return false;
if (ase->data_path_state != AudioStreamDataPathState::DATA_PATH_ESTABLISHED)
return false;
return true;
}
void get_left_and_right_stream(const std::vector<uint8_t>& data,
std::vector<int16_t>& chan_left,
std::vector<int16_t>& chan_right,
bool prepare_mono = false) {
uint16_t num_of_frames_per_ch = lc3_encoder->lc3Config.NF;
chan_left.reserve(num_of_frames_per_ch);
chan_right.reserve(num_of_frames_per_ch);
for (int i = 0; i < num_of_frames_per_ch; i++) {
const uint8_t* sample = data.data() + i * 4;
int16_t left = (int16_t)((*(sample + 1) << 8) + *sample) >> 1;
sample += 2;
int16_t right = (int16_t)((*(sample + 1) << 8) + *sample) >> 1;
if (prepare_mono) {
uint16_t mono_data = (int16_t)(((uint32_t)left + (uint32_t)right) >> 1);
left = mono_data;
right = mono_data;
}
chan_left.push_back(left);
chan_right.push_back(right);
}
}
void PrepareAndSendToTwoDevices(
const std::vector<uint8_t>& data,
struct le_audio::stream_configuration* stream_conf) {
uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
uint16_t left_cis_handle = 0;
uint16_t right_cis_handle = 0;
uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF;
for (auto [cis_handle, audio_location] : stream_conf->sink_streams) {
if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft)
left_cis_handle = cis_handle;
if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight)
right_cis_handle = cis_handle;
}
if (data.size() < 2 /* bytes per sample */ * 2 /* channels */ *
number_of_required_samples_per_channel) {
LOG(ERROR) << __func__ << "Missing samples";
return;
}
bool mono = (left_cis_handle == 0) || (right_cis_handle == 0);
std::vector<int16_t> chan_left;
std::vector<int16_t> chan_right;
get_left_and_right_stream(data, chan_left, chan_right, mono);
std::vector<uint8_t> chan_left_enc(byte_count, 0);
std::vector<uint8_t> chan_right_enc(byte_count, 0);
uint8_t err = 0;
if (left_cis_handle)
err |= lc3_encoder->run((const int16_t*)chan_left.data(),
chan_left_enc.size(), chan_left_enc.data(), 0);
if (right_cis_handle)
err |= lc3_encoder->run((const int16_t*)chan_right.data(),
chan_right_enc.size(), chan_right_enc.data(), 1);
if (err != Lc3Encoder::ERROR_FREE) {
LOG(ERROR) << " error while encoding; error code: "
<< "\t encoded samples: " << chan_left_enc.size()
<< "\t err: " << static_cast<uint8_t>(err);
return;
}
/* Send data to the controller */
if (left_cis_handle)
IsoManager::GetInstance()->SendIsoData(
left_cis_handle, chan_left_enc.data(), chan_left_enc.size());
if (right_cis_handle)
IsoManager::GetInstance()->SendIsoData(
right_cis_handle, chan_right_enc.data(), chan_right_enc.size());
}
void PrepareAndSendToSingleDevice(
const std::vector<uint8_t>& data,
struct le_audio::stream_configuration* stream_conf) {
int num_channels = lc3_encoder->lc3Config.Nc;
uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
auto cis_handle = stream_conf->sink_streams.front().first;
uint16_t number_of_required_samples_per_channel = lc3_encoder->lc3Config.NF;
if ((int)data.size() < (2 /* bytes per sample */ * num_channels *
number_of_required_samples_per_channel)) {
LOG(ERROR) << __func__ << "Missing samples";
return;
}
std::vector<uint8_t> chan_encoded(num_channels * byte_count, 0);
uint8_t err = 0;
if (num_channels == 1) {
err = lc3_encoder->run((const int16_t*)data.data(), byte_count,
chan_encoded.data(), 0);
} else {
std::vector<int16_t> chan_left;
std::vector<int16_t> chan_right;
get_left_and_right_stream(data, chan_left, chan_right, false);
err |= lc3_encoder->run((const int16_t*)chan_left.data(), byte_count,
chan_encoded.data(), 0);
err |= lc3_encoder->run((const int16_t*)chan_right.data(), byte_count,
chan_encoded.data() + byte_count, 1);
}
if (err != Lc3Encoder::ERROR_FREE) {
LOG(ERROR) << " error while encoding; error code: "
<< "\t err: " << static_cast<uint8_t>(err);
return;
}
/* Send data to the controller */
IsoManager::GetInstance()->SendIsoData(cis_handle, chan_encoded.data(),
chan_encoded.size());
}
struct le_audio::stream_configuration* GetStreamSinkConfiguration(
LeAudioDeviceGroup* group) {
struct le_audio::stream_configuration* stream_conf = &group->stream_conf;
int num_of_devices = 0;
int num_of_channels = 0;
uint32_t sample_freq_hz = 0;
uint32_t frame_duration_us = 0;
uint16_t octets_per_frame = 0;
LOG(INFO) << __func__ << " group_id: " << group->group_id_;
/* This contains pair of cis handle and audio location */
std::vector<std::pair<uint16_t, uint32_t>> streams;
for (auto* device = group->GetFirstActiveDevice(); device != nullptr;
device = group->GetNextActiveDevice(device)) {
auto* ase = device->GetFirstActiveAseByDirection(
le_audio::types::kLeAudioDirectionSink);
if (ase) {
LOG(INFO) << __func__ << "device: " << device->address_;
num_of_devices++;
}
for (; ase != nullptr;
ase = device->GetNextActiveAseWithSameDirection(ase)) {
streams.emplace_back(std::make_pair(
ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
num_of_channels += ase->codec_config.channel_count;
if (sample_freq_hz == 0) {
sample_freq_hz = ase->codec_config.GetSamplingFrequencyHz();
} else {
LOG_ASSERT(sample_freq_hz ==
ase->codec_config.GetSamplingFrequencyHz())
<< __func__ << " sample freq mismatch: " << +sample_freq_hz
<< " != " << ase->codec_config.GetSamplingFrequencyHz();
}
if (frame_duration_us == 0) {
frame_duration_us = ase->codec_config.GetFrameDurationUs();
} else {
LOG_ASSERT(frame_duration_us ==
ase->codec_config.GetFrameDurationUs())
<< __func__ << " frame duration mismatch: " << +frame_duration_us
<< " != " << ase->codec_config.GetFrameDurationUs();
}
if (octets_per_frame == 0) {
octets_per_frame = ase->codec_config.octets_per_codec_frame;
} else {
LOG_ASSERT(octets_per_frame ==
ase->codec_config.octets_per_codec_frame)
<< __func__ << " octets per frame mismatch: " << +octets_per_frame
<< " != " << ase->codec_config.octets_per_codec_frame;
}
LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl
<< " to stream. Allocation: "
<< +ase->codec_config.audio_channel_allocation
<< " sample_freq: " << +sample_freq_hz
<< " frame_duration: " << +frame_duration_us
<< " octects per frame: " << +octets_per_frame;
}
}
if (streams.empty()) return nullptr;
stream_conf->sink_streams = std::move(streams);
stream_conf->sink_num_of_devices = num_of_devices;
stream_conf->sink_num_of_channels = num_of_channels;
stream_conf->sink_sample_frequency_hz = sample_freq_hz;
stream_conf->sink_frame_duration_us = frame_duration_us;
stream_conf->sink_octets_per_codec_frame = octets_per_frame;
stream_conf->valid = true;
stream_conf->conf = group->GetActiveConfiguration();
LOG(INFO) << __func__ << " configuration: " << stream_conf->conf->name;
return stream_conf;
}
void OnAudioDataReady(const std::vector<uint8_t>& data) {
if (active_group_id_ == bluetooth::groups::kGroupUnknown ||
!audio_source_ready_to_send)
return;
LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_);
if (!group) {
LOG(ERROR) << __func__ << "There is no streaming group available";
return;
}
auto stream_conf = group->stream_conf;
if (!stream_conf.valid || (stream_conf.sink_num_of_devices > 2)) {
LOG(ERROR) << __func__ << " Stream configufation is not valid.";
return;
}
if (stream_conf.sink_num_of_devices == 2) {
PrepareAndSendToTwoDevices(data, &stream_conf);
} else {
PrepareAndSendToSingleDevice(data, &stream_conf);
}
}
void SendAudioData(uint8_t* data, uint16_t size) {
/* Get only one channel for MONO microphone */
/* Gather data for channel */
uint16_t required_for_channel_byte_count =
lc3_decoder->lc3Config.getByteCountFromBitrate(32000);
size_t required_byte_count = current_sink_codec_config.num_channels *
required_for_channel_byte_count;
if (required_byte_count != size) {
LOG(ERROR) << "Insufficient data for decoding and send, required: "
<< int(required_byte_count) << ", received: " << int(size);
return;
}
uint8_t BEC_detect = 0;
std::vector<int16_t> pcm_data_decoded(lc3_decoder->lc3Config.NF, 0);
auto err = lc3_decoder->run(data, required_for_channel_byte_count, 0,
pcm_data_decoded.data(),
pcm_data_decoded.size(), BEC_detect);
/* TODO: How handle failing decoding ? */
if (err != Lc3Decoder::ERROR_FREE) {
LOG(ERROR) << " error while decoding error code: "
<< static_cast<int>(err);
return;
}
uint16_t to_write = sizeof(int16_t) * pcm_data_decoded.size();
uint16_t written = LeAudioClientAudioSink::SendData(
(uint8_t*)pcm_data_decoded.data(), to_write);
/* TODO: What to do if not all data sinked ? */
if (written != to_write) LOG(ERROR) << __func__ << ", not all data sinked";
LOG(INFO) << __func__;
}
static inline Lc3Config::FrameDuration Lc3ConfigFrameDuration(
uint32_t frame_duration_us) {
if (frame_duration_us == LeAudioCodecConfiguration::kInterval7500Us)
return Lc3Config::FrameDuration::d7p5ms;
else
return Lc3Config::FrameDuration::d10ms;
}
bool StartSendingAudio(int group_id) {
LOG(INFO) << __func__;
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
LeAudioDevice* device = group->GetFirstActiveDevice();
LOG_ASSERT(device) << __func__
<< " Shouldn't be called without an active device.";
/* Assume 2 ases max just for now. */
auto* stream_conf = GetStreamSinkConfiguration(group);
if (stream_conf == nullptr) {
LOG(ERROR) << __func__ << " could not get sink configuration";
return false;
}
if (lc3_encoder) {
LOG(WARNING)
<< " The encoder instance should have been already released.";
delete lc3_encoder;
lc3_encoder = nullptr;
}
/* One or multiple audio channels encoder */
lc3_encoder = new Lc3Encoder(Lc3Config(
current_source_codec_config.sample_rate,
Lc3ConfigFrameDuration(current_source_codec_config.data_interval_us),
current_source_codec_config.num_channels));
uint16_t remote_delay_ms =
group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink);
LeAudioClientAudioSource::UpdateRemoteDelay(remote_delay_ms);
LeAudioClientAudioSource::ConfirmStreamingRequest();
return true;
}
struct le_audio::stream_configuration* GetStreamSourceConfiguration(
LeAudioDeviceGroup* group) {
LeAudioDevice* device = group->GetFirstActiveDevice();
LOG_ASSERT(device) << __func__
<< " Shouldn't be called without an active device.";
ase* ase = device->GetFirstActiveAseByDirection(
le_audio::types::kLeAudioDirectionSource);
if (!ase) return nullptr;
/* For now we support one microphone only*/
auto* stream_conf = &group->stream_conf;
std::vector<std::pair<uint16_t, uint32_t>> streams;
stream_conf->source_streams.emplace_back(std::make_pair(
ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
stream_conf->source_num_of_devices = 1;
stream_conf->source_num_of_channels = 1;
stream_conf->source_sample_frequency_hz =
ase->codec_config.GetSamplingFrequencyHz();
stream_conf->source_frame_duration_us =
ase->codec_config.GetFrameDurationUs();
stream_conf->source_octets_per_codec_frame =
ase->codec_config.octets_per_codec_frame;
stream_conf->valid = true;
stream_conf->conf = group->GetActiveConfiguration();
LOG(INFO) << __func__ << " Added CIS: " << +ase->cis_conn_hdl
<< " to stream. Allocation: "
<< +ase->codec_config.audio_channel_allocation
<< " sample_freq: " << +stream_conf->source_sample_frequency_hz
<< " frame_duration: " << +stream_conf->source_frame_duration_us
<< " octects per frame: "
<< +stream_conf->source_octets_per_codec_frame;
return stream_conf;
}
void StartReceivingAudio(int group_id) {
LOG(INFO) << __func__;
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
auto* stream_conf = GetStreamSourceConfiguration(group);
if (!stream_conf) {
LOG(WARNING) << " Could not get source configuration for group "
<< active_group_id_ << " probably microphone not configured";
return;
}
Lc3Config lc3Config(
current_sink_codec_config.sample_rate,
Lc3ConfigFrameDuration(current_sink_codec_config.data_interval_us), 1);
lc3_decoder = new Lc3Decoder(lc3Config);
uint16_t remote_delay_ms =
group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource);
LeAudioClientAudioSink::UpdateRemoteDelay(remote_delay_ms);
LeAudioClientAudioSink::ConfirmStreamingRequest();
}
void SuspendAudio(void) {
audio_sink_ready_to_receive = false;
audio_source_ready_to_send = false;
if (lc3_encoder) {
delete lc3_encoder;
lc3_encoder = nullptr;
}
if (lc3_decoder) {
LOG(INFO) << __func__ << " stopping sink";
delete lc3_decoder;
lc3_decoder = nullptr;
}
}
void StopAudio(void) {
SuspendAudio();
ClientAudioIntefraceRelease();
}
void Dump(int fd) {
dprintf(fd, " Active group: %d\n", active_group_id_);
dprintf(fd, " LE Audio Groups:\n");
aseGroups_.Dump(fd);
dprintf(fd, " Not grouped devices:\n");
leAudioDevices_.Dump(fd, bluetooth::groups::kGroupUnknown);
}
void Cleanup(void) {
leAudioDevices_.Cleanup();
aseGroups_.Cleanup();
StopAudio();
if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_);
}
void UpdateCurrentHalSessions(int group_id, LeAudioContextType context_type) {
if (group_id == bluetooth::groups::kGroupUnknown) {
LOG(WARNING) << ", cannot start straming if no active group set";
return;
}
auto group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(group_id);
return;
}
std::optional<LeAudioCodecConfiguration> source_configuration =
group->GetCodecConfigurationByDirection(
context_type, le_audio::types::kLeAudioDirectionSink);
std::optional<LeAudioCodecConfiguration> sink_configuration =
group->GetCodecConfigurationByDirection(
context_type, le_audio::types::kLeAudioDirectionSource);
if (source_configuration) {
bool send_active = false;
/* Stream configuration differs from previous one */
if (!current_source_codec_config.IsInvalid() &&
(*source_configuration != current_source_codec_config)) {
callbacks_->OnGroupStatus(group_id, GroupStatus::INACTIVE);
send_active = true;
LeAudioClientAudioSource::Stop();
}
current_source_codec_config = *source_configuration;
LeAudioClientAudioSource::Start(current_source_codec_config,
audioSinkReceiver);
if (send_active) {
callbacks_->OnGroupStatus(group_id, GroupStatus::ACTIVE);
}
} else {
if (!current_source_codec_config.IsInvalid()) {
LeAudioClientAudioSource::Stop();
current_source_codec_config = {0, 0, 0, 0};
}
LOG(INFO) << __func__
<< ", group does not supports source direction for"
" context: "
<< static_cast<int>(context_type);
}
if (sink_configuration) {
bool send_active = false;
/* Stream configuration differs from previous one */
if (!current_sink_codec_config.IsInvalid() &&
(*sink_configuration != current_sink_codec_config)) {
callbacks_->OnGroupStatus(group_id, GroupStatus::INACTIVE);
send_active = true;
LeAudioClientAudioSink::Stop();
}
current_sink_codec_config = *sink_configuration;
LeAudioClientAudioSink::Start(current_sink_codec_config,
audioSourceReceiver);
if (send_active) {
callbacks_->OnGroupStatus(group_id, GroupStatus::ACTIVE);
}
} else {
if (!current_sink_codec_config.IsInvalid()) {
LeAudioClientAudioSink::Stop();
current_sink_codec_config = {0, 0, 0, 0};
}
LOG(INFO) << __func__
<< ", group does not supports sink direction for"
" context: "
<< static_cast<int>(context_type);
}
}
void OnAudioResume() {
if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
LOG(WARNING) << ", cannot start straming if no active group set";
return;
}
auto group = aseGroups_.FindById(active_group_id_);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(active_group_id_);
return;
}
std::optional<LeAudioCodecConfiguration> source_configuration =
group->GetCodecConfigurationByDirection(
current_context_type_, le_audio::types::kLeAudioDirectionSink);
std::optional<LeAudioCodecConfiguration> sink_configuration =
group->GetCodecConfigurationByDirection(
current_context_type_, le_audio::types::kLeAudioDirectionSource);
/* Check if Bluetooth audio HAL session requires reconfiguration */
bool sessions_requires_update =
(((source_configuration &&
(*source_configuration != current_source_codec_config)) ||
(!source_configuration &&
!current_source_codec_config.IsInvalid()))) ||
((sink_configuration &&
(*sink_configuration != current_sink_codec_config)) ||
(!sink_configuration && !current_sink_codec_config.IsInvalid()));
if (sessions_requires_update) {
CancelStreamingRequest();
do_in_main_thread(FROM_HERE,
base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions,
base::Unretained(instance), active_group_id_,
current_context_type_));
return;
}
/* TODO check if group already started streaming */
GroupStream(active_group_id_, static_cast<uint16_t>(current_context_type_));
}
void OnAudioSuspend() {
if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
LOG(WARNING) << ", there is no longer active group";
return;
}
GroupStop(active_group_id_);
}
void OnAudioSinkSuspend() {
LOG(INFO) << __func__;
if (!audio_source_ready_to_send) return;
audio_source_ready_to_send = false;
/* Last suspends group - triggers group stop */
if (!audio_sink_ready_to_receive && !audio_source_ready_to_send)
OnAudioSuspend();
}
void OnAudioSinkResume() {
LOG(INFO) << __func__;
auto group = aseGroups_.FindById(active_group_id_);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(active_group_id_);
return;
}
/* Check if the device resume is expected */
if (!group->GetCodecConfigurationByDirection(
current_context_type_, le_audio::types::kLeAudioDirectionSink)) {
LOG(ERROR) << __func__ << ", invalid resume request for context type: "
<< loghex(static_cast<int>(current_context_type_));
LeAudioClientAudioSource::CancelStreamingRequest();
return;
}
/* First resume request from sink/source triggers group start */
if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) {
audio_source_ready_to_send = true;
OnAudioResume();
return;
}
if (audio_sink_ready_to_receive) {
LOG(INFO) << __func__ << " audio_sink_ready_to_receive";
audio_source_ready_to_send = true;
/* If signalling part is completed trigger start reveivin audio here,
* otherwise it'll be called on group streaming state callback
*/
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
StartSendingAudio(active_group_id_);
} else {
/* Ask framework to come back later */
DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n"
<< " audio_sink_ready_to_receive: "
<< audio_sink_ready_to_receive << "\n"
<< " audio_source_ready_to_send:" << audio_source_ready_to_send
<< "\n"
<< " current_context_type_: "
<< static_cast<int>(current_context_type_) << "\n"
<< " group exist? " << (group ? " yes " : " no ") << "\n";
CancelStreamingRequest();
}
}
void OnAudioSourceSuspend() {
LOG(INFO) << __func__;
if (!audio_sink_ready_to_receive) return;
audio_sink_ready_to_receive = false;
/* Last suspends group - triggers group stop */
if (!audio_sink_ready_to_receive && !audio_source_ready_to_send)
OnAudioSuspend();
}
void OnAudioSourceResume() {
LOG(INFO) << __func__;
auto group = aseGroups_.FindById(active_group_id_);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(active_group_id_);
return;
}
/* Check if the device resume is expected */
if (!group->GetCodecConfigurationByDirection(
current_context_type_, le_audio::types::kLeAudioDirectionSource)) {
LOG(ERROR) << __func__ << ", invalid resume request for context type: "
<< loghex(static_cast<int>(current_context_type_));
LeAudioClientAudioSink::CancelStreamingRequest();
return;
}
/* First resume request from sink/source triggers group start */
if (!audio_sink_ready_to_receive && !audio_source_ready_to_send) {
OnAudioResume();
audio_sink_ready_to_receive = true;
return;
}
if (audio_source_ready_to_send) {
audio_sink_ready_to_receive = true;
/* If signalling part is completed trigger start reveivin audio here,
* otherwise it'll be called on group streaming state callback
*/
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)
StartReceivingAudio(active_group_id_);
}
}
LeAudioContextType AudioContentToLeAudioContext(
audio_content_type_t content_type, audio_usage_t usage) {
switch (content_type) {
case AUDIO_CONTENT_TYPE_SPEECH:
return LeAudioContextType::CONVERSATIONAL;
case AUDIO_CONTENT_TYPE_MUSIC:
case AUDIO_CONTENT_TYPE_MOVIE:
case AUDIO_CONTENT_TYPE_SONIFICATION:
return LeAudioContextType::MEDIA;
default:
break;
}
/* Context is not clear, consider also usage of stream */
switch (usage) {
case AUDIO_USAGE_VOICE_COMMUNICATION:
return LeAudioContextType::CONVERSATIONAL;
case AUDIO_USAGE_GAME:
return LeAudioContextType::GAME;
case AUDIO_USAGE_NOTIFICATION:
return LeAudioContextType::NOTIFICATIONS;
case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
return LeAudioContextType::RINGTONE;
case AUDIO_USAGE_ALARM:
return LeAudioContextType::ALERTS;
case AUDIO_USAGE_EMERGENCY:
return LeAudioContextType::EMERGENCYALARM;
default:
break;
}
return LeAudioContextType::MEDIA;
}
LeAudioContextType ChooseContextType(
std::vector<LeAudioContextType>& available_contents) {
/* Mini policy. Voice is prio 1, media is prio 2 */
auto iter = find(available_contents.begin(), available_contents.end(),
LeAudioContextType::CONVERSATIONAL);
if (iter != available_contents.end())
return LeAudioContextType::CONVERSATIONAL;
iter = find(available_contents.begin(), available_contents.end(),
LeAudioContextType::MEDIA);
if (iter != available_contents.end()) return LeAudioContextType::MEDIA;
/*TODO do something smarter here */
return available_contents[0];
}
void OnAudioMetadataUpdate(const source_metadata_t& source_metadata) {
auto tracks = source_metadata.tracks;
auto track_count = source_metadata.track_count;
std::vector<LeAudioContextType> contexts;
while (track_count) {
DLOG(INFO) << __func__ << ": usage=" << tracks->usage
<< ", content_type=" << tracks->content_type
<< ", gain=" << tracks->gain;
auto new_context =
AudioContentToLeAudioContext(tracks->content_type, tracks->usage);
contexts.push_back(new_context);
--track_count;
++tracks;
}
auto new_context = ChooseContextType(contexts);
auto group = aseGroups_.FindById(active_group_id_);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(active_group_id_);
return;
}
if ((new_context != current_context_type_) &&
(group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
LOG(WARNING) << ", cannot start straming if no active group set";
return;
}
std::optional<LeAudioCodecConfiguration> source_configuration =
group->GetCodecConfigurationByDirection(
new_context, le_audio::types::kLeAudioDirectionSink);
std::optional<LeAudioCodecConfiguration> sink_configuration =
group->GetCodecConfigurationByDirection(
new_context, le_audio::types::kLeAudioDirectionSource);
if ((source_configuration &&
(*source_configuration != current_source_codec_config)) ||
(sink_configuration &&
(*sink_configuration != current_sink_codec_config))) {
do_in_main_thread(
FROM_HERE, base::Bind(&LeAudioClientImpl::UpdateCurrentHalSessions,
base::Unretained(instance), group->group_id_,
new_context));
current_context_type_ = new_context;
GroupStop(group->group_id_);
return;
}
/* Configuration is the same for new context, just will do update
* metadata of stream
*/
GroupStream(active_group_id_, static_cast<uint16_t>(new_context));
}
current_context_type_ = new_context;
}
static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status,
uint16_t hdl, uint16_t len, uint8_t* value,
void* data) {
if (!instance) return;
if (status == GATT_SUCCESS) {
instance->LeAudioCharValueHandle(conn_id, hdl, len,
static_cast<uint8_t*>(value));
}
/* We use data to keep notify connected flag. */
if (data && !!PTR_TO_INT(data)) {
LeAudioDevice* leAudioDevice =
instance->leAudioDevices_.FindByConnId(conn_id);
leAudioDevice->notify_connected_after_read_ = false;
instance->connectionReady(leAudioDevice);
}
}
void IsoCigEventsCb(uint16_t event_type, void* data) {
switch (event_type) {
case bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl: {
auto* evt = static_cast<cig_create_cmpl_evt*>(data);
LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id);
groupStateMachine_->ProcessHciNotifOnCigCreate(
group, evt->status, evt->cig_id, evt->conn_handles);
} break;
case bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl: {
auto* evt = static_cast<cig_remove_cmpl_evt*>(data);
LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id);
groupStateMachine_->ProcessHciNotifOnCigRemove(evt->status, group);
remove_group_if_possible(group);
} break;
default:
LOG(ERROR) << __func__ << " Invalid event " << int{event_type};
}
}
void IsoCisEventsCb(uint16_t event_type, void* data) {
switch (event_type) {
case bluetooth::hci::iso_manager::kIsoEventCisDataAvailable: {
auto* event =
static_cast<bluetooth::hci::iso_manager::cis_data_evt*>(data);
if (!audio_sink_ready_to_receive) break;
SendAudioData(event->p_msg->data + event->p_msg->offset,
event->p_msg->len - event->p_msg->offset);
} break;
case bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl: {
auto* event =
static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>(
data);
LeAudioDevice* leAudioDevice =
leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl);
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: "
<< +event->cis_conn_hdl;
break;
}
LeAudioDeviceGroup* group =
aseGroups_.FindById(leAudioDevice->group_id_);
if (event->max_pdu_mtos > 0)
group->SetTransportLatency(le_audio::types::kLeAudioDirectionSink,
event->trans_lat_mtos);
if (event->max_pdu_stom > 0)
group->SetTransportLatency(le_audio::types::kLeAudioDirectionSource,
event->trans_lat_stom);
groupStateMachine_->ProcessHciNotifCisEstablished(group, leAudioDevice,
event);
} break;
case bluetooth::hci::iso_manager::kIsoEventCisDisconnected: {
auto* event =
static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>(
data);
LeAudioDevice* leAudioDevice =
leAudioDevices_.FindByCisConnHdl(event->cis_conn_hdl);
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: "
<< +event->cis_conn_hdl;
break;
}
LeAudioDeviceGroup* group =
aseGroups_.FindById(leAudioDevice->group_id_);
groupStateMachine_->ProcessHciNotifCisDisconnected(group, leAudioDevice,
event);
} break;
default:
LOG(INFO) << ", Not handeled ISO event";
break;
}
}
void IsoSetupIsoDataPathCb(uint8_t status, uint16_t conn_handle,
uint8_t /* cig_id */) {
LeAudioDevice* leAudioDevice =
leAudioDevices_.FindByCisConnHdl(conn_handle);
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
instance->groupStateMachine_->ProcessHciNotifSetupIsoDataPath(
group, leAudioDevice, status, conn_handle);
}
void IsoRemoveIsoDataPathCb(uint8_t status, uint16_t conn_handle,
uint8_t /* cig_id */) {
LeAudioDevice* leAudioDevice =
leAudioDevices_.FindByCisConnHdl(conn_handle);
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
instance->groupStateMachine_->ProcessHciNotifRemoveIsoDataPath(
group, leAudioDevice, status, conn_handle);
}
void IsoLinkQualityReadCb(
uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets,
uint32_t txFlushedPackets, uint32_t txLastSubeventPackets,
uint32_t retransmittedPackets, uint32_t crcErrorPackets,
uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) {
LeAudioDevice* leAudioDevice =
leAudioDevices_.FindByCisConnHdl(conn_handle);
if (!leAudioDevice) {
LOG(WARNING) << __func__ << ", device under connection handle: "
<< loghex(conn_handle)
<< ", has been disconnecected in meantime";
return;
}
LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
instance->groupStateMachine_->ProcessHciNotifIsoLinkQualityRead(
group, leAudioDevice, conn_handle, txUnackedPackets, txFlushedPackets,
txLastSubeventPackets, retransmittedPackets, crcErrorPackets,
rxUnreceivedPackets, duplicatePackets);
}
void StatusReportCb(int group_id, GroupStreamStatus status) {
switch (status) {
case GroupStreamStatus::STREAMING:
stream_request_started_ = false;
if (audio_source_ready_to_send) StartSendingAudio(active_group_id_);
if (audio_sink_ready_to_receive) StartReceivingAudio(active_group_id_);
break;
case GroupStreamStatus::SUSPENDED:
/** Stop Audio but don't release all the Audio resources */
SuspendAudio();
break;
case GroupStreamStatus::IDLE:
if (stream_request_started_) {
stream_request_started_ = false;
CancelStreamingRequest();
}
break;
default:
break;
}
}
private:
tGATT_IF gatt_if_;
bluetooth::le_audio::LeAudioClientCallbacks* callbacks_;
LeAudioDevices leAudioDevices_;
LeAudioDeviceGroups aseGroups_;
LeAudioGroupStateMachine* groupStateMachine_;
int active_group_id_;
bool stream_request_started_;
LeAudioContextType current_context_type_;
bool audio_sink_ready_to_receive;
bool audio_source_ready_to_send;
LeAudioCodecConfiguration current_source_codec_config;
LeAudioCodecConfiguration current_sink_codec_config;
Lc3Encoder* lc3_encoder;
Lc3Decoder* lc3_decoder;
std::vector<uint8_t> encoded_data;
const void* audio_source_instance_;
const void* audio_sink_instance_;
void ClientAudioIntefraceRelease() {
if (audio_source_instance_) {
LeAudioClientAudioSource::Stop();
LeAudioClientAudioSource::Release(audio_source_instance_);
audio_source_instance_ = nullptr;
}
if (audio_sink_instance_) {
LeAudioClientAudioSink::Stop();
LeAudioClientAudioSink::Release(audio_sink_instance_);
audio_sink_instance_ = nullptr;
}
}
};
/* This is a generic callback method for gatt client which handles every client
* application events.
*/
void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
if (!p_data || !instance) return;
DLOG(INFO) << __func__ << " event = " << +event;
switch (event) {
case BTA_GATTC_DEREG_EVT:
break;
case BTA_GATTC_NOTIF_EVT:
instance->LeAudioCharValueHandle(
p_data->notify.conn_id, p_data->notify.handle, p_data->notify.len,
static_cast<uint8_t*>(p_data->notify.value));
if (!p_data->notify.is_notify)
BTA_GATTC_SendIndConfirm(p_data->notify.conn_id, p_data->notify.handle);
break;
case BTA_GATTC_OPEN_EVT:
instance->OnGattConnected(p_data->open.status, p_data->open.conn_id,
p_data->open.client_if, p_data->open.remote_bda,
p_data->open.transport, p_data->open.mtu);
break;
case BTA_GATTC_ENC_CMPL_CB_EVT:
instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS);
break;
case BTA_GATTC_CLOSE_EVT:
instance->OnGattDisconnected(
p_data->close.conn_id, p_data->close.client_if,
p_data->close.remote_bda, p_data->close.reason);
break;
case BTA_GATTC_SEARCH_CMPL_EVT:
instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id,
p_data->search_cmpl.status);
break;
case BTA_GATTC_SRVC_DISC_DONE_EVT:
instance->OnGattServiceDiscoveryDone(p_data->service_changed.remote_bda);
break;
case BTA_GATTC_SRVC_CHG_EVT:
instance->OnServiceChangeEvent(p_data->remote_bda);
break;
case BTA_GATTC_CFG_MTU_EVT:
break;
default:
break;
}
}
class LeAudioStateMachineHciCallbacksImpl : public CigCallbacks {
public:
void OnCigEvent(uint8_t event, void* data) override {
if (instance) instance->IsoCigEventsCb(event, data);
}
void OnCisEvent(uint8_t event, void* data) override {
if (instance) instance->IsoCisEventsCb(event, data);
}
void OnSetupIsoDataPath(uint8_t status, uint16_t conn_handle,
uint8_t cig_id) override {
if (instance) instance->IsoSetupIsoDataPathCb(status, conn_handle, cig_id);
}
void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle,
uint8_t cig_id) override {
if (instance) instance->IsoRemoveIsoDataPathCb(status, conn_handle, cig_id);
}
void OnIsoLinkQualityRead(
uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets,
uint32_t txFlushedPackets, uint32_t txLastSubeventPackets,
uint32_t retransmittedPackets, uint32_t crcErrorPackets,
uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) {
if (instance)
instance->IsoLinkQualityReadCb(conn_handle, cig_id, txUnackedPackets,
txFlushedPackets, txLastSubeventPackets,
retransmittedPackets, crcErrorPackets,
rxUnreceivedPackets, duplicatePackets);
}
};
LeAudioStateMachineHciCallbacksImpl stateMachineHciCallbacksImpl;
class CallbacksImpl : public LeAudioGroupStateMachine::Callbacks {
public:
void StatusReportCb(int group_id, GroupStreamStatus status) override {
if (instance) instance->StatusReportCb(group_id, status);
}
void OnStateTransitionTimeout(int group_id) override {
if (instance) instance->OnLeAudioDeviceSetStateTimeout(group_id);
}
};
CallbacksImpl stateMachineCallbacksImpl;
class LeAudioClientAudioSinkReceiverImpl
: public LeAudioClientAudioSinkReceiver {
public:
void OnAudioDataReady(const std::vector<uint8_t>& data) override {
if (instance) instance->OnAudioDataReady(data);
}
void OnAudioSuspend(std::promise<void> do_suspend_promise) override {
if (instance) instance->OnAudioSinkSuspend();
do_suspend_promise.set_value();
}
void OnAudioResume(std::promise<void> do_resume_promise) override {
if (instance) instance->OnAudioSinkResume();
do_resume_promise.set_value();
}
void OnAudioMetadataUpdate(
std::promise<void> do_metadata_update_promise,
const source_metadata_t& source_metadata) override {
if (instance) instance->OnAudioMetadataUpdate(source_metadata);
do_metadata_update_promise.set_value();
}
};
class LeAudioClientAudioSourceReceiverImpl
: public LeAudioClientAudioSourceReceiver {
public:
void OnAudioSuspend(std::promise<void> do_suspend_promise) override {
if (instance) instance->OnAudioSourceSuspend();
do_suspend_promise.set_value();
}
void OnAudioResume(std::promise<void> do_resume_promise) override {
if (instance) instance->OnAudioSourceResume();
do_resume_promise.set_value();
}
};
LeAudioClientAudioSinkReceiverImpl audioSinkReceiverImpl;
LeAudioClientAudioSourceReceiverImpl audioSourceReceiverImpl;
class DeviceGroupsCallbacksImpl : public DeviceGroupsCallbacks {
public:
void OnGroupAdded(const RawAddress& address, const bluetooth::Uuid& uuid,
int group_id) override {
if (instance) instance->OnGroupAddedCb(address, uuid, group_id);
}
void OnGroupMemberAdded(const RawAddress& address, int group_id) override {
if (instance) instance->OnGroupMemberAddedCb(address, group_id);
}
void OnGroupMemberRemoved(const RawAddress& address, int group_id) override {
if (instance) instance->OnGroupMemberRemovedCb(address, group_id);
}
void OnGroupRemoved(const bluetooth::Uuid& uuid, int group_id) {
/* to implement if needed */
}
void OnGroupAddFromStorage(const RawAddress& address,
const bluetooth::Uuid& uuid, int group_id) {
/* to implement if needed */
}
};
class DeviceGroupsCallbacksImpl;
DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl;
} // namespace
void LeAudioClient::AddFromStorage(const RawAddress& addr, bool autoconnect) {
if (!instance) {
LOG(ERROR) << "Not initialized yet";
return;
}
instance->AddFromStorage(addr, autoconnect);
}
bool LeAudioClient::IsLeAudioClientRunning(void) { return instance != nullptr; }
LeAudioClient* LeAudioClient::Get() {
CHECK(instance);
return instance;
}
/* Initializer of main le audio implementation class and its instance */
void LeAudioClient::Initialize(
bluetooth::le_audio::LeAudioClientCallbacks* callbacks_,
base::Closure initCb, base::Callback<bool()> hal_2_1_verifier) {
if (instance) {
LOG(ERROR) << "Already initialized";
return;
}
if (!controller_get_interface()
->supports_ble_connected_isochronous_stream_central() &&
!controller_get_interface()
->supports_ble_connected_isochronous_stream_peripheral()) {
LOG(ERROR) << "Controller reports no ISO support."
" LeAudioClient Init aborted.";
return;
}
LOG_ASSERT(std::move(hal_2_1_verifier).Run())
<< __func__
<< ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either "
"disable LE Audio Profile, or update your HAL";
IsoManager::GetInstance()->Start();
audioSinkReceiver = &audioSinkReceiverImpl;
audioSourceReceiver = &audioSourceReceiverImpl;
stateMachineHciCallbacks = &stateMachineHciCallbacksImpl;
stateMachineCallbacks = &stateMachineCallbacksImpl;
device_group_callbacks = &deviceGroupsCallbacksImpl;
instance = new LeAudioClientImpl(callbacks_, stateMachineCallbacks, initCb);
IsoManager::GetInstance()->RegisterCigCallbacks(stateMachineHciCallbacks);
}
void LeAudioClient::DebugDump(int fd) {
dprintf(fd, "LeAudio Manager: \n");
if (instance)
instance->Dump(fd);
else
dprintf(fd, " Not initialized \n");
LeAudioClientAudioSource::DebugDump(fd);
LeAudioClientAudioSink::DebugDump(fd);
dprintf(fd, "\n");
}
void LeAudioClient::Cleanup(void) {
if (!instance) {
LOG(ERROR) << "Not initialized";
return;
}
LeAudioClientImpl* ptr = instance;
instance = nullptr;
ptr->Cleanup();
delete ptr;
LeAudioGroupStateMachine::Cleanup();
IsoManager::GetInstance()->Stop();
}