Files
lucky-cat/components/platform/src/runtime/platform_multimodal_runtime.c
T

320 lines
10 KiB
C

/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "platform.h"
#include <string.h>
#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);
}