/* * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "platform.h" #include #include "multimodal/audio_pipeline.h" #include "multimodal/transport_ws.h" #include "platform_battery_internal.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" static platform_multimodal_transport_callbacks_t s_multimodal_transport_callbacks; static portMUX_TYPE s_multimodal_transport_cb_lock = portMUX_INITIALIZER_UNLOCKED; static platform_multimodal_downstream_playback_callbacks_t s_multimodal_downstream_playback_callbacks; static portMUX_TYPE s_multimodal_downstream_playback_cb_lock = portMUX_INITIALIZER_UNLOCKED; static uint32_t s_battery_playback_generation; static bool s_battery_playback_active; /* 将扬声器播放纳入电池高负载窗口,避免负载压降被误判为真实低电量。 */ static void platform_battery_note_playback_event( multimodal_audio_downstream_playback_event_type_t type, uint32_t generation) { bool begin_load = false; bool end_load = false; portENTER_CRITICAL(&s_multimodal_downstream_playback_cb_lock); if (type == MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STARTED) { if (!s_battery_playback_active) { s_battery_playback_active = true; begin_load = true; } s_battery_playback_generation = generation; } else if (s_battery_playback_active && s_battery_playback_generation == generation) { s_battery_playback_active = false; s_battery_playback_generation = 0U; end_load = true; } portEXIT_CRITICAL(&s_multimodal_downstream_playback_cb_lock); if (begin_load) { platform_battery_load_begin(PLATFORM_BATTERY_LOAD_AUDIO); } if (end_load) { platform_battery_load_end(PLATFORM_BATTERY_LOAD_AUDIO); } } static void snapshot_transport_callbacks( platform_multimodal_transport_callbacks_t *out_callbacks) { if (out_callbacks == NULL) { return; } portENTER_CRITICAL(&s_multimodal_transport_cb_lock); *out_callbacks = s_multimodal_transport_callbacks; portEXIT_CRITICAL(&s_multimodal_transport_cb_lock); /* 回调快照离开临界区后再调用,防止用户代码在自旋锁内运行。 */ } static void platform_multimodal_transport_event_bridge(const multimodal_ws_event_t *event, void *user_data) { /* 此处只做底层事件形状转换,连接策略和业务语义由上层负责。 */ (void)user_data; if (event == NULL) { return; } platform_multimodal_transport_callbacks_t callbacks = {0}; snapshot_transport_callbacks(&callbacks); if (event->type == MULTIMODAL_WS_EVENT_CONNECTED) { if (callbacks.on_connected != NULL) { callbacks.on_connected(callbacks.user_data); } return; } if (event->type == MULTIMODAL_WS_EVENT_DISCONNECTED) { if (callbacks.on_disconnected != NULL) { callbacks.on_disconnected(callbacks.user_data); } return; } if (event->type == MULTIMODAL_WS_EVENT_ERROR) { if (callbacks.on_error != NULL) { callbacks.on_error(callbacks.user_data); } return; } if (event->type == MULTIMODAL_WS_EVENT_TEXT_MESSAGE && callbacks.on_text_payload != NULL) { callbacks.on_text_payload(event->data, event->data_len, callbacks.user_data); return; } if (event->type == MULTIMODAL_WS_EVENT_BINARY_MESSAGE && callbacks.on_binary_payload != NULL) { callbacks.on_binary_payload(event->data, event->data_len, callbacks.user_data); } } static void snapshot_downstream_playback_callbacks( platform_multimodal_downstream_playback_callbacks_t *out_callbacks) { if (out_callbacks == NULL) { return; } portENTER_CRITICAL(&s_multimodal_downstream_playback_cb_lock); *out_callbacks = s_multimodal_downstream_playback_callbacks; portEXIT_CRITICAL(&s_multimodal_downstream_playback_cb_lock); } esp_err_t platform_multimodal_transport_start( const char *bearer_token, const char *workspace_id, const platform_multimodal_transport_callbacks_t *callbacks) { if (bearer_token == NULL || bearer_token[0] == '\0') { return ESP_ERR_INVALID_ARG; } platform_multimodal_transport_callbacks_t effective_callbacks = {0}; if (callbacks != NULL) { effective_callbacks = *callbacks; } bool has_callback = effective_callbacks.on_connected != NULL || effective_callbacks.on_disconnected != NULL || effective_callbacks.on_error != NULL || effective_callbacks.on_text_payload != NULL || effective_callbacks.on_binary_payload != NULL; portENTER_CRITICAL(&s_multimodal_transport_cb_lock); s_multimodal_transport_callbacks = effective_callbacks; portEXIT_CRITICAL(&s_multimodal_transport_cb_lock); return multimodal_ws_start( bearer_token, workspace_id, has_callback ? platform_multimodal_transport_event_bridge : NULL, NULL); } esp_err_t platform_multimodal_transport_stop(int timeout_ms) { esp_err_t err = multimodal_ws_stop(timeout_ms); portENTER_CRITICAL(&s_multimodal_transport_cb_lock); memset(&s_multimodal_transport_callbacks, 0, sizeof(s_multimodal_transport_callbacks)); portEXIT_CRITICAL(&s_multimodal_transport_cb_lock); return err; } bool platform_multimodal_transport_is_connected(void) { return multimodal_ws_is_connected(); } esp_err_t platform_multimodal_transport_send_text(const char *label, const char *text) { return multimodal_ws_send_text(label, text); } esp_err_t platform_multimodal_transport_send_text_realtime(const char *label, const char *text) { return multimodal_ws_send_text_realtime(label, text); } esp_err_t platform_multimodal_transport_send_binary_realtime(const uint8_t *data, size_t len) { return multimodal_ws_send_binary_realtime(data, len); } esp_err_t platform_multimodal_audio_init(void) { return multimodal_audio_init(); } void platform_multimodal_audio_deinit(void) { multimodal_audio_deinit(); } esp_err_t platform_multimodal_audio_start_uplink( esp_err_t (*uplink_send)(const uint8_t *data, size_t len, void *user_data), void *user_data) { return multimodal_audio_start_uplink(uplink_send, user_data); } esp_err_t platform_multimodal_audio_stop_uplink(int timeout_ms) { return multimodal_audio_stop_uplink(timeout_ms); } bool platform_multimodal_audio_is_uplink_running(void) { return multimodal_audio_is_uplink_running(); } bool platform_multimodal_audio_is_initialized(void) { return multimodal_audio_is_initialized(); } esp_err_t platform_multimodal_audio_set_speaker_output_enabled(bool enabled) { return multimodal_audio_set_speaker_output_enabled(enabled); } esp_err_t platform_multimodal_audio_register_pcm_observer( platform_multimodal_audio_pcm_observer_t observer, void *user_data) { return multimodal_audio_register_pcm_observer(observer, user_data); } esp_err_t platform_multimodal_audio_unregister_pcm_observer( platform_multimodal_audio_pcm_observer_t observer, void *user_data, int timeout_ms) { return multimodal_audio_unregister_pcm_observer( observer, user_data, timeout_ms); } esp_err_t platform_multimodal_audio_play_downstream_opus(const uint8_t *data, size_t len) { return multimodal_audio_handle_downstream_opus(data, len); } esp_err_t platform_multimodal_audio_begin_downstream_response( uint32_t *out_playback_generation) { return multimodal_audio_begin_downstream_response(out_playback_generation); } esp_err_t platform_multimodal_audio_finish_downstream_response(void) { return multimodal_audio_finish_downstream_response(); } esp_err_t platform_multimodal_audio_stop_downstream_playback(void) { return multimodal_audio_stop_downstream_playback(); } static void platform_multimodal_downstream_playback_bridge( const multimodal_audio_downstream_playback_event_t *event, void *user_data) { (void)user_data; platform_multimodal_downstream_playback_callbacks_t callbacks = {0}; snapshot_downstream_playback_callbacks(&callbacks); if (event == NULL) { return; } platform_battery_note_playback_event(event->type, event->playback_generation); switch (event->type) { case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STARTED: if (callbacks.on_started != NULL) { callbacks.on_started(event->playback_generation, callbacks.user_data); } break; case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_COMPLETED: if (callbacks.on_completed != NULL) { callbacks.on_completed(event->playback_generation, callbacks.user_data); } break; case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STOPPED: if (callbacks.on_stopped != NULL) { callbacks.on_stopped(event->playback_generation, callbacks.user_data); } break; case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STALLED: if (callbacks.on_stalled != NULL) { callbacks.on_stalled(event->playback_generation, callbacks.user_data); } break; default: break; } } esp_err_t platform_multimodal_audio_register_downstream_playback_callbacks( const platform_multimodal_downstream_playback_callbacks_t *callbacks) { platform_multimodal_downstream_playback_callbacks_t effective_callbacks = {0}; if (callbacks != NULL) { effective_callbacks = *callbacks; } portENTER_CRITICAL(&s_multimodal_downstream_playback_cb_lock); s_multimodal_downstream_playback_callbacks = effective_callbacks; portEXIT_CRITICAL(&s_multimodal_downstream_playback_cb_lock); bool has_callback = effective_callbacks.on_started != NULL || effective_callbacks.on_completed != NULL || effective_callbacks.on_stopped != NULL; return multimodal_audio_register_downstream_playback_callback( has_callback ? platform_multimodal_downstream_playback_bridge : NULL, NULL); }