|
|
@ -37,7 +37,8 @@ namespace drivers { |
|
|
|
|
|
|
|
|
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "bluetooth"; |
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "bluetooth"; |
|
|
|
|
|
|
|
|
|
|
|
DRAM_ATTR static PcmBuffer* sStream = nullptr; |
|
|
|
DRAM_ATTR static PcmBuffer* sStream1 = nullptr; |
|
|
|
|
|
|
|
DRAM_ATTR static PcmBuffer* sStream2 = nullptr; |
|
|
|
DRAM_ATTR static std::atomic<float> sVolumeFactor = 1.f; |
|
|
|
DRAM_ATTR static std::atomic<float> sVolumeFactor = 1.f; |
|
|
|
|
|
|
|
|
|
|
|
static tasks::WorkerPool* sBgWorker; |
|
|
|
static tasks::WorkerPool* sBgWorker; |
|
|
@ -96,13 +97,15 @@ IRAM_ATTR auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t { |
|
|
|
if (buf == nullptr || buf_size <= 0) { |
|
|
|
if (buf == nullptr || buf_size <= 0) { |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
PcmBuffer* stream = sStream; |
|
|
|
PcmBuffer* stream1 = sStream1; |
|
|
|
if (stream == nullptr) { |
|
|
|
PcmBuffer* stream2 = sStream2; |
|
|
|
|
|
|
|
if (stream1 == nullptr || stream2 == nullptr) { |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int16_t* samples = reinterpret_cast<int16_t*>(buf); |
|
|
|
int16_t* samples = reinterpret_cast<int16_t*>(buf); |
|
|
|
stream->receive({samples, static_cast<size_t>(buf_size / 2)}, false); |
|
|
|
stream1->receive({samples, static_cast<size_t>(buf_size / 2)}, false, false); |
|
|
|
|
|
|
|
stream2->receive({samples, static_cast<size_t>(buf_size / 2)}, true, false); |
|
|
|
|
|
|
|
|
|
|
|
// Apply software volume scaling.
|
|
|
|
// Apply software volume scaling.
|
|
|
|
float factor = sVolumeFactor.load(); |
|
|
|
float factor = sVolumeFactor.load(); |
|
|
@ -181,14 +184,16 @@ auto Bluetooth::PreferredDevice() -> std::optional<bluetooth::MacAndName> { |
|
|
|
return bluetooth::BluetoothState::preferred_device(); |
|
|
|
return bluetooth::BluetoothState::preferred_device(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto Bluetooth::SetSource(PcmBuffer* src) -> void { |
|
|
|
auto Bluetooth::SetSources(PcmBuffer* src1, PcmBuffer* src2) -> void { |
|
|
|
auto lock = bluetooth::BluetoothState::lock(); |
|
|
|
auto lock = bluetooth::BluetoothState::lock(); |
|
|
|
if (src == bluetooth::BluetoothState::source()) { |
|
|
|
PcmBuffer *cur1, *cur2; |
|
|
|
|
|
|
|
std::tie(cur1, cur2) = bluetooth::BluetoothState::sources(); |
|
|
|
|
|
|
|
if (src1 == cur1 && src2 == cur2) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
bluetooth::BluetoothState::source(src); |
|
|
|
bluetooth::BluetoothState::sources(src1, src2); |
|
|
|
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch( |
|
|
|
tinyfsm::FsmList<bluetooth::BluetoothState>::dispatch( |
|
|
|
bluetooth::events::SourceChanged{}); |
|
|
|
bluetooth::events::SourcesChanged{}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto Bluetooth::SetVolumeFactor(float f) -> void { |
|
|
|
auto Bluetooth::SetVolumeFactor(float f) -> void { |
|
|
@ -348,7 +353,6 @@ std::optional<MacAndName> BluetoothState::sPreferredDevice_{}; |
|
|
|
std::optional<MacAndName> BluetoothState::sConnectingDevice_{}; |
|
|
|
std::optional<MacAndName> BluetoothState::sConnectingDevice_{}; |
|
|
|
int BluetoothState::sConnectAttemptsRemaining_{0}; |
|
|
|
int BluetoothState::sConnectAttemptsRemaining_{0}; |
|
|
|
|
|
|
|
|
|
|
|
std::atomic<PcmBuffer*> BluetoothState::sSource_; |
|
|
|
|
|
|
|
std::function<void(Event)> BluetoothState::sEventHandler_; |
|
|
|
std::function<void(Event)> BluetoothState::sEventHandler_; |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::Init(NvsStorage& storage) -> void { |
|
|
|
auto BluetoothState::Init(NvsStorage& storage) -> void { |
|
|
@ -377,12 +381,13 @@ auto BluetoothState::preferred_device(std::optional<MacAndName> addr) -> void { |
|
|
|
sPreferredDevice_ = addr; |
|
|
|
sPreferredDevice_ = addr; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::source() -> PcmBuffer* { |
|
|
|
auto BluetoothState::sources() -> std::pair<PcmBuffer*, PcmBuffer*> { |
|
|
|
return sSource_.load(); |
|
|
|
return {sStream1, sStream2}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::source(PcmBuffer* src) -> void { |
|
|
|
auto BluetoothState::sources(PcmBuffer* src1, PcmBuffer* src2) -> void { |
|
|
|
sSource_.store(src); |
|
|
|
sStream1 = src1; |
|
|
|
|
|
|
|
sStream2 = src2; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void { |
|
|
|
auto BluetoothState::event_handler(std::function<void(Event)> cb) -> void { |
|
|
@ -508,11 +513,13 @@ void Disabled::react(const events::Enable&) { |
|
|
|
// AVRCP Target
|
|
|
|
// AVRCP Target
|
|
|
|
err = esp_avrc_tg_init(); |
|
|
|
err = esp_avrc_tg_init(); |
|
|
|
if (err != ESP_OK) { |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(kTag, "Error during target init: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error during target init: %s %d", esp_err_to_name(err), |
|
|
|
|
|
|
|
err); |
|
|
|
} |
|
|
|
} |
|
|
|
err = esp_avrc_tg_register_callback(avrcp_tg_cb); |
|
|
|
err = esp_avrc_tg_register_callback(avrcp_tg_cb); |
|
|
|
if (err != ESP_OK) { |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(kTag, "Error registering AVRC tg callback: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error registering AVRC tg callback: %s %d", |
|
|
|
|
|
|
|
esp_err_to_name(err), err); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Set the supported passthrough commands on the tg
|
|
|
|
// Set the supported passthrough commands on the tg
|
|
|
@ -522,10 +529,12 @@ void Disabled::react(const events::Enable&) { |
|
|
|
do { |
|
|
|
do { |
|
|
|
// Sleep for a bit
|
|
|
|
// Sleep for a bit
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10)); |
|
|
|
vTaskDelay(pdMS_TO_TICKS(10)); |
|
|
|
err = esp_avrc_tg_get_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_ALLOWED_CMD, &psth); |
|
|
|
err = esp_avrc_tg_get_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_ALLOWED_CMD, |
|
|
|
|
|
|
|
&psth); |
|
|
|
} while (err != ESP_OK); |
|
|
|
} while (err != ESP_OK); |
|
|
|
|
|
|
|
|
|
|
|
err = esp_avrc_tg_set_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD, &psth); |
|
|
|
err = esp_avrc_tg_set_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD, |
|
|
|
|
|
|
|
&psth); |
|
|
|
if (err != ESP_OK) { |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
} |
|
|
|
} |
|
|
@ -534,7 +543,6 @@ void Disabled::react(const events::Enable&) { |
|
|
|
ESP_AVRC_RN_VOLUME_CHANGE); |
|
|
|
ESP_AVRC_RN_VOLUME_CHANGE); |
|
|
|
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); |
|
|
|
assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC
|
|
|
|
// Initialise A2DP. This handles streaming audio. Currently ESP-IDF's SBC
|
|
|
|
// encoder only supports 2 channels of interleaved 16 bit samples, at
|
|
|
|
// encoder only supports 2 channels of interleaved 16 bit samples, at
|
|
|
|
// 44.1kHz, so there is no additional configuration to be done for the
|
|
|
|
// 44.1kHz, so there is no additional configuration to be done for the
|
|
|
@ -724,9 +732,8 @@ void Connected::react(const events::PreferredDeviceChanged& ev) { |
|
|
|
transit<Connecting>(); |
|
|
|
transit<Connecting>(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Connected::react(const events::SourceChanged& ev) { |
|
|
|
void Connected::react(const events::SourcesChanged& ev) { |
|
|
|
sStream = sSource_; |
|
|
|
if (sStream1 != nullptr && sStream2 != nullptr) { |
|
|
|
if (sStream != nullptr) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "checking source is ready"); |
|
|
|
ESP_LOGI(kTag, "checking source is ready"); |
|
|
|
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); |
|
|
|
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); |
|
|
|
} else { |
|
|
|
} else { |
|
|
@ -775,7 +782,8 @@ void Connected::react(events::internal::Avrc ev) { |
|
|
|
switch (ev.type) { |
|
|
|
switch (ev.type) { |
|
|
|
case ESP_AVRC_CT_CONNECTION_STATE_EVT: |
|
|
|
case ESP_AVRC_CT_CONNECTION_STATE_EVT: |
|
|
|
if (ev.param.conn_stat.connected) { |
|
|
|
if (ev.param.conn_stat.connected) { |
|
|
|
auto err = esp_avrc_ct_send_register_notification_cmd(4, ESP_AVRC_RN_VOLUME_CHANGE, 0); |
|
|
|
auto err = esp_avrc_ct_send_register_notification_cmd( |
|
|
|
|
|
|
|
4, ESP_AVRC_RN_VOLUME_CHANGE, 0); |
|
|
|
if (err != ESP_OK) { |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
} |
|
|
|
} |
|
|
@ -787,15 +795,20 @@ void Connected::react(events::internal::Avrc ev) { |
|
|
|
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: |
|
|
|
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: |
|
|
|
// The remote device is telling us about its capabilities! We don't
|
|
|
|
// The remote device is telling us about its capabilities! We don't
|
|
|
|
// currently care about any of them.
|
|
|
|
// currently care about any of them.
|
|
|
|
ESP_LOGI(kTag, "Recieved capabilitites: %lu", ev.param.rmt_feats.feat_mask); |
|
|
|
ESP_LOGI(kTag, "Recieved capabilitites: %lu", |
|
|
|
|
|
|
|
ev.param.rmt_feats.feat_mask); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: |
|
|
|
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: |
|
|
|
if (ev.param.change_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { |
|
|
|
if (ev.param.change_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { |
|
|
|
if (sEventHandler_) { |
|
|
|
if (sEventHandler_) { |
|
|
|
std::invoke(sEventHandler_, bluetooth::RemoteVolumeChanged{.new_vol = ev.param.change_ntf.event_parameter.volume}); |
|
|
|
std::invoke( |
|
|
|
|
|
|
|
sEventHandler_, |
|
|
|
|
|
|
|
bluetooth::RemoteVolumeChanged{ |
|
|
|
|
|
|
|
.new_vol = ev.param.change_ntf.event_parameter.volume}); |
|
|
|
} |
|
|
|
} |
|
|
|
// Resubscribe to volume facts
|
|
|
|
// Resubscribe to volume facts
|
|
|
|
auto err = esp_avrc_ct_send_register_notification_cmd(4, ESP_AVRC_RN_VOLUME_CHANGE, 0); |
|
|
|
auto err = esp_avrc_ct_send_register_notification_cmd( |
|
|
|
|
|
|
|
4, ESP_AVRC_RN_VOLUME_CHANGE, 0); |
|
|
|
if (err != ESP_OK) { |
|
|
|
if (err != ESP_OK) { |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
} |
|
|
|
} |
|
|
@ -809,16 +822,20 @@ void Connected::react(events::internal::Avrc ev) { |
|
|
|
void Connected::react(const events::internal::Avrctg ev) { |
|
|
|
void Connected::react(const events::internal::Avrctg ev) { |
|
|
|
switch (ev.type) { |
|
|
|
switch (ev.type) { |
|
|
|
case ESP_AVRC_TG_CONNECTION_STATE_EVT: |
|
|
|
case ESP_AVRC_TG_CONNECTION_STATE_EVT: |
|
|
|
ESP_LOGI(kTag, "Got connection event. Connected: %s", ev.param.conn_stat.connected ? "true" : "false"); |
|
|
|
ESP_LOGI(kTag, "Got connection event. Connected: %s", |
|
|
|
|
|
|
|
ev.param.conn_stat.connected ? "true" : "false"); |
|
|
|
if (ev.param.conn_stat.connected) { |
|
|
|
if (ev.param.conn_stat.connected) { |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: |
|
|
|
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: |
|
|
|
ESP_LOGI(kTag, "Got remote features feat flag %d", ev.param.rmt_feats.ct_feat_flag); |
|
|
|
ESP_LOGI(kTag, "Got remote features feat flag %d", |
|
|
|
ESP_LOGI(kTag, "Got remote features feat mask %lu", ev.param.rmt_feats.feat_mask); |
|
|
|
ev.param.rmt_feats.ct_feat_flag); |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "Got remote features feat mask %lu", |
|
|
|
|
|
|
|
ev.param.rmt_feats.feat_mask); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: |
|
|
|
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: |
|
|
|
ESP_LOGI(kTag, "Got passthrough event keycode: %x, %d", ev.param.psth_cmd.key_code, ev.param.psth_cmd.key_state); |
|
|
|
ESP_LOGI(kTag, "Got passthrough event keycode: %x, %d", |
|
|
|
|
|
|
|
ev.param.psth_cmd.key_code, ev.param.psth_cmd.key_state); |
|
|
|
if (ev.param.psth_cmd.key_state == 1 && sEventHandler_) { |
|
|
|
if (ev.param.psth_cmd.key_state == 1 && sEventHandler_) { |
|
|
|
switch (ev.param.psth_cmd.key_code) { |
|
|
|
switch (ev.param.psth_cmd.key_code) { |
|
|
|
case ESP_AVRC_PT_CMD_PLAY: |
|
|
|
case ESP_AVRC_PT_CMD_PLAY: |
|
|
@ -840,7 +857,8 @@ void Connected::react(const events::internal::Avrctg ev) { |
|
|
|
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kBackward); |
|
|
|
std::invoke(sEventHandler_, bluetooth::SimpleEvent::kBackward); |
|
|
|
break; |
|
|
|
break; |
|
|
|
default: |
|
|
|
default: |
|
|
|
ESP_LOGI(kTag, "Unhandled passthrough cmd. Key code: %d", ev.param.psth_cmd.key_code); |
|
|
|
ESP_LOGI(kTag, "Unhandled passthrough cmd. Key code: %d", |
|
|
|
|
|
|
|
ev.param.psth_cmd.key_code); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
@ -855,7 +873,8 @@ void Connected::react(const events::internal::Avrctg ev) { |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
ESP_LOGE(kTag, "Error: %s %d", esp_err_to_name(err), err); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
ESP_LOGW(kTag, "unhandled AVRC TG Register Notification event: %u", ev.param.reg_ntf.event_id); |
|
|
|
ESP_LOGW(kTag, "unhandled AVRC TG Register Notification event: %u", |
|
|
|
|
|
|
|
ev.param.reg_ntf.event_id); |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|