chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(listenai-cmake REQUIRED HINTS $ENV{ARCS_BASE})
project(arcs)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(src)
listenai_add_executable(${PROJECT_NAME})

View File

@@ -0,0 +1 @@
osource "$ARCS_BASE/Kconfig"

View File

@@ -0,0 +1,216 @@
# 人脸识别算法示例
## 功能说明
演示如何使用 ACOMP 人脸识别组件进行人脸检测、对齐、活体检测和特征提取。本示例展示了人脸识别算法的基本使用流程,包括图像输入、算法处理和结果输出。
## 硬件连接
- **调试串口**: CP核的UART0,PA3引脚波特率 921600,用于日志输出
- **调试串口**: AP核的UART1,PA21引脚波特率 921600,用于日志输出
## 示例内容
1. 初始化 ACOMP组件、人脸识别组件
2. 配置活体检测模式和阈值
3. 创建图像输入数据流通道
4. 发送测试图像到人脸识别算法
5. 接收并处理人脸识别结果(人脸框、姿态角、活体得分等)
## 编译运行
```bash
./build.sh -C -DBOARD=arcs_evb
```
## 烧录固件
### 1. 烧录 AP 核固件(Boot Core)
```bash
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x00 ./res/ap.bin
```
### 2. 烧录算法资源到 Flash
```bash
# 人脸检测模型
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x100000 ./res/algo/face_detect_thinker.bin
# 人脸对齐模型
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x170000 ./res/algo/face_align_thinker.bin
# 活体检测模型
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x300000 ./res/algo/face_nir_thinker_lineart_split.bin
# 人脸特征提取模型
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x410000 ./res/algo/face_verify_thinker.bin
```
### 3. 烧录 CP 核固件
```bash
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x800000 ./build/arcs.bin
```
## CP预期输出
```
********Arcs SDK 0.1.1 @ v0.0.1-860-gd85bab268324********
Running on hart-id: 1
CP=======! Hard ID: 1
I/elog [00:00:00.009 1 elog_async] EasyLogger V2.2.99 is initialize success.
I/main [00:00:00.011 1 main] ic_message_init done!
I/acomp_ipc [00:00:02.011 1 rpc_client] [0]acomp remote dev index 1 name acomp.fd
I/acomp_fd [00:00:02.011 1 main] acomp fd init enter
I/acomp_fd [00:00:02.012 1 main] acomp fd dev index 1,name:acomp.fd
I/acomp_fd [00:00:02.012 1 main] acomp fd dev index 1,name:acomp.fd
I/acomp_fd [00:00:02.013 1 main] acomp fd init exit
I/acomp_fd [00:00:02.013 1 main] acomp fd prepare enter
I/acomp_fd [00:00:02.013 1 main] acomp fd prepare:0x2880c4c0, size:128
I/acomp_fd [00:00:02.015 1 main] acomp fd prepare exit
I/acomp_fd [00:00:02.015 1 main] acomp fd start enter
I/acomp_fd [00:00:02.215 1 main] acomp fd start exit
I/acomp_fd [00:00:02.215 1 main] acomp fd add callback enter
I/acomp_fd [00:00:02.216 1 main] acomp fd add callback exit
I/acomp_fd [00:00:02.216 1 main] acomp_fd_live_detect_mode_set enter
I/acomp_fd [00:00:02.217 1 main] acomp_fd_live_detect_mode_set exit
INF:[acomp_stream_channel_create]Acomp stream channel 0x288aa5f4,0x200150d4,'stream.fd_image' created successfully ,vring phy addr=0x288144e0, num_descs=4, buffer_size=153632, direction=M2R, role=Master
I/acomp_fd [00:00:02.227 1 main] acomp_fd_stream_ch_enable chn(stream.fd_image) index(0),desc(0x2880bb58)
I/app_fd [00:00:03.420 1 rpc_client] fd result:2732,
I/app_fd [00:00:03.420 1 rpc_client] detect face cnt: 1
max area result:
index: 0
face_rect: [x:124, y:96, w:84, h:106]
face_score: 0.909907
face_align_cnt:68
head_pose:[yaw:-1.790493, pitch:-4.476233, roll:-2.685740]
face_live_result:[status:1, scores[0]:0.000066, scores[1]:0.999934]
feature_cnt:384
compare_cnt:0
compare_scores:[0.000000, 0.000000]
...
```
## 核心 API
| API | 说明 |
|-----|------|
| `acomp_fd_init()` | 初始化人脸识别组件 |
| `acomp_fd_prepare()` | 准备人脸识别算法资源 |
| `acomp_fd_start()` | 启动人脸识别算法 |
| `acomp_fd_add_callback()` | 注册人脸识别结果回调函数 |
| `acomp_fd_live_detect_mode_set()` | 配置活体检测模式 |
| `acomp_fd_stream_ch_enable()` | 使能图像输入数据流通道 |
| `acomp_fd_stream_tx_buffer_alloc()` | 分配发送缓冲区 |
| `acomp_fd_stream_tx_buffer_submit()` | 提交图像数据到算法 |
## 关键代码
### 初始化人脸识别组件
```c
/* 初始化算法组件 */
acomp_init();
/* 初始化人脸识别 */
acomp_fd_init();
acomp_fd_prepare();
acomp_fd_start();
/* 注册结果回调 */
acomp_fd_add_callback(FD_CB_EVENT_ENGINE_RLT, fd_event_handler, NULL);
/* 配置活体检测 */
const acomp_fd_live_detect_mode_t mode = {
.enable = true,
.score_threshold = {
0.51, // 非活体阈值
0.1, // 活体得分阈值(如果活体得分大于等于这个阈值, 就认为是活体,如果小于这个阈值,就不会进行人脸特征提取和人脸识别)
},
};
acomp_fd_live_detect_mode_set(&mode);
```
### 创建图像输入流
```c
acomp_stream_chn_create_desc_t desc = {
.cname = "stream.fd_image",
.direction = ACOMP_STREAM_DIRECTION_M2R,
.index = 0,
.buffer_size = 320 * 240 * 2 + sizeof(acomp_fd_input_frame_t),
.num_descs = 4,
.kick_policy = 1,
};
acomp_fd_stream_ch_enable(0, &desc);
```
### 发送图像数据
```c
/* 分配发送缓冲区 */
uint8_t *buffer = acomp_fd_stream_tx_buffer_alloc(0, &buf_size, &desc_idx);
/* 填充图像数据 */
acomp_fd_input_frame_t *fd_frame = (acomp_fd_input_frame_t *)buffer;
fd_frame->format = PIX_FMT_YUV422_YUYV_PACKED;
fd_frame->width = 320;
fd_frame->height = 240;
fd_frame->length = 320 * 240 * 2;
memcpy(fd_frame->data, image_data, fd_frame->length);
/* 提交到算法 */
acomp_fd_stream_tx_buffer_submit(0, buffer, buf_size, desc_idx);
```
## 配置说明
### 算法资源地址配置
`prj.conf` 中配置算法模型在 Flash 中的地址:
```
CONFIG_ACOMP_FD_RES_FACE_DETECT_ADDRESS=0x30100000
CONFIG_ACOMP_FD_RES_FACE_DETECT_LENGTH=450936
CONFIG_ACOMP_FD_RES_FACE_ALIGN_ADDRESS=0x30170000
CONFIG_ACOMP_FD_RES_FACE_ALIGN_LENGTH=1588664
CONFIG_ACOMP_FD_RES_FACE_LIVE_ADDRESS=0x30300000
CONFIG_ACOMP_FD_RES_FACE_LIVE_LENGTH=1095288
CONFIG_ACOMP_FD_FACE_VERIFY_ADDRESS=0x30410000
CONFIG_ACOMP_FD_FACE_VERIFY_LENGTH=2953752
```
### PSRAM 堆大小配置
```
CONFIG_PSRAM_HEAP_SIZE=0x100000
```
## 算法内存占用
双麦算法内存占用如下(粗略统计) 详细内存分布见`memap.h`文件
| MCU核心 | 内存类型 | 大小 | 备注 |
|---------|----------|----|-----|
| AP | FLASH | 6546KB | 4个算法资源 + ap固件大小 + cp固件大小 |
| AP | PSRAM | 7900KB | 算法实例用的PSRAM + 4个算法资源拷贝到PSRAM上 + 动态内存 |
| AP | SRAM | 8KB | IPC共享内存 |
| AP | SRAM | 60KB | AP的SRAM |
| AP | SRAM | 384KB | 算法实例大小 |
| AP | SRAM | 28KB | LUNA的*(.sharedmem.*) |
| AP | LUNASRAM | 64KB | LUNA专用的SRAM大小 |
## 注意事项
1. **算法资源烧录**: 必须先烧录 AP 核固件和算法模型资源到 Flash,否则人脸识别算法无法正常运行
2. **内存配置**: 人脸识别算法需要较大的 PSRAM 空间,建议配置 `CONFIG_PSRAM_HEAP_SIZE` 不小于 0x100000
3. **图像格式**: 当前示例使用 YUYV422 格式的图像输入,分辨率为 320x240
4. **活体检测阈值**: 活体得分阈值需要根据实际应用场景调整,阈值越高误识率越低但拒识率越高
5. **双核协作**: 本示例运行在 CP 核(HARTID=1),需要 AP 核(HARTID=0)提供算法支持
6. **回调函数**: 人脸识别结果通过回调函数异步返回,不要在回调中执行耗时操作

View File

@@ -0,0 +1,177 @@
#!/bin/bash
set -e
usage() {
echo "使用方式: $0 [选项]"
echo "选项:"
echo " -S, --Source <path> 指定项目源码路径 (默认为当前脚本所在目录)"
echo " -t, --target <target> 指定构建目标 (如 menuconfig)"
echo " -C, --Clean 清理构建目录"
echo " -B, --build 构建输出目录"
echo " -h, --help 显示此帮助信息"
echo " -r, --release 以 Release 模式构建 (移除 DEBUG_PATH 信息)"
echo " -w, --warnings-as-errors 将警告视为错误"
echo " -D<var>=<value> 传递 CMake 变量 (可多次使用)"
echo ""
echo "示例:"
echo " $0 -S samples/helloworld -DBOARD=arcs_mini 指定板型构建"
echo " $0 -S samples/helloworld -DBOARD=arcs_evb 使用 EVB 板型"
echo " $0 -S samples/helloworld -t menuconfig -DBOARD=arcs_mini 运行 menuconfig"
echo " $0 -C -S samples/helloworld -DBOARD=arcs_mini 清理并重新构建"
echo " $0 -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards 使用自定义板型"
exit 1
}
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_PATH="$SCRIPT_DIR"
TARGET=""
CLEAN=false
OUTPUT="build"
WARNINGS_AS_ERRORS=false
RELEASE=false
ARCS_BASE_DIR_NAME="arcs-sdk"
ARCS_DEV_TOOLS_DIR_NAME="listenai-dev-tools"
ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME="gcc"
ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME="listenai-tools"
find_arcs_base() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_BASE_DIR_NAME"
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found ARCS_BASE: $current_dir/$dir_name"
export ARCS_BASE="$current_dir/$dir_name"
return 0
fi
current_dir=$(dirname "$current_dir")
done
echo "ARCS_BASE not found, Please add ARCS_BASE environment variable or set ARCS_BASE_DIR_NAME to the correct directory."
echo "Current Target ARCS_BASE directory name: $ARCS_BASE_DIR_NAME."
exit 1
}
find_dev_tools() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_DEV_TOOLS_DIR_NAME"
echo "trying to find $dir_name in parent directories..."
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found $dir_name: $current_dir/$dir_name"
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME" ]; then
echo "Found LISTENAI_TOOLS_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
export LISTENAI_TOOLS_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
fi
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME" ]; then
echo "Found NUCLEI_TOOLCHAIN_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
export NUCLEI_TOOLCHAIN_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
fi
return 0
fi
current_dir=$(dirname "$current_dir")
done
}
while [[ $# -gt 0 ]]; do
case $1 in
-S|--Source)
PROJECT_PATH="$2"
shift 2
;;
-B|--build)
OUTPUT="$2"
shift 2
;;
-t|--target)
TARGET="$2"
shift 2
;;
-C|--Clean)
CLEAN=true
shift 1
;;
-w|--warnings-as-errors)
WARNINGS_AS_ERRORS=true
shift 1
;;
-h|--help)
usage
;;
-r|--release)
RELEASE=true
shift 1
;;
-D*)
CMAKE_VARS+=("$1")
shift 1
;;
*)
echo "未知参数: $1"
usage
;;
esac
done
echo "Source: $PROJECT_PATH"
echo "Target: $TARGET"
echo "Clean : $CLEAN"
if [ -z "$LISTENAI_TOOLS_PATH" ] || [ -z "$NUCLEI_TOOLCHAIN_PATH" ]; then
find_dev_tools
fi
if [ -z "${LISTENAI_TOOLS_PATH}" ]; then
export LISTENAI_TOOLS_PATH="请添加 LISTENAI_TOOLS_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 LISTENAI_TOOLS_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
if [ -z "${NUCLEI_TOOLCHAIN_PATH}" ]; then
export NUCLEI_TOOLCHAIN_PATH="请添加 NUCLEI_TOOLCHAIN_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 NUCLEI_TOOLCHAIN_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
############### 下面代码不用修改 ##################
# 构建工具的位置
CMAKE_PROGRAM="$LISTENAI_TOOLS_PATH/cmake/bin/cmake"
NINJA_PROGRAM="$LISTENAI_TOOLS_PATH/ninja/ninja"
# 配置环境变量 ARCS_BASE
if [ -z "$ARCS_BASE" ]; then
find_arcs_base
fi
if [ "$CLEAN" = true ]; then
rm -rf $OUTPUT
fi
# Initialize CMAKE_VARS array if it doesn't exist
declare -a CMAKE_VARS
# Add warnings-as-errors flag if enabled
if [ "$WARNINGS_AS_ERRORS" = true ]; then
CMAKE_VARS+=("-DCMAKE_C_FLAGS=-Werror")
CMAKE_VARS+=("-DCMAKE_CXX_FLAGS=-Werror")
echo "Treating warnings as errors"
fi
# Add release flags if enabled
if [ "$RELEASE" = true ]; then
CMAKE_VARS+=("-DENABLE_DEBUG_PATH=OFF")
echo "Release mode enabled (-DENABLE_DEBUG_PATH=OFF)"
fi
$CMAKE_PROGRAM -B "$OUTPUT" -G Ninja -S "$PROJECT_PATH" \
-DCMAKE_MAKE_PROGRAM="$NINJA_PROGRAM" \
"${CMAKE_VARS[@]}"
if [ -z "$TARGET" ]; then
$CMAKE_PROGRAM --build "$OUTPUT" -j4
else
$CMAKE_PROGRAM --build "$OUTPUT" --target "$TARGET"
fi

View File

@@ -0,0 +1,169 @@
#ifndef __ARCS_MEMAP_HEADER__
#define __ARCS_MEMAP_HEADER__
#define __KB__(x) ((x) * 1024)
#define __MB__(x) ((x) * 1024 * 1024)
#if CONFIG_ARCS_AP_CORE
#define MEM_ILM_BASE 0x00080000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00100000
#define MEM_DLM_SIZE (__KB__(8))
#else
#define MEM_ILM_BASE 0x00280000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00300000
#define MEM_DLM_SIZE (__KB__(8))
#endif
#define MEM_BTRAM_BASE 0x200C0000
#define MEM_BTRAM_SIZE (__KB__(24))
/* FLASH 分配 */
#define MEM_TOTAL_FLASH_SIZE __MB__(16)
#define MEM_AP_FLASH_BASE 0x30000000
#define MEM_AP_FLASH_SIZE __KB__(1024)
#define MEM_CP_FLASH_BASE (0x30000000 + __MB__(8))
#define MEM_CP_FLASH_SIZE __MB__(8)
/* PSRAM 分配 */
#define MEM_AP_PSRAM_BASE 0x28000000
#define MEM_AP_PSRAM_SIZE __MB__(8)
#define MEM_CP_PSRAM_BASE ((MEM_AP_PSRAM_BASE) + (MEM_AP_PSRAM_SIZE))
#define MEM_CP_PSRAM_SIZE __MB__(8)
#if CONFIG_ARCS_AP_CORE
#define MEM_PSRAM_BASE MEM_AP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_AP_PSRAM_SIZE
#else
#define MEM_PSRAM_BASE MEM_CP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_CP_PSRAM_SIZE
#endif
#if CONFIG_ARCS_AP_CORE
#define MEM_FLASH_BASE MEM_AP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_AP_FLASH_SIZE
#else
#define MEM_FLASH_BASE MEM_CP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_CP_FLASH_SIZE
#endif
#define __MEM_TOTAL_SRAM_START__ 0x20000080 //暂留128(0x80)字节给WiFi硬件
#define __MEM_TOTAL_SRAM_END__ 0x20050000
/**
* SRAM 应用侧分配
* --------------------------------------------
* 0x20000000 | CP-WIFI-RAM(这段内存必须在 0x20000000 + 256KB区间)
* | |
* (256K) | CP-SRAM(128K)
* | | AP-SRAM(64KB)
* | |
* | | AP-WIFI-RAM (这段内存必须在 0x20000000 + 256KB区间)
* -------------- |
* 0x20040000 | 接在CP核上的SRAM(总计64K)
* (8K) | IPC共享内存
* (28K) | LUNA CODE & DATA
* -------------- |
* 0x20048800 | 接在AP核上的SRAM(暂时没用)
* | |
* --------------------------------------------
* 0x20050000 | AP算法使用(HardCode)
* | |
* (384K) | ALGO
* | |
* --------------------------------------------
* 0x200B0000 |
* | |
* (24K) | LUNA 核专属内存
* | |
* --------------------------------------------
* 0x200C0000
*/
/* 独立给算法使用 */
#define MEM_APRAM_BASE 0x20050000
#define MEM_APRAM_SIZE (__KB__(384))
/* 注意,修改以下内存时, 必须与CP保持同步修改 */
#define MEM_CP_WIFI_RAM_BASE __MEM_TOTAL_SRAM_START__
#define MEM_CP_WIFI_RAM_SIZE (__KB__(64) - 128) //暂留128(0x80)字节给WiFi硬件
#define MEM_CP_SRAM_BASE ((MEM_CP_WIFI_RAM_BASE) + (MEM_CP_WIFI_RAM_SIZE))
#define MEM_CP_SRAM_SIZE (__KB__(128))
#define MEM_AP_SRAM_BASE ((MEM_CP_SRAM_BASE) + (MEM_CP_SRAM_SIZE))
#define MEM_AP_SRAM_SIZE (__KB__(64))
#define MEM_AP_WIFI_RAM_BASE ((MEM_AP_SRAM_BASE) + (MEM_AP_SRAM_SIZE))
#define MEM_AP_WIFI_RAM_SIZE (__KB__(0))
#define MEM_IPC_BASE ((MEM_AP_WIFI_RAM_BASE) + (MEM_AP_WIFI_RAM_SIZE))
#define MEM_IPC_SIZE (__KB__(8))
#define MEM_LUNA_BASE ((MEM_IPC_BASE) + (MEM_IPC_SIZE))
#define MEM_LUNA_SIZE (__KB__(28))
// #define MEM_AP_SRAM_BASE ((MEM_LUNA_BASE) + (MEM_LUNA_SIZE))
// #define MEM_AP_SRAM_SIZE (__KB__(62))
#if CONFIG_ARCS_CP_CORE
#define MEM_SRAM_BASE MEM_CP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_CP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_CP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_CP_WIFI_RAM_SIZE
#else
#define MEM_SRAM_BASE MEM_AP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_AP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_AP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_AP_WIFI_RAM_SIZE
#endif
/* 检查下内存是否超出 */
#if (MEM_IPC_BASE + MEM_IPC_SIZE) > __MEM_TOTAL_SRAM_END__
#error "mem sram overflow"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_CP_WIFI_RAM_BASE < 0x20000000) || ((MEM_CP_WIFI_RAM_BASE + MEM_CP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "cp wifi sram error"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_AP_WIFI_RAM_BASE < 0x20000000) || ((MEM_AP_WIFI_RAM_BASE + MEM_AP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "ap wifi sram error"
#endif
/* luna code & instruction & data 必须要在 0x20040000 ~ 0x200B0000 + 448(KB)之间 */
#if (MEM_LUNA_BASE < 0x20040000) || ((MEM_LUNA_BASE + MEM_LUNA_SIZE) > (0x20040000 + __KB__(448)))
#error "ap luna sram error"
#endif
/* 根据实际需要调整 */
#if CONFIG_ARCS_AP_CORE
/* 这个是SRAM HEAP大小的定义, 该heap从SRAM中分配 */
#define SRAM_HEAP_SIZE (__KB__(5))
// /* WIFI校准数据大小 */
// #define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
// #define MEM_WIFI_TRACE_SIZE (__KB__(1))
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
#define MEM_WIFI_LA_DUMP_SIZE (__KB__(32))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#else
/* 在CP侧, 没有wifi的校准数据与跟踪数据, 这里定义为0 */
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(0))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#endif
#endif//__ARCS_MEMAP_HEADER__

View File

@@ -0,0 +1,61 @@
# 使用自定义内存配置文件和自定义链接脚本
CONFIG_MEM_CONFIG=n
CONFIG_MEM_CONFIG_USE_CUSTOM_FILE=y
# CP core
CONFIG_ARCS_CP_CORE=y
# not boot loader
# CP这里一定不能使能否则会初始化时钟和PSRAM和AP冲突
CONFIG_BOOT_HART=n
CONFIG_BOOT=n
# C LIB
CONFIG_LINK_OPTION_NONE_SPECS=y
# HEAP
CONFIG_HEAP_SIZE=0x10000
CONFIG_PSRAM_HEAP_SIZE=0x100000
# CPP
CONFIG_LINK_CPP_RUNTIME_SECTIONS=n
CONFIG_CPP_EXCEPTIONS=n
# Debug
CONFIG_BACK_TRACE=y
# freertos
CONFIG_FREERTOS_STATIC_ALLOCATION_ENABLE=y
# main task
CONFIG_MAIN_TASK_STACK_SIZE=8192
CONFIG_MAIN_TASK_PRIORITY=5
# IPC
CONFIG_ARCS_HAL_LSF=y
CONFIG_IPC_LSF=y
CONFIG_DISK_MEM=n
# CONFIG_ARCS_HAL_IC_MUTEX=n
# GPDMA
CONFIG_ARCS_GPDMA_FULL_INIT=y
# acomp fd
CONFIG_ACOMP=y
CONFIG_ACOMP_FD=y
CONFIG_ACOMP_FD_RES_FACE_DETECT_ADDRESS=0x30100000
CONFIG_ACOMP_FD_RES_FACE_DETECT_LENGTH=450936
CONFIG_ACOMP_FD_RES_FACE_ALIGN_ADDRESS=0x30170000
CONFIG_ACOMP_FD_RES_FACE_ALIGN_LENGTH=1588664
CONFIG_ACOMP_FD_RES_FACE_LIVE_ADDRESS=0x30300000
CONFIG_ACOMP_FD_RES_FACE_LIVE_LENGTH=1095288
CONFIG_ACOMP_FD_FACE_VERIFY_ADDRESS=0x30410000
CONFIG_ACOMP_FD_FACE_VERIFY_LENGTH=2953752
# watchdog
CONFIG_BOOT_WITH_WATCHDOG=y
CONFIG_WATCHDOG_ENABLE=n
# work queue
CONFIG_WORK_QUEUE=y

Binary file not shown.

View File

@@ -0,0 +1,4 @@
tests:
samples.algorithms.face_detect:
build_only: true

View File

@@ -0,0 +1,11 @@
listenai_library_named(app)
listenai_library_sources(
main.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(app_fd)

View File

@@ -0,0 +1,10 @@
listenai_library_named(app_fd)
listenai_library_sources(
app_fd.c
app_fd_image_input.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1,78 @@
#include "stdio.h"
#include <string.h>
#include <stdbool.h>
#include "acomp_fd.h"
#define TAG "app_fd"
#include "lisa_log.h"
/* 人脸识别结果回调 */
static void fd_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & FD_CB_EVENT_ENGINE_RLT) {
LISA_LOGI(TAG, "fd result:%d,%s", event_data_len, (char *)event_data);
acomp_fd_result_info_t *info = (acomp_fd_result_info_t *)event_data;
uint32_t result_cnt = info->results_cnt;
if (result_cnt > 0) {
uint32_t max_area_index = info->max_area_results_index;
acomp_fd_result_t *result = (acomp_fd_result_t *)&info->results[max_area_index];
LISA_LOGI(TAG, "detect face cnt: %d\n\n"
"max area result:\n"
"index: %d\n"
"face_rect: [x:%d, y:%d, w:%d, h:%d]\n"
"face_score: %f\n"
"face_align_cnt:%d\n"
"head_pose:[yaw:%f, pitch:%f, roll:%f]\n"
"face_live_result:[status:%d, scores[0]:%f, scores[1]:%f]\n"
"feature_cnt:%d\n"
"compare_cnt:%d\n"
"compare_scores:[%f, %f]\n",
result_cnt,
max_area_index,
result->face_rect.x,
result->face_rect.y,
result->face_rect.w,
result->face_rect.h,
result->face_score,
result->n_align_point,
result->pose.yaw,
result->pose.pitch,
result->pose.roll,
result->live_result.status,
result->live_result.scores[0],
result->live_result.scores[1],
result->feature_cnt,
result->compare_cnt,
result->compare_scores[0],
result->compare_scores[1]);
} else {
LISA_LOGI(TAG, "no face detected");
}
}
}
int app_fd_init(void)
{
/* 人脸识别算法 */
acomp_fd_init();
acomp_fd_prepare();
acomp_fd_start();
acomp_fd_add_callback(FD_CB_EVENT_ENGINE_RLT, fd_event_handler, NULL);
const acomp_fd_live_detect_mode_t mode = {
.enable = true, // 启用活体检测
.score_threshold = {
0.5f, // 非活体得分阈值(实际没用到这个参数)
0.5f, // 活体得分阈值(如果活体得分大于等于这个阈值, 就认为是活体,如果小于这个阈值,就不会进行人脸特征提取和人脸识别)
},
};
acomp_fd_live_detect_mode_set(&mode);
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int app_fd_init(void);

View File

@@ -0,0 +1,82 @@
#include "stdio.h"
#include <string.h>
#include <stdbool.h>
#include "workqueue.h"
#include "app_fd_image_input.h"
#define TAG "app_fd_stream"
#include "lisa_log.h"
/* fd image in stream */
#define FD_IMAGE_FRAME_STREAM_CH_INDEX (0)
#define FD_IMAGE_FRAME_STREAM_CH_CNAME "stream.fd_image"
#define FD_IMAGE_FRAME_STREAM_BUF_SIZE (320 * 240 * 2 + sizeof(acomp_fd_input_frame_t))
static workqueue_t *fd_in_wq = NULL;
int app_fd_image_stream_init(void)
{
acomp_stream_chn_create_desc_t desc = {
.cname = FD_IMAGE_FRAME_STREAM_CH_CNAME,
.direction = ACOMP_STREAM_DIRECTION_M2R,
.index = FD_IMAGE_FRAME_STREAM_CH_INDEX,
.buffer_size = FD_IMAGE_FRAME_STREAM_BUF_SIZE,
.num_descs = 4,
.kick_policy = 1,
};
fd_in_wq = workqueue_create("fd_in_wq", 9, 10, 8096);
if (fd_in_wq == NULL) {
LISA_LOGE(TAG, "Failed to create fd_in_wq");
}
acomp_fd_stream_ch_enable(FD_IMAGE_FRAME_STREAM_CH_INDEX,&desc);
return 0;
}
static void fd_in_wq_handler(void *para)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
acomp_fd_input_frame_t *fd_frame;
image_frame_t *msg = (image_frame_t *)para;
LISA_LOGD(TAG, "fd_in_wq_handler");
buffer = acomp_fd_stream_tx_buffer_alloc(FD_IMAGE_FRAME_STREAM_CH_INDEX, &buf_size, &desc_idx);
if(buffer && buf_size > 0){
fd_frame = (acomp_fd_input_frame_t *)buffer;
fd_frame->format = msg->image_format;
fd_frame->index = msg->image_index;
fd_frame->width = msg->image_width;
fd_frame->height = msg->image_height;
fd_frame->length = msg->image_length;
memcpy(fd_frame->data, msg->image_data, msg->image_length);
acomp_fd_stream_tx_buffer_submit(FD_IMAGE_FRAME_STREAM_CH_INDEX, buffer, buf_size, desc_idx);
}
psram_free(msg);
}
int app_fd_image_send(image_frame_t *data)
{
int ret;
image_frame_t *msg = psram_malloc(sizeof(image_frame_t));
memcpy(msg, data, sizeof(image_frame_t));
ret = workqueue_submit(fd_in_wq, fd_in_wq_handler, msg);
if (ret == 0) {
LISA_LOGE(TAG, "workqueue submit failed:%d", ret);
psram_free(msg);
}
return ret;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
#include "acomp_fd.h"
typedef struct {
uint8_t * image_data; /* Image data pointer */
uint32_t image_index; /* Image index */
uint32_t image_length; /* Image data length */
uint16_t image_width; /* Image width */
uint16_t image_height; /* Image height */
acomp_fd_pixel_format image_format; /* Image Pixel format */
} image_frame_t;
int app_fd_image_stream_init(void);
int app_fd_image_send(image_frame_t *frame);

View File

@@ -0,0 +1,52 @@
#include "stdio.h"
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "ic_message.h"
#include "app_fd.h"
#include "app_fd_image_input.h"
#include "test_image.h"
#define TAG "main"
#include "lisa_log.h"
int main(int argc, char **argv)
{
printf("CP=======! Hard ID: %d\n", CONFIG_HARTID);
ic_message_init();
LISA_LOGI(TAG, "ic_message_init done!");
vTaskDelay(pdMS_TO_TICKS(2000));
/* 算法组件初始化 */
acomp_init();
/* 人脸识别组件初始化 */
app_fd_init();
/* 人脸识别组件输入图片数据流初始化 */
app_fd_image_stream_init();
image_frame_t image_frame = {
.image_data = (uint8_t *)yuyv422_320_240,
.image_index = 0,
.image_length = yuyv422_320_240_len,
.image_width = 320,
.image_height = 240,
.image_format = PIX_FMT_YUV422_YUYV_PACKED,
};
while(1){
vTaskDelay(pdMS_TO_TICKS(1000));
/* 给算法组件输入图片数据 */
app_fd_image_send(&image_frame);
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
.. _samples_algorithms:
算法组件示例
============
本节提供了基于acomp算法组件的示例,展示如何使用算法组件
.. toctree::
:maxdepth: 1
wake_up/wakeup_single/README.md
wake_up/wakeup_double/README.md
face_detect/README.md

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(listenai-cmake REQUIRED HINTS $ENV{ARCS_BASE})
project(arcs)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/app_wakeup)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/workqueue)
listenai_add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PRIVATE src/main.c
PRIVATE src/app_wakeup/app_wakeup.c
PRIVATE src/app_wakeup/wakeup_audio_in.c
PRIVATE src/app_wakeup/wakeup_audio_out.c
)

View File

@@ -0,0 +1 @@
osource "$ARCS_BASE/Kconfig"

View File

@@ -0,0 +1,435 @@
# 双麦唤醒算法示例
## 功能说明
演示如何使用 ACOMP 唤醒算法组件实现语音唤醒功能,包括音频数据采集、回采处理、唤醒检测和结果输出。
本示例展示了双麦克风阵列的唤醒算法应用,支持关键词识别、角度检测、超时处理以及算法回声消除音频输出等完整的唤醒流程。
用户可以通过语音唤醒算法,算法检测到唤醒词或命令词后,会输出识别结果。
## 硬件连接
- **音频输入**: 双麦克风输入(MIC1, MIC2),用ADC采样采样率 16kHz
- **音频输出**: DAC单声道播放输出,同时回采参考信号,采样率 16kHz
- **调试串口**: CP核的UART0,PA3引脚波特率 921600,用于日志输出
- **调试串口**: AP核的UART1,PA4引脚波特率 921600,用于日志输出
## 示例内容
1. 初始化 ACOMP 唤醒算法引擎
2. 配置音频输入输出设备(双麦克风录音 + 单声道播放(同时获取回采))
3. 创建音频数据流通道(M2R 和 R2M)
4. 采集音频数据并融合麦克风和回采信号
5. 将融合后的音频数据送入唤醒算法引擎
6. 处理唤醒结果(关键词、角度、超时等事件)
7. 输出回声消除后的音频数据流
## 编译
```{eval-rst}
.. include:: /sample_build.rst
```
### 烧录固件
```bash
# 烧录 AP 核固件Boot Core
# 烧录地址0x30000000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x00 ./res/ap.bin
# 烧录算法资源到 0x30200000 和 0x304d0000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x200000 ./res/algo/algo.bin
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x4d0000 ./res/algo/wrap.json
# 烧录 CP 核固件
# 烧录地址0x30050000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x500000 ./build/arcs.bin
```
## 预期输出
### CP日志
**初始化阶段:**
```
********Arcs SDK 0.0.22 @ v0.0.1-691-gfaa226ea04ba********
Running on hart-id: 1
I/elog [341:39:56.905 1 elog_async] EasyLogger V2.2.99 is initialize success.
I/lisa_audio_record [341:39:56.906 1 audio_dispatch] LISA Audio Record initialized
I/lisa_audio_play [341:39:56.906 1 audio_dispatch] LISA Audio Play initialized
I/main [341:39:56.906 1 main] CP=======! Hard ID: 1
I/main [341:39:56.908 1 main] ic_message_init done!
I/acomp_ipc [341:39:58.908 1 rpc_client] [0]acomp remote dev index 2 name acomp.logger
I/acomp_ipc [341:39:58.908 1 rpc_client] [1]acomp remote dev index 1 name acomp.wakeup
I/acomp_wakeup [341:39:58.908 1 main] acomp wakeup init enter
I/acomp_wakeup [341:39:58.908 1 main] acomp wakeup dev index 1,name:acomp.wakeup
I/acomp_wakeup [341:39:58.911 1 main] acomp wakeup init exit
I/acomp_wakeup [341:39:58.911 1 main] acomp wakeup prepare enter
I/acomp_wakeup [341:39:58.914 1 main] acomp wakeup prepare exit
I/wakeup [341:39:58.914 1 main] acomp_wakeup_prepare ret:0
I/acomp_wakeup [341:39:58.914 1 main] acomp wakeup start enter
I/acomp_wakeup [341:40:00.090 1 main] acomp wakeup start exit
I/wakeup [341:40:00.090 1 main] acomp_wakeup_start ret:0
I/acomp_wakeup [341:40:00.090 1 main] acomp wakeup set algo mode enter, mode=0
I/acomp_wakeup [341:40:00.095 1 main] acomp wakeup set algo mode exit
I/wakeup [341:40:00.095 1 main] acomp_wakeup_set_algo_mode ret:0
INF:[acomp_stream_channel_create]Acomp stream channel 0x2882b774,0x20015b50,'stream.tocloud' created successfully ,vring phy addr=0x2880d680, num_descs=8, buffer_size=15360, direction=R2M, role=Master
I/acomp_wakeup [341:40:00.100 1 main] acomp_wakeup_stream_ch_enable chn(stream.tocloud) index(1),desc(0x2880cd08)
INF:[acomp_stream_channel_create]Acomp stream channel 0x28831854,0x20015c38,'stream.mix2ch' created successfully ,vring phy addr=0x2882f7c0, num_descs=4, buffer_size=2048, direction=M2R, role=Master
I/acomp_wakeup [341:40:00.104 1 main] acomp_wakeup_stream_ch_enable chn(stream.mix2ch) index(0),desc(0x2880ccf8)
I/wakeup_in [341:40:00.105 1 main] Audio 设备获取成功
I/wakeup_in [341:40:00.105 1 main] 统一回调注册成功
I/lisa_audio_record [341:40:00.105 1 main] Record configured: rate=16000, gain=30/0 dB
I/lisa_audio_play [341:40:00.105 1 main] Play configured: rate=16000, gain=0/-18 dB, buffers=12×256
I/lisa_audio_record [341:40:00.106 1 main] Record started
I/wakeup_in [341:40:00.106 1 main] 录音已启动
I/lisa_audio_play [341:40:00.106 1 main] Play started
I/wakeup_in [341:40:00.106 1 main] 播音已启动
I/wakeup_in [341:40:00.106 1 main] 相位补偿已设置
```
**唤醒检测成功:**
- 用户可以用语音来唤醒算法,算法检测到唤醒词或命令词后,会输出识别结果
- 唤醒词:你好小奥,小奥同学
- 命令词开浴霸关浴霸开恒温暖关恒温暖打开自然风关闭自然风具体命令词见res目录下minglingci.txt
> 注意: 算法需要被唤醒后,才可以识别命令词
```
I/wakeup [341:40:05.778 1 rpc_client] WAKE(1): KEY=1(ni hao xiao ao), NCM=876, len:278
I/wakeup [341:40:14.339 1 rpc_client] WAKE(0): KEY=1(kai yu ba), NCM=380, len:350
I/wakeup [341:40:18.098 1 rpc_client] WAKE(1): KEY=1(ni hao xiao ao), NCM=848, len:278
I/wakeup [341:40:26.001 1 rpc_client] WAKE(1): KEY=1(ni hao xiao ao), NCM=786, len:279
I/wakeup [341:40:28.677 1 rpc_client] WAKE(0): KEY=2(guan yu ba), NCM=711, len:352
```
**唤醒超时:**
算法需要被唤醒后,才可以识别命令词,如长时间没有识别到命令词,会触发超时事件,用户需重新唤醒
```
I/wakeup [341:40:44.745 1 rpc_client] wakeup timeout
```
### AP日志
```
********Arcs SDK@test_deploy-163-gb9c7998b-@v0.0.22********
Running on hart-id: 0
AP Hard ID: 0
boot cp from address: 0x30500000
luna_version:0x3000200
I/wakeup [00:00:00.058 0 main] sys_acomp_wakeup_init
I/components [00:00:00.060 0 main] Reserved driver ID: 1
I/components [00:00:00.060 0 main] acomp register driver index:1,name:acomp.wakeup
I/logger [00:00:00.061 0 main] sys_acomp_logger_init
I/components [00:00:00.062 0 main] Reserved driver ID: 2
I/components [00:00:00.063 0 main] acomp register driver index:2,name:acomp.logger
INF:[acomp_context_init 257]components context init success
I/wakeup [00:00:02.059 0 rpc_async_serv] wakeup_create enter
I/wakeup [00:00:02.060 0 rpc_async_serv] wakeup_create exit
I/wakeup [00:00:02.061 0 acomp_workqueue] wakeup_prepare enter
I/wakeup [00:00:02.062 0 a_workqueue] wakeup_prepare exit
I/wakeup [00:00:02.064 0 acomp_workqueue] wakeup_start enter
I/wakeup_algo [00:00:02.065 0 acomp_workqueue] cae_esr_mlp Resource, addr=0x30200000, size=2926656
I/wakeup_algo [00:00:02.066 0 acomp_workqueue] Wrap json, addr=0x304d0000, size=1008
JSON content (1008 chars):
{"wrap":{"dev":"4002","adc":0,"trans":1,"param":[{"key":"wakesoffset","val":160},{"key":"wakeeoffset","val":-160}]},"cae":{"dist":30,"bits":16,"chns":{"mic":2,"ref":2,"out":9},"wkmask":3,"param":[{"key":"mode","val":"0"},{"key":"textdep","val":1},{"key":"16386","val":100},{"key":"16387","val":150},{"key":"32770","val":0},{"key":"8","val":0},{"key":"9","val":0}]},"esr":{"param":[{"key":"prewstat","val":2},{"key":"prewthr","val":400},{"key":"mode","val":1},{"key":"model","val":0},{"key":"1105","val":0},{"key":"1107","val":0},{"key":"1138","val":1},{"key":"1106","val":0},{"key":"2119","val":0},{"key":"1124","val":1},{"key":"1180","val":2},{"key":"1126","val":63},{"key":"1125","val":10},{"key":"1118","val":8},{"key":"1119","val":40},{"key":"1130","val":5},{"key":"1131","val":40},{"key":"1132","val":5},{"key":"1133","val":8},{"key":"1134","val":-1},{"key":"1136","val":0},{"key":"1408","val":2},{"key":"1409","val":4},{"key":"1129","val":8}],"switchparam":[{"key":"2118","val_main":500,"val_cmd":0}]}}
I/wakeup_algo [00:00:02.079 0 acomp_workqueue]
ai_mem_t memap: json: 0x304d0000, psram: 0x0, size: 0, res: 0x30200000, size: 2926656
ai_get_info ret=0, len=795
info content (795 chars):
{"wrap":{"version":"600x.aiwrap.v1000.1.2.1 beta (Nov 21 2025 15:58:50)","inst_size":31712,"share_size":15360,"samples":{"in":256,"out":1280}},"cae":{"version":"5001.cae.v1000.1.2.1 beta (Nov 13 2025 15:08:05)","memory":{"inst_ram_size":47904,"temp_ram_size":310496,"psram_size":1451168},"feature":{"mic_num":2,"ref_num":2,"local_out_channels":5,"aec_out_channels":0,"asr_out_channels":1,"nbit":16,"nsample":16000,"frame_num":5,"frame_size":256,"support_pre_wk_info":1,"support_wk_info":1}},"esr":{"version":"mini-esr-ARCS Tag5.0.0.0.2.1_27,Sep 9 2025,17:48:46","memory":{"share_mem_size":240672,"inst_mem_size":251232},"feature":{"input_pcm_channel":5,"input_pcm_bit":16,"input_pcm_frame_count":8,"input_pcm_sample_rate":16000,"max_main_word_count":2,"max_asr_word_count":107,"alog_type":27}}}
I/wakeup_algo [00:00:02.403 0 acomp_workqueue] wrap_size: 31712
I/wakeup_algo [00:00:02.404 0 acomp_workqueue] cae_psram: 1451168
I/wakeup_algo [00:00:02.405 0 acomp_workqueue] inst_ram_size: 47904
I/wakeup_algo [00:00:02.406 0 acomp_workqueue] temp_ram_size: 310496
I/wakeup_algo [00:00:02.407 0 acomp_workqueue] esr_share_mem: 240672
I/wakeup_algo [00:00:02.407 0 acomp_workqueue] esr_psram: 251232
I/wakeup_algo [00:00:02.409 0 acomp_workqueue] Memory requirements - psram_size: 1734144, share_size: 358432
I/wakeup_algo [00:00:02.410 0 acomp_workqueue] algo_psram_buffer allocated at 0x28021b00, size=1734144
Resource: arcs_cldnn_aes_1024_d1_meidi7ci_20250714.bin,addr: 0x281c9100, size: 247744, compress: 0
Resource: arcs_cldnn_mctd_1024_d5d0_meidi7ci_20250710.bin,addr: 0x282058c0, size: 345408, compress: 0
Resource: arcs_cldnn_sp_1024_d4d0_20250714.bin,addr: 0x28259e00, size: 345024, compress: 0
Resource: cae_ffw0_501.json,addr: 0x282ae1c0, size: 141, compress: 0
Resource: mlp_main.bin,addr: 0x282ae260, size: 616864, compress: 0
Resource: mlp_mlc.bin,addr: 0x28344c00, size: 1299680, compress: 0
Resource: filler_keywords_main.bin,addr: 0x284820e0, size: 608, compress: 0
Resource: filler_keywords_main.bin,addr: 0x28482340, size: 608, compress: 0
Resource: filler_keywords_minglingci.bin,addr: 0x284825a0, size: 34400, compress: 0
Resource: filler_keywords_minglingci.bin,addr: 0x2848ac00, size: 34400, compress: 0
I/wakeup_algo [00:00:03.226 0 acomp_workqueue] ai_create success(used: psram=0x1a75e0, share=0x57800)
I/wakeup_algo [00:00:03.228 0 acomp_workqueue] esrstag: 1
I/wakeup_algo [00:00:03.229 0 acomp_workqueue] algo_set_mode(0)
I/wakeup_algo [00:00:03.238 0 acomp_workqueue] MODE=0
I/wakeup [00:00:03.239 0 acomp_workqueue] wakeup_start exit
I/wakeup [00:00:03.241 0 acomp_workqueue] wakeup_control enter
I/wakeup [00:00:03.242 0 acomp_workqueue] wakeup_control: WAKEUP_IPC_CONTROL_SUBCMD_ALGO_MODE_SET
I/wakeup [00:00:03.243 0 acomp_workqueue] Set algo mode: 0
I/wakeup_algo [00:00:03.244 0 acomp_workqueue] Algo mode set to: WAKEUP
INF:[acomp_stream_channel_create]Acomp stream channel 0x280016a0,0x20052180,'stream.tocloud' created successfully ,vring phy addr=0x2880d680, num_descs=8, buffer_size=25600, direction=R2M, role=Remote
INF:[acomp_stream_channel_create]Acomp stream channel 0x280016d0,0x200521ec,'stream.mix2ch' created successfully ,vring phy addr=0x288437c0, num_descs=4, buffer_size=2048, direction=M2R, role=Remote
I/wakeup_algo [00:00:04.971 0 acomp_workqueue] counter_avg=204.51(M/S), cnt_part=327218363
E/wakeup_algo [00:00:05.761 0 acomp_workqueue] [wrap]error: code=1; msg={"rlt":[{"istart":43,"iresid":1,"iduration":13,"nfillerscore":1333,"nkeywordscore":12970,"ncm":919,"ncmThreshold":400,"keyword":"ni hao xiao ao","intent":"ni hao xiao ao","bMain":1,"bAbsorb":0}]}
I/wakeup_algo [00:00:06.019 0 acomp_workqueue] [wrap]wakeup: chn=1; result={"rlt":[{"istart":43,"iresid":1,"iduration":20,"nfillerscore":1759,"nkeywordscore":18812,"ncm":830,"ncmThreshold":400,"keyword":"ni hao xiao ao","intent":"ni hao xiao ao","nDelayFrame":0,"nThrowFrame":62,"decId":0,"branch":0,"wakeUpType":0,"bMain":1,"bAbsorb":0,"iframe":65}]}
I/wakeup [00:00:06.023 0 acomp_workqueue] wakeup_algo_result_cb: data_len(276)
I/wakeup_algo [00:00:06.027 0 acomp_workqueue] WAKE(1): CHN=1, KEY=1(ni hao xiao ao), NCM=830, len:276
I/wakeup_algo [00:00:06.113 0 acomp_workqueue] algo_set_mode(1)
I/wakeup_algo [00:00:06.123 0 acomp_workqueue] MODE=1
I/wakeup_algo [00:00:06.577 0 acomp_workqueue] counter_avg=219.85(M/S), cnt_part=351752029
I/wakeup_algo [00:00:08.177 0 acomp_workqueue] counter_avg=220.23(M/S), cnt_part=352369137
I/wakeup_algo [00:00:09.778 0 acomp_workqueue] counter_avg=220.39(M/S), cnt_part=352616103
I/wakeup_algo [00:00:09.938 0 acomp_workqueue] [wrap]wakeup: chn=1; result={"rlt":[{"istart":77,"iresid":2,"iduration":20,"nfillerscore":0,"nkeywordscore":0,"ncm":502,"ncmThreshold":300,"keyword":"guan yu ba","intent":"guan yu ba","nRltIdx":1,"nDelayFrame":5,"nThrowFrame":96,"decId":0,"branch":0,"wakeUpType":0,"VadGap":0,"nE2eIntervalFrame":0,"nE2eNodeFrame":0,"nStartStateThreshold":0,"bMain":0,"bAbsorb":0,"iframe":164}]}
I/wakeup [00:00:09.942 0 acomp_workqueue] wakeup_algo_result_cb: data_len(350)
I/wakeup_algo [00:00:09.947 0 acomp_workqueue] WAKE(0): CHN=1, KEY=2(guan yu ba), NCM=502, len:350
I/wakeup_algo [00:00:11.380 0 acomp_workqueue] counter_avg=222.83(M/S), cnt_part=356531282
I/wakeup_algo [00:00:12.977 0 acomp_workqueue] counter_avg=220.21(M/S), cnt_part=352343366
I/wakeup_algo [00:00:14.579 0 acomp_workqueue] counter_avg=220.40(M/S), cnt_part=352634514
I/wakeup_algo [00:00:15.220 0 acomp_workqueue] [wrap]wakeup: chn=1; result={"rlt":[{"istart":203,"iresid":1,"iduration":26,"nfillerscore":0,"nkeywordscore":0,"ncm":648,"ncmThreshold":300,"keyword":"kai yu ba","intent":"kai yu ba","nRltIdx":2,"nDelayFrame":5,"nThrowFrame":228,"decId":0,"branch":0,"wakeUpType":0,"VadGap":0,"nE2eIntervalFrame":0,"nE2eNodeFrame":0,"nStartStateThreshold":0,"bMain":0,"bAbsorb":0,"iframe":296}]}
I/wakeup [00:00:15.225 0 acomp_workqueue] wakeup_algo_result_cb: data_len(350)
I/wakeup_algo [00:00:15.228 0 acomp_workqueue] WAKE(0): CHN=1, KEY=1(kai yu ba), NCM=648, len:350
I/wakeup_algo [00:00:16.177 0 acomp_workqueue] counter_avg=222.50(M/S), cnt_part=355992438
I/wakeup_algo [00:00:17.777 0 acomp_workqueue] counter_avg=218.77(M/S), cnt_part=350037055
I/wakeup_algo [00:00:19.377 0 acomp_workqueue] counter_avg=219.22(M/S), cnt_part=350752636
I/wakeup_algo [00:00:20.977 0 acomp_workqueue] counter_avg=219.33(M/S), cnt_part=350930771
I/wakeup_algo [00:00:22.577 0 acomp_workqueue] counter_avg=218.72(M/S), cnt_part=349958010
I/wakeup_algo [00:00:24.177 0 acomp_workqueue] counter_avg=219.19(M/S), cnt_part=350711441
I/wakeup_algo [00:00:25.778 0 acomp_workqueue] counter_avg=219.53(M/S), cnt_part=351251256
I/wakeup_algo [00:00:27.377 0 acomp_workqueue] counter_avg=219.40(M/S), cnt_part=351035391
I/wakeup_algo [00:00:28.977 0 acomp_workqueue] counter_avg=219.51(M/S), cnt_part=351213292
I/wakeup_algo [00:00:30.577 0 acomp_workqueue] counter_avg=219.62(M/S), cnt_part=351384765
I/wakeup_algo [00:00:31.220 0 acomp_workqueue] algo_set_mode(0)
I/wakeup_algo [00:00:31.229 0 acomp_workqueue] MODE=0
I/wakeup [00:00:31.230 0 acomp_workqueue] wakeup_algo_timeout_cb
I/wakeup_algo [00:00:32.173 0 acomp_workqueue] counter_avg=214.95(M/S), cnt_part=343912662
I/wakeup_algo [00:00:33.774 0 acomp_workqueue] counter_avg=213.49(M/S), cnt_part=341580196
```
## 核心 API
| API | 说明 |
|-----|------|
| `acomp_wakeup_init()` | 初始化唤醒算法组件 |
| `acomp_wakeup_prepare()` | 准备唤醒算法引擎 |
| `acomp_wakeup_start()` | 启动唤醒算法引擎 |
| `acomp_wakeup_set_algo_mode()` | 设置算法模式(唤醒模式) |
| `acomp_wakeup_add_callback()` | 注册唤醒事件回调函数 |
| `acomp_wakeup_stream_ch_enable()` | 使能音频数据流通道 |
| `acomp_wakeup_stream_tx_buffer_alloc()` | 分配发送缓冲区(M2R) |
| `acomp_wakeup_stream_tx_buffer_submit()` | 提交发送缓冲区 |
| `acomp_wakeup_stream_rx_buffer_get()` | 获取接收缓冲区(R2M) |
| `acomp_wakeup_stream_rx_buffer_release()` | 释放接收缓冲区 |
| `lisa_audio_register_callback()` | 注册音频设备回调 |
| `lisa_audio_record_config()` | 配置录音参数 |
| `lisa_audio_play_config()` | 配置播放参数 |
| `lisa_audio_record_start()` | 启动录音 |
| `lisa_audio_play_start()` | 启动播放 |
## 关键代码
### 唤醒算法事件回调
```c
static void wakeup_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
/* 唤醒结果 */
LISA_LOGI(TAG, "wakeup result:%d,%s", event_data_len, (char *)event_data);
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
/* 唤醒超时 */
LISA_LOGI(TAG, "wakeup timeout");
} else if (event & WAKEUP_CB_EVENT_ENGINE_ANGLE) {
/* 角度信息 */
for (int i = 0; i < event_data_len/sizeof(short); i++) {
short angle = *((short*)event_data + i);
LISA_LOGD(TAG, "wakeup angle[%d]: %d", i, angle);
}
}
}
static void wakeup_stream_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
/* 算法音频输出事件 */
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
if (rx_sem != NULL) {
xSemaphoreGive(rx_sem);
}
#endif
}
}
```
### 获取麦克风和回采的音频
```c
/* 音频回调函数 - 收集麦克风和回采数据 */
static void unified_audio_callback(const lisa_audio_event_t *event, void *user_data)
{
wakeup_in_msg_t *msg = psram_malloc(sizeof(wakeup_in_msg_t));
msg->mic = (mic_in_t*)event->record_buffer;
msg->ref = (ref_in_t*)(event->echo_buffer ? event->echo_buffer : zero_echo);
msg->sample_cnt = event->record_samples;
/* 提交到工作队列处理 */
workqueue_submit(wakeup_in_wq, wakeup_in_wq_handler, msg);
}
```
### 麦克风和回采的音频融合后送入算法
```c
static void wakeup_in_wq_handler(void *para)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
wakeup_in_msg_t *msg = (wakeup_in_msg_t*)para;
LISA_LOGD(LOG_TAG, "wakeup_in_wq_handler, sample_cnt: %u", msg->sample_cnt);
buffer = acomp_wakeup_stream_tx_buffer_alloc(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, &buf_size, &desc_idx);
if(buffer && buf_size > 0){
stream_data_fusion((acomp_wakeup_audio_in_t*)buffer, msg->mic, msg->ref, msg->sample_cnt);
acomp_wakeup_stream_tx_buffer_submit(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, buffer, buf_size, desc_idx);
}
psram_free(msg);
}
```
### 用户对算法输出的音频进行处理
用户可以获取算法输出的音频数据,进行处理,如发送算法输出的回声消除的音频到云端进行大模型意图识别
```c
static void wakeup_out_task(void *pvParameters)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
int ret;
while (1) {
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
/* 自动触发模式:等待来自回调的信号量 */
xSemaphoreTake(rx_sem, pdMS_TO_TICKS(50));
#else
/* 手动触发模式:等待超时时间 */
vTaskDelay(pdMS_TO_TICKS(5));
#endif
/* 从 R2M 流中获取数据 */
while (1) {
buffer = acomp_wakeup_stream_rx_buffer_get(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, &len, &desc_idx);
if (buffer != NULL && len > 0) {
LISA_LOGD(TAG, "acomp_wakeup_stream_rx_buffer_get ok");
// 2. 处理接收到的数据
// 数据格式为 acomp_wakeup_audio_out_t 数组5通道交织
// {mic0, mic1, ref0, out1, out2} * N 个采样点
// 其中 out1 为回声消除后的音频,可用于云端识别
// process_output_audio(buffer, len);
/* 释放缓冲区 */
ret = acomp_wakeup_stream_rx_buffer_release(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, desc_idx, len, buffer);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to release buffer: %d", ret);
}
} else {
/* 没有更多数据,跳出内层循环 */
break;
}
}
}
}
```
## 算法模式说明
### 双麦克风模式 (CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC)
- **输入通道**: 2 个麦克风 + 2 个回采参考
- **数据格式**: 4 通道交织(mic1, mic2, ref1, ref2)
- **特点**: 支持波束成形和角度检测,唤醒性能更好
### 音频参数配置
```c
#define SAMPLE_RATE LISA_AUDIO_RATE_16K /* 采样率 16kHz */
#define SAMPLE_BITS LISA_AUDIO_BIT_16 /* 采样位宽 16bit */
#define RECORD_CHANNELS LISA_AUDIO_CH_STEREO /* 录音双声道 */
#define PLAY_CHANNELS LISA_AUDIO_CH_LEFT /* 播放单声道 */
#define BUFFER_SAMPLES ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT /* 缓冲区采样点数 */
```
### 增益配置
```c
#define RECORD_ANALOG_GAIN 30 /* 录音模拟增益 16dB */
#define RECORD_DIGITAL_GAIN 0 /* 录音数字增益 8dB */
#define PLAY_ANALOG_GAIN 0 /* 播放模拟增益 6dB */
#define PLAY_DIGITAL_GAIN -18 /* 播放数字增益 -18dB */
```
### 算法资源配置
在 `prj.conf` 中配置算法资源地址和大小:
```
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=y
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=2926656
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=1008
```
### 堆内存配置
```
CONFIG_HEAP_SIZE=0x10000 /* 内部 SRAM 堆 64KB */
CONFIG_PSRAM_HEAP_SIZE=0x700000 /* PSRAM 堆 7MB */
```
## 数据流说明
### M2R 流(Master to Remote)
- **通道索引**: 0
- **通道名称**: "stream.mix2ch"
- **方向**: 应用核(CP) → 算法核(AP)
- **数据**: 融合后的麦克风和回采数据(输入给算法)
- **缓冲区大小**: 一次2048 字节(256 采样点 × 4 通道 × 2 字节)
### R2M 流(Remote to Master)
- **通道索引**: 1
- **通道名称**: "stream.tocloud"
- **方向**: 算法核(AP) → 应用核(CP)
- **数据**: 算法处理后的音频数据(5 通道)(mic1, mic2, ref, 回声消除后的音频(可用于意图识别), 算法后端输入音频)
- **缓冲区大小**: 一次输出 15360或10240 字节
## 算法内存占用
双麦算法内存占用如下(粗略统计) 详细内存分布见`memap.h`文件
| MCU核心 | 内存类型 | 大小 | 备注 |
|---------|----------|----|-----|
| AP | FLASH | 3470KB | 2个算法资源 + ap固件大小 + cp固件大小 |
| AP | PSRAM | 5000KB | 算法实例用的PSRAM + 2个算法资源拷贝到PSRAM上 + 动态内存 |
| AP | SRAM | 8KB | IPC共享内存 |
| AP | SRAM | 62KB | AP的SRAM |
| AP | SRAM | 350KB | 算法实例大小 |
| AP | SRAM | 28KB | LUNA的*(.sharedmem.*) |
| AP | LUNASRAM | 64KB | LUNA专用的SRAM大小 |

View File

@@ -0,0 +1,177 @@
#!/bin/bash
set -e
usage() {
echo "使用方式: $0 [选项]"
echo "选项:"
echo " -S, --Source <path> 指定项目源码路径 (默认为当前脚本所在目录)"
echo " -t, --target <target> 指定构建目标 (如 menuconfig)"
echo " -C, --Clean 清理构建目录"
echo " -B, --build 构建输出目录"
echo " -h, --help 显示此帮助信息"
echo " -r, --release 以 Release 模式构建 (移除 DEBUG_PATH 信息)"
echo " -w, --warnings-as-errors 将警告视为错误"
echo " -D<var>=<value> 传递 CMake 变量 (可多次使用)"
echo ""
echo "示例:"
echo " $0 -S samples/helloworld -DBOARD=arcs_mini 指定板型构建"
echo " $0 -S samples/helloworld -DBOARD=arcs_evb 使用 EVB 板型"
echo " $0 -S samples/helloworld -t menuconfig -DBOARD=arcs_mini 运行 menuconfig"
echo " $0 -C -S samples/helloworld -DBOARD=arcs_mini 清理并重新构建"
echo " $0 -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards 使用自定义板型"
exit 1
}
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_PATH="$SCRIPT_DIR"
TARGET=""
CLEAN=false
OUTPUT="build"
WARNINGS_AS_ERRORS=false
RELEASE=false
ARCS_BASE_DIR_NAME="arcs-sdk"
ARCS_DEV_TOOLS_DIR_NAME="listenai-dev-tools"
ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME="gcc"
ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME="listenai-tools"
find_arcs_base() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_BASE_DIR_NAME"
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found ARCS_BASE: $current_dir/$dir_name"
export ARCS_BASE="$current_dir/$dir_name"
return 0
fi
current_dir=$(dirname "$current_dir")
done
echo "ARCS_BASE not found, Please add ARCS_BASE environment variable or set ARCS_BASE_DIR_NAME to the correct directory."
echo "Current Target ARCS_BASE directory name: $ARCS_BASE_DIR_NAME."
exit 1
}
find_dev_tools() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_DEV_TOOLS_DIR_NAME"
echo "trying to find $dir_name in parent directories..."
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found $dir_name: $current_dir/$dir_name"
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME" ]; then
echo "Found LISTENAI_TOOLS_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
export LISTENAI_TOOLS_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
fi
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME" ]; then
echo "Found NUCLEI_TOOLCHAIN_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
export NUCLEI_TOOLCHAIN_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
fi
return 0
fi
current_dir=$(dirname "$current_dir")
done
}
while [[ $# -gt 0 ]]; do
case $1 in
-S|--Source)
PROJECT_PATH="$2"
shift 2
;;
-B|--build)
OUTPUT="$2"
shift 2
;;
-t|--target)
TARGET="$2"
shift 2
;;
-C|--Clean)
CLEAN=true
shift 1
;;
-w|--warnings-as-errors)
WARNINGS_AS_ERRORS=true
shift 1
;;
-h|--help)
usage
;;
-r|--release)
RELEASE=true
shift 1
;;
-D*)
CMAKE_VARS+=("$1")
shift 1
;;
*)
echo "未知参数: $1"
usage
;;
esac
done
echo "Source: $PROJECT_PATH"
echo "Target: $TARGET"
echo "Clean : $CLEAN"
if [ -z "$LISTENAI_TOOLS_PATH" ] || [ -z "$NUCLEI_TOOLCHAIN_PATH" ]; then
find_dev_tools
fi
if [ -z "${LISTENAI_TOOLS_PATH}" ]; then
export LISTENAI_TOOLS_PATH="请添加 LISTENAI_TOOLS_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 LISTENAI_TOOLS_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
if [ -z "${NUCLEI_TOOLCHAIN_PATH}" ]; then
export NUCLEI_TOOLCHAIN_PATH="请添加 NUCLEI_TOOLCHAIN_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 NUCLEI_TOOLCHAIN_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
############### 下面代码不用修改 ##################
# 构建工具的位置
CMAKE_PROGRAM="$LISTENAI_TOOLS_PATH/cmake/bin/cmake"
NINJA_PROGRAM="$LISTENAI_TOOLS_PATH/ninja/ninja"
# 配置环境变量 ARCS_BASE
if [ -z "$ARCS_BASE" ]; then
find_arcs_base
fi
if [ "$CLEAN" = true ]; then
rm -rf $OUTPUT
fi
# Initialize CMAKE_VARS array if it doesn't exist
declare -a CMAKE_VARS
# Add warnings-as-errors flag if enabled
if [ "$WARNINGS_AS_ERRORS" = true ]; then
CMAKE_VARS+=("-DCMAKE_C_FLAGS=-Werror")
CMAKE_VARS+=("-DCMAKE_CXX_FLAGS=-Werror")
echo "Treating warnings as errors"
fi
# Add release flags if enabled
if [ "$RELEASE" = true ]; then
CMAKE_VARS+=("-DENABLE_DEBUG_PATH=OFF")
echo "Release mode enabled (-DENABLE_DEBUG_PATH=OFF)"
fi
$CMAKE_PROGRAM -B "$OUTPUT" -G Ninja -S "$PROJECT_PATH" \
-DCMAKE_MAKE_PROGRAM="$NINJA_PROGRAM" \
"${CMAKE_VARS[@]}"
if [ -z "$TARGET" ]; then
$CMAKE_PROGRAM --build "$OUTPUT" -j4
else
$CMAKE_PROGRAM --build "$OUTPUT" --target "$TARGET"
fi

View File

@@ -0,0 +1,165 @@
#ifndef __ARCS_MEMAP_HEADER__
#define __ARCS_MEMAP_HEADER__
#define __KB__(x) ((x) * 1024)
#define __MB__(x) ((x) * 1024 * 1024)
#if CONFIG_ARCS_AP_CORE
#define MEM_ILM_BASE 0x00080000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00100000
#define MEM_DLM_SIZE (__KB__(8))
#else
#define MEM_ILM_BASE 0x00280000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00300000
#define MEM_DLM_SIZE (__KB__(8))
#endif
#define MEM_BTRAM_BASE 0x200C0000
#define MEM_BTRAM_SIZE (__KB__(24))
/* FLASH 分配 */
#define MEM_TOTAL_FLASH_SIZE __MB__(16)
#define MEM_AP_FLASH_BASE 0x30000000
#define MEM_AP_FLASH_SIZE __KB__(1024)
#define MEM_CP_FLASH_BASE (0x30000000 + __MB__(5))
#define MEM_CP_FLASH_SIZE __MB__(8)
/* PSRAM 分配 */
#define MEM_AP_PSRAM_BASE 0x28000000
#define MEM_AP_PSRAM_SIZE __MB__(8)
#define MEM_CP_PSRAM_BASE ((MEM_AP_PSRAM_BASE) + (MEM_AP_PSRAM_SIZE))
#define MEM_CP_PSRAM_SIZE __MB__(8)
#if CONFIG_ARCS_AP_CORE
#define MEM_PSRAM_BASE MEM_AP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_AP_PSRAM_SIZE
#else
#define MEM_PSRAM_BASE MEM_CP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_CP_PSRAM_SIZE
#endif
#if CONFIG_ARCS_AP_CORE
#define MEM_FLASH_BASE MEM_AP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_AP_FLASH_SIZE
#else
#define MEM_FLASH_BASE MEM_CP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_CP_FLASH_SIZE
#endif
#define __MEM_TOTAL_SRAM_START__ 0x20000080 //暂留128(0x80)字节给WiFi硬件
#define __MEM_TOTAL_SRAM_END__ 0x20050000
/**
* SRAM 应用侧分配
* --------------------------------------------
* 0x20000000 | CP-WIFI-RAM(这段内存必须在 0x20000000 + 256KB区间)
* | |
* (256K) | CP-SRAM
* | |
* | | AP-WIFI-RAM (这段内存必须在 0x20000000 + 256KB区间)
* -------------- |
* 0x20040000 | 硬件接在CP核上的SRAM(地址0x20040000总计64K)
* (8K) | IPC共享内存
* (28K) | LUNA CODE & DATA
* -------------- |
* 0x20049000 | 硬件接在AP核上的SRAM(地址0x20050000总计384K)
* (62K) | AP-SRAM
* --------------------------------------------
* 0x20058800 | AP算法使用(HardCode)
* | |
* (350K) | ALGO
* | |
* --------------------------------------------
* 0x200B0000 |
* | |
* (24K) | LUNA 核专属内存
* | |
* --------------------------------------------
* 0x200C0000
*/
/* 独立给算法使用 */
#define MEM_APRAM_BASE 0x20058800
#define MEM_APRAM_SIZE (__KB__(350))
/* 注意,修改以下内存时, 必须与CP保持同步修改 */
#define MEM_CP_WIFI_RAM_BASE __MEM_TOTAL_SRAM_START__
#define MEM_CP_WIFI_RAM_SIZE (__KB__(64) - 128) //暂留128(0x80)字节给WiFi硬件
#define MEM_CP_SRAM_BASE ((MEM_CP_WIFI_RAM_BASE) + (MEM_CP_WIFI_RAM_SIZE))
#define MEM_CP_SRAM_SIZE (__KB__(192))
#define MEM_AP_WIFI_RAM_BASE ((MEM_CP_SRAM_BASE) + (MEM_CP_SRAM_SIZE))
#define MEM_AP_WIFI_RAM_SIZE (__KB__(0))
#define MEM_IPC_BASE ((MEM_AP_WIFI_RAM_BASE) + (MEM_AP_WIFI_RAM_SIZE))
#define MEM_IPC_SIZE (__KB__(8))
#define MEM_LUNA_BASE ((MEM_IPC_BASE) + (MEM_IPC_SIZE))
#define MEM_LUNA_SIZE (__KB__(28))
#define MEM_AP_SRAM_BASE ((MEM_LUNA_BASE) + (MEM_LUNA_SIZE))
#define MEM_AP_SRAM_SIZE (__KB__(62))
#if CONFIG_ARCS_CP_CORE
#define MEM_SRAM_BASE MEM_CP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_CP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_CP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_CP_WIFI_RAM_SIZE
#else
#define MEM_SRAM_BASE MEM_AP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_AP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_AP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_AP_WIFI_RAM_SIZE
#endif
/* 检查下内存是否超出 */
#if (MEM_IPC_BASE + MEM_IPC_SIZE) > __MEM_TOTAL_SRAM_END__
#error "mem sram overflow"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_CP_WIFI_RAM_BASE < 0x20000000) || ((MEM_CP_WIFI_RAM_BASE + MEM_CP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "cp wifi sram error"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_AP_WIFI_RAM_BASE < 0x20000000) || ((MEM_AP_WIFI_RAM_BASE + MEM_AP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "ap wifi sram error"
#endif
/* luna code & instruction & data 必须要在 0x20040000 ~ 0x200B0000 + 448(KB)之间 */
#if (MEM_LUNA_BASE < 0x20040000) || ((MEM_LUNA_BASE + MEM_LUNA_SIZE) > (0x20040000 + __KB__(448)))
#error "ap luna sram error"
#endif
/* 根据实际需要调整 */
#if CONFIG_ARCS_AP_CORE
/* 这个是SRAM HEAP大小的定义, 该heap从SRAM中分配 */
#define SRAM_HEAP_SIZE (__KB__(5))
// /* WIFI校准数据大小 */
// #define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
// #define MEM_WIFI_TRACE_SIZE (__KB__(1))
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
#define MEM_WIFI_LA_DUMP_SIZE (__KB__(32))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#else
/* 在CP侧, 没有wifi的校准数据与跟踪数据, 这里定义为0 */
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(0))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#endif
#endif//__ARCS_MEMAP_HEADER__

View File

@@ -0,0 +1,63 @@
# 使用自定义内存配置文件和自定义链接脚本
CONFIG_MEM_CONFIG=n
CONFIG_MEM_CONFIG_USE_CUSTOM_FILE=y
# CP core
CONFIG_ARCS_CP_CORE=y
# not boot loader
CONFIG_BOOT_HART=n
CONFIG_BOOT=n
# C LIB
CONFIG_LINK_OPTION_NONE_SPECS=y
# HEAP
CONFIG_HEAP_SIZE=0x10000
CONFIG_PSRAM_HEAP_SIZE=0x700000
# CPP
CONFIG_LINK_CPP_RUNTIME_SECTIONS=n
CONFIG_CPP_EXCEPTIONS=n
# Debug
CONFIG_BACK_TRACE=y
# freertos
CONFIG_FREERTOS_STATIC_ALLOCATION_ENABLE=y
# main task
CONFIG_MAIN_TASK_STACK_SIZE=8192
CONFIG_MAIN_TASK_PRIORITY=5
# cJSON
CONFIG_CJSON=y
# IPC
CONFIG_ARCS_HAL_LSF=y
CONFIG_IPC_LSF=y
CONFIG_DISK_MEM=n
# CONFIG_ARCS_HAL_IC_MUTEX=n
# GPDMA
CONFIG_ARCS_GPDMA_FULL_INIT=y
# Audio
CONFIG_LISA_DEVICE=y
CONFIG_LISA_AUDIO_DEVICE=y
CONFIG_LISA_AUDIO_PLAY_ECHO_ENABLE=y
# acomp wakeup
CONFIG_ACOMP=y
CONFIG_ACOMP_WAKEUP=y
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=y
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=2926656
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=1008
# watchdog
CONFIG_BOOT_WITH_WATCHDOG=y
CONFIG_WATCHDOG_ENABLE=n
CONFIG_WORK_QUEUE=y

View File

@@ -0,0 +1,108 @@
小奥同学 xiao ao tong xue
你好小奥 ni hao xiao ao
开浴霸 kai yu ba
关浴霸 guan yu ba
开恒温暖 kai heng wen nuan
关恒温暖 guan heng wen nuan
打开自然风 da kai zi ran feng
关闭自然风 guan bi zi ran feng
风速调到一档 feng su tiao dao yi dang
风速调到二档 feng su tiao dao er dang
风速调到三档 feng su tiao dao san dang
风速调到四档 feng su tiao dao si dang
风速调到五档 feng su tiao dao wu dang
调大风速 tiao da feng su
调小风速 tiao xiao feng su
打开自动换气 da kai zi dong huan qi
关闭自动换气 guan bi zi dong huan qi
换气调到一档 huan qi tiao dao yi dang
换气调到二档 huan qi tiao dao er dang
换气调到三档 huan qi tiao dao san dang
换气调到四档 huan qi tiao dao si dang
换气调到五档 huan qi tiao dao wu dang
调大换气 tiao da huan qi
调小换气 tiao xiao huan qi
开干燥 kai gan zao
关干燥 guan gan zao
打开摆叶 da kai bai ye
暂停摆叶 zan ting bai ye
关闭摆叶 guan bi bai ye
打开浴霸照明 da kai yu ba zhao ming
关闭浴霸照明 guan bi yu ba zhao ming
开启小夜灯 kai qi xiao ye deng
关掉小夜灯 guan diao xiao ye deng
打开浴室灯光 da kai yu shi deng guang
关闭浴室灯光 guan bi yu shi deng guang
开灯 kai deng
关灯 guan deng
打开空气管家 da kai kong qi guan jia
关闭空气管家 guan bi kong qi guan jia
温度调到三十度 wen du tiao dao san shi du
温度调到三十一度 wen du tiao dao san shi yi du
温度调到三十二度 wen du tiao dao san shi er du
温度调到三十三度 wen du tiao dao san shi san du
温度调到三十四度 wen du tiao dao san shi si du
温度调到三十五度 wen du tiao dao san shi wu du
温度调到三十六度 wen du tiao dao san shi liu du
温度调到三十七度 wen du tiao dao san shi qi du
温度调到三十八度 wen du tiao dao san shi ba du
温度调到三十九度 wen du tiao dao san shi jiu du
温度调到四十度 wen du tiao dao si shi du
温度调到四十一度 wen du tiao dao si shi yi du
温度调到四十二度 wen du tiao dao si shi er du
调高温度 tiao gao wen du
调低温度 tiao di wen du
调高音量 tiao gao yin liang
降低音量 jiang di yin liang
打开橙光 da kai cheng guang
打开黄光 da kai huang guang
关闭橙光 guan bi cheng guang
关闭黄光 guan bi huang guang
打开美容光 da kai mei rong guang
关闭美容光 guan bi mei rong guang
定时十分钟 ding shi shi fen zhong
定时二十分钟 ding shi er shi fen zhong
定时三十分钟 ding shi san shi fen zhong
定时四十分钟 ding shi si shi fen zhong
打开低噪换气 da kai di zao huan qi
关闭低噪换气 guan bi di zao huan qi
打开快速换气 da kai kuai su huan qi
关闭快速换气 guan bi kuai su huan qi
打开除菌 da kai chu jun
关闭除菌 guan bi chu jun
打开除异味 da kai chu yi wei
关闭除异味 guan bi chu yi wei
开启热干燥 kai qi re gan zao
关热干燥 guan re gan zao
开启凉干燥 kai qi liang gan zao
关凉干燥 guan liang gan zao
开巡航换气 kai xun hang huan qi
关巡航换气 guan xun hang huan qi
打开巡航取暖 da kai xun hang qu nuan
关闭巡航取暖 guan bi xun hang qu nuan
打开巡航 NBSS da kai xun hang NBSS
关闭巡航 NBSS guan bi xun hang NBSS
开启双区取暖 kai qi shuang qu qu nuan
关双区取暖 guan shuang qu qu nuan
开启湿区取暖 kai qi shi qu qu nuan
关湿区取暖 guan shi qu qu nuan
开干区取暖 kai gan qu qu nuan
打开恒温干燥 da kai heng wen gan zao
关闭恒温干燥 guan bi heng wen gan zao
打开普通干燥 da kai pu tong gan zao
关闭普通干燥 guan bi pu tong gan zao
打开干区吹风 da kai gan qu chui feng
关闭干区吹风 guan bi gan qu chui feng
打开湿区吹风 da kai shi qu chui feng
关闭湿区吹风 guan bi shi qu chui feng
打开亮肤光 da kai liang fu guang
关闭亮肤光 guan bi liang fu guang
打开抗衰光 da kai kang shuai guang
关闭抗衰光 guan bi kang shuai guang
打开修护光 da kai xiu hu guang
关闭修护光 guan bi xiu hu guang
打开舒敏光 da kai shu min guang
关闭舒敏光 guan bi shu min guang
滴 一 声 di yi sheng
滴 二 声 di er sheng
关闭除臭 guan bi chu chou

View File

@@ -0,0 +1 @@
{"wrap":{"dev":"4002","adc":0,"trans":1,"param":[{"key":"wakesoffset","val":160},{"key":"wakeeoffset","val":-160}]},"cae":{"dist":30,"bits":16,"chns":{"mic":2,"ref":2,"out":9},"wkmask":3,"param":[{"key":"mode","val":"0"},{"key":"textdep","val":1},{"key":"16386","val":100},{"key":"16387","val":150},{"key":"32770","val":0},{"key":"8","val":0},{"key":"9","val":0}]},"esr":{"param":[{"key":"prewstat","val":2},{"key":"prewthr","val":400},{"key":"mode","val":1},{"key":"model","val":0},{"key":"1105","val":0},{"key":"1107","val":0},{"key":"1138","val":1},{"key":"1106","val":0},{"key":"2119","val":0},{"key":"1124","val":1},{"key":"1180","val":2},{"key":"1126","val":63},{"key":"1125","val":10},{"key":"1118","val":8},{"key":"1119","val":40},{"key":"1130","val":5},{"key":"1131","val":40},{"key":"1132","val":5},{"key":"1133","val":8},{"key":"1134","val":-1},{"key":"1136","val":0},{"key":"1408","val":2},{"key":"1409","val":4},{"key":"1129","val":8}],"switchparam":[{"key":"2118","val_main":500,"val_cmd":0}]}}

Binary file not shown.

View File

@@ -0,0 +1,9 @@
tests:
samples.algorithms.wakeup:
programmer: arcs
runner: uart
build_only: true
log_analyzer:
type: "regex"
pattern: "相位补偿已设置"

View File

@@ -0,0 +1,88 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "cJSON.h"
#include "acomp_wakeup.h"
#include "wakeup_audio_in.h"
#include "wakeup_audio_out.h"
#define TAG "wakeup"
#include "lisa_log.h"
static int app_algo_keyword_and_kid_extract(const uint8_t *const in)
{
int ret = -1;
int len = strlen(in);
cJSON *root = NULL;
if ((root = cJSON_Parse(in))) {
cJSON *item = cJSON_GetObjectItem(root, "rlt")->child;
uint16_t wkkey = cJSON_GetObjectItem(item, "iresid")->valueint;
uint16_t wkncm = cJSON_GetObjectItem(item, "ncm")->valueint;
char *wkcmd = cJSON_GetObjectItem(item, "keyword")->valuestring;
int wkmain = -1;
if (cJSON_HasObjectItem(item, "bMain")) {
wkmain = cJSON_GetObjectItem(item, "bMain")->valueint;
}
LISA_LOGI(TAG, "WAKE(%d): KEY=%d(%s), NCM=%d, len:%d", wkmain, wkkey, wkcmd, wkncm, len);
cJSON_Delete(root);
} else {
LISA_LOGE(TAG, "JSON:\"%s\"", in);
}
return ret;
}
static void wakeup_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
int ret;
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
/* 算法识别结果 */
LISA_LOGD(TAG, "wakeup result:%d,%s", event_data_len, (char *)event_data);
app_algo_keyword_and_kid_extract((const uint8_t *)event_data);
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
/* 算法唤醒超时 */
LISA_LOGI(TAG, "wakeup timeout");
} else if (event & WAKEUP_CB_EVENT_ENGINE_ANGLE) {
/* 算法识别角度 */
for (int i = 0; i < event_data_len/sizeof(short); i++) {
short angle = *((short*)event_data + i);
LISA_LOGD(TAG, "wakeup angle[%d]: %d", i, angle);
}
} else {
LISA_LOGW(TAG, "unknown wakeup event:0X%X", event);
}
}
int app_wakeup_init(void)
{
int ret = 0;
acomp_wakeup_init();
acomp_wakeup_add_callback(WAKEUP_CB_EVENT_ENGINE_RLT
| WAKEUP_CB_EVENT_ENGINE_TIMEOUT
| WAKEUP_CB_EVENT_ENGINE_ANGLE,
wakeup_event_handler, NULL);
ret = acomp_wakeup_prepare();
LISA_LOGI(TAG, "acomp_wakeup_prepare ret:%d", ret);
ret = acomp_wakeup_start();
LISA_LOGI(TAG, "acomp_wakeup_start ret:%d", ret);
ret = acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_WAKEUP);
LISA_LOGI(TAG, "acomp_wakeup_set_algo_mode ret:%d", ret);
wakeup_audio_out_init();
wakeup_audio_in_init();
return ret;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int app_wakeup_init(void);

View File

@@ -0,0 +1,235 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "workqueue.h"
#include "lisa_device.h"
#include "lisa_audio.h"
#include "acomp_wakeup.h"
#define TAG "wakeup_in"
#include "lisa_log.h"
#define AUDIO_DEVICE_NAME "audio0"
/* 音频参数配置 - 单声道(左声道) */
#define SAMPLE_RATE LISA_AUDIO_RATE_16K
#define SAMPLE_BITS LISA_AUDIO_BIT_16
#define RECORD_CHANNELS LISA_AUDIO_CH_STEREO
#define PLAY_CHANNELS LISA_AUDIO_CH_LEFT
#define BUFFER_COUNT 12
#define BUFFER_SAMPLES ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT
/* Record 增益配置 */
#define RECORD_ANALOG_GAIN 30 /* 16 dB */
#define RECORD_DIGITAL_GAIN 0 /* 8 dB */
/* Play 增益配置 */
#define PLAY_ANALOG_GAIN 0 /* 6 dB */
#define PLAY_DIGITAL_GAIN -18
/* wakeup audio in stream */
#define WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX (0)
#define WAKEUP_AUDIO_MIX2CH_STREAM_CH_CNAME "stream.mix2ch"
#define WAKEUP_AUDIO_MIX2CH_STREAM_BUF_SIZE (ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONCE_FRAME)
typedef struct {
short mic0;
short mic1;
}mic_in_t;
typedef struct {
short ref;
}ref_in_t;
typedef struct {
mic_in_t *mic;
ref_in_t *ref;
uint32_t sample_cnt;
}wakeup_in_msg_t;
static workqueue_t *wakeup_in_wq = NULL;
static short zero_echo[BUFFER_SAMPLES] = {0};
static mic_in_t record_zero_echo[BUFFER_SAMPLES] = {0};
static void stream_data_fusion(acomp_wakeup_audio_in_t *algo_buf_in, mic_in_t *adc_data, ref_in_t *ref_data, int sample_cnt)
{
for (int i = 0; i < sample_cnt; i++) {
// 双麦软回采
algo_buf_in[i].mic0 = adc_data[i].mic0;
algo_buf_in[i].mic1 = adc_data[i].mic1;
algo_buf_in[i].ref0 = ref_data[i].ref;
algo_buf_in[i].ref1 = ref_data[i].ref;
}
}
static void wakeup_in_wq_handler(void *para)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
wakeup_in_msg_t *msg = (wakeup_in_msg_t*)para;
LISA_LOGD(LOG_TAG, "wakeup_in_wq_handler, sample_cnt: %u", msg->sample_cnt);
buffer = acomp_wakeup_stream_tx_buffer_alloc(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, &buf_size, &desc_idx);
if(buffer && buf_size > 0){
stream_data_fusion((acomp_wakeup_audio_in_t*)buffer, msg->mic, msg->ref, msg->sample_cnt);
acomp_wakeup_stream_tx_buffer_submit(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, buffer, buf_size, desc_idx);
}
psram_free(msg);
}
/**
* @brief Unified audio callback handler - 仅收集数据
*/
static void unified_audio_callback(const lisa_audio_event_t *event, void *user_data)
{
bool has_record = (event->record_buffer != NULL);
bool has_echo = (event->echo_buffer != NULL);
if (has_record) {
LISA_LOGD(LOG_TAG, "unified_audio_callback, record_samples: %d", event->record_samples);
}
if (has_echo) {
LISA_LOGD(LOG_TAG, "unified_audio_callback, echo_samples: %d", event->echo_samples);
}
wakeup_in_msg_t *msg = psram_malloc(sizeof(wakeup_in_msg_t));
#if 1
msg->mic = (mic_in_t*)event->record_buffer;
msg->ref = (ref_in_t*)(event->echo_buffer ? event->echo_buffer : zero_echo);
msg->sample_cnt = ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT;
#else
msg->mic = (mic_in_t*)record_zero_echo;
msg->ref = (ref_in_t*)zero_echo;
msg->sample_cnt = 256;
#endif
workqueue_submit(wakeup_in_wq, wakeup_in_wq_handler, msg);
}
static int audio_device_init(void)
{
int ret = 0;
/* 初始化GPDMAlisa_audio会用到 */
GPDMA_Initialize();
/* 获取 Audio 设备 */
lisa_device_t *audio_dev = lisa_device_get(AUDIO_DEVICE_NAME);
if (!audio_dev) {
LISA_LOGE(LOG_TAG, "获取 Audio 设备失败");
return -1;
}
LISA_LOGI(LOG_TAG, "Audio 设备获取成功");
/* 注册统一回调函数 */
ret = lisa_audio_register_callback(audio_dev, unified_audio_callback, NULL);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "注册统一回调失败: %d", ret);
return -1;
}
LISA_LOGI(LOG_TAG, "统一回调注册成功");
/* 统一配置 Record */
lisa_audio_record_config_t record_config = {
.format = { .sample_rate = SAMPLE_RATE, .channels = RECORD_CHANNELS, .sample_bits = SAMPLE_BITS },
.gain = { .analog_gain = RECORD_ANALOG_GAIN, .digital_gain = RECORD_DIGITAL_GAIN },
.differential_input = true,
.enable_hpf = false,
};
ret = lisa_audio_record_config(audio_dev, &record_config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Record 配置失败: %d", ret);
goto cleanup;
}
/* 统一配置 Play */
lisa_audio_play_config_t play_config = {
.format = { .sample_rate = SAMPLE_RATE, .channels = PLAY_CHANNELS, .sample_bits = SAMPLE_BITS },
.gain = { .analog_gain = PLAY_ANALOG_GAIN, .digital_gain = PLAY_DIGITAL_GAIN },
.buffer_count = BUFFER_COUNT,
.buffer_samples = BUFFER_SAMPLES,
};
ret = lisa_audio_play_config(audio_dev, &play_config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Play 配置失败: %d", ret);
goto cleanup;
}
/* 统一启动录音和播音 */
ret = lisa_audio_record_start(audio_dev);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "启动录音失败: %d", ret);
goto cleanup;
}
LISA_LOGI(LOG_TAG, "录音已启动");
ret = lisa_audio_play_start(audio_dev);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "启动播音失败: %d", ret);
lisa_audio_record_stop(audio_dev);
goto cleanup;
}
LISA_LOGI(LOG_TAG, "播音已启动");
/* 设置相位补偿 */
lisa_audio_phase_compensation_t phase_comp = {
.record_skip_samples = 10,
.echo_skip_samples = 0,
};
ret = lisa_audio_set_phase_compensation(audio_dev, &phase_comp);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "设置相位补偿失败: %d", ret);
return ret;
}
LISA_LOGI(LOG_TAG, "相位补偿已设置");
return 0;
cleanup:
lisa_audio_record_stop(audio_dev);
lisa_audio_play_stop(audio_dev);
vTaskDelay(pdMS_TO_TICKS(200)); // 等待资源释放
/* 注销统一回调 */
if (audio_dev) {
lisa_audio_unregister_callback(audio_dev, unified_audio_callback);
LISA_LOGI(LOG_TAG, "统一回调已注销");
}
return ret;
}
int wakeup_audio_in_init(void)
{
int ret = 0;
acomp_stream_chn_create_desc_t desc = {
.cname = WAKEUP_AUDIO_MIX2CH_STREAM_CH_CNAME,
.direction = ACOMP_STREAM_DIRECTION_M2R,
.index = WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX,
.buffer_size = WAKEUP_AUDIO_MIX2CH_STREAM_BUF_SIZE,
.num_descs = 4,
.kick_policy = 1,
};
acomp_wakeup_stream_ch_enable(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX,&desc);
wakeup_in_wq = workqueue_create("wakeup_in_wq", 9, 10, 8096);
if (wakeup_in_wq == NULL) {
LISA_LOGE(TAG, "Failed to create wakeup_in_wq");
}
audio_device_init();
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int wakeup_audio_in_init(void);

View File

@@ -0,0 +1,108 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "acomp_wakeup.h"
#define TAG "wakeup_out"
#include "lisa_log.h"
#define WAKEUP_AUDIO_OUT_STREAM_CH_INDEX (1)
#define WAKEUP_AUDIO_OUT_STREAM_CH_CNAME "stream.tocloud"
#define WAKEUP_AUDIO_OUT_STREAM (1)
#define WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO (1)
static SemaphoreHandle_t rx_sem;
static void wakeup_stream_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
/* 算法音频输出事件 */
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
if (rx_sem != NULL) {
xSemaphoreGive(rx_sem);
}
#endif
}
}
static void wakeup_out_task(void *pvParameters)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
int ret;
while (1) {
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
/* 自动触发模式:等待来自回调的信号量 */
xSemaphoreTake(rx_sem, pdMS_TO_TICKS(50));
#else
/* 手动触发模式:等待超时时间 */
vTaskDelay(pdMS_TO_TICKS(5));
#endif
/* 从 R2M 流中获取数据 */
while (1) {
buffer = acomp_wakeup_stream_rx_buffer_get(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, &len, &desc_idx);
if (buffer != NULL && len > 0) {
LISA_LOGD(TAG, "acomp_wakeup_stream_rx_buffer_get ok");
// process_output_audio(buffer, len);
/* 释放缓冲区 */
ret = acomp_wakeup_stream_rx_buffer_release(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, desc_idx, len, buffer);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to release buffer: %d", ret);
}
} else {
/* 没有更多数据,跳出内层循环 */
break;
}
}
}
vTaskDelete(NULL);
}
int wakeup_audio_out_init(void)
{
int ret;
acomp_stream_chn_create_desc_t rx_desc = {
.cname = WAKEUP_AUDIO_OUT_STREAM_CH_CNAME,
.direction = ACOMP_STREAM_DIRECTION_R2M,
.index = WAKEUP_AUDIO_OUT_STREAM_CH_INDEX,
.buffer_size = ACOMP_WAKEUP_AUDIO_OUTPUT_MAX_LEN_ONCE_FRAME,
.num_descs = 8,
#if WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
.kick_policy = 1, /* 手动触发 */
#else
.kick_policy = 0, /* 手动触发 */
#endif
};
#if WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
rx_sem = xSemaphoreCreateBinary();
if (rx_sem == NULL) {
LISA_LOGE(TAG, "Failed to create rx semaphore");
}
#endif
acomp_wakeup_stream_ch_enable(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX,&rx_desc);
acomp_wakeup_add_callback(WAKEUP_CB_EVENT_STREAM_UPDATE, wakeup_stream_handler, NULL);
ret = xTaskCreate(wakeup_out_task, "wakeup_out_task", 4096, NULL, 9, NULL);
if (ret != pdPASS) {
LISA_LOGE(TAG, "Failed to create wakeup_out_task task");
}
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int wakeup_audio_out_init(void);

View File

@@ -0,0 +1,31 @@
#include "stdio.h"
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "ic_message.h"
#include "acomp.h"
#include "app_wakeup.h"
#define TAG "main"
#include "lisa_log.h"
int main(int argc, char **argv)
{
int ret = 0;
LISA_LOGI(TAG, "CP=======! Hard ID: %d", CONFIG_HARTID);
ic_message_init();
LISA_LOGI(TAG, "ic_message_init done!");
vTaskDelay(pdMS_TO_TICKS(2000));
acomp_init();
app_wakeup_init();
return 0;
}

View File

@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(listenai-cmake REQUIRED HINTS $ENV{ARCS_BASE})
project(arcs)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/app_wakeup)
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/workqueue)
listenai_add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PRIVATE src/main.c
PRIVATE src/app_wakeup/app_wakeup.c
PRIVATE src/app_wakeup/wakeup_audio_in.c
PRIVATE src/app_wakeup/wakeup_audio_out.c
# PRIVATE src/workqueue/workqueue.c
)

View File

@@ -0,0 +1 @@
osource "$ARCS_BASE/Kconfig"

View File

@@ -0,0 +1,456 @@
# 单麦唤醒算法示例
## 功能说明
演示如何使用 ACOMP 唤醒算法组件实现语音唤醒功能,包括音频数据采集、回采处理、唤醒检测和结果输出。
本示例展示了单麦克风的唤醒算法应用,支持关键词识别、超时处理以及算法回声消除音频输出等完整的唤醒流程。
用户可以通过语音唤醒算法,算法检测到唤醒词或命令词后,会输出识别结果。
## 硬件连接
- **音频输入**: 单麦克风输入(MIC1),用ADC采样采样率 16kHz
- **音频输出**: DAC单声道播放输出,同时软回采参考信号,采样率 16kHz
- **调试串口**: CP核的UART0,PA3引脚波特率 921600,用于日志输出
- **调试串口**: AP核的UART2,PA4引脚波特率 921600,用于日志输出
## 示例内容
1. 初始化 ACOMP 唤醒算法引擎
2. 配置音频输入输出设备(单通道录音: mic0 + ref0(软回采) + 单声道播放)
3. 创建音频数据流通道(M2R 和 R2M)
4. 采集音频数据并融合麦克风和回采信号(录音第二路作为参考回采)
5. 将融合后的音频数据送入唤醒算法引擎
6. 处理唤醒结果(关键词、角度、超时等事件)
7. 输出回声消除后的音频数据流
## 编译
```{eval-rst}
.. include:: /sample_build.rst
```
### 烧录固件
```bash
# 烧录 AP 核固件Boot Core
# 烧录地址0x30000000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x00 ./res/ap.bin
# 烧录算法资源到 0x30200000 和 0x304d0000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x200000 ./res/algo/algo.bin
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x4d0000 ./res/algo/wrap.json
# 烧录 CP 核固件
# 烧录地址0x30050000
cskburn -s /dev/ttyACM0 -b 3000000 -C arcs 0x500000 ./build/arcs.bin
```
## 预期输出
### CP日志
**初始化阶段:**
```
********Arcs SDK 0.0.22 @ v0.0.23.temp.docs-41-g718db8e869c5********
Running on hart-id: 1
I/elog [341:39:56.907 1 elog_async] EasyLogger V2.2.99 is initialize success.
I/lisa_audio_record [341:39:56.907 1 audio_dispatch] LISA Audio Record initialized
I/lisa_audio_play [341:39:56.907 1 audio_dispatch] LISA Audio Play initialized
I/main [341:39:56.908 1 main] CP=======! Hard ID: 1
I/main [341:39:56.909 1 main] ic_message_init done!
I/acomp_ipc [341:39:58.910 1 rpc_client] [0]acomp remote dev index 1 name acomp.wakeup
I/acomp_wakeup [341:39:58.910 1 main] acomp wakeup init enter
I/acomp_wakeup [341:39:58.910 1 main] acomp wakeup dev index 1,name:acomp.wakeup
I/acomp_wakeup [341:39:58.912 1 main] acomp wakeup init exit
I/acomp_wakeup [341:39:58.912 1 main] acomp wakeup prepare enter
I/acomp_wakeup [341:39:58.913 1 main] acomp wakeup prepare exit
I/wakeup [341:39:58.913 1 main] acomp_wakeup_prepare ret:0
I/acomp_wakeup [341:39:58.913 1 main] acomp wakeup start enter
I/acomp_wakeup [341:39:59.482 1 main] acomp wakeup start exit
I/wakeup [341:39:59.482 1 main] acomp_wakeup_start ret:0
I/acomp_wakeup [341:39:59.483 1 main] acomp wakeup set algo mode enter, mode=0
I/acomp_wakeup [341:39:59.484 1 main] acomp wakeup set algo mode exit
I/wakeup [341:39:59.484 1 main] acomp_wakeup_set_algo_mode ret:0
INF:[acomp_stream_channel_create]Acomp stream channel 0x2883f754,0x20015b50,'stream.tocloud' created successfully ,vring phy addr=0x2880d660, num_descs=8, buffer_size=25600, direction=R2M, role=Master
I/acomp_wakeup [341:39:59.488 1 main] acomp_wakeup_stream_ch_enable chn(stream.tocloud) index(1),desc(0x2880cd08)
INF:[acomp_stream_channel_create]Acomp stream channel 0x28844834,0x20015c38,'stream.mix2ch' created successfully ,vring phy addr=0x288437a0, num_descs=4, buffer_size=1024, direction=M2R, role=Master
I/acomp_wakeup [341:39:59.490 1 main] acomp_wakeup_stream_ch_enable chn(stream.mix2ch) index(0),desc(0x2880ccf8)
I/wakeup_in [341:39:59.491 1 main] Audio 设备获取成功
I/wakeup_in [341:39:59.491 1 main] 统一回调注册成功
I/lisa_audio_record [341:39:59.491 1 main] Record configured: rate=16000, gain=30/0 dB
I/lisa_audio_play [341:39:59.492 1 main] Play configured: rate=16000, gain=0/-18 dB, buffers=12×256
I/lisa_audio_record [341:39:59.492 1 main] Record started
I/wakeup_in [341:39:59.492 1 main] 录音已启动
I/lisa_audio_play [341:39:59.492 1 main] Play started
I/wakeup_in [341:39:59.493 1 main] 播音已启动
I/wakeup_in [341:39:59.493 1 main] 相位补偿已设置
```
**唤醒检测成功:**
- 用户可以用语音来唤醒算法,算法检测到唤醒词或命令词后,会输出识别结果
- 唤醒词:小聆小聆
- 命令词:无
> 注意: 算法需要被唤醒后,才可以识别命令词
```
I/wakeup [341:40:05.778 1 rpc_client] WAKE(1): KEY=0(xiao3 ling2 xiao3 ling2), NCM=157, len:371
```
**唤醒超时:**
算法需要被唤醒后,才可以识别命令词,如长时间没有识别到命令词,会触发超时事件,用户需重新唤醒
```
I/wakeup [341:40:44.745 1 rpc_client] wakeup timeout
```
### AP日志
```
********Arcs SDK 0.0.22 @ v0.0.23.temp.docs-41-g718db8e869c5********
Running on hart-id: 0
I/elog [00:00:00.039 0 elog_async] EasyLogger V2.2.99 is initialize success.
AP Hard ID: 0
boot cp from address: 0x30500000
luna_version:0x3000200
I/wakeup [00:00:00.055 0 main] sys_acomp_wakeup_init
I/components [00:00:00.056 0 main] Reserved driver ID: 1
I/components [00:00:00.056 0 main] acomp register driver index:1,name:acomp.wakeup
INF:[acomp_context_init 257]components context init success
I/wakeup [00:00:02.056 0 rpc_async_serv] wakeup_create enter
I/wakeup [00:00:02.056 0 rpc_async_serv] wakeup_create exit
I/wakeup [00:00:02.058 0 acomp_workqueue] wakeup_prepare enter
I/wakeup [00:00:02.058 0 acomp_workqueue] wakeup_prepare exit
I/wakeup [00:00:02.059 0 acomp_workqueue] wakeup_start enter
I/wakeup_algo [00:00:02.059 0 acomp_workqueue] cae_esr_mlp Resource, addr=0x30200000, size=1431296
I/wakeup_algo [00:00:02.059 0 acomp_workqueue] Wrap json, addr=0x304d0000, size=763
JSON content (763 chars):
{"wrap":{"dev":"4002","trans":0,"param":[{"key":"wakesoffset","val":160},{"key":"wakeeoffset","val":-160}]},"cae":{"dist":30,"bits":16,"chns":{"mic":1,"ref":1},"wkmask":3,"param":[{"key":"mode","val":"0"},{"key":"textdep","val":1}]},"esr":{"param":[{"key":"prewstat","val":5},{"key":"prewthr","val":10000},{"key":"mode","val":1},{"key":"model","val":0},{"key":"1305","val":1},{"key":"1106","val":0},{"key":"2119","val":0},{"key":"1124","val":1},{"key":"1180","val":2},{"key":"1126","val":63},{"key":"1125","val":10},{"key":"1105","val":2},{"key":"1118","val":8},{"key":"1119","val":40},{"key":"1130","val":5},{"key":"1131","val":8},{"key":"1136","val":1}],"switchparam":[{"key":"2118","val_main":[500],"val_cmd":[0]},{"key":"1107","val_main":[2],"val_cmd":[2]}]}}
I/wakeup_algo [00:00:02.069 0 acomp_workqueue]
ai_mem_t memap: json: 0x304d0000, psram: 0x0, size: 0, res: 0x30200000, size: 1431296
ai_get_info ret=0, len=790
info content (790 chars):
{"wrap":{"version":"600x.aiwrap.v1000.1.0.1 beta arcs_sp (Aug 12 2025 18:47:23)","inst_size":8608,"share_size":2560,"samples":{"in":256,"out":1280}},"cae":{"version":"arcs.cae.v1000.0.0.1 beta (Aug 12 2025 18:04:12)","memory":{"inst_ram_size":29184,"temp_ram_size":51200,"psram_size":0},"feature":{"mic_num":1,"ref_num":1,"local_out_channels":1,"aec_out_channels":0,"asr_out_channels":0,"nbit":16,"nsample":16000,"frame_num":5,"frame_size":256,"support_pre_wk_info":0,"support_wk_info":0}},"esr":{"version":"mini-esr-ARCS Tag5.1.2.1.2.5_26,Aug 12 2025,10:17:16","memory":{"share_mem_size":58848,"inst_mem_size":60288},"feature":{"input_pcm_channel":1,"input_pcm_bit":16,"input_pcm_frame_count":8,"input_pcm_sample_rate":16000,"max_main_word_count":1,"max_asr_word_count":1,"alog_type":26}}}
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] wrap_size: 8608
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] cae_psram: 0
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] inst_ram_size: 29184
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] temp_ram_size: 51200
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] esr_share_mem: 58848
I/wakeup_algo [00:00:02.206 0 acomp_workqueue] esr_psram: 60288
I/wakeup_algo [00:00:02.207 0 acomp_workqueue] Memory requirements - psram_size: 68928, share_size: 88064
I/wakeup_algo [00:00:02.207 0 acomp_workqueue] algo_psram_buffer allocated at 0x28024e80, size=68928
Resource: mlp.bin,addr: 0x28035bc0, size: 1223424, compress: 0
Resource: main.bin,addr: 0x281606c0, size: 912, compress: 0
Resource: cmds.bin,addr: 0x28160a60, size: 6048, compress: 0
Resource: cae_ffw0_100.json,addr: 0x28162200, size: 141, compress: 0
Resource: cae_aes.bin,addr: 0x281622a0, size: 200000, compress: 0
I/wakeup_algo [00:00:02.625 0 acomp_workqueue] ai_create success(used: psram=0x10d00, share=0x157e0)
I/wakeup_algo [00:00:02.626 0 acomp_workqueue] esrstag: 1
I/wakeup_algo [00:00:02.626 0 acomp_workqueue] algo_set_mode(0)
I/wakeup_algo [00:00:02.627 0 acomp_workqueue] MODE=0
I/wakeup [00:00:02.627 0 acomp_workqueue] wakeup_start exit
I/wakeup [00:00:02.628 0 acomp_workqueue] wakeup_control enter
I/wakeup [00:00:02.629 0 acomp_workqueue] wakeup_control: WAKEUP_IPC_CONTROL_SUBCMD_ALGO_MODE_SET
I/wakeup [00:00:02.629 0 acomp_workqueue] Set algo mode: 0
I/wakeup_algo [00:00:02.629 0 acomp_workqueue] Algo mode set to: WAKEUP
INF:[acomp_stream_channel_create]Acomp stream channel 0x28004a20,0x20051d34,'stream.tocloud' created successfully ,vring phy addr=0x2880d660, num_descs=8, buffer_size=25600, direction=R2M, role=Remote
INF:[acomp_stream_channel_create]Acomp stream channel 0x28004a50,0x20051da0,'stream.mix2ch' created successfully ,vring phy addr=0x288437a0, num_descs=4, buffer_size=1024, direction=M2R, role=Remote
I/wakeup_algo [00:00:04.307 0 acomp_workqueue] counter_avg=119.07(M/S), cnt_part=190512871
I/wakeup_algo [00:00:05.907 0 acomp_workqueue] counter_avg=125.59(M/S), cnt_part=200938084
I/wakeup_algo [00:00:07.507 0 acomp_workqueue] counter_avg=125.71(M/S), cnt_part=201138800
I/wakeup_algo [00:00:09.107 0 acomp_workqueue] counter_avg=125.68(M/S), cnt_part=201081353
```
## 核心 API
| API | 说明 |
|-----|------|
| `acomp_wakeup_init()` | 初始化唤醒算法组件 |
| `acomp_wakeup_prepare()` | 准备唤醒算法引擎 |
| `acomp_wakeup_start()` | 启动唤醒算法引擎 |
| `acomp_wakeup_set_algo_mode()` | 设置算法模式(唤醒模式) |
| `acomp_wakeup_add_callback()` | 注册唤醒事件回调函数 |
| `acomp_wakeup_stream_ch_enable()` | 使能音频数据流通道 |
| `acomp_wakeup_stream_tx_buffer_alloc()` | 分配发送缓冲区(M2R) |
| `acomp_wakeup_stream_tx_buffer_submit()` | 提交发送缓冲区 |
| `acomp_wakeup_stream_rx_buffer_get()` | 获取接收缓冲区(R2M) |
| `acomp_wakeup_stream_rx_buffer_release()` | 释放接收缓冲区 |
| `lisa_audio_register_callback()` | 注册音频设备回调 |
| `lisa_audio_record_config()` | 配置录音参数 |
| `lisa_audio_play_config()` | 配置播放参数 |
| `lisa_audio_record_start()` | 启动录音 |
| `lisa_audio_play_start()` | 启动播放 |
## 关键代码
### 唤醒算法事件回调
```c
static void wakeup_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
/* 唤醒结果 */
LISA_LOGI(TAG, "wakeup result:%d,%s", event_data_len, (char *)event_data);
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
/* 唤醒超时 */
LISA_LOGI(TAG, "wakeup timeout");
} else if (event & WAKEUP_CB_EVENT_ENGINE_ANGLE) {
/* 角度信息 */
for (int i = 0; i < event_data_len/sizeof(short); i++) {
short angle = *((short*)event_data + i);
LISA_LOGD(TAG, "wakeup angle[%d]: %d", i, angle);
}
}
}
static void wakeup_stream_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
/* 算法音频输出事件 */
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
if (rx_sem != NULL) {
xSemaphoreGive(rx_sem);
}
#endif
}
}
```
### 获取麦克风和回采的音频
```c
/* 音频回调函数 - 收集麦克风和回采数据 */
static void unified_audio_callback(const lisa_audio_event_t *event, void *user_data)
{
wakeup_in_msg_t *msg = psram_malloc(sizeof(wakeup_in_msg_t));
msg->mic = (mic_in_t*)event->record_buffer;
msg->ref = (ref_in_t*)(event->echo_buffer ? event->echo_buffer : zero_echo);
msg->sample_cnt = event->record_samples;
/* 提交到工作队列处理 */
workqueue_submit(wakeup_in_wq, wakeup_in_wq_handler, msg);
}
```
### 麦克风和回采的音频融合后送入算法
```c
static void wakeup_in_wq_handler(void *para)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
wakeup_in_msg_t *msg = (wakeup_in_msg_t*)para;
LISA_LOGD(LOG_TAG, "wakeup_in_wq_handler, sample_cnt: %u", msg->sample_cnt);
buffer = acomp_wakeup_stream_tx_buffer_alloc(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, &buf_size, &desc_idx);
if(buffer && buf_size > 0){
stream_data_fusion((acomp_wakeup_audio_in_t*)buffer, msg->mic, msg->ref, msg->sample_cnt);
acomp_wakeup_stream_tx_buffer_submit(WAKEUP_AUDIO_MIX2CH_STREAM_CH_INDEX, buffer, buf_size, desc_idx);
}
psram_free(msg);
}
```
### 用户对算法输出的音频进行处理
用户可以获取算法输出的音频数据,进行处理,如发送算法输出的回声消除的音频到云端进行大模型意图识别
```c
static void wakeup_out_task(void *pvParameters)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
int ret;
while (1) {
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
/* 自动触发模式:等待来自回调的信号量 */
xSemaphoreTake(rx_sem, pdMS_TO_TICKS(50));
#else
/* 手动触发模式:等待超时时间 */
vTaskDelay(pdMS_TO_TICKS(5));
#endif
/* 从 R2M 流中获取数据 */
while (1) {
buffer = acomp_wakeup_stream_rx_buffer_get(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, &len, &desc_idx);
if (buffer != NULL && len > 0) {
LISA_LOGD(TAG, "acomp_wakeup_stream_rx_buffer_get ok");
// 2. 处理接收到的数据
// 数据格式为 acomp_wakeup_audio_out_t 数组5通道交织
// {mic0, mic1, ref0, out1, out2} * N 个采样点
// 其中 out1 为回声消除后的音频,可用于云端识别
// process_output_audio(buffer, len);
/* 释放缓冲区 */
ret = acomp_wakeup_stream_rx_buffer_release(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, desc_idx, len, buffer);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to release buffer: %d", ret);
}
} else {
/* 没有更多数据,跳出内层循环 */
break;
}
}
}
}
```
## 算法模式说明
### 单麦克风模式 (CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=n)
- **输入通道**: 1 个麦克风 + 1 个回采参考
- **数据格式**: 2 通道交织(mic1, ref1)
- **特点**: 支持波束成形和角度检测,唤醒性能更好
### 音频参数配置
```c
#define SAMPLE_RATE LISA_AUDIO_RATE_16K /* 采样率 16kHz */
#define SAMPLE_BITS LISA_AUDIO_BIT_16 /* 采样位宽 16bit */
#define RECORD_CHANNELS LISA_AUDIO_CH_STEREO /* 录音双声道 */
#define PLAY_CHANNELS LISA_AUDIO_CH_LEFT /* 播放单声道 */
#define BUFFER_SAMPLES ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT /* 缓冲区采样点数 */
```
### 增益配置
```c
#define RECORD_ANALOG_GAIN 30 /* 录音模拟增益 16dB */
#define RECORD_DIGITAL_GAIN 0 /* 录音数字增益 8dB */
#define PLAY_ANALOG_GAIN 0 /* 播放模拟增益 6dB */
#define PLAY_DIGITAL_GAIN -18 /* 播放数字增益 -18dB */
```
### 算法资源配置
在 `prj.conf` 中配置算法资源地址和大小:
```
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=n
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=1431296
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=763
```
### 堆内存配置
```
CONFIG_HEAP_SIZE=0x10000 /* 内部 SRAM 堆 64KB */
CONFIG_PSRAM_HEAP_SIZE=0x700000 /* PSRAM 堆 7MB */
```
## 数据流说明
### M2R 流(Master to Remote)
- **通道索引**: 0
- **通道名称**: "stream.mi x 1ch"
- **方向**: 应用核(CP) → 算法核(AP)
- **数据**: 融合后的麦克风和回采数据(输入给算法)
- **缓冲区大小**: 一次1024 字节(256 采样点 × 2 通道 × 2 字节)
### R2M 流(Remote to Master)
- **通道索引**: 1
- **通道名称**: "stream.tocloud"
- **方向**: 算法核(AP) → 应用核(CP)
- **数据**: 算法处理后的音频数据(3 通道)(mic1, ref, 回声消除后的音频(可用于意图识别), 算法后端输入音频)
- **缓冲区大小**: 一次输出 4帧 或6帧
## 算法内存占用
单麦算法内存占用如下(粗略统计) 详细内存分布见`memap.h`文件
| MCU核心 | 内存类型 | 大小 | 备注 |
|---------|----------|----|-----|
| AP | FLASH | 1674KB | 2个算法资源 + ap固件大小 + cp固件大小 |
| AP | PSRAM | 1550KB | 算法实例用的PSRAM + 2个算法资源拷贝到PSRAM上 + 动态内存 |
| AP | SRAM | 8KB | IPC共享内存 |
| AP | SRAM | 62KB | AP的SRAM |
| AP | SRAM | 290KB | 算法实例大小 |
| AP | SRAM | 28KB | LUNA的*(.sharedmem.*) |
| AP | LUNASRAM | 64KB | LUNA专用的SRAM大小 |

View File

@@ -0,0 +1,177 @@
#!/bin/bash
set -e
usage() {
echo "使用方式: $0 [选项]"
echo "选项:"
echo " -S, --Source <path> 指定项目源码路径 (默认为当前脚本所在目录)"
echo " -t, --target <target> 指定构建目标 (如 menuconfig)"
echo " -C, --Clean 清理构建目录"
echo " -B, --build 构建输出目录"
echo " -h, --help 显示此帮助信息"
echo " -r, --release 以 Release 模式构建 (移除 DEBUG_PATH 信息)"
echo " -w, --warnings-as-errors 将警告视为错误"
echo " -D<var>=<value> 传递 CMake 变量 (可多次使用)"
echo ""
echo "示例:"
echo " $0 -S samples/helloworld -DBOARD=arcs_mini 指定板型构建"
echo " $0 -S samples/helloworld -DBOARD=arcs_evb 使用 EVB 板型"
echo " $0 -S samples/helloworld -t menuconfig -DBOARD=arcs_mini 运行 menuconfig"
echo " $0 -C -S samples/helloworld -DBOARD=arcs_mini 清理并重新构建"
echo " $0 -S samples/helloworld -DBOARD=my_board -DBOARD_SEARCH_PATH=/path/to/boards 使用自定义板型"
exit 1
}
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_PATH="$SCRIPT_DIR"
TARGET=""
CLEAN=false
OUTPUT="build"
WARNINGS_AS_ERRORS=false
RELEASE=false
ARCS_BASE_DIR_NAME="arcs-sdk"
ARCS_DEV_TOOLS_DIR_NAME="listenai-dev-tools"
ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME="gcc"
ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME="listenai-tools"
find_arcs_base() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_BASE_DIR_NAME"
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found ARCS_BASE: $current_dir/$dir_name"
export ARCS_BASE="$current_dir/$dir_name"
return 0
fi
current_dir=$(dirname "$current_dir")
done
echo "ARCS_BASE not found, Please add ARCS_BASE environment variable or set ARCS_BASE_DIR_NAME to the correct directory."
echo "Current Target ARCS_BASE directory name: $ARCS_BASE_DIR_NAME."
exit 1
}
find_dev_tools() {
local current_dir=$(cd "$(dirname "$0")" && pwd)
local dir_name="$ARCS_DEV_TOOLS_DIR_NAME"
echo "trying to find $dir_name in parent directories..."
while [ "$current_dir" != "/" ]; do
if [ -d "$current_dir/$dir_name" ]; then
echo "Found $dir_name: $current_dir/$dir_name"
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME" ]; then
echo "Found LISTENAI_TOOLS_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
export LISTENAI_TOOLS_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_LISTENAI_TOOLS_DIR_NAME"
fi
if [ -d "$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME" ]; then
echo "Found NUCLEI_TOOLCHAIN_PATH: $current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
export NUCLEI_TOOLCHAIN_PATH="$current_dir/$dir_name/$ARCS_DEV_TOOL_TOOLCHAIN_DIR_NAME"
fi
return 0
fi
current_dir=$(dirname "$current_dir")
done
}
while [[ $# -gt 0 ]]; do
case $1 in
-S|--Source)
PROJECT_PATH="$2"
shift 2
;;
-B|--build)
OUTPUT="$2"
shift 2
;;
-t|--target)
TARGET="$2"
shift 2
;;
-C|--Clean)
CLEAN=true
shift 1
;;
-w|--warnings-as-errors)
WARNINGS_AS_ERRORS=true
shift 1
;;
-h|--help)
usage
;;
-r|--release)
RELEASE=true
shift 1
;;
-D*)
CMAKE_VARS+=("$1")
shift 1
;;
*)
echo "未知参数: $1"
usage
;;
esac
done
echo "Source: $PROJECT_PATH"
echo "Target: $TARGET"
echo "Clean : $CLEAN"
if [ -z "$LISTENAI_TOOLS_PATH" ] || [ -z "$NUCLEI_TOOLCHAIN_PATH" ]; then
find_dev_tools
fi
if [ -z "${LISTENAI_TOOLS_PATH}" ]; then
export LISTENAI_TOOLS_PATH="请添加 LISTENAI_TOOLS_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 LISTENAI_TOOLS_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
if [ -z "${NUCLEI_TOOLCHAIN_PATH}" ]; then
export NUCLEI_TOOLCHAIN_PATH="请添加 NUCLEI_TOOLCHAIN_PATH 环境变量,或在此行设置正确的路径"
echo "请添加 NUCLEI_TOOLCHAIN_PATH 环境变量或者修改脚本后, 注释脚本第 $LINENO";exit 1;
fi
############### 下面代码不用修改 ##################
# 构建工具的位置
CMAKE_PROGRAM="$LISTENAI_TOOLS_PATH/cmake/bin/cmake"
NINJA_PROGRAM="$LISTENAI_TOOLS_PATH/ninja/ninja"
# 配置环境变量 ARCS_BASE
if [ -z "$ARCS_BASE" ]; then
find_arcs_base
fi
if [ "$CLEAN" = true ]; then
rm -rf $OUTPUT
fi
# Initialize CMAKE_VARS array if it doesn't exist
declare -a CMAKE_VARS
# Add warnings-as-errors flag if enabled
if [ "$WARNINGS_AS_ERRORS" = true ]; then
CMAKE_VARS+=("-DCMAKE_C_FLAGS=-Werror")
CMAKE_VARS+=("-DCMAKE_CXX_FLAGS=-Werror")
echo "Treating warnings as errors"
fi
# Add release flags if enabled
if [ "$RELEASE" = true ]; then
CMAKE_VARS+=("-DENABLE_DEBUG_PATH=OFF")
echo "Release mode enabled (-DENABLE_DEBUG_PATH=OFF)"
fi
$CMAKE_PROGRAM -B "$OUTPUT" -G Ninja -S "$PROJECT_PATH" \
-DCMAKE_MAKE_PROGRAM="$NINJA_PROGRAM" \
"${CMAKE_VARS[@]}"
if [ -z "$TARGET" ]; then
$CMAKE_PROGRAM --build "$OUTPUT" -j4
else
$CMAKE_PROGRAM --build "$OUTPUT" --target "$TARGET"
fi

View File

@@ -0,0 +1,165 @@
#ifndef __ARCS_MEMAP_HEADER__
#define __ARCS_MEMAP_HEADER__
#define __KB__(x) ((x) * 1024)
#define __MB__(x) ((x) * 1024 * 1024)
#if CONFIG_ARCS_AP_CORE
#define MEM_ILM_BASE 0x00080000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00100000
#define MEM_DLM_SIZE (__KB__(8))
#else
#define MEM_ILM_BASE 0x00280000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00300000
#define MEM_DLM_SIZE (__KB__(8))
#endif
#define MEM_BTRAM_BASE 0x200C0000
#define MEM_BTRAM_SIZE (__KB__(24))
/* FLASH 分配 */
#define MEM_TOTAL_FLASH_SIZE __MB__(16)
#define MEM_AP_FLASH_BASE 0x30000000
#define MEM_AP_FLASH_SIZE __KB__(1024)
#define MEM_CP_FLASH_BASE (0x30000000 + __MB__(5))
#define MEM_CP_FLASH_SIZE __MB__(8)
/* PSRAM 分配 */
#define MEM_AP_PSRAM_BASE 0x28000000
#define MEM_AP_PSRAM_SIZE __MB__(8)
#define MEM_CP_PSRAM_BASE ((MEM_AP_PSRAM_BASE) + (MEM_AP_PSRAM_SIZE))
#define MEM_CP_PSRAM_SIZE __MB__(8)
#if CONFIG_ARCS_AP_CORE
#define MEM_PSRAM_BASE MEM_AP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_AP_PSRAM_SIZE
#else
#define MEM_PSRAM_BASE MEM_CP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_CP_PSRAM_SIZE
#endif
#if CONFIG_ARCS_AP_CORE
#define MEM_FLASH_BASE MEM_AP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_AP_FLASH_SIZE
#else
#define MEM_FLASH_BASE MEM_CP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_CP_FLASH_SIZE
#endif
#define __MEM_TOTAL_SRAM_START__ 0x20000080 //暂留128(0x80)字节给WiFi硬件
#define __MEM_TOTAL_SRAM_END__ 0x20050000
/**
* SRAM 应用侧分配
* --------------------------------------------
* 0x20000000 | CP-WIFI-RAM(这段内存必须在 0x20000000 + 256KB区间)
* | |
* (256K) | CP-SRAM
* | |
* | | AP-WIFI-RAM (这段内存必须在 0x20000000 + 256KB区间)
* -------------- |
* 0x20040000 | 硬件接在CP核上的SRAM(地址0x20040000总计64K)
* (8K) | IPC共享内存
* (28K) | LUNA CODE & DATA
* -------------- |
* 0x20049000 | 硬件接在AP核上的SRAM(地址0x20050000总计384K)
* (62K) | AP-SRAM
* --------------------------------------------
* 0x20058800 | AP算法使用(HardCode)
* | |
* (350K) | ALGO
* | |
* --------------------------------------------
* 0x200B0000 |
* | |
* (24K) | LUNA 核专属内存
* | |
* --------------------------------------------
* 0x200C0000
*/
/* 独立给算法使用 */
#define MEM_APRAM_BASE 0x20058800
#define MEM_APRAM_SIZE (__KB__(350))
/* 注意,修改以下内存时, 必须与CP保持同步修改 */
#define MEM_CP_WIFI_RAM_BASE __MEM_TOTAL_SRAM_START__
#define MEM_CP_WIFI_RAM_SIZE (__KB__(64) - 128) //暂留128(0x80)字节给WiFi硬件
#define MEM_CP_SRAM_BASE ((MEM_CP_WIFI_RAM_BASE) + (MEM_CP_WIFI_RAM_SIZE))
#define MEM_CP_SRAM_SIZE (__KB__(192))
#define MEM_AP_WIFI_RAM_BASE ((MEM_CP_SRAM_BASE) + (MEM_CP_SRAM_SIZE))
#define MEM_AP_WIFI_RAM_SIZE (__KB__(0))
#define MEM_IPC_BASE ((MEM_AP_WIFI_RAM_BASE) + (MEM_AP_WIFI_RAM_SIZE))
#define MEM_IPC_SIZE (__KB__(8))
#define MEM_LUNA_BASE ((MEM_IPC_BASE) + (MEM_IPC_SIZE))
#define MEM_LUNA_SIZE (__KB__(28))
#define MEM_AP_SRAM_BASE ((MEM_LUNA_BASE) + (MEM_LUNA_SIZE))
#define MEM_AP_SRAM_SIZE (__KB__(62))
#if CONFIG_ARCS_CP_CORE
#define MEM_SRAM_BASE MEM_CP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_CP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_CP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_CP_WIFI_RAM_SIZE
#else
#define MEM_SRAM_BASE MEM_AP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_AP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_AP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_AP_WIFI_RAM_SIZE
#endif
/* 检查下内存是否超出 */
#if (MEM_IPC_BASE + MEM_IPC_SIZE) > __MEM_TOTAL_SRAM_END__
#error "mem sram overflow"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_CP_WIFI_RAM_BASE < 0x20000000) || ((MEM_CP_WIFI_RAM_BASE + MEM_CP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "cp wifi sram error"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_AP_WIFI_RAM_BASE < 0x20000000) || ((MEM_AP_WIFI_RAM_BASE + MEM_AP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "ap wifi sram error"
#endif
/* luna code & instruction & data 必须要在 0x20040000 ~ 0x200B0000 + 448(KB)之间 */
#if (MEM_LUNA_BASE < 0x20040000) || ((MEM_LUNA_BASE + MEM_LUNA_SIZE) > (0x20040000 + __KB__(448)))
#error "ap luna sram error"
#endif
/* 根据实际需要调整 */
#if CONFIG_ARCS_AP_CORE
/* 这个是SRAM HEAP大小的定义, 该heap从SRAM中分配 */
#define SRAM_HEAP_SIZE (__KB__(5))
// /* WIFI校准数据大小 */
// #define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
// #define MEM_WIFI_TRACE_SIZE (__KB__(1))
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
#define MEM_WIFI_LA_DUMP_SIZE (__KB__(32))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#else
/* 在CP侧, 没有wifi的校准数据与跟踪数据, 这里定义为0 */
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(0))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#endif
#endif//__ARCS_MEMAP_HEADER__

View File

@@ -0,0 +1,63 @@
# 使用自定义内存配置文件和自定义链接脚本
CONFIG_MEM_CONFIG=n
CONFIG_MEM_CONFIG_USE_CUSTOM_FILE=y
# CP core
CONFIG_ARCS_CP_CORE=y
# not boot loader
CONFIG_BOOT_HART=n
CONFIG_BOOT=n
# C LIB
CONFIG_LINK_OPTION_NONE_SPECS=y
# HEAP
CONFIG_HEAP_SIZE=0x10000
CONFIG_PSRAM_HEAP_SIZE=0x700000
# CPP
CONFIG_LINK_CPP_RUNTIME_SECTIONS=n
CONFIG_CPP_EXCEPTIONS=n
# Debug
CONFIG_BACK_TRACE=y
# freertos
CONFIG_FREERTOS_STATIC_ALLOCATION_ENABLE=y
# main task
CONFIG_MAIN_TASK_STACK_SIZE=8192
CONFIG_MAIN_TASK_PRIORITY=5
# cJSON
CONFIG_CJSON=y
# IPC
CONFIG_ARCS_HAL_LSF=y
CONFIG_IPC_LSF=y
CONFIG_DISK_MEM=n
# CONFIG_ARCS_HAL_IC_MUTEX=n
# GPDMA
CONFIG_ARCS_GPDMA_FULL_INIT=y
# Audio
CONFIG_LISA_DEVICE=y
CONFIG_LISA_AUDIO_DEVICE=y
CONFIG_LISA_AUDIO_PLAY_ECHO_ENABLE=y
# acomp wakeup
CONFIG_ACOMP=y
CONFIG_ACOMP_WAKEUP=y
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=n
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=1431296
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=763
# watchdog
CONFIG_BOOT_WITH_WATCHDOG=y
CONFIG_WATCHDOG_ENABLE=n
CONFIG_WORK_QUEUE=y

View File

@@ -0,0 +1 @@
{"wrap":{"dev":"4002","trans":1,"param":[{"key":"wakesoffset","val":160},{"key":"wakeeoffset","val":-160}]},"cae":{"dist":30,"bits":16,"chns":{"mic":1,"ref":1},"wkmask":3,"param":[{"key":"mode","val":"0"},{"key":"textdep","val":1}]},"esr":{"param":[{"key":"prewstat","val":5},{"key":"prewthr","val":10000},{"key":"mode","val":1},{"key":"model","val":0},{"key":"1305","val":1},{"key":"1106","val":0},{"key":"2119","val":0},{"key":"1124","val":1},{"key":"1180","val":2},{"key":"1126","val":63},{"key":"1125","val":10},{"key":"1105","val":2},{"key":"1118","val":8},{"key":"1119","val":40},{"key":"1130","val":5},{"key":"1131","val":8},{"key":"1136","val":1}],"switchparam":[{"key":"2118","val_main":[500],"val_cmd":[0]},{"key":"1107","val_main":[2],"val_cmd":[2]}]}}

Binary file not shown.

View File

@@ -0,0 +1,9 @@
tests:
samples.algorithms.wakeup_single:
programmer: arcs
runner: uart
build_only: true
log_analyzer:
type: "regex"
pattern: "相位补偿已设置"

View File

@@ -0,0 +1,88 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "cJSON.h"
#include "acomp_wakeup.h"
#include "wakeup_audio_in.h"
#include "wakeup_audio_out.h"
#define TAG "wakeup"
#include "lisa_log.h"
static int app_algo_keyword_and_kid_extract(const uint8_t *const in)
{
int ret = -1;
int len = strlen(in);
cJSON *root = NULL;
if ((root = cJSON_Parse(in))) {
cJSON *item = cJSON_GetObjectItem(root, "rlt")->child;
uint16_t wkkey = cJSON_GetObjectItem(item, "iresid")->valueint;
uint16_t wkncm = cJSON_GetObjectItem(item, "ncm")->valueint;
char *wkcmd = cJSON_GetObjectItem(item, "keyword")->valuestring;
int wkmain = -1;
if (cJSON_HasObjectItem(item, "bMain")) {
wkmain = cJSON_GetObjectItem(item, "bMain")->valueint;
}
LISA_LOGI(TAG, "WAKE(%d): KEY=%d(%s), NCM=%d, len:%d", wkmain, wkkey, wkcmd, wkncm, len);
cJSON_Delete(root);
} else {
LISA_LOGE(TAG, "JSON:\"%s\"", in);
}
return ret;
}
static void wakeup_event_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
int ret;
if (event & WAKEUP_CB_EVENT_ENGINE_RLT) {
/* 算法识别结果 */
LISA_LOGD(TAG, "wakeup result:%d,%s", event_data_len, (char *)event_data);
app_algo_keyword_and_kid_extract((const uint8_t *)event_data);
} else if (event & WAKEUP_CB_EVENT_ENGINE_TIMEOUT) {
/* 算法唤醒超时 */
LISA_LOGI(TAG, "wakeup timeout");
} else if (event & WAKEUP_CB_EVENT_ENGINE_ANGLE) {
/* 算法识别角度 */
for (int i = 0; i < event_data_len/sizeof(short); i++) {
short angle = *((short*)event_data + i);
LISA_LOGD(TAG, "wakeup angle[%d]: %d", i, angle);
}
} else {
LISA_LOGW(TAG, "unknown wakeup event:0X%X", event);
}
}
int app_wakeup_init(void)
{
int ret = 0;
acomp_wakeup_init();
acomp_wakeup_add_callback(WAKEUP_CB_EVENT_ENGINE_RLT
| WAKEUP_CB_EVENT_ENGINE_TIMEOUT
| WAKEUP_CB_EVENT_ENGINE_ANGLE,
wakeup_event_handler, NULL);
ret = acomp_wakeup_prepare();
LISA_LOGI(TAG, "acomp_wakeup_prepare ret:%d", ret);
ret = acomp_wakeup_start();
LISA_LOGI(TAG, "acomp_wakeup_start ret:%d", ret);
ret = acomp_wakeup_set_algo_mode(ACOMP_WAKEUP_ALGO_MODE_WAKEUP);
LISA_LOGI(TAG, "acomp_wakeup_set_algo_mode ret:%d", ret);
wakeup_audio_out_init();
wakeup_audio_in_init();
return ret;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int app_wakeup_init(void);

View File

@@ -0,0 +1,233 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "workqueue.h"
#include "lisa_device.h"
#include "lisa_audio.h"
#include "acomp_wakeup.h"
#define TAG "wakeup_in"
#include "lisa_log.h"
#define AUDIO_DEVICE_NAME "audio0"
/* 音频参数配置 - 单声道(左声道) */
#define SAMPLE_RATE LISA_AUDIO_RATE_16K
#define SAMPLE_BITS LISA_AUDIO_BIT_16
#define RECORD_CHANNELS LISA_AUDIO_CH_STEREO
#define PLAY_CHANNELS LISA_AUDIO_CH_LEFT
#define BUFFER_COUNT 12
#define BUFFER_SAMPLES ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT
/* Record 增益配置 */
#define RECORD_ANALOG_GAIN 30 /* 16 dB */
#define RECORD_DIGITAL_GAIN 0 /* 8 dB */
/* Play 增益配置 */
#define PLAY_ANALOG_GAIN 0 /* 6 dB */
#define PLAY_DIGITAL_GAIN -18
/* wakeup audio in stream */
#define WAKEUP_AUDIO_MIX1CH_STREAM_CH_INDEX (0)
#define WAKEUP_AUDIO_MIX1CH_STREAM_CH_CNAME "stream.mix2ch"
#define WAKEUP_AUDIO_MIX1CH_STREAM_BUF_SIZE (ACOMP_WAKEUP_AUDIO_INPUT_LEN_ONCE_FRAME)
typedef struct {
short mic0;
short mic1;
}mic_in_t;
typedef struct {
short ref;
}ref_in_t;
typedef struct {
mic_in_t *mic;
ref_in_t *ref;
uint32_t sample_cnt;
}wakeup_in_msg_t;
static workqueue_t *wakeup_in_wq = NULL;
static short zero_echo[BUFFER_SAMPLES] = {0};
static mic_in_t record_zero_echo[BUFFER_SAMPLES] = {0};
static void stream_data_fusion(acomp_wakeup_audio_in_t *algo_buf_in, mic_in_t *adc_data, ref_in_t *ref_data, int sample_cnt)
{
for (int i = 0; i < sample_cnt; i++) {
// 单麦硬回采
algo_buf_in[i].mic0 = adc_data[i].mic0;
algo_buf_in[i].ref0 = adc_data[i].mic1;
}
}
static void wakeup_in_wq_handler(void *para)
{
uint8_t *buffer;
uint32_t buf_size;
uint16_t desc_idx;
wakeup_in_msg_t *msg = (wakeup_in_msg_t*)para;
LISA_LOGD(LOG_TAG, "wakeup_in_wq_handler, sample_cnt: %u", msg->sample_cnt);
buffer = acomp_wakeup_stream_tx_buffer_alloc(WAKEUP_AUDIO_MIX1CH_STREAM_CH_INDEX, &buf_size, &desc_idx);
if(buffer && buf_size > 0){
stream_data_fusion((acomp_wakeup_audio_in_t*)buffer, msg->mic, msg->ref, msg->sample_cnt);
acomp_wakeup_stream_tx_buffer_submit(WAKEUP_AUDIO_MIX1CH_STREAM_CH_INDEX, buffer, buf_size, desc_idx);
}
psram_free(msg);
}
/**
* @brief Unified audio callback handler - 仅收集数据
*/
static void unified_audio_callback(const lisa_audio_event_t *event, void *user_data)
{
bool has_record = (event->record_buffer != NULL);
bool has_echo = (event->echo_buffer != NULL);
if (has_record) {
LISA_LOGD(LOG_TAG, "unified_audio_callback, record_samples: %d", event->record_samples);
}
if (has_echo) {
LISA_LOGD(LOG_TAG, "unified_audio_callback, echo_samples: %d", event->echo_samples);
}
wakeup_in_msg_t *msg = psram_malloc(sizeof(wakeup_in_msg_t));
#if 1
msg->mic = (mic_in_t*)event->record_buffer;
msg->ref = (ref_in_t*)(event->echo_buffer ? event->echo_buffer : zero_echo);
msg->sample_cnt = ACOMP_WAKEUP_AUDIO_INPUT_SAMPLE_CNT;
#else
msg->mic = (mic_in_t*)record_zero_echo;
msg->ref = (ref_in_t*)zero_echo;
msg->sample_cnt = 256;
#endif
workqueue_submit(wakeup_in_wq, wakeup_in_wq_handler, msg);
}
static int audio_device_init(void)
{
int ret = 0;
/* 初始化GPDMAlisa_audio会用到 */
GPDMA_Initialize();
/* 获取 Audio 设备 */
lisa_device_t *audio_dev = lisa_device_get(AUDIO_DEVICE_NAME);
if (!audio_dev) {
LISA_LOGE(LOG_TAG, "获取 Audio 设备失败");
return -1;
}
LISA_LOGI(LOG_TAG, "Audio 设备获取成功");
/* 注册统一回调函数 */
ret = lisa_audio_register_callback(audio_dev, unified_audio_callback, NULL);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "注册统一回调失败: %d", ret);
return -1;
}
LISA_LOGI(LOG_TAG, "统一回调注册成功");
/* 统一配置 Record */
lisa_audio_record_config_t record_config = {
.format = { .sample_rate = SAMPLE_RATE, .channels = RECORD_CHANNELS, .sample_bits = SAMPLE_BITS },
.gain = { .analog_gain = RECORD_ANALOG_GAIN, .digital_gain = RECORD_DIGITAL_GAIN },
.differential_input = true,
.enable_hpf = false,
};
ret = lisa_audio_record_config(audio_dev, &record_config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Record 配置失败: %d", ret);
goto cleanup;
}
/* 统一配置 Play */
lisa_audio_play_config_t play_config = {
.format = { .sample_rate = SAMPLE_RATE, .channels = PLAY_CHANNELS, .sample_bits = SAMPLE_BITS },
.gain = { .analog_gain = PLAY_ANALOG_GAIN, .digital_gain = PLAY_DIGITAL_GAIN },
.buffer_count = BUFFER_COUNT,
.buffer_samples = BUFFER_SAMPLES,
};
ret = lisa_audio_play_config(audio_dev, &play_config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "Play 配置失败: %d", ret);
goto cleanup;
}
/* 统一启动录音和播音 */
ret = lisa_audio_record_start(audio_dev);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "启动录音失败: %d", ret);
goto cleanup;
}
LISA_LOGI(LOG_TAG, "录音已启动");
ret = lisa_audio_play_start(audio_dev);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "启动播音失败: %d", ret);
lisa_audio_record_stop(audio_dev);
goto cleanup;
}
LISA_LOGI(LOG_TAG, "播音已启动");
/* 设置相位补偿 */
lisa_audio_phase_compensation_t phase_comp = {
.record_skip_samples = 10,
.echo_skip_samples = 0,
};
ret = lisa_audio_set_phase_compensation(audio_dev, &phase_comp);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(LOG_TAG, "设置相位补偿失败: %d", ret);
return ret;
}
LISA_LOGI(LOG_TAG, "相位补偿已设置");
return 0;
cleanup:
lisa_audio_record_stop(audio_dev);
lisa_audio_play_stop(audio_dev);
vTaskDelay(pdMS_TO_TICKS(200)); // 等待资源释放
/* 注销统一回调 */
if (audio_dev) {
lisa_audio_unregister_callback(audio_dev, unified_audio_callback);
LISA_LOGI(LOG_TAG, "统一回调已注销");
}
return ret;
}
int wakeup_audio_in_init(void)
{
int ret = 0;
acomp_stream_chn_create_desc_t desc = {
.cname = WAKEUP_AUDIO_MIX1CH_STREAM_CH_CNAME,
.direction = ACOMP_STREAM_DIRECTION_M2R,
.index = WAKEUP_AUDIO_MIX1CH_STREAM_CH_INDEX,
.buffer_size = WAKEUP_AUDIO_MIX1CH_STREAM_BUF_SIZE,
.num_descs = 4,
.kick_policy = 1,
};
acomp_wakeup_stream_ch_enable(WAKEUP_AUDIO_MIX1CH_STREAM_CH_INDEX,&desc);
wakeup_in_wq = workqueue_create("wakeup_in_wq", 9, 10, 8096);
if (wakeup_in_wq == NULL) {
LISA_LOGE(TAG, "Failed to create wakeup_in_wq");
}
audio_device_init();
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int wakeup_audio_in_init(void);

View File

@@ -0,0 +1,108 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "acomp_wakeup.h"
#define TAG "wakeup_out"
#include "lisa_log.h"
#define WAKEUP_AUDIO_OUT_STREAM_CH_INDEX (1)
#define WAKEUP_AUDIO_OUT_STREAM_CH_CNAME "stream.tocloud"
#define WAKEUP_AUDIO_OUT_STREAM (1)
#define WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO (1)
static SemaphoreHandle_t rx_sem;
static void wakeup_stream_handler(uint32_t event, void *event_data, uint32_t event_data_len, void *priv)
{
if (event & WAKEUP_CB_EVENT_STREAM_UPDATE) {
/* 算法音频输出事件 */
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
if (rx_sem != NULL) {
xSemaphoreGive(rx_sem);
}
#endif
}
}
static void wakeup_out_task(void *pvParameters)
{
uint8_t *buffer;
uint32_t len;
uint16_t desc_idx;
int ret;
while (1) {
#ifdef WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
/* 自动触发模式:等待来自回调的信号量 */
xSemaphoreTake(rx_sem, pdMS_TO_TICKS(50));
#else
/* 手动触发模式:等待超时时间 */
vTaskDelay(pdMS_TO_TICKS(5));
#endif
/* 从 R2M 流中获取数据 */
while (1) {
buffer = acomp_wakeup_stream_rx_buffer_get(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, &len, &desc_idx);
if (buffer != NULL && len > 0) {
LISA_LOGD(TAG, "acomp_wakeup_stream_rx_buffer_get ok");
// process_output_audio(buffer, len);
/* 释放缓冲区 */
ret = acomp_wakeup_stream_rx_buffer_release(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX, desc_idx, len, buffer);
if (ret != ACOMP_ERR_OK) {
LISA_LOGW(TAG, "Failed to release buffer: %d", ret);
}
} else {
/* 没有更多数据,跳出内层循环 */
break;
}
}
}
vTaskDelete(NULL);
}
int wakeup_audio_out_init(void)
{
int ret;
acomp_stream_chn_create_desc_t rx_desc = {
.cname = WAKEUP_AUDIO_OUT_STREAM_CH_CNAME,
.direction = ACOMP_STREAM_DIRECTION_R2M,
.index = WAKEUP_AUDIO_OUT_STREAM_CH_INDEX,
.buffer_size = ACOMP_WAKEUP_AUDIO_OUTPUT_MAX_LEN_ONCE_FRAME,
.num_descs = 8,
#if WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
.kick_policy = 1, /* 手动触发 */
#else
.kick_policy = 0, /* 手动触发 */
#endif
};
#if WAKEUP_AUDIO_OUT_STREAM_KICK_AUTO
rx_sem = xSemaphoreCreateBinary();
if (rx_sem == NULL) {
LISA_LOGE(TAG, "Failed to create rx semaphore");
}
#endif
acomp_wakeup_stream_ch_enable(WAKEUP_AUDIO_OUT_STREAM_CH_INDEX,&rx_desc);
acomp_wakeup_add_callback(WAKEUP_CB_EVENT_STREAM_UPDATE, wakeup_stream_handler, NULL);
ret = xTaskCreate(wakeup_out_task, "wakeup_out_task", 4096, NULL, 9, NULL);
if (ret != pdPASS) {
LISA_LOGE(TAG, "Failed to create wakeup_out_task task");
}
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
int wakeup_audio_out_init(void);

View File

@@ -0,0 +1,31 @@
#include "stdio.h"
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "ic_message.h"
#include "acomp.h"
#include "app_wakeup.h"
#define TAG "main"
#include "lisa_log.h"
int main(int argc, char **argv)
{
int ret = 0;
LISA_LOGI(TAG, "CP=======! Hard ID: %d", CONFIG_HARTID);
ic_message_init();
LISA_LOGI(TAG, "ic_message_init done!");
vTaskDelay(pdMS_TO_TICKS(2000));
acomp_init();
app_wakeup_init();
return 0;
}