feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
+2
-1
@@ -60,6 +60,7 @@ volc_hal_display_t volc_hal_display_create(volc_hal_display_config_t* config);
|
||||
* @return volc_hal_display_t Display instance pointer
|
||||
*/
|
||||
int volc_hal_display_set_brightness(volc_hal_display_t display, int brightness);
|
||||
int volc_hal_display_get_brightness(volc_hal_display_t display);
|
||||
|
||||
/**
|
||||
* @brief Destroy a Display instance
|
||||
@@ -82,4 +83,4 @@ int volc_hal_display_set_content(volc_hal_display_t display, volc_hal_display_ob
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -48,6 +48,10 @@ int volc_hal_set_audio_player_volume(volc_hal_player_t player, int volume);
|
||||
* @return the volume
|
||||
*/
|
||||
int volc_hal_get_audio_player_volume(volc_hal_player_t player);
|
||||
// Board codec exists while idle; cloud volume must not depend on a temporary
|
||||
// conversation player, which is created and destroyed for each call.
|
||||
int volc_hal_set_device_volume(int volume);
|
||||
int volc_hal_get_device_volume(void);
|
||||
|
||||
/**
|
||||
* @brief Destroy a player instance
|
||||
@@ -90,6 +94,10 @@ int volc_hal_player_play_data(volc_hal_player_t player, const void* data, int le
|
||||
void volc_hal_player_discard_pending(volc_hal_player_t player);
|
||||
/* Allow a fully received short response to start without filling the prebuffer. */
|
||||
void volc_hal_player_finish_segment(volc_hal_player_t player);
|
||||
/* Conversation task only, while it owns a live player. Includes feeder tail. */
|
||||
bool volc_hal_player_voice_quiet(volc_hal_player_t player, unsigned quiet_ms);
|
||||
/* Set before start. NULL leaves non-call audio unrestricted. */
|
||||
void volc_hal_player_set_authorization_gate(volc_hal_player_t player, bool (*allowed)(void));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/* Generated by tools/generate_call_status_font.py; do not hand edit.
|
||||
* Source SHA256: abfffdf0cd809cf2e3cd9425988b8945786fb5166bf4c5e91d2b6e1fb3839f74
|
||||
* 2x nearest-neighbor glyphs; packed 1bpp, GFX 1/256-pixel advance.
|
||||
* The original source font's leading notice is preserved below.
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 1
|
||||
* Opts: --bpp 1 --size 16 --no-compress --stride 1 --align 1 --font 方正兰亭纤黑.TTF --range 32-126,19968-40869,12288-12544,65280-65312 --format lvgl -o zh_font.c
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LVGL_VERSION_MAJOR
|
||||
#error "Build this font in its own GFX-only translation unit"
|
||||
#endif
|
||||
#include "widget/gfx_font_lvgl.h"
|
||||
|
||||
#if LVGL_VERSION_MAJOR != 8 || LV_FONT_FMT_TXT_LARGE != 1
|
||||
#error "Unexpected GFX font compatibility ABI"
|
||||
#endif
|
||||
_Static_assert(sizeof(lv_font_fmt_txt_glyph_dsc_t) == 16, "GFX LARGE glyph ABI required");
|
||||
|
||||
static const uint8_t status_bitmap[] = {
|
||||
/* U+003F ? */
|
||||
15,240,15,240,48,12,48,12,192,15,192,15,192,3,192,3,0,15,0,15,0,12,0,12,
|
||||
0,240,0,240,3,192,3,192,3,0,3,0,3,0,3,0,0,0,0,0,0,0,0,0,
|
||||
3,0,3,0,
|
||||
/* U+4E2D 中 */
|
||||
0,12,0,0,3,0,0,0,192,0,0,48,0,0,12,0,0,3,0,15,255,255,255,255,
|
||||
255,255,192,12,0,240,3,0,60,0,192,15,0,48,3,192,12,0,240,3,0,60,0,192,
|
||||
15,0,48,3,255,255,255,255,255,255,252,0,192,15,0,48,3,0,12,0,0,3,0,0,
|
||||
0,192,0,0,48,0,0,12,0,0,3,0,0,0,192,0,0,48,0,0,12,0,0,3,
|
||||
0,0,
|
||||
/* U+5728 在 */
|
||||
0,12,0,0,0,48,0,0,3,0,0,0,12,0,0,63,255,255,252,255,255,255,240,3,
|
||||
0,0,0,12,0,0,0,192,48,0,3,0,192,0,48,3,0,0,192,12,0,3,0,48,
|
||||
0,12,0,192,0,243,255,255,3,207,255,252,51,0,48,0,204,0,192,0,48,3,0,0,
|
||||
192,12,0,3,0,48,0,12,0,192,0,48,3,0,0,192,12,0,3,0,48,0,12,0,
|
||||
192,0,63,255,255,192,255,255,255,3,0,0,0,12,0,0,0,
|
||||
/* U+5931 失 */
|
||||
0,3,0,0,0,12,0,0,48,48,0,0,192,192,0,3,3,0,0,12,12,0,0,255,
|
||||
255,255,3,255,255,252,60,3,0,0,240,12,0,3,0,48,0,12,0,192,0,0,3,0,
|
||||
0,0,12,0,15,255,255,255,255,255,255,255,0,3,0,0,0,12,0,0,0,204,0,0,
|
||||
3,48,0,0,12,192,0,0,51,0,0,3,3,0,0,12,12,0,3,192,15,0,15,0,
|
||||
60,3,192,0,15,207,0,0,63,192,0,0,3,0,0,0,0,
|
||||
/* U+5F85 待 */
|
||||
0,0,48,0,0,0,192,0,48,3,0,0,192,12,0,15,0,48,0,60,0,192,0,192,
|
||||
255,255,3,3,255,252,48,0,48,0,192,0,192,12,60,3,0,48,240,12,0,3,63,255,
|
||||
252,12,255,255,240,192,0,0,3,0,0,0,60,0,3,0,240,0,12,12,195,255,255,243,
|
||||
15,255,255,12,0,3,0,48,0,12,0,192,192,48,3,3,0,192,12,3,3,0,48,12,
|
||||
12,0,192,48,48,3,0,192,192,12,0,3,0,48,0,12,0,192,15,240,3,0,63,192,
|
||||
/* U+63A5 接 */
|
||||
12,0,48,0,192,3,0,12,0,48,0,192,3,0,12,63,255,240,195,255,255,255,12,3,
|
||||
207,240,192,60,12,3,3,0,192,48,48,12,255,255,240,207,255,255,12,0,0,0,192,0,
|
||||
0,63,3,0,3,240,48,0,204,255,255,252,207,255,255,12,12,3,0,192,192,48,12,12,
|
||||
3,0,192,192,48,12,15,12,0,192,240,192,12,0,252,0,192,15,192,12,15,195,192,192,
|
||||
252,60,240,240,0,63,15,0,3,
|
||||
/* U+65AD 断 */
|
||||
192,192,0,12,12,0,0,192,192,255,252,12,15,255,204,204,192,12,204,204,0,204,204,192,
|
||||
12,204,204,0,192,192,192,12,12,12,0,192,192,192,12,12,12,0,207,252,255,252,255,207,
|
||||
255,195,192,192,204,60,12,12,195,240,192,204,63,12,12,204,204,192,204,204,204,12,240,192,
|
||||
192,207,12,12,12,192,192,192,204,12,12,12,192,3,0,204,0,48,12,255,255,0,207,255,
|
||||
240,12,0,15,0,192,0,240,12,
|
||||
/* U+673A 机 */
|
||||
12,0,0,0,48,0,0,0,192,63,240,3,0,255,192,12,3,3,0,48,12,12,15,252,
|
||||
48,48,63,240,192,192,12,3,3,0,48,12,12,0,192,48,48,3,0,192,192,63,3,3,
|
||||
0,252,12,12,3,204,48,48,15,48,192,192,204,3,3,3,48,12,12,12,192,48,48,51,
|
||||
0,192,192,12,3,3,0,48,12,12,0,192,192,48,195,3,0,195,12,12,3,12,48,48,
|
||||
12,48,195,0,48,195,12,0,195,12,192,3,240,51,0,15,192,
|
||||
/* U+675F 束 */
|
||||
0,3,0,0,0,12,0,15,255,255,255,255,255,255,255,0,3,0,0,0,12,0,0,0,
|
||||
48,0,0,0,192,0,15,255,255,192,63,255,255,3,0,48,12,12,0,192,48,48,3,0,
|
||||
192,192,12,3,3,0,48,12,12,0,192,48,15,255,255,192,63,255,255,0,0,252,0,0,
|
||||
3,240,0,0,51,48,0,0,204,192,0,12,48,192,0,48,195,0,3,3,3,192,12,12,
|
||||
15,15,192,48,3,255,0,192,15,0,3,0,0,0,12,0,0,
|
||||
/* U+6B63 正 */
|
||||
63,255,255,252,255,255,255,240,0,12,0,0,0,48,0,0,0,192,0,0,3,0,0,0,
|
||||
12,0,0,0,48,0,3,0,192,0,12,3,0,0,48,12,0,0,192,48,0,3,0,255,
|
||||
240,12,3,255,192,48,12,0,0,192,48,0,3,0,192,0,12,3,0,0,48,12,0,0,
|
||||
192,48,0,3,0,192,0,12,3,0,0,48,12,0,0,192,48,0,255,255,255,255,255,255,
|
||||
255,240,
|
||||
/* U+7ED3 结 */
|
||||
3,0,12,0,12,0,48,0,48,0,192,0,192,3,0,12,15,255,252,48,63,255,240,192,
|
||||
0,192,3,0,3,0,48,192,12,0,195,0,48,15,252,0,192,63,240,3,0,3,15,255,
|
||||
240,12,63,255,192,192,0,0,3,0,0,0,12,0,0,0,48,0,0,3,60,63,255,12,
|
||||
240,255,252,60,3,0,48,240,12,0,192,0,48,3,0,0,192,12,3,243,0,48,15,204,
|
||||
0,195,192,63,255,15,0,255,252,0,3,0,48,0,12,0,192,
|
||||
/* U+8BDD 话 */
|
||||
48,0,0,0,192,0,0,3,3,255,255,12,15,255,252,12,0,48,0,48,0,192,0,0,
|
||||
3,0,0,0,12,0,0,255,255,252,3,255,255,255,192,3,0,63,0,12,0,12,0,48,
|
||||
0,48,0,192,0,192,3,0,3,0,12,0,12,15,255,240,48,63,255,192,192,192,3,3,
|
||||
3,0,12,12,12,0,48,48,48,0,192,192,192,3,3,3,0,12,12,204,0,48,51,48,
|
||||
0,192,240,255,255,3,195,255,252,0,12,0,48,0,48,0,192,
|
||||
/* U+8D25 败 */
|
||||
0,0,12,0,0,0,12,0,63,252,12,0,63,252,12,0,48,12,48,0,48,12,48,0,
|
||||
51,12,63,255,51,12,63,255,51,12,48,48,51,12,48,48,51,12,240,48,51,12,240,48,
|
||||
51,15,48,48,51,15,48,48,51,12,48,48,51,12,48,48,51,12,48,192,51,12,48,192,
|
||||
51,12,12,192,51,12,12,192,51,12,12,192,51,12,12,192,12,192,3,0,12,192,3,0,
|
||||
12,60,12,192,12,60,12,192,60,0,48,48,60,0,48,48,240,3,192,15,240,3,192,15,
|
||||
0,3,0,0,0,3,0,0,
|
||||
/* U+8FDE 连 */
|
||||
48,0,192,0,192,3,0,0,192,12,0,3,0,48,0,12,63,255,252,48,255,255,240,0,
|
||||
48,0,0,0,192,0,0,3,12,0,0,12,48,0,0,192,192,0,3,3,0,252,12,12,
|
||||
3,240,48,48,0,195,255,255,195,15,255,255,12,0,12,0,48,0,48,0,192,0,192,3,
|
||||
0,3,0,12,255,255,252,51,255,255,240,192,0,192,3,0,3,0,12,0,12,0,48,0,
|
||||
48,3,60,0,0,12,240,0,0,192,255,255,255,3,255,255,240,
|
||||
/* U+901A 通 */
|
||||
60,63,255,240,240,255,255,192,240,48,12,3,192,192,48,3,0,255,0,12,3,252,0,3,
|
||||
255,255,0,15,255,252,252,48,48,51,240,192,192,192,195,255,255,3,15,255,252,12,48,48,
|
||||
48,48,192,192,192,195,3,3,3,12,12,12,12,63,255,240,48,255,255,192,195,3,3,3,
|
||||
12,12,12,12,48,48,48,48,192,192,195,243,0,63,15,204,0,252,192,255,255,255,3,255,
|
||||
255,240,
|
||||
};
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t status_glyphs[] = {
|
||||
{0}, /* glyph zero is reserved */
|
||||
{0,4960,16,26,2,-2}, /* ? */
|
||||
{52,8192,26,30,2,-4}, /* 中 */
|
||||
{150,8192,30,30,0,-4}, /* 在 */
|
||||
{263,8192,30,30,2,-4}, /* 失 */
|
||||
{376,8192,30,32,2,-4}, /* 待 */
|
||||
{496,8192,28,30,2,-4}, /* 接 */
|
||||
{601,8192,28,30,2,-4}, /* 断 */
|
||||
{706,8192,30,30,2,-4}, /* 机 */
|
||||
{819,8192,30,30,0,-4}, /* 束 */
|
||||
{932,8192,30,26,0,-2}, /* 正 */
|
||||
{1030,8192,30,30,0,-4}, /* 结 */
|
||||
{1143,8192,30,30,0,-4}, /* 话 */
|
||||
{1256,8192,32,32,0,-6}, /* 败 */
|
||||
{1384,8192,30,30,0,-4}, /* 连 */
|
||||
{1497,8192,30,26,0,-2}, /* 通 */
|
||||
};
|
||||
|
||||
#define STATUS_CMAP(cp, id) { .range_start=(cp), .range_length=1, .glyph_id_start=(id), .unicode_list=NULL, .glyph_id_ofs_list=NULL, .list_length=0, .type=LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY }
|
||||
static const lv_font_fmt_txt_cmap_t status_cmaps[] = {
|
||||
STATUS_CMAP(0x003F, 1),
|
||||
STATUS_CMAP(0x4E2D, 2),
|
||||
STATUS_CMAP(0x5728, 3),
|
||||
STATUS_CMAP(0x5931, 4),
|
||||
STATUS_CMAP(0x5F85, 5),
|
||||
STATUS_CMAP(0x63A5, 6),
|
||||
STATUS_CMAP(0x65AD, 7),
|
||||
STATUS_CMAP(0x673A, 8),
|
||||
STATUS_CMAP(0x675F, 9),
|
||||
STATUS_CMAP(0x6B63, 10),
|
||||
STATUS_CMAP(0x7ED3, 11),
|
||||
STATUS_CMAP(0x8BDD, 12),
|
||||
STATUS_CMAP(0x8D25, 13),
|
||||
STATUS_CMAP(0x8FDE, 14),
|
||||
STATUS_CMAP(0x901A, 15),
|
||||
};
|
||||
#undef STATUS_CMAP
|
||||
|
||||
static const lv_font_fmt_txt_dsc_t status_font_dsc = {
|
||||
.glyph_bitmap=status_bitmap, .glyph_dsc=status_glyphs, .cmaps=status_cmaps,
|
||||
.kern_dsc=NULL, .kern_scale=0, .cmap_num=15, .bpp=1,
|
||||
.kern_classes=0, .bitmap_format=0, .cache=NULL
|
||||
};
|
||||
|
||||
static const lv_font_t status_font = {
|
||||
.get_glyph_dsc=lv_font_get_glyph_dsc_fmt_txt, .get_glyph_bitmap=lv_font_get_bitmap_fmt_txt,
|
||||
.line_height=38, .base_line=10, .subpx=LV_FONT_SUBPX_NONE, .static_bitmap=1,
|
||||
.underline_position=-6, .underline_thickness=2, .dsc=&status_font_dsc, .fallback=NULL, .user_data=NULL
|
||||
};
|
||||
|
||||
const void *echoear_call_status_font(void) { return &status_font; }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/* Generated by tools/generate_call_status_font.py; do not hand edit.
|
||||
* Source SHA256: abfffdf0cd809cf2e3cd9425988b8945786fb5166bf4c5e91d2b6e1fb3839f74
|
||||
* 2x nearest-neighbor glyphs; packed 1bpp, GFX 1/256-pixel advance.
|
||||
* The original source font's leading notice is preserved below.
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/* GFX compatibility font; deliberately exposes no LVGL types. */
|
||||
const void *echoear_call_status_font(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+49
-55
@@ -2,6 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdatomic.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "volc_hal.h"
|
||||
#include "volc_hal_capture.h"
|
||||
@@ -39,8 +43,10 @@ typedef struct {
|
||||
volc_hal_capture_data_cb_t data_cb; // Data callback function
|
||||
volc_hal_audio_wakeup_cb_t audio_wakeup_cb; // audio wakeup cb
|
||||
void* user_data; // User data pointer
|
||||
volc_osal_tid_t capture_thread;
|
||||
volatile bool is_started;
|
||||
TaskHandle_t capture_thread;
|
||||
SemaphoreHandle_t capture_stopped;
|
||||
uint8_t *read_buf;
|
||||
atomic_bool is_started;
|
||||
} volc_hal_capture_impl_t;
|
||||
|
||||
extern basic_board_periph_t global_periph;
|
||||
@@ -61,13 +67,12 @@ static void __volc_audio_capture_task(void *arg)
|
||||
volc_hal_capture_impl_t *impl = (volc_hal_capture_impl_t *)arg;
|
||||
LOGI("capture task started...");
|
||||
int read_size = __volc_capture_get_default_read_size(impl);
|
||||
uint8_t *read_buf = volc_osal_malloc(read_size);
|
||||
// mem_assert(read_buf);
|
||||
uint8_t *read_buf = impl->read_buf;
|
||||
|
||||
while(impl->is_started) {
|
||||
int ret = audio_recorder_read_data(read_buf, read_size);
|
||||
|
||||
if (ret > 0 && impl->data_cb && impl->audio_capture_config.state == CAPTURE) {
|
||||
if (impl->is_started && ret > 0 && impl->data_cb && impl->audio_capture_config.state == CAPTURE) {
|
||||
#ifdef CONFIG_VOLC_AUDIO_G711A
|
||||
volc_hal_frame_info_t frame_info = {
|
||||
.data_type = VOLC_AUDIO_DATA_TYPE_G711A,
|
||||
@@ -83,13 +88,10 @@ static void __volc_audio_capture_task(void *arg)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// audio_recorder_pause();
|
||||
if (impl->capture_thread) {
|
||||
volc_osal_thread_destroy(impl->capture_thread);
|
||||
impl->capture_thread = NULL;
|
||||
}
|
||||
volc_osal_free(read_buf);
|
||||
volc_osal_thread_exit(NULL);
|
||||
// Stop owns deletion with the original WithCaps handle. No old reader may
|
||||
// survive into the next recorder, or erase a newly created task's handle.
|
||||
xSemaphoreGive(impl->capture_stopped);
|
||||
for (;;) vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
volc_hal_capture_t volc_hal_capture_create(volc_hal_capture_config_t *config)
|
||||
@@ -100,6 +102,9 @@ volc_hal_capture_t volc_hal_capture_create(volc_hal_capture_config_t *config)
|
||||
return NULL;
|
||||
}
|
||||
volc_hal_capture_impl_t *capture = (volc_hal_capture_impl_t *)volc_osal_calloc(1, sizeof(volc_hal_capture_impl_t));
|
||||
if (!capture) return NULL;
|
||||
capture->capture_stopped = xSemaphoreCreateBinary();
|
||||
if (!capture->capture_stopped) { volc_osal_free(capture); return NULL; }
|
||||
// config
|
||||
capture->is_started = false;
|
||||
capture->media_type = config->media_type;
|
||||
@@ -132,53 +137,37 @@ volc_hal_capture_t volc_hal_capture_create(volc_hal_capture_config_t *config)
|
||||
return (volc_hal_capture_t)capture;
|
||||
}
|
||||
|
||||
static void __start_audio_capture(volc_hal_capture_impl_t *capture)
|
||||
static int __start_audio(volc_hal_capture_impl_t *capture, bool wakeup)
|
||||
{
|
||||
// audio_pipeline_unlink(impl->audio_capture_config.audio_pipeline);
|
||||
av_processor_afe_config_t afe_config = DEFAULT_AV_PROCESSOR_AFE_CONFIG();
|
||||
afe_config.ai_mode_wakeup = false;
|
||||
afe_config.ai_mode_wakeup = wakeup;
|
||||
audio_recorder_config_t recorder_config = DEFAULT_AUDIO_RECORDER_CONFIG();
|
||||
memcpy((void *)&recorder_config.encoder_cfg, &capture->audio_capture_config.recorder_cfg, sizeof(av_processor_encoder_config_t));
|
||||
memcpy(&recorder_config.afe_config, &afe_config, sizeof(av_processor_afe_config_t));
|
||||
recorder_config.recorder_event_cb = capture->audio_wakeup_cb;
|
||||
recorder_config.recorder_ctx = NULL;
|
||||
audio_recorder_open(&recorder_config);
|
||||
|
||||
volc_osal_thread_param_t param = {0};
|
||||
snprintf(param.name, sizeof(param.name), "%s", "volc_capture_task");
|
||||
param.stack_size = 4 * 1024;
|
||||
param.priority = 5;
|
||||
param.stack_in_ext = 1;
|
||||
capture->read_buf = volc_osal_malloc(__volc_capture_get_default_read_size(capture));
|
||||
if (!capture->read_buf) return -1;
|
||||
if (audio_recorder_open(&recorder_config) != ESP_OK) {
|
||||
volc_osal_free(capture->read_buf); capture->read_buf = NULL;
|
||||
return -1;
|
||||
}
|
||||
capture->is_started = true;
|
||||
capture->audio_capture_config.state = CAPTURE;
|
||||
esp_err_t ret = volc_osal_thread_create(&capture->capture_thread, ¶m, __volc_audio_capture_task, capture);
|
||||
|
||||
LOGI("capture task start... mode: CAPTURE");
|
||||
return;
|
||||
}
|
||||
|
||||
static void __start_audio_wake(volc_hal_capture_impl_t *capture)
|
||||
{
|
||||
av_processor_afe_config_t afe_config = DEFAULT_AV_PROCESSOR_AFE_CONFIG();
|
||||
afe_config.ai_mode_wakeup = true;
|
||||
audio_recorder_config_t recorder_config = DEFAULT_AUDIO_RECORDER_CONFIG();
|
||||
memcpy((void *)&recorder_config.encoder_cfg, &capture->audio_capture_config.recorder_cfg, sizeof(av_processor_encoder_config_t));
|
||||
memcpy(&recorder_config.afe_config, &afe_config, sizeof(av_processor_afe_config_t));
|
||||
recorder_config.recorder_event_cb = capture->audio_wakeup_cb;
|
||||
recorder_config.recorder_ctx = NULL;
|
||||
audio_recorder_open(&recorder_config);
|
||||
|
||||
volc_osal_thread_param_t param = {0};
|
||||
snprintf(param.name, sizeof(param.name), "%s", "volc_capture_task");
|
||||
param.stack_size = 4 * 1024;
|
||||
param.priority = 5;
|
||||
param.stack_in_ext = 1;
|
||||
capture->is_started = true;
|
||||
capture->audio_capture_config.state = WAKEUP;
|
||||
esp_err_t ret = volc_osal_thread_create(&capture->capture_thread, ¶m, __volc_audio_capture_task, capture);
|
||||
|
||||
LOGI("capture task start... mode: WAKEUP");
|
||||
return;
|
||||
capture->audio_capture_config.state = wakeup ? WAKEUP : CAPTURE;
|
||||
// RTC Opus encoding needs the larger stack only during conversation.
|
||||
if (xTaskCreatePinnedToCoreWithCaps(__volc_audio_capture_task, "volc_capture_task",
|
||||
wakeup ? 4*1024 : 48*1024, capture, 5, &capture->capture_thread,
|
||||
1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) != pdPASS) {
|
||||
capture->is_started = false;
|
||||
capture->capture_thread = NULL;
|
||||
audio_recorder_close();
|
||||
capture->audio_capture_config.state = INITED;
|
||||
volc_osal_free(capture->read_buf); capture->read_buf = NULL;
|
||||
return -1;
|
||||
}
|
||||
LOGI("capture task start... mode: %s", wakeup ? "WAKEUP" : "CAPTURE");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int volc_hal_capture_start(volc_hal_capture_t capture,volc_hal_audio_capture_mode_e mode)
|
||||
@@ -191,13 +180,11 @@ int volc_hal_capture_start(volc_hal_capture_t capture,volc_hal_audio_capture_mod
|
||||
switch (mode)
|
||||
{
|
||||
case VOLC_AUDIO_MODE_WAKEUP:
|
||||
__start_audio_wake(impl);
|
||||
break;
|
||||
return __start_audio(impl, true);
|
||||
case VOLC_AUDIO_MODE_CAPTURE:
|
||||
__start_audio_capture(impl);
|
||||
break;
|
||||
return __start_audio(impl, false);
|
||||
default:
|
||||
break;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -217,6 +204,12 @@ int volc_hal_capture_stop(volc_hal_capture_t capture)
|
||||
// audio_recorder_pause();
|
||||
// audio_recorder_resume();
|
||||
audio_recorder_close();
|
||||
if (impl->capture_thread) {
|
||||
xSemaphoreTake(impl->capture_stopped, portMAX_DELAY);
|
||||
vTaskDeleteWithCaps(impl->capture_thread);
|
||||
impl->capture_thread = NULL;
|
||||
}
|
||||
volc_osal_free(impl->read_buf); impl->read_buf = NULL;
|
||||
impl->audio_capture_config.state = INITED;
|
||||
break;
|
||||
case VOLC_MEDIA_TYPE_VIDEO:
|
||||
@@ -252,6 +245,7 @@ void volc_hal_capture_destroy(volc_hal_capture_t capture)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
vSemaphoreDelete(impl->capture_stopped);
|
||||
volc_osal_free(impl);
|
||||
}
|
||||
|
||||
|
||||
+83
-30
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "volc_hal.h"
|
||||
#include "volc_hal_display.h"
|
||||
#include "volc_lvgl_source.h"
|
||||
#include "echoear_call_status_font.h"
|
||||
#include "hw_init.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_mmap_assets.h"
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <stddef.h>
|
||||
#include "volc_osal.h"
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include "basic_board.h"
|
||||
|
||||
#include "mmap_generate_eaf.h"
|
||||
@@ -31,6 +32,8 @@ typedef struct volc_hal_display_impl {
|
||||
gfx_obj_t *s_label;
|
||||
gfx_obj_t *s_subtitle;
|
||||
gfx_obj_t *s_setup_hint;
|
||||
atomic_int requested_status;
|
||||
int rendered_status;
|
||||
esp_lcd_panel_io_handle_t display_io_handle;
|
||||
SemaphoreHandle_t flush_done;
|
||||
int s_current_emote;
|
||||
@@ -40,6 +43,10 @@ typedef struct volc_hal_display_impl {
|
||||
static volc_hal_display_impl_t* global_display_impl = NULL;
|
||||
extern basic_board_periph_t global_periph;
|
||||
|
||||
static const char *const call_status_texts[] = {
|
||||
"", "待机", "正在连接", "通话中", "正在结束", "通话结束", "连接失败", "连接中断"
|
||||
};
|
||||
|
||||
static esp_lv_adapter_rotation_t __get_configured_rotation(void)
|
||||
{
|
||||
#if CONFIG_EXAMPLE_DISPLAY_ROTATION_0
|
||||
@@ -157,6 +164,43 @@ static esp_err_t __create_animation(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Keep status outside the eyes, in the safe area of the round panel. GFX
|
||||
* caches the glyph mask; update it only when the status actually changes. */
|
||||
static esp_err_t __create_status_label(void)
|
||||
{
|
||||
gfx_emote_lock(global_display_impl->s_gfx);
|
||||
gfx_obj_t *label = gfx_label_create(global_display_impl->s_gfx);
|
||||
if (!label) {
|
||||
gfx_emote_unlock(global_display_impl->s_gfx);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
global_display_impl->s_label = label;
|
||||
gfx_obj_set_size(label, 240, 42);
|
||||
gfx_obj_align(label, GFX_ALIGN_TOP_MID, 0, 58);
|
||||
gfx_label_set_font(label, (gfx_font_t)echoear_call_status_font());
|
||||
gfx_label_set_text_align(label, GFX_TEXT_ALIGN_CENTER);
|
||||
gfx_label_set_long_mode(label, GFX_LABEL_LONG_CLIP);
|
||||
gfx_label_set_color(label, GFX_COLOR_HEX(0xFFFFFF));
|
||||
gfx_label_set_bg_color(label, GFX_COLOR_HEX(0x000000));
|
||||
gfx_label_set_bg_enable(label, true);
|
||||
gfx_obj_set_visible(label, false);
|
||||
esp_err_t ret = gfx_label_set_text(label, "");
|
||||
gfx_emote_unlock(global_display_impl->s_gfx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __render_status(volc_hal_display_impl_t *display)
|
||||
{
|
||||
int requested = atomic_load(&display->requested_status);
|
||||
if (requested == display->rendered_status) return;
|
||||
gfx_emote_lock(display->s_gfx);
|
||||
if (gfx_label_set_text(display->s_label, call_status_texts[requested]) == ESP_OK) {
|
||||
gfx_obj_set_visible(display->s_label, requested != 0);
|
||||
display->rendered_status = requested;
|
||||
}
|
||||
gfx_emote_unlock(display->s_gfx);
|
||||
}
|
||||
|
||||
static void __emote_task(void *arg)
|
||||
{
|
||||
/* Start animation playback */
|
||||
@@ -166,6 +210,7 @@ static void __emote_task(void *arg)
|
||||
|
||||
/* The display task owns animation playback for the lifetime of the app. */
|
||||
while (1) {
|
||||
__render_status(global_display_impl);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
@@ -267,7 +312,10 @@ volc_hal_display_t volc_hal_display_create(volc_hal_display_config_t* config)
|
||||
* animation instead; application status text explains the setup state.
|
||||
*/
|
||||
global_display_impl->s_current_emote = MMAP_EAF_HAPPY_EAF;
|
||||
atomic_init(&global_display_impl->requested_status, 0);
|
||||
global_display_impl->rendered_status = -1;
|
||||
__create_animation();
|
||||
ESP_ERROR_CHECK(__create_status_label());
|
||||
volc_osal_thread_param_t param = {0};
|
||||
snprintf(param.name, sizeof(param.name), "%s", "volc_capture_task");
|
||||
param.stack_size = 8 * 1024;
|
||||
@@ -282,36 +330,31 @@ volc_hal_display_t volc_hal_display_create(volc_hal_display_config_t* config)
|
||||
return (volc_hal_display_t)global_display_impl;
|
||||
}
|
||||
|
||||
static int display_backlight(volc_hal_display_t display, periph_ledc_handle_t **handle, uint32_t *maximum)
|
||||
{
|
||||
if(!display || esp_board_manager_get_device_handle("lcd_brightness",(void **)handle)!=ESP_OK || !*handle)return -1;
|
||||
dev_ledc_ctrl_config_t *device=NULL;periph_ledc_config_t *config=NULL;
|
||||
if(esp_board_manager_get_device_config("lcd_brightness",(void *)&device)!=ESP_OK || !device)return -1;
|
||||
if(esp_board_manager_get_periph_config(device->ledc_name,(void **)&config)!=ESP_OK || !config)return -1;
|
||||
if(config->duty_resolution<1 || config->duty_resolution>20)return -1;
|
||||
*maximum=(1U<<(uint32_t)config->duty_resolution)-1;
|
||||
return 0;
|
||||
}
|
||||
int volc_hal_display_set_brightness(volc_hal_display_t display, int brightness)
|
||||
{
|
||||
if(display == NULL) return -1;
|
||||
if (brightness > 100) {
|
||||
brightness = 100;
|
||||
}
|
||||
if (brightness < 0) {
|
||||
brightness = 0;
|
||||
}
|
||||
|
||||
periph_ledc_handle_t *ledc_handle = NULL;
|
||||
|
||||
esp_board_manager_get_device_handle("lcd_brightness", (void **)&ledc_handle);
|
||||
|
||||
dev_ledc_ctrl_config_t *dev_ledc_cfg = NULL;
|
||||
esp_err_t config_ret = esp_board_manager_get_device_config("lcd_brightness", (void*)&dev_ledc_cfg);
|
||||
if (config_ret != ESP_OK) {
|
||||
return -1;
|
||||
}
|
||||
// ESP_LOGI(TAG, "dev_ledc_cfg.ledc_name: %s, name: %s, type: %s", dev_ledc_cfg->ledc_name, dev_ledc_cfg->name, dev_ledc_cfg->type);
|
||||
periph_ledc_config_t *ledc_config = NULL;
|
||||
esp_board_manager_get_periph_config(dev_ledc_cfg->ledc_name, (void**)&ledc_config);
|
||||
uint32_t duty = (brightness * ((1 << (uint32_t)ledc_config->duty_resolution) - 1)) / 100;
|
||||
// ESP_LOGI(TAG, "duty_cycle: %" PRIu32 ", speed_mode: %d, channel: %d, duty_resolution: %d", duty, ledc_handle->speed_mode, ledc_handle->channel, ledc_config->duty_resolution);
|
||||
// ESP_BOARD_RETURN_ON_ERROR(ledc_set_duty(ledc_handle->speed_mode, ledc_handle->channel, duty), TAG, "LEDC set duty failed");
|
||||
ledc_set_duty(ledc_handle->speed_mode, ledc_handle->channel, duty);
|
||||
// ESP_BOARD_RETURN_ON_ERROR(ledc_update_duty(ledc_handle->speed_mode, ledc_handle->channel), TAG, "LEDC update duty failed");
|
||||
ledc_update_duty(ledc_handle->speed_mode, ledc_handle->channel);
|
||||
return 0;
|
||||
// return bsp_display_brightness_set(brightness);
|
||||
periph_ledc_handle_t *handle=NULL;uint32_t maximum=0;
|
||||
if(display_backlight(display,&handle,&maximum))return -1;
|
||||
if(brightness<0)brightness=0;if(brightness>100)brightness=100;
|
||||
if(ledc_set_duty(handle->speed_mode,handle->channel,brightness*maximum/100)!=ESP_OK)return -1;
|
||||
return ledc_update_duty(handle->speed_mode,handle->channel)==ESP_OK?0:-1;
|
||||
}
|
||||
int volc_hal_display_get_brightness(volc_hal_display_t display)
|
||||
{
|
||||
periph_ledc_handle_t *handle=NULL;uint32_t maximum=0;
|
||||
if(display_backlight(display,&handle,&maximum))return -1;
|
||||
uint32_t duty=ledc_get_duty(handle->speed_mode,handle->channel);
|
||||
if(duty>maximum)return -1;
|
||||
return (duty*100+maximum/2)/maximum;
|
||||
}
|
||||
|
||||
void volc_hal_display_destroy(volc_hal_display_t display)
|
||||
@@ -339,7 +382,17 @@ int volc_hal_display_set_content(volc_hal_display_t display, volc_hal_display_ob
|
||||
volc_hal_display_impl_t* global_display_impl = (volc_hal_display_impl_t*)(g_hal_context->display_handle);
|
||||
|
||||
if(obj == VOLC_DISPLAY_OBJ_STATUS && type == VOLC_DISPLAY_TEXT){
|
||||
ESP_LOGI("DISPLAY", "Status: %s", (const char *)content);
|
||||
if (!content) return -1;
|
||||
int requested = 0;
|
||||
for (int i = 1; i < sizeof(call_status_texts) / sizeof(call_status_texts[0]); ++i) {
|
||||
if (strcmp(content, call_status_texts[i]) == 0) { requested = i; break; }
|
||||
}
|
||||
if (strcmp(content, "轻触屏幕开始聊天") == 0 ||
|
||||
strcmp(content, "轻触屏幕或说嗨,乐鑫") == 0) requested = 1;
|
||||
/* RTC callbacks never wait on the renderer's full-frame lock. Other
|
||||
* setup text keeps its previous behavior, with no subtitle rendering. */
|
||||
atomic_store(&global_display_impl->requested_status, requested);
|
||||
return 0;
|
||||
}
|
||||
if(obj == VOLC_DISPLAY_OBJ_SUBTITLE && type == VOLC_DISPLAY_TEXT){
|
||||
ESP_LOGI("DISPLAY", "Subtitle: %s", (const char *)content);
|
||||
|
||||
+93
-11
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "volc_hal.h"
|
||||
#include "volc_hal_player.h"
|
||||
@@ -38,17 +39,33 @@ typedef struct {
|
||||
audio_player_config_t audio_player_config;
|
||||
video_player_config_t video_player_config;
|
||||
};
|
||||
volatile bool is_started;
|
||||
atomic_bool is_started;
|
||||
atomic_int feeder_stop_phase;
|
||||
StreamBufferHandle_t pcm_stream;
|
||||
StaticStreamBuffer_t stream_state;
|
||||
uint8_t *stream_storage;
|
||||
SemaphoreHandle_t worker_stopped;
|
||||
volatile bool discard_pending;
|
||||
volatile bool segment_complete;
|
||||
bool (*authorization_gate)(void);
|
||||
atomic_bool write_in_progress;
|
||||
atomic_uint last_voice_write_ms;
|
||||
} volc_player_impl_t;
|
||||
|
||||
extern basic_board_periph_t global_periph;
|
||||
|
||||
static int stop_feeder_once(volc_player_impl_t *impl)
|
||||
{
|
||||
int expected = 0;
|
||||
if (atomic_compare_exchange_strong(&impl->feeder_stop_phase, &expected, 1)) {
|
||||
int ret = audio_feeder_stop();
|
||||
atomic_store(&impl->feeder_stop_phase, 2);
|
||||
return ret;
|
||||
}
|
||||
while (atomic_load(&impl->feeder_stop_phase) == 1) vTaskDelay(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcm_playback_task(void *arg)
|
||||
{
|
||||
volc_player_impl_t *impl = arg;
|
||||
@@ -56,7 +73,22 @@ static void pcm_playback_task(void *arg)
|
||||
bool primed = false;
|
||||
uint64_t first_byte_ms = 0;
|
||||
uint64_t last_feed_ms = 0;
|
||||
bool authorization_closed = false;
|
||||
uint64_t report_ms=volc_osal_get_time_ms();
|
||||
unsigned fed_bytes=0,max_write_ms=0,write_errors=0;
|
||||
while (impl->is_started) {
|
||||
uint64_t tick_ms=volc_osal_get_time_ms();
|
||||
if(tick_ms-report_ms>=1000){
|
||||
LOGI("PLAYBACK stats span_ms=%u fed_ms=%u queued_ms=%u max_write_ms=%u errors=%u primed=%d",(unsigned)(tick_ms-report_ms),fed_bytes/32,(unsigned)xStreamBufferBytesAvailable(impl->pcm_stream)/32,max_write_ms,write_errors,primed);
|
||||
report_ms=tick_ms;fed_bytes=max_write_ms=write_errors=0;
|
||||
}
|
||||
// Runs in the audio task even while the control task is blocked on HTTP.
|
||||
if (authorization_closed || (impl->authorization_gate && !impl->authorization_gate())) {
|
||||
if (!authorization_closed) { stop_feeder_once(impl); authorization_closed = true; }
|
||||
while (xStreamBufferReceive(impl->pcm_stream, pcm, sizeof(pcm), 0)) {}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
if (impl->discard_pending) {
|
||||
while (xStreamBufferReceive(impl->pcm_stream, pcm, sizeof(pcm), 0)) {}
|
||||
impl->discard_pending = false;
|
||||
@@ -80,8 +112,10 @@ static void pcm_playback_task(void *arg)
|
||||
LOGI("PLAYBACK start source=16000Hz mono PCM16 buffered=%u wait_ms=%u complete=%d",
|
||||
(unsigned)available, (unsigned)(volc_osal_get_time_ms() - first_byte_ms), impl->segment_complete);
|
||||
}
|
||||
size_t count = xStreamBufferReceive(impl->pcm_stream, pcm, sizeof(pcm), pdMS_TO_TICKS(30));
|
||||
atomic_store(&impl->write_in_progress, true);
|
||||
size_t count = xStreamBufferReceive(impl->pcm_stream, pcm, sizeof(pcm), 0);
|
||||
if (!count) {
|
||||
atomic_store(&impl->write_in_progress, false);
|
||||
/* An empty ingress queue does not mean the speaker has run dry:
|
||||
* the downstream feeder may still hold 256 ms of PCM. Keep feeding
|
||||
* arriving chunks immediately instead of inserting another prefill
|
||||
@@ -91,13 +125,35 @@ static void pcm_playback_task(void *arg)
|
||||
first_byte_ms = 0;
|
||||
LOGI("PLAYBACK idle after %u ms without PCM", PCM_REPRIME_IDLE_MS);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
if (audio_feeder_feed_data(pcm, count) == ESP_OK) {
|
||||
if (impl->authorization_gate && !impl->authorization_gate()) {
|
||||
atomic_store(&impl->write_in_progress, false);
|
||||
continue;
|
||||
}
|
||||
uint64_t magnitude = 0;
|
||||
for (size_t i = 0; i + 1 < count; i += 2) {
|
||||
int16_t sample;
|
||||
memcpy(&sample, pcm + i, sizeof(sample));
|
||||
magnitude += sample < 0 ? -(int)sample : sample;
|
||||
}
|
||||
bool voiced = count >= 2 && magnitude / (count / 2) >= 64;
|
||||
if (voiced) atomic_store(&impl->last_voice_write_ms, (uint32_t)volc_osal_get_time_ms());
|
||||
// A full 1024-byte chunk represents 32 ms at 16 kHz PCM16. Allow
|
||||
// the sink to consume it without dropping audio at a 20 ms timeout.
|
||||
uint64_t write_ms=volc_osal_get_time_ms();
|
||||
if (audio_feeder_feed_data_timeout(pcm, count, 50) == ESP_OK) {
|
||||
fed_bytes+=count;
|
||||
last_feed_ms = volc_osal_get_time_ms();
|
||||
} else if (impl->is_started) {
|
||||
write_errors++;
|
||||
LOGE("PLAYBACK feeder write failed");
|
||||
}
|
||||
unsigned elapsed_write=(unsigned)(volc_osal_get_time_ms()-write_ms);
|
||||
if (voiced) atomic_store(&impl->last_voice_write_ms, (uint32_t)volc_osal_get_time_ms());
|
||||
atomic_store(&impl->write_in_progress, false);
|
||||
if(elapsed_write>max_write_ms)max_write_ms=elapsed_write;
|
||||
}
|
||||
xSemaphoreGive(impl->worker_stopped);
|
||||
vTaskDelete(NULL);
|
||||
@@ -115,10 +171,7 @@ volc_hal_player_t volc_hal_player_create(volc_hal_player_config_t* config)
|
||||
impl->media_type = config->media_type;
|
||||
switch (impl->media_type) {
|
||||
case VOLC_MEDIA_TYPE_AUDIO:
|
||||
if(global_periph.play_dev){
|
||||
esp_codec_dev_set_out_vol(global_periph.play_dev, DEFAULT_AUDIO_VOL);
|
||||
}
|
||||
impl->audio_player_config.volume = DEFAULT_AUDIO_VOL;
|
||||
impl->audio_player_config.volume = volc_hal_get_device_volume();
|
||||
#if (CONFIG_VOLC_AUDIO_G711A)
|
||||
impl->audio_player_config.feeder_cfg.format = AV_PROCESSOR_FORMAT_ID_G711A;
|
||||
impl->audio_player_config.feeder_cfg.params.g711.audio_info.sample_rate = 8000;
|
||||
@@ -168,14 +221,24 @@ volc_hal_player_t volc_hal_player_create(volc_hal_player_config_t* config)
|
||||
return (volc_hal_player_t)impl;
|
||||
}
|
||||
|
||||
int volc_hal_set_device_volume(int volume){
|
||||
if(volume<0 || volume>100 || !global_periph.play_dev)return -1;
|
||||
return esp_codec_dev_set_out_vol(global_periph.play_dev,volume);
|
||||
}
|
||||
int volc_hal_get_device_volume(void){
|
||||
int volume=-1;
|
||||
if(!global_periph.play_dev || esp_codec_dev_get_out_vol(global_periph.play_dev,&volume)!=ESP_OK)return -1;
|
||||
return volume;
|
||||
}
|
||||
int volc_hal_set_audio_player_volume(volc_hal_player_t player, int volume){
|
||||
volc_player_impl_t* impl = (volc_player_impl_t*)player;
|
||||
if (impl == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if(impl->media_type == VOLC_MEDIA_TYPE_AUDIO){
|
||||
impl->audio_player_config.volume = volume;
|
||||
return esp_codec_dev_set_out_vol(global_periph.play_dev, volume);
|
||||
int result=volc_hal_set_device_volume(volume);
|
||||
if (result==ESP_OK) impl->audio_player_config.volume = volume;
|
||||
return result;
|
||||
} else if(impl->media_type == VOLC_MEDIA_TYPE_VIDEO){
|
||||
LOGE("media type %d not supported set volume", impl->media_type);
|
||||
return -1;
|
||||
@@ -187,7 +250,7 @@ int volc_hal_get_audio_player_volume(volc_hal_player_t player){
|
||||
if (impl == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return impl->audio_player_config.volume;
|
||||
return volc_hal_get_device_volume();
|
||||
}
|
||||
|
||||
void volc_hal_player_destroy(volc_hal_player_t player)
|
||||
@@ -231,6 +294,9 @@ int volc_hal_player_start(volc_hal_player_t player)
|
||||
case VOLC_MEDIA_TYPE_AUDIO:
|
||||
if (audio_feeder_run() != ESP_OK) return -1;
|
||||
xStreamBufferReset(impl->pcm_stream);
|
||||
atomic_store(&impl->feeder_stop_phase, 0);
|
||||
atomic_store(&impl->write_in_progress, false);
|
||||
atomic_store(&impl->last_voice_write_ms, (uint32_t)volc_osal_get_time_ms());
|
||||
impl->is_started = true;
|
||||
if (xTaskCreatePinnedToCore(pcm_playback_task, "pcm_playback", 4096, impl, 5, NULL, 1) != pdPASS) {
|
||||
impl->is_started = false;
|
||||
@@ -260,7 +326,7 @@ int volc_hal_player_stop(volc_hal_player_t player)
|
||||
case VOLC_MEDIA_TYPE_AUDIO:
|
||||
impl->is_started = false;
|
||||
/* Stop the feeder first to release any in-flight blocking write. */
|
||||
int ret = audio_feeder_stop();
|
||||
int ret = stop_feeder_once(impl);
|
||||
xSemaphoreTake(impl->worker_stopped, portMAX_DELAY);
|
||||
return ret;
|
||||
break;
|
||||
@@ -313,3 +379,19 @@ void volc_hal_player_finish_segment(volc_hal_player_t player)
|
||||
volc_player_impl_t *impl = player;
|
||||
if (impl && impl->is_started) impl->segment_complete = true;
|
||||
}
|
||||
|
||||
bool volc_hal_player_voice_quiet(volc_hal_player_t player, unsigned quiet_ms)
|
||||
{
|
||||
volc_player_impl_t *impl = player;
|
||||
if (!impl || !impl->is_started || !impl->pcm_stream) return false;
|
||||
uint32_t now = (uint32_t)volc_osal_get_time_ms();
|
||||
return !atomic_load(&impl->write_in_progress) &&
|
||||
xStreamBufferBytesAvailable(impl->pcm_stream) == 0 &&
|
||||
now - atomic_load(&impl->last_voice_write_ms) >= quiet_ms;
|
||||
}
|
||||
|
||||
void volc_hal_player_set_authorization_gate(volc_hal_player_t player, bool (*allowed)(void))
|
||||
{
|
||||
volc_player_impl_t *impl = player;
|
||||
if (impl && !impl->is_started) impl->authorization_gate = allowed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user