blob: add09d3b82b3b690c14913bc84090cd46e4e70f5 [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 "bta/le_audio/broadcaster/state_machine.h"
#include <base/bind_helpers.h>
#include <functional>
#include <iostream>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "bta/le_audio/broadcaster/broadcaster_types.h"
#include "bta/le_audio/le_audio_types.h"
#include "gd/common/strings.h"
#include "osi/include/log.h"
#include "service/common/bluetooth/low_energy_constants.h"
#include "stack/include/ble_advertiser.h"
#include "stack/include/btm_iso_api.h"
#include "stack/include/btu.h"
using bluetooth::common::ToString;
using bluetooth::hci::IsoManager;
using bluetooth::hci::iso_manager::big_create_cmpl_evt;
using bluetooth::hci::iso_manager::big_terminate_cmpl_evt;
using namespace le_audio::broadcaster;
namespace {
class BroadcastStateMachineImpl : public BroadcastStateMachine {
public:
BroadcastStateMachineImpl(BroadcastStateMachineConfig msg)
: active_config_(std::nullopt),
sm_config_(std::move(msg)),
suspending_(false) {}
~BroadcastStateMachineImpl() {
if (GetState() == State::STREAMING) TerminateBig();
DestroyBroadcastAnnouncement();
if (callbacks_) callbacks_->OnStateMachineDestroyed(GetInstanceId());
}
bool Initialize() override {
static constexpr uint8_t sNumBisMax = 31;
if (sm_config_.codec_wrapper.GetNumChannels() > sNumBisMax) {
LOG_ERROR(
"Channel count of %d exceeds the maximum number of possible BISes, "
"which is %d",
sm_config_.codec_wrapper.GetNumChannels(), sNumBisMax);
return false;
}
CreateBroadcastAnnouncement(sm_config_.broadcast_id,
sm_config_.announcement,
sm_config_.streaming_phy);
return true;
}
const BroadcastCodecWrapper& GetCodecConfig() const override {
return sm_config_.codec_wrapper;
}
std::optional<BigConfig> const& GetBigConfig() const override {
return active_config_;
}
BroadcastStateMachineConfig const& GetStateMachineConfig() const override {
return sm_config_;
}
void RequestOwnAddress(
base::Callback<void(uint8_t /* address_type*/, RawAddress /*address*/)>
cb) override {
uint8_t instance_id = GetInstanceId();
advertiser_if_->GetOwnAddress(instance_id, cb);
}
void RequestOwnAddress(void) override {
uint8_t instance_id = GetInstanceId();
RequestOwnAddress(
base::Bind(&IBroadcastStateMachineCallbacks::OnOwnAddressResponse,
base::Unretained(this->callbacks_), instance_id));
}
RawAddress GetOwnAddress() override {
LOG_INFO();
return addr_;
}
uint8_t GetOwnAddressType() override {
LOG_INFO();
return addr_type_;
}
bluetooth::le_audio::BroadcastId const& GetBroadcastId() const override {
return sm_config_.broadcast_id;
}
std::optional<bluetooth::le_audio::BroadcastCode> GetBroadcastCode()
const override {
return sm_config_.broadcast_code;
}
void UpdateBroadcastAnnouncement(
BasicAudioAnnouncementData announcement) override {
std::vector<uint8_t> periodic_data;
PreparePeriodicData(announcement, periodic_data);
sm_config_.announcement = std::move(announcement);
advertiser_if_->SetPeriodicAdvertisingData(instance_id_, periodic_data,
base::DoNothing());
}
void ProcessMessage(Message msg, const void* data = nullptr) override {
LOG_INFO("Instance_id=%d, state=%s, message=%s", GetInstanceId(),
ToString(GetState()).c_str(), ToString(msg).c_str());
switch (msg) {
case Message::START:
start_msg_handlers[StateMachine::GetState()](data);
break;
case Message::STOP:
stop_msg_handlers[StateMachine::GetState()](data);
break;
case Message::SUSPEND:
suspend_msg_handlers[StateMachine::GetState()](data);
break;
};
}
static IBroadcastStateMachineCallbacks* callbacks_;
static base::WeakPtr<BleAdvertisingManager> advertiser_if_;
private:
std::optional<BigConfig> active_config_;
BroadcastStateMachineConfig sm_config_;
bool suspending_;
/* Message handlers for each possible state */
typedef std::function<void(const void*)> msg_handler_t;
const std::array<msg_handler_t, BroadcastStateMachine::STATE_COUNT>
start_msg_handlers{
/* in STOPPED state */
[this](const void*) {
SetState(State::CONFIGURING);
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState());
EnableAnnouncement();
},
/* in CONFIGURING state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURED state */
[this](const void*) { CreateBig(); },
/* in STOPPING state */
[](const void*) { /* Do nothing */ },
/* in STREAMING state */
[](const void*) { /* Do nothing */ }};
const std::array<msg_handler_t, BroadcastStateMachine::STATE_COUNT>
stop_msg_handlers{
/* in STOPPED state */
[](const void*) { /* Already stopped */ },
/* in CONFIGURING state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURED state */
[this](const void*) {
SetState(State::STOPPING);
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState());
DisableAnnouncement();
},
/* in STOPPING state */
[](const void*) { /* Do nothing */ },
/* in STREAMING state */
[this](const void*) {
if ((active_config_ != std::nullopt) && !suspending_) {
suspending_ = false;
SetState(State::STOPPING);
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState());
TriggerIsoDatapathTeardown(active_config_->connection_handles[0]);
}
}};
const std::array<msg_handler_t, BroadcastStateMachine::STATE_COUNT>
suspend_msg_handlers{
/* in STOPPED state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURING state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURED state */
[](const void*) { /* Already suspended */ },
/* in STOPPING state */
[](const void*) { /* Do nothing */ },
/* in STREAMING state */
[this](const void*) {
if ((active_config_ != std::nullopt) && !suspending_) {
suspending_ = true;
TriggerIsoDatapathTeardown(active_config_->connection_handles[0]);
}
}};
const std::array<msg_handler_t, BroadcastStateMachine::STATE_COUNT>
resume_msg_handlers{/* in STOPPED state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURING state */
[](const void*) { /* Do nothing */ },
/* in CONFIGURED state */
[this](const void*) { CreateBig(); },
/* in STOPPING state */
[](const void*) { /* Do nothing */ },
/* in STREAMING state */
[](const void*) { /* Already streaming */ }};
void OnAddressResponse(uint8_t addr_type, RawAddress addr) {
LOG_INFO("own address=%s, type=%d", ToString(addr).c_str(), addr_type);
addr_ = addr;
addr_type_ = addr_type;
}
void CreateAnnouncementCb(uint8_t instance_id, int8_t tx_power,
uint8_t status) {
LOG_INFO("instance_id=%d tx_power=%d status=%d", instance_id, tx_power,
status);
/* If this callback gets called the instance_id is valid even though the
* status can be other than BTM_BLE_MULTI_ADV_SUCCESS. We must set it here
* to properly identify the instance when callback gets called.
*/
instance_id_ = instance_id;
if (status != BTM_BLE_MULTI_ADV_SUCCESS) {
LOG_ERROR("Creating Announcement failed");
callbacks_->OnStateMachineCreateStatus(instance_id, false);
return;
}
/* Ext. advertisings are already on */
SetState(State::CONFIGURED);
callbacks_->OnStateMachineCreateStatus(instance_id, true);
callbacks_->OnStateMachineEvent(instance_id, State::CONFIGURED);
advertiser_if_->GetOwnAddress(
instance_id, base::Bind(&BroadcastStateMachineImpl::OnAddressResponse,
base::Unretained(this)));
}
void CreateAnnouncementTimeoutCb(uint8_t instance_id, uint8_t status) {
LOG_INFO("instance_id=%d status=%d", instance_id, status);
instance_id_ = instance_id;
callbacks_->OnStateMachineCreateStatus(instance_id, false);
}
void CreateBroadcastAnnouncement(
bluetooth::le_audio::BroadcastId& broadcast_id,
const BasicAudioAnnouncementData& announcement, uint8_t streaming_phy) {
LOG_INFO();
if (advertiser_if_ != nullptr) {
tBTM_BLE_ADV_PARAMS adv_params;
tBLE_PERIODIC_ADV_PARAMS periodic_params;
std::vector<uint8_t> adv_data;
std::vector<uint8_t> periodic_data;
PrepareAdvertisingData(broadcast_id, adv_data);
PreparePeriodicData(announcement, periodic_data);
adv_params.adv_int_min = 0x00A0; /* 160 * 0,625 = 100ms */
adv_params.adv_int_max = 0x0140; /* 320 * 0,625 = 200ms */
adv_params.advertising_event_properties = 0;
adv_params.channel_map = bluetooth::kAdvertisingChannelAll;
adv_params.adv_filter_policy = 0;
adv_params.tx_power = -15;
adv_params.primary_advertising_phy = PHY_LE_1M;
adv_params.secondary_advertising_phy = streaming_phy;
adv_params.scan_request_notification_enable = 0;
periodic_params.max_interval = BroadcastStateMachine::kPaIntervalMax;
periodic_params.min_interval = BroadcastStateMachine::kPaIntervalMin;
periodic_params.periodic_advertising_properties = 0;
periodic_params.enable = true;
/* Callback returns the status and handle which we use later in
* CreateBIG command.
*/
advertiser_if_->StartAdvertisingSet(
base::Bind(&BroadcastStateMachineImpl::CreateAnnouncementCb,
base::Unretained(this)),
&adv_params, adv_data, std::vector<uint8_t>(), &periodic_params,
periodic_data, 0 /* duration */, 0 /* maxExtAdvEvents */,
base::Bind(&BroadcastStateMachineImpl::CreateAnnouncementTimeoutCb,
base::Unretained(this)));
}
}
void DestroyBroadcastAnnouncement() {
if (BleAdvertisingManager::IsInitialized())
advertiser_if_->Unregister(GetInstanceId());
}
void EnableAnnouncementCb(bool enable, uint8_t status) {
LOG_INFO("operation=%s, instance_id=%d, status=%d",
(enable ? "enable" : "disable"), GetInstanceId(), status);
if (status == BTM_BLE_MULTI_ADV_SUCCESS) {
/* Periodic is enabled but without BIGInfo. Stream is suspended. */
if (enable) {
SetState(State::CONFIGURED);
/* Target state is always STREAMING state - start it now. */
ProcessMessage(Message::START);
} else {
/* User wanted to stop the announcement - report target state reached */
SetState(State::STOPPED);
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState());
}
}
}
void EnableAnnouncementTimeoutCb(bool enable, uint8_t status) {
LOG_INFO("operation=%s, instance_id=%d, status=%d",
(enable ? "enable" : "disable"), GetInstanceId(), status);
if (enable) {
/* Timeout on enabling */
SetState(State::STOPPED);
} else {
/* Timeout on disabling */
SetState(State::CONFIGURED);
}
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState());
}
void EnableAnnouncement() {
LOG_INFO("instance_id=%d", GetInstanceId());
advertiser_if_->Enable(
GetInstanceId(), true,
base::Bind(&BroadcastStateMachineImpl::EnableAnnouncementCb,
base::Unretained(this), true),
0, 0, /* Enable until stopped */
base::Bind(&BroadcastStateMachineImpl::EnableAnnouncementTimeoutCb,
base::Unretained(this), true));
}
void CreateBig(void) {
LOG_INFO("instance_id=%d", GetInstanceId());
/* TODO: Figure out how to decide on the currently hard-codded params. */
struct bluetooth::hci::iso_manager::big_create_params big_params = {
.adv_handle = GetInstanceId(),
.num_bis = sm_config_.codec_wrapper.GetNumChannels(),
.sdu_itv = callbacks_->GetSduItv(GetInstanceId()),
.max_sdu_size = sm_config_.codec_wrapper.GetMaxSduSize(),
.max_transport_latency =
callbacks_->GetMaxTransportLatency(GetInstanceId()),
.rtn = callbacks_->GetNumRetransmit(GetInstanceId()),
.phy = sm_config_.streaming_phy,
.packing = 0x00, /* Sequencial */
.framing = 0x00, /* Unframed */
.enc = static_cast<uint8_t>(sm_config_.broadcast_code ? 1 : 0),
.enc_code = sm_config_.broadcast_code ? *sm_config_.broadcast_code
: std::array<uint8_t, 16>({0}),
};
IsoManager::GetInstance()->CreateBig(GetInstanceId(),
std::move(big_params));
}
void DisableAnnouncement(void) {
LOG_INFO("instance_id=%d", GetInstanceId());
advertiser_if_->Enable(
GetInstanceId(), false,
base::Bind(&BroadcastStateMachineImpl::EnableAnnouncementCb,
base::Unretained(this), false),
0, 0,
base::Bind(&BroadcastStateMachineImpl::EnableAnnouncementTimeoutCb,
base::Unretained(this), false));
}
void TerminateBig() {
LOG_INFO("suspending=%d", suspending_);
/* Terminate with reason: Connection Terminated By Local Host */
IsoManager::GetInstance()->TerminateBig(GetInstanceId(), 0x16);
}
void OnSetupIsoDataPath(uint8_t status, uint16_t conn_hdl) override {
LOG_ASSERT(active_config_ != std::nullopt);
if (status != 0) {
LOG_ERROR("Failure creating data path. Tearing down the BIG now.");
suspending_ = true;
TerminateBig();
return;
}
/* Look for the next BIS handle */
auto handle_it = std::find_if(
active_config_->connection_handles.begin(),
active_config_->connection_handles.end(),
[conn_hdl](const auto& handle) { return conn_hdl == handle; });
LOG_ASSERT(handle_it != active_config_->connection_handles.end());
handle_it = std::next(handle_it);
if (handle_it == active_config_->connection_handles.end()) {
/* It was the last BIS to set up - change state to streaming */
SetState(State::STREAMING);
callbacks_->OnStateMachineEvent(
GetInstanceId(), GetState(),
&sm_config_.codec_wrapper.GetLeAudioCodecConfiguration());
} else {
/* Note: We would feed a watchdog here if we had one */
/* There are more BISes to set up data path for */
LOG_INFO("There is more data paths to set up.");
TriggerIsoDatapathSetup(*handle_it);
}
}
void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle) override {
LOG_ASSERT(active_config_ != std::nullopt);
if (status != 0) {
LOG_ERROR("Failure removing data path. Tearing down the BIG now.");
TerminateBig();
return;
}
/* Look for the next BIS handle */
auto handle_it = std::find_if(
active_config_->connection_handles.begin(),
active_config_->connection_handles.end(),
[conn_handle](const auto& handle) { return conn_handle == handle; });
LOG_ASSERT(handle_it != active_config_->connection_handles.end());
handle_it = std::next(handle_it);
if (handle_it == active_config_->connection_handles.end()) {
/* It was the last one to set up - start tearing down the BIG */
TerminateBig();
} else {
/* Note: We would feed a watchdog here if we had one */
/* There are more BISes to tear down data path for */
LOG_INFO("There is more data paths to tear down.");
TriggerIsoDatapathTeardown(*handle_it);
}
}
void TriggerIsoDatapathSetup(uint16_t conn_handle) {
LOG_INFO("conn_hdl=%d", conn_handle);
LOG_ASSERT(active_config_ != std::nullopt);
/* Note: For the LC3 software encoding on the Host side, the coding format
* should be set to 'Transparent' and no codec configuration shall be sent
* to the controller. 'codec_id_company' and 'codec_id_vendor' shall be
* ignored if 'codec_id_format' is not set to 'Vendor'.
*/
auto codec_id = sm_config_.codec_wrapper.GetLeAudioCodecId();
uint8_t hci_coding_format =
(codec_id.coding_format == le_audio::types::kLeAudioCodingFormatLC3)
? bluetooth::hci::kIsoCodingFormatTransparent
: bluetooth::hci::kIsoCodingFormatVendorSpecific;
bluetooth::hci::iso_manager::iso_data_path_params param = {
.data_path_dir = bluetooth::hci::iso_manager::kIsoDataPathDirectionIn,
.data_path_id = bluetooth::hci::iso_manager::kIsoDataPathHci,
.codec_id_format = hci_coding_format,
.codec_id_company = codec_id.vendor_company_id,
.codec_id_vendor = codec_id.vendor_codec_id,
/* TODO: Implement HCI command to get the controller delay */
.controller_delay = 0x00000000,
};
if (codec_id.coding_format != le_audio::types::kLeAudioCodingFormatLC3)
param.codec_conf = sm_config_.codec_wrapper.GetCodecSpecData();
IsoManager::GetInstance()->SetupIsoDataPath(conn_handle, std::move(param));
}
void TriggerIsoDatapathTeardown(uint16_t conn_handle) {
LOG_INFO("conn_hdl=%d", conn_handle);
LOG_ASSERT(active_config_ != std::nullopt);
SetMuted(true);
IsoManager::GetInstance()->RemoveIsoDataPath(
conn_handle, bluetooth::hci::iso_manager::kIsoDataPathDirectionIn);
}
void HandleHciEvent(uint16_t event, void* data) override {
switch (event) {
case HCI_BLE_CREATE_BIG_CPL_EVT: {
auto* evt = static_cast<big_create_cmpl_evt*>(data);
if (evt->big_id != GetInstanceId()) {
LOG_ERROR("State=%s, Event=%d, Unknown big, big_id=%d",
ToString(GetState()).c_str(), event, evt->big_id);
break;
}
if (evt->status == 0x00) {
LOG_INFO("BIG create BIG complete, big_id=%d", evt->big_id);
active_config_ = {
.status = evt->status,
.big_id = evt->big_id,
.big_sync_delay = evt->big_sync_delay,
.transport_latency_big = evt->transport_latency_big,
.phy = evt->phy,
.nse = evt->nse,
.bn = evt->bn,
.pto = evt->pto,
.irc = evt->irc,
.max_pdu = evt->max_pdu,
.iso_interval = evt->iso_interval,
.connection_handles = evt->conn_handles,
};
TriggerIsoDatapathSetup(evt->conn_handles[0]);
} else {
LOG_ERROR(
"State=%s Event=%d. Unable to create big, big_id=%d, status=%d",
ToString(GetState()).c_str(), event, evt->big_id, evt->status);
}
} break;
case HCI_BLE_TERM_BIG_CPL_EVT: {
auto* evt = static_cast<big_terminate_cmpl_evt*>(data);
LOG_INFO("BIG terminate BIG cmpl, reason=%d big_id=%d", evt->reason,
evt->big_id);
if (evt->big_id != GetInstanceId()) {
LOG_ERROR("State=%s Event=%d, unknown instance=%d",
ToString(GetState()).c_str(), event, evt->big_id);
break;
}
active_config_ = std::nullopt;
/* Go back to configured if BIG is inactive (we are still announcing) */
SetState(State::CONFIGURED);
/* Check if we got this HCI event due to STOP or SUSPEND message. */
if (suspending_) {
callbacks_->OnStateMachineEvent(GetInstanceId(), GetState(), evt);
suspending_ = false;
} else {
DisableAnnouncement();
}
} break;
default:
LOG_ERROR("State=%s Unknown event=%d", ToString(GetState()).c_str(),
event);
break;
}
}
};
IBroadcastStateMachineCallbacks* BroadcastStateMachineImpl::callbacks_ =
nullptr;
base::WeakPtr<BleAdvertisingManager> BroadcastStateMachineImpl::advertiser_if_;
} /* namespace */
std::unique_ptr<BroadcastStateMachine> BroadcastStateMachine::CreateInstance(
BroadcastStateMachineConfig msg) {
return std::make_unique<BroadcastStateMachineImpl>(std::move(msg));
}
void BroadcastStateMachine::Initialize(
IBroadcastStateMachineCallbacks* callbacks) {
BroadcastStateMachineImpl::callbacks_ = callbacks;
/* Get BLE advertiser interface */
if (BleAdvertisingManager::IsInitialized()) {
LOG_INFO("BleAdvertisingManager acquired");
BroadcastStateMachineImpl::advertiser_if_ = BleAdvertisingManager::Get();
} else {
LOG_INFO("Could not acquire BleAdvertisingManager!");
BroadcastStateMachineImpl::advertiser_if_ = nullptr;
}
}
namespace le_audio {
namespace broadcaster {
std::ostream& operator<<(std::ostream& os,
const BroadcastStateMachine::Message& msg) {
static const char* char_value_[BroadcastStateMachine::MESSAGE_COUNT] = {
"START", "SUSPEND", "STOP"};
os << char_value_[static_cast<uint8_t>(msg)];
return os;
}
std::ostream& operator<<(std::ostream& os,
const BroadcastStateMachine::State& state) {
static const char* char_value_[BroadcastStateMachine::STATE_COUNT] = {
"STOPPED", "CONFIGURING", "CONFIGURED", "STOPPING", "STREAMING"};
os << char_value_[static_cast<uint8_t>(state)];
return os;
}
std::ostream& operator<<(std::ostream& os,
const le_audio::broadcaster::BigConfig& config) {
os << "\n";
os << " Status: 0x" << std::hex << +config.status << std::dec << "\n";
os << " BIG ID: " << +config.big_id << "\n";
os << " Sync delay: " << config.big_sync_delay << "\n";
os << " Transport Latency: " << config.transport_latency_big << "\n";
os << " Phy: " << +config.phy << "\n";
os << " Nse: " << +config.nse << "\n";
os << " Bn: " << +config.bn << "\n";
os << " Pto: " << +config.pto << "\n";
os << " Irc: " << +config.irc << "\n";
os << " Max pdu: " << config.max_pdu << "\n";
os << " Iso interval: " << config.iso_interval << "\n";
os << " Connection handles (BISes): [";
for (auto& el : config.connection_handles) {
os << std::hex << +el << std::dec << ":";
}
os << "]";
return os;
}
std::ostream& operator<<(
std::ostream& os,
const le_audio::broadcaster::BroadcastStateMachineConfig& config) {
const char* const PHYS[] = {"NONE", "1M", "2M", "CODED"};
os << "\n";
os << " Broadcast ID: [";
for (auto& el : config.broadcast_id) {
os << std::hex << +el << ":";
}
os << "]\n";
os << " Streaming PHY: "
<< ((config.streaming_phy > 3) ? std::to_string(config.streaming_phy)
: PHYS[config.streaming_phy])
<< "\n";
os << " Codec Wrapper: " << config.codec_wrapper << "\n";
if (config.broadcast_code) {
os << " Broadcast Code: [";
for (auto& el : *config.broadcast_code) {
os << std::hex << +el << ":";
}
os << "]\n";
} else {
os << " Broadcast Code: NONE\n";
}
std::vector<uint8_t> an_raw;
config.announcement.ToRawPacket(an_raw);
os << " Announcement RAW: [";
for (auto& el : an_raw) {
os << std::hex << +el << ":";
}
os << "]";
return os;
}
std::ostream& operator<<(
std::ostream& os,
const le_audio::broadcaster::BroadcastStateMachine& machine) {
os << " Broadcast state machine: {"
<< " Instance ID: " << +machine.GetInstanceId() << "\n"
<< " State: " << machine.GetState() << "\n";
os << " State Machine Config: " << machine.GetStateMachineConfig()
<< "\n";
if (machine.GetBigConfig()) {
os << " BigConfig: " << *machine.GetBigConfig() << "\n";
} else {
os << " BigConfig: NONE\n";
}
os << " }\n";
return os;
}
} // namespace broadcaster
} // namespace le_audio