blob: 3f0c3598b721e974fe59790a81637b8e1f792377 [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 "state_machine.h"
#include <base/bind.h>
#include <base/callback.h>
#include <map>
#include "bt_types.h"
#include "bta_gatt_queue.h"
#include "bta_le_audio_api.h"
#include "btm_iso_api.h"
#include "client_parser.h"
#include "devices.h"
#include "hcimsgs.h"
#include "le_audio_types.h"
#include "osi/include/alarm.h"
#include "osi/include/osi.h"
#include "osi/include/properties.h"
// clang-format off
/* ASCS state machine 1.0
*
* State machine manages group of ASEs to make transition from one state to
* another according to specification and keeping involved necessary externals
* like: ISO, CIG, ISO data path, audio path form/to upper layer.
*
* GroupStream (API): GroupStream method of this le audio implementation class
* object should allow transition from Idle (No Caching),
* Codec Configured (Caching after release) state to
* Streaming for all ASEs in group within time limit. Time
* limit should keep safe whole state machine from being
* stucked in any in-middle state, which is not a destination
* state.
*
* TODO Second functionality of streaming should be switch
* context which will base on previous state, context type.
*
* GroupStop (API): GroupStop method of this le audio implementation class
* object should allow safe transition from any state to Idle
* or Codec Configured (if caching supported).
*
* ╔══════════════════╦═════════════════════════════╦══════════════╦══════════════════╦══════╗
* ║ Current State ║ ASE Control Point Operation ║ Result ║ Next State ║ Note ║
* ╠══════════════════╬═════════════════════════════╬══════════════╬══════════════════╬══════╣
* ║ Idle ║ Config Codec ║ Success ║ Codec Configured ║ + ║
* ║ Codec Configured ║ Config Codec ║ Success ║ Codec Configured ║ - ║
* ║ Codec Configured ║ Release ║ Success ║ Releasing ║ + ║
* ║ Codec Configured ║ Config QoS ║ Success ║ QoS Configured ║ + ║
* ║ QoS Configured ║ Config Codec ║ Success ║ Codec Configured ║ - ║
* ║ QoS Configured ║ Config QoS ║ Success ║ QoS Configured ║ - ║
* ║ QoS Configured ║ Release ║ Success ║ Releasing ║ + ║
* ║ QoS Configured ║ Enable ║ Success ║ Enabling ║ + ║
* ║ Enabling ║ Release ║ Success ║ Releasing ║ + ║
* ║ Enabling ║ Update Metadata ║ Success ║ Enabling ║ - ║
* ║ Enabling ║ Disable ║ Success ║ Disabling ║ - ║
* ║ Enabling ║ Receiver Start Ready ║ Success ║ Streaming ║ + ║
* ║ Streaming ║ Update Metadata ║ Success ║ Streaming ║ - ║
* ║ Streaming ║ Disable ║ Success ║ Disabling ║ + ║
* ║ Streaming ║ Release ║ Success ║ Releasing ║ + ║
* ║ Disabling ║ Receiver Stop Ready ║ Success ║ QoS Configured ║ + ║
* ║ Disabling ║ Release ║ Success ║ Releasing ║ + ║
* ║ Releasing ║ Released (no caching) ║ Success ║ Idle ║ + ║
* ║ Releasing ║ Released (caching) ║ Success ║ Codec Configured ║ - ║
* ╚══════════════════╩═════════════════════════════╩══════════════╩══════════════════╩══════╝
*
* + - supported transition
* - - not supported
*/
// clang-format on
using bluetooth::hci::IsoManager;
using bluetooth::le_audio::GroupStreamStatus;
using le_audio::LeAudioDevice;
using le_audio::LeAudioDeviceGroup;
using le_audio::LeAudioGroupStateMachine;
using le_audio::types::ase;
using le_audio::types::AseState;
using le_audio::types::AudioStreamDataPathState;
namespace {
constexpr int linkQualityCheckInterval = 4000;
static void link_quality_cb(void* data) {
// very ugly, but we need to pass just two bytes
uint16_t cis_conn_handle = *((uint16_t*)data);
IsoManager::GetInstance()->ReadIsoLinkQuality(cis_conn_handle);
}
class LeAudioGroupStateMachineImpl;
LeAudioGroupStateMachineImpl* instance;
class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
public:
LeAudioGroupStateMachineImpl(Callbacks* state_machine_callbacks_)
: state_machine_callbacks_(state_machine_callbacks_),
watchdog_(alarm_new("LeAudioStateMachineTimer")) {}
~LeAudioGroupStateMachineImpl() {
alarm_free(watchdog_);
watchdog_ = nullptr;
}
bool AttachToStream(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) override {
LOG(INFO) << __func__ << " group id: " << group->group_id_
<< " device: " << leAudioDevice->address_;
/* This function is used to attach the device to the stream.
* Limitation here is that device should be previously in the streaming
* group and just got reconnected.
*/
if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
LOG(ERROR) << __func__
<< " group not in the streaming state: " << group->GetState();
return false;
}
PrepareAndSendCodecConfigure(group, leAudioDevice);
return true;
}
bool StartStream(LeAudioDeviceGroup* group,
le_audio::types::LeAudioContextType context_type) override {
LOG(INFO) << __func__ << " current state: " << group->GetState();
switch (group->GetState()) {
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
if (!group->Configure(context_type)) {
LOG(ERROR) << __func__ << ", failed to set ASE configuration";
return false;
}
/* All ASEs should aim to achieve target state */
group->SetContextType(context_type);
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice());
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED: {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", group has no active devices";
return false;
}
/* All ASEs should aim to achieve target state */
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
PrepareAndSendEnable(leAudioDevice);
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: {
/* This case just updates the metadata for the stream, in case
* stream configuration is satisfied
*/
if (!group->IsMetadataChanged(context_type)) return true;
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", group has no active devices";
return false;
}
PrepareAndSendUpdateMetadata(group, leAudioDevice, context_type);
break;
}
default:
LOG(ERROR) << "Unable to transit from " << group->GetState();
return false;
}
return true;
}
void SuspendStream(LeAudioDeviceGroup* group) override {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
/* All ASEs should aim to achieve target state */
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
PrepareAndSendDisable(leAudioDevice);
}
void StopStream(LeAudioDeviceGroup* group) override {
if (group->IsReleasing()) {
LOG(INFO) << __func__ << ", group already in releasing process";
return;
}
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
if (leAudioDevice == nullptr) {
LOG(ERROR) << __func__
<< " Shouldn't be called without an active device.";
return;
}
/* All Ases should aim to achieve target state */
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
PrepareAndSendRelease(leAudioDevice);
}
void ProcessGattNotifEvent(uint8_t* value, uint16_t len, struct ase* ase,
LeAudioDevice* leAudioDevice,
LeAudioDeviceGroup* group) override {
struct le_audio::client_parser::ascs::ase_rsp_hdr arh;
ParseAseStatusHeader(arh, len, value);
LOG(INFO) << __func__ << " " << leAudioDevice->address_
<< ", ASE id: " << +ase->id << " state changed " << ase->state
<< " -> " << AseState(arh.state);
switch (static_cast<AseState>(arh.state)) {
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
AseStateMachineProcessIdle(arh, ase, group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
AseStateMachineProcessCodecConfigured(
arh, ase, value + le_audio::client_parser::ascs::kAseRspHdrMinLen,
len - le_audio::client_parser::ascs::kAseRspHdrMinLen, group,
leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
AseStateMachineProcessQosConfigured(arh, ase, group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
AseStateMachineProcessEnabling(arh, ase, group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
AseStateMachineProcessStreaming(
arh, ase, value + le_audio::client_parser::ascs::kAseRspHdrMinLen,
len - le_audio::client_parser::ascs::kAseRspHdrMinLen, group,
leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING:
AseStateMachineProcessDisabling(arh, ase, group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
AseStateMachineProcessReleasing(arh, ase, group, leAudioDevice);
break;
default:
LOG(ERROR) << __func__
<< ", Wrong AES status: " << static_cast<int>(arh.state);
StopStream(group);
break;
}
}
void ProcessHciNotifOnCigCreate(LeAudioDeviceGroup* group, uint8_t status,
uint8_t cig_id,
std::vector<uint16_t> conn_handles) override {
uint8_t i = 0;
LeAudioDevice* leAudioDevice;
struct le_audio::types::ase* ase;
/* TODO: What if not all cises will be configured ?
* conn_handle.size() != active ases in group
*/
if (!group) {
LOG(ERROR) << __func__ << ", invalid cig";
return;
}
if (status != HCI_SUCCESS) {
LOG(ERROR) << __func__
<< ", failed to create CIG, reason: " << loghex(status);
StopStream(group);
return;
}
group->cig_created_ = true;
LOG(INFO) << __func__ << "Group id: " << +group->group_id_
<< " conn_handle size " << +conn_handles.size();
/* Assign all connection handles to ases. CIS ID order is represented by the
* order of active ASEs in active leAudioDevices
*/
leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
/* Assign all connection handles to ases */
do {
ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::IDLE);
LOG_ASSERT(ase) << __func__
<< " shouldn't be called without an active ASE";
do {
auto ases_pair = leAudioDevice->GetAsesByCisId(ase->cis_id);
if (ases_pair.sink) {
ases_pair.sink->cis_conn_hdl = conn_handles[i];
ases_pair.sink->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
}
if (ases_pair.source) {
ases_pair.source->cis_conn_hdl = conn_handles[i];
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
}
i++;
} while ((ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::IDLE)) &&
(i < conn_handles.size()));
} while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) &&
(i < conn_handles.size()));
/* Last node configured, process group to codec configured state */
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
PrepareAndSendEnable(leAudioDevice);
} else {
LOG(ERROR) << __func__
<< ", invalid state transition, from: " << group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
return;
}
}
void FreeLinkQualityReports(LeAudioDevice* leAudioDevice) {
if (leAudioDevice->link_quality_timer == nullptr) return;
alarm_free(leAudioDevice->link_quality_timer);
leAudioDevice->link_quality_timer = nullptr;
}
void ProcessHciNotifOnCigRemove(uint8_t status,
LeAudioDeviceGroup* group) override {
if (status) {
LOG(ERROR) << __func__
<< ", failed to remove cig, id: " << loghex(group->group_id_)
<< ", status: " << loghex(status);
return;
}
group->cig_created_ = false;
LeAudioDevice* leAudioDevice = group->GetFirstDevice();
if (!leAudioDevice) return;
do {
FreeLinkQualityReports(leAudioDevice);
for (auto& ase : leAudioDevice->ases_) {
ase.data_path_state = AudioStreamDataPathState::IDLE;
ase.cis_id = le_audio::kInvalidCisId;
}
} while ((leAudioDevice = group->GetNextDevice(leAudioDevice)));
}
void ProcessHciNotifSetupIsoDataPath(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice,
uint8_t status,
uint16_t conn_handle) override {
if (status) {
LOG(ERROR) << __func__ << ", failed to setup data path";
StopStream(group);
return;
}
/* Update state for the given cis.*/
auto ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::CIS_ESTABLISHED);
if (ase->cis_conn_hdl != conn_handle) {
LOG(ERROR) << __func__ << " Cannot find ase by handle " << +conn_handle;
return;
}
ase->data_path_state = AudioStreamDataPathState::DATA_PATH_ESTABLISHED;
if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
LOG(WARNING) << __func__ << " Group " << group->group_id_
<< " is not targeting streaming state any more";
return;
}
ase = leAudioDevice->GetNextActiveAse(ase);
if (!ase) {
leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
if (!leAudioDevice) {
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::STREAMING);
return;
}
ase = leAudioDevice->GetFirstActiveAse();
}
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
if (ase->data_path_state == AudioStreamDataPathState::CIS_ESTABLISHED)
PrepareDataPath(ase);
else
LOG(ERROR) << __func__
<< " CIS got disconnected? handle: " << +ase->cis_conn_hdl;
}
void ProcessHciNotifRemoveIsoDataPath(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice,
uint8_t status,
uint16_t conn_hdl) override {
if (status != HCI_SUCCESS) {
LOG(ERROR) << __func__ << ", failed to remove ISO data path, reason: "
<< loghex(status);
StopStream(group);
return;
}
bool do_disconnect = false;
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(conn_hdl);
if (ases_pair.sink && (ases_pair.sink->data_path_state ==
AudioStreamDataPathState::DATA_PATH_ESTABLISHED)) {
ases_pair.sink->data_path_state =
AudioStreamDataPathState::CIS_DISCONNECTING;
do_disconnect = true;
}
if (ases_pair.source &&
ases_pair.source->data_path_state ==
AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_DISCONNECTING;
do_disconnect = true;
}
if (do_disconnect)
IsoManager::GetInstance()->DisconnectCis(conn_hdl, HCI_ERR_PEER_USER);
}
void ProcessHciNotifIsoLinkQualityRead(
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
uint8_t conn_handle, uint32_t txUnackedPackets, uint32_t txFlushedPackets,
uint32_t txLastSubeventPackets, uint32_t retransmittedPackets,
uint32_t crcErrorPackets, uint32_t rxUnreceivedPackets,
uint32_t duplicatePackets) {
LOG(INFO) << "conn_handle: " << loghex(conn_handle)
<< ", txUnackedPackets: " << loghex(txUnackedPackets)
<< ", txFlushedPackets: " << loghex(txFlushedPackets)
<< ", txLastSubeventPackets: " << loghex(txLastSubeventPackets)
<< ", retransmittedPackets: " << loghex(retransmittedPackets)
<< ", crcErrorPackets: " << loghex(crcErrorPackets)
<< ", rxUnreceivedPackets: " << loghex(rxUnreceivedPackets)
<< ", duplicatePackets: " << loghex(duplicatePackets);
}
void ProcessHciNotifAclDisconnected(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
FreeLinkQualityReports(leAudioDevice);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
if (!group) {
LOG(ERROR) << __func__
<< " group is null for device: " << leAudioDevice->address_
<< " group_id: " << leAudioDevice->group_id_;
return;
}
auto* stream_conf = &group->stream_conf;
if (stream_conf->valid) {
stream_conf->sink_streams.erase(
std::remove_if(stream_conf->sink_streams.begin(),
stream_conf->sink_streams.end(),
[leAudioDevice](auto& pair) {
auto ases =
leAudioDevice->GetAsesByCisConnHdl(pair.first);
return ases.sink;
}),
stream_conf->sink_streams.end());
stream_conf->source_streams.erase(
std::remove_if(stream_conf->source_streams.begin(),
stream_conf->source_streams.end(),
[leAudioDevice](auto& pair) {
auto ases =
leAudioDevice->GetAsesByCisConnHdl(pair.first);
return ases.source;
}),
stream_conf->source_streams.end());
if (stream_conf->sink_streams.empty() &&
stream_conf->source_streams.empty()) {
LOG(INFO) << __func__ << " stream stopped ";
stream_conf->valid = false;
}
}
/* mark ASEs as not used. */
leAudioDevice->DeactivateAllAses();
DLOG(INFO) << __func__ << " device: " << leAudioDevice->address_
<< " group connected: " << group->IsAnyDeviceConnected()
<< " all active ase disconnected: "
<< group->HaveAllActiveDevicesCisDisc();
/* Group has changed. Lets update available contexts */
group->UpdateActiveContextsMap();
/* ACL of one of the device has been dropped.
* If there is active CIS, do nothing here. Just update the active contexts
* table
*/
if (group->IsAnyDeviceConnected() &&
!group->HaveAllActiveDevicesCisDisc()) {
return;
}
/* Group is not connected and all the CISes are down.
* If group is in Idle there is nothing to do here */
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
LOG(INFO) << __func__ << " group: " << group->group_id_ << " is in IDLE";
return;
}
/* Clean states and destroy HCI group */
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::IDLE);
if (!group->cig_created_) return;
IsoManager::GetInstance()->RemoveCig(group->group_id_);
}
void ProcessHciNotifCisEstablished(
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event)
override {
std::vector<uint8_t> value;
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
if (event->status) {
if (ases_pair.sink)
ases_pair.sink->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
if (ases_pair.source)
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
/* CIS establishment failed. Remove CIG if no other CIS is already created
* or pending. If CIS is established, this will be handled in disconnected
* complete event
*/
if (group->HaveAllActiveDevicesCisDisc()) {
IsoManager::GetInstance()->RemoveCig(group->group_id_);
}
LOG(ERROR) << __func__
<< ", failed to create CIS, status: " << loghex(event->status);
StopStream(group);
return;
}
if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
LOG(ERROR) << __func__ << ", Unintended CIS establishement event came";
StopStream(group);
return;
}
if (ases_pair.sink)
ases_pair.sink->data_path_state =
AudioStreamDataPathState::CIS_ESTABLISHED;
if (ases_pair.source)
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_ESTABLISHED;
if (osi_property_get_bool("persist.bluetooth.iso_link_quality_report",
false)) {
leAudioDevice->link_quality_timer =
alarm_new_periodic("le_audio_cis_link_quality");
leAudioDevice->link_quality_timer_data = event->cis_conn_hdl;
alarm_set_on_mloop(leAudioDevice->link_quality_timer,
linkQualityCheckInterval, link_quality_cb,
&leAudioDevice->link_quality_timer_data);
}
if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
/* More cis established event has to come */
return;
}
std::vector<uint8_t> ids;
/* All CISes created. Send start ready for source ASE before we can go
* to streaming state.
*/
struct ase* ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
do {
if (ase->direction == le_audio::types::kLeAudioDirectionSource)
ids.push_back(ase->id);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
if (ids.size() > 0) {
le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
ids, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
return;
}
/* Cis establishment may came after setting group state to streaming, e.g.
* for autonomous scenario when ase is sink */
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
group->IsGroupStreamReady()) {
/* No more transition for group */
alarm_cancel(watchdog_);
PrepareDataPath(group);
}
}
void ProcessHciNotifCisDisconnected(
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
const bluetooth::hci::iso_manager::cis_disconnected_evt* event) override {
/* Reset the disconnected CIS states */
FreeLinkQualityReports(leAudioDevice);
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
if (ases_pair.sink) {
ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_ASSIGNED;
}
if (ases_pair.source) {
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
}
/* Invalidate stream configuration if needed */
auto* stream_conf = &group->stream_conf;
if (stream_conf->valid) {
if (ases_pair.sink) {
stream_conf->sink_streams.erase(
std::remove_if(stream_conf->sink_streams.begin(),
stream_conf->sink_streams.end(),
[&event](auto& pair) {
return event->cis_conn_hdl == pair.first;
}),
stream_conf->sink_streams.end());
}
if (ases_pair.source) {
stream_conf->source_streams.erase(
std::remove_if(stream_conf->source_streams.begin(),
stream_conf->source_streams.end(),
[&event](auto& pair) {
return event->cis_conn_hdl == pair.first;
}),
stream_conf->source_streams.end());
}
if (stream_conf->sink_streams.empty() &&
stream_conf->source_streams.empty()) {
LOG(INFO) << __func__ << " stream stopped ";
stream_conf->valid = false;
}
}
auto target_state = group->GetTargetState();
switch (target_state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
/* Something wrong happen when streaming or when creating stream.
* If there is other device connected and streaming, just leave it as it
* is, otherwise stop the stream.
*/
if (!group->HaveAllActiveDevicesCisDisc()) {
/* TODO: Reconfigure LC3 codec from here or maybe other place?*/
return;
}
/*If there is no more ase to stream. Suspend audio and clear state
* machine -> go to Idle */
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::SUSPENDED);
StopStream(group);
return;
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
/* Intentional group disconnect has finished, but the last CIS in the
* event came after the ASE notification.
* If group is already suspended and all CIS are disconnected, we can
* report SUSPENDED state.
*/
if ((group->GetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
group->HaveAllActiveDevicesCisDisc()) {
/* No more transition for group */
alarm_cancel(watchdog_);
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::SUSPENDED);
return;
}
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
/* Those two are used when closing the stream and CIS disconnection is
* expected */
if (group->HaveAllActiveDevicesCisDisc()) {
IsoManager::GetInstance()->RemoveCig(group->group_id_);
return;
}
break;
default:
break;
}
/* We should send Receiver Stop Ready when acting as a source */
if (ases_pair.source &&
ases_pair.source->state == AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) {
std::vector<uint8_t> ids = {ases_pair.source->id};
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStopReady(ids,
value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
/* Tear down CIS's data paths within the group */
struct ase* ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::DATA_PATH_ESTABLISHED);
if (!ase) {
leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
/* No more ASEs to disconnect their CISes */
if (!leAudioDevice) return;
ase = leAudioDevice->GetFirstActiveAse();
}
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
if (ase->data_path_state ==
AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
IsoManager::GetInstance()->RemoveIsoDataPath(
ase->cis_conn_hdl,
(ases_pair.sink
? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
: 0x00) |
(ases_pair.source
? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
: 0x00));
}
}
private:
static constexpr uint64_t kStateTransitionTimeoutMs = 5000;
static constexpr char kStateTransitionTimeoutMsProp[] =
"persist.bluetooth.leaudio.device.set.state.timeoutms";
Callbacks* state_machine_callbacks_;
alarm_t* watchdog_;
/* This callback is called on timeout during transition to target state */
void OnStateTransitionTimeout(int group_id) {
state_machine_callbacks_->OnStateTransitionTimeout(group_id);
}
void SetTargetState(LeAudioDeviceGroup* group, AseState state) {
group->SetTargetState(state);
/* Group should tie in time to get requested status */
uint64_t timeoutMs = kStateTransitionTimeoutMs;
timeoutMs =
osi_property_get_int32(kStateTransitionTimeoutMsProp, timeoutMs);
if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
alarm_set_on_mloop(
watchdog_, timeoutMs,
[](void* data) {
if (instance) instance->OnStateTransitionTimeout(PTR_TO_INT(data));
},
INT_TO_PTR(group->group_id_));
}
void CigCreate(LeAudioDeviceGroup* group) {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
struct ase* ase;
uint32_t sdu_interval_mtos, sdu_interval_stom;
uint8_t packing, framing, sca;
std::vector<EXT_CIS_CFG> cis_cfgs;
if (group->cig_created_) {
LOG(ERROR) << __func__ << " group id " << group->group_id_
<< " is already created in the controller. ";
return;
}
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", no active devices in group";
return;
}
sdu_interval_mtos =
group->GetSduInterval(le_audio::types::kLeAudioDirectionSink);
sdu_interval_stom =
group->GetSduInterval(le_audio::types::kLeAudioDirectionSource);
sca = group->GetSCA();
packing = group->GetPacking();
framing = group->GetFraming();
uint16_t max_trans_lat_mtos = group->GetMaxTransportLatencyMtos();
uint16_t max_trans_lat_stom = group->GetMaxTransportLatencyStom();
do {
ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__
<< " shouldn't be called without an active ASE";
do {
auto& cis = ase->cis_id;
auto iter =
find_if(cis_cfgs.begin(), cis_cfgs.end(),
[&cis](auto const& cfg) { return cis == cfg.cis_id; });
/* CIS configuration already on list */
if (iter != cis_cfgs.end()) continue;
auto ases_pair = leAudioDevice->GetAsesByCisId(cis);
EXT_CIS_CFG cis_cfg;
cis_cfg.cis_id = ase->cis_id;
cis_cfg.phy_mtos =
group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
cis_cfg.phy_stom =
group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
if (ases_pair.sink) {
/* TODO: config should be previously adopted */
cis_cfg.max_sdu_size_mtos = ase->max_sdu_size;
cis_cfg.rtn_mtos = ase->retrans_nb;
}
if (ases_pair.source) {
/* TODO: config should be previously adopted */
cis_cfg.max_sdu_size_stom = ase->max_sdu_size;
cis_cfg.rtn_stom = ase->retrans_nb;
}
cis_cfgs.push_back(cis_cfg);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
} while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)));
bluetooth::hci::iso_manager::cig_create_params param = {
.sdu_itv_mtos = sdu_interval_mtos,
.sdu_itv_stom = sdu_interval_stom,
.sca = sca,
.packing = packing,
.framing = framing,
.max_trans_lat_stom = max_trans_lat_stom,
.max_trans_lat_mtos = max_trans_lat_mtos,
.cis_cfgs = std::move(cis_cfgs),
};
IsoManager::GetInstance()->CreateCig(group->group_id_, std::move(param));
}
static void CisCreateForDevice(LeAudioDevice* leAudioDevice) {
std::vector<EXT_CIS_CREATE_CFG> conn_pairs;
struct ase* ase = leAudioDevice->GetFirstActiveAse();
do {
/* First in ase pair is Sink, second Source */
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
/* Already in pending state - bi-directional CIS */
if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING)
continue;
if (ases_pair.sink)
ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_PENDING;
if (ases_pair.source)
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_PENDING;
uint16_t acl_handle =
BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl,
.acl_conn_handle = acl_handle});
LOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl
<< " acl handle : " << loghex(+acl_handle);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
IsoManager::GetInstance()->EstablishCis(
{.conn_pairs = std::move(conn_pairs)});
}
static void CisCreate(LeAudioDeviceGroup* group) {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
struct ase* ase;
std::vector<EXT_CIS_CREATE_CFG> conn_pairs;
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__
<< " shouldn't be called without an active ASE";
do {
/* First is ase pair is Sink, second Source */
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
/* Already in pending state - bi-directional CIS */
if (ase->data_path_state == AudioStreamDataPathState::CIS_PENDING)
continue;
if (ases_pair.sink)
ases_pair.sink->data_path_state =
AudioStreamDataPathState::CIS_PENDING;
if (ases_pair.source)
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_PENDING;
uint16_t acl_handle =
BTM_GetHCIConnHandle(leAudioDevice->address_, BT_TRANSPORT_LE);
conn_pairs.push_back({.cis_conn_handle = ase->cis_conn_hdl,
.acl_conn_handle = acl_handle});
DLOG(INFO) << __func__ << " cis handle: " << +ase->cis_conn_hdl
<< " acl handle : " << loghex(+acl_handle);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
} while ((leAudioDevice = group->GetNextActiveDevice(leAudioDevice)));
IsoManager::GetInstance()->EstablishCis(
{.conn_pairs = std::move(conn_pairs)});
}
static void PrepareDataPath(const struct ase* ase) {
/* TODO Handle HW offloading as we handle here only HCI for now */
bluetooth::hci::iso_manager::iso_data_path_params param = {
.data_path_dir =
ase->direction == le_audio::types::kLeAudioDirectionSink
? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
: bluetooth::hci::iso_manager::kIsoDataPathDirectionOut,
.data_path_id = bluetooth::hci::iso_manager::kIsoDataPathHci,
.codec_id_format = ase->codec_id.coding_format,
.codec_id_company = ase->codec_id.vendor_company_id,
.codec_id_vendor = ase->codec_id.vendor_codec_id,
.controller_delay = 0x00000000,
.codec_conf = std::vector<uint8_t>(),
};
IsoManager::GetInstance()->SetupIsoDataPath(ase->cis_conn_hdl,
std::move(param));
}
static inline void PrepareDataPath(LeAudioDeviceGroup* group) {
auto* leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
auto* ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
PrepareDataPath(ase);
}
static void ReleaseDataPath(LeAudioDeviceGroup* group) {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
auto ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::DATA_PATH_ESTABLISHED);
LOG_ASSERT(ase) << __func__
<< " Shouldn't be called without an active ASE.";
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
IsoManager::GetInstance()->RemoveIsoDataPath(
ase->cis_conn_hdl,
(ases_pair.sink ? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
: 0x00) |
(ases_pair.source
? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
: 0x00));
}
void AseStateMachineProcessIdle(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
if (ase->id == 0x00) {
/* Initial state of Ase - update id */
LOG(INFO) << __func__
<< ", discovered ase id: " << static_cast<int>(arh.id);
ase->id = arh.id;
}
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: {
LeAudioDevice* leAudioDeviceNext;
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
ase->active = false;
if (!leAudioDevice->HaveAllActiveAsesSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) {
/* More ASEs notification from this device has to come for this group
*/
return;
}
leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
/* Configure ASEs for next device in group */
if (leAudioDeviceNext) {
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::IDLE);
}
break;
}
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
StopStream(group);
break;
}
}
void StartConfigQoSForTheGroup(LeAudioDeviceGroup* group) {
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
if (!leAudioDevice) {
LOG(ERROR) << __func__ << ", no active devices in group";
StopStream(group);
return;
}
PrepareAndSendConfigQos(group, leAudioDevice);
}
void PrepareAndSendCodecConfigure(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
struct le_audio::client_parser::ascs::ctp_codec_conf conf;
std::vector<struct le_audio::client_parser::ascs::ctp_codec_conf> confs;
struct ase* ase;
ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
do {
conf.ase_id = ase->id;
conf.target_latency = group->GetTargetLatency();
conf.target_phy = group->GetTargetPhy(ase->direction);
conf.codec_id = ase->codec_id;
conf.codec_config = ase->codec_config;
confs.push_back(conf);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpCodecConfig(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
void AseStateMachineProcessCodecConfigured(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
uint8_t* data, uint16_t len, LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
/* ase contain current ASE state. New state is in "arh" */
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: {
if (ase->id == 0x00) {
/* Initial state of Ase - update id */
LOG(INFO) << __func__
<< ", discovered ase id: " << static_cast<int>(arh.id);
ase->id = arh.id;
}
LeAudioDevice* leAudioDeviceNext;
struct le_audio::client_parser::ascs::ase_codec_configured_state_params
rsp;
/* Cache codec configured status values for further
* configuration/reconfiguration
*/
if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) {
StopStream(group);
return;
}
ase->framing = rsp.framing;
ase->preferred_phy = rsp.preferred_phy;
ase->max_transport_latency = rsp.max_transport_latency;
ase->pres_delay_min = rsp.pres_delay_min;
ase->pres_delay_max = rsp.pres_delay_max;
ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min;
ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max;
ase->retrans_nb = rsp.preferred_retrans_nb;
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
/* This is autonomus change of the remote device */
LOG(INFO) << __func__ << " Autonomus change. Just store it. ";
return;
}
if (leAudioDevice->HaveAnyUnconfiguredAses()) {
/* More ASEs notification from this device has to come for this group
*/
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
PrepareAndSendConfigQos(group, leAudioDevice);
return;
}
leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
/* Configure ASEs for next device in group */
if (leAudioDeviceNext) {
PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
} else {
/* Last node configured, process group to codec configured state */
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
StartConfigQoSForTheGroup(group);
return;
} else {
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
return;
}
}
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
/* Received Configured in Configured state. This could be done
* autonomously because of the reconfiguration done by us
*/
struct le_audio::client_parser::ascs::ase_codec_configured_state_params
rsp;
/* Cache codec configured status values for further
* configuration/reconfiguration
*/
if (!ParseAseStatusCodecConfiguredStateParams(rsp, len, data)) {
StopStream(group);
return;
}
ase->framing = rsp.framing;
ase->preferred_phy = rsp.preferred_phy;
ase->max_transport_latency = rsp.max_transport_latency;
ase->pres_delay_min = rsp.pres_delay_min;
ase->pres_delay_max = rsp.pres_delay_max;
ase->preferred_pres_delay_min = rsp.preferred_pres_delay_min;
ase->preferred_pres_delay_max = rsp.preferred_pres_delay_max;
ase->retrans_nb = rsp.preferred_retrans_nb;
/* This may be a notification from a re-configured ASE */
ase->reconfigure = false;
if (leAudioDevice->HaveAnyUnconfiguredAses()) {
/* Waiting for others to be reconfigured */
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
PrepareAndSendConfigQos(group, leAudioDevice);
return;
}
LeAudioDevice* leAudioDeviceNext =
group->GetNextActiveDevice(leAudioDevice);
/* Configure ASEs for next device in group */
if (leAudioDeviceNext) {
PrepareAndSendCodecConfigure(group, leAudioDeviceNext);
} else {
/* Last node configured, process group to codec configured state */
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
StartConfigQoSForTheGroup(group);
return;
} else {
LOG(ERROR) << __func__
<< ", Autonomouse change ?: " << group->GetState()
<< ", to: " << group->GetTargetState();
}
}
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
/* TODO: Config Codec */
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
LeAudioDevice* leAudioDeviceNext;
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
ase->active = false;
if (!leAudioDevice->HaveAllActiveAsesSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)) {
/* More ASEs notification from this device has to come for this group
*/
return;
}
leAudioDeviceNext = group->GetNextActiveDevice(leAudioDevice);
/* Configure ASEs for next device in group */
if (leAudioDeviceNext) {
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
/* Remote device has cache and keep staying in configured state after
* release. Therefore, we assume this is a target state requested by
* remote device.
*/
group->SetTargetState(group->GetState());
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::IDLE);
}
break;
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
StopStream(group);
break;
}
}
void AseStateMachineProcessQosConfigured(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
if (!leAudioDevice->HaveAllActiveAsesSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
/* More ASEs notification from this device has to come for this group
*/
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
PrepareAndSendEnable(leAudioDevice);
return;
}
LeAudioDevice* leAudioDeviceNext =
group->GetNextActiveDevice(leAudioDevice);
/* Configure ASEs qos for next device in group */
if (leAudioDeviceNext) {
PrepareAndSendConfigQos(group, leAudioDeviceNext);
} else {
CigCreate(group);
}
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
/* TODO: Config Codec error/Config Qos/Config QoS error/Enable error */
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
if (ase->direction == le_audio::types::kLeAudioDirectionSource) {
/* Source ASE cannot go from Streaming to QoS Configured state */
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
StopStream(group);
return;
}
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
/* Process the Disable Transition of the rest of group members if no
* more ASE notifications has to come from this device. */
if (leAudioDevice->IsReadyToSuspendStream())
ProcessGroupDisable(group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: {
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED;
/* More ASEs notification from this device has to come for this group */
if (!group->HaveAllActiveDevicesAsesTheSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED))
return;
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
if (!group->HaveAllActiveDevicesCisDisc()) return;
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
/* No more transition for group */
alarm_cancel(watchdog_);
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::SUSPENDED);
} else {
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
return;
}
break;
}
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
StopStream(group);
break;
}
}
void PrepareAndSendEnable(LeAudioDevice* leAudioDevice) {
struct le_audio::client_parser::ascs::ctp_enable conf;
std::vector<struct le_audio::client_parser::ascs::ctp_enable> confs;
std::vector<uint8_t> value;
struct ase* ase;
ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
do {
conf.ase_id = ase->id;
conf.metadata = ase->metadata;
confs.push_back(conf);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
le_audio::client_parser::ascs::PrepareAseCtpEnable(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
void PrepareAndSendDisable(LeAudioDevice* leAudioDevice) {
ase* ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
std::vector<uint8_t> ids;
do {
ids.push_back(ase->id);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpDisable(ids, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
void PrepareAndSendRelease(LeAudioDevice* leAudioDevice) {
ase* ase = leAudioDevice->GetFirstActiveAse();
LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
std::vector<uint8_t> ids;
do {
ids.push_back(ase->id);
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpRelease(ids, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
void PrepareAndSendConfigQos(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs;
for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
ase = leAudioDevice->GetNextActiveAse(ase)) {
/* Get completive (to be bi-directional CIS) CIS ID for ASE */
uint8_t cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase);
if (cis_id == le_audio::kInvalidCisId) {
/* Get next free CIS ID for group */
cis_id = group->GetFirstFreeCisId();
if (cis_id == le_audio::kInvalidCisId) {
LOG(ERROR) << __func__ << ", failed to get free CIS ID";
StopStream(group);
return;
}
}
ase->cis_id = cis_id;
/* TODO: Configure first ASE qos according to context type */
struct le_audio::client_parser::ascs::ctp_qos_conf conf;
conf.ase_id = ase->id;
conf.cig = group->group_id_;
conf.cis = ase->cis_id;
conf.framing = group->GetFraming();
conf.phy = group->GetPhyBitmask(ase->direction);
conf.max_sdu = ase->max_sdu_size;
conf.retrans_nb = ase->retrans_nb;
if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) {
LOG(ERROR) << __func__ << ", inconsistent presentation delay for group";
StopStream(group);
return;
}
conf.sdu_interval = group->GetSduInterval(ase->direction);
if (!conf.sdu_interval) {
LOG(ERROR) << __func__ << ", unsupported SDU interval for group";
StopStream(group);
return;
}
if (ase->direction == le_audio::types::kLeAudioDirectionSink) {
conf.max_transport_latency = group->GetMaxTransportLatencyMtos();
} else {
conf.max_transport_latency = group->GetMaxTransportLatencyStom();
}
confs.push_back(conf);
}
LOG_ASSERT(confs.size() > 0)
<< __func__ << " shouldn't be called without an active ASE";
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
void PrepareAndSendUpdateMetadata(
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
le_audio::types::LeAudioContextType context_type) {
std::vector<struct le_audio::client_parser::ascs::ctp_update_metadata>
confs;
for (; leAudioDevice;
leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
if (!leAudioDevice->IsMetadataChanged(context_type)) continue;
auto new_metadata = leAudioDevice->GetMetadata(context_type);
/* Request server to update ASEs with new metadata */
for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
ase = leAudioDevice->GetNextActiveAse(ase)) {
struct le_audio::client_parser::ascs::ctp_update_metadata conf;
conf.ase_id = ase->id;
conf.metadata = new_metadata;
confs.push_back(conf);
}
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpUpdateMetadata(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
return;
}
}
void AseStateMachineProcessEnabling(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
CisCreateForDevice(leAudioDevice);
return;
}
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
if (leAudioDevice->IsReadyToCreateStream())
ProcessGroupEnable(group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
/* Enable/Switch Content */
break;
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
StopStream(group);
break;
}
}
void AseStateMachineProcessStreaming(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
uint8_t* data, uint16_t len, LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
/* As per ASCS 1.0 :
* If a CIS has been established and the server is acting as Audio Sink
* for the ASE, and if the server is ready to receive audio data
* transmitted by the client, the server may autonomously initiate the
* Receiver Start Ready, as defined in Section 5.4, without first
* sending a notification of the ASE characteristic value in the
* Enabling state.
*/
if (ase->direction != le_audio::types::kLeAudioDirectionSink) {
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
StopStream(group);
return;
}
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
CisCreateForDevice(leAudioDevice);
return;
}
if (leAudioDevice->IsReadyToCreateStream())
ProcessGroupEnable(group, leAudioDevice);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING: {
std::vector<uint8_t> value;
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
auto* stream_conf = &group->stream_conf;
if (ase->direction == le_audio::types::kLeAudioDirectionSource) {
stream_conf->source_streams.emplace_back(std::make_pair(
ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
} else {
stream_conf->sink_streams.emplace_back(std::make_pair(
ase->cis_conn_hdl, ase->codec_config.audio_channel_allocation));
}
}
if (!group->HaveAllActiveDevicesAsesTheSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
/* More ASEs notification form this device has to come for this group
*/
return;
}
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We are here because of the reconnection of the single device. */
return;
}
/* Last node is in streaming state */
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
/* Not all CISes establish evens came */
if (!group->IsGroupStreamReady()) return;
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* No more transition for group */
alarm_cancel(watchdog_);
PrepareDataPath(group);
return;
} else {
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
return;
}
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: {
struct le_audio::client_parser::ascs::ase_transient_state_params rsp;
if (!ParseAseStatusTransientStateParams(rsp, len, data)) {
StopStream(group);
return;
}
/* Cache current set up metadata values for for further possible
* reconfiguration
*/
for (struct ase* ase = leAudioDevice->GetFirstActiveAse();
ase != nullptr; ase = leAudioDevice->GetNextActiveAse(ase)) {
ase->metadata = rsp.metadata;
}
PrepareAndSendUpdateMetadata(group, leAudioDevice,
group->GetContextType());
break;
}
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
StopStream(group);
break;
}
}
void AseStateMachineProcessDisabling(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
if (ase->direction == le_audio::types::kLeAudioDirectionSink) {
/* Sink ASE state machine does not have Disabling state */
LOG(ERROR) << __func__
<< ", invalid state transition, from: " << group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
return;
}
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
/* TODO: Disable */
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING;
/* Process the Disable Transition of the rest of group members if no
* more ASE notifications has to come from this device. */
if (leAudioDevice->IsReadyToSuspendStream())
ProcessGroupDisable(group, leAudioDevice);
break;
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
StopStream(group);
break;
}
}
void AseStateMachineProcessReleasing(
struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
if (!group) {
LOG(ERROR) << __func__ << ", leAudioDevice doesn't belong to any group";
return;
}
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: {
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
break;
}
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
/* At this point all of the active ASEs within group are released. */
if (group->cig_created_)
IsoManager::GetInstance()->RemoveCig(group->group_id_);
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
if (group->HaveAllActiveDevicesAsesTheSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING))
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING: {
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING;
/* Since single ase gets into Releasing state, lets assume our new
* target state is IDLE
*/
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
/* Happens when bi-directional completive ASE releasing state came */
if (ase->data_path_state == AudioStreamDataPathState::CIS_DISCONNECTING)
break;
if (ase->data_path_state ==
AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
auto ases_pair =
leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
IsoManager::GetInstance()->RemoveIsoDataPath(
ase->cis_conn_hdl,
(ases_pair.sink
? bluetooth::hci::iso_manager::kIsoDataPathDirectionOut
: 0x00) |
(ases_pair.source
? bluetooth::hci::iso_manager::kIsoDataPathDirectionIn
: 0x00));
} else if (ase->data_path_state ==
AudioStreamDataPathState::CIS_ESTABLISHED ||
ase->data_path_state ==
AudioStreamDataPathState::CIS_PENDING) {
IsoManager::GetInstance()->DisconnectCis(ase->cis_conn_hdl,
HCI_ERR_PEER_USER);
} else {
DLOG(INFO) << __func__ << ", Nothing to do ase data path state: "
<< static_cast<int>(ase->data_path_state);
}
break;
}
default:
LOG(ERROR) << __func__ << ", invalid state transition, from: "
<< static_cast<int>(ase->state) << ", to: "
<< static_cast<int>(
AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
break;
}
}
void ProcessGroupEnable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
/* Enable ASEs for next device in group. */
LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
if (deviceNext) {
PrepareAndSendEnable(deviceNext);
return;
}
/* At this point all of the active ASEs within group are enabled. The server
* might perform autonomous state transition for Sink ASE and skip Enabling
* state notification and transit to Streaming directly. So check the group
* state, because we might be ready to create CIS. */
if (group->HaveAllActiveDevicesAsesTheSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) {
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
} else {
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING);
}
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
CisCreate(group);
} else {
LOG(ERROR) << __func__
<< ", invalid state transition, from: " << group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
}
}
void ProcessGroupDisable(LeAudioDeviceGroup* group, LeAudioDevice* device) {
/* Disable ASEs for next device in group. */
LeAudioDevice* deviceNext = group->GetNextActiveDevice(device);
if (deviceNext) {
PrepareAndSendDisable(deviceNext);
return;
}
/* At this point all of the active ASEs within group are disabled. As there
* is no Disabling state for Sink ASE, it might happen that all of the
* active ASEs are Sink ASE and will transit to QoS state. So check
* the group state, because we might be ready to release data path. */
if (group->HaveAllActiveDevicesAsesTheSameState(
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)) {
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
} else {
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);
}
/* Transition to QoS configured is done by CIS disconnection */
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
ReleaseDataPath(group);
} else {
LOG(ERROR) << __func__
<< ", invalid state transition, from: " << group->GetState()
<< ", to: " << group->GetTargetState();
StopStream(group);
}
}
};
} // namespace
namespace le_audio {
void LeAudioGroupStateMachine::Initialize(Callbacks* state_machine_callbacks_) {
if (instance) {
LOG(ERROR) << "Already initialized";
return;
}
instance = new LeAudioGroupStateMachineImpl(state_machine_callbacks_);
}
void LeAudioGroupStateMachine::Cleanup() {
if (!instance) return;
LeAudioGroupStateMachineImpl* ptr = instance;
instance = nullptr;
delete ptr;
}
LeAudioGroupStateMachine* LeAudioGroupStateMachine::Get() {
CHECK(instance);
return instance;
}
} // namespace le_audio