503 lines
18 KiB
C
503 lines
18 KiB
C
#include "multimodal/audio_pipeline.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "audio_codec_ctrl_if.h"
|
|
#include "audio_codec_data_if.h"
|
|
#include "audio_codec_gpio_if.h"
|
|
#include "audio_codec_if.h"
|
|
#include "es8311_codec.h"
|
|
#include "esp_codec_dev.h"
|
|
#include "esp_codec_dev_defaults.h"
|
|
#include "esp_audio_dec.h"
|
|
#include "esp_audio_enc.h"
|
|
#include "esp_afe_aec.h"
|
|
#include "esp_err.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_opus_dec.h"
|
|
#include "esp_opus_enc.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/idf_additions.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/semphr.h"
|
|
#include "freertos/task.h"
|
|
#include "soc/soc_caps.h"
|
|
#include "sdkconfig.h"
|
|
#include "driver/i2s_std.h"
|
|
#include "driver/i2c_master.h"
|
|
|
|
/*
|
|
* Kconfig 兼容默认值:仅在配置项缺失时生效。后面的编译期约束仍强制语音链路使用
|
|
* 16 kHz、双物理时隙和16位 PCM,因此默认值不等于所有板型都可直接运行。
|
|
* These are compatibility fallbacks for missing Kconfig values; compile-time
|
|
* checks below still enforce the audio format required by voice/AEC processing.
|
|
*/
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_ENABLE_CODEC_PATH
|
|
#define CONFIG_MULTIMODAL_AUDIO_ENABLE_CODEC_PATH 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_CODEC_SAMPLE_RATE
|
|
#define CONFIG_MULTIMODAL_AUDIO_CODEC_SAMPLE_RATE 16000
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_CODEC_CHANNELS
|
|
#define CONFIG_MULTIMODAL_AUDIO_CODEC_CHANNELS 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_CODEC_BITS_PER_SAMPLE
|
|
#define CONFIG_MULTIMODAL_AUDIO_CODEC_BITS_PER_SAMPLE 16
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2C_PORT
|
|
#define CONFIG_MULTIMODAL_AUDIO_I2C_PORT 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2C_SCL_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2C_SCL_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2C_SDA_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2C_SDA_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2C_ADDR
|
|
#define CONFIG_MULTIMODAL_AUDIO_I2C_ADDR 0x18
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_PORT
|
|
#define CONFIG_MULTIMODAL_AUDIO_I2S_PORT 0
|
|
#endif
|
|
|
|
/* I2C/I2S GPIO 必须由板级 Kconfig 明确提供,避免错误默认引脚驱动真实硬件。 */
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_MCLK_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2S_MCLK_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_BCLK_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2S_BCLK_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_WS_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2S_WS_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_DOUT_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2S_DOUT_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_DIN_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_I2S_DIN_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_USE_MCLK
|
|
#define CONFIG_MULTIMODAL_AUDIO_USE_MCLK 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_PA_GPIO
|
|
#error "CONFIG_MULTIMODAL_AUDIO_PA_GPIO must be defined in Kconfig"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_DMA_DESC_NUM
|
|
/*
|
|
* I2S DMA 默认4个描述符、每描述符160帧:增加可提高抗调度抖动能力,但会占用更多
|
|
* DMA 内存并增加排队延迟;减少则降低内存占用,但更容易出现采集/播放 underrun。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_I2S_DMA_DESC_NUM 4
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_I2S_DMA_FRAME_NUM
|
|
#define CONFIG_MULTIMODAL_AUDIO_I2S_DMA_FRAME_NUM 160
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_OUT_VOLUME
|
|
/* codec 初始增益属于硬件安全基线;调整前需同时检查削波、啸叫和扬声器功率。 */
|
|
#define CONFIG_MULTIMODAL_AUDIO_OUT_VOLUME 60
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_IN_GAIN_DB
|
|
#define CONFIG_MULTIMODAL_AUDIO_IN_GAIN_DB 10
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_MIC_CHANNEL
|
|
/*
|
|
* 双时隙输入中,麦克风与 AEC 回采必须使用不同槽位;滤波长度增大可覆盖更长回声,
|
|
* 但会增加运算量和工作内存。编译期检查防止两个通道配置为同一槽位。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_MIC_CHANNEL 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_AEC_REFERENCE_CHANNEL
|
|
#define CONFIG_MULTIMODAL_AUDIO_AEC_REFERENCE_CHANNEL 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_AEC_FILTER_LENGTH
|
|
#define CONFIG_MULTIMODAL_AUDIO_AEC_FILTER_LENGTH 4
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_LOG_UPLINK_LEVELS
|
|
#define CONFIG_MULTIMODAL_AUDIO_LOG_UPLINK_LEVELS 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_IDLE_MUTE_MS
|
|
/*
|
|
* 下行播放时序权衡:首次积累 PREBUFFER 后开声;断流超过 REBUFFER_GAP 后重新缓冲,
|
|
* 最大积累量受 MAX_REBUFFER 限制以避免延迟持续增长;正常排空后等待 IDLE_MUTE
|
|
* 再静音以减少爆音,活动响应长时间无数据则按 ACTIVE_STALL 判定卡顿。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_IDLE_MUTE_MS 250
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_ACTIVE_STALL_MUTE_MS
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_ACTIVE_STALL_MUTE_MS 3000
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PREBUFFER_MS
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PREBUFFER_MS 160
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_REBUFFER_GAP_MS
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_REBUFFER_GAP_MS 120
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_MAX_REBUFFER_MS
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_MAX_REBUFFER_MS 640
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_DIAGNOSTICS_ENABLE
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_DIAGNOSTICS_ENABLE 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_TASK_STACK
|
|
/*
|
|
* 下行任务承担 Opus 解码和 codec 写入,栈下限保护最深调用链。队列长度和单包容量
|
|
* 共同决定最坏内存占用;配置值会被归一化到 MIN/CAP 范围,避免过小不可运行或
|
|
* 过大耗尽 PSRAM。修改时应同时评估端到端延迟和网络突发大小。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_TASK_STACK 12288
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_TASK_STACK_MIN_BYTES 8192U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_QUEUE_LEN
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_QUEUE_LEN 1024
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_QUEUE_MIN_LEN 16U
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_QUEUE_CAP_LEN 1024U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_MAX_PACKET_BYTES
|
|
#define CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_MAX_PACKET_BYTES 2048
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_PACKET_MIN_BYTES 256U
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_PACKET_CAP_BYTES 8192U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_UPLINK_TASK_STACK
|
|
/*
|
|
* 上行采集栈包含 I2S、AEC 和 Opus 编码调用链,因此基线大于纯发送任务。发送队列
|
|
* 将网络抖动与实时采集隔离;5ms 入队等待刻意选择“及时丢包”而非阻塞采集。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_UPLINK_TASK_STACK 32768
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_UPLINK_TASK_STACK_MIN_BYTES 32768U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_TASK_STACK
|
|
#define CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_TASK_STACK 12288
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_UPLINK_SEND_TASK_STACK_MIN_BYTES 8192U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_QUEUE_LEN
|
|
#define CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_QUEUE_LEN 24
|
|
#endif
|
|
|
|
#define MULTIMODAL_AUDIO_UPLINK_SEND_QUEUE_MIN_LEN 24U
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_TIMEOUT_MS
|
|
#define CONFIG_MULTIMODAL_AUDIO_UPLINK_SEND_TIMEOUT_MS 5
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_UPLINK_MAX_PACKET_BYTES
|
|
#define CONFIG_MULTIMODAL_AUDIO_UPLINK_MAX_PACKET_BYTES 1024
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_INIT_RETRY_COUNT
|
|
/*
|
|
* 初始化重试前先检查最大连续 DMA/内部内存块,避免在堆碎片严重时构建出半初始化
|
|
* codec。阈值是创建 I2S/codec 控制对象所需的安全水位,不是总空闲内存要求。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_AUDIO_INIT_RETRY_COUNT 1
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_INIT_MIN_LARGEST_DMA
|
|
#define CONFIG_MULTIMODAL_AUDIO_INIT_MIN_LARGEST_DMA 4096
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_INIT_MIN_LARGEST_INTERNAL
|
|
#define CONFIG_MULTIMODAL_AUDIO_INIT_MIN_LARGEST_INTERNAL 6144
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_FRAME_DURATION_MS
|
|
/*
|
|
* 服务端协议要求 60ms raw-Opus 帧。16kbps、低复杂度和关闭 VBR/DTX/FEC 是当前
|
|
* 实时性与 MCU 负载基线;改变任一项需同步验证服务端解码、包容量和网络带宽。
|
|
*/
|
|
#define CONFIG_MULTIMODAL_OPUS_FRAME_DURATION_MS 60
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_BITRATE_KBPS
|
|
#define CONFIG_MULTIMODAL_OPUS_BITRATE_KBPS 16
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_COMPLEXITY
|
|
#define CONFIG_MULTIMODAL_OPUS_COMPLEXITY 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_ENABLE_VBR
|
|
#define CONFIG_MULTIMODAL_OPUS_ENABLE_VBR 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_ENABLE_DTX
|
|
#define CONFIG_MULTIMODAL_OPUS_ENABLE_DTX 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_ENABLE_FEC
|
|
#define CONFIG_MULTIMODAL_OPUS_ENABLE_FEC 0
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_AUDIO_CODEC_SAMPLE_RATE != 16000
|
|
#error "Multimodal voice requires 16 kHz audio"
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_AUDIO_CODEC_CHANNELS != 2
|
|
#error "ES8311 local wake AEC requires two physical codec slots"
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_AUDIO_CODEC_BITS_PER_SAMPLE != 16
|
|
#error "ESP AEC requires signed 16-bit PCM"
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_AUDIO_MIC_CHANNEL == CONFIG_MULTIMODAL_AUDIO_AEC_REFERENCE_CHANNEL
|
|
#error "Microphone and AEC reference must use distinct codec slots"
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_OPUS_FRAME_DURATION_MS != 60
|
|
#error "Multimodal voice requires 60 ms raw-opus frames"
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_OPUS_DEC_SELF_DELIMITED
|
|
#define CONFIG_MULTIMODAL_OPUS_DEC_SELF_DELIMITED 0
|
|
#endif
|
|
|
|
#ifndef CONFIG_MULTIMODAL_AUDIO_TASK_CORE
|
|
/* 音频任务默认固定到本地实时核心;负数表示由调度器决定,需重新验证抖动。 */
|
|
#define CONFIG_MULTIMODAL_AUDIO_TASK_CORE 1
|
|
#endif
|
|
|
|
#if CONFIG_MULTIMODAL_AUDIO_TASK_CORE < 0
|
|
#define MULTIMODAL_AUDIO_CORE_ID tskNO_AFFINITY
|
|
#else
|
|
#define MULTIMODAL_AUDIO_CORE_ID ((BaseType_t)CONFIG_MULTIMODAL_AUDIO_TASK_CORE)
|
|
#endif
|
|
|
|
/*
|
|
* 解码缓冲包含最小工作区和保护余量;TX bounce 缓冲用于处理 codec 短写。
|
|
* AEC 缓冲按16字节对齐以满足 DSP/SIMD 访问要求。
|
|
*/
|
|
#define MULTIMODAL_PCM_SAMPLE_BYTES 2U
|
|
#define MULTIMODAL_MIN_DECODER_PCM_BUFFER_BYTES 2048U
|
|
#define MULTIMODAL_DECODER_BUFFER_GUARD_BYTES 512U
|
|
#define MULTIMODAL_AUDIO_DOWNSTREAM_TX_BOUNCE_BYTES 3200U
|
|
#define MULTIMODAL_AUDIO_DEFAULT_MIC_CHANNEL ((uint8_t)CONFIG_MULTIMODAL_AUDIO_MIC_CHANNEL)
|
|
#define MULTIMODAL_AUDIO_DEFAULT_AEC_REFERENCE_CHANNEL \
|
|
((uint8_t)CONFIG_MULTIMODAL_AUDIO_AEC_REFERENCE_CHANNEL)
|
|
#define MULTIMODAL_AUDIO_AEC_BUFFER_ALIGNMENT 16U
|
|
|
|
static const char *TAG = "multimodal_audio";
|
|
static portMUX_TYPE s_audio_runtime_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
/* 音频上下行共享 codec/I2S 资源,因此所有生命周期状态集中保存在单一上下文中。 */
|
|
typedef struct {
|
|
const audio_codec_data_if_t *data_if;
|
|
const audio_codec_ctrl_if_t *ctrl_if;
|
|
const audio_codec_gpio_if_t *gpio_if;
|
|
const audio_codec_if_t *codec_if;
|
|
esp_codec_dev_handle_t codec_dev;
|
|
esp_codec_dev_sample_info_t fs;
|
|
i2s_chan_handle_t tx_handle;
|
|
i2s_chan_handle_t rx_handle;
|
|
i2c_master_bus_handle_t i2c_bus;
|
|
uint8_t i2c_probe_addr_7bit;
|
|
uint8_t i2c_ctrl_addr_8bit;
|
|
esp_audio_enc_handle_t opus_enc;
|
|
esp_audio_dec_handle_t opus_dec;
|
|
afe_aec_handle_t *afe_aec_handle;
|
|
int opus_enc_in_bytes;
|
|
int opus_enc_out_bytes;
|
|
uint32_t opus_sample_rate;
|
|
uint32_t opus_frame_duration_ms;
|
|
uint32_t opus_bitrate_bps;
|
|
uint8_t *uplink_codec_buf;
|
|
int16_t *uplink_mono_buf;
|
|
int16_t *uplink_aec_clean_buf;
|
|
uint8_t *uplink_opus_buf;
|
|
size_t uplink_codec_read_bytes;
|
|
size_t uplink_aec_frame_bytes;
|
|
size_t uplink_aec_frame_samples;
|
|
size_t uplink_mono_bytes;
|
|
size_t uplink_mono_buf_capacity_bytes;
|
|
size_t uplink_mono_fill_bytes;
|
|
size_t uplink_opus_buf_bytes;
|
|
uint8_t *downstream_pcm_buf;
|
|
size_t downstream_pcm_buf_bytes;
|
|
int16_t *downstream_codec_out_buf;
|
|
size_t downstream_codec_out_buf_bytes;
|
|
uint8_t *downstream_tx_bounce_buf;
|
|
size_t downstream_tx_bounce_buf_bytes;
|
|
uint32_t downstream_playback_generation;
|
|
volatile bool downstream_decoder_reset_pending;
|
|
bool downstream_playback_in_progress;
|
|
bool downstream_output_muted;
|
|
bool downstream_output_mute_state_known;
|
|
bool downstream_output_primed;
|
|
bool downstream_response_active;
|
|
bool downstream_response_end_received;
|
|
TickType_t downstream_last_audio_tick;
|
|
TickType_t downstream_last_response_activity_tick;
|
|
esp_timer_handle_t downstream_idle_mute_timer;
|
|
QueueHandle_t downstream_playback_queue;
|
|
bool downstream_playback_queue_with_caps;
|
|
TaskHandle_t downstream_playback_task;
|
|
bool downstream_playback_starting;
|
|
volatile bool downstream_playback_stop_requested;
|
|
bool downstream_playback_stopping;
|
|
bool downstream_playback_keep_stopping_on_exit;
|
|
bool audio_deinit_in_progress;
|
|
uint8_t mic_capture_channel;
|
|
uint8_t aec_reference_channel;
|
|
QueueHandle_t uplink_send_queue;
|
|
bool uplink_send_queue_with_caps;
|
|
TaskHandle_t uplink_task;
|
|
TaskHandle_t uplink_send_task;
|
|
volatile bool uplink_recording_active;
|
|
volatile bool uplink_task_exit_requested;
|
|
bool uplink_send_in_flight;
|
|
bool uplink_tail_flush_requested;
|
|
bool uplink_stop_incomplete;
|
|
esp_err_t uplink_tail_flush_error;
|
|
bool uplink_first_network_pcm_pending;
|
|
bool uplink_first_network_packet_pending;
|
|
TickType_t uplink_network_start_tick;
|
|
multimodal_audio_uplink_cb_t uplink_cb;
|
|
void *uplink_cb_ctx;
|
|
bool opus_ready;
|
|
bool initialized;
|
|
} multimodal_audio_ctx_t;
|
|
|
|
static multimodal_audio_ctx_t s_audio;
|
|
/* 下行播放以 generation 标识会话,终止事件只唤醒等待同一代的调用方。 */
|
|
static multimodal_audio_downstream_playback_callback_t s_downstream_playback_cb;
|
|
static void *s_downstream_playback_cb_ctx;
|
|
static SemaphoreHandle_t s_downstream_playback_completion_sem;
|
|
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
|
static StaticSemaphore_t s_downstream_playback_completion_sem_storage;
|
|
#endif
|
|
static bool s_downstream_playback_wait_registered;
|
|
static uint32_t s_downstream_playback_wait_generation;
|
|
static bool s_downstream_last_terminal_valid;
|
|
static uint32_t s_downstream_last_terminal_generation;
|
|
static multimodal_audio_downstream_playback_event_type_t
|
|
s_downstream_last_terminal_type;
|
|
#define MULTIMODAL_AUDIO_PCM_OBSERVER_SLOTS 2U
|
|
typedef struct {
|
|
multimodal_audio_pcm_observer_t callback;
|
|
void *ctx;
|
|
uint32_t in_flight;
|
|
uint32_t registration;
|
|
bool enabled;
|
|
} audio_pcm_observer_slot_t;
|
|
static audio_pcm_observer_slot_t s_pcm_observers[MULTIMODAL_AUDIO_PCM_OBSERVER_SLOTS];
|
|
static uint32_t s_pcm_observer_registration;
|
|
static StaticSemaphore_t s_uplink_capture_mutex_storage;
|
|
static SemaphoreHandle_t s_uplink_capture_mutex;
|
|
static StaticSemaphore_t s_uplink_lifecycle_mutex_storage;
|
|
static SemaphoreHandle_t s_uplink_lifecycle_mutex;
|
|
static bool s_uplink_capture_mutex_initializing;
|
|
static portMUX_TYPE s_pcm_observer_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
static bool s_uplink_stack_warned;
|
|
static bool s_uplink_send_stack_warned;
|
|
static bool s_uplink_send_queue_warned;
|
|
static bool s_downstream_playback_stack_warned;
|
|
static bool s_downstream_playback_queue_warned;
|
|
static bool s_downstream_playback_packet_warned;
|
|
static uint32_t s_uplink_overflow_count;
|
|
static TickType_t s_uplink_overflow_last_log_tick;
|
|
static uint32_t s_downstream_overflow_count;
|
|
static TickType_t s_downstream_overflow_last_log_tick;
|
|
static uint32_t s_downstream_queued_packet_total;
|
|
static uint32_t s_downstream_played_packet_total;
|
|
#if CONFIG_MULTIMODAL_AUDIO_DOWNSTREAM_DIAGNOSTICS_ENABLE
|
|
typedef struct {
|
|
TickType_t response_begin_tick;
|
|
TickType_t arrival_last_tick;
|
|
TickType_t first_arrival_tick;
|
|
TickType_t first_playback_tick;
|
|
TickType_t last_playback_tick;
|
|
TickType_t queue_empty_since_tick;
|
|
uint32_t arrival_gap_min_ms;
|
|
uint32_t arrival_gap_max_ms;
|
|
uint64_t arrival_gap_total_ms;
|
|
uint32_t arrival_gap_samples;
|
|
UBaseType_t queue_depth_min;
|
|
UBaseType_t queue_depth_max;
|
|
uint32_t queue_empty_events;
|
|
uint32_t queue_empty_max_ms;
|
|
uint32_t enqueue_failures;
|
|
uint32_t playback_failures;
|
|
bool summary_logged;
|
|
} downstream_diagnostics_t;
|
|
|
|
static downstream_diagnostics_t s_downstream_diagnostics;
|
|
#endif
|
|
static SemaphoreHandle_t s_downstream_playback_queue_mutex;
|
|
static bool s_downstream_playback_queue_mutex_initializing;
|
|
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
|
static StaticSemaphore_t s_downstream_playback_queue_mutex_buf;
|
|
#endif
|
|
|
|
typedef struct {
|
|
uint16_t len;
|
|
uint8_t data[CONFIG_MULTIMODAL_AUDIO_UPLINK_MAX_PACKET_BYTES];
|
|
} uplink_packet_t;
|
|
|
|
typedef struct {
|
|
size_t len;
|
|
uint32_t playback_generation;
|
|
uint8_t *data;
|
|
} downstream_packet_t;
|
|
|
|
/*
|
|
* 实现文件拆分规则:
|
|
* - 下列 *_impl.c 不能单独加入 CMake 或独立编译,只能由本文件按当前顺序包含;
|
|
* - 它们共享本文件上方定义的 static 类型、状态、宏和前置声明;
|
|
* - 拆分仅为了按职责降低阅读长度,不形成新的模块或链接边界;
|
|
* - include 顺序有意为 helpers -> codec -> debug -> runtime,后面的实现可以使用
|
|
* 前面已经定义的内部辅助函数,调整顺序前必须检查可见声明和依赖方向。
|
|
*
|
|
* Included *_impl.c files are not standalone translation units. They share this
|
|
* file's static state and are split only for readability. Their include order is
|
|
* dependency-sensitive and must not be changed without reviewing symbol visibility.
|
|
*/
|
|
|
|
#include "audio_pipeline/audio_pipeline_helpers_impl.c"
|
|
#include "audio_pipeline/audio_pipeline_codec_impl.c"
|
|
#include "audio_pipeline/audio_debug_dump_impl.c"
|
|
#include "audio_pipeline/audio_pipeline_runtime_impl.c"
|