启动后播放开机声音完成

This commit is contained in:
CSBOOK\44744
2026-06-25 16:22:51 +08:00
parent 56ac1cd5d6
commit 5812fc500b
67 changed files with 342 additions and 204 deletions
+14 -1
View File
@@ -52,11 +52,15 @@ idf_component_register(
)
set(platform_lvgl_animation_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lvgl_animations")
set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts")
set(platform_lvgl_animation_image_dir "${CMAKE_CURRENT_BINARY_DIR}/lvgl_animations_image")
set(platform_lvgl_animation_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/lvgl_animations_image.stamp")
file(GLOB_RECURSE platform_lvgl_animation_bin_files CONFIGURE_DEPENDS
"${platform_lvgl_animation_src_dir}/*.bin"
)
file(GLOB_RECURSE platform_audio_prompt_files CONFIGURE_DEPENDS
"${platform_audio_prompt_src_dir}/*.opus"
)
set(platform_lvgl_animation_stage_commands)
foreach(animation_file IN LISTS platform_lvgl_animation_bin_files)
@@ -69,6 +73,15 @@ foreach(animation_file IN LISTS platform_lvgl_animation_bin_files)
"${platform_lvgl_animation_image_dir}/${animation_rel_path}"
)
endforeach()
foreach(audio_file IN LISTS platform_audio_prompt_files)
file(RELATIVE_PATH audio_rel_path "${platform_audio_prompt_src_dir}" "${audio_file}")
list(APPEND platform_lvgl_animation_stage_commands
COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_lvgl_animation_image_dir}/audio_prompts"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${audio_file}"
"${platform_lvgl_animation_image_dir}/audio_prompts/${audio_rel_path}"
)
endforeach()
add_custom_command(
OUTPUT "${platform_lvgl_animation_stage_stamp}"
@@ -76,7 +89,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_lvgl_animation_image_dir}"
${platform_lvgl_animation_stage_commands}
COMMAND ${CMAKE_COMMAND} -E touch "${platform_lvgl_animation_stage_stamp}"
DEPENDS ${platform_lvgl_animation_bin_files}
DEPENDS ${platform_lvgl_animation_bin_files} ${platform_audio_prompt_files}
)
littlefs_create_partition_image(
Binary file not shown.
@@ -296,7 +296,8 @@ typedef struct {
bool uplink_send_queue_with_caps;
TaskHandle_t uplink_task;
TaskHandle_t uplink_send_task;
volatile bool uplink_stop_requested;
volatile bool uplink_recording_active;
volatile bool uplink_task_exit_requested;
multimodal_audio_uplink_cb_t uplink_cb;
void *uplink_cb_ctx;
bool opus_ready;
@@ -79,24 +79,46 @@ static void reset_uplink_debug_stats(void)
#endif
}
static bool audio_get_uplink_stop_requested(const multimodal_audio_ctx_t *ctx)
static bool audio_get_uplink_recording_active(const multimodal_audio_ctx_t *ctx)
{
bool stop_requested = true;
bool active = false;
portENTER_CRITICAL(&s_audio_runtime_lock);
if (ctx != NULL) {
stop_requested = ctx->uplink_stop_requested;
active = ctx->uplink_recording_active;
}
portEXIT_CRITICAL(&s_audio_runtime_lock);
return stop_requested;
return active;
}
static void audio_set_uplink_stop_requested(multimodal_audio_ctx_t *ctx, bool stop_requested)
static void audio_set_uplink_recording_active(multimodal_audio_ctx_t *ctx, bool active)
{
portENTER_CRITICAL(&s_audio_runtime_lock);
if (ctx != NULL) {
ctx->uplink_stop_requested = stop_requested;
ctx->uplink_recording_active = active;
}
portEXIT_CRITICAL(&s_audio_runtime_lock);
}
static bool audio_get_uplink_task_exit_requested(const multimodal_audio_ctx_t *ctx)
{
bool exit_requested = true;
portENTER_CRITICAL(&s_audio_runtime_lock);
if (ctx != NULL) {
exit_requested = ctx->uplink_task_exit_requested;
}
portEXIT_CRITICAL(&s_audio_runtime_lock);
return exit_requested;
}
static void audio_set_uplink_task_exit_requested(multimodal_audio_ctx_t *ctx, bool exit_requested)
{
portENTER_CRITICAL(&s_audio_runtime_lock);
if (ctx != NULL) {
ctx->uplink_task_exit_requested = exit_requested;
}
portEXIT_CRITICAL(&s_audio_runtime_lock);
}
@@ -512,6 +534,7 @@ static void notify_downstream_playback_stopped(void);
static esp_err_t prime_downstream_output_path(multimodal_audio_ctx_t *ctx);
static esp_err_t handle_downstream_opus_sync(const uint8_t *data, size_t len);
static esp_err_t ensure_downstream_playback_task(multimodal_audio_ctx_t *ctx);
static esp_err_t ensure_uplink_worker_tasks(multimodal_audio_ctx_t *ctx);
static esp_err_t stop_downstream_idle_mute_task(multimodal_audio_ctx_t *ctx, int timeout_ms);
#define MULTIMODAL_AUDIO_DOWNSTREAM_STATS_INTERVAL 200U
@@ -620,25 +643,14 @@ static void uplink_send_task(void *arg)
(unsigned)get_uplink_send_queue_len(),
(unsigned)CONFIG_MULTIMODAL_AUDIO_UPLINK_MAX_PACKET_BYTES);
while (true) {
bool stop_requested = audio_get_uplink_stop_requested(ctx);
while (!audio_get_uplink_task_exit_requested(ctx)) {
if (!ctx->uplink_send_queue) {
if (stop_requested) {
break;
}
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
uplink_packet_t packet = {0};
if (stop_requested && uxQueueMessagesWaiting(ctx->uplink_send_queue) == 0U) {
break;
}
if (xQueueReceive(ctx->uplink_send_queue, &packet, pdMS_TO_TICKS(100)) != pdTRUE) {
if (stop_requested) {
break;
}
continue;
}
if (packet.len == 0U) {
@@ -697,7 +709,12 @@ static void uplink_capture_task(void *arg)
(unsigned)ctx->fs.channel,
(unsigned)ctx->mic_capture_channel);
while (!audio_get_uplink_stop_requested(ctx)) {
while (!audio_get_uplink_task_exit_requested(ctx)) {
if (!audio_get_uplink_recording_active(ctx)) {
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100));
continue;
}
if (!ctx->initialized || !ctx->codec_dev || !ctx->opus_enc) {
vTaskDelay(pdMS_TO_TICKS(20));
continue;
@@ -736,6 +753,69 @@ static void uplink_capture_task(void *arg)
delete_audio_task_with_caps(NULL);
}
static esp_err_t ensure_uplink_worker_tasks(multimodal_audio_ctx_t *ctx)
{
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!ctx->initialized || !ctx->codec_dev || !ctx->opus_enc ||
!ctx->uplink_codec_buf || !ctx->uplink_mono_buf || !ctx->uplink_opus_buf) {
return ESP_ERR_INVALID_STATE;
}
if (ctx->uplink_send_queue == NULL) {
bool queue_with_caps = false;
UBaseType_t queue_len = get_uplink_send_queue_len();
ctx->uplink_send_queue = create_uplink_send_queue(queue_len, &queue_with_caps);
if (ctx->uplink_send_queue == NULL) {
ESP_LOGW(TAG,
"Create uplink send queue failed (len=%u item=%u)",
(unsigned)queue_len,
(unsigned)sizeof(uplink_packet_t));
return ESP_ERR_NO_MEM;
}
ctx->uplink_send_queue_with_caps = queue_with_caps;
}
audio_set_uplink_task_exit_requested(ctx, false);
TaskHandle_t capture_task = NULL;
TaskHandle_t send_task = NULL;
audio_snapshot_uplink_tasks(ctx, &capture_task, &send_task);
if (capture_task == NULL) {
BaseType_t created = create_audio_task_with_caps(uplink_capture_task,
"mm_audio_uplink",
get_uplink_task_stack_bytes(),
NULL,
5,
&ctx->uplink_task,
true,
MULTIMODAL_AUDIO_CORE_ID);
if (created != pdPASS) {
audio_set_uplink_capture_task(ctx, NULL);
return ESP_ERR_NO_MEM;
}
}
audio_snapshot_uplink_tasks(ctx, NULL, &send_task);
if (send_task == NULL) {
BaseType_t created = create_audio_task_with_caps(uplink_send_task,
"mm_audio_up_send",
get_uplink_send_task_stack_bytes(),
NULL,
6,
&ctx->uplink_send_task,
true,
MULTIMODAL_AUDIO_CORE_ID);
if (created != pdPASS) {
audio_set_uplink_send_task(ctx, NULL);
return ESP_ERR_NO_MEM;
}
}
return ESP_OK;
}
esp_err_t multimodal_audio_init(void)
{
#if !CONFIG_MULTIMODAL_AUDIO_ENABLE_CODEC_PATH
@@ -793,8 +873,29 @@ esp_err_t multimodal_audio_init(void)
audio_set_deinit_in_progress(&s_audio, false);
audio_set_downstream_playback_stopping(&s_audio, false);
audio_set_downstream_playback_stop_requested(&s_audio, false);
audio_set_uplink_recording_active(&s_audio, false);
audio_set_uplink_task_exit_requested(&s_audio, false);
s_audio.initialized = true;
audio_debug_dump_init();
err = ensure_uplink_worker_tasks(&s_audio);
if (err == ESP_OK) {
err = ensure_downstream_playback_task(&s_audio);
}
if (err != ESP_OK) {
ESP_LOGW(TAG, "Create audio runtime tasks failed: %s", esp_err_to_name(err));
s_audio.initialized = false;
audio_debug_dump_deinit();
(void)multimodal_audio_stop_uplink(1000);
(void)multimodal_audio_stop_downstream_playback();
cleanup_codec_path(&s_audio);
deinit_buses(&s_audio);
if (!reuse_prewarmed_opus) {
cleanup_opus_path(&s_audio);
memset(&s_audio, 0, sizeof(s_audio));
}
last_err = err;
continue;
}
ESP_LOGI(TAG,
"Codec initialized: %uHz %uch %ubit opus(frame=%ums bitrate=%ukbps)",
(unsigned)s_audio.fs.sample_rate,
@@ -838,6 +939,12 @@ void multimodal_audio_deinit(void)
audio_set_downstream_playback_stopping(&s_audio, true);
audio_debug_dump_deinit();
(void)multimodal_audio_stop_uplink(2000);
audio_set_uplink_task_exit_requested(&s_audio, true);
TaskHandle_t notify_capture_task = NULL;
audio_snapshot_uplink_tasks(&s_audio, &notify_capture_task, NULL);
if (notify_capture_task != NULL) {
xTaskNotifyGive(notify_capture_task);
}
(void)multimodal_audio_stop_downstream_playback();
for (int i = 0; i < 100 && audio_has_uplink_tasks(&s_audio); ++i) {
vTaskDelay(pdMS_TO_TICKS(20));
@@ -894,140 +1001,25 @@ esp_err_t multimodal_audio_start_uplink(multimodal_audio_uplink_cb_t cb, void *c
return ESP_ERR_INVALID_STATE;
}
if (audio_get_uplink_stop_requested(&s_audio)) {
ESP_LOGW(TAG, "Mic uplink stop in progress");
return ESP_ERR_INVALID_STATE;
}
if (audio_has_uplink_tasks(&s_audio)) {
return ESP_OK;
}
audio_set_uplink_callback(&s_audio, cb, cb_ctx);
s_audio.uplink_mono_fill_bytes = 0U;
memset(s_audio.uplink_mono_buf, 0, s_audio.uplink_mono_buf_capacity_bytes);
audio_set_uplink_stop_requested(&s_audio, false);
reset_uplink_debug_stats();
uint32_t uplink_stack_bytes = get_uplink_task_stack_bytes();
uint32_t uplink_send_stack_bytes = get_uplink_send_task_stack_bytes();
UBaseType_t uplink_send_queue_len = get_uplink_send_queue_len();
log_heap_snapshot("before_uplink_start");
if (s_audio.uplink_send_queue == NULL) {
bool queue_with_caps = false;
s_audio.uplink_send_queue = create_uplink_send_queue(uplink_send_queue_len, &queue_with_caps);
if (s_audio.uplink_send_queue == NULL) {
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
size_t largest_internal = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#if SOC_SPIRAM_SUPPORTED
size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
size_t largest_spiram = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
ESP_LOGW(TAG,
"Create uplink send queue failed (len=%u item=%u free_int=%u largest_int=%u free_spiram=%u largest_spiram=%u)",
(unsigned)uplink_send_queue_len,
(unsigned)sizeof(uplink_packet_t),
(unsigned)free_internal,
(unsigned)largest_internal,
(unsigned)free_spiram,
(unsigned)largest_spiram);
#else
ESP_LOGW(TAG,
"Create uplink send queue failed (len=%u item=%u free_int=%u largest_int=%u)",
(unsigned)uplink_send_queue_len,
(unsigned)sizeof(uplink_packet_t),
(unsigned)free_internal,
(unsigned)largest_internal);
#endif
esp_err_t err = ensure_uplink_worker_tasks(&s_audio);
if (err != ESP_OK) {
audio_set_uplink_callback(&s_audio, NULL, NULL);
audio_set_uplink_stop_requested(&s_audio, false);
return ESP_ERR_NO_MEM;
return err;
}
s_audio.uplink_send_queue_with_caps = queue_with_caps;
} else {
reset_uplink_send_queue(&s_audio);
}
BaseType_t created = create_audio_task_with_caps(uplink_capture_task,
"mm_audio_uplink",
uplink_stack_bytes,
NULL,
5,
&s_audio.uplink_task,
true,
MULTIMODAL_AUDIO_CORE_ID);
if (created != pdPASS) {
audio_set_uplink_capture_task(&s_audio, NULL);
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
size_t largest_internal = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#if SOC_SPIRAM_SUPPORTED
size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
size_t largest_spiram = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
ESP_LOGW(TAG,
"Create uplink capture task failed (stack=%uB free_int=%u largest_int=%u free_spiram=%u largest_spiram=%u)",
(unsigned)uplink_stack_bytes,
(unsigned)free_internal,
(unsigned)largest_internal,
(unsigned)free_spiram,
(unsigned)largest_spiram);
#else
ESP_LOGW(TAG,
"Create uplink capture task failed (stack=%uB free_int=%u largest_int=%u)",
(unsigned)uplink_stack_bytes,
(unsigned)free_internal,
(unsigned)largest_internal);
#endif
reset_uplink_send_queue(&s_audio);
audio_set_uplink_callback(&s_audio, NULL, NULL);
audio_set_uplink_stop_requested(&s_audio, false);
return ESP_ERR_NO_MEM;
}
TaskHandle_t existing_send_task = NULL;
audio_snapshot_uplink_tasks(&s_audio, NULL, &existing_send_task);
if (existing_send_task == NULL) {
BaseType_t send_created = create_audio_task_with_caps(uplink_send_task,
"mm_audio_up_send",
uplink_send_stack_bytes,
NULL,
6,
&s_audio.uplink_send_task,
true,
MULTIMODAL_AUDIO_CORE_ID);
if (send_created != pdPASS) {
audio_set_uplink_send_task(&s_audio, NULL);
audio_set_uplink_stop_requested(&s_audio, true);
for (int i = 0; i < 40 && audio_has_uplink_tasks(&s_audio); ++i) {
vTaskDelay(pdMS_TO_TICKS(20));
}
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
size_t largest_internal = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#if SOC_SPIRAM_SUPPORTED
size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
size_t largest_spiram = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
ESP_LOGW(TAG,
"Create uplink send task failed (stack=%uB free_int=%u largest_int=%u free_spiram=%u largest_spiram=%u)",
(unsigned)uplink_send_stack_bytes,
(unsigned)free_internal,
(unsigned)largest_internal,
(unsigned)free_spiram,
(unsigned)largest_spiram);
#else
ESP_LOGW(TAG,
"Create uplink send task failed (stack=%uB free_int=%u largest_int=%u)",
(unsigned)uplink_send_stack_bytes,
(unsigned)free_internal,
(unsigned)largest_internal);
#endif
reset_uplink_send_queue(&s_audio);
audio_set_uplink_callback(&s_audio, NULL, NULL);
if (!audio_has_uplink_tasks(&s_audio)) {
audio_set_uplink_stop_requested(&s_audio, false);
audio_set_uplink_recording_active(&s_audio, true);
TaskHandle_t capture_task = NULL;
audio_snapshot_uplink_tasks(&s_audio, &capture_task, NULL);
if (capture_task != NULL) {
xTaskNotifyGive(capture_task);
}
return ESP_ERR_NO_MEM;
}
}
log_heap_snapshot("after_uplink_start");
return ESP_OK;
}
@@ -1038,63 +1030,18 @@ esp_err_t multimodal_audio_stop_uplink(int timeout_ms)
return ESP_ERR_INVALID_ARG;
}
if (!audio_has_uplink_tasks(&s_audio)) {
(void)timeout_ms;
audio_set_uplink_recording_active(&s_audio, false);
reset_uplink_send_queue(&s_audio);
audio_set_uplink_callback(&s_audio, NULL, NULL);
s_audio.uplink_mono_fill_bytes = 0U;
audio_set_uplink_stop_requested(&s_audio, false);
return ESP_OK;
}
audio_set_uplink_stop_requested(&s_audio, true);
if (timeout_ms == 0) {
audio_set_uplink_callback(&s_audio, NULL, NULL);
return ESP_OK;
}
int effective_timeout_ms = timeout_ms;
int waited_ms = 0;
while (audio_has_uplink_tasks(&s_audio) && waited_ms < effective_timeout_ms) {
vTaskDelay(pdMS_TO_TICKS(20));
waited_ms += 20;
}
TaskHandle_t capture_task = NULL;
TaskHandle_t send_task = NULL;
audio_snapshot_uplink_tasks(&s_audio, &capture_task, &send_task);
esp_err_t result = ESP_OK;
if (capture_task != NULL || send_task != NULL) {
ESP_LOGW(TAG,
"Mic uplink stop timeout, force delete (capture=%p send=%p)",
(void *)capture_task,
(void *)send_task);
result = ESP_ERR_TIMEOUT;
if (capture_task != NULL) {
delete_audio_task_with_caps(capture_task);
audio_set_uplink_capture_task(&s_audio, NULL);
}
if (send_task != NULL) {
delete_audio_task_with_caps(send_task);
audio_set_uplink_send_task(&s_audio, NULL);
}
}
reset_uplink_send_queue(&s_audio);
audio_set_uplink_callback(&s_audio, NULL, NULL);
s_audio.uplink_mono_fill_bytes = 0U;
audio_set_uplink_stop_requested(&s_audio, false);
log_heap_snapshot("after_uplink_stop");
return result;
return ESP_OK;
}
bool multimodal_audio_is_uplink_running(void)
{
TaskHandle_t capture_task = NULL;
TaskHandle_t send_task = NULL;
audio_snapshot_uplink_tasks(&s_audio, &capture_task, &send_task);
return capture_task != NULL &&
send_task != NULL &&
!audio_get_uplink_stop_requested(&s_audio);
return audio_get_uplink_recording_active(&s_audio);
}
static uint32_t snapshot_downstream_playback_generation(void)
@@ -1664,9 +1611,6 @@ static void downstream_playback_task(void *arg)
if (received != pdTRUE || packet.data == NULL || packet.len == 0U) {
if (snapshot_downstream_response_end_received()) {
maybe_notify_downstream_playback_completed_after_drain("queue_empty");
if (!snapshot_downstream_response_active()) {
break;
}
}
vTaskDelay(pdMS_TO_TICKS(10));
continue;
@@ -1923,11 +1867,16 @@ esp_err_t multimodal_audio_stop_downstream_playback(void)
(void)advance_downstream_playback_generation();
notify_downstream_playback_stopped();
bool keep_stopping = audio_get_deinit_in_progress(ctx);
esp_err_t task_stop_err = stop_downstream_playback_task(ctx, 1000, keep_stopping);
if (audio_get_deinit_in_progress(ctx)) {
esp_err_t task_stop_err = stop_downstream_playback_task(ctx, 1000, true);
if (task_stop_err != ESP_OK && task_stop_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Stop downstream playback task failed: %s", esp_err_to_name(task_stop_err));
}
} else {
drain_downstream_playback_queue(ctx);
audio_set_downstream_playback_stop_requested(ctx, false);
audio_set_downstream_playback_stopping(ctx, false);
}
if (!ctx->initialized || ctx->codec_dev == NULL) {
return ESP_OK;
@@ -4,11 +4,20 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "esp_littlefs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "multimodal/audio_pipeline.h"
#include "power_manager.h"
#include "sdkconfig.h"
#include "voice_prompt.h"
@@ -21,8 +30,19 @@
#error "CONFIG_TQ_POWER_HOLD_ACTIVE_LEVEL must be defined in Kconfig"
#endif
#define VOICE_PROMPT_DEFAULT_BASE_PATH "/littlefs/audio_prompts"
#define VOICE_PROMPT_LITTLEFS_BASE_PATH "/littlefs"
#define VOICE_PROMPT_LITTLEFS_PARTITION "gifpack"
#define VOICE_PROMPT_FRAME_HEADER_BYTES 4U
#define VOICE_PROMPT_MAX_OPUS_PACKET_BYTES 2048U
#define VOICE_PROMPT_OPUS_FRAME_MS 60U
#define VOICE_PROMPT_DRAIN_MARGIN_MS 120U
static const char *TAG = "power_manager";
static const char *VOICE_TAG = "voice_prompt";
static bool s_power_hold_configured;
static bool s_voice_prompt_littlefs_mount_attempted;
static esp_err_t s_voice_prompt_littlefs_mount_result = ESP_ERR_INVALID_STATE;
static uint64_t power_manager_gpio_bit_mask(int gpio_num)
{
@@ -91,8 +111,163 @@ void platform_power_hold_init(void)
power_manager_set_keep_awake(true);
}
static esp_err_t voice_prompt_mount_littlefs(void)
{
if (s_voice_prompt_littlefs_mount_attempted) {
return s_voice_prompt_littlefs_mount_result;
}
esp_vfs_littlefs_conf_t conf = {
.base_path = VOICE_PROMPT_LITTLEFS_BASE_PATH,
.partition_label = VOICE_PROMPT_LITTLEFS_PARTITION,
.partition = NULL,
.format_if_mount_failed = false,
.dont_mount = false,
};
esp_err_t err = esp_vfs_littlefs_register(&conf);
if (err == ESP_ERR_INVALID_STATE &&
esp_littlefs_mounted(VOICE_PROMPT_LITTLEFS_PARTITION)) {
err = ESP_OK;
}
if (err == ESP_OK) {
size_t total_bytes = 0;
size_t used_bytes = 0;
esp_err_t info_err = esp_littlefs_info(VOICE_PROMPT_LITTLEFS_PARTITION,
&total_bytes,
&used_bytes);
if (info_err == ESP_OK) {
ESP_LOGI(VOICE_TAG,
"LittleFS mounted at %s, used=%u total=%u",
VOICE_PROMPT_LITTLEFS_BASE_PATH,
(unsigned)used_bytes,
(unsigned)total_bytes);
}
} else {
ESP_LOGW(VOICE_TAG,
"Mount LittleFS failed: %s",
esp_err_to_name(err));
}
s_voice_prompt_littlefs_mount_attempted = true;
s_voice_prompt_littlefs_mount_result = err;
return err;
}
esp_err_t voice_prompt_play_file(const char *path)
{
(void)path;
return ESP_ERR_NOT_SUPPORTED;
if (path == NULL || path[0] == '\0') {
return ESP_ERR_INVALID_ARG;
}
esp_err_t mount_err = voice_prompt_mount_littlefs();
if (mount_err != ESP_OK) {
return mount_err;
}
char resolved_path[160] = {0};
if (path[0] == '/') {
int written = snprintf(resolved_path, sizeof(resolved_path), "%s", path);
if (written < 0 || written >= (int)sizeof(resolved_path)) {
return ESP_ERR_INVALID_ARG;
}
} else {
int written = snprintf(resolved_path,
sizeof(resolved_path),
"%s/%s",
VOICE_PROMPT_DEFAULT_BASE_PATH,
path);
if (written < 0 || written >= (int)sizeof(resolved_path)) {
return ESP_ERR_INVALID_ARG;
}
}
FILE *file = fopen(resolved_path, "rb");
if (file == NULL) {
ESP_LOGW(VOICE_TAG, "Open prompt failed: %s", resolved_path);
return ESP_ERR_NOT_FOUND;
}
esp_err_t err = multimodal_audio_begin_downstream_response();
if (err != ESP_OK) {
ESP_LOGW(VOICE_TAG, "Begin prompt playback failed: %s", esp_err_to_name(err));
fclose(file);
return err;
}
uint8_t *packet = (uint8_t *)heap_caps_malloc(VOICE_PROMPT_MAX_OPUS_PACKET_BYTES,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (packet == NULL) {
ESP_LOGW(VOICE_TAG,
"Allocate prompt opus packet buffer in PSRAM failed: %u bytes",
(unsigned)VOICE_PROMPT_MAX_OPUS_PACKET_BYTES);
fclose(file);
(void)multimodal_audio_finish_downstream_response();
return ESP_ERR_NO_MEM;
}
uint32_t packet_count = 0;
size_t total_bytes = 0;
while (true) {
uint8_t header[VOICE_PROMPT_FRAME_HEADER_BYTES] = {0};
size_t header_read = fread(header, 1, sizeof(header), file);
if (header_read == 0U) {
break;
}
if (header_read != sizeof(header)) {
ESP_LOGW(VOICE_TAG, "Truncated prompt frame header: %s", resolved_path);
err = ESP_ERR_INVALID_SIZE;
break;
}
uint32_t packet_len = ((uint32_t)header[0] << 24) |
((uint32_t)header[1] << 16) |
((uint32_t)header[2] << 8) |
(uint32_t)header[3];
if (packet_len == 0U || packet_len > VOICE_PROMPT_MAX_OPUS_PACKET_BYTES) {
ESP_LOGW(VOICE_TAG,
"Invalid prompt opus packet size: %u in %s",
(unsigned)packet_len,
resolved_path);
err = ESP_ERR_INVALID_SIZE;
break;
}
size_t payload_read = fread(packet, 1, packet_len, file);
if (payload_read != packet_len) {
ESP_LOGW(VOICE_TAG, "Truncated prompt opus packet: %s", resolved_path);
err = ESP_ERR_INVALID_SIZE;
break;
}
err = multimodal_audio_handle_downstream_opus(packet, packet_len);
if (err != ESP_OK) {
ESP_LOGW(VOICE_TAG,
"Queue prompt opus packet failed: %s",
esp_err_to_name(err));
break;
}
packet_count++;
total_bytes += packet_len;
}
free(packet);
fclose(file);
esp_err_t finish_err = multimodal_audio_finish_downstream_response();
if (err == ESP_OK) {
err = finish_err;
}
if (err == ESP_OK) {
uint32_t drain_ms = packet_count * VOICE_PROMPT_OPUS_FRAME_MS +
VOICE_PROMPT_DRAIN_MARGIN_MS;
vTaskDelay(pdMS_TO_TICKS(drain_ms));
ESP_LOGI(VOICE_TAG,
"Prompt queued: %s packets=%u bytes=%u",
resolved_path,
(unsigned)packet_count,
(unsigned)total_bytes);
}
return err;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -16,7 +16,7 @@ CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_TQ_TASK_MONITOR_ENABLE=y
# CONFIG_TQ_BOOT_SEQUENCE_DRY_RUN is not set
CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS=3000
CONFIG_TQ_BOOT_SOUND_FILE=""
CONFIG_TQ_BOOT_SOUND_FILE="boot.opus"
CONFIG_TQ_NETWORK_TASK_CORE=0
CONFIG_TQ_LOCAL_TASK_CORE=1
CONFIG_TQ_TASK_MONITOR_INTERVAL_MS=10000